@adia-ai/web-components 0.6.34 → 0.6.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/CHANGELOG.md +42 -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.js +1 -1
  46. package/components/command/command.js +1 -1
  47. package/components/date-range-picker/{class.js → date-range-picker.class.js} +18 -2
  48. package/components/date-range-picker/date-range-picker.css +51 -5
  49. package/components/date-range-picker/date-range-picker.js +1 -1
  50. package/components/datetime-picker/{class.js → datetime-picker.class.js} +1 -1
  51. package/components/datetime-picker/datetime-picker.js +1 -1
  52. package/components/demo-toggle/demo-toggle.js +1 -1
  53. package/components/description-list/description-list.js +1 -1
  54. package/components/divider/divider.js +1 -1
  55. package/components/drawer/drawer.js +1 -1
  56. package/components/embed/embed.js +1 -1
  57. package/components/empty-state/empty-state.js +1 -1
  58. package/components/feed/feed.js +1 -1
  59. package/components/field/field.js +1 -1
  60. package/components/field/field.test.js +1 -1
  61. package/components/fields/fields.js +1 -1
  62. package/components/grid/grid.js +1 -1
  63. package/components/heatmap/heatmap.js +1 -1
  64. package/components/icon/icon.js +1 -1
  65. package/components/image/image.js +1 -1
  66. package/components/index.js +3 -0
  67. package/components/inline-message/inline-message.a2ui.json +143 -0
  68. package/components/inline-message/inline-message.class.js +169 -0
  69. package/components/inline-message/inline-message.css +75 -0
  70. package/components/inline-message/inline-message.d.ts +31 -0
  71. package/components/inline-message/inline-message.examples.md +19 -0
  72. package/components/inline-message/inline-message.js +17 -0
  73. package/components/inline-message/inline-message.test.js +203 -0
  74. package/components/inline-message/inline-message.yaml +205 -0
  75. package/components/input/input.css +1 -1
  76. package/components/input/input.js +1 -1
  77. package/components/input/input.yaml +5 -4
  78. package/components/inspector/inspector.js +1 -1
  79. package/components/integration-card/integration-card.js +1 -1
  80. package/components/kbd/kbd.js +1 -1
  81. package/components/link/link.js +1 -1
  82. package/components/list/list-item.yaml +2 -2
  83. package/components/list/list.js +1 -1
  84. package/components/list-window/list-window.js +1 -1
  85. package/components/loading-overlay/loading-overlay.a2ui.json +176 -0
  86. package/components/loading-overlay/loading-overlay.class.js +203 -0
  87. package/components/loading-overlay/loading-overlay.css +81 -0
  88. package/components/loading-overlay/loading-overlay.d.ts +24 -0
  89. package/components/loading-overlay/loading-overlay.examples.md +50 -0
  90. package/components/loading-overlay/loading-overlay.js +17 -0
  91. package/components/loading-overlay/loading-overlay.test.js +257 -0
  92. package/components/loading-overlay/loading-overlay.yaml +260 -0
  93. package/components/menu/menu-divider.yaml +1 -1
  94. package/components/menu/menu-item.yaml +1 -1
  95. package/components/menu/menu.a2ui.json +3 -0
  96. package/components/menu/menu.js +1 -1
  97. package/components/menu/menu.yaml +7 -0
  98. package/components/modal/{class.js → modal.class.js} +12 -1
  99. package/components/modal/modal.css +11 -1
  100. package/components/modal/modal.js +1 -1
  101. package/components/nav/nav.js +1 -1
  102. package/components/nav-group/nav-group.js +1 -1
  103. package/components/nav-item/nav-item.js +1 -1
  104. package/components/noodles/noodles.js +1 -1
  105. package/components/option-card/option-card.js +1 -1
  106. package/components/otp-input/otp-input.js +1 -1
  107. package/components/page/page.js +1 -1
  108. package/components/pagination/pagination.js +1 -1
  109. package/components/pane/pane.js +1 -1
  110. package/components/pipeline-status/pipeline-status.js +1 -1
  111. package/components/popover/popover.a2ui.json +8 -1
  112. package/components/popover/popover.js +1 -1
  113. package/components/popover/popover.yaml +14 -1
  114. package/components/progress/progress.js +1 -1
  115. package/components/progress-row/progress-row.js +1 -1
  116. package/components/radio/radio.js +1 -1
  117. package/components/range/range.js +1 -1
  118. package/components/rating/rating.js +1 -1
  119. package/components/richtext/richtext.js +1 -1
  120. package/components/row/row.js +1 -1
  121. package/components/search/search.js +1 -1
  122. package/components/segment/segment.js +1 -1
  123. package/components/segmented/segmented.js +1 -1
  124. package/components/select/select.a2ui.json +58 -4
  125. package/components/select/{class.js → select.class.js} +415 -6
  126. package/components/select/select.css +158 -0
  127. package/components/select/select.d.ts +31 -1
  128. package/components/select/select.js +1 -1
  129. package/components/select/select.test.js +202 -0
  130. package/components/select/select.yaml +126 -5
  131. package/components/skeleton/skeleton.js +1 -1
  132. package/components/slider/slider.js +1 -1
  133. package/components/spinner/spinner.a2ui.json +3 -2
  134. package/components/spinner/{class.js → spinner.class.js} +33 -3
  135. package/components/spinner/spinner.css +91 -35
  136. package/components/spinner/spinner.d.ts +2 -2
  137. package/components/spinner/spinner.js +1 -1
  138. package/components/spinner/spinner.test.js +49 -11
  139. package/components/spinner/spinner.yaml +9 -1
  140. package/components/stack/stack.js +1 -1
  141. package/components/step-progress/step-progress.js +1 -1
  142. package/components/stepper/stepper-item.yaml +1 -1
  143. package/components/stepper/stepper.js +1 -1
  144. package/components/stream/stream.js +1 -1
  145. package/components/swatch/swatch.js +1 -1
  146. package/components/swiper/swiper.js +1 -1
  147. package/components/switch/switch.js +1 -1
  148. package/components/table/table.css +1 -1
  149. package/components/table/table.js +1 -1
  150. package/components/table-toolbar/{class.js → table-toolbar.class.js} +1 -1
  151. package/components/table-toolbar/table-toolbar.js +1 -1
  152. package/components/tabs/tab.yaml +2 -2
  153. package/components/tabs/tabs.js +1 -1
  154. package/components/tag/tag.js +1 -1
  155. package/components/tags-input/tags-input.a2ui.json +337 -0
  156. package/components/tags-input/tags-input.class.js +776 -0
  157. package/components/tags-input/tags-input.css +201 -0
  158. package/components/tags-input/tags-input.d.ts +120 -0
  159. package/components/tags-input/tags-input.examples.md +92 -0
  160. package/components/tags-input/tags-input.js +17 -0
  161. package/components/tags-input/tags-input.test.js +368 -0
  162. package/components/tags-input/tags-input.yaml +367 -0
  163. package/components/text/text.js +1 -1
  164. package/components/textarea/textarea.a2ui.json +1 -1
  165. package/components/textarea/textarea.js +1 -1
  166. package/components/textarea/textarea.yaml +11 -8
  167. package/components/time-picker/time-picker.js +1 -1
  168. package/components/timeline/timeline-item.yaml +2 -2
  169. package/components/timeline/{class.js → timeline.class.js} +1 -1
  170. package/components/timeline/timeline.js +1 -1
  171. package/components/toast/toast.js +1 -1
  172. package/components/toggle-group/toggle-group.js +1 -1
  173. package/components/toggle-group/toggle-option.yaml +1 -1
  174. package/components/toggle-scheme/toggle-scheme.js +1 -1
  175. package/components/toolbar/toolbar-group.yaml +1 -1
  176. package/components/toolbar/toolbar.js +1 -1
  177. package/components/tooltip/tooltip.js +1 -1
  178. package/components/tree/tree-item.yaml +1 -1
  179. package/components/tree/tree.js +1 -1
  180. package/components/upload/upload.js +1 -1
  181. package/dist/web-components.min.css +1 -1
  182. package/dist/web-components.min.js +111 -90
  183. package/package.json +3 -3
  184. package/styles/components.css +3 -0
  185. /package/components/accordion/{class.js → accordion.class.js} +0 -0
  186. /package/components/action-list/{class.js → action-list.class.js} +0 -0
  187. /package/components/agent-feedback-bar/{class.js → agent-feedback-bar.class.js} +0 -0
  188. /package/components/agent-questions/{class.js → agent-questions.class.js} +0 -0
  189. /package/components/agent-reasoning/{class.js → agent-reasoning.class.js} +0 -0
  190. /package/components/agent-suggestions/{class.js → agent-suggestions.class.js} +0 -0
  191. /package/components/avatar/{class.js → avatar.class.js} +0 -0
  192. /package/components/badge/{class.js → badge.class.js} +0 -0
  193. /package/components/block/{class.js → block.class.js} +0 -0
  194. /package/components/breadcrumb/{class.js → breadcrumb.class.js} +0 -0
  195. /package/components/button/{class.js → button.class.js} +0 -0
  196. /package/components/calendar-picker/{class.js → calendar-picker.class.js} +0 -0
  197. /package/components/card/{class.js → card.class.js} +0 -0
  198. /package/components/chart/{class.js → chart.class.js} +0 -0
  199. /package/components/chart-legend/{class.js → chart-legend.class.js} +0 -0
  200. /package/components/chat-thread/{class.js → chat-thread.class.js} +0 -0
  201. /package/components/check/{class.js → check.class.js} +0 -0
  202. /package/components/code/{class.js → code.class.js} +0 -0
  203. /package/components/col/{class.js → col.class.js} +0 -0
  204. /package/components/color-input/{class.js → color-input.class.js} +0 -0
  205. /package/components/color-picker/{class.js → color-picker.class.js} +0 -0
  206. /package/components/combobox/{class.js → combobox.class.js} +0 -0
  207. /package/components/command/{class.js → command.class.js} +0 -0
  208. /package/components/demo-toggle/{class.js → demo-toggle.class.js} +0 -0
  209. /package/components/description-list/{class.js → description-list.class.js} +0 -0
  210. /package/components/divider/{class.js → divider.class.js} +0 -0
  211. /package/components/drawer/{class.js → drawer.class.js} +0 -0
  212. /package/components/embed/{class.js → embed.class.js} +0 -0
  213. /package/components/empty-state/{class.js → empty-state.class.js} +0 -0
  214. /package/components/feed/{class.js → feed.class.js} +0 -0
  215. /package/components/field/{class.js → field.class.js} +0 -0
  216. /package/components/fields/{class.js → fields.class.js} +0 -0
  217. /package/components/grid/{class.js → grid.class.js} +0 -0
  218. /package/components/heatmap/{class.js → heatmap.class.js} +0 -0
  219. /package/components/icon/{class.js → icon.class.js} +0 -0
  220. /package/components/image/{class.js → image.class.js} +0 -0
  221. /package/components/input/{class.js → input.class.js} +0 -0
  222. /package/components/inspector/{class.js → inspector.class.js} +0 -0
  223. /package/components/integration-card/{class.js → integration-card.class.js} +0 -0
  224. /package/components/kbd/{class.js → kbd.class.js} +0 -0
  225. /package/components/link/{class.js → link.class.js} +0 -0
  226. /package/components/list/{class.js → list.class.js} +0 -0
  227. /package/components/list-window/{class.js → list-window.class.js} +0 -0
  228. /package/components/menu/{class.js → menu.class.js} +0 -0
  229. /package/components/nav/{class.js → nav.class.js} +0 -0
  230. /package/components/nav-group/{class.js → nav-group.class.js} +0 -0
  231. /package/components/nav-item/{class.js → nav-item.class.js} +0 -0
  232. /package/components/noodles/{class.js → noodles.class.js} +0 -0
  233. /package/components/option-card/{class.js → option-card.class.js} +0 -0
  234. /package/components/otp-input/{class.js → otp-input.class.js} +0 -0
  235. /package/components/page/{class.js → page.class.js} +0 -0
  236. /package/components/pagination/{class.js → pagination.class.js} +0 -0
  237. /package/components/pane/{class.js → pane.class.js} +0 -0
  238. /package/components/pipeline-status/{class.js → pipeline-status.class.js} +0 -0
  239. /package/components/popover/{class.js → popover.class.js} +0 -0
  240. /package/components/progress/{class.js → progress.class.js} +0 -0
  241. /package/components/progress-row/{class.js → progress-row.class.js} +0 -0
  242. /package/components/radio/{class.js → radio.class.js} +0 -0
  243. /package/components/range/{class.js → range.class.js} +0 -0
  244. /package/components/rating/{class.js → rating.class.js} +0 -0
  245. /package/components/richtext/{class.js → richtext.class.js} +0 -0
  246. /package/components/row/{class.js → row.class.js} +0 -0
  247. /package/components/search/{class.js → search.class.js} +0 -0
  248. /package/components/segment/{class.js → segment.class.js} +0 -0
  249. /package/components/segmented/{class.js → segmented.class.js} +0 -0
  250. /package/components/skeleton/{class.js → skeleton.class.js} +0 -0
  251. /package/components/slider/{class.js → slider.class.js} +0 -0
  252. /package/components/stack/{class.js → stack.class.js} +0 -0
  253. /package/components/step-progress/{class.js → step-progress.class.js} +0 -0
  254. /package/components/stepper/{class.js → stepper.class.js} +0 -0
  255. /package/components/stream/{class.js → stream.class.js} +0 -0
  256. /package/components/swatch/{class.js → swatch.class.js} +0 -0
  257. /package/components/swiper/{class.js → swiper.class.js} +0 -0
  258. /package/components/switch/{class.js → switch.class.js} +0 -0
  259. /package/components/table/{class.js → table.class.js} +0 -0
  260. /package/components/tabs/{class.js → tabs.class.js} +0 -0
  261. /package/components/tag/{class.js → tag.class.js} +0 -0
  262. /package/components/text/{class.js → text.class.js} +0 -0
  263. /package/components/textarea/{class.js → textarea.class.js} +0 -0
  264. /package/components/time-picker/{class.js → time-picker.class.js} +0 -0
  265. /package/components/toast/{class.js → toast.class.js} +0 -0
  266. /package/components/toggle-group/{class.js → toggle-group.class.js} +0 -0
  267. /package/components/toggle-scheme/{class.js → toggle-scheme.class.js} +0 -0
  268. /package/components/toolbar/{class.js → toolbar.class.js} +0 -0
  269. /package/components/tooltip/{class.js → tooltip.class.js} +0 -0
  270. /package/components/tree/{class.js → tree.class.js} +0 -0
  271. /package/components/upload/{class.js → upload.class.js} +0 -0
