@adia-ai/web-components 0.6.34 → 0.6.36

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 (280) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/color/index.js +1 -1
  3. package/components/accordion/accordion-item.yaml +2 -2
  4. package/components/accordion/accordion.js +1 -1
  5. package/components/action-list/action-item.yaml +2 -2
  6. package/components/action-list/action-list.js +1 -1
  7. package/components/agent-artifact/{class.js → agent-artifact.class.js} +1 -1
  8. package/components/agent-artifact/agent-artifact.js +1 -1
  9. package/components/agent-feedback-bar/agent-feedback-bar.js +1 -1
  10. package/components/agent-questions/agent-questions.js +1 -1
  11. package/components/agent-reasoning/agent-reasoning.js +1 -1
  12. package/components/agent-suggestions/agent-suggestions.js +1 -1
  13. package/components/alert/alert.a2ui.json +64 -1
  14. package/components/alert/{class.js → alert.class.js} +189 -2
  15. package/components/alert/alert.css +78 -0
  16. package/components/alert/alert.d.ts +14 -0
  17. package/components/alert/alert.js +1 -1
  18. package/components/alert/alert.test.js +184 -0
  19. package/components/alert/alert.yaml +114 -1
  20. package/components/avatar/avatar-group.yaml +2 -2
  21. package/components/avatar/avatar.js +1 -1
  22. package/components/badge/badge.js +1 -1
  23. package/components/block/block.js +1 -1
  24. package/components/breadcrumb/breadcrumb.js +1 -1
  25. package/components/button/button.js +1 -1
  26. package/components/calendar-grid/calendar-grid.a2ui.json +10 -0
  27. package/components/calendar-grid/{class.js → calendar-grid.class.js} +30 -4
  28. package/components/calendar-grid/calendar-grid.css +20 -0
  29. package/components/calendar-grid/calendar-grid.d.ts +4 -0
  30. package/components/calendar-grid/calendar-grid.js +1 -1
  31. package/components/calendar-grid/calendar-grid.yaml +20 -0
  32. package/components/calendar-picker/calendar-picker.js +1 -1
  33. package/components/card/card.js +1 -1
  34. package/components/chart/chart.js +1 -1
  35. package/components/chart-legend/chart-legend.js +1 -1
  36. package/components/chat-thread/chat-input.a2ui.json +1 -1
  37. package/components/chat-thread/chat-input.js +6 -1
  38. package/components/chat-thread/chat-input.yaml +4 -1
  39. package/components/chat-thread/chat-thread.js +1 -1
  40. package/components/check/check.js +1 -1
  41. package/components/code/code.js +1 -1
  42. package/components/col/col.js +1 -1
  43. package/components/color-input/color-input.js +1 -1
  44. package/components/color-picker/color-picker.js +1 -1
  45. package/components/combobox/combobox.css +12 -0
  46. package/components/combobox/combobox.js +1 -1
  47. package/components/command/command.js +1 -1
  48. package/components/date-range-picker/{class.js → date-range-picker.class.js} +19 -3
  49. package/components/date-range-picker/date-range-picker.css +55 -6
  50. package/components/date-range-picker/date-range-picker.js +1 -1
  51. package/components/datetime-picker/{class.js → datetime-picker.class.js} +1 -1
  52. package/components/datetime-picker/datetime-picker.css +7 -1
  53. package/components/datetime-picker/datetime-picker.js +1 -1
  54. package/components/demo-toggle/demo-toggle.js +1 -1
  55. package/components/description-list/description-list.js +1 -1
  56. package/components/divider/divider.js +1 -1
  57. package/components/drawer/drawer.js +1 -1
  58. package/components/embed/embed.js +1 -1
  59. package/components/empty-state/empty-state.js +1 -1
  60. package/components/feed/feed.js +1 -1
  61. package/components/field/field.js +1 -1
  62. package/components/field/field.test.js +1 -1
  63. package/components/fields/fields.js +1 -1
  64. package/components/grid/grid.js +1 -1
  65. package/components/heatmap/heatmap.js +1 -1
  66. package/components/icon/icon.js +1 -1
  67. package/components/image/image.js +1 -1
  68. package/components/index.js +3 -0
  69. package/components/inline-message/inline-message.a2ui.json +143 -0
  70. package/components/inline-message/inline-message.class.js +169 -0
  71. package/components/inline-message/inline-message.css +75 -0
  72. package/components/inline-message/inline-message.d.ts +31 -0
  73. package/components/inline-message/inline-message.examples.md +19 -0
  74. package/components/inline-message/inline-message.js +17 -0
  75. package/components/inline-message/inline-message.test.js +203 -0
  76. package/components/inline-message/inline-message.yaml +205 -0
  77. package/components/input/input.css +16 -2
  78. package/components/input/input.js +1 -1
  79. package/components/input/input.test.js +40 -0
  80. package/components/input/input.yaml +5 -4
  81. package/components/inspector/inspector.js +1 -1
  82. package/components/integration-card/integration-card.js +1 -1
  83. package/components/kbd/kbd.js +1 -1
  84. package/components/link/link.js +1 -1
  85. package/components/list/list-item.yaml +2 -2
  86. package/components/list/list.js +1 -1
  87. package/components/list-window/list-window.js +1 -1
  88. package/components/loading-overlay/loading-overlay.a2ui.json +176 -0
  89. package/components/loading-overlay/loading-overlay.class.js +203 -0
  90. package/components/loading-overlay/loading-overlay.css +81 -0
  91. package/components/loading-overlay/loading-overlay.d.ts +24 -0
  92. package/components/loading-overlay/loading-overlay.examples.md +50 -0
  93. package/components/loading-overlay/loading-overlay.js +17 -0
  94. package/components/loading-overlay/loading-overlay.test.js +257 -0
  95. package/components/loading-overlay/loading-overlay.yaml +260 -0
  96. package/components/menu/menu-divider.yaml +1 -1
  97. package/components/menu/menu-item.yaml +1 -1
  98. package/components/menu/menu.a2ui.json +3 -0
  99. package/components/menu/menu.js +1 -1
  100. package/components/menu/menu.yaml +7 -0
  101. package/components/modal/{class.js → modal.class.js} +12 -1
  102. package/components/modal/modal.css +11 -1
  103. package/components/modal/modal.js +1 -1
  104. package/components/nav/nav.js +1 -1
  105. package/components/nav-group/nav-group.js +1 -1
  106. package/components/nav-item/nav-item.js +1 -1
  107. package/components/noodles/noodles.js +1 -1
  108. package/components/option-card/option-card.js +1 -1
  109. package/components/otp-input/otp-input.js +1 -1
  110. package/components/page/page.js +1 -1
  111. package/components/pagination/pagination.js +1 -1
  112. package/components/pane/pane.js +1 -1
  113. package/components/pipeline-status/pipeline-status.js +1 -1
  114. package/components/popover/popover.a2ui.json +8 -1
  115. package/components/popover/popover.js +1 -1
  116. package/components/popover/popover.yaml +14 -1
  117. package/components/progress/progress.js +1 -1
  118. package/components/progress-row/progress-row.js +1 -1
  119. package/components/radio/radio.js +1 -1
  120. package/components/range/range.js +1 -1
  121. package/components/rating/rating.js +1 -1
  122. package/components/richtext/richtext.js +1 -1
  123. package/components/row/row.js +1 -1
  124. package/components/search/{class.js → search.class.js} +2 -0
  125. package/components/search/search.js +1 -1
  126. package/components/segment/segment.js +1 -1
  127. package/components/segmented/segmented.js +1 -1
  128. package/components/select/select.a2ui.json +58 -4
  129. package/components/select/{class.js → select.class.js} +415 -6
  130. package/components/select/select.css +158 -0
  131. package/components/select/select.d.ts +31 -1
  132. package/components/select/select.js +1 -1
  133. package/components/select/select.test.js +202 -0
  134. package/components/select/select.yaml +126 -5
  135. package/components/skeleton/skeleton.js +1 -1
  136. package/components/slider/slider.js +1 -1
  137. package/components/spinner/spinner.a2ui.json +3 -2
  138. package/components/spinner/{class.js → spinner.class.js} +33 -3
  139. package/components/spinner/spinner.css +91 -35
  140. package/components/spinner/spinner.d.ts +2 -2
  141. package/components/spinner/spinner.js +1 -1
  142. package/components/spinner/spinner.test.js +49 -11
  143. package/components/spinner/spinner.yaml +9 -1
  144. package/components/stack/stack.js +1 -1
  145. package/components/step-progress/step-progress.js +1 -1
  146. package/components/stepper/stepper-item.yaml +1 -1
  147. package/components/stepper/stepper.js +1 -1
  148. package/components/stream/stream.js +1 -1
  149. package/components/swatch/swatch.js +1 -1
  150. package/components/swiper/swiper.js +1 -1
  151. package/components/switch/switch.js +1 -1
  152. package/components/table/table.css +1 -1
  153. package/components/table/table.js +1 -1
  154. package/components/table-toolbar/{class.js → table-toolbar.class.js} +2 -1
  155. package/components/table-toolbar/table-toolbar.js +1 -1
  156. package/components/tabs/tab.yaml +2 -2
  157. package/components/tabs/tabs.js +1 -1
  158. package/components/tag/tag.a2ui.json +9 -0
  159. package/components/tag/{class.js → tag.class.js} +8 -1
  160. package/components/tag/tag.css +84 -20
  161. package/components/tag/tag.js +1 -1
  162. package/components/tag/tag.test.js +75 -1
  163. package/components/tag/tag.yaml +14 -0
  164. package/components/tags-input/tags-input.a2ui.json +337 -0
  165. package/components/tags-input/tags-input.class.js +783 -0
  166. package/components/tags-input/tags-input.css +210 -0
  167. package/components/tags-input/tags-input.d.ts +120 -0
  168. package/components/tags-input/tags-input.examples.md +92 -0
  169. package/components/tags-input/tags-input.js +17 -0
  170. package/components/tags-input/tags-input.test.js +368 -0
  171. package/components/tags-input/tags-input.yaml +367 -0
  172. package/components/text/text.js +1 -1
  173. package/components/textarea/textarea.a2ui.json +1 -1
  174. package/components/textarea/textarea.css +10 -1
  175. package/components/textarea/textarea.js +1 -1
  176. package/components/textarea/textarea.yaml +11 -8
  177. package/components/time-picker/time-picker.js +1 -1
  178. package/components/timeline/timeline-item.yaml +2 -2
  179. package/components/timeline/{class.js → timeline.class.js} +1 -1
  180. package/components/timeline/timeline.js +1 -1
  181. package/components/toast/toast.js +1 -1
  182. package/components/toggle-group/toggle-group.js +1 -1
  183. package/components/toggle-group/toggle-option.yaml +1 -1
  184. package/components/toggle-scheme/toggle-scheme.js +1 -1
  185. package/components/toolbar/toolbar-group.yaml +1 -1
  186. package/components/toolbar/toolbar.js +1 -1
  187. package/components/tooltip/tooltip.js +1 -1
  188. package/components/tree/tree-item.yaml +1 -1
  189. package/components/tree/tree.js +1 -1
  190. package/components/upload/upload.js +1 -1
  191. package/core/provider.js +19 -2
  192. package/dist/web-components.min.css +1 -1
  193. package/dist/web-components.min.js +112 -90
  194. package/package.json +3 -3
  195. package/styles/components.css +3 -0
  196. /package/components/accordion/{class.js → accordion.class.js} +0 -0
  197. /package/components/action-list/{class.js → action-list.class.js} +0 -0
  198. /package/components/agent-feedback-bar/{class.js → agent-feedback-bar.class.js} +0 -0
  199. /package/components/agent-questions/{class.js → agent-questions.class.js} +0 -0
  200. /package/components/agent-reasoning/{class.js → agent-reasoning.class.js} +0 -0
  201. /package/components/agent-suggestions/{class.js → agent-suggestions.class.js} +0 -0
  202. /package/components/avatar/{class.js → avatar.class.js} +0 -0
  203. /package/components/badge/{class.js → badge.class.js} +0 -0
  204. /package/components/block/{class.js → block.class.js} +0 -0
  205. /package/components/breadcrumb/{class.js → breadcrumb.class.js} +0 -0
  206. /package/components/button/{class.js → button.class.js} +0 -0
  207. /package/components/calendar-picker/{class.js → calendar-picker.class.js} +0 -0
  208. /package/components/card/{class.js → card.class.js} +0 -0
  209. /package/components/chart/{class.js → chart.class.js} +0 -0
  210. /package/components/chart-legend/{class.js → chart-legend.class.js} +0 -0
  211. /package/components/chat-thread/{class.js → chat-thread.class.js} +0 -0
  212. /package/components/check/{class.js → check.class.js} +0 -0
  213. /package/components/code/{class.js → code.class.js} +0 -0
  214. /package/components/col/{class.js → col.class.js} +0 -0
  215. /package/components/color-input/{class.js → color-input.class.js} +0 -0
  216. /package/components/color-picker/{class.js → color-picker.class.js} +0 -0
  217. /package/components/combobox/{class.js → combobox.class.js} +0 -0
  218. /package/components/command/{class.js → command.class.js} +0 -0
  219. /package/components/demo-toggle/{class.js → demo-toggle.class.js} +0 -0
  220. /package/components/description-list/{class.js → description-list.class.js} +0 -0
  221. /package/components/divider/{class.js → divider.class.js} +0 -0
  222. /package/components/drawer/{class.js → drawer.class.js} +0 -0
  223. /package/components/embed/{class.js → embed.class.js} +0 -0
  224. /package/components/empty-state/{class.js → empty-state.class.js} +0 -0
  225. /package/components/feed/{class.js → feed.class.js} +0 -0
  226. /package/components/field/{class.js → field.class.js} +0 -0
  227. /package/components/fields/{class.js → fields.class.js} +0 -0
  228. /package/components/grid/{class.js → grid.class.js} +0 -0
  229. /package/components/heatmap/{class.js → heatmap.class.js} +0 -0
  230. /package/components/icon/{class.js → icon.class.js} +0 -0
  231. /package/components/image/{class.js → image.class.js} +0 -0
  232. /package/components/input/{class.js → input.class.js} +0 -0
  233. /package/components/inspector/{class.js → inspector.class.js} +0 -0
  234. /package/components/integration-card/{class.js → integration-card.class.js} +0 -0
  235. /package/components/kbd/{class.js → kbd.class.js} +0 -0
  236. /package/components/link/{class.js → link.class.js} +0 -0
  237. /package/components/list/{class.js → list.class.js} +0 -0
  238. /package/components/list-window/{class.js → list-window.class.js} +0 -0
  239. /package/components/menu/{class.js → menu.class.js} +0 -0
  240. /package/components/nav/{class.js → nav.class.js} +0 -0
  241. /package/components/nav-group/{class.js → nav-group.class.js} +0 -0
  242. /package/components/nav-item/{class.js → nav-item.class.js} +0 -0
  243. /package/components/noodles/{class.js → noodles.class.js} +0 -0
  244. /package/components/option-card/{class.js → option-card.class.js} +0 -0
  245. /package/components/otp-input/{class.js → otp-input.class.js} +0 -0
  246. /package/components/page/{class.js → page.class.js} +0 -0
  247. /package/components/pagination/{class.js → pagination.class.js} +0 -0
  248. /package/components/pane/{class.js → pane.class.js} +0 -0
  249. /package/components/pipeline-status/{class.js → pipeline-status.class.js} +0 -0
  250. /package/components/popover/{class.js → popover.class.js} +0 -0
  251. /package/components/progress/{class.js → progress.class.js} +0 -0
  252. /package/components/progress-row/{class.js → progress-row.class.js} +0 -0
  253. /package/components/radio/{class.js → radio.class.js} +0 -0
  254. /package/components/range/{class.js → range.class.js} +0 -0
  255. /package/components/rating/{class.js → rating.class.js} +0 -0
  256. /package/components/richtext/{class.js → richtext.class.js} +0 -0
  257. /package/components/row/{class.js → row.class.js} +0 -0
  258. /package/components/segment/{class.js → segment.class.js} +0 -0
  259. /package/components/segmented/{class.js → segmented.class.js} +0 -0
  260. /package/components/skeleton/{class.js → skeleton.class.js} +0 -0
  261. /package/components/slider/{class.js → slider.class.js} +0 -0
  262. /package/components/stack/{class.js → stack.class.js} +0 -0
  263. /package/components/step-progress/{class.js → step-progress.class.js} +0 -0
  264. /package/components/stepper/{class.js → stepper.class.js} +0 -0
  265. /package/components/stream/{class.js → stream.class.js} +0 -0
  266. /package/components/swatch/{class.js → swatch.class.js} +0 -0
  267. /package/components/swiper/{class.js → swiper.class.js} +0 -0
  268. /package/components/switch/{class.js → switch.class.js} +0 -0
  269. /package/components/table/{class.js → table.class.js} +0 -0
  270. /package/components/tabs/{class.js → tabs.class.js} +0 -0
  271. /package/components/text/{class.js → text.class.js} +0 -0
  272. /package/components/textarea/{class.js → textarea.class.js} +0 -0
  273. /package/components/time-picker/{class.js → time-picker.class.js} +0 -0
  274. /package/components/toast/{class.js → toast.class.js} +0 -0
  275. /package/components/toggle-group/{class.js → toggle-group.class.js} +0 -0
  276. /package/components/toggle-scheme/{class.js → toggle-scheme.class.js} +0 -0
  277. /package/components/toolbar/{class.js → toolbar.class.js} +0 -0
  278. /package/components/tooltip/{class.js → tooltip.class.js} +0 -0
  279. /package/components/tree/{class.js → tree.class.js} +0 -0
  280. /package/components/upload/{class.js → upload.class.js} +0 -0
