@wordpress/components 25.14.0 → 25.15.1-next.79a6196f.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 (568) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/CONTRIBUTING.md +57 -115
  3. package/LICENSE.md +1 -1
  4. package/build/base-control/index.js +17 -13
  5. package/build/base-control/index.js.map +1 -1
  6. package/build/border-box-control/border-box-control-linked-button/component.js +1 -1
  7. package/build/border-box-control/border-box-control-linked-button/component.js.map +1 -1
  8. package/build/border-control/border-control-style-picker/component.js +1 -1
  9. package/build/border-control/border-control-style-picker/component.js.map +1 -1
  10. package/build/box-control/index.js +1 -1
  11. package/build/box-control/index.js.map +1 -1
  12. package/build/box-control/linked-button.js +1 -1
  13. package/build/box-control/linked-button.js.map +1 -1
  14. package/build/button/index.js +1 -1
  15. package/build/button/index.js.map +1 -1
  16. package/build/color-palette/index.native.js +11 -7
  17. package/build/color-palette/index.native.js.map +1 -1
  18. package/build/color-picker/color-copy-button.js +1 -1
  19. package/build/color-picker/color-copy-button.js.map +1 -1
  20. package/build/context/wordpress-component.js.map +1 -1
  21. package/build/custom-select-control-v2/index.js +11 -10
  22. package/build/custom-select-control-v2/index.js.map +1 -1
  23. package/build/date-time/date/styles.js +8 -8
  24. package/build/date-time/date/styles.js.map +1 -1
  25. package/build/dropdown-menu-v2/index.js +205 -159
  26. package/build/dropdown-menu-v2/index.js.map +1 -1
  27. package/build/dropdown-menu-v2/styles.js +86 -77
  28. package/build/dropdown-menu-v2/styles.js.map +1 -1
  29. package/build/dropdown-menu-v2/types.js.map +1 -1
  30. package/build/duotone-picker/duotone-picker.js +4 -3
  31. package/build/duotone-picker/duotone-picker.js.map +1 -1
  32. package/build/font-size-picker/index.js +4 -2
  33. package/build/font-size-picker/index.js.map +1 -1
  34. package/build/font-size-picker/index.native.js +6 -3
  35. package/build/font-size-picker/index.native.js.map +1 -1
  36. package/build/form-token-field/index.js +10 -5
  37. package/build/form-token-field/index.js.map +1 -1
  38. package/build/form-token-field/token.js +1 -0
  39. package/build/form-token-field/token.js.map +1 -1
  40. package/build/gradient-picker/index.js +3 -2
  41. package/build/gradient-picker/index.js.map +1 -1
  42. package/build/index.native.js +20 -3
  43. package/build/index.native.js.map +1 -1
  44. package/build/input-control/styles/input-control-styles.js +32 -29
  45. package/build/input-control/styles/input-control-styles.js.map +1 -1
  46. package/build/input-control/types.js.map +1 -1
  47. package/build/lock-unlock.js +18 -0
  48. package/build/lock-unlock.js.map +1 -0
  49. package/build/mobile/bottom-sheet/index.native.js +8 -0
  50. package/build/mobile/bottom-sheet/index.native.js.map +1 -1
  51. package/build/mobile/color-settings/palette.screen.native.js +8 -4
  52. package/build/mobile/color-settings/palette.screen.native.js.map +1 -1
  53. package/build/mobile/global-styles-context/utils.native.js +26 -13
  54. package/build/mobile/global-styles-context/utils.native.js.map +1 -1
  55. package/build/mobile/image/constants.js +12 -0
  56. package/build/mobile/image/constants.js.map +1 -0
  57. package/build/mobile/image/index.native.js +26 -18
  58. package/build/mobile/image/index.native.js.map +1 -1
  59. package/build/mobile/keyboard-aware-flat-list/index.android.js +40 -8
  60. package/build/mobile/keyboard-aware-flat-list/index.android.js.map +1 -1
  61. package/build/mobile/keyboard-aware-flat-list/index.ios.js +44 -68
  62. package/build/mobile/keyboard-aware-flat-list/index.ios.js.map +1 -1
  63. package/build/mobile/keyboard-aware-flat-list/use-scroll-to-element.native.js +39 -0
  64. package/build/mobile/keyboard-aware-flat-list/use-scroll-to-element.native.js.map +1 -0
  65. package/build/mobile/keyboard-aware-flat-list/{use-scroll-to-text-input.native.js → use-scroll-to-section.native.js} +22 -29
  66. package/build/mobile/keyboard-aware-flat-list/use-scroll-to-section.native.js.map +1 -0
  67. package/build/mobile/keyboard-aware-flat-list/use-scroll.native.js +93 -0
  68. package/build/mobile/keyboard-aware-flat-list/use-scroll.native.js.map +1 -0
  69. package/build/mobile/utils/get-px-from-css-unit.native.js +302 -0
  70. package/build/mobile/utils/get-px-from-css-unit.native.js.map +1 -0
  71. package/build/modal/index.js +18 -13
  72. package/build/modal/index.js.map +1 -1
  73. package/build/navigation/menu/menu-title.js +1 -1
  74. package/build/navigation/menu/menu-title.js.map +1 -1
  75. package/build/navigator/navigator-provider/component.js +13 -15
  76. package/build/navigator/navigator-provider/component.js.map +1 -1
  77. package/build/navigator/navigator-screen/component.js +23 -63
  78. package/build/navigator/navigator-screen/component.js.map +1 -1
  79. package/build/navigator/styles.js +52 -0
  80. package/build/navigator/styles.js.map +1 -0
  81. package/build/number-control/index.js +4 -8
  82. package/build/number-control/index.js.map +1 -1
  83. package/build/number-control/types.js.map +1 -1
  84. package/build/palette-edit/index.js +15 -54
  85. package/build/palette-edit/index.js.map +1 -1
  86. package/build/private-apis.js +11 -26
  87. package/build/private-apis.js.map +1 -1
  88. package/build/private-apis.native.js +21 -0
  89. package/build/private-apis.native.js.map +1 -0
  90. package/build/radio-control/index.js +1 -0
  91. package/build/radio-control/index.js.map +1 -1
  92. package/build/range-control/index.js +1 -1
  93. package/build/range-control/index.js.map +1 -1
  94. package/build/select-control/styles/select-control-styles.js +15 -25
  95. package/build/select-control/styles/select-control-styles.js.map +1 -1
  96. package/build/slot-fill/index.js +3 -2
  97. package/build/slot-fill/index.js.map +1 -1
  98. package/build/slot-fill/types.js.map +1 -1
  99. package/build/snackbar/types.js.map +1 -1
  100. package/build/tabs/styles.js +3 -3
  101. package/build/tabs/styles.js.map +1 -1
  102. package/build/tabs/tabpanel.js +9 -7
  103. package/build/tabs/tabpanel.js.map +1 -1
  104. package/build/toggle-group-control/toggle-group-control/component.js +4 -4
  105. package/build/toggle-group-control/toggle-group-control/component.js.map +1 -1
  106. package/build/toggle-group-control/toggle-group-control/styles.js +29 -15
  107. package/build/toggle-group-control/toggle-group-control/styles.js.map +1 -1
  108. package/build/toggle-group-control/toggle-group-control-option-base/styles.js +9 -9
  109. package/build/toggle-group-control/toggle-group-control-option-base/styles.js.map +1 -1
  110. package/build/tools-panel/tools-panel/component.js +3 -1
  111. package/build/tools-panel/tools-panel/component.js.map +1 -1
  112. package/build/tools-panel/tools-panel-header/component.js +9 -8
  113. package/build/tools-panel/tools-panel-header/component.js.map +1 -1
  114. package/build/tools-panel/types.js.map +1 -1
  115. package/build/tooltip/index.js +34 -10
  116. package/build/tooltip/index.js.map +1 -1
  117. package/build/tooltip/types.js.map +1 -1
  118. package/build/truncate/hook.js +10 -4
  119. package/build/truncate/hook.js.map +1 -1
  120. package/build/truncate/types.js.map +1 -1
  121. package/build/unit-control/index.js +1 -1
  122. package/build/unit-control/index.js.map +1 -1
  123. package/build/utils/strings.js +34 -3
  124. package/build/utils/strings.js.map +1 -1
  125. package/build-module/base-control/index.js +16 -12
  126. package/build-module/base-control/index.js.map +1 -1
  127. package/build-module/border-box-control/border-box-control-linked-button/component.js +1 -1
  128. package/build-module/border-box-control/border-box-control-linked-button/component.js.map +1 -1
  129. package/build-module/border-control/border-control-style-picker/component.js +1 -1
  130. package/build-module/border-control/border-control-style-picker/component.js.map +1 -1
  131. package/build-module/box-control/index.js +1 -1
  132. package/build-module/box-control/index.js.map +1 -1
  133. package/build-module/box-control/linked-button.js +1 -1
  134. package/build-module/box-control/linked-button.js.map +1 -1
  135. package/build-module/button/index.js +1 -1
  136. package/build-module/button/index.js.map +1 -1
  137. package/build-module/color-palette/index.native.js +11 -7
  138. package/build-module/color-palette/index.native.js.map +1 -1
  139. package/build-module/color-picker/color-copy-button.js +1 -1
  140. package/build-module/color-picker/color-copy-button.js.map +1 -1
  141. package/build-module/context/wordpress-component.js.map +1 -1
  142. package/build-module/custom-select-control-v2/index.js +11 -10
  143. package/build-module/custom-select-control-v2/index.js.map +1 -1
  144. package/build-module/date-time/date/styles.js +8 -8
  145. package/build-module/date-time/date/styles.js.map +1 -1
  146. package/build-module/dropdown-menu-v2/index.js +201 -154
  147. package/build-module/dropdown-menu-v2/index.js.map +1 -1
  148. package/build-module/dropdown-menu-v2/styles.js +68 -61
  149. package/build-module/dropdown-menu-v2/styles.js.map +1 -1
  150. package/build-module/dropdown-menu-v2/types.js.map +1 -1
  151. package/build-module/duotone-picker/duotone-picker.js +4 -3
  152. package/build-module/duotone-picker/duotone-picker.js.map +1 -1
  153. package/build-module/font-size-picker/index.js +4 -2
  154. package/build-module/font-size-picker/index.js.map +1 -1
  155. package/build-module/font-size-picker/index.native.js +5 -2
  156. package/build-module/font-size-picker/index.native.js.map +1 -1
  157. package/build-module/form-token-field/index.js +10 -5
  158. package/build-module/form-token-field/index.js.map +1 -1
  159. package/build-module/form-token-field/token.js +1 -0
  160. package/build-module/form-token-field/token.js.map +1 -1
  161. package/build-module/gradient-picker/index.js +3 -2
  162. package/build-module/gradient-picker/index.js.map +1 -1
  163. package/build-module/index.native.js +6 -1
  164. package/build-module/index.native.js.map +1 -1
  165. package/build-module/input-control/styles/input-control-styles.js +31 -29
  166. package/build-module/input-control/styles/input-control-styles.js.map +1 -1
  167. package/build-module/input-control/types.js.map +1 -1
  168. package/build-module/lock-unlock.js +9 -0
  169. package/build-module/lock-unlock.js.map +1 -0
  170. package/build-module/mobile/bottom-sheet/index.native.js +9 -1
  171. package/build-module/mobile/bottom-sheet/index.native.js.map +1 -1
  172. package/build-module/mobile/color-settings/palette.screen.native.js +8 -4
  173. package/build-module/mobile/color-settings/palette.screen.native.js.map +1 -1
  174. package/build-module/mobile/global-styles-context/utils.native.js +25 -13
  175. package/build-module/mobile/global-styles-context/utils.native.js.map +1 -1
  176. package/build-module/mobile/image/constants.js +5 -0
  177. package/build-module/mobile/image/constants.js.map +1 -0
  178. package/build-module/mobile/image/index.native.js +25 -16
  179. package/build-module/mobile/image/index.native.js.map +1 -1
  180. package/build-module/mobile/keyboard-aware-flat-list/index.android.js +40 -6
  181. package/build-module/mobile/keyboard-aware-flat-list/index.android.js.map +1 -1
  182. package/build-module/mobile/keyboard-aware-flat-list/index.ios.js +46 -68
  183. package/build-module/mobile/keyboard-aware-flat-list/index.ios.js.map +1 -1
  184. package/build-module/mobile/keyboard-aware-flat-list/use-scroll-to-element.native.js +33 -0
  185. package/build-module/mobile/keyboard-aware-flat-list/use-scroll-to-element.native.js.map +1 -0
  186. package/build-module/mobile/keyboard-aware-flat-list/{use-scroll-to-text-input.native.js → use-scroll-to-section.native.js} +21 -27
  187. package/build-module/mobile/keyboard-aware-flat-list/use-scroll-to-section.native.js.map +1 -0
  188. package/build-module/mobile/keyboard-aware-flat-list/use-scroll.native.js +86 -0
  189. package/build-module/mobile/keyboard-aware-flat-list/use-scroll.native.js.map +1 -0
  190. package/build-module/mobile/utils/get-px-from-css-unit.native.js +294 -0
  191. package/build-module/mobile/utils/get-px-from-css-unit.native.js.map +1 -0
  192. package/build-module/modal/index.js +18 -13
  193. package/build-module/modal/index.js.map +1 -1
  194. package/build-module/navigation/menu/menu-title.js +1 -1
  195. package/build-module/navigation/menu/menu-title.js.map +1 -1
  196. package/build-module/navigator/navigator-provider/component.js +3 -16
  197. package/build-module/navigator/navigator-provider/component.js.map +1 -1
  198. package/build-module/navigator/navigator-screen/component.js +16 -70
  199. package/build-module/navigator/navigator-screen/component.js.map +1 -1
  200. package/build-module/navigator/styles.js +47 -0
  201. package/build-module/navigator/styles.js.map +1 -0
  202. package/build-module/number-control/index.js +4 -8
  203. package/build-module/number-control/index.js.map +1 -1
  204. package/build-module/number-control/types.js.map +1 -1
  205. package/build-module/palette-edit/index.js +15 -51
  206. package/build-module/palette-edit/index.js.map +1 -1
  207. package/build-module/private-apis.js +10 -23
  208. package/build-module/private-apis.js.map +1 -1
  209. package/build-module/private-apis.native.js +14 -0
  210. package/build-module/private-apis.native.js.map +1 -0
  211. package/build-module/radio-control/index.js +1 -0
  212. package/build-module/radio-control/index.js.map +1 -1
  213. package/build-module/range-control/index.js +1 -1
  214. package/build-module/range-control/index.js.map +1 -1
  215. package/build-module/select-control/styles/select-control-styles.js +15 -25
  216. package/build-module/select-control/styles/select-control-styles.js.map +1 -1
  217. package/build-module/slot-fill/index.js +3 -2
  218. package/build-module/slot-fill/index.js.map +1 -1
  219. package/build-module/slot-fill/types.js.map +1 -1
  220. package/build-module/snackbar/types.js.map +1 -1
  221. package/build-module/tabs/styles.js +3 -3
  222. package/build-module/tabs/styles.js.map +1 -1
  223. package/build-module/tabs/tabpanel.js +9 -7
  224. package/build-module/tabs/tabpanel.js.map +1 -1
  225. package/build-module/toggle-group-control/toggle-group-control/component.js +4 -4
  226. package/build-module/toggle-group-control/toggle-group-control/component.js.map +1 -1
  227. package/build-module/toggle-group-control/toggle-group-control/styles.js +29 -15
  228. package/build-module/toggle-group-control/toggle-group-control/styles.js.map +1 -1
  229. package/build-module/toggle-group-control/toggle-group-control-option-base/styles.js +9 -9
  230. package/build-module/toggle-group-control/toggle-group-control-option-base/styles.js.map +1 -1
  231. package/build-module/tools-panel/tools-panel/component.js +3 -1
  232. package/build-module/tools-panel/tools-panel/component.js.map +1 -1
  233. package/build-module/tools-panel/tools-panel-header/component.js +9 -8
  234. package/build-module/tools-panel/tools-panel-header/component.js.map +1 -1
  235. package/build-module/tools-panel/types.js.map +1 -1
  236. package/build-module/tooltip/index.js +34 -12
  237. package/build-module/tooltip/index.js.map +1 -1
  238. package/build-module/tooltip/types.js.map +1 -1
  239. package/build-module/truncate/hook.js +10 -4
  240. package/build-module/truncate/hook.js.map +1 -1
  241. package/build-module/truncate/types.js.map +1 -1
  242. package/build-module/unit-control/index.js +1 -1
  243. package/build-module/unit-control/index.js.map +1 -1
  244. package/build-module/utils/strings.js +32 -2
  245. package/build-module/utils/strings.js.map +1 -1
  246. package/build-style/style-rtl.css +29 -5
  247. package/build-style/style.css +29 -5
  248. package/build-types/base-control/index.d.ts +3 -27
  249. package/build-types/base-control/index.d.ts.map +1 -1
  250. package/build-types/base-control/stories/index.story.d.ts +4 -1
  251. package/build-types/base-control/stories/index.story.d.ts.map +1 -1
  252. package/build-types/border-box-control/border-box-control/hook.d.ts +4 -4
  253. package/build-types/border-box-control/border-box-control-linked-button/hook.d.ts +6 -6
  254. package/build-types/border-box-control/border-box-control-split-controls/hook.d.ts +4 -4
  255. package/build-types/border-box-control/border-box-control-visualizer/hook.d.ts +4 -4
  256. package/build-types/border-box-control/stories/index.story.d.ts +1 -1
  257. package/build-types/border-control/border-control/hook.d.ts +4 -4
  258. package/build-types/border-control/border-control-dropdown/hook.d.ts +4 -4
  259. package/build-types/border-control/border-control-style-picker/hook.d.ts +4 -4
  260. package/build-types/border-control/stories/index.story.d.ts +6 -6
  261. package/build-types/box-control/stories/index.story.d.ts +42 -42
  262. package/build-types/box-control/styles/box-control-styles.d.ts +1 -1
  263. package/build-types/button/deprecated.d.ts +3 -3
  264. package/build-types/card/card/hook.d.ts +4 -4
  265. package/build-types/card/card-body/hook.d.ts +4 -4
  266. package/build-types/card/card-divider/hook.d.ts +4 -4
  267. package/build-types/card/card-footer/hook.d.ts +4 -4
  268. package/build-types/card/card-header/hook.d.ts +4 -4
  269. package/build-types/card/card-media/hook.d.ts +4 -4
  270. package/build-types/color-palette/styles.d.ts +2 -2
  271. package/build-types/color-picker/component.d.ts +2 -2
  272. package/build-types/color-picker/stories/index.story.d.ts +1 -1
  273. package/build-types/color-picker/stories/index.story.d.ts.map +1 -1
  274. package/build-types/color-picker/styles.d.ts +3 -3
  275. package/build-types/composite/test/index.d.ts.map +1 -0
  276. package/build-types/context/wordpress-component.d.ts +3 -3
  277. package/build-types/context/wordpress-component.d.ts.map +1 -1
  278. package/build-types/custom-select-control-v2/index.d.ts +3 -2
  279. package/build-types/custom-select-control-v2/index.d.ts.map +1 -1
  280. package/build-types/custom-select-control-v2/stories/index.story.d.ts +4 -3
  281. package/build-types/custom-select-control-v2/stories/index.story.d.ts.map +1 -1
  282. package/build-types/date-time/date/styles.d.ts +3 -3
  283. package/build-types/date-time/date-time/styles.d.ts +1 -1
  284. package/build-types/date-time/time/styles.d.ts +4 -4
  285. package/build-types/dropdown/index.d.ts +1 -1
  286. package/build-types/dropdown/index.d.ts.map +1 -1
  287. package/build-types/dropdown/stories/index.story.d.ts +3 -3
  288. package/build-types/dropdown/stories/index.story.d.ts.map +1 -1
  289. package/build-types/dropdown-menu/index.d.ts +1 -1
  290. package/build-types/dropdown-menu/index.d.ts.map +1 -1
  291. package/build-types/dropdown-menu/stories/index.story.d.ts +2 -2
  292. package/build-types/dropdown-menu/stories/index.story.d.ts.map +1 -1
  293. package/build-types/dropdown-menu-v2/index.d.ts +18 -15
  294. package/build-types/dropdown-menu-v2/index.d.ts.map +1 -1
  295. package/build-types/dropdown-menu-v2/stories/index.story.d.ts +7 -2
  296. package/build-types/dropdown-menu-v2/stories/index.story.d.ts.map +1 -1
  297. package/build-types/dropdown-menu-v2/styles.d.ts +77 -23
  298. package/build-types/dropdown-menu-v2/styles.d.ts.map +1 -1
  299. package/build-types/dropdown-menu-v2/types.d.ts +89 -173
  300. package/build-types/dropdown-menu-v2/types.d.ts.map +1 -1
  301. package/build-types/duotone-picker/duotone-picker.d.ts.map +1 -1
  302. package/build-types/elevation/hook.d.ts +4 -4
  303. package/build-types/flex/flex/hook.d.ts +4 -4
  304. package/build-types/flex/flex-block/hook.d.ts +4 -4
  305. package/build-types/flex/flex-item/hook.d.ts +4 -4
  306. package/build-types/focal-point-picker/stories/index.story.d.ts +4 -4
  307. package/build-types/focal-point-picker/styles/focal-point-picker-style.d.ts +1 -1
  308. package/build-types/font-size-picker/index.d.ts.map +1 -1
  309. package/build-types/font-size-picker/styles.d.ts +1 -1
  310. package/build-types/form-token-field/index.d.ts.map +1 -1
  311. package/build-types/form-token-field/token.d.ts.map +1 -1
  312. package/build-types/grid/hook.d.ts +4 -4
  313. package/build-types/h-stack/hook.d.ts +4 -4
  314. package/build-types/heading/component.d.ts +1 -1
  315. package/build-types/heading/hook.d.ts +4 -4
  316. package/build-types/input-control/styles/input-control-styles.d.ts +11 -0
  317. package/build-types/input-control/styles/input-control-styles.d.ts.map +1 -1
  318. package/build-types/input-control/types.d.ts +1 -1
  319. package/build-types/input-control/types.d.ts.map +1 -1
  320. package/build-types/item-group/item/hook.d.ts +4 -4
  321. package/build-types/item-group/item-group/hook.d.ts +4 -4
  322. package/build-types/lock-unlock.d.ts +3 -0
  323. package/build-types/lock-unlock.d.ts.map +1 -0
  324. package/build-types/menu-item/index.d.ts +1 -1
  325. package/build-types/menu-item/stories/index.story.d.ts +4 -4
  326. package/build-types/mobile/image/constants.d.ts +5 -0
  327. package/build-types/mobile/image/constants.d.ts.map +1 -0
  328. package/build-types/modal/index.d.ts.map +1 -1
  329. package/build-types/navigation/styles/navigation-styles.d.ts +3 -3
  330. package/build-types/navigator/navigator-back-button/hook.d.ts +6 -6
  331. package/build-types/navigator/navigator-button/hook.d.ts +6 -6
  332. package/build-types/navigator/navigator-provider/component.d.ts.map +1 -1
  333. package/build-types/navigator/navigator-screen/component.d.ts +1 -7
  334. package/build-types/navigator/navigator-screen/component.d.ts.map +1 -1
  335. package/build-types/navigator/styles.d.ts +9 -0
  336. package/build-types/navigator/styles.d.ts.map +1 -0
  337. package/build-types/number-control/index.d.ts +1 -1
  338. package/build-types/number-control/index.d.ts.map +1 -1
  339. package/build-types/number-control/stories/index.story.d.ts +1 -1
  340. package/build-types/number-control/types.d.ts +1 -1
  341. package/build-types/palette-edit/index.d.ts +3 -8
  342. package/build-types/palette-edit/index.d.ts.map +1 -1
  343. package/build-types/palette-edit/styles.d.ts +3 -3
  344. package/build-types/popover/index.d.ts +1 -1
  345. package/build-types/popover/index.d.ts.map +1 -1
  346. package/build-types/popover/stories/e2e/index.story.d.ts +1 -1
  347. package/build-types/private-apis.d.ts +0 -1
  348. package/build-types/private-apis.d.ts.map +1 -1
  349. package/build-types/radio-control/index.d.ts.map +1 -1
  350. package/build-types/range-control/index.d.ts +1 -1
  351. package/build-types/range-control/styles/range-control-styles.d.ts +1 -1
  352. package/build-types/resizable-box/index.d.ts +1 -1
  353. package/build-types/resizable-box/resize-tooltip/index.d.ts +1 -1
  354. package/build-types/resizable-box/stories/index.story.d.ts +2 -2
  355. package/build-types/scrollable/hook.d.ts +4 -4
  356. package/build-types/search-control/index.d.ts +1 -1
  357. package/build-types/search-control/stories/index.story.d.ts +2 -2
  358. package/build-types/select-control/styles/select-control-styles.d.ts.map +1 -1
  359. package/build-types/slot-fill/index.d.ts +1 -1
  360. package/build-types/slot-fill/index.d.ts.map +1 -1
  361. package/build-types/slot-fill/types.d.ts +4 -0
  362. package/build-types/slot-fill/types.d.ts.map +1 -1
  363. package/build-types/snackbar/index.d.ts +2 -2
  364. package/build-types/snackbar/stories/index.story.d.ts +0 -3
  365. package/build-types/snackbar/stories/index.story.d.ts.map +1 -1
  366. package/build-types/snackbar/types.d.ts +1 -1
  367. package/build-types/snackbar/types.d.ts.map +1 -1
  368. package/build-types/spacer/hook.d.ts +4 -4
  369. package/build-types/surface/hook.d.ts +4 -4
  370. package/build-types/tabs/styles.d.ts.map +1 -1
  371. package/build-types/tabs/tabpanel.d.ts +1 -1
  372. package/build-types/tabs/tabpanel.d.ts.map +1 -1
  373. package/build-types/text/hook.d.ts +4 -4
  374. package/build-types/text-control/index.d.ts +1 -1
  375. package/build-types/textarea-control/index.d.ts +1 -1
  376. package/build-types/toggle-control/stories/index.story.d.ts +2 -2
  377. package/build-types/toggle-group-control/toggle-group-control/as-button-group.d.ts +1 -1
  378. package/build-types/toggle-group-control/toggle-group-control/as-radio-group.d.ts +1 -1
  379. package/build-types/toggle-group-control/toggle-group-control/component.d.ts.map +1 -1
  380. package/build-types/toggle-group-control/toggle-group-control/styles.d.ts +2 -2
  381. package/build-types/toggle-group-control/toggle-group-control/styles.d.ts.map +1 -1
  382. package/build-types/toggle-group-control/toggle-group-control-option/component.d.ts +1 -1
  383. package/build-types/toggle-group-control/toggle-group-control-option-base/component.d.ts +1 -1
  384. package/build-types/toggle-group-control/toggle-group-control-option-base/component.d.ts.map +1 -1
  385. package/build-types/toggle-group-control/toggle-group-control-option-base/styles.d.ts.map +1 -1
  386. package/build-types/toggle-group-control/toggle-group-control-option-icon/component.d.ts +1 -1
  387. package/build-types/toolbar/toolbar-button/index.d.ts +3 -3
  388. package/build-types/tools-panel/tools-panel/component.d.ts.map +1 -1
  389. package/build-types/tools-panel/tools-panel/hook.d.ts +5 -4
  390. package/build-types/tools-panel/tools-panel/hook.d.ts.map +1 -1
  391. package/build-types/tools-panel/tools-panel-header/component.d.ts.map +1 -1
  392. package/build-types/tools-panel/tools-panel-header/hook.d.ts +5 -4
  393. package/build-types/tools-panel/tools-panel-header/hook.d.ts.map +1 -1
  394. package/build-types/tools-panel/tools-panel-item/hook.d.ts +4 -4
  395. package/build-types/tools-panel/types.d.ts +9 -0
  396. package/build-types/tools-panel/types.d.ts.map +1 -1
  397. package/build-types/tooltip/index.d.ts +1 -1
  398. package/build-types/tooltip/index.d.ts.map +1 -1
  399. package/build-types/tooltip/stories/index.story.d.ts +10 -1
  400. package/build-types/tooltip/stories/index.story.d.ts.map +1 -1
  401. package/build-types/tooltip/types.d.ts +3 -0
  402. package/build-types/tooltip/types.d.ts.map +1 -1
  403. package/build-types/truncate/hook.d.ts +5 -5
  404. package/build-types/truncate/hook.d.ts.map +1 -1
  405. package/build-types/truncate/types.d.ts +4 -0
  406. package/build-types/truncate/types.d.ts.map +1 -1
  407. package/build-types/unit-control/index.d.ts +1 -1
  408. package/build-types/unit-control/styles/unit-control-styles.d.ts +1 -1
  409. package/build-types/utils/strings.d.ts +14 -2
  410. package/build-types/utils/strings.d.ts.map +1 -1
  411. package/build-types/v-stack/hook.d.ts +4 -4
  412. package/build-types/v-stack/stories/index.story.d.ts +1 -1
  413. package/package.json +20 -21
  414. package/src/alignment-matrix-control/test/index.tsx +10 -16
  415. package/src/base-control/index.tsx +21 -16
  416. package/src/border-box-control/border-box-control-linked-button/component.tsx +1 -1
  417. package/src/border-control/border-control-style-picker/component.tsx +1 -1
  418. package/src/box-control/index.tsx +1 -1
  419. package/src/box-control/linked-button.tsx +1 -1
  420. package/src/button/README.md +32 -6
  421. package/src/button/index.tsx +1 -1
  422. package/src/button-group/README.md +0 -6
  423. package/src/card/card/README.md +1 -1
  424. package/src/checkbox-control/README.md +1 -9
  425. package/src/color-palette/index.native.js +18 -7
  426. package/src/color-picker/color-copy-button.tsx +1 -1
  427. package/src/combobox-control/README.md +0 -6
  428. package/src/composite/test/index.tsx +576 -0
  429. package/src/context/wordpress-component.ts +11 -6
  430. package/src/custom-select-control/README.md +0 -6
  431. package/src/custom-select-control-v2/index.tsx +13 -12
  432. package/src/date-time/date/styles.ts +3 -3
  433. package/src/dropdown-menu/README.md +0 -5
  434. package/src/dropdown-menu-v2/README.md +75 -136
  435. package/src/dropdown-menu-v2/index.tsx +321 -231
  436. package/src/dropdown-menu-v2/stories/index.story.tsx +522 -126
  437. package/src/dropdown-menu-v2/styles.ts +226 -151
  438. package/src/dropdown-menu-v2/test/index.tsx +480 -188
  439. package/src/dropdown-menu-v2/types.ts +98 -184
  440. package/src/duotone-picker/duotone-picker.tsx +7 -3
  441. package/src/font-size-picker/index.native.js +8 -2
  442. package/src/font-size-picker/index.tsx +4 -2
  443. package/src/form-toggle/README.md +0 -6
  444. package/src/form-token-field/index.tsx +11 -7
  445. package/src/form-token-field/test/index.tsx +97 -0
  446. package/src/form-token-field/token.tsx +1 -0
  447. package/src/gradient-picker/index.tsx +2 -2
  448. package/src/index.native.js +6 -1
  449. package/src/input-control/styles/input-control-styles.tsx +10 -8
  450. package/src/input-control/types.ts +1 -1
  451. package/src/lock-unlock.js +10 -0
  452. package/src/menu-group/README.md +0 -8
  453. package/src/menu-items-choice/README.md +0 -7
  454. package/src/mobile/bottom-sheet/index.native.js +15 -1
  455. package/src/mobile/color-settings/palette.screen.native.js +7 -5
  456. package/src/mobile/global-styles-context/test/fixtures/theme.native.js +0 -20
  457. package/src/mobile/global-styles-context/utils.native.js +28 -19
  458. package/src/mobile/image/constants.js +1 -0
  459. package/src/mobile/image/index.native.js +55 -18
  460. package/src/mobile/image/style.native.scss +35 -9
  461. package/src/mobile/keyboard-aware-flat-list/index.android.js +50 -5
  462. package/src/mobile/keyboard-aware-flat-list/index.ios.js +65 -91
  463. package/src/mobile/keyboard-aware-flat-list/test/{use-scroll-to-text-input.native.js → use-scroll-to-section.native.js} +27 -25
  464. package/src/mobile/keyboard-aware-flat-list/test/use-scroll.native.js +71 -0
  465. package/src/mobile/keyboard-aware-flat-list/use-scroll-to-element.native.js +41 -0
  466. package/src/mobile/keyboard-aware-flat-list/{use-scroll-to-text-input.native.js → use-scroll-to-section.native.js} +22 -27
  467. package/src/mobile/keyboard-aware-flat-list/use-scroll.native.js +100 -0
  468. package/src/mobile/utils/get-px-from-css-unit.native.js +329 -0
  469. package/src/mobile/utils/test/get-px-from-css-unit.native.js +172 -0
  470. package/src/modal/README.md +0 -6
  471. package/src/modal/index.tsx +18 -16
  472. package/src/modal/test/index.tsx +90 -1
  473. package/src/navigation/menu/menu-title.tsx +1 -1
  474. package/src/navigator/navigator-provider/component.tsx +3 -4
  475. package/src/navigator/navigator-screen/component.tsx +15 -93
  476. package/src/navigator/styles.ts +71 -0
  477. package/src/navigator/test/index.tsx +0 -64
  478. package/src/notice/README.md +0 -6
  479. package/src/number-control/README.md +2 -2
  480. package/src/number-control/index.tsx +4 -8
  481. package/src/number-control/types.ts +1 -1
  482. package/src/palette-edit/index.tsx +15 -58
  483. package/src/palette-edit/test/index.tsx +1 -75
  484. package/src/panel/README.md +0 -6
  485. package/src/private-apis.native.js +13 -0
  486. package/src/private-apis.ts +12 -37
  487. package/src/radio-control/README.md +0 -6
  488. package/src/radio-control/index.tsx +4 -1
  489. package/src/radio-control/style.scss +29 -2
  490. package/src/radio-group/README.md +0 -6
  491. package/src/range-control/README.md +1 -9
  492. package/src/range-control/index.tsx +1 -1
  493. package/src/search-control/README.md +0 -6
  494. package/src/select-control/README.md +0 -6
  495. package/src/select-control/styles/select-control-styles.ts +10 -28
  496. package/src/slot-fill/index.tsx +5 -2
  497. package/src/slot-fill/types.ts +5 -0
  498. package/src/snackbar/README.md +0 -6
  499. package/src/snackbar/stories/index.story.tsx +7 -5
  500. package/src/snackbar/style.scss +4 -3
  501. package/src/snackbar/types.ts +2 -1
  502. package/src/spacer/README.md +0 -2
  503. package/src/tab-panel/README.md +0 -5
  504. package/src/tab-panel/test/index.tsx +39 -56
  505. package/src/tabs/styles.ts +7 -1
  506. package/src/tabs/tabpanel.tsx +7 -6
  507. package/src/tabs/test/index.tsx +56 -0
  508. package/src/text-control/README.md +0 -6
  509. package/src/textarea-control/README.md +0 -6
  510. package/src/toggle-group-control/test/__snapshots__/index.tsx.snap +12 -16
  511. package/src/toggle-group-control/test/index.tsx +58 -45
  512. package/src/toggle-group-control/toggle-group-control/component.tsx +5 -4
  513. package/src/toggle-group-control/toggle-group-control/styles.ts +13 -19
  514. package/src/toggle-group-control/toggle-group-control-option/README.md +1 -1
  515. package/src/toggle-group-control/toggle-group-control-option-base/README.md +1 -1
  516. package/src/toggle-group-control/toggle-group-control-option-base/styles.ts +3 -2
  517. package/src/toggle-group-control/toggle-group-control-option-icon/README.md +1 -1
  518. package/src/toolbar/toolbar/README.md +0 -6
  519. package/src/tools-panel/test/index.tsx +12 -20
  520. package/src/tools-panel/tools-panel/README.md +7 -0
  521. package/src/tools-panel/tools-panel/component.tsx +2 -0
  522. package/src/tools-panel/tools-panel-header/README.md +7 -0
  523. package/src/tools-panel/tools-panel-header/component.tsx +20 -13
  524. package/src/tools-panel/types.ts +9 -0
  525. package/src/tooltip/README.md +4 -0
  526. package/src/tooltip/index.tsx +48 -10
  527. package/src/tooltip/stories/index.story.tsx +18 -1
  528. package/src/tooltip/test/index.tsx +404 -254
  529. package/src/tooltip/types.ts +4 -0
  530. package/src/tree-grid/README.md +0 -4
  531. package/src/truncate/README.md +8 -0
  532. package/src/truncate/hook.ts +17 -10
  533. package/src/truncate/test/index.tsx +54 -27
  534. package/src/truncate/types.ts +4 -0
  535. package/src/unit-control/index.tsx +1 -1
  536. package/src/utils/strings.ts +30 -2
  537. package/src/utils/test/strings.js +96 -1
  538. package/tsconfig.tsbuildinfo +1 -1
  539. package/build/dropdown-menu-v2-ariakit/index.js +0 -256
  540. package/build/dropdown-menu-v2-ariakit/index.js.map +0 -1
  541. package/build/dropdown-menu-v2-ariakit/styles.js +0 -180
  542. package/build/dropdown-menu-v2-ariakit/styles.js.map +0 -1
  543. package/build/dropdown-menu-v2-ariakit/types.js +0 -6
  544. package/build/dropdown-menu-v2-ariakit/types.js.map +0 -1
  545. package/build/mobile/keyboard-aware-flat-list/use-scroll-to-text-input.native.js.map +0 -1
  546. package/build-module/dropdown-menu-v2-ariakit/index.js +0 -237
  547. package/build-module/dropdown-menu-v2-ariakit/index.js.map +0 -1
  548. package/build-module/dropdown-menu-v2-ariakit/styles.js +0 -165
  549. package/build-module/dropdown-menu-v2-ariakit/styles.js.map +0 -1
  550. package/build-module/dropdown-menu-v2-ariakit/types.js +0 -2
  551. package/build-module/dropdown-menu-v2-ariakit/types.js.map +0 -1
  552. package/build-module/mobile/keyboard-aware-flat-list/use-scroll-to-text-input.native.js.map +0 -1
  553. package/build-types/dropdown-menu-v2-ariakit/index.d.ts +0 -20
  554. package/build-types/dropdown-menu-v2-ariakit/index.d.ts.map +0 -1
  555. package/build-types/dropdown-menu-v2-ariakit/stories/index.story.d.ts +0 -16
  556. package/build-types/dropdown-menu-v2-ariakit/stories/index.story.d.ts.map +0 -1
  557. package/build-types/dropdown-menu-v2-ariakit/styles.d.ts +0 -96
  558. package/build-types/dropdown-menu-v2-ariakit/styles.d.ts.map +0 -1
  559. package/build-types/dropdown-menu-v2-ariakit/test/index.d.ts.map +0 -1
  560. package/build-types/dropdown-menu-v2-ariakit/types.d.ts +0 -168
  561. package/build-types/dropdown-menu-v2-ariakit/types.d.ts.map +0 -1
  562. package/src/dropdown-menu-v2-ariakit/README.md +0 -331
  563. package/src/dropdown-menu-v2-ariakit/index.tsx +0 -383
  564. package/src/dropdown-menu-v2-ariakit/stories/index.story.tsx +0 -617
  565. package/src/dropdown-menu-v2-ariakit/styles.ts +0 -345
  566. package/src/dropdown-menu-v2-ariakit/test/index.tsx +0 -1108
  567. package/src/dropdown-menu-v2-ariakit/types.ts +0 -179
  568. /package/build-types/{dropdown-menu-v2-ariakit → composite}/test/index.d.ts +0 -0
