@adia-ai/web-components 0.8.40 → 0.8.41

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 (378) hide show
  1. package/CHANGELOG.md +50 -3
  2. package/MIGRATION.md +1042 -16
  3. package/README.md +25 -30
  4. package/USAGE.md +26 -0
  5. package/bin/audit-template-children.mjs +556 -0
  6. package/components/accordion/accordion-item.a2ui.json +4 -0
  7. package/components/accordion/accordion-item.yaml +2 -0
  8. package/components/accordion/accordion.a2ui.json +4 -0
  9. package/components/accordion/accordion.yaml +2 -0
  10. package/components/action-list/action-item.a2ui.json +24 -2
  11. package/components/action-list/action-item.yaml +28 -3
  12. package/components/action-list/action-list.a2ui.json +6 -1
  13. package/components/action-list/action-list.class.js +52 -11
  14. package/components/action-list/action-list.css +43 -3
  15. package/components/action-list/action-list.d.ts +4 -2
  16. package/components/action-list/action-list.yaml +5 -0
  17. package/components/adia-mark/adia-mark.a2ui.json +1 -0
  18. package/components/adia-wordmark/adia-wordmark.a2ui.json +1 -0
  19. package/components/agent-artifact/agent-artifact.a2ui.json +1 -0
  20. package/components/agent-feedback-bar/agent-feedback-bar.a2ui.json +6 -5
  21. package/components/agent-feedback-bar/agent-feedback-bar.yaml +6 -2
  22. package/components/agent-questions/agent-questions.a2ui.json +1 -0
  23. package/components/agent-reasoning/agent-reasoning.a2ui.json +6 -5
  24. package/components/agent-reasoning/agent-reasoning.yaml +6 -2
  25. package/components/agent-suggestions/agent-suggestions.a2ui.json +11 -2
  26. package/components/agent-suggestions/agent-suggestions.class.js +2 -1
  27. package/components/agent-suggestions/agent-suggestions.d.ts +4 -4
  28. package/components/agent-suggestions/agent-suggestions.yaml +4 -2
  29. package/components/agent-trace/agent-trace.a2ui.json +6 -5
  30. package/components/agent-trace/agent-trace.yaml +6 -2
  31. package/components/alert/alert.a2ui.json +1 -0
  32. package/components/alert/alert.css +1 -1
  33. package/components/anchor-bar/anchor-bar.a2ui.json +1 -0
  34. package/components/aside/aside.a2ui.json +1 -0
  35. package/components/avatar/avatar-group.a2ui.json +7 -1
  36. package/components/avatar/avatar-group.yaml +1 -0
  37. package/components/avatar/avatar.a2ui.json +12 -11
  38. package/components/avatar/avatar.d.ts +1 -1
  39. package/components/avatar/avatar.yaml +4 -4
  40. package/components/badge/badge.a2ui.json +1 -0
  41. package/components/block/block.a2ui.json +1 -0
  42. package/components/blockquote/blockquote.a2ui.json +1 -0
  43. package/components/breadcrumb/breadcrumb.a2ui.json +1 -0
  44. package/components/button/button.a2ui.json +1 -3
  45. package/components/button/button.yaml +1 -3
  46. package/components/calendar-grid/calendar-grid.a2ui.json +1 -0
  47. package/components/calendar-grid/calendar-grid.class.js +2 -1
  48. package/components/calendar-picker/calendar-picker.a2ui.json +143 -24
  49. package/components/calendar-picker/calendar-picker.class.js +490 -197
  50. package/components/calendar-picker/calendar-picker.css +120 -171
  51. package/components/calendar-picker/calendar-picker.yaml +225 -47
  52. package/components/canvas/canvas.a2ui.json +6 -5
  53. package/components/canvas/canvas.d.ts +37 -0
  54. package/components/canvas/canvas.js +72 -3
  55. package/components/canvas/canvas.yaml +6 -2
  56. package/components/card/card.a2ui.json +25 -2
  57. package/components/card/card.class.js +3 -0
  58. package/components/card/card.css +31 -1
  59. package/components/card/card.d.ts +2 -2
  60. package/components/card/card.examples.md +3 -3
  61. package/components/card/card.yaml +28 -4
  62. package/components/chart/chart.a2ui.json +9 -4
  63. package/components/chart/chart.class.js +6 -6
  64. package/components/chart/chart.css +6 -4
  65. package/components/chart/chart.d.ts +1 -1
  66. package/components/chart/chart.yaml +5 -3
  67. package/components/chart-legend/chart-legend.a2ui.json +1 -0
  68. package/components/chat-thread/chat-input.a2ui.json +1 -0
  69. package/components/chat-thread/chat-thread.a2ui.json +10 -9
  70. package/components/chat-thread/chat-thread.yaml +13 -20
  71. package/components/check/check.a2ui.json +10 -9
  72. package/components/check/check.yaml +4 -4
  73. package/components/code/code.a2ui.json +1 -0
  74. package/components/col/col.a2ui.json +1 -0
  75. package/components/color-input/color-input.a2ui.json +1 -0
  76. package/components/color-picker/color-picker.a2ui.json +1 -0
  77. package/components/combobox/combobox.a2ui.json +3 -2
  78. package/components/combobox/combobox.yaml +2 -2
  79. package/components/command/command.a2ui.json +12 -10
  80. package/components/command/command.class.js +3 -2
  81. package/components/command/command.yaml +17 -4
  82. package/components/context-menu/context-menu.a2ui.json +1 -0
  83. package/components/date-range-picker/date-range-picker.a2ui.json +2 -1
  84. package/components/date-range-picker/date-range-picker.yaml +1 -1
  85. package/components/datetime-picker/datetime-picker.a2ui.json +15 -108
  86. package/components/datetime-picker/datetime-picker.class.js +37 -690
  87. package/components/datetime-picker/datetime-picker.css +11 -183
  88. package/components/datetime-picker/datetime-picker.d.ts +4 -5
  89. package/components/datetime-picker/datetime-picker.yaml +44 -135
  90. package/components/demo-toggle/demo-toggle.a2ui.json +1 -0
  91. package/components/demo-toggle/demo-toggle.css +1 -1
  92. package/components/description-list/description-list.a2ui.json +1 -0
  93. package/components/display-field/display-field.a2ui.json +1 -0
  94. package/components/divider/divider.a2ui.json +1 -0
  95. package/components/drawer/drawer.a2ui.json +1 -3
  96. package/components/drawer/drawer.css +1 -1
  97. package/components/drawer/drawer.yaml +0 -2
  98. package/components/drilldown/drilldown.a2ui.json +22 -20
  99. package/components/drilldown/drilldown.class.js +4 -3
  100. package/components/drilldown/drilldown.css +6 -6
  101. package/components/drilldown/drilldown.yaml +40 -9
  102. package/components/embed/embed.a2ui.json +6 -5
  103. package/components/embed/embed.yaml +2 -2
  104. package/components/empty-state/empty-state.a2ui.json +1 -0
  105. package/components/feed/feed-item.a2ui.json +25 -5
  106. package/components/feed/feed-item.yaml +28 -7
  107. package/components/feed/feed.a2ui.json +9 -2
  108. package/components/feed/feed.class.js +19 -12
  109. package/components/feed/feed.css +8 -8
  110. package/components/feed/feed.yaml +14 -1
  111. package/components/field/field.a2ui.json +1 -0
  112. package/components/field/field.examples.md +1 -1
  113. package/components/fields/fields.a2ui.json +1 -0
  114. package/components/fields/fields.examples.md +1 -1
  115. package/components/footer/footer.a2ui.json +1 -0
  116. package/components/frame/frame.a2ui.json +1 -0
  117. package/components/grid/grid.a2ui.json +1 -0
  118. package/components/header/header.a2ui.json +3 -2
  119. package/components/header/header.yaml +3 -3
  120. package/components/heatmap/heatmap.a2ui.json +2 -1
  121. package/components/heatmap/heatmap.class.js +1 -1
  122. package/components/heatmap/heatmap.css +4 -4
  123. package/components/heatmap/heatmap.yaml +1 -1
  124. package/components/icon/icon.a2ui.json +1 -0
  125. package/components/image/image.a2ui.json +28 -14
  126. package/components/image/image.class.js +38 -23
  127. package/components/image/image.css +18 -8
  128. package/components/image/image.d.ts +2 -2
  129. package/components/image/image.examples.md +3 -3
  130. package/components/image/image.yaml +23 -7
  131. package/components/inline-edit/inline-edit.a2ui.json +1 -0
  132. package/components/inline-message/inline-message.a2ui.json +3 -1
  133. package/components/inline-message/inline-message.class.js +3 -1
  134. package/components/inline-message/inline-message.d.ts +2 -2
  135. package/components/inline-message/inline-message.yaml +2 -1
  136. package/components/input/input.a2ui.json +66 -16
  137. package/components/input/input.class.js +64 -7
  138. package/components/input/input.css +14 -14
  139. package/components/input/input.d.ts +9 -4
  140. package/components/input/input.yaml +83 -10
  141. package/components/inspector/inspector.a2ui.json +1 -0
  142. package/components/integration-card/integration-card.a2ui.json +1 -0
  143. package/components/kbd/kbd.a2ui.json +1 -0
  144. package/components/link/link.a2ui.json +2 -1
  145. package/components/link/link.yaml +1 -1
  146. package/components/list/list-item.a2ui.json +9 -0
  147. package/components/list/list-item.yaml +12 -0
  148. package/components/list/list.a2ui.json +9 -3
  149. package/components/list/list.class.js +14 -1
  150. package/components/list/list.css +12 -1
  151. package/components/list/list.d.ts +7 -5
  152. package/components/list/list.yaml +16 -6
  153. package/components/list-window/list-window.a2ui.json +1 -0
  154. package/components/list-window/list-window.class.js +2 -1
  155. package/components/loading-overlay/loading-overlay.a2ui.json +1 -0
  156. package/components/mark/mark.a2ui.json +2 -1
  157. package/components/mark/mark.yaml +1 -1
  158. package/components/menu/menu-divider.a2ui.json +1 -0
  159. package/components/menu/menu-item.a2ui.json +3 -1
  160. package/components/menu/menu-item.yaml +6 -0
  161. package/components/menu/menu-label.a2ui.json +1 -0
  162. package/components/menu/menu.a2ui.json +3 -2
  163. package/components/menu/menu.class.js +2 -2
  164. package/components/menu/menu.d.ts +2 -2
  165. package/components/menu/menu.yaml +3 -3
  166. package/components/modal/modal.a2ui.json +1 -0
  167. package/components/nav/nav.a2ui.json +1 -0
  168. package/components/nav/nav.class.js +14 -3
  169. package/components/nav-group/nav-group.a2ui.json +1 -0
  170. package/components/nav-group/nav-group.class.js +3 -1
  171. package/components/nav-item/nav-item.a2ui.json +1 -0
  172. package/components/noodles/noodles.a2ui.json +1 -0
  173. package/components/noodles/noodles.class.js +5 -1
  174. package/components/number-format/number-format.a2ui.json +1 -0
  175. package/components/option-card/option-card.a2ui.json +1 -0
  176. package/components/otp-input/otp-input.a2ui.json +3 -2
  177. package/components/otp-input/otp-input.yaml +9 -2
  178. package/components/page/page.a2ui.json +13 -0
  179. package/components/page/page.d.ts +1 -1
  180. package/components/page/page.yaml +1 -0
  181. package/components/pagination/pagination.a2ui.json +1 -0
  182. package/components/pagination/pagination.class.js +3 -2
  183. package/components/pane/pane.a2ui.json +6 -0
  184. package/components/pane/pane.class.js +3 -3
  185. package/components/pane/pane.css +2 -2
  186. package/components/pane/pane.yaml +3 -0
  187. package/components/password-strength/password-strength.a2ui.json +1 -0
  188. package/components/pipeline-status/pipeline-status.a2ui.json +1 -0
  189. package/components/popover/popover.a2ui.json +7 -6
  190. package/components/popover/popover.class.js +2 -2
  191. package/components/popover/popover.d.ts +2 -2
  192. package/components/popover/popover.yaml +4 -4
  193. package/components/preview/preview.a2ui.json +1 -0
  194. package/components/preview/preview.class.js +5 -1
  195. package/components/progress/progress.a2ui.json +47 -24
  196. package/components/progress/progress.class.js +66 -11
  197. package/components/progress/progress.css +44 -26
  198. package/components/progress/progress.d.ts +7 -5
  199. package/components/progress/progress.examples.md +2 -2
  200. package/components/progress/progress.yaml +235 -53
  201. package/components/progress-row/progress-row.a2ui.json +16 -12
  202. package/components/progress-row/progress-row.d.ts +2 -2
  203. package/components/progress-row/progress-row.examples.md +2 -14
  204. package/components/progress-row/progress-row.yaml +28 -36
  205. package/components/qr-code/qr-code.a2ui.json +3 -2
  206. package/components/qr-code/qr-code.class.js +4 -4
  207. package/components/qr-code/qr-code.css +1 -1
  208. package/components/qr-code/qr-code.yaml +5 -3
  209. package/components/radio/radio.a2ui.json +8 -7
  210. package/components/radio/radio.class.js +8 -0
  211. package/components/radio/radio.yaml +3 -3
  212. package/components/radio-group/radio-group.a2ui.json +5 -3
  213. package/components/radio-group/radio-group.class.js +11 -0
  214. package/components/radio-group/radio-group.yaml +7 -1
  215. package/components/range/range.a2ui.json +6 -0
  216. package/components/range/range.class.js +2 -2
  217. package/components/range/range.css +2 -2
  218. package/components/range/range.yaml +3 -0
  219. package/components/rating/rating.a2ui.json +1 -0
  220. package/components/relative-time/relative-time.a2ui.json +1 -0
  221. package/components/richtext/richtext.a2ui.json +2 -1
  222. package/components/richtext/richtext.yaml +1 -1
  223. package/components/row/row.a2ui.json +1 -0
  224. package/components/search/search.a2ui.json +1 -0
  225. package/components/search/search.class.js +4 -1
  226. package/components/section/section.a2ui.json +1 -0
  227. package/components/segment/segment.a2ui.json +21 -10
  228. package/components/segment/segment.css +15 -0
  229. package/components/segment/segment.d.ts +2 -1
  230. package/components/segment/segment.yaml +20 -13
  231. package/components/segmented/segmented.a2ui.json +36 -7
  232. package/components/segmented/segmented.class.js +113 -22
  233. package/components/segmented/segmented.css +3 -0
  234. package/components/segmented/segmented.d.ts +3 -1
  235. package/components/segmented/segmented.yaml +80 -8
  236. package/components/select/select.a2ui.json +18 -13
  237. package/components/select/select.class.js +61 -37
  238. package/components/select/select.css +15 -13
  239. package/components/select/select.yaml +18 -7
  240. package/components/skeleton/skeleton.a2ui.json +8 -7
  241. package/components/skeleton/skeleton.class.js +8 -7
  242. package/components/skeleton/skeleton.css +21 -6
  243. package/components/skeleton/skeleton.d.ts +2 -2
  244. package/components/skeleton/skeleton.examples.md +2 -2
  245. package/components/skeleton/skeleton.yaml +3 -2
  246. package/components/skip-nav/skip-nav.a2ui.json +1 -0
  247. package/components/slider/slider.a2ui.json +6 -0
  248. package/components/slider/slider.class.js +20 -3
  249. package/components/slider/slider.css +2 -2
  250. package/components/slider/slider.yaml +3 -0
  251. package/components/spinner/spinner.a2ui.json +1 -0
  252. package/components/stack/stack.a2ui.json +1 -0
  253. package/components/stat/stat.a2ui.json +1 -0
  254. package/components/stat/stat.js +2 -2
  255. package/components/step-progress/step-progress.a2ui.json +1 -0
  256. package/components/step-progress/step-progress.yaml +1 -1
  257. package/components/stepper/stepper-item.a2ui.json +7 -3
  258. package/components/stepper/stepper-item.yaml +15 -5
  259. package/components/stepper/stepper.a2ui.json +5 -1
  260. package/components/stepper/stepper.class.js +31 -16
  261. package/components/stepper/stepper.d.ts +1 -1
  262. package/components/stepper/stepper.yaml +4 -2
  263. package/components/stream/stream.a2ui.json +1 -0
  264. package/components/swatch/swatch.a2ui.json +1 -0
  265. package/components/swiper/swiper.a2ui.json +1 -0
  266. package/components/swiper/swiper.class.js +5 -1
  267. package/components/switch/switch.a2ui.json +12 -11
  268. package/components/switch/switch.css +1 -1
  269. package/components/switch/switch.yaml +6 -6
  270. package/components/table/table.a2ui.json +1 -0
  271. package/components/table/table.class.js +14 -12
  272. package/components/table/table.css +11 -9
  273. package/components/table-toolbar/table-toolbar.a2ui.json +1 -0
  274. package/components/table-toolbar/table-toolbar.class.js +38 -19
  275. package/components/table-toolbar/table-toolbar.css +1 -1
  276. package/components/tabs/tab.a2ui.json +9 -0
  277. package/components/tabs/tab.js +4 -0
  278. package/components/tabs/tab.yaml +15 -0
  279. package/components/tabs/tabs.a2ui.json +9 -4
  280. package/components/tabs/tabs.class.js +13 -1
  281. package/components/tabs/tabs.css +8 -0
  282. package/components/tabs/tabs.d.ts +2 -0
  283. package/components/tabs/tabs.yaml +9 -2
  284. package/components/tag/tag.a2ui.json +1 -0
  285. package/components/tags-input/tags-input.a2ui.json +22 -17
  286. package/components/tags-input/tags-input.class.js +13 -11
  287. package/components/tags-input/tags-input.css +10 -6
  288. package/components/tags-input/tags-input.d.ts +16 -7
  289. package/components/tags-input/tags-input.examples.md +2 -2
  290. package/components/tags-input/tags-input.yaml +25 -8
  291. package/components/text/text.a2ui.json +6 -5
  292. package/components/text/text.css +10 -5
  293. package/components/text/text.d.ts +2 -2
  294. package/components/text/text.yaml +9 -4
  295. package/components/textarea/textarea.a2ui.json +2 -1
  296. package/components/textarea/textarea.yaml +1 -1
  297. package/components/theme-provider/theme-provider.a2ui.json +2 -1
  298. package/components/theme-provider/theme-provider.d.ts +5 -3
  299. package/components/theme-provider/theme-provider.yaml +5 -3
  300. package/components/time-picker/time-picker.a2ui.json +1 -0
  301. package/components/time-picker/time-picker.class.js +4 -2
  302. package/components/timeline/timeline-item.a2ui.json +26 -3
  303. package/components/timeline/timeline-item.yaml +22 -7
  304. package/components/timeline/timeline.a2ui.json +4 -0
  305. package/components/timeline/timeline.class.js +29 -18
  306. package/components/timeline/timeline.d.ts +3 -3
  307. package/components/timeline/timeline.yaml +2 -0
  308. package/components/toast/toast.a2ui.json +10 -3
  309. package/components/toast/toast.yaml +20 -13
  310. package/components/toc/toc.a2ui.json +1 -0
  311. package/components/toggle-group/toggle-group.a2ui.json +7 -3
  312. package/components/toggle-group/toggle-group.class.js +40 -1
  313. package/components/toggle-group/toggle-group.css +6 -1
  314. package/components/toggle-group/toggle-group.d.ts +2 -2
  315. package/components/toggle-group/toggle-group.yaml +21 -12
  316. package/components/toggle-group/toggle-option.a2ui.json +7 -3
  317. package/components/toggle-group/toggle-option.yaml +13 -7
  318. package/components/toggle-scheme/toggle-scheme.a2ui.json +2 -1
  319. package/components/toggle-scheme/toggle-scheme.yaml +1 -1
  320. package/components/toolbar/toolbar-group.a2ui.json +1 -0
  321. package/components/toolbar/toolbar.a2ui.json +1 -0
  322. package/components/toolbar/toolbar.class.js +3 -0
  323. package/components/tooltip/tooltip.a2ui.json +1 -0
  324. package/components/tour/tour-step.a2ui.json +4 -0
  325. package/components/tour/tour-step.yaml +2 -0
  326. package/components/tour/tour.a2ui.json +4 -0
  327. package/components/tour/tour.yaml +2 -0
  328. package/components/tree/tree-item.a2ui.json +5 -0
  329. package/components/tree/tree-item.yaml +3 -0
  330. package/components/tree/tree.a2ui.json +4 -0
  331. package/components/tree/tree.class.js +2 -1
  332. package/components/tree/tree.yaml +2 -0
  333. package/components/upload/upload.a2ui.json +8 -1
  334. package/components/upload/upload.class.js +35 -5
  335. package/components/upload/upload.css +28 -0
  336. package/components/upload/upload.d.ts +16 -0
  337. package/components/upload/upload.yaml +27 -7
  338. package/components/visually-hidden/visually-hidden.a2ui.json +1 -0
  339. package/core/dom.d.ts +11 -0
  340. package/core/dom.js +34 -0
  341. package/core/form.js +7 -0
  342. package/core/index.d.ts +1 -0
  343. package/core/index.js +1 -0
  344. package/core/responsive.js +16 -4
  345. package/core/template.js +2 -1
  346. package/custom-elements.json +437 -489
  347. package/dist/host.min.css +1 -1
  348. package/dist/host.sheet.js +1 -1
  349. package/dist/theme-provider.min.js +5 -5
  350. package/dist/web-components.min.css +1 -1
  351. package/dist/web-components.min.js +91 -104
  352. package/dist/web-components.sheet.js +1 -1
  353. package/package.json +4 -2
  354. package/patterns/conversion-funnel/conversion-funnel.examples.html +15 -15
  355. package/patterns/inline-dialog/inline-dialog.examples.html +2 -2
  356. package/patterns/inline-dialog/inline-dialog.html +4 -0
  357. package/patterns/timezone-selector/timezone-selector.examples.html +1 -1
  358. package/patterns/timezone-selector/timezone-selector.html +2 -2
  359. package/styles/api/text.css +7 -0
  360. package/traits/_catalog.json +129 -41
  361. package/traits/anchor-positioning/anchor-positioning.js +1 -0
  362. package/traits/announcer/announcer.js +1 -0
  363. package/traits/arrow-grid-nav/arrow-grid-nav.js +1 -0
  364. package/traits/attention-pulse/attention-pulse.js +1 -0
  365. package/traits/count-up/count-up.js +1 -0
  366. package/traits/define.js +78 -0
  367. package/traits/focus-restore/focus-restore.js +2 -1
  368. package/traits/haptic-feedback/haptic-feedback.js +1 -0
  369. package/traits/input-mask/input-mask.js +1 -0
  370. package/traits/intersection-observer/intersection-observer.js +1 -0
  371. package/traits/layout-animation/layout-animation.js +1 -0
  372. package/traits/noise-texture/noise-texture.js +1 -0
  373. package/traits/resize-observer/resize-observer.js +1 -0
  374. package/traits/scroll-progress/scroll-progress.js +1 -0
  375. package/traits/snap-to-grid/snap-to-grid.js +1 -0
  376. package/traits/sound-feedback/sound-feedback.js +1 -0
  377. package/traits/spring-animate/spring-animate.js +1 -0
  378. package/traits/validation/validation.js +1 -0
