stimeo-ui 0.13.0 → 0.15.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 +73 -5
- data/dist/cable/index.js +60 -14
- data/dist/controllers/announcer_controller.js +88 -61
- data/dist/controllers/auto_submit_controller.js +40 -5
- data/dist/controllers/breadcrumb_controller.js +29 -3
- data/dist/controllers/calendar_controller.js +51 -10
- data/dist/controllers/carousel_controller.js +8 -6
- data/dist/controllers/checkbox_controller.js +27 -2
- data/dist/controllers/conditional_fields_controller.js +36 -7
- data/dist/controllers/currency_input_controller.js +77 -25
- data/dist/controllers/date_range_picker_controller.js +43 -10
- data/dist/controllers/direct_upload_controller.js +21 -10
- data/dist/controllers/dirty_form_controller.js +32 -12
- data/dist/controllers/flash_controller.js +145 -43
- data/dist/controllers/form_field_controller.js +7 -1
- data/dist/controllers/frame_loading_controller.js +29 -6
- data/dist/controllers/highlight_controller.js +63 -23
- 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 +43 -11
- data/dist/controllers/masonry_controller.js +32 -14
- 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 +28 -4
- data/dist/controllers/otp_controller.js +23 -10
- data/dist/controllers/overflow_indicator_controller.js +31 -8
- data/dist/controllers/overflow_menu_controller.js +39 -4
- 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/read_more_controller.js +31 -5
- data/dist/controllers/reading_progress_controller.js +65 -19
- data/dist/controllers/relative_time_controller.js +40 -9
- 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_area_controller.js +70 -26
- data/dist/controllers/scroll_restore_controller.js +37 -16
- data/dist/controllers/scroll_visibility_controller.js +71 -42
- data/dist/controllers/scrollspy_controller.js +76 -32
- data/dist/controllers/smart_sticky_header_controller.js +72 -37
- data/dist/controllers/stick_to_bottom_controller.js +29 -3
- data/dist/controllers/sticky_observer_controller.js +10 -5
- data/dist/controllers/submit_once_controller.js +29 -6
- data/dist/controllers/textarea_autosize_controller.js +29 -3
- data/dist/controllers/theme_controller.js +23 -13
- data/dist/controllers/toast_controller.js +136 -73
- data/dist/controllers/toggle_group_controller.js +11 -3
- data/dist/controllers/toolbar_controller.js +7 -3
- data/dist/index.js +629 -597
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +2 -2
|
@@ -8,10 +8,15 @@ var LayoutObserver = class {
|
|
|
8
8
|
#resizeObserverFactory;
|
|
9
9
|
#resizeObserver = null;
|
|
10
10
|
#observingViewport = false;
|
|
11
|
+
#loadContainer = null;
|
|
11
12
|
/** Stable bound handler so add/removeEventListener target the same reference. */
|
|
12
13
|
#handleViewportResize = () => {
|
|
13
14
|
this.#callback();
|
|
14
15
|
};
|
|
16
|
+
/** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */
|
|
17
|
+
#handleDescendantLoad = () => {
|
|
18
|
+
this.#callback();
|
|
19
|
+
};
|
|
15
20
|
constructor(callback, options = {}) {
|
|
16
21
|
this.#callback = callback;
|
|
17
22
|
this.#resizeObserverFactory = options.resizeObserverFactory ?? (typeof ResizeObserver === "undefined" ? null : (cb) => new ResizeObserver(cb));
|
|
@@ -47,14 +52,35 @@ var LayoutObserver = class {
|
|
|
47
52
|
window.removeEventListener("resize", this.#handleViewportResize);
|
|
48
53
|
}
|
|
49
54
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
55
|
+
* Starts reporting a `load` from anywhere inside `container` — an image or a
|
|
56
|
+
* frame settling changes the box it sits in, and it measures as zero high until
|
|
57
|
+
* then. `load` does not bubble, so the subscription is a capture-phase listener
|
|
58
|
+
* on the container itself and nothing the caller spells.
|
|
59
|
+
*
|
|
60
|
+
* **One container at a time.** A further call moves the observation, so a widget
|
|
61
|
+
* whose content element is swapped at runtime releases the element it let go by
|
|
62
|
+
* naming the new one — there is no second place for the release to drift from.
|
|
63
|
+
*/
|
|
64
|
+
observeDescendantLoads(container) {
|
|
65
|
+
this.unobserveDescendantLoads();
|
|
66
|
+
this.#loadContainer = container;
|
|
67
|
+
container.addEventListener("load", this.#handleDescendantLoad, true);
|
|
68
|
+
}
|
|
69
|
+
/** Stops reporting descendant loads without affecting element or viewport observation. */
|
|
70
|
+
unobserveDescendantLoads() {
|
|
71
|
+
this.#loadContainer?.removeEventListener("load", this.#handleDescendantLoad, true);
|
|
72
|
+
this.#loadContainer = null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Releases every observation: disconnects the {@link ResizeObserver} and removes
|
|
76
|
+
* the viewport and descendant-load listeners. Safe to call multiple times. Call
|
|
77
|
+
* this from a controller's `disconnect()`.
|
|
53
78
|
*/
|
|
54
79
|
disconnect() {
|
|
55
80
|
this.#resizeObserver?.disconnect();
|
|
56
81
|
this.#resizeObserver = null;
|
|
57
82
|
this.unobserveViewport();
|
|
83
|
+
this.unobserveDescendantLoads();
|
|
58
84
|
}
|
|
59
85
|
};
|
|
60
86
|
|
|
@@ -127,13 +153,6 @@ var MasonryController = class extends Controller {
|
|
|
127
153
|
#mutationObserver = null;
|
|
128
154
|
/** Last published column count, so `layout` fires only on real changes. */
|
|
129
155
|
#lastColumns = 0;
|
|
130
|
-
/**
|
|
131
|
-
* Re-pack when a descendant resource finishes loading. Images/iframes report a
|
|
132
|
-
* height of 0 until loaded, which would skew the shortest-column packing if the
|
|
133
|
-
* first pass ran before they settled; `load` does not bubble, so this is bound in
|
|
134
|
-
* the capture phase to catch every descendant.
|
|
135
|
-
*/
|
|
136
|
-
#onLoad = () => this.#reconcile.schedule();
|
|
137
156
|
/** Resolves the declared column width once, falling back when it is unreadable. */
|
|
138
157
|
minColumnWidthValueChanged() {
|
|
139
158
|
this.#minColumnWidth = usableNumber(this.minColumnWidthValue, DEFAULT_MIN_COLUMN_WIDTH);
|
|
@@ -153,7 +172,7 @@ var MasonryController = class extends Controller {
|
|
|
153
172
|
*
|
|
154
173
|
* The removal is queued rather than immediate because teardown reports every
|
|
155
174
|
* target as disconnected: doing it here would strip the whole grid just before
|
|
156
|
-
* a Turbo snapshot is taken.
|
|
175
|
+
* a Turbo snapshot is taken. The coalescer's `cancel` drops the queue
|
|
157
176
|
* with the pass, so only a genuine target change reaches it.
|
|
158
177
|
*/
|
|
159
178
|
itemTargetDisconnected(item) {
|
|
@@ -168,18 +187,17 @@ var MasonryController = class extends Controller {
|
|
|
168
187
|
this.#mutationObserver = new MutationObserver(() => this.#reconcile.schedule());
|
|
169
188
|
this.#mutationObserver.observe(this.element, { childList: true, subtree: true });
|
|
170
189
|
}
|
|
171
|
-
this.
|
|
190
|
+
this.#layout.observeDescendantLoads(this.element);
|
|
172
191
|
this.#relayout();
|
|
173
192
|
this.#reconcile.activate();
|
|
174
193
|
}
|
|
175
|
-
/** Releases
|
|
194
|
+
/** Releases every observation so nothing fires after detach. */
|
|
176
195
|
disconnect() {
|
|
177
196
|
this.#reconcile.cancel();
|
|
178
197
|
this.#released.clear();
|
|
179
198
|
this.#layout.disconnect();
|
|
180
199
|
this.#mutationObserver?.disconnect();
|
|
181
200
|
this.#mutationObserver = null;
|
|
182
|
-
this.element.removeEventListener("load", this.#onLoad, true);
|
|
183
201
|
this.#lastColumns = 0;
|
|
184
202
|
}
|
|
185
203
|
/**
|
|
@@ -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);
|
|
@@ -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)),
|
|
@@ -57,6 +57,30 @@ var DetachGate = class _DetachGate {
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
// src/utils/listener_set.ts
|
|
61
|
+
var ListenerSet = class {
|
|
62
|
+
/** The generation every `add` joins until the next `dispose()`. */
|
|
63
|
+
#abort = new AbortController();
|
|
64
|
+
/**
|
|
65
|
+
* Attaches `handler` to the open generation, exactly as the caller spelled it.
|
|
66
|
+
*
|
|
67
|
+
* The set supplies the signal, so `options` carries everything else the DOM
|
|
68
|
+
* accepts — `capture` included, which has to match at release time and no
|
|
69
|
+
* longer has a second place to drift from.
|
|
70
|
+
*/
|
|
71
|
+
add(target, type, handler, options) {
|
|
72
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Releases every listener of the open generation, synchronously, and opens the
|
|
76
|
+
* next one. Idempotent, and safe before anything has been added.
|
|
77
|
+
*/
|
|
78
|
+
dispose() {
|
|
79
|
+
this.#abort.abort();
|
|
80
|
+
this.#abort = new AbortController();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
60
84
|
// src/controllers/optimistic_controller.ts
|
|
61
85
|
var HIDDEN_MARKER = "data-optimistic-toggled";
|
|
62
86
|
var BUSY_MARKER = "data-optimistic-busy";
|
|
@@ -73,6 +97,7 @@ var OptimisticController = class extends Controller {
|
|
|
73
97
|
/** The submission that owns the applied optimistic state, if any. */
|
|
74
98
|
#pending = null;
|
|
75
99
|
#gate = new DetachGate();
|
|
100
|
+
#listeners = new ListenerSet();
|
|
76
101
|
/** True between `connect()` and `disconnect()`, so a repeat connect rewinds nothing. */
|
|
77
102
|
#connected = false;
|
|
78
103
|
connect() {
|
|
@@ -80,13 +105,12 @@ var OptimisticController = class extends Controller {
|
|
|
80
105
|
this.#gate.cancel();
|
|
81
106
|
this.#connected = true;
|
|
82
107
|
if (restored && this.element.hasAttribute("data-optimistic")) this.#revert();
|
|
83
|
-
this.element
|
|
84
|
-
this.element
|
|
108
|
+
this.#listeners.add(this.element, "turbo:submit-start", this.#onSubmitStart);
|
|
109
|
+
this.#listeners.add(this.element, "turbo:submit-end", this.#onSubmitEnd);
|
|
85
110
|
}
|
|
86
111
|
disconnect() {
|
|
87
112
|
this.#connected = false;
|
|
88
|
-
this.
|
|
89
|
-
this.element.removeEventListener("turbo:submit-end", this.#onSubmitEnd);
|
|
113
|
+
this.#listeners.dispose();
|
|
90
114
|
this.#gate.disconnected(this, () => {
|
|
91
115
|
this.#pending = null;
|
|
92
116
|
});
|
|
@@ -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) {
|
|
@@ -63,10 +63,15 @@ var LayoutObserver = class {
|
|
|
63
63
|
#resizeObserverFactory;
|
|
64
64
|
#resizeObserver = null;
|
|
65
65
|
#observingViewport = false;
|
|
66
|
+
#loadContainer = null;
|
|
66
67
|
/** Stable bound handler so add/removeEventListener target the same reference. */
|
|
67
68
|
#handleViewportResize = () => {
|
|
68
69
|
this.#callback();
|
|
69
70
|
};
|
|
71
|
+
/** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */
|
|
72
|
+
#handleDescendantLoad = () => {
|
|
73
|
+
this.#callback();
|
|
74
|
+
};
|
|
70
75
|
constructor(callback, options = {}) {
|
|
71
76
|
this.#callback = callback;
|
|
72
77
|
this.#resizeObserverFactory = options.resizeObserverFactory ?? (typeof ResizeObserver === "undefined" ? null : (cb) => new ResizeObserver(cb));
|
|
@@ -102,14 +107,35 @@ var LayoutObserver = class {
|
|
|
102
107
|
window.removeEventListener("resize", this.#handleViewportResize);
|
|
103
108
|
}
|
|
104
109
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
110
|
+
* Starts reporting a `load` from anywhere inside `container` — an image or a
|
|
111
|
+
* frame settling changes the box it sits in, and it measures as zero high until
|
|
112
|
+
* then. `load` does not bubble, so the subscription is a capture-phase listener
|
|
113
|
+
* on the container itself and nothing the caller spells.
|
|
114
|
+
*
|
|
115
|
+
* **One container at a time.** A further call moves the observation, so a widget
|
|
116
|
+
* whose content element is swapped at runtime releases the element it let go by
|
|
117
|
+
* naming the new one — there is no second place for the release to drift from.
|
|
118
|
+
*/
|
|
119
|
+
observeDescendantLoads(container) {
|
|
120
|
+
this.unobserveDescendantLoads();
|
|
121
|
+
this.#loadContainer = container;
|
|
122
|
+
container.addEventListener("load", this.#handleDescendantLoad, true);
|
|
123
|
+
}
|
|
124
|
+
/** Stops reporting descendant loads without affecting element or viewport observation. */
|
|
125
|
+
unobserveDescendantLoads() {
|
|
126
|
+
this.#loadContainer?.removeEventListener("load", this.#handleDescendantLoad, true);
|
|
127
|
+
this.#loadContainer = null;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Releases every observation: disconnects the {@link ResizeObserver} and removes
|
|
131
|
+
* the viewport and descendant-load listeners. Safe to call multiple times. Call
|
|
132
|
+
* this from a controller's `disconnect()`.
|
|
108
133
|
*/
|
|
109
134
|
disconnect() {
|
|
110
135
|
this.#resizeObserver?.disconnect();
|
|
111
136
|
this.#resizeObserver = null;
|
|
112
137
|
this.unobserveViewport();
|
|
138
|
+
this.unobserveDescendantLoads();
|
|
113
139
|
}
|
|
114
140
|
};
|
|
115
141
|
|
|
@@ -324,7 +350,7 @@ var OverflowIndicatorController = class extends Controller {
|
|
|
324
350
|
this.#observedViewport = next;
|
|
325
351
|
this.#layout.observe(next);
|
|
326
352
|
this.#layout.observeViewport();
|
|
327
|
-
|
|
353
|
+
this.#layout.observeDescendantLoads(next);
|
|
328
354
|
this.#syncContentObservation();
|
|
329
355
|
if (typeof MutationObserver !== "undefined") {
|
|
330
356
|
this.#mutationObserver = new MutationObserver(() => {
|
|
@@ -362,16 +388,13 @@ var OverflowIndicatorController = class extends Controller {
|
|
|
362
388
|
#stopObservingViewport() {
|
|
363
389
|
this.#mutationObserver?.disconnect();
|
|
364
390
|
this.#mutationObserver = null;
|
|
365
|
-
this.#
|
|
391
|
+
this.#layout.unobserveDescendantLoads();
|
|
366
392
|
if (this.#observedViewport) this.#layout.unobserve(this.#observedViewport);
|
|
367
393
|
for (const content of this.#observedContent) this.#layout.unobserve(content);
|
|
368
394
|
this.#observedContent.clear();
|
|
369
395
|
this.#observedViewport = null;
|
|
370
396
|
this.#layout.unobserveViewport();
|
|
371
397
|
}
|
|
372
|
-
#onContentLoad = () => {
|
|
373
|
-
if (this.#connected) this.update();
|
|
374
|
-
};
|
|
375
398
|
get #threshold() {
|
|
376
399
|
const value = this.thresholdValue;
|
|
377
400
|
return Number.isFinite(value) ? Math.max(0, value) : 1;
|
|
@@ -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]");
|
|
@@ -55,10 +64,15 @@ var LayoutObserver = class {
|
|
|
55
64
|
#resizeObserverFactory;
|
|
56
65
|
#resizeObserver = null;
|
|
57
66
|
#observingViewport = false;
|
|
67
|
+
#loadContainer = null;
|
|
58
68
|
/** Stable bound handler so add/removeEventListener target the same reference. */
|
|
59
69
|
#handleViewportResize = () => {
|
|
60
70
|
this.#callback();
|
|
61
71
|
};
|
|
72
|
+
/** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */
|
|
73
|
+
#handleDescendantLoad = () => {
|
|
74
|
+
this.#callback();
|
|
75
|
+
};
|
|
62
76
|
constructor(callback, options = {}) {
|
|
63
77
|
this.#callback = callback;
|
|
64
78
|
this.#resizeObserverFactory = options.resizeObserverFactory ?? (typeof ResizeObserver === "undefined" ? null : (cb) => new ResizeObserver(cb));
|
|
@@ -94,14 +108,35 @@ var LayoutObserver = class {
|
|
|
94
108
|
window.removeEventListener("resize", this.#handleViewportResize);
|
|
95
109
|
}
|
|
96
110
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
111
|
+
* Starts reporting a `load` from anywhere inside `container` — an image or a
|
|
112
|
+
* frame settling changes the box it sits in, and it measures as zero high until
|
|
113
|
+
* then. `load` does not bubble, so the subscription is a capture-phase listener
|
|
114
|
+
* on the container itself and nothing the caller spells.
|
|
115
|
+
*
|
|
116
|
+
* **One container at a time.** A further call moves the observation, so a widget
|
|
117
|
+
* whose content element is swapped at runtime releases the element it let go by
|
|
118
|
+
* naming the new one — there is no second place for the release to drift from.
|
|
119
|
+
*/
|
|
120
|
+
observeDescendantLoads(container) {
|
|
121
|
+
this.unobserveDescendantLoads();
|
|
122
|
+
this.#loadContainer = container;
|
|
123
|
+
container.addEventListener("load", this.#handleDescendantLoad, true);
|
|
124
|
+
}
|
|
125
|
+
/** Stops reporting descendant loads without affecting element or viewport observation. */
|
|
126
|
+
unobserveDescendantLoads() {
|
|
127
|
+
this.#loadContainer?.removeEventListener("load", this.#handleDescendantLoad, true);
|
|
128
|
+
this.#loadContainer = null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Releases every observation: disconnects the {@link ResizeObserver} and removes
|
|
132
|
+
* the viewport and descendant-load listeners. Safe to call multiple times. Call
|
|
133
|
+
* this from a controller's `disconnect()`.
|
|
100
134
|
*/
|
|
101
135
|
disconnect() {
|
|
102
136
|
this.#resizeObserver?.disconnect();
|
|
103
137
|
this.#resizeObserver = null;
|
|
104
138
|
this.unobserveViewport();
|
|
139
|
+
this.unobserveDescendantLoads();
|
|
105
140
|
}
|
|
106
141
|
};
|
|
107
142
|
|
|
@@ -502,7 +537,7 @@ var OverflowMenuController = class extends Controller {
|
|
|
502
537
|
}
|
|
503
538
|
/** The managed item that is, or contains, `el` — focus rescue works on either. */
|
|
504
539
|
#ownerOf(el) {
|
|
505
|
-
return this.#items
|
|
540
|
+
return ownerOf(this.#items, el);
|
|
506
541
|
}
|
|
507
542
|
/** Whether the item takes up room in the bar; an authored `hidden` one does not. */
|
|
508
543
|
#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) {
|
|
@@ -63,10 +63,15 @@ var LayoutObserver = class {
|
|
|
63
63
|
#resizeObserverFactory;
|
|
64
64
|
#resizeObserver = null;
|
|
65
65
|
#observingViewport = false;
|
|
66
|
+
#loadContainer = null;
|
|
66
67
|
/** Stable bound handler so add/removeEventListener target the same reference. */
|
|
67
68
|
#handleViewportResize = () => {
|
|
68
69
|
this.#callback();
|
|
69
70
|
};
|
|
71
|
+
/** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */
|
|
72
|
+
#handleDescendantLoad = () => {
|
|
73
|
+
this.#callback();
|
|
74
|
+
};
|
|
70
75
|
constructor(callback, options = {}) {
|
|
71
76
|
this.#callback = callback;
|
|
72
77
|
this.#resizeObserverFactory = options.resizeObserverFactory ?? (typeof ResizeObserver === "undefined" ? null : (cb) => new ResizeObserver(cb));
|
|
@@ -102,14 +107,35 @@ var LayoutObserver = class {
|
|
|
102
107
|
window.removeEventListener("resize", this.#handleViewportResize);
|
|
103
108
|
}
|
|
104
109
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
110
|
+
* Starts reporting a `load` from anywhere inside `container` — an image or a
|
|
111
|
+
* frame settling changes the box it sits in, and it measures as zero high until
|
|
112
|
+
* then. `load` does not bubble, so the subscription is a capture-phase listener
|
|
113
|
+
* on the container itself and nothing the caller spells.
|
|
114
|
+
*
|
|
115
|
+
* **One container at a time.** A further call moves the observation, so a widget
|
|
116
|
+
* whose content element is swapped at runtime releases the element it let go by
|
|
117
|
+
* naming the new one — there is no second place for the release to drift from.
|
|
118
|
+
*/
|
|
119
|
+
observeDescendantLoads(container) {
|
|
120
|
+
this.unobserveDescendantLoads();
|
|
121
|
+
this.#loadContainer = container;
|
|
122
|
+
container.addEventListener("load", this.#handleDescendantLoad, true);
|
|
123
|
+
}
|
|
124
|
+
/** Stops reporting descendant loads without affecting element or viewport observation. */
|
|
125
|
+
unobserveDescendantLoads() {
|
|
126
|
+
this.#loadContainer?.removeEventListener("load", this.#handleDescendantLoad, true);
|
|
127
|
+
this.#loadContainer = null;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Releases every observation: disconnects the {@link ResizeObserver} and removes
|
|
131
|
+
* the viewport and descendant-load listeners. Safe to call multiple times. Call
|
|
132
|
+
* this from a controller's `disconnect()`.
|
|
108
133
|
*/
|
|
109
134
|
disconnect() {
|
|
110
135
|
this.#resizeObserver?.disconnect();
|
|
111
136
|
this.#resizeObserver = null;
|
|
112
137
|
this.unobserveViewport();
|
|
138
|
+
this.unobserveDescendantLoads();
|
|
113
139
|
}
|
|
114
140
|
};
|
|
115
141
|
|
|
@@ -192,7 +218,7 @@ var ReadMoreController = class extends Controller {
|
|
|
192
218
|
this.#observedContent = next;
|
|
193
219
|
this.#layout.observe(next);
|
|
194
220
|
this.#layout.observeViewport();
|
|
195
|
-
|
|
221
|
+
this.#layout.observeDescendantLoads(next);
|
|
196
222
|
if (typeof MutationObserver !== "undefined") {
|
|
197
223
|
this.#contentMutationObserver = new MutationObserver(this.#update);
|
|
198
224
|
this.#contentMutationObserver.observe(next, {
|
|
@@ -206,7 +232,7 @@ var ReadMoreController = class extends Controller {
|
|
|
206
232
|
this.#deferredHide.releaseAll();
|
|
207
233
|
if (this.#observedContent) {
|
|
208
234
|
this.#layout.unobserve(this.#observedContent);
|
|
209
|
-
this.#
|
|
235
|
+
this.#layout.unobserveDescendantLoads();
|
|
210
236
|
}
|
|
211
237
|
this.#observedContent = null;
|
|
212
238
|
this.#contentMutationObserver?.disconnect();
|