@wordpress/components 30.9.0 → 31.0.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 (233) hide show
  1. package/CHANGELOG.md +26 -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/form-token-field/index.js +1 -13
  13. package/build/form-token-field/index.js.map +3 -3
  14. package/build/menu/styles.js +17 -17
  15. package/build/menu/styles.js.map +2 -2
  16. package/build/menu-item/index.js +1 -1
  17. package/build/menu-item/index.js.map +2 -2
  18. package/build/notice/index.js +1 -1
  19. package/build/notice/index.js.map +2 -2
  20. package/build/query-controls/index.js +0 -1
  21. package/build/query-controls/index.js.map +2 -2
  22. package/build/snackbar/index.js +1 -1
  23. package/build/snackbar/index.js.map +1 -1
  24. package/build/validated-form-controls/components/checkbox-control.js +0 -10
  25. package/build/validated-form-controls/components/checkbox-control.js.map +2 -2
  26. package/build/validated-form-controls/components/combobox-control.js +1 -11
  27. package/build/validated-form-controls/components/combobox-control.js.map +2 -2
  28. package/build/validated-form-controls/components/custom-select-control.js +0 -10
  29. package/build/validated-form-controls/components/custom-select-control.js.map +2 -2
  30. package/build/validated-form-controls/components/form-token-field.js +2 -13
  31. package/build/validated-form-controls/components/form-token-field.js.map +2 -2
  32. package/build/validated-form-controls/components/input-control.js +0 -10
  33. package/build/validated-form-controls/components/input-control.js.map +2 -2
  34. package/build/validated-form-controls/components/number-control.js +0 -10
  35. package/build/validated-form-controls/components/number-control.js.map +2 -2
  36. package/build/validated-form-controls/components/radio-control.js +0 -10
  37. package/build/validated-form-controls/components/radio-control.js.map +2 -2
  38. package/build/validated-form-controls/components/range-control.js +0 -10
  39. package/build/validated-form-controls/components/range-control.js.map +2 -2
  40. package/build/validated-form-controls/components/select-control.js +0 -10
  41. package/build/validated-form-controls/components/select-control.js.map +2 -2
  42. package/build/validated-form-controls/components/text-control.js +0 -10
  43. package/build/validated-form-controls/components/text-control.js.map +2 -2
  44. package/build/validated-form-controls/components/textarea-control.js +0 -10
  45. package/build/validated-form-controls/components/textarea-control.js.map +2 -2
  46. package/build/validated-form-controls/components/toggle-control.js +0 -10
  47. package/build/validated-form-controls/components/toggle-control.js.map +2 -2
  48. package/build/validated-form-controls/components/toggle-group-control.js +0 -10
  49. package/build/validated-form-controls/components/toggle-group-control.js.map +2 -2
  50. package/build/validated-form-controls/control-with-error.js +53 -58
  51. package/build/validated-form-controls/control-with-error.js.map +2 -2
  52. package/build-module/alignment-matrix-control/cell.js +131 -3
  53. package/build-module/alignment-matrix-control/cell.js.map +3 -3
  54. package/build-module/alignment-matrix-control/index.js +134 -6
  55. package/build-module/alignment-matrix-control/index.js.map +3 -3
  56. package/build-module/angle-picker-control/angle-circle.js +109 -15
  57. package/build-module/angle-picker-control/angle-circle.js.map +3 -3
  58. package/build-module/angle-picker-control/index.js +12 -7
  59. package/build-module/angle-picker-control/index.js.map +2 -2
  60. package/build-module/dropdown-menu/index.js +1 -1
  61. package/build-module/dropdown-menu/index.js.map +2 -2
  62. package/build-module/form-token-field/index.js +1 -13
  63. package/build-module/form-token-field/index.js.map +2 -2
  64. package/build-module/menu/styles.js +17 -17
  65. package/build-module/menu/styles.js.map +2 -2
  66. package/build-module/menu-item/index.js +1 -1
  67. package/build-module/menu-item/index.js.map +2 -2
  68. package/build-module/notice/index.js +1 -1
  69. package/build-module/notice/index.js.map +2 -2
  70. package/build-module/query-controls/index.js +0 -1
  71. package/build-module/query-controls/index.js.map +2 -2
  72. package/build-module/snackbar/index.js +1 -1
  73. package/build-module/snackbar/index.js.map +1 -1
  74. package/build-module/validated-form-controls/components/checkbox-control.js +0 -10
  75. package/build-module/validated-form-controls/components/checkbox-control.js.map +2 -2
  76. package/build-module/validated-form-controls/components/combobox-control.js +1 -11
  77. package/build-module/validated-form-controls/components/combobox-control.js.map +2 -2
  78. package/build-module/validated-form-controls/components/custom-select-control.js +0 -10
  79. package/build-module/validated-form-controls/components/custom-select-control.js.map +2 -2
  80. package/build-module/validated-form-controls/components/form-token-field.js +2 -13
  81. package/build-module/validated-form-controls/components/form-token-field.js.map +2 -2
  82. package/build-module/validated-form-controls/components/input-control.js +0 -10
  83. package/build-module/validated-form-controls/components/input-control.js.map +2 -2
  84. package/build-module/validated-form-controls/components/number-control.js +0 -10
  85. package/build-module/validated-form-controls/components/number-control.js.map +2 -2
  86. package/build-module/validated-form-controls/components/radio-control.js +0 -10
  87. package/build-module/validated-form-controls/components/radio-control.js.map +2 -2
  88. package/build-module/validated-form-controls/components/range-control.js +0 -10
  89. package/build-module/validated-form-controls/components/range-control.js.map +2 -2
  90. package/build-module/validated-form-controls/components/select-control.js +0 -10
  91. package/build-module/validated-form-controls/components/select-control.js.map +2 -2
  92. package/build-module/validated-form-controls/components/text-control.js +0 -10
  93. package/build-module/validated-form-controls/components/text-control.js.map +2 -2
  94. package/build-module/validated-form-controls/components/textarea-control.js +0 -10
  95. package/build-module/validated-form-controls/components/textarea-control.js.map +2 -2
  96. package/build-module/validated-form-controls/components/toggle-control.js +0 -10
  97. package/build-module/validated-form-controls/components/toggle-control.js.map +2 -2
  98. package/build-module/validated-form-controls/components/toggle-group-control.js +0 -10
  99. package/build-module/validated-form-controls/components/toggle-group-control.js.map +2 -2
  100. package/build-module/validated-form-controls/control-with-error.js +53 -58
  101. package/build-module/validated-form-controls/control-with-error.js.map +2 -2
  102. package/build-style/style-rtl.css +21 -33
  103. package/build-style/style.css +21 -33
  104. package/build-types/alignment-matrix-control/cell.d.ts.map +1 -1
  105. package/build-types/alignment-matrix-control/index.d.ts.map +1 -1
  106. package/build-types/angle-picker-control/angle-circle.d.ts +1 -1
  107. package/build-types/angle-picker-control/angle-circle.d.ts.map +1 -1
  108. package/build-types/angle-picker-control/index.d.ts.map +1 -1
  109. package/build-types/form-token-field/index.d.ts.map +1 -1
  110. package/build-types/form-token-field/stories/index.story.d.ts.map +1 -1
  111. package/build-types/form-token-field/types.d.ts +0 -6
  112. package/build-types/form-token-field/types.d.ts.map +1 -1
  113. package/build-types/notice/index.d.ts.map +1 -1
  114. package/build-types/notice/stories/index.story.d.ts.map +1 -1
  115. package/build-types/query-controls/index.d.ts.map +1 -1
  116. package/build-types/validated-form-controls/components/checkbox-control.d.ts +1 -1
  117. package/build-types/validated-form-controls/components/checkbox-control.d.ts.map +1 -1
  118. package/build-types/validated-form-controls/components/combobox-control.d.ts +2 -3
  119. package/build-types/validated-form-controls/components/combobox-control.d.ts.map +1 -1
  120. package/build-types/validated-form-controls/components/custom-select-control.d.ts +1 -2
  121. package/build-types/validated-form-controls/components/custom-select-control.d.ts.map +1 -1
  122. package/build-types/validated-form-controls/components/form-token-field.d.ts +1 -2
  123. package/build-types/validated-form-controls/components/form-token-field.d.ts.map +1 -1
  124. package/build-types/validated-form-controls/components/input-control.d.ts +1 -2
  125. package/build-types/validated-form-controls/components/input-control.d.ts.map +1 -1
  126. package/build-types/validated-form-controls/components/number-control.d.ts +1 -1
  127. package/build-types/validated-form-controls/components/number-control.d.ts.map +1 -1
  128. package/build-types/validated-form-controls/components/radio-control.d.ts +1 -1
  129. package/build-types/validated-form-controls/components/radio-control.d.ts.map +1 -1
  130. package/build-types/validated-form-controls/components/range-control.d.ts +1 -1
  131. package/build-types/validated-form-controls/components/range-control.d.ts.map +1 -1
  132. package/build-types/validated-form-controls/components/select-control.d.ts +2 -3
  133. package/build-types/validated-form-controls/components/select-control.d.ts.map +1 -1
  134. package/build-types/validated-form-controls/components/stories/checkbox-control.story.d.ts.map +1 -1
  135. package/build-types/validated-form-controls/components/stories/combobox-control.story.d.ts.map +1 -1
  136. package/build-types/validated-form-controls/components/stories/custom-select-control.story.d.ts.map +1 -1
  137. package/build-types/validated-form-controls/components/stories/form-token-field.story.d.ts.map +1 -1
  138. package/build-types/validated-form-controls/components/stories/input-control.story.d.ts.map +1 -1
  139. package/build-types/validated-form-controls/components/stories/number-control.story.d.ts.map +1 -1
  140. package/build-types/validated-form-controls/components/stories/overview.story.d.ts +7 -0
  141. package/build-types/validated-form-controls/components/stories/overview.story.d.ts.map +1 -1
  142. package/build-types/validated-form-controls/components/stories/radio-control.story.d.ts.map +1 -1
  143. package/build-types/validated-form-controls/components/stories/range-control.story.d.ts.map +1 -1
  144. package/build-types/validated-form-controls/components/stories/select-control.story.d.ts.map +1 -1
  145. package/build-types/validated-form-controls/components/stories/text-control.story.d.ts.map +1 -1
  146. package/build-types/validated-form-controls/components/stories/textarea-control.story.d.ts.map +1 -1
  147. package/build-types/validated-form-controls/components/stories/toggle-control.story.d.ts.map +1 -1
  148. package/build-types/validated-form-controls/components/stories/toggle-group-control.story.d.ts.map +1 -1
  149. package/build-types/validated-form-controls/components/text-control.d.ts +1 -1
  150. package/build-types/validated-form-controls/components/text-control.d.ts.map +1 -1
  151. package/build-types/validated-form-controls/components/textarea-control.d.ts +1 -1
  152. package/build-types/validated-form-controls/components/textarea-control.d.ts.map +1 -1
  153. package/build-types/validated-form-controls/components/toggle-control.d.ts +1 -1
  154. package/build-types/validated-form-controls/components/toggle-control.d.ts.map +1 -1
  155. package/build-types/validated-form-controls/components/toggle-group-control.d.ts +1 -1
  156. package/build-types/validated-form-controls/components/toggle-group-control.d.ts.map +1 -1
  157. package/build-types/validated-form-controls/components/types.d.ts +1 -9
  158. package/build-types/validated-form-controls/components/types.d.ts.map +1 -1
  159. package/build-types/validated-form-controls/control-with-error.d.ts +4 -5
  160. package/build-types/validated-form-controls/control-with-error.d.ts.map +1 -1
  161. package/package.json +20 -20
  162. package/src/alignment-matrix-control/cell.tsx +14 -3
  163. package/src/alignment-matrix-control/index.tsx +15 -6
  164. package/src/alignment-matrix-control/style.module.scss +84 -0
  165. package/src/angle-picker-control/angle-circle.tsx +27 -12
  166. package/src/angle-picker-control/index.tsx +8 -7
  167. package/src/angle-picker-control/style.module.scss +40 -0
  168. package/src/button/style.scss +1 -1
  169. package/src/dropdown-menu/index.tsx +1 -1
  170. package/src/dropdown-menu/style.scss +1 -1
  171. package/src/form-token-field/README.md +0 -2
  172. package/src/form-token-field/index.tsx +1 -13
  173. package/src/form-token-field/stories/index.story.tsx +0 -2
  174. package/src/form-token-field/test/index.tsx +0 -1
  175. package/src/form-token-field/types.ts +0 -6
  176. package/src/guide/style.scss +3 -3
  177. package/src/menu/styles.ts +2 -2
  178. package/src/menu-item/index.tsx +1 -1
  179. package/src/menu-item/test/__snapshots__/index.js.snap +4 -4
  180. package/src/modal/style.scss +5 -5
  181. package/src/notice/index.tsx +53 -46
  182. package/src/notice/stories/index.story.tsx +17 -1
  183. package/src/notice/style.scss +3 -20
  184. package/src/query-controls/index.tsx +0 -1
  185. package/src/snackbar/index.tsx +1 -1
  186. package/src/validated-form-controls/components/checkbox-control.tsx +1 -14
  187. package/src/validated-form-controls/components/combobox-control.tsx +1 -14
  188. package/src/validated-form-controls/components/custom-select-control.tsx +1 -19
  189. package/src/validated-form-controls/components/form-token-field.tsx +4 -21
  190. package/src/validated-form-controls/components/input-control.tsx +1 -14
  191. package/src/validated-form-controls/components/number-control.tsx +1 -16
  192. package/src/validated-form-controls/components/radio-control.tsx +2 -18
  193. package/src/validated-form-controls/components/range-control.tsx +1 -14
  194. package/src/validated-form-controls/components/select-control.tsx +1 -23
  195. package/src/validated-form-controls/components/stories/checkbox-control.story.tsx +11 -20
  196. package/src/validated-form-controls/components/stories/combobox-control.story.tsx +8 -17
  197. package/src/validated-form-controls/components/stories/custom-select-control.story.tsx +8 -17
  198. package/src/validated-form-controls/components/stories/form-token-field.story.tsx +14 -26
  199. package/src/validated-form-controls/components/stories/input-control.story.tsx +25 -50
  200. package/src/validated-form-controls/components/stories/number-control.story.tsx +10 -19
  201. package/src/validated-form-controls/components/stories/overview.mdx +3 -3
  202. package/src/validated-form-controls/components/stories/overview.story.tsx +94 -79
  203. package/src/validated-form-controls/components/stories/radio-control.story.tsx +11 -20
  204. package/src/validated-form-controls/components/stories/range-control.story.tsx +8 -17
  205. package/src/validated-form-controls/components/stories/select-control.story.tsx +9 -18
  206. package/src/validated-form-controls/components/stories/text-control.story.tsx +11 -17
  207. package/src/validated-form-controls/components/stories/textarea-control.story.tsx +12 -16
  208. package/src/validated-form-controls/components/stories/toggle-control.story.tsx +11 -20
  209. package/src/validated-form-controls/components/stories/toggle-group-control.story.tsx +8 -17
  210. package/src/validated-form-controls/components/text-control.tsx +1 -14
  211. package/src/validated-form-controls/components/textarea-control.tsx +1 -14
  212. package/src/validated-form-controls/components/toggle-control.tsx +1 -14
  213. package/src/validated-form-controls/components/toggle-group-control.tsx +1 -14
  214. package/src/validated-form-controls/components/types.ts +1 -9
  215. package/src/validated-form-controls/control-with-error.tsx +57 -84
  216. package/src/validated-form-controls/style.scss +7 -7
  217. package/src/validated-form-controls/test/control-with-error.tsx +66 -5
  218. package/tsconfig.json +1 -0
  219. package/tsconfig.tsbuildinfo +1 -1
  220. package/build/alignment-matrix-control/styles.js +0 -105
  221. package/build/alignment-matrix-control/styles.js.map +0 -7
  222. package/build/angle-picker-control/styles/angle-picker-control-styles.js +0 -88
  223. package/build/angle-picker-control/styles/angle-picker-control-styles.js.map +0 -7
  224. package/build-module/alignment-matrix-control/styles.js +0 -67
  225. package/build-module/alignment-matrix-control/styles.js.map +0 -7
  226. package/build-module/angle-picker-control/styles/angle-picker-control-styles.js +0 -50
  227. package/build-module/angle-picker-control/styles/angle-picker-control-styles.js.map +0 -7
  228. package/build-types/alignment-matrix-control/styles.d.ts +0 -21
  229. package/build-types/alignment-matrix-control/styles.d.ts.map +0 -1
  230. package/build-types/angle-picker-control/styles/angle-picker-control-styles.d.ts +0 -18
  231. package/build-types/angle-picker-control/styles/angle-picker-control-styles.d.ts.map +0 -1
  232. package/src/alignment-matrix-control/styles.ts +0 -113
  233. package/src/angle-picker-control/styles/angle-picker-control-styles.tsx +0 -58
