@furystack/shades-common-components 12.4.0 → 12.6.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 (297) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/esm/components/app-bar-link.spec.js +16 -19
  3. package/esm/components/app-bar-link.spec.js.map +1 -1
  4. package/esm/components/app-bar.spec.js +6 -4
  5. package/esm/components/app-bar.spec.js.map +1 -1
  6. package/esm/components/avatar.spec.js +9 -9
  7. package/esm/components/avatar.spec.js.map +1 -1
  8. package/esm/components/breadcrumb.spec.js +2 -2
  9. package/esm/components/breadcrumb.spec.js.map +1 -1
  10. package/esm/components/button-group.d.ts +32 -0
  11. package/esm/components/button-group.d.ts.map +1 -1
  12. package/esm/components/button-group.js +26 -2
  13. package/esm/components/button-group.js.map +1 -1
  14. package/esm/components/button-group.spec.js +127 -11
  15. package/esm/components/button-group.spec.js.map +1 -1
  16. package/esm/components/button.spec.js +4 -4
  17. package/esm/components/button.spec.js.map +1 -1
  18. package/esm/components/cache-view.spec.js +2 -3
  19. package/esm/components/cache-view.spec.js.map +1 -1
  20. package/esm/components/carousel.spec.js +47 -47
  21. package/esm/components/carousel.spec.js.map +1 -1
  22. package/esm/components/circular-progress.spec.js +2 -2
  23. package/esm/components/command-palette/command-palette-input.spec.js +23 -19
  24. package/esm/components/command-palette/command-palette-input.spec.js.map +1 -1
  25. package/esm/components/command-palette/command-palette-suggestion-list.spec.js +27 -27
  26. package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -1
  27. package/esm/components/command-palette/index.spec.js +64 -51
  28. package/esm/components/command-palette/index.spec.js.map +1 -1
  29. package/esm/components/context-menu/context-menu.spec.js +33 -33
  30. package/esm/components/context-menu/context-menu.spec.js.map +1 -1
  31. package/esm/components/data-grid/body.spec.js +13 -13
  32. package/esm/components/data-grid/body.spec.js.map +1 -1
  33. package/esm/components/data-grid/data-grid-row.spec.js +8 -8
  34. package/esm/components/data-grid/data-grid-row.spec.js.map +1 -1
  35. package/esm/components/data-grid/data-grid.d.ts +47 -3
  36. package/esm/components/data-grid/data-grid.d.ts.map +1 -1
  37. package/esm/components/data-grid/data-grid.js +8 -11
  38. package/esm/components/data-grid/data-grid.js.map +1 -1
  39. package/esm/components/data-grid/data-grid.spec.js +71 -28
  40. package/esm/components/data-grid/data-grid.spec.js.map +1 -1
  41. package/esm/components/data-grid/filters/boolean-filter.d.ts +12 -0
  42. package/esm/components/data-grid/filters/boolean-filter.d.ts.map +1 -0
  43. package/esm/components/data-grid/filters/boolean-filter.js +27 -0
  44. package/esm/components/data-grid/filters/boolean-filter.js.map +1 -0
  45. package/esm/components/data-grid/filters/boolean-filter.spec.d.ts +2 -0
  46. package/esm/components/data-grid/filters/boolean-filter.spec.d.ts.map +1 -0
  47. package/esm/components/data-grid/filters/boolean-filter.spec.js +114 -0
  48. package/esm/components/data-grid/filters/boolean-filter.spec.js.map +1 -0
  49. package/esm/components/data-grid/filters/date-filter.d.ts +12 -0
  50. package/esm/components/data-grid/filters/date-filter.d.ts.map +1 -0
  51. package/esm/components/data-grid/filters/date-filter.js +109 -0
  52. package/esm/components/data-grid/filters/date-filter.js.map +1 -0
  53. package/esm/components/data-grid/filters/date-filter.spec.d.ts +2 -0
  54. package/esm/components/data-grid/filters/date-filter.spec.d.ts.map +1 -0
  55. package/esm/components/data-grid/filters/date-filter.spec.js +145 -0
  56. package/esm/components/data-grid/filters/date-filter.spec.js.map +1 -0
  57. package/esm/components/data-grid/filters/enum-filter.d.ts +16 -0
  58. package/esm/components/data-grid/filters/enum-filter.d.ts.map +1 -0
  59. package/esm/components/data-grid/filters/enum-filter.js +72 -0
  60. package/esm/components/data-grid/filters/enum-filter.js.map +1 -0
  61. package/esm/components/data-grid/filters/enum-filter.spec.d.ts +2 -0
  62. package/esm/components/data-grid/filters/enum-filter.spec.d.ts.map +1 -0
  63. package/esm/components/data-grid/filters/enum-filter.spec.js +136 -0
  64. package/esm/components/data-grid/filters/enum-filter.spec.js.map +1 -0
  65. package/esm/components/data-grid/filters/filter-dropdown.d.ts +6 -0
  66. package/esm/components/data-grid/filters/filter-dropdown.d.ts.map +1 -0
  67. package/esm/components/data-grid/filters/filter-dropdown.js +41 -0
  68. package/esm/components/data-grid/filters/filter-dropdown.js.map +1 -0
  69. package/esm/components/data-grid/filters/filter-dropdown.spec.d.ts +2 -0
  70. package/esm/components/data-grid/filters/filter-dropdown.spec.d.ts.map +1 -0
  71. package/esm/components/data-grid/filters/filter-dropdown.spec.js +69 -0
  72. package/esm/components/data-grid/filters/filter-dropdown.spec.js.map +1 -0
  73. package/esm/components/data-grid/filters/filter-styles.d.ts +24 -0
  74. package/esm/components/data-grid/filters/filter-styles.d.ts.map +1 -0
  75. package/esm/components/data-grid/filters/filter-styles.js +25 -0
  76. package/esm/components/data-grid/filters/filter-styles.js.map +1 -0
  77. package/esm/components/data-grid/filters/index.d.ts +7 -0
  78. package/esm/components/data-grid/filters/index.d.ts.map +1 -0
  79. package/esm/components/data-grid/filters/index.js +7 -0
  80. package/esm/components/data-grid/filters/index.js.map +1 -0
  81. package/esm/components/data-grid/filters/number-filter.d.ts +12 -0
  82. package/esm/components/data-grid/filters/number-filter.d.ts.map +1 -0
  83. package/esm/components/data-grid/filters/number-filter.js +65 -0
  84. package/esm/components/data-grid/filters/number-filter.js.map +1 -0
  85. package/esm/components/data-grid/filters/number-filter.spec.d.ts +2 -0
  86. package/esm/components/data-grid/filters/number-filter.spec.d.ts.map +1 -0
  87. package/esm/components/data-grid/filters/number-filter.spec.js +142 -0
  88. package/esm/components/data-grid/filters/number-filter.spec.js.map +1 -0
  89. package/esm/components/data-grid/filters/string-filter.d.ts +12 -0
  90. package/esm/components/data-grid/filters/string-filter.d.ts.map +1 -0
  91. package/esm/components/data-grid/filters/string-filter.js +63 -0
  92. package/esm/components/data-grid/filters/string-filter.js.map +1 -0
  93. package/esm/components/data-grid/filters/string-filter.spec.d.ts +2 -0
  94. package/esm/components/data-grid/filters/string-filter.spec.d.ts.map +1 -0
  95. package/esm/components/data-grid/filters/string-filter.spec.js +128 -0
  96. package/esm/components/data-grid/filters/string-filter.spec.js.map +1 -0
  97. package/esm/components/data-grid/footer.d.ts +1 -0
  98. package/esm/components/data-grid/footer.d.ts.map +1 -1
  99. package/esm/components/data-grid/footer.js +24 -16
  100. package/esm/components/data-grid/footer.js.map +1 -1
  101. package/esm/components/data-grid/footer.spec.js +111 -71
  102. package/esm/components/data-grid/footer.spec.js.map +1 -1
  103. package/esm/components/data-grid/header.d.ts +6 -9
  104. package/esm/components/data-grid/header.d.ts.map +1 -1
  105. package/esm/components/data-grid/header.js +51 -117
  106. package/esm/components/data-grid/header.js.map +1 -1
  107. package/esm/components/data-grid/header.spec.js +116 -187
  108. package/esm/components/data-grid/header.spec.js.map +1 -1
  109. package/esm/components/data-grid/index.d.ts +1 -0
  110. package/esm/components/data-grid/index.d.ts.map +1 -1
  111. package/esm/components/data-grid/index.js +1 -0
  112. package/esm/components/data-grid/index.js.map +1 -1
  113. package/esm/components/data-grid/selection-cell.spec.js +8 -8
  114. package/esm/components/data-grid/selection-cell.spec.js.map +1 -1
  115. package/esm/components/drawer/drawer-toggle-button.spec.js +22 -22
  116. package/esm/components/drawer/drawer-toggle-button.spec.js.map +1 -1
  117. package/esm/components/drawer/index.spec.js +36 -36
  118. package/esm/components/drawer/index.spec.js.map +1 -1
  119. package/esm/components/dropdown.spec.js +38 -30
  120. package/esm/components/dropdown.spec.js.map +1 -1
  121. package/esm/components/fab.spec.js +4 -4
  122. package/esm/components/fab.spec.js.map +1 -1
  123. package/esm/components/form.spec.js +37 -37
  124. package/esm/components/form.spec.js.map +1 -1
  125. package/esm/components/grid.d.ts +3 -0
  126. package/esm/components/grid.d.ts.map +1 -1
  127. package/esm/components/grid.js +3 -0
  128. package/esm/components/grid.js.map +1 -1
  129. package/esm/components/grid.spec.js +3 -3
  130. package/esm/components/grid.spec.js.map +1 -1
  131. package/esm/components/image.spec.js +55 -52
  132. package/esm/components/image.spec.js.map +1 -1
  133. package/esm/components/inputs/autocomplete.d.ts +3 -0
  134. package/esm/components/inputs/autocomplete.d.ts.map +1 -1
  135. package/esm/components/inputs/autocomplete.js +3 -0
  136. package/esm/components/inputs/autocomplete.js.map +1 -1
  137. package/esm/components/inputs/autocomplete.spec.js +7 -14
  138. package/esm/components/inputs/autocomplete.spec.js.map +1 -1
  139. package/esm/components/inputs/checkbox.spec.js +22 -22
  140. package/esm/components/inputs/checkbox.spec.js.map +1 -1
  141. package/esm/components/inputs/input-number.spec.js +47 -47
  142. package/esm/components/inputs/input-number.spec.js.map +1 -1
  143. package/esm/components/inputs/input.spec.js +53 -53
  144. package/esm/components/inputs/input.spec.js.map +1 -1
  145. package/esm/components/inputs/radio-group.spec.js +14 -14
  146. package/esm/components/inputs/radio-group.spec.js.map +1 -1
  147. package/esm/components/inputs/radio.spec.js +16 -16
  148. package/esm/components/inputs/radio.spec.js.map +1 -1
  149. package/esm/components/inputs/select.spec.js +74 -74
  150. package/esm/components/inputs/select.spec.js.map +1 -1
  151. package/esm/components/inputs/slider.spec.js +16 -16
  152. package/esm/components/inputs/slider.spec.js.map +1 -1
  153. package/esm/components/inputs/switch.spec.js +24 -24
  154. package/esm/components/inputs/switch.spec.js.map +1 -1
  155. package/esm/components/inputs/text-area.spec.js +17 -17
  156. package/esm/components/inputs/text-area.spec.js.map +1 -1
  157. package/esm/components/linear-progress.spec.js +2 -2
  158. package/esm/components/list/list.d.ts +10 -0
  159. package/esm/components/list/list.d.ts.map +1 -1
  160. package/esm/components/list/list.js +23 -2
  161. package/esm/components/list/list.js.map +1 -1
  162. package/esm/components/list/list.spec.js +137 -36
  163. package/esm/components/list/list.spec.js.map +1 -1
  164. package/esm/components/markdown/markdown-display.spec.js +15 -15
  165. package/esm/components/markdown/markdown-display.spec.js.map +1 -1
  166. package/esm/components/markdown/markdown-editor.spec.js +8 -8
  167. package/esm/components/markdown/markdown-editor.spec.js.map +1 -1
  168. package/esm/components/markdown/markdown-input.d.ts +14 -0
  169. package/esm/components/markdown/markdown-input.d.ts.map +1 -1
  170. package/esm/components/markdown/markdown-input.js +48 -2
  171. package/esm/components/markdown/markdown-input.js.map +1 -1
  172. package/esm/components/markdown/markdown-input.spec.js +114 -17
  173. package/esm/components/markdown/markdown-input.spec.js.map +1 -1
  174. package/esm/components/menu/menu.spec.js +28 -28
  175. package/esm/components/menu/menu.spec.js.map +1 -1
  176. package/esm/components/modal.spec.js +15 -18
  177. package/esm/components/modal.spec.js.map +1 -1
  178. package/esm/components/noty-list.spec.js +25 -23
  179. package/esm/components/noty-list.spec.js.map +1 -1
  180. package/esm/components/page-container/index.spec.js +16 -16
  181. package/esm/components/page-container/index.spec.js.map +1 -1
  182. package/esm/components/page-container/page-header.spec.js +16 -16
  183. package/esm/components/page-container/page-header.spec.js.map +1 -1
  184. package/esm/components/page-layout/index.spec.js +29 -29
  185. package/esm/components/page-layout/index.spec.js.map +1 -1
  186. package/esm/components/paper.spec.js +3 -3
  187. package/esm/components/paper.spec.js.map +1 -1
  188. package/esm/components/rating.spec.js +61 -61
  189. package/esm/components/rating.spec.js.map +1 -1
  190. package/esm/components/skeleton.spec.js +10 -6
  191. package/esm/components/skeleton.spec.js.map +1 -1
  192. package/esm/components/suggest/index.d.ts +10 -2
  193. package/esm/components/suggest/index.d.ts.map +1 -1
  194. package/esm/components/suggest/index.js +21 -1
  195. package/esm/components/suggest/index.js.map +1 -1
  196. package/esm/components/suggest/index.spec.js +50 -0
  197. package/esm/components/suggest/index.spec.js.map +1 -1
  198. package/esm/components/suggest/suggest-input.spec.js +4 -10
  199. package/esm/components/suggest/suggest-input.spec.js.map +1 -1
  200. package/esm/components/tabs.spec.js +30 -30
  201. package/esm/components/tabs.spec.js.map +1 -1
  202. package/esm/components/tree/tree.spec.js +27 -27
  203. package/esm/components/tree/tree.spec.js.map +1 -1
  204. package/esm/components/typography.spec.js +3 -3
  205. package/esm/components/typography.spec.js.map +1 -1
  206. package/esm/components/wizard/index.d.ts +8 -0
  207. package/esm/components/wizard/index.d.ts.map +1 -1
  208. package/esm/components/wizard/index.js +90 -0
  209. package/esm/components/wizard/index.js.map +1 -1
  210. package/esm/components/wizard/index.spec.js +84 -7
  211. package/esm/components/wizard/index.spec.js.map +1 -1
  212. package/esm/utils/promisify-animation.d.ts.map +1 -1
  213. package/esm/utils/promisify-animation.js +3 -0
  214. package/esm/utils/promisify-animation.js.map +1 -1
  215. package/package.json +3 -3
  216. package/src/components/app-bar-link.spec.tsx +16 -19
  217. package/src/components/app-bar.spec.tsx +6 -4
  218. package/src/components/avatar.spec.tsx +9 -9
  219. package/src/components/breadcrumb.spec.tsx +2 -2
  220. package/src/components/button-group.spec.tsx +155 -11
  221. package/src/components/button-group.tsx +49 -2
  222. package/src/components/button.spec.tsx +4 -4
  223. package/src/components/cache-view.spec.tsx +3 -3
  224. package/src/components/carousel.spec.tsx +47 -47
  225. package/src/components/circular-progress.spec.tsx +2 -2
  226. package/src/components/command-palette/command-palette-input.spec.tsx +23 -19
  227. package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +27 -27
  228. package/src/components/command-palette/index.spec.tsx +64 -51
  229. package/src/components/context-menu/context-menu.spec.tsx +33 -33
  230. package/src/components/data-grid/body.spec.tsx +13 -13
  231. package/src/components/data-grid/data-grid-row.spec.tsx +8 -8
  232. package/src/components/data-grid/data-grid.spec.tsx +106 -28
  233. package/src/components/data-grid/data-grid.tsx +57 -13
  234. package/src/components/data-grid/filters/boolean-filter.spec.tsx +142 -0
  235. package/src/components/data-grid/filters/boolean-filter.tsx +45 -0
  236. package/src/components/data-grid/filters/date-filter.spec.tsx +181 -0
  237. package/src/components/data-grid/filters/date-filter.tsx +162 -0
  238. package/src/components/data-grid/filters/enum-filter.spec.tsx +168 -0
  239. package/src/components/data-grid/filters/enum-filter.tsx +119 -0
  240. package/src/components/data-grid/filters/filter-dropdown.spec.tsx +89 -0
  241. package/src/components/data-grid/filters/filter-dropdown.tsx +60 -0
  242. package/src/components/data-grid/filters/filter-styles.ts +26 -0
  243. package/src/components/data-grid/filters/index.ts +6 -0
  244. package/src/components/data-grid/filters/number-filter.spec.tsx +174 -0
  245. package/src/components/data-grid/filters/number-filter.tsx +115 -0
  246. package/src/components/data-grid/filters/string-filter.spec.tsx +157 -0
  247. package/src/components/data-grid/filters/string-filter.tsx +112 -0
  248. package/src/components/data-grid/footer.spec.tsx +130 -74
  249. package/src/components/data-grid/footer.tsx +41 -34
  250. package/src/components/data-grid/header.spec.tsx +128 -212
  251. package/src/components/data-grid/header.tsx +95 -183
  252. package/src/components/data-grid/index.tsx +1 -0
  253. package/src/components/data-grid/selection-cell.spec.tsx +8 -8
  254. package/src/components/drawer/drawer-toggle-button.spec.tsx +22 -22
  255. package/src/components/drawer/index.spec.tsx +36 -36
  256. package/src/components/dropdown.spec.tsx +38 -30
  257. package/src/components/fab.spec.tsx +4 -4
  258. package/src/components/form.spec.tsx +37 -37
  259. package/src/components/grid.spec.tsx +3 -3
  260. package/src/components/grid.tsx +3 -0
  261. package/src/components/image.spec.tsx +55 -52
  262. package/src/components/inputs/autocomplete.spec.tsx +7 -14
  263. package/src/components/inputs/autocomplete.tsx +3 -0
  264. package/src/components/inputs/checkbox.spec.tsx +22 -22
  265. package/src/components/inputs/input-number.spec.tsx +47 -47
  266. package/src/components/inputs/input.spec.tsx +53 -53
  267. package/src/components/inputs/radio-group.spec.tsx +14 -14
  268. package/src/components/inputs/radio.spec.tsx +16 -16
  269. package/src/components/inputs/select.spec.tsx +74 -74
  270. package/src/components/inputs/slider.spec.tsx +16 -16
  271. package/src/components/inputs/switch.spec.tsx +24 -24
  272. package/src/components/inputs/text-area.spec.tsx +17 -17
  273. package/src/components/linear-progress.spec.tsx +2 -2
  274. package/src/components/list/list.spec.tsx +209 -36
  275. package/src/components/list/list.tsx +56 -19
  276. package/src/components/markdown/markdown-display.spec.tsx +15 -15
  277. package/src/components/markdown/markdown-editor.spec.tsx +8 -8
  278. package/src/components/markdown/markdown-input.spec.tsx +159 -17
  279. package/src/components/markdown/markdown-input.tsx +65 -1
  280. package/src/components/menu/menu.spec.tsx +28 -28
  281. package/src/components/modal.spec.tsx +15 -18
  282. package/src/components/noty-list.spec.tsx +25 -23
  283. package/src/components/page-container/index.spec.tsx +16 -16
  284. package/src/components/page-container/page-header.spec.tsx +16 -16
  285. package/src/components/page-layout/index.spec.tsx +29 -29
  286. package/src/components/paper.spec.tsx +3 -3
  287. package/src/components/rating.spec.tsx +61 -61
  288. package/src/components/skeleton.spec.tsx +10 -6
  289. package/src/components/suggest/index.spec.tsx +83 -0
  290. package/src/components/suggest/index.tsx +36 -3
  291. package/src/components/suggest/suggest-input.spec.tsx +4 -10
  292. package/src/components/tabs.spec.tsx +30 -30
  293. package/src/components/tree/tree.spec.tsx +27 -27
  294. package/src/components/typography.spec.tsx +3 -3
  295. package/src/components/wizard/index.spec.tsx +123 -6
  296. package/src/components/wizard/index.tsx +125 -0
  297. package/src/utils/promisify-animation.ts +3 -0
