@wordpress/components 30.9.1-next.8b30e05b0.0 → 31.0.1-next.6deb34194.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/CHANGELOG.md +25 -4
  2. package/build/alignment-matrix-control/cell.js +131 -3
  3. package/build/alignment-matrix-control/cell.js.map +4 -4
  4. package/build/alignment-matrix-control/index.js +134 -6
  5. package/build/alignment-matrix-control/index.js.map +3 -3
  6. package/build/angle-picker-control/angle-circle.js +119 -15
  7. package/build/angle-picker-control/angle-circle.js.map +4 -4
  8. package/build/angle-picker-control/index.js +12 -7
  9. package/build/angle-picker-control/index.js.map +3 -3
  10. package/build/dropdown-menu/index.js +1 -1
  11. package/build/dropdown-menu/index.js.map +2 -2
  12. package/build/menu/styles.js +17 -17
  13. package/build/menu/styles.js.map +2 -2
  14. package/build/menu-item/index.js +1 -1
  15. package/build/menu-item/index.js.map +2 -2
  16. package/build/notice/index.js +1 -1
  17. package/build/notice/index.js.map +2 -2
  18. package/build/snackbar/index.js +1 -1
  19. package/build/snackbar/index.js.map +1 -1
  20. package/build/validated-form-controls/components/checkbox-control.js +0 -10
  21. package/build/validated-form-controls/components/checkbox-control.js.map +2 -2
  22. package/build/validated-form-controls/components/combobox-control.js +1 -11
  23. package/build/validated-form-controls/components/combobox-control.js.map +2 -2
  24. package/build/validated-form-controls/components/custom-select-control.js +0 -10
  25. package/build/validated-form-controls/components/custom-select-control.js.map +2 -2
  26. package/build/validated-form-controls/components/form-token-field.js +2 -12
  27. package/build/validated-form-controls/components/form-token-field.js.map +2 -2
  28. package/build/validated-form-controls/components/input-control.js +0 -10
  29. package/build/validated-form-controls/components/input-control.js.map +2 -2
  30. package/build/validated-form-controls/components/number-control.js +0 -10
  31. package/build/validated-form-controls/components/number-control.js.map +2 -2
  32. package/build/validated-form-controls/components/radio-control.js +0 -10
  33. package/build/validated-form-controls/components/radio-control.js.map +2 -2
  34. package/build/validated-form-controls/components/range-control.js +0 -10
  35. package/build/validated-form-controls/components/range-control.js.map +2 -2
  36. package/build/validated-form-controls/components/select-control.js +0 -10
  37. package/build/validated-form-controls/components/select-control.js.map +2 -2
  38. package/build/validated-form-controls/components/text-control.js +0 -10
  39. package/build/validated-form-controls/components/text-control.js.map +2 -2
  40. package/build/validated-form-controls/components/textarea-control.js +0 -10
  41. package/build/validated-form-controls/components/textarea-control.js.map +2 -2
  42. package/build/validated-form-controls/components/toggle-control.js +0 -10
  43. package/build/validated-form-controls/components/toggle-control.js.map +2 -2
  44. package/build/validated-form-controls/components/toggle-group-control.js +0 -10
  45. package/build/validated-form-controls/components/toggle-group-control.js.map +2 -2
  46. package/build/validated-form-controls/control-with-error.js +53 -58
  47. package/build/validated-form-controls/control-with-error.js.map +2 -2
  48. package/build-module/alignment-matrix-control/cell.js +131 -3
  49. package/build-module/alignment-matrix-control/cell.js.map +3 -3
  50. package/build-module/alignment-matrix-control/index.js +134 -6
  51. package/build-module/alignment-matrix-control/index.js.map +3 -3
  52. package/build-module/angle-picker-control/angle-circle.js +109 -15
  53. package/build-module/angle-picker-control/angle-circle.js.map +3 -3
  54. package/build-module/angle-picker-control/index.js +12 -7
  55. package/build-module/angle-picker-control/index.js.map +2 -2
  56. package/build-module/dropdown-menu/index.js +1 -1
  57. package/build-module/dropdown-menu/index.js.map +2 -2
  58. package/build-module/menu/styles.js +17 -17
  59. package/build-module/menu/styles.js.map +2 -2
  60. package/build-module/menu-item/index.js +1 -1
  61. package/build-module/menu-item/index.js.map +2 -2
  62. package/build-module/notice/index.js +1 -1
  63. package/build-module/notice/index.js.map +2 -2
  64. package/build-module/snackbar/index.js +1 -1
  65. package/build-module/snackbar/index.js.map +1 -1
  66. package/build-module/validated-form-controls/components/checkbox-control.js +0 -10
  67. package/build-module/validated-form-controls/components/checkbox-control.js.map +2 -2
  68. package/build-module/validated-form-controls/components/combobox-control.js +1 -11
  69. package/build-module/validated-form-controls/components/combobox-control.js.map +2 -2
  70. package/build-module/validated-form-controls/components/custom-select-control.js +0 -10
  71. package/build-module/validated-form-controls/components/custom-select-control.js.map +2 -2
  72. package/build-module/validated-form-controls/components/form-token-field.js +2 -12
  73. package/build-module/validated-form-controls/components/form-token-field.js.map +2 -2
  74. package/build-module/validated-form-controls/components/input-control.js +0 -10
  75. package/build-module/validated-form-controls/components/input-control.js.map +2 -2
  76. package/build-module/validated-form-controls/components/number-control.js +0 -10
  77. package/build-module/validated-form-controls/components/number-control.js.map +2 -2
  78. package/build-module/validated-form-controls/components/radio-control.js +0 -10
  79. package/build-module/validated-form-controls/components/radio-control.js.map +2 -2
  80. package/build-module/validated-form-controls/components/range-control.js +0 -10
  81. package/build-module/validated-form-controls/components/range-control.js.map +2 -2
  82. package/build-module/validated-form-controls/components/select-control.js +0 -10
  83. package/build-module/validated-form-controls/components/select-control.js.map +2 -2
  84. package/build-module/validated-form-controls/components/text-control.js +0 -10
  85. package/build-module/validated-form-controls/components/text-control.js.map +2 -2
  86. package/build-module/validated-form-controls/components/textarea-control.js +0 -10
  87. package/build-module/validated-form-controls/components/textarea-control.js.map +2 -2
  88. package/build-module/validated-form-controls/components/toggle-control.js +0 -10
  89. package/build-module/validated-form-controls/components/toggle-control.js.map +2 -2
  90. package/build-module/validated-form-controls/components/toggle-group-control.js +0 -10
  91. package/build-module/validated-form-controls/components/toggle-group-control.js.map +2 -2
  92. package/build-module/validated-form-controls/control-with-error.js +53 -58
  93. package/build-module/validated-form-controls/control-with-error.js.map +2 -2
  94. package/build-style/style-rtl.css +21 -33
  95. package/build-style/style.css +21 -33
  96. package/build-types/alignment-matrix-control/cell.d.ts.map +1 -1
  97. package/build-types/alignment-matrix-control/index.d.ts.map +1 -1
  98. package/build-types/angle-picker-control/angle-circle.d.ts +1 -1
  99. package/build-types/angle-picker-control/angle-circle.d.ts.map +1 -1
  100. package/build-types/angle-picker-control/index.d.ts.map +1 -1
  101. package/build-types/notice/index.d.ts.map +1 -1
  102. package/build-types/notice/stories/index.story.d.ts.map +1 -1
  103. package/build-types/validated-form-controls/components/checkbox-control.d.ts +1 -1
  104. package/build-types/validated-form-controls/components/checkbox-control.d.ts.map +1 -1
  105. package/build-types/validated-form-controls/components/combobox-control.d.ts +2 -3
  106. package/build-types/validated-form-controls/components/combobox-control.d.ts.map +1 -1
  107. package/build-types/validated-form-controls/components/custom-select-control.d.ts +1 -2
  108. package/build-types/validated-form-controls/components/custom-select-control.d.ts.map +1 -1
  109. package/build-types/validated-form-controls/components/form-token-field.d.ts +1 -2
  110. package/build-types/validated-form-controls/components/form-token-field.d.ts.map +1 -1
  111. package/build-types/validated-form-controls/components/input-control.d.ts +1 -2
  112. package/build-types/validated-form-controls/components/input-control.d.ts.map +1 -1
  113. package/build-types/validated-form-controls/components/number-control.d.ts +1 -1
  114. package/build-types/validated-form-controls/components/number-control.d.ts.map +1 -1
  115. package/build-types/validated-form-controls/components/radio-control.d.ts +1 -1
  116. package/build-types/validated-form-controls/components/radio-control.d.ts.map +1 -1
  117. package/build-types/validated-form-controls/components/range-control.d.ts +1 -1
  118. package/build-types/validated-form-controls/components/range-control.d.ts.map +1 -1
  119. package/build-types/validated-form-controls/components/select-control.d.ts +2 -3
  120. package/build-types/validated-form-controls/components/select-control.d.ts.map +1 -1
  121. package/build-types/validated-form-controls/components/stories/checkbox-control.story.d.ts.map +1 -1
  122. package/build-types/validated-form-controls/components/stories/combobox-control.story.d.ts.map +1 -1
  123. package/build-types/validated-form-controls/components/stories/custom-select-control.story.d.ts.map +1 -1
  124. package/build-types/validated-form-controls/components/stories/form-token-field.story.d.ts.map +1 -1
  125. package/build-types/validated-form-controls/components/stories/input-control.story.d.ts.map +1 -1
  126. package/build-types/validated-form-controls/components/stories/number-control.story.d.ts.map +1 -1
  127. package/build-types/validated-form-controls/components/stories/overview.story.d.ts +7 -0
  128. package/build-types/validated-form-controls/components/stories/overview.story.d.ts.map +1 -1
  129. package/build-types/validated-form-controls/components/stories/radio-control.story.d.ts.map +1 -1
  130. package/build-types/validated-form-controls/components/stories/range-control.story.d.ts.map +1 -1
  131. package/build-types/validated-form-controls/components/stories/select-control.story.d.ts.map +1 -1
  132. package/build-types/validated-form-controls/components/stories/text-control.story.d.ts.map +1 -1
  133. package/build-types/validated-form-controls/components/stories/textarea-control.story.d.ts.map +1 -1
  134. package/build-types/validated-form-controls/components/stories/toggle-control.story.d.ts.map +1 -1
  135. package/build-types/validated-form-controls/components/stories/toggle-group-control.story.d.ts.map +1 -1
  136. package/build-types/validated-form-controls/components/text-control.d.ts +1 -1
  137. package/build-types/validated-form-controls/components/text-control.d.ts.map +1 -1
  138. package/build-types/validated-form-controls/components/textarea-control.d.ts +1 -1
  139. package/build-types/validated-form-controls/components/textarea-control.d.ts.map +1 -1
  140. package/build-types/validated-form-controls/components/toggle-control.d.ts +1 -1
  141. package/build-types/validated-form-controls/components/toggle-control.d.ts.map +1 -1
  142. package/build-types/validated-form-controls/components/toggle-group-control.d.ts +1 -1
  143. package/build-types/validated-form-controls/components/toggle-group-control.d.ts.map +1 -1
  144. package/build-types/validated-form-controls/components/types.d.ts +1 -9
  145. package/build-types/validated-form-controls/components/types.d.ts.map +1 -1
  146. package/build-types/validated-form-controls/control-with-error.d.ts +4 -5
  147. package/build-types/validated-form-controls/control-with-error.d.ts.map +1 -1
  148. package/package.json +20 -20
  149. package/src/alignment-matrix-control/cell.tsx +14 -3
  150. package/src/alignment-matrix-control/index.tsx +15 -6
  151. package/src/alignment-matrix-control/style.module.scss +84 -0
  152. package/src/angle-picker-control/angle-circle.tsx +27 -12
  153. package/src/angle-picker-control/index.tsx +8 -7
  154. package/src/angle-picker-control/style.module.scss +40 -0
  155. package/src/button/style.scss +1 -1
  156. package/src/dropdown-menu/index.tsx +1 -1
  157. package/src/dropdown-menu/style.scss +1 -1
  158. package/src/guide/style.scss +3 -3
  159. package/src/menu/styles.ts +2 -2
  160. package/src/menu-item/index.tsx +1 -1
  161. package/src/menu-item/test/__snapshots__/index.js.snap +4 -4
  162. package/src/modal/style.scss +5 -5
  163. package/src/notice/index.tsx +53 -46
  164. package/src/notice/stories/index.story.tsx +17 -1
  165. package/src/notice/style.scss +3 -20
  166. package/src/snackbar/index.tsx +1 -1
  167. package/src/validated-form-controls/components/checkbox-control.tsx +1 -14
  168. package/src/validated-form-controls/components/combobox-control.tsx +1 -14
  169. package/src/validated-form-controls/components/custom-select-control.tsx +1 -19
  170. package/src/validated-form-controls/components/form-token-field.tsx +2 -15
  171. package/src/validated-form-controls/components/input-control.tsx +1 -14
  172. package/src/validated-form-controls/components/number-control.tsx +1 -16
  173. package/src/validated-form-controls/components/radio-control.tsx +2 -18
  174. package/src/validated-form-controls/components/range-control.tsx +1 -14
  175. package/src/validated-form-controls/components/select-control.tsx +1 -23
  176. package/src/validated-form-controls/components/stories/checkbox-control.story.tsx +11 -20
  177. package/src/validated-form-controls/components/stories/combobox-control.story.tsx +8 -17
  178. package/src/validated-form-controls/components/stories/custom-select-control.story.tsx +8 -17
  179. package/src/validated-form-controls/components/stories/form-token-field.story.tsx +14 -26
  180. package/src/validated-form-controls/components/stories/input-control.story.tsx +25 -50
  181. package/src/validated-form-controls/components/stories/number-control.story.tsx +10 -19
  182. package/src/validated-form-controls/components/stories/overview.mdx +3 -3
  183. package/src/validated-form-controls/components/stories/overview.story.tsx +94 -79
  184. package/src/validated-form-controls/components/stories/radio-control.story.tsx +11 -20
  185. package/src/validated-form-controls/components/stories/range-control.story.tsx +8 -17
  186. package/src/validated-form-controls/components/stories/select-control.story.tsx +9 -18
  187. package/src/validated-form-controls/components/stories/text-control.story.tsx +11 -17
  188. package/src/validated-form-controls/components/stories/textarea-control.story.tsx +12 -16
  189. package/src/validated-form-controls/components/stories/toggle-control.story.tsx +11 -20
  190. package/src/validated-form-controls/components/stories/toggle-group-control.story.tsx +8 -17
  191. package/src/validated-form-controls/components/text-control.tsx +1 -14
  192. package/src/validated-form-controls/components/textarea-control.tsx +1 -14
  193. package/src/validated-form-controls/components/toggle-control.tsx +1 -14
  194. package/src/validated-form-controls/components/toggle-group-control.tsx +1 -14
  195. package/src/validated-form-controls/components/types.ts +1 -9
  196. package/src/validated-form-controls/control-with-error.tsx +57 -84
  197. package/src/validated-form-controls/style.scss +7 -7
  198. package/src/validated-form-controls/test/control-with-error.tsx +66 -5
  199. package/tsconfig.json +1 -0
  200. package/tsconfig.tsbuildinfo +1 -1
  201. package/build/alignment-matrix-control/styles.js +0 -105
  202. package/build/alignment-matrix-control/styles.js.map +0 -7
  203. package/build/angle-picker-control/styles/angle-picker-control-styles.js +0 -88
  204. package/build/angle-picker-control/styles/angle-picker-control-styles.js.map +0 -7
  205. package/build-module/alignment-matrix-control/styles.js +0 -67
  206. package/build-module/alignment-matrix-control/styles.js.map +0 -7
  207. package/build-module/angle-picker-control/styles/angle-picker-control-styles.js +0 -50
  208. package/build-module/angle-picker-control/styles/angle-picker-control-styles.js.map +0 -7
  209. package/build-types/alignment-matrix-control/styles.d.ts +0 -21
  210. package/build-types/alignment-matrix-control/styles.d.ts.map +0 -1
  211. package/build-types/angle-picker-control/styles/angle-picker-control-styles.d.ts +0 -18
  212. package/build-types/angle-picker-control/styles/angle-picker-control-styles.d.ts.map +0 -1
  213. package/src/alignment-matrix-control/styles.ts +0 -113
  214. package/src/angle-picker-control/styles/angle-picker-control-styles.tsx +0 -58