@@ -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
  },
@@ -33,12 +33,6 @@ export const Default: StoryObj< typeof ValidatedRangeControl > = {
33
33
  useState<
34
34
  React.ComponentProps< typeof ValidatedRangeControl >[ 'value' ]
35
35
  >();
36
- const [ customValidity, setCustomValidity ] =
37
- useState<
38
- React.ComponentProps<
39
- typeof ValidatedRangeControl
40
- >[ 'customValidity' ]
41
- >( undefined );
42
36
 
43
37
  return (
44
38
  <ValidatedRangeControl
@@ -48,17 +42,14 @@ export const Default: StoryObj< typeof ValidatedRangeControl > = {
48
42
  setValue( newValue );
49
43
  onChange?.( newValue );
50
44
  } }
51
- onValidate={ ( v ) => {
52
- if ( v && v % 2 !== 0 ) {
53
- setCustomValidity( {
54
- type: 'invalid',
55
- message: 'Choose an even number.',
56
- } );
57
- } else {
58
- setCustomValidity( undefined );
59
- }
60
- } }
61
- customValidity={ customValidity }
45
+ customValidity={
46
+ value && value % 2 !== 0
47
+ ? {
48
+ type: 'invalid',
49
+ message: 'Choose an even number.',
50
+ }
51
+ : undefined
52
+ }
62
53
  />
63
54
  );