@@ -148,12 +148,7 @@ export class UIInput extends UIFormElement {
148
148
  const trailingNodes = logicalSlotted(this, 'trailing');
149
149
 
150
150
  if (!this.querySelector('[slot="text"]')) {
151
- const labelId = this.label ? `input-label-${++UIInput.#labelSeq}` : '';
152
- this.innerHTML = this.#shellHTML(labelId);
153
- // innerHTML wiped the consumer-supplied leading/trailing nodes; re-attach
154
- // them into the field at the right positions. Order matches the JSDoc:
155
- // prefix → leading → label → text → suffix → trailing → controls
156
- this.#installAffordances(leadingNodes, trailingNodes);
151
+ this.#buildShell(leadingNodes, trailingNodes);
157
152
  }
158
153
 
159
154
  this.#textEl = this.querySelector('[slot="text"]');
@@ -200,6 +195,64 @@ export class UIInput extends UIFormElement {
200
195
  }
201
196
  }
202
197
 
198
+ #buildShell(leadingNodes, trailingNodes) {
199
+ const labelId = this.label ? `input-label-${++UIInput.#labelSeq}` : '';
200
+ this.innerHTML = this.#shellHTML(labelId);
201
+ // innerHTML wiped the consumer-supplied leading/trailing nodes; re-attach
202
+ // them into the field at the right positions. Order matches the JSDoc:
203
+ // prefix → leading → label → text → suffix → trailing → controls
204
+ this.#installAffordances(leadingNodes, trailingNodes);
205
+ }
206
+
207
+ /**
208
+ * Post-connect `type` reconciliation (gh#1332 review follow-up). The shell
209
+ * is built once at connect, but `type` is reactive and reflected — since
210
+ * number-mode CSS now keys off the host's [type="number"] directly, a
211
+ * text↔number flip after connect would restyle without restructuring
212
+ * (role, inputmode, stepper controls, beforeinput filter all stale).
213
+ * Rebuild the shell + listeners when the built mode disagrees with the
214
+ * live property. No-op on every render where the modes already agree.
215
+ */
216
+ #reconcileTypeMode() {
217
+ if (this.#isNativePassword || !this.#textEl) return;
218
+ const builtNumberMode = !!this.querySelector(':scope > [slot="field"] [data-controls]');
219
+ if (builtNumberMode === this.#isNumberMode) return;
220
+
221
+ // Unwire the old surface, capture consumer affordances from inside the
222
+ // live field (logicalSlotted won't re-match nodes already moved there).
223
+ this.#textEl.removeEventListener('input', this.#onInput);
224
+ this.#textEl.removeEventListener('keydown', this.#onKeydown);
225
+ this.#textEl.removeEventListener('blur', this.#onBlur);
226
+ this.#textEl.removeEventListener('focus', this.#onFocus);
227
+ this.#textEl.removeEventListener('paste', this.#onPaste);
228
+ this.#textEl.removeEventListener('beforeinput', this.#onBeforeInput);
229
+ const field = this.querySelector(':scope > [slot="field"]');
230
+ const leadingNodes = field ? [...field.querySelectorAll(':scope > [slot="leading"]')] : [];
231
+ const trailingNodes = field ? [...field.querySelectorAll(':scope > [slot="trailing"]')] : [];
232
+
233
+ this.#buildShell(leadingNodes, trailingNodes);
234
+ this.setAttribute('role', this.#isNumberMode ? 'spinbutton' : 'textbox');
235
+ this.#textEl = this.querySelector('[slot="text"]');
236
+ this.#labelEl = this.querySelector('[slot="label"]');
237
+ this.#upBtn = this.querySelector('[data-step="up"]');
238
+ this.#downBtn = this.querySelector('[data-step="down"]');
239
+ if (this.#textEl) {
240
+ this.#textEl.addEventListener('input', this.#onInput);
241
+ this.#textEl.addEventListener('keydown', this.#onKeydown);
242
+ this.#textEl.addEventListener('blur', this.#onBlur);
243
+ this.#textEl.addEventListener('focus', this.#onFocus);
244
+ this.#textEl.addEventListener('paste', this.#onPaste);
245
+ if (this.#isNumberMode) {
246
+ this.#textEl.addEventListener('beforeinput', this.#onBeforeInput);
247
+ }
248
+ }
249
+ this.#upBtn?.addEventListener('pointerdown', this.#onStepperUpDown);
250
+ this.#downBtn?.addEventListener('pointerdown', this.#onStepperDownDown);
251
+ if (this.prefix || this.suffix) {
252
+ whenIconRegistryReady.then(() => this.#promoteAffixes());
253
+ }
254
+ }
255
+
203
256
  #shellHTML(labelId) {
204
257
  const prefix = this.prefix ? `<span slot="prefix">${renderAffix(this.prefix)}</span>` : '';
205
258
  const label = this.label ? `<span slot="label" id="${labelId}">${this.label}</span>` : '';
@@ -234,8 +287,11 @@ export class UIInput extends UIFormElement {
234
287
  icon="caret-down" data-step="down" aria-label="Decrease"></button-ui>
235
288
  </span>` : '';
236
289
 
290
+ // No private data-number mirror on the field div — number-mode CSS keys
291
+ // off the declared, reflected host [type="number"] directly (gh#1332
292
+ // Category A; select-ui data-multi-chips precedent).
237
293
  return `
238
- <div slot="field"${this.#isNumberMode ? ' data-number' : ''}>
294
+ <div slot="field">
239
295
  ${prefix}${label}${editable}${suffix}${controls}
240
296
  </div>
241
297
  `;
@@ -372,6 +428,7 @@ export class UIInput extends UIFormElement {
372
428
 
373
429
  render() {
374
430
  if (!this.#textEl) return;
431
+ this.#reconcileTypeMode();
375
432
 
376
433
  const text = this.value ?? '';
377
434
 
@@ -191,10 +191,10 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
191
191
  absolutely against the field's inline-end edge so it NEVER affects
192
192
  the field's flex-sized height — number-mode inputs share the same
193
193
  24/30/36px (sm/md/lg) baseline as text/email/password/etc. */
194
- [data-number] [slot="text"] {
194
+ :scope[type="number"] [slot="field"] [slot="text"] {
195
195
  text-align: end;
196
196
  font-variant-numeric: tabular-nums;
197
- font-weight: var(--a-weight-medium, 500);
197
+ font-weight: var(--a-weight-medium);
198
198
  /* Reserve space for the absolutely-positioned controls column so the
199
199
  value never collides with the stepper buttons. */
200
200
  padding-inline-end: var(--input-controls-width, calc(var(--input-height) * 0.7));
@@ -202,7 +202,7 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
202
202
 
203
203
  /* Suffix sits flush after the value in number mode (no auto margin so
204
204
  the value+suffix pair stays right-aligned together). */
205
- [data-number] [slot="suffix"] {
205
+ :scope[type="number"] [slot="field"] [slot="suffix"] {
206
206
  margin-inline-start: 0;
207
207
  /* Reserve space for the absolutely-positioned controls column so the
208
208
  suffix never collides with the stepper buttons. The padding moves
@@ -210,11 +210,11 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
210
210
  — only one element needs the reservation since they're adjacent. */
211
211
  margin-inline-end: var(--input-controls-width, calc(var(--input-height) * 0.7));
212
212
  }
213
- [data-number]:has([slot="suffix"]) [slot="text"] {
213
+ :scope[type="number"] [slot="field"]:has([slot="suffix"]) [slot="text"] {
214
214
  padding-inline-end: 0;
215
215
  }
216
216
 
217
- [data-number] {
217
+ :scope[type="number"] [slot="field"] {
218
218
  position: relative;
219
219
  }
220
220
 
@@ -230,7 +230,7 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
230
230
  overflow: hidden;
231
231
  }
232
232
 
233
- [data-number] [slot="controls"] button-ui {
233
+ :scope[type="number"] [slot="field"] [slot="controls"] button-ui {
234
234
  /* Defeat button-ui's intrinsic min-height (driven by --button-height) so
235
235
  its content can't push past half the column height. Padding + border
236
236
  are zeroed; the divider between the two buttons comes from the
@@ -256,14 +256,14 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
256
256
  because its own `:where(:scope)` declaration of --icon-size wins over
257
257
  any value inherited from its parent button-ui. Tying it to
258
258
  --input-height keeps the caret proportional across sm/md/lg. */
259
- [data-number] [slot="controls"] icon-ui {
259
+ :scope[type="number"] [slot="field"] [slot="controls"] icon-ui {
260
260
  --icon-size: calc(var(--input-height) * 0.4);
261
261
  }
262
- [data-number] [slot="controls"] button-ui:hover {
262
+ :scope[type="number"] [slot="field"] [slot="controls"] button-ui:hover {
263
263
  --button-bg: var(--a-ui-bg-hover);
264
264
  --button-fg: var(--a-ui-text);
265
265
  }
266
- [data-number] [slot="controls"] button-ui[disabled] {
266
+ :scope[type="number"] [slot="field"] [slot="controls"] button-ui[disabled] {
267
267
  --button-fg: var(--a-ui-text-disabled);
268
268
  pointer-events: none;
269
269
  }
@@ -273,11 +273,11 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
273
273
  field radius cleanly. The divider between the two buttons is the
274
274
  inline-start border on the column + the bottom-border on the upper
275
275
  button. */
276
- [data-number] [slot="controls"] button-ui:first-child {
276
+ :scope[type="number"] [slot="field"] [slot="controls"] button-ui:first-child {
277
277
  border-bottom: 1px solid var(--input-border);
278
278
  border-start-end-radius: calc(var(--input-radius) - 1px);
279
279
  }
280
- [data-number] [slot="controls"] button-ui:last-child {
280
+ :scope[type="number"] [slot="field"] [slot="controls"] button-ui:last-child {
281
281
  border-end-end-radius: calc(var(--input-radius) - 1px);
282
282
  }
283
283
 
@@ -287,8 +287,8 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
287
287
  margin-inline-end: 0;
288
288
  border-inline-start: none;
289
289
  }
290
- :scope[raw] [data-number] [slot="controls"] button-ui:first-child,
291
- :scope[raw] [data-number] [slot="controls"] button-ui:last-child {
290
+ :scope[raw][type="number"] [slot="field"] [slot="controls"] button-ui:first-child,
291
+ :scope[raw][type="number"] [slot="field"] [slot="controls"] button-ui:last-child {
292
292
  border-bottom: none;
293
293
  border-radius: 0;
294
294
  }
@@ -373,7 +373,7 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
373
373
  for the stepper column so it doesn't overlap. (controls is
374
374
  absolutely positioned at inset-inline-end: 0; trailing sits in the
375
375
  flex flow.) Same pattern as [slot="suffix"] in number mode. */
376
- [data-number]:has(> [slot="trailing"]) > [slot="trailing"] {
376
+ :scope[type="number"] [slot="field"]:has(> [slot="trailing"]) > [slot="trailing"] {
377
377
  margin-inline-end: var(--input-controls-width, calc(var(--input-height) * 0.7));
378
378
  }
379
379
 
@@ -1,20 +1,25 @@
1
1
  /**
2
- * `<input-ui>` — Text + email + password + number + tel + url + search input.
2
+ * `<input-ui>` — Text + email + password + number + tel + url input.
3
3
  *
4
4
  * @see https://ui-kit.exe.xyz/site/components/input
5
5
  */
6
6
 
7
7
  import { UIFormElement } from '../../core/form.js';
8
8
 
9
- /** Native HTML input types this primitive supports. */
9
+ /**
10
+ * Native HTML input types this primitive actually implements. Does NOT
11
+ * cover date/datetime-local/time/month/week/color/search — those never
12
+ * rendered a native affordance on this primitive (v0.9.0, gh#1363 B10);
13
+ * route to CalendarPicker/DatetimePicker/TimePicker/ColorInput/Search
14
+ * instead.
15
+ */
10
16
  export type UIInputType =
11
17
  | 'text'
12
18
  | 'email'
13
19
  | 'password'
14
20
  | 'number'
15
21
  | 'tel'
16
- | 'url'
17
- | 'search';
22
+ | 'url';
18
23
 
19
24
  export interface InputChangeEventDetail {
20
25
  value: string;
@@ -23,8 +23,19 @@ props:
23
23
  type: string
24
24
  default: ''
25
25
  type:
26
- description: Input type. `password` wraps a native `<input>` for disc masking; `number` renders a contenteditable + stepper
27
- buttons (no native input). All other types use plain contenteditable.
26
+ description: >-
27
+ Input type. `password` wraps a native `<input>` for disc masking;
28
+ `number` renders a contenteditable + stepper buttons (no native
29
+ input). `text`/`email`/`tel`/`url` use plain contenteditable.
30
+ input-ui does NOT implement date/time/color/search affordances —
31
+ those HTML5 type values render as a plain contenteditable with no
32
+ calendar, clock, swatch, or search affordance (first-pass-VALID
33
+ against this enum historically, but functionally broken generation
34
+ output). Use the owning primitive instead — CalendarPicker for date
35
+ (+ month/week, no dedicated granularity primitive exists),
36
+ DatetimePicker for datetime-local, TimePicker for time,
37
+ ColorInput/ColorPicker for color, Search for search. See
38
+ anti_patterns.
28
39
  type: string
29
40
  default: text
30
41
  enum:
@@ -34,13 +45,6 @@ props:
34
45
  - number
35
46
  - tel
36
47
  - url
37
- - search
38
- - date
39
- - datetime-local
40
- - time
41
- - month
42
- - week
43
- - color
44
48
  required:
45
49
  description: Marks the field as required for form validation. Sets aria-required.
46
50
  type: boolean
@@ -282,7 +286,71 @@ a2ui:
282
286
  primitive ALSO fires a bubbling `submit` event on Enter
283
287
  (unconditional, no opt-in attribute); <chat-input-ui> simply
284
288
  builds on that semantic.
285
- anti_patterns: []
289
+ - >-
290
+ input-ui's `type` enum covers only the modes it actually implements
291
+ — text/email/password/number/tel/url (gh#1363 B10). Never
292
+ set [type] to an HTML5 value input-ui doesn't render — route
293
+ date/time/color/search intents to the owning primitive instead:
294
+ CalendarPicker, DatetimePicker, TimePicker, ColorInput (or
295
+ ColorPicker directly), Search. See anti_patterns.
296
+ anti_patterns:
297
+ - wrong: |
298
+ {"component": "Input", "type": "date"}
299
+ why: |
300
+ input-ui's `type` enum no longer includes "date" (gh#1363
301
+ B10) — it never rendered a native date picker, just a plain
302
+ contenteditable surface with no calendar affordance. Use
303
+ CalendarPicker for single-date input.
304
+ fix: |
305
+ {"component": "CalendarPicker"}
306
+ - wrong: |
307
+ {"component": "Input", "type": "datetime-local"}
308
+ why: |
309
+ Same gap as "date" — no native datetime picker rendered. Use
310
+ DatetimePicker for combined date + time input.
311
+ fix: |
312
+ {"component": "DatetimePicker"}
313
+ - wrong: |
314
+ {"component": "Input", "type": "time"}
315
+ why: |
316
+ Same gap as "date" — no native time picker rendered. Use
317
+ TimePicker for time-of-day input.
318
+ fix: |
319
+ {"component": "TimePicker"}
320
+ - wrong: |
321
+ {"component": "Input", "type": "month"}
322
+ why: |
323
+ No native month picker rendered, and no dedicated month-granularity
324
+ primitive exists in the catalog. CalendarPicker (day granularity)
325
+ is the closest routed primitive.
326
+ fix: |
327
+ {"component": "CalendarPicker"}
328
+ - wrong: |
329
+ {"component": "Input", "type": "week"}
330
+ why: |
331
+ No native week picker rendered, and no dedicated week-granularity
332
+ primitive exists in the catalog. CalendarPicker (day granularity)
333
+ is the closest routed primitive.
334
+ fix: |
335
+ {"component": "CalendarPicker"}
336
+ - wrong: |
337
+ {"component": "Input", "type": "color"}
338
+ why: |
339
+ No native color swatch/picker rendered — just a plain text
340
+ surface. Use ColorInput for a compact form-bearing swatch +
341
+ popover, or ColorPicker directly for the full OKLCH picker
342
+ surface.
343
+ fix: |
344
+ {"component": "ColorInput"}
345
+ - wrong: |
346
+ {"component": "Input", "type": "search"}
347
+ why: |
348
+ No search icon or clear affordance rendered — plain
349
+ contenteditable, functionally identical to the default text
350
+ type. Use Search for the dedicated magnifying-glass + clear +
351
+ debounced-search-event primitive.
352
+ fix: |
353
+ {"component": "Search"}
286
354
  examples:
287
355
  - name: quantity-stepper
288
356
  description: Number input with [+]/[-] stepper buttons, min/max bounds, and a quantity label. Use for product quantity,
@@ -391,3 +459,8 @@ related:
391
459
  - table
392
460
  - pagination
393
461
  - check-box
462
+ - search
463
+ - calendar-picker
464
+ - datetime-picker
465
+ - time-picker
466
+ - color-input
@@ -46,6 +46,7 @@
46
46
  "inspector"
47
47
  ],
48
48
  "name": "UIInspector",
49
+ "parts": {},
49
50
  "related": [
50
51
  "A2uiRoot",
51
52
  "EditorSidebar",
@@ -150,6 +150,7 @@
150
150
  "tile"
151
151
  ],
152
152
  "name": "UIIntegrationCard",
153
+ "parts": {},
153
154
  "related": [
154
155
  "card",
155
156
  "badge",
@@ -56,6 +56,7 @@
56
56
  "keybinding"
57
57
  ],
58
58
  "name": "UIKbd",
59
+ "parts": {},
59
60
  "related": [
60
61
  "grid"
61
62
  ],
@@ -79,7 +79,7 @@
79
79
  "❌ `<link-ui>` with no `href` AND no `press` handler — a link to nowhere is a bug. Either set `href` or wire a navigate action handler.",
80
80
  "❌ `<link-ui>` for form submission — submission is a button concern. Use `<button-ui type=\"submit\">`."
81
81
  ],
82
- "category": "content",
82
+ "category": "display",
83
83
  "composes": [],
84
84
  "events": {
85
85
  "press": {
@@ -121,6 +121,7 @@
121
121
  "url"
122
122
  ],
123
123
  "name": "UILink",
124
+ "parts": {},
124
125
  "related": [
125
126
  "Button",
126
127
  "NavItem",
@@ -3,7 +3,7 @@ name: UILink
3
3
  tag: link-ui
4
4
  status: stable
5
5
  component: Link
6
- category: content
6
+ category: display
7
7
  version: 1
8
8
  description: |
9
9
  Inline navigation primitive — semantic `<a href>` wrapper. Use for
@@ -24,6 +24,11 @@
24
24
  "description": "Optional leading icon name (Phosphor).",
25
25
  "type": "string"
26
26
  },
27
+ "selected": {
28
+ "description": "Whether this row is the current selection. Managed by the parent <list-ui>[selectable] — don't set directly; the parent's [selectedKey] is the single source of truth (ADR-0056).",
29
+ "type": "boolean",
30
+ "default": false
31
+ },
27
32
  "text": {
28
33
  "description": "Primary text. Renders as semibold inline body.",
29
34
  "type": "string"
@@ -34,6 +39,9 @@
34
39
  ],
35
40
  "unevaluatedProperties": false,
36
41
  "x-adiaui": {
42
+ "allowedParents": [
43
+ "List"
44
+ ],
37
45
  "anti_patterns": [],
38
46
  "category": "layout",
39
47
  "composes": [],
@@ -47,6 +55,7 @@
47
55
  "row"
48
56
  ],
49
57
  "name": "UIListItem",
58
+ "parts": {},
50
59
  "related": [
51
60
  "List",
52
61
  "NavItem",
@@ -28,6 +28,14 @@ props:
28
28
  description:
29
29
  description: Secondary line below the primary text. Subtle color.
30
30
  type: string
31
+ selected:
32
+ description: >-
33
+ Whether this row is the current selection. Managed by the parent
34
+ <list-ui>[selectable] — don't set directly; the parent's
35
+ [selectedKey] is the single source of truth (ADR-0056).
36
+ type: boolean
37
+ default: false
38
+ reflect: true
31
39
 
32
40
  slots:
33
41
  text:
@@ -78,6 +86,8 @@ related:
78
86
  - TreeItem
79
87
 
80
88
  a2ui:
89
+ allowedParents:
90
+ - List
81
91
  rules:
82
92
  - rule: 'Child of <list-ui> — one row of generic-list content.'
83
93
  reason: 'Cluster contract.'
@@ -85,3 +95,5 @@ a2ui:
85
95
  reason: 'Per-context list-row primitives — list-item is generic.'
86
96
  - rule: 'Interactive list-items should have role=''button'' or be wrapped in <button-ui>; default is non-interactive.'
87
97
  reason: 'Accessibility — semantic clarity.'
98
+ - rule: '[selected] reflects the row current-selection state under <list-ui selectable> — it is parent-managed (ADR-0056); drive selection via the parent''s [selectedKey], never [selected] directly.'
99
+ reason: 'Single source of truth stays on the parent, mirroring <segment-ui>.'
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://adiaui.dev/a2ui/v0_9/components/List.json",
4
4
  "title": "List",
5
- "description": "Styled flat list with optional row dividers and icons — each row\nis a <list-item-ui> child. Selectable mode via [selectable] +\n[selectedKey] / `select` event. Composes <icon-ui> for row leading\nglyphs. Distinct from <nav-ui> (sidebar navigation, route-aware),\n<tree-ui> (hierarchical with arbitrary nesting), and <table-ui>\n(tabular data with columns). Use list-ui for non-navigable,\nnon-tabular vertical rows — comment lists, file lists, message\nstreams outside chat-shell.\n",
5
+ "description": "Styled flat list with optional row dividers and icons — each row\nis a <list-item-ui> child. Selectable mode via [selectable] +\n[selectedKey] / `selection-change` event. Composes <icon-ui> for row\nleading glyphs. Distinct from <nav-ui> (sidebar navigation, route-aware),\n<tree-ui> (hierarchical with arbitrary nesting), and <table-ui>\n(tabular data with columns). Use list-ui for non-navigable,\nnon-tabular vertical rows — comment lists, file lists, message\nstreams outside chat-shell.\n",
6
6
  "type": "object",
7
7
  "allOf": [
8
8
  {
@@ -32,13 +32,18 @@
32
32
  "default": false
33
33
  },
34
34
  "selectedKey": {
35
- "description": "ID (or data-key) of the currently-selected child listitem.",
35
+ "description": "ID (or data-key) of the currently-selected child listitem — drives each child's [selected] reflected prop (ADR-0056, #1363 B7). Kept as `selectedKey` rather than renamed to `value` (segmented/tabs/ select's parent-selection prop name): list rows are identified by DOM id/data-key, not an option value, and ADR-0056 itself governs only the child's own [selected] state surface, not this naming — a cross-family rename has no ratifying ADR (B5/B9-shaped follow-up).",
36
36
  "type": "string",
37
37
  "default": ""
38
38
  },
39
39
  "spacing": {
40
- "description": "Item spacing",
40
+ "description": "Item spacing scale — `sm`/`lg` modulate the per-item inset; `md` (default) is the base inset.",
41
41
  "type": "string",
42
+ "enum": [
43
+ "sm",
44
+ "md",
45
+ "lg"
46
+ ],
42
47
  "default": "md"
43
48
  }
44
49
  },
@@ -84,6 +89,7 @@
84
89
  "star"
85
90
  ],
86
91
  "name": "UIList",
92
+ "parts": {},
87
93
  "related": [
88
94
  "ListItem",
89
95
  "Tree",
@@ -28,7 +28,9 @@
28
28
  * When `selectable`:
29
29
  * - Children (role="listitem") become keyboard-focusable via roving tabindex.
30
30
  * - Click selects the child; key is its id or data-key.
31
- * - aria-selected is set on all children.
31
+ * - [selected] (ADR-0056, #1363 B7) and aria-selected are both set on
32
+ * all children — [selected] is the declared API state, aria-selected
33
+ * is wiring.
32
34
  * - Fires `selection-change` with { key, previousKey }.
33
35
  */
34
36
 
@@ -73,6 +75,7 @@ export class UIList extends UIElement {
73
75
  // (patient-visit rail's roving tabindex, gh#746).
74
76
  if (this.#stampedSelection) {
75
77
  for (const ch of this.#items()) {
78
+ ch.removeAttribute('selected');
76
79
  ch.removeAttribute('aria-selected');
77
80
  ch.removeAttribute('tabindex');
78
81
  }
@@ -87,6 +90,11 @@ export class UIList extends UIElement {
87
90
  for (const ch of items) {
88
91
  const k = this.#keyOf(ch);
89
92
  const selected = k && k === key;
93
+ // [selected] (ADR-0056, #1363 B7) is the declared, reflected API
94
+ // surface — parent-managed, mirrors <segment-ui>. aria-selected
95
+ // stays as-is: ARIA is wiring, not API.
96
+ if (selected) ch.setAttribute('selected', '');
97
+ else ch.removeAttribute('selected');
90
98
  ch.setAttribute('aria-selected', selected ? 'true' : 'false');
91
99
  ch.setAttribute('tabindex', selected ? '0' : '-1');
92
100
  if (selected) focusTarget = ch;
@@ -173,6 +181,11 @@ export class UIListItem extends UIElement {
173
181
  icon: { type: String, default: '', reflect: true },
174
182
  text: { type: String, default: '', reflect: true },
175
183
  description: { type: String, default: '', reflect: true },
184
+ // ADR-0056 (gh#1303, #1363 B7): declared reflected selection-item
185
+ // state, parent-managed — <list-ui>[selectable] stamps this instead
186
+ // of leaving selection visible only via aria-selected. Mirrors
187
+ // <segment-ui>'s [selected].
188
+ selected: { type: Boolean, default: false, reflect: true },
176
189
  };
177
190
 
178
191
  static template = () => null;
@@ -67,7 +67,18 @@
67
67
  background: var(--a-bg-hover);
68
68
  }
69
69
 
70
- :scope[selectable] > [role="listitem"][aria-selected="true"] {
70
+ /* [selected] is the declared, reflected state hook (ADR-0056, #1363 B7)
71
+ — list.class.js stamps it in lockstep with aria-selected; ARIA stays
72
+ wiring, not the style hook. Matched on the tag directly (not only
73
+ [role="listitem"]): UIList's adopt-or-stamp (gh#753) lets a
74
+ consumer-authored role (e.g. role="button") survive on <list-item-ui>
75
+ — #items() still selects/stamps it by TAG, so the style hook must
76
+ too, or a role-overridden selected row silently loses its selected
77
+ colors. The role fallback stays for non-list-item-ui children that
78
+ opt into role="listitem" themselves (list-ui also hosts "arbitrary
79
+ children", list.yaml). */
80
+ :scope[selectable] > list-item-ui[selected],
81
+ :scope[selectable] > [role="listitem"][selected] {
71
82
  background: var(--list-bg-selected);
72
83
  color: var(--list-fg-selected);
73
84
  border-inline-start-color: var(--list-border-selected);
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * `<list-ui>` — Styled flat list with optional row dividers and icons — each row
3
3
  is a <list-item-ui> child. Selectable mode via [selectable] +
4
- [selectedKey] / `select` event. Composes <icon-ui> for row leading
5
- glyphs. Distinct from <nav-ui> (sidebar navigation, route-aware),
4
+ [selectedKey] / `selection-change` event. Composes <icon-ui> for row
5
+ leading glyphs. Distinct from <nav-ui> (sidebar navigation, route-aware),
6
6
  <tree-ui> (hierarchical with arbitrary nesting), and <table-ui>
7
7
  (tabular data with columns). Use list-ui for non-navigable,
8
8
  non-tabular vertical rows — comment lists, file lists, message
@@ -41,10 +41,10 @@ so content sits at the list-ui edges (canonical edge-to-edge style).
41
41
  divider: boolean;
42
42
  /** Enable selection on child listitems (roving tabindex + aria-selected). */
43
43
  selectable: boolean;
44
- /** ID (or data-key) of the currently-selected child listitem. */
44
+ /** ID (or data-key) of the currently-selected child listitem — drives each child's [selected] reflected prop (ADR-0056, #1363 B7). Kept as `selectedKey` rather than renamed to `value` (segmented/tabs/ select's parent-selection prop name): list rows are identified by DOM id/data-key, not an option value, and ADR-0056 itself governs only the child's own [selected] state surface, not this naming — a cross-family rename has no ratifying ADR (B5/B9-shaped follow-up). */
45
45
  selectedKey: string;
46
- /** Item spacing */
47
- spacing: string;
46
+ /** Item spacing scale — `sm`/`lg` modulate the per-item inset; `md` (default) is the base inset. */
47
+ spacing: 'sm' | 'md' | 'lg';
48
48
 
49
49
  addEventListener(type: 'selection-change', listener: (ev: ListSelectionChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
50
50
  addEventListener<K extends keyof HTMLElementEventMap>(
@@ -59,6 +59,8 @@ export class UIListItem extends UIElement {
59
59
  description: string;
60
60
  /** Optional leading icon name (Phosphor). */
61
61
  icon: string;
62
+ /** Whether this row is the current selection. Managed by the parent <list-ui>[selectable] — don't set directly; the parent's [selectedKey] is the single source of truth (ADR-0056). */
63
+ selected: boolean;
62
64
  /** Primary text. Renders as semibold inline body. */
63
65
  text: string;
64
66
  }
@@ -10,8 +10,8 @@ version: 1
10
10
  description: |
11
11
  Styled flat list with optional row dividers and icons — each row
12
12
  is a <list-item-ui> child. Selectable mode via [selectable] +
13
- [selectedKey] / `select` event. Composes <icon-ui> for row leading
14
- glyphs. Distinct from <nav-ui> (sidebar navigation, route-aware),
13
+ [selectedKey] / `selection-change` event. Composes <icon-ui> for row
14
+ leading glyphs. Distinct from <nav-ui> (sidebar navigation, route-aware),
15
15
  <tree-ui> (hierarchical with arbitrary nesting), and <table-ui>
16
16
  (tabular data with columns). Use list-ui for non-navigable,
17
17
  non-tabular vertical rows — comment lists, file lists, message
@@ -40,14 +40,22 @@ props:
40
40
  type: boolean
41
41
  default: false
42
42
  selectedKey:
43
- description: ID (or data-key) of the currently-selected child listitem.
43
+ description: >-
44
+ ID (or data-key) of the currently-selected child listitem — drives
45
+ each child's [selected] reflected prop (ADR-0056, #1363 B7). Kept
46
+ as `selectedKey` rather than renamed to `value` (segmented/tabs/
47
+ select's parent-selection prop name): list rows are identified by
48
+ DOM id/data-key, not an option value, and ADR-0056 itself governs
49
+ only the child's own [selected] state surface, not this naming —
50
+ a cross-family rename has no ratifying ADR (B5/B9-shaped follow-up).
44
51
  type: string
45
52
  default: ""
46
53
  attribute: selected-key
47
54
  spacing:
48
- description: Item spacing
55
+ description: Item spacing scale — `sm`/`lg` modulate the per-item inset; `md` (default) is the base inset.
49
56
  type: string
50
57
  default: md
58
+ enum: [sm, md, lg]
51
59
  events:
52
60
  selection-change:
53
61
  description: "Fired on selection-change."
@@ -68,10 +76,12 @@ a2ui:
68
76
  rules:
69
77
  - rule: 'Generic vertical list container — hosts <list-item-ui> or arbitrary children.'
70
78
  reason: 'Lowest-friction list primitive.'
71
- - rule: 'For interactive selection lists use <nav-ui> (single-select navigation) or <menu-ui> (action menu); list-ui is content display.'
72
- reason: 'Different interaction contracts.'
79
+ - rule: 'list-ui supports single selection natively via [selectable] + [selectedKey] (ADR-0056, #1363 B7) — the parent stamps the matching <list-item-ui>''s [selected] + aria-selected and fires selection-change. For route-aware navigation use <nav-ui> instead; for a popover action menu use <menu-ui> instead both have different interaction contracts than a plain selectable list.'
80
+ reason: 'list-ui selectable is the supported non-navigation, non-menu selection surface; nav-ui/menu-ui remain the right choice for their own contracts.'
73
81
  - rule: 'For data-grid / sortable / sticky-header needs use <table-ui> instead.'
74
82
  reason: 'Table has different semantics.'
83
+ - rule: 'list-ui carries no fire-and-forget "action" mode. [selectable] keeps a persisted single selection (`selectedKey` / `aria-selected`, roving tabindex); for rows that activate immediately with no persisted selection state use <action-list-ui> + <action-item-ui> instead.'
84
+ reason: 'Container-level decision (#1363 C5): kept separate rather than absorbed as a mode axis — stateful selection and fire-and-forget activation are genuinely different interaction contracts, and list-item-ui''s selection state is under active convergence elsewhere (#1379); rationale in gh#1374 Findings.'
75
85
  anti_patterns: []
76
86
  examples:
77
87
  - name: basic-list
@@ -179,6 +179,7 @@
179
179
  "10k-rows"
180
180
  ],
181
181
  "name": "UIListWindow",
182
+ "parts": {},
182
183
  "related": [
183
184
  "List",
184
185
  "ListItem",