@@ -182,6 +182,74 @@
182
182
  :scope[variant="ghost"] > [slot="trigger"]:hover > [slot="caret"] {
183
183
  color: var(--select-fg-subtle, var(--select-fg-subtle-default));
184
184
  }
185
+
186
+ /* ── SPEC-040 — Multi-select chip trigger ──
187
+ [data-multi-chips] is set on the host by render() when [multiple] is
188
+ active. Layout switches from inline-text trigger to wrap-flex chip row.
189
+ Tag-ui chips inside [data-chips] inherit the host's tag-ui token set;
190
+ the +more pill is a CSS-only button styled to match. */
191
+ :scope[data-multi-chips] > [slot="trigger"] {
192
+ flex-wrap: wrap;
193
+ align-items: center;
194
+ gap: var(--a-space-1);
195
+ /* min-height tracks single-row height when empty; flex-wrap allows
196
+ it to grow as chips overflow. */
197
+ padding-block: calc(var(--select-py, var(--a-space-1)));
198
+ }
199
+ :scope[data-multi-chips] [data-chips] {
200
+ display: contents; /* let chips participate in the trigger's flex line */
201
+ }
202
+ :scope[data-multi-chips] [data-chips] tag-ui {
203
+ /* Chip styling tracks the active variant via tag-ui's own tokens. */
204
+ --tag-size-default: var(--a-ui-tiny);
205
+ }
206
+ :scope[data-multi-chips] [data-chips] [data-more] {
207
+ appearance: none;
208
+ border: 1px solid var(--select-border, var(--select-border-default));
209
+ background: var(--select-bg-hover, var(--select-bg-hover-default));
210
+ color: var(--select-fg-muted, var(--select-fg-muted-default));
211
+ border-radius: var(--a-radius-full);
212
+ padding: 0 var(--a-space-2);
213
+ font: inherit;
214
+ font-size: var(--a-ui-tiny);
215
+ line-height: calc(var(--a-size) - var(--a-space-3));
216
+ cursor: pointer;
217
+ }
218
+ :scope[data-multi-chips] [data-chips] [data-more]:hover {
219
+ background: var(--a-bg-hover);
220
+ }
221
+
222
+ /* Clear-all `x` affordance. Visible only when [clearable] AND chips. */
223
+ :scope[data-multi-chips] [data-clear-all] {
224
+ appearance: none;
225
+ background: transparent;
226
+ border: none;
227
+ padding: var(--a-space-0);
228
+ margin-inline-start: var(--a-space-1);
229
+ color: var(--select-fg-muted, var(--select-fg-muted-default));
230
+ cursor: pointer;
231
+ border-radius: var(--a-radius-full);
232
+ --a-icon-size: var(--a-ui-sm);
233
+ display: inline-flex;
234
+ align-items: center;
235
+ }
236
+ :scope[data-multi-chips] [data-clear-all][hidden] { display: none; }
237
+ :scope[data-multi-chips] [data-clear-all]:hover {
238
+ color: var(--select-fg, var(--select-fg-default));
239
+ background: var(--a-bg-hover);
240
+ }
241
+
242
+ /* In multi-select with chips present, the trigger display element is
243
+ the rest-of-row slot — make it flex-grow but not push chips off. */
244
+ :scope[data-multi-chips] [slot="display"] {
245
+ flex: 1;
246
+ min-width: 4ch;
247
+ }
248
+ /* Hide the display when empty in multi-mode (chips fill the slot). */
249
+ :scope[data-multi-chips] [slot="display"]:empty {
250
+ flex: 0 0 0;
251
+ padding: 0;
252
+ }
185
253
  }