64
55
  },
@@ -29,13 +29,7 @@ export default meta;
29
29
 
30
30
  export const Default: StoryObj< typeof ValidatedSelectControl > = {
31
31
  render: function Template( { onChange, ...args } ) {
32
- const [ value, setValue ] = useState( '' );
33
- const [ customValidity, setCustomValidity ] =
34
- useState<
35
- React.ComponentProps<
36
- typeof ValidatedSelectControl
37
- >[ 'customValidity' ]
38
- >( undefined );
32
+ const [ value, setValue ] = useState< string | undefined >( '' );
39
33
 
40
34
  return (
41
35
  <ValidatedSelectControl
@@ -45,17 +39,14 @@ export const Default: StoryObj< typeof ValidatedSelectControl > = {
45
39
  setValue( newValue );
46
40
  onChange?.( newValue );
47
41
  } }
48
- onValidate={ ( v ) => {
49
- if ( v === '1' ) {
50
- setCustomValidity( {
51
- type: 'invalid',
52
- message: 'Option 1 is not allowed.',
53
- } );
54
- } else {
55
- setCustomValidity( undefined );
56
- }
57
- } }
58
- customValidity={ customValidity }
42
+ customValidity={
43
+ value === '1'
44
+ ? {
45
+ type: 'invalid',
46
+ message: 'Option 1 is not allowed.',
47
+ }
48
+ : undefined
49
+ }
59
50
  />
60
51
  );
61
52
  },