@@ -1,6 +1,6 @@
1
1
  import { Injector } from '@furystack/inject'
2
- import { createComponent, initializeShadeRoot } from '@furystack/shades'
3
- import { sleepAsync, usingAsync } from '@furystack/utils'
2
+ import { createComponent, flushUpdates, initializeShadeRoot } from '@furystack/shades'
3
+ import { usingAsync } from '@furystack/utils'
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
5
  import { MarkdownEditor } from './markdown-editor.js'
6
6
 
@@ -24,7 +24,7 @@ describe('MarkdownEditor', () => {
24
24
  jsxElement: <MarkdownEditor value="# Hello" />,
25
25
  })
26
26
 
27
- await sleepAsync(50)
27
+ await flushUpdates()
28
28
 
29
29
  const el = document.querySelector('shade-markdown-editor')
30
30
  expect(el).not.toBeNull()
@@ -42,7 +42,7 @@ describe('MarkdownEditor', () => {
42
42
  jsxElement: <MarkdownEditor value="# Hello" />,
43
43
  })
44
44
 
45
- await sleepAsync(50)
45
+ await flushUpdates()
46
46
 
47
47
  const split = document.querySelector('.md-editor-split') as HTMLElement
48
48
  expect(split).not.toBeNull()
@@ -68,7 +68,7 @@ describe('MarkdownEditor', () => {
68
68
  jsxElement: <MarkdownEditor value="# Hello" layout="above-below" />,
69
69
  })
