Commit f6617bc0c96 for woocommerce
commit f6617bc0c967a4f14a9e18b5dbee20f61a9d4c93
Author: Daniel Mallory <daniel.mallory@automattic.com>
Date: Mon Jun 1 15:10:16 2026 +0100
Add modern settings SDK opt-in path (#64387)
* feat: add modern settings SDK opt-in path
* feat: migrate products settings to modern SDK
* feat: expose modern settings public API
* feat: style modern settings sections
* feat: align modern settings nav styling
* feat: add rich groups and compound settings SDK
* refactor: remove compound settings SDK support
* Add modern settings SDK extension lifecycle APIs
* Expose modern settings shell schema metadata
* Refine modern settings shell header
* Move modern settings navigation into SDK shell
* Use breadcrumbs for modern settings shell navigation
* Restore primary settings nav outside modern shell
* Wrap modern settings primary tabs
* Use default settings primary nav styling
* Align modern section tabs with content
* Restore modern settings content spacing
* Use Admin UI Page for modern settings shell
* Fix modern settings SDK review issues
* Fix modern settings form save prompt
* Address modern settings review hardening
* Add modern settings HTML rendering tests
* Rename modern settings SDK to settings UI SDK
* Address settings UI SDK review feedback
* Fix settings UI embed loading
* Fix settings UI CI lint issues
* Address settings UI PR review feedback
* Use esbuild for settings UI SDK builds
* Fix product gallery unit test expectation
* Isolate settings UI feature flag tests
diff --git a/docs/extensions/settings-and-config/registering-settings-ui-components.md b/docs/extensions/settings-and-config/registering-settings-ui-components.md
new file mode 100644
index 00000000000..c3243995539
--- /dev/null
+++ b/docs/extensions/settings-and-config/registering-settings-ui-components.md
@@ -0,0 +1,179 @@
+---
+post_title: Registering settings UI components
+sidebar_label: Settings UI components
+sidebar_position: 6
+---
+
+# Registering settings UI components
+
+Use custom components when a WooCommerce settings field needs plugin-specific React UI that cannot be represented by a native field type.
+
+For most fields, prefer the native renderer. Custom components are best for specialized selectors, previews, or validation flows.
+
+## PHP field metadata
+
+Declare a stable component name on the field:
+
+```php
+array(
+ 'id' => 'my_plugin_payment_methods',
+ 'title' => __( 'Payment methods', 'my-plugin' ),
+ 'type' => 'multiselect',
+ 'component' => 'my-plugin/payment-method-picker',
+ 'options' => array(
+ 'card' => __( 'Card', 'my-plugin' ),
+ 'bnpl' => __( 'Buy now, pay later', 'my-plugin' ),
+ ),
+)
+```
+
+The `component` value is a name, not a script handle. It lets the PHP schema say which renderer a field intends to use while JavaScript supplies the implementation.
+
+## Register JavaScript components
+
+Register components with `registerSettingsExtension()` from `@woocommerce/settings-ui-sdk`:
+
+```ts
+import { registerSettingsExtension } from '@woocommerce/settings-ui-sdk';
+import { PaymentMethodPicker } from './payment-method-picker';
+
+registerSettingsExtension( {
+ scope: {
+ page: 'my_plugin',
+ section: 'payments',
+ },
+ components: {
+ 'my-plugin/payment-method-picker': PaymentMethodPicker,
+ },
+} );
+```
+
+Registrations are scoped by settings page and, optionally, by section. This prevents one plugin from accidentally replacing another plugin's field behavior.
+
+## Component props
+
+Custom components receive stable field props:
+
+```ts
+type SettingsFieldComponentProps = {
+ field: {
+ id: string;
+ label: string;
+ type: string;
+ description?: string;
+ value?: string | number | boolean | string[] | null;
+ options?: Array< { label: string; value: string } >;
+ component?: string;
+ placeholder?: string;
+ disabled?: boolean;
+ customAttributes?: Record< string, string | number | boolean >;
+ };
+ value: string | number | boolean | string[] | null;
+ onChange: ( value: string | number | boolean | string[] | null ) => void;
+ context: {
+ page: string;
+ section?: string;
+ };
+};
+```
+
+Call `onChange()` with the next field value. The SDK handles hidden input serialization for the field's save adapter.
+
+## Example component
+
+```tsx
+import type { SettingsFieldComponentProps } from '@woocommerce/settings-ui-sdk';
+
+export const PaymentMethodPicker = ( {
+ field,
+ value,
+ onChange,
+}: SettingsFieldComponentProps ) => {
+ const selectedValues = Array.isArray( value ) ? value : [];
+
+ return (
+ <fieldset>
+ <legend>{ field.label }</legend>
+ { field.options?.map( ( option ) => {
+ const checked = selectedValues.includes( option.value );
+
+ return (
+ <label key={ option.value }>
+ <input
+ type="checkbox"
+ checked={ checked }
+ onChange={ () => {
+ onChange(
+ checked
+ ? selectedValues.filter(
+ ( item ) =>
+ item !== option.value
+ )
+ : [ ...selectedValues, option.value ]
+ );
+ } }
+ />
+ { option.label }
+ </label>
+ );
+ } ) }
+ </fieldset>
+ );
+};
+```
+
+## Field-specific overrides
+
+If a legacy field cannot add `component` metadata directly, register a field override by field id:
+
+```ts
+registerSettingsExtension( {
+ scope: {
+ page: 'my_plugin',
+ },
+ fieldOverrides: {
+ my_plugin_payment_methods: PaymentMethodPicker,
+ },
+} );
+```
+
+Field overrides are useful during migration, but component metadata is preferred because the intended renderer stays close to the PHP field schema.
+
+## Type renderers
+
+Use `typeRenderers` when every field of a type should share the same renderer within a page scope:
+
+```ts
+registerSettingsExtension( {
+ scope: {
+ page: 'my_plugin',
+ },
+ typeRenderers: {
+ my_plugin_color: ColorField,
+ },
+} );
+```
+
+Resolution order is:
+
+1. `field.component`
+2. `fieldOverrides[ field.id ]`
+3. `typeRenderers[ field.type ]`
+4. Native SDK field renderer
+
+## Enqueue the component script
+
+Register your script in WordPress and return its handle from the settings UI adapter:
+
+```php
+<?php
+use Automattic\WooCommerce\Admin\Settings\LegacySettingsPageAdapter;
+
+final class My_Plugin_Settings_UI_Page extends LegacySettingsPageAdapter {
+ public function get_script_handles( string $section ): array {
+ return array( 'my-plugin-settings-ui' );
+ }
+}
+```
+
+WooCommerce loads the SDK first, then your script, then mounts the settings app.
diff --git a/docs/extensions/settings-and-config/settings-ui-sdk.md b/docs/extensions/settings-and-config/settings-ui-sdk.md
new file mode 100644
index 00000000000..633f22e2896
--- /dev/null
+++ b/docs/extensions/settings-and-config/settings-ui-sdk.md
@@ -0,0 +1,212 @@
+---
+post_title: Settings UI SDK
+sidebar_label: Settings UI SDK
+sidebar_position: 5
+---
+
+# Settings UI SDK
+
+The settings UI SDK is an opt-in path for rendering WooCommerce settings pages with React while keeping the existing `WC_Settings_Page` registration and save flow.
+
+It is designed for extension authors who want to migrate incrementally. PHP still owns page registration, settings schema, permissions, script dependencies, and persistence. React owns field rendering and client-side interaction.
+
+## Status
+
+- The SDK is behind the `settings-ui` feature flag.
+- With the flag disabled, settings pages keep the legacy PHP renderer.
+- With the flag enabled, a settings page still has to opt in explicitly.
+- Saves use the existing WooCommerce settings form POST flow by default.
+- The public PHP API is available under `Automattic\WooCommerce\Admin\Settings`.
+
+## Enable the feature flag
+
+For local testing, enable the feature with a small mu-plugin:
+
+```php
+<?php
+add_filter(
+ 'woocommerce_admin_features',
+ static function ( array $features ): array {
+ $features[] = 'settings-ui';
+ return array_values( array_unique( $features ) );
+ }
+);
+```
+
+## Opt in a settings page
+
+A `WC_Settings_Page` subclass opts in by returning a settings UI adapter from `get_settings_ui_page()`.
+
+For pages that only need native fields, use `LegacySettingsPageAdapter`:
+
+```php
+<?php
+use Automattic\WooCommerce\Admin\Settings\LegacySettingsPageAdapter;
+use Automattic\WooCommerce\Admin\Settings\SettingsUIPageInterface;
+
+class My_Plugin_Settings_Page extends WC_Settings_Page {
+ public function __construct() {
+ $this->id = 'my_plugin';
+ $this->label = __( 'My plugin', 'my-plugin' );
+
+ parent::__construct();
+ }
+
+ public function get_settings_ui_page(): ?SettingsUIPageInterface {
+ return new LegacySettingsPageAdapter( $this );
+ }
+}
+```
+
+WooCommerce only uses the adapter when the `settings-ui` feature flag is enabled. Returning an adapter does not change the page while the feature flag is disabled.
+
+## Native field migration
+
+The legacy adapter converts the existing `get_settings()` array into a canonical schema for React. It supports common settings fields:
+
+- `text`
+- `password`
+- `email`
+- `url`
+- `tel`
+- `number`
+- `textarea`
+- `checkbox`
+- `select`
+- `radio`
+- `multiselect`
+- `multi_select_countries`
+- `single_select_country`
+- `single_select_page`
+- `info`
+
+Fields before the first `title` marker are placed into a default group automatically.
+
+The default save adapter is `form_post`, which serializes hidden inputs so `WC_Admin_Settings::save_fields()` continues to save the submitted values.
+
+## Custom component migration
+
+If a field needs a custom React UI, declare a component name in the PHP field schema:
+
+```php
+array(
+ 'id' => 'my_plugin_payment_methods',
+ 'title' => __( 'Payment methods', 'my-plugin' ),
+ 'type' => 'multiselect',
+ 'component' => 'my-plugin/payment-method-picker',
+ 'options' => array(
+ 'card' => __( 'Card', 'my-plugin' ),
+ 'bnpl' => __( 'Buy now, pay later', 'my-plugin' ),
+ ),
+)
+```
+
+Then register that component from JavaScript:
+
+```ts
+import { registerSettingsExtension } from '@woocommerce/settings-ui-sdk';
+import { PaymentMethodPicker } from './payment-method-picker';
+
+registerSettingsExtension( {
+ scope: {
+ page: 'my_plugin',
+ },
+ components: {
+ 'my-plugin/payment-method-picker': PaymentMethodPicker,
+ },
+} );
+```
+
+See [Registering settings UI components](./registering-settings-ui-components.md) for the full component contract.
+
+## Load extension scripts before mount
+
+Custom component scripts must load before the settings app mounts. Return their registered WordPress script handles from the adapter:
+
+```php
+<?php
+use Automattic\WooCommerce\Admin\Settings\LegacySettingsPageAdapter;
+
+final class My_Plugin_Settings_UI_Page extends LegacySettingsPageAdapter {
+ public function get_script_handles( string $section ): array {
+ return array( 'my-plugin-settings-ui' );
+ }
+}
+```
+
+The settings embed script depends on the SDK and these handles only for the opted-in page. Other settings pages do not load the SDK.
+
+## Save adapters
+
+The SDK supports two save adapters:
+
+| Adapter | Behavior |
+| ----------- | -------------------------------------------------------------------- |
+| `form_post` | Serializes hidden inputs for the existing WooCommerce settings form. |
+| `none` | Does not submit a value. Use for display-only fields. |
+
+The legacy adapter uses `form_post` by default. A field can override its save behavior:
+
+```php
+array(
+ 'id' => 'my_plugin_read_only_notice',
+ 'type' => 'info',
+ 'text' => __( 'This field is display only.', 'my-plugin' ),
+ 'save' => array(
+ 'adapter' => 'none',
+ ),
+)
+```
+
+## Rich group descriptions and actions
+
+Group title rows can include sanitized description markup and structured header actions. Use this for contextual links such as documentation or secondary actions that belong to the whole group, rather than creating a display-only custom field.
+
+```php
+array(
+ 'id' => 'my_plugin_checkout',
+ 'type' => 'title',
+ 'title' => __( 'Checkout experience', 'my-plugin' ),
+ 'desc' => sprintf(
+ /* translators: %s: documentation link */
+ __( 'Choose where customers can use express payment methods. %s', 'my-plugin' ),
+ '<a href="' . esc_url( 'https://example.com/docs' ) . '">' . esc_html__( 'Learn more', 'my-plugin' ) . '</a>'
+ ),
+ 'actions' => array(
+ array(
+ 'id' => 'manage',
+ 'label' => __( 'Manage locations', 'my-plugin' ),
+ 'href' => admin_url( 'admin.php?page=wc-settings&tab=shipping' ),
+ 'variant' => 'secondary',
+ ),
+ ),
+)
+```
+
+Descriptions are sanitized with `wp_kses_post()`. Actions are structured data with `id`, `label`, `href`, optional `variant`, optional `target`, and optional `rel`.
+
+## Reference migration in WooCommerce core
+
+The Products settings page is the Core reference migration. With `settings-ui` enabled, the Products tab renders through the settings UI SDK. With the flag disabled, it renders through the existing legacy settings UI.
+
+Use this page to verify the native migration path before testing a plugin-specific page such as WooPayments.
+
+## Testing an extension integration
+
+1. Enable the `settings-ui` feature flag.
+2. Return a settings UI adapter from your `WC_Settings_Page` subclass.
+3. Start with native fields and confirm the page renders and saves.
+4. Add `component` metadata only for fields that need custom UI.
+5. Register scoped JavaScript components with `registerSettingsExtension()`.
+6. Return custom script handles from `get_script_handles()` so they load before mount.
+7. Disable the feature flag and confirm the legacy page still renders unchanged.
+
+## Diagnostics
+
+In development, the SDK logs warnings for common integration issues:
+
+- The settings payload is missing.
+- The SDK script is missing for a settings UI mount.
+- A field declares a component that is not registered.
+- A field type is unsupported.
+- A field declares an unknown save adapter.
diff --git a/packages/js/dependency-extraction-webpack-plugin/assets/packages.js b/packages/js/dependency-extraction-webpack-plugin/assets/packages.js
index 6447993bc7c..385d53937dc 100644
--- a/packages/js/dependency-extraction-webpack-plugin/assets/packages.js
+++ b/packages/js/dependency-extraction-webpack-plugin/assets/packages.js
@@ -14,6 +14,7 @@ module.exports = [
'@woocommerce/experimental',
'@woocommerce/explat',
'@woocommerce/extend-cart-checkout-block',
+ '@woocommerce/settings-ui-sdk',
'@woocommerce/navigation',
'@woocommerce/notices',
'@woocommerce/number',
diff --git a/packages/js/dependency-extraction-webpack-plugin/changelog/add-settings-ui-sdk-package b/packages/js/dependency-extraction-webpack-plugin/changelog/add-settings-ui-sdk-package
new file mode 100644
index 00000000000..c18dd9b21f7
--- /dev/null
+++ b/packages/js/dependency-extraction-webpack-plugin/changelog/add-settings-ui-sdk-package
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Register the settings UI SDK package with dependency extraction.
diff --git a/packages/js/settings-ui-sdk/README.md b/packages/js/settings-ui-sdk/README.md
new file mode 100644
index 00000000000..b9ae76b38b8
--- /dev/null
+++ b/packages/js/settings-ui-sdk/README.md
@@ -0,0 +1,39 @@
+# Settings UI SDK
+
+React utilities for WooCommerce settings pages that opt in to the settings UI renderer.
+
+For the full integration guide, see [Settings UI SDK](../../../docs/extensions/settings-and-config/settings-ui-sdk.md).
+
+## Usage
+
+PHP settings pages continue to register through `WC_Settings_Page`. A page opts in by returning a settings UI adapter from `get_settings_ui_page()`. The adapter provides the canonical schema, save adapter, and any script handles that must load before the React app mounts.
+
+Custom JavaScript registers scoped components:
+
+```ts
+import { registerSettingsExtension } from '@woocommerce/settings-ui-sdk';
+import { PaymentMethodPicker } from './payment-method-picker';
+
+registerSettingsExtension( {
+ scope: {
+ page: 'my_plugin',
+ section: 'payments',
+ },
+ components: {
+ 'my-plugin/payment-method-picker': PaymentMethodPicker,
+ },
+} );
+```
+
+Field schemas can then reference the registered component:
+
+```php
+array(
+ 'id' => 'my_plugin_payment_methods',
+ 'type' => 'multiselect',
+ 'title' => 'Payment methods',
+ 'component' => 'my-plugin/payment-method-picker',
+)
+```
+
+Use native fields when possible and register custom components only for fields that need plugin-specific UI.
diff --git a/packages/js/settings-ui-sdk/build.mjs b/packages/js/settings-ui-sdk/build.mjs
new file mode 100644
index 00000000000..746bc61fc4b
--- /dev/null
+++ b/packages/js/settings-ui-sdk/build.mjs
@@ -0,0 +1,112 @@
+import { build, context } from 'esbuild';
+import { glob } from 'glob';
+import { rm } from 'node:fs/promises';
+import chokidar from 'chokidar';
+
+const watch = process.argv.includes( '--watch' );
+const format = process.argv.includes( '--cjs' ) ? 'cjs' : 'esm';
+const outdir = format === 'cjs' ? 'build' : 'build-module';
+
+const ENTRY_GLOB = 'src/**/*.{ts,tsx,js,jsx}';
+const ENTRY_IGNORE = [
+ '**/test/**',
+ '**/stories/**',
+ '**/*.test.{ts,tsx,js,jsx}',
+ '**/*.d.ts',
+];
+
+async function resolveEntryPoints() {
+ return glob( ENTRY_GLOB, { ignore: ENTRY_IGNORE } );
+}
+
+function makeOptions( entryPoints ) {
+ return {
+ entryPoints,
+ outdir,
+ outbase: 'src',
+ bundle: false,
+ format,
+ platform: 'neutral',
+ target: 'esnext',
+ loader: { '.js': 'jsx', '.jsx': 'jsx', '.ts': 'ts', '.tsx': 'tsx' },
+ jsx: 'transform',
+ jsxFactory: 'createElement',
+ jsxFragment: 'Fragment',
+ logLevel: 'warning',
+ sourcemap: false,
+ };
+}
+
+function summarize( result ) {
+ const errors = result.errors.length;
+ const warnings = result.warnings.length;
+ const parts = [];
+ if ( errors ) parts.push( `${ errors } error(s)` );
+ if ( warnings ) parts.push( `${ warnings } warning(s)` );
+ return parts.length ? ` — ${ parts.join( ', ' ) }` : '';
+}
+
+// Wrap a watch-mode step so a single failure (disk error, build crash, etc.)
+// doesn't take the watcher process down. Errors are surfaced; the loop survives.
+async function safe( label, fn ) {
+ try {
+ return await fn();
+ } catch ( error ) {
+ console.error( `[watch] ${ label } failed:`, error?.message ?? error );
+ return null;
+ }
+}
+
+await rm( outdir, { recursive: true, force: true } );
+
+if ( watch ) {
+ const startupT0 = Date.now();
+ let entryPoints = await resolveEntryPoints();
+ let ctx = await context( makeOptions( entryPoints ) );
+ const initial = await safe( 'startup build', () => ctx.rebuild() );
+ console.log( `[watch] ready in ${ Date.now() - startupT0 }ms — ${ entryPoints.length } entry point(s)${ initial ? summarize( initial ) : '' }` );
+
+ // esbuild's own watcher polls the filesystem, which can miss or delay
+ // changes (especially edits to files added after context creation).
+ // chokidar uses OS-level events (fsevents/inotify) and drives rebuilds
+ // directly: changes call ctx.rebuild() (preserves the AST cache),
+ // add/unlink trigger a debounced context restart (entry list changed).
+ let pending;
+ const pendingChanges = new Set();
+ const restart = ( path, kind ) => {
+ pendingChanges.add( `${ path } (${ kind })` );
+ clearTimeout( pending );
+ pending = setTimeout( () => safe( 'restart', async () => {
+ const changes = [ ...pendingChanges ];
+ pendingChanges.clear();
+ const preview = changes.slice( 0, 3 ).join( ', ' );
+ const suffix = changes.length > 3 ? `, +${ changes.length - 3 } more` : '';
+ console.log( `[watch] restarting (${ preview }${ suffix })` );
+ const t0 = Date.now();
+ await ctx.dispose();
+ await rm( outdir, { recursive: true, force: true } );
+ entryPoints = await resolveEntryPoints();
+ ctx = await context( makeOptions( entryPoints ) );
+ const result = await ctx.rebuild();
+ console.log( `[watch] rebuilt in ${ Date.now() - t0 }ms — ${ entryPoints.length } entry point(s)${ summarize( result ) }` );
+ } ), 200 );
+ };
+
+ chokidar
+ .watch( ENTRY_GLOB, { ignored: ENTRY_IGNORE, ignoreInitial: true } )
+ .on( 'add', ( path ) => restart( path, 'added' ) )
+ .on( 'unlink', ( path ) => restart( path, 'deleted' ) )
+ .on( 'change', async ( path ) => {
+ const t0 = Date.now();
+ const result = await safe( `rebuild ${ path }`, () => ctx.rebuild() );
+ if ( result ) {
+ console.log( `[watch] rebuilt ${ path } in ${ Date.now() - t0 }ms${ summarize( result ) }` );
+ }
+ } );
+} else {
+ const entryPoints = await resolveEntryPoints();
+ const t0 = Date.now();
+ console.log( `[build] ${ entryPoints.length } entry point(s)...` );
+ const result = await build( makeOptions( entryPoints ) );
+ console.log( `[build] done in ${ Date.now() - t0 }ms${ summarize( result ) }` );
+}
diff --git a/packages/js/settings-ui-sdk/changelog/add-settings-ui-sdk b/packages/js/settings-ui-sdk/changelog/add-settings-ui-sdk
new file mode 100644
index 00000000000..db0c5e2ffaf
--- /dev/null
+++ b/packages/js/settings-ui-sdk/changelog/add-settings-ui-sdk
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add the settings UI SDK package.
diff --git a/packages/js/settings-ui-sdk/jest.config.json b/packages/js/settings-ui-sdk/jest.config.json
new file mode 100644
index 00000000000..51e32138299
--- /dev/null
+++ b/packages/js/settings-ui-sdk/jest.config.json
@@ -0,0 +1,5 @@
+{
+ "rootDir": "./",
+ "roots": [ "<rootDir>/src" ],
+ "preset": "./node_modules/@woocommerce/internal-js-tests/jest-preset.js"
+}
diff --git a/packages/js/settings-ui-sdk/package.json b/packages/js/settings-ui-sdk/package.json
new file mode 100644
index 00000000000..01e7899ce93
--- /dev/null
+++ b/packages/js/settings-ui-sdk/package.json
@@ -0,0 +1,104 @@
+{
+ "name": "@woocommerce/settings-ui-sdk",
+ "version": "0.1.0",
+ "description": "React utilities for rendering extensible WooCommerce settings pages.",
+ "author": "Automattic",
+ "license": "GPL-2.0-or-later",
+ "keywords": [
+ "wordpress",
+ "woocommerce",
+ "settings"
+ ],
+ "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/settings-ui-sdk/README.md",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/woocommerce/woocommerce.git"
+ },
+ "bugs": {
+ "url": "https://github.com/woocommerce/woocommerce/issues"
+ },
+ "main": "build/index.js",
+ "module": "build-module/index.js",
+ "types": "build-types",
+ "react-native": "src/index",
+ "files": [
+ "build",
+ "build-module",
+ "build-types"
+ ],
+ "dependencies": {
+ "@woocommerce/sanitize": "workspace:*",
+ "@wordpress/admin-ui": "catalog:wp-min",
+ "@wordpress/components": "catalog:wp-min",
+ "@wordpress/element": "catalog:wp-min",
+ "@wordpress/i18n": "catalog:wp-min"
+ },
+ "devDependencies": {
+ "@babel/core": "7.25.7",
+ "@types/jest": "29.5.x",
+ "@types/react": "18.3.x",
+ "@woocommerce/eslint-plugin": "workspace:*",
+ "@woocommerce/internal-js-tests": "workspace:*",
+ "@woocommerce/internal-ts-config": "workspace:*",
+ "chokidar": "3.6.x",
+ "esbuild": "0.24.x",
+ "eslint": "^8.55.0",
+ "glob": "^10.3.10",
+ "jest": "29.5.x",
+ "jest-cli": "29.5.x",
+ "jest-environment-jsdom": "29.5.x",
+ "rimraf": "5.0.5",
+ "ts-jest": "29.1.x",
+ "typescript": "5.7.x"
+ },
+ "peerDependencies": {
+ "@types/react": "18.3.x",
+ "react": "18.3.x",
+ "react-dom": "18.3.x"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "scripts": {
+ "build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
+ "build:project": "pnpm --if-present '/^build:project:.*$/'",
+ "build:project:esm": "node build.mjs",
+ "build:publish:project": "pnpm --if-present '/^build:publish:project:.*$/'",
+ "build:publish:project:cjs": "node build.mjs --cjs",
+ "build:publish:project:types": "tsc --build --emitDeclarationOnly",
+ "build:publish:project:deps": "pnpm build",
+ "lint": "pnpm --if-present '/^lint:lang:.*$/'",
+ "lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
+ "lint:fix:lang:js": "eslint src --fix",
+ "lint:lang:js": "eslint src",
+ "lint:lang:types": "tsc --build --emitDeclarationOnly",
+ "prepack": "pnpm build:publish:project",
+ "test:js": "jest --config ./jest.config.json --passWithNoTests",
+ "watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
+ "watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+ "watch:build:project:esm": "node build.mjs --watch"
+ },
+ "config": {
+ "ci": {
+ "lint": {
+ "command": "lint",
+ "changes": "src/**/*.{js,jsx,ts,tsx}"
+ },
+ "tests": [
+ {
+ "name": "JavaScript",
+ "command": "test:js",
+ "changes": [
+ "jest.config.json",
+ "tsconfig.json",
+ "src/**/*.{js,jsx,ts,tsx}"
+ ],
+ "events": [
+ "pull_request",
+ "push"
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/packages/js/settings-ui-sdk/src/diagnostics.ts b/packages/js/settings-ui-sdk/src/diagnostics.ts
new file mode 100644
index 00000000000..d68a0fe8c94
--- /dev/null
+++ b/packages/js/settings-ui-sdk/src/diagnostics.ts
@@ -0,0 +1,41 @@
+declare const process:
+ | {
+ env: {
+ NODE_ENV?: string;
+ };
+ }
+ | undefined;
+
+const isDevelopment = () => {
+ if ( typeof process === 'undefined' ) {
+ return true;
+ }
+
+ return process.env.NODE_ENV !== 'production';
+};
+
+export const warn = ( message: string, context?: unknown ) => {
+ if ( ! isDevelopment() ) {
+ return;
+ }
+
+ if ( context ) {
+ // eslint-disable-next-line no-console
+ console.warn( `[WooCommerce settings UI] ${ message }`, context );
+ return;
+ }
+
+ // eslint-disable-next-line no-console
+ console.warn( `[WooCommerce settings UI] ${ message }` );
+};
+
+export const error = ( message: string, context?: unknown ) => {
+ if ( context ) {
+ // eslint-disable-next-line no-console
+ console.error( `[WooCommerce settings UI] ${ message }`, context );
+ return;
+ }
+
+ // eslint-disable-next-line no-console
+ console.error( `[WooCommerce settings UI] ${ message }` );
+};
diff --git a/packages/js/settings-ui-sdk/src/hidden-inputs.tsx b/packages/js/settings-ui-sdk/src/hidden-inputs.tsx
new file mode 100644
index 00000000000..3491aac16fa
--- /dev/null
+++ b/packages/js/settings-ui-sdk/src/hidden-inputs.tsx
@@ -0,0 +1,86 @@
+/**
+ * External dependencies
+ */
+import { createElement, Fragment } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { error } from './diagnostics';
+import type { SettingsUIField, SettingsValue } from './types';
+
+type HiddenInput = {
+ name: string;
+ value: string;
+};
+
+const getFieldName = ( field: SettingsUIField ) => field.save?.name || field.id;
+
+const getArrayFieldName = ( name: string ) =>
+ name.endsWith( '[]' ) ? name : `${ name }[]`;
+
+export const getHiddenInputs = (
+ field: SettingsUIField,
+ value: SettingsValue
+): HiddenInput[] => {
+ const adapter = field.save?.adapter || 'form_post';
+
+ if ( adapter === 'none' ) {
+ return [];
+ }
+
+ if ( adapter !== 'form_post' ) {
+ error( `Save adapter "${ adapter }" is not supported.`, { field } );
+ return [];
+ }
+
+ const name = getFieldName( field );
+
+ if ( field.type === 'checkbox' ) {
+ return [
+ {
+ name,
+ value:
+ value === true || value === 'yes' || value === '1'
+ ? 'yes'
+ : 'no',
+ },
+ ];
+ }
+
+ if ( field.type === 'array' ) {
+ return ( Array.isArray( value ) ? value : [] ).map( ( item ) => ( {
+ name: getArrayFieldName( name ),
+ value: String( item ),
+ } ) );
+ }
+
+ return [
+ {
+ name,
+ value:
+ value === null || typeof value === 'undefined'
+ ? ''
+ : String( value ),
+ },
+ ];
+};
+
+export const HiddenInputs = ( {
+ field,
+ value,
+}: {
+ field: SettingsUIField;
+ value: SettingsValue;
+} ) => (
+ <>
+ { getHiddenInputs( field, value ).map( ( input, index ) => (
+ <input
+ key={ `${ input.name }-${ index }` }
+ type="hidden"
+ name={ input.name }
+ value={ input.value }
+ />
+ ) ) }
+ </>
+);
diff --git a/packages/js/settings-ui-sdk/src/html.ts b/packages/js/settings-ui-sdk/src/html.ts
new file mode 100644
index 00000000000..5c915304b86
--- /dev/null
+++ b/packages/js/settings-ui-sdk/src/html.ts
@@ -0,0 +1,6 @@
+/**
+ * External dependencies
+ */
+import { sanitizeHTML } from '@woocommerce/sanitize';
+
+export const sanitizeSettingsHtml = ( html?: string ) => sanitizeHTML( html );
diff --git a/packages/js/settings-ui-sdk/src/index.ts b/packages/js/settings-ui-sdk/src/index.ts
new file mode 100644
index 00000000000..48e3815ee7f
--- /dev/null
+++ b/packages/js/settings-ui-sdk/src/index.ts
@@ -0,0 +1,37 @@
+export { SettingsUIErrorBoundary, SettingsUIPage } from './settings-ui-page';
+export { NativeSettingsField } from './native-fields';
+export { HiddenInputs, getHiddenInputs } from './hidden-inputs';
+export {
+ registerSettingsExtension,
+ resolveFieldComponent,
+ resolveFieldVisibilityPredicate,
+ resolveGroupVisibilityPredicate,
+ resolveRegionComponent,
+ resolveSaveHandler,
+} from './registry';
+export type {
+ SettingsUIField,
+ SettingsUIGroup,
+ SettingsUIGroupAction,
+ SettingsUIOption,
+ SettingsUIRegistry,
+ SettingsUISaveSchema,
+ SettingsUISaveStrategy,
+ SettingsUISchema,
+ SettingsUIShell,
+ SettingsUIShellBreadcrumb,
+ SettingsExtensionRegistration,
+ SettingsExtensionScope,
+ SettingsFieldComponent,
+ SettingsFieldComponentProps,
+ SettingsFieldContext,
+ SettingsRegionComponent,
+ SettingsRegionComponentProps,
+ SettingsSaveHandler,
+ SettingsSaveHandlerArgs,
+ SettingsSaveResult,
+ SettingsValue,
+ SettingsValues,
+ SettingsVisibilityPredicate,
+ SettingsVisibilityPredicateArgs,
+} from './types';
diff --git a/packages/js/settings-ui-sdk/src/native-fields.tsx b/packages/js/settings-ui-sdk/src/native-fields.tsx
new file mode 100644
index 00000000000..a317f2f86de
--- /dev/null
+++ b/packages/js/settings-ui-sdk/src/native-fields.tsx
@@ -0,0 +1,187 @@
+/**
+ * External dependencies
+ */
+import {
+ BaseControl,
+ CheckboxControl,
+ SelectControl,
+ TextControl,
+ TextareaControl,
+} from '@wordpress/components';
+import { createElement, RawHTML } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { warn } from './diagnostics';
+import { sanitizeSettingsHtml } from './html';
+import type { SettingsFieldComponentProps, SettingsValue } from './types';
+
+type TextInputType =
+ | 'text'
+ | 'password'
+ | 'datetime-local'
+ | 'date'
+ | 'time'
+ | 'email'
+ | 'url'
+ | 'tel'
+ | 'number';
+
+const textInputTypes: TextInputType[] = [
+ 'text',
+ 'password',
+ 'datetime-local',
+ 'date',
+ 'time',
+ 'email',
+ 'url',
+ 'tel',
+ 'number',
+];
+
+const toStringValue = ( value: SettingsValue ) =>
+ value === null || typeof value === 'undefined' ? '' : String( value );
+
+const isTextInputType = ( type: string ): type is TextInputType =>
+ textInputTypes.includes( type as TextInputType );
+
+const getHelp = ( description?: string ) =>
+ description ? (
+ <span
+ dangerouslySetInnerHTML={ {
+ __html: sanitizeSettingsHtml( description ),
+ } }
+ />
+ ) : undefined;
+
+export const NativeSettingsField = ( {
+ field,
+ value,
+ onChange,
+}: SettingsFieldComponentProps ) => {
+ if ( field.type === 'info' ) {
+ return (
+ <div className="wc-settings-ui__info" id={ field.id }>
+ <strong>{ field.label }</strong>
+ { field.description ? (
+ <RawHTML>
+ { sanitizeSettingsHtml( field.description ) }
+ </RawHTML>
+ ) : null }
+ </div>
+ );
+ }
+
+ if ( field.type === 'checkbox' ) {
+ return (
+ <CheckboxControl
+ className="wc-settings-ui__control"
+ label={ field.label }
+ help={ getHelp( field.description ) }
+ checked={ value === true || value === 'yes' || value === '1' }
+ disabled={ field.disabled }
+ onChange={ onChange }
+ __nextHasNoMarginBottom
+ />
+ );
+ }
+
+ if ( field.type === 'textarea' ) {
+ return (
+ <TextareaControl
+ className="wc-settings-ui__control"
+ label={ field.label }
+ help={ getHelp( field.description ) }
+ value={ toStringValue( value ) }
+ placeholder={ field.placeholder }
+ disabled={ field.disabled }
+ onChange={ onChange }
+ __nextHasNoMarginBottom
+ />
+ );
+ }
+
+ if ( field.type === 'select' || field.type === 'radio' ) {
+ return (
+ <SelectControl
+ className="wc-settings-ui__control"
+ label={ field.label }
+ help={ getHelp( field.description ) }
+ value={ toStringValue( value ) }
+ options={ field.options || [] }
+ disabled={ field.disabled }
+ onChange={ onChange }
+ __next40pxDefaultSize
+ __nextHasNoMarginBottom
+ />
+ );
+ }
+
+ if ( field.type === 'array' ) {
+ const selectedValues = Array.isArray( value ) ? value : [];
+
+ return (
+ <BaseControl
+ className="wc-settings-ui__control"
+ id={ field.id }
+ label={ field.label }
+ help={ getHelp( field.description ) }
+ __nextHasNoMarginBottom
+ >
+ <select
+ id={ field.id }
+ multiple
+ disabled={ field.disabled }
+ value={ selectedValues }
+ onChange={ ( event ) => {
+ onChange(
+ Array.from(
+ event.currentTarget.selectedOptions
+ ).map( ( option ) => option.value )
+ );
+ } }
+ >
+ { ( field.options || [] ).map( ( option ) => (
+ <option key={ option.value } value={ option.value }>
+ { option.label }
+ </option>
+ ) ) }
+ </select>
+ </BaseControl>
+ );
+ }
+
+ if ( isTextInputType( field.type ) ) {
+ return (
+ <TextControl
+ className="wc-settings-ui__control"
+ type={ field.type }
+ label={ field.label }
+ help={ getHelp( field.description ) }
+ value={ toStringValue( value ) }
+ placeholder={ field.placeholder }
+ disabled={ field.disabled }
+ onChange={ onChange }
+ __next40pxDefaultSize
+ __nextHasNoMarginBottom
+ { ...field.customAttributes }
+ />
+ );
+ }
+
+ warn( `Field type "${ field.type }" is not supported.`, { field } );
+
+ return (
+ <TextControl
+ className="wc-settings-ui__control"
+ label={ field.label }
+ help={ getHelp( field.description ) }
+ value={ toStringValue( value ) }
+ disabled={ field.disabled }
+ onChange={ onChange }
+ __next40pxDefaultSize
+ __nextHasNoMarginBottom
+ />
+ );
+};
diff --git a/packages/js/settings-ui-sdk/src/registry.ts b/packages/js/settings-ui-sdk/src/registry.ts
new file mode 100644
index 00000000000..f883b794401
--- /dev/null
+++ b/packages/js/settings-ui-sdk/src/registry.ts
@@ -0,0 +1,293 @@
+/**
+ * Internal dependencies
+ */
+import { warn } from './diagnostics';
+import type {
+ SettingsUIField,
+ SettingsExtensionRegistration,
+ SettingsFieldComponent,
+ SettingsFieldContext,
+ SettingsRegionComponent,
+ SettingsSaveHandler,
+ SettingsVisibilityPredicate,
+} from './types';
+
+const registrations: SettingsExtensionRegistration[] = [];
+
+const registrationMapKeys = [
+ 'components',
+ 'fieldOverrides',
+ 'typeRenderers',
+ 'fieldVisibility',
+ 'groupVisibility',
+ 'saveHandlers',
+ 'regions',
+] as const;
+
+type RegistrationMapKey = ( typeof registrationMapKeys )[ number ];
+
+const isPlainRecord = ( value: unknown ): value is Record< string, unknown > =>
+ typeof value === 'object' && value !== null && ! Array.isArray( value );
+
+const isValidRegistration = (
+ registration: unknown
+): registration is SettingsExtensionRegistration => {
+ if ( ! isPlainRecord( registration ) ) {
+ return false;
+ }
+
+ const scope = registration.scope;
+ if ( ! isPlainRecord( scope ) ) {
+ return false;
+ }
+
+ if ( typeof scope.page !== 'string' || scope.page.length === 0 ) {
+ return false;
+ }
+
+ if (
+ typeof scope.section !== 'undefined' &&
+ typeof scope.section !== 'string'
+ ) {
+ return false;
+ }
+
+ return registrationMapKeys.every( ( key ) => {
+ const value = registration[ key ];
+ return typeof value === 'undefined' || isPlainRecord( value );
+ } );
+};
+
+const scopeMatches = (
+ registration: SettingsExtensionRegistration,
+ context: SettingsFieldContext
+) => {
+ if ( registration.scope.page !== context.page ) {
+ return false;
+ }
+
+ return (
+ ! registration.scope.section ||
+ registration.scope.section === context.section
+ );
+};
+
+const getScopeKey = ( scope: SettingsExtensionRegistration[ 'scope' ] ) =>
+ `${ scope.page }::${ scope.section || 'default' }`;
+
+const hasDuplicateScopeAndKeys = (
+ registration: SettingsExtensionRegistration,
+ key: RegistrationMapKey
+) => {
+ const entries = registration[ key ];
+ if ( ! entries ) {
+ return;
+ }
+
+ const incomingKeys = Object.keys( entries );
+ if ( incomingKeys.length === 0 ) {
+ return;
+ }
+
+ const scopeKey = getScopeKey( registration.scope );
+ for ( const existing of registrations ) {
+ if ( getScopeKey( existing.scope ) !== scopeKey ) {
+ continue;
+ }
+
+ const existingEntries = existing[ key ];
+ if ( ! existingEntries ) {
+ continue;
+ }
+
+ if (
+ incomingKeys.some( ( entryKey ) =>
+ Object.prototype.hasOwnProperty.call(
+ existingEntries,
+ entryKey
+ )
+ )
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+export const registerSettingsExtension = (
+ registration: SettingsExtensionRegistration
+) => {
+ if ( ! isValidRegistration( registration ) ) {
+ warn( 'Invalid settings extension registration payload.', {
+ registration,
+ } );
+ return;
+ }
+
+ const hasDuplicateKeys = registrationMapKeys.some( ( key ) =>
+ hasDuplicateScopeAndKeys( registration, key )
+ );
+
+ if ( hasDuplicateKeys ) {
+ warn(
+ `Registration already exists for scope "${ getScopeKey(
+ registration.scope
+ ) }". Replacing the existing registration.`,
+ { registration }
+ );
+ for ( let i = registrations.length - 1; i >= 0; i-- ) {
+ if (
+ getScopeKey( registrations[ i ].scope ) ===
+ getScopeKey( registration.scope )
+ ) {
+ registrations.splice( i, 1 );
+ }
+ }
+ }
+
+ registrations.push( registration );
+};
+
+export const __resetRegistry = () => {
+ registrations.splice( 0 );
+};
+
+export const resolveFieldComponent = (
+ field: SettingsUIField,
+ context: SettingsFieldContext
+): SettingsFieldComponent | undefined => {
+ if ( field.component ) {
+ for ( let i = registrations.length - 1; i >= 0; i-- ) {
+ const registration = registrations[ i ];
+ if ( ! scopeMatches( registration, context ) ) {
+ continue;
+ }
+
+ const namedComponent = registration.components?.[ field.component ];
+ if ( namedComponent ) {
+ return namedComponent;
+ }
+ }
+ }
+
+ for ( let i = registrations.length - 1; i >= 0; i-- ) {
+ const registration = registrations[ i ];
+ if ( ! scopeMatches( registration, context ) ) {
+ continue;
+ }
+
+ const fieldOverride = registration.fieldOverrides?.[ field.id ];
+ if ( fieldOverride ) {
+ return fieldOverride;
+ }
+ }
+
+ for ( let i = registrations.length - 1; i >= 0; i-- ) {
+ const registration = registrations[ i ];
+ if ( ! scopeMatches( registration, context ) ) {
+ continue;
+ }
+
+ const typeRenderer = registration.typeRenderers?.[ field.type ];
+ if ( typeRenderer ) {
+ return typeRenderer;
+ }
+ }
+
+ if ( field.component ) {
+ warn( `Component "${ field.component }" is not registered.`, {
+ field,
+ context,
+ } );
+ }
+
+ return undefined;
+};
+
+export const resolveFieldVisibilityPredicate = (
+ fieldId: string,
+ context: SettingsFieldContext
+): SettingsVisibilityPredicate | undefined => {
+ for ( let i = registrations.length - 1; i >= 0; i-- ) {
+ const registration = registrations[ i ];
+ if ( ! scopeMatches( registration, context ) ) {
+ continue;
+ }
+
+ const predicate = registration.fieldVisibility?.[ fieldId ];
+ if ( predicate ) {
+ return predicate;
+ }
+ }
+
+ return undefined;
+};
+
+export const resolveGroupVisibilityPredicate = (
+ groupId: string,
+ context: SettingsFieldContext
+): SettingsVisibilityPredicate | undefined => {
+ for ( let i = registrations.length - 1; i >= 0; i-- ) {
+ const registration = registrations[ i ];
+ if ( ! scopeMatches( registration, context ) ) {
+ continue;
+ }
+
+ const predicate = registration.groupVisibility?.[ groupId ];
+ if ( predicate ) {
+ return predicate;
+ }
+ }
+
+ return undefined;
+};
+
+export const resolveSaveHandler = (
+ handler: string,
+ context: SettingsFieldContext
+): SettingsSaveHandler | undefined => {
+ for ( let i = registrations.length - 1; i >= 0; i-- ) {
+ const registration = registrations[ i ];
+ if ( ! scopeMatches( registration, context ) ) {
+ continue;
+ }
+
+ const saveHandler = registration.saveHandlers?.[ handler ];
+ if ( saveHandler ) {
+ return saveHandler;
+ }
+ }
+
+ warn( `Save handler "${ handler }" is not registered.`, { context } );
+ return undefined;
+};
+
+export const resolveRegionComponent = (
+ component: string,
+ context: SettingsFieldContext
+): SettingsRegionComponent | undefined => {
+ for ( let i = registrations.length - 1; i >= 0; i-- ) {
+ const registration = registrations[ i ];
+ if ( ! scopeMatches( registration, context ) ) {
+ continue;
+ }
+
+ const region = registration.regions?.[ component ];
+ if ( region ) {
+ return region;
+ }
+ }
+
+ warn( `Region component "${ component }" is not registered.`, {
+ context,
+ } );
+ return undefined;
+};
+
+if ( typeof window !== 'undefined' ) {
+ window.wcSettingsUI = {
+ ...( window.wcSettingsUI || {} ),
+ registerSettingsExtension,
+ };
+}
diff --git a/packages/js/settings-ui-sdk/src/settings-ui-page.tsx b/packages/js/settings-ui-sdk/src/settings-ui-page.tsx
new file mode 100644
index 00000000000..4a6fd5db543
--- /dev/null
+++ b/packages/js/settings-ui-sdk/src/settings-ui-page.tsx
@@ -0,0 +1,621 @@
+/**
+ * External dependencies
+ */
+import { Page } from '@wordpress/admin-ui';
+import { Button, Notice } from '@wordpress/components';
+import {
+ Component,
+ createElement,
+ RawHTML,
+ useCallback,
+ useEffect,
+ useMemo,
+ useState,
+} from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import type { ErrorInfo, ReactNode } from 'react';
+
+/**
+ * Internal dependencies
+ */
+import { HiddenInputs } from './hidden-inputs';
+import { error, warn } from './diagnostics';
+import { sanitizeSettingsHtml } from './html';
+import { NativeSettingsField } from './native-fields';
+import {
+ resolveFieldComponent,
+ resolveFieldVisibilityPredicate,
+ resolveGroupVisibilityPredicate,
+ resolveRegionComponent,
+ resolveSaveHandler,
+} from './registry';
+import type {
+ SettingsUIField,
+ SettingsUIGroup,
+ SettingsUISaveStrategy,
+ SettingsUISchema,
+ SettingsFieldContext,
+ SettingsValue,
+ SettingsValues,
+} from './types';
+
+type SaveNotice = {
+ status: 'success' | 'error';
+ message: string;
+};
+
+const getInitialValues = ( schema: SettingsUISchema ): SettingsValues => {
+ const values: SettingsValues = {};
+
+ Object.values( schema.groups ).forEach( ( group ) => {
+ group.fields.forEach( ( field ) => {
+ values[ field.id ] =
+ typeof field.value === 'undefined' ? '' : field.value;
+ } );
+ } );
+
+ return values;
+};
+
+const areValuesEqual = ( a: SettingsValue, b: SettingsValue ) => {
+ if ( Array.isArray( a ) || Array.isArray( b ) ) {
+ return (
+ Array.isArray( a ) &&
+ Array.isArray( b ) &&
+ a.length === b.length &&
+ a.every( ( value, index ) => value === b[ index ] )
+ );
+ }
+
+ return a === b;
+};
+
+const getChangedValues = (
+ values: SettingsValues,
+ initialValues: SettingsValues
+) => {
+ const changedValues: Partial< SettingsValues > = {};
+
+ Object.keys( values ).forEach( ( key ) => {
+ if ( ! areValuesEqual( values[ key ], initialValues[ key ] ) ) {
+ changedValues[ key ] = values[ key ];
+ }
+ } );
+
+ return changedValues;
+};
+
+const getFieldTypeClassName = ( type: string ) =>
+ `wc-settings-ui__field--${ type.replace( /[^a-z0-9_-]/gi, '-' ) }`;
+
+const getActionVariant = ( variant?: string ) =>
+ ( [ 'primary', 'secondary', 'tertiary', 'link' ].includes( variant || '' )
+ ? variant
+ : 'secondary' ) as 'primary' | 'secondary' | 'tertiary' | 'link';
+
+const getSaveStrategy = ( schema: SettingsUISchema ): SettingsUISaveStrategy =>
+ schema.save || { adapter: 'form_post' };
+
+const clearLegacyFormPrompt = () => {
+ window.onbeforeunload = null;
+};
+
+const GroupHeader = ( { group }: { group: SettingsUIGroup } ) => {
+ const hasHeaderContent =
+ group.title || group.description || ( group.actions || [] ).length > 0;
+
+ if ( ! hasHeaderContent ) {
+ return null;
+ }
+
+ return (
+ <div className="wc-settings-ui__group-header">
+ { group.title ? <h2>{ group.title }</h2> : null }
+ { group.description ? (
+ <div className="wc-settings-ui__group-description">
+ <RawHTML>
+ { sanitizeSettingsHtml( group.description ) }
+ </RawHTML>
+ </div>
+ ) : null }
+ { group.actions && group.actions.length > 0 ? (
+ <div className="wc-settings-ui__group-actions">
+ { group.actions.map( ( action ) => (
+ <Button
+ key={ action.id }
+ variant={ getActionVariant( action.variant ) }
+ href={ action.href }
+ target={ action.target }
+ rel={ action.rel }
+ >
+ { action.label }
+ </Button>
+ ) ) }
+ </div>
+ ) : null }
+ </div>
+ );
+};
+
+const valueMatchesVisibilityRule = (
+ value: SettingsValue,
+ expected: SettingsValue | SettingsValue[] | undefined
+) => {
+ const expectedValues = Array.isArray( expected )
+ ? expected
+ : [ expected ?? true ];
+
+ return expectedValues.some( ( expectedValue ) =>
+ areValuesEqual( value, expectedValue )
+ );
+};
+
+const getVisible = ( {
+ id,
+ kind,
+ field,
+ values,
+ initialValues,
+ context,
+ schema,
+}: {
+ id: string;
+ kind: 'field' | 'group';
+ field?: SettingsUIField;
+ values: SettingsValues;
+ initialValues: SettingsValues;
+ context: SettingsFieldContext;
+ schema: SettingsUISchema;
+} ) => {
+ const predicate =
+ kind === 'field'
+ ? resolveFieldVisibilityPredicate( id, context )
+ : resolveGroupVisibilityPredicate( id, context );
+
+ if ( predicate ) {
+ try {
+ return predicate( { values, initialValues, context, schema } );
+ } catch ( predicateError ) {
+ warn(
+ `Visibility predicate for ${ kind } "${ id }" failed. Rendering it visible.`,
+ { error: predicateError, context }
+ );
+ return true;
+ }
+ }
+
+ if ( field?.visibility ) {
+ return valueMatchesVisibilityRule(
+ values[ field.visibility.controller ],
+ field.visibility.value
+ );
+ }
+
+ return true;
+};
+
+const getAllFields = ( schema: SettingsUISchema ): SettingsUIField[] =>
+ Object.values( schema.groups ).flatMap( ( group ) => group.fields );
+
+type ErrorBoundaryProps = {
+ children: ReactNode;
+};
+
+type ErrorBoundaryState = {
+ hasError: boolean;
+};
+
+export class SettingsUIErrorBoundary extends Component<
+ ErrorBoundaryProps,
+ ErrorBoundaryState
+> {
+ state: ErrorBoundaryState = { hasError: false };
+
+ static getDerivedStateFromError(): ErrorBoundaryState {
+ return { hasError: true };
+ }
+
+ componentDidCatch( caughtError: Error, errorInfo: ErrorInfo ) {
+ error( 'Settings UI render failed.', {
+ error: caughtError,
+ errorInfo,
+ } );
+ }
+
+ render() {
+ if ( this.state.hasError ) {
+ return (
+ <Notice status="error" isDismissible={ false }>
+ { __(
+ 'Something went wrong while rendering this settings page. Reload the page with the settings UI feature disabled to use the classic settings screen.',
+ 'woocommerce'
+ ) }
+ </Notice>
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
+const ShellHeader = ( {
+ schema,
+ context,
+ values,
+ initialValues,
+ isDirty,
+ isSaving,
+ saveStrategy,
+ onSave,
+ children,
+}: {
+ schema: SettingsUISchema;
+ context: SettingsFieldContext;
+ values: SettingsValues;
+ initialValues: SettingsValues;
+ isDirty: boolean;
+ isSaving: boolean;
+ saveStrategy: SettingsUISaveStrategy;
+ onSave: () => void;
+ children: ReactNode;
+} ) => {
+ const shell = schema.shell || {};
+ const title = shell.title || schema.title;
+ const NavigationComponent = shell.navigationComponent
+ ? resolveRegionComponent( shell.navigationComponent, context )
+ : undefined;
+ const hasNavigation = Boolean(
+ ( shell.navigation && shell.navigation.length > 0 ) ||
+ ( shell.sectionNavigation && shell.sectionNavigation.length > 0 ) ||
+ NavigationComponent
+ );
+ const showSaveButton = saveStrategy.adapter !== 'none';
+ const saveButtonType =
+ saveStrategy.adapter === 'form_post' ? 'submit' : 'button';
+
+ const breadcrumbs =
+ shell.breadcrumbs && shell.breadcrumbs.length > 0 ? (
+ <nav
+ className="wc-settings-ui-shell__breadcrumbs"
+ aria-label={ __( 'Breadcrumbs', 'woocommerce' ) }
+ >
+ { shell.breadcrumbs.map( ( breadcrumb, index ) => (
+ <span
+ className="wc-settings-ui-shell__breadcrumb"
+ key={ `${ breadcrumb.label }-${ index }` }
+ >
+ { breadcrumb.href ? (
+ <a href={ breadcrumb.href }>{ breadcrumb.label }</a>
+ ) : (
+ <span>{ breadcrumb.label }</span>
+ ) }
+ </span>
+ ) ) }
+ </nav>
+ ) : undefined;
+
+ const actions = showSaveButton ? (
+ <Button
+ className="woocommerce-save-button"
+ variant="primary"
+ type={ saveButtonType }
+ name="save"
+ value={ __( 'Save changes', 'woocommerce' ) }
+ disabled={ ! isDirty || isSaving }
+ isBusy={ isSaving }
+ onClick={
+ saveStrategy.adapter === 'form_post'
+ ? clearLegacyFormPrompt
+ : onSave
+ }
+ >
+ { __( 'Save changes', 'woocommerce' ) }
+ </Button>
+ ) : undefined;
+
+ return (
+ <Page
+ className="wc-settings-ui-shell"
+ headingLevel={ 1 }
+ title={ title }
+ breadcrumbs={ breadcrumbs }
+ actions={ actions }
+ showSidebarToggle={ false }
+ >
+ { hasNavigation ? (
+ <div className="wc-settings-ui-shell__navigation">
+ { shell.navigation && shell.navigation.length > 0 ? (
+ <nav
+ className="wc-settings-ui-shell__tabs wc-settings-ui-shell__tabs--primary"
+ aria-label={ __( 'Settings pages', 'woocommerce' ) }
+ >
+ { shell.navigation.map( ( item ) => (
+ <a
+ className={
+ item.active
+ ? 'wc-settings-ui-shell__tab is-active'
+ : 'wc-settings-ui-shell__tab'
+ }
+ href={ item.href }
+ key={ item.id }
+ >
+ { item.label }
+ </a>
+ ) ) }
+ </nav>
+ ) : null }
+ { shell.sectionNavigation &&
+ shell.sectionNavigation.length > 0 ? (
+ <nav
+ className="wc-settings-ui-shell__tabs wc-settings-ui-shell__tabs--secondary"
+ aria-label={ __(
+ 'Settings sections',
+ 'woocommerce'
+ ) }
+ >
+ { shell.sectionNavigation.map( ( item ) => (
+ <a
+ className={
+ item.active
+ ? 'wc-settings-ui-shell__tab is-active'
+ : 'wc-settings-ui-shell__tab'
+ }
+ href={ item.href }
+ key={ item.id }
+ >
+ { item.label }
+ </a>
+ ) ) }
+ </nav>
+ ) : null }
+ { NavigationComponent ? (
+ <NavigationComponent
+ values={ values }
+ initialValues={ initialValues }
+ context={ context }
+ schema={ schema }
+ />
+ ) : null }
+ </div>
+ ) : null }
+ { children }
+ </Page>
+ );
+};
+
+export const SettingsUIPage = ( {
+ schema,
+ page,
+ section,
+}: {
+ schema: SettingsUISchema;
+ page?: string;
+ section?: string;
+} ) => {
+ const [ initialValues, setInitialValues ] = useState< SettingsValues >(
+ () => getInitialValues( schema )
+ );
+ const [ values, setValuesState ] = useState< SettingsValues >( () =>
+ getInitialValues( schema )
+ );
+ const [ isSaving, setIsSaving ] = useState( false );
+ const [ saveNotice, setSaveNotice ] = useState< SaveNotice | null >( null );
+ const context: SettingsFieldContext = useMemo(
+ () => ( {
+ page: page || schema.id,
+ section: section || schema.section,
+ } ),
+ [ page, schema.id, schema.section, section ]
+ );
+ const saveStrategy = getSaveStrategy( schema );
+ const changedValues = useMemo(
+ () => getChangedValues( values, initialValues ),
+ [ initialValues, values ]
+ );
+ const dirtyFields = useMemo(
+ () => Object.keys( changedValues ),
+ [ changedValues ]
+ );
+ const isDirty = dirtyFields.length > 0;
+
+ useEffect( () => {
+ const nextValues = getInitialValues( schema );
+ setInitialValues( nextValues );
+ setValuesState( nextValues );
+ setSaveNotice( null );
+ }, [ schema ] );
+
+ const setValue = useCallback(
+ ( fieldId: string, nextValue: SettingsValue ) => {
+ setValuesState( ( currentValues ) => ( {
+ ...currentValues,
+ [ fieldId ]: nextValue,
+ } ) );
+ },
+ []
+ );
+
+ const setValues = useCallback(
+ ( nextValues: Partial< SettingsValues > ) => {
+ setValuesState( ( currentValues ) => {
+ const mergedValues: SettingsValues = { ...currentValues };
+
+ Object.entries( nextValues ).forEach(
+ ( [ fieldId, value ] ) => {
+ if ( typeof value !== 'undefined' ) {
+ mergedValues[ fieldId ] = value;
+ }
+ }
+ );
+
+ return mergedValues;
+ } );
+ },
+ []
+ );
+
+ const handleCustomSave = useCallback( async () => {
+ if ( saveStrategy.adapter !== 'custom' ) {
+ return;
+ }
+
+ const handlerName =
+ 'handler' in saveStrategy ? saveStrategy.handler : undefined;
+ const handler = handlerName
+ ? resolveSaveHandler( handlerName, context )
+ : undefined;
+ if ( ! handler ) {
+ setSaveNotice( {
+ status: 'error',
+ message: __( 'Unable to save settings.', 'woocommerce' ),
+ } );
+ return;
+ }
+
+ setIsSaving( true );
+ setSaveNotice( null );
+
+ try {
+ const result = await handler( {
+ values,
+ initialValues,
+ changedValues,
+ dirtyFields,
+ context,
+ schema,
+ } );
+ const savedValues = result?.values || values;
+ setValuesState( savedValues );
+ setInitialValues( savedValues );
+ setSaveNotice( {
+ status: 'success',
+ message:
+ result?.notice ||
+ __( 'Settings saved successfully.', 'woocommerce' ),
+ } );
+ } catch ( saveError ) {
+ const message =
+ saveError instanceof Error && saveError.message
+ ? saveError.message
+ : __( 'Unable to save settings.', 'woocommerce' );
+ setSaveNotice( { status: 'error', message } );
+ } finally {
+ setIsSaving( false );
+ }
+ }, [
+ changedValues,
+ context,
+ dirtyFields,
+ initialValues,
+ saveStrategy,
+ schema,
+ values,
+ ] );
+
+ const visibleGroups = useMemo(
+ () =>
+ Object.values( schema.groups )
+ .filter( ( group ) =>
+ getVisible( {
+ id: group.id,
+ kind: 'group',
+ values,
+ initialValues,
+ context,
+ schema,
+ } )
+ )
+ .map( ( group ) => ( {
+ ...group,
+ fields: group.fields.filter( ( field ) =>
+ getVisible( {
+ id: field.id,
+ kind: 'field',
+ field,
+ values,
+ initialValues,
+ context,
+ schema,
+ } )
+ ),
+ } ) )
+ .filter( ( group ) => group.fields.length > 0 ),
+ [ context, initialValues, schema, values ]
+ );
+
+ const formPostFields =
+ saveStrategy.adapter === 'form_post' ? getAllFields( schema ) : [];
+
+ return (
+ <ShellHeader
+ schema={ schema }
+ context={ context }
+ values={ values }
+ initialValues={ initialValues }
+ isDirty={ isDirty }
+ isSaving={ isSaving }
+ saveStrategy={ saveStrategy }
+ onSave={ handleCustomSave }
+ >
+ { saveNotice ? (
+ <Notice
+ className="wc-settings-ui-shell__notice"
+ status={ saveNotice.status }
+ isDismissible
+ onRemove={ () => setSaveNotice( null ) }
+ >
+ { saveNotice.message }
+ </Notice>
+ ) : null }
+ <div className="wc-settings-ui">
+ { visibleGroups.map( ( group ) => (
+ <section className="wc-settings-ui__group" key={ group.id }>
+ <GroupHeader group={ group } />
+ <div className="wc-settings-ui__group-panel">
+ { group.fields.map( ( field ) => {
+ const FieldComponent =
+ resolveFieldComponent( field, context ) ||
+ NativeSettingsField;
+ const value = values[ field.id ];
+
+ return (
+ <div
+ className={ [
+ 'wc-settings-ui__field',
+ getFieldTypeClassName( field.type ),
+ ].join( ' ' ) }
+ key={ field.id }
+ >
+ <FieldComponent
+ field={ field }
+ value={ value }
+ context={ context }
+ values={ values }
+ initialValues={ initialValues }
+ setValue={ setValue }
+ setValues={ setValues }
+ onChange={ ( nextValue ) =>
+ setValue( field.id, nextValue )
+ }
+ />
+ </div>
+ );
+ } ) }
+ </div>
+ </section>
+ ) ) }
+ </div>
+ { formPostFields.length > 0 ? (
+ <div className="wc-settings-ui__hidden-inputs">
+ { formPostFields.map( ( field ) => (
+ <HiddenInputs
+ field={ field }
+ value={ values[ field.id ] }
+ key={ field.id }
+ />
+ ) ) }
+ </div>
+ ) : null }
+ </ShellHeader>
+ );
+};
diff --git a/packages/js/settings-ui-sdk/src/test/hidden-inputs.test.ts b/packages/js/settings-ui-sdk/src/test/hidden-inputs.test.ts
new file mode 100644
index 00000000000..a2b790f6f22
--- /dev/null
+++ b/packages/js/settings-ui-sdk/src/test/hidden-inputs.test.ts
@@ -0,0 +1,51 @@
+/**
+ * Internal dependencies
+ */
+import { getHiddenInputs } from '../hidden-inputs';
+
+describe( 'getHiddenInputs', () => {
+ it( 'serializes checkbox values for legacy form posts', () => {
+ expect(
+ getHiddenInputs(
+ {
+ id: 'enabled',
+ label: 'Enabled',
+ type: 'checkbox',
+ save: { adapter: 'form_post', name: 'enabled' },
+ },
+ true
+ )
+ ).toEqual( [ { name: 'enabled', value: 'yes' } ] );
+ } );
+
+ it( 'serializes array values with bracketed field names', () => {
+ expect(
+ getHiddenInputs(
+ {
+ id: 'methods',
+ label: 'Methods',
+ type: 'array',
+ save: { adapter: 'form_post', name: 'methods' },
+ },
+ [ 'card', 'link' ]
+ )
+ ).toEqual( [
+ { name: 'methods[]', value: 'card' },
+ { name: 'methods[]', value: 'link' },
+ ] );
+ } );
+
+ it( 'does not serialize fields using the none adapter', () => {
+ expect(
+ getHiddenInputs(
+ {
+ id: 'info',
+ label: 'Info',
+ type: 'info',
+ save: { adapter: 'none' },
+ },
+ ''
+ )
+ ).toEqual( [] );
+ } );
+} );
diff --git a/packages/js/settings-ui-sdk/src/test/html-rendering.test.tsx b/packages/js/settings-ui-sdk/src/test/html-rendering.test.tsx
new file mode 100644
index 00000000000..a37b0d16d72
--- /dev/null
+++ b/packages/js/settings-ui-sdk/src/test/html-rendering.test.tsx
@@ -0,0 +1,147 @@
+/**
+ * External dependencies
+ */
+import { createElement } from '@wordpress/element';
+import { act } from 'react';
+import { createRoot } from 'react-dom/client';
+import type { ReactNode } from 'react';
+
+jest.mock( '@wordpress/admin-ui', () => ( {
+ Page: ( { children }: { children: ReactNode } ) => <>{ children }</>,
+} ) );
+
+/**
+ * Internal dependencies
+ */
+import { SettingsUIPage } from '../settings-ui-page';
+import { NativeSettingsField } from '../native-fields';
+import type { SettingsUISchema } from '../types';
+
+globalThis.IS_REACT_ACT_ENVIRONMENT = true;
+
+const unsafeDescription =
+ '<strong>Safe</strong><script>alert("x")</script><img src=x onerror=alert(1)><a href="javascript:alert(1)" onclick="alert(1)">Link</a><iframe src="https://example.com"></iframe>';
+
+const renderElement = ( element: JSX.Element ) => {
+ const container = document.createElement( 'div' );
+ document.body.appendChild( container );
+ const root = createRoot( container );
+
+ act( () => {
+ root.render( element );
+ } );
+
+ return { container, root };
+};
+
+const expectUnsafeMarkupRemoved = ( container: HTMLElement ) => {
+ expect( container.querySelector( 'strong' )?.textContent ).toBe( 'Safe' );
+ expect( container.querySelector( 'script' ) ).toBeNull();
+ expect( container.querySelector( 'img' ) ).toBeNull();
+ expect( container.querySelector( 'iframe' ) ).toBeNull();
+ expect( container.innerHTML ).not.toContain( 'onerror' );
+ expect( container.innerHTML ).not.toContain( 'onclick' );
+ expect( container.innerHTML ).not.toContain( 'javascript:' );
+};
+
+describe( 'settings HTML rendering', () => {
+ it( 'sanitizes native field help before rendering', () => {
+ const { container, root } = renderElement(
+ <NativeSettingsField
+ field={ {
+ id: 'test_field',
+ label: 'Test field',
+ type: 'text',
+ description: unsafeDescription,
+ } }
+ value=""
+ onChange={ jest.fn() }
+ context={ { page: 'test' } }
+ values={ {} }
+ initialValues={ {} }
+ setValue={ jest.fn() }
+ setValues={ jest.fn() }
+ />
+ );
+
+ expectUnsafeMarkupRemoved( container );
+
+ act( () => root.unmount() );
+ container.remove();
+ } );
+
+ it( 'hides fields with unmet native visibility rules', () => {
+ const schema: SettingsUISchema = {
+ id: 'test-page',
+ title: 'Test page',
+ section: 'default',
+ save: { adapter: 'none' },
+ groups: {
+ general: {
+ id: 'general',
+ fields: [
+ {
+ id: 'controller',
+ label: 'Controller',
+ type: 'checkbox',
+ value: false,
+ },
+ {
+ id: 'dependent',
+ label: 'Dependent field',
+ type: 'text',
+ visibility: {
+ controller: 'controller',
+ value: true,
+ },
+ },
+ ],
+ },
+ },
+ };
+
+ const { container, root } = renderElement(
+ <SettingsUIPage schema={ schema } />
+ );
+
+ expect( container.textContent ).toContain( 'Controller' );
+ expect( container.textContent ).not.toContain( 'Dependent field' );
+
+ act( () => root.unmount() );
+ container.remove();
+ } );
+
+ it( 'sanitizes info fields and group descriptions before rendering', () => {
+ const schema: SettingsUISchema = {
+ id: 'test-page',
+ title: 'Test page',
+ section: 'default',
+ save: { adapter: 'none' },
+ groups: {
+ general: {
+ id: 'general',
+ title: 'General',
+ description: unsafeDescription,
+ fields: [
+ {
+ id: 'info_field',
+ label: 'Info field',
+ type: 'info',
+ description: unsafeDescription,
+ },
+ ],
+ },
+ },
+ };
+
+ const { container, root } = renderElement(
+ <SettingsUIPage schema={ schema } />
+ );
+
+ expect( container.textContent ).toContain( 'Info field' );
+ expectUnsafeMarkupRemoved( container );
+
+ act( () => root.unmount() );
+ container.remove();
+ } );
+} );
diff --git a/packages/js/settings-ui-sdk/src/test/html.test.ts b/packages/js/settings-ui-sdk/src/test/html.test.ts
new file mode 100644
index 00000000000..456f3ebe57b
--- /dev/null
+++ b/packages/js/settings-ui-sdk/src/test/html.test.ts
@@ -0,0 +1,21 @@
+/**
+ * Internal dependencies
+ */
+import { sanitizeSettingsHtml } from '../html';
+
+describe( 'sanitizeSettingsHtml', () => {
+ it( 'neutralizes unsafe markup in settings HTML', () => {
+ const sanitized = sanitizeSettingsHtml(
+ '<strong>Safe</strong><script>alert("x")</script><img src=x onerror=alert(1)><a href="javascript:alert(1)" onclick="alert(1)">Link</a><iframe src="https://example.com"></iframe>'
+ );
+
+ expect( sanitized ).toContain( '<strong>Safe</strong>' );
+ expect( sanitized ).toContain( '>Link</a>' );
+ expect( sanitized ).not.toContain( '<script' );
+ expect( sanitized ).not.toContain( '<img' );
+ expect( sanitized ).not.toContain( 'onerror' );
+ expect( sanitized ).not.toContain( 'onclick' );
+ expect( sanitized ).not.toContain( 'javascript:' );
+ expect( sanitized ).not.toContain( '<iframe' );
+ } );
+} );
diff --git a/packages/js/settings-ui-sdk/src/test/registry.test.ts b/packages/js/settings-ui-sdk/src/test/registry.test.ts
new file mode 100644
index 00000000000..b71d19b8c17
--- /dev/null
+++ b/packages/js/settings-ui-sdk/src/test/registry.test.ts
@@ -0,0 +1,206 @@
+/**
+ * Internal dependencies
+ */
+import {
+ __resetRegistry,
+ registerSettingsExtension,
+ resolveFieldComponent,
+ resolveFieldVisibilityPredicate,
+ resolveGroupVisibilityPredicate,
+ resolveRegionComponent,
+ resolveSaveHandler,
+} from '../registry';
+import type {
+ SettingsExtensionRegistration,
+ SettingsFieldComponent,
+ SettingsRegionComponent,
+ SettingsSaveHandler,
+ SettingsVisibilityPredicate,
+} from '../types';
+
+describe( 'settings extension registry', () => {
+ afterEach( () => {
+ __resetRegistry();
+ } );
+
+ it( 'resolves named field components within the matching scope', () => {
+ const component: SettingsFieldComponent = () => null;
+
+ registerSettingsExtension( {
+ scope: { page: 'registry-test', section: 'advanced' },
+ components: {
+ 'test/component': component,
+ },
+ } );
+
+ expect(
+ resolveFieldComponent(
+ {
+ id: 'field',
+ label: 'Field',
+ type: 'text',
+ component: 'test/component',
+ },
+ { page: 'registry-test', section: 'advanced' }
+ )
+ ).toBe( component );
+ } );
+
+ it( 'resolves field components by documented precedence before registration recency', () => {
+ const component: SettingsFieldComponent = () => null;
+ const fieldOverride: SettingsFieldComponent = () => null;
+ const typeRenderer: SettingsFieldComponent = () => null;
+
+ registerSettingsExtension( {
+ scope: { page: 'registry-precedence' },
+ components: {
+ 'test/component': component,
+ },
+ fieldOverrides: {
+ field: fieldOverride,
+ },
+ } );
+ registerSettingsExtension( {
+ scope: { page: 'registry-precedence' },
+ typeRenderers: {
+ text: typeRenderer,
+ },
+ } );
+
+ expect(
+ resolveFieldComponent(
+ {
+ id: 'field',
+ label: 'Field',
+ type: 'text',
+ component: 'test/component',
+ },
+ { page: 'registry-precedence' }
+ )
+ ).toBe( component );
+ expect(
+ resolveFieldComponent(
+ {
+ id: 'field',
+ label: 'Field',
+ type: 'text',
+ },
+ { page: 'registry-precedence' }
+ )
+ ).toBe( fieldOverride );
+ } );
+
+ it( 'ignores malformed registration payloads', () => {
+ const warnSpy = jest
+ .spyOn( console, 'warn' )
+ .mockImplementation( () => undefined );
+
+ expect( () =>
+ registerSettingsExtension( {
+ scope: { page: 'registry-invalid' },
+ components: [],
+ } as unknown as SettingsExtensionRegistration )
+ ).not.toThrow();
+ expect(
+ resolveFieldComponent(
+ {
+ id: 'field',
+ label: 'Field',
+ type: 'text',
+ component: '0',
+ },
+ { page: 'registry-invalid' }
+ )
+ ).toBeUndefined();
+ expect( warnSpy ).toHaveBeenCalledWith(
+ expect.stringContaining(
+ 'Invalid settings extension registration payload.'
+ ),
+ expect.any( Object )
+ );
+
+ warnSpy.mockRestore();
+ } );
+
+ it( 'ignores registrations outside the current page scope', () => {
+ const component: SettingsFieldComponent = () => null;
+
+ registerSettingsExtension( {
+ scope: { page: 'registry-test-other' },
+ typeRenderers: {
+ text: component,
+ },
+ } );
+
+ expect(
+ resolveFieldComponent(
+ {
+ id: 'field',
+ label: 'Field',
+ type: 'text',
+ },
+ { page: 'registry-test-missing' }
+ )
+ ).toBeUndefined();
+ } );
+
+ it( 'resolves visibility predicates by field and group scope', () => {
+ const fieldPredicate: SettingsVisibilityPredicate = () => true;
+ const groupPredicate: SettingsVisibilityPredicate = () => false;
+
+ registerSettingsExtension( {
+ scope: { page: 'registry-visibility', section: 'payments' },
+ fieldVisibility: {
+ field: fieldPredicate,
+ },
+ groupVisibility: {
+ group: groupPredicate,
+ },
+ } );
+
+ expect(
+ resolveFieldVisibilityPredicate( 'field', {
+ page: 'registry-visibility',
+ section: 'payments',
+ } )
+ ).toBe( fieldPredicate );
+ expect(
+ resolveGroupVisibilityPredicate( 'group', {
+ page: 'registry-visibility',
+ section: 'payments',
+ } )
+ ).toBe( groupPredicate );
+ expect(
+ resolveFieldVisibilityPredicate( 'field', {
+ page: 'registry-visibility',
+ section: 'other',
+ } )
+ ).toBeUndefined();
+ } );
+
+ it( 'resolves save handlers and region components by scope', () => {
+ const saveHandler: SettingsSaveHandler = () => undefined;
+ const region: SettingsRegionComponent = () => null;
+
+ registerSettingsExtension( {
+ scope: { page: 'registry-save-region' },
+ saveHandlers: {
+ 'save/handler': saveHandler,
+ },
+ regions: {
+ 'region/component': region,
+ },
+ } );
+
+ expect(
+ resolveSaveHandler( 'save/handler', {
+ page: 'registry-save-region',
+ } )
+ ).toBe( saveHandler );
+ expect(
+ resolveRegionComponent( 'region/component', {
+ page: 'registry-save-region',
+ } )
+ ).toBe( region );
+ } );
+} );
diff --git a/packages/js/settings-ui-sdk/src/types.ts b/packages/js/settings-ui-sdk/src/types.ts
new file mode 100644
index 00000000000..8b360fc615f
--- /dev/null
+++ b/packages/js/settings-ui-sdk/src/types.ts
@@ -0,0 +1,178 @@
+export type SettingsValue = string | number | boolean | string[] | null;
+
+export type SettingsValues = Record< string, SettingsValue >;
+
+export type SettingsUIOption = {
+ label: string;
+ value: string;
+};
+
+export type SettingsUISaveAdapter =
+ | 'form_post'
+ | 'none'
+ | ( string & NonNullable< unknown > );
+
+export type SettingsUISaveSchema = {
+ adapter: SettingsUISaveAdapter;
+ name?: string;
+};
+
+export type SettingsUISaveStrategy =
+ | { adapter: 'form_post' }
+ | { adapter: 'custom'; handler: string }
+ | { adapter: 'none' }
+ | { adapter: string & NonNullable< unknown >; handler?: string };
+
+export type SettingsUIVisibilityRule = {
+ controller: string;
+ value?: SettingsValue | SettingsValue[];
+};
+
+export type SettingsUIField = {
+ id: string;
+ label: string;
+ type: string;
+ description?: string;
+ value?: SettingsValue;
+ options?: SettingsUIOption[];
+ component?: string;
+ placeholder?: string;
+ disabled?: boolean;
+ customAttributes?: Record< string, string | number | boolean >;
+ visibility?: SettingsUIVisibilityRule;
+ save?: SettingsUISaveSchema;
+};
+
+export type SettingsUIGroupAction = {
+ id: string;
+ label: string;
+ href: string;
+ variant?: 'primary' | 'secondary' | 'tertiary' | 'link' | string;
+ target?: string;
+ rel?: string;
+};
+
+export type SettingsUIGroup = {
+ id: string;
+ title?: string;
+ description?: string;
+ actions?: SettingsUIGroupAction[];
+ fields: SettingsUIField[];
+};
+
+export type SettingsUIShellBreadcrumb = {
+ label: string;
+ href?: string;
+};
+
+export type SettingsUIShellNavigationItem = {
+ id: string;
+ label: string;
+ href: string;
+ active?: boolean;
+};
+
+export type SettingsUIShell = {
+ title?: string;
+ breadcrumbs?: SettingsUIShellBreadcrumb[];
+ navigation?: SettingsUIShellNavigationItem[];
+ sectionNavigation?: SettingsUIShellNavigationItem[];
+ navigationComponent?: string;
+};
+
+export type SettingsUISchema = {
+ id: string;
+ title?: string;
+ section?: string;
+ save?: SettingsUISaveStrategy;
+ shell?: SettingsUIShell;
+ groups: Record< string, SettingsUIGroup >;
+};
+
+export type SettingsFieldContext = {
+ page: string;
+ section?: string;
+};
+
+export type SettingsFieldComponentProps = {
+ field: SettingsUIField;
+ value: SettingsValue;
+ onChange: ( value: SettingsValue ) => void;
+ values: SettingsValues;
+ initialValues: SettingsValues;
+ setValue: ( fieldId: string, value: SettingsValue ) => void;
+ setValues: ( values: Partial< SettingsValues > ) => void;
+ context: SettingsFieldContext;
+};
+
+export type SettingsFieldComponent = (
+ props: SettingsFieldComponentProps
+) => JSX.Element | null;
+
+export type SettingsVisibilityPredicateArgs = {
+ values: SettingsValues;
+ initialValues: SettingsValues;
+ context: SettingsFieldContext;
+ schema: SettingsUISchema;
+};
+
+export type SettingsVisibilityPredicate = (
+ args: SettingsVisibilityPredicateArgs
+) => boolean;
+
+export type SettingsSaveHandlerArgs = {
+ values: SettingsValues;
+ initialValues: SettingsValues;
+ changedValues: Partial< SettingsValues >;
+ dirtyFields: string[];
+ context: SettingsFieldContext;
+ schema: SettingsUISchema;
+};
+
+export type SettingsSaveResult = void | {
+ values?: SettingsValues;
+ notice?: string;
+};
+
+export type SettingsSaveHandler = (
+ args: SettingsSaveHandlerArgs
+) => Promise< SettingsSaveResult > | SettingsSaveResult;
+
+export type SettingsRegionComponentProps = {
+ values: SettingsValues;
+ initialValues: SettingsValues;
+ context: SettingsFieldContext;
+ schema: SettingsUISchema;
+};
+
+export type SettingsRegionComponent = (
+ props: SettingsRegionComponentProps
+) => JSX.Element | null;
+
+export type SettingsExtensionScope = {
+ page: string;
+ section?: string;
+};
+
+export type SettingsExtensionRegistration = {
+ scope: SettingsExtensionScope;
+ components?: Record< string, SettingsFieldComponent >;
+ fieldOverrides?: Record< string, SettingsFieldComponent >;
+ typeRenderers?: Record< string, SettingsFieldComponent >;
+ fieldVisibility?: Record< string, SettingsVisibilityPredicate >;
+ groupVisibility?: Record< string, SettingsVisibilityPredicate >;
+ saveHandlers?: Record< string, SettingsSaveHandler >;
+ regions?: Record< string, SettingsRegionComponent >;
+};
+
+export type SettingsUIRegistry = {
+ registerSettingsExtension: (
+ registration: SettingsExtensionRegistration
+ ) => void;
+};
+
+declare global {
+ interface Window {
+ wcSettingsUI?: SettingsUIRegistry;
+ }
+}
diff --git a/packages/js/settings-ui-sdk/tsconfig-cjs.json b/packages/js/settings-ui-sdk/tsconfig-cjs.json
new file mode 100644
index 00000000000..78af0d193db
--- /dev/null
+++ b/packages/js/settings-ui-sdk/tsconfig-cjs.json
@@ -0,0 +1,10 @@
+{
+ "extends": "@woocommerce/internal-ts-config/tsconfig-cjs.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "build",
+ "typeRoots": [ "./node_modules/@types" ]
+ },
+ "include": [ "src/**/*" ],
+ "exclude": [ "**/test/**" ]
+}
diff --git a/packages/js/settings-ui-sdk/tsconfig.json b/packages/js/settings-ui-sdk/tsconfig.json
new file mode 100644
index 00000000000..aaf85323f16
--- /dev/null
+++ b/packages/js/settings-ui-sdk/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "extends": "@woocommerce/internal-ts-config/tsconfig.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "noCheck": false,
+ "outDir": "build-module",
+ "declaration": true,
+ "declarationMap": true,
+ "declarationDir": "./build-types",
+ "typeRoots": [
+ "./node_modules/@types"
+ ],
+ "composite": true
+ },
+ "include": [
+ "src/**/*"
+ ],
+ "exclude": [
+ "**/test/**"
+ ],
+ "references": [
+ {
+ "path": "../sanitize"
+ }
+ ]
+}
diff --git a/plugins/woocommerce/changelog/add-settings-ui-sdk b/plugins/woocommerce/changelog/add-settings-ui-sdk
new file mode 100644
index 00000000000..e16f0d0afa0
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-settings-ui-sdk
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add an opt-in settings UI SDK path for WooCommerce settings pages.
diff --git a/plugins/woocommerce/client/admin/client/settings/settings-ui-registry.tsx b/plugins/woocommerce/client/admin/client/settings/settings-ui-registry.tsx
new file mode 100644
index 00000000000..7d5cbb238c6
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings/settings-ui-registry.tsx
@@ -0,0 +1,82 @@
+/**
+ * External dependencies
+ */
+import { createElement, createRoot } from '@wordpress/element';
+import type { ComponentType, ReactNode } from 'react';
+import type { SettingsUISchema } from '@woocommerce/settings-ui-sdk';
+
+/**
+ * Internal dependencies
+ */
+import { getAdminSetting } from '~/utils/admin-settings';
+
+declare global {
+ interface Window {
+ wc?: {
+ settingsUiSdk?: {
+ SettingsUIErrorBoundary: ComponentType< {
+ children: ReactNode;
+ } >;
+ SettingsUIPage: ( props: {
+ schema: SettingsUISchema;
+ page: string;
+ section?: string;
+ } ) => JSX.Element | null;
+ };
+ };
+ }
+}
+
+const getSchema = (
+ page: string,
+ section: string
+): SettingsUISchema | undefined => {
+ const settings = getAdminSetting( 'settingsUI', {} );
+ const sectionKey = section || 'default';
+ return settings?.[ page ]?.[ sectionKey ];
+};
+
+export const registerSettingsUIScreens = () => {
+ const SettingsUIErrorBoundary =
+ window.wc?.settingsUiSdk?.SettingsUIErrorBoundary;
+ const SettingsUIPage = window.wc?.settingsUiSdk?.SettingsUIPage;
+
+ if ( ! SettingsUIErrorBoundary || ! SettingsUIPage ) {
+ if (
+ document.querySelector< HTMLElement >( '[data-wc-settings-ui="1"]' )
+ ) {
+ // eslint-disable-next-line no-console
+ console.warn( '[WooCommerce settings UI] SDK script is missing.' );
+ }
+ return;
+ }
+
+ document
+ .querySelectorAll< HTMLElement >( '[data-wc-settings-ui="1"]' )
+ .forEach( ( element ) => {
+ const page = element.dataset.wcSettingsPage || '';
+ const section = element.dataset.wcSettingsSection || '';
+ const schema = getSchema( page, section );
+
+ if ( ! schema ) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ '[WooCommerce settings UI] Settings payload is missing.',
+ { page, section }
+ );
+ return;
+ }
+
+ createRoot( element ).render(
+ createElement(
+ SettingsUIErrorBoundary,
+ null,
+ createElement( SettingsUIPage, {
+ schema,
+ page,
+ section: section || schema.section,
+ } )
+ )
+ );
+ } );
+};
diff --git a/plugins/woocommerce/client/admin/client/typings/global.d.ts b/plugins/woocommerce/client/admin/client/typings/global.d.ts
index 330b05da639..4b633e4f2fd 100644
--- a/plugins/woocommerce/client/admin/client/typings/global.d.ts
+++ b/plugins/woocommerce/client/admin/client/typings/global.d.ts
@@ -69,6 +69,7 @@ declare global {
'remote-inbox-notifications': boolean;
'remote-free-extensions': boolean;
settings: boolean;
+ 'settings-ui': boolean;
'shipping-label-banner': boolean;
subscriptions: boolean;
'store-alerts': boolean;
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/index.tsx b/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/index.tsx
index 40fcbbee801..0362e4f973d 100644
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/index.tsx
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/index.tsx
@@ -10,6 +10,7 @@ import { createRoot } from '@wordpress/element';
/**
* Internal dependencies
*/
+import './settings-ui.scss';
import { isFeatureEnabled } from '~/utils/features';
import {
SettingsPaymentsBacsWrapper,
@@ -30,6 +31,7 @@ import { registerSettingsEmailImageUrlFill } from '~/settings-email/settings-ema
import { registerSettingsEmailPreviewFill } from '~/settings-email/settings-email-preview-slotfill';
import { registerSettingsEmailFeedbackFill } from '~/settings-email/settings-email-feedback-slotfill';
import { registerSettingsEmailListingFill } from '~/settings-email/settings-email-listing-slotfill';
+import { registerSettingsUIScreens } from '~/settings/settings-ui-registry';
const renderPaymentsSettings = () => {
const pages = [
@@ -88,6 +90,10 @@ const registerSlotFills = () => {
registerSettingsEmailListingFill();
}
+ if ( features?.[ 'settings-ui' ] === true ) {
+ registerSettingsUIScreens();
+ }
+
registerSettingsEmailColorPaletteFill();
registerSettingsEmailFeedbackFill();
registerSettingsEmailImageUrlFill();
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss b/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
new file mode 100644
index 00000000000..5911212d033
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
@@ -0,0 +1,448 @@
+@import "@wordpress/admin-ui/build-style/style.css";
+
+body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
+ background-color: #fff;
+
+ #mainform,
+ #wpcontent,
+ #wpbody-content,
+ .wrap.woocommerce {
+ background-color: #fff;
+ }
+
+ #mainform {
+ padding: 0 48px 48px;
+ }
+
+ #mainform > .submit {
+ display: none;
+ }
+
+ #mainform .subsubsub:not(.list-table-filters) {
+ border-bottom: 1px solid #f0f0f0;
+ box-sizing: border-box;
+ color: transparent;
+ display: flex;
+ float: none;
+ gap: 0;
+ margin: 0 -48px 48px;
+ overflow-x: auto;
+ padding: 0 48px;
+ width: auto;
+
+ li {
+ color: transparent;
+ margin: 0;
+ padding: 0;
+ }
+
+ li a,
+ li:first-child a,
+ li:last-child a {
+ align-items: center;
+ color: #1e1e1e;
+ display: inline-flex;
+ font-size: 13px;
+ font-weight: 500;
+ height: 48px;
+ line-height: 20px;
+ margin: 0;
+ padding: 0 16px;
+ position: relative;
+ text-decoration: none;
+ white-space: nowrap;
+
+ &:focus,
+ &:hover,
+ &:active {
+ box-shadow: none;
+ color: var(--wp-admin-theme-color, #3858e9);
+ outline: none;
+ }
+
+ &.current {
+ color: #1e1e1e;
+ font-weight: 500;
+
+ &::after {
+ background: var(--wp-admin-theme-color, #3858e9);
+ border-radius: 999px;
+ bottom: 0;
+ content: "";
+ height: 2px;
+ left: 0;
+ position: absolute;
+ right: 0;
+ }
+ }
+ }
+
+ + br.clear {
+ display: none;
+ }
+ }
+
+ .wc-settings-ui-shell.admin-ui-page {
+ box-sizing: border-box;
+ color: #1e1e1e;
+ font-size: 13px;
+ height: auto;
+ line-height: 20px;
+ margin: 0 -48px;
+ }
+
+ .wc-settings-ui-shell .admin-ui-page__header {
+ padding-left: 48px;
+ padding-right: 48px;
+ position: static;
+ }
+
+ .wc-settings-ui-shell:has(.wc-settings-ui-shell__navigation) .admin-ui-page__header {
+ border-bottom: 0;
+ }
+
+ .wc-settings-ui-shell .admin-ui-page__header-title {
+ font-size: 20px;
+ font-weight: 500;
+ line-height: 24px;
+ }
+
+ .wc-settings-ui-shell__breadcrumbs {
+ color: #757575;
+ font-size: 12px;
+ line-height: 16px;
+ margin: 0 0 8px;
+ }
+
+ .wc-settings-ui-shell__breadcrumb {
+ &:not(:last-child)::after {
+ content: "/";
+ margin: 0 8px;
+ }
+
+ a {
+ color: #505050;
+ text-decoration: none;
+
+ &:focus,
+ &:hover {
+ color: var(--wp-admin-theme-color, #3858e9);
+ }
+ }
+ }
+
+
+ .wc-settings-ui-shell__navigation {
+ border-bottom: 1px solid #f0f0f0;
+ margin: 0;
+ width: 100%;
+ }
+
+ .wc-settings-ui-shell__tabs {
+ background: #fff;
+ display: flex;
+ margin: 0 !important;
+ overflow-x: auto;
+ padding: 0 48px;
+ width: 100%;
+ }
+
+ .wc-settings-ui-shell__tabs--secondary {
+ border-bottom: 0;
+ }
+
+ .wc-settings-ui-shell__tab {
+ align-items: center;
+ color: #1e1e1e;
+ display: inline-flex;
+ font-size: 13px;
+ font-weight: 500;
+ height: 48px;
+ line-height: 20px;
+ padding: 0 16px;
+ position: relative;
+ text-decoration: none;
+ white-space: nowrap;
+
+ &:focus,
+ &:hover,
+ &:active {
+ box-shadow: none;
+ color: var(--wp-admin-theme-color, #3858e9);
+ outline: none;
+ }
+
+ &.is-active {
+ color: #1e1e1e;
+
+ &::after {
+ background: var(--wp-admin-theme-color, #3858e9);
+ border-radius: 999px;
+ bottom: 0;
+ content: "";
+ height: 2px;
+ left: 0;
+ position: absolute;
+ right: 0;
+ }
+ }
+ }
+
+ .wc-settings-ui-shell__notice.components-notice {
+ margin: 24px 48px 0;
+ }
+
+ .wc-settings-ui {
+ --wc-settings-ui-panel-width: 681px;
+ --wc-settings-ui-sidebar-width: 296px;
+
+ box-sizing: border-box;
+ color: #1e1e1e;
+ font-size: 13px;
+ line-height: 20px;
+ margin: 48px 48px;
+ max-width: calc(var(--wc-settings-ui-sidebar-width) + var(--wc-settings-ui-panel-width) + 24px);
+ }
+
+ .wc-settings-ui__group {
+ align-items: flex-start;
+ display: flex;
+ gap: 24px;
+ margin: 0 0 48px;
+ }
+
+ .wc-settings-ui__group-header {
+ flex: 0 0 var(--wc-settings-ui-sidebar-width);
+ max-width: var(--wc-settings-ui-sidebar-width);
+ min-width: 0;
+
+ h2 {
+ color: #070707;
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 24px;
+ margin: 0;
+ }
+
+ .wc-settings-ui__group-description {
+ color: #505050;
+ font-size: 14px;
+ line-height: 20px;
+ margin: 4px 0 0;
+ max-width: 240px;
+
+ p {
+ margin: 0 0 8px;
+ }
+
+ p:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ .wc-settings-ui__group-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-top: 16px;
+ }
+ }
+
+ .wc-settings-ui__group-panel {
+ background: #fff;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
+ display: flex;
+ flex: 0 1 var(--wc-settings-ui-panel-width);
+ flex-direction: column;
+ gap: 16px;
+ max-width: var(--wc-settings-ui-panel-width);
+ padding: 24px;
+ width: 100%;
+ }
+
+ .wc-settings-ui__field {
+ margin: 0;
+ width: 100%;
+ }
+
+ .wc-settings-ui__actions {
+ margin-left: calc(var(--wc-settings-ui-sidebar-width) + 24px);
+ }
+
+ .wc-settings-ui__info {
+ background: #f0f0f1;
+ border-radius: 4px;
+ color: #505050;
+ font-size: 13px;
+ line-height: 20px;
+ padding: 16px;
+
+ strong {
+ color: #1e1e1e;
+ display: block;
+ font-weight: 500;
+ margin-bottom: 4px;
+ }
+
+ p {
+ margin: 0;
+ }
+ }
+
+ .wc-settings-ui__control.components-base-control {
+ margin-bottom: 0;
+ width: 100%;
+ }
+
+ .wc-settings-ui__control {
+ .components-base-control__field {
+ margin-bottom: 0;
+ }
+
+ .components-base-control__label {
+ color: #1e1e1e;
+ display: block;
+ font-size: 11px;
+ font-weight: 500;
+ line-height: 16px;
+ margin: 0 0 8px;
+ text-transform: uppercase;
+ }
+
+ .components-base-control__help {
+ color: #505050;
+ font-size: 12px;
+ line-height: 16px;
+ margin: 8px 0 0;
+ }
+ }
+
+ .wc-settings-ui__control input[type="text"],
+ .wc-settings-ui__control input[type="password"],
+ .wc-settings-ui__control input[type="datetime-local"],
+ .wc-settings-ui__control input[type="date"],
+ .wc-settings-ui__control input[type="time"],
+ .wc-settings-ui__control input[type="email"],
+ .wc-settings-ui__control input[type="url"],
+ .wc-settings-ui__control input[type="tel"],
+ .wc-settings-ui__control input[type="number"],
+ .wc-settings-ui__control select,
+ .wc-settings-ui__control textarea {
+ border-color: #949494;
+ border-radius: 2px;
+ color: #1e1e1e;
+ font-size: 13px;
+ line-height: 20px;
+ margin: 0;
+ max-width: none;
+ min-height: 40px;
+ width: 100%;
+ }
+
+ .wc-settings-ui__control input[type="text"],
+ .wc-settings-ui__control input[type="password"],
+ .wc-settings-ui__control input[type="datetime-local"],
+ .wc-settings-ui__control input[type="date"],
+ .wc-settings-ui__control input[type="time"],
+ .wc-settings-ui__control input[type="email"],
+ .wc-settings-ui__control input[type="url"],
+ .wc-settings-ui__control input[type="tel"],
+ .wc-settings-ui__control input[type="number"],
+ .wc-settings-ui__control select {
+ padding: 8px 12px;
+ }
+
+ .wc-settings-ui__control textarea {
+ padding: 10px 12px;
+ }
+
+ .wc-settings-ui__field--checkbox {
+ .components-base-control__field {
+ align-items: flex-start;
+ display: flex;
+ gap: 12px;
+ }
+
+ .components-h-stack {
+ align-items: flex-start;
+ gap: 12px;
+ }
+
+ .components-checkbox-control__input-container {
+ flex: 0 0 auto;
+ margin: 0;
+ }
+
+ .components-checkbox-control__label {
+ color: #1e1e1e;
+ font-size: 13px;
+ line-height: 20px;
+ }
+
+ .components-base-control__help {
+ margin-left: 32px;
+ }
+ }
+
+ @media (max-width: 960px) {
+ #mainform {
+ padding: 0 24px 32px;
+ }
+
+ #mainform .subsubsub:not(.list-table-filters) {
+ margin-left: -24px;
+ margin-right: -24px;
+ padding-left: 24px;
+ padding-right: 24px;
+ }
+
+ .wc-settings-ui-shell {
+ margin-left: -24px;
+ margin-right: -24px;
+ }
+
+ .wc-settings-ui-shell .admin-ui-page__header {
+ padding-left: 24px;
+ padding-right: 24px;
+ }
+
+ .wc-settings-ui-shell__navigation {
+ margin-left: -24px;
+ margin-right: -24px;
+ width: calc(100% + 48px);
+ }
+
+ .wc-settings-ui-shell__tabs {
+ padding-left: 24px;
+ padding-right: 24px;
+ }
+
+ .wc-settings-ui-shell__notice.components-notice {
+ margin-left: 24px;
+ margin-right: 24px;
+ }
+
+ .wc-settings-ui {
+ margin: 32px 24px;
+ max-width: none;
+ }
+
+ .wc-settings-ui__group {
+ display: block;
+ }
+
+ .wc-settings-ui__group-header {
+ margin-bottom: 16px;
+ max-width: none;
+ }
+
+ .wc-settings-ui__group-panel {
+ max-width: none;
+ }
+
+ .wc-settings-ui__actions {
+ margin-left: 0;
+ }
+ }
+}
diff --git a/plugins/woocommerce/client/admin/config/core.json b/plugins/woocommerce/client/admin/config/core.json
index a90099f04e8..9a8247c4928 100644
--- a/plugins/woocommerce/client/admin/config/core.json
+++ b/plugins/woocommerce/client/admin/config/core.json
@@ -29,6 +29,7 @@
"remote-free-extensions": true,
"payment-gateway-suggestions": true,
"printful": true,
+ "settings-ui": false,
"shipping-label-banner": true,
"subscriptions": true,
"store-alerts": true,
diff --git a/plugins/woocommerce/client/admin/config/development.json b/plugins/woocommerce/client/admin/config/development.json
index 7a43c6f9e8b..89bdafe155f 100644
--- a/plugins/woocommerce/client/admin/config/development.json
+++ b/plugins/woocommerce/client/admin/config/development.json
@@ -29,6 +29,7 @@
"printful": true,
"remote-inbox-notifications": true,
"remote-free-extensions": true,
+ "settings-ui": false,
"shipping-label-banner": true,
"subscriptions": true,
"store-alerts": true,
diff --git a/plugins/woocommerce/client/admin/package.json b/plugins/woocommerce/client/admin/package.json
index 1151c5d3e22..4eb56a73d92 100644
--- a/plugins/woocommerce/client/admin/package.json
+++ b/plugins/woocommerce/client/admin/package.json
@@ -65,6 +65,7 @@
"@woocommerce/email-editor": "workspace:*",
"@woocommerce/experimental": "workspace:*",
"@woocommerce/explat": "workspace:*",
+ "@woocommerce/settings-ui-sdk": "workspace:*",
"@woocommerce/navigation": "workspace:*",
"@woocommerce/number": "workspace:*",
"@woocommerce/onboarding": "workspace:*",
@@ -73,6 +74,7 @@
"@woocommerce/sanitize": "workspace:*",
"@woocommerce/tracks": "workspace:*",
"@wordpress/a11y": "catalog:wp-min",
+ "@wordpress/admin-ui": "catalog:wp-min",
"@wordpress/api-fetch": "catalog:wp-min",
"@wordpress/base-styles": "catalog:wp-min",
"@wordpress/blob": "catalog:wp-min",
@@ -315,6 +317,8 @@
"node_modules/@woocommerce/experimental/build-types",
"node_modules/@woocommerce/explat/build",
"node_modules/@woocommerce/explat/build-types",
+ "node_modules/@woocommerce/settings-ui-sdk/build",
+ "node_modules/@woocommerce/settings-ui-sdk/build-types",
"node_modules/@woocommerce/navigation/build",
"node_modules/@woocommerce/navigation/build-types",
"node_modules/@woocommerce/number/build",
diff --git a/plugins/woocommerce/client/admin/tsconfig.json b/plugins/woocommerce/client/admin/tsconfig.json
index 89d37c8d5a2..e476f64e2ce 100644
--- a/plugins/woocommerce/client/admin/tsconfig.json
+++ b/plugins/woocommerce/client/admin/tsconfig.json
@@ -77,6 +77,9 @@
{
"path": "../../../../packages/js/sanitize"
},
+ {
+ "path": "../../../../packages/js/settings-ui-sdk"
+ },
{
"path": "../../../../packages/js/tracks"
}
diff --git a/plugins/woocommerce/client/admin/webpack.config.js b/plugins/woocommerce/client/admin/webpack.config.js
index 545415cec91..9e96f337171 100644
--- a/plugins/woocommerce/client/admin/webpack.config.js
+++ b/plugins/woocommerce/client/admin/webpack.config.js
@@ -59,6 +59,7 @@ const wcAdminPackages = [
'block-templates',
'product-editor',
'sanitize',
+ 'settings-ui-sdk',
'remote-logging',
'email-editor',
];
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/test/block.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/test/block.ts
index 489478add1c..dad090075de 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/test/block.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/test/block.ts
@@ -165,14 +165,11 @@ describe( 'Product Gallery Block', () => {
const innerBlocks = block.querySelector( '.block-editor-inner-blocks' );
expect( innerBlocks ).toBeInTheDocument();
- // Check layout container
+ // Check layout container.
const layout = block.querySelector(
'.block-editor-block-list__layout'
);
expect( layout ).toBeInTheDocument();
- expect( layout ).toHaveClass( 'is-layout-flex' );
- expect( layout ).toHaveClass( 'is-horizontal' );
- expect( layout ).toHaveClass( 'is-nowrap' );
// Check for viewer block and its inner blocks
const viewerBlock = screen.getByRole( 'document', {
diff --git a/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php b/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php
index b72872f0b3e..82b308f8690 100644
--- a/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php
+++ b/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php
@@ -8,6 +8,9 @@
declare( strict_types = 1);
+use Automattic\WooCommerce\Admin\Features\Features;
+use Automattic\WooCommerce\Admin\Settings\SettingsUIPageInterface;
+
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
@@ -83,6 +86,7 @@ if ( ! class_exists( 'WC_Settings_Page', false ) ) :
add_action( 'woocommerce_settings_' . $this->id, array( $this, 'output' ) );
add_action( 'woocommerce_settings_save_' . $this->id, array( $this, 'save' ) );
add_action( 'woocommerce_admin_field_add_settings_slot', array( $this, 'add_settings_slot' ) );
+ add_filter( 'admin_body_class', array( $this, 'add_settings_ui_body_class' ) );
}
/**
@@ -105,6 +109,45 @@ if ( ! class_exists( 'WC_Settings_Page', false ) ) :
return $this->label;
}
+ /**
+ * Get the settings UI page adapter for this settings page.
+ *
+ * Settings pages can override this to opt in to the settings UI renderer
+ * while retaining the classic WooCommerce settings page route and save flow.
+ *
+ * @since 10.9.0
+ * @return SettingsUIPageInterface|null
+ */
+ public function get_settings_ui_page(): ?SettingsUIPageInterface {
+ return null;
+ }
+
+ /**
+ * Add a body class for settings pages rendered through the settings UI SDK.
+ *
+ * @since 10.9.0
+ *
+ * @param string $classes The existing body classes for the admin area.
+ * @return string The modified body classes for the admin area.
+ */
+ public function add_settings_ui_body_class( $classes ) {
+ global $current_tab;
+
+ if ( ! is_string( $classes ) || $this->id !== $current_tab ) {
+ return $classes;
+ }
+
+ if ( ! Features::is_enabled( 'settings-ui' ) || ! $this->get_settings_ui_page() instanceof SettingsUIPageInterface ) {
+ return $classes;
+ }
+
+ if ( str_contains( $classes, 'woocommerce-settings-ui-page' ) ) {
+ return $classes;
+ }
+
+ return "$classes woocommerce-settings-ui-page";
+ }
+
/**
* Creates the React mount point for settings slot.
*/
@@ -265,6 +308,49 @@ if ( ! class_exists( 'WC_Settings_Page', false ) ) :
public function output() {
global $current_section;
+ $settings_ui_page = $this->get_settings_ui_page();
+ $section_key = '' === $current_section ? 'default' : $current_section;
+ $page_id = $settings_ui_page instanceof SettingsUIPageInterface ? $settings_ui_page->get_page_id() : '';
+ $schema_failed = ! empty( $GLOBALS['wc_settings_ui_schema_failed'][ $page_id ][ $section_key ] );
+
+ if ( Features::is_enabled( 'settings-ui' ) && $settings_ui_page instanceof SettingsUIPageInterface && ! $schema_failed ) {
+ $render_settings_ui = true;
+
+ try {
+ $script_handles = $settings_ui_page->get_script_handles( $current_section );
+ } catch ( \Throwable $e ) {
+ $script_handles = array();
+ $render_settings_ui = false;
+
+ if ( $e instanceof \Exception ) {
+ wc_caught_exception( $e, __CLASS__ . '::' . __FUNCTION__ );
+ }
+ }
+
+ if ( $render_settings_ui ) {
+ /**
+ * Extension-provided handles may violate the interface contract.
+ *
+ * @var mixed[] $script_handles
+ */
+ foreach ( $script_handles as $script_handle ) {
+ if ( is_string( $script_handle ) && '' !== $script_handle ) {
+ wp_enqueue_script( $script_handle );
+ }
+ }
+
+ $GLOBALS['hide_save_button'] = true;
+
+ printf(
+ '<div id="%1$s" data-wc-settings-ui="1" data-wc-settings-page="%2$s" data-wc-settings-section="%3$s"></div>',
+ esc_attr( 'wc_settings_ui_' . sanitize_html_class( $this->id ) . '_' . sanitize_html_class( '' === $current_section ? 'default' : $current_section ) ),
+ esc_attr( $settings_ui_page->get_page_id() ),
+ esc_attr( $current_section )
+ );
+ return;
+ }
+ }
+
// We can't use "get_settings_for_section" here
// for compatibility with derived classes overriding "get_settings".
$settings = $this->get_settings( $current_section );
diff --git a/plugins/woocommerce/includes/admin/settings/class-wc-settings-products.php b/plugins/woocommerce/includes/admin/settings/class-wc-settings-products.php
index f617e8a850a..a61dc47e687 100644
--- a/plugins/woocommerce/includes/admin/settings/class-wc-settings-products.php
+++ b/plugins/woocommerce/includes/admin/settings/class-wc-settings-products.php
@@ -6,6 +6,8 @@
* @version 2.4.0
*/
+use Automattic\WooCommerce\Admin\Settings\SettingsUIPageInterface;
+use Automattic\WooCommerce\Internal\Admin\Settings\SettingsUIPages\ProductsSettingsPageAdapter;
use Automattic\WooCommerce\Utilities\I18nUtil;
if ( ! defined( 'ABSPATH' ) ) {
@@ -38,6 +40,16 @@ class WC_Settings_Products extends WC_Settings_Page {
*/
public $icon = 'box';
+ /**
+ * Get the settings UI page adapter for this settings page.
+ *
+ * @since 10.9.0
+ * @return SettingsUIPageInterface|null
+ */
+ public function get_settings_ui_page(): ?SettingsUIPageInterface {
+ return new ProductsSettingsPageAdapter( $this );
+ }
+
/**
* Get own sections.
*
diff --git a/plugins/woocommerce/includes/admin/views/html-admin-settings.php b/plugins/woocommerce/includes/admin/views/html-admin-settings.php
index e1894e3f58b..a59b0c74177 100644
--- a/plugins/woocommerce/includes/admin/views/html-admin-settings.php
+++ b/plugins/woocommerce/includes/admin/views/html-admin-settings.php
@@ -10,6 +10,7 @@
// phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment
use Automattic\WooCommerce\Admin\Features\Features;
+use Automattic\WooCommerce\Admin\Settings\SettingsUIPageInterface;
use Automattic\WooCommerce\Utilities\FeaturesUtil;
if ( ! defined( 'ABSPATH' ) ) {
@@ -36,7 +37,25 @@ if ( ! $tab_exists ) {
exit;
}
-$hide_nav = 'checkout' === $current_tab && in_array( $current_section, array( 'offline', 'bacs', 'cheque', 'cod' ), true );
+$hide_nav = 'checkout' === $current_tab && in_array( $current_section, array( 'offline', 'bacs', 'cheque', 'cod' ), true );
+$is_settings_ui_page = false;
+$settings_ui_settings_page = null;
+
+if ( Features::is_enabled( 'settings-ui' ) ) {
+ foreach ( WC_Admin_Settings::get_settings_pages() as $settings_page ) {
+ if ( ! $settings_page instanceof WC_Settings_Page || $settings_page->get_id() !== $current_tab ) {
+ continue;
+ }
+
+ $is_settings_ui_page = $settings_page->get_settings_ui_page() instanceof SettingsUIPageInterface;
+ $settings_ui_settings_page = $is_settings_ui_page ? $settings_page : null;
+ break;
+ }
+}
+
+if ( $settings_ui_settings_page instanceof WC_Settings_Page ) {
+ remove_action( 'woocommerce_sections_' . $current_tab, array( $settings_ui_settings_page, 'output_sections' ) );
+}
// Move 'Advanced' to the last.
if ( array_key_exists( 'advanced', $tabs ) ) {
@@ -114,12 +133,12 @@ $marketplace_links = array(
<?php endif; ?>
<h1 class="screen-reader-text"><?php echo esc_html( $current_tab_label ); ?></h1>
<?php
- do_action( 'woocommerce_sections_' . $current_tab );
+ do_action( 'woocommerce_sections_' . $current_tab );
- WC_Admin_Settings::show_messages();
+ WC_Admin_Settings::show_messages();
- do_action( 'woocommerce_settings_' . $current_tab );
- do_action( 'woocommerce_settings_tabs_' . $current_tab ); // @deprecated 3.4.0 hook.
+ do_action( 'woocommerce_settings_' . $current_tab );
+ do_action( 'woocommerce_settings_tabs_' . $current_tab );
?>
<p class="submit">
<?php if ( empty( $GLOBALS['hide_save_button'] ) ) : ?>
diff --git a/plugins/woocommerce/src/Admin/Settings/LegacySettingsPageAdapter.php b/plugins/woocommerce/src/Admin/Settings/LegacySettingsPageAdapter.php
new file mode 100644
index 00000000000..ecf83147408
--- /dev/null
+++ b/plugins/woocommerce/src/Admin/Settings/LegacySettingsPageAdapter.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Public legacy WC_Settings_Page adapter for settings UI.
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Admin\Settings;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Adapts a WC_Settings_Page instance into the settings UI page contract.
+ *
+ * Extensions can use this class directly for native-field migrations, or
+ * subclass it to add component metadata, script handles, or custom save behavior.
+ *
+ * @since 10.9.0
+ */
+class LegacySettingsPageAdapter extends \Automattic\WooCommerce\Internal\Admin\Settings\LegacySettingsPageAdapter {}
diff --git a/plugins/woocommerce/src/Admin/Settings/SettingsUIPageInterface.php b/plugins/woocommerce/src/Admin/Settings/SettingsUIPageInterface.php
new file mode 100644
index 00000000000..cf79b4925b5
--- /dev/null
+++ b/plugins/woocommerce/src/Admin/Settings/SettingsUIPageInterface.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Settings UI page contract.
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Admin\Settings;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Contract for settings pages that opt into the settings UI renderer.
+ *
+ * @since 10.9.0
+ */
+interface SettingsUIPageInterface {
+
+ /**
+ * Get the stable page id used for scoping the settings UI.
+ *
+ * @since 10.9.0
+ *
+ * @return string
+ */
+ public function get_page_id(): string;
+
+ /**
+ * Build the canonical settings schema for a section.
+ *
+ * @since 10.9.0
+ *
+ * @param string $section Section id. Empty string means the default section.
+ * @return array
+ */
+ public function get_schema( string $section ): array;
+
+ /**
+ * Get script handles that must be loaded before the settings UI app mounts.
+ *
+ * @since 10.9.0
+ *
+ * @param string $section Section id. Empty string means the default section.
+ * @return string[]
+ */
+ public function get_script_handles( string $section ): array;
+
+ /**
+ * Get the default save adapter for fields on this page.
+ *
+ * Supported values are `form_post` and `none`.
+ *
+ * @since 10.9.0
+ *
+ * @param string $section Section id. Empty string means the default section.
+ * @return string
+ */
+ public function get_save_adapter( string $section ): string;
+}
diff --git a/plugins/woocommerce/src/Admin/Settings/SettingsUISchema.php b/plugins/woocommerce/src/Admin/Settings/SettingsUISchema.php
new file mode 100644
index 00000000000..a91b6ef2a3b
--- /dev/null
+++ b/plugins/woocommerce/src/Admin/Settings/SettingsUISchema.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * Public settings UI schema helpers.
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Admin\Settings;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Converts WooCommerce settings definitions into the settings UI schema.
+ *
+ * @since 10.9.0
+ */
+class SettingsUISchema extends \Automattic\WooCommerce\Internal\Admin\Settings\SettingsUISchema {}
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings.php b/plugins/woocommerce/src/Internal/Admin/Settings.php
index f7dff32332e..41b0ecc3124 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings.php
@@ -10,6 +10,7 @@ use Automattic\WooCommerce\Admin\API\Reports\Orders\DataStore as OrdersDataStore
use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Admin\PageController;
use Automattic\WooCommerce\Admin\PluginsHelper;
+use Automattic\WooCommerce\Admin\Settings\SettingsUIPageInterface;
use Automattic\WooCommerce\Utilities\FeaturesUtil;
use Automattic\WooCommerce\Utilities\OrderUtil;
use WC_Marketplace_Suggestions;
@@ -221,6 +222,7 @@ class Settings {
//phpcs:ignore
$settings['variationTitleAttributesSeparator'] = apply_filters( 'woocommerce_product_variation_title_attributes_separator', ' - ', new \WC_Product() );
+ $settings = $this->add_settings_ui_schema( $settings );
if ( ! empty( $preload_data_endpoints ) ) {
$settings['dataEndpoints'] = isset( $settings['dataEndpoints'] )
? $settings['dataEndpoints']
@@ -399,4 +401,106 @@ class Settings {
}
return $settings;
}
+
+ /**
+ * Add the settings UI schema for the current classic settings page.
+ *
+ * @param array $settings Array of component settings.
+ * @return array
+ */
+ private function add_settings_ui_schema( array $settings ): array {
+ if ( ! PageController::is_settings_page() || ! Features::is_enabled( 'settings-ui' ) || ! current_user_can( 'manage_woocommerce' ) ) {
+ return $settings;
+ }
+
+ $settings_ui_page = $this->get_current_settings_ui_page();
+ if ( ! $settings_ui_page ) {
+ return $settings;
+ }
+
+ $section = $this->get_current_settings_section();
+ $section_key = '' === $section ? 'default' : $section;
+ $page_id = $settings_ui_page->get_page_id();
+
+ if ( ! isset( $settings['settingsUI'] ) || ! is_array( $settings['settingsUI'] ) ) {
+ $settings['settingsUI'] = array();
+ }
+ if ( ! isset( $settings['settingsUI'][ $page_id ] ) || ! is_array( $settings['settingsUI'][ $page_id ] ) ) {
+ $settings['settingsUI'][ $page_id ] = array();
+ }
+
+ try {
+ $settings['settingsUI'][ $page_id ][ $section_key ] = $settings_ui_page->get_schema( $section );
+ } catch ( \Throwable $e ) {
+ $GLOBALS['wc_settings_ui_schema_failed'][ $page_id ][ $section_key ] = true;
+
+ if ( $e instanceof \Exception ) {
+ wc_caught_exception( $e, __CLASS__ . '::' . __FUNCTION__ );
+ }
+ }
+
+ return $settings;
+ }
+
+ /**
+ * Get the settings UI adapter for the current settings tab.
+ *
+ * @return SettingsUIPageInterface|null
+ */
+ private function get_current_settings_ui_page(): ?SettingsUIPageInterface {
+ if ( ! class_exists( '\WC_Admin_Settings' ) ) {
+ return null;
+ }
+
+ $current_tab = $this->get_current_settings_tab();
+ foreach ( \WC_Admin_Settings::get_settings_pages() as $settings_page ) {
+ if ( ! $settings_page instanceof \WC_Settings_Page || $settings_page->get_id() !== $current_tab ) {
+ continue;
+ }
+
+ $settings_ui_page = $settings_page->get_settings_ui_page();
+ return $settings_ui_page instanceof SettingsUIPageInterface ? $settings_ui_page : null;
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the current WooCommerce settings tab.
+ *
+ * @return string
+ */
+ private function get_current_settings_tab(): string {
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ if ( ! isset( $_GET['tab'] ) ) {
+ return 'general';
+ }
+
+ $tab = wp_unslash( $_GET['tab'] );
+ // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+
+ if ( ! is_string( $tab ) ) {
+ return 'general';
+ }
+
+ $tab = sanitize_title( $tab );
+ return '' !== $tab ? $tab : 'general';
+ }
+
+ /**
+ * Get the current WooCommerce settings section.
+ *
+ * @return string
+ */
+ private function get_current_settings_section(): string {
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ if ( ! isset( $_GET['section'] ) ) {
+ return '';
+ }
+
+ $section = wp_unslash( $_GET['section'] );
+ // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+
+ return is_string( $section ) ? sanitize_title( $section ) : '';
+ }
}
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/LegacySettingsPageAdapter.php b/plugins/woocommerce/src/Internal/Admin/Settings/LegacySettingsPageAdapter.php
new file mode 100644
index 00000000000..2a9ee9aa9c4
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/LegacySettingsPageAdapter.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * Legacy WC_Settings_Page adapter for settings UI.
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Internal\Admin\Settings;
+
+use Automattic\WooCommerce\Admin\Settings\SettingsUIPageInterface as PublicSettingsUIPageInterface;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Adapts a WC_Settings_Page instance into the settings UI page contract.
+ *
+ * Internal implementation of the legacy settings adapter. Extensions should use
+ * Automattic\WooCommerce\Admin\Settings\LegacySettingsPageAdapter.
+ *
+ * @since 10.9.0
+ */
+class LegacySettingsPageAdapter implements PublicSettingsUIPageInterface {
+
+ /**
+ * Legacy settings page.
+ *
+ * @var \WC_Settings_Page
+ */
+ protected \WC_Settings_Page $settings_page;
+
+ /**
+ * Constructor.
+ *
+ * @since 10.9.0
+ *
+ * @param \WC_Settings_Page $settings_page Legacy settings page.
+ */
+ public function __construct( \WC_Settings_Page $settings_page ) {
+ $this->settings_page = $settings_page;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function get_page_id(): string {
+ return $this->settings_page->get_id();
+ }
+
+ /**
+ * Build the canonical settings schema for a section.
+ *
+ * @param string $section Section id. Empty string means the default section.
+ * @return array
+ */
+ public function get_schema( string $section ): array {
+ $schema = SettingsUISchema::from_legacy_settings(
+ $this->settings_page->get_id(),
+ $section,
+ $this->settings_page->get_label(),
+ $this->settings_page->get_settings( $section ),
+ $this->get_save_adapter( $section )
+ );
+
+ $schema['shell']['sectionNavigation'] = $this->get_section_navigation( $section );
+
+ return $schema;
+ }
+
+ /**
+ * Get secondary settings section navigation for the settings UI shell.
+ *
+ * @param string $current_section Current section id.
+ * @return array<int, array{id: string, label: string, href: string, active: bool}>
+ */
+ private function get_section_navigation( string $current_section ): array {
+ $sections = $this->settings_page->get_sections();
+ if ( empty( $sections ) || 1 === count( $sections ) ) {
+ return array();
+ }
+
+ $navigation = array();
+ foreach ( $sections as $id => $label ) {
+ $section_id = (string) $id;
+ $navigation[] = array(
+ 'id' => '' === $section_id ? 'default' : $section_id,
+ 'label' => wp_strip_all_tags( html_entity_decode( (string) $label, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) ),
+ 'href' => add_query_arg(
+ array(
+ 'page' => 'wc-settings',
+ 'tab' => sanitize_title( $this->settings_page->get_id() ),
+ 'section' => sanitize_title( $section_id ),
+ ),
+ admin_url( 'admin.php' )
+ ),
+ 'active' => $current_section === $section_id,
+ );
+ }
+
+ return $navigation;
+ }
+
+ /**
+ * Get script handles that must be loaded before the settings UI app mounts.
+ *
+ * @param string $section Section id. Empty string means the default section.
+ * @return string[]
+ */
+ public function get_script_handles( string $section ): array {
+ return array();
+ }
+
+ /**
+ * Get the default save adapter for fields on this page.
+ *
+ * @param string $section Section id. Empty string means the default section.
+ * @return string
+ */
+ public function get_save_adapter( string $section ): string {
+ return 'form_post';
+ }
+}
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/SettingsUIPageInterface.php b/plugins/woocommerce/src/Internal/Admin/Settings/SettingsUIPageInterface.php
new file mode 100644
index 00000000000..383b65034c4
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/SettingsUIPageInterface.php
@@ -0,0 +1,19 @@
+<?php
+/**
+ * Settings UI page contract.
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Internal\Admin\Settings;
+
+use Automattic\WooCommerce\Admin\Settings\SettingsUIPageInterface as PublicSettingsUIPageInterface;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Internal alias for the public settings UI page contract.
+ *
+ * @since 10.9.0
+ */
+interface SettingsUIPageInterface extends PublicSettingsUIPageInterface {}
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/SettingsUIPages/ProductsSettingsPageAdapter.php b/plugins/woocommerce/src/Internal/Admin/Settings/SettingsUIPages/ProductsSettingsPageAdapter.php
new file mode 100644
index 00000000000..c88062a42bc
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/SettingsUIPages/ProductsSettingsPageAdapter.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Products settings adapter for settings UI.
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Internal\Admin\Settings\SettingsUIPages;
+
+use Automattic\WooCommerce\Admin\Settings\LegacySettingsPageAdapter;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Adapts the WooCommerce Products settings page for the settings UI renderer.
+ *
+ * @since 10.9.0
+ */
+final class ProductsSettingsPageAdapter extends LegacySettingsPageAdapter {
+
+ /**
+ * Build the canonical settings schema for a section.
+ *
+ * @param string $section Section id. Empty string means the default section.
+ * @return array
+ */
+ public function get_schema( string $section ): array {
+ $schema = parent::get_schema( $section );
+
+ $schema['shell']['title'] = __( 'Product settings', 'woocommerce' );
+
+ if ( '' === $section ) {
+ $schema = $this->with_field_options(
+ $schema,
+ 'woocommerce_shop_page_id',
+ $this->get_page_options()
+ );
+ }
+
+ return $schema;
+ }
+
+ /**
+ * Add options to a field in a schema.
+ *
+ * @param array $schema Schema.
+ * @param string $field_id Field id.
+ * @param array $options Field options.
+ * @return array
+ */
+ private function with_field_options( array $schema, string $field_id, array $options ): array {
+ if ( empty( $options ) || ! isset( $schema['groups'] ) || ! is_array( $schema['groups'] ) ) {
+ return $schema;
+ }
+
+ foreach ( $schema['groups'] as $group_id => $group ) {
+ if ( ! isset( $group['fields'] ) || ! is_array( $group['fields'] ) ) {
+ continue;
+ }
+
+ foreach ( $group['fields'] as $field_index => $field ) {
+ if ( ! is_array( $field ) || ( $field['id'] ?? null ) !== $field_id ) {
+ continue;
+ }
+
+ $schema['groups'][ $group_id ]['fields'][ $field_index ]['options'] = $options;
+ return $schema;
+ }
+ }
+
+ return $schema;
+ }
+
+ /**
+ * Build the page options for the shop page selector.
+ *
+ * @return array<int, array{label: string, value: string}>
+ */
+ private function get_page_options(): array {
+ $pages = get_pages(
+ array(
+ 'sort_column' => 'menu_order',
+ 'sort_order' => 'ASC',
+ 'post_status' => array( 'publish', 'private', 'draft' ),
+ )
+ );
+
+ $options = array(
+ array(
+ 'label' => __( 'Select a page...', 'woocommerce' ),
+ 'value' => '',
+ ),
+ );
+
+ if ( ! is_array( $pages ) ) {
+ return $options;
+ }
+
+ foreach ( $pages as $page ) {
+ $options[] = array(
+ 'label' => wp_strip_all_tags( $page->post_title ),
+ 'value' => (string) $page->ID,
+ );
+ }
+
+ return $options;
+ }
+}
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/SettingsUISchema.php b/plugins/woocommerce/src/Internal/Admin/Settings/SettingsUISchema.php
new file mode 100644
index 00000000000..8e64164af5e
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/SettingsUISchema.php
@@ -0,0 +1,460 @@
+<?php
+/**
+ * Settings UI schema builder.
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Internal\Admin\Settings;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Builds the canonical settings schema consumed by the settings UI renderer.
+ *
+ * @since 10.9.0
+ */
+class SettingsUISchema {
+
+ /**
+ * Default group id for fields before the first title marker.
+ *
+ * @var string
+ */
+ private const DEFAULT_GROUP_ID = 'default';
+
+ /**
+ * Build a schema from a legacy WC settings array.
+ *
+ * @since 10.9.0
+ *
+ * @param string $page_id Settings page id.
+ * @param string $section Section id. Empty string means the default section.
+ * @param string $title Page title.
+ * @param array $settings Legacy settings definitions.
+ * @param string $default_save_adapter Default save adapter.
+ * @return array
+ */
+ public static function from_legacy_settings( string $page_id, string $section, string $title, array $settings, string $default_save_adapter = 'form_post' ): array {
+ $groups = array();
+ $current_group = null;
+ $current_id = null;
+ $group_index = 0;
+ $visibility_controller = null;
+
+ foreach ( $settings as $setting ) {
+ if ( ! is_array( $setting ) ) {
+ continue;
+ }
+
+ $type = isset( $setting['type'] ) && is_string( $setting['type'] ) ? $setting['type'] : 'text';
+
+ if ( 'title' === $type ) {
+ $visibility_controller = null;
+ if ( $current_group && $current_id ) {
+ $groups[ $current_id ] = $current_group;
+ }
+
+ $current_id = isset( $setting['id'] ) && is_scalar( $setting['id'] ) && '' !== (string) $setting['id']
+ ? (string) $setting['id']
+ : 'group_' . $group_index;
+ $current_group = array(
+ 'id' => $current_id,
+ 'title' => isset( $setting['title'] ) && is_scalar( $setting['title'] ) ? html_entity_decode( (string) $setting['title'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) : '',
+ 'description' => isset( $setting['desc'] ) && is_scalar( $setting['desc'] ) ? wp_kses_post( (string) $setting['desc'] ) : '',
+ 'actions' => self::get_group_actions( $setting ),
+ 'order' => isset( $setting['order'] ) ? (int) $setting['order'] : $group_index,
+ 'fields' => array(),
+ );
+ ++$group_index;
+ continue;
+ }
+
+ if ( 'sectionend' === $type ) {
+ $visibility_controller = null;
+ if ( $current_group && $current_id ) {
+ $groups[ $current_id ] = $current_group;
+ }
+ $current_group = null;
+ $current_id = null;
+ continue;
+ }
+
+ if ( empty( $setting['id'] ) ) {
+ continue;
+ }
+
+ if ( ! $current_group ) {
+ $current_id = self::DEFAULT_GROUP_ID;
+ $current_group = self::get_default_group( $group_index );
+ ++$group_index;
+ }
+
+ $field = self::transform_legacy_field( $setting, $default_save_adapter, $visibility_controller );
+ if ( $field ) {
+ $current_group['fields'][] = $field;
+ }
+
+ if ( 'checkbox' === $type && 'option' === ( $setting['show_if_checked'] ?? null ) ) {
+ $visibility_controller = $field['id'] ?? null;
+ }
+
+ if ( 'end' === ( $setting['checkboxgroup'] ?? null ) ) {
+ $visibility_controller = null;
+ }
+ }
+
+ if ( $current_group && $current_id ) {
+ $groups[ $current_id ] = $current_group;
+ }
+
+ uasort(
+ $groups,
+ static function ( array $a, array $b ): int {
+ return ( $a['order'] ?? 999 ) <=> ( $b['order'] ?? 999 );
+ }
+ );
+
+ $decoded_title = html_entity_decode( $title, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 );
+
+ return array(
+ 'id' => $page_id,
+ 'title' => $decoded_title,
+ 'section' => '' === $section ? self::DEFAULT_GROUP_ID : $section,
+ 'save' => array(
+ 'adapter' => $default_save_adapter,
+ ),
+ 'shell' => array(
+ 'title' => $decoded_title,
+ ),
+ 'groups' => $groups,
+ );
+ }
+
+ /**
+ * Transform a legacy field into the canonical schema.
+ *
+ * @param array $setting Legacy field definition.
+ * @param string $default_save_adapter Default save adapter.
+ * @param string|null $visibility_controller Current checkbox group controller.
+ * @return array|null
+ */
+ private static function transform_legacy_field( array $setting, string $default_save_adapter, ?string $visibility_controller = null ): ?array {
+ $id = isset( $setting['id'] ) && is_scalar( $setting['id'] ) ? (string) $setting['id'] : '';
+ $type = isset( $setting['type'] ) && is_string( $setting['type'] ) ? $setting['type'] : 'text';
+ if ( '' === $id ) {
+ return null;
+ }
+
+ $canonical_type = self::normalize_type( $type );
+ $field = array(
+ 'id' => $id,
+ 'label' => self::get_field_label( $setting, $id, $type ),
+ 'type' => $canonical_type,
+ 'description' => self::get_field_description( $setting, $type ),
+ 'value' => self::get_field_value( $setting, $canonical_type ),
+ 'save' => self::get_save_schema( $setting, $default_save_adapter ),
+ );
+
+ foreach ( array( 'component', 'placeholder', 'disabled' ) as $key ) {
+ if ( array_key_exists( $key, $setting ) ) {
+ $field[ $key ] = $setting[ $key ];
+ }
+ }
+
+ if ( isset( $setting['custom_attributes'] ) && is_array( $setting['custom_attributes'] ) ) {
+ $field['customAttributes'] = self::get_custom_attributes( $setting['custom_attributes'] );
+ }
+
+ $visibility = self::get_field_visibility( $setting, $visibility_controller );
+ if ( $visibility ) {
+ $field['visibility'] = $visibility;
+ }
+
+ $options = self::get_options( $setting );
+ if ( ! empty( $options ) ) {
+ $field['options'] = $options;
+ }
+
+ if ( 'info' === $type && '' === $field['description'] && isset( $setting['text'] ) && is_scalar( $setting['text'] ) ) {
+ $field['description'] = wp_kses_post( (string) $setting['text'] );
+ $field['save'] = array( 'adapter' => 'none' );
+ }
+
+ return $field;
+ }
+
+ /**
+ * Get a field label.
+ *
+ * @param array $setting Legacy field definition.
+ * @param string $id Field id.
+ * @param string $type Raw field type.
+ * @return string
+ */
+ private static function get_field_label( array $setting, string $id, string $type ): string {
+ if ( 'checkbox' === $type && isset( $setting['desc'] ) && is_scalar( $setting['desc'] ) && '' !== (string) $setting['desc'] ) {
+ return wp_strip_all_tags( html_entity_decode( (string) $setting['desc'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) );
+ }
+
+ foreach ( array( 'title', 'name' ) as $key ) {
+ if ( isset( $setting[ $key ] ) && is_scalar( $setting[ $key ] ) && '' !== (string) $setting[ $key ] ) {
+ return wp_strip_all_tags( html_entity_decode( (string) $setting[ $key ], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) );
+ }
+ }
+
+ return $id;
+ }
+
+ /**
+ * Get a field description.
+ *
+ * @param array $setting Legacy field definition.
+ * @param string $type Raw field type.
+ * @return string
+ */
+ private static function get_field_description( array $setting, string $type ): string {
+ $description = 'checkbox' === $type || ! isset( $setting['desc'] ) || ! is_scalar( $setting['desc'] )
+ ? ''
+ : wp_kses_post( (string) $setting['desc'] );
+
+ $desc_tip = isset( $setting['desc_tip'] ) && is_string( $setting['desc_tip'] ) && '' !== $setting['desc_tip']
+ ? wp_kses_post( $setting['desc_tip'] )
+ : '';
+
+ if ( '' === $description ) {
+ return $desc_tip;
+ }
+
+ if ( '' === $desc_tip ) {
+ return $description;
+ }
+
+ return $description . '<br />' . $desc_tip;
+ }
+
+ /**
+ * Normalize legacy field type.
+ *
+ * @param string $type Legacy field type.
+ * @return string
+ */
+ private static function normalize_type( string $type ): string {
+ $type_map = array(
+ 'multiselect' => 'array',
+ 'multi_select_countries' => 'array',
+ 'single_select_country' => 'select',
+ 'single_select_page' => 'select',
+ );
+
+ return $type_map[ $type ] ?? $type;
+ }
+
+ /**
+ * Get a field value.
+ *
+ * @param array $setting Legacy field definition.
+ * @param string $type Canonical field type.
+ * @return mixed
+ */
+ private static function get_field_value( array $setting, string $type ) {
+ if ( array_key_exists( 'value', $setting ) ) {
+ return self::normalize_value( $setting['value'], $type );
+ }
+
+ $default = $setting['default'] ?? '';
+ $value = \WC_Admin_Settings::get_option( (string) $setting['id'], $default );
+
+ return self::normalize_value( $value, $type );
+ }
+
+ /**
+ * Normalize a value for the canonical schema.
+ *
+ * @param mixed $value Field value.
+ * @param string $type Canonical type.
+ * @return mixed
+ */
+ private static function normalize_value( $value, string $type ) {
+ switch ( $type ) {
+ case 'array':
+ return is_array( $value ) ? array_values( $value ) : array();
+ case 'checkbox':
+ return function_exists( 'wc_string_to_bool' ) ? wc_string_to_bool( $value ) : (bool) $value;
+ default:
+ return $value;
+ }
+ }
+
+ /**
+ * Get a field save schema.
+ *
+ * @param array $setting Legacy field definition.
+ * @param string $default_save_adapter Default save adapter.
+ * @return array
+ */
+ private static function get_save_schema( array $setting, string $default_save_adapter ): array {
+ if ( isset( $setting['save'] ) && is_array( $setting['save'] ) ) {
+ return $setting['save'];
+ }
+
+ if ( isset( $setting['is_option'] ) && false === $setting['is_option'] ) {
+ return array( 'adapter' => 'none' );
+ }
+
+ $field_name = isset( $setting['field_name'] ) && is_scalar( $setting['field_name'] )
+ ? (string) $setting['field_name']
+ : (string) $setting['id'];
+
+ return array(
+ 'adapter' => $default_save_adapter,
+ 'name' => $field_name,
+ );
+ }
+
+ /**
+ * Get visibility metadata for legacy conditional fields.
+ *
+ * @param array $setting Legacy field definition.
+ * @param string|null $visibility_controller Current checkbox group controller.
+ * @return array|null
+ */
+ private static function get_field_visibility( array $setting, ?string $visibility_controller ): ?array {
+ $class_names = isset( $setting['class'] ) && is_string( $setting['class'] ) ? explode( ' ', $setting['class'] ) : array();
+ if ( in_array( 'manage_stock_field', $class_names, true ) ) {
+ return array(
+ 'controller' => 'woocommerce_manage_stock',
+ 'value' => true,
+ );
+ }
+
+ if ( 'yes' === ( $setting['show_if_checked'] ?? null ) && $visibility_controller ) {
+ return array(
+ 'controller' => $visibility_controller,
+ 'value' => true,
+ );
+ }
+
+ return null;
+ }
+
+ /**
+ * Normalize field options.
+ *
+ * @param array $setting Legacy field definition.
+ * @return array
+ */
+ private static function get_options( array $setting ): array {
+ if ( ! isset( $setting['options'] ) || ! is_array( $setting['options'] ) ) {
+ return array();
+ }
+
+ $options = array();
+ foreach ( $setting['options'] as $value => $label ) {
+ if ( ! is_scalar( $label ) && null !== $label ) {
+ continue;
+ }
+
+ $options[] = array(
+ 'label' => is_scalar( $label ) ? wp_strip_all_tags( html_entity_decode( (string) $label, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) ) : '',
+ 'value' => (string) $value,
+ );
+ }
+
+ return $options;
+ }
+
+ /**
+ * Normalize custom attributes for React controls.
+ *
+ * @param array $custom_attributes Raw custom attributes.
+ * @return array
+ */
+ private static function get_custom_attributes( array $custom_attributes ): array {
+ $attributes = array();
+
+ foreach ( $custom_attributes as $attribute => $value ) {
+ if ( ! is_scalar( $value ) ) {
+ continue;
+ }
+
+ $attribute_key = sanitize_key( (string) $attribute );
+ if ( '' === $attribute_key ) {
+ continue;
+ }
+
+ $attributes[ $attribute_key ] = $value;
+ }
+
+ return $attributes;
+ }
+
+ /**
+ * Normalize group header actions.
+ *
+ * @param array $setting Legacy title setting definition.
+ * @return array
+ */
+ private static function get_group_actions( array $setting ): array {
+ if ( empty( $setting['actions'] ) || ! is_array( $setting['actions'] ) ) {
+ return array();
+ }
+
+ $actions = array();
+
+ foreach ( $setting['actions'] as $index => $action ) {
+ if ( ! is_array( $action ) || empty( $action['label'] ) || ! is_scalar( $action['label'] ) ) {
+ continue;
+ }
+
+ $href = $action['href'] ?? $action['url'] ?? '';
+ if ( ! is_scalar( $href ) || '' === (string) $href ) {
+ continue;
+ }
+
+ $href = esc_url_raw( (string) $href );
+ if ( '' === $href ) {
+ continue;
+ }
+
+ $normalized_action = array(
+ 'id' => isset( $action['id'] ) && is_scalar( $action['id'] ) ? sanitize_key( (string) $action['id'] ) : 'action_' . $index,
+ 'label' => wp_strip_all_tags( html_entity_decode( (string) $action['label'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) ),
+ 'href' => $href,
+ );
+
+ if ( isset( $action['variant'] ) && is_scalar( $action['variant'] ) ) {
+ $normalized_action['variant'] = sanitize_key( (string) $action['variant'] );
+ }
+
+ if ( isset( $action['target'] ) && is_scalar( $action['target'] ) && in_array( (string) $action['target'], array( '_blank', '_self', '_parent', '_top' ), true ) ) {
+ $normalized_action['target'] = (string) $action['target'];
+ }
+
+ if ( isset( $action['rel'] ) && is_scalar( $action['rel'] ) ) {
+ $normalized_action['rel'] = sanitize_text_field( (string) $action['rel'] );
+ }
+
+ $actions[] = $normalized_action;
+ }
+
+ return $actions;
+ }
+
+ /**
+ * Get the default group.
+ *
+ * @param int $order Group order.
+ * @return array
+ */
+ private static function get_default_group( int $order ): array {
+ return array(
+ 'id' => self::DEFAULT_GROUP_ID,
+ 'title' => '',
+ 'description' => '',
+ 'actions' => array(),
+ 'order' => $order,
+ 'fields' => array(),
+ );
+ }
+}
diff --git a/plugins/woocommerce/src/Internal/Admin/WCAdminAssets.php b/plugins/woocommerce/src/Internal/Admin/WCAdminAssets.php
index b50685cac45..c6092fa6d6e 100644
--- a/plugins/woocommerce/src/Internal/Admin/WCAdminAssets.php
+++ b/plugins/woocommerce/src/Internal/Admin/WCAdminAssets.php
@@ -8,6 +8,7 @@ namespace Automattic\WooCommerce\Internal\Admin;
use _WP_Dependency;
use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Admin\PageController;
+use Automattic\WooCommerce\Admin\Settings\SettingsUIPageInterface;
use Automattic\WooCommerce\Internal\Admin\Loader;
use Automattic\WooCommerce\Utilities\FeaturesUtil;
/**
@@ -250,7 +251,7 @@ class WCAdminAssets {
wp_enqueue_style( 'wc-onboarding' );
if ( PageController::is_settings_page() ) {
- $this->register_script( 'wp-admin-scripts', 'settings-embed', true );
+ $this->register_script( 'wp-admin-scripts', 'settings-embed', true, $this->get_settings_ui_script_dependencies() );
$this->register_style( 'settings-embed', 'style', array( 'wp-components' ) );
}
@@ -266,6 +267,13 @@ class WCAdminAssets {
* @return array Modified dependencies.
*/
private function modify_script_dependencies( $dependencies, $script ) {
+ $dependencies = array_map(
+ static function ( $dependency ) {
+ return 'wp-route' === $dependency ? 'wp-router' : $dependency;
+ },
+ $dependencies
+ );
+
switch ( $script ) {
case WC_ADMIN_APP:
// Remove wp-editor dependency if we're not on a customize store page since we don't use wp-editor in other pages.
@@ -322,6 +330,7 @@ class WCAdminAssets {
'wc-block-templates',
'wc-experimental-products-app',
'wc-product-editor',
+ 'wc-settings-ui-sdk',
'wc-remote-logging',
'wc-sanitize',
);
@@ -341,6 +350,7 @@ class WCAdminAssets {
'wc-experimental',
'wc-navigation',
'wc-product-editor',
+ 'wc-settings-ui-sdk',
WC_ADMIN_APP,
);
@@ -439,6 +449,110 @@ class WCAdminAssets {
}
}
+ /**
+ * Get extension script handles that must load before the settings embed app mounts.
+ *
+ * @return array
+ */
+ private function get_settings_ui_script_dependencies(): array {
+ if ( ! PageController::is_settings_page() || ! Features::is_enabled( 'settings-ui' ) || ! current_user_can( 'manage_woocommerce' ) ) {
+ return array();
+ }
+
+ $settings_ui_page = $this->get_current_settings_ui_page();
+ if ( ! $settings_ui_page ) {
+ return array();
+ }
+
+ $extension_handles = array();
+ try {
+ $extension_handles = $settings_ui_page->get_script_handles( $this->get_current_settings_section() );
+ } catch ( \Throwable $e ) {
+ if ( $e instanceof \Exception ) {
+ wc_caught_exception( $e, __CLASS__ . '::' . __FUNCTION__ );
+ }
+ }
+
+ /**
+ * Extension-provided handles may violate the interface contract.
+ *
+ * @var mixed[] $extension_handles
+ */
+ $dependencies = array_merge(
+ array( 'wc-settings-ui-sdk' ),
+ array_filter(
+ $extension_handles,
+ static function ( $script_handle ): bool {
+ return is_string( $script_handle ) && '' !== $script_handle;
+ }
+ )
+ );
+
+ return array_values( array_unique( $dependencies ) );
+ }
+
+ /**
+ * Get the settings UI adapter for the current settings tab.
+ *
+ * @return SettingsUIPageInterface|null
+ */
+ private function get_current_settings_ui_page(): ?SettingsUIPageInterface {
+ if ( ! class_exists( '\WC_Admin_Settings' ) ) {
+ return null;
+ }
+
+ $current_tab = $this->get_current_settings_tab();
+ foreach ( \WC_Admin_Settings::get_settings_pages() as $settings_page ) {
+ if ( ! $settings_page instanceof \WC_Settings_Page || $settings_page->get_id() !== $current_tab ) {
+ continue;
+ }
+
+ $settings_ui_page = $settings_page->get_settings_ui_page();
+ return $settings_ui_page instanceof SettingsUIPageInterface ? $settings_ui_page : null;
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the current WooCommerce settings tab.
+ *
+ * @return string
+ */
+ private function get_current_settings_tab(): string {
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ if ( ! isset( $_GET['tab'] ) ) {
+ return 'general';
+ }
+
+ $tab = wp_unslash( $_GET['tab'] );
+ // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+
+ if ( ! is_string( $tab ) ) {
+ return 'general';
+ }
+
+ $tab = sanitize_title( $tab );
+ return '' !== $tab ? $tab : 'general';
+ }
+
+ /**
+ * Get the current WooCommerce settings section.
+ *
+ * @return string
+ */
+ private function get_current_settings_section(): string {
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ if ( ! isset( $_GET['section'] ) ) {
+ return '';
+ }
+
+ $section = wp_unslash( $_GET['section'] );
+ // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+
+ return is_string( $section ) ? sanitize_title( $section ) : '';
+ }
+
/**
* Injects wp-shared-settings as a dependency if it's present.
*/
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIFeatureFlagTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIFeatureFlagTest.php
new file mode 100644
index 00000000000..a3141a1c03a
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIFeatureFlagTest.php
@@ -0,0 +1,316 @@
+<?php
+/**
+ * Settings UI feature flag tests.
+ *
+ * @package WooCommerce\Tests\Internal\Admin\Settings
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\Admin\Settings;
+
+use Automattic\WooCommerce\Internal\Admin\Settings;
+use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for the settings UI feature flag boundary.
+ */
+class SettingsUIFeatureFlagTest extends WC_Unit_Test_Case {
+
+ /**
+ * Original request globals.
+ *
+ * @var array
+ */
+ private array $original_get = array();
+
+ /**
+ * Original current settings section.
+ *
+ * @var mixed
+ */
+ private $original_current_section = null;
+
+ /**
+ * Original current settings tab.
+ *
+ * @var mixed
+ */
+ private $original_current_tab = null;
+
+ /**
+ * Whether the hide save button global existed before the test.
+ *
+ * @var bool
+ */
+ private bool $original_hide_save_button_exists = false;
+
+ /**
+ * Original hide save button global value.
+ *
+ * @var mixed
+ */
+ private $original_hide_save_button = null;
+
+ /**
+ * Set up test environment.
+ */
+ public function setUp(): void {
+ parent::setUp();
+
+ include_once WC_ABSPATH . 'includes/admin/class-wc-admin-settings.php';
+ include_once WC_ABSPATH . 'includes/admin/settings/class-wc-settings-page.php';
+
+ global $current_section, $current_tab;
+
+ $this->original_get = $_GET; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $this->original_current_section = $current_section ?? null;
+ $this->original_current_tab = $current_tab ?? null;
+ $this->original_hide_save_button_exists = array_key_exists( 'hide_save_button', $GLOBALS );
+ $this->original_hide_save_button = $this->original_hide_save_button_exists ? $GLOBALS['hide_save_button'] : null;
+ unset( $GLOBALS['hide_save_button'] );
+ }
+
+ /**
+ * Tear down test environment.
+ */
+ public function tearDown(): void {
+ global $current_section, $current_tab;
+
+ $_GET = $this->original_get;
+ $current_section = $this->original_current_section;
+ $current_tab = $this->original_current_tab;
+
+ if ( $this->original_hide_save_button_exists ) {
+ $GLOBALS['hide_save_button'] = $this->original_hide_save_button;
+ } else {
+ unset( $GLOBALS['hide_save_button'] );
+ }
+
+ remove_filter( 'woocommerce_admin_features', array( $this, 'enable_settings_ui_feature' ) );
+ remove_filter( 'woocommerce_admin_features', array( $this, 'disable_settings_ui_feature' ) );
+
+ parent::tearDown();
+ }
+
+ /**
+ * It keeps opted-in pages on the legacy renderer when the feature flag is disabled.
+ */
+ public function test_opted_in_page_uses_legacy_output_when_feature_flag_is_disabled(): void {
+ add_filter( 'woocommerce_admin_features', array( $this, 'disable_settings_ui_feature' ) );
+
+ global $current_section;
+ $current_section = '';
+ $page = $this->get_settings_ui_test_page();
+
+ ob_start();
+ $page->output();
+ $output = ob_get_clean();
+
+ $this->assertStringContainsString( 'name="woocommerce_settings_ui_flag_test"', $output );
+ $this->assertStringNotContainsString( 'data-wc-settings-ui="1"', $output );
+ $this->assertArrayNotHasKey( 'hide_save_button', $GLOBALS );
+ }
+
+ /**
+ * It renders the settings UI mount point only when the feature flag is enabled.
+ */
+ public function test_opted_in_page_uses_settings_ui_output_when_feature_flag_is_enabled(): void {
+ add_filter( 'woocommerce_admin_features', array( $this, 'enable_settings_ui_feature' ) );
+
+ global $current_section;
+ $current_section = '';
+ $page = $this->get_settings_ui_test_page();
+
+ ob_start();
+ $page->output();
+ $output = ob_get_clean();
+
+ $this->assertStringContainsString( 'data-wc-settings-ui="1"', $output );
+ $this->assertStringContainsString( 'data-wc-settings-page="settings_ui_flag_test"', $output );
+ $this->assertStringNotContainsString( 'name="woocommerce_settings_ui_flag_test"', $output );
+ $this->assertTrue( $GLOBALS['hide_save_button'] );
+ }
+
+ /**
+ * It exposes section navigation metadata from legacy settings pages.
+ */
+ public function test_legacy_adapter_adds_shell_navigation_metadata(): void {
+ $page = $this->get_settings_ui_page_with_sections();
+ $adapter = new \Automattic\WooCommerce\Admin\Settings\LegacySettingsPageAdapter( $page );
+ $schema = $adapter->get_schema( '' );
+
+ $this->assertSame( 'Settings UI flag test', $schema['shell']['title'] );
+ $this->assertArrayNotHasKey( 'breadcrumbs', $schema['shell'] );
+ $this->assertArrayNotHasKey( 'navigation', $schema['shell'] );
+ $this->assertSame( 'General', $schema['shell']['sectionNavigation'][0]['label'] );
+ $this->assertTrue( $schema['shell']['sectionNavigation'][0]['active'] );
+ $this->assertSame( 'inventory', $schema['shell']['sectionNavigation'][1]['id'] );
+ }
+
+ /**
+ * It does not inject settings UI shared data when the feature flag is disabled.
+ */
+ public function test_shared_settings_are_not_injected_when_feature_flag_is_disabled(): void {
+ add_filter( 'woocommerce_admin_features', array( $this, 'disable_settings_ui_feature' ) );
+
+ $_GET['page'] = 'wc-settings';
+ $_GET['tab'] = 'products';
+
+ $settings = $this->invoke_private_method( new Settings(), 'add_settings_ui_schema', array( array() ) );
+
+ $this->assertArrayNotHasKey( 'settingsUI', $settings );
+ }
+
+ /**
+ * It does not add settings UI script dependencies when the feature flag is disabled.
+ */
+ public function test_settings_ui_script_dependencies_are_empty_when_feature_flag_is_disabled(): void {
+ add_filter( 'woocommerce_admin_features', array( $this, 'disable_settings_ui_feature' ) );
+
+ $_GET['page'] = 'wc-settings';
+ $_GET['tab'] = 'products';
+
+ $dependencies = $this->invoke_private_method( new WCAdminAssets(), 'get_settings_ui_script_dependencies' );
+
+ $this->assertSame( array(), $dependencies );
+ }
+
+ /**
+ * It does not add the settings UI body class when the feature flag is disabled.
+ */
+ public function test_settings_ui_body_class_is_not_added_when_feature_flag_is_disabled(): void {
+ add_filter( 'woocommerce_admin_features', array( $this, 'disable_settings_ui_feature' ) );
+
+ global $current_tab;
+ $current_tab = 'settings_ui_flag_test';
+ $page = $this->get_settings_ui_test_page();
+
+ $classes = $page->add_settings_ui_body_class( 'existing-class' );
+
+ $this->assertSame( 'existing-class', $classes );
+ }
+
+ /**
+ * It adds the settings UI body class when the feature flag is enabled.
+ */
+ public function test_settings_ui_body_class_is_added_when_feature_flag_is_enabled(): void {
+ add_filter( 'woocommerce_admin_features', array( $this, 'enable_settings_ui_feature' ) );
+
+ global $current_tab;
+ $current_tab = 'settings_ui_flag_test';
+ $page = $this->get_settings_ui_test_page();
+
+ $classes = $page->add_settings_ui_body_class( 'existing-class' );
+
+ $this->assertStringContainsString( 'existing-class', $classes );
+ $this->assertStringContainsString( 'woocommerce-settings-ui-page', $classes );
+ }
+
+ /**
+ * Enable the settings UI feature flag.
+ *
+ * @param array $features Feature flags.
+ * @return array
+ */
+ public function enable_settings_ui_feature( array $features ): array {
+ $features[] = 'settings-ui';
+ return array_values( array_unique( $features ) );
+ }
+
+ /**
+ * Disable the settings UI feature flag.
+ *
+ * @param array $features Feature flags.
+ * @return array
+ */
+ public function disable_settings_ui_feature( array $features ): array {
+ return array_values( array_diff( $features, array( 'settings-ui' ) ) );
+ }
+
+ /**
+ * Build a settings page that opts into the settings UI renderer.
+ *
+ * @return \WC_Settings_Page
+ */
+ private function get_settings_ui_test_page(): \WC_Settings_Page {
+ return new class() extends \WC_Settings_Page {
+ /**
+ * Constructor.
+ */
+ public function __construct() {
+ $this->id = 'settings_ui_flag_test';
+ $this->label = 'Settings UI flag test';
+ }
+
+ /**
+ * Get the settings UI page adapter.
+ *
+ * @return \Automattic\WooCommerce\Admin\Settings\SettingsUIPageInterface|null
+ */
+ public function get_settings_ui_page(): ?\Automattic\WooCommerce\Admin\Settings\SettingsUIPageInterface {
+ return new \Automattic\WooCommerce\Admin\Settings\LegacySettingsPageAdapter( $this );
+ }
+
+ /**
+ * Get settings for the default section.
+ *
+ * @return array
+ */
+ protected function get_settings_for_default_section() {
+ return array(
+ array(
+ 'id' => 'woocommerce_settings_ui_flag_test',
+ 'type' => 'text',
+ 'title' => 'Settings UI flag test',
+ ),
+ );
+ }
+ };
+ }
+
+ /**
+ * Build a settings page with multiple sections.
+ *
+ * @return \WC_Settings_Page
+ */
+ private function get_settings_ui_page_with_sections(): \WC_Settings_Page {
+ return new class() extends \WC_Settings_Page {
+ /**
+ * Constructor.
+ */
+ public function __construct() {
+ $this->id = 'settings_ui_flag_test';
+ $this->label = 'Settings UI flag test';
+ }
+
+ /**
+ * Get sections for this test page.
+ *
+ * @return array
+ */
+ protected function get_own_sections() {
+ return array(
+ '' => 'General',
+ 'inventory' => 'Inventory',
+ );
+ }
+ };
+ }
+
+ /**
+ * Invoke a private method for focused feature-flag assertions.
+ *
+ * @param object $target Object instance.
+ * @param string $method_name Method name.
+ * @param array $arguments Method arguments.
+ * @return mixed
+ */
+ private function invoke_private_method( object $target, string $method_name, array $arguments = array() ) {
+ $method = new \ReflectionMethod( $target, $method_name );
+ $method->setAccessible( true );
+
+ return $method->invokeArgs( $target, $arguments );
+ }
+}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIPages/ProductsSettingsPageAdapterTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIPages/ProductsSettingsPageAdapterTest.php
new file mode 100644
index 00000000000..1def530fac3
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIPages/ProductsSettingsPageAdapterTest.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * ProductsSettingsPageAdapter tests.
+ *
+ * @package WooCommerce\Tests\Internal\Admin\Settings\SettingsUIPages
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\Admin\Settings\SettingsUIPages;
+
+use Automattic\WooCommerce\Internal\Admin\Settings\SettingsUIPages\ProductsSettingsPageAdapter;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for ProductsSettingsPageAdapter.
+ */
+class ProductsSettingsPageAdapterTest extends WC_Unit_Test_Case {
+
+ /**
+ * It adds page options to the shop page selector.
+ */
+ public function test_get_schema_adds_shop_page_options(): void {
+ $page_id = self::factory()->post->create(
+ array(
+ 'post_type' => 'page',
+ 'post_status' => 'publish',
+ 'post_title' => 'Shop',
+ )
+ );
+
+ $settings_page = new class() extends \WC_Settings_Page {
+ /**
+ * Constructor.
+ */
+ public function __construct() {
+ $this->id = 'products';
+ $this->label = 'Products';
+ }
+
+ /**
+ * Get settings.
+ *
+ * @param string $current_section Current section.
+ * @return array
+ */
+ public function get_settings( $current_section = '' ) {
+ return array(
+ array(
+ 'id' => 'catalog_options',
+ 'type' => 'title',
+ 'title' => 'Shop pages',
+ ),
+ array(
+ 'id' => 'woocommerce_shop_page_id',
+ 'type' => 'single_select_page',
+ 'title' => 'Shop page',
+ ),
+ array(
+ 'id' => 'catalog_options',
+ 'type' => 'sectionend',
+ ),
+ );
+ }
+ };
+
+ $adapter = new ProductsSettingsPageAdapter( $settings_page );
+ $schema = $adapter->get_schema( '' );
+ $field = $schema['groups']['catalog_options']['fields'][0];
+
+ $this->assertSame( 'woocommerce_shop_page_id', $field['id'] );
+ $this->assertContains(
+ array(
+ 'label' => 'Shop',
+ 'value' => (string) $page_id,
+ ),
+ $field['options']
+ );
+ }
+}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUISchemaTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUISchemaTest.php
new file mode 100644
index 00000000000..1b18c436f35
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUISchemaTest.php
@@ -0,0 +1,397 @@
+<?php
+/**
+ * SettingsUISchema tests.
+ *
+ * @package WooCommerce\Tests\Internal\Admin\Settings
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\Admin\Settings;
+
+use Automattic\WooCommerce\Internal\Admin\Settings\SettingsUISchema;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for SettingsUISchema.
+ */
+class SettingsUISchemaTest extends WC_Unit_Test_Case {
+
+ /**
+ * @testdox It includes page-level save and shell metadata.
+ */
+ public function test_from_legacy_settings_includes_page_save_and_shell_metadata(): void {
+ $schema = SettingsUISchema::from_legacy_settings(
+ 'test',
+ '',
+ 'Test & settings',
+ array(),
+ 'none'
+ );
+
+ $this->assertSame(
+ array( 'adapter' => 'none' ),
+ $schema['save'],
+ 'The page-level save strategy should use the provided default adapter.'
+ );
+ $this->assertSame(
+ array( 'title' => 'Test & settings' ),
+ $schema['shell'],
+ 'The shell title should use the decoded page title.'
+ );
+ }
+
+ /**
+ * @testdox It skips malformed settings entries.
+ */
+ public function test_from_legacy_settings_skips_malformed_settings_entries(): void {
+ $schema = SettingsUISchema::from_legacy_settings(
+ 'test',
+ '',
+ 'Test settings',
+ array(
+ 'not a setting',
+ null,
+ array(
+ 'id' => 'woocommerce_test_text',
+ 'type' => 'text',
+ 'title' => 'Test text',
+ ),
+ )
+ );
+
+ $this->assertCount( 1, $schema['groups']['default']['fields'] );
+ $this->assertSame( 'woocommerce_test_text', $schema['groups']['default']['fields'][0]['id'] );
+ }
+
+ /**
+ * @testdox It groups fields that appear before the first title marker.
+ */
+ public function test_from_legacy_settings_creates_default_group_for_fields_before_title(): void {
+ update_option( 'woocommerce_test_text', 'saved value' );
+
+ $schema = SettingsUISchema::from_legacy_settings(
+ 'test',
+ '',
+ 'Test settings',
+ array(
+ array(
+ 'id' => 'woocommerce_test_text',
+ 'type' => 'text',
+ 'title' => 'Test text',
+ ),
+ )
+ );
+
+ $this->assertArrayHasKey( 'default', $schema['groups'] );
+ $this->assertSame( 'default', array_key_first( $schema['groups'] ) );
+ $this->assertSame( 'woocommerce_test_text', $schema['groups']['default']['fields'][0]['id'] );
+ $this->assertSame( 'saved value', $schema['groups']['default']['fields'][0]['value'] );
+ }
+
+ /**
+ * @testdox It keeps component metadata with the field schema.
+ */
+ public function test_from_legacy_settings_preserves_component_metadata(): void {
+ $schema = SettingsUISchema::from_legacy_settings(
+ 'test',
+ 'advanced',
+ 'Test settings',
+ array(
+ array(
+ 'id' => 'group',
+ 'type' => 'title',
+ 'title' => 'Group',
+ ),
+ array(
+ 'id' => 'woocommerce_test_component',
+ 'type' => 'multiselect',
+ 'title' => 'Component field',
+ 'component' => 'test/component',
+ 'custom_attributes' => array(
+ 'min' => 1,
+ 'step' => 1,
+ ),
+ 'options' => array(
+ 'a' => 'Option A',
+ ),
+ ),
+ )
+ );
+
+ $field = $schema['groups']['group']['fields'][0];
+
+ $this->assertSame( 'array', $field['type'] );
+ $this->assertSame( 'test/component', $field['component'] );
+ $this->assertSame(
+ array(
+ 'min' => 1,
+ 'step' => 1,
+ ),
+ $field['customAttributes']
+ );
+ $this->assertSame(
+ array(
+ array(
+ 'label' => 'Option A',
+ 'value' => 'a',
+ ),
+ ),
+ $field['options']
+ );
+ }
+
+ /**
+ * @testdox It preserves sanitized group description markup and header actions.
+ */
+ public function test_from_legacy_settings_preserves_group_description_and_actions(): void {
+ $schema = SettingsUISchema::from_legacy_settings(
+ 'test',
+ 'advanced',
+ 'Test settings',
+ array(
+ array(
+ 'id' => 'group',
+ 'type' => 'title',
+ 'title' => 'Group',
+ 'desc' => 'Read the <a href="https://woocommerce.com">documentation</a><script>alert("x")</script>.',
+ 'actions' => array(
+ array(
+ 'id' => 'learn-more',
+ 'label' => 'Learn more',
+ 'href' => 'https://woocommerce.com/documentation',
+ 'variant' => 'secondary',
+ 'target' => '_blank',
+ 'rel' => 'noopener noreferrer',
+ ),
+ ),
+ ),
+ )
+ );
+
+ $group = $schema['groups']['group'];
+
+ $this->assertSame( 'Read the <a href="https://woocommerce.com">documentation</a>alert("x").', $group['description'] );
+ $this->assertSame(
+ array(
+ array(
+ 'id' => 'learn-more',
+ 'label' => 'Learn more',
+ 'href' => 'https://woocommerce.com/documentation',
+ 'variant' => 'secondary',
+ 'target' => '_blank',
+ 'rel' => 'noopener noreferrer',
+ ),
+ ),
+ $group['actions']
+ );
+ }
+
+ /**
+ * @testdox It uses checkbox descriptions as labels and desc_tip as help text.
+ */
+ public function test_from_legacy_settings_uses_checkbox_desc_as_label(): void {
+ $schema = SettingsUISchema::from_legacy_settings(
+ 'test',
+ '',
+ 'Test settings',
+ array(
+ array(
+ 'id' => 'woocommerce_test_checkbox',
+ 'type' => 'checkbox',
+ 'title' => 'Checkbox row',
+ 'desc' => 'Enable the test option',
+ 'desc_tip' => 'This is help text.',
+ ),
+ )
+ );
+
+ $field = $schema['groups']['default']['fields'][0];
+
+ $this->assertSame( 'Enable the test option', $field['label'] );
+ $this->assertSame( 'This is help text.', $field['description'] );
+ }
+
+ /**
+ * @testdox It does not render boolean desc_tip values as help text.
+ */
+ public function test_from_legacy_settings_ignores_boolean_desc_tip(): void {
+ $schema = SettingsUISchema::from_legacy_settings(
+ 'test',
+ '',
+ 'Test settings',
+ array(
+ array(
+ 'id' => 'woocommerce_test_select',
+ 'type' => 'select',
+ 'title' => 'Select field',
+ 'desc' => 'Select help text.',
+ 'desc_tip' => true,
+ 'options' => array(
+ 'a' => 'Option A',
+ ),
+ ),
+ )
+ );
+
+ $this->assertSame( 'Select help text.', $schema['groups']['default']['fields'][0]['description'] );
+ }
+
+ /**
+ * @testdox It uses legacy field names for form POST save schema.
+ */
+ public function test_from_legacy_settings_uses_field_name_for_form_post_save_schema(): void {
+ $schema = SettingsUISchema::from_legacy_settings(
+ 'test',
+ '',
+ 'Test settings',
+ array(
+ array(
+ 'id' => 'woocommerce_test_nested',
+ 'type' => 'text',
+ 'title' => 'Nested field',
+ 'field_name' => 'woocommerce_test[nested]',
+ ),
+ )
+ );
+
+ $this->assertSame(
+ array(
+ 'adapter' => 'form_post',
+ 'name' => 'woocommerce_test[nested]',
+ ),
+ $schema['groups']['default']['fields'][0]['save']
+ );
+ }
+
+ /**
+ * @testdox It sanitizes info field text and marks info fields as non-saving.
+ */
+ public function test_from_legacy_settings_sanitizes_info_field_text_and_marks_info_fields_as_non_saving(): void {
+ $schema = SettingsUISchema::from_legacy_settings(
+ 'test',
+ '',
+ 'Test settings',
+ array(
+ array(
+ 'id' => 'woocommerce_test_info',
+ 'type' => 'info',
+ 'text' => 'Read-only <strong>information</strong><script>alert("x")</script>.',
+ ),
+ )
+ );
+
+ $field = $schema['groups']['default']['fields'][0];
+
+ $this->assertSame( 'Read-only <strong>information</strong>alert("x").', $field['description'] );
+ $this->assertSame( array( 'adapter' => 'none' ), $field['save'] );
+ }
+
+ /**
+ * @testdox It preserves both legacy descriptions and string desc_tip values.
+ */
+ public function test_from_legacy_settings_preserves_desc_and_string_desc_tip(): void {
+ $schema = SettingsUISchema::from_legacy_settings(
+ 'test',
+ '',
+ 'Test settings',
+ array(
+ array(
+ 'id' => 'woocommerce_test_text',
+ 'type' => 'text',
+ 'title' => 'Text field',
+ 'desc' => 'Visible help text.',
+ 'desc_tip' => 'Tooltip help text.',
+ ),
+ )
+ );
+
+ $this->assertSame( 'Visible help text.<br />Tooltip help text.', $schema['groups']['default']['fields'][0]['description'] );
+ }
+
+ /**
+ * @testdox It adds visibility metadata for legacy checkbox groups and stock fields.
+ */
+ public function test_from_legacy_settings_adds_visibility_metadata_for_legacy_conditional_fields(): void {
+ $schema = SettingsUISchema::from_legacy_settings(
+ 'test',
+ '',
+ 'Test settings',
+ array(
+ array(
+ 'id' => 'woocommerce_enable_reviews',
+ 'type' => 'checkbox',
+ 'desc' => 'Enable product reviews',
+ 'checkboxgroup' => 'start',
+ 'show_if_checked' => 'option',
+ ),
+ array(
+ 'id' => 'woocommerce_review_rating_required',
+ 'type' => 'checkbox',
+ 'desc' => 'Star ratings should be required',
+ 'checkboxgroup' => 'end',
+ 'show_if_checked' => 'yes',
+ ),
+ array(
+ 'id' => 'woocommerce_hold_stock_minutes',
+ 'type' => 'number',
+ 'title' => 'Hold stock',
+ 'class' => 'manage_stock_field',
+ ),
+ )
+ );
+
+ $fields = $schema['groups']['default']['fields'];
+
+ $this->assertSame(
+ array(
+ 'controller' => 'woocommerce_enable_reviews',
+ 'value' => true,
+ ),
+ $fields[1]['visibility']
+ );
+ $this->assertSame(
+ array(
+ 'controller' => 'woocommerce_manage_stock',
+ 'value' => true,
+ ),
+ $fields[2]['visibility']
+ );
+ }
+
+ /**
+ * @testdox It sanitizes custom attribute keys and option labels.
+ */
+ public function test_from_legacy_settings_sanitizes_custom_attribute_keys_and_option_labels(): void {
+ $schema = SettingsUISchema::from_legacy_settings(
+ 'test',
+ '',
+ 'Test settings',
+ array(
+ array(
+ 'id' => 'woocommerce_test_select',
+ 'type' => 'select',
+ 'title' => 'Select field',
+ 'custom_attributes' => array(
+ 'onChange' => 'alert(1)',
+ 'min' => 1,
+ ),
+ 'options' => array(
+ 'a' => '<strong>Option A</strong>',
+ ),
+ ),
+ )
+ );
+
+ $field = $schema['groups']['default']['fields'][0];
+
+ $this->assertSame(
+ array(
+ 'onchange' => 'alert(1)',
+ 'min' => 1,
+ ),
+ $field['customAttributes']
+ );
+ $this->assertSame( 'Option A', $field['options'][0]['label'] );
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8aef07ff1a1..2dffb4c905e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -24,6 +24,9 @@ catalogs:
'@wordpress/a11y':
specifier: 4.19.1
version: 4.19.1
+ '@wordpress/admin-ui':
+ specifier: 1.12.0
+ version: 1.12.0
'@wordpress/api-fetch':
specifier: 7.19.2
version: 7.19.2
@@ -540,7 +543,7 @@ importers:
version: 14.14.6(@babel/core@7.25.7)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack-virtual-modules@0.6.2)(webpack@5.97.1)
'@wordpress/block-library':
specifier: catalog:wp-min
- version: 9.19.6(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ version: 9.19.6(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/blocks':
specifier: catalog:wp-min
version: 14.8.2(react@18.3.1)
@@ -1398,7 +1401,7 @@ importers:
version: 4.19.1
'@wordpress/editor':
specifier: catalog:wp-min
- version: 14.19.7(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ version: 14.19.7(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/element':
specifier: catalog:wp-min
version: 6.19.1
@@ -2763,7 +2766,7 @@ importers:
version: 8.19.7(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/editor':
specifier: catalog:wp-min
- version: 14.19.7(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ version: 14.19.7(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/element':
specifier: catalog:wp-min
version: 6.19.1
@@ -3083,6 +3086,79 @@ importers:
specifier: 5.7.x
version: 5.7.3
+ packages/js/settings-ui-sdk:
+ dependencies:
+ '@woocommerce/sanitize':
+ specifier: workspace:*
+ version: link:../sanitize
+ '@wordpress/admin-ui':
+ specifier: catalog:wp-min
+ version: 1.12.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/components':
+ specifier: catalog:wp-min
+ version: 29.5.4(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/element':
+ specifier: catalog:wp-min
+ version: 6.19.1
+ '@wordpress/i18n':
+ specifier: catalog:wp-min
+ version: 5.19.1
+ react:
+ specifier: 18.3.x
+ version: 18.3.1
+ react-dom:
+ specifier: 18.3.x
+ version: 18.3.1(react@18.3.1)
+ devDependencies:
+ '@babel/core':
+ specifier: 7.25.7
+ version: 7.25.7
+ '@types/jest':
+ specifier: 29.5.x
+ version: 29.5.14
+ '@types/react':
+ specifier: 18.3.x
+ version: 18.3.28
+ '@woocommerce/eslint-plugin':
+ specifier: workspace:*
+ version: link:../eslint-plugin
+ '@woocommerce/internal-js-tests':
+ specifier: workspace:*
+ version: link:../internal-js-tests
+ '@woocommerce/internal-ts-config':
+ specifier: workspace:*
+ version: link:../internal-ts-config
+ chokidar:
+ specifier: 3.6.x
+ version: 3.6.0
+ esbuild:
+ specifier: 0.24.x
+ version: 0.24.2
+ eslint:
+ specifier: ^8.55.0
+ version: 8.57.1
+ glob:
+ specifier: ^10.3.10
+ version: 10.5.0
+ jest:
+ specifier: 29.5.x
+ version: 29.5.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
+ jest-cli:
+ specifier: 29.5.x
+ version: 29.5.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
+ jest-environment-jsdom:
+ specifier: 29.5.x
+ version: 29.5.0
+ rimraf:
+ specifier: 5.0.5
+ version: 5.0.5
+ ts-jest:
+ specifier: 29.1.x
+ version: 29.1.5(@babel/core@7.25.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.7))(esbuild@0.24.2)(jest@29.5.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))(typescript@5.7.3)
+ typescript:
+ specifier: 5.7.x
+ version: 5.7.3
+
packages/js/tracks:
dependencies:
debug:
@@ -3527,12 +3603,18 @@ importers:
'@woocommerce/sanitize':
specifier: workspace:*
version: link:../../../../packages/js/sanitize
+ '@woocommerce/settings-ui-sdk':
+ specifier: workspace:*
+ version: link:../../../../packages/js/settings-ui-sdk
'@woocommerce/tracks':
specifier: workspace:*
version: link:../../../../packages/js/tracks
'@wordpress/a11y':
specifier: catalog:wp-min
version: 4.19.1
+ '@wordpress/admin-ui':
+ specifier: catalog:wp-min
+ version: 1.12.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/api-fetch':
specifier: catalog:wp-min
version: 7.19.2
@@ -3577,7 +3659,7 @@ importers:
version: 5.15.0(patch_hash=63381743e38412fb89154386a5d169639ca10f8315407527829db669201fce9b)(@babel/helper-module-imports@7.28.6)(@babel/types@7.29.0)(@emotion/is-prop-valid@1.4.0)(@preact/signals-core@1.14.1)(@types/react@18.3.28)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/editor':
specifier: catalog:wp-min
- version: 14.19.7(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ version: 14.19.7(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/element':
specifier: catalog:wp-min
version: 6.19.1
@@ -11466,11 +11548,6 @@ packages:
engines: {node: '>=18.12.0', npm: '>=8.19.2'}
hasBin: true
- '@wordpress/i18n@6.17.0':
- resolution: {integrity: sha512-v1SLBweg7CRzQ+5+WSC1U93i8h9d3AoB0YBvMsd6gWI5vO8Zh4YKlEMexvrHQC++WN83egwqux84fWEdeU0MUA==}
- engines: {node: '>=18.12.0', npm: '>=8.19.2'}
- hasBin: true
-
'@wordpress/i18n@6.18.0':
resolution: {integrity: sha512-6dYCih4wUwi7Csu4RNfHiAKkgWhpSQdl8YthvQUF59Sfsoia3RCdtd4K2l7W4f18ldFA/RXjShMjvSexWy6OyQ==}
engines: {node: '>=18.12.0', npm: '>=8.19.2'}
@@ -27090,7 +27167,7 @@ snapshots:
'@types/istanbul-lib-coverage': 2.0.6
collect-v8-coverage: 1.0.3
- '@jest/test-sequencer@26.6.3(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))':
+ '@jest/test-sequencer@26.6.3':
dependencies:
'@jest/test-result': 26.6.2
graceful-fs: 4.2.11
@@ -27098,11 +27175,7 @@ snapshots:
jest-runner: 26.6.3(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
jest-runtime: 26.6.3(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
transitivePeerDependencies:
- - bufferutil
- - canvas
- supports-color
- - ts-node
- - utf-8-validate
'@jest/test-sequencer@29.7.0':
dependencies:
@@ -32395,7 +32468,7 @@ snapshots:
'@types/react': 18.3.28
'@wordpress/components': 28.13.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/data': 10.19.2(react@18.3.1)
- '@wordpress/editor': 14.19.7(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/editor': 14.19.7(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/element': 6.44.0
transitivePeerDependencies:
- '@date-fns/tz'
@@ -32974,10 +33047,10 @@ snapshots:
'@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@4.15.2)(webpack@5.97.1)':
dependencies:
- webpack: 5.97.1(@swc/core@1.15.24)(webpack-cli@5.1.4)
+ webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.1)(webpack-dev-server@4.15.2)(webpack@5.97.1)
optionalDependencies:
- webpack-dev-server: 4.15.2(debug@4.4.3)(webpack-cli@5.1.4)(webpack@5.97.1)
+ webpack-dev-server: 4.15.2(webpack-cli@5.1.4)(webpack@5.97.1)
'@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.97.1)':
dependencies:
@@ -33032,12 +33105,32 @@ snapshots:
'@wordpress/dom-ready': 4.45.1-next.v.202605131032.0
'@wordpress/i18n': 6.18.1-next.v.202605131032.0
+ '@wordpress/admin-ui@1.12.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ dependencies:
+ '@wordpress/base-styles': 6.20.0
+ '@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/element': 6.44.0
+ '@wordpress/i18n': 6.18.0
+ '@wordpress/private-apis': 1.44.0
+ '@wordpress/route': 0.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/ui': 0.11.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ clsx: 2.1.1
+ react: 18.3.1
+ transitivePeerDependencies:
+ - '@date-fns/tz'
+ - '@emotion/is-prop-valid'
+ - '@types/react'
+ - date-fns
+ - react-dom
+ - stylelint
+ - supports-color
+
'@wordpress/admin-ui@1.12.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
'@wordpress/base-styles': 6.20.0
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/element': 6.44.0
- '@wordpress/i18n': 6.17.0
+ '@wordpress/i18n': 6.18.0
'@wordpress/private-apis': 1.44.0
'@wordpress/route': 0.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/ui': 0.11.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
@@ -33052,15 +33145,15 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/admin-ui@1.12.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
+ '@wordpress/admin-ui@1.12.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
'@wordpress/base-styles': 6.20.0
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/element': 6.44.0
- '@wordpress/i18n': 6.17.0
+ '@wordpress/i18n': 6.18.0
'@wordpress/private-apis': 1.44.0
'@wordpress/route': 0.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/ui': 0.11.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/ui': 0.11.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
clsx: 2.1.1
react: 18.3.1
transitivePeerDependencies:
@@ -33072,15 +33165,15 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/admin-ui@1.12.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ '@wordpress/admin-ui@1.12.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
'@wordpress/base-styles': 6.20.0
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/element': 6.44.0
- '@wordpress/i18n': 6.17.0
+ '@wordpress/i18n': 6.18.0
'@wordpress/private-apis': 1.44.0
'@wordpress/route': 0.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/ui': 0.11.0(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/ui': 0.11.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
clsx: 2.1.1
react: 18.3.1
transitivePeerDependencies:
@@ -33097,7 +33190,7 @@ snapshots:
'@wordpress/base-styles': 6.20.0
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/element': 6.44.0
- '@wordpress/i18n': 6.17.0
+ '@wordpress/i18n': 6.18.0
'@wordpress/private-apis': 1.44.0
'@wordpress/route': 0.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/ui': 0.11.0(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -33126,7 +33219,7 @@ snapshots:
'@wordpress/api-fetch@7.44.0':
dependencies:
- '@wordpress/i18n': 6.17.0
+ '@wordpress/i18n': 6.18.0
'@wordpress/url': 4.44.0
'@wordpress/autop@3.16.0':
@@ -33533,7 +33626,7 @@ snapshots:
- '@types/react-dom'
- supports-color
- '@wordpress/block-editor@15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
+ '@wordpress/block-editor@15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
'@react-spring/web': 9.7.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/a11y': 4.45.0
@@ -33545,7 +33638,7 @@ snapshots:
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
'@wordpress/data': 10.44.0(react@18.3.1)
- '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/date': 5.45.0
'@wordpress/deprecated': 4.45.0
'@wordpress/dom': 4.45.0
@@ -33596,7 +33689,7 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/block-editor@15.17.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ '@wordpress/block-editor@15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
'@react-spring/web': 9.7.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/a11y': 4.45.0
@@ -33608,7 +33701,7 @@ snapshots:
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
'@wordpress/data': 10.44.0(react@18.3.1)
- '@wordpress/dataviews': 14.2.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/date': 5.45.0
'@wordpress/deprecated': 4.45.0
'@wordpress/dom': 4.45.0
@@ -33805,10 +33898,10 @@ snapshots:
'@wordpress/keyboard-shortcuts': 5.44.0(react@18.3.1)
'@wordpress/keycodes': 4.44.0
'@wordpress/notices': 5.19.2(react@18.3.1)
- '@wordpress/patterns': 2.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/patterns': 2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/primitives': 4.44.0(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/reusable-blocks': 5.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/reusable-blocks': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/rich-text': 7.44.0(react@18.3.1)
'@wordpress/server-side-render': 5.23.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/url': 4.44.0
@@ -33896,11 +33989,11 @@ snapshots:
'@wordpress/autop': 4.44.0
'@wordpress/base-styles': 6.20.0
'@wordpress/blob': 4.44.0
- '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/blocks': 15.17.0(react@18.3.1)
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/data': 10.44.0(react@18.3.1)
'@wordpress/date': 5.45.0
'@wordpress/deprecated': 4.45.0
@@ -33917,10 +34010,10 @@ snapshots:
'@wordpress/keycodes': 4.45.0
'@wordpress/latex-to-mathml': 1.12.0
'@wordpress/notices': 5.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/patterns': 2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/patterns': 2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/primitives': 4.45.0(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/reusable-blocks': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/reusable-blocks': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/rich-text': 7.45.0(react@18.3.1)
'@wordpress/server-side-render': 6.20.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/upload-media': 0.29.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -34594,23 +34687,23 @@ snapshots:
'@types/gradient-parser': 0.1.3
'@types/highlight-words-core': 1.2.1
'@use-gesture/react': 10.3.1(react@18.3.1)
- '@wordpress/a11y': 4.19.1
+ '@wordpress/a11y': 4.45.0
'@wordpress/compose': 7.44.0(react@18.3.1)
- '@wordpress/date': 5.44.0
- '@wordpress/deprecated': 4.44.0
- '@wordpress/dom': 4.44.0
+ '@wordpress/date': 5.45.0
+ '@wordpress/deprecated': 4.45.0
+ '@wordpress/dom': 4.45.0
'@wordpress/element': 6.44.0
- '@wordpress/escape-html': 3.44.0
- '@wordpress/hooks': 4.44.0
- '@wordpress/html-entities': 4.44.0
+ '@wordpress/escape-html': 3.45.0
+ '@wordpress/hooks': 4.45.0
+ '@wordpress/html-entities': 4.45.0
'@wordpress/i18n': 5.26.0
'@wordpress/icons': 10.32.0(react@18.3.1)
- '@wordpress/is-shallow-equal': 5.44.0
- '@wordpress/keycodes': 4.44.0
- '@wordpress/primitives': 4.44.0(react@18.3.1)
+ '@wordpress/is-shallow-equal': 5.45.0
+ '@wordpress/keycodes': 4.45.0
+ '@wordpress/primitives': 4.45.0(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/rich-text': 7.44.0(react@18.3.1)
- '@wordpress/warning': 3.44.0
+ '@wordpress/rich-text': 7.45.0(react@18.3.1)
+ '@wordpress/warning': 3.45.0
change-case: 4.1.2
clsx: 2.1.1
colord: 2.9.3
@@ -34648,23 +34741,23 @@ snapshots:
'@types/gradient-parser': 0.1.3
'@types/highlight-words-core': 1.2.1
'@use-gesture/react': 10.3.1(react@18.3.1)
- '@wordpress/a11y': 4.19.1
+ '@wordpress/a11y': 4.45.0
'@wordpress/compose': 7.44.0(react@18.3.1)
- '@wordpress/date': 5.44.0
- '@wordpress/deprecated': 4.44.0
- '@wordpress/dom': 4.44.0
+ '@wordpress/date': 5.45.0
+ '@wordpress/deprecated': 4.45.0
+ '@wordpress/dom': 4.45.0
'@wordpress/element': 6.44.0
- '@wordpress/escape-html': 3.44.0
- '@wordpress/hooks': 4.44.0
- '@wordpress/html-entities': 4.44.0
+ '@wordpress/escape-html': 3.45.0
+ '@wordpress/hooks': 4.45.0
+ '@wordpress/html-entities': 4.45.0
'@wordpress/i18n': 5.26.0
'@wordpress/icons': 10.32.0(react@18.3.1)
- '@wordpress/is-shallow-equal': 5.44.0
- '@wordpress/keycodes': 4.44.0
- '@wordpress/primitives': 4.44.0(react@18.3.1)
+ '@wordpress/is-shallow-equal': 5.45.0
+ '@wordpress/keycodes': 4.45.0
+ '@wordpress/primitives': 4.45.0(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/rich-text': 7.44.0(react@18.3.1)
- '@wordpress/warning': 3.44.0
+ '@wordpress/rich-text': 7.45.0(react@18.3.1)
+ '@wordpress/warning': 3.45.0
change-case: 4.1.2
clsx: 2.1.1
colord: 2.9.3
@@ -34769,7 +34862,7 @@ snapshots:
'@wordpress/escape-html': 3.44.0
'@wordpress/hooks': 4.44.0
'@wordpress/html-entities': 4.44.0
- '@wordpress/i18n': 6.17.0
+ '@wordpress/i18n': 6.18.0
'@wordpress/icons': 12.2.0(react@18.3.1)
'@wordpress/is-shallow-equal': 5.44.0
'@wordpress/keycodes': 4.44.0
@@ -34826,7 +34919,7 @@ snapshots:
'@wordpress/escape-html': 3.44.0
'@wordpress/hooks': 4.44.0
'@wordpress/html-entities': 4.44.0
- '@wordpress/i18n': 6.17.0
+ '@wordpress/i18n': 6.18.0
'@wordpress/icons': 12.2.0(react@18.3.1)
'@wordpress/is-shallow-equal': 5.44.0
'@wordpress/keycodes': 4.44.0
@@ -35058,10 +35151,10 @@ snapshots:
'@wordpress/core-commands@1.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
- '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/commands': 1.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/data': 10.44.0(react@18.3.1)
'@wordpress/element': 6.44.0
'@wordpress/html-entities': 4.45.0
@@ -35209,17 +35302,17 @@ snapshots:
- '@types/react-dom'
- supports-color
- '@wordpress/core-data@7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
+ '@wordpress/core-data@7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
'@wordpress/api-fetch': 7.44.0
- '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/blocks': 15.17.0(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
'@wordpress/data': 10.44.0(react@18.3.1)
'@wordpress/deprecated': 4.44.0
'@wordpress/element': 6.44.0
'@wordpress/html-entities': 4.44.0
- '@wordpress/i18n': 6.17.0
+ '@wordpress/i18n': 6.18.0
'@wordpress/is-shallow-equal': 5.44.0
'@wordpress/private-apis': 1.44.0
'@wordpress/rich-text': 7.44.0(react@18.3.1)
@@ -35242,17 +35335,17 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/core-data@7.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ '@wordpress/core-data@7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
'@wordpress/api-fetch': 7.44.0
- '@wordpress/block-editor': 15.17.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/blocks': 15.17.0(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
'@wordpress/data': 10.44.0(react@18.3.1)
'@wordpress/deprecated': 4.44.0
'@wordpress/element': 6.44.0
'@wordpress/html-entities': 4.44.0
- '@wordpress/i18n': 6.17.0
+ '@wordpress/i18n': 6.18.0
'@wordpress/is-shallow-equal': 5.44.0
'@wordpress/private-apis': 1.44.0
'@wordpress/rich-text': 7.44.0(react@18.3.1)
@@ -35285,7 +35378,7 @@ snapshots:
'@wordpress/deprecated': 4.44.0
'@wordpress/element': 6.44.0
'@wordpress/html-entities': 4.44.0
- '@wordpress/i18n': 6.17.0
+ '@wordpress/i18n': 6.18.0
'@wordpress/is-shallow-equal': 5.44.0
'@wordpress/private-apis': 1.44.0
'@wordpress/rich-text': 7.44.0(react@18.3.1)
@@ -35427,7 +35520,7 @@ snapshots:
rememo: 4.0.2
use-memo-one: 1.1.3(react@18.3.1)
- '@wordpress/dataviews@14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
+ '@wordpress/dataviews@14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
'@ariakit/react': 0.4.25(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/base-styles': 7.0.0
@@ -35442,7 +35535,7 @@ snapshots:
'@wordpress/keycodes': 4.45.0
'@wordpress/primitives': 4.45.0(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/ui': 0.12.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/ui': 0.12.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/warning': 3.45.0
clsx: 2.1.1
colord: 2.9.3
@@ -35459,7 +35552,7 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/dataviews@14.2.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ '@wordpress/dataviews@14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
'@ariakit/react': 0.4.25(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/base-styles': 7.0.0
@@ -35474,7 +35567,7 @@ snapshots:
'@wordpress/keycodes': 4.45.0
'@wordpress/primitives': 4.45.0(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/ui': 0.12.0(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/ui': 0.12.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/warning': 3.45.0
clsx: 2.1.1
colord: 2.9.3
@@ -35754,11 +35847,11 @@ snapshots:
'@wordpress/components': 29.12.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
'@wordpress/core-commands': 1.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/data': 10.44.0(react@18.3.1)
'@wordpress/deprecated': 4.44.0
'@wordpress/dom': 4.44.0
- '@wordpress/editor': 14.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/editor': 14.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/element': 6.44.0
'@wordpress/hooks': 4.44.0
'@wordpress/html-entities': 4.44.0
@@ -35905,6 +35998,124 @@ snapshots:
- supports-color
- utf-8-validate
+ '@wordpress/editor@14.19.7(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ dependencies:
+ '@babel/runtime': 7.25.7
+ '@wordpress/a11y': 4.19.1
+ '@wordpress/api-fetch': 7.44.0
+ '@wordpress/blob': 4.44.0
+ '@wordpress/block-editor': 14.21.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/blocks': 14.15.0(react@18.3.1)
+ '@wordpress/commands': 1.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/components': 29.12.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/compose': 7.44.0(react@18.3.1)
+ '@wordpress/core-data': 7.19.6(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/data': 10.19.2(react@18.3.1)
+ '@wordpress/dataviews': 4.22.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/date': 5.44.0
+ '@wordpress/deprecated': 4.44.0
+ '@wordpress/dom': 4.44.0
+ '@wordpress/element': 6.44.0
+ '@wordpress/fields': 0.11.6(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/hooks': 4.44.0
+ '@wordpress/html-entities': 4.44.0
+ '@wordpress/i18n': 5.26.0
+ '@wordpress/icons': 10.32.0(react@18.3.1)
+ '@wordpress/interface': 9.29.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/keyboard-shortcuts': 5.44.0(react@18.3.1)
+ '@wordpress/keycodes': 4.44.0
+ '@wordpress/media-utils': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/notices': 5.19.2(react@18.3.1)
+ '@wordpress/patterns': 2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/plugins': 7.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/preferences': 4.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/private-apis': 1.44.0
+ '@wordpress/reusable-blocks': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/rich-text': 7.44.0(react@18.3.1)
+ '@wordpress/server-side-render': 5.23.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/url': 4.44.0
+ '@wordpress/warning': 3.44.0
+ '@wordpress/wordcount': 4.44.0
+ change-case: 4.1.2
+ client-zip: 2.5.0
+ clsx: 2.1.1
+ date-fns: 3.6.0
+ deepmerge: 4.3.1
+ fast-deep-equal: 3.1.3
+ is-plain-object: 5.0.0
+ memize: 2.1.1
+ react: 18.3.1
+ react-autosize-textarea: 7.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
+ remove-accents: 0.5.0
+ uuid: 9.0.1
+ transitivePeerDependencies:
+ - '@date-fns/tz'
+ - '@emotion/is-prop-valid'
+ - '@types/react'
+ - '@types/react-dom'
+ - stylelint
+ - supports-color
+
+ '@wordpress/editor@14.19.7(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.25.7
+ '@wordpress/a11y': 4.19.1
+ '@wordpress/api-fetch': 7.44.0
+ '@wordpress/blob': 4.44.0
+ '@wordpress/block-editor': 14.21.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/blocks': 14.15.0(react@18.3.1)
+ '@wordpress/commands': 1.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/components': 29.12.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/compose': 7.44.0(react@18.3.1)
+ '@wordpress/core-data': 7.19.6(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/data': 10.19.2(react@18.3.1)
+ '@wordpress/dataviews': 4.22.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/date': 5.44.0
+ '@wordpress/deprecated': 4.44.0
+ '@wordpress/dom': 4.44.0
+ '@wordpress/element': 6.44.0
+ '@wordpress/fields': 0.11.6(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/hooks': 4.44.0
+ '@wordpress/html-entities': 4.44.0
+ '@wordpress/i18n': 5.26.0
+ '@wordpress/icons': 10.32.0(react@18.3.1)
+ '@wordpress/interface': 9.29.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/keyboard-shortcuts': 5.44.0(react@18.3.1)
+ '@wordpress/keycodes': 4.44.0
+ '@wordpress/media-utils': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/notices': 5.19.2(react@18.3.1)
+ '@wordpress/patterns': 2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/plugins': 7.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/preferences': 4.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/private-apis': 1.44.0
+ '@wordpress/reusable-blocks': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/rich-text': 7.44.0(react@18.3.1)
+ '@wordpress/server-side-render': 5.23.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/url': 4.44.0
+ '@wordpress/warning': 3.44.0
+ '@wordpress/wordcount': 4.44.0
+ change-case: 4.1.2
+ client-zip: 2.5.0
+ clsx: 2.1.1
+ date-fns: 3.6.0
+ deepmerge: 4.3.1
+ fast-deep-equal: 3.1.3
+ is-plain-object: 5.0.0
+ memize: 2.1.1
+ react: 18.3.1
+ react-autosize-textarea: 7.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
+ remove-accents: 0.5.0
+ uuid: 9.0.1
+ transitivePeerDependencies:
+ - '@date-fns/tz'
+ - '@emotion/is-prop-valid'
+ - '@types/react'
+ - '@types/react-dom'
+ - stylelint
+ - supports-color
+
'@wordpress/editor@14.19.7(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
'@babel/runtime': 7.25.7
@@ -35923,21 +36134,21 @@ snapshots:
'@wordpress/deprecated': 4.44.0
'@wordpress/dom': 4.44.0
'@wordpress/element': 6.44.0
- '@wordpress/fields': 0.11.6(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/fields': 0.11.6(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/hooks': 4.44.0
'@wordpress/html-entities': 4.44.0
'@wordpress/i18n': 5.26.0
'@wordpress/icons': 10.32.0(react@18.3.1)
- '@wordpress/interface': 9.29.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/interface': 9.29.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/keyboard-shortcuts': 5.44.0(react@18.3.1)
'@wordpress/keycodes': 4.44.0
- '@wordpress/media-utils': 5.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/media-utils': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/notices': 5.19.2(react@18.3.1)
- '@wordpress/patterns': 2.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/patterns': 2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/plugins': 7.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/preferences': 4.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/reusable-blocks': 5.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/reusable-blocks': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/rich-text': 7.44.0(react@18.3.1)
'@wordpress/server-side-render': 5.23.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/url': 4.44.0
@@ -36023,51 +36234,118 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/editor@14.19.7(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@wordpress/editor@14.19.7(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.25.7
+ '@wordpress/a11y': 4.19.1
+ '@wordpress/api-fetch': 7.44.0
+ '@wordpress/blob': 4.44.0
+ '@wordpress/block-editor': 14.21.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/blocks': 14.15.0(react@18.3.1)
+ '@wordpress/commands': 1.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/components': 29.12.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/compose': 7.44.0(react@18.3.1)
+ '@wordpress/core-data': 7.19.6(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/data': 10.19.2(react@18.3.1)
+ '@wordpress/dataviews': 4.22.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/date': 5.44.0
+ '@wordpress/deprecated': 4.44.0
+ '@wordpress/dom': 4.44.0
+ '@wordpress/element': 6.44.0
+ '@wordpress/fields': 0.11.6(@emotion/is-prop-valid@1.4.0)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/hooks': 4.44.0
+ '@wordpress/html-entities': 4.44.0
+ '@wordpress/i18n': 5.26.0
+ '@wordpress/icons': 10.32.0(react@18.3.1)
+ '@wordpress/interface': 9.29.0(@emotion/is-prop-valid@1.4.0)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/keyboard-shortcuts': 5.44.0(react@18.3.1)
+ '@wordpress/keycodes': 4.44.0
+ '@wordpress/media-utils': 5.44.0(@emotion/is-prop-valid@1.4.0)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/notices': 5.19.2(react@18.3.1)
+ '@wordpress/patterns': 2.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/plugins': 7.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/preferences': 4.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/private-apis': 1.44.0
+ '@wordpress/reusable-blocks': 5.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/rich-text': 7.44.0(react@18.3.1)
+ '@wordpress/server-side-render': 5.23.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/url': 4.44.0
+ '@wordpress/warning': 3.44.0
+ '@wordpress/wordcount': 4.44.0
+ change-case: 4.1.2
+ client-zip: 2.5.0
+ clsx: 2.1.1
+ date-fns: 3.6.0
+ deepmerge: 4.3.1
+ fast-deep-equal: 3.1.3
+ is-plain-object: 5.0.0
+ memize: 2.1.1
+ react: 18.3.1
+ react-autosize-textarea: 7.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
+ remove-accents: 0.5.0
+ uuid: 9.0.1
+ transitivePeerDependencies:
+ - '@date-fns/tz'
+ - '@emotion/is-prop-valid'
+ - '@types/react'
+ - '@types/react-dom'
+ - stylelint
+ - supports-color
+
+ '@wordpress/editor@14.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
- '@babel/runtime': 7.25.7
- '@wordpress/a11y': 4.19.1
+ '@floating-ui/react-dom': 2.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/a11y': 4.45.0
'@wordpress/api-fetch': 7.44.0
+ '@wordpress/base-styles': 6.20.0
'@wordpress/blob': 4.44.0
- '@wordpress/block-editor': 14.21.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/blocks': 14.15.0(react@18.3.1)
+ '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/block-serialization-default-parser': 5.44.0
+ '@wordpress/blocks': 15.17.0(react@18.3.1)
'@wordpress/commands': 1.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/components': 29.12.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
- '@wordpress/core-data': 7.19.6(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/data': 10.19.2(react@18.3.1)
- '@wordpress/dataviews': 4.22.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/data': 10.44.0(react@18.3.1)
+ '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/date': 5.44.0
'@wordpress/deprecated': 4.44.0
'@wordpress/dom': 4.44.0
'@wordpress/element': 6.44.0
- '@wordpress/fields': 0.11.6(@emotion/is-prop-valid@1.4.0)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/fields': 0.36.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/global-styles-engine': 1.11.0(react@18.3.1)
+ '@wordpress/global-styles-ui': 1.11.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/hooks': 4.44.0
'@wordpress/html-entities': 4.44.0
- '@wordpress/i18n': 5.26.0
- '@wordpress/icons': 10.32.0(react@18.3.1)
- '@wordpress/interface': 9.29.0(@emotion/is-prop-valid@1.4.0)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/i18n': 6.18.0
+ '@wordpress/icons': 12.2.0(react@18.3.1)
+ '@wordpress/interface': 9.29.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/keyboard-shortcuts': 5.44.0(react@18.3.1)
'@wordpress/keycodes': 4.44.0
- '@wordpress/media-utils': 5.44.0(@emotion/is-prop-valid@1.4.0)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/notices': 5.19.2(react@18.3.1)
- '@wordpress/patterns': 2.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/media-editor': 0.7.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/media-fields': 0.9.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/media-utils': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/notices': 5.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/patterns': 2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/plugins': 7.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/preferences': 4.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/reusable-blocks': 5.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/reusable-blocks': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/rich-text': 7.44.0(react@18.3.1)
- '@wordpress/server-side-render': 5.23.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/server-side-render': 6.20.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/upload-media': 0.29.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/url': 4.44.0
+ '@wordpress/views': 1.11.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/warning': 3.44.0
'@wordpress/wordcount': 4.44.0
change-case: 4.1.2
client-zip: 2.5.0
clsx: 2.1.1
+ colord: 2.9.3
date-fns: 3.6.0
- deepmerge: 4.3.1
+ diff: 4.0.4
fast-deep-equal: 3.1.3
- is-plain-object: 5.0.0
memize: 2.1.1
react: 18.3.1
react-autosize-textarea: 7.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -36082,50 +36360,50 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/editor@14.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
+ '@wordpress/editor@14.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@floating-ui/react-dom': 2.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/a11y': 4.45.0
'@wordpress/api-fetch': 7.44.0
'@wordpress/base-styles': 6.20.0
'@wordpress/blob': 4.44.0
- '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/block-serialization-default-parser': 5.44.0
'@wordpress/blocks': 15.17.0(react@18.3.1)
'@wordpress/commands': 1.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/data': 10.44.0(react@18.3.1)
- '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/date': 5.44.0
'@wordpress/deprecated': 4.44.0
'@wordpress/dom': 4.44.0
'@wordpress/element': 6.44.0
- '@wordpress/fields': 0.36.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/fields': 0.36.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/global-styles-engine': 1.11.0(react@18.3.1)
- '@wordpress/global-styles-ui': 1.11.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/global-styles-ui': 1.11.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/hooks': 4.44.0
'@wordpress/html-entities': 4.44.0
- '@wordpress/i18n': 6.17.0
+ '@wordpress/i18n': 6.18.0
'@wordpress/icons': 12.2.0(react@18.3.1)
- '@wordpress/interface': 9.29.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/interface': 9.29.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/keyboard-shortcuts': 5.44.0(react@18.3.1)
'@wordpress/keycodes': 4.44.0
- '@wordpress/media-editor': 0.7.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
- '@wordpress/media-fields': 0.9.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
- '@wordpress/media-utils': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/media-editor': 0.7.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/media-fields': 0.9.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/media-utils': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/notices': 5.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/patterns': 2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/patterns': 2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/plugins': 7.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/preferences': 4.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/reusable-blocks': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/reusable-blocks': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/rich-text': 7.44.0(react@18.3.1)
'@wordpress/server-side-render': 6.20.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/upload-media': 0.29.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/url': 4.44.0
- '@wordpress/views': 1.11.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/views': 1.11.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/warning': 3.44.0
'@wordpress/wordcount': 4.44.0
change-case: 4.1.2
@@ -36186,7 +36464,7 @@ snapshots:
'@babel/runtime': 7.25.7
'@types/react': 18.3.28
'@types/react-dom': 18.3.7(@types/react@18.3.28)
- '@wordpress/escape-html': 3.44.0
+ '@wordpress/escape-html': 3.45.0
change-case: 4.1.2
is-plain-object: 5.0.0
react: 18.3.1
@@ -36265,7 +36543,7 @@ snapshots:
cosmiconfig: 7.1.0
eslint: 8.57.1
eslint-config-prettier: 8.10.2(eslint@8.57.1)
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@4.4.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.57.1)
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.11)(eslint@8.57.1)
eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(jest@29.5.0(@types/node@24.12.2)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))(typescript@5.7.3)
eslint-plugin-jsdoc: 39.9.1(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
@@ -36294,7 +36572,7 @@ snapshots:
cosmiconfig: 7.1.0
eslint: 8.57.1
eslint-config-prettier: 8.10.2(eslint@8.57.1)
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@4.4.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.57.1)
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.11)(eslint@8.57.1)
eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(jest@29.7.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))(typescript@5.7.3)
eslint-plugin-jsdoc: 39.9.1(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
@@ -36323,7 +36601,7 @@ snapshots:
cosmiconfig: 7.1.0
eslint: 8.57.1
eslint-config-prettier: 8.10.2(eslint@8.57.1)
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@4.4.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.57.1)
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.11)(eslint@8.57.1)
eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(jest@29.5.0(@types/node@24.12.2)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))(typescript@5.7.3)
eslint-plugin-jsdoc: 39.9.1(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
@@ -36434,7 +36712,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- '@wordpress/fields@0.11.6(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ '@wordpress/fields@0.11.6(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
'@babel/runtime': 7.25.7
'@wordpress/api-fetch': 7.44.0
@@ -36443,7 +36721,7 @@ snapshots:
'@wordpress/blocks': 14.15.0(react@18.3.1)
'@wordpress/components': 29.12.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/data': 10.44.0(react@18.3.1)
'@wordpress/dataviews': 4.22.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/date': 5.45.0
@@ -36452,9 +36730,9 @@ snapshots:
'@wordpress/html-entities': 4.45.0
'@wordpress/i18n': 5.26.0
'@wordpress/icons': 10.32.0(react@18.3.1)
- '@wordpress/media-utils': 5.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/media-utils': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/notices': 5.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/patterns': 2.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/patterns': 2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/primitives': 4.45.0(react@18.3.1)
'@wordpress/private-apis': 1.44.0
'@wordpress/router': 1.44.0(react@18.3.1)
@@ -36599,6 +36877,48 @@ snapshots:
- stylelint
- supports-color
+ '@wordpress/fields@0.36.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@react-spring/web': 9.7.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/api-fetch': 7.44.0
+ '@wordpress/base-styles': 6.20.0
+ '@wordpress/blob': 4.44.0
+ '@wordpress/blocks': 15.17.0(react@18.3.1)
+ '@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/compose': 7.44.0(react@18.3.1)
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/data': 10.44.0(react@18.3.1)
+ '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/date': 5.45.0
+ '@wordpress/element': 6.44.0
+ '@wordpress/hooks': 4.45.0
+ '@wordpress/html-entities': 4.45.0
+ '@wordpress/i18n': 6.18.0
+ '@wordpress/icons': 12.2.0(react@18.3.1)
+ '@wordpress/media-utils': 5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/notices': 5.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/patterns': 2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/primitives': 4.45.0(react@18.3.1)
+ '@wordpress/private-apis': 1.44.0
+ '@wordpress/router': 1.44.0(react@18.3.1)
+ '@wordpress/url': 4.44.0
+ '@wordpress/warning': 3.45.0
+ '@wordpress/wordcount': 4.44.0
+ change-case: 4.1.2
+ client-zip: 2.5.0
+ clsx: 2.1.1
+ react: 18.3.1
+ remove-accents: 0.5.0
+ transitivePeerDependencies:
+ - '@date-fns/tz'
+ - '@emotion/is-prop-valid'
+ - '@types/react'
+ - '@types/react-dom'
+ - date-fns
+ - react-dom
+ - stylelint
+ - supports-color
+
'@wordpress/format-library@5.19.6(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.7
@@ -36626,7 +36946,7 @@ snapshots:
dependencies:
'@wordpress/blocks': 15.17.0(react@18.3.1)
'@wordpress/data': 10.44.0(react@18.3.1)
- '@wordpress/i18n': 6.17.0
+ '@wordpress/i18n': 6.18.0
'@wordpress/style-engine': 2.44.0
colord: 2.9.3
deepmerge: 4.3.1
@@ -36667,6 +36987,37 @@ snapshots:
- stylelint
- supports-color
+ '@wordpress/global-styles-ui@1.11.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@wordpress/a11y': 4.45.0
+ '@wordpress/api-fetch': 7.44.0
+ '@wordpress/base-styles': 6.20.0
+ '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/blocks': 15.17.0(react@18.3.1)
+ '@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/compose': 7.44.0(react@18.3.1)
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/data': 10.44.0(react@18.3.1)
+ '@wordpress/date': 5.45.0
+ '@wordpress/element': 6.44.0
+ '@wordpress/global-styles-engine': 1.11.0(react@18.3.1)
+ '@wordpress/i18n': 6.18.0
+ '@wordpress/icons': 12.2.0(react@18.3.1)
+ '@wordpress/keycodes': 4.45.0
+ '@wordpress/private-apis': 1.44.0
+ change-case: 4.1.2
+ clsx: 2.1.1
+ colord: 2.9.3
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ transitivePeerDependencies:
+ - '@date-fns/tz'
+ - '@emotion/is-prop-valid'
+ - '@types/react'
+ - '@types/react-dom'
+ - stylelint
+ - supports-color
+
'@wordpress/hooks@3.58.0':
dependencies:
'@babel/runtime': 7.25.7
@@ -36709,7 +37060,7 @@ snapshots:
'@wordpress/i18n@5.19.1':
dependencies:
'@babel/runtime': 7.25.7
- '@wordpress/hooks': 4.44.0
+ '@wordpress/hooks': 4.45.0
gettext-parser: 1.4.0
memize: 2.1.1
sprintf-js: 1.1.3
@@ -36724,14 +37075,6 @@ snapshots:
sprintf-js: 1.1.3
tannin: 1.2.0
- '@wordpress/i18n@6.17.0':
- dependencies:
- '@tannin/sprintf': 1.3.3
- '@wordpress/hooks': 4.45.0
- gettext-parser: 1.4.0
- memize: 2.1.1
- tannin: 1.2.0
-
'@wordpress/i18n@6.18.0':
dependencies:
'@tannin/sprintf': 1.3.3
@@ -36886,10 +37229,10 @@ snapshots:
- '@types/react'
- supports-color
- '@wordpress/interface@9.29.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
+ '@wordpress/interface@9.29.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
'@wordpress/a11y': 4.45.0
- '@wordpress/admin-ui': 1.12.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/admin-ui': 1.12.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/base-styles': 6.20.0
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
@@ -36912,10 +37255,10 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/interface@9.29.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ '@wordpress/interface@9.29.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
'@wordpress/a11y': 4.45.0
- '@wordpress/admin-ui': 1.12.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/admin-ui': 1.12.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/base-styles': 6.20.0
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
@@ -37025,7 +37368,7 @@ snapshots:
jest-matcher-utils: 29.7.0
jest-mock: 29.7.0
- '@wordpress/jest-console@8.44.0(jest@29.7.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))':
+ '@wordpress/jest-console@8.44.0(jest@29.7.0(@types/node@24.12.2)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))':
dependencies:
jest: 29.7.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
jest-matcher-utils: 29.7.0
@@ -37043,7 +37386,7 @@ snapshots:
'@wordpress/jest-preset-default@12.22.0(@babel/core@7.25.7)(jest@29.7.0(@types/node@24.12.2)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))':
dependencies:
'@babel/core': 7.25.7
- '@wordpress/jest-console': 8.44.0(jest@29.7.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))
+ '@wordpress/jest-console': 8.44.0(jest@29.7.0(@types/node@24.12.2)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))
babel-jest: 29.7.0(@babel/core@7.25.7)
jest: 29.7.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
transitivePeerDependencies:
@@ -37061,7 +37404,7 @@ snapshots:
'@wordpress/jest-preset-default@12.44.0(@babel/core@7.25.7)(jest@29.7.0(@types/node@24.12.2)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))':
dependencies:
'@babel/core': 7.25.7
- '@wordpress/jest-console': 8.44.0(jest@29.7.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))
+ '@wordpress/jest-console': 8.44.0(jest@29.7.0(@types/node@24.12.2)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))
babel-jest: 29.7.0(@babel/core@7.25.7)
jest: 29.7.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
transitivePeerDependencies:
@@ -37152,14 +37495,30 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/media-fields@0.9.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
+ '@wordpress/media-editor@0.7.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.25.7
+ '@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/element': 6.44.0
+ '@wordpress/i18n': 6.18.0
+ react: 18.3.1
+ transitivePeerDependencies:
+ - '@date-fns/tz'
+ - '@emotion/is-prop-valid'
+ - '@types/react'
+ - react-dom
+ - stylelint
+ - supports-color
+
+ '@wordpress/media-fields@0.9.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
'@wordpress/base-styles': 6.20.0
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/data': 10.44.0(react@18.3.1)
- '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/date': 5.45.0
'@wordpress/element': 6.44.0
'@wordpress/i18n': 6.18.0
@@ -37177,14 +37536,14 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/media-fields@0.9.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ '@wordpress/media-fields@0.9.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
'@wordpress/base-styles': 6.20.0
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/data': 10.44.0(react@18.3.1)
- '@wordpress/dataviews': 14.2.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/date': 5.45.0
'@wordpress/element': 6.44.0
'@wordpress/i18n': 6.18.0
@@ -37244,23 +37603,23 @@ snapshots:
'@wordpress/i18n': 5.26.0
'@wordpress/private-apis': 1.44.0
- '@wordpress/media-utils@5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
+ '@wordpress/media-utils@5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
'@wordpress/api-fetch': 7.44.0
'@wordpress/base-styles': 6.20.0
'@wordpress/blob': 4.44.0
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/data': 10.44.0(react@18.3.1)
- '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/element': 6.44.0
'@wordpress/i18n': 6.18.0
'@wordpress/icons': 12.2.0(react@18.3.1)
- '@wordpress/media-fields': 0.9.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/media-fields': 0.9.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/notices': 5.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/ui': 0.11.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
- '@wordpress/views': 1.11.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/ui': 0.11.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/views': 1.11.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
clsx: 2.1.1
react: 18.3.1
transitivePeerDependencies:
@@ -37273,23 +37632,23 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/media-utils@5.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ '@wordpress/media-utils@5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
'@wordpress/api-fetch': 7.44.0
'@wordpress/base-styles': 6.20.0
'@wordpress/blob': 4.44.0
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/data': 10.44.0(react@18.3.1)
- '@wordpress/dataviews': 14.2.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/element': 6.44.0
'@wordpress/i18n': 6.18.0
'@wordpress/icons': 12.2.0(react@18.3.1)
- '@wordpress/media-fields': 0.9.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/media-fields': 0.9.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/notices': 5.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/ui': 0.11.0(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
- '@wordpress/views': 1.11.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/ui': 0.11.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/views': 1.11.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
clsx: 2.1.1
react: 18.3.1
transitivePeerDependencies:
@@ -37434,15 +37793,15 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/patterns@2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
+ '@wordpress/patterns@2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
'@wordpress/a11y': 4.45.0
'@wordpress/base-styles': 6.20.0
- '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/blocks': 15.17.0(react@18.3.1)
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/data': 10.44.0(react@18.3.1)
'@wordpress/element': 6.44.0
'@wordpress/html-entities': 4.45.0
@@ -37461,15 +37820,15 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/patterns@2.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ '@wordpress/patterns@2.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
'@wordpress/a11y': 4.45.0
'@wordpress/base-styles': 6.20.0
- '@wordpress/block-editor': 15.17.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/blocks': 15.17.0(react@18.3.1)
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/data': 10.44.0(react@18.3.1)
'@wordpress/element': 6.44.0
'@wordpress/html-entities': 4.45.0
@@ -37835,13 +38194,13 @@ snapshots:
- supports-color
- utf-8-validate
- '@wordpress/reusable-blocks@5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
+ '@wordpress/reusable-blocks@5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
'@wordpress/base-styles': 6.20.0
- '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/blocks': 15.17.0(react@18.3.1)
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/data': 10.44.0(react@18.3.1)
'@wordpress/element': 6.44.0
'@wordpress/i18n': 6.18.0
@@ -37859,13 +38218,13 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/reusable-blocks@5.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ '@wordpress/reusable-blocks@5.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
'@wordpress/base-styles': 6.20.0
- '@wordpress/block-editor': 15.17.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/blocks': 15.17.0(react@18.3.1)
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/data': 10.44.0(react@18.3.1)
'@wordpress/element': 6.44.0
'@wordpress/i18n': 6.18.0
@@ -38050,7 +38409,7 @@ snapshots:
expect-puppeteer: 4.4.0
filenamify: 4.3.0
jest: 26.6.3(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
- jest-circus: 26.6.3(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
+ jest-circus: 26.6.3
jest-dev-server: 5.0.3
jest-environment-node: 26.6.2
markdownlint: 0.23.1
@@ -38732,6 +39091,28 @@ snapshots:
'@wordpress/token-list@3.44.0': {}
+ '@wordpress/ui@0.11.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ dependencies:
+ '@base-ui/react': 1.4.1(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/a11y': 4.45.0
+ '@wordpress/compose': 7.44.0(react@18.3.1)
+ '@wordpress/element': 6.44.0
+ '@wordpress/i18n': 6.18.0
+ '@wordpress/icons': 12.2.0(react@18.3.1)
+ '@wordpress/keycodes': 4.45.0
+ '@wordpress/primitives': 4.45.0(react@18.3.1)
+ '@wordpress/private-apis': 1.44.0
+ '@wordpress/theme': 0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ clsx: 2.1.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ tabbable: 6.4.0
+ transitivePeerDependencies:
+ - '@date-fns/tz'
+ - '@types/react'
+ - date-fns
+ - stylelint
+
'@wordpress/ui@0.11.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
'@base-ui/react': 1.4.1(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -38754,7 +39135,7 @@ snapshots:
- date-fns
- stylelint
- '@wordpress/ui@0.11.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
+ '@wordpress/ui@0.11.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
'@base-ui/react': 1.4.1(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/a11y': 4.45.0
@@ -38765,7 +39146,7 @@ snapshots:
'@wordpress/keycodes': 4.45.0
'@wordpress/primitives': 4.45.0(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/theme': 0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/theme': 0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
clsx: 2.1.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
@@ -38776,9 +39157,9 @@ snapshots:
- date-fns
- stylelint
- '@wordpress/ui@0.11.0(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ '@wordpress/ui@0.11.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
- '@base-ui/react': 1.4.1(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@3.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@base-ui/react': 1.4.1(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/a11y': 4.45.0
'@wordpress/compose': 7.44.0(react@18.3.1)
'@wordpress/element': 6.44.0
@@ -38787,7 +39168,7 @@ snapshots:
'@wordpress/keycodes': 4.45.0
'@wordpress/primitives': 4.45.0(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/theme': 0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/theme': 0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
clsx: 2.1.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
@@ -38809,7 +39190,7 @@ snapshots:
'@wordpress/keycodes': 4.45.0
'@wordpress/primitives': 4.45.0(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/theme': 0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/theme': 0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
clsx: 2.1.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
@@ -38820,7 +39201,7 @@ snapshots:
- date-fns
- stylelint
- '@wordpress/ui@0.12.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
+ '@wordpress/ui@0.12.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
'@base-ui/react': 1.4.1(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/a11y': 4.45.0
@@ -38831,7 +39212,7 @@ snapshots:
'@wordpress/keycodes': 4.45.0
'@wordpress/primitives': 4.45.0(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/theme': 0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/theme': 0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
clsx: 2.1.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
@@ -38842,7 +39223,7 @@ snapshots:
- date-fns
- stylelint
- '@wordpress/ui@0.12.0(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ '@wordpress/ui@0.12.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
'@base-ui/react': 1.4.1(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/a11y': 4.45.0
@@ -38853,7 +39234,7 @@ snapshots:
'@wordpress/keycodes': 4.45.0
'@wordpress/primitives': 4.45.0(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/theme': 0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/theme': 0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
clsx: 2.1.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
@@ -38875,7 +39256,7 @@ snapshots:
'@wordpress/keycodes': 4.45.0
'@wordpress/primitives': 4.45.0(react@18.3.1)
'@wordpress/private-apis': 1.44.0
- '@wordpress/theme': 0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/theme': 0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
clsx: 2.1.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
@@ -39042,11 +39423,11 @@ snapshots:
'@wordpress/element': 6.44.0
react: 18.3.1
- '@wordpress/views@1.11.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
+ '@wordpress/views@1.11.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
dependencies:
- '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/data': 10.44.0(react@18.3.1)
- '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/element': 6.44.0
'@wordpress/preferences': 4.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/private-apis': 1.44.0
@@ -39061,11 +39442,11 @@ snapshots:
- stylelint
- supports-color
- '@wordpress/views@1.11.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)':
+ '@wordpress/views@1.11.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))':
dependencies:
- '@wordpress/core-data': 7.44.0(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/data': 10.44.0(react@18.3.1)
- '@wordpress/dataviews': 14.2.0(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
+ '@wordpress/dataviews': 14.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
'@wordpress/element': 6.44.0
'@wordpress/preferences': 4.44.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/private-apis': 1.44.0
@@ -39140,11 +39521,11 @@ snapshots:
dependencies:
'@wordpress/api-fetch': 7.44.0
'@wordpress/base-styles': 6.20.0
- '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/block-editor': 15.17.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/blocks': 15.17.0(react@18.3.1)
'@wordpress/components': 32.6.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/compose': 7.44.0(react@18.3.1)
- '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@16.26.1(typescript@5.7.3))
+ '@wordpress/core-data': 7.44.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.4.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@14.16.1)
'@wordpress/data': 10.44.0(react@18.3.1)
'@wordpress/element': 6.44.0
'@wordpress/i18n': 6.18.0
@@ -41466,7 +41847,7 @@ snapshots:
schema-utils: 4.3.3
serialize-javascript: 6.0.2
tinyglobby: 0.2.16
- webpack: 5.97.1(@swc/core@1.15.24)(webpack-cli@5.1.4)
+ webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.18.20)(webpack-cli@5.1.4)
core-js-compat@3.49.0:
dependencies:
@@ -41712,7 +42093,7 @@ snapshots:
postcss-value-parser: 4.2.0
semver: 7.7.4
optionalDependencies:
- webpack: 5.97.1(@swc/core@1.15.24)(webpack-cli@5.1.4)
+ webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.24.2)(webpack-cli@5.1.4)
css-select-base-adapter@0.1.1: {}
@@ -46208,7 +46589,7 @@ snapshots:
jest-util: 29.7.0
p-limit: 3.1.0
- jest-circus@26.6.3(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)):
+ jest-circus@26.6.3:
dependencies:
'@babel/traverse': 7.29.0
'@jest/environment': 26.6.2
@@ -46232,11 +46613,7 @@ snapshots:
stack-utils: 2.0.6
throat: 5.0.0
transitivePeerDependencies:
- - bufferutil
- - canvas
- supports-color
- - ts-node
- - utf-8-validate
jest-circus@29.5.0:
dependencies:
@@ -46357,7 +46734,7 @@ snapshots:
jest-config@26.6.3(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)):
dependencies:
'@babel/core': 7.25.7
- '@jest/test-sequencer': 26.6.3(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
+ '@jest/test-sequencer': 26.6.3
'@jest/types': 26.6.2
babel-jest: 26.6.3(@babel/core@7.25.7)
chalk: 4.1.2
@@ -48361,7 +48738,7 @@ snapshots:
dependencies:
schema-utils: 4.3.3
tapable: 2.3.2
- webpack: 5.97.1(@swc/core@1.15.24)(webpack-cli@5.1.4)
+ webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.24.2)(webpack-cli@5.1.4)
minimalistic-assert@1.0.1: {}
@@ -48884,7 +49261,7 @@ snapshots:
dependencies:
hosted-git-info: 6.1.3
proc-log: 3.0.0
- semver: 7.5.4
+ semver: 7.7.4
validate-npm-package-name: 5.0.1
npm-package-arg@8.1.5:
@@ -49928,7 +50305,7 @@ snapshots:
postcss: 8.4.49
schema-utils: 3.3.0
semver: 7.7.4
- webpack: 5.97.1(@swc/core@1.15.24)(webpack-cli@5.1.4)
+ webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.18.20)(webpack-cli@5.1.4)
postcss-loader@4.3.0(postcss@8.5.9)(webpack@5.97.1(@swc/core@1.15.24)):
dependencies:
@@ -51912,7 +52289,7 @@ snapshots:
neo-async: 2.6.2
schema-utils: 3.3.0
semver: 7.7.4
- webpack: 5.97.1(@swc/core@1.15.24)(webpack-cli@5.1.4)
+ webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.18.20)(webpack-cli@5.1.4)
optionalDependencies:
sass: 1.69.5
@@ -54547,11 +54924,11 @@ snapshots:
import-local: 3.2.0
interpret: 3.1.1
rechoir: 0.8.0
- webpack: 5.97.1(@swc/core@1.15.24)(webpack-cli@5.1.4)
+ webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack-merge: 5.10.0
optionalDependencies:
webpack-bundle-analyzer: 4.9.1
- webpack-dev-server: 4.15.2(debug@4.4.3)(webpack-cli@5.1.4)(webpack@5.97.1)
+ webpack-dev-server: 4.15.2(webpack-cli@5.1.4)(webpack@5.97.1)
webpack-cli@5.1.4(webpack-dev-server@4.15.2)(webpack@5.97.1):
dependencies:
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 474633ff242..0ccb6766dab 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -9,6 +9,7 @@ catalogs:
'@types/wordpress__edit-post': 8.4.2
'@types/wordpress__viewport': 5.5.3
'@wordpress/a11y': 4.19.1
+ '@wordpress/admin-ui': 1.12.0
'@wordpress/api-fetch': 7.19.2
'@wordpress/base-styles': 5.19.1
'@wordpress/blob': 4.19.1