@@ -32,32 +32,23 @@ export default meta;
32
32
  export const Default: StoryObj< typeof ValidatedCheckboxControl > = {
33
33
  render: function Template( { onChange, ...args } ) {
34
34
  const [ checked, setChecked ] = useState( false );
35
- const [ customValidity, setCustomValidity ] =
36
- useState<
37
- React.ComponentProps<
38
- typeof ValidatedCheckboxControl
39
- >[ 'customValidity' ]
40
- >( undefined );
41
35
 
42
36
  return (
43
37
  <ValidatedCheckboxControl
44
38
  { ...args }
45
39
  checked={ checked }
46
- onChange={ ( value ) => {
47
- setChecked( value );
48
- onChange?.( value );
40
+ onChange={ ( newValue ) => {
41
+ setChecked( newValue );
42
+ onChange?.( newValue );
49
43
  } }
50
- onValidate={ ( value ) => {
51
- if ( value ) {
52
- setCustomValidity( {
53
- type: 'invalid',
54
- message: 'This checkbox may not be checked.',
55
- } );
56
- } else {
57
- setCustomValidity( undefined );
58
- }
59
- } }
60
- customValidity={ customValidity }
44
+ customValidity={
45
+ checked
46
+ ? {
47
+ type: 'invalid',
48
+ message: 'This checkbox may not be checked.',
49
+ }
50
+ : undefined
51
+ }
61
52
  />
62
53
  );
63
54
  },
@@ -35,12 +35,6 @@ export const Default: StoryObj< typeof ValidatedComboboxControl > = {
35
35
  typeof ValidatedComboboxControl
36
36
  >[ 'value' ]
37
37
  >();
38
- const [ customValidity, setCustomValidity ] =
39
- useState<
40
- React.ComponentProps<
41
- typeof ValidatedComboboxControl
42
- >[ 'customValidity' ]
43
- >( undefined );
44
38
 
45
39
  return (
46
40
  <ValidatedComboboxControl
@@ -50,17 +44,14 @@ export const Default: StoryObj< typeof ValidatedComboboxControl > = {
50
44
  setValue( newValue );
51
45
  onChange?.( newValue );
52
46
  } }
53
- onValidate={ ( v ) => {
54
- if ( v === 'a' ) {
55
- setCustomValidity( {
56
- type: 'invalid',
57
- message: 'Option A is not allowed.',
58
- } );
59
- } else {
60
- setCustomValidity( undefined );
61
- }
62
- } }
63
- customValidity={ customValidity }
47
+ customValidity={
48
+ value === 'a'
49
+ ? {
50
+ type: 'invalid',
51
+ message: 'Option A is not allowed.',
52
+ }
53
+ : undefined
54
+ }
64
55
  />
65
56
  );
66
57
  },
@@ -35,12 +35,6 @@ export const Default: StoryObj< typeof ValidatedCustomSelectControl > = {
35
35
  typeof ValidatedCustomSelectControl
36
36
  >[ 'value' ]
37
37
  >();
38
- const [ customValidity, setCustomValidity ] =
39
- useState<
40
- React.ComponentProps<
41
- typeof ValidatedCustomSelectControl
42
- >[ 'customValidity' ]
43
- >( undefined );
44
38
 
45
39
  return (
46
40
  <ValidatedCustomSelectControl
@@ -50,17 +44,14 @@ export const Default: StoryObj< typeof ValidatedCustomSelectControl > = {
50
44
  setValue( newValue.selectedItem );
51
45
  onChange?.( newValue );
52
46
  } }
53
- onValidate={ ( v ) => {
54
- if ( v?.key === 'a' ) {
55
- setCustomValidity( {
56
- type: 'invalid',
57
- message: 'Option A is not allowed.',
58
- } );
59
- } else {
60
- setCustomValidity( undefined );
61
- }
62
- } }
63
- customValidity={ customValidity }
47
+ customValidity={
48
+ value?.key === 'a'
49
+ ? {
50
+ type: 'invalid',
51
+ message: 'Option A is not allowed.',
52
+ }
53
+ : undefined
54
+ }
64
55
  />
65
56
  );
66
57
  },
@@ -36,39 +36,27 @@ export default meta;
36
36
  export const Default: StoryObj< typeof ValidatedFormTokenField > = {
37
37
  render: function Template( { onChange, ...args } ) {
38
38
  const [ value, setValue ] = useState< ( string | TokenItem )[] >( [] );
39
- const [ customValidity, setCustomValidity ] =
40
- useState<
41
- React.ComponentProps<
42
- typeof ValidatedFormTokenField
43
- >[ 'customValidity' ]
44
- >( undefined );
45
39
 
46
40
  return (
47
41
  <ValidatedFormTokenField
48
42
  { ...args }
49
43
  value={ value }
50
- onChange={ ( newValue, ...rest ) => {
44
+ onChange={ ( newValue ) => {
51
45
  setValue( newValue );
52
- onChange?.( newValue, ...rest );
46
+ onChange?.( newValue );
53
47
  } }
54
- onValidate={ ( v ) => {
55
- if (
56
- v?.some( ( token ) => {
57
- const tokenValue =
58
- typeof token === 'string' ? token : token.value;
59
- return tokenValue.toLowerCase() === 'error';
60
- } )
61
- ) {
62
- setCustomValidity( {
63
- type: 'invalid',
64
- message: 'The tag "error" is not allowed.',
65
- } );
66
- return;
67
- }
68
-
69
- setCustomValidity( undefined );
70
- } }
71
- customValidity={ customValidity }
48
+ customValidity={
49
+ value?.some( ( token ) => {
50
+ const tokenValue =
51
+ typeof token === 'string' ? token : token.value;
52
+ return tokenValue.toLowerCase() === 'error';
53
+ } )
54
+ ? {
55
+ type: 'invalid',
56
+ message: 'The tag "error" is not allowed.',
57
+ }
58
+ : undefined
59
+ }
72
60
  />
73
61
  );
74
62
  },
@@ -42,16 +42,7 @@ export default meta;
42
42
 
43
43
  export const Default: StoryObj< typeof ValidatedInputControl > = {
44
44
  render: function Template( { onChange, ...args } ) {
45
- const [ value, setValue ] =
46
- useState<
47
- React.ComponentProps< typeof ValidatedInputControl >[ 'value' ]
48
- >( '' );
49
- const [ customValidity, setCustomValidity ] =
50
- useState<
51
- React.ComponentProps<
52
- typeof ValidatedInputControl
53
- >[ 'customValidity' ]
54
- >( undefined );
45
+ const [ value, setValue ] = useState< string | undefined >( '' );
55
46
 
56
47
  return (
57
48
  <ValidatedInputControl
@@ -61,17 +52,14 @@ export const Default: StoryObj< typeof ValidatedInputControl > = {
61
52
  setValue( newValue );
62
53
  onChange?.( newValue, ...rest );
63
54
  } }
64
- onValidate={ ( v ) => {
65
- if ( v?.toLowerCase() === 'error' ) {
66
- setCustomValidity( {
67
- type: 'invalid',
68
- message: 'The word "error" is not allowed.',
69
- } );
70
- } else {
71
- setCustomValidity( undefined );
72
- }
73
- } }
74
- customValidity={ customValidity }
55
+ customValidity={
56
+ value?.toLowerCase() === 'error'
57
+ ? {
58
+ type: 'invalid',
59
+ message: 'The word "error" is not allowed.',
60
+ }
61
+ : undefined
62
+ }
75
63
  />
76
64
  );
77
65
  },
@@ -89,17 +77,8 @@ Default.args = {
89
77
  */
90
78
  export const Password: StoryObj< typeof ValidatedInputControl > = {
91
79
  render: function Template( { onChange, ...args } ) {
92
- const [ value, setValue ] =
93
- useState<
94
- React.ComponentProps< typeof ValidatedInputControl >[ 'value' ]
95
- >( '' );
80
+ const [ value, setValue ] = useState< string | undefined >( '' );
96
81
  const [ visible, setVisible ] = useState( false );
97
- const [ customValidity, setCustomValidity ] =
98
- useState<
99
- React.ComponentProps<
100
- typeof ValidatedInputControl
101
- >[ 'customValidity' ]
102
- >( undefined );
103
82
 
104
83
  return (
105
84
  <ValidatedInputControl
@@ -122,34 +101,30 @@ export const Password: StoryObj< typeof ValidatedInputControl > = {
122
101
  setValue( newValue );
123
102
  onChange?.( newValue, ...rest );
124
103
  } }
125
- onValidate={ ( v ) => {
126
- if ( ! /\d/.test( v ?? '' ) ) {
127
- setCustomValidity( {
128
- type: 'invalid',
104
+ customValidity={ ( () => {
105
+ if ( ! /\d/.test( value ?? '' ) ) {
106
+ return {
107
+ type: 'invalid' as const,
129
108
  message:
130
109
  'Password must include at least one number.',
131
- } );
132
- return;
110
+ };
133
111
  }
134
- if ( ! /[A-Z]/.test( v ?? '' ) ) {
135
- setCustomValidity( {
136
- type: 'invalid',
112
+ if ( ! /[A-Z]/.test( value ?? '' ) ) {
113
+ return {
114
+ type: 'invalid' as const,
137
115
  message:
138
116
  'Password must include at least one capital letter.',
139
- } );
140
- return;
117
+ };
141
118
  }
142
- if ( ! /[!@£$%^&*#]/.test( v ?? '' ) ) {
143
- setCustomValidity( {
144
- type: 'invalid',
119
+ if ( ! /[!@£$%^&*#]/.test( value ?? '' ) ) {
120
+ return {
121
+ type: 'invalid' as const,
145
122
  message:
146
123
  'Password must include at least one symbol.',
147
- } );
148
- return;
124
+ };
149
125
  }
150
- setCustomValidity( undefined );
151
- } }
152
- customValidity={ customValidity }
126
+ return undefined;
127
+ } )() }
153
128
  />
154
129
  );
155
130
  },
@@ -37,32 +37,23 @@ export const Default: StoryObj< typeof ValidatedNumberControl > = {
37
37
  useState<
38
38
  React.ComponentProps< typeof ValidatedNumberControl >[ 'value' ]
39
39
  >();
40
- const [ customValidity, setCustomValidity ] =
41
- useState<
42
- React.ComponentProps<
43
- typeof ValidatedNumberControl
44
- >[ 'customValidity' ]
45
- >( undefined );
46
40
 
47
41
  return (
48
42
  <ValidatedNumberControl
49
43
  { ...args }
50
44
  value={ value }
51
- onChange={ ( newValue, ...rest ) => {
45
+ onChange={ ( newValue, extra ) => {
52
46
  setValue( newValue );
53
- onChange?.( newValue, ...rest );
54
- } }
55
- onValidate={ ( v ) => {
56
- if ( v && parseInt( v.toString(), 10 ) % 2 !== 0 ) {
57
- setCustomValidity( {
58
- type: 'invalid',
59
- message: 'Choose an even number.',
60
- } );
61
- } else {
62
- setCustomValidity( undefined );
63
- }
47
+ onChange?.( newValue, extra );
64
48
  } }
65
- customValidity={ customValidity }
49
+ customValidity={
50
+ value && parseInt( value.toString(), 10 ) % 2 !== 0
51
+ ? {
52
+ type: 'invalid',
53
+ message: 'Choose an even number.',
54
+ }
55
+ : undefined
56
+ }
66
57
  />
67
58
  );
68
59
  },
@@ -16,7 +16,7 @@ We are still gathering feedback and iterating. Please get in touch with `@WordPr
16
16
 
17
17
  Component APIs are the same as the underlying WordPress components, with the addition of some optional props:
18
18
 
19
- <ArgTypes of={ ValidatedInputControl } include={ [ 'required', 'markWhenOptional', 'onValidate', 'customValidity' ] } />
19
+ <ArgTypes of={ ValidatedInputControl } include={ [ 'required', 'markWhenOptional', 'customValidity' ] } />
20
20
 
21
21
  ## Implementation
22
22
 
@@ -32,14 +32,14 @@ These components are designed to work with the native HTML5 [Constraint Validati
32
32
 
33
33
  ### No easy access to underlying validation APIs
34
34
 
35
- - The consumer does not have a simple way to trigger their custom validators at an arbitrary point in time. Currently, the validation timings for custom validators are handled by the component — first on `blur`, and then on every `change` after the field is considered "touched". Note that this limitation only applies to **custom** validators, not the standard attribute-based validators like `required` or `pattern`, which can be triggered at any time using the `reportValidity()` method on either the underlying control element or the wrapping `<form>` element.
35
+ - The consumer does not have a simple way to show validation messages at an arbitrary point in time, unless the fields are wrapped in a `form` element. Currently, when to show validation messages to the user are handled by the component — any messages are first made visible on `blur` after the field is considered "touched", or if the user tries to submit the enclosing `form`. Messages can also be triggered to show at any time by calling the `reportValidity()` method on the wrapping `<form>` element.
36
36
  - The consumer does not have direct access to the [`validity` object](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState) of the underlying element.
37
37
 
38
38
  We don't foresee these being needed much, but it is technically possible by the consumer accessing the target element's validation APIs via `ref`. Better docs or ergonomics can be added when we have actual use cases.
39
39
 
40
40
  ### Delegate elements
41
41
 
42
- 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.
42
+ The implementations for `ToggleGroupControl`, `CustomSelectControl`, and `FormTokenField` 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.
43
43
 
44
44
  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.
45
45
 
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import type { Meta, StoryObj } from '@storybook/react';
5
5
  import { expect, userEvent, waitFor, within } from '@storybook/test';
6
+ import clsx from 'clsx';
6
7
 
7
8
  /**
8
9
  * WordPress dependencies
@@ -32,20 +33,8 @@ type Story = StoryObj< typeof ControlWithError >;
32
33
  */
33
34
  export const WithMultipleControls: Story = {
34
35
  render: function Template() {
35
- const [ text, setText ] = useState( '' );
36
- const [ text2, setText2 ] = useState( '' );
37
- const [ customValidity, setCustomValidity ] =
38
- useState<
39
- React.ComponentProps<
40
- typeof ValidatedInputControl
41
- >[ 'customValidity' ]
42
- >( undefined );
43
- const [ customValidity2, setCustomValidity2 ] =
44
- useState<
45
- React.ComponentProps<
46
- typeof ValidatedInputControl
47
- >[ 'customValidity' ]
48
- >( undefined );
36
+ const [ text, setText ] = useState< string | undefined >( '' );
37
+ const [ text2, setText2 ] = useState< string | undefined >( '' );
49
38
 
50
39
  return (
51
40
  <>
@@ -54,36 +43,30 @@ export const WithMultipleControls: Story = {
54
43
  required
55
44
  value={ text }
56
45
  help="The word 'error' will trigger an error."
57
- onValidate={ ( value ) => {
58
- if ( value?.toLowerCase() === 'error' ) {
59
- setCustomValidity( {
60
- type: 'invalid',
61
- message: 'The word "error" is not allowed.',
62
- } );
63
- } else {
64
- setCustomValidity( undefined );
65
- }
66
- } }
67
- customValidity={ customValidity }
68
- onChange={ ( value ) => setText( value ?? '' ) }
46
+ onChange={ setText }
47
+ customValidity={
48
+ text?.toLowerCase() === 'error'
49
+ ? {
50
+ type: 'invalid',
51
+ message: 'The word "error" is not allowed.',
52
+ }
53
+ : undefined
54
+ }
69
55
  />
70
56
  <ValidatedInputControl
71
57
  label="Text"
72
58
  required
73
59
  value={ text2 }
74
60
  help="The word 'error' will trigger an error."
75
- onValidate={ ( value ) => {
76
- if ( value?.toLowerCase() === 'error' ) {
77
- setCustomValidity2( {
78
- type: 'invalid',
79
- message: 'The word "error" is not allowed.',
80
- } );
81
- } else {
82
- setCustomValidity2( undefined );
83
- }
84
- } }
85
- onChange={ ( value ) => setText2( value ?? '' ) }
86
- customValidity={ customValidity2 }
61
+ onChange={ setText2 }
62
+ customValidity={
63
+ text2?.toLowerCase() === 'error'
64
+ ? {
65
+ type: 'invalid',
66
+ message: 'The word "error" is not allowed.',
67
+ }
68
+ : undefined
69
+ }
87
70
  />
88
71
  </>
89
72
  );
@@ -96,37 +79,44 @@ export const WithMultipleControls: Story = {
96
79
  */
97
80
  export const WithHelpTextReplacement: Story = {
98
81
  render: function Template() {
99
- const [ text, setText ] = useState( '' );
100
- const [ customValidity, setCustomValidity ] =
101
- useState<
102
- React.ComponentProps<
103
- typeof ValidatedInputControl
104
- >[ 'customValidity' ]
105
- >( undefined );
82
+ const [ text, setText ] = useState< string | undefined >( '' );
83
+ const isInvalid = text?.toLowerCase() === 'error';
106
84
 
107
85
  return (
108
- <ValidatedInputControl
109
- label="Text"
110
- required
111
- value={ text }
112
- help={
113
- customValidity
114
- ? undefined
115
- : 'The word "error" is not allowed.'
86
+ <>
87
+ <style>
88
+ { `
89
+ .my-control:has(:invalid[data-validity-visible]) .my-control__help:not(.is-visible) {
90
+ display: none;
116
91
  }
117
- onValidate={ ( value ) => {
118
- if ( value?.toLowerCase() === 'error' ) {
119
- setCustomValidity( {
120
- type: 'invalid',
121
- message: 'The word "error" is not allowed.',
122
- } );
123
- } else {
124
- setCustomValidity( undefined );
92
+ ` }
93
+ </style>
94
+ <ValidatedInputControl
95
+ className="my-control"
96
+ label="Text"
97
+ required
98
+ value={ text }
99
+ help={
100
+ <span
101
+ className={ clsx(
102
+ 'my-control__help',
103
+ ! isInvalid && 'is-visible'
104
+ ) }
105
+ >
106
+ The word &quot;error&quot; is not allowed.
107
+ </span>
125
108
  }
126
- } }
127
- onChange={ ( value ) => setText( value ?? '' ) }
128
- customValidity={ customValidity }
129
- />
109
+ onChange={ setText }
110
+ customValidity={
111
+ isInvalid
112
+ ? {
113
+ type: 'invalid',
114
+ message: 'The word "error" is not allowed.',
115
+ }
116
+ : undefined
117
+ }
118
+ />
119
+ </>
130
120
  );
131
121
  },
132
122
  };
@@ -150,23 +140,19 @@ export const AsyncValidation: StoryObj< typeof ValidatedInputControl > = {
150
140
  >( undefined );
151
141
 
152
142
  const timeoutRef = useRef< ReturnType< typeof setTimeout > >();
153
- const previousValidationValueRef = useRef< unknown >( '' );
154
143
 
155
144
  // eslint-disable-next-line react-hooks/exhaustive-deps
156
145
  const debouncedValidate = useCallback(
157
146
  debounce( ( v ) => {
158
- if ( v === previousValidationValueRef.current ) {
147
+ if ( v === '' ) {
159
148
  return;
160
149
  }
161
150
 
162
- previousValidationValueRef.current = v;
163
-
164
151
  setCustomValidity( {
165
152
  type: 'validating',
166
153
  message: 'Validating...',
167
154
  } );
168
155
 
169
- clearTimeout( timeoutRef.current );
170
156
  timeoutRef.current = setTimeout( () => {
171
157
  if ( v?.toString().toLowerCase() === 'error' ) {
172
158
  setCustomValidity( {
@@ -190,8 +176,10 @@ export const AsyncValidation: StoryObj< typeof ValidatedInputControl > = {
190
176
  value={ text }
191
177
  onChange={ ( newValue ) => {
192
178
  setText( newValue ?? '' );
179
+ setCustomValidity( undefined );
180
+ clearTimeout( timeoutRef.current );
181
+ debouncedValidate( newValue );
193
182
  } }
194
- onValidate={ debouncedValidate }
195
183
  customValidity={ customValidity }
196
184
  />
197
185
  );
@@ -225,14 +213,6 @@ const AsyncValidationWithTest: StoryObj< typeof ValidatedInputControl > = {
225
213
  await new Promise( ( resolve ) => setTimeout( resolve, 500 ) );
226
214
  await userEvent.clear( canvas.getByRole( 'textbox' ) );
227
215
 
228
- // Should show validating state when transitioning from valid to invalid.
229
- await waitFor(
230
- () => {
231
- expect( canvas.getByText( 'Validating...' ) ).toBeVisible();
232
- },
233
- { timeout: 2500 }
234
- );
235
-
236
216
  await waitFor(
237
217
  () => {
238
218
  expect(
@@ -288,3 +268,38 @@ const AsyncValidationWithTest: StoryObj< typeof ValidatedInputControl > = {
288
268
  );
289
269
  },
290
270
  };
271
+
272
+ /**
273
+ * Custom validity errors are effective immediately, even when they are not yet visible
274
+ * to the user. For example, in this form where the initial value is already invalid,
275
+ * the error message will be shown to the user once the submit button is clicked,
276
+ * even if the input has never been interacted with.
277
+ */
278
+ export const CustomErrorsOnSubmit: StoryObj< typeof ValidatedInputControl > = {
279
+ args: {
280
+ label: 'Text',
281
+ required: true,
282
+ help: 'The word "error" will trigger an error.',
283
+ },
284
+ render: function Template( { ...args } ) {
285
+ const [ text, setText ] = useState< string | undefined >( 'error' );
286
+
287
+ return (
288
+ <>
289
+ <ValidatedInputControl
290
+ { ...args }
291
+ value={ text }
292
+ onChange={ setText }
293
+ customValidity={
294
+ text === 'error'
295
+ ? {
296
+ type: 'invalid',
297
+ message: 'The word "error" is not allowed.',
298
+ }
299
+ : undefined
300
+ }
301
+ />
302
+ </>
303
+ );
304
+ },
305
+ };
@@ -35,32 +35,23 @@ export const Default: StoryObj< typeof ValidatedRadioControl > = {
35
35
  typeof ValidatedRadioControl
36
36
  >[ 'selected' ]
37
37
  >();
38
- const [ customValidity, setCustomValidity ] =
39
- useState<
40
- React.ComponentProps<
41
- typeof ValidatedRadioControl
42
- >[ 'customValidity' ]
43
- >( undefined );
44
38
 
45
39
  return (
46
40
  <ValidatedRadioControl
47
41
  { ...args }
48
42
  selected={ selected }
49
- onChange={ ( value ) => {
50
- setSelected( value );
51
- onChange?.( value );
52
- } }
53
- onValidate={ ( v ) => {
54
- if ( v === 'b' ) {
55
- setCustomValidity( {
56
- type: 'invalid',
57
- message: 'Option B is not allowed.',
58
- } );
59
- } else {
60
- setCustomValidity( undefined );
61
- }
43
+ onChange={ ( newValue ) => {
44
+ setSelected( newValue );
45
+ onChange?.( newValue );
62
46
  } }
63
- customValidity={ customValidity }
47
+ customValidity={
48
+ selected === 'b'
49
+ ? {
50
+ type: 'invalid',
51
+ message: 'Option B is not allowed.',
52
+ }
53
+ : undefined
54
+ }
64
55
  />
65
56
  );
66
57
  },