@@ -29,13 +29,10 @@ export default meta;
29
29
 
30
30
  export const Default: StoryObj< typeof ValidatedTextControl > = {
31
31
  render: function Template( { onChange, ...args } ) {
32
- const [ value, setValue ] = useState( '' );
33
- const [ customValidity, setCustomValidity ] =
32
+ const [ value, setValue ] =
34
33
  useState<
35
- React.ComponentProps<
36
- typeof ValidatedTextControl
37
- >[ 'customValidity' ]
38
- >( undefined );
34
+ React.ComponentProps< typeof ValidatedTextControl >[ 'value' ]
35
+ >( '' );
39
36
 
40
37
  return (
41
38
  <ValidatedTextControl
@@ -45,17 +42,14 @@ export const Default: StoryObj< typeof ValidatedTextControl > = {
45
42
  setValue( newValue );
46
43
  onChange?.( newValue );
47
44
  } }
48
- onValidate={ ( v ) => {
49
- if ( v?.toString().toLowerCase() === 'error' ) {
50
- setCustomValidity( {
51
- type: 'invalid',
52
- message: 'The word "error" is not allowed.',
53
- } );
54
- } else {
55
- setCustomValidity( undefined );
56
- }
57
- } }
58
- customValidity={ customValidity }
45
+ customValidity={
46
+ value === 'error'
47
+ ? {
48
+ type: 'invalid',
49
+ message: 'The word "error" is not allowed.',
50
+ }
51
+ : undefined
52
+ }
59
53
  />
60
54
  );
61
55
  },
