diff --git a/.eslintrc.js b/.eslintrc.js index d0c22090b93e8..e5f42eea656b9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -137,6 +137,11 @@ const restrictedSyntax = [ message: 'Avoid truthy checks on length property rendering, as zero length is rendered verbatim.', }, + { + selector: + 'CallExpression[callee.name=/^(__|_x|_n|_nx)$/] > Literal[value=/^toggle\\b/i]', + message: "Avoid using the verb 'Toggle' in translatable strings", + }, ]; /** `no-restricted-syntax` rules for components. */ diff --git a/.github/workflows/sync-assets-to-plugin-repo.yml b/.github/workflows/sync-assets-to-plugin-repo.yml new file mode 100644 index 0000000000000..c841b3ffc7957 --- /dev/null +++ b/.github/workflows/sync-assets-to-plugin-repo.yml @@ -0,0 +1,48 @@ +name: Sync Gutenberg plugin assets to WordPress.org plugin repo + +on: + push: + branches: + - trunk + paths: + - assets/** + +jobs: + sync-assets: + name: Sync assets to WordPress.org plugin repo + runs-on: ubuntu-latest + environment: wp.org plugin + env: + PLUGIN_REPO_URL: 'https://plugins.svn.wordpress.org/gutenberg' + SVN_USERNAME: ${{ secrets.SVN_USERNAME }} + SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} + + steps: + - name: Check out Gutenberg assets folder from WP.org plugin repo + run: | + svn checkout "$PLUGIN_REPO_URL/assets" \ + --username "$SVN_USERNAME" --password "$SVN_PASSWORD" + + - name: Delete everything + run: find assets -type f -not -path 'assets/.svn/*' -delete + + - name: Checkout assets from current release + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + sparse-checkout: | + assets + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + path: git + + - name: Copy files from git checkout to svn working copy + run: cp -R git/assets/* assets + + - name: Commit the updated assets + working-directory: ./assets + run: | + svn st | awk '/^?/ {print $2}' | xargs -r svn add + svn st | awk '/^!/ {print $2}' | xargs -r svn rm + svn commit . \ + -m "Sync assets for commit $GITHUB_SHA" \ + --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" \ + --config-option=servers:global:http-timeout=600 diff --git a/LICENSE.md b/LICENSE.md index 983294723c480..12a05f0c071a6 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ ## Gutenberg - Copyright 2016-2024 by the contributors + Copyright 2016-2025 by the contributors **License for Contributions (on and after April 15, 2021)** diff --git a/assets/README.md b/assets/README.md new file mode 100644 index 0000000000000..e437ec744d380 --- /dev/null +++ b/assets/README.md @@ -0,0 +1,7 @@ +## Gutenberg Plugin Assets + +The contents of this directory are synced from the [`assets/` directory in the Gutenberg repository on GitHub](https://github.com/WordPress/gutenberg/tree/trunk/assets) to the [`assets/` directory of the Gutenberg WordPress.org plugin repository](https://plugins.trac.wordpress.org/browser/gutenberg/assets). **Any changes committed directly to the plugin repository on WordPress.org will be overwritten.** + +The sync is performed by a [GitHub Actions workflow](https://github.com/WordPress/gutenberg/actions/workflows/sync-assets-to-plugin-repo.yml) that is triggered whenever a file in this directory is changed. + +Since that workflow requires access to WP.org plugin repository credentials, it needs to be approved manually by a member of the Gutenberg Core team. If you don't have the necessary permissions, please ask someone in [#core-editor](https://wordpress.slack.com/archives/C02QB2JS7). diff --git a/assets/banner-1544x500.jpg b/assets/banner-1544x500.jpg new file mode 100644 index 0000000000000..12e7192dd4285 Binary files /dev/null and b/assets/banner-1544x500.jpg differ diff --git a/assets/banner-772x250.jpg b/assets/banner-772x250.jpg new file mode 100644 index 0000000000000..316f7741071cb Binary files /dev/null and b/assets/banner-772x250.jpg differ diff --git a/assets/icon-128x128.jpg b/assets/icon-128x128.jpg new file mode 100644 index 0000000000000..051af8504a919 Binary files /dev/null and b/assets/icon-128x128.jpg differ diff --git a/assets/icon-256x256.jpg b/assets/icon-256x256.jpg new file mode 100644 index 0000000000000..b7497f61652b7 Binary files /dev/null and b/assets/icon-256x256.jpg differ diff --git a/backport-changelog/6.8/7069.md b/backport-changelog/6.8/7069.md deleted file mode 100644 index 3e734637ddbb2..0000000000000 --- a/backport-changelog/6.8/7069.md +++ /dev/null @@ -1,6 +0,0 @@ -https://github.com/WordPress/wordpress-develop/pull/7069 - -* https://github.com/WordPress/gutenberg/pull/63401 -* https://github.com/WordPress/gutenberg/pull/66918 -* https://github.com/WordPress/gutenberg/pull/67018 -* https://github.com/WordPress/gutenberg/pull/67552 diff --git a/backport-changelog/6.8/7865.md b/backport-changelog/6.8/7865.md index f7b23c944dc32..b5de24b8ee63d 100644 --- a/backport-changelog/6.8/7865.md +++ b/backport-changelog/6.8/7865.md @@ -1,3 +1,4 @@ https://github.com/WordPress/wordpress-develop/pull/7865 -* https://github.com/WordPress/gutenberg/pull/66851 \ No newline at end of file +* https://github.com/WordPress/gutenberg/pull/66851 +* https://github.com/WordPress/gutenberg/pull/68174 diff --git a/backport-changelog/6.8/8015.md b/backport-changelog/6.8/8015.md new file mode 100644 index 0000000000000..214705518a0e7 --- /dev/null +++ b/backport-changelog/6.8/8015.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/8015 + +* https://github.com/WordPress/gutenberg/pull/68058 diff --git a/backport-changelog/6.8/8031.md b/backport-changelog/6.8/8031.md new file mode 100644 index 0000000000000..864dd7562cdf3 --- /dev/null +++ b/backport-changelog/6.8/8031.md @@ -0,0 +1,4 @@ +https://github.com/WordPress/wordpress-develop/pull/8031 + +* https://github.com/WordPress/gutenberg/pull/66675 +* https://github.com/WordPress/gutenberg/pull/68243 diff --git a/backport-changelog/6.8/8032.md b/backport-changelog/6.8/8032.md new file mode 100644 index 0000000000000..4d2ad5fae5a38 --- /dev/null +++ b/backport-changelog/6.8/8032.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/8032 + +* https://github.com/WordPress/gutenberg/pull/68003 diff --git a/bin/api-docs/gen-components-docs/get-tags-from-storybook.mjs b/bin/api-docs/gen-components-docs/get-tags-from-storybook.mjs new file mode 100644 index 0000000000000..84d7beaf1e407 --- /dev/null +++ b/bin/api-docs/gen-components-docs/get-tags-from-storybook.mjs @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import fs from 'node:fs/promises'; +import babel from '@babel/core'; + +/** + * Returns `meta.tags` from a Storybook file. + * + * @param {string} filePath + * @return {Promise} Array of tags. + */ +export async function getTagsFromStorybook( filePath ) { + const fileContent = await fs.readFile( filePath, 'utf8' ); + const parsedFile = babel.parse( fileContent, { + filename: filePath, + } ); + + const meta = parsedFile.program.body.find( + ( node ) => + node.type === 'VariableDeclaration' && + node.declarations[ 0 ].id.name === 'meta' + ); + + return ( + meta.declarations[ 0 ].init.properties + .find( ( node ) => node.key.name === 'tags' ) + ?.value.elements.map( ( node ) => node.value ) ?? [] + ); +} diff --git a/bin/api-docs/gen-components-docs/index.mjs b/bin/api-docs/gen-components-docs/index.mjs index c7109dc4982c3..30888acf851ca 100644 --- a/bin/api-docs/gen-components-docs/index.mjs +++ b/bin/api-docs/gen-components-docs/index.mjs @@ -11,6 +11,7 @@ import path from 'path'; */ import { generateMarkdownDocs } from './markdown/index.mjs'; import { getDescriptionsForSubcomponents } from './get-subcomponent-descriptions.mjs'; +import { getTagsFromStorybook } from './get-tags-from-storybook.mjs'; const MANIFEST_GLOB = 'packages/components/src/**/docs-manifest.json'; @@ -113,9 +114,17 @@ await Promise.all( } ) ?? [] ); + const tags = await getTagsFromStorybook( + path.resolve( + path.dirname( manifestPath ), + 'stories/index.story.tsx' + ) + ); + const docs = generateMarkdownDocs( { typeDocs, subcomponentTypeDocs, + tags, } ); const outputFile = path.resolve( path.dirname( manifestPath ), diff --git a/bin/api-docs/gen-components-docs/markdown/index.mjs b/bin/api-docs/gen-components-docs/markdown/index.mjs index 126fdf0057b6e..28e20dc3de12e 100644 --- a/bin/api-docs/gen-components-docs/markdown/index.mjs +++ b/bin/api-docs/gen-components-docs/markdown/index.mjs @@ -8,14 +8,33 @@ import json2md from 'json2md'; */ import { generateMarkdownPropsJson } from './props.mjs'; -export function generateMarkdownDocs( { typeDocs, subcomponentTypeDocs } ) { +/** + * Converter for strings that are already formatted as Markdown. + * + * @param {string} [input] + * @return {string} The trimmed input if it is contentful, otherwise an empty string. + */ +json2md.converters.md = ( input ) => { + return input?.trim() || ''; +}; + +export function generateMarkdownDocs( { + typeDocs, + subcomponentTypeDocs, + tags, +} ) { const mainDocsJson = [ { h1: typeDocs.displayName }, '', + tags.includes( 'status-private' ) && [ + { + p: '🔒 This component is locked as a [private API](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-private-apis/). We do not yet recommend using this outside of the Gutenberg project.', + }, + ], { p: `

See the WordPress Storybook for more detailed, interactive documentation.

`, }, - typeDocs.description, + { md: typeDocs.description }, ...generateMarkdownPropsJson( typeDocs.props ), ]; @@ -26,7 +45,7 @@ export function generateMarkdownDocs( { typeDocs, subcomponentTypeDocs } ) { { h3: subcomponentTypeDoc.displayName, }, - subcomponentTypeDoc.description, + { md: subcomponentTypeDoc.description }, ...generateMarkdownPropsJson( subcomponentTypeDoc.props, { headingLevel: 4, } ), @@ -36,5 +55,5 @@ export function generateMarkdownDocs( { typeDocs, subcomponentTypeDocs } ) { return json2md( [ ...mainDocsJson, ...subcomponentDocsJson ].filter( Boolean ) - ); + ).replace( /\n+$/gm, '\n' ); // clean unnecessary consecutive newlines } diff --git a/bin/api-docs/gen-components-docs/markdown/props.mjs b/bin/api-docs/gen-components-docs/markdown/props.mjs index aaa7304121752..bacd86256f7e6 100644 --- a/bin/api-docs/gen-components-docs/markdown/props.mjs +++ b/bin/api-docs/gen-components-docs/markdown/props.mjs @@ -33,7 +33,6 @@ export function generateMarkdownPropsJson( props, { headingLevel = 2 } = {} ) { return [ { [ `h${ headingLevel + 1 }` ]: `\`${ key }\`` }, - prop.description, { ul: [ `Type: \`${ renderPropType( prop.type ) }\``, @@ -42,6 +41,7 @@ export function generateMarkdownPropsJson( props, { headingLevel = 2 } = {} ) { `Default: \`${ prop.defaultValue.value }\``, ].filter( Boolean ), }, + { md: prop.description }, ]; } ) .filter( Boolean ); diff --git a/bin/plugin/lib/utils.js b/bin/plugin/lib/utils.js index 4f57269d60c77..f4ef86c96ff08 100644 --- a/bin/plugin/lib/utils.js +++ b/bin/plugin/lib/utils.js @@ -2,7 +2,7 @@ * External dependencies */ // @ts-ignore -const inquirer = require( 'inquirer' ); +const { confirm } = require( '@inquirer/prompts' ); const fs = require( 'fs' ); const childProcess = require( 'child_process' ); const { v4: uuid } = require( 'uuid' ); @@ -97,14 +97,19 @@ async function askForConfirmation( isDefault = true, abortMessage = 'Aborting.' ) { - const { isReady } = await inquirer.prompt( [ - { - type: 'confirm', - name: 'isReady', + let isReady = false; + try { + isReady = await confirm( { default: isDefault, message, - }, - ] ); + } ); + } catch ( error ) { + if ( error instanceof Error && error.name === 'ExitPromptError' ) { + console.log( 'Cancelled.' ); + process.exit( 1 ); + } + throw error; + } if ( ! isReady ) { log( formats.error( '\n' + abortMessage ) ); diff --git a/bin/test-create-block.sh b/bin/test-create-block.sh index 99b7e8e608260..7df3b214af042 100755 --- a/bin/test-create-block.sh +++ b/bin/test-create-block.sh @@ -56,7 +56,7 @@ if [ "$expected" -ne "$actual" ]; then exit 1 fi expected=7 -actual=$( find src -maxdepth 1 -type f | wc -l ) +actual=$( find src -maxdepth 2 -type f | wc -l ) if [ "$expected" -ne "$actual" ]; then error "Expected $expected files in the \`src\` directory, but found $actual." exit 1 @@ -70,7 +70,7 @@ status "Building block..." status "Verifying build..." expected=9 -actual=$( find build -maxdepth 1 -type f | wc -l ) +actual=$( find build -maxdepth 2 -type f | wc -l ) if [ "$expected" -ne "$actual" ]; then error "Expected $expected files in the \`build\` directory, but found $actual." exit 1 diff --git a/bin/tsconfig.json b/bin/tsconfig.json index 3ec5d5826a045..4baf899c9dce9 100644 --- a/bin/tsconfig.json +++ b/bin/tsconfig.json @@ -16,6 +16,7 @@ "noEmit": true, "outDir": ".cache" }, + "include": [], "files": [ "./api-docs/update-api-docs.js", "./plugin/config.js", diff --git a/bin/validate-tsconfig.mjs b/bin/validate-tsconfig.mjs index 91d74b1bdb413..47d6a320d7290 100755 --- a/bin/validate-tsconfig.mjs +++ b/bin/validate-tsconfig.mjs @@ -29,14 +29,33 @@ for ( const packageName of packagesWithTypes ) { hasErrors = true; } - const packageJson = JSON.parse( - readFileSync( `packages/${ packageName }/package.json`, 'utf8' ) - ); - const tsconfigJson = JSON.parse( - stripJsonComments( - readFileSync( `packages/${ packageName }/tsconfig.json`, 'utf8' ) - ) - ); + let packageJson; + try { + packageJson = JSON.parse( + readFileSync( `packages/${ packageName }/package.json`, 'utf8' ) + ); + } catch ( e ) { + console.error( + `Error parsing package.json for package ${ packageName }` + ); + throw e; + } + let tsconfigJson; + try { + tsconfigJson = JSON.parse( + stripJsonComments( + readFileSync( + `packages/${ packageName }/tsconfig.json`, + 'utf8' + ) + ) + ); + } catch ( e ) { + console.error( + `Error parsing tsconfig.json for package ${ packageName }` + ); + throw e; + } if ( packageJson.dependencies ) { for ( const dependency of Object.keys( packageJson.dependencies ) ) { if ( dependency.startsWith( '@wordpress/' ) ) { diff --git a/changelog.txt b/changelog.txt index 665265aef64d4..bb71ae8617d7f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,458 @@ == Changelog == += 20.0.0-rc.1 = + + +## Changelog + +### Features + +#### Interactivity API +- Prevent each directive errors and allow any iterable. ([67798](https://github.com/WordPress/gutenberg/pull/67798)) + + +### Enhancements + +- Add dropdown menu props to ToolsPanel component. ([68019](https://github.com/WordPress/gutenberg/pull/68019)) +- Create Block: Allow external templates to customize more fields. ([68193](https://github.com/WordPress/gutenberg/pull/68193)) +- Create Block: Optimize the default template for multiple blocks case. ([68175](https://github.com/WordPress/gutenberg/pull/68175)) +- DOM: Support class wildcard matcher in 'cleanNodeList'. ([67830](https://github.com/WordPress/gutenberg/pull/67830)) +- Scripts: Recommend passing JS entry points with paths. ([68251](https://github.com/WordPress/gutenberg/pull/68251)) +- Upgrade sass to version 1.54.0. ([68380](https://github.com/WordPress/gutenberg/pull/68380)) +- Use Badge component in dataview grids. ([68062](https://github.com/WordPress/gutenberg/pull/68062)) +- Use Badge component in page markers. ([68103](https://github.com/WordPress/gutenberg/pull/68103)) +- postcss-plugins-preset: Bump autoprefixer to 10.4.20. ([68237](https://github.com/WordPress/gutenberg/pull/68237)) +- wp-env: Add multisite support. ([67845](https://github.com/WordPress/gutenberg/pull/67845)) + +#### Block Library +- Add Tools Panel dropdown menu props to More block. ([68039](https://github.com/WordPress/gutenberg/pull/68039)) +- Add block example attribute for Comments Form block. ([68267](https://github.com/WordPress/gutenberg/pull/68267)) +- Add block example attribute for Comments block. ([68266](https://github.com/WordPress/gutenberg/pull/68266)) +- Archive: Add dropdown menu props to ToolsPanel component. ([68010](https://github.com/WordPress/gutenberg/pull/68010)) +- Archives Block: Refactor setting panel. ([67841](https://github.com/WordPress/gutenberg/pull/67841)) +- Button Block: Refactor setting panel. ([67887](https://github.com/WordPress/gutenberg/pull/67887)) +- Button: Update Settings text labels. ([68265](https://github.com/WordPress/gutenberg/pull/68265)) +- Date Block: Add dropdown menu props to ToolsPanel component. ([68018](https://github.com/WordPress/gutenberg/pull/68018)) +- Date Block: Refactor settings panel to use ToolsPanel. ([67906](https://github.com/WordPress/gutenberg/pull/67906)) +- Details Block: Migrate to Toolspanel. ([67966](https://github.com/WordPress/gutenberg/pull/67966)) +- Excerpt Block: Refactor settings panel to use ToolsPanel. ([67908](https://github.com/WordPress/gutenberg/pull/67908)) +- Featured Image Block: Refactor setting panel. ([67456](https://github.com/WordPress/gutenberg/pull/67456)) +- Introduce new filter "render_block_core_navigation_link_allowed_post_status". ([63181](https://github.com/WordPress/gutenberg/pull/63181)) +- Latest Posts Border Block Support. ([66353](https://github.com/WordPress/gutenberg/pull/66353)) +- Login/Logout: Add dropdown menu props to ToolsPanel component. ([68009](https://github.com/WordPress/gutenberg/pull/68009)) +- Login/Logout: Refactor settings panel to use ToolsPanel. ([67909](https://github.com/WordPress/gutenberg/pull/67909)) +- More Block: Refactor settings panel to use ToolsPanel. ([67905](https://github.com/WordPress/gutenberg/pull/67905)) +- Navigation Submenu Block: Refactor settings panel to use ToolsPanel. ([67969](https://github.com/WordPress/gutenberg/pull/67969)) +- Page List Block: Add dropdown menu props to ToolsPanel component. ([68012](https://github.com/WordPress/gutenberg/pull/68012)) +- Page List Block: Refactor settings panel to use ToolsPanel. ([67903](https://github.com/WordPress/gutenberg/pull/67903)) +- Post Featured Image: Use the 'ResolutionTool' component. ([68294](https://github.com/WordPress/gutenberg/pull/68294)) +- Query Page Numbers Block: Refactor settings panel to use ToolsPanel. ([67958](https://github.com/WordPress/gutenberg/pull/67958)) +- Query Page Numbers: Add dropdown menu props to ToolsPanel component. ([68013](https://github.com/WordPress/gutenberg/pull/68013)) +- Query Pagination: Refactor settings panel to use ToolsPanel. ([67914](https://github.com/WordPress/gutenberg/pull/67914)) +- Query Pagination: Update 'showLabel' help text. ([68105](https://github.com/WordPress/gutenberg/pull/68105)) +- Query Total block: Reduce concatenation in the output text. ([68150](https://github.com/WordPress/gutenberg/pull/68150)) +- Read More: Add example preview. ([68288](https://github.com/WordPress/gutenberg/pull/68288)) +- Refactor "Settings" panel of Navigation Item block to use ToolsPanel instead of PanelBody. ([67973](https://github.com/WordPress/gutenberg/pull/67973)) +- Replace PanelBody with ToolsPanel and ToolsPanelItem in column block. ([67913](https://github.com/WordPress/gutenberg/pull/67913)) +- Replace PanelBody with ToolsPanel and ToolsPanelItem in spacer block. ([67981](https://github.com/WordPress/gutenberg/pull/67981)) +- Replace PanelBody with ToolsPanel in columns block. ([67910](https://github.com/WordPress/gutenberg/pull/67910)) +- Site Title Block: Add dropdown menu props to ToolsPanel component. ([68017](https://github.com/WordPress/gutenberg/pull/68017)) +- Site Title Block: Refactor settings panel to use ToolsPanel. ([67898](https://github.com/WordPress/gutenberg/pull/67898)) +- Social Icon: Migrate to Toolspanel. ([67974](https://github.com/WordPress/gutenberg/pull/67974)) +- Social Icons: Migrate to Toolspanel. ([67975](https://github.com/WordPress/gutenberg/pull/67975)) +- Table Block: Refactor settings panel to use ToolsPanel. ([67896](https://github.com/WordPress/gutenberg/pull/67896)) +- Tag Cloud Block: Refactor settings panel to use ToolsPanel. ([67911](https://github.com/WordPress/gutenberg/pull/67911)) +- Video Block: Refactor setting panel. ([67044](https://github.com/WordPress/gutenberg/pull/67044)) + +#### Components +- : Badge Component. ([66555](https://github.com/WordPress/gutenberg/pull/66555)) +- Badge: Support text truncation. ([68107](https://github.com/WordPress/gutenberg/pull/68107)) +- Button: Add hover style to `secondary` variant. ([67325](https://github.com/WordPress/gutenberg/pull/67325)) +- CreateTemplatePartModalContents: Use native radio inputs. ([67702](https://github.com/WordPress/gutenberg/pull/67702)) +- Menu: More granular sub-components. ([67422](https://github.com/WordPress/gutenberg/pull/67422)) +- RangeControl: Animate thumb and track only when using marks. ([67836](https://github.com/WordPress/gutenberg/pull/67836)) +- Storybook: Add more `max-width` containers. ([68080](https://github.com/WordPress/gutenberg/pull/68080)) +- Storybook: Upgrade to the latest version (v8.4.7). ([67863](https://github.com/WordPress/gutenberg/pull/67863)) +- Storybook: Upgrade to v8.0.x. ([67574](https://github.com/WordPress/gutenberg/pull/67574)) +- Unite inline Ariakit imports. ([67818](https://github.com/WordPress/gutenberg/pull/67818)) + +#### Style Book +- Give style book its own route so it can be linked to directly. ([67811](https://github.com/WordPress/gutenberg/pull/67811)) +- Stylebook: Add the Appearance -> Design submenu through `admin_menu` action. ([68174](https://github.com/WordPress/gutenberg/pull/68174)) +- Try splitting style book into sections. ([68071](https://github.com/WordPress/gutenberg/pull/68071)) +- Try toggle instead of dropdown to show stylebook. ([67810](https://github.com/WordPress/gutenberg/pull/67810)) + +#### Design Tools +- Post Comments Link: Add Border Support. ([68450](https://github.com/WordPress/gutenberg/pull/68450)) +- Post Template: Add Border and Spacing Support. ([64425](https://github.com/WordPress/gutenberg/pull/64425)) +- Query Total: Add Border Support. ([68323](https://github.com/WordPress/gutenberg/pull/68323)) + +#### Block Editor +- Add reset button to ColorGradientSettingsDropdown. ([67800](https://github.com/WordPress/gutenberg/pull/67800)) +- ChildLayoutControl: Use units defined in theme.json. ([67784](https://github.com/WordPress/gutenberg/pull/67784)) +- KeyboardShortcuts: Update delete shortcut to use `shift + Backspace`. ([68164](https://github.com/WordPress/gutenberg/pull/68164)) + +#### Block hooks +- Apply to Post Content (on frontend and in editor). ([67272](https://github.com/WordPress/gutenberg/pull/67272)) +- Synced Patterns: Apply Block Hooks. ([68058](https://github.com/WordPress/gutenberg/pull/68058)) + +#### Media +- Split upload into verbs and nouns. ([68227](https://github.com/WordPress/gutenberg/pull/68227)) + +#### Zoom Out +- Remove placeholder of default paragraph when it's the only block and canvas is zoomed out. ([68106](https://github.com/WordPress/gutenberg/pull/68106)) + +#### Interactivity API +- iAPI Router: Handle styles assets on region-based navigation. ([67826](https://github.com/WordPress/gutenberg/pull/67826)) + +#### Plugin +- Add a Playground blueprint json to the /assets/blueprints folder of Plugin Repo. ([67742](https://github.com/WordPress/gutenberg/pull/67742)) + +#### Site Editor +- Pages: Add "Set as posts page" action. ([67650](https://github.com/WordPress/gutenberg/pull/67650)) + +#### Write mode +- Allow template part editing in write mode. ([67372](https://github.com/WordPress/gutenberg/pull/67372)) + +#### Patterns +- Replace Starter Content modal with inserter panel. ([66836](https://github.com/WordPress/gutenberg/pull/66836)) + +#### Commands +- Add command to navigate to site editor. ([66722](https://github.com/WordPress/gutenberg/pull/66722)) + +#### Inspector Controls +- Use custom name in block sidebar if available (retaining block type information). ([65641](https://github.com/WordPress/gutenberg/pull/65641)) + + +### New APIs + +#### Components +- BoxControl: Add support for presets. ([67688](https://github.com/WordPress/gutenberg/pull/67688)) + + +### Bug Fixes + +- Add duotone and dimensions to the block level for translation. ([68243](https://github.com/WordPress/gutenberg/pull/68243)) +- Add text domain option while scaffolding the block in create-block. ([57197](https://github.com/WordPress/gutenberg/pull/57197)) +- Added `is-focus-mode` class on all viewports. ([67377](https://github.com/WordPress/gutenberg/pull/67377)) +- Editor: Fix initial edits applied again after saving the post. ([68273](https://github.com/WordPress/gutenberg/pull/68273)) +- Fix dataviews commonjs export. ([67962](https://github.com/WordPress/gutenberg/pull/67962)) +- Get active element within the iframe when restoring focus. ([68060](https://github.com/WordPress/gutenberg/pull/68060)) +- Make strings in theme.json translatable. ([66675](https://github.com/WordPress/gutenberg/pull/66675)) +- Scripts: Use fork of `rtlcss-webpack-plugin` to fix issues with deps. ([68201](https://github.com/WordPress/gutenberg/pull/68201)) + +#### Block Library +- Columns: Add space above notice text. ([68259](https://github.com/WordPress/gutenberg/pull/68259)) +- Enhance: Improve pagination logic in core/query-pagination-previous block. ([68070](https://github.com/WordPress/gutenberg/pull/68070)) +- Fix author information leakage by author blocks for Custom Post Types without author support & display notice to user. ([67136](https://github.com/WordPress/gutenberg/pull/67136)) +- Media & Text: Correctly reset the 'useFeaturedImage' attribute. ([68247](https://github.com/WordPress/gutenberg/pull/68247)) +- Navigation Submenu Block: Add dropdown menu props to ToolsPanel component. ([68015](https://github.com/WordPress/gutenberg/pull/68015)) +- Page List Block: Fix critical error when converting to link. ([68076](https://github.com/WordPress/gutenberg/pull/68076)) +- Page List block: Don't wrap Edit button with ToolsPanelItem component. ([68248](https://github.com/WordPress/gutenberg/pull/68248)) +- Query Total: Remove nested element. ([68304](https://github.com/WordPress/gutenberg/pull/68304)) +- Table Block: Fix margin/padding to include caption in spacing. ([68281](https://github.com/WordPress/gutenberg/pull/68281)) +- Update SiteTitle block to Fix `isLink` Toggle Behavior. ([68295](https://github.com/WordPress/gutenberg/pull/68295)) +- i18n: Make example and variations translatable in `post-navigation-link`. ([68375](https://github.com/WordPress/gutenberg/pull/68375)) +- i18n: Make example translatable in `query-no-results`. ([68376](https://github.com/WordPress/gutenberg/pull/68376)) +- i18n: Make example translatable in `table-of-contents`. ([68377](https://github.com/WordPress/gutenberg/pull/68377)) + +#### Components +- Block Editor: Fix the 'Reset all' bug for the 'ResolutionTool' component. ([68296](https://github.com/WordPress/gutenberg/pull/68296)) +- BoxControl: Better minimum value support. ([67819](https://github.com/WordPress/gutenberg/pull/67819)) +- BoxControl: Fix `aria-valuetext` value. ([68362](https://github.com/WordPress/gutenberg/pull/68362)) +- Fix end-to-end storybook. ([68307](https://github.com/WordPress/gutenberg/pull/68307)) +- Fixing Text Contrast for Dark Mode. ([68349](https://github.com/WordPress/gutenberg/pull/68349)) +- FontSizePicker: Add `display: Contents` rule to custom size select. ([68280](https://github.com/WordPress/gutenberg/pull/68280)) +- Storybook: Fix `emotion/is-prop-valid` warning. ([68202](https://github.com/WordPress/gutenberg/pull/68202)) +- Storybook: Fix a few editor styles warnings. ([68198](https://github.com/WordPress/gutenberg/pull/68198)) +- Storybook: Fix warnings in Layout document. ([67865](https://github.com/WordPress/gutenberg/pull/67865)) +- Use default value in `useMediaUploadSettings`. ([68100](https://github.com/WordPress/gutenberg/pull/68100)) + +#### Block Editor +- Media Replace Flow: Add custom toggle support and fix button height. ([68084](https://github.com/WordPress/gutenberg/pull/68084)) +- BlockCard: Fix title alignment. ([68115](https://github.com/WordPress/gutenberg/pull/68115)) +- DateFormatPicker: Fix styles & spacing. ([68079](https://github.com/WordPress/gutenberg/pull/68079)) +- Fix Iframe error for links without 'href'. ([68024](https://github.com/WordPress/gutenberg/pull/68024)) +- Grid Visualizer: Improve observation logic. ([68230](https://github.com/WordPress/gutenberg/pull/68230)) +- List View: Fix appender size. ([68221](https://github.com/WordPress/gutenberg/pull/68221)) +- MediaReplaceFlow: Remove store subscription in favor of modern CSS. ([68276](https://github.com/WordPress/gutenberg/pull/68276)) +- Remove patterns from the Quick Inserter to prevent misuse in block-specific contexts. ([67738](https://github.com/WordPress/gutenberg/pull/67738)) +- Revert 'Warning' component autofocus. ([68133](https://github.com/WordPress/gutenberg/pull/68133)) + +#### Post Editor +- DataViews: Fix text in action for setting site home page. ([67787](https://github.com/WordPress/gutenberg/pull/67787)) +- Edit post: Fix meta box pane’s pointer capture. ([68252](https://github.com/WordPress/gutenberg/pull/68252)) +- Editor: Remove HTML from the post title in the document bar. ([68358](https://github.com/WordPress/gutenberg/pull/68358)) +- Fix: Some 403 errors for editor roles. ([68146](https://github.com/WordPress/gutenberg/pull/68146)) +- Improve logic to show entities saved panel description. ([67971](https://github.com/WordPress/gutenberg/pull/67971)) + +#### DataViews +- Don't render actions dropdown when all eligible ones are `primary`. ([68168](https://github.com/WordPress/gutenberg/pull/68168)) +- Handle `grid` preview size based on container width. ([68078](https://github.com/WordPress/gutenberg/pull/68078)) +- Hide actions related UI in `grid` when no actions or bulk actions are passed. ([68033](https://github.com/WordPress/gutenberg/pull/68033)) +- Pages: Update layout-specific configuration when the view is updated. ([67881](https://github.com/WordPress/gutenberg/pull/67881)) +- Use `action.disabled` state to disable actions (primary and secondary). ([68275](https://github.com/WordPress/gutenberg/pull/68275)) + +#### Site Editor +- Add CSS classname to fix the negative margins not appearing in the Navigation Screen. ([67825](https://github.com/WordPress/gutenberg/pull/67825)) +- Fix obsolete `getLocationWithParams` usage. ([68388](https://github.com/WordPress/gutenberg/pull/68388)) +- Pages: Remove unnecessary padding for items. ([67977](https://github.com/WordPress/gutenberg/pull/67977)) +- Update active menu item appearance. ([68147](https://github.com/WordPress/gutenberg/pull/68147)) + +#### Style Book +- Fix global styles updating in style book. ([68111](https://github.com/WordPress/gutenberg/pull/68111)) +- Fix style book background color. ([68088](https://github.com/WordPress/gutenberg/pull/68088)) +- Fix uploading background images in stylebook view. ([68159](https://github.com/WordPress/gutenberg/pull/68159)) +- Stylebook: Avoid double line in subcategory titles. ([67752](https://github.com/WordPress/gutenberg/pull/67752)) + +#### Zoom Out +- Allow replace operation on empty default block in Zoom Out. ([68026](https://github.com/WordPress/gutenberg/pull/68026)) +- Fix don't show inserter in Zoom Out dropzone when the text is visible. ([68031](https://github.com/WordPress/gutenberg/pull/68031)) +- Hide separators for currently dragged section in Zoom Out. ([67638](https://github.com/WordPress/gutenberg/pull/67638)) +- Make Write mode and Zoom out block options menus consistent. ([67749](https://github.com/WordPress/gutenberg/pull/67749)) + +#### Design Tools +- Background supports: Add default controls supports. ([68085](https://github.com/WordPress/gutenberg/pull/68085)) +- Block supports: Show selected item in font family select control. ([68254](https://github.com/WordPress/gutenberg/pull/68254)) +- Fix: Ensure consistency in editor tools for navigation buttons and delete options. ([67253](https://github.com/WordPress/gutenberg/pull/67253)) + +#### Template Editor +- Fix: Editing "Page" is broken for low capability users. ([68110](https://github.com/WordPress/gutenberg/pull/68110)) +- Plugin: Fix eligibility check for post types' default rendering mode. ([67879](https://github.com/WordPress/gutenberg/pull/67879)) + +#### Widgets Editor +- Customizer Widgets: Fix inserter button size and animation. ([67880](https://github.com/WordPress/gutenberg/pull/67880)) +- Widget Editor: Fix: Close button is not working. ([65443](https://github.com/WordPress/gutenberg/pull/65443)) + +#### Meta Boxes +- Show metabox when pattern is accessed directly. ([68255](https://github.com/WordPress/gutenberg/pull/68255)) + +#### Typography +- Button Block: Set proper typography for inner elements. ([68023](https://github.com/WordPress/gutenberg/pull/68023)) + +#### History +- Query Pagination: Fix 'undo' trap. ([68022](https://github.com/WordPress/gutenberg/pull/68022)) + +#### npm Packages +- Add --glob argument to rimraf cli scripts. ([67829](https://github.com/WordPress/gutenberg/pull/67829)) + +#### Paste +- Image: Avoid link class loss when pasting for raw transformation. ([67803](https://github.com/WordPress/gutenberg/pull/67803)) + +#### Extensibility +- Make Block Bindings work with `editor.BlockEdit` hook (2nd try). ([67523](https://github.com/WordPress/gutenberg/pull/67523)) + + +### Accessibility + +- Dataviews List layout: Do not use grid role on a `ul` element. ([67849](https://github.com/WordPress/gutenberg/pull/67849)) +- Fix: Templates and patterns are nesting two elements with the button role. ([67801](https://github.com/WordPress/gutenberg/pull/67801)) +- [Dataviews] Fix: Media item focus style is not visible on Grid. ([67789](https://github.com/WordPress/gutenberg/pull/67789)) + +#### Block Editor +- Fix: Inserter category tabs: Avoid unnecessary aria-label. ([68160](https://github.com/WordPress/gutenberg/pull/68160)) +- Improve accessibility of the Warning component in the block editor. ([67433](https://github.com/WordPress/gutenberg/pull/67433)) + +#### Global Styles +- Shadows: Always show reset button if hover is not supported. ([68122](https://github.com/WordPress/gutenberg/pull/68122)) +- Visual Refactor: Add Chevron Icon for Shadows in Global Styles. ([67720](https://github.com/WordPress/gutenberg/pull/67720)) + +#### Block Library +- Button: Replace ButtonGroup usage with ToggleGroupControl. ([65346](https://github.com/WordPress/gutenberg/pull/65346)) +- Fix Choose menu label when a menu has been deleted. ([67009](https://github.com/WordPress/gutenberg/pull/67009)) + +#### DataViews +- Add confirm dialog before Permanently delete. ([67824](https://github.com/WordPress/gutenberg/pull/67824)) + +#### Site Editor +- Make sure the sidebar navigation item focus style is fully visible. ([67817](https://github.com/WordPress/gutenberg/pull/67817)) + +#### Components +- CustomSelectControl: Refactor to use Ariakit store state for current value. ([67815](https://github.com/WordPress/gutenberg/pull/67815)) + + +### Performance + +#### Block Library +- Don't fetch media details if the block doesn't use a featured image. ([68299](https://github.com/WordPress/gutenberg/pull/68299)) +- Media & Text: Optimize block editor store subscriptions. ([68290](https://github.com/WordPress/gutenberg/pull/68290)) + + +### Experiments + +#### DataViews +- Proof of concept: Visualize hierarchical data. ([66479](https://github.com/WordPress/gutenberg/pull/66479)) + + +### Documentation + +- .wp-env.json schema: Add `testsPort` field. ([68220](https://github.com/WordPress/gutenberg/pull/68220)) +- Add README for TextAlignmentControl component. ([68126](https://github.com/WordPress/gutenberg/pull/68126)) +- Add layout related updates to the DataForm README. ([68050](https://github.com/WordPress/gutenberg/pull/68050)) +- Added Global Documentation in load.php. ([68325](https://github.com/WordPress/gutenberg/pull/68325)) +- Badge component: Fix Storybook URL link. ([68077](https://github.com/WordPress/gutenberg/pull/68077)) +- Badge: Fix up extra newline in readme. ([68359](https://github.com/WordPress/gutenberg/pull/68359)) +- Block Editor Storybook: Restructure the directory and add badges to private components. ([68352](https://github.com/WordPress/gutenberg/pull/68352)) +- Clarify template property behavior in InnerBlocks documentation to specify prefill when empty. ([66911](https://github.com/WordPress/gutenberg/pull/66911)) +- Components: Normalize newlines in auto-generated READMEs. ([68208](https://github.com/WordPress/gutenberg/pull/68208)) +- Components: Prevent broken lists in auto-generated readmes. ([68301](https://github.com/WordPress/gutenberg/pull/68301)) +- Components: Warn private API in auto-generated readmes. ([68317](https://github.com/WordPress/gutenberg/pull/68317)) +- Create a catalog list of private APIs. ([66558](https://github.com/WordPress/gutenberg/pull/66558)) +- DateFormatPicker: Improve line breaks in JSDoc and README. ([68006](https://github.com/WordPress/gutenberg/pull/68006)) +- Doc: Add JSDoc and update README for BlockCard component. ([68114](https://github.com/WordPress/gutenberg/pull/68114)) +- Docs: Fix some typos on reference-guide data-core-block-editor.md. ([68066](https://github.com/WordPress/gutenberg/pull/68066)) +- Documenting innerBlocks in save function. ([66689](https://github.com/WordPress/gutenberg/pull/66689)) +- Fix reference to `wp-env start` in documentation. ([68034](https://github.com/WordPress/gutenberg/pull/68034)) +- Fix wrong `npm start` command. ([65221](https://github.com/WordPress/gutenberg/pull/65221)) +- Fix: Fix link to minimal-block example plugin code. ([67888](https://github.com/WordPress/gutenberg/pull/67888)) +- Fixed typo in README of TextTransformControl. ([68443](https://github.com/WordPress/gutenberg/pull/68443)) +- Section Styles: Update block style variation documentation. ([68169](https://github.com/WordPress/gutenberg/pull/68169)) +- Storybook : Add TextTransformControl stories. ([67365](https://github.com/WordPress/gutenberg/pull/67365)) +- Storybook: Add BorderRadiusControl story. ([67383](https://github.com/WordPress/gutenberg/pull/67383)) +- Storybook: Add PlainText Storybook stories. ([67341](https://github.com/WordPress/gutenberg/pull/67341)) +- Storybook: Add stories for BlockCard component. ([67191](https://github.com/WordPress/gutenberg/pull/67191)) +- Storybook: Add stories for BlockTitle Component. ([67234](https://github.com/WordPress/gutenberg/pull/67234)) +- Storybook: Add stories for DateFormatPicker Component. ([67290](https://github.com/WordPress/gutenberg/pull/67290)) +- Storybook: Add stories for the ContrastChecker component. ([68120](https://github.com/WordPress/gutenberg/pull/68120)) +- Storybook: Add stories for the TextAlignmentControl component. ([67371](https://github.com/WordPress/gutenberg/pull/67371)) +- Storybook: Add stories for the TextDecorationControl component. ([67337](https://github.com/WordPress/gutenberg/pull/67337)) +- Storybook: Add story for the Warning component. ([68124](https://github.com/WordPress/gutenberg/pull/68124)) +- Storybook: Make prop sort order consistent. ([68152](https://github.com/WordPress/gutenberg/pull/68152)) +- Tabs: Auto-generate README. ([68209](https://github.com/WordPress/gutenberg/pull/68209)) +- Update platform documentation intro. ([61341](https://github.com/WordPress/gutenberg/pull/61341)) +- Update the copyright license to 2025. ([68440](https://github.com/WordPress/gutenberg/pull/68440)) +- Updated @since Doc Order in Inline documentation. ([68003](https://github.com/WordPress/gutenberg/pull/68003)) +- Updated Document URL in Documentation. ([67990](https://github.com/WordPress/gutenberg/pull/67990)) +- Updated Small Typo in documentation in docs/getting-started/faq.md file. ([68357](https://github.com/WordPress/gutenberg/pull/68357)) +- [Docs] Fix: Two broken links to the packages reference API and to blocks documentation. ([67889](https://github.com/WordPress/gutenberg/pull/67889)) +- env: Fix changelog entry. ([68219](https://github.com/WordPress/gutenberg/pull/68219)) +- theme.json schema: Fix block list. ([68343](https://github.com/WordPress/gutenberg/pull/68343)) + + +### Code Quality + +- Adding myself as a code owner of the block library package. ([67891](https://github.com/WordPress/gutenberg/pull/67891)) +- Create Block: Migrate Inquirer.js dependency to the new API. ([67877](https://github.com/WordPress/gutenberg/pull/67877)) +- Fix indentation in the upload-media tsconfig. ([68083](https://github.com/WordPress/gutenberg/pull/68083)) +- Fix indentation in upload-media package.json. ([68037](https://github.com/WordPress/gutenberg/pull/68037)) +- Fix: Invalid JSDoc syntax for optional object. ([68061](https://github.com/WordPress/gutenberg/pull/68061)) +- Remove some obsolete stylelint `at-rule-no-unknown` disable rules. ([68087](https://github.com/WordPress/gutenberg/pull/68087)) + +#### Components +- DatePicker: Prepare day buttons for 40px default size. ([68156](https://github.com/WordPress/gutenberg/pull/68156)) +- DropZone: Make the drop zone in Storybook the same size as the item. ([68231](https://github.com/WordPress/gutenberg/pull/68231)) +- Fix Button size violations in misc. unit tests. ([68154](https://github.com/WordPress/gutenberg/pull/68154)) +- Fix: Add soft deperecation notice for the ButtonGroup component. ([65429](https://github.com/WordPress/gutenberg/pull/65429)) +- InputControl : Deprecate 36px default size. ([66897](https://github.com/WordPress/gutenberg/pull/66897)) +- Menu: Migrate Storybook examples to CSF3. ([68204](https://github.com/WordPress/gutenberg/pull/68204)) +- Menu: Use ariakit types. ([68206](https://github.com/WordPress/gutenberg/pull/68206)) +- Navigation: Prepare for hard deprecation. ([68158](https://github.com/WordPress/gutenberg/pull/68158)) +- Navigation: Upsize back buttons. ([68157](https://github.com/WordPress/gutenberg/pull/68157)) +- RadioGroup: Log deprecation warning. ([68067](https://github.com/WordPress/gutenberg/pull/68067)) +- SelectControl : Deprecate 36px default size. ([66898](https://github.com/WordPress/gutenberg/pull/66898)) +- Slot: Use layout effect and update Cover block unit tests. ([68176](https://github.com/WordPress/gutenberg/pull/68176)) +- SlotFill: Use observableMap everywhere, remove manual rerendering. ([67400](https://github.com/WordPress/gutenberg/pull/67400)) +- Tabs: Use correct ariakit type for root component. ([68207](https://github.com/WordPress/gutenberg/pull/68207)) +- TreeSelect: Deprecate 36px default size. ([67855](https://github.com/WordPress/gutenberg/pull/67855)) + +#### Plugin +- chore: fix return type for `WP_Duotone_Gutenberg::Get_selector()`. ([66695](https://github.com/WordPress/gutenberg/pull/66695)) +- fix: Deprecated `WP_Webfonts()` constructor takes no arguments. ([66700](https://github.com/WordPress/gutenberg/pull/66700)) +- fix: Remove extraneous arg from `gutenberg_url()` call in `gutenberg_posts_dashboard()`. ([66699](https://github.com/WordPress/gutenberg/pull/66699)) +- fix: Remove extraneous param from `remove_filter()` calls. ([66697](https://github.com/WordPress/gutenberg/pull/66697)) +- fix: Wrong number of `$accepted_args` on `add_filter()` calls. ([66694](https://github.com/WordPress/gutenberg/pull/66694)) +- fix: explicitly return false in `WP_Theme_JSON_Gutenberg::Should_override_preset()`. ([66696](https://github.com/WordPress/gutenberg/pull/66696)) + +#### Block Editor +- Fix ESLint warnings for the 'useInnerBlockTemplateSync' hook. ([68355](https://github.com/WordPress/gutenberg/pull/68355)) +- FontAppearanceControl: Deprecate 36px default size. ([67854](https://github.com/WordPress/gutenberg/pull/67854)) +- FontFamilyControl: Deprecate 36px default size. ([67853](https://github.com/WordPress/gutenberg/pull/67853)) +- Inserter: Use 40px default size for toggle button. ([68155](https://github.com/WordPress/gutenberg/pull/68155)) +- LineHeightControl: Deprecate 36px default size. ([67850](https://github.com/WordPress/gutenberg/pull/67850)) + +#### Post Editor +- DocumentTools: Use standard ToolbarButton for inserter. ([68332](https://github.com/WordPress/gutenberg/pull/68332)) +- Editor: Remove constants for notices. ([68361](https://github.com/WordPress/gutenberg/pull/68361)) +- Editor: Remove the 'content-only' check from 'TemplatePartConverterMenuItem'. ([67961](https://github.com/WordPress/gutenberg/pull/67961)) + +#### DataViews +- DataForm: Add unit tests. ([68054](https://github.com/WordPress/gutenberg/pull/68054)) +- DataForm: Remove `FormFieldVisibility`. ([68203](https://github.com/WordPress/gutenberg/pull/68203)) +- [DataView] Initial list of unit tests for the DataView component. ([68205](https://github.com/WordPress/gutenberg/pull/68205)) + +#### Block Library +- Columns: Replace some store selectors with 'getBlockOrder'. ([67991](https://github.com/WordPress/gutenberg/pull/67991)) +- Fix trailing spaces in navigation block classnames. ([68161](https://github.com/WordPress/gutenberg/pull/68161)) + +#### Site Editor +- Edit Site: Standardize reduced motion handling using media queries. ([68419](https://github.com/WordPress/gutenberg/pull/68419)) + +#### Design Tools +- Block Supports: Revert stabilization of typography, border, skip serialization and default controls supports. ([68163](https://github.com/WordPress/gutenberg/pull/68163)) + +#### Zoom Out +- Correct spelling in Zoom Out Inserters comment. ([68051](https://github.com/WordPress/gutenberg/pull/68051)) + +#### Block API +- Fail gracefully when block in `createBlock` function is not registered. ([68043](https://github.com/WordPress/gutenberg/pull/68043)) + +#### Icons +- Deprecate `warning` and rename to `cautionFilled`. ([67895](https://github.com/WordPress/gutenberg/pull/67895)) + + +### Tools + +#### Build Tooling +- Add new private `upload-media` package. ([66290](https://github.com/WordPress/gutenberg/pull/66290)) +- Build: Simplify tsconfig.json files. ([68326](https://github.com/WordPress/gutenberg/pull/68326)) +- Clean script: Use braces instead of @-pattern for glob. ([67833](https://github.com/WordPress/gutenberg/pull/67833)) +- Fix VS Code performance. ([68347](https://github.com/WordPress/gutenberg/pull/68347)) +- Fix tsconfig for test/ directory. ([68346](https://github.com/WordPress/gutenberg/pull/68346)) +- Fix: Script with glob option doesn't work on Windows. ([67862](https://github.com/WordPress/gutenberg/pull/67862)) + +#### Testing +- Page - Quick Edit: Add end-to-end tests. ([68151](https://github.com/WordPress/gutenberg/pull/68151)) +- Add ESLint rule to prevent usage of the verb 'toggle' in translatable strings. ([67741](https://github.com/WordPress/gutenberg/pull/67741)) +- Enhance template registration end-to-end tests to handle welcome dialog visibility. ([68059](https://github.com/WordPress/gutenberg/pull/68059)) + + +### Various + +- ActionItem.Slot: Render as `MenuGroup` by default. ([67985](https://github.com/WordPress/gutenberg/pull/67985)) +- Storybook: Add BlockAlignmentMatrixControl Stories and update README. ([68007](https://github.com/WordPress/gutenberg/pull/68007)) +- Update "Call to Action" to "Call to action". ([67876](https://github.com/WordPress/gutenberg/pull/67876)) + +#### Plugin +- Assets: Add README.md about syncing. ([68128](https://github.com/WordPress/gutenberg/pull/68128)) +- Workflows: Sync assets to plugin repo upon change in trunk. ([68052](https://github.com/WordPress/gutenberg/pull/68052)) + + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @benazeer-ben: Add command to navigate to site editor. ([66722](https://github.com/WordPress/gutenberg/pull/66722)) +- @dhruvikpatel18: Fixed typo in README of TextTransformControl. ([68443](https://github.com/WordPress/gutenberg/pull/68443)) +- @fushar: Stylebook: Add the Appearance -> Design submenu through `admin_menu` action. ([68174](https://github.com/WordPress/gutenberg/pull/68174)) +- @im3dabasia: Storybook : Add TextTransformControl stories. ([67365](https://github.com/WordPress/gutenberg/pull/67365)) +- @justlevine: fix: Deprecated `WP_Webfonts()` constructor takes no arguments. ([66700](https://github.com/WordPress/gutenberg/pull/66700)) +- @karthick-murugan: Latest Posts Border Block Support. ([66353](https://github.com/WordPress/gutenberg/pull/66353)) +- @mayurprajapatii: Updated Document URL in Documentation. ([67990](https://github.com/WordPress/gutenberg/pull/67990)) +- @PARTHVATALIYA: Widget Editor: Fix: Close button is not working. ([65443](https://github.com/WordPress/gutenberg/pull/65443)) +- @prasadkarmalkar: Replace PanelBody with ToolsPanel and ToolsPanelItem in column block. ([67913](https://github.com/WordPress/gutenberg/pull/67913)) +- @rilwis: Fix wrong `npm start` command. ([65221](https://github.com/WordPress/gutenberg/pull/65221)) +- @sarthaknagoshe2002: Clarify template property behavior in InnerBlocks documentation to specify prefill when empty. ([66911](https://github.com/WordPress/gutenberg/pull/66911)) +- @timse201: Split upload into verbs and nouns. ([68227](https://github.com/WordPress/gutenberg/pull/68227)) +- @vampdroid: Add text domain option while scaffolding the block in create-block. ([57197](https://github.com/WordPress/gutenberg/pull/57197)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @afercia @akasunil @benazeer-ben @bph @Chrico @ciampo @d-alleyne @DAreRodz @dhruvikpatel18 @draganescu @ellatrix @fabiankaegy @fushar @getdave @gigitux @gziolo @hbhalodia @himanshupathak95 @im3dabasia @Infinite-Null @jameskoster @jasmussen @jeryj @jorgefilipecosta @jsnajdr @juanfra @justlevine @karthick-murugan @kmanijak @louwie17 @Lovor01 @Mamaduka @manzoorwanijk @matiasbenedetto @Mayank-Tripathi32 @mayurprajapatii @mcsf @michalczaplinski @mikachan @mirka @ntsekouras @oandregal @ockham @PARTHVATALIYA @prasadkarmalkar @ramonjd @rilwis @rinkalpagdar @Rishit30G @rohitmathur-7 @SainathPoojary @sarthaknagoshe2002 @SH4LIN @shail-mehta @shimotmk @sirreal @stokesman @Sukhendu2002 @swissspidy @t-hamano @talldan @tellthemachines @timse201 @tyxla @up1512001 @vampdroid @Vrishabhsk @yogeshbhutkar @youknowriad + + = 19.9.0 = ## Changelog diff --git a/docs/contributors/triage.md b/docs/contributors/triage.md index 33275b8d3df01..5c4cf7c161f03 100644 --- a/docs/contributors/triage.md +++ b/docs/contributors/triage.md @@ -9,7 +9,7 @@ To keep the repository healthy, it needs to be triaged regularly. **Triage is th The triage team is an open group of people with a particular role of making sure triage is done consistently across the Gutenberg repo. There are various types of triage which happen: - Regular self triage sessions done by members on their own time. -- Organised triage sessions done as a group at a set time. You can [review the meetings page](https://make.wordpress.org/meetings/) to find these triage sessions and appropriate slack channels. +- Organized triage sessions done as a group at a set time. You can [review the meetings page](https://make.wordpress.org/meetings/) to find these triage sessions and appropriate slack channels. - Focused triage sessions on a specific board, label or feature. These are the expectations of being a triage team member: diff --git a/docs/explanations/architecture/styles.md b/docs/explanations/architecture/styles.md index 5f5e73d1372f7..68f09f04d21d3 100644 --- a/docs/explanations/architecture/styles.md +++ b/docs/explanations/architecture/styles.md @@ -37,10 +37,8 @@ The user may change the state of this block by applying different styles: a text After some user modifications to the block, the initial markup may become something like this: ```html -

+

``` This is what we refer to as "user-provided block styles", also know as "local styles" or "serialized styles". Essentially, each tool (font size, color, etc) ends up adding some classes and/or inline styles to the block markup. The CSS styling for these classes is part of the block, global, or theme stylesheets. @@ -125,7 +123,7 @@ The block supports API only serializes the font size value to the wrapper, resul This is an active area of work you can follow [in the tracking issue](https://github.com/WordPress/gutenberg/issues/38167). The linked proposal is exploring a different way to serialize the user changes: instead of each block support serializing its own data (for example, classes such as `has-small-font-size`, `has-green-color`) the idea is the block would get a single class instead (for example, `wp-style-UUID`) and the CSS styling for that class will be generated in the server by WordPress. -While work continues in that proposal, there's an escape hatch, an experimental option block authors can use. Any block support can skip the serialization to HTML markup by using `skipSerialization`. For example: +While work continues in that proposal, there's an escape hatch, an experimental option block authors can use. Any block support can skip the serialization to HTML markup by using `__experimentalSkipSerialization`. For example: ```json { @@ -134,7 +132,7 @@ While work continues in that proposal, there's an escape hatch, an experimental "supports": { "typography": { "fontSize": true, - "skipSerialization": true + "__experimentalSkipSerialization": true } } } @@ -142,7 +140,7 @@ While work continues in that proposal, there's an escape hatch, an experimental This means that the typography block support will do all of the things (create a UI control, bind the block attribute to the control, etc) except serializing the user values into the HTML markup. The classes and inline styles will not be automatically applied to the wrapper and it is the block author's responsibility to implement this in the `edit`, `save`, and `render_callback` functions. See [this issue](https://github.com/WordPress/gutenberg/issues/28913) for examples of how it was done for some blocks provided by WordPress. -Note that, if `skipSerialization` is enabled for a group (typography, color, spacing) it affects _all_ block supports within this group. In the example above _all_ the properties within the `typography` group will be affected (e.g. `fontSize`, `lineHeight`, `fontFamily` .etc). +Note that, if `__experimentalSkipSerialization` is enabled for a group (typography, color, spacing) it affects _all_ block supports within this group. In the example above _all_ the properties within the `typography` group will be affected (e.g. `fontSize`, `lineHeight`, `fontFamily` .etc). To enable for a _single_ property only, you may use an array to declare which properties are to be skipped. In the example below, only `fontSize` will skip serialization, leaving other items within the `typography` group (e.g. `lineHeight`, `fontFamily` .etc) unaffected. @@ -154,7 +152,7 @@ To enable for a _single_ property only, you may use an array to declare which pr "typography": { "fontSize": true, "lineHeight": true, - "skipSerialization": [ "fontSize" ] + "__experimentalSkipSerialization": [ "fontSize" ] } } } @@ -475,7 +473,7 @@ If blocks do this, they need to be registered in the server using the `block.jso Every chunk of styles can only use a single selector. -This is particularly relevant if the block is using `skipSerialization` to serialize the different style properties to different nodes other than the wrapper. See "Current limitations of blocks supports" for more. +This is particularly relevant if the block is using `__experimentalSkipSerialization` to serialize the different style properties to different nodes other than the wrapper. See "Current limitations of blocks supports" for more. #### 3. **Only a single property per block** diff --git a/docs/getting-started/faq.md b/docs/getting-started/faq.md index 8ac489e3c154a..d9120cc58197e 100644 --- a/docs/getting-started/faq.md +++ b/docs/getting-started/faq.md @@ -8,7 +8,7 @@ What follows is a set of questions that have come up from the last few years of “Gutenberg” is the name of the project to create a new editor experience for WordPress — contributors have been working on it since January 2017 and it’s one of the most significant changes to WordPress in years. It’s built on the idea of using “blocks” to write and design posts and pages. This will serve as the foundation for future improvements to WordPress, including blocks as a way not just to design posts and pages, but also entire sites. The overall goal is to simplify the first-time user experience of WordPress — for those who are writing, editing, publishing, and designing web pages. The editing experience is intended to give users a better visual representation of what their post or page will look like when they hit publish. Originally, this was the kickoff goal: -> The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery. +> The editor will endeavor to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery. Key takeaways include the following points: diff --git a/docs/getting-started/fundamentals/javascript-in-the-block-editor.md b/docs/getting-started/fundamentals/javascript-in-the-block-editor.md index 7accc5d4c2129..4cd7c0b36fe86 100644 --- a/docs/getting-started/fundamentals/javascript-in-the-block-editor.md +++ b/docs/getting-started/fundamentals/javascript-in-the-block-editor.md @@ -26,7 +26,7 @@ The diagram below provides an overview of the build process when using the `wp-s - **Production Mode (`npm run build`):** In this mode, `wp-scripts` compiles your JavaScript, minifying the output to reduce file size and improve loading times in the browser. This is ideal for deploying your code to a live site. -- **Development Mode (`npm run start`):** This mode is tailored for active development. It skips minification for easier debugging, generates source maps for better error tracking, and watches your source files for changes. When a change is detected, it automatically rebuilds the affected files, allowing you to see updates in real-time. +- **Development Mode (`npm start`):** This mode is tailored for active development. It skips minification for easier debugging, generates source maps for better error tracking, and watches your source files for changes. When a change is detected, it automatically rebuilds the affected files, allowing you to see updates in real-time. The `wp-scripts` package also facilitates the use of JavaScript modules, allowing code distribution across multiple files and resulting in a streamlined bundle after the build process. The [block-development-example](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) GitHub repository provides some good examples. diff --git a/docs/how-to-guides/themes/global-settings-and-styles.md b/docs/how-to-guides/themes/global-settings-and-styles.md index f71bd67bfaf2e..205a3ee862ce6 100644 --- a/docs/how-to-guides/themes/global-settings-and-styles.md +++ b/docs/how-to-guides/themes/global-settings-and-styles.md @@ -1053,16 +1053,16 @@ Pseudo selectors `:hover`, `:focus`, `:visited`, `:active`, `:link`, `:any-link` #### Variations -A block can have a "style variation", as defined per the [block.json specification](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/#styles-optional). Theme authors can define the style attributes for an existing style variation using the theme.json file. Styles for unregistered style variations will be ignored. +A block can have a "style variation," as defined in the [block.json specification](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/#styles-optional). Theme authors can define the style attributes for an existing style variation using the `theme.json` file. Styles for unregistered style variations will be ignored. -Note that variations are a "block concept", they only exist bound to blocks. The `theme.json` specification respects that distinction by only allowing `variations` at the block-level but not at the top-level. It's also worth highlighting that only variations defined in the `block.json` file of the block are considered "registered": so far, the style variations added via `register_block_style` or in the client are ignored, see [this issue](https://github.com/WordPress/gutenberg/issues/49602) for more information. +Note that variations are a "block concept"—they only exist when bound to blocks. The `theme.json` specification respects this distinction by only allowing `variations` at the block level, not the top level. It’s also worth highlighting that only variations defined in the `block.json` file of the block or via `register_block_style` on the server are considered "registered" for `theme.json` styling purposes. For example, this is how to provide styles for the existing `plain` variation for the `core/quote` block: ```json { "version": 3, - "styles":{ + "styles": { "blocks": { "core/quote": { "variations": { @@ -1078,7 +1078,7 @@ For example, this is how to provide styles for the existing `plain` variation fo } ``` -The resulting CSS output is this: +The resulting CSS output is: ```css .wp-block-quote.is-style-plain { @@ -1086,6 +1086,99 @@ The resulting CSS output is this: } ``` +It is also possible for multiple block types to share the same variation styles. There are two recommended ways to define such shared styles: + +1. `theme.json` partial files +2. programmatically, using `register_block_style` + +##### Variation Theme.json Partials + +Like theme style variation partials, those for block style variations reside within a theme's `/styles` directory. However, they are differentiated from theme style variations by the introduction of a top-level property called `blockTypes`. The `blockTypes` property is an array of block types for which the block style variation has been registered. + +Additionally, a `slug` property is available to provide consistency between the different sources that may define block style variations and to decouple the `slug` from the translatable `title` property. + +The following is an example of a `theme.json` partial that defines styles for the "Variation A" block style for the Group, Columns, and Media & Text block types: + +```json +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Variation A", + "slug": "variation-a", + "blockTypes": [ "core/group", "core/columns", "core/media-text" ], + "styles": { + "color": { + "background": "#eed8d3", + "text": "#201819" + }, + "elements": { + "heading": { + "color": { + "text": "#201819" + } + } + }, + "blocks": { + "core/group": { + "color": { + "background": "#825f58", + "text": "#eed8d3" + }, + "elements": { + "heading": { + "color": { + "text": "#eed8d3" + } + } + } + } + } + } +} +``` + +##### Programmatically Registering Variation Styles + +As an alternative to `theme.json` partials, you can register variation styles at the same time as registering the variation itself through `register_block_style`. This is done by registering the block style for an array of block types while also passing a "style object" within the `style_data` option. + +The example below registers a "Green" variation for the Group and Columns blocks. Note that the style object passed via `style_data` follows the same shape as the `styles` property of a `theme.json` partial. + +```php +register_block_style( + array( 'core/group', 'core/columns' ), + array( + 'name' => 'green', + 'label' => __( 'Green' ), + 'style_data' => array( + 'color' => array( + 'background' => '#4f6f52', + 'text' => '#d2e3c8', + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'background' => '#739072', + 'text' => '#e3eedd', + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => '#ead196', + ), + ':hover' => array( + 'color' => array( + 'text' => '#ebd9b4', + ), + ), + ), + ), + ), + ) +); +``` + ### customTemplates
Supported in WordPress from version 5.9.
diff --git a/docs/reference-guides/block-api/block-edit-save.md b/docs/reference-guides/block-api/block-edit-save.md index 86721c77e463c..a50a17b75cb54 100644 --- a/docs/reference-guides/block-api/block-edit-save.md +++ b/docs/reference-guides/block-api/block-edit-save.md @@ -183,9 +183,34 @@ save: ( { attributes } ) => { ``` - When saving your block, you want to save the attributes in the same format specified by the attribute source definition. If no attribute source is specified, the attribute will be saved to the block's comment delimiter. See the [Block Attributes documentation](/docs/reference-guides/block-api/block-attributes.md) for more details. +### innerBlocks + +There is a second property in the props passed to the `save` function, `innerBlocks`. This property is typically used for internal operations, and there are very few scenarios where you would need to use it. + +`innerBlocks`, when initialized, is an array containing object representations of nested blocks. In those rare cases where you might use this property, +it can help you adjust how a block is rendered. For example, you could render a block differently based on the number of nested blocks or if a specific block type is present.. + + +```jsx +save: ( { attributes, innerBlocks } ) => { + const { className, ...rest } = useBlockProps.save(); + + // innerBlocks could also be an object - react element during initialization + const numberOfInnerBlocks = innerBlocks?.length; + if ( numberOfInnerBlocks > 1 ) { + className = className + ( className ? ' ' : '' ) + 'more-than-one'; + }; + const blockProps = { ...rest, className }; + + return
{ attributes.content }
; +}; +``` + + +Here, an additional class is added to the block if number of inner blocks is greater than one, allowing for different styling of the block. + ## Examples Here are a couple examples of using attributes, edit, and save all together. diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 7e031fa525e1f..0715b1e3547e2 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -661,7 +661,7 @@ Contains the block elements used to render a post, like the title, date, feature - **Name:** core/post-template - **Category:** theme - **Ancestor:** core/query -- **Supports:** align (full, wide), color (background, gradients, link, text), interactivity (clientNavigation), layout, spacing (blockGap), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ +- **Supports:** align (full, wide), color (background, gradients, link, text), interactivity (clientNavigation), layout, spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ ## Post Terms diff --git a/gutenberg.php b/gutenberg.php index 29cd0f63b4077..f736359a8b357 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.6 * Requires PHP: 7.2 - * Version: 19.9.0 + * Version: 20.0.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/jsconfig.json b/jsconfig.json deleted file mode 100644 index 204c9955c3cff..0000000000000 --- a/jsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@wordpress/*": [ "./*", "./packages/*/src" ] - } - }, - "exclude": [ - "build", - "build-module", - "node_modules", - "packages/e2e-tests/plugins", - "vendor" - ] -} diff --git a/lib/block-supports/block-style-variations.php b/lib/block-supports/block-style-variations.php index 3942fed24b98a..5f7d02007ed39 100644 --- a/lib/block-supports/block-style-variations.php +++ b/lib/block-supports/block-style-variations.php @@ -211,10 +211,10 @@ function gutenberg_render_block_style_variation_support_styles( $parsed_block ) * block attributes in the `render_block_data` filter gets applied to the * block's markup. * - * @see gutenberg_render_block_style_variation_support_styles - * * @since 6.6.0 * + * @see gutenberg_render_block_style_variation_support_styles + * * @param string $block_content Rendered block content. * @param array $block Block object. * @@ -273,7 +273,7 @@ function gutenberg_enqueue_block_style_variation_styles() { } // Add Gutenberg filters and action. -add_filter( 'render_block_data', 'gutenberg_render_block_style_variation_support_styles', 10, 2 ); +add_filter( 'render_block_data', 'gutenberg_render_block_style_variation_support_styles' ); add_filter( 'render_block', 'gutenberg_render_block_style_variation_class_name', 10, 2 ); add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_block_style_variation_styles', 1 ); diff --git a/lib/block-supports/border.php b/lib/block-supports/border.php index f890ed84566b7..bd4c772675a5e 100644 --- a/lib/block-supports/border.php +++ b/lib/block-supports/border.php @@ -17,7 +17,7 @@ function gutenberg_register_border_support( $block_type ) { $block_type->attributes = array(); } - if ( block_has_support( $block_type, array( 'border' ) ) && ! array_key_exists( 'style', $block_type->attributes ) ) { + if ( block_has_support( $block_type, array( '__experimentalBorder' ) ) && ! array_key_exists( 'style', $block_type->attributes ) ) { $block_type->attributes['style'] = array( 'type' => 'object', ); @@ -52,7 +52,7 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { if ( gutenberg_has_border_feature_support( $block_type, 'radius' ) && isset( $block_attributes['style']['border']['radius'] ) && - ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'radius' ) + ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'radius' ) ) { $border_radius = $block_attributes['style']['border']['radius']; @@ -67,7 +67,7 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { if ( gutenberg_has_border_feature_support( $block_type, 'style' ) && isset( $block_attributes['style']['border']['style'] ) && - ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'style' ) + ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'style' ) ) { $border_block_styles['style'] = $block_attributes['style']['border']['style']; } @@ -76,7 +76,7 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { if ( $has_border_width_support && isset( $block_attributes['style']['border']['width'] ) && - ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'width' ) + ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'width' ) ) { $border_width = $block_attributes['style']['border']['width']; @@ -91,7 +91,7 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { // Border color. if ( $has_border_color_support && - ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'color' ) + ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'color' ) ) { $preset_border_color = array_key_exists( 'borderColor', $block_attributes ) ? "var:preset|color|{$block_attributes['borderColor']}" : null; $custom_border_color = $block_attributes['style']['border']['color'] ?? null; @@ -103,9 +103,9 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { foreach ( array( 'top', 'right', 'bottom', 'left' ) as $side ) { $border = $block_attributes['style']['border'][ $side ] ?? null; $border_side_values = array( - 'width' => isset( $border['width'] ) && ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'width' ) ? $border['width'] : null, - 'color' => isset( $border['color'] ) && ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'color' ) ? $border['color'] : null, - 'style' => isset( $border['style'] ) && ! wp_should_skip_block_supports_serialization( $block_type, 'border', 'style' ) ? $border['style'] : null, + 'width' => isset( $border['width'] ) && ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'width' ) ? $border['width'] : null, + 'color' => isset( $border['color'] ) && ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'color' ) ? $border['color'] : null, + 'style' => isset( $border['style'] ) && ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'style' ) ? $border['style'] : null, ); $border_block_styles[ $side ] = $border_side_values; } @@ -129,9 +129,9 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { /** * Checks whether the current block type supports the border feature requested. * - * If the `border` support flag is a boolean `true` all border + * If the `__experimentalBorder` support flag is a boolean `true` all border * support features are available. Otherwise, the specific feature's support - * flag nested under `border` must be enabled for the feature + * flag nested under `experimentalBorder` must be enabled for the feature * to be opted into. * * @param WP_Block_Type $block_type Block type to check for support. @@ -141,17 +141,17 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) { * @return boolean Whether or not the feature is supported. */ function gutenberg_has_border_feature_support( $block_type, $feature, $default_value = false ) { - // Check if all border support features have been opted into via `"border": true`. + // Check if all border support features have been opted into via `"__experimentalBorder": true`. if ( $block_type instanceof WP_Block_Type ) { - $block_type_supports_border = $block_type->supports['border'] ?? $default_value; + $block_type_supports_border = $block_type->supports['__experimentalBorder'] ?? $default_value; if ( true === $block_type_supports_border ) { return true; } } // Check if the specific feature has been opted into individually - // via nested flag under `border`. - return block_has_support( $block_type, array( 'border', $feature ), $default_value ); + // via nested flag under `__experimentalBorder`. + return block_has_support( $block_type, array( '__experimentalBorder', $feature ), $default_value ); } // Register the block support. diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index 35a41270a1980..f3243bc717895 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -255,12 +255,12 @@ function gutenberg_render_elements_class_name( $block_content, $block ) { } // Remove deprecated WordPress core filters. -remove_filter( 'render_block', 'wp_render_elements_support', 10, 2 ); -remove_filter( 'pre_render_block', 'wp_render_elements_support_styles', 10, 2 ); +remove_filter( 'render_block', 'wp_render_elements_support', 10 ); +remove_filter( 'pre_render_block', 'wp_render_elements_support_styles', 10 ); // Remove WordPress core filters to avoid rendering duplicate elements stylesheet & attaching classes twice. -remove_filter( 'render_block', 'wp_render_elements_class_name', 10, 2 ); -remove_filter( 'render_block_data', 'wp_render_elements_support_styles', 10, 1 ); +remove_filter( 'render_block', 'wp_render_elements_class_name', 10 ); +remove_filter( 'render_block_data', 'wp_render_elements_support_styles', 10 ); add_filter( 'render_block', 'gutenberg_render_elements_class_name', 10, 2 ); add_filter( 'render_block_data', 'gutenberg_render_elements_support_styles', 10, 1 ); diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index ddbd1917c3054..7d63074ccb09b 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -1055,8 +1055,8 @@ static function ( $matches ) { } if ( function_exists( 'wp_restore_group_inner_container' ) ) { - remove_filter( 'render_block', 'wp_restore_group_inner_container', 10, 2 ); - remove_filter( 'render_block_core/group', 'wp_restore_group_inner_container', 10, 2 ); + remove_filter( 'render_block', 'wp_restore_group_inner_container', 10 ); + remove_filter( 'render_block_core/group', 'wp_restore_group_inner_container', 10 ); } add_filter( 'render_block_core/group', 'gutenberg_restore_group_inner_container', 10, 2 ); @@ -1118,6 +1118,6 @@ function gutenberg_restore_image_outer_container( $block_content, $block ) { } if ( function_exists( 'wp_restore_image_outer_container' ) ) { - remove_filter( 'render_block_core/image', 'wp_restore_image_outer_container', 10, 2 ); + remove_filter( 'render_block_core/image', 'wp_restore_image_outer_container', 10 ); } add_filter( 'render_block_core/image', 'gutenberg_restore_image_outer_container', 10, 2 ); diff --git a/lib/block-supports/settings.php b/lib/block-supports/settings.php index b175fe778ce1b..0246b5c039c86 100644 --- a/lib/block-supports/settings.php +++ b/lib/block-supports/settings.php @@ -128,7 +128,7 @@ function _gutenberg_add_block_level_preset_styles( $pre_render, $block ) { return null; } // Remove WordPress core filter to avoid rendering duplicate settings style blocks. -remove_filter( 'render_block', '_wp_add_block_level_presets_class', 10, 2 ); -remove_filter( 'pre_render_block', '_wp_add_block_level_preset_styles', 10, 2 ); +remove_filter( 'render_block', '_wp_add_block_level_presets_class', 10 ); +remove_filter( 'pre_render_block', '_wp_add_block_level_preset_styles', 10 ); add_filter( 'render_block', '_gutenberg_add_block_level_presets_class', 10, 2 ); add_filter( 'pre_render_block', '_gutenberg_add_block_level_preset_styles', 10, 2 ); diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index 21086b94f15c1..a4719b7bdd409 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -20,16 +20,16 @@ function gutenberg_register_typography_support( $block_type ) { return; } - $has_font_family_support = $typography_supports['fontFamily'] ?? false; + $has_font_family_support = $typography_supports['__experimentalFontFamily'] ?? false; $has_font_size_support = $typography_supports['fontSize'] ?? false; - $has_font_style_support = $typography_supports['fontStyle'] ?? false; - $has_font_weight_support = $typography_supports['fontWeight'] ?? false; - $has_letter_spacing_support = $typography_supports['letterSpacing'] ?? false; + $has_font_style_support = $typography_supports['__experimentalFontStyle'] ?? false; + $has_font_weight_support = $typography_supports['__experimentalFontWeight'] ?? false; + $has_letter_spacing_support = $typography_supports['__experimentalLetterSpacing'] ?? false; $has_line_height_support = $typography_supports['lineHeight'] ?? false; $has_text_align_support = $typography_supports['textAlign'] ?? false; $has_text_columns_support = $typography_supports['textColumns'] ?? false; - $has_text_decoration_support = $typography_supports['textDecoration'] ?? false; - $has_text_transform_support = $typography_supports['textTransform'] ?? false; + $has_text_decoration_support = $typography_supports['__experimentalTextDecoration'] ?? false; + $has_text_transform_support = $typography_supports['__experimentalTextTransform'] ?? false; $has_writing_mode_support = $typography_supports['__experimentalWritingMode'] ?? false; $has_typography_support = $has_font_family_support @@ -91,16 +91,16 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { return array(); } - $has_font_family_support = $typography_supports['fontFamily'] ?? false; + $has_font_family_support = $typography_supports['__experimentalFontFamily'] ?? false; $has_font_size_support = $typography_supports['fontSize'] ?? false; - $has_font_style_support = $typography_supports['fontStyle'] ?? false; - $has_font_weight_support = $typography_supports['fontWeight'] ?? false; - $has_letter_spacing_support = $typography_supports['letterSpacing'] ?? false; + $has_font_style_support = $typography_supports['__experimentalFontStyle'] ?? false; + $has_font_weight_support = $typography_supports['__experimentalFontWeight'] ?? false; + $has_letter_spacing_support = $typography_supports['__experimentalLetterSpacing'] ?? false; $has_line_height_support = $typography_supports['lineHeight'] ?? false; $has_text_align_support = $typography_supports['textAlign'] ?? false; $has_text_columns_support = $typography_supports['textColumns'] ?? false; - $has_text_decoration_support = $typography_supports['textDecoration'] ?? false; - $has_text_transform_support = $typography_supports['textTransform'] ?? false; + $has_text_decoration_support = $typography_supports['__experimentalTextDecoration'] ?? false; + $has_text_transform_support = $typography_supports['__experimentalTextTransform'] ?? false; $has_writing_mode_support = $typography_supports['__experimentalWritingMode'] ?? false; // Whether to skip individual block support features. diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index 5f3b1bb5cd6b1..cc49c320da650 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -640,7 +640,7 @@ private static function get_global_styles_presets( $sources ) { * * @param string $block_name The block name. * - * @return string The CSS selector or null if there is no support. + * @return ?string The CSS selector or null if there is no support. */ private static function get_selector( $block_name ) { $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); @@ -669,6 +669,8 @@ private static function get_selector( $block_name ) { // Regular filter.duotone support uses filter.duotone selectors with fallbacks. return wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ), true ); } + + return null; } /** diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 778dcdbec78d9..3af123d96bcc5 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -615,10 +615,10 @@ class WP_Theme_JSON_Gutenberg { * @var string[] */ const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = array( - 'border' => 'border', - 'color' => 'color', - 'spacing' => 'spacing', - 'typography' => 'typography', + '__experimentalBorder' => 'border', + 'color' => 'color', + 'spacing' => 'spacing', + 'typography' => 'typography', ); /** @@ -3413,6 +3413,8 @@ protected static function should_override_preset( $theme_json, $path, $override return true; } + + return false; } /** diff --git a/lib/compat/wordpress-6.8/blocks.php b/lib/compat/wordpress-6.8/blocks.php index 8e176e58c8d7f..6cfa98691020e 100644 --- a/lib/compat/wordpress-6.8/blocks.php +++ b/lib/compat/wordpress-6.8/blocks.php @@ -5,151 +5,6 @@ * @package gutenberg */ -/** - * Filters the block type arguments during registration to stabilize - * experimental block supports. - * - * This is a temporary compatibility shim as the approach in core is for this - * to be handled within the WP_Block_Type class rather than requiring a filter. - * - * @param array $args Array of arguments for registering a block type. - * @return array Array of arguments for registering a block type. - */ -function gutenberg_stabilize_experimental_block_supports( $args ) { - if ( empty( $args['supports'] ) ) { - return $args; - } - - $experimental_supports_map = array( '__experimentalBorder' => 'border' ); - $common_experimental_properties = array( - '__experimentalDefaultControls' => 'defaultControls', - '__experimentalSkipSerialization' => 'skipSerialization', - ); - $experimental_support_properties = array( - 'typography' => array( - '__experimentalFontFamily' => 'fontFamily', - '__experimentalFontStyle' => 'fontStyle', - '__experimentalFontWeight' => 'fontWeight', - '__experimentalLetterSpacing' => 'letterSpacing', - '__experimentalTextDecoration' => 'textDecoration', - '__experimentalTextTransform' => 'textTransform', - ), - ); - $done = array(); - - $updated_supports = array(); - foreach ( $args['supports'] as $support => $config ) { - /* - * If this support config has already been stabilized, skip it. - * A stable support key occurring after an experimental key, gets - * stabilized then so that the two configs can be merged effectively. - */ - if ( isset( $done[ $support ] ) ) { - continue; - } - - $stable_support_key = $experimental_supports_map[ $support ] ?? $support; - - /* - * Use the support's config as is when it's not in need of stabilization. - * - * A support does not need stabilization if: - * - The support key doesn't need stabilization AND - * - Either: - * - The config isn't an array, so can't have experimental properties OR - * - The config is an array but has no experimental properties to stabilize. - */ - if ( $support === $stable_support_key && - ( ! is_array( $config ) || - ( ! isset( $experimental_support_properties[ $stable_support_key ] ) && - empty( array_intersect_key( $common_experimental_properties, $config ) ) - ) - ) - ) { - $updated_supports[ $support ] = $config; - continue; - } - - $stabilize_config = function ( $unstable_config, $stable_support_key ) use ( $experimental_support_properties, $common_experimental_properties ) { - if ( ! is_array( $unstable_config ) ) { - return $unstable_config; - } - - $stable_config = array(); - foreach ( $unstable_config as $key => $value ) { - // Get stable key from support-specific map, common properties map, or keep original. - $stable_key = $experimental_support_properties[ $stable_support_key ][ $key ] ?? - $common_experimental_properties[ $key ] ?? - $key; - - $stable_config[ $stable_key ] = $value; - - /* - * The `__experimentalSkipSerialization` key needs to be kept until - * WP 6.8 becomes the minimum supported version. This is due to the - * core `wp_should_skip_block_supports_serialization` function only - * checking for `__experimentalSkipSerialization` in earlier versions. - */ - if ( '__experimentalSkipSerialization' === $key || 'skipSerialization' === $key ) { - $stable_config['__experimentalSkipSerialization'] = $value; - } - } - return $stable_config; - }; - - // Stabilize the config value. - $stable_config = is_array( $config ) ? $stabilize_config( $config, $stable_support_key ) : $config; - - /* - * If a plugin overrides the support config with the `register_block_type_args` - * filter, both experimental and stable configs may be present. In that case, - * use the order keys are defined in to determine the final value. - * - If config is an array, merge the arrays in their order of definition. - * - If config is not an array, use the value defined last. - * - * The reason for preferring the last defined key is that after filters - * are applied, the last inserted key is likely the most up-to-date value. - * We cannot determine with certainty which value was "last modified" so - * the insertion order is the best guess. The extreme edge case of multiple - * filters tweaking the same support property will become less over time as - * extenders migrate existing blocks and plugins to stable keys. - */ - if ( $support !== $stable_support_key && isset( $args['supports'][ $stable_support_key ] ) ) { - $key_positions = array_flip( array_keys( $args['supports'] ) ); - $experimental_first = - ( $key_positions[ $support ] ?? PHP_INT_MAX ) < - ( $key_positions[ $stable_support_key ] ?? PHP_INT_MAX ); - - /* - * To merge the alternative support config effectively, it also needs to be - * stabilized before merging to keep stabilized and experimental flags in - * sync. - */ - $args['supports'][ $stable_support_key ] = $stabilize_config( $args['supports'][ $stable_support_key ], $stable_support_key ); - // Prevents reprocessing this support as it was stabilized above. - $done[ $stable_support_key ] = true; - - if ( is_array( $stable_config ) && is_array( $args['supports'][ $stable_support_key ] ) ) { - $stable_config = $experimental_first - ? array_merge( $stable_config, $args['supports'][ $stable_support_key ] ) - : array_merge( $args['supports'][ $stable_support_key ], $stable_config ); - } else { - $stable_config = $experimental_first - ? $args['supports'][ $stable_support_key ] - : $stable_config; - } - } - - $updated_supports[ $stable_support_key ] = $stable_config; - } - - $args['supports'] = $updated_supports; - - return $args; -} - -add_filter( 'register_block_type_args', 'gutenberg_stabilize_experimental_block_supports', PHP_INT_MAX, 1 ); - function gutenberg_apply_block_hooks_to_post_content( $content ) { // The `the_content` filter does not provide the post that the content is coming from. // However, we can infer it by calling `get_post()`, which will return the current post @@ -170,7 +25,7 @@ function gutenberg_apply_block_hooks_to_post_content( $content ) { * @return WP_REST_Response The response object. */ function gutenberg_insert_hooked_blocks_into_rest_response( $response, $post ) { - if ( empty( $response->data['content']['raw'] ) || empty( $response->data['content']['rendered'] ) ) { + if ( empty( $response->data['content']['raw'] ) ) { return $response; } @@ -185,6 +40,8 @@ function gutenberg_insert_hooked_blocks_into_rest_response( $response, $post ) { if ( 'wp_navigation' === $post->post_type ) { $wrapper_block_type = 'core/navigation'; + } elseif ( 'wp_block' === $post->post_type ) { + $wrapper_block_type = 'core/block'; } else { $wrapper_block_type = 'core/post-content'; } @@ -206,6 +63,11 @@ function gutenberg_insert_hooked_blocks_into_rest_response( $response, $post ) { $response->data['content']['raw'] = $content; + // If the rendered content was previously empty, we leave it like that. + if ( empty( $response->data['content']['rendered'] ) ) { + return $response; + } + // No need to inject hooked blocks twice. $priority = has_filter( 'the_content', 'apply_block_hooks_to_content' ); if ( false !== $priority ) { @@ -224,6 +86,7 @@ function gutenberg_insert_hooked_blocks_into_rest_response( $response, $post ) { } add_filter( 'rest_prepare_page', 'gutenberg_insert_hooked_blocks_into_rest_response', 10, 2 ); add_filter( 'rest_prepare_post', 'gutenberg_insert_hooked_blocks_into_rest_response', 10, 2 ); +add_filter( 'rest_prepare_wp_block', 'gutenberg_insert_hooked_blocks_into_rest_response', 10, 2 ); /** * Updates the wp_postmeta with the list of ignored hooked blocks @@ -272,6 +135,8 @@ function gutenberg_update_ignored_hooked_blocks_postmeta( $post ) { if ( 'wp_navigation' === $post->post_type ) { $wrapper_block_type = 'core/navigation'; + } elseif ( 'wp_block' === $post->post_type ) { + $wrapper_block_type = 'core/block'; } else { $wrapper_block_type = 'core/post-content'; } @@ -311,3 +176,4 @@ function gutenberg_update_ignored_hooked_blocks_postmeta( $post ) { } add_filter( 'rest_pre_insert_page', 'gutenberg_update_ignored_hooked_blocks_postmeta' ); add_filter( 'rest_pre_insert_post', 'gutenberg_update_ignored_hooked_blocks_postmeta' ); +add_filter( 'rest_pre_insert_wp_block', 'gutenberg_update_ignored_hooked_blocks_postmeta' ); diff --git a/lib/compat/wordpress-6.8/site-editor.php b/lib/compat/wordpress-6.8/site-editor.php index 53d04c2e543f4..9b2575676047d 100644 --- a/lib/compat/wordpress-6.8/site-editor.php +++ b/lib/compat/wordpress-6.8/site-editor.php @@ -145,4 +145,4 @@ function gutenberg_add_styles_submenu_item() { } } } -add_action( 'admin_init', 'gutenberg_add_styles_submenu_item' ); +add_action( 'admin_menu', 'gutenberg_add_styles_submenu_item' ); diff --git a/lib/experimental/font-face/bc-layer/webfonts-deprecations.php b/lib/experimental/font-face/bc-layer/webfonts-deprecations.php index 2534d8db16527..fb5e6b315dbda 100644 --- a/lib/experimental/font-face/bc-layer/webfonts-deprecations.php +++ b/lib/experimental/font-face/bc-layer/webfonts-deprecations.php @@ -28,7 +28,7 @@ function wp_webfonts() { global $wp_webfonts; if ( ! ( $wp_webfonts instanceof WP_Webfonts ) ) { - $wp_webfonts = new WP_Webfonts( wp_fonts() ); + $wp_webfonts = new WP_Webfonts(); } return $wp_webfonts; diff --git a/lib/experimental/kses-allowed-html.php b/lib/experimental/kses-allowed-html.php index 122faef7b4ca2..9a4f2e7c614b8 100644 --- a/lib/experimental/kses-allowed-html.php +++ b/lib/experimental/kses-allowed-html.php @@ -40,4 +40,4 @@ function gutenberg_kses_allowed_html( $allowedtags ) { ); return $allowedtags; } -add_filter( 'wp_kses_allowed_html', 'gutenberg_kses_allowed_html', 10, 2 ); +add_filter( 'wp_kses_allowed_html', 'gutenberg_kses_allowed_html' ); diff --git a/lib/experimental/media/load.php b/lib/experimental/media/load.php index bcb02accf62a6..18bfd22d4de00 100644 --- a/lib/experimental/media/load.php +++ b/lib/experimental/media/load.php @@ -247,6 +247,8 @@ function gutenberg_set_up_cross_origin_isolation() { * Uses an output buffer to add crossorigin="anonymous" where needed. * * @link https://web.dev/coop-coep/ + * + * @global bool $is_safari */ function gutenberg_start_cross_origin_isolation_output_buffer(): void { global $is_safari; diff --git a/lib/experimental/posts/load.php b/lib/experimental/posts/load.php index 699534f1886f5..b6dd9d55a8d7d 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', __FILE__ ), + gutenberg_url( 'build/edit-site/posts.css' ), array( 'wp-components', 'wp-commands', 'wp-edit-site' ) ); wp_enqueue_style( 'wp-gutenberg-posts-dashboard' ); diff --git a/lib/rest-api.php b/lib/rest-api.php index 424927acf1f4a..783abc24d3ee3 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -26,7 +26,7 @@ function gutenberg_override_global_styles_endpoint( array $args ): array { return $args; } -add_filter( 'register_wp_global_styles_post_type_args', 'gutenberg_override_global_styles_endpoint', 10, 2 ); +add_filter( 'register_wp_global_styles_post_type_args', 'gutenberg_override_global_styles_endpoint' ); /** * Registers the Edit Site Export REST API routes. diff --git a/lib/theme-i18n.json b/lib/theme-i18n.json index e4d14502132cb..1b7a8d0d31190 100644 --- a/lib/theme-i18n.json +++ b/lib/theme-i18n.json @@ -45,6 +45,13 @@ } ] }, + "shadow": { + "presets": [ + { + "name": "Shadow name" + } + ] + }, "blocks": { "*": { "typography": { @@ -69,6 +76,18 @@ { "name": "Gradient name" } + ], + "duotone": [ + { + "name": "Duotone name" + } + ] + }, + "dimensions": { + "aspectRatios": [ + { + "name": "Aspect ratio name" + } ] }, "spacing": { diff --git a/package-lock.json b/package-lock.json index 813dea6f7b367..2b2a7e2e7abcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "19.9.0", + "version": "20.0.0-rc.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "19.9.0", + "version": "20.0.0-rc.1", "hasInstallScript": true, "license": "GPL-2.0-or-later", "workspaces": [ @@ -22,9 +22,11 @@ "@babel/runtime-corejs3": "7.25.7", "@babel/traverse": "7.25.7", "@emotion/babel-plugin": "11.11.0", + "@emotion/is-prop-valid": "1.2.2", "@emotion/jest": "11.7.1", "@emotion/native": "11.0.0", "@geometricpanda/storybook-addon-badges": "2.0.5", + "@inquirer/prompts": "7.2.0", "@octokit/rest": "16.26.0", "@octokit/types": "6.34.0", "@octokit/webhooks-types": "5.8.0", @@ -54,6 +56,7 @@ "@types/estree": "1.0.5", "@types/istanbul-lib-report": "3.0.0", "@types/mime": "2.0.3", + "@types/node": "20.17.10", "@types/npm-package-arg": "6.1.1", "@types/prettier": "2.4.4", "@types/qs": "6.9.7", @@ -104,7 +107,6 @@ "filenamify": "4.2.0", "glob": "7.1.2", "husky": "7.0.0", - "inquirer": "7.1.0", "jest": "29.6.2", "jest-environment-jsdom": "^29.6.2", "jest-jasmine2": "29.6.2", @@ -142,8 +144,8 @@ "redux": "5.0.1", "resize-observer-polyfill": "1.5.1", "rimraf": "5.0.10", - "rtlcss": "4.0.0", - "sass": "1.50.1", + "rtlcss": "4.3.0", + "sass": "1.54.0", "sass-loader": "16.0.3", "semver": "7.5.4", "simple-git": "3.24.0", @@ -4467,6 +4469,19 @@ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/is-prop-valid/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, "node_modules/@emotion/jest": { "version": "11.7.1", "resolved": "https://registry.npmjs.org/@emotion/jest/-/jest-11.7.1.tgz", @@ -4618,19 +4633,6 @@ } } }, - "node_modules/@emotion/styled/node_modules/@emotion/is-prop-valid": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", - "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", - "dependencies": { - "@emotion/memoize": "^0.8.1" - } - }, - "node_modules/@emotion/styled/node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" - }, "node_modules/@emotion/unitless": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", @@ -5265,6 +5267,264 @@ "node": ">=6.9.0" } }, + "node_modules/@inquirer/checkbox": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.0.3.tgz", + "integrity": "sha512-CEt9B4e8zFOGtc/LYeQx5m8nfqQeG/4oNNv0PUvXGG0mys+wR/WbJ3B4KfSQ4Fcr3AQfpiuFOi3fVvmPfvNbxw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/figures": "^1.0.8", + "@inquirer/type": "^3.0.1", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.0.tgz", + "integrity": "sha512-osaBbIMEqVFjTX5exoqPXs6PilWQdjaLhGtMDXMXg/yxkHXNq43GlxGyTA35lK2HpzUgDN+Cjh/2AmqCN0QJpw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.1.tgz", + "integrity": "sha512-rmZVXy9iZvO3ZStEe/ayuuwIJ23LSF13aPMlLMTQARX6lGUBDHGV8UB5i9MRrfy0+mZwt5/9bdy8llszSD3NQA==", + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.8", + "@inquirer/type": "^3.0.1", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.0.tgz", + "integrity": "sha512-Z3LeGsD3WlItDqLxTPciZDbGtm0wrz7iJGS/uUxSiQxef33ZrBq7LhsXg30P7xrWz1kZX4iGzxxj5SKZmJ8W+w==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.3.tgz", + "integrity": "sha512-MDszqW4HYBpVMmAoy/FA9laLrgo899UAga0itEjsYrBthKieDZNc0e16gdn7N3cQ0DSf/6zsTBZMuDYDQU4ktg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.8.tgz", + "integrity": "sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.0.tgz", + "integrity": "sha512-16B8A9hY741yGXzd8UJ9R8su/fuuyO2e+idd7oVLYjP23wKJ6ILRIIHcnXe8/6AoYgwRS2zp4PNsW/u/iZ24yg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.3.tgz", + "integrity": "sha512-HA/W4YV+5deKCehIutfGBzNxWH1nhvUC67O4fC9ufSijn72yrYnRmzvC61dwFvlXIG1fQaYWi+cqNE9PaB9n6Q==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.3.tgz", + "integrity": "sha512-3qWjk6hS0iabG9xx0U1plwQLDBc/HA/hWzLFFatADpR6XfE62LqPr9GpFXBkLU0KQUaIXZ996bNG+2yUvocH8w==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.2.0.tgz", + "integrity": "sha512-ZXYZ5oGVrb+hCzcglPeVerJ5SFwennmDOPfXq1WyeZIrPGySLbl4W6GaSsBFvu3WII36AOK5yB8RMIEEkBjf8w==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.0.3", + "@inquirer/confirm": "^5.1.0", + "@inquirer/editor": "^4.2.0", + "@inquirer/expand": "^4.0.3", + "@inquirer/input": "^4.1.0", + "@inquirer/number": "^3.0.3", + "@inquirer/password": "^4.0.3", + "@inquirer/rawlist": "^4.0.3", + "@inquirer/search": "^3.0.3", + "@inquirer/select": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.3.tgz", + "integrity": "sha512-5MhinSzfmOiZlRoPezfbJdfVCZikZs38ja3IOoWe7H1dxL0l3Z2jAUgbBldeyhhOkELdGvPlBfQaNbeLslib1w==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/type": "^3.0.1", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.3.tgz", + "integrity": "sha512-mQTCbdNolTGvGGVCJSI6afDwiSGTV+fMLPEIMDJgIV6L/s3+RYRpxt6t0DYnqMQmemnZ/Zq0vTIRwoHT1RgcTg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/figures": "^1.0.8", + "@inquirer/type": "^3.0.1", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.0.3.tgz", + "integrity": "sha512-OZfKDtDE8+J54JYAFTUGZwvKNfC7W/gFCjDkcsO7HnTH/wljsZo9y/FJquOxMy++DY0+9l9o/MOZ8s5s1j5wmw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.1", + "@inquirer/figures": "^1.0.8", + "@inquirer/type": "^3.0.1", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.1.tgz", + "integrity": "sha512-+ksJMIy92sOAiAccGpcKZUc3bYO07cADnscIxHBknEm3uNts3movSmBofc1908BNy5edKscxYeAdaX1NXkHS6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -11582,6 +11842,16 @@ } } }, + "node_modules/@storybook/builder-webpack5/node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, "node_modules/@storybook/builder-webpack5/node_modules/css-loader": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", @@ -11645,6 +11915,13 @@ "webpack": "^5.0.0" } }, + "node_modules/@storybook/builder-webpack5/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "node_modules/@storybook/builder-webpack5/node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -11719,6 +11996,23 @@ "storybook": "^8.4.7" } }, + "node_modules/@storybook/core-webpack/node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@storybook/core-webpack/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "node_modules/@storybook/core/node_modules/recast": { "version": "0.23.9", "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.9.tgz", @@ -11931,6 +12225,16 @@ } } }, + "node_modules/@storybook/preset-react-webpack/node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, "node_modules/@storybook/preset-react-webpack/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -12009,6 +12313,13 @@ "node": ">=6" } }, + "node_modules/@storybook/preset-react-webpack/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "node_modules/@storybook/preview-api": { "version": "8.4.7", "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.4.7.tgz", @@ -12245,6 +12556,23 @@ } } }, + "node_modules/@storybook/react-webpack5/node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@storybook/react-webpack5/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "node_modules/@storybook/source-loader": { "version": "8.4.7", "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-8.4.7.tgz", @@ -13590,11 +13918,12 @@ } }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "20.17.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz", + "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==", + "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~6.19.2" } }, "node_modules/@types/node-forge": { @@ -14943,23 +15272,6 @@ "node": "^16.13 || >=18" } }, - "node_modules/@wdio/repl/node_modules/@types/node": { - "version": "20.17.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz", - "integrity": "sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@wdio/repl/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" - }, "node_modules/@wdio/types": { "version": "8.16.12", "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.16.12.tgz", @@ -14973,23 +15285,6 @@ "node": "^16.13 || >=18" } }, - "node_modules/@wdio/types/node_modules/@types/node": { - "version": "20.17.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz", - "integrity": "sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@wdio/types/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" - }, "node_modules/@wdio/utils": { "version": "8.16.17", "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.16.17.tgz", @@ -17255,43 +17550,6 @@ "integrity": "sha512-LEeSAWeh2Gfa2FtlQE1shxQ8zi5F9GHarrGKz08TMdODD5T4eH6BMsvtnhbWZ+XQn+Gb6om/917ucvRu7l7ukw==", "dev": true }, - "node_modules/autoprefixer": { - "version": "10.4.14", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", - "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - } - ], - "dependencies": { - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001464", - "fraction.js": "^4.2.0", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/autoprefixer/node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, "node_modules/autosize": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/autosize/-/autosize-4.0.2.tgz", @@ -17750,27 +18008,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/babel-runtime": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz", - "integrity": "sha512-zeCYxDePWYAT/DfmQWIHsMSFW2vv45UIwIAMjGvQVsTd47RwsiRH0uK1yzyWZ7LDBKdhnGDPM6NYEO5CZyhPrg==", - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.10.0" - } - }, - "node_modules/babel-runtime/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==" - }, "node_modules/bail": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz", @@ -19349,9 +19586,13 @@ } }, "node_modules/cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } }, "node_modules/client-zip": { "version": "2.4.5", @@ -24408,9 +24649,10 @@ } }, "node_modules/external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "license": "MIT", "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -25402,6 +25644,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, "engines": { "node": "*" }, @@ -27503,145 +27746,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/inquirer/node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/inquirer/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inquirer/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -41124,14 +41228,14 @@ "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==" }, "node_modules/rtlcss": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.0.0.tgz", - "integrity": "sha512-j6oypPP+mgFwDXL1JkLCtm6U/DQntMUqlv5SOhpgHhdIE+PmBcjrtAHIpXfbIup47kD5Sgja9JDsDF1NNOsBwQ==", - "dev": true, + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", + "license": "MIT", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0", - "postcss": "^8.4.6", + "postcss": "^8.4.21", "strip-json-comments": "^3.1.1" }, "bin": { @@ -41141,96 +41245,10 @@ "node": ">=12.0.0" } }, - "node_modules/rtlcss-webpack-plugin": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/rtlcss-webpack-plugin/-/rtlcss-webpack-plugin-4.0.7.tgz", - "integrity": "sha512-ouSbJtgcLBBQIsMgarxsDnfgRqm/AS4BKls/mz/Xb6HSl+PdEzefTR+Wz5uWQx4odoX0g261Z7yb3QBz0MTm0g==", - "dependencies": { - "babel-runtime": "~6.25.0", - "rtlcss": "^3.5.0" - } - }, - "node_modules/rtlcss-webpack-plugin/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rtlcss-webpack-plugin/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rtlcss-webpack-plugin/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rtlcss-webpack-plugin/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/rtlcss-webpack-plugin/node_modules/rtlcss": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-3.5.0.tgz", - "integrity": "sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A==", - "dependencies": { - "find-up": "^5.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.3.11", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "rtlcss": "bin/rtlcss.js" - } - }, - "node_modules/rtlcss-webpack-plugin/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/rtlcss/node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -41349,6 +41367,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, "engines": { "node": ">=0.12.0" } @@ -41427,6 +41446,7 @@ "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, "dependencies": { "tslib": "^1.9.0" }, @@ -41437,7 +41457,8 @@ "node_modules/rxjs/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "node_modules/sade": { "version": "1.8.1", @@ -41531,9 +41552,9 @@ } }, "node_modules/sass": { - "version": "1.50.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.50.1.tgz", - "integrity": "sha512-noTnY41KnlW2A9P8sdwESpDmo+KBNkukI1i8+hOK3footBUcohNHtdOJbckp46XO95nuvcHDDZ+4tmOnpK3hjw==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.0.tgz", + "integrity": "sha512-C4zp79GCXZfK0yoHZg+GxF818/aclhp9F48XBu/+bm9vXEVAYov9iU3FBVRMq3Hx3OA4jfKL+p2K9180mEh0xQ==", "license": "MIT", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -45316,9 +45337,10 @@ } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" }, "node_modules/unherit": { "version": "1.1.1", @@ -46470,16 +46492,6 @@ "node": ">=14.16" } }, - "node_modules/webdriver/node_modules/@types/node": { - "version": "20.17.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz", - "integrity": "sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, "node_modules/webdriver/node_modules/cacheable-lookup": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", @@ -46640,13 +46652,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/webdriver/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" - }, "node_modules/webdriver/node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -46743,16 +46748,6 @@ } } }, - "node_modules/webdriverio/node_modules/@types/node": { - "version": "20.17.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz", - "integrity": "sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, "node_modules/webdriverio/node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", @@ -47000,13 +46995,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/webdriverio/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" - }, "node_modules/webdriverio/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -48655,6 +48643,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zip-stream": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.2.tgz", @@ -48708,12 +48708,12 @@ }, "packages/a11y": { "name": "@wordpress/a11y", - "version": "4.14.0", + "version": "4.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/dom-ready": "*", - "@wordpress/i18n": "*" + "@wordpress/dom-ready": "file:../dom-ready", + "@wordpress/i18n": "file:../i18n" }, "engines": { "node": ">=18.12.0", @@ -48722,14 +48722,14 @@ }, "packages/annotations": { "name": "@wordpress/annotations", - "version": "3.14.0", + "version": "3.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/data": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/rich-text": "*", + "@wordpress/data": "file:../data", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/rich-text": "file:../rich-text", "uuid": "^9.0.1" }, "engines": { @@ -48750,12 +48750,12 @@ }, "packages/api-fetch": { "name": "@wordpress/api-fetch", - "version": "7.14.0", + "version": "7.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/i18n": "*", - "@wordpress/url": "*" + "@wordpress/i18n": "file:../i18n", + "@wordpress/url": "file:../url" }, "engines": { "node": ">=18.12.0", @@ -48764,7 +48764,7 @@ }, "packages/autop": { "name": "@wordpress/autop", - "version": "4.14.0", + "version": "4.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -48776,7 +48776,7 @@ }, "packages/babel-plugin-import-jsx-pragma": { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "5.14.0", + "version": "5.15.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -48788,7 +48788,7 @@ }, "packages/babel-plugin-makepot": { "name": "@wordpress/babel-plugin-makepot", - "version": "6.14.0", + "version": "6.15.0", "license": "GPL-2.0-or-later", "dependencies": { "deepmerge": "^4.3.0", @@ -48805,7 +48805,7 @@ }, "packages/babel-preset-default": { "name": "@wordpress/babel-preset-default", - "version": "8.14.0", + "version": "8.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/core": "7.25.7", @@ -48814,8 +48814,8 @@ "@babel/preset-env": "7.25.7", "@babel/preset-typescript": "7.25.7", "@babel/runtime": "7.25.7", - "@wordpress/browserslist-config": "*", - "@wordpress/warning": "*", + "@wordpress/browserslist-config": "file:../browserslist-config", + "@wordpress/warning": "file:../warning", "browserslist": "^4.21.10", "core-js": "^3.31.0", "react": "^18.3.0" @@ -49936,7 +49936,7 @@ }, "packages/base-styles": { "name": "@wordpress/base-styles", - "version": "5.14.0", + "version": "5.15.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -49945,7 +49945,7 @@ }, "packages/blob": { "name": "@wordpress/blob", - "version": "4.14.0", + "version": "4.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -49957,28 +49957,28 @@ }, "packages/block-directory": { "name": "@wordpress/block-directory", - "version": "5.14.0", + "version": "5.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/plugins": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/editor": "file:../editor", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/plugins": "file:../plugins", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", "change-case": "^4.1.2", "clsx": "^2.1.1" }, @@ -49993,44 +49993,44 @@ }, "packages/block-editor": { "name": "@wordpress/block-editor", - "version": "14.9.0", + "version": "14.10.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", "@react-spring/web": "^9.4.5", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-serialization-default-parser": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/preferences": "*", - "@wordpress/priority-queue": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/style-engine": "*", - "@wordpress/token-list": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-serialization-default-parser": "file:../block-serialization-default-parser", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/notices": "file:../notices", + "@wordpress/preferences": "file:../preferences", + "@wordpress/priority-queue": "file:../priority-queue", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/style-engine": "file:../style-engine", + "@wordpress/token-list": "file:../token-list", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", @@ -50093,43 +50093,43 @@ }, "packages/block-library": { "name": "@wordpress/block-library", - "version": "9.14.0", + "version": "9.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/autop": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interactivity": "*", - "@wordpress/interactivity-router": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/rich-text": "*", - "@wordpress/server-side-render": "*", - "@wordpress/url": "*", - "@wordpress/viewport": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/autop": "file:../autop", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interactivity": "file:../interactivity", + "@wordpress/interactivity-router": "file:../interactivity-router", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/server-side-render": "file:../server-side-render", + "@wordpress/url": "file:../url", + "@wordpress/viewport": "file:../viewport", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", @@ -50159,7 +50159,7 @@ }, "packages/block-serialization-default-parser": { "name": "@wordpress/block-serialization-default-parser", - "version": "5.14.0", + "version": "5.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -50171,7 +50171,7 @@ }, "packages/block-serialization-spec-parser": { "name": "@wordpress/block-serialization-spec-parser", - "version": "5.14.0", + "version": "5.15.0", "license": "GPL-2.0-or-later", "dependencies": { "pegjs": "^0.10.0", @@ -50184,25 +50184,25 @@ }, "packages/blocks": { "name": "@wordpress/blocks", - "version": "14.3.0", + "version": "14.4.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/autop": "*", - "@wordpress/blob": "*", - "@wordpress/block-serialization-default-parser": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/shortcode": "*", - "@wordpress/warning": "*", + "@wordpress/autop": "file:../autop", + "@wordpress/blob": "file:../blob", + "@wordpress/block-serialization-default-parser": "file:../block-serialization-default-parser", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/shortcode": "file:../shortcode", + "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", "colord": "^2.7.0", "fast-deep-equal": "^3.1.3", @@ -50238,7 +50238,7 @@ }, "packages/browserslist-config": { "name": "@wordpress/browserslist-config", - "version": "6.14.0", + "version": "6.15.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -50247,17 +50247,17 @@ }, "packages/commands": { "name": "@wordpress/commands", - "version": "1.14.0", + "version": "1.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/private-apis": "*", + "@wordpress/components": "file:../components", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/private-apis": "file:../private-apis", "clsx": "^2.1.1", "cmdk": "^1.0.0" }, @@ -50486,7 +50486,7 @@ }, "packages/components": { "name": "@wordpress/components", - "version": "29.0.0", + "version": "29.1.1", "license": "GPL-2.0-or-later", "dependencies": { "@ariakit/react": "^0.4.15", @@ -50501,23 +50501,23 @@ "@types/gradient-parser": "0.1.3", "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.3.1", - "@wordpress/a11y": "*", - "@wordpress/compose": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keycodes": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/warning": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/compose": "file:../compose", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", @@ -50577,18 +50577,18 @@ }, "packages/compose": { "name": "@wordpress/compose", - "version": "7.14.0", + "version": "7.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", "@types/mousetrap": "^1.6.8", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keycodes": "*", - "@wordpress/priority-queue": "*", - "@wordpress/undo-manager": "*", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/priority-queue": "file:../priority-queue", + "@wordpress/undo-manager": "file:../undo-manager", "change-case": "^4.1.2", "clipboard": "^2.0.11", "mousetrap": "^1.6.5", @@ -50614,23 +50614,23 @@ }, "packages/core-commands": { "name": "@wordpress/core-commands", - "version": "1.14.0", + "version": "1.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/block-editor": "*", - "@wordpress/commands": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/private-apis": "*", - "@wordpress/router": "*", - "@wordpress/url": "*" + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/commands": "file:../commands", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/router": "file:../router", + "@wordpress/url": "file:../url" }, "engines": { "node": ">=18.12.0", @@ -50643,26 +50643,26 @@ }, "packages/core-data": { "name": "@wordpress/core-data", - "version": "7.14.0", + "version": "7.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/sync": "*", - "@wordpress/undo-manager": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/sync": "file:../sync", + "@wordpress/undo-manager": "file:../undo-manager", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", "equivalent-key-map": "^0.2.2", "fast-deep-equal": "^3.1.3", @@ -50688,17 +50688,17 @@ }, "packages/create-block": { "name": "@wordpress/create-block", - "version": "4.57.0", + "version": "4.58.1", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/lazy-import": "*", + "@inquirer/prompts": "^7.2.0", + "@wordpress/lazy-import": "file:../lazy-import", "chalk": "^4.0.0", "change-case": "^4.1.2", "check-node-version": "^4.1.0", "commander": "^9.2.0", "execa": "^4.0.2", "fast-glob": "^3.2.7", - "inquirer": "^7.1.0", "make-dir": "^3.0.0", "mustache": "^4.0.0", "npm-package-arg": "^8.1.5", @@ -50715,7 +50715,7 @@ }, "packages/create-block-interactive-template": { "name": "@wordpress/create-block-interactive-template", - "version": "2.14.0", + "version": "2.15.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -50724,7 +50724,7 @@ }, "packages/create-block-tutorial-template": { "name": "@wordpress/create-block-tutorial-template", - "version": "4.14.0", + "version": "4.15.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -50733,30 +50733,30 @@ }, "packages/customize-widgets": { "name": "@wordpress/customize-widgets", - "version": "5.14.0", + "version": "5.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interface": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/media-utils": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/widgets": "*", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/widgets": "file:../widgets", "clsx": "^2.1.1", "fast-deep-equal": "^3.1.3" }, @@ -50771,17 +50771,17 @@ }, "packages/data": { "name": "@wordpress/data", - "version": "10.14.0", + "version": "10.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/compose": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/priority-queue": "*", - "@wordpress/private-apis": "*", - "@wordpress/redux-routine": "*", + "@wordpress/compose": "file:../compose", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/priority-queue": "file:../priority-queue", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/redux-routine": "file:../redux-routine", "deepmerge": "^4.3.0", "equivalent-key-map": "^0.2.2", "is-plain-object": "^5.0.0", @@ -50800,13 +50800,13 @@ }, "packages/data-controls": { "name": "@wordpress/data-controls", - "version": "4.14.0", + "version": "4.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*" + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated" }, "engines": { "node": ">=18.12.0", @@ -50818,20 +50818,20 @@ }, "packages/dataviews": { "name": "@wordpress/dataviews", - "version": "4.10.0", + "version": "4.11.1", "license": "GPL-2.0-or-later", "dependencies": { "@ariakit/react": "^0.4.15", "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/warning": "*", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/warning": "file:../warning", "clsx": "^2.1.1", "remove-accents": "^0.5.0" }, @@ -50845,11 +50845,11 @@ }, "packages/date": { "name": "@wordpress/date", - "version": "5.14.0", + "version": "5.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/deprecated": "*", + "@wordpress/deprecated": "file:../deprecated", "moment": "^2.29.4", "moment-timezone": "^0.5.40" }, @@ -50860,7 +50860,7 @@ }, "packages/dependency-extraction-webpack-plugin": { "name": "@wordpress/dependency-extraction-webpack-plugin", - "version": "6.14.0", + "version": "6.15.0", "license": "GPL-2.0-or-later", "dependencies": { "json2php": "^0.0.7" @@ -50875,11 +50875,11 @@ }, "packages/deprecated": { "name": "@wordpress/deprecated", - "version": "4.14.0", + "version": "4.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/hooks": "*" + "@wordpress/hooks": "file:../hooks" }, "engines": { "node": ">=18.12.0", @@ -50888,7 +50888,7 @@ }, "packages/docgen": { "name": "@wordpress/docgen", - "version": "2.14.0", + "version": "2.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/core": "7.25.7", @@ -50909,11 +50909,11 @@ }, "packages/dom": { "name": "@wordpress/dom", - "version": "4.14.0", + "version": "4.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/deprecated": "*" + "@wordpress/deprecated": "file:../deprecated" }, "engines": { "node": ">=18.12.0", @@ -50922,7 +50922,7 @@ }, "packages/dom-ready": { "name": "@wordpress/dom-ready", - "version": "4.14.0", + "version": "4.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -50934,13 +50934,13 @@ }, "packages/e2e-test-utils": { "name": "@wordpress/e2e-test-utils", - "version": "11.14.0", + "version": "11.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/keycodes": "*", - "@wordpress/url": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/url": "file:../url", "change-case": "^4.1.2", "form-data": "^4.0.0", "node-fetch": "2.7.0" @@ -50956,7 +50956,7 @@ }, "packages/e2e-test-utils-playwright": { "name": "@wordpress/e2e-test-utils-playwright", - "version": "1.14.0", + "version": "1.15.0", "license": "GPL-2.0-or-later", "dependencies": { "change-case": "^4.1.2", @@ -50982,16 +50982,16 @@ }, "packages/e2e-tests": { "name": "@wordpress/e2e-tests", - "version": "8.14.0", + "version": "8.15.1", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/e2e-test-utils": "*", - "@wordpress/interactivity": "*", - "@wordpress/interactivity-router": "*", - "@wordpress/jest-console": "*", - "@wordpress/jest-puppeteer-axe": "*", - "@wordpress/scripts": "*", - "@wordpress/url": "*", + "@wordpress/e2e-test-utils": "file:../e2e-test-utils", + "@wordpress/interactivity": "file:../interactivity", + "@wordpress/interactivity-router": "file:../interactivity-router", + "@wordpress/jest-console": "file:../jest-console", + "@wordpress/jest-puppeteer-axe": "file:../jest-puppeteer-axe", + "@wordpress/scripts": "file:../scripts", + "@wordpress/url": "file:../url", "chalk": "^4.0.0", "expect-puppeteer": "^4.4.0", "filenamify": "^4.2.0", @@ -51020,39 +51020,39 @@ }, "packages/edit-post": { "name": "@wordpress/edit-post", - "version": "8.14.0", + "version": "8.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-commands": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*", - "@wordpress/viewport": "*", - "@wordpress/warning": "*", - "@wordpress/widgets": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-commands": "file:../core-commands", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/editor": "file:../editor", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/notices": "file:../notices", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", + "@wordpress/viewport": "file:../viewport", + "@wordpress/warning": "file:../warning", + "@wordpress/widgets": "file:../widgets", "clsx": "^2.1.1", "memize": "^2.1.0" }, @@ -51067,50 +51067,51 @@ }, "packages/edit-site": { "name": "@wordpress/edit-site", - "version": "6.14.0", + "version": "6.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", "@react-spring/web": "^9.4.5", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-commands": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dataviews": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/fields": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/router": "*", - "@wordpress/style-engine": "*", - "@wordpress/url": "*", - "@wordpress/viewport": "*", - "@wordpress/widgets": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-commands": "file:../core-commands", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dataviews": "file:../dataviews", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/editor": "file:../editor", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/fields": "file:../fields", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/router": "file:../router", + "@wordpress/style-engine": "file:../style-engine", + "@wordpress/url": "file:../url", + "@wordpress/viewport": "file:../viewport", + "@wordpress/widgets": "file:../widgets", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.9.2", @@ -51129,36 +51130,36 @@ }, "packages/edit-widgets": { "name": "@wordpress/edit-widgets", - "version": "6.14.0", + "version": "6.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interface": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/media-utils": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/url": "*", - "@wordpress/widgets": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/url": "file:../url", + "@wordpress/widgets": "file:../widgets", "clsx": "^2.1.1" }, "engines": { @@ -51172,45 +51173,45 @@ }, "packages/editor": { "name": "@wordpress/editor", - "version": "14.14.0", + "version": "14.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dataviews": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/fields": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interface": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/media-utils": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/rich-text": "*", - "@wordpress/server-side-render": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dataviews": "file:../dataviews", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/fields": "file:../fields", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/server-side-render": "file:../server-side-render", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "client-zip": "^2.4.5", "clsx": "^2.1.1", @@ -51234,13 +51235,13 @@ }, "packages/element": { "name": "@wordpress/element", - "version": "6.14.0", + "version": "6.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", "@types/react": "^18.2.79", "@types/react-dom": "^18.2.25", - "@wordpress/escape-html": "*", + "@wordpress/escape-html": "file:../escape-html", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.3.0", @@ -51253,15 +51254,15 @@ }, "packages/env": { "name": "@wordpress/env", - "version": "10.14.0", + "version": "10.15.0", "license": "GPL-2.0-or-later", "dependencies": { + "@inquirer/prompts": "^7.2.0", "chalk": "^4.0.0", "copy-dir": "^1.3.0", "docker-compose": "^0.24.3", "extract-zip": "^1.6.7", "got": "^11.8.5", - "inquirer": "^7.1.0", "js-yaml": "^3.13.1", "ora": "^4.0.2", "rimraf": "^5.0.10", @@ -51302,7 +51303,7 @@ }, "packages/escape-html": { "name": "@wordpress/escape-html", - "version": "3.14.0", + "version": "3.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -51314,14 +51315,14 @@ }, "packages/eslint-plugin": { "name": "@wordpress/eslint-plugin", - "version": "22.0.0", + "version": "22.1.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/eslint-parser": "7.25.7", "@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/parser": "^6.4.1", - "@wordpress/babel-preset-default": "*", - "@wordpress/prettier-config": "*", + "@wordpress/babel-preset-default": "file:../babel-preset-default", + "@wordpress/prettier-config": "file:../prettier-config", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", @@ -51383,33 +51384,33 @@ }, "packages/fields": { "name": "@wordpress/fields", - "version": "0.6.0", + "version": "0.7.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dataviews": "*", - "@wordpress/date": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/media-utils": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/router": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dataviews": "file:../dataviews", + "@wordpress/date": "file:../date", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/router": "file:../router", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", "change-case": "4.1.2", "client-zip": "^2.4.5", "clsx": "2.1.1", @@ -51425,22 +51426,22 @@ }, "packages/format-library": { "name": "@wordpress/format-library", - "version": "5.14.0", + "version": "5.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/block-editor": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/url": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/url": "file:../url" }, "engines": { "node": ">=18.12.0", @@ -51453,7 +51454,7 @@ }, "packages/hooks": { "name": "@wordpress/hooks", - "version": "4.14.0", + "version": "4.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -51465,7 +51466,7 @@ }, "packages/html-entities": { "name": "@wordpress/html-entities", - "version": "4.14.0", + "version": "4.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -51477,11 +51478,11 @@ }, "packages/i18n": { "name": "@wordpress/i18n", - "version": "5.14.0", + "version": "5.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/hooks": "*", + "@wordpress/hooks": "file:../hooks", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "sprintf-js": "^1.1.1", @@ -51497,12 +51498,12 @@ }, "packages/icons": { "name": "@wordpress/icons", - "version": "10.14.0", + "version": "10.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", - "@wordpress/primitives": "*" + "@wordpress/element": "file:../element", + "@wordpress/primitives": "file:../primitives" }, "engines": { "node": ">=18.12.0", @@ -51511,7 +51512,7 @@ }, "packages/interactivity": { "name": "@wordpress/interactivity", - "version": "6.14.0", + "version": "6.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@preact/signals": "^1.3.0", @@ -51524,11 +51525,11 @@ }, "packages/interactivity-router": { "name": "@wordpress/interactivity-router", - "version": "2.14.0", + "version": "2.15.1", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/a11y": "*", - "@wordpress/interactivity": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/interactivity": "file:../interactivity" }, "engines": { "node": ">=18.12.0", @@ -51537,21 +51538,21 @@ }, "packages/interface": { "name": "@wordpress/interface", - "version": "8.3.0", + "version": "9.0.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/viewport": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/viewport": "file:../viewport", "clsx": "^2.1.1" }, "engines": { @@ -51565,7 +51566,7 @@ }, "packages/is-shallow-equal": { "name": "@wordpress/is-shallow-equal", - "version": "5.14.0", + "version": "5.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -51577,7 +51578,7 @@ }, "packages/jest-console": { "name": "@wordpress/jest-console", - "version": "8.14.0", + "version": "8.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -51593,10 +51594,10 @@ }, "packages/jest-preset-default": { "name": "@wordpress/jest-preset-default", - "version": "12.14.0", + "version": "12.15.1", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/jest-console": "*", + "@wordpress/jest-console": "file:../jest-console", "babel-jest": "29.7.0" }, "engines": { @@ -51610,7 +51611,7 @@ }, "packages/jest-puppeteer-axe": { "name": "@wordpress/jest-puppeteer-axe", - "version": "7.14.0", + "version": "7.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@axe-core/puppeteer": "^4.0.0", @@ -51632,13 +51633,13 @@ }, "packages/keyboard-shortcuts": { "name": "@wordpress/keyboard-shortcuts", - "version": "5.14.0", + "version": "5.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/keycodes": "*" + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/keycodes": "file:../keycodes" }, "engines": { "node": ">=18.12.0", @@ -51650,11 +51651,11 @@ }, "packages/keycodes": { "name": "@wordpress/keycodes", - "version": "4.14.0", + "version": "4.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/i18n": "*" + "@wordpress/i18n": "file:../i18n" }, "engines": { "node": ">=18.12.0", @@ -51663,7 +51664,7 @@ }, "packages/lazy-import": { "name": "@wordpress/lazy-import", - "version": "2.14.0", + "version": "2.15.0", "license": "GPL-2.0-or-later", "dependencies": { "execa": "^4.0.2", @@ -51677,16 +51678,16 @@ }, "packages/list-reusable-blocks": { "name": "@wordpress/list-reusable-blocks", - "version": "5.14.0", + "version": "5.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", "change-case": "^4.1.2" }, "engines": { @@ -51700,15 +51701,15 @@ }, "packages/media-utils": { "name": "@wordpress/media-utils", - "version": "5.14.0", + "version": "5.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/private-apis": "*" + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/private-apis": "file:../private-apis" }, "engines": { "node": ">=18.12.0", @@ -51717,12 +51718,12 @@ }, "packages/notices": { "name": "@wordpress/notices", - "version": "5.14.0", + "version": "5.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/data": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/data": "file:../data" }, "engines": { "node": ">=18.12.0", @@ -51734,7 +51735,7 @@ }, "packages/npm-package-json-lint-config": { "name": "@wordpress/npm-package-json-lint-config", - "version": "5.14.0", + "version": "5.15.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -51746,17 +51747,17 @@ }, "packages/nux": { "name": "@wordpress/nux", - "version": "9.14.0", + "version": "9.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*" + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons" }, "engines": { "node": ">=18.12.0", @@ -51769,24 +51770,24 @@ }, "packages/patterns": { "name": "@wordpress/patterns", - "version": "2.14.0", + "version": "2.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url" }, "engines": { "node": ">=18.12.0", @@ -51799,17 +51800,17 @@ }, "packages/plugins": { "name": "@wordpress/plugins", - "version": "7.14.0", + "version": "7.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/icons": "*", - "@wordpress/is-shallow-equal": "*", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/icons": "file:../icons", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "memize": "^2.0.1" }, "engines": { @@ -51823,11 +51824,11 @@ }, "packages/postcss-plugins-preset": { "name": "@wordpress/postcss-plugins-preset", - "version": "5.14.0", + "version": "5.15.1", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/base-styles": "*", - "autoprefixer": "^10.2.5" + "@wordpress/base-styles": "file:../base-styles", + "autoprefixer": "^10.4.20" }, "engines": { "node": ">=18.12.0", @@ -51837,9 +51838,62 @@ "postcss": "^8.0.0" } }, + "packages/postcss-plugins-preset/node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "packages/postcss-plugins-preset/node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "packages/postcss-plugins-preset/node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, "packages/postcss-themes": { "name": "@wordpress/postcss-themes", - "version": "6.14.0", + "version": "6.15.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -51851,19 +51905,19 @@ }, "packages/preferences": { "name": "@wordpress/preferences", - "version": "4.14.0", + "version": "4.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/private-apis": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/private-apis": "file:../private-apis", "clsx": "^2.1.1" }, "engines": { @@ -51877,11 +51931,11 @@ }, "packages/preferences-persistence": { "name": "@wordpress/preferences-persistence", - "version": "2.14.0", + "version": "2.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*" + "@wordpress/api-fetch": "file:../api-fetch" }, "engines": { "node": ">=18.12.0", @@ -51890,7 +51944,7 @@ }, "packages/prettier-config": { "name": "@wordpress/prettier-config", - "version": "4.14.0", + "version": "4.15.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -51902,11 +51956,11 @@ }, "packages/primitives": { "name": "@wordpress/primitives", - "version": "4.14.0", + "version": "4.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", + "@wordpress/element": "file:../element", "clsx": "^2.1.1" }, "engines": { @@ -51919,7 +51973,7 @@ }, "packages/priority-queue": { "name": "@wordpress/priority-queue", - "version": "3.14.0", + "version": "3.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -51932,7 +51986,7 @@ }, "packages/private-apis": { "name": "@wordpress/private-apis", - "version": "1.14.0", + "version": "1.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -51944,7 +51998,7 @@ }, "packages/project-management-automation": { "name": "@wordpress/project-management-automation", - "version": "2.14.0", + "version": "2.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@actions/core": "1.9.1", @@ -51972,12 +52026,12 @@ }, "packages/react-i18n": { "name": "@wordpress/react-i18n", - "version": "4.14.0", + "version": "4.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", - "@wordpress/i18n": "*", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", "utility-types": "^3.10.0" }, "engines": { @@ -51990,8 +52044,8 @@ "version": "1.121.0", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/element": "*", - "@wordpress/keycodes": "*" + "@wordpress/element": "file:../element", + "@wordpress/keycodes": "file:../keycodes" }, "engines": { "node": ">=18.12.0", @@ -52007,7 +52061,7 @@ "version": "1.121.0", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/react-native-aztec": "*" + "@wordpress/react-native-aztec": "file:../react-native-aztec" }, "engines": { "node": ">=18.12.0", @@ -52032,18 +52086,18 @@ "@react-navigation/native": "6.0.14", "@react-navigation/routers": "5.4.9", "@react-navigation/stack": "6.3.5", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/data": "*", - "@wordpress/edit-post": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/react-native-aztec": "*", - "@wordpress/react-native-bridge": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/data": "file:../data", + "@wordpress/edit-post": "file:../edit-post", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/react-native-aztec": "file:../react-native-aztec", + "@wordpress/react-native-bridge": "file:../react-native-bridge", "core-js": "^3.31.0", "fast-average-color": "^9.1.1", "gettext-parser": "^1.3.1", @@ -52128,7 +52182,7 @@ }, "packages/readable-js-assets-webpack-plugin": { "name": "@wordpress/readable-js-assets-webpack-plugin", - "version": "3.14.0", + "version": "3.15.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -52140,7 +52194,7 @@ }, "packages/redux-routine": { "name": "@wordpress/redux-routine", - "version": "5.14.0", + "version": "5.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -52183,21 +52237,21 @@ }, "packages/reusable-blocks": { "name": "@wordpress/reusable-blocks", - "version": "5.14.0", + "version": "5.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*" + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url" }, "engines": { "node": ">=18.12.0", @@ -52210,18 +52264,18 @@ }, "packages/rich-text": { "name": "@wordpress/rich-text", - "version": "7.14.0", + "version": "7.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/i18n": "*", - "@wordpress/keycodes": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/i18n": "file:../i18n", + "@wordpress/keycodes": "file:../keycodes", "memize": "^2.1.0" }, "engines": { @@ -52234,14 +52288,14 @@ }, "packages/router": { "name": "@wordpress/router", - "version": "1.14.0", + "version": "1.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/compose": "*", - "@wordpress/element": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*", + "@wordpress/compose": "file:../compose", + "@wordpress/element": "file:../element", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", "history": "^5.3.0", "route-recognizer": "^0.3.4" }, @@ -52255,22 +52309,22 @@ }, "packages/scripts": { "name": "@wordpress/scripts", - "version": "30.7.0", + "version": "30.8.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/core": "7.25.7", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@svgr/webpack": "^8.0.1", - "@wordpress/babel-preset-default": "*", - "@wordpress/browserslist-config": "*", - "@wordpress/dependency-extraction-webpack-plugin": "*", - "@wordpress/e2e-test-utils-playwright": "*", - "@wordpress/eslint-plugin": "*", - "@wordpress/jest-preset-default": "*", - "@wordpress/npm-package-json-lint-config": "*", - "@wordpress/postcss-plugins-preset": "*", - "@wordpress/prettier-config": "*", - "@wordpress/stylelint-config": "*", + "@wordpress/babel-preset-default": "file:../babel-preset-default", + "@wordpress/browserslist-config": "file:../browserslist-config", + "@wordpress/dependency-extraction-webpack-plugin": "file:../dependency-extraction-webpack-plugin", + "@wordpress/e2e-test-utils-playwright": "file:../e2e-test-utils-playwright", + "@wordpress/eslint-plugin": "file:../eslint-plugin", + "@wordpress/jest-preset-default": "file:../jest-preset-default", + "@wordpress/npm-package-json-lint-config": "file:../npm-package-json-lint-config", + "@wordpress/postcss-plugins-preset": "file:../postcss-plugins-preset", + "@wordpress/prettier-config": "file:../prettier-config", + "@wordpress/stylelint-config": "file:../stylelint-config", "adm-zip": "^0.5.9", "babel-jest": "29.7.0", "babel-loader": "9.2.1", @@ -52306,8 +52360,8 @@ "react-refresh": "^0.14.0", "read-pkg-up": "^7.0.1", "resolve-bin": "^0.4.0", - "rtlcss-webpack-plugin": "^4.0.7", - "sass": "^1.50.1", + "rtlcss": "^4.3.0", + "sass": "^1.54.0", "sass-loader": "^16.0.3", "schema-utils": "^4.2.0", "source-map-loader": "^3.0.0", @@ -52393,19 +52447,19 @@ }, "packages/server-side-render": { "name": "@wordpress/server-side-render", - "version": "5.14.0", + "version": "5.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/url": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/url": "file:../url", "fast-deep-equal": "^3.1.3" }, "engines": { @@ -52419,7 +52473,7 @@ }, "packages/shortcode": { "name": "@wordpress/shortcode", - "version": "4.14.0", + "version": "4.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -52432,7 +52486,7 @@ }, "packages/style-engine": { "name": "@wordpress/style-engine", - "version": "2.14.0", + "version": "2.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -52445,7 +52499,7 @@ }, "packages/stylelint-config": { "name": "@wordpress/stylelint-config", - "version": "23.6.0", + "version": "23.7.0", "license": "MIT", "dependencies": { "@stylistic/stylelint-plugin": "^3.0.1", @@ -52556,12 +52610,12 @@ }, "packages/sync": { "name": "@wordpress/sync", - "version": "1.14.0", + "version": "1.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", "@types/simple-peer": "^9.11.5", - "@wordpress/url": "*", + "@wordpress/url": "file:../url", "import-locals": "^2.0.0", "lib0": "^0.2.42", "simple-peer": "^9.11.0", @@ -52577,7 +52631,7 @@ }, "packages/token-list": { "name": "@wordpress/token-list", - "version": "3.14.0", + "version": "3.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" @@ -52589,11 +52643,11 @@ }, "packages/undo-manager": { "name": "@wordpress/undo-manager", - "version": "1.14.0", + "version": "1.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/is-shallow-equal": "*" + "@wordpress/is-shallow-equal": "file:../is-shallow-equal" }, "engines": { "node": ">=18.12.0", @@ -52624,7 +52678,7 @@ }, "packages/url": { "name": "@wordpress/url", - "version": "4.14.0", + "version": "4.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", @@ -52637,13 +52691,13 @@ }, "packages/viewport": { "name": "@wordpress/viewport", - "version": "6.14.0", + "version": "6.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/element": "*" + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element" }, "engines": { "node": ">=18.12.0", @@ -52667,7 +52721,7 @@ }, "packages/warning": { "name": "@wordpress/warning", - "version": "3.14.0", + "version": "3.15.0", "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", @@ -52676,21 +52730,21 @@ }, "packages/widgets": { "name": "@wordpress/widgets", - "version": "4.14.0", + "version": "4.15.1", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", "clsx": "^2.1.1" }, "engines": { @@ -52704,7 +52758,7 @@ }, "packages/wordcount": { "name": "@wordpress/wordcount", - "version": "4.14.0", + "version": "4.15.0", "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "7.25.7" diff --git a/package.json b/package.json index 52402a266c7c7..58a99ada7308a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "19.9.0", + "version": "20.0.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", @@ -31,9 +31,11 @@ "@babel/runtime-corejs3": "7.25.7", "@babel/traverse": "7.25.7", "@emotion/babel-plugin": "11.11.0", + "@emotion/is-prop-valid": "1.2.2", "@emotion/jest": "11.7.1", "@emotion/native": "11.0.0", "@geometricpanda/storybook-addon-badges": "2.0.5", + "@inquirer/prompts": "7.2.0", "@octokit/rest": "16.26.0", "@octokit/types": "6.34.0", "@octokit/webhooks-types": "5.8.0", @@ -63,6 +65,7 @@ "@types/estree": "1.0.5", "@types/istanbul-lib-report": "3.0.0", "@types/mime": "2.0.3", + "@types/node": "20.17.10", "@types/npm-package-arg": "6.1.1", "@types/prettier": "2.4.4", "@types/qs": "6.9.7", @@ -113,7 +116,6 @@ "filenamify": "4.2.0", "glob": "7.1.2", "husky": "7.0.0", - "inquirer": "7.1.0", "jest": "29.6.2", "jest-environment-jsdom": "^29.6.2", "jest-jasmine2": "29.6.2", @@ -151,8 +153,8 @@ "redux": "5.0.1", "resize-observer-polyfill": "1.5.1", "rimraf": "5.0.10", - "rtlcss": "4.0.0", - "sass": "1.50.1", + "rtlcss": "4.3.0", + "sass": "1.54.0", "sass-loader": "16.0.3", "semver": "7.5.4", "simple-git": "3.24.0", diff --git a/packages/a11y/CHANGELOG.md b/packages/a11y/CHANGELOG.md index d61f28833d3f6..5c8241a12f8b4 100644 --- a/packages/a11y/CHANGELOG.md +++ b/packages/a11y/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/a11y/package.json b/packages/a11y/package.json index 6ad4f8acb9bd8..dc2a9db468dc8 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/a11y", - "version": "4.14.0", + "version": "4.15.1", "description": "Accessibility (a11y) utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -32,8 +32,8 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/dom-ready": "*", - "@wordpress/i18n": "*" + "@wordpress/dom-ready": "file:../dom-ready", + "@wordpress/i18n": "file:../i18n" }, "publishConfig": { "access": "public" diff --git a/packages/a11y/tsconfig.json b/packages/a11y/tsconfig.json index 093c2775f96d6..13229eadde8f2 100644 --- a/packages/a11y/tsconfig.json +++ b/packages/a11y/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../dom-ready" }, { "path": "../i18n" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../dom-ready" }, { "path": "../i18n" } ] } diff --git a/packages/annotations/CHANGELOG.md b/packages/annotations/CHANGELOG.md index 66433930b6375..2db47776ea25d 100644 --- a/packages/annotations/CHANGELOG.md +++ b/packages/annotations/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.15.0 (2025-01-02) + ## 3.14.0 (2024-12-11) ## 3.13.0 (2024-11-27) diff --git a/packages/annotations/package.json b/packages/annotations/package.json index e10ece8600e81..f48dc22ff9579 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "3.14.0", + "version": "3.15.1", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,10 +28,10 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/data": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/rich-text": "*", + "@wordpress/data": "file:../data", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/rich-text": "file:../rich-text", "uuid": "^9.0.1" }, "peerDependencies": { diff --git a/packages/api-fetch/CHANGELOG.md b/packages/api-fetch/CHANGELOG.md index d1d481d6ff9fc..c03af1b66cb83 100644 --- a/packages/api-fetch/CHANGELOG.md +++ b/packages/api-fetch/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 7.15.0 (2025-01-02) + ## 7.14.0 (2024-12-11) ## 7.13.0 (2024-11-27) diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index fd2430dfc7760..6e1a81f1f9688 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/api-fetch", - "version": "7.14.0", + "version": "7.15.1", "description": "Utility to make WordPress REST API requests.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,8 +30,8 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/i18n": "*", - "@wordpress/url": "*" + "@wordpress/i18n": "file:../i18n", + "@wordpress/url": "file:../url" }, "publishConfig": { "access": "public" diff --git a/packages/api-fetch/tsconfig.json b/packages/api-fetch/tsconfig.json index f9d517286a102..635fe4a8c0d35 100644 --- a/packages/api-fetch/tsconfig.json +++ b/packages/api-fetch/tsconfig.json @@ -1,11 +1,6 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, "references": [ { "path": "../i18n" }, { "path": "../url" } ], - "include": [ "src/**/*" ], - "exclude": [ "**/test/**/*" ] + "exclude": [ "**/test" ] } diff --git a/packages/autop/CHANGELOG.md b/packages/autop/CHANGELOG.md index fc054bb8c14e0..7dc60247ccfa6 100644 --- a/packages/autop/CHANGELOG.md +++ b/packages/autop/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/autop/package.json b/packages/autop/package.json index cdffb6175b31e..f696f0f178735 100644 --- a/packages/autop/package.json +++ b/packages/autop/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/autop", - "version": "4.14.0", + "version": "4.15.0", "description": "WordPress's automatic paragraph functions `autop` and `removep`.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/autop/tsconfig.json b/packages/autop/tsconfig.json index a09ec7466c435..f68a855bab79c 100644 --- a/packages/autop/tsconfig.json +++ b/packages/autop/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../dom-ready" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../dom-ready" } ] } diff --git a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md index d372ad314b1b6..7952463060d69 100644 --- a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md +++ b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index d7ebed0e46f28..44d0649a6e66d 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "5.14.0", + "version": "5.15.0", "description": "Babel transform plugin for automatically injecting an import to be used as the pragma for the React JSX Transform plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-makepot/CHANGELOG.md b/packages/babel-plugin-makepot/CHANGELOG.md index 7f608c6704635..4520b626df51c 100644 --- a/packages/babel-plugin-makepot/CHANGELOG.md +++ b/packages/babel-plugin-makepot/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.15.0 (2025-01-02) + ## 6.14.0 (2024-12-11) ## 6.13.0 (2024-11-27) diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index 352da89d84b37..e5bd79453a1f5 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-makepot", - "version": "6.14.0", + "version": "6.15.0", "description": "WordPress Babel internationalization (i18n) plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index 1401fc5d1452b..3e5e3b667f38b 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 8.15.0 (2025-01-02) + ## 8.14.0 (2024-12-11) ## 8.13.0 (2024-11-27) diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index b983a198f42f9..48046c00bfb3a 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-preset-default", - "version": "8.14.0", + "version": "8.15.1", "description": "Default Babel preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -38,8 +38,8 @@ "@babel/preset-env": "7.25.7", "@babel/preset-typescript": "7.25.7", "@babel/runtime": "7.25.7", - "@wordpress/browserslist-config": "*", - "@wordpress/warning": "*", + "@wordpress/browserslist-config": "file:../browserslist-config", + "@wordpress/warning": "file:../warning", "browserslist": "^4.21.10", "core-js": "^3.31.0", "react": "^18.3.0" diff --git a/packages/base-styles/CHANGELOG.md b/packages/base-styles/CHANGELOG.md index ccdb7976cd0c2..1331b656810ff 100644 --- a/packages/base-styles/CHANGELOG.md +++ b/packages/base-styles/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/base-styles/_animations.scss b/packages/base-styles/_animations.scss index e5bbf86375735..728f702ba1630 100644 --- a/packages/base-styles/_animations.scss +++ b/packages/base-styles/_animations.scss @@ -14,10 +14,10 @@ } } - - animation: __wp-base-styles-fade-in $speed $easing $delay; - animation-fill-mode: forwards; - @include reduce-motion("animation"); + @media not (prefers-reduced-motion) { + animation: __wp-base-styles-fade-in $speed $easing $delay; + animation-fill-mode: forwards; + } } @mixin animation__fade-out($speed: 0.08s, $delay: 0s, $easing: linear) { @@ -30,10 +30,10 @@ } } - - animation: __wp-base-styles-fade-out $speed $easing $delay; - animation-fill-mode: forwards; - @include reduce-motion("animation"); + @media not (prefers-reduced-motion) { + animation: __wp-base-styles-fade-out $speed $easing $delay; + animation-fill-mode: forwards; + } } // Deprecated diff --git a/packages/base-styles/_mixins.scss b/packages/base-styles/_mixins.scss index e2f953e578781..9f089b8d9e832 100644 --- a/packages/base-styles/_mixins.scss +++ b/packages/base-styles/_mixins.scss @@ -141,10 +141,13 @@ // Tabs, Inputs, Square buttons. @mixin input-style__neutral() { box-shadow: 0 0 0 transparent; - transition: box-shadow 0.1s linear; + + @media not (prefers-reduced-motion) { + transition: box-shadow 0.1s linear; + } + border-radius: $radius-small; border: $border-width solid $gray-600; - @include reduce-motion("transition"); } diff --git a/packages/base-styles/package.json b/packages/base-styles/package.json index 0677b61ca0bfd..6866965ec5657 100644 --- a/packages/base-styles/package.json +++ b/packages/base-styles/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/base-styles", - "version": "5.14.0", + "version": "5.15.0", "description": "Base SCSS utilities and variables for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blob/CHANGELOG.md b/packages/blob/CHANGELOG.md index 03c4724426eb6..a0082c8ea8858 100644 --- a/packages/blob/CHANGELOG.md +++ b/packages/blob/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/blob/package.json b/packages/blob/package.json index b69a5c2a5d913..42ac3b59e6cf8 100644 --- a/packages/blob/package.json +++ b/packages/blob/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blob", - "version": "4.14.0", + "version": "4.15.0", "description": "Blob utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blob/tsconfig.json b/packages/blob/tsconfig.json index 6e33d8ff82d47..7ff060ab6ce10 100644 --- a/packages/blob/tsconfig.json +++ b/packages/blob/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/block-directory/CHANGELOG.md b/packages/block-directory/CHANGELOG.md index eb6b832b407e1..f6f37d48607da 100644 --- a/packages/block-directory/CHANGELOG.md +++ b/packages/block-directory/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index fc1176b98fa3a..89cf16cacd849 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-directory", - "version": "5.14.0", + "version": "5.15.1", "description": "Extend editor with block directory features to search, download and install blocks.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,24 +28,24 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/plugins": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/editor": "file:../editor", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/plugins": "file:../plugins", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", "change-case": "^4.1.2", "clsx": "^2.1.1" }, diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 06e1c9a4a746e..82598709e775a 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 14.10.0 (2025-01-02) + ## 14.9.0 (2024-12-11) ## 14.8.0 (2024-11-27) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 13dffce114f59..8fe2c5f1179dc 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -713,10 +713,50 @@ Undocumented declaration. ### PlainText +Render an auto-growing textarea allow users to fill any textual content. + _Related_ - +_Usage_ + +```jsx +import { registerBlockType } from '@wordpress/blocks'; +import { PlainText } from '@wordpress/block-editor'; + +registerBlockType( 'my-plugin/example-block', { + // ... + + attributes: { + content: { + type: 'string', + }, + }, + + edit( { className, attributes, setAttributes } ) { + return ( + setAttributes( { content } ) } + /> + ); + }, +} ); +``` + +_Parameters_ + +- _props_ `Object`: Component props. +- _props.value_ `string`: String value of the textarea. +- _props.onChange_ `Function`: Function called when the text value changes. +- _props.ref_ `[Object]`: The component forwards the `ref` property to the `TextareaAutosize` component. + +_Returns_ + +- `Element`: Plain text component + ### privateApis Private @wordpress/block-editor APIs. diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index c5e82b5924585..da1687b28d25b 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-editor", - "version": "14.9.0", + "version": "14.10.1", "description": "Generic block editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -37,37 +37,37 @@ "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", "@react-spring/web": "^9.4.5", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-serialization-default-parser": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/preferences": "*", - "@wordpress/priority-queue": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/style-engine": "*", - "@wordpress/token-list": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-serialization-default-parser": "file:../block-serialization-default-parser", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/notices": "file:../notices", + "@wordpress/preferences": "file:../preferences", + "@wordpress/priority-queue": "file:../priority-queue", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/style-engine": "file:../style-engine", + "@wordpress/token-list": "file:../token-list", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", diff --git a/packages/block-editor/src/components/background-image-control/index.js b/packages/block-editor/src/components/background-image-control/index.js index 2703aa3988d64..6c703ad2eadb4 100644 --- a/packages/block-editor/src/components/background-image-control/index.js +++ b/packages/block-editor/src/components/background-image-control/index.js @@ -24,6 +24,7 @@ import { Placeholder, Spinner, __experimentalDropdownContentWrapper as DropdownContentWrapper, + Button, } from '@wordpress/components'; import { __, _x, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; @@ -378,6 +379,9 @@ function BackgroundImageControls( { /> } variant="secondary" + renderToggle={ ( props ) => ( + <Button { ...props } __next40pxDefaultSize /> + ) } onError={ onUploadError } onReset={ () => { closeAndFocus(); diff --git a/packages/block-editor/src/components/background-image-control/style.scss b/packages/block-editor/src/components/background-image-control/style.scss index cde8044c24c12..b9c94916039c4 100644 --- a/packages/block-editor/src/components/background-image-control/style.scss +++ b/packages/block-editor/src/components/background-image-control/style.scss @@ -23,7 +23,10 @@ .components-dropdown { display: block; - height: 36px; + + .block-editor-global-styles-background-panel__dropdown-toggle { + height: 40px; + } } } @@ -44,7 +47,6 @@ .components-dropdown { display: block; - height: 36px; } button.components-button { diff --git a/packages/block-editor/src/components/block-alignment-matrix-control/README.md b/packages/block-editor/src/components/block-alignment-matrix-control/README.md index dfb38e1596412..b4267d68fe1fd 100644 --- a/packages/block-editor/src/components/block-alignment-matrix-control/README.md +++ b/packages/block-editor/src/components/block-alignment-matrix-control/README.md @@ -41,13 +41,36 @@ const controls = ( /> </BlockControls> </> -} +); ``` ### Props -| Name | Type | Default | Description | -| ---------- | ---------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `label` | `string` | `Change matrix alignment` | concise description of tool's functionality. | -| `onChange` | `function` | `noop` | the function to execute upon a user's change of the matrix state | -| `value` | `string` | `center` | describes the content alignment location and can be `top`, `right`, `bottom`, `left`, `topRight`, `bottomRight`, `bottomLeft`, `topLeft` | +### `label` + +- **Type:** `string` +- **Default:** `'Change matrix alignment'` + +Label for the control. + +### `onChange` + +- **Type:** `Function` +- **Default:** `noop` + +Function to execute upon a user's change of the matrix state. + +### `value` + +- **Type:** `string` +- **Default:** `'center'` +- **Options:** `'center'`, `'center center'`, `'center left'`, `'center right'`, `'top center'`, `'top left'`, `'top right'`, `'bottom center'`, `'bottom left'`, `'bottom right'` + +Content alignment location. + +### `isDisabled` + +- **Type:** `boolean` +- **Default:** `false` + +Whether the control should be disabled. \ No newline at end of file diff --git a/packages/block-editor/src/components/block-alignment-matrix-control/index.js b/packages/block-editor/src/components/block-alignment-matrix-control/index.js index cdec41dfc7b97..fef7b424fdc94 100644 --- a/packages/block-editor/src/components/block-alignment-matrix-control/index.js +++ b/packages/block-editor/src/components/block-alignment-matrix-control/index.js @@ -11,6 +11,37 @@ import { const noop = () => {}; +/** + * The alignment matrix control allows users to quickly adjust inner block alignment. + * + * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/block-alignment-matrix-control/README.md + * + * @example + * ```jsx + * function Example() { + * return ( + * <BlockControls> + * <BlockAlignmentMatrixControl + * label={ __( 'Change content position' ) } + * value="center" + * onChange={ ( nextPosition ) => + * setAttributes( { contentPosition: nextPosition } ) + * } + * /> + * </BlockControls> + * ); + * } + * ``` + * + * @param {Object} props Component props. + * @param {string} props.label Label for the control. Defaults to 'Change matrix alignment'. + * @param {Function} props.onChange Function to execute upon change of matrix state. + * @param {string} props.value Content alignment location. One of: 'center', 'center center', + * 'center left', 'center right', 'top center', 'top left', + * 'top right', 'bottom center', 'bottom left', 'bottom right'. + * @param {boolean} props.isDisabled Whether the control should be disabled. + * @return {Element} The BlockAlignmentMatrixControl component. + */ function BlockAlignmentMatrixControl( props ) { const { label = __( 'Change matrix alignment' ), diff --git a/packages/block-editor/src/components/block-alignment-matrix-control/stories/index.story.js b/packages/block-editor/src/components/block-alignment-matrix-control/stories/index.story.js new file mode 100644 index 0000000000000..c2e1d27ea55b9 --- /dev/null +++ b/packages/block-editor/src/components/block-alignment-matrix-control/stories/index.story.js @@ -0,0 +1,78 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import BlockAlignmentMatrixControl from '../'; + +const meta = { + title: 'BlockEditor/BlockAlignmentMatrixControl', + component: BlockAlignmentMatrixControl, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'Renders a control for selecting block alignment using a matrix of alignment options.', + }, + }, + }, + argTypes: { + label: { + control: 'text', + table: { + type: { summary: 'string' }, + defaultValue: { summary: "'Change matrix alignment'" }, + }, + description: 'Label for the control.', + }, + onChange: { + action: 'onChange', + control: { type: null }, + table: { + type: { summary: 'function' }, + defaultValue: { summary: '() => {}' }, + }, + description: + "Function to execute upon a user's change of the matrix state.", + }, + isDisabled: { + control: 'boolean', + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: 'false' }, + }, + description: 'Whether the control should be disabled.', + }, + value: { + control: { type: null }, + table: { + type: { summary: 'string' }, + defaultValue: { summary: "'center'" }, + }, + description: 'Content alignment location.', + }, + }, +}; + +export default meta; + +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState(); + + return ( + <BlockAlignmentMatrixControl + { ...args } + value={ value } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + /> + ); + }, +}; diff --git a/packages/block-editor/src/components/block-card/README.md b/packages/block-editor/src/components/block-card/README.md index 216cf4e3865a0..79a42bc20df74 100644 --- a/packages/block-editor/src/components/block-card/README.md +++ b/packages/block-editor/src/components/block-card/README.md @@ -21,6 +21,7 @@ const MyBlockCard = () => ( icon={ paragraph } title="Paragraph" description="Start with the basic building block of all narrative." + name="Custom Block" /> ); ``` @@ -45,6 +46,12 @@ The title of the block. The description of the block. +#### name + +- **Type:** `String` + +The custom name of the block. + ## Related components Block Editor components are components that can be used to compose the UI of your block editor. Thus, they can only be used under a [`BlockEditorProvider`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/provider/README.md) in the components tree. diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index cdf52ee7df031..525a594702e30 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -6,27 +6,55 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import deprecated from '@wordpress/deprecated'; import { Button, __experimentalText as Text, __experimentalVStack as VStack, privateApis as componentsPrivateApis, } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import deprecated from '@wordpress/deprecated'; +import { __, isRTL } from '@wordpress/i18n'; import { chevronLeft, chevronRight } from '@wordpress/icons'; -import { __, _x, isRTL, sprintf } from '@wordpress/i18n'; -import { useSelect, useDispatch } from '@wordpress/data'; -import { createInterpolateElement } from '@wordpress/element'; /** * Internal dependencies */ -import BlockIcon from '../block-icon'; -import { store as blockEditorStore } from '../../store'; import { unlock } from '../../lock-unlock'; +import { store as blockEditorStore } from '../../store'; +import BlockIcon from '../block-icon'; const { Badge } = unlock( componentsPrivateApis ); +/** + * A card component that displays block information including title, icon, and description. + * Can be used to show block metadata and navigation controls for parent blocks. + * + * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/block-card/README.md + * + * @example + * ```jsx + * function Example() { + * return ( + * <BlockCard + * title="My Block" + * icon="smiley" + * description="A simple block example" + * name="Custom Block" + * /> + * ); + * } + * ``` + * + * @param {Object} props Component props. + * @param {string} props.title The title of the block. + * @param {string|Object} props.icon The icon of the block. This can be any of [WordPress' Dashicons](https://developer.wordpress.org/resource/dashicons/), or a custom `svg` element. + * @param {string} props.description The description of the block. + * @param {Object} [props.blockType] Deprecated: Object containing block type data. + * @param {string} [props.className] Additional classes to apply to the card. + * @param {string} [props.name] Custom block name to display before the title. + * @return {Element} Block card component. + */ function BlockCard( { title, icon, description, blockType, className, name } ) { if ( blockType ) { deprecated( '`blockType` property in `BlockCard component`', { @@ -71,25 +99,10 @@ function BlockCard( { title, icon, description, blockType, className, name } ) { <BlockIcon icon={ icon } showColors /> <VStack spacing={ 1 }> <h2 className="block-editor-block-card__title"> - { name?.length - ? createInterpolateElement( - sprintf( - // translators: 1: Custom block name. 2: Block title. - _x( - '<span>%1$s</span> <badge>%2$s</badge>', - 'block label' - ), - name, - title - ), - { - span: ( - <span className="block-editor-block-card__name" /> - ), - badge: <Badge />, - } - ) - : title } + <span className="block-editor-block-card__name"> + { !! name?.length ? name : title } + </span> + { !! name?.length && <Badge>{ title }</Badge> } </h2> { description && ( <Text className="block-editor-block-card__description"> diff --git a/packages/block-editor/src/components/block-card/style.scss b/packages/block-editor/src/components/block-card/style.scss index b02310fb630f4..a5cb675597908 100644 --- a/packages/block-editor/src/components/block-card/style.scss +++ b/packages/block-editor/src/components/block-card/style.scss @@ -16,10 +16,13 @@ font-size: $default-font-size; line-height: $default-line-height; margin: 0; - padding: 3px 0; // This makes the title as high as the icon. } } +.block-editor-block-card__name { + padding: 3px 0; // This makes the title as high as the icon. +} + .block-editor-block-card .block-editor-block-icon { flex: 0 0 $button-size-small; margin-left: 0; diff --git a/packages/block-editor/src/components/block-inspector/README.md b/packages/block-editor/src/components/block-inspector/README.md index 4c2fd2a06a61b..e6d7811ffe3a2 100644 --- a/packages/block-editor/src/components/block-inspector/README.md +++ b/packages/block-editor/src/components/block-inspector/README.md @@ -16,6 +16,16 @@ import { BlockInspector } from '@wordpress/block-editor'; const MyBlockInspector = () => <BlockInspector />; ``` +### Props + +#### showNoBlockSelectedMessage + +Whether to display a "No block selected" message when no block is selected. + +- Type: `boolean` +- Required: No +- Default: `true` + ## Related components Block Editor components are components that can be used to compose the UI of your block editor. Thus, they can only be used under a [BlockEditorProvider](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/provider/README.md) in the components tree. diff --git a/packages/block-editor/src/components/block-title/stories/index.story.js b/packages/block-editor/src/components/block-title/stories/index.story.js new file mode 100644 index 0000000000000..dc66fc721e515 --- /dev/null +++ b/packages/block-editor/src/components/block-title/stories/index.story.js @@ -0,0 +1,76 @@ +/** + * WordPress dependencies + */ +import { registerCoreBlocks } from '@wordpress/block-library'; +import { createBlock } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { ExperimentalBlockEditorProvider } from '../../provider'; +import BlockTitle from '../'; + +// Register core blocks for the story environment +registerCoreBlocks(); + +// Sample blocks for testing +const blocks = [ createBlock( 'core/paragraph' ) ]; + +const meta = { + title: 'BlockEditor/BlockTitle', + component: BlockTitle, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + "Renders the block's configured title as a string, or empty if the title cannot be determined.", + }, + }, + }, + decorators: [ + ( Story ) => ( + <ExperimentalBlockEditorProvider value={ blocks }> + <Story /> + </ExperimentalBlockEditorProvider> + ), + ], + argTypes: { + clientId: { + control: { type: null }, + description: 'Client ID of block.', + table: { + type: { + summary: 'string', + }, + }, + }, + maximumLength: { + control: { type: 'number' }, + description: + 'The maximum length that the block title string may be before truncated.', + table: { + type: { + summary: 'number', + }, + }, + }, + context: { + control: { type: 'text' }, + description: 'The context to pass to `getBlockLabel`.', + table: { + type: { + summary: 'string', + }, + }, + }, + }, +}; + +export default meta; + +export const Default = { + args: { + clientId: blocks[ 0 ].clientId, + }, +}; diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss index 80fe4c420d1e1..35d075c1a99b7 100644 --- a/packages/block-editor/src/components/block-tools/style.scss +++ b/packages/block-editor/src/components/block-tools/style.scss @@ -78,6 +78,7 @@ color: $white; padding: 0; + // TODO: Consider passing size="small" to the Inserter toggle instead. // Special dimensions for this button. min-width: $button-size-small; height: $button-size-small; diff --git a/packages/block-editor/src/components/border-radius-control/README.md b/packages/block-editor/src/components/border-radius-control/README.md new file mode 100644 index 0000000000000..7b048dfdb7e0d --- /dev/null +++ b/packages/block-editor/src/components/border-radius-control/README.md @@ -0,0 +1,59 @@ +# BorderRadiusControl + +`BorderRadiusControl` is a React component that provides a user interface for managing border radius values. It allows users to control the border radius of each corner independently or link them together for uniform values. + +## Usage + +```jsx +/** + * WordPress dependencies + */ +import { __experimentalBorderRadiusControl as BorderRadiusControl } from '@wordpress/block-editor'; +import { useState } from '@wordpress/element'; + +const MyBorderRadiusControl = () => { + const [values, setValues] = useState({ + topLeft: '10px', + topRight: '10px', + bottomLeft: '10px', + bottomRight: '10px', + }); + + return ( + <BorderRadiusControl + values={values} + onChange={setValues} + /> + ); +}; +``` + +## Props + +### values + +An object containing the border radius values for each corner. + +- **Type:** `Object` +- **Required:** No +- **Default:** `undefined` + +The values object has the following schema: + +| Property | Description | Type | +| ----------- | ------------------------------------ | ------ | +| topLeft | Border radius for top left corner | string | +| topRight | Border radius for top right corner | string | +| bottomLeft | Border radius for bottom left corner | string | +| bottomRight | Border radius for bottom right corner| string | + +Each value should be a valid CSS border radius value (e.g., '10px', '1em'). + +### onChange + +Callback function that is called when any border radius value changes. + +- **Type:** `Function` +- **Required:** Yes + +The function receives the updated values object as its argument. \ No newline at end of file diff --git a/packages/block-editor/src/components/border-radius-control/stories/index.story.js b/packages/block-editor/src/components/border-radius-control/stories/index.story.js new file mode 100644 index 0000000000000..28844a5e5cace --- /dev/null +++ b/packages/block-editor/src/components/border-radius-control/stories/index.story.js @@ -0,0 +1,58 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import BorderRadiusControl from '../'; + +const meta = { + title: 'BlockEditor/BorderRadiusControl', + component: BorderRadiusControl, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: 'Control to display border radius options.', + }, + }, + }, + argTypes: { + values: { + control: 'object', + description: 'Border radius values.', + table: { + type: { summary: 'object' }, + }, + }, + onChange: { + action: 'onChange', + control: { type: null }, + table: { + type: { summary: 'function' }, + }, + description: 'Callback to handle onChange.', + }, + }, +}; + +export default meta; + +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ values, setValues ] = useState( args.values ); + + return ( + <BorderRadiusControl + { ...args } + values={ values } + onChange={ ( ...changeArgs ) => { + setValues( ...changeArgs ); + onChange( ...changeArgs ); + } } + /> + ); + }, +}; diff --git a/packages/block-editor/src/components/colors-gradients/dropdown.js b/packages/block-editor/src/components/colors-gradients/dropdown.js index 71b27c06e7ccf..e667927bee760 100644 --- a/packages/block-editor/src/components/colors-gradients/dropdown.js +++ b/packages/block-editor/src/components/colors-gradients/dropdown.js @@ -15,6 +15,13 @@ import { __experimentalHStack as HStack, __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; +import { useRef } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { reset as resetIcon } from '@wordpress/icons'; /** * Internal dependencies @@ -76,7 +83,15 @@ const LabeledColorIndicator = ( { colorValue, label } ) => ( const renderToggle = ( settings ) => ( { onToggle, isOpen } ) => { - const { colorValue, label } = settings; + const { + clearable, + colorValue, + gradientValue, + onColorChange, + onGradientChange, + label, + } = settings; + const colorButtonRef = useRef( undefined ); const toggleProps = { onClick: onToggle, @@ -85,15 +100,45 @@ const renderToggle = { 'is-open': isOpen } ), 'aria-expanded': isOpen, + ref: colorButtonRef, + }; + + const clearValue = () => { + if ( colorValue ) { + onColorChange(); + } else if ( gradientValue ) { + onGradientChange(); + } }; + const value = colorValue ?? gradientValue; + return ( - <Button __next40pxDefaultSize { ...toggleProps }> - <LabeledColorIndicator - colorValue={ colorValue } - label={ label } - /> - </Button> + <> + <Button __next40pxDefaultSize { ...toggleProps }> + <LabeledColorIndicator + colorValue={ value } + label={ label } + /> + </Button> + { clearable && value && ( + <Button + __next40pxDefaultSize + label={ __( 'Reset' ) } + className="block-editor-panel-color-gradient-settings__reset" + size="small" + icon={ resetIcon } + onClick={ () => { + clearValue(); + if ( isOpen ) { + onToggle(); + } + // Return focus to parent button + colorButtonRef.current?.focus(); + } } + /> + ) } + </> ); }; @@ -143,8 +188,12 @@ export default function ColorGradientSettingsDropdown( { ...setting, }; const toggleSettings = { - colorValue: setting.gradientValue ?? setting.colorValue, + clearable: setting.clearable, label: setting.label, + colorValue: setting.colorValue, + gradientValue: setting.gradientValue, + onColorChange: setting.onColorChange, + onGradientChange: setting.onGradientChange, }; return ( diff --git a/packages/block-editor/src/components/colors-gradients/style.scss b/packages/block-editor/src/components/colors-gradients/style.scss index 222a5b239cf99..fbdf144a4176b 100644 --- a/packages/block-editor/src/components/colors-gradients/style.scss +++ b/packages/block-editor/src/components/colors-gradients/style.scss @@ -140,4 +140,9 @@ $swatch-gap: 12px; &:hover { opacity: 1; } + + @media (hover: none) { + // Show reset button on devices that do not support hover. + opacity: 1; + } } diff --git a/packages/block-editor/src/components/contrast-checker/stories/index.story.js b/packages/block-editor/src/components/contrast-checker/stories/index.story.js new file mode 100644 index 0000000000000..4518ab2ba7cd6 --- /dev/null +++ b/packages/block-editor/src/components/contrast-checker/stories/index.story.js @@ -0,0 +1,117 @@ +/** + * Internal dependencies + */ +import ContrastChecker from '../'; + +const meta = { + title: 'BlockEditor/ContrastChecker', + component: ContrastChecker, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'Determines if contrast for text styles is sufficient (WCAG 2.0 AA) when used with a given background color.', + }, + }, + }, + argTypes: { + backgroundColor: { + control: 'color', + description: + 'The background color to check the contrast of text against.', + table: { + type: { + summary: 'string', + }, + }, + }, + fallbackBackgroundColor: { + control: 'color', + description: + 'A fallback background color value, in case `backgroundColor` is not available.', + table: { + type: { + summary: 'string', + }, + }, + }, + textColor: { + control: 'color', + description: + 'The text color to check the contrast of the background against.', + table: { + type: { + summary: 'string', + }, + }, + }, + fallbackTextColor: { + control: 'color', + description: + 'A fallback text color value, in case `textColor` is not available.', + table: { + type: { + summary: 'string', + }, + }, + }, + fontSize: { + control: 'number', + description: + 'The font-size (as a `px` value) of the text to check the contrast against.', + table: { + type: { + summary: 'number', + }, + }, + }, + isLargeText: { + control: 'boolean', + description: + 'Whether the text is large (approximately `24px` or higher).', + table: { + type: { + summary: 'boolean', + }, + }, + }, + linkColor: { + control: 'color', + description: 'The link color to check the contrast against.', + table: { + type: { + summary: 'string', + }, + }, + }, + fallbackLinkColor: { + control: 'color', + description: 'Fallback link color if linkColor is not available.', + table: { + type: { + summary: 'string', + }, + }, + }, + enableAlphaChecker: { + control: 'boolean', + description: 'Whether to enable checking for transparent colors.', + table: { + type: { + summary: 'boolean', + }, + defaultValue: { summary: false }, + }, + }, + }, +}; + +export default meta; + +export const Default = { + args: { + backgroundColor: '#ffffff', + textColor: '#ffffff', + }, +}; diff --git a/packages/block-editor/src/components/default-block-appender/content.scss b/packages/block-editor/src/components/default-block-appender/content.scss index 71ede90d25c0c..361268fb2b37d 100644 --- a/packages/block-editor/src/components/default-block-appender/content.scss +++ b/packages/block-editor/src/components/default-block-appender/content.scss @@ -42,6 +42,7 @@ color: $white; padding: 0; + // TODO: Consider passing size="small" to the Inserter toggle instead. // Special dimensions for this button. min-width: $button-size-small; height: $button-size-small; diff --git a/packages/block-editor/src/components/dimensions-tool/stories/aspect-ratio-tool.story.js b/packages/block-editor/src/components/dimensions-tool/stories/aspect-ratio-tool.story.js index b853d78005294..aeb8a5f957425 100644 --- a/packages/block-editor/src/components/dimensions-tool/stories/aspect-ratio-tool.story.js +++ b/packages/block-editor/src/components/dimensions-tool/stories/aspect-ratio-tool.story.js @@ -13,8 +13,9 @@ import { import AspectRatioTool from '../aspect-ratio-tool'; export default { - title: 'BlockEditor (Private APIs)/DimensionsTool/AspectRatioTool', + title: 'BlockEditor/DimensionsTool/AspectRatioTool', component: AspectRatioTool, + tags: [ 'status-private' ], argTypes: { panelId: { control: false }, onChange: { action: 'changed' }, diff --git a/packages/block-editor/src/components/dimensions-tool/stories/index.story.js b/packages/block-editor/src/components/dimensions-tool/stories/index.story.js index ebf08fba0c686..0ccfba2b9e97a 100644 --- a/packages/block-editor/src/components/dimensions-tool/stories/index.story.js +++ b/packages/block-editor/src/components/dimensions-tool/stories/index.story.js @@ -13,8 +13,9 @@ import { import DimensionsTool from '..'; export default { - title: 'BlockEditor (Private APIs)/DimensionsTool', + title: 'BlockEditor/DimensionsTool/DimensionsTool', component: DimensionsTool, + tags: [ 'status-private' ], argTypes: { panelId: { control: false }, onChange: { action: 'changed' }, diff --git a/packages/block-editor/src/components/dimensions-tool/stories/scale-tool.story.js b/packages/block-editor/src/components/dimensions-tool/stories/scale-tool.story.js index b485bf68a892d..ea0a3ec194bee 100644 --- a/packages/block-editor/src/components/dimensions-tool/stories/scale-tool.story.js +++ b/packages/block-editor/src/components/dimensions-tool/stories/scale-tool.story.js @@ -13,8 +13,9 @@ import { import ScaleTool from '../scale-tool'; export default { - title: 'BlockEditor (Private APIs)/DimensionsTool/ScaleTool', + title: 'BlockEditor/DimensionsTool/ScaleTool', component: ScaleTool, + tags: [ 'status-private' ], argTypes: { panelId: { control: false }, onChange: { action: 'changed' }, diff --git a/packages/block-editor/src/components/dimensions-tool/stories/width-height-tool.story.js b/packages/block-editor/src/components/dimensions-tool/stories/width-height-tool.story.js index eed3cbc02f466..86b3b4b22be60 100644 --- a/packages/block-editor/src/components/dimensions-tool/stories/width-height-tool.story.js +++ b/packages/block-editor/src/components/dimensions-tool/stories/width-height-tool.story.js @@ -13,8 +13,9 @@ import { import WidthHeightTool from '../width-height-tool'; export default { - title: 'BlockEditor (Private APIs)/DimensionsTool/WidthHeightTool', + title: 'BlockEditor/DimensionsTool/WidthHeightTool', component: WidthHeightTool, + tags: [ 'status-private' ], argTypes: { panelId: { control: false }, onChange: { action: 'changed' }, diff --git a/packages/block-editor/src/components/font-family/index.js b/packages/block-editor/src/components/font-family/index.js index 6a723bb24c48e..b685e3990287f 100644 --- a/packages/block-editor/src/components/font-family/index.js +++ b/packages/block-editor/src/components/font-family/index.js @@ -72,12 +72,14 @@ export default function FontFamilyControl( { ); } + const selectedValue = + options.find( ( option ) => option.key === value ) ?? ''; return ( <CustomSelectControl __next40pxDefaultSize={ __next40pxDefaultSize } __shouldNotWarnDeprecated36pxSize label={ __( 'Font' ) } - value={ value } + value={ selectedValue } onChange={ ( { selectedItem } ) => onChange( selectedItem.key ) } options={ options } className={ clsx( 'block-editor-font-family-control', className, { diff --git a/packages/block-editor/src/components/global-styles/color-panel.js b/packages/block-editor/src/components/global-styles/color-panel.js index f1a1834967ed9..5d5c02d179307 100644 --- a/packages/block-editor/src/components/global-styles/color-panel.js +++ b/packages/block-editor/src/components/global-styles/color-panel.js @@ -250,6 +250,9 @@ function ColorPanelDropdown( { icon={ resetIcon } onClick={ () => { resetValue(); + if ( isOpen ) { + onToggle(); + } // Return focus to parent button colorGradientDropdownButtonRef.current?.focus(); } } diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js index 93e5cc9afdbb3..5022e8ba591db 100644 --- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js @@ -855,7 +855,7 @@ describe( 'global styles renderer', () => { it( 'should return block selectors data with old experimental selectors', () => { const imageSupports = { - border: { + __experimentalBorder: { radius: true, __experimentalSelector: 'img, .crop-area', }, diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index fabc65d143d1a..cd4ad0cea50e0 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -47,7 +47,7 @@ const ELEMENT_CLASS_NAMES = { // List of block support features that can have their related styles // generated under their own feature level selector rather than the block's. const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = { - border: 'border', + __experimentalBorder: 'border', color: 'color', spacing: 'spacing', typography: 'typography', @@ -624,7 +624,7 @@ function pickStyleKeys( treeToPickFrom ) { // clone the style objects so that `getFeatureDeclarations` can remove consumed keys from it const clonedEntries = pickedEntries.map( ( [ key, style ] ) => [ key, - structuredClone( style ), + JSON.parse( JSON.stringify( style ) ), ] ); return Object.fromEntries( clonedEntries ); } diff --git a/packages/block-editor/src/components/grid/grid-visualizer.js b/packages/block-editor/src/components/grid/grid-visualizer.js index 81da0457ffc5c..9d89866bbff5f 100644 --- a/packages/block-editor/src/components/grid/grid-visualizer.js +++ b/packages/block-editor/src/components/grid/grid-visualizer.js @@ -62,6 +62,17 @@ const GridVisualizerGrid = forwardRef( observer.observe( element ); observers.push( observer ); } + + const mutationObserver = new window.MutationObserver( () => { + setGridInfo( getGridInfo( gridElement ) ); + } ); + mutationObserver.observe( gridElement, { + attributeFilter: [ 'style', 'class' ], + childList: true, + subtree: true, + } ); + observers.push( mutationObserver ); + return () => { for ( const observer of observers ) { observer.disconnect(); diff --git a/packages/block-editor/src/components/grid/utils.js b/packages/block-editor/src/components/grid/utils.js index fc012c645f091..2101410808542 100644 --- a/packages/block-editor/src/components/grid/utils.js +++ b/packages/block-editor/src/components/grid/utils.js @@ -160,6 +160,21 @@ export function getGridInfo( gridElement ) { gridElement, 'grid-template-rows' ); + const borderTopWidth = getComputedCSS( gridElement, 'border-top-width' ); + const borderRightWidth = getComputedCSS( + gridElement, + 'border-right-width' + ); + const borderBottomWidth = getComputedCSS( + gridElement, + 'border-bottom-width' + ); + const borderLeftWidth = getComputedCSS( gridElement, 'border-left-width' ); + const paddingTop = getComputedCSS( gridElement, 'padding-top' ); + const paddingRight = getComputedCSS( gridElement, 'padding-right' ); + const paddingBottom = getComputedCSS( gridElement, 'padding-bottom' ); + const paddingLeft = getComputedCSS( gridElement, 'padding-left' ); + const numColumns = gridTemplateColumns.split( ' ' ).length; const numRows = gridTemplateRows.split( ' ' ).length; const numItems = numColumns * numRows; @@ -172,7 +187,10 @@ export function getGridInfo( gridElement ) { gridTemplateColumns, gridTemplateRows, gap: getComputedCSS( gridElement, 'gap' ), - padding: getComputedCSS( gridElement, 'padding' ), + paddingTop: `calc(${ paddingTop } + ${ borderTopWidth })`, + paddingRight: `calc(${ paddingRight } + ${ borderRightWidth })`, + paddingBottom: `calc(${ paddingBottom } + ${ borderBottomWidth })`, + paddingLeft: `calc(${ paddingLeft } + ${ borderLeftWidth })`, }, }; } diff --git a/packages/block-editor/src/components/image-size-control/index.js b/packages/block-editor/src/components/image-size-control/index.js index b5bb705ab101c..3432e85728fd3 100644 --- a/packages/block-editor/src/components/image-size-control/index.js +++ b/packages/block-editor/src/components/image-size-control/index.js @@ -8,7 +8,7 @@ import { __experimentalToggleGroupControl as ToggleGroupControl, __experimentalToggleGroupControlOption as ToggleGroupControlOption, } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies @@ -137,7 +137,11 @@ export default function ImageSizeControl( { <ToggleGroupControlOption key={ scale } value={ scale } - label={ `${ scale }%` } + label={ sprintf( + /* translators: Percentage value. */ + __( '%1$d%%' ), + scale + ) } /> ); } ) } diff --git a/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js b/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js index fd801779372aa..505785c87914d 100644 --- a/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js +++ b/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js @@ -7,7 +7,7 @@ import fastDeepEqual from 'fast-deep-equal/es6'; * WordPress dependencies */ import { useRef, useLayoutEffect } from '@wordpress/element'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useRegistry } from '@wordpress/data'; import { synchronizeBlocksWithTemplate } from '@wordpress/blocks'; /** @@ -42,14 +42,7 @@ export default function useInnerBlockTemplateSync( ) { // Instead of adding a useSelect mapping here, please add to the useSelect // mapping in InnerBlocks! Every subscription impacts performance. - - const { - getBlocks, - getSelectedBlocksInitialCaretPosition, - isBlockSelected, - } = useSelect( blockEditorStore ); - const { replaceInnerBlocks, __unstableMarkNextChangeAsNotPersistent } = - useDispatch( blockEditorStore ); + const registry = useRegistry(); // Maintain a reference to the previous value so we can do a deep equality check. const existingTemplateRef = useRef( null ); @@ -57,6 +50,14 @@ export default function useInnerBlockTemplateSync( useLayoutEffect( () => { let isCancelled = false; + const { + getBlocks, + getSelectedBlocksInitialCaretPosition, + isBlockSelected, + } = registry.select( blockEditorStore ); + const { replaceInnerBlocks, __unstableMarkNextChangeAsNotPersistent } = + registry.dispatch( blockEditorStore ); + // There's an implicit dependency between useInnerBlockTemplateSync and useNestedSettingsUpdate // The former needs to happen after the latter and since the latter is using microtasks to batch updates (performance optimization), // we need to schedule this one in a microtask as well. @@ -110,5 +111,11 @@ export default function useInnerBlockTemplateSync( return () => { isCancelled = true; }; - }, [ template, templateLock, clientId ] ); + }, [ + template, + templateLock, + clientId, + registry, + templateInsertUpdatesSelection, + ] ); } diff --git a/packages/block-editor/src/components/inserter/block-patterns-explorer/index.js b/packages/block-editor/src/components/inserter/block-patterns-explorer/index.js index 93a03ee200497..2bc41a7176954 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-explorer/index.js +++ b/packages/block-editor/src/components/inserter/block-patterns-explorer/index.js @@ -14,9 +14,8 @@ import { usePatternCategories } from '../block-patterns-tab/use-pattern-categori function PatternsExplorer( { initialCategory, rootClientId } ) { const [ searchValue, setSearchValue ] = useState( '' ); - const [ selectedCategory, setSelectedCategory ] = useState( - initialCategory?.name - ); + const [ selectedCategory, setSelectedCategory ] = + useState( initialCategory ); const patternCategories = usePatternCategories( rootClientId ); diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/index.js b/packages/block-editor/src/components/inserter/block-patterns-tab/index.js index 45db4732aa9c6..f250ed6f12eba 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/index.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/index.js @@ -70,7 +70,9 @@ function BlockPatternsTab( { ) } { showPatternsExplorer && ( <PatternsExplorerModal - initialCategory={ selectedCategory || categories[ 0 ] } + initialCategory={ + selectedCategory?.name || categories[ 0 ]?.name + } patternCategories={ categories } onModalClose={ () => setShowPatternsExplorer( false ) } rootClientId={ rootClientId } diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js index c6ce9ba97d250..f9af2b6f8c42d 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js @@ -69,19 +69,19 @@ export function PatternCategoryPreviews( { return false; } - if ( category.name === allPatternsCategory.name ) { + if ( category.name === allPatternsCategory?.name ) { return true; } if ( - category.name === myPatternsCategory.name && + category.name === myPatternsCategory?.name && pattern.type === INSERTER_PATTERN_TYPES.user ) { return true; } if ( - category.name === starterPatternsCategory.name && + category.name === starterPatternsCategory?.name && pattern.blockTypes?.includes( 'core/post-content' ) ) { return true; @@ -149,7 +149,7 @@ export function PatternCategoryPreviews( { level={ 4 } as="div" > - { category.label } + { category?.label } </Heading> </FlexBlock> <PatternsFilter diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js b/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js index 766082bd7690d..5c3560d384181 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/patterns-filter.js @@ -25,7 +25,7 @@ import { const getShouldDisableSyncFilter = ( sourceFilter ) => sourceFilter !== 'all' && sourceFilter !== 'user'; const getShouldHideSourcesFilter = ( category ) => { - return category.name === myPatternsCategory.name; + return category?.name === myPatternsCategory.name; }; const PATTERN_SOURCE_MENU_OPTIONS = [ @@ -60,7 +60,7 @@ export function PatternsFilter( { // the user may be confused when switching to another category if the haven't explicity set // this filter themselves. const currentPatternSourceFilter = - category.name === myPatternsCategory.name + category?.name === myPatternsCategory.name ? INSERTER_PATTERN_TYPES.user : patternSourceFilter; diff --git a/packages/block-editor/src/components/inserter/category-tabs/index.js b/packages/block-editor/src/components/inserter/category-tabs/index.js index ff0a130f1a827..7f5f9ba3f65ad 100644 --- a/packages/block-editor/src/components/inserter/category-tabs/index.js +++ b/packages/block-editor/src/components/inserter/category-tabs/index.js @@ -64,9 +64,10 @@ function CategoryTabs( { <Tabs.Tab key={ category.name } tabId={ category.name } - aria-label={ category.label } aria-current={ - category === selectedCategory ? 'true' : undefined + category.name === selectedCategory?.name + ? 'true' + : undefined } > { category.label } diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index 47304a7d77174..59d78a6f0edc6 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -60,6 +60,7 @@ const defaultRenderToggle = ( { return ( <Wrapper + __next40pxDefaultSize={ toggleProps.as ? undefined : true } icon={ plus } label={ label } tooltipPosition="bottom" diff --git a/packages/block-editor/src/components/keyboard-shortcuts/index.js b/packages/block-editor/src/components/keyboard-shortcuts/index.js index 9b83844646922..fcc2cea404375 100644 --- a/packages/block-editor/src/components/keyboard-shortcuts/index.js +++ b/packages/block-editor/src/components/keyboard-shortcuts/index.js @@ -29,8 +29,8 @@ function KeyboardShortcutsRegister() { category: 'block', description: __( 'Remove the selected block(s).' ), keyCombination: { - modifier: 'access', - character: 'z', + modifier: 'shift', + character: 'backspace', }, } ); diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 2916622efabee..3529c27b56e24 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -553,13 +553,18 @@ svg { } .list-view-appender .block-editor-inserter__toggle { - background-color: #1e1e1e; - color: #fff; - margin: $grid-unit-10 0 0 24px; - height: 24px; - min-width: 24px; + background-color: $gray-900; + color: $white; + margin: $grid-unit-10 0 0 $grid-unit-30; + height: $button-size-small; padding: 0; + // TODO: Consider passing size="small" to the Inserter toggle instead. + // Special dimensions for this button. + &.has-icon.is-next-40px-default-size { + min-width: $button-size-small; + } + &:hover, &:focus { background: var(--wp-admin-theme-color); diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js index 0cbc6c8c26203..b19411893b86c 100644 --- a/packages/block-editor/src/components/media-placeholder/index.js +++ b/packages/block-editor/src/components/media-placeholder/index.js @@ -15,7 +15,7 @@ import { __experimentalInputControlSuffixWrapper as InputControlSuffixWrapper, withFilters, } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { useState, useEffect } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { keyboardReturn } from '@wordpress/icons'; @@ -482,7 +482,7 @@ export function MediaPlaceholder( { ) } onClick={ openFileDialog } > - { __( 'Upload' ) } + { _x( 'Upload', 'verb' ) } </Button> { uploadMediaLibraryButton } { renderUrlSelectionUI() } @@ -512,7 +512,7 @@ export function MediaPlaceholder( { 'block-editor-media-placeholder__upload-button' ) } > - { __( 'Upload' ) } + { _x( 'Upload', 'verb' ) } </Button> ) } onChange={ onUpload } diff --git a/packages/block-editor/src/components/media-replace-flow/README.md b/packages/block-editor/src/components/media-replace-flow/README.md index a5808ab956198..b3427efffbcf1 100644 --- a/packages/block-editor/src/components/media-replace-flow/README.md +++ b/packages/block-editor/src/components/media-replace-flow/README.md @@ -98,3 +98,10 @@ If passed, children are rendered inside the dropdown. - Required: No If passed, children are rendered inside the dropdown. If a function is provided for this prop, it will receive an object with the `onClose` prop as an argument. + +### renderToggle + +- Type: `func` +- Required: No + +If passed, it will be used to render the provided button instead of the default one. It should accept and pass through `button` props to a `button` element. diff --git a/packages/block-editor/src/components/media-replace-flow/index.js b/packages/block-editor/src/components/media-replace-flow/index.js index 0da15033a86bf..53c2a66634f0a 100644 --- a/packages/block-editor/src/components/media-replace-flow/index.js +++ b/packages/block-editor/src/components/media-replace-flow/index.js @@ -1,21 +1,15 @@ -/** - * External dependencies - */ -import clsx from 'clsx'; - /** * WordPress dependencies */ -import { useRef } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; import { FormFileUpload, NavigableMenu, MenuItem, - ToolbarButton, Dropdown, withFilters, + ToolbarButton, } from '@wordpress/components'; import { useSelect, withDispatch } from '@wordpress/data'; import { DOWN } from '@wordpress/keycodes'; @@ -60,12 +54,9 @@ const MediaReplaceFlow = ( { addToGallery, handleUpload = true, popoverProps, + renderToggle, } ) => { - const mediaUpload = useSelect( ( select ) => { - return select( blockEditorStore ).getSettings().mediaUpload; - }, [] ); - const canUpload = !! mediaUpload; - const editMediaButtonRef = useRef(); + const { getSettings } = useSelect( blockEditorStore ); const errorNoticeID = `block-editor/media-replace-flow/error-notice/${ ++uniqueId }`; const onUploadError = ( message ) => { @@ -107,7 +98,7 @@ const MediaReplaceFlow = ( { return onSelect( files ); } onFilesUpload( files ); - mediaUpload( { + getSettings().mediaUpload( { allowedTypes, filesList: files, onFileChange: ( [ media ] ) => { @@ -141,17 +132,27 @@ const MediaReplaceFlow = ( { <Dropdown popoverProps={ popoverProps } contentClassName="block-editor-media-replace-flow__options" - renderToggle={ ( { isOpen, onToggle } ) => ( - <ToolbarButton - ref={ editMediaButtonRef } - aria-expanded={ isOpen } - aria-haspopup="true" - onClick={ onToggle } - onKeyDown={ openOnArrowDown } - > - { name } - </ToolbarButton> - ) } + renderToggle={ ( { isOpen, onToggle } ) => { + if ( renderToggle ) { + return renderToggle( { + 'aria-expanded': isOpen, + 'aria-haspopup': 'true', + onClick: onToggle, + onKeyDown: openOnArrowDown, + children: name, + } ); + } + return ( + <ToolbarButton + aria-expanded={ isOpen } + aria-haspopup="true" + onClick={ onToggle } + onKeyDown={ openOnArrowDown } + > + { name } + </ToolbarButton> + ); + } } renderContent={ ( { onClose } ) => ( <> <NavigableMenu className="block-editor-media-replace-flow__media-upload-menu"> @@ -188,7 +189,7 @@ const MediaReplaceFlow = ( { openFileDialog(); } } > - { __( 'Upload' ) } + { _x( 'Upload', 'verb' ) } </MenuItem> ); } } @@ -219,15 +220,7 @@ const MediaReplaceFlow = ( { </NavigableMenu> { onSelectURL && ( // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions - <form - className={ clsx( - 'block-editor-media-flow__url-input', - { - 'has-siblings': - canUpload || onToggleFeaturedImage, - } - ) } - > + <form className="block-editor-media-flow__url-input"> <span className="block-editor-media-replace-flow__image-url-label"> { __( 'Current media URL:' ) } </span> @@ -238,7 +231,6 @@ const MediaReplaceFlow = ( { showSuggestions={ false } onChange={ ( { url } ) => { onSelectURL( url ); - editMediaButtonRef.current.focus(); } } /> </form> diff --git a/packages/block-editor/src/components/media-replace-flow/style.scss b/packages/block-editor/src/components/media-replace-flow/style.scss index 61df542cf5840..d9d8d1c98c11f 100644 --- a/packages/block-editor/src/components/media-replace-flow/style.scss +++ b/packages/block-editor/src/components/media-replace-flow/style.scss @@ -9,17 +9,17 @@ margin-left: 4px; } +.block-editor-media-replace-flow__media-upload-menu:not(:empty) + .block-editor-media-flow__url-input { + border-top: $border-width solid $gray-900; + margin-top: $grid-unit-10; + padding-bottom: $grid-unit-10; +} + .block-editor-media-flow__url-input { margin-right: -$grid-unit-10; margin-left: -$grid-unit-10; padding: $grid-unit-20; - &.has-siblings { - border-top: $border-width solid $gray-900; - margin-top: $grid-unit-10; - padding-bottom: $grid-unit-10; - } - .block-editor-media-replace-flow__image-url-label { display: block; top: $grid-unit-20; diff --git a/packages/block-editor/src/components/plain-text/README.md b/packages/block-editor/src/components/plain-text/README.md index aa15758118afd..1e0a7888ed1e4 100644 --- a/packages/block-editor/src/components/plain-text/README.md +++ b/packages/block-editor/src/components/plain-text/README.md @@ -6,11 +6,11 @@ Render an auto-growing textarea allow users to fill any textual content. ### `value: string` -_Required._ String value of the textarea +_Required._ String value of the textarea. ### `onChange( value: string ): Function` -_Required._ Called when the value changes. +_Required._ Function called when the text value changes. You can also pass any extra prop to the textarea rendered by this component. diff --git a/packages/block-editor/src/components/plain-text/index.js b/packages/block-editor/src/components/plain-text/index.js index 4bd6681f4eb07..d28aabebf7a14 100644 --- a/packages/block-editor/src/components/plain-text/index.js +++ b/packages/block-editor/src/components/plain-text/index.js @@ -15,7 +15,41 @@ import { forwardRef } from '@wordpress/element'; import EditableText from '../editable-text'; /** + * Render an auto-growing textarea allow users to fill any textual content. + * * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/plain-text/README.md + * + * @example + * ```jsx + * import { registerBlockType } from '@wordpress/blocks'; + * import { PlainText } from '@wordpress/block-editor'; + * + * registerBlockType( 'my-plugin/example-block', { + * // ... + * + * attributes: { + * content: { + * type: 'string', + * }, + * }, + * + * edit( { className, attributes, setAttributes } ) { + * return ( + * <PlainText + * className={ className } + * value={ attributes.content } + * onChange={ ( content ) => setAttributes( { content } ) } + * /> + * ); + * }, + * } ); + * ```` + * + * @param {Object} props Component props. + * @param {string} props.value String value of the textarea. + * @param {Function} props.onChange Function called when the text value changes. + * @param {Object} [props.ref] The component forwards the `ref` property to the `TextareaAutosize` component. + * @return {Element} Plain text component */ const PlainText = forwardRef( ( { __experimentalVersion, ...props }, ref ) => { if ( __experimentalVersion === 2 ) { diff --git a/packages/block-editor/src/components/plain-text/stories/index.story.js b/packages/block-editor/src/components/plain-text/stories/index.story.js new file mode 100644 index 0000000000000..d1a6253c0870a --- /dev/null +++ b/packages/block-editor/src/components/plain-text/stories/index.story.js @@ -0,0 +1,75 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import PlainText from '..'; + +const meta = { + title: 'BlockEditor/PlainText', + component: PlainText, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'PlainText renders an auto-growing textarea that allows users to enter any textual content.', + }, + }, + }, + argTypes: { + value: { + control: { + type: null, + }, + table: { + type: { + summary: 'string', + }, + }, + description: 'String value of the textarea.', + }, + onChange: { + action: 'onChange', + control: { + type: null, + }, + table: { + type: { + summary: 'function', + }, + }, + description: 'Function called when the text value changes.', + }, + className: { + control: 'text', + table: { + type: { + summary: 'string', + }, + }, + description: 'Additional class name for the PlainText.', + }, + }, +}; + +export default meta; + +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState(); + return ( + <PlainText + { ...args } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); + }, +}; diff --git a/packages/block-editor/src/components/resolution-tool/index.js b/packages/block-editor/src/components/resolution-tool/index.js index df43cb6acb096..b73a2d5f24972 100644 --- a/packages/block-editor/src/components/resolution-tool/index.js +++ b/packages/block-editor/src/components/resolution-tool/index.js @@ -33,6 +33,7 @@ export default function ResolutionTool( { options = DEFAULT_SIZE_OPTIONS, defaultValue = DEFAULT_SIZE_OPTIONS[ 0 ].value, isShownByDefault = true, + resetAllFilter, } ) { const displayValue = value ?? defaultValue; return ( @@ -42,6 +43,7 @@ export default function ResolutionTool( { onDeselect={ () => onChange( defaultValue ) } isShownByDefault={ isShownByDefault } panelId={ panelId } + resetAllFilter={ resetAllFilter } > <SelectControl __nextHasNoMarginBottom diff --git a/packages/block-editor/src/components/resolution-tool/stories/index.story.js b/packages/block-editor/src/components/resolution-tool/stories/index.story.js index 3fedb6d6facae..531618b38224f 100644 --- a/packages/block-editor/src/components/resolution-tool/stories/index.story.js +++ b/packages/block-editor/src/components/resolution-tool/stories/index.story.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useState } from '@wordpress/element'; +import { useReducer } from '@wordpress/element'; import { Panel, __experimentalToolsPanel as ToolsPanel, @@ -13,30 +13,56 @@ import { import ResolutionTool from '..'; export default { - title: 'BlockEditor (Private APIs)/ResolutionControl', + title: 'BlockEditor/ResolutionControl', component: ResolutionTool, + tags: [ 'status-private' ], argTypes: { panelId: { control: false }, onChange: { action: 'changed' }, }, }; -export const Default = ( { panelId, onChange: onChangeProp, ...props } ) => { - const [ resolution, setResolution ] = useState( undefined ); - const resetAll = () => { - setResolution( undefined ); +export const Default = ( { + label, + panelId, + onChange: onChangeProp, + ...props +} ) => { + const [ attributes, setAttributes ] = useReducer( + ( prevState, nextState ) => ( { ...prevState, ...nextState } ), + {} + ); + const { resolution } = attributes; + const resetAll = ( resetFilters = [] ) => { + let newAttributes = {}; + + resetFilters.forEach( ( resetFilter ) => { + newAttributes = { + ...newAttributes, + ...resetFilter( newAttributes ), + }; + } ); + + setAttributes( newAttributes ); onChangeProp( undefined ); }; return ( <Panel> - <ToolsPanel panelId={ panelId } resetAll={ resetAll }> + <ToolsPanel + label={ label } + panelId={ panelId } + resetAll={ resetAll } + > <ResolutionTool panelId={ panelId } onChange={ ( newValue ) => { - setResolution( newValue ); + setAttributes( { resolution: newValue } ); onChangeProp( newValue ); } } value={ resolution } + resetAllFilter={ () => ( { + resolution: undefined, + } ) } { ...props } /> </ToolsPanel> @@ -44,5 +70,7 @@ export const Default = ( { panelId, onChange: onChangeProp, ...props } ) => { ); }; Default.args = { + label: 'Settings', + defaultValue: 'full', panelId: 'panel-id', }; diff --git a/packages/block-editor/src/components/responsive-block-control/index.js b/packages/block-editor/src/components/responsive-block-control/index.js index 148ba9600f003..388e7ec543693 100644 --- a/packages/block-editor/src/components/responsive-block-control/index.js +++ b/packages/block-editor/src/components/responsive-block-control/index.js @@ -57,7 +57,7 @@ function ResponsiveBlockControl( props ) { ); const toggleHelpText = __( - 'Toggle between using the same value for all screen sizes or using a unique value per screen size.' + 'Choose whether to use the same value for all screen sizes or a unique value for each screen size.' ); const defaultControl = renderDefaultControl( diff --git a/packages/block-editor/src/components/rich-text/event-listeners/delete.js b/packages/block-editor/src/components/rich-text/event-listeners/delete.js index ae3fd733bb94e..8373ca3c9f72a 100644 --- a/packages/block-editor/src/components/rich-text/event-listeners/delete.js +++ b/packages/block-editor/src/components/rich-text/event-listeners/delete.js @@ -6,7 +6,7 @@ import { isCollapsed, isEmpty } from '@wordpress/rich-text'; export default ( props ) => ( element ) => { function onKeyDown( event ) { - const { keyCode } = event; + const { keyCode, shiftKey } = event; if ( event.defaultPrevented ) { return; @@ -30,6 +30,11 @@ export default ( props ) => ( element ) => { return; } + // Exclude shift+backspace as they are shortcuts for deleting blocks. + if ( shiftKey ) { + return; + } + if ( onMerge ) { onMerge( ! isReverse ); } diff --git a/packages/block-editor/src/components/tabbed-sidebar/README.md b/packages/block-editor/src/components/tabbed-sidebar/README.md index 42001dbfc79cb..35c44730ffc15 100644 --- a/packages/block-editor/src/components/tabbed-sidebar/README.md +++ b/packages/block-editor/src/components/tabbed-sidebar/README.md @@ -1,21 +1,19 @@ -# Tabbed Panel +# TabbedSidebar -The `TabbedPanel` component is used to create the secondary panels in the editor. +The `TabbedSidebar` component is used to create secondary panels in the editor with tabbed navigation. ## Development guidelines -This acts as a wrapper for the `Tabs` component, but adding conventions that can be shared between all secondary panels, for example: +This acts as a wrapper for the `Tabs` component, adding conventions that can be shared between all secondary panels, including: - A close button - Tabs that fill the panel -- Custom scollbars +- Custom scrollbars ### Usage -Renders a block alignment toolbar with alignments options. - ```jsx -import { TabbedSidebar } from '@wordpress/components'; +import { TabbedSidebar } from '@wordpress/block-editor'; const MyTabbedSidebar = () => ( <TabbedSidebar @@ -23,7 +21,7 @@ const MyTabbedSidebar = () => ( { name: 'slug-1', title: _x( 'Title 1', 'context' ), - panel: <PanelContents>, + panel: <PanelContents />, panelRef: useRef('an-optional-ref'), }, { @@ -35,6 +33,8 @@ const MyTabbedSidebar = () => ( onClose={ onClickCloseButton } onSelect={ onSelectTab } defaultTabId="slug-1" + selectedTab="slug-1" + closeButtonLabel="Close sidebar" ref={ tabsRef } /> ); @@ -47,30 +47,41 @@ const MyTabbedSidebar = () => ( - **Type:** `String` - **Default:** `undefined` -This is passed to the `Tabs` component so it can handle the tab to select by default when it component renders. +The ID of the tab to be selected by default when the component renders. ### `onClose` - **Type:** `Function` -The function that is called when the close button is clicked. +Function called when the close button is clicked. ### `onSelect` - **Type:** `Function` -This is passed to the `Tabs` component - it will be called when a tab has been selected. It is passed the selected tab's ID as an argument. +Function called when a tab is selected. Receives the selected tab's ID as an argument. ### `selectedTab` - **Type:** `String` - **Default:** `undefined` -This is passed to the `Tabs` component - it will display this tab as selected. +The ID of the currently selected tab. ### `tabs` - **Type:** `Array` - **Default:** `undefined` -An array of tabs which will be rendered as `TabList` and `TabPanel` components. +Array of tab objects. Each tab should have: + +- `name` (string): Unique identifier for the tab +- `title` (string): Display title for the tab +- `panel` (React.Node): Content to display in the tab panel +- `panelRef` (React.Ref, optional): Reference to the tab panel element + +#### `closeButtonLabel` + +- **Type:** `String` + +Accessibility label for the close button. \ No newline at end of file diff --git a/packages/block-editor/src/components/tabbed-sidebar/index.js b/packages/block-editor/src/components/tabbed-sidebar/index.js index c9ff6bbf6555f..f142f538cfe8f 100644 --- a/packages/block-editor/src/components/tabbed-sidebar/index.js +++ b/packages/block-editor/src/components/tabbed-sidebar/index.js @@ -15,6 +15,44 @@ import { unlock } from '../../lock-unlock'; const { Tabs } = unlock( componentsPrivateApis ); +/** + * A component that creates a tabbed sidebar with a close button. + * + * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/tabbed-sidebar/README.md + * + * @example + * ```jsx + * function MyTabbedSidebar() { + * return ( + * <TabbedSidebar + * tabs={ [ + * { + * name: 'tab1', + * title: 'Settings', + * panel: <PanelContents />, + * } + * ] } + * onClose={ () => {} } + * onSelect={ () => {} } + * defaultTabId="tab1" + * selectedTab="tab1" + * closeButtonLabel="Close sidebar" + * /> + * ); + * } + * ``` + * + * @param {Object} props Component props. + * @param {string} [props.defaultTabId] The ID of the tab to be selected by default when the component renders. + * @param {Function} props.onClose Function called when the close button is clicked. + * @param {Function} props.onSelect Function called when a tab is selected. Receives the selected tab's ID as an argument. + * @param {string} props.selectedTab The ID of the currently selected tab. + * @param {Array} props.tabs Array of tab objects. Each tab should have: name (string), title (string), + * panel (React.Node), and optionally panelRef (React.Ref). + * @param {string} props.closeButtonLabel Accessibility label for the close button. + * @param {Object} ref Forward ref to the tabs list element. + * @return {Element} The tabbed sidebar component. + */ function TabbedSidebar( { defaultTabId, onClose, onSelect, selectedTab, tabs, closeButtonLabel }, ref diff --git a/packages/block-editor/src/components/tabbed-sidebar/stories/index.story.js b/packages/block-editor/src/components/tabbed-sidebar/stories/index.story.js new file mode 100644 index 0000000000000..49825be19b90c --- /dev/null +++ b/packages/block-editor/src/components/tabbed-sidebar/stories/index.story.js @@ -0,0 +1,104 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import TabbedSidebar from '../'; + +const meta = { + title: 'BlockEditor/TabbedSidebar', + component: TabbedSidebar, + tags: [ 'status-private' ], + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'A component that creates a tabbed sidebar with a close button.', + }, + }, + }, + argTypes: { + defaultTabId: { + control: { type: null }, + table: { + type: { summary: 'string' }, + }, + description: + 'The ID of the tab to be selected by default when the component renders.', + }, + onClose: { + action: 'onClose', + control: { type: null }, + table: { + type: { summary: 'function' }, + }, + description: 'Function called when the close button is clicked.', + }, + onSelect: { + action: 'onSelect', + control: { type: null }, + table: { + type: { summary: 'function' }, + }, + description: + "Function called when a tab is selected. Receives the selected tab's ID as an argument.", + }, + selectedTab: { + control: { type: null }, + table: { + type: { summary: 'string' }, + }, + description: 'The ID of the currently selected tab.', + }, + tabs: { + control: { type: 'array' }, + table: { + type: { summary: 'array' }, + }, + description: + 'Array of tab objects. Each tab should have: name (string), title (string), panel (React.Node), and optionally panelRef (React.Ref).', + }, + closeButtonLabel: { + control: { type: 'text' }, + table: { + type: { summary: 'string' }, + }, + description: 'Accessibility label for the close button.', + }, + }, +}; + +export default meta; + +const DEMO_TABS = [ + { name: 'tab1', title: 'Settings' }, + { name: 'tab2', title: 'Styles' }, + { name: 'tab3', title: 'Advanced' }, +]; + +export const Default = { + render: function Template( { onSelect, onClose, ...args } ) { + const [ selectedTab, setSelectedTab ] = useState(); + + return ( + <TabbedSidebar + { ...args } + selectedTab={ selectedTab } + onSelect={ ( ...changeArgs ) => { + onSelect( ...changeArgs ); + setSelectedTab( ...changeArgs ); + } } + onClose={ onClose } + /> + ); + }, + args: { + tabs: DEMO_TABS, + defaultTabId: 'tab1', + closeButtonLabel: 'Close Sidebar', + }, +}; diff --git a/packages/block-editor/src/components/text-alignment-control/README.md b/packages/block-editor/src/components/text-alignment-control/README.md new file mode 100644 index 0000000000000..243a5fec7938b --- /dev/null +++ b/packages/block-editor/src/components/text-alignment-control/README.md @@ -0,0 +1,49 @@ +# TextAlignmentControl + +The `TextAlignmentControl` component is responsible for rendering a control element that allows users to select and apply text alignment options to blocks or elements in the Gutenberg editor. It provides an intuitive interface for aligning text with options such as `left`, `center` and `right`. + +## Usage + +Renders the Text Alignment Component with `left`, `center` and `right` alignment options. + +```jsx +import { TextAlignmentControl } from '@wordpress/block-editor'; + +const MyTextAlignmentControlComponent = () => ( + <TextAlignmentControl + value={ textAlign } + onChange={ ( value ) => { + setAttributes( { textAlign: value } ); + } } + /> +); +``` + +## Props + +### `value` + +- **Type:** `String` +- **Default:** `undefined` +- **Options:** `left`, `center`, `right`, `justify` + +The current value of the text alignment setting. You may only choose from the `Options` listed above. + +### `onChange` + +- **Type:** `Function` + +A callback function invoked when the text alignment value is changed via an interaction with any of the options. The function is called with the new alignment value (`left`, `center`, `right`) as the only argument. + +### `className` + +- **Type:** `String` + +Class name to add to the control for custom styling. + +### `options` + +- **Type:** `Array` +- **Default:** [`left`, `center`, `right`] + +An array that determines which alignment options will be available in the control. You can pass an array of alignment values to customize the options. diff --git a/packages/block-editor/src/components/text-alignment-control/stories/index.story.js b/packages/block-editor/src/components/text-alignment-control/stories/index.story.js index fd97f9b60e6a9..076535ab330d6 100644 --- a/packages/block-editor/src/components/text-alignment-control/stories/index.story.js +++ b/packages/block-editor/src/components/text-alignment-control/stories/index.story.js @@ -11,6 +11,7 @@ import TextAlignmentControl from '../'; const meta = { title: 'BlockEditor/TextAlignmentControl', component: TextAlignmentControl, + tags: [ 'status-private' ], parameters: { docs: { canvas: { sourceState: 'shown' }, diff --git a/packages/block-editor/src/components/text-decoration-control/README.md b/packages/block-editor/src/components/text-decoration-control/README.md index a606140baa330..87fb6e89bd571 100644 --- a/packages/block-editor/src/components/text-decoration-control/README.md +++ b/packages/block-editor/src/components/text-decoration-control/README.md @@ -28,7 +28,6 @@ Then, you can use the component in your block editor UI: ### `value` - **Type:** `String` -- **Default:** `none` - **Options:** `none`, `underline`, `line-through` The current value of the Text Decoration setting. You may only choose from the `Options` listed above. diff --git a/packages/block-editor/src/components/text-decoration-control/stories/index.story.js b/packages/block-editor/src/components/text-decoration-control/stories/index.story.js index 2212b484185cd..d139b30a2bb4b 100644 --- a/packages/block-editor/src/components/text-decoration-control/stories/index.story.js +++ b/packages/block-editor/src/components/text-decoration-control/stories/index.story.js @@ -8,26 +8,61 @@ import { useState } from '@wordpress/element'; */ import TextDecorationControl from '../'; -export default { +const meta = { title: 'BlockEditor/TextDecorationControl', component: TextDecorationControl, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: 'Control to facilitate text decoration selections.', + }, + }, + }, argTypes: { - onChange: { action: 'onChange' }, + value: { + control: { type: null }, + description: 'Currently selected text decoration.', + table: { + type: { + summary: 'string', + }, + }, + }, + onChange: { + action: 'onChange', + control: { type: null }, + description: 'Handles change in text decoration selection.', + table: { + type: { + summary: 'function', + }, + }, + }, + className: { + control: 'text', + description: 'Additional class name to apply.', + table: { + type: { summary: 'string' }, + }, + }, }, }; -const Template = ( { onChange, ...args } ) => { - const [ value, setValue ] = useState(); - return ( - <TextDecorationControl - { ...args } - onChange={ ( ...changeArgs ) => { - onChange( ...changeArgs ); - setValue( ...changeArgs ); - } } - value={ value } - /> - ); -}; +export default meta; -export const Default = Template.bind( {} ); +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState(); + return ( + <TextDecorationControl + { ...args } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); + }, +}; diff --git a/packages/block-editor/src/components/text-transform-control/README.md b/packages/block-editor/src/components/text-transform-control/README.md index 2d40cc16ba86f..cd23461d3eb33 100644 --- a/packages/block-editor/src/components/text-transform-control/README.md +++ b/packages/block-editor/src/components/text-transform-control/README.md @@ -1,8 +1,8 @@ # TextTransformControl The `TextTransformControl` component is responsible for rendering a control element that allows users to select and apply text transformation options to blocks or elements in the Gutenberg editor. It provides an intuitive interface for changing the text appearance by applying different transformations such as `none`, `uppercase`, `lowercase`, `capitalize`. - -![TextTransformConrol Element in Inspector Control](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/text-transform-component.png?raw=true) + +![TextTransformControl Element in Inspector Control](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/text-transform-component.png?raw=true) ## Development guidelines @@ -28,7 +28,6 @@ const MyTextTransformControlComponent = () => ( ### `value` - **Type:** `String` -- **Default:** `none` - **Options:** `none`, `uppercase`, `lowercase`, `capitalize` The current value of the Text Transform setting. You may only choose from the `Options` listed above. @@ -37,4 +36,4 @@ The current value of the Text Transform setting. You may only choose from the `O - **Type:** `Function` -A callback function invoked when the Text Transform value is changed via an interaction with any of the buttons. Called with the Text Transform value (`none`, `uppercase`, `lowercase`, `capitalize`) as the only argument. \ No newline at end of file +A callback function invoked when the Text Transform value is changed via an interaction with any of the buttons. Called with the Text Transform value (`none`, `uppercase`, `lowercase`, `capitalize`) as the only argument. diff --git a/packages/block-editor/src/components/text-transform-control/stories/index.story.js b/packages/block-editor/src/components/text-transform-control/stories/index.story.js index 96dd8ed479dc4..77dc550368da1 100644 --- a/packages/block-editor/src/components/text-transform-control/stories/index.story.js +++ b/packages/block-editor/src/components/text-transform-control/stories/index.story.js @@ -8,26 +8,63 @@ import { useState } from '@wordpress/element'; */ import TextTransformControl from '../'; -export default { +const meta = { title: 'BlockEditor/TextTransformControl', component: TextTransformControl, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'Control to facilitate text transformation selections.', + }, + }, + }, argTypes: { - onChange: { action: 'onChange' }, + onChange: { + action: 'onChange', + control: { + type: null, + }, + description: 'Handles change in text transform selection.', + table: { + type: { + summary: 'function', + }, + }, + }, + className: { + control: { type: 'text' }, + description: 'Class name to add to the control.', + table: { + type: { summary: 'string' }, + }, + }, + value: { + control: { type: null }, + description: 'Currently selected text transform.', + table: { + type: { summary: 'string' }, + }, + }, }, }; -const Template = ( { onChange, ...args } ) => { - const [ value, setValue ] = useState(); - return ( - <TextTransformControl - { ...args } - onChange={ ( ...changeArgs ) => { - onChange( ...changeArgs ); - setValue( ...changeArgs ); - } } - value={ value } - /> - ); -}; +export default meta; + +export const Default = { + render: function Template( { onChange, ...args } ) { + const [ value, setValue ] = useState(); -export const Default = Template.bind( {} ); + return ( + <TextTransformControl + { ...args } + onChange={ ( ...changeArgs ) => { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); + }, +}; diff --git a/packages/block-editor/src/components/warning/stories/index.story.js b/packages/block-editor/src/components/warning/stories/index.story.js new file mode 100644 index 0000000000000..ee881059f302d --- /dev/null +++ b/packages/block-editor/src/components/warning/stories/index.story.js @@ -0,0 +1,86 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Button } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import Warning from '../'; + +const meta = { + title: 'BlockEditor/Warning', + component: Warning, + parameters: { + docs: { + canvas: { sourceState: 'shown' }, + description: { + component: + 'Displays a warning message with optional action buttons and secondary actions dropdown.', + }, + }, + }, + argTypes: { + children: { + control: 'text', + description: + 'Intended to represent the block to which the warning pertains.', + table: { + type: { summary: 'string|element' }, + }, + }, + className: { + control: 'text', + description: 'Classes to pass to element.', + table: { + type: { summary: 'string' }, + }, + }, + actions: { + control: 'object', + description: + 'An array of elements to be rendered as action buttons in the warning element.', + table: { + type: { summary: 'Element[]' }, + }, + }, + secondaryActions: { + control: 'object', + description: + 'An array of { title, onClick } to be rendered as options in a dropdown of secondary actions.', + table: { + type: { summary: '{ title: string, onClick: Function }[]' }, + }, + }, + }, +}; + +export default meta; + +export const Default = { + args: { + children: __( 'This block ran into an issue.' ), + }, +}; + +export const WithActions = { + args: { + ...Default.args, + actions: [ + <Button key="fix-issue" __next40pxDefaultSize variant="primary"> + { __( 'Fix issue' ) } + </Button>, + ], + }, +}; + +export const WithSecondaryActions = { + args: { + ...Default.args, + secondaryActions: [ + { title: __( 'Get help' ) }, + { title: __( 'Remove block' ) }, + ], + }, +}; diff --git a/packages/block-editor/src/hooks/border.js b/packages/block-editor/src/hooks/border.js index 14b3dbf7669b3..4ab4c69a41f31 100644 --- a/packages/block-editor/src/hooks/border.js +++ b/packages/block-editor/src/hooks/border.js @@ -31,7 +31,7 @@ import { import { store as blockEditorStore } from '../store'; import { __ } from '@wordpress/i18n'; -export const BORDER_SUPPORT_KEY = 'border'; +export const BORDER_SUPPORT_KEY = '__experimentalBorder'; export const SHADOW_SUPPORT_KEY = 'shadow'; const getColorByProperty = ( colors, property, value ) => { @@ -161,8 +161,14 @@ export function BorderPanel( { clientId, name, setAttributes, settings } ) { } const defaultControls = { - ...getBlockSupport( name, [ BORDER_SUPPORT_KEY, 'defaultControls' ] ), - ...getBlockSupport( name, [ SHADOW_SUPPORT_KEY, 'defaultControls' ] ), + ...getBlockSupport( name, [ + BORDER_SUPPORT_KEY, + '__experimentalDefaultControls', + ] ), + ...getBlockSupport( name, [ + SHADOW_SUPPORT_KEY, + '__experimentalDefaultControls', + ] ), }; return ( diff --git a/packages/block-editor/src/hooks/color.js b/packages/block-editor/src/hooks/color.js index 2fecc10a31198..ef8984c936785 100644 --- a/packages/block-editor/src/hooks/color.js +++ b/packages/block-editor/src/hooks/color.js @@ -290,7 +290,7 @@ export function ColorEdit( { clientId, name, setAttributes, settings } ) { const defaultControls = getBlockSupport( name, [ COLOR_SUPPORT_KEY, - 'defaultControls', + '__experimentalDefaultControls', ] ); const enableContrastChecking = diff --git a/packages/block-editor/src/hooks/dimensions.js b/packages/block-editor/src/hooks/dimensions.js index c98cc34e4272c..ffa4048b7740e 100644 --- a/packages/block-editor/src/hooks/dimensions.js +++ b/packages/block-editor/src/hooks/dimensions.js @@ -88,11 +88,11 @@ export function DimensionsPanel( { clientId, name, setAttributes, settings } ) { const defaultDimensionsControls = getBlockSupport( name, [ DIMENSIONS_SUPPORT_KEY, - 'defaultControls', + '__experimentalDefaultControls', ] ); const defaultSpacingControls = getBlockSupport( name, [ SPACING_SUPPORT_KEY, - 'defaultControls', + '__experimentalDefaultControls', ] ); const defaultControls = { ...defaultDimensionsControls, diff --git a/packages/block-editor/src/hooks/font-family.js b/packages/block-editor/src/hooks/font-family.js index e5d8e02ab8ec0..ba9a66a8bcf04 100644 --- a/packages/block-editor/src/hooks/font-family.js +++ b/packages/block-editor/src/hooks/font-family.js @@ -13,7 +13,7 @@ import { shouldSkipSerialization } from './utils'; import { TYPOGRAPHY_SUPPORT_KEY } from './typography'; import { unlock } from '../lock-unlock'; -export const FONT_FAMILY_SUPPORT_KEY = 'typography.fontFamily'; +export const FONT_FAMILY_SUPPORT_KEY = 'typography.__experimentalFontFamily'; const { kebabCase } = unlock( componentsPrivateApis ); /** diff --git a/packages/block-editor/src/hooks/index.native.js b/packages/block-editor/src/hooks/index.native.js index c7f9df868f2bd..0e4c2aa276fd4 100644 --- a/packages/block-editor/src/hooks/index.native.js +++ b/packages/block-editor/src/hooks/index.native.js @@ -33,3 +33,4 @@ export { getColorClassesAndStyles, useColorProps } from './use-color-props'; export { getSpacingClassesAndStyles } from './use-spacing-props'; export { useCachedTruthy } from './use-cached-truthy'; export { useEditorWrapperStyles } from './use-editor-wrapper-styles'; +export { getTypographyClassesAndStyles } from './use-typography-props'; diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index 5be2b1b3fd40a..998d13cfd2224 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -98,16 +98,22 @@ function addAttribute( settings ) { * @type {Record<string, string[]>} */ const skipSerializationPathsEdit = { - [ `${ BORDER_SUPPORT_KEY }.skipSerialization` ]: [ 'border' ], - [ `${ COLOR_SUPPORT_KEY }.skipSerialization` ]: [ COLOR_SUPPORT_KEY ], - [ `${ TYPOGRAPHY_SUPPORT_KEY }.skipSerialization` ]: [ + [ `${ BORDER_SUPPORT_KEY }.__experimentalSkipSerialization` ]: [ 'border' ], + [ `${ COLOR_SUPPORT_KEY }.__experimentalSkipSerialization` ]: [ + COLOR_SUPPORT_KEY, + ], + [ `${ TYPOGRAPHY_SUPPORT_KEY }.__experimentalSkipSerialization` ]: [ TYPOGRAPHY_SUPPORT_KEY, ], - [ `${ DIMENSIONS_SUPPORT_KEY }.skipSerialization` ]: [ + [ `${ DIMENSIONS_SUPPORT_KEY }.__experimentalSkipSerialization` ]: [ DIMENSIONS_SUPPORT_KEY, ], - [ `${ SPACING_SUPPORT_KEY }.skipSerialization` ]: [ SPACING_SUPPORT_KEY ], - [ `${ SHADOW_SUPPORT_KEY }.skipSerialization` ]: [ SHADOW_SUPPORT_KEY ], + [ `${ SPACING_SUPPORT_KEY }.__experimentalSkipSerialization` ]: [ + SPACING_SUPPORT_KEY, + ], + [ `${ SHADOW_SUPPORT_KEY }.__experimentalSkipSerialization` ]: [ + SHADOW_SUPPORT_KEY, + ], }; /** @@ -245,7 +251,7 @@ export function omitStyle( style, paths, preserveReference = false ) { let newStyle = style; if ( ! preserveReference ) { - newStyle = structuredClone( style ); + newStyle = JSON.parse( JSON.stringify( style ) ); } if ( ! Array.isArray( paths ) ) { diff --git a/packages/block-editor/src/hooks/supports.js b/packages/block-editor/src/hooks/supports.js index 102b78bbb96e6..75f2bdf2dc219 100644 --- a/packages/block-editor/src/hooks/supports.js +++ b/packages/block-editor/src/hooks/supports.js @@ -6,20 +6,20 @@ import { Platform } from '@wordpress/element'; const ALIGN_SUPPORT_KEY = 'align'; const ALIGN_WIDE_SUPPORT_KEY = 'alignWide'; -const BORDER_SUPPORT_KEY = 'border'; +const BORDER_SUPPORT_KEY = '__experimentalBorder'; const COLOR_SUPPORT_KEY = 'color'; const CUSTOM_CLASS_NAME_SUPPORT_KEY = 'customClassName'; -const FONT_FAMILY_SUPPORT_KEY = 'typography.fontFamily'; +const FONT_FAMILY_SUPPORT_KEY = 'typography.__experimentalFontFamily'; const FONT_SIZE_SUPPORT_KEY = 'typography.fontSize'; const LINE_HEIGHT_SUPPORT_KEY = 'typography.lineHeight'; /** * Key within block settings' support array indicating support for font style. */ -const FONT_STYLE_SUPPORT_KEY = 'typography.fontStyle'; +const FONT_STYLE_SUPPORT_KEY = 'typography.__experimentalFontStyle'; /** * Key within block settings' support array indicating support for font weight. */ -const FONT_WEIGHT_SUPPORT_KEY = 'typography.fontWeight'; +const FONT_WEIGHT_SUPPORT_KEY = 'typography.__experimentalFontWeight'; /** * Key within block settings' supports array indicating support for text * align e.g. settings found in `block.json`. @@ -34,7 +34,7 @@ const TEXT_COLUMNS_SUPPORT_KEY = 'typography.textColumns'; * Key within block settings' supports array indicating support for text * decorations e.g. settings found in `block.json`. */ -const TEXT_DECORATION_SUPPORT_KEY = 'typography.textDecoration'; +const TEXT_DECORATION_SUPPORT_KEY = 'typography.__experimentalTextDecoration'; /** * Key within block settings' supports array indicating support for writing mode * e.g. settings found in `block.json`. @@ -44,13 +44,13 @@ const WRITING_MODE_SUPPORT_KEY = 'typography.__experimentalWritingMode'; * Key within block settings' supports array indicating support for text * transforms e.g. settings found in `block.json`. */ -const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.textTransform'; +const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.__experimentalTextTransform'; /** * Key within block settings' supports array indicating support for letter-spacing * e.g. settings found in `block.json`. */ -const LETTER_SPACING_SUPPORT_KEY = 'typography.letterSpacing'; +const LETTER_SPACING_SUPPORT_KEY = 'typography.__experimentalLetterSpacing'; const LAYOUT_SUPPORT_KEY = 'layout'; const TYPOGRAPHY_SUPPORT_KEYS = [ LINE_HEIGHT_SUPPORT_KEY, diff --git a/packages/block-editor/src/hooks/test/style.js b/packages/block-editor/src/hooks/test/style.js index 40e7169194b82..2cfe299b8c8d9 100644 --- a/packages/block-editor/src/hooks/test/style.js +++ b/packages/block-editor/src/hooks/test/style.js @@ -133,7 +133,8 @@ describe( 'addSaveProps', () => { const applySkipSerialization = ( features ) => { const updatedSettings = { ...blockSettings }; Object.keys( features ).forEach( ( key ) => { - updatedSettings.supports[ key ].skipSerialization = features[ key ]; + updatedSettings.supports[ key ].__experimentalSkipSerialization = + features[ key ]; } ); return updatedSettings; }; diff --git a/packages/block-editor/src/hooks/typography.js b/packages/block-editor/src/hooks/typography.js index 160894eac4e61..cf3f4327c8f03 100644 --- a/packages/block-editor/src/hooks/typography.js +++ b/packages/block-editor/src/hooks/typography.js @@ -27,12 +27,12 @@ function omit( object, keys ) { ); } -const LETTER_SPACING_SUPPORT_KEY = 'typography.letterSpacing'; -const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.textTransform'; -const TEXT_DECORATION_SUPPORT_KEY = 'typography.textDecoration'; +const LETTER_SPACING_SUPPORT_KEY = 'typography.__experimentalLetterSpacing'; +const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.__experimentalTextTransform'; +const TEXT_DECORATION_SUPPORT_KEY = 'typography.__experimentalTextDecoration'; const TEXT_COLUMNS_SUPPORT_KEY = 'typography.textColumns'; -const FONT_STYLE_SUPPORT_KEY = 'typography.fontStyle'; -const FONT_WEIGHT_SUPPORT_KEY = 'typography.fontWeight'; +const FONT_STYLE_SUPPORT_KEY = 'typography.__experimentalFontStyle'; +const FONT_WEIGHT_SUPPORT_KEY = 'typography.__experimentalFontWeight'; const WRITING_MODE_SUPPORT_KEY = 'typography.__experimentalWritingMode'; export const TYPOGRAPHY_SUPPORT_KEY = 'typography'; export const TYPOGRAPHY_SUPPORT_KEYS = [ @@ -133,7 +133,7 @@ export function TypographyPanel( { clientId, name, setAttributes, settings } ) { const defaultControls = getBlockSupport( name, [ TYPOGRAPHY_SUPPORT_KEY, - 'defaultControls', + '__experimentalDefaultControls', ] ); return ( diff --git a/packages/block-editor/src/hooks/use-zoom-out.js b/packages/block-editor/src/hooks/use-zoom-out.js index adcea8b605aeb..5c37822eba4b3 100644 --- a/packages/block-editor/src/hooks/use-zoom-out.js +++ b/packages/block-editor/src/hooks/use-zoom-out.js @@ -2,13 +2,14 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useEffect, useRef } from '@wordpress/element'; +import { useEffect, useRef, useContext } from '@wordpress/element'; /** * Internal dependencies */ import { store as blockEditorStore } from '../store'; import { unlock } from '../lock-unlock'; +import BlockContext from '../components/block-context'; /** * A hook used to set the editor mode to zoomed out mode, invoking the hook sets the mode. @@ -19,6 +20,7 @@ import { unlock } from '../lock-unlock'; * @param {boolean} enabled If we should enter into zoomOut mode or not */ export function useZoomOut( enabled = true ) { + const { postId } = useContext( BlockContext ); const { setZoomLevel, resetZoomLevel } = unlock( useDispatch( blockEditorStore ) ); @@ -37,6 +39,7 @@ export function useZoomOut( enabled = true ) { const controlZoomLevelRef = useRef( false ); const isEnabledRef = useRef( enabled ); + const postIdRef = useRef( postId ); /** * This hook tracks if the zoom state was changed manually by the user via clicking @@ -55,6 +58,11 @@ export function useZoomOut( enabled = true ) { useEffect( () => { isEnabledRef.current = enabled; + // If the user created a new post/page, we should take control of the zoom level. + if ( postIdRef.current !== postId ) { + controlZoomLevelRef.current = true; + } + if ( enabled !== isZoomOut() ) { controlZoomLevelRef.current = true; @@ -71,5 +79,5 @@ export function useZoomOut( enabled = true ) { resetZoomLevel(); } }; - }, [ enabled, isZoomOut, resetZoomLevel, setZoomLevel ] ); + }, [ enabled, isZoomOut, postId, resetZoomLevel, setZoomLevel ] ); } diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index ac6e55efe4d3b..4334f70b9d13b 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -124,7 +124,7 @@ export function shouldSkipSerialization( feature ) { const support = getBlockSupport( blockNameOrType, featureSet ); - const skipSerialization = support?.skipSerialization; + const skipSerialization = support?.__experimentalSkipSerialization; if ( Array.isArray( skipSerialization ) ) { return skipSerialization.includes( feature ); diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index c46778d889b3e..72b87a59e8f57 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -502,13 +502,23 @@ export const getParentSectionBlock = ( state, clientId ) => { * @return {boolean} Whether the block is a content locking parent. */ export function isSectionBlock( state, clientId ) { + const blockName = getBlockName( state, clientId ); + if ( + blockName === 'core/block' || + getTemplateLock( state, clientId ) === 'contentOnly' + ) { + return true; + } + + // Template parts become sections in navigation mode. + const _isNavigationMode = isNavigationMode( state ); + if ( _isNavigationMode && blockName === 'core/template-part' ) { + return true; + } + const sectionRootClientId = getSectionRootClientId( state ); const sectionClientIds = getBlockOrder( state, sectionRootClientId ); - return ( - getBlockName( state, clientId ) === 'core/block' || - getTemplateLock( state, clientId ) === 'contentOnly' || - ( isNavigationMode( state ) && sectionClientIds.includes( clientId ) ) - ); + return _isNavigationMode && sectionClientIds.includes( clientId ); } /** diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index edae9c392c37d..fc3803462d892 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1964,8 +1964,14 @@ export function temporarilyEditingFocusModeRevert( state = '', action ) { export function blockEditingModes( state = new Map(), action ) { switch ( action.type ) { case 'SET_BLOCK_EDITING_MODE': + if ( state.get( action.clientId ) === action.mode ) { + return state; + } return new Map( state ).set( action.clientId, action.mode ); case 'UNSET_BLOCK_EDITING_MODE': { + if ( ! state.has( action.clientId ) ) { + return state; + } const newState = new Map( state ); newState.delete( action.clientId ); return newState; @@ -2186,19 +2192,19 @@ function getBlockTreeBlock( state, clientId ) { * The callback receives the current block as its argument. */ function traverseBlockTree( state, clientId, callback ) { - const parentTree = getBlockTreeBlock( state, clientId ); - if ( ! parentTree ) { + const tree = getBlockTreeBlock( state, clientId ); + if ( ! tree ) { return; } - callback( parentTree ); + callback( tree ); - if ( ! parentTree?.innerBlocks?.length ) { + if ( ! tree?.innerBlocks?.length ) { return; } - for ( const block of parentTree?.innerBlocks ) { - traverseBlockTree( state, block.clientId, callback ); + for ( const innerBlock of tree?.innerBlocks ) { + traverseBlockTree( state, innerBlock.clientId, callback ); } } @@ -2212,8 +2218,12 @@ function traverseBlockTree( state, clientId, callback ) { * @return {string|undefined} The client ID of the parent block if found, undefined otherwise. */ function findParentInClientIdsList( state, clientId, clientIds ) { + if ( ! clientIds.length ) { + return; + } + let parent = state.blocks.parents.get( clientId ); - while ( parent ) { + while ( parent !== undefined ) { if ( clientIds.includes( parent ) ) { return parent; } @@ -2258,15 +2268,65 @@ function getDerivedBlockEditingModesForTree( // so the default block editing mode is set to disabled. const sectionRootClientId = state.settings?.[ sectionRootClientIdKey ]; const sectionClientIds = state.blocks.order.get( sectionRootClientId ); - const syncedPatternClientIds = Object.keys( - state.blocks.controlledInnerBlocks - ).filter( - ( clientId ) => - state.blocks.byClientId?.get( clientId )?.name === 'core/block' + const hasDisabledBlocks = Array.from( state.blockEditingModes ).some( + ( [ , mode ] ) => mode === 'disabled' ); + const templatePartClientIds = []; + const syncedPatternClientIds = []; + + Object.keys( state.blocks.controlledInnerBlocks ).forEach( ( clientId ) => { + const block = state.blocks.byClientId?.get( clientId ); + + if ( block?.name === 'core/template-part' ) { + templatePartClientIds.push( clientId ); + } + + if ( block?.name === 'core/block' ) { + syncedPatternClientIds.push( clientId ); + } + } ); traverseBlockTree( state, treeClientId, ( block ) => { const { clientId, name: blockName } = block; + + // If the block already has an explicit block editing mode set, + // don't override it. + if ( state.blockEditingModes.has( clientId ) ) { + return; + } + + // Disabled explicit block editing modes are inherited by children. + // It's an expensive calculation, so only do it if there are disabled blocks. + if ( hasDisabledBlocks ) { + // Look through parents to find one with an explicit block editing mode. + let ancestorBlockEditingMode; + let parent = state.blocks.parents.get( clientId ); + while ( parent !== undefined ) { + // There's a chance we only just calculated this for the parent, + // if so we can return that value for a faster lookup. + if ( derivedBlockEditingModes.has( parent ) ) { + ancestorBlockEditingMode = + derivedBlockEditingModes.get( parent ); + } else if ( state.blockEditingModes.has( parent ) ) { + // Checking the explicit block editing mode will be slower, + // as the block editing mode is more likely to be set on a + // distant ancestor. + ancestorBlockEditingMode = + state.blockEditingModes.get( parent ); + } + if ( ancestorBlockEditingMode ) { + break; + } + parent = state.blocks.parents.get( parent ); + } + + // If the ancestor block editing mode is disabled, it's inherited by the child. + if ( ancestorBlockEditingMode === 'disabled' ) { + derivedBlockEditingModes.set( clientId, 'disabled' ); + return; + } + } + if ( isZoomedOut || isNavMode ) { // If the root block is the section root set its editing mode to contentOnly. if ( clientId === sectionRootClientId ) { @@ -2287,15 +2347,41 @@ function getDerivedBlockEditingModesForTree( // If zoomed out, all blocks that aren't sections or the section root are // disabled. - // If the tree root is not in a section, set its editing mode to disabled. - if ( - isZoomedOut || - ! findParentInClientIdsList( state, clientId, sectionClientIds ) - ) { + if ( isZoomedOut ) { derivedBlockEditingModes.set( clientId, 'disabled' ); return; } + const isInSection = !! findParentInClientIdsList( + state, + clientId, + sectionClientIds + ); + if ( ! isInSection ) { + if ( clientId === '' ) { + derivedBlockEditingModes.set( clientId, 'disabled' ); + return; + } + + // Allow selection of template parts outside of sections. + if ( blockName === 'core/template-part' ) { + derivedBlockEditingModes.set( clientId, 'contentOnly' ); + return; + } + + const isInTemplatePart = !! findParentInClientIdsList( + state, + clientId, + templatePartClientIds + ); + // Allow contentOnly blocks in template parts outside of sections + // to be editable. Only disable blocks that don't fit this criteria. + if ( ! isInTemplatePart && ! isContentBlock( blockName ) ) { + derivedBlockEditingModes.set( clientId, 'disabled' ); + return; + } + } + // Handle synced pattern content so the inner blocks of a synced pattern are // properly disabled. if ( syncedPatternClientIds.length ) { @@ -2560,11 +2646,16 @@ export function withDerivedBlockEditingModes( reducer ) { } break; } + case 'SET_BLOCK_EDITING_MODE': + case 'UNSET_BLOCK_EDITING_MODE': case 'SET_HAS_CONTROLLED_INNER_BLOCKS': { - const updatedBlock = nextState.blocks.tree.get( + const updatedBlock = getBlockTreeBlock( + nextState, action.clientId ); - // The block might have been removed. + + // The block might have been removed in which case it'll be + // handled by the `REMOVE_BLOCKS` action. if ( ! updatedBlock ) { break; } @@ -2573,6 +2664,7 @@ export function withDerivedBlockEditingModes( reducer ) { getDerivedBlockEditingModesUpdates( { prevState: state, nextState, + removedClientIds: [ action.clientId ], addedBlocks: [ updatedBlock ], isNavMode: false, } ); @@ -2580,6 +2672,7 @@ export function withDerivedBlockEditingModes( reducer ) { getDerivedBlockEditingModesUpdates( { prevState: state, nextState, + removedClientIds: [ action.clientId ], addedBlocks: [ updatedBlock ], isNavMode: true, } ); diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 7f8ddc7a4be31..31ee6778da8d0 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -3087,9 +3087,7 @@ export const getBlockEditingMode = createRegistrySelector( const isContent = hasContentRoleAttribute( name ); return isContent ? 'contentOnly' : 'disabled'; } - // Otherwise, check if there's an ancestor that is contentOnly - const parentMode = getBlockEditingMode( state, rootClientId ); - return parentMode === 'contentOnly' ? 'default' : parentMode; + return 'default'; } ); diff --git a/packages/block-editor/src/store/test/private-selectors.js b/packages/block-editor/src/store/test/private-selectors.js index 268d463f227d4..07c133dbacafe 100644 --- a/packages/block-editor/src/store/test/private-selectors.js +++ b/packages/block-editor/src/store/test/private-selectors.js @@ -122,6 +122,7 @@ describe( 'private selectors', () => { '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': {}, }, blockEditingModes: new Map( [] ), + derivedBlockEditingModes: new Map( [] ), }; const hasContentRoleAttribute = jest.fn( () => false ); @@ -142,6 +143,7 @@ describe( 'private selectors', () => { const state = { ...baseState, blockEditingModes: new Map( [] ), + derivedBlockEditingModes: new Map( [] ), }; expect( isBlockSubtreeDisabled( @@ -157,6 +159,12 @@ describe( 'private selectors', () => { blockEditingModes: new Map( [ [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], ] ), + derivedBlockEditingModes: new Map( [ + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + ] ), }; expect( isBlockSubtreeDisabled( @@ -166,10 +174,18 @@ describe( 'private selectors', () => { ).toBe( true ); } ); - it( 'should return true when top level block is disabled via inheritence and there are no editing modes within it', () => { + it( 'should return true when top level block is disabled via inheritance and there are no editing modes within it', () => { const state = { ...baseState, blockEditingModes: new Map( [ [ '', 'disabled' ] ] ), + derivedBlockEditingModes: new Map( [ + [ '6cf70164-9097-4460-bcbf-200560546988', 'disabled' ], + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + ] ), }; expect( isBlockSubtreeDisabled( @@ -186,6 +202,11 @@ describe( 'private selectors', () => { [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], ] ), + derivedBlockEditingModes: new Map( [ + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + ] ), }; expect( isBlockSubtreeDisabled( @@ -202,6 +223,11 @@ describe( 'private selectors', () => { [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'default' ], ] ), + derivedBlockEditingModes: new Map( [ + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + ] ), }; expect( isBlockSubtreeDisabled( @@ -218,6 +244,13 @@ describe( 'private selectors', () => { [ '', 'disabled' ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'default' ], ] ), + derivedBlockEditingModes: new Map( [ + [ '6cf70164-9097-4460-bcbf-200560546988', 'disabled' ], + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + ] ), }; expect( isBlockSubtreeDisabled( @@ -303,6 +336,7 @@ describe( 'private selectors', () => { const state = { ...baseState, blockEditingModes: new Map( [] ), + derivedBlockEditingModes: new Map( [] ), }; expect( getEnabledClientIdsTree( state ) ).toEqual( [ { @@ -340,6 +374,7 @@ describe( 'private selectors', () => { const state = { ...baseState, blockEditingModes: new Map( [] ), + derivedBlockEditingModes: new Map( [] ), }; expect( getEnabledClientIdsTree( @@ -375,6 +410,10 @@ describe( 'private selectors', () => { [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'contentOnly' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'contentOnly' ], ] ), + derivedBlockEditingModes: new Map( [ + [ '6cf70164-9097-4460-bcbf-200560546988', 'disabled' ], + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + ] ), }; expect( getEnabledClientIdsTree( state ) ).toEqual( [ { @@ -412,6 +451,7 @@ describe( 'private selectors', () => { ] ), }, blockEditingModes: new Map(), + derivedBlockEditingModes: new Map(), }; expect( getEnabledBlockParents( @@ -433,7 +473,7 @@ describe( 'private selectors', () => { ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', - '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', ], [ '4c2b7140-fffd-44b4-b2a7-820c670a6514', @@ -442,6 +482,7 @@ describe( 'private selectors', () => { ] ), order: new Map( [ + [ '', [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ] ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', [ @@ -453,12 +494,15 @@ describe( 'private selectors', () => { 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', [ '4c2b7140-fffd-44b4-b2a7-820c670a6514' ], ], - [ '', [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ] ], ] ), }, blockEditingModes: new Map( [ [ '', 'disabled' ], - [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'default' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'default' ], + ] ), + derivedBlockEditingModes: new Map( [ + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], ] ), blockListSettings: {}, }; @@ -467,10 +511,7 @@ describe( 'private selectors', () => { state, '4c2b7140-fffd-44b4-b2a7-820c670a6514' ) - ).toEqual( [ - '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', - 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', - ] ); + ).toEqual( [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c' ] ); } ); it( 'should order from bottom to top if ascending is true', () => { @@ -493,6 +534,7 @@ describe( 'private selectors', () => { ], ] ), order: new Map( [ + [ '', [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ] ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f' ], @@ -505,13 +547,15 @@ describe( 'private selectors', () => { 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', [ '4c2b7140-fffd-44b4-b2a7-820c670a6514' ], ], - [ '', [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ] ], ] ), }, blockEditingModes: new Map( [ [ '', 'disabled' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'default' ], ] ), + derivedBlockEditingModes: new Map( [ + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + ] ), blockListSettings: {}, }; expect( diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index dd1665d6736ad..6706ff2fbb59e 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -12,8 +12,7 @@ import { createBlock, privateApis, } from '@wordpress/blocks'; -import { combineReducers, select } from '@wordpress/data'; -import { store as preferencesStore } from '@wordpress/preferences'; +import { combineReducers } from '@wordpress/data'; /** * Internal dependencies @@ -3576,6 +3575,7 @@ describe( 'state', () => { blocks, settings, zoomLevel, + blockEditingModes, } ) ); @@ -3598,15 +3598,6 @@ describe( 'state', () => { describe( 'edit mode', () => { let initialState; beforeAll( () => { - select.mockImplementation( ( storeName ) => { - if ( storeName === preferencesStore ) { - return { - get: jest.fn( () => 'edit' ), - }; - } - return select( storeName ); - } ); - initialState = dispatchActions( [ { @@ -3651,10 +3642,6 @@ describe( 'state', () => { ); } ); - afterAll( () => { - select.mockRestore(); - } ); - it( 'returns no block editing modes when zoomed out / navigation mode are not active and there are no synced patterns', () => { expect( initialState.derivedBlockEditingModes ).toEqual( new Map() @@ -3665,15 +3652,6 @@ describe( 'state', () => { describe( 'synced patterns', () => { let initialState; beforeAll( () => { - select.mockImplementation( ( storeName ) => { - if ( storeName === preferencesStore ) { - return { - get: jest.fn( () => 'edit' ), - }; - } - return select( storeName ); - } ); - // Simulates how the editor typically inserts controlled blocks, // - first the pattern is inserted with no inner blocks. // - next the pattern is marked as a controlled block. @@ -3818,10 +3796,6 @@ describe( 'state', () => { ); } ); - afterAll( () => { - select.mockRestore(); - } ); - it( 'returns the expected block editing modes for synced patterns', () => { // Only the parent pattern and its own children that have bindings // are in contentOnly mode. All other blocks are disabled. @@ -3840,60 +3814,8 @@ describe( 'state', () => { ); } ); - it( 'removes block editing modes when synced patterns are removed', () => { - const { derivedBlockEditingModes } = dispatchActions( - [ - { - type: 'REMOVE_BLOCKS', - clientIds: [ 'root-pattern' ], - }, - ], - testReducer, - initialState - ); - - expect( derivedBlockEditingModes ).toEqual( new Map() ); - } ); - - it( 'returns the expected block editing modes for synced patterns when switching to navigation mode', () => { - select.mockImplementation( ( storeName ) => { - if ( storeName === preferencesStore ) { - return { - get: jest.fn( () => 'navigation' ), - }; - } - return select( storeName ); - } ); - - const { - derivedBlockEditingModes, - derivedNavModeBlockEditingModes, - } = dispatchActions( - [ - { - type: 'SET_EDITOR_MODE', - mode: 'navigation', - }, - ], - testReducer, - initialState - ); - - expect( derivedBlockEditingModes ).toEqual( - new Map( - Object.entries( { - 'pattern-paragraph': 'disabled', - 'pattern-group': 'disabled', - 'pattern-paragraph-with-overrides': 'contentOnly', // Pattern child with bindings. - 'nested-pattern': 'disabled', - 'nested-paragraph': 'disabled', - 'nested-group': 'disabled', - 'nested-paragraph-with-overrides': 'disabled', - } ) - ) - ); - - expect( derivedNavModeBlockEditingModes ).toEqual( + it( 'returns the expected block editing modes for synced patterns in navigation mode', () => { + expect( initialState.derivedNavModeBlockEditingModes ).toEqual( new Map( Object.entries( { '': 'contentOnly', // Section root. @@ -3912,15 +3834,21 @@ describe( 'state', () => { } ) ) ); + } ); - select.mockImplementation( ( storeName ) => { - if ( storeName === preferencesStore ) { - return { - get: jest.fn( () => 'edit' ), - }; - } - return select( storeName ); - } ); + it( 'removes block editing modes when synced patterns are removed', () => { + const { derivedBlockEditingModes } = dispatchActions( + [ + { + type: 'REMOVE_BLOCKS', + clientIds: [ 'root-pattern' ], + }, + ], + testReducer, + initialState + ); + + expect( derivedBlockEditingModes ).toEqual( new Map() ); } ); it( 'returns the expected block editing modes for synced patterns when switching to zoomed out mode', () => { @@ -3961,52 +3889,104 @@ describe( 'state', () => { let initialState; beforeAll( () => { - select.mockImplementation( ( storeName ) => { - if ( storeName === preferencesStore ) { - return { - get: jest.fn( () => 'navigation' ), - }; - } - return select( storeName ); - } ); - initialState = dispatchActions( [ { type: 'UPDATE_SETTINGS', settings: { - [ sectionRootClientIdKey ]: '', + [ sectionRootClientIdKey ]: 'section-root', }, }, { type: 'RESET_BLOCKS', blocks: [ + { + name: 'core/template-part', + clientId: 'header', + attributes: {}, + innerBlocks: [], + }, { name: 'core/group', - clientId: 'group-1', + clientId: 'section-root', attributes: {}, innerBlocks: [ - { - name: 'core/paragraph', - clientId: 'paragraph-1', - attributes: {}, - innerBlocks: [], - }, { name: 'core/group', - clientId: 'group-2', + clientId: 'group-1', attributes: {}, innerBlocks: [ { name: 'core/paragraph', - clientId: 'paragraph-2', + clientId: 'paragraph-1', attributes: {}, innerBlocks: [], }, + { + name: 'core/group', + clientId: 'group-2', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: + 'paragraph-2', + attributes: {}, + innerBlocks: [], + }, + ], + }, ], }, ], }, + { + name: 'core/template-part', + clientId: 'footer', + attributes: {}, + innerBlocks: [], + }, + ], + }, + { + type: 'SET_HAS_CONTROLLED_INNER_BLOCKS', + clientId: 'header', + hasControlledInnerBlocks: true, + }, + { + type: 'REPLACE_INNER_BLOCKS', + rootClientId: 'header', + blocks: [ + { + name: 'core/group', + clientId: 'header-group', + attributes: {}, + innerBlocks: [ + { + name: 'core/paragraph', + clientId: 'header-paragraph', + attributes: {}, + innerBlocks: [], + }, + ], + }, + ], + }, + { + type: 'SET_HAS_CONTROLLED_INNER_BLOCKS', + clientId: 'footer', + hasControlledInnerBlocks: true, + }, + { + type: 'REPLACE_INNER_BLOCKS', + rootClientId: 'footer', + blocks: [ + { + name: 'core/paragraph', + clientId: 'footer-paragraph', + attributes: {}, + innerBlocks: [], + }, ], }, ], @@ -4014,15 +3994,17 @@ describe( 'state', () => { ); } ); - afterAll( () => { - select.mockRestore(); - } ); - it( 'returns the expected block editing modes', () => { expect( initialState.derivedNavModeBlockEditingModes ).toEqual( new Map( Object.entries( { - '': 'contentOnly', // Section root. + '': 'disabled', + header: 'contentOnly', // Template part. + 'header-group': 'disabled', // Content block in template part. + 'header-paragraph': 'contentOnly', // Content block in template part. + footer: 'contentOnly', // Template part. + 'footer-paragraph': 'contentOnly', // Content block in template part. + 'section-root': 'contentOnly', // Section root. 'group-1': 'contentOnly', // Section block. 'paragraph-1': 'contentOnly', // Content block in section. 'group-2': 'disabled', // Non-content block in section. @@ -4032,6 +4014,49 @@ describe( 'state', () => { ); } ); + it( 'allows content blocks to be disabled explicitly using the block editing mode', () => { + const { + derivedNavModeBlockEditingModes, + blockEditingModes: _blockEditingModes, + } = dispatchActions( + [ + { + type: 'SET_BLOCK_EDITING_MODE', + clientId: 'paragraph-1', + mode: 'disabled', + }, + ], + testReducer, + initialState + ); + + // Paragraph 1 is explicitly disabled and omitted from the + // derived block editing modes. + expect( _blockEditingModes ).toEqual( + new Map( + Object.entries( { + 'paragraph-1': 'disabled', + } ) + ) + ); + expect( derivedNavModeBlockEditingModes ).toEqual( + new Map( + Object.entries( { + '': 'disabled', + header: 'contentOnly', + 'header-group': 'disabled', + 'header-paragraph': 'contentOnly', + footer: 'contentOnly', + 'footer-paragraph': 'contentOnly', + 'section-root': 'contentOnly', + 'group-1': 'contentOnly', + 'group-2': 'disabled', + 'paragraph-2': 'contentOnly', + } ) + ) + ); + } ); + it( 'removes block editing modes when blocks are removed', () => { const { derivedNavModeBlockEditingModes } = dispatchActions( [ @@ -4047,7 +4072,13 @@ describe( 'state', () => { expect( derivedNavModeBlockEditingModes ).toEqual( new Map( Object.entries( { - '': 'contentOnly', + '': 'disabled', + header: 'contentOnly', // Template part. + 'header-group': 'disabled', // Content block in template part. + 'header-paragraph': 'contentOnly', // Content block in template part. + footer: 'contentOnly', // Template part. + 'footer-paragraph': 'contentOnly', // Content block in template part. + 'section-root': 'contentOnly', 'group-1': 'contentOnly', 'paragraph-1': 'contentOnly', } ) @@ -4060,7 +4091,7 @@ describe( 'state', () => { [ { type: 'INSERT_BLOCKS', - rootClientId: '', + rootClientId: 'section-root', blocks: [ { name: 'core/group', @@ -4091,7 +4122,13 @@ describe( 'state', () => { expect( derivedNavModeBlockEditingModes ).toEqual( new Map( Object.entries( { - '': 'contentOnly', // Section root. + '': 'disabled', // Section root. + header: 'contentOnly', // Template part. + 'header-group': 'disabled', // Content block in template part. + 'header-paragraph': 'contentOnly', // Content block in template part. + footer: 'contentOnly', // Template part. + 'footer-paragraph': 'contentOnly', // Content block in template part. + 'section-root': 'contentOnly', // Section root. 'group-1': 'contentOnly', // Section block. 'paragraph-1': 'contentOnly', // Content block in section. 'group-2': 'disabled', // Non-content block in section. @@ -4111,7 +4148,7 @@ describe( 'state', () => { type: 'MOVE_BLOCKS_TO_POSITION', clientIds: [ 'group-2' ], fromRootClientId: 'group-1', - toRootClientId: '', + toRootClientId: 'section-root', }, ], testReducer, @@ -4120,7 +4157,13 @@ describe( 'state', () => { expect( derivedNavModeBlockEditingModes ).toEqual( new Map( Object.entries( { - '': 'contentOnly', // Section root. + '': 'disabled', // Section root. + header: 'contentOnly', // Template part. + 'header-group': 'disabled', // Content block in template part. + 'header-paragraph': 'contentOnly', // Content block in template part. + footer: 'contentOnly', // Template part. + 'footer-paragraph': 'contentOnly', // Content block in template part. + 'section-root': 'contentOnly', // Section root. 'group-1': 'contentOnly', // Section block. 'paragraph-1': 'contentOnly', // Content block in section. 'group-2': 'contentOnly', // New section block. @@ -4148,10 +4191,16 @@ describe( 'state', () => { new Map( Object.entries( { '': 'disabled', - 'group-1': 'contentOnly', - 'paragraph-1': 'contentOnly', - 'group-2': 'contentOnly', - 'paragraph-2': 'contentOnly', + header: 'contentOnly', // Template part. + 'header-group': 'disabled', // Content block in template part. + 'header-paragraph': 'contentOnly', // Content block in template part. + footer: 'contentOnly', // Template part. + 'footer-paragraph': 'contentOnly', // Content block in template part. + 'section-root': 'disabled', + 'group-1': 'contentOnly', // New section root. + 'paragraph-1': 'contentOnly', // Section and content block + 'group-2': 'contentOnly', // Section. + 'paragraph-2': 'contentOnly', // Content block. } ) ) ); @@ -4224,49 +4273,6 @@ describe( 'state', () => { ); } ); - it( 'overrides navigation mode', () => { - select.mockImplementation( ( storeName ) => { - if ( storeName === preferencesStore ) { - return { - get: jest.fn( () => 'navigation' ), - }; - } - return select( storeName ); - } ); - - const { derivedBlockEditingModes } = dispatchActions( - [ - { - type: 'SET_EDITOR_MODE', - mode: 'navigation', - }, - ], - testReducer, - initialState - ); - - expect( derivedBlockEditingModes ).toEqual( - new Map( - Object.entries( { - '': 'contentOnly', // Section root. - 'group-1': 'contentOnly', // Section block. - 'paragraph-1': 'disabled', - 'group-2': 'disabled', - 'paragraph-2': 'disabled', - } ) - ) - ); - - select.mockImplementation( ( storeName ) => { - if ( storeName === preferencesStore ) { - return { - get: jest.fn( () => 'edit' ), - }; - } - return select( storeName ); - } ); - } ); - it( 'removes block editing modes when blocks are removed', () => { const { derivedBlockEditingModes } = dispatchActions( [ diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 51949bfd468ca..388d592787b66 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -4465,6 +4465,7 @@ describe( 'getBlockEditingMode', () => { '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': {}, }, blockEditingModes: new Map( [] ), + derivedBlockEditingModes: new Map( [] ), }; const hasContentRoleAttribute = jest.fn( () => false ); @@ -4519,6 +4520,13 @@ describe( 'getBlockEditingMode', () => { blockEditingModes: new Map( [ [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], ] ), + derivedBlockEditingModes: new Map( [ + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fed515b958s', 'disabled' ], + ] ), }; expect( getBlockEditingMode( state, 'b3247f75-fd94-4fef-97f9-5bfd162cc416' ) @@ -4545,6 +4553,12 @@ describe( 'getBlockEditingMode', () => { [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'default' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], ] ), + derivedBlockEditingModes: new Map( [ + [ '6cf70164-9097-4460-bcbf-200560546988', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fed515b958s', 'disabled' ], + ] ), }; expect( getBlockEditingMode( state, 'b3247f75-fd94-4fef-97f9-5bfd162cc416' ) @@ -4555,6 +4569,15 @@ describe( 'getBlockEditingMode', () => { const state = { ...baseState, blockEditingModes: new Map( [ [ '', 'disabled' ] ] ), + derivedBlockEditingModes: new Map( [ + [ '6cf70164-9097-4460-bcbf-200560546988', 'disabled' ], + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fed515b958s', 'disabled' ], + ] ), }; expect( getBlockEditingMode( state, 'b3247f75-fd94-4fef-97f9-5bfd162cc416' ) diff --git a/packages/block-editor/tsconfig.json b/packages/block-editor/tsconfig.json index a3c7d1ffd8807..a3a6bd18f451d 100644 --- a/packages/block-editor/tsconfig.json +++ b/packages/block-editor/tsconfig.json @@ -1,10 +1,6 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, "references": [ { "path": "../a11y" }, { "path": "../api-fetch" }, @@ -37,5 +33,6 @@ // NOTE: This package is being progressively typed. You are encouraged to // expand this array with files which can be type-checked. At some point in // the future, this can be simplified to an `includes` of `src/**/*`. - "files": [ "src/components/block-context/index.js", "src/utils/dom.js" ] + "files": [ "src/components/block-context/index.js", "src/utils/dom.js" ], + "include": [] } diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 823d89ecd854f..68631a03626d3 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 9.15.0 (2025-01-02) + ## 9.14.0 (2024-12-11) ## 9.13.0 (2024-11-27) diff --git a/packages/block-library/package.json b/packages/block-library/package.json index d7cc75bc17764..c7f0571d4aa01 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "9.14.0", + "version": "9.15.1", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -41,39 +41,39 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/autop": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interactivity": "*", - "@wordpress/interactivity-router": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/rich-text": "*", - "@wordpress/server-side-render": "*", - "@wordpress/url": "*", - "@wordpress/viewport": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/autop": "file:../autop", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interactivity": "file:../interactivity", + "@wordpress/interactivity-router": "file:../interactivity-router", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/server-side-render": "file:../server-side-render", + "@wordpress/url": "file:../url", + "@wordpress/viewport": "file:../viewport", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", diff --git a/packages/block-library/src/block/index.php b/packages/block-library/src/block/index.php index 8beef975fad6f..e8075115cabda 100644 --- a/packages/block-library/src/block/index.php +++ b/packages/block-library/src/block/index.php @@ -87,6 +87,26 @@ function render_block_core_block( $attributes ) { add_filter( 'render_block_context', $filter_block_context, 1 ); } + $ignored_hooked_blocks = get_post_meta( $attributes['ref'], '_wp_ignored_hooked_blocks', true ); + if ( ! empty( $ignored_hooked_blocks ) ) { + $ignored_hooked_blocks = json_decode( $ignored_hooked_blocks, true ); + $attributes['metadata'] = array( + 'ignoredHookedBlocks' => $ignored_hooked_blocks, + ); + } + + // Wrap in "Block" block so the Block Hooks algorithm can insert blocks + // that are hooked as first or last child of `core/block`. + $content = get_comment_delimited_block_content( + 'core/block', + $attributes, + $content + ); + // Apply Block Hooks. + $content = apply_block_hooks_to_content( $content, $reusable_block ); + // Remove block wrapper. + $content = remove_serialized_parent_block( $content ); + $content = do_blocks( $content ); unset( $seen_refs[ $attributes['ref'] ] ); diff --git a/packages/block-library/src/button/block.json b/packages/block-library/src/button/block.json index 2c1c05baa20dd..6fcb7aca4c592 100644 --- a/packages/block-library/src/button/block.json +++ b/packages/block-library/src/button/block.json @@ -85,6 +85,16 @@ } }, "typography": { + "__experimentalSkipSerialization": [ + "fontSize", + "lineHeight", + "fontFamily", + "fontWeight", + "fontStyle", + "textTransform", + "textDecoration", + "letterSpacing" + ], "fontSize": true, "lineHeight": true, "__experimentalFontFamily": true, @@ -122,7 +132,6 @@ "width": true } }, - "__experimentalSelector": ".wp-block-button .wp-block-button__link", "interactivity": { "clientNavigation": true } @@ -132,5 +141,11 @@ { "name": "outline", "label": "Outline" } ], "editorStyle": "wp-block-button-editor", - "style": "wp-block-button" + "style": "wp-block-button", + "selectors": { + "root": ".wp-block-button .wp-block-button__link", + "typography": { + "writingMode": ".wp-block-button" + } + } } diff --git a/packages/block-library/src/button/deprecated.js b/packages/block-library/src/button/deprecated.js index 8ab83e1b09518..f478c39a0dc32 100644 --- a/packages/block-library/src/button/deprecated.js +++ b/packages/block-library/src/button/deprecated.js @@ -14,6 +14,8 @@ import { __experimentalGetBorderClassesAndStyles as getBorderClassesAndStyles, __experimentalGetColorClassesAndStyles as getColorClassesAndStyles, __experimentalGetSpacingClassesAndStyles as getSpacingClassesAndStyles, + __experimentalGetShadowClassesAndStyles as getShadowClassesAndStyles, + __experimentalGetElementClassName, } from '@wordpress/block-editor'; import { compose } from '@wordpress/compose'; @@ -132,6 +134,192 @@ const blockAttributes = { }, }; +const v12 = { + attributes: { + tagName: { + type: 'string', + enum: [ 'a', 'button' ], + default: 'a', + }, + type: { + type: 'string', + default: 'button', + }, + textAlign: { + type: 'string', + }, + url: { + type: 'string', + source: 'attribute', + selector: 'a', + attribute: 'href', + }, + title: { + type: 'string', + source: 'attribute', + selector: 'a,button', + attribute: 'title', + role: 'content', + }, + text: { + type: 'rich-text', + source: 'rich-text', + selector: 'a,button', + role: 'content', + }, + linkTarget: { + type: 'string', + source: 'attribute', + selector: 'a', + attribute: 'target', + role: 'content', + }, + rel: { + type: 'string', + source: 'attribute', + selector: 'a', + attribute: 'rel', + role: 'content', + }, + placeholder: { + type: 'string', + }, + backgroundColor: { + type: 'string', + }, + textColor: { + type: 'string', + }, + gradient: { + type: 'string', + }, + width: { + type: 'number', + }, + }, + supports: { + anchor: true, + align: true, + alignWide: false, + color: { + __experimentalSkipSerialization: true, + gradients: true, + __experimentalDefaultControls: { + background: true, + text: true, + }, + }, + typography: { + fontSize: true, + lineHeight: true, + __experimentalFontFamily: true, + __experimentalFontWeight: true, + __experimentalFontStyle: true, + __experimentalTextTransform: true, + __experimentalTextDecoration: true, + __experimentalLetterSpacing: true, + __experimentalWritingMode: true, + __experimentalDefaultControls: { + fontSize: true, + }, + }, + reusable: false, + shadow: { + __experimentalSkipSerialization: true, + }, + spacing: { + __experimentalSkipSerialization: true, + padding: [ 'horizontal', 'vertical' ], + __experimentalDefaultControls: { + padding: true, + }, + }, + __experimentalBorder: { + color: true, + radius: true, + style: true, + width: true, + __experimentalSkipSerialization: true, + __experimentalDefaultControls: { + color: true, + radius: true, + style: true, + width: true, + }, + }, + __experimentalSelector: '.wp-block-button__link', + interactivity: { + clientNavigation: true, + }, + }, + save( { attributes, className } ) { + const { + tagName, + type, + textAlign, + fontSize, + linkTarget, + rel, + style, + text, + title, + url, + width, + } = attributes; + + const TagName = tagName || 'a'; + const isButtonTag = 'button' === TagName; + const buttonType = type || 'button'; + const borderProps = getBorderClassesAndStyles( attributes ); + const colorProps = getColorClassesAndStyles( attributes ); + const spacingProps = getSpacingClassesAndStyles( attributes ); + const shadowProps = getShadowClassesAndStyles( attributes ); + const buttonClasses = clsx( + 'wp-block-button__link', + colorProps.className, + borderProps.className, + { + [ `has-text-align-${ textAlign }` ]: textAlign, + // For backwards compatibility add style that isn't provided via + // block support. + 'no-border-radius': style?.border?.radius === 0, + }, + __experimentalGetElementClassName( 'button' ) + ); + const buttonStyle = { + ...borderProps.style, + ...colorProps.style, + ...spacingProps.style, + ...shadowProps.style, + }; + + // The use of a `title` attribute here is soft-deprecated, but still applied + // if it had already been assigned, for the sake of backward-compatibility. + // A title will no longer be assigned for new or updated button block links. + + const wrapperClasses = clsx( className, { + [ `has-custom-width wp-block-button__width-${ width }` ]: width, + [ `has-custom-font-size` ]: fontSize || style?.typography?.fontSize, + } ); + + return ( + <div { ...useBlockProps.save( { className: wrapperClasses } ) }> + <RichText.Content + tagName={ TagName } + type={ isButtonTag ? buttonType : null } + className={ buttonClasses } + href={ isButtonTag ? null : url } + title={ title } + style={ buttonStyle } + value={ text } + target={ isButtonTag ? null : linkTarget } + rel={ isButtonTag ? null : rel } + /> + </div> + ); + }, +}; + const v11 = { attributes: { url: { @@ -399,6 +587,7 @@ const v10 = { }; const deprecated = [ + v12, v11, v10, { diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index d00e522f5a5d2..06e10f604650e 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -14,7 +14,7 @@ import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { useEffect, useState, useRef, useMemo } from '@wordpress/element'; import { TextControl, @@ -39,6 +39,8 @@ import { __experimentalGetElementClassName, store as blockEditorStore, useBlockEditingMode, + getTypographyClassesAndStyles as useTypographyProps, + useSettings, } from '@wordpress/block-editor'; import { displayShortcut, isKeyboardEvent, ENTER } from '@wordpress/keycodes'; import { link, linkOff } from '@wordpress/icons'; @@ -125,14 +127,14 @@ function WidthPanel( { selectedWidth, setAttributes } ) { dropdownMenuProps={ dropdownMenuProps } > <ToolsPanelItem - label={ __( 'Button width' ) } + label={ __( 'Width' ) } isShownByDefault hasValue={ () => !! selectedWidth } onDeselect={ () => setAttributes( { width: undefined } ) } __nextHasNoMarginBottom > <ToggleGroupControl - label={ __( 'Button width' ) } + label={ __( 'Width' ) } value={ selectedWidth } onChange={ ( newWidth ) => setAttributes( { width: newWidth } ) @@ -146,7 +148,11 @@ function WidthPanel( { selectedWidth, setAttributes } ) { <ToggleGroupControlOption key={ widthValue } value={ widthValue } - label={ `${ widthValue }%` } + label={ sprintf( + /* translators: Percentage value. */ + __( '%1$d%%' ), + widthValue + ) } /> ); } ) } @@ -266,6 +272,19 @@ function ButtonEdit( props ) { [ context, isSelected, metadata?.bindings?.url ] ); + const [ fluidTypographySettings, layout ] = useSettings( + 'typography.fluid', + 'layout' + ); + const typographyProps = useTypographyProps( attributes, { + typography: { + fluid: fluidTypographySettings, + }, + layout: { + wideSize: layout?.wideSize, + }, + } ); + return ( <> <div @@ -273,7 +292,6 @@ function ButtonEdit( props ) { className={ clsx( blockProps.className, { [ `has-custom-width wp-block-button__width-${ width }` ]: width, - [ `has-custom-font-size` ]: blockProps.style.fontSize, } ) } > <RichText @@ -292,11 +310,14 @@ function ButtonEdit( props ) { 'wp-block-button__link', colorProps.className, borderProps.className, + typographyProps.className, { [ `has-text-align-${ textAlign }` ]: textAlign, // For backwards compatibility add style that isn't // provided via block support. 'no-border-radius': style?.border?.radius === 0, + [ `has-custom-font-size` ]: + blockProps.style.fontSize, }, __experimentalGetElementClassName( 'button' ) ) } @@ -305,6 +326,8 @@ function ButtonEdit( props ) { ...colorProps.style, ...spacingProps.style, ...shadowProps.style, + ...typographyProps.style, + writingMode: undefined, } } onReplace={ onReplace } onMerge={ mergeBlocks } diff --git a/packages/block-library/src/button/save.js b/packages/block-library/src/button/save.js index 8cb9da6fbfbc1..4255868d50fbc 100644 --- a/packages/block-library/src/button/save.js +++ b/packages/block-library/src/button/save.js @@ -14,6 +14,7 @@ import { __experimentalGetSpacingClassesAndStyles as getSpacingClassesAndStyles, __experimentalGetShadowClassesAndStyles as getShadowClassesAndStyles, __experimentalGetElementClassName, + getTypographyClassesAndStyles, } from '@wordpress/block-editor'; export default function save( { attributes, className } ) { @@ -38,15 +39,18 @@ export default function save( { attributes, className } ) { const colorProps = getColorClassesAndStyles( attributes ); const spacingProps = getSpacingClassesAndStyles( attributes ); const shadowProps = getShadowClassesAndStyles( attributes ); + const typographyProps = getTypographyClassesAndStyles( attributes ); const buttonClasses = clsx( 'wp-block-button__link', colorProps.className, borderProps.className, + typographyProps.className, { [ `has-text-align-${ textAlign }` ]: textAlign, // For backwards compatibility add style that isn't provided via // block support. 'no-border-radius': style?.border?.radius === 0, + [ `has-custom-font-size` ]: fontSize || style?.typography?.fontSize, }, __experimentalGetElementClassName( 'button' ) ); @@ -55,6 +59,8 @@ export default function save( { attributes, className } ) { ...colorProps.style, ...spacingProps.style, ...shadowProps.style, + ...typographyProps.style, + writingMode: undefined, }; // The use of a `title` attribute here is soft-deprecated, but still applied @@ -63,7 +69,6 @@ export default function save( { attributes, className } ) { const wrapperClasses = clsx( className, { [ `has-custom-width wp-block-button__width-${ width }` ]: width, - [ `has-custom-font-size` ]: fontSize || style?.typography?.fontSize, } ); return ( diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index cad79c356fe03..f454de2e5e120 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -13,6 +13,7 @@ import { ToggleControl, __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem, + __experimentalVStack as VStack, } from '@wordpress/components'; import { @@ -166,24 +167,29 @@ function ColumnInspectorControls( { hasValue={ () => count } onDeselect={ () => updateColumns( count, minCount ) } > - <RangeControl - __nextHasNoMarginBottom - __next40pxDefaultSize - label={ __( 'Columns' ) } - value={ count } - onChange={ ( value ) => - updateColumns( count, Math.max( minCount, value ) ) - } - min={ Math.max( 1, minCount ) } - max={ Math.max( 6, count ) } - /> - { count > 6 && ( - <Notice status="warning" isDismissible={ false }> - { __( - 'This column count exceeds the recommended amount and may cause visual breakage.' - ) } - </Notice> - ) } + <VStack spacing={ 4 }> + <RangeControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Columns' ) } + value={ count } + onChange={ ( value ) => + updateColumns( + count, + Math.max( minCount, value ) + ) + } + min={ Math.max( 1, minCount ) } + max={ Math.max( 6, count ) } + /> + { count > 6 && ( + <Notice status="warning" isDismissible={ false }> + { __( + 'This column count exceeds the recommended amount and may cause visual breakage.' + ) } + </Notice> + ) } + </VStack> </ToolsPanelItem> ) } <ToolsPanelItem diff --git a/packages/block-library/src/comments-pagination-next/block.json b/packages/block-library/src/comments-pagination-next/block.json index 3f7ebe677328d..22e20bfa8dbf2 100644 --- a/packages/block-library/src/comments-pagination-next/block.json +++ b/packages/block-library/src/comments-pagination-next/block.json @@ -12,11 +12,6 @@ "type": "string" } }, - "example": { - "attributes": { - "label": "Comments Next Page" - } - }, "usesContext": [ "postId", "comments/paginationArrow" ], "supports": { "reusable": false, diff --git a/packages/block-library/src/comments-pagination-next/index.js b/packages/block-library/src/comments-pagination-next/index.js index 2df0e8da6aa99..5e67bc851b179 100644 --- a/packages/block-library/src/comments-pagination-next/index.js +++ b/packages/block-library/src/comments-pagination-next/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { queryPaginationNext as icon } from '@wordpress/icons'; /** @@ -16,6 +17,11 @@ export { metadata, name }; export const settings = { icon, edit, + example: { + attributes: { + label: __( 'Newer Comments' ), + }, + }, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/comments-pagination-previous/block.json b/packages/block-library/src/comments-pagination-previous/block.json index eb5203af33c86..0871b000c569d 100644 --- a/packages/block-library/src/comments-pagination-previous/block.json +++ b/packages/block-library/src/comments-pagination-previous/block.json @@ -12,11 +12,6 @@ "type": "string" } }, - "example": { - "attributes": { - "label": "Comments Previous Page" - } - }, "usesContext": [ "postId", "comments/paginationArrow" ], "supports": { "reusable": false, diff --git a/packages/block-library/src/comments-pagination-previous/index.js b/packages/block-library/src/comments-pagination-previous/index.js index 80e555ccc79d9..975d4c0b6cbc0 100644 --- a/packages/block-library/src/comments-pagination-previous/index.js +++ b/packages/block-library/src/comments-pagination-previous/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { queryPaginationPrevious as icon } from '@wordpress/icons'; /** @@ -16,6 +17,11 @@ export { metadata, name }; export const settings = { icon, edit, + example: { + attributes: { + label: __( 'Older Comments' ), + }, + }, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/comments/index.js b/packages/block-library/src/comments/index.js index 21db8b986d6e5..b907bd41e3c6a 100644 --- a/packages/block-library/src/comments/index.js +++ b/packages/block-library/src/comments/index.js @@ -17,6 +17,7 @@ export { metadata, name }; export const settings = { icon, + example: {}, edit, save, deprecated, diff --git a/packages/block-library/src/cover/edit/index.js b/packages/block-library/src/cover/edit/index.js index ced3097320329..1eafe99e283eb 100644 --- a/packages/block-library/src/cover/edit/index.js +++ b/packages/block-library/src/cover/edit/index.js @@ -114,11 +114,18 @@ function CoverEdit( { const { __unstableMarkNextChangeAsNotPersistent } = useDispatch( blockEditorStore ); - const media = useSelect( - ( select ) => - featuredImage && - select( coreStore ).getMedia( featuredImage, { context: 'view' } ), - [ featuredImage ] + const { media } = useSelect( + ( select ) => { + return { + media: + featuredImage && useFeaturedImage + ? select( coreStore ).getMedia( featuredImage, { + context: 'view', + } ) + : undefined, + }; + }, + [ featuredImage, useFeaturedImage ] ); const mediaUrl = media?.media_details?.sizes?.[ sizeSlug ]?.source_url ?? diff --git a/packages/block-library/src/cover/test/edit.js b/packages/block-library/src/cover/test/edit.js index f5d6a5301ef6d..72f51150c2744 100644 --- a/packages/block-library/src/cover/test/edit.js +++ b/packages/block-library/src/cover/test/edit.js @@ -200,9 +200,7 @@ describe( 'Cover block', () => { await selectBlock( 'Block: Cover' ); expect( - screen.getByRole( 'heading', { - name: 'Settings', - } ) + await screen.findByRole( 'heading', { name: 'Settings' } ) ).toBeInTheDocument(); } ); } ); @@ -216,7 +214,7 @@ describe( 'Cover block', () => { ); await selectBlock( 'Block: Cover' ); await userEvent.click( - screen.getByLabelText( 'Fixed background' ) + await screen.findByLabelText( 'Fixed background' ) ); expect( screen.getByLabelText( 'Block: Cover' ) ).toHaveClass( 'has-parallax' @@ -232,7 +230,7 @@ describe( 'Cover block', () => { ); await selectBlock( 'Block: Cover' ); await userEvent.click( - screen.getByLabelText( 'Repeated background' ) + await screen.findByLabelText( 'Repeated background' ) ); expect( screen.getByLabelText( 'Block: Cover' ) ).toHaveClass( 'is-repeated' @@ -245,7 +243,7 @@ describe( 'Cover block', () => { } ); await selectBlock( 'Block: Cover' ); - await userEvent.clear( screen.getByLabelText( 'Left' ) ); + await userEvent.clear( await screen.findByLabelText( 'Left' ) ); await userEvent.type( screen.getByLabelText( 'Left' ), '100' ); expect( @@ -262,7 +260,7 @@ describe( 'Cover block', () => { await selectBlock( 'Block: Cover' ); await userEvent.type( - screen.getByLabelText( 'Alternative text' ), + await screen.findByLabelText( 'Alternative text' ), 'Me' ); expect( screen.getByAltText( 'Me' ) ).toBeInTheDocument(); diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index a16d5a6c2c69c..34be4387caad7 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -49,7 +49,6 @@ @import "./template-part/editor.scss"; @import "./text-columns/editor.scss"; @import "./video/editor.scss"; -@import "./post-template/editor.scss"; @import "./query/editor.scss"; @import "./query-pagination/editor.scss"; @import "./query-pagination-numbers/editor.scss"; diff --git a/packages/block-library/src/latest-posts/block.json b/packages/block-library/src/latest-posts/block.json index bb8c2d24962f3..58b1c6da81ca3 100644 --- a/packages/block-library/src/latest-posts/block.json +++ b/packages/block-library/src/latest-posts/block.json @@ -111,6 +111,18 @@ "fontSize": true } }, + "__experimentalBorder": { + "radius": true, + "color": true, + "width": true, + "style": true, + "__experimentalDefaultControls": { + "radius": true, + "color": true, + "width": true, + "style": true + } + }, "interactivity": { "clientNavigation": true } diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js index a946a499b26f2..820c792730311 100644 --- a/packages/block-library/src/media-text/edit.js +++ b/packages/block-library/src/media-text/edit.js @@ -76,6 +76,7 @@ function attributesFromMedia( { mediaLink: undefined, href: undefined, focalPoint: undefined, + useFeaturedImage: false, } ); return; } @@ -128,10 +129,37 @@ function attributesFromMedia( { mediaLink: media.link || undefined, href: newHref, focalPoint: undefined, + useFeaturedImage: false, } ); }; } +function MediaTextResolutionTool( { image, value, onChange } ) { + const { imageSizes } = useSelect( ( select ) => { + const { getSettings } = select( blockEditorStore ); + return { + imageSizes: getSettings().imageSizes, + }; + }, [] ); + + if ( ! imageSizes?.length ) { + return null; + } + + const imageSizeOptions = imageSizes + .filter( ( { slug } ) => getImageSourceUrlBySizeSlug( image, slug ) ) + .map( ( { name, slug } ) => ( { value: slug, label: name } ) ); + + return ( + <ResolutionTool + value={ value } + defaultValue={ DEFAULT_MEDIA_SIZE_SLUG } + options={ imageSizeOptions } + onChange={ onChange } + /> + ); +} + function MediaTextEdit( { attributes, isSelected, @@ -152,12 +180,12 @@ function MediaTextEdit( { mediaType, mediaUrl, mediaWidth, + mediaSizeSlug, rel, verticalAlignment, allowedBlocks, useFeaturedImage, } = attributes; - const mediaSizeSlug = attributes.mediaSizeSlug || DEFAULT_MEDIA_SIZE_SLUG; const [ featuredImage ] = useEntityProp( 'postType', @@ -166,11 +194,32 @@ function MediaTextEdit( { postId ); - const featuredImageMedia = useSelect( - ( select ) => - featuredImage && - select( coreStore ).getMedia( featuredImage, { context: 'view' } ), - [ featuredImage ] + const { featuredImageMedia } = useSelect( + ( select ) => { + return { + featuredImageMedia: + featuredImage && useFeaturedImage + ? select( coreStore ).getMedia( featuredImage, { + context: 'view', + } ) + : undefined, + }; + }, + [ featuredImage, useFeaturedImage ] + ); + + const { image } = useSelect( + ( select ) => { + return { + image: + mediaId && isSelected + ? select( coreStore ).getMedia( mediaId, { + context: 'view', + } ) + : null, + }; + }, + [ isSelected, mediaId ] ); const featuredImageURL = useFeaturedImage @@ -197,22 +246,6 @@ function MediaTextEdit( { } ); }; - const { imageSizes, image } = useSelect( - ( select ) => { - const { getSettings } = select( blockEditorStore ); - return { - image: - mediaId && isSelected - ? select( coreStore ).getMedia( mediaId, { - context: 'view', - } ) - : null, - imageSizes: getSettings()?.imageSizes, - }; - }, - [ isSelected, mediaId ] - ); - const refMedia = useRef(); const imperativeFocalPointPreview = ( value ) => { const { style } = refMedia.current; @@ -260,10 +293,6 @@ function MediaTextEdit( { const onVerticalAlignmentChange = ( alignment ) => { setAttributes( { verticalAlignment: alignment } ); }; - - const imageSizeOptions = imageSizes - .filter( ( { slug } ) => getImageSourceUrlBySizeSlug( image, slug ) ) - .map( ( { name, slug } ) => ( { value: slug, label: name } ) ); const updateImage = ( newMediaSizeSlug ) => { const newUrl = getImageSourceUrlBySizeSlug( image, newMediaSizeSlug ); @@ -409,9 +438,9 @@ function MediaTextEdit( { </ToolsPanelItem> ) } { mediaType === 'image' && ! useFeaturedImage && ( - <ResolutionTool + <MediaTextResolutionTool + image={ image } value={ mediaSizeSlug } - options={ imageSizeOptions } onChange={ updateImage } /> ) } diff --git a/packages/block-library/src/more/edit.js b/packages/block-library/src/more/edit.js index 21e26b47bfb16..af903640b6b8d 100644 --- a/packages/block-library/src/more/edit.js +++ b/packages/block-library/src/more/edit.js @@ -10,6 +10,10 @@ import { import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; import { ENTER } from '@wordpress/keycodes'; import { getDefaultBlockName, createBlock } from '@wordpress/blocks'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; const DEFAULT_TEXT = __( 'Read more' ); @@ -41,6 +45,8 @@ export default function MoreEdit( { width: `${ ( customText ? customText : DEFAULT_TEXT ).length + 1.2 }em`, }; + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); + return ( <> <InspectorControls> @@ -51,6 +57,7 @@ export default function MoreEdit( { noTeaser: false, } ); } } + dropdownMenuProps={ dropdownMenuProps } > <ToolsPanelItem label={ __( 'Hide excerpt' ) } diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 39073b848d3ca..5966739aa61a6 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -9,7 +9,8 @@ import clsx from 'clsx'; import { createBlock } from '@wordpress/blocks'; import { useSelect, useDispatch } from '@wordpress/data'; import { - PanelBody, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, TextControl, TextareaControl, ToolbarButton, @@ -161,71 +162,110 @@ function getMissingText( type ) { function Controls( { attributes, setAttributes, setIsLabelFieldFocused } ) { const { label, url, description, title, rel } = attributes; return ( - <PanelBody title={ __( 'Settings' ) }> - <TextControl - __nextHasNoMarginBottom - __next40pxDefaultSize - value={ label ? stripHTML( label ) : '' } - onChange={ ( labelValue ) => { - setAttributes( { label: labelValue } ); - } } + <ToolsPanel label={ __( 'Settings' ) }> + <ToolsPanelItem + hasValue={ () => !! label } label={ __( 'Text' ) } - autoComplete="off" - onFocus={ () => setIsLabelFieldFocused( true ) } - onBlur={ () => setIsLabelFieldFocused( false ) } - /> - <TextControl - __nextHasNoMarginBottom - __next40pxDefaultSize - value={ url ? safeDecodeURI( url ) : '' } - onChange={ ( urlValue ) => { - updateAttributes( - { url: urlValue }, - setAttributes, - attributes - ); - } } + onDeselect={ () => setAttributes( { label: '' } ) } + isShownByDefault + > + <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Text' ) } + value={ label ? stripHTML( label ) : '' } + onChange={ ( labelValue ) => { + setAttributes( { label: labelValue } ); + } } + autoComplete="off" + onFocus={ () => setIsLabelFieldFocused( true ) } + onBlur={ () => setIsLabelFieldFocused( false ) } + /> + </ToolsPanelItem> + + <ToolsPanelItem + hasValue={ () => !! url } label={ __( 'Link' ) } - autoComplete="off" - /> - <TextareaControl - __nextHasNoMarginBottom - value={ description || '' } - onChange={ ( descriptionValue ) => { - setAttributes( { description: descriptionValue } ); - } } + onDeselect={ () => setAttributes( { url: '' } ) } + isShownByDefault + > + <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Link' ) } + value={ url ? safeDecodeURI( url ) : '' } + onChange={ ( urlValue ) => { + updateAttributes( + { url: urlValue }, + setAttributes, + attributes + ); + } } + autoComplete="off" + /> + </ToolsPanelItem> + + <ToolsPanelItem + hasValue={ () => !! description } label={ __( 'Description' ) } - help={ __( - 'The description will be displayed in the menu if the current theme supports it.' - ) } - /> - <TextControl - __nextHasNoMarginBottom - __next40pxDefaultSize - value={ title || '' } - onChange={ ( titleValue ) => { - setAttributes( { title: titleValue } ); - } } + onDeselect={ () => setAttributes( { description: '' } ) } + isShownByDefault + > + <TextareaControl + __nextHasNoMarginBottom + label={ __( 'Description' ) } + value={ description || '' } + onChange={ ( descriptionValue ) => { + setAttributes( { description: descriptionValue } ); + } } + help={ __( + 'The description will be displayed in the menu if the current theme supports it.' + ) } + /> + </ToolsPanelItem> + + <ToolsPanelItem + hasValue={ () => !! title } label={ __( 'Title attribute' ) } - autoComplete="off" - help={ __( - 'Additional information to help clarify the purpose of the link.' - ) } - /> - <TextControl - __nextHasNoMarginBottom - __next40pxDefaultSize - value={ rel || '' } - onChange={ ( relValue ) => { - setAttributes( { rel: relValue } ); - } } + onDeselect={ () => setAttributes( { title: '' } ) } + isShownByDefault + > + <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Title attribute' ) } + value={ title || '' } + onChange={ ( titleValue ) => { + setAttributes( { title: titleValue } ); + } } + autoComplete="off" + help={ __( + 'Additional information to help clarify the purpose of the link.' + ) } + /> + </ToolsPanelItem> + + <ToolsPanelItem + hasValue={ () => !! rel } label={ __( 'Rel attribute' ) } - autoComplete="off" - help={ __( - 'The relationship of the linked URL as space-separated link types.' - ) } - /> - </PanelBody> + onDeselect={ () => setAttributes( { rel: '' } ) } + isShownByDefault + > + <TextControl + __nextHasNoMarginBottom + __next40pxDefaultSize + label={ __( 'Rel attribute' ) } + value={ rel || '' } + onChange={ ( relValue ) => { + setAttributes( { rel: relValue } ); + } } + autoComplete="off" + help={ __( + 'The relationship of the linked URL as space-separated link types.' + ) } + /> + </ToolsPanelItem> + </ToolsPanel> ); } diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js index dbdbd23b13b2f..315169f2736ad 100644 --- a/packages/block-library/src/navigation-submenu/edit.js +++ b/packages/block-library/src/navigation-submenu/edit.js @@ -44,6 +44,7 @@ import { getColors, getNavigationChildBlockProps, } from '../navigation/edit/utils'; +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; const ALLOWED_BLOCKS = [ 'core/navigation-link', @@ -153,6 +154,7 @@ export default function NavigationSubmenuEdit( { const isDraggingWithin = useIsDraggingWithin( listItemRef ); const itemLabelPlaceholder = __( 'Add text…' ); const ref = useRef(); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); const { parentCount, @@ -394,6 +396,7 @@ export default function NavigationSubmenuEdit( { rel: '', } ); } } + dropdownMenuProps={ dropdownMenuProps } > <ToolsPanelItem label={ __( 'Text' ) } diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index 9a56e399fcfec..43ca833153427 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -539,8 +539,8 @@ private static function get_responsive_container_markup( $attributes, $inner_blo $inner_blocks_html, $toggle_aria_label_open, $toggle_aria_label_close, - esc_attr( implode( ' ', $responsive_container_classes ) ), - esc_attr( implode( ' ', $open_button_classes ) ), + esc_attr( trim( implode( ' ', $responsive_container_classes ) ) ), + esc_attr( trim( implode( ' ', $open_button_classes ) ) ), ( ! empty( $overlay_inline_styles ) ) ? "style=\"$overlay_inline_styles\"" : '', $toggle_button_content, $toggle_close_button_content, diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js index ef927ecceccf2..8f1409f864f9b 100644 --- a/packages/block-library/src/page-list/edit.js +++ b/packages/block-library/src/page-list/edit.js @@ -359,24 +359,18 @@ export default function PageListEdit( { ) } { allowConvertToLinks && ( - <ToolsPanelItem - label={ __( 'Edit Menu' ) } - isShownByDefault - hasValue={ () => false } - > - <div> - <p>{ convertDescription }</p> - <Button - __next40pxDefaultSize - variant="primary" - accessibleWhenDisabled - disabled={ ! hasResolvedPages } - onClick={ convertToNavigationLinks } - > - { __( 'Edit' ) } - </Button> - </div> - </ToolsPanelItem> + <div style={ { gridColumn: '1 / -1' } }> + <p>{ convertDescription }</p> + <Button + __next40pxDefaultSize + variant="primary" + accessibleWhenDisabled + disabled={ ! hasResolvedPages } + onClick={ convertToNavigationLinks } + > + { __( 'Edit' ) } + </Button> + </div> ) } </ToolsPanel> </InspectorControls> diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index 02ca1feceae55..f1c2e15537b99 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -20,14 +20,16 @@ import { useBlockProps, useSettings, useBlockEditingMode, + store as blockEditorStore, } from '@wordpress/block-editor'; +import { useSelect } from '@wordpress/data'; import { getBlockSupport } from '@wordpress/blocks'; import { formatLtr } from '@wordpress/icons'; - /** * Internal dependencies */ import { useOnEnter } from './use-enter'; +import { unlock } from '../lock-unlock'; function ParagraphRTLControl( { direction, setDirection } ) { return ( @@ -109,7 +111,11 @@ function ParagraphBlock( { isSelected: isSingleSelected, name, } ) { - const { align, content, direction, dropCap, placeholder } = attributes; + const isZoomOut = useSelect( ( select ) => + unlock( select( blockEditorStore ) ).isZoomOut() + ); + + const { align, content, direction, dropCap } = attributes; const blockProps = useBlockProps( { ref: useOnEnter( { clientId, content } ), className: clsx( { @@ -119,6 +125,12 @@ function ParagraphBlock( { style: { direction }, } ); const blockEditingMode = useBlockEditingMode(); + let { placeholder } = attributes; + if ( isZoomOut ) { + placeholder = ''; + } else if ( ! placeholder ) { + placeholder = __( 'Type / to choose a block' ); + } return ( <> @@ -170,8 +182,10 @@ function ParagraphBlock( { : __( 'Block: Paragraph' ) } data-empty={ RichText.isEmpty( content ) } - placeholder={ placeholder || __( 'Type / to choose a block' ) } - data-custom-placeholder={ placeholder ? true : undefined } + placeholder={ placeholder } + data-custom-placeholder={ + placeholder && ! isZoomOut ? true : undefined + } __unstableEmbedURLOnPaste __unstableAllowPrefixTransformations /> diff --git a/packages/block-library/src/post-author-name/block.json b/packages/block-library/src/post-author-name/block.json index 68d2c49bd9105..23211f0bf5bf4 100644 --- a/packages/block-library/src/post-author-name/block.json +++ b/packages/block-library/src/post-author-name/block.json @@ -12,11 +12,13 @@ }, "isLink": { "type": "boolean", - "default": false + "default": false, + "role": "content" }, "linkTarget": { "type": "string", - "default": "_self" + "default": "_self", + "role": "content" } }, "usesContext": [ "postType", "postId" ], diff --git a/packages/block-library/src/post-author/block.json b/packages/block-library/src/post-author/block.json index d66498c8ee3df..c7f2f01550a61 100644 --- a/packages/block-library/src/post-author/block.json +++ b/packages/block-library/src/post-author/block.json @@ -26,11 +26,13 @@ }, "isLink": { "type": "boolean", - "default": false + "default": false, + "role": "content" }, "linkTarget": { "type": "string", - "default": "_self" + "default": "_self", + "role": "content" } }, "usesContext": [ "postType", "postId", "queryId" ], diff --git a/packages/block-library/src/post-comments-form/block.json b/packages/block-library/src/post-comments-form/block.json index af893ccb67a08..4b6b333b75cfa 100644 --- a/packages/block-library/src/post-comments-form/block.json +++ b/packages/block-library/src/post-comments-form/block.json @@ -56,5 +56,10 @@ "wp-block-post-comments-form", "wp-block-buttons", "wp-block-button" - ] + ], + "example": { + "attributes": { + "textAlign": "center" + } + } } diff --git a/packages/block-library/src/post-comments-link/block.json b/packages/block-library/src/post-comments-link/block.json index 67831b1d15c5d..8e23bc7a69507 100644 --- a/packages/block-library/src/post-comments-link/block.json +++ b/packages/block-library/src/post-comments-link/block.json @@ -42,6 +42,13 @@ }, "interactivity": { "clientNavigation": true + }, + "__experimentalBorder": { + "radius": true, + "color": true, + "width": true, + "style": true } - } + }, + "style": "wp-block-post-comments-link" } diff --git a/packages/block-library/src/post-comments-link/style.scss b/packages/block-library/src/post-comments-link/style.scss new file mode 100644 index 0000000000000..110179d3ee1df --- /dev/null +++ b/packages/block-library/src/post-comments-link/style.scss @@ -0,0 +1,4 @@ +.wp-block-post-comments-link { + // This block has customizable padding, border-box makes that more predictable. + box-sizing: border-box; +} diff --git a/packages/block-library/src/post-date/block.json b/packages/block-library/src/post-date/block.json index 470bddae53bdf..dadc0d2f489fe 100644 --- a/packages/block-library/src/post-date/block.json +++ b/packages/block-library/src/post-date/block.json @@ -15,7 +15,8 @@ }, "isLink": { "type": "boolean", - "default": false + "default": false, + "role": "content" }, "displayType": { "type": "string", diff --git a/packages/block-library/src/post-date/edit.js b/packages/block-library/src/post-date/edit.js index 6ac39c0f14798..36de2f7e5d725 100644 --- a/packages/block-library/src/post-date/edit.js +++ b/packages/block-library/src/post-date/edit.js @@ -34,6 +34,11 @@ import { edit } from '@wordpress/icons'; import { DOWN } from '@wordpress/keycodes'; import { useSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; + export default function PostDateEdit( { attributes: { textAlign, format, isLink, displayType }, context: { postId, postType: postTypeSlug, queryId }, @@ -45,6 +50,7 @@ export default function PostDateEdit( { [ `wp-block-post-date__modified-date` ]: displayType === 'modified', } ), } ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); // Use internal state instead of a ref to make sure that the component // re-renders when the popover's anchor updates. @@ -170,6 +176,7 @@ export default function PostDateEdit( { displayType: 'date', } ); } } + dropdownMenuProps={ dropdownMenuProps } > <ToolsPanelItem hasValue={ () => diff --git a/packages/block-library/src/post-featured-image/block.json b/packages/block-library/src/post-featured-image/block.json index 8b431ffc62579..3cd144caa0cf4 100644 --- a/packages/block-library/src/post-featured-image/block.json +++ b/packages/block-library/src/post-featured-image/block.json @@ -9,7 +9,8 @@ "attributes": { "isLink": { "type": "boolean", - "default": false + "default": false, + "role": "content" }, "aspectRatio": { "type": "string" @@ -30,11 +31,13 @@ "rel": { "type": "string", "attribute": "rel", - "default": "" + "default": "", + "role": "content" }, "linkTarget": { "type": "string", - "default": "_self" + "default": "_self", + "role": "content" }, "overlayColor": { "type": "string" diff --git a/packages/block-library/src/post-featured-image/dimension-controls.js b/packages/block-library/src/post-featured-image/dimension-controls.js index 5a3e40a126bf8..9a71a96b2db84 100644 --- a/packages/block-library/src/post-featured-image/dimension-controls.js +++ b/packages/block-library/src/post-featured-image/dimension-controls.js @@ -12,10 +12,18 @@ import { } from '@wordpress/components'; import { useSettings, + privateApis as blockEditorPrivateApis, store as blockEditorStore, } from '@wordpress/block-editor'; import { useSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { unlock } from '../lock-unlock'; + +const { ResolutionTool } = unlock( blockEditorPrivateApis ); + const SCALE_OPTIONS = ( <> <ToggleGroupControlOption @@ -223,30 +231,19 @@ const DimensionControls = ( { </ToolsPanelItem> ) } { !! imageSizeOptions.length && ( - <ToolsPanelItem - hasValue={ () => !! sizeSlug } - label={ __( 'Resolution' ) } - onDeselect={ () => - setAttributes( { sizeSlug: undefined } ) + <ResolutionTool + panelId={ clientId } + value={ sizeSlug } + defaultValue={ DEFAULT_SIZE } + options={ imageSizeOptions } + onChange={ ( nextSizeSlug ) => + setAttributes( { sizeSlug: nextSizeSlug } ) } + isShownByDefault={ false } resetAllFilter={ () => ( { - sizeSlug: undefined, + sizeSlug: DEFAULT_SIZE, } ) } - isShownByDefault={ false } - panelId={ clientId } - > - <SelectControl - __next40pxDefaultSize - __nextHasNoMarginBottom - label={ __( 'Resolution' ) } - value={ sizeSlug || DEFAULT_SIZE } - options={ imageSizeOptions } - onChange={ ( nextSizeSlug ) => - setAttributes( { sizeSlug: nextSizeSlug } ) - } - help={ __( 'Select the size of the source image.' ) } - /> - </ToolsPanelItem> + /> ) } </> ); diff --git a/packages/block-library/src/post-navigation-link/block.json b/packages/block-library/src/post-navigation-link/block.json index 5f1b295119822..ce733759846fe 100644 --- a/packages/block-library/src/post-navigation-link/block.json +++ b/packages/block-library/src/post-navigation-link/block.json @@ -34,12 +34,6 @@ "default": "" } }, - "example": { - "attributes": { - "label": "Next post", - "arrow": "arrow" - } - }, "usesContext": [ "postType" ], "supports": { "reusable": false, diff --git a/packages/block-library/src/post-navigation-link/index.js b/packages/block-library/src/post-navigation-link/index.js index e85e594990adb..4bcb199906705 100644 --- a/packages/block-library/src/post-navigation-link/index.js +++ b/packages/block-library/src/post-navigation-link/index.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + /** * Internal dependencies */ @@ -12,6 +17,12 @@ export { metadata, name }; export const settings = { edit, variations, + example: { + attributes: { + label: __( 'Next post' ), + arrow: 'arrow', + }, + }, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/post-navigation-link/variations.js b/packages/block-library/src/post-navigation-link/variations.js index 4f52b21338af1..e49be1542685e 100644 --- a/packages/block-library/src/post-navigation-link/variations.js +++ b/packages/block-library/src/post-navigation-link/variations.js @@ -17,7 +17,7 @@ const variations = [ scope: [ 'inserter', 'transform' ], example: { attributes: { - label: 'Next post', + label: __( 'Next post' ), arrow: 'arrow', }, }, @@ -33,7 +33,7 @@ const variations = [ scope: [ 'inserter', 'transform' ], example: { attributes: { - label: 'Previous post', + label: __( 'Previous post' ), arrow: 'arrow', }, }, diff --git a/packages/block-library/src/post-template/block.json b/packages/block-library/src/post-template/block.json index 6e1f58155590f..d379a46d3142f 100644 --- a/packages/block-library/src/post-template/block.json +++ b/packages/block-library/src/post-template/block.json @@ -43,15 +43,25 @@ } }, "spacing": { + "margin": true, + "padding": true, "blockGap": { "__experimentalDefault": "1.25em" }, "__experimentalDefaultControls": { - "blockGap": true + "blockGap": true, + "padding": false, + "margin": false } }, "interactivity": { "clientNavigation": true + }, + "__experimentalBorder": { + "radius": true, + "color": true, + "width": true, + "style": true } }, "style": "wp-block-post-template", diff --git a/packages/block-library/src/post-template/editor.scss b/packages/block-library/src/post-template/editor.scss deleted file mode 100644 index 7b426b0f3d37a..0000000000000 --- a/packages/block-library/src/post-template/editor.scss +++ /dev/null @@ -1,7 +0,0 @@ -.editor-styles-wrapper { - ul.wp-block-post-template { - padding-left: 0; - margin-left: 0; - list-style: none; - } -} diff --git a/packages/block-library/src/post-template/style.scss b/packages/block-library/src/post-template/style.scss index 806aadc77470e..e6896f2db024a 100644 --- a/packages/block-library/src/post-template/style.scss +++ b/packages/block-library/src/post-template/style.scss @@ -4,6 +4,8 @@ max-width: 100%; list-style: none; padding: 0; + // This block has customizable padding, border-box makes that more predictable. + box-sizing: border-box; // These rules no longer apply but should be kept for backwards compatibility. &.is-flex-container { diff --git a/packages/block-library/src/post-title/block.json b/packages/block-library/src/post-title/block.json index ecb5053d6cd39..5587d71b148d0 100644 --- a/packages/block-library/src/post-title/block.json +++ b/packages/block-library/src/post-title/block.json @@ -20,16 +20,19 @@ }, "isLink": { "type": "boolean", - "default": false + "default": false, + "role": "content" }, "rel": { "type": "string", "attribute": "rel", - "default": "" + "default": "", + "role": "content" }, "linkTarget": { "type": "string", - "default": "_self" + "default": "_self", + "role": "content" } }, "example": { diff --git a/packages/block-library/src/query-no-results/block.json b/packages/block-library/src/query-no-results/block.json index c7d3ff500e0f4..c2b7224aa40b1 100644 --- a/packages/block-library/src/query-no-results/block.json +++ b/packages/block-library/src/query-no-results/block.json @@ -8,16 +8,6 @@ "ancestor": [ "core/query" ], "textdomain": "default", "usesContext": [ "queryId", "query" ], - "example": { - "innerBlocks": [ - { - "name": "core/paragraph", - "attributes": { - "content": "No posts were found." - } - } - ] - }, "supports": { "align": true, "reusable": false, diff --git a/packages/block-library/src/query-no-results/index.js b/packages/block-library/src/query-no-results/index.js index 1c56638cdfdba..fab5993148470 100644 --- a/packages/block-library/src/query-no-results/index.js +++ b/packages/block-library/src/query-no-results/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { loop as icon } from '@wordpress/icons'; /** @@ -18,6 +19,16 @@ export const settings = { icon, edit, save, + example: { + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { + content: __( 'No posts were found.' ), + }, + }, + ], + }, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/query-pagination-previous/index.php b/packages/block-library/src/query-pagination-previous/index.php index 1592f0a10cbff..20b59109874d9 100644 --- a/packages/block-library/src/query-pagination-previous/index.php +++ b/packages/block-library/src/query-pagination-previous/index.php @@ -19,14 +19,14 @@ function render_block_core_query_pagination_previous( $attributes, $content, $block ) { $page_key = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; $enhanced_pagination = isset( $block->context['enhancedPagination'] ) && $block->context['enhancedPagination']; + $max_page = isset( $block->context['query']['pages'] ) ? (int) $block->context['query']['pages'] : 0; $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; - - $wrapper_attributes = get_block_wrapper_attributes(); - $show_label = isset( $block->context['showLabel'] ) ? (bool) $block->context['showLabel'] : true; - $default_label = __( 'Previous Page' ); - $label_text = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? esc_html( $attributes['label'] ) : $default_label; - $label = $show_label ? $label_text : ''; - $pagination_arrow = get_query_pagination_arrow( $block, false ); + $wrapper_attributes = get_block_wrapper_attributes(); + $show_label = isset( $block->context['showLabel'] ) ? (bool) $block->context['showLabel'] : true; + $default_label = __( 'Previous Page' ); + $label_text = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? esc_html( $attributes['label'] ) : $default_label; + $label = $show_label ? $label_text : ''; + $pagination_arrow = get_query_pagination_arrow( $block, false ); if ( ! $label ) { $wrapper_attributes .= ' aria-label="' . $label_text . '"'; } @@ -44,13 +44,20 @@ function render_block_core_query_pagination_previous( $attributes, $content, $bl add_filter( 'previous_posts_link_attributes', $filter_link_attributes ); $content = get_previous_posts_link( $label ); remove_filter( 'previous_posts_link_attributes', $filter_link_attributes ); - } elseif ( 1 !== $page ) { - $content = sprintf( - '<a href="%1$s" %2$s>%3$s</a>', - esc_url( add_query_arg( $page_key, $page - 1 ) ), - $wrapper_attributes, - $label - ); + } else { + $block_query = new WP_Query( build_query_vars_from_query_block( $block, $page ) ); + $block_max_pages = $block_query->max_num_pages; + $total = ! $max_page || $max_page > $block_max_pages ? $block_max_pages : $max_page; + wp_reset_postdata(); + + if ( 1 < $page && $page <= $total ) { + $content = sprintf( + '<a href="%1$s" %2$s>%3$s</a>', + esc_url( add_query_arg( $page_key, $page - 1 ) ), + $wrapper_attributes, + $label + ); + } } if ( $enhanced_pagination && isset( $content ) ) { diff --git a/packages/block-library/src/query-pagination/query-pagination-label-control.js b/packages/block-library/src/query-pagination/query-pagination-label-control.js index 9ff80a663adeb..16766c19bef08 100644 --- a/packages/block-library/src/query-pagination/query-pagination-label-control.js +++ b/packages/block-library/src/query-pagination/query-pagination-label-control.js @@ -9,9 +9,7 @@ export function QueryPaginationLabelControl( { value, onChange } ) { <ToggleControl __nextHasNoMarginBottom label={ __( 'Show label text' ) } - help={ __( - 'Toggle off to hide the label text, e.g. "Next Page".' - ) } + help={ __( 'Make label text visible, e.g. "Next Page".' ) } onChange={ onChange } checked={ value === true } /> diff --git a/packages/block-library/src/query-total/block.json b/packages/block-library/src/query-total/block.json index 02dbbbbb00f74..f6449fbd8ad4b 100644 --- a/packages/block-library/src/query-total/block.json +++ b/packages/block-library/src/query-total/block.json @@ -40,6 +40,13 @@ "__experimentalDefaultControls": { "fontSize": true } + }, + "__experimentalBorder": { + "radius": true, + "color": true, + "width": true, + "style": true } - } + }, + "style": "wp-block-query-total" } diff --git a/packages/block-library/src/query-total/edit.js b/packages/block-library/src/query-total/edit.js index 4824021ae99b0..d91a199071572 100644 --- a/packages/block-library/src/query-total/edit.js +++ b/packages/block-library/src/query-total/edit.js @@ -48,27 +48,25 @@ export default function QueryTotalEdit( { attributes, setAttributes } ) { // Controls for the block. const controls = ( - <> - <BlockControls> - <ToolbarGroup> - <ToolbarDropdownMenu - icon={ getButtonPositionIcon() } - label={ __( 'Change display type' ) } - controls={ buttonPositionControls } - /> - </ToolbarGroup> - </BlockControls> - </> + <BlockControls> + <ToolbarGroup> + <ToolbarDropdownMenu + icon={ getButtonPositionIcon() } + label={ __( 'Change display type' ) } + controls={ buttonPositionControls } + /> + </ToolbarGroup> + </BlockControls> ); // Render output based on the selected display type. const renderDisplay = () => { if ( displayType === 'total-results' ) { - return <div>{ __( '12 results found' ) }</div>; + return <>{ __( '12 results found' ) }</>; } if ( displayType === 'range-display' ) { - return <div>{ __( 'Displaying 1 – 10 of 12' ) }</div>; + return <>{ __( 'Displaying 1 – 10 of 12' ) }</>; } return null; diff --git a/packages/block-library/src/query-total/index.php b/packages/block-library/src/query-total/index.php index 5a8ab76b5d1ef..ff2ac486727b9 100644 --- a/packages/block-library/src/query-total/index.php +++ b/packages/block-library/src/query-total/index.php @@ -40,32 +40,28 @@ function render_block_core_query_total( $attributes, $content, $block ) { switch ( $attributes['displayType'] ) { case 'range-display': if ( $start === $end ) { - $range_text = sprintf( + $output = sprintf( /* translators: 1: Start index of posts, 2: Total number of posts */ __( 'Displaying %1$s of %2$s' ), - '<strong>' . $start . '</strong>', - '<strong>' . $max_rows . '</strong>' + $start, + $max_rows ); } else { - $range_text = sprintf( + $output = sprintf( /* translators: 1: Start index of posts, 2: End index of posts, 3: Total number of posts */ __( 'Displaying %1$s – %2$s of %3$s' ), - '<strong>' . $start . '</strong>', - '<strong>' . $end . '</strong>', - '<strong>' . $max_rows . '</strong>' + $start, + $end, + $max_rows ); } - $output = sprintf( '<p>%s</p>', $range_text ); break; case 'total-results': default: - $output = sprintf( - '<p><strong>%d</strong> %s</p>', - $max_rows, - _n( 'result found', 'results found', $max_rows ) - ); + // translators: %d: number of results. + $output = sprintf( _n( '%d result found', '%d results found', $max_rows ), $max_rows ); break; } diff --git a/packages/block-library/src/query-total/style.scss b/packages/block-library/src/query-total/style.scss new file mode 100644 index 0000000000000..c6a2bc131cfaf --- /dev/null +++ b/packages/block-library/src/query-total/style.scss @@ -0,0 +1,4 @@ +.wp-block-query-total { + // This block has customizable padding, border-box makes that more predictable. + box-sizing: border-box; +} diff --git a/packages/block-library/src/read-more/index.js b/packages/block-library/src/read-more/index.js index 497cd77f429e6..f982f35151b4b 100644 --- a/packages/block-library/src/read-more/index.js +++ b/packages/block-library/src/read-more/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { link as icon } from '@wordpress/icons'; /** @@ -16,6 +17,11 @@ export { metadata, name }; export const settings = { icon, edit, + example: { + attributes: { + content: __( 'Read more' ), + }, + }, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index f193c04e2493a..b4ac37220c816 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -34,7 +34,7 @@ import { } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { Icon, search } from '@wordpress/icons'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; /** @@ -470,7 +470,11 @@ export default function SearchEdit( { <ToggleGroupControlOption key={ widthValue } value={ widthValue } - label={ `${ widthValue }%` } + label={ sprintf( + /* translators: Percentage value. */ + __( '%1$d%%' ), + widthValue + ) } /> ); } ) } diff --git a/packages/block-library/src/site-logo/block.json b/packages/block-library/src/site-logo/block.json index 3bdbdc1b809ab..1f5b3a5525e3e 100644 --- a/packages/block-library/src/site-logo/block.json +++ b/packages/block-library/src/site-logo/block.json @@ -12,11 +12,13 @@ }, "isLink": { "type": "boolean", - "default": true + "default": true, + "role": "content" }, "linkTarget": { "type": "string", - "default": "_self" + "default": "_self", + "role": "content" }, "shouldSyncIcon": { "type": "boolean" diff --git a/packages/block-library/src/site-title/block.json b/packages/block-library/src/site-title/block.json index c75b1bc229beb..8edf6b945f9ce 100644 --- a/packages/block-library/src/site-title/block.json +++ b/packages/block-library/src/site-title/block.json @@ -20,11 +20,13 @@ }, "isLink": { "type": "boolean", - "default": true + "default": true, + "role": "content" }, "linkTarget": { "type": "string", - "default": "_self" + "default": "_self", + "role": "content" } }, "example": { diff --git a/packages/block-library/src/site-title/edit.js b/packages/block-library/src/site-title/edit.js index 644629a96fe4e..0e3e96bd87cb3 100644 --- a/packages/block-library/src/site-title/edit.js +++ b/packages/block-library/src/site-title/edit.js @@ -25,6 +25,11 @@ import { import { createBlock, getDefaultBlockName } from '@wordpress/blocks'; import { decodeEntities } from '@wordpress/html-entities'; +/** + * Internal dependencies + */ +import { useToolsPanelDropdownMenuProps } from '../utils/hooks'; + export default function SiteTitleEdit( { attributes, setAttributes, @@ -47,6 +52,7 @@ export default function SiteTitleEdit( { }; }, [] ); const { editEntityRecord } = useDispatch( coreStore ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); function setTitle( newTitle ) { editEntityRecord( 'root', 'site', undefined, { @@ -117,15 +123,16 @@ export default function SiteTitleEdit( { label={ __( 'Settings' ) } resetAll={ () => { setAttributes( { - isLink: false, + isLink: true, linkTarget: '_self', } ); } } + dropdownMenuProps={ dropdownMenuProps } > <ToolsPanelItem - hasValue={ () => isLink !== false } + hasValue={ () => ! isLink } label={ __( 'Make title link to home' ) } - onDeselect={ () => setAttributes( { isLink: false } ) } + onDeselect={ () => setAttributes( { isLink: true } ) } isShownByDefault > <ToggleControl diff --git a/packages/block-library/src/spacer/controls.js b/packages/block-library/src/spacer/controls.js index 1e899e15aff0d..fde06d3ee8c33 100644 --- a/packages/block-library/src/spacer/controls.js +++ b/packages/block-library/src/spacer/controls.js @@ -10,10 +10,11 @@ import { privateApis as blockEditorPrivateApis, } from '@wordpress/block-editor'; import { - PanelBody, __experimentalUseCustomUnits as useCustomUnits, __experimentalUnitControl as UnitControl, __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { View } from '@wordpress/primitives'; @@ -94,28 +95,54 @@ export default function SpacerControls( { } ) { return ( <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + width: undefined, + height: '100px', + } ); + } } + > { orientation === 'horizontal' && ( - <DimensionInput + <ToolsPanelItem label={ __( 'Width' ) } - value={ width } - onChange={ ( nextWidth ) => - setAttributes( { width: nextWidth } ) + isShownByDefault + hasValue={ () => width !== undefined } + onDeselect={ () => + setAttributes( { width: undefined } ) } - isResizing={ isResizing } - /> + > + <DimensionInput + label={ __( 'Width' ) } + value={ width } + onChange={ ( nextWidth ) => + setAttributes( { width: nextWidth } ) + } + isResizing={ isResizing } + /> + </ToolsPanelItem> ) } { orientation !== 'horizontal' && ( - <DimensionInput + <ToolsPanelItem label={ __( 'Height' ) } - value={ height } - onChange={ ( nextHeight ) => - setAttributes( { height: nextHeight } ) + isShownByDefault + hasValue={ () => height !== '100px' } + onDeselect={ () => + setAttributes( { height: '100px' } ) } - isResizing={ isResizing } - /> + > + <DimensionInput + label={ __( 'Height' ) } + value={ height } + onChange={ ( nextHeight ) => + setAttributes( { height: nextHeight } ) + } + isResizing={ isResizing } + /> + </ToolsPanelItem> ) } - </PanelBody> + </ToolsPanel> </InspectorControls> ); } diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index a8819c2084dc2..c61049c23151b 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -37,6 +37,7 @@ @import "./post-author-biography/style.scss"; @import "./post-comments-form/style.scss"; @import "./post-content/style.scss"; +@import "./post-comments-link/style.scss"; @import "./post-date/style.scss"; @import "./post-excerpt/style.scss"; @import "./post-featured-image/style.scss"; @@ -50,6 +51,7 @@ @import "./post-template/style.scss"; @import "./query-pagination/style.scss"; @import "./query-title/style.scss"; +@import "./query-total/style.scss"; @import "./quote/style.scss"; @import "./read-more/style.scss"; @import "./rss/style.scss"; diff --git a/packages/block-library/src/table-of-contents/block.json b/packages/block-library/src/table-of-contents/block.json index 5eb6e729d3f03..68266166080bb 100644 --- a/packages/block-library/src/table-of-contents/block.json +++ b/packages/block-library/src/table-of-contents/block.json @@ -62,57 +62,5 @@ } } }, - "example": { - "innerBlocks": [ - { - "name": "core/heading", - "attributes": { - "level": 2, - "content": "Heading" - } - }, - { - "name": "core/heading", - "attributes": { - "level": 3, - "content": "Subheading" - } - }, - { - "name": "core/heading", - "attributes": { - "level": 2, - "content": "Heading" - } - }, - { - "name": "core/heading", - "attributes": { - "level": 3, - "content": "Subheading" - } - } - ], - "attributes": { - "headings": [ - { - "content": "Heading", - "level": 2 - }, - { - "content": "Subheading", - "level": 3 - }, - { - "content": "Heading", - "level": 2 - }, - { - "content": "Subheading", - "level": 3 - } - ] - } - }, "style": "wp-block-table-of-contents" } diff --git a/packages/block-library/src/table-of-contents/edit.js b/packages/block-library/src/table-of-contents/edit.js index c95b89200cb88..394ff2666067d 100644 --- a/packages/block-library/src/table-of-contents/edit.js +++ b/packages/block-library/src/table-of-contents/edit.js @@ -122,7 +122,7 @@ export default function TableOfContentsEdit( { 'Only including headings from the current page (if the post is paginated).' ) : __( - 'Toggle to only include headings from the current page (if the post is paginated).' + 'Include headings from all pages (if the post is paginated).' ) } /> diff --git a/packages/block-library/src/table-of-contents/index.js b/packages/block-library/src/table-of-contents/index.js index 408538a7dcadb..ff1b658966f19 100644 --- a/packages/block-library/src/table-of-contents/index.js +++ b/packages/block-library/src/table-of-contents/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { tableOfContents as icon } from '@wordpress/icons'; /** @@ -19,6 +20,58 @@ export const settings = { icon, edit, save, + example: { + innerBlocks: [ + { + name: 'core/heading', + attributes: { + level: 2, + content: __( 'Heading' ), + }, + }, + { + name: 'core/heading', + attributes: { + level: 3, + content: __( 'Subheading' ), + }, + }, + { + name: 'core/heading', + attributes: { + level: 2, + content: __( 'Heading' ), + }, + }, + { + name: 'core/heading', + attributes: { + level: 3, + content: __( 'Subheading' ), + }, + }, + ], + attributes: { + headings: [ + { + content: __( 'Heading' ), + level: 2, + }, + { + content: __( 'Subheading' ), + level: 3, + }, + { + content: __( 'Heading' ), + level: 2, + }, + { + content: __( 'Subheading' ), + level: 3, + }, + ], + }, + }, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/table/block.json b/packages/block-library/src/table/block.json index 11dd5b5f323e3..2f0ea753f6f8d 100644 --- a/packages/block-library/src/table/block.json +++ b/packages/block-library/src/table/block.json @@ -195,11 +195,14 @@ "width": true } }, - "__experimentalSelector": ".wp-block-table > table", "interactivity": { "clientNavigation": true } }, + "selectors": { + "root": ".wp-block-table > table", + "spacing": ".wp-block-table" + }, "styles": [ { "name": "regular", diff --git a/packages/block-library/src/video/edit-common-settings.js b/packages/block-library/src/video/edit-common-settings.js index 9394bfaf5c614..4f85f929b07cf 100644 --- a/packages/block-library/src/video/edit-common-settings.js +++ b/packages/block-library/src/video/edit-common-settings.js @@ -2,7 +2,11 @@ * WordPress dependencies */ import { __, _x } from '@wordpress/i18n'; -import { ToggleControl, SelectControl } from '@wordpress/components'; +import { + ToggleControl, + SelectControl, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; import { useMemo, useCallback, Platform } from '@wordpress/element'; const options = [ @@ -47,50 +51,104 @@ const VideoSettings = ( { setAttributes, attributes } ) => { return ( <> - <ToggleControl - __nextHasNoMarginBottom + <ToolsPanelItem label={ __( 'Autoplay' ) } - onChange={ toggleFactory.autoplay } - checked={ !! autoplay } - help={ getAutoplayHelp } - /> - <ToggleControl - __nextHasNoMarginBottom + isShownByDefault + hasValue={ () => !! autoplay } + onDeselect={ () => { + setAttributes( { autoplay: false } ); + } } + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Autoplay' ) } + onChange={ toggleFactory.autoplay } + checked={ !! autoplay } + help={ getAutoplayHelp } + /> + </ToolsPanelItem> + <ToolsPanelItem label={ __( 'Loop' ) } - onChange={ toggleFactory.loop } - checked={ !! loop } - /> - <ToggleControl - __nextHasNoMarginBottom + isShownByDefault + hasValue={ () => !! loop } + onDeselect={ () => { + setAttributes( { loop: false } ); + } } + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Loop' ) } + onChange={ toggleFactory.loop } + checked={ !! loop } + /> + </ToolsPanelItem> + <ToolsPanelItem label={ __( 'Muted' ) } - onChange={ toggleFactory.muted } - checked={ !! muted } - /> - <ToggleControl - __nextHasNoMarginBottom + isShownByDefault + hasValue={ () => !! muted } + onDeselect={ () => { + setAttributes( { muted: false } ); + } } + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Muted' ) } + onChange={ toggleFactory.muted } + checked={ !! muted } + /> + </ToolsPanelItem> + <ToolsPanelItem label={ __( 'Playback controls' ) } - onChange={ toggleFactory.controls } - checked={ !! controls } - /> - <ToggleControl - __nextHasNoMarginBottom - /* translators: Setting to play videos within the webpage on mobile browsers rather than opening in a fullscreen player. */ + isShownByDefault + hasValue={ () => ! controls } + onDeselect={ () => { + setAttributes( { controls: true } ); + } } + > + <ToggleControl + __nextHasNoMarginBottom + label={ __( 'Playback controls' ) } + onChange={ toggleFactory.controls } + checked={ !! controls } + /> + </ToolsPanelItem> + <ToolsPanelItem label={ __( 'Play inline' ) } - onChange={ toggleFactory.playsInline } - checked={ !! playsInline } - help={ __( - 'When enabled, videos will play directly within the webpage on mobile browsers, instead of opening in a fullscreen player.' - ) } - /> - <SelectControl - __next40pxDefaultSize - __nextHasNoMarginBottom + isShownByDefault + hasValue={ () => !! playsInline } + onDeselect={ () => { + setAttributes( { playsInline: false } ); + } } + > + <ToggleControl + __nextHasNoMarginBottom + /* translators: Setting to play videos within the webpage on mobile browsers rather than opening in a fullscreen player. */ + label={ __( 'Play inline' ) } + onChange={ toggleFactory.playsInline } + checked={ playsInline } + help={ __( + 'When enabled, videos will play directly within the webpage on mobile browsers, instead of opening in a fullscreen player.' + ) } + /> + </ToolsPanelItem> + <ToolsPanelItem label={ __( 'Preload' ) } - value={ preload } - onChange={ onChangePreload } - options={ options } - hideCancelButton - /> + isShownByDefault + hasValue={ () => preload !== 'metadata' } + onDeselect={ () => { + setAttributes( { preload: 'metadata' } ); + } } + > + <SelectControl + __next40pxDefaultSize + __nextHasNoMarginBottom + label={ __( 'Preload' ) } + value={ preload } + onChange={ onChangePreload } + options={ options } + hideCancelButton + /> + </ToolsPanelItem> </> ); }; diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index 32221919c7ea2..95ecab25f9598 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -8,25 +8,21 @@ import clsx from 'clsx'; */ import { isBlobURL } from '@wordpress/blob'; import { - BaseControl, - Button, Disabled, - PanelBody, Spinner, Placeholder, + __experimentalToolsPanel as ToolsPanel, } from '@wordpress/components'; import { BlockControls, BlockIcon, InspectorControls, MediaPlaceholder, - MediaUpload, - MediaUploadCheck, MediaReplaceFlow, useBlockProps, } from '@wordpress/block-editor'; import { useRef, useEffect, useState } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { useInstanceId } from '@wordpress/compose'; import { useDispatch } from '@wordpress/data'; import { video as icon } from '@wordpress/icons'; @@ -35,15 +31,18 @@ import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ +import PosterImage from './poster-image'; import { createUpgradedEmbedBlock } from '../embed/util'; -import { useUploadMediaFromBlobURL } from '../utils/hooks'; +import { + useUploadMediaFromBlobURL, + useToolsPanelDropdownMenuProps, +} from '../utils/hooks'; import VideoCommonSettings from './edit-common-settings'; import TracksEditor from './tracks-editor'; import Tracks from './tracks'; import { Caption } from '../utils/caption'; const ALLOWED_MEDIA_TYPES = [ 'video' ]; -const VIDEO_POSTER_ALLOWED_MEDIA_TYPES = [ 'image' ]; function VideoEdit( { isSelected: isSingleSelected, @@ -55,9 +54,9 @@ function VideoEdit( { } ) { const instanceId = useInstanceId( VideoEdit ); const videoPlayer = useRef(); - const posterImageButton = useRef(); const { id, controls, poster, src, tracks } = attributes; const [ temporaryURL, setTemporaryURL ] = useState( attributes.blob ); + const dropdownMenuProps = useToolsPanelDropdownMenuProps(); useUploadMediaFromBlobURL( { url: temporaryURL, @@ -174,19 +173,6 @@ function VideoEdit( { ); } - function onSelectPoster( image ) { - setAttributes( { poster: image.url } ); - } - - function onRemovePoster() { - setAttributes( { poster: undefined } ); - - // Move focus back to the Media Upload button. - posterImageButton.current.focus(); - } - - const videoPosterDescription = `video-block__poster-image-description-${ instanceId }`; - return ( <> { isSingleSelected && ( @@ -214,63 +200,31 @@ function VideoEdit( { </> ) } <InspectorControls> - <PanelBody title={ __( 'Settings' ) }> + <ToolsPanel + label={ __( 'Settings' ) } + resetAll={ () => { + setAttributes( { + autoplay: false, + controls: true, + loop: false, + muted: false, + playsInline: false, + preload: 'metadata', + poster: '', + } ); + } } + dropdownMenuProps={ dropdownMenuProps } + > <VideoCommonSettings setAttributes={ setAttributes } attributes={ attributes } /> - <MediaUploadCheck> - <div className="editor-video-poster-control"> - <BaseControl.VisualLabel> - { __( 'Poster image' ) } - </BaseControl.VisualLabel> - <MediaUpload - title={ __( 'Select poster image' ) } - onSelect={ onSelectPoster } - allowedTypes={ - VIDEO_POSTER_ALLOWED_MEDIA_TYPES - } - render={ ( { open } ) => ( - <Button - __next40pxDefaultSize - variant="primary" - onClick={ open } - ref={ posterImageButton } - aria-describedby={ - videoPosterDescription - } - > - { ! poster - ? __( 'Select' ) - : __( 'Replace' ) } - </Button> - ) } - /> - <p id={ videoPosterDescription } hidden> - { poster - ? sprintf( - /* translators: %s: poster image URL. */ - __( - 'The current poster image url is %s' - ), - poster - ) - : __( - 'There is no poster image currently selected' - ) } - </p> - { !! poster && ( - <Button - __next40pxDefaultSize - onClick={ onRemovePoster } - variant="tertiary" - > - { __( 'Remove' ) } - </Button> - ) } - </div> - </MediaUploadCheck> - </PanelBody> + <PosterImage + poster={ poster } + setAttributes={ setAttributes } + instanceId={ instanceId } + /> + </ToolsPanel> </InspectorControls> <figure { ...blockProps }> { /* diff --git a/packages/block-library/src/video/poster-image.js b/packages/block-library/src/video/poster-image.js new file mode 100644 index 0000000000000..cde95f974d8e6 --- /dev/null +++ b/packages/block-library/src/video/poster-image.js @@ -0,0 +1,86 @@ +/** + * WordPress dependencies + */ +import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor'; +import { + Button, + BaseControl, + __experimentalToolsPanelItem as ToolsPanelItem, +} from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { useRef } from '@wordpress/element'; + +function PosterImage( { poster, setAttributes, instanceId } ) { + const posterImageButton = useRef(); + const VIDEO_POSTER_ALLOWED_MEDIA_TYPES = [ 'image' ]; + + const videoPosterDescription = `video-block__poster-image-description-${ instanceId }`; + + function onSelectPoster( image ) { + setAttributes( { poster: image.url } ); + } + + function onRemovePoster() { + setAttributes( { poster: undefined } ); + + // Move focus back to the Media Upload button. + posterImageButton.current.focus(); + } + + return ( + <ToolsPanelItem + label={ __( 'Poster image' ) } + isShownByDefault + hasValue={ () => !! poster } + onDeselect={ () => { + setAttributes( { poster: '' } ); + } } + > + <MediaUploadCheck> + <div className="editor-video-poster-control"> + <BaseControl.VisualLabel> + { __( 'Poster image' ) } + </BaseControl.VisualLabel> + <MediaUpload + title={ __( 'Select poster image' ) } + onSelect={ onSelectPoster } + allowedTypes={ VIDEO_POSTER_ALLOWED_MEDIA_TYPES } + render={ ( { open } ) => ( + <Button + __next40pxDefaultSize + variant="primary" + onClick={ open } + ref={ posterImageButton } + aria-describedby={ videoPosterDescription } + > + { ! poster ? __( 'Select' ) : __( 'Replace' ) } + </Button> + ) } + /> + <p id={ videoPosterDescription } hidden> + { poster + ? sprintf( + /* translators: %s: poster image URL. */ + __( 'The current poster image url is %s' ), + poster + ) + : __( + 'There is no poster image currently selected' + ) } + </p> + { !! poster && ( + <Button + __next40pxDefaultSize + onClick={ onRemovePoster } + variant="tertiary" + > + { __( 'Remove' ) } + </Button> + ) } + </div> + </MediaUploadCheck> + </ToolsPanelItem> + ); +} + +export default PosterImage; diff --git a/packages/block-library/src/video/tracks-editor.js b/packages/block-library/src/video/tracks-editor.js index 33036a14f1fec..a0152885f5567 100644 --- a/packages/block-library/src/video/tracks-editor.js +++ b/packages/block-library/src/video/tracks-editor.js @@ -323,7 +323,7 @@ export default function TracksEditor( { tracks = [], onChange } ) { openFileDialog(); } } > - { __( 'Upload' ) } + { _x( 'Upload', 'verb' ) } </MenuItem> ); } } diff --git a/packages/block-library/tsconfig.json b/packages/block-library/tsconfig.json index 2a2cb1653d428..a8423ee4a2709 100644 --- a/packages/block-library/tsconfig.json +++ b/packages/block-library/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ], "strictNullChecks": true }, diff --git a/packages/block-serialization-default-parser/CHANGELOG.md b/packages/block-serialization-default-parser/CHANGELOG.md index a0e82f4d19b25..856ea6eb95065 100644 --- a/packages/block-serialization-default-parser/CHANGELOG.md +++ b/packages/block-serialization-default-parser/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index 7bbd52414f91e..82f0125fc8584 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-default-parser", - "version": "5.14.0", + "version": "5.15.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-default-parser/tsconfig.json b/packages/block-serialization-default-parser/tsconfig.json index 6e33d8ff82d47..7ff060ab6ce10 100644 --- a/packages/block-serialization-default-parser/tsconfig.json +++ b/packages/block-serialization-default-parser/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/block-serialization-spec-parser/CHANGELOG.md b/packages/block-serialization-spec-parser/CHANGELOG.md index 52719d1172dd7..8cbdadc118ebe 100644 --- a/packages/block-serialization-spec-parser/CHANGELOG.md +++ b/packages/block-serialization-spec-parser/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 84d5797857082..3f9d5bfff4983 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-spec-parser", - "version": "5.14.0", + "version": "5.15.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index 2e5aac914e578..066a68341b2a7 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 14.4.0 (2025-01-02) + ## 14.3.0 (2024-12-11) ## 14.2.0 (2024-11-27) diff --git a/packages/blocks/package.json b/packages/blocks/package.json index e94bb60f5aa34..4f02e328d43bf 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "14.3.0", + "version": "14.4.1", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,21 +31,21 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/autop": "*", - "@wordpress/blob": "*", - "@wordpress/block-serialization-default-parser": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/shortcode": "*", - "@wordpress/warning": "*", + "@wordpress/autop": "file:../autop", + "@wordpress/blob": "file:../blob", + "@wordpress/block-serialization-default-parser": "file:../block-serialization-default-parser", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/shortcode": "file:../shortcode", + "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", "colord": "^2.7.0", "fast-deep-equal": "^3.1.3", diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index aaf6558c47bad..620dfcbb8599c 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -58,12 +58,12 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, borderColor: { value: [ 'border', 'color' ], - support: [ 'border', 'color' ], + support: [ '__experimentalBorder', 'color' ], useEngine: true, }, borderRadius: { value: [ 'border', 'radius' ], - support: [ 'border', 'radius' ], + support: [ '__experimentalBorder', 'radius' ], properties: { borderTopLeftRadius: 'topLeft', borderTopRightRadius: 'topRight', @@ -74,72 +74,72 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, borderStyle: { value: [ 'border', 'style' ], - support: [ 'border', 'style' ], + support: [ '__experimentalBorder', 'style' ], useEngine: true, }, borderWidth: { value: [ 'border', 'width' ], - support: [ 'border', 'width' ], + support: [ '__experimentalBorder', 'width' ], useEngine: true, }, borderTopColor: { value: [ 'border', 'top', 'color' ], - support: [ 'border', 'color' ], + support: [ '__experimentalBorder', 'color' ], useEngine: true, }, borderTopStyle: { value: [ 'border', 'top', 'style' ], - support: [ 'border', 'style' ], + support: [ '__experimentalBorder', 'style' ], useEngine: true, }, borderTopWidth: { value: [ 'border', 'top', 'width' ], - support: [ 'border', 'width' ], + support: [ '__experimentalBorder', 'width' ], useEngine: true, }, borderRightColor: { value: [ 'border', 'right', 'color' ], - support: [ 'border', 'color' ], + support: [ '__experimentalBorder', 'color' ], useEngine: true, }, borderRightStyle: { value: [ 'border', 'right', 'style' ], - support: [ 'border', 'style' ], + support: [ '__experimentalBorder', 'style' ], useEngine: true, }, borderRightWidth: { value: [ 'border', 'right', 'width' ], - support: [ 'border', 'width' ], + support: [ '__experimentalBorder', 'width' ], useEngine: true, }, borderBottomColor: { value: [ 'border', 'bottom', 'color' ], - support: [ 'border', 'color' ], + support: [ '__experimentalBorder', 'color' ], useEngine: true, }, borderBottomStyle: { value: [ 'border', 'bottom', 'style' ], - support: [ 'border', 'style' ], + support: [ '__experimentalBorder', 'style' ], useEngine: true, }, borderBottomWidth: { value: [ 'border', 'bottom', 'width' ], - support: [ 'border', 'width' ], + support: [ '__experimentalBorder', 'width' ], useEngine: true, }, borderLeftColor: { value: [ 'border', 'left', 'color' ], - support: [ 'border', 'color' ], + support: [ '__experimentalBorder', 'color' ], useEngine: true, }, borderLeftStyle: { value: [ 'border', 'left', 'style' ], - support: [ 'border', 'style' ], + support: [ '__experimentalBorder', 'style' ], useEngine: true, }, borderLeftWidth: { value: [ 'border', 'left', 'width' ], - support: [ 'border', 'width' ], + support: [ '__experimentalBorder', 'width' ], useEngine: true, }, color: { @@ -183,7 +183,7 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, fontFamily: { value: [ 'typography', 'fontFamily' ], - support: [ 'typography', 'fontFamily' ], + support: [ 'typography', '__experimentalFontFamily' ], useEngine: true, }, fontSize: { @@ -193,12 +193,12 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, fontStyle: { value: [ 'typography', 'fontStyle' ], - support: [ 'typography', 'fontStyle' ], + support: [ 'typography', '__experimentalFontStyle' ], useEngine: true, }, fontWeight: { value: [ 'typography', 'fontWeight' ], - support: [ 'typography', 'fontWeight' ], + support: [ 'typography', '__experimentalFontWeight' ], useEngine: true, }, lineHeight: { @@ -240,17 +240,17 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { }, textDecoration: { value: [ 'typography', 'textDecoration' ], - support: [ 'typography', 'textDecoration' ], + support: [ 'typography', '__experimentalTextDecoration' ], useEngine: true, }, textTransform: { value: [ 'typography', 'textTransform' ], - support: [ 'typography', 'textTransform' ], + support: [ 'typography', '__experimentalTextTransform' ], useEngine: true, }, letterSpacing: { value: [ 'typography', 'letterSpacing' ], - support: [ 'typography', 'letterSpacing' ], + support: [ 'typography', '__experimentalLetterSpacing' ], useEngine: true, }, writingMode: { @@ -297,23 +297,3 @@ export const __EXPERIMENTAL_PATHS_WITH_OVERRIDE = { 'typography.fontSizes': true, 'spacing.spacingSizes': true, }; - -export const EXPERIMENTAL_SUPPORTS_MAP = { - __experimentalBorder: 'border', -}; - -export const COMMON_EXPERIMENTAL_PROPERTIES = { - __experimentalDefaultControls: 'defaultControls', - __experimentalSkipSerialization: 'skipSerialization', -}; - -export const EXPERIMENTAL_SUPPORT_PROPERTIES = { - typography: { - __experimentalFontFamily: 'fontFamily', - __experimentalFontStyle: 'fontStyle', - __experimentalFontWeight: 'fontWeight', - __experimentalLetterSpacing: 'letterSpacing', - __experimentalTextDecoration: 'textDecoration', - __experimentalTextTransform: 'textTransform', - }, -}; diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index 25bf64ca65dc9..5eacf96fb1e5b 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -17,6 +17,7 @@ import { getGroupingBlockName, } from './registration'; import { + isBlockRegistered, normalizeBlockType, __experimentalSanitizeBlockAttributes, } from './utils'; @@ -31,6 +32,14 @@ import { * @return {Object} Block object. */ export function createBlock( name, attributes = {}, innerBlocks = [] ) { + if ( ! isBlockRegistered( name ) ) { + return createBlock( 'core/missing', { + originalName: name, + originalContent: '', + originalUndelimitedContent: '', + } ); + } + const sanitizedAttributes = __experimentalSanitizeBlockAttributes( name, attributes @@ -94,15 +103,22 @@ export function __experimentalCloneSanitizedBlock( mergeAttributes = {}, newInnerBlocks ) { + const { name } = block; + + if ( ! isBlockRegistered( name ) ) { + return createBlock( 'core/missing', { + originalName: name, + originalContent: '', + originalUndelimitedContent: '', + } ); + } + const clientId = uuid(); - const sanitizedAttributes = __experimentalSanitizeBlockAttributes( - block.name, - { - ...block.attributes, - ...mergeAttributes, - } - ); + const sanitizedAttributes = __experimentalSanitizeBlockAttributes( name, { + ...block.attributes, + ...mergeAttributes, + } ); return { ...block, @@ -583,20 +599,11 @@ export function switchToBlockType( blocks, name ) { * * @return {Object} block. */ -export const getBlockFromExample = ( name, example ) => { - try { - return createBlock( - name, - example.attributes, - ( example.innerBlocks ?? [] ).map( ( innerBlock ) => - getBlockFromExample( innerBlock.name, innerBlock ) - ) - ); - } catch { - return createBlock( 'core/missing', { - originalName: name, - originalContent: '', - originalUndelimitedContent: '', - } ); - } -}; +export const getBlockFromExample = ( name, example ) => + createBlock( + name, + example.attributes, + ( example.innerBlocks ?? [] ).map( ( innerBlock ) => + getBlockFromExample( innerBlock.name, innerBlock ) + ) + ); diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index 71231121362a4..6f7e13f27ebe8 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -109,23 +109,12 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { attributes ); - let [ blockName, blockAttributes ] = + const [ blockName, blockAttributes ] = convertLegacyBlockNameAndAttributes( name, normalizedAttributes ); - // If a Block is undefined at this point, use the core/missing block as - // a placeholder for a better user experience. - if ( undefined === getBlockType( blockName ) ) { - blockAttributes = { - originalName: name, - originalContent: '', - originalUndelimitedContent: '', - }; - blockName = 'core/missing'; - } - return createBlock( blockName, blockAttributes, diff --git a/packages/blocks/src/api/test/utils.js b/packages/blocks/src/api/test/utils.js index 548bbb27da388..b1906b65b4208 100644 --- a/packages/blocks/src/api/test/utils.js +++ b/packages/blocks/src/api/test/utils.js @@ -12,6 +12,7 @@ import { isUnmodifiedDefaultBlock, getAccessibleBlockLabel, getBlockLabel, + isBlockRegistered, __experimentalSanitizeBlockAttributes, getBlockAttributesNamesByRole, isContentBlock, @@ -213,6 +214,20 @@ describe( 'getAccessibleBlockLabel', () => { } ); } ); +describe( 'isBlockRegistered', () => { + it( 'returns true if the block is registered', () => { + registerBlockType( 'core/test-block', { title: 'Test block' } ); + expect( isBlockRegistered( 'core/test-block' ) ).toBe( true ); + unregisterBlockType( 'core/test-block' ); + } ); + + it( 'returns false if the block is not registered', () => { + expect( isBlockRegistered( 'core/not-registered-test-block' ) ).toBe( + false + ); + } ); +} ); + describe( 'sanitizeBlockAttributes', () => { afterEach( () => { getBlockTypes().forEach( ( block ) => { diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index 1a21503649655..ad94d9d5c9e0c 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -266,6 +266,17 @@ export function getDefault( attributeSchema ) { } } +/** + * Check if a block is registered. + * + * @param {string} name The block's name. + * + * @return {boolean} Whether the block is registered. + */ +export function isBlockRegistered( name ) { + return getBlockType( name ) !== undefined; +} + /** * Ensure attributes contains only values defined by block type, and merge * default values for missing attributes. @@ -370,9 +381,21 @@ export const __experimentalGetBlockAttributesNamesByRole = ( ...args ) => { return getBlockAttributesNamesByRole( ...args ); }; +/** + * Checks if a block is a content block by examining its attributes. + * A block is considered a content block if it has at least one attribute + * with a role of 'content'. + * + * @param {string} name The name of the block to check. + * @return {boolean} Whether the block is a content block. + */ export function isContentBlock( name ) { const attributes = getBlockType( name )?.attributes; + if ( ! attributes ) { + return false; + } + return !! Object.keys( attributes )?.some( ( attributeKey ) => { const attribute = attributes[ attributeKey ]; return ( diff --git a/packages/blocks/src/store/process-block-type.js b/packages/blocks/src/store/process-block-type.js index 0ca28a3c3e207..bc7b1a0e10e77 100644 --- a/packages/blocks/src/store/process-block-type.js +++ b/packages/blocks/src/store/process-block-type.js @@ -15,13 +15,7 @@ import warning from '@wordpress/warning'; * Internal dependencies */ import { isValidIcon, normalizeIconObject, omit } from '../api/utils'; -import { - BLOCK_ICON_DEFAULT, - DEPRECATED_ENTRY_KEYS, - EXPERIMENTAL_SUPPORTS_MAP, - COMMON_EXPERIMENTAL_PROPERTIES, - EXPERIMENTAL_SUPPORT_PROPERTIES, -} from '../api/constants'; +import { BLOCK_ICON_DEFAULT, DEPRECATED_ENTRY_KEYS } from '../api/constants'; /** @typedef {import('../api/registration').WPBlockType} WPBlockType */ @@ -68,155 +62,6 @@ function mergeBlockVariations( return result; } -/** - * Stabilizes a block support configuration by converting experimental properties - * to their stable equivalents. - * - * @param {Object} unstableConfig The support configuration to stabilize. - * @param {string} stableSupportKey The stable support key for looking up properties. - * @return {Object} The stabilized support configuration. - */ -function stabilizeSupportConfig( unstableConfig, stableSupportKey ) { - const stableConfig = {}; - for ( const [ key, value ] of Object.entries( unstableConfig ) ) { - // Get stable key from support-specific map, common properties map, or keep original. - const stableKey = - EXPERIMENTAL_SUPPORT_PROPERTIES[ stableSupportKey ]?.[ key ] ?? - COMMON_EXPERIMENTAL_PROPERTIES[ key ] ?? - key; - - stableConfig[ stableKey ] = value; - - /* - * The `__experimentalSkipSerialization` key needs to be kept until - * WP 6.8 becomes the minimum supported version. This is due to the - * core `wp_should_skip_block_supports_serialization` function only - * checking for `__experimentalSkipSerialization` in earlier versions. - */ - if ( - key === '__experimentalSkipSerialization' || - key === 'skipSerialization' - ) { - stableConfig.__experimentalSkipSerialization = value; - } - } - return stableConfig; -} - -/** - * Stabilizes experimental block supports by converting experimental keys and properties - * to their stable equivalents. - * - * @param {Object|undefined} rawSupports The block supports configuration to stabilize. - * @return {Object|undefined} The stabilized block supports configuration. - */ -function stabilizeSupports( rawSupports ) { - if ( ! rawSupports ) { - return rawSupports; - } - - /* - * Create a new object to avoid mutating the original. This ensures that - * custom block plugins that rely on immutable supports are not affected. - * See: https://github.com/WordPress/gutenberg/pull/66849#issuecomment-2463614281 - */ - const newSupports = {}; - const done = {}; - - for ( const [ support, config ] of Object.entries( rawSupports ) ) { - /* - * If this support config has already been stabilized, skip it. - * A stable support key occurring after an experimental key, gets - * stabilized then so that the two configs can be merged effectively. - */ - if ( done[ support ] ) { - continue; - } - - const stableSupportKey = - EXPERIMENTAL_SUPPORTS_MAP[ support ] ?? support; - - /* - * Use the support's config as is when it's not in need of stabilization. - * A support does not need stabilization if: - * - The support key doesn't need stabilization AND - * - Either: - * - The config isn't an object, so can't have experimental properties OR - * - The config is an object but has no experimental properties to stabilize. - */ - if ( - support === stableSupportKey && - ( ! isPlainObject( config ) || - ( ! EXPERIMENTAL_SUPPORT_PROPERTIES[ stableSupportKey ] && - Object.keys( config ).every( - ( key ) => ! COMMON_EXPERIMENTAL_PROPERTIES[ key ] - ) ) ) - ) { - newSupports[ support ] = config; - continue; - } - - // Stabilize the config value. - const stableConfig = isPlainObject( config ) - ? stabilizeSupportConfig( config, stableSupportKey ) - : config; - - /* - * If a plugin overrides the support config with the `blocks.registerBlockType` - * filter, both experimental and stable configs may be present. In that case, - * use the order keys are defined in to determine the final value. - * - If config is an array, merge the arrays in their order of definition. - * - If config is not an array, use the value defined last. - * - * The reason for preferring the last defined key is that after filters - * are applied, the last inserted key is likely the most up-to-date value. - * We cannot determine with certainty which value was "last modified" so - * the insertion order is the best guess. The extreme edge case of multiple - * filters tweaking the same support property will become less over time as - * extenders migrate existing blocks and plugins to stable keys. - */ - if ( - support !== stableSupportKey && - Object.hasOwn( rawSupports, stableSupportKey ) - ) { - const keyPositions = Object.keys( rawSupports ).reduce( - ( acc, key, index ) => { - acc[ key ] = index; - return acc; - }, - {} - ); - const experimentalFirst = - ( keyPositions[ support ] ?? Number.MAX_VALUE ) < - ( keyPositions[ stableSupportKey ] ?? Number.MAX_VALUE ); - - if ( isPlainObject( rawSupports[ stableSupportKey ] ) ) { - /* - * To merge the alternative support config effectively, it also needs to be - * stabilized before merging to keep stabilized and experimental flags in sync. - */ - rawSupports[ stableSupportKey ] = stabilizeSupportConfig( - rawSupports[ stableSupportKey ], - stableSupportKey - ); - newSupports[ stableSupportKey ] = experimentalFirst - ? { ...stableConfig, ...rawSupports[ stableSupportKey ] } - : { ...rawSupports[ stableSupportKey ], ...stableConfig }; - // Prevents reprocessing this support as it was merged above. - done[ stableSupportKey ] = true; - } else { - newSupports[ stableSupportKey ] = experimentalFirst - ? rawSupports[ stableSupportKey ] - : stableConfig; - } - } else { - newSupports[ stableSupportKey ] = stableConfig; - } - } - - return newSupports; -} - /** * Takes the unprocessed block type settings, merges them with block type metadata * and applies all the existing filters for the registered block type. @@ -257,9 +102,6 @@ export const processBlockType = ), }; - // Stabilize any experimental supports before applying filters. - blockType.supports = stabilizeSupports( blockType.supports ); - const settings = applyFilters( 'blocks.registerBlockType', blockType, @@ -267,10 +109,6 @@ export const processBlockType = null ); - // Re-stabilize any experimental supports after applying filters. - // This ensures that any supports updated by filters are also stabilized. - blockType.supports = stabilizeSupports( blockType.supports ); - if ( settings.description && typeof settings.description !== 'string' @@ -281,40 +119,29 @@ export const processBlockType = } if ( settings.deprecated ) { - settings.deprecated = settings.deprecated.map( ( deprecation ) => { - // Stabilize any experimental supports before applying filters. - let filteredDeprecation = { - ...deprecation, - supports: stabilizeSupports( deprecation.supports ), - }; - - filteredDeprecation = // Only keep valid deprecation keys. - applyFilters( - 'blocks.registerBlockType', - // Merge deprecation keys with pre-filter settings - // so that filters that depend on specific keys being - // present don't fail. - { - // Omit deprecation keys here so that deprecations - // can opt out of specific keys like "supports". - ...omit( blockType, DEPRECATED_ENTRY_KEYS ), - ...filteredDeprecation, - }, - blockType.name, - filteredDeprecation - ); - // Re-stabilize any experimental supports after applying filters. - // This ensures that any supports updated by filters are also stabilized. - filteredDeprecation.supports = stabilizeSupports( - filteredDeprecation.supports - ); - - return Object.fromEntries( - Object.entries( filteredDeprecation ).filter( ( [ key ] ) => + settings.deprecated = settings.deprecated.map( ( deprecation ) => + Object.fromEntries( + Object.entries( + // Only keep valid deprecation keys. + applyFilters( + 'blocks.registerBlockType', + // Merge deprecation keys with pre-filter settings + // so that filters that depend on specific keys being + // present don't fail. + { + // Omit deprecation keys here so that deprecations + // can opt out of specific keys like "supports". + ...omit( blockType, DEPRECATED_ENTRY_KEYS ), + ...deprecation, + }, + blockType.name, + deprecation + ) + ).filter( ( [ key ] ) => DEPRECATED_ENTRY_KEYS.includes( key ) ) - ); - } ); + ) + ); } if ( ! isPlainObject( settings ) ) { diff --git a/packages/blocks/src/store/test/private-selectors.js b/packages/blocks/src/store/test/private-selectors.js index 2c173b96b0bcb..ada2bd7c8cbcf 100644 --- a/packages/blocks/src/store/test/private-selectors.js +++ b/packages/blocks/src/store/test/private-selectors.js @@ -127,12 +127,12 @@ describe( 'private selectors', () => { name: 'core/example-block', supports: { typography: { - fontFamily: true, - fontStyle: true, - fontWeight: true, - textDecoration: true, - textTransform: true, - letterSpacing: true, + __experimentalFontFamily: true, + __experimentalFontStyle: true, + __experimentalFontWeight: true, + __experimentalTextDecoration: true, + __experimentalTextTransform: true, + __experimentalLetterSpacing: true, fontSize: true, lineHeight: true, }, diff --git a/packages/blocks/src/store/test/process-block-type.js b/packages/blocks/src/store/test/process-block-type.js deleted file mode 100644 index 82b2c1ad3080d..0000000000000 --- a/packages/blocks/src/store/test/process-block-type.js +++ /dev/null @@ -1,490 +0,0 @@ -/** - * WordPress dependencies - */ -import { addFilter, removeFilter } from '@wordpress/hooks'; - -/** - * Internal dependencies - */ -import { processBlockType } from '../process-block-type'; - -describe( 'processBlockType', () => { - const baseBlockSettings = { - apiVersion: 3, - attributes: {}, - edit: () => null, - name: 'test/block', - save: () => null, - title: 'Test Block', - }; - - const select = { - getBootstrappedBlockType: () => null, - }; - - afterEach( () => { - removeFilter( 'blocks.registerBlockType', 'test/filterSupports' ); - } ); - - it( 'should stabilize experimental block supports', () => { - const blockSettings = { - ...baseBlockSettings, - supports: { - typography: { - fontSize: true, - lineHeight: true, - __experimentalFontFamily: true, - __experimentalFontStyle: true, - __experimentalFontWeight: true, - __experimentalLetterSpacing: true, - __experimentalTextTransform: true, - __experimentalTextDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, - }, - __experimentalBorder: { - color: true, - radius: true, - style: true, - width: true, - __experimentalDefaultControls: { - color: true, - radius: true, - style: true, - width: true, - }, - }, - }, - }; - - const processedBlockType = processBlockType( - 'test/block', - blockSettings - )( { select } ); - - expect( processedBlockType.supports ).toMatchObject( { - typography: { - fontSize: true, - lineHeight: true, - fontFamily: true, - fontStyle: true, - fontWeight: true, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - defaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, - }, - border: { - color: true, - radius: true, - style: true, - width: true, - defaultControls: { - color: true, - radius: true, - style: true, - width: true, - }, - }, - } ); - } ); - - it( 'should reapply transformations after supports are filtered', () => { - const blockSettings = { - ...baseBlockSettings, - supports: { - typography: { - fontSize: true, - lineHeight: true, - __experimentalFontFamily: true, - __experimentalFontStyle: true, - __experimentalFontWeight: true, - __experimentalLetterSpacing: true, - __experimentalTextTransform: true, - __experimentalTextDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, - }, - __experimentalBorder: { - color: true, - radius: true, - style: true, - width: true, - __experimentalDefaultControls: { - color: true, - radius: true, - style: true, - width: true, - }, - }, - }, - }; - - addFilter( - 'blocks.registerBlockType', - 'test/filterSupports', - ( settings, name ) => { - if ( name === 'test/block' && settings.supports.typography ) { - settings.supports.typography.__experimentalFontFamily = false; - settings.supports.typography.__experimentalFontStyle = false; - settings.supports.typography.__experimentalFontWeight = false; - if ( ! settings.supports.__experimentalBorder ) { - settings.supports.__experimentalBorder = {}; - } - settings.supports.__experimentalBorder.radius = false; - } - return settings; - } - ); - - const processedBlockType = processBlockType( - 'test/block', - blockSettings - )( { select } ); - - expect( processedBlockType.supports ).toMatchObject( { - typography: { - fontSize: true, - lineHeight: true, - fontFamily: false, - fontStyle: false, - fontWeight: false, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - defaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, - }, - border: { - color: true, - radius: false, - style: true, - width: true, - defaultControls: { - color: true, - radius: true, - style: true, - width: true, - }, - }, - } ); - } ); - - describe( 'block deprecations', () => { - const deprecatedBlockSettings = { - ...baseBlockSettings, - supports: { - typography: { - fontSize: true, - lineHeight: true, - fontFamily: true, - fontStyle: true, - fontWeight: true, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - __experimentalDefaultControls: { - fontSize: true, - fontAppearance: true, - textTransform: true, - }, - }, - border: { - color: true, - radius: true, - style: true, - width: true, - __experimentalDefaultControls: { - color: true, - radius: true, - style: true, - width: true, - }, - }, - }, - deprecated: [ - { - supports: { - typography: { - __experimentalFontFamily: true, - __experimentalFontStyle: true, - __experimentalFontWeight: true, - __experimentalLetterSpacing: true, - __experimentalTextTransform: true, - __experimentalTextDecoration: true, - __experimentalWritingMode: true, - }, - __experimentalBorder: { - color: true, - radius: true, - style: true, - width: true, - __experimentalDefaultControls: { - color: true, - radius: true, - style: true, - width: true, - }, - }, - }, - }, - ], - }; - - beforeEach( () => { - // Freeze the deprecated block object and its supports so that the original is not mutated. - Object.freeze( deprecatedBlockSettings.deprecated[ 0 ] ); - Object.freeze( deprecatedBlockSettings.deprecated[ 0 ].supports ); - } ); - - it( 'should stabilize experimental supports', () => { - const processedBlockType = processBlockType( - 'test/block', - deprecatedBlockSettings - )( { select } ); - - expect( processedBlockType.deprecated[ 0 ].supports ).toMatchObject( - { - typography: { - fontFamily: true, - fontStyle: true, - fontWeight: true, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - }, - border: { - color: true, - radius: true, - style: true, - width: true, - defaultControls: { - color: true, - radius: true, - style: true, - width: true, - }, - }, - } - ); - } ); - - it( 'should reapply transformations after supports are filtered', () => { - addFilter( - 'blocks.registerBlockType', - 'test/filterSupports', - ( settings, name ) => { - if ( - name === 'test/block' && - settings.supports.typography - ) { - settings.supports.typography.__experimentalFontFamily = false; - settings.supports.typography.__experimentalFontStyle = false; - settings.supports.typography.__experimentalFontWeight = false; - settings.supports.__experimentalBorder = { - radius: false, - }; - } - return settings; - } - ); - - const processedBlockType = processBlockType( - 'test/block', - deprecatedBlockSettings - )( { select } ); - - expect( processedBlockType.deprecated[ 0 ].supports ).toMatchObject( - { - typography: { - fontFamily: false, - fontStyle: false, - fontWeight: false, - letterSpacing: true, - textTransform: true, - textDecoration: true, - __experimentalWritingMode: true, - }, - border: { - color: true, - radius: false, - style: true, - width: true, - defaultControls: { - color: true, - radius: true, - style: true, - width: true, - }, - }, - } - ); - } ); - } ); - - it( 'should stabilize common experimental properties across all supports', () => { - const blockSettings = { - ...baseBlockSettings, - supports: { - typography: { - fontSize: true, - __experimentalDefaultControls: { - fontSize: true, - }, - __experimentalSkipSerialization: true, - }, - spacing: { - padding: true, - __experimentalDefaultControls: { - padding: true, - }, - __experimentalSkipSerialization: true, - }, - }, - }; - - const processedBlockType = processBlockType( - 'test/block', - blockSettings - )( { select } ); - - expect( processedBlockType.supports ).toMatchObject( { - typography: { - fontSize: true, - defaultControls: { - fontSize: true, - }, - skipSerialization: true, - __experimentalSkipSerialization: true, - }, - spacing: { - padding: true, - defaultControls: { - padding: true, - }, - skipSerialization: true, - __experimentalSkipSerialization: true, - }, - } ); - } ); - - it( 'should merge experimental and stable keys in order of definition', () => { - const blockSettings = { - ...baseBlockSettings, - supports: { - __experimentalBorder: { - color: true, - radius: false, - }, - border: { - color: false, - style: true, - }, - }, - }; - - const processedBlockType = processBlockType( - 'test/block', - blockSettings - )( { select } ); - - expect( processedBlockType.supports ).toMatchObject( { - border: { - color: false, - radius: false, - style: true, - }, - } ); - - const reversedSettings = { - ...baseBlockSettings, - supports: { - border: { - color: false, - style: true, - }, - __experimentalBorder: { - color: true, - radius: false, - }, - }, - }; - - const reversedProcessedType = processBlockType( - 'test/block', - reversedSettings - )( { select } ); - - expect( reversedProcessedType.supports ).toMatchObject( { - border: { - color: true, - radius: false, - style: true, - }, - } ); - } ); - - it( 'should handle non-object config values', () => { - const blockSettings = { - ...baseBlockSettings, - supports: { - __experimentalBorder: true, - border: false, - }, - }; - - const processedBlockType = processBlockType( - 'test/block', - blockSettings - )( { select } ); - - expect( processedBlockType.supports ).toMatchObject( { - border: false, - } ); - } ); - - it( 'should not modify supports that do not need stabilization', () => { - const blockSettings = { - ...baseBlockSettings, - supports: { - align: true, - spacing: { - padding: true, - margin: true, - }, - }, - }; - - const processedBlockType = processBlockType( - 'test/block', - blockSettings - )( { select } ); - - expect( processedBlockType.supports ).toMatchObject( { - align: true, - spacing: { - padding: true, - margin: true, - }, - } ); - } ); -} ); diff --git a/packages/browserslist-config/CHANGELOG.md b/packages/browserslist-config/CHANGELOG.md index 770dd74df0fcc..4660344c056ec 100644 --- a/packages/browserslist-config/CHANGELOG.md +++ b/packages/browserslist-config/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.15.0 (2025-01-02) + ## 6.14.0 (2024-12-11) ## 6.13.0 (2024-11-27) diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index 2d9520b2adb45..0b0a3799c5017 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/browserslist-config", - "version": "6.14.0", + "version": "6.15.0", "description": "WordPress Browserslist shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/commands/CHANGELOG.md b/packages/commands/CHANGELOG.md index 4bc86b8f433f2..2c6f05046402c 100644 --- a/packages/commands/CHANGELOG.md +++ b/packages/commands/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.15.0 (2025-01-02) + ## 1.14.0 (2024-12-11) ## 1.13.0 (2024-11-27) diff --git a/packages/commands/package.json b/packages/commands/package.json index 9f7d1ea1e8931..0316fc525b0d8 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/commands", - "version": "1.14.0", + "version": "1.15.1", "description": "Handles the commands menu.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,13 +29,13 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/private-apis": "*", + "@wordpress/components": "file:../components", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/private-apis": "file:../private-apis", "clsx": "^2.1.1", "cmdk": "^1.0.0" }, diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index db8b0f749217f..d9b0896229678 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,9 +2,14 @@ ## Unreleased +## 29.1.0 (2025-01-02) + ### Enhancements - `BoxControl`: Add presets support ([#67688](https://github.com/WordPress/gutenberg/pull/67688)). +- `Navigation`: Upsize back buttons ([#68157](https://github.com/WordPress/gutenberg/pull/68157)). +- `Heading`: Fix text contrast for dark mode ([#68349](https://github.com/WordPress/gutenberg/pull/68349)). +- `Text`: Fix text contrast for dark mode ([#68349](https://github.com/WordPress/gutenberg/pull/68349)). ### Deprecations @@ -13,19 +18,25 @@ - `InputControl`: Deprecate 36px default size ([#66897](https://github.com/WordPress/gutenberg/pull/66897)). - `RadioGroup`: Log deprecation warning ([#68067](https://github.com/WordPress/gutenberg/pull/68067)). - Soft deprecate `ButtonGroup` component. Use `ToggleGroupControl` instead ([#65429](https://github.com/WordPress/gutenberg/pull/65429)). +- `Navigation`: Log deprecation warning for removal in WP 7.1. Use `Navigator` instead ([#68158](https://github.com/WordPress/gutenberg/pull/68158)). ### Bug Fixes - `BoxControl`: Better respect for the `min` prop in the Range Slider ([#67819](https://github.com/WordPress/gutenberg/pull/67819)). +- `FontSizePicker`: Add `display:contents` rule to fix overflowing text in the custom size select. ([#68280](https://github.com/WordPress/gutenberg/pull/68280)). +- `BoxControl`: Fix aria-valuetext value ([#68362](https://github.com/WordPress/gutenberg/pull/68362)). ### Experimental - Add new `Badge` component ([#66555](https://github.com/WordPress/gutenberg/pull/66555)). - `Menu`: refactor to more granular sub-components ([#67422](https://github.com/WordPress/gutenberg/pull/67422)). +- `Badge`: Support text truncation ([#68107](https://github.com/WordPress/gutenberg/pull/68107)). ### Internal - `SlotFill`: rewrite the non-portal version to use `observableMap` ([#67400](https://github.com/WordPress/gutenberg/pull/67400)). +- `DatePicker`: Prepare day buttons for 40px default size ([#68156](https://github.com/WordPress/gutenberg/pull/68156)). +- `SlotFill`: register slots in a layout effect ([#68176](https://github.com/WordPress/gutenberg/pull/68176)). ## 29.0.0 (2024-12-11) diff --git a/packages/components/package.json b/packages/components/package.json index 79df8e92d84b6..eef3ee7435e8d 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "29.0.0", + "version": "29.1.1", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -44,23 +44,23 @@ "@types/gradient-parser": "0.1.3", "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.3.1", - "@wordpress/a11y": "*", - "@wordpress/compose": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keycodes": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/warning": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/compose": "file:../compose", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.7.0", diff --git a/packages/components/src/alignment-matrix-control/README.md b/packages/components/src/alignment-matrix-control/README.md index af97e3ae0607c..8ba9f6378c185 100644 --- a/packages/components/src/alignment-matrix-control/README.md +++ b/packages/components/src/alignment-matrix-control/README.md @@ -21,47 +21,48 @@ const Example = () => { ); }; ``` + ## Props ### `defaultValue` -If provided, sets the default alignment value. - - Type: `"center" | "top left" | "top center" | "top right" | "center left" | "center center" | "center right" | "bottom left" | "bottom center" | "bottom right"` - Required: No - Default: `'center center'` -### `label` +If provided, sets the default alignment value. -Accessible label. If provided, sets the `aria-label` attribute of the -underlying `grid` widget. +### `label` - Type: `string` - Required: No - Default: `'Alignment Matrix Control'` -### `onChange` +Accessible label. If provided, sets the `aria-label` attribute of the +underlying `grid` widget. -A function that receives the updated alignment value. +### `onChange` - Type: `(newValue: AlignmentMatrixControlValue) => void` - Required: No -### `value` +A function that receives the updated alignment value. -The current alignment value. +### `value` - Type: `"center" | "top left" | "top center" | "top right" | "center left" | "center center" | "center right" | "bottom left" | "bottom center" | "bottom right"` - Required: No -### `width` +The current alignment value. -If provided, sets the width of the control. +### `width` - Type: `number` - Required: No - Default: `92` +If provided, sets the width of the control. + ## Subcomponents ### AlignmentMatrixControl.Icon @@ -70,16 +71,16 @@ If provided, sets the width of the control. ##### `disablePointerEvents` -If `true`, disables pointer events on the icon. - - Type: `boolean` - Required: No - Default: `true` -##### `value` +If `true`, disables pointer events on the icon. -The current alignment value. +##### `value` - Type: `"center" | "top left" | "top center" | "top right" | "center left" | "center center" | "center right" | "bottom left" | "bottom center" | "bottom right"` - Required: No - Default: `center` + +The current alignment value. diff --git a/packages/components/src/angle-picker-control/README.md b/packages/components/src/angle-picker-control/README.md index d9389c6564338..9908282fd9ef9 100644 --- a/packages/components/src/angle-picker-control/README.md +++ b/packages/components/src/angle-picker-control/README.md @@ -23,34 +23,35 @@ function Example() { ); } ``` + ## Props ### `as` -The HTML element or React component to render the component as. - - Type: `"symbol" | "object" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | ...` - Required: No -### `label` +The HTML element or React component to render the component as. -Label to use for the angle picker. +### `label` - Type: `string` - Required: No - Default: `__( 'Angle' )` -### `onChange` +Label to use for the angle picker. -A function that receives the new value of the input. +### `onChange` - Type: `(value: number) => void` - Required: Yes -### `value` +A function that receives the new value of the input. -The current value of the input. The value represents an angle in degrees -and should be a value between 0 and 360. +### `value` - Type: `string | number` - Required: Yes + +The current value of the input. The value represents an angle in degrees +and should be a value between 0 and 360. diff --git a/packages/components/src/badge/README.md b/packages/components/src/badge/README.md index 0be531ca6f2df..2100939684a85 100644 --- a/packages/components/src/badge/README.md +++ b/packages/components/src/badge/README.md @@ -2,21 +2,23 @@ <!-- This file is generated automatically and cannot be edited directly. Make edits via TypeScript types and TSDocs. --> +🔒 This component is locked as a [private API](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-private-apis/). We do not yet recommend using this outside of the Gutenberg project. + <p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-badge--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p> ## Props ### `children` -Text to display inside the badge. - - Type: `string` - Required: Yes -### `intent` +Text to display inside the badge. -Badge variant. +### `intent` - Type: `"default" | "info" | "success" | "warning" | "error"` - Required: No - Default: `default` + +Badge variant. diff --git a/packages/components/src/badge/index.tsx b/packages/components/src/badge/index.tsx index 8a55f3881215f..ee08003c3911d 100644 --- a/packages/components/src/badge/index.tsx +++ b/packages/components/src/badge/index.tsx @@ -56,9 +56,10 @@ function Badge( { icon={ contextBasedIcon() } size={ 16 } fill="currentColor" + className="components-badge__icon" /> ) } - { children } + <span className="components-badge__content">{ children }</span> </span> ); } diff --git a/packages/components/src/badge/stories/index.story.tsx b/packages/components/src/badge/stories/index.story.tsx index 7f827d3bfabf5..bbe0bef2a7947 100644 --- a/packages/components/src/badge/stories/index.story.tsx +++ b/packages/components/src/badge/stories/index.story.tsx @@ -8,12 +8,12 @@ import type { Meta, StoryObj } from '@storybook/react'; */ import Badge from '..'; -const meta = { +const meta: Meta< typeof Badge > = { component: Badge, title: 'Components/Containers/Badge', id: 'components-badge', tags: [ 'status-private' ], -} satisfies Meta< typeof Badge >; +}; export default meta; diff --git a/packages/components/src/badge/styles.scss b/packages/components/src/badge/styles.scss index e1e9cd5312d11..d3f82482cf774 100644 --- a/packages/components/src/badge/styles.scss +++ b/packages/components/src/badge/styles.scss @@ -6,17 +6,18 @@ $badge-colors: ( ); .components-badge { + @include reset; + background-color: color-mix(in srgb, $white 90%, var(--base-color)); color: color-mix(in srgb, $black 50%, var(--base-color)); padding: 0 $grid-unit-10; min-height: $grid-unit-30; + max-width: 100%; border-radius: $radius-small; font-size: $font-size-small; font-weight: 400; - flex-shrink: 0; line-height: $font-line-height-small; - width: fit-content; - display: flex; + display: inline-flex; align-items: center; gap: 2px; @@ -36,3 +37,13 @@ $badge-colors: ( } } } + +.components-badge__icon { + flex-shrink: 0; +} + +.components-badge__content { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} diff --git a/packages/components/src/badge/test/index.tsx b/packages/components/src/badge/test/index.tsx index 47c832eb3c830..114a8f426c7af 100644 --- a/packages/components/src/badge/test/index.tsx +++ b/packages/components/src/badge/test/index.tsx @@ -6,12 +6,17 @@ import { render, screen } from '@testing-library/react'; /** * Internal dependencies */ -import Badge from '..'; +import _Badge from '..'; + +const testid = 'my-badge'; +const Badge = ( props: React.ComponentProps< typeof _Badge > ) => ( + <_Badge data-testid={ testid } { ...props } /> +); describe( 'Badge', () => { it( 'should render correctly with default props', () => { render( <Badge>Code is Poetry</Badge> ); - const badge = screen.getByText( 'Code is Poetry' ); + const badge = screen.getByTestId( testid ); expect( badge ).toBeInTheDocument(); expect( badge.tagName ).toBe( 'SPAN' ); expect( badge ).toHaveClass( 'components-badge' ); @@ -19,14 +24,14 @@ describe( 'Badge', () => { it( 'should render as per its intent and contain an icon', () => { render( <Badge intent="error">Code is Poetry</Badge> ); - const badge = screen.getByText( 'Code is Poetry' ); + const badge = screen.getByTestId( testid ); expect( badge ).toHaveClass( 'components-badge', 'is-error' ); expect( badge ).toHaveClass( 'has-icon' ); } ); it( 'should combine custom className with default class', () => { render( <Badge className="custom-class">Code is Poetry</Badge> ); - const badge = screen.getByText( 'Code is Poetry' ); + const badge = screen.getByTestId( testid ); expect( badge ).toHaveClass( 'components-badge' ); expect( badge ).toHaveClass( 'custom-class' ); } ); diff --git a/packages/components/src/base-control/README.md b/packages/components/src/base-control/README.md index 839464b41260b..2a82c19845e47 100644 --- a/packages/components/src/base-control/README.md +++ b/packages/components/src/base-control/README.md @@ -25,71 +25,71 @@ const MyCustomTextareaControl = ({ children, ...baseProps }) => ( ); ); ``` + ## Props ### `__nextHasNoMarginBottom` -Start opting into the new margin-free styles that will become the default in a future version. - - Type: `boolean` - Required: No - Default: `false` -### `as` +Start opting into the new margin-free styles that will become the default in a future version. -The HTML element or React component to render the component as. +### `as` - Type: `"symbol" | "object" | "label" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | ... 516 more ... | ("view" & FunctionComponent<...>)` - Required: No -### `className` +The HTML element or React component to render the component as. +### `className` - Type: `string` - Required: No ### `children` -The content to be displayed within the `BaseControl`. - - Type: `ReactNode` - Required: Yes +The content to be displayed within the `BaseControl`. + ### `help` + - Type: `ReactNode` + - Required: No + Additional description for the control. Only use for meaningful description or instructions for the control. An element containing the description will be programmatically associated to the BaseControl by the means of an `aria-describedby` attribute. - - Type: `ReactNode` - - Required: No - ### `hideLabelFromVision` -If true, the label will only be visible to screen readers. - - Type: `boolean` - Required: No - Default: `false` +If true, the label will only be visible to screen readers. + ### `id` + - Type: `string` + - Required: No + The HTML `id` of the control element (passed in as a child to `BaseControl`) to which labels and help text are being generated. This is necessary to accessibly associate the label with that element. The recommended way is to use the `useBaseControlProps` hook, which takes care of generating a unique `id` for you. Otherwise, if you choose to pass an explicit `id` to this prop, you are responsible for ensuring the uniqueness of the `id`. - - Type: `string` - - Required: No - ### `label` -If this property is added, a label will be generated using label property as the content. - - Type: `ReactNode` - Required: No +If this property is added, a label will be generated using label property as the content. + ## Subcomponents ### BaseControl.VisualLabel @@ -113,18 +113,19 @@ const MyBaseControl = () => ( </BaseControl> ); ``` + #### Props ##### `as` -The HTML element or React component to render the component as. - - Type: `"symbol" | "object" | "label" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | ...` - Required: No -##### `children` +The HTML element or React component to render the component as. -The content to be displayed within the `BaseControl.VisualLabel`. +##### `children` - Type: `ReactNode` - Required: Yes + +The content to be displayed within the `BaseControl.VisualLabel`. diff --git a/packages/components/src/box-control/README.md b/packages/components/src/box-control/README.md index da08cafceee42..4c0f100065092 100644 --- a/packages/components/src/box-control/README.md +++ b/packages/components/src/box-control/README.md @@ -28,34 +28,33 @@ function Example() { ); }; ``` + ## Props ### `__next40pxDefaultSize` -Start opting into the larger default height that will become the default size in a future version. - - Type: `boolean` - Required: No - Default: `false` -### `allowReset` +Start opting into the larger default height that will become the default size in a future version. -If this property is true, a button to reset the box control is rendered. +### `allowReset` - Type: `boolean` - Required: No - Default: `true` -### `id` +If this property is true, a button to reset the box control is rendered. -The id to use as a base for the unique HTML id attribute of the control. +### `id` - Type: `string` - Required: No -### `inputProps` +The id to use as a base for the unique HTML id attribute of the control. -Props for the internal `UnitControl` components. +### `inputProps` - Type: `UnitControlPassthroughProps` - Required: No @@ -63,42 +62,42 @@ Props for the internal `UnitControl` components. min: 0, }` -### `label` +Props for the internal `UnitControl` components. -Heading label for the control. +### `label` - Type: `string` - Required: No - Default: `__( 'Box Control' )` -### `onChange` +Heading label for the control. -A callback function when an input value changes. +### `onChange` - Type: `(next: BoxControlValue) => void` - Required: No - Default: `() => {}` -### `presets` +A callback function when an input value changes. -Available presets to pick from. +### `presets` - Type: `Preset[]` - Required: No +Available presets to pick from. + ### `presetKey` + - Type: `string` + - Required: No + The key of the preset to apply. If you provide a list of presets, you must provide a preset key to use. The format of preset selected values is going to be `var:preset|${ presetKey }|${ presetSlug }` - - Type: `string` - - Required: No - ### `resetValues` -The `top`, `right`, `bottom`, and `left` box dimension values to use when the control is reset. - - Type: `BoxControlValue` - Required: No - Default: `{ @@ -108,35 +107,37 @@ The `top`, `right`, `bottom`, and `left` box dimension values to use when the co left: undefined, }` +The `top`, `right`, `bottom`, and `left` box dimension values to use when the control is reset. + ### `sides` + - Type: `readonly (keyof BoxControlValue | "horizontal" | "vertical")[]` + - Required: No + Collection of sides to allow control of. If omitted or empty, all sides will be available. Allowed values are "top", "right", "bottom", "left", "vertical", and "horizontal". - - Type: `readonly (keyof BoxControlValue | "horizontal" | "vertical")[]` - - Required: No - ### `splitOnAxis` -If this property is true, when the box control is unlinked, vertical and horizontal controls -can be used instead of updating individual sides. - - Type: `boolean` - Required: No - Default: `false` -### `units` +If this property is true, when the box control is unlinked, vertical and horizontal controls +can be used instead of updating individual sides. -Available units to select from. +### `units` - Type: `WPUnitControlUnit[]` - Required: No - Default: `CSS_UNITS` -### `values` +Available units to select from. -The current values of the control, expressed as an object of `top`, `right`, `bottom`, and `left` values. +### `values` - Type: `BoxControlValue` - Required: No + +The current values of the control, expressed as an object of `top`, `right`, `bottom`, and `left` values. diff --git a/packages/components/src/box-control/input-control.tsx b/packages/components/src/box-control/input-control.tsx index 81fbcad42c1d0..27dff1991d857 100644 --- a/packages/components/src/box-control/input-control.tsx +++ b/packages/components/src/box-control/input-control.tsx @@ -264,7 +264,7 @@ export default function BoxInputControl( { } aria-valuetext={ marks[ presetIndex !== undefined ? presetIndex + 1 : 0 ] - .label + .tooltip } renderTooltipContent={ ( index ) => marks[ ! index ? 0 : index ].tooltip diff --git a/packages/components/src/button/README.md b/packages/components/src/button/README.md index 99a6d0f9c24cf..c67c795addbf4 100644 --- a/packages/components/src/button/README.md +++ b/packages/components/src/button/README.md @@ -17,19 +17,24 @@ const Mybutton = () => ( </Button> ); ``` + ## Props ### `__next40pxDefaultSize` + - Type: `boolean` + - Required: No + - Default: `false` + Start opting into the larger default height that will become the default size in a future version. +### `accessibleWhenDisabled` + - Type: `boolean` - Required: No - Default: `false` -### `accessibleWhenDisabled` - Whether to keep the button focusable when disabled. In most cases, it is recommended to set this to `true`. Disabling a control without maintaining focusability @@ -39,111 +44,111 @@ or by preventing focus from returning to a trigger element. Learn more about the [focusability of disabled controls](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols) in the WAI-ARIA Authoring Practices Guide. - - Type: `boolean` - - Required: No - - Default: `false` - ### `children` -The button's children. - - Type: `ReactNode` - Required: No -### `description` +The button's children. -A visually hidden accessible description for the button. +### `description` - Type: `string` - Required: No +A visually hidden accessible description for the button. + ### `disabled` + - Type: `boolean` + - Required: No + Whether the button is disabled. If `true`, this will force a `button` element to be rendered, even when an `href` is given. In most cases, it is recommended to also set the `accessibleWhenDisabled` prop to `true`. - - Type: `boolean` - - Required: No - ### `href` -If provided, renders `a` instead of `button`. - - Type: `string` - Required: Yes -### `icon` +If provided, renders `a` instead of `button`. -If provided, renders an Icon component inside the button. +### `icon` - Type: `IconType` - Required: No -### `iconPosition` +If provided, renders an Icon component inside the button. -If provided with `icon`, sets the position of icon relative to the `text`. +### `iconPosition` - Type: `"left" | "right"` - Required: No - Default: `'left'` +If provided with `icon`, sets the position of icon relative to the `text`. + ### `iconSize` + - Type: `number` + - Required: No + If provided with `icon`, sets the icon size. Please refer to the Icon component for more details regarding the default value of its `size` prop. - - Type: `number` - - Required: No - ### `isBusy` -Indicates activity while a action is being performed. - - Type: `boolean` - Required: No -### `isDestructive` +Indicates activity while a action is being performed. -Renders a red text-based button style to indicate destructive behavior. +### `isDestructive` - Type: `boolean` - Required: No -### `isPressed` +Renders a red text-based button style to indicate destructive behavior. -Renders a pressed button style. +### `isPressed` - Type: `boolean` - Required: No -### `label` +Renders a pressed button style. -Sets the `aria-label` of the component, if none is provided. -Sets the Tooltip content if `showTooltip` is provided. +### `label` - Type: `string` - Required: No -### `shortcut` +Sets the `aria-label` of the component, if none is provided. +Sets the Tooltip content if `showTooltip` is provided. -If provided with `showTooltip`, appends the Shortcut label to the tooltip content. -If an object is provided, it should contain `display` and `ariaLabel` keys. +### `shortcut` - Type: `string | { display: string; ariaLabel: string; }` - Required: No -### `showTooltip` +If provided with `showTooltip`, appends the Shortcut label to the tooltip content. +If an object is provided, it should contain `display` and `ariaLabel` keys. -If provided, renders a Tooltip component for the button. +### `showTooltip` - Type: `boolean` - Required: No +If provided, renders a Tooltip component for the button. + ### `size` + - Type: `"small" | "default" | "compact"` + - Required: No + - Default: `'default'` + The size of the button. - `'default'`: For normal text-label buttons, unless it is a toggle button. @@ -152,34 +157,33 @@ The size of the button. If the deprecated `isSmall` prop is also defined, this prop will take precedence. - - Type: `"small" | "default" | "compact"` - - Required: No - - Default: `'default'` - ### `text` -If provided, displays the given text inside the button. If the button contains children elements, the text is displayed before them. - - Type: `string` - Required: No -### `tooltipPosition` +If provided, displays the given text inside the button. If the button contains children elements, the text is displayed before them. -If provided with `showTooltip`, sets the position of the tooltip. -Please refer to the Tooltip component for more details regarding the defaults. +### `tooltipPosition` - Type: `"top" | "middle" | "bottom" | "top center" | "top left" | "top right" | "middle center" | "middle left" | "middle right" | "bottom center" | ...` - Required: No -### `target` +If provided with `showTooltip`, sets the position of the tooltip. +Please refer to the Tooltip component for more details regarding the defaults. -If provided with `href`, sets the `target` attribute to the `a`. +### `target` - Type: `string` - Required: No +If provided with `href`, sets the `target` attribute to the `a`. + ### `variant` + - Type: `"link" | "primary" | "secondary" | "tertiary"` + - Required: No + Specifies the button's style. The accepted values are: @@ -188,6 +192,3 @@ The accepted values are: 2. `'secondary'` (the default button styles) 3. `'tertiary'` (the text-based button styles) 4. `'link'` (the link button styles) - - - Type: `"link" | "primary" | "secondary" | "tertiary"` - - Required: No diff --git a/packages/components/src/button/test/index.tsx b/packages/components/src/button/test/index.tsx index 8161e68c4e21b..664c755ac4404 100644 --- a/packages/components/src/button/test/index.tsx +++ b/packages/components/src/button/test/index.tsx @@ -6,19 +6,26 @@ import { render, screen } from '@testing-library/react'; /** * WordPress dependencies */ -import { createRef } from '@wordpress/element'; +import { createRef, forwardRef } from '@wordpress/element'; import { plusCircle } from '@wordpress/icons'; /** * Internal dependencies */ -import Button from '..'; +import _Button from '..'; import Tooltip from '../../tooltip'; import cleanupTooltip from '../../tooltip/test/utils'; import { press } from '@ariakit/test'; jest.mock( '../../icon', () => () => <div data-testid="test-icon" /> ); +const Button = forwardRef( + ( + props: React.ComponentProps< typeof _Button >, + ref: React.ForwardedRef< unknown > + ) => <_Button __next40pxDefaultSize { ...props } ref={ ref } /> +); + describe( 'Button', () => { describe( 'basic rendering', () => { it( 'should render a button element with only one class', () => { diff --git a/packages/components/src/date-time/date/index.tsx b/packages/components/src/date-time/date/index.tsx index ca093f9d70847..e7afcccf249dc 100644 --- a/packages/components/src/date-time/date/index.tsx +++ b/packages/components/src/date-time/date/index.tsx @@ -306,6 +306,7 @@ function Day( { return ( <DayButton + __next40pxDefaultSize ref={ ref } className="components-datetime__date__day" // Unused, for backwards compatibility. disabled={ isInvalid } diff --git a/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap b/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap index fd6cc2df3fcde..b1adfd5d9221a 100644 --- a/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap +++ b/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap @@ -63,7 +63,7 @@ exports[`DimensionControl rendering renders with custom sizes 1`] = ` } .emotion-12 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; @@ -345,7 +345,7 @@ exports[`DimensionControl rendering renders with defaults 1`] = ` } .emotion-12 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; @@ -637,7 +637,7 @@ exports[`DimensionControl rendering renders with icon and custom icon label 1`] } .emotion-12 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; @@ -941,7 +941,7 @@ exports[`DimensionControl rendering renders with icon and default icon label 1`] } .emotion-12 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; diff --git a/packages/components/src/drop-zone/stories/index.story.tsx b/packages/components/src/drop-zone/stories/index.story.tsx index 7e2dcbf03c03b..fe0be94e74fe8 100644 --- a/packages/components/src/drop-zone/stories/index.story.tsx +++ b/packages/components/src/drop-zone/stories/index.story.tsx @@ -21,7 +21,13 @@ export default meta; const Template: StoryFn< typeof DropZone > = ( props ) => { return ( - <div style={ { background: 'lightgray', padding: 16 } }> + <div + style={ { + background: 'lightgray', + padding: 32, + position: 'relative', + } } + > Drop something here <DropZone { ...props } /> </div> diff --git a/packages/components/src/font-size-picker/styles.ts b/packages/components/src/font-size-picker/styles.ts index f47ca41b51eb7..b0e33b5aea3a2 100644 --- a/packages/components/src/font-size-picker/styles.ts +++ b/packages/components/src/font-size-picker/styles.ts @@ -16,6 +16,7 @@ export const Container = styled.fieldset` border: 0; margin: 0; padding: 0; + display: contents; `; export const Header = styled( HStack )` diff --git a/packages/components/src/form-file-upload/README.md b/packages/components/src/form-file-upload/README.md index c6a7205815de5..74e6e36938338 100644 --- a/packages/components/src/form-file-upload/README.md +++ b/packages/components/src/form-file-upload/README.md @@ -19,60 +19,64 @@ const MyFormFileUpload = () => ( </FormFileUpload> ); ``` + ## Props ### `__next40pxDefaultSize` -Start opting into the larger default height that will become the default size in a future version. - - Type: `boolean` - Required: No - Default: `false` +Start opting into the larger default height that will become the default size in a future version. + ### `accept` + - Type: `string` + - Required: No + A string passed to the `input` element that tells the browser which [file types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers) can be uploaded by the user. e.g: `image/*,video/*`. - - Type: `string` - - Required: No - ### `children` -Children are passed as children of `Button`. - - Type: `ReactNode` - Required: No +Children are passed as children of `Button`. + ### `icon` + - Type: `IconType` + - Required: No + The icon to render in the default button. See the `Icon` component docs for more information. - - Type: `IconType` - - Required: No - ### `multiple` -Whether to allow multiple selection of files or not. - - Type: `boolean` - Required: No - Default: `false` +Whether to allow multiple selection of files or not. + ### `onChange` + - Type: `ChangeEventHandler<HTMLInputElement>` + - Required: Yes + Callback function passed directly to the `input` file element. Select files will be available in `event.currentTarget.files`. - - Type: `ChangeEventHandler<HTMLInputElement>` - - Required: Yes - ### `onClick` + - Type: `MouseEventHandler<HTMLInputElement>` + - Required: No + Callback function passed directly to the `input` file element. This can be useful when you want to force a `change` event to fire when @@ -89,17 +93,14 @@ an empty string in the `onClick` function. </FormFileUpload> ``` - - Type: `MouseEventHandler<HTMLInputElement>` - - Required: No - ### `render` + - Type: `(arg: { openFileDialog: () => void; }) => ReactNode` + - Required: No + Optional callback function used to render the UI. If passed, the component does not render the default UI (a button) and calls this function to render it. The function receives an object with property `openFileDialog`, a function that, when called, opens the browser native file upload modal window. - - - Type: `(arg: { openFileDialog: () => void; }) => ReactNode` - - Required: No diff --git a/packages/components/src/gradient-picker/README.md b/packages/components/src/gradient-picker/README.md index ec0210d03c0a4..275c46ec5958c 100644 --- a/packages/components/src/gradient-picker/README.md +++ b/packages/components/src/gradient-picker/README.md @@ -43,114 +43,115 @@ const MyGradientPicker = () => { ); }; ``` + ## Props ### `__experimentalIsRenderedInSidebar` -Whether this is rendered in the sidebar. - - Type: `boolean` - Required: No - Default: `false` -### `asButtons` +Whether this is rendered in the sidebar. -Whether the control should present as a set of buttons, -each with its own tab stop. +### `asButtons` - Type: `boolean` - Required: No - Default: `false` -### `aria-label` +Whether the control should present as a set of buttons, +each with its own tab stop. -A label to identify the purpose of the control. +### `aria-label` - Type: `string` - Required: No -### `aria-labelledby` +A label to identify the purpose of the control. -An ID of an element to provide a label for the control. +### `aria-labelledby` - Type: `string` - Required: No -### `className` +An ID of an element to provide a label for the control. -The class name added to the wrapper. +### `className` - Type: `string` - Required: No -### `clearable` +The class name added to the wrapper. -Whether the palette should have a clearing button or not. +### `clearable` - Type: `boolean` - Required: No - Default: `true` -### `disableCustomGradients` +Whether the palette should have a clearing button or not. -If true, the gradient picker will not be displayed and only defined -gradients from `gradients` will be shown. +### `disableCustomGradients` - Type: `boolean` - Required: No - Default: `false` -### `enableAlpha` +If true, the gradient picker will not be displayed and only defined +gradients from `gradients` will be shown. -Whether to enable alpha transparency options in the picker. +### `enableAlpha` - Type: `boolean` - Required: No - Default: `true` +Whether to enable alpha transparency options in the picker. + ### `gradients` + - Type: `GradientsProp` + - Required: No + - Default: `[]` + An array of objects as predefined gradients displayed above the gradient selector. Alternatively, if there are multiple sets (or 'origins') of gradients, you can pass an array of objects each with a `name` and a `gradients` array which will in turn contain the predefined gradient objects. - - Type: `GradientsProp` - - Required: No - - Default: `[]` - ### `headingLevel` -The heading level. Only applies in cases where gradients are provided -from multiple origins (i.e. when the array passed as the `gradients` prop -contains two or more items). - - Type: `1 | 2 | 3 | 4 | 5 | 6 | "1" | "2" | "3" | "4" | ...` - Required: No - Default: `2` -### `loop` +The heading level. Only applies in cases where gradients are provided +from multiple origins (i.e. when the array passed as the `gradients` prop +contains two or more items). -Prevents keyboard interaction from wrapping around. -Only used when `asButtons` is not true. +### `loop` - Type: `boolean` - Required: No - Default: `true` -### `onChange` +Prevents keyboard interaction from wrapping around. +Only used when `asButtons` is not true. -The function called when a new gradient has been defined. It is passed to -the `currentGradient` as an argument. +### `onChange` - Type: `(currentGradient: string) => void` - Required: Yes -### `value` +The function called when a new gradient has been defined. It is passed to +the `currentGradient` as an argument. -The current value of the gradient. Pass a css gradient string (See default value for example). -Optionally pass in a `null` value to specify no gradient is currently selected. +### `value` - Type: `string` - Required: No - Default: `'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)'` + +The current value of the gradient. Pass a css gradient string (See default value for example). +Optionally pass in a `null` value to specify no gradient is currently selected. diff --git a/packages/components/src/heading/hook.ts b/packages/components/src/heading/hook.ts index d242afe1fdb2f..132595d69c4f7 100644 --- a/packages/components/src/heading/hook.ts +++ b/packages/components/src/heading/hook.ts @@ -14,7 +14,7 @@ export function useHeading( const { as: asProp, level = 2, - color = COLORS.gray[ 900 ], + color = COLORS.theme.foreground, isBlock = true, weight = CONFIG.fontWeightHeading as import('react').CSSProperties[ 'fontWeight' ], ...otherProps diff --git a/packages/components/src/heading/test/__snapshots__/index.tsx.snap b/packages/components/src/heading/test/__snapshots__/index.tsx.snap index cf863c4b2bb2e..675810948404f 100644 --- a/packages/components/src/heading/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/heading/test/__snapshots__/index.tsx.snap @@ -2,12 +2,12 @@ exports[`props should render correctly 1`] = ` .emotion-0 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; text-wrap: pretty; - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); font-size: calc(1.95 * 13px); font-weight: 600; display: block; @@ -30,7 +30,7 @@ Snapshot Diff: @@ -1,10 +1,10 @@ Array [ Object { - "color": "#1e1e1e", + "color": "var(--wp-components-color-foreground, #1e1e1e)", "display": "block", - "font-size": "calc(1.25 * 13px)", + "font-size": "calc(1.95 * 13px)", @@ -49,7 +49,7 @@ Snapshot Diff: @@ -1,10 +1,10 @@ Array [ Object { - "color": "#1e1e1e", + "color": "var(--wp-components-color-foreground, #1e1e1e)", "display": "block", - "font-size": "calc(1.25 * 13px)", + "font-size": "calc(1.95 * 13px)", diff --git a/packages/components/src/icon/README.md b/packages/components/src/icon/README.md index 63d52c1fd20b1..2c9726dbcf541 100644 --- a/packages/components/src/icon/README.md +++ b/packages/components/src/icon/README.md @@ -11,10 +11,15 @@ import { wordpress } from '@wordpress/icons'; <Icon icon={ wordpress } /> ``` + ## Props ### `icon` + - Type: `IconType` + - Required: No + - Default: `null` + The icon to render. In most cases, you should use an icon from [the `@wordpress/icons` package](https://wordpress.github.io/gutenberg/?path=/story/icons-icon--library). @@ -24,16 +29,12 @@ Other supported values are: component instances, functions, The `size` value, as well as any other additional props, will be passed through. - - Type: `IconType` - - Required: No - - Default: `null` - ### `size` -The size (width and height) of the icon. - -Defaults to `20` when `icon` is a string (i.e. a Dashicon id), otherwise `24`. - - Type: `number` - Required: No - Default: `'string' === typeof icon ? 20 : 24` + +The size (width and height) of the icon. + +Defaults to `20` when `icon` is a string (i.e. a Dashicon id), otherwise `24`. diff --git a/packages/components/src/menu/checkbox-item.tsx b/packages/components/src/menu/checkbox-item.tsx index ddb700b43324a..69339387c3add 100644 --- a/packages/components/src/menu/checkbox-item.tsx +++ b/packages/components/src/menu/checkbox-item.tsx @@ -21,7 +21,7 @@ export const MenuCheckboxItem = forwardRef< HTMLDivElement, WordPressComponentProps< MenuCheckboxItemProps, 'div', false > >( function MenuCheckboxItem( - { suffix, children, hideOnClick = false, ...props }, + { suffix, children, disabled = false, hideOnClick = false, ...props }, ref ) { const menuContext = useContext( MenuContext ); @@ -37,6 +37,7 @@ export const MenuCheckboxItem = forwardRef< ref={ ref } { ...props } accessibleWhenDisabled + disabled={ disabled } hideOnClick={ hideOnClick } store={ menuContext.store } > diff --git a/packages/components/src/menu/item.tsx b/packages/components/src/menu/item.tsx index 84ff050bcc223..a716cbcc89654 100644 --- a/packages/components/src/menu/item.tsx +++ b/packages/components/src/menu/item.tsx @@ -15,7 +15,15 @@ export const MenuItem = forwardRef< HTMLDivElement, WordPressComponentProps< MenuItemProps, 'div', false > >( function MenuItem( - { prefix, suffix, children, hideOnClick = true, store, ...props }, + { + prefix, + suffix, + children, + disabled = false, + hideOnClick = true, + store, + ...props + }, ref ) { const menuContext = useContext( MenuContext ); @@ -37,6 +45,7 @@ export const MenuItem = forwardRef< ref={ ref } { ...props } accessibleWhenDisabled + disabled={ disabled } hideOnClick={ hideOnClick } store={ computedStore } > diff --git a/packages/components/src/menu/radio-item.tsx b/packages/components/src/menu/radio-item.tsx index 5534a6b7f3e10..28b3199d7d36b 100644 --- a/packages/components/src/menu/radio-item.tsx +++ b/packages/components/src/menu/radio-item.tsx @@ -28,7 +28,7 @@ export const MenuRadioItem = forwardRef< HTMLDivElement, WordPressComponentProps< MenuRadioItemProps, 'div', false > >( function MenuRadioItem( - { suffix, children, hideOnClick = false, ...props }, + { suffix, children, disabled = false, hideOnClick = false, ...props }, ref ) { const menuContext = useContext( MenuContext ); @@ -44,6 +44,7 @@ export const MenuRadioItem = forwardRef< ref={ ref } { ...props } accessibleWhenDisabled + disabled={ disabled } hideOnClick={ hideOnClick } store={ menuContext.store } > diff --git a/packages/components/src/menu/stories/index.story.tsx b/packages/components/src/menu/stories/index.story.tsx index dcd890370a1e0..37ebb6f905dc8 100644 --- a/packages/components/src/menu/stories/index.story.tsx +++ b/packages/components/src/menu/stories/index.story.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import type { Meta, StoryFn } from '@storybook/react'; +import type { StoryObj, Meta } from '@storybook/react'; import { css } from '@emotion/react'; /** @@ -67,315 +67,279 @@ const meta: Meta< typeof Menu > = { }; export default meta; -export const Default: StoryFn< typeof Menu > = ( props: MenuProps ) => ( - <Menu { ...props }> - <Menu.TriggerButton - render={ <Button __next40pxDefaultSize variant="secondary" /> } - > - Open menu - </Menu.TriggerButton> - <Menu.Popover> - <Menu.Item> - <Menu.ItemLabel>Label</Menu.ItemLabel> - </Menu.Item> - <Menu.Item> - <Menu.ItemLabel>Label</Menu.ItemLabel> - <Menu.ItemHelpText>Help text</Menu.ItemHelpText> - </Menu.Item> - <Menu.Item> - <Menu.ItemLabel>Label</Menu.ItemLabel> - <Menu.ItemHelpText> - The menu item help text is automatically truncated when - there are more than two lines of text - </Menu.ItemHelpText> - </Menu.Item> - <Menu.Item hideOnClick={ false }> - <Menu.ItemLabel>Label</Menu.ItemLabel> - <Menu.ItemHelpText> - This item doesn&apos;t close the menu on click - </Menu.ItemHelpText> - </Menu.Item> - <Menu.Item disabled>Disabled item</Menu.Item> - <Menu.Separator /> - <Menu.Group> - <Menu.GroupLabel>Group label</Menu.GroupLabel> - <Menu.Item prefix={ <Icon icon={ customLink } size={ 24 } /> }> - <Menu.ItemLabel>With prefix</Menu.ItemLabel> - </Menu.Item> - <Menu.Item suffix="⌘S">With suffix</Menu.Item> - <Menu.Item - disabled - prefix={ <Icon icon={ formatCapitalize } size={ 24 } /> } - suffix="⌥⌘T" +export const Default: StoryObj< typeof Menu > = { + args: { + children: ( + <> + <Menu.TriggerButton + render={ + <Button __next40pxDefaultSize variant="secondary" /> + } > - <Menu.ItemLabel> - Disabled with prefix and suffix - </Menu.ItemLabel> - <Menu.ItemHelpText>And help text</Menu.ItemHelpText> - </Menu.Item> - </Menu.Group> - </Menu.Popover> - </Menu> -); -Default.args = {}; - -export const WithSubmenu: StoryFn< typeof Menu > = ( props: MenuProps ) => ( - <Menu { ...props }> - <Menu.TriggerButton - render={ <Button __next40pxDefaultSize variant="secondary" /> } - > - Open menu - </Menu.TriggerButton> - <Menu.Popover> - <Menu.Item>Level 1 item</Menu.Item> - <Menu> - <Menu.SubmenuTriggerItem suffix="Suffix"> - <Menu.ItemLabel> - Submenu trigger item with a long label - </Menu.ItemLabel> - </Menu.SubmenuTriggerItem> + Open menu + </Menu.TriggerButton> <Menu.Popover> <Menu.Item> - <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> + <Menu.ItemLabel>Label</Menu.ItemLabel> + </Menu.Item> + <Menu.Item> + <Menu.ItemLabel>Label</Menu.ItemLabel> + <Menu.ItemHelpText>Help text</Menu.ItemHelpText> </Menu.Item> <Menu.Item> - <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> + <Menu.ItemLabel>Label</Menu.ItemLabel> + <Menu.ItemHelpText> + The menu item help text is automatically truncated + when there are more than two lines of text + </Menu.ItemHelpText> + </Menu.Item> + <Menu.Item hideOnClick={ false }> + <Menu.ItemLabel>Label</Menu.ItemLabel> + <Menu.ItemHelpText> + This item doesn&apos;t close the menu on click + </Menu.ItemHelpText> </Menu.Item> + <Menu.Item disabled>Disabled item</Menu.Item> + <Menu.Separator /> + <Menu.Group> + <Menu.GroupLabel>Group label</Menu.GroupLabel> + <Menu.Item + prefix={ <Icon icon={ customLink } size={ 24 } /> } + > + <Menu.ItemLabel>With prefix</Menu.ItemLabel> + </Menu.Item> + <Menu.Item suffix="⌘S">With suffix</Menu.Item> + <Menu.Item + disabled + prefix={ + <Icon icon={ formatCapitalize } size={ 24 } /> + } + suffix="⌥⌘T" + > + <Menu.ItemLabel> + Disabled with prefix and suffix + </Menu.ItemLabel> + <Menu.ItemHelpText>And help text</Menu.ItemHelpText> + </Menu.Item> + </Menu.Group> + </Menu.Popover> + </> + ), + }, +}; + +export const WithSubmenu: StoryObj< typeof Menu > = { + args: { + ...Default.args, + children: ( + <> + <Menu.TriggerButton + render={ + <Button __next40pxDefaultSize variant="secondary" /> + } + > + Open menu + </Menu.TriggerButton> + <Menu.Popover> + <Menu.Item>Level 1 item</Menu.Item> <Menu> - <Menu.SubmenuTriggerItem> - <Menu.ItemLabel>Submenu trigger</Menu.ItemLabel> + <Menu.SubmenuTriggerItem suffix="Suffix"> + <Menu.ItemLabel> + Submenu trigger item with a long label + </Menu.ItemLabel> </Menu.SubmenuTriggerItem> <Menu.Popover> <Menu.Item> - <Menu.ItemLabel>Level 3 item</Menu.ItemLabel> + <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> </Menu.Item> <Menu.Item> - <Menu.ItemLabel>Level 3 item</Menu.ItemLabel> + <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> </Menu.Item> + <Menu> + <Menu.SubmenuTriggerItem> + <Menu.ItemLabel> + Submenu trigger + </Menu.ItemLabel> + </Menu.SubmenuTriggerItem> + <Menu.Popover> + <Menu.Item> + <Menu.ItemLabel> + Level 3 item + </Menu.ItemLabel> + </Menu.Item> + <Menu.Item> + <Menu.ItemLabel> + Level 3 item + </Menu.ItemLabel> + </Menu.Item> + </Menu.Popover> + </Menu> </Menu.Popover> </Menu> </Menu.Popover> - </Menu> - </Menu.Popover> - </Menu> -); -WithSubmenu.args = { - ...Default.args, + </> + ), + }, }; -export const WithCheckboxes: StoryFn< typeof Menu > = ( props: MenuProps ) => { - const [ isAChecked, setAChecked ] = useState( false ); - const [ isBChecked, setBChecked ] = useState( true ); - const [ multipleCheckboxesValue, setMultipleCheckboxesValue ] = useState< - string[] - >( [ 'b' ] ); - - const onMultipleCheckboxesCheckedChange: React.ComponentProps< - typeof Menu.CheckboxItem - >[ 'onChange' ] = ( e ) => { - setMultipleCheckboxesValue( ( prevValues ) => { - if ( prevValues.includes( e.target.value ) ) { - return prevValues.filter( ( val ) => val !== e.target.value ); - } - return [ ...prevValues, e.target.value ]; - } ); - }; +export const WithCheckboxes: StoryObj< typeof Menu > = { + render: function WithCheckboxes( props: MenuProps ) { + const [ isAChecked, setAChecked ] = useState( false ); + const [ isBChecked, setBChecked ] = useState( true ); + const [ multipleCheckboxesValue, setMultipleCheckboxesValue ] = + useState< string[] >( [ 'b' ] ); - return ( - <Menu { ...props }> - <Menu.TriggerButton - render={ <Button __next40pxDefaultSize variant="secondary" /> } - > - Open menu - </Menu.TriggerButton> - <Menu.Popover> - <Menu.Group> - <Menu.GroupLabel> - Single selection, uncontrolled - </Menu.GroupLabel> - <Menu.CheckboxItem - name="checkbox-individual-uncontrolled-a" - value="a" - suffix="⌥⌘T" - > - <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> - <Menu.ItemHelpText> - Initially unchecked - </Menu.ItemHelpText> - </Menu.CheckboxItem> - <Menu.CheckboxItem - name="checkbox-individual-uncontrolled-b" - value="b" - defaultChecked - > - <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> - <Menu.ItemHelpText>Initially checked</Menu.ItemHelpText> - </Menu.CheckboxItem> - </Menu.Group> - <Menu.Separator /> - <Menu.Group> - <Menu.GroupLabel> - Single selection, controlled - </Menu.GroupLabel> - <Menu.CheckboxItem - name="checkbox-individual-controlled-a" - value="a" - checked={ isAChecked } - onChange={ ( e ) => { - setAChecked( e.target.checked ); - } } - > - <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> - <Menu.ItemHelpText> - Initially unchecked - </Menu.ItemHelpText> - </Menu.CheckboxItem> - <Menu.CheckboxItem - name="checkbox-individual-controlled-b" - value="b" - checked={ isBChecked } - onChange={ ( e ) => setBChecked( e.target.checked ) } - > - <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> - <Menu.ItemHelpText>Initially checked</Menu.ItemHelpText> - </Menu.CheckboxItem> - </Menu.Group> - <Menu.Separator /> - <Menu.Group> - <Menu.GroupLabel> - Multiple selection, uncontrolled - </Menu.GroupLabel> - <Menu.CheckboxItem - name="checkbox-multiple-uncontrolled" - value="a" - > - <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> - <Menu.ItemHelpText> - Initially unchecked - </Menu.ItemHelpText> - </Menu.CheckboxItem> - <Menu.CheckboxItem - name="checkbox-multiple-uncontrolled" - value="b" - defaultChecked - > - <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> - <Menu.ItemHelpText>Initially checked</Menu.ItemHelpText> - </Menu.CheckboxItem> - </Menu.Group> - <Menu.Separator /> - <Menu.Group> - <Menu.GroupLabel> - Multiple selection, controlled - </Menu.GroupLabel> - <Menu.CheckboxItem - name="checkbox-multiple-controlled" - value="a" - checked={ multipleCheckboxesValue.includes( 'a' ) } - onChange={ onMultipleCheckboxesCheckedChange } - > - <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> - <Menu.ItemHelpText> - Initially unchecked - </Menu.ItemHelpText> - </Menu.CheckboxItem> - <Menu.CheckboxItem - name="checkbox-multiple-controlled" - value="b" - checked={ multipleCheckboxesValue.includes( 'b' ) } - onChange={ onMultipleCheckboxesCheckedChange } - > - <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> - <Menu.ItemHelpText>Initially checked</Menu.ItemHelpText> - </Menu.CheckboxItem> - </Menu.Group> - </Menu.Popover> - </Menu> - ); -}; -WithCheckboxes.args = { - ...Default.args, -}; + const onMultipleCheckboxesCheckedChange: React.ComponentProps< + typeof Menu.CheckboxItem + >[ 'onChange' ] = ( e ) => { + setMultipleCheckboxesValue( ( prevValues ) => { + if ( prevValues.includes( e.target.value ) ) { + return prevValues.filter( + ( val ) => val !== e.target.value + ); + } + return [ ...prevValues, e.target.value ]; + } ); + }; -export const WithRadios: StoryFn< typeof Menu > = ( props: MenuProps ) => { - const [ radioValue, setRadioValue ] = useState( 'two' ); - const onRadioChange: React.ComponentProps< - typeof Menu.RadioItem - >[ 'onChange' ] = ( e ) => setRadioValue( e.target.value ); + return ( + <Menu { ...props }> + <Menu.TriggerButton + render={ + <Button __next40pxDefaultSize variant="secondary" /> + } + > + Open menu + </Menu.TriggerButton> + <Menu.Popover> + <Menu.Group> + <Menu.GroupLabel> + Single selection, uncontrolled + </Menu.GroupLabel> + <Menu.CheckboxItem + name="checkbox-individual-uncontrolled-a" + value="a" + suffix="⌥⌘T" + > + <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially unchecked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + <Menu.CheckboxItem + name="checkbox-individual-uncontrolled-b" + value="b" + defaultChecked + > + <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially checked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + </Menu.Group> + <Menu.Separator /> + <Menu.Group> + <Menu.GroupLabel> + Single selection, controlled + </Menu.GroupLabel> + <Menu.CheckboxItem + name="checkbox-individual-controlled-a" + value="a" + checked={ isAChecked } + onChange={ ( e ) => { + setAChecked( e.target.checked ); + } } + > + <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially unchecked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + <Menu.CheckboxItem + name="checkbox-individual-controlled-b" + value="b" + checked={ isBChecked } + onChange={ ( e ) => + setBChecked( e.target.checked ) + } + > + <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially checked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + </Menu.Group> + <Menu.Separator /> + <Menu.Group> + <Menu.GroupLabel> + Multiple selection, uncontrolled + </Menu.GroupLabel> + <Menu.CheckboxItem + name="checkbox-multiple-uncontrolled" + value="a" + > + <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially unchecked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + <Menu.CheckboxItem + name="checkbox-multiple-uncontrolled" + value="b" + defaultChecked + > + <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially checked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + </Menu.Group> + <Menu.Separator /> + <Menu.Group> + <Menu.GroupLabel> + Multiple selection, controlled + </Menu.GroupLabel> + <Menu.CheckboxItem + name="checkbox-multiple-controlled" + value="a" + checked={ multipleCheckboxesValue.includes( 'a' ) } + onChange={ onMultipleCheckboxesCheckedChange } + > + <Menu.ItemLabel>Checkbox item A</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially unchecked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + <Menu.CheckboxItem + name="checkbox-multiple-controlled" + value="b" + checked={ multipleCheckboxesValue.includes( 'b' ) } + onChange={ onMultipleCheckboxesCheckedChange } + > + <Menu.ItemLabel>Checkbox item B</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially checked + </Menu.ItemHelpText> + </Menu.CheckboxItem> + </Menu.Group> + </Menu.Popover> + </Menu> + ); + }, - return ( - <Menu { ...props }> - <Menu.TriggerButton - render={ <Button __next40pxDefaultSize variant="secondary" /> } - > - Open menu - </Menu.TriggerButton> - <Menu.Popover> - <Menu.Group> - <Menu.GroupLabel>Uncontrolled</Menu.GroupLabel> - <Menu.RadioItem name="radio-uncontrolled" value="one"> - <Menu.ItemLabel>Radio item 1</Menu.ItemLabel> - <Menu.ItemHelpText> - Initially unchecked - </Menu.ItemHelpText> - </Menu.RadioItem> - <Menu.RadioItem - name="radio-uncontrolled" - value="two" - defaultChecked - > - <Menu.ItemLabel>Radio item 2</Menu.ItemLabel> - <Menu.ItemHelpText>Initially checked</Menu.ItemHelpText> - </Menu.RadioItem> - </Menu.Group> - <Menu.Separator /> - <Menu.Group> - <Menu.GroupLabel>Controlled</Menu.GroupLabel> - <Menu.RadioItem - name="radio-controlled" - value="one" - checked={ radioValue === 'one' } - onChange={ onRadioChange } - > - <Menu.ItemLabel>Radio item 1</Menu.ItemLabel> - <Menu.ItemHelpText> - Initially unchecked - </Menu.ItemHelpText> - </Menu.RadioItem> - <Menu.RadioItem - name="radio-controlled" - value="two" - checked={ radioValue === 'two' } - onChange={ onRadioChange } - > - <Menu.ItemLabel>Radio item 2</Menu.ItemLabel> - <Menu.ItemHelpText>Initially checked</Menu.ItemHelpText> - </Menu.RadioItem> - </Menu.Group> - </Menu.Popover> - </Menu> - ); -}; -WithRadios.args = { - ...Default.args, + args: { + ...Default.args, + }, }; -const modalOnTopOfMenuPopover = css` - && { - z-index: 1000000; - } -`; - -// For more examples with `Modal`, check https://ariakit.org/examples/menu-wordpress-modal -export const WithModals: StoryFn< typeof Menu > = ( props: MenuProps ) => { - const [ isOuterModalOpen, setOuterModalOpen ] = useState( false ); - const [ isInnerModalOpen, setInnerModalOpen ] = useState( false ); - - const cx = useCx(); - const modalOverlayClassName = cx( modalOnTopOfMenuPopover ); +export const WithRadios: StoryObj< typeof Menu > = { + render: function WithRadios( props: MenuProps ) { + const [ radioValue, setRadioValue ] = useState( 'two' ); + const onRadioChange: React.ComponentProps< + typeof Menu.RadioItem + >[ 'onChange' ] = ( e ) => setRadioValue( e.target.value ); - return ( - <> + return ( <Menu { ...props }> <Menu.TriggerButton render={ @@ -385,49 +349,133 @@ export const WithModals: StoryFn< typeof Menu > = ( props: MenuProps ) => { Open menu </Menu.TriggerButton> <Menu.Popover> - <Menu.Item - onClick={ () => setOuterModalOpen( true ) } - hideOnClick={ false } - > - <Menu.ItemLabel>Open outer modal</Menu.ItemLabel> - </Menu.Item> - <Menu.Item - onClick={ () => setInnerModalOpen( true ) } - hideOnClick={ false } - > - <Menu.ItemLabel>Open inner modal</Menu.ItemLabel> - </Menu.Item> - { isInnerModalOpen && ( - <Modal - onRequestClose={ () => setInnerModalOpen( false ) } - overlayClassName={ modalOverlayClassName } + <Menu.Group> + <Menu.GroupLabel>Uncontrolled</Menu.GroupLabel> + <Menu.RadioItem name="radio-uncontrolled" value="one"> + <Menu.ItemLabel>Radio item 1</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially unchecked + </Menu.ItemHelpText> + </Menu.RadioItem> + <Menu.RadioItem + name="radio-uncontrolled" + value="two" + defaultChecked > - Modal&apos;s contents - <button - onClick={ () => setInnerModalOpen( false ) } - > - Close - </button> - </Modal> - ) } + <Menu.ItemLabel>Radio item 2</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially checked + </Menu.ItemHelpText> + </Menu.RadioItem> + </Menu.Group> + <Menu.Separator /> + <Menu.Group> + <Menu.GroupLabel>Controlled</Menu.GroupLabel> + <Menu.RadioItem + name="radio-controlled" + value="one" + checked={ radioValue === 'one' } + onChange={ onRadioChange } + > + <Menu.ItemLabel>Radio item 1</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially unchecked + </Menu.ItemHelpText> + </Menu.RadioItem> + <Menu.RadioItem + name="radio-controlled" + value="two" + checked={ radioValue === 'two' } + onChange={ onRadioChange } + > + <Menu.ItemLabel>Radio item 2</Menu.ItemLabel> + <Menu.ItemHelpText> + Initially checked + </Menu.ItemHelpText> + </Menu.RadioItem> + </Menu.Group> </Menu.Popover> </Menu> - { isOuterModalOpen && ( - <Modal - onRequestClose={ () => setOuterModalOpen( false ) } - overlayClassName={ modalOverlayClassName } - > - Modal&apos;s contents - <button onClick={ () => setOuterModalOpen( false ) }> - Close - </button> - </Modal> - ) } - </> - ); + ); + }, + + args: { + ...Default.args, + }, }; -WithModals.args = { - ...Default.args, + +const modalOnTopOfMenuPopover = css` + && { + z-index: 1000000; + } +`; + +export const WithModals: StoryObj< typeof Menu > = { + render: function WithModals( props: MenuProps ) { + const [ isOuterModalOpen, setOuterModalOpen ] = useState( false ); + const [ isInnerModalOpen, setInnerModalOpen ] = useState( false ); + + const cx = useCx(); + const modalOverlayClassName = cx( modalOnTopOfMenuPopover ); + + return ( + <> + <Menu { ...props }> + <Menu.TriggerButton + render={ + <Button __next40pxDefaultSize variant="secondary" /> + } + > + Open menu + </Menu.TriggerButton> + <Menu.Popover> + <Menu.Item + onClick={ () => setOuterModalOpen( true ) } + hideOnClick={ false } + > + <Menu.ItemLabel>Open outer modal</Menu.ItemLabel> + </Menu.Item> + <Menu.Item + onClick={ () => setInnerModalOpen( true ) } + hideOnClick={ false } + > + <Menu.ItemLabel>Open inner modal</Menu.ItemLabel> + </Menu.Item> + { isInnerModalOpen && ( + <Modal + onRequestClose={ () => + setInnerModalOpen( false ) + } + overlayClassName={ modalOverlayClassName } + > + Modal&apos;s contents + <button + onClick={ () => setInnerModalOpen( false ) } + > + Close + </button> + </Modal> + ) } + </Menu.Popover> + </Menu> + { isOuterModalOpen && ( + <Modal + onRequestClose={ () => setOuterModalOpen( false ) } + overlayClassName={ modalOverlayClassName } + > + Modal&apos;s contents + <button onClick={ () => setOuterModalOpen( false ) }> + Close + </button> + </Modal> + ) } + </> + ); + }, + + args: { + ...Default.args, + }, }; const ExampleSlotFill = createSlotFill( 'Example' ); @@ -478,9 +526,62 @@ const Fill = ( { children }: { children: React.ReactNode } ) => { ); }; -export const WithSlotFill: StoryFn< typeof Menu > = ( props: MenuProps ) => { - return ( - <SlotFillProvider> +export const WithSlotFill: StoryObj< typeof Menu > = { + render: ( props: MenuProps ) => { + return ( + <SlotFillProvider> + <Menu { ...props }> + <Menu.TriggerButton + render={ + <Button __next40pxDefaultSize variant="secondary" /> + } + > + Open menu + </Menu.TriggerButton> + <Menu.Popover> + <Menu.Item> + <Menu.ItemLabel>Item</Menu.ItemLabel> + </Menu.Item> + <Slot /> + </Menu.Popover> + </Menu> + + <Fill> + <Menu.Item> + <Menu.ItemLabel>Item from fill</Menu.ItemLabel> + </Menu.Item> + <Menu> + <Menu.SubmenuTriggerItem> + <Menu.ItemLabel>Submenu from fill</Menu.ItemLabel> + </Menu.SubmenuTriggerItem> + <Menu.Popover> + <Menu.Item> + <Menu.ItemLabel> + Submenu item from fill + </Menu.ItemLabel> + </Menu.Item> + </Menu.Popover> + </Menu> + </Fill> + </SlotFillProvider> + ); + }, + + args: { + ...Default.args, + }, +}; + +const toolbarVariantContextValue = { + Menu: { + variant: 'toolbar', + }, +}; + +export const ToolbarVariant: StoryObj< typeof Menu > = { + render: ( props: MenuProps ) => ( + // TODO: add toolbar + <ContextSystemProvider value={ toolbarVariantContextValue }> <Menu { ...props }> <Menu.TriggerButton render={ @@ -491,140 +592,104 @@ export const WithSlotFill: StoryFn< typeof Menu > = ( props: MenuProps ) => { </Menu.TriggerButton> <Menu.Popover> <Menu.Item> - <Menu.ItemLabel>Item</Menu.ItemLabel> + <Menu.ItemLabel>Level 1 item</Menu.ItemLabel> </Menu.Item> - <Slot /> + <Menu.Item> + <Menu.ItemLabel>Level 1 item</Menu.ItemLabel> + </Menu.Item> + <Menu.Separator /> + <Menu> + <Menu.SubmenuTriggerItem> + <Menu.ItemLabel>Submenu trigger</Menu.ItemLabel> + </Menu.SubmenuTriggerItem> + <Menu.Popover> + <Menu.Item> + <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> + </Menu.Item> + </Menu.Popover> + </Menu> </Menu.Popover> </Menu> + </ContextSystemProvider> + ), - <Fill> - <Menu.Item> - <Menu.ItemLabel>Item from fill</Menu.ItemLabel> - </Menu.Item> - <Menu> - <Menu.SubmenuTriggerItem> - <Menu.ItemLabel>Submenu from fill</Menu.ItemLabel> - </Menu.SubmenuTriggerItem> - <Menu.Popover> - <Menu.Item> - <Menu.ItemLabel> - Submenu item from fill - </Menu.ItemLabel> - </Menu.Item> - </Menu.Popover> - </Menu> - </Fill> - </SlotFillProvider> - ); -}; -WithSlotFill.args = { - ...Default.args, -}; - -const toolbarVariantContextValue = { - Menu: { - variant: 'toolbar', + args: { + ...Default.args, }, }; -export const ToolbarVariant: StoryFn< typeof Menu > = ( props: MenuProps ) => ( - // TODO: add toolbar - <ContextSystemProvider value={ toolbarVariantContextValue }> - <Menu { ...props }> - <Menu.TriggerButton - render={ <Button __next40pxDefaultSize variant="secondary" /> } - > - Open menu - </Menu.TriggerButton> - <Menu.Popover> - <Menu.Item> - <Menu.ItemLabel>Level 1 item</Menu.ItemLabel> - </Menu.Item> - <Menu.Item> - <Menu.ItemLabel>Level 1 item</Menu.ItemLabel> - </Menu.Item> - <Menu.Separator /> - <Menu> - <Menu.SubmenuTriggerItem> - <Menu.ItemLabel>Submenu trigger</Menu.ItemLabel> - </Menu.SubmenuTriggerItem> - <Menu.Popover> - <Menu.Item> - <Menu.ItemLabel>Level 2 item</Menu.ItemLabel> - </Menu.Item> - </Menu.Popover> - </Menu> - </Menu.Popover> - </Menu> - </ContextSystemProvider> -); -ToolbarVariant.args = { - ...Default.args, -}; -export const InsideModal: StoryFn< typeof Menu > = ( props: MenuProps ) => { - const [ isModalOpen, setModalOpen ] = useState( false ); - return ( - <> - <Button - onClick={ () => setModalOpen( true ) } - __next40pxDefaultSize - variant="secondary" - > - Open modal - </Button> - { isModalOpen && ( - <Modal - onRequestClose={ () => setModalOpen( false ) } - title="Menu inside modal" +export const InsideModal: StoryObj< typeof Menu > = { + render: function InsideModal( props: MenuProps ) { + const [ isModalOpen, setModalOpen ] = useState( false ); + return ( + <> + <Button + onClick={ () => setModalOpen( true ) } + __next40pxDefaultSize + variant="secondary" > - <Menu { ...props }> - <Menu.TriggerButton - render={ - <Button - __next40pxDefaultSize - variant="secondary" - /> - } - > - Open menu - </Menu.TriggerButton> - <Menu.Popover> - <Menu.Item> - <Menu.ItemLabel>Level 1 item</Menu.ItemLabel> - </Menu.Item> - <Menu.Item> - <Menu.ItemLabel>Level 1 item</Menu.ItemLabel> - </Menu.Item> - <Menu.Separator /> - <Menu> - <Menu.SubmenuTriggerItem> + Open modal + </Button> + { isModalOpen && ( + <Modal + onRequestClose={ () => setModalOpen( false ) } + title="Menu inside modal" + > + <Menu { ...props }> + <Menu.TriggerButton + render={ + <Button + __next40pxDefaultSize + variant="secondary" + /> + } + > + Open menu + </Menu.TriggerButton> + <Menu.Popover> + <Menu.Item> <Menu.ItemLabel> - Submenu trigger + Level 1 item </Menu.ItemLabel> - </Menu.SubmenuTriggerItem> - <Menu.Popover> - <Menu.Item> + </Menu.Item> + <Menu.Item> + <Menu.ItemLabel> + Level 1 item + </Menu.ItemLabel> + </Menu.Item> + <Menu.Separator /> + <Menu> + <Menu.SubmenuTriggerItem> <Menu.ItemLabel> - Level 2 item + Submenu trigger </Menu.ItemLabel> - </Menu.Item> - </Menu.Popover> - </Menu> - </Menu.Popover> - </Menu> - <Button onClick={ () => setModalOpen( false ) }> - Close modal - </Button> - </Modal> - ) } - </> - ); -}; -InsideModal.args = { - ...Default.args, -}; -InsideModal.parameters = { - docs: { - source: { type: 'code' }, + </Menu.SubmenuTriggerItem> + <Menu.Popover> + <Menu.Item> + <Menu.ItemLabel> + Level 2 item + </Menu.ItemLabel> + </Menu.Item> + </Menu.Popover> + </Menu> + </Menu.Popover> + </Menu> + <Button onClick={ () => setModalOpen( false ) }> + Close modal + </Button> + </Modal> + ) } + </> + ); + }, + + args: { + ...Default.args, + }, + + parameters: { + docs: { + source: { type: 'code' }, + }, }, }; diff --git a/packages/components/src/menu/types.ts b/packages/components/src/menu/types.ts index f58b5bcc89b95..f9bb0782529d1 100644 --- a/packages/components/src/menu/types.ts +++ b/packages/components/src/menu/types.ts @@ -2,7 +2,6 @@ * External dependencies */ import type * as Ariakit from '@ariakit/react'; -import type { Placement } from '@floating-ui/react-dom'; export interface MenuContext { /** @@ -17,77 +16,93 @@ export interface MenuContext { export interface MenuProps { /** - * The contents of the menu (ie. one or more menu items). + * The elements, which should include one instance of the `Menu.TriggerButton` + * component and one instance of the `Menu.Popover` component. */ - children?: React.ReactNode; + children?: Ariakit.MenuProviderProps[ 'children' ]; /** - * The open state of the menu popover when it is initially rendered. Use when - * not wanting to control its open state. + * Whether the menu popover and its contents should be visible by default. + * + * Note: this prop will be overridden by the `open` prop if it is + * provided (meaning the component will be used in "controlled" mode). * * @default false */ - defaultOpen?: boolean; + defaultOpen?: Ariakit.MenuProviderProps[ 'defaultOpen' ]; /** - * The controlled open state of the menu popover. Must be used in conjunction - * with `onOpenChange`. + * Whether the menu popover and its contents should be visible. + * Should be used in conjunction with `onOpenChange` in order to control + * the open state of the menu popover. + * + * Note: this prop will set the component in "controlled" mode, and it will + * override the `defaultOpen` prop. */ - open?: boolean; + open?: Ariakit.MenuProviderProps[ 'open' ]; /** - * Event handler called when the open state of the menu popover changes. + * A callback that gets called when the `open` state changes. */ - onOpenChange?: ( open: boolean ) => void; + onOpenChange?: Ariakit.MenuProviderProps[ 'setOpen' ]; /** * The placement of the menu popover. * - * @default 'bottom-start' for root-level menus, 'right-start' for nested menus + * @default 'bottom-start' for root-level menus, 'right-start' for submenus */ - placement?: Placement; + placement?: Ariakit.MenuProviderProps[ 'placement' ]; } export interface MenuPopoverProps { /** - * The contents of the dropdown. + * The contents of the menu popover, which should include instances of the + * `Menu.Item`, `Menu.CheckboxItem`, `Menu.RadioItem`, `Menu.Group`, and + * `Menu.Separator` components. */ - children?: React.ReactNode; + children?: Ariakit.MenuProps[ 'children' ]; /** * The modality of the menu popover. When set to true, interaction with * outside elements will be disabled and only menu content will be visible to * screen readers. * + * Determines whether the menu popover is modal. Modal dialogs have distinct + * states and behaviors: + * - The `portal` and `preventBodyScroll` props are set to `true`. They can + * still be manually set to `false`. + * - When the dialog is open, element tree outside it will be inert. + * * @default true */ - modal?: boolean; + modal?: Ariakit.MenuProps[ 'modal' ]; /** * The distance between the popover and the anchor element. * * @default 8 for root-level menus, 16 for nested menus */ - gutter?: number; + gutter?: Ariakit.MenuProps[ 'gutter' ]; /** * The skidding of the popover along the anchor element. Can be set to * negative values to make the popover shift to the opposite side. * * @default 0 for root-level menus, -8 for nested menus */ - shift?: number; + shift?: Ariakit.MenuProps[ 'shift' ]; /** - * Determines whether the menu popover will be hidden when the user presses - * the Escape key. + * Determines if the menu popover will hide when the user presses the + * Escape key. + * + * This prop can be either a boolean or a function that accepts an event as an + * argument and returns a boolean. The event object represents the keydown + * event that initiated the hide action, which could be either a native + * keyboard event or a React synthetic event. * * @default `( event ) => { event.preventDefault(); return true; }` */ - hideOnEscape?: - | boolean - | ( ( - event: KeyboardEvent | React.KeyboardEvent< Element > - ) => boolean ); + hideOnEscape?: Ariakit.MenuProps[ 'hideOnEscape' ]; } export interface MenuTriggerButtonProps { /** * The contents of the menu trigger button. */ - children?: React.ReactNode; + children?: Ariakit.MenuButtonProps[ 'children' ]; /** * Allows the component to be rendered as a different HTML element or React * component. The value can be a React element or a function that takes in the @@ -103,9 +118,6 @@ export interface MenuTriggerButtonProps { * This feature can be combined with the `accessibleWhenDisabled` prop to * make disabled elements still accessible via keyboard. * - * **Note**: For this prop to work, the `focusable` prop must be set to - * `true`, if it's not set by default. - * * @default false */ disabled?: Ariakit.MenuButtonProps[ 'disabled' ]; @@ -129,42 +141,54 @@ export interface MenuTriggerButtonProps { export interface MenuGroupProps { /** - * The contents of the menu group (ie. an optional menu group label and one - * or more menu items). + * The contents of the menu group, which should include one instance of the + * `Menu.GroupLabel` component and one or more instances of `Menu.Item`, + * `Menu.CheckboxItem`, and `Menu.RadioItem`. */ - children: React.ReactNode; + children: Ariakit.MenuGroupProps[ 'children' ]; } export interface MenuGroupLabelProps { /** - * The contents of the menu group label. + * The contents of the menu group label, which should provide an accessible + * label for the menu group. */ - children: React.ReactNode; + children: Ariakit.MenuGroupLabelProps[ 'children' ]; } export interface MenuItemProps { /** - * The contents of the menu item. + * The contents of the menu item, which could include one instance of the + * `Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` + * component. */ - children: React.ReactNode; + children: Ariakit.MenuItemProps[ 'children' ]; /** - * The contents of the menu item's prefix. + * The contents of the menu item's prefix, such as an icon. */ prefix?: React.ReactNode; /** - * The contents of the menu item's suffix. + * The contents of the menu item's suffix, such as a keyboard shortcut. */ suffix?: React.ReactNode; /** - * Whether to hide the menu popover when the menu item is clicked. + * Determines if the menu should hide when this item is clicked. + * + * **Note**: This behavior isn't triggered if this menu item is rendered as a + * link and modifier keys are used to either open the link in a new tab or + * download it. * * @default true */ - hideOnClick?: boolean; + hideOnClick?: Ariakit.MenuItemProps[ 'hideOnClick' ]; /** - * Determines if the element is disabled. + * Determines if the element is disabled. This sets the `aria-disabled` + * attribute accordingly, enabling support for all elements, including those + * that don't support the native `disabled` attribute. + * + * @default false */ - disabled?: boolean; + disabled?: Ariakit.MenuItemProps[ 'disabled' ]; /** * Allows the component to be rendered as a different HTML element or React * component. The value can be a React element or a function that takes in the @@ -173,73 +197,137 @@ export interface MenuItemProps { */ render?: Ariakit.MenuItemProps[ 'render' ]; /** - * The ariakit store. This prop is only meant for internal use. + * The ariakit menu store. This prop is only meant for internal use. * @ignore */ store?: Ariakit.MenuItemProps[ 'store' ]; } -export interface MenuCheckboxItemProps - extends Omit< MenuItemProps, 'prefix' | 'hideOnClick' > { +export interface MenuCheckboxItemProps { /** - * Whether to hide the menu popover when the menu item is clicked. + * The contents of the menu item, which could include one instance of the + * `Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` + * component. + */ + children: Ariakit.MenuItemCheckboxProps[ 'children' ]; + /** + * The contents of the menu item's suffix, such as a keyboard shortcut. + */ + suffix?: React.ReactNode; + /** + * Determines if the menu should hide when this item is clicked. + * + * **Note**: This behavior isn't triggered if this menu item is rendered as a + * link and modifier keys are used to either open the link in a new tab or + * download it. * * @default false */ - hideOnClick?: boolean; + hideOnClick?: Ariakit.MenuItemCheckboxProps[ 'hideOnClick' ]; + /** + * Determines if the element is disabled. This sets the `aria-disabled` + * attribute accordingly, enabling support for all elements, including those + * that don't support the native `disabled` attribute. + * + * @default false + */ + disabled?: Ariakit.MenuItemCheckboxProps[ 'disabled' ]; + /** + * Allows the component to be rendered as a different HTML element or React + * component. The value can be a React element or a function that takes in the + * original component props and gives back a React element with the props + * merged. + */ + render?: Ariakit.MenuItemCheckboxProps[ 'render' ]; /** * The checkbox menu item's name. */ - name: string; + name: Ariakit.MenuItemCheckboxProps[ 'name' ]; /** * The checkbox item's value, useful when using multiple checkbox menu items * associated to the same `name`. */ - value?: string; + value?: Ariakit.MenuItemCheckboxProps[ 'value' ]; /** * The controlled checked state of the checkbox menu item. + * + * Note: this prop will override the `defaultChecked` prop. */ - checked?: boolean; + checked?: Ariakit.MenuItemCheckboxProps[ 'checked' ]; /** * The checked state of the checkbox menu item when it is initially rendered. * Use when not wanting to control its checked state. + * + * Note: this prop will be overriden by the `checked` prop, if it is defined. */ - defaultChecked?: boolean; + defaultChecked?: Ariakit.MenuItemCheckboxProps[ 'defaultChecked' ]; /** - * Event handler called when the checked state of the checkbox menu item changes. + * A function that is called when the checkbox's checked state changes. */ - onChange?: ( event: React.ChangeEvent< HTMLInputElement > ) => void; + onChange?: Ariakit.MenuItemCheckboxProps[ 'onChange' ]; } -export interface MenuRadioItemProps - extends Omit< MenuItemProps, 'prefix' | 'hideOnClick' > { +export interface MenuRadioItemProps { + /** + * The contents of the menu item, which could include one instance of the + * `Menu.ItemLabel` component and/or one instance of the `Menu.ItemHelpText` + * component. + */ + children: Ariakit.MenuItemRadioProps[ 'children' ]; + /** + * The contents of the menu item's suffix, such as a keyboard shortcut. + */ + suffix?: React.ReactNode; + /** + * Determines if the menu should hide when this item is clicked. + * + * **Note**: This behavior isn't triggered if this menu item is rendered as a + * link and modifier keys are used to either open the link in a new tab or + * download it. + * + * @default false + */ + hideOnClick?: Ariakit.MenuItemRadioProps[ 'hideOnClick' ]; /** - * Whether to hide the menu popover when the menu item is clicked. + * Determines if the element is disabled. This sets the `aria-disabled` + * attribute accordingly, enabling support for all elements, including those + * that don't support the native `disabled` attribute. * * @default false */ - hideOnClick?: boolean; + disabled?: Ariakit.MenuItemRadioProps[ 'disabled' ]; + /** + * Allows the component to be rendered as a different HTML element or React + * component. The value can be a React element or a function that takes in the + * original component props and gives back a React element with the props + * merged. + */ + render?: Ariakit.MenuItemRadioProps[ 'render' ]; /** * The radio item's name. */ - name: string; + name: Ariakit.MenuItemRadioProps[ 'name' ]; /** * The radio item's value. */ - value: string | number; + value: Ariakit.MenuItemRadioProps[ 'value' ]; /** * The controlled checked state of the radio menu item. + * + * Note: this prop will override the `defaultChecked` prop. */ - checked?: boolean; + checked?: Ariakit.MenuItemRadioProps[ 'checked' ]; /** * The checked state of the radio menu item when it is initially rendered. * Use when not wanting to control its checked state. + * + * Note: this prop will be overriden by the `checked` prop, if it is defined. */ - defaultChecked?: boolean; + defaultChecked?: Ariakit.MenuItemRadioProps[ 'defaultChecked' ]; /** - * Event handler called when the checked radio menu item changes. + * A function that is called when the checkbox's checked state changes. */ - onChange?: ( event: React.ChangeEvent< HTMLInputElement > ) => void; + onChange?: Ariakit.MenuItemRadioProps[ 'onChange' ]; } export interface MenuSeparatorProps {} diff --git a/packages/components/src/navigation/back-button/index.tsx b/packages/components/src/navigation/back-button/index.tsx index 077e5a8dbdc6d..ce4a90d9ae7a5 100644 --- a/packages/components/src/navigation/back-button/index.tsx +++ b/packages/components/src/navigation/back-button/index.tsx @@ -49,6 +49,7 @@ function UnforwardedNavigationBackButton( const icon = isRTL() ? chevronRight : chevronLeft; return ( <MenuBackButtonUI + __next40pxDefaultSize className={ classes } href={ href } variant="tertiary" diff --git a/packages/components/src/navigation/index.tsx b/packages/components/src/navigation/index.tsx index 92f431dfb22fc..ef37caf2f5214 100644 --- a/packages/components/src/navigation/index.tsx +++ b/packages/components/src/navigation/index.tsx @@ -6,6 +6,7 @@ import clsx from 'clsx'; /** * WordPress dependencies */ +import deprecated from '@wordpress/deprecated'; import { useEffect, useRef, useState } from '@wordpress/element'; import { isRTL } from '@wordpress/i18n'; @@ -79,6 +80,12 @@ export function Navigation( { const navigationTree = useCreateNavigationTree(); const defaultSlideOrigin = isRTL() ? 'right' : 'left'; + deprecated( 'wp.components.Navigation (and all subcomponents)', { + since: '6.8', + version: '7.1', + alternative: 'wp.components.Navigator', + } ); + const setActiveMenu: NavigationContextType[ 'setActiveMenu' ] = ( menuId, slideInOrigin = defaultSlideOrigin diff --git a/packages/components/src/navigation/item/index.tsx b/packages/components/src/navigation/item/index.tsx index 4f4cc2a5dc7a2..160ed36ac6368 100644 --- a/packages/components/src/navigation/item/index.tsx +++ b/packages/components/src/navigation/item/index.tsx @@ -79,6 +79,8 @@ export function NavigationItem( props: NavigationItemProps ) { ? restProps : { as: Button, + __next40pxDefaultSize: + 'as' in restProps ? restProps.as === undefined : true, href, onClick: onItemClick, 'aria-current': isActive ? 'page' : undefined, diff --git a/packages/components/src/navigation/test/index.tsx b/packages/components/src/navigation/test/index.tsx index 20646a6c809bf..fed939068c0bf 100644 --- a/packages/components/src/navigation/test/index.tsx +++ b/packages/components/src/navigation/test/index.tsx @@ -176,6 +176,10 @@ describe( 'Navigation', () => { const menuItems = screen.getAllByRole( 'listitem' ); + expect( console ).toHaveWarnedWith( + 'wp.components.Navigation (and all subcomponents) is deprecated since version 6.8 and will be removed in version 7.1. Please use wp.components.Navigator instead.' + ); + expect( menuItems ).toHaveLength( 4 ); expect( menuItems[ 0 ] ).toHaveTextContent( 'Item 1' ); expect( menuItems[ 1 ] ).toHaveTextContent( 'Item 2' ); diff --git a/packages/components/src/navigator/test/index.tsx b/packages/components/src/navigator/test/index.tsx index cab6e9a4cdadf..07b118eaaef70 100644 --- a/packages/components/src/navigator/test/index.tsx +++ b/packages/components/src/navigator/test/index.tsx @@ -75,6 +75,7 @@ function CustomNavigatorButton( { } ) { return ( <Navigator.Button + __next40pxDefaultSize onClick={ () => { // Used to spy on the values passed to `navigator.goTo`. onClick?.( { type: 'goTo', path } ); @@ -95,6 +96,7 @@ function CustomNavigatorGoToBackButton( { const { goTo } = useNavigator(); return ( <Button + __next40pxDefaultSize onClick={ () => { goTo( path, { isBack: true } ); // Used to spy on the values passed to `navigator.goTo`. @@ -115,6 +117,7 @@ function CustomNavigatorGoToSkipFocusButton( { const { goTo } = useNavigator(); return ( <Button + __next40pxDefaultSize onClick={ () => { goTo( path, { skipFocus: true } ); // Used to spy on the values passed to `navigator.goTo`. @@ -136,6 +139,7 @@ function CustomNavigatorBackButton( { } ) { return ( <Navigator.BackButton + __next40pxDefaultSize onClick={ () => { // Used to spy on the values passed to `navigator.goBack`. onClick?.( { type: 'goBack' } ); diff --git a/packages/components/src/slot-fill/slot.tsx b/packages/components/src/slot-fill/slot.tsx index 82feaa04199f5..c1182562672c0 100644 --- a/packages/components/src/slot-fill/slot.tsx +++ b/packages/components/src/slot-fill/slot.tsx @@ -9,7 +9,7 @@ import type { ReactElement, ReactNode, Key } from 'react'; import { useObservableValue } from '@wordpress/compose'; import { useContext, - useEffect, + useLayoutEffect, useRef, Children, cloneElement, @@ -54,7 +54,7 @@ function Slot( props: Omit< SlotComponentProps, 'bubblesVirtually' > ) { const { name, children, fillProps = {} } = props; - useEffect( () => { + useLayoutEffect( () => { const instance = instanceRef.current; registry.registerSlot( name, instance ); return () => registry.unregisterSlot( name, instance ); diff --git a/packages/components/src/tabs/README.md b/packages/components/src/tabs/README.md index 9c7e846046c90..7f5f3219adfd1 100644 --- a/packages/components/src/tabs/README.md +++ b/packages/components/src/tabs/README.md @@ -1,254 +1,218 @@ # Tabs -<div class="callout callout-alert"> -This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. -</div> - -Tabs is a collection of React components that combine to render an [ARIA-compliant tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/). - -Tabs organizes content across different screens, data sets, and interactions. It has two sections: a list of tabs, and the view to show when tabs are chosen. - -## Development guidelines - -### Usage - -#### Uncontrolled Mode - -Tabs can be used in an uncontrolled mode, where the component manages its own state. In this mode, the `defaultTabId` prop can be used to set the initially selected tab. If this prop is not set, the first tab will be selected by default. In addition, in most cases where the currently active tab becomes disabled or otherwise unavailable, uncontrolled mode will automatically fall back to selecting the first available tab. - -```jsx -import { Tabs } from '@wordpress/components'; - -const onSelect = ( tabName ) => { - console.log( 'Selecting tab', tabName ); -}; - -const MyUncontrolledTabs = () => ( - <Tabs onSelect={ onSelect } defaultTabId="tab2"> - <Tabs.TabList> - <Tabs.Tab tabId="tab1" title="Tab 1"> - Tab 1 - </Tabs.Tab> - <Tabs.Tab tabId="tab2" title="Tab 2"> - Tab 2 - </Tabs.Tab> - <Tabs.Tab tabId="tab3" title="Tab 3"> - Tab 3 - </Tabs.Tab> - </Tabs.TabList> - <Tabs.TabPanel tabId="tab1"> - <p>Selected tab: Tab 1</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab2"> - <p>Selected tab: Tab 2</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab3"> - <p>Selected tab: Tab 3</p> - </Tabs.TabPanel> - </Tabs> - ); -``` - -#### Controlled Mode - -Tabs can also be used in a controlled mode, where the parent component specifies the `selectedTabId` and the `onSelect` props to control tab selection. In this mode, the `defaultTabId` prop will be ignored if it is provided. If the `selectedTabId` is `null`, no tab is selected. In this mode, if the currently selected tab becomes disabled or otherwise unavailable, the component will _not_ fall back to another available tab, leaving the controlling component in charge of implementing the desired logic. - -```jsx -import { Tabs } from '@wordpress/components'; - const [ selectedTabId, setSelectedTabId ] = useState< - string | undefined | null - >(); - -const onSelect = ( tabName ) => { - console.log( 'Selecting tab', tabName ); -}; - -const MyControlledTabs = () => ( - <Tabs - selectedTabId={ selectedTabId } - onSelect={ ( selectedId ) => { - setSelectedTabId( selectedId ); - onSelect( selectedId ); - } } - > - <Tabs.TabList> - <Tabs.Tab tabId="tab1" title="Tab 1"> - Tab 1 - </Tabs.Tab> - <Tabs.Tab tabId="tab2" title="Tab 2"> - Tab 2 - </Tabs.Tab> - <Tabs.Tab tabId="tab3" title="Tab 3"> - Tab 3 - </Tabs.Tab> - </Tabs.TabList> - <Tabs.TabPanel tabId="tab1"> - <p>Selected tab: Tab 1</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab2"> - <p>Selected tab: Tab 2</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab3"> - <p>Selected tab: Tab 3</p> - </Tabs.TabPanel> - </Tabs> - ); -``` - -### Components and Sub-components - -Tabs is comprised of four individual components: -- `Tabs`: a wrapper component and context provider. It is responsible for managing the state of the tabs and rendering the `TabList` and `TabPanels`. -- `TabList`: a wrapper component for the `Tab` components. It is responsible for rendering the list of tabs. -- `Tab`: renders a single tab. The currently active tab receives default styling that can be overridden with CSS targeting [aria-selected="true"]. -- `TabPanel`: renders the content to display for a single tab once that tab is selected. - -#### Tabs - -##### Props - -###### `children`: `React.ReactNode` - -The children elements, which should include one instance of the `Tabs.Tablist` component and as many instances of the `Tabs.TabPanel` components as there are `Tabs.Tab` components. - -- Required: Yes - -###### `selectOnMove`: `boolean` - -Determines if the tab should be selected when it receives focus. If set to `false`, the tab will only be selected upon clicking, not when using arrow keys to shift focus (manual tab activation). See the [official W3C docs](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/) for more info. - -- Required: No -- Default: `true` - -###### `selectedTabId`: `string | null` +<!-- This file is generated automatically and cannot be edited directly. Make edits via TypeScript types and TSDocs. --> -The id of the tab whose panel is currently visible. +🔒 This component is locked as a [private API](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-private-apis/). We do not yet recommend using this outside of the Gutenberg project. -If left `undefined`, it will be automatically set to the first enabled tab, and the component assumes it is being used in "uncontrolled" mode. +<p class="callout callout-info">See the <a href="https://wordpress.github.io/gutenberg/?path=/docs/components-tabs--docs">WordPress Storybook</a> for more detailed, interactive documentation.</p> -Consequently, any value different than `undefined` will set the component in "controlled" mode. When in "controlled" mode, the `null` value will result in no tabs being selected, and the tablist becoming tabbable. +Tabs is a collection of React components that combine to render +an [ARIA-compliant tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/). -- Required: No +Tabs organizes content across different screens, data sets, and interactions. +It has two sections: a list of tabs, and the view to show when a tab is chosen. -###### `defaultTabId`: `string | null` +`Tabs` itself is a wrapper component and context provider. +It is responsible for managing the state of the tabs, and rendering one instance of the `Tabs.TabList` component and one or more instances of the `Tab.TabPanel` component. -The id of the tab whose panel is currently visible. +## Props -If left `undefined`, it will be automatically set to the first enabled tab. If set to `null`, no tab will be selected, and the tablist will be tabbable. +### `activeTabId` -_Note: this prop will be overridden by the `selectedTabId` prop if it is provided (meaning the component will be used in "controlled" mode)._ + - Type: `string` + - Required: No -- Required: No +The current active tab `id`. The active tab is the tab element within the +tablist widget that has DOM focus. -###### `onSelect`: `( ( selectedId: string | null | undefined ) => void )` +- `null` represents the tablist (ie. the base composite element). Users + will be able to navigate out of it using arrow keys. +- If `activeTabId` is initially set to `null`, the base composite element + itself will have focus and users will be able to navigate to it using + arrow keys. -The function called when the `selectedTabId` changes. +### `children` -- Required: No -- Default: `noop` + - Type: `ReactNode` + - Required: Yes -###### `activeTabId`: `string | null` +The children elements, which should include one instance of the +`Tabs.Tablist` component and as many instances of the `Tabs.TabPanel` +components as there are `Tabs.Tab` components. -The current active tab `id`. The active tab is the tab element within the tablist widget that has DOM focus. +### `defaultTabId` -- `null` represents the tablist (ie. the base composite element). Users - will be able to navigate out of it using arrow keys; -- If `activeTabId` is initially set to `null`, the base composite element - itself will have focus and users will be able to navigate to it using - arrow keys. + - Type: `string` + - Required: No + +The id of the tab whose panel is currently visible. -- Required: No +If left `undefined`, it will be automatically set to the first enabled +tab. If set to `null`, no tab will be selected, and the tablist will be +tabbable. -###### `defaultActiveTabId`: `string | null` +Note: this prop will be overridden by the `selectedTabId` prop if it is +provided (meaning the component will be used in "controlled" mode). -The tab id that should be active by default when the composite widget is rendered. If `null`, the tablist element itself will have focus and users will be able to navigate to it using arrow keys. If `undefined`, the first enabled item will be focused. +### `defaultActiveTabId` -_Note: this prop will be overridden by the `activeTabId` prop if it is provided._ + - Type: `string` + - Required: No -- Required: No +The tab id that should be active by default when the composite widget is +rendered. If `null`, the tablist element itself will have focus +and users will be able to navigate to it using arrow keys. If `undefined`, +the first enabled item will be focused. -###### `onActiveTabIdChange`: `( ( activeId: string | null | undefined ) => void )` +Note: this prop will be overridden by the `activeTabId` prop if it is +provided. + +### `onSelect` + + - Type: `(selectedId: string) => void` + - Required: No The function called when the `selectedTabId` changes. -- Required: No -- Default: `noop` +### `onActiveTabIdChange` + + - Type: `(activeId: string) => void` + - Required: No + +A callback that gets called when the `activeTabId` state changes. -###### `orientation`: `'horizontal' | 'vertical' | 'both'` +### `orientation` -Defines the orientation of the tablist and determines which arrow keys can be used to move focus: + - Type: `"horizontal" | "vertical" | "both"` + - Required: No + - Default: `"horizontal"` -- `both`: all arrow keys work; -- `horizontal`: only left and right arrow keys work; +Defines the orientation of the tablist and determines which arrow keys +can be used to move focus: + +- `both`: all arrow keys work. +- `horizontal`: only left and right arrow keys work. - `vertical`: only up and down arrow keys work. -- Required: No -- Default: `horizontal` +### `selectOnMove` + + - Type: `boolean` + - Required: No + - Default: `true` + +Determines if the tab should be selected when it receives focus. If set to +`false`, the tab will only be selected upon clicking, not when using arrow +keys to shift focus (manual tab activation). See the [official W3C docs](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/) +for more info. + +### `selectedTabId` + + - Type: `string` + - Required: No + +The id of the tab whose panel is currently visible. + +If left `undefined`, it will be automatically set to the first enabled +tab, and the component assumes it is being used in "uncontrolled" mode. -#### TabList +Consequently, any value different than `undefined` will set the component +in "controlled" mode. When in "controlled" mode, the `null` value will +result in no tabs being selected, and the tablist becoming tabbable. -##### Props +## Subcomponents -###### `children`: `React.ReactNode` +### Tabs.TabList -The children elements, which should include one or more instances of the `Tabs.Tab` component. +A wrapper component for the `Tab` components. -- Required: No +It is responsible for rendering the list of tabs. -#### Tab +#### Props -##### Props +##### `children` -###### `tabId`: `string` + - Type: `ReactNode` + - Required: Yes -The unique ID of the tab. It will be used to register the tab and match it to a corresponding `Tabs.TabPanel` component. If not provided, a unique ID will be automatically generated. +The children elements, which should include one or more instances of the +`Tabs.Tab` component. -- Required: Yes +### Tabs.Tab -###### `children`: `React.ReactNode` +Renders a single tab. + +The currently active tab receives default styling that can be +overridden with CSS targeting `[aria-selected="true"]`. + +#### Props + +##### `children` + + - Type: `ReactNode` + - Required: No The contents of the tab. -- Required: No +##### `disabled` -###### `disabled`: `boolean` + - Type: `boolean` + - Required: No + - Default: `false` -Determines if the tab should be disabled. Note that disabled tabs can still be accessed via the keyboard when navigating through the tablist. +Determines if the tab should be disabled. Note that disabled tabs can +still be accessed via the keyboard when navigating through the tablist. -- Required: No -- Default: `false` +##### `render` -###### `render`: `React.ReactNode` + - Type: `RenderProp<HTMLAttributes<any> & { ref?: Ref<any>; }> | ReactElement<any, string | JSXElementConstructor<any>>` + - Required: No -Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged. +Allows the component to be rendered as a different HTML element or React +component. The value can be a React element or a function that takes in the +original component props and gives back a React element with the props +merged. By default, the tab will be rendered as a `button` element. -- Required: No +##### `tabId` -#### TabPanel + - Type: `string` + - Required: Yes -##### Props +The unique ID of the tab. It will be used to register the tab and match +it to a corresponding `Tabs.TabPanel` component. -###### `children`: `React.ReactNode` +### Tabs.TabPanel -The contents of the tab panel. +Renders the content to display for a single tab once that tab is selected. -- Required: No +#### Props -###### `tabId`: `string` +##### `children` -The unique `id` of the `Tabs.Tab` component controlling this panel. This connection is used to assign the `aria-labelledby` attribute to the tab panel and to determine if the tab panel should be visible. + - Type: `ReactNode` + - Required: No -If not provided, this link is automatically established by matching the order of `Tabs.Tab` and `Tabs.TabPanel` elements in the DOM. +The contents of the tab panel. -- Required: Yes +##### `focusable` -###### `focusable`: `boolean` + - Type: `boolean` + - Required: No + - Default: `true` Determines whether or not the tabpanel element should be focusable. +If `false`, pressing the tab key will skip over the tabpanel, and instead +focus on the first focusable element in the panel (if there is one). + +##### `tabId` + + - Type: `string` + - Required: Yes -If `false`, pressing the tab key will skip over the tabpanel, and instead focus on the first focusable element in the panel (if there is one). +The unique `id` of the `Tabs.Tab` component controlling this panel. This +connection is used to assign the `aria-labelledby` attribute to the tab +panel and to determine if the tab panel should be visible. -- Required: No -- Default: `true` +If not provided, this link is automatically established by matching the +order of `Tabs.Tab` and `Tabs.TabPanel` elements in the DOM. diff --git a/packages/components/src/tabs/docs-manifest.json b/packages/components/src/tabs/docs-manifest.json new file mode 100644 index 0000000000000..fc24b177ef616 --- /dev/null +++ b/packages/components/src/tabs/docs-manifest.json @@ -0,0 +1,22 @@ +{ + "$schema": "../../schemas/docs-manifest.json", + "displayName": "Tabs", + "filePath": "./index.tsx", + "subcomponents": [ + { + "displayName": "TabList", + "preferredDisplayName": "Tabs.TabList", + "filePath": "./tablist.tsx" + }, + { + "displayName": "Tab", + "preferredDisplayName": "Tabs.Tab", + "filePath": "./tab.tsx" + }, + { + "displayName": "TabPanel", + "preferredDisplayName": "Tabs.TabPanel", + "filePath": "./tabpanel.tsx" + } + ] +} diff --git a/packages/components/src/tabs/index.tsx b/packages/components/src/tabs/index.tsx index 819d259395daf..2cbe487976c59 100644 --- a/packages/components/src/tabs/index.tsx +++ b/packages/components/src/tabs/index.tsx @@ -36,11 +36,14 @@ function internalToExternalTabId( } /** - * Display one panel of content at a time with a tabbed interface, based on the - * WAI-ARIA Tabs Pattern⁠. + * Tabs is a collection of React components that combine to render + * an [ARIA-compliant tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/). * - * @see https://www.w3.org/WAI/ARIA/apg/patterns/tabs/ - * ``` + * Tabs organizes content across different screens, data sets, and interactions. + * It has two sections: a list of tabs, and the view to show when a tab is chosen. + * + * `Tabs` itself is a wrapper component and context provider. + * It is responsible for managing the state of the tabs, and rendering one instance of the `Tabs.TabList` component and one or more instances of the `Tab.TabPanel` component. */ export const Tabs = Object.assign( function Tabs( { @@ -121,12 +124,26 @@ export const Tabs = Object.assign( ); }, { + /** + * Renders a single tab. + * + * The currently active tab receives default styling that can be + * overridden with CSS targeting `[aria-selected="true"]`. + */ Tab: Object.assign( Tab, { displayName: 'Tabs.Tab', } ), + /** + * A wrapper component for the `Tab` components. + * + * It is responsible for rendering the list of tabs. + */ TabList: Object.assign( TabList, { displayName: 'Tabs.TabList', } ), + /** + * Renders the content to display for a single tab once that tab is selected. + */ TabPanel: Object.assign( TabPanel, { displayName: 'Tabs.TabPanel', } ), diff --git a/packages/components/src/tabs/stories/best-practices.mdx b/packages/components/src/tabs/stories/best-practices.mdx new file mode 100644 index 0000000000000..a8bb9cf20a5f0 --- /dev/null +++ b/packages/components/src/tabs/stories/best-practices.mdx @@ -0,0 +1,99 @@ +import { Meta } from '@storybook/blocks'; + +import * as TabsStories from './index.story'; + +<Meta of={ TabsStories } name="Best Practices" /> + +# Tabs + +## Usage + +### Uncontrolled Mode + +Tabs can be used in an uncontrolled mode, where the component manages its own state. In this mode, the `defaultTabId` prop can be used to set the initially selected tab. If this prop is not set, the first tab will be selected by default. In addition, in most cases where the currently active tab becomes disabled or otherwise unavailable, uncontrolled mode will automatically fall back to selecting the first available tab. + +```jsx +import { Tabs } from '@wordpress/components'; + +const onSelect = ( tabName ) => { + console.log( 'Selecting tab', tabName ); +}; + +const MyUncontrolledTabs = () => ( + <Tabs onSelect={ onSelect } defaultTabId="tab2"> + <Tabs.TabList> + <Tabs.Tab tabId="tab1" title="Tab 1"> + Tab 1 + </Tabs.Tab> + <Tabs.Tab tabId="tab2" title="Tab 2"> + Tab 2 + </Tabs.Tab> + <Tabs.Tab tabId="tab3" title="Tab 3"> + Tab 3 + </Tabs.Tab> + </Tabs.TabList> + <Tabs.TabPanel tabId="tab1"> + <p>Selected tab: Tab 1</p> + </Tabs.TabPanel> + <Tabs.TabPanel tabId="tab2"> + <p>Selected tab: Tab 2</p> + </Tabs.TabPanel> + <Tabs.TabPanel tabId="tab3"> + <p>Selected tab: Tab 3</p> + </Tabs.TabPanel> + </Tabs> +); +``` + +### Controlled Mode + +Tabs can also be used in a controlled mode, where the parent component specifies the `selectedTabId` and the `onSelect` props to control tab selection. In this mode, the `defaultTabId` prop will be ignored if it is provided. If the `selectedTabId` is `null`, no tab is selected. In this mode, if the currently selected tab becomes disabled or otherwise unavailable, the component will _not_ fall back to another available tab, leaving the controlling component in charge of implementing the desired logic. + +```tsx +import { Tabs } from '@wordpress/components'; + +const [ selectedTabId, setSelectedTabId ] = useState< + string | undefined | null +>(); + +const onSelect = ( tabName ) => { + console.log( 'Selecting tab', tabName ); +}; + +const MyControlledTabs = () => ( + <Tabs + selectedTabId={ selectedTabId } + onSelect={ ( selectedId ) => { + setSelectedTabId( selectedId ); + onSelect( selectedId ); + } } + > + <Tabs.TabList> + <Tabs.Tab tabId="tab1" title="Tab 1"> + Tab 1 + </Tabs.Tab> + <Tabs.Tab tabId="tab2" title="Tab 2"> + Tab 2 + </Tabs.Tab> + <Tabs.Tab tabId="tab3" title="Tab 3"> + Tab 3 + </Tabs.Tab> + </Tabs.TabList> + <Tabs.TabPanel tabId="tab1"> + <p>Selected tab: Tab 1</p> + </Tabs.TabPanel> + <Tabs.TabPanel tabId="tab2"> + <p>Selected tab: Tab 2</p> + </Tabs.TabPanel> + <Tabs.TabPanel tabId="tab3"> + <p>Selected tab: Tab 3</p> + </Tabs.TabPanel> + </Tabs> +); +``` + +### Using `Tabs` with links + +The semantics implemented by the `Tabs` component don't align well with the semantics needed by a list of links. Furthermore, end users usually expect every link to be tabbable, while `Tabs.Tablist` is a [composite](https://w3c.github.io/aria/#composite) widget acting as a single tab stop. + +For these reasons, even if the `Tabs` component is fully extensible, we don't recommend using `Tabs` with links, and we don't currently provide any related Storybook example. diff --git a/packages/components/src/tabs/stories/index.story.tsx b/packages/components/src/tabs/stories/index.story.tsx index e434bb501d85c..0502d6400a4f5 100644 --- a/packages/components/src/tabs/stories/index.story.tsx +++ b/packages/components/src/tabs/stories/index.story.tsx @@ -15,7 +15,6 @@ import { useState } from '@wordpress/element'; */ import { Tabs } from '..'; import { Slot, Fill, Provider as SlotFillProvider } from '../../slot-fill'; -import DropdownMenu from '../../dropdown-menu'; import Button from '../../button'; import Tooltip from '../../tooltip'; import Icon from '../../icon'; @@ -367,133 +366,3 @@ const CloseButtonTemplate: StoryFn< typeof Tabs > = ( props ) => { ); }; export const InsertCustomElements = CloseButtonTemplate.bind( {} ); - -const ControlledModeTemplate: StoryFn< typeof Tabs > = ( props ) => { - const [ selectedTabId, setSelectedTabId ] = useState< - string | undefined | null - >( props.selectedTabId ); - - return ( - <> - <Tabs - { ...props } - selectedTabId={ selectedTabId } - onSelect={ ( selectedId ) => { - setSelectedTabId( selectedId ); - props.onSelect?.( selectedId ); - } } - > - <Tabs.TabList> - <Tabs.Tab tabId="tab1">Tab 1</Tabs.Tab> - - <Tabs.Tab tabId="tab2">Tab 2</Tabs.Tab> - - <Tabs.Tab tabId="tab3">Tab 3</Tabs.Tab> - </Tabs.TabList> - <Tabs.TabPanel tabId="tab1"> - <p>Selected tab: Tab 1</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab2"> - <p>Selected tab: Tab 2</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab3"> - <p>Selected tab: Tab 3</p> - </Tabs.TabPanel> - </Tabs> - <div style={ { marginTop: '200px' } }> - <p>Select a tab:</p> - <DropdownMenu - controls={ [ - { - onClick: () => setSelectedTabId( 'tab1' ), - title: 'Tab 1', - isActive: selectedTabId === 'tab1', - }, - { - onClick: () => setSelectedTabId( 'tab2' ), - title: 'Tab 2', - isActive: selectedTabId === 'tab2', - }, - { - onClick: () => setSelectedTabId( 'tab3' ), - title: 'Tab 3', - isActive: selectedTabId === 'tab3', - }, - ] } - label="Choose a tab. The power is yours." - /> - </div> - </> - ); -}; - -export const ControlledMode = ControlledModeTemplate.bind( {} ); -ControlledMode.args = { - selectedTabId: 'tab3', -}; - -const TabBecomesDisabledTemplate: StoryFn< typeof Tabs > = ( props ) => { - const [ disableTab2, setDisableTab2 ] = useState( false ); - - return ( - <> - <Button - variant="primary" - onClick={ () => setDisableTab2( ! disableTab2 ) } - > - { disableTab2 ? 'Enable' : 'Disable' } Tab 2 - </Button> - <Tabs { ...props }> - <Tabs.TabList> - <Tabs.Tab tabId="tab1">Tab 1</Tabs.Tab> - <Tabs.Tab tabId="tab2" disabled={ disableTab2 }> - Tab 2 - </Tabs.Tab> - <Tabs.Tab tabId="tab3">Tab 3</Tabs.Tab> - </Tabs.TabList> - <Tabs.TabPanel tabId="tab1"> - <p>Selected tab: Tab 1</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab2"> - <p>Selected tab: Tab 2</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab3"> - <p>Selected tab: Tab 3</p> - </Tabs.TabPanel> - </Tabs> - </> - ); -}; -export const TabBecomesDisabled = TabBecomesDisabledTemplate.bind( {} ); - -const TabGetsRemovedTemplate: StoryFn< typeof Tabs > = ( props ) => { - const [ removeTab1, setRemoveTab1 ] = useState( false ); - - return ( - <> - <Button - variant="primary" - onClick={ () => setRemoveTab1( ! removeTab1 ) } - > - { removeTab1 ? 'Restore' : 'Remove' } Tab 1 - </Button> - <Tabs { ...props }> - <Tabs.TabList> - { ! removeTab1 && <Tabs.Tab tabId="tab1">Tab 1</Tabs.Tab> } - <Tabs.Tab tabId="tab2">Tab 2</Tabs.Tab> - <Tabs.Tab tabId="tab3">Tab 3</Tabs.Tab> - </Tabs.TabList> - <Tabs.TabPanel tabId="tab1"> - <p>Selected tab: Tab 1</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab2"> - <p>Selected tab: Tab 2</p> - </Tabs.TabPanel> - <Tabs.TabPanel tabId="tab3"> - <p>Selected tab: Tab 3</p> - </Tabs.TabPanel> - </Tabs> - </> - ); -}; -export const TabGetsRemoved = TabGetsRemovedTemplate.bind( {} ); diff --git a/packages/components/src/tabs/types.ts b/packages/components/src/tabs/types.ts index 959a82509a05d..7ef0f919322c0 100644 --- a/packages/components/src/tabs/types.ts +++ b/packages/components/src/tabs/types.ts @@ -22,18 +22,16 @@ export type TabsProps = { * `Tabs.Tablist` component and as many instances of the `Tabs.TabPanel` * components as there are `Tabs.Tab` components. */ - children: Ariakit.TabProps[ 'children' ]; + children: Ariakit.TabProviderProps[ 'children' ]; /** * Determines if the tab should be selected when it receives focus. If set to * `false`, the tab will only be selected upon clicking, not when using arrow - * keys to shift focus (manual tab activation). See the official W3C docs + * keys to shift focus (manual tab activation). See the [official W3C docs](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/) * for more info. * * @default true - * - * @see https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/ */ - selectOnMove?: Ariakit.TabStoreProps[ 'selectOnMove' ]; + selectOnMove?: Ariakit.TabProviderProps[ 'selectOnMove' ]; /** * The id of the tab whose panel is currently visible. * @@ -44,7 +42,7 @@ export type TabsProps = { * in "controlled" mode. When in "controlled" mode, the `null` value will * result in no tabs being selected, and the tablist becoming tabbable. */ - selectedTabId?: Ariakit.TabStoreProps[ 'selectedId' ]; + selectedTabId?: Ariakit.TabProviderProps[ 'selectedId' ]; /** * The id of the tab whose panel is currently visible. * @@ -55,21 +53,22 @@ export type TabsProps = { * Note: this prop will be overridden by the `selectedTabId` prop if it is * provided (meaning the component will be used in "controlled" mode). */ - defaultTabId?: Ariakit.TabStoreProps[ 'defaultSelectedId' ]; + defaultTabId?: Ariakit.TabProviderProps[ 'defaultSelectedId' ]; /** * The function called when the `selectedTabId` changes. */ - onSelect?: Ariakit.TabStoreProps[ 'setSelectedId' ]; + onSelect?: Ariakit.TabProviderProps[ 'setSelectedId' ]; /** * The current active tab `id`. The active tab is the tab element within the * tablist widget that has DOM focus. + * * - `null` represents the tablist (ie. the base composite element). Users * will be able to navigate out of it using arrow keys. * - If `activeTabId` is initially set to `null`, the base composite element * itself will have focus and users will be able to navigate to it using - * arrow keys.activeTabId + * arrow keys. */ - activeTabId?: Ariakit.TabStoreProps[ 'activeId' ]; + activeTabId?: Ariakit.TabProviderProps[ 'activeId' ]; /** * The tab id that should be active by default when the composite widget is * rendered. If `null`, the tablist element itself will have focus @@ -79,21 +78,22 @@ export type TabsProps = { * Note: this prop will be overridden by the `activeTabId` prop if it is * provided. */ - defaultActiveTabId?: Ariakit.TabStoreProps[ 'defaultActiveId' ]; + defaultActiveTabId?: Ariakit.TabProviderProps[ 'defaultActiveId' ]; /** * A callback that gets called when the `activeTabId` state changes. */ - onActiveTabIdChange?: Ariakit.TabStoreProps[ 'setActiveId' ]; + onActiveTabIdChange?: Ariakit.TabProviderProps[ 'setActiveId' ]; /** * Defines the orientation of the tablist and determines which arrow keys * can be used to move focus: + * * - `both`: all arrow keys work. * - `horizontal`: only left and right arrow keys work. * - `vertical`: only up and down arrow keys work. * * @default "horizontal" */ - orientation?: Ariakit.TabStoreProps[ 'orientation' ]; + orientation?: Ariakit.TabProviderProps[ 'orientation' ]; }; export type TabListProps = { @@ -105,7 +105,6 @@ export type TabListProps = { }; // TODO: consider prop name changes (tabId, selectedTabId) -// switch to auto-generated README // compound technique export type TabProps = { diff --git a/packages/components/src/text/hook.ts b/packages/components/src/text/hook.ts index a447b2ce5133b..243b00202460e 100644 --- a/packages/components/src/text/hook.ts +++ b/packages/components/src/text/hook.ts @@ -105,8 +105,8 @@ export default function useText( getOptimalTextShade( optimizeReadabilityFor ) === 'dark'; sx.optimalTextColor = isOptimalTextColorDark - ? css( { color: COLORS.gray[ 900 ] } ) - : css( { color: COLORS.white } ); + ? css( { color: COLORS.theme.foreground } ) + : css( { color: COLORS.theme.foregroundInverted } ); } return cx( diff --git a/packages/components/src/text/styles.ts b/packages/components/src/text/styles.ts index e777ed4f0941d..7d3b70e2ab239 100644 --- a/packages/components/src/text/styles.ts +++ b/packages/components/src/text/styles.ts @@ -9,7 +9,7 @@ import { css } from '@emotion/react'; import { COLORS, CONFIG } from '../utils'; export const Text = css` - color: ${ COLORS.gray[ 900 ] }; + color: ${ COLORS.theme.foreground }; line-height: ${ CONFIG.fontLineHeightBase }; margin: 0; text-wrap: balance; /* Fallback for Safari. */ diff --git a/packages/components/src/text/test/__snapshots__/index.tsx.snap b/packages/components/src/text/test/__snapshots__/index.tsx.snap index 1b98c0853ac54..caa876cb24dc7 100644 --- a/packages/components/src/text/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/text/test/__snapshots__/index.tsx.snap @@ -6,7 +6,7 @@ Snapshot Diff: + Base styles @@ -3,8 +3,9 @@ - "color": "#1e1e1e", + "color": "var(--wp-components-color-foreground, #1e1e1e)", "font-size": "calc((13 / 13) * 13px)", "font-weight": "normal", "line-height": "1.4", @@ -19,7 +19,7 @@ Snapshot Diff: exports[`Text should render highlighted words with highlightCaseSensitive 1`] = ` .emotion-0 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; @@ -52,7 +52,7 @@ exports[`Text should render highlighted words with highlightCaseSensitive 1`] = exports[`Text snapshot tests should render correctly 1`] = ` .emotion-0 { - color: #1e1e1e; + color: var(--wp-components-color-foreground, #1e1e1e); line-height: 1.4; margin: 0; text-wrap: balance; diff --git a/packages/components/src/text/test/index.tsx b/packages/components/src/text/test/index.tsx index 5fad5582f4d46..e6f6423b6b572 100644 --- a/packages/components/src/text/test/index.tsx +++ b/packages/components/src/text/test/index.tsx @@ -25,7 +25,7 @@ describe( 'Text', () => { </Text> ); expect( screen.getByRole( 'heading' ) ).toHaveStyle( { - color: COLORS.white, + color: 'rgb( 255, 255, 255 )', } ); } ); diff --git a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap index 91e9f291ddf01..18837ae79a325 100644 --- a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap @@ -357,7 +357,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = </div> </div> <button - class="components-button" + class="components-button is-next-40px-default-size" type="button" > Reset @@ -626,7 +626,7 @@ exports[`ToggleGroupControl controlled should render correctly with text options </div> </div> <button - class="components-button" + class="components-button is-next-40px-default-size" type="button" > Reset diff --git a/packages/components/src/toggle-group-control/test/index.tsx b/packages/components/src/toggle-group-control/test/index.tsx index 44cfda69c423c..28928a9735a37 100644 --- a/packages/components/src/toggle-group-control/test/index.tsx +++ b/packages/components/src/toggle-group-control/test/index.tsx @@ -57,9 +57,15 @@ const ControlledToggleGroupControl = ( { } } value={ value } /> - <Button onClick={ () => setValue( undefined ) }>Reset</Button> + <Button + onClick={ () => setValue( undefined ) } + __next40pxDefaultSize + > + Reset + </Button> { extraButtonOptions?.map( ( obj ) => ( <Button + __next40pxDefaultSize key={ obj.value } onClick={ () => setValue( obj.value ) } > diff --git a/packages/components/src/tree-select/README.md b/packages/components/src/tree-select/README.md index 493c83bf993b0..d2f73443a2a88 100644 --- a/packages/components/src/tree-select/README.md +++ b/packages/components/src/tree-select/README.md @@ -51,97 +51,101 @@ const MyTreeSelect = () => { ); } ``` + ## Props ### `__next40pxDefaultSize` -Start opting into the larger default height that will become the default size in a future version. - - Type: `boolean` - Required: No - Default: `false` -### `__nextHasNoMarginBottom` +Start opting into the larger default height that will become the default size in a future version. -Start opting into the new margin-free styles that will become the default in a future version. +### `__nextHasNoMarginBottom` - Type: `boolean` - Required: No - Default: `false` -### `children` +Start opting into the new margin-free styles that will become the default in a future version. -As an alternative to the `options` prop, `optgroup`s and `options` can be -passed in as `children` for more customizability. +### `children` - Type: `ReactNode` - Required: No -### `disabled` +As an alternative to the `options` prop, `optgroup`s and `options` can be +passed in as `children` for more customizability. -If true, the `input` will be disabled. +### `disabled` - Type: `boolean` - Required: No - Default: `false` -### `hideLabelFromVision` +If true, the `input` will be disabled. -If true, the label will only be visible to screen readers. +### `hideLabelFromVision` - Type: `boolean` - Required: No - Default: `false` +If true, the label will only be visible to screen readers. + ### `help` + - Type: `ReactNode` + - Required: No + Additional description for the control. Only use for meaningful description or instructions for the control. An element containing the description will be programmatically associated to the BaseControl by the means of an `aria-describedby` attribute. - - Type: `ReactNode` - - Required: No - ### `label` -If this property is added, a label will be generated using label property as the content. - - Type: `ReactNode` - Required: No -### `labelPosition` +If this property is added, a label will be generated using label property as the content. -The position of the label. +### `labelPosition` - Type: `"top" | "bottom" | "side" | "edge"` - Required: No - Default: `'top'` -### `noOptionLabel` +The position of the label. -If this property is added, an option will be added with this label to represent empty selection. +### `noOptionLabel` - Type: `string` - Required: No -### `onChange` +If this property is added, an option will be added with this label to represent empty selection. -A function that receives the value of the new option that is being selected as input. +### `onChange` - Type: `(value: string, extra?: { event?: ChangeEvent<HTMLSelectElement>; }) => void` - Required: No +A function that receives the value of the new option that is being selected as input. + ### `options` + - Type: `readonly ({ label: string; value: string; } & Omit<OptionHTMLAttributes<HTMLOptionElement>, "label" | "value">)[]` + - Required: No + An array of option property objects to be rendered, each with a `label` and `value` property, as well as any other `<option>` attributes. - - Type: `readonly ({ label: string; value: string; } & Omit<OptionHTMLAttributes<HTMLOptionElement>, "label" | "value">)[]` - - Required: No - ### `prefix` + - Type: `ReactNode` + - Required: No + Renders an element on the left side of the input. By default, the prefix is aligned with the edge of the input border, with no padding. @@ -159,26 +163,26 @@ import { /> ``` - - Type: `ReactNode` - - Required: No - ### `selectedId` -The id of the currently selected node. - - Type: `string` - Required: No -### `size` +The id of the currently selected node. -Adjusts the size of the input. +### `size` - Type: `"default" | "small" | "compact" | "__unstable-large"` - Required: No - Default: `'default'` +Adjusts the size of the input. + ### `suffix` + - Type: `ReactNode` + - Required: No + Renders an element on the right side of the input. By default, the suffix is aligned with the edge of the input border, with no padding. @@ -196,20 +200,17 @@ import { /> ``` - - Type: `ReactNode` - - Required: No - ### `tree` -An array containing the tree objects with the possible nodes the user can select. - - Type: `Tree[]` - Required: No -### `variant` +An array containing the tree objects with the possible nodes the user can select. -The style variant of the control. +### `variant` - Type: `"default" | "minimal"` - Required: No - Default: `'default'` + +The style variant of the control. diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index 2033a6f43fede..09bfef2c53b07 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env", "gutenberg-test-env", @@ -31,7 +29,6 @@ { "path": "../rich-text" }, { "path": "../warning" } ], - "include": [ "src/**/*" ], "exclude": [ "src/**/*.android.js", "src/**/*.ios.js", diff --git a/packages/compose/CHANGELOG.md b/packages/compose/CHANGELOG.md index 452eacbe289ff..4442395a6e1a3 100644 --- a/packages/compose/CHANGELOG.md +++ b/packages/compose/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 7.15.0 (2025-01-02) + ## 7.14.0 (2024-12-11) ## 7.13.0 (2024-11-27) diff --git a/packages/compose/package.json b/packages/compose/package.json index 6afbb1c954f47..d0eabb8562972 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/compose", - "version": "7.14.0", + "version": "7.15.1", "description": "WordPress higher-order components (HOCs).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,13 +33,13 @@ "dependencies": { "@babel/runtime": "7.25.7", "@types/mousetrap": "^1.6.8", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keycodes": "*", - "@wordpress/priority-queue": "*", - "@wordpress/undo-manager": "*", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/priority-queue": "file:../priority-queue", + "@wordpress/undo-manager": "file:../undo-manager", "change-case": "^4.1.2", "clipboard": "^2.0.11", "mousetrap": "^1.6.5", diff --git a/packages/core-commands/CHANGELOG.md b/packages/core-commands/CHANGELOG.md index fe5f12707b086..4fd1572c33aba 100644 --- a/packages/core-commands/CHANGELOG.md +++ b/packages/core-commands/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.15.0 (2025-01-02) + ## 1.14.0 (2024-12-11) ## 1.13.0 (2024-11-27) diff --git a/packages/core-commands/package.json b/packages/core-commands/package.json index 32cd82f4686b0..f6a2084dcccc0 100644 --- a/packages/core-commands/package.json +++ b/packages/core-commands/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-commands", - "version": "1.14.0", + "version": "1.15.1", "description": "WordPress core reusable commands.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,19 +29,19 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/block-editor": "*", - "@wordpress/commands": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/private-apis": "*", - "@wordpress/router": "*", - "@wordpress/url": "*" + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/commands": "file:../commands", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/router": "file:../router", + "@wordpress/url": "file:../url" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index 90395f58fc802..a9cc92f3e41ab 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 7.15.0 (2025-01-02) + ## 7.14.0 (2024-12-11) ## 7.13.0 (2024-11-27) diff --git a/packages/core-data/package.json b/packages/core-data/package.json index b0e5c10ddd24b..ca76317f22f7b 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "7.14.0", + "version": "7.15.1", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,22 +33,22 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/sync": "*", - "@wordpress/undo-manager": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/sync": "file:../sync", + "@wordpress/undo-manager": "file:../undo-manager", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", "change-case": "^4.1.2", "equivalent-key-map": "^0.2.2", "fast-deep-equal": "^3.1.3", diff --git a/packages/core-data/src/private-selectors.ts b/packages/core-data/src/private-selectors.ts index 0d4a28ad174a1..fb0401509694e 100644 --- a/packages/core-data/src/private-selectors.ts +++ b/packages/core-data/src/private-selectors.ts @@ -6,7 +6,12 @@ import { createSelector, createRegistrySelector } from '@wordpress/data'; /** * Internal dependencies */ -import { getDefaultTemplateId, getEntityRecord, type State } from './selectors'; +import { + canUser, + getDefaultTemplateId, + getEntityRecord, + type State, +} from './selectors'; import { STORE_NAME } from './name'; import { unlock } from './lock-unlock'; @@ -134,6 +139,13 @@ interface SiteData { export const getHomePage = createRegistrySelector( ( select ) => createSelector( () => { + const canReadSiteData = select( STORE_NAME ).canUser( 'read', { + kind: 'root', + name: 'site', + } ); + if ( ! canReadSiteData ) { + return null; + } const siteData = select( STORE_NAME ).getEntityRecord( 'root', 'site' @@ -156,7 +168,10 @@ export const getHomePage = createRegistrySelector( ( select ) => return { postType: 'wp_template', postId: frontPageTemplateId }; }, ( state ) => [ - getEntityRecord( state, 'root', 'site' ), + canUser( state, 'read', { + kind: 'root', + name: 'site', + } ) && getEntityRecord( state, 'root', 'site' ), getDefaultTemplateId( state, { slug: 'front-page', } ), @@ -165,6 +180,13 @@ export const getHomePage = createRegistrySelector( ( select ) => ); export const getPostsPageId = createRegistrySelector( ( select ) => () => { + const canReadSiteData = select( STORE_NAME ).canUser( 'read', { + kind: 'root', + name: 'site', + } ); + if ( ! canReadSiteData ) { + return null; + } const siteData = select( STORE_NAME ).getEntityRecord( 'root', 'site' ) as | SiteData | undefined; diff --git a/packages/core-data/tsconfig.json b/packages/core-data/tsconfig.json index 26602d82ab0c0..57c9d208e4c68 100644 --- a/packages/core-data/tsconfig.json +++ b/packages/core-data/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "checkJs": false, "noImplicitAny": false }, @@ -23,6 +21,5 @@ { "path": "../undo-manager" }, { "path": "../url" }, { "path": "../warning" } - ], - "include": [ "src/**/*" ] + ] } diff --git a/packages/create-block-interactive-template/package.json b/packages/create-block-interactive-template/package.json index 96a71fddfc91f..564fbf552b76b 100644 --- a/packages/create-block-interactive-template/package.json +++ b/packages/create-block-interactive-template/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/create-block-interactive-template", - "version": "2.14.0", + "version": "2.15.0", "description": "Template for @wordpress/create-block to create interactive blocks with the Interactivity API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache b/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache index 48469aa7d0d93..cb7f45b720794 100644 --- a/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache +++ b/packages/create-block-interactive-template/plugin-templates/$slug.php.mustache @@ -8,8 +8,12 @@ * Description: {{description}} {{/description}} * Version: {{version}} - * Requires at least: 6.6 - * Requires PHP: 7.2 +{{#requiresAtLeast}} + * Requires at least: {{requiresAtLeast}} +{{/requiresAtLeast}} +{{#requiresPHP}} + * Requires PHP: {{requiresPHP}} +{{/requiresPHP}} {{#author}} * Author: {{author}} {{/author}} diff --git a/packages/create-block-interactive-template/plugin-templates/readme.txt.mustache b/packages/create-block-interactive-template/plugin-templates/readme.txt.mustache index c3abf5ae4ec02..19a4c8e78587b 100644 --- a/packages/create-block-interactive-template/plugin-templates/readme.txt.mustache +++ b/packages/create-block-interactive-template/plugin-templates/readme.txt.mustache @@ -3,7 +3,9 @@ Contributors: {{author}} {{/author}} Tags: block -Tested up to: 6.6 +{{#testedUpTo}} +Tested up to: {{testedUpTo}} +{{/testedUpTo}} Stable tag: {{version}} {{#license}} License: {{license}} diff --git a/packages/create-block-tutorial-template/CHANGELOG.md b/packages/create-block-tutorial-template/CHANGELOG.md index c183144513708..bad91521a42b9 100644 --- a/packages/create-block-tutorial-template/CHANGELOG.md +++ b/packages/create-block-tutorial-template/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/create-block-tutorial-template/package.json b/packages/create-block-tutorial-template/package.json index 7a585777f3f97..9ab1c66252577 100644 --- a/packages/create-block-tutorial-template/package.json +++ b/packages/create-block-tutorial-template/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/create-block-tutorial-template", - "version": "4.14.0", + "version": "4.15.0", "description": "This is a template for @wordpress/create-block that creates an example 'Copyright Date' block. This block is used in the official WordPress block development Quick Start Guide.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache b/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache index 7ce4be3f7cc73..49959fb5b2f69 100644 --- a/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache +++ b/packages/create-block-tutorial-template/plugin-templates/$slug.php.mustache @@ -8,8 +8,12 @@ * Description: {{description}} {{/description}} * Version: {{version}} - * Requires at least: 6.6 - * Requires PHP: 7.2 +{{#requiresAtLeast}} + * Requires at least: {{requiresAtLeast}} +{{/requiresAtLeast}} +{{#requiresPHP}} + * Requires PHP: {{requiresPHP}} +{{/requiresPHP}} {{#author}} * Author: {{author}} {{/author}} diff --git a/packages/create-block-tutorial-template/plugin-templates/readme.txt.mustache b/packages/create-block-tutorial-template/plugin-templates/readme.txt.mustache index c3abf5ae4ec02..19a4c8e78587b 100644 --- a/packages/create-block-tutorial-template/plugin-templates/readme.txt.mustache +++ b/packages/create-block-tutorial-template/plugin-templates/readme.txt.mustache @@ -3,7 +3,9 @@ Contributors: {{author}} {{/author}} Tags: block -Tested up to: 6.6 +{{#testedUpTo}} +Tested up to: {{testedUpTo}} +{{/testedUpTo}} Stable tag: {{version}} {{#license}} License: {{license}} diff --git a/packages/create-block/CHANGELOG.md b/packages/create-block/CHANGELOG.md index d9b81d8509bcf..5ebb9dc3f7cf1 100644 --- a/packages/create-block/CHANGELOG.md +++ b/packages/create-block/CHANGELOG.md @@ -2,9 +2,17 @@ ## Unreleased +## 4.58.0 (2025-01-02) + ### Enhancement - Add support for custom `textdomain` property for the scaffolded block ([#57197](https://github.com/WordPress/gutenberg/pull/57197)). +- Allow external templates to customize additional plugin header and readme fields: "Requires at least", "Requires PHP", and "Tested up to" ([#68193](https://github.com/WordPress/gutenberg/pull/68193)) +- Update the default template to scaffold a block in its subfolder to make it easier to update to multiple blocks in a single plugin ([#68175](https://github.com/WordPress/gutenberg/pull/68175)). + +### Internal + +- Refactored the code to use new API introduced together with `@inquirer/prompts` instead of legacy `inquirer` package ([#67877](https://github.com/WordPress/gutenberg/pull/67877)). ## 4.57.0 (2024-12-11) diff --git a/packages/create-block/docs/external-template.md b/packages/create-block/docs/external-template.md index 45c3cba8c9271..d840896f266f3 100644 --- a/packages/create-block/docs/external-template.md +++ b/packages/create-block/docs/external-template.md @@ -76,10 +76,13 @@ The following configurable variables are used with the template files. Template - `npmDevDependencies` (default: `[]`) – the list of remote npm packages to be installed in the project with [`npm install --save-dev`](https://docs.npmjs.com/cli/v8/commands/npm-install) when `wpScripts` is enabled. - `customPackageJSON` (no default) - allows definition of additional properties for the generated package.json file. -**Plugin header fields** ([learn more](https://developer.wordpress.org/plugins/plugin-basics/header-requirements/)): +**Plugin header and readme fields** (learn more about [header requirements](https://developer.wordpress.org/plugins/plugin-basics/header-requirements/) and [readmes](https://developer.wordpress.org/plugins/wordpress-org/how-your-readme-txt-works/)): - `pluginURI` (no default) – the home page of the plugin. - `version` (default: `'0.1.0'`) – the current version number of the plugin. +- `requiresAtLeast` (default: `'6.7'`) – the lowest WordPress version that the plugin will work on. +- `requiresPHP` (default: `'7.4'`) – the minimum required PHP version for use with this plugin. +- `testedUpTo` (default: `'6.7'`) – the highest WordPress version that the plugin has been tested against. - `author` (default: `'The WordPress Contributors'`) – the name of the plugin author(s). - `license` (default: `'GPL-2.0-or-later'`) – the short name of the plugin’s license. - `licenseURI` (default: `'https://www.gnu.org/licenses/gpl-2.0.html'`) – a link to the full text of the license. @@ -97,6 +100,7 @@ The following configurable variables are used with the template files. Template - `description` (no default) – a short description for your block. - `dashicon` (no default) – an icon property thats makes it easier to identify a block ([available values](https://developer.wordpress.org/resource/dashicons/)). - `category` (default: `'widgets'`) – blocks are grouped into categories to help users browse and discover them. The categories provided by core are `text`, `media`, `design`, `widgets`, `theme`, and `embed`. +- `textdomain` (defaults to the `slug` value) – the text domain used to make strings translatable ([more info](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#text-domains)). - `attributes` (no default) – block attributes ([more details](https://developer.wordpress.org/block-editor/developers/block-api/block-attributes/)). - `supports` (no default) – optional block extended support features ([more details](https://developer.wordpress.org/block-editor/developers/block-api/block-supports/). - `editorScript` (default: `'file:./index.js'`) – an editor script definition. diff --git a/packages/create-block/lib/check-system-requirements.js b/packages/create-block/lib/check-system-requirements.js index 4a88d167d437c..152931bc19141 100644 --- a/packages/create-block/lib/check-system-requirements.js +++ b/packages/create-block/lib/check-system-requirements.js @@ -1,7 +1,7 @@ /** * External dependencies */ -const inquirer = require( 'inquirer' ); +const { confirm } = require( '@inquirer/prompts' ); const checkSync = require( 'check-node-version' ); const tools = require( 'check-node-version/tools' ); const { promisify } = require( 'util' ); @@ -34,14 +34,10 @@ async function checkSystemRequirements( engines ) { log.error( 'The program may not complete correctly if you continue.' ); log.info( '' ); - const { yesContinue } = await inquirer.prompt( [ - { - type: 'confirm', - name: 'yesContinue', - message: 'Are you sure you want to continue anyway?', - default: false, - }, - ] ); + const yesContinue = await confirm( { + message: 'Are you sure you want to continue anyway?', + default: false, + } ); if ( ! yesContinue ) { log.error( 'Cancelled.' ); diff --git a/packages/create-block/lib/index.js b/packages/create-block/lib/index.js index c84e143b1a6ca..ccc2e91b106e2 100644 --- a/packages/create-block/lib/index.js +++ b/packages/create-block/lib/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -const inquirer = require( 'inquirer' ); +const { confirm, select } = require( '@inquirer/prompts' ); const { capitalCase } = require( 'change-case' ); const program = require( 'commander' ); @@ -14,9 +14,9 @@ const log = require( './log' ); const { engines, version } = require( '../package.json' ); const scaffold = require( './scaffold' ); const { - getPluginTemplate, getDefaultValues, - getPrompts, + getProjectTemplate, + runPrompts, } = require( './templates' ); const commandName = `wp-create-block`; @@ -79,11 +79,13 @@ program targetDir, } ) => { - await checkSystemRequirements( engines ); try { - const pluginTemplate = await getPluginTemplate( templateName ); + await checkSystemRequirements( engines ); + + const projectTemplate = + await getProjectTemplate( templateName ); const availableVariants = Object.keys( - pluginTemplate.variants + projectTemplate.variants ); if ( variant && ! availableVariants.includes( variant ) ) { if ( ! availableVariants.length ) { @@ -113,7 +115,7 @@ program if ( slug ) { const defaultValues = getDefaultValues( - pluginTemplate, + projectTemplate, variant ); const answers = { @@ -123,7 +125,7 @@ program title: capitalCase( slug ), ...optionsValues, }; - await scaffold( pluginTemplate, answers ); + await scaffold( projectTemplate, answers ); } else { log.info( '' ); log.info( @@ -133,25 +135,22 @@ program ); if ( ! variant && availableVariants.length > 1 ) { - const result = await inquirer.prompt( { - type: 'list', - name: 'variant', + variant = await select( { message: 'The template variant to use for this block:', - choices: availableVariants, + choices: availableVariants.map( ( value ) => ( { + value, + } ) ), } ); - variant = result.variant; } const defaultValues = getDefaultValues( - pluginTemplate, + projectTemplate, variant ); - const filterOptionsProvided = ( { name } ) => - ! Object.keys( optionsValues ).includes( name ); - const blockPrompts = getPrompts( - pluginTemplate, + const blockAnswers = await runPrompts( + projectTemplate, [ 'slug', 'namespace', @@ -159,46 +158,36 @@ program 'description', 'dashicon', 'category', - 'textdomain', - ], - variant - ).filter( filterOptionsProvided ); - const blockAnswers = await inquirer.prompt( blockPrompts ); - - const pluginAnswers = plugin - ? await inquirer - .prompt( { - type: 'confirm', - name: 'configurePlugin', - message: - 'Do you want to customize the WordPress plugin?', - default: false, - } ) - .then( async ( { configurePlugin } ) => { - if ( ! configurePlugin ) { - return {}; - } + ! plugin && 'textdomain', + ].filter( Boolean ), + variant, + optionsValues + ); - const pluginPrompts = getPrompts( - pluginTemplate, - [ - 'pluginURI', - 'version', - 'author', - 'license', - 'licenseURI', - 'domainPath', - 'updateURI', - ], - variant - ).filter( filterOptionsProvided ); - const result = - await inquirer.prompt( pluginPrompts ); - return result; - } ) - : {}; + const pluginAnswers = + plugin && + ( await confirm( { + message: + 'Do you want to customize the WordPress plugin?', + default: false, + } ) ) + ? await runPrompts( + projectTemplate, + [ + 'pluginURI', + 'version', + 'author', + 'license', + 'licenseURI', + 'domainPath', + 'updateURI', + ], + variant, + optionsValues + ) + : {}; - await scaffold( pluginTemplate, { + await scaffold( projectTemplate, { ...defaultValues, ...optionsValues, variant, @@ -210,6 +199,9 @@ program if ( error instanceof CLIError ) { log.error( error.message ); process.exit( 1 ); + } else if ( error.name === 'ExitPromptError' ) { + log.info( 'Cancelled.' ); + process.exit( 1 ); } else { throw error; } diff --git a/packages/create-block/lib/prompts.js b/packages/create-block/lib/prompts.js index 625320b15c9d3..88bdaf22635d3 100644 --- a/packages/create-block/lib/prompts.js +++ b/packages/create-block/lib/prompts.js @@ -11,7 +11,6 @@ const upperFirst = ( [ firstLetter, ...rest ] ) => // Block metadata. const slug = { type: 'input', - name: 'slug', message: 'The block slug used for identification (also the output folder name):', validate( input ) { @@ -25,7 +24,6 @@ const slug = { const namespace = { type: 'input', - name: 'namespace', message: 'The internal namespace for the block name (something unique for your products):', validate( input ) { @@ -39,25 +37,22 @@ const namespace = { const title = { type: 'input', - name: 'title', message: 'The display title for your block:', - filter( input ) { + transformer( input ) { return input && upperFirst( input ); }, }; const description = { type: 'input', - name: 'description', message: 'The short description for your block (optional):', - filter( input ) { + transformer( input ) { return input && upperFirst( input ); }, }; const dashicon = { type: 'input', - name: 'dashicon', message: 'The dashicon to make it easier to identify your block (optional):', validate( input ) { @@ -67,23 +62,23 @@ const dashicon = { return true; }, - filter( input ) { + transformer( input ) { return input && input.replace( /dashicon(s)?-/, '' ); }, }; const category = { - type: 'list', - name: 'category', + type: 'select', message: 'The category name to help users browse and discover your block:', - choices: [ 'text', 'media', 'design', 'widgets', 'theme', 'embed' ], + choices: [ 'text', 'media', 'design', 'widgets', 'theme', 'embed' ].map( + ( value ) => ( { value } ) + ), }; const textdomain = { type: 'input', - name: 'textdomain', message: - 'The text domain used to internationalize text in the block (by default it will be same as slug):', + 'The text domain used to make strings translatable in the block (optional):', validate( input ) { if ( input.length && ! /^[a-z][a-z0-9\-]*$/.test( input ) ) { return 'Invalid text domain specified. Text domain can contain only lowercase alphanumeric characters or dashes, and start with a letter.'; @@ -96,14 +91,12 @@ const textdomain = { // Plugin header fields. const pluginURI = { type: 'input', - name: 'pluginURI', message: 'The home page of the plugin (optional). Unique URL outside of WordPress.org:', }; const version = { type: 'input', - name: 'version', message: 'The current version number of the plugin:', validate( input ) { // Regular expression was copied from https://semver.org. @@ -119,32 +112,27 @@ const version = { const author = { type: 'input', - name: 'author', message: 'The name of the plugin author (optional). Multiple authors may be listed using commas:', }; const license = { type: 'input', - name: 'license', message: 'The short name of the plugin’s license (optional):', }; const licenseURI = { type: 'input', - name: 'licenseURI', message: 'A link to the full text of the license (optional):', }; const domainPath = { type: 'input', - name: 'domainPath', message: 'A custom domain path for the translations (optional):', }; const updateURI = { type: 'input', - name: 'updateURI', message: 'A custom update URI for the plugin (optional):', }; diff --git a/packages/create-block/lib/scaffold.js b/packages/create-block/lib/scaffold.js index bc7cb3b8bfcd3..44812e3d5954d 100644 --- a/packages/create-block/lib/scaffold.js +++ b/packages/create-block/lib/scaffold.js @@ -36,6 +36,9 @@ module.exports = async ( domainPath, updateURI, version, + requiresAtLeast, + requiresPHP, + testedUpTo, wpScripts, wpEnv, npmDependencies, @@ -58,13 +61,12 @@ module.exports = async ( } ) => { slug = slug.toLowerCase(); - namespace = namespace.toLowerCase(); const rootDirectory = join( process.cwd(), targetDir || slug ); const transformedValues = transformer( { $schema, apiVersion, plugin, - namespace, + namespace: namespace.toLowerCase(), slug, title, description, @@ -79,12 +81,15 @@ module.exports = async ( domainPath, updateURI, version, + requiresAtLeast, + requiresPHP, + testedUpTo, wpScripts, wpEnv, npmDependencies, npmDevDependencies, customScripts, - folderName, + folderName: folderName.replace( /\$slug/g, slug ), editorScript, editorStyle, style, diff --git a/packages/create-block/lib/templates.js b/packages/create-block/lib/templates.js index 4e70ee66fd3a4..3604ac99a35ea 100644 --- a/packages/create-block/lib/templates.js +++ b/packages/create-block/lib/templates.js @@ -1,6 +1,7 @@ /** * External dependencies */ +const inquirer = require( '@inquirer/prompts' ); const { command } = require( 'execa' ); const glob = require( 'fast-glob' ); const { resolve } = require( 'path' ); @@ -58,6 +59,7 @@ const predefinedPluginTemplates = { }, viewScript: 'file:./view.js', example: {}, + folderName: './src/$slug', }, variants: { static: {}, @@ -157,7 +159,7 @@ const configToTemplate = async ( { }; }; -const getPluginTemplate = async ( templateName ) => { +const getProjectTemplate = async ( templateName ) => { if ( predefinedPluginTemplates[ templateName ] ) { return await configToTemplate( predefinedPluginTemplates[ templateName ] @@ -224,16 +226,20 @@ const getPluginTemplate = async ( templateName ) => { } }; -const getDefaultValues = ( pluginTemplate, variant ) => { +const getDefaultValues = ( projectTemplate, variant ) => { return { $schema: 'https://schemas.wp.org/trunk/block.json', apiVersion: 3, namespace: 'create-block', category: 'widgets', + textdomain: '', author: 'The WordPress Contributors', license: 'GPL-2.0-or-later', licenseURI: 'https://www.gnu.org/licenses/gpl-2.0.html', version: '0.1.0', + requiresAtLeast: '6.7', + requiresPHP: '7.4', + testedUpTo: '6.7', wpScripts: true, customScripts: {}, wpEnv: false, @@ -243,20 +249,33 @@ const getDefaultValues = ( pluginTemplate, variant ) => { editorStyle: 'file:./index.css', style: 'file:./style-index.css', transformer: ( view ) => view, - ...pluginTemplate.defaultValues, - ...pluginTemplate.variants?.[ variant ], - variantVars: getVariantVars( pluginTemplate.variants, variant ), + ...projectTemplate.defaultValues, + ...projectTemplate.variants?.[ variant ], + variantVars: getVariantVars( projectTemplate.variants, variant ), }; }; -const getPrompts = ( pluginTemplate, keys, variant ) => { - const defaultValues = getDefaultValues( pluginTemplate, variant ); - return keys.map( ( promptName ) => { - return { - ...prompts[ promptName ], +const runPrompts = async ( + projectTemplate, + promptNames, + variant, + optionsValues +) => { + const defaultValues = getDefaultValues( projectTemplate, variant ); + const result = {}; + for ( const promptName of promptNames ) { + if ( Object.keys( optionsValues ).includes( promptName ) ) { + continue; + } + + const { type, ...config } = prompts[ promptName ]; + result[ promptName ] = await inquirer[ type ]( { + ...config, default: defaultValues[ promptName ], - }; - } ); + } ); + } + + return result; }; const getVariantVars = ( variants, variant ) => { @@ -277,7 +296,7 @@ const getVariantVars = ( variants, variant ) => { }; module.exports = { - getPluginTemplate, getDefaultValues, - getPrompts, + getProjectTemplate, + runPrompts, }; diff --git a/packages/create-block/lib/templates/es5/$slug.php.mustache b/packages/create-block/lib/templates/es5/$slug.php.mustache index 825fd1bfd8b5a..5beb2ca06712c 100644 --- a/packages/create-block/lib/templates/es5/$slug.php.mustache +++ b/packages/create-block/lib/templates/es5/$slug.php.mustache @@ -7,9 +7,13 @@ {{#description}} * Description: {{description}} {{/description}} - * Requires at least: 6.6 - * Requires PHP: 7.2 * Version: {{version}} +{{#requiresAtLeast}} + * Requires at least: {{requiresAtLeast}} +{{/requiresAtLeast}} +{{#requiresPHP}} + * Requires PHP: {{requiresPHP}} +{{/requiresPHP}} {{#author}} * Author: {{author}} {{/author}} diff --git a/packages/create-block/lib/templates/es5/readme.txt.mustache b/packages/create-block/lib/templates/es5/readme.txt.mustache index c3abf5ae4ec02..19a4c8e78587b 100644 --- a/packages/create-block/lib/templates/es5/readme.txt.mustache +++ b/packages/create-block/lib/templates/es5/readme.txt.mustache @@ -3,7 +3,9 @@ Contributors: {{author}} {{/author}} Tags: block -Tested up to: 6.6 +{{#testedUpTo}} +Tested up to: {{testedUpTo}} +{{/testedUpTo}} Stable tag: {{version}} {{#license}} License: {{license}} diff --git a/packages/create-block/lib/templates/plugin/$slug.php.mustache b/packages/create-block/lib/templates/plugin/$slug.php.mustache index 75666af3a850b..e229560a655c3 100644 --- a/packages/create-block/lib/templates/plugin/$slug.php.mustache +++ b/packages/create-block/lib/templates/plugin/$slug.php.mustache @@ -7,9 +7,13 @@ {{#description}} * Description: {{description}} {{/description}} - * Requires at least: 6.6 - * Requires PHP: 7.2 * Version: {{version}} +{{#requiresAtLeast}} + * Requires at least: {{requiresAtLeast}} +{{/requiresAtLeast}} +{{#requiresPHP}} + * Requires PHP: {{requiresPHP}} +{{/requiresPHP}} {{#author}} * Author: {{author}} {{/author}} @@ -42,6 +46,6 @@ if ( ! defined( 'ABSPATH' ) ) { * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() { - register_block_type( __DIR__ . '/build' ); + register_block_type( __DIR__ . '/build/{{slug}}' ); } add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' ); diff --git a/packages/create-block/lib/templates/plugin/readme.txt.mustache b/packages/create-block/lib/templates/plugin/readme.txt.mustache index c3abf5ae4ec02..19a4c8e78587b 100644 --- a/packages/create-block/lib/templates/plugin/readme.txt.mustache +++ b/packages/create-block/lib/templates/plugin/readme.txt.mustache @@ -3,7 +3,9 @@ Contributors: {{author}} {{/author}} Tags: block -Tested up to: 6.6 +{{#testedUpTo}} +Tested up to: {{testedUpTo}} +{{/testedUpTo}} Stable tag: {{version}} {{#license}} License: {{license}} diff --git a/packages/create-block/package.json b/packages/create-block/package.json index 375fee43ba1f7..c3ec08036971c 100644 --- a/packages/create-block/package.json +++ b/packages/create-block/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/create-block", - "version": "4.57.0", + "version": "4.58.1", "description": "Generates PHP, JS and CSS code for registering a block for a WordPress plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,14 +31,14 @@ "wp-create-block": "./index.js" }, "dependencies": { - "@wordpress/lazy-import": "*", + "@inquirer/prompts": "^7.2.0", + "@wordpress/lazy-import": "file:../lazy-import", "chalk": "^4.0.0", "change-case": "^4.1.2", "check-node-version": "^4.1.0", "commander": "^9.2.0", "execa": "^4.0.2", "fast-glob": "^3.2.7", - "inquirer": "^7.1.0", "make-dir": "^3.0.0", "mustache": "^4.0.0", "npm-package-arg": "^8.1.5", diff --git a/packages/customize-widgets/CHANGELOG.md b/packages/customize-widgets/CHANGELOG.md index 95ec034125a5a..5dbd3dd9c0cd1 100644 --- a/packages/customize-widgets/CHANGELOG.md +++ b/packages/customize-widgets/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/customize-widgets/package.json b/packages/customize-widgets/package.json index 236a50342ff7a..12df1e4c078cb 100644 --- a/packages/customize-widgets/package.json +++ b/packages/customize-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/customize-widgets", - "version": "5.14.0", + "version": "5.15.1", "description": "Widgets blocks in Customizer Module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -26,26 +26,26 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interface": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/media-utils": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/widgets": "*", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/widgets": "file:../widgets", "clsx": "^2.1.1", "fast-deep-equal": "^3.1.3" }, diff --git a/packages/data-controls/CHANGELOG.md b/packages/data-controls/CHANGELOG.md index 639bd22ba15da..bb1af5f3d2cbb 100644 --- a/packages/data-controls/CHANGELOG.md +++ b/packages/data-controls/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/data-controls/package.json b/packages/data-controls/package.json index f1438a39e2a45..dbf7bd0fe988e 100644 --- a/packages/data-controls/package.json +++ b/packages/data-controls/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data-controls", - "version": "4.14.0", + "version": "4.15.1", "description": "A set of common controls for the @wordpress/data api.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,9 +30,9 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*" + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated" }, "peerDependencies": { "react": "^18.0.0" diff --git a/packages/data-controls/tsconfig.json b/packages/data-controls/tsconfig.json index 5ccc6045880d4..faa13b152672b 100644 --- a/packages/data-controls/tsconfig.json +++ b/packages/data-controls/tsconfig.json @@ -2,14 +2,11 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] }, "references": [ { "path": "../api-fetch" }, { "path": "../data" }, { "path": "../deprecated" } - ], - "include": [ "src/**/*" ] + ] } diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index b134a93aa77f6..6a8d1871fab56 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 10.15.0 (2025-01-02) + ## 10.14.0 (2024-12-11) ## 10.13.0 (2024-11-27) diff --git a/packages/data/package.json b/packages/data/package.json index ca5af390dc51c..fe9d64f2f76bf 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "10.14.0", + "version": "10.15.1", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,13 +31,13 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/compose": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/is-shallow-equal": "*", - "@wordpress/priority-queue": "*", - "@wordpress/private-apis": "*", - "@wordpress/redux-routine": "*", + "@wordpress/compose": "file:../compose", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/priority-queue": "file:../priority-queue", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/redux-routine": "file:../redux-routine", "deepmerge": "^4.3.0", "equivalent-key-map": "^0.2.2", "is-plain-object": "^5.0.0", diff --git a/packages/data/tsconfig.json b/packages/data/tsconfig.json index 2bfc881dc6216..b73eca0d342f0 100644 --- a/packages/data/tsconfig.json +++ b/packages/data/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "checkJs": false }, "references": [ @@ -14,6 +12,5 @@ { "path": "../is-shallow-equal" }, { "path": "../priority-queue" }, { "path": "../redux-routine" } - ], - "include": [ "src/**/*" ] + ] } diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md index 965d98e80d6ae..ed7964499f53a 100644 --- a/packages/dataviews/CHANGELOG.md +++ b/packages/dataviews/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.11.0 (2025-01-02) + ### Bug Fixes - Fixed commonjs export ([#67962](https://github.com/WordPress/gutenberg/pull/67962)) diff --git a/packages/dataviews/package.json b/packages/dataviews/package.json index 7f6d96745acab..62150d133c411 100644 --- a/packages/dataviews/package.json +++ b/packages/dataviews/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dataviews", - "version": "4.10.0", + "version": "4.11.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", @@ -46,15 +46,15 @@ "dependencies": { "@ariakit/react": "^0.4.15", "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/warning": "*", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/warning": "file:../warning", "clsx": "^2.1.1", "remove-accents": "^0.5.0" }, diff --git a/packages/dataviews/src/components/dataviews-context/index.ts b/packages/dataviews/src/components/dataviews-context/index.ts index bcacfe32d47bd..992048f909706 100644 --- a/packages/dataviews/src/components/dataviews-context/index.ts +++ b/packages/dataviews/src/components/dataviews-context/index.ts @@ -29,6 +29,7 @@ type DataViewsContextType< Item > = { getItemLevel?: ( item: Item ) => number; onClickItem?: ( item: Item ) => void; isItemClickable: ( item: Item ) => boolean; + containerWidth: number; }; const DataViewsContext = createContext< DataViewsContextType< any > >( { @@ -46,6 +47,7 @@ const DataViewsContext = createContext< DataViewsContextType< any > >( { openedFilter: null, getItemId: ( item ) => item.id, isItemClickable: () => true, + containerWidth: 0, } ); export default DataViewsContext; diff --git a/packages/dataviews/src/components/dataviews-item-actions/index.tsx b/packages/dataviews/src/components/dataviews-item-actions/index.tsx index f849eca7c783f..70df04e4333e6 100644 --- a/packages/dataviews/src/components/dataviews-item-actions/index.tsx +++ b/packages/dataviews/src/components/dataviews-item-actions/index.tsx @@ -75,6 +75,8 @@ function ButtonTrigger< Item >( { <Button label={ label } icon={ action.icon } + disabled={ !! action.disabled } + accessibleWhenDisabled isDestructive={ action.isDestructive } size="compact" onClick={ onClick } @@ -90,7 +92,7 @@ function MenuItemTrigger< Item >( { const label = typeof action.label === 'string' ? action.label : action.label( items ); return ( - <Menu.Item onClick={ onClick }> + <Menu.Item disabled={ action.disabled } onClick={ onClick }> <Menu.ItemLabel>{ label }</Menu.ItemLabel> </Menu.Item> ); @@ -145,13 +147,6 @@ export function ActionsMenuGroup< Item >( { ); } -function hasOnlyOneActionAndIsPrimary< Item >( - primaryActions: Action< Item >[], - actions: Action< Item >[] -) { - return primaryActions.length === 1 && actions.length === 1; -} - export default function ItemActions< Item >( { item, actions, @@ -184,7 +179,8 @@ export default function ItemActions< Item >( { ); } - if ( hasOnlyOneActionAndIsPrimary( primaryActions, actions ) ) { + // If all actions are primary, there is no need to render the dropdown. + if ( primaryActions.length === eligibleActions.length ) { return ( <PrimaryActions item={ item } diff --git a/packages/dataviews/src/components/dataviews-selection-checkbox/index.tsx b/packages/dataviews/src/components/dataviews-selection-checkbox/index.tsx index 827f061976443..e069e7d74b0ef 100644 --- a/packages/dataviews/src/components/dataviews-selection-checkbox/index.tsx +++ b/packages/dataviews/src/components/dataviews-selection-checkbox/index.tsx @@ -1,8 +1,8 @@ /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; import { CheckboxControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -29,20 +29,11 @@ export default function DataViewsSelectionCheckbox< Item >( { }: DataViewsSelectionCheckboxProps< Item > ) { const id = getItemId( item ); const checked = ! disabled && selection.includes( id ); - let selectionLabel; - if ( titleField?.getValue && item ) { - // eslint-disable-next-line @wordpress/valid-sprintf - selectionLabel = sprintf( - checked - ? /* translators: %s: item title. */ __( 'Deselect item: %s' ) - : /* translators: %s: item title. */ __( 'Select item: %s' ), - titleField.getValue( { item } ) - ); - } else { - selectionLabel = checked - ? __( 'Select a new item' ) - : __( 'Deselect item' ); - } + + // Fallback label to ensure accessibility + const selectionLabel = + titleField?.getValue?.( { item } ) || __( '(no title)' ); + return ( <CheckboxControl className="dataviews-selection-checkbox" diff --git a/packages/dataviews/src/components/dataviews/index.tsx b/packages/dataviews/src/components/dataviews/index.tsx index 872703bc5e1e1..a0a8948813654 100644 --- a/packages/dataviews/src/components/dataviews/index.tsx +++ b/packages/dataviews/src/components/dataviews/index.tsx @@ -8,6 +8,7 @@ import type { ReactNode } from 'react'; */ import { __experimentalHStack as HStack } from '@wordpress/components'; import { useMemo, useState } from '@wordpress/element'; +import { useResizeObserver } from '@wordpress/compose'; /** * Internal dependencies @@ -75,6 +76,15 @@ export default function DataViews< Item >( { isItemClickable = defaultIsItemClickable, header, }: DataViewsProps< Item > ) { + const [ containerWidth, setContainerWidth ] = useState( 0 ); + const containerRef = useResizeObserver( + ( resizeObserverEntries: any ) => { + setContainerWidth( + resizeObserverEntries[ 0 ].borderBoxSize[ 0 ].inlineSize + ); + }, + { box: 'border-box' } + ); const [ selectionState, setSelectionState ] = useState< string[] >( [] ); const isUncontrolled = selectionProperty === undefined || onChangeSelection === undefined; @@ -120,9 +130,10 @@ export default function DataViews< Item >( { getItemLevel, isItemClickable, onClickItem, + containerWidth, } } > - <div className="dataviews-wrapper"> + <div className="dataviews-wrapper" ref={ containerRef }> <HStack alignment="top" justify="space-between" diff --git a/packages/dataviews/src/components/form-field-visibility/index.tsx b/packages/dataviews/src/components/form-field-visibility/index.tsx deleted file mode 100644 index 8cea59f11b7ae..0000000000000 --- a/packages/dataviews/src/components/form-field-visibility/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/** - * WordPress dependencies - */ -import { useMemo } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import type { NormalizedField } from '../../types'; - -type FormFieldVisibilityProps< Item > = React.PropsWithChildren< { - field: NormalizedField< Item >; - data: Item; -} >; - -export default function FormFieldVisibility< Item >( { - data, - field, - children, -}: FormFieldVisibilityProps< Item > ) { - const isVisible = useMemo( () => { - if ( field.isVisible ) { - return field.isVisible( data ); - } - return true; - }, [ field.isVisible, data ] ); - - if ( ! isVisible ) { - return null; - } - return children; -} diff --git a/packages/dataviews/src/dataviews-layouts/grid/preview-size-picker.tsx b/packages/dataviews/src/dataviews-layouts/grid/preview-size-picker.tsx index b48c6422bd6b3..027632090b31b 100644 --- a/packages/dataviews/src/dataviews-layouts/grid/preview-size-picker.tsx +++ b/packages/dataviews/src/dataviews-layouts/grid/preview-size-picker.tsx @@ -3,7 +3,6 @@ */ import { RangeControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useViewportMatch } from '@wordpress/compose'; import { useMemo, useContext } from '@wordpress/element'; /** @@ -12,7 +11,9 @@ import { useMemo, useContext } from '@wordpress/element'; import DataViewsContext from '../../components/dataviews-context'; import type { ViewGrid } from '../../types'; -const viewportBreaks = { +const viewportBreaks: { + [ key: string ]: { min: number; max: number; default: number }; +} = { xhuge: { min: 3, max: 6, default: 5 }, huge: { min: 2, max: 4, default: 4 }, xlarge: { min: 2, max: 3, default: 3 }, @@ -20,38 +21,35 @@ const viewportBreaks = { mobile: { min: 1, max: 2, default: 2 }, }; -function useViewPortBreakpoint() { - const isXHuge = useViewportMatch( 'xhuge', '>=' ); - const isHuge = useViewportMatch( 'huge', '>=' ); - const isXlarge = useViewportMatch( 'xlarge', '>=' ); - const isLarge = useViewportMatch( 'large', '>=' ); - const isMobile = useViewportMatch( 'mobile', '>=' ); +/** + * Breakpoints were adjusted from media queries breakpoints to account for + * the sidebar width. This was done to match the existing styles we had. + */ +const BREAKPOINTS = { + xhuge: 1520, + huge: 1140, + xlarge: 780, + large: 480, + mobile: 0, +}; - if ( isXHuge ) { - return 'xhuge'; - } - if ( isHuge ) { - return 'huge'; - } - if ( isXlarge ) { - return 'xlarge'; - } - if ( isLarge ) { - return 'large'; - } - if ( isMobile ) { - return 'mobile'; +function useViewPortBreakpoint() { + const containerWidth = useContext( DataViewsContext ).containerWidth; + for ( const [ key, value ] of Object.entries( BREAKPOINTS ) ) { + if ( containerWidth >= value ) { + return key; + } } - return null; + return 'mobile'; } export function useUpdatedPreviewSizeOnViewportChange() { - const viewport = useViewPortBreakpoint(); const view = useContext( DataViewsContext ).view as ViewGrid; + const viewport = useViewPortBreakpoint(); return useMemo( () => { const previewSize = view.layout?.previewSize; let newPreviewSize; - if ( ! viewport || ! previewSize ) { + if ( ! previewSize ) { return; } const breakValues = viewportBreaks[ viewport ]; @@ -69,9 +67,8 @@ export default function PreviewSizePicker() { const viewport = useViewPortBreakpoint(); const context = useContext( DataViewsContext ); const view = context.view as ViewGrid; - const breakValues = viewportBreaks[ viewport || 'mobile' ]; + const breakValues = viewportBreaks[ viewport ]; const previewSizeToUse = view.layout?.previewSize || breakValues.default; - const marks = useMemo( () => Array.from( @@ -84,11 +81,9 @@ export default function PreviewSizePicker() { ), [ breakValues ] ); - - if ( ! viewport ) { + if ( viewport === 'mobile' ) { return null; } - return ( <RangeControl __nextHasNoMarginBottom diff --git a/packages/dataviews/src/dataviews-layouts/grid/style.scss b/packages/dataviews/src/dataviews-layouts/grid/style.scss index acabf35e9c41c..333e6e9a4caf9 100644 --- a/packages/dataviews/src/dataviews-layouts/grid/style.scss +++ b/packages/dataviews/src/dataviews-layouts/grid/style.scss @@ -3,6 +3,7 @@ grid-template-rows: max-content; padding: 0 $grid-unit-60 $grid-unit-30; transition: padding ease-out 0.1s; + container-type: inline-size; @include reduce-motion("transition"); @@ -115,21 +116,25 @@ } .dataviews-view-grid.dataviews-view-grid { - grid-template-columns: repeat(1, minmax(0, 1fr)); - - @include break-mobile() { + /** + * Breakpoints were adjusted from media queries breakpoints to account for + * the sidebar width. This was done to match the existing styles we had. + */ + @container (max-width: 480px) { + grid-template-columns: repeat(1, minmax(0, 1fr)); + padding-left: $grid-unit-30; + padding-right: $grid-unit-30; + } + @container (min-width: 480px) { grid-template-columns: repeat(2, minmax(0, 1fr)); } - - @include break-xlarge() { + @container (min-width: 780px) { grid-template-columns: repeat(3, minmax(0, 1fr)); } - - @include break-huge() { + @container (min-width: 1140px) { grid-template-columns: repeat(4, minmax(0, 1fr)); } - - @include break-xhuge() { + @container (min-width: 1520px) { grid-template-columns: repeat(5, minmax(0, 1fr)); } } @@ -152,13 +157,6 @@ top: $grid-unit-10; } -@container (max-width: 430px) { - .dataviews-view-grid { - padding-left: $grid-unit-30; - padding-right: $grid-unit-30; - } -} - .dataviews-view-grid__media--clickable { cursor: pointer; } diff --git a/packages/dataviews/src/dataviews-layouts/list/index.tsx b/packages/dataviews/src/dataviews-layouts/list/index.tsx index 651834599f8ac..dadc53b5d733a 100644 --- a/packages/dataviews/src/dataviews-layouts/list/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/list/index.tsx @@ -101,6 +101,8 @@ function PrimaryActionGridCell< Item >( { render={ <Button label={ label } + disabled={ !! primaryAction.disabled } + accessibleWhenDisabled icon={ primaryAction.icon } isDestructive={ primaryAction.isDestructive } size="small" @@ -124,6 +126,8 @@ function PrimaryActionGridCell< Item >( { render={ <Button label={ label } + disabled={ !! primaryAction.disabled } + accessibleWhenDisabled icon={ primaryAction.icon } isDestructive={ primaryAction.isDestructive } size="small" diff --git a/packages/dataviews/src/test/dataviews.tsx b/packages/dataviews/src/test/dataviews.tsx new file mode 100644 index 0000000000000..fb55bf8064622 --- /dev/null +++ b/packages/dataviews/src/test/dataviews.tsx @@ -0,0 +1,380 @@ +/** + * External dependencies + */ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +/** + * WordPress dependencies + */ +import { useMemo, useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import DataViews from '../components/dataviews'; +import { LAYOUT_GRID, LAYOUT_LIST, LAYOUT_TABLE } from '../constants'; +import type { Action, View } from '../types'; +import { filterSortAndPaginate } from '../filter-and-sort-data-view'; + +type Data = { + id: number; + title: string; + author?: number; + order?: number; +}; + +const DEFAULT_VIEW = { + type: 'table' as const, + search: '', + page: 1, + perPage: 10, + layout: {}, + filters: [], +}; + +const defaultLayouts = { + [ LAYOUT_TABLE ]: {}, + [ LAYOUT_GRID ]: {}, + [ LAYOUT_LIST ]: {}, +}; + +const fields = [ + { + id: 'title', + label: 'Title', + type: 'text' as const, + }, + { + id: 'order', + label: 'Order', + type: 'integer' as const, + }, + { + id: 'author', + label: 'Author', + type: 'integer' as const, + elements: [ + { value: 1, label: 'Jane' }, + { value: 2, label: 'John' }, + ], + }, + { + label: 'Image', + id: 'image', + render: ( { item }: { item: Data } ) => { + return ( + <svg + width="400" + height="180" + data-testid={ 'image-field-' + item.id } + > + <rect + x="50" + y="20" + rx="20" + ry="20" + width="150" + height="150" + style={ { fill: 'red', opacity: 0.5 } } + /> + </svg> + ); + }, + enableSorting: false, + }, +]; + +const actions: Action< Data >[] = [ + { + id: 'delete', + label: 'Delete', + isDestructive: true, + supportsBulk: true, + RenderModal: () => <div>Modal Content</div>, + }, +]; + +const data: Data[] = [ + { + id: 1, + title: 'Hello World', + author: 1, + order: 1, + }, + { + id: 2, + title: 'Homepage', + author: 2, + order: 1, + }, + { + id: 3, + title: 'Posts', + author: 2, + order: 1, + }, +]; + +function DataViewWrapper( { + view: additionalView, + ...props +}: Partial< Parameters< typeof DataViews< Data > >[ 0 ] > ) { + const [ view, setView ] = useState< View >( { + ...DEFAULT_VIEW, + fields: [ 'title', 'order', 'author' ], + ...additionalView, + } ); + + const { data: shownData, paginationInfo } = useMemo( () => { + return filterSortAndPaginate( data, view, props.fields || fields ); + }, [ view, props.fields ] ); + + const dataViewProps = { + getItemId: ( item: Data ) => item.id.toString(), + paginationInfo, + data: shownData, + view, + fields, + onChangeView: setView, + actions: [], + defaultLayouts, + ...props, + }; + + return <DataViews { ...dataViewProps } />; +} + +// jest.useFakeTimers(); + +describe( 'DataViews component', () => { + it( 'should show "No results" if data is empty', () => { + render( <DataViewWrapper data={ [] } /> ); + expect( screen.getByText( 'No results' ) ).toBeInTheDocument(); + } ); + + it( 'should filter results by "search" text, if field has enableGlobalSearch set to true', async () => { + const fieldsWithSearch = [ + { + ...fields[ 0 ], + enableGlobalSearch: true, + }, + fields[ 1 ], + ]; + render( + <DataViewWrapper + fields={ fieldsWithSearch } + view={ { ...DEFAULT_VIEW, search: 'Hello' } } + /> + ); + // Row count includes header. + expect( screen.getAllByRole( 'row' ).length ).toEqual( 2 ); + expect( screen.getByText( 'Hello World' ) ).toBeInTheDocument(); + } ); + + it( 'should display matched element label if field contains elements list', () => { + render( + <DataViewWrapper + data={ [ { id: 1, author: 3, title: 'Hello World' } ] } + fields={ [ + { + id: 'author', + label: 'Author', + type: 'integer' as const, + elements: [ + { value: 1, label: 'Jane' }, + { value: 2, label: 'John' }, + { value: 3, label: 'Tim' }, + ], + }, + ] } + /> + ); + expect( screen.getByText( 'Tim' ) ).toBeInTheDocument(); + } ); + + it( 'should render custom render function if defined in field definition', () => { + render( + <DataViewWrapper + data={ [ { id: 1, title: 'Test Title' } ] } + fields={ [ + { + id: 'title', + label: 'Title', + type: 'text' as const, + render: ( { item }: { item: Data } ) => { + return item.title?.toUpperCase(); + }, + }, + ] } + /> + ); + expect( screen.getByText( 'TEST TITLE' ) ).toBeInTheDocument(); + } ); + + describe( 'in table view', () => { + it( 'should display columns for each field', () => { + render( <DataViewWrapper /> ); + const displayedColumnFields = fields.filter( ( field ) => + [ 'title', 'order', 'author' ].includes( field.id ) + ); + for ( const field of displayedColumnFields ) { + expect( + screen.getByRole( 'button', { name: field.label } ) + ).toBeInTheDocument(); + } + } ); + + it( 'should display the passed in data', () => { + render( <DataViewWrapper /> ); + for ( const item of data ) { + expect( + screen.getAllByText( item.title )[ 0 ] + ).toBeInTheDocument(); + } + } ); + + it( 'should display title column if defined using titleField', () => { + render( + <DataViewWrapper + view={ { + ...DEFAULT_VIEW, + fields: [ 'order', 'author' ], + titleField: 'title', + } } + /> + ); + for ( const item of data ) { + expect( + screen.getAllByText( item.title )[ 0 ] + ).toBeInTheDocument(); + } + } ); + + it( 'should render actions column if actions are supported and passed in', () => { + render( <DataViewWrapper actions={ actions } /> ); + expect( screen.getByText( 'Actions' ) ).toBeInTheDocument(); + } ); + + it( 'should trigger the onClickItem callback if isItemClickable returns true and title field is clicked', async () => { + const onClickItemCallback = jest.fn(); + + render( + <DataViewWrapper + view={ { + ...DEFAULT_VIEW, + fields: [ 'author' ], + titleField: 'title', + } } + actions={ actions } + isItemClickable={ () => true } + onClickItem={ onClickItemCallback } + /> + ); + const titleField = screen.getByText( data[ 0 ].title ); + const user = userEvent.setup(); + await user.click( titleField ); + expect( onClickItemCallback ).toHaveBeenCalledWith( data[ 0 ] ); + } ); + } ); + + describe( 'in grid view', () => { + it( 'should display the passed in data', () => { + render( + <DataViewWrapper + view={ { + type: 'grid', + } } + /> + ); + for ( const item of data ) { + expect( + screen.getAllByText( item.title )[ 0 ] + ).toBeInTheDocument(); + } + } ); + + it( 'should render mediaField if defined', () => { + render( + <DataViewWrapper + view={ { + type: 'grid', + mediaField: 'image', + } } + /> + ); + for ( const item of data ) { + expect( + screen.getByTestId( 'image-field-' + item.id ) + ).toBeInTheDocument(); + } + } ); + + it( 'should render actions dropdown if actions are supported and passed in for each grid item', () => { + render( + <DataViewWrapper + view={ { + type: 'grid', + } } + actions={ actions } + /> + ); + expect( + screen.getAllByRole( 'button', { name: 'Actions' } ).length + ).toEqual( 3 ); + } ); + + it( 'should trigger the onClickItem callback if isItemClickable returns true and a media field is clicked', async () => { + const mediaClickItemCallback = jest.fn(); + + render( + <DataViewWrapper + view={ { + type: 'grid', + mediaField: 'image', + } } + actions={ actions } + isItemClickable={ () => true } + onClickItem={ mediaClickItemCallback } + /> + ); + const imageField = screen.getByTestId( + 'image-field-' + data[ 0 ].id + ); + const user = userEvent.setup(); + await user.click( imageField ); + expect( mediaClickItemCallback ).toHaveBeenCalledWith( data[ 0 ] ); + } ); + } ); + + describe( 'in list view', () => { + it( 'should display the passed in data', () => { + render( + <DataViewWrapper + view={ { + type: 'list', + } } + /> + ); + for ( const item of data ) { + expect( + screen.getAllByText( item.title )[ 0 ] + ).toBeInTheDocument(); + } + } ); + + it( 'should render actions dropdown if actions are supported and passed in for each list item', () => { + render( + <DataViewWrapper + view={ { + type: 'list', + } } + actions={ actions } + /> + ); + expect( + screen.getAllByRole( 'button', { name: 'Actions' } ).length + ).toEqual( 3 ); + } ); + } ); +} ); diff --git a/packages/dataviews/tsconfig.json b/packages/dataviews/tsconfig.json index 3f0865cc39854..a7c8759d257cb 100644 --- a/packages/dataviews/tsconfig.json +++ b/packages/dataviews/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env", "gutenberg-test-env", @@ -22,7 +20,6 @@ { "path": "../private-apis" }, { "path": "../warning" } ], - "include": [ "src" ], "exclude": [ "src/**/*.android.js", "src/**/*.ios.js", diff --git a/packages/date/CHANGELOG.md b/packages/date/CHANGELOG.md index 684c189d6b73a..fc40475514a57 100644 --- a/packages/date/CHANGELOG.md +++ b/packages/date/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/date/package.json b/packages/date/package.json index 4466f1120bc65..053a0d8518f42 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/date", - "version": "5.14.0", + "version": "5.15.1", "description": "Date module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,7 +29,7 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/deprecated": "*", + "@wordpress/deprecated": "file:../deprecated", "moment": "^2.29.4", "moment-timezone": "^0.5.40" }, diff --git a/packages/date/tsconfig.json b/packages/date/tsconfig.json index 0c9e6d5ed02b0..605262dd7cc95 100644 --- a/packages/date/tsconfig.json +++ b/packages/date/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../deprecated" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../deprecated" } ] } diff --git a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md index ace84ad1fedb0..ece0cffaae3c9 100644 --- a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md +++ b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.15.0 (2025-01-02) + ## 6.14.0 (2024-12-11) ## 6.13.0 (2024-11-27) diff --git a/packages/dependency-extraction-webpack-plugin/package.json b/packages/dependency-extraction-webpack-plugin/package.json index e167373554ff6..79d310edac530 100644 --- a/packages/dependency-extraction-webpack-plugin/package.json +++ b/packages/dependency-extraction-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dependency-extraction-webpack-plugin", - "version": "6.14.0", + "version": "6.15.0", "description": "Extract WordPress script dependencies from webpack bundles.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/deprecated/CHANGELOG.md b/packages/deprecated/CHANGELOG.md index cfbeeca4eddfb..067c06ab633fa 100644 --- a/packages/deprecated/CHANGELOG.md +++ b/packages/deprecated/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index 535432cc64ac0..b474fd3fa8177 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/deprecated", - "version": "4.14.0", + "version": "4.15.1", "description": "Deprecation utility for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,7 +30,7 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/hooks": "*" + "@wordpress/hooks": "file:../hooks" }, "publishConfig": { "access": "public" diff --git a/packages/deprecated/tsconfig.json b/packages/deprecated/tsconfig.json index f90e327f124d7..b2186db14f4cc 100644 --- a/packages/deprecated/tsconfig.json +++ b/packages/deprecated/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../hooks" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../hooks" } ] } diff --git a/packages/docgen/CHANGELOG.md b/packages/docgen/CHANGELOG.md index 911bce7b68509..50b5b3e4f1c73 100644 --- a/packages/docgen/CHANGELOG.md +++ b/packages/docgen/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.15.0 (2025-01-02) + ## 2.14.0 (2024-12-11) ## 2.13.0 (2024-11-27) diff --git a/packages/docgen/package.json b/packages/docgen/package.json index d42b430b8d31e..1ba89d62af5b2 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/docgen", - "version": "2.14.0", + "version": "2.15.0", "description": "Autogenerate public API documentation from exports and JSDoc comments.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/docgen/tsconfig.json b/packages/docgen/tsconfig.json index df0072645c53b..eebc743289aec 100644 --- a/packages/docgen/tsconfig.json +++ b/packages/docgen/tsconfig.json @@ -2,8 +2,8 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "lib", - "declarationDir": "build-types" + "rootDir": "lib" }, - "include": [ "lib/get-leading-comments.js" ] + "files": [ "lib/get-leading-comments.js" ], + "include": [] } diff --git a/packages/dom-ready/CHANGELOG.md b/packages/dom-ready/CHANGELOG.md index 582bb51f7c0a7..dffd021d7c2b9 100644 --- a/packages/dom-ready/CHANGELOG.md +++ b/packages/dom-ready/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index c4f1f7ee3dabb..b3f9b1e8c18fa 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom-ready", - "version": "4.14.0", + "version": "4.15.0", "description": "Execute callback after the DOM is loaded.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom-ready/tsconfig.json b/packages/dom-ready/tsconfig.json index 6e33d8ff82d47..7ff060ab6ce10 100644 --- a/packages/dom-ready/tsconfig.json +++ b/packages/dom-ready/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/dom/CHANGELOG.md b/packages/dom/CHANGELOG.md index 9b90dc200d2cf..c5d636422908c 100644 --- a/packages/dom/CHANGELOG.md +++ b/packages/dom/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/dom/package.json b/packages/dom/package.json index 0b4e1f26d0acb..98994ac199d1b 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom", - "version": "4.14.0", + "version": "4.15.1", "description": "DOM utilities module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,7 +31,7 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/deprecated": "*" + "@wordpress/deprecated": "file:../deprecated" }, "publishConfig": { "access": "public" diff --git a/packages/dom/tsconfig.json b/packages/dom/tsconfig.json index 7cdff6c141151..e44d6b98c5085 100644 --- a/packages/dom/tsconfig.json +++ b/packages/dom/tsconfig.json @@ -2,10 +2,7 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] }, - "include": [ "src/**/*" ], "references": [ { "path": "../deprecated" } ] } diff --git a/packages/e2e-test-utils-playwright/CHANGELOG.md b/packages/e2e-test-utils-playwright/CHANGELOG.md index 0b834cbfeb403..d0a123bb0440f 100644 --- a/packages/e2e-test-utils-playwright/CHANGELOG.md +++ b/packages/e2e-test-utils-playwright/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.15.0 (2025-01-02) + ## 1.14.0 (2024-12-11) ## 1.13.0 (2024-11-27) diff --git a/packages/e2e-test-utils-playwright/package.json b/packages/e2e-test-utils-playwright/package.json index e46ea5b833a84..469c0ea0c390d 100644 --- a/packages/e2e-test-utils-playwright/package.json +++ b/packages/e2e-test-utils-playwright/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils-playwright", - "version": "1.14.0", + "version": "1.15.0", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-test-utils-playwright/tsconfig.json b/packages/e2e-test-utils-playwright/tsconfig.json index 5e52bb94f706d..947a4a0f82fc7 100644 --- a/packages/e2e-test-utils-playwright/tsconfig.json +++ b/packages/e2e-test-utils-playwright/tsconfig.json @@ -7,16 +7,13 @@ "module": "Node16", "moduleResolution": "node16", "types": [ "node" ], - "rootDir": "src", "noEmit": false, "outDir": "build", "sourceMap": true, "declaration": true, "declarationMap": true, - "declarationDir": "build-types", "emitDeclarationOnly": false, "allowJs": true, "checkJs": false - }, - "include": [ "src/**/*" ] + } } diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md index c7f28da583333..16b664a9b796b 100644 --- a/packages/e2e-test-utils/CHANGELOG.md +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 11.15.0 (2025-01-02) + ## 11.14.0 (2024-12-11) ## 11.13.0 (2024-11-27) diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index 41f73b235d213..68edf45f0173a 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils", - "version": "11.14.0", + "version": "11.15.1", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,9 +31,9 @@ "module": "build-module/index.js", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/keycodes": "*", - "@wordpress/url": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/url": "file:../url", "change-case": "^4.1.2", "form-data": "^4.0.0", "node-fetch": "2.7.0" diff --git a/packages/e2e-tests/CHANGELOG.md b/packages/e2e-tests/CHANGELOG.md index d4a17ef2a10f7..6ac9745b54e34 100644 --- a/packages/e2e-tests/CHANGELOG.md +++ b/packages/e2e-tests/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 8.15.0 (2025-01-02) + ## 8.14.0 (2024-12-11) ## 8.13.0 (2024-11-27) diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 759591e566e78..3733cebc42abb 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "8.14.0", + "version": "8.15.1", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -24,13 +24,13 @@ "npm": ">=8.19.2" }, "dependencies": { - "@wordpress/e2e-test-utils": "*", - "@wordpress/interactivity": "*", - "@wordpress/interactivity-router": "*", - "@wordpress/jest-console": "*", - "@wordpress/jest-puppeteer-axe": "*", - "@wordpress/scripts": "*", - "@wordpress/url": "*", + "@wordpress/e2e-test-utils": "file:../e2e-test-utils", + "@wordpress/interactivity": "file:../interactivity", + "@wordpress/interactivity-router": "file:../interactivity-router", + "@wordpress/jest-console": "file:../jest-console", + "@wordpress/jest-puppeteer-axe": "file:../jest-puppeteer-axe", + "@wordpress/scripts": "file:../scripts", + "@wordpress/url": "file:../url", "chalk": "^4.0.0", "expect-puppeteer": "^4.4.0", "filenamify": "^4.2.0", diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/assets/10x10_e2e_test_image_blue.png b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/assets/10x10_e2e_test_image_blue.png new file mode 100644 index 0000000000000..c4f8e7c5146d3 Binary files /dev/null and b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/assets/10x10_e2e_test_image_blue.png differ diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/block.json b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/block.json new file mode 100644 index 0000000000000..644ea70f74dca --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/block.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "test/router-styles-blue", + "title": "E2E Interactivity tests - router styles - Blue", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewStyle": "file:./style.css", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/render.php b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/render.php new file mode 100644 index 0000000000000..3f5da308db092 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/render.php @@ -0,0 +1,35 @@ +<?php +/** + * HTML for testing the iAPI's style assets management. + * + * @package gutenberg-test-interactive-blocks + * + * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable + */ + +add_action( + 'wp_enqueue_scripts', + function () { + wp_enqueue_style( + 'blue-from-link', + plugin_dir_url( __FILE__ ) . 'style-from-link.css', + array() + ); + + $custom_css = ' + .blue-from-inline { + color: rgb(0, 0, 255); + } + '; + + wp_register_style( 'test-router-styles', false ); + wp_enqueue_style( 'test-router-styles' ); + wp_add_inline_style( 'test-router-styles', $custom_css ); + } +); + +$wrapper_attributes = get_block_wrapper_attributes( + array( 'data-testid' => 'blue-block' ) +); +?> +<p <?php echo $wrapper_attributes; ?>>Blue</p> diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/style-from-link.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/style-from-link.css new file mode 100644 index 0000000000000..f55f12f4d594c --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/style-from-link.css @@ -0,0 +1,7 @@ +.blue-from-link { + color: rgb(0, 0, 255); +} + +.background-from-link { + background-image: url('./assets/10x10_e2e_test_image_blue.png'); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/style.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/style.css new file mode 100644 index 0000000000000..84d891e90242a --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-blue/style.css @@ -0,0 +1,4 @@ +.wp-block-test-router-styles-blue, +.blue { + color: rgb(0, 0, 255); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/assets/10x10_e2e_test_image_green.png b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/assets/10x10_e2e_test_image_green.png new file mode 100644 index 0000000000000..34ec87925d8c5 Binary files /dev/null and b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/assets/10x10_e2e_test_image_green.png differ diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/block.json b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/block.json new file mode 100644 index 0000000000000..e2edda625571b --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/block.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "test/router-styles-green", + "title": "E2E Interactivity tests - router styles - Green", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewStyle": "file:./style.css", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/render.php b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/render.php new file mode 100644 index 0000000000000..4418a2d3ab0f3 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/render.php @@ -0,0 +1,35 @@ +<?php +/** + * HTML for testing the iAPI's style assets management. + * + * @package gutenberg-test-interactive-blocks + * + * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable + */ + +add_action( + 'wp_enqueue_scripts', + function () { + wp_enqueue_style( + 'green-from-link', + plugin_dir_url( __FILE__ ) . 'style-from-link.css', + array() + ); + + $custom_css = ' + .green-from-inline { + color: rgb(0, 255, 0); + } + '; + + wp_register_style( 'test-router-styles', false ); + wp_enqueue_style( 'test-router-styles' ); + wp_add_inline_style( 'test-router-styles', $custom_css ); + } +); + +$wrapper_attributes = get_block_wrapper_attributes( + array( 'data-testid' => 'green-block' ) +); +?> +<p <?php echo $wrapper_attributes; ?>>Green</p> diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/style-from-link.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/style-from-link.css new file mode 100644 index 0000000000000..b3d7d7b111e52 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/style-from-link.css @@ -0,0 +1,7 @@ +.green-from-link { + color: rgb(0, 255, 0); +} + +.background-from-link { + background-image: url('./assets/10x10_e2e_test_image_green.png'); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/style.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/style.css new file mode 100644 index 0000000000000..0c457588f625c --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-green/style.css @@ -0,0 +1,4 @@ +.wp-block-test-router-styles-green, +.green { + color: rgb(0, 255, 0); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/assets/10x10_e2e_test_image_red.png b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/assets/10x10_e2e_test_image_red.png new file mode 100644 index 0000000000000..3264bf6427c27 Binary files /dev/null and b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/assets/10x10_e2e_test_image_red.png differ diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/block.json b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/block.json new file mode 100644 index 0000000000000..582d7019062c6 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/block.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "test/router-styles-red", + "title": "E2E Interactivity tests - router styles - Red", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewStyle": "file:./style.css", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/render.php b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/render.php new file mode 100644 index 0000000000000..e8474cf69b825 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/render.php @@ -0,0 +1,35 @@ +<?php +/** + * HTML for testing the iAPI's style assets management. + * + * @package gutenberg-test-interactive-blocks + * + * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable + */ + +add_action( + 'wp_enqueue_scripts', + function () { + wp_enqueue_style( + 'red-from-link', + plugin_dir_url( __FILE__ ) . 'style-from-link.css', + array() + ); + + $custom_css = ' + .red-from-inline { + color: rgb(255, 0, 0); + } + '; + + wp_register_style( 'test-router-styles', false ); + wp_enqueue_style( 'test-router-styles' ); + wp_add_inline_style( 'test-router-styles', $custom_css ); + } +); + +$wrapper_attributes = get_block_wrapper_attributes( + array( 'data-testid' => 'red-block' ) +); +?> +<p <?php echo $wrapper_attributes; ?>>Red</p> diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/style-from-link.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/style-from-link.css new file mode 100644 index 0000000000000..0f7d622807989 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/style-from-link.css @@ -0,0 +1,7 @@ +.red-from-link { + color: rgb(255, 0, 0); +} + +.background-from-link { + background-image: url('./assets/10x10_e2e_test_image_red.png'); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/style.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/style.css new file mode 100644 index 0000000000000..eac7e3af16e0b --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-red/style.css @@ -0,0 +1,4 @@ +.wp-block-test-router-styles-red, +.red { + color: rgb(255, 0, 0); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/block.json b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/block.json new file mode 100644 index 0000000000000..a1a95b4c81e3b --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/block.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "test/router-styles-wrapper", + "title": "E2E Interactivity tests - router styles - Wrapper", + "category": "text", + "icon": "heart", + "description": "", + "supports": { + "interactivity": true + }, + "textdomain": "e2e-interactivity", + "viewScriptModule": "file:./view.js", + "viewStyle": "file:./style.css", + "render": "file:./render.php" +} diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/render.php b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/render.php new file mode 100644 index 0000000000000..6373e8e9bc235 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/render.php @@ -0,0 +1,70 @@ +<?php +/** + * HTML for testing the iAPI's style assets management. + * + * @package gutenberg-test-interactive-blocks + * + * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable + */ + +$wrapper_attributes = get_block_wrapper_attributes(); +?> +<div <?php echo $wrapper_attributes; ?>> + <!-- These get colored when the corresponding block is present. --> + <fieldset> + <legend>Styles from block styles</legend> + <p data-testid="red" class="red">Red</p> + <p data-testid="green" class="green">Green</p> + <p data-testid="blue" class="blue">Blue</p> + <p data-testid="all" class="red green blue">All</p> + </fieldset> + + <!-- These get colored when the corresponding block enqueues a referenced stylesheet. --> + <fieldset> + <legend>Styles from referenced style sheets</legend> + <p data-testid="red-from-link" class="red-from-link">Red from link</p> + <p data-testid="green-from-link" class="green-from-link">Green from link</p> + <p data-testid="blue-from-link" class="blue-from-link">Blue from link</p> + <p data-testid="all-from-link" class="red-from-link green-from-link blue-from-link">All from link</p> + <div data-testid="background-from-link"class="background-from-link" style="width: 10px; height: 10px"></div> + </fieldset> + + <!-- These get colored when the corresponding block adds inline style. --> + <fieldset> + <legend>Styles from inline styles</legend> + <p data-testid="red-from-inline" class="red-from-inline">Red</p> + <p data-testid="green-from-inline" class="green-from-inline">Green</p> + <p data-testid="blue-from-inline" class="blue-from-inline">Blue</p> + <p data-testid="all-from-inline" class="red-from-inline green-from-inline blue-from-inline">All</p> + </fieldset> + + <!-- Links to pages with different blocks combination. --> + <nav data-wp-interactive="test/router-styles"> + <?php foreach ( $attributes['links'] as $label => $link ) : ?> + <a + data-testid="link <?php echo $label; ?>" + data-wp-on--click="actions.navigate" + href="<?php echo $link; ?>" + > + <?php echo $label; ?> + </a> + <?php endforeach; ?> + </nav> + + <!-- HTML updated on navigation. --> + <div + data-wp-interactive="test/router-styles" + data-wp-router-region="router-styles" + > + <?php echo $content; ?> + </div> + + <!-- Text to check whether a navigation was client-side. --> + <div + data-testid="client-side navigation" + data-wp-interactive="test/router-styles" + data-wp-bind--hidden="!state.clientSideNavigation" + > + Client-side navigation + </div> +</div> diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/style.css b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/style.css new file mode 100644 index 0000000000000..12773560c4180 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/style.css @@ -0,0 +1,3 @@ +.wp-block-test-router-styles-wrapper { + color: rgb(160, 12, 60); +} \ No newline at end of file diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/view.asset.php b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/view.asset.php new file mode 100644 index 0000000000000..bdaec8d1b67a9 --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/view.asset.php @@ -0,0 +1,9 @@ +<?php return array( + 'dependencies' => array( + '@wordpress/interactivity', + array( + 'id' => '@wordpress/interactivity-router', + 'import' => 'dynamic', + ), + ), +); diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/view.js b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/view.js new file mode 100644 index 0000000000000..5b3b42f2b413e --- /dev/null +++ b/packages/e2e-tests/plugins/interactive-blocks/router-styles-wrapper/view.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { store } from '@wordpress/interactivity'; + +const { state } = store( 'test/router-styles', { + state: { + clientSideNavigation: false, + }, + actions: { + *navigate( e ) { + e.preventDefault(); + const { actions } = yield import( + '@wordpress/interactivity-router' + ); + yield actions.navigate( e.target.href ); + state.clientSideNavigation = true; + }, + }, +} ); diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 0a6099be916b3..716a9abae651c 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 8.15.0 (2025-01-02) + ## 8.14.0 (2024-12-11) ## 8.13.0 (2024-11-27) diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 6984e70088e86..875a39c53f662 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "8.14.0", + "version": "8.15.1", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,35 +29,35 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-commands": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*", - "@wordpress/viewport": "*", - "@wordpress/warning": "*", - "@wordpress/widgets": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-commands": "file:../core-commands", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/editor": "file:../editor", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/notices": "file:../notices", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", + "@wordpress/viewport": "file:../viewport", + "@wordpress/warning": "file:../warning", + "@wordpress/widgets": "file:../widgets", "clsx": "^2.1.1", "memize": "^2.1.0" }, diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index b0a2b3f7d76b8..acc71afe1db0a 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -318,7 +318,9 @@ function MetaBoxesMain() { // the event to end the drag is captured by the target (resize handle) // whether or not it’s under the pointer. onPointerDown: ( { pointerId, target } ) => { - target.setPointerCapture( pointerId ); + if ( separatorRef.current.parentElement.contains( target ) ) { + target.setPointerCapture( pointerId ); + } }, onResizeStart: ( event, direction, elementRef ) => { if ( isAutoHeight ) { @@ -405,6 +407,9 @@ function Layout( { const isRenderingPostOnly = getRenderingMode() === 'post-only'; const isNotDesignPostType = ! DESIGN_POST_TYPES.includes( currentPostType ); + const isDirectlyEditingPattern = + currentPostType === 'wp_block' && + ! onNavigateToPreviousEntityRecord; return { mode: getEditorMode(), @@ -415,7 +420,9 @@ function Layout( { !! select( blockEditorStore ).getBlockSelectionStart(), showIconLabels: get( 'core', 'showIconLabels' ), isDistractionFree: get( 'core', 'distractionFree' ), - showMetaBoxes: isNotDesignPostType && ! isZoomOut(), + showMetaBoxes: + ( isNotDesignPostType && ! isZoomOut() ) || + isDirectlyEditingPattern, isWelcomeGuideVisible: isFeatureActive( 'welcomeGuide' ), templateId: supportsTemplateMode && @@ -433,6 +440,7 @@ function Layout( { currentPostId, isEditingTemplate, settings.supportsTemplateMode, + onNavigateToPreviousEntityRecord, ] ); useMetaBoxInitialization( hasActiveMetaboxes ); diff --git a/packages/edit-site/CHANGELOG.md b/packages/edit-site/CHANGELOG.md index 1016783cc8b61..d0f7b6b60e458 100644 --- a/packages/edit-site/CHANGELOG.md +++ b/packages/edit-site/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.15.0 (2025-01-02) + ## 6.14.0 (2024-12-11) ## 6.13.0 (2024-11-27) diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 51045be503de3..5a3f04f1d0138 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-site", - "version": "6.14.0", + "version": "6.15.1", "description": "Edit Site Page module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,45 +30,46 @@ "dependencies": { "@babel/runtime": "7.25.7", "@react-spring/web": "^9.4.5", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-commands": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dataviews": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/editor": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/fields": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/router": "*", - "@wordpress/style-engine": "*", - "@wordpress/url": "*", - "@wordpress/viewport": "*", - "@wordpress/widgets": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-commands": "file:../core-commands", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dataviews": "file:../dataviews", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/editor": "file:../editor", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/fields": "file:../fields", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/router": "file:../router", + "@wordpress/style-engine": "file:../style-engine", + "@wordpress/url": "file:../url", + "@wordpress/viewport": "file:../viewport", + "@wordpress/widgets": "file:../widgets", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "clsx": "^2.1.1", "colord": "^2.9.2", diff --git a/packages/edit-site/src/components/add-new-pattern/index.js b/packages/edit-site/src/components/add-new-pattern/index.js index 63452691c1c37..85a8c70f9c335 100644 --- a/packages/edit-site/src/components/add-new-pattern/index.js +++ b/packages/edit-site/src/components/add-new-pattern/index.js @@ -25,7 +25,7 @@ import { TEMPLATE_PART_POST_TYPE, } from '../../utils/constants'; -const { useHistory } = unlock( routerPrivateApis ); +const { useHistory, useLocation } = unlock( routerPrivateApis ); const { CreatePatternModal, useAddPatternCategory } = unlock( editPatternsPrivateApis ); @@ -33,6 +33,7 @@ const { CreateTemplatePartModal } = unlock( editorPrivateApis ); export default function AddNewPattern() { const history = useHistory(); + const location = useLocation(); const [ showPatternModal, setShowPatternModal ] = useState( false ); const [ showTemplatePartModal, setShowTemplatePartModal ] = useState( false ); @@ -159,13 +160,12 @@ export default function AddNewPattern() { return; } try { - const { - params: { postType, categoryId }, - } = history.getLocationWithParams(); let currentCategoryId; // When we're not handling template parts, we should // add or create the proper pattern category. - if ( postType !== TEMPLATE_PART_POST_TYPE ) { + if ( + location.query.postType !== TEMPLATE_PART_POST_TYPE + ) { /* * categoryMap.values() returns an iterator. * Iterator.prototype.find() is not yet widely supported. @@ -173,7 +173,10 @@ export default function AddNewPattern() { */ const currentCategory = Array.from( categoryMap.values() - ).find( ( term ) => term.name === categoryId ); + ).find( + ( term ) => + term.name === location.query.categoryId + ); if ( currentCategory ) { currentCategoryId = currentCategory.id || @@ -194,7 +197,7 @@ export default function AddNewPattern() { // category. if ( ! currentCategoryId && - categoryId !== 'my-patterns' + location.query.categoryId !== 'my-patterns' ) { history.navigate( `/pattern?categoryId=${ PATTERN_DEFAULT_CATEGORY }` diff --git a/packages/edit-site/src/components/canvas-loader/style.scss b/packages/edit-site/src/components/canvas-loader/style.scss index 3d74d408aeced..33ff6dc38c3f5 100644 --- a/packages/edit-site/src/components/canvas-loader/style.scss +++ b/packages/edit-site/src/components/canvas-loader/style.scss @@ -9,9 +9,10 @@ align-items: center; justify-content: center; - animation: 0.5s ease 0.2s edit-site-canvas-loader__fade-in-animation; - animation-fill-mode: forwards; - @include reduce-motion("animation"); + @media not (prefers-reduced-motion) { + animation: 0.5s ease 0.2s edit-site-canvas-loader__fade-in-animation; + animation-fill-mode: forwards; + } & > div { width: 160px; diff --git a/packages/edit-site/src/components/editor-canvas-container/style.scss b/packages/edit-site/src/components/editor-canvas-container/style.scss index 07d666fb293c5..c544f88f6bcd5 100644 --- a/packages/edit-site/src/components/editor-canvas-container/style.scss +++ b/packages/edit-site/src/components/editor-canvas-container/style.scss @@ -26,7 +26,9 @@ position: absolute; right: 0; top: 0; - transition: all 0.3s; // Match .block-editor-iframe__body transition. + @media not (prefers-reduced-motion) { + transition: all 0.3s; // Match .block-editor-iframe__body transition. + } } .edit-site-editor-canvas-container__close-button { diff --git a/packages/edit-site/src/components/editor/style.scss b/packages/edit-site/src/components/editor/style.scss index a6cc508496694..625b2633ab724 100644 --- a/packages/edit-site/src/components/editor/style.scss +++ b/packages/edit-site/src/components/editor/style.scss @@ -1,7 +1,9 @@ .edit-site-editor__editor-interface { opacity: 1; - transition: opacity 0.1s ease-out; - @include reduce-motion( "transition" ); + + @media not (prefers-reduced-motion) { + transition: opacity 0.1s ease-out; + } &.is-loading { opacity: 0; diff --git a/packages/edit-site/src/components/global-styles/confirm-reset-shadow-dialog.js b/packages/edit-site/src/components/global-styles/confirm-reset-shadow-dialog.js new file mode 100644 index 0000000000000..b8f5b77010ff6 --- /dev/null +++ b/packages/edit-site/src/components/global-styles/confirm-reset-shadow-dialog.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { __experimentalConfirmDialog as ConfirmDialog } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +function ConfirmResetShadowDialog( { + text, + confirmButtonText, + isOpen, + toggleOpen, + onConfirm, +} ) { + const handleConfirm = async () => { + toggleOpen(); + onConfirm(); + }; + + const handleCancel = () => { + toggleOpen(); + }; + + return ( + <ConfirmDialog + isOpen={ isOpen } + cancelButtonText={ __( 'Cancel' ) } + confirmButtonText={ confirmButtonText } + onCancel={ handleCancel } + onConfirm={ handleConfirm } + size="medium" + > + { text } + </ConfirmDialog> + ); +} + +export default ConfirmResetShadowDialog; diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/index.js b/packages/edit-site/src/components/global-styles/font-library-modal/index.js index 27093e0ef1cbb..5661a002f71ec 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/index.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/index.js @@ -28,7 +28,7 @@ const DEFAULT_TAB = { const UPLOAD_TAB = { id: 'upload-fonts', - title: __( 'Upload' ), + title: _x( 'Upload', 'noun' ), }; const tabsFromCollections = ( collections ) => diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/style.scss b/packages/edit-site/src/components/global-styles/font-library-modal/style.scss index 7568fea2b6f80..11a1c6d668937 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/style.scss +++ b/packages/edit-site/src/components/global-styles/font-library-modal/style.scss @@ -129,8 +129,10 @@ $footer-height: 70px; .font-library-modal__font-variant_demo-text { white-space: nowrap; flex-shrink: 0; - transition: opacity 0.3s ease-in-out; - @include reduce-motion( "transition" ); + + @media not (prefers-reduced-motion) { + transition: opacity 0.3s ease-in-out; + } } } diff --git a/packages/edit-site/src/components/global-styles/screen-block.js b/packages/edit-site/src/components/global-styles/screen-block.js index 347d3cd1bc0a7..64f49574b6b03 100644 --- a/packages/edit-site/src/components/global-styles/screen-block.js +++ b/packages/edit-site/src/components/global-styles/screen-block.js @@ -113,8 +113,9 @@ function ScreenBlock( { name, variation } ) { if ( settingsForBlockElement?.spacing?.blockGap && blockType?.supports?.spacing?.blockGap && - ( blockType?.supports?.spacing?.skipSerialization === true || - blockType?.supports?.spacing?.skipSerialization?.some?.( + ( blockType?.supports?.spacing?.__experimentalSkipSerialization === + true || + blockType?.supports?.spacing?.__experimentalSkipSerialization?.some?.( ( spacingType ) => spacingType === 'blockGap' ) ) ) { diff --git a/packages/edit-site/src/components/global-styles/shadows-edit-panel.js b/packages/edit-site/src/components/global-styles/shadows-edit-panel.js index 9fd7959a6c09c..93c6fe5751327 100644 --- a/packages/edit-site/src/components/global-styles/shadows-edit-panel.js +++ b/packages/edit-site/src/components/global-styles/shadows-edit-panel.js @@ -392,7 +392,12 @@ function ShadowItem( { shadow, onChange, canRemove, onRemove } ) { 'aria-expanded': isOpen, }; const removeButtonProps = { - onClick: onRemove, + onClick: () => { + if ( isOpen ) { + onToggle(); + } + onRemove(); + }, className: clsx( 'edit-site-global-styles__shadow-editor__remove-button', { 'is-open': isOpen } diff --git a/packages/edit-site/src/components/global-styles/shadows-panel.js b/packages/edit-site/src/components/global-styles/shadows-panel.js index 5df8208ebdb09..8e93ab2b15fb0 100644 --- a/packages/edit-site/src/components/global-styles/shadows-panel.js +++ b/packages/edit-site/src/components/global-styles/shadows-panel.js @@ -8,10 +8,17 @@ import { Button, Flex, FlexItem, + privateApis as componentsPrivateApis, } from '@wordpress/components'; import { __, sprintf, isRTL } from '@wordpress/i18n'; import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; -import { plus, Icon, chevronLeft, chevronRight } from '@wordpress/icons'; +import { + plus, + Icon, + chevronLeft, + chevronRight, + moreVertical, +} from '@wordpress/icons'; /** * Internal dependencies @@ -21,8 +28,11 @@ import Subtitle from './subtitle'; import { NavigationButtonAsItem } from './navigation-button'; import ScreenHeader from './header'; import { getNewIndexFromPresets } from './utils'; +import { useState } from '@wordpress/element'; +import ConfirmResetShadowDialog from './confirm-reset-shadow-dialog'; const { useGlobalSetting } = unlock( blockEditorPrivateApis ); +const { Menu } = unlock( componentsPrivateApis ); export const defaultShadow = '6px 6px 9px rgba(0, 0, 0, 0.2)'; @@ -40,8 +50,27 @@ export default function ShadowsPanel() { setCustomShadows( [ ...( customShadows || [] ), shadow ] ); }; + const handleResetShadows = () => { + setCustomShadows( [] ); + }; + + const [ isResetDialogOpen, setIsResetDialogOpen ] = useState( false ); + + const toggleResetDialog = () => setIsResetDialogOpen( ! isResetDialogOpen ); + return ( <> + { isResetDialogOpen && ( + <ConfirmResetShadowDialog + text={ __( + 'Are you sure you want to remove all custom shadows?' + ) } + confirmButtonText={ __( 'Remove' ) } + isOpen={ isResetDialogOpen } + toggleOpen={ toggleResetDialog } + onConfirm={ handleResetShadows } + /> + ) } <ScreenHeader title={ __( 'Shadows' ) } description={ __( @@ -73,6 +102,7 @@ export default function ShadowsPanel() { category="custom" canCreate onCreate={ onCreateShadow } + onReset={ toggleResetDialog } /> </VStack> </div> @@ -80,7 +110,14 @@ export default function ShadowsPanel() { ); } -function ShadowList( { label, shadows, category, canCreate, onCreate } ) { +function ShadowList( { + label, + shadows, + category, + canCreate, + onCreate, + onReset, +} ) { const handleAddShadow = () => { const newIndex = getNewIndexFromPresets( shadows, 'shadow-' ); onCreate( { @@ -115,6 +152,26 @@ function ShadowList( { label, shadows, category, canCreate, onCreate } ) { /> </FlexItem> ) } + { !! shadows?.length && category === 'custom' && ( + <Menu> + <Menu.TriggerButton + render={ + <Button + size="small" + icon={ moreVertical } + label={ __( 'Shadow options' ) } + /> + } + /> + <Menu.Popover> + <Menu.Item onClick={ onReset }> + <Menu.ItemLabel> + { __( 'Remove all custom shadows' ) } + </Menu.ItemLabel> + </Menu.Item> + </Menu.Popover> + </Menu> + ) } </HStack> { shadows.length > 0 && ( <ItemGroup isBordered isSeparated> @@ -138,9 +195,7 @@ function ShadowItem( { shadow, category } ) { > <HStack> <FlexItem>{ shadow.name }</FlexItem> - <FlexItem display="flex"> - <Icon icon={ isRTL() ? chevronLeft : chevronRight } /> - </FlexItem> + <Icon icon={ isRTL() ? chevronLeft : chevronRight } /> </HStack> </NavigationButtonAsItem> ); diff --git a/packages/edit-site/src/components/global-styles/style.scss b/packages/edit-site/src/components/global-styles/style.scss index 68cc40c4b6206..99b1c8c92bbd0 100644 --- a/packages/edit-site/src/components/global-styles/style.scss +++ b/packages/edit-site/src/components/global-styles/style.scss @@ -169,10 +169,18 @@ top: $grid-unit; opacity: 0; + &.edit-site-global-styles__shadow-editor__remove-button { + border: none; + } + .edit-site-global-styles__shadow-editor__dropdown-toggle:hover + &, &:focus, &:hover { - border: none; + opacity: 1; + } + + @media (hover: none) { + // Show reset button on devices that do not support hover. opacity: 1; } } diff --git a/packages/edit-site/src/components/global-styles/variations/style.scss b/packages/edit-site/src/components/global-styles/variations/style.scss index 5f57c72f180b1..b092e09e48750 100644 --- a/packages/edit-site/src/components/global-styles/variations/style.scss +++ b/packages/edit-site/src/components/global-styles/variations/style.scss @@ -9,9 +9,10 @@ outline-offset: -$border-width; overflow: hidden; position: relative; - // Add the same transition that block style variations and other buttons have. - transition: outline 0.1s linear; - @include reduce-motion("transition"); + @media not (prefers-reduced-motion) { + // Add the same transition that block style variations and other buttons have. + transition: outline 0.1s linear; + } &.is-pill { height: $button-size-compact; diff --git a/packages/edit-site/src/components/layout/style.scss b/packages/edit-site/src/components/layout/style.scss index 2c7e6ce1b10c8..8d44015d52967 100644 --- a/packages/edit-site/src/components/layout/style.scss +++ b/packages/edit-site/src/components/layout/style.scss @@ -115,10 +115,13 @@ .edit-site-resizable-frame__inner-content { box-shadow: $elevation-x-small; - transition: border-radius, box-shadow 0.4s; // This ensure the radius work properly. overflow: hidden; + @media (prefers-reduced-motion: no-preference) { + transition: border-radius, box-shadow 0.4s; + } + .edit-site-layout:not(.is-full-canvas) & { border-radius: $radius-large; } @@ -195,8 +198,6 @@ html.canvas-mode-edit-transition::view-transition-group(toggle) { } &::before { - transition: box-shadow 0.1s ease; - @include reduce-motion("transition"); content: ""; display: block; position: absolute; @@ -206,6 +207,10 @@ html.canvas-mode-edit-transition::view-transition-group(toggle) { left: 9px; border-radius: $radius-medium; box-shadow: none; + + @media not (prefers-reduced-motion) { + transition: box-shadow 0.1s ease; + } } .edit-site-layout__view-mode-toggle-icon { diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss index d5520e5d97cdf..2cacc8fab607c 100644 --- a/packages/edit-site/src/components/page-patterns/style.scss +++ b/packages/edit-site/src/components/page-patterns/style.scss @@ -26,10 +26,12 @@ top: 0; z-index: 2; flex-shrink: 0; - transition: padding ease-out 0.1s; - @include reduce-motion("transition"); min-height: $grid-unit-50; + @media not (prefers-reduced-motion) { + transition: padding ease-out 0.1s; + } + .edit-site-patterns__title { min-height: $grid-unit-50; diff --git a/packages/edit-site/src/components/page-templates/style.scss b/packages/edit-site/src/components/page-templates/style.scss index 29df1f5bd0803..bb9069e2c5038 100644 --- a/packages/edit-site/src/components/page-templates/style.scss +++ b/packages/edit-site/src/components/page-templates/style.scss @@ -67,9 +67,11 @@ height: $grid-unit-20; object-fit: cover; opacity: 0; - transition: opacity 0.1s linear; - @include reduce-motion("transition"); border-radius: 100%; + + @media not (prefers-reduced-motion) { + transition: opacity 0.1s linear; + } } &.is-loaded { diff --git a/packages/edit-site/src/components/page/style.scss b/packages/edit-site/src/components/page/style.scss index 7759081e36f59..23e79420a7fbb 100644 --- a/packages/edit-site/src/components/page/style.scss +++ b/packages/edit-site/src/components/page/style.scss @@ -4,8 +4,10 @@ height: calc(100% - #{$header-height}); /* stylelint-disable-next-line property-no-unknown -- '@container' not globally permitted */ container: edit-site-page / inline-size; - transition: width ease-out 0.2s; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: width ease-out 0.2s; + } @include break-medium() { height: 100%; @@ -19,8 +21,10 @@ position: sticky; top: 0; z-index: z-index(".edit-site-page-header"); - transition: padding ease-out 0.1s; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: padding ease-out 0.1s; + } .components-heading { color: $gray-900; diff --git a/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js b/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js index 467648e814276..463ce0003fba2 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js +++ b/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js @@ -27,7 +27,7 @@ import DataViewItem from './dataview-item'; import AddNewItem from './add-new-view'; import { unlock } from '../../lock-unlock'; -const { useHistory } = unlock( routerPrivateApis ); +const { useHistory, useLocation } = unlock( routerPrivateApis ); const EMPTY_ARRAY = []; @@ -85,6 +85,7 @@ function RenameItemModalContent( { dataviewId, currentTitle, setIsRenaming } ) { function CustomDataViewItem( { dataviewId, isActive } ) { const history = useHistory(); + const location = useLocation(); const { dataview } = useSelect( ( select ) => { const { getEditedEntityRecord } = select( coreStore ); @@ -145,10 +146,10 @@ function CustomDataViewItem( { dataviewId, isActive } ) { } ); if ( isActive ) { - const { - params: { postType }, - } = history.getLocationWithParams(); - history.replace( { postType } ); + history.replace( { + postType: + location.query.postType, + } ); } onClose(); } } diff --git a/packages/edit-site/src/components/sidebar-navigation-item/style.scss b/packages/edit-site/src/components/sidebar-navigation-item/style.scss index 230967c4c7e0e..57b7e84bd57a8 100644 --- a/packages/edit-site/src/components/sidebar-navigation-item/style.scss +++ b/packages/edit-site/src/components/sidebar-navigation-item/style.scss @@ -18,6 +18,7 @@ &[aria-current="true"] { background: $gray-800; color: $white; + font-weight: $font-weight-medium; } // Make sure the focus style is drawn on top of the current item background. diff --git a/packages/edit-site/src/components/site-hub/style.scss b/packages/edit-site/src/components/site-hub/style.scss index 39f44d2bef7bb..4099f7064ff05 100644 --- a/packages/edit-site/src/components/site-hub/style.scss +++ b/packages/edit-site/src/components/site-hub/style.scss @@ -65,8 +65,10 @@ opacity: 0; position: absolute; right: 0; - transition: opacity 0.1s linear; - @include reduce-motion("transition"); + + @media not (prefers-reduced-motion) { + transition: opacity 0.1s linear; + } } &:hover::after, diff --git a/packages/edit-site/src/components/style-book/categories.ts b/packages/edit-site/src/components/style-book/categories.ts index 2c1b627c6d0c6..b36c211eaa546 100644 --- a/packages/edit-site/src/components/style-book/categories.ts +++ b/packages/edit-site/src/components/style-book/categories.ts @@ -1,6 +1,8 @@ /** * WordPress dependencies */ +// @wordpress/blocks imports are not typed. +// @ts-expect-error import { getCategories } from '@wordpress/blocks'; /** @@ -29,15 +31,19 @@ export function getExamplesByCategory( if ( ! categoryDefinition?.slug || ! examples?.length ) { return; } - - if ( categoryDefinition?.subcategories?.length ) { - return categoryDefinition.subcategories.reduce( + const categories: CategoryExamples[] = + categoryDefinition?.subcategories ?? []; + if ( categories.length ) { + return categories.reduce( ( acc, subcategoryDefinition ) => { const subcategoryExamples = getExamplesByCategory( subcategoryDefinition, examples ); if ( subcategoryExamples ) { + if ( ! acc.subcategories ) { + acc.subcategories = []; + } acc.subcategories = [ ...acc.subcategories, subcategoryExamples, @@ -48,7 +54,6 @@ export function getExamplesByCategory( { title: categoryDefinition.title, slug: categoryDefinition.slug, - subcategories: [], } ); } @@ -84,8 +89,9 @@ export function getTopLevelStyleBookCategories(): StyleBookCategory[] { ...STYLE_BOOK_THEME_SUBCATEGORIES, ...STYLE_BOOK_CATEGORIES, ].map( ( { slug } ) => slug ); - const extraCategories = getCategories().filter( + const extraCategories: StyleBookCategory[] = getCategories(); + const extraCategoriesFiltered = extraCategories.filter( ( { slug } ) => ! reservedCategories.includes( slug ) ); - return [ ...STYLE_BOOK_CATEGORIES, ...extraCategories ]; + return [ ...STYLE_BOOK_CATEGORIES, ...extraCategoriesFiltered ]; } diff --git a/packages/edit-site/src/components/style-book/color-examples.tsx b/packages/edit-site/src/components/style-book/color-examples.tsx index bdc7bc7936bc1..032a3d92faa2b 100644 --- a/packages/edit-site/src/components/style-book/color-examples.tsx +++ b/packages/edit-site/src/components/style-book/color-examples.tsx @@ -11,26 +11,21 @@ import { View } from '@wordpress/primitives'; import { getColorClassName, __experimentalGetGradientClass, + // @wordpress/block-editor imports are not typed. + // @ts-expect-error } from '@wordpress/block-editor'; /** * Internal dependencies */ -import type { Color, Gradient } from './types'; - -type Props = { - colors: Color[] | Gradient[]; - type: 'colors' | 'gradients'; - templateColumns?: string | number; - itemHeight?: string; -}; +import type { Color, Gradient, ColorExampleProps } from './types'; const ColorExamples = ( { colors, type, templateColumns = '1fr 1fr', itemHeight = '52px', -}: Props ): JSX.Element | null => { +}: ColorExampleProps ): JSX.Element | null => { if ( ! colors ) { return null; } diff --git a/packages/edit-site/src/components/style-book/constants.ts b/packages/edit-site/src/components/style-book/constants.ts index ea99279fd9e65..dcd41287fa239 100644 --- a/packages/edit-site/src/components/style-book/constants.ts +++ b/packages/edit-site/src/components/style-book/constants.ts @@ -148,6 +148,55 @@ export const STYLE_BOOK_CATEGORIES: StyleBookCategory[] = [ }, ]; +// Style book preview subcategories for all blocks section. +export const STYLE_BOOK_ALL_BLOCKS_SUBCATEGORIES: StyleBookCategory[] = [ + ...STYLE_BOOK_THEME_SUBCATEGORIES, + { + slug: 'media', + title: __( 'Media' ), + blocks: [ 'core/post-featured-image' ], + }, + { + slug: 'widgets', + title: __( 'Widgets' ), + blocks: [], + }, + { + slug: 'embed', + title: __( 'Embeds' ), + include: [], + }, +]; + +// Style book preview categories are organised slightly differently to the editor ones. +export const STYLE_BOOK_PREVIEW_CATEGORIES: StyleBookCategory[] = [ + { + slug: 'overview', + title: __( 'Overview' ), + blocks: [], + }, + { + slug: 'text', + title: __( 'Text' ), + blocks: [ + 'core/post-content', + 'core/home-link', + 'core/navigation-link', + ], + }, + { + slug: 'colors', + title: __( 'Colors' ), + blocks: [], + }, + { + slug: 'blocks', + title: __( 'All Blocks' ), + blocks: [], + subcategories: STYLE_BOOK_ALL_BLOCKS_SUBCATEGORIES, + }, +]; + // Forming a "block formatting context" to prevent margin collapsing. // @see https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context const ROOT_CONTAINER = ` diff --git a/packages/edit-site/src/components/style-book/duotone-examples.tsx b/packages/edit-site/src/components/style-book/duotone-examples.tsx index 7ee90e61f1c6a..babba4328bcc2 100644 --- a/packages/edit-site/src/components/style-book/duotone-examples.tsx +++ b/packages/edit-site/src/components/style-book/duotone-examples.tsx @@ -9,7 +9,11 @@ import { View } from '@wordpress/primitives'; */ import type { Duotone } from './types'; -const DuotoneExamples = ( { duotones } ): JSX.Element | null => { +const DuotoneExamples = ( { + duotones, +}: { + duotones: Duotone[]; +} ): JSX.Element | null => { if ( ! duotones ) { return null; } diff --git a/packages/edit-site/src/components/style-book/examples.tsx b/packages/edit-site/src/components/style-book/examples.tsx index 81ae2d8089fa5..046f08524851e 100644 --- a/packages/edit-site/src/components/style-book/examples.tsx +++ b/packages/edit-site/src/components/style-book/examples.tsx @@ -7,16 +7,18 @@ import { getBlockTypes, getBlockFromExample, createBlock, + // @wordpress/blocks imports are not typed. + // @ts-expect-error } from '@wordpress/blocks'; /** * Internal dependencies */ import type { - Block, BlockExample, ColorOrigin, MultiOriginPalettes, + BlockType, } from './types'; import ColorExamples from './color-examples'; import DuotoneExamples from './duotone-examples'; @@ -37,11 +39,14 @@ function getColorExamples( colors: MultiOriginPalettes ): BlockExample[] { const examples: BlockExample[] = []; STYLE_BOOK_COLOR_GROUPS.forEach( ( group ) => { - const palette = colors[ group.type ].find( - ( origin: ColorOrigin ) => origin.slug === group.origin - ); + const palette = colors[ group.type as keyof MultiOriginPalettes ]; + const paletteFiltered = Array.isArray( palette ) + ? palette.find( + ( origin: ColorOrigin ) => origin.slug === group.origin + ) + : undefined; - if ( palette?.[ group.type ] ) { + if ( paletteFiltered?.[ group.type ] ) { const example: BlockExample = { name: group.slug, title: group.title, @@ -49,13 +54,15 @@ function getColorExamples( colors: MultiOriginPalettes ): BlockExample[] { }; if ( group.type === 'duotones' ) { example.content = ( - <DuotoneExamples duotones={ palette[ group.type ] } /> + <DuotoneExamples + duotones={ paletteFiltered[ group.type ] } + /> ); examples.push( example ); } else { example.content = ( <ColorExamples - colors={ palette[ group.type ] } + colors={ paletteFiltered[ group.type ] } type={ group.type } /> ); @@ -79,9 +86,11 @@ function getOverviewBlockExamples( const examples: BlockExample[] = []; // Get theme palette from colors if they exist. - const themePalette = colors?.colors.find( - ( origin: ColorOrigin ) => origin.slug === 'theme' - ); + const themePalette = Array.isArray( colors?.colors ) + ? colors.colors.find( + ( origin: ColorOrigin ) => origin.slug === 'theme' + ) + : undefined; if ( themePalette ) { const themeColorexample: BlockExample = { @@ -91,7 +100,7 @@ function getOverviewBlockExamples( content: ( <ColorExamples colors={ themePalette.colors } - type={ colors } + type="colors" templateColumns="repeat(auto-fill, minmax( 200px, 1fr ))" itemHeight="32px" /> @@ -102,7 +111,7 @@ function getOverviewBlockExamples( } // Get examples for typography blocks. - const typographyBlockExamples: Block[] = []; + const typographyBlockExamples: BlockType[] = []; if ( getBlockType( 'core/heading' ) ) { const headingBlock = createBlock( 'core/heading', { @@ -202,7 +211,7 @@ function getOverviewBlockExamples( */ export function getExamples( colors: MultiOriginPalettes ): BlockExample[] { const nonHeadingBlockExamples = getBlockTypes() - .filter( ( blockType ) => { + .filter( ( blockType: BlockType ) => { const { name, example, supports } = blockType; return ( name !== 'core/heading' && @@ -210,7 +219,7 @@ export function getExamples( colors: MultiOriginPalettes ): BlockExample[] { supports?.inserter !== false ); } ) - .map( ( blockType ) => ( { + .map( ( blockType: BlockType ) => ( { name: blockType.name, title: blockType.title, category: blockType.category, diff --git a/packages/edit-site/src/components/style-book/index.js b/packages/edit-site/src/components/style-book/index.js index ecbd729ee8a0a..723953777e2b2 100644 --- a/packages/edit-site/src/components/style-book/index.js +++ b/packages/edit-site/src/components/style-book/index.js @@ -35,6 +35,8 @@ import { useEffect, } from '@wordpress/element'; import { ENTER, SPACE } from '@wordpress/keycodes'; +import { uploadMedia } from '@wordpress/media-utils'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -49,7 +51,11 @@ import { import { getExamples } from './examples'; import { store as siteEditorStore } from '../../store'; import { useSection } from '../sidebar-global-styles-wrapper'; -import { STYLE_BOOK_COLOR_GROUPS } from '../style-book/constants'; +import { GlobalStylesRenderer } from '../global-styles-renderer'; +import { + STYLE_BOOK_COLOR_GROUPS, + STYLE_BOOK_PREVIEW_CATEGORIES, +} from '../style-book/constants'; const { ExperimentalBlockEditorProvider, @@ -88,35 +94,24 @@ const scrollToSection = ( anchorId, iframe ) => { }; /** - * Parses a Block Editor navigation path to extract the block name and - * build a style book navigation path. The object can be extended to include a category, - * representing a style book tab/section. + * Parses a Block Editor navigation path to build a style book navigation path. + * The object can be extended to include a category, representing a style book tab/section. * * @param {string} path An internal Block Editor navigation path. * @return {null|{block: string}} An object containing the example to navigate to. */ const getStyleBookNavigationFromPath = ( path ) => { if ( path && typeof path === 'string' ) { - if ( path === '/' ) { + if ( + path === '/' || + path.startsWith( '/typography' ) || + path.startsWith( '/colors' ) || + path.startsWith( '/blocks' ) + ) { return { top: true, }; } - - if ( path.startsWith( '/typography' ) ) { - return { - block: 'typography', - }; - } - let block = path.includes( '/blocks/' ) - ? decodeURIComponent( path.split( '/blocks/' )[ 1 ] ) - : null; - // Default to theme-colors if the path ends with /colors. - block = path.endsWith( '/colors' ) ? 'theme-colors' : block; - - return { - block, - }; } return null; }; @@ -310,29 +305,43 @@ function StyleBook( { ) ) } </Tabs.TabList> </div> - { tabs.map( ( tab ) => ( - <Tabs.TabPanel - key={ tab.slug } - tabId={ tab.slug } - focusable={ false } - className="edit-site-style-book__tabpanel" - > - <StyleBookBody - category={ tab.slug } - examples={ examples } - isSelected={ isSelected } - onSelect={ onSelect } - settings={ settings } - sizes={ sizes } - title={ tab.title } - goTo={ goTo } - /> - </Tabs.TabPanel> - ) ) } + { tabs.map( ( tab ) => { + const categoryDefinition = tab.slug + ? getTopLevelStyleBookCategories().find( + ( _category ) => + _category.slug === tab.slug + ) + : null; + const filteredExamples = categoryDefinition + ? getExamplesByCategory( + categoryDefinition, + examples + ) + : { examples }; + return ( + <Tabs.TabPanel + key={ tab.slug } + tabId={ tab.slug } + focusable={ false } + className="edit-site-style-book__tabpanel" + > + <StyleBookBody + category={ tab.slug } + examples={ filteredExamples } + isSelected={ isSelected } + onSelect={ onSelect } + settings={ settings } + sizes={ sizes } + title={ tab.title } + goTo={ goTo } + /> + </Tabs.TabPanel> + ); + } ) } </Tabs> ) : ( <StyleBookBody - examples={ examplesForSinglePageUse } + examples={ { examples: examplesForSinglePageUse } } isSelected={ isSelected } onClick={ onClick } onSelect={ onSelect } @@ -360,10 +369,22 @@ export const StyleBookPreview = ( { userConfig = {}, isStatic = false } ) => { [] ); + const canUserUploadMedia = useSelect( + ( select ) => + select( coreStore ).canUser( 'create', { + kind: 'root', + name: 'media', + } ), + [] + ); + // Update block editor settings because useMultipleOriginColorsAndGradients fetch colours from there. useEffect( () => { - dispatch( blockEditorStore ).updateSettings( siteEditorSettings ); - }, [ siteEditorSettings ] ); + dispatch( blockEditorStore ).updateSettings( { + ...siteEditorSettings, + mediaUpload: canUserUploadMedia ? uploadMedia : undefined, + } ); + }, [ siteEditorSettings, canUserUploadMedia ] ); const [ section, onChangeSection ] = useSection(); @@ -404,6 +425,44 @@ export const StyleBookPreview = ( { userConfig = {}, isStatic = false } ) => { const examples = getExamples( colors ); const examplesForSinglePageUse = getExamplesForSinglePageUse( examples ); + let previewCategory = null; + if ( section.includes( '/colors' ) ) { + previewCategory = 'colors'; + } else if ( section.includes( '/typography' ) ) { + previewCategory = 'text'; + } else if ( section.includes( '/blocks' ) ) { + previewCategory = 'blocks'; + const blockName = + decodeURIComponent( section ).split( '/blocks/' )[ 1 ]; + if ( + blockName && + examples.find( ( example ) => example.name === blockName ) + ) { + previewCategory = blockName; + } + } else if ( ! isStatic ) { + previewCategory = 'overview'; + } + const categoryDefinition = STYLE_BOOK_PREVIEW_CATEGORIES.find( + ( category ) => category.slug === previewCategory + ); + + // If there's no category definition there may be a single block. + const filteredExamples = categoryDefinition + ? getExamplesByCategory( categoryDefinition, examples ) + : { + examples: [ + examples.find( + ( example ) => example.name === previewCategory + ), + ], + }; + + // If there's no preview category, show all examples. + const displayedExamples = previewCategory + ? filteredExamples + : { examples: examplesForSinglePageUse }; + const { base: baseConfig } = useContext( GlobalStylesContext ); const goTo = getStyleBookNavigationFromPath( section ); @@ -432,8 +491,9 @@ export const StyleBookPreview = ( { userConfig = {}, isStatic = false } ) => { <div className="edit-site-style-book"> { resizeObserver } <BlockEditorProvider settings={ settings }> + <GlobalStylesRenderer disableRootPadding /> <StyleBookBody - examples={ examplesForSinglePageUse } + examples={ displayedExamples } settings={ settings } goTo={ goTo } sizes={ sizes } @@ -446,7 +506,6 @@ export const StyleBookPreview = ( { userConfig = {}, isStatic = false } ) => { }; export const StyleBookBody = ( { - category, examples, isSelected, onClick, @@ -492,13 +551,6 @@ export const StyleBookBody = ( { if ( hasIframeLoaded && iframeRef?.current ) { if ( goTo?.top ) { scrollToSection( 'top', iframeRef?.current ); - return; - } - if ( goTo?.block ) { - scrollToSection( - `example-${ goTo?.block }`, - iframeRef?.current - ); } } }, [ iframeRef?.current, goTo, scrollToSection, hasIframeLoaded ] ); @@ -525,8 +577,7 @@ export const StyleBookBody = ( { className={ clsx( 'edit-site-style-book__examples', { 'is-wide': sizes.width > 600, } ) } - examples={ examples } - category={ category } + filteredExamples={ examples } label={ title ? sprintf( @@ -538,24 +589,14 @@ export const StyleBookBody = ( { } isSelected={ isSelected } onSelect={ onSelect } - key={ category } + key={ title } /> </Iframe> ); }; const Examples = memo( - ( { className, examples, category, label, isSelected, onSelect } ) => { - const categoryDefinition = category - ? getTopLevelStyleBookCategories().find( - ( _category ) => _category.slug === category - ) - : null; - - const filteredExamples = categoryDefinition - ? getExamplesByCategory( categoryDefinition, examples ) - : { examples }; - + ( { className, filteredExamples, label, isSelected, onSelect } ) => { return ( <Composite orientation="vertical" diff --git a/packages/edit-site/src/components/style-book/types.ts b/packages/edit-site/src/components/style-book/types.ts index 9f65039121856..9a97c3aad7f79 100644 --- a/packages/edit-site/src/components/style-book/types.ts +++ b/packages/edit-site/src/components/style-book/types.ts @@ -32,7 +32,7 @@ export type StyleBookColorGroup = { origin: string; slug: string; title: string; - type: string; + type: 'colors' | 'gradients' | 'duotones'; }; export type Color = { slug: string }; @@ -42,6 +42,13 @@ export type Duotone = { slug: string; }; +export type ColorExampleProps = { + colors: Color[] | Gradient[]; + type: StyleBookColorGroup[ 'type' ]; + templateColumns?: string | number; + itemHeight?: string; +}; + export type ColorOrigin = { name: string; slug: string; @@ -58,3 +65,16 @@ export type MultiOriginPalettes = { duotones: Omit< ColorOrigin, 'colors' | 'gradients' >; gradients: Omit< ColorOrigin, 'colors' | 'duotones' >; }; + +/* + * Typing the items from getBlockTypes from '@wordpress/blocks' + * to appease the TS linter. + */ +export type BlockType = { + name: string; + title: string; + category: string; + example: BlockType; + attributes: Record< string, unknown >; + supports: Record< string, unknown >; +}; diff --git a/packages/edit-site/tsconfig.json b/packages/edit-site/tsconfig.json new file mode 100644 index 0000000000000..d6c82614bf534 --- /dev/null +++ b/packages/edit-site/tsconfig.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig.json", + "extends": "../../tsconfig.base.json", + "references": [ + { "path": "../a11y" }, + { "path": "../api-fetch" }, + { "path": "../autop" }, + { "path": "../blob" }, + { "path": "../block-library" }, + { "path": "../block-editor" }, + { "path": "../components" }, + { "path": "../compose" }, + { "path": "../core-data" }, + { "path": "../data" }, + { "path": "../dataviews" }, + { "path": "../date" }, + { "path": "../deprecated" }, + { "path": "../dom" }, + { "path": "../editor" }, + { "path": "../element" }, + { "path": "../escape-html" }, + { "path": "../fields" }, + { "path": "../hooks" }, + { "path": "../html-entities" }, + { "path": "../i18n" }, + { "path": "../icons" }, + { "path": "../interactivity" }, + { "path": "../interactivity-router" }, + { "path": "../media-utils" }, + { "path": "../notices" }, + { "path": "../keycodes" }, + { "path": "../plugins" }, + { "path": "../primitives" }, + { "path": "../private-apis" }, + { "path": "../rich-text" }, + { "path": "../router" }, + { "path": "../style-engine" }, + { "path": "../url" }, + { "path": "../wordcount" } + ], + // NOTE: This package is being progressively typed. You are encouraged to + // expand this array with files which can be type-checked. At some point in + // the future, this can be simplified to an `includes` of `src/**/*`. + "files": [ + "src/components/style-book/categories.ts", + "src/components/style-book/constants.ts", + "src/components/style-book/types.ts", + "src/components/style-book/color-examples.tsx", + "src/components/style-book/duotone-examples.tsx", + "src/components/style-book/examples.tsx" + ], + "include": [] +} diff --git a/packages/edit-widgets/CHANGELOG.md b/packages/edit-widgets/CHANGELOG.md index cadbb1442cbbb..83cd14b26c592 100644 --- a/packages/edit-widgets/CHANGELOG.md +++ b/packages/edit-widgets/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.15.0 (2025-01-02) + ## 6.14.0 (2024-12-11) ## 6.13.0 (2024-11-27) diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index b343188212239..553f0dcf29785 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-widgets", - "version": "6.14.0", + "version": "6.15.1", "description": "Widgets Page module for WordPress..", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,32 +29,32 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interface": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/media-utils": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/url": "*", - "@wordpress/widgets": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/url": "file:../url", + "@wordpress/widgets": "file:../widgets", "clsx": "^2.1.1" }, "peerDependencies": { diff --git a/packages/edit-widgets/src/components/header/style.scss b/packages/edit-widgets/src/components/header/style.scss index 642a641e6e595..32214cb0f157a 100644 --- a/packages/edit-widgets/src/components/header/style.scss +++ b/packages/edit-widgets/src/components/header/style.scss @@ -147,8 +147,9 @@ } svg { - transition: transform cubic-bezier(0.165, 0.84, 0.44, 1) 0.2s; - @include reduce-motion("transition"); + @media not (prefers-reduced-motion) { + transition: transform cubic-bezier(0.165, 0.84, 0.44, 1) 0.2s; + } } &.is-pressed { diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 570ef8dc81501..57f4d5334113e 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 14.15.0 (2025-01-02) + ## 14.14.0 (2024-12-11) ## 14.13.0 (2024-11-27) diff --git a/packages/editor/package.json b/packages/editor/package.json index b19e2fb3dab71..0988411207a65 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "14.14.0", + "version": "14.15.1", "description": "Enhanced block editor for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -34,41 +34,41 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/commands": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dataviews": "*", - "@wordpress/date": "*", - "@wordpress/deprecated": "*", - "@wordpress/dom": "*", - "@wordpress/element": "*", - "@wordpress/fields": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/interface": "*", - "@wordpress/keyboard-shortcuts": "*", - "@wordpress/keycodes": "*", - "@wordpress/media-utils": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/private-apis": "*", - "@wordpress/reusable-blocks": "*", - "@wordpress/rich-text": "*", - "@wordpress/server-side-render": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", - "@wordpress/wordcount": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/commands": "file:../commands", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dataviews": "file:../dataviews", + "@wordpress/date": "file:../date", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", + "@wordpress/fields": "file:../fields", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/interface": "file:../interface", + "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/reusable-blocks": "file:../reusable-blocks", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/server-side-render": "file:../server-side-render", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", + "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", "client-zip": "^2.4.5", "clsx": "^2.1.1", diff --git a/packages/editor/src/components/document-bar/index.js b/packages/editor/src/components/document-bar/index.js index f5ca65dfe18ed..544b5024d88a8 100644 --- a/packages/editor/src/components/document-bar/index.js +++ b/packages/editor/src/components/document-bar/index.js @@ -22,6 +22,7 @@ import { store as commandsStore } from '@wordpress/commands'; import { useRef, useEffect } from '@wordpress/element'; import { useReducedMotion } from '@wordpress/compose'; import { decodeEntities } from '@wordpress/html-entities'; +import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; /** * Internal dependencies @@ -200,7 +201,7 @@ export default function DocumentBar( props ) { <Text size="body" as="h1"> <span className="editor-document-bar__post-title"> { title - ? decodeEntities( title ) + ? stripHTML( title ) : __( 'No title' ) } </span> { pageTypeBadge && ( diff --git a/packages/editor/src/components/document-tools/index.js b/packages/editor/src/components/document-tools/index.js index a98def685e93a..71a8b1b094a13 100644 --- a/packages/editor/src/components/document-tools/index.js +++ b/packages/editor/src/components/document-tools/index.js @@ -10,7 +10,7 @@ import { useViewportMatch } from '@wordpress/compose'; import { useSelect, useDispatch } from '@wordpress/data'; import { __, _x } from '@wordpress/i18n'; import { NavigableToolbar, ToolSelector } from '@wordpress/block-editor'; -import { Button, ToolbarItem } from '@wordpress/components'; +import { ToolbarButton, ToolbarItem } from '@wordpress/components'; import { listView, plus } from '@wordpress/icons'; import { useCallback } from '@wordpress/element'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; @@ -118,9 +118,8 @@ function DocumentTools( { className, disableBlockTools = false } ) { > <div className="editor-document-tools__left"> { ! isDistractionFree && ( - <ToolbarItem + <ToolbarButton ref={ inserterSidebarToggleRef } - as={ Button } className="editor-document-tools__inserter-toggle" variant="primary" isPressed={ isInserterOpened } @@ -159,8 +158,7 @@ function DocumentTools( { className, disableBlockTools = false } ) { size="compact" /> { ! isDistractionFree && ( - <ToolbarItem - as={ Button } + <ToolbarButton className="editor-document-tools__document-overview-toggle" icon={ listView } disabled={ disableBlockTools } @@ -175,7 +173,6 @@ function DocumentTools( { className, disableBlockTools = false } ) { } aria-expanded={ isListViewOpen } ref={ listViewToggleRef } - size="compact" /> ) } </> diff --git a/packages/editor/src/components/document-tools/style.scss b/packages/editor/src/components/document-tools/style.scss index a1abfd5abd7ae..dfafff2126d66 100644 --- a/packages/editor/src/components/document-tools/style.scss +++ b/packages/editor/src/components/document-tools/style.scss @@ -74,14 +74,8 @@ } .editor-document-tools .editor-document-tools__left > .editor-document-tools__inserter-toggle.has-icon { - min-width: $button-size-compact; - width: $button-size-compact; - height: $button-size-compact; - padding: 0; - .show-icon-labels & { width: auto; - height: $button-size-compact; padding: 0 $grid-unit-10; } } diff --git a/packages/editor/src/components/post-actions/set-as-homepage.js b/packages/editor/src/components/post-actions/set-as-homepage.js index 671906575b412..cb67e251ed58c 100644 --- a/packages/editor/src/components/post-actions/set-as-homepage.js +++ b/packages/editor/src/components/post-actions/set-as-homepage.js @@ -122,8 +122,13 @@ const SetAsHomepageModal = ( { items, closeModal } ) => { export const useSetAsHomepageAction = () => { const { pageOnFront, pageForPosts } = useSelect( ( select ) => { - const { getEntityRecord } = select( coreStore ); - const siteSettings = getEntityRecord( 'root', 'site' ); + const { getEntityRecord, canUser } = select( coreStore ); + const siteSettings = canUser( 'read', { + kind: 'root', + name: 'site', + } ) + ? getEntityRecord( 'root', 'site' ) + : undefined; return { pageOnFront: siteSettings?.page_on_front, pageForPosts: siteSettings?.page_for_posts, diff --git a/packages/editor/src/components/post-actions/set-as-posts-page.js b/packages/editor/src/components/post-actions/set-as-posts-page.js index 67c42a7991fe4..830c2cac734f1 100644 --- a/packages/editor/src/components/post-actions/set-as-posts-page.js +++ b/packages/editor/src/components/post-actions/set-as-posts-page.js @@ -118,8 +118,14 @@ const SetAsPostsPageModal = ( { items, closeModal } ) => { export const useSetAsPostsPageAction = () => { const { pageOnFront, pageForPosts } = useSelect( ( select ) => { - const { getEntityRecord } = select( coreStore ); - const siteSettings = getEntityRecord( 'root', 'site' ); + const { getEntityRecord, canUser } = select( coreStore ); + const siteSettings = canUser( 'read', { + kind: 'root', + name: 'site', + } ) + ? getEntityRecord( 'root', 'site' ) + : undefined; + return { pageOnFront: siteSettings?.page_on_front, pageForPosts: siteSettings?.page_for_posts, diff --git a/packages/editor/src/components/post-card-panel/index.js b/packages/editor/src/components/post-card-panel/index.js index 7849f014ab49c..895545cb007f0 100644 --- a/packages/editor/src/components/post-card-panel/index.js +++ b/packages/editor/src/components/post-card-panel/index.js @@ -6,12 +6,13 @@ import { __experimentalHStack as HStack, __experimentalVStack as VStack, __experimentalText as Text, + privateApis as componentsPrivateApis, } from '@wordpress/components'; import { store as coreStore } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; import { useMemo } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; -import { decodeEntities } from '@wordpress/html-entities'; +import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; /** * Internal dependencies @@ -25,6 +26,7 @@ import { unlock } from '../../lock-unlock'; import PostActions from '../post-actions'; import usePageTypeBadge from '../../utils/pageTypeBadge'; import { getTemplateInfo } from '../../utils/get-template-info'; +const { Badge } = unlock( componentsPrivateApis ); /** * Renders a title of the post type and the available quick actions available within a 3-dot dropdown. @@ -92,7 +94,7 @@ export default function PostCardPanel( { labels?.name ); } else if ( postTitle ) { - title = decodeEntities( postTitle ); + title = stripHTML( postTitle ); } return ( @@ -109,11 +111,11 @@ export default function PostCardPanel( { className="editor-post-card-panel__title" as="h2" > - { title } + <span className="editor-post-card-panel__title-name"> + { title } + </span> { pageTypeBadge && postIds.length === 1 && ( - <span className="editor-post-card-panel__title-badge"> - { pageTypeBadge } - </span> + <Badge>{ pageTypeBadge }</Badge> ) } </Text> <PostActions diff --git a/packages/editor/src/components/post-card-panel/style.scss b/packages/editor/src/components/post-card-panel/style.scss index c3638b313a828..5fa54c67f14e5 100644 --- a/packages/editor/src/components/post-card-panel/style.scss +++ b/packages/editor/src/components/post-card-panel/style.scss @@ -9,7 +9,6 @@ &.editor-post-card-panel__title { @include heading-medium(); margin: 0; - padding: 2px 0; display: flex; align-items: center; flex-wrap: wrap; @@ -34,19 +33,11 @@ margin-bottom: $grid-unit-10; } + .editor-post-card-panel__title-name { + padding: 2px 0; + } + .editor-post-card-panel__description { color: $gray-700; } } - -.editor-post-card-panel__title-badge { - background: $gray-100; - color: $gray-800; - padding: 0 $grid-unit-05; - border-radius: $radius-small; - font-size: 12px; - font-weight: 400; - flex-shrink: 0; - line-height: $grid-unit-05 * 5; - display: inline-block; -} diff --git a/packages/editor/src/components/post-publish-panel/maybe-upload-media.js b/packages/editor/src/components/post-publish-panel/maybe-upload-media.js index 32ea69c425e0b..a92a479415434 100644 --- a/packages/editor/src/components/post-publish-panel/maybe-upload-media.js +++ b/packages/editor/src/components/post-publish-panel/maybe-upload-media.js @@ -9,7 +9,7 @@ import { __unstableAnimatePresence as AnimatePresence, } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { useState } from '@wordpress/element'; import { isBlobURL } from '@wordpress/blob'; @@ -260,7 +260,7 @@ export default function MaybeUploadMediaPanel() { variant="primary" onClick={ uploadImages } > - { __( 'Upload' ) } + { _x( 'Upload', 'verb' ) } </Button> ) } </div> diff --git a/packages/editor/src/components/preferences-modal/index.js b/packages/editor/src/components/preferences-modal/index.js index 72042bca03b70..fba60405e7e4b 100644 --- a/packages/editor/src/components/preferences-modal/index.js +++ b/packages/editor/src/components/preferences-modal/index.js @@ -26,7 +26,6 @@ import PageAttributesCheck from '../page-attributes/check'; import PostTypeSupportCheck from '../post-type-support-check'; import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; -import { useStartPatterns } from '../start-page-options'; const { PreferencesModal, @@ -73,7 +72,6 @@ function PreferencesModalContents( { extraSections = {} } ) { const { setIsListViewOpened, setIsInserterOpened } = useDispatch( editorStore ); const { set: setPreference } = useDispatch( preferencesStore ); - const hasStarterPatterns = !! useStartPatterns().length; const sections = useMemo( () => @@ -114,16 +112,14 @@ function PreferencesModalContents( { extraSections = {} } ) { 'Allow right-click contextual menus' ) } /> - { hasStarterPatterns && ( - <PreferenceToggleControl - scope="core" - featureName="enableChoosePatternModal" - help={ __( - 'Shows starter patterns when creating a new page.' - ) } - label={ __( 'Show starter patterns' ) } - /> - ) } + <PreferenceToggleControl + scope="core" + featureName="enableChoosePatternModal" + help={ __( + 'Shows starter patterns when creating a new page.' + ) } + label={ __( 'Show starter patterns' ) } + /> </PreferencesModalSection> <PreferencesModalSection title={ __( 'Document settings' ) } @@ -341,7 +337,6 @@ function PreferencesModalContents( { extraSections = {} } ) { setIsListViewOpened, setPreference, isLargeViewport, - hasStarterPatterns, ] ); diff --git a/packages/editor/src/components/provider/disable-non-page-content-blocks.js b/packages/editor/src/components/provider/disable-non-page-content-blocks.js index ae4fd1075fc26..ffbf1ac062546 100644 --- a/packages/editor/src/components/provider/disable-non-page-content-blocks.js +++ b/packages/editor/src/components/provider/disable-non-page-content-blocks.js @@ -16,9 +16,13 @@ import usePostContentBlocks from './use-post-content-blocks'; */ export default function DisableNonPageContentBlocks() { const contentOnlyIds = usePostContentBlocks(); - const templateParts = useSelect( ( select ) => { - const { getBlocksByName } = select( blockEditorStore ); - return getBlocksByName( 'core/template-part' ); + const { templateParts, isNavigationMode } = useSelect( ( select ) => { + const { getBlocksByName, isNavigationMode: _isNavigationMode } = + select( blockEditorStore ); + return { + templateParts: getBlocksByName( 'core/template-part' ), + isNavigationMode: _isNavigationMode(), + }; }, [] ); const disabledIds = useSelect( ( select ) => { @@ -32,38 +36,85 @@ export default function DisableNonPageContentBlocks() { const registry = useRegistry(); + // The code here is split into multiple `useEffects` calls. + // This is done to avoid setting/unsetting block editing modes multiple times unnecessarily. + // + // For example, the block editing mode of the root block (clientId: '') only + // needs to be set once, not when `contentOnlyIds` or `disabledIds` change. + // + // It's also unlikely that these different types of blocks are being inserted + // or removed at the same time, so using different effects reflects that. + useEffect( () => { + const { setBlockEditingMode, unsetBlockEditingMode } = + registry.dispatch( blockEditorStore ); + + setBlockEditingMode( '', 'disabled' ); + + return () => { + unsetBlockEditingMode( '' ); + }; + }, [ registry ] ); + useEffect( () => { const { setBlockEditingMode, unsetBlockEditingMode } = registry.dispatch( blockEditorStore ); registry.batch( () => { - setBlockEditingMode( '', 'disabled' ); for ( const clientId of contentOnlyIds ) { setBlockEditingMode( clientId, 'contentOnly' ); } - for ( const clientId of templateParts ) { - setBlockEditingMode( clientId, 'contentOnly' ); - } - for ( const clientId of disabledIds ) { - setBlockEditingMode( clientId, 'disabled' ); - } } ); return () => { registry.batch( () => { - unsetBlockEditingMode( '' ); for ( const clientId of contentOnlyIds ) { unsetBlockEditingMode( clientId ); } + } ); + }; + }, [ contentOnlyIds, registry ] ); + + useEffect( () => { + const { setBlockEditingMode, unsetBlockEditingMode } = + registry.dispatch( blockEditorStore ); + + registry.batch( () => { + if ( ! isNavigationMode ) { for ( const clientId of templateParts ) { - unsetBlockEditingMode( clientId ); + setBlockEditingMode( clientId, 'contentOnly' ); } + } + } ); + + return () => { + registry.batch( () => { + if ( ! isNavigationMode ) { + for ( const clientId of templateParts ) { + unsetBlockEditingMode( clientId ); + } + } + } ); + }; + }, [ templateParts, isNavigationMode, registry ] ); + + useEffect( () => { + const { setBlockEditingMode, unsetBlockEditingMode } = + registry.dispatch( blockEditorStore ); + + registry.batch( () => { + for ( const clientId of disabledIds ) { + setBlockEditingMode( clientId, 'disabled' ); + } + } ); + + return () => { + registry.batch( () => { for ( const clientId of disabledIds ) { unsetBlockEditingMode( clientId ); } } ); }; - }, [ templateParts, contentOnlyIds, disabledIds, registry ] ); + }, [ disabledIds, registry ] ); return null; } diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 4b8a3b6b23c25..1259eae623de9 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -163,6 +163,7 @@ export const ExperimentalEditorProvider = withRegistryProvider( BlockEditorProviderComponent = ExperimentalBlockEditorProvider, __unstableTemplate: template, } ) => { + const hasTemplate = !! template; const { editorSettings, selection, @@ -195,7 +196,9 @@ export const ExperimentalEditorProvider = withRegistryProvider( isReady: __unstableIsEditorReady(), mode: getRenderingMode(), defaultMode: - postTypeObject?.default_rendering_mode ?? 'post-only', + hasTemplate && postTypeObject?.default_rendering_mode + ? postTypeObject?.default_rendering_mode + : 'post-only', selection: getEditorSelection(), postTypeEntities: post.type === 'wp_template' @@ -203,7 +206,7 @@ export const ExperimentalEditorProvider = withRegistryProvider( : null, }; }, - [ post.type ] + [ post.type, hasTemplate ] ); const shouldRenderTemplate = !! template && mode !== 'post-only'; @@ -300,15 +303,11 @@ export const ExperimentalEditorProvider = withRegistryProvider( } ); } - }, [ - createWarningNotice, - initialEdits, - settings, - post, - recovery, - setupEditor, - updatePostLock, - ] ); + + // The dependencies of the hook are omitted deliberately + // We only want to run setupEditor (with initialEdits) only once per post. + // A better solution in the future would be to split this effect into multiple ones. + }, [] ); // Synchronizes the active post with the state useEffect( () => { diff --git a/packages/editor/src/components/start-page-options/index.js b/packages/editor/src/components/start-page-options/index.js index 783a4a224fa37..d7874000ffc42 100644 --- a/packages/editor/src/components/start-page-options/index.js +++ b/packages/editor/src/components/start-page-options/index.js @@ -1,16 +1,8 @@ /** * WordPress dependencies */ -import { Modal } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { useState, useMemo } from '@wordpress/element'; -import { - store as blockEditorStore, - __experimentalBlockPatternsList as BlockPatternsList, -} from '@wordpress/block-editor'; +import { useEffect } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; -import { store as coreStore } from '@wordpress/core-data'; -import { __unstableSerializeAndClean } from '@wordpress/blocks'; import { store as preferencesStore } from '@wordpress/preferences'; import { store as interfaceStore } from '@wordpress/interface'; @@ -18,124 +10,41 @@ import { store as interfaceStore } from '@wordpress/interface'; * Internal dependencies */ import { store as editorStore } from '../../store'; -import { TEMPLATE_POST_TYPE } from '../../store/constants'; - -export function useStartPatterns() { - // A pattern is a start pattern if it includes 'core/post-content' in its blockTypes, - // and it has no postTypes declared and the current post type is page or if - // the current post type is part of the postTypes declared. - const { blockPatternsWithPostContentBlockType, postType } = useSelect( - ( select ) => { - const { getPatternsByBlockTypes, getBlocksByName } = - select( blockEditorStore ); - const { getCurrentPostType, getRenderingMode } = - select( editorStore ); - const rootClientId = - getRenderingMode() === 'post-only' - ? '' - : getBlocksByName( 'core/post-content' )?.[ 0 ]; - return { - blockPatternsWithPostContentBlockType: getPatternsByBlockTypes( - 'core/post-content', - rootClientId - ), - postType: getCurrentPostType(), - }; - }, - [] - ); - - return useMemo( () => { - if ( ! blockPatternsWithPostContentBlockType?.length ) { - return []; - } - - /* - * Filter patterns without postTypes declared if the current postType is page - * or patterns that declare the current postType in its post type array. - */ - return blockPatternsWithPostContentBlockType.filter( ( pattern ) => { - return ( - ( postType === 'page' && ! pattern.postTypes ) || - ( Array.isArray( pattern.postTypes ) && - pattern.postTypes.includes( postType ) ) - ); - } ); - }, [ postType, blockPatternsWithPostContentBlockType ] ); -} - -function PatternSelection( { blockPatterns, onChoosePattern } ) { - const { editEntityRecord } = useDispatch( coreStore ); - const { postType, postId } = useSelect( ( select ) => { - const { getCurrentPostType, getCurrentPostId } = select( editorStore ); - - return { - postType: getCurrentPostType(), - postId: getCurrentPostId(), - }; - }, [] ); - return ( - <BlockPatternsList - blockPatterns={ blockPatterns } - onClickPattern={ ( _pattern, blocks ) => { - editEntityRecord( 'postType', postType, postId, { - blocks, - content: ( { blocks: blocksForSerialization = [] } ) => - __unstableSerializeAndClean( blocksForSerialization ), - } ); - onChoosePattern(); - } } - /> - ); -} - -function StartPageOptionsModal( { onClose } ) { - const startPatterns = useStartPatterns(); - const hasStartPattern = startPatterns.length > 0; - - if ( ! hasStartPattern ) { - return null; - } - - return ( - <Modal - title={ __( 'Choose a pattern' ) } - isFullScreen - onRequestClose={ onClose } - > - <div className="editor-start-page-options__modal-content"> - <PatternSelection - blockPatterns={ startPatterns } - onChoosePattern={ onClose } - /> - </div> - </Modal> - ); -} export default function StartPageOptions() { - const [ isClosed, setIsClosed ] = useState( false ); - const shouldEnableModal = useSelect( ( select ) => { - const { isEditedPostDirty, isEditedPostEmpty, getCurrentPostType } = - select( editorStore ); + const { postId, shouldEnable } = useSelect( ( select ) => { + const { + isEditedPostDirty, + isEditedPostEmpty, + getCurrentPostId, + getCurrentPostType, + } = select( editorStore ); const preferencesModalActive = select( interfaceStore ).isModalActive( 'editor/preferences' ); const choosePatternModalEnabled = select( preferencesStore ).get( 'core', 'enableChoosePatternModal' ); - return ( - choosePatternModalEnabled && - ! preferencesModalActive && - ! isEditedPostDirty() && - isEditedPostEmpty() && - TEMPLATE_POST_TYPE !== getCurrentPostType() - ); + return { + postId: getCurrentPostId(), + shouldEnable: + choosePatternModalEnabled && + ! preferencesModalActive && + ! isEditedPostDirty() && + isEditedPostEmpty() && + 'page' === getCurrentPostType(), + }; }, [] ); + const { setIsInserterOpened } = useDispatch( editorStore ); + + useEffect( () => { + if ( shouldEnable ) { + setIsInserterOpened( { + tab: 'patterns', + category: 'core/starter-content', + } ); + } + }, [ postId, shouldEnable, setIsInserterOpened ] ); - if ( ! shouldEnableModal || isClosed ) { - return null; - } - - return <StartPageOptionsModal onClose={ () => setIsClosed( true ) } />; + return null; } diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 9d0de08718cd2..6a628512f62bf 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -23,7 +23,6 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { TRASH_POST_NOTICE_ID } from './constants'; import { localAutosaveSet } from './local-autosave'; import { getNotificationArgumentsForSaveSuccess, @@ -347,7 +346,6 @@ export const trashPost = const postType = await registry .resolveSelect( coreStore ) .getPostType( postTypeSlug ); - registry.dispatch( noticesStore ).removeNotice( TRASH_POST_NOTICE_ID ); const { rest_base: restBase, rest_namespace: restNamespace = 'wp/v2' } = postType; dispatch( { type: 'REQUEST_POST_DELETE_START' } ); diff --git a/packages/editor/src/store/constants.ts b/packages/editor/src/store/constants.ts index 73d6a104370c3..2cb0903453466 100644 --- a/packages/editor/src/store/constants.ts +++ b/packages/editor/src/store/constants.ts @@ -11,8 +11,6 @@ export const EDIT_MERGE_PROPERTIES = new Set( [ 'meta' ] ); */ export const STORE_NAME = 'core/editor'; -export const SAVE_POST_NOTICE_ID = 'SAVE_POST_NOTICE_ID'; -export const TRASH_POST_NOTICE_ID = 'TRASH_POST_NOTICE_ID'; export const PERMALINK_POSTNAME_REGEX = /%(?:postname|pagename)%/; export const ONE_MINUTE_IN_MS = 60 * 1000; export const AUTOSAVE_PROPERTIES = [ 'title', 'excerpt', 'content' ]; diff --git a/packages/editor/src/store/utils/notice-builder.js b/packages/editor/src/store/utils/notice-builder.js index 58fc9ca0d747e..9e1230b2ea88c 100644 --- a/packages/editor/src/store/utils/notice-builder.js +++ b/packages/editor/src/store/utils/notice-builder.js @@ -3,11 +3,6 @@ */ import { __ } from '@wordpress/i18n'; -/** - * Internal dependencies - */ -import { SAVE_POST_NOTICE_ID, TRASH_POST_NOTICE_ID } from '../constants'; - /** * Builds the arguments for a success notification dispatch. * @@ -68,7 +63,7 @@ export function getNotificationArgumentsForSaveSuccess( data ) { return [ noticeMessage, { - id: SAVE_POST_NOTICE_ID, + id: 'editor-save', type: 'snackbar', actions, }, @@ -113,7 +108,7 @@ export function getNotificationArgumentsForSaveFail( data ) { return [ noticeMessage, { - id: SAVE_POST_NOTICE_ID, + id: 'editor-save', }, ]; } @@ -131,7 +126,7 @@ export function getNotificationArgumentsForTrashFail( data ) { ? data.error.message : __( 'Trashing failed' ), { - id: TRASH_POST_NOTICE_ID, + id: 'editor-trash-fail', }, ]; } diff --git a/packages/editor/src/store/utils/test/notice-builder.js b/packages/editor/src/store/utils/test/notice-builder.js index e66a96259680f..d97ec0f9f9483 100644 --- a/packages/editor/src/store/utils/test/notice-builder.js +++ b/packages/editor/src/store/utils/test/notice-builder.js @@ -6,7 +6,6 @@ import { getNotificationArgumentsForSaveFail, getNotificationArgumentsForTrashFail, } from '../notice-builder'; -import { SAVE_POST_NOTICE_ID, TRASH_POST_NOTICE_ID } from '../../constants'; describe( 'getNotificationArgumentsForSaveSuccess()', () => { const postType = { @@ -27,7 +26,7 @@ describe( 'getNotificationArgumentsForSaveSuccess()', () => { }; const post = { ...previousPost }; const defaultExpectedAction = { - id: SAVE_POST_NOTICE_ID, + id: 'editor-save', actions: [], type: 'snackbar', }; @@ -106,7 +105,7 @@ describe( 'getNotificationArgumentsForSaveFail()', () => { const error = { code: '42', message: 'Something went wrong.' }; const post = { status: 'publish' }; const edits = { status: 'publish' }; - const defaultExpectedAction = { id: SAVE_POST_NOTICE_ID }; + const defaultExpectedAction = { id: 'editor-save' }; [ [ 'when error code is `rest_autosave_no_changes`', @@ -190,7 +189,7 @@ describe( 'getNotificationArgumentsForTrashFail()', () => { ].forEach( ( [ description, error, message ] ) => { // eslint-disable-next-line jest/valid-title it( description, () => { - const expectedValue = [ message, { id: TRASH_POST_NOTICE_ID } ]; + const expectedValue = [ message, { id: 'editor-trash-fail' } ]; expect( getNotificationArgumentsForTrashFail( { error } ) ).toEqual( expectedValue ); diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json index 3c45fbcb10db3..00a8f3860e292 100644 --- a/packages/editor/tsconfig.json +++ b/packages/editor/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "checkJs": false }, "references": [ @@ -34,6 +32,5 @@ { "path": "../url" }, { "path": "../warning" }, { "path": "../wordcount" } - ], - "include": [ "src" ] + ] } diff --git a/packages/element/CHANGELOG.md b/packages/element/CHANGELOG.md index 3bd53cb55978e..a77cc038309d6 100644 --- a/packages/element/CHANGELOG.md +++ b/packages/element/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.15.0 (2025-01-02) + ## 6.14.0 (2024-12-11) ## 6.13.0 (2024-11-27) diff --git a/packages/element/package.json b/packages/element/package.json index cd205f74eccbf..d441dc21fafd1 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/element", - "version": "6.14.0", + "version": "6.15.1", "description": "Element React module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,7 +33,7 @@ "@babel/runtime": "7.25.7", "@types/react": "^18.2.79", "@types/react-dom": "^18.2.25", - "@wordpress/escape-html": "*", + "@wordpress/escape-html": "file:../escape-html", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.3.0", diff --git a/packages/element/tsconfig.json b/packages/element/tsconfig.json index ad6a489d33e9a..a1df062eb218b 100644 --- a/packages/element/tsconfig.json +++ b/packages/element/tsconfig.json @@ -2,12 +2,8 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", - "noImplicitAny": false, "strictNullChecks": false }, - "references": [ { "path": "../escape-html" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../escape-html" } ] } diff --git a/packages/env/CHANGELOG.md b/packages/env/CHANGELOG.md index 252e1e65dae87..f12cde8f06433 100644 --- a/packages/env/CHANGELOG.md +++ b/packages/env/CHANGELOG.md @@ -2,12 +2,21 @@ ## Unreleased +## 10.15.0 (2025-01-02) + +### Enhancements + +- Add support for WordPress multisite installations. Enabled via the new `multisite` environment config ([#67845](https://github.com/WordPress/gutenberg/pull/67845)). + +### Internal + +- Refactored the code to use new API introduced together with `@inquirer/prompts` instead of legacy `inquirer` package ([#67877](https://github.com/WordPress/gutenberg/pull/67877)). + ## 10.14.0 (2024-12-11) ### Enhancements -- Add phpMyAdmin as an optional service. Enabled via the new `phpmyadminPort` environment config, as well as env vars `WP_ENV_PHPMYADMIN_PORT` and `WP_ENV_TESTS_PHPMYADMIN_PORT`. -- Add support for WordPress multisite installations. Enabled via the new `multisite` environment config. +- Add phpMyAdmin as an optional service. Enabled via the new `phpmyadminPort` environment config, as well as env vars `WP_ENV_PHPMYADMIN_PORT` and `WP_ENV_TESTS_PHPMYADMIN_PORT` ([#67588](https://github.com/WordPress/gutenberg/pull/67588)). ### Internal diff --git a/packages/env/lib/commands/destroy.js b/packages/env/lib/commands/destroy.js index 46b923dc3c9ac..016838ea21844 100644 --- a/packages/env/lib/commands/destroy.js +++ b/packages/env/lib/commands/destroy.js @@ -5,7 +5,7 @@ const { v2: dockerCompose } = require( 'docker-compose' ); const fs = require( 'fs' ).promises; const path = require( 'path' ); -const inquirer = require( 'inquirer' ); +const { confirm } = require( '@inquirer/prompts' ); /** * Promisified dependencies @@ -40,14 +40,19 @@ module.exports = async function destroy( { spinner, scripts, debug } ) { 'WARNING! This will remove Docker containers, volumes, networks, and images associated with the WordPress instance.' ); - const { yesDelete } = await inquirer.prompt( [ - { - type: 'confirm', - name: 'yesDelete', + let yesDelete = false; + try { + yesDelete = await confirm( { message: 'Are you sure you want to continue?', default: false, - }, - ] ); + } ); + } catch ( error ) { + if ( error.name === 'ExitPromptError' ) { + console.log( 'Cancelled.' ); + process.exit( 1 ); + } + throw error; + } spinner.start(); diff --git a/packages/env/lib/commands/start.js b/packages/env/lib/commands/start.js index e476fd8c2b67b..db05b82060d2c 100644 --- a/packages/env/lib/commands/start.js +++ b/packages/env/lib/commands/start.js @@ -6,7 +6,7 @@ const { v2: dockerCompose } = require( 'docker-compose' ); const util = require( 'util' ); const path = require( 'path' ); const fs = require( 'fs' ).promises; -const inquirer = require( 'inquirer' ); +const { confirm } = require( '@inquirer/prompts' ); /** * Promisified dependencies @@ -328,15 +328,21 @@ async function checkForLegacyInstall( spinner ) { ' and ' ) }. Installs are now in your home folder.\n` ); - const { yesDelete } = await inquirer.prompt( [ - { - type: 'confirm', - name: 'yesDelete', + let yesDelete = false; + try { + yesDelete = confirm( { message: 'Do you wish to delete these old installs to reclaim disk space?', default: true, - }, - ] ); + } ); + } catch ( error ) { + if ( error.name === 'ExitPromptError' ) { + console.log( 'Cancelled.' ); + process.exit( 1 ); + } + throw error; + } + if ( yesDelete ) { await Promise.all( installs.map( ( install ) => rimraf( install ) ) ); spinner.info( 'Old installs deleted successfully.' ); diff --git a/packages/env/package.json b/packages/env/package.json index d86d518e41e49..40c3caae8370d 100644 --- a/packages/env/package.json +++ b/packages/env/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/env", - "version": "10.14.0", + "version": "10.15.0", "description": "A zero-config, self contained local WordPress environment for development and testing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -36,12 +36,12 @@ "wp-env": "bin/wp-env" }, "dependencies": { + "@inquirer/prompts": "^7.2.0", "chalk": "^4.0.0", "copy-dir": "^1.3.0", "docker-compose": "^0.24.3", "extract-zip": "^1.6.7", "got": "^11.8.5", - "inquirer": "^7.1.0", "js-yaml": "^3.13.1", "ora": "^4.0.2", "rimraf": "^5.0.10", diff --git a/packages/escape-html/CHANGELOG.md b/packages/escape-html/CHANGELOG.md index ddad2e437b0ab..89dafd8379821 100644 --- a/packages/escape-html/CHANGELOG.md +++ b/packages/escape-html/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.15.0 (2025-01-02) + ## 3.14.0 (2024-12-11) ## 3.13.0 (2024-11-27) diff --git a/packages/escape-html/package.json b/packages/escape-html/package.json index ec5b759d46ae1..a6c356fcb7bbc 100644 --- a/packages/escape-html/package.json +++ b/packages/escape-html/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/escape-html", - "version": "3.14.0", + "version": "3.15.0", "description": "Escape HTML utils.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/escape-html/tsconfig.json b/packages/escape-html/tsconfig.json index 6e33d8ff82d47..7ff060ab6ce10 100644 --- a/packages/escape-html/tsconfig.json +++ b/packages/escape-html/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 8dcb02746344f..c6c068960cefc 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 22.1.0 (2025-01-02) + ## 22.0.0 (2024-12-11) ### Breaking Changes diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 30d76ea374ad2..a7a02c7d94377 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/eslint-plugin", - "version": "22.0.0", + "version": "22.1.1", "description": "ESLint plugin for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -34,8 +34,8 @@ "@babel/eslint-parser": "7.25.7", "@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/parser": "^6.4.1", - "@wordpress/babel-preset-default": "*", - "@wordpress/prettier-config": "*", + "@wordpress/babel-preset-default": "file:../babel-preset-default", + "@wordpress/prettier-config": "file:../prettier-config", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", diff --git a/packages/eslint-plugin/tsconfig.json b/packages/eslint-plugin/tsconfig.json index e17815f78a6a1..a769c50a12df4 100644 --- a/packages/eslint-plugin/tsconfig.json +++ b/packages/eslint-plugin/tsconfig.json @@ -3,12 +3,12 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "module": "CommonJS", - "rootDir": "rules", - "declarationDir": "build-types" + "rootDir": "rules" }, "references": [ { "path": "../prettier-config" } ], // NOTE: This package is being progressively typed. You are encouraged to // expand this array with files which can be type-checked. At some point in // the future, this can be simplified to an `includes` of `src/**/*`. - "files": [ "rules/dependency-group.js", "rules/no-unsafe-wp-apis.js" ] + "files": [ "rules/dependency-group.js", "rules/no-unsafe-wp-apis.js" ], + "include": [] } diff --git a/packages/fields/CHANGELOG.md b/packages/fields/CHANGELOG.md index a94e566bca394..0a0cad41c7684 100644 --- a/packages/fields/CHANGELOG.md +++ b/packages/fields/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 0.7.0 (2025-01-02) + ## 0.6.0 (2024-12-11) ## 0.5.0 (2024-11-27) diff --git a/packages/fields/package.json b/packages/fields/package.json index 2c201e6f8d4ce..38a65b1b54fa1 100644 --- a/packages/fields/package.json +++ b/packages/fields/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/fields", - "version": "0.6.0", + "version": "0.7.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", @@ -33,29 +33,29 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/dataviews": "*", - "@wordpress/date": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/media-utils": "*", - "@wordpress/notices": "*", - "@wordpress/patterns": "*", - "@wordpress/primitives": "*", - "@wordpress/private-apis": "*", - "@wordpress/router": "*", - "@wordpress/url": "*", - "@wordpress/warning": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/dataviews": "file:../dataviews", + "@wordpress/date": "file:../date", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "@wordpress/patterns": "file:../patterns", + "@wordpress/primitives": "file:../primitives", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/router": "file:../router", + "@wordpress/url": "file:../url", + "@wordpress/warning": "file:../warning", "change-case": "4.1.2", "client-zip": "^2.4.5", "clsx": "2.1.1", diff --git a/packages/fields/src/actions/permanently-delete-post.tsx b/packages/fields/src/actions/permanently-delete-post.tsx index 688ba5b9918df..136fcdda9a3e6 100644 --- a/packages/fields/src/actions/permanently-delete-post.tsx +++ b/packages/fields/src/actions/permanently-delete-post.tsx @@ -2,10 +2,19 @@ * WordPress dependencies */ import { store as coreStore } from '@wordpress/core-data'; -import { __, sprintf } from '@wordpress/i18n'; +import { __, _n, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import type { Action } from '@wordpress/dataviews'; import { trash } from '@wordpress/icons'; +import { useState } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; +import { + Button, + __experimentalText as Text, + __experimentalHStack as HStack, + __experimentalVStack as VStack, +} from '@wordpress/components'; +import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies @@ -25,93 +34,155 @@ const permanentlyDeletePost: Action< PostWithPermissions > = { const { status, permissions } = item; return status === 'trash' && permissions?.delete; }, - async callback( posts, { registry, onActionPerformed } ) { + hideModalHeader: true, + RenderModal: ( { items, closeModal, onActionPerformed } ) => { + const [ isBusy, setIsBusy ] = useState( false ); const { createSuccessNotice, createErrorNotice } = - registry.dispatch( noticesStore ); - const { deleteEntityRecord } = registry.dispatch( coreStore ); - const promiseResult = await Promise.allSettled( - posts.map( ( post ) => { - return deleteEntityRecord( - 'postType', - post.type, - post.id, - { force: true }, - { throwOnError: true } - ); - } ) + useDispatch( noticesStore ); + const { deleteEntityRecord } = useDispatch( coreStore ); + + return ( + <VStack spacing="5"> + <Text> + { items.length > 1 + ? sprintf( + // translators: %d: number of items to delete. + _n( + 'Are you sure you want to permanently delete %d item?', + 'Are you sure you want to permanently delete %d items?', + items.length + ), + items.length + ) + : sprintf( + // translators: %s: The post's title + __( + 'Are you sure you want to permanently delete "%s"?' + ), + decodeEntities( getItemTitle( items[ 0 ] ) ) + ) } + </Text> + <HStack justify="right"> + <Button + variant="tertiary" + onClick={ closeModal } + disabled={ isBusy } + accessibleWhenDisabled + __next40pxDefaultSize + > + { __( 'Cancel' ) } + </Button> + <Button + variant="primary" + onClick={ async () => { + setIsBusy( true ); + const promiseResult = await Promise.allSettled( + items.map( ( post ) => + deleteEntityRecord( + 'postType', + post.type, + post.id, + { force: true }, + { throwOnError: true } + ) + ) + ); + + // If all the promises were fulfilled with success. + if ( + promiseResult.every( + ( { status } ) => status === 'fulfilled' + ) + ) { + let successMessage; + if ( promiseResult.length === 1 ) { + successMessage = sprintf( + /* translators: The posts's title. */ + __( '"%s" permanently deleted.' ), + getItemTitle( items[ 0 ] ) + ); + } else { + successMessage = __( + 'The items were permanently deleted.' + ); + } + createSuccessNotice( successMessage, { + type: 'snackbar', + id: 'permanently-delete-post-action', + } ); + onActionPerformed?.( items ); + } else { + // If there was at lease one failure. + let errorMessage; + // If we were trying to permanently delete a single post. + if ( promiseResult.length === 1 ) { + const typedError = promiseResult[ 0 ] as { + reason?: CoreDataError; + }; + if ( typedError.reason?.message ) { + errorMessage = + typedError.reason.message; + } else { + errorMessage = __( + 'An error occurred while permanently deleting the item.' + ); + } + // If we were trying to permanently delete multiple posts + } else { + const errorMessages = new Set(); + const failedPromises = promiseResult.filter( + ( { status } ) => status === 'rejected' + ); + for ( const failedPromise of failedPromises ) { + const typedError = failedPromise as { + reason?: CoreDataError; + }; + if ( typedError.reason?.message ) { + errorMessages.add( + typedError.reason.message + ); + } + } + if ( errorMessages.size === 0 ) { + errorMessage = __( + 'An error occurred while permanently deleting the items.' + ); + } else if ( errorMessages.size === 1 ) { + errorMessage = sprintf( + /* translators: %s: an error message */ + __( + 'An error occurred while permanently deleting the items: %s' + ), + [ ...errorMessages ][ 0 ] + ); + } else { + errorMessage = sprintf( + /* translators: %s: a list of comma separated error messages */ + __( + 'Some errors occurred while permanently deleting the items: %s' + ), + [ ...errorMessages ].join( ',' ) + ); + } + } + createErrorNotice( errorMessage, { + type: 'snackbar', + } ); + } + + setIsBusy( false ); + closeModal?.(); + } } + isBusy={ isBusy } + disabled={ isBusy } + accessibleWhenDisabled + __next40pxDefaultSize + > + { __( 'Delete permanently' ) } + </Button> + </HStack> + </VStack> ); - // If all the promises were fulfilled with success. - if ( promiseResult.every( ( { status } ) => status === 'fulfilled' ) ) { - let successMessage; - if ( promiseResult.length === 1 ) { - successMessage = sprintf( - /* translators: The posts's title. */ - __( '"%s" permanently deleted.' ), - getItemTitle( posts[ 0 ] ) - ); - } else { - successMessage = __( 'The items were permanently deleted.' ); - } - createSuccessNotice( successMessage, { - type: 'snackbar', - id: 'permanently-delete-post-action', - } ); - onActionPerformed?.( posts ); - } else { - // If there was at lease one failure. - let errorMessage; - // If we were trying to permanently delete a single post. - if ( promiseResult.length === 1 ) { - const typedError = promiseResult[ 0 ] as { - reason?: CoreDataError; - }; - if ( typedError.reason?.message ) { - errorMessage = typedError.reason.message; - } else { - errorMessage = __( - 'An error occurred while permanently deleting the item.' - ); - } - // If we were trying to permanently delete multiple posts - } else { - const errorMessages = new Set(); - const failedPromises = promiseResult.filter( - ( { status } ) => status === 'rejected' - ); - for ( const failedPromise of failedPromises ) { - const typedError = failedPromise as { - reason?: CoreDataError; - }; - if ( typedError.reason?.message ) { - errorMessages.add( typedError.reason.message ); - } - } - if ( errorMessages.size === 0 ) { - errorMessage = __( - 'An error occurred while permanently deleting the items.' - ); - } else if ( errorMessages.size === 1 ) { - errorMessage = sprintf( - /* translators: %s: an error message */ - __( - 'An error occurred while permanently deleting the items: %s' - ), - [ ...errorMessages ][ 0 ] - ); - } else { - errorMessage = sprintf( - /* translators: %s: a list of comma separated error messages */ - __( - 'Some errors occurred while permanently deleting the items: %s' - ), - [ ...errorMessages ].join( ',' ) - ); - } - } - createErrorNotice( errorMessage, { - type: 'snackbar', - } ); - } }, }; diff --git a/packages/fields/src/fields/page-title/style.scss b/packages/fields/src/fields/page-title/style.scss deleted file mode 100644 index def56aa466a8a..0000000000000 --- a/packages/fields/src/fields/page-title/style.scss +++ /dev/null @@ -1,10 +0,0 @@ -.fields-field__page-title__badge { - background: $gray-100; - color: $gray-800; - padding: 0 $grid-unit-05; - border-radius: $radius-small; - font-size: 12px; - font-weight: 400; - flex-shrink: 0; - line-height: $grid-unit-05 * 5; -} diff --git a/packages/fields/src/fields/page-title/view.tsx b/packages/fields/src/fields/page-title/view.tsx index 0be4c16d5d29a..eb5184362ec82 100644 --- a/packages/fields/src/fields/page-title/view.tsx +++ b/packages/fields/src/fields/page-title/view.tsx @@ -5,12 +5,15 @@ import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import type { Settings } from '@wordpress/core-data'; +import { privateApis as componentsPrivateApis } from '@wordpress/components'; /** * Internal dependencies */ import type { CommonPost } from '../../types'; import { BaseTitleView } from '../title/view'; +import { unlock } from '../../lock-unlock'; +const { Badge } = unlock( componentsPrivateApis ); export default function PageTitleView( { item }: { item: CommonPost } ) { const { frontPageId, postsPageId } = useSelect( ( select ) => { @@ -27,11 +30,11 @@ export default function PageTitleView( { item }: { item: CommonPost } ) { return ( <BaseTitleView item={ item } className="fields-field__page-title"> { [ frontPageId, postsPageId ].includes( item.id as number ) && ( - <span className="fields-field__page-title__badge"> + <Badge> { item.id === frontPageId ? __( 'Homepage' ) : __( 'Posts Page' ) } - </span> + </Badge> ) } </BaseTitleView> ); diff --git a/packages/fields/src/style.scss b/packages/fields/src/style.scss index d9a571270fbb6..96b1f816de5b6 100644 --- a/packages/fields/src/style.scss +++ b/packages/fields/src/style.scss @@ -3,5 +3,4 @@ @import "./fields/featured-image/style.scss"; @import "./fields/template/style.scss"; @import "./fields/title/style.scss"; -@import "./fields/page-title/style.scss"; @import "./fields/pattern-title/style.scss"; diff --git a/packages/fields/tsconfig.json b/packages/fields/tsconfig.json index 46ac86d48e11e..552aa73b8e5cc 100644 --- a/packages/fields/tsconfig.json +++ b/packages/fields/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "checkJs": false }, "references": [ @@ -29,6 +27,5 @@ { "path": "../url" }, { "path": "../block-editor" }, { "path": "../warning" } - ], - "include": [ "src" ] + ] } diff --git a/packages/format-library/CHANGELOG.md b/packages/format-library/CHANGELOG.md index 4ba16131ee983..c262b3a7a2b9a 100644 --- a/packages/format-library/CHANGELOG.md +++ b/packages/format-library/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/format-library/package.json b/packages/format-library/package.json index e67542e26e18f..bf98181720c36 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "5.14.0", + "version": "5.15.1", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,18 +28,18 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/block-editor": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/private-apis": "*", - "@wordpress/rich-text": "*", - "@wordpress/url": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/url": "file:../url" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/hooks/CHANGELOG.md b/packages/hooks/CHANGELOG.md index 18e4445c8b316..59f0159c1c197 100644 --- a/packages/hooks/CHANGELOG.md +++ b/packages/hooks/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 9ba87a51cf8ed..f89240d9ef0cb 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/hooks", - "version": "4.14.0", + "version": "4.15.0", "description": "WordPress hooks library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/hooks/tsconfig.json b/packages/hooks/tsconfig.json index 9e3edfe0ae443..f197b56919708 100644 --- a/packages/hooks/tsconfig.json +++ b/packages/hooks/tsconfig.json @@ -2,9 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] - }, - "include": [ "src/**/*" ] + } } diff --git a/packages/html-entities/CHANGELOG.md b/packages/html-entities/CHANGELOG.md index 2b5fbf25e11b3..a4886fd0d8260 100644 --- a/packages/html-entities/CHANGELOG.md +++ b/packages/html-entities/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/html-entities/package.json b/packages/html-entities/package.json index ed949a1a39ce7..ab1e13c45ce20 100644 --- a/packages/html-entities/package.json +++ b/packages/html-entities/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/html-entities", - "version": "4.14.0", + "version": "4.15.0", "description": "HTML entity utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/html-entities/tsconfig.json b/packages/html-entities/tsconfig.json index 6e33d8ff82d47..7ff060ab6ce10 100644 --- a/packages/html-entities/tsconfig.json +++ b/packages/html-entities/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/i18n/CHANGELOG.md b/packages/i18n/CHANGELOG.md index 0d9c832ddf9d1..439bee3ef508f 100644 --- a/packages/i18n/CHANGELOG.md +++ b/packages/i18n/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 9242c409004eb..abd78a69c1110 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/i18n", - "version": "5.14.0", + "version": "5.15.1", "description": "WordPress internationalization (i18n) library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -32,7 +32,7 @@ }, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/hooks": "*", + "@wordpress/hooks": "file:../hooks", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "sprintf-js": "^1.1.1", diff --git a/packages/i18n/tsconfig.json b/packages/i18n/tsconfig.json index f90e327f124d7..b2186db14f4cc 100644 --- a/packages/i18n/tsconfig.json +++ b/packages/i18n/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../hooks" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../hooks" } ] } diff --git a/packages/icons/CHANGELOG.md b/packages/icons/CHANGELOG.md index 64c1a58b549ca..8219b5e7bbb32 100644 --- a/packages/icons/CHANGELOG.md +++ b/packages/icons/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 10.15.0 (2025-01-02) + - Add new `caution` icon ([#66555](https://github.com/WordPress/gutenberg/pull/66555)). - Add new `error` icon ([#66555](https://github.com/WordPress/gutenberg/pull/66555)). - Deprecate `warning` icon and rename to `cautionFilled` ([#67895](https://github.com/WordPress/gutenberg/pull/67895)). diff --git a/packages/icons/package.json b/packages/icons/package.json index 2b321efdcc54e..9874b2fc0b54e 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/icons", - "version": "10.14.0", + "version": "10.15.1", "description": "WordPress Icons package, based on dashicon.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,8 +31,8 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", - "@wordpress/primitives": "*" + "@wordpress/element": "file:../element", + "@wordpress/primitives": "file:../primitives" }, "publishConfig": { "access": "public" diff --git a/packages/icons/tsconfig.json b/packages/icons/tsconfig.json index 2877b1d31633b..75638a3b50a79 100644 --- a/packages/icons/tsconfig.json +++ b/packages/icons/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ], "references": [ { "path": "../element" }, { "path": "../primitives" } ] } diff --git a/packages/interactivity-router/CHANGELOG.md b/packages/interactivity-router/CHANGELOG.md index c42da0fc72602..1773b2957adc4 100644 --- a/packages/interactivity-router/CHANGELOG.md +++ b/packages/interactivity-router/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.15.0 (2025-01-02) + ## 2.14.0 (2024-12-11) ## 2.13.0 (2024-11-27) diff --git a/packages/interactivity-router/package.json b/packages/interactivity-router/package.json index 8cf30ec4eecb4..a4739e2ef2b6a 100644 --- a/packages/interactivity-router/package.json +++ b/packages/interactivity-router/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/interactivity-router", - "version": "2.14.0", + "version": "2.15.1", "description": "Package that exposes state and actions from the `core/router` store, part of the Interactivity API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,8 +28,8 @@ "wpScriptModuleExports": "./build-module/index.js", "types": "build-types", "dependencies": { - "@wordpress/a11y": "*", - "@wordpress/interactivity": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/interactivity": "file:../interactivity" }, "publishConfig": { "access": "public" diff --git a/packages/interactivity-router/src/assets/styles.ts b/packages/interactivity-router/src/assets/styles.ts new file mode 100644 index 0000000000000..ddb41eabc7a75 --- /dev/null +++ b/packages/interactivity-router/src/assets/styles.ts @@ -0,0 +1,79 @@ +const cssUrlRegEx = + /url\(\s*(?:(["'])((?:\\.|[^\n\\"'])+)\1|((?:\\.|[^\s,"'()\\])+))\s*\)/g; + +const resolveUrl = ( relativeUrl: string, baseUrl: string ) => { + try { + return new URL( relativeUrl, baseUrl ).toString(); + } catch ( e ) { + return relativeUrl; + } +}; + +const withAbsoluteUrls = ( cssText: string, baseUrl: string ) => + cssText.replace( + cssUrlRegEx, + ( _match, quotes = '', relUrl1, relUrl2 ) => + `url(${ quotes }${ resolveUrl( + relUrl1 || relUrl2, + baseUrl + ) }${ quotes })` + ); + +const styleSheetCache = new Map< string, Promise< CSSStyleSheet > >(); + +const getCachedSheet = async ( + sheetId: string, + factory: () => Promise< CSSStyleSheet > +) => { + if ( ! styleSheetCache.has( sheetId ) ) { + styleSheetCache.set( sheetId, factory() ); + } + return styleSheetCache.get( sheetId ); +}; + +const sheetFromLink = async ( + { id, href, sheet: elementSheet }: HTMLLinkElement, + baseUrl: string +) => { + const sheetId = id || href; + const sheetUrl = resolveUrl( href, baseUrl ); + + if ( elementSheet ) { + return getCachedSheet( sheetId, () => { + const sheet = new CSSStyleSheet(); + for ( const { cssText } of elementSheet.cssRules ) { + sheet.insertRule( withAbsoluteUrls( cssText, sheetUrl ) ); + } + return Promise.resolve( sheet ); + } ); + } + return getCachedSheet( sheetId, async () => { + const response = await fetch( href ); + const text = await response.text(); + const sheet = new CSSStyleSheet(); + await sheet.replace( withAbsoluteUrls( text, sheetUrl ) ); + return sheet; + } ); +}; + +const sheetFromStyle = async ( { textContent }: HTMLStyleElement ) => { + const sheetId = textContent; + return getCachedSheet( sheetId, async () => { + const sheet = new CSSStyleSheet(); + await sheet.replace( textContent ); + return sheet; + } ); +}; + +export const generateCSSStyleSheets = ( + doc: Document, + baseUrl: string = ( doc.location || window.location ).href +): Promise< CSSStyleSheet >[] => + [ ...doc.querySelectorAll( 'style,link[rel=stylesheet]' ) ].map( + ( element ) => { + if ( 'LINK' === element.nodeName ) { + return sheetFromLink( element as HTMLLinkElement, baseUrl ); + } + return sheetFromStyle( element as HTMLStyleElement ); + } + ); diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts deleted file mode 100644 index 69139348b582f..0000000000000 --- a/packages/interactivity-router/src/head.ts +++ /dev/null @@ -1,126 +0,0 @@ -/** - * The cache of prefetched stylesheets and scripts. - */ -export const headElements = new Map< - string, - { tag: HTMLElement; text?: string } ->(); - -/** - * Helper to update only the necessary tags in the head. - * - * @async - * @param newHead The head elements of the new page. - */ -export const updateHead = async ( newHead: HTMLHeadElement[] ) => { - // Helper to get the tag id store in the cache. - const getTagId = ( tag: Element ) => tag.id || tag.outerHTML; - - // Map incoming head tags by their content. - const newHeadMap = new Map< string, Element >(); - for ( const child of newHead ) { - newHeadMap.set( getTagId( child ), child ); - } - - const toRemove: Element[] = []; - - // Detect nodes that should be added or removed. - for ( const child of document.head.children ) { - const id = getTagId( child ); - // Always remove styles and links as they might change. - if ( child.nodeName === 'LINK' || child.nodeName === 'STYLE' ) { - toRemove.push( child ); - } else if ( newHeadMap.has( id ) ) { - newHeadMap.delete( id ); - } else if ( child.nodeName !== 'SCRIPT' && child.nodeName !== 'META' ) { - toRemove.push( child ); - } - } - - await Promise.all( - [ ...headElements.entries() ] - .filter( ( [ , { tag } ] ) => tag.nodeName === 'SCRIPT' ) - .map( async ( [ url ] ) => { - await import( /* webpackIgnore: true */ url ); - } ) - ); - - // Prepare new assets. - const toAppend = [ ...newHeadMap.values() ]; - - // Apply the changes. - toRemove.forEach( ( n ) => n.remove() ); - document.head.append( ...toAppend ); -}; - -/** - * Fetches and processes head assets (stylesheets and scripts) from a specified document. - * - * @async - * @param doc The document from which to fetch head assets. It should support standard DOM querying methods. - * - * @return Returns an array of HTML elements representing the head assets. - */ -export const fetchHeadAssets = async ( - doc: Document -): Promise< HTMLElement[] > => { - const headTags = []; - - // We only want to fetch module scripts because regular scripts (without - // `async` or `defer` attributes) can depend on the execution of other scripts. - // Scripts found in the head are blocking and must be executed in order. - const scripts = doc.querySelectorAll< HTMLScriptElement >( - 'script[type="module"][src]' - ); - - scripts.forEach( ( script ) => { - const src = script.getAttribute( 'src' ); - if ( ! headElements.has( src ) ) { - // add the <link> elements to prefetch the module scripts - const link = doc.createElement( 'link' ); - link.rel = 'modulepreload'; - link.href = src; - document.head.append( link ); - headElements.set( src, { tag: script } ); - } - } ); - - const stylesheets = doc.querySelectorAll< HTMLLinkElement >( - 'link[rel=stylesheet]' - ); - - await Promise.all( - Array.from( stylesheets ).map( async ( tag ) => { - const href = tag.getAttribute( 'href' ); - if ( ! href ) { - return; - } - - if ( ! headElements.has( href ) ) { - try { - const response = await fetch( href ); - const text = await response.text(); - headElements.set( href, { - tag, - text, - } ); - } catch ( e ) { - // eslint-disable-next-line no-console - console.error( e ); - } - } - - const headElement = headElements.get( href ); - const styleElement = doc.createElement( 'style' ); - styleElement.textContent = headElement.text; - - headTags.push( styleElement ); - } ) - ); - - return [ - doc.querySelector( 'title' ), - ...doc.querySelectorAll( 'style' ), - ...headTags, - ]; -}; diff --git a/packages/interactivity-router/src/index.ts b/packages/interactivity-router/src/index.ts index 0c10e896ce1ef..ded21d35dd588 100644 --- a/packages/interactivity-router/src/index.ts +++ b/packages/interactivity-router/src/index.ts @@ -6,7 +6,7 @@ import { store, privateApis, getConfig } from '@wordpress/interactivity'; /** * Internal dependencies */ -import { fetchHeadAssets, updateHead, headElements } from './head'; +import { generateCSSStyleSheets } from './assets/styles'; const { directivePrefix, @@ -37,16 +37,18 @@ interface PrefetchOptions { interface VdomParams { vdom?: typeof initialVdom; + baseUrl?: string; } interface Page { regions: Record< string, any >; - head: HTMLHeadElement[]; + styles: Promise< CSSStyleSheet >[]; + scriptModules: string[]; title: string; initialData: any; } -type RegionsToVdom = ( dom: Document, params?: VdomParams ) => Promise< Page >; +type RegionsToVdom = ( dom: Document, params?: VdomParams ) => Page; // Check if the navigation mode is full page or region based. const navigationMode: 'regionBased' | 'fullPage' = @@ -73,7 +75,7 @@ const fetchPage = async ( url: string, { html }: { html: string } ) => { html = await res.text(); } const dom = new window.DOMParser().parseFromString( html, 'text/html' ); - return regionsToVdom( dom ); + return regionsToVdom( dom, { baseUrl: url } ); } catch ( e ) { return false; } @@ -81,12 +83,17 @@ const fetchPage = async ( url: string, { html }: { html: string } ) => { // Return an object with VDOM trees of those HTML regions marked with a // `router-region` directive. -const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { +const regionsToVdom: RegionsToVdom = ( dom, { vdom, baseUrl } = {} ) => { const regions = { body: undefined }; - let head: HTMLElement[]; + const styles = generateCSSStyleSheets( dom, baseUrl ); + const scriptModules = [ + ...dom.querySelectorAll< HTMLScriptElement >( + 'script[type=module][src]' + ), + ].map( ( s ) => s.src ); + if ( globalThis.IS_GUTENBERG_PLUGIN ) { if ( navigationMode === 'fullPage' ) { - head = await fetchHeadAssets( dom ); regions.body = vdom ? vdom.get( document.body ) : toVdom( dom.body ); @@ -103,15 +110,28 @@ const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { } const title = dom.querySelector( 'title' )?.innerText; const initialData = parseServerData( dom ); - return { regions, head, title, initialData }; + return { regions, styles, scriptModules, title, initialData }; }; // Render all interactive regions contained in the given page. const renderRegions = async ( page: Page ) => { + // Wait for styles and modules to be ready. + await Promise.all( [ + ...page.styles, + ...page.scriptModules.map( + ( src ) => import( /* webpackIgnore: true */ src ) + ), + ] ); + // Replace style sheets. + const sheets = await Promise.all( page.styles ); + window.document + .querySelectorAll( 'style,link[rel=stylesheet]' ) + .forEach( ( element ) => element.remove() ); + window.document.adoptedStyleSheets = sheets; + if ( globalThis.IS_GUTENBERG_PLUGIN ) { if ( navigationMode === 'fullPage' ) { - // Once this code is tested and more mature, the head should be updated for region based navigation as well. - await updateHead( page.head ); + // Update HTML. const fragment = getRegionRootFragment( document.body ); batch( () => { populateServerData( page.initialData ); @@ -169,23 +189,14 @@ window.addEventListener( 'popstate', async () => { // Initialize the router and cache the initial page using the initial vDOM. // Once this code is tested and more mature, the head should be updated for // region based navigation as well. -if ( globalThis.IS_GUTENBERG_PLUGIN ) { - if ( navigationMode === 'fullPage' ) { - // Cache the scripts. Has to be called before fetching the assets. - [].map.call( - document.querySelectorAll( 'script[type="module"][src]' ), - ( script ) => { - headElements.set( script.getAttribute( 'src' ), { - tag: script, - } ); - } - ); - await fetchHeadAssets( document ); - } -} pages.set( getPagePath( window.location.href ), - Promise.resolve( regionsToVdom( document, { vdom: initialVdom } ) ) + Promise.resolve( + regionsToVdom( document, { + vdom: initialVdom, + baseUrl: window.location.href, + } ) + ) ); // Check if the link is valid for client-side navigation. diff --git a/packages/interactivity-router/tsconfig.json b/packages/interactivity-router/tsconfig.json index f601a26a86f5f..616718560d02c 100644 --- a/packages/interactivity-router/tsconfig.json +++ b/packages/interactivity-router/tsconfig.json @@ -2,11 +2,8 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "checkJs": false, "strict": false }, - "references": [ { "path": "../a11y" }, { "path": "../interactivity" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../a11y" }, { "path": "../interactivity" } ] } diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md index ec54cdfd7c036..818a16b8dd5e6 100644 --- a/packages/interactivity/CHANGELOG.md +++ b/packages/interactivity/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.15.0 (2025-01-02) + ### Enhancements - Allow more iterables to be used in each directives ([#67798](https://github.com/WordPress/gutenberg/pull/67798)). diff --git a/packages/interactivity/package.json b/packages/interactivity/package.json index 784901d9e70cc..42d694b42b21e 100644 --- a/packages/interactivity/package.json +++ b/packages/interactivity/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/interactivity", - "version": "6.14.0", + "version": "6.15.0", "description": "Package that provides a standard and simple way to handle the frontend interactivity of Gutenberg blocks.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/interactivity/tsconfig.json b/packages/interactivity/tsconfig.json index 1d154e2089065..a4d86e65fa1dd 100644 --- a/packages/interactivity/tsconfig.json +++ b/packages/interactivity/tsconfig.json @@ -2,10 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", - "noImplicitAny": false - }, - "include": [ "src/**/*" ] + } } diff --git a/packages/interactivity/tsconfig.test.json b/packages/interactivity/tsconfig.test.json index 6a90abc2ba221..ad6813d6fec0f 100644 --- a/packages/interactivity/tsconfig.test.json +++ b/packages/interactivity/tsconfig.test.json @@ -2,12 +2,12 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", "noEmit": true, "emitDeclarationOnly": false, "types": [ "jest" ] }, "references": [ { "path": "./tsconfig.json" } ], "files": [ "src/test/store.ts" ], + "include": [], "exclude": [] } diff --git a/packages/interface/CHANGELOG.md b/packages/interface/CHANGELOG.md index 172d70b09fad3..24328fd9357ea 100644 --- a/packages/interface/CHANGELOG.md +++ b/packages/interface/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 9.0.0 (2025-01-02) + ### Breaking Changes - `ActionItem.Slot`: Render as `MenuGroup` by default ([#67985](https://github.com/WordPress/gutenberg/pull/67985)). diff --git a/packages/interface/package.json b/packages/interface/package.json index 351bca68ee3c2..305a78822db3c 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/interface", - "version": "8.3.0", + "version": "9.0.1", "description": "Interface module for WordPress. The package contains shared functionality across the modern JavaScript-based WordPress screens.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -34,17 +34,17 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/plugins": "*", - "@wordpress/preferences": "*", - "@wordpress/viewport": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/plugins": "file:../plugins", + "@wordpress/preferences": "file:../preferences", + "@wordpress/viewport": "file:../viewport", "clsx": "^2.1.1" }, "peerDependencies": { diff --git a/packages/is-shallow-equal/CHANGELOG.md b/packages/is-shallow-equal/CHANGELOG.md index 3c6e84b8184eb..7e493563b336e 100644 --- a/packages/is-shallow-equal/CHANGELOG.md +++ b/packages/is-shallow-equal/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index c9c96621bf74c..7c940fc5c50d3 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/is-shallow-equal", - "version": "5.14.0", + "version": "5.15.0", "description": "Test for shallow equality between two objects or arrays.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/is-shallow-equal/tsconfig.json b/packages/is-shallow-equal/tsconfig.json index 6e33d8ff82d47..7ff060ab6ce10 100644 --- a/packages/is-shallow-equal/tsconfig.json +++ b/packages/is-shallow-equal/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/jest-console/CHANGELOG.md b/packages/jest-console/CHANGELOG.md index d0e4df6942eb5..8cff929ee0cd3 100644 --- a/packages/jest-console/CHANGELOG.md +++ b/packages/jest-console/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 8.15.0 (2025-01-02) + ## 8.14.0 (2024-12-11) ## 8.13.0 (2024-11-27) diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json index 611fbe34713f9..5ecc0d544b457 100644 --- a/packages/jest-console/package.json +++ b/packages/jest-console/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-console", - "version": "8.14.0", + "version": "8.15.0", "description": "Custom Jest matchers for the Console object.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/jest-preset-default/CHANGELOG.md b/packages/jest-preset-default/CHANGELOG.md index 6e5bbdd5555bc..1aa11a49a7ca5 100644 --- a/packages/jest-preset-default/CHANGELOG.md +++ b/packages/jest-preset-default/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 12.15.0 (2025-01-02) + ## 12.14.0 (2024-12-11) ## 12.13.0 (2024-11-27) diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json index e53da15a2c836..fe89cb96aabf7 100644 --- a/packages/jest-preset-default/package.json +++ b/packages/jest-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-preset-default", - "version": "12.14.0", + "version": "12.15.1", "description": "Default Jest preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,7 +31,7 @@ ], "main": "index.js", "dependencies": { - "@wordpress/jest-console": "*", + "@wordpress/jest-console": "file:../jest-console", "babel-jest": "29.7.0" }, "peerDependencies": { diff --git a/packages/jest-puppeteer-axe/CHANGELOG.md b/packages/jest-puppeteer-axe/CHANGELOG.md index 3c8ba71b6e3e5..fb138c899a490 100644 --- a/packages/jest-puppeteer-axe/CHANGELOG.md +++ b/packages/jest-puppeteer-axe/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 7.15.0 (2025-01-02) + ## 7.14.0 (2024-12-11) ## 7.13.0 (2024-11-27) diff --git a/packages/jest-puppeteer-axe/package.json b/packages/jest-puppeteer-axe/package.json index 0a74c9f2ab32d..34123f9a5215d 100644 --- a/packages/jest-puppeteer-axe/package.json +++ b/packages/jest-puppeteer-axe/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-puppeteer-axe", - "version": "7.14.0", + "version": "7.15.0", "description": "Axe API integration with Jest and Puppeteer.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/keyboard-shortcuts/CHANGELOG.md b/packages/keyboard-shortcuts/CHANGELOG.md index 55554040ece4f..12c5fd83b755f 100644 --- a/packages/keyboard-shortcuts/CHANGELOG.md +++ b/packages/keyboard-shortcuts/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/keyboard-shortcuts/package.json b/packages/keyboard-shortcuts/package.json index 7e01cffbb5431..abdd11eaf000a 100644 --- a/packages/keyboard-shortcuts/package.json +++ b/packages/keyboard-shortcuts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/keyboard-shortcuts", - "version": "5.14.0", + "version": "5.15.1", "description": "Handling keyboard shortcuts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,9 +28,9 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/keycodes": "*" + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/keycodes": "file:../keycodes" }, "peerDependencies": { "react": "^18.0.0" diff --git a/packages/keycodes/CHANGELOG.md b/packages/keycodes/CHANGELOG.md index 14542f4aa4da6..6c707ba004f79 100644 --- a/packages/keycodes/CHANGELOG.md +++ b/packages/keycodes/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index 14cb909c88485..c5e0432ecc403 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/keycodes", - "version": "4.14.0", + "version": "4.15.1", "description": "Keycodes utilities for WordPress. Used to check for keyboard events across browsers/operating systems.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,7 +30,7 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/i18n": "*" + "@wordpress/i18n": "file:../i18n" }, "publishConfig": { "access": "public" diff --git a/packages/keycodes/tsconfig.json b/packages/keycodes/tsconfig.json index be13213c265f0..9534c034fa89e 100644 --- a/packages/keycodes/tsconfig.json +++ b/packages/keycodes/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../i18n" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../i18n" } ] } diff --git a/packages/lazy-import/CHANGELOG.md b/packages/lazy-import/CHANGELOG.md index dacbba1f0060b..1c687c7d422dc 100644 --- a/packages/lazy-import/CHANGELOG.md +++ b/packages/lazy-import/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.15.0 (2025-01-02) + ## 2.14.0 (2024-12-11) ## 2.13.0 (2024-11-27) diff --git a/packages/lazy-import/package.json b/packages/lazy-import/package.json index 3aa30657885f7..7be37d101b850 100644 --- a/packages/lazy-import/package.json +++ b/packages/lazy-import/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/lazy-import", - "version": "2.14.0", + "version": "2.15.0", "description": "Lazily import a module, installing it automatically if missing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/lazy-import/tsconfig.json b/packages/lazy-import/tsconfig.json index 3bf8bde807404..9647e449d3545 100644 --- a/packages/lazy-import/tsconfig.json +++ b/packages/lazy-import/tsconfig.json @@ -3,8 +3,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "lib", - "declarationDir": "build-types", "useUnknownInCatchVariables": false }, - "include": [ "lib/**/*" ] + "include": [ "lib" ] } diff --git a/packages/list-reusable-blocks/CHANGELOG.md b/packages/list-reusable-blocks/CHANGELOG.md index b1db0286dbfeb..1b01d7fb68968 100644 --- a/packages/list-reusable-blocks/CHANGELOG.md +++ b/packages/list-reusable-blocks/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 834de576907a2..8dcefb672371e 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "5.14.0", + "version": "5.15.1", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,12 +28,12 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", "change-case": "^4.1.2" }, "peerDependencies": { diff --git a/packages/media-utils/CHANGELOG.md b/packages/media-utils/CHANGELOG.md index 06d5211b10332..587a71c02d6c7 100644 --- a/packages/media-utils/CHANGELOG.md +++ b/packages/media-utils/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/media-utils/package.json b/packages/media-utils/package.json index 6a79d295a6434..9032c03b27399 100644 --- a/packages/media-utils/package.json +++ b/packages/media-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/media-utils", - "version": "5.14.0", + "version": "5.15.1", "description": "WordPress Media Upload Utils.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,11 +29,11 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blob": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/private-apis": "*" + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/private-apis": "file:../private-apis" }, "publishConfig": { "access": "public" diff --git a/packages/media-utils/tsconfig.json b/packages/media-utils/tsconfig.json index ca3e93c2dee66..380e55bc58ff0 100644 --- a/packages/media-utils/tsconfig.json +++ b/packages/media-utils/tsconfig.json @@ -2,12 +2,9 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ], "checkJs": false }, - "include": [ "src/**/*" ], "references": [ { "path": "../api-fetch" }, { "path": "../blob" }, diff --git a/packages/notices/CHANGELOG.md b/packages/notices/CHANGELOG.md index a75201cfdd4f0..00f312134bc5f 100644 --- a/packages/notices/CHANGELOG.md +++ b/packages/notices/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/notices/package.json b/packages/notices/package.json index 3056e0bf84267..b3255f274ccd4 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/notices", - "version": "5.14.0", + "version": "5.15.1", "description": "State management for notices.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,8 +29,8 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/data": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/data": "file:../data" }, "peerDependencies": { "react": "^18.0.0" diff --git a/packages/notices/tsconfig.json b/packages/notices/tsconfig.json index e36a6fe9f4eb6..9c48147297764 100644 --- a/packages/notices/tsconfig.json +++ b/packages/notices/tsconfig.json @@ -2,11 +2,8 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ], "checkJs": false }, - "references": [ { "path": "../a11y" }, { "path": "../data" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../a11y" }, { "path": "../data" } ] } diff --git a/packages/npm-package-json-lint-config/CHANGELOG.md b/packages/npm-package-json-lint-config/CHANGELOG.md index 8c1b85aff77c7..7f3e9d25e8aa4 100644 --- a/packages/npm-package-json-lint-config/CHANGELOG.md +++ b/packages/npm-package-json-lint-config/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/npm-package-json-lint-config/package.json b/packages/npm-package-json-lint-config/package.json index 8cf174b83de3f..6aee7045890a3 100644 --- a/packages/npm-package-json-lint-config/package.json +++ b/packages/npm-package-json-lint-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/npm-package-json-lint-config", - "version": "5.14.0", + "version": "5.15.0", "description": "WordPress npm-package-json-lint shareable configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/CHANGELOG.md b/packages/nux/CHANGELOG.md index f237636fc665e..2bd4ba0b9f84b 100644 --- a/packages/nux/CHANGELOG.md +++ b/packages/nux/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 9.15.0 (2025-01-02) + ## 9.14.0 (2024-12-11) ## 9.13.0 (2024-11-27) diff --git a/packages/nux/package.json b/packages/nux/package.json index d62f84e7e47c5..09208583f28db 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "9.14.0", + "version": "9.15.1", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,13 +33,13 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*" + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/patterns/CHANGELOG.md b/packages/patterns/CHANGELOG.md index be2d674bf65ae..7daad3affe645 100644 --- a/packages/patterns/CHANGELOG.md +++ b/packages/patterns/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.15.0 (2025-01-02) + ## 2.14.0 (2024-12-11) ## 2.13.0 (2024-11-27) diff --git a/packages/patterns/package.json b/packages/patterns/package.json index 7f0036197d1bb..7593061718ab4 100644 --- a/packages/patterns/package.json +++ b/packages/patterns/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/patterns", - "version": "2.14.0", + "version": "2.15.1", "description": "Management of user pattern editing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,20 +33,20 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/html-entities": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*" + "@wordpress/a11y": "file:../a11y", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/plugins/CHANGELOG.md b/packages/plugins/CHANGELOG.md index 7310b5e0c7e76..2db64083e81a7 100644 --- a/packages/plugins/CHANGELOG.md +++ b/packages/plugins/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 7.15.0 (2025-01-02) + ## 7.14.0 (2024-12-11) ## 7.13.0 (2024-11-27) diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 800ef0a6e0d15..64ab1b46c1801 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/plugins", - "version": "7.14.0", + "version": "7.15.1", "description": "Plugins module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,13 +29,13 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/icons": "*", - "@wordpress/is-shallow-equal": "*", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/icons": "file:../icons", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "memize": "^2.0.1" }, "peerDependencies": { diff --git a/packages/plugins/tsconfig.json b/packages/plugins/tsconfig.json index 47c626f9ddedc..66fb760f8896d 100644 --- a/packages/plugins/tsconfig.json +++ b/packages/plugins/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] }, "references": [ @@ -14,6 +12,5 @@ { "path": "../hooks" }, { "path": "../icons" }, { "path": "../is-shallow-equal" } - ], - "include": [ "src/**/*" ] + ] } diff --git a/packages/postcss-plugins-preset/CHANGELOG.md b/packages/postcss-plugins-preset/CHANGELOG.md index b1425332402f5..e41cae5d57626 100644 --- a/packages/postcss-plugins-preset/CHANGELOG.md +++ b/packages/postcss-plugins-preset/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +## 5.15.0 (2025-01-02) + +### Enhancements + +- The bundled `autoprefixer` dependency has been updated from requiring `^10.2.5` to requiring `^10.4.20` (see [#68237](https://github.com/WordPress/gutenberg/pull/68237)). + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/postcss-plugins-preset/package.json b/packages/postcss-plugins-preset/package.json index caecfb0939863..1fc9fe12e79b2 100644 --- a/packages/postcss-plugins-preset/package.json +++ b/packages/postcss-plugins-preset/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/postcss-plugins-preset", - "version": "5.14.0", + "version": "5.15.1", "description": "PostCSS sharable plugins preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,8 +30,8 @@ ], "main": "lib/index.js", "dependencies": { - "@wordpress/base-styles": "*", - "autoprefixer": "^10.2.5" + "@wordpress/base-styles": "file:../base-styles", + "autoprefixer": "^10.4.20" }, "peerDependencies": { "postcss": "^8.0.0" diff --git a/packages/postcss-themes/CHANGELOG.md b/packages/postcss-themes/CHANGELOG.md index 32ebd24aa94e1..1aecce05b5665 100644 --- a/packages/postcss-themes/CHANGELOG.md +++ b/packages/postcss-themes/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.15.0 (2025-01-02) + ## 6.14.0 (2024-12-11) ## 6.13.0 (2024-11-27) diff --git a/packages/postcss-themes/package.json b/packages/postcss-themes/package.json index 38881eb8a5e71..64f5dab1b49ff 100644 --- a/packages/postcss-themes/package.json +++ b/packages/postcss-themes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/postcss-themes", - "version": "6.14.0", + "version": "6.15.0", "description": "PostCSS plugin to generate theme colors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/preferences-persistence/CHANGELOG.md b/packages/preferences-persistence/CHANGELOG.md index 5be962a36256a..d084efe6bc7ff 100644 --- a/packages/preferences-persistence/CHANGELOG.md +++ b/packages/preferences-persistence/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.15.0 (2025-01-02) + ## 2.14.0 (2024-12-11) ## 2.13.0 (2024-11-27) diff --git a/packages/preferences-persistence/package.json b/packages/preferences-persistence/package.json index 219f8edf72111..dcf3367f74a67 100644 --- a/packages/preferences-persistence/package.json +++ b/packages/preferences-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/preferences-persistence", - "version": "2.14.0", + "version": "2.15.1", "description": "Persistence utilities for `wordpress/preferences`.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,7 +30,7 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*" + "@wordpress/api-fetch": "file:../api-fetch" }, "publishConfig": { "access": "public" diff --git a/packages/preferences/CHANGELOG.md b/packages/preferences/CHANGELOG.md index d82d770bd70e2..1df2c47b863e9 100644 --- a/packages/preferences/CHANGELOG.md +++ b/packages/preferences/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/preferences/package.json b/packages/preferences/package.json index 45de983c52e6b..fc9e1fdea496a 100644 --- a/packages/preferences/package.json +++ b/packages/preferences/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/preferences", - "version": "4.14.0", + "version": "4.15.1", "description": "Utilities for managing WordPress preferences.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,15 +31,15 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/private-apis": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/private-apis": "file:../private-apis", "clsx": "^2.1.1" }, "peerDependencies": { diff --git a/packages/prettier-config/CHANGELOG.md b/packages/prettier-config/CHANGELOG.md index 981dc02074994..e0145b0aa7bcb 100644 --- a/packages/prettier-config/CHANGELOG.md +++ b/packages/prettier-config/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/prettier-config/package.json b/packages/prettier-config/package.json index d881e0846684c..10a588adf0ad9 100644 --- a/packages/prettier-config/package.json +++ b/packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/prettier-config", - "version": "4.14.0", + "version": "4.15.0", "description": "WordPress Prettier shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/prettier-config/tsconfig.json b/packages/prettier-config/tsconfig.json index 0636ff7d0081d..7899aeee7dfbc 100644 --- a/packages/prettier-config/tsconfig.json +++ b/packages/prettier-config/tsconfig.json @@ -3,8 +3,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "lib", - "declarationDir": "build-types", "types": [ "node" ] }, - "include": [ "lib/**/*" ] + "include": [ "lib" ] } diff --git a/packages/primitives/CHANGELOG.md b/packages/primitives/CHANGELOG.md index 73b568eca5ffa..d4440a5bbdbf9 100644 --- a/packages/primitives/CHANGELOG.md +++ b/packages/primitives/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/primitives/package.json b/packages/primitives/package.json index 3fa92c0c2681a..3fc711d641f45 100644 --- a/packages/primitives/package.json +++ b/packages/primitives/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/primitives", - "version": "4.14.0", + "version": "4.15.1", "description": "WordPress cross-platform primitives.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,7 +33,7 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", + "@wordpress/element": "file:../element", "clsx": "^2.1.1" }, "peerDependencies": { diff --git a/packages/primitives/tsconfig.json b/packages/primitives/tsconfig.json index 59a95359b5ea6..5dea3e64597b4 100644 --- a/packages/primitives/tsconfig.json +++ b/packages/primitives/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ], "references": [ { "path": "../element" } ] } diff --git a/packages/priority-queue/CHANGELOG.md b/packages/priority-queue/CHANGELOG.md index 8b76778cacb2c..d98ee1b10fdc2 100644 --- a/packages/priority-queue/CHANGELOG.md +++ b/packages/priority-queue/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.15.0 (2025-01-02) + ## 3.14.0 (2024-12-11) ## 3.13.0 (2024-11-27) diff --git a/packages/priority-queue/package.json b/packages/priority-queue/package.json index c1528daa6d148..401d73935df59 100644 --- a/packages/priority-queue/package.json +++ b/packages/priority-queue/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/priority-queue", - "version": "3.14.0", + "version": "3.15.0", "description": "Generic browser priority queue.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/priority-queue/tsconfig.json b/packages/priority-queue/tsconfig.json index 96d649eb7a623..2a790d65e6761 100644 --- a/packages/priority-queue/tsconfig.json +++ b/packages/priority-queue/tsconfig.json @@ -2,9 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "requestidlecallback" ] - }, - "include": [ "src/**/*" ] + } } diff --git a/packages/private-apis/CHANGELOG.md b/packages/private-apis/CHANGELOG.md index 497e0b1bd7e9c..dc1098b6cfa2a 100644 --- a/packages/private-apis/CHANGELOG.md +++ b/packages/private-apis/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.15.0 (2025-01-02) + ## 1.14.0 (2024-12-11) ## 1.13.0 (2024-11-27) diff --git a/packages/private-apis/package.json b/packages/private-apis/package.json index 4e1c0727bf3a4..c43584ce35fca 100644 --- a/packages/private-apis/package.json +++ b/packages/private-apis/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/private-apis", - "version": "1.14.0", + "version": "1.15.0", "description": "Internal experimental APIs for WordPress core.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/private-apis/tsconfig.json b/packages/private-apis/tsconfig.json index 9e3edfe0ae443..f197b56919708 100644 --- a/packages/private-apis/tsconfig.json +++ b/packages/private-apis/tsconfig.json @@ -2,9 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] - }, - "include": [ "src/**/*" ] + } } diff --git a/packages/project-management-automation/CHANGELOG.md b/packages/project-management-automation/CHANGELOG.md index fdcb72a1312db..791cfc87a3a73 100644 --- a/packages/project-management-automation/CHANGELOG.md +++ b/packages/project-management-automation/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.15.0 (2025-01-02) + ## 2.14.0 (2024-12-11) ## 2.13.0 (2024-11-27) diff --git a/packages/project-management-automation/package.json b/packages/project-management-automation/package.json index 6d268051bc3f3..9d1c74e8d15f5 100644 --- a/packages/project-management-automation/package.json +++ b/packages/project-management-automation/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/project-management-automation", - "version": "2.14.0", + "version": "2.15.0", "description": "GitHub Action that implements various automation to assist with managing the Gutenberg GitHub repository.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/project-management-automation/tsconfig.json b/packages/project-management-automation/tsconfig.json index 0636ff7d0081d..7899aeee7dfbc 100644 --- a/packages/project-management-automation/tsconfig.json +++ b/packages/project-management-automation/tsconfig.json @@ -3,8 +3,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "rootDir": "lib", - "declarationDir": "build-types", "types": [ "node" ] }, - "include": [ "lib/**/*" ] + "include": [ "lib" ] } diff --git a/packages/react-i18n/CHANGELOG.md b/packages/react-i18n/CHANGELOG.md index 0c750681dd32a..ac6293e67cd92 100644 --- a/packages/react-i18n/CHANGELOG.md +++ b/packages/react-i18n/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/react-i18n/package.json b/packages/react-i18n/package.json index 78566b3e23efe..b0590bf1c3192 100644 --- a/packages/react-i18n/package.json +++ b/packages/react-i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-i18n", - "version": "4.14.0", + "version": "4.15.1", "description": "React bindings for @wordpress/i18n.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,8 +30,8 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/element": "*", - "@wordpress/i18n": "*", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", "utility-types": "^3.10.0" }, "publishConfig": { diff --git a/packages/react-i18n/tsconfig.json b/packages/react-i18n/tsconfig.json index e8e7f164f89a3..32b019421ed3d 100644 --- a/packages/react-i18n/tsconfig.json +++ b/packages/react-i18n/tsconfig.json @@ -1,10 +1,5 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "references": [ { "path": "../element" }, { "path": "../i18n" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../element" }, { "path": "../i18n" } ] } diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index b0327b531395e..e2f5d5f425d86 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -23,8 +23,8 @@ "npm": ">=8.19.2" }, "dependencies": { - "@wordpress/element": "*", - "@wordpress/keycodes": "*" + "@wordpress/element": "file:../element", + "@wordpress/keycodes": "file:../keycodes" }, "peerDependencies": { "react": "*", diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index b0c0a2485520d..925b83103dca0 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -24,7 +24,7 @@ "main": "index.js", "react-native": "index", "dependencies": { - "@wordpress/react-native-aztec": "*" + "@wordpress/react-native-aztec": "file:../react-native-aztec" }, "peerDependencies": { "react-native": "*" diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index e6e53af1190ad..3a345a23e0a5d 100644 --- a/packages/react-native-editor/package.json +++ b/packages/react-native-editor/package.json @@ -38,18 +38,18 @@ "@react-navigation/native": "6.0.14", "@react-navigation/routers": "5.4.9", "@react-navigation/stack": "6.3.5", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/block-library": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/data": "*", - "@wordpress/edit-post": "*", - "@wordpress/element": "*", - "@wordpress/hooks": "*", - "@wordpress/i18n": "*", - "@wordpress/react-native-aztec": "*", - "@wordpress/react-native-bridge": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/data": "file:../data", + "@wordpress/edit-post": "file:../edit-post", + "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", + "@wordpress/i18n": "file:../i18n", + "@wordpress/react-native-aztec": "file:../react-native-aztec", + "@wordpress/react-native-bridge": "file:../react-native-bridge", "core-js": "^3.31.0", "fast-average-color": "^9.1.1", "gettext-parser": "^1.3.1", diff --git a/packages/readable-js-assets-webpack-plugin/CHANGELOG.md b/packages/readable-js-assets-webpack-plugin/CHANGELOG.md index e31f645fab52b..b61f5e6ac6c7f 100644 --- a/packages/readable-js-assets-webpack-plugin/CHANGELOG.md +++ b/packages/readable-js-assets-webpack-plugin/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.15.0 (2025-01-02) + ## 3.14.0 (2024-12-11) ## 3.13.0 (2024-11-27) diff --git a/packages/readable-js-assets-webpack-plugin/package.json b/packages/readable-js-assets-webpack-plugin/package.json index de912f65e1a0b..8c6a4ab348ae1 100644 --- a/packages/readable-js-assets-webpack-plugin/package.json +++ b/packages/readable-js-assets-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/readable-js-assets-webpack-plugin", - "version": "3.14.0", + "version": "3.15.0", "description": "Generate a readable JS file for each JS asset.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/redux-routine/CHANGELOG.md b/packages/redux-routine/CHANGELOG.md index 5d0d032adc2b5..99cb81dace1f2 100644 --- a/packages/redux-routine/CHANGELOG.md +++ b/packages/redux-routine/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index 9ec7656b00e26..3a2080a46a51c 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/redux-routine", - "version": "5.14.0", + "version": "5.15.0", "description": "Redux middleware for generator coroutines.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/redux-routine/tsconfig.json b/packages/redux-routine/tsconfig.json index 6e33d8ff82d47..7ff060ab6ce10 100644 --- a/packages/redux-routine/tsconfig.json +++ b/packages/redux-routine/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/report-flaky-tests/tsconfig.json b/packages/report-flaky-tests/tsconfig.json index 09fc242db010d..26fcd6f5e51c6 100644 --- a/packages/report-flaky-tests/tsconfig.json +++ b/packages/report-flaky-tests/tsconfig.json @@ -3,10 +3,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "module": "CommonJS", - "declarationDir": "build-types", - "rootDir": "src", "types": [ "jest" ] }, - "include": [ "src/**/*" ], - "exclude": [ "src/__tests__/**/*", "src/__fixtures__/**/*" ] + "exclude": [ "src/__tests__", "src/__fixtures__" ] } diff --git a/packages/reusable-blocks/CHANGELOG.md b/packages/reusable-blocks/CHANGELOG.md index d65b1f8186ecf..5c0a6684d537f 100644 --- a/packages/reusable-blocks/CHANGELOG.md +++ b/packages/reusable-blocks/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/reusable-blocks/package.json b/packages/reusable-blocks/package.json index 2d40d8c087fcc..eb7a3097053cd 100644 --- a/packages/reusable-blocks/package.json +++ b/packages/reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/reusable-blocks", - "version": "5.14.0", + "version": "5.15.1", "description": "Reusable blocks utilities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,17 +31,17 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*" + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/rich-text/CHANGELOG.md b/packages/rich-text/CHANGELOG.md index a981325b3c33f..a80bd1e2d27d0 100644 --- a/packages/rich-text/CHANGELOG.md +++ b/packages/rich-text/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 7.15.0 (2025-01-02) + ## 7.14.0 (2024-12-11) ## 7.13.0 (2024-11-27) diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 39baaa28652d9..5a645aec1225b 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/rich-text", - "version": "7.14.0", + "version": "7.15.1", "description": "Rich text value and manipulation API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,14 +33,14 @@ ], "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/a11y": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/escape-html": "*", - "@wordpress/i18n": "*", - "@wordpress/keycodes": "*", + "@wordpress/a11y": "file:../a11y", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", + "@wordpress/i18n": "file:../i18n", + "@wordpress/keycodes": "file:../keycodes", "memize": "^2.1.0" }, "peerDependencies": { diff --git a/packages/rich-text/tsconfig.json b/packages/rich-text/tsconfig.json index 57fe0ae604215..5dadcb0ed0045 100644 --- a/packages/rich-text/tsconfig.json +++ b/packages/rich-text/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ], "checkJs": false }, @@ -16,6 +14,5 @@ { "path": "../escape-html" }, { "path": "../i18n" }, { "path": "../keycodes" } - ], - "include": [ "src/**/*" ] + ] } diff --git a/packages/router/CHANGELOG.md b/packages/router/CHANGELOG.md index 661dce80f426a..d818aa0bc1f3c 100644 --- a/packages/router/CHANGELOG.md +++ b/packages/router/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.15.0 (2025-01-02) + ## 1.14.0 (2024-12-11) ## 1.13.0 (2024-11-27) diff --git a/packages/router/package.json b/packages/router/package.json index 5932e01f03173..e15293ed060a4 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/router", - "version": "1.14.0", + "version": "1.15.1", "description": "Router API for WordPress pages.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,10 +29,10 @@ "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/compose": "*", - "@wordpress/element": "*", - "@wordpress/private-apis": "*", - "@wordpress/url": "*", + "@wordpress/compose": "file:../compose", + "@wordpress/element": "file:../element", + "@wordpress/private-apis": "file:../private-apis", + "@wordpress/url": "file:../url", "history": "^5.3.0", "route-recognizer": "^0.3.4" }, diff --git a/packages/router/tsconfig.json b/packages/router/tsconfig.json index 8706b546ff304..7d9ba227795ad 100644 --- a/packages/router/tsconfig.json +++ b/packages/router/tsconfig.json @@ -2,8 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] }, "references": [ @@ -11,6 +9,5 @@ { "path": "../element" }, { "path": "../private-apis" }, { "path": "../url" } - ], - "include": [ "src/**/*" ] + ] } diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 42afdd02e0d60..9428b2bf54c95 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -2,6 +2,18 @@ ## Unreleased +## 30.8.0 (2025-01-02) + +### Enhancements + +- Recommend listing JavaScript entry points as paths passed to the `start` and `build` commands ([#68251](https://github.com/WordPress/gutenberg/pull/68251)). +- Introduce a new option `--source-path` to customize the source directory used with the `start` and `build` commands ([#68251](https://github.com/WordPress/gutenberg/pull/68251)). + +### Internal + +- The bundled `rtlcss-webpack-plugin` dependency has been replaced with a modified fork of the plugin to fix issues with the original package ([#68201](https://github.com/WordPress/gutenberg/pull/68201)). +- The bundled `sass` dependency has been updated from `^1.50.0` to `^1.54.0` ([#68380](https://github.com/WordPress/gutenberg/pull/68380)). + ## 30.7.0 (2024-12-11) ### Internal diff --git a/packages/scripts/README.md b/packages/scripts/README.md index f86a4c6091c40..aaf4e03d8a060 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -46,10 +46,6 @@ _Example:_ It might also be a good idea to get familiar with the [JavaScript Build Setup tutorial](https://github.com/WordPress/gutenberg/tree/HEAD/docs/how-to-guides/javascript/js-build-setup.md) for setting up a development environment to use ESNext syntax. It gives a very in-depth explanation of how to use the [build](#build) and [start](#start) scripts. -## Automatic block.json detection and the source code directory - -When using the `start` or `build` commands, the source code directory ( the default is `./src`) and its subdirectories are scanned for the existence of `block.json` files. If one or more are found, they are treated a entry points and will be output into corresponding folders in the `build` directory. This allows for the creation of multiple blocks that use a single build process. The source directory can be customized using the `--webpack-src-dir` flag and the output directory with the `--output-path` flag. - ## Updating to New Release To update an existing project to a new version of `@wordpress/scripts`, open the [changelog](https://github.com/WordPress/gutenberg/blob/HEAD/packages/scripts/CHANGELOG.md), find the version you’re currently on (check `package.json` in the top-level directory of your project), and apply the migration instructions for the newer versions. @@ -66,19 +62,7 @@ Transforms your code according the configuration provided so it’s ready for pr _This script exits after producing a single build. For incremental builds, better suited for development, see the [start](#start) script._ -The entry points for your project get detected by scanning all script fields in `block.json` files located in the `src` directory. The script fields in `block.json` should pass relative paths to `block.json` in the same folder. - -_Example:_ - -```json -{ - "editorScript": "file:index.js", - "script": "file:script.js", - "viewScript": "file:view.js" -} -``` - -The fallback entry point is `src/index.js` (other supported extensions: `.jsx`, `.ts`, and `.tsx`) in case there is no `block.json` file found. In that scenario, the output generated will be written to `build/index.js`. +#### Usage _Example:_ @@ -88,7 +72,7 @@ _Example:_ "build": "wp-scripts build", "build:custom": "wp-scripts build entry-one.js entry-two.js --output-path=custom", "build:copy-php": "wp-scripts build --webpack-copy-php", - "build:custom-directory": "wp-scripts build --webpack-src-dir=custom-directory" + "build:custom-directory": "wp-scripts build --source-path=custom-directory" } } ``` @@ -104,20 +88,21 @@ This script automatically use the optimized config but sometimes you may want to - `--webpack-bundle-analyzer` – enables visualization for the size of webpack output files with an interactive zoomable treemap. - `--webpack-copy-php` – enables copying all PHP files from the source directory ( default is `src` ) and its subfolders to the output directory. -- `--webpack-no-externals` – disables scripts' assets generation, and omits the list of default externals. -- `--webpack-src-dir` – Allows customization of the source code directory. Default is `src`. -- `--output-path` – Allows customization of the output directory. Default is `build`. +- `--webpack-no-externals` – disables scripts’ assets generation, and omits the list of default externals. +- `--source-path` – allows customization of the source directory. The default is the project root `.` when [entry points are listed](#listing-entry-points) in the command, or `src` otherwise. +- `--output-path` – allows customization of the output directory. The default is the `build` folder. Experimental support for the block.json `viewScriptModule` field is available via the `--experimental-modules` option. With this option enabled, script and module fields will all be compiled. The `viewScriptModule` field is analogous to the `viewScript` field, but will compile a module and should be registered in WordPress using the Modules API. +Learn more about [using build scripts](#using-build-scripts) to optimize the development experience based on your specific needs. + #### Advanced information This script uses [webpack](https://webpack.js.org/) behind the scenes. It’ll look for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it’ll use the default config provided by `@wordpress/scripts` packages. Learn more in the [Advanced Usage](#advanced-usage) section. - ### `build-blocks-manifest` This script generates a PHP file containing block metadata from all @@ -128,10 +113,12 @@ when registering multiple block types, as it allows you to use Usage: `wp-scripts build-blocks-manifest [options]` Options: -- `--input`: Specify the input directory (default: 'build') -- `--output`: Specify the output file path (default: 'build/blocks-manifest.php') + +- `--input`: Specify the input directory (default: 'build') +- `--output`: Specify the output file path (default: 'build/blocks-manifest.php') Example: + ```bash wp-scripts build-blocks-manifest --input=src --output=dist/blocks-manifest.php ``` @@ -382,8 +369,8 @@ This is how you create a custom root folder inside the zip file. - When updating a plugin, WordPress expects a folder in the root of the zip file which matches the plugin name. So be aware that this may affect the plugin update process. - `--root-folder` - Add a custom root folder to the zip file. -- `npm run plugin-zip` - By default, unzipping your plugin's zip file will result in a folder with the same name as your plugin. -- `npm run plugin-zip --root-folder='custom-directory'` - Your plugin's zip file will be unzipped into a folder named `custom-directory`. +- `npm run plugin-zip` - By default, unzipping your plugin’s zip file will result in a folder with the same name as your plugin. +- `npm run plugin-zip --root-folder='custom-directory'` - Your plugin’s zip file will be unzipped into a folder named `custom-directory`. - `npm run plugin-zip --no-root-folder` - This will create a zip file that has no folder inside, your plugin files will be unzipped directly into the target directory. ### `start` @@ -392,19 +379,7 @@ Transforms your code according the configuration provided so it’s ready for de _For single builds, better suited for production, see the [build](#build) script._ -The entry points for your project get detected by scanning all script fields in `block.json` files located in the `src` directory. The script fields in `block.json` should pass relative paths to `block.json` in the same folder. - -_Example:_ - -```json -{ - "editorScript": "file:index.js", - "script": "file:script.js", - "viewScript": "file:view.js" -} -``` - -The fallback entry point is `src/index.js` (other supported extensions: `.jsx`, `.ts`, and `.tsx`) in case there is no `block.json` file found. In that scenario, the output generated will be written to `build/index.js`. +#### Usage _Example:_ @@ -415,7 +390,7 @@ _Example:_ "start:hot": "wp-scripts start --hot", "start:custom": "wp-scripts start entry-one.js entry-two.js --output-path=custom", "start:copy-php": "wp-scripts start --webpack-copy-php", - "start:custom-directory": "wp-scripts start --webpack-src-dir=custom-directory" + "start:custom-directory": "wp-scripts start --source-path=custom-directory" } } ``` @@ -435,15 +410,17 @@ This script automatically use the optimized config but sometimes you may want to - `--webpack-bundle-analyzer` – enables visualization for the size of webpack output files with an interactive zoomable treemap. - `--webpack-copy-php` – enables copying all PHP files from the source directory ( default is `src` ) and its subfolders to the output directory. - `--webpack-devtool` – controls how source maps are generated. See options at https://webpack.js.org/configuration/devtool/#devtool. -- `--webpack-no-externals` – disables scripts' assets generation, and omits the list of default externals. -- `--webpack-src-dir` – Allows customization of the source code directory. Default is `src`. -- `--output-path` – Allows customization of the output directory. Default is `build`. +- `--webpack-no-externals` – disables scripts’ assets generation, and omits the list of default externals. +- `--source-path` – allows customization of the source directory. The default is the project root `.` when [entry points are listed](#listing-entry-points) in the command, or `src` otherwise. +- `--output-path` – allows customization of the output directory. The default is the `build` folder. Experimental support for the block.json `viewScriptModule` field is available via the `--experimental-modules` option. With this option enabled, script and module fields will all be compiled. The `viewScriptModule` field is analogous to the `viewScript` field, but will compile a module and should be registered in WordPress using the Modules API. +Learn more about [using build scripts](#using-build-scripts) to optimize the development experience based on your specific needs. + #### Advanced information This script uses [webpack](https://webpack.js.org/) behind the scenes. It’ll look for a webpack config in the top-level directory of your package and will use it if it finds one. If none is found, it’ll use the default config provided by `@wordpress/scripts` packages. Learn more in the [Advanced Usage](#advanced-usage) section. @@ -492,7 +469,7 @@ We enforce that all tests run serially in the current process using [--runInBand When tests fail, both a screenshot and an HTML snapshot will be taken of the page and stored in the `artifacts/` directory at the root of your project. These snapshots may help debug failed tests during development or when running tests in a CI environment. -The `artifacts/` directory can be customized by setting the `WP_ARTIFACTS_PATH` environment variable to the relative path of the desired directory within your project's root. For example: to change the default directory from `artifacts/` to `my/custom/artifacts`, you could use `WP_ARTIFACTS_PATH=my/custom/artifacts npm run test:e2e`. +The `artifacts/` directory can be customized by setting the `WP_ARTIFACTS_PATH` environment variable to the relative path of the desired directory within your project’s root. For example: to change the default directory from `artifacts/` to `my/custom/artifacts`, you could use `WP_ARTIFACTS_PATH=my/custom/artifacts npm run test:e2e`. #### Advanced information @@ -581,11 +558,11 @@ To do so, you can add a file called `playwright.config.ts` or `playwright.config When tests fail, snapshots will be taken of the page and stored in the `artifacts/` directory at the root of your project. These snapshots may help debug failed tests during development or when running tests in a CI environment. -The `artifacts/` directory can be customized by setting the `WP_ARTIFACTS_PATH` environment variable to the relative path of the desired directory within your project's root. For example: to change the default directory from `artifacts/` to `my/custom/artifacts`, you could use `WP_ARTIFACTS_PATH=my/custom/artifacts npm run test:playwright`. +The `artifacts/` directory can be customized by setting the `WP_ARTIFACTS_PATH` environment variable to the relative path of the desired directory within your project’s root. For example: to change the default directory from `artifacts/` to `my/custom/artifacts`, you could use `WP_ARTIFACTS_PATH=my/custom/artifacts npm run test:playwright`. #### Advanced information -You are able to use all of Playwright's [CLI options](https://playwright.dev/docs/test-cli#reference). You can also run `./node_modules/.bin/wp-scripts test-playwright --help` or `npm run test:playwright:help` (as mentioned above) to view all the available options. Learn more in the [Advanced Usage](#advanced-usage) section. +You are able to use all of Playwright’s [CLI options](https://playwright.dev/docs/test-cli#reference). You can also run `./node_modules/.bin/wp-scripts test-playwright --help` or `npm run test:playwright:help` (as mentioned above) to view all the available options. Learn more in the [Advanced Usage](#advanced-usage) section. ## Passing Node.js options @@ -639,30 +616,49 @@ To also debug the browser context, run `wp-scripts --inspect-brk test-e2e --pupp For more e2e debugging tips check out the [Puppeteer debugging docs](https://developers.google.com/web/tools/puppeteer/debugging). -## Advanced Usage +## Using build scripts -In general, this package should be used with the set of recommended config files. While it’s possible to override every single config file provided, if you have to do it, it means that your use case is far more complicated than anticipated. If that happens, it would be better to avoid using the whole abstraction layer and set up your project with full control over tooling used. +The `build` and `start` commands use [webpack](https://webpack.js.org/) behind the scenes. webpack is used to bundle and optimize code for web applications, enabling developers to manage dependencies efficiently, enhance performance, and simplify the development workflow. -### Working with build scripts +### Listing entry points -The `build` and `start` commands use [webpack](https://webpack.js.org/) behind the scenes. webpack is a tool that helps you transform your code into something else. For example: it can take code written in ESNext and output ES5 compatible code that is minified for production. +The simplest way to list JavaScript entry points is to pass them as arguments for the command. -#### Default webpack config +_Example:_ -`@wordpress/scripts` bundles the default webpack config used as a base by the WordPress editor. These are the defaults: +```bash +wp-scripts build entry-one.js entry-two.js +``` -- [Entry](https://webpack.js.org/configuration/entry-context/#entry): the entry points for your project get detected by scanning all script fields in `block.json` files located in the `src` directory. The fallback entry point is `src/index.js` (other supported extensions: `.jsx`, `.ts`, and `.tsx`) in case there is no `block.json` file found. -- [Output](https://webpack.js.org/configuration/output): `build/[name].js`, for example: `build/index.js`, or `build/my-block/index.js`. -- [Loaders](https://webpack.js.org/loaders/): - - [`babel-loader`](https://webpack.js.org/loaders/babel-loader/) allows transpiling JavaScript and TypeScript files using Babel and webpack. - - [`@svgr/webpack`](https://www.npmjs.com/package/@svgr/webpack) and [`url-loader`](https://webpack.js.org/loaders/url-loader/) makes it possible to handle SVG files in JavaScript code. - - [`css-loader`](https://webpack.js.org/loaders/css-loader/) chained with [`postcss-loader`](https://webpack.js.org/loaders/postcss-loader/) and [sass-loader](https://webpack.js.org/loaders/sass-loader/) let webpack process CSS, SASS or SCSS files referenced in JavaScript files. -- [Plugins](https://webpack.js.org/configuration/plugins) (among others): - - [`CopyWebpackPlugin`](https://webpack.js.org/plugins/copy-webpack-plugin/) copies all `block.json` files discovered in the `src` directory to the build directory. - - [`MiniCssExtractPlugin`](https://webpack.js.org/plugins/mini-css-extract-plugin/) extracts CSS into separate files. It creates a CSS file per JavaScript entry point which contains CSS. - - [`@wordpress/dependency-extraction-webpack-plugin`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/dependency-extraction-webpack-plugin/README.md) is used with the default configuration to ensure that WordPress provided scripts are not included in the built bundle. +The default location for the source files is the project’s root. In effect, the command above will look for `entry-one.js` and `entry-two.js` in the project’s root and output the generated files into the `build` directory. + +### Automatic block.json detection and the source code directory -#### Using CSS +A convenient alternative for blocks is using automatic entry point detection. In that case, the source code directory (the default is `./src`) and its subdirectories are scanned for the existence of `block.json` files. If one or more are found, the JavaScript files listed in metadata are treated as entry points and will be output into corresponding folders in the `build` directory. The script fields in `block.json` should pass relative paths to `block.json` in the same folder. + +_Example:_ + +```json +{ + "editorScript": "file:index.js", + "script": "file:script.js", + "viewScript": "file:view.js" +} +``` + +This allows for the creation of multiple blocks that use a single build process triggered with a simple command: + +```bash +wp-scripts build +``` + +The source directory can be customized using the `--source-path` flag and the output directory with the `--output-path` flag. + +### Fallback entry point + +The fallback entry point is `src/index.js` (other supported extensions: `.jsx`, `.ts`, and `.tsx`) in case there is no `block.json` file found. In that scenario, the output generated will be written to `build/index.js`. + +### Importing styles in JavaScript _Example:_ @@ -694,19 +690,19 @@ When you run the build using the default command `wp-scripts build` (also applie 1. `index.css` – all imported CSS files are bundled into one chunk named after the entry point, which defaults to `index.js`, and thus the file created becomes `index.css`. This is for styles used only in the editor. 2. `style-index.css` – imported `style.css` file(s) (applies to PCSS, SASS and SCSS extensions) get bundled into one `style-index.css` file that is meant to be used both on the front-end and in the editor. -You can also have multiple entry points as described in the docs for the script: +For example, when the project has two entry points: ```bash -wp-scripts start entry-one.js entry-two.js --output-path=custom +wp-scripts build entry-one.js entry-two.js ``` -If you do so, then CSS files generated will follow the names of the entry points: `entry-one.css` and `entry-two.css`. +In that case, the CSS generated based on import statements in the JavaScript code will follow the names of the entry points: `entry-one.css` and `entry-two.css`. -Avoid using `style` keyword in an entry point name, this might break your build process. +_Important:_ Avoid using `style` keyword in an entry point name, this might break your build process. You can also bundle CSS modules by prefixing `.module` to the extension, e.g. `style.module.scss`. Otherwise, these files are handled like all other `style.scss`. They will also be extracted into `style-index.css`. -#### Using fonts and images +### Using fonts and images It is possible to reference font (`woff`, `woff2`, `eot`, `ttf` and `otf`) and image (`bmp`, `png`, `jpg`, `jpeg`, `gif` and `wepb`) files from CSS that is controlled by webpack as explained in the previous section. @@ -724,7 +720,7 @@ _Example:_ } ``` -#### Using SVG +### Using SVG _Example:_ @@ -739,18 +735,37 @@ const App = () => ( ); ``` -#### Provide your own webpack config +## Advanced Usage + +This package should generally be used with the set of recommended config files. While it’s possible to override every config file provided, if you have to do it, your use case is far more complicated than anticipated. If that happens, it would be better to avoid using the whole abstraction layer and set up your project with full control over the tooling used. + +### Default webpack config + +`@wordpress/scripts` bundles the default webpack config used as a base by the WordPress editor. These are the defaults: + +- [Entry](https://webpack.js.org/configuration/entry-context/#entry): the entry points for your project get detected by scanning all script fields in `block.json` files located in the `src` directory. The fallback entry point is `src/index.js` (other supported extensions: `.jsx`, `.ts`, and `.tsx`) in case there is no `block.json` file found. +- [Output](https://webpack.js.org/configuration/output): `build/[name].js`, for example: `build/index.js`, or `build/my-block/index.js`. +- [Loaders](https://webpack.js.org/loaders/): + - [`babel-loader`](https://webpack.js.org/loaders/babel-loader/) allows transpiling JavaScript and TypeScript files using Babel and webpack. + - [`@svgr/webpack`](https://www.npmjs.com/package/@svgr/webpack) and [`url-loader`](https://webpack.js.org/loaders/url-loader/) makes it possible to handle SVG files in JavaScript code. + - [`css-loader`](https://webpack.js.org/loaders/css-loader/) chained with [`postcss-loader`](https://webpack.js.org/loaders/postcss-loader/) and [sass-loader](https://webpack.js.org/loaders/sass-loader/) let webpack process CSS, SASS or SCSS files referenced in JavaScript files. +- [Plugins](https://webpack.js.org/configuration/plugins) (among others): + - [`CopyWebpackPlugin`](https://webpack.js.org/plugins/copy-webpack-plugin/) copies all `block.json` files discovered in the `src` directory to the build directory. + - [`MiniCssExtractPlugin`](https://webpack.js.org/plugins/mini-css-extract-plugin/) extracts CSS into separate files. It creates a CSS file per JavaScript entry point which contains CSS. + - [`@wordpress/dependency-extraction-webpack-plugin`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/dependency-extraction-webpack-plugin/README.md) is used with the default configuration to ensure that WordPress provided scripts are not included in the built bundle. + +### Provide your own webpack config Should there be any situation where you want to provide your own webpack config, you can do so. The `build` and `start` commands will use your provided file when: - the command receives a `--config` argument. Example: `wp-scripts build --config my-own-webpack-config.js`. - there is a file called `webpack.config.js` or `webpack.config.babel.js` in the top-level directory of your project (at the same level as `package.json`). -##### Extending the webpack config +#### Extending the webpack config To extend the provided webpack config, or replace subsections within the provided webpack config, you can provide your own `webpack.config.js` file, `require` the provided `webpack.config.js` file, and use the [`spread` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) to import all of or part of the provided configuration. -In the example below, a `webpack.config.js` file is added to the root folder extending the provided webpack config to include custom logic to parse module's source and convert it to a JavaScript object using [`toml`](https://www.npmjs.com/package/toml). It may be useful to import toml or other non-JSON files as JSON, without specific loaders: +In the example below, a `webpack.config.js` file is added to the root folder extending the provided webpack config to include custom logic to parse module’s source and convert it to a JavaScript object using [`toml`](https://www.npmjs.com/package/toml). It may be useful to import toml or other non-JSON files as JSON, without specific loaders: ```javascript const toml = require( 'toml' ); @@ -781,8 +796,8 @@ If you follow this approach, please, be aware that: ## Contributing to this package -This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. +This is an individual package that’s part of the Gutenberg project. The project is organized as a monorepo. It’s made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. -To find out more about contributing to this package or Gutenberg as a whole, please read the project's main [contributor guide](https://github.com/WordPress/gutenberg/tree/HEAD/CONTRIBUTING.md). +To find out more about contributing to this package or Gutenberg as a whole, please read the project’s main [contributor guide](https://github.com/WordPress/gutenberg/tree/HEAD/CONTRIBUTING.md). <br /><br /><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 1829da5cdc15d..f0e425a8d5998 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -9,7 +9,6 @@ const browserslist = require( 'browserslist' ); const MiniCSSExtractPlugin = require( 'mini-css-extract-plugin' ); const { basename, dirname, relative, resolve, sep } = require( 'path' ); const ReactRefreshWebpackPlugin = require( '@pmmmwh/react-refresh-webpack-plugin' ); -const RtlCssPlugin = require( 'rtlcss-webpack-plugin' ); const TerserPlugin = require( 'terser-webpack-plugin' ); const { realpathSync } = require( 'fs' ); const { sync: glob } = require( 'fast-glob' ); @@ -23,19 +22,20 @@ const postcssPlugins = require( '@wordpress/postcss-plugins-preset' ); /** * Internal dependencies */ +const PhpFilePathsPlugin = require( '../plugins/php-file-paths-plugin' ); +const RtlCssPlugin = require( '../plugins/rtlcss-webpack-plugin' ); const { fromConfigRoot, hasBabelConfig, hasArgInCLI, hasCssnanoConfig, hasPostCSSConfig, - getWordPressSrcDirectory, + getProjectSourcePath, getWebpackEntryPoints, getAsBooleanFromENV, getBlockJsonModuleFields, getBlockJsonScriptFields, fromProjectRoot, - PhpFilePathsPlugin, } = require( '../utils' ); const isProduction = process.env.NODE_ENV === 'production'; @@ -302,14 +302,14 @@ const scriptConfig = { } ), new PhpFilePathsPlugin( { - context: getWordPressSrcDirectory(), + context: getProjectSourcePath(), props: [ 'render', 'variations' ], } ), new CopyWebpackPlugin( { patterns: [ { from: '**/block.json', - context: getWordPressSrcDirectory(), + context: getProjectSourcePath(), noErrorOnMissing: true, transform( content, absoluteFrom ) { const convertExtension = ( path ) => { @@ -346,7 +346,7 @@ const scriptConfig = { const runtimePath = relative( dirname( absoluteFrom ), fromProjectRoot( - getWordPressSrcDirectory() + + getProjectSourcePath() + sep + 'runtime.js' ) @@ -375,7 +375,7 @@ const scriptConfig = { }, { from: '**/*.php', - context: getWordPressSrcDirectory(), + context: getProjectSourcePath(), noErrorOnMissing: true, filter: ( filepath ) => { return ( @@ -396,9 +396,7 @@ const scriptConfig = { filename: '[name].css', } ), // RtlCssPlugin to generate RTL CSS files. - new RtlCssPlugin( { - filename: `[name]-rtl.css`, - } ), + new RtlCssPlugin(), // React Fast Refresh. hasReactFastRefresh && new ReactRefreshWebpackPlugin(), // WP_NO_EXTERNALS global variable controls whether scripts' assets get @@ -417,7 +415,7 @@ if ( hasExperimentalModulesFlag ) { /** @type {ReadonlyArray<string>} */ this.blockJsonFiles = glob( '**/block.json', { absolute: true, - cwd: fromProjectRoot( getWordPressSrcDirectory() ), + cwd: fromProjectRoot( getProjectSourcePath() ), } ); } diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 7b0d37a5344b2..3c536cd2ae44e 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/scripts", - "version": "30.7.0", + "version": "30.8.1", "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -25,6 +25,7 @@ "files": [ "bin", "config", + "plugins", "scripts", "utils" ], @@ -35,16 +36,16 @@ "@babel/core": "7.25.7", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@svgr/webpack": "^8.0.1", - "@wordpress/babel-preset-default": "*", - "@wordpress/browserslist-config": "*", - "@wordpress/dependency-extraction-webpack-plugin": "*", - "@wordpress/e2e-test-utils-playwright": "*", - "@wordpress/eslint-plugin": "*", - "@wordpress/jest-preset-default": "*", - "@wordpress/npm-package-json-lint-config": "*", - "@wordpress/postcss-plugins-preset": "*", - "@wordpress/prettier-config": "*", - "@wordpress/stylelint-config": "*", + "@wordpress/babel-preset-default": "file:../babel-preset-default", + "@wordpress/browserslist-config": "file:../browserslist-config", + "@wordpress/dependency-extraction-webpack-plugin": "file:../dependency-extraction-webpack-plugin", + "@wordpress/e2e-test-utils-playwright": "file:../e2e-test-utils-playwright", + "@wordpress/eslint-plugin": "file:../eslint-plugin", + "@wordpress/jest-preset-default": "file:../jest-preset-default", + "@wordpress/npm-package-json-lint-config": "file:../npm-package-json-lint-config", + "@wordpress/postcss-plugins-preset": "file:../postcss-plugins-preset", + "@wordpress/prettier-config": "file:../prettier-config", + "@wordpress/stylelint-config": "file:../stylelint-config", "adm-zip": "^0.5.9", "babel-jest": "29.7.0", "babel-loader": "9.2.1", @@ -80,8 +81,8 @@ "react-refresh": "^0.14.0", "read-pkg-up": "^7.0.1", "resolve-bin": "^0.4.0", - "rtlcss-webpack-plugin": "^4.0.7", - "sass": "^1.50.1", + "rtlcss": "^4.3.0", + "sass": "^1.54.0", "sass-loader": "^16.0.3", "schema-utils": "^4.2.0", "source-map-loader": "^3.0.0", diff --git a/packages/scripts/utils/php-file-paths-plugin.js b/packages/scripts/plugins/php-file-paths-plugin/index.js similarity index 92% rename from packages/scripts/utils/php-file-paths-plugin.js rename to packages/scripts/plugins/php-file-paths-plugin/index.js index 6f95dae6505a8..df39e1626a876 100644 --- a/packages/scripts/utils/php-file-paths-plugin.js +++ b/packages/scripts/plugins/php-file-paths-plugin/index.js @@ -6,7 +6,7 @@ const { validate } = require( 'schema-utils' ); /** * Internal dependencies */ -const { getPhpFilePaths } = require( './config' ); +const { getPhpFilePaths } = require( '../../utils' ); const phpFilePathsPluginSchema = { type: 'object', @@ -57,4 +57,4 @@ class PhpFilePathsPlugin { } } -module.exports = { PhpFilePathsPlugin }; +module.exports = PhpFilePathsPlugin; diff --git a/packages/scripts/plugins/rtlcss-webpack-plugin/index.js b/packages/scripts/plugins/rtlcss-webpack-plugin/index.js new file mode 100644 index 0000000000000..c46c01320c763 --- /dev/null +++ b/packages/scripts/plugins/rtlcss-webpack-plugin/index.js @@ -0,0 +1,66 @@ +/** + * Parts of this source were derived and modified from the package + * rtlcss-webpack-plugin, released under the MIT license. + * + * https://github.com/wix-incubator/rtlcss-webpack-plugin + * + * Copyright (c) 2018 Wix.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * External dependencies + */ +const path = require( 'node:path' ); +const rtlcss = require( 'rtlcss' ); +const webpack = require( 'webpack' ); + +const cssOnly = ( filename ) => path.extname( filename ) === '.css'; + +class RtlCssPlugin { + processAssets = ( compilation, callback ) => { + const chunks = Array.from( compilation.chunks ); + + // Explore each chunk (build output): + chunks.forEach( ( chunk ) => { + // Explore each asset filename generated by the chunk: + const files = Array.from( chunk.files ); + + files.filter( cssOnly ).forEach( ( filename ) => { + // Get the asset source for each file generated by the chunk: + const src = compilation.assets[ filename ].source(); + const dst = rtlcss.process( src ); + const dstFileName = compilation.getPath( '[name]-rtl.css', { + chunk, + cssFileName: filename, + } ); + + compilation.assets[ dstFileName ] = + new webpack.sources.RawSource( dst ); + chunk.files.add( dstFileName ); + } ); + } ); + + callback(); + }; + + apply( compiler ) { + compiler.hooks.compilation.tap( 'RtlCssPlugin', ( compilation ) => { + compilation.hooks.processAssets.tapAsync( + { + name: 'TPAStylePlugin.pluginName', + stage: compilation.PROCESS_ASSETS_STAGE_OPTIMIZE, + }, + ( chunks, callback ) => + this.processAssets( compilation, callback ) + ); + } ); + } +} + +module.exports = RtlCssPlugin; diff --git a/packages/scripts/scripts/build.js b/packages/scripts/scripts/build.js index 0eef2afb451bf..8c7b768ba3e69 100644 --- a/packages/scripts/scripts/build.js +++ b/packages/scripts/scripts/build.js @@ -7,31 +7,11 @@ const { sync: resolveBin } = require( 'resolve-bin' ); /** * Internal dependencies */ -const { getWebpackArgs, hasArgInCLI, getArgFromCLI } = require( '../utils' ); +const { getWebpackArgs } = require( '../utils' ); const EXIT_ERROR_CODE = 1; process.env.NODE_ENV = process.env.NODE_ENV || 'production'; -if ( hasArgInCLI( '--experimental-modules' ) ) { - process.env.WP_EXPERIMENTAL_MODULES = true; -} - -if ( hasArgInCLI( '--webpack-no-externals' ) ) { - process.env.WP_NO_EXTERNALS = true; -} - -if ( hasArgInCLI( '--webpack-bundle-analyzer' ) ) { - process.env.WP_BUNDLE_ANALYZER = true; -} - -if ( hasArgInCLI( '--webpack-copy-php' ) ) { - process.env.WP_COPY_PHP_FILES_TO_DIST = true; -} - -process.env.WP_SRC_DIRECTORY = hasArgInCLI( '--webpack-src-dir' ) - ? getArgFromCLI( '--webpack-src-dir' ) - : 'src'; - const { status } = spawn( resolveBin( 'webpack' ), getWebpackArgs(), { stdio: 'inherit', } ); diff --git a/packages/scripts/scripts/start.js b/packages/scripts/scripts/start.js index 6296192ef302b..fd0a191f168e9 100644 --- a/packages/scripts/scripts/start.js +++ b/packages/scripts/scripts/start.js @@ -7,33 +7,9 @@ const { sync: resolveBin } = require( 'resolve-bin' ); /** * Internal dependencies */ -const { getArgFromCLI, getWebpackArgs, hasArgInCLI } = require( '../utils' ); +const { getWebpackArgs, hasArgInCLI } = require( '../utils' ); const EXIT_ERROR_CODE = 1; -if ( hasArgInCLI( '--experimental-modules' ) ) { - process.env.WP_EXPERIMENTAL_MODULES = true; -} - -if ( hasArgInCLI( '--webpack-no-externals' ) ) { - process.env.WP_NO_EXTERNALS = true; -} - -if ( hasArgInCLI( '--webpack-bundle-analyzer' ) ) { - process.env.WP_BUNDLE_ANALYZER = true; -} - -if ( hasArgInCLI( '--webpack--devtool' ) ) { - process.env.WP_DEVTOOL = getArgFromCLI( '--webpack--devtool' ); -} - -if ( hasArgInCLI( '--webpack-copy-php' ) ) { - process.env.WP_COPY_PHP_FILES_TO_DIST = true; -} - -process.env.WP_SRC_DIRECTORY = hasArgInCLI( '--webpack-src-dir' ) - ? getArgFromCLI( '--webpack-src-dir' ) - : 'src'; - const webpackArgs = getWebpackArgs(); if ( hasArgInCLI( '--hot' ) ) { webpackArgs.unshift( 'serve' ); diff --git a/packages/scripts/utils/config.js b/packages/scripts/utils/config.js index 3d99f3784859d..be6f183137891 100644 --- a/packages/scripts/utils/config.js +++ b/packages/scripts/utils/config.js @@ -9,6 +9,7 @@ const { sync: glob } = require( 'fast-glob' ); * Internal dependencies */ const { + getArgFromCLI, getArgsFromCLI, getFileArgsFromCLI, hasArgInCLI, @@ -114,9 +115,37 @@ const getWebpackArgs = () => { // Gets all args from CLI without those prefixed with `--webpack`. let webpackArgs = getArgsFromCLI( [ '--experimental-modules', + '--source-path', '--webpack', ] ); + if ( hasArgInCLI( '--experimental-modules' ) ) { + process.env.WP_EXPERIMENTAL_MODULES = true; + } + + if ( hasArgInCLI( '--source-path' ) ) { + process.env.WP_SOURCE_PATH = getArgFromCLI( '--source-path' ); + } else if ( hasArgInCLI( '--webpack-src-dir' ) ) { + // Backwards compatibility. + process.env.WP_SOURCE_PATH = getArgFromCLI( '--webpack-src-dir' ); + } + + if ( hasArgInCLI( '--webpack-bundle-analyzer' ) ) { + process.env.WP_BUNDLE_ANALYZER = true; + } + + if ( hasArgInCLI( '--webpack-copy-php' ) ) { + process.env.WP_COPY_PHP_FILES_TO_DIST = true; + } + + if ( hasArgInCLI( '--webpack--devtool' ) ) { + process.env.WP_DEVTOOL = getArgFromCLI( '--webpack--devtool' ); + } + + if ( hasArgInCLI( '--webpack-no-externals' ) ) { + process.env.WP_NO_EXTERNALS = true; + } + const hasWebpackOutputOption = hasArgInCLI( '-o' ) || hasArgInCLI( '--output' ); if ( @@ -136,10 +165,6 @@ const getWebpackArgs = () => { const pathToEntry = ( path ) => { const entryName = basename( path, '.js' ); - if ( ! path.startsWith( './' ) ) { - path = './' + path; - } - return [ entryName, path ]; }; @@ -162,7 +187,11 @@ const getWebpackArgs = () => { const [ entryName, path ] = fileArg.includes( '=' ) ? fileArg.split( '=' ) : pathToEntry( fileArg ); - entry[ entryName ] = path; + entry[ entryName ] = fromProjectRoot( + process.env.WP_SOURCE_PATH + ? join( process.env.WP_SOURCE_PATH, path ) + : path + ); } ); process.env.WP_ENTRY = JSON.stringify( entry ); } @@ -176,20 +205,20 @@ const getWebpackArgs = () => { }; /** - * Returns the WordPress source directory. It defaults to 'src' if the - * `process.env.WP_SRC_DIRECTORY` variable is not set. + * Returns the project source path. It defaults to 'src' if the + * `process.env.WP_SOURCE_PATH` variable is not set. * * @return {string} The WordPress source directory. */ -function getWordPressSrcDirectory() { - return process.env.WP_SRC_DIRECTORY || 'src'; +function getProjectSourcePath() { + return process.env.WP_SOURCE_PATH || 'src'; } /** - * Detects the list of entry points to use with webpack. There are three ways to do this: - * 1. Use the legacy webpack 4 format passed as CLI arguments. - * 2. Scan `block.json` files for scripts. - * 3. Fallback to `src/index.*` file. + * Detects the list of entry points to use with webpack. There are three alternative ways to do this: + * 1. Use the recommended command format that lists the paths to JavaScript files. + * 2. Scan `block.json` files to detect referenced JavaScript and PHP files automatically. + * 3. Fallback to the `src/index.*` file. * * @see https://webpack.js.org/concepts/entry-points/ * @@ -200,31 +229,32 @@ function getWebpackEntryPoints( buildType ) { * @return {Object<string,string>} The list of entry points. */ return () => { - // 1. Handles the legacy format for entry points when explicitly provided with the `process.env.WP_ENTRY`. + // 1. Uses the recommended command format that lists entry points as paths to JavaScript files. + // Example: `wp-scripts build one.js two.js`. if ( process.env.WP_ENTRY ) { return buildType === 'script' ? JSON.parse( process.env.WP_ENTRY ) : {}; } - // Continue only if the source directory exists. - if ( ! hasProjectFile( getWordPressSrcDirectory() ) ) { + // Continues only if the source directory exists. Defaults to "src" if not explicitly set in the command. + if ( ! hasProjectFile( getProjectSourcePath() ) ) { warn( - `Source directory "${ getWordPressSrcDirectory() }" was not found. Please confirm there is a "src" directory in the root or the value passed to --webpack-src-dir is correct.` + `Source directory "${ getProjectSourcePath() }" was not found. Please confirm there is a "src" directory in the root or the value passed with "--output-path" is correct.` ); return {}; } // 2. Checks whether any block metadata files can be detected in the defined source directory. - // It scans all discovered files looking for JavaScript assets and converts them to entry points. + // It scans all discovered files, looks for JavaScript assets, and converts them to entry points. const blockMetadataFiles = glob( '**/block.json', { absolute: true, - cwd: fromProjectRoot( getWordPressSrcDirectory() ), + cwd: fromProjectRoot( getProjectSourcePath() ), } ); if ( blockMetadataFiles.length > 0 ) { const srcDirectory = fromProjectRoot( - getWordPressSrcDirectory() + sep + getProjectSourcePath() + sep ); const entryPoints = {}; @@ -276,7 +306,7 @@ function getWebpackEntryPoints( buildType ) { ) }" listed in "${ blockMetadataFile.replace( fromProjectRoot( sep ), '' - ) }". File is located outside of the "${ getWordPressSrcDirectory() }" directory.` + ) }". File is located outside of the "${ getProjectSourcePath() }" directory.` ); continue; } @@ -290,7 +320,7 @@ function getWebpackEntryPoints( buildType ) { `${ entryName }.?(m)[jt]s?(x)`, { absolute: true, - cwd: fromProjectRoot( getWordPressSrcDirectory() ), + cwd: fromProjectRoot( getProjectSourcePath() ), } ); @@ -302,7 +332,7 @@ function getWebpackEntryPoints( buildType ) { ) }" listed in "${ blockMetadataFile.replace( fromProjectRoot( sep ), '' - ) }". File does not exist in the "${ getWordPressSrcDirectory() }" directory.` + ) }". File does not exist in the "${ getProjectSourcePath() }" directory.` ); continue; } @@ -322,15 +352,15 @@ function getWebpackEntryPoints( buildType ) { } // 3. Checks whether a standard file name can be detected in the defined source directory, - // and converts the discovered file to entry point. + // and converts the discovered file to entry point. const [ entryFile ] = glob( 'index.[jt]s?(x)', { absolute: true, - cwd: fromProjectRoot( getWordPressSrcDirectory() ), + cwd: fromProjectRoot( getProjectSourcePath() ), } ); if ( ! entryFile ) { warn( - `No entry file discovered in the "${ getWordPressSrcDirectory() }" directory.` + `No entry file discovered in the "${ getProjectSourcePath() }" directory.` ); return {}; } @@ -412,10 +442,10 @@ function getPhpFilePaths( context, props ) { module.exports = { getJestOverrideConfigFile, + getPhpFilePaths, + getProjectSourcePath, getWebpackArgs, - getWordPressSrcDirectory, getWebpackEntryPoints, - getPhpFilePaths, hasBabelConfig, hasCssnanoConfig, hasJestConfig, diff --git a/packages/scripts/utils/index.js b/packages/scripts/utils/index.js index cb7e592f83d55..b26df4bd479d9 100644 --- a/packages/scripts/utils/index.js +++ b/packages/scripts/utils/index.js @@ -13,10 +13,10 @@ const { } = require( './cli' ); const { getJestOverrideConfigFile, + getPhpFilePaths, + getProjectSourcePath, getWebpackArgs, - getWordPressSrcDirectory, getWebpackEntryPoints, - getPhpFilePaths, hasBabelConfig, hasCssnanoConfig, hasJestConfig, @@ -29,7 +29,6 @@ const { getBlockJsonModuleFields, getBlockJsonScriptFields, } = require( './block-json' ); -const { PhpFilePathsPlugin } = require( './php-file-paths-plugin' ); module.exports = { fromProjectRoot, @@ -41,10 +40,10 @@ module.exports = { getJestOverrideConfigFile, getNodeArgsFromCLI, getPackageProp, + getPhpFilePaths, + getProjectSourcePath, getWebpackArgs, - getWordPressSrcDirectory, getWebpackEntryPoints, - getPhpFilePaths, getBlockJsonModuleFields, getBlockJsonScriptFields, hasArgInCLI, @@ -56,6 +55,5 @@ module.exports = { hasPostCSSConfig, hasPrettierConfig, hasProjectFile, - PhpFilePathsPlugin, spawnScript, }; diff --git a/packages/server-side-render/CHANGELOG.md b/packages/server-side-render/CHANGELOG.md index 57ebb0f3b81fe..13eaac895ce90 100644 --- a/packages/server-side-render/CHANGELOG.md +++ b/packages/server-side-render/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 5.15.0 (2025-01-02) + ## 5.14.0 (2024-12-11) ## 5.13.0 (2024-11-27) diff --git a/packages/server-side-render/README.md b/packages/server-side-render/README.md index ef7cd9bf0189c..ba6fae302ca0a 100644 --- a/packages/server-side-render/README.md +++ b/packages/server-side-render/README.md @@ -79,7 +79,7 @@ add_filter( 'rest_endpoints', 'add_rest_method'); ### skipBlockSupportAttributes -Remove attributes and style properties applied by the block supports. This prevents duplication of styles in the block wrapper and the `ServerSideRender` components. Even if certain features skip serialization to HTML markup by `skipSerialization`, all attributes and style properties are removed. +Remove attributes and style properties applied by the block supports. This prevents duplication of styles in the block wrapper and the `ServerSideRender` components. Even if certain features skip serialization to HTML markup by `__experimentalSkipSerialization`, all attributes and style properties are removed. - Type: `Boolean` - Required: No diff --git a/packages/server-side-render/package.json b/packages/server-side-render/package.json index b8865a16a056f..1d6d20ddf1bec 100644 --- a/packages/server-side-render/package.json +++ b/packages/server-side-render/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/server-side-render", - "version": "5.14.0", + "version": "5.15.1", "description": "The component used with WordPress to server-side render a preview of dynamic blocks to display in the editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -29,15 +29,15 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/deprecated": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/url": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/url": "file:../url", "fast-deep-equal": "^3.1.3" }, "peerDependencies": { diff --git a/packages/shortcode/CHANGELOG.md b/packages/shortcode/CHANGELOG.md index 6deb33613cf02..c071d8634ba26 100644 --- a/packages/shortcode/CHANGELOG.md +++ b/packages/shortcode/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## Enhancements diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json index 2fd7961f7543c..52cba8c5405d2 100644 --- a/packages/shortcode/package.json +++ b/packages/shortcode/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/shortcode", - "version": "4.14.0", + "version": "4.15.0", "description": "Shortcode module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/shortcode/tsconfig.json b/packages/shortcode/tsconfig.json index 79aa09d0ad56e..2ab16a25d5178 100644 --- a/packages/shortcode/tsconfig.json +++ b/packages/shortcode/tsconfig.json @@ -2,10 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "checkJs": false - }, - "references": [], - "include": [ "src" ] + } } diff --git a/packages/style-engine/CHANGELOG.md b/packages/style-engine/CHANGELOG.md index 5d117fdf8ad8e..fb9f5743c7754 100644 --- a/packages/style-engine/CHANGELOG.md +++ b/packages/style-engine/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 2.15.0 (2025-01-02) + ## 2.14.0 (2024-12-11) ## 2.13.0 (2024-11-27) diff --git a/packages/style-engine/package.json b/packages/style-engine/package.json index d9257c1bb3dfc..ff297fd44a420 100644 --- a/packages/style-engine/package.json +++ b/packages/style-engine/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/style-engine", - "version": "2.14.0", + "version": "2.15.0", "description": "A suite of parsers and compilers for WordPress styles.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/style-engine/tsconfig.json b/packages/style-engine/tsconfig.json index 6e33d8ff82d47..7ff060ab6ce10 100644 --- a/packages/style-engine/tsconfig.json +++ b/packages/style-engine/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/stylelint-config/CHANGELOG.md b/packages/stylelint-config/CHANGELOG.md index d40b5868bef6f..5c983bd427c7a 100644 --- a/packages/stylelint-config/CHANGELOG.md +++ b/packages/stylelint-config/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 23.7.0 (2025-01-02) + ## 23.6.0 (2024-12-11) ## 23.5.0 (2024-11-27) diff --git a/packages/stylelint-config/package.json b/packages/stylelint-config/package.json index 209f38e37ac46..a4aa85cfbf9b6 100644 --- a/packages/stylelint-config/package.json +++ b/packages/stylelint-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/stylelint-config", - "version": "23.6.0", + "version": "23.7.0", "description": "stylelint config for WordPress development.", "author": "The WordPress Contributors", "license": "MIT", diff --git a/packages/sync/CHANGELOG.md b/packages/sync/CHANGELOG.md index c73cf441e7d64..d4c18f7257fa1 100644 --- a/packages/sync/CHANGELOG.md +++ b/packages/sync/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.15.0 (2025-01-02) + ## 1.14.0 (2024-12-11) ## 1.13.0 (2024-11-27) diff --git a/packages/sync/package.json b/packages/sync/package.json index 7735c04e1886d..db19e473f7842 100644 --- a/packages/sync/package.json +++ b/packages/sync/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/sync", - "version": "1.14.0", + "version": "1.15.1", "description": "Sync Data.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,7 +31,7 @@ "dependencies": { "@babel/runtime": "7.25.7", "@types/simple-peer": "^9.11.5", - "@wordpress/url": "*", + "@wordpress/url": "file:../url", "import-locals": "^2.0.0", "lib0": "^0.2.42", "simple-peer": "^9.11.0", diff --git a/packages/sync/tsconfig.json b/packages/sync/tsconfig.json index 40b3ecb72f9ab..f0a5cb0530d29 100644 --- a/packages/sync/tsconfig.json +++ b/packages/sync/tsconfig.json @@ -2,10 +2,7 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "node" ] }, - "include": [ "src/**/*" ], "references": [ { "path": "../url" } ] } diff --git a/packages/token-list/CHANGELOG.md b/packages/token-list/CHANGELOG.md index e4166268d5aae..f29b9baa78f97 100644 --- a/packages/token-list/CHANGELOG.md +++ b/packages/token-list/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.15.0 (2025-01-02) + ## 3.14.0 (2024-12-11) ## 3.13.0 (2024-11-27) diff --git a/packages/token-list/package.json b/packages/token-list/package.json index 957dfec295f5c..5f5f0e0a594ac 100644 --- a/packages/token-list/package.json +++ b/packages/token-list/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/token-list", - "version": "3.14.0", + "version": "3.15.0", "description": "Constructable, plain JavaScript DOMTokenList implementation, supporting non-browser runtimes.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/token-list/tsconfig.json b/packages/token-list/tsconfig.json index d1947d4c52ffd..7ff060ab6ce10 100644 --- a/packages/token-list/tsconfig.json +++ b/packages/token-list/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/undo-manager/CHANGELOG.md b/packages/undo-manager/CHANGELOG.md index 618a17c84bf8a..fe652ac7e5312 100644 --- a/packages/undo-manager/CHANGELOG.md +++ b/packages/undo-manager/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.15.0 (2025-01-02) + ## 1.14.0 (2024-12-11) ## 1.13.0 (2024-11-27) diff --git a/packages/undo-manager/package.json b/packages/undo-manager/package.json index 321d9e51ad5fc..f29b0cd7749f6 100644 --- a/packages/undo-manager/package.json +++ b/packages/undo-manager/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/undo-manager", - "version": "1.14.0", + "version": "1.15.1", "description": "A small package to manage undo/redo.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,7 +31,7 @@ "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/is-shallow-equal": "*" + "@wordpress/is-shallow-equal": "file:../is-shallow-equal" }, "publishConfig": { "access": "public" diff --git a/packages/undo-manager/tsconfig.json b/packages/undo-manager/tsconfig.json index 055c19d5bf513..a3c336bec4560 100644 --- a/packages/undo-manager/tsconfig.json +++ b/packages/undo-manager/tsconfig.json @@ -2,10 +2,7 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "node" ] }, - "references": [ { "path": "../is-shallow-equal" } ], - "include": [ "src/**/*" ] + "references": [ { "path": "../is-shallow-equal" } ] } diff --git a/packages/upload-media/tsconfig.json b/packages/upload-media/tsconfig.json index 3e9fff3edb53e..df9f913b1e11b 100644 --- a/packages/upload-media/tsconfig.json +++ b/packages/upload-media/tsconfig.json @@ -2,11 +2,8 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] }, - "include": [ "src/**/*" ], "references": [ { "path": "../api-fetch" }, { "path": "../blob" }, diff --git a/packages/url/CHANGELOG.md b/packages/url/CHANGELOG.md index b3d57acc53c15..e30f9db1df88c 100644 --- a/packages/url/CHANGELOG.md +++ b/packages/url/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/url/package.json b/packages/url/package.json index 437761955e67c..de7171d505df0 100644 --- a/packages/url/package.json +++ b/packages/url/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/url", - "version": "4.14.0", + "version": "4.15.0", "description": "WordPress URL utilities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/url/tsconfig.json b/packages/url/tsconfig.json index 6e33d8ff82d47..7ff060ab6ce10 100644 --- a/packages/url/tsconfig.json +++ b/packages/url/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/viewport/CHANGELOG.md b/packages/viewport/CHANGELOG.md index bfbfe1c762946..9007be8855347 100644 --- a/packages/viewport/CHANGELOG.md +++ b/packages/viewport/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 6.15.0 (2025-01-02) + ## 6.14.0 (2024-12-11) ## 6.13.0 (2024-11-27) diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 3514f4116e071..8a81f61b3d834 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/viewport", - "version": "6.14.0", + "version": "6.15.1", "description": "Viewport module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -28,9 +28,9 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/compose": "*", - "@wordpress/data": "*", - "@wordpress/element": "*" + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element" }, "peerDependencies": { "react": "^18.0.0" diff --git a/packages/vips/tsconfig.json b/packages/vips/tsconfig.json index 6e33d8ff82d47..7ff060ab6ce10 100644 --- a/packages/vips/tsconfig.json +++ b/packages/vips/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/packages/warning/CHANGELOG.md b/packages/warning/CHANGELOG.md index b50db13eec793..48738d49e2742 100644 --- a/packages/warning/CHANGELOG.md +++ b/packages/warning/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 3.15.0 (2025-01-02) + ## 3.14.0 (2024-12-11) ## 3.13.0 (2024-11-27) diff --git a/packages/warning/package.json b/packages/warning/package.json index baf7b5d1925b3..b371ef03ed431 100644 --- a/packages/warning/package.json +++ b/packages/warning/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/warning", - "version": "3.14.0", + "version": "3.15.0", "description": "Warning utility for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/warning/tsconfig.json b/packages/warning/tsconfig.json index 9e3edfe0ae443..f197b56919708 100644 --- a/packages/warning/tsconfig.json +++ b/packages/warning/tsconfig.json @@ -2,9 +2,6 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types", "types": [ "gutenberg-env" ] - }, - "include": [ "src/**/*" ] + } } diff --git a/packages/widgets/CHANGELOG.md b/packages/widgets/CHANGELOG.md index f225f20e80c00..43b9676905d04 100644 --- a/packages/widgets/CHANGELOG.md +++ b/packages/widgets/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/widgets/package.json b/packages/widgets/package.json index a08c34d43bc51..a9b7e28f67293 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/widgets", - "version": "4.14.0", + "version": "4.15.1", "description": "Functionality used by the widgets block editor in the Widgets screen and the Customizer.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -26,17 +26,17 @@ "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", - "@wordpress/api-fetch": "*", - "@wordpress/block-editor": "*", - "@wordpress/blocks": "*", - "@wordpress/components": "*", - "@wordpress/compose": "*", - "@wordpress/core-data": "*", - "@wordpress/data": "*", - "@wordpress/element": "*", - "@wordpress/i18n": "*", - "@wordpress/icons": "*", - "@wordpress/notices": "*", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/block-editor": "file:../block-editor", + "@wordpress/blocks": "file:../blocks", + "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/icons": "file:../icons", + "@wordpress/notices": "file:../notices", "clsx": "^2.1.1" }, "peerDependencies": { diff --git a/packages/wordcount/CHANGELOG.md b/packages/wordcount/CHANGELOG.md index 9dab5fb73d856..37e23d6a1b7c3 100644 --- a/packages/wordcount/CHANGELOG.md +++ b/packages/wordcount/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 4.15.0 (2025-01-02) + ## 4.14.0 (2024-12-11) ## 4.13.0 (2024-11-27) diff --git a/packages/wordcount/package.json b/packages/wordcount/package.json index b0b42cbe7900c..c82a471403054 100644 --- a/packages/wordcount/package.json +++ b/packages/wordcount/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/wordcount", - "version": "4.14.0", + "version": "4.15.0", "description": "WordPress word count utility.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/wordcount/tsconfig.json b/packages/wordcount/tsconfig.json index 6e33d8ff82d47..7ff060ab6ce10 100644 --- a/packages/wordcount/tsconfig.json +++ b/packages/wordcount/tsconfig.json @@ -1,9 +1,4 @@ { "$schema": "https://json.schemastore.org/tsconfig.json", - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "declarationDir": "build-types" - }, - "include": [ "src/**/*" ] + "extends": "../../tsconfig.base.json" } diff --git a/phpunit/block-supports/border-test.php b/phpunit/block-supports/border-test.php index 510633b48aab5..858e4e92cc174 100644 --- a/phpunit/block-supports/border-test.php +++ b/phpunit/block-supports/border-test.php @@ -128,11 +128,11 @@ public function test_flat_border_with_skipped_serialization() { 'test/flat-border-with-skipped-serialization', array( '__experimentalBorder' => array( - 'color' => true, - 'radius' => true, - 'width' => true, - 'style' => true, - 'skipSerialization' => true, + 'color' => true, + 'radius' => true, + 'width' => true, + 'style' => true, + '__experimentalSkipSerialization' => true, ), ) ); @@ -459,375 +459,4 @@ public function test_split_borders_with_named_colors() { $this->assertSame( $expected, $actual ); } - /** - * Tests that stabilized border supports will also apply to blocks using - * the experimental syntax, for backwards compatibility with existing blocks. - * - * @covers ::gutenberg_apply_border_support - */ - public function test_should_apply_experimental_border_supports() { - $this->test_block_name = 'test/experimental-border-supports'; - register_block_type( - $this->test_block_name, - array( - 'api_version' => 3, - 'attributes' => array( - 'style' => array( - 'type' => 'object', - ), - ), - 'supports' => array( - '__experimentalBorder' => array( - 'color' => true, - 'radius' => true, - 'style' => true, - 'width' => true, - '__experimentalDefaultControls' => array( - 'color' => true, - 'radius' => true, - 'style' => true, - 'width' => true, - ), - ), - ), - ) - ); - $registry = WP_Block_Type_Registry::get_instance(); - $block_type = $registry->get_registered( $this->test_block_name ); - $block_atts = array( - 'style' => array( - 'border' => array( - 'color' => '#72aee6', - 'radius' => '10px', - 'style' => 'dashed', - 'width' => '2px', - ), - ), - ); - - $actual = gutenberg_apply_border_support( $block_type, $block_atts ); - $expected = array( - 'class' => 'has-border-color', - 'style' => 'border-color:#72aee6;border-radius:10px;border-style:dashed;border-width:2px;', - ); - - $this->assertSame( $expected, $actual ); - } - - /** - * Tests that stabilized border supports are applied correctly. - * - * @covers ::gutenberg_apply_border_support - */ - public function test_should_apply_stabilized_border_supports() { - $this->test_block_name = 'test/stabilized-border-supports'; - register_block_type( - $this->test_block_name, - array( - 'api_version' => 3, - 'attributes' => array( - 'style' => array( - 'type' => 'object', - ), - ), - 'supports' => array( - 'border' => array( - 'color' => true, - 'radius' => true, - 'style' => true, - 'width' => true, - '__experimentalDefaultControls' => array( - 'color' => true, - 'radius' => true, - 'style' => true, - 'width' => true, - ), - ), - ), - ) - ); - $registry = WP_Block_Type_Registry::get_instance(); - $block_type = $registry->get_registered( $this->test_block_name ); - $block_atts = array( - 'style' => array( - 'border' => array( - 'color' => '#72aee6', - 'radius' => '10px', - 'style' => 'dashed', - 'width' => '2px', - ), - ), - ); - - $actual = gutenberg_apply_border_support( $block_type, $block_atts ); - $expected = array( - 'class' => 'has-border-color', - 'style' => 'border-color:#72aee6;border-radius:10px;border-style:dashed;border-width:2px;', - ); - - $this->assertSame( $expected, $actual ); - } - - /** - * Tests that experimental border support configuration gets stabilized correctly. - */ - public function test_should_stabilize_border_supports() { - $block_type_args = array( - 'supports' => array( - '__experimentalBorder' => array( - 'color' => true, - 'radius' => true, - 'style' => true, - 'width' => true, - '__experimentalSkipSerialization' => true, - '__experimentalDefaultControls' => array( - 'color' => true, - 'radius' => true, - 'style' => true, - 'width' => true, - ), - ), - ), - ); - - $actual = gutenberg_stabilize_experimental_block_supports( $block_type_args ); - $expected = array( - 'supports' => array( - 'border' => array( - 'color' => true, - 'radius' => true, - 'style' => true, - 'width' => true, - 'skipSerialization' => true, - // Has to be kept due to core's `wp_should_skip_block_supports_serialization` only checking the experimental flag until 6.8. - '__experimentalSkipSerialization' => true, - 'defaultControls' => array( - 'color' => true, - 'radius' => true, - 'style' => true, - 'width' => true, - ), - ), - ), - ); - - $this->assertSame( $expected, $actual, 'Stabilized border block support config does not match.' ); - } - - /** - * Tests the merging of border support configuration when stabilizing - * experimental config. Due to the ability to filter block type args, plugins - * or themes could filter using outdated experimental keys. While not every - * permutation of filtering can be covered, the majority of use cases are - * served best by merging configs based on the order they were defined if possible. - */ - public function test_should_stabilize_border_supports_using_order_based_merge() { - $experimental_border_config = array( - 'color' => true, - 'radius' => true, - 'style' => true, - 'width' => true, - '__experimentalSkipSerialization' => true, - '__experimentalDefaultControls' => array( - 'color' => true, - 'radius' => true, - 'style' => true, - 'width' => true, - ), - - /* - * The following simulates theme/plugin filtering using `__experimentalBorder` - * key but stable serialization and default control keys. - */ - 'skipSerialization' => false, - 'defaultControls' => array( - 'color' => true, - 'radius' => false, - 'style' => true, - 'width' => true, - ), - ); - $stable_border_config = array( - 'color' => true, - 'radius' => true, - 'style' => false, - 'width' => true, - 'skipSerialization' => false, - 'defaultControls' => array( - 'color' => true, - 'radius' => false, - 'style' => false, - 'width' => true, - ), - - /* - * The following simulates theme/plugin filtering using stable `border` key - * but experimental serialization and default control keys. - */ - '__experimentalSkipSerialization' => true, - '__experimentalDefaultControls' => array( - 'color' => false, - 'radius' => false, - 'style' => false, - 'width' => false, - ), - ); - - $experimental_first_args = array( - 'supports' => array( - '__experimentalBorder' => $experimental_border_config, - 'border' => $stable_border_config, - ), - ); - - $actual = gutenberg_stabilize_experimental_block_supports( $experimental_first_args ); - $expected = array( - 'supports' => array( - 'border' => array( - 'color' => true, - 'radius' => true, - 'style' => false, - 'width' => true, - 'skipSerialization' => true, - '__experimentalSkipSerialization' => true, - 'defaultControls' => array( - 'color' => false, - 'radius' => false, - 'style' => false, - 'width' => false, - ), - - ), - ), - ); - $this->assertSame( $expected, $actual, 'Merged stabilized border block support config does not match when experimental keys are first.' ); - - $stable_first_args = array( - 'supports' => array( - 'border' => $stable_border_config, - '__experimentalBorder' => $experimental_border_config, - ), - ); - - $actual = gutenberg_stabilize_experimental_block_supports( $stable_first_args ); - $expected = array( - 'supports' => array( - 'border' => array( - 'color' => true, - 'radius' => true, - 'style' => true, - 'width' => true, - 'skipSerialization' => false, - '__experimentalSkipSerialization' => false, - 'defaultControls' => array( - 'color' => true, - 'radius' => false, - 'style' => true, - 'width' => true, - ), - ), - ), - ); - $this->assertSame( $expected, $actual, 'Merged stabilized border block support config does not match when stable keys are first.' ); - } - - /** - * Tests that boolean border support configurations are handled correctly. - * - * @dataProvider data_boolean_border_supports - * - * @param array $supports The supports configuration to test. - * @param boolean|array $expected_value The expected final border support value. - */ - public function test_should_handle_boolean_border_supports( $supports, $expected_value ) { - $args = array( - 'supports' => $supports, - ); - - $actual = gutenberg_stabilize_experimental_block_supports( $args ); - - $this->assertSame( $expected_value, $actual['supports']['border'] ); - } - - /** - * Data provider for boolean border support tests. - * - * @return array Test parameters. - */ - public function data_boolean_border_supports() { - return array( - 'experimental true only' => array( - array( - '__experimentalBorder' => true, - ), - true, - ), - 'experimental false only' => array( - array( - '__experimentalBorder' => false, - ), - false, - ), - 'experimental true before stable false' => array( - array( - '__experimentalBorder' => true, - 'border' => false, - ), - false, - ), - 'stable true before experimental false' => array( - array( - 'border' => true, - '__experimentalBorder' => false, - ), - false, - ), - 'experimental array before stable boolean' => array( - array( - '__experimentalBorder' => array( - 'color' => true, - 'width' => true, - ), - 'border' => false, - ), - false, - ), - 'stable array before experimental boolean' => array( - array( - 'border' => array( - 'color' => true, - 'width' => true, - ), - '__experimentalBorder' => true, - ), - true, - ), - 'experimental boolean before stable array' => array( - array( - '__experimentalBorder' => true, - 'border' => array( - 'color' => true, - 'width' => true, - ), - ), - array( - 'color' => true, - 'width' => true, - ), - ), - 'stable boolean before experimental array' => array( - array( - 'border' => false, - '__experimentalBorder' => array( - 'color' => true, - 'width' => true, - ), - ), - array( - 'color' => true, - 'width' => true, - ), - ), - ); - } } diff --git a/phpunit/block-supports/typography-test.php b/phpunit/block-supports/typography-test.php index 1804659c11af3..eafd505db6ec6 100644 --- a/phpunit/block-supports/typography-test.php +++ b/phpunit/block-supports/typography-test.php @@ -283,111 +283,6 @@ public function test_should_generate_classname_for_font_family() { $this->assertSame( $expected, $actual ); } - /** - * Tests that stabilized typography supports will also apply to blocks using - * the experimental syntax, for backwards compatibility with existing blocks. - * - * @covers ::gutenberg_apply_typography_support - */ - public function test_should_apply_experimental_typography_supports() { - $this->test_block_name = 'test/experimental-typography-supports'; - register_block_type( - $this->test_block_name, - array( - 'api_version' => 3, - 'attributes' => array( - 'style' => array( - 'type' => 'object', - ), - ), - 'supports' => array( - 'typography' => array( - '__experimentalFontFamily' => true, - '__experimentalFontStyle' => true, - '__experimentalFontWeight' => true, - '__experimentalLetterSpacing' => true, - '__experimentalTextDecoration' => true, - '__experimentalTextTransform' => true, - ), - ), - ) - ); - $registry = WP_Block_Type_Registry::get_instance(); - $block_type = $registry->get_registered( $this->test_block_name ); - $block_atts = array( - 'fontFamily' => 'serif', - 'style' => array( - 'typography' => array( - 'fontStyle' => 'italic', - 'fontWeight' => 'bold', - 'letterSpacing' => '1px', - 'textDecoration' => 'underline', - 'textTransform' => 'uppercase', - ), - ), - ); - - $actual = gutenberg_apply_typography_support( $block_type, $block_atts ); - $expected = array( - 'class' => 'has-serif-font-family', - 'style' => 'font-style:italic;font-weight:bold;text-decoration:underline;text-transform:uppercase;letter-spacing:1px;', - ); - - $this->assertSame( $expected, $actual ); - } - - /** - * Tests that stabilized typography supports are applied correctly. - * - * @covers ::gutenberg_apply_typography_support - */ - public function test_should_apply_stabilized_typography_supports() { - $this->test_block_name = 'test/experimental-typography-supports'; - register_block_type( - $this->test_block_name, - array( - 'api_version' => 3, - 'attributes' => array( - 'style' => array( - 'type' => 'object', - ), - ), - 'supports' => array( - 'typography' => array( - 'fontFamily' => true, - 'fontStyle' => true, - 'fontWeight' => true, - 'letterSpacing' => true, - 'textDecoration' => true, - 'textTransform' => true, - ), - ), - ) - ); - $registry = WP_Block_Type_Registry::get_instance(); - $block_type = $registry->get_registered( $this->test_block_name ); - $block_atts = array( - 'fontFamily' => 'serif', - 'style' => array( - 'typography' => array( - 'fontStyle' => 'italic', - 'fontWeight' => 'bold', - 'letterSpacing' => '1px', - 'textDecoration' => 'underline', - 'textTransform' => 'uppercase', - ), - ), - ); - - $actual = gutenberg_apply_typography_support( $block_type, $block_atts ); - $expected = array( - 'class' => 'has-serif-font-family', - 'style' => 'font-style:italic;font-weight:bold;text-decoration:underline;text-transform:uppercase;letter-spacing:1px;', - ); - - $this->assertSame( $expected, $actual ); - } - /** * Tests generating font size values, including fluid formulae, from fontSizes preset. * diff --git a/schemas/json/theme.json b/schemas/json/theme.json index a1f51ace92025..4eec377e3a94b 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -922,6 +922,9 @@ "core/file": { "$ref": "#/definitions/settingsPropertiesComplete" }, + "core/footnotes": { + "$ref": "#/definitions/settingsPropertiesComplete" + }, "core/freeform": { "$ref": "#/definitions/settingsPropertiesComplete" }, @@ -1030,9 +1033,6 @@ "core/post-terms": { "$ref": "#/definitions/settingsPropertiesComplete" }, - "core/post-time-to-read": { - "$ref": "#/definitions/settingsPropertiesComplete" - }, "core/post-title": { "$ref": "#/definitions/settingsPropertiesComplete" }, @@ -1063,6 +1063,9 @@ "core/query-title": { "$ref": "#/definitions/settingsPropertiesComplete" }, + "core/query-total": { + "$ref": "#/definitions/settingsPropertiesComplete" + }, "core/quote": { "$ref": "#/definitions/settingsPropertiesComplete" }, @@ -1102,9 +1105,6 @@ "core/table": { "$ref": "#/definitions/settingsPropertiesComplete" }, - "core/table-of-contents": { - "$ref": "#/definitions/settingsPropertiesComplete" - }, "core/tag-cloud": { "$ref": "#/definitions/settingsPropertiesComplete" }, @@ -1902,6 +1902,9 @@ "core/file": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, + "core/footnotes": { + "$ref": "#/definitions/stylesPropertiesAndElementsComplete" + }, "core/freeform": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, @@ -2010,9 +2013,6 @@ "core/post-terms": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, - "core/post-time-to-read": { - "$ref": "#/definitions/stylesPropertiesAndElementsComplete" - }, "core/post-title": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, @@ -2043,6 +2043,9 @@ "core/query-title": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, + "core/query-total": { + "$ref": "#/definitions/stylesPropertiesAndElementsComplete" + }, "core/quote": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, @@ -2082,9 +2085,6 @@ "core/table": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, - "core/table-of-contents": { - "$ref": "#/definitions/stylesPropertiesAndElementsComplete" - }, "core/tag-cloud": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, @@ -2316,6 +2316,9 @@ "core/file": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, + "core/footnotes": { + "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" + }, "core/freeform": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, @@ -2424,9 +2427,6 @@ "core/post-terms": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, - "core/post-time-to-read": { - "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" - }, "core/post-title": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, @@ -2457,6 +2457,9 @@ "core/query-title": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, + "core/query-total": { + "$ref": "#/definitions/stylesPropertiesAndElementsComplete" + }, "core/quote": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, @@ -2496,9 +2499,6 @@ "core/table": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, - "core/table-of-contents": { - "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" - }, "core/tag-cloud": { "$ref": "#/definitions/stylesVariationBlockPropertiesComplete" }, diff --git a/schemas/json/wp-env.json b/schemas/json/wp-env.json index 3958818714e4e..5761fb3d87711 100644 --- a/schemas/json/wp-env.json +++ b/schemas/json/wp-env.json @@ -49,7 +49,7 @@ "default": [] }, "port": { - "description": "The primary port number to use for the installation. You'll access the instance through the port: 'http://localhost:8888'.", + "description": "The primary port number to use for the installation. You'll access the instance through the port: http://localhost:8888", "type": "integer", "default": 8888 }, @@ -114,6 +114,11 @@ } }, "default": {} + }, + "testsPort": { + "description": "The port number for the test site. You'll access the instance through the port: http://localhost:8889", + "type": "integer", + "default": 8889 } } }, @@ -125,7 +130,7 @@ "$ref": "#/definitions/wpEnvPropertyNames" }, { - "enum": [ "$schema", "env" ] + "enum": [ "$schema", "env", "testsPort" ] } ] } diff --git a/storybook/preview.js b/storybook/preview.js index 8372103cd9944..b74640d9bcfbc 100644 --- a/storybook/preview.js +++ b/storybook/preview.js @@ -108,6 +108,9 @@ export const parameters = { sort: 'requiredFirst', }, docs: { + controls: { + sort: 'requiredFirst', + }, // Flips the order of the description and the primary component story // so the component is always visible before the fold. page: () => ( diff --git a/storybook/stories/playground/box/index.js b/storybook/stories/playground/box/index.js index cca522a90c144..35656c7d6edc0 100644 --- a/storybook/stories/playground/box/index.js +++ b/storybook/stories/playground/box/index.js @@ -12,7 +12,7 @@ import { /** * Internal dependencies */ -import editorStyles from '../editor-styles'; +import { editorStyles } from '../editor-styles'; import './style.css'; export default function EditorBox() { diff --git a/storybook/stories/playground/with-undo-redo/index.js b/storybook/stories/playground/with-undo-redo/index.js index b5a6067cad24b..0952543679ce0 100644 --- a/storybook/stories/playground/with-undo-redo/index.js +++ b/storybook/stories/playground/with-undo-redo/index.js @@ -15,7 +15,7 @@ import { undo as undoIcon, redo as redoIcon } from '@wordpress/icons'; /** * Internal dependencies */ -import editorStyles from '../editor-styles'; +import { editorStyles } from '../editor-styles'; import './style.css'; export default function EditorWithUndoRedo() { diff --git a/storybook/stories/playground/zoom-out/index.js b/storybook/stories/playground/zoom-out/index.js index 8b72a831d710e..c4d9a716c9069 100644 --- a/storybook/stories/playground/zoom-out/index.js +++ b/storybook/stories/playground/zoom-out/index.js @@ -16,7 +16,7 @@ import { parse } from '@wordpress/blocks'; /** * Internal dependencies */ -import editorStyles from '../editor-styles'; +import { editorStyles } from '../editor-styles'; // eslint-disable-next-line @wordpress/dependency-group import contentCss from '!!raw-loader!../../../../packages/block-editor/build-style/content.css'; import { pattern } from './pattern'; diff --git a/test/e2e/playwright.config.ts b/test/e2e/playwright.config.ts index f5410f2230372..bb93f342f9bf8 100644 --- a/test/e2e/playwright.config.ts +++ b/test/e2e/playwright.config.ts @@ -8,7 +8,7 @@ import { defineConfig, devices } from '@playwright/test'; /** * WordPress dependencies */ -const baseConfig = require( '@wordpress/scripts/config/playwright.config' ); +import baseConfig from '@wordpress/scripts/config/playwright.config.js'; const config = defineConfig( { ...baseConfig, diff --git a/test/e2e/specs/editor/blocks/buttons.spec.js b/test/e2e/specs/editor/blocks/buttons.spec.js index c7fdc18429e11..ad19af747238d 100644 --- a/test/e2e/specs/editor/blocks/buttons.spec.js +++ b/test/e2e/specs/editor/blocks/buttons.spec.js @@ -268,7 +268,7 @@ test.describe( 'Buttons', () => { .getByRole( 'tab', { name: 'Settings' } ) .click(); await page - .getByRole( 'radiogroup', { name: 'Button width' } ) + .getByRole( 'radiogroup', { name: 'Width' } ) .getByRole( 'radio', { name: '25%' } ) .click(); diff --git a/test/e2e/specs/editor/blocks/image.spec.js b/test/e2e/specs/editor/blocks/image.spec.js index d3cddd9c3a51c..79cb01038da23 100644 --- a/test/e2e/specs/editor/blocks/image.spec.js +++ b/test/e2e/specs/editor/blocks/image.spec.js @@ -424,9 +424,6 @@ test.describe( 'Image', () => { page, editor, } ) => { - // This is a temp workaround for dragging and dropping images from the inserter. - // This should be removed when we have the zoom out view for media categories. - await page.setViewportSize( { width: 1400, height: 800 } ); await editor.insertBlock( { name: 'core/image' } ); const imageBlock = editor.canvas.getByRole( 'document', { name: 'Block: Image', diff --git a/test/e2e/specs/editor/blocks/navigation.spec.js b/test/e2e/specs/editor/blocks/navigation.spec.js index 83e95a08c0f6a..769e30c99dab3 100644 --- a/test/e2e/specs/editor/blocks/navigation.spec.js +++ b/test/e2e/specs/editor/blocks/navigation.spec.js @@ -276,7 +276,7 @@ test.describe( 'Navigation block', () => { await pageUtils.pressKeys( 'ArrowDown' ); // remove the child link - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); const submenuBlock2 = editor.canvas.getByRole( 'document', { name: 'Block: Submenu', @@ -494,7 +494,7 @@ test.describe( 'Navigation block', () => { await pageUtils.pressKeys( 'ArrowDown', { times: 4 } ); await navigation.checkLabelFocus( 'wordpress.org' ); // Delete the nav link - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); // Focus moved to sibling await navigation.checkLabelFocus( 'Dog' ); // Add a link back so we can delete the first submenu link and see if focus returns to the parent submenu item @@ -507,15 +507,15 @@ test.describe( 'Navigation block', () => { await pageUtils.pressKeys( 'ArrowUp', { times: 2 } ); await navigation.checkLabelFocus( 'Dog' ); // Delete the nav link - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await pageUtils.pressKeys( 'ArrowDown' ); // Focus moved to parent submenu item await navigation.checkLabelFocus( 'example.com' ); // Deleting this should move focus to the sibling item - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await navigation.checkLabelFocus( 'Cat' ); // Deleting with no more siblings should focus the navigation block again - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await expect( navBlock ).toBeFocused(); // Wait until the nav block inserter is visible before we continue. await expect( navBlockInserter ).toBeVisible(); diff --git a/test/e2e/specs/editor/various/block-deletion.spec.js b/test/e2e/specs/editor/various/block-deletion.spec.js index 9346412c46bcb..5e4ead97c986f 100644 --- a/test/e2e/specs/editor/various/block-deletion.spec.js +++ b/test/e2e/specs/editor/various/block-deletion.spec.js @@ -134,7 +134,7 @@ test.describe( 'Block deletion', () => { ).toBeFocused(); // Remove the current paragraph via dedicated keyboard shortcut. - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); // Ensure the last block was removed. await expect.poll( editor.getBlocks ).toMatchObject( [ diff --git a/test/e2e/specs/editor/various/block-locking.spec.js b/test/e2e/specs/editor/various/block-locking.spec.js index a8895d282fb95..b31fc9e2cd140 100644 --- a/test/e2e/specs/editor/various/block-locking.spec.js +++ b/test/e2e/specs/editor/various/block-locking.spec.js @@ -133,7 +133,7 @@ test.describe( 'Block Locking', () => { ).toBeVisible(); await paragraph.click(); - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await expect.poll( editor.getBlocks ).toMatchObject( [ { diff --git a/test/e2e/specs/editor/various/list-view.spec.js b/test/e2e/specs/editor/various/list-view.spec.js index 531faa8bea049..6bf1689a40049 100644 --- a/test/e2e/specs/editor/various/list-view.spec.js +++ b/test/e2e/specs/editor/various/list-view.spec.js @@ -809,8 +809,8 @@ test.describe( 'List View', () => { // Delete remaining blocks. // Keyboard shortcut should also work. - await pageUtils.pressKeys( 'access+z' ); - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await expect .poll( listViewUtils.getBlocksWithA11yAttributes, @@ -842,7 +842,7 @@ test.describe( 'List View', () => { { name: 'core/heading', selected: false }, ] ); - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await expect .poll( listViewUtils.getBlocksWithA11yAttributes, @@ -865,7 +865,7 @@ test.describe( 'List View', () => { .getByRole( 'gridcell', { name: 'File' } ) .getByRole( 'link' ) .focus(); - for ( const keys of [ 'Delete', 'Backspace', 'access+z' ] ) { + for ( const keys of [ 'Delete', 'Backspace', 'shift+Backspace' ] ) { await pageUtils.pressKeys( keys ); await expect .poll( @@ -1133,7 +1133,7 @@ test.describe( 'List View', () => { optionsForFileMenu, 'Pressing Space should also open the menu dropdown' ).toBeVisible(); - await pageUtils.pressKeys( 'access+z' ); // Keyboard shortcut for Delete. + await pageUtils.pressKeys( 'shift+Backspace' ); // Keyboard shortcut for Delete. await expect .poll( listViewUtils.getBlocksWithA11yAttributes, @@ -1153,7 +1153,7 @@ test.describe( 'List View', () => { optionsForFileMenu.getByRole( 'menuitem', { name: 'Delete' } ), 'The delete menu item should be hidden for locked blocks' ).toBeHidden(); - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await expect .poll( listViewUtils.getBlocksWithA11yAttributes, diff --git a/test/e2e/specs/editor/various/template-resolution.spec.js b/test/e2e/specs/editor/various/template-resolution.spec.js index 13503ddaf23d5..82e336feff733 100644 --- a/test/e2e/specs/editor/various/template-resolution.spec.js +++ b/test/e2e/specs/editor/various/template-resolution.spec.js @@ -55,12 +55,15 @@ test.describe( 'Template resolution', () => { status: 'publish', } ); await admin.editPost( newPage.id ); + await page.locator( 'role=button[name="Block Inserter"i]' ).click(); await editor.openDocumentSettingsSidebar(); await expect( page.getByRole( 'button', { name: 'Template options' } ) ).toHaveText( 'Single Entries' ); await updateSiteSettings( { requestUtils, pageId: newPage.id } ); await page.reload(); + await page.locator( 'role=button[name="Block Inserter"i]' ).click(); + await editor.openDocumentSettingsSidebar(); await expect( page.getByRole( 'button', { name: 'Template options' } ) ).toHaveText( 'Index' ); @@ -81,6 +84,7 @@ test.describe( 'Template resolution', () => { postType: 'page', canvas: 'edit', } ); + await page.locator( 'role=button[name="Block Inserter"i]' ).click(); await editor.openDocumentSettingsSidebar(); await expect( page.getByRole( 'button', { name: 'Template options' } ) diff --git a/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-1-chromium.png b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-1-chromium.png new file mode 100644 index 0000000000000..4bc0f7a6b1dd7 Binary files /dev/null and b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-1-chromium.png differ diff --git a/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-2-chromium.png b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-2-chromium.png new file mode 100644 index 0000000000000..7339cccdb78f2 Binary files /dev/null and b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-2-chromium.png differ diff --git a/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-3-chromium.png b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-3-chromium.png new file mode 100644 index 0000000000000..97943030eb1e8 Binary files /dev/null and b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-3-chromium.png differ diff --git a/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-4-chromium.png b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-4-chromium.png new file mode 100644 index 0000000000000..b7c455784e8a4 Binary files /dev/null and b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-4-chromium.png differ diff --git a/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-5-chromium.png b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-5-chromium.png new file mode 100644 index 0000000000000..b7c455784e8a4 Binary files /dev/null and b/test/e2e/specs/interactivity/__snapshots__/Router-styles-should-support-relative-URLs-in-referenced-style-sheets-5-chromium.png differ diff --git a/test/e2e/specs/interactivity/fixtures/index.ts b/test/e2e/specs/interactivity/fixtures/index.ts index 607221ffb1ec4..08a72d20ef5ff 100644 --- a/test/e2e/specs/interactivity/fixtures/index.ts +++ b/test/e2e/specs/interactivity/fixtures/index.ts @@ -18,8 +18,8 @@ export const test = base.extend< Fixtures >( { async ( { requestUtils }, use ) => { await use( new InteractivityUtils( { requestUtils } ) ); }, - // @ts-ignore: The required type is 'test', but can be 'worker' too. See + // This is a hack, 'worker' is a valid value but the type is wrong. // https://playwright.dev/docs/test-fixtures#worker-scoped-fixtures - { scope: 'worker' }, + { scope: 'worker' as 'test' }, ], } ); diff --git a/test/e2e/specs/interactivity/fixtures/interactivity-utils.ts b/test/e2e/specs/interactivity/fixtures/interactivity-utils.ts index fd850a6e39fae..74436673f10b7 100644 --- a/test/e2e/specs/interactivity/fixtures/interactivity-utils.ts +++ b/test/e2e/specs/interactivity/fixtures/interactivity-utils.ts @@ -6,6 +6,30 @@ import type { RequestUtils } from '@wordpress/e2e-test-utils-playwright'; type AddPostWithBlockOptions = { alias?: string; attributes?: Record< string, any >; + innerBlocks?: Block[]; +}; + +type Block = [ + type: string, + attributes?: Record< string, any >, + innerBlocks?: Block[], +]; + +const generateBlockMarkup = ( [ + type, + attributes, + innerBlocks, +]: Block ): string => { + const typeAndAttributes = attributes + ? `${ type } ${ JSON.stringify( attributes ) }` + : type; + + if ( ! innerBlocks ) { + return `<!-- wp:${ typeAndAttributes } /-->`; + } + return `<!-- wp:${ typeAndAttributes } -->${ innerBlocks + .map( generateBlockMarkup ) + .join( '' ) }<!--/ wp:${ type } -->`; }; export default class InteractivityUtils { @@ -40,7 +64,7 @@ export default class InteractivityUtils { async addPostWithBlock( name: string, - { attributes, alias }: AddPostWithBlockOptions = {} + { attributes, alias, innerBlocks }: AddPostWithBlockOptions = {} ) { const block = attributes ? `${ name } ${ JSON.stringify( attributes ) }` @@ -50,8 +74,14 @@ export default class InteractivityUtils { alias = block; } + const content = generateBlockMarkup( [ + name, + attributes, + innerBlocks, + ] ); + const payload = { - content: `<!-- wp:${ block } /-->`, + content, status: 'publish' as 'publish', date_gmt: '2023-01-01T00:00:00', title: alias, diff --git a/test/e2e/specs/interactivity/router-styles.spec.ts b/test/e2e/specs/interactivity/router-styles.spec.ts new file mode 100644 index 0000000000000..7bc575af37816 --- /dev/null +++ b/test/e2e/specs/interactivity/router-styles.spec.ts @@ -0,0 +1,232 @@ +/** + * Internal dependencies + */ +import { test, expect } from './fixtures'; + +const COLOR_RED = 'rgb(255, 0, 0)'; +const COLOR_GREEN = 'rgb(0, 255, 0)'; +const COLOR_BLUE = 'rgb(0, 0, 255)'; +const COLOR_WRAPPER = 'rgb(160, 12, 60)'; + +test.describe( 'Router styles', () => { + test.beforeAll( async ( { interactivityUtils: utils } ) => { + await utils.activatePlugins(); + const red = await utils.addPostWithBlock( + 'test/router-styles-wrapper', + { + alias: 'red', + innerBlocks: [ [ 'test/router-styles-red' ] ], + } + ); + const green = await utils.addPostWithBlock( + 'test/router-styles-wrapper', + { + alias: 'green', + innerBlocks: [ [ 'test/router-styles-green' ] ], + } + ); + const blue = await utils.addPostWithBlock( + 'test/router-styles-wrapper', + { + alias: 'blue', + innerBlocks: [ [ 'test/router-styles-blue' ] ], + } + ); + + const all = await utils.addPostWithBlock( + 'test/router-styles-wrapper', + { + alias: 'all', + innerBlocks: [ + [ 'test/router-styles-red' ], + [ 'test/router-styles-green' ], + [ 'test/router-styles-blue' ], + ], + } + ); + + await utils.addPostWithBlock( 'test/router-styles-wrapper', { + alias: 'none', + attributes: { links: { red, green, blue, all } }, + } ); + } ); + + test.beforeEach( async ( { page, interactivityUtils: utils } ) => { + await page.goto( utils.getLink( 'none' ) ); + } ); + + test.afterAll( async ( { interactivityUtils: utils } ) => { + await utils.deactivatePlugins(); + await utils.deleteAllPosts(); + } ); + + test( 'should add and remove styles from style tags', async ( { + page, + } ) => { + const csn = page.getByTestId( 'client-side navigation' ); + const red = page.getByTestId( 'red' ); + const green = page.getByTestId( 'green' ); + const blue = page.getByTestId( 'blue' ); + const all = page.getByTestId( 'all' ); + + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_WRAPPER ); + + await page.getByTestId( 'link red' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_RED ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_RED ); + + await page.getByTestId( 'link green' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_GREEN ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_GREEN ); + + await page.getByTestId( 'link blue' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_BLUE ); + await expect( all ).toHaveCSS( 'color', COLOR_BLUE ); + + await page.getByTestId( 'link all' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_RED ); + await expect( green ).toHaveCSS( 'color', COLOR_GREEN ); + await expect( blue ).toHaveCSS( 'color', COLOR_BLUE ); + await expect( all ).toHaveCSS( 'color', COLOR_BLUE ); + } ); + + test( 'should add and remove styles from referenced style sheets', async ( { + page, + } ) => { + const csn = page.getByTestId( 'client-side navigation' ); + const red = page.getByTestId( 'red-from-link' ); + const green = page.getByTestId( 'green-from-link' ); + const blue = page.getByTestId( 'blue-from-link' ); + const all = page.getByTestId( 'all-from-link' ); + + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_WRAPPER ); + + await page.getByTestId( 'link red' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_RED ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_RED ); + + await page.getByTestId( 'link green' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_GREEN ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_GREEN ); + + await page.getByTestId( 'link blue' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_BLUE ); + await expect( all ).toHaveCSS( 'color', COLOR_BLUE ); + + await page.getByTestId( 'link all' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_RED ); + await expect( green ).toHaveCSS( 'color', COLOR_GREEN ); + await expect( blue ).toHaveCSS( 'color', COLOR_BLUE ); + await expect( all ).toHaveCSS( 'color', COLOR_BLUE ); + } ); + + test( 'should support relative URLs in referenced style sheets', async ( { + page, + } ) => { + const csn = page.getByTestId( 'client-side navigation' ); + const background = page.getByTestId( 'background-from-link' ); + + await expect( background ).toHaveScreenshot(); + + await page.getByTestId( 'link red' ).click(); + + await expect( csn ).toBeVisible(); + await expect( background ).toHaveScreenshot(); + + await page.getByTestId( 'link green' ).click(); + + await expect( csn ).toBeVisible(); + await expect( background ).toHaveScreenshot(); + + await page.getByTestId( 'link blue' ).click(); + + await expect( csn ).toBeVisible(); + await expect( background ).toHaveScreenshot(); + + await page.getByTestId( 'link all' ).click(); + + await expect( csn ).toBeVisible(); + await expect( background ).toHaveScreenshot(); + } ); + + test( 'should update style tags with modified content', async ( { + page, + } ) => { + const csn = page.getByTestId( 'client-side navigation' ); + const red = page.getByTestId( 'red-from-inline' ); + const green = page.getByTestId( 'green-from-inline' ); + const blue = page.getByTestId( 'blue-from-inline' ); + const all = page.getByTestId( 'all-from-inline' ); + + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_WRAPPER ); + + await page.getByTestId( 'link red' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_RED ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_RED ); + + await page.getByTestId( 'link green' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_GREEN ); + await expect( blue ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( all ).toHaveCSS( 'color', COLOR_GREEN ); + + await page.getByTestId( 'link blue' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( green ).toHaveCSS( 'color', COLOR_WRAPPER ); + await expect( blue ).toHaveCSS( 'color', COLOR_BLUE ); + await expect( all ).toHaveCSS( 'color', COLOR_BLUE ); + + await page.getByTestId( 'link all' ).click(); + + await expect( csn ).toBeVisible(); + await expect( red ).toHaveCSS( 'color', COLOR_RED ); + await expect( green ).toHaveCSS( 'color', COLOR_GREEN ); + await expect( blue ).toHaveCSS( 'color', COLOR_BLUE ); + await expect( all ).toHaveCSS( 'color', COLOR_BLUE ); + } ); +} ); diff --git a/test/e2e/specs/site-editor/block-style-variations.spec.js b/test/e2e/specs/site-editor/block-style-variations.spec.js index 03fc5398f4a0a..1fa8972d34d6c 100644 --- a/test/e2e/specs/site-editor/block-style-variations.spec.js +++ b/test/e2e/specs/site-editor/block-style-variations.spec.js @@ -317,9 +317,7 @@ async function draftNewPage( page ) { // Create a Group block with 2 nested Group blocks. async function addPageContent( editor, page ) { - const inserterButton = page.locator( - 'role=button[name="Block Inserter"i]' - ); + const inserterButton = page.locator( 'role=tab[name="Blocks"i]' ); await inserterButton.click(); await page.type( 'role=searchbox[name="Search"i]', 'Group' ); await page.click( diff --git a/test/e2e/specs/site-editor/page-list.spec.js b/test/e2e/specs/site-editor/page-list.spec.js index fa9cb86cd1d62..120ded6a2b6d0 100644 --- a/test/e2e/specs/site-editor/page-list.spec.js +++ b/test/e2e/specs/site-editor/page-list.spec.js @@ -2,19 +2,27 @@ * WordPress dependencies */ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); +/** + * External dependencies + */ +const path = require( 'path' ); + +const createPages = async ( requestUtils ) => { + await requestUtils.createPage( { + title: 'Privacy Policy', + status: 'publish', + } ); + await requestUtils.createPage( { + title: 'Sample Page', + status: 'publish', + } ); +}; test.describe( 'Page List', () => { test.beforeAll( async ( { requestUtils } ) => { // Activate a theme with permissions to access the site editor. await requestUtils.activateTheme( 'emptytheme' ); - await requestUtils.createPage( { - title: 'Privacy Policy', - status: 'publish', - } ); - await requestUtils.createPage( { - title: 'Sample Page', - status: 'publish', - } ); + await createPages( requestUtils ); } ); test.afterAll( async ( { requestUtils } ) => { @@ -53,4 +61,356 @@ test.describe( 'Page List', () => { page.getByRole( 'searchbox', { name: 'Search' } ) ).toHaveValue( 'Privacy' ); } ); + + test.describe( 'Quick Edit Mode', () => { + const fields = { + featuredImage: { + performEdit: async ( page ) => { + const placeholder = page.getByRole( 'button', { + name: 'Choose an image…', + } ); + await placeholder.click(); + const mediaLibrary = page.getByRole( 'dialog' ); + const TEST_IMAGE_FILE_PATH = path.resolve( + __dirname, + '../../assets/10x10_e2e_test_image_z9T8jK.png' + ); + + const fileChooserPromise = + page.waitForEvent( 'filechooser' ); + await mediaLibrary.getByText( 'Select files' ).click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles( TEST_IMAGE_FILE_PATH ); + await mediaLibrary + .locator( '.media-frame-toolbar' ) + .waitFor( { + state: 'hidden', + } ); + + await mediaLibrary + .getByRole( 'button', { name: 'Select', exact: true } ) + .click(); + }, + assertInitialState: async ( page ) => { + const el = page.getByText( 'Choose an image…' ); + const placeholder = page.getByRole( 'button', { + name: 'Choose an image…', + } ); + await expect( el ).toBeVisible(); + await expect( placeholder ).toBeVisible(); + }, + assertEditedState: async ( page ) => { + const placeholder = page.getByRole( 'button', { + name: 'Choose an image…', + } ); + await expect( placeholder ).toBeHidden(); + const img = page.locator( + '.fields-controls__featured-image-image' + ); + await expect( img ).toBeVisible(); + }, + }, + statusVisibility: { + performEdit: async ( page ) => { + const statusAndVisibility = page.getByLabel( + 'Status & Visibility' + ); + await statusAndVisibility.click(); + const options = [ + 'Published', + 'Draft', + 'Pending Review', + 'Private', + ]; + + for ( const option of options ) { + await page + .getByRole( 'radio', { name: option } ) + .click(); + await expect( statusAndVisibility ).toContainText( + option + ); + + if ( option !== 'Private' ) { + await page + .getByRole( 'checkbox', { + name: 'Password protected', + } ) + .check(); + } + } + }, + assertInitialState: async ( page ) => { + const statusAndVisibility = page.getByLabel( + 'Status & Visibility' + ); + await expect( statusAndVisibility ).toContainText( + 'Published' + ); + }, + assertEditedState: async ( page ) => { + const statusAndVisibility = page.getByLabel( + 'Status & Visibility' + ); + await expect( statusAndVisibility ).toContainText( + 'Private' + ); + }, + }, + author: { + assertInitialState: async ( page ) => { + const author = page.getByLabel( 'Author' ); + await expect( author ).toContainText( 'admin' ); + }, + performEdit: async ( page ) => { + const author = page.getByLabel( 'Author' ); + await author.click(); + const selectElement = page.locator( + 'select:has(option[value="1"])' + ); + await selectElement.selectOption( { value: '1' } ); + }, + assertEditedState: async () => {}, + }, + date: { + assertInitialState: async ( page ) => { + const dateEl = page.getByLabel( 'Edit Date' ); + const date = new Date(); + const yy = String( date.getFullYear() ); + + await expect( dateEl ).toContainText( yy ); + }, + performEdit: async ( page ) => { + const dateEl = page.getByLabel( 'Edit Date' ); + await dateEl.click(); + const date = new Date(); + const yy = Number( date.getFullYear() ); + const yyEl = page.locator( + `input[type="number"][value="${ yy }"]` + ); + + await yyEl.focus(); + await page.keyboard.press( 'ArrowUp' ); + }, + assertEditedState: async ( page ) => { + const date = new Date(); + const yy = Number( date.getFullYear() ); + const dateEl = page.getByLabel( 'Edit Date' ); + await expect( dateEl ).toContainText( String( yy + 1 ) ); + }, + }, + slug: { + assertInitialState: async ( page ) => { + const slug = page.getByLabel( 'Edit Slug' ); + await expect( slug ).toContainText( 'privacy-policy' ); + }, + performEdit: async ( page ) => { + const slug = page.getByLabel( 'Edit Slug' ); + await slug.click(); + await expect( + page.getByRole( 'link', { + name: 'http://localhost:8889/?', + } ) + ).toBeVisible(); + }, + assertEditedState: async () => {}, + }, + parent: { + assertInitialState: async ( page ) => { + const parent = page.getByLabel( 'Edit Parent' ); + await expect( parent ).toContainText( 'None' ); + }, + performEdit: async ( page ) => { + const parent = page.getByLabel( 'Edit Parent' ); + await parent.click(); + await page + .getByLabel( 'Parent', { exact: true } ) + .fill( 'Sample' ); + + await page + .getByRole( 'option', { name: 'Sample Page' } ) + .click(); + }, + assertEditedState: async ( page ) => { + const parent = page.getByLabel( 'Edit Parent' ); + await expect( parent ).toContainText( 'Sample Page' ); + }, + }, + // TODO: Wrap up this test once https://github.com/WordPress/gutenberg/issues/68173 is fixed + // template: { + // assertInitialState: async ( page ) => { + // const template = page.getByRole( 'button', { + // name: 'Single Entries', + // } ); + // await expect( template ).toContainText( 'Single Entries' ); + // }, + // edit: async ( page ) => { + // const template = page.getByRole( 'button', { + // name: 'Single Entries', + // } ); + // await template.click(); + // await page + // .getByRole( 'menuitem', { name: 'Swap template' } ) + // .click(); + // }, + // assertEditedState: async ( page ) => { + // + // }, + // }, + discussion: { + assertInitialState: async ( page ) => { + const discussion = page.getByLabel( 'Edit Discussion' ); + await expect( discussion ).toContainText( 'Closed' ); + }, + performEdit: async ( page ) => { + const discussion = page.getByLabel( 'Edit Discussion' ); + await discussion.click(); + await page + .getByLabel( 'Open', { + exact: true, + } ) + .check(); + }, + assertEditedState: async ( page ) => { + const discussion = page.getByLabel( 'Edit Discussion' ); + await expect( discussion ).toContainText( 'Open' ); + }, + }, + }; + + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.setGutenbergExperiments( [ + 'gutenberg-quick-edit-dataviews', + ] ); + } ); + + test.beforeEach( async ( { admin, page } ) => { + await admin.visitSiteEditor(); + await page.getByRole( 'button', { name: 'Pages' } ).click(); + await page.getByRole( 'button', { name: 'Layout' } ).click(); + await page.getByRole( 'menuitemradio', { name: 'Table' } ).click(); + const privacyPolicyCheckbox = page.getByRole( 'checkbox', { + name: 'Select Item: Privacy Policy', + } ); + + await privacyPolicyCheckbox.check(); + + await page.getByRole( 'button', { name: 'Details' } ).click(); + } ); + + Object.entries( fields ).forEach( + ( [ + key, + { performEdit, assertInitialState, assertEditedState }, + ] ) => { + // Asserts are done in the individual functions + // eslint-disable-next-line playwright/expect-expect + test( `should initialize, edit, and update ${ key } field correctly`, async ( { + page, + } ) => { + await assertInitialState( page ); + await performEdit( page ); + await assertEditedState( page ); + } ); + } + ); + + test( 'should save multiple field changes and update Data Views UI', async ( { + page, + requestUtils, + } ) => { + const selectedItem = page.locator( '.is-selected' ); + const imagePlaceholder = selectedItem.locator( + '.fields-controls__featured-image-placeholder' + ); + const status = selectedItem.getByRole( 'cell', { + name: 'Published', + } ); + await expect( status ).toBeVisible(); + + const { featuredImage, statusVisibility } = fields; + await statusVisibility.performEdit( page ); + await featuredImage.performEdit( page ); + // Ensure that no dropdown is open + await page.getByRole( 'button', { name: 'Close' } ).click(); + const saveButton = page.getByLabel( 'Review 1 change…' ); + await saveButton.click(); + await page.getByRole( 'button', { name: 'Save' } ).click(); + const updatedStatus = selectedItem.getByRole( 'cell', { + name: 'Private', + } ); + await expect( imagePlaceholder ).toBeHidden(); + await expect( updatedStatus ).toBeVisible(); + + // Reset the page to its original state + await requestUtils.deleteAllPages(); + await createPages( requestUtils ); + } ); + + // TODO: Wrap up this test once https://github.com/WordPress/gutenberg/pull/67584 is merged + // test( 'should update pages according to the changes', async ( { + // page, + // } ) => { + // const samplePage = page.getByRole( 'checkbox', { + // name: 'Select Item: Sample Page', + // } ); + + // await samplePage.check(); + + // const table = page.getByRole( 'table' ); + + // const selectedItems = table.locator( '.is-selected', { + // strict: false, + // } ); + + // expect( await selectedItems.all() ).toHaveLength( 2 ); + + // const imagePlaceholders = selectedItems.locator( + // '.fields-controls__featured-image-placeholder', + // { strict: false } + // ); + + // for ( const imagePlaceholder of await imagePlaceholders.all() ) { + // await expect( imagePlaceholder ).toBeVisible(); + // } + + // const statuses = selectedItems.getByRole( 'cell', { + // name: 'Public', + // } ); + + // for ( const status of await statuses.all() ) { + // await expect( status ).toBeVisible(); + // } + + // const { featuredImage, statusVisibility } = fields; + // await statusVisibility.edit( page ); + // await featuredImage.edit( page ); + // // Ensure that no dropdown is open + // await page.getByRole( 'button', { name: 'Close' } ).click(); + // const saveButton = page.getByLabel( 'Review 1 change…' ); + // await saveButton.click(); + // await page.getByRole( 'button', { name: 'Save' } ).click(); + // const updatedStatus = selectedItems.getByRole( + // 'cell', + // { + // name: 'Private', + // }, + // { + // strict: false, + // } + // ); + + // for ( const imagePlaceholder of await imagePlaceholders.all() ) { + // await expect( imagePlaceholder ).toBeHidden(); + // } + + // for ( const status of await updatedStatus.all() ) { + // await expect( status ).toBeVisible(); + // } + // } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.setGutenbergExperiments( [] ); + } ); + } ); } ); diff --git a/test/e2e/specs/site-editor/pages.spec.js b/test/e2e/specs/site-editor/pages.spec.js index 4817651bac8f9..37b164e85a597 100644 --- a/test/e2e/specs/site-editor/pages.spec.js +++ b/test/e2e/specs/site-editor/pages.spec.js @@ -272,6 +272,7 @@ test.describe( 'Pages', () => { // Create new page that has the default template so as to swap it. await draftNewPage( page ); + await page.locator( 'role=button[name="Block Inserter"i]' ).click(); await editor.openDocumentSettingsSidebar(); const templateOptionsButton = page .getByRole( 'region', { name: 'Editor settings' } ) @@ -294,6 +295,7 @@ test.describe( 'Pages', () => { } ); // Now reset, and apply the default template back. + await editor.openDocumentSettingsSidebar(); await templateOptionsButton.click(); const resetButton = page .getByRole( 'menu', { name: 'Template options' } ) @@ -308,6 +310,7 @@ test.describe( 'Pages', () => { editor, } ) => { await draftNewPage( page ); + await page.locator( 'role=button[name="Block Inserter"i]' ).click(); await editor.openDocumentSettingsSidebar(); const templateOptionsButton = page .getByRole( 'region', { name: 'Editor settings' } ) diff --git a/test/e2e/specs/site-editor/template-part.spec.js b/test/e2e/specs/site-editor/template-part.spec.js index d88273574bc4b..9d5c0ca05b0d9 100644 --- a/test/e2e/specs/site-editor/template-part.spec.js +++ b/test/e2e/specs/site-editor/template-part.spec.js @@ -375,7 +375,7 @@ test.describe( 'Template Part', () => { await editor.selectBlocks( siteTitle ); // Remove the default site title block. - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); // Insert a group block with a Site Title block inside. await editor.insertBlock( { diff --git a/test/e2e/specs/widgets/editing-widgets.spec.js b/test/e2e/specs/widgets/editing-widgets.spec.js index 019e07fe87daa..f4d160f8a36db 100644 --- a/test/e2e/specs/widgets/editing-widgets.spec.js +++ b/test/e2e/specs/widgets/editing-widgets.spec.js @@ -573,7 +573,7 @@ test.describe( 'Widgets screen', () => { .getByRole( 'document', { name: 'Block: Paragraph' } ) .filter( { hasText: 'Second Paragraph' } ) .focus(); - await pageUtils.pressKeys( 'access+z' ); + await pageUtils.pressKeys( 'shift+Backspace' ); await widgetsScreen.saveWidgets(); await expect.poll( widgetsScreen.getWidgetAreaBlocks ).toMatchObject( { diff --git a/test/e2e/tsconfig.json b/test/e2e/tsconfig.json index 28d349fc19bef..080d514f6f363 100644 --- a/test/e2e/tsconfig.json +++ b/test/e2e/tsconfig.json @@ -2,11 +2,11 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { + "checkJs": false, "noEmit": true, - "emitDeclarationOnly": false, - "allowJs": true, - "checkJs": false + "rootDir": ".", + "types": [ "node" ] }, - "include": [ "**/*" ], + "include": [ "." ], "exclude": [] } diff --git a/test/integration/fixtures/blocks/core__button__deprecated-v10.serialized.html b/test/integration/fixtures/blocks/core__button__deprecated-v10.serialized.html index e4c7b89c79461..0bebe131629f2 100644 --- a/test/integration/fixtures/blocks/core__button__deprecated-v10.serialized.html +++ b/test/integration/fixtures/blocks/core__button__deprecated-v10.serialized.html @@ -1,3 +1,3 @@ <!-- wp:button {"fontFamily":"cambria-georgia"} --> -<div class="wp-block-button has-cambria-georgia-font-family"><a class="wp-block-button__link wp-element-button">My button</a></div> +<div class="wp-block-button"><a class="wp-block-button__link has-cambria-georgia-font-family wp-element-button">My button</a></div> <!-- /wp:button --> diff --git a/test/integration/fixtures/blocks/core__button__deprecated-v12.html b/test/integration/fixtures/blocks/core__button__deprecated-v12.html new file mode 100644 index 0000000000000..b62b6f0020569 --- /dev/null +++ b/test/integration/fixtures/blocks/core__button__deprecated-v12.html @@ -0,0 +1,15 @@ +<!-- wp:button {"fontSize":"xx-large"} --> +<div class="wp-block-button has-custom-font-size has-xx-large-font-size"><a class="wp-block-button__link wp-element-button">My button 1</a></div> +<!-- /wp:button --> + +<!-- wp:button {"style":{"typography":{"fontStyle":"normal","fontWeight":"800"}}} --> +<div class="wp-block-button" style="font-style:normal;font-weight:800"><a class="wp-block-button__link wp-element-button">My button 2</a></div> +<!-- /wp:button --> + +<!-- wp:button {"style":{"typography":{"letterSpacing":"39px"}}} --> +<div class="wp-block-button" style="letter-spacing:39px"><a class="wp-block-button__link wp-element-button">My button 3</a></div> +<!-- /wp:button --> + +<!-- wp:button {"tagName":"button","style":{"typography":{"letterSpacing":"39px"}}} --> +<div class="wp-block-button" style="letter-spacing:39px"><button type="button" class="wp-block-button__link wp-element-button">My button 4</button></div> +<!-- /wp:button --> diff --git a/test/integration/fixtures/blocks/core__button__deprecated-v12.json b/test/integration/fixtures/blocks/core__button__deprecated-v12.json new file mode 100644 index 0000000000000..2c204623dc252 --- /dev/null +++ b/test/integration/fixtures/blocks/core__button__deprecated-v12.json @@ -0,0 +1,59 @@ +[ + { + "name": "core/button", + "isValid": true, + "attributes": { + "tagName": "a", + "type": "button", + "text": "My button 1", + "fontSize": "xx-large" + }, + "innerBlocks": [] + }, + { + "name": "core/button", + "isValid": true, + "attributes": { + "tagName": "a", + "type": "button", + "text": "My button 2", + "style": { + "typography": { + "fontStyle": "normal", + "fontWeight": "800" + } + } + }, + "innerBlocks": [] + }, + { + "name": "core/button", + "isValid": true, + "attributes": { + "tagName": "a", + "type": "button", + "text": "My button 3", + "style": { + "typography": { + "letterSpacing": "39px" + } + } + }, + "innerBlocks": [] + }, + { + "name": "core/button", + "isValid": true, + "attributes": { + "tagName": "button", + "type": "button", + "text": "My button 4", + "style": { + "typography": { + "letterSpacing": "39px" + } + } + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__button__deprecated-v12.parsed.json b/test/integration/fixtures/blocks/core__button__deprecated-v12.parsed.json new file mode 100644 index 0000000000000..d631bc600e49a --- /dev/null +++ b/test/integration/fixtures/blocks/core__button__deprecated-v12.parsed.json @@ -0,0 +1,81 @@ +[ + { + "blockName": "core/button", + "attrs": { + "fontSize": "xx-large" + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-button has-custom-font-size has-xx-large-font-size\"><a class=\"wp-block-button__link wp-element-button\">My button 1</a></div>\n", + "innerContent": [ + "\n<div class=\"wp-block-button has-custom-font-size has-xx-large-font-size\"><a class=\"wp-block-button__link wp-element-button\">My button 1</a></div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n\n", + "innerContent": [ "\n\n" ] + }, + { + "blockName": "core/button", + "attrs": { + "style": { + "typography": { + "fontStyle": "normal", + "fontWeight": "800" + } + } + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-button\" style=\"font-style:normal;font-weight:800\"><a class=\"wp-block-button__link wp-element-button\">My button 2</a></div>\n", + "innerContent": [ + "\n<div class=\"wp-block-button\" style=\"font-style:normal;font-weight:800\"><a class=\"wp-block-button__link wp-element-button\">My button 2</a></div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n\n", + "innerContent": [ "\n\n" ] + }, + { + "blockName": "core/button", + "attrs": { + "style": { + "typography": { + "letterSpacing": "39px" + } + } + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-button\" style=\"letter-spacing:39px\"><a class=\"wp-block-button__link wp-element-button\">My button 3</a></div>\n", + "innerContent": [ + "\n<div class=\"wp-block-button\" style=\"letter-spacing:39px\"><a class=\"wp-block-button__link wp-element-button\">My button 3</a></div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n\n", + "innerContent": [ "\n\n" ] + }, + { + "blockName": "core/button", + "attrs": { + "tagName": "button", + "style": { + "typography": { + "letterSpacing": "39px" + } + } + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-button\" style=\"letter-spacing:39px\"><button type=\"button\" class=\"wp-block-button__link wp-element-button\">My button 4</button></div>\n", + "innerContent": [ + "\n<div class=\"wp-block-button\" style=\"letter-spacing:39px\"><button type=\"button\" class=\"wp-block-button__link wp-element-button\">My button 4</button></div>\n" + ] + } +] diff --git a/test/integration/fixtures/blocks/core__button__deprecated-v12.serialized.html b/test/integration/fixtures/blocks/core__button__deprecated-v12.serialized.html new file mode 100644 index 0000000000000..8de25b59343b3 --- /dev/null +++ b/test/integration/fixtures/blocks/core__button__deprecated-v12.serialized.html @@ -0,0 +1,15 @@ +<!-- wp:button {"fontSize":"xx-large"} --> +<div class="wp-block-button"><a class="wp-block-button__link has-xx-large-font-size has-custom-font-size wp-element-button">My button 1</a></div> +<!-- /wp:button --> + +<!-- wp:button {"style":{"typography":{"fontStyle":"normal","fontWeight":"800"}}} --> +<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" style="font-style:normal;font-weight:800">My button 2</a></div> +<!-- /wp:button --> + +<!-- wp:button {"style":{"typography":{"letterSpacing":"39px"}}} --> +<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" style="letter-spacing:39px">My button 3</a></div> +<!-- /wp:button --> + +<!-- wp:button {"tagName":"button","style":{"typography":{"letterSpacing":"39px"}}} --> +<div class="wp-block-button"><button type="button" class="wp-block-button__link wp-element-button" style="letter-spacing:39px">My button 4</button></div> +<!-- /wp:button --> diff --git a/test/performance/playwright.config.ts b/test/performance/playwright.config.ts index fafca3a589122..75e87c4d2d0f0 100644 --- a/test/performance/playwright.config.ts +++ b/test/performance/playwright.config.ts @@ -8,7 +8,7 @@ import { defineConfig } from '@playwright/test'; /** * WordPress dependencies */ -const baseConfig = require( '@wordpress/scripts/config/playwright.config' ); +import baseConfig from '@wordpress/scripts/config/playwright.config.js'; process.env.ASSETS_PATH = path.join( __dirname, 'assets' ); diff --git a/test/performance/tsconfig.json b/test/performance/tsconfig.json index 28d349fc19bef..080d514f6f363 100644 --- a/test/performance/tsconfig.json +++ b/test/performance/tsconfig.json @@ -2,11 +2,11 @@ "$schema": "https://json.schemastore.org/tsconfig.json", "extends": "../../tsconfig.base.json", "compilerOptions": { + "checkJs": false, "noEmit": true, - "emitDeclarationOnly": false, - "allowJs": true, - "checkJs": false + "rootDir": ".", + "types": [ "node" ] }, - "include": [ "**/*" ], + "include": [ "." ], "exclude": [] } diff --git a/test/storybook-playwright/storybook/main.js b/test/storybook-playwright/storybook/main.js index b80833ca725f9..f68f586f47720 100644 --- a/test/storybook-playwright/storybook/main.js +++ b/test/storybook-playwright/storybook/main.js @@ -5,7 +5,10 @@ const baseConfig = require( '../../../storybook/main' ); const config = { ...baseConfig, - addons: [ '@storybook/addon-toolbars' ], + addons: [ + '@storybook/addon-toolbars', + '@storybook/addon-webpack5-compiler-babel', + ], docs: undefined, staticDirs: undefined, stories: [ diff --git a/test/unit/config/global-mocks.js b/test/unit/config/global-mocks.js index 8db2c180fadf3..ce64f03b514be 100644 --- a/test/unit/config/global-mocks.js +++ b/test/unit/config/global-mocks.js @@ -3,7 +3,6 @@ */ import { TextDecoder, TextEncoder } from 'node:util'; import { Blob as BlobPolyfill, File as FilePolyfill } from 'node:buffer'; -import 'core-js/stable/structured-clone'; jest.mock( '@wordpress/compose', () => { return { @@ -50,6 +49,3 @@ if ( ! global.TextEncoder ) { // Override jsdom built-ins with native node implementation. global.Blob = BlobPolyfill; global.File = FilePolyfill; - -// Polyfill structuredClone for jsdom. -global.structuredClone = structuredClone; diff --git a/tools/webpack/blocks.js b/tools/webpack/blocks.js index c05318d5b060f..0bf72c58ba568 100644 --- a/tools/webpack/blocks.js +++ b/tools/webpack/blocks.js @@ -8,7 +8,7 @@ const { realpathSync } = require( 'fs' ); /** * WordPress dependencies */ -const { PhpFilePathsPlugin } = require( '@wordpress/scripts/utils' ); +const PhpFilePathsPlugin = require( '@wordpress/scripts/plugins/php-file-paths-plugin' ); /** * Internal dependencies diff --git a/tsconfig.base.json b/tsconfig.base.json index a766eedaeddca..38c6ac761aab6 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -31,8 +31,12 @@ "resolveJsonModule": true, "typeRoots": [ "./typings", "./node_modules/@types" ], - "types": [] + "types": [], + + "rootDir": "${configDir}/src", + "declarationDir": "${configDir}/build-types" }, + "include": [ "${configDir}/src" ], "exclude": [ "**/*.android.js", "**/*.ios.js", diff --git a/tsconfig.json b/tsconfig.json index 93d0bd976dd00..d6bbcb27f0adb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,7 @@ { "path": "packages/dom" }, { "path": "packages/dom-ready" }, { "path": "packages/e2e-test-utils-playwright" }, + { "path": "packages/edit-site" }, { "path": "packages/editor" }, { "path": "packages/element" }, { "path": "packages/escape-html" }, @@ -59,7 +60,9 @@ { "path": "packages/url" }, { "path": "packages/vips" }, { "path": "packages/warning" }, - { "path": "packages/wordcount" } + { "path": "packages/wordcount" }, + { "path": "test/e2e" }, + { "path": "test/performance" } ], "files": [] }