@wordpress/components 33.1.1-next.v.202605131032.0 → 35.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 (287) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/build/autocomplete/index.cjs +5 -4
  3. package/build/autocomplete/index.cjs.map +3 -3
  4. package/build/box-control/index.cjs +0 -6
  5. package/build/box-control/index.cjs.map +2 -2
  6. package/build/box-control/utils.cjs +0 -38
  7. package/build/box-control/utils.cjs.map +3 -3
  8. package/build/custom-gradient-picker/gradient-bar/index.cjs.map +2 -2
  9. package/build/draggable/index.cjs +101 -7
  10. package/build/draggable/index.cjs.map +3 -3
  11. package/build/form-token-field/index.cjs +41 -7
  12. package/build/form-token-field/index.cjs.map +2 -2
  13. package/build/index.cjs +0 -17
  14. package/build/index.cjs.map +2 -2
  15. package/build/notice/index.cjs +33 -35
  16. package/build/notice/index.cjs.map +2 -2
  17. package/build/popover/index.cjs +12 -0
  18. package/build/popover/index.cjs.map +2 -2
  19. package/build/tabs/styles.cjs +5 -5
  20. package/build/tabs/styles.cjs.map +2 -2
  21. package/build-module/autocomplete/index.mjs +6 -5
  22. package/build-module/autocomplete/index.mjs.map +2 -2
  23. package/build-module/box-control/index.mjs +0 -2
  24. package/build-module/box-control/index.mjs.map +2 -2
  25. package/build-module/box-control/utils.mjs +0 -27
  26. package/build-module/box-control/utils.mjs.map +2 -2
  27. package/build-module/custom-gradient-picker/gradient-bar/index.mjs.map +2 -2
  28. package/build-module/draggable/index.mjs +101 -7
  29. package/build-module/draggable/index.mjs.map +3 -3
  30. package/build-module/form-token-field/index.mjs +41 -7
  31. package/build-module/form-token-field/index.mjs.map +2 -2
  32. package/build-module/index.mjs +87 -99
  33. package/build-module/index.mjs.map +2 -2
  34. package/build-module/notice/index.mjs +34 -36
  35. package/build-module/notice/index.mjs.map +2 -2
  36. package/build-module/popover/index.mjs +12 -0
  37. package/build-module/popover/index.mjs.map +2 -2
  38. package/build-module/tabs/styles.mjs +5 -5
  39. package/build-module/tabs/styles.mjs.map +2 -2
  40. package/build-style/style-rtl.css +64 -66
  41. package/build-style/style.css +64 -66
  42. package/build-types/autocomplete/index.d.ts.map +1 -1
  43. package/build-types/badge/stories/e2e/index.story.d.ts +7 -0
  44. package/build-types/badge/stories/e2e/index.story.d.ts.map +1 -0
  45. package/build-types/box-control/index.d.ts +0 -1
  46. package/build-types/box-control/index.d.ts.map +1 -1
  47. package/build-types/box-control/styles/box-control-styles.d.ts +1 -2
  48. package/build-types/box-control/styles/box-control-styles.d.ts.map +1 -1
  49. package/build-types/box-control/utils.d.ts +0 -13
  50. package/build-types/box-control/utils.d.ts.map +1 -1
  51. package/build-types/button/stories/e2e/index.story.d.ts +1 -0
  52. package/build-types/button/stories/e2e/index.story.d.ts.map +1 -1
  53. package/build-types/color-picker/styles.d.ts +1 -2
  54. package/build-types/color-picker/styles.d.ts.map +1 -1
  55. package/build-types/custom-gradient-picker/gradient-bar/index.d.ts.map +1 -1
  56. package/build-types/draggable/index.d.ts.map +1 -1
  57. package/build-types/draggable/stories/index.story.d.ts +4 -5
  58. package/build-types/draggable/stories/index.story.d.ts.map +1 -1
  59. package/build-types/draggable/types.d.ts +4 -0
  60. package/build-types/draggable/types.d.ts.map +1 -1
  61. package/build-types/form-token-field/index.d.ts.map +1 -1
  62. package/build-types/icon/stories/index.story.d.ts +0 -6
  63. package/build-types/icon/stories/index.story.d.ts.map +1 -1
  64. package/build-types/index.d.ts +1 -6
  65. package/build-types/index.d.ts.map +1 -1
  66. package/build-types/notice/index.d.ts.map +1 -1
  67. package/build-types/notice/types.d.ts +1 -2
  68. package/build-types/notice/types.d.ts.map +1 -1
  69. package/build-types/popover/index.d.ts.map +1 -1
  70. package/build-types/range-control/index.d.ts +1 -2
  71. package/build-types/range-control/index.d.ts.map +1 -1
  72. package/build-types/range-control/stories/index.story.d.ts.map +1 -1
  73. package/build-types/range-control/types.d.ts +0 -4
  74. package/build-types/range-control/types.d.ts.map +1 -1
  75. package/build-types/tab-panel/stories/index.story.d.ts +0 -6
  76. package/build-types/tab-panel/stories/index.story.d.ts.map +1 -1
  77. package/build-types/tabs/stories/index.story.d.ts +0 -6
  78. package/build-types/tabs/stories/index.story.d.ts.map +1 -1
  79. package/build-types/tabs/styles.d.ts.map +1 -1
  80. package/build-types/validated-form-controls/components/range-control.d.ts +1 -2
  81. package/build-types/validated-form-controls/components/range-control.d.ts.map +1 -1
  82. package/package.json +24 -22
  83. package/src/autocomplete/index.tsx +25 -7
  84. package/src/badge/stories/e2e/index.story.tsx +21 -0
  85. package/src/box-control/index.tsx +0 -1
  86. package/src/box-control/utils.ts +0 -43
  87. package/src/button/stories/e2e/index.story.tsx +11 -0
  88. package/src/custom-gradient-picker/gradient-bar/index.tsx +1 -1
  89. package/src/draggable/index.tsx +32 -10
  90. package/src/draggable/stories/index.story.tsx +11 -6
  91. package/src/draggable/style.module.scss +29 -0
  92. package/src/draggable/types.ts +4 -0
  93. package/src/form-token-field/index.tsx +84 -8
  94. package/src/form-token-field/test/index.tsx +189 -0
  95. package/src/icon/stories/index.story.tsx +2 -14
  96. package/src/index.ts +0 -6
  97. package/src/menu/test/index.tsx +9 -4
  98. package/src/modal/style.scss +2 -1
  99. package/src/notice/README.md +1 -2
  100. package/src/notice/index.tsx +57 -64
  101. package/src/notice/style.scss +49 -41
  102. package/src/notice/test/__snapshots__/index.tsx.snap +23 -23
  103. package/src/notice/test/index.tsx +5 -5
  104. package/src/notice/types.ts +1 -2
  105. package/src/popover/index.tsx +28 -0
  106. package/src/popover/test/index.tsx +138 -1
  107. package/src/range-control/stories/index.story.tsx +0 -1
  108. package/src/range-control/types.ts +0 -4
  109. package/src/style.scss +0 -1
  110. package/src/tab-panel/stories/index.story.tsx +2 -13
  111. package/src/tab-panel/style.scss +36 -14
  112. package/src/tabs/stories/index.story.tsx +2 -13
  113. package/src/tabs/styles.ts +3 -8
  114. package/build/navigation/back-button/index.cjs +0 -86
  115. package/build/navigation/back-button/index.cjs.map +0 -7
  116. package/build/navigation/constants.cjs +0 -34
  117. package/build/navigation/constants.cjs.map +0 -7
  118. package/build/navigation/context.cjs +0 -58
  119. package/build/navigation/context.cjs.map +0 -7
  120. package/build/navigation/group/context.cjs +0 -38
  121. package/build/navigation/group/context.cjs.map +0 -7
  122. package/build/navigation/group/index.cjs +0 -88
  123. package/build/navigation/group/index.cjs.map +0 -7
  124. package/build/navigation/index.cjs +0 -113
  125. package/build/navigation/index.cjs.map +0 -7
  126. package/build/navigation/item/base-content.cjs +0 -44
  127. package/build/navigation/item/base-content.cjs.map +0 -7
  128. package/build/navigation/item/base.cjs +0 -66
  129. package/build/navigation/item/base.cjs.map +0 -7
  130. package/build/navigation/item/index.cjs +0 -119
  131. package/build/navigation/item/index.cjs.map +0 -7
  132. package/build/navigation/item/use-navigation-tree-item.cjs +0 -64
  133. package/build/navigation/item/use-navigation-tree-item.cjs.map +0 -7
  134. package/build/navigation/menu/context.cjs +0 -39
  135. package/build/navigation/menu/context.cjs.map +0 -7
  136. package/build/navigation/menu/index.cjs +0 -114
  137. package/build/navigation/menu/index.cjs.map +0 -7
  138. package/build/navigation/menu/menu-title-search.cjs +0 -111
  139. package/build/navigation/menu/menu-title-search.cjs.map +0 -7
  140. package/build/navigation/menu/menu-title.cjs +0 -104
  141. package/build/navigation/menu/menu-title.cjs.map +0 -7
  142. package/build/navigation/menu/search-no-results-found.cjs +0 -48
  143. package/build/navigation/menu/search-no-results-found.cjs.map +0 -7
  144. package/build/navigation/menu/use-navigation-tree-menu.cjs +0 -51
  145. package/build/navigation/menu/use-navigation-tree-menu.cjs.map +0 -7
  146. package/build/navigation/styles/navigation-styles.cjs +0 -170
  147. package/build/navigation/styles/navigation-styles.cjs.map +0 -7
  148. package/build/navigation/types.cjs +0 -19
  149. package/build/navigation/types.cjs.map +0 -7
  150. package/build/navigation/use-create-navigation-tree.cjs +0 -103
  151. package/build/navigation/use-create-navigation-tree.cjs.map +0 -7
  152. package/build/navigation/use-navigation-tree-nodes.cjs +0 -60
  153. package/build/navigation/use-navigation-tree-nodes.cjs.map +0 -7
  154. package/build/navigation/utils.cjs +0 -45
  155. package/build/navigation/utils.cjs.map +0 -7
  156. package/build-module/navigation/back-button/index.mjs +0 -51
  157. package/build-module/navigation/back-button/index.mjs.map +0 -7
  158. package/build-module/navigation/constants.mjs +0 -8
  159. package/build-module/navigation/constants.mjs.map +0 -7
  160. package/build-module/navigation/context.mjs +0 -32
  161. package/build-module/navigation/context.mjs.map +0 -7
  162. package/build-module/navigation/group/context.mjs +0 -12
  163. package/build-module/navigation/group/context.mjs.map +0 -7
  164. package/build-module/navigation/group/index.mjs +0 -53
  165. package/build-module/navigation/group/index.mjs.map +0 -7
  166. package/build-module/navigation/index.mjs +0 -78
  167. package/build-module/navigation/index.mjs.map +0 -7
  168. package/build-module/navigation/item/base-content.mjs +0 -23
  169. package/build-module/navigation/item/base-content.mjs.map +0 -7
  170. package/build-module/navigation/item/base.mjs +0 -35
  171. package/build-module/navigation/item/base.mjs.map +0 -7
  172. package/build-module/navigation/item/index.mjs +0 -84
  173. package/build-module/navigation/item/index.mjs.map +0 -7
  174. package/build-module/navigation/item/use-navigation-tree-item.mjs +0 -39
  175. package/build-module/navigation/item/use-navigation-tree-item.mjs.map +0 -7
  176. package/build-module/navigation/menu/context.mjs +0 -13
  177. package/build-module/navigation/menu/context.mjs.map +0 -7
  178. package/build-module/navigation/menu/index.mjs +0 -79
  179. package/build-module/navigation/menu/index.mjs.map +0 -7
  180. package/build-module/navigation/menu/menu-title-search.mjs +0 -80
  181. package/build-module/navigation/menu/menu-title-search.mjs.map +0 -7
  182. package/build-module/navigation/menu/menu-title.mjs +0 -73
  183. package/build-module/navigation/menu/menu-title.mjs.map +0 -7
  184. package/build-module/navigation/menu/search-no-results-found.mjs +0 -27
  185. package/build-module/navigation/menu/search-no-results-found.mjs.map +0 -7
  186. package/build-module/navigation/menu/use-navigation-tree-menu.mjs +0 -26
  187. package/build-module/navigation/menu/use-navigation-tree-menu.mjs.map +0 -7
  188. package/build-module/navigation/styles/navigation-styles.mjs +0 -124
  189. package/build-module/navigation/styles/navigation-styles.mjs.map +0 -7
  190. package/build-module/navigation/types.mjs +0 -1
  191. package/build-module/navigation/types.mjs.map +0 -7
  192. package/build-module/navigation/use-create-navigation-tree.mjs +0 -78
  193. package/build-module/navigation/use-create-navigation-tree.mjs.map +0 -7
  194. package/build-module/navigation/use-navigation-tree-nodes.mjs +0 -35
  195. package/build-module/navigation/use-navigation-tree-nodes.mjs.map +0 -7
  196. package/build-module/navigation/utils.mjs +0 -9
  197. package/build-module/navigation/utils.mjs.map +0 -7
  198. package/build-types/navigation/back-button/index.d.ts +0 -7
  199. package/build-types/navigation/back-button/index.d.ts.map +0 -1
  200. package/build-types/navigation/constants.d.ts +0 -3
  201. package/build-types/navigation/constants.d.ts.map +0 -1
  202. package/build-types/navigation/context.d.ts +0 -4
  203. package/build-types/navigation/context.d.ts.map +0 -1
  204. package/build-types/navigation/group/context.d.ts +0 -7
  205. package/build-types/navigation/group/context.d.ts.map +0 -1
  206. package/build-types/navigation/group/index.d.ts +0 -7
  207. package/build-types/navigation/group/index.d.ts.map +0 -1
  208. package/build-types/navigation/index.d.ts +0 -46
  209. package/build-types/navigation/index.d.ts.map +0 -1
  210. package/build-types/navigation/item/base-content.d.ts +0 -3
  211. package/build-types/navigation/item/base-content.d.ts.map +0 -1
  212. package/build-types/navigation/item/base.d.ts +0 -3
  213. package/build-types/navigation/item/base.d.ts.map +0 -1
  214. package/build-types/navigation/item/index.d.ts +0 -7
  215. package/build-types/navigation/item/index.d.ts.map +0 -1
  216. package/build-types/navigation/item/use-navigation-tree-item.d.ts +0 -3
  217. package/build-types/navigation/item/use-navigation-tree-item.d.ts.map +0 -1
  218. package/build-types/navigation/menu/context.d.ts +0 -7
  219. package/build-types/navigation/menu/context.d.ts.map +0 -1
  220. package/build-types/navigation/menu/index.d.ts +0 -7
  221. package/build-types/navigation/menu/index.d.ts.map +0 -1
  222. package/build-types/navigation/menu/menu-title-search.d.ts +0 -3
  223. package/build-types/navigation/menu/menu-title-search.d.ts.map +0 -1
  224. package/build-types/navigation/menu/menu-title.d.ts +0 -3
  225. package/build-types/navigation/menu/menu-title.d.ts.map +0 -1
  226. package/build-types/navigation/menu/search-no-results-found.d.ts +0 -3
  227. package/build-types/navigation/menu/search-no-results-found.d.ts.map +0 -1
  228. package/build-types/navigation/menu/use-navigation-tree-menu.d.ts +0 -3
  229. package/build-types/navigation/menu/use-navigation-tree-menu.d.ts.map +0 -1
  230. package/build-types/navigation/stories/index.story.d.ts +0 -23
  231. package/build-types/navigation/stories/index.story.d.ts.map +0 -1
  232. package/build-types/navigation/stories/utils/controlled-state.d.ts +0 -7
  233. package/build-types/navigation/stories/utils/controlled-state.d.ts.map +0 -1
  234. package/build-types/navigation/stories/utils/default.d.ts +0 -10
  235. package/build-types/navigation/stories/utils/default.d.ts.map +0 -1
  236. package/build-types/navigation/stories/utils/group.d.ts +0 -10
  237. package/build-types/navigation/stories/utils/group.d.ts.map +0 -1
  238. package/build-types/navigation/stories/utils/hide-if-empty.d.ts +0 -10
  239. package/build-types/navigation/stories/utils/hide-if-empty.d.ts.map +0 -1
  240. package/build-types/navigation/stories/utils/more-examples.d.ts +0 -10
  241. package/build-types/navigation/stories/utils/more-examples.d.ts.map +0 -1
  242. package/build-types/navigation/stories/utils/search.d.ts +0 -10
  243. package/build-types/navigation/stories/utils/search.d.ts.map +0 -1
  244. package/build-types/navigation/styles/navigation-styles.d.ts +0 -55
  245. package/build-types/navigation/styles/navigation-styles.d.ts.map +0 -1
  246. package/build-types/navigation/test/index.d.ts +0 -2
  247. package/build-types/navigation/test/index.d.ts.map +0 -1
  248. package/build-types/navigation/types.d.ts +0 -266
  249. package/build-types/navigation/types.d.ts.map +0 -1
  250. package/build-types/navigation/use-create-navigation-tree.d.ts +0 -15
  251. package/build-types/navigation/use-create-navigation-tree.d.ts.map +0 -1
  252. package/build-types/navigation/use-navigation-tree-nodes.d.ts +0 -10
  253. package/build-types/navigation/use-navigation-tree-nodes.d.ts.map +0 -1
  254. package/build-types/navigation/utils.d.ts +0 -3
  255. package/build-types/navigation/utils.d.ts.map +0 -1
  256. package/src/draggable/style.scss +0 -21
  257. package/src/navigation/README.md +0 -267
  258. package/src/navigation/back-button/index.tsx +0 -73
  259. package/src/navigation/constants.tsx +0 -2
  260. package/src/navigation/context.tsx +0 -40
  261. package/src/navigation/group/context.tsx +0 -16
  262. package/src/navigation/group/index.tsx +0 -73
  263. package/src/navigation/index.tsx +0 -152
  264. package/src/navigation/item/base-content.tsx +0 -31
  265. package/src/navigation/item/base.tsx +0 -42
  266. package/src/navigation/item/index.tsx +0 -112
  267. package/src/navigation/item/use-navigation-tree-item.tsx +0 -47
  268. package/src/navigation/menu/context.tsx +0 -20
  269. package/src/navigation/menu/index.tsx +0 -105
  270. package/src/navigation/menu/menu-title-search.tsx +0 -99
  271. package/src/navigation/menu/menu-title.tsx +0 -100
  272. package/src/navigation/menu/search-no-results-found.tsx +0 -34
  273. package/src/navigation/menu/use-navigation-tree-menu.tsx +0 -29
  274. package/src/navigation/stories/index.story.tsx +0 -62
  275. package/src/navigation/stories/style.css +0 -25
  276. package/src/navigation/stories/utils/controlled-state.tsx +0 -149
  277. package/src/navigation/stories/utils/default.tsx +0 -92
  278. package/src/navigation/stories/utils/group.tsx +0 -61
  279. package/src/navigation/stories/utils/hide-if-empty.tsx +0 -66
  280. package/src/navigation/stories/utils/more-examples.tsx +0 -162
  281. package/src/navigation/stories/utils/search.tsx +0 -91
  282. package/src/navigation/styles/navigation-styles.tsx +0 -197
  283. package/src/navigation/test/index.tsx +0 -347
  284. package/src/navigation/types.ts +0 -325
  285. package/src/navigation/use-create-navigation-tree.tsx +0 -110
  286. package/src/navigation/use-navigation-tree-nodes.tsx +0 -31
  287. package/src/navigation/utils.tsx +0 -11
