stimeo-ui 0.16.0 → 0.17.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 +31 -0
- data/README.md +8 -1
- data/dist/cable/index.js +363 -436
- data/dist/controllers/accordion_controller.js +166 -55
- data/dist/controllers/alert_dialog_controller.js +862 -221
- data/dist/controllers/announcer_controller.js +135 -198
- data/dist/controllers/aspect_ratio_controller.js +0 -10
- data/dist/controllers/auto_submit_controller.js +271 -127
- data/dist/controllers/avatar_controller.js +171 -105
- data/dist/controllers/breadcrumb_controller.js +0 -130
- data/dist/controllers/bulk_select_controller.js +262 -52
- data/dist/controllers/calendar_controller.js +175 -209
- data/dist/controllers/carousel_controller.js +461 -399
- data/dist/controllers/character_counter_controller.js +246 -156
- data/dist/controllers/checkbox_controller.js +32 -74
- data/dist/controllers/clipboard_controller.js +168 -147
- data/dist/controllers/collapsible_controller.js +301 -120
- data/dist/controllers/color_picker_controller.js +149 -139
- data/dist/controllers/combobox_controller.js +360 -133
- data/dist/controllers/command_palette_controller.js +970 -358
- data/dist/controllers/conditional_fields_controller.js +166 -124
- data/dist/controllers/confirm_controller.js +939 -248
- data/dist/controllers/context_menu_controller.js +256 -128
- data/dist/controllers/count_up_controller.js +73 -21
- data/dist/controllers/countdown_controller.js +183 -148
- data/dist/controllers/currency_input_controller.js +134 -158
- data/dist/controllers/data_grid_controller.js +114 -186
- data/dist/controllers/date_range_picker_controller.js +288 -206
- data/dist/controllers/dialog_controller.js +858 -223
- data/dist/controllers/direct_upload_controller.js +73 -153
- data/dist/controllers/dirty_form_controller.js +14 -48
- data/dist/controllers/dismissible_controller.js +175 -21
- data/dist/controllers/drawer_controller.js +933 -341
- data/dist/controllers/dropdown_controller.js +312 -82
- data/dist/controllers/editable_controller.js +158 -68
- data/dist/controllers/empty_state_controller.js +67 -53
- data/dist/controllers/file_dropzone_controller.js +311 -207
- data/dist/controllers/filter_controller.js +67 -39
- data/dist/controllers/flash_controller.js +172 -314
- data/dist/controllers/focus_controller.js +654 -240
- data/dist/controllers/form_field_controller.js +147 -132
- data/dist/controllers/form_validation_controller.js +13 -86
- data/dist/controllers/frame_loading_controller.js +264 -213
- data/dist/controllers/highlight_controller.js +107 -75
- data/dist/controllers/hover_card_controller.js +309 -118
- data/dist/controllers/idle_controller.js +267 -101
- data/dist/controllers/input_mask_controller.js +71 -72
- data/dist/controllers/intersection_controller.js +147 -106
- data/dist/controllers/lazy_frame_controller.js +9 -65
- data/dist/controllers/listbox_controller.js +306 -204
- data/dist/controllers/local_time_controller.js +54 -64
- data/dist/controllers/masonry_controller.js +70 -93
- data/dist/controllers/menu_controller.js +214 -156
- data/dist/controllers/menubar_controller.js +170 -329
- data/dist/controllers/meter_controller.js +151 -67
- data/dist/controllers/multi_select_controller.js +593 -358
- data/dist/controllers/navigation_menu_controller.js +124 -232
- data/dist/controllers/nested_form_controller.js +352 -182
- data/dist/controllers/network_status_controller.js +242 -54
- data/dist/controllers/number_input_controller.js +320 -263
- data/dist/controllers/optimistic_controller.js +16 -76
- data/dist/controllers/otp_controller.js +198 -193
- data/dist/controllers/overflow_indicator_controller.js +116 -113
- data/dist/controllers/overflow_menu_controller.js +343 -283
- data/dist/controllers/pagination_controller.js +295 -119
- data/dist/controllers/password_reveal_controller.js +389 -121
- data/dist/controllers/password_strength_controller.js +99 -153
- data/dist/controllers/persist_controller.js +237 -103
- data/dist/controllers/pointer_drag_controller.js +185 -165
- data/dist/controllers/popover_controller.js +236 -97
- data/dist/controllers/portal_controller.js +209 -76
- data/dist/controllers/preview_guard_controller.js +232 -116
- data/dist/controllers/progress_controller.js +125 -56
- data/dist/controllers/radio_group_controller.js +117 -137
- data/dist/controllers/range_slider_controller.js +192 -105
- data/dist/controllers/rating_controller.js +418 -207
- data/dist/controllers/read_more_controller.js +134 -63
- data/dist/controllers/reading_progress_controller.js +147 -135
- data/dist/controllers/relative_time_controller.js +145 -110
- data/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
- data/dist/controllers/resizable_controller.js +255 -139
- data/dist/controllers/roving_controller.js +39 -84
- data/dist/controllers/scroll_area_controller.js +428 -225
- data/dist/controllers/scroll_restore_controller.js +0 -53
- data/dist/controllers/scroll_visibility_controller.js +395 -145
- data/dist/controllers/scrollspy_controller.js +181 -242
- data/dist/controllers/separator_controller.js +267 -140
- data/dist/controllers/sidebar_controller.js +1023 -386
- data/dist/controllers/skeleton_controller.js +248 -97
- data/dist/controllers/slider_controller.js +159 -79
- data/dist/controllers/smart_sticky_header_controller.js +162 -63
- data/dist/controllers/sortable_controller.js +0 -112
- data/dist/controllers/spinner_controller.js +277 -169
- data/dist/controllers/step_indicator_controller.js +99 -46
- data/dist/controllers/stepper_controller.js +114 -73
- data/dist/controllers/stick_to_bottom_controller.js +262 -138
- data/dist/controllers/sticky_observer_controller.js +214 -49
- data/dist/controllers/submit_once_controller.js +294 -205
- data/dist/controllers/switch_controller.js +33 -60
- data/dist/controllers/tabs_controller.js +35 -29
- data/dist/controllers/tags_input_controller.js +136 -145
- data/dist/controllers/textarea_autosize_controller.js +71 -58
- data/dist/controllers/theme_controller.js +130 -112
- data/dist/controllers/time_picker_controller.js +146 -80
- data/dist/controllers/toast_controller.js +186 -297
- data/dist/controllers/toggle_group_controller.js +95 -136
- data/dist/controllers/toolbar_controller.js +68 -98
- data/dist/controllers/tooltip_controller.js +158 -124
- data/dist/controllers/transition_controller.js +123 -119
- data/dist/controllers/tree_view_controller.js +150 -345
- data/dist/index.js +6370 -9346
- data/dist/positioning/index.js +148 -47
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +3 -3
|
@@ -1,30 +1,20 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
// src/controllers/overflow_indicator_controller.ts
|
|
4
|
-
|
|
5
|
-
// src/utils/blur_deferral.ts
|
|
6
3
|
var BlurDeferral = class {
|
|
7
|
-
/** Elements currently holding an update back, mapped to their `blur` listener. */
|
|
8
4
|
#pending = /* @__PURE__ */ new Map();
|
|
9
|
-
/** Called after a pending element blurs and has been detached. */
|
|
10
5
|
#onRelease;
|
|
11
|
-
/** @param onRelease - Invoked once `element` actually blurs; never on `release`. */
|
|
12
6
|
constructor(onRelease) {
|
|
13
7
|
this.#onRelease = onRelease;
|
|
14
8
|
}
|
|
15
|
-
/** Number of elements currently holding an update back. */
|
|
16
9
|
get size() {
|
|
17
10
|
return this.#pending.size;
|
|
18
11
|
}
|
|
19
|
-
/** Snapshot of the pending elements, safe to iterate while releasing them. */
|
|
20
12
|
get elements() {
|
|
21
13
|
return [...this.#pending.keys()];
|
|
22
14
|
}
|
|
23
|
-
/** Whether `element` is currently holding an update back. */
|
|
24
15
|
has(element) {
|
|
25
16
|
return this.#pending.has(element);
|
|
26
17
|
}
|
|
27
|
-
/** Holds an update back until `element` blurs. Idempotent (no stacked listeners). */
|
|
28
18
|
defer(element) {
|
|
29
19
|
if (this.#pending.has(element)) return;
|
|
30
20
|
const onBlur = () => {
|
|
@@ -34,22 +24,18 @@ var BlurDeferral = class {
|
|
|
34
24
|
this.#pending.set(element, onBlur);
|
|
35
25
|
element.addEventListener("blur", onBlur);
|
|
36
26
|
}
|
|
37
|
-
/** Defers `element` as the only pending entry, cancelling any others. */
|
|
38
27
|
deferOnly(element) {
|
|
39
28
|
for (const pending of this.elements) {
|
|
40
29
|
if (pending !== element) this.#detach(pending);
|
|
41
30
|
}
|
|
42
31
|
this.defer(element);
|
|
43
32
|
}
|
|
44
|
-
/** Cancels `element`'s deferral without completing it; no-ops when not pending. */
|
|
45
33
|
release(element) {
|
|
46
34
|
this.#detach(element);
|
|
47
35
|
}
|
|
48
|
-
/** Cancels every deferral without completing any of them. */
|
|
49
36
|
releaseAll() {
|
|
50
37
|
for (const element of this.elements) this.#detach(element);
|
|
51
38
|
}
|
|
52
|
-
/** Removes the `blur` listener for `element` and forgets it. */
|
|
53
39
|
#detach(element) {
|
|
54
40
|
const onBlur = this.#pending.get(element);
|
|
55
41
|
if (onBlur) element.removeEventListener("blur", onBlur);
|
|
@@ -57,14 +43,8 @@ var BlurDeferral = class {
|
|
|
57
43
|
}
|
|
58
44
|
};
|
|
59
45
|
|
|
60
|
-
// src/utils/frame_coalescer.ts
|
|
61
46
|
var FrameCoalescer = class {
|
|
62
47
|
#frame = null;
|
|
63
|
-
/**
|
|
64
|
-
* Runs `run` on the next frame, unless a frame is already pending — the first
|
|
65
|
-
* request of a burst wins and the rest are dropped. The pending frame is
|
|
66
|
-
* released before `run`, so `run` may request the next one.
|
|
67
|
-
*/
|
|
68
48
|
schedule(run) {
|
|
69
49
|
if (this.#frame !== null) return;
|
|
70
50
|
this.#frame = requestAnimationFrame(() => {
|
|
@@ -72,14 +52,6 @@ var FrameCoalescer = class {
|
|
|
72
52
|
run();
|
|
73
53
|
});
|
|
74
54
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Drops the pending frame, and reaches the platform only when there is one.
|
|
77
|
-
*
|
|
78
|
-
* There is no handle value that stands for "nothing pending":
|
|
79
|
-
* `cancelAnimationFrame` takes an `unsigned long`, so a negative placeholder
|
|
80
|
-
* arrives as a large positive number that the same allocator can hand out, and
|
|
81
|
-
* an idle cancel would then drop a frame belonging to someone else.
|
|
82
|
-
*/
|
|
83
55
|
cancel() {
|
|
84
56
|
if (this.#frame === null) return;
|
|
85
57
|
cancelAnimationFrame(this.#frame);
|
|
@@ -87,18 +59,15 @@ var FrameCoalescer = class {
|
|
|
87
59
|
}
|
|
88
60
|
};
|
|
89
61
|
|
|
90
|
-
// src/utils/layout_observer.ts
|
|
91
62
|
var LayoutObserver = class {
|
|
92
63
|
#callback;
|
|
93
64
|
#resizeObserverFactory;
|
|
94
65
|
#resizeObserver = null;
|
|
95
66
|
#observingViewport = false;
|
|
96
67
|
#loadContainer = null;
|
|
97
|
-
/** Stable bound handler so add/removeEventListener target the same reference. */
|
|
98
68
|
#handleViewportResize = () => {
|
|
99
69
|
this.#callback();
|
|
100
70
|
};
|
|
101
|
-
/** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */
|
|
102
71
|
#handleDescendantLoad = () => {
|
|
103
72
|
this.#callback();
|
|
104
73
|
};
|
|
@@ -106,11 +75,6 @@ var LayoutObserver = class {
|
|
|
106
75
|
this.#callback = callback;
|
|
107
76
|
this.#resizeObserverFactory = options.resizeObserverFactory ?? (typeof ResizeObserver === "undefined" ? null : (cb) => new ResizeObserver(cb));
|
|
108
77
|
}
|
|
109
|
-
/**
|
|
110
|
-
* Starts observing an element's size. Repeated calls observe additional
|
|
111
|
-
* elements through the same shared observer. No-ops when no
|
|
112
|
-
* `ResizeObserver` implementation is available.
|
|
113
|
-
*/
|
|
114
78
|
observe(element) {
|
|
115
79
|
if (!this.#resizeObserverFactory) return;
|
|
116
80
|
if (!this.#resizeObserver) {
|
|
@@ -120,47 +84,28 @@ var LayoutObserver = class {
|
|
|
120
84
|
}
|
|
121
85
|
this.#resizeObserver.observe(element);
|
|
122
86
|
}
|
|
123
|
-
/** Stops observing a single element while leaving any others in place. */
|
|
124
87
|
unobserve(element) {
|
|
125
88
|
this.#resizeObserver?.unobserve(element);
|
|
126
89
|
}
|
|
127
|
-
/** Starts observing viewport resizes. Idempotent: the listener is added once. */
|
|
128
90
|
observeViewport() {
|
|
129
91
|
if (this.#observingViewport) return;
|
|
130
92
|
this.#observingViewport = true;
|
|
131
93
|
window.addEventListener("resize", this.#handleViewportResize);
|
|
132
94
|
}
|
|
133
|
-
/** Stops observing viewport resizes without affecting element observation. */
|
|
134
95
|
unobserveViewport() {
|
|
135
96
|
if (!this.#observingViewport) return;
|
|
136
97
|
this.#observingViewport = false;
|
|
137
98
|
window.removeEventListener("resize", this.#handleViewportResize);
|
|
138
99
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Starts reporting a `load` from anywhere inside `container` — an image or a
|
|
141
|
-
* frame settling changes the box it sits in, and it measures as zero high until
|
|
142
|
-
* then. `load` does not bubble, so the subscription is a capture-phase listener
|
|
143
|
-
* on the container itself and nothing the caller spells.
|
|
144
|
-
*
|
|
145
|
-
* **One container at a time.** A further call moves the observation, so a widget
|
|
146
|
-
* whose content element is swapped at runtime releases the element it let go by
|
|
147
|
-
* naming the new one — there is no second place for the release to drift from.
|
|
148
|
-
*/
|
|
149
100
|
observeDescendantLoads(container) {
|
|
150
101
|
this.unobserveDescendantLoads();
|
|
151
102
|
this.#loadContainer = container;
|
|
152
103
|
container.addEventListener("load", this.#handleDescendantLoad, true);
|
|
153
104
|
}
|
|
154
|
-
/** Stops reporting descendant loads without affecting element or viewport observation. */
|
|
155
105
|
unobserveDescendantLoads() {
|
|
156
106
|
this.#loadContainer?.removeEventListener("load", this.#handleDescendantLoad, true);
|
|
157
107
|
this.#loadContainer = null;
|
|
158
108
|
}
|
|
159
|
-
/**
|
|
160
|
-
* Releases every observation: disconnects the {@link ResizeObserver} and removes
|
|
161
|
-
* the viewport and descendant-load listeners. Safe to call multiple times. Call
|
|
162
|
-
* this from a controller's `disconnect()`.
|
|
163
|
-
*/
|
|
164
109
|
disconnect() {
|
|
165
110
|
this.#resizeObserver?.disconnect();
|
|
166
111
|
this.#resizeObserver = null;
|
|
@@ -169,31 +114,17 @@ var LayoutObserver = class {
|
|
|
169
114
|
}
|
|
170
115
|
};
|
|
171
116
|
|
|
172
|
-
// src/utils/listener_set.ts
|
|
173
117
|
var ListenerSet = class {
|
|
174
|
-
/** The generation every `add` joins until the next `dispose()`. */
|
|
175
118
|
#abort = new AbortController();
|
|
176
|
-
/**
|
|
177
|
-
* Attaches `handler` to the open generation, exactly as the caller spelled it.
|
|
178
|
-
*
|
|
179
|
-
* The set supplies the signal, so `options` carries everything else the DOM
|
|
180
|
-
* accepts — `capture` included, which has to match at release time and no
|
|
181
|
-
* longer has a second place to drift from.
|
|
182
|
-
*/
|
|
183
119
|
add(target, type, handler, options) {
|
|
184
120
|
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
185
121
|
}
|
|
186
|
-
/**
|
|
187
|
-
* Releases every listener of the open generation, synchronously, and opens the
|
|
188
|
-
* next one. Idempotent, and safe before anything has been added.
|
|
189
|
-
*/
|
|
190
122
|
dispose() {
|
|
191
123
|
this.#abort.abort();
|
|
192
124
|
this.#abort = new AbortController();
|
|
193
125
|
}
|
|
194
126
|
};
|
|
195
127
|
|
|
196
|
-
// src/utils/logical_scroll.ts
|
|
197
128
|
function isRtl(element) {
|
|
198
129
|
return window.getComputedStyle(element).direction === "rtl";
|
|
199
130
|
}
|
|
@@ -210,19 +141,113 @@ function physicalScrollDelta(element, horizontal, logicalDelta) {
|
|
|
210
141
|
return horizontal && isRtl(element) ? -logicalDelta : logicalDelta;
|
|
211
142
|
}
|
|
212
143
|
|
|
213
|
-
|
|
144
|
+
var MicrotaskCoalescer = class {
|
|
145
|
+
#run;
|
|
146
|
+
#queued = false;
|
|
147
|
+
#active = false;
|
|
148
|
+
#generation = 0;
|
|
149
|
+
constructor(run) {
|
|
150
|
+
this.#run = run;
|
|
151
|
+
}
|
|
152
|
+
activate() {
|
|
153
|
+
this.#active = true;
|
|
154
|
+
}
|
|
155
|
+
cancel() {
|
|
156
|
+
this.#active = false;
|
|
157
|
+
this.#queued = false;
|
|
158
|
+
this.#generation += 1;
|
|
159
|
+
}
|
|
160
|
+
schedule() {
|
|
161
|
+
if (!this.#active || this.#queued) return;
|
|
162
|
+
this.#queued = true;
|
|
163
|
+
const generation = this.#generation;
|
|
164
|
+
queueMicrotask(() => {
|
|
165
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
166
|
+
this.#queued = false;
|
|
167
|
+
this.#run();
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
var MorphRenderWatcher = class {
|
|
173
|
+
#listeners = new ListenerSet();
|
|
174
|
+
#pass;
|
|
175
|
+
#element = null;
|
|
176
|
+
#onMorph = () => this.#pass.schedule();
|
|
177
|
+
constructor(run) {
|
|
178
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
179
|
+
}
|
|
180
|
+
observe(element) {
|
|
181
|
+
if (this.#element === element) return;
|
|
182
|
+
this.disconnect();
|
|
183
|
+
this.#element = element;
|
|
184
|
+
this.#pass.activate();
|
|
185
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
186
|
+
}
|
|
187
|
+
schedule() {
|
|
188
|
+
this.#pass.schedule();
|
|
189
|
+
}
|
|
190
|
+
disconnect() {
|
|
191
|
+
this.#listeners.dispose();
|
|
192
|
+
this.#pass.cancel();
|
|
193
|
+
this.#element = null;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
var NUMBER_BOUNDS = {
|
|
198
|
+
finite: { finite: true }};
|
|
199
|
+
function matchesNumberBounds(value, bounds) {
|
|
200
|
+
if (!Number.isFinite(value)) {
|
|
201
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
202
|
+
if (direction === null) return false;
|
|
203
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
204
|
+
}
|
|
205
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
206
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
207
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
208
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
209
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function readNumber(raw, fallback, bounds) {
|
|
214
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
var NumberValueReader = class {
|
|
218
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
219
|
+
read(owner, name, raw, fallback, bounds) {
|
|
220
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
221
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
222
|
+
this.#lastRejected.delete(name);
|
|
223
|
+
return resolved;
|
|
224
|
+
}
|
|
225
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
226
|
+
const literal = owner.element.getAttribute(attribute);
|
|
227
|
+
if (literal === null) {
|
|
228
|
+
this.#lastRejected.delete(name);
|
|
229
|
+
return resolved;
|
|
230
|
+
}
|
|
231
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
232
|
+
this.#lastRejected.set(name, literal);
|
|
233
|
+
console.warn(
|
|
234
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return resolved;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
214
241
|
function prefersReducedMotion() {
|
|
215
242
|
return typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
216
243
|
}
|
|
217
244
|
|
|
218
|
-
// src/controllers/overflow_indicator_controller.ts
|
|
219
245
|
var DIRECTION_PARAM = "direction-param";
|
|
220
|
-
var OverflowIndicatorController = class extends Controller {
|
|
221
|
-
|
|
246
|
+
var OverflowIndicatorController = class _OverflowIndicatorController extends Controller {
|
|
247
|
+
#numbers = new NumberValueReader();
|
|
222
248
|
get #directionParam() {
|
|
223
249
|
return `data-${this.identifier}-${DIRECTION_PARAM}`;
|
|
224
250
|
}
|
|
225
|
-
/** Selects every button carrying that param. */
|
|
226
251
|
get #directionButtonSelector() {
|
|
227
252
|
return `[${this.#directionParam}]`;
|
|
228
253
|
}
|
|
@@ -231,37 +256,34 @@ var OverflowIndicatorController = class extends Controller {
|
|
|
231
256
|
orientation: { type: String, default: "horizontal" },
|
|
232
257
|
threshold: { type: Number, default: 1 }
|
|
233
258
|
};
|
|
259
|
+
static valueConstraints = {
|
|
260
|
+
threshold: NUMBER_BOUNDS.finite
|
|
261
|
+
};
|
|
234
262
|
static actions = ["scrollByPage"];
|
|
235
263
|
static events = ["change"];
|
|
236
264
|
#layout = new LayoutObserver(() => {
|
|
237
265
|
if (this.#connected) this.#refresh();
|
|
238
266
|
});
|
|
239
|
-
/** Folds a burst of scrolls into one measurement per frame. */
|
|
240
267
|
#frames = new FrameCoalescer();
|
|
241
|
-
/** Holds the viewport's scroll listener for exactly as long as that viewport. */
|
|
242
268
|
#viewportListeners = new ListenerSet();
|
|
243
269
|
#onScroll = () => this.#frames.schedule(() => this.#refresh());
|
|
244
270
|
#connected = false;
|
|
245
271
|
#observedViewport = null;
|
|
246
272
|
#observedContent = /* @__PURE__ */ new Set();
|
|
247
273
|
#mutationObserver = null;
|
|
248
|
-
/**
|
|
249
|
-
* Boundary buttons whose native `disabled` is held back while they hold focus.
|
|
250
|
-
*
|
|
251
|
-
* Completing the deferral is what actually disables the button, so the release
|
|
252
|
-
* callback — unlike {@link #cancelPendingButtonDisable} — applies it.
|
|
253
|
-
*/
|
|
254
274
|
#pendingButtonDisables = new BlurDeferral((button) => {
|
|
255
275
|
this.#restorePendingMarkers(button);
|
|
256
276
|
if (!button.disabled) this.#disableButton(button);
|
|
257
277
|
});
|
|
258
|
-
/** Last reported room, so `change` fires only on transitions. */
|
|
259
278
|
#state = null;
|
|
279
|
+
#morphRender = new MorphRenderWatcher(() => this.#refresh());
|
|
260
280
|
connect() {
|
|
281
|
+
this.#morphRender.observe(this.element);
|
|
261
282
|
this.#connected = true;
|
|
262
283
|
this.#syncViewport();
|
|
263
284
|
}
|
|
264
285
|
disconnect() {
|
|
286
|
+
this.#morphRender.disconnect();
|
|
265
287
|
this.#connected = false;
|
|
266
288
|
this.#stopObservingViewport();
|
|
267
289
|
this.#layout.disconnect();
|
|
@@ -281,11 +303,6 @@ var OverflowIndicatorController = class extends Controller {
|
|
|
281
303
|
thresholdValueChanged() {
|
|
282
304
|
if (this.#connected) this.#refresh();
|
|
283
305
|
}
|
|
284
|
-
/**
|
|
285
|
-
* Re-measures remaining scroll room and reflects the state hooks.
|
|
286
|
-
*
|
|
287
|
-
* @stimeoRenderRoot
|
|
288
|
-
*/
|
|
289
306
|
#refresh() {
|
|
290
307
|
if (!this.hasViewportTarget) return;
|
|
291
308
|
const vp = this.viewportTarget;
|
|
@@ -302,7 +319,6 @@ var OverflowIndicatorController = class extends Controller {
|
|
|
302
319
|
this.dispatch("change", { detail: { start, end } });
|
|
303
320
|
}
|
|
304
321
|
}
|
|
305
|
-
/** Scrolls one viewport page toward the `direction` param (`start`/`end`). */
|
|
306
322
|
scrollByPage(event) {
|
|
307
323
|
if (!this.hasViewportTarget) return;
|
|
308
324
|
const direction = this.#directionFromEvent(event);
|
|
@@ -320,7 +336,6 @@ var OverflowIndicatorController = class extends Controller {
|
|
|
320
336
|
vp.scrollBy({ top: delta, behavior });
|
|
321
337
|
}
|
|
322
338
|
}
|
|
323
|
-
/** Mirrors remaining room onto any direction buttons by toggling `disabled`. */
|
|
324
339
|
#syncButtons(start, end) {
|
|
325
340
|
for (const button of this.#pendingButtonDisables.elements) {
|
|
326
341
|
if (!button.isConnected || button.closest(`[data-controller~='${this.identifier}']`) !== this.element) {
|
|
@@ -337,12 +352,6 @@ var OverflowIndicatorController = class extends Controller {
|
|
|
337
352
|
else if (direction === "end") this.#toggleButton(button, end);
|
|
338
353
|
}
|
|
339
354
|
}
|
|
340
|
-
/**
|
|
341
|
-
* Reflects the remaining room onto a page button's `disabled`, owning only the
|
|
342
|
-
* `disabled` it sets itself via a marker (`data-overflow-indicator-disabled`,
|
|
343
|
-
* like `number-input`/`conditional-fields`). An author-disabled button (e.g. the
|
|
344
|
-
* whole control disabled) is therefore never blindly re-enabled.
|
|
345
|
-
*/
|
|
346
355
|
#toggleButton(button, hasRoom) {
|
|
347
356
|
if (!this.#pendingButtonDisables.has(button) && button.hasAttribute("data-overflow-indicator-pending-disabled")) {
|
|
348
357
|
this.#cancelPendingButtonDisable(button);
|
|
@@ -362,10 +371,6 @@ var OverflowIndicatorController = class extends Controller {
|
|
|
362
371
|
this.#disableButton(button);
|
|
363
372
|
}
|
|
364
373
|
}
|
|
365
|
-
/**
|
|
366
|
-
* Keeps a boundary button focusable until native blur, exposing its temporary
|
|
367
|
-
* inoperability with `aria-disabled` and making its action a no-op meanwhile.
|
|
368
|
-
*/
|
|
369
374
|
#deferButtonDisable(button) {
|
|
370
375
|
if (this.#pendingButtonDisables.has(button)) return;
|
|
371
376
|
button.setAttribute("data-overflow-indicator-pending-disabled", "");
|
|
@@ -380,18 +385,10 @@ var OverflowIndicatorController = class extends Controller {
|
|
|
380
385
|
button.disabled = true;
|
|
381
386
|
button.setAttribute("data-overflow-indicator-disabled", "");
|
|
382
387
|
}
|
|
383
|
-
/** Cancels a pending disable without applying it, restoring the displaced state. */
|
|
384
388
|
#cancelPendingButtonDisable(button) {
|
|
385
389
|
this.#pendingButtonDisables.release(button);
|
|
386
390
|
this.#restorePendingMarkers(button);
|
|
387
391
|
}
|
|
388
|
-
/**
|
|
389
|
-
* Drops the pending marker and gives back the `aria-disabled` it displaced.
|
|
390
|
-
*
|
|
391
|
-
* Reads the displaced value from the DOM rather than memory: a Turbo snapshot is
|
|
392
|
-
* cloned before `disconnect()` runs, so a restored page can arrive with markers
|
|
393
|
-
* this instance never wrote.
|
|
394
|
-
*/
|
|
395
392
|
#restorePendingMarkers(button) {
|
|
396
393
|
button.removeAttribute("data-overflow-indicator-pending-disabled");
|
|
397
394
|
const displaced = button.getAttribute("data-overflow-indicator-aria-disabled");
|
|
@@ -408,7 +405,6 @@ var OverflowIndicatorController = class extends Controller {
|
|
|
408
405
|
this.#cancelPendingButtonDisable(button);
|
|
409
406
|
}
|
|
410
407
|
}
|
|
411
|
-
/** Moves resize/mutation/load observation to the current viewport target. */
|
|
412
408
|
#syncViewport() {
|
|
413
409
|
if (!this.#connected) return;
|
|
414
410
|
const next = this.hasViewportTarget ? this.viewportTarget : null;
|
|
@@ -438,7 +434,6 @@ var OverflowIndicatorController = class extends Controller {
|
|
|
438
434
|
this.#state = null;
|
|
439
435
|
this.#refresh();
|
|
440
436
|
}
|
|
441
|
-
/** Observes direct content boxes whose resize can change the viewport's scroll extent. */
|
|
442
437
|
#syncContentObservation() {
|
|
443
438
|
const next = new Set(this.#observedViewport?.children ?? []);
|
|
444
439
|
for (const content of this.#observedContent) {
|
|
@@ -467,16 +462,24 @@ var OverflowIndicatorController = class extends Controller {
|
|
|
467
462
|
this.#layout.unobserveViewport();
|
|
468
463
|
}
|
|
469
464
|
get #threshold() {
|
|
470
|
-
const value = this
|
|
471
|
-
return
|
|
465
|
+
const value = this.#safeThreshold;
|
|
466
|
+
return Math.max(0, value);
|
|
472
467
|
}
|
|
473
468
|
#directionFromEvent(event) {
|
|
474
469
|
const params = event.params;
|
|
475
470
|
const direction = params?.direction;
|
|
476
471
|
return direction === "start" || direction === "end" ? direction : null;
|
|
477
472
|
}
|
|
473
|
+
get #safeThreshold() {
|
|
474
|
+
return this.#numbers.read(
|
|
475
|
+
this,
|
|
476
|
+
"threshold",
|
|
477
|
+
this.thresholdValue,
|
|
478
|
+
_OverflowIndicatorController.values.threshold.default,
|
|
479
|
+
_OverflowIndicatorController.valueConstraints.threshold
|
|
480
|
+
);
|
|
481
|
+
}
|
|
478
482
|
};
|
|
479
483
|
|
|
480
484
|
export { OverflowIndicatorController };
|
|
481
|
-
//# sourceMappingURL=overflow_indicator_controller.js.map
|
|
482
485
|
//# sourceMappingURL=overflow_indicator_controller.js.map
|