@@ -50,9 +50,16 @@ export class UISpinner extends UIElement {
50
50
  label: { type: String, default: 'Loading', reflect: false },
51
51
  };
52
52
 
53
- // No stamped children. The visual is pure CSS (::before / per-variant
54
- // peers) on the host. Same shape as the spec calls out for SPEC-003
55
- // <visually-hidden-ui> a CSS-styled wrapper with no internal stamp.
53
+ // Arc / ring / knight variants are pure ::before paint — no children.
54
+ // Dots variant stamps three <span data-spinner-dot=N> children in
55
+ // #syncDots() so each dot can carry an independent animation-delay.
56
+ // Pre-fix: ::before painted dot1, ::after painted dot3, and a
57
+ // box-shadow on ::before painted the middle dot. The pseudo-element
58
+ // approach broke spacing (the box-shadow dot doesn't participate in
59
+ // flex-gap math; the middle dot can't animate; the outer dots picked
60
+ // up scale from keyframes but the middle stayed static). Three real
61
+ // children let flex-gap distribute the row evenly AND give each dot
62
+ // its own animation phase.
56
63
  static template = () => null;
57
64
 
58
65
  connected() {
@@ -60,10 +67,33 @@ export class UISpinner extends UIElement {
60
67
  if (!this.hasAttribute('role')) this.setAttribute('role', 'progressbar');
61
68
  if (!this.hasAttribute('aria-busy')) this.setAttribute('aria-busy', 'true');
62
69
  this.setAttribute('aria-valuetext', this.label || 'Loading');
70
+ this.#syncDots();
63
71
  }
64
72
 
65
73
  render() {
66
74
  // Keep aria-valuetext in sync if `label` changes after connect.
67
75
  this.setAttribute('aria-valuetext', this.label || 'Loading');
76
+ this.#syncDots();
77
+ }
78
+
79
+ #syncDots() {
80
+ const isDots = this.variant === 'dots';
81
+ const existing = this.querySelectorAll(':scope > [data-spinner-dot]');
82
+ if (isDots) {
83
+ if (existing.length === 3) return; // already stamped
84
+ // Wipe any stale dots from a previous variant + stamp fresh.
85
+ existing.forEach((n) => n.remove());
86
+ const frag = document.createDocumentFragment();
87
+ for (let i = 1; i <= 3; i++) {
88
+ const span = document.createElement('span');
89
+ span.setAttribute('data-spinner-dot', String(i));
90
+ span.setAttribute('aria-hidden', 'true');
91
+ frag.appendChild(span);
92
+ }
93
+ this.appendChild(frag);
94
+ } else if (existing.length) {
95
+ // Variant changed away from dots — drop the stamped children.
96
+ existing.forEach((n) => n.remove());
97
+ }
68
98
  }
