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,104 +1,184 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
// src/controllers/separator_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
|
-
|
|
12
|
-
|
|
8
|
+
function sharedRegistry(key, create) {
|
|
9
|
+
const symbol = Symbol.for(key);
|
|
10
|
+
const scope = globalThis;
|
|
11
|
+
const existing = scope[symbol];
|
|
12
|
+
if (existing !== void 0) return existing;
|
|
13
|
+
const registry = create();
|
|
14
|
+
Object.defineProperty(globalThis, symbol, { value: registry });
|
|
15
|
+
return registry;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isAttributeValue(value) {
|
|
19
|
+
return value === null || typeof value === "string";
|
|
20
|
+
}
|
|
21
|
+
function readLeaseRecord(element, record) {
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
24
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
25
|
+
const [original, written, ...rest] = parsed;
|
|
26
|
+
const linked = rest[0] !== false;
|
|
27
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
28
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
29
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
30
|
+
} catch {
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
var AttributeLease = class _AttributeLease {
|
|
35
|
+
static #registry = sharedRegistry(
|
|
36
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
37
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
38
|
+
);
|
|
13
39
|
#attribute;
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
40
|
+
#record;
|
|
41
|
+
#base;
|
|
42
|
+
#held = /* @__PURE__ */ new Set();
|
|
43
|
+
constructor(attribute, owner, options = {}) {
|
|
17
44
|
this.#attribute = attribute;
|
|
45
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
46
|
+
this.#base = options.base === true;
|
|
47
|
+
const names = _AttributeLease.#registry.names;
|
|
48
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
18
49
|
}
|
|
19
|
-
/** Writes or removes the leased attribute while preserving its authored value. */
|
|
20
50
|
write(element, value) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
this.#reflect(element, value);
|
|
51
|
+
const stack = this.#stack(element);
|
|
52
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
53
|
+
this.#held.add(element);
|
|
54
|
+
const shows = !this.#covered(element, stack, hold);
|
|
55
|
+
hold.written = value;
|
|
56
|
+
this.#mark(element, stack);
|
|
57
|
+
if (shows) this.#reflect(element, value);
|
|
31
58
|
}
|
|
32
|
-
/** Returns one lease without overwriting a value subsequently authored by a consumer. */
|
|
33
59
|
return(element) {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
this.#
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
60
|
+
const held = this.#held.delete(element);
|
|
61
|
+
const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
|
|
62
|
+
const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
|
|
63
|
+
const hold = this.#take(stack);
|
|
64
|
+
if (!hold) return false;
|
|
65
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
66
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
67
|
+
const index = stack.indexOf(hold);
|
|
68
|
+
stack.splice(index, 1);
|
|
69
|
+
const above = stack[index];
|
|
70
|
+
if (above) above.linked &&= hold.linked;
|
|
71
|
+
this.#mark(element, stack);
|
|
72
|
+
if (!owns) return true;
|
|
73
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
74
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
returnAll() {
|
|
78
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
79
|
+
}
|
|
80
|
+
#stack(element) {
|
|
81
|
+
const stacks = _AttributeLease.#registry.stacks;
|
|
82
|
+
const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
83
|
+
stacks.set(element, byAttribute);
|
|
84
|
+
const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
|
|
85
|
+
byAttribute.set(this.#attribute, stack);
|
|
86
|
+
return stack;
|
|
87
|
+
}
|
|
88
|
+
#seed(element) {
|
|
89
|
+
const found = [];
|
|
90
|
+
for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
|
|
91
|
+
const read = readLeaseRecord(element, record);
|
|
92
|
+
if (read) found.push({ ...read, record });
|
|
93
|
+
}
|
|
94
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
95
|
+
return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
96
|
+
lease: null,
|
|
97
|
+
record,
|
|
98
|
+
written,
|
|
99
|
+
original,
|
|
100
|
+
linked: linked && index > 0
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
#take(stack) {
|
|
104
|
+
const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
|
|
105
|
+
if (hold) hold.lease = this;
|
|
106
|
+
return hold;
|
|
107
|
+
}
|
|
108
|
+
#acquire(element, stack) {
|
|
109
|
+
const current = element.getAttribute(this.#attribute);
|
|
110
|
+
const top = stack.at(-1);
|
|
111
|
+
const bottom = stack[0];
|
|
112
|
+
const over = top?.written === current ? top : void 0;
|
|
113
|
+
const from = this.#base ? bottom : over;
|
|
114
|
+
const hold = {
|
|
115
|
+
lease: this,
|
|
116
|
+
record: this.#record,
|
|
117
|
+
written: current,
|
|
118
|
+
original: from ? from.original : current,
|
|
119
|
+
linked: !this.#base && over !== void 0
|
|
120
|
+
};
|
|
121
|
+
if (this.#base && bottom) {
|
|
122
|
+
bottom.linked = true;
|
|
123
|
+
stack.unshift(hold);
|
|
124
|
+
} else stack.push(hold);
|
|
125
|
+
return hold;
|
|
126
|
+
}
|
|
127
|
+
#covered(element, stack, hold) {
|
|
128
|
+
const current = element.getAttribute(this.#attribute);
|
|
129
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
130
|
+
}
|
|
131
|
+
#mark(element, stack) {
|
|
132
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
133
|
+
stack.forEach((hold, index) => {
|
|
134
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
135
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
136
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
137
|
+
records.set(
|
|
138
|
+
hold.record,
|
|
139
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
for (const [name, recorded] of records) {
|
|
143
|
+
if (element.getAttribute(name) === recorded) continue;
|
|
144
|
+
if (recorded === null) element.removeAttribute(name);
|
|
145
|
+
else element.setAttribute(name, recorded);
|
|
146
|
+
}
|
|
39
147
|
}
|
|
40
|
-
/** Reflects only a real value transition, avoiding self-triggered mutation work. */
|
|
41
148
|
#reflect(element, value) {
|
|
42
149
|
if (element.getAttribute(this.#attribute) === value) return;
|
|
43
150
|
if (value === null) element.removeAttribute(this.#attribute);
|
|
44
151
|
else element.setAttribute(this.#attribute, value);
|
|
45
152
|
}
|
|
46
|
-
/** Returns every outstanding lease using the same ownership check as {@link return}. */
|
|
47
|
-
returnAll() {
|
|
48
|
-
for (const element of Array.from(this.#records.keys())) this.return(element);
|
|
49
|
-
}
|
|
50
153
|
};
|
|
51
154
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
/** The shared listener; installed while at least one instance is subscribed. */
|
|
57
|
-
static #onBeforeCache = () => {
|
|
58
|
-
for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
|
|
59
|
-
};
|
|
60
|
-
#rewind;
|
|
61
|
-
/** @param rewind - the pass that returns this controller's state to its initial form. */
|
|
62
|
-
constructor(rewind) {
|
|
63
|
-
this.#rewind = rewind;
|
|
155
|
+
var ListenerSet = class {
|
|
156
|
+
#abort = new AbortController();
|
|
157
|
+
add(target, type, handler, options) {
|
|
158
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
64
159
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
_BeforeCacheReset.#subscribers.add(this);
|
|
69
|
-
if (first) {
|
|
70
|
-
document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
/** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
|
|
74
|
-
deactivate() {
|
|
75
|
-
_BeforeCacheReset.#subscribers.delete(this);
|
|
76
|
-
if (_BeforeCacheReset.#subscribers.size > 0) return;
|
|
77
|
-
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
160
|
+
dispose() {
|
|
161
|
+
this.#abort.abort();
|
|
162
|
+
this.#abort = new AbortController();
|
|
78
163
|
}
|
|
79
164
|
};
|
|
80
165
|
|
|
81
|
-
// src/utils/microtask_coalescer.ts
|
|
82
166
|
var MicrotaskCoalescer = class {
|
|
83
167
|
#run;
|
|
84
168
|
#queued = false;
|
|
85
169
|
#active = false;
|
|
86
170
|
#generation = 0;
|
|
87
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
88
171
|
constructor(run) {
|
|
89
172
|
this.#run = run;
|
|
90
173
|
}
|
|
91
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
92
174
|
activate() {
|
|
93
175
|
this.#active = true;
|
|
94
176
|
}
|
|
95
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
96
177
|
cancel() {
|
|
97
178
|
this.#active = false;
|
|
98
179
|
this.#queued = false;
|
|
99
180
|
this.#generation += 1;
|
|
100
181
|
}
|
|
101
|
-
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
102
182
|
schedule() {
|
|
103
183
|
if (!this.#active || this.#queued) return;
|
|
104
184
|
this.#queued = true;
|
|
@@ -111,7 +191,76 @@ var MicrotaskCoalescer = class {
|
|
|
111
191
|
}
|
|
112
192
|
};
|
|
113
193
|
|
|
114
|
-
|
|
194
|
+
var MorphRenderWatcher = class {
|
|
195
|
+
#listeners = new ListenerSet();
|
|
196
|
+
#pass;
|
|
197
|
+
#element = null;
|
|
198
|
+
#onMorph = () => this.#pass.schedule();
|
|
199
|
+
constructor(run) {
|
|
200
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
201
|
+
}
|
|
202
|
+
observe(element) {
|
|
203
|
+
if (this.#element === element) return;
|
|
204
|
+
this.disconnect();
|
|
205
|
+
this.#element = element;
|
|
206
|
+
this.#pass.activate();
|
|
207
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
208
|
+
}
|
|
209
|
+
schedule() {
|
|
210
|
+
this.#pass.schedule();
|
|
211
|
+
}
|
|
212
|
+
disconnect() {
|
|
213
|
+
this.#listeners.dispose();
|
|
214
|
+
this.#pass.cancel();
|
|
215
|
+
this.#element = null;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
var NUMBER_BOUNDS = {
|
|
220
|
+
finite: { finite: true },
|
|
221
|
+
positive: { finite: true, exclusiveMin: 0 }};
|
|
222
|
+
function matchesNumberBounds(value, bounds) {
|
|
223
|
+
if (!Number.isFinite(value)) {
|
|
224
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
225
|
+
if (direction === null) return false;
|
|
226
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
227
|
+
}
|
|
228
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
229
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
230
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
231
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
232
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function readNumber(raw, fallback, bounds) {
|
|
237
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
var NumberValueReader = class {
|
|
241
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
242
|
+
read(owner, name, raw, fallback, bounds) {
|
|
243
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
244
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
245
|
+
this.#lastRejected.delete(name);
|
|
246
|
+
return resolved;
|
|
247
|
+
}
|
|
248
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
249
|
+
const literal = owner.element.getAttribute(attribute);
|
|
250
|
+
if (literal === null) {
|
|
251
|
+
this.#lastRejected.delete(name);
|
|
252
|
+
return resolved;
|
|
253
|
+
}
|
|
254
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
255
|
+
this.#lastRejected.set(name, literal);
|
|
256
|
+
console.warn(
|
|
257
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
return resolved;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
115
264
|
function effectiveStep(step) {
|
|
116
265
|
return Number.isFinite(step) && step > 0 ? step : 1;
|
|
117
266
|
}
|
|
@@ -217,10 +366,10 @@ function finiteFallback(min, max) {
|
|
|
217
366
|
return 0;
|
|
218
367
|
}
|
|
219
368
|
|
|
220
|
-
// src/controllers/separator_controller.ts
|
|
221
369
|
var DEFAULT_MIN = 0;
|
|
222
370
|
var DEFAULT_MAX = 100;
|
|
223
|
-
var SeparatorController = class extends Controller {
|
|
371
|
+
var SeparatorController = class _SeparatorController extends Controller {
|
|
372
|
+
#numbers = new NumberValueReader();
|
|
224
373
|
static values = {
|
|
225
374
|
orientation: { type: String, default: "horizontal" },
|
|
226
375
|
focusable: { type: Boolean, default: false },
|
|
@@ -229,76 +378,55 @@ var SeparatorController = class extends Controller {
|
|
|
229
378
|
step: { type: Number, default: 1 },
|
|
230
379
|
value: { type: Number, default: DEFAULT_MIN }
|
|
231
380
|
};
|
|
381
|
+
static valueConstraints = {
|
|
382
|
+
min: NUMBER_BOUNDS.finite,
|
|
383
|
+
max: NUMBER_BOUNDS.finite,
|
|
384
|
+
step: NUMBER_BOUNDS.positive,
|
|
385
|
+
value: NUMBER_BOUNDS.finite
|
|
386
|
+
};
|
|
232
387
|
static actions = ["onKeydown"];
|
|
233
388
|
static events = ["change", "reconcile"];
|
|
234
|
-
#role = new AttributeLease("role");
|
|
235
|
-
#tabindex = new AttributeLease("tabindex");
|
|
236
|
-
#orientation = new AttributeLease("aria-orientation");
|
|
237
|
-
#minimum = new AttributeLease("aria-valuemin");
|
|
238
|
-
#maximum = new AttributeLease("aria-valuemax");
|
|
239
|
-
#current = new AttributeLease("aria-valuenow");
|
|
240
|
-
|
|
241
|
-
#repaint = new MicrotaskCoalescer(() => this.#render());
|
|
242
|
-
/** Restores authored semantics before Turbo snapshots the element. */
|
|
243
|
-
#beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
|
|
244
|
-
/**
|
|
245
|
-
* The baseline a published value is compared with: the value last published
|
|
246
|
-
* through `aria-valuenow`, or `null` while this connection has published none.
|
|
247
|
-
* It moves to each value a key the user presses or a repaint publishes. While
|
|
248
|
-
* nothing is published — the separator is decorative — it keeps the last
|
|
249
|
-
* value published, so a value that moved meanwhile is reported once when the
|
|
250
|
-
* separator publishes again; one that connected decorative keeps `null`, so
|
|
251
|
-
* its first published value is only taken as the baseline.
|
|
252
|
-
*/
|
|
389
|
+
#role = new AttributeLease("role", this.identifier);
|
|
390
|
+
#tabindex = new AttributeLease("tabindex", this.identifier);
|
|
391
|
+
#orientation = new AttributeLease("aria-orientation", this.identifier);
|
|
392
|
+
#minimum = new AttributeLease("aria-valuemin", this.identifier);
|
|
393
|
+
#maximum = new AttributeLease("aria-valuemax", this.identifier);
|
|
394
|
+
#current = new AttributeLease("aria-valuenow", this.identifier);
|
|
395
|
+
#repaint = new MorphRenderWatcher(() => this.#render());
|
|
253
396
|
#settled = null;
|
|
254
|
-
/**
|
|
255
|
-
* Hydrates authored range ARIA into absent Values once and renders them
|
|
256
|
-
* without writing the normalized value back. What the first render publishes
|
|
257
|
-
* is the baseline, so connecting reports nothing.
|
|
258
|
-
*/
|
|
259
397
|
connect() {
|
|
260
398
|
this.#hydrateValues();
|
|
261
|
-
this.#repaint.
|
|
262
|
-
this.#beforeCache.activate();
|
|
399
|
+
this.#repaint.observe(this.element);
|
|
263
400
|
this.#settled = null;
|
|
264
401
|
this.#render();
|
|
265
402
|
}
|
|
266
|
-
/** Cancels repaint work and returns every semantic attribute this instance controlled. */
|
|
267
403
|
disconnect() {
|
|
268
|
-
this.#repaint.
|
|
269
|
-
this.#beforeCache.deactivate();
|
|
404
|
+
this.#repaint.disconnect();
|
|
270
405
|
this.#returnAttributes();
|
|
271
406
|
}
|
|
272
|
-
/** Repaints an orientation changed by application code or a Turbo morph. */
|
|
273
407
|
orientationValueChanged() {
|
|
274
408
|
this.#repaint.schedule();
|
|
275
409
|
}
|
|
276
|
-
/** Adds or removes the focusable range semantics at runtime. */
|
|
277
410
|
focusableValueChanged() {
|
|
278
411
|
this.#repaint.schedule();
|
|
279
412
|
}
|
|
280
|
-
/** Repaints a minimum changed by application code or a Turbo morph. */
|
|
281
413
|
minValueChanged() {
|
|
282
414
|
this.#repaint.schedule();
|
|
283
415
|
}
|
|
284
|
-
/** Repaints a maximum changed by application code or a Turbo morph. */
|
|
285
416
|
maxValueChanged() {
|
|
286
417
|
this.#repaint.schedule();
|
|
287
418
|
}
|
|
288
|
-
/** Repaints a step changed by application code or a Turbo morph. */
|
|
289
419
|
stepValueChanged() {
|
|
290
420
|
this.#repaint.schedule();
|
|
291
421
|
}
|
|
292
|
-
/** Repaints a current value changed by application code or a Turbo morph. */
|
|
293
422
|
valueValueChanged() {
|
|
294
423
|
this.#repaint.schedule();
|
|
295
424
|
}
|
|
296
|
-
/** Adjusts the value on arrow / Home / End keys (focusable variant only). */
|
|
297
425
|
onKeydown(event) {
|
|
298
426
|
if (isReservedArrowChord(event) || !this.focusableValue) return;
|
|
299
427
|
const horizontal = this.#effectiveOrientation === "horizontal";
|
|
300
428
|
const range = this.#effectiveRange;
|
|
301
|
-
const declared = this
|
|
429
|
+
const declared = this.#safeValue;
|
|
302
430
|
let next = null;
|
|
303
431
|
switch (event.key) {
|
|
304
432
|
case "ArrowUp":
|
|
@@ -326,16 +454,6 @@ var SeparatorController = class extends Controller {
|
|
|
326
454
|
event.preventDefault();
|
|
327
455
|
this.#commit(next);
|
|
328
456
|
}
|
|
329
|
-
/**
|
|
330
|
-
* Stores a normalized user value, renders immediately, and reports it as
|
|
331
|
-
* `change` when it differs from the value last published. The comparison is
|
|
332
|
-
* with the value last published, not with the value the key started from, so
|
|
333
|
-
* a page write no repaint has published yet is reported once, as this change,
|
|
334
|
-
* and a key that ends where the value was last published reports nothing. The
|
|
335
|
-
* baseline moves before the render and the report, so neither the repaint the
|
|
336
|
-
* Value write schedules nor a move a subscriber makes while `change` is
|
|
337
|
-
* dispatched is measured from the value this key replaced.
|
|
338
|
-
*/
|
|
339
457
|
#commit(raw) {
|
|
340
458
|
const value = snapSteppedValue(raw, this.#effectiveRange);
|
|
341
459
|
const reported = this.#settled;
|
|
@@ -344,20 +462,10 @@ var SeparatorController = class extends Controller {
|
|
|
344
462
|
this.#render();
|
|
345
463
|
if (value !== reported) this.dispatch("change", { detail: { value } });
|
|
346
464
|
}
|
|
347
|
-
/**
|
|
348
|
-
* Reflects live Values into the connected element without writing them back,
|
|
349
|
-
* then compares what it published with the baseline. A decorative separator
|
|
350
|
-
* publishes no value (`null` here): the baseline stays the last value
|
|
351
|
-
* published. A value is reported once as `reconcile` only when it moved from a
|
|
352
|
-
* published baseline, so the first value a connection publishes is only taken
|
|
353
|
-
* as the baseline.
|
|
354
|
-
*
|
|
355
|
-
* @stimeoRenderRoot
|
|
356
|
-
*/
|
|
357
465
|
#render() {
|
|
358
466
|
const range = this.#effectiveRange;
|
|
359
467
|
const focusable = this.focusableValue;
|
|
360
|
-
const published = focusable ? snapSteppedValue(this
|
|
468
|
+
const published = focusable ? snapSteppedValue(this.#safeValue, range) : null;
|
|
361
469
|
this.#role.write(this.element, "separator");
|
|
362
470
|
this.#orientation.write(this.element, this.#effectiveOrientation);
|
|
363
471
|
this.#tabindex.write(this.element, focusable ? "0" : null);
|
|
@@ -370,27 +478,19 @@ var SeparatorController = class extends Controller {
|
|
|
370
478
|
this.dispatch("reconcile", { detail: { value: published } });
|
|
371
479
|
}
|
|
372
480
|
}
|
|
373
|
-
/** Effective orientation; unknown strings fall back to the horizontal contract. */
|
|
374
481
|
get #effectiveOrientation() {
|
|
375
482
|
return this.orientationValue === "vertical" ? "vertical" : "horizontal";
|
|
376
483
|
}
|
|
377
|
-
/** Finite, ordered range shared by rendering and keyboard stepping. */
|
|
378
484
|
get #effectiveRange() {
|
|
379
|
-
const min =
|
|
380
|
-
const candidateMax =
|
|
485
|
+
const min = this.#safeMin;
|
|
486
|
+
const candidateMax = this.#safeMax;
|
|
381
487
|
return {
|
|
382
488
|
min,
|
|
383
489
|
max: Math.max(min, candidateMax),
|
|
384
|
-
step: this
|
|
490
|
+
step: this.#safeStep,
|
|
385
491
|
base: min
|
|
386
492
|
};
|
|
387
493
|
}
|
|
388
|
-
/**
|
|
389
|
-
* Copies valid authored ARIA into Values only when no explicit Value exists.
|
|
390
|
-
* This fill is the one Value write `connect()` makes: it never overwrites a
|
|
391
|
-
* declaration, and no Value callback runs it, so a declaration the page
|
|
392
|
-
* changes later is only ever read.
|
|
393
|
-
*/
|
|
394
494
|
#hydrateValues() {
|
|
395
495
|
if (!this.#hasInput("orientation")) {
|
|
396
496
|
const orientation = this.element.getAttribute("aria-orientation");
|
|
@@ -405,18 +505,15 @@ var SeparatorController = class extends Controller {
|
|
|
405
505
|
const value = this.#finiteAttribute("aria-valuenow");
|
|
406
506
|
if (!this.#hasInput("value") && value !== null) this.valueValue = value;
|
|
407
507
|
}
|
|
408
|
-
/** Whether the author supplied the namespaced Value instead of using its default. */
|
|
409
508
|
#hasInput(name) {
|
|
410
509
|
return this.element.hasAttribute(`data-${this.identifier}-${name}-value`);
|
|
411
510
|
}
|
|
412
|
-
/** Strict finite-number parser for the one-time progressive-enhancement bridge. */
|
|
413
511
|
#finiteAttribute(name) {
|
|
414
512
|
const raw = this.element.getAttribute(name);
|
|
415
513
|
if (raw === null || raw.trim() === "") return null;
|
|
416
514
|
const value = Number(raw);
|
|
417
515
|
return Number.isFinite(value) ? value : null;
|
|
418
516
|
}
|
|
419
|
-
/** Returns attributes without overwriting values a consumer changed after render. */
|
|
420
517
|
#returnAttributes() {
|
|
421
518
|
this.#role.return(this.element);
|
|
422
519
|
this.#tabindex.return(this.element);
|
|
@@ -425,13 +522,43 @@ var SeparatorController = class extends Controller {
|
|
|
425
522
|
this.#maximum.return(this.element);
|
|
426
523
|
this.#current.return(this.element);
|
|
427
524
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
525
|
+
get #safeMin() {
|
|
526
|
+
return this.#numbers.read(
|
|
527
|
+
this,
|
|
528
|
+
"min",
|
|
529
|
+
this.minValue,
|
|
530
|
+
_SeparatorController.values.min.default,
|
|
531
|
+
_SeparatorController.valueConstraints.min
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
get #safeMax() {
|
|
535
|
+
return this.#numbers.read(
|
|
536
|
+
this,
|
|
537
|
+
"max",
|
|
538
|
+
this.maxValue,
|
|
539
|
+
_SeparatorController.values.max.default,
|
|
540
|
+
_SeparatorController.valueConstraints.max
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
get #safeStep() {
|
|
544
|
+
return this.#numbers.read(
|
|
545
|
+
this,
|
|
546
|
+
"step",
|
|
547
|
+
this.stepValue,
|
|
548
|
+
_SeparatorController.values.step.default,
|
|
549
|
+
_SeparatorController.valueConstraints.step
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
get #safeValue() {
|
|
553
|
+
return this.#numbers.read(
|
|
554
|
+
this,
|
|
555
|
+
"value",
|
|
556
|
+
this.valueValue,
|
|
557
|
+
this.#effectiveRange.min,
|
|
558
|
+
_SeparatorController.valueConstraints.value
|
|
559
|
+
);
|
|
432
560
|
}
|
|
433
561
|
};
|
|
434
562
|
|
|
435
563
|
export { SeparatorController };
|
|
436
|
-
//# sourceMappingURL=separator_controller.js.map
|
|
437
564
|
//# sourceMappingURL=separator_controller.js.map
|