@adia-ai/web-components 0.6.33 → 0.6.35

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 (391) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/color/index.js +1 -1
  3. package/components/accordion/accordion-item.yaml +2 -2
  4. package/components/accordion/accordion.css +2 -2
  5. package/components/accordion/accordion.js +1 -1
  6. package/components/action-list/action-item.yaml +2 -2
  7. package/components/action-list/action-list.css +2 -2
  8. package/components/action-list/action-list.js +1 -1
  9. package/components/agent-artifact/{class.js → agent-artifact.class.js} +1 -1
  10. package/components/agent-artifact/agent-artifact.css +31 -31
  11. package/components/agent-artifact/agent-artifact.js +1 -1
  12. package/components/agent-feedback-bar/agent-feedback-bar.css +10 -10
  13. package/components/agent-feedback-bar/agent-feedback-bar.js +1 -1
  14. package/components/agent-questions/agent-questions.css +57 -57
  15. package/components/agent-questions/agent-questions.js +1 -1
  16. package/components/agent-reasoning/agent-reasoning.css +62 -62
  17. package/components/agent-reasoning/agent-reasoning.js +1 -1
  18. package/components/agent-suggestions/agent-suggestions.css +4 -4
  19. package/components/agent-suggestions/agent-suggestions.js +1 -1
  20. package/components/agent-trace/agent-trace.css +53 -53
  21. package/components/alert/alert.a2ui.json +64 -1
  22. package/components/alert/{class.js → alert.class.js} +189 -2
  23. package/components/alert/alert.css +119 -41
  24. package/components/alert/alert.d.ts +14 -0
  25. package/components/alert/alert.js +1 -1
  26. package/components/alert/alert.test.js +184 -0
  27. package/components/alert/alert.yaml +114 -1
  28. package/components/avatar/avatar-group.yaml +2 -2
  29. package/components/avatar/avatar.css +27 -27
  30. package/components/avatar/avatar.js +1 -1
  31. package/components/badge/badge.css +27 -27
  32. package/components/badge/badge.js +1 -1
  33. package/components/block/block.css +16 -16
  34. package/components/block/block.js +1 -1
  35. package/components/breadcrumb/breadcrumb.css +23 -23
  36. package/components/breadcrumb/breadcrumb.js +1 -1
  37. package/components/button/button.css +101 -91
  38. package/components/button/button.js +1 -1
  39. package/components/calendar-grid/calendar-grid.a2ui.json +146 -0
  40. package/components/calendar-grid/calendar-grid.class.js +326 -0
  41. package/components/calendar-grid/calendar-grid.css +246 -0
  42. package/components/calendar-grid/calendar-grid.d.ts +41 -0
  43. package/components/calendar-grid/calendar-grid.js +17 -0
  44. package/components/calendar-grid/calendar-grid.yaml +136 -0
  45. package/components/calendar-picker/calendar-picker.css +139 -139
  46. package/components/calendar-picker/calendar-picker.js +1 -1
  47. package/components/canvas/canvas.css +12 -12
  48. package/components/card/card.css +83 -83
  49. package/components/card/card.js +1 -1
  50. package/components/chart/chart.css +224 -224
  51. package/components/chart/chart.js +1 -1
  52. package/components/chart-legend/chart-legend.css +26 -26
  53. package/components/chart-legend/chart-legend.js +1 -1
  54. package/components/chat-thread/chat-input.a2ui.json +1 -1
  55. package/components/chat-thread/chat-input.js +6 -1
  56. package/components/chat-thread/chat-input.yaml +4 -1
  57. package/components/chat-thread/chat-thread.js +1 -1
  58. package/components/check/check.css +40 -40
  59. package/components/check/check.js +1 -1
  60. package/components/code/code.css +125 -125
  61. package/components/code/code.js +1 -1
  62. package/components/col/col.css +15 -15
  63. package/components/col/col.js +1 -1
  64. package/components/color-input/color-input.js +1 -1
  65. package/components/color-picker/color-picker.css +55 -55
  66. package/components/color-picker/color-picker.js +1 -1
  67. package/components/combobox/combobox.a2ui.json +363 -0
  68. package/components/combobox/combobox.class.js +861 -0
  69. package/components/combobox/combobox.css +244 -0
  70. package/components/combobox/combobox.d.ts +113 -0
  71. package/components/combobox/combobox.examples.md +59 -0
  72. package/components/combobox/combobox.js +17 -0
  73. package/components/combobox/combobox.test.js +181 -0
  74. package/components/combobox/combobox.yaml +369 -0
  75. package/components/command/command.css +90 -90
  76. package/components/command/command.js +1 -1
  77. package/components/date-range-picker/date-range-picker.a2ui.json +300 -0
  78. package/components/date-range-picker/date-range-picker.class.js +791 -0
  79. package/components/date-range-picker/date-range-picker.css +224 -0
  80. package/components/date-range-picker/date-range-picker.d.ts +82 -0
  81. package/components/date-range-picker/date-range-picker.examples.md +37 -0
  82. package/components/date-range-picker/date-range-picker.js +17 -0
  83. package/components/date-range-picker/date-range-picker.test.js +387 -0
  84. package/components/date-range-picker/date-range-picker.yaml +285 -0
  85. package/components/datetime-picker/datetime-picker.a2ui.json +334 -0
  86. package/components/datetime-picker/datetime-picker.class.js +706 -0
  87. package/components/datetime-picker/datetime-picker.css +150 -0
  88. package/components/datetime-picker/datetime-picker.d.ts +86 -0
  89. package/components/datetime-picker/datetime-picker.examples.md +46 -0
  90. package/components/datetime-picker/datetime-picker.js +17 -0
  91. package/components/datetime-picker/datetime-picker.test.js +454 -0
  92. package/components/datetime-picker/datetime-picker.yaml +332 -0
  93. package/components/demo-toggle/demo-toggle.css +27 -27
  94. package/components/demo-toggle/demo-toggle.js +1 -1
  95. package/components/description-list/description-list.css +18 -18
  96. package/components/description-list/description-list.js +1 -1
  97. package/components/divider/divider.css +24 -24
  98. package/components/divider/divider.js +1 -1
  99. package/components/drawer/drawer.js +1 -1
  100. package/components/embed/embed.css +6 -6
  101. package/components/embed/embed.js +1 -1
  102. package/components/empty-state/empty-state.css +27 -27
  103. package/components/empty-state/empty-state.js +1 -1
  104. package/components/feed/feed.css +12 -12
  105. package/components/feed/feed.js +1 -1
  106. package/components/field/field.css +28 -28
  107. package/components/field/field.js +1 -1
  108. package/components/field/field.test.js +1 -1
  109. package/components/fields/fields.css +5 -5
  110. package/components/fields/fields.js +1 -1
  111. package/components/grid/grid.css +5 -5
  112. package/components/grid/grid.js +1 -1
  113. package/components/heatmap/heatmap.css +63 -63
  114. package/components/heatmap/heatmap.js +1 -1
  115. package/components/icon/icon.css +12 -12
  116. package/components/icon/icon.js +1 -1
  117. package/components/image/image.css +14 -14
  118. package/components/image/image.js +1 -1
  119. package/components/index.js +11 -0
  120. package/components/inline-message/inline-message.a2ui.json +143 -0
  121. package/components/inline-message/inline-message.class.js +169 -0
  122. package/components/inline-message/inline-message.css +75 -0
  123. package/components/inline-message/inline-message.d.ts +31 -0
  124. package/components/inline-message/inline-message.examples.md +19 -0
  125. package/components/inline-message/inline-message.js +17 -0
  126. package/components/inline-message/inline-message.test.js +203 -0
  127. package/components/inline-message/inline-message.yaml +205 -0
  128. package/components/input/input.css +67 -67
  129. package/components/input/input.js +1 -1
  130. package/components/input/input.yaml +5 -4
  131. package/components/inspector/inspector.css +6 -6
  132. package/components/inspector/inspector.js +1 -1
  133. package/components/integration-card/integration-card.a2ui.json +268 -0
  134. package/components/integration-card/integration-card.class.js +410 -0
  135. package/components/integration-card/integration-card.css +169 -0
  136. package/components/integration-card/integration-card.d.ts +63 -0
  137. package/components/integration-card/integration-card.examples.md +41 -0
  138. package/components/integration-card/integration-card.js +17 -0
  139. package/components/integration-card/integration-card.test.js +306 -0
  140. package/components/integration-card/integration-card.yaml +280 -0
  141. package/components/kbd/kbd.css +32 -32
  142. package/components/kbd/kbd.js +1 -1
  143. package/components/link/link.css +12 -12
  144. package/components/link/link.js +1 -1
  145. package/components/list/list-item.yaml +2 -2
  146. package/components/list/list.css +8 -8
  147. package/components/list/list.js +1 -1
  148. package/components/list-window/list-window.a2ui.json +277 -0
  149. package/components/list-window/list-window.class.js +688 -0
  150. package/components/list-window/list-window.css +124 -0
  151. package/components/list-window/list-window.d.ts +84 -0
  152. package/components/list-window/list-window.examples.md +73 -0
  153. package/components/list-window/list-window.js +17 -0
  154. package/components/list-window/list-window.test.js +303 -0
  155. package/components/list-window/list-window.yaml +270 -0
  156. package/components/loading-overlay/loading-overlay.a2ui.json +176 -0
  157. package/components/loading-overlay/loading-overlay.class.js +203 -0
  158. package/components/loading-overlay/loading-overlay.css +81 -0
  159. package/components/loading-overlay/loading-overlay.d.ts +24 -0
  160. package/components/loading-overlay/loading-overlay.examples.md +50 -0
  161. package/components/loading-overlay/loading-overlay.js +17 -0
  162. package/components/loading-overlay/loading-overlay.test.js +257 -0
  163. package/components/loading-overlay/loading-overlay.yaml +260 -0
  164. package/components/menu/menu-divider.yaml +1 -1
  165. package/components/menu/menu-item.yaml +1 -1
  166. package/components/menu/menu.a2ui.json +3 -0
  167. package/components/menu/menu.css +8 -8
  168. package/components/menu/menu.js +1 -1
  169. package/components/menu/menu.yaml +7 -0
  170. package/components/modal/{class.js → modal.class.js} +12 -1
  171. package/components/modal/modal.css +54 -44
  172. package/components/modal/modal.js +1 -1
  173. package/components/nav/nav.css +40 -40
  174. package/components/nav/nav.js +1 -1
  175. package/components/nav-group/nav-group.css +52 -52
  176. package/components/nav-group/nav-group.js +1 -1
  177. package/components/nav-item/nav-item.css +44 -44
  178. package/components/nav-item/nav-item.js +1 -1
  179. package/components/noodles/noodles.css +31 -31
  180. package/components/noodles/noodles.js +1 -1
  181. package/components/option-card/option-card.css +69 -69
  182. package/components/option-card/option-card.js +1 -1
  183. package/components/otp-input/otp-input.css +30 -30
  184. package/components/otp-input/otp-input.js +1 -1
  185. package/components/page/page.css +18 -18
  186. package/components/page/page.js +1 -1
  187. package/components/pagination/pagination.css +61 -61
  188. package/components/pagination/pagination.js +1 -1
  189. package/components/pane/pane.css +57 -57
  190. package/components/pane/pane.js +1 -1
  191. package/components/pipeline-status/pipeline-status.css +65 -65
  192. package/components/pipeline-status/pipeline-status.js +1 -1
  193. package/components/popover/popover.a2ui.json +8 -1
  194. package/components/popover/popover.css +17 -17
  195. package/components/popover/popover.js +1 -1
  196. package/components/popover/popover.yaml +14 -1
  197. package/components/progress/progress.css +23 -23
  198. package/components/progress/progress.js +1 -1
  199. package/components/progress-row/progress-row.css +17 -17
  200. package/components/progress-row/progress-row.js +1 -1
  201. package/components/radio/radio.css +39 -39
  202. package/components/radio/radio.js +1 -1
  203. package/components/range/range.css +55 -55
  204. package/components/range/range.js +1 -1
  205. package/components/rating/rating.css +28 -28
  206. package/components/rating/rating.js +1 -1
  207. package/components/richtext/richtext.css +133 -133
  208. package/components/richtext/richtext.js +1 -1
  209. package/components/row/row.css +19 -19
  210. package/components/row/row.js +1 -1
  211. package/components/search/search.css +5 -5
  212. package/components/search/search.js +1 -1
  213. package/components/segment/segment.css +24 -24
  214. package/components/segment/segment.js +1 -1
  215. package/components/segmented/segmented.css +25 -25
  216. package/components/segmented/segmented.js +1 -1
  217. package/components/select/select.a2ui.json +58 -4
  218. package/components/select/{class.js → select.class.js} +415 -6
  219. package/components/select/select.css +242 -84
  220. package/components/select/select.d.ts +31 -1
  221. package/components/select/select.js +1 -1
  222. package/components/select/select.test.js +202 -0
  223. package/components/select/select.yaml +126 -5
  224. package/components/skeleton/skeleton.css +14 -14
  225. package/components/skeleton/skeleton.js +1 -1
  226. package/components/slider/slider.css +46 -46
  227. package/components/slider/slider.js +1 -1
  228. package/components/spinner/spinner.a2ui.json +198 -0
  229. package/components/spinner/spinner.class.js +99 -0
  230. package/components/spinner/spinner.css +221 -0
  231. package/components/spinner/spinner.d.ts +26 -0
  232. package/components/spinner/spinner.examples.md +26 -0
  233. package/components/spinner/spinner.js +17 -0
  234. package/components/spinner/spinner.test.js +272 -0
  235. package/components/spinner/spinner.yaml +238 -0
  236. package/components/stack/stack.css +11 -11
  237. package/components/stack/stack.js +1 -1
  238. package/components/stat/stat.css +25 -25
  239. package/components/step-progress/step-progress.css +20 -20
  240. package/components/step-progress/step-progress.js +1 -1
  241. package/components/stepper/stepper-item.yaml +1 -1
  242. package/components/stepper/stepper.css +29 -29
  243. package/components/stepper/stepper.js +1 -1
  244. package/components/stream/stream.css +12 -12
  245. package/components/stream/stream.js +1 -1
  246. package/components/swatch/swatch.css +68 -68
  247. package/components/swatch/swatch.js +1 -1
  248. package/components/swiper/swiper.css +57 -57
  249. package/components/swiper/swiper.js +1 -1
  250. package/components/switch/switch.css +52 -52
  251. package/components/switch/switch.js +1 -1
  252. package/components/table/table.css +163 -163
  253. package/components/table/table.js +1 -1
  254. package/components/table-toolbar/{class.js → table-toolbar.class.js} +1 -1
  255. package/components/table-toolbar/table-toolbar.css +32 -32
  256. package/components/table-toolbar/table-toolbar.js +1 -1
  257. package/components/tabs/tab.yaml +2 -2
  258. package/components/tabs/tabs.css +51 -51
  259. package/components/tabs/tabs.js +1 -1
  260. package/components/tag/tag.css +48 -48
  261. package/components/tag/tag.js +1 -1
  262. package/components/tags-input/tags-input.a2ui.json +337 -0
  263. package/components/tags-input/tags-input.class.js +776 -0
  264. package/components/tags-input/tags-input.css +201 -0
  265. package/components/tags-input/tags-input.d.ts +120 -0
  266. package/components/tags-input/tags-input.examples.md +92 -0
  267. package/components/tags-input/tags-input.js +17 -0
  268. package/components/tags-input/tags-input.test.js +368 -0
  269. package/components/tags-input/tags-input.yaml +367 -0
  270. package/components/text/text.css +44 -44
  271. package/components/text/text.js +1 -1
  272. package/components/textarea/textarea.a2ui.json +1 -1
  273. package/components/textarea/textarea.css +46 -46
  274. package/components/textarea/textarea.js +1 -1
  275. package/components/textarea/textarea.yaml +11 -8
  276. package/components/time-picker/time-picker.a2ui.json +267 -0
  277. package/components/time-picker/time-picker.class.js +693 -0
  278. package/components/time-picker/time-picker.css +122 -0
  279. package/components/time-picker/time-picker.d.ts +75 -0
  280. package/components/time-picker/time-picker.examples.md +35 -0
  281. package/components/time-picker/time-picker.js +17 -0
  282. package/components/time-picker/time-picker.test.js +287 -0
  283. package/components/time-picker/time-picker.yaml +256 -0
  284. package/components/timeline/timeline-item.yaml +2 -2
  285. package/components/timeline/{class.js → timeline.class.js} +1 -1
  286. package/components/timeline/timeline.css +50 -50
  287. package/components/timeline/timeline.js +1 -1
  288. package/components/toast/toast.css +58 -58
  289. package/components/toast/toast.js +1 -1
  290. package/components/toggle-group/toggle-group.css +6 -6
  291. package/components/toggle-group/toggle-group.js +1 -1
  292. package/components/toggle-group/toggle-option.yaml +1 -1
  293. package/components/toggle-scheme/toggle-scheme.css +2 -2
  294. package/components/toggle-scheme/toggle-scheme.js +1 -1
  295. package/components/toolbar/toolbar-group.yaml +1 -1
  296. package/components/toolbar/toolbar.css +17 -17
  297. package/components/toolbar/toolbar.js +1 -1
  298. package/components/tooltip/tooltip.css +2 -2
  299. package/components/tooltip/tooltip.js +1 -1
  300. package/components/tree/tree-item.yaml +1 -1
  301. package/components/tree/tree.css +37 -37
  302. package/components/tree/tree.js +1 -1
  303. package/components/upload/upload.css +49 -49
  304. package/components/upload/upload.js +1 -1
  305. package/dist/web-components.min.css +1 -1
  306. package/dist/web-components.min.js +146 -87
  307. package/package.json +3 -3
  308. package/styles/components.css +11 -0
  309. /package/components/accordion/{class.js → accordion.class.js} +0 -0
  310. /package/components/action-list/{class.js → action-list.class.js} +0 -0
  311. /package/components/agent-feedback-bar/{class.js → agent-feedback-bar.class.js} +0 -0
  312. /package/components/agent-questions/{class.js → agent-questions.class.js} +0 -0
  313. /package/components/agent-reasoning/{class.js → agent-reasoning.class.js} +0 -0
  314. /package/components/agent-suggestions/{class.js → agent-suggestions.class.js} +0 -0
  315. /package/components/avatar/{class.js → avatar.class.js} +0 -0
  316. /package/components/badge/{class.js → badge.class.js} +0 -0
  317. /package/components/block/{class.js → block.class.js} +0 -0
  318. /package/components/breadcrumb/{class.js → breadcrumb.class.js} +0 -0
  319. /package/components/button/{class.js → button.class.js} +0 -0
  320. /package/components/calendar-picker/{class.js → calendar-picker.class.js} +0 -0
  321. /package/components/card/{class.js → card.class.js} +0 -0
  322. /package/components/chart/{class.js → chart.class.js} +0 -0
  323. /package/components/chart-legend/{class.js → chart-legend.class.js} +0 -0
  324. /package/components/chat-thread/{class.js → chat-thread.class.js} +0 -0
  325. /package/components/check/{class.js → check.class.js} +0 -0
  326. /package/components/code/{class.js → code.class.js} +0 -0
  327. /package/components/col/{class.js → col.class.js} +0 -0
  328. /package/components/color-input/{class.js → color-input.class.js} +0 -0
  329. /package/components/color-picker/{class.js → color-picker.class.js} +0 -0
  330. /package/components/command/{class.js → command.class.js} +0 -0
  331. /package/components/demo-toggle/{class.js → demo-toggle.class.js} +0 -0
  332. /package/components/description-list/{class.js → description-list.class.js} +0 -0
  333. /package/components/divider/{class.js → divider.class.js} +0 -0
  334. /package/components/drawer/{class.js → drawer.class.js} +0 -0
  335. /package/components/embed/{class.js → embed.class.js} +0 -0
  336. /package/components/empty-state/{class.js → empty-state.class.js} +0 -0
  337. /package/components/feed/{class.js → feed.class.js} +0 -0
  338. /package/components/field/{class.js → field.class.js} +0 -0
  339. /package/components/fields/{class.js → fields.class.js} +0 -0
  340. /package/components/grid/{class.js → grid.class.js} +0 -0
  341. /package/components/heatmap/{class.js → heatmap.class.js} +0 -0
  342. /package/components/icon/{class.js → icon.class.js} +0 -0
  343. /package/components/image/{class.js → image.class.js} +0 -0
  344. /package/components/input/{class.js → input.class.js} +0 -0
  345. /package/components/inspector/{class.js → inspector.class.js} +0 -0
  346. /package/components/kbd/{class.js → kbd.class.js} +0 -0
  347. /package/components/link/{class.js → link.class.js} +0 -0
  348. /package/components/list/{class.js → list.class.js} +0 -0
  349. /package/components/menu/{class.js → menu.class.js} +0 -0
  350. /package/components/nav/{class.js → nav.class.js} +0 -0
  351. /package/components/nav-group/{class.js → nav-group.class.js} +0 -0
  352. /package/components/nav-item/{class.js → nav-item.class.js} +0 -0
  353. /package/components/noodles/{class.js → noodles.class.js} +0 -0
  354. /package/components/option-card/{class.js → option-card.class.js} +0 -0
  355. /package/components/otp-input/{class.js → otp-input.class.js} +0 -0
  356. /package/components/page/{class.js → page.class.js} +0 -0
  357. /package/components/pagination/{class.js → pagination.class.js} +0 -0
  358. /package/components/pane/{class.js → pane.class.js} +0 -0
  359. /package/components/pipeline-status/{class.js → pipeline-status.class.js} +0 -0
  360. /package/components/popover/{class.js → popover.class.js} +0 -0
  361. /package/components/progress/{class.js → progress.class.js} +0 -0
  362. /package/components/progress-row/{class.js → progress-row.class.js} +0 -0
  363. /package/components/radio/{class.js → radio.class.js} +0 -0
  364. /package/components/range/{class.js → range.class.js} +0 -0
  365. /package/components/rating/{class.js → rating.class.js} +0 -0
  366. /package/components/richtext/{class.js → richtext.class.js} +0 -0
  367. /package/components/row/{class.js → row.class.js} +0 -0
  368. /package/components/search/{class.js → search.class.js} +0 -0
  369. /package/components/segment/{class.js → segment.class.js} +0 -0
  370. /package/components/segmented/{class.js → segmented.class.js} +0 -0
  371. /package/components/skeleton/{class.js → skeleton.class.js} +0 -0
  372. /package/components/slider/{class.js → slider.class.js} +0 -0
  373. /package/components/stack/{class.js → stack.class.js} +0 -0
  374. /package/components/step-progress/{class.js → step-progress.class.js} +0 -0
  375. /package/components/stepper/{class.js → stepper.class.js} +0 -0
  376. /package/components/stream/{class.js → stream.class.js} +0 -0
  377. /package/components/swatch/{class.js → swatch.class.js} +0 -0
  378. /package/components/swiper/{class.js → swiper.class.js} +0 -0
  379. /package/components/switch/{class.js → switch.class.js} +0 -0
  380. /package/components/table/{class.js → table.class.js} +0 -0
  381. /package/components/tabs/{class.js → tabs.class.js} +0 -0
  382. /package/components/tag/{class.js → tag.class.js} +0 -0
  383. /package/components/text/{class.js → text.class.js} +0 -0
  384. /package/components/textarea/{class.js → textarea.class.js} +0 -0
  385. /package/components/toast/{class.js → toast.class.js} +0 -0
  386. /package/components/toggle-group/{class.js → toggle-group.class.js} +0 -0
  387. /package/components/toggle-scheme/{class.js → toggle-scheme.class.js} +0 -0
  388. /package/components/toolbar/{class.js → toolbar.class.js} +0 -0
  389. /package/components/tooltip/{class.js → tooltip.class.js} +0 -0
  390. /package/components/tree/{class.js → tree.class.js} +0 -0
  391. /package/components/upload/{class.js → upload.class.js} +0 -0
