@adia-ai/web-components 0.6.32 → 0.6.34

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 (164) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/components/accordion/accordion.css +2 -2
  3. package/components/action-list/action-list.css +2 -2
  4. package/components/agent-artifact/agent-artifact.css +31 -31
  5. package/components/agent-feedback-bar/agent-feedback-bar.css +10 -10
  6. package/components/agent-questions/agent-questions.css +57 -57
  7. package/components/agent-reasoning/agent-reasoning.css +62 -62
  8. package/components/agent-suggestions/agent-suggestions.css +4 -4
  9. package/components/agent-trace/agent-trace.css +53 -53
  10. package/components/alert/alert.css +41 -41
  11. package/components/avatar/avatar.css +27 -27
  12. package/components/badge/badge.css +27 -27
  13. package/components/block/block.css +16 -16
  14. package/components/breadcrumb/breadcrumb.css +23 -23
  15. package/components/button/button.css +101 -91
  16. package/components/calendar-grid/calendar-grid.a2ui.json +136 -0
  17. package/components/calendar-grid/calendar-grid.css +226 -0
  18. package/components/calendar-grid/calendar-grid.d.ts +37 -0
  19. package/components/calendar-grid/calendar-grid.js +17 -0
  20. package/components/calendar-grid/calendar-grid.yaml +116 -0
  21. package/components/calendar-grid/class.js +300 -0
  22. package/components/calendar-picker/calendar-picker.css +139 -139
  23. package/components/canvas/canvas.css +12 -12
  24. package/components/card/card.css +83 -83
  25. package/components/chart/chart.css +224 -224
  26. package/components/chart-legend/chart-legend.css +26 -26
  27. package/components/check/check.css +40 -40
  28. package/components/code/code.css +125 -125
  29. package/components/col/col.css +15 -15
  30. package/components/color-picker/color-picker.css +55 -55
  31. package/components/combobox/class.js +861 -0
  32. package/components/combobox/combobox.a2ui.json +363 -0
  33. package/components/combobox/combobox.css +244 -0
  34. package/components/combobox/combobox.d.ts +113 -0
  35. package/components/combobox/combobox.examples.md +59 -0
  36. package/components/combobox/combobox.js +17 -0
  37. package/components/combobox/combobox.test.js +181 -0
  38. package/components/combobox/combobox.yaml +369 -0
  39. package/components/command/command.css +90 -90
  40. package/components/date-range-picker/class.js +775 -0
  41. package/components/date-range-picker/date-range-picker.a2ui.json +300 -0
  42. package/components/date-range-picker/date-range-picker.css +178 -0
  43. package/components/date-range-picker/date-range-picker.d.ts +82 -0
  44. package/components/date-range-picker/date-range-picker.examples.md +37 -0
  45. package/components/date-range-picker/date-range-picker.js +17 -0
  46. package/components/date-range-picker/date-range-picker.test.js +387 -0
  47. package/components/date-range-picker/date-range-picker.yaml +285 -0
  48. package/components/datetime-picker/class.js +706 -0
  49. package/components/datetime-picker/datetime-picker.a2ui.json +334 -0
  50. package/components/datetime-picker/datetime-picker.css +150 -0
  51. package/components/datetime-picker/datetime-picker.d.ts +86 -0
  52. package/components/datetime-picker/datetime-picker.examples.md +46 -0
  53. package/components/datetime-picker/datetime-picker.js +17 -0
  54. package/components/datetime-picker/datetime-picker.test.js +454 -0
  55. package/components/datetime-picker/datetime-picker.yaml +332 -0
  56. package/components/demo-toggle/demo-toggle.css +27 -27
  57. package/components/description-list/description-list.css +18 -18
  58. package/components/divider/divider.css +24 -24
  59. package/components/embed/embed.css +6 -6
  60. package/components/empty-state/empty-state.css +27 -27
  61. package/components/feed/feed.css +12 -12
  62. package/components/field/field.css +37 -28
  63. package/components/field/field.test.js +32 -0
  64. package/components/fields/fields.css +5 -5
  65. package/components/grid/grid.css +5 -5
  66. package/components/heatmap/heatmap.css +63 -63
  67. package/components/icon/icon.css +12 -12
  68. package/components/image/image.css +14 -14
  69. package/components/index.js +8 -0
  70. package/components/input/input.css +66 -66
  71. package/components/inspector/inspector.css +6 -6
  72. package/components/integration-card/class.js +410 -0
  73. package/components/integration-card/integration-card.a2ui.json +268 -0
  74. package/components/integration-card/integration-card.css +169 -0
  75. package/components/integration-card/integration-card.d.ts +63 -0
  76. package/components/integration-card/integration-card.examples.md +41 -0
  77. package/components/integration-card/integration-card.js +17 -0
  78. package/components/integration-card/integration-card.test.js +306 -0
  79. package/components/integration-card/integration-card.yaml +280 -0
  80. package/components/kbd/kbd.css +32 -32
  81. package/components/link/link.css +12 -12
  82. package/components/list/list.css +8 -8
  83. package/components/list-window/class.js +688 -0
  84. package/components/list-window/list-window.a2ui.json +277 -0
  85. package/components/list-window/list-window.css +124 -0
  86. package/components/list-window/list-window.d.ts +84 -0
  87. package/components/list-window/list-window.examples.md +73 -0
  88. package/components/list-window/list-window.js +17 -0
  89. package/components/list-window/list-window.test.js +303 -0
  90. package/components/list-window/list-window.yaml +270 -0
  91. package/components/menu/menu.css +8 -8
  92. package/components/modal/modal.css +43 -43
  93. package/components/nav/nav.css +40 -40
  94. package/components/nav-group/nav-group.css +52 -52
  95. package/components/nav-item/nav-item.css +44 -44
  96. package/components/noodles/noodles.css +31 -31
  97. package/components/option-card/option-card.css +69 -69
  98. package/components/otp-input/otp-input.css +30 -30
  99. package/components/page/page.css +18 -18
  100. package/components/pagination/pagination.css +61 -61
  101. package/components/pane/pane.css +57 -57
  102. package/components/pipeline-status/pipeline-status.css +65 -65
  103. package/components/popover/popover.css +17 -17
  104. package/components/progress/progress.css +23 -23
  105. package/components/progress-row/progress-row.css +17 -17
  106. package/components/radio/radio.css +39 -39
  107. package/components/range/range.css +55 -55
  108. package/components/rating/rating.css +28 -28
  109. package/components/richtext/richtext.css +133 -133
  110. package/components/row/row.css +19 -19
  111. package/components/search/search.css +5 -5
  112. package/components/segment/segment.css +24 -24
  113. package/components/segmented/segmented.css +25 -25
  114. package/components/select/select.css +84 -84
  115. package/components/skeleton/skeleton.css +14 -14
  116. package/components/slider/slider.css +46 -46
  117. package/components/spinner/class.js +69 -0
  118. package/components/spinner/spinner.a2ui.json +197 -0
  119. package/components/spinner/spinner.css +165 -0
  120. package/components/spinner/spinner.d.ts +26 -0
  121. package/components/spinner/spinner.examples.md +26 -0
  122. package/components/spinner/spinner.js +17 -0
  123. package/components/spinner/spinner.test.js +234 -0
  124. package/components/spinner/spinner.yaml +230 -0
  125. package/components/stack/stack.css +11 -11
  126. package/components/stat/stat.css +25 -25
  127. package/components/step-progress/step-progress.css +20 -20
  128. package/components/stepper/stepper.css +29 -29
  129. package/components/stream/stream.css +12 -12
  130. package/components/swatch/swatch.css +68 -68
  131. package/components/swiper/swiper.css +57 -57
  132. package/components/switch/switch.css +52 -52
  133. package/components/table/class.js +9 -0
  134. package/components/table/table.a2ui.json +1 -1
  135. package/components/table/table.css +162 -162
  136. package/components/table/table.d.ts +1 -1
  137. package/components/table/table.test.js +53 -0
  138. package/components/table/table.yaml +13 -1
  139. package/components/table-toolbar/table-toolbar.css +32 -32
  140. package/components/tabs/tabs.css +51 -51
  141. package/components/tag/tag.css +48 -48
  142. package/components/text/text.css +44 -44
  143. package/components/textarea/textarea.css +46 -46
  144. package/components/time-picker/class.js +693 -0
  145. package/components/time-picker/time-picker.a2ui.json +267 -0
  146. package/components/time-picker/time-picker.css +122 -0
  147. package/components/time-picker/time-picker.d.ts +75 -0
  148. package/components/time-picker/time-picker.examples.md +35 -0
  149. package/components/time-picker/time-picker.js +17 -0
  150. package/components/time-picker/time-picker.test.js +287 -0
  151. package/components/time-picker/time-picker.yaml +256 -0
  152. package/components/timeline/timeline.css +50 -50
  153. package/components/toast/toast.css +58 -58
  154. package/components/toggle-group/toggle-group.css +6 -6
  155. package/components/toggle-scheme/toggle-scheme.css +2 -2
  156. package/components/toolbar/toolbar.css +17 -17
  157. package/components/tooltip/tooltip.css +2 -2
  158. package/components/tree/tree.css +37 -37
  159. package/components/upload/upload.css +49 -49
  160. package/dist/icons-manifest.js +3 -3
  161. package/dist/web-components.min.css +1 -1
  162. package/dist/web-components.min.js +121 -83
  163. package/package.json +1 -1
  164. package/styles/components.css +8 -0