@@ -1929,6 +1929,195 @@ describe( 'FormTokenField', () => {
1929
1929
  expect( onChangeSpy ).toHaveBeenCalledTimes( 2 );
1930
1930
  expectTokensToBeInTheDocument( [ 'cherry', 'Cranberry' ] );
1931
1931
  } );
1932
+
1933
+ it( 'should still preventDefault on Enter when validation rejects the value', async () => {
1934
+ const user = userEvent.setup();
1935
+
1936
+ const onChangeSpy = jest.fn();
1937
+ const onSubmitSpy = jest.fn( ( e: React.FormEvent ) =>
1938
+ e.preventDefault()
1939
+ );
1940
+ const startsWithCapitalLetter = ( tokenText: string ) =>
1941
+ /^[A-Z]/.test( tokenText );
1942
+
1943
+ render(
1944
+ <form onSubmit={ onSubmitSpy }>
1945
+ <FormTokenFieldWithState
1946
+ onChange={ onChangeSpy }
1947
+ __experimentalValidateInput={ startsWithCapitalLetter }
1948
+ />
1949
+ </form>
1950
+ );
1951
+
1952
+ const input = screen.getByRole( 'combobox' );
1953
+
1954
+ // Type 'hello' — lowercase, fails validation.
1955
+ // Press Enter — should NOT submit the parent form.
1956
+ await user.type( input, 'hello[Enter]' );
1957
+ expect( onChangeSpy ).not.toHaveBeenCalled();
1958
+ expect( onSubmitSpy ).not.toHaveBeenCalled();
1959
+ expect( input ).toHaveValue( 'hello' );
1960
+ } );
1961
+
1962
+ it( 'should not preventDefault on space when validation fails and `tokenizeOnSpace` is true', async () => {
1963
+ const user = userEvent.setup();
1964
+
1965
+ const onChangeSpy = jest.fn();
1966
+ const startsWithCapitalLetter = ( tokenText: string ) =>
1967
+ /^[A-Z]/.test( tokenText );
1968
+
1969
+ render(
1970
+ <FormTokenFieldWithState
1971
+ onChange={ onChangeSpy }
1972
+ tokenizeOnSpace
1973
+ __experimentalValidateInput={ startsWithCapitalLetter }
1974
+ />
1975
+ );
1976
+
1977
+ const input = screen.getByRole( 'combobox' );
1978
+
1979
+ // Type 'hello ', lowercase, fails validation.
1980
+ // The space should be typed into the input (not prevented), and
1981
+ // the trailing space should be preserved by the rejoin so the
1982
+ // user can keep typing past the failed-validation space.
1983
+ await user.type( input, 'hello ' );
1984
+ expect( onChangeSpy ).not.toHaveBeenCalled();
1985
+ expect( input ).toHaveValue( 'hello ' );
1986
+
1987
+ // User can keep typing past the failed-validation space.
1988
+ await user.type( input, 'w' );
1989
+ expect( input ).toHaveValue( 'hello w' );
1990
+
1991
+ // Clear and type 'Hello ', capital letter, passes validation.
1992
+ // The space should be prevented, and a token should be created.
1993
+ await user.clear( input );
1994
+ await user.type( input, 'Hello ' );
1995
+ expect( onChangeSpy ).toHaveBeenCalledTimes( 1 );
1996
+ expect( onChangeSpy ).toHaveBeenCalledWith( [ 'Hello' ] );
1997
+ expectTokensToBeInTheDocument( [ 'Hello' ] );
1998
+ } );
1999
+
2000
+ it( 'should filter out invalid tokens when pasting with separators', async () => {
2001
+ const user = userEvent.setup();
2002
+
2003
+ const onChangeSpy = jest.fn();
2004
+ const startsWithCapitalLetter = ( tokenText: string ) =>
2005
+ /^[A-Z]/.test( tokenText );
2006
+
2007
+ render(
2008
+ <FormTokenFieldWithState
2009
+ onChange={ onChangeSpy }
2010
+ __experimentalValidateInput={ startsWithCapitalLetter }
2011
+ />
2012
+ );
2013
+
2014
+ const input = screen.getByRole( 'combobox' );
2015
+
2016
+ // Paste values separated by comma, only valid ones should be added.
2017
+ // Uses paste (not type) because comma keystrokes go through
2018
+ // handleCommaKey, while pasted text goes through
2019
+ // onInputChangeHandler which splits by separator.
2020
+ await user.click( input );
2021
+ await user.paste( 'Apple,banana,Cherry,' );
2022
+ expect( onChangeSpy ).toHaveBeenCalledWith( [ 'Apple', 'Cherry' ] );
2023
+ expectTokensToBeInTheDocument( [ 'Apple', 'Cherry' ] );
2024
+ expectTokensNotToBeInTheDocument( [ 'banana' ] );
2025
+ expect( input ).toHaveValue( 'banana,' );
2026
+ } );
2027
+
2028
+ it( 'should leave all segments in the input when none pass validation on paste', async () => {
2029
+ const user = userEvent.setup();
2030
+
2031
+ const onChangeSpy = jest.fn();
2032
+ const startsWithCapitalLetter = ( tokenText: string ) =>
2033
+ /^[A-Z]/.test( tokenText );
2034
+
2035
+ render(
2036
+ <FormTokenFieldWithState
2037
+ onChange={ onChangeSpy }
2038
+ __experimentalValidateInput={ startsWithCapitalLetter }
2039
+ />
2040
+ );
2041
+
2042
+ const input = screen.getByRole( 'combobox' );
2043
+
2044
+ await user.click( input );
2045
+ await user.paste( 'apple,banana,cherry,' );
2046
+
2047
+ expect( onChangeSpy ).not.toHaveBeenCalled();
2048
+ expect( input ).toHaveValue( 'apple,banana,cherry,' );
2049
+ } );
2050
+
2051
+ it( 'should commit a trailing valid segment and leave only failed segments in the input when pasting without a trailing separator', async () => {
2052
+ const user = userEvent.setup();
2053
+
2054
+ const onChangeSpy = jest.fn();
2055
+ const startsWithCapitalLetter = ( tokenText: string ) =>
2056
+ /^[A-Z]/.test( tokenText );
2057
+
2058
+ render(
2059
+ <FormTokenFieldWithState
2060
+ onChange={ onChangeSpy }
2061
+ __experimentalValidateInput={ startsWithCapitalLetter }
2062
+ />
2063
+ );
2064
+
2065
+ const input = screen.getByRole( 'combobox' );
2066
+
2067
+ await user.click( input );
2068
+ await user.paste( 'Apple,banana,Cherry' );
2069
+
2070
+ expect( onChangeSpy ).toHaveBeenCalledWith( [ 'Apple', 'Cherry' ] );
2071
+ expectTokensToBeInTheDocument( [ 'Apple', 'Cherry' ] );
2072
+ expect( input ).toHaveValue( 'banana' );
2073
+ } );
2074
+
2075
+ it( 'should not leave a duplicate of an existing token in the input when pasting comma-separated values', async () => {
2076
+ const user = userEvent.setup();
2077
+
2078
+ const onChangeSpy = jest.fn();
2079
+
2080
+ render(
2081
+ <FormTokenFieldWithState
2082
+ onChange={ onChangeSpy }
2083
+ initialValue={ [ 'Apple' ] }
2084
+ />
2085
+ );
2086
+
2087
+ const input = screen.getByRole( 'combobox' );
2088
+
2089
+ await user.click( input );
2090
+ await user.paste( 'Apple,Cherry,' );
2091
+
2092
+ expect( onChangeSpy ).toHaveBeenCalledWith( [ 'Apple', 'Cherry' ] );
2093
+ expectTokensToBeInTheDocument( [ 'Apple', 'Cherry' ] );
2094
+ expect( input ).toHaveValue( '' );
2095
+ } );
2096
+
2097
+ it( 'should not leave a duplicate of an existing token in the input when pasting comma-separated values with `__experimentalValidateInput`', async () => {
2098
+ const user = userEvent.setup();
2099
+
2100
+ const onChangeSpy = jest.fn();
2101
+ const startsWithCapitalLetter = ( tokenText: string ) =>
2102
+ /^[A-Z]/.test( tokenText );
2103
+
2104
+ render(
2105
+ <FormTokenFieldWithState
2106
+ onChange={ onChangeSpy }
2107
+ initialValue={ [ 'Apple' ] }
2108
+ __experimentalValidateInput={ startsWithCapitalLetter }
2109
+ />
2110
+ );
2111
+
2112
+ const input = screen.getByRole( 'combobox' );
2113
+
2114
+ await user.click( input );
2115
+ await user.paste( 'Apple,Cherry,' );
2116
+
2117
+ expect( onChangeSpy ).toHaveBeenCalledWith( [ 'Apple', 'Cherry' ] );
2118
+ expectTokensToBeInTheDocument( [ 'Apple', 'Cherry' ] );
2119
+ expect( input ).toHaveValue( '' );
2120
+ } );
1932
2121
  } );
