@furystack/shades-common-components 13.5.0 → 15.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (540) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +3 -3
  3. package/esm/components/accordion/accordion-item.d.ts.map +1 -1
  4. package/esm/components/accordion/accordion-item.js +7 -10
  5. package/esm/components/accordion/accordion-item.js.map +1 -1
  6. package/esm/components/accordion/accordion.d.ts +7 -0
  7. package/esm/components/accordion/accordion.d.ts.map +1 -1
  8. package/esm/components/accordion/accordion.js +5 -2
  9. package/esm/components/accordion/accordion.js.map +1 -1
  10. package/esm/components/accordion/accordion.spec.js +91 -50
  11. package/esm/components/accordion/accordion.spec.js.map +1 -1
  12. package/esm/components/alert.js +1 -1
  13. package/esm/components/alert.js.map +1 -1
  14. package/esm/components/app-bar-link.js +1 -1
  15. package/esm/components/app-bar-link.js.map +1 -1
  16. package/esm/components/app-bar.js +1 -1
  17. package/esm/components/app-bar.js.map +1 -1
  18. package/esm/components/avatar.js +1 -1
  19. package/esm/components/avatar.js.map +1 -1
  20. package/esm/components/badge.js +1 -1
  21. package/esm/components/badge.js.map +1 -1
  22. package/esm/components/breadcrumb.js +1 -1
  23. package/esm/components/breadcrumb.js.map +1 -1
  24. package/esm/components/breadcrumb.spec.js +3 -3
  25. package/esm/components/breadcrumb.spec.js.map +1 -1
  26. package/esm/components/button-group.js +4 -4
  27. package/esm/components/button-group.js.map +1 -1
  28. package/esm/components/button.js +1 -1
  29. package/esm/components/button.js.map +1 -1
  30. package/esm/components/button.spec.js +1 -1
  31. package/esm/components/button.spec.js.map +1 -1
  32. package/esm/components/cache-view.js +1 -1
  33. package/esm/components/cache-view.js.map +1 -1
  34. package/esm/components/cache-view.spec.js +2 -2
  35. package/esm/components/cache-view.spec.js.map +1 -1
  36. package/esm/components/card.js +5 -5
  37. package/esm/components/card.js.map +1 -1
  38. package/esm/components/carousel.js +2 -2
  39. package/esm/components/carousel.js.map +1 -1
  40. package/esm/components/chip.d.ts.map +1 -1
  41. package/esm/components/chip.js +5 -3
  42. package/esm/components/chip.js.map +1 -1
  43. package/esm/components/chip.spec.js +42 -0
  44. package/esm/components/chip.spec.js.map +1 -1
  45. package/esm/components/circular-progress.d.ts +2 -4
  46. package/esm/components/circular-progress.d.ts.map +1 -1
  47. package/esm/components/circular-progress.js +3 -6
  48. package/esm/components/circular-progress.js.map +1 -1
  49. package/esm/components/circular-progress.spec.js +19 -14
  50. package/esm/components/circular-progress.spec.js.map +1 -1
  51. package/esm/components/command-palette/command-palette-input.js +1 -1
  52. package/esm/components/command-palette/command-palette-input.js.map +1 -1
  53. package/esm/components/command-palette/command-palette-suggestion-list.js +1 -1
  54. package/esm/components/command-palette/command-palette-suggestion-list.js.map +1 -1
  55. package/esm/components/command-palette/command-palette-suggestion-list.spec.js +1 -1
  56. package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -1
  57. package/esm/components/command-palette/index.d.ts.map +1 -1
  58. package/esm/components/command-palette/index.js +15 -2
  59. package/esm/components/command-palette/index.js.map +1 -1
  60. package/esm/components/command-palette/index.spec.js +78 -33
  61. package/esm/components/command-palette/index.spec.js.map +1 -1
  62. package/esm/components/context-menu/context-menu-item.js +1 -1
  63. package/esm/components/context-menu/context-menu-item.js.map +1 -1
  64. package/esm/components/context-menu/context-menu.js +1 -1
  65. package/esm/components/context-menu/context-menu.js.map +1 -1
  66. package/esm/components/data-grid/body.js +1 -1
  67. package/esm/components/data-grid/body.js.map +1 -1
  68. package/esm/components/data-grid/data-grid-row.d.ts.map +1 -1
  69. package/esm/components/data-grid/data-grid-row.js +19 -3
  70. package/esm/components/data-grid/data-grid-row.js.map +1 -1
  71. package/esm/components/data-grid/data-grid.d.ts +12 -2
  72. package/esm/components/data-grid/data-grid.d.ts.map +1 -1
  73. package/esm/components/data-grid/data-grid.js +33 -13
  74. package/esm/components/data-grid/data-grid.js.map +1 -1
  75. package/esm/components/data-grid/data-grid.spec.js +170 -90
  76. package/esm/components/data-grid/data-grid.spec.js.map +1 -1
  77. package/esm/components/data-grid/filters/boolean-filter.d.ts +2 -2
  78. package/esm/components/data-grid/filters/boolean-filter.d.ts.map +1 -1
  79. package/esm/components/data-grid/filters/boolean-filter.js +4 -4
  80. package/esm/components/data-grid/filters/boolean-filter.js.map +1 -1
  81. package/esm/components/data-grid/filters/boolean-filter.spec.js +18 -17
  82. package/esm/components/data-grid/filters/boolean-filter.spec.js.map +1 -1
  83. package/esm/components/data-grid/filters/date-filter.d.ts +2 -2
  84. package/esm/components/data-grid/filters/date-filter.d.ts.map +1 -1
  85. package/esm/components/data-grid/filters/date-filter.js +6 -6
  86. package/esm/components/data-grid/filters/date-filter.js.map +1 -1
  87. package/esm/components/data-grid/filters/date-filter.spec.js +26 -21
  88. package/esm/components/data-grid/filters/date-filter.spec.js.map +1 -1
  89. package/esm/components/data-grid/filters/enum-filter.d.ts +2 -2
  90. package/esm/components/data-grid/filters/enum-filter.d.ts.map +1 -1
  91. package/esm/components/data-grid/filters/enum-filter.js +5 -5
  92. package/esm/components/data-grid/filters/enum-filter.js.map +1 -1
  93. package/esm/components/data-grid/filters/enum-filter.spec.js +21 -19
  94. package/esm/components/data-grid/filters/enum-filter.spec.js.map +1 -1
  95. package/esm/components/data-grid/filters/filter-dropdown.js +1 -1
  96. package/esm/components/data-grid/filters/filter-dropdown.js.map +1 -1
  97. package/esm/components/data-grid/filters/number-filter.d.ts +2 -2
  98. package/esm/components/data-grid/filters/number-filter.d.ts.map +1 -1
  99. package/esm/components/data-grid/filters/number-filter.js +5 -5
  100. package/esm/components/data-grid/filters/number-filter.js.map +1 -1
  101. package/esm/components/data-grid/filters/number-filter.spec.js +23 -21
  102. package/esm/components/data-grid/filters/number-filter.spec.js.map +1 -1
  103. package/esm/components/data-grid/filters/string-filter.d.ts +2 -2
  104. package/esm/components/data-grid/filters/string-filter.d.ts.map +1 -1
  105. package/esm/components/data-grid/filters/string-filter.js +5 -5
  106. package/esm/components/data-grid/filters/string-filter.js.map +1 -1
  107. package/esm/components/data-grid/filters/string-filter.spec.js +21 -19
  108. package/esm/components/data-grid/filters/string-filter.spec.js.map +1 -1
  109. package/esm/components/data-grid/footer.d.ts +2 -2
  110. package/esm/components/data-grid/footer.d.ts.map +1 -1
  111. package/esm/components/data-grid/footer.js +8 -13
  112. package/esm/components/data-grid/footer.js.map +1 -1
  113. package/esm/components/data-grid/footer.spec.js +38 -27
  114. package/esm/components/data-grid/footer.spec.js.map +1 -1
  115. package/esm/components/data-grid/header.d.ts +6 -6
  116. package/esm/components/data-grid/header.d.ts.map +1 -1
  117. package/esm/components/data-grid/header.js +16 -17
  118. package/esm/components/data-grid/header.js.map +1 -1
  119. package/esm/components/data-grid/header.spec.js +66 -60
  120. package/esm/components/data-grid/header.spec.js.map +1 -1
  121. package/esm/components/data-grid/selection-cell.d.ts.map +1 -1
  122. package/esm/components/data-grid/selection-cell.js +2 -2
  123. package/esm/components/data-grid/selection-cell.js.map +1 -1
  124. package/esm/components/dialog.d.ts +11 -0
  125. package/esm/components/dialog.d.ts.map +1 -1
  126. package/esm/components/dialog.js +3 -3
  127. package/esm/components/dialog.js.map +1 -1
  128. package/esm/components/dialog.spec.js +54 -2
  129. package/esm/components/dialog.spec.js.map +1 -1
  130. package/esm/components/divider.js +1 -1
  131. package/esm/components/divider.js.map +1 -1
  132. package/esm/components/drawer/drawer-toggle-button.js +1 -1
  133. package/esm/components/drawer/drawer-toggle-button.js.map +1 -1
  134. package/esm/components/drawer/index.js +1 -1
  135. package/esm/components/drawer/index.js.map +1 -1
  136. package/esm/components/dropdown.d.ts.map +1 -1
  137. package/esm/components/dropdown.js +2 -2
  138. package/esm/components/dropdown.js.map +1 -1
  139. package/esm/components/dropdown.spec.js +8 -0
  140. package/esm/components/dropdown.spec.js.map +1 -1
  141. package/esm/components/fab.js +1 -1
  142. package/esm/components/fab.js.map +1 -1
  143. package/esm/components/form.js +1 -1
  144. package/esm/components/form.js.map +1 -1
  145. package/esm/components/grid.js +1 -1
  146. package/esm/components/grid.js.map +1 -1
  147. package/esm/components/icons/icon.js +1 -1
  148. package/esm/components/icons/icon.js.map +1 -1
  149. package/esm/components/image.d.ts.map +1 -1
  150. package/esm/components/image.js +17 -8
  151. package/esm/components/image.js.map +1 -1
  152. package/esm/components/image.spec.js +60 -0
  153. package/esm/components/image.spec.js.map +1 -1
  154. package/esm/components/inputs/autocomplete.js +1 -1
  155. package/esm/components/inputs/autocomplete.js.map +1 -1
  156. package/esm/components/inputs/checkbox.d.ts.map +1 -1
  157. package/esm/components/inputs/checkbox.js +2 -1
  158. package/esm/components/inputs/checkbox.js.map +1 -1
  159. package/esm/components/inputs/checkbox.spec.js +1 -1
  160. package/esm/components/inputs/checkbox.spec.js.map +1 -1
  161. package/esm/components/inputs/input-number.js +1 -1
  162. package/esm/components/inputs/input-number.js.map +1 -1
  163. package/esm/components/inputs/input-number.spec.js +1 -1
  164. package/esm/components/inputs/input-number.spec.js.map +1 -1
  165. package/esm/components/inputs/input.js +1 -1
  166. package/esm/components/inputs/input.js.map +1 -1
  167. package/esm/components/inputs/input.spec.js +1 -1
  168. package/esm/components/inputs/input.spec.js.map +1 -1
  169. package/esm/components/inputs/radio-group.js +1 -1
  170. package/esm/components/inputs/radio-group.js.map +1 -1
  171. package/esm/components/inputs/radio-group.spec.js +1 -1
  172. package/esm/components/inputs/radio-group.spec.js.map +1 -1
  173. package/esm/components/inputs/radio.d.ts.map +1 -1
  174. package/esm/components/inputs/radio.js +2 -1
  175. package/esm/components/inputs/radio.js.map +1 -1
  176. package/esm/components/inputs/radio.spec.js +1 -1
  177. package/esm/components/inputs/radio.spec.js.map +1 -1
  178. package/esm/components/inputs/select.js +1 -1
  179. package/esm/components/inputs/select.js.map +1 -1
  180. package/esm/components/inputs/slider.d.ts.map +1 -1
  181. package/esm/components/inputs/slider.js +2 -1
  182. package/esm/components/inputs/slider.js.map +1 -1
  183. package/esm/components/inputs/switch.d.ts.map +1 -1
  184. package/esm/components/inputs/switch.js +2 -1
  185. package/esm/components/inputs/switch.js.map +1 -1
  186. package/esm/components/inputs/switch.spec.js +1 -1
  187. package/esm/components/inputs/switch.spec.js.map +1 -1
  188. package/esm/components/inputs/text-area.js +1 -1
  189. package/esm/components/inputs/text-area.js.map +1 -1
  190. package/esm/components/inputs/text-area.spec.js +1 -1
  191. package/esm/components/inputs/text-area.spec.js.map +1 -1
  192. package/esm/components/linear-progress.d.ts +2 -4
  193. package/esm/components/linear-progress.d.ts.map +1 -1
  194. package/esm/components/linear-progress.js +3 -6
  195. package/esm/components/linear-progress.js.map +1 -1
  196. package/esm/components/linear-progress.spec.js +21 -18
  197. package/esm/components/linear-progress.spec.js.map +1 -1
  198. package/esm/components/list/list-item.d.ts.map +1 -1
  199. package/esm/components/list/list-item.js +22 -6
  200. package/esm/components/list/list-item.js.map +1 -1
  201. package/esm/components/list/list.d.ts +7 -0
  202. package/esm/components/list/list.d.ts.map +1 -1
  203. package/esm/components/list/list.js +29 -9
  204. package/esm/components/list/list.js.map +1 -1
  205. package/esm/components/list/list.spec.js +117 -23
  206. package/esm/components/list/list.spec.js.map +1 -1
  207. package/esm/components/loader.js +1 -1
  208. package/esm/components/loader.js.map +1 -1
  209. package/esm/components/loader.spec.js +1 -1
  210. package/esm/components/loader.spec.js.map +1 -1
  211. package/esm/components/markdown/markdown-display.d.ts.map +1 -1
  212. package/esm/components/markdown/markdown-display.js +12 -2
  213. package/esm/components/markdown/markdown-display.js.map +1 -1
  214. package/esm/components/markdown/markdown-display.spec.js +98 -1
  215. package/esm/components/markdown/markdown-display.spec.js.map +1 -1
  216. package/esm/components/markdown/markdown-editor.js +1 -1
  217. package/esm/components/markdown/markdown-editor.js.map +1 -1
  218. package/esm/components/markdown/markdown-editor.spec.js +88 -1
  219. package/esm/components/markdown/markdown-editor.spec.js.map +1 -1
  220. package/esm/components/markdown/markdown-input.js +1 -1
  221. package/esm/components/markdown/markdown-input.js.map +1 -1
  222. package/esm/components/markdown/markdown-input.spec.js +1 -1
  223. package/esm/components/markdown/markdown-input.spec.js.map +1 -1
  224. package/esm/components/menu/menu.js +2 -2
  225. package/esm/components/menu/menu.js.map +1 -1
  226. package/esm/components/modal.d.ts +10 -0
  227. package/esm/components/modal.d.ts.map +1 -1
  228. package/esm/components/modal.js +25 -5
  229. package/esm/components/modal.js.map +1 -1
  230. package/esm/components/modal.spec.js +89 -4
  231. package/esm/components/modal.spec.js.map +1 -1
  232. package/esm/components/noty-list.js +2 -2
  233. package/esm/components/noty-list.js.map +1 -1
  234. package/esm/components/page-container/index.js +1 -1
  235. package/esm/components/page-container/index.js.map +1 -1
  236. package/esm/components/page-container/page-header.js +1 -1
  237. package/esm/components/page-container/page-header.js.map +1 -1
  238. package/esm/components/page-layout/index.js +2 -2
  239. package/esm/components/page-layout/index.js.map +1 -1
  240. package/esm/components/page-layout/index.spec.js +14 -0
  241. package/esm/components/page-layout/index.spec.js.map +1 -1
  242. package/esm/components/pagination.js +1 -1
  243. package/esm/components/pagination.js.map +1 -1
  244. package/esm/components/paper.js +1 -1
  245. package/esm/components/paper.js.map +1 -1
  246. package/esm/components/rating.d.ts.map +1 -1
  247. package/esm/components/rating.js +29 -22
  248. package/esm/components/rating.js.map +1 -1
  249. package/esm/components/rating.spec.js +152 -5
  250. package/esm/components/rating.spec.js.map +1 -1
  251. package/esm/components/result.js +1 -1
  252. package/esm/components/result.js.map +1 -1
  253. package/esm/components/skeleton.js +1 -1
  254. package/esm/components/skeleton.js.map +1 -1
  255. package/esm/components/suggest/index.d.ts.map +1 -1
  256. package/esm/components/suggest/index.js +15 -2
  257. package/esm/components/suggest/index.js.map +1 -1
  258. package/esm/components/suggest/index.spec.js +99 -44
  259. package/esm/components/suggest/index.spec.js.map +1 -1
  260. package/esm/components/suggest/suggest-input.js +1 -1
  261. package/esm/components/suggest/suggest-input.js.map +1 -1
  262. package/esm/components/suggest/suggest-input.spec.js +1 -1
  263. package/esm/components/suggest/suggest-input.spec.js.map +1 -1
  264. package/esm/components/suggest/suggestion-list.js +1 -1
  265. package/esm/components/suggest/suggestion-list.js.map +1 -1
  266. package/esm/components/suggest/suggestion-list.spec.js +1 -1
  267. package/esm/components/suggest/suggestion-list.spec.js.map +1 -1
  268. package/esm/components/tabs.d.ts.map +1 -1
  269. package/esm/components/tabs.js +6 -2
  270. package/esm/components/tabs.js.map +1 -1
  271. package/esm/components/timeline.js +2 -2
  272. package/esm/components/timeline.js.map +1 -1
  273. package/esm/components/tooltip.js +1 -1
  274. package/esm/components/tooltip.js.map +1 -1
  275. package/esm/components/tree/tree-item.d.ts.map +1 -1
  276. package/esm/components/tree/tree-item.js +19 -6
  277. package/esm/components/tree/tree-item.js.map +1 -1
  278. package/esm/components/tree/tree.d.ts +7 -0
  279. package/esm/components/tree/tree.d.ts.map +1 -1
  280. package/esm/components/tree/tree.js +13 -4
  281. package/esm/components/tree/tree.js.map +1 -1
  282. package/esm/components/tree/tree.spec.js +64 -2
  283. package/esm/components/tree/tree.spec.js.map +1 -1
  284. package/esm/components/typography.js +1 -1
  285. package/esm/components/typography.js.map +1 -1
  286. package/esm/components/wizard/index.js +1 -1
  287. package/esm/components/wizard/index.js.map +1 -1
  288. package/esm/components/wizard/index.spec.js +3 -3
  289. package/esm/components/wizard/index.spec.js.map +1 -1
  290. package/esm/services/collection-service.d.ts +9 -0
  291. package/esm/services/collection-service.d.ts.map +1 -1
  292. package/esm/services/collection-service.js +33 -11
  293. package/esm/services/collection-service.js.map +1 -1
  294. package/esm/services/collection-service.spec.js +33 -24
  295. package/esm/services/collection-service.spec.js.map +1 -1
  296. package/esm/services/css-variable-theme.d.ts +7 -0
  297. package/esm/services/css-variable-theme.d.ts.map +1 -1
  298. package/esm/services/css-variable-theme.js +23 -0
  299. package/esm/services/css-variable-theme.js.map +1 -1
  300. package/esm/services/css-variable-theme.spec.js +1 -0
  301. package/esm/services/css-variable-theme.spec.js.map +1 -1
  302. package/esm/services/list-service.d.ts +9 -0
  303. package/esm/services/list-service.d.ts.map +1 -1
  304. package/esm/services/list-service.js +13 -13
  305. package/esm/services/list-service.js.map +1 -1
  306. package/esm/services/list-service.spec.js +13 -33
  307. package/esm/services/list-service.spec.js.map +1 -1
  308. package/esm/services/theme-provider-service.d.ts +3 -0
  309. package/esm/services/theme-provider-service.d.ts.map +1 -1
  310. package/esm/services/theme-provider-service.js.map +1 -1
  311. package/esm/services/tree-service.d.ts.map +1 -1
  312. package/esm/services/tree-service.js +5 -9
  313. package/esm/services/tree-service.js.map +1 -1
  314. package/esm/services/tree-service.spec.js +12 -9
  315. package/esm/services/tree-service.spec.js.map +1 -1
  316. package/esm/themes/architect-theme.d.ts +1 -0
  317. package/esm/themes/architect-theme.d.ts.map +1 -1
  318. package/esm/themes/architect-theme.js +1 -0
  319. package/esm/themes/architect-theme.js.map +1 -1
  320. package/esm/themes/auditore-theme.d.ts +1 -0
  321. package/esm/themes/auditore-theme.d.ts.map +1 -1
  322. package/esm/themes/auditore-theme.js +1 -0
  323. package/esm/themes/auditore-theme.js.map +1 -1
  324. package/esm/themes/black-mesa-theme.d.ts +1 -0
  325. package/esm/themes/black-mesa-theme.d.ts.map +1 -1
  326. package/esm/themes/black-mesa-theme.js +1 -0
  327. package/esm/themes/black-mesa-theme.js.map +1 -1
  328. package/esm/themes/chieftain-theme.d.ts +1 -0
  329. package/esm/themes/chieftain-theme.d.ts.map +1 -1
  330. package/esm/themes/chieftain-theme.js +1 -0
  331. package/esm/themes/chieftain-theme.js.map +1 -1
  332. package/esm/themes/default-dark-theme.d.ts +1 -0
  333. package/esm/themes/default-dark-theme.d.ts.map +1 -1
  334. package/esm/themes/default-dark-theme.js +1 -0
  335. package/esm/themes/default-dark-theme.js.map +1 -1
  336. package/esm/themes/default-light-theme.d.ts +1 -0
  337. package/esm/themes/default-light-theme.d.ts.map +1 -1
  338. package/esm/themes/default-light-theme.js +1 -0
  339. package/esm/themes/default-light-theme.js.map +1 -1
  340. package/esm/themes/dragonborn-theme.d.ts +1 -0
  341. package/esm/themes/dragonborn-theme.d.ts.map +1 -1
  342. package/esm/themes/dragonborn-theme.js +1 -0
  343. package/esm/themes/dragonborn-theme.js.map +1 -1
  344. package/esm/themes/hawkins-theme.d.ts +1 -0
  345. package/esm/themes/hawkins-theme.d.ts.map +1 -1
  346. package/esm/themes/hawkins-theme.js +1 -0
  347. package/esm/themes/hawkins-theme.js.map +1 -1
  348. package/esm/themes/jedi-theme.d.ts +1 -0
  349. package/esm/themes/jedi-theme.d.ts.map +1 -1
  350. package/esm/themes/jedi-theme.js +1 -0
  351. package/esm/themes/jedi-theme.js.map +1 -1
  352. package/esm/themes/neon-runner-theme.d.ts +1 -0
  353. package/esm/themes/neon-runner-theme.d.ts.map +1 -1
  354. package/esm/themes/neon-runner-theme.js +1 -0
  355. package/esm/themes/neon-runner-theme.js.map +1 -1
  356. package/esm/themes/paladin-theme.d.ts +1 -0
  357. package/esm/themes/paladin-theme.d.ts.map +1 -1
  358. package/esm/themes/paladin-theme.js +1 -0
  359. package/esm/themes/paladin-theme.js.map +1 -1
  360. package/esm/themes/plumber-theme.d.ts +1 -0
  361. package/esm/themes/plumber-theme.d.ts.map +1 -1
  362. package/esm/themes/plumber-theme.js +1 -0
  363. package/esm/themes/plumber-theme.js.map +1 -1
  364. package/esm/themes/replicant-theme.d.ts +1 -0
  365. package/esm/themes/replicant-theme.d.ts.map +1 -1
  366. package/esm/themes/replicant-theme.js +1 -0
  367. package/esm/themes/replicant-theme.js.map +1 -1
  368. package/esm/themes/sandworm-theme.d.ts +1 -0
  369. package/esm/themes/sandworm-theme.d.ts.map +1 -1
  370. package/esm/themes/sandworm-theme.js +1 -0
  371. package/esm/themes/sandworm-theme.js.map +1 -1
  372. package/esm/themes/shadow-broker-theme.d.ts +1 -0
  373. package/esm/themes/shadow-broker-theme.d.ts.map +1 -1
  374. package/esm/themes/shadow-broker-theme.js +1 -0
  375. package/esm/themes/shadow-broker-theme.js.map +1 -1
  376. package/esm/themes/sith-theme.d.ts +1 -0
  377. package/esm/themes/sith-theme.d.ts.map +1 -1
  378. package/esm/themes/sith-theme.js +1 -0
  379. package/esm/themes/sith-theme.js.map +1 -1
  380. package/esm/themes/vault-dweller-theme.d.ts +1 -0
  381. package/esm/themes/vault-dweller-theme.d.ts.map +1 -1
  382. package/esm/themes/vault-dweller-theme.js +1 -0
  383. package/esm/themes/vault-dweller-theme.js.map +1 -1
  384. package/esm/themes/wild-hunt-theme.d.ts +1 -0
  385. package/esm/themes/wild-hunt-theme.d.ts.map +1 -1
  386. package/esm/themes/wild-hunt-theme.js +1 -0
  387. package/esm/themes/wild-hunt-theme.js.map +1 -1
  388. package/esm/themes/xenomorph-theme.d.ts +1 -0
  389. package/esm/themes/xenomorph-theme.d.ts.map +1 -1
  390. package/esm/themes/xenomorph-theme.js +1 -0
  391. package/esm/themes/xenomorph-theme.js.map +1 -1
  392. package/package.json +3 -3
  393. package/src/components/accordion/accordion-item.tsx +10 -15
  394. package/src/components/accordion/accordion.spec.tsx +134 -79
  395. package/src/components/accordion/accordion.tsx +14 -2
  396. package/src/components/alert.tsx +1 -1
  397. package/src/components/app-bar-link.tsx +1 -1
  398. package/src/components/app-bar.tsx +1 -1
  399. package/src/components/avatar.tsx +1 -1
  400. package/src/components/badge.tsx +1 -1
  401. package/src/components/breadcrumb.spec.tsx +3 -3
  402. package/src/components/breadcrumb.tsx +1 -1
  403. package/src/components/button-group.tsx +4 -4
  404. package/src/components/button.spec.tsx +1 -1
  405. package/src/components/button.tsx +1 -1
  406. package/src/components/cache-view.spec.tsx +2 -2
  407. package/src/components/cache-view.tsx +3 -3
  408. package/src/components/card.tsx +5 -5
  409. package/src/components/carousel.tsx +2 -2
  410. package/src/components/chip.spec.tsx +64 -0
  411. package/src/components/chip.tsx +5 -2
  412. package/src/components/circular-progress.spec.tsx +20 -14
  413. package/src/components/circular-progress.tsx +5 -11
  414. package/src/components/command-palette/command-palette-input.tsx +1 -1
  415. package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +1 -1
  416. package/src/components/command-palette/command-palette-suggestion-list.tsx +1 -1
  417. package/src/components/command-palette/index.spec.tsx +95 -33
  418. package/src/components/command-palette/index.tsx +16 -4
  419. package/src/components/context-menu/context-menu-item.tsx +1 -1
  420. package/src/components/context-menu/context-menu.tsx +1 -1
  421. package/src/components/data-grid/body.tsx +1 -1
  422. package/src/components/data-grid/data-grid-row.tsx +21 -3
  423. package/src/components/data-grid/data-grid.spec.tsx +246 -92
  424. package/src/components/data-grid/data-grid.tsx +52 -21
  425. package/src/components/data-grid/filters/boolean-filter.spec.tsx +29 -18
  426. package/src/components/data-grid/filters/boolean-filter.tsx +6 -6
  427. package/src/components/data-grid/filters/date-filter.spec.tsx +35 -22
  428. package/src/components/data-grid/filters/date-filter.tsx +8 -8
  429. package/src/components/data-grid/filters/enum-filter.spec.tsx +35 -20
  430. package/src/components/data-grid/filters/enum-filter.tsx +7 -7
  431. package/src/components/data-grid/filters/filter-dropdown.tsx +1 -1
  432. package/src/components/data-grid/filters/number-filter.spec.tsx +32 -22
  433. package/src/components/data-grid/filters/number-filter.tsx +7 -7
  434. package/src/components/data-grid/filters/string-filter.spec.tsx +32 -20
  435. package/src/components/data-grid/filters/string-filter.tsx +7 -7
  436. package/src/components/data-grid/footer.spec.tsx +79 -31
  437. package/src/components/data-grid/footer.tsx +10 -15
  438. package/src/components/data-grid/header.spec.tsx +152 -68
  439. package/src/components/data-grid/header.tsx +64 -27
  440. package/src/components/data-grid/selection-cell.tsx +2 -1
  441. package/src/components/dialog.spec.tsx +77 -2
  442. package/src/components/dialog.tsx +15 -2
  443. package/src/components/divider.tsx +1 -1
  444. package/src/components/drawer/drawer-toggle-button.tsx +1 -1
  445. package/src/components/drawer/index.tsx +1 -1
  446. package/src/components/dropdown.spec.tsx +9 -0
  447. package/src/components/dropdown.tsx +2 -1
  448. package/src/components/fab.tsx +1 -1
  449. package/src/components/form.tsx +1 -1
  450. package/src/components/grid.tsx +1 -1
  451. package/src/components/icons/icon.tsx +1 -1
  452. package/src/components/image.spec.tsx +82 -0
  453. package/src/components/image.tsx +18 -9
  454. package/src/components/inputs/autocomplete.tsx +1 -1
  455. package/src/components/inputs/checkbox.spec.tsx +1 -1
  456. package/src/components/inputs/checkbox.tsx +2 -1
  457. package/src/components/inputs/input-number.spec.tsx +1 -1
  458. package/src/components/inputs/input-number.tsx +1 -1
  459. package/src/components/inputs/input.spec.tsx +1 -1
  460. package/src/components/inputs/input.tsx +1 -1
  461. package/src/components/inputs/radio-group.spec.tsx +1 -1
  462. package/src/components/inputs/radio-group.tsx +1 -1
  463. package/src/components/inputs/radio.spec.tsx +1 -1
  464. package/src/components/inputs/radio.tsx +2 -1
  465. package/src/components/inputs/select.tsx +1 -1
  466. package/src/components/inputs/slider.tsx +2 -1
  467. package/src/components/inputs/switch.spec.tsx +1 -1
  468. package/src/components/inputs/switch.tsx +2 -1
  469. package/src/components/inputs/text-area.spec.tsx +1 -1
  470. package/src/components/inputs/text-area.tsx +1 -1
  471. package/src/components/linear-progress.spec.tsx +22 -18
  472. package/src/components/linear-progress.tsx +5 -11
  473. package/src/components/list/list-item.tsx +23 -5
  474. package/src/components/list/list.spec.tsx +165 -32
  475. package/src/components/list/list.tsx +38 -11
  476. package/src/components/loader.spec.tsx +1 -1
  477. package/src/components/loader.tsx +1 -1
  478. package/src/components/markdown/markdown-display.spec.tsx +133 -1
  479. package/src/components/markdown/markdown-display.tsx +13 -2
  480. package/src/components/markdown/markdown-editor.spec.tsx +124 -1
  481. package/src/components/markdown/markdown-editor.tsx +1 -1
  482. package/src/components/markdown/markdown-input.spec.tsx +1 -1
  483. package/src/components/markdown/markdown-input.tsx +1 -1
  484. package/src/components/menu/menu.tsx +2 -2
  485. package/src/components/modal.spec.tsx +127 -4
  486. package/src/components/modal.tsx +42 -4
  487. package/src/components/noty-list.tsx +2 -2
  488. package/src/components/page-container/index.tsx +1 -1
  489. package/src/components/page-container/page-header.tsx +1 -1
  490. package/src/components/page-layout/index.spec.tsx +20 -0
  491. package/src/components/page-layout/index.tsx +2 -2
  492. package/src/components/pagination.tsx +1 -1
  493. package/src/components/paper.tsx +1 -1
  494. package/src/components/rating.spec.tsx +200 -5
  495. package/src/components/rating.tsx +29 -23
  496. package/src/components/result.tsx +1 -1
  497. package/src/components/skeleton.tsx +1 -1
  498. package/src/components/suggest/index.spec.tsx +148 -44
  499. package/src/components/suggest/index.tsx +16 -3
  500. package/src/components/suggest/suggest-input.spec.tsx +1 -1
  501. package/src/components/suggest/suggest-input.tsx +1 -1
  502. package/src/components/suggest/suggestion-list.spec.tsx +1 -1
  503. package/src/components/suggest/suggestion-list.tsx +1 -1
  504. package/src/components/tabs.tsx +6 -2
  505. package/src/components/timeline.tsx +2 -2
  506. package/src/components/tooltip.tsx +1 -1
  507. package/src/components/tree/tree-item.tsx +20 -5
  508. package/src/components/tree/tree.spec.tsx +101 -2
  509. package/src/components/tree/tree.tsx +22 -4
  510. package/src/components/typography.tsx +1 -1
  511. package/src/components/wizard/index.spec.tsx +3 -3
  512. package/src/components/wizard/index.tsx +1 -1
  513. package/src/services/collection-service.spec.ts +33 -24
  514. package/src/services/collection-service.ts +35 -13
  515. package/src/services/css-variable-theme.spec.ts +1 -0
  516. package/src/services/css-variable-theme.ts +25 -0
  517. package/src/services/list-service.spec.ts +13 -42
  518. package/src/services/list-service.ts +15 -13
  519. package/src/services/theme-provider-service.ts +2 -0
  520. package/src/services/tree-service.spec.ts +12 -9
  521. package/src/services/tree-service.ts +5 -8
  522. package/src/themes/architect-theme.ts +1 -0
  523. package/src/themes/auditore-theme.ts +1 -0
  524. package/src/themes/black-mesa-theme.ts +1 -0
  525. package/src/themes/chieftain-theme.ts +1 -0
  526. package/src/themes/default-dark-theme.ts +1 -0
  527. package/src/themes/default-light-theme.ts +1 -0
  528. package/src/themes/dragonborn-theme.ts +1 -0
  529. package/src/themes/hawkins-theme.ts +1 -0
  530. package/src/themes/jedi-theme.ts +1 -0
  531. package/src/themes/neon-runner-theme.ts +1 -0
  532. package/src/themes/paladin-theme.ts +1 -0
  533. package/src/themes/plumber-theme.ts +1 -0
  534. package/src/themes/replicant-theme.ts +1 -0
  535. package/src/themes/sandworm-theme.ts +1 -0
  536. package/src/themes/shadow-broker-theme.ts +1 -0
  537. package/src/themes/sith-theme.ts +1 -0
  538. package/src/themes/vault-dweller-theme.ts +1 -0
  539. package/src/themes/wild-hunt-theme.ts +1 -0
  540. package/src/themes/xenomorph-theme.ts +1 -0