69
99
  }
@@ -8,10 +8,23 @@
8
8
  --spinner-size-default: 1rem; /* icon-ui md */
9
9
  --spinner-color-default: currentColor;
10
10
  --spinner-stroke-default: 2px;
11
- --spinner-duration-default: var(--a-duration-slow);
11
+ /* Rotational cadence — interaction-duration tokens (`--a-duration-*`,
12
+ 120-300 ms) are tuned for hover/focus transitions; a full spinner
13
+ revolution at 300 ms reads as a frantic blur. Canonical loader
14
+ cadence is ~0.8 s per revolution (smooth, perceptible motion). */
15
+ --spinner-duration-default: 0.8s;
12
16
  --spinner-track-opacity-default: 0.25;
13
17
  --spinner-dot-size-default: calc(var(--spinner-size, var(--spinner-size-default)) / 4);
14
18
  --spinner-dot-gap-default: calc(var(--spinner-size, var(--spinner-size-default)) / 8);
19
+ /* Knight-rider variant — horizontal track with a sliding thumb.
20
+ Track width is a separate scale from the round-spinner --spinner-size
21
+ (Knight reads as a bar, not a glyph). Track height defaults to the
22
+ spinner-stroke for visual continuity with arc/ring. Thumb width
23
+ expressed as a 0-1 ratio of the track so the keyframe math is
24
+ single-variable. */
25
+ --spinner-bar-track-width-default: 4rem;
26
+ --spinner-bar-track-height-default: var(--spinner-stroke, var(--spinner-stroke-default));
27
+ --spinner-bar-thumb-ratio-default: 0.3;
15
28
  }