70
70
 
71
- await sleepAsync(50)
71
+ await flushUpdates()
72
72
 
73
73
  const split = document.querySelector('.md-editor-split') as HTMLElement
74
74
  expect(split).not.toBeNull()
@@ -88,7 +88,7 @@ describe('MarkdownEditor', () => {
88
88
  jsxElement: <MarkdownEditor value="# Hello" layout="tabs" />,
89
89
  })
90
90
 
91
- await sleepAsync(50)
91
+ await flushUpdates()
92
92
 
93
93
  const tabs = document.querySelector('shade-markdown-editor shade-tabs')
94
94
  expect(tabs).not.toBeNull()
@@ -108,7 +108,7 @@ describe('MarkdownEditor', () => {
108
108
  jsxElement: <MarkdownEditor value="# Hello" layout="tabs" />,
109
109
  })
110
110
 
111
- await sleepAsync(50)
111
+ await flushUpdates()
112
112
 
113
113
  const input = document.querySelector('shade-markdown-editor shade-markdown-input')
114
114
  expect(input).not.toBeNull()
@@ -127,7 +127,7 @@ describe('MarkdownEditor', () => {
127
127
  jsxElement: <MarkdownEditor value={mdContent} />,
128
128
  })
129
129
 
130
- await sleepAsync(50)
130
+ await flushUpdates()
131
131
 