@@ -0,0 +1,270 @@
1
+ # Authored 2026-05-23 for SPEC-022 (Virtualized / Windowed List). The
2
+ # component name in catalog metadata reads "Virtualized Windowed List"
3
+ # for retrieval / searchability; the tag is the shorter `list-window-ui`
4
+ # which lives alongside `<list-ui>` / `<list-item-ui>` in the list-*
5
+ # namespace.
6
+ #
7
+ # Edit this file; run `npm run build:components` to regenerate the
8
+ # `list-window.a2ui.json` sidecar. Never hand-edit the .a2ui.json.
9
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
10
+ name: UIListWindow
11
+ tag: list-window-ui
12
+ status: stable
13
+ component: ListWindow
14
+ category: display
15
+ version: 1
16
+ description: >-
17
+ Virtualized / windowed list primitive. Renders only the visible slice
18
+ of a large items[] array (chat threads, feeds, log streams, nav lists,
19
+ search-result panes) — typically ≤50 DOM rows regardless of the
20
+ underlying collection size. Composes a `render`-function prop OR a
21
+ slotted <template> for row materialization; ships a fixed-size
22
+ fast-path (constant-time index→offset math) and a variable-size
23
+ measurement fallback. Distinct from <list-ui> (renders every child,
24
+ preferred for short lists < 50 items) and <table-ui> (tabular data
25
+ with columns). Use list-window-ui when items.length is large enough
26
+ that rendering every row would block the main thread or stutter
27
+ scroll.
28
+ # Per ADR-0027 — primitives that programmatically create other primitives
29
+ # do NOT auto-import them. Consumer (or demo shell) must explicitly import.
30
+ composes:
31
+ - skeleton-ui
32
+ props:
33
+ items:
34
+ description: The items to virtualize. Required for prop-driven authoring; ignored when data-stream-src is set.
35
+ type: array
36
+ default: []
37
+ itemSize:
38
+ description: Fixed item height in pixels. When > 0, uses the constant-time fast path (avoids per-row measurement).
39
+ type: number
40
+ default: 0
41
+ reflect: true
42
+ attribute: item-size
43
+ itemSizeRem:
44
+ description: Fixed item height in rem. Useful for typographic-scale rows that should track the body type.
45
+ type: number
46
+ default: 0
47
+ reflect: true
48
+ attribute: item-size-rem
49
+ estimatedSize:
50
+ description: Initial guess for variable-height rows. Used until the first measurement pass refines the offset cache.
51
+ type: number
52
+ default: 48
53
+ reflect: true
54
+ attribute: estimated-size
55
+ overscan:
56
+ description: >-
57
+ Rows to render above + below the visible window. 0–20 is reasonable; > 50 negates the windowing benefit.
58
+ type: number
59
+ default: 3
60
+ reflect: true
61
+ direction:
62
+ description: Scroll axis — vertical (default) or horizontal carousel.
63
+ type: string
64
+ default: vertical
65
+ enum:
66
+ - vertical
67
+ - horizontal
68
+ reflect: true
69
+ pinBottom:
70
+ description: When appending items, keep scroll pinned to the bottom (chat-thread / log-tail pattern).
71
+ type: boolean
72
+ default: false
73
+ reflect: true
74
+ attribute: pin-bottom
75
+ startIndex:
76
+ description: Index to scroll to on mount. Useful for restoring scroll position on remount.
77
+ type: number
78
+ default: 0
79
+ attribute: start-index
80
+ loading:
81
+ description: Render skeleton rows in the visible window. Sets aria-busy="true" on the host.
82
+ type: boolean
83
+ default: false
84
+ reflect: true
85
+ events:
86
+ range-change:
87
+ description: Fired when the visible row range (start/end indices) changes due to scroll.
88
+ detail:
89
+ startIndex:
90
+ type: number
91
+ description: First rendered row index.
92
+ endIndex:
93
+ type: number
94
+ description: Last rendered row index (exclusive).
95
+ items:
96
+ type: array
97
+ description: The items currently materialized in the window.
98
+ item-click:
99
+ description: Fired when a rendered row is clicked.
100
+ detail:
101
+ item:
102
+ description: The clicked item (full item-shape from items[]).
103
+ index:
104
+ type: number
105
+ description: Item index in the full items[] array.
106
+ scroll-end:
107
+ description: Fired when the user scrolls to the bottom (within 1 viewport). Use for infinite-load patterns.
108
+ detail:
109
+ index:
110
+ type: number
111
+ description: Last visible row index.
112
+ scroll-start:
113
+ description: Fired when the user scrolls to the top (within 1 viewport). Use for "load older" patterns.
114
+ detail:
115
+ index:
116
+ type: number
117
+ description: First visible row index.
118
+ measure:
119
+ description: Fired when a variable-height row is measured. Useful for instrumenting the offset cache.
120
+ detail:
121
+ index:
122
+ type: number
123
+ description: Index of the row that was measured.
124
+ height:
125
+ type: number
126
+ description: Measured row height in pixels.
127
+ slots:
128
+ default:
129
+ description: A single <template> element used to clone rows (declarative-template authoring). Mutually exclusive with the render prop.
130
+ empty:
131
+ description: Custom empty-state content when items.length === 0.
132
+ loading:
133
+ description: Custom skeleton row template; falls back to <skeleton-ui> when omitted.
134
+ before:
135
+ description: Sticky-top content (filter chips, summary stat).
136
+ after:
137
+ description: Sticky-bottom content (composer, "load older" button).
138
+ states:
139
+ - name: idle
140
+ description: Default — rendering and reconciling normally.
141
+ - name: loading
142
+ attribute: loading
143
+ description: Skeleton rows; data fetch in flight.
144
+ - name: empty
145
+ attribute: empty
146
+ description: items.length === 0.
147
+ - name: measuring
148
+ attribute: measuring
149
+ description: First-mount measurement pass on variable-height rows; suppresses scroll-end events.
150
+ - name: disabled
151
+ attribute: disabled
152
+ description: Pointer events blocked.
153
+ traits: []
154
+ tokens:
155
+ --list-window-bg:
156
+ description: Host background (defaults to --a-bg).
157
+ --list-window-row-gap:
158
+ description: Row spacing in the visible window (defaults to --a-space-1).
159
+ --list-window-overscan-bg:
160
+ description: Visible buffer rows background — transparent by default.
161
+ --list-window-sentinel-size:
162
+ description: Top + bottom IntersectionObserver-target sentinel size (defaults to --a-space-2).
163
+ keywords:
164
+ - list-window
165
+ - virtualized
166
+ - windowed
167
+ - virtual-scroll
168
+ - infinite-scroll
169
+ - large-list
170
+ - feed
171
+ - chat-thread
172
+ - log-stream
173
+ - 10k-rows
174
+ synonyms:
175
+ virtualized:
176
+ - virtual-scroll
177
+ - windowed
178
+ - list-window
179
+ windowed:
180
+ - virtualized
181
+ - virtual-scroll
182
+ - list-window
183
+ virtual-scroll:
184
+ - virtualized
185
+ - windowed
186
+ - list-window
187
+ infinite-scroll:
188
+ - virtualized
189
+ - virtual-scroll
190
+ - list-window
191
+ large-list:
192
+ - virtualized
193
+ - list-window
194
+ related:
195
+ - List
196
+ - ListItem
197
+ - Feed
198
+ - ChatThread
199
+ - Table
200
+ a2ui:
201
+ rules:
202
+ - rule: 'ListWindow.items MUST be an array of plain objects OR scalars. Functions / DOM nodes / Promises are invalid.'
203
+ reason: 'Items are reconciled via key-fn + serialized to row DOM; non-data values cannot survive the round-trip.'
204
+ - rule: 'ListWindow.render cannot be expressed in A2UI JSON — declarative authoring MUST use a <template> child (or default <list-item-ui> for objects with a text field).'
205
+ reason: 'A2UI is a JSON catalog; function values have no transport.'
206
+ - rule: 'When items.length > 200, the validator SHOULD recommend ListWindow over List to keep the DOM tractable.'
207
+ reason: 'List renders every item; at large N the cost is super-linear in layout / paint / memory.'
208
+ - rule: 'ListWindow MUST have a defined height (via parent layout or style="height:..."). An unbounded-height windowed list defeats the windowing math.'
209
+ reason: 'Without a viewport bound the scroll container has no visible-window size; every item would mount.'
210
+ - rule: 'ListWindow.item-size SHOULD be set when item heights are known and constant — the fast-path is significantly cheaper.'
211
+ reason: 'Constant-time index→offset math beats per-row measurement.'
212
+ - rule: 'Do NOT nest ListWindow inside another scroll container; double-scroll containers break the IntersectionObserver math. Use one scroll boundary.'
213
+ reason: 'Nested scroll containers create ambiguous visible-window targets.'
214
+ - rule: 'Do NOT use ListWindow for short lists (< 50 items). The windowing overhead exceeds the cost of rendering all rows. Use List for short lists.'
215
+ reason: 'Below the windowing threshold the bookkeeping is pure cost.'
216
+ - rule: 'Do NOT use for tabular data — that is Table with virtualized rows.'
217
+ reason: 'Different surface; Table owns columns + grid roles.'
218
+ anti_patterns:
219
+ - wrong: |
220
+ { "component": "List", "items": [/* 10000 items */] }
221
+ why: |
222
+ Rendering 10k items into <list-ui> blows up the DOM and main thread. Scroll
223
+ jank, mount lag, and memory pressure all degrade.
224
+ fix: |
225
+ { "component": "ListWindow", "items": [/* 10000 items */], "item-size": 48 }
226
+ - wrong: |
227
+ { "component": "ListWindow", "items": [/* … */] }
228
+ (with no parent height + no item-size + no estimated-size)
229
+ why: |
230
+ Without a height bound, the scroll container has no viewport, so the windowing
231
+ math reports "all items visible" and the whole list mounts.
232
+ fix: |
233
+ Wrap in a container with a height, or set style="height:480px" on the
234
+ ListWindow itself.
235
+ - wrong: |
236
+ { "component": "ListWindow", "overscan": 200, "items": [/* … */] }
237
+ why: |
238
+ Overscan=200 materializes 200 rows above + below the viewport. That defeats
239
+ the entire point of windowing.
240
+ fix: |
241
+ { "component": "ListWindow", "overscan": 5, "items": [/* … */] }
242
+ examples:
243
+ - name: chat-thread-list
244
+ description: Virtualized chat-thread message list with declarative <template> rows.
245
+ a2ui: >-
246
+ [
247
+ {
248
+ "id": "root",
249
+ "component": "Card",
250
+ "children": ["thread"]
251
+ },
252
+ {
253
+ "id": "thread",
254
+ "component": "ListWindow",
255
+ "item-size": 56,
256
+ "overscan": 5,
257
+ "pin-bottom": true
258
+ }
259
+ ]
260
+ - name: log-tail-stream
261
+ description: SSE-streamed JSONL log tail with sticky-bottom pin.
262
+ a2ui: >-
263
+ [
264
+ {
265
+ "id": "logs",
266
+ "component": "ListWindow",
267
+ "item-size": 24,
268
+ "pin-bottom": true
269
+ }
270
+ ]
@@ -0,0 +1,176 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/LoadingOverlay.json",
4
+ "title": "LoadingOverlay",
5
+ "description": "Container-scoped busy overlay. Covers a positioned parent region with a centered spinner (auto-stamped) or slotted indicator (skeleton-ui, progress-ui, custom) while async work is in flight. Wires aria-busy onto the parent on connect; applies a [delay] grace window to avoid flash on fast loads. For viewport-scoped / route loaders use a dedicated route-loader pattern; for submit-button busy use <button-ui loading>.",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "active": {
17
+ "description": "When set, overlay is visible and the parent container is marked aria-busy. Toggle from consumer code; default hidden.",
18
+ "$ref": "common_types.json#/$defs/DynamicBoolean"
19
+ },
20
+ "component": {
21
+ "const": "LoadingOverlay"
22
+ },
23
+ "delay": {
24
+ "description": "Suppress the overlay until this many ms elapse. Prevents flash on fast loads. Default 200ms.",
25
+ "$ref": "common_types.json#/$defs/DynamicNumber"
26
+ },
27
+ "label": {
28
+ "description": "Accessible operation name used by the host aria-label and forwarded to the auto-stamped spinner.",
29
+ "$ref": "common_types.json#/$defs/DynamicString"
30
+ },
31
+ "variant": {
32
+ "description": "Backdrop treatment — default (muted scrim), transparent (no backdrop fill — useful with full-area skeleton indicators), blur (light scrim with backdrop-filter blur).",
33
+ "$ref": "common_types.json#/$defs/DynamicString"
34
+ }
35
+ },
36
+ "required": [
37
+ "component"
38
+ ],
39
+ "unevaluatedProperties": false,
40
+ "x-adiaui": {
41
+ "anti_patterns": [
42
+ {
43
+ "fix": "{\"component\": \"Card\", \"children\": [\n {\"component\": \"LoadingOverlay\", \"active\": true},\n {\"component\": \"Table\", \"data-stream-src\": \"...\"}\n]}\n",
44
+ "why": "LoadingOverlay is container-scoped. Attaching it directly to Page\ncovers the entire viewport but lacks the route-loader's wiring\n(skeleton routing, route-level animation). Use the route loader.\n",
45
+ "wrong": "{\"component\": \"Page\", \"children\": [\n {\"component\": \"LoadingOverlay\", \"active\": true}\n]}\n"
46
+ },
47
+ {
48
+ "fix": "{\"component\": \"Modal\", \"open\": true, \"loading\": true}\n",
49
+ "why": "Modal owns its own busy state and focus trap; nesting a\nLoadingOverlay introduces two competing inert toggles.\n",
50
+ "wrong": "{\"component\": \"Modal\", \"open\": true, \"children\": [\n {\"component\": \"LoadingOverlay\", \"active\": true}\n]}\n"
51
+ },
52
+ {
53
+ "fix": "{\"component\": \"Form\", \"children\": [\n {\"component\": \"Button\", \"text\": \"Submitting\", \"type\": \"submit\", \"loading\": true, \"disabled\": true}\n]}\n",
54
+ "why": "A form-submit affordance belongs on the button itself, not as an\noverlay over the entire form region. Use Button[loading].\n",
55
+ "wrong": "{\"component\": \"Form\", \"children\": [\n {\"component\": \"Button\", \"text\": \"Submit\", \"type\": \"submit\"},\n {\"component\": \"LoadingOverlay\", \"active\": true}\n]}\n"
56
+ }
57
+ ],
58
+ "category": "feedback",
59
+ "composes": [],
60
+ "events": {},
61
+ "examples": [
62
+ {
63
+ "description": "Card containing a table that loads on mount; the overlay covers the card's body while data streams.",
64
+ "a2ui": "[\n {\n \"id\": \"card\",\n \"component\": \"Card\",\n \"children\": [\"sec\"]\n },\n {\n \"id\": \"sec\",\n \"component\": \"Section\",\n \"children\": [\"overlay\", \"table\"]\n },\n {\n \"id\": \"overlay\",\n \"component\": \"LoadingOverlay\",\n \"active\": true,\n \"label\": \"Loading orders…\"\n },\n {\n \"id\": \"table\",\n \"component\": \"Table\",\n \"data-stream-src\": \"/api/orders\"\n }\n]",
65
+ "name": "card-with-loading-table"
66
+ },
67
+ {
68
+ "description": "Skeleton-shaped placeholder overlay using the transparent variant — slot a stack of <Skeleton> rows.",
69
+ "a2ui": "[\n {\n \"id\": \"ov\",\n \"component\": \"LoadingOverlay\",\n \"active\": true,\n \"variant\": \"transparent\",\n \"label\": \"Loading content…\",\n \"children\": [\"sk-stack\"]\n },\n {\n \"id\": \"sk-stack\",\n \"component\": \"Column\",\n \"gap\": \"2\",\n \"children\": [\"sk1\", \"sk2\", \"sk3\"]\n },\n {\n \"id\": \"sk1\",\n \"component\": \"Skeleton\",\n \"width\": \"100%\",\n \"height\": \"1rem\"\n },\n {\n \"id\": \"sk2\",\n \"component\": \"Skeleton\",\n \"width\": \"80%\",\n \"height\": \"1rem\"\n },\n {\n \"id\": \"sk3\",\n \"component\": \"Skeleton\",\n \"width\": \"60%\",\n \"height\": \"1rem\"\n }\n]",
70
+ "name": "skeleton-overlay"
71
+ },
72
+ {
73
+ "description": "Overlay with the default 200ms grace window. If the underlying load resolves in less than 200ms, the overlay never paints.",
74
+ "a2ui": "[\n {\n \"id\": \"ov\",\n \"component\": \"LoadingOverlay\",\n \"active\": true,\n \"delay\": 200,\n \"label\": \"Loading\"\n }\n]",
75
+ "name": "fast-load-no-flash"
76
+ }
77
+ ],
78
+ "keywords": [
79
+ "loading",
80
+ "overlay",
81
+ "busy",
82
+ "spinner",
83
+ "skeleton",
84
+ "placeholder",
85
+ "feedback",
86
+ "progress",
87
+ "indicator",
88
+ "wait"
89
+ ],
90
+ "name": "UILoadingOverlay",
91
+ "related": [
92
+ "Spinner",
93
+ "Skeleton",
94
+ "Progress",
95
+ "Card",
96
+ "Table",
97
+ "Modal"
98
+ ],
99
+ "slots": {
100
+ "default": {
101
+ "description": "Busy indicator content — <spinner-ui>, <skeleton-ui>, <progress-ui>, or custom. If empty, a centered <spinner-ui size=\"lg\"> is auto-stamped."
102
+ }
103
+ },
104
+ "states": [
105
+ {
106
+ "description": "Default, hidden. Parent interactive.",
107
+ "name": "idle"
108
+ },
109
+ {
110
+ "description": "Overlay visible; parent has aria-busy=\"true\".",
111
+ "attribute": "active",
112
+ "name": "active"
113
+ }
114
+ ],
115
+ "status": "stable",
116
+ "synonyms": {
117
+ "busy": [
118
+ "loading",
119
+ "overlay",
120
+ "busy",
121
+ "spinner"
122
+ ],
123
+ "loader": [
124
+ "loading",
125
+ "overlay",
126
+ "spinner"
127
+ ],
128
+ "loading": [
129
+ "loading",
130
+ "overlay",
131
+ "busy",
132
+ "spinner",
133
+ "skeleton"
134
+ ],
135
+ "overlay": [
136
+ "loading",
137
+ "overlay",
138
+ "busy"
139
+ ],
140
+ "spinner": [
141
+ "loading",
142
+ "overlay",
143
+ "spinner"
144
+ ]
145
+ },
146
+ "tag": "loading-overlay-ui",
147
+ "tokens": {
148
+ "--loading-overlay-bg": {
149
+ "description": "Backdrop fill color. Default uses the generic neutral scrim token.",
150
+ "default": "var(--a-scrim-default)"
151
+ },
152
+ "--loading-overlay-duration": {
153
+ "description": "Fade in / fade out duration.",
154
+ "default": "var(--a-duration)"
155
+ },
156
+ "--loading-overlay-easing": {
157
+ "description": "Fade easing curve.",
158
+ "default": "var(--a-easing-out)"
159
+ },
160
+ "--loading-overlay-gap": {
161
+ "description": "Vertical gap between the indicator and any sibling content (e.g. label, skeleton stack).",
162
+ "default": "var(--a-space-3)"
163
+ },
164
+ "--loading-overlay-radius": {
165
+ "description": "Backdrop corner radius. Matches the parent surface by default.",
166
+ "default": "var(--a-radius-md)"
167
+ },
168
+ "--loading-overlay-z": {
169
+ "description": "Stacking order. Sits above sibling content, below modal-ui (which uses ::backdrop on top-layer dialog).",
170
+ "default": "50"
171
+ }
172
+ },
173
+ "traits": [],
174
+ "version": 1
175
+ }
176
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Non-side-effect class export for `<loading-overlay-ui>`.
3
+ *
4
+ * Importing this file gives you the class without auto-registering the
5
+ * tag. Useful for test isolation, subclassing with tag-name override, or
6
+ * selective composition.
7
+ *
8
+ * The auto-register path stays at `@adia-ai/web-components/components/loading-overlay`
9
+ * (which imports this file + calls `defineIfFree()`).
10
+ *
11
+ * @see ../../USAGE.md#registration--auto-vs-explicit
12
+ */
13
+
14
+ /**
15
+ * <loading-overlay-ui active label="Loading orders…"></loading-overlay-ui>
16
+ * <loading-overlay-ui active variant="transparent" delay="0">
17
+ * <skeleton-ui width="100%" height="1rem"></skeleton-ui>
18
+ * <skeleton-ui width="80%" height="1rem"></skeleton-ui>
19
+ * </loading-overlay-ui>
20
+ *
21
+ * Container-scoped busy overlay. Covers a positioned parent region with
22
+ * a spinner, skeleton, or custom busy indicator while async work is in
23
+ * flight. The host:
24
+ *
25
+ * 1. Absolutely positions to fill the offsetParent (consumer's
26
+ * responsibility to ensure that parent is `position: relative` or
27
+ * similar — documented in the spec / usage notes; we do NOT mutate
28
+ * parent layout styles).
29
+ * 2. Applies aria-busy="true" to the parent on activate; releases on
30
+ * deactivate / disconnect. This is screen-reader politeness; the
31
+ * backdrop's pointer-events:auto handles the click/focus block at
32
+ * the CSS level (no native [inert] attribute toggling — that's
33
+ * kept minimal to avoid disturbing the consumer's focus model).
34
+ * 3. Auto-stamps a centered <spinner-ui size="lg"> when the default
35
+ * slot is empty.
36
+ * 4. Honors a `[delay]` grace window (default 200ms) — if the active
37
+ * flag is cleared before the timer fires, the overlay never
38
+ * paints. Avoids flash on fast loads.
39
+ *
40
+ * Props (attributes):
41
+ * active — overlay visible (default: false)
42
+ * delay — grace window in ms before paint (default: 200)
43
+ * label — accessible label, also forwarded to auto-stamped spinner
44
+ * variant — default | transparent | blur
45
+ *
46
+ * Lifecycle: connected() seeds aria-label on host + caches parent
47
+ * reference for aria-busy bookkeeping. Disconnected() clears any
48
+ * pending delay timer and releases aria-busy on the parent. State
49
+ * changes are driven by the reactive [active] setter wrapper, which
50
+ * starts/cancels the timer + applies aria-busy when the timer fires.
51
+ *
52
+ * @see SPEC-034 (docs/specs/implementation-ready/SPEC-034-loading-overlay.md)
53
+ */
54
+
55
+ import { UIElement } from '../../core/element.js';
56
+
57
+ export class UILoadingOverlay extends UIElement {
58
+ static properties = {
59
+ active: { type: Boolean, default: false, reflect: true },
60
+ delay: { type: Number, default: 200, reflect: true },
61
+ label: { type: String, default: 'Loading…', reflect: true },
62
+ variant: { type: String, default: 'default', reflect: true },
63
+ };
64
+
65
+ // No stamped children authored by the template engine; auto-stamp of
66
+ // the default spinner happens imperatively in connected() / render()
67
+ // when the slot is empty. Authored children (skeleton/progress/etc.)
68
+ // are left in place per ADR-0033 (light-DOM substrate).
69
+ static template = () => null;
70
+
71
+ #parent = null;
72
+ #delayTimer = null;
73
+ #activeApplied = false; // tracks whether aria-busy / opacity actually painted
74
+
75
+ constructor() {
76
+ super();
77
+ // Wrap the auto-installed `active` setter so state transitions kick
78
+ // the delay timer synchronously with the assignment — matching the
79
+ // drawer-ui Safari pattern (avoids microtask scheduling delays).
80
+ const desc = Object.getOwnPropertyDescriptor(this, 'active');
81
+ if (desc?.set) {
82
+ const origSet = desc.set;
83
+ Object.defineProperty(this, 'active', {
84
+ get: desc.get,
85
+ set: (v) => {
86
+ const prev = !!desc.get.call(this);
87
+ origSet.call(this, v);
88
+ if (!!v !== prev) this.#syncActive(!!v);
89
+ },
90
+ configurable: true,
91
+ });
92
+ }
93
+ }
94
+
95
+ connected() {
96
+ // Host ARIA: role=status is a polite live region; aria-label
97
+ // names the operation. Both are seeded once; render() keeps the
98
+ // label fresh on prop changes.
99
+ if (!this.hasAttribute('role')) this.setAttribute('role', 'status');
100
+ if (!this.hasAttribute('aria-live')) this.setAttribute('aria-live', 'polite');
101
+ this.setAttribute('aria-label', this.label || 'Loading');
102
+
103
+ // Cache the parent so disconnected() can release aria-busy even if
104
+ // the element is detached before parentElement is re-readable.
105
+ this.#parent = this.parentElement;
106
+
107
+ // If the element mounted with [active] already set (declarative),
108
+ // honor it. The reactive setter wrapper only fires on subsequent
109
+ // assignments, so we kick the timer here too.
110
+ if (this.active) this.#syncActive(true);
111
+ }
112
+
113
+ disconnected() {
114
+ // Symmetric lifecycle: clear any in-flight timer + release aria-busy
115
+ // on the parent we cached at connect time.
116
+ this.#clearTimer();
117
+ if (this.#activeApplied) this.#releaseBusy();
118
+ this.#parent = null;
119
+ }
120
+
121
+ render() {
122
+ // Keep the host label fresh if [label] is mutated after connect.
123
+ // Forward it to an auto-stamped spinner-ui (if present) so screen
124
+ // readers hear a single coherent name.
125
+ const label = this.label || 'Loading';
126
+ this.setAttribute('aria-label', label);
127
+
128
+ const ownSpinner = this.querySelector(':scope > spinner-ui[data-loading-overlay-auto]');
129
+ if (ownSpinner && ownSpinner.getAttribute('label') !== label) {
130
+ ownSpinner.setAttribute('label', label);
131
+ }
132
+
133
+ // Ensure the auto-stamped spinner exists when the slot is empty.
134
+ // Authored children (consumer-supplied indicators) win; we only
135
+ // stamp when there are no element children at all.
136
+ if (this.children.length === 0) {
137
+ this.#stampDefaultSpinner();
138
+ }
139
+ }
140
+
141
+ // ── Internals ───────────────────────────────────────────────────────
142
+
143
+ #syncActive(next) {
144
+ // Clear any prior in-flight timer first; the new state owns the
145
+ // grace window.
146
+ this.#clearTimer();
147
+
148
+ if (next) {
149
+ const delay = Math.max(0, Number(this.delay) || 0);
150
+ if (delay === 0) {
151
+ this.#applyBusy();
152
+ } else {
153
+ this.#delayTimer = setTimeout(() => {
154
+ this.#delayTimer = null;
155
+ // Only paint if we're still active — caller may have cleared
156
+ // [active] during the grace window.
157
+ if (this.active) this.#applyBusy();
158
+ }, delay);
159
+ }
160
+ } else if (this.#activeApplied) {
161
+ this.#releaseBusy();
162
+ }
163
+ }
164
+
165
+ #applyBusy() {
166
+ const parent = this.#parent || this.parentElement;
167
+ if (parent && !parent.hasAttribute('aria-busy')) {
168
+ parent.setAttribute('aria-busy', 'true');
169
+ }
170
+ this.#activeApplied = true;
171
+ }
172
+
173
+ #releaseBusy() {
174
+ const parent = this.#parent || this.parentElement;
175
+ // Only remove aria-busy if WE set it (heuristic: matches "true").
176
+ // If the consumer authored their own aria-busy="true" before we
177
+ // mounted, we leave it; if they set "false" we leave it. Same
178
+ // shape as drawer-ui releasing dialog state.
179
+ if (parent && parent.getAttribute('aria-busy') === 'true') {
180
+ parent.removeAttribute('aria-busy');
181
+ }
182
+ this.#activeApplied = false;
183
+ }
184
+
185
+ #clearTimer() {
186
+ if (this.#delayTimer !== null) {
187
+ clearTimeout(this.#delayTimer);
188
+ this.#delayTimer = null;
189
+ }
190
+ }
191
+
192
+ #stampDefaultSpinner() {
193
+ // Imperative stamp — light-DOM child, marked with a data attribute
194
+ // so render()'s "did the consumer slot something?" check stays
195
+ // unambiguous on subsequent renders.
196
+ const sp = document.createElement('spinner-ui');
197
+ sp.setAttribute('size', 'lg');
198
+ sp.setAttribute('tone', 'subtle');
199
+ sp.setAttribute('label', this.label || 'Loading');
200
+ sp.setAttribute('data-loading-overlay-auto', '');
201
+ this.appendChild(sp);
202
+ }
203
+ }