@@ -7,8 +7,8 @@
7
7
  Variables
8
8
  W = track width (100% of the track element)
9
9
  t = thumb width (full outer width including internal padding)
10
- h = track height (var(--slider-track-height))
11
- pad = internal padding on the thumb (var(--slider-thumb-padding))
10
+ h = track height (var(--slider-track-height, var(--slider-track-height-default)))
11
+ pad = internal padding on the thumb (var(--slider-thumb-padding, var(--slider-thumb-padding-default)))
12
12
  p = progress (0.0 → 1.0, written by JS as --slider-pct)
13
13
 
14
14
  The thumb consists of two layers:
@@ -44,8 +44,8 @@
44
44
  In CSS (percentages relative to W):
45
45
 
46
46
  left = calc(t/2 + p · (W − t))
47
- = calc(var(--slider-thumb-width) / 2
48
- + var(--slider-pct) * (100% - var(--slider-thumb-width)))
47
+ = calc(var(--slider-thumb-width, var(--slider-thumb-width-default)) / 2
48
+ + var(--slider-pct, var(--slider-pct-default)) * (100% - var(--slider-thumb-width, var(--slider-thumb-width-default))))
49
49
 
50
50
  The element is shifted back by 50% via transform so its
51
51
  geometric center lands on the computed point.