@@ -26,33 +26,29 @@ export default meta;
26
26
 
27
27
  export const Default: StoryObj< typeof ValidatedTextareaControl > = {
28
28
  render: function Template( { onChange, ...args } ) {
29
- const [ value, setValue ] = useState( '' );
30
- const [ customValidity, setCustomValidity ] =
29
+ const [ value, setValue ] =
31
30
  useState<
32
31
  React.ComponentProps<
33
32
  typeof ValidatedTextareaControl
34
- >[ 'customValidity' ]
35
- >( undefined );
33
+ >[ 'value' ]
34
+ >( '' );
36
35
 
37
36
  return (
38
37
  <ValidatedTextareaControl
39
38
  { ...args }
39
+ value={ value }
40
40
  onChange={ ( newValue ) => {
41
41
  setValue( newValue );
42
42
  onChange?.( newValue );
43
43
  } }
44
- value={ value }
45
- onValidate={ ( v ) => {
46
- if ( v?.toLowerCase() === 'error' ) {
47
- setCustomValidity( {
48
- type: 'invalid',
49
- message: 'The word "error" is not allowed.',
50
- } );
51
- } else {
52
- setCustomValidity( undefined );
53
- }
54
- } }
55
- customValidity={ customValidity }
44
+ customValidity={
45
+ value?.toLowerCase() === 'error'
46
+ ? {
47
+ type: 'invalid',
48
+ message: 'The word "error" is not allowed.',
49
+ }
50
+ : undefined
51
+ }
56
52
  />
57
53
  );
58
54
  },
@@ -30,32 +30,23 @@ export default meta;
30
30
  export const Default: StoryObj< typeof ValidatedToggleControl > = {
31
31
  render: function Template( { onChange, ...args } ) {
32
32
  const [ checked, setChecked ] = useState( false );
33
- const [ customValidity, setCustomValidity ] =
34
- useState<
35
- React.ComponentProps<
36
- typeof ValidatedToggleControl
37
- >[ 'customValidity' ]
38
- >( undefined );
39
33
 
40
34
  return (
41
35
  <ValidatedToggleControl
42
36
  { ...args }
43
37
  checked={ checked }
44
- onChange={ ( value ) => {
45
- setChecked( value );
46
- onChange?.( value );
38
+ onChange={ ( newValue ) => {
39
+ setChecked( newValue );
40
+ onChange?.( newValue );
47
41
  } }
48
- onValidate={ ( v ) => {
49
- if ( v ) {
50
- setCustomValidity( {
51
- type: 'invalid',
52
- message: 'This toggle may not be enabled.',
53
- } );
54
- } else {
55
- setCustomValidity( undefined );
56
- }
57
- } }
58
- customValidity={ customValidity }
42
+ customValidity={
43
+ checked
44
+ ? {
45
+ type: 'invalid',
46
+ message: 'This toggle may not be enabled.',
47
+ }
48
+ : undefined
49
+ }
59
50
  />
60
51
  );
61
52
  },
@@ -36,12 +36,6 @@ export const Default: StoryObj< typeof ValidatedToggleGroupControl > = {
36
36
  typeof ValidatedToggleGroupControl
37
37
  >[ 'value' ]
38
38
  >( '1' );
39
- const [ customValidity, setCustomValidity ] =
40
- useState<
41
- React.ComponentProps<
42
- typeof ValidatedToggleGroupControl
43
- >[ 'customValidity' ]
44
- >( undefined );
45
39
 
46
40
  return (
47
41
  <ValidatedToggleGroupControl
@@ -51,17 +45,14 @@ export const Default: StoryObj< typeof ValidatedToggleGroupControl > = {
51
45
  setValue( newValue );
52
46
  onChange?.( newValue );
53
47
  } }
54
- onValidate={ ( v ) => {
55
- if ( v === '2' ) {
56
- setCustomValidity( {
57
- type: 'invalid',
58
- message: 'Option 2 is not allowed.',
59
- } );
60
- } else {
61
- setCustomValidity( undefined );
62
- }
63
- } }
64
- customValidity={ customValidity }
48
+ customValidity={
49
+ value === '2'
50
+ ? {
51
+ type: 'invalid',
52
+ message: 'Option 2 is not allowed.',
53
+ }
54
+ : undefined
55
+ }
65
56
  />
66
57
  );
67
58
  },
@@ -10,36 +10,27 @@ import { forwardRef, useRef } from '@wordpress/element';
10
10
  import { ControlWithError } from '../control-with-error';
11
11
  import type { ValidatedControlProps } from './types';
12
12
  import TextControl from '../../text-control';
13
- import type { TextControlProps } from '../../text-control/types';
14
-
15
- type Value = TextControlProps[ 'value' ];
16
13
 
17
14
  const UnforwardedValidatedTextControl = (
18
15
  {
19
16
  required,
20
- onValidate,
21
17
  customValidity,
22
- onChange,
23
18
  markWhenOptional,
24
19
  ...restProps
25
20
  }: Omit<
26
21
  React.ComponentProps< typeof TextControl >,
27
22
  '__next40pxDefaultSize' | '__nextHasNoMarginBottom'
28
23
  > &
29
- ValidatedControlProps< Value >,
24
+ ValidatedControlProps,
30
25
  forwardedRef: React.ForwardedRef< HTMLInputElement >
31
26
  ) => {
32
27
  const validityTargetRef = useRef< HTMLInputElement >( null );
33
28
  const mergedRefs = useMergeRefs( [ forwardedRef, validityTargetRef ] );
34
- const valueRef = useRef< Value >( restProps.value );
35
29
 
36
30
  return (
37
31
  <ControlWithError
38
32
  required={ required }
39
33
  markWhenOptional={ markWhenOptional }
40
- onValidate={ () => {
41
- return onValidate?.( valueRef.current );
42
- } }
43
34
  customValidity={ customValidity }
44
35
  getValidityTarget={ () => validityTargetRef.current }
45
36
  >
@@ -47,10 +38,6 @@ const UnforwardedValidatedTextControl = (
47
38
  __next40pxDefaultSize
48
39
  __nextHasNoMarginBottom
49
40
  ref={ mergedRefs }
50
- onChange={ ( value ) => {
51
- valueRef.current = value;
52
- onChange?.( value );
53
- } }
54
41
  { ...restProps }
55
42
  />
56
43
  </ControlWithError>
@@ -10,46 +10,33 @@ import { useMergeRefs } from '@wordpress/compose';
10
10
  import { ControlWithError } from '../control-with-error';
11
11
  import type { ValidatedControlProps } from './types';
12
12
  import TextareaControl from '../../textarea-control';
13
- import type { TextareaControlProps } from '../../textarea-control/types';
14
-
15
- type Value = TextareaControlProps[ 'value' ];
16
13
 
17
14
  const UnforwardedValidatedTextareaControl = (
18
15
  {
19
16
  required,
20
- onValidate,
21
17
  customValidity,
22
- onChange,
23
18
  markWhenOptional,
24
19
  ...restProps
25
20
  }: Omit<
26
21
  React.ComponentProps< typeof TextareaControl >,
27
22
  '__nextHasNoMarginBottom'
28
23
  > &
29
- ValidatedControlProps< Value >,
24
+ ValidatedControlProps,
30
25
  forwardedRef: React.ForwardedRef< HTMLTextAreaElement >
31
26
  ) => {
32
27
  const validityTargetRef = useRef< HTMLTextAreaElement >( null );
33
28
  const mergedRefs = useMergeRefs( [ forwardedRef, validityTargetRef ] );
34
- const valueRef = useRef< Value >( restProps.value );
35
29
 
36
30
  return (
37
31
  <ControlWithError
38
32
  required={ required }
39
33
  markWhenOptional={ markWhenOptional }
40
- onValidate={ () => {
41
- return onValidate?.( valueRef.current );
42
- } }
43
34
  customValidity={ customValidity }
44
35
  getValidityTarget={ () => validityTargetRef.current }
45
36
  >
46
37
  <TextareaControl
47
38
  __nextHasNoMarginBottom
48
39
  ref={ mergedRefs }
49
- onChange={ ( value ) => {
50
- valueRef.current = value;
51
- onChange?.( value );
52
- } }
53
40
  { ...restProps }
54
41
  />
55
42
  </ControlWithError>