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
|
@@ -103,6 +103,12 @@ 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
|
+
|
|
106
112
|
// src/utils/safe_timeout.ts
|
|
107
113
|
var TimerRegistry = class {
|
|
108
114
|
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
@@ -395,7 +401,7 @@ var NavigationMenuController = class extends Controller {
|
|
|
395
401
|
*/
|
|
396
402
|
#onPointerLeave = (event) => {
|
|
397
403
|
const next = event instanceof MouseEvent ? event.relatedTarget : null;
|
|
398
|
-
if (
|
|
404
|
+
if (ownerIndex(this.#hoverElements, next) !== -1) return;
|
|
399
405
|
this.#hoveredTrigger = null;
|
|
400
406
|
this.#hoverTimers.clearAll();
|
|
401
407
|
this.#hoverTimers.set(() => this.#closeFromHover(), this.hoverDelayValue);
|
|
@@ -465,7 +471,7 @@ var NavigationMenuController = class extends Controller {
|
|
|
465
471
|
*/
|
|
466
472
|
get #hoverElements() {
|
|
467
473
|
const areas = this.hoverAreaTargets;
|
|
468
|
-
const covered = (element) => areas
|
|
474
|
+
const covered = (element) => ownerIndex(areas, element) !== -1;
|
|
469
475
|
return [
|
|
470
476
|
...areas,
|
|
471
477
|
...this.triggerTargets.filter((trigger) => !covered(trigger)),
|
|
@@ -152,6 +152,25 @@ var CompositionTracker = class {
|
|
|
152
152
|
};
|
|
153
153
|
};
|
|
154
154
|
|
|
155
|
+
// src/utils/declared_value.ts
|
|
156
|
+
function parseDeclared(raw, parse, fallback) {
|
|
157
|
+
try {
|
|
158
|
+
return parse(raw);
|
|
159
|
+
} catch {
|
|
160
|
+
return fallback;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function compileRegExp(source, anchor = "none") {
|
|
164
|
+
return parseDeclared(
|
|
165
|
+
source,
|
|
166
|
+
(text) => {
|
|
167
|
+
const bare = new RegExp(text);
|
|
168
|
+
return anchor === "exact" ? new RegExp(`^(?:${text})$`) : bare;
|
|
169
|
+
},
|
|
170
|
+
null
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
155
174
|
// src/utils/half_width.ts
|
|
156
175
|
var FULL_WIDTH_SHIFT = 65248;
|
|
157
176
|
function halfWidthChar(char) {
|
|
@@ -201,13 +220,7 @@ var MicrotaskCoalescer = class {
|
|
|
201
220
|
|
|
202
221
|
// src/controllers/otp_controller.ts
|
|
203
222
|
var DEFAULT_PATTERN = "[0-9]";
|
|
204
|
-
|
|
205
|
-
try {
|
|
206
|
-
return new RegExp(`^${source}$`);
|
|
207
|
-
} catch {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
223
|
+
var DEFAULT_MATCHER = new RegExp(`^(?:${DEFAULT_PATTERN})$`);
|
|
211
224
|
function hasModifier(event) {
|
|
212
225
|
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
213
226
|
}
|
|
@@ -222,7 +235,7 @@ var OtpController = class extends Controller {
|
|
|
222
235
|
static actions = ["onInput", "onKeydown", "onPaste", "onPointerDown", "clear"];
|
|
223
236
|
static events = ["change", "complete", "invalid", "reconcile"];
|
|
224
237
|
/** Validated matcher; the hot path never compiles a raw declaration. */
|
|
225
|
-
#pattern =
|
|
238
|
+
#pattern = DEFAULT_MATCHER;
|
|
226
239
|
/** Source of {@link #pattern}, reported in `invalid` so consumers can word it. */
|
|
227
240
|
#patternSource = DEFAULT_PATTERN;
|
|
228
241
|
/** Public state carried by the last dispatch; keeps a no-op sync silent. */
|
|
@@ -295,9 +308,9 @@ var OtpController = class extends Controller {
|
|
|
295
308
|
* the new pattern no longer accepts, so the combined value stays interpretable.
|
|
296
309
|
*/
|
|
297
310
|
patternValueChanged() {
|
|
298
|
-
const compiled =
|
|
311
|
+
const compiled = compileRegExp(this.patternValue, "exact");
|
|
299
312
|
this.#patternSource = compiled ? this.patternValue : DEFAULT_PATTERN;
|
|
300
|
-
this.#pattern = compiled ??
|
|
313
|
+
this.#pattern = compiled ?? DEFAULT_MATCHER;
|
|
301
314
|
if (!this.#connected) return;
|
|
302
315
|
let dropped = false;
|
|
303
316
|
for (const field of this.fieldTargets) {
|
|
@@ -31,6 +31,15 @@ var BeforeCacheReset = class _BeforeCacheReset {
|
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
// src/utils/event_owner.ts
|
|
35
|
+
function ownerIndex(candidates, node) {
|
|
36
|
+
if (!(node instanceof Node)) return -1;
|
|
37
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
38
|
+
}
|
|
39
|
+
function ownerOf(candidates, node) {
|
|
40
|
+
return candidates[ownerIndex(candidates, node)] ?? null;
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
// src/utils/focus_candidate.ts
|
|
35
44
|
function inheritsFieldsetDisabled(control) {
|
|
36
45
|
let fieldset = control.closest("fieldset[disabled]");
|
|
@@ -502,7 +511,7 @@ var OverflowMenuController = class extends Controller {
|
|
|
502
511
|
}
|
|
503
512
|
/** The managed item that is, or contains, `el` — focus rescue works on either. */
|
|
504
513
|
#ownerOf(el) {
|
|
505
|
-
return this.#items
|
|
514
|
+
return ownerOf(this.#items, el);
|
|
506
515
|
}
|
|
507
516
|
/** Whether the item takes up room in the bar; an authored `hidden` one does not. */
|
|
508
517
|
#rendered(item) {
|
|
@@ -63,6 +63,15 @@ var DetachGate = class _DetachGate {
|
|
|
63
63
|
}
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
// src/utils/event_owner.ts
|
|
67
|
+
function ownerIndex(candidates, node) {
|
|
68
|
+
if (!(node instanceof Node)) return -1;
|
|
69
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
70
|
+
}
|
|
71
|
+
function ownerOf(candidates, node) {
|
|
72
|
+
return candidates[ownerIndex(candidates, node)] ?? null;
|
|
73
|
+
}
|
|
74
|
+
|
|
66
75
|
// src/controllers/pointer_drag_controller.ts
|
|
67
76
|
var NATIVE_KEY_OWNERS = "input, textarea, select, button, a[href], summary, [contenteditable]";
|
|
68
77
|
var PointerDragController = class _PointerDragController extends Controller {
|
|
@@ -406,9 +415,7 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
406
415
|
}
|
|
407
416
|
/** Resolves the handle owning an event target (the handle or a descendant). */
|
|
408
417
|
#handleFor(target) {
|
|
409
|
-
|
|
410
|
-
if (!node) return null;
|
|
411
|
-
return this.#handles().find((handle) => handle === node || handle.contains(node)) ?? null;
|
|
418
|
+
return ownerOf(this.#handles(), target);
|
|
412
419
|
}
|
|
413
420
|
#pointerTypeOf(event) {
|
|
414
421
|
const type = event.pointerType;
|
|
@@ -2,6 +2,26 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
2
2
|
|
|
3
3
|
// src/controllers/portal_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
|
+
function validSelector(element, raw, fallback) {
|
|
14
|
+
if (raw.length === 0) return fallback;
|
|
15
|
+
return parseDeclared(
|
|
16
|
+
raw,
|
|
17
|
+
(selector) => {
|
|
18
|
+
element.matches(selector);
|
|
19
|
+
return selector;
|
|
20
|
+
},
|
|
21
|
+
fallback
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
5
25
|
// src/utils/detach_gate.ts
|
|
6
26
|
var DetachGate = class _DetachGate {
|
|
7
27
|
/** Set while a probe is queued, waiting for a reconnect to cancel it. */
|
|
@@ -81,16 +101,7 @@ var PortalController = class extends Controller {
|
|
|
81
101
|
* instead, which reads as a mistake and can be traced back to the declaration.
|
|
82
102
|
*/
|
|
83
103
|
toValueChanged() {
|
|
84
|
-
|
|
85
|
-
if (selector.length > 0) {
|
|
86
|
-
try {
|
|
87
|
-
this.element.matches(selector);
|
|
88
|
-
this.#toSelector = selector;
|
|
89
|
-
return;
|
|
90
|
-
} catch {
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
this.#toSelector = "body";
|
|
104
|
+
this.#toSelector = validSelector(this.element, this.toValue, "body");
|
|
94
105
|
}
|
|
95
106
|
connect() {
|
|
96
107
|
this.#gate.cancel();
|
|
@@ -20,6 +20,15 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
20
20
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// src/utils/event_owner.ts
|
|
24
|
+
function ownerIndex(candidates, node) {
|
|
25
|
+
if (!(node instanceof Node)) return -1;
|
|
26
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
27
|
+
}
|
|
28
|
+
function ownerOf(candidates, node) {
|
|
29
|
+
return candidates[ownerIndex(candidates, node)] ?? null;
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
// src/utils/focus_candidate.ts
|
|
24
33
|
function inheritsFieldsetDisabled(control) {
|
|
25
34
|
let fieldset = control.closest("fieldset[disabled]");
|
|
@@ -479,9 +488,8 @@ var RadioGroupController = class extends Controller {
|
|
|
479
488
|
}
|
|
480
489
|
/** Finds this group's radio containing an event target, excluding nested groups. */
|
|
481
490
|
#radioForEventTarget(target) {
|
|
482
|
-
if (!this.#ownsEventTarget(target)) return
|
|
483
|
-
|
|
484
|
-
return this.radioTargets.find((radio) => radio === node || radio.contains(node));
|
|
491
|
+
if (!this.#ownsEventTarget(target)) return null;
|
|
492
|
+
return ownerOf(this.radioTargets, target);
|
|
485
493
|
}
|
|
486
494
|
/** Whether the closest Radio Group scope around a target is this instance. */
|
|
487
495
|
#ownsEventTarget(target) {
|
|
@@ -31,6 +31,35 @@ var BeforeCacheReset = class _BeforeCacheReset {
|
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
// src/utils/intl_format.ts
|
|
35
|
+
var formatters = /* @__PURE__ */ new WeakMap();
|
|
36
|
+
function intlFormatter(factory, locale, options, fallbackLocale) {
|
|
37
|
+
const cache = cacheFor(factory);
|
|
38
|
+
const key = cacheKey(locale, options, fallbackLocale);
|
|
39
|
+
if (cache.has(key)) return cache.get(key);
|
|
40
|
+
const formatter = construct(factory, locale, options);
|
|
41
|
+
cache.set(key, formatter);
|
|
42
|
+
return formatter;
|
|
43
|
+
}
|
|
44
|
+
function cacheFor(factory) {
|
|
45
|
+
const existing = formatters.get(factory);
|
|
46
|
+
if (existing) return existing;
|
|
47
|
+
const created = /* @__PURE__ */ new Map();
|
|
48
|
+
formatters.set(factory, created);
|
|
49
|
+
return created;
|
|
50
|
+
}
|
|
51
|
+
function cacheKey(locale, options, fallbackLocale) {
|
|
52
|
+
return JSON.stringify([locale, options, fallbackLocale]);
|
|
53
|
+
}
|
|
54
|
+
function construct(factory, locale, options, fallbackLocale) {
|
|
55
|
+
try {
|
|
56
|
+
return new factory(locale, options);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (!(error instanceof RangeError)) throw error;
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
34
63
|
// src/utils/microtask_coalescer.ts
|
|
35
64
|
var MicrotaskCoalescer = class {
|
|
36
65
|
#run;
|
|
@@ -128,6 +157,7 @@ var UNITS = [
|
|
|
128
157
|
{ limit: 31557600, unit: "month", ms: 26298e5 },
|
|
129
158
|
YEAR_SCALE
|
|
130
159
|
];
|
|
160
|
+
var RELATIVE_OPTIONS = { numeric: "auto" };
|
|
131
161
|
var RelativeTimeController = class extends Controller {
|
|
132
162
|
static values = {
|
|
133
163
|
locale: { type: String, default: "" },
|
|
@@ -235,11 +265,7 @@ var RelativeTimeController = class extends Controller {
|
|
|
235
265
|
* when the runtime rejects that locale.
|
|
236
266
|
*/
|
|
237
267
|
get #formatter() {
|
|
238
|
-
|
|
239
|
-
return new Intl.RelativeTimeFormat(this.#locale, { numeric: "auto" });
|
|
240
|
-
} catch {
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
268
|
+
return intlFormatter(Intl.RelativeTimeFormat, this.#locale, RELATIVE_OPTIONS);
|
|
243
269
|
}
|
|
244
270
|
/** Locale precedence: the value, then the nearest `lang` up the ancestor chain. */
|
|
245
271
|
get #locale() {
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
+
// src/controllers/reset_before_cache_controller.ts
|
|
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
|
+
function validSelector(element, raw, fallback) {
|
|
14
|
+
if (raw.length === 0) return fallback;
|
|
15
|
+
return parseDeclared(
|
|
16
|
+
raw,
|
|
17
|
+
(selector) => {
|
|
18
|
+
element.matches(selector);
|
|
19
|
+
return selector;
|
|
20
|
+
},
|
|
21
|
+
fallback
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
3
25
|
// src/controllers/reset_before_cache_controller.ts
|
|
4
26
|
var STATELESS_INPUT_TYPES = /* @__PURE__ */ new Set(["hidden", "submit", "reset", "button", "image"]);
|
|
5
27
|
function restoreField(element) {
|
|
@@ -43,16 +65,7 @@ var ResetBeforeCacheController = class extends Controller {
|
|
|
43
65
|
* silently taking the sweep down.
|
|
44
66
|
*/
|
|
45
67
|
scopeValueChanged() {
|
|
46
|
-
|
|
47
|
-
if (selector.length > 0) {
|
|
48
|
-
try {
|
|
49
|
-
this.element.matches(selector);
|
|
50
|
-
this.#scopeSelector = selector;
|
|
51
|
-
return;
|
|
52
|
-
} catch {
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
this.#scopeSelector = "";
|
|
68
|
+
this.#scopeSelector = validSelector(this.element, this.scopeValue, "");
|
|
56
69
|
}
|
|
57
70
|
connect() {
|
|
58
71
|
document.addEventListener("turbo:before-cache", this.#onBeforeCache);
|
|
@@ -8,6 +8,12 @@ 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/event_owner.ts
|
|
12
|
+
function ownerIndex(candidates, node) {
|
|
13
|
+
if (!(node instanceof Node)) return -1;
|
|
14
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
// src/utils/microtask_coalescer.ts
|
|
12
18
|
var MicrotaskCoalescer = class {
|
|
13
19
|
#run;
|
|
@@ -154,8 +160,7 @@ var ResizableController = class extends Controller {
|
|
|
154
160
|
const panes = [];
|
|
155
161
|
if (this.hasPrimaryTarget) panes.push(this.primaryTarget);
|
|
156
162
|
if (this.hasSecondaryTarget) panes.push(this.secondaryTarget);
|
|
157
|
-
const
|
|
158
|
-
const current = panes.findIndex((pane) => target instanceof Node && pane.contains(target));
|
|
163
|
+
const current = ownerIndex(panes, event.target);
|
|
159
164
|
const next = panes[(current + 1) % panes.length];
|
|
160
165
|
if (!next) return;
|
|
161
166
|
event.preventDefault();
|
|
@@ -16,6 +16,12 @@ function hasModifierChord(event) {
|
|
|
16
16
|
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// src/utils/event_owner.ts
|
|
20
|
+
function ownerIndex(candidates, node) {
|
|
21
|
+
if (!(node instanceof Node)) return -1;
|
|
22
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
// src/utils/focus_candidate.ts
|
|
20
26
|
function inheritsFieldsetDisabled(control) {
|
|
21
27
|
let fieldset = control.closest("fieldset[disabled]");
|
|
@@ -182,9 +188,7 @@ var RovingController = class extends Controller {
|
|
|
182
188
|
};
|
|
183
189
|
/** Resolves the item index owning an event target (the item or a descendant). */
|
|
184
190
|
#indexOf(target) {
|
|
185
|
-
|
|
186
|
-
if (!node) return -1;
|
|
187
|
-
return this.itemTargets.findIndex((item) => item === node || item.contains(node));
|
|
191
|
+
return ownerIndex(this.itemTargets, target);
|
|
188
192
|
}
|
|
189
193
|
/** Makes `index` the tab stop (optionally focusing it), emitting `change` once. */
|
|
190
194
|
#activate(index, focus) {
|
|
@@ -57,6 +57,26 @@ var BlurDeferral = class {
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
// src/utils/declared_value.ts
|
|
61
|
+
function parseDeclared(raw, parse, fallback) {
|
|
62
|
+
try {
|
|
63
|
+
return parse(raw);
|
|
64
|
+
} catch {
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function validSelector(element, raw, fallback) {
|
|
69
|
+
if (raw.length === 0) return fallback;
|
|
70
|
+
return parseDeclared(
|
|
71
|
+
raw,
|
|
72
|
+
(selector) => {
|
|
73
|
+
element.matches(selector);
|
|
74
|
+
return selector;
|
|
75
|
+
},
|
|
76
|
+
fallback
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
60
80
|
// src/utils/reduced_motion.ts
|
|
61
81
|
function prefersReducedMotion() {
|
|
62
82
|
return typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
@@ -223,11 +243,11 @@ var ScrollVisibilityController = class extends Controller {
|
|
|
223
243
|
}
|
|
224
244
|
/** Validates `root` once so connect never parses a selector that throws. */
|
|
225
245
|
rootValueChanged() {
|
|
226
|
-
this.#rootSelector =
|
|
246
|
+
this.#rootSelector = validSelector(this.element, this.rootValue, "");
|
|
227
247
|
}
|
|
228
248
|
/** Validates `focusSelector` once so `toTop` never parses a selector that throws. */
|
|
229
249
|
focusSelectorValueChanged() {
|
|
230
|
-
this.#focusSelector =
|
|
250
|
+
this.#focusSelector = validSelector(this.element, this.focusSelectorValue, "");
|
|
231
251
|
}
|
|
232
252
|
/** Scrolls the source to the top and, optionally, moves focus to a safe target. */
|
|
233
253
|
toTop() {
|
|
@@ -299,17 +319,6 @@ var ScrollVisibilityController = class extends Controller {
|
|
|
299
319
|
if (focused instanceof HTMLElement && this.elementTarget.contains(focused)) return focused;
|
|
300
320
|
return null;
|
|
301
321
|
}
|
|
302
|
-
/** Returns `declared` when it parses as a selector, and `""` when it does not. */
|
|
303
|
-
#validSelector(declared) {
|
|
304
|
-
if (declared.length > 0) {
|
|
305
|
-
try {
|
|
306
|
-
this.element.matches(declared);
|
|
307
|
-
return declared;
|
|
308
|
-
} catch {
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return "";
|
|
312
|
-
}
|
|
313
322
|
#scrollY() {
|
|
314
323
|
if (this.#scrollSource === window) {
|
|
315
324
|
return window.scrollY ?? window.pageYOffset ?? 0;
|
|
@@ -2,14 +2,19 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
2
2
|
|
|
3
3
|
// src/controllers/scrollspy_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;
|
|
@@ -239,7 +244,7 @@ var ScrollspyController = class extends Controller {
|
|
|
239
244
|
* only rewrites attributes, so Stimulus fires no target callback: without
|
|
240
245
|
* this, a link re-pointed from `#intro` to `#faq` would keep the controller
|
|
241
246
|
* observing `#intro` for the rest of the page's life. The filter is exactly
|
|
242
|
-
*
|
|
247
|
+
* `href` and `data-href`; guarding on `MutationObserver` keeps the
|
|
243
248
|
* controller usable where the API is absent, matching `IntersectionWatcher`'s
|
|
244
249
|
* own support guard.
|
|
245
250
|
*/
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
+
// src/controllers/smart_sticky_header_controller.ts
|
|
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
|
+
function validSelector(element, raw, fallback) {
|
|
14
|
+
if (raw.length === 0) return fallback;
|
|
15
|
+
return parseDeclared(
|
|
16
|
+
raw,
|
|
17
|
+
(selector) => {
|
|
18
|
+
element.matches(selector);
|
|
19
|
+
return selector;
|
|
20
|
+
},
|
|
21
|
+
fallback
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
3
25
|
// src/controllers/smart_sticky_header_controller.ts
|
|
4
26
|
var DEFAULT_OFFSET = 80;
|
|
5
27
|
var SmartStickyHeaderController = class extends Controller {
|
|
@@ -41,7 +63,7 @@ var SmartStickyHeaderController = class extends Controller {
|
|
|
41
63
|
#onFocusin = () => this.#apply(false);
|
|
42
64
|
/** Validates `containerSelector` once so connect never parses a selector that throws. */
|
|
43
65
|
containerSelectorValueChanged() {
|
|
44
|
-
this.#containerSelector =
|
|
66
|
+
this.#containerSelector = validSelector(this.element, this.containerSelectorValue, "");
|
|
45
67
|
}
|
|
46
68
|
/** Re-decides when application code (or a Turbo morph) changes `offset` at runtime. */
|
|
47
69
|
offsetValueChanged() {
|
|
@@ -71,17 +93,6 @@ var SmartStickyHeaderController = class extends Controller {
|
|
|
71
93
|
}
|
|
72
94
|
return window;
|
|
73
95
|
}
|
|
74
|
-
/** Returns `declared` when it parses as a selector, and `""` when it does not. */
|
|
75
|
-
#validSelector(declared) {
|
|
76
|
-
if (declared.length > 0) {
|
|
77
|
-
try {
|
|
78
|
-
this.element.matches(declared);
|
|
79
|
-
return declared;
|
|
80
|
-
} catch {
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return "";
|
|
84
|
-
}
|
|
85
96
|
get #scrollY() {
|
|
86
97
|
const scroller = this.#scrollerEl;
|
|
87
98
|
return scroller === window ? window.scrollY : scroller.scrollTop;
|
|
@@ -2,6 +2,15 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
2
2
|
|
|
3
3
|
// src/controllers/sticky_observer_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;
|
|
@@ -23,6 +23,26 @@ function hasModifierChord(event) {
|
|
|
23
23
|
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// src/utils/declared_value.ts
|
|
27
|
+
function parseDeclared(raw, parse, fallback) {
|
|
28
|
+
try {
|
|
29
|
+
return parse(raw);
|
|
30
|
+
} catch {
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function validSelector(element, raw, fallback) {
|
|
35
|
+
if (raw.length === 0) return fallback;
|
|
36
|
+
return parseDeclared(
|
|
37
|
+
raw,
|
|
38
|
+
(selector) => {
|
|
39
|
+
element.matches(selector);
|
|
40
|
+
return selector;
|
|
41
|
+
},
|
|
42
|
+
fallback
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
26
46
|
// src/utils/roving_tabindex.ts
|
|
27
47
|
var RovingTabindex = class {
|
|
28
48
|
/** Returns the current ordered item elements; called on every operation. */
|
|
@@ -167,16 +187,7 @@ var ThemeController = class extends Controller {
|
|
|
167
187
|
}
|
|
168
188
|
/** Validates the `target` declaration once, so the render path never parses. */
|
|
169
189
|
targetValueChanged() {
|
|
170
|
-
|
|
171
|
-
if (selector.length > 0) {
|
|
172
|
-
try {
|
|
173
|
-
this.element.matches(selector);
|
|
174
|
-
this.#targetSelector = selector;
|
|
175
|
-
return;
|
|
176
|
-
} catch {
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
this.#targetSelector = DEFAULT_TARGET;
|
|
190
|
+
this.#targetSelector = validSelector(this.element, this.targetValue, DEFAULT_TARGET);
|
|
180
191
|
}
|
|
181
192
|
/** Re-derives the single Tab stop and ARIA for an option set that changed. */
|
|
182
193
|
optionTargetConnected() {
|
|
@@ -189,9 +200,8 @@ var ThemeController = class extends Controller {
|
|
|
189
200
|
/**
|
|
190
201
|
* Selects the mode the activated option declares.
|
|
191
202
|
*
|
|
192
|
-
* Read through
|
|
193
|
-
*
|
|
194
|
-
* declares.
|
|
203
|
+
* Read through the same lane that decides which option is checked, so the two
|
|
204
|
+
* can never disagree about what an option declares.
|
|
195
205
|
*/
|
|
196
206
|
set(event) {
|
|
197
207
|
const option = event.currentTarget;
|
|
@@ -20,6 +20,15 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
20
20
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// src/utils/event_owner.ts
|
|
24
|
+
function ownerIndex(candidates, node) {
|
|
25
|
+
if (!(node instanceof Node)) return -1;
|
|
26
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
27
|
+
}
|
|
28
|
+
function ownerOf(candidates, node) {
|
|
29
|
+
return candidates[ownerIndex(candidates, node)] ?? null;
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
// src/utils/focus_candidate.ts
|
|
24
33
|
function inheritsFieldsetDisabled(control) {
|
|
25
34
|
let fieldset = control.closest("fieldset[disabled]");
|
|
@@ -363,9 +372,8 @@ var ToggleGroupController = class extends Controller {
|
|
|
363
372
|
}
|
|
364
373
|
/** Finds this group's item containing an event target, excluding nested groups. */
|
|
365
374
|
#itemForEventTarget(target) {
|
|
366
|
-
if (!this.#ownsEventTarget(target)) return
|
|
367
|
-
|
|
368
|
-
return this.itemTargets.find((item) => item === node || item.contains(node));
|
|
375
|
+
if (!this.#ownsEventTarget(target)) return null;
|
|
376
|
+
return ownerOf(this.itemTargets, target);
|
|
369
377
|
}
|
|
370
378
|
/** Whether the closest Toggle Group scope around a target is this instance. */
|
|
371
379
|
#ownsEventTarget(target) {
|
|
@@ -60,6 +60,12 @@ var CompositionTracker = class {
|
|
|
60
60
|
};
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
+
// src/utils/event_owner.ts
|
|
64
|
+
function ownerIndex(candidates, node) {
|
|
65
|
+
if (!(node instanceof Node)) return -1;
|
|
66
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
67
|
+
}
|
|
68
|
+
|
|
63
69
|
// src/utils/focus_candidate.ts
|
|
64
70
|
function inheritsFieldsetDisabled(control) {
|
|
65
71
|
let fieldset = control.closest("fieldset[disabled]");
|
|
@@ -268,9 +274,7 @@ var ToolbarController = class extends Controller {
|
|
|
268
274
|
}
|
|
269
275
|
/** Index in `controlTargets` of the control owning `target` (it or a descendant). */
|
|
270
276
|
#indexOf(target) {
|
|
271
|
-
|
|
272
|
-
if (!node) return -1;
|
|
273
|
-
return this.controlTargets.findIndex((control) => control === node || control.contains(node));
|
|
277
|
+
return ownerIndex(this.controlTargets, target);
|
|
274
278
|
}
|
|
275
279
|
/** Controls eligible for the roving tab stop (excludes native disabled / hidden). */
|
|
276
280
|
get #navigableControls() {
|