@@ -60,50 +60,50 @@
60
60
  ═══════════════════════════════════════════════════════════════ */
61
61
 
62
62
  /* ── Layout ── */
63
- --slider-gap: var(--a-space-1);
64
- --slider-readout-gap: var(--a-space-0-5);
65
- --slider-radius: var(--a-radius-full);
63
+ --slider-gap-default: var(--a-space-1);
64
+ --slider-readout-gap-default: var(--a-space-0-5);
65
+ --slider-radius-default: var(--a-radius-full);
66
66
 
67
67
  /* Track height scales with the universal [size] attribute
68
68
  via --a-toggle-size: 16 sm / 20 md / 24 lg at density=1.
69
69
  Override --slider-track-height directly for custom sizes. */
70
- --slider-track-height: var(--a-toggle-size);
70
+ --slider-track-height-default: var(--a-toggle-size);
71
71
 
72
72
  /* Thumb padding: internal breathing room on all sides.
73
73
  The visual pill sits pad px away from the container edge. */
74
- --slider-thumb-padding: 2px;
74
+ --slider-thumb-padding-default: 2px;
75
75
 
76
76
  /* Visual thumb height: track height minus top and bottom padding. */
77
- --slider-thumb-visual-h: calc(var(--slider-track-height) - 2 * var(--slider-thumb-padding));
77
+ --slider-thumb-visual-h-default: calc(var(--slider-track-height, var(--slider-track-height-default)) - 2 * var(--slider-thumb-padding, var(--slider-thumb-padding-default)));
78
78
 
79
79
  /* Visual thumb width: 2×1 pill ratio. */
80
- --slider-thumb-visual-w: calc(var(--slider-thumb-visual-h) * 2);
80
+ --slider-thumb-visual-w-default: calc(var(--slider-thumb-visual-h, var(--slider-thumb-visual-h-default)) * 2);
81
81
 
82
82
  /* Container / geometry thumb width: visual width plus left and
83
83
  right padding. This is the width used in the travel equation. */
84
- --slider-thumb-width: calc(var(--slider-thumb-visual-w) + 2 * var(--slider-thumb-padding));
84
+ --slider-thumb-width-default: calc(var(--slider-thumb-visual-w, var(--slider-thumb-visual-w-default)) + 2 * var(--slider-thumb-padding, var(--slider-thumb-padding-default)));
85
85
 
86
86
  /* Progress fraction (0.0 → 1.0) written by JS. */
87
- --slider-pct: 0;
87
+ --slider-pct-default: 0;
88
88
 
89
89
  /* ── Colors ──
90
90
  Track: dim recessed surface | Fill: primary | Thumb: white chrome */
91
- --slider-track-bg: var(--a-bg-muted);
92
- --slider-fill-bg: var(--a-primary-bg);
93
- --slider-thumb-bg: var(--a-chrome-light);
94
- --slider-fill-bg-disabled: var(--a-border-subtle);
95
- --slider-thumb-bg-disabled: var(--a-fg-muted);
91
+ --slider-track-bg-default: var(--a-bg-muted);
92
+ --slider-fill-bg-default: var(--a-primary-bg);
93
+ --slider-thumb-bg-default: var(--a-chrome-light);
94
+ --slider-fill-bg-disabled-default: var(--a-border-subtle);
95
+ --slider-thumb-bg-disabled-default: var(--a-fg-muted);
96
96
 
97
97
  /* ── Typography ── */
98
- --slider-font-size: var(--a-ui-size);
99
- --slider-value-weight: var(--a-weight-bold);
98
+ --slider-font-size-default: var(--a-ui-size);
99
+ --slider-value-weight-default: var(--a-weight-bold);
100
100
 
101
101
  /* ── Transition ── */
102
- --slider-duration: var(--a-duration-fast);
103
- --slider-easing: var(--a-easing);
102
+ --slider-duration-default: var(--a-duration-fast);
103
+ --slider-easing-default: var(--a-easing);
104
104
 
105
105
  /* ── Focus ── */
106
- --slider-focus-ring: var(--a-focus-ring);
106
+ --slider-focus-ring-default: var(--a-focus-ring);
107
107
  text-align: start; /* §text-align-reset — blocks inheritance from centered ancestors */
108
108
  }
109
109
 
@@ -112,7 +112,7 @@
112
112
  box-sizing: border-box;
113
113
  display: flex;
114
114
  flex-direction: column;
115
- gap: var(--slider-gap);
115
+ gap: var(--slider-gap, var(--slider-gap-default));
116
116
  }
