diff --git a/.github/workflows/enforce-pr-labels.yml b/.github/workflows/enforce-pr-labels.yml
new file mode 100644
index 0000000000000..2d55965dc0bae
--- /dev/null
+++ b/.github/workflows/enforce-pr-labels.yml
@@ -0,0 +1,18 @@
+name: Enforce labels on Pull Request
+on:
+ pull_request_target:
+ types: [opened, labeled, unlabeled, synchronize]
+jobs:
+ type-related-labels:
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ steps:
+ - uses: mheap/github-action-required-labels@v5
+ with:
+ mode: exactly
+ count: 1
+ labels: '[Type] Accessibility (a11y), [Type] Automated Testing, [Type] Breaking Change, [Type] Bug, [Type] Build Tooling, [Type] Code Quality, [Type] Copy, [Type] Developer Documentation, [Type] Enhancement, [Type] Experimental, [Type] Feature, [Type] New API, [Type] Task, [Type] Performance, [Type] Project Management, [Type] Security, [Type] WP Core Ticket'
+ add_comment: true
+ message: "## ⚠️ Type of PR label error\n To merge this PR, it requires {{ errorString }} {{ count }} label indicating the type of PR. Other labels are optional and not being checked here. \n- **Type-related labels to choose from**: {{ provided }}.\n- **Labels found**: {{ applied }}."
+ exit_type: success
diff --git a/bin/plugin/commands/changelog.js b/bin/plugin/commands/changelog.js
index 69be77ac66991..c9e6d039596c5 100644
--- a/bin/plugin/commands/changelog.js
+++ b/bin/plugin/commands/changelog.js
@@ -126,10 +126,6 @@ const LABEL_FEATURE_MAPPING = {
'REST API Interaction': 'REST API',
'New Block': 'Block Library',
'Accessibility (a11y)': 'Accessibility',
- '[a11y] Color Contrast': 'Accessibility',
- '[a11y] Keyboard & Focus': 'Accessibility',
- '[a11y] Labelling': 'Accessibility',
- '[a11y] Zooming': 'Accessibility',
'[Package] E2E Tests': 'Testing',
'[Package] E2E Test Utils': 'Testing',
'Automated Testing': 'Testing',
diff --git a/bin/plugin/commands/test/fixtures/pull-requests.json b/bin/plugin/commands/test/fixtures/pull-requests.json
index 34f3c06c5d854..eccb2257a2c1f 100644
--- a/bin/plugin/commands/test/fixtures/pull-requests.json
+++ b/bin/plugin/commands/test/fixtures/pull-requests.json
@@ -5747,13 +5747,13 @@
"description": "/packages/components"
},
{
- "id": 1344464662,
- "node_id": "MDU6TGFiZWwxMzQ0NDY0NjYy",
- "url": "https://api.github.com/repos/WordPress/gutenberg/labels/[a11y]%20Keyboard%20&%20Focus",
- "name": "[a11y] Keyboard & Focus",
- "color": "efde5d",
+ "id": 546517042,
+ "node_id": "MDU6TGFiZWw1NDY1MTcwNDI=",
+ "url": "https://api.github.com/repos/WordPress/gutenberg/labels/Accessibility%20(a11y)",
+ "name": "Accessibility (a11y)",
+ "color": "655104",
"default": false,
- "description": ""
+ "description": "Changes that impact accessibility and need corresponding review (e.g. markup changes)."
}
],
"state": "closed",
diff --git a/changelog.txt b/changelog.txt
index ede5cff91e4ed..ff4942d8e06a1 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,15 +1,13 @@
== Changelog ==
-= 16.3.0-rc.1 =
-
-
+= 16.3.0 =
## Changelog
### Enhancements
#### Site Editor
-- Edit Site: Add delay and fade-in animation to loading spinner. ([51902](https://github.com/WordPress/gutenberg/pull/51902))
+- Add delay and fade-in animation to loading spinner. ([51902](https://github.com/WordPress/gutenberg/pull/51902))
- Make "My patterns" category permanently visible. ([52531](https://github.com/WordPress/gutenberg/pull/52531))
- Remove "Theme patterns" heading in Pattern library. ([52570](https://github.com/WordPress/gutenberg/pull/52570))
- Remove sidebar group descriptions. ([52453](https://github.com/WordPress/gutenberg/pull/52453))
@@ -17,27 +15,27 @@
- Swap pattern creation options. ([52726](https://github.com/WordPress/gutenberg/pull/52726))
- Update Dashboard button tooltips in the site editor. ([52465](https://github.com/WordPress/gutenberg/pull/52465))
- Update Site Editor frame z-index. ([52180](https://github.com/WordPress/gutenberg/pull/52180))
-- Update descriptions in Pattern library. ([52468](https://github.com/WordPress/gutenberg/pull/52468))
- Update locked pattern tooltips. ([52497](https://github.com/WordPress/gutenberg/pull/52497))
- Update navigation menu title size & weight in detail panels. ([52477](https://github.com/WordPress/gutenberg/pull/52477))
- Update pattern library copy. ([52340](https://github.com/WordPress/gutenberg/pull/52340))
-- Update: Show more intuitive archive titles on Query Title block. ([52521](https://github.com/WordPress/gutenberg/pull/52521))
+- Show more intuitive archive titles on Query Title block. ([52521](https://github.com/WordPress/gutenberg/pull/52521))
+- Adapt template part hint copy. ([52527](https://github.com/WordPress/gutenberg/pull/52527))
#### Patterns
- Add hint to show template part move. ([52395](https://github.com/WordPress/gutenberg/pull/52395))
- Add renaming, duplication, and deletion options. ([52270](https://github.com/WordPress/gutenberg/pull/52270))
- Add sync tooltip. ([52458](https://github.com/WordPress/gutenberg/pull/52458))
- Display all custom template part areas in sidebar nav. ([52355](https://github.com/WordPress/gutenberg/pull/52355))
-- Don't override the rootClientID in create menu - only set if undefined. ([52713](https://github.com/WordPress/gutenberg/pull/52713))
+- Don't override the rootClientID in create menu - only set if undefined. ([52713](https://github.com/WordPress/gutenberg/pull/52713))
- Enable focus mode editing. ([52427](https://github.com/WordPress/gutenberg/pull/52427))
- Remove `reusable` text from menu once rename hint has been dismissed. ([52664](https://github.com/WordPress/gutenberg/pull/52664))
- Stop endless snackbars appearing. ([52012](https://github.com/WordPress/gutenberg/pull/52012))
-- Try: Sticky header and pagination on Patterns page. ([52663](https://github.com/WordPress/gutenberg/pull/52663))
+- Sticky header and pagination on Patterns page. ([52663](https://github.com/WordPress/gutenberg/pull/52663))
- Update manage pattern links to go to site editor if available. ([52403](https://github.com/WordPress/gutenberg/pull/52403))
-- [Patterns] Separate sync status into a filter control. ([52303](https://github.com/WordPress/gutenberg/pull/52303))
+- Separate sync status into a filter control. ([52303](https://github.com/WordPress/gutenberg/pull/52303))
#### Components
-- Adapt template part hint copy. ([52527](https://github.com/WordPress/gutenberg/pull/52527))
+
- Adding support for defined IDs in `TextControl` component. ([52028](https://github.com/WordPress/gutenberg/pull/52028))
- Updated "position" default value. ([52148](https://github.com/WordPress/gutenberg/pull/52148))
@@ -48,30 +46,17 @@
#### Block Library
- Add back old Navigation and File blocks JavaScript implementation when Gutenberg is not installed. ([52553](https://github.com/WordPress/gutenberg/pull/52553))
- Home link block: Add 'current-menu-item'. ([51478](https://github.com/WordPress/gutenberg/pull/51478))
+- Use next40pxDefaultSize on RangeControl components. ([52257](https://github.com/WordPress/gutenberg/pull/52257))
#### Block Editor
- Add maxLength to LinkControl search item URLs. ([52523](https://github.com/WordPress/gutenberg/pull/52523))
-- i18n: Make the tab labels of `ColorGradientSettingsDropdown` component translatable. ([52669](https://github.com/WordPress/gutenberg/pull/52669))
+- Make the tab labels of `ColorGradientSettingsDropdown` component translatable. ([52669](https://github.com/WordPress/gutenberg/pull/52669))
+- Add support for arrays to `setImmutably` util. ([52280](https://github.com/WordPress/gutenberg/pull/52280))
+- Stabilize `defaultBlock`, `directInsert` API's and `getDirectInsertBlock` selector. ([52083](https://github.com/WordPress/gutenberg/pull/52083))
#### NUX
- Restore `@wordpress/nux` to trunk. ([52455](https://github.com/WordPress/gutenberg/pull/52455))
-#### Block API
-- Block Editor: Add support for arrays to `setImmutably` util. ([52280](https://github.com/WordPress/gutenberg/pull/52280))
-
-#### Inspector Controls
-- Use next40pxDefaultSize on RangeControl components. ([52257](https://github.com/WordPress/gutenberg/pull/52257))
-
-#### Fonts API
-- Font Face: To generate and print font-face styles for theme.json fonts. ([51770](https://github.com/WordPress/gutenberg/pull/51770))
-
-
-### New APIs
-
-#### Nested / Inner Blocks
-- Stabilize `defaultBlock`, `directInsert` API's and `getDirectInsertBlock` selector. ([52083](https://github.com/WordPress/gutenberg/pull/52083))
-
-
### Bug Fixes
#### Patterns
@@ -84,27 +69,29 @@
- Ensure that the unsaved title is not persisted when reopening the modal. ([52473](https://github.com/WordPress/gutenberg/pull/52473))
- Fix bug with Create Patterns menu not showing in site editor page editing. ([52671](https://github.com/WordPress/gutenberg/pull/52671))
- Fix renaming in Site View sidebar rename saves all edits for Template Parts and Navigation Menus. ([52373](https://github.com/WordPress/gutenberg/pull/52373))
-- Fix: Patterns & template parts: Remove "apply globally" option from block settings. ([52160](https://github.com/WordPress/gutenberg/pull/52160))
+- Patterns & template parts: Remove "apply globally" option from block settings. ([52160](https://github.com/WordPress/gutenberg/pull/52160))
- Rename edit label to Edit Block Pattern to resolve edge case in Chrome. ([52496](https://github.com/WordPress/gutenberg/pull/52496))
- Show uncategorized patterns on the Editor > Patterns page. ([52633](https://github.com/WordPress/gutenberg/pull/52633))
- Site Editor Patterns: Filter out patterns that are not available in the inserter. ([52675](https://github.com/WordPress/gutenberg/pull/52675))
- Update the title of Pattern block in the block inspector card. ([52010](https://github.com/WordPress/gutenberg/pull/52010))
#### Site Editor
-- Edit Site: Fix the pattern with the post types becomes the placeholder pattern when editing template part. ([52503](https://github.com/WordPress/gutenberg/pull/52503))
+- Fix the pattern with the post types becomes the placeholder pattern when editing template part. ([52503](https://github.com/WordPress/gutenberg/pull/52503))
- Fix "Manage all patterns" link appearance. ([52532](https://github.com/WordPress/gutenberg/pull/52532))
- Fix document title icon appearance. ([52424](https://github.com/WordPress/gutenberg/pull/52424))
- Fix entering edit mode in site editor. ([52406](https://github.com/WordPress/gutenberg/pull/52406))
-- Fix missing Add Template Part button in Template Parts page. ([52542](https://github.com/WordPress/gutenberg/pull/52542))
+- Fix missing "Add Template Part" button in Template Parts page. ([52542](https://github.com/WordPress/gutenberg/pull/52542))
- Fix undo/redo in site editor code editor's mode. ([52695](https://github.com/WordPress/gutenberg/pull/52695))
- Remove status icon. ([52457](https://github.com/WordPress/gutenberg/pull/52457))
- Reset device preview type when exiting the editing mode. ([52566](https://github.com/WordPress/gutenberg/pull/52566))
- ResizableFrame: Fix styling in Firefox. ([52700](https://github.com/WordPress/gutenberg/pull/52700))
- Site Editor Pages: Load the appropriate template if posts page set. ([52266](https://github.com/WordPress/gutenberg/pull/52266))
- Site Editor Patterns: Ensure sidebar does not shrink when long pattern titles are used. ([52547](https://github.com/WordPress/gutenberg/pull/52547))
-- Use lowercase p in in "Manage Patterns". ([52617](https://github.com/WordPress/gutenberg/pull/52617))
+- Use lowercase p in "Manage Patterns". ([52617](https://github.com/WordPress/gutenberg/pull/52617))
- Do not navigate to the styles pages unless you're in a random listing page. ([52728](https://github.com/WordPress/gutenberg/pull/52728))
- Fix multiple navigation blocks in pattern template. ([52707](https://github.com/WordPress/gutenberg/pull/52707))
+- Don't allow creating template part on the Patterns page for non-block themes. ([52656](https://github.com/WordPress/gutenberg/pull/52656))
+- Exit template focus when opening the W menu. ([52235](https://github.com/WordPress/gutenberg/pull/52235))
#### Block Library
- Fix console warning by improving error handling in Nav block classic menu conversion. ([52591](https://github.com/WordPress/gutenberg/pull/52591))
@@ -117,13 +104,16 @@
- Rich Text/Footnotes: Fix getRichTextValues for useInnerBlocksProps.save. ([52682](https://github.com/WordPress/gutenberg/pull/52682))
- Search block: Enqueue view script through block.json. ([52552](https://github.com/WordPress/gutenberg/pull/52552))
- Use `_get_block_template_file` function and set $area variable. ([52708](https://github.com/WordPress/gutenberg/pull/52708))
+- Cover Block: Fix block deprecation when fixed background is enabled. ([51612](https://github.com/WordPress/gutenberg/pull/51612))
+- Fix image block v6 deprecation. ([52822](https://github.com/WordPress/gutenberg/pull/52822))
+- Image: Use the correct method for caption class in recent deprecation. ([52853](https://github.com/WordPress/gutenberg/pull/52853))
#### Accessibility
-- Change Delete page menu item to Move to trash. ([52641](https://github.com/WordPress/gutenberg/pull/52641))
+- Change "Delete page" menu item to "Move to trash". ([52641](https://github.com/WordPress/gutenberg/pull/52641))
- Change password input to type text so contents are visible. ([52622](https://github.com/WordPress/gutenberg/pull/52622))
- Do not autofocus page title field in the 'Draft a new page' modal dialog. ([52603](https://github.com/WordPress/gutenberg/pull/52603))
- Fix Shift+Tab to Block Toolbar. ([52613](https://github.com/WordPress/gutenberg/pull/52613))
-- Item: Unify focus style and add default font styles. ([52495](https://github.com/WordPress/gutenberg/pull/52495))
+- Unify focus style and add default font styles. ([52495](https://github.com/WordPress/gutenberg/pull/52495))
- Navigation block: Add notice on reduced accessibility. ([52251](https://github.com/WordPress/gutenberg/pull/52251))
- Password protected field: Remove autofocus and improve placeholder text consistency. ([52634](https://github.com/WordPress/gutenberg/pull/52634))
- ResizableFrame: Make keyboard accessible. ([52443](https://github.com/WordPress/gutenberg/pull/52443))
@@ -133,12 +123,12 @@
- Add 'reusable' keyword to Pattern blocks. ([52543](https://github.com/WordPress/gutenberg/pull/52543))
- Avoid errors in Dimension visualizers when switching between iframed and non-iframed editors. ([52588](https://github.com/WordPress/gutenberg/pull/52588))
- Ensure synced patterns are accounted for in 'getAllowedBlocks'. ([52546](https://github.com/WordPress/gutenberg/pull/52546))
-- Fix: Remove link action of Link UI for draft pages created from Nav block does not correctly remove link. ([52415](https://github.com/WordPress/gutenberg/pull/52415))
+- Remove link action of Link UI for draft pages created from Nav block does not correctly remove link. ([52415](https://github.com/WordPress/gutenberg/pull/52415))
- LinkControl: Add width to ensure ellipsis truncating works. ([52575](https://github.com/WordPress/gutenberg/pull/52575))
- LinkControl: Fix mark highlight to bold. ([52517](https://github.com/WordPress/gutenberg/pull/52517))
- Post Content link color should not be applied to placeholder component links. ([52367](https://github.com/WordPress/gutenberg/pull/52367))
- Fix highlight change when using transform menu. ([52752](https://github.com/WordPress/gutenberg/pull/52752))
-- Fix: Apply text color selection to link color. ([52379](https://github.com/WordPress/gutenberg/pull/52379))
+- Apply text color selection to link color. ([52379](https://github.com/WordPress/gutenberg/pull/52379))
#### Components
- Block Editor: Display variation icon in the 'BlockDraggable' component. ([52502](https://github.com/WordPress/gutenberg/pull/52502))
@@ -149,7 +139,7 @@
- Top Toolbar: Move the preferences selection into the main useSelect. ([52332](https://github.com/WordPress/gutenberg/pull/52332))
#### Post Editor
-- Editor: Remove a block select button from the multi-entity saving flow. ([52753](https://github.com/WordPress/gutenberg/pull/52753))
+- Remove a block select button from the multi-entity saving flow. ([52753](https://github.com/WordPress/gutenberg/pull/52753))
- Fix Site editor page when JS support is disabled. ([52376](https://github.com/WordPress/gutenberg/pull/52376))
- Fix initial block parsing. ([52417](https://github.com/WordPress/gutenberg/pull/52417))
- Simplify the code editor of edit-post. ([52751](https://github.com/WordPress/gutenberg/pull/52751))
@@ -161,7 +151,6 @@
#### Themes
- Fix admin_url() for preview link of block themes. ([52399](https://github.com/WordPress/gutenberg/pull/52399))
-- Site Editor: Don't allow creating template part on the Patterns page for non-block themes. ([52656](https://github.com/WordPress/gutenberg/pull/52656))
#### Fonts API
- Deprecate and make Fonts API non-functional. ([52485](https://github.com/WordPress/gutenberg/pull/52485))
@@ -170,13 +159,6 @@
#### Extensibility
- Page Content Focus: Ignore page content within a Query Loop block. ([52351](https://github.com/WordPress/gutenberg/pull/52351))
-#### Page Content Focus
-- Exit template focus when opening the W menu. ([52235](https://github.com/WordPress/gutenberg/pull/52235))
-
-#### Block Validation/Deprecation
-- Cover Block: Fix block deprecation when fixed background is enabled. ([51612](https://github.com/WordPress/gutenberg/pull/51612))
-
-
### Performance
#### Post Editor
@@ -194,16 +176,17 @@
### Experiments
-#### Project Management
-- Github workflow: Add a PHP backport changes action. ([52096](https://github.com/WordPress/gutenberg/pull/52096))
-
#### Interactivity API
- Prevent scripts from loading if behaviors are not used. ([52140](https://github.com/WordPress/gutenberg/pull/52140))
+#### Fonts API
+- Font Face: To generate and print font-face styles for theme.json fonts. ([51770](https://github.com/WordPress/gutenberg/pull/51770))
+
+
### Documentation
-- (readme.md) Document the new process for releasing point releases for old release branches. ([49968](https://github.com/WordPress/gutenberg/pull/49968))
+- Document the new process for releasing point releases for old release branches. ([49968](https://github.com/WordPress/gutenberg/pull/49968))
- Add layout API documentation. ([52673](https://github.com/WordPress/gutenberg/pull/52673))
- Added README for the "caption" component. ([52033](https://github.com/WordPress/gutenberg/pull/52033))
- Added documentation text-transform component #52072. ([52243](https://github.com/WordPress/gutenberg/pull/52243))
@@ -216,9 +199,8 @@
### Code Quality
+#### Block Editor
- Add missing `@emotion/react` dep to block-editor. ([52475](https://github.com/WordPress/gutenberg/pull/52475))
-- Code Data: Fix ESLint warning for 'useEntityProp' hook. ([52757](https://github.com/WordPress/gutenberg/pull/52757))
-- Lodash: Deprecate `_.set()`. ([52407](https://github.com/WordPress/gutenberg/pull/52407))
- Lodash: Remove remaining `_.get()` from block editor and deprecate. ([52561](https://github.com/WordPress/gutenberg/pull/52561))
- Make use of accessing private APIs from thunks directly. ([52214](https://github.com/WordPress/gutenberg/pull/52214))
@@ -238,7 +220,7 @@
#### Post Editor
- EntityRecordItem: Fix ESLint warnings and remove unnecessary memoization. ([52630](https://github.com/WordPress/gutenberg/pull/52630))
- PostPreviewButton: Rewrite to functional, avoid state transitions in lifecycles. ([44971](https://github.com/WordPress/gutenberg/pull/44971))
-- correct a typo: Sapce -> space. ([52578](https://github.com/WordPress/gutenberg/pull/52578))
+- Correct a typo: Sapce -> space. ([52578](https://github.com/WordPress/gutenberg/pull/52578))
#### Site Editor
- Fix incorrect 'useSelect' usage. ([52683](https://github.com/WordPress/gutenberg/pull/52683))
@@ -247,6 +229,10 @@
#### Reusable Blocks
- Update package to use relative path. ([52712](https://github.com/WordPress/gutenberg/pull/52712))
+#### Core Data
+- Core Data: Fix ESLint warning for 'useEntityProp' hook. ([52757](https://github.com/WordPress/gutenberg/pull/52757))
+
+
### Tools
@@ -261,21 +247,20 @@
- Try fixing block context end-to-end test failure. ([52513](https://github.com/WordPress/gutenberg/pull/52513))
- Use posts instead of template parts for navigation color tests. ([52654](https://github.com/WordPress/gutenberg/pull/52654))
- end-to-end Test Utils: Improve test reliability in plugins/themes and login procedures. ([52144](https://github.com/WordPress/gutenberg/pull/52144))
-- test: Enable jest-watch-typeahead for native tests. ([51869](https://github.com/WordPress/gutenberg/pull/51869))
-- test: Expand mobile editor tests. ([52446](https://github.com/WordPress/gutenberg/pull/52446))
+- Enable jest-watch-typeahead for native tests. ([51869](https://github.com/WordPress/gutenberg/pull/51869))
+- Expand mobile editor tests. ([52446](https://github.com/WordPress/gutenberg/pull/52446))
#### Build Tooling
- Backport tools: Sort PRs to be cherry picked by merged/closed date. ([52667](https://github.com/WordPress/gutenberg/pull/52667))
- Create block interactive template. ([52612](https://github.com/WordPress/gutenberg/pull/52612))
- Fix Webpack to watch the `interactivity` package files. ([52642](https://github.com/WordPress/gutenberg/pull/52642))
- Update caniuse-lite, browserslist and core-js. ([52420](https://github.com/WordPress/gutenberg/pull/52420))
+- Lodash: Deprecate `_.set()`. ([52407](https://github.com/WordPress/gutenberg/pull/52407))
#### Project Management
- Update issue gardening automation with new label. ([52173](https://github.com/WordPress/gutenberg/pull/52173))
-
-### Various
-
- Revert "Update Changelog for 16.1.2". ([52433](https://github.com/WordPress/gutenberg/pull/52433))
+- Github workflow: Add a PHP backport changes action. ([52096](https://github.com/WordPress/gutenberg/pull/52096))
## First time contributors
@@ -294,6 +279,8 @@ The following contributors merged PRs in this release:
@aaronrobertshaw @afercia @andrewhayward @andrewserong @anomiex @arthur791004 @BenjaminZekavica @bfintal @carolinan @Clorith @dcalhoun @derekblank @diegohaz @draganescu @ellatrix @fluiddot @fullofcaffeine @geriux @getdave @ghorivipul97 @glendaviesnz @hellofromtonya @jameskoster @jeryj @jorgefilipecosta @jsnajdr @juanmaguitar @kevin940726 @luisherranz @MaggieCabrera @Mamaduka @michalczaplinski @mirka @noisysocks @ntsekouras @peterwilsoncc @pooja-muchandikar @Presskopp @priethor @ramonjd @richtabor @SantosGuillamot @SavPhill @SaxonF @scruffian @sethrubenstein @spacedmonkey @swissspidy @t-hamano @tellthemachines @tyxla @walbo @westonruter @youknowriad
+
+
= 16.2.1 =
## Changelog
diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md
index d0a6ca7d356b2..afc50e46ad2d5 100644
--- a/docs/reference-guides/core-blocks.md
+++ b/docs/reference-guides/core-blocks.md
@@ -275,7 +275,7 @@ Add a link to a downloadable file. ([Source](https://github.com/WordPress/gutenb
- **Name:** core/footnotes
- **Category:** text
-- **Supports:** ~~html~~, ~~multiple~~, ~~reusable~~
+- **Supports:** color (background, link, text), ~~html~~, ~~multiple~~, ~~reusable~~
- **Attributes:**
## Classic
@@ -646,7 +646,7 @@ Add text that respects your spacing and tabs, and also allows styling. ([Source]
- **Name:** core/preformatted
- **Category:** text
-- **Supports:** anchor, color (background, gradients, text), typography (fontSize, lineHeight)
+- **Supports:** anchor, color (background, gradients, text), spacing (margin, padding), typography (fontSize, lineHeight)
- **Attributes:** content
## Pullquote
diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md
index a66c0991e3d27..f2bc3374f9e72 100644
--- a/docs/reference-guides/data/data-core.md
+++ b/docs/reference-guides/data/data-core.md
@@ -136,7 +136,7 @@ _Parameters_
_Returns_
-- `Object | null`: The current global styles.
+- `Array< object > | null`: The current global styles.
### getCurrentUser
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 a2e88320cf58b..da08b4fe9eac1 100644
--- a/docs/reference-guides/theme-json-reference/theme-json-living.md
+++ b/docs/reference-guides/theme-json-reference/theme-json-living.md
@@ -171,6 +171,16 @@ Settings related to typography.
Generate custom CSS custom properties of the form `--wp--custom--{key}--{nested-key}: {value};`. `camelCased` keys are transformed to `kebab-case` as to follow the CSS property naming schema. Keys at different depth levels are separated by `--`, so keys should not include `--` in the name.
+---
+
+### behaviors
+
+Settings related to behaviors.
+
+| Property | Type | Default | Props |
+| --- | --- | --- |--- |
+| lightbox | boolean | false | |
+
---
## Styles
diff --git a/gutenberg.php b/gutenberg.php
index cb3300bfae0b4..980b2304ac796 100644
--- a/gutenberg.php
+++ b/gutenberg.php
@@ -5,7 +5,7 @@
* Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality.
* Requires at least: 6.1
* Requires PHP: 5.6
- * Version: 16.3.0-rc.1
+ * Version: 16.3.0
* Author: Gutenberg Team
* Text Domain: gutenberg
*
diff --git a/lib/block-supports/behaviors.php b/lib/block-supports/behaviors.php
index a18700a588410..95639178c2de4 100644
--- a/lib/block-supports/behaviors.php
+++ b/lib/block-supports/behaviors.php
@@ -44,7 +44,6 @@ function gutenberg_register_behaviors_support( $block_type ) {
* @return string Filtered block content.
*/
function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) {
- $experiments = get_option( 'gutenberg-experiments' );
$link_destination = isset( $block['attrs']['linkDestination'] ) ? $block['attrs']['linkDestination'] : 'none';
// Get the lightbox setting from the block attributes.
if ( isset( $block['attrs']['behaviors']['lightbox'] ) ) {
@@ -63,7 +62,7 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) {
return $block_content;
}
- if ( ! $lightbox_settings || 'none' !== $link_destination || empty( $experiments['gutenberg-interactivity-api-core-blocks'] ) ) {
+ if ( ! $lightbox_settings || 'none' !== $link_destination ) {
return $block_content;
}
diff --git a/lib/blocks.php b/lib/blocks.php
index e98f711b5c85a..f160d2a7080d3 100644
--- a/lib/blocks.php
+++ b/lib/blocks.php
@@ -172,6 +172,40 @@ function gutenberg_reregister_core_block_types() {
add_action( 'init', 'gutenberg_reregister_core_block_types' );
+/**
+ * Adds the defer loading strategy to all registered blocks.
+ *
+ * This function would not be part of core merge. Instead, the register_block_script_handle() function would be patched
+ * as follows.
+ *
+ * ```
+ * --- a/wp-includes/blocks.php
+ * +++ b/wp-includes/blocks.php
+ * @ @ -153,7 +153,8 @ @ function register_block_script_handle( $metadata, $field_name, $index = 0 ) {
+ * $script_handle,
+ * $script_uri,
+ * $script_dependencies,
+ * - isset( $script_asset['version'] ) ? $script_asset['version'] : false
+ * + isset( $script_asset['version'] ) ? $script_asset['version'] : false,
+ * + array( 'strategy' => 'defer' )
+ * );
+ * if ( ! $result ) {
+ * return false;
+ * ```
+ *
+ * @see register_block_script_handle()
+ */
+function gutenberg_defer_block_view_scripts() {
+ $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();
+ foreach ( $block_types as $block_type ) {
+ foreach ( $block_type->view_script_handles as $view_script_handle ) {
+ wp_script_add_data( $view_script_handle, 'strategy', 'defer' );
+ }
+ }
+}
+
+add_action( 'init', 'gutenberg_defer_block_view_scripts', 100 );
+
/**
* Deregisters the existing core block type and its assets.
*
diff --git a/lib/compat/wordpress-6.3/navigation-fallback.php b/lib/compat/wordpress-6.3/navigation-fallback.php
index 5619e3204627a..5cc84f4a1c848 100644
--- a/lib/compat/wordpress-6.3/navigation-fallback.php
+++ b/lib/compat/wordpress-6.3/navigation-fallback.php
@@ -13,28 +13,36 @@
* Navigation Fallback REST endpoint.
*
* The endpoint may embed the full Navigation Menu object into the
- * response as the `self` link. By default the Posts Controller
- * will only exposes a limited subset of fields but the editor requires
- * additional fields to be available in order to utilise the menu.
+ * response as the `self` link. By default, the Posts Controller
+ * will only expose a limited subset of fields but the editor requires
+ * additional fields to be available in order to utilize the menu.
*
* @param array $schema the schema for the `wp_navigation` post.
* @return array the modified schema.
*/
-function gutenberg_add_fields_to_navigation_fallback_embeded_links( $schema ) {
+function gutenberg_add_fields_to_navigation_fallback_embedded_links( $schema ) {
// Expose top level fields.
$schema['properties']['status']['context'] = array_merge( $schema['properties']['status']['context'], array( 'embed' ) );
$schema['properties']['content']['context'] = array_merge( $schema['properties']['content']['context'], array( 'embed' ) );
- // Expose sub properties of content field.
- // These aren't exposed by the posts controller by default, see:
- // https://github.com/WordPress/wordpress-develop/blob/5c3c6258e468c67ba00bbd13db29994f1a57a52a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php#L2425.
+ /*
+ * Exposes sub properties of content field.
+ * These sub properties aren't exposed by the posts controller by default,
+ * for requests where context is `embed`.
+ *
+ * @see WP_REST_Posts_Controller::get_item_schema()
+ */
$schema['properties']['content']['properties']['raw']['context'] = array_merge( $schema['properties']['content']['properties']['raw']['context'], array( 'embed' ) );
$schema['properties']['content']['properties']['rendered']['context'] = array_merge( $schema['properties']['content']['properties']['rendered']['context'], array( 'embed' ) );
$schema['properties']['content']['properties']['block_version']['context'] = array_merge( $schema['properties']['content']['properties']['block_version']['context'], array( 'embed' ) );
- // Expose sub properties of title field.
- // These aren't exposed by the posts controller by default, see:
- // https://github.com/WordPress/wordpress-develop/blob/5c3c6258e468c67ba00bbd13db29994f1a57a52a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php#L2401.
+ /*
+ * Exposes sub properties of title field.
+ * These sub properties aren't exposed by the posts controller by default,
+ * for requests where context is `embed`.
+ *
+ * @see WP_REST_Posts_Controller::get_item_schema()
+ */
$schema['properties']['title']['properties']['raw']['context'] = array_merge( $schema['properties']['title']['properties']['raw']['context'], array( 'embed' ) );
return $schema;
@@ -42,5 +50,5 @@ function gutenberg_add_fields_to_navigation_fallback_embeded_links( $schema ) {
add_filter(
'rest_wp_navigation_item_schema',
- 'gutenberg_add_fields_to_navigation_fallback_embeded_links'
+ 'gutenberg_add_fields_to_navigation_fallback_embedded_links'
);
diff --git a/lib/compat/wordpress-6.3/rest-api.php b/lib/compat/wordpress-6.3/rest-api.php
index ecb8f52392fef..d7fa31cd33fe6 100644
--- a/lib/compat/wordpress-6.3/rest-api.php
+++ b/lib/compat/wordpress-6.3/rest-api.php
@@ -85,15 +85,18 @@ function add_modified_wp_template_schema() {
}
add_filter( 'rest_api_init', 'add_modified_wp_template_schema' );
-/**
- * Registers the block patterns REST API routes.
- */
-function gutenberg_register_rest_block_patterns() {
- $block_patterns = new Gutenberg_REST_Block_Patterns_Controller_6_3();
- $block_patterns->register_routes();
+// If the Auto-inserting Blocks experiment is enabled, we load the block patterns
+// controller in lib/experimental/rest-api.php instead.
+if ( ! gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) {
+ /**
+ * Registers the block patterns REST API routes.
+ */
+ function gutenberg_register_rest_block_patterns() {
+ $block_patterns = new Gutenberg_REST_Block_Patterns_Controller_6_3();
+ $block_patterns->register_routes();
+ }
+ add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' );
}
-add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' );
-
/**
* Registers the Navigation Fallbacks REST API routes.
diff --git a/lib/experimental/auto-inserting-blocks.php b/lib/experimental/auto-inserting-blocks.php
new file mode 100644
index 0000000000000..9df94fcbfcd8f
--- /dev/null
+++ b/lib/experimental/auto-inserting-blocks.php
@@ -0,0 +1,246 @@
+ 0 ) {
+ if ( ! is_string( $block['innerContent'][ $chunk_index ] ) ) {
+ $anchor_block_index--;
+ }
+ $chunk_index++;
+ }
+ // Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`)
+ // when rendering blocks, we also need to insert a value (`null`, to mark a block
+ // location) into that array.
+ array_splice( $block['innerContent'], $chunk_index, 0, array( null ) );
+ }
+ return $block;
+ };
+}
+
+/**
+ * Register blocks for auto-insertion, based on their block.json metadata.
+ *
+ * @param array $settings Array of determined settings for registering a block type.
+ * @param array $metadata Metadata provided for registering a block type.
+ * @return array Updated settings array.
+ */
+function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) {
+ if ( ! isset( $metadata['__experimentalAutoInsert'] ) ) {
+ return $settings;
+ }
+ $auto_insert = $metadata['__experimentalAutoInsert'];
+
+ /**
+ * Map the camelCased position string from block.json to the snake_cased block type position
+ * used in the auto-inserting block registration function.
+ *
+ * @var array
+ */
+ $property_mappings = array(
+ 'before' => 'before',
+ 'after' => 'after',
+ 'firstChild' => 'first_child',
+ 'lastChild' => 'last_child',
+ );
+
+ $inserted_block_name = $metadata['name'];
+ foreach ( $auto_insert as $anchor_block_name => $position ) {
+ // Avoid infinite recursion (auto-inserting next to or into self).
+ if ( $inserted_block_name === $anchor_block_name ) {
+ _doing_it_wrong(
+ __METHOD__,
+ __( 'Cannot auto-insert block next to itself.', 'gutenberg' ),
+ '6.4.0'
+ );
+ continue;
+ }
+
+ if ( ! isset( $property_mappings[ $position ] ) ) {
+ continue;
+ }
+
+ $mapped_position = $property_mappings[ $position ];
+
+ gutenberg_register_auto_inserted_block( $inserted_block_name, $mapped_position, $anchor_block_name );
+
+ $settings['auto_insert'][ $anchor_block_name ] = $mapped_position;
+ }
+
+ return $settings;
+}
+add_filter( 'block_type_metadata_settings', 'gutenberg_register_auto_inserted_blocks', 10, 2 );
+
+/**
+ * Register block for auto-insertion into the frontend and REST API.
+ *
+ * Register a block for auto-insertion into the frontend and into the markup
+ * returned by the templates and patterns REST API endpoints.
+ *
+ * This is currently done by filtering parsed blocks as obtained from a block template
+ * template part, or pattern and injecting the auto-inserted block where applicable.
+ *
+ * @todo In the long run, we'd likely want some sort of registry for auto-inserted blocks.
+ *
+ * @param string $inserted_block The name of the block to insert.
+ * @param string $position The desired position of the auto-inserted block, relative to its anchor block.
+ * Can be 'before', 'after', 'first_child', or 'last_child'.
+ * @param string $anchor_block The name of the block to insert the auto-inserted block next to.
+ * @return void
+ */
+function gutenberg_register_auto_inserted_block( $inserted_block, $position, $anchor_block ) {
+ $inserted_block = array(
+ 'blockName' => $inserted_block,
+ 'attrs' => array(),
+ 'innerHTML' => '',
+ 'innerContent' => array(),
+ 'innerBlocks' => array(),
+ );
+
+ $inserter = gutenberg_auto_insert_block( $inserted_block, $position, $anchor_block );
+ add_filter( 'gutenberg_serialize_block', $inserter, 10, 1 );
+}
+
+/**
+ * Parse and reserialize block templates to allow running filters.
+ *
+ * By parsing a block template's content and then reserializing it
+ * via `gutenberg_serialize_blocks()`, we are able to run filters
+ * on the parsed blocks.
+ *
+ * @param WP_Block_Template[] $query_result Array of found block templates.
+ * @return WP_Block_Template[] Updated array of found block templates.
+ */
+function gutenberg_parse_and_serialize_block_templates( $query_result ) {
+ foreach ( $query_result as $block_template ) {
+ if ( 'custom' === $block_template->source ) {
+ continue;
+ }
+ $blocks = parse_blocks( $block_template->content );
+ $block_template->content = gutenberg_serialize_blocks( $blocks );
+ }
+
+ return $query_result;
+}
+add_filter( 'get_block_templates', 'gutenberg_parse_and_serialize_block_templates', 10, 1 );
+
+/**
+ * Filters the block template object after it has been (potentially) fetched from the theme file.
+ *
+ * By parsing a block template's content and then reserializing it
+ * via `gutenberg_serialize_blocks()`, we are able to run filters
+ * on the parsed blocks.
+ *
+ * @param WP_Block_Template|null $block_template The found block template, or null if there is none.
+ */
+function gutenberg_parse_and_serialize_blocks( $block_template ) {
+
+ $blocks = parse_blocks( $block_template->content );
+ $block_template->content = gutenberg_serialize_blocks( $blocks );
+
+ return $block_template;
+}
+add_filter( 'get_block_file_template', 'gutenberg_parse_and_serialize_blocks', 10, 1 );
+
+// Helper functions.
+// -----------------
+// The sole purpose of the following two functions (`gutenberg_serialize_block`
+// and `gutenberg_serialize_blocks`), which are otherwise copies of their unprefixed
+// counterparts (`serialize_block` and `serialize_blocks`) is to apply a filter
+// (also called `gutenberg_serialize_block`) as an entry point for modifications
+// to the parsed blocks.
+
+/**
+ * Filterable version of `serialize_block()`.
+ *
+ * This function is identical to `serialize_block()`, except that it applies
+ * the `gutenberg_serialize_block` filter to each block before it is serialized.
+ *
+ * @param array $block The block to be serialized.
+ * @return string The serialized block.
+ *
+ * @see serialize_block()
+ */
+function gutenberg_serialize_block( $block ) {
+ $block_content = '';
+
+ /**
+ * Filters a parsed block before it is serialized.
+ *
+ * @param array $block The block to be serialized.
+ */
+ $block = apply_filters( 'gutenberg_serialize_block', $block );
+
+ $index = 0;
+ foreach ( $block['innerContent'] as $chunk ) {
+ if ( is_string( $chunk ) ) {
+ $block_content .= $chunk;
+ } else { // Compare to WP_Block::render().
+ $inner_block = $block['innerBlocks'][ $index++ ];
+ $block_content .= gutenberg_serialize_block( $inner_block );
+ }
+ }
+
+ if ( ! is_array( $block['attrs'] ) ) {
+ $block['attrs'] = array();
+ }
+
+ return get_comment_delimited_block_content(
+ $block['blockName'],
+ $block['attrs'],
+ $block_content
+ );
+}
+
+/**
+ * Filterable version of `serialize_blocks()`.
+ *
+ * This function is identical to `serialize_blocks()`, except that it applies
+ * the `gutenberg_serialize_block` filter to each block before it is serialized.
+ *
+ * @param array $blocks The blocks to be serialized.
+ * @return string[] The serialized blocks.
+ *
+ * @see serialize_blocks()
+ */
+function gutenberg_serialize_blocks( $blocks ) {
+ return implode( '', array_map( 'gutenberg_serialize_block', $blocks ) );
+}
diff --git a/lib/experimental/class-gutenberg-rest-block-patterns-controller.php b/lib/experimental/class-gutenberg-rest-block-patterns-controller.php
new file mode 100644
index 0000000000000..1ac567959b146
--- /dev/null
+++ b/lib/experimental/class-gutenberg-rest-block-patterns-controller.php
@@ -0,0 +1,40 @@
+get_data();
+
+ $blocks = parse_blocks( $data['content'] );
+ $data['content'] = gutenberg_serialize_blocks( $blocks ); // Serialize or render?
+
+ return rest_ensure_response( $data );
+ }
+}
diff --git a/lib/experimental/disable-tinymce.php b/lib/experimental/disable-tinymce.php
index 824f1ab9a73ae..ff9185cff567d 100644
--- a/lib/experimental/disable-tinymce.php
+++ b/lib/experimental/disable-tinymce.php
@@ -61,14 +61,15 @@ function gutenberg_post_being_edited_requires_classic_block() {
return false;
}
- // Handle the post editor.
- if ( ! empty( $_GET['post'] ) && ! empty( $_GET['action'] ) && 'edit' === $_GET['action'] ) {
- $current_post = get_post( intval( $_GET['post'] ) );
- if ( ! $current_post || is_wp_error( $current_post ) ) {
- return false;
- }
+ // Continue only if we're in the post editor.
+ if ( empty( $_GET['post'] ) || empty( $_GET['action'] ) || 'edit' !== $_GET['action'] ) {
+ return false;
+ }
- $content = $current_post->post_content;
+ // Bail if for some reason the post isn't found.
+ $current_post = get_post( absint( $_GET['post'] ) );
+ if ( ! $current_post ) {
+ return false;
}
// Check if block editor is disabled by "Classic Editor" or another plugin.
@@ -79,13 +80,15 @@ function_exists( 'use_block_editor_for_post_type' ) &&
return true;
}
+ $content = $current_post->post_content;
if ( empty( $content ) ) {
return false;
}
$parsed_blocks = parse_blocks( $content );
foreach ( $parsed_blocks as $block ) {
- if ( empty( $block['blockName'] ) && strlen( trim( $block['innerHTML'] ) ) > 0 ) {
+ $is_freeform_block = empty( $block['blockName'] ) || 'core/freeform' === $block['blockName'];
+ if ( $is_freeform_block && strlen( trim( $block['innerHTML'] ) ) > 0 ) {
return true;
}
}
diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php
index 15d964e2deec7..9c7f66a587a3a 100644
--- a/lib/experimental/editor-settings.php
+++ b/lib/experimental/editor-settings.php
@@ -86,9 +86,6 @@ function gutenberg_enable_experiments() {
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-group-grid-variation', $gutenberg_experiments ) ) {
wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableGroupGridVariation = true', 'before' );
}
- if ( $gutenberg_experiments && array_key_exists( 'gutenberg-interactivity-api-core-blocks', $gutenberg_experiments ) ) {
- wp_add_inline_script( 'wp-block-editor', 'window.__experimentalInteractivityAPI = true', 'before' );
- }
if ( gutenberg_is_experiment_enabled( 'gutenberg-no-tinymce' ) ) {
wp_add_inline_script( 'wp-block-library', 'window.__experimentalDisableTinymce = true', 'before' );
diff --git a/lib/experimental/interactivity-api/blocks.php b/lib/experimental/interactivity-api/blocks.php
deleted file mode 100644
index 6997f2695c9d6..0000000000000
--- a/lib/experimental/interactivity-api/blocks.php
+++ /dev/null
@@ -1,27 +0,0 @@
-$store";
+ echo sprintf(
+ '',
+ wp_json_encode( self::$store, JSON_HEX_TAG | JSON_HEX_AMP )
+ );
}
}
diff --git a/lib/experimental/interactivity-api/scripts.php b/lib/experimental/interactivity-api/scripts.php
index e95bf518c75f7..ed1fca8550070 100644
--- a/lib/experimental/interactivity-api/scripts.php
+++ b/lib/experimental/interactivity-api/scripts.php
@@ -7,20 +7,32 @@
*/
/**
- * Move interactive scripts to the footer. This is a temporary measure to make
- * it work with `wp_store` and it should be replaced with deferred scripts or
- * modules.
+ * Makes sure that interactivity scripts execute after all `wp_store` directives have been printed to the page.
+ *
+ * In WordPress 6.3+ this is achieved by printing in the head but marking the scripts with defer. This has the benefit
+ * of early discovery so the script is loaded by the browser, while at the same time not blocking rendering. In older
+ * versions of WordPress, this is achieved by loading the scripts in the footer.
+ *
+ * @link https://make.wordpress.org/core/2023/07/14/registering-scripts-with-async-and-defer-attributes-in-wordpress-6-3/
*/
function gutenberg_interactivity_move_interactive_scripts_to_the_footer() {
- // Move the @wordpress/interactivity package to the footer.
- wp_script_add_data( 'wp-interactivity', 'group', 1 );
+ $supports_defer = version_compare( strtok( get_bloginfo( 'version' ), '-' ), '6.3', '>=' );
+ if ( $supports_defer ) {
+ // Defer execution of @wordpress/interactivity package but continue loading in head.
+ wp_script_add_data( 'wp-interactivity', 'strategy', 'defer' );
+ wp_script_add_data( 'wp-interactivity', 'group', 0 );
+ } else {
+ // Move the @wordpress/interactivity package to the footer.
+ wp_script_add_data( 'wp-interactivity', 'group', 1 );
+ }
// Move all the view scripts of the interactive blocks to the footer.
$registered_blocks = \WP_Block_Type_Registry::get_instance()->get_all_registered();
foreach ( array_values( $registered_blocks ) as $block ) {
if ( isset( $block->supports['interactivity'] ) && $block->supports['interactivity'] ) {
foreach ( $block->view_script_handles as $handle ) {
- wp_script_add_data( $handle, 'group', 1 );
+ // Note that all block view scripts are already made defer by default.
+ wp_script_add_data( $handle, 'group', $supports_defer ? 0 : 1 );
}
}
}
diff --git a/lib/experimental/rest-api.php b/lib/experimental/rest-api.php
index 7c6a9bf74d739..8e548600b3875 100644
--- a/lib/experimental/rest-api.php
+++ b/lib/experimental/rest-api.php
@@ -10,6 +10,17 @@
die( 'Silence is golden.' );
}
+if ( gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) {
+ /**
+ * Registers the block patterns REST API routes.
+ */
+ function gutenberg_register_rest_block_patterns() {
+ $block_patterns = new Gutenberg_REST_Block_Patterns_Controller();
+ $block_patterns->register_routes();
+ }
+ add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' );
+}
+
/**
* Registers the customizer nonces REST API routes.
*/
diff --git a/lib/experiments-page.php b/lib/experiments-page.php
index ea298784aec0c..3f468d0cbd12d 100644
--- a/lib/experiments-page.php
+++ b/lib/experiments-page.php
@@ -80,26 +80,26 @@ function gutenberg_initialize_experiments_settings() {
);
add_settings_field(
- 'gutenberg-interactivity-api-core-blocks',
- __( 'Interactivity API and Behaviors UI', 'gutenberg' ),
+ 'gutenberg-no-tinymce',
+ __( 'Disable TinyMCE and Classic block', 'gutenberg' ),
'gutenberg_display_experiment_field',
'gutenberg-experiments',
'gutenberg_experiments_section',
array(
- 'label' => __( 'Use the Interactivity API to enable the Behaviors UI in the Image block.', 'gutenberg' ),
- 'id' => 'gutenberg-interactivity-api-core-blocks',
+ 'label' => __( 'Disable TinyMCE and Classic block', 'gutenberg' ),
+ 'id' => 'gutenberg-no-tinymce',
)
);
add_settings_field(
- 'gutenberg-no-tinymce',
- __( 'Disable TinyMCE and Classic block', 'gutenberg' ),
+ 'gutenberg-auto-inserting-blocks',
+ __( 'Auto-inserting blocks', 'gutenberg' ),
'gutenberg_display_experiment_field',
'gutenberg-experiments',
'gutenberg_experiments_section',
array(
- 'label' => __( 'Disable TinyMCE and Classic block', 'gutenberg' ),
- 'id' => 'gutenberg-no-tinymce',
+ 'label' => __( 'Test Auto-inserting blocks', 'gutenberg' ),
+ 'id' => 'gutenberg-auto-inserting-blocks',
)
);
diff --git a/lib/load.php b/lib/load.php
index 3ffd026a8a444..85e9f9575e6e6 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -69,6 +69,9 @@ function gutenberg_is_experiment_enabled( $name ) {
require_once __DIR__ . '/experimental/class-wp-rest-customizer-nonces.php';
}
require_once __DIR__ . '/experimental/class-gutenberg-rest-template-revision-count.php';
+ if ( gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) {
+ require_once __DIR__ . '/experimental/class-gutenberg-rest-block-patterns-controller.php';
+ }
require_once __DIR__ . '/experimental/rest-api.php';
}
@@ -117,8 +120,8 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/experimental/disable-tinymce.php';
}
-if ( gutenberg_is_experiment_enabled( 'gutenberg-interactivity-api-core-blocks' ) ) {
- require __DIR__ . '/experimental/interactivity-api/blocks.php';
+if ( gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) {
+ require __DIR__ . '/experimental/auto-inserting-blocks.php';
}
require __DIR__ . '/experimental/interactivity-api/class-wp-interactivity-store.php';
require __DIR__ . '/experimental/interactivity-api/store.php';
diff --git a/package-lock.json b/package-lock.json
index 12ebe3296be94..0687de2ca68ce 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "gutenberg",
- "version": "16.3.0-rc.1",
+ "version": "16.3.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index e91a45d78d55a..e34ee35b9a5f4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gutenberg",
- "version": "16.3.0-rc.1",
+ "version": "16.3.0",
"private": true,
"description": "A new WordPress editor experience.",
"author": "The WordPress Contributors",
diff --git a/packages/base-styles/_breakpoints.scss b/packages/base-styles/_breakpoints.scss
index 3dd164c99753a..e180757b8cac9 100644
--- a/packages/base-styles/_breakpoints.scss
+++ b/packages/base-styles/_breakpoints.scss
@@ -3,6 +3,7 @@
*/
// Most used breakpoints
+$break-xhuge: 1920px;
$break-huge: 1440px;
$break-wide: 1280px;
$break-xlarge: 1080px;
diff --git a/packages/base-styles/_mixins.scss b/packages/base-styles/_mixins.scss
index 7dbba40c95aa9..da49b110ddbe7 100644
--- a/packages/base-styles/_mixins.scss
+++ b/packages/base-styles/_mixins.scss
@@ -5,6 +5,12 @@
* Breakpoint mixins
*/
+@mixin break-xhuge() {
+ @media (min-width: #{ ($break-xhuge) }) {
+ @content;
+ }
+}
+
@mixin break-huge() {
@media (min-width: #{ ($break-huge) }) {
@content;
diff --git a/packages/block-editor/src/autocompleters/block.js b/packages/block-editor/src/autocompleters/block.js
index 4d189e52b7cdd..bc06c9de5aaaf 100644
--- a/packages/block-editor/src/autocompleters/block.js
+++ b/packages/block-editor/src/autocompleters/block.js
@@ -5,6 +5,7 @@ import { useSelect } from '@wordpress/data';
import {
createBlock,
createBlocksFromInnerBlocksTemplate,
+ parse,
} from '@wordpress/blocks';
import { useMemo } from '@wordpress/element';
@@ -116,14 +117,28 @@ function createBlockCompleter() {
return ! ( /\S/.test( before ) || /\S/.test( after ) );
},
getOptionCompletion( inserterItem ) {
- const { name, initialAttributes, innerBlocks } = inserterItem;
+ const {
+ name,
+ initialAttributes,
+ innerBlocks,
+ syncStatus,
+ content,
+ } = inserterItem;
+
return {
action: 'replace',
- value: createBlock(
- name,
- initialAttributes,
- createBlocksFromInnerBlocksTemplate( innerBlocks )
- ),
+ value:
+ syncStatus === 'unsynced'
+ ? parse( content, {
+ __unstableSkipMigrationLogs: true,
+ } )
+ : createBlock(
+ name,
+ initialAttributes,
+ createBlocksFromInnerBlocksTemplate(
+ innerBlocks
+ )
+ ),
};
},
};
diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js
index aa1a40ef48057..a2acb3c7b53be 100644
--- a/packages/block-editor/src/components/block-list/block.js
+++ b/packages/block-editor/src/components/block-list/block.js
@@ -505,9 +505,14 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => {
) {
__unstableMarkLastChangeAsPersistent();
}
+ //Unsynced patterns are nested in an array so we need to flatten them.
+ const replacementBlocks =
+ blocks?.length === 1 && Array.isArray( blocks[ 0 ] )
+ ? blocks[ 0 ]
+ : blocks;
replaceBlocks(
[ ownProps.clientId ],
- blocks,
+ replacementBlocks,
indexToSelect,
initialPosition
);
diff --git a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap
index 93ff7de880717..039d35937294b 100644
--- a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap
+++ b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap
@@ -172,7 +172,7 @@ exports[`ColorPaletteControl matches the snapshot 1`] = `
diff --git a/packages/block-editor/src/components/editor-styles/index.js b/packages/block-editor/src/components/editor-styles/index.js
index 66f2a08f115a4..3c66c5beb08d3 100644
--- a/packages/block-editor/src/components/editor-styles/index.js
+++ b/packages/block-editor/src/components/editor-styles/index.js
@@ -43,13 +43,13 @@ function useDarkThemeBodyClassName( styles ) {
body.appendChild( tempCanvas );
backgroundColor = defaultView
- .getComputedStyle( tempCanvas, null )
+ ?.getComputedStyle( tempCanvas, null )
.getPropertyValue( 'background-color' );
body.removeChild( tempCanvas );
} else {
backgroundColor = defaultView
- .getComputedStyle( canvas, null )
+ ?.getComputedStyle( canvas, null )
.getPropertyValue( 'background-color' );
}
const colordBackgroundColor = colord( backgroundColor );
diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js
index 96be5779124d7..9347afe54298e 100644
--- a/packages/block-editor/src/components/global-styles/hooks.js
+++ b/packages/block-editor/src/components/global-styles/hooks.js
@@ -551,7 +551,7 @@ export function __experimentalUseHasBehaviorsPanel(
name,
{ blockSupportOnly = false } = {}
) {
- if ( ! settings?.behaviors || ! window?.__experimentalInteractivityAPI ) {
+ if ( ! settings?.behaviors ) {
return false;
}
diff --git a/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js b/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js
index 6d6333587b7d0..c0b8d7ff92512 100644
--- a/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js
+++ b/packages/block-editor/src/components/inserter/hooks/use-patterns-state.js
@@ -15,7 +15,7 @@ import { store as blockEditorStore } from '../../../store';
const CUSTOM_CATEGORY = {
name: 'custom',
label: __( 'My patterns' ),
- description: __( 'Custom patterns add by site users' ),
+ description: __( 'Custom patterns added by site users' ),
};
/**
diff --git a/packages/block-editor/src/components/inserter/reusable-blocks-tab.js b/packages/block-editor/src/components/inserter/reusable-blocks-tab.js
index 08cd8d57ba0d0..920b9f56384d4 100644
--- a/packages/block-editor/src/components/inserter/reusable-blocks-tab.js
+++ b/packages/block-editor/src/components/inserter/reusable-blocks-tab.js
@@ -22,7 +22,10 @@ function ReusableBlocksList( { onHover, onInsert, rootClientId } ) {
);
const filteredItems = useMemo( () => {
- return items.filter( ( { category } ) => category === 'reusable' );
+ return items.filter(
+ ( { category, syncStatus } ) =>
+ category === 'reusable' && syncStatus !== 'unsynced'
+ );
}, [ items ] );
if ( filteredItems.length === 0 ) {
diff --git a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js
index aa5bfe0299292..a1a369d3f9408 100644
--- a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js
+++ b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js
@@ -489,7 +489,11 @@ export default function useListViewDropZone( { dropZoneElement } ) {
const ref = useDropZone( {
dropZoneElement,
- onDrop: onBlockDrop,
+ onDrop( event ) {
+ if ( target ) {
+ onBlockDrop( event );
+ }
+ },
onDragLeave() {
throttled.cancel();
setTarget( null );
diff --git a/packages/block-editor/src/components/media-replace-flow/index.native.js b/packages/block-editor/src/components/media-replace-flow/index.native.js
index 49e98a3b3b9ca..ca2ce4ee78c63 100644
--- a/packages/block-editor/src/components/media-replace-flow/index.native.js
+++ b/packages/block-editor/src/components/media-replace-flow/index.native.js
@@ -1,3 +1,12 @@
-// MediaReplaceFlow component is not yet implemented in the native version,
-// so we return an empty component instead.
-export default () => null;
+/**
+ * External dependencies
+ */
+import { View } from 'react-native';
+
+// MediaReplaceFlow component is not yet implemented in the native version.
+// For testing purposes, we are using an empty View component with a testID prop.
+const MediaReplaceFlow = () => {
+ return ;
+};
+
+export default MediaReplaceFlow;
diff --git a/packages/block-editor/src/components/rich-text/format-edit.js b/packages/block-editor/src/components/rich-text/format-edit.js
index 75b077ab321d4..a70b9f8f77881 100644
--- a/packages/block-editor/src/components/rich-text/format-edit.js
+++ b/packages/block-editor/src/components/rich-text/format-edit.js
@@ -2,43 +2,67 @@
* WordPress dependencies
*/
import { getActiveFormat, getActiveObject } from '@wordpress/rich-text';
+import { useContext, useMemo } from '@wordpress/element';
-export default function FormatEdit( {
- formatTypes,
- onChange,
- onFocus,
- value,
- forwardedRef,
-} ) {
- return formatTypes.map( ( settings ) => {
- const { name, edit: Edit } = settings;
-
- if ( ! Edit ) {
- return null;
- }
-
- const activeFormat = getActiveFormat( value, name );
- const isActive = activeFormat !== undefined;
- const activeObject = getActiveObject( value );
- const isObjectActive =
- activeObject !== undefined && activeObject.type === name;
-
- return (
-
- );
- } );
+/**
+ * Internal dependencies
+ */
+import BlockContext from '../block-context';
+
+const DEFAULT_BLOCK_CONTEXT = {};
+
+export const usesContextKey = Symbol( 'usesContext' );
+
+function Edit( { onChange, onFocus, value, forwardedRef, settings } ) {
+ const {
+ name,
+ edit: EditFunction,
+ [ usesContextKey ]: usesContext,
+ } = settings;
+
+ const blockContext = useContext( BlockContext );
+
+ // Assign context values using the block type's declared context needs.
+ const context = useMemo( () => {
+ return usesContext
+ ? Object.fromEntries(
+ Object.entries( blockContext ).filter( ( [ key ] ) =>
+ usesContext.includes( key )
+ )
+ )
+ : DEFAULT_BLOCK_CONTEXT;
+ }, [ usesContext, blockContext ] );
+
+ if ( ! EditFunction ) {
+ return null;
+ }
+
+ const activeFormat = getActiveFormat( value, name );
+ const isActive = activeFormat !== undefined;
+ const activeObject = getActiveObject( value );
+ const isObjectActive =
+ activeObject !== undefined && activeObject.type === name;
+
+ return (
+
+ );
+}
+
+export default function FormatEdit( { formatTypes, ...props } ) {
+ return formatTypes.map( ( settings ) => (
+
+ ) );
}
diff --git a/packages/block-editor/src/hooks/behaviors.js b/packages/block-editor/src/hooks/behaviors.js
index 27ef5b7afbfe9..ab44edbfa8fd8 100644
--- a/packages/block-editor/src/hooks/behaviors.js
+++ b/packages/block-editor/src/hooks/behaviors.js
@@ -199,10 +199,8 @@ export const withBehaviors = createHigherOrderComponent( ( BlockEdit ) => {
};
}, 'withBehaviors' );
-if ( window?.__experimentalInteractivityAPI ) {
- addFilter(
- 'editor.BlockEdit',
- 'core/behaviors/with-inspector-control',
- withBehaviors
- );
-}
+addFilter(
+ 'editor.BlockEdit',
+ 'core/behaviors/with-inspector-control',
+ withBehaviors
+);
diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js
index 20aeaa2a79040..e4bdb5d37b16b 100644
--- a/packages/block-editor/src/private-apis.js
+++ b/packages/block-editor/src/private-apis.js
@@ -23,6 +23,7 @@ import {
default as ReusableBlocksRenameHint,
useReusableBlocksRenameHint,
} from './components/inserter/reusable-block-rename-hint';
+import { usesContextKey } from './components/rich-text/format-edit';
/**
* Private @wordpress/block-editor APIs.
@@ -49,4 +50,5 @@ lock( privateApis, {
ResolutionTool,
ReusableBlocksRenameHint,
useReusableBlocksRenameHint,
+ usesContextKey,
} );
diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js
index 1d7ec460fe8d9..17573d76d016f 100644
--- a/packages/block-editor/src/store/actions.js
+++ b/packages/block-editor/src/store/actions.js
@@ -948,36 +948,30 @@ export const __unstableSplitSelection =
valueA = remove( valueA, selectionA.offset, valueA.text.length );
valueB = remove( valueB, 0, selectionB.offset );
- dispatch.replaceBlocks(
- select.getSelectedBlockClientIds(),
- [
- {
- // Preserve the original client ID.
- ...blockA,
- attributes: {
- ...blockA.attributes,
- [ selectionA.attributeKey ]: toHTMLString( {
- value: valueA,
- ...mapRichTextSettings( attributeDefinitionA ),
- } ),
- },
+ dispatch.replaceBlocks( select.getSelectedBlockClientIds(), [
+ {
+ // Preserve the original client ID.
+ ...blockA,
+ attributes: {
+ ...blockA.attributes,
+ [ selectionA.attributeKey ]: toHTMLString( {
+ value: valueA,
+ ...mapRichTextSettings( attributeDefinitionA ),
+ } ),
},
- createBlock( getDefaultBlockName() ),
- {
- // Preserve the original client ID.
- ...blockB,
- attributes: {
- ...blockB.attributes,
- [ selectionB.attributeKey ]: toHTMLString( {
- value: valueB,
- ...mapRichTextSettings( attributeDefinitionB ),
- } ),
- },
+ },
+ {
+ // Preserve the original client ID.
+ ...blockB,
+ attributes: {
+ ...blockB.attributes,
+ [ selectionB.attributeKey ]: toHTMLString( {
+ value: valueB,
+ ...mapRichTextSettings( attributeDefinitionB ),
+ } ),
},
- ],
- 1, // If we don't pass the `indexToSelect` it will default to the last block.
- select.getSelectedBlocksInitialCaretPosition()
- );
+ },
+ ] );
};
/**
diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js
index c3d8847b03239..e459e536c9091 100644
--- a/packages/block-editor/src/store/selectors.js
+++ b/packages/block-editor/src/store/selectors.js
@@ -2023,6 +2023,7 @@ export const getInserterItems = createSelector(
utility: 1, // Deprecated.
frecency,
content: reusableBlock.content.raw,
+ syncStatus: reusableBlock.wp_pattern_sync_status,
};
};
@@ -2031,18 +2032,7 @@ export const getInserterItems = createSelector(
'core/block',
rootClientId
)
- ? getReusableBlocks( state )
- .filter(
- ( reusableBlock ) =>
- // Reusable blocks that are fully synced should have no sync status set
- // for backwards compat between patterns and old reusable blocks, but
- // some in release 16.1 may have had sync status inadvertantly set to
- // 'fully' if created in the site editor.
- reusableBlock.wp_pattern_sync_status === 'fully' ||
- reusableBlock.wp_pattern_sync_status === '' ||
- ! reusableBlock.wp_pattern_sync_status
- )
- .map( buildReusableBlockInserterItem )
+ ? getReusableBlocks( state ).map( buildReusableBlockInserterItem )
: [];
const buildBlockTypeInserterItem = buildBlockTypeItem( state, {
diff --git a/packages/block-library/src/file/index.php b/packages/block-library/src/file/index.php
index 621f07b5b88ee..7dd77b20f466c 100644
--- a/packages/block-library/src/file/index.php
+++ b/packages/block-library/src/file/index.php
@@ -24,7 +24,7 @@ function gutenberg_block_core_file_update_interactive_view_script( $metadata ) {
}
/**
- * When the `core/file` block is rendering, check if we need to enqueue the `'wp-block-file-view` script.
+ * When the `core/file` block is rendering, check if we need to enqueue the `wp-block-file-view` script.
*
* @param array $attributes The block attributes.
* @param string $content The block content.
diff --git a/packages/block-library/src/footnotes/block.json b/packages/block-library/src/footnotes/block.json
index e021e9c5225da..6a94c42eb42e5 100644
--- a/packages/block-library/src/footnotes/block.json
+++ b/packages/block-library/src/footnotes/block.json
@@ -9,6 +9,13 @@
"textdomain": "default",
"usesContext": [ "postId", "postType" ],
"supports": {
+ "color": {
+ "link": true,
+ "__experimentalDefaultControls": {
+ "background": true,
+ "text": true
+ }
+ },
"html": false,
"multiple": false,
"reusable": false
diff --git a/packages/block-library/src/footnotes/edit.js b/packages/block-library/src/footnotes/edit.js
index fdfe7a94039af..b8b92170fe217 100644
--- a/packages/block-library/src/footnotes/edit.js
+++ b/packages/block-library/src/footnotes/edit.js
@@ -17,6 +17,18 @@ export default function FootnotesEdit( { context: { postType, postId } } ) {
const footnotes = meta?.footnotes ? JSON.parse( meta.footnotes ) : [];
const blockProps = useBlockProps();
+ if ( postType !== 'post' && postType !== 'page' ) {
+ return (
+
+ }
+ label={ __( 'Footnotes' ) }
+ // To do: add instructions. We can't add new string in RC.
+ />
+
+ );
+ }
+
if ( ! footnotes.length ) {
return (
diff --git a/packages/block-library/src/footnotes/format.js b/packages/block-library/src/footnotes/format.js
index eb700787d02ee..2086005a50993 100644
--- a/packages/block-library/src/footnotes/format.js
+++ b/packages/block-library/src/footnotes/format.js
@@ -12,14 +12,18 @@ import { insertObject } from '@wordpress/rich-text';
import {
RichTextToolbarButton,
store as blockEditorStore,
+ privateApis,
} from '@wordpress/block-editor';
import { useSelect, useDispatch, useRegistry } from '@wordpress/data';
-import { createBlock } from '@wordpress/blocks';
+import { createBlock, store as blocksStore } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { name } from './block.json';
+import { unlock } from '../lock-unlock';
+
+const { usesContextKey } = unlock( privateApis );
export const formatName = 'core/footnote';
export const format = {
@@ -30,7 +34,13 @@ export const format = {
'data-fn': 'data-fn',
},
contentEditable: false,
- edit: function Edit( { value, onChange, isObjectActive } ) {
+ [ usesContextKey ]: [ 'postType' ],
+ edit: function Edit( {
+ value,
+ onChange,
+ isObjectActive,
+ context: { postType },
+ } ) {
const registry = useRegistry();
const {
getSelectedBlockClientId,
@@ -38,9 +48,20 @@ export const format = {
getBlockName,
getBlocks,
} = useSelect( blockEditorStore );
+ const footnotesBlockType = useSelect( ( select ) =>
+ select( blocksStore ).getBlockType( name )
+ );
const { selectionChange, insertBlock } =
useDispatch( blockEditorStore );
+ if ( ! footnotesBlockType ) {
+ return null;
+ }
+
+ if ( postType !== 'post' && postType !== 'page' ) {
+ return null;
+ }
+
function onClick() {
registry.batch( () => {
let id;
diff --git a/packages/block-library/src/footnotes/index.js b/packages/block-library/src/footnotes/index.js
index c0f3d60ada543..c5e851af7e033 100644
--- a/packages/block-library/src/footnotes/index.js
+++ b/packages/block-library/src/footnotes/index.js
@@ -21,7 +21,6 @@ export const settings = {
edit,
};
-// Would be good to remove the format and HoR if the block is unregistered.
registerFormatType( formatName, format );
export const init = () => {
diff --git a/packages/block-library/src/footnotes/index.php b/packages/block-library/src/footnotes/index.php
index ca9aca60abfb6..9815033820406 100644
--- a/packages/block-library/src/footnotes/index.php
+++ b/packages/block-library/src/footnotes/index.php
@@ -83,136 +83,132 @@ function register_block_core_footnotes() {
}
add_action( 'init', 'register_block_core_footnotes' );
-add_action(
- 'wp_after_insert_post',
- /**
- * Saves the footnotes meta value to the revision.
- *
- * @since 6.3.0
- *
- * @param int $revision_id The revision ID.
- */
- static function( $revision_id ) {
- $post_id = wp_is_post_revision( $revision_id );
-
- if ( $post_id ) {
+/**
+ * Saves the footnotes meta value to the revision.
+ *
+ * @since 6.3.0
+ *
+ * @param int $revision_id The revision ID.
+ */
+function wp_save_footnotes_meta( $revision_id ) {
+ $post_id = wp_is_post_revision( $revision_id );
+
+ if ( $post_id ) {
+ $footnotes = get_post_meta( $post_id, 'footnotes', true );
+
+ if ( $footnotes ) {
+ // Can't use update_post_meta() because it doesn't allow revisions.
+ update_metadata( 'post', $revision_id, 'footnotes', $footnotes );
+ }
+ }
+}
+add_action( 'wp_after_insert_post', 'wp_save_footnotes_meta' );
+
+/**
+ * Keeps track of the revision ID for "rest_after_insert_{$post_type}".
+ *
+ * @since 6.3.0
+ *
+ * @global int $wp_temporary_footnote_revision_id The footnote revision ID.
+ *
+ * @param int $revision_id The revision ID.
+ */
+function wp_keep_footnotes_revision_id( $revision_id ) {
+ global $wp_temporary_footnote_revision_id;
+ $wp_temporary_footnote_revision_id = $revision_id;
+}
+add_action( '_wp_put_post_revision', 'wp_keep_footnotes_revision_id' );
+
+/**
+ * This is a specific fix for the REST API. The REST API doesn't update
+ * the post and post meta in one go (through `meta_input`). While it
+ * does fix the `wp_after_insert_post` hook to be called correctly after
+ * updating meta, it does NOT fix hooks such as post_updated and
+ * save_post, which are normally also fired after post meta is updated
+ * in `wp_insert_post()`. Unfortunately, `wp_save_post_revision` is
+ * added to the `post_updated` action, which means the meta is not
+ * available at the time, so we have to add it afterwards through the
+ * `"rest_after_insert_{$post_type}"` action.
+ *
+ * @since 6.3.0
+ *
+ * @global int $wp_temporary_footnote_revision_id The footnote revision ID.
+ *
+ * @param WP_Post $post The post object.
+ */
+function wp_add_footnotes_revisions_to_post_meta( $post ) {
+ global $wp_temporary_footnote_revision_id;
+
+ if ( $wp_temporary_footnote_revision_id ) {
+ $revision = get_post( $wp_temporary_footnote_revision_id );
+
+ if ( ! $revision ) {
+ return;
+ }
+
+ $post_id = $revision->post_parent;
+
+ // Just making sure we're updating the right revision.
+ if ( $post->ID === $post_id ) {
$footnotes = get_post_meta( $post_id, 'footnotes', true );
if ( $footnotes ) {
// Can't use update_post_meta() because it doesn't allow revisions.
- update_metadata( 'post', $revision_id, 'footnotes', $footnotes );
+ update_metadata( 'post', $wp_temporary_footnote_revision_id, 'footnotes', $footnotes );
}
}
}
-);
-
-add_action(
- '_wp_put_post_revision',
- /**
- * Keeps track of the revision ID for "rest_after_insert_{$post_type}".
- *
- * @param int $revision_id The revision ID.
- */
- static function( $revision_id ) {
- global $_gutenberg_revision_id;
- $_gutenberg_revision_id = $revision_id;
- }
-);
+}
foreach ( array( 'post', 'page' ) as $post_type ) {
- add_action(
- "rest_after_insert_{$post_type}",
- /**
- * This is a specific fix for the REST API. The REST API doesn't update
- * the post and post meta in one go (through `meta_input`). While it
- * does fix the `wp_after_insert_post` hook to be called correctly after
- * updating meta, it does NOT fix hooks such as post_updated and
- * save_post, which are normally also fired after post meta is updated
- * in `wp_insert_post()`. Unfortunately, `wp_save_post_revision` is
- * added to the `post_updated` action, which means the meta is not
- * available at the time, so we have to add it afterwards through the
- * `"rest_after_insert_{$post_type}"` action.
- *
- * @since 6.3.0
- *
- * @param WP_Post $post The post object.
- */
- static function( $post ) {
- global $_gutenberg_revision_id;
-
- if ( $_gutenberg_revision_id ) {
- $revision = get_post( $_gutenberg_revision_id );
- $post_id = $revision->post_parent;
-
- // Just making sure we're updating the right revision.
- if ( $post->ID === $post_id ) {
- $footnotes = get_post_meta( $post_id, 'footnotes', true );
-
- if ( $footnotes ) {
- // Can't use update_post_meta() because it doesn't allow revisions.
- update_metadata( 'post', $_gutenberg_revision_id, 'footnotes', $footnotes );
- }
- }
- }
- }
- );
+ add_action( "rest_after_insert_{$post_type}", 'wp_add_footnotes_revisions_to_post_meta' );
}
-add_action(
- 'wp_restore_post_revision',
- /**
- * Restores the footnotes meta value from the revision.
- *
- * @since 6.3.0
- *
- * @param int $post_id The post ID.
- * @param int $revision_id The revision ID.
- */
- static function( $post_id, $revision_id ) {
- $footnotes = get_post_meta( $revision_id, 'footnotes', true );
+/**
+ * Restores the footnotes meta value from the revision.
+ *
+ * @since 6.3.0
+ *
+ * @param int $post_id The post ID.
+ * @param int $revision_id The revision ID.
+ */
+function wp_restore_footnotes_from_revision( $post_id, $revision_id ) {
+ $footnotes = get_post_meta( $revision_id, 'footnotes', true );
- if ( $footnotes ) {
- update_post_meta( $post_id, 'footnotes', $footnotes );
- } else {
- delete_post_meta( $post_id, 'footnotes' );
- }
- },
- 10,
- 2
-);
-
-add_filter(
- '_wp_post_revision_fields',
- /**
- * Adds the footnotes field to the revision.
- *
- * @since 6.3.0
- *
- * @param array $fields The revision fields.
- * @return array The revision fields.
- */
- static function( $fields ) {
- $fields['footnotes'] = __( 'Footnotes' );
- return $fields;
+ if ( $footnotes ) {
+ update_post_meta( $post_id, 'footnotes', $footnotes );
+ } else {
+ delete_post_meta( $post_id, 'footnotes' );
}
-);
-
-add_filter(
- 'wp_post_revision_field_footnotes',
- /**
- * Gets the footnotes field from the revision.
- *
- * @since 6.3.0
- *
- * @param string $revision_field The field value, but $revision->$field
- * (footnotes) does not exist.
- * @param string $field The field name, in this case "footnotes".
- * @param object $revision The revision object to compare against.
- * @return string The field value.
- */
- static function( $revision_field, $field, $revision ) {
- return get_metadata( 'post', $revision->ID, $field, true );
- },
- 10,
- 3
-);
+}
+add_action( 'wp_restore_post_revision', 'wp_restore_footnotes_from_revision', 10, 2 );
+
+/**
+ * Adds the footnotes field to the revision.
+ *
+ * @since 6.3.0
+ *
+ * @param array $fields The revision fields.
+ * @return array The revision fields.
+ */
+function wp_add_footnotes_to_revision( $fields ) {
+ $fields['footnotes'] = __( 'Footnotes' );
+ return $fields;
+}
+add_filter( '_wp_post_revision_fields', 'wp_add_footnotes_to_revision' );
+
+/**
+ * Gets the footnotes field from the revision.
+ *
+ * @since 6.3.0
+ *
+ * @param string $revision_field The field value, but $revision->$field
+ * (footnotes) does not exist.
+ * @param string $field The field name, in this case "footnotes".
+ * @param object $revision The revision object to compare against.
+ * @return string The field value.
+ */
+function wp_get_footnotes_from_revision( $revision_field, $field, $revision ) {
+ return get_metadata( 'post', $revision->ID, $field, true );
+}
+add_filter( 'wp_post_revision_field_footnotes', 'wp_get_footnotes_from_revision', 10, 3 );
diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js
index 5ae8cd2d6820d..198892e286aba 100644
--- a/packages/block-library/src/gallery/edit.js
+++ b/packages/block-library/src/gallery/edit.js
@@ -635,25 +635,27 @@ function GalleryEdit( props ) {
/>
) }
-
- image.id )
- .map( ( image ) => image.id ) }
- addToGallery={ hasImageIds }
- />
-
{ Platform.isWeb && (
-
+ <>
+
+ image.id )
+ .map( ( image ) => image.id ) }
+ addToGallery={ hasImageIds }
+ />
+
+
+ >
) }
{
expect( getEditorHtml() ).toMatchSnapshot();
} );
+ it( 'does not display MediaReplaceFlow component within the block toolbar', async () => {
+ const screen = await initializeWithGalleryBlock( {
+ numberOfItems: 3,
+ media,
+ } );
+ const { queryByTestId } = screen;
+
+ fireEvent.press( getBlock( screen, 'Gallery' ) );
+
+ // Expect the native MediaReplaceFlow component to not be present in the block toolbar
+ const mediaReplaceFlow = queryByTestId( 'media-replace-flow' );
+ expect( mediaReplaceFlow ).toBeNull();
+ } );
+
// Test cases related to TC013 - Settings - Columns
// Reference: https://github.com/wordpress-mobile/test-cases/blob/trunk/test-cases/gutenberg/gallery.md#tc013
describe( 'Columns setting', () => {
diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json
index 7c8b2c2715c99..4d044d221ec70 100644
--- a/packages/block-library/src/image/block.json
+++ b/packages/block-library/src/image/block.json
@@ -127,5 +127,6 @@
{ "name": "rounded", "label": "Rounded" }
],
"editorStyle": "wp-block-image-editor",
- "style": "wp-block-image"
+ "style": "wp-block-image",
+ "viewScript": "file:./view-interactivity.min.js"
}
diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php
index 9c8f66b7ffe18..b9bcf461dcf60 100644
--- a/packages/block-library/src/image/index.php
+++ b/packages/block-library/src/image/index.php
@@ -32,7 +32,6 @@ function render_block_core_image( $attributes, $content, $block ) {
}
$should_load_view_script = false;
- $experiments = get_option( 'gutenberg-experiments' );
$link_destination = isset( $attributes['linkDestination'] ) ? $attributes['linkDestination'] : 'none';
// Get the lightbox setting from the block attributes.
if ( isset( $attributes['behaviors']['lightbox'] ) ) {
@@ -50,8 +49,7 @@ function render_block_core_image( $attributes, $content, $block ) {
// If the lightbox is enabled, the image is not linked, and the Interactivity API is enabled, load the view script.
if ( isset( $lightbox_settings['enabled'] ) &&
true === $lightbox_settings['enabled'] &&
- 'none' === $link_destination &&
- ! empty( $experiments['gutenberg-interactivity-api-core-blocks'] )
+ 'none' === $link_destination
) {
$should_load_view_script = true;
}
diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js
index 911bd0c37451e..736b552bf4259 100644
--- a/packages/block-library/src/index.js
+++ b/packages/block-library/src/index.js
@@ -282,7 +282,11 @@ export const registerCoreBlocks = (
blocks.forEach( ( { init } ) => init() );
setDefaultBlockName( paragraph.name );
- if ( window.wp && window.wp.oldEditor ) {
+ if (
+ window.wp &&
+ window.wp.oldEditor &&
+ blocks.some( ( { name } ) => name === classic.name )
+ ) {
setFreeformContentHandlerName( classic.name );
}
setUnregisteredTypeHandlerName( missing.name );
diff --git a/packages/block-library/src/list-item/hooks/use-merge.js b/packages/block-library/src/list-item/hooks/use-merge.js
index 8c186b27f6dc9..da81fe0a7bfde 100644
--- a/packages/block-library/src/list-item/hooks/use-merge.js
+++ b/packages/block-library/src/list-item/hooks/use-merge.js
@@ -107,11 +107,18 @@ export default function useMerge( clientId, onMerge ) {
} else if ( previousBlockClientId ) {
const trailingId = getTrailingId( previousBlockClientId );
registry.batch( () => {
- moveBlocksToPosition(
- getBlockOrder( clientId ),
- clientId,
- previousBlockClientId
- );
+ // When merging a list item with a previous trailing list
+ // item, we also need to move any nested list items. First,
+ // check if there's a listed list. If there's a nested list,
+ // append its nested list items to the trailing list.
+ const [ nestedListClientId ] = getBlockOrder( clientId );
+ if ( nestedListClientId ) {
+ moveBlocksToPosition(
+ getBlockOrder( nestedListClientId ),
+ nestedListClientId,
+ getBlockRootClientId( trailingId )
+ );
+ }
mergeBlocks( trailingId, clientId );
} );
} else {
diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php
index 79988c3ee350b..52376aba0d44e 100644
--- a/packages/block-library/src/navigation/index.php
+++ b/packages/block-library/src/navigation/index.php
@@ -671,19 +671,33 @@ function render_block_core_navigation( $attributes, $content, $block ) {
$inner_blocks_html .= '';
}
- // If the script already exists, there is no point in removing it from viewScript.
- $should_load_view_script = ( $is_responsive_menu || ( $has_submenus && ( $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] ) ) );
- $view_js_file = 'wp-block-navigation-view';
- if ( ! wp_script_is( $view_js_file ) ) {
- $script_handles = $block->block_type->view_script_handles;
-
- // If the script is not needed, and it is still in the `view_script_handles`, remove it.
- if ( ! $should_load_view_script && in_array( $view_js_file, $script_handles, true ) ) {
- $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file, 'wp-block-navigation-view-2' ) );
- }
- // If the script is needed, but it was previously removed, add it again.
- if ( $should_load_view_script && ! in_array( $view_js_file, $script_handles, true ) ) {
- $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file, 'wp-block-navigation-view-2' ) );
+ $needed_script_map = array(
+ 'wp-block-navigation-view' => ( $has_submenus && ( $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] ) ),
+ 'wp-block-navigation-view-2' => $is_responsive_menu,
+ );
+
+ $should_load_view_script = false;
+ if ( gutenberg_should_block_use_interactivity_api( 'core/navigation' ) ) {
+ // TODO: The script is still loaded even when it isn't needed when the Interactivity API is used.
+ $should_load_view_script = count( array_filter( $needed_script_map ) ) > 0;
+ } else {
+ foreach ( $needed_script_map as $view_script_handle => $is_view_script_needed ) {
+
+ // If the script already exists, there is no point in removing it from viewScript.
+ if ( wp_script_is( $view_script_handle ) ) {
+ continue;
+ }
+
+ $script_handles = $block->block_type->view_script_handles;
+
+ // If the script is not needed, and it is still in the `view_script_handles`, remove it.
+ if ( ! $is_view_script_needed && in_array( $view_script_handle, $script_handles, true ) ) {
+ $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_script_handle ) );
+ }
+ // If the script is needed, but it was previously removed, add it again.
+ if ( $is_view_script_needed && ! in_array( $view_script_handle, $script_handles, true ) ) {
+ $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_script_handle ) );
+ }
}
}
diff --git a/packages/block-library/src/navigation/view-modal.js b/packages/block-library/src/navigation/view-modal.js
index 9477d262816d9..62de6e8808bf0 100644
--- a/packages/block-library/src/navigation/view-modal.js
+++ b/packages/block-library/src/navigation/view-modal.js
@@ -1,16 +1,22 @@
+/*eslint-env browser*/
/**
* External dependencies
*/
import MicroModal from 'micromodal';
// Responsive navigation toggle.
-function navigationToggleModal( modal ) {
+
+/**
+ * Toggles responsive navigation.
+ *
+ * @param {HTMLDivElement} modal
+ * @param {boolean} isHidden
+ */
+function navigationToggleModal( modal, isHidden ) {
const dialogContainer = modal.querySelector(
`.wp-block-navigation__responsive-dialog`
);
- const isHidden = 'true' === modal.getAttribute( 'aria-hidden' );
-
modal.classList.toggle( 'has-modal-open', ! isHidden );
dialogContainer.toggleAttribute( 'aria-modal', ! isHidden );
@@ -23,10 +29,15 @@ function navigationToggleModal( modal ) {
}
// Add a class to indicate the modal is open.
- const htmlElement = document.documentElement;
- htmlElement.classList.toggle( 'has-modal-open' );
+ document.documentElement.classList.toggle( 'has-modal-open' );
}
+/**
+ * Checks whether the provided link is an anchor on the current page.
+ *
+ * @param {HTMLAnchorElement} node
+ * @return {boolean} Is anchor.
+ */
function isLinkToAnchorOnCurrentPage( node ) {
return (
node.hash &&
@@ -37,42 +48,80 @@ function isLinkToAnchorOnCurrentPage( node ) {
);
}
-window.addEventListener( 'load', () => {
- MicroModal.init( {
- onShow: navigationToggleModal,
- onClose: navigationToggleModal,
- openClass: 'is-menu-open',
+/**
+ * Handles effects after opening the modal.
+ *
+ * @param {HTMLDivElement} modal
+ */
+function onShow( modal ) {
+ navigationToggleModal( modal, false );
+ modal.addEventListener( 'click', handleAnchorLinkClicksInsideModal, {
+ passive: true,
} );
+}
- // Close modal automatically on clicking anchor links inside modal.
- const navigationLinks = document.querySelectorAll(
- '.wp-block-navigation-item__content'
- );
+/**
+ * Handles effects after closing the modal.
+ *
+ * @param {HTMLDivElement} modal
+ */
+function onClose( modal ) {
+ navigationToggleModal( modal, true );
+ modal.removeEventListener( 'click', handleAnchorLinkClicksInsideModal, {
+ passive: true,
+ } );
+}
- navigationLinks.forEach( function ( link ) {
- // Ignore non-anchor links and anchor links which open on a new tab.
- if (
- ! isLinkToAnchorOnCurrentPage( link ) ||
- link.attributes?.target === '_blank'
- ) {
- return;
- }
+/**
+ * Handle clicks to anchor links in modal using event delegation by closing modal automatically
+ *
+ * @param {UIEvent} event
+ */
+function handleAnchorLinkClicksInsideModal( event ) {
+ const link = event.target.closest( '.wp-block-navigation-item__content' );
+ if ( ! ( link instanceof HTMLAnchorElement ) ) {
+ return;
+ }
- // Find the specific parent modal for this link
- // since .close() won't work without an ID if there are
- // multiple navigation menus in a post/page.
- const modal = link.closest(
- '.wp-block-navigation__responsive-container'
- );
- const modalId = modal?.getAttribute( 'id' );
+ // Ignore non-anchor links and anchor links which open on a new tab.
+ if (
+ ! isLinkToAnchorOnCurrentPage( link ) ||
+ link.attributes?.target === '_blank'
+ ) {
+ return;
+ }
- link.addEventListener( 'click', () => {
- // check if modal exists and is open before trying to close it
- // otherwise Micromodal will toggle the `has-modal-open` class
- // on the html tag which prevents scrolling
- if ( modalId && modal.classList.contains( 'has-modal-open' ) ) {
- MicroModal.close( modalId );
- }
- } );
- } );
-} );
+ // Find the specific parent modal for this link
+ // since .close() won't work without an ID if there are
+ // multiple navigation menus in a post/page.
+ const modal = link.closest( '.wp-block-navigation__responsive-container' );
+ const modalId = modal?.getAttribute( 'id' );
+ if ( ! modalId ) {
+ return;
+ }
+
+ // check if modal exists and is open before trying to close it
+ // otherwise Micromodal will toggle the `has-modal-open` class
+ // on the html tag which prevents scrolling
+ if ( modalId && modal.classList.contains( 'has-modal-open' ) ) {
+ MicroModal.close( modalId );
+ }
+}
+
+// MicroModal.init() does not support event delegation for the open trigger, so here MicroModal.show() is called manually.
+document.addEventListener(
+ 'click',
+ ( event ) => {
+ /** @type {HTMLElement} */
+ const target = event.target;
+
+ if ( target.dataset.micromodalTrigger ) {
+ MicroModal.show( target.dataset.micromodalTrigger, {
+ onShow,
+ onClose,
+ openClass: 'is-menu-open',
+ } );
+ }
+ },
+ { passive: true }
+);
diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js
index 19805a44ae4ae..d808d1707d5bf 100644
--- a/packages/block-library/src/navigation/view.js
+++ b/packages/block-library/src/navigation/view.js
@@ -1,62 +1,94 @@
+/*eslint-env browser*/
// Open on click functionality.
-function closeSubmenus( element ) {
- element
+
+/**
+ * Keep track of whether a submenu is open to short-circuit delegated event listeners.
+ *
+ * @type {boolean}
+ */
+let hasOpenSubmenu = false;
+
+/**
+ * Close submenu items for a navigation item.
+ *
+ * @param {HTMLElement} navigationItem - Either a NAV or LI element.
+ */
+function closeSubmenus( navigationItem ) {
+ navigationItem
.querySelectorAll( '[aria-expanded="true"]' )
.forEach( function ( toggle ) {
toggle.setAttribute( 'aria-expanded', 'false' );
} );
+ hasOpenSubmenu = false;
}
-function toggleSubmenuOnClick( event ) {
- const buttonToggle = event.target.closest( '[aria-expanded]' );
- const isSubmenuOpen = buttonToggle.getAttribute( 'aria-expanded' );
+/**
+ * Toggle submenu on click.
+ *
+ * @param {HTMLButtonElement} buttonToggle
+ */
+function toggleSubmenuOnClick( buttonToggle ) {
+ const isSubmenuOpen =
+ buttonToggle.getAttribute( 'aria-expanded' ) === 'true';
+ const navigationItem = buttonToggle.closest( '.wp-block-navigation-item' );
- if ( isSubmenuOpen === 'true' ) {
- closeSubmenus( buttonToggle.closest( '.wp-block-navigation-item' ) );
+ if ( isSubmenuOpen ) {
+ closeSubmenus( navigationItem );
} else {
// Close all sibling submenus.
- const parentElement = buttonToggle.closest(
- '.wp-block-navigation-item'
- );
const navigationParent = buttonToggle.closest(
'.wp-block-navigation__submenu-container, .wp-block-navigation__container, .wp-block-page-list'
);
navigationParent
.querySelectorAll( '.wp-block-navigation-item' )
- .forEach( function ( child ) {
- if ( child !== parentElement ) {
+ .forEach( ( child ) => {
+ if ( child !== navigationItem ) {
closeSubmenus( child );
}
} );
+
// Open submenu.
buttonToggle.setAttribute( 'aria-expanded', 'true' );
+ hasOpenSubmenu = true;
}
}
-// Necessary for some themes such as TT1 Blocks, where
-// scripts could be loaded before the body.
-window.addEventListener( 'load', () => {
- const submenuButtons = document.querySelectorAll(
- '.wp-block-navigation-submenu__toggle'
- );
+// Open on button click or close on click outside.
+document.addEventListener(
+ 'click',
+ function ( event ) {
+ const target = event.target;
+ const button = target.closest( '.wp-block-navigation-submenu__toggle' );
- submenuButtons.forEach( function ( button ) {
- button.addEventListener( 'click', toggleSubmenuOnClick );
- } );
+ // Close any other open submenus.
+ if ( hasOpenSubmenu ) {
+ const navigationBlocks = document.querySelectorAll(
+ '.wp-block-navigation'
+ );
+ navigationBlocks.forEach( function ( block ) {
+ if ( ! block.contains( target ) ) {
+ closeSubmenus( block );
+ }
+ } );
+ }
+
+ // Now open the submenu if one was clicked.
+ if ( button instanceof HTMLButtonElement ) {
+ toggleSubmenuOnClick( button );
+ }
+ },
+ { passive: true }
+);
+
+// Close on focus outside or escape key.
+document.addEventListener(
+ 'keyup',
+ function ( event ) {
+ // Abort if there aren't any submenus open anyway.
+ if ( ! hasOpenSubmenu ) {
+ return;
+ }
- // Close on click outside.
- document.addEventListener( 'click', function ( event ) {
- const navigationBlocks = document.querySelectorAll(
- '.wp-block-navigation'
- );
- navigationBlocks.forEach( function ( block ) {
- if ( ! block.contains( event.target ) ) {
- closeSubmenus( block );
- }
- } );
- } );
- // Close on focus outside or escape key.
- document.addEventListener( 'keyup', function ( event ) {
const submenuBlocks = document.querySelectorAll(
'.wp-block-navigation-item.has-child'
);
@@ -70,5 +102,6 @@ window.addEventListener( 'load', () => {
toggle?.focus();
}
} );
- } );
-} );
+ },
+ { passive: true }
+);
diff --git a/packages/block-library/src/pattern/index.php b/packages/block-library/src/pattern/index.php
index 6368bdb7b7487..bc42e891d9f1f 100644
--- a/packages/block-library/src/pattern/index.php
+++ b/packages/block-library/src/pattern/index.php
@@ -41,7 +41,17 @@ function render_block_core_pattern( $attributes ) {
}
$pattern = $registry->get_registered( $slug );
- return do_blocks( $pattern['content'] );
+ $content = $pattern['content'];
+
+ $gutenberg_experiments = get_option( 'gutenberg-experiments' );
+ if ( $gutenberg_experiments && ! empty( $gutenberg_experiments['gutenberg-auto-inserting-blocks'] ) ) {
+ // TODO: In the long run, we'd likely want to have a filter in the `WP_Block_Patterns_Registry` class
+ // instead to allow us plugging in code like this.
+ $blocks = parse_blocks( $content );
+ $content = gutenberg_serialize_blocks( $blocks );
+ }
+
+ return do_blocks( $content );
}
add_action( 'init', 'register_block_core_pattern' );
diff --git a/packages/block-library/src/preformatted/block.json b/packages/block-library/src/preformatted/block.json
index f781cb1125efa..ec6ea839385eb 100644
--- a/packages/block-library/src/preformatted/block.json
+++ b/packages/block-library/src/preformatted/block.json
@@ -25,6 +25,10 @@
"text": true
}
},
+ "spacing": {
+ "padding": true,
+ "margin": true
+ },
"typography": {
"fontSize": true,
"lineHeight": true,
diff --git a/packages/block-library/src/preformatted/style.scss b/packages/block-library/src/preformatted/style.scss
index 71e60ffe4ea52..783fee74d4f4f 100644
--- a/packages/block-library/src/preformatted/style.scss
+++ b/packages/block-library/src/preformatted/style.scss
@@ -1,7 +1,10 @@
.wp-block-preformatted {
+ // This block has customizable padding, border-box makes that more predictable.
+ box-sizing: border-box;
white-space: pre-wrap;
}
-.wp-block-preformatted.has-background {
+// Add low specificity default padding when a background is used.
+:where(.wp-block-preformatted.has-background) {
padding: $block-bg-padding--v $block-bg-padding--h;
}
diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php
index b0fed54d2f385..670ceb0eb66c5 100644
--- a/packages/block-library/src/search/index.php
+++ b/packages/block-library/src/search/index.php
@@ -140,8 +140,10 @@ function render_block_core_search( $attributes, $content, $block ) {
$button->add_class( implode( ' ', $button_classes ) );
if ( 'expand-searchfield' === $attributes['buttonBehavior'] && 'button-only' === $attributes['buttonPosition'] ) {
$button->set_attribute( 'aria-label', __( 'Expand search field' ) );
+ $button->set_attribute( 'data-toggled-aria-label', __( 'Submit Search' ) );
$button->set_attribute( 'aria-controls', 'wp-block-search__input-' . $input_id );
$button->set_attribute( 'aria-expanded', 'false' );
+ $button->set_attribute( 'type', 'button' ); // Will be set to submit after clicking.
} else {
$button->set_attribute( 'aria-label', wp_strip_all_tags( $attributes['buttonText'] ) );
}
diff --git a/packages/block-library/src/search/view.js b/packages/block-library/src/search/view.js
index 0909121b25bf0..5aaf1dd1ef3ad 100644
--- a/packages/block-library/src/search/view.js
+++ b/packages/block-library/src/search/view.js
@@ -1,68 +1,172 @@
-window.addEventListener( 'DOMContentLoaded', () => {
- const hiddenClass = 'wp-block-search__searchfield-hidden';
-
- Array.from(
- document.getElementsByClassName(
- 'wp-block-search__button-behavior-expand'
- )
- ).forEach( ( block ) => {
- const searchField = block.querySelector( '.wp-block-search__input' );
- const searchButton = block.querySelector( '.wp-block-search__button' );
- const searchLabel = block.querySelector( '.wp-block-search__label' );
- const ariaLabel = searchButton.getAttribute( 'aria-label' );
- const id = searchField.getAttribute( 'id' );
-
- const toggleSearchField = ( showSearchField ) => {
- if ( showSearchField ) {
- searchField.removeAttribute( 'aria-hidden' );
- searchField.removeAttribute( 'tabindex' );
- searchButton.removeAttribute( 'aria-expanded' );
- searchButton.removeAttribute( 'aria-controls' );
- searchButton.setAttribute( 'type', 'submit' );
- searchButton.setAttribute( 'aria-label', 'Submit Search' );
-
- return block.classList.remove( hiddenClass );
- }
-
- searchButton.removeAttribute( 'type' );
- searchField.setAttribute( 'aria-hidden', 'true' );
- searchField.setAttribute( 'tabindex', '-1' );
- searchButton.setAttribute( 'aria-expanded', 'false' );
- searchButton.setAttribute( 'aria-controls', id );
- searchButton.setAttribute( 'aria-label', ariaLabel );
- return block.classList.add( hiddenClass );
- };
-
- const hideSearchField = ( e ) => {
- if ( ! e.target.closest( '.wp-block-search' ) ) {
- return toggleSearchField( false );
- }
-
- if ( e.key === 'Escape' ) {
- searchButton.focus();
- return toggleSearchField( false );
- }
- };
-
- const handleButtonClick = ( e ) => {
- if ( block.classList.contains( hiddenClass ) ) {
- e.preventDefault();
- searchField.focus();
- toggleSearchField( true );
- }
- };
-
- searchButton.removeAttribute( 'type' );
- searchField.addEventListener( 'keydown', ( e ) => {
- hideSearchField( e );
- } );
- searchButton.addEventListener( 'click', handleButtonClick );
- searchButton.addEventListener( 'keydown', ( e ) => {
- hideSearchField( e );
- } );
- if ( searchLabel ) {
- searchLabel.addEventListener( 'click', handleButtonClick );
- }
- document.body.addEventListener( 'click', hideSearchField );
+/*eslint-env browser*/
+
+/** @type {?HTMLFormElement} */
+let expandedSearchBlock = null;
+
+const hiddenClass = 'wp-block-search__searchfield-hidden';
+
+/**
+ * Toggles aria-label with data-toggled-aria-label.
+ *
+ * @param {HTMLElement} element
+ */
+function toggleAriaLabel( element ) {
+ if ( ! ( 'toggledAriaLabel' in element.dataset ) ) {
+ throw new Error( 'Element lacks toggledAriaLabel in dataset.' );
+ }
+
+ const ariaLabel = element.dataset.toggledAriaLabel;
+ element.dataset.toggledAriaLabel = element.ariaLabel;
+ element.ariaLabel = ariaLabel;
+}
+
+/**
+ * Gets search input.
+ *
+ * @param {HTMLFormElement} block Search block.
+ * @return {HTMLInputElement} Search input.
+ */
+function getSearchInput( block ) {
+ return block.querySelector( '.wp-block-search__input' );
+}
+
+/**
+ * Gets search button.
+ *
+ * @param {HTMLFormElement} block Search block.
+ * @return {HTMLButtonElement} Search button.
+ */
+function getSearchButton( block ) {
+ return block.querySelector( '.wp-block-search__button' );
+}
+
+/**
+ * Handles keydown event to collapse an expanded Search block (when pressing Escape key).
+ *
+ * @param {KeyboardEvent} event
+ */
+function handleKeydownEvent( event ) {
+ if ( ! expandedSearchBlock ) {
+ // In case the event listener wasn't removed in time.
+ return;
+ }
+
+ if ( event.key === 'Escape' ) {
+ const block = expandedSearchBlock; // This is nullified by collapseExpandedSearchBlock().
+ collapseExpandedSearchBlock();
+ getSearchButton( block ).focus();
+ }
+}
+
+/**
+ * Handles keyup event to collapse an expanded Search block (e.g. when tabbing out of expanded Search block).
+ *
+ * @param {KeyboardEvent} event
+ */
+function handleKeyupEvent( event ) {
+ if ( ! expandedSearchBlock ) {
+ // In case the event listener wasn't removed in time.
+ return;
+ }
+
+ if ( event.target.closest( '.wp-block-search' ) !== expandedSearchBlock ) {
+ collapseExpandedSearchBlock();
+ }
+}
+
+/**
+ * Expands search block.
+ *
+ * Inverse of what is done in collapseExpandedSearchBlock().
+ *
+ * @param {HTMLFormElement} block Search block.
+ */
+function expandSearchBlock( block ) {
+ // Make sure only one is open at a time.
+ if ( expandedSearchBlock ) {
+ collapseExpandedSearchBlock();
+ }
+
+ const searchField = getSearchInput( block );
+ const searchButton = getSearchButton( block );
+
+ searchButton.type = 'submit';
+ searchField.ariaHidden = 'false';
+ searchField.tabIndex = 0;
+ searchButton.ariaExpanded = 'true';
+ searchButton.removeAttribute( 'aria-controls' ); // Note: Seemingly not reflected with searchButton.ariaControls.
+ toggleAriaLabel( searchButton );
+ block.classList.remove( hiddenClass );
+
+ searchField.focus(); // Note that Chrome seems to do this automatically.
+
+ // The following two must be inverse of what is done in collapseExpandedSearchBlock().
+ document.addEventListener( 'keydown', handleKeydownEvent, {
+ passive: true,
+ } );
+ document.addEventListener( 'keyup', handleKeyupEvent, {
+ passive: true,
+ } );
+
+ expandedSearchBlock = block;
+}
+
+/**
+ * Collapses the expanded search block.
+ *
+ * Inverse of what is done in expandSearchBlock().
+ */
+function collapseExpandedSearchBlock() {
+ if ( ! expandedSearchBlock ) {
+ throw new Error( 'Expected expandedSearchBlock to be defined.' );
+ }
+ const block = expandedSearchBlock;
+ const searchField = getSearchInput( block );
+ const searchButton = getSearchButton( block );
+
+ searchButton.type = 'button';
+ searchField.ariaHidden = 'true';
+ searchField.tabIndex = -1;
+ searchButton.ariaExpanded = 'false';
+ searchButton.setAttribute( 'aria-controls', searchField.id ); // Note: Seemingly not reflected with searchButton.ariaControls.
+ toggleAriaLabel( searchButton );
+ block.classList.add( hiddenClass );
+
+ // The following two must be inverse of what is done in expandSearchBlock().
+ document.removeEventListener( 'keydown', handleKeydownEvent, {
+ passive: true,
} );
-} );
+ document.removeEventListener( 'keyup', handleKeyupEvent, {
+ passive: true,
+ } );
+
+ expandedSearchBlock = null;
+}
+
+// Listen for click events anywhere on the document so this script can be loaded asynchronously in the head.
+document.addEventListener(
+ 'click',
+ ( event ) => {
+ // Get the ancestor expandable Search block of the clicked element.
+ const block = event.target.closest(
+ '.wp-block-search__button-behavior-expand'
+ );
+
+ /*
+ * If there is already an expanded search block and either the current click was not for a Search block or it was
+ * for another block, then collapse the currently-expanded block.
+ */
+ if ( expandedSearchBlock && block !== expandedSearchBlock ) {
+ collapseExpandedSearchBlock();
+ }
+
+ // If the click was on or inside a collapsed Search block, expand it.
+ if (
+ block instanceof HTMLFormElement &&
+ block.classList.contains( hiddenClass )
+ ) {
+ expandSearchBlock( block );
+ }
+ },
+ { passive: true }
+);
diff --git a/packages/block-library/src/template-part/edit/import-controls.js b/packages/block-library/src/template-part/edit/import-controls.js
index f629691535a32..c8f4fbc2e647d 100644
--- a/packages/block-library/src/template-part/edit/import-controls.js
+++ b/packages/block-library/src/template-part/edit/import-controls.js
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
-import { __, sprintf } from '@wordpress/i18n';
+import { __, _x, sprintf } from '@wordpress/i18n';
import { useMemo, useState } from '@wordpress/element';
import { useDispatch, useSelect, useRegistry } from '@wordpress/data';
import {
@@ -163,7 +163,7 @@ export function TemplatePartImportControls( { area, setAttributes } ) {
isBusy={ isBusy }
aria-disabled={ isBusy || ! selectedSidebar }
>
- { __( 'Import' ) }
+ { _x( 'Import', 'button label' ) }
diff --git a/packages/block-library/src/template-part/index.php b/packages/block-library/src/template-part/index.php
index 1066aa0141915..cb4c2228b3e0c 100644
--- a/packages/block-library/src/template-part/index.php
+++ b/packages/block-library/src/template-part/index.php
@@ -67,14 +67,11 @@ function render_block_core_template_part( $attributes ) {
// Else, if the template part was provided by the active theme,
// render the corresponding file content.
if ( 0 === validate_file( $attributes['slug'] ) ) {
- $block_template_file = _get_block_template_file( 'wp_template_part', $attributes['slug'] );
- if ( $block_template_file ) {
- $template_part_file_path = $block_template_file['path'];
- $content = (string) file_get_contents( $template_part_file_path );
- $content = '' !== $content ? _inject_theme_attribute_in_block_template_content( $content ) : '';
- if ( isset( $block_template_file['area'] ) ) {
- $area = $block_template_file['area'];
- }
+ $block_template = get_block_file_template( $template_part_id, 'wp_template_part' );
+
+ $content = $block_template->content;
+ if ( isset( $block_template->area ) ) {
+ $area = $block_template->area;
}
}
diff --git a/packages/block-library/src/verse/edit.js b/packages/block-library/src/verse/edit.js
index 4ff910c8c2fa0..1ca846fb848b5 100644
--- a/packages/block-library/src/verse/edit.js
+++ b/packages/block-library/src/verse/edit.js
@@ -13,7 +13,6 @@ import {
AlignmentToolbar,
useBlockProps,
} from '@wordpress/block-editor';
-import { createBlock, getDefaultBlockName } from '@wordpress/blocks';
export default function VerseEdit( {
attributes,
@@ -21,7 +20,6 @@ export default function VerseEdit( {
mergeBlocks,
onRemove,
style,
- insertBlocksAfter,
} ) {
const { textAlign, content } = attributes;
const blockProps = useBlockProps( {
@@ -58,9 +56,6 @@ export default function VerseEdit( {
textAlign={ textAlign }
{ ...blockProps }
__unstablePastePlainText
- __unstableOnSplitAtEnd={ () =>
- insertBlocksAfter( createBlock( getDefaultBlockName() ) )
- }
/>
>
);
diff --git a/packages/block-library/src/verse/test/edit.native.js b/packages/block-library/src/verse/test/edit.native.js
index aa7cedbbbfd46..bbdacbb90366a 100644
--- a/packages/block-library/src/verse/test/edit.native.js
+++ b/packages/block-library/src/verse/test/edit.native.js
@@ -64,15 +64,13 @@ describe( 'Verse block', () => {
const verseTextInput = await screen.findByPlaceholderText(
'Write verse…'
);
- typeInRichText( verseTextInput, 'A great statement.Again', {
- finalSelectionStart: 18,
- finalSelectionEnd: 18,
- } );
+ typeInRichText( verseTextInput, 'A great statement.' );
fireEvent( verseTextInput, 'onKeyDown', {
nativeEvent: {},
preventDefault() {},
keyCode: ENTER,
} );
+ typeInRichText( verseTextInput, 'Again' );
// Assert
expect( getEditorHtml() ).toMatchInlineSnapshot( `
diff --git a/packages/commands/src/components/command-menu.js b/packages/commands/src/components/command-menu.js
index b4a828f34303d..1bd3e38675861 100644
--- a/packages/commands/src/components/command-menu.js
+++ b/packages/commands/src/components/command-menu.js
@@ -1,13 +1,19 @@
/**
* External dependencies
*/
-import { Command } from 'cmdk';
+import { Command, useCommandState } from 'cmdk';
/**
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
-import { useState, useEffect, useRef, useCallback } from '@wordpress/element';
+import {
+ useState,
+ useEffect,
+ useRef,
+ useCallback,
+ useMemo,
+} from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import {
Modal,
@@ -43,6 +49,7 @@ function CommandMenuLoader( { name, search, hook, setLoader, close } ) {
key={ command.name }
value={ command.searchLabel ?? command.label }
onSelect={ () => command.callback( { close } ) }
+ id={ command.name }
>
command.callback( { close } ) }
+ id={ command.name }
>
state.value );
+ const selectedItemId = useMemo( () => {
+ const item = document.querySelector(
+ `[cmdk-item=""][data-value="${ _value }"]`
+ );
+ return item?.getAttribute( 'id' );
+ }, [ _value ] );
+ useEffect( () => {
+ // Focus the command palette input when mounting the modal.
+ if ( isOpen ) {
+ commandMenuInput.current.focus();
+ }
+ }, [ isOpen ] );
+ return (
+
+ );
+}
+
export function CommandMenu() {
const { registerShortcut } = useDispatch( keyboardShortcutsStore );
const [ search, setSearch ] = useState( '' );
@@ -149,7 +183,6 @@ export function CommandMenu() {
);
const { open, close } = useDispatch( commandsStore );
const [ loaders, setLoaders ] = useState( {} );
- const commandMenuInput = useRef();
useEffect( () => {
registerShortcut( {
@@ -191,16 +224,23 @@ export function CommandMenu() {
close();
};
- useEffect( () => {
- // Focus the command palette input when mounting the modal.
- if ( isOpen ) {
- commandMenuInput.current.focus();
- }
- }, [ isOpen ] );
-
if ( ! isOpen ) {
return false;
}
+
+ const onKeyDown = ( event ) => {
+ if (
+ // Ignore keydowns from IMEs
+ event.nativeEvent.isComposing ||
+ // Workaround for Mac Safari where the final Enter/Backspace of an IME composition
+ // is `isComposing=false`, even though it's technically still part of the composition.
+ // These can only be detected by keyCode.
+ event.keyCode === 229
+ ) {
+ event.preventDefault();
+ }
+ };
+
const isLoading = Object.values( loaders ).some( Boolean );
return (
@@ -211,13 +251,15 @@ export function CommandMenu() {
__experimentalHideHeader
>
-
+
-
diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss
index 11114cce856ba..b4545b6ee3844 100644
--- a/packages/commands/src/components/style.scss
+++ b/packages/commands/src/components/style.scss
@@ -116,8 +116,8 @@
position: relative;
}
- [cmdk-group]:has([cmdk-group-items]:not(:empty)) + [cmdk-group]:has([cmdk-group-items]:not(:empty)) {
- border-top: 1px solid $gray-200;
+ [cmdk-group]:has([cmdk-group-items]:not(:empty)):not([hidden]) + [cmdk-group]:has([cmdk-group-items]:not(:empty)) {
+ border-top: $border-width solid $gray-200;
}
}
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 45ed6d2ec13fc..c903989f45628 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -2,6 +2,14 @@
## Unreleased
+### Enhancements
+
+- `ColorPalette`, `BorderControl`: Don't hyphenate hex value in `aria-label` ([#52932](https://github.com/WordPress/gutenberg/pull/52932)).
+
+### Bug Fix
+
+- `Modal`: Fix loss of focus when clicking outside ([#52653](https://github.com/WordPress/gutenberg/pull/52653)).
+
## 25.4.0 (2023-07-20)
### Enhancements
diff --git a/packages/components/src/border-control/border-control-dropdown/component.tsx b/packages/components/src/border-control/border-control-dropdown/component.tsx
index c9998af094fbd..8eb220a0209c1 100644
--- a/packages/components/src/border-control/border-control-dropdown/component.tsx
+++ b/packages/components/src/border-control/border-control-dropdown/component.tsx
@@ -30,12 +30,8 @@ import type { DropdownProps as DropdownComponentProps } from '../../dropdown/typ
import type { ColorProps, DropdownProps } from '../types';
const getAriaLabelColorValue = ( colorValue: string ) => {
- const isHex = colorValue.startsWith( '#' );
-
// Leave hex values as-is. Remove the `var()` wrapper from CSS vars.
- const displayValue = colorValue.replace( /^var\((.+)\)$/, '$1' );
-
- return isHex ? displayValue.split( '' ).join( '-' ) : displayValue;
+ return colorValue.replace( /^var\((.+)\)$/, '$1' );
};
const getColorObject = (
@@ -79,14 +75,14 @@ const getToggleAriaLabel = (
const ariaLabelValue = getAriaLabelColorValue( colorObject.color );
return style
? sprintf(
- // translators: %1$s: The name of the color e.g. "vivid red". %2$s: The color's hex code, with added hyphens e.g: "#-f-0-0". %3$s: The current border style selection e.g. "solid".
+ // translators: %1$s: The name of the color e.g. "vivid red". %2$s: The color's hex code e.g.: "#f00:". %3$s: The current border style selection e.g. "solid".
'Border color and style picker. The currently selected color is called "%1$s" and has a value of "%2$s". The currently selected style is "%3$s".',
colorObject.name,
ariaLabelValue,
style
)
: sprintf(
- // translators: %1$s: The name of the color e.g. "vivid red". %2$s: The color's hex code, with added hyphens e.g: "#-f-0-0".
+ // translators: %1$s: The name of the color e.g. "vivid red". %2$s: The color's hex code e.g.: "#f00:".
'Border color and style picker. The currently selected color is called "%1$s" and has a value of "%2$s".',
colorObject.name,
ariaLabelValue
@@ -97,13 +93,13 @@ const getToggleAriaLabel = (
const ariaLabelValue = getAriaLabelColorValue( colorValue );
return style
? sprintf(
- // translators: %1$s: The color's hex code, with added hyphens e.g: "#-f-0-0". %2$s: The current border style selection e.g. "solid".
+ // translators: %1$s: The color's hex code e.g.: "#f00:". %2$s: The current border style selection e.g. "solid".
'Border color and style picker. The currently selected color has a value of "%1$s". The currently selected style is "%2$s".',
ariaLabelValue,
style
)
: sprintf(
- // translators: %1$s: The color's hex code, with added hyphens e.g: "#-f-0-0".
+ // translators: %1$s: The color's hex code e.g: "#f00".
'Border color and style picker. The currently selected color has a value of "%1$s".',
ariaLabelValue
);
@@ -114,7 +110,7 @@ const getToggleAriaLabel = (
if ( colorObject ) {
return sprintf(
- // translators: %1$s: The name of the color e.g. "vivid red". %2$s: The color's hex code, with added hyphens e.g: "#-f-0-0".
+ // translators: %1$s: The name of the color e.g. "vivid red". %2$s: The color's hex code e.g: "#f00".
'Border color picker. The currently selected color is called "%1$s" and has a value of "%2$s".',
colorObject.name,
getAriaLabelColorValue( colorObject.color )
@@ -123,7 +119,7 @@ const getToggleAriaLabel = (
if ( colorValue ) {
return sprintf(
- // translators: %1$s: The color's hex code, with added hyphens e.g: "#-f-0-0".
+ // translators: %1$s: The color's hex code e.g: "#f00".
'Border color picker. The currently selected color has a value of "%1$s".',
getAriaLabelColorValue( colorValue )
);
diff --git a/packages/components/src/border-control/test/index.js b/packages/components/src/border-control/test/index.js
index ae54ce5c55a84..4e971e59e87b2 100644
--- a/packages/components/src/border-control/test/index.js
+++ b/packages/components/src/border-control/test/index.js
@@ -215,7 +215,7 @@ describe( 'BorderControl', () => {
expect(
screen.getByLabelText(
- 'Border color and style picker. The currently selected color is called "Blue" and has a value of "#-7-2-a-e-e-6".'
+ 'Border color and style picker. The currently selected color is called "Blue" and has a value of "#72aee6".'
)
).toBeInTheDocument();
} );
@@ -226,7 +226,7 @@ describe( 'BorderControl', () => {
expect(
screen.getByLabelText(
- 'Border color and style picker. The currently selected color has a value of "#-4-b-1-d-8-0".'
+ 'Border color and style picker. The currently selected color has a value of "#4b1d80".'
)
).toBeInTheDocument();
} );
@@ -239,7 +239,7 @@ describe( 'BorderControl', () => {
expect(
screen.getByLabelText(
- 'Border color and style picker. The currently selected color is called "Blue" and has a value of "#-7-2-a-e-e-6". The currently selected style is "dotted".'
+ 'Border color and style picker. The currently selected color is called "Blue" and has a value of "#72aee6". The currently selected style is "dotted".'
)
).toBeInTheDocument();
} );
@@ -252,7 +252,7 @@ describe( 'BorderControl', () => {
expect(
screen.getByLabelText(
- 'Border color and style picker. The currently selected color has a value of "#-4-b-1-d-8-0". The currently selected style is "dashed".'
+ 'Border color and style picker. The currently selected color has a value of "#4b1d80". The currently selected style is "dashed".'
)
).toBeInTheDocument();
} );
@@ -280,7 +280,7 @@ describe( 'BorderControl', () => {
expect(
screen.getByLabelText(
- 'Border color picker. The currently selected color is called "Blue" and has a value of "#-7-2-a-e-e-6".'
+ 'Border color picker. The currently selected color is called "Blue" and has a value of "#72aee6".'
)
).toBeInTheDocument();
} );
@@ -294,7 +294,7 @@ describe( 'BorderControl', () => {
expect(
screen.getByLabelText(
- 'Border color picker. The currently selected color has a value of "#-4-b-1-d-8-0".'
+ 'Border color picker. The currently selected color has a value of "#4b1d80".'
)
).toBeInTheDocument();
} );
diff --git a/packages/components/src/color-palette/index.tsx b/packages/components/src/color-palette/index.tsx
index 2347897540f63..b3b46a68ff21f 100644
--- a/packages/components/src/color-palette/index.tsx
+++ b/packages/components/src/color-palette/index.tsx
@@ -224,12 +224,12 @@ function UnforwardedColorPalette(
const displayValue = value?.replace( /^var\((.+)\)$/, '$1' );
const customColorAccessibleLabel = !! displayValue
? sprintf(
- // translators: %1$s: The name of the color e.g: "vivid red". %2$s: The color's hex code, with added hyphens e.g: "#-f-0-0".
+ // translators: %1$s: The name of the color e.g: "vivid red". %2$s: The color's hex code e.g: "#f00".
__(
'Custom color picker. The currently selected color is called "%1$s" and has a value of "%2$s".'
),
buttonLabelName,
- isHex ? displayValue.split( '' ).join( '-' ) : displayValue
+ displayValue
)
: __( 'Custom color picker.' );
diff --git a/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap b/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap
index 8c7557366e37d..a08760a33e252 100644
--- a/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap
+++ b/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap
@@ -193,7 +193,7 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = `
diff --git a/packages/components/src/color-palette/test/index.tsx b/packages/components/src/color-palette/test/index.tsx
index 90254463ef7a7..65ed02f9e7b2b 100644
--- a/packages/components/src/color-palette/test/index.tsx
+++ b/packages/components/src/color-palette/test/index.tsx
@@ -240,11 +240,7 @@ describe( 'ColorPalette', () => {
expect( screen.getByText( EXAMPLE_COLORS[ 0 ].color ) ).toBeVisible();
expect(
screen.getByRole( 'button', {
- name: `Custom color picker. The currently selected color is called "${
- EXAMPLE_COLORS[ 0 ].name
- }" and has a value of "${ EXAMPLE_COLORS[ 0 ].color
- .split( '' )
- .join( '-' ) }".`,
+ name: `Custom color picker. The currently selected color is called "${ EXAMPLE_COLORS[ 0 ].name }" and has a value of "${ EXAMPLE_COLORS[ 0 ].color }".`,
expanded: false,
} )
).toBeInTheDocument();
diff --git a/packages/components/src/modal/index.tsx b/packages/components/src/modal/index.tsx
index d9c7b602b8392..7667a815561a8 100644
--- a/packages/components/src/modal/index.tsx
+++ b/packages/components/src/modal/index.tsx
@@ -170,6 +170,15 @@ function UnforwardedModal(
[ hasScrolledContent ]
);
+ const onOverlayPress: React.PointerEventHandler< HTMLDivElement > = (
+ event
+ ) => {
+ if ( event.target === event.currentTarget ) {
+ event.preventDefault();
+ onRequestClose( event );
+ }
+ };
+
return createPortal(
// eslint-disable-next-line jsx-a11y/no-static-element-interactions