@@ -2,10 +2,7 @@
2
2
  * External dependencies
3
3
  */
4
4
  import { render, screen, waitFor } from '@testing-library/react';
5
- import {
6
- default as userEvent,
7
- PointerEventsCheckLevel,
8
- } from '@testing-library/user-event';
5
+ import { press, click, hover, type } from '@ariakit/test';
9
6
 
10
7
  /**
11
8
  * WordPress dependencies
@@ -19,12 +16,9 @@ import {
19
16
  DropdownMenu,
20
17
  DropdownMenuCheckboxItem,
21
18
  DropdownMenuItem,
22
- DropdownMenuLabel,
23
- DropdownMenuRadioGroup,
24
19
  DropdownMenuRadioItem,
25
20
  DropdownMenuSeparator,
26
- DropdownSubMenu,
27
- DropdownSubMenuTrigger,
21
+ DropdownMenuGroup,
28
22
  } from '..';
29
23
 
30
24
  const delay = ( delayInMs: number ) => {
@@ -34,23 +28,18 @@ const delay = ( delayInMs: number ) => {
34
28
  describe( 'DropdownMenu', () => {
35
29
  // See https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/
36
30
  it( 'should follow the WAI-ARIA spec', async () => {
37
- // Radio and Checkbox items'
38
- const user = userEvent.setup();
39
-
40
31
  render(
41
32
  <DropdownMenu trigger={ <button>Open dropdown</button> }>
42
33
  <DropdownMenuItem>Dropdown menu item</DropdownMenuItem>
43
34
  <DropdownMenuSeparator />
44
- <DropdownSubMenu
35
+ <DropdownMenu
45
36
  trigger={
46
- <DropdownSubMenuTrigger>
47
- Dropdown submenu
48
- </DropdownSubMenuTrigger>
37
+ <DropdownMenuItem>Dropdown submenu</DropdownMenuItem>
49
38
  }
50
39
  >
51
40
  <DropdownMenuItem>Dropdown submenu item 1</DropdownMenuItem>
52
41
  <DropdownMenuItem>Dropdown submenu item 2</DropdownMenuItem>
53
- </DropdownSubMenu>
42
+ </DropdownMenu>
54
43
  </DropdownMenu>
55
44
  );
56
45
 
@@ -61,11 +50,13 @@ describe( 'DropdownMenu', () => {
61
50
  expect( toggleButton ).toHaveAttribute( 'aria-haspopup', 'menu' );
62
51
  expect( toggleButton ).toHaveAttribute( 'aria-expanded', 'false' );
63
52
 
64
- await user.click( toggleButton );
53
+ await click( toggleButton );
65
54
 
66
55
  expect( toggleButton ).toHaveAttribute( 'aria-expanded', 'true' );
67
56
 
68
- expect( screen.getByRole( 'menu' ) ).toHaveFocus();
57
+ expect(
58
+ screen.getByRole( 'menu', { name: toggleButton.textContent ?? '' } )
59
+ ).toHaveFocus();
69
60
  expect( screen.getByRole( 'separator' ) ).toHaveAttribute(
70
61
  'aria-orientation',
71
62
  'horizontal'
@@ -78,11 +69,15 @@ describe( 'DropdownMenu', () => {
78
69
  expect( submenuTrigger ).toHaveAttribute( 'aria-haspopup', 'menu' );
79
70
  expect( submenuTrigger ).toHaveAttribute( 'aria-expanded', 'false' );
80
71
 
81
- await user.hover( submenuTrigger );
72
+ await hover( submenuTrigger );
82
73
 
83
74
  // Wait for the open animation after hovering
84
75
  await waitFor( () =>
85
- expect( screen.getAllByRole( 'menu' ) ).toHaveLength( 2 )
76
+ expect(
77
+ screen.getByRole( 'menu', {
78
+ name: submenuTrigger.textContent ?? '',
79
+ } )
80
+ ).toBeVisible()
86
81
  );
87
82
 
88
83
  expect( submenuTrigger ).toHaveAttribute( 'aria-expanded', 'true' );
@@ -93,9 +88,7 @@ describe( 'DropdownMenu', () => {
93
88
  } );
94
89
 
95
90
  describe( 'pointer and keyboard interactions', () => {
96
- it( 'should open when clicking the trigger', async () => {
97
- const user = userEvent.setup();
98
-
91
+ it( 'should open and focus the menu when clicking the trigger', async () => {
99
92
  render(
100
93
  <DropdownMenu trigger={ <button>Open dropdown</button> }>
101
94
  <DropdownMenuItem>Dropdown menu item</DropdownMenuItem>
@@ -106,24 +99,52 @@ describe( 'DropdownMenu', () => {
106
99
  name: 'Open dropdown',
107
100
  } );
108
101
 
109
- // DropdownMenu closed, the content is not displayed
102
+ // DropdownMenu closed
110
103
  expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
111
- expect( screen.queryByRole( 'menuitem' ) ).not.toBeInTheDocument();
112
104
 
113
105
  // Click to open the menu
114
- await user.click( toggleButton );
106
+ await click( toggleButton );
115
107
 
116
- // DropdownMenu open, the content is displayed
117
- expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
118
- expect( screen.getByRole( 'menuitem' ) ).toBeInTheDocument();
108
+ // DropdownMenu open, focus is on the menu wrapper
109
+ expect( screen.getByRole( 'menu' ) ).toHaveFocus();
119
110
  } );
120
111
 
121
- it( 'should open when pressing the arrow down key on the trigger', async () => {
122
- const user = userEvent.setup();
112
+ it( 'should open and focus the first item when pressing the arrow down key on the trigger', async () => {
113
+ render(
114
+ <DropdownMenu trigger={ <button>Open dropdown</button> }>
115
+ <DropdownMenuItem disabled>First item</DropdownMenuItem>
116
+ <DropdownMenuItem>Second item</DropdownMenuItem>
117
+ <DropdownMenuItem>Third item</DropdownMenuItem>
118
+ </DropdownMenu>
119
+ );
120
+
121
+ const toggleButton = screen.getByRole( 'button', {
122
+ name: 'Open dropdown',
123
+ } );
124
+
125
+ // Move focus on the toggle
126
+ await press.Tab();
127
+
128
+ expect( toggleButton ).toHaveFocus();
129
+
130
+ // DropdownMenu closed
131
+ expect( screen.queryByRole( 'menuitem' ) ).not.toBeInTheDocument();
132
+
133
+ await press.ArrowDown();
134
+
135
+ // DropdownMenu open, focus is on the first focusable item
136
+ // (disabled items are still focusable and accessible)
137
+ expect(
138
+ screen.getByRole( 'menuitem', { name: 'First item' } )
139
+ ).toHaveFocus();
140
+ } );
123
141
 
142
+ it( 'should open and focus the first item when pressing the space key on the trigger', async () => {
124
143
  render(
125
144
  <DropdownMenu trigger={ <button>Open dropdown</button> }>
126
- <DropdownMenuItem>Dropdown menu item</DropdownMenuItem>
145
+ <DropdownMenuItem disabled>First item</DropdownMenuItem>
146
+ <DropdownMenuItem>Second item</DropdownMenuItem>
147
+ <DropdownMenuItem>Third item</DropdownMenuItem>
127
148
  </DropdownMenu>
128
149
  );
129
150
 
@@ -132,50 +153,52 @@ describe( 'DropdownMenu', () => {
132
153
  } );
133
154
 
134
155
  // Move focus on the toggle
135
- await user.keyboard( '{Tab}' );
156
+ await press.Tab();
136
157
 
137
158
  expect( toggleButton ).toHaveFocus();
138
159
 
139
- // DropdownMenu closed, the content is not displayed
160
+ // DropdownMenu closed
140
161
  expect( screen.queryByRole( 'menuitem' ) ).not.toBeInTheDocument();
141
162
 
142
- await user.keyboard( '{ArrowDown}' );
163
+ await press.Space();
143
164
 
144
- // DropdownMenu open, the content is displayed
145
- expect( screen.getByRole( 'menuitem' ) ).toBeInTheDocument();
165
+ // DropdownMenu open, focus is on the first focusable item
166
+ // (disabled items are still focusable and accessible
167
+ expect(
168
+ screen.getByRole( 'menuitem', { name: 'First item' } )
169
+ ).toHaveFocus();
146
170
  } );
147
171
 
148
172
  it( 'should close when pressing the escape key', async () => {
149
- const user = userEvent.setup();
150
-
151
173
  render(
152
- <DropdownMenu
153
- defaultOpen
154
- trigger={ <button>Open dropdown</button> }
155
- >
174
+ <DropdownMenu trigger={ <button>Open dropdown</button> }>
156
175
  <DropdownMenuItem>Dropdown menu item</DropdownMenuItem>
157
176
  </DropdownMenu>
158
177
  );
159
178
 
160
- // The menu is focused automatically when `defaultOpen` is set.
179
+ const trigger = screen.getByRole( 'button', {
180
+ name: 'Open dropdown',
181
+ } );
182
+
183
+ await click( trigger );
184
+
185
+ // Focuses menu on mouse click, focuses first item on keyboard press
186
+ // Can be changed with a custom useEffect
161
187
  expect( screen.getByRole( 'menu' ) ).toHaveFocus();
162
188
 
163
189
  // Pressing esc will close the menu and move focus to the toggle
164
- await user.keyboard( '{Escape}' );
190
+ await press.Escape();
165
191
 
166
192
  expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
167
- expect(
168
- screen.getByRole( 'button', { name: 'Open dropdown' } )
169
- ).toHaveFocus();
193
+
194
+ await waitFor( () =>
195
+ expect(
196
+ screen.getByRole( 'button', { name: 'Open dropdown' } )
197
+ ).toHaveFocus()
198
+ );
170
199
  } );
171
200
 
172
201
  it( 'should close when clicking outside of the content', async () => {
173
- const user = userEvent.setup( {
174
- // Disabling this check otherwise testing-library would complain
175
- // when clicking on document.body to close the dropdown menu.
176
- pointerEventsCheck: PointerEventsCheckLevel.Never,
177
- } );
178
-
179
202
  render(
180
203
  <DropdownMenu
181
204
  defaultOpen
@@ -188,14 +211,12 @@ describe( 'DropdownMenu', () => {
188
211
  expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
189
212
 
190
213
  // Click on the body (ie. outside of the dropdown menu)
191
- await user.click( document.body );
214
+ await click( document.body );
192
215
 
193
216
  expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
194
217
  } );
195
218
 
196
219
  it( 'should close when clicking on a menu item', async () => {
197
- const user = userEvent.setup();
198
-
199
220
  render(
200
221
  <DropdownMenu
201
222
  defaultOpen
@@ -208,18 +229,32 @@ describe( 'DropdownMenu', () => {
208
229
  expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
209
230
 
210
231
  // Clicking a menu item will close the menu
211
- await user.click( screen.getByRole( 'menuitem' ) );
232
+ await click( screen.getByRole( 'menuitem' ) );
212
233
 
213
234
  expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
214
235
  } );
215
236
 
216
- it( 'should not close when clicking on a disabled menu item', async () => {
217
- const user = userEvent.setup( {
218
- // Disabling this check otherwise testing-library would complain
219
- // when clicking on a disabled element with pointer-events: none
220
- pointerEventsCheck: PointerEventsCheckLevel.Never,
221
- } );
237
+ it( 'should not close when clicking on a menu item when the `hideOnClick` prop is set to `false`', async () => {
238
+ render(
239
+ <DropdownMenu
240
+ defaultOpen
241
+ trigger={ <button>Open dropdown</button> }
242
+ >
243
+ <DropdownMenuItem hideOnClick={ false }>
244
+ Dropdown menu item
245
+ </DropdownMenuItem>
246
+ </DropdownMenu>
247
+ );
248
+
249
+ expect( screen.getByRole( 'menu' ) ).toBeVisible();
250
+
251
+ // Clicking a menu item will close the menu
252
+ await click( screen.getByRole( 'menuitem' ) );
253
+
254
+ expect( screen.getByRole( 'menu' ) ).toBeVisible();
255
+ } );
222
256
 
257
+ it( 'should not close when clicking on a disabled menu item', async () => {
223
258
  render(
224
259
  <DropdownMenu
225
260
  defaultOpen
@@ -234,14 +269,12 @@ describe( 'DropdownMenu', () => {
234
269
  expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
235
270
 
236
271
  // Clicking a disabled menu item won't close the menu
237
- await user.click( screen.getByRole( 'menuitem' ) );
272
+ await click( screen.getByRole( 'menuitem' ) );
238
273
 
239
274
  expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
240
275
  } );
241
276
 
242
277
  it( 'should reveal submenu content when hovering over the submenu trigger', async () => {
243
- const user = userEvent.setup();
244
-
245
278
  render(
246
279
  <DropdownMenu
247
280
  defaultOpen
@@ -249,11 +282,11 @@ describe( 'DropdownMenu', () => {
249
282
  >
250
283
  <DropdownMenuItem>Dropdown menu item 1</DropdownMenuItem>
251
284
  <DropdownMenuItem>Dropdown menu item 2</DropdownMenuItem>
252
- <DropdownSubMenu
285
+ <DropdownMenu
253
286
  trigger={
254
- <DropdownSubMenuTrigger>
287
+ <DropdownMenuItem>
255
288
  Dropdown submenu
256
- </DropdownSubMenuTrigger>
289
+ </DropdownMenuItem>
257
290
  }
258
291
  >
259
292
  <DropdownMenuItem>
@@ -262,7 +295,7 @@ describe( 'DropdownMenu', () => {
262
295
  <DropdownMenuItem>
263
296
  Dropdown submenu item 2
264
297
  </DropdownMenuItem>
265
- </DropdownSubMenu>
298
+ </DropdownMenu>
266
299
  <DropdownMenuItem>Dropdown menu item 3</DropdownMenuItem>
267
300
  </DropdownMenu>
268
301
  );
@@ -274,7 +307,7 @@ describe( 'DropdownMenu', () => {
274
307
  } )
275
308
  ).not.toBeInTheDocument();
276
309
 
277
- await user.hover(
310
+ await hover(
278
311
  screen.getByRole( 'menuitem', { name: 'Dropdown submenu' } )
279
312
  );
280
313
 
@@ -287,8 +320,6 @@ describe( 'DropdownMenu', () => {
287
320
  } );
288
321
 
289
322
  it( 'should navigate menu items and subitems using the arrow, spacebar and enter keys', async () => {
290
- const user = userEvent.setup();
291
-
292
323
  render(
293
324
  <DropdownMenu
294
325
  defaultOpen
@@ -296,11 +327,11 @@ describe( 'DropdownMenu', () => {
296
327
  >
297
328
  <DropdownMenuItem>Dropdown menu item 1</DropdownMenuItem>
298
329
  <DropdownMenuItem>Dropdown menu item 2</DropdownMenuItem>
299
- <DropdownSubMenu
330
+ <DropdownMenu
300
331
  trigger={
301
- <DropdownSubMenuTrigger>
332
+ <DropdownMenuItem>
302
333
  Dropdown submenu
303
- </DropdownSubMenuTrigger>
334
+ </DropdownMenuItem>
304
335
  }
305
336
  >
306
337
  <DropdownMenuItem>
@@ -309,67 +340,69 @@ describe( 'DropdownMenu', () => {
309
340
  <DropdownMenuItem>
310
341
  Dropdown submenu item 2
311
342
  </DropdownMenuItem>
312
- </DropdownSubMenu>
343
+ </DropdownMenu>
313
344
  <DropdownMenuItem>Dropdown menu item 3</DropdownMenuItem>
314
345
  </DropdownMenu>
315
346
  );
316
347
 
317
348
  // The menu is focused automatically when `defaultOpen` is set.
318
- expect( screen.getByRole( 'menu' ) ).toHaveFocus();
349
+ await waitFor( () =>
350
+ expect( screen.getByRole( 'menu' ) ).toHaveFocus()
351
+ );
319
352
 
320
353
  // Arrow up/down selects menu items
321
354
  // The selection wraps around from last to first and viceversa
322
- await user.keyboard( '{ArrowDown}' );
355
+ await press.ArrowDown();
323
356
  expect(
324
357
  screen.getByRole( 'menuitem', { name: 'Dropdown menu item 1' } )
325
358
  ).toHaveFocus();
326
359
 
327
- await user.keyboard( '{ArrowDown}' );
360
+ await press.ArrowDown();
328
361
  expect(
329
362
  screen.getByRole( 'menuitem', { name: 'Dropdown menu item 2' } )
330
363
  ).toHaveFocus();
331
364
 
332
- await user.keyboard( '{ArrowDown}' );
365
+ await press.ArrowDown();
333
366
  expect(
334
367
  screen.getByRole( 'menuitem', { name: 'Dropdown submenu' } )
335
368
  ).toHaveFocus();
336
369
 
337
- await user.keyboard( '{ArrowDown}' );
370
+ await press.ArrowDown();
338
371
  expect(
339
372
  screen.getByRole( 'menuitem', { name: 'Dropdown menu item 3' } )
340
373
  ).toHaveFocus();
341
374
 
342
- await user.keyboard( '{ArrowDown}' );
375
+ await press.ArrowDown();
343
376
  expect(
344
377
  screen.getByRole( 'menuitem', { name: 'Dropdown menu item 1' } )
345
378
  ).toHaveFocus();
346
379
 
347
- await user.keyboard( '{ArrowUp}' );
380
+ await press.ArrowUp();
348
381
  expect(
349
382
  screen.getByRole( 'menuitem', { name: 'Dropdown menu item 3' } )
350
383
  ).toHaveFocus();
351
384
 
352
- await user.keyboard( '{ArrowUp}' );
385
+ await press.ArrowUp();
353
386
  expect(
354
387
  screen.getByRole( 'menuitem', { name: 'Dropdown submenu' } )
355
388
  ).toHaveFocus();
356
389
 
357
390
  // Arrow right/left can be used to enter/leave submenus
358
- await user.keyboard( '{ArrowRight}' );
391
+ await press.ArrowRight();
359
392
  expect(
360
393
  screen.getByRole( 'menuitem', {
361
394
  name: 'Dropdown submenu item 1',
362
395
  } )
363
396
  ).toHaveFocus();
364
397
 
365
- await user.keyboard( '{ArrowDown}' );
398
+ await press.ArrowDown();
366
399
  expect(
367
400
  screen.getByRole( 'menuitem', {
368
401
  name: 'Dropdown submenu item 2',
369
402
  } )
370
403
  ).toHaveFocus();
371
404
 
372
- await user.keyboard( '{ArrowLeft}' );
405
+ await press.ArrowLeft();
373
406
  expect(
374
407
  screen.getByRole( 'menuitem', {
375
408
  name: 'Dropdown submenu',
@@ -377,28 +410,28 @@ describe( 'DropdownMenu', () => {
377
410
  ).toHaveFocus();
378
411
 
379
412
  // Spacebar or enter key can also be used to enter a submenu
380
- await user.keyboard( '{Enter}' );
413
+ await press.Enter();
381
414
  expect(
382
415
  screen.getByRole( 'menuitem', {
383
416
  name: 'Dropdown submenu item 1',
384
417
  } )
385
418
  ).toHaveFocus();
386
419
 
387
- await user.keyboard( '{ArrowLeft}' );
420
+ await press.ArrowLeft();
388
421
  expect(
389
422
  screen.getByRole( 'menuitem', {
390
423
  name: 'Dropdown submenu',
391
424
  } )
392
425
  ).toHaveFocus();
393
426
 
394
- await user.keyboard( '{Spacebar}' );
427
+ await press.Space();
395
428
  expect(
396
429
  screen.getByRole( 'menuitem', {
397
430
  name: 'Dropdown submenu item 1',
398
431
  } )
399
432
  ).toHaveFocus();
400
433
 
401
- await user.keyboard( '{ArrowLeft}' );
434
+ await press.ArrowLeft();
402
435
  expect(
403
436
  screen.getByRole( 'menuitem', {
404
437
  name: 'Dropdown submenu',
@@ -406,32 +439,37 @@ describe( 'DropdownMenu', () => {
406
439
  ).toHaveFocus();
407
440
  } );
408
441
 
409
- it( 'should check menu radio items', async () => {
410
- const user = userEvent.setup();
411
-
442
+ it( 'should check radio items and keep the menu open when clicking (controlled)', async () => {
412
443
  const onRadioValueChangeSpy = jest.fn();
413
444
 
414
445
  const ControlledRadioGroup = () => {
415
- const [ radioValue, setRadioValue ] = useState< string >();
446
+ const [ radioValue, setRadioValue ] = useState( 'two' );
447
+ const onRadioChange: React.ComponentProps<
448
+ typeof DropdownMenuRadioItem
449
+ >[ 'onChange' ] = ( e ) => {
450
+ onRadioValueChangeSpy( e.target.value );
451
+ setRadioValue( e.target.value );
452
+ };
416
453
  return (
417
454
  <DropdownMenu trigger={ <button>Open dropdown</button> }>
418
- <DropdownMenuRadioGroup
419
- value={ radioValue }
420
- onValueChange={ ( value ) => {
421
- onRadioValueChangeSpy( value );
422
- setRadioValue( value );
423
- } }
424
- >
425
- <DropdownMenuLabel>
426
- Radio group label
427
- </DropdownMenuLabel>
428
- <DropdownMenuRadioItem value="radio-one">
455
+ <DropdownMenuGroup>
456
+ <DropdownMenuRadioItem
457
+ name="radio-test"
458
+ value="radio-one"
459
+ checked={ radioValue === 'radio-one' }
460
+ onChange={ onRadioChange }
461
+ >
429
462
  Radio item one
430
463
  </DropdownMenuRadioItem>
431
- <DropdownMenuRadioItem value="radio-two">
464
+ <DropdownMenuRadioItem
465
+ name="radio-test"
466
+ value="radio-two"
467
+ checked={ radioValue === 'radio-two' }
468
+ onChange={ onRadioChange }
469
+ >
432
470
  Radio item two
433
471
  </DropdownMenuRadioItem>
434
- </DropdownMenuRadioGroup>
472
+ </DropdownMenuGroup>
435
473
  </DropdownMenu>
436
474
  );
437
475
  };
@@ -439,7 +477,7 @@ describe( 'DropdownMenu', () => {
439
477
  render( <ControlledRadioGroup /> );
440
478
 
441
479
  // Open dropdown
442
- await user.click(
480
+ await click(
443
481
  screen.getByRole( 'button', { name: 'Open dropdown' } )
444
482
  );
445
483
 
@@ -453,7 +491,7 @@ describe( 'DropdownMenu', () => {
453
491
  ).not.toBeChecked();
454
492
 
455
493
  // Click first radio item, make sure that the callback fires
456
- await user.click(
494
+ await click(
457
495
  screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
458
496
  );
459
497
  expect( onRadioValueChangeSpy ).toHaveBeenCalledTimes( 1 );
@@ -461,11 +499,6 @@ describe( 'DropdownMenu', () => {
461
499
  'radio-one'
462
500
  );
463
501
 
464
- // Open dropdown
465
- await user.click(
466
- screen.getByRole( 'button', { name: 'Open dropdown' } )
467
- );
468
-
469
502
  // Make sure that first radio is checked
470
503
  expect(
471
504
  screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
@@ -475,7 +508,7 @@ describe( 'DropdownMenu', () => {
475
508
  ).not.toBeChecked();
476
509
 
477
510
  // Click second radio item, make sure that the callback fires
478
- await user.click(
511
+ await click(
479
512
  screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
480
513
  );
481
514
  expect( onRadioValueChangeSpy ).toHaveBeenCalledTimes( 2 );
@@ -483,11 +516,83 @@ describe( 'DropdownMenu', () => {
483
516
  'radio-two'
484
517
  );
485
518
 
519
+ // Make sure that second radio is selected
520
+ expect(
521
+ screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
522
+ ).not.toBeChecked();
523
+ expect(
524
+ screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
525
+ ).toBeChecked();
526
+ } );
527
+
528
+ it( 'should check radio items and keep the menu open when clicking (uncontrolled)', async () => {
529
+ const onRadioValueChangeSpy = jest.fn();
530
+ render(
531
+ <DropdownMenu trigger={ <button>Open dropdown</button> }>
532
+ <DropdownMenuGroup>
533
+ <DropdownMenuRadioItem
534
+ name="radio-test"
535
+ value="radio-one"
536
+ onChange={ ( e ) =>
537
+ onRadioValueChangeSpy( e.target.value )
538
+ }
539
+ >
540
+ Radio item one
541
+ </DropdownMenuRadioItem>
542
+ <DropdownMenuRadioItem
543
+ name="radio-test"
544
+ value="radio-two"
545
+ defaultChecked
546
+ onChange={ ( e ) =>
547
+ onRadioValueChangeSpy( e.target.value )
548
+ }
549
+ >
550
+ Radio item two
551
+ </DropdownMenuRadioItem>
552
+ </DropdownMenuGroup>
553
+ </DropdownMenu>
554
+ );
555
+
486
556
  // Open dropdown
487
- await user.click(
557
+ await click(
488
558
  screen.getByRole( 'button', { name: 'Open dropdown' } )
489
559
  );
490
560
 
561
+ // Radio item two should be checked (`defaultChecked` prop)
562
+ expect( screen.getAllByRole( 'menuitemradio' ) ).toHaveLength( 2 );
563
+ expect(
564
+ screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
565
+ ).not.toBeChecked();
566
+ expect(
567
+ screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
568
+ ).toBeChecked();
569
+
570
+ // Click first radio item, make sure that the callback fires
571
+ await click(
572
+ screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
573
+ );
574
+ expect( onRadioValueChangeSpy ).toHaveBeenCalledTimes( 1 );
575
+ expect( onRadioValueChangeSpy ).toHaveBeenLastCalledWith(
576
+ 'radio-one'
577
+ );
578
+
579
+ // Make sure that first radio is checked
580
+ expect(
581
+ screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
582
+ ).toBeChecked();
583
+ expect(
584
+ screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
585
+ ).not.toBeChecked();
586
+
587
+ // Click second radio item, make sure that the callback fires
588
+ await click(
589
+ screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
590
+ );
591
+ expect( onRadioValueChangeSpy ).toHaveBeenCalledTimes( 2 );
592
+ expect( onRadioValueChangeSpy ).toHaveBeenLastCalledWith(
593
+ 'radio-two'
594
+ );
595
+
491
596
  // Make sure that second radio is selected
492
597
  expect(
493
598
  screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
@@ -497,9 +602,7 @@ describe( 'DropdownMenu', () => {
497
602
  ).toBeChecked();
498
603
  } );
499
604
 
500
- it( 'should check menu checkbox items', async () => {
501
- const user = userEvent.setup();
502
-
605
+ it( 'should check checkbox items and keep the menu open when clicking (controlled)', async () => {
503
606
  const onCheckboxValueChangeSpy = jest.fn();
504
607
 
505
608
  const ControlledRadioGroup = () => {
@@ -507,26 +610,36 @@ describe( 'DropdownMenu', () => {
507
610
  useState< boolean >();
508
611
  const [ itemTwoChecked, setItemTwoChecked ] =
509
612
  useState< boolean >();
613
+
510
614
  return (
511
615
  <DropdownMenu trigger={ <button>Open dropdown</button> }>
512
- <DropdownMenuLabel>
513
- Checkbox group label
514
- </DropdownMenuLabel>
515
616
  <DropdownMenuCheckboxItem
617
+ name="item-one"
618
+ value="item-one-value"
516
619
  checked={ itemOneChecked }
517
- onCheckedChange={ ( checked ) => {
518
- setItemOneChecked( checked );
519
- onCheckboxValueChangeSpy( 'item-one', checked );
620
+ onChange={ ( e ) => {
621
+ onCheckboxValueChangeSpy(
622
+ e.target.name,
623
+ e.target.value,
624
+ e.target.checked
625
+ );
626
+ setItemOneChecked( e.target.checked );
520
627
  } }
521
628
  >
522
629
  Checkbox item one
523
630
  </DropdownMenuCheckboxItem>
524
631
 
525
632
  <DropdownMenuCheckboxItem
633
+ name="item-two"
634
+ value="item-two-value"
526
635
  checked={ itemTwoChecked }
527
- onCheckedChange={ ( checked ) => {
528
- setItemTwoChecked( checked );
529
- onCheckboxValueChangeSpy( 'item-two', checked );
636
+ onChange={ ( e ) => {
637
+ onCheckboxValueChangeSpy(
638
+ e.target.name,
639
+ e.target.value,
640
+ e.target.checked
641
+ );
642
+ setItemTwoChecked( e.target.checked );
530
643
  } }
531
644
  >
532
645
  Checkbox item two
@@ -538,7 +651,7 @@ describe( 'DropdownMenu', () => {
538
651
  render( <ControlledRadioGroup /> );
539
652
 
540
653
  // Open dropdown
541
- await user.click(
654
+ await click(
542
655
  screen.getByRole( 'button', { name: 'Open dropdown' } )
543
656
  );
544
657
 
@@ -558,7 +671,7 @@ describe( 'DropdownMenu', () => {
558
671
  ).not.toBeChecked();
559
672
 
560
673
  // Click first checkbox item, make sure that the callback fires
561
- await user.click(
674
+ await click(
562
675
  screen.getByRole( 'menuitemcheckbox', {
563
676
  name: 'Checkbox item one',
564
677
  } )
@@ -566,14 +679,10 @@ describe( 'DropdownMenu', () => {
566
679
  expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 1 );
567
680
  expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
568
681
  'item-one',
682
+ 'item-one-value',
569
683
  true
570
684
  );
571
685
 
572
- // Open dropdown
573
- await user.click(
574
- screen.getByRole( 'button', { name: 'Open dropdown' } )
575
- );
576
-
577
686
  // Make sure that first checkbox is checked
578
687
  expect(
579
688
  screen.getByRole( 'menuitemcheckbox', {
@@ -582,7 +691,7 @@ describe( 'DropdownMenu', () => {
582
691
  ).toBeChecked();
583
692
 
584
693
  // Click second checkbox item, make sure that the callback fires
585
- await user.click(
694
+ await click(
586
695
  screen.getByRole( 'menuitemcheckbox', {
587
696
  name: 'Checkbox item two',
588
697
  } )
@@ -590,14 +699,10 @@ describe( 'DropdownMenu', () => {
590
699
  expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 2 );
591
700
  expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
592
701
  'item-two',
702
+ 'item-two-value',
593
703
  true
594
704
  );
595
705
 
596
- // Open dropdown
597
- await user.click(
598
- screen.getByRole( 'button', { name: 'Open dropdown' } )
599
- );
600
-
601
706
  // Make sure that second checkbox is selected
602
707
  expect(
603
708
  screen.getByRole( 'menuitemcheckbox', {
@@ -606,7 +711,7 @@ describe( 'DropdownMenu', () => {
606
711
  ).toBeChecked();
607
712
 
608
713
  // Click second checkbox item, make sure that the callback fires
609
- await user.click(
714
+ await click(
610
715
  screen.getByRole( 'menuitemcheckbox', {
611
716
  name: 'Checkbox item two',
612
717
  } )
@@ -614,27 +719,203 @@ describe( 'DropdownMenu', () => {
614
719
  expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 3 );
615
720
  expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
616
721
  'item-two',
722
+ 'item-two-value',
617
723
  false
618
724
  );
619
725
 
726
+ // Make sure that second checkbox is unselected
727
+ expect(
728
+ screen.getByRole( 'menuitemcheckbox', {
729
+ name: 'Checkbox item two',
730
+ } )
731
+ ).not.toBeChecked();
732
+ } );
733
+
734
+ it( 'should check checkbox items and keep the menu open when clicking (uncontrolled)', async () => {
735
+ const onCheckboxValueChangeSpy = jest.fn();
736
+
737
+ render(
738
+ <DropdownMenu trigger={ <button>Open dropdown</button> }>
739
+ <DropdownMenuCheckboxItem
740
+ name="item-one"
741
+ value="item-one-value"
742
+ onChange={ ( e ) => {
743
+ onCheckboxValueChangeSpy(
744
+ e.target.name,
745
+ e.target.value,
746
+ e.target.checked
747
+ );
748
+ } }
749
+ >
750
+ Checkbox item one
751
+ </DropdownMenuCheckboxItem>
752
+
753
+ <DropdownMenuCheckboxItem
754
+ name="item-two"
755
+ value="item-two-value"
756
+ defaultChecked
757
+ onChange={ ( e ) => {
758
+ onCheckboxValueChangeSpy(
759
+ e.target.name,
760
+ e.target.value,
761
+ e.target.checked
762
+ );
763
+ } }
764
+ >
765
+ Checkbox item two
766
+ </DropdownMenuCheckboxItem>
767
+ </DropdownMenu>
768
+ );
769
+
620
770
  // Open dropdown
621
- await user.click(
771
+ await click(
622
772
  screen.getByRole( 'button', { name: 'Open dropdown' } )
623
773
  );
624
774
 
625
- // Make sure that second checkbox is unselected
775
+ // Checkbox item two should be checked (`defaultChecked`)
776
+ expect( screen.getAllByRole( 'menuitemcheckbox' ) ).toHaveLength(
777
+ 2
778
+ );
779
+ expect(
780
+ screen.getByRole( 'menuitemcheckbox', {
781
+ name: 'Checkbox item one',
782
+ } )
783
+ ).not.toBeChecked();
784
+ expect(
785
+ screen.getByRole( 'menuitemcheckbox', {
786
+ name: 'Checkbox item two',
787
+ } )
788
+ ).toBeChecked();
789
+
790
+ // Click first checkbox item, make sure that the callback fires
791
+ await click(
792
+ screen.getByRole( 'menuitemcheckbox', {
793
+ name: 'Checkbox item one',
794
+ } )
795
+ );
796
+ expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 1 );
797
+ expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
798
+ 'item-one',
799
+ 'item-one-value',
800
+ true
801
+ );
802
+
803
+ // Make sure that first checkbox is checked
804
+ expect(
805
+ screen.getByRole( 'menuitemcheckbox', {
806
+ name: 'Checkbox item one',
807
+ } )
808
+ ).toBeChecked();
809
+
810
+ // Click second checkbox item, make sure that the callback fires
811
+ await click(
812
+ screen.getByRole( 'menuitemcheckbox', {
813
+ name: 'Checkbox item two',
814
+ } )
815
+ );
816
+ expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 2 );
817
+ expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
818
+ 'item-two',
819
+ 'item-two-value',
820
+ false
821
+ );
822
+
823
+ // Make sure that second checkbox is unchecked
626
824
  expect(
627
825
  screen.getByRole( 'menuitemcheckbox', {
628
826
  name: 'Checkbox item two',
629
827
  } )
630
828
  ).not.toBeChecked();
829
+
830
+ // Click second checkbox item, make sure that the callback fires
831
+ await click(
832
+ screen.getByRole( 'menuitemcheckbox', {
833
+ name: 'Checkbox item two',
834
+ } )
835
+ );
836
+ expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 3 );
837
+ expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
838
+ 'item-two',
839
+ 'item-two-value',
840
+ true
841
+ );
842
+
843
+ // Make sure that second checkbox is unselected
844
+ expect(
845
+ screen.getByRole( 'menuitemcheckbox', {
846
+ name: 'Checkbox item two',
847
+ } )
848
+ ).toBeChecked();
849
+ } );
850
+ } );
851
+
852
+ describe( 'modality', () => {
853
+ it( 'should be modal by default', async () => {
854
+ render(
855
+ <>
856
+ <DropdownMenu trigger={ <button>Open dropdown</button> }>
857
+ <DropdownMenuItem>Dropdown menu item</DropdownMenuItem>
858
+ </DropdownMenu>
859
+ <button>Button outside of dropdown</button>
860
+ </>
861
+ );
862
+
863
+ // Click to open the menu
864
+ await click(
865
+ screen.getByRole( 'button', {
866
+ name: 'Open dropdown',
867
+ } )
868
+ );
869
+
870
+ // DropdownMenu open, focus is on the menu wrapper
871
+ expect( screen.getByRole( 'menu' ) ).toHaveFocus();
872
+
873
+ expect(
874
+ screen.queryByRole( 'button', {
875
+ name: 'Button outside of dropdown',
876
+ } )
877
+ ).not.toBeInTheDocument();
878
+ } );
879
+
880
+ it( 'should not be modal when the `modal` prop is set to `false`', async () => {
881
+ render(
882
+ <>
883
+ <DropdownMenu
884
+ trigger={ <button>Open dropdown</button> }
885
+ modal={ false }
886
+ >
887
+ <DropdownMenuItem>Dropdown menu item</DropdownMenuItem>
888
+ </DropdownMenu>
889
+ <button>Button outside of dropdown</button>
890
+ </>
891
+ );
892
+
893
+ // Click to open the menu
894
+ await click(
895
+ screen.getByRole( 'button', {
896
+ name: 'Open dropdown',
897
+ } )
898
+ );
899
+
900
+ // DropdownMenu open, focus is on the menu wrapper
901
+ expect( screen.getByRole( 'menu' ) ).toHaveFocus();
902
+
903
+ // DropdownMenu is not modal, therefore the outer button is part of the
904
+ // accessibility tree and can be found.
905
+ const outerButton = screen.getByRole( 'button', {
906
+ name: 'Button outside of dropdown',
907
+ } );
908
+
909
+ // The outer button can be focused by pressing tab. Doing so will cause
910
+ // the DropdownMenu to close.
911
+ await press.Tab();
912
+ expect( outerButton ).toBeInTheDocument();
913
+ expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
631
914
  } );
632
915
  } );
633
916
 
634
917
  describe( 'items prefix and suffix', () => {
635
918
  it( 'should display a prefix on regular items', async () => {
636
- const user = userEvent.setup();
637
-
638
919
  render(
639
920
  <DropdownMenu trigger={ <button>Open dropdown</button> }>
640
921
  <DropdownMenuItem prefix={ <>Item prefix</> }>
@@ -644,7 +925,7 @@ describe( 'DropdownMenu', () => {
644
925
  );
645
926
 
646
927
  // Click to open the menu
647
- await user.click(
928
+ await click(
648
929
  screen.getByRole( 'button', {
649
930
  name: 'Open dropdown',
650
931
  } )
@@ -659,8 +940,6 @@ describe( 'DropdownMenu', () => {
659
940
  } );
660
941
 
661
942
  it( 'should display a suffix on regular items', async () => {
662
- const user = userEvent.setup();
663
-
664
943
  render(
665
944
  <DropdownMenu trigger={ <button>Open dropdown</button> }>
666
945
  <DropdownMenuItem suffix={ <>Item suffix</> }>
@@ -670,7 +949,7 @@ describe( 'DropdownMenu', () => {
670
949
  );
671
950
 
672
951
  // Click to open the menu
673
- await user.click(
952
+ await click(
674
953
  screen.getByRole( 'button', {
675
954
  name: 'Open dropdown',
676
955
  } )
@@ -685,23 +964,20 @@ describe( 'DropdownMenu', () => {
685
964
  } );
686
965
 
687
966
  it( 'should display a suffix on radio items', async () => {
688
- const user = userEvent.setup();
689
-
690
967
  render(
691
968
  <DropdownMenu trigger={ <button>Open dropdown</button> }>
692
- <DropdownMenuRadioGroup>
693
- <DropdownMenuRadioItem
694
- value="radio-one"
695
- suffix="Radio suffix"
696
- >
697
- Radio item one
698
- </DropdownMenuRadioItem>
699
- </DropdownMenuRadioGroup>
969
+ <DropdownMenuRadioItem
970
+ name="radio-test"
971
+ value="radio-one"
972
+ suffix="Radio suffix"
973
+ >
974
+ Radio item one
975
+ </DropdownMenuRadioItem>
700
976
  </DropdownMenu>
701
977
  );
702
978
 
703
979
  // Click to open the menu
704
- await user.click(
980
+ await click(
705
981
  screen.getByRole( 'button', {
706
982
  name: 'Open dropdown',
707
983
  } )
@@ -716,18 +992,20 @@ describe( 'DropdownMenu', () => {
716
992
  } );
717
993
 
718
994
  it( 'should display a suffix on checkbox items', async () => {
719
- const user = userEvent.setup();
720
-
721
995
  render(
722
996
  <DropdownMenu trigger={ <button>Open dropdown</button> }>
723
- <DropdownMenuCheckboxItem suffix={ 'Checkbox suffix' }>
997
+ <DropdownMenuCheckboxItem
998
+ name="checkbox-test"
999
+ value="checkbox-one"
1000
+ suffix="Checkbox suffix"
1001
+ >
724
1002
  Checkbox item one
725
1003
  </DropdownMenuCheckboxItem>
726
1004
  </DropdownMenu>
727
1005
  );
728
1006
 
729
1007
  // Click to open the menu
730
- await user.click(
1008
+ await click(
731
1009
  screen.getByRole( 'button', {
732
1010
  name: 'Open dropdown',
733
1011
  } )
@@ -744,8 +1022,6 @@ describe( 'DropdownMenu', () => {
744
1022
 
745
1023
  describe( 'typeahead', () => {
746
1024
  it( 'should highlight matching item', async () => {
747
- const user = userEvent.setup();
748
-
749
1025
  render(
750
1026
  <DropdownMenu trigger={ <button>Open dropdown</button> }>
751
1027
  <DropdownMenuItem>One</DropdownMenuItem>
@@ -754,60 +1030,76 @@ describe( 'DropdownMenu', () => {
754
1030
  );
755
1031
 
756
1032
  // Click to open the menu
757
- await user.click(
1033
+ await click(
758
1034
  screen.getByRole( 'button', {
759
1035
  name: 'Open dropdown',
760
1036
  } )
761
1037
  );
762
- expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
1038
+ expect( screen.getByRole( 'menu' ) ).toHaveFocus();
763
1039
 
764
1040
  // Type "tw", it should match and focus the item with content "Two"
765
- await user.keyboard( 'tw' );
1041
+ await type( 'tw' );
766
1042
  expect(
767
1043
  screen.getByRole( 'menuitem', { name: 'Two' } )
768
1044
  ).toHaveFocus();
769
1045
 
770
1046
  // Wait for the typeahead timer to reset and interpret
771
1047
  // the next keystrokes as a new search
772
- await delay( 1000 );
1048
+ await delay( 500 );
773
1049
 
774
1050
  // Type "on", it should match and focus the item with content "One"
775
- await user.keyboard( 'on' );
1051
+ await type( 'on' );
776
1052
  expect(
777
1053
  screen.getByRole( 'menuitem', { name: 'One' } )
778
1054
  ).toHaveFocus();
779
1055
  } );
780
1056
 
781
- it( 'should use the textValue prop if specificied', async () => {
782
- const user = userEvent.setup();
783
-
1057
+ it( 'should keep previous focus when no matches are found', async () => {
784
1058
  render(
785
1059
  <DropdownMenu trigger={ <button>Open dropdown</button> }>
786
1060
  <DropdownMenuItem>One</DropdownMenuItem>
787
- <DropdownMenuItem textValue="Four">Two</DropdownMenuItem>
1061
+ <DropdownMenuItem>Two</DropdownMenuItem>
788
1062
  </DropdownMenu>
789
1063
  );
790
1064
 
791
1065
  // Click to open the menu
792
- await user.click(
1066
+ await click(
793
1067
  screen.getByRole( 'button', {
794
1068
  name: 'Open dropdown',
795
1069
  } )
796
1070
  );
797
- expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
1071
+ expect( screen.getByRole( 'menu' ) ).toHaveFocus();
798
1072
 
799
- // Type "tw", it should not match the item with content "Two" because it
800
- // that item specifies the "textValue" prop. Therefore, the menu container
801
- // retains focus.
802
- await user.keyboard( 'tw' );
1073
+ // Type a string that doesn't match any items. Focus shouldn't move.
1074
+ await type( 'abc' );
803
1075
  expect( screen.getByRole( 'menu' ) ).toHaveFocus();
804
1076
 
805
1077
  // Wait for the typeahead timer to reset and interpret
806
1078
  // the next keystrokes as a new search
807
- await delay( 1000 );
1079
+ await delay( 500 );
1080
+
1081
+ // Type "on", it should match and focus the item with content "One"
1082
+ await type( 'on' );
1083
+ expect(
1084
+ screen.getByRole( 'menuitem', { name: 'One' } )
1085
+ ).toHaveFocus();
1086
+
1087
+ // Wait for the typeahead timer to reset and interpret
1088
+ // the next keystrokes as a new search
1089
+ await delay( 500 );
1090
+
1091
+ // Type a string that doesn't match any items. Focus shouldn't move.
1092
+ await type( 'abc' );
1093
+ expect(
1094
+ screen.getByRole( 'menuitem', { name: 'One' } )
1095
+ ).toHaveFocus();
808
1096
 
809
- // Type "fo", it should match and focus the item with textValue "Four"
810
- await user.keyboard( 'fo' );
1097
+ // Wait for the typeahead timer to reset and interpret
1098
+ // the next keystrokes as a new search
1099
+ await delay( 500 );
1100
+
1101
+ // Type "tw", it should match and focus the item with content "Two"
1102
+ await type( 'tw' );
811
1103
  expect(
812
1104
  screen.getByRole( 'menuitem', { name: 'Two' } )
813
1105
  ).toHaveFocus();