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,40 +1,42 @@
|
|
|
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
|
}
|
|
10
7
|
|
|
11
|
-
// src/utils/event_owner.ts
|
|
12
8
|
function ownerIndex(candidates, node) {
|
|
13
9
|
if (!(node instanceof Node)) return -1;
|
|
14
10
|
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
15
11
|
}
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
var ListenerSet = class {
|
|
14
|
+
#abort = new AbortController();
|
|
15
|
+
add(target, type, handler, options) {
|
|
16
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
17
|
+
}
|
|
18
|
+
dispose() {
|
|
19
|
+
this.#abort.abort();
|
|
20
|
+
this.#abort = new AbortController();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
18
24
|
var MicrotaskCoalescer = class {
|
|
19
25
|
#run;
|
|
20
26
|
#queued = false;
|
|
21
27
|
#active = false;
|
|
22
28
|
#generation = 0;
|
|
23
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
24
29
|
constructor(run) {
|
|
25
30
|
this.#run = run;
|
|
26
31
|
}
|
|
27
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
28
32
|
activate() {
|
|
29
33
|
this.#active = true;
|
|
30
34
|
}
|
|
31
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
32
35
|
cancel() {
|
|
33
36
|
this.#active = false;
|
|
34
37
|
this.#queued = false;
|
|
35
38
|
this.#generation += 1;
|
|
36
39
|
}
|
|
37
|
-
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
38
40
|
schedule() {
|
|
39
41
|
if (!this.#active || this.#queued) return;
|
|
40
42
|
this.#queued = true;
|
|
@@ -47,85 +49,197 @@ var MicrotaskCoalescer = class {
|
|
|
47
49
|
}
|
|
48
50
|
};
|
|
49
51
|
|
|
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;
|
|
52
|
+
var MorphRenderWatcher = class {
|
|
53
|
+
#listeners = new ListenerSet();
|
|
54
|
+
#pass;
|
|
55
|
+
#element = null;
|
|
56
|
+
#onMorph = () => this.#pass.schedule();
|
|
57
|
+
constructor(run) {
|
|
58
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
62
59
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
observe(element) {
|
|
61
|
+
if (this.#element === element) return;
|
|
62
|
+
this.disconnect();
|
|
63
|
+
this.#element = element;
|
|
64
|
+
this.#pass.activate();
|
|
65
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
66
|
+
}
|
|
67
|
+
schedule() {
|
|
68
|
+
this.#pass.schedule();
|
|
69
|
+
}
|
|
70
|
+
disconnect() {
|
|
71
|
+
this.#listeners.dispose();
|
|
72
|
+
this.#pass.cancel();
|
|
73
|
+
this.#element = null;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
var NUMBER_BOUNDS = {
|
|
78
|
+
finite: { finite: true },
|
|
79
|
+
positive: { finite: true, exclusiveMin: 0 }};
|
|
80
|
+
function matchesNumberBounds(value, bounds) {
|
|
81
|
+
if (!Number.isFinite(value)) {
|
|
82
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
83
|
+
if (direction === null) return false;
|
|
84
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
70
85
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
87
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
88
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
89
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
90
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readNumber(raw, fallback, bounds) {
|
|
95
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
var NumberValueReader = class {
|
|
99
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
100
|
+
read(owner, name, raw, fallback, bounds) {
|
|
101
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
102
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
103
|
+
this.#lastRejected.delete(name);
|
|
104
|
+
return resolved;
|
|
105
|
+
}
|
|
106
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
107
|
+
const literal = owner.element.getAttribute(attribute);
|
|
108
|
+
if (literal === null) {
|
|
109
|
+
this.#lastRejected.delete(name);
|
|
110
|
+
return resolved;
|
|
111
|
+
}
|
|
112
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
113
|
+
this.#lastRejected.set(name, literal);
|
|
114
|
+
console.warn(
|
|
115
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return resolved;
|
|
76
119
|
}
|
|
77
120
|
};
|
|
78
121
|
|
|
79
|
-
|
|
80
|
-
|
|
122
|
+
function queryOpenDescendants(root, selector) {
|
|
123
|
+
const matches = [];
|
|
124
|
+
const roots = [root];
|
|
125
|
+
for (const scope of roots) {
|
|
126
|
+
matches.push(...scope.querySelectorAll(selector));
|
|
127
|
+
if (scope.nodeType === Node.ELEMENT_NODE) {
|
|
128
|
+
const shadow = scope.shadowRoot;
|
|
129
|
+
if (shadow) roots.push(shadow);
|
|
130
|
+
}
|
|
131
|
+
for (const element of scope.querySelectorAll("*")) {
|
|
132
|
+
if (element.shadowRoot) roots.push(element.shadowRoot);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return matches;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function sharedRegistry(key, create) {
|
|
139
|
+
const symbol = Symbol.for(key);
|
|
140
|
+
const scope = globalThis;
|
|
141
|
+
const existing = scope[symbol];
|
|
142
|
+
if (existing !== void 0) return existing;
|
|
143
|
+
const registry = create();
|
|
144
|
+
Object.defineProperty(globalThis, symbol, { value: registry });
|
|
145
|
+
return registry;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
var TabindexLoan = class _TabindexLoan {
|
|
149
|
+
static #registry = sharedRegistry(
|
|
150
|
+
"stimeo-ui.tabindex-loan.registry.v1",
|
|
151
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
|
|
152
|
+
);
|
|
81
153
|
#value;
|
|
154
|
+
#record;
|
|
82
155
|
#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") {
|
|
156
|
+
constructor(value, owner) {
|
|
91
157
|
this.#value = value;
|
|
158
|
+
this.#record = `data-${owner}-tabindex-loan`;
|
|
159
|
+
_TabindexLoan.#registry.names.add(this.#record);
|
|
92
160
|
}
|
|
93
|
-
/** Lends `element` the value; no-ops when it already carries a `tabindex`. */
|
|
94
161
|
lend(element) {
|
|
95
|
-
|
|
96
|
-
element.
|
|
162
|
+
const stack = this.#stack(element);
|
|
163
|
+
const current = element.getAttribute("tabindex");
|
|
164
|
+
const held = stack.find((hold) => hold.loan === this) ?? stack.find(
|
|
165
|
+
(hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
|
|
166
|
+
);
|
|
167
|
+
const linked = stack.at(-1)?.value === current;
|
|
168
|
+
if (!held && current !== null && !linked) return;
|
|
97
169
|
this.#lent.add(element);
|
|
98
|
-
|
|
170
|
+
if (held) held.loan = this;
|
|
171
|
+
else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
|
|
172
|
+
this.#mark(element, stack);
|
|
173
|
+
if (current === null || !held && current !== this.#value) {
|
|
174
|
+
element.setAttribute("tabindex", this.#value);
|
|
175
|
+
}
|
|
99
176
|
}
|
|
100
|
-
/** Takes back every loan whose value is still the one that was lent. */
|
|
101
177
|
returnAll() {
|
|
102
178
|
for (const element of this.#lent) {
|
|
103
|
-
|
|
179
|
+
const stack = this.#stack(element);
|
|
180
|
+
const hold = stack.find((held) => held.loan === this);
|
|
181
|
+
if (hold) this.#release(element, stack, hold);
|
|
104
182
|
}
|
|
105
183
|
this.#lent.clear();
|
|
106
|
-
this.#beforeCache.deactivate();
|
|
107
184
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
185
|
+
reclaim(element) {
|
|
186
|
+
if (element.getAttribute(this.#record) !== this.#value) return;
|
|
187
|
+
const stack = this.#stack(element);
|
|
188
|
+
const hold = stack.find((held) => held.loan === null && held.record === this.#record);
|
|
189
|
+
if (hold && !element.matches(":focus")) this.#release(element, stack, hold);
|
|
190
|
+
}
|
|
191
|
+
reclaimWithin(root) {
|
|
192
|
+
for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
|
|
193
|
+
this.reclaim(element);
|
|
194
|
+
}
|
|
115
195
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
196
|
+
#stack(element) {
|
|
197
|
+
const stacks = _TabindexLoan.#registry.stacks;
|
|
198
|
+
const stack = stacks.get(element) ?? this.#seed(element);
|
|
199
|
+
stacks.set(element, stack);
|
|
200
|
+
return stack;
|
|
201
|
+
}
|
|
202
|
+
#seed(element) {
|
|
203
|
+
const current = element.getAttribute("tabindex");
|
|
204
|
+
const holds = [];
|
|
205
|
+
for (const record of _TabindexLoan.#registry.names) {
|
|
206
|
+
const value = element.getAttribute(record);
|
|
207
|
+
if (value !== null) holds.push({ loan: null, record, value, linked: true });
|
|
208
|
+
}
|
|
209
|
+
holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
|
|
210
|
+
for (const [index, hold] of holds.entries()) hold.linked = index > 0;
|
|
211
|
+
return holds;
|
|
212
|
+
}
|
|
213
|
+
#release(element, stack, hold) {
|
|
214
|
+
const current = element.getAttribute("tabindex");
|
|
215
|
+
const index = stack.indexOf(hold);
|
|
216
|
+
const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
|
|
217
|
+
stack.splice(index, 1);
|
|
218
|
+
const above = stack[index];
|
|
219
|
+
if (above) above.linked &&= hold.linked;
|
|
220
|
+
this.#mark(element, stack);
|
|
221
|
+
if (!owns) return;
|
|
222
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
223
|
+
if (!below) element.removeAttribute("tabindex");
|
|
224
|
+
else if (below.value !== current) element.setAttribute("tabindex", below.value);
|
|
225
|
+
}
|
|
226
|
+
#mark(element, stack) {
|
|
227
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
228
|
+
for (const hold of stack) records.set(hold.record, hold.value);
|
|
229
|
+
for (const [name, recorded] of records) {
|
|
230
|
+
if (element.getAttribute(name) === recorded) continue;
|
|
231
|
+
if (recorded === null) element.removeAttribute(name);
|
|
232
|
+
else element.setAttribute(name, recorded);
|
|
233
|
+
}
|
|
119
234
|
}
|
|
120
235
|
};
|
|
121
236
|
|
|
122
|
-
// src/controllers/resizable_controller.ts
|
|
123
|
-
var TRANSIENT = new TransientHooks({ attributes: ["data-dragging"] });
|
|
124
237
|
var DEFAULT_MIN = 0;
|
|
125
238
|
var DEFAULT_MAX = 100;
|
|
126
239
|
var DEFAULT_VALUE = 50;
|
|
127
240
|
var DEFAULT_STEP = 1;
|
|
128
|
-
var ResizableController = class extends Controller {
|
|
241
|
+
var ResizableController = class _ResizableController extends Controller {
|
|
242
|
+
#numbers = new NumberValueReader();
|
|
129
243
|
static targets = ["primary", "secondary", "separator"];
|
|
130
244
|
static values = {
|
|
131
245
|
min: { type: Number, default: DEFAULT_MIN },
|
|
@@ -133,32 +247,24 @@ var ResizableController = class extends Controller {
|
|
|
133
247
|
step: { type: Number, default: DEFAULT_STEP },
|
|
134
248
|
value: { type: Number, default: DEFAULT_VALUE }
|
|
135
249
|
};
|
|
250
|
+
static valueConstraints = {
|
|
251
|
+
min: NUMBER_BOUNDS.finite,
|
|
252
|
+
max: NUMBER_BOUNDS.finite,
|
|
253
|
+
step: NUMBER_BOUNDS.positive,
|
|
254
|
+
value: NUMBER_BOUNDS.finite
|
|
255
|
+
};
|
|
136
256
|
static actions = ["onKeydown", "onPointerDown", "toggle"];
|
|
137
257
|
static events = ["change", "reconcile"];
|
|
138
|
-
/** Where the divider sat before the current collapse; `null` when unknown. */
|
|
139
258
|
#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
259
|
#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
260
|
#confirmed = DEFAULT_VALUE;
|
|
151
|
-
|
|
152
|
-
#
|
|
153
|
-
/** `tabindex` lent to a pane so `F6` can put focus on it; panes carry none. */
|
|
154
|
-
#paneTabindex = new TabindexLoan();
|
|
155
|
-
/** Releases the pane cycle listener bound in {@link connect}. */
|
|
261
|
+
#drag = null;
|
|
262
|
+
#paneTabindex = new TabindexLoan("-1", this.identifier);
|
|
156
263
|
#cycleAbort = null;
|
|
157
|
-
|
|
158
|
-
#repaint = new MicrotaskCoalescer(() => this.#render());
|
|
264
|
+
#repaint = new MorphRenderWatcher(() => this.#render());
|
|
159
265
|
connect() {
|
|
160
|
-
|
|
161
|
-
this.#repaint.
|
|
266
|
+
this.#paneTabindex.reclaimWithin(this.element);
|
|
267
|
+
this.#repaint.observe(this.element);
|
|
162
268
|
this.#confirmed = this.#position;
|
|
163
269
|
this.#render();
|
|
164
270
|
this.#cycleAbort = new AbortController();
|
|
@@ -166,17 +272,13 @@ var ResizableController = class extends Controller {
|
|
|
166
272
|
signal: this.#cycleAbort.signal
|
|
167
273
|
});
|
|
168
274
|
}
|
|
169
|
-
/** Cancels any active pointer drag so listeners never leak past disconnect. */
|
|
170
275
|
disconnect() {
|
|
171
|
-
this.#
|
|
172
|
-
this.#dragAbort = null;
|
|
276
|
+
this.#endDrag();
|
|
173
277
|
this.#cycleAbort?.abort();
|
|
174
278
|
this.#cycleAbort = null;
|
|
175
|
-
this.#repaint.
|
|
279
|
+
this.#repaint.disconnect();
|
|
176
280
|
this.#paneTabindex.returnAll();
|
|
177
|
-
this.element.removeAttribute("data-dragging");
|
|
178
281
|
}
|
|
179
|
-
/** Moves focus to the next pane on `F6`, wrapping; entering at the first one. */
|
|
180
282
|
#onCycleKeydown = (event) => {
|
|
181
283
|
if (event.key !== "F6") return;
|
|
182
284
|
if (event.isComposing) return;
|
|
@@ -192,38 +294,39 @@ var ResizableController = class extends Controller {
|
|
|
192
294
|
this.#paneTabindex.lend(next);
|
|
193
295
|
next.focus();
|
|
194
296
|
};
|
|
195
|
-
/** Re-renders when application code (or a Turbo morph) changes `value` at runtime. */
|
|
196
297
|
valueValueChanged() {
|
|
197
298
|
this.#repaint.schedule();
|
|
198
299
|
}
|
|
199
|
-
/** Re-renders when application code (or a Turbo morph) changes `min` at runtime. */
|
|
200
300
|
minValueChanged() {
|
|
201
301
|
this.#repaint.schedule();
|
|
202
302
|
}
|
|
203
|
-
/** Re-renders when application code (or a Turbo morph) changes `max` at runtime. */
|
|
204
303
|
maxValueChanged() {
|
|
205
304
|
this.#repaint.schedule();
|
|
206
305
|
}
|
|
207
|
-
/** Re-publishes range and position onto a separator swapped in after connect. */
|
|
208
306
|
separatorTargetConnected() {
|
|
209
307
|
this.#repaint.schedule();
|
|
210
308
|
}
|
|
211
|
-
|
|
309
|
+
separatorTargetDisconnected(separator) {
|
|
310
|
+
if (this.#drag?.separator === separator && !this.separatorTargets.includes(separator)) {
|
|
311
|
+
this.#endDrag();
|
|
312
|
+
}
|
|
313
|
+
this.#repaint.schedule();
|
|
314
|
+
}
|
|
212
315
|
onPointerDown(event) {
|
|
213
316
|
if (!this.hasSeparatorTarget || event.button !== 0) return;
|
|
214
317
|
event.preventDefault();
|
|
215
|
-
const
|
|
318
|
+
const separators = this.separatorTargets;
|
|
319
|
+
const separator = separators[ownerIndex(separators, event.target)] ?? this.separatorTarget;
|
|
216
320
|
separator.setPointerCapture(event.pointerId);
|
|
217
321
|
separator.focus();
|
|
218
322
|
this.element.setAttribute("data-dragging", "true");
|
|
219
|
-
this.#
|
|
323
|
+
this.#drag?.abort.abort();
|
|
220
324
|
const abort = new AbortController();
|
|
221
|
-
this.#
|
|
325
|
+
this.#drag = { separator, pointerId: event.pointerId, abort };
|
|
222
326
|
separator.addEventListener("pointermove", this.#onPointerMove, { signal: abort.signal });
|
|
223
327
|
separator.addEventListener("pointerup", this.#onPointerUp, { signal: abort.signal });
|
|
224
328
|
separator.addEventListener("pointercancel", this.#onPointerUp, { signal: abort.signal });
|
|
225
329
|
}
|
|
226
|
-
/** Keydown adjustments for ArrowUp/Down/Left/Right and Home/End. */
|
|
227
330
|
onKeydown(event) {
|
|
228
331
|
if (isReservedArrowChord(event)) return;
|
|
229
332
|
if (!this.hasSeparatorTarget) return;
|
|
@@ -279,7 +382,6 @@ var ResizableController = class extends Controller {
|
|
|
279
382
|
this.#commit(nextValue);
|
|
280
383
|
}
|
|
281
384
|
}
|
|
282
|
-
/** Collapses the primary pane to its minimum, or returns it to the last position. */
|
|
283
385
|
toggle() {
|
|
284
386
|
const { min, max } = this.#range;
|
|
285
387
|
if (this.#position > min) {
|
|
@@ -298,26 +400,25 @@ var ResizableController = class extends Controller {
|
|
|
298
400
|
const fraction = Number.isFinite(raw) ? Math.max(0, Math.min(raw, 1)) : 0;
|
|
299
401
|
this.#move(Math.round(fraction * 100));
|
|
300
402
|
};
|
|
301
|
-
#onPointerUp = (
|
|
302
|
-
this
|
|
303
|
-
this.#dragAbort?.abort();
|
|
304
|
-
this.#dragAbort = null;
|
|
305
|
-
if (this.hasSeparatorTarget) {
|
|
306
|
-
this.separatorTarget.releasePointerCapture(event.pointerId);
|
|
307
|
-
}
|
|
403
|
+
#onPointerUp = () => {
|
|
404
|
+
this.#endDrag();
|
|
308
405
|
this.#confirm();
|
|
309
406
|
};
|
|
310
|
-
|
|
407
|
+
#endDrag() {
|
|
408
|
+
this.element.removeAttribute("data-dragging");
|
|
409
|
+
const drag = this.#drag;
|
|
410
|
+
this.#drag = null;
|
|
411
|
+
if (!drag) return;
|
|
412
|
+
drag.abort.abort();
|
|
413
|
+
try {
|
|
414
|
+
drag.separator.releasePointerCapture?.(drag.pointerId);
|
|
415
|
+
} catch {
|
|
416
|
+
}
|
|
417
|
+
}
|
|
311
418
|
#commit(raw) {
|
|
312
419
|
this.#move(raw);
|
|
313
420
|
this.#confirm();
|
|
314
421
|
}
|
|
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
422
|
#move(raw) {
|
|
322
423
|
const { min, max } = this.#range;
|
|
323
424
|
const position = Math.max(min, Math.min(raw, max));
|
|
@@ -325,14 +426,6 @@ var ResizableController = class extends Controller {
|
|
|
325
426
|
this.#painted = position;
|
|
326
427
|
this.#paint();
|
|
327
428
|
}
|
|
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
429
|
#render() {
|
|
337
430
|
this.#paint();
|
|
338
431
|
const position = this.#position;
|
|
@@ -342,11 +435,9 @@ var ResizableController = class extends Controller {
|
|
|
342
435
|
this.#confirmed = position;
|
|
343
436
|
this.dispatch("reconcile", { detail: this.#detail(position) });
|
|
344
437
|
}
|
|
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
438
|
#paint() {
|
|
439
|
+
if (this.#drag) this.element.setAttribute("data-dragging", "true");
|
|
440
|
+
else this.element.removeAttribute("data-dragging");
|
|
350
441
|
const { min, max } = this.#range;
|
|
351
442
|
const position = this.#position;
|
|
352
443
|
this.element.style.setProperty("--stimeo--resizable-fraction", String(position / 100));
|
|
@@ -356,43 +447,68 @@ var ResizableController = class extends Controller {
|
|
|
356
447
|
this.separatorTarget.setAttribute("aria-valuemax", String(max));
|
|
357
448
|
}
|
|
358
449
|
}
|
|
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
450
|
#confirm() {
|
|
365
451
|
const position = this.#position;
|
|
366
452
|
if (position === this.#confirmed) return;
|
|
367
453
|
this.#confirmed = position;
|
|
368
454
|
this.dispatch("change", { detail: this.#detail(position) });
|
|
369
455
|
}
|
|
370
|
-
/** The detail both events carry for `position`. */
|
|
371
456
|
#detail(position) {
|
|
372
457
|
return { value: position, fraction: position / 100 };
|
|
373
458
|
}
|
|
374
|
-
/** The declared range after validation; an unreadable bound uses its default. */
|
|
375
459
|
get #range() {
|
|
376
|
-
const min =
|
|
377
|
-
const max =
|
|
460
|
+
const min = this.#safeMin;
|
|
461
|
+
const max = this.#safeMax;
|
|
378
462
|
return { min, max: Math.max(min, max) };
|
|
379
463
|
}
|
|
380
|
-
/** The declared position, clamped into the validated range. */
|
|
381
464
|
get #position() {
|
|
382
465
|
const { min, max } = this.#range;
|
|
383
|
-
const value =
|
|
466
|
+
const value = this.#safeValue;
|
|
384
467
|
return Math.max(min, Math.min(value, max));
|
|
385
468
|
}
|
|
386
|
-
/** The keyboard increment; a non-positive or unreadable one uses the default. */
|
|
387
469
|
get #step() {
|
|
388
|
-
return
|
|
470
|
+
return this.#safeStep;
|
|
389
471
|
}
|
|
390
|
-
/** Whether the divider runs vertically; a separator that does not say is horizontal. */
|
|
391
472
|
get #isVertical() {
|
|
392
473
|
return this.hasSeparatorTarget && this.separatorTarget.getAttribute("aria-orientation") === "vertical";
|
|
393
474
|
}
|
|
475
|
+
get #safeMin() {
|
|
476
|
+
return this.#numbers.read(
|
|
477
|
+
this,
|
|
478
|
+
"min",
|
|
479
|
+
this.minValue,
|
|
480
|
+
_ResizableController.values.min.default,
|
|
481
|
+
_ResizableController.valueConstraints.min
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
get #safeMax() {
|
|
485
|
+
return this.#numbers.read(
|
|
486
|
+
this,
|
|
487
|
+
"max",
|
|
488
|
+
this.maxValue,
|
|
489
|
+
_ResizableController.values.max.default,
|
|
490
|
+
_ResizableController.valueConstraints.max
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
get #safeStep() {
|
|
494
|
+
return this.#numbers.read(
|
|
495
|
+
this,
|
|
496
|
+
"step",
|
|
497
|
+
this.stepValue,
|
|
498
|
+
_ResizableController.values.step.default,
|
|
499
|
+
_ResizableController.valueConstraints.step
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
get #safeValue() {
|
|
503
|
+
return this.#numbers.read(
|
|
504
|
+
this,
|
|
505
|
+
"value",
|
|
506
|
+
this.valueValue,
|
|
507
|
+
_ResizableController.values.value.default,
|
|
508
|
+
_ResizableController.valueConstraints.value
|
|
509
|
+
);
|
|
510
|
+
}
|
|
394
511
|
};
|
|
395
512
|
|
|
396
513
|
export { ResizableController };
|
|
397
|
-
//# sourceMappingURL=resizable_controller.js.map
|
|
398
514
|
//# sourceMappingURL=resizable_controller.js.map
|