117
117
 
118
118
  :scope[data-direction="row"] {
@@ -126,7 +126,7 @@
126
126
  display: flex;
127
127
  align-items: baseline;
128
128
  justify-content: space-between;
129
- font-size: var(--slider-font-size);
129
+ font-size: var(--slider-font-size, var(--slider-font-size-default));
130
130
  }
131
131
 
132
132
  [slot="label"] {
@@ -136,12 +136,12 @@
136
136
  [slot="readout"] {
137
137
  display: flex;
138
138
  align-items: baseline;
139
- gap: var(--slider-readout-gap);
139
+ gap: var(--slider-readout-gap, var(--slider-readout-gap-default));
140
140
  }
141
141
 
142
142
  [slot="value"] {
143
143
  color: var(--a-fg);
144
- font-weight: var(--slider-value-weight);
144
+ font-weight: var(--slider-value-weight, var(--slider-value-weight-default));
145
145
  font-variant-numeric: tabular-nums;
146
146
  }
147
147
 
@@ -152,9 +152,9 @@
152
152
  /* Track: the reference frame W for all geometry calculations. */
153
153
  [slot="track"] {
154
154
  position: relative;
155
- height: var(--slider-track-height);
156
- border-radius: var(--slider-radius);
157
- background: var(--slider-track-bg);
155
+ height: var(--slider-track-height, var(--slider-track-height-default));
156
+ border-radius: var(--slider-radius, var(--slider-radius-default));
157
+ background: var(--slider-track-bg, var(--slider-track-bg-default));
158
158
  cursor: pointer;
159
159
  touch-action: none;
160
160
  }
@@ -166,10 +166,10 @@
166
166
  left: 0;
167
167
  height: 100%;
168
168
  border-radius: inherit;
169
- background: var(--slider-fill-bg);
169
+ background: var(--slider-fill-bg, var(--slider-fill-bg-default));
170
170
  pointer-events: none;
171
- width: calc(var(--slider-thumb-width)
172
- + var(--slider-pct) * (100% - var(--slider-thumb-width)));
171
+ width: calc(var(--slider-thumb-width, var(--slider-thumb-width-default))
172
+ + var(--slider-pct, var(--slider-pct-default)) * (100% - var(--slider-thumb-width, var(--slider-thumb-width-default))));
173
173
  }
174
174
 
175
175
  /* Thumb CONTAINER: full track height, geometry width.
@@ -178,18 +178,18 @@
178
178
  [slot="thumb"] {
179
179
  position: absolute;
180
180
  top: 50%;
181
- left: calc(var(--slider-thumb-width) / 2
182
- + var(--slider-pct) * (100% - var(--slider-thumb-width)));
183
- width: var(--slider-thumb-width);
184
- height: var(--slider-track-height);
185
- border-radius: var(--slider-radius);
181
+ left: calc(var(--slider-thumb-width, var(--slider-thumb-width-default)) / 2
182
+ + var(--slider-pct, var(--slider-pct-default)) * (100% - var(--slider-thumb-width, var(--slider-thumb-width-default))));
183
+ width: var(--slider-thumb-width, var(--slider-thumb-width-default));
184
+ height: var(--slider-track-height, var(--slider-track-height-default));
185
+ border-radius: var(--slider-radius, var(--slider-radius-default));
186
186
  background: transparent;
187
187
  transform: translate(-50%, -50%);
188
188
  cursor: grab;
189
189
  touch-action: none;
190
190
  transition:
191
- left var(--slider-duration) var(--slider-easing),
192
- transform var(--slider-duration) var(--slider-easing);
191
+ left var(--slider-duration, var(--slider-duration-default)) var(--slider-easing, var(--slider-easing-default)),
192
+ transform var(--slider-duration, var(--slider-duration-default)) var(--slider-easing, var(--slider-easing-default));
193
193
  z-index: 1;
194
194
  }
195
195
 
@@ -200,15 +200,15 @@
200
200
  [slot="thumb"]::before {
201
201
  content: '';
202
202
  position: absolute;
203
- inset: var(--slider-thumb-padding);
203
+ inset: var(--slider-thumb-padding, var(--slider-thumb-padding-default));
204
204
  border-radius: inherit;
205
- background: var(--slider-thumb-bg);
205
+ background: var(--slider-thumb-bg, var(--slider-thumb-bg-default));
206
206
  }
207
207
 
208
208
  /* During drag: kill the left transition so the thumb follows
209
209
  the pointer frame-by-frame without CSS interpolation lag. */
210
210
  :scope[data-dragging] [slot="thumb"] {
211
- transition: transform var(--slider-duration) var(--slider-easing);
211
+ transition: transform var(--slider-duration, var(--slider-duration-default)) var(--slider-easing, var(--slider-easing-default));
212
212
  }
213
213
 
214
214
  [slot="thumb"]:hover {
@@ -221,13 +221,13 @@
221
221
  }
222
222
 
223
223
  :scope:focus-visible { outline: none; }
