stimeo-ui 0.3.0 → 0.5.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +124 -0
- data/dist/controllers/alert_dialog_controller.js +32 -5
- data/dist/controllers/announcer_controller.js +255 -20
- data/dist/controllers/aspect_ratio_controller.js +1 -1
- data/dist/controllers/breadcrumb_controller.js +5 -1
- data/dist/controllers/carousel_controller.js +5 -1
- data/dist/controllers/clipboard_controller.js +8 -3
- data/dist/controllers/collapsible_controller.js +4 -1
- data/dist/controllers/color_picker_controller.js +52 -2
- data/dist/controllers/command_palette_controller.js +32 -5
- data/dist/controllers/confirm_controller.js +32 -5
- data/dist/controllers/context_menu_controller.js +2 -2
- data/dist/controllers/countdown_controller.js +117 -13
- data/dist/controllers/date_range_picker_controller.js +55 -1
- data/dist/controllers/dialog_controller.js +32 -5
- data/dist/controllers/direct_upload_controller.js +3 -3
- data/dist/controllers/drawer_controller.js +32 -5
- data/dist/controllers/empty_state_controller.js +128 -23
- data/dist/controllers/flash_controller.js +161 -21
- data/dist/controllers/focus_controller.js +32 -5
- data/dist/controllers/form_validation_controller.js +8 -2
- data/dist/controllers/frame_loading_controller.js +261 -27
- data/dist/controllers/highlight_controller.js +38 -1
- data/dist/controllers/idle_controller.js +13 -2
- data/dist/controllers/local_time_controller.js +102 -6
- data/dist/controllers/masonry_controller.js +1 -1
- data/dist/controllers/meter_controller.js +147 -26
- data/dist/controllers/network_status_controller.js +29 -11
- data/dist/controllers/number_input_controller.js +191 -24
- data/dist/controllers/overflow_menu_controller.js +34 -7
- data/dist/controllers/pagination_controller.js +5 -1
- data/dist/controllers/password_strength_controller.js +1 -1
- data/dist/controllers/pointer_drag_controller.js +10 -0
- data/dist/controllers/portal_controller.js +10 -0
- data/dist/controllers/progress_controller.js +123 -12
- data/dist/controllers/range_slider_controller.js +449 -93
- data/dist/controllers/rating_controller.js +55 -0
- data/dist/controllers/relative_time_controller.js +135 -12
- data/dist/controllers/scroll_area_controller.js +1 -1
- data/dist/controllers/separator_controller.js +13 -17
- data/dist/controllers/sidebar_controller.js +37 -8
- data/dist/controllers/skeleton_controller.js +143 -22
- data/dist/controllers/slider_controller.js +342 -50
- data/dist/controllers/spinner_controller.js +244 -28
- data/dist/controllers/step_indicator_controller.js +85 -6
- data/dist/controllers/stepper_controller.js +2 -0
- data/dist/controllers/stick_to_bottom_controller.js +60 -10
- data/dist/controllers/switch_controller.js +162 -18
- data/dist/controllers/textarea_autosize_controller.js +1 -1
- data/dist/controllers/time_picker_controller.js +6 -3
- data/dist/controllers/tree_view_controller.js +19 -1
- data/dist/index.js +2278 -596
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +2 -2
|
@@ -18,6 +18,214 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
18
18
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// src/utils/microtask_coalescer.ts
|
|
22
|
+
var MicrotaskCoalescer = class {
|
|
23
|
+
#run;
|
|
24
|
+
#queued = false;
|
|
25
|
+
#active = false;
|
|
26
|
+
#generation = 0;
|
|
27
|
+
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
28
|
+
constructor(run) {
|
|
29
|
+
this.#run = run;
|
|
30
|
+
}
|
|
31
|
+
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
32
|
+
activate() {
|
|
33
|
+
this.#active = true;
|
|
34
|
+
}
|
|
35
|
+
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
36
|
+
cancel() {
|
|
37
|
+
this.#active = false;
|
|
38
|
+
this.#queued = false;
|
|
39
|
+
this.#generation += 1;
|
|
40
|
+
}
|
|
41
|
+
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
42
|
+
schedule() {
|
|
43
|
+
if (!this.#active || this.#queued) return;
|
|
44
|
+
this.#queued = true;
|
|
45
|
+
const generation = this.#generation;
|
|
46
|
+
queueMicrotask(() => {
|
|
47
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
48
|
+
this.#queued = false;
|
|
49
|
+
this.#run();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// src/utils/owned_pointer_session.ts
|
|
55
|
+
var OwnedPointerSession = class {
|
|
56
|
+
pointerId;
|
|
57
|
+
#owner;
|
|
58
|
+
#handlers;
|
|
59
|
+
#abort = new AbortController();
|
|
60
|
+
#active = true;
|
|
61
|
+
constructor(start, owner, handlers) {
|
|
62
|
+
this.pointerId = start.pointerId;
|
|
63
|
+
this.#owner = owner;
|
|
64
|
+
this.#handlers = handlers;
|
|
65
|
+
const { signal } = this.#abort;
|
|
66
|
+
owner.ownerDocument.addEventListener("pointermove", this.#onMove, { signal });
|
|
67
|
+
owner.ownerDocument.addEventListener("pointerup", this.#onEndEvent, { signal });
|
|
68
|
+
owner.ownerDocument.addEventListener("pointercancel", this.#onEndEvent, { signal });
|
|
69
|
+
owner.addEventListener("lostpointercapture", this.#onLostCapture, { signal });
|
|
70
|
+
try {
|
|
71
|
+
owner.setPointerCapture?.(this.pointerId);
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Whether this session still owns its pointer and listeners. */
|
|
76
|
+
get active() {
|
|
77
|
+
return this.#active;
|
|
78
|
+
}
|
|
79
|
+
/** Whether `event` belongs to the initiating pointer of the live session. */
|
|
80
|
+
owns(event) {
|
|
81
|
+
return this.#active && event.pointerId === this.pointerId;
|
|
82
|
+
}
|
|
83
|
+
/** Releases capture/listeners and invokes the end callback exactly once. */
|
|
84
|
+
end() {
|
|
85
|
+
if (!this.#active) return;
|
|
86
|
+
this.#active = false;
|
|
87
|
+
this.#abort.abort();
|
|
88
|
+
try {
|
|
89
|
+
this.#owner.releasePointerCapture?.(this.pointerId);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
this.#handlers.end?.();
|
|
93
|
+
}
|
|
94
|
+
#onMove = (event) => {
|
|
95
|
+
if (this.owns(event)) this.#handlers.move(event);
|
|
96
|
+
};
|
|
97
|
+
#onEndEvent = (event) => {
|
|
98
|
+
if (this.owns(event)) this.end();
|
|
99
|
+
};
|
|
100
|
+
#onLostCapture = (event) => {
|
|
101
|
+
const pointerId = event.pointerId;
|
|
102
|
+
if (typeof pointerId === "number" && pointerId !== this.pointerId) return;
|
|
103
|
+
this.end();
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/utils/range.ts
|
|
108
|
+
function rangeFraction(value, min, max) {
|
|
109
|
+
const span = max - min;
|
|
110
|
+
if (!(span > 0)) return 0;
|
|
111
|
+
const clamped = Math.min(max, Math.max(min, value));
|
|
112
|
+
let fraction;
|
|
113
|
+
if (Number.isFinite(span)) {
|
|
114
|
+
fraction = (clamped - min) / span;
|
|
115
|
+
} else {
|
|
116
|
+
const scale = Math.max(Math.abs(min), Math.abs(max));
|
|
117
|
+
fraction = (clamped / scale - min / scale) / (max / scale - min / scale);
|
|
118
|
+
}
|
|
119
|
+
if (!Number.isFinite(fraction)) return 0;
|
|
120
|
+
return Math.min(1, Math.max(0, fraction));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/utils/stepped_value.ts
|
|
124
|
+
function effectiveStep(step) {
|
|
125
|
+
return Number.isFinite(step) && step > 0 ? step : 1;
|
|
126
|
+
}
|
|
127
|
+
function snapSteppedValue(raw, range) {
|
|
128
|
+
if (!(range.min <= range.max)) return finiteFallback(range.min, range.max);
|
|
129
|
+
const input = Number.isNaN(raw) ? finiteFallback(range.min, range.max) : raw;
|
|
130
|
+
const clamped = Math.min(range.max, Math.max(range.min, input));
|
|
131
|
+
const step = effectiveStep(range.step);
|
|
132
|
+
const base = stepBase(range);
|
|
133
|
+
const candidates = [];
|
|
134
|
+
if (Number.isFinite(range.min)) candidates.push(range.min);
|
|
135
|
+
if (Number.isFinite(range.max)) candidates.push(range.max);
|
|
136
|
+
const gridPosition = (clamped - base) / step;
|
|
137
|
+
if (Number.isFinite(gridPosition)) {
|
|
138
|
+
addGridCandidate(candidates, Math.floor(gridPosition), range, base, step);
|
|
139
|
+
addGridCandidate(candidates, Math.ceil(gridPosition), range, base, step);
|
|
140
|
+
}
|
|
141
|
+
if (candidates.length === 0) return clamped;
|
|
142
|
+
let nearest = candidates[0];
|
|
143
|
+
let nearestDistance = Math.abs(clamped - nearest);
|
|
144
|
+
for (const candidate of candidates.slice(1)) {
|
|
145
|
+
const distance = Math.abs(clamped - candidate);
|
|
146
|
+
if (distance < nearestDistance || nearlyEqual(distance, nearestDistance) && candidate > nearest) {
|
|
147
|
+
nearest = candidate;
|
|
148
|
+
nearestDistance = distance;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return nearest;
|
|
152
|
+
}
|
|
153
|
+
function stepSteppedValue(current, count, range) {
|
|
154
|
+
const value = snapSteppedValue(current, range);
|
|
155
|
+
const distance = Math.abs(Math.trunc(count));
|
|
156
|
+
if (!Number.isFinite(distance) || distance === 0) return value;
|
|
157
|
+
const direction = Math.sign(count);
|
|
158
|
+
const adjacent = adjacentSteppedValue(value, direction, range);
|
|
159
|
+
const raw = adjacent + direction * (distance - 1) * effectiveStep(range.step);
|
|
160
|
+
return snapSteppedValue(raw, range);
|
|
161
|
+
}
|
|
162
|
+
function adjacentSteppedValue(current, direction, range) {
|
|
163
|
+
const step = effectiveStep(range.step);
|
|
164
|
+
const base = stepBase(range);
|
|
165
|
+
const candidates = [];
|
|
166
|
+
if (direction > 0) {
|
|
167
|
+
const position2 = (current - base) / step;
|
|
168
|
+
if (Number.isFinite(position2)) {
|
|
169
|
+
let gridIndex = Math.floor(position2) + 1;
|
|
170
|
+
let candidate = cleanGridValue(base + gridIndex * step, base, step);
|
|
171
|
+
if (candidate < current || nearlyEqual(candidate, current)) {
|
|
172
|
+
gridIndex += 1;
|
|
173
|
+
candidate = cleanGridValue(base + gridIndex * step, base, step);
|
|
174
|
+
}
|
|
175
|
+
if (candidate > current && !nearlyEqual(candidate, current) && within(candidate, range.min, range.max)) {
|
|
176
|
+
candidates.push(clamp(candidate, range));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return clamp(Math.min(...candidates), range);
|
|
180
|
+
}
|
|
181
|
+
const position = (current - base) / step;
|
|
182
|
+
if (Number.isFinite(position)) {
|
|
183
|
+
let gridIndex = Math.ceil(position) - 1;
|
|
184
|
+
let candidate = cleanGridValue(base + gridIndex * step, base, step);
|
|
185
|
+
if (candidate > current || nearlyEqual(candidate, current)) {
|
|
186
|
+
gridIndex -= 1;
|
|
187
|
+
candidate = cleanGridValue(base + gridIndex * step, base, step);
|
|
188
|
+
}
|
|
189
|
+
if (candidate < current && !nearlyEqual(candidate, current) && within(candidate, range.min, range.max)) {
|
|
190
|
+
candidates.push(clamp(candidate, range));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return clamp(Math.max(...candidates), range);
|
|
194
|
+
}
|
|
195
|
+
function addGridCandidate(candidates, index, range, base, step) {
|
|
196
|
+
const candidate = cleanGridValue(base + index * step, base, step);
|
|
197
|
+
if (within(candidate, range.min, range.max)) candidates.push(clamp(candidate, range));
|
|
198
|
+
}
|
|
199
|
+
function stepBase(range) {
|
|
200
|
+
if (range.base !== void 0 && Number.isFinite(range.base)) return range.base;
|
|
201
|
+
return Number.isFinite(range.min) ? range.min : 0;
|
|
202
|
+
}
|
|
203
|
+
function cleanGridValue(value, base, step) {
|
|
204
|
+
const precision = Math.max(decimalPlaces(base), decimalPlaces(step));
|
|
205
|
+
return precision <= 100 ? Number(value.toFixed(precision)) : value;
|
|
206
|
+
}
|
|
207
|
+
function decimalPlaces(value) {
|
|
208
|
+
const [coefficient = "", exponentText] = Math.abs(value).toString().toLowerCase().split("e");
|
|
209
|
+
const fractionLength = coefficient.split(".")[1]?.length ?? 0;
|
|
210
|
+
const exponent = exponentText === void 0 ? 0 : Number(exponentText);
|
|
211
|
+
return Math.max(0, fractionLength - exponent);
|
|
212
|
+
}
|
|
213
|
+
function within(value, min, max) {
|
|
214
|
+
return (value > min || nearlyEqual(value, min)) && (value < max || nearlyEqual(value, max));
|
|
215
|
+
}
|
|
216
|
+
function clamp(value, range) {
|
|
217
|
+
return Math.min(range.max, Math.max(range.min, value));
|
|
218
|
+
}
|
|
219
|
+
function nearlyEqual(left, right) {
|
|
220
|
+
const scale = Math.max(Math.abs(left), Math.abs(right));
|
|
221
|
+
return Number.isFinite(left) && Number.isFinite(right) && Math.abs(left - right) <= Number.EPSILON * scale;
|
|
222
|
+
}
|
|
223
|
+
function finiteFallback(min, max) {
|
|
224
|
+
if (Number.isFinite(min)) return min;
|
|
225
|
+
if (Number.isFinite(max)) return max;
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
21
229
|
// src/controllers/slider_controller.ts
|
|
22
230
|
var FRACTION_PROPERTY = "--stimeo--slider-fraction";
|
|
23
231
|
var SliderController = class extends Controller {
|
|
@@ -31,40 +239,79 @@ var SliderController = class extends Controller {
|
|
|
31
239
|
};
|
|
32
240
|
static actions = ["onKeydown", "onPointerDown"];
|
|
33
241
|
static events = ["change"];
|
|
34
|
-
/**
|
|
35
|
-
#
|
|
242
|
+
/** One initiating pointer owns each live drag and its stable target snapshot. */
|
|
243
|
+
#drag = null;
|
|
244
|
+
/** Collapses a morph that swaps several render Values into one silent repaint. */
|
|
245
|
+
#repaint = new MicrotaskCoalescer(() => this.#render());
|
|
36
246
|
/** Whether the consumer declared a mirroring track and the direction mirrors it. */
|
|
37
247
|
get #mirrored() {
|
|
38
248
|
return this.logicalTrackValue && isRtl(this.element);
|
|
39
249
|
}
|
|
40
250
|
/** Clamps the initial value and renders the starting position. */
|
|
41
251
|
connect() {
|
|
42
|
-
this.#
|
|
252
|
+
this.#repaint.activate();
|
|
253
|
+
this.#commit(this.valueValue, false);
|
|
43
254
|
}
|
|
44
255
|
/** Cancels any active pointer drag so document listeners never leak. */
|
|
45
256
|
disconnect() {
|
|
46
|
-
this.#
|
|
47
|
-
this.#
|
|
257
|
+
this.#repaint.cancel();
|
|
258
|
+
this.#endDrag();
|
|
259
|
+
}
|
|
260
|
+
/** Silently repaints a minimum changed by application code or a Turbo morph. */
|
|
261
|
+
minValueChanged() {
|
|
262
|
+
this.#repaint.schedule();
|
|
263
|
+
}
|
|
264
|
+
/** Silently repaints a maximum changed by application code or a Turbo morph. */
|
|
265
|
+
maxValueChanged() {
|
|
266
|
+
this.#repaint.schedule();
|
|
267
|
+
}
|
|
268
|
+
/** Silently repaints a step changed by application code or a Turbo morph. */
|
|
269
|
+
stepValueChanged() {
|
|
270
|
+
this.#repaint.schedule();
|
|
271
|
+
}
|
|
272
|
+
/** Silently repaints a value changed by application code or a Turbo morph. */
|
|
273
|
+
valueValueChanged() {
|
|
274
|
+
this.#repaint.schedule();
|
|
275
|
+
}
|
|
276
|
+
/** Hydrates a thumb inserted or replaced at runtime with the current ARIA state. */
|
|
277
|
+
thumbTargetConnected(thumb) {
|
|
278
|
+
const value = this.#currentValue();
|
|
279
|
+
this.#renderThumb(thumb, value);
|
|
280
|
+
this.#renderFraction(value);
|
|
281
|
+
if (this.#drag && this.#drag.thumb === null) {
|
|
282
|
+
this.#drag.thumb = thumb;
|
|
283
|
+
thumb.focus();
|
|
284
|
+
}
|
|
285
|
+
this.#repaint.schedule();
|
|
286
|
+
}
|
|
287
|
+
/** Drops the stale focus owner while allowing a live track gesture to continue. */
|
|
288
|
+
thumbTargetDisconnected(thumb) {
|
|
289
|
+
if (this.#drag?.thumb === thumb) this.#drag.thumb = null;
|
|
290
|
+
}
|
|
291
|
+
/** Ends a gesture whose geometry target disappeared or ceased being a target. */
|
|
292
|
+
trackTargetDisconnected(track) {
|
|
293
|
+
if (this.#drag?.track === track) this.#endDrag();
|
|
48
294
|
}
|
|
49
295
|
/** Handles keyboard stepping per the APG slider model. */
|
|
50
296
|
onKeydown(event) {
|
|
51
297
|
if (isReservedArrowChord(event)) return;
|
|
52
|
-
const big = this.stepValue * 10;
|
|
53
298
|
let next = null;
|
|
299
|
+
const current = this.#currentValue();
|
|
300
|
+
const range = this.#steppedRange;
|
|
54
301
|
switch (this.#mirrored ? logicalArrowKey(event.key, this.element) : event.key) {
|
|
55
302
|
case "ArrowRight":
|
|
56
303
|
case "ArrowUp":
|
|
57
|
-
next =
|
|
304
|
+
next = stepSteppedValue(current, 1, range);
|
|
58
305
|
break;
|
|
59
306
|
case "ArrowLeft":
|
|
60
307
|
case "ArrowDown":
|
|
61
|
-
next =
|
|
308
|
+
next = stepSteppedValue(current, -1, range);
|
|
62
309
|
break;
|
|
63
310
|
case "PageUp":
|
|
64
|
-
next =
|
|
311
|
+
next = stepSteppedValue(current, 10, range);
|
|
65
312
|
break;
|
|
66
313
|
case "PageDown":
|
|
67
|
-
next =
|
|
314
|
+
next = stepSteppedValue(current, -10, range);
|
|
68
315
|
break;
|
|
69
316
|
case "Home":
|
|
70
317
|
next = this.minValue;
|
|
@@ -76,57 +323,102 @@ var SliderController = class extends Controller {
|
|
|
76
323
|
return;
|
|
77
324
|
}
|
|
78
325
|
event.preventDefault();
|
|
79
|
-
this.#
|
|
326
|
+
this.#commit(next, true);
|
|
80
327
|
}
|
|
81
328
|
/** Begins a pointer drag: sets the value and tracks subsequent movement. */
|
|
82
329
|
onPointerDown(event) {
|
|
83
|
-
if (!this.hasTrackTarget) return;
|
|
84
|
-
|
|
330
|
+
if (event.button !== 0 || this.#drag || !this.hasTrackTarget || !this.hasThumbTarget) return;
|
|
331
|
+
const track = this.trackTarget;
|
|
332
|
+
const thumb = this.thumbTarget;
|
|
85
333
|
const mirrored = this.#mirrored;
|
|
86
|
-
this.#
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
334
|
+
const value = this.#valueFromClientX(event.clientX, mirrored, track);
|
|
335
|
+
if (value === null) return;
|
|
336
|
+
event.preventDefault();
|
|
337
|
+
this.#commit(value, true);
|
|
338
|
+
thumb.focus();
|
|
339
|
+
const drag = { pointer: null, track, thumb };
|
|
340
|
+
drag.pointer = new OwnedPointerSession(event, track, {
|
|
341
|
+
move: (move) => {
|
|
342
|
+
if (!track.isConnected) {
|
|
343
|
+
this.#endDrag();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const moved = this.#valueFromClientX(move.clientX, mirrored, track);
|
|
347
|
+
if (moved !== null) this.#commit(moved, true);
|
|
348
|
+
},
|
|
349
|
+
end: () => {
|
|
350
|
+
if (this.#drag === drag) this.#drag = null;
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
this.#drag = drag;
|
|
354
|
+
}
|
|
355
|
+
/** Maps a pointer X coordinate to a raw value using a stable track snapshot. */
|
|
356
|
+
#valueFromClientX(clientX, mirrored, track) {
|
|
357
|
+
const rect = track.getBoundingClientRect();
|
|
358
|
+
if (rect.width === 0) return null;
|
|
104
359
|
const offset = (clientX - rect.left) / rect.width;
|
|
105
360
|
const fraction = mirrored ? 1 - offset : offset;
|
|
106
|
-
this
|
|
361
|
+
return this.minValue + fraction * (this.maxValue - this.minValue);
|
|
107
362
|
}
|
|
108
363
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* change — symmetric with `range-slider` — unless `silent` (the initial
|
|
113
|
-
* connect render, which is not a user edit).
|
|
364
|
+
* Stores a normalized value and renders synchronously for responsive input.
|
|
365
|
+
* Morph callbacks use {@link #render} instead, so they never write Values back
|
|
366
|
+
* or dispatch a user-facing change event.
|
|
114
367
|
*/
|
|
115
|
-
#
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.
|
|
368
|
+
#commit(raw, notify) {
|
|
369
|
+
const previous = this.#currentValue();
|
|
370
|
+
const value = snapSteppedValue(raw, this.#steppedRange);
|
|
371
|
+
if (!Object.is(this.valueValue, value)) this.valueValue = value;
|
|
372
|
+
this.#renderValue(value);
|
|
373
|
+
if (notify && value !== previous) this.dispatch("change", { detail: { value } });
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Reflects the normalized current Value without mutating or dispatching it.
|
|
377
|
+
*
|
|
378
|
+
* @stimeoRenderRoot
|
|
379
|
+
*/
|
|
380
|
+
#render() {
|
|
381
|
+
this.#renderValue(this.#currentValue());
|
|
382
|
+
}
|
|
383
|
+
/** Reflects one normalized value on the current target and CSS output. */
|
|
384
|
+
#renderValue(value) {
|
|
121
385
|
if (this.hasThumbTarget) {
|
|
122
|
-
this.thumbTarget
|
|
123
|
-
this.thumbTarget.setAttribute("aria-valuemax", String(this.maxValue));
|
|
124
|
-
this.thumbTarget.setAttribute("aria-valuenow", String(value));
|
|
386
|
+
this.#renderThumb(this.thumbTarget, value);
|
|
125
387
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
388
|
+
this.#renderFraction(value);
|
|
389
|
+
}
|
|
390
|
+
/** Writes only ARIA values that differ, including on a replacement target. */
|
|
391
|
+
#renderThumb(thumb, value) {
|
|
392
|
+
const attributes = {
|
|
393
|
+
"aria-valuemin": String(this.minValue),
|
|
394
|
+
"aria-valuemax": String(this.maxValue),
|
|
395
|
+
"aria-valuenow": String(value)
|
|
396
|
+
};
|
|
397
|
+
for (const [name, next] of Object.entries(attributes)) {
|
|
398
|
+
if (thumb.getAttribute(name) !== next) thumb.setAttribute(name, next);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/** Writes the behavior-only positioning hook only when its value changed. */
|
|
402
|
+
#renderFraction(value) {
|
|
403
|
+
const fraction = rangeFraction(value, this.minValue, this.maxValue);
|
|
404
|
+
const next = String(fraction);
|
|
405
|
+
if (this.element.style.getPropertyValue(FRACTION_PROPERTY) !== next) {
|
|
406
|
+
this.element.style.setProperty(FRACTION_PROPERTY, next);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/** Current normalized value derived from the live declarative inputs. */
|
|
410
|
+
#currentValue() {
|
|
411
|
+
return snapSteppedValue(this.valueValue, this.#steppedRange);
|
|
412
|
+
}
|
|
413
|
+
/** Shared range configuration; finite endpoints remain allowed off the grid. */
|
|
414
|
+
get #steppedRange() {
|
|
415
|
+
return { min: this.minValue, max: this.maxValue, step: this.stepValue };
|
|
416
|
+
}
|
|
417
|
+
/** Ends the current pointer session without dispatching another change. */
|
|
418
|
+
#endDrag() {
|
|
419
|
+
const drag = this.#drag;
|
|
420
|
+
this.#drag = null;
|
|
421
|
+
drag?.pointer?.end();
|
|
130
422
|
}
|
|
131
423
|
};
|
|
132
424
|
|