@@ -1,10 +1,18 @@
1
1
  import { Injector } from '@furystack/inject'
2
- import { createComponent, initializeShadeRoot } from '@furystack/shades'
2
+ import { createComponent, initializeShadeRoot, Shade } from '@furystack/shades'
3
3
  import { ObservableValue, sleepAsync, usingAsync } from '@furystack/utils'
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
5
  import { ThemeProviderService } from '../services/theme-provider-service.js'
6
6
  import { LinearProgress } from './linear-progress.js'
7
7
 
8
+ const ProgressWrapper = Shade<{ obs: ObservableValue<number> }>({
9
+ customElementName: 'test-linear-progress-wrapper',
10
+ render: ({ props, useObservable }) => {
11
+ const [value] = useObservable('value', props.obs)
12
+ return <LinearProgress variant="determinate" value={value} />
13
+ },
14
+ })
15
+
8
16
  describe('LinearProgress', () => {
9
17
  let originalAnimate: typeof Element.prototype.animate
10
18
 
@@ -35,7 +43,7 @@ describe('LinearProgress', () => {
35
43
  vi.restoreAllMocks()
36
44
  })
37
45
 
38
- it('should render with shadow DOM', async () => {
46
+ it('should render as custom element', async () => {
39
47
  await usingAsync(new Injector(), async (injector) => {
40
48
  const rootElement = document.getElementById('root') as HTMLDivElement
41
49
 
@@ -90,12 +98,11 @@ describe('LinearProgress', () => {
90
98
  it('should set aria-valuenow for determinate variant', async () => {
91
99
  await usingAsync(new Injector(), async (injector) => {
92
100
  const rootElement = document.getElementById('root') as HTMLDivElement
93
- const value = new ObservableValue(50)
94
101
 
95
102
  initializeShadeRoot({
96
103
  injector,
97
104
  rootElement,
98
- jsxElement: <LinearProgress variant="determinate" value={value} />,
105
+ jsxElement: <LinearProgress variant="determinate" value={50} />,
99
106
  })
100
107
 
101
108
  await sleepAsync(50)
@@ -110,12 +117,11 @@ describe('LinearProgress', () => {
110
117
  it('should set bar width based on value', async () => {
111
118
  await usingAsync(new Injector(), async (injector) => {
112
119
  const rootElement = document.getElementById('root') as HTMLDivElement
113
- const value = new ObservableValue(75)
114
120
 
115
121
  initializeShadeRoot({
116
122
  injector,
117
123
  rootElement,
118
- jsxElement: <LinearProgress variant="determinate" value={value} />,
124
+ jsxElement: <LinearProgress variant="determinate" value={75} />,
119
125
  })
120
126
 
121
127
  await sleepAsync(50)
@@ -128,12 +134,11 @@ describe('LinearProgress', () => {
128
134
  it('should clamp value to 0-100 range', async () => {
129
135
  await usingAsync(new Injector(), async (injector) => {
130
136
  const rootElement = document.getElementById('root') as HTMLDivElement
131
- const value = new ObservableValue(150)
132
137
 
133
138
  initializeShadeRoot({
134
139
  injector,
135
140
  rootElement,
136
- jsxElement: <LinearProgress variant="determinate" value={value} />,
141
+ jsxElement: <LinearProgress variant="determinate" value={150} />,
137
142
  })
138
143
 
139
144
  await sleepAsync(50)
@@ -146,12 +151,11 @@ describe('LinearProgress', () => {
146
151
  it('should clamp negative values to 0', async () => {
147
152
  await usingAsync(new Injector(), async (injector) => {
148
153
  const rootElement = document.getElementById('root') as HTMLDivElement
149
- const value = new ObservableValue(-20)
150
154
 
151
155
  initializeShadeRoot({
152
156
  injector,
153
157
  rootElement,
154
- jsxElement: <LinearProgress variant="determinate" value={value} />,
158
+ jsxElement: <LinearProgress variant="determinate" value={-20} />,
155
159
  })
156
160
 
157
161
  await sleepAsync(50)
@@ -161,15 +165,15 @@ describe('LinearProgress', () => {
161
165
  })
162
166
  })
163
167
 
164
- it('should update bar width when observable value changes', async () => {
168
+ it('should update bar width when value prop changes', async () => {
165
169
  await usingAsync(new Injector(), async (injector) => {
166
170
  const rootElement = document.getElementById('root') as HTMLDivElement
167
- const value = new ObservableValue(20)
171
+ const obs = new ObservableValue(20)
168
172
 
169
173
  initializeShadeRoot({
170
174
  injector,
171
175
  rootElement,
172
- jsxElement: <LinearProgress variant="determinate" value={value} />,
176
+ jsxElement: <ProgressWrapper obs={obs} />,
173
177
  })
174
178
 
175
179
  await sleepAsync(50)
@@ -177,22 +181,22 @@ describe('LinearProgress', () => {
177
181
  const bar = document.querySelector('shade-linear-progress .progress-bar') as HTMLElement
178
182
  expect(bar.style.width).toBe('20%')
179
183
 
180
- value.setValue(80)
184
+ obs.setValue(80)
181
185
  await sleepAsync(50)
182
186
 
183
187
  expect(bar.style.width).toBe('80%')
184
188
  })
185
189
  })
186
190
 
187
- it('should update aria-valuenow when observable value changes', async () => {
191
+ it('should update aria-valuenow when value prop changes', async () => {
188
192
  await usingAsync(new Injector(), async (injector) => {
189
193
  const rootElement = document.getElementById('root') as HTMLDivElement
190
- const value = new ObservableValue(30)
194
+ const obs = new ObservableValue(30)
191
195
 
192
196
  initializeShadeRoot({
193
197
  injector,
194
198
  rootElement,
195
- jsxElement: <LinearProgress variant="determinate" value={value} />,
199
+ jsxElement: <ProgressWrapper obs={obs} />,
196
200
  })
197
201
 
198
202
  await sleepAsync(50)
@@ -200,7 +204,7 @@ describe('LinearProgress', () => {
200
204
  const el = document.querySelector('shade-linear-progress') as HTMLElement
201
205
  expect(el.getAttribute('aria-valuenow')).toBe('30')
202
206
 
203
- value.setValue(90)
207
+ obs.setValue(90)
204
208
  await sleepAsync(50)
205
209
 
206
210
  expect(el.getAttribute('aria-valuenow')).toBe('90')
@@ -1,5 +1,4 @@
1
1
  import { Shade, createComponent } from '@furystack/shades'
2
- import type { ObservableValue } from '@furystack/utils'
3
2
  import { buildTransition, cssVariableTheme } from '../services/css-variable-theme.js'
4
3
  import type { Palette } from '../services/theme-provider-service.js'
5
4
  import { ThemeProviderService } from '../services/theme-provider-service.js'
@@ -7,10 +6,9 @@ import { promisifyAnimation } from '../utils/promisify-animation.js'
7
6
 
8
7
  export type LinearProgressProps = {
9
8
  /**
10
- * An observable progress value (0–100). Used when variant is 'determinate'.
11
- * The component subscribes internally and updates the bar without re-rendering.
9
+ * A progress value (0–100). Used when variant is 'determinate'.
12
10
  */
13
- value?: ObservableValue<number>
11
+ value?: number
14
12
  /**
15
13
  * The variant of the progress indicator.
16
14
  * - 'determinate': shows a fixed progress bar based on `value`
@@ -33,7 +31,7 @@ export type LinearProgressProps = {
33
31
  const clampValue = (v: number) => Math.max(0, Math.min(100, v))
34
32
 
35
33
  export const LinearProgress = Shade<LinearProgressProps>({
36
- shadowDomName: 'shade-linear-progress',
34
+ customElementName: 'shade-linear-progress',
37
35
  css: {
38
36
  display: 'block',
39
37
  fontFamily: cssVariableTheme.typography.fontFamily,
@@ -68,15 +66,11 @@ export const LinearProgress = Shade<LinearProgressProps>({
68
66
  transition: 'none',
69
67
  },
70
68
  },
71
- render: ({ props, injector, useObservable, useHostProps, useRef }) => {
69
+ render: ({ props, injector, useHostProps, useRef }) => {
72
70
  const themeProvider = injector.getInstance(ThemeProviderService)
73
71
  const barRef = useRef<HTMLElement>('progressBar')
74
72
  const variant = props.variant || 'indeterminate'
75
-
76
- if (variant === 'determinate' && props.value) {
77
- useObservable('progressValue', props.value)
78
- }
79
- const value = clampValue(props.value?.getValue() ?? 0)
73
+ const value = clampValue(props.value ?? 0)
80
74
 
81
75
  const color = themeProvider.theme.palette[props.color || 'primary'].main
82
76
  useHostProps({
@@ -14,7 +14,7 @@ export type ListItemProps<T> = {
14
14
  }
15
15
 
16
16
  export const ListItem: <T>(props: ListItemProps<T>, children: ChildrenList) => JSX.Element<any> = Shade({
17
- shadowDomName: 'shade-list-item',
17
+ customElementName: 'shade-list-item',
18
18
  css: {
19
19
  display: 'flex',
20
20
  fontFamily: cssVariableTheme.typography.fontFamily,
@@ -46,8 +46,21 @@ export const ListItem: <T>(props: ListItemProps<T>, children: ChildrenList) => J
46
46
  const isSelected = selection.includes(item)
47
47
 
48
48
  useHostProps({
49
+ tabIndex: isFocused ? 0 : -1,
50
+ 'data-spatial-nav-target': '',
49
51
  role: 'option',
50
52
  'aria-selected': isSelected.toString(),
53
+ onpointerdown: () => {
54
+ listService.setFocusAnchor()
55
+ },
56
+ onfocus: () => {
57
+ if (listService.focusedItem.getValue() !== item) {
58
+ listService.focusedItem.setValue(item)
59
+ }
60
+ if (!listService.hasFocus.getValue()) {
61
+ listService.hasFocus.setValue(true)
62
+ }
63
+ },
51
64
  onclick: (ev: MouseEvent) => {
52
65
  listService.handleItemClick(item, ev)
53
66
  },
@@ -65,11 +78,16 @@ export const ListItem: <T>(props: ListItemProps<T>, children: ChildrenList) => J
65
78
  queueMicrotask(() => {
66
79
  const el = wrapperRef.current
67
80
  if (!el) return
81
+ const hostEl = el.closest('shade-list-item') as HTMLElement
82
+ if (!hostEl) return
83
+
84
+ if (document.activeElement !== hostEl) {
85
+ hostEl.focus({ preventScroll: true })
86
+ }
87
+
68
88
  const scrollContainer = el.closest('shade-list') as HTMLElement
69
89
  if (scrollContainer) {
70
90
  const containerRect = scrollContainer.getBoundingClientRect()
71
- const hostEl = el.closest('shade-list-item') as HTMLElement
72
- if (!hostEl) return
73
91
  const itemRect = hostEl.getBoundingClientRect()
74
92
  const itemTopInContainer = itemRect.top - containerRect.top
75
93
  const itemBottomInContainer = itemRect.bottom - containerRect.top
@@ -77,12 +95,12 @@ export const ListItem: <T>(props: ListItemProps<T>, children: ChildrenList) => J
77
95
  if (itemTopInContainer < 0) {
78
96
  scrollContainer.scrollTo({
79
97
  top: scrollContainer.scrollTop + itemTopInContainer,
80
- behavior: 'smooth',
98
+ behavior: 'instant',
81
99
  })
82
100
  } else if (itemBottomInContainer > scrollContainer.clientHeight) {
83
101
  scrollContainer.scrollTo({
84
102
  top: scrollContainer.scrollTop + (itemBottomInContainer - scrollContainer.clientHeight),
85
- behavior: 'smooth',
103
+ behavior: 'instant',
86
104
  })
87
105
  }
88
106
  }
@@ -252,7 +252,7 @@ describe('List', () => {
252
252
  })
253
253
  })
254
254
 
255
- it('should lose focus on click outside', async () => {
255
+ it('should lose focus on focusout to an outside element', async () => {
256
256
  await usingAsync(new Injector(), async (injector) => {
257
257
  const rootElement = document.getElementById('root') as HTMLDivElement
258
258
  const service = createTestService()
@@ -262,7 +262,7 @@ describe('List', () => {
262
262
  rootElement,
263
263
  jsxElement: (
264
264
  <>
265
- <div data-testid="outside">Outside</div>
265
+ <button data-testid="outside">Outside</button>
266
266
  <List<TestItem> items={testItems} listService={service} renderItem={(item) => <span>{item.name}</span>} />
267
267
  </>
268
268
  ),
@@ -277,7 +277,7 @@ describe('List', () => {
277
277
  expect(service.hasFocus.getValue()).toBe(true)
278
278
 
279
279
  const outside = document.querySelector('[data-testid="outside"]') as HTMLElement
280
- outside?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
280
+ wrapper?.dispatchEvent(new FocusEvent('focusout', { bubbles: true, relatedTarget: outside }))
281
281
 
282
282
  expect(service.hasFocus.getValue()).toBe(false)
283
283
 
@@ -336,6 +336,87 @@ describe('List', () => {
336
336
  service[Symbol.dispose]()
337
337
  })
338
338
  })
339
+
340
+ it('should not initialize focusedItem on wrapper focusin (items handle focus individually)', async () => {
341
+ await usingAsync(new Injector(), async (injector) => {
342
+ const rootElement = document.getElementById('root') as HTMLDivElement
343
+ const service = createTestService()
344
+
345
+ initializeShadeRoot({
346
+ injector,
347
+ rootElement,
348
+ jsxElement: (
349
+ <List<TestItem> items={testItems} listService={service} renderItem={(item) => <span>{item.name}</span>} />
350
+ ),
351
+ })
352
+
353
+ await flushUpdates()
354
+ await new Promise((r) => setTimeout(r, 0))
355
+
356
+ expect(service.hasFocus.getValue()).toBe(false)
357
+ expect(service.focusedItem.getValue()).toBeUndefined()
358
+
359
+ service[Symbol.dispose]()
360
+ })
361
+ })
362
+
363
+ it('should clear hasFocus on focusout when focus moves outside', async () => {
364
+ await usingAsync(new Injector(), async (injector) => {
365
+ const rootElement = document.getElementById('root') as HTMLDivElement
366
+ const service = createTestService()
367
+ const outsideEl = document.createElement('button')
368
+ outsideEl.textContent = 'Outside'
369
+ document.body.appendChild(outsideEl)
370
+
371
+ initializeShadeRoot({
372
+ injector,
373
+ rootElement,
374
+ jsxElement: (
375
+ <List<TestItem> items={testItems} listService={service} renderItem={(item) => <span>{item.name}</span>} />
376
+ ),
377
+ })
378
+
379
+ await flushUpdates()
380
+ await new Promise((r) => setTimeout(r, 0))
381
+
382
+ service.hasFocus.setValue(true)
383
+
384
+ const wrapper = document.querySelector('.shade-list-wrapper') as HTMLElement
385
+ wrapper?.dispatchEvent(new FocusEvent('focusout', { bubbles: true, relatedTarget: outsideEl }))
386
+
387
+ expect(service.hasFocus.getValue()).toBe(false)
388
+
389
+ outsideEl.remove()
390
+ service[Symbol.dispose]()
391
+ })
392
+ })
393
+
394
+ it('should clear hasFocus on focusout when relatedTarget is null', async () => {
395
+ await usingAsync(new Injector(), async (injector) => {
396
+ const rootElement = document.getElementById('root') as HTMLDivElement
397
+ const service = createTestService()
398
+
399
+ initializeShadeRoot({
400
+ injector,
401
+ rootElement,
402
+ jsxElement: (
403
+ <List<TestItem> items={testItems} listService={service} renderItem={(item) => <span>{item.name}</span>} />
404
+ ),
405
+ })
406
+
407
+ await flushUpdates()
408
+ await new Promise((r) => setTimeout(r, 0))
409
+
410
+ service.hasFocus.setValue(true)
411
+
412
+ const wrapper = document.querySelector('.shade-list-wrapper') as HTMLElement
413
+ wrapper?.dispatchEvent(new FocusEvent('focusout', { bubbles: true, relatedTarget: null }))
414
+
415
+ expect(service.hasFocus.getValue()).toBe(false)
416
+
417
+ service[Symbol.dispose]()
418
+ })
419
+ })
339
420
  })
340
421
 
341
422
  describe('selection', () => {
@@ -426,7 +507,7 @@ describe('List', () => {
426
507
  })
427
508
 
428
509
  describe('keyboard navigation', () => {
429
- it('should handle ArrowDown to move focus to next item', async () => {
510
+ it('should not handle ArrowDown (delegated to spatial navigation)', async () => {
430
511
  await usingAsync(new Injector(), async (injector) => {
431
512
  const rootElement = document.getElementById('root') as HTMLDivElement
432
513
  const service = createTestService()
@@ -446,13 +527,13 @@ describe('List', () => {
446
527
 
447
528
  window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }))
448
529
 
449
- expect(service.focusedItem.getValue()).toEqual(testItems[1])
530
+ expect(service.focusedItem.getValue()).toEqual(testItems[0])
450
531
 
451
532
  service[Symbol.dispose]()
452
533
  })
453
534
  })
454
535
 
455
- it('should handle ArrowUp to move focus to previous item', async () => {
536
+ it('should not handle ArrowUp (delegated to spatial navigation)', async () => {
456
537
  await usingAsync(new Injector(), async (injector) => {
457
538
  const rootElement = document.getElementById('root') as HTMLDivElement
458
539
  const service = createTestService()
@@ -472,7 +553,7 @@ describe('List', () => {
472
553
 
473
554
  window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }))
474
555
 
475
- expect(service.focusedItem.getValue()).toEqual(testItems[0])
556
+ expect(service.focusedItem.getValue()).toEqual(testItems[1])
476
557
 
477
558
  service[Symbol.dispose]()
478
559
  })
@@ -666,31 +747,6 @@ describe('List', () => {
666
747
  })
667
748
  })
668
749
 
669
- it('should handle Tab to toggle focus', async () => {
670
- await usingAsync(new Injector(), async (injector) => {
671
- const rootElement = document.getElementById('root') as HTMLDivElement
672
- const service = createTestService()
673
-
674
- service.hasFocus.setValue(true)
675
-
676
- initializeShadeRoot({
677
- injector,
678
- rootElement,
679
- jsxElement: (
680
- <List<TestItem> items={testItems} listService={service} renderItem={(item) => <span>{item.name}</span>} />
681
- ),
682
- })
683
-
684
- await flushUpdates()
685
-
686
- window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }))
687
-
688
- expect(service.hasFocus.getValue()).toBe(false)
689
-
690
- service[Symbol.dispose]()
691
- })
692
- })
693
-
694
750
  it('should not handle keyboard when not focused', async () => {
695
751
  await usingAsync(new Injector(), async (injector) => {
696
752
  const rootElement = document.getElementById('root') as HTMLDivElement
@@ -956,6 +1012,83 @@ describe('List', () => {
956
1012
  })
957
1013
  })
958
1014
 
1015
+ describe('item spatial navigation attributes', () => {
1016
+ it('should set data-spatial-nav-target on list items', async () => {
1017
+ await usingAsync(new Injector(), async (injector) => {
1018
+ const rootElement = document.getElementById('root') as HTMLDivElement
1019
+ const service = createTestService()
1020
+
1021
+ initializeShadeRoot({
1022
+ injector,
1023
+ rootElement,
1024
+ jsxElement: (
1025
+ <List<TestItem> items={testItems} listService={service} renderItem={(item) => <span>{item.name}</span>} />
1026
+ ),
1027
+ })
1028
+
1029
+ await flushUpdates()
1030
+
1031
+ const items = document.querySelectorAll('shade-list-item')
1032
+ for (const item of items) {
1033
+ expect(item.hasAttribute('data-spatial-nav-target')).toBe(true)
1034
+ }
1035
+
1036
+ service[Symbol.dispose]()
1037
+ })
1038
+ })
1039
+
1040
+ it('should set tabIndex 0 on focused item and -1 on others', async () => {
1041
+ await usingAsync(new Injector(), async (injector) => {
1042
+ const rootElement = document.getElementById('root') as HTMLDivElement
1043
+ const service = createTestService()
1044
+
1045
+ service.focusedItem.setValue(testItems[1])
1046
+
1047
+ initializeShadeRoot({
1048
+ injector,
1049
+ rootElement,
1050
+ jsxElement: (
1051
+ <List<TestItem> items={testItems} listService={service} renderItem={(item) => <span>{item.name}</span>} />
1052
+ ),
1053
+ })
1054
+
1055
+ await flushUpdates()
1056
+
1057
+ const items = document.querySelectorAll<HTMLDivElement>('shade-list-item')
1058
+ expect(items[0]?.tabIndex).toBe(-1)
1059
+ expect(items[1]?.tabIndex).toBe(0)
1060
+ expect(items[2]?.tabIndex).toBe(-1)
1061
+
1062
+ service[Symbol.dispose]()
1063
+ })
1064
+ })
1065
+
1066
+ it('should sync focusedItem on item onfocus', async () => {
1067
+ await usingAsync(new Injector(), async (injector) => {
1068
+ const rootElement = document.getElementById('root') as HTMLDivElement
1069
+ const service = createTestService()
1070
+
1071
+ initializeShadeRoot({
1072
+ injector,
1073
+ rootElement,
1074
+ jsxElement: (
1075
+ <List<TestItem> items={testItems} listService={service} renderItem={(item) => <span>{item.name}</span>} />
1076
+ ),
1077
+ })
1078
+
1079
+ await flushUpdates()
1080
+
1081
+ const items = document.querySelectorAll('shade-list-item')
1082
+ items[2]?.dispatchEvent(new FocusEvent('focus'))
1083
+
1084
+ expect(service.focusedItem.getValue()).toEqual(testItems[2])
1085
+ expect(service.hasFocus.getValue()).toBe(true)
1086
+
1087
+ service[Symbol.dispose]()
1088
+ })
1089
+ })
1090
+ })
1091
+
959
1092
  describe('keyboard listener cleanup', () => {
960
1093
  it('should remove keyboard listener when component is disconnected', async () => {
961
1094
  await usingAsync(new Injector(), async (injector) => {
@@ -1,11 +1,12 @@
1
1
  import type { ChildrenList, PartialElement } from '@furystack/shades'
2
2
  import { createComponent, Shade } from '@furystack/shades'
3
- import { ClickAwayService } from '../../services/click-away-service.js'
4
3
  import { cssVariableTheme } from '../../services/css-variable-theme.js'
5
4
  import type { ListService } from '../../services/list-service.js'
6
5
  import { Pagination } from '../pagination.js'
7
6
  import { ListItem } from './list-item.js'
8
7
 
8
+ let nextListId = 0
9
+
9
10
  export type ListItemState = {
10
11
  isFocused: boolean
11
12
  isSelected: boolean
@@ -31,10 +32,17 @@ export type ListProps<T> = {
31
32
  onSelectionChange?: (selected: T[]) => void
32
33
  /** Optional pagination configuration. When provided, items are sliced and a Pagination control is rendered. */
33
34
  pagination?: ListPaginationProps
35
+ /**
36
+ * Section name for spatial navigation scoping.
37
+ * Sets `data-nav-section` on the list wrapper so that SpatialNavigationService
38
+ * constrains arrow-key navigation within the list.
39
+ * Auto-generated per instance when not provided.
40
+ */
41
+ navSection?: string
34
42
  } & PartialElement<HTMLDivElement>
35
43
 
36
44
  export const List: <T>(props: ListProps<T>, children: ChildrenList) => JSX.Element<any> = Shade({
37
- shadowDomName: 'shade-list',
45
+ customElementName: 'shade-list',
38
46
  css: {
39
47
  display: 'block',
40
48
  fontFamily: cssVariableTheme.typography.fontFamily,
@@ -46,8 +54,9 @@ export const List: <T>(props: ListProps<T>, children: ChildrenList) => JSX.Eleme
46
54
  padding: '8px 0',
47
55
  },
48
56
  },
49
- render: ({ props, useDisposable, useHostProps, useRef }) => {
57
+ render: ({ props, useDisposable, useHostProps, useRef, useState }) => {
50
58
  const wrapperRef = useRef<HTMLDivElement>('listWrapper')
59
+ const [navSectionId] = useState('navSectionId', String(nextListId++))
51
60
 
52
61
  useDisposable('keydown-handler', () => {
53
62
  const listener = (ev: KeyboardEvent) => {
@@ -60,8 +69,8 @@ export const List: <T>(props: ListProps<T>, children: ChildrenList) => JSX.Eleme
60
69
  }
61
70
  }
62
71
  }
63
- window.addEventListener('keydown', listener)
64
- return { [Symbol.dispose]: () => window.removeEventListener('keydown', listener) }
72
+ window.addEventListener('keydown', listener, true)
73
+ return { [Symbol.dispose]: () => window.removeEventListener('keydown', listener, true) }
65
74
  })
66
75
 
67
76
  const { pagination } = props
@@ -79,13 +88,30 @@ export const List: <T>(props: ListProps<T>, children: ChildrenList) => JSX.Eleme
79
88
 
80
89
  props.listService.items.setValue(visibleItems)
81
90
 
82
- useDisposable(
83
- 'clickAway',
84
- () =>
85
- new ClickAwayService(wrapperRef, () => {
91
+ useDisposable('focus-coordination', () => {
92
+ const handleFocusOut = (ev: FocusEvent) => {
93
+ const wrapper = wrapperRef.current
94
+ if (wrapper && (!ev.relatedTarget || !wrapper.contains(ev.relatedTarget as Node))) {
86
95
  props.listService.hasFocus.setValue(false)
87
- }),
88
- )
96
+ }
97
+ }
98
+
99
+ queueMicrotask(() => {
100
+ const wrapper = wrapperRef.current
101
+ if (wrapper) {
102
+ wrapper.addEventListener('focusout', handleFocusOut)
103
+ }
104
+ })
105
+
106
+ return {
107
+ [Symbol.dispose]: () => {
108
+ const wrapper = wrapperRef.current
109
+ if (wrapper) {
110
+ wrapper.removeEventListener('focusout', handleFocusOut)
111
+ }
112
+ },
113
+ }
114
+ })
89
115
 
90
116
  if (props.onSelectionChange) {
91
117
  const { onSelectionChange } = props
@@ -107,6 +133,7 @@ export const List: <T>(props: ListProps<T>, children: ChildrenList) => JSX.Eleme
107
133
  role="listbox"
108
134
  ariaMultiSelectable="true"
109
135
  className="shade-list-wrapper"
136
+ data-nav-section={props.navSection ?? `list-${navSectionId}`}
110
137
  onclick={() => props.listService.hasFocus.setValue(true)}
111
138
  >
112
139
  {visibleItems.map((item) => (
@@ -40,7 +40,7 @@ describe('Loader', () => {
40
40
  vi.restoreAllMocks()
41
41
  })
42
42
 
43
- it('should render with shadow DOM', async () => {
43
+ it('should render as custom element', async () => {
44
44
  await usingAsync(new Injector(), async (injector) => {
45
45
  const rootElement = document.getElementById('root') as HTMLDivElement
46
46
 
@@ -25,7 +25,7 @@ interface LoaderProps {
25
25
  }
26
26
 
27
27
  export const Loader = Shade<LoaderProps>({
28
- shadowDomName: 'shade-loader',
28
+ customElementName: 'shade-loader',
29
29
  css: {
30
30
  display: 'inline-block',
31
31
  fontFamily: cssVariableTheme.typography.fontFamily,