stimeo-ui 0.5.0 → 0.6.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.
- package/CHANGELOG.md +116 -0
- package/dist/controllers/aspect_ratio_controller.d.ts +1 -3
- package/dist/controllers/aspect_ratio_controller.js +19 -11
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.d.ts +36 -15
- package/dist/controllers/avatar_controller.js +195 -40
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +12 -3
- package/dist/controllers/carousel_controller.js +85 -9
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +31 -5
- package/dist/controllers/checkbox_controller.js +136 -25
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +7 -1
- package/dist/controllers/color_picker_controller.js +35 -9
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +23 -4
- package/dist/controllers/date_range_picker_controller.js +157 -30
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +7 -1
- package/dist/controllers/file_dropzone_controller.js +26 -3
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +2 -3
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +2 -2
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +3 -3
- package/dist/controllers/idle_controller.js +27 -5
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.js +5 -3
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +46 -12
- package/dist/controllers/multi_select_controller.js +460 -151
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +24 -6
- package/dist/controllers/number_input_controller.js +275 -51
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.js +4 -0
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.js +33 -0
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +3 -3
- package/dist/controllers/password_strength_controller.js +20 -2
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +4 -4
- package/dist/controllers/persist_controller.js +24 -5
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +2 -2
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +1 -2
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +41 -15
- package/dist/controllers/radio_group_controller.js +540 -56
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +36 -31
- package/dist/controllers/rating_controller.js +272 -89
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.js +33 -0
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +4 -0
- package/dist/controllers/roving_controller.js +60 -5
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +6 -0
- package/dist/controllers/scroll_area_controller.js +154 -22
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.js +33 -0
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +41 -11
- package/dist/controllers/tags_input_controller.js +356 -120
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +42 -13
- package/dist/controllers/time_picker_controller.js +296 -107
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +39 -13
- package/dist/controllers/toggle_group_controller.js +378 -55
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +5 -3
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +2 -2
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +2 -3
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.js +5 -3
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.js +2637 -781
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +16 -3
- package/dist/inspector/cli.js +22 -6
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +22 -6
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +8 -8
- package/dist/inspector/manifest.json +169 -37
- package/dist/positioning/index.d.ts +2 -2
- package/dist/positioning/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -8,6 +8,90 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
8
8
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
// src/utils/attribute_lease.ts
|
|
12
|
+
var AttributeLease = class {
|
|
13
|
+
#attribute;
|
|
14
|
+
#records = /* @__PURE__ */ new Map();
|
|
15
|
+
/** @param attribute - The attribute whose temporary values this lease owns. */
|
|
16
|
+
constructor(attribute) {
|
|
17
|
+
this.#attribute = attribute;
|
|
18
|
+
}
|
|
19
|
+
/** Writes or removes the leased attribute while preserving its authored value. */
|
|
20
|
+
write(element, value) {
|
|
21
|
+
const existing = this.#records.get(element);
|
|
22
|
+
if (existing) {
|
|
23
|
+
existing.written = value;
|
|
24
|
+
} else {
|
|
25
|
+
this.#records.set(element, {
|
|
26
|
+
original: element.getAttribute(this.#attribute),
|
|
27
|
+
written: value
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (value === null) element.removeAttribute(this.#attribute);
|
|
31
|
+
else element.setAttribute(this.#attribute, value);
|
|
32
|
+
}
|
|
33
|
+
/** Returns one lease without overwriting a value subsequently authored by a consumer. */
|
|
34
|
+
return(element) {
|
|
35
|
+
const record = this.#records.get(element);
|
|
36
|
+
if (!record) return;
|
|
37
|
+
this.#records.delete(element);
|
|
38
|
+
if (element.getAttribute(this.#attribute) !== record.written) return;
|
|
39
|
+
if (record.original === null) element.removeAttribute(this.#attribute);
|
|
40
|
+
else element.setAttribute(this.#attribute, record.original);
|
|
41
|
+
}
|
|
42
|
+
/** Returns every outstanding lease using the same ownership check as {@link return}. */
|
|
43
|
+
returnAll() {
|
|
44
|
+
for (const element of Array.from(this.#records.keys())) this.return(element);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/utils/composition_tracker.ts
|
|
49
|
+
var CompositionTracker = class {
|
|
50
|
+
#observedTargets = /* @__PURE__ */ new Set();
|
|
51
|
+
#activeTargets = /* @__PURE__ */ new Set();
|
|
52
|
+
#onStart;
|
|
53
|
+
#onEnd;
|
|
54
|
+
constructor(options = {}) {
|
|
55
|
+
this.#onStart = options.onStart;
|
|
56
|
+
this.#onEnd = options.onEnd;
|
|
57
|
+
}
|
|
58
|
+
/** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
|
|
59
|
+
observe(target) {
|
|
60
|
+
if (this.#observedTargets.has(target)) return;
|
|
61
|
+
target.addEventListener("compositionstart", this.#handleStart);
|
|
62
|
+
target.addEventListener("compositionend", this.#handleEnd);
|
|
63
|
+
this.#observedTargets.add(target);
|
|
64
|
+
}
|
|
65
|
+
/** Stops tracking one target and clears any active composition it owned. */
|
|
66
|
+
unobserve(target) {
|
|
67
|
+
if (!this.#observedTargets.delete(target)) return;
|
|
68
|
+
target.removeEventListener("compositionstart", this.#handleStart);
|
|
69
|
+
target.removeEventListener("compositionend", this.#handleEnd);
|
|
70
|
+
this.#activeTargets.delete(target);
|
|
71
|
+
}
|
|
72
|
+
/** Releases every listener and clears state so reconnect starts cleanly. */
|
|
73
|
+
disconnect() {
|
|
74
|
+
for (const target of this.#observedTargets) {
|
|
75
|
+
target.removeEventListener("compositionstart", this.#handleStart);
|
|
76
|
+
target.removeEventListener("compositionend", this.#handleEnd);
|
|
77
|
+
}
|
|
78
|
+
this.#observedTargets.clear();
|
|
79
|
+
this.#activeTargets.clear();
|
|
80
|
+
}
|
|
81
|
+
/** True when lifecycle tracking or the current event reports composition. */
|
|
82
|
+
isComposing(event) {
|
|
83
|
+
return this.#activeTargets.size > 0 || event?.isComposing === true;
|
|
84
|
+
}
|
|
85
|
+
#handleStart = (event) => {
|
|
86
|
+
if (event.currentTarget) this.#activeTargets.add(event.currentTarget);
|
|
87
|
+
this.#onStart?.(event);
|
|
88
|
+
};
|
|
89
|
+
#handleEnd = (event) => {
|
|
90
|
+
if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);
|
|
91
|
+
this.#onEnd?.(event);
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
11
95
|
// src/utils/microtask_coalescer.ts
|
|
12
96
|
var MicrotaskCoalescer = class {
|
|
13
97
|
#run;
|
|
@@ -215,6 +299,7 @@ function finiteFallback(min, max) {
|
|
|
215
299
|
}
|
|
216
300
|
|
|
217
301
|
// src/controllers/number_input_controller.ts
|
|
302
|
+
var OWNED_DISABLED = "data-number-input-disabled";
|
|
218
303
|
var NumberInputController = class _NumberInputController extends Controller {
|
|
219
304
|
static targets = ["input", "increment", "decrement"];
|
|
220
305
|
static values = {
|
|
@@ -224,7 +309,7 @@ var NumberInputController = class _NumberInputController extends Controller {
|
|
|
224
309
|
pageStep: { type: Number, default: 0 }
|
|
225
310
|
};
|
|
226
311
|
static actions = ["decrement", "increment", "onInput", "onKeydown"];
|
|
227
|
-
static events = ["change"];
|
|
312
|
+
static events = ["change", "reconcile"];
|
|
228
313
|
/** Delay (ms) a button must be held before auto-repeat starts. */
|
|
229
314
|
static #HOLD_DELAY_MS = 400;
|
|
230
315
|
/** Interval (ms) between auto-repeat steps once a hold has started. */
|
|
@@ -236,8 +321,18 @@ var NumberInputController = class _NumberInputController extends Controller {
|
|
|
236
321
|
* of poisoning the next legitimate click.
|
|
237
322
|
*/
|
|
238
323
|
static #SUPPRESS_RESET_MS = 250;
|
|
239
|
-
/** Aborts
|
|
240
|
-
#
|
|
324
|
+
/** Aborts global pointer guards on disconnect so none outlive the element. */
|
|
325
|
+
#globalGuards = null;
|
|
326
|
+
/** Per-target guards allow an old button to be released without touching its replacement. */
|
|
327
|
+
#buttonGuards = /* @__PURE__ */ new Map();
|
|
328
|
+
/** Tracks IME lifecycle on the current input, including confirming keys without a signal. */
|
|
329
|
+
#composition = new CompositionTracker();
|
|
330
|
+
/** Restores authored custom-spinbutton ARIA when a target leaves or the controller stops. */
|
|
331
|
+
#ariaValueNow = new AttributeLease("aria-valuenow");
|
|
332
|
+
#ariaValueMin = new AttributeLease("aria-valuemin");
|
|
333
|
+
#ariaValueMax = new AttributeLease("aria-valuemax");
|
|
334
|
+
/** Last reconciled or user-committed numeric value. */
|
|
335
|
+
#lastValue = null;
|
|
241
336
|
/** Timers for the hold delay and the suppress-reset safety net. */
|
|
242
337
|
#holdTimeouts = new SafeTimeout();
|
|
243
338
|
/** The running auto-repeat interval (one at a time). */
|
|
@@ -248,63 +343,120 @@ var NumberInputController = class _NumberInputController extends Controller {
|
|
|
248
343
|
#repeatedDuringHold = false;
|
|
249
344
|
/** True when the next `click` is the trailing one after a hold and must be ignored. */
|
|
250
345
|
#suppressNextClick = false;
|
|
251
|
-
/**
|
|
346
|
+
/** Button and pointer that own the current hold, so other instances/pointers cannot end it. */
|
|
347
|
+
#holdButton = null;
|
|
348
|
+
#holdPointerId = null;
|
|
349
|
+
/** Collapses runtime range/step changes into one input reconciliation. */
|
|
252
350
|
#repaint = new MicrotaskCoalescer(() => this.#reconcile());
|
|
253
351
|
/** Normalizes any initial value and wires the focus/hold pointer guards. */
|
|
254
352
|
connect() {
|
|
255
353
|
this.#repaint.activate();
|
|
256
|
-
|
|
354
|
+
this.#globalGuards = new AbortController();
|
|
355
|
+
const { signal } = this.#globalGuards;
|
|
356
|
+
for (const button of this.incrementTargets) this.#wireButton(button, 1);
|
|
357
|
+
for (const button of this.decrementTargets) this.#wireButton(button, -1);
|
|
358
|
+
window.addEventListener("pointerup", this.#onPointerEnd, { signal });
|
|
359
|
+
window.addEventListener("pointercancel", this.#onPointerEnd, { signal });
|
|
360
|
+
window.addEventListener("blur", this.#onWindowBlur, { signal });
|
|
257
361
|
this.#reconcile();
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (this.hasIncrementTarget) this.#wireButton(this.incrementTarget, 1, signal);
|
|
261
|
-
if (this.hasDecrementTarget) this.#wireButton(this.decrementTarget, -1, signal);
|
|
262
|
-
window.addEventListener("pointerup", this.#stopHold, { signal });
|
|
263
|
-
window.addEventListener("pointercancel", this.#stopHold, { signal });
|
|
264
|
-
window.addEventListener("blur", this.#stopHold, { signal });
|
|
265
|
-
}
|
|
266
|
-
/** Releases the pointer guards and tears down every pending timer and hold state. */
|
|
362
|
+
}
|
|
363
|
+
/** Releases listeners, derived attributes, timers, and transient value state. */
|
|
267
364
|
disconnect() {
|
|
268
365
|
this.#repaint.cancel();
|
|
269
|
-
this.#
|
|
270
|
-
this.#
|
|
271
|
-
this.#
|
|
366
|
+
this.#composition.disconnect();
|
|
367
|
+
this.#globalGuards?.abort();
|
|
368
|
+
this.#globalGuards = null;
|
|
369
|
+
const buttons = /* @__PURE__ */ new Set([
|
|
370
|
+
...this.#buttonGuards.keys(),
|
|
371
|
+
...this.incrementTargets,
|
|
372
|
+
...this.decrementTargets
|
|
373
|
+
]);
|
|
374
|
+
for (const guard of this.#buttonGuards.values()) guard.abort();
|
|
375
|
+
this.#buttonGuards.clear();
|
|
376
|
+
for (const button of buttons) this.#releaseButtonState(button);
|
|
377
|
+
this.#stopHold(false);
|
|
272
378
|
this.#repeatedDuringHold = false;
|
|
273
379
|
this.#suppressNextClick = false;
|
|
274
380
|
this.#holdTimeouts.clearAll();
|
|
275
381
|
this.#holdIntervals.clearAll();
|
|
382
|
+
this.#ariaValueNow.returnAll();
|
|
383
|
+
this.#ariaValueMin.returnAll();
|
|
384
|
+
this.#ariaValueMax.returnAll();
|
|
385
|
+
this.#lastValue = null;
|
|
276
386
|
}
|
|
277
|
-
/**
|
|
387
|
+
/** Reconciles the value against a minimum changed by application code or a Turbo morph. */
|
|
278
388
|
minValueChanged() {
|
|
279
389
|
this.#repaint.schedule();
|
|
280
390
|
}
|
|
281
|
-
/**
|
|
391
|
+
/** Reconciles the value against a maximum changed by application code or a Turbo morph. */
|
|
282
392
|
maxValueChanged() {
|
|
283
393
|
this.#repaint.schedule();
|
|
284
394
|
}
|
|
285
|
-
/**
|
|
395
|
+
/** Reconciles the value against a step changed by application code or a Turbo morph. */
|
|
286
396
|
stepValueChanged() {
|
|
287
397
|
this.#repaint.schedule();
|
|
288
398
|
}
|
|
399
|
+
/** Tracks and reconciles an input inserted or replaced at runtime. */
|
|
400
|
+
inputTargetConnected(input) {
|
|
401
|
+
this.#composition.observe(input);
|
|
402
|
+
this.#repaint.schedule();
|
|
403
|
+
}
|
|
404
|
+
/** Releases ARIA and composition state owned by an input that left the controller. */
|
|
405
|
+
inputTargetDisconnected(input) {
|
|
406
|
+
this.#composition.unobserve(input);
|
|
407
|
+
this.#releaseInputAria(input);
|
|
408
|
+
this.#stopHold(false);
|
|
409
|
+
this.#lastValue = null;
|
|
410
|
+
this.#repaint.schedule();
|
|
411
|
+
}
|
|
412
|
+
/** Wires focus preservation and hold behavior on a runtime increment target. */
|
|
413
|
+
incrementTargetConnected(button) {
|
|
414
|
+
this.#wireButton(button, 1);
|
|
415
|
+
this.#repaint.schedule();
|
|
416
|
+
}
|
|
417
|
+
/** Releases only the increment target that actually disconnected. */
|
|
418
|
+
incrementTargetDisconnected(button) {
|
|
419
|
+
this.#unwireButton(button);
|
|
420
|
+
this.#repaint.schedule();
|
|
421
|
+
}
|
|
422
|
+
/** Wires focus preservation and hold behavior on a runtime decrement target. */
|
|
423
|
+
decrementTargetConnected(button) {
|
|
424
|
+
this.#wireButton(button, -1);
|
|
425
|
+
this.#repaint.schedule();
|
|
426
|
+
}
|
|
427
|
+
/** Releases only the decrement target that actually disconnected. */
|
|
428
|
+
decrementTargetDisconnected(button) {
|
|
429
|
+
this.#unwireButton(button);
|
|
430
|
+
this.#repaint.schedule();
|
|
431
|
+
}
|
|
289
432
|
/** Increases by one step. Bound via `data-action` (click). */
|
|
290
433
|
increment() {
|
|
291
434
|
if (this.#consumeSuppressedClick()) return;
|
|
435
|
+
if (!this.hasInputTarget) return;
|
|
292
436
|
this.#commitStep(1);
|
|
293
437
|
this.inputTarget.focus();
|
|
294
438
|
}
|
|
295
439
|
/** Decreases by one step. Bound via `data-action` (click). */
|
|
296
440
|
decrement() {
|
|
297
441
|
if (this.#consumeSuppressedClick()) return;
|
|
442
|
+
if (!this.hasInputTarget) return;
|
|
298
443
|
this.#commitStep(-1);
|
|
299
444
|
this.inputTarget.focus();
|
|
300
445
|
}
|
|
301
446
|
/** Clamps and snaps a typed value. Bound via `data-action` (change). */
|
|
302
447
|
onInput() {
|
|
303
|
-
if (this.
|
|
448
|
+
if (!this.hasInputTarget) return;
|
|
449
|
+
if (this.inputTarget.value.trim() === "") {
|
|
450
|
+
this.#lastValue = null;
|
|
451
|
+
this.#reflectEmpty();
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
304
454
|
this.#commit(this.#currentValue());
|
|
305
455
|
}
|
|
306
456
|
/** Keyboard stepping per the APG spinbutton model. */
|
|
307
457
|
onKeydown(event) {
|
|
458
|
+
if (!this.hasInputTarget) return;
|
|
459
|
+
if (this.#composition.isComposing(event)) return;
|
|
308
460
|
if (isReservedArrowChord(event)) return;
|
|
309
461
|
let next = null;
|
|
310
462
|
switch (event.key) {
|
|
@@ -339,21 +491,39 @@ var NumberInputController = class _NumberInputController extends Controller {
|
|
|
339
491
|
* hold; leaving the button while held stops it (the global listeners cover
|
|
340
492
|
* release/cancel/blur).
|
|
341
493
|
*/
|
|
342
|
-
#wireButton(button, direction
|
|
494
|
+
#wireButton(button, direction) {
|
|
495
|
+
if (this.#buttonGuards.has(button)) return;
|
|
496
|
+
const guard = new AbortController();
|
|
497
|
+
this.#buttonGuards.set(button, guard);
|
|
343
498
|
button.addEventListener("pointerdown", (event) => this.#armHold(event, button, direction), {
|
|
344
|
-
signal
|
|
499
|
+
signal: guard.signal
|
|
345
500
|
});
|
|
346
|
-
button.addEventListener(
|
|
501
|
+
button.addEventListener(
|
|
502
|
+
"pointerleave",
|
|
503
|
+
(event) => {
|
|
504
|
+
if (this.#holdButton === button && this.#ownsPointer(event)) this.#stopHold();
|
|
505
|
+
},
|
|
506
|
+
{ signal: guard.signal }
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
/** Aborts one target's listeners and returns any state derived onto that button. */
|
|
510
|
+
#unwireButton(button) {
|
|
511
|
+
this.#buttonGuards.get(button)?.abort();
|
|
512
|
+
this.#buttonGuards.delete(button);
|
|
513
|
+
if (this.#holdButton === button) this.#stopHold(false);
|
|
514
|
+
this.#releaseButtonState(button);
|
|
347
515
|
}
|
|
348
516
|
/** Starts a hold: focus the input, then schedule the first repeat after a delay. */
|
|
349
517
|
#armHold(event, button, direction) {
|
|
350
518
|
const pointerButton = event.button;
|
|
351
519
|
if (typeof pointerButton === "number" && pointerButton !== 0) return;
|
|
352
|
-
if (button.disabled) return;
|
|
520
|
+
if (button.disabled || !this.hasInputTarget) return;
|
|
353
521
|
event.preventDefault();
|
|
354
522
|
this.inputTarget.focus();
|
|
355
|
-
this.#stopHold();
|
|
523
|
+
this.#stopHold(false);
|
|
356
524
|
this.#holdActive = true;
|
|
525
|
+
this.#holdButton = button;
|
|
526
|
+
this.#holdPointerId = this.#pointerId(event);
|
|
357
527
|
this.#repeatedDuringHold = false;
|
|
358
528
|
this.#suppressNextClick = false;
|
|
359
529
|
this.#holdTimeouts.set(() => {
|
|
@@ -372,18 +542,39 @@ var NumberInputController = class _NumberInputController extends Controller {
|
|
|
372
542
|
* trailing synthetic `click` must be ignored, so it is suppressed until the
|
|
373
543
|
* click consumes it or a short safety-net timeout clears it.
|
|
374
544
|
*/
|
|
375
|
-
#stopHold =
|
|
545
|
+
#stopHold(suppressTrailingClick = true) {
|
|
376
546
|
if (!this.#holdActive) return;
|
|
377
547
|
this.#holdActive = false;
|
|
548
|
+
this.#holdButton = null;
|
|
549
|
+
this.#holdPointerId = null;
|
|
378
550
|
this.#holdTimeouts.clearAll();
|
|
379
551
|
this.#holdIntervals.clearAll();
|
|
380
|
-
if (this.#repeatedDuringHold) {
|
|
552
|
+
if (suppressTrailingClick && this.#repeatedDuringHold) {
|
|
381
553
|
this.#suppressNextClick = true;
|
|
382
554
|
this.#holdTimeouts.set(() => {
|
|
383
555
|
this.#suppressNextClick = false;
|
|
384
556
|
}, _NumberInputController.#SUPPRESS_RESET_MS);
|
|
557
|
+
} else if (!suppressTrailingClick) {
|
|
558
|
+
this.#suppressNextClick = false;
|
|
385
559
|
}
|
|
560
|
+
}
|
|
561
|
+
/** Ends a hold only for its initiating pointer (legacy/synthetic events remain accepted). */
|
|
562
|
+
#onPointerEnd = (event) => {
|
|
563
|
+
if (this.#ownsPointer(event)) this.#stopHold();
|
|
386
564
|
};
|
|
565
|
+
/** Window deactivation is global and always ends the active hold. */
|
|
566
|
+
#onWindowBlur = () => {
|
|
567
|
+
this.#stopHold();
|
|
568
|
+
};
|
|
569
|
+
/** Extracts pointer identity when the runtime event exposes it. */
|
|
570
|
+
#pointerId(event) {
|
|
571
|
+
return event.pointerId ?? null;
|
|
572
|
+
}
|
|
573
|
+
/** Whether `event` belongs to the active pointer, with a synthetic-event fallback. */
|
|
574
|
+
#ownsPointer(event) {
|
|
575
|
+
const pointerId = this.#pointerId(event);
|
|
576
|
+
return this.#holdActive && (this.#holdPointerId === null || pointerId === null || pointerId === this.#holdPointerId);
|
|
577
|
+
}
|
|
387
578
|
/** Consumes a pending trailing-click suppression; returns true if the click was swallowed. */
|
|
388
579
|
#consumeSuppressedClick() {
|
|
389
580
|
if (!this.#suppressNextClick) return false;
|
|
@@ -392,53 +583,80 @@ var NumberInputController = class _NumberInputController extends Controller {
|
|
|
392
583
|
return true;
|
|
393
584
|
}
|
|
394
585
|
/**
|
|
395
|
-
* Normalizes `raw`, reflects it, and dispatches `
|
|
396
|
-
*
|
|
586
|
+
* Normalizes `raw`, reflects it, and dispatches `{ value: number }` only when
|
|
587
|
+
* the committed numeric value differs from the last reconciled value.
|
|
397
588
|
*
|
|
398
589
|
* @returns Whether the value changed (drives the auto-repeat's bound stop).
|
|
399
590
|
*/
|
|
400
591
|
#commit(raw) {
|
|
401
592
|
const value = this.#normalize(raw);
|
|
402
|
-
const changed = this
|
|
593
|
+
const changed = this.#lastValue === null || value !== this.#lastValue;
|
|
403
594
|
this.#write(value);
|
|
595
|
+
this.#lastValue = value;
|
|
404
596
|
if (changed) this.dispatch("change", { detail: { value } });
|
|
405
597
|
return changed;
|
|
406
598
|
}
|
|
407
599
|
/** Commits an adjacent endpoint/grid value and reports whether it moved. */
|
|
408
600
|
#commitStep(count) {
|
|
601
|
+
if (!this.hasInputTarget) return false;
|
|
409
602
|
return this.#commit(stepSteppedValue(this.#currentValue(), count, this.#steppedRange));
|
|
410
603
|
}
|
|
411
604
|
/**
|
|
412
|
-
*
|
|
605
|
+
* Reflects the current value after a range or step morph, reporting a moved
|
|
606
|
+
* value as `reconcile`.
|
|
413
607
|
*
|
|
414
608
|
* @stimeoRenderRoot
|
|
415
609
|
*/
|
|
416
610
|
#reconcile() {
|
|
417
|
-
if (!this.hasInputTarget)
|
|
611
|
+
if (!this.hasInputTarget) {
|
|
612
|
+
this.#lastValue = null;
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
418
615
|
if (this.inputTarget.value.trim() !== "") {
|
|
419
|
-
this.#
|
|
616
|
+
const previous = this.#lastValue;
|
|
617
|
+
const value = this.#normalize(this.#currentValue());
|
|
618
|
+
this.#write(value);
|
|
619
|
+
this.#lastValue = value;
|
|
620
|
+
if (previous !== null && value !== previous) {
|
|
621
|
+
this.dispatch("reconcile", { detail: { value } });
|
|
622
|
+
}
|
|
420
623
|
} else {
|
|
421
|
-
this.#
|
|
624
|
+
this.#lastValue = null;
|
|
625
|
+
this.#reflectEmpty();
|
|
422
626
|
}
|
|
423
627
|
}
|
|
424
628
|
/** Reflects `value` on the input (and ARIA for non-native hosts) and the buttons. */
|
|
425
629
|
#write(value) {
|
|
426
|
-
this.inputTarget
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if (Number.isFinite(this.minValue)) {
|
|
430
|
-
this.inputTarget.setAttribute("aria-valuemin", String(this.minValue));
|
|
431
|
-
}
|
|
432
|
-
if (Number.isFinite(this.maxValue)) {
|
|
433
|
-
this.inputTarget.setAttribute("aria-valuemax", String(this.maxValue));
|
|
434
|
-
}
|
|
435
|
-
}
|
|
630
|
+
const input = this.inputTarget;
|
|
631
|
+
input.value = String(value);
|
|
632
|
+
this.#syncInputAria(input, value);
|
|
436
633
|
this.#updateButtons(value);
|
|
437
634
|
}
|
|
635
|
+
/** Reflects a blank value without inventing a numeric `aria-valuenow`. */
|
|
636
|
+
#reflectEmpty() {
|
|
637
|
+
this.#syncInputAria(this.inputTarget, null);
|
|
638
|
+
this.#updateButtons(this.#currentValue());
|
|
639
|
+
}
|
|
640
|
+
/** Synchronizes controller-derived ARIA while preserving authored teardown values. */
|
|
641
|
+
#syncInputAria(input, value) {
|
|
642
|
+
if (input.getAttribute("role") !== "spinbutton") {
|
|
643
|
+
this.#releaseInputAria(input);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
this.#ariaValueNow.write(input, value === null ? null : String(value));
|
|
647
|
+
this.#ariaValueMin.write(input, Number.isFinite(this.minValue) ? String(this.minValue) : null);
|
|
648
|
+
this.#ariaValueMax.write(input, Number.isFinite(this.maxValue) ? String(this.maxValue) : null);
|
|
649
|
+
}
|
|
650
|
+
/** Returns all custom-spinbutton ARIA leased on `input`. */
|
|
651
|
+
#releaseInputAria(input) {
|
|
652
|
+
this.#ariaValueNow.return(input);
|
|
653
|
+
this.#ariaValueMin.return(input);
|
|
654
|
+
this.#ariaValueMax.return(input);
|
|
655
|
+
}
|
|
438
656
|
/** Disables a step button at its bound, returning focus to the input first. */
|
|
439
657
|
#updateButtons(value) {
|
|
440
|
-
|
|
441
|
-
|
|
658
|
+
for (const button of this.incrementTargets) this.#toggleButton(button, value < this.maxValue);
|
|
659
|
+
for (const button of this.decrementTargets) this.#toggleButton(button, value > this.minValue);
|
|
442
660
|
}
|
|
443
661
|
/**
|
|
444
662
|
* Enables or disables a step button at its bound, never disabling one while it
|
|
@@ -448,16 +666,22 @@ var NumberInputController = class _NumberInputController extends Controller {
|
|
|
448
666
|
*/
|
|
449
667
|
#toggleButton(button, enabled) {
|
|
450
668
|
if (enabled) {
|
|
451
|
-
if (button.hasAttribute(
|
|
669
|
+
if (button.hasAttribute(OWNED_DISABLED)) {
|
|
452
670
|
button.disabled = false;
|
|
453
|
-
button.removeAttribute(
|
|
671
|
+
button.removeAttribute(OWNED_DISABLED);
|
|
454
672
|
}
|
|
455
673
|
return;
|
|
456
674
|
}
|
|
457
675
|
if (button.disabled) return;
|
|
458
|
-
if (document.activeElement === button) this.inputTarget.focus();
|
|
676
|
+
if (document.activeElement === button && this.hasInputTarget) this.inputTarget.focus();
|
|
459
677
|
button.disabled = true;
|
|
460
|
-
button.setAttribute(
|
|
678
|
+
button.setAttribute(OWNED_DISABLED, "");
|
|
679
|
+
}
|
|
680
|
+
/** Returns `disabled` only when its explicit marker still identifies controller ownership. */
|
|
681
|
+
#releaseButtonState(button) {
|
|
682
|
+
if (!button.hasAttribute(OWNED_DISABLED)) return;
|
|
683
|
+
button.disabled = false;
|
|
684
|
+
button.removeAttribute(OWNED_DISABLED);
|
|
461
685
|
}
|
|
462
686
|
/** The current numeric value, falling back to a finite min (else 0) when blank. */
|
|
463
687
|
#currentValue() {
|