186
254
 
187
255
  /* ── Top-layer: cannot inherit component tokens ── */
@@ -320,3 +388,93 @@ select-ui > [slot="hint"] {
320
388
  color: var(--select-hint-fg, var(--select-fg-muted, var(--select-fg-muted-default)));
321
389
  line-height: var(--select-hint-lh, 1.4);
322
390
  }
391
+
392
+ /* ── SPEC-040 — Multi-select popover affordances ──
393
+ Top-layer: popover lives outside @scope so reference --a-* directly. */
394
+
395
+ /* Select-all / Clear control row above the option list. */
396
+ select-ui [slot="listbox"] [data-select-all] {
397
+ display: flex;
398
+ align-items: center;
399
+ justify-content: flex-end;
400
+ padding: var(--a-space-1);
401
+ border-bottom: 1px solid var(--a-border-subtle);
402
+ margin-bottom: var(--a-space-1);
403
+ }
404
+ select-ui [slot="listbox"] [data-select-all-btn] {
405
+ appearance: none;
406
+ background: transparent;
407
+ border: none;
408
+ font: inherit;
409
+ font-size: var(--a-ui-xs);
410
+ color: var(--a-accent);
411
+ cursor: pointer;
412
+ padding: var(--a-space-1) var(--a-space-2);
413
+ border-radius: var(--a-radius-sm);
414
+ }
415
+ select-ui [slot="listbox"] [data-select-all-btn]:hover {
416
+ background: var(--a-bg-hover);
417
+ }
418
+
419
+ /* Multi-select option rows — leading checkbox indicator + label.
420
+ The check icon is hidden by default and revealed at aria-selected. */
421
+ select-ui [role="option"][data-multi-option] {
422
+ display: flex;
423
+ align-items: center;
424
+ gap: var(--a-space-2);
425
+ }
426
+ select-ui [role="option"][data-multi-option] [data-checkbox] {
427
+ flex-shrink: 0;
428
+ display: inline-flex;
429
+ align-items: center;
430
+ justify-content: center;
431
+ width: 1rem;
432
+ height: 1rem;
433
+ border: 1px solid var(--a-ui-border);
434
+ border-radius: var(--a-radius-sm);
435
+ background: var(--a-canvas-bright);
436
+ margin-inline-end: 0; /* row gap already provides spacing */
437
+ transition:
438
+ background var(--a-duration-fast) var(--a-easing),
439
+ border-color var(--a-duration-fast) var(--a-easing);
440
+ }
441
+ /* Hide the check icon when unselected — overrides the option-row icon-ui
442
+ rule above. */
443
+ select-ui [role="option"][data-multi-option] [data-checkbox] icon-ui {
444
+ --a-icon-size: 0.75rem;
445
+ color: transparent;
446
+ margin: 0;
447
+ vertical-align: 0;
448
+ transition: color var(--a-duration-fast) var(--a-easing);
449
+ }
450
+ select-ui [role="option"][data-multi-option][aria-selected="true"] [data-checkbox] {
451
+ background: var(--a-accent);
452
+ border-color: var(--a-accent);
453
+ }
454
+ select-ui [role="option"][data-multi-option][aria-selected="true"] [data-checkbox] icon-ui {
455
+ color: var(--a-chrome-light);
456
+ }
457
+ select-ui [role="option"][data-multi-option] [data-option-label] {
458
+ flex: 1;
459
+ min-width: 0;
460
+ }
461
+ /* Multi-option selected state — override the single-select "bold +
462
+ accent bg" treatment so the row stays light + the checkbox carries
463
+ the selected signal. */
464
+ select-ui [role="option"][data-multi-option][aria-selected="true"] {
465
+ background: transparent;
466
+ font-weight: inherit;
467
+ color: var(--select-fg, var(--select-fg-default));
468
+ }
469
+ select-ui [role="option"][data-multi-option][aria-selected="true"]:hover,
470
+ select-ui [role="option"][data-multi-option][aria-selected="true"][data-focused] {
471
+ background: var(--select-option-bg-hover, var(--select-option-bg-hover-default));
472
+ }
473
+
474
+ /* Empty state when there are no options. */
475
+ select-ui [slot="listbox"] [data-empty] {
476
+ padding: var(--a-space-3) var(--a-ui-px);
477
+ color: var(--a-fg-muted);
478
+ text-align: center;
479
+ font-size: var(--a-ui-xs);
480
+ }
@@ -17,7 +17,12 @@ export interface SelectOption {
17
17
  }