1933
2122
 
1934
2123
  describe( 'maxLength', () => {
@@ -1,31 +1,19 @@
1
- /**
2
- * External dependencies
3
- */
4
1
  import type { Meta, StoryFn } from '@storybook/react-vite';
5
-
6
- /**
7
- * WordPress dependencies
8
- */
9
2
  import { SVG, Path } from '@wordpress/primitives';
10
3
  import { wordpress } from '@wordpress/icons';
11
-
12
- /**
13
- * Internal dependencies
14
- */
15
4
  import Icon from '..';
16
5
  import { VStack } from '../../v-stack';
17
6
 
18
7
  const meta: Meta< typeof Icon > = {
19
- tags: [ 'manifest' ],
20
8
  title: 'Components/Icon',
21
9
  component: Icon,
22
10
  parameters: {
23
11
  controls: { expanded: true },
24
12
  docs: { canvas: { sourceState: 'shown' } },
25
13
  componentStatus: {
26
- status: 'recommended',
14
+ status: 'use-with-caution',
27
15
  whereUsed: 'global',
28
- notes: 'Prefer this component over the `Icon` component from `@wordpress/icons`.',
16
+ notes: 'When rendering SVGs, use `Icon` from `@wordpress/ui` instead.',
29
17
  },
30
18
  },
31
19
  };
package/src/index.ts CHANGED
@@ -45,7 +45,6 @@ export {
45
45
  /** @deprecated Import `BoxControl` instead. */
46
46
  default as __experimentalBoxControl,
47
47
  default as BoxControl,
48
- applyValueToSides as __experimentalApplyValueToSides,
49
48
  } from './box-control';
50
49
  export { default as Button } from './button';
51
50
  export { default as ButtonGroup } from './button-group';
@@ -118,11 +117,6 @@ export { default as MenuItemsChoice } from './menu-items-choice';
118
117
  export { default as Modal } from './modal';
119
118
  export { default as ScrollLock } from './scroll-lock';
120
119
  export { NavigableMenu, TabbableContainer } from './navigable-container';
121
- export { default as __experimentalNavigation } from './navigation';
122
- export { default as __experimentalNavigationBackButton } from './navigation/back-button';
123
- export { default as __experimentalNavigationGroup } from './navigation/group';
124
- export { default as __experimentalNavigationItem } from './navigation/item';
125
- export { default as __experimentalNavigationMenu } from './navigation/menu';
126
120
  export {
127
121
  /** @deprecated Import `Navigator` instead. */
128
122
  NavigatorProvider as __experimentalNavigatorProvider,
@@ -156,7 +156,7 @@ describe( 'Menu', () => {
156
156
  await waitForFocusedMenuItem( 'First item' );
157
157
  } );
158
158
 
159
- it( 'should open and focus the first item when pressing the space key on the trigger', async () => {
159
+ it( 'should open when pressing the space key on the trigger', async () => {
160
160
  render(
161
161
  <Menu>
162
162
  <Menu.TriggerButton>Open dropdown</Menu.TriggerButton>
@@ -181,11 +181,16 @@ describe( 'Menu', () => {
181
181
  expect( screen.queryByRole( 'menuitem' ) ).not.toBeInTheDocument();
182
182
 
183
183
  // Keyboard-triggered clicks have `detail: 0`, which Ariakit uses to
184
- // choose the initial item focus path instead of the pointer path.
184
+ // distinguish keyboard activation from pointer activation.
185
185
  await press.Space( toggleButton, { detail: 0 } );
186
186
 
187
- // Menu open, focus is on the first focusable item
188
- await waitForFocusedMenuItem( 'First item' );
187
+ await waitFor( () =>
188
+ expect( toggleButton ).toHaveAttribute(
189
+ 'aria-expanded',
190
+ 'true'
191
+ )
192
+ );
193
+ expect( screen.getByRole( 'menu' ) ).toBeVisible();
189
194
  } );
190
195
 
191
196
  it( 'should close when pressing the escape key', async () => {
@@ -200,7 +200,8 @@
200
200
  align-items: center;
201
201
  height: $header-height + $grid-unit-10; // 72px
202
202
  width: 100%;
203
- z-index: z-index(".components-modal__header");
203
+ // Stack above modal content that may overlap the header.
204
+ z-index: 10;
204
205
  position: absolute;
205
206
  top: 0;
206
207
  left: 0;
@@ -17,7 +17,6 @@ Notices display at the top of the screen, below any toolbars anchored to the top
17
17
  Notices are color-coded to indicate the type of message being communicated:
18
18
 
19
19
  - **Informational** notices are **blue** by default.
20
- - If there is a parent `Theme` component with an `accent` color prop, informational notices will take on that color instead.
21
20
  - **Success** notices are **green.**
22
21
  - **Warning** notices are **yellow.**
23
22
  - **Error** notices are **red.**
@@ -101,7 +100,7 @@ Used to provide a custom spoken message in place of the `children` default.
101
100
 
102
101
  #### `status`: `'warning' | 'success' | 'error' | 'info'`
103
102
 
104
- Determines the color of the notice: `warning` (yellow), `success` (green), `error` (red), or `'info'`. By default `'info'` will be blue, but if there is a parent Theme component with an accent color prop, the notice will take on that color instead.
103
+ Determines the color of the notice: `warning` (yellow), `success` (green), `error` (red), or `'info'`. By default `'info'` will be blue.
105
104
 
106
105
  - Required: No
107
106
  - Default: `info`
@@ -9,7 +9,7 @@ import clsx from 'clsx';
9
9
  import { __ } from '@wordpress/i18n';
10
10
  import { RawHTML, useEffect, renderToString } from '@wordpress/element';
11
11
  import { speak } from '@wordpress/a11y';
12
- import { close } from '@wordpress/icons';
12
+ import { closeSmall } from '@wordpress/icons';
13
13
 
14
14
  /**
15
15
  * Internal dependencies
@@ -93,9 +93,9 @@ function Notice( {
93
93
  }: NoticeProps ) {
94
94
  useSpokenMessage( spokenMessage, politeness );
95
95
 
96
- const classes = clsx( className, 'components-notice', 'is-' + status, {
97
- 'is-dismissible': isDismissible,
98
- } );
96
+ // Dismissibility is not a wrapper modifier; target `.components-notice__dismiss`
97
+ // or `.components-notice:has(.components-notice__dismiss)` from outside CSS.
98
+ const classes = clsx( className, 'components-notice', 'is-' + status );
99
99
 
100
100
  if ( __unstableHTML && typeof children === 'string' ) {
101
101
  children = <RawHTML>{ children }</RawHTML>;
@@ -109,71 +109,64 @@ function Notice( {
109
109
  return (
110
110
  <div className={ classes }>
111
111
  <VisuallyHidden>{ getStatusLabel( status ) }</VisuallyHidden>
112
- <div className="components-notice__content">
113
- { children }
114
- { actions.length > 0 && (
115
- <div className="components-notice__actions">
116
- { actions.map(
117
- (
118
- {
119
- className: buttonCustomClasses,
120
- label,
121
- isPrimary,
122
- variant,
123
- noDefaultClasses = false,
124
- onClick,
125
- url,
126
- disabled,
127
- }: NoticeAction &
128
- // `isPrimary` is a legacy prop included for
129
- // backcompat, but `variant` should be used
130
- // instead.
131
- Pick< DeprecatedButtonProps, 'isPrimary' >,
132
- index
133
- ) => {
134
- let computedVariant = variant;
135
- if (
136
- variant !== 'primary' &&
137
- ! noDefaultClasses
138
- ) {
139
- computedVariant = ! url
140
- ? 'secondary'
141
- : 'link';
142
- }
143
- if (
144
- typeof computedVariant === 'undefined' &&
145
- isPrimary
146
- ) {
147
- computedVariant = 'primary';
148
- }
149
-
150
- return (
151
- <Button
152
- __next40pxDefaultSize
153
- key={ index }
154
- href={ url }
155
- variant={ computedVariant }
156
- onClick={ onClick }
157
- disabled={ disabled }
158
- accessibleWhenDisabled
159
- className={ clsx(
160
- 'components-notice__action',
161
- buttonCustomClasses
162
- ) }
163
- >
164
- { label }
165
- </Button>
166
- );
112
+ <div className="components-notice__content">{ children }</div>
113
+ { actions.length > 0 && (
114
+ <div className="components-notice__actions">
115
+ { actions.map(
116
+ (
117
+ {
118
+ className: buttonCustomClasses,
119
+ label,
120
+ isPrimary,
121
+ variant,
122
+ noDefaultClasses = false,
123
+ onClick,
124
+ url,
125
+ disabled,
126
+ }: NoticeAction &
127
+ // `isPrimary` is a legacy prop included for
128
+ // backcompat, but `variant` should be used
129
+ // instead.
130
+ Pick< DeprecatedButtonProps, 'isPrimary' >,
131
+ index
132
+ ) => {
133
+ let computedVariant = variant;
134
+ if ( variant !== 'primary' && ! noDefaultClasses ) {
135
+ computedVariant = ! url ? 'secondary' : 'link';
136
+ }
137
+ if (
138
+ typeof computedVariant === 'undefined' &&
139
+ isPrimary
140
+ ) {
141
+ computedVariant = 'primary';
167
142
  }
168
- ) }
169
- </div>
170
- ) }
171
- </div>
143
+
144
+ return (
145
+ <Button
146
+ size="compact"
147
+ key={ index }
148
+ href={ url }
149
+ variant={ computedVariant }
150
+ onClick={ onClick }
151
+ disabled={ disabled }
152
+ accessibleWhenDisabled
153
+ className={ clsx(
154
+ 'components-notice__action',
155
+ buttonCustomClasses
156
+ ) }
157
+ >
158
+ { label }
159
+ </Button>
160
+ );
161
+ }
162
+ ) }
163
+ </div>
164
+ ) }
172
165
  { isDismissible && (
173
166
  <Button
174
167
  size="small"
175
168
  className="components-notice__dismiss"
176
- icon={ close }
169
+ icon={ closeSmall }
177
170
  label={ __( 'Close' ) }
178
171
  onClick={ onDismissNotice }
179
172
  />
@@ -1,67 +1,77 @@
1
- @use "sass:color";
2
- @use "@wordpress/base-styles/colors" as *;
3
1
  @use "@wordpress/base-styles/variables" as *;
4
- @use "../utils/theme-variables" as *;
5
2
 
6
3
  .components-notice {
7
- display: flex;
8
- font-family: $default-font;
9
- font-size: $default-font-size;
10
- background-color: $white;
11
- border-left: 4px solid $components-color-accent;
12
- padding: 8px 12px;
13
- align-items: center;
14
- color: $gray-900;
15
-
16
- &.is-dismissible {
17
- position: relative;
4
+ --wp-components-notice-background-color: var(--wpds-color-bg-surface-info-weak);
5
+ --wp-components-notice-border-color: var(--wpds-color-stroke-surface-info);
6
+ --wp-components-notice-text-color: var(--wpds-color-fg-content-info);
18
7
 
19
- .components-notice__content {
20
- margin-right: #{ $button-size-small + $border-width };
21
- }
22
- }
8
+ box-sizing: border-box;
9
+ display: grid;
10
+ grid-template-columns: 1fr auto;
11
+ align-items: start;
12
+ padding: var(--wpds-dimension-padding-md);
13
+ border: var(--wpds-border-width-xs) solid var(--wp-components-notice-border-color);
14
+ border-radius: var(--wpds-border-radius-lg);
15
+ background-color: var(--wp-components-notice-background-color);
16
+ color: var(--wp-components-notice-text-color);
17
+ font-family: var(--wpds-typography-font-family-body);
18
+ font-size: var(--wpds-typography-font-size-md);
19
+ line-height: var(--wpds-typography-line-height-sm);
23
20
 
24
21
  &.is-success {
25
- border-left-color: $alert-green;
26
- background-color: color.adjust($alert-green, $lightness: +45%);
22
+ --wp-components-notice-background-color: var(--wpds-color-bg-surface-success-weak);
23
+ --wp-components-notice-border-color: var(--wpds-color-stroke-surface-success);
24
+ --wp-components-notice-text-color: var(--wpds-color-fg-content-success);
27
25
  }
28
26
 
29
27
  &.is-warning {
30
- border-left-color: $alert-yellow;
31
- background-color: color.adjust($alert-yellow, $lightness: +35%);
28
+ --wp-components-notice-background-color: var(--wpds-color-bg-surface-warning-weak);
29
+ --wp-components-notice-border-color: var(--wpds-color-stroke-surface-warning);
30
+ --wp-components-notice-text-color: var(--wpds-color-fg-content-warning);
32
31
  }
33
32
 
34
33
  &.is-error {
35
- border-left-color: $alert-red;
36
- background-color: color.adjust($alert-red, $lightness: +35%);
34
+ --wp-components-notice-background-color: var(--wpds-color-bg-surface-error-weak);
35
+ --wp-components-notice-border-color: var(--wpds-color-stroke-surface-error);
36
+ --wp-components-notice-text-color: var(--wpds-color-fg-content-error);
37
37
  }
38
38
  }
39
39
 
40
40
  .components-notice__content {
41
- flex-grow: 1;
42
- margin-top: $grid-unit-05;
43
- margin-bottom: $grid-unit-05;
41
+ grid-column: 1;
42
+ grid-row: 1;
43
+
44
+ // Centers single-line copy with the dismiss row when it is present. The same
45
+ // padding is kept when the notice is not dismissible so block height stays
46
+ // consistent across variants, matching `@wordpress/ui` Notice (`.title` /
47
+ // `.description` always use `--text-vertical-padding` whether or not `CloseIcon`
48
+ // is rendered).
49
+ // TODO: Replace `$button-size-small` with a WPDS size token once size tokens
50
+ // land in `@wordpress/theme`.
51
+ padding-block: calc((#{$button-size-small} - 1lh) / 2);
44
52
  }
45
53
 
46
54
  .components-notice__actions {
55
+ grid-column: 1;
56
+ grid-row: 2;
57
+
47
58
  display: flex;
48
59
  flex-wrap: wrap;
49
60
  align-items: center;
50
- gap: $grid-unit-15;
51
- margin-top: $grid-unit-15;
61
+ gap: var(--wpds-dimension-gap-md);
62
+ margin-top: var(--wpds-dimension-gap-sm);
52
63
  }
53
64
 
54
65
  .components-notice__dismiss {
55
- color: $gray-700;
56
-
57
- // Place the dismiss button at the top of the container, even when text wraps onto two lines.
58
- align-self: flex-start;
59
- flex-shrink: 0;
66
+ grid-column: 2;
67
+ grid-row: 1;
68
+ color: var(--wpds-color-fg-interactive-neutral);
69
+ margin-inline-start: var(--wpds-dimension-gap-xs);
60
70
 
61
71
  &:not(:disabled):not([aria-disabled="true"]):not(.is-secondary):hover,
62
72
  &:not(:disabled):not([aria-disabled="true"]):not(.is-secondary):active,
63
73
  &:not(:disabled):not([aria-disabled="true"]):focus {
64
- color: $gray-900;
74
+ color: var(--wpds-color-fg-interactive-neutral-active);
65
75
  background-color: transparent;
66
76
  }
67
77
 
@@ -71,13 +81,11 @@
71
81
  }
72
82
 
73
83
  .components-notice-list {
84
+ display: flex;
85
+ flex-direction: column;
86
+ gap: var(--wpds-dimension-gap-md);
87
+
74
88
  // The notice should never be wider than the viewport, or the close button might be hidden. Especially relevant at high zoom levels. Related to https://core.trac.wordpress.org/ticket/47603#ticket.
75
89
  max-width: 100vw;
76
90
  box-sizing: border-box;
77
-
78
- .components-notice__content {
79
- margin-top: $grid-unit-15;
80
- margin-bottom: $grid-unit-15;
81
- line-height: 2;
82
- }
83
91
  }
@@ -3,7 +3,7 @@
3
3
  exports[`Notice should match snapshot 1`] = `
4
4
  <div>
5
5
  <div
6
- class="components-notice is-success is-dismissible"
6
+ class="components-notice is-success"
7
7
  >
8
8
  <div
9
9
  class="components-visually-hidden css-1ragr82-PolymorphicDiv emotion-0"
@@ -18,28 +18,28 @@ exports[`Notice should match snapshot 1`] = `
18
18
  class="components-notice__content"
19
19
  >
20
20
  Example
21
- <div
22
- class="components-notice__actions"
21
+ </div>
22
+ <div
23
+ class="components-notice__actions"
24
+ >
25
+ <a
26
+ class="components-button components-notice__action is-compact is-link"
27
+ href="https://example.com"
28
+ >
29
+ More information
30
+ </a>
31
+ <button
32
+ class="components-button components-notice__action is-secondary is-compact"
33
+ type="button"
34
+ >
35
+ Cancel
36
+ </button>
37
+ <button
38
+ class="components-button components-notice__action is-primary is-compact"
39
+ type="button"
23
40
  >
24
- <a
25
- class="components-button components-notice__action is-next-40px-default-size is-link"
26
- href="https://example.com"
27
- >
28
- More information
29
- </a>
30
- <button
31
- class="components-button components-notice__action is-next-40px-default-size is-secondary"
32
- type="button"
33
- >
34
- Cancel
35
- </button>
36
- <button
37
- class="components-button components-notice__action is-next-40px-default-size is-primary"
38
- type="button"
39
- >
40
- Submit
41
- </button>
42
- </div>
41
+ Submit
42
+ </button>
43
43
  </div>
44
44
  <button
45
45
  aria-label="Close"
@@ -55,7 +55,7 @@ exports[`Notice should match snapshot 1`] = `
55
55
  xmlns="http://www.w3.org/2000/svg"
56
56
  >
57
57
  <path
58
- d="m13.06 12 6.47-6.47-1.06-1.06L12 10.94 5.53 4.47 4.47 5.53 10.94 12l-6.47 6.47 1.06 1.06L12 13.06l6.47 6.47 1.06-1.06L13.06 12Z"
58
+ d="M12 13.06l3.712 3.713 1.061-1.06L13.061 12l3.712-3.712-1.06-1.06L12 10.938 8.288 7.227l-1.061 1.06L10.939 12l-3.712 3.712 1.06 1.061L12 13.061z"
59
59
  />
60
60
  </svg>
61
61
  </button>
@@ -43,14 +43,14 @@ describe( 'Notice', () => {
43
43
  expect( container ).toMatchSnapshot();
44
44
  } );
45
45
 
46
- it( 'should not have is-dismissible class when isDismissible prop is false', () => {
47
- const { container } = render(
46
+ it( 'should not render a dismiss control when isDismissible prop is false', () => {
47
+ render(
48
48
  <Notice isDismissible={ false }>I cannot be dismissed!</Notice>
49
49
  );
50
- const wrapper = getNoticeWrapper( container );
51
50
 
52
- expect( wrapper ).toHaveClass( 'components-notice' );
53
- expect( wrapper ).not.toHaveClass( 'is-dismissible' );
51
+ expect(
52
+ screen.queryByRole( 'button', { name: 'Close' } )
53
+ ).not.toBeInTheDocument();
54
54
  } );
55
55
 
56
56
  it( 'should default to info status', () => {