@adia-ai/web-components 0.5.14 → 0.5.16

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.
@@ -63,6 +63,7 @@ export class UISlider extends UIFormElement {
63
63
  #trackEl = null;
64
64
  #thumbEl = null;
65
65
  #dragging = false;
66
+ #dragOffset = 0;
66
67
 
67
68
  get #pct() {
68
69
  const range = this.max - this.min;
@@ -140,9 +141,10 @@ export class UISlider extends UIFormElement {
140
141
  }
141
142
 
142
143
  const pct = this.#pct;
143
- const fill = this.querySelector('[slot="fill"]');
144
- if (fill) fill.style.width = `${pct}%`;
145
- if (this.#thumbEl) this.#thumbEl.style.left = `${pct}%`;
144
+ // Write progress to CSS custom property for pure-CSS positioning.
145
+ // --slider-pct is a fraction (0.0–1.0) used in calc() alongside
146
+ // --slider-travel so thumb + fill stay inside the track.
147
+ this.style.setProperty('--slider-pct', String(pct / 100));
146
148
 
147
149
  const valueEl = this.querySelector('[slot="value"]');
148
150
  if (valueEl) valueEl.textContent = this.#format(this.value);
@@ -152,9 +154,34 @@ export class UISlider extends UIFormElement {
152
154
  this.syncValue(String(this.value));
153
155
  }
154
156
 
157
+ /**
158
+ * Inverse geometry: given a *desired* thumb-center viewport-x, compute
159
+ * the slider value such that the thumb center lands exactly at that
160
+ * coordinate (clamped at the min/max extremes).
161
+ *
162
+ * Forward geometry (in slider.css):
163
+ * thumb_center(p) = t/2 + p · (W − t)
164
+ *
165
+ * Inverse (solve for p, clamped):
166
+ * p = clamp01((target − t/2) / (W − t))
167
+ *
168
+ * Two call paths share this:
169
+ * • #onTrackClick — clientX is the click position; thumb center lands
170
+ * under the cursor (or snaps to the t/2 end-zone when clicked beyond).
171
+ * • #onPointerMove — (clientX − dragOffset) is the *intended* thumb
172
+ * center (offset preserves where the user originally grabbed the
173
+ * thumb, so dragging feels relative rather than snap-to-cursor).
174
+ */
155
175
  #valueFromX(clientX) {
156
- const rect = this.#trackEl.getBoundingClientRect();
157
- const ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
176
+ if (!this.#trackEl || !this.#thumbEl) return this.min;
177
+ const trackRect = this.#trackEl.getBoundingClientRect();
178
+ const thumbRect = this.#thumbEl.getBoundingClientRect();
179
+ const W = trackRect.width;
180
+ const t = thumbRect.width;
181
+ const travel = W - t;
182
+ if (travel <= 0) return this.min;
183
+ const target = clientX - trackRect.left; // desired thumb-center, track-relative
184
+ const ratio = Math.max(0, Math.min(1, (target - t / 2) / travel));
158
185
  const raw = this.min + ratio * (this.max - this.min);
159
186
  return this.#snap(raw);
160
187
  }
@@ -178,6 +205,28 @@ export class UISlider extends UIFormElement {
178
205
  if (this.disabled) return;
179
206
  e.preventDefault();
180
207
  this.#dragging = true;
208
+ this.setAttribute('data-dragging', '');
209
+ // Capture the offset between the click point and the thumb's
210
+ // *logical* center (derived from `this.value` via the same forward
211
+ // equation the CSS uses). We deliberately do NOT read the thumb's
212
+ // bounding rect here — the CSS `left` transition can leave the
213
+ // physical rect mid-animation between values, producing a stale
214
+ // offset that would translate into a snap on the first move.
215
+ // Logical center is transition-immune and matches what #valueFromX
216
+ // inverts.
217
+ if (this.#trackEl && this.#thumbEl) {
218
+ const trackRect = this.#trackEl.getBoundingClientRect();
219
+ const thumbRect = this.#thumbEl.getBoundingClientRect();
220
+ const W = trackRect.width;
221
+ const t = thumbRect.width;
222
+ const travel = W - t;
223
+ const range = this.max - this.min;
224
+ const p = range > 0 ? (this.value - this.min) / range : 0;
225
+ const logicalCenter = trackRect.left + t / 2 + p * travel;
226
+ this.#dragOffset = e.clientX - logicalCenter;
227
+ } else {
228
+ this.#dragOffset = 0;
229
+ }
181
230
  this.#thumbEl.setPointerCapture(e.pointerId);
182
231
  this.#thumbEl.addEventListener('pointermove', this.#onPointerMove);
183
232
  this.#thumbEl.addEventListener('pointerup', this.#onPointerUp);
@@ -185,11 +234,16 @@ export class UISlider extends UIFormElement {
185
234
 
186
235
  #onPointerMove = (e) => {
187
236
  if (!this.#dragging) return;
188
- this.#setValue(this.#valueFromX(e.clientX));
237
+ // Subtract the captured offset so the thumb center tracks the
238
+ // cursor relative to where the user originally pressed, avoiding
239
+ // the initial snap.
240
+ this.#setValue(this.#valueFromX(e.clientX - this.#dragOffset));
189
241
  };
190
242
 
191
243
  #onPointerUp = (e) => {
192
244
  this.#dragging = false;
245
+ this.#dragOffset = 0;
246
+ this.removeAttribute('data-dragging');
193
247
  this.#thumbEl.releasePointerCapture(e.pointerId);
194
248
  this.#thumbEl.removeEventListener('pointermove', this.#onPointerMove);
195
249
  this.#thumbEl.removeEventListener('pointerup', this.#onPointerUp);
@@ -153,13 +153,19 @@
153
153
  "tag": "slider-ui",
154
154
  "tokens": {
155
155
  "--slider-fill": {
156
- "description": "Filled track / thumb color"
156
+ "description": "Filled portion / progress color (was mixed accent, now primary)"
157
157
  },
158
- "--slider-thumb-size": {
159
- "description": "Thumb diameter"
158
+ "--slider-thumb-height": {
159
+ "description": "Thumb pill height (track-height − 2× inset)"
160
+ },
161
+ "--slider-thumb-width": {
162
+ "description": "Thumb pill width (2× thumb-height, driven by track-height)"
160
163
  },
161
164
  "--slider-track": {
162
- "description": "Track background color"
165
+ "description": "Unfilled track background color"
166
+ },
167
+ "--slider-track-height": {
168
+ "description": "Full track height (scales via universal [size] attribute)"
163
169
  }
164
170
  },
165
171
  "traits": [],
@@ -1,24 +1,98 @@
1
1
  @scope (slider-ui) {
2
2
  :where(:scope) {
3
- /* ── Layout ── (sizes scale with universal [size] attribute) */
3
+ /* ═══════════════════════════════════════════════════════════════
4
+ SLIDER GEOMETRY — Mathematical foundation
5
+ ═══════════════════════════════════════════════════════════════
6
+
7
+ Variables
8
+ W = track width (100% of the track element)
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))
12
+ p = progress (0.0 → 1.0, written by JS as --slider-pct)
13
+
14
+ The thumb consists of two layers:
15
+ • Container: the geometry element that the fill aligns to.
16
+ Sized to t × h. Provides the full-height grab area.
17
+ • Visual: the white pill rendered inside the container via
18
+ ::before. Sized to (t−2·pad) × (h−2·pad), centered
19
+ by inset = pad on all sides.
20
+
21
+ ── Progressive fill ──
22
+ The blue fill starts at the thumb's full outer width and
23
+ grows incrementally:
24
+
25
+ fill_width(p) = t + p · (W − t) (1)
26
+
27
+ At p = 0 → fill = t (entire thumb container is blue)
28
+ At p = 1 → fill = W (blue spans the full track)
29
+
30
+ The fill is anchored at the left track edge (left: 0), so:
31
+
32
+ fill_right(p) = t + p · (W − t) (2)
33
+
34
+ ── Thumb travel ──
35
+ The thumb container's center moves across the travel zone:
36
+
37
+ travel = W − t (3)
38
+
39
+ thumb_center(p) = t/2 + p · (W − t) (4)
40
+
41
+ At p = 0 → center at t/2 (container left flush, visual inset by pad)
42
+ At p = 1 → center at W−t/2 (container right flush, visual inset by pad)
43
+
44
+ In CSS (percentages relative to W):
45
+
46
+ left = calc(t/2 + p · (W − t))
47
+ = calc(var(--slider-thumb-width) / 2
48
+ + var(--slider-pct) * (100% - var(--slider-thumb-width)))
49
+
50
+ The element is shifted back by 50% via transform so its
51
+ geometric center lands on the computed point.
52
+
53
+ ── Thumb visual inset ──
54
+ The white pill is rendered by ::before with:
55
+ inset: pad (applied on all four sides)
56
+
57
+ This means the visual pill is inset from the container edge
58
+ by pad pixels, giving breathing room between the white surface
59
+ and the track boundary at both extremes.
60
+ ═══════════════════════════════════════════════════════════════ */
61
+
62
+ /* ── Layout ── */
4
63
  --slider-gap: var(--a-space-1);
5
64
  --slider-readout-gap: var(--a-space-0-5);
6
65
  --slider-radius: var(--a-radius-full);
7
- --slider-track-height: calc(var(--a-size) * 0.125);
8
- --slider-track-area-height: calc(var(--a-size) * 0.75);
9
- --slider-thumb-size: calc(var(--a-size) * 0.375);
10
66
 
11
- /* ── Colors ── */
67
+ /* Track height scales with the universal [size] attribute
68
+ via --a-toggle-size: 16 sm / 20 md / 24 lg at density=1.
69
+ Override --slider-track-height directly for custom sizes. */
70
+ --slider-track-height: var(--a-toggle-size);
71
+
72
+ /* Thumb padding: internal breathing room on all sides.
73
+ The visual pill sits pad px away from the container edge. */
74
+ --slider-thumb-padding: 2px;
75
+
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));
78
+
79
+ /* Visual thumb width: 2×1 pill ratio. */
80
+ --slider-thumb-visual-w: calc(var(--slider-thumb-visual-h) * 2);
81
+
82
+ /* Container / geometry thumb width: visual width plus left and
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));
85
+
86
+ /* Progress fraction (0.0 → 1.0) written by JS. */
87
+ --slider-pct: 0;
88
+
89
+ /* ── Colors ──
90
+ Track: dim recessed surface | Fill: primary | Thumb: white chrome */
12
91
  --slider-track-bg: var(--a-bg-muted);
13
- --slider-fill-bg: var(--a-accent-bg);
14
- --slider-thumb-bg: var(--a-accent-bg);
15
- --slider-thumb-border: var(--a-bg); /* intentional: cutout ring must match page bg */
16
- --slider-thumb-hover-ring: 0 0 0 4px var(--a-bg-muted);
92
+ --slider-fill-bg: var(--a-primary-bg);
93
+ --slider-thumb-bg: var(--a-chrome-light);
17
94
  --slider-fill-bg-disabled: var(--a-border-subtle);
18
95
  --slider-thumb-bg-disabled: var(--a-fg-muted);
19
- --slider-label-fg: var(--a-fg-muted);
20
- --slider-value-fg: var(--a-fg);
21
- --slider-suffix-fg: var(--a-fg-muted);
22
96
 
23
97
  /* ── Typography ── */
24
98
  --slider-font-size: var(--a-ui-size);
@@ -28,7 +102,7 @@
28
102
  --slider-duration: var(--a-duration-fast);
29
103
  --slider-easing: var(--a-easing);
30
104
 
31
- /* ── State ── */
105
+ /* ── Focus ── */
32
106
  --slider-focus-ring: var(--a-focus-ring);
33
107
  }
34
108
 
@@ -55,7 +129,7 @@
55
129
  }
56
130
 
57
131
  [slot="label"] {
58
- color: var(--slider-label-fg);
132
+ color: var(--a-fg-subtle);
59
133
  }
60
134
 
61
135
  [slot="readout"] {
@@ -65,86 +139,102 @@
65
139
  }
66
140
 
67
141
  [slot="value"] {
68
- color: var(--slider-value-fg);
142
+ color: var(--a-fg);
69
143
  font-weight: var(--slider-value-weight);
70
144
  font-variant-numeric: tabular-nums;
71
145
  }
72
146
 
73
147
  [slot="suffix"] {
74
- color: var(--slider-suffix-fg);
148
+ color: var(--a-fg-muted);
75
149
  }
76
150
 
77
- /* Track */
151
+ /* Track: the reference frame W for all geometry calculations. */
78
152
  [slot="track"] {
79
153
  position: relative;
80
- height: var(--slider-track-area-height);
81
- display: flex;
82
- align-items: center;
154
+ height: var(--slider-track-height);
155
+ border-radius: var(--slider-radius);
156
+ background: var(--slider-track-bg);
83
157
  cursor: pointer;
84
158
  touch-action: none;
85
159
  }
86
160
 
161
+ /* Fill — equation (1): width = t + p·(W−t) */
87
162
  [slot="fill"] {
88
163
  position: absolute;
164
+ top: 0;
89
165
  left: 0;
90
- height: var(--slider-track-height);
91
- border-radius: var(--slider-radius);
166
+ height: 100%;
167
+ border-radius: inherit;
92
168
  background: var(--slider-fill-bg);
93
169
  pointer-events: none;
170
+ width: calc(var(--slider-thumb-width)
171
+ + var(--slider-pct) * (100% - var(--slider-thumb-width)));
94
172
  }
95
173
 
96
- [slot="track"]::before {
97
- content: '';
98
- position: absolute;
99
- left: 0;
100
- right: 0;
101
- height: var(--slider-track-height);
102
- border-radius: var(--slider-radius);
103
- background: var(--slider-track-bg);
104
- }
105
-
106
- /* Thumb */
174
+ /* Thumb CONTAINER: full track height, geometry width.
175
+ Transparent background; the white pill is rendered by ::before.
176
+ This element provides a generous vertical grab area. */
107
177
  [slot="thumb"] {
108
178
  position: absolute;
109
- width: var(--slider-thumb-size);
110
- height: var(--slider-thumb-size);
179
+ top: 50%;
180
+ left: calc(var(--slider-thumb-width) / 2
181
+ + var(--slider-pct) * (100% - var(--slider-thumb-width)));
182
+ width: var(--slider-thumb-width);
183
+ height: var(--slider-track-height);
111
184
  border-radius: var(--slider-radius);
112
- background: var(--slider-thumb-bg);
113
- border: 2px solid var(--slider-thumb-border);
114
- transform: translateX(-50%);
185
+ background: transparent;
186
+ transform: translate(-50%, -50%);
115
187
  cursor: grab;
116
188
  touch-action: none;
117
189
  transition:
118
- transform var(--slider-duration) var(--slider-easing),
119
- box-shadow var(--slider-duration) var(--slider-easing);
190
+ left var(--slider-duration) var(--slider-easing),
191
+ transform var(--slider-duration) var(--slider-easing);
120
192
  z-index: 1;
121
193
  }
194
+
195
+ /* Thumb VISUAL: white pill rendered inside the transparent
196
+ container via ::before. Inset = padding on all four sides,
197
+ so the pill is vertically and horizontally inset from the
198
+ container edge by pad pixels. */
199
+ [slot="thumb"]::before {
200
+ content: '';
201
+ position: absolute;
202
+ inset: var(--slider-thumb-padding);
203
+ border-radius: inherit;
204
+ background: var(--slider-thumb-bg);
205
+ }
206
+
207
+ /* During drag: kill the left transition so the thumb follows
208
+ the pointer frame-by-frame without CSS interpolation lag. */
209
+ :scope[data-dragging] [slot="thumb"] {
210
+ transition: transform var(--slider-duration) var(--slider-easing);
211
+ }
212
+
122
213
  [slot="thumb"]:hover {
123
- box-shadow: var(--slider-thumb-hover-ring);
214
+ transform: translate(-50%, -50%) scale(1.05);
124
215
  }
216
+
125
217
  [slot="thumb"]:active {
126
- transform: translateX(-50%) scale(1.15);
218
+ transform: translate(-50%, -50%) scale(1.1);
127
219
  cursor: grabbing;
128
220
  }
221
+
129
222
  :scope:focus-visible { outline: none; }
130
223
  :scope:focus-visible [slot="thumb"] { box-shadow: var(--slider-focus-ring); }
131
224
 
132
225
  /* Disabled */
133
226
  :scope[disabled] [slot="track"] { cursor: not-allowed; }
134
- :scope[disabled] [slot="fill"] { background: var(--slider-fill-bg-disabled); }
135
- :scope[disabled] [slot="thumb"] { cursor: not-allowed; background: var(--slider-thumb-bg-disabled); }
136
- :scope[disabled] [slot="thumb"]:hover { box-shadow: none; }
137
-
138
- /* ── Hint (§184, v0.5.5, FEEDBACK-08 §7) ──
139
- Small caption rendered beneath the track. aria-describedby is
140
- wired on the host in class.js so screen readers announce this
141
- as a description (distinct from aria-label, which comes from
142
- [label]). Uses the same muted typography as field-ui's hint. */
227
+ :scope[disabled] [slot="fill"] { background: var(--slider-fill-bg-disabled); }
228
+ :scope[disabled] [slot="thumb"]::before {
229
+ background: var(--slider-thumb-bg-disabled);
230
+ }
231
+
232
+ /* ── Hint (§184, v0.5.5, FEEDBACK-08 §7) ── */
143
233
  [slot="hint"] {
144
234
  display: block;
145
235
  margin-top: var(--slider-hint-mt, var(--a-space-1));
146
236
  font-size: var(--slider-hint-size, var(--a-ui-xs));
147
- color: var(--slider-hint-fg, var(--a-fg-muted));
237
+ color: var(--slider-hint-fg, var(--a-fg-muted));
148
238
  line-height: var(--slider-hint-lh, 1.4);
149
239
  }
150
240
  }
@@ -14,43 +14,46 @@ function mount(html) {
14
14
  describe('slider-ui', () => {
15
15
  beforeEach(() => { document.body.innerHTML = ''; });
16
16
 
17
- it('renders thumb at correct % for initial value', async () => {
17
+ // §330 (v0.5.16) slider iOS-style pill redesign. Thumb position no
18
+ // longer set via thumb.style.left; instead the host gets a CSS custom
19
+ // property `--slider-pct` (fraction 0.0–1.0) and CSS calc() composes
20
+ // the full geometry: `fill_width(p) = t + p·(W−t)`, where t = thumb
21
+ // width and W = track width. Test asserts the CSS variable carries
22
+ // the expected fraction (50% → 0.5).
23
+ const pct = (s) => s.style.getPropertyValue('--slider-pct').trim();
24
+
25
+ it('renders host --slider-pct for initial value', async () => {
18
26
  const s = mount('<slider-ui value="50" min="0" max="100"></slider-ui>');
19
27
  await tick();
20
- const thumb = s.querySelector('[slot="thumb"]');
21
- expect(thumb.style.left).toBe('50%');
28
+ expect(pct(s)).toBe('0.5');
22
29
  });
23
30
 
24
31
  // Property reactivity contract — locks in the behavior that consumer
25
32
  // feedback (FEEDBACK-adia-packages.md §5, 2026-05-12) incorrectly
26
33
  // claimed was broken. UIElement's signal-backed property setters
27
- // (core/element.js:31-50) trigger the host's render effect on every
28
- // property change; slider-ui's render() reads this.value and updates
29
- // [slot="thumb"].style.left. This test catches any regression that
30
- // would break undo/redo or any other programmatic-value flow.
31
- it('moves thumb when .value is set programmatically', async () => {
34
+ // trigger the host's render effect on every property change; slider-ui's
35
+ // render() reads this.value and writes --slider-pct on the host.
36
+ it('updates --slider-pct when .value is set programmatically', async () => {
32
37
  const s = mount('<slider-ui value="50" min="0" max="100"></slider-ui>');
33
38
  await tick();
34
- const thumb = s.querySelector('[slot="thumb"]');
35
- expect(thumb.style.left).toBe('50%');
39
+ expect(pct(s)).toBe('0.5');
36
40
 
37
41
  s.value = 75;
38
42
  await tick();
39
- expect(thumb.style.left).toBe('75%');
43
+ expect(pct(s)).toBe('0.75');
40
44
 
41
45
  s.value = 10;
42
46
  await tick();
43
- expect(thumb.style.left).toBe('10%');
47
+ expect(pct(s)).toBe('0.1');
44
48
  });
45
49
 
46
- it('moves thumb when attribute is set imperatively', async () => {
50
+ it('updates --slider-pct when attribute is set imperatively', async () => {
47
51
  const s = mount('<slider-ui value="50" min="0" max="100"></slider-ui>');
48
52
  await tick();
49
- const thumb = s.querySelector('[slot="thumb"]');
50
53
 
51
54
  s.setAttribute('value', '25');
52
55
  await tick();
53
- expect(thumb.style.left).toBe('25%');
56
+ expect(pct(s)).toBe('0.25');
54
57
  });
55
58
 
56
59
  it('updates [slot="value"] readout text when .value changes', async () => {
@@ -82,11 +82,15 @@ states:
82
82
  traits: []
83
83
  tokens:
84
84
  --slider-fill:
85
- description: Filled track / thumb color
86
- --slider-thumb-size:
87
- description: Thumb diameter
85
+ description: Filled portion / progress color (was mixed accent, now primary)
86
+ --slider-thumb-width:
87
+ description: Thumb pill width (2× thumb-height, driven by track-height)
88
+ --slider-thumb-height:
89
+ description: Thumb pill height (track-height − 2× inset)
88
90
  --slider-track:
89
- description: Track background color
91
+ description: Unfilled track background color
92
+ --slider-track-height:
93
+ description: Full track height (scales via universal [size] attribute)
90
94
  a2ui:
91
95
  rules: []
92
96
  anti_patterns: []
@@ -20,13 +20,13 @@
20
20
  --stepper-weight: var(--a-weight-medium);
21
21
  --stepper-border-size: 2px;
22
22
 
23
- --stepper-active-bg: var(--a-bg);
24
- --stepper-active-border: var(--a-accent);
25
- --stepper-active-fg: var(--a-accent);
23
+ --stepper-bg-active: var(--a-bg);
24
+ --stepper-border-active: var(--a-accent);
25
+ --stepper-fg-active: var(--a-accent);
26
26
 
27
- --stepper-done-bg: var(--a-accent);
28
- --stepper-done-border: var(--a-accent);
29
- --stepper-done-fg: var(--a-accent-fg);
27
+ --stepper-bg-done: var(--a-accent);
28
+ --stepper-border-done: var(--a-accent);
29
+ --stepper-fg-done: var(--a-accent-fg);
30
30
 
31
31
  --stepper-line: var(--a-border-subtle);
32
32
  --stepper-line-done: var(--a-accent);
@@ -76,13 +76,13 @@
76
76
  --stepper-item-weight: var(--stepper-weight, var(--a-weight-medium));
77
77
  --stepper-item-border-size: var(--stepper-border-size, 2px);
78
78
 
79
- --stepper-item-active-bg: var(--stepper-active-bg, var(--a-bg));
80
- --stepper-item-active-border: var(--stepper-active-border, var(--a-accent));
81
- --stepper-item-active-fg: var(--stepper-active-fg, var(--a-accent));
79
+ --stepper-item-bg-active: var(--stepper-bg-active, var(--a-bg));
80
+ --stepper-item-border-active: var(--stepper-border-active, var(--a-accent));
81
+ --stepper-item-fg-active: var(--stepper-fg-active, var(--a-accent));
82
82
 
83
- --stepper-item-done-bg: var(--stepper-done-bg, var(--a-accent));
84
- --stepper-item-done-border: var(--stepper-done-border, var(--a-accent));
85
- --stepper-item-done-fg: var(--stepper-done-fg, var(--a-accent-fg));
83
+ --stepper-item-bg-done: var(--stepper-bg-done, var(--a-accent));
84
+ --stepper-item-border-done: var(--stepper-border-done, var(--a-accent));
85
+ --stepper-item-fg-done: var(--stepper-fg-done, var(--a-accent-fg));
86
86
 
87
87
  --stepper-item-line: var(--stepper-line, var(--a-border-subtle));
88
88
  --stepper-item-line-done: var(--stepper-line-done, var(--a-accent));
@@ -158,9 +158,9 @@
158
158
 
159
159
  /* Active state */
160
160
  :scope[status="active"]::after {
161
- border-color: var(--stepper-item-active-border);
162
- color: var(--stepper-item-active-fg);
163
- background: var(--stepper-item-active-bg);
161
+ border-color: var(--stepper-item-border-active);
162
+ color: var(--stepper-item-fg-active);
163
+ background: var(--stepper-item-bg-active);
164
164
  }
165
165
 
166
166
  :scope[status="active"] [slot="label"] {
@@ -171,9 +171,9 @@
171
171
  /* Completed state — checkmark replaces number */
172
172
  :scope[status="completed"]::after {
173
173
  content: '\2713';
174
- background: var(--stepper-item-done-bg);
175
- border-color: var(--stepper-item-done-border);
176
- color: var(--stepper-item-done-fg);
174
+ background: var(--stepper-item-bg-done);
175
+ border-color: var(--stepper-item-border-done);
176
+ color: var(--stepper-item-fg-done);
177
177
  }
178
178
 
179
179
  :scope[status="completed"]::before {
@@ -214,15 +214,15 @@
214
214
  }
215
215
 
216
216
  :scope[status="active"] [slot="icon"] {
217
- border-color: var(--stepper-item-active-border);
218
- color: var(--stepper-item-active-fg);
219
- background: var(--stepper-item-active-bg);
217
+ border-color: var(--stepper-item-border-active);
218
+ color: var(--stepper-item-fg-active);
219
+ background: var(--stepper-item-bg-active);
220
220
  }
221
221
 
222
222
  :scope[status="completed"] [slot="icon"] {
223
- background: var(--stepper-item-done-bg);
224
- border-color: var(--stepper-item-done-border);
225
- color: var(--stepper-item-done-fg);
223
+ background: var(--stepper-item-bg-done);
224
+ border-color: var(--stepper-item-border-done);
225
+ color: var(--stepper-item-fg-done);
226
226
  }
227
227
 
228
228
  /* ── Content slots ── */