stimeo-ui 0.12.0 → 0.14.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 +71 -0
- data/dist/cable/index.js +438 -152
- data/dist/controllers/calendar_controller.js +34 -2
- data/dist/controllers/carousel_controller.js +9 -7
- data/dist/controllers/command_palette_controller.js +2 -2
- data/dist/controllers/conditional_fields_controller.js +7 -1
- data/dist/controllers/currency_input_controller.js +68 -23
- data/dist/controllers/date_range_picker_controller.js +34 -10
- data/dist/controllers/direct_upload_controller.js +21 -10
- data/dist/controllers/drawer_controller.js +2 -2
- data/dist/controllers/form_field_controller.js +7 -1
- data/dist/controllers/input_mask_controller.js +33 -12
- data/dist/controllers/intersection_controller.js +10 -5
- data/dist/controllers/lazy_frame_controller.js +10 -5
- data/dist/controllers/local_time_controller.js +34 -7
- data/dist/controllers/masonry_controller.js +1 -1
- data/dist/controllers/menu_controller.js +10 -2
- data/dist/controllers/menubar_controller.js +10 -2
- data/dist/controllers/navigation_menu_controller.js +8 -2
- data/dist/controllers/optimistic_controller.js +141 -20
- data/dist/controllers/otp_controller.js +23 -10
- data/dist/controllers/overflow_menu_controller.js +10 -1
- data/dist/controllers/pointer_drag_controller.js +10 -3
- data/dist/controllers/portal_controller.js +21 -10
- data/dist/controllers/radio_group_controller.js +11 -3
- data/dist/controllers/relative_time_controller.js +31 -5
- data/dist/controllers/reset_before_cache_controller.js +23 -10
- data/dist/controllers/resizable_controller.js +7 -2
- data/dist/controllers/roving_controller.js +7 -3
- data/dist/controllers/scroll_visibility_controller.js +22 -13
- data/dist/controllers/scrollspy_controller.js +11 -6
- data/dist/controllers/smart_sticky_header_controller.js +23 -12
- data/dist/controllers/sticky_observer_controller.js +10 -5
- data/dist/controllers/theme_controller.js +23 -13
- data/dist/controllers/toast_controller.js +1 -1
- data/dist/controllers/toggle_group_controller.js +11 -3
- data/dist/controllers/toolbar_controller.js +7 -3
- data/dist/index.js +259 -202
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +2 -2
|
@@ -18,6 +18,35 @@ 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/intl_format.ts
|
|
22
|
+
var formatters = /* @__PURE__ */ new WeakMap();
|
|
23
|
+
function intlFormatter(factory, locale, options, fallbackLocale) {
|
|
24
|
+
const cache = cacheFor(factory);
|
|
25
|
+
const key = cacheKey(locale, options, fallbackLocale);
|
|
26
|
+
if (cache.has(key)) return cache.get(key);
|
|
27
|
+
const formatter = construct(factory, locale, options, fallbackLocale);
|
|
28
|
+
cache.set(key, formatter);
|
|
29
|
+
return formatter;
|
|
30
|
+
}
|
|
31
|
+
function cacheFor(factory) {
|
|
32
|
+
const existing = formatters.get(factory);
|
|
33
|
+
if (existing) return existing;
|
|
34
|
+
const created = /* @__PURE__ */ new Map();
|
|
35
|
+
formatters.set(factory, created);
|
|
36
|
+
return created;
|
|
37
|
+
}
|
|
38
|
+
function cacheKey(locale, options, fallbackLocale) {
|
|
39
|
+
return JSON.stringify([locale, options, fallbackLocale]);
|
|
40
|
+
}
|
|
41
|
+
function construct(factory, locale, options, fallbackLocale) {
|
|
42
|
+
try {
|
|
43
|
+
return new factory(locale, options);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (!(error instanceof RangeError)) throw error;
|
|
46
|
+
return new factory(fallbackLocale, options);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
21
50
|
// src/utils/dates.ts
|
|
22
51
|
function toISODateString(date) {
|
|
23
52
|
const year = date.getFullYear();
|
|
@@ -51,6 +80,10 @@ function parseISOMonthString(monthStr) {
|
|
|
51
80
|
function toISOMonthString(date) {
|
|
52
81
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
|
53
82
|
}
|
|
83
|
+
var MONTH_LABEL_OPTIONS = { month: "long", year: "numeric" };
|
|
84
|
+
function monthLabelFormatter(locale) {
|
|
85
|
+
return intlFormatter(Intl.DateTimeFormat, locale, MONTH_LABEL_OPTIONS, "en");
|
|
86
|
+
}
|
|
54
87
|
|
|
55
88
|
// src/utils/safe_timeout.ts
|
|
56
89
|
var TimerRegistry = class {
|
|
@@ -288,8 +321,7 @@ var CalendarController = class extends Controller {
|
|
|
288
321
|
const month = monthStart.getMonth() + 1;
|
|
289
322
|
if (this.hasLabelTarget) {
|
|
290
323
|
const lang = document.documentElement.lang || "en";
|
|
291
|
-
|
|
292
|
-
this.labelTarget.textContent = formatter.format(monthStart);
|
|
324
|
+
this.labelTarget.textContent = monthLabelFormatter(lang).format(monthStart);
|
|
293
325
|
}
|
|
294
326
|
const days = this.#calculateGridDays(year, month);
|
|
295
327
|
const dayElements = this.dayTargets;
|
|
@@ -90,6 +90,12 @@ var BeforeCacheReset = class _BeforeCacheReset {
|
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
+
// src/utils/event_owner.ts
|
|
94
|
+
function ownerIndex(candidates, node) {
|
|
95
|
+
if (!(node instanceof Node)) return -1;
|
|
96
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
97
|
+
}
|
|
98
|
+
|
|
93
99
|
// src/utils/microtask_coalescer.ts
|
|
94
100
|
var MicrotaskCoalescer = class {
|
|
95
101
|
#run;
|
|
@@ -252,7 +258,7 @@ var CarouselController = class extends Controller {
|
|
|
252
258
|
#handledEvents = /* @__PURE__ */ new WeakSet();
|
|
253
259
|
/**
|
|
254
260
|
* Whether `connect()` has run. Scheduling is already inert outside that window
|
|
255
|
-
* (
|
|
261
|
+
* (`MicrotaskCoalescer`), so this gates the Value callbacks Stimulus
|
|
256
262
|
* delivers ahead of `connect()`.
|
|
257
263
|
*/
|
|
258
264
|
#connected = false;
|
|
@@ -510,9 +516,7 @@ var CarouselController = class extends Controller {
|
|
|
510
516
|
}
|
|
511
517
|
/** Position of the picker that is or contains `node`, or `-1` when none does. */
|
|
512
518
|
#pickerIndexFor(node) {
|
|
513
|
-
return this.pickerTargets
|
|
514
|
-
(picker) => node instanceof Node && (picker === node || picker.contains(node))
|
|
515
|
-
);
|
|
519
|
+
return ownerIndex(this.pickerTargets, node);
|
|
516
520
|
}
|
|
517
521
|
/** Whether a focus transition lands on another control of this same carousel. */
|
|
518
522
|
#focusStaysInside(event) {
|
|
@@ -731,9 +735,7 @@ function hasModifier(event) {
|
|
|
731
735
|
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
732
736
|
}
|
|
733
737
|
function hits(elements, node) {
|
|
734
|
-
return elements
|
|
735
|
-
(element) => node instanceof Node && (element === node || element.contains(node))
|
|
736
|
-
);
|
|
738
|
+
return ownerIndex(elements, node) !== -1;
|
|
737
739
|
}
|
|
738
740
|
function setAttributeIfChanged(element, name, value) {
|
|
739
741
|
if (element.getAttribute(name) !== value) element.setAttribute(name, value);
|
|
@@ -512,7 +512,7 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
512
512
|
* re-deriving from the declarative `open` Value (which would slam a user-opened
|
|
513
513
|
* palette shut). The `open` Value only seeds the initial state of a genuinely
|
|
514
514
|
* fresh render. We normalize to a clean closed baseline first so {@link open}
|
|
515
|
-
* runs its full setup — the
|
|
515
|
+
* runs its full setup — the `FocusTrap` is a fresh instance after a
|
|
516
516
|
* reconnect and must be re-activated.
|
|
517
517
|
*/
|
|
518
518
|
connect() {
|
|
@@ -617,7 +617,7 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
617
617
|
}
|
|
618
618
|
/**
|
|
619
619
|
* Combobox navigation keys (arrows / Home / End / Enter), bound to the input.
|
|
620
|
-
* `Tab` (focus trap) and `Escape` (close) are owned by the
|
|
620
|
+
* `Tab` (focus trap) and `Escape` (close) are owned by the shared `FocusTrap` at
|
|
621
621
|
* the document level, so they work no matter which element inside the dialog has
|
|
622
622
|
* focus — not only the input.
|
|
623
623
|
*/
|
|
@@ -2,6 +2,12 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
2
2
|
|
|
3
3
|
// src/controllers/conditional_fields_controller.ts
|
|
4
4
|
|
|
5
|
+
// src/utils/event_owner.ts
|
|
6
|
+
function ownerIndex(candidates, node) {
|
|
7
|
+
if (!(node instanceof Node)) return -1;
|
|
8
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
// src/utils/focus_candidate.ts
|
|
6
12
|
function inheritsFieldsetDisabled(control) {
|
|
7
13
|
let fieldset = control.closest("fieldset[disabled]");
|
|
@@ -393,7 +399,7 @@ var ConditionalFieldsController = class extends Controller {
|
|
|
393
399
|
}
|
|
394
400
|
/** Whether an authored mutation target belongs to a trigger, region, or managed control. */
|
|
395
401
|
#elementAffectsState(target, triggers, regions) {
|
|
396
|
-
return triggers
|
|
402
|
+
return ownerIndex(triggers, target) !== -1 || ownerIndex(regions, target) !== -1;
|
|
397
403
|
}
|
|
398
404
|
/** Whether a form owns at least one current trigger, including `form=` associations. */
|
|
399
405
|
#hasTriggerOwnedBy(form) {
|
|
@@ -58,6 +58,36 @@ function halfWidthChar(char) {
|
|
|
58
58
|
return char === "\u3000" ? " " : char;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// src/utils/intl_format.ts
|
|
62
|
+
var formatters = /* @__PURE__ */ new WeakMap();
|
|
63
|
+
function intlFormatter(factory, locale, options, fallbackLocale) {
|
|
64
|
+
const cache = cacheFor(factory);
|
|
65
|
+
const key = cacheKey(locale, options, fallbackLocale);
|
|
66
|
+
if (cache.has(key)) return cache.get(key);
|
|
67
|
+
const formatter = construct(factory, locale, options, fallbackLocale);
|
|
68
|
+
cache.set(key, formatter);
|
|
69
|
+
return formatter;
|
|
70
|
+
}
|
|
71
|
+
function cacheFor(factory) {
|
|
72
|
+
const existing = formatters.get(factory);
|
|
73
|
+
if (existing) return existing;
|
|
74
|
+
const created = /* @__PURE__ */ new Map();
|
|
75
|
+
formatters.set(factory, created);
|
|
76
|
+
return created;
|
|
77
|
+
}
|
|
78
|
+
function cacheKey(locale, options, fallbackLocale) {
|
|
79
|
+
return JSON.stringify([locale, options, fallbackLocale]);
|
|
80
|
+
}
|
|
81
|
+
function construct(factory, locale, options, fallbackLocale) {
|
|
82
|
+
try {
|
|
83
|
+
return new factory(locale, options);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (!(error instanceof RangeError)) throw error;
|
|
86
|
+
if (fallbackLocale === void 0) return null;
|
|
87
|
+
return new factory(fallbackLocale, options);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
61
91
|
// src/controllers/currency_input_controller.ts
|
|
62
92
|
var CurrencyInputController = class extends Controller {
|
|
63
93
|
static targets = ["display", "field", "srValue"];
|
|
@@ -309,37 +339,52 @@ var CurrencyInputController = class extends Controller {
|
|
|
309
339
|
* formatters from the validated set.
|
|
310
340
|
*/
|
|
311
341
|
#revalidate() {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
new Intl.NumberFormat(this.localeValue);
|
|
315
|
-
this.#locale = this.localeValue;
|
|
316
|
-
} catch {
|
|
317
|
-
}
|
|
342
|
+
const declaredLocale = intlFormatter(Intl.NumberFormat, this.localeValue, {});
|
|
343
|
+
this.#locale = declaredLocale === null ? "en-US" : this.localeValue;
|
|
318
344
|
const precision = this.precisionValue;
|
|
319
345
|
this.#precision = Number.isInteger(precision) && precision >= 0 && precision <= 100 ? precision : 2;
|
|
320
346
|
this.#currency = "";
|
|
321
347
|
if (this.currencyValue !== "") {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
348
|
+
const declaredCurrency = intlFormatter(Intl.NumberFormat, this.#locale, {
|
|
349
|
+
style: "currency",
|
|
350
|
+
currency: this.currencyValue
|
|
351
|
+
});
|
|
352
|
+
if (declaredCurrency !== null) this.#currency = this.currencyValue;
|
|
327
353
|
}
|
|
328
|
-
this.#grouping =
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
354
|
+
this.#grouping = intlFormatter(
|
|
355
|
+
Intl.NumberFormat,
|
|
356
|
+
this.#locale,
|
|
357
|
+
{ useGrouping: true, maximumFractionDigits: 0 },
|
|
358
|
+
this.#locale
|
|
359
|
+
);
|
|
360
|
+
this.#fixed = intlFormatter(
|
|
361
|
+
Intl.NumberFormat,
|
|
362
|
+
this.#locale,
|
|
363
|
+
{
|
|
364
|
+
useGrouping: true,
|
|
365
|
+
minimumFractionDigits: this.#precision,
|
|
366
|
+
maximumFractionDigits: this.#precision
|
|
367
|
+
},
|
|
368
|
+
this.#locale
|
|
369
|
+
);
|
|
370
|
+
this.#accessible = this.#currency ? intlFormatter(
|
|
371
|
+
Intl.NumberFormat,
|
|
372
|
+
this.#locale,
|
|
373
|
+
{ style: "currency", currency: this.#currency },
|
|
374
|
+
this.#locale
|
|
375
|
+
) : this.#fixed;
|
|
376
|
+
const parts = intlFormatter(Intl.NumberFormat, this.#locale, {}, this.#locale).formatToParts(
|
|
377
|
+
11111.1
|
|
378
|
+
);
|
|
339
379
|
this.#group = parts.find((p) => p.type === "group")?.value ?? ",";
|
|
340
380
|
this.#decimal = parts.find((p) => p.type === "decimal")?.value ?? ".";
|
|
341
381
|
this.#digits.clear();
|
|
342
|
-
const digitFormatter =
|
|
382
|
+
const digitFormatter = intlFormatter(
|
|
383
|
+
Intl.NumberFormat,
|
|
384
|
+
this.#locale,
|
|
385
|
+
{ useGrouping: false },
|
|
386
|
+
this.#locale
|
|
387
|
+
);
|
|
343
388
|
for (let i = 0; i <= 9; i++) {
|
|
344
389
|
const digit = digitFormatter.format(i);
|
|
345
390
|
if (digit !== String(i)) this.#digits.set(digit, String(i));
|
|
@@ -47,6 +47,35 @@ var BeforeCacheReset = class _BeforeCacheReset {
|
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
// src/utils/intl_format.ts
|
|
51
|
+
var formatters = /* @__PURE__ */ new WeakMap();
|
|
52
|
+
function intlFormatter(factory, locale, options, fallbackLocale) {
|
|
53
|
+
const cache = cacheFor(factory);
|
|
54
|
+
const key = cacheKey(locale, options, fallbackLocale);
|
|
55
|
+
if (cache.has(key)) return cache.get(key);
|
|
56
|
+
const formatter = construct(factory, locale, options, fallbackLocale);
|
|
57
|
+
cache.set(key, formatter);
|
|
58
|
+
return formatter;
|
|
59
|
+
}
|
|
60
|
+
function cacheFor(factory) {
|
|
61
|
+
const existing = formatters.get(factory);
|
|
62
|
+
if (existing) return existing;
|
|
63
|
+
const created = /* @__PURE__ */ new Map();
|
|
64
|
+
formatters.set(factory, created);
|
|
65
|
+
return created;
|
|
66
|
+
}
|
|
67
|
+
function cacheKey(locale, options, fallbackLocale) {
|
|
68
|
+
return JSON.stringify([locale, options, fallbackLocale]);
|
|
69
|
+
}
|
|
70
|
+
function construct(factory, locale, options, fallbackLocale) {
|
|
71
|
+
try {
|
|
72
|
+
return new factory(locale, options);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (!(error instanceof RangeError)) throw error;
|
|
75
|
+
return new factory(fallbackLocale, options);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
50
79
|
// src/utils/dates.ts
|
|
51
80
|
function toISODateString(date) {
|
|
52
81
|
const year = date.getFullYear();
|
|
@@ -80,6 +109,10 @@ function parseISOMonthString(monthStr) {
|
|
|
80
109
|
function toISOMonthString(date) {
|
|
81
110
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
|
82
111
|
}
|
|
112
|
+
var MONTH_LABEL_OPTIONS = { month: "long", year: "numeric" };
|
|
113
|
+
function monthLabelFormatter(locale) {
|
|
114
|
+
return intlFormatter(Intl.DateTimeFormat, locale, MONTH_LABEL_OPTIONS, "en");
|
|
115
|
+
}
|
|
83
116
|
|
|
84
117
|
// src/utils/microtask_coalescer.ts
|
|
85
118
|
var MicrotaskCoalescer = class {
|
|
@@ -433,7 +466,7 @@ var DateRangePickerController = class extends Controller {
|
|
|
433
466
|
if (this.#previewDate && !this.#isSelectable(this.#previewDate)) this.#previewDate = "";
|
|
434
467
|
if (this.hasMonthLabelTarget) {
|
|
435
468
|
const lang = document.documentElement.lang || "en";
|
|
436
|
-
const formatter =
|
|
469
|
+
const formatter = monthLabelFormatter(lang);
|
|
437
470
|
this.monthLabelTarget.textContent = formatter.format(new Date(year, month - 1, 1));
|
|
438
471
|
}
|
|
439
472
|
const [rangeStart, rangeEnd] = this.#visualRange();
|
|
@@ -588,15 +621,6 @@ function normalizeISO(value) {
|
|
|
588
621
|
function orderRange(start, end) {
|
|
589
622
|
return start && end && end < start ? [end, start] : [start, end];
|
|
590
623
|
}
|
|
591
|
-
function monthFormatter(locale) {
|
|
592
|
-
const options = { month: "long", year: "numeric" };
|
|
593
|
-
try {
|
|
594
|
-
return new Intl.DateTimeFormat(locale, options);
|
|
595
|
-
} catch (error) {
|
|
596
|
-
if (!(error instanceof RangeError)) throw error;
|
|
597
|
-
return new Intl.DateTimeFormat("en", options);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
624
|
function computePreset(name) {
|
|
601
625
|
const today = /* @__PURE__ */ new Date();
|
|
602
626
|
const todayStr = toISODateString(today);
|
|
@@ -48,6 +48,26 @@ var BeforeCacheReset = class _BeforeCacheReset {
|
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
// src/utils/declared_value.ts
|
|
52
|
+
function parseDeclared(raw, parse, fallback) {
|
|
53
|
+
try {
|
|
54
|
+
return parse(raw);
|
|
55
|
+
} catch {
|
|
56
|
+
return fallback;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function validSelector(element, raw, fallback) {
|
|
60
|
+
if (raw.length === 0) return fallback;
|
|
61
|
+
return parseDeclared(
|
|
62
|
+
raw,
|
|
63
|
+
(selector) => {
|
|
64
|
+
element.matches(selector);
|
|
65
|
+
return selector;
|
|
66
|
+
},
|
|
67
|
+
fallback
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
51
71
|
// src/utils/safe_timeout.ts
|
|
52
72
|
var TimerRegistry = class {
|
|
53
73
|
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
@@ -156,16 +176,7 @@ var DirectUploadController = class extends Controller {
|
|
|
156
176
|
}
|
|
157
177
|
/** Validates `scope` once so the per-event path never parses or throws. */
|
|
158
178
|
scopeValueChanged() {
|
|
159
|
-
|
|
160
|
-
if (selector.length > 0) {
|
|
161
|
-
try {
|
|
162
|
-
this.element.matches(selector);
|
|
163
|
-
this.#scopeSelector = selector;
|
|
164
|
-
return;
|
|
165
|
-
} catch {
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
this.#scopeSelector = "";
|
|
179
|
+
this.#scopeSelector = validSelector(this.element, this.scopeValue, "");
|
|
169
180
|
}
|
|
170
181
|
/** Updates a row's progress and the aggregate, emitting `progress`. */
|
|
171
182
|
#updateProgress(id, percent, name) {
|
|
@@ -586,7 +586,7 @@ var DrawerController = class extends Controller {
|
|
|
586
586
|
* rather than being re-derived from the declarative `open` Value (which would
|
|
587
587
|
* close a user-opened drawer). The `open` Value only seeds a genuinely fresh
|
|
588
588
|
* render. We normalize to a clean closed baseline first so {@link open} runs its
|
|
589
|
-
* full reveal + trap activation — the
|
|
589
|
+
* full reveal + trap activation — the `FocusTrap` is inactive after a
|
|
590
590
|
* disconnect and must be re-activated.
|
|
591
591
|
*/
|
|
592
592
|
connect() {
|
|
@@ -719,7 +719,7 @@ var DrawerController = class extends Controller {
|
|
|
719
719
|
* Runs once the close transition has finished: applies `hidden` to the panel
|
|
720
720
|
* and overlay, then reverts the modal side effects (scroll lock, background
|
|
721
721
|
* `inert`, keydown listener) and restores focus to the opener. Deferring the
|
|
722
|
-
*
|
|
722
|
+
* `FocusTrap` teardown to here — rather than at {@link close} time — keeps
|
|
723
723
|
* the background unreachable and focus trapped for the whole exit animation.
|
|
724
724
|
*/
|
|
725
725
|
#applyHidden(panel) {
|
|
@@ -100,6 +100,12 @@ var BeforeCacheReset = class _BeforeCacheReset {
|
|
|
100
100
|
}
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
+
// src/utils/event_owner.ts
|
|
104
|
+
function ownerIndex(candidates, node) {
|
|
105
|
+
if (!(node instanceof Node)) return -1;
|
|
106
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
107
|
+
}
|
|
108
|
+
|
|
103
109
|
// src/utils/microtask_coalescer.ts
|
|
104
110
|
var MicrotaskCoalescer = class {
|
|
105
111
|
#run;
|
|
@@ -357,7 +363,7 @@ var FormFieldController = class _FormFieldController extends Controller {
|
|
|
357
363
|
return this.errorTargets.includes(target);
|
|
358
364
|
return record.attributeName === "id" && [...this.descriptionTargets, ...this.errorTargets].includes(target);
|
|
359
365
|
}
|
|
360
|
-
return this.errorTargets
|
|
366
|
+
return ownerIndex(this.errorTargets, target) !== -1;
|
|
361
367
|
}
|
|
362
368
|
/** Returns borrowed control ARIA before Turbo snapshots the page. */
|
|
363
369
|
#rewindForCache() {
|
|
@@ -49,6 +49,30 @@ var CompositionTracker = class {
|
|
|
49
49
|
};
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
// src/utils/declared_value.ts
|
|
53
|
+
function parseDeclared(raw, parse, fallback) {
|
|
54
|
+
try {
|
|
55
|
+
return parse(raw);
|
|
56
|
+
} catch {
|
|
57
|
+
return fallback;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function compileRegExp(source, anchor = "none") {
|
|
61
|
+
return parseDeclared(
|
|
62
|
+
source,
|
|
63
|
+
(text) => {
|
|
64
|
+
const bare = new RegExp(text);
|
|
65
|
+
return anchor === "exact" ? new RegExp(`^(?:${text})$`) : bare;
|
|
66
|
+
},
|
|
67
|
+
null
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
function parseJsonObject(raw) {
|
|
71
|
+
const parsed = parseDeclared(raw, (text) => JSON.parse(text), null);
|
|
72
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
73
|
+
return parsed;
|
|
74
|
+
}
|
|
75
|
+
|
|
52
76
|
// src/utils/focus_candidate.ts
|
|
53
77
|
function inheritsFieldsetDisabled(control) {
|
|
54
78
|
let fieldset = control.closest("fieldset[disabled]");
|
|
@@ -118,22 +142,19 @@ function applyMask(value, pattern, tokens) {
|
|
|
118
142
|
}
|
|
119
143
|
function compileTokens(declaration) {
|
|
120
144
|
const map = /* @__PURE__ */ new Map();
|
|
121
|
-
for (const [key, source] of Object.entries(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
145
|
+
for (const [key, source] of Object.entries(DEFAULT_TOKENS)) {
|
|
146
|
+
const compiled = compileRegExp(source, "exact");
|
|
147
|
+
if (compiled) map.set(key, compiled);
|
|
148
|
+
}
|
|
149
|
+
for (const [key, source] of Object.entries(parseTokens(declaration))) {
|
|
150
|
+
const compiled = compileRegExp(source, "exact");
|
|
151
|
+
if (compiled) map.set(key, compiled);
|
|
126
152
|
}
|
|
127
153
|
return map;
|
|
128
154
|
}
|
|
129
155
|
function parseTokens(declaration) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
parsed = JSON.parse(declaration);
|
|
133
|
-
} catch {
|
|
134
|
-
return {};
|
|
135
|
-
}
|
|
136
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
156
|
+
const parsed = parseJsonObject(declaration);
|
|
157
|
+
if (parsed === null) return {};
|
|
137
158
|
const tokens = {};
|
|
138
159
|
for (const [key, source] of Object.entries(parsed)) {
|
|
139
160
|
if (typeof source === "string") tokens[key] = source;
|
|
@@ -2,6 +2,15 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
2
2
|
|
|
3
3
|
// src/controllers/intersection_controller.ts
|
|
4
4
|
|
|
5
|
+
// src/utils/declared_value.ts
|
|
6
|
+
function parseDeclared(raw, parse, fallback) {
|
|
7
|
+
try {
|
|
8
|
+
return parse(raw);
|
|
9
|
+
} catch {
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
5
14
|
// src/utils/intersection_watcher.ts
|
|
6
15
|
function isBeforeRootStart(entry) {
|
|
7
16
|
const rect = entry.boundingClientRect;
|
|
@@ -11,11 +20,7 @@ function isBeforeRootStart(entry) {
|
|
|
11
20
|
}
|
|
12
21
|
function queryRoot(selector) {
|
|
13
22
|
if (!selector) return null;
|
|
14
|
-
|
|
15
|
-
return document.querySelector(selector);
|
|
16
|
-
} catch {
|
|
17
|
-
}
|
|
18
|
-
return null;
|
|
23
|
+
return parseDeclared(selector, (raw) => document.querySelector(raw), null);
|
|
19
24
|
}
|
|
20
25
|
var IntersectionWatcher = class {
|
|
21
26
|
#onEntries;
|
|
@@ -2,14 +2,19 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
2
2
|
|
|
3
3
|
// src/controllers/lazy_frame_controller.ts
|
|
4
4
|
|
|
5
|
-
// src/utils/
|
|
6
|
-
function
|
|
7
|
-
if (!selector) return null;
|
|
5
|
+
// src/utils/declared_value.ts
|
|
6
|
+
function parseDeclared(raw, parse, fallback) {
|
|
8
7
|
try {
|
|
9
|
-
return
|
|
8
|
+
return parse(raw);
|
|
10
9
|
} catch {
|
|
10
|
+
return fallback;
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/utils/intersection_watcher.ts
|
|
15
|
+
function queryRoot(selector) {
|
|
16
|
+
if (!selector) return null;
|
|
17
|
+
return parseDeclared(selector, (raw) => document.querySelector(raw), null);
|
|
13
18
|
}
|
|
14
19
|
var IntersectionWatcher = class {
|
|
15
20
|
#onEntries;
|
|
@@ -2,6 +2,35 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
2
2
|
|
|
3
3
|
// src/controllers/local_time_controller.ts
|
|
4
4
|
|
|
5
|
+
// src/utils/intl_format.ts
|
|
6
|
+
var formatters = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
function intlFormatter(factory, locale, options, fallbackLocale) {
|
|
8
|
+
const cache = cacheFor(factory);
|
|
9
|
+
const key = cacheKey(locale, options, fallbackLocale);
|
|
10
|
+
if (cache.has(key)) return cache.get(key);
|
|
11
|
+
const formatter = construct(factory, locale, options);
|
|
12
|
+
cache.set(key, formatter);
|
|
13
|
+
return formatter;
|
|
14
|
+
}
|
|
15
|
+
function cacheFor(factory) {
|
|
16
|
+
const existing = formatters.get(factory);
|
|
17
|
+
if (existing) return existing;
|
|
18
|
+
const created = /* @__PURE__ */ new Map();
|
|
19
|
+
formatters.set(factory, created);
|
|
20
|
+
return created;
|
|
21
|
+
}
|
|
22
|
+
function cacheKey(locale, options, fallbackLocale) {
|
|
23
|
+
return JSON.stringify([locale, options, fallbackLocale]);
|
|
24
|
+
}
|
|
25
|
+
function construct(factory, locale, options, fallbackLocale) {
|
|
26
|
+
try {
|
|
27
|
+
return new factory(locale, options);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (!(error instanceof RangeError)) throw error;
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
5
34
|
// src/utils/microtask_coalescer.ts
|
|
6
35
|
var MicrotaskCoalescer = class {
|
|
7
36
|
#run;
|
|
@@ -150,8 +179,9 @@ var LocalTimeController = class extends Controller {
|
|
|
150
179
|
/**
|
|
151
180
|
* Formats `date` with `Intl.DateTimeFormat`, including each style only when it
|
|
152
181
|
* is a valid keyword (so a consumer can show date-only or time-only by clearing
|
|
153
|
-
* the other). Returns `null` when neither style is usable or
|
|
154
|
-
* the caller can leave the
|
|
182
|
+
* the other). Returns `null` when neither style is usable or the declaration
|
|
183
|
+
* names a locale or time zone `Intl` rejects, so the caller can leave the
|
|
184
|
+
* authored text untouched.
|
|
155
185
|
*/
|
|
156
186
|
#applyFormat(date, dateStyle, timeStyle) {
|
|
157
187
|
const options = {
|
|
@@ -160,11 +190,8 @@ var LocalTimeController = class extends Controller {
|
|
|
160
190
|
};
|
|
161
191
|
if (options.dateStyle === void 0 && options.timeStyle === void 0) return null;
|
|
162
192
|
if (this.timeZoneValue.length > 0) options.timeZone = this.timeZoneValue;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
} catch {
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
193
|
+
const formatter = intlFormatter(Intl.DateTimeFormat, this.#locale, options);
|
|
194
|
+
return formatter === null ? null : formatter.format(date);
|
|
168
195
|
}
|
|
169
196
|
/** Locale precedence: the value, then the nearest `lang` up the ancestor chain. */
|
|
170
197
|
get #locale() {
|
|
@@ -153,7 +153,7 @@ var MasonryController = class extends Controller {
|
|
|
153
153
|
*
|
|
154
154
|
* The removal is queued rather than immediate because teardown reports every
|
|
155
155
|
* target as disconnected: doing it here would strip the whole grid just before
|
|
156
|
-
* a Turbo snapshot is taken.
|
|
156
|
+
* a Turbo snapshot is taken. The coalescer's `cancel` drops the queue
|
|
157
157
|
* with the pass, so only a genuine target change reaches it.
|
|
158
158
|
*/
|
|
159
159
|
itemTargetDisconnected(item) {
|
|
@@ -98,6 +98,15 @@ var EscapeLayer = class _EscapeLayer {
|
|
|
98
98
|
}
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
+
// src/utils/event_owner.ts
|
|
102
|
+
function ownerIndex(candidates, node) {
|
|
103
|
+
if (!(node instanceof Node)) return -1;
|
|
104
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
105
|
+
}
|
|
106
|
+
function ownerOf(candidates, node) {
|
|
107
|
+
return candidates[ownerIndex(candidates, node)] ?? null;
|
|
108
|
+
}
|
|
109
|
+
|
|
101
110
|
// src/utils/safe_timeout.ts
|
|
102
111
|
var TimerRegistry = class {
|
|
103
112
|
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
@@ -338,8 +347,7 @@ var MenuController = class extends Controller {
|
|
|
338
347
|
};
|
|
339
348
|
/** The item target that is, or contains, `node`; `null` when it is neither. */
|
|
340
349
|
#itemFrom(node) {
|
|
341
|
-
|
|
342
|
-
return this.itemTargets.find((item) => item === node || item.contains(node)) ?? null;
|
|
350
|
+
return ownerOf(this.itemTargets, node);
|
|
343
351
|
}
|
|
344
352
|
/**
|
|
345
353
|
* First look at every click inside the controller, and the only point at which
|
|
@@ -103,6 +103,15 @@ var EscapeLayer = class _EscapeLayer {
|
|
|
103
103
|
}
|
|
104
104
|
};
|
|
105
105
|
|
|
106
|
+
// src/utils/event_owner.ts
|
|
107
|
+
function ownerIndex(candidates, node) {
|
|
108
|
+
if (!(node instanceof Node)) return -1;
|
|
109
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
110
|
+
}
|
|
111
|
+
function ownerOf(candidates, node) {
|
|
112
|
+
return candidates[ownerIndex(candidates, node)] ?? null;
|
|
113
|
+
}
|
|
114
|
+
|
|
106
115
|
// src/utils/roving_tabindex.ts
|
|
107
116
|
var RovingTabindex = class {
|
|
108
117
|
/** Returns the current ordered item elements; called on every operation. */
|
|
@@ -577,8 +586,7 @@ var MenubarController = class extends Controller {
|
|
|
577
586
|
* menu without moving focus.
|
|
578
587
|
*/
|
|
579
588
|
#dismissOpenMenu() {
|
|
580
|
-
const
|
|
581
|
-
const menu = this.menuTargets.find((candidate) => candidate.contains(active));
|
|
589
|
+
const menu = ownerOf(this.menuTargets, document.activeElement);
|
|
582
590
|
if (menu) {
|
|
583
591
|
const top = this.#topFor(menu);
|
|
584
592
|
this.#closeMenu(top);
|