@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.
- package/CHANGELOG.md +234 -0
- package/components/badge/class.js +5 -1
- package/components/button/class.js +5 -1
- package/components/code/class.js +42 -5
- package/components/code/code.a2ui.json +1 -1
- package/components/code/code.d.ts +27 -0
- package/components/code/code.yaml +7 -1
- package/components/heatmap/heatmap.css +1 -1
- package/components/list/list.css +6 -6
- package/components/nav-group/nav-group.css +2 -2
- package/components/page/page.css +1 -1
- package/components/segmented/class.js +22 -0
- package/components/select/select.css +2 -2
- package/components/slider/class.js +60 -6
- package/components/slider/slider.a2ui.json +10 -4
- package/components/slider/slider.css +142 -52
- package/components/slider/slider.test.js +18 -15
- package/components/slider/slider.yaml +8 -4
- package/components/stepper/stepper.css +24 -24
- package/components/swatch/class.js +92 -1
- package/components/swatch/swatch.css +2 -2
- package/components/swatch/swatch.test.js +269 -0
- package/components/swiper/class.js +247 -54
- package/components/swiper/swiper.a2ui.json +19 -0
- package/components/swiper/swiper.css +87 -21
- package/components/swiper/swiper.d.ts +6 -0
- package/components/swiper/swiper.yaml +18 -0
- package/components/tag/class.js +6 -1
- package/components/text/text.a2ui.json +16 -2
- package/components/text/text.d.ts +28 -45
- package/components/text/text.yaml +25 -1
- package/components/toolbar/toolbar.css +1 -1
- package/package.json +1 -1
- package/styles/colors/semantics.css +7 -84
|
@@ -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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
157
|
-
const
|
|
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
|
-
|
|
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
|
|
156
|
+
"description": "Filled portion / progress color (was mixed accent, now primary)"
|
|
157
157
|
},
|
|
158
|
-
"--slider-thumb-
|
|
159
|
-
"description": "Thumb
|
|
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": "
|
|
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
|
-
/*
|
|
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
|
-
/*
|
|
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-
|
|
14
|
-
--slider-thumb-bg: var(--a-
|
|
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
|
-
/* ──
|
|
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(--
|
|
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(--
|
|
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(--
|
|
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-
|
|
81
|
-
|
|
82
|
-
|
|
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:
|
|
91
|
-
border-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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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:
|
|
113
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
214
|
+
transform: translate(-50%, -50%) scale(1.05);
|
|
124
215
|
}
|
|
216
|
+
|
|
125
217
|
[slot="thumb"]:active {
|
|
126
|
-
transform:
|
|
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"]
|
|
135
|
-
:scope[disabled] [slot="thumb"] {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
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
|
-
|
|
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(
|
|
43
|
+
expect(pct(s)).toBe('0.75');
|
|
40
44
|
|
|
41
45
|
s.value = 10;
|
|
42
46
|
await tick();
|
|
43
|
-
expect(
|
|
47
|
+
expect(pct(s)).toBe('0.1');
|
|
44
48
|
});
|
|
45
49
|
|
|
46
|
-
it('
|
|
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(
|
|
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
|
|
86
|
-
--slider-thumb-
|
|
87
|
-
description: Thumb
|
|
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:
|
|
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
|
|
24
|
-
--stepper-active
|
|
25
|
-
--stepper-active
|
|
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
|
|
28
|
-
--stepper-done
|
|
29
|
-
--stepper-done
|
|
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
|
|
80
|
-
--stepper-item-active
|
|
81
|
-
--stepper-item-active
|
|
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
|
|
84
|
-
--stepper-item-done
|
|
85
|
-
--stepper-item-done
|
|
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
|
|
162
|
-
color: var(--stepper-item-active
|
|
163
|
-
background: var(--stepper-item-active
|
|
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
|
|
175
|
-
border-color: var(--stepper-item-done
|
|
176
|
-
color: var(--stepper-item-done
|
|
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
|
|
218
|
-
color: var(--stepper-item-active
|
|
219
|
-
background: var(--stepper-item-active
|
|
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
|
|
224
|
-
border-color: var(--stepper-item-done
|
|
225
|
-
color: var(--stepper-item-done
|
|
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 ── */
|