16
29
 
17
30
  /* ── Block 2 — BASE ────────────────────────────────────────────── */
@@ -65,20 +78,17 @@
65
78
  }
66
79
 
67
80
  /* ── Variant: DOTS — three bouncing dots ─────────────────────────
68
- The dots layout uses inline-flex + three ::before/::after-style
69
- children. Since pseudo-elements only give us two, we render dots
70
- via three radial-gradient stops on a background animation that
71
- translates each stop independently — but a simpler, more robust
72
- shape is to overlay three identical dot pseudos via a flexbox
73
- container of pseudo + two child pseudos isn't possible without
74
- a stamped child. So we use a single ::before that paints all
75
- three dots via background gradients + an animation on the
76
- `--dot-bounce` custom property (animated via @property).
81
+ Layout: inline-flex with three real <span data-spinner-dot=N>
82
+ children stamped by spinner.class.js #syncDots(). flex-gap distributes
83
+ the row evenly; each child carries its own animation-delay so
84
+ the bounce reads as a left-to-right wave.
77
85
 
78
- Pragmatic alternative: render the three dots using ::before +
79
- ::after pseudos as the outer two dots, and a tightly-controlled
80
- box-shadow on ::before to paint the middle dot. Animate each via
81
- keyframes that stagger the bounce timing. */
86
+ Why three real children (instead of ::before / ::after + a
87
+ middle dot painted via box-shadow): box-shadow paints the
88
+ middle dot but does NOT participate in flex-gap, so the box-
89
+ shadow dot and the right pseudo had asymmetric spacing. The
90
+ middle dot also couldn't carry its own @keyframes animation
91
+ (box-shadow can't animate per-stop). Stamped children fix both. */
82
92
  :scope[variant="dots"] {
83
93
  display: inline-flex;
84
94
  align-items: center;
@@ -88,35 +98,61 @@
88
98
  gap: var(--spinner-dot-gap, var(--spinner-dot-gap-default));
89
99
  }
90
100
 
91
- :scope[variant="dots"]::before,
92
- :scope[variant="dots"]::after {
93
- content: "";
94
- display: block;
101
+ :scope[variant="dots"] > [data-spinner-dot] {
95
102
  width: var(--spinner-dot-size, var(--spinner-dot-size-default));
96
103
  height: var(--spinner-dot-size, var(--spinner-dot-size-default));
97
104
  border-radius: 50%;
98
105
  background: currentColor;
99
106
  animation: spinner-ui-bounce var(--spinner-duration, var(--spinner-duration-default)) ease-in-out infinite;
100
107
  }
101
-
102
- /* Stagger the trailing dot so the row reads as a wave. The middle
103
- dot is painted via box-shadow on ::before — same dot diameter, a
104
- dot-gap to the right, painted with the same currentColor. The
105
- middle dot's animation phase is the average of the two pseudos. */
106
- :scope[variant="dots"]::before {
107
- box-shadow:
108
- calc(var(--spinner-dot-size, var(--spinner-dot-size-default)) + var(--spinner-dot-gap, var(--spinner-dot-gap-default))) 0 0 currentColor;
109
- margin-right: calc(var(--spinner-dot-size, var(--spinner-dot-size-default)) + var(--spinner-dot-gap, var(--spinner-dot-gap-default)));
110
- animation-delay: 0s;
108
+ /* Stagger each dot so the row reads as a wave left → right.
109
+ Three equal slices of one revolution (0, T/3, 2T/3) give the
110
+ classic three-dot loading rhythm. */
111
+ :scope[variant="dots"] > [data-spinner-dot="1"] { animation-delay: 0s; }
112
+ :scope[variant="dots"] > [data-spinner-dot="2"] {
113
+ animation-delay: calc(var(--spinner-duration, var(--spinner-duration-default)) / 3);
114
+ }
115
+ :scope[variant="dots"] > [data-spinner-dot="3"] {
116
+ animation-delay: calc(var(--spinner-duration, var(--spinner-duration-default)) * 2 / 3);
111
117
  }
112
118
 
113
- :scope[variant="dots"]::after {
114
- animation-delay: calc(var(--spinner-duration, var(--spinner-duration-default)) / 3);
119
+ /* ── Variant: KNIGHT — knight-rider sliding bar ──────────────────
120
+ Horizontal track with a thumb sliding back-and-forth via
121
+ `animation-direction: alternate`. Thumb width is a 0–1 ratio of
122
+ the track so the keyframe math reduces to translateX(((1/ratio) - 1) * 100%)
123
+ of the THUMB's own width — i.e. travel = (track - thumb) px,
124
+ expressed in thumb-relative percentages so it scales with any
125
+ track width without a calc that touches both axes. Default
126
+ ratio 0.3 → translate end = 233.33% (of thumb width). */
127
+ :scope[variant="knight"] {
128
+ display: inline-block;
129
+ width: var(--spinner-bar-track-width, var(--spinner-bar-track-width-default));
130
+ height: var(--spinner-bar-track-height, var(--spinner-bar-track-height-default));
131
+ background: color-mix(
132
+ in oklch,
133
+ currentColor calc(var(--spinner-track-opacity, var(--spinner-track-opacity-default)) * 100%),
134
+ transparent
135
+ );
136
+ border-radius: 9999px;
137
+ position: relative;
138
+ overflow: hidden;
139
+ }
140
+ :scope[variant="knight"]::before {
141
+ content: "";
142
+ position: absolute;
143
+ inset: 0 auto 0 0; /* top:0; right:auto; bottom:0; left:0 */
144
+ width: calc(var(--spinner-bar-thumb-ratio, var(--spinner-bar-thumb-ratio-default)) * 100%);
145
+ background: currentColor;
146
+ border-radius: inherit;
147
+ animation: spinner-ui-knight
148
+ var(--spinner-duration, var(--spinner-duration-default))
149
+ ease-in-out infinite alternate;
115
150
  }
116
151
 
117
152
  /* ── Paused state — freeze in place ────────────────────────────── */
118
153
  :scope[paused]::before,
119
- :scope[paused]::after {
154
+ :scope[paused]::after,
155
+ :scope[paused] > [data-spinner-dot] {
120
156
  animation-play-state: paused;
121
157
  }
122
158
 
@@ -126,7 +162,8 @@
126
162
  JS lifecycle. */
127
163
  @media (prefers-reduced-motion: reduce) {
128
164
  :scope::before,
129
- :scope::after {
165
+ :scope::after,
166
+ :scope > [data-spinner-dot] {
130
167
  animation: none !important;
131
168
  }
132
169
  :scope::before {
@@ -145,12 +182,18 @@
145
182
  text-align: center;
146
183
  color: currentColor;
147
184
  }
148
- :scope[variant="dots"]::after {
149
- display: none;
150
- }
185
+ /* Dots variant — hide all three stamped dots and let the host's
186
+ ::before paint the ellipsis fallback. */
187
+ :scope[variant="dots"] > [data-spinner-dot] { display: none; }
151
188
  :scope[variant="dots"] {
152
189
  width: var(--spinner-size, var(--spinner-size-default)); /* shrink back to a square in reduced mode */
153
190
  }
191
+ /* Knight-rider — hide the sliding thumb; the host's ::before
192
+ above repaints as the ellipsis fallback. */
193
+ :scope[variant="knight"] {
194
+ width: var(--spinner-size, var(--spinner-size-default));
195
+ background: transparent;
196
+ }
154
197
  }
155
198
 
156
199
  /* ── Keyframes ─────────────────────────────────────────────────── */
@@ -162,4 +205,17 @@
162
205
  0%, 80%, 100% { transform: scale(0.6); opacity: 0.6; }
163
206
  40% { transform: scale(1); opacity: 1; }
164
207
  }
208
+
209
+ /* Knight-rider — thumb slides from left edge to (track - thumb)
210
+ and bounces back via `animation-direction: alternate`. The
211
+ translate distance is expressed in thumb-relative percentages:
212
+ translateX(((1 / ratio) - 1) * 100%) of the thumb's own width
213
+ equals (track - thumb) px. At default ratio 0.3 this resolves
214
+ to translateX(233.33%). */
215
+ @keyframes spinner-ui-knight {
216
+ from { transform: translateX(0); }
217
+ to { transform: translateX(
218
+ calc((1 / var(--spinner-bar-thumb-ratio, var(--spinner-bar-thumb-ratio-default)) - 1) * 100%)
219
+ ); }
220
+ }
165
221
  }
@@ -21,6 +21,6 @@ export class UISpinner extends UIElement {
21
21
  size: 'sm' | 'md' | 'lg';
22
22
  /** Color tone — `current` inherits parent text color (matches button label), `accent` uses brand accent, `subtle` is muted, `inverse` flips for on-accent surfaces. */
23
23
  tone: 'current' | 'accent' | 'subtle' | 'inverse';
24
- /** Visual flavor — arc (rotating quarter-circle), ring (full ring with one colored segment), dots (three bouncing dots). */
25
- variant: 'arc' | 'ring' | 'dots';
24
+ /** 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). */
25
+ variant: 'arc' | 'ring' | 'dots' | 'knight';
26
26
  }
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UISpinner } from './class.js';
13
+ import { UISpinner } from './spinner.class.js';
14
14
 
15
15
  defineIfFree('spinner-ui', UISpinner);
16
16
 
@@ -182,16 +182,20 @@ describe('spinner-ui — CSS source contract', () => {
182
182
  }
183
183
  });
184
184
 
185
- it('size="sm" / "md" / "lg" all override --spinner-size', () => {
186
- expect(SPINNER_CSS).toMatch(/:scope\[size="sm"\][^}]*--spinner-size:\s*0\.875rem/);
187
- expect(SPINNER_CSS).toMatch(/:scope\[size="md"\][^}]*--spinner-size:\s*1rem/);
188
- expect(SPINNER_CSS).toMatch(/:scope\[size="lg"\][^}]*--spinner-size:\s*1\.25rem/);
185
+ it('size="sm" / "md" / "lg" all override --spinner-size-default', () => {
186
+ // The two-token convention (--{prop}-default for defaults +
187
+ // var(--{prop}, var(--{prop}-default)) at use sites) means size
188
+ // variants flip the -default token; consumer overrides write to
189
+ // the bare --{prop} which wins via the var() fallback chain.
190
+ expect(SPINNER_CSS).toMatch(/:scope\[size="sm"\][^}]*--spinner-size-default:\s*0\.875rem/);
191
+ expect(SPINNER_CSS).toMatch(/:scope\[size="md"\][^}]*--spinner-size-default:\s*1rem/);
192
+ expect(SPINNER_CSS).toMatch(/:scope\[size="lg"\][^}]*--spinner-size-default:\s*1\.25rem/);
189
193
  });
