From bc703ddfdcf0cb6185de1771acd8c98d151738e5 Mon Sep 17 00:00:00 2001 From: Bernie Reiter <96308+ockham@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:36:22 +0200 Subject: [PATCH 01/21] Post Date: Add block deprecation to change source arg to field (#72483) Add a block deprecation for the Post Date block to update the block bindings source arg from `key` to `field`. Co-authored-by: ockham Co-authored-by: ntsekouras --- .../block-library/src/post-date/deprecated.js | 100 +++++++++++++++++- packages/block-library/src/post-date/edit.js | 3 +- .../block-library/src/post-date/variations.js | 28 ++--- packages/editor/src/bindings/post-data.js | 4 +- .../core__post-date__deprecated-v3.html | 1 + .../core__post-date__deprecated-v3.json | 20 ++++ ...core__post-date__deprecated-v3.parsed.json | 20 ++++ ...__post-date__deprecated-v3.serialized.html | 1 + 8 files changed, 153 insertions(+), 24 deletions(-) create mode 100644 test/integration/fixtures/blocks/core__post-date__deprecated-v3.html create mode 100644 test/integration/fixtures/blocks/core__post-date__deprecated-v3.json create mode 100644 test/integration/fixtures/blocks/core__post-date__deprecated-v3.parsed.json create mode 100644 test/integration/fixtures/blocks/core__post-date__deprecated-v3.serialized.html diff --git a/packages/block-library/src/post-date/deprecated.js b/packages/block-library/src/post-date/deprecated.js index fc829ba554416e..875a64faef03ff 100644 --- a/packages/block-library/src/post-date/deprecated.js +++ b/packages/block-library/src/post-date/deprecated.js @@ -8,6 +8,104 @@ import clsx from 'clsx'; */ import migrateFontFamily from '../utils/migrate-font-family'; +const v3 = { + attributes: { + datetime: { + type: 'string', + role: 'content', + }, + textAlign: { + type: 'string', + }, + format: { + type: 'string', + }, + isLink: { + type: 'boolean', + default: false, + role: 'content', + }, + }, + supports: { + html: false, + color: { + gradients: true, + link: true, + __experimentalDefaultControls: { + background: true, + text: true, + link: true, + }, + }, + spacing: { + margin: true, + padding: true, + }, + typography: { + fontSize: true, + lineHeight: true, + __experimentalFontFamily: true, + __experimentalFontWeight: true, + __experimentalFontStyle: true, + __experimentalTextTransform: true, + __experimentalTextDecoration: true, + __experimentalLetterSpacing: true, + __experimentalDefaultControls: { + fontSize: true, + }, + }, + interactivity: { + clientNavigation: true, + }, + __experimentalBorder: { + radius: true, + color: true, + width: true, + style: true, + __experimentalDefaultControls: { + radius: true, + color: true, + width: true, + style: true, + }, + }, + }, + save() { + return null; + }, + migrate( { + metadata: { + bindings: { + datetime: { + source, + args: { key, ...otherArgs }, + }, + ...otherBindings + }, + ...otherMetadata + }, + ...otherAttributes + } ) { + // Change the block bindings source argument name from "key" to "field". + return { + metadata: { + bindings: { + datetime: { + source, + args: { field: key, ...otherArgs }, + }, + ...otherBindings, + }, + ...otherMetadata, + }, + ...otherAttributes, + }; + }, + isEligible( attributes ) { + return !! attributes?.metadata?.bindings?.datetime?.args?.key; + }, +}; + const v2 = { attributes: { textAlign: { @@ -152,4 +250,4 @@ const v1 = { * * See block-deprecation.md */ -export default [ v2, v1 ]; +export default [ v3, v2, v1 ]; diff --git a/packages/block-library/src/post-date/edit.js b/packages/block-library/src/post-date/edit.js index 7b9573833a0614..d995b39f99c648 100644 --- a/packages/block-library/src/post-date/edit.js +++ b/packages/block-library/src/post-date/edit.js @@ -48,8 +48,7 @@ export default function PostDateEdit( { } ) { const displayType = metadata?.bindings?.datetime?.source === 'core/post-data' && - ( metadata?.bindings?.datetime?.args?.field || - metadata?.bindings?.datetime?.args?.key ); + metadata?.bindings?.datetime?.args?.field; const blockProps = useBlockProps( { className: clsx( { diff --git a/packages/block-library/src/post-date/variations.js b/packages/block-library/src/post-date/variations.js index 5a45b90c623e5c..74490b349c37ff 100644 --- a/packages/block-library/src/post-date/variations.js +++ b/packages/block-library/src/post-date/variations.js @@ -20,15 +20,11 @@ const variations = [ }, }, scope: [ 'block', 'inserter', 'transform' ], - isActive: ( blockAttributes ) => { - const fieldValue = - blockAttributes?.metadata?.bindings?.datetime?.args?.field || - blockAttributes?.metadata?.bindings?.datetime?.args?.key; - return ( - blockAttributes?.metadata?.bindings?.datetime?.source === - 'core/post-data' && fieldValue === 'date' - ); - }, + isActive: ( blockAttributes ) => + blockAttributes?.metadata?.bindings?.datetime?.source === + 'core/post-data' && + blockAttributes?.metadata?.bindings?.datetime?.args?.field === + 'date', icon: postDate, }, { @@ -47,15 +43,11 @@ const variations = [ className: 'wp-block-post-date__modified-date', }, scope: [ 'block', 'inserter', 'transform' ], - isActive: ( blockAttributes ) => { - const fieldValue = - blockAttributes?.metadata?.bindings?.datetime?.args?.field || - blockAttributes?.metadata?.bindings?.datetime?.args?.key; - return ( - blockAttributes?.metadata?.bindings?.datetime?.source === - 'core/post-data' && fieldValue === 'modified' - ); - }, + isActive: ( blockAttributes ) => + blockAttributes?.metadata?.bindings?.datetime?.source === + 'core/post-data' && + blockAttributes?.metadata?.bindings?.datetime?.args?.field === + 'modified', icon: postDate, }, ]; diff --git a/packages/editor/src/bindings/post-data.js b/packages/editor/src/bindings/post-data.js index c80ca9d944c44e..7ca7e265ac7822 100644 --- a/packages/editor/src/bindings/post-data.js +++ b/packages/editor/src/bindings/post-data.js @@ -107,9 +107,7 @@ export default { const newValues = {}; for ( const [ attributeName, source ] of Object.entries( bindings ) ) { // Use the value, the field label, or the field key. - const fieldKey = globalThis.IS_GUTENBERG_PLUGIN - ? source.args.field || source.args.key - : source.args.field; + const fieldKey = source.args.field; const { value: fieldValue, label: fieldLabel } = dataFields?.[ fieldKey ] || {}; newValues[ attributeName ] = fieldValue ?? fieldLabel ?? fieldKey; diff --git a/test/integration/fixtures/blocks/core__post-date__deprecated-v3.html b/test/integration/fixtures/blocks/core__post-date__deprecated-v3.html new file mode 100644 index 00000000000000..8c2f8584aa5c46 --- /dev/null +++ b/test/integration/fixtures/blocks/core__post-date__deprecated-v3.html @@ -0,0 +1 @@ + diff --git a/test/integration/fixtures/blocks/core__post-date__deprecated-v3.json b/test/integration/fixtures/blocks/core__post-date__deprecated-v3.json new file mode 100644 index 00000000000000..11bcf25a6b6a61 --- /dev/null +++ b/test/integration/fixtures/blocks/core__post-date__deprecated-v3.json @@ -0,0 +1,20 @@ +[ + { + "name": "core/post-date", + "isValid": true, + "attributes": { + "metadata": { + "bindings": { + "datetime": { + "source": "core/post-data", + "args": { + "field": "date" + } + } + } + }, + "isLink": false + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__post-date__deprecated-v3.parsed.json b/test/integration/fixtures/blocks/core__post-date__deprecated-v3.parsed.json new file mode 100644 index 00000000000000..63e8f55ebc7b67 --- /dev/null +++ b/test/integration/fixtures/blocks/core__post-date__deprecated-v3.parsed.json @@ -0,0 +1,20 @@ +[ + { + "blockName": "core/post-date", + "attrs": { + "metadata": { + "bindings": { + "datetime": { + "source": "core/post-data", + "args": { + "key": "date" + } + } + } + } + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } +] diff --git a/test/integration/fixtures/blocks/core__post-date__deprecated-v3.serialized.html b/test/integration/fixtures/blocks/core__post-date__deprecated-v3.serialized.html new file mode 100644 index 00000000000000..a7d235d3ae5d5a --- /dev/null +++ b/test/integration/fixtures/blocks/core__post-date__deprecated-v3.serialized.html @@ -0,0 +1 @@ + From 6a0e7e14bcfc04e7f1b39850159e2727822e3c2d Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Mon, 20 Oct 2025 09:37:40 -0500 Subject: [PATCH 02/21] Fix empty url value from unbinding entity from inspector sidebar (#72447) When unsyncing a navigation link url, we now set the url as the synced entity url to avoid empty url states which cause bugs in the canvas. We also defer updating the url value in the store until the blur event to avoid the canvas receiving a temporarily empty value (such as when a user clears the URL in order to start typing a new URL). ---- Co-authored-by: jeryj Co-authored-by: getdave Co-authored-by: ockham --- .../src/navigation-link/shared/controls.js | 59 +++++++++++++------ .../navigation-link/shared/test/controls.js | 23 +++++--- .../specs/editor/blocks/navigation.spec.js | 2 +- 3 files changed, 57 insertions(+), 27 deletions(-) diff --git a/packages/block-library/src/navigation-link/shared/controls.js b/packages/block-library/src/navigation-link/shared/controls.js index c8c5909493198c..5f4eacf446d297 100644 --- a/packages/block-library/src/navigation-link/shared/controls.js +++ b/packages/block-library/src/navigation-link/shared/controls.js @@ -11,7 +11,7 @@ import { TextareaControl, } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { useRef, useEffect } from '@wordpress/element'; +import { useRef, useEffect, useState } from '@wordpress/element'; import { useInstanceId } from '@wordpress/compose'; import { safeDecodeURI } from '@wordpress/url'; import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; @@ -77,6 +77,15 @@ export function Controls( { attributes, setAttributes, clientId } ) { const inputId = useInstanceId( Controls, 'link-input' ); const helpTextId = `${ inputId }__help`; + // Local state to control the input value + const [ inputValue, setInputValue ] = useState( url ); + + // Sync local state when url prop changes (e.g., from undo/redo or external updates) + useEffect( () => { + setInputValue( url ); + lastURLRef.current = url; + }, [ url ] ); + // Use the entity binding hook internally const { hasUrlBinding, clearBinding } = useEntityBinding( { clientId, @@ -95,12 +104,19 @@ export function Controls( { attributes, setAttributes, clientId } ) { // setAttributes is actually setBoundAttributes, a wrapper function that // processes attributes through the binding system. // See: packages/block-editor/src/components/block-edit/edit.js - updateBlockAttributes( clientId, { url: '', id: undefined } ); + updateBlockAttributes( clientId, { + url: lastURLRef.current, // set the lastURLRef as the new editable value so we avoid bugs from empty link states + id: undefined, + } ); }; useEffect( () => { + // Checking for ! hasUrlBinding is a defensive check, as we would + // only want to focus the input if the url is not bound to an entity. if ( ! hasUrlBinding && shouldFocusURLInputRef.current ) { - urlInputRef.current?.focus(); + // focuses and highlights the url input value, giving the user + // the ability to delete the value quickly or edit it. + urlInputRef.current?.select(); } shouldFocusURLInputRef.current = false; }, [ hasUrlBinding ] ); @@ -149,18 +165,20 @@ export function Controls( { attributes, setAttributes, clientId } ) { __next40pxDefaultSize id={ inputId } label={ __( 'Link' ) } - value={ url ? safeDecodeURI( url ) : '' } - onChange={ ( urlValue ) => { - if ( hasUrlBinding ) { - return; // Prevent editing when URL is bound - } - setAttributes( { - url: encodeURI( safeDecodeURI( urlValue ) ), - } ); - } } + value={ inputValue ? safeDecodeURI( inputValue ) : '' } autoComplete="off" type="url" disabled={ hasUrlBinding } + onChange={ ( newValue ) => { + if ( hasUrlBinding ) { + return; + } + + // Defer updating the url attribute until onBlur to prevent the canvas from + // treating a temporary empty value as a committed value, which replaces the + // label with placeholder text. + setInputValue( newValue ); + } } onFocus={ () => { if ( hasUrlBinding ) { return; @@ -171,12 +189,19 @@ export function Controls( { attributes, setAttributes, clientId } ) { if ( hasUrlBinding ) { return; } + + const finalValue = ! inputValue + ? lastURLRef.current + : inputValue; + + // Update local state immediately so input reflects the reverted value if the value was cleared + setInputValue( finalValue ); + // Defer the updateAttributes call to ensure entity connection isn't severed by accident. - updateAttributes( - { url: ! url ? lastURLRef.current : url }, - setAttributes, - { ...attributes, url: lastURLRef.current } - ); + updateAttributes( { url: finalValue }, setAttributes, { + ...attributes, + url: lastURLRef.current, + } ); } } help={ hasUrlBinding && ( diff --git a/packages/block-library/src/navigation-link/shared/test/controls.js b/packages/block-library/src/navigation-link/shared/test/controls.js index 028998fb7e7754..5cf12faa521c1e 100644 --- a/packages/block-library/src/navigation-link/shared/test/controls.js +++ b/packages/block-library/src/navigation-link/shared/test/controls.js @@ -6,6 +6,7 @@ * External dependencies */ import { render, screen, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; /** * Internal dependencies @@ -95,18 +96,22 @@ describe( 'Controls', () => { expect( urlInput.value ).toBe( 'https://example.com/test page' ); } ); - it( 'encodes URL values when changed', () => { + it( 'calls updateAttributes with new URL on blur', async () => { + const user = userEvent.setup(); render( ); const urlInput = screen.getByLabelText( 'Link' ); - fireEvent.change( urlInput, { - target: { value: 'https://example.com/test page' }, - } ); + await user.click( urlInput ); + await user.clear( urlInput ); + await user.type( urlInput, 'https://example.com/test page' ); + await user.tab(); - expect( defaultProps.setAttributes ).toHaveBeenCalledWith( { - url: 'https://example.com/test%20page', - } ); + expect( mockUpdateAttributes ).toHaveBeenCalledWith( + { url: 'https://example.com/test page' }, + defaultProps.setAttributes, + { ...defaultProps.attributes, url: 'https://example.com' } + ); } ); it( 'calls updateAttributes on URL blur', () => { @@ -143,11 +148,11 @@ describe( 'Controls', () => { target: { value: 'https://new.com' }, } ); - // Blur should call updateAttributes with the current URL (since url exists) + // Blur should call updateAttributes with the new URL value from the input fireEvent.blur( urlInput ); expect( mockUpdateAttributes ).toHaveBeenCalledWith( - { url: 'https://different.com' }, // Current URL from attributes (not input value) + { url: 'https://new.com' }, // New URL from input value defaultProps.setAttributes, { ...propsWithDifferentUrl.attributes, diff --git a/test/e2e/specs/editor/blocks/navigation.spec.js b/test/e2e/specs/editor/blocks/navigation.spec.js index 599eecae166132..b1f8ba212b03bd 100644 --- a/test/e2e/specs/editor/blocks/navigation.spec.js +++ b/test/e2e/specs/editor/blocks/navigation.spec.js @@ -1069,7 +1069,7 @@ test.describe( 'Navigation block', () => { } ); await unlinkButton.click(); await expect( linkInput ).toBeEnabled(); - await expect( linkInput ).toHaveValue( '' ); + await expect( linkInput ).toHaveValue( updatedPage.link ); await expect( linkInput ).toBeFocused(); } ); } ); From 418ab4c829953ba568a7d4269bb91fd1b490ffc8 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 20 Oct 2025 15:57:15 +0100 Subject: [PATCH 03/21] Add end to end tests for Fit Text (#72406) Co-authored-by: jorgefilipecosta Co-authored-by: mcsf --- test/e2e/specs/editor/blocks/fit-text.spec.js | 414 ++++++++++++++++++ 1 file changed, 414 insertions(+) create mode 100644 test/e2e/specs/editor/blocks/fit-text.spec.js diff --git a/test/e2e/specs/editor/blocks/fit-text.spec.js b/test/e2e/specs/editor/blocks/fit-text.spec.js new file mode 100644 index 00000000000000..ce9f22a561c81d --- /dev/null +++ b/test/e2e/specs/editor/blocks/fit-text.spec.js @@ -0,0 +1,414 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Fit Text', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + + test.describe( 'Editor functionality', () => { + test( 'should enable fit text on a heading block', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'Test Heading', + level: 2, + }, + } ); + + await editor.openDocumentSettingsSidebar(); + + // Enable Fit text control via Typography options menu + await page + .getByRole( 'region', { name: 'Editor settings' } ) + .getByRole( 'button', { name: 'Typography options' } ) + .click(); + await page + .getByRole( 'menu', { name: 'Typography options' } ) + .getByRole( 'menuitemcheckbox', { name: 'Show Fit text' } ) + .click(); + + const fitTextToggle = page.getByRole( 'checkbox', { + name: 'Fit text', + } ); + + await fitTextToggle.click(); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/heading', + attributes: { + content: 'Test Heading', + level: 2, + fitText: true, + }, + }, + ] ); + + const headingBlock = editor.canvas.locator( + '[data-type="core/heading"]' + ); + + await expect( headingBlock ).toHaveClass( /has-fit-text/ ); + } ); + + test( 'should disable fit text when toggled off', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'Test Heading', + level: 2, + fitText: true, + }, + } ); + + await editor.openDocumentSettingsSidebar(); + + const fitTextToggle = page.getByRole( 'checkbox', { + name: 'Fit text', + } ); + + await fitTextToggle.click(); + + const blocks = await editor.getBlocks(); + expect( blocks[ 0 ].attributes.fitText ).toBeUndefined(); + } ); + + test( 'should enable fit text on a paragraph block', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'Test paragraph with fit text enabled', + }, + } ); + + await editor.openDocumentSettingsSidebar(); + + // Enable Fit text control via Typography options menu + await page + .getByRole( 'region', { name: 'Editor settings' } ) + .getByRole( 'button', { name: 'Typography options' } ) + .click(); + await page + .getByRole( 'menu', { name: 'Typography options' } ) + .getByRole( 'menuitemcheckbox', { name: 'Show Fit text' } ) + .click(); + + const fitTextToggle = page.getByRole( 'checkbox', { + name: 'Fit text', + } ); + + await fitTextToggle.click(); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/paragraph', + attributes: { + content: 'Test paragraph with fit text enabled', + fitText: true, + }, + }, + ] ); + + const paragraphBlock = editor.canvas.locator( + '[data-type="core/paragraph"]' + ); + + await expect( paragraphBlock ).toHaveClass( /has-fit-text/ ); + } ); + + test( 'should apply font size dynamically based on container width in editor', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'Resizable Text', + level: 2, + fitText: true, + }, + } ); + + const headingBlock = editor.canvas.locator( + '[data-type="core/heading"]' + ); + + // Wait for fit text to apply + await headingBlock.waitFor( { state: 'attached' } ); + await expect( headingBlock ).toHaveClass( /has-fit-text/ ); + + const initialFontSize = await headingBlock.evaluate( ( el ) => { + return window.getComputedStyle( el ).fontSize; + } ); + + // Add more text to force smaller font size + await headingBlock.click(); + await page.keyboard.press( 'End' ); + await page.keyboard.type( + ' that is much longer and should have smaller font' + ); + + // Wait for DOM to update and fit text to recalculate + await headingBlock.waitFor( { state: 'attached' } ); + + const newFontSize = await headingBlock.evaluate( ( el ) => { + return window.getComputedStyle( el ).fontSize; + } ); + + const initialSize = parseFloat( initialFontSize ); + const newSize = parseFloat( newFontSize ); + + // Font size should decrease with more content + expect( newSize ).toBeLessThan( initialSize ); + } ); + + test( 'should apply much larger font size with fit text compared to without fit text for a short text', async ( { + editor, + } ) => { + // Insert two paragraphs with same content for comparison + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'Hello', + }, + } ); + + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'Hello', + fitText: true, + }, + } ); + + const paragraphBlocks = editor.canvas.locator( + '[data-type="core/paragraph"]' + ); + + // Wait for fit text to apply + await paragraphBlocks.nth( 1 ).waitFor( { state: 'attached' } ); + await expect( paragraphBlocks.nth( 1 ) ).toHaveClass( + /has-fit-text/ + ); + + const normalFontSize = await paragraphBlocks + .nth( 0 ) + .evaluate( ( el ) => { + return window.getComputedStyle( el ).fontSize; + } ); + + const fitTextFontSize = await paragraphBlocks + .nth( 1 ) + .evaluate( ( el ) => { + return window.getComputedStyle( el ).fontSize; + } ); + + const normalSize = parseFloat( normalFontSize ); + const fitTextSize = parseFloat( fitTextFontSize ); + + // Fit text should scale up significantly for short content + expect( fitTextSize ).toBeGreaterThan( normalSize * 2 ); + } ); + } ); + + test.describe( 'Frontend functionality', () => { + test( 'should render fit text correctly on the frontend', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'Frontend Test', + level: 2, + fitText: true, + }, + } ); + + await editor.publishPost(); + + const postUrl = await page.evaluate( () => + window.wp.data.select( 'core/editor' ).getPermalink() + ); + + await page.goto( postUrl ); + + const heading = page.locator( 'h2.has-fit-text' ); + + await expect( heading ).toBeVisible(); + await expect( heading ).toHaveClass( /has-fit-text/ ); + + // Verify data attribute is set (added by frontend script) + const fitTextId = await heading.getAttribute( 'data-fit-text-id' ); + expect( fitTextId ).toBeTruthy(); + + // Verify style element exists for this fit text instance. + const styleElement = page.locator( + `style#fit-text-${ fitTextId }` + ); + await expect( styleElement ).toBeAttached(); + + const computedFontSize = await heading.evaluate( ( el ) => { + return window.getComputedStyle( el ).fontSize; + } ); + + const styleContent = await styleElement.textContent(); + const fontSizeMatch = styleContent.match( + /font-size:\s*(\d+(?:\.\d+)?)px/ + ); + expect( fontSizeMatch ).toBeTruthy(); + const expectedFontSize = parseFloat( fontSizeMatch[ 1 ] ); + + expect( parseFloat( computedFontSize ) ).toBe( expectedFontSize ); + } ); + + test( 'should resize text on window resize on the frontend', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/heading', + attributes: { + content: 'Resize Me', + level: 2, + fitText: true, + }, + } ); + + await editor.publishPost(); + + const postUrl = await page.evaluate( () => + window.wp.data.select( 'core/editor' ).getPermalink() + ); + + await page.goto( postUrl ); + + const heading = page.locator( 'h2.has-fit-text' ); + + // Wait for fit text to initialize (verify frontend script ran) + await heading.waitFor( { state: 'attached' } ); + const fitTextId = await heading.getAttribute( 'data-fit-text-id' ); + expect( fitTextId ).toBeTruthy(); + + // Verify style element exists for this fit text instance + const styleElement = page.locator( + `style#fit-text-${ fitTextId }` + ); + await styleElement.waitFor( { state: 'attached' } ); + + const initialFontSize = await heading.evaluate( ( el ) => { + return window.getComputedStyle( el ).fontSize; + } ); + + // Capture style content before resize + const styleBeforeResize = await styleElement.textContent(); + + await page.setViewportSize( { width: 440, height: 720 } ); + + // Wait for fit text to recalculate (style content changes) + await page.waitForFunction( + ( { styleId, previousContent } ) => { + const style = document.getElementById( styleId ); + return ( + style && + style.textContent !== previousContent && + style.textContent.trim().length > 0 + ); + }, + { + styleId: `fit-text-${ fitTextId }`, + previousContent: styleBeforeResize, + }, + { timeout: 5000 } + ); + + // Verify the same element instance is maintained (ID unchanged) + const fitTextIdAfterResize = + await heading.getAttribute( 'data-fit-text-id' ); + expect( fitTextIdAfterResize ).toBe( fitTextId ); + + const newFontSize = await heading.evaluate( ( el ) => { + return window.getComputedStyle( el ).fontSize; + } ); + + const initialSize = parseFloat( initialFontSize ); + const newSize = parseFloat( newFontSize ); + + // Font size should adapt to narrower viewport + expect( newSize ).toBeLessThan( initialSize ); + } ); + + test( 'should apply much larger font size with fit text compared to without fit text on frontend for a short text', async ( { + editor, + page, + } ) => { + // Insert two paragraphs with same content for comparison + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'Hello', + }, + } ); + + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: 'Hello', + fitText: true, + }, + } ); + + await editor.publishPost(); + + const postUrl = await page.evaluate( () => + window.wp.data.select( 'core/editor' ).getPermalink() + ); + + await page.goto( postUrl ); + + const fitTextParagraph = page.locator( 'p.has-fit-text' ); + + // Wait for fit text to initialize (verify frontend script ran) + await fitTextParagraph.waitFor( { state: 'visible' } ); + const fitTextId = + await fitTextParagraph.getAttribute( 'data-fit-text-id' ); + expect( fitTextId ).toBeTruthy(); + + // Verify style element exists for this fit text instance + const styleElement = page.locator( + `style#fit-text-${ fitTextId }` + ); + await expect( styleElement ).toBeAttached(); + + const paragraphs = page.locator( 'p' ); + + const normalFontSize = await paragraphs + .first() + .evaluate( ( el ) => { + return window.getComputedStyle( el ).fontSize; + } ); + + const fitTextFontSize = await fitTextParagraph.evaluate( ( el ) => { + return window.getComputedStyle( el ).fontSize; + } ); + + const normalSize = parseFloat( normalFontSize ); + const fitTextSize = parseFloat( fitTextFontSize ); + + // Fit text should scale up significantly for short content + expect( fitTextSize ).toBeGreaterThan( normalSize * 2 ); + } ); + } ); +} ); From d7548cb1d23edbb3efccf511f32484ede023b2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:20:59 +0200 Subject: [PATCH 04/21] Prevent registering private APIs twice (#72493) Co-authored-by: oandregal Co-authored-by: ellatrix Co-authored-by: mcsf --- .../fields/src/actions/duplicate-post.tsx | 34 +++++++---- packages/fields/src/actions/reorder-page.tsx | 60 ++++++++++++------- 2 files changed, 59 insertions(+), 35 deletions(-) diff --git a/packages/fields/src/actions/duplicate-post.tsx b/packages/fields/src/actions/duplicate-post.tsx index 034ecf9d4182ac..7af2131f9eee35 100644 --- a/packages/fields/src/actions/duplicate-post.tsx +++ b/packages/fields/src/actions/duplicate-post.tsx @@ -6,25 +6,32 @@ import { store as coreStore } from '@wordpress/core-data'; import { __, sprintf, _x } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { useState } from '@wordpress/element'; -import { DataForm } from '@wordpress/dataviews'; import { Button, __experimentalHStack as HStack, __experimentalVStack as VStack, + __experimentalInputControl as InputControl, } from '@wordpress/components'; -import type { Action } from '@wordpress/dataviews'; /** * Internal dependencies */ -import { titleField } from '../fields'; import type { BasePost, CoreDataError } from '../types'; import { getItemTitle } from './utils'; -const fields = [ titleField ]; -const formDuplicateAction = { - fields: [ 'title' ], -}; +interface RenderModalProps< Item > { + items: Item[]; + closeModal?: () => void; + onActionPerformed?: ( items: Item[] ) => void; +} + +interface Action< Item > { + id: string; + label: string; + isEligible?: ( item: Item ) => boolean; + modalFocusOnMount?: string; + RenderModal: ( props: RenderModalProps< Item > ) => JSX.Element; +} const duplicatePost: Action< BasePost > = { id: 'duplicate-post', @@ -149,14 +156,15 @@ const duplicatePost: Action< BasePost > = { ) } ) } - + setItem( ( prev ) => ( { ...prev, - ...changes, + title: value || __( 'No title' ), } ) ) } /> diff --git a/packages/fields/src/actions/reorder-page.tsx b/packages/fields/src/actions/reorder-page.tsx index 2c92f7fd7bb460..cce7b24ef38935 100644 --- a/packages/fields/src/actions/reorder-page.tsx +++ b/packages/fields/src/actions/reorder-page.tsx @@ -6,24 +6,39 @@ import { store as coreStore } from '@wordpress/core-data'; import { __ } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { useState } from '@wordpress/element'; -import { DataForm, useFormValidity } from '@wordpress/dataviews'; import { Button, __experimentalHStack as HStack, __experimentalVStack as VStack, + __experimentalInputControl as InputControl, } from '@wordpress/components'; -import type { Action, RenderModalProps } from '@wordpress/dataviews'; /** * Internal dependencies */ import type { CoreDataError, BasePost } from '../types'; -import { orderField } from '../fields'; -const fields = [ orderField ]; -const formOrderAction = { - fields: [ 'menu_order' ], -}; +interface RenderModalProps< Item > { + items: Item[]; + closeModal?: () => void; + onActionPerformed?: ( items: Item[] ) => void; +} + +interface Action< Item > { + id: string; + label: string; + isEligible?: ( item: Item ) => boolean; + modalFocusOnMount?: string; + RenderModal: ( props: RenderModalProps< Item > ) => JSX.Element; +} + +function isItemValid( item: BasePost ): boolean { + return ( + typeof item.menu_order === 'number' && + Number.isInteger( item.menu_order ) && + item.menu_order > 0 + ); +} function ReorderModal( { items, @@ -31,17 +46,12 @@ function ReorderModal( { onActionPerformed, }: RenderModalProps< BasePost > ) { const [ item, setItem ] = useState( items[ 0 ] ); - const orderInput = item.menu_order; const { editEntityRecord, saveEditedEntityRecord } = useDispatch( coreStore ); const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); - const { validity, isValid } = useFormValidity( - item, - fields, - formOrderAction - ); + const isValid = isItemValid( item ); async function onOrder( event: React.FormEvent ) { event.preventDefault(); @@ -52,7 +62,7 @@ function ReorderModal( { try { await editEntityRecord( 'postType', item.type, item.id, { - menu_order: orderInput, + menu_order: item.menu_order, } ); closeModal?.(); // Persist edited entity. @@ -83,15 +93,21 @@ function ReorderModal( { 'Determines the order of pages. Pages with the same order value are sorted alphabetically. Negative order values are supported.' ) } - { - return setItem( { + { + const parsed = parseInt( value as string, 10 ); // absorbs '' and undefined + setItem( { ...item, - ...changes, + menu_order: isNaN( parsed ) ? undefined : parsed, } ); } } /> From b1c6b59917a2ad9ff1836be52a895ee74a26af11 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Mon, 20 Oct 2025 15:58:51 +0000 Subject: [PATCH 05/21] Bump plugin version to 21.9.0-rc.2 --- gutenberg.php | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 432366e8f76881..86527a61326dc4 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.7 * Requires PHP: 7.2 - * Version: 21.9.0-rc.1 + * Version: 21.9.0-rc.2 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 0b439f7b27e4bd..5a791e51b84365 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "21.9.0-rc.1", + "version": "21.9.0-rc.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "21.9.0-rc.1", + "version": "21.9.0-rc.2", "hasInstallScript": true, "license": "GPL-2.0-or-later", "workspaces": [ diff --git a/package.json b/package.json index 638781233c0023..c15ea88a3320b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "21.9.0-rc.1", + "version": "21.9.0-rc.2", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From 71650deadec03389761dd0a44159c6cb90048677 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Mon, 20 Oct 2025 16:21:20 +0000 Subject: [PATCH 06/21] Update Changelog for 21.9.0-rc.2 --- changelog.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/changelog.txt b/changelog.txt index 7497839653ac17..79540c403fed88 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,30 @@ == Changelog == += 21.9.0-rc.2 = + + +## Changelog + +### Bug Fixes + +- Prevent registering private APIs twice. ([72493](https://github.com/WordPress/gutenberg/pull/72493)) + +#### Collaboration +- Notes: Don't render 'no results' message for floating sidebar. ([72484](https://github.com/WordPress/gutenberg/pull/72484)) + +#### Block bindings +- Post Date: Add block deprecation to change source arg to field. ([72483](https://github.com/WordPress/gutenberg/pull/72483)) + + + + +## Contributors + +The following contributors merged PRs in this release: + +@Mamaduka @oandregal @ockham + + = 21.9.0-rc.1 = From 8b865d54a8fd21b27204fb0d46ec404d12456839 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Mon, 20 Oct 2025 17:35:19 +0000 Subject: [PATCH 07/21] chore(release): publish - @wordpress/block-directory@5.33.1 - @wordpress/block-library@9.33.1 - @wordpress/customize-widgets@5.33.1 - @wordpress/edit-post@8.33.1 - @wordpress/edit-site@6.33.1 - @wordpress/edit-widgets@6.33.1 - @wordpress/editor@14.33.1 - @wordpress/fields@0.25.1 --- package-lock.json | 16 ++++++++-------- packages/block-directory/package.json | 2 +- packages/block-library/package.json | 2 +- packages/customize-widgets/package.json | 2 +- packages/edit-post/package.json | 2 +- packages/edit-site/package.json | 2 +- packages/edit-widgets/package.json | 2 +- packages/editor/package.json | 2 +- packages/fields/package.json | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5a791e51b84365..919180c52b20fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51215,7 +51215,7 @@ }, "packages/block-directory": { "name": "@wordpress/block-directory", - "version": "5.33.0", + "version": "5.33.1", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/a11y": "file:../a11y", @@ -51352,7 +51352,7 @@ }, "packages/block-library": { "name": "@wordpress/block-library", - "version": "9.33.0", + "version": "9.33.1", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/a11y": "file:../a11y", @@ -51964,7 +51964,7 @@ }, "packages/customize-widgets": { "name": "@wordpress/customize-widgets", - "version": "5.33.0", + "version": "5.33.1", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/base-styles": "file:../base-styles", @@ -52252,7 +52252,7 @@ }, "packages/edit-post": { "name": "@wordpress/edit-post", - "version": "8.33.0", + "version": "8.33.1", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/a11y": "file:../a11y", @@ -52299,7 +52299,7 @@ }, "packages/edit-site": { "name": "@wordpress/edit-site", - "version": "6.33.0", + "version": "6.33.1", "license": "GPL-2.0-or-later", "dependencies": { "@react-spring/web": "^9.4.5", @@ -52363,7 +52363,7 @@ }, "packages/edit-widgets": { "name": "@wordpress/edit-widgets", - "version": "6.33.0", + "version": "6.33.1", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/api-fetch": "file:../api-fetch", @@ -52406,7 +52406,7 @@ }, "packages/editor": { "name": "@wordpress/editor", - "version": "14.33.0", + "version": "14.33.1", "license": "GPL-2.0-or-later", "dependencies": { "@floating-ui/react-dom": "2.0.8", @@ -52591,7 +52591,7 @@ }, "packages/fields": { "name": "@wordpress/fields", - "version": "0.25.0", + "version": "0.25.1", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/api-fetch": "file:../api-fetch", diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index c7e465aa4f9024..3b0de31523efd1 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-directory", - "version": "5.33.0", + "version": "5.33.1", "description": "Extend editor with block directory features to search, download and install blocks.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 4eddbbfbc2886a..3933af6729fc98 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "9.33.0", + "version": "9.33.1", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/customize-widgets/package.json b/packages/customize-widgets/package.json index a449ac0d670b8e..205a670693eda1 100644 --- a/packages/customize-widgets/package.json +++ b/packages/customize-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/customize-widgets", - "version": "5.33.0", + "version": "5.33.1", "description": "Widgets blocks in Customizer Module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 128ac87e268064..0fc432f262f24c 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "8.33.0", + "version": "8.33.1", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index eedda6c086e31a..7515b5003885b8 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-site", - "version": "6.33.0", + "version": "6.33.1", "description": "Edit Site Page module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index 0cb8b8e3eb9798..ec515e3f620d7d 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-widgets", - "version": "6.33.0", + "version": "6.33.1", "description": "Widgets Page module for WordPress..", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/package.json b/packages/editor/package.json index fe0e368f04291c..046a72204faf6a 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "14.33.0", + "version": "14.33.1", "description": "Enhanced block editor for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/fields/package.json b/packages/fields/package.json index 7036b276c7bd62..fb5c2a03b1fb8a 100644 --- a/packages/fields/package.json +++ b/packages/fields/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/fields", - "version": "0.25.0", + "version": "0.25.1", "description": "DataViews is a component that provides an API to render datasets using different types of layouts (table, grid, list, etc.).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 1fb8a63b5ad4432b066c03aaba7882bb0ed6df0c Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 20 Oct 2025 23:57:53 +0100 Subject: [PATCH 08/21] Build: Update output folders for scripts and modules (#72482) Co-authored-by: youknowriad Co-authored-by: Mamaduka --- .github/workflows/bundle-size.yml | 2 +- .github/workflows/unit-test.yml | 2 +- bin/packages/build-vendors.mjs | 12 +++--- bin/packages/build.mjs | 29 ++++++++++--- gutenberg.php | 2 +- lib/blocks.php | 14 +++--- lib/client-assets.php | 72 +++++++++++++++---------------- lib/experimental/posts/load.php | 2 +- lib/load.php | 14 +++--- 9 files changed, 84 insertions(+), 65 deletions(-) diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml index 8a024cdea3505f..570484ac4b8993 100644 --- a/.github/workflows/bundle-size.yml +++ b/.github/workflows/bundle-size.yml @@ -61,5 +61,5 @@ jobs: - uses: preactjs/compressed-size-action@946a292cd35bd1088e0d7eb92b69d1a8d5b5d76a # v2.8.0 with: repo-token: '${{ secrets.GITHUB_TOKEN }}' - pattern: '{build/**/*.min.js,build/**/*.css,build-module/**/*.min.js}' + pattern: '{build/scripts/**/*.min.js,build/styles/**/*.css,build/modules/**/*.min.js}' clean-script: 'distclean' diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 0ba4a7b562ec46..55bc636df9acf8 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -157,7 +157,6 @@ jobs: name: build-assets path: | ./build/ - ./build-module/ test-php: name: PHP ${{ matrix.php }}${{ matrix.multisite && ' multisite' || '' }}${{ matrix.wordpress != '' && format( ' (WP {0}) ', matrix.wordpress ) || '' }} on Linux @@ -234,6 +233,7 @@ jobs: uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: name: build-assets + path: build - name: Docker debug information run: | diff --git a/bin/packages/build-vendors.mjs b/bin/packages/build-vendors.mjs index aaca09a6220e13..a558009011605f 100755 --- a/bin/packages/build-vendors.mjs +++ b/bin/packages/build-vendors.mjs @@ -10,7 +10,7 @@ import esbuild from 'esbuild'; const __dirname = path.dirname( fileURLToPath( import.meta.url ) ); const ROOT_DIR = path.resolve( __dirname, '../..' ); -const BUILD_DIR = path.join( ROOT_DIR, 'build' ); +const BUILD_DIR = path.join( ROOT_DIR, 'build', 'scripts' ); const VENDORS_DIR = path.join( BUILD_DIR, 'vendors' ); /** @@ -23,19 +23,19 @@ async function copyReactUMDFiles() { const filesToCopy = [ { from: 'node_modules/react/umd/react.development.js', - to: 'build/vendors/react.js', + to: 'react.js', }, { from: 'node_modules/react/umd/react.production.min.js', - to: 'build/vendors/react.min.js', + to: 'react.min.js', }, { from: 'node_modules/react-dom/umd/react-dom.development.js', - to: 'build/vendors/react-dom.js', + to: 'react-dom.js', }, { from: 'node_modules/react-dom/umd/react-dom.production.min.js', - to: 'build/vendors/react-dom.min.js', + to: 'react-dom.min.js', }, ]; @@ -44,7 +44,7 @@ async function copyReactUMDFiles() { await Promise.all( filesToCopy.map( ( { from, to } ) => { const source = path.join( ROOT_DIR, from ); - const dest = path.join( ROOT_DIR, to ); + const dest = path.join( VENDORS_DIR, to ); return copyFile( source, dest ); } ) ); diff --git a/bin/packages/build.mjs b/bin/packages/build.mjs index 656f791618835d..181a02943c8302 100644 --- a/bin/packages/build.mjs +++ b/bin/packages/build.mjs @@ -523,7 +523,13 @@ async function bundlePackage( packageName ) { if ( packageJson.wpScript ) { const entryPoint = resolveEntryPoint( packageDir, packageJson ); - const outputDir = path.join( PACKAGES_DIR, '..', 'build', packageName ); + const outputDir = path.join( + PACKAGES_DIR, + '..', + 'build', + 'scripts', + packageName + ); const target = browserslistToEsbuild(); const globalName = `wp.${ kebabToCamelCase( packageName ) }`; @@ -572,7 +578,8 @@ async function bundlePackage( packageName ) { const rootBuildModuleDir = path.join( PACKAGES_DIR, '..', - 'build-module', + 'build', + 'modules', packageName ); @@ -615,7 +622,13 @@ async function bundlePackage( packageName ) { if ( packageJson.wpScript ) { const buildStyleDir = path.join( packageDir, 'build-style' ); - const outputDir = path.join( PACKAGES_DIR, '..', 'build', packageName ); + const outputDir = path.join( + PACKAGES_DIR, + '..', + 'build', + 'styles', + packageName + ); const isProduction = process.env.NODE_ENV === 'production'; const cssFiles = await glob( @@ -663,7 +676,13 @@ async function bundlePackage( packageName ) { if ( packageJson.wpCopyFiles ) { const { files, transforms = {} } = packageJson.wpCopyFiles; const sourceDir = path.join( packageDir, 'src' ); - const outputDir = path.join( PACKAGES_DIR, '..', 'build', packageName ); + const outputDir = path.join( + PACKAGES_DIR, + '..', + 'build', + 'scripts', + packageName + ); for ( const filePattern of files ) { const matchedFiles = await glob( @@ -1027,7 +1046,7 @@ async function buildAll() { const isBundled = await bundlePackage( packageName ); const buildTime = Date.now() - startBundleTime; if ( isBundled ) { - console.log( `โœ” Bundled ${ packageName } (${ buildTime }ms)` ); + console.log( ` โœ” Bundled ${ packageName } (${ buildTime }ms)` ); } } ) ); diff --git a/gutenberg.php b/gutenberg.php index 86527a61326dc4..897d2ac133066e 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -55,7 +55,7 @@ function gutenberg_build_files_notice() { */ function gutenberg_pre_init() { global $wp_version; - if ( defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE && ! file_exists( __DIR__ . '/build/blocks' ) ) { + if ( defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE && ! file_exists( __DIR__ . '/build/scripts/blocks' ) ) { add_action( 'admin_notices', 'gutenberg_build_files_notice' ); return; } diff --git a/lib/blocks.php b/lib/blocks.php index 2d6567a6a91537..16206647f9526f 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -12,9 +12,9 @@ function gutenberg_reregister_core_block_types() { // Blocks directory may not exist if working from a fresh clone. $blocks_dirs = array( - __DIR__ . '/../build/block-library/', - __DIR__ . '/../build/edit-widgets/blocks/', - __DIR__ . '/../build/widgets/blocks/', + __DIR__ . '/../build/scripts/block-library/', + __DIR__ . '/../build/scripts/edit-widgets/blocks/', + __DIR__ . '/../build/scripts/widgets/blocks/', ); foreach ( $blocks_dirs as $blocks_dir ) { @@ -124,7 +124,7 @@ function gutenberg_register_core_block_assets( $block_name ) { // else (for development or test) default to use the current time. $default_version = defined( 'GUTENBERG_VERSION' ) && ! SCRIPT_DEBUG ? GUTENBERG_VERSION : time(); - $style_path = "build/block-library/$block_name/"; + $style_path = "build/styles/block-library/$block_name/"; $stylesheet_url = gutenberg_url( $style_path . 'style.css' ); $stylesheet_path = gutenberg_dir_path() . $style_path . ( is_rtl() ? 'style-rtl.css' : 'style.css' ); @@ -152,8 +152,8 @@ function gutenberg_register_core_block_assets( $block_name ) { // Get the path to the block's stylesheet. $theme_style_path = is_rtl() - ? "build/block-library/$block_name/theme-rtl.css" - : "build/block-library/$block_name/theme.css"; + ? "build/styles/block-library/$block_name/theme-rtl.css" + : "build/styles/block-library/$block_name/theme.css"; // If the file exists, enqueue it. if ( file_exists( gutenberg_dir_path() . $theme_style_path ) ) { @@ -168,7 +168,7 @@ function gutenberg_register_core_block_assets( $block_name ) { } } - $editor_style_path = "build/block-library/$block_name/style-editor.css"; + $editor_style_path = "build/styles/block-library/$block_name/style-editor.css"; if ( file_exists( gutenberg_dir_path() . $editor_style_path ) ) { wp_deregister_style( "wp-block-{$block_name}-editor" ); wp_register_style( diff --git a/lib/client-assets.php b/lib/client-assets.php index 1bed2a946f991b..65dfcdc89864b7 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -118,8 +118,8 @@ function gutenberg_override_translation_file( $file, $handle ) { return $file; } - // Ignore scripts that are not found in the expected `build/` location. - $script_path = gutenberg_dir_path() . 'build/' . substr( $handle, 3 ) . '/index.min.js'; + // Ignore scripts that are not found in the expected `build/scripts/` location. + $script_path = gutenberg_dir_path() . 'build/scripts/' . substr( $handle, 3 ) . '/index.min.js'; if ( ! file_exists( $script_path ) ) { return $file; } @@ -176,7 +176,7 @@ function gutenberg_override_style( $styles, $handle, $src, $deps = array(), $ver /** * Registers all the WordPress packages scripts that are in the standardized - * `build/` location. + * `build/scripts/` location. * * @since 4.5.0 * @@ -188,9 +188,9 @@ function gutenberg_register_packages_scripts( $scripts ) { $default_version = defined( 'GUTENBERG_VERSION' ) && ! SCRIPT_DEBUG ? GUTENBERG_VERSION : time(); $file_extension = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '.js' : '.min.js'; - foreach ( glob( gutenberg_dir_path() . 'build/*/index' . $file_extension ) as $path ) { + foreach ( glob( gutenberg_dir_path() . 'build/scripts/*/index' . $file_extension ) as $path ) { // Prefix `wp-` to package directory to get script handle. - // For example, `โ€ฆ/build/a11y/index.min.js` becomes `wp-a11y`. + // For example, `โ€ฆ/build/scripts/a11y/index.min.js` becomes `wp-a11y`. $handle = 'wp-' . basename( dirname( $path ) ); /** @@ -249,7 +249,7 @@ function gutenberg_register_packages_scripts( $scripts ) { /** * Registers all the WordPress packages styles that are in the standardized - * `build/` location. + * `build/styles/` location. * * @since 6.7.0 * @@ -265,7 +265,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-block-editor-content', - gutenberg_url( 'build/block-editor/content.css' ), + gutenberg_url( 'build/styles/block-editor/content.css' ), array( 'wp-components' ), $version ); @@ -275,7 +275,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-block-editor', - gutenberg_url( 'build/block-editor/style.css' ), + gutenberg_url( 'build/styles/block-editor/style.css' ), array( 'wp-components', 'wp-preferences' ), $version ); @@ -284,7 +284,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-editor', - gutenberg_url( 'build/editor/style.css' ), + gutenberg_url( 'build/styles/editor/style.css' ), array( 'wp-components', 'wp-block-editor', 'wp-patterns', 'wp-reusable-blocks', 'wp-preferences' ), $version ); @@ -293,7 +293,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-edit-post', - gutenberg_url( 'build/edit-post/style.css' ), + gutenberg_url( 'build/styles/edit-post/style.css' ), array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-block-library', 'wp-commands', 'wp-preferences' ), $version ); @@ -302,7 +302,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-components', - gutenberg_url( 'build/components/style.css' ), + gutenberg_url( 'build/styles/components/style.css' ), array( 'dashicons' ), $version ); @@ -312,17 +312,17 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-block-library', - gutenberg_url( 'build/block-library/' . $block_library_filename . '.css' ), + gutenberg_url( 'build/styles/block-library/' . $block_library_filename . '.css' ), array(), $version ); $styles->add_data( 'wp-block-library', 'rtl', 'replace' ); - $styles->add_data( 'wp-block-library', 'path', gutenberg_dir_path() . 'build/block-library/' . $block_library_filename . '.css' ); + $styles->add_data( 'wp-block-library', 'path', gutenberg_dir_path() . 'build/styles/block-library/' . $block_library_filename . '.css' ); gutenberg_override_style( $styles, 'wp-format-library', - gutenberg_url( 'build/format-library/style.css' ), + gutenberg_url( 'build/styles/format-library/style.css' ), array( 'wp-block-editor', 'wp-components' ), $version ); @@ -354,7 +354,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-reset-editor-styles', - gutenberg_url( 'build/block-library/reset.css' ), + gutenberg_url( 'build/styles/block-library/reset.css' ), array( 'common', 'forms' ), // Make sure the reset is loaded after the default WP Admin styles. $version ); @@ -363,7 +363,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-editor-classic-layout-styles', - gutenberg_url( 'build/edit-post/classic.css' ), + gutenberg_url( 'build/styles/edit-post/classic.css' ), array(), $version ); @@ -372,7 +372,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-block-library-editor', - gutenberg_url( 'build/block-library/editor.css' ), + gutenberg_url( 'build/styles/block-library/editor.css' ), array(), $version ); @@ -381,7 +381,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-edit-blocks', - gutenberg_url( 'build/block-library/editor.css' ), + gutenberg_url( 'build/styles/block-library/editor.css' ), $wp_edit_blocks_dependencies, $version ); @@ -390,7 +390,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-nux', - gutenberg_url( 'build/nux/style.css' ), + gutenberg_url( 'build/styles/nux/style.css' ), array( 'wp-components' ), $version ); @@ -399,7 +399,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-block-library-theme', - gutenberg_url( 'build/block-library/theme.css' ), + gutenberg_url( 'build/styles/block-library/theme.css' ), array(), $version ); @@ -408,7 +408,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-list-reusable-blocks', - gutenberg_url( 'build/list-reusable-blocks/style.css' ), + gutenberg_url( 'build/styles/list-reusable-blocks/style.css' ), array( 'wp-components' ), $version ); @@ -417,7 +417,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-commands', - gutenberg_url( 'build/commands/style.css' ), + gutenberg_url( 'build/styles/commands/style.css' ), array( 'wp-components' ), $version ); @@ -426,7 +426,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-edit-site', - gutenberg_url( 'build/edit-site/style.css' ), + gutenberg_url( 'build/styles/edit-site/style.css' ), array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-block-library-editor', 'common', 'forms', 'wp-commands', 'wp-preferences' ), $version ); @@ -435,7 +435,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-edit-widgets', - gutenberg_url( 'build/edit-widgets/style.css' ), + gutenberg_url( 'build/styles/edit-widgets/style.css' ), array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-patterns', 'wp-widgets', 'wp-preferences' ), $version ); @@ -444,7 +444,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-block-directory', - gutenberg_url( 'build/block-directory/style.css' ), + gutenberg_url( 'build/styles/block-directory/style.css' ), array( 'wp-block-editor', 'wp-components' ), $version ); @@ -453,7 +453,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-customize-widgets', - gutenberg_url( 'build/customize-widgets/style.css' ), + gutenberg_url( 'build/styles/customize-widgets/style.css' ), array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-widgets', 'wp-preferences' ), $version ); @@ -462,7 +462,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-patterns', - gutenberg_url( 'build/patterns/style.css' ), + gutenberg_url( 'build/styles/patterns/style.css' ), array( 'wp-components' ), $version ); @@ -471,7 +471,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-reusable-blocks', - gutenberg_url( 'build/reusable-blocks/style.css' ), + gutenberg_url( 'build/styles/reusable-blocks/style.css' ), array( 'wp-components' ), $version ); @@ -480,7 +480,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-widgets', - gutenberg_url( 'build/widgets/style.css' ), + gutenberg_url( 'build/styles/widgets/style.css' ), array( 'wp-components' ) ); $styles->add_data( 'wp-widgets', 'rtl', 'replace' ); @@ -488,7 +488,7 @@ function gutenberg_register_packages_styles( $styles ) { gutenberg_override_style( $styles, 'wp-preferences', - gutenberg_url( 'build/preferences/style.css' ), + gutenberg_url( 'build/styles/preferences/style.css' ), array( 'wp-components' ), $version ); @@ -594,7 +594,7 @@ function gutenberg_register_vendor_scripts( $scripts ) { gutenberg_override_script( $scripts, 'react', - gutenberg_url( 'build/vendors/react' . $extension ), + gutenberg_url( 'build/scripts/vendors/react' . $extension ), // See https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md#externalising-react. SCRIPT_DEBUG ? array( 'wp-react-refresh-entry', 'wp-polyfill' ) : array( 'wp-polyfill' ), '18' @@ -602,7 +602,7 @@ function gutenberg_register_vendor_scripts( $scripts ) { gutenberg_override_script( $scripts, 'react-dom', - gutenberg_url( 'build/vendors/react-dom' . $extension ), + gutenberg_url( 'build/scripts/vendors/react-dom' . $extension ), array( 'react' ), '18' ); @@ -610,7 +610,7 @@ function gutenberg_register_vendor_scripts( $scripts ) { gutenberg_override_script( $scripts, 'react-jsx-runtime', - gutenberg_url( 'build/vendors/react-jsx-runtime' . $extension ), + gutenberg_url( 'build/scripts/vendors/react-jsx-runtime' . $extension ), array( 'react' ), '18' ); @@ -631,7 +631,7 @@ function gutenberg_default_script_modules() { * Uses RecursiveDirectoryIterator to find all *.min.js files at any nesting depth. */ $all_assets = array(); - $build_module_dir = gutenberg_dir_path() . 'build-module'; + $build_module_dir = gutenberg_dir_path() . 'build/modules'; if ( is_dir( $build_module_dir ) ) { $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $build_module_dir, RecursiveDirectoryIterator::SKIP_DOTS ) @@ -645,7 +645,7 @@ function gutenberg_default_script_modules() { } $asset = require $asset_file; - $file_name = str_replace( gutenberg_dir_path() . 'build-module/', '', $path ); + $file_name = str_replace( gutenberg_dir_path() . 'build/modules/', '', $path ); $asset['dependencies'] = $asset['module_dependencies'] ?? array(); $all_assets[ $file_name ] = $asset; } @@ -696,7 +696,7 @@ function gutenberg_default_script_modules() { gutenberg_register_interactive_script_module_id( $script_module_id ); - $path = gutenberg_url( "build-module/{$file_name}" ); + $path = gutenberg_url( "build/modules/{$file_name}" ); wp_register_script_module( $script_module_id, $path, $script_module_data['dependencies'], $script_module_data['version'], $args ); // The $args parameter is new as of WP 6.9 per . } } diff --git a/lib/experimental/posts/load.php b/lib/experimental/posts/load.php index 5100ad26a3eb8b..b8f2a86a86e26c 100644 --- a/lib/experimental/posts/load.php +++ b/lib/experimental/posts/load.php @@ -51,7 +51,7 @@ function gutenberg_posts_dashboard() { do_action( 'enqueue_block_editor_assets' ); wp_register_style( 'wp-gutenberg-posts-dashboard', - gutenberg_url( 'build/edit-site/posts.css' ), + gutenberg_url( 'build/styles/edit-site/posts.css' ), array( 'wp-components', 'wp-commands', 'wp-edit-site' ) ); wp_enqueue_style( 'wp-gutenberg-posts-dashboard' ); diff --git a/lib/load.php b/lib/load.php index 6a7c1f3866145c..6ce8365747064b 100644 --- a/lib/load.php +++ b/lib/load.php @@ -134,13 +134,13 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/block-template-utils.php'; // Copied package PHP files. -if ( is_dir( __DIR__ . '/../build/style-engine' ) ) { - require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-declarations-gutenberg.php'; - require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rule-gutenberg.php'; - require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-css-rules-store-gutenberg.php'; - require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-processor-gutenberg.php'; - require_once __DIR__ . '/../build/style-engine/class-wp-style-engine-gutenberg.php'; - require_once __DIR__ . '/../build/style-engine/style-engine-gutenberg.php'; +if ( is_dir( __DIR__ . '/../build/scripts/style-engine' ) ) { + require_once __DIR__ . '/../build/scripts/style-engine/class-wp-style-engine-css-declarations-gutenberg.php'; + require_once __DIR__ . '/../build/scripts/style-engine/class-wp-style-engine-css-rule-gutenberg.php'; + require_once __DIR__ . '/../build/scripts/style-engine/class-wp-style-engine-css-rules-store-gutenberg.php'; + require_once __DIR__ . '/../build/scripts/style-engine/class-wp-style-engine-processor-gutenberg.php'; + require_once __DIR__ . '/../build/scripts/style-engine/class-wp-style-engine-gutenberg.php'; + require_once __DIR__ . '/../build/scripts/style-engine/style-engine-gutenberg.php'; } // Block supports overrides. From e01800b717c9e9dd6c908641a79cf207c5e95ac6 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Tue, 21 Oct 2025 15:25:24 +0900 Subject: [PATCH 09/21] Storybook: Fix `dev` script (#72487) Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: youknowriad Co-authored-by: aduth --- .gitignore | 1 + bin/dev.mjs | 24 ++++++++++++++++++++++++ package-lock.json | 1 + package.json | 6 +++--- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 95a0bd7ead37d5..62baa1054f5a41 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ gutenberg.zip coverage .phpunit.result.cache .reassure +.dev-ready # Directories/files that may appear in your environment diff --git a/bin/dev.mjs b/bin/dev.mjs index f1427f5535f523..44d00be0cc5b93 100755 --- a/bin/dev.mjs +++ b/bin/dev.mjs @@ -6,6 +6,7 @@ import { spawn } from 'child_process'; import { fileURLToPath } from 'url'; import path from 'path'; +import fs from 'fs'; const __dirname = path.dirname( fileURLToPath( import.meta.url ) ); const ROOT_DIR = path.resolve( __dirname, '..' ); @@ -93,6 +94,22 @@ function execAsync( command, args = [], options = {} ) { } ); } +/** + * Create and clean up a marker file to signal that the build is ready. + * The marker file can be watched by other processes that depend on the build. + */ +const readyMarkerFile = { + markerPath: path.join( ROOT_DIR, '.dev-ready' ), + create() { + fs.writeFileSync( this.markerPath, Date.now().toString() ); + }, + cleanup() { + if ( fs.existsSync( this.markerPath ) ) { + fs.unlinkSync( this.markerPath ); + } + }, +}; + /** * Main dev orchestration function. */ @@ -101,6 +118,9 @@ async function dev() { const startTime = Date.now(); + // Clean up marker file from previous runs + readyMarkerFile.cleanup(); + try { // Step 1: Clean packages console.log( '๐Ÿงน Cleaning packages...' ); @@ -146,6 +166,9 @@ async function dev() { ) }s)\n` ); + // Write a marker file to signal that the build is ready + readyMarkerFile.create(); + // Step 7: Start watch mode with both TypeScript and package builds console.log( '๐Ÿ‘€ Starting watch mode...\n' ); console.log( ' - TypeScript compiler watching for type changes' ); @@ -172,6 +195,7 @@ async function dev() { console.log( '\n\n๐Ÿ‘‹ Stopping watch mode...' ); tscWatch.kill(); buildWatch.kill(); + readyMarkerFile.cleanup(); process.exit( 0 ); }; diff --git a/package-lock.json b/package-lock.json index 919180c52b20fd..2e90cd93e8dfe0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -165,6 +165,7 @@ "toposort": "2.0.2", "typescript": "5.7.2", "uuid": "9.0.1", + "wait-on": "8.0.1", "webdriverio": "8.16.20", "webpack": "5.97.0", "webpack-bundle-analyzer": "4.9.1" diff --git a/package.json b/package.json index c15ea88a3320b4..c29dcb51c27ffb 100644 --- a/package.json +++ b/package.json @@ -174,6 +174,7 @@ "toposort": "2.0.2", "typescript": "5.7.2", "uuid": "9.0.1", + "wait-on": "8.0.1", "webdriverio": "8.16.20", "webpack": "5.97.0", "webpack-bundle-analyzer": "4.9.1" @@ -229,9 +230,8 @@ "start": "npm run dev", "prestorybook:build": "npm run build", "storybook:build": "storybook build -c ./storybook -o ./storybook/build", - "prestorybook:dev": "npm run build", - "storybook:dev": "concurrently \"npm run dev\" \"storybook dev -c ./storybook -p 50240\"", - "storybook:e2e:dev": "concurrently \"npm run dev\" \"storybook dev -c test/storybook-playwright/storybook -p 50241\"", + "storybook:dev": "concurrently \"npm run dev\" \"wait-on .dev-ready && storybook dev -c ./storybook -p 50240\"", + "storybook:e2e:dev": "concurrently \"npm run dev\" \"wait-on .dev-ready && storybook dev -c test/storybook-playwright/storybook -p 50241\"", "test": "npm-run-all lint test:unit", "test:create-block": "bash ./bin/test-create-block.sh", "test:e2e": "wp-scripts test-playwright --config test/e2e/playwright.config.ts", From 0d2b36baa23dddbd855ad659087ac4111d290e97 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Tue, 21 Oct 2025 14:35:15 +0800 Subject: [PATCH 10/21] Remove broken content only settings menu items feature (#72470) * Remove content only settings menu items * Remove styles Co-authored-by: talldan Co-authored-by: andrewserong --- .../content-only-settings-menu.js | 185 ------------------ .../content-only-settings-menu.native.js | 4 - .../components/block-settings-menu/style.scss | 6 - .../editor/src/components/provider/index.js | 2 - packages/editor/src/style.scss | 1 - 5 files changed, 198 deletions(-) delete mode 100644 packages/editor/src/components/block-settings-menu/content-only-settings-menu.js delete mode 100644 packages/editor/src/components/block-settings-menu/content-only-settings-menu.native.js delete mode 100644 packages/editor/src/components/block-settings-menu/style.scss diff --git a/packages/editor/src/components/block-settings-menu/content-only-settings-menu.js b/packages/editor/src/components/block-settings-menu/content-only-settings-menu.js deleted file mode 100644 index af0e9b30ae83b4..00000000000000 --- a/packages/editor/src/components/block-settings-menu/content-only-settings-menu.js +++ /dev/null @@ -1,185 +0,0 @@ -/** - * WordPress dependencies - */ -import { - BlockSettingsMenuControls, - __unstableBlockSettingsMenuFirstItem as BlockSettingsMenuFirstItem, - store as blockEditorStore, - useBlockDisplayInformation, -} from '@wordpress/block-editor'; -import { store as coreStore } from '@wordpress/core-data'; -import { __experimentalText as Text, MenuItem } from '@wordpress/components'; -import { useSelect, useDispatch } from '@wordpress/data'; -import { __, _x } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { store as editorStore } from '../../store'; -import { unlock } from '../../lock-unlock'; -import usePostContentBlocks from '../provider/use-post-content-blocks'; - -function ContentOnlySettingsMenuItems( { clientId, onClose } ) { - const postContentBlocks = usePostContentBlocks(); - const { entity, onNavigateToEntityRecord, canEditTemplates } = useSelect( - ( select ) => { - const { - getBlockParentsByBlockName, - getSettings, - getBlockAttributes, - getBlockParents, - } = select( blockEditorStore ); - const { getCurrentTemplateId, getRenderingMode } = - select( editorStore ); - const patternParent = getBlockParentsByBlockName( - clientId, - 'core/block', - true - )[ 0 ]; - - let record; - if ( patternParent ) { - record = select( coreStore ).getEntityRecord( - 'postType', - 'wp_block', - getBlockAttributes( patternParent ).ref - ); - } else if ( - getRenderingMode() === 'template-locked' && - ! getBlockParents( clientId ).some( ( parent ) => - postContentBlocks.includes( parent ) - ) - ) { - record = select( coreStore ).getEntityRecord( - 'postType', - 'wp_template', - getCurrentTemplateId() - ); - } - if ( ! record ) { - return {}; - } - const _canEditTemplates = select( coreStore ).canUser( 'create', { - kind: 'postType', - name: 'wp_template', - } ); - return { - canEditTemplates: _canEditTemplates, - entity: record, - onNavigateToEntityRecord: - getSettings().onNavigateToEntityRecord, - }; - }, - [ clientId, postContentBlocks ] - ); - - if ( ! entity ) { - return ( - - ); - } - - const isPattern = entity.type === 'wp_block'; - let helpText = isPattern - ? __( - 'Edit the pattern to move, delete, or make further changes to this block.' - ) - : __( - 'Edit the template to move, delete, or make further changes to this block.' - ); - - if ( ! canEditTemplates ) { - helpText = __( - 'Only users with permissions to edit the template can move or delete this block' - ); - } - - return ( - <> - - { - onNavigateToEntityRecord( { - postId: entity.id, - postType: entity.type, - } ); - } } - disabled={ ! canEditTemplates } - > - { isPattern ? __( 'Edit pattern' ) : __( 'Edit template' ) } - - - - { helpText } - - - ); -} - -function TemplateLockContentOnlyMenuItems( { clientId, onClose } ) { - const { contentLockingParent } = useSelect( - ( select ) => { - const { getContentLockingParent } = unlock( - select( blockEditorStore ) - ); - return { - contentLockingParent: getContentLockingParent( clientId ), - }; - }, - [ clientId ] - ); - const blockDisplayInformation = - useBlockDisplayInformation( contentLockingParent ); - const blockEditorActions = useDispatch( blockEditorStore ); - if ( ! blockDisplayInformation?.title ) { - return null; - } - - const { modifyContentLockBlock } = unlock( blockEditorActions ); - - return ( - <> - - { - modifyContentLockBlock( contentLockingParent ); - onClose(); - } } - > - { _x( 'Unlock', 'Unlock content locked blocks' ) } - - - - { __( - 'Temporarily unlock the parent block to edit, delete or make further changes to this block.' - ) } - - - ); -} - -export default function ContentOnlySettingsMenu() { - return ( - - { ( { selectedClientIds, onClose } ) => - selectedClientIds.length === 1 && ( - - ) - } - - ); -} diff --git a/packages/editor/src/components/block-settings-menu/content-only-settings-menu.native.js b/packages/editor/src/components/block-settings-menu/content-only-settings-menu.native.js deleted file mode 100644 index 11cebce87bbba5..00000000000000 --- a/packages/editor/src/components/block-settings-menu/content-only-settings-menu.native.js +++ /dev/null @@ -1,4 +0,0 @@ -// Render nothing in native for now. -export default function ContentOnlySettingsMenu() { - return null; -} diff --git a/packages/editor/src/components/block-settings-menu/style.scss b/packages/editor/src/components/block-settings-menu/style.scss deleted file mode 100644 index 033bd8c726a199..00000000000000 --- a/packages/editor/src/components/block-settings-menu/style.scss +++ /dev/null @@ -1,6 +0,0 @@ -@use "@wordpress/base-styles/variables" as *; - -.editor-content-only-settings-menu__description { - padding: $grid-unit; - min-width: 235px; -} diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 008f81be9fdf57..38c7bed482c68e 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -32,7 +32,6 @@ import useCommands from '../commands'; import BlockRemovalWarnings from '../block-removal-warnings'; import StartPageOptions from '../start-page-options'; import KeyboardShortcutHelpModal from '../keyboard-shortcut-help-modal'; -import ContentOnlySettingsMenu from '../block-settings-menu/content-only-settings-menu'; import StartTemplateOptions from '../start-template-options'; import EditorKeyboardShortcuts from '../global-keyboard-shortcuts'; import PatternRenameModal from '../pattern-rename-modal'; @@ -373,7 +372,6 @@ export const ExperimentalEditorProvider = withRegistryProvider( <> - { mode === 'template-locked' && ( ) } diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index 3626d71e90c4e9..6bca912a5577a5 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -3,7 +3,6 @@ @use "./components/autocompleters/style.scss" as *; @use "./components/collab-sidebar/style.scss" as *; @use "./components/collapsible-block-toolbar/style.scss" as *; -@use "./components/block-settings-menu/style.scss" as *; @use "./components/block-visibility/style.scss" as *; @use "./components/blog-title/style.scss" as *; @use "./components/document-bar/style.scss" as *; From 977ac21e3f3fd59bd3b03ad2d23de00867eaba2b Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Tue, 21 Oct 2025 08:34:25 +0100 Subject: [PATCH 11/21] Dataviews: Make bulk actions buttons text based (#72501) Co-authored-by: jorgefilipecosta Co-authored-by: oandregal --- .../src/components/dataviews-bulk-actions/index.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/dataviews/src/components/dataviews-bulk-actions/index.tsx b/packages/dataviews/src/components/dataviews-bulk-actions/index.tsx index 114b6dc46ad71e..5ddf13861ba202 100644 --- a/packages/dataviews/src/components/dataviews-bulk-actions/index.tsx +++ b/packages/dataviews/src/components/dataviews-bulk-actions/index.tsx @@ -169,13 +169,12 @@ function ActionTrigger< Item >( { ); } @@ -335,7 +334,6 @@ function FooterContent< Item >( { actions.filter( ( action ) => { return ( action.supportsBulk && - action.icon && selectedItems.some( ( item ) => ! action.isEligible || action.isEligible( item ) From c1f90a606e516ca99ea973c04e18dde64f020eaf Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Tue, 21 Oct 2025 17:05:52 +0900 Subject: [PATCH 12/21] Button: Ensure that icons don't shrink (#72474) * Button: Ensure that icons don't shrink * Add changelog Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: jasmussen Co-authored-by: aduth --- packages/components/CHANGELOG.md | 4 ++++ packages/components/src/button/style.scss | 1 + 2 files changed, 5 insertions(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index b154c67525de5c..dd4cefe1cb2d47 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -7,6 +7,10 @@ - `Button`: Update font weight to `500` ([#70787](https://github.com/WordPress/gutenberg/pull/70787)). - `TabPanel`: Update tab font weight ([#72455](https://github.com/WordPress/gutenberg/pull/72455)). +### Bug Fixes + +- `Button`: Ensure that icons don't shrink ([#72474](https://github.com/WordPress/gutenberg/pull/72474)). + ## 30.6.0 (2025-10-17) ### Enhancements diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 5b42187a5bdb37..694b83f2cebd05 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -390,6 +390,7 @@ svg { fill: currentColor; outline: none; + flex-shrink: 0; // Optimize for high contrast modes. // See also https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/. From 7b1a5c209f1b9efa0049bf7dd069381809e338ab Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 21 Oct 2025 12:00:34 +0100 Subject: [PATCH 13/21] Optimize CI: Limit the number of job variations (PHP and JS matrix) in PRs (#72506) --- .github/workflows/create-block.yml | 11 ++++++ .github/workflows/unit-test.yml | 55 ++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/.github/workflows/create-block.yml b/.github/workflows/create-block.yml index d8fe7e192cf933..188f91a2ef2bb5 100644 --- a/.github/workflows/create-block.yml +++ b/.github/workflows/create-block.yml @@ -26,8 +26,19 @@ jobs: strategy: fail-fast: false matrix: + event: ['${{ github.event_name }}'] node: ['20', '22', '24'] os: ['macos-15', 'ubuntu-24.04', 'windows-2025'] + exclude: + # On PRs: Only test Node 20 on Ubuntu + - event: 'pull_request' + node: '22' + - event: 'pull_request' + node: '24' + - event: 'pull_request' + os: 'macos-15' + - event: 'pull_request' + os: 'windows-2025' steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 55bc636df9acf8..3bbf17aca1e6d9 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -33,8 +33,15 @@ jobs: strategy: fail-fast: false matrix: + event: ['${{ github.event_name }}'] node: ['20', '22', '24'] shard: ['1/4', '2/4', '3/4', '4/4'] + exclude: + # On PRs: Only test Node 20 + - event: 'pull_request' + node: '22' + - event: 'pull_request' + node: '24' steps: - name: Checkout repository @@ -78,7 +85,14 @@ jobs: strategy: fail-fast: false matrix: + event: ['${{ github.event_name }}'] node: ['20', '22', '24'] + exclude: + # On PRs: Only test Node 20 + - event: 'pull_request' + node: '22' + - event: 'pull_request' + node: '24' steps: - name: Checkout repository @@ -169,23 +183,36 @@ jobs: strategy: fail-fast: true matrix: - php: - - '7.2' - - '7.3' - - '7.4' - - '8.0' - - '8.1' - - '8.2' - - '8.3' + event: ['${{ github.event_name }}'] + php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] multisite: [false, true] - wordpress: [''] # Latest WordPress version. - include: - # Test with the previous WP version. - - php: '7.2' + wordpress: ['', 'previous major version'] + exclude: + # On PRs: Only test PHP 8.3, single-site, latest WP + - event: 'pull_request' + php: '7.2' + - event: 'pull_request' + php: '7.3' + - event: 'pull_request' + php: '7.4' + - event: 'pull_request' + php: '8.0' + - event: 'pull_request' + php: '8.1' + - event: 'pull_request' + php: '8.2' + - event: 'pull_request' + multisite: true + - event: 'pull_request' wordpress: 'previous major version' - - php: '7.4' + # On trunk/releases: Only test previous WP version with specific PHP versions + - php: '7.3' wordpress: 'previous major version' - - php: '8.3' + - php: '8.0' + wordpress: 'previous major version' + - php: '8.1' + wordpress: 'previous major version' + - php: '8.2' wordpress: 'previous major version' env: From e86dd9dfc49c264c67650e57ee4db312759ecd7f Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:47:02 +0200 Subject: [PATCH 14/21] Template activation: duplicate action should be available outside GB (#72513) Co-authored-by: ellatrix Co-authored-by: jorgefilipecosta Co-authored-by: oandregal Co-authored-by: priethor < priethor@git.wordpress.org> --- .../src/dataviews/store/private-actions.ts | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/editor/src/dataviews/store/private-actions.ts b/packages/editor/src/dataviews/store/private-actions.ts index eb068ad64c58f9..06d92a2d75bea0 100644 --- a/packages/editor/src/dataviews/store/private-actions.ts +++ b/packages/editor/src/dataviews/store/private-actions.ts @@ -133,19 +133,28 @@ export const registerPostTypeSchema = .resolveSelect( coreStore ) .getCurrentTheme(); + let canDuplicate = + ! [ 'wp_block', 'wp_template_part' ].includes( + postTypeConfig.slug + ) && + canCreate && + duplicatePost; + + // @ts-ignore + if ( ! globalThis.IS_GUTENBERG_PLUGIN ) { + // Outside Gutenberg, disable duplication except for wp_template. + if ( 'wp_template' !== postTypeConfig.slug ) { + canDuplicate = undefined; + } + } + const actions = [ postTypeConfig.viewable ? viewPost : undefined, !! postTypeConfig.supports?.revisions ? viewPostRevisions : undefined, // @ts-ignore - globalThis.IS_GUTENBERG_PLUGIN - ? ! [ 'wp_block', 'wp_template_part' ].includes( - postTypeConfig.slug - ) && - canCreate && - duplicatePost - : undefined, + canDuplicate, postTypeConfig.slug === 'wp_template_part' && canCreate && currentTheme?.is_block_theme From bd9d7adb4e83eef549d595159a2a33e63947fe3c Mon Sep 17 00:00:00 2001 From: Miguel Fonseca <150562+mcsf@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:47:58 +0100 Subject: [PATCH 15/21] Template activation: improve UX for custom templates (#72499) Co-authored-by: mcsf Co-authored-by: juanfra Co-authored-by: ellatrix Co-authored-by: priethor < priethor@git.wordpress.org> --- .../src/components/dataviews-actions/index.js | 1 + .../src/components/page-templates/fields.js | 21 ++++++++++---- .../src/components/page-templates/index.js | 28 +++++++++++++------ 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/packages/edit-site/src/components/dataviews-actions/index.js b/packages/edit-site/src/components/dataviews-actions/index.js index 323d93fa7bff9d..00f6e514a65bcc 100644 --- a/packages/edit-site/src/components/dataviews-actions/index.js +++ b/packages/edit-site/src/components/dataviews-actions/index.js @@ -35,6 +35,7 @@ export const useSetActiveTemplateAction = () => { icon: pencil, isEligible( item ) { return ( + ! item._isCustom && ! ( item.slug === 'index' && item.source === 'theme' ) && item.theme === activeTheme.stylesheet ); diff --git a/packages/edit-site/src/components/page-templates/fields.js b/packages/edit-site/src/components/page-templates/fields.js index 4a5f0ae0377e2d..9bbed2bf9d933c 100644 --- a/packages/edit-site/src/components/page-templates/fields.js +++ b/packages/edit-site/src/components/page-templates/fields.js @@ -11,7 +11,7 @@ import { __experimentalHStack as HStack, privateApis as componentsPrivateApis, } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { useState, useMemo } from '@wordpress/element'; import { decodeEntities } from '@wordpress/html-entities'; import { parse } from '@wordpress/blocks'; @@ -155,6 +155,19 @@ export const activeField = { id: 'active', getValue: ( { item } ) => item._isActive, render: function Render( { item } ) { + if ( item._isCustom ) { + return ( + + { __( 'N/A' ) } + + ); + } + const isActive = item._isActive; return ( @@ -190,10 +203,6 @@ export const slugField = { const defaultTemplateType = defaultTemplateTypes.find( ( type ) => type.slug === item.slug ); - return ( - defaultTemplateType?.title || - // translators: %s is the slug of a custom template. - __( 'Custom' ) - ); + return defaultTemplateType?.title || _x( 'Custom', 'template type' ); }, }; diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js index 7847c1a9901478..5bd1784be78835 100644 --- a/packages/edit-site/src/components/page-templates/index.js +++ b/packages/edit-site/src/components/page-templates/index.js @@ -72,14 +72,18 @@ export default function PageTemplates() { }, } ); - const { activeTemplatesOption, activeTheme } = useSelect( ( select ) => { - const { getEntityRecord, getCurrentTheme } = select( coreStore ); - return { - activeTemplatesOption: getEntityRecord( 'root', 'site' ) - ?.active_templates, - activeTheme: getCurrentTheme(), - }; - } ); + const { activeTemplatesOption, activeTheme, defaultTemplateTypes } = + useSelect( ( select ) => { + const { getEntityRecord, getCurrentTheme } = select( coreStore ); + return { + activeTemplatesOption: getEntityRecord( 'root', 'site' ) + ?.active_templates, + activeTheme: getCurrentTheme(), + defaultTemplateTypes: + select( coreStore ).getCurrentTheme() + ?.default_template_types, + }; + } ); // Todo: this will have to be better so that we're not fetching all the // records all the time. Active templates query will need to move server // side. @@ -150,8 +154,14 @@ export default function PageTemplates() { _isActive: activeTemplates.find( ( template ) => template.id === record.id ), + _isCustom: + record.is_custom || + ( ! record.meta?.is_wp_suggestion && + ! defaultTemplateTypes.find( + ( type ) => type.slug === record.slug + ) ), } ) ); - }, [ _records, activeTemplates ] ); + }, [ _records, activeTemplates, defaultTemplateTypes ] ); const users = useSelect( ( select ) => { From 9b06483547f5c480beb407c06f0c0ee753f0c178 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Tue, 21 Oct 2025 12:20:02 +0000 Subject: [PATCH 16/21] Update changelog files --- packages/dataviews/CHANGELOG.md | 2 ++ packages/dataviews/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md index 1cafda631abf14..5f0f24806b8074 100644 --- a/packages/dataviews/CHANGELOG.md +++ b/packages/dataviews/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 10.1.0 (2025-10-21) + ### Enhancements - Dataviews: Use text based buttons for actions instead of text. [#72417](https://github.com/WordPress/gutenberg/pull/72417) diff --git a/packages/dataviews/package.json b/packages/dataviews/package.json index af29ffc2774520..1414840c7eaee1 100644 --- a/packages/dataviews/package.json +++ b/packages/dataviews/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dataviews", - "version": "10.0.0", + "version": "10.1.0-prerelease", "description": "DataViews is a component that provides an API to render datasets using different types of layouts (table, grid, list, etc.).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 3430c47eb899b3780ee739a6d6cdb9e85d5aaad5 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Tue, 21 Oct 2025 12:21:56 +0000 Subject: [PATCH 17/21] chore(release): publish - @wordpress/block-directory@5.33.2 - @wordpress/dataviews@10.1.0 - @wordpress/edit-post@8.33.2 - @wordpress/edit-site@6.33.2 - @wordpress/editor@14.33.2 - @wordpress/fields@0.25.2 - @wordpress/views@1.0.1 --- package-lock.json | 14 +++++++------- packages/block-directory/package.json | 2 +- packages/dataviews/package.json | 2 +- packages/edit-post/package.json | 2 +- packages/edit-site/package.json | 2 +- packages/editor/package.json | 2 +- packages/fields/package.json | 2 +- packages/views/package.json | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e90cd93e8dfe0..9e9ac48c36e980 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51216,7 +51216,7 @@ }, "packages/block-directory": { "name": "@wordpress/block-directory", - "version": "5.33.1", + "version": "5.33.2", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/a11y": "file:../a11y", @@ -52048,7 +52048,7 @@ }, "packages/dataviews": { "name": "@wordpress/dataviews", - "version": "10.0.0", + "version": "10.1.0", "license": "GPL-2.0-or-later", "dependencies": { "@ariakit/react": "^0.4.15", @@ -52253,7 +52253,7 @@ }, "packages/edit-post": { "name": "@wordpress/edit-post", - "version": "8.33.1", + "version": "8.33.2", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/a11y": "file:../a11y", @@ -52300,7 +52300,7 @@ }, "packages/edit-site": { "name": "@wordpress/edit-site", - "version": "6.33.1", + "version": "6.33.2", "license": "GPL-2.0-or-later", "dependencies": { "@react-spring/web": "^9.4.5", @@ -52407,7 +52407,7 @@ }, "packages/editor": { "name": "@wordpress/editor", - "version": "14.33.1", + "version": "14.33.2", "license": "GPL-2.0-or-later", "dependencies": { "@floating-ui/react-dom": "2.0.8", @@ -52592,7 +52592,7 @@ }, "packages/fields": { "name": "@wordpress/fields", - "version": "0.25.1", + "version": "0.25.2", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/api-fetch": "file:../api-fetch", @@ -53889,7 +53889,7 @@ }, "packages/views": { "name": "@wordpress/views", - "version": "1.0.0", + "version": "1.0.1", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/data": "file:../data", diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index 3b0de31523efd1..e4ae1e274d8d7f 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-directory", - "version": "5.33.1", + "version": "5.33.2", "description": "Extend editor with block directory features to search, download and install blocks.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dataviews/package.json b/packages/dataviews/package.json index 1414840c7eaee1..69fdea33009c33 100644 --- a/packages/dataviews/package.json +++ b/packages/dataviews/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dataviews", - "version": "10.1.0-prerelease", + "version": "10.1.0", "description": "DataViews is a component that provides an API to render datasets using different types of layouts (table, grid, list, etc.).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 0fc432f262f24c..8feadb1a588d82 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "8.33.1", + "version": "8.33.2", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 7515b5003885b8..4e64c6e3c3cde9 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-site", - "version": "6.33.1", + "version": "6.33.2", "description": "Edit Site Page module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/package.json b/packages/editor/package.json index 046a72204faf6a..8608e16bede103 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "14.33.1", + "version": "14.33.2", "description": "Enhanced block editor for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/fields/package.json b/packages/fields/package.json index fb5c2a03b1fb8a..9de57dc4604341 100644 --- a/packages/fields/package.json +++ b/packages/fields/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/fields", - "version": "0.25.1", + "version": "0.25.2", "description": "DataViews is a component that provides an API to render datasets using different types of layouts (table, grid, list, etc.).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/views/package.json b/packages/views/package.json index 4c40829468783c..153047b136db27 100644 --- a/packages/views/package.json +++ b/packages/views/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/views", - "version": "1.0.0", + "version": "1.0.1", "description": "View persistence and management for WordPress DataViews.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 9595c60e9638d1f23285285d4272faaff68ea19b Mon Sep 17 00:00:00 2001 From: Chris Zarate Date: Tue, 21 Oct 2025 06:49:59 -0600 Subject: [PATCH 18/21] Ensure Yjs imports are inside experimental flag check (#72503) --- packages/core-data/src/actions.js | 4 +- packages/core-data/src/entities.js | 97 ++++++++++++++---------- packages/core-data/src/resolvers.js | 4 +- packages/core-data/src/sync.ts | 14 +++- packages/core-data/src/test/resolvers.js | 14 ++-- 5 files changed, 83 insertions(+), 50 deletions(-) diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 6faad5041ec858..5594e77663d9c8 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -19,7 +19,7 @@ import { receiveItems, removeItems, receiveQueriedItems } from './queried-data'; import { DEFAULT_ENTITY_KEY } from './entities'; import { createBatch } from './batch'; import { STORE_NAME } from './name'; -import { LOCAL_EDITOR_ORIGIN, syncManager } from './sync'; +import { LOCAL_EDITOR_ORIGIN, getSyncManager } from './sync'; import logEntityDeprecation from './utils/log-entity-deprecation'; /** @@ -415,7 +415,7 @@ export const editEntityRecord = const objectType = `${ kind }/${ name }`; const objectId = recordId; - syncManager.update( + getSyncManager()?.update( objectType, objectId, edit.edits, diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 4057b068bbd496..d7b7e421656481 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -272,41 +272,7 @@ async function loadPostTypeEntities() { ); const namespace = postType?.rest_namespace ?? 'wp/v2'; - /** - * @type {import('@wordpress/sync').SyncConfig} - */ - const syncConfig = { - /** - * Apply changes from the local editor to the local CRDT document so - * that those changes can be synced to other peers (via the provider). - * - * @param {import('@wordpress/sync').CRDTDoc} crdtDoc - * @param {Partial< import('@wordpress/sync').ObjectData >} changes - * @return {void} - */ - applyChangesToCRDTDoc: ( crdtDoc, changes ) => - applyPostChangesToCRDTDoc( crdtDoc, changes, postType ), - - /** - * Extract changes from a CRDT document that can be used to update the - * local editor state. - * - * @param {import('@wordpress/sync').CRDTDoc} crdtDoc - * @param {import('@wordpress/sync').ObjectData} editedRecord - * @return {Partial< import('@wordpress/sync').ObjectData >} Changes to record - */ - getChangesFromCRDTDoc: ( crdtDoc, editedRecord ) => - getPostChangesFromCRDTDoc( crdtDoc, editedRecord, postType ), - - /** - * Sync features supported by the entity. - * - * @type {Record< string, boolean >} - */ - supports: {}, - }; - - return { + const entity = { kind: 'postType', baseURL: `/${ namespace }/${ postType.rest_base }`, baseURLParams: { context: 'edit' }, @@ -326,7 +292,6 @@ async function loadPostTypeEntities() { : String( record.id ) ), __unstablePrePersist: isTemplate ? undefined : prePersistPostType, __unstable_rest_base: postType.rest_base, - syncConfig, supportsPagination: true, getRevisionsUrl: ( parentId, revisionId ) => `/${ namespace }/${ @@ -336,6 +301,50 @@ async function loadPostTypeEntities() { }`, revisionKey: DEFAULT_ENTITY_KEY, }; + + if ( window.__experimentalEnableSync ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { + /** + * @type {import('@wordpress/sync').SyncConfig} + */ + entity.syncConfig = { + /** + * Apply changes from the local editor to the local CRDT document so + * that those changes can be synced to other peers (via the provider). + * + * @param {import('@wordpress/sync').CRDTDoc} crdtDoc + * @param {Partial< import('@wordpress/sync').ObjectData >} changes + * @return {void} + */ + applyChangesToCRDTDoc: ( crdtDoc, changes ) => + applyPostChangesToCRDTDoc( crdtDoc, changes, postType ), + + /** + * Extract changes from a CRDT document that can be used to update the + * local editor state. + * + * @param {import('@wordpress/sync').CRDTDoc} crdtDoc + * @param {import('@wordpress/sync').ObjectData} editedRecord + * @return {Partial< import('@wordpress/sync').ObjectData >} Changes to record + */ + getChangesFromCRDTDoc: ( crdtDoc, editedRecord ) => + getPostChangesFromCRDTDoc( + crdtDoc, + editedRecord, + postType + ), + + /** + * Sync features supported by the entity. + * + * @type {Record< string, boolean >} + */ + supports: {}, + }; + } + } + + return entity; } ); } @@ -373,13 +382,21 @@ async function loadSiteEntity() { name: 'site', kind: 'root', baseURL: '/wp/v2/settings', - syncConfig: { - applyChangesToCRDTDoc: defaultApplyChangesToCRDTDoc, - getChangesFromCRDTDoc: defaultGetChangesFromCRDTDoc, - }, meta: {}, }; + if ( window.__experimentalEnableSync ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { + /** + * @type {import('@wordpress/sync').SyncConfig} + */ + entity.syncConfig = { + applyChangesToCRDTDoc: defaultApplyChangesToCRDTDoc, + getChangesFromCRDTDoc: defaultGetChangesFromCRDTDoc, + }; + } + } + const site = await apiFetch( { path: entity.baseURL, method: 'OPTIONS', diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 53ff8520ce4c0d..1f6420ccb1345f 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -15,7 +15,7 @@ import apiFetch from '@wordpress/api-fetch'; */ import { STORE_NAME } from './name'; import { additionalEntityConfigLoaders, DEFAULT_ENTITY_KEY } from './entities'; -import { syncManager } from './sync'; +import { getSyncManager } from './sync'; import { forwardResolver, getNormalizedCommaSeparable, @@ -185,7 +185,7 @@ export const getEntityRecord = } ); // Load the entity record for syncing. - await syncManager.load( + await getSyncManager()?.load( entityConfig.syncConfig, objectType, objectId, diff --git a/packages/core-data/src/sync.ts b/packages/core-data/src/sync.ts index e709e8731acc36..338568438eb70c 100644 --- a/packages/core-data/src/sync.ts +++ b/packages/core-data/src/sync.ts @@ -5,8 +5,20 @@ import { CRDT_RECORD_MAP_KEY, LOCAL_EDITOR_ORIGIN, LOCAL_SYNC_MANAGER_ORIGIN, + type SyncManager, createSyncManager, } from '@wordpress/sync'; export { CRDT_RECORD_MAP_KEY, LOCAL_EDITOR_ORIGIN, LOCAL_SYNC_MANAGER_ORIGIN }; -export const syncManager = createSyncManager(); + +let syncManager: SyncManager; + +export function getSyncManager(): SyncManager | undefined { + if ( syncManager ) { + return syncManager; + } + + syncManager = createSyncManager(); + + return syncManager; +} diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index f98d3b4817ef90..652e74c4c78c3c 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -6,13 +6,11 @@ import triggerFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ -import { syncManager } from '../sync'; +import { getSyncManager } from '../sync'; jest.mock( '@wordpress/api-fetch' ); jest.mock( '../sync', () => ( { - syncManager: { - load: jest.fn(), - }, + getSyncManager: jest.fn(), } ) ); /** @@ -42,6 +40,8 @@ describe( 'getEntityRecord', () => { const resolveSelect = { getEntitiesConfig: jest.fn( () => ENTITIES ) }; let dispatch; + let syncManager; + beforeEach( async () => { dispatch = Object.assign( jest.fn(), { receiveEntityRecords: jest.fn(), @@ -51,7 +51,11 @@ describe( 'getEntityRecord', () => { finishResolutions: jest.fn(), } ); triggerFetch.mockReset(); - syncManager.load.mockClear(); + + syncManager = { + load: jest.fn(), + }; + getSyncManager.mockImplementation( () => syncManager ); } ); afterEach( () => { From ea8144bcc9b16bb8c483060abedbaf8363ad30e3 Mon Sep 17 00:00:00 2001 From: Bernie Reiter <96308+ockham@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:53:46 +0200 Subject: [PATCH 19/21] Post Date block: In deprecation, add check if source is `core/post-data` (#72518) Co-authored-by: ockham Co-authored-by: ntsekouras Co-authored-by: cbravobernal --- packages/block-library/src/post-date/deprecated.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/post-date/deprecated.js b/packages/block-library/src/post-date/deprecated.js index 875a64faef03ff..1aaae679c1e4a8 100644 --- a/packages/block-library/src/post-date/deprecated.js +++ b/packages/block-library/src/post-date/deprecated.js @@ -102,7 +102,11 @@ const v3 = { }; }, isEligible( attributes ) { - return !! attributes?.metadata?.bindings?.datetime?.args?.key; + return ( + attributes?.metadata?.bindings?.datetime?.source === + 'core/post-data' && + !! attributes?.metadata?.bindings?.datetime?.args?.key + ); }, }; From d2988bfa625bf6960be3457423c0d1167061b72d Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Tue, 21 Oct 2025 07:34:37 -0500 Subject: [PATCH 20/21] Add early return to avoid subscriptions for blocks without bindings --- packages/block-editor/src/components/rich-text/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index a06d7ef9ce0985..b037251740e613 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -171,11 +171,15 @@ export function RichTextWrapper( const { disableBoundBlock, bindingsPlaceholder, bindingsLabel } = useSelect( ( select ) => { + // Early return BEFORE accessing store to avoid subscription for blocks without bindings + if ( ! blockBindings?.[ identifier ] ) { + return {}; + } + const { __experimentalBlockBindingsSupportedAttributes } = select( blockEditorStore ).getSettings(); if ( - ! blockBindings?.[ identifier ] || ! ( blockName in __experimentalBlockBindingsSupportedAttributes ) From b4f6a7791e135abc57470d16cb7bdeef77cae440 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 22 Oct 2025 09:35:44 -0500 Subject: [PATCH 21/21] Check for RichText bindings from context instead of new subscription --- .../src/components/rich-text/index.js | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index b037251740e613..a1e0f2fa75416e 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -41,6 +41,7 @@ import { getAllowedFormats } from './utils'; import { Content, valueToHTMLString } from './content'; import { withDeprecations } from './with-deprecations'; import BlockContext from '../block-context'; +import { PrivateBlockContext } from '../block-list/private-block-context'; export const keyboardShortcutContext = createContext(); keyboardShortcutContext.displayName = 'keyboardShortcutContext'; @@ -124,9 +125,10 @@ export function RichTextWrapper( const instanceId = useInstanceId( RichTextWrapper ); const anchorRef = useRef(); const context = useBlockEditContext(); - const { clientId, isSelected: isBlockSelected, name: blockName } = context; + const { clientId, isSelected: isBlockSelected } = context; const blockBindings = context[ blockBindingsKey ]; const blockContext = useContext( BlockContext ); + const { bindableAttributes } = useContext( PrivateBlockContext ); const registry = useRegistry(); const selector = ( select ) => { // Avoid subscribing to the block editor store if the block is not @@ -171,19 +173,7 @@ export function RichTextWrapper( const { disableBoundBlock, bindingsPlaceholder, bindingsLabel } = useSelect( ( select ) => { - // Early return BEFORE accessing store to avoid subscription for blocks without bindings - if ( ! blockBindings?.[ identifier ] ) { - return {}; - } - - const { __experimentalBlockBindingsSupportedAttributes } = - select( blockEditorStore ).getSettings(); - - if ( - ! ( - blockName in __experimentalBlockBindingsSupportedAttributes - ) - ) { + if ( ! blockBindings?.[ identifier ] || ! bindableAttributes ) { return {}; } @@ -256,7 +246,7 @@ export function RichTextWrapper( [ blockBindings, identifier, - blockName, + bindableAttributes, adjustedValue, clientId, blockContext,