stimeo-ui 0.16.0 → 0.18.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 +136 -0
- data/README.md +27 -11
- data/dist/cable/index.js +384 -449
- data/dist/controllers/accordion_controller.js +174 -55
- data/dist/controllers/alert_dialog_controller.js +903 -235
- data/dist/controllers/announcer_controller.js +238 -205
- data/dist/controllers/aspect_ratio_controller.js +0 -10
- data/dist/controllers/auto_submit_controller.js +414 -145
- data/dist/controllers/avatar_controller.js +185 -107
- data/dist/controllers/breadcrumb_controller.js +0 -130
- data/dist/controllers/bulk_select_controller.js +282 -58
- data/dist/controllers/calendar_controller.js +228 -260
- data/dist/controllers/carousel_controller.js +536 -436
- data/dist/controllers/character_counter_controller.js +493 -207
- data/dist/controllers/checkbox_controller.js +38 -74
- data/dist/controllers/clipboard_controller.js +261 -149
- data/dist/controllers/collapsible_controller.js +304 -120
- data/dist/controllers/color_picker_controller.js +178 -146
- data/dist/controllers/combobox_controller.js +522 -156
- data/dist/controllers/command_palette_controller.js +1156 -395
- data/dist/controllers/conditional_fields_controller.js +300 -171
- data/dist/controllers/confirm_controller.js +980 -262
- data/dist/controllers/context_menu_controller.js +346 -153
- data/dist/controllers/count_up_controller.js +73 -21
- data/dist/controllers/countdown_controller.js +281 -158
- data/dist/controllers/currency_input_controller.js +575 -225
- data/dist/controllers/data_grid_controller.js +248 -214
- data/dist/controllers/date_range_picker_controller.js +338 -235
- data/dist/controllers/dialog_controller.js +899 -237
- data/dist/controllers/direct_upload_controller.js +97 -167
- data/dist/controllers/dirty_form_controller.js +72 -69
- data/dist/controllers/dismissible_controller.js +196 -24
- data/dist/controllers/drawer_controller.js +974 -355
- data/dist/controllers/dropdown_controller.js +339 -96
- data/dist/controllers/editable_controller.js +315 -88
- data/dist/controllers/empty_state_controller.js +79 -58
- data/dist/controllers/file_dropzone_controller.js +322 -211
- data/dist/controllers/filter_controller.js +77 -42
- data/dist/controllers/flash_controller.js +238 -347
- data/dist/controllers/focus_controller.js +679 -240
- data/dist/controllers/form_field_controller.js +156 -132
- data/dist/controllers/form_validation_controller.js +31 -97
- data/dist/controllers/frame_loading_controller.js +275 -217
- data/dist/controllers/highlight_controller.js +107 -75
- data/dist/controllers/hover_card_controller.js +327 -130
- data/dist/controllers/idle_controller.js +271 -101
- data/dist/controllers/input_mask_controller.js +483 -97
- data/dist/controllers/intersection_controller.js +150 -106
- data/dist/controllers/lazy_frame_controller.js +24 -68
- data/dist/controllers/listbox_controller.js +523 -240
- data/dist/controllers/local_time_controller.js +102 -77
- data/dist/controllers/masonry_controller.js +80 -96
- data/dist/controllers/menu_controller.js +302 -188
- data/dist/controllers/menubar_controller.js +232 -345
- data/dist/controllers/meter_controller.js +417 -87
- data/dist/controllers/multi_select_controller.js +785 -393
- data/dist/controllers/navigation_menu_controller.js +190 -250
- data/dist/controllers/nested_form_controller.js +396 -192
- data/dist/controllers/network_status_controller.js +268 -62
- data/dist/controllers/number_input_controller.js +610 -303
- data/dist/controllers/optimistic_controller.js +45 -87
- data/dist/controllers/otp_controller.js +658 -255
- data/dist/controllers/overflow_indicator_controller.js +122 -113
- data/dist/controllers/overflow_menu_controller.js +470 -295
- data/dist/controllers/pagination_controller.js +332 -123
- data/dist/controllers/password_reveal_controller.js +392 -121
- data/dist/controllers/password_strength_controller.js +111 -158
- data/dist/controllers/persist_controller.js +165 -111
- data/dist/controllers/pointer_drag_controller.js +434 -188
- data/dist/controllers/popover_controller.js +269 -113
- data/dist/controllers/portal_controller.js +215 -76
- data/dist/controllers/preview_guard_controller.js +243 -116
- data/dist/controllers/progress_controller.js +391 -78
- data/dist/controllers/radio_group_controller.js +278 -211
- data/dist/controllers/range_slider_controller.js +220 -113
- data/dist/controllers/rating_controller.js +458 -211
- data/dist/controllers/read_more_controller.js +138 -63
- data/dist/controllers/reading_progress_controller.js +166 -137
- data/dist/controllers/relative_time_controller.js +211 -112
- data/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
- data/dist/controllers/resizable_controller.js +299 -150
- data/dist/controllers/roving_controller.js +49 -88
- data/dist/controllers/scroll_area_controller.js +490 -246
- data/dist/controllers/scroll_restore_controller.js +14 -55
- data/dist/controllers/scroll_visibility_controller.js +432 -149
- data/dist/controllers/scrollspy_controller.js +193 -248
- data/dist/controllers/separator_controller.js +285 -142
- data/dist/controllers/sidebar_controller.js +1069 -404
- data/dist/controllers/skeleton_controller.js +259 -101
- data/dist/controllers/slider_controller.js +187 -86
- data/dist/controllers/smart_sticky_header_controller.js +173 -66
- data/dist/controllers/sortable_controller.js +26 -125
- data/dist/controllers/spinner_controller.js +288 -173
- data/dist/controllers/step_indicator_controller.js +105 -46
- data/dist/controllers/stepper_controller.js +120 -73
- data/dist/controllers/stick_to_bottom_controller.js +275 -140
- data/dist/controllers/sticky_observer_controller.js +220 -49
- data/dist/controllers/submit_once_controller.js +376 -227
- data/dist/controllers/switch_controller.js +201 -84
- data/dist/controllers/tabs_controller.js +41 -30
- data/dist/controllers/tags_input_controller.js +293 -173
- data/dist/controllers/textarea_autosize_controller.js +108 -74
- data/dist/controllers/theme_controller.js +140 -116
- data/dist/controllers/time_picker_controller.js +331 -127
- data/dist/controllers/toast_controller.js +244 -324
- data/dist/controllers/toggle_group_controller.js +252 -191
- data/dist/controllers/toolbar_controller.js +219 -120
- data/dist/controllers/tooltip_controller.js +158 -124
- data/dist/controllers/transition_controller.js +123 -119
- data/dist/controllers/tree_view_controller.js +283 -369
- data/dist/index.js +8573 -10410
- data/dist/positioning/index.js +154 -47
- data/lib/generators/stimeo/install/install_generator.rb +7 -2
- data/lib/generators/stimeo/install/templates/stimeo.js +19 -6
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +3 -3
|
@@ -1,40 +1,48 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
// src/controllers/resizable_controller.ts
|
|
4
|
-
|
|
5
|
-
// src/utils/arrow_step.ts
|
|
6
3
|
function isReservedArrowChord(event, allow = []) {
|
|
7
4
|
if (!event.key.startsWith("Arrow")) return false;
|
|
8
5
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
9
6
|
}
|
|
7
|
+
function hasModifierChord(event) {
|
|
8
|
+
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
9
|
+
}
|
|
10
10
|
|
|
11
|
-
// src/utils/event_owner.ts
|
|
12
11
|
function ownerIndex(candidates, node) {
|
|
13
12
|
if (!(node instanceof Node)) return -1;
|
|
14
13
|
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
var ListenerSet = class {
|
|
17
|
+
#abort = new AbortController();
|
|
18
|
+
add(target, type, handler, options) {
|
|
19
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
20
|
+
}
|
|
21
|
+
dispose() {
|
|
22
|
+
this.#abort.abort();
|
|
23
|
+
this.#abort = new AbortController();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
18
27
|
var MicrotaskCoalescer = class {
|
|
19
28
|
#run;
|
|
20
29
|
#queued = false;
|
|
21
30
|
#active = false;
|
|
22
31
|
#generation = 0;
|
|
23
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
24
32
|
constructor(run) {
|
|
25
33
|
this.#run = run;
|
|
26
34
|
}
|
|
27
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
28
35
|
activate() {
|
|
29
36
|
this.#active = true;
|
|
30
37
|
}
|
|
31
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
32
38
|
cancel() {
|
|
33
39
|
this.#active = false;
|
|
34
40
|
this.#queued = false;
|
|
35
41
|
this.#generation += 1;
|
|
36
42
|
}
|
|
37
|
-
|
|
43
|
+
get pending() {
|
|
44
|
+
return this.#queued;
|
|
45
|
+
}
|
|
38
46
|
schedule() {
|
|
39
47
|
if (!this.#active || this.#queued) return;
|
|
40
48
|
this.#queued = true;
|
|
@@ -47,85 +55,227 @@ var MicrotaskCoalescer = class {
|
|
|
47
55
|
}
|
|
48
56
|
};
|
|
49
57
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
};
|
|
58
|
-
#rewind;
|
|
59
|
-
/** @param rewind - the pass that returns this controller's state to its initial form. */
|
|
60
|
-
constructor(rewind) {
|
|
61
|
-
this.#rewind = rewind;
|
|
58
|
+
var MorphRenderWatcher = class {
|
|
59
|
+
#listeners = new ListenerSet();
|
|
60
|
+
#pass;
|
|
61
|
+
#element = null;
|
|
62
|
+
#onMorph = () => this.#pass.schedule();
|
|
63
|
+
constructor(run) {
|
|
64
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
62
65
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
observe(element) {
|
|
67
|
+
if (this.#element === element) return;
|
|
68
|
+
this.disconnect();
|
|
69
|
+
this.#element = element;
|
|
70
|
+
this.#pass.activate();
|
|
71
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
72
|
+
}
|
|
73
|
+
schedule() {
|
|
74
|
+
this.#pass.schedule();
|
|
75
|
+
}
|
|
76
|
+
get pending() {
|
|
77
|
+
return this.#pass.pending;
|
|
78
|
+
}
|
|
79
|
+
disconnect() {
|
|
80
|
+
this.#listeners.dispose();
|
|
81
|
+
this.#pass.cancel();
|
|
82
|
+
this.#element = null;
|
|
70
83
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
var NUMBER_BOUNDS = {
|
|
87
|
+
finite: { finite: true },
|
|
88
|
+
positive: { finite: true, exclusiveMin: 0 }};
|
|
89
|
+
function matchesNumberBounds(value, bounds) {
|
|
90
|
+
if (!Number.isFinite(value)) {
|
|
91
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
92
|
+
if (direction === null) return false;
|
|
93
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
94
|
+
}
|
|
95
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
96
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
97
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
98
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
99
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function readNumber(raw, fallback, bounds) {
|
|
104
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
var NumberValueReader = class {
|
|
108
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
109
|
+
read(owner, name, raw, fallback, bounds) {
|
|
110
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
111
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
112
|
+
this.#lastRejected.delete(name);
|
|
113
|
+
return resolved;
|
|
114
|
+
}
|
|
115
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
116
|
+
const literal = owner.element.getAttribute(attribute);
|
|
117
|
+
if (literal === null) {
|
|
118
|
+
this.#lastRejected.delete(name);
|
|
119
|
+
return resolved;
|
|
120
|
+
}
|
|
121
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
122
|
+
this.#lastRejected.set(name, literal);
|
|
123
|
+
console.warn(
|
|
124
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return resolved;
|
|
76
128
|
}
|
|
77
129
|
};
|
|
78
130
|
|
|
79
|
-
|
|
80
|
-
|
|
131
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
132
|
+
if (element.getAttribute(name) === value) return false;
|
|
133
|
+
if (value === null) element.removeAttribute(name);
|
|
134
|
+
else element.setAttribute(name, value);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
function deepActiveElement(ownerDocument) {
|
|
138
|
+
let active = ownerDocument.activeElement;
|
|
139
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
140
|
+
return active;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function queryOpenDescendants(root, selector) {
|
|
144
|
+
const matches = [];
|
|
145
|
+
const roots = [root];
|
|
146
|
+
for (const scope of roots) {
|
|
147
|
+
matches.push(...scope.querySelectorAll(selector));
|
|
148
|
+
if (scope.nodeType === Node.ELEMENT_NODE) {
|
|
149
|
+
const shadow = scope.shadowRoot;
|
|
150
|
+
if (shadow) roots.push(shadow);
|
|
151
|
+
}
|
|
152
|
+
for (const element of scope.querySelectorAll("*")) {
|
|
153
|
+
if (element.shadowRoot) roots.push(element.shadowRoot);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return matches;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function sharedRegistry(key, create) {
|
|
160
|
+
const symbol = Symbol.for(key);
|
|
161
|
+
const scope = globalThis;
|
|
162
|
+
const existing = scope[symbol];
|
|
163
|
+
if (existing !== void 0) return existing;
|
|
164
|
+
const registry = create();
|
|
165
|
+
Object.defineProperty(globalThis, symbol, { value: registry });
|
|
166
|
+
return registry;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function holdsFocus(element) {
|
|
170
|
+
return element.matches(":focus") || deepActiveElement(element.ownerDocument) === element;
|
|
171
|
+
}
|
|
172
|
+
var TabindexLoan = class _TabindexLoan {
|
|
173
|
+
static #registry = sharedRegistry(
|
|
174
|
+
"stimeo-ui.tabindex-loan.registry.v1",
|
|
175
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
|
|
176
|
+
);
|
|
81
177
|
#value;
|
|
178
|
+
#record;
|
|
179
|
+
#preserveFocus;
|
|
82
180
|
#lent = /* @__PURE__ */ new Set();
|
|
83
|
-
|
|
84
|
-
#beforeCache = new BeforeCacheReset(() => this.returnAll());
|
|
85
|
-
/**
|
|
86
|
-
* @param value - the `tabindex` to lend. `"-1"` (the default) is
|
|
87
|
-
* programmatically focusable but not a Tab stop; `"0"` is a real Tab stop,
|
|
88
|
-
* which a scroll region with no focusable content of its own needs.
|
|
89
|
-
*/
|
|
90
|
-
constructor(value = "-1") {
|
|
181
|
+
constructor(value, owner, options = {}) {
|
|
91
182
|
this.#value = value;
|
|
183
|
+
this.#record = `data-${owner}-tabindex-loan`;
|
|
184
|
+
this.#preserveFocus = options.preserveFocus === true;
|
|
185
|
+
_TabindexLoan.#registry.names.add(this.#record);
|
|
92
186
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
187
|
+
lend(element, options = {}) {
|
|
188
|
+
const stack = this.#stack(element);
|
|
189
|
+
const current = element.getAttribute("tabindex");
|
|
190
|
+
const held = stack.find((hold) => hold.loan === this) ?? stack.find(
|
|
191
|
+
(hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
|
|
192
|
+
);
|
|
193
|
+
const linked = stack.at(-1)?.value === current;
|
|
194
|
+
if (!held && options.onlyIfUnfocusable && (current !== null || element.tabIndex >= 0)) return;
|
|
195
|
+
if (!held && current !== null && !linked) return;
|
|
97
196
|
this.#lent.add(element);
|
|
98
|
-
|
|
197
|
+
if (held) held.loan = this;
|
|
198
|
+
else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
|
|
199
|
+
this.#mark(element, stack);
|
|
200
|
+
if (current === null || !held && current !== this.#value) {
|
|
201
|
+
element.setAttribute("tabindex", this.#value);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return(element) {
|
|
205
|
+
const stack = this.#stack(element);
|
|
206
|
+
const hold = stack.find((held) => held.loan === this) ?? stack.find(
|
|
207
|
+
(held) => held.loan === null && held.record === this.#record && held.value === this.#value
|
|
208
|
+
);
|
|
209
|
+
if (!hold) return false;
|
|
210
|
+
if (!this.#release(element, stack, hold, this.#preserveFocus)) {
|
|
211
|
+
hold.loan = this;
|
|
212
|
+
this.#lent.add(element);
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
this.#lent.delete(element);
|
|
216
|
+
return true;
|
|
99
217
|
}
|
|
100
|
-
/** Takes back every loan whose value is still the one that was lent. */
|
|
101
218
|
returnAll() {
|
|
102
|
-
for (const element of this.#lent)
|
|
103
|
-
|
|
219
|
+
for (const element of Array.from(this.#lent)) this.return(element);
|
|
220
|
+
}
|
|
221
|
+
reclaim(element) {
|
|
222
|
+
if (element.getAttribute(this.#record) !== this.#value) return;
|
|
223
|
+
const stack = this.#stack(element);
|
|
224
|
+
const hold = stack.find((held) => held.loan === null && held.record === this.#record);
|
|
225
|
+
if (hold && !holdsFocus(element)) this.#release(element, stack, hold);
|
|
226
|
+
}
|
|
227
|
+
reclaimWithin(root) {
|
|
228
|
+
for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
|
|
229
|
+
this.reclaim(element);
|
|
104
230
|
}
|
|
105
|
-
this.#lent.clear();
|
|
106
|
-
this.#beforeCache.deactivate();
|
|
107
231
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
constructor(options) {
|
|
114
|
-
this.#attributes = options.attributes;
|
|
232
|
+
#stack(element) {
|
|
233
|
+
const stacks = _TabindexLoan.#registry.stacks;
|
|
234
|
+
const stack = stacks.get(element) ?? this.#seed(element);
|
|
235
|
+
stacks.set(element, stack);
|
|
236
|
+
return stack;
|
|
115
237
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
238
|
+
#seed(element) {
|
|
239
|
+
const current = element.getAttribute("tabindex");
|
|
240
|
+
const holds = [];
|
|
241
|
+
for (const record of _TabindexLoan.#registry.names) {
|
|
242
|
+
const value = element.getAttribute(record);
|
|
243
|
+
if (value !== null) holds.push({ loan: null, record, value, linked: true });
|
|
244
|
+
}
|
|
245
|
+
holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
|
|
246
|
+
for (const [index, hold] of holds.entries()) hold.linked = index > 0;
|
|
247
|
+
return holds;
|
|
248
|
+
}
|
|
249
|
+
#release(element, stack, hold, preserveFocus = false) {
|
|
250
|
+
const current = element.getAttribute("tabindex");
|
|
251
|
+
const index = stack.indexOf(hold);
|
|
252
|
+
const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
|
|
253
|
+
if (owns && preserveFocus && holdsFocus(element)) return false;
|
|
254
|
+
stack.splice(index, 1);
|
|
255
|
+
const above = stack[index];
|
|
256
|
+
if (above) above.linked &&= hold.linked;
|
|
257
|
+
this.#mark(element, stack);
|
|
258
|
+
if (!owns) return true;
|
|
259
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
260
|
+
if (!below) element.removeAttribute("tabindex");
|
|
261
|
+
else if (below.value !== current) element.setAttribute("tabindex", below.value);
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
#mark(element, stack) {
|
|
265
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
266
|
+
for (const hold of stack) records.set(hold.record, hold.value);
|
|
267
|
+
for (const [name, recorded] of records) {
|
|
268
|
+
setAttributeIfChanged(element, name, recorded);
|
|
269
|
+
}
|
|
119
270
|
}
|
|
120
271
|
};
|
|
121
272
|
|
|
122
|
-
// src/controllers/resizable_controller.ts
|
|
123
|
-
var TRANSIENT = new TransientHooks({ attributes: ["data-dragging"] });
|
|
124
273
|
var DEFAULT_MIN = 0;
|
|
125
274
|
var DEFAULT_MAX = 100;
|
|
126
275
|
var DEFAULT_VALUE = 50;
|
|
127
276
|
var DEFAULT_STEP = 1;
|
|
128
|
-
var ResizableController = class extends Controller {
|
|
277
|
+
var ResizableController = class _ResizableController extends Controller {
|
|
278
|
+
#numbers = new NumberValueReader();
|
|
129
279
|
static targets = ["primary", "secondary", "separator"];
|
|
130
280
|
static values = {
|
|
131
281
|
min: { type: Number, default: DEFAULT_MIN },
|
|
@@ -133,55 +283,39 @@ var ResizableController = class extends Controller {
|
|
|
133
283
|
step: { type: Number, default: DEFAULT_STEP },
|
|
134
284
|
value: { type: Number, default: DEFAULT_VALUE }
|
|
135
285
|
};
|
|
286
|
+
static valueConstraints = {
|
|
287
|
+
min: NUMBER_BOUNDS.finite,
|
|
288
|
+
max: NUMBER_BOUNDS.finite,
|
|
289
|
+
step: NUMBER_BOUNDS.positive,
|
|
290
|
+
value: NUMBER_BOUNDS.finite
|
|
291
|
+
};
|
|
136
292
|
static actions = ["onKeydown", "onPointerDown", "toggle"];
|
|
137
293
|
static events = ["change", "reconcile"];
|
|
138
|
-
/** Where the divider sat before the current collapse; `null` when unknown. */
|
|
139
294
|
#valueBeforeCollapse = null;
|
|
140
|
-
/**
|
|
141
|
-
* The position last painted: moved by each move the user makes — every step of
|
|
142
|
-
* a drag included — and by each repaint that finds a page-driven move.
|
|
143
|
-
*/
|
|
144
295
|
#painted = DEFAULT_VALUE;
|
|
145
|
-
/**
|
|
146
|
-
* The position last confirmed: the one connecting published, then moved by
|
|
147
|
-
* each `change` and `reconcile`. A drag moves the painted position step by
|
|
148
|
-
* step and this one only when it ends.
|
|
149
|
-
*/
|
|
150
296
|
#confirmed = DEFAULT_VALUE;
|
|
151
|
-
|
|
152
|
-
#
|
|
153
|
-
|
|
154
|
-
#
|
|
155
|
-
/** Releases the pane cycle listener bound in {@link connect}. */
|
|
156
|
-
#cycleAbort = null;
|
|
157
|
-
/** Folds the range and position inputs of one morph batch into a single paint. */
|
|
158
|
-
#repaint = new MicrotaskCoalescer(() => this.#render());
|
|
297
|
+
#drag = null;
|
|
298
|
+
#paneTabindex = new TabindexLoan("-1", this.identifier);
|
|
299
|
+
#listeners = new ListenerSet();
|
|
300
|
+
#repaint = new MorphRenderWatcher(() => this.#render());
|
|
159
301
|
connect() {
|
|
160
|
-
|
|
161
|
-
this.#repaint.
|
|
302
|
+
this.#paneTabindex.reclaimWithin(this.element);
|
|
303
|
+
this.#repaint.observe(this.element);
|
|
162
304
|
this.#confirmed = this.#position;
|
|
163
305
|
this.#render();
|
|
164
|
-
this.#
|
|
165
|
-
this.element.addEventListener("keydown", this.#onCycleKeydown, {
|
|
166
|
-
signal: this.#cycleAbort.signal
|
|
167
|
-
});
|
|
306
|
+
this.#listeners.add(this.element, "keydown", this.#onCycleKeydown);
|
|
168
307
|
}
|
|
169
|
-
/** Cancels any active pointer drag so listeners never leak past disconnect. */
|
|
170
308
|
disconnect() {
|
|
171
|
-
this.#
|
|
172
|
-
this.#
|
|
173
|
-
this.#
|
|
174
|
-
this.#cycleAbort = null;
|
|
175
|
-
this.#repaint.cancel();
|
|
309
|
+
this.#endDrag();
|
|
310
|
+
this.#listeners.dispose();
|
|
311
|
+
this.#repaint.disconnect();
|
|
176
312
|
this.#paneTabindex.returnAll();
|
|
177
|
-
this.element.removeAttribute("data-dragging");
|
|
178
313
|
}
|
|
179
|
-
/** Moves focus to the next pane on `F6`, wrapping; entering at the first one. */
|
|
180
314
|
#onCycleKeydown = (event) => {
|
|
181
315
|
if (event.key !== "F6") return;
|
|
182
316
|
if (event.isComposing) return;
|
|
183
317
|
if (event.defaultPrevented) return;
|
|
184
|
-
if (event
|
|
318
|
+
if (hasModifierChord(event)) return;
|
|
185
319
|
const panes = [];
|
|
186
320
|
if (this.hasPrimaryTarget) panes.push(this.primaryTarget);
|
|
187
321
|
if (this.hasSecondaryTarget) panes.push(this.secondaryTarget);
|
|
@@ -192,40 +326,42 @@ var ResizableController = class extends Controller {
|
|
|
192
326
|
this.#paneTabindex.lend(next);
|
|
193
327
|
next.focus();
|
|
194
328
|
};
|
|
195
|
-
/** Re-renders when application code (or a Turbo morph) changes `value` at runtime. */
|
|
196
329
|
valueValueChanged() {
|
|
197
330
|
this.#repaint.schedule();
|
|
198
331
|
}
|
|
199
|
-
/** Re-renders when application code (or a Turbo morph) changes `min` at runtime. */
|
|
200
332
|
minValueChanged() {
|
|
201
333
|
this.#repaint.schedule();
|
|
202
334
|
}
|
|
203
|
-
/** Re-renders when application code (or a Turbo morph) changes `max` at runtime. */
|
|
204
335
|
maxValueChanged() {
|
|
205
336
|
this.#repaint.schedule();
|
|
206
337
|
}
|
|
207
|
-
/** Re-publishes range and position onto a separator swapped in after connect. */
|
|
208
338
|
separatorTargetConnected() {
|
|
209
339
|
this.#repaint.schedule();
|
|
210
340
|
}
|
|
211
|
-
|
|
341
|
+
separatorTargetDisconnected(separator) {
|
|
342
|
+
if (this.#drag?.separator === separator && !this.separatorTargets.includes(separator)) {
|
|
343
|
+
this.#endDrag();
|
|
344
|
+
}
|
|
345
|
+
this.#repaint.schedule();
|
|
346
|
+
}
|
|
212
347
|
onPointerDown(event) {
|
|
213
348
|
if (!this.hasSeparatorTarget || event.button !== 0) return;
|
|
214
349
|
event.preventDefault();
|
|
215
|
-
const
|
|
350
|
+
const separators = this.separatorTargets;
|
|
351
|
+
const separator = separators[ownerIndex(separators, event.target)] ?? this.separatorTarget;
|
|
216
352
|
separator.setPointerCapture(event.pointerId);
|
|
217
353
|
separator.focus();
|
|
218
354
|
this.element.setAttribute("data-dragging", "true");
|
|
219
|
-
this.#
|
|
355
|
+
this.#drag?.abort.abort();
|
|
220
356
|
const abort = new AbortController();
|
|
221
|
-
this.#
|
|
357
|
+
this.#drag = { separator, pointerId: event.pointerId, abort };
|
|
222
358
|
separator.addEventListener("pointermove", this.#onPointerMove, { signal: abort.signal });
|
|
223
359
|
separator.addEventListener("pointerup", this.#onPointerUp, { signal: abort.signal });
|
|
224
360
|
separator.addEventListener("pointercancel", this.#onPointerUp, { signal: abort.signal });
|
|
225
361
|
}
|
|
226
|
-
/** Keydown adjustments for ArrowUp/Down/Left/Right and Home/End. */
|
|
227
362
|
onKeydown(event) {
|
|
228
363
|
if (isReservedArrowChord(event)) return;
|
|
364
|
+
if ((event.key === "Home" || event.key === "End") && hasModifierChord(event)) return;
|
|
229
365
|
if (!this.hasSeparatorTarget) return;
|
|
230
366
|
const { min, max } = this.#range;
|
|
231
367
|
const isVertical = this.#isVertical;
|
|
@@ -279,7 +415,6 @@ var ResizableController = class extends Controller {
|
|
|
279
415
|
this.#commit(nextValue);
|
|
280
416
|
}
|
|
281
417
|
}
|
|
282
|
-
/** Collapses the primary pane to its minimum, or returns it to the last position. */
|
|
283
418
|
toggle() {
|
|
284
419
|
const { min, max } = this.#range;
|
|
285
420
|
if (this.#position > min) {
|
|
@@ -298,26 +433,25 @@ var ResizableController = class extends Controller {
|
|
|
298
433
|
const fraction = Number.isFinite(raw) ? Math.max(0, Math.min(raw, 1)) : 0;
|
|
299
434
|
this.#move(Math.round(fraction * 100));
|
|
300
435
|
};
|
|
301
|
-
#onPointerUp = (
|
|
302
|
-
this
|
|
303
|
-
this.#dragAbort?.abort();
|
|
304
|
-
this.#dragAbort = null;
|
|
305
|
-
if (this.hasSeparatorTarget) {
|
|
306
|
-
this.separatorTarget.releasePointerCapture(event.pointerId);
|
|
307
|
-
}
|
|
436
|
+
#onPointerUp = () => {
|
|
437
|
+
this.#endDrag();
|
|
308
438
|
this.#confirm();
|
|
309
439
|
};
|
|
310
|
-
|
|
440
|
+
#endDrag() {
|
|
441
|
+
this.element.removeAttribute("data-dragging");
|
|
442
|
+
const drag = this.#drag;
|
|
443
|
+
this.#drag = null;
|
|
444
|
+
if (!drag) return;
|
|
445
|
+
drag.abort.abort();
|
|
446
|
+
try {
|
|
447
|
+
drag.separator.releasePointerCapture?.(drag.pointerId);
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
450
|
+
}
|
|
311
451
|
#commit(raw) {
|
|
312
452
|
this.#move(raw);
|
|
313
453
|
this.#confirm();
|
|
314
454
|
}
|
|
315
|
-
/**
|
|
316
|
-
* Stores the clamped position the user moved to and paints it. The painted
|
|
317
|
-
* position moves with it before anything is reported, so the repaint the Value
|
|
318
|
-
* write schedules — and any move a subscriber makes while `change` is
|
|
319
|
-
* dispatched — is measured from this position.
|
|
320
|
-
*/
|
|
321
455
|
#move(raw) {
|
|
322
456
|
const { min, max } = this.#range;
|
|
323
457
|
const position = Math.max(min, Math.min(raw, max));
|
|
@@ -325,14 +459,6 @@ var ResizableController = class extends Controller {
|
|
|
325
459
|
this.#painted = position;
|
|
326
460
|
this.#paint();
|
|
327
461
|
}
|
|
328
|
-
/**
|
|
329
|
-
* Publishes the position the Values declare without writing it back. A
|
|
330
|
-
* position the user did not paint is the page's move, reported once as
|
|
331
|
-
* `reconcile` unless it lands on the position last confirmed — a drag the page
|
|
332
|
-
* puts back where it started has nothing new to report.
|
|
333
|
-
*
|
|
334
|
-
* @stimeoRenderRoot
|
|
335
|
-
*/
|
|
336
462
|
#render() {
|
|
337
463
|
this.#paint();
|
|
338
464
|
const position = this.#position;
|
|
@@ -342,11 +468,9 @@ var ResizableController = class extends Controller {
|
|
|
342
468
|
this.#confirmed = position;
|
|
343
469
|
this.dispatch("reconcile", { detail: this.#detail(position) });
|
|
344
470
|
}
|
|
345
|
-
/**
|
|
346
|
-
* Publishes the position: the fraction consumer CSS multiplies a pane by, and
|
|
347
|
-
* the range assistive tech reads off the separator.
|
|
348
|
-
*/
|
|
349
471
|
#paint() {
|
|
472
|
+
if (this.#drag) this.element.setAttribute("data-dragging", "true");
|
|
473
|
+
else this.element.removeAttribute("data-dragging");
|
|
350
474
|
const { min, max } = this.#range;
|
|
351
475
|
const position = this.#position;
|
|
352
476
|
this.element.style.setProperty("--stimeo--resizable-fraction", String(position / 100));
|
|
@@ -356,43 +480,68 @@ var ResizableController = class extends Controller {
|
|
|
356
480
|
this.separatorTarget.setAttribute("aria-valuemax", String(max));
|
|
357
481
|
}
|
|
358
482
|
}
|
|
359
|
-
/**
|
|
360
|
-
* Reports the published position as `change` when the user's operation moved
|
|
361
|
-
* it from the position last confirmed. A key at an edge, a press released
|
|
362
|
-
* without moving and a drag released where it started report nothing.
|
|
363
|
-
*/
|
|
364
483
|
#confirm() {
|
|
365
484
|
const position = this.#position;
|
|
366
485
|
if (position === this.#confirmed) return;
|
|
367
486
|
this.#confirmed = position;
|
|
368
487
|
this.dispatch("change", { detail: this.#detail(position) });
|
|
369
488
|
}
|
|
370
|
-
/** The detail both events carry for `position`. */
|
|
371
489
|
#detail(position) {
|
|
372
490
|
return { value: position, fraction: position / 100 };
|
|
373
491
|
}
|
|
374
|
-
/** The declared range after validation; an unreadable bound uses its default. */
|
|
375
492
|
get #range() {
|
|
376
|
-
const min =
|
|
377
|
-
const max =
|
|
493
|
+
const min = this.#safeMin;
|
|
494
|
+
const max = this.#safeMax;
|
|
378
495
|
return { min, max: Math.max(min, max) };
|
|
379
496
|
}
|
|
380
|
-
/** The declared position, clamped into the validated range. */
|
|
381
497
|
get #position() {
|
|
382
498
|
const { min, max } = this.#range;
|
|
383
|
-
const value =
|
|
499
|
+
const value = this.#safeValue;
|
|
384
500
|
return Math.max(min, Math.min(value, max));
|
|
385
501
|
}
|
|
386
|
-
/** The keyboard increment; a non-positive or unreadable one uses the default. */
|
|
387
502
|
get #step() {
|
|
388
|
-
return
|
|
503
|
+
return this.#safeStep;
|
|
389
504
|
}
|
|
390
|
-
/** Whether the divider runs vertically; a separator that does not say is horizontal. */
|
|
391
505
|
get #isVertical() {
|
|
392
506
|
return this.hasSeparatorTarget && this.separatorTarget.getAttribute("aria-orientation") === "vertical";
|
|
393
507
|
}
|
|
508
|
+
get #safeMin() {
|
|
509
|
+
return this.#numbers.read(
|
|
510
|
+
this,
|
|
511
|
+
"min",
|
|
512
|
+
this.minValue,
|
|
513
|
+
_ResizableController.values.min.default,
|
|
514
|
+
_ResizableController.valueConstraints.min
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
get #safeMax() {
|
|
518
|
+
return this.#numbers.read(
|
|
519
|
+
this,
|
|
520
|
+
"max",
|
|
521
|
+
this.maxValue,
|
|
522
|
+
_ResizableController.values.max.default,
|
|
523
|
+
_ResizableController.valueConstraints.max
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
get #safeStep() {
|
|
527
|
+
return this.#numbers.read(
|
|
528
|
+
this,
|
|
529
|
+
"step",
|
|
530
|
+
this.stepValue,
|
|
531
|
+
_ResizableController.values.step.default,
|
|
532
|
+
_ResizableController.valueConstraints.step
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
get #safeValue() {
|
|
536
|
+
return this.#numbers.read(
|
|
537
|
+
this,
|
|
538
|
+
"value",
|
|
539
|
+
this.valueValue,
|
|
540
|
+
_ResizableController.values.value.default,
|
|
541
|
+
_ResizableController.valueConstraints.value
|
|
542
|
+
);
|
|
543
|
+
}
|
|
394
544
|
};
|
|
395
545
|
|
|
396
546
|
export { ResizableController };
|
|
397
|
-
//# sourceMappingURL=resizable_controller.js.map
|
|
398
547
|
//# sourceMappingURL=resizable_controller.js.map
|