224
- :scope:focus-visible [slot="thumb"] { box-shadow: var(--slider-focus-ring); }
224
+ :scope:focus-visible [slot="thumb"] { box-shadow: var(--slider-focus-ring, var(--slider-focus-ring-default)); }
225
225
 
226
226
  /* Disabled */
227
227
  :scope[disabled] [slot="track"] { cursor: not-allowed; }
228
- :scope[disabled] [slot="fill"] { background: var(--slider-fill-bg-disabled); }
228
+ :scope[disabled] [slot="fill"] { background: var(--slider-fill-bg-disabled, var(--slider-fill-bg-disabled-default)); }
229
229
  :scope[disabled] [slot="thumb"]::before {
230
- background: var(--slider-thumb-bg-disabled);
230
+ background: var(--slider-thumb-bg-disabled, var(--slider-thumb-bg-disabled-default));
231
231
  }
232
232
 
233
233
  /* ── Hint (§184, v0.5.5, FEEDBACK-08 §7) ── */
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Non-side-effect class export for `<spinner-ui>`.
3
+ *
4
+ * Importing this file gives you the class without auto-registering the
5
+ * tag. Useful for test isolation, subclassing with tag-name override, or
6
+ * selective composition.
7
+ *
8
+ * The auto-register path stays at `@adia-ai/web-components/components/spinner`
9
+ * (which imports this file + calls `defineIfFree()`).
10
+ *
11
+ * @see ../../USAGE.md#registration--auto-vs-explicit
12
+ */
13
+
14
+ /**
15
+ * <spinner-ui size="md" variant="arc" tone="current" label="Loading"></spinner-ui>
16
+ * <spinner-ui variant="dots" tone="subtle" label="Assistant is typing"></spinner-ui>
17
+ * <spinner-ui paused size="lg"></spinner-ui>
18
+ *
19
+ * Circular animated indicator for INDETERMINATE loading. The visual is
20
+ * rendered entirely via `::before` (and per-variant peers) on the host;
21
+ * the component stamps no internal DOM (`static template = () => null`).
22
+ *
23
+ * Props (attributes):
24
+ * size — sm | md (default) | lg
25
+ * variant — arc (default) | ring | dots
26
+ * tone — current (default) | subtle | accent | inverse
27
+ * paused — freeze the animation in place (default: false)
28
+ * label — accessible operation name (default: "Loading")
29
+ *
30
+ * ARIA: role="progressbar" + aria-busy="true" + aria-valuetext=<label>.
31
+ * Reduced motion: when prefers-reduced-motion: reduce is active, the
32
+ * spinning animation is replaced with a static "…" ellipsis via CSS
33
+ * only — no JS observer required. WCAG 2.3.3.
34
+ *
35
+ * Lifecycle: pure-CSS animation. No timers, no observers. connected()
36
+ * sets the three ARIA attributes once; render() keeps aria-valuetext in
37
+ * sync with the [label] prop.
38
+ *
39
+ * @see SPEC-001 (docs/specs/implementation-ready/SPEC-001-spinner-loader.md)
40
+ */
41
+
42
+ import { UIElement } from '../../core/element.js';
43
+
44
+ export class UISpinner extends UIElement {
45
+ static properties = {
46
+ size: { type: String, default: 'md', reflect: true },
47
+ variant: { type: String, default: 'arc', reflect: true },
48
+ tone: { type: String, default: 'current', reflect: true },
49
+ paused: { type: Boolean, default: false, reflect: true },
50
+ label: { type: String, default: 'Loading', reflect: false },
51
+ };
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.
56
+ static template = () => null;
57
+
58
+ connected() {
59
+ // ARIA wiring — fixed for the lifetime of the element.
60
+ if (!this.hasAttribute('role')) this.setAttribute('role', 'progressbar');
61
+ if (!this.hasAttribute('aria-busy')) this.setAttribute('aria-busy', 'true');
62
+ this.setAttribute('aria-valuetext', this.label || 'Loading');
63
+ }
64
+
65
+ render() {
66
+ // Keep aria-valuetext in sync if `label` changes after connect.
67
+ this.setAttribute('aria-valuetext', this.label || 'Loading');
68
+ }
69
+ }
@@ -0,0 +1,197 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/Spinner.json",
4
+ "title": "Spinner",
5
+ "description": "Circular animated indicator for indeterminate loading. Renders a rotating arc, full ring, or three bouncing dots inside a sized box; the animation runs while the element is in the DOM and `[paused]` is unset. Fills the circular-spinner gap left by <skeleton-ui> (rectangular placeholder) and <progress-ui> (linear determinate bar) — use <spinner-ui> when the wait duration is unknown and the shape of the eventual content is irregular or the region is too small for a placeholder block.",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "component": {
17
+ "const": "Spinner"
18
+ },
19
+ "label": {
20
+ "description": "Accessible operation name surfaced via `aria-valuetext`. Override for context-specific labels (\"Saving\", \"Uploading\").",
21
+ "type": "string",
22
+ "default": "Loading"
23
+ },
24
+ "paused": {
25
+ "description": "Pause the animation in-place. Useful for screenshot tests and explicit-control flows.",
26
+ "type": "boolean",
27
+ "default": false
28
+ },
29
+ "size": {
30
+ "description": "Diameter — matches icon-ui's ladder (sm 14px, md 16px, lg 20px).",
31
+ "type": "string",
32
+ "enum": [
33
+ "sm",
34
+ "md",
35
+ "lg"
36
+ ],
37
+ "default": "md"
38
+ },
39
+ "tone": {
40
+ "description": "Color tone — `current` inherits parent text color (matches button label), `accent` uses brand accent, `subtle` is muted, `inverse` flips for on-accent surfaces.",
41
+ "type": "string",
42
+ "enum": [
43
+ "current",
44
+ "accent",
45
+ "subtle",
46
+ "inverse"
47
+ ],
48
+ "default": "current"
49
+ },
50
+ "variant": {
51
+ "description": "Visual flavor — arc (rotating quarter-circle), ring (full ring with one colored segment), dots (three bouncing dots).",
52
+ "type": "string",
53
+ "enum": [
54
+ "arc",
55
+ "ring",
56
+ "dots"
57
+ ],
58
+ "default": "arc"
59
+ }
60
+ },
61
+ "required": [
62
+ "component"
63
+ ],
64
+ "unevaluatedProperties": false,
65
+ "x-adiaui": {
66
+ "anti_patterns": [
67
+ {
68
+ "fix": "{\"component\": \"Progress\", \"value\": 42, \"max\": 100}\n",
69
+ "why": "Spinner is INDETERMINATE only. `value` and any quantitative\nprogress field belongs on Progress, not Spinner. Also \"Spin\" is\nnot a valid operation label.\n",
70
+ "wrong": "{\"component\": \"Spinner\", \"label\": \"Spin\", \"value\": 0.42}\n"
71
+ },
72
+ {
73
+ "fix": "{\"component\": \"Spinner\", \"size\": \"md\"}\n",
74
+ "why": "Skeleton is a placeholder; rotation is not part of its contract.\nA rotating circle is a Spinner.\n",
75
+ "wrong": "{\"component\": \"Skeleton\", \"variant\": \"circle\", \"animation\": \"rotate\"}\n"
76
+ },
77
+ {
78
+ "fix": "{\"component\": \"Card\", \"children\": [\n {\"component\": \"Spinner\", \"size\": \"lg\"}\n]}\n",
79
+ "why": "Multiple sibling spinners in one region produce visual noise\nwithout extra information. Use one parent-level Spinner.\n",
80
+ "wrong": "{\"component\": \"Card\", \"children\": [\n {\"component\": \"Spinner\"},\n {\"component\": \"Spinner\"},\n {\"component\": \"Spinner\"}\n]}\n"
81
+ }
82
+ ],
83
+ "category": "feedback",
84
+ "composes": [],
85
+ "events": {},
86
+ "examples": [
87
+ {
88
+ "description": "Loading button — primary action with a saving spinner. The button is disabled while the operation is in progress; the spinner matches the button label color via tone=\"current\".",
89
+ "a2ui": "[\n {\n \"id\": \"btn-save\",\n \"component\": \"Button\",\n \"text\": \"Saving\",\n \"variant\": \"primary\",\n \"disabled\": true,\n \"children\": [\"sp-1\"]\n },\n {\n \"id\": \"sp-1\",\n \"component\": \"Spinner\",\n \"size\": \"sm\",\n \"tone\": \"current\",\n \"label\": \"Saving\"\n }\n]",
90
+ "name": "button-saving"
91
+ },
92
+ {
93
+ "description": "Standalone centered spinner inside a card while body content fetches.",
94
+ "a2ui": "[\n {\n \"id\": \"card\",\n \"component\": \"Card\",\n \"children\": [\"row\"]\n },\n {\n \"id\": \"row\",\n \"component\": \"Row\",\n \"justify\": \"center\",\n \"align\": \"center\",\n \"children\": [\"sp\"]\n },\n {\n \"id\": \"sp\",\n \"component\": \"Spinner\",\n \"size\": \"lg\",\n \"tone\": \"subtle\",\n \"label\": \"Loading dashboard\"\n }\n]",
95
+ "name": "centered-card-loading"
96
+ },
97
+ {
98
+ "description": "Three bouncing dots — good for chat / typing indicators.",
99
+ "a2ui": "[\n {\n \"id\": \"sp\",\n \"component\": \"Spinner\",\n \"variant\": \"dots\",\n \"tone\": \"subtle\",\n \"label\": \"Assistant is typing\"\n }\n]",
100
+ "name": "typing-indicator"
101
+ }
102
+ ],
103
+ "keywords": [
104
+ "spinner",
105
+ "loader",
106
+ "loading",
107
+ "indeterminate",
108
+ "progress",
109
+ "busy",
110
+ "feedback",
111
+ "circular"
112
+ ],
113
+ "name": "UISpinner",
114
+ "related": [
115
+ "Progress",
116
+ "Skeleton",
117
+ "Button",
118
+ "EmptyState"
119
+ ],
120
+ "slots": {},
121
+ "states": [
122
+ {
123
+ "description": "Default; animation active.",
124
+ "name": "running"
125
+ },
126
+ {
127
+ "description": "Animation frozen at the current frame.",
128
+ "attribute": "paused",
129
+ "name": "paused"
130
+ },
131
+ {
132
+ "description": "Triggered by prefers-reduced-motion. Animation replaced with a static ellipsis. Detected via CSS, not JS.",
133
+ "name": "reduced"
134
+ }
135
+ ],
136
+ "status": "stable",
137
+ "synonyms": {
138
+ "busy": [
139
+ "spinner",
140
+ "loading"
141
+ ],
142
+ "indeterminate": [
143
+ "spinner",
144
+ "progress"
145
+ ],
146
+ "loader": [
147
+ "spinner",
148
+ "loading",
149
+ "progress"
150
+ ],
151
+ "loading": [
152
+ "spinner",
153
+ "loading",
154
+ "progress",
155
+ "skeleton"
156
+ ],
157
+ "saving": [
158
+ "spinner",
159
+ "loading"
160
+ ],
161
+ "spinner": [
162
+ "spinner",
163
+ "loader",
164
+ "progress"
165
+ ],
166
+ "uploading": [
167
+ "spinner",
168
+ "loading"
169
+ ]
170
+ },
171
+ "tag": "spinner-ui",
172
+ "tokens": {
173
+ "--spinner-color": {
174
+ "description": "Color of the active arc / ring / dots. Defaults to currentColor so a tone-driven cascade resolves naturally.",
175
+ "default": "currentColor"
176
+ },
177
+ "--spinner-duration": {
178
+ "description": "One full rotation duration.",
179
+ "default": "var(--a-duration-slow)"
180
+ },
181
+ "--spinner-size": {
182
+ "description": "Diameter of the spinner box.",
183
+ "default": "1rem"
184
+ },
185
+ "--spinner-stroke": {
186
+ "description": "Border thickness for the arc / ring variants.",
187
+ "default": "2px"
188
+ },
189
+ "--spinner-track-opacity": {
190
+ "description": "Opacity for the non-rotating ring track (variant=ring only).",
191
+ "default": "0.25"
192
+ }
193
+ },
194
+ "traits": [],
195
+ "version": 1
196
+ }
197
+ }
@@ -0,0 +1,165 @@
1
+ @scope (spinner-ui) {
2
+ /* ── Block 1 — TOKENS ─────────────────────────────────────────────
3
+ Spinner inherits the icon ladder for sizing (matches icon-ui's
4
+ `:scope[size="..."]` rems). The semantic colors come via `tone=…`
5
+ re-assigning --spinner-color. Stroke is 2px (raw px allowed for
6
+ borders 1-2px per component-token-contract §"Forbidden patterns"). */
7
+ :where(:scope) {
8
+ --spinner-size-default: 1rem; /* icon-ui md */
9
+ --spinner-color-default: currentColor;
10
+ --spinner-stroke-default: 2px;
11
+ --spinner-duration-default: var(--a-duration-slow);
12
+ --spinner-track-opacity-default: 0.25;
13
+ --spinner-dot-size-default: calc(var(--spinner-size, var(--spinner-size-default)) / 4);
14
+ --spinner-dot-gap-default: calc(var(--spinner-size, var(--spinner-size-default)) / 8);
15
+ }
16
+
17
+ /* ── Block 2 — BASE ────────────────────────────────────────────── */
18
+ :scope {
19
+ box-sizing: border-box;
20
+ display: inline-block;
21
+ width: var(--spinner-size, var(--spinner-size-default));
22
+ height: var(--spinner-size, var(--spinner-size-default));
23
+ color: var(--spinner-color, var(--spinner-color-default));
24
+ vertical-align: middle;
25
+ flex-shrink: 0;
26
+ }
27
+
28
+ /* ── Size ladder (mirrors icon-ui's local rem declarations) ─────── */
29
+ :scope[size="sm"] { --spinner-size-default: 0.875rem; } /* 14px */
30
+ :scope[size="md"] { --spinner-size-default: 1rem; } /* 16px */
31
+ :scope[size="lg"] { --spinner-size-default: 1.25rem; } /* 20px */
32
+
33
+ /* ── Tones (override --spinner-color token) ────────────────────── */
34
+ :scope[tone="current"] { --spinner-color-default: currentColor; }
35
+ :scope[tone="subtle"] { --spinner-color-default: var(--a-fg-subtle); }
36
+ :scope[tone="accent"] { --spinner-color-default: var(--a-accent-strong); }
37
+ :scope[tone="inverse"] { --spinner-color-default: var(--a-chrome-light); }
38
+
39
+ /* ── Variant: ARC (default) — rotating quarter-circle border ───── */
40
+ :scope[variant="arc"]::before,
41
+ :scope:not([variant])::before {
42
+ content: "";
43
+ box-sizing: border-box;
44
+ display: block;
45
+ width: 100%;
46
+ height: 100%;
47
+ border-radius: 50%;
48
+ border: var(--spinner-stroke, var(--spinner-stroke-default)) solid;
49
+ border-color: currentColor transparent transparent transparent;
50
+ animation: spinner-ui-rotate var(--spinner-duration, var(--spinner-duration-default)) linear infinite;
51
+ }
52
+
53
+ /* ── Variant: RING — full ring with one rotating colored segment ── */
54
+ :scope[variant="ring"]::before {
55
+ content: "";
56
+ box-sizing: border-box;
57
+ display: block;
58
+ width: 100%;
59
+ height: 100%;
60
+ border-radius: 50%;
61
+ border: var(--spinner-stroke, var(--spinner-stroke-default)) solid;
62
+ border-color: color-mix(in oklch, currentColor calc(var(--spinner-track-opacity, var(--spinner-track-opacity-default)) * 100%), transparent);
63
+ border-top-color: currentColor;
64
+ animation: spinner-ui-rotate var(--spinner-duration, var(--spinner-duration-default)) linear infinite;
65
+ }
66
+
67
+ /* ── 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).
77
+
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. */
82
+ :scope[variant="dots"] {
83
+ display: inline-flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ width: auto; /* dots row is wider than tall */
87
+ min-width: var(--spinner-size, var(--spinner-size-default));
88
+ gap: var(--spinner-dot-gap, var(--spinner-dot-gap-default));
89
+ }
90
+
91
+ :scope[variant="dots"]::before,
92
+ :scope[variant="dots"]::after {
93
+ content: "";
94
+ display: block;
95
+ width: var(--spinner-dot-size, var(--spinner-dot-size-default));
96
+ height: var(--spinner-dot-size, var(--spinner-dot-size-default));
97
+ border-radius: 50%;
98
+ background: currentColor;
99
+ animation: spinner-ui-bounce var(--spinner-duration, var(--spinner-duration-default)) ease-in-out infinite;
100
+ }
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;
111
+ }
112
+
113
+ :scope[variant="dots"]::after {
114
+ animation-delay: calc(var(--spinner-duration, var(--spinner-duration-default)) / 3);
115
+ }
116
+
117
+ /* ── Paused state — freeze in place ────────────────────────────── */
118
+ :scope[paused]::before,
119
+ :scope[paused]::after {
120
+ animation-play-state: paused;
121
+ }
122
+
123
+ /* ── Reduced-motion replacement: static "…" ellipsis ────────────
124
+ WCAG 2.3.3 — animations of any duration are a vestibular risk.
125
+ The static replacement still announces aria-busy="true" via the
126
+ JS lifecycle. */
127
+ @media (prefers-reduced-motion: reduce) {
128
+ :scope::before,
129
+ :scope::after {
130
+ animation: none !important;
131
+ }
132
+ :scope::before {
133
+ content: "…";
134
+ border: none;
135
+ background: transparent;
136
+ box-shadow: none;
137
+ margin: 0;
138
+ width: 100%;
139
+ height: 100%;
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ font: inherit;
144
+ line-height: 1;
145
+ text-align: center;
146
+ color: currentColor;
147
+ }
148
+ :scope[variant="dots"]::after {
149
+ display: none;
150
+ }
151
+ :scope[variant="dots"] {
152
+ width: var(--spinner-size, var(--spinner-size-default)); /* shrink back to a square in reduced mode */
153
+ }
154
+ }
155
+
156
+ /* ── Keyframes ─────────────────────────────────────────────────── */
157
+ @keyframes spinner-ui-rotate {
158
+ to { transform: rotate(360deg); }
159
+ }
160
+
161
+ @keyframes spinner-ui-bounce {
162
+ 0%, 80%, 100% { transform: scale(0.6); opacity: 0.6; }
163
+ 40% { transform: scale(1); opacity: 1; }
164
+ }
165
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `<spinner-ui>` — Circular animated indicator for indeterminate loading. Renders a rotating arc, full ring, or three bouncing dots inside a sized box; the animation runs while the element is in the DOM and `[paused]` is unset. Fills the circular-spinner gap left by <skeleton-ui> (rectangular placeholder) and <progress-ui> (linear determinate bar) — use <spinner-ui> when the wait duration is unknown and the shape of the eventual content is irregular or the region is too small for a placeholder block.
3
+ *
4
+ * @see https://ui-kit.exe.xyz/site/components/spinner
5
+ *
6
+ * Type declarations generated by scripts/build/dts-codegen.mjs from
7
+ * the component's `.a2ui.json` sidecar(s). Edit the source `.yaml`,
8
+ * run `npm run build:components`, then `npm run codegen:dts` to
9
+ * regenerate; or hand-author this file fully if rich event types are
10
+ * needed beyond what the yaml `events:` block can express.
11
+ */
12
+
13
+ import { UIElement } from '../../core/element.js';
14
+
15
+ export class UISpinner extends UIElement {
16
+ /** Accessible operation name surfaced via `aria-valuetext`. Override for context-specific labels ("Saving", "Uploading"). */
17
+ label: string;
18
+ /** Pause the animation in-place. Useful for screenshot tests and explicit-control flows. */
19
+ paused: boolean;
20
+ /** Diameter — matches icon-ui's ladder (sm 14px, md 16px, lg 20px). */
21
+ size: 'sm' | 'md' | 'lg';
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
+ 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';
26
+ }