From eb573850a1ee8c9e14474c89f9bb1f96d1cd15c7 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Tue, 11 Aug 2026 18:39:32 +0100 Subject: [PATCH 1/3] DataViews: vendor ValidatedToggleGroupControl --- packages/components/src/private-apis.ts | 2 - .../components/index.ts | 1 - .../components/stories/overview.mdx | 4 +- .../stories/toggle-group-control.story.tsx | 58 ------------------- .../dataform-controls/toggle-group.tsx | 5 +- .../validated-form-controls/index.ts | 1 + .../validated-form-controls/style.scss | 5 ++ .../test/toggle-group-control.tsx | 4 +- .../toggle-group-control.tsx | 25 +++++--- 9 files changed, 27 insertions(+), 78 deletions(-) delete mode 100644 packages/components/src/validated-form-controls/components/stories/toggle-group-control.story.tsx rename packages/{components/src => dataviews/src/components}/validated-form-controls/test/toggle-group-control.tsx (85%) rename packages/{components/src/validated-form-controls/components => dataviews/src/components/validated-form-controls}/toggle-group-control.tsx (62%) diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts index 40d044dbb50946..fe941a1e4e972a 100644 --- a/packages/components/src/private-apis.ts +++ b/packages/components/src/private-apis.ts @@ -10,7 +10,6 @@ import { ValidatedContentEditableControl, ValidatedTextareaControl, ValidatedToggleControl, - ValidatedToggleGroupControl, } from './validated-form-controls'; import ContentEditableControl from './content-editable-control'; @@ -27,5 +26,4 @@ lock( privateApis, { ValidatedContentEditableControl, ValidatedTextareaControl, ValidatedToggleControl, - ValidatedToggleGroupControl, } ); diff --git a/packages/components/src/validated-form-controls/components/index.ts b/packages/components/src/validated-form-controls/components/index.ts index b168f130aa644f..7d0df907570547 100644 --- a/packages/components/src/validated-form-controls/components/index.ts +++ b/packages/components/src/validated-form-controls/components/index.ts @@ -4,4 +4,3 @@ export * from './range-control'; export * from './content-editable-control'; export * from './textarea-control'; export * from './toggle-control'; -export * from './toggle-group-control'; diff --git a/packages/components/src/validated-form-controls/components/stories/overview.mdx b/packages/components/src/validated-form-controls/components/stories/overview.mdx index 37fe3d204763be..b772efa874c27a 100644 --- a/packages/components/src/validated-form-controls/components/stories/overview.mdx +++ b/packages/components/src/validated-form-controls/components/stories/overview.mdx @@ -39,9 +39,9 @@ We don't foresee these being needed much, but it is technically possible by the ### Delegate elements -The implementations for `ToggleGroupControl` and `CustomSelectControl` use a "delegate" element for validation, due to upstream limitations that prevent us from using the actual underlying elements for constraint validation. A delegate element in this context is a visually hidden form element that we "delegate" the Constraint Validation API concerns to. +The implementation for `CustomSelectControl` uses a "delegate" element for validation, due to upstream limitations that prevent us from using the actual underlying elements for constraint validation. A delegate element in this context is a visually hidden form element that we "delegate" the Constraint Validation API concerns to. -This is not ideal, but lets us maintain a consistent mechanism to scroll to the invalid field when attempting to submit, and block the form from submitting. It is hopefully fine as a stopgap, given the low amount of actual validation that will need happen on these two specific components. +This is not ideal, but lets us maintain a consistent mechanism to scroll to the invalid field when attempting to submit, and block the form from submitting. It is hopefully fine as a stopgap, given the low amount of actual validation that will need happen on this specific component. ### Controlled mode only diff --git a/packages/components/src/validated-form-controls/components/stories/toggle-group-control.story.tsx b/packages/components/src/validated-form-controls/components/stories/toggle-group-control.story.tsx deleted file mode 100644 index 016ae81c53db38..00000000000000 --- a/packages/components/src/validated-form-controls/components/stories/toggle-group-control.story.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useState } from '@wordpress/element'; -import type { StoryObj, Meta } from '@storybook/react-vite'; -import { formDecorator } from './story-utils'; -import { ValidatedToggleGroupControl } from '../toggle-group-control'; -import { ToggleGroupControlOption } from '../../../toggle-group-control'; - -const meta: Meta< typeof ValidatedToggleGroupControl > = { - title: 'Components/Selection & Input/Validated Form Controls/ValidatedToggleGroupControl', - id: 'components-validatedtogglegroupcontrol', - component: ValidatedToggleGroupControl, - tags: [ 'status-private' ], - decorators: formDecorator, - args: { onChange: () => {} }, - argTypes: { - value: { control: false }, - }, -}; -export default meta; - -export const Default: StoryObj< typeof ValidatedToggleGroupControl > = { - render: function Template( { onChange, ...args } ) { - const [ value, setValue ] = - useState< - React.ComponentProps< - typeof ValidatedToggleGroupControl - >[ 'value' ] - >( '1' ); - - return ( - { - setValue( newValue ); - onChange?.( newValue ); - } } - customValidity={ - value === '2' - ? { - type: 'invalid', - message: 'Option 2 is not allowed.', - } - : undefined - } - /> - ); - }, -}; -Default.args = { - required: true, - label: 'Toggle Group', - isBlock: true, - children: [ - , - , - ], - help: 'Selecting option 2 will trigger an error.', -}; diff --git a/packages/dataviews/src/components/dataform-controls/toggle-group.tsx b/packages/dataviews/src/components/dataform-controls/toggle-group.tsx index fa457201a84c28..41431bbb2835c6 100644 --- a/packages/dataviews/src/components/dataform-controls/toggle-group.tsx +++ b/packages/dataviews/src/components/dataform-controls/toggle-group.tsx @@ -1,16 +1,13 @@ import { - privateApis, __experimentalToggleGroupControlOption as ToggleGroupControlOption, Spinner, } from '@wordpress/components'; import { useCallback } from '@wordpress/element'; import type { DataFormControlProps } from '../../types'; -import { unlock } from '../../lock-unlock'; +import { ValidatedToggleGroupControl } from '../validated-form-controls'; import getCustomValidity from './utils/get-custom-validity'; import useElements from '../../hooks/use-elements'; -const { ValidatedToggleGroupControl } = unlock( privateApis ); - export default function ToggleGroup< Item >( { data, field, diff --git a/packages/dataviews/src/components/validated-form-controls/index.ts b/packages/dataviews/src/components/validated-form-controls/index.ts index 75a6a79f34f445..3dbd78a40b80df 100644 --- a/packages/dataviews/src/components/validated-form-controls/index.ts +++ b/packages/dataviews/src/components/validated-form-controls/index.ts @@ -5,4 +5,5 @@ export { ValidatedFormTokenField } from './form-token-field'; export { ValidatedNumberControl } from './number-control'; export { ValidatedRadioControl } from './radio-control'; export { ValidatedSelectControl } from './select-control'; +export { ValidatedToggleGroupControl } from './toggle-group-control'; export type { ValidatedControlProps } from './types'; diff --git a/packages/dataviews/src/components/validated-form-controls/style.scss b/packages/dataviews/src/components/validated-form-controls/style.scss index 90fae86c3def43..2b53b6f56131ca 100644 --- a/packages/dataviews/src/components/validated-form-controls/style.scss +++ b/packages/dataviews/src/components/validated-form-controls/style.scss @@ -25,6 +25,11 @@ --wp-admin-theme-color: #{$alert-red}; border-color: $alert-red; } + + // For ToggleGroupControl + &:has(input[type="radio"]:invalid[data-validity-visible]) { + --wp-components-color-accent: #{$alert-red}; + } } .dataviews-validated-control__error-delegate { diff --git a/packages/components/src/validated-form-controls/test/toggle-group-control.tsx b/packages/dataviews/src/components/validated-form-controls/test/toggle-group-control.tsx similarity index 85% rename from packages/components/src/validated-form-controls/test/toggle-group-control.tsx rename to packages/dataviews/src/components/validated-form-controls/test/toggle-group-control.tsx index 90438c4a31ecf9..58fc1d58efea87 100644 --- a/packages/components/src/validated-form-controls/test/toggle-group-control.tsx +++ b/packages/dataviews/src/components/validated-form-controls/test/toggle-group-control.tsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react'; -import { ValidatedToggleGroupControl } from '../components'; -import { ToggleGroupControlOption } from '../../toggle-group-control'; +import { __experimentalToggleGroupControlOption as ToggleGroupControlOption } from '@wordpress/components'; +import { ValidatedToggleGroupControl } from '../toggle-group-control'; // The `help` prop is rendered visually by BaseControl but is not // programmatically associated with the toggle group via aria-describedby. diff --git a/packages/components/src/validated-form-controls/components/toggle-group-control.tsx b/packages/dataviews/src/components/validated-form-controls/toggle-group-control.tsx similarity index 62% rename from packages/components/src/validated-form-controls/components/toggle-group-control.tsx rename to packages/dataviews/src/components/validated-form-controls/toggle-group-control.tsx index bc5d5471abad76..70d82c5d6c80a7 100644 --- a/packages/components/src/validated-form-controls/components/toggle-group-control.tsx +++ b/packages/dataviews/src/components/validated-form-controls/toggle-group-control.tsx @@ -1,7 +1,14 @@ import { forwardRef, useId, useRef } from '@wordpress/element'; -import { ControlWithError } from '../control-with-error'; +import { __experimentalToggleGroupControl as ToggleGroupControl } from '@wordpress/components'; +import { ControlWithError } from './control-with-error'; import type { ValidatedControlProps } from './types'; -import { ToggleGroupControl } from '../../toggle-group-control'; + +type ToggleGroupControlProps = React.ComponentProps< + typeof ToggleGroupControl +>; + +type ValidatedToggleGroupControlProps = ToggleGroupControlProps & + ValidatedControlProps; const UnforwardedValidatedToggleGroupControl = ( { @@ -9,8 +16,7 @@ const UnforwardedValidatedToggleGroupControl = ( customValidity, markWhenOptional, ...restProps - }: React.ComponentProps< typeof ToggleGroupControl > & - ValidatedControlProps, + }: ValidatedToggleGroupControlProps, forwardedRef: React.ForwardedRef< HTMLInputElement > ) => { const validityTargetRef = useRef< HTMLInputElement >( null ); @@ -18,7 +24,7 @@ const UnforwardedValidatedToggleGroupControl = ( const nameAttr = useId(); return ( -
+
& + React.RefAttributes< HTMLInputElement > +> = forwardRef( UnforwardedValidatedToggleGroupControl ); ValidatedToggleGroupControl.displayName = 'ValidatedToggleGroupControl'; From cd1390ce85bd1dfc6c05daf999089331b1cfa8dc Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Tue, 11 Aug 2026 18:40:53 +0100 Subject: [PATCH 2/3] Add changelog entries --- packages/components/CHANGELOG.md | 1 + packages/dataviews/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 006c8e709e1336..da780b31dbd653 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -44,6 +44,7 @@ - Remove `ValidatedNumberControl` from the private APIs; it now lives internally in `@wordpress/dataviews`, its only consumer ([#81433](https://github.com/WordPress/gutenberg/pull/81433)). - Remove `ValidatedRadioControl` from the private APIs; it now lives internally in `@wordpress/dataviews`, its only consumer ([#81434](https://github.com/WordPress/gutenberg/pull/81434)). - Remove `ValidatedFormTokenField` from the private APIs; it now lives internally in `@wordpress/dataviews`, its only consumer ([#81451](https://github.com/WordPress/gutenberg/pull/81451)). +- Remove `ValidatedToggleGroupControl` from the private APIs; it now lives internally in `@wordpress/dataviews`, its only consumer ([#81450](https://github.com/WordPress/gutenberg/pull/81450)). - Remove `ValidatedSelectControl` from the private APIs; it now lives internally in `@wordpress/dataviews`, its only consumer ([#81391](https://github.com/WordPress/gutenberg/pull/81391)). - Move `withIgnoreIMEEvents` to `@wordpress/keycodes`, where it is now a public API, and remove it from this package's private APIs ([#81343](https://github.com/WordPress/gutenberg/pull/81343)). - Extract the private `kebabCase` utility to the new `@wordpress/kebab-case` package and remove it from the private APIs; all consumers now use the package directly ([#81294](https://github.com/WordPress/gutenberg/pull/81294)). diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md index 3bc7b2212d988f..40e389069a4df2 100644 --- a/packages/dataviews/CHANGELOG.md +++ b/packages/dataviews/CHANGELOG.md @@ -15,6 +15,7 @@ - DataForm: Internalize `ValidatedNumberControl` instead of unlocking it from the `@wordpress/components` private APIs. [#81433](https://github.com/WordPress/gutenberg/pull/81433) - DataForm: Internalize `ValidatedRadioControl` instead of unlocking it from the `@wordpress/components` private APIs. [#81434](https://github.com/WordPress/gutenberg/pull/81434) - DataForm: Internalize `ValidatedFormTokenField` instead of unlocking it from the `@wordpress/components` private APIs. [#81451](https://github.com/WordPress/gutenberg/pull/81451) +- DataForm: Internalize `ValidatedToggleGroupControl` instead of unlocking it from the `@wordpress/components` private APIs. [#81450](https://github.com/WordPress/gutenberg/pull/81450) ### New Features From 8a6ec2e33fd92210b43c5045dbea19f277025ea4 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Wed, 12 Aug 2026 09:04:56 +0100 Subject: [PATCH 3/3] Remove no longer used styles for validated toggle group --- packages/components/src/validated-form-controls/style.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/components/src/validated-form-controls/style.scss b/packages/components/src/validated-form-controls/style.scss index fb989675da65e4..7ad99b2e470aff 100644 --- a/packages/components/src/validated-form-controls/style.scss +++ b/packages/components/src/validated-form-controls/style.scss @@ -27,11 +27,6 @@ border-color: $alert-red; } - // For ToggleGroupControl - &:has(input[type="radio"]:invalid[data-validity-visible]) { - --wp-components-color-accent: #{$alert-red}; - } - // For ContentEditableControl &:has(input:invalid[data-validity-visible]) .components-validated-control__content-editable [role="textbox"] {