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,14 +1,21 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function sharedRegistry(key, create) {
|
|
4
|
+
const symbol = Symbol.for(key);
|
|
5
|
+
const scope = globalThis;
|
|
6
|
+
const existing = scope[symbol];
|
|
7
|
+
if (existing !== void 0) return existing;
|
|
8
|
+
const registry = create();
|
|
9
|
+
Object.defineProperty(globalThis, symbol, { value: registry });
|
|
10
|
+
return registry;
|
|
11
|
+
}
|
|
4
12
|
|
|
5
|
-
|
|
6
|
-
var counter = 0;
|
|
13
|
+
var ids = sharedRegistry("stimeo-ui.aria-ids.registry.v1", () => ({ counter: 0 }));
|
|
7
14
|
function uniqueId(prefix = "stimeo") {
|
|
8
15
|
let candidate;
|
|
9
16
|
do {
|
|
10
|
-
counter += 1;
|
|
11
|
-
candidate = `${prefix}-${counter}`;
|
|
17
|
+
ids.counter += 1;
|
|
18
|
+
candidate = `${prefix}-${ids.counter}`;
|
|
12
19
|
} while (typeof document !== "undefined" && document.getElementById(candidate) !== null);
|
|
13
20
|
return candidate;
|
|
14
21
|
}
|
|
@@ -19,7 +26,143 @@ function ensureId(element, prefix = "stimeo") {
|
|
|
19
26
|
return id;
|
|
20
27
|
}
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
function isAttributeValue(value) {
|
|
30
|
+
return value === null || typeof value === "string";
|
|
31
|
+
}
|
|
32
|
+
function readLeaseRecord(element, record) {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
35
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
36
|
+
const [original, written, ...rest] = parsed;
|
|
37
|
+
const linked = rest[0] !== false;
|
|
38
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
39
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
40
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
41
|
+
} catch {
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
var AttributeLease = class _AttributeLease {
|
|
46
|
+
static #registry = sharedRegistry(
|
|
47
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
48
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
49
|
+
);
|
|
50
|
+
#attribute;
|
|
51
|
+
#record;
|
|
52
|
+
#base;
|
|
53
|
+
#held = /* @__PURE__ */ new Set();
|
|
54
|
+
constructor(attribute, owner, options = {}) {
|
|
55
|
+
this.#attribute = attribute;
|
|
56
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
57
|
+
this.#base = options.base === true;
|
|
58
|
+
const names = _AttributeLease.#registry.names;
|
|
59
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
60
|
+
}
|
|
61
|
+
write(element, value) {
|
|
62
|
+
const stack = this.#stack(element);
|
|
63
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
64
|
+
this.#held.add(element);
|
|
65
|
+
const shows = !this.#covered(element, stack, hold);
|
|
66
|
+
hold.written = value;
|
|
67
|
+
this.#mark(element, stack);
|
|
68
|
+
if (shows) this.#reflect(element, value);
|
|
69
|
+
}
|
|
70
|
+
return(element) {
|
|
71
|
+
const held = this.#held.delete(element);
|
|
72
|
+
const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
|
|
73
|
+
const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
|
|
74
|
+
const hold = this.#take(stack);
|
|
75
|
+
if (!hold) return false;
|
|
76
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
77
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
78
|
+
const index = stack.indexOf(hold);
|
|
79
|
+
stack.splice(index, 1);
|
|
80
|
+
const above = stack[index];
|
|
81
|
+
if (above) above.linked &&= hold.linked;
|
|
82
|
+
this.#mark(element, stack);
|
|
83
|
+
if (!owns) return true;
|
|
84
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
85
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
returnAll() {
|
|
89
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
90
|
+
}
|
|
91
|
+
#stack(element) {
|
|
92
|
+
const stacks = _AttributeLease.#registry.stacks;
|
|
93
|
+
const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
94
|
+
stacks.set(element, byAttribute);
|
|
95
|
+
const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
|
|
96
|
+
byAttribute.set(this.#attribute, stack);
|
|
97
|
+
return stack;
|
|
98
|
+
}
|
|
99
|
+
#seed(element) {
|
|
100
|
+
const found = [];
|
|
101
|
+
for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
|
|
102
|
+
const read = readLeaseRecord(element, record);
|
|
103
|
+
if (read) found.push({ ...read, record });
|
|
104
|
+
}
|
|
105
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
106
|
+
return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
107
|
+
lease: null,
|
|
108
|
+
record,
|
|
109
|
+
written,
|
|
110
|
+
original,
|
|
111
|
+
linked: linked && index > 0
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
#take(stack) {
|
|
115
|
+
const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
|
|
116
|
+
if (hold) hold.lease = this;
|
|
117
|
+
return hold;
|
|
118
|
+
}
|
|
119
|
+
#acquire(element, stack) {
|
|
120
|
+
const current = element.getAttribute(this.#attribute);
|
|
121
|
+
const top = stack.at(-1);
|
|
122
|
+
const bottom = stack[0];
|
|
123
|
+
const over = top?.written === current ? top : void 0;
|
|
124
|
+
const from = this.#base ? bottom : over;
|
|
125
|
+
const hold = {
|
|
126
|
+
lease: this,
|
|
127
|
+
record: this.#record,
|
|
128
|
+
written: current,
|
|
129
|
+
original: from ? from.original : current,
|
|
130
|
+
linked: !this.#base && over !== void 0
|
|
131
|
+
};
|
|
132
|
+
if (this.#base && bottom) {
|
|
133
|
+
bottom.linked = true;
|
|
134
|
+
stack.unshift(hold);
|
|
135
|
+
} else stack.push(hold);
|
|
136
|
+
return hold;
|
|
137
|
+
}
|
|
138
|
+
#covered(element, stack, hold) {
|
|
139
|
+
const current = element.getAttribute(this.#attribute);
|
|
140
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
141
|
+
}
|
|
142
|
+
#mark(element, stack) {
|
|
143
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
144
|
+
stack.forEach((hold, index) => {
|
|
145
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
146
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
147
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
148
|
+
records.set(
|
|
149
|
+
hold.record,
|
|
150
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
for (const [name, recorded] of records) {
|
|
154
|
+
if (element.getAttribute(name) === recorded) continue;
|
|
155
|
+
if (recorded === null) element.removeAttribute(name);
|
|
156
|
+
else element.setAttribute(name, recorded);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
#reflect(element, value) {
|
|
160
|
+
if (element.getAttribute(this.#attribute) === value) return;
|
|
161
|
+
if (value === null) element.removeAttribute(this.#attribute);
|
|
162
|
+
else element.setAttribute(this.#attribute, value);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
23
166
|
function claimsWhileFocusWithin(element) {
|
|
24
167
|
return () => {
|
|
25
168
|
const active = element.ownerDocument.activeElement;
|
|
@@ -27,17 +170,11 @@ function claimsWhileFocusWithin(element) {
|
|
|
27
170
|
};
|
|
28
171
|
}
|
|
29
172
|
var EscapeLayer = class _EscapeLayer {
|
|
30
|
-
static #registries =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
#claims = null;
|
|
36
|
-
/**
|
|
37
|
-
* Activates this layer at the top of its document's Escape stack, installing
|
|
38
|
-
* the document's shared resolver listener if this is its first layer.
|
|
39
|
-
* Re-activating an already-active layer moves it to the top.
|
|
40
|
-
*/
|
|
173
|
+
static #registries = sharedRegistry(
|
|
174
|
+
"stimeo-ui.escape-layer.registry.v1",
|
|
175
|
+
() => /* @__PURE__ */ new WeakMap()
|
|
176
|
+
);
|
|
177
|
+
#active = null;
|
|
41
178
|
activate(ownerDocument = document, options) {
|
|
42
179
|
this.deactivate();
|
|
43
180
|
let registry = _EscapeLayer.#registries.get(ownerDocument);
|
|
@@ -46,44 +183,31 @@ var EscapeLayer = class _EscapeLayer {
|
|
|
46
183
|
_EscapeLayer.#registries.set(ownerDocument, registry);
|
|
47
184
|
ownerDocument.addEventListener("keydown", registry.onKeydown);
|
|
48
185
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
this.#
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Removes this layer from its document's Escape stack, uninstalling the
|
|
56
|
-
* shared listener when the stack empties. Safe to call when inactive.
|
|
57
|
-
*/
|
|
186
|
+
const entry = { onDismiss: options.onDismiss, claims: options.claims ?? null };
|
|
187
|
+
registry.stack.push(entry);
|
|
188
|
+
this.#active = { document: ownerDocument, entry };
|
|
189
|
+
}
|
|
58
190
|
deactivate() {
|
|
59
|
-
const
|
|
60
|
-
if (!
|
|
61
|
-
const registry = _EscapeLayer.#registries.get(
|
|
191
|
+
const active = this.#active;
|
|
192
|
+
if (!active) return;
|
|
193
|
+
const registry = _EscapeLayer.#registries.get(active.document);
|
|
62
194
|
if (registry) {
|
|
63
|
-
const index = registry.stack.lastIndexOf(
|
|
195
|
+
const index = registry.stack.lastIndexOf(active.entry);
|
|
64
196
|
if (index >= 0) registry.stack.splice(index, 1);
|
|
65
197
|
if (registry.stack.length === 0) {
|
|
66
|
-
|
|
67
|
-
_EscapeLayer.#registries.delete(
|
|
198
|
+
active.document.removeEventListener("keydown", registry.onKeydown);
|
|
199
|
+
_EscapeLayer.#registries.delete(active.document);
|
|
68
200
|
}
|
|
69
201
|
}
|
|
70
|
-
this.#
|
|
71
|
-
|
|
72
|
-
this.#claims = null;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Whether this active layer would own a press right now: it is the topmost
|
|
76
|
-
* layer whose {@link EscapeLayerOptions.claims} passes. Exposed for tests
|
|
77
|
-
* and diagnostics — production dismissal goes through the shared listener.
|
|
78
|
-
*/
|
|
202
|
+
this.#active = null;
|
|
203
|
+
}
|
|
79
204
|
get ownsEscape() {
|
|
80
|
-
const
|
|
81
|
-
if (!
|
|
82
|
-
const registry = _EscapeLayer.#registries.get(
|
|
205
|
+
const active = this.#active;
|
|
206
|
+
if (!active) return false;
|
|
207
|
+
const registry = _EscapeLayer.#registries.get(active.document);
|
|
83
208
|
if (!registry) return false;
|
|
84
|
-
return _EscapeLayer.#resolveOwner(registry.stack) ===
|
|
209
|
+
return _EscapeLayer.#resolveOwner(registry.stack) === active.entry;
|
|
85
210
|
}
|
|
86
|
-
/** Builds a document's registry with its shared resolver listener. */
|
|
87
211
|
static #createRegistry() {
|
|
88
212
|
const registry = {
|
|
89
213
|
stack: [],
|
|
@@ -92,24 +216,86 @@ var EscapeLayer = class _EscapeLayer {
|
|
|
92
216
|
const owner = _EscapeLayer.#resolveOwner(registry.stack);
|
|
93
217
|
if (!owner) return;
|
|
94
218
|
event.preventDefault();
|
|
95
|
-
owner
|
|
219
|
+
owner.onDismiss();
|
|
96
220
|
}
|
|
97
221
|
};
|
|
98
222
|
return registry;
|
|
99
223
|
}
|
|
100
|
-
/** The topmost stack layer whose claims predicate passes, or `null`. */
|
|
101
224
|
static #resolveOwner(stack) {
|
|
102
225
|
for (let index = stack.length - 1; index >= 0; index--) {
|
|
103
226
|
const layer = stack[index];
|
|
104
227
|
if (!layer) continue;
|
|
105
|
-
if (layer
|
|
228
|
+
if (layer.claims && !layer.claims()) continue;
|
|
106
229
|
return layer;
|
|
107
230
|
}
|
|
108
231
|
return null;
|
|
109
232
|
}
|
|
110
233
|
};
|
|
111
234
|
|
|
112
|
-
|
|
235
|
+
var ListenerSet = class {
|
|
236
|
+
#abort = new AbortController();
|
|
237
|
+
add(target, type, handler, options) {
|
|
238
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
239
|
+
}
|
|
240
|
+
dispose() {
|
|
241
|
+
this.#abort.abort();
|
|
242
|
+
this.#abort = new AbortController();
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
var MicrotaskCoalescer = class {
|
|
247
|
+
#run;
|
|
248
|
+
#queued = false;
|
|
249
|
+
#active = false;
|
|
250
|
+
#generation = 0;
|
|
251
|
+
constructor(run) {
|
|
252
|
+
this.#run = run;
|
|
253
|
+
}
|
|
254
|
+
activate() {
|
|
255
|
+
this.#active = true;
|
|
256
|
+
}
|
|
257
|
+
cancel() {
|
|
258
|
+
this.#active = false;
|
|
259
|
+
this.#queued = false;
|
|
260
|
+
this.#generation += 1;
|
|
261
|
+
}
|
|
262
|
+
schedule() {
|
|
263
|
+
if (!this.#active || this.#queued) return;
|
|
264
|
+
this.#queued = true;
|
|
265
|
+
const generation = this.#generation;
|
|
266
|
+
queueMicrotask(() => {
|
|
267
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
268
|
+
this.#queued = false;
|
|
269
|
+
this.#run();
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
var MorphRenderWatcher = class {
|
|
275
|
+
#listeners = new ListenerSet();
|
|
276
|
+
#pass;
|
|
277
|
+
#element = null;
|
|
278
|
+
#onMorph = () => this.#pass.schedule();
|
|
279
|
+
constructor(run) {
|
|
280
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
281
|
+
}
|
|
282
|
+
observe(element) {
|
|
283
|
+
if (this.#element === element) return;
|
|
284
|
+
this.disconnect();
|
|
285
|
+
this.#element = element;
|
|
286
|
+
this.#pass.activate();
|
|
287
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
288
|
+
}
|
|
289
|
+
schedule() {
|
|
290
|
+
this.#pass.schedule();
|
|
291
|
+
}
|
|
292
|
+
disconnect() {
|
|
293
|
+
this.#listeners.dispose();
|
|
294
|
+
this.#pass.cancel();
|
|
295
|
+
this.#element = null;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
113
299
|
var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
|
|
114
300
|
var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
|
|
115
301
|
function stateReasonFor(event) {
|
|
@@ -119,35 +305,56 @@ function stateReasonFor(event) {
|
|
|
119
305
|
return "user";
|
|
120
306
|
}
|
|
121
307
|
|
|
122
|
-
// src/controllers/dropdown_controller.ts
|
|
123
308
|
var DropdownController = class extends Controller {
|
|
124
309
|
static targets = ["trigger", "menu"];
|
|
125
310
|
static actions = ["close", "open", "toggle"];
|
|
126
311
|
static events = ["close", "open"];
|
|
127
|
-
/** Escape-stack membership while open; the shared resolver dismisses via it. */
|
|
128
312
|
#escapeLayer = new EscapeLayer();
|
|
129
|
-
|
|
313
|
+
#menuHidden = new AttributeLease("hidden", this.identifier);
|
|
314
|
+
#expanded = new AttributeLease("aria-expanded", this.identifier);
|
|
315
|
+
#suppliedControls = /* @__PURE__ */ new WeakMap();
|
|
316
|
+
#menu = null;
|
|
317
|
+
#connected = false;
|
|
130
318
|
#reporting = false;
|
|
131
|
-
|
|
319
|
+
#openState = false;
|
|
320
|
+
#morphRender = new MorphRenderWatcher(() => this.#repair());
|
|
132
321
|
#onOutsideClick = (event) => {
|
|
133
322
|
if (!this.element.contains(event.target)) {
|
|
134
323
|
this.#close("outside");
|
|
135
324
|
}
|
|
136
325
|
};
|
|
137
|
-
/** Starts in the closed state and registers outside-click handling. */
|
|
138
326
|
connect() {
|
|
139
327
|
this.#associateTriggerWithMenu();
|
|
140
328
|
this.#close("api");
|
|
141
329
|
document.addEventListener("click", this.#onOutsideClick, true);
|
|
330
|
+
this.#morphRender.observe(this.element);
|
|
331
|
+
this.#connected = true;
|
|
142
332
|
this.#reporting = true;
|
|
143
333
|
}
|
|
144
|
-
/** Removes the listeners registered in {@link connect}. */
|
|
145
334
|
disconnect() {
|
|
335
|
+
this.#connected = false;
|
|
146
336
|
this.#reporting = false;
|
|
147
337
|
this.#escapeLayer.deactivate();
|
|
148
338
|
document.removeEventListener("click", this.#onOutsideClick, true);
|
|
339
|
+
this.#morphRender.disconnect();
|
|
340
|
+
}
|
|
341
|
+
triggerTargetConnected() {
|
|
342
|
+
if (this.#connected) this.#reflectTrigger();
|
|
343
|
+
}
|
|
344
|
+
triggerTargetDisconnected(trigger) {
|
|
345
|
+
if (!this.triggerTargets.includes(trigger)) {
|
|
346
|
+
this.#expanded.return(trigger);
|
|
347
|
+
this.#withdrawControls(trigger);
|
|
348
|
+
}
|
|
349
|
+
if (this.#connected) this.#reflectTrigger();
|
|
350
|
+
}
|
|
351
|
+
menuTargetConnected() {
|
|
352
|
+
this.#adoptMenu();
|
|
353
|
+
}
|
|
354
|
+
menuTargetDisconnected(menu) {
|
|
355
|
+
this.#adoptMenu();
|
|
356
|
+
if (!this.menuTargets.includes(menu)) this.#menuHidden.return(menu);
|
|
149
357
|
}
|
|
150
|
-
/** Toggles the menu between open and closed. Bound via `data-action`. */
|
|
151
358
|
toggle(event) {
|
|
152
359
|
if (this.#isOpen) {
|
|
153
360
|
this.close(event);
|
|
@@ -155,62 +362,85 @@ var DropdownController = class extends Controller {
|
|
|
155
362
|
this.open(event);
|
|
156
363
|
}
|
|
157
364
|
}
|
|
158
|
-
/** Reveals the menu and reflects the open state on the trigger. */
|
|
159
365
|
open(event) {
|
|
160
366
|
this.#open(stateReasonFor(event));
|
|
161
367
|
}
|
|
162
|
-
/** Hides the menu and reflects the closed state on the trigger. */
|
|
163
368
|
close(event) {
|
|
164
369
|
this.#close(stateReasonFor(event));
|
|
165
370
|
}
|
|
166
|
-
/** Reveals the menu, reflects the open state, and reports a move. */
|
|
167
371
|
#open(reason) {
|
|
168
372
|
if (!this.hasMenuTarget) return;
|
|
373
|
+
const menu = this.menuTarget;
|
|
169
374
|
const was = this.#isOpen;
|
|
170
375
|
this.#escapeLayer.activate(document, {
|
|
171
376
|
onDismiss: () => this.#closeAndRestore(),
|
|
172
377
|
claims: claimsWhileFocusWithin(this.element)
|
|
173
378
|
});
|
|
174
|
-
this.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
379
|
+
this.#menuHidden.write(menu, null);
|
|
380
|
+
this.#menu = menu;
|
|
381
|
+
this.#openState = true;
|
|
382
|
+
if (this.hasTriggerTarget) this.#expanded.write(this.triggerTarget, "true");
|
|
178
383
|
if (!was && this.#reporting) this.dispatch("open", { detail: { reason }, cancelable: false });
|
|
179
384
|
}
|
|
180
|
-
/** Hides the menu, reflects the closed state, and reports a move. */
|
|
181
385
|
#close(reason) {
|
|
182
386
|
const was = this.#isOpen;
|
|
183
387
|
this.#escapeLayer.deactivate();
|
|
184
|
-
|
|
185
|
-
this.menuTarget
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
388
|
+
this.#openState = false;
|
|
389
|
+
const menu = this.hasMenuTarget ? this.menuTarget : null;
|
|
390
|
+
this.#menu = menu;
|
|
391
|
+
if (!menu) return;
|
|
392
|
+
this.#menuHidden.write(menu, "");
|
|
393
|
+
if (this.hasTriggerTarget) this.#expanded.write(this.triggerTarget, "false");
|
|
189
394
|
if (was && this.#reporting) this.dispatch("close", { detail: { reason }, cancelable: false });
|
|
190
395
|
}
|
|
191
|
-
|
|
396
|
+
#adoptMenu() {
|
|
397
|
+
if (!this.#connected) return;
|
|
398
|
+
const menu = this.hasMenuTarget ? this.menuTarget : null;
|
|
399
|
+
const previous = this.#menu;
|
|
400
|
+
this.#menu = menu;
|
|
401
|
+
if (!menu) {
|
|
402
|
+
this.#escapeLayer.deactivate();
|
|
403
|
+
this.#openState = false;
|
|
404
|
+
} else if (menu !== previous) {
|
|
405
|
+
this.#menuHidden.write(menu, previous?.hidden ?? true ? "" : null);
|
|
406
|
+
}
|
|
407
|
+
this.#reflectTrigger();
|
|
408
|
+
}
|
|
409
|
+
#reflectTrigger() {
|
|
410
|
+
if (!this.hasTriggerTarget) return;
|
|
411
|
+
this.#expanded.write(this.triggerTarget, String(this.#isOpen));
|
|
412
|
+
this.#associateTriggerWithMenu();
|
|
413
|
+
}
|
|
414
|
+
#repair() {
|
|
415
|
+
if (!this.hasMenuTarget) return;
|
|
416
|
+
this.#menuHidden.write(this.menuTarget, this.#openState ? null : "");
|
|
417
|
+
this.#reflectTrigger();
|
|
418
|
+
}
|
|
192
419
|
#closeAndRestore() {
|
|
193
420
|
this.#close("escape");
|
|
194
421
|
if (this.hasTriggerTarget) this.triggerTarget.focus();
|
|
195
422
|
}
|
|
196
|
-
/** Whether the menu is currently visible. */
|
|
197
423
|
get #isOpen() {
|
|
198
424
|
return this.hasMenuTarget && !this.menuTarget.hidden;
|
|
199
425
|
}
|
|
200
|
-
/**
|
|
201
|
-
* Associates the disclosure trigger and controlled region without clobbering
|
|
202
|
-
* authored markup.
|
|
203
|
-
*/
|
|
204
426
|
#associateTriggerWithMenu() {
|
|
205
427
|
if (!this.hasTriggerTarget || !this.hasMenuTarget) return;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
);
|
|
428
|
+
const trigger = this.triggerTarget;
|
|
429
|
+
const controls = trigger.getAttribute("aria-controls");
|
|
430
|
+
if (controls !== null && controls !== this.#suppliedControls.get(trigger)) return;
|
|
431
|
+
const id = ensureId(this.menuTarget, "stimeo--dropdown-menu");
|
|
432
|
+
if (controls === id) return;
|
|
433
|
+
trigger.setAttribute("aria-controls", id);
|
|
434
|
+
this.#suppliedControls.set(trigger, id);
|
|
435
|
+
}
|
|
436
|
+
#withdrawControls(trigger) {
|
|
437
|
+
const supplied = this.#suppliedControls.get(trigger);
|
|
438
|
+
this.#suppliedControls.delete(trigger);
|
|
439
|
+
if (supplied !== void 0 && trigger.getAttribute("aria-controls") === supplied) {
|
|
440
|
+
trigger.removeAttribute("aria-controls");
|
|
441
|
+
}
|
|
211
442
|
}
|
|
212
443
|
};
|
|
213
444
|
|
|
214
445
|
export { DropdownController };
|
|
215
|
-
//# sourceMappingURL=dropdown_controller.js.map
|
|
216
446
|
//# sourceMappingURL=dropdown_controller.js.map
|