18
18
 
19
19
  export interface SelectChangeEventDetail<V extends string = string> {
20
- value: V;
20
+ /** Single-select: the new value. Multi-select: array of selected ids. */
21
+ value: V | V[];
22
+ /** Multi-select only: ids added in this change. */
23
+ added?: V[];
24
+ /** Multi-select only: ids removed in this change. */
25
+ removed?: V[];
21
26
  }
22
27
  export type SelectChangeEvent<V extends string = string> = CustomEvent<SelectChangeEventDetail<V>>;
23
28
 
@@ -26,6 +31,13 @@ export interface SelectActionEventDetail {
26
31
  }
27
32
  export type SelectActionEvent = CustomEvent<SelectActionEventDetail>;
28
33
 
34
+ /** SPEC-040 — fired when [multiple] toggle suppressed by min/max constraints. */
35
+ export interface SelectInvalidEventDetail {
36
+ value: string[];
37
+ reason: 'min' | 'max';
38
+ }
39
+ export type SelectInvalidEvent = CustomEvent<SelectInvalidEventDetail>;
40
+
29
41
  export class UISelect extends UIFormElement {
30
42
  placeholder: string;
31
43
  /** Open/closed reflected attribute — toggled by trigger click / keyboard. */
@@ -40,6 +52,16 @@ export class UISelect extends UIFormElement {
40
52
  /** Allow values not in the option list (combobox mode). */
41
53
  freeText: boolean;
42
54
  divider: boolean;
55
+ /** SPEC-040 — cap visible chips in multi-select trigger. 0 = unlimited. */
56
+ maxChips: number;
57
+ /** SPEC-040 — minimum required selections in multi-select mode. */
58
+ min: number;
59
+ /** SPEC-040 — maximum allowed selections in multi-select mode. 0 = unlimited. */
60
+ max: number;
61
+ /** SPEC-040 — render Select-all / Clear control above the option list. */
62
+ selectAll: boolean;
63
+ /** SPEC-040 — render a clear-all `x` affordance in the trigger. */
64
+ clearable: boolean;
43
65
  /** §207 (v0.5.7): hint text below the field, wired to aria-describedby. */
44
66
  hint: string;
45
67
  /**
@@ -63,6 +85,13 @@ export class UISelect extends UIFormElement {
63
85
  */
64
86
  options: readonly SelectOption[];
65
87
 
88
+ /** SPEC-040 — toggle one option's selection in multi-select mode. */
89
+ toggle(id: string): void;
90
+ /** SPEC-040 — select every non-disabled option in multi-select mode. */
91
+ selectAllOptions(): void;
92
+ /** SPEC-040 — clear all selections. */
93
+ clear(): void;
94
+
66
95
  addEventListener<K extends keyof HTMLElementEventMap>(
67
96
  type: K,
68
97
  listener: (this: UISelect, ev: HTMLElementEventMap[K]) => unknown,
@@ -70,4 +99,5 @@ export class UISelect extends UIFormElement {
70
99
  ): void;
71
100
  addEventListener(type: 'change', listener: (ev: SelectChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
72
101
  addEventListener(type: 'action', listener: (ev: SelectActionEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
102
+ addEventListener(type: 'invalid', listener: (ev: SelectInvalidEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
73
103
  }
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UISelect } from './class.js';
13
+ import { UISelect } from './select.class.js';
14
14
 
15
15
  defineIfFree('select-ui', UISelect);
16
16
 
@@ -86,4 +86,206 @@ describe('select-ui', () => {
86
86
 
87
87
  expect(selected).toEqual(['b']);
88
88
  });
89
+
90
+ // ── SPEC-040 (multi-select) ──
91
+
92
+ it('multi-select: [multiple] sets data-multi-chips and renders tag-ui chips per selected value', async () => {
93
+ const s = mount(`
94
+ <select-ui multiple value="a,c">
95
+ <option value="a">Alpha</option>
96
+ <option value="b">Beta</option>
97
+ <option value="c">Gamma</option>
98
+ </select-ui>
99
+ `);
100
+ await tick();
101
+ expect(s.hasAttribute('data-multi-chips')).toBe(true);
102
+ const chips = s.querySelectorAll('[slot="trigger"] [data-chips] tag-ui');
103
+ expect(chips.length).toBe(2);
104
+ const chipValues = Array.from(chips).map((c) => c.dataset.chipValue);
105
+ expect(chipValues).toContain('a');
106
+ expect(chipValues).toContain('c');
107
+ });
108
+
109
+ it('multi-select: aria-multiselectable=true is set on the listbox', async () => {
110
+ const s = mount(`
111
+ <select-ui multiple open>
112
+ <option value="a">Alpha</option>
113
+ <option value="b">Beta</option>
114
+ </select-ui>
115
+ `);
116
+ await tick();
117
+ const lb = s.querySelector('[slot="listbox"]');
118
+ expect(lb).toBeTruthy();
119
+ expect(lb.getAttribute('aria-multiselectable')).toBe('true');
120
+ });
121
+
122
+ it('multi-select: clicking an option toggles selection without closing the popover', async () => {
123
+ const s = mount(`
124
+ <select-ui multiple open>
125
+ <option value="a">Alpha</option>
126
+ <option value="b">Beta</option>
127
+ <option value="c">Gamma</option>
128
+ </select-ui>
129
+ `);
130
+ await tick();
131
+ const opts = s.querySelectorAll('[role="option"]');
132
+ const optA = Array.from(opts).find((o) => o.getAttribute('data-value') === 'a');
133
+ optA.click();
134
+ await tick();
135
+ expect(s.open).toBe(true); // still open
136
+ expect(s.value).toBe('a');
137
+
138
+ const optC = Array.from(s.querySelectorAll('[role="option"]')).find((o) => o.getAttribute('data-value') === 'c');
139
+ optC.click();
140
+ await tick();
141
+ expect(s.open).toBe(true);
142
+ const parts = new Set(s.value.split(','));
143
+ expect(parts.has('a')).toBe(true);
144
+ expect(parts.has('c')).toBe(true);
145
+ });
146
+
147
+ it('multi-select: Backspace from the trigger removes the last chip', async () => {
148
+ const s = mount(`
149
+ <select-ui multiple value="a,b">
150
+ <option value="a">Alpha</option>
151
+ <option value="b">Beta</option>
152
+ </select-ui>
153
+ `);
154
+ await tick();
155
+ const ev = new KeyboardEvent('keydown', { key: 'Backspace', bubbles: true });
156
+ s.dispatchEvent(ev);
157
+ await tick();
158
+ expect(s.value).toBe('a');
159
+ });
160
+
161
+ it('multi-select: toggle(id) is a public API', async () => {
162
+ const s = mount(`
163
+ <select-ui multiple>
164
+ <option value="a">Alpha</option>
165
+ <option value="b">Beta</option>
166
+ </select-ui>
167
+ `);
168
+ await tick();
169
+ s.toggle('a');
170
+ await tick();
171
+ expect(s.value).toBe('a');
172
+ s.toggle('a');
173
+ await tick();
174
+ expect(s.value).toBe('');
175
+ });
176
+
177
+ it('multi-select: [max] suppresses further selection and fires invalid', async () => {
178
+ const s = mount(`
179
+ <select-ui multiple max="2" value="a,b">
180
+ <option value="a">Alpha</option>
181
+ <option value="b">Beta</option>
182
+ <option value="c">Gamma</option>
183
+ </select-ui>
184
+ `);
185
+ await tick();
186
+ let invalidFired = null;
187
+ s.addEventListener('invalid', (e) => { invalidFired = e.detail; });
188
+ s.toggle('c');
189
+ await tick();
190
+ expect(invalidFired).toBeTruthy();
191
+ expect(invalidFired.reason).toBe('max');
192
+ expect(s.value).toBe('a,b'); // unchanged
193
+ });
194
+
195
+ it('multi-select: clear() empties value and fires change with removed array', async () => {
196
+ const s = mount(`
197
+ <select-ui multiple value="a,b">
198
+ <option value="a">Alpha</option>
199
+ <option value="b">Beta</option>
200
+ </select-ui>
201
+ `);
202
+ await tick();
203
+ let changed = null;
204
+ s.addEventListener('change', (e) => { changed = e.detail; });
205
+ s.clear();
206
+ await tick();
207
+ expect(s.value).toBe('');
208
+ expect(changed.removed).toEqual(['a', 'b']);
209
+ });
210
+
211
+ it('multi-select: selectAllOptions() selects every non-disabled option', async () => {
212
+ const s = mount(`
213
+ <select-ui multiple>
214
+ <option value="a">Alpha</option>
215
+ <option value="b">Beta</option>
216
+ <option value="c" disabled>Gamma</option>
217
+ </select-ui>
218
+ `);
219
+ await tick();
220
+ s.selectAllOptions();
221
+ await tick();
222
+ const parts = new Set(s.value.split(','));
223
+ expect(parts.has('a')).toBe(true);
224
+ expect(parts.has('b')).toBe(true);
225
+ expect(parts.has('c')).toBe(false); // disabled
226
+ });
227
+
228
+ it('multi-select: [max-chips] shows "+N more" pill when exceeded', async () => {
229
+ const s = mount(`
230
+ <select-ui multiple max-chips="2" value="a,b,c,d">
231
+ <option value="a">Alpha</option>
232
+ <option value="b">Beta</option>
233
+ <option value="c">Gamma</option>
234
+ <option value="d">Delta</option>
235
+ </select-ui>
236
+ `);
237
+ await tick();
238
+ const chips = s.querySelectorAll('[slot="trigger"] [data-chips] tag-ui');
239
+ expect(chips.length).toBe(2);
240
+ const more = s.querySelector('[slot="trigger"] [data-more]');
241
+ expect(more).toBeTruthy();
242
+ expect(more.textContent).toBe('+2 more');
243
+ });
244
+
245
+ it('multi-select: [clearable] reveals the clear-all button when chips present', async () => {
246
+ const s = mount(`
247
+ <select-ui multiple clearable value="a">
248
+ <option value="a">Alpha</option>
249
+ <option value="b">Beta</option>
250
+ </select-ui>
251
+ `);
252
+ await tick();
253
+ const clearBtn = s.querySelector('[data-clear-all]');
254
+ expect(clearBtn).toBeTruthy();
255
+ expect(clearBtn.hasAttribute('hidden')).toBe(false);
256
+ });
257
+
258
+ it('multi-select: [select-all] renders the bulk-action header', async () => {
259
+ const s = mount(`
260
+ <select-ui multiple select-all open>
261
+ <option value="a">Alpha</option>
262
+ <option value="b">Beta</option>
263
+ </select-ui>
264
+ `);
265
+ await tick();
266
+ const header = s.querySelector('[slot="listbox"] [data-select-all]');
267
+ expect(header).toBeTruthy();
268
+ const btn = header.querySelector('[data-select-all-btn]');
269
+ expect(btn).toBeTruthy();
270
+ expect(btn.textContent).toBe('Select all');
271
+ });
272
+
273
+ it('multi-select: chip remove event empties that selection from value', async () => {
274
+ const s = mount(`
275
+ <select-ui multiple value="a,b">
276
+ <option value="a">Alpha</option>
277
+ <option value="b">Beta</option>
278
+ </select-ui>
279
+ `);
280
+ await tick();
281
+ const chipA = s.querySelector('tag-ui[data-chip-value="a"]');
282
+ expect(chipA).toBeTruthy();
283
+ // Simulate tag-ui's remove event (bubbles, with detail.text)
284
+ chipA.dispatchEvent(new CustomEvent('remove', {
285
+ bubbles: true,
286
+ detail: { text: 'Alpha', value: 'Alpha' },
287
+ }));
288
+ await tick();
289
+ expect(s.value).toBe('b');
290
+ });
89
291
  });
@@ -8,17 +8,23 @@ component: Select
8
8
  category: input
9
9
  version: 1
10
10
  description: |
11
- Single-select dropdown primitive — the canonical AdiaUI select
12
- control. Form-bearing via UIFormElement: [name], [value],
11
+ Single- and multi-select dropdown primitive — the canonical AdiaUI
12
+ select control. Form-bearing via UIFormElement: [name], [value],
13
13
  [required], [disabled], fires `change`. Options via native
14
14
  <option> / <optgroup> children, programmatic `.options` array, or
15
15
  JSON [data-options] (hydrated by <editor-shell>'s wireSelects).
16
16
  Use for single-select with > 4 options; for ≤ 4 options use
17
- <segmented-ui> or <radio-ui>. Multi-select via [multiple searchable].
17
+ <segmented-ui> or <radio-ui>. Multi-select via [multiple]: trigger
18
+ renders <tag-ui> chips per selection, popover shows checkbox-style
19
+ option list, Backspace removes the last chip, [select-all] adds
20
+ bulk controls, [max-chips] caps the visible chip count with a
21
+ "+N more" pill. Form value stays a comma-separated string under
22
+ [name] for native <form> compatibility (parse with .split(',')).
18
23
  # Per ADR-0027 — primitives that programmatically create other primitives
19
24
  # do NOT auto-import them. Consumer (or demo shell) must explicitly import.
20
25
  composes:
21
- - icon-ui # chevron + option-row affixes (created in render)
26
+ - icon-ui # chevron + option-row affixes (created in render)
27
+ - tag-ui # multi-select chip per selected option in the trigger
22
28
  props:
23
29
  name:
24
30
  description: Form control name for form data submission
@@ -89,9 +95,56 @@ props:
89
95
  type: number
90
96
  default: null
91
97
  multiple:
92
- description: Enables multi-select (comma-separated values)
98
+ description: |
99
+ Enables multi-select. Trigger renders <tag-ui> chips per selection
100
+ (not comma-separated text); popover renders checkbox-style option
101
+ rows where clicks toggle without closing; Backspace from the
102
+ trigger removes the last chip. Form value remains comma-separated
103
+ under [name] for native <form> compatibility.
93
104
  type: boolean
94
105
  default: false
106
+ reflect: true
107
+ max-chips:
108
+ description: |
109
+ Multi-select only. Caps the number of chips visible in the
110
+ trigger. When `value.length > max-chips`, the first N chips
111
+ render plus a "+M more" pill that opens the popover on click.
112
+ `0` (default) = unlimited.
113
+ type: number
114
+ default: 0
115
+ reflect: true
116
+ min:
117
+ description: |
118
+ Multi-select only. Minimum required selections. Below this floor
119
+ the host's form validity goes invalid (valueMissing) and the
120
+ `invalid` event fires.
121
+ type: number
122
+ default: 0
123
+ reflect: true
124
+ max:
125
+ description: |
126
+ Multi-select only. Maximum allowed selections. Toggling past the
127
+ cap is suppressed; the `invalid` event fires with reason="max".
128
+ `0` (default) = unlimited.
129
+ type: number
130
+ default: 0
131
+ reflect: true
132
+ select-all:
133
+ description: |
134
+ Multi-select only. Renders a "Select all" / "Clear" control row
135
+ above the option list. Click selects every non-disabled option;
136
+ when already all selected, click clears.
137
+ type: boolean
138
+ default: false
139
+ reflect: true
140
+ clearable:
141
+ description: |
142
+ Multi-select only. Adds a clear-all `x` affordance to the trigger
143
+ when at least one chip is present. Click empties value and fires
144
+ `change`.
145
+ type: boolean
146
+ default: false
147
+ reflect: true
95
148
  open:
96
149
  description: Whether the listbox is open
97
150
  type: boolean
@@ -128,6 +181,10 @@ props:
128
181
  events:
129
182
  change:
130
183
  description: Fired when selected value changes
184
+ invalid:
185
+ description: |
186
+ Multi-select only. Fired when a toggle is suppressed by [min] or
187
+ [max] constraints. detail = { value, reason: 'min' | 'max' }.
131
188
  usage: |-
132
189
  §225 (v0.5.9) — Consumer-authored children must be native `<option>` or
133
190
  `<optgroup>`. The `#parseOptions()` walker reads only those two tag names;
@@ -173,6 +230,9 @@ traits: []
173
230
  tokens: {}
174
231
  requiredIcons:
175
232
  - caret-up-down
233
+ - check # checkbox indicator on each option in [multiple] mode
234
+ - x # chip dismiss + clear-all affordance ([multiple] + [clearable])
235
+ - magnifying-glass # search-input prefix ([searchable])
176
236
  a2ui:
177
237
  rules:
178
238
  - >-
@@ -200,6 +260,18 @@ a2ui:
200
260
  unmatched values are valid (tag entry, email-with-suggestion).
201
261
  Use [multiple searchable] for multi-select rather than
202
262
  authoring a separate multi-select primitive.
263
+ - >-
264
+ For multi-select, set [multiple] — the trigger automatically
265
+ renders <tag-ui> chips per selected option; the popover renders
266
+ checkbox-style option rows where clicks toggle membership
267
+ without closing. Form value is comma-separated under [name].
268
+ There is NO `<multi-select-ui>` tag — that name does not exist.
269
+ - >-
270
+ Multi-select bulk controls: [select-all] renders a "Select all" /
271
+ "Clear" control above the option list; [clearable] adds a
272
+ clear-all `x` affordance to the trigger. [max-chips] caps the
273
+ visible chip count and renders "+N more". [min] / [max] gate
274
+ form validity.
203
275
  anti_patterns: []
204
276
  examples:
205
277
  - name: bleed-design-settings
@@ -341,6 +413,37 @@ examples:
341
413
  "variant": "primary"
342
414
  }
343
415
  ]
416
+ - name: multi-select-tags
417
+ description: Multi-select with chip trigger + checkbox option list (SPEC-040).
418
+ a2ui: >-
419
+ [
420
+ {
421
+ "id": "root",
422
+ "component": "Card",
423
+ "children": ["sec"]
424
+ },
425
+ {
426
+ "id": "sec",
427
+ "component": "Section",
428
+ "children": ["col"]
429
+ },
430
+ {
431
+ "id": "col",
432
+ "component": "Column",
433
+ "children": ["tags"],
434
+ "gap": "3"
435
+ },
436
+ {
437
+ "id": "tags",
438
+ "component": "Select",
439
+ "multiple": true,
440
+ "selectAll": true,
441
+ "label": "Tags",
442
+ "name": "tags",
443
+ "placeholder": "Pick tags...",
444
+ "value": "urgent,backend"
445
+ }
446
+ ]
344
447
  - name: create-form
345
448
  description: Simple account creation form with text fields, select, and action buttons.
346
449
  a2ui: >-
@@ -443,6 +546,12 @@ keywords:
443
546
  - dropdown
444
547
  - combobox
445
548
  - autocomplete
549
+ - multi-select
550
+ - multiple
551
+ - chips
552
+ - tokens
553
+ - tags
554
+ - picker
446
555
  synonyms:
447
556
  autocomplete:
448
557
  - combobox
@@ -461,6 +570,18 @@ synonyms:
461
570
  - menu
462
571
  - select
463
572
  - form
573
+ multi-select:
574
+ - multiselect
575
+ - multi-pick
576
+ - chips
577
+ - tokens
578
+ - tags
579
+ - multiple
580
+ multiple:
581
+ - multi-select
582
+ - multiselect
583
+ - chips
584
+ - select-many
464
585
  related:
465
586
  - image
466
587
  - button
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UISkeleton } from './class.js';
13
+ import { UISkeleton } from './skeleton.class.js';
14
14
 
15
15
  defineIfFree('skeleton-ui', UISkeleton);
16
16
 
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UISlider } from './class.js';
13
+ import { UISlider } from './slider.class.js';
14
14
 
15
15
  defineIfFree('slider-ui', UISlider);
16
16
 
@@ -48,12 +48,13 @@
48
48
  "default": "current"
49
49
  },
50
50
  "variant": {
51
- "description": "Visual flavor — arc (rotating quarter-circle), ring (full ring with one colored segment), dots (three bouncing dots).",
51
+ "description": "Visual flavor — `arc` (rotating quarter-circle, the default glyph spinner), `ring` (full ring with one colored segment rotating around it), `dots` (three bouncing dots — animated in a left-to-right wave), `knight` (horizontal \"knight-rider\" bar — a sliding thumb that bounces back-and-forth across a track; widest variant, reads as a determinate-looking bar but is indeterminate by intent).",
52
52
  "type": "string",
53
53
  "enum": [
54
54
  "arc",
55
55
  "ring",
56
- "dots"
56
+ "dots",
57
+ "knight"
57
58
  ],
58
59
  "default": "arc"
59
60
  }