190
194
 
191
- it('tone="subtle" / "accent" / "inverse" override --spinner-color via semantic tokens', () => {
192
- expect(SPINNER_CSS).toMatch(/:scope\[tone="subtle"\][^}]*--spinner-color:\s*var\(--a-fg-subtle\)/);
193
- expect(SPINNER_CSS).toMatch(/:scope\[tone="accent"\][^}]*--spinner-color:\s*var\(--a-accent-strong\)/);
194
- expect(SPINNER_CSS).toMatch(/:scope\[tone="inverse"\][^}]*--spinner-color:\s*var\(--a-chrome-light\)/);
195
+ it('tone="subtle" / "accent" / "inverse" override --spinner-color-default via semantic tokens', () => {
196
+ expect(SPINNER_CSS).toMatch(/:scope\[tone="subtle"\][^}]*--spinner-color-default:\s*var\(--a-fg-subtle\)/);
197
+ expect(SPINNER_CSS).toMatch(/:scope\[tone="accent"\][^}]*--spinner-color-default:\s*var\(--a-accent-strong\)/);
198
+ expect(SPINNER_CSS).toMatch(/:scope\[tone="inverse"\][^}]*--spinner-color-default:\s*var\(--a-chrome-light\)/);
195
199
  });
196
200
 
197
201
  it('arc variant uses a rotating quarter-circle border on ::before', () => {
@@ -206,12 +210,46 @@ describe('spinner-ui — CSS source contract', () => {
206
210
  expect(SPINNER_CSS).toMatch(/border-top-color:\s*currentColor/);
207
211
  });
208
212
 
209
- it('dots variant uses ::before + ::after with bounce keyframes', () => {
210
- expect(SPINNER_CSS).toMatch(/:scope\[variant="dots"\]::before/);
211
- expect(SPINNER_CSS).toMatch(/:scope\[variant="dots"\]::after/);
213
+ it('dots variant uses 3 stamped [data-spinner-dot] children with bounce keyframes', () => {
214
+ // Pre-fix: ::before painted dot1 + a box-shadow middle dot, ::after
215
+ // painted dot3. Spacing was uneven (box-shadow doesn't participate
216
+ // in flex-gap math) and the middle dot couldn't animate. Post-fix
217
+ // (spinner.class.js #syncDots): three real <span data-spinner-dot=N>
218
+ // children stamped on connect; flex-gap distributes the row evenly
219
+ // and each child carries an independent animation-delay so the
220
+ // bounce reads as a left-to-right wave.
221
+ expect(SPINNER_CSS).toMatch(/:scope\[variant="dots"\]\s*>\s*\[data-spinner-dot\]/);
222
+ expect(SPINNER_CSS).toMatch(/:scope\[variant="dots"\]\s*>\s*\[data-spinner-dot="1"\]/);
223
+ expect(SPINNER_CSS).toMatch(/:scope\[variant="dots"\]\s*>\s*\[data-spinner-dot="2"\]/);
224
+ expect(SPINNER_CSS).toMatch(/:scope\[variant="dots"\]\s*>\s*\[data-spinner-dot="3"\]/);
212
225
  expect(SPINNER_CSS).toMatch(/animation:\s*spinner-ui-bounce/);
213
226
  });
214
227
 
228
+ it('knight variant uses a sliding ::before thumb with knight keyframes', () => {
229
+ expect(SPINNER_CSS).toMatch(/:scope\[variant="knight"\]\s*\{/);
230
+ expect(SPINNER_CSS).toMatch(/:scope\[variant="knight"\]::before/);
231
+ expect(SPINNER_CSS).toMatch(/animation:\s*spinner-ui-knight/);
232
+ // Keyframe math: translateX((1/ratio - 1) * 100%) sweeps the thumb
233
+ // from x=0 to x=(track - thumb) px, expressed in thumb-relative %.
234
+ expect(SPINNER_CSS).toMatch(/@keyframes\s+spinner-ui-knight/);
235
+ });
236
+
237
+ it('knight stamps NO data-spinner-dot children (pseudo-element paint only)', async () => {
238
+ const k = mount('<spinner-ui variant="knight"></spinner-ui>');
239
+ await tick();
240
+ expect(k.querySelectorAll(':scope > [data-spinner-dot]').length).toBe(0);
241
+ });
242
+
243
+ it('dots stamps exactly 3 data-spinner-dot children on connect', async () => {
244
+ const d = mount('<spinner-ui variant="dots"></spinner-ui>');
245
+ await tick();
246
+ const dots = d.querySelectorAll(':scope > [data-spinner-dot]');
247
+ expect(dots.length).toBe(3);
248
+ expect(dots[0].getAttribute('data-spinner-dot')).toBe('1');
249
+ expect(dots[1].getAttribute('data-spinner-dot')).toBe('2');
250
+ expect(dots[2].getAttribute('data-spinner-dot')).toBe('3');
251
+ });
252
+
215
253
  it('paused state freezes the animation via animation-play-state', () => {
216
254
  expect(SPINNER_CSS).toMatch(/:scope\[paused\][^}]*animation-play-state:\s*paused/);
217
255
  });
@@ -26,13 +26,21 @@ props:
26
26
  - lg
27
27
  reflect: true
28
28
  variant:
29
- description: Visual flavor — arc (rotating quarter-circle), ring (full ring with one colored segment), dots (three bouncing dots).
29
+ description: >-
30
+ Visual flavor —
31
+ `arc` (rotating quarter-circle, the default glyph spinner),
32
+ `ring` (full ring with one colored segment rotating around it),
33
+ `dots` (three bouncing dots — animated in a left-to-right wave),
34
+ `knight` (horizontal "knight-rider" bar — a sliding thumb that
35
+ bounces back-and-forth across a track; widest variant, reads as
36
+ a determinate-looking bar but is indeterminate by intent).
30
37
  type: string
31
38
  default: arc
32
39
  enum:
33
40
  - arc
34
41
  - ring
35
42
  - dots
43
+ - knight
36
44
  reflect: true
37
45
  tone:
38
46
  description: Color tone — `current` inherits parent text color (matches button label), `accent` uses brand accent, `subtle` is muted, `inverse` flips for on-accent surfaces.
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UIStack } from './class.js';
13
+ import { UIStack } from './stack.class.js';
14
14
 
15
15
  defineIfFree('stack-ui', UIStack);
16
16
 
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UIStepProgress } from './class.js';
13
+ import { UIStepProgress } from './step-progress.class.js';
14
14
 
15
15
  defineIfFree('step-progress-ui', UIStepProgress);
16
16
 
@@ -1,7 +1,7 @@
1
1
  # Edit this file; run `npm run build:components` to regenerate a2ui.json.
2
2
  #
3
3
  # §228 (v0.5.9): authored to close the §229 baseline class-export-undeclared
4
- # drift for UIStepperItem. Ships as a sibling class in stepper/class.js
4
+ # drift for UIStepperItem. Ships as a sibling class in stepper/stepper.class.js
5
5
  # + is registered alongside UIStepper.
6
6
 
7
7
  # Child component of <stepper-ui>. Surface only inside that parent.
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UIStepper, UIStepperItem } from './class.js';
13
+ import { UIStepper, UIStepperItem } from './stepper.class.js';
14
14
 
15
15
  defineIfFree('stepper-ui', UIStepper);
16
16
  defineIfFree('stepper-item-ui', UIStepperItem);
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UIStream } from './class.js';
13
+ import { UIStream } from './stream.class.js';
14
14
 
