@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 } from 'vitest'
5
5
  import { Avatar } from './avatar.js'
6
6
 
@@ -24,7 +24,7 @@ describe('Avatar component', () => {
24
24
  jsxElement: <Avatar avatarUrl={testUrl} />,
25
25
  })
26
26
 
27
- await sleepAsync(50)
27
+ await flushUpdates()
28
28
 
29
29
  const avatar = document.querySelector('shade-avatar')
30
30
  expect(avatar).not.toBeNull()
@@ -46,7 +46,7 @@ describe('Avatar component', () => {
46
46
  jsxElement: <Avatar avatarUrl="invalid-url" />,
47
47
  })
48
48
 
49
- await sleepAsync(50)
49
+ await flushUpdates()
50
50
 
51
51
  const avatar = document.querySelector('shade-avatar')
52
52
  expect(avatar).not.toBeNull()
@@ -58,7 +58,7 @@ describe('Avatar component', () => {
58
58
  const errorEvent = new Event('error')
59
59
  img?.dispatchEvent(errorEvent)
60
60
 
61
- await sleepAsync(50)
61
+ await flushUpdates()
62
62
 
63
63
  // After error, img should be replaced with fallback div
64
64
  const fallbackImg = avatar?.querySelector('img')
@@ -81,7 +81,7 @@ describe('Avatar component', () => {
81
81
  jsxElement: <Avatar avatarUrl="invalid-url" fallback={customFallback} />,
82
82
  })
83
83
 
84
- await sleepAsync(50)
84
+ await flushUpdates()
85
85
 
86
86
  const avatar = document.querySelector('shade-avatar')
87
87
  expect(avatar).not.toBeNull()
@@ -93,7 +93,7 @@ describe('Avatar component', () => {
93
93
  const errorEvent = new Event('error')
94
94
  img?.dispatchEvent(errorEvent)
95
95
 
96
- await sleepAsync(50)
96
+ await flushUpdates()
97
97
 
98
98
  // After error, img should be replaced with fallback div
99
99
  const fallbackImg = avatar?.querySelector('img')
@@ -116,7 +116,7 @@ describe('Avatar component', () => {
116
116
  jsxElement: <Avatar avatarUrl="https://example.com/avatar.png" style={{ width: '64px', height: '64px' }} />,
117
117
  })
118
118
 
119
- await sleepAsync(50)
119
+ await flushUpdates()
120
120
 
121
121
  const avatar = document.querySelector('shade-avatar') as HTMLElement
122
122
  expect(avatar).not.toBeNull()
@@ -135,7 +135,7 @@ describe('Avatar component', () => {
135
135
  jsxElement: <Avatar avatarUrl="https://example.com/avatar.png" className="custom-avatar" title="User Avatar" />,
136
136
  })
137
137
 
138
- await sleepAsync(50)
138
+ await flushUpdates()
139
139
 
140
140
  const avatar = document.querySelector('shade-avatar') as HTMLElement
141
141
  expect(avatar).not.toBeNull()
@@ -1,7 +1,7 @@
1
1
  import { Injector } from '@furystack/inject'
2
2
  import { createComponent, flushUpdates, initializeShadeRoot, LocationService } from '@furystack/shades'
3
3
  import type { ExtractRouteParams, NestedRoute } from '@furystack/shades'
4
- import { sleepAsync, usingAsync } from '@furystack/utils'
4
+ import { usingAsync } from '@furystack/utils'
5
5
  import { afterEach, beforeEach, describe, expect, expectTypeOf, it, vi } from 'vitest'
6
6
  import type { BreadcrumbItem, TypedBreadcrumbProps } from './breadcrumb.js'
7
7
  import { Breadcrumb, createBreadcrumb } from './breadcrumb.js'
@@ -247,7 +247,7 @@ describe('Breadcrumb', () => {
247
247
  ),
248
248
  })
249
249
 
250
- await sleepAsync(50)
250
+ await flushUpdates()
251
251
 
252
252
  const breadcrumb = rootElement.querySelector('nav[is="shade-breadcrumb"]')
253
253
  expect(breadcrumb?.classList.contains('custom-breadcrumb')).toBe(true)
@@ -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 {
6
6
  ButtonGroup,
@@ -28,7 +28,7 @@ describe('ButtonGroup', () => {
28
28
  rootElement: root,
29
29
  jsxElement: <ButtonGroup {...props}>{children}</ButtonGroup>,
30
30
  })
31
- await sleepAsync(50)
31
+ await flushUpdates()
32
32
  return {
33
33
  injector,
34
34
  group: root.querySelector('shade-button-group') as HTMLElement,
@@ -132,6 +132,78 @@ describe('ButtonGroup', () => {
132
132
  })
133
133
  })
134
134
 
135
+ describe('ToggleButton', () => {
136
+ beforeEach(() => {
137
+ document.body.innerHTML = '<div id="root"></div>'
138
+ })
139
+
140
+ afterEach(() => {
141
+ document.body.innerHTML = ''
142
+ })
143
+
144
+ const renderToggleButton = async (props: Parameters<typeof ToggleButton>[0]) => {
145
+ const injector = new Injector()
146
+ const root = document.getElementById('root')!
147
+ initializeShadeRoot({
148
+ injector,
149
+ rootElement: root,
150
+ jsxElement: <ToggleButton {...props}>Label</ToggleButton>,
151
+ })
152
+ await flushUpdates()
153
+ return {
154
+ injector,
155
+ button: root.querySelector('button[is="shade-toggle-button"]') as HTMLButtonElement,
156
+ [Symbol.asyncDispose]: () => injector[Symbol.asyncDispose](),
157
+ }
158
+ }
159
+
160
+ describe('pressed', () => {
161
+ it('should not set data-selected when pressed is not specified', async () => {
162
+ await usingAsync(await renderToggleButton({ value: 'a' }), async ({ button }) => {
163
+ expect(button.hasAttribute('data-selected')).toBe(false)
164
+ })
165
+ })
166
+
167
+ it('should set data-selected when pressed is true', async () => {
168
+ await usingAsync(await renderToggleButton({ value: 'a', pressed: true }), async ({ button }) => {
169
+ expect(button.hasAttribute('data-selected')).toBe(true)
170
+ })
171
+ })
172
+
173
+ it('should not set data-selected when pressed is false', async () => {
174
+ await usingAsync(await renderToggleButton({ value: 'a', pressed: false }), async ({ button }) => {
175
+ expect(button.hasAttribute('data-selected')).toBe(false)
176
+ })
177
+ })
178
+ })
179
+
180
+ describe('size', () => {
181
+ it('should not set data-size by default', async () => {
182
+ await usingAsync(await renderToggleButton({ value: 'a' }), async ({ button }) => {
183
+ expect(button.hasAttribute('data-size')).toBe(false)
184
+ })
185
+ })
186
+
187
+ it('should not set data-size for medium (default)', async () => {
188
+ await usingAsync(await renderToggleButton({ value: 'a', size: 'medium' }), async ({ button }) => {
189
+ expect(button.hasAttribute('data-size')).toBe(false)
190
+ })
191
+ })
192
+
193
+ it('should set data-size="small" for small size', async () => {
194
+ await usingAsync(await renderToggleButton({ value: 'a', size: 'small' }), async ({ button }) => {
195
+ expect(button.getAttribute('data-size')).toBe('small')
196
+ })
197
+ })
198
+
199
+ it('should set data-size="large" for large size', async () => {
200
+ await usingAsync(await renderToggleButton({ value: 'a', size: 'large' }), async ({ button }) => {
201
+ expect(button.getAttribute('data-size')).toBe('large')
202
+ })
203
+ })
204
+ })
205
+ })
206
+
135
207
  describe('ToggleButtonGroup', () => {
136
208
  beforeEach(() => {
137
209
  document.body.innerHTML = '<div id="root"></div>'
@@ -149,7 +221,7 @@ describe('ToggleButtonGroup', () => {
149
221
  rootElement: root,
150
222
  jsxElement: <ToggleButtonGroup {...props}>{children}</ToggleButtonGroup>,
151
223
  })
152
- await sleepAsync(100)
224
+ await flushUpdates()
153
225
  return {
154
226
  injector,
155
227
  group: root.querySelector('shade-toggle-button-group') as HTMLElement,
@@ -257,8 +329,8 @@ describe('ToggleButtonGroup', () => {
257
329
  <ToggleButton value="right">Right</ToggleButton>,
258
330
  ] as unknown as JSX.Element[]),
259
331
  async ({ group }) => {
260
- // Wait for requestAnimationFrame
261
- await sleepAsync(50)
332
+ await flushUpdates()
333
+ await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
262
334
  const centerBtn = group.querySelector('button[data-value="center"]') as HTMLButtonElement
263
335
  const leftBtn = group.querySelector('button[data-value="left"]') as HTMLButtonElement
264
336
  expect(centerBtn.hasAttribute('data-selected')).toBe(true)
@@ -275,7 +347,8 @@ describe('ToggleButtonGroup', () => {
275
347
  <ToggleButton value="underline">U</ToggleButton>,
276
348
  ] as unknown as JSX.Element[]),
277
349
  async ({ group }) => {
278
- await sleepAsync(50)
350
+ await flushUpdates()
351
+ await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
279
352
  const boldBtn = group.querySelector('button[data-value="bold"]') as HTMLButtonElement
280
353
  const italicBtn = group.querySelector('button[data-value="italic"]') as HTMLButtonElement
281
354
  const underlineBtn = group.querySelector('button[data-value="underline"]') as HTMLButtonElement
@@ -314,6 +387,75 @@ describe('ToggleButtonGroup', () => {
314
387
  })
315
388
  })
316
389
  })
390
+
391
+ describe('size', () => {
392
+ it('should not set data-size on children by default', async () => {
393
+ await usingAsync(
394
+ await renderToggleGroup({}, [
395
+ <ToggleButton value="a">A</ToggleButton>,
396
+ <ToggleButton value="b">B</ToggleButton>,
397
+ ] as unknown as JSX.Element[]),
398
+ async ({ group }) => {
399
+ await flushUpdates()
400
+ await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
401
+ const buttons = group.querySelectorAll('button[data-value]')
402
+ buttons.forEach((btn) => {
403
+ expect(btn.hasAttribute('data-size')).toBe(false)
404
+ })
405
+ },
406
+ )
407
+ })
408
+
409
+ it('should propagate size="small" to child buttons', async () => {
410
+ await usingAsync(
411
+ await renderToggleGroup({ size: 'small' }, [
412
+ <ToggleButton value="a">A</ToggleButton>,
413
+ <ToggleButton value="b">B</ToggleButton>,
414
+ ] as unknown as JSX.Element[]),
415
+ async ({ group }) => {
416
+ await flushUpdates()
417
+ await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
418
+ const buttons = group.querySelectorAll('button[data-value]')
419
+ buttons.forEach((btn) => {
420
+ expect(btn.getAttribute('data-size')).toBe('small')
421
+ })
422
+ },
423
+ )
424
+ })
425
+
426
+ it('should propagate size="large" to child buttons', async () => {
427
+ await usingAsync(
428
+ await renderToggleGroup({ size: 'large' }, [
429
+ <ToggleButton value="a">A</ToggleButton>,
430
+ <ToggleButton value="b">B</ToggleButton>,
431
+ ] as unknown as JSX.Element[]),
432
+ async ({ group }) => {
433
+ await flushUpdates()
434
+ await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
435
+ const buttons = group.querySelectorAll('button[data-value]')
436
+ buttons.forEach((btn) => {
437
+ expect(btn.getAttribute('data-size')).toBe('large')
438
+ })
439
+ },
440
+ )
441
+ })
442
+
443
+ it('should remove data-size when size is medium', async () => {
444
+ await usingAsync(
445
+ await renderToggleGroup({ size: 'medium' }, [
446
+ <ToggleButton value="a" size="small">
447
+ A
448
+ </ToggleButton>,
449
+ ] as unknown as JSX.Element[]),
450
+ async ({ group }) => {
451
+ await flushUpdates()
452
+ await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
453
+ const btn = group.querySelector('button[data-value]')
454
+ expect(btn?.hasAttribute('data-size')).toBe(false)
455
+ },
456
+ )
457
+ })
458
+ })
317
459
  })
318
460
 
319
461
  describe('SegmentedControl', () => {
@@ -339,7 +481,7 @@ describe('SegmentedControl', () => {
339
481
  rootElement: root,
340
482
  jsxElement: <SegmentedControl options={defaultOptions} {...props} />,
341
483
  })
342
- await sleepAsync(50)
484
+ await flushUpdates()
343
485
  return {
344
486
  injector,
345
487
  control: root.querySelector('shade-segmented-control') as HTMLElement,
@@ -502,7 +644,7 @@ describe('ToggleButtonGroup edge cases', () => {
502
644
  rootElement: root,
503
645
  jsxElement: <ToggleButtonGroup {...props}>{children}</ToggleButtonGroup>,
504
646
  })
505
- await sleepAsync(100)
647
+ await flushUpdates()
506
648
  return {
507
649
  injector,
508
650
  group: root.querySelector('shade-toggle-button-group') as HTMLElement,
@@ -577,7 +719,8 @@ describe('ToggleButtonGroup edge cases', () => {
577
719
  <ToggleButton value="b">B</ToggleButton>,
578
720
  ] as unknown as JSX.Element[]),
579
721
  async ({ group }) => {
580
- await sleepAsync(50)
722
+ await flushUpdates()
723
+ await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
581
724
  const buttons = group.querySelectorAll('button[data-value]')
582
725
  buttons.forEach((btn) => {
583
726
  expect(btn.hasAttribute('disabled')).toBe(true)
@@ -593,7 +736,8 @@ describe('ToggleButtonGroup edge cases', () => {
593
736
  <ToggleButton value="b">B</ToggleButton>,
594
737
  ] as unknown as JSX.Element[]),
595
738
  async ({ group }) => {
596
- await sleepAsync(50)
739
+ await flushUpdates()
740
+ await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
597
741
  const buttons = group.querySelectorAll('button[data-value]')
598
742
  // First button should have top radius
599
743
  expect((buttons[0] as HTMLElement).style.borderRadius).toContain('0 0')
@@ -89,6 +89,19 @@ export type ToggleButtonProps = PartialElement<HTMLButtonElement> & {
89
89
  value: string
90
90
  /** Whether the button is disabled */
91
91
  disabled?: boolean
92
+ /**
93
+ * The size of the toggle button.
94
+ * @default 'medium'
95
+ */
96
+ size?: 'small' | 'medium' | 'large'
97
+ /**
98
+ * Whether the button is in a pressed (selected) state.
99
+ * Use this for standalone toggle buttons or controlled state.
100
+ * When used inside a `ToggleButtonGroup`, the group manages the
101
+ * pressed state automatically via its `value` prop and will
102
+ * override this attribute.
103
+ */
104
+ pressed?: boolean
92
105
  }
93
106
 
94
107
  export const ToggleButton = Shade<ToggleButtonProps>({
@@ -111,13 +124,17 @@ export const ToggleButton = Shade<ToggleButtonProps>({
111
124
  userSelect: 'none',
112
125
  background: 'transparent',
113
126
  color: 'var(--toggle-color-main)',
114
- boxShadow: '0px 0px 0px 1px var(--toggle-color-main)',
127
+ boxShadow: 'none',
115
128
  transition: buildTransition(
116
129
  ['background', cssVariableTheme.transitions.duration.normal, cssVariableTheme.transitions.easing.default],
117
130
  ['color', cssVariableTheme.transitions.duration.normal, cssVariableTheme.transitions.easing.default],
118
131
  ['box-shadow', cssVariableTheme.transitions.duration.normal, cssVariableTheme.transitions.easing.default],
119
132
  ),
120
133
 
134
+ '&[data-grouped]': {
135
+ boxShadow: '0px 0px 0px 1px var(--toggle-color-main)',
136
+ },
137
+
121
138
  '&:hover:not(:disabled):not([data-selected])': {
122
139
  background: 'color-mix(in srgb, var(--toggle-color-main) 10%, transparent)',
123
140
  },
@@ -140,11 +157,27 @@ export const ToggleButton = Shade<ToggleButtonProps>({
140
157
  '&:active:not(:disabled)': {
141
158
  transform: 'scale(0.96)',
142
159
  },
160
+
161
+ '&[data-size="small"]': {
162
+ padding: `${cssVariableTheme.spacing.xs} ${cssVariableTheme.spacing.sm}`,
163
+ fontSize: cssVariableTheme.typography.fontSize.sm,
164
+ },
165
+
166
+ '&[data-size="large"]': {
167
+ padding: `${cssVariableTheme.spacing.md} ${cssVariableTheme.spacing.xl}`,
168
+ fontSize: cssVariableTheme.typography.fontSize.lg,
169
+ },
143
170
  },
144
171
  render: ({ props, children, useHostProps }) => {
145
172
  useHostProps({
146
173
  'data-value': props.value || undefined,
174
+ 'data-size': props.size && props.size !== 'medium' ? props.size : undefined,
175
+ 'data-selected': props.pressed ? '' : undefined,
147
176
  type: 'button',
177
+ style: {
178
+ '--toggle-color-main': cssVariableTheme.text.secondary,
179
+ ...(props.style as Record<string, string>),
180
+ },
148
181
  })
149
182
 
150
183
  return <>{children}</>
@@ -171,6 +204,12 @@ export type ToggleButtonGroupProps = PartialElement<HTMLElement> & {
171
204
  orientation?: 'horizontal' | 'vertical'
172
205
  /** Whether all toggle buttons are disabled */
173
206
  disabled?: boolean
207
+ /**
208
+ * Size applied to all toggle buttons in the group.
209
+ * Individual ToggleButton `size` props are overridden by this value.
210
+ * @default 'medium'
211
+ */
212
+ size?: 'small' | 'medium' | 'large'
174
213
  }
175
214
 
176
215
  export const ToggleButtonGroup: (props: ToggleButtonGroupProps, children: ChildrenList) => JSX.Element =
@@ -228,7 +267,7 @@ export const ToggleButtonGroup: (props: ToggleButtonGroupProps, children: Childr
228
267
  return { [Symbol.dispose]: () => el?.removeEventListener('click', handleClick) }
229
268
  })
230
269
 
231
- const { orientation = 'horizontal', disabled, color, style } = props
270
+ const { orientation = 'horizontal', disabled, color, size, style } = props
232
271
  const selectedValues = Array.isArray(props.value) ? props.value : props.value ? [props.value] : ([] as string[])
233
272
 
234
273
  const colors = color ? paletteFullColors[color] : defaultToggleColors
@@ -256,10 +295,18 @@ export const ToggleButtonGroup: (props: ToggleButtonGroupProps, children: Childr
256
295
  btn.removeAttribute('data-selected')
257
296
  }
258
297
 
298
+ btn.setAttribute('data-grouped', '')
299
+
259
300
  if (disabled) {
260
301
  btn.setAttribute('disabled', '')
261
302
  }
262
303
 
304
+ if (size && size !== 'medium') {
305
+ btn.setAttribute('data-size', size)
306
+ } else if (size === 'medium') {
307
+ btn.removeAttribute('data-size')
308
+ }
309
+
263
310
  // Propagate color CSS variable
264
311
  ;(btn as HTMLElement).style.setProperty('--toggle-color-main', colors.main)
265
312
 
@@ -1,6 +1,6 @@
1
1
  import { Injector } from '@furystack/inject'
2
- import { createComponent, initializeShadeRoot, Shade } from '@furystack/shades'
3
- import { sleepAsync, usingAsync } from '@furystack/utils'
2
+ import { createComponent, flushUpdates, initializeShadeRoot, Shade } from '@furystack/shades'
3
+ import { usingAsync } from '@furystack/utils'
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
5
  import { Button } from './button.js'
6
6
 
@@ -22,7 +22,7 @@ describe('Button', () => {
22
22
  rootElement: root,
23
23
  jsxElement: <Button {...props}>{children}</Button>,
24
24
  })
25
- await sleepAsync(50)
25
+ await flushUpdates()
26
26
  return {
27
27
  injector,
28
28
  button: root.querySelector('button[is="shade-button"]') as HTMLButtonElement,
@@ -86,7 +86,7 @@ describe('Button', () => {
86
86
  jsxElement: <TestComponent variant="contained" />,
87
87
  })
88
88
 
89
- await sleepAsync(50)
89
+ await flushUpdates()
90
90
  const button = root.querySelector('button[is="shade-button"]') as HTMLButtonElement
91
91
  expect(button.getAttribute('data-variant')).toBe('contained')
92
92
  })
@@ -1,7 +1,7 @@
1
1
  import { Cache } from '@furystack/cache'
2
2
  import type { CacheResult, CacheWithValue } from '@furystack/cache'
3
3
  import { Shade, createComponent, flushUpdates } from '@furystack/shades'
4
- import { sleepAsync } from '@furystack/utils'
4
+
5
5
  import { describe, expect, it, vi } from 'vitest'
6
6
  import { CacheView } from './cache-view.js'
7
7
 
@@ -162,7 +162,7 @@ describe('CacheView', () => {
162
162
 
163
163
  loadFn.mockResolvedValueOnce('recovered')
164
164
  capturedRetry!()
165
- await sleepAsync(50)
165
+ await flushUpdates()
166
166
 
167
167
  const observable = cache.getObservable('test')
168
168
  const result = observable.getValue()
@@ -183,7 +183,7 @@ describe('CacheView', () => {
183
183
  const contentEl = cacheView.querySelector('test-cache-content')
184
184
  expect(contentEl).not.toBeNull()
185
185
 
186
- await sleepAsync(50)
186
+ await flushUpdates()
187
187
  // reload should have been called (initial load + obsolete reload)
188
188
  expect(loadFn).toHaveBeenCalledTimes(2)
189
189
  cache[Symbol.dispose]()