132
132
  const textarea = document.querySelector('shade-markdown-editor textarea') as HTMLTextAreaElement
133
133
  expect(textarea.value).toBe(mdContent)
@@ -1,7 +1,8 @@
1
1
  import { Injector } from '@furystack/inject'
2
- import { createComponent, initializeShadeRoot } from '@furystack/shades'
3
- import { sleepAsync, usingAsync } from '@furystack/utils'
2
+ import { createComponent, flushUpdates, initializeShadeRoot } from '@furystack/shades'
3
+ import { usingAsync } from '@furystack/utils'
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
+ import { Form } from '../form.js'
5
6
  import { MarkdownInput } from './markdown-input.js'
6
7
 
7
8
  describe('MarkdownInput', () => {
@@ -24,7 +25,7 @@ describe('MarkdownInput', () => {
24
25
  jsxElement: <MarkdownInput value="" />,
25
26
  })
26
27
 
27
- await sleepAsync(50)
28
+ await flushUpdates()
28
29
 
29
30
  const el = document.querySelector('shade-markdown-input')
30
31
  expect(el).not.toBeNull()
@@ -41,7 +42,7 @@ describe('MarkdownInput', () => {
41
42
  jsxElement: <MarkdownInput value="# Hello" />,
42
43
  })
43
44
 
44
- await sleepAsync(50)
45
+ await flushUpdates()
45
46
 
46
47
  const textarea = document.querySelector('shade-markdown-input textarea') as HTMLTextAreaElement
47
48
  expect(textarea).not.toBeNull()
@@ -59,7 +60,7 @@ describe('MarkdownInput', () => {
59
60
  jsxElement: <MarkdownInput value="" labelTitle="Markdown Content" />,
60
61
  })
61
62
 
62
- await sleepAsync(50)
63
+ await flushUpdates()
63
64
 
64
65
  const label = document.querySelector('shade-markdown-input label')
65
66
  expect(label?.textContent).toContain('Markdown Content')
@@ -76,7 +77,7 @@ describe('MarkdownInput', () => {
76
77
  jsxElement: <MarkdownInput value="" placeholder="Type markdown..." />,
77
78
  })
78
79
 
79
- await sleepAsync(50)
80
+ await flushUpdates()
80
81
 
81
82
  const textarea = document.querySelector('shade-markdown-input textarea') as HTMLTextAreaElement
82
83
  expect(textarea.placeholder).toBe('Type markdown...')
@@ -93,7 +94,7 @@ describe('MarkdownInput', () => {
93
94
  jsxElement: <MarkdownInput value="" disabled />,
94
95
  })
95
96
 
96
- await sleepAsync(50)
97
+ await flushUpdates()
97
98
 
98
99
  const wrapper = document.querySelector('shade-markdown-input') as HTMLElement
99
100
  expect(wrapper.hasAttribute('data-disabled')).toBe(true)
@@ -113,7 +114,7 @@ describe('MarkdownInput', () => {
113
114
  jsxElement: <MarkdownInput value="" readOnly />,
114
115
  })
115
116
 
116
- await sleepAsync(50)
117
+ await flushUpdates()
117
118
 
118
119
  const textarea = document.querySelector('shade-markdown-input textarea') as HTMLTextAreaElement
119
120
  expect(textarea.readOnly).toBe(true)
@@ -131,13 +132,13 @@ describe('MarkdownInput', () => {
131
132
  jsxElement: <MarkdownInput value="" onValueChange={onValueChange} />,
132
133
  })
133
134
 
134
- await sleepAsync(50)
135
+ await flushUpdates()
135
136
 
136
137
  const textarea = document.querySelector('shade-markdown-input textarea') as HTMLTextAreaElement
137
138
  textarea.value = '# New content'
138
139
  textarea.dispatchEvent(new Event('input', { bubbles: true }))
139
140
 
140
- await sleepAsync(50)
141
+ await flushUpdates()
141
142
 
142
143
  expect(onValueChange).toHaveBeenCalledWith('# New content')
143
144
  })