15
15
  defineIfFree('stream-ui', UIStream);
16
16
 
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UISwatch } from './class.js';
13
+ import { UISwatch } from './swatch.class.js';
14
14
 
15
15
  defineIfFree('swatch-ui', UISwatch);
16
16
 
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UISwiper } from './class.js';
13
+ import { UISwiper } from './swiper.class.js';
14
14
 
15
15
  defineIfFree('swiper-ui', UISwiper);
16
16
 
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UISwitch } from './class.js';
13
+ import { UISwitch } from './switch.class.js';
14
14
 
15
15
  defineIfFree('switch-ui', UISwitch);
16
16
 
@@ -433,7 +433,7 @@
433
433
 
434
434
  /* ═══════ Loading (skeleton rows) ═══════
435
435
  Skeleton rows replace real rows while [loading] is set on the host
436
- (see class.js #renderOverlays). Each row is a [data-skeleton-row]
436
+ (see table.class.js #renderOverlays). Each row is a [data-skeleton-row]
437
437
  containing <skeleton-ui> cells. Inherit body-row layout so column
438
438
  widths track the header, then suppress hover/striping/click states
439
439
  (no real data to interact with). */
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UITable } from './class.js';
13
+ import { UITable } from './table.class.js';
14
14
 
15
15
  defineIfFree('table-ui', UITable);
