diff --git a/docs/reference-guides/block-api/block-edit-save.md b/docs/reference-guides/block-api/block-edit-save.md index a50a17b75cb54d..b48ca0a7033968 100644 --- a/docs/reference-guides/block-api/block-edit-save.md +++ b/docs/reference-guides/block-api/block-edit-save.md @@ -135,6 +135,22 @@ const addListItem = ( newListItem ) => { Why do this? In JavaScript, arrays and objects are passed by reference, so this practice ensures changes won't affect other code that might hold references to the same data. Furthermore, the Gutenberg project follows the philosophy of the Redux library that [state should be immutable](https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability)—data should not be changed directly, but instead a new version of the data created containing the changes. +The `setAttribute` also supports an updater function as an argument. It must be a pure function, which takes current attributes as its only argument and returns updated attributes. This method is helpful when you want to update an value based on a previous state or when working with objects and arrays. + +```js +// Toggle a setting when the user clicks the button. +const toggleSetting = () => + setAttributes( ( currentAttr ) => ( { + mySetting: ! currentAttr.mySetting, + } ) ); + +// Add item to the list. +const addListItem = ( newListItem ) => + setAttributes( ( currentAttr ) => ( { + list: [ ...currentAttr.list, newListItem ], + } ) ); +``` + ## Save The `save` function defines the way in which the different attributes should be combined into the final markup, which is then serialized into `post_content`. diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 29f8a97b031ce2..0839ffb71a8e37 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -262,15 +262,19 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, registry ) => { // Do not add new properties here, use `useDispatch` instead to avoid // leaking new props to the public API (editor.BlockListBlock filter). return { - setAttributes( newAttributes ) { + setAttributes( nextAttributes ) { const { getMultiSelectedBlockClientIds } = registry.select( blockEditorStore ); const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds(); - const { clientId } = ownProps; + const { clientId, attributes } = ownProps; const clientIds = multiSelectedBlockClientIds.length ? multiSelectedBlockClientIds : [ clientId ]; + const newAttributes = + typeof nextAttributes === 'function' + ? nextAttributes( attributes ) + : nextAttributes; updateBlockAttributes( clientIds, newAttributes ); }, diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js b/packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js index a53983b95954ad..40414b28559365 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-firefox-draggable-compatibility.js @@ -41,12 +41,15 @@ function restore( node ) { function down( event ) { const { target } = event; - const { ownerDocument, isContentEditable } = target; + const { ownerDocument, isContentEditable, tagName } = target; + const isInputOrTextArea = [ 'INPUT', 'TEXTAREA' ].includes( tagName ); + const nodes = nodesByDocument.get( ownerDocument ); - if ( isContentEditable ) { - // Whenever an editable element is clicked, check which draggable - // blocks contain this element, and temporarily disable draggability. + if ( isContentEditable || isInputOrTextArea ) { + // Whenever an editable element or an input or textarea is clicked, + // check which draggable blocks contain this element, and temporarily + // disable draggability. for ( const node of nodes ) { if ( node.getAttribute( 'draggable' ) === 'true' && @@ -57,8 +60,8 @@ function down( event ) { } } } else { - // Whenever a non-editable element is clicked, re-enable draggability - // for any blocks that were previously disabled. + // Whenever a non-editable element or an input or textarea is clicked, + // re-enable draggability for any blocks that were previously disabled. for ( const node of nodes ) { restore( node ); } @@ -66,11 +69,11 @@ function down( event ) { } /** - * In Firefox, the `draggable` and `contenteditable` attributes don't play well - * together. When `contenteditable` is within a `draggable` element, selection - * doesn't get set in the right place. The only solution is to temporarily - * remove the `draggable` attribute clicking inside `contenteditable` elements. - * + * In Firefox, the `draggable` and `contenteditable` or `input` or `textarea` + * elements don't play well together. When these elements are within a + * `draggable` element, selection doesn't get set in the right place. The only + * solution is to temporarily remove the `draggable` attribute clicking inside + * these elements. * @return {Function} Cleanup function. */ export function useFirefoxDraggableCompatibility() { diff --git a/packages/block-library/src/latest-posts/index.php b/packages/block-library/src/latest-posts/index.php index f8fd8ea27bc453..b970f486bb6a1c 100644 --- a/packages/block-library/src/latest-posts/index.php +++ b/packages/block-library/src/latest-posts/index.php @@ -33,6 +33,9 @@ function block_core_latest_posts_get_excerpt_length() { * * @since 5.0.0 * + * @global WP_Post $post Global post object. + * @global int $block_core_latest_posts_excerpt_length Excerpt length set by the Latest Posts core block. + * * @param array $attributes The block attributes. * * @return string Returns the post content with latest posts added. diff --git a/packages/block-library/src/query/edit/query-content.js b/packages/block-library/src/query/edit/query-content.js index 459ce2af018c2a..55a2b24cfd1a51 100644 --- a/packages/block-library/src/query/edit/query-content.js +++ b/packages/block-library/src/query/edit/query-content.js @@ -86,8 +86,11 @@ export default function QueryContent( { // because updates are batched after the render and changes in different query properties // would cause to override previous wanted changes. const updateQuery = useCallback( - ( newQuery ) => setAttributes( { query: { ...query, ...newQuery } } ), - [ query, setAttributes ] + ( newQuery ) => + setAttributes( ( prevAttributes ) => ( { + query: { ...prevAttributes.query, ...newQuery }, + } ) ), + [ setAttributes ] ); useEffect( () => { const newQuery = {}; diff --git a/packages/create-block/lib/templates/plugin/$slug.php.mustache b/packages/create-block/lib/templates/plugin/$slug.php.mustache index f839b1d3b42064..be2b6f5b4e03b1 100644 --- a/packages/create-block/lib/templates/plugin/$slug.php.mustache +++ b/packages/create-block/lib/templates/plugin/$slug.php.mustache @@ -37,7 +37,17 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } - +{{#wpScripts}} +/** + * Registers the block using a `blocks-manifest.php` file, which improves the performance of block type registration. + * Behind the scenes, it also registers all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://make.wordpress.org/core/2025/03/13/more-efficient-block-type-registration-in-6-8/ + * @see https://make.wordpress.org/core/2024/10/17/new-block-type-registration-apis-to-improve-performance-in-wordpress-6-7/ + */ +{{/wpScripts}} +{{^wpScripts}} /** * Registers the block using the metadata loaded from the `block.json` file. * Behind the scenes, it registers also all assets so they can be enqueued @@ -45,18 +55,38 @@ if ( ! defined( 'ABSPATH' ) ) { * * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ +{{/wpScripts}} function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { {{#wpScripts}} - if ( function_exists( 'wp_register_block_types_from_metadata_collection' ) ) { // Function introduced in WordPress 6.8. + /** + * Registers the block(s) metadata from the `blocks-manifest.php` and registers the block type(s) + * based on the registered block metadata. + * Added in WordPress 6.8 to simplify the block metadata registration process added in WordPress 6.7. + * + * @see https://make.wordpress.org/core/2025/03/13/more-efficient-block-type-registration-in-6-8/ + */ + if ( function_exists( 'wp_register_block_types_from_metadata_collection' ) ) { wp_register_block_types_from_metadata_collection( __DIR__ . '/build', __DIR__ . '/build/blocks-manifest.php' ); - } else { - if ( function_exists( 'wp_register_block_metadata_collection' ) ) { // Function introduced in WordPress 6.7. - wp_register_block_metadata_collection( __DIR__ . '/build', __DIR__ . '/build/blocks-manifest.php' ); - } - $manifest_data = require __DIR__ . '/build/blocks-manifest.php'; - foreach ( array_keys( $manifest_data ) as $block_type ) { - register_block_type( __DIR__ . "/build/{$block_type}" ); - } + return; + } + + /** + * Registers the block(s) metadata from the `blocks-manifest.php` file. + * Added to WordPress 6.7 to improve the performance of block type registration. + * + * @see https://make.wordpress.org/core/2024/10/17/new-block-type-registration-apis-to-improve-performance-in-wordpress-6-7/ + */ + if ( function_exists( 'wp_register_block_metadata_collection' ) ) { + wp_register_block_metadata_collection( __DIR__ . '/build', __DIR__ . '/build/blocks-manifest.php' ); + } + /** + * Registers the block type(s) in the `blocks-manifest.php` file. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ + $manifest_data = require __DIR__ . '/build/blocks-manifest.php'; + foreach ( array_keys( $manifest_data ) as $block_type ) { + register_block_type( __DIR__ . "/build/{$block_type}" ); } {{/wpScripts}} {{^wpScripts}} diff --git a/packages/edit-post/src/components/layout/use-should-iframe.js b/packages/edit-post/src/components/layout/use-should-iframe.js index cd2a893d8d1cd0..250f98afca2b05 100644 --- a/packages/edit-post/src/components/layout/use-should-iframe.js +++ b/packages/edit-post/src/components/layout/use-should-iframe.js @@ -11,24 +11,26 @@ import { store as blockEditorStore } from '@wordpress/block-editor'; */ import { unlock } from '../../lock-unlock'; +const isGutenbergPlugin = globalThis.IS_GUTENBERG_PLUGIN ? true : false; + export function useShouldIframe() { return useSelect( ( select ) => { const { getEditorSettings, getCurrentPostType, getDeviceType } = select( editorStore ); return ( - // If the theme is block based, we ALWAYS use the iframe for - // consistency across the post and site editor. The iframe was - // introduced long before the sited editor and block themes, so - // these themes are expecting it. - getEditorSettings().__unstableIsBlockBasedTheme || - // For classic themes, we also still want to iframe all the special + // If the theme is block based and the Gutenberg plugin is active, + // we ALWAYS use the iframe for consistency across the post and site + // editor. + ( isGutenbergPlugin && + getEditorSettings().__unstableIsBlockBasedTheme ) || + // We also still want to iframe all the special // editor features and modes such as device previews, zoom out, and // template/pattern editing. getDeviceType() !== 'Desktop' || [ 'wp_template', 'wp_block' ].includes( getCurrentPostType() ) || unlock( select( blockEditorStore ) ).isZoomOut() || - // Finally, still iframe the editor for classic themes if all blocks - // are v3 (which means they are marked as iframe-compatible). + // Finally, still iframe the editor if all blocks are v3 (which means + // they are marked as iframe-compatible). select( blocksStore ) .getBlockTypes() .every( ( type ) => type.apiVersion >= 3 ) diff --git a/packages/editor/src/components/start-page-options/index.js b/packages/editor/src/components/start-page-options/index.js index 14d052052e7005..a1661ee2fcbadf 100644 --- a/packages/editor/src/components/start-page-options/index.js +++ b/packages/editor/src/components/start-page-options/index.js @@ -17,6 +17,7 @@ import { store as interfaceStore } from '@wordpress/interface'; /** * Internal dependencies */ +import { TEMPLATE_POST_TYPE } from '../../store/constants'; import { store as editorStore } from '../../store'; export function useStartPatterns() { @@ -152,7 +153,8 @@ export default function StartPageOptions() { return { postId: getCurrentPostId(), enabled: - choosePatternModalEnabled && 'page' === getCurrentPostType(), + choosePatternModalEnabled && + TEMPLATE_POST_TYPE !== getCurrentPostType(), }; }, [] ); diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 9535e4dab6e390..efca9bf643f47a 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Bug Fixes + +- Fix: `--blocks-manifest` CLI flag doesn't work when the directory name has space ([#69766](https://github.com/WordPress/gutenberg/pull/69766)). + + ## 30.14.0 (2025-03-27) ### New Features diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index fc7592a5eaf585..74ce5e0553e217 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -275,9 +275,9 @@ class BlocksManifestPlugin { apply( compiler ) { compiler.hooks.afterEmit.tap( 'BlocksManifest', () => { exec( - `node ${ fromScriptsRoot( + `node "${ fromScriptsRoot( 'build-blocks-manifest' - ) } --input="${ compiler.options.output.path }"` + ) }" --input="${ compiler.options.output.path }"` ); } ); }