@@ -153,13 +154,154 @@ describe('MarkdownInput', () => {
153
154
  jsxElement: <MarkdownInput value="" rows={20} />,
154
155
  })
155
156
 
156
- await sleepAsync(50)
157
+ await flushUpdates()
157
158
 
158
159
  const textarea = document.querySelector('shade-markdown-input textarea') as HTMLTextAreaElement
159
160
  expect(textarea.rows).toBe(20)
160
161
  })
161
162
  })
162
163
 
164
+ it('should set name attribute on textarea', async () => {
165
+ await usingAsync(new Injector(), async (injector) => {
166
+ const rootElement = document.getElementById('root') as HTMLDivElement
167
+
168
+ initializeShadeRoot({
169
+ injector,
170
+ rootElement,
171
+ jsxElement: <MarkdownInput value="" name="description" />,
172
+ })
173
+
174
+ await flushUpdates()
175
+
176
+ const textarea = document.querySelector('shade-markdown-input textarea') as HTMLTextAreaElement
177
+ expect(textarea.name).toBe('description')
178
+ })
179
+ })
180
+
181
+ it('should set required attribute on textarea', async () => {
182
+ await usingAsync(new Injector(), async (injector) => {
183
+ const rootElement = document.getElementById('root') as HTMLDivElement
184
+
185
+ initializeShadeRoot({
186
+ injector,
187
+ rootElement,
188
+ jsxElement: <MarkdownInput value="" required />,
189
+ })
190
+
191
+ await flushUpdates()
192
+
193
+ const textarea = document.querySelector('shade-markdown-input textarea') as HTMLTextAreaElement
194
+ expect(textarea.required).toBe(true)
195
+ })
196
+ })
197
+
198
+ it('should set data-invalid when required and value is empty', async () => {
199
+ await usingAsync(new Injector(), async (injector) => {
200
+ const rootElement = document.getElementById('root') as HTMLDivElement
201
+
202
+ initializeShadeRoot({
203
+ injector,
204
+ rootElement,
205
+ jsxElement: <MarkdownInput value="" required />,
206
+ })
207
+
208
+ await flushUpdates()
209
+
210
+ const wrapper = document.querySelector('shade-markdown-input') as HTMLElement
211
+ expect(wrapper.hasAttribute('data-invalid')).toBe(true)
212
+ })
213
+ })
214
+
215
+ it('should not set data-invalid when required and value is provided', async () => {
216
+ await usingAsync(new Injector(), async (injector) => {
217
+ const rootElement = document.getElementById('root') as HTMLDivElement
218
+
219
+ initializeShadeRoot({
220
+ injector,
221
+ rootElement,
222
+ jsxElement: <MarkdownInput value="some content" required />,
223
+ })
224
+
225
+ await flushUpdates()
226
+
227
+ const wrapper = document.querySelector('shade-markdown-input') as HTMLElement
228
+ expect(wrapper.hasAttribute('data-invalid')).toBe(false)
229
+ })
230
+ })
231
+
232
+ it('should show validation error message from getValidationResult', async () => {
233
+ await usingAsync(new Injector(), async (injector) => {
234
+ const rootElement = document.getElementById('root') as HTMLDivElement
235
+
236
+ initializeShadeRoot({
237
+ injector,
238
+ rootElement,
239
+ jsxElement: (
240
+ <MarkdownInput
241
+ value="short"
242
+ getValidationResult={({ value }) =>
243
+ value.length < 10 ? { isValid: false, message: 'Too short' } : { isValid: true }
244
+ }
245
+ />
246
+ ),
247
+ })
248
+
249
+ await flushUpdates()
250
+
251
+ const wrapper = document.querySelector('shade-markdown-input') as HTMLElement
252
+ expect(wrapper.hasAttribute('data-invalid')).toBe(true)
253
+ expect(wrapper.textContent).toContain('Too short')
254
+ })
255
+ })
256
+
257
+ it('should show helper text from getHelperText', async () => {
258
+ await usingAsync(new Injector(), async (injector) => {
259
+ const rootElement = document.getElementById('root') as HTMLDivElement
260
+
261
+ initializeShadeRoot({
262
+ injector,
263
+ rootElement,
264
+ jsxElement: <MarkdownInput value="" getHelperText={() => 'Write some markdown here'} />,
265
+ })
266
+
267
+ await flushUpdates()
268
+
269
+ const wrapper = document.querySelector('shade-markdown-input') as HTMLElement
270
+ expect(wrapper.textContent).toContain('Write some markdown here')
271
+ })
272
+ })
273
+
274
+ it('should render with validation inside a Form', async () => {
275
+ await usingAsync(new Injector(), async (injector) => {
276
+ const rootElement = document.getElementById('root') as HTMLDivElement
277
+
278
+ initializeShadeRoot({
279
+ injector,
280
+ rootElement,
281
+ jsxElement: (
282
+ <Form onSubmit={() => {}} validate={(_data): _data is { content: string } => true}>
283
+ <MarkdownInput
284
+ value="short"
285
+ name="content"
286
+ getValidationResult={({ value }) =>
287
+ value.length < 10 ? { isValid: false, message: 'Too short' } : { isValid: true }
288
+ }
289
+ />
290
+ </Form>
291
+ ),
292
+ })
293
+
294
+ await flushUpdates()
295
+
296
+ const wrapper = document.querySelector('shade-markdown-input') as HTMLElement
297
+ expect(wrapper.hasAttribute('data-invalid')).toBe(true)
298
+ expect(wrapper.textContent).toContain('Too short')
299
+
300
+ const textarea = wrapper.querySelector('textarea') as HTMLTextAreaElement
301
+ expect(textarea.name).toBe('content')
302
+ })
303
+ })
304
+
163
305
  describe('image paste', () => {
164
306
  const createPasteEvent = (items: Array<{ type: string; file: File | null }>) => {
165
307
  const pasteEvent = new Event('paste', { bubbles: true, cancelable: true })
@@ -200,7 +342,7 @@ describe('MarkdownInput', () => {
200
342
  jsxElement: <MarkdownInput value="Hello " onValueChange={onValueChange} />,
201
343
  })
202
344
 
203
- await sleepAsync(50)
345
+ await flushUpdates()
204
346
 
205
347
  const textarea = document.querySelector('shade-markdown-input textarea') as HTMLTextAreaElement
206
348
  textarea.selectionStart = 6
@@ -210,7 +352,7 @@ describe('MarkdownInput', () => {
210
352
  const pasteEvent = createPasteEvent([{ type: 'image/png', file }])
211
353
  textarea.dispatchEvent(pasteEvent)
212
354
 
213
- await sleepAsync(100)
355
+ await flushUpdates()
214
356
 
215
357
  expect(onValueChange).toHaveBeenCalledOnce()
216
358
  const result = onValueChange.mock.calls[0][0] as string
@@ -233,14 +375,14 @@ describe('MarkdownInput', () => {
233
375
  jsxElement: <MarkdownInput value="" onValueChange={onValueChange} maxImageSizeBytes={5} />,
234
376
  })
235
377
 
236
- await sleepAsync(50)
378
+ await flushUpdates()
237
379
 
238
380
  const textarea = document.querySelector('shade-markdown-input textarea') as HTMLTextAreaElement
239
381
  const file = new File(['this-is-larger-than-5-bytes'], 'big.png', { type: 'image/png' })
240
382
  const pasteEvent = createPasteEvent([{ type: 'image/png', file }])
241
383
  textarea.dispatchEvent(pasteEvent)
242
384
 
243
- await sleepAsync(100)
385
+ await flushUpdates()
244
386
 
245
387
  expect(onValueChange).not.toHaveBeenCalled()
246
388
  })
@@ -257,14 +399,14 @@ describe('MarkdownInput', () => {
257
399
  jsxElement: <MarkdownInput value="" onValueChange={onValueChange} />,
258
400
  })
259
401
 
260
- await sleepAsync(50)
402
+ await flushUpdates()
261
403
 
262
404
  const textarea = document.querySelector('shade-markdown-input textarea') as HTMLTextAreaElement
263
405
  const file = new File(['text content'], 'note.txt', { type: 'text/plain' })
264
406
  const pasteEvent = createPasteEvent([{ type: 'text/plain', file }])
265
407
  const wasDefaultPrevented = !textarea.dispatchEvent(pasteEvent)
266
408
 
267
- await sleepAsync(100)
409
+ await flushUpdates()
268
410
 
269
411
  expect(wasDefaultPrevented).toBe(false)
270
412
  expect(onValueChange).not.toHaveBeenCalled()
@@ -1,5 +1,7 @@
1
1
  import { Shade, createComponent } from '@furystack/shades'
2
2
  import { cssVariableTheme } from '../../services/css-variable-theme.js'
3
+ import type { InputValidationResult } from '../inputs/input.js'
4
+ import { FormService } from '../form.js'
3
5
 
4
6
  const DEFAULT_MAX_IMAGE_SIZE = 256 * 1024
5
7
 
@@ -20,6 +22,14 @@ export type MarkdownInputProps = {
20
22
  labelTitle?: string
21
23
  /** Number of visible text rows */
22
24
  rows?: number
25
+ /** Form field name for FormService integration */
26
+ name?: string
27
+ /** Whether the field is required */
28
+ required?: boolean
29
+ /** Custom validation callback */
30
+ getValidationResult?: (options: { value: string }) => InputValidationResult
31
+ /** Optional helper text callback */
32
+ getHelperText?: (options: { value: string; validationResult?: InputValidationResult }) => JSX.Element | string
23
33
  }
24
34
 
25
35
  /**
@@ -53,6 +63,11 @@ export const MarkdownInput = Shade<MarkdownInputProps>({
53
63
  color: cssVariableTheme.palette.primary.main,
54
64
  },
55
65
 
66
+ '&[data-invalid] label': {
67
+ borderColor: cssVariableTheme.palette.error.main,
68
+ color: cssVariableTheme.palette.error.main,
69
+ },
70
+
56
71
  '& textarea': {
57
72
  border: 'none',
58
73
  backgroundColor: 'transparent',
@@ -71,15 +86,61 @@ export const MarkdownInput = Shade<MarkdownInputProps>({
71
86
  '&:focus-within textarea': {
72
87
  boxShadow: `0px 3px 0px ${cssVariableTheme.palette.primary.main}`,
73
88
  },
89
+
90
+ '&[data-invalid]:focus-within textarea': {
91
+ boxShadow: `0px 3px 0px ${cssVariableTheme.palette.error.main}`,
92
+ },
93
+
94
+ '& .helperText': {
95
+ fontSize: cssVariableTheme.typography.fontSize.xs,
96
+ marginTop: '6px',
97
+ opacity: '0.85',
98
+ lineHeight: '1.4',
99
+ },
74
100
  },
75
- render: ({ props, useHostProps, useRef }) => {
101
+ render: ({ props, injector, useDisposable, useHostProps, useRef }) => {
76
102
  const maxSize = props.maxImageSizeBytes ?? DEFAULT_MAX_IMAGE_SIZE
77
103
  const textareaRef = useRef<HTMLTextAreaElement>('textarea')
78
104
 
105
+ useDisposable('form-registration', () => {
106
+ const formService = injector.cachedSingletons.has(FormService) ? injector.getInstance(FormService) : null
107
+ if (formService) {
108
+ queueMicrotask(() => {
109
+ if (textareaRef.current) formService.inputs.add(textareaRef.current as unknown as HTMLInputElement)
110
+ })
111
+ }
112
+ return {
113
+ [Symbol.dispose]: () => {
114
+ if (textareaRef.current && formService)
115
+ formService.inputs.delete(textareaRef.current as unknown as HTMLInputElement)
116
+ },
117
+ }
118
+ })
119
+
120
+ const validationResult = props.getValidationResult?.({ value: props.value })
121
+ const isRequired = props.required && !props.value
122
+ const isInvalid = validationResult?.isValid === false || isRequired
123
+
124
+ if (injector.cachedSingletons.has(FormService) && props.name) {
125
+ const formService = injector.getInstance(FormService)
126
+ const fieldResult: InputValidationResult = isRequired
127
+ ? { isValid: false, message: 'Value is required' }
128
+ : validationResult || { isValid: true }
129
+ const validity = textareaRef.current?.validity ?? ({} as ValidityState)
130
+ formService.setFieldState(props.name as keyof unknown, fieldResult, validity)
131
+ }
132
+
79
133
  useHostProps({
80
134
  'data-disabled': props.disabled ? '' : undefined,
135
+ 'data-invalid': isInvalid ? '' : undefined,
81
136
  })
82
137
 
138
+ const helperNode =
139
+ (validationResult?.isValid === false && validationResult?.message) ||
140
+ (isRequired && 'Value is required') ||
141
+ props.getHelperText?.({ value: props.value, validationResult }) ||
142
+ ''
143
+
83
144
  const handleInput = (ev: Event) => {
84
145
  const target = ev.target as HTMLTextAreaElement
85
146
  props.onValueChange?.(target.value)
@@ -129,6 +190,8 @@ export const MarkdownInput = Shade<MarkdownInputProps>({
129
190
  {props.labelTitle ? <span>{props.labelTitle}</span> : null}
130
191
  <textarea
131
192
  ref={textareaRef}
193
+ name={props.name}
194
+ required={props.required}
132
195
  value={props.value}
133
196
  oninput={handleInput}
134
197
  onpaste={handlePaste}
@@ -137,6 +200,7 @@ export const MarkdownInput = Shade<MarkdownInputProps>({
137
200
  placeholder={props.placeholder}
138
201
  rows={props.rows ?? 10}
139
202
  />
203
+ {helperNode ? <span className="helperText">{helperNode}</span> : null}
140
204
  </label>
141
205
  )
142
206
  },
@@ -1,6 +1,6 @@
1
1
  import { Injector } from '@furystack/inject'
2
- import { createComponent, initializeShadeRoot } from '@furystack/shades'
3
- import { sleepAsync, usingAsync } from '@furystack/utils'
2
+ import { createComponent, flushUpdates, initializeShadeRoot } from '@furystack/shades'
3
+ import { usingAsync } from '@furystack/utils'
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
5
  import type { MenuEntry } from './menu-types.js'
6
6
  import { Menu } from './menu.js'
@@ -29,7 +29,7 @@ describe('Menu', () => {
29
29
  rootElement: root,
30
30
  jsxElement: <Menu {...props} />,
31
31
  })
32
- await sleepAsync(50)
32
+ await flushUpdates()
33
33
  return {
34
34
  injector,
35
35
  menu: root.querySelector('shade-menu') as HTMLElement,
@@ -219,7 +219,7 @@ describe('Menu', () => {
219
219
  const groupLabel = menu.querySelector('.menu-group-label-inline') as HTMLElement
220
220
  expect(groupLabel).toBeTruthy()
221
221
  groupLabel.click()
222
- await sleepAsync(50)
222
+ await flushUpdates()
223
223
 
224
224
  const groupItems = menu.querySelectorAll('[role="menuitem"]')
225
225
  expect(groupItems.length).toBe(1)
@@ -239,7 +239,7 @@ describe('Menu', () => {
239
239
  await usingAsync(await renderMenu({ items: createTestItems() }), async ({ menu }) => {
240
240
  menu.focus()
241
241
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }))
242
- await sleepAsync(50)
242
+ await flushUpdates()
243
243
 
244
244
  const focusedItem = menu.querySelector('.menu-item.focused')
245
245
  expect(focusedItem).toBeTruthy()
@@ -251,7 +251,7 @@ describe('Menu', () => {
251
251
  await usingAsync(await renderMenu({ items: createTestItems(), mode: 'horizontal' }), async ({ menu }) => {
252
252
  menu.focus()
253
253
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }))
254
- await sleepAsync(50)
254
+ await flushUpdates()
255
255
 
256
256
  const focusedItem = menu.querySelector('.menu-item.focused')
257
257
  expect(focusedItem).toBeTruthy()
@@ -265,10 +265,10 @@ describe('Menu', () => {
265
265
  menu.focus()
266
266
  // Navigate to first item
267
267
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }))
268
- await sleepAsync(50)
268
+ await flushUpdates()
269
269
  // Press Enter
270
270
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))
271
- await sleepAsync(50)
271
+ await flushUpdates()
272
272
  expect(handleSelect).toHaveBeenCalledWith('home')
273
273
  })
274
274
  })
@@ -277,7 +277,7 @@ describe('Menu', () => {
277
277
  await usingAsync(await renderMenu({ items: createTestItems() }), async ({ menu }) => {
278
278
  menu.focus()
279
279
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true }))
280
- await sleepAsync(50)
280
+ await flushUpdates()
281
281
 
282
282
  const focusedItem = menu.querySelector('.menu-item.focused')
283
283
  expect(focusedItem?.getAttribute('data-key')).toBe('settings')
@@ -289,10 +289,10 @@ describe('Menu', () => {
289
289
  menu.focus()
290
290
  // Go to end first
291
291
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true }))
292
- await sleepAsync(50)
292
+ await flushUpdates()
293
293
  // Then Home
294
294
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', bubbles: true }))
295
- await sleepAsync(50)
295
+ await flushUpdates()
296
296
 
297
297
  const focusedItem = menu.querySelector('.menu-item.focused')
298
298
  expect(focusedItem?.getAttribute('data-key')).toBe('home')
@@ -308,10 +308,10 @@ describe('Menu', () => {
308
308
  menu.focus()
309
309
  // Navigate to end
310
310
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true }))
311
- await sleepAsync(50)
311
+ await flushUpdates()
312
312
  // One more down should wrap to first
313
313
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }))
314
- await sleepAsync(50)
314
+ await flushUpdates()
315
315
 
316
316
  const focusedItem = menu.querySelector('.menu-item.focused')
317
317
  expect(focusedItem?.getAttribute('data-key')).toBe('a')
@@ -327,10 +327,10 @@ describe('Menu', () => {
327
327
  menu.focus()
328
328
  // Navigate to Home first
329
329
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', bubbles: true }))
330
- await sleepAsync(50)
330
+ await flushUpdates()
331
331
  // One more up should wrap to last
332
332
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }))
333
- await sleepAsync(50)
333
+ await flushUpdates()
334
334
 
335
335
  const focusedItem = menu.querySelector('.menu-item.focused')
336
336
  expect(focusedItem?.getAttribute('data-key')).toBe('b')
@@ -342,9 +342,9 @@ describe('Menu', () => {
342
342
  await usingAsync(await renderMenu({ items: createTestItems(), onSelect: handleSelect }), async ({ menu }) => {
343
343
  menu.focus()
344
344
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }))
345
- await sleepAsync(50)
345
+ await flushUpdates()
346
346
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true }))
347
- await sleepAsync(50)
347
+ await flushUpdates()
348
348
  expect(handleSelect).toHaveBeenCalledWith('home')
349
349
  })
350
350
  })
@@ -354,11 +354,11 @@ describe('Menu', () => {
354
354
  menu.focus()
355
355
  // First go to end
356
356
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true }))
357
- await sleepAsync(50)
357
+ await flushUpdates()
358
358
 
359
359
  // Then ArrowUp
360
360
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }))
361
- await sleepAsync(50)
361
+ await flushUpdates()
362
362
 
363
363
  const focusedItem = menu.querySelector('.menu-item.focused')
364
364
  expect(focusedItem?.getAttribute('data-key')).toBe('about')
@@ -370,11 +370,11 @@ describe('Menu', () => {
370
370
  menu.focus()
371
371
  // First go to end
372
372
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true }))
373
- await sleepAsync(50)
373
+ await flushUpdates()
374
374
 
375
375
  // Then ArrowLeft
376
376
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true }))
377
- await sleepAsync(50)
377
+ await flushUpdates()
378
378
 
379
379
  const focusedItem = menu.querySelector('.menu-item.focused')
380
380
  expect(focusedItem?.getAttribute('data-key')).toBe('about')
@@ -386,7 +386,7 @@ describe('Menu', () => {
386
386
  await usingAsync(await renderMenu({ items: createTestItems(), onSelect: handleSelect }), async ({ menu }) => {
387
387
  menu.focus()
388
388
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'a', bubbles: true }))
389
- await sleepAsync(50)
389
+ await flushUpdates()
390
390
  expect(handleSelect).not.toHaveBeenCalled()
391
391
  })
392
392
  })
@@ -397,7 +397,7 @@ describe('Menu', () => {
397
397
  menu.focus()
398
398
  // Press Enter without navigating to any item first
399
399
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))
400
- await sleepAsync(50)
400
+ await flushUpdates()
401
401
  expect(handleSelect).not.toHaveBeenCalled()
402
402
  })
403
403
  })
@@ -407,7 +407,7 @@ describe('Menu', () => {
407
407
  await usingAsync(await renderMenu({ items }), async ({ menu }) => {
408
408
  menu.focus()
409
409
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }))
410
- await sleepAsync(50)
410
+ await flushUpdates()
411
411
  const focusedItem = menu.querySelector('.menu-item.focused')
412
412
  expect(focusedItem).toBeNull()
413
413
  })
@@ -419,7 +419,7 @@ describe('Menu', () => {
419
419
  await usingAsync(await renderMenu({ items: createTestItems() }), async ({ menu }) => {
420
420
  const item = menu.querySelector('[data-key="about"]') as HTMLElement
421
421
  item.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
422
- await sleepAsync(50)
422
+ await flushUpdates()
423
423
 
424
424
  expect(item.classList.contains('focused')).toBe(true)
425
425
  })
@@ -430,7 +430,7 @@ describe('Menu', () => {
430
430
  await usingAsync(await renderMenu({ items }), async ({ menu }) => {
431
431
  const item = menu.querySelector('[data-key="disabled-item"]') as HTMLElement
432
432
  item.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
433
- await sleepAsync(50)
433
+ await flushUpdates()
434
434
 
435
435
  expect(item.classList.contains('focused')).toBe(false)
436
436
  })
@@ -452,13 +452,13 @@ describe('Menu', () => {
452
452
 
453
453
  // Expand
454
454
  groupLabel.click()
455
- await sleepAsync(50)
455
+ await flushUpdates()
456
456
  const groupChildren = menu.querySelector('.menu-group-children') as HTMLElement
457
457
  expect(groupChildren.style.display).toBe('')
458
458
 
459
459
  // Collapse
460
460
  groupLabel.click()
461
- await sleepAsync(50)
461
+ await flushUpdates()
462
462
  expect(groupChildren.style.display).toBe('none')
463
463
  })
464
464
  })