16
16
 
@@ -124,7 +124,7 @@ export class UITableToolbar extends UIElement {
124
124
  variant: { type: String, default: 'default', reflect: true },
125
125
  };
126
126
 
127
- // §205 (v0.5.7): dynamic sort-indicator icons (class.js:576 — nested ternary
127
+ // §205 (v0.5.7): dynamic sort-indicator icons (table-toolbar.class.js:576 — nested ternary
128
128
  // `dir === 'asc' ? 'arrow-up' : dir === 'desc' ? 'arrow-down' : 'caret-up-down'`).
129
129
  // Per FEEDBACK-16 §1 + §209 slot-11 ternary-walker discovery.
130
130
  static requiredIcons = ['arrow-up', 'arrow-down', 'caret-up-down'];
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UITableToolbar } from './class.js';
13
+ import { UITableToolbar } from './table-toolbar.class.js';
14
14
 
15
15
  defineIfFree('table-toolbar-ui', UITableToolbar);
16
16
 
@@ -1,9 +1,9 @@
1
1
  # Edit this file; run `npm run build:components` to regenerate a2ui.json.
2
2
  #
3
3
  # §176 (v0.5.5): authored to close the §175 baseline-orphan class. The
4
- # component already existed as a sibling class in the parent's class.js
4
+ # component already existed as a sibling class in the parent's tabs.class.js
5
5
  # + was registered alongside the parent (e.g. UIList + UIListItem both
6
- # from list/class.js). The catalog just lacked its own entry. With the
6
+ # from list/list.class.js). The catalog just lacked its own entry. With the
7
7
  # §172 sibling-yaml scanner, this file gets picked up next to the parent
8
8
  # yaml.
9
9
 
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UITabs } from './class.js';
13
+ import { UITabs } from './tabs.class.js';
14
14
 
15
15
  defineIfFree('tabs-ui', UITabs);
16
16
 
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { defineIfFree } from '../../core/register.js';
13
- import { UITag } from './class.js';
13
+ import { UITag } from './tag.class.js';
14
14
 
15
15
  defineIfFree('tag-ui', UITag);
16
16