stimeo-ui 0.13.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 +39 -5
- data/dist/controllers/calendar_controller.js +31 -7
- data/dist/controllers/carousel_controller.js +8 -6
- 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 +31 -7
- data/dist/controllers/direct_upload_controller.js +21 -10
- 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/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/toggle_group_controller.js +11 -3
- data/dist/controllers/toolbar_controller.js +7 -3
- data/dist/index.js +165 -171
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8220bc5fa861ec2aa75c7abeb2fcb6758a4ba6b56606b77d5bc6d948c9550a09
|
|
4
|
+
data.tar.gz: 95b3643b1a9bbdfdf7d556b5952c1431e6adc1ae557e5d0cf5f40b102276593a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 837c894e5a64b823b2afc662dc4cb6e74daeedb04bee3d348bbd56127ee544cb31247f0534ffca2c60a1f676a2421ec2e3dbe0bef6ecbaeed85eac17f41ac1e1
|
|
7
|
+
data.tar.gz: 22e7cfc49254e48c27915cb9b83f632ea1348db396046a5a41be5513c8a8e4f01f00feee422c582c2423569d1cdf73228201ffc848554bb54cd1d927e18d9550
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
While the version is `0.x`, the public API (the `stimeo--*` data attributes) may
|
|
8
8
|
change between releases.
|
|
9
9
|
|
|
10
|
+
## [0.14.0] - 2026-09-19
|
|
11
|
+
|
|
12
|
+
Minor release with no new components. input-mask changes what a broken `tokens`
|
|
13
|
+
source falls back to, and currency-input, local-time, and relative-time narrow
|
|
14
|
+
which failures they absorb, so read Changed before upgrading. The Inspector
|
|
15
|
+
manifest stays on schema v12.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Inspector: `missing-action-event` (error) — a `data-action` that omits the
|
|
20
|
+
event on an element with no default one binds nothing, while the markup still
|
|
21
|
+
reads as wired. The elements that have a default are `<a>`, `<button>`,
|
|
22
|
+
`<details>`, `<form>`, `<input>`, `<select>`, and `<textarea>`.
|
|
23
|
+
- Inspector: `valueSyntaxConstraints` reports a `stimeo--otp` `pattern` that is
|
|
24
|
+
not a regular expression, and a `stimeo--input-mask` `tokens` that is not a
|
|
25
|
+
JSON object of them, before the page runs.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- input-mask: a `tokens` source that does not compile leaves that key on its
|
|
30
|
+
default (`9`, `a`, `*`) instead of dropping the key, and a source that
|
|
31
|
+
compiles only against the anchors the controller adds — `0)|(1` — is refused.
|
|
32
|
+
- currency-input, local-time, and relative-time absorb only what a declaration
|
|
33
|
+
can get wrong: a locale, time zone, currency code, or digit count `Intl`
|
|
34
|
+
rejects. Any other failure surfaces.
|
|
35
|
+
- Inspector: a `data-action` that omits the event resolves to the one Stimulus
|
|
36
|
+
binds for that element, so a shorthand on a `<form>` reports
|
|
37
|
+
`missing-action-completion` where it passed in silence.
|
|
38
|
+
- The markup examples in the published types name the event
|
|
39
|
+
(`click->stimeo--dialog#open`).
|
|
40
|
+
|
|
10
41
|
## [0.13.0] - 2026-09-13
|
|
11
42
|
|
|
12
43
|
Minor release with no new components. The three server-bound components are
|
|
@@ -21,11 +52,13 @@ so read Changed before upgrading. The Inspector manifest stays on schema v12.
|
|
|
21
52
|
A delta carrying your own `id` is applied unless an optimistic add of the same
|
|
22
53
|
size is still outstanding. Runtime changes to `channel` / `params`
|
|
23
54
|
re-subscribe.
|
|
24
|
-
- presence: a leave notice is sent by the last element claiming
|
|
25
|
-
than by every element
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
55
|
+
- presence: on `disconnect()` a leave notice is sent by the last element claiming
|
|
56
|
+
that `id` rather than by every element; `pagehide` still sends from each one,
|
|
57
|
+
because the whole page is going away. A `channel` key in `params` no longer
|
|
58
|
+
overrides the declared `channel`, `heartbeat` / `timeout` fall back to their
|
|
59
|
+
defaults unless finite, positive and within the browser timer ceiling
|
|
60
|
+
(2147483647 ms) — typing-indicator's `timeout` takes the same bound — and
|
|
61
|
+
`template` clones only its root element.
|
|
29
62
|
- optimistic: `turbo:submit-end` is matched to the submission it names instead of
|
|
30
63
|
to any ending that reaches the element, so a submission whose ending never
|
|
31
64
|
arrives no longer strands the element. The `data-optimistic-toggled` marker
|
|
@@ -1035,6 +1068,7 @@ Initial public alpha: 101 behavior-only, accessible Stimulus controllers driven
|
|
|
1035
1068
|
by `data-*` attributes, shipping no CSS. Published to npm (with provenance) and
|
|
1036
1069
|
RubyGems.
|
|
1037
1070
|
|
|
1071
|
+
[0.14.0]: https://github.com/taiyaky/stimeo-ui/releases/tag/v0.14.0
|
|
1038
1072
|
[0.13.0]: https://github.com/taiyaky/stimeo-ui/releases/tag/v0.13.0
|
|
1039
1073
|
[0.12.0]: https://github.com/taiyaky/stimeo-ui/releases/tag/v0.12.0
|
|
1040
1074
|
[0.11.0]: https://github.com/taiyaky/stimeo-ui/releases/tag/v0.11.0
|
|
@@ -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,14 +80,9 @@ 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" };
|
|
54
84
|
function monthLabelFormatter(locale) {
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
return new Intl.DateTimeFormat(locale, options);
|
|
58
|
-
} catch (error) {
|
|
59
|
-
if (!(error instanceof RangeError)) throw error;
|
|
60
|
-
return new Intl.DateTimeFormat("en", options);
|
|
61
|
-
}
|
|
85
|
+
return intlFormatter(Intl.DateTimeFormat, locale, MONTH_LABEL_OPTIONS, "en");
|
|
62
86
|
}
|
|
63
87
|
|
|
64
88
|
// src/utils/safe_timeout.ts
|
|
@@ -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;
|
|
@@ -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);
|
|
@@ -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,14 +109,9 @@ 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" };
|
|
83
113
|
function monthLabelFormatter(locale) {
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
return new Intl.DateTimeFormat(locale, options);
|
|
87
|
-
} catch (error) {
|
|
88
|
-
if (!(error instanceof RangeError)) throw error;
|
|
89
|
-
return new Intl.DateTimeFormat("en", options);
|
|
90
|
-
}
|
|
114
|
+
return intlFormatter(Intl.DateTimeFormat, locale, MONTH_LABEL_OPTIONS, "en");
|
|
91
115
|
}
|
|
92
116
|
|
|
93
117
|
// src/utils/microtask_coalescer.ts
|
|
@@ -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) {
|
|
@@ -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);
|