stimeo-ui 0.14.0 → 0.16.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 +212 -0
- data/README.md +120 -0
- data/dist/cable/index.js +123 -29
- data/dist/controllers/accordion_controller.js +98 -9
- data/dist/controllers/announcer_controller.js +96 -62
- data/dist/controllers/auto_submit_controller.js +83 -7
- data/dist/controllers/avatar_controller.js +1 -1
- data/dist/controllers/breadcrumb_controller.js +38 -11
- data/dist/controllers/bulk_select_controller.js +7 -6
- data/dist/controllers/calendar_controller.js +340 -123
- data/dist/controllers/carousel_controller.js +263 -38
- data/dist/controllers/character_counter_controller.js +40 -2
- data/dist/controllers/checkbox_controller.js +81 -12
- data/dist/controllers/clipboard_controller.js +63 -5
- data/dist/controllers/collapsible_controller.js +99 -14
- data/dist/controllers/color_picker_controller.js +80 -34
- data/dist/controllers/combobox_controller.js +106 -16
- data/dist/controllers/command_palette_controller.js +35 -3
- data/dist/controllers/conditional_fields_controller.js +85 -17
- data/dist/controllers/confirm_controller.js +3 -0
- data/dist/controllers/context_menu_controller.js +32 -12
- data/dist/controllers/countdown_controller.js +129 -26
- data/dist/controllers/currency_input_controller.js +221 -67
- data/dist/controllers/data_grid_controller.js +195 -29
- data/dist/controllers/date_range_picker_controller.js +151 -30
- data/dist/controllers/dialog_controller.js +35 -8
- data/dist/controllers/direct_upload_controller.js +22 -4
- data/dist/controllers/dirty_form_controller.js +46 -13
- data/dist/controllers/dismissible_controller.js +1 -0
- data/dist/controllers/drawer_controller.js +54 -19
- data/dist/controllers/dropdown_controller.js +36 -9
- data/dist/controllers/editable_controller.js +34 -0
- data/dist/controllers/file_dropzone_controller.js +144 -51
- data/dist/controllers/filter_controller.js +20 -6
- data/dist/controllers/flash_controller.js +432 -71
- data/dist/controllers/focus_controller.js +1 -0
- data/dist/controllers/form_field_controller.js +7 -5
- data/dist/controllers/form_validation_controller.js +19 -13
- data/dist/controllers/frame_loading_controller.js +45 -8
- data/dist/controllers/highlight_controller.js +82 -25
- data/dist/controllers/hover_card_controller.js +40 -14
- data/dist/controllers/idle_controller.js +90 -5
- data/dist/controllers/input_mask_controller.js +65 -9
- data/dist/controllers/intersection_controller.js +3 -0
- data/dist/controllers/lazy_frame_controller.js +11 -2
- data/dist/controllers/listbox_controller.js +203 -45
- data/dist/controllers/local_time_controller.js +10 -5
- data/dist/controllers/masonry_controller.js +31 -15
- data/dist/controllers/menu_controller.js +45 -16
- data/dist/controllers/menubar_controller.js +58 -24
- data/dist/controllers/meter_controller.js +9 -5
- data/dist/controllers/multi_select_controller.js +278 -104
- data/dist/controllers/navigation_menu_controller.js +48 -15
- data/dist/controllers/nested_form_controller.js +37 -8
- data/dist/controllers/network_status_controller.js +9 -1
- data/dist/controllers/number_input_controller.js +124 -21
- data/dist/controllers/optimistic_controller.js +42 -5
- data/dist/controllers/otp_controller.js +198 -55
- data/dist/controllers/overflow_indicator_controller.js +115 -21
- data/dist/controllers/overflow_menu_controller.js +141 -44
- data/dist/controllers/pagination_controller.js +74 -28
- data/dist/controllers/password_reveal_controller.js +59 -2
- data/dist/controllers/persist_controller.js +30 -8
- data/dist/controllers/pointer_drag_controller.js +131 -52
- data/dist/controllers/popover_controller.js +45 -11
- data/dist/controllers/portal_controller.js +6 -2
- data/dist/controllers/preview_guard_controller.js +16 -1
- data/dist/controllers/progress_controller.js +8 -4
- data/dist/controllers/radio_group_controller.js +42 -17
- data/dist/controllers/range_slider_controller.js +88 -42
- data/dist/controllers/rating_controller.js +39 -15
- data/dist/controllers/read_more_controller.js +100 -7
- data/dist/controllers/reading_progress_controller.js +65 -19
- data/dist/controllers/relative_time_controller.js +10 -5
- data/dist/controllers/resizable_controller.js +82 -22
- data/dist/controllers/scroll_area_controller.js +75 -27
- data/dist/controllers/scroll_restore_controller.js +37 -16
- data/dist/controllers/scroll_visibility_controller.js +49 -30
- data/dist/controllers/scrollspy_controller.js +71 -26
- data/dist/controllers/separator_controller.js +66 -37
- data/dist/controllers/sidebar_controller.js +77 -18
- data/dist/controllers/skeleton_controller.js +6 -1
- data/dist/controllers/slider_controller.js +82 -47
- data/dist/controllers/smart_sticky_header_controller.js +60 -26
- data/dist/controllers/sortable_controller.js +17 -2
- data/dist/controllers/spinner_controller.js +10 -2
- data/dist/controllers/step_indicator_controller.js +18 -17
- data/dist/controllers/stepper_controller.js +101 -19
- data/dist/controllers/stick_to_bottom_controller.js +104 -8
- data/dist/controllers/submit_once_controller.js +45 -9
- data/dist/controllers/switch_controller.js +101 -10
- data/dist/controllers/tabs_controller.js +21 -2
- data/dist/controllers/tags_input_controller.js +209 -59
- data/dist/controllers/textarea_autosize_controller.js +29 -3
- data/dist/controllers/theme_controller.js +64 -14
- data/dist/controllers/time_picker_controller.js +23 -8
- data/dist/controllers/toast_controller.js +451 -105
- data/dist/controllers/toggle_group_controller.js +159 -23
- data/dist/controllers/toolbar_controller.js +32 -0
- data/dist/controllers/tooltip_controller.js +39 -13
- data/dist/controllers/transition_controller.js +4 -0
- data/dist/controllers/tree_view_controller.js +169 -16
- data/dist/index.js +5002 -1911
- data/dist/positioning/index.js +2 -0
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +2 -2
|
@@ -103,6 +103,25 @@ var DetachGate = class _DetachGate {
|
|
|
103
103
|
}
|
|
104
104
|
};
|
|
105
105
|
|
|
106
|
+
// src/utils/element_part.ts
|
|
107
|
+
function matchingPart(root, selector) {
|
|
108
|
+
return root.matches(selector) ? root : root.querySelector(selector);
|
|
109
|
+
}
|
|
110
|
+
function writeLabel(slot, text) {
|
|
111
|
+
if (slot.childElementCount === 0) {
|
|
112
|
+
slot.textContent = text;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
let first = null;
|
|
116
|
+
for (const node of Array.from(slot.childNodes)) {
|
|
117
|
+
if (node.nodeType !== Node.TEXT_NODE) continue;
|
|
118
|
+
if (first) node.remove();
|
|
119
|
+
else first = node;
|
|
120
|
+
}
|
|
121
|
+
if (first) first.nodeValue = text;
|
|
122
|
+
else slot.prepend(text);
|
|
123
|
+
}
|
|
124
|
+
|
|
106
125
|
// src/utils/focus_candidate.ts
|
|
107
126
|
function inheritsFieldsetDisabled(control) {
|
|
108
127
|
let fieldset = control.closest("fieldset[disabled]");
|
|
@@ -114,10 +133,93 @@ function inheritsFieldsetDisabled(control) {
|
|
|
114
133
|
return false;
|
|
115
134
|
}
|
|
116
135
|
|
|
136
|
+
// src/utils/target_selector.ts
|
|
137
|
+
function targetSelector(identifier, name) {
|
|
138
|
+
return `[data-${identifier}-target~="${name}"]`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/utils/template_row.ts
|
|
142
|
+
function cloneTemplateRoot(template) {
|
|
143
|
+
const root = template.content.firstElementChild;
|
|
144
|
+
return root instanceof HTMLElement ? root.cloneNode(true) : null;
|
|
145
|
+
}
|
|
146
|
+
var TemplateRow = class {
|
|
147
|
+
#options;
|
|
148
|
+
#warned = false;
|
|
149
|
+
constructor(options) {
|
|
150
|
+
this.#options = options;
|
|
151
|
+
}
|
|
152
|
+
/** The attribute selector for one declared part, in this controller's namespace. */
|
|
153
|
+
selector(name) {
|
|
154
|
+
return targetSelector(this.#options.identifier, name);
|
|
155
|
+
}
|
|
156
|
+
/** Re-arms the once-per-connection diagnostic. */
|
|
157
|
+
connect() {
|
|
158
|
+
this.#warned = false;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Names one missing part on the console, at most once per connection, and
|
|
162
|
+
* returns `null` so a caller can hand it straight back.
|
|
163
|
+
*/
|
|
164
|
+
report(missing) {
|
|
165
|
+
return this.#say(`lacks ${missing}`);
|
|
166
|
+
}
|
|
167
|
+
/** Writes one diagnostic per connection, saying what the template got wrong. */
|
|
168
|
+
#say(problem) {
|
|
169
|
+
if (this.#warned) return null;
|
|
170
|
+
this.#warned = true;
|
|
171
|
+
const { identifier, outcome, noun } = this.#options;
|
|
172
|
+
console.warn(`Stimeo UI: "${identifier}" ${outcome} because its ${noun} ${problem}.`);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Clones the row and resolves its parts, or names what the template got wrong
|
|
177
|
+
* and returns `null`. `values` fills the button's authored `aria-label`.
|
|
178
|
+
*/
|
|
179
|
+
instantiate(template, values) {
|
|
180
|
+
const { root: rootName, required, optional, button: buttonName } = this.#options;
|
|
181
|
+
const root = cloneTemplateRoot(template);
|
|
182
|
+
if (!root?.matches(this.selector(rootName))) {
|
|
183
|
+
return this.report(`${this.#article(rootName)} "${rootName}" root`);
|
|
184
|
+
}
|
|
185
|
+
const count = template.content.children.length;
|
|
186
|
+
if (count !== 1) {
|
|
187
|
+
return this.#say(`holds ${count} elements, and the row has to be its only one`);
|
|
188
|
+
}
|
|
189
|
+
const slots = {};
|
|
190
|
+
for (const name2 of required) {
|
|
191
|
+
const part = this.#resolve(root, name2);
|
|
192
|
+
if (!part) return this.report(`${this.#article(name2)} "${name2}" target`);
|
|
193
|
+
slots[name2] = part;
|
|
194
|
+
}
|
|
195
|
+
for (const name2 of optional ?? []) slots[name2] = this.#resolve(root, name2);
|
|
196
|
+
const named = this.#resolve(root, buttonName);
|
|
197
|
+
if (!(named instanceof HTMLButtonElement)) {
|
|
198
|
+
return this.report(`${this.#article(buttonName)} "${buttonName}" target <button>`);
|
|
199
|
+
}
|
|
200
|
+
const button = named;
|
|
201
|
+
const name = button.getAttribute("aria-label")?.trim() ?? "";
|
|
202
|
+
if (name === "") return this.report(`a non-empty aria-label on its "${buttonName}" target`);
|
|
203
|
+
button.setAttribute("aria-label", fillTemplate(name, values));
|
|
204
|
+
return { root, slots, button };
|
|
205
|
+
}
|
|
206
|
+
/** The row may be its own part; otherwise the first descendant carrying the target. */
|
|
207
|
+
#resolve(root, name) {
|
|
208
|
+
return matchingPart(root, this.selector(name));
|
|
209
|
+
}
|
|
210
|
+
#article(name) {
|
|
211
|
+
return /^[aeiou]/i.test(name) ? "an" : "a";
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
117
215
|
// src/controllers/file_dropzone_controller.ts
|
|
118
216
|
var DRAGOVER_ATTRIBUTE = "data-dragover";
|
|
119
|
-
var INVALID_ATTRIBUTE = "
|
|
217
|
+
var INVALID_ATTRIBUTE = "invalid";
|
|
120
218
|
var FileDropzoneController = class extends Controller {
|
|
219
|
+
/** The hook above, in the namespace this controller is registered under. */
|
|
220
|
+
get #invalidAttribute() {
|
|
221
|
+
return `data-${this.identifier}-${INVALID_ATTRIBUTE}`;
|
|
222
|
+
}
|
|
121
223
|
static targets = [
|
|
122
224
|
"zone",
|
|
123
225
|
"trigger",
|
|
@@ -147,14 +249,22 @@ var FileDropzoneController = class extends Controller {
|
|
|
147
249
|
#entries = [];
|
|
148
250
|
/** Whether a drag is currently over the zone; the source for `data-dragover`. */
|
|
149
251
|
#dragging = false;
|
|
150
|
-
/**
|
|
151
|
-
#
|
|
252
|
+
/** Builds one preview item from the authored template and owns its diagnostic. */
|
|
253
|
+
#rows = new TemplateRow({
|
|
254
|
+
identifier: this.identifier,
|
|
255
|
+
root: "item",
|
|
256
|
+
required: ["name"],
|
|
257
|
+
optional: ["thumb"],
|
|
258
|
+
button: "remove",
|
|
259
|
+
outcome: "added no file",
|
|
260
|
+
noun: "item template"
|
|
261
|
+
});
|
|
152
262
|
#gate = new DetachGate();
|
|
153
263
|
#beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
|
|
154
264
|
/** Subscribes to the Turbo cache rewind and re-arms the template diagnostic. */
|
|
155
265
|
connect() {
|
|
156
266
|
this.#gate.cancel();
|
|
157
|
-
this.#
|
|
267
|
+
this.#rows.connect();
|
|
158
268
|
this.#beforeCache.activate();
|
|
159
269
|
}
|
|
160
270
|
/**
|
|
@@ -246,15 +356,20 @@ var FileDropzoneController = class extends Controller {
|
|
|
246
356
|
*/
|
|
247
357
|
#onItemClick = (event) => {
|
|
248
358
|
const button = event.target.closest(
|
|
249
|
-
|
|
359
|
+
`button${this.#rows.selector("remove")}`
|
|
250
360
|
);
|
|
251
361
|
const index = this.#entries.findIndex((entry) => entry.item.contains(button));
|
|
252
362
|
if (index !== -1) this.#removeAt(index);
|
|
253
363
|
};
|
|
254
|
-
/**
|
|
364
|
+
/**
|
|
365
|
+
* Validates each incoming file, renders the accepted ones, and reports the batch.
|
|
366
|
+
*
|
|
367
|
+
* @stimeoRuntimeOnly The limits decide which files of this one drop are taken, and the texts word
|
|
368
|
+
* the announcements it makes.
|
|
369
|
+
*/
|
|
255
370
|
#addFiles(files) {
|
|
256
371
|
this.#rehome();
|
|
257
|
-
if (this.hasZoneTarget) this.zoneTarget.removeAttribute(
|
|
372
|
+
if (this.hasZoneTarget) this.zoneTarget.removeAttribute(this.#invalidAttribute);
|
|
258
373
|
const rejected = /* @__PURE__ */ new Map();
|
|
259
374
|
const turnedAway = [];
|
|
260
375
|
let addedName = "";
|
|
@@ -262,7 +377,7 @@ var FileDropzoneController = class extends Controller {
|
|
|
262
377
|
for (const file of Array.from(files)) {
|
|
263
378
|
const reason = this.#validate(file);
|
|
264
379
|
if (reason !== null) {
|
|
265
|
-
if (this.hasZoneTarget) this.zoneTarget.setAttribute(
|
|
380
|
+
if (this.hasZoneTarget) this.zoneTarget.setAttribute(this.#invalidAttribute, "");
|
|
266
381
|
const batch = rejected.get(reason);
|
|
267
382
|
if (batch) batch.count += 1;
|
|
268
383
|
else rejected.set(reason, { name: file.name, count: 1 });
|
|
@@ -310,26 +425,25 @@ var FileDropzoneController = class extends Controller {
|
|
|
310
425
|
/**
|
|
311
426
|
* Builds one preview item (name, optional thumbnail, remove button) and reports
|
|
312
427
|
* whether it was rendered.
|
|
428
|
+
*
|
|
429
|
+
* A refused row leaves the addition a no-op — nothing about the selection, the
|
|
430
|
+
* native input, the announcements, or the events changes — and the row reports
|
|
431
|
+
* why on the console once per connection.
|
|
313
432
|
*/
|
|
314
433
|
#appendFile(file) {
|
|
315
|
-
if (!this.hasListTarget)
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
'button[data-stimeo--file-dropzone-target~="remove"]'
|
|
323
|
-
);
|
|
324
|
-
const removeName = button?.getAttribute("aria-label")?.trim() ?? "";
|
|
325
|
-
if (!item) return this.#warnTemplate('an "item" root');
|
|
326
|
-
if (!name) return this.#warnTemplate('a "name" element');
|
|
327
|
-
if (!button) return this.#warnTemplate('a "remove" target <button>');
|
|
328
|
-
if (removeName === "") {
|
|
329
|
-
return this.#warnTemplate('a non-empty aria-label on its "remove" target');
|
|
434
|
+
if (!this.hasListTarget) {
|
|
435
|
+
this.#rows.report('a "list" target to render into');
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
if (!this.hasItemTemplateTarget) {
|
|
439
|
+
this.#rows.report('an "itemTemplate" target');
|
|
440
|
+
return false;
|
|
330
441
|
}
|
|
331
|
-
|
|
332
|
-
|
|
442
|
+
const row = this.#rows.instantiate(this.itemTemplateTarget, { name: file.name });
|
|
443
|
+
if (!row) return false;
|
|
444
|
+
const item = row.root;
|
|
445
|
+
writeLabel(row.slots.name, file.name);
|
|
446
|
+
const thumb = row.slots.thumb;
|
|
333
447
|
let url;
|
|
334
448
|
if (thumb && file.type.startsWith("image/")) {
|
|
335
449
|
url = URL.createObjectURL(file);
|
|
@@ -339,32 +453,10 @@ var FileDropzoneController = class extends Controller {
|
|
|
339
453
|
} else if (thumb) {
|
|
340
454
|
thumb.hidden = true;
|
|
341
455
|
}
|
|
342
|
-
this.listTarget.appendChild(
|
|
456
|
+
this.listTarget.appendChild(item);
|
|
343
457
|
this.#entries.push({ file, item, url });
|
|
344
458
|
return true;
|
|
345
459
|
}
|
|
346
|
-
/** Resolves one declared part inside a cloned item template. */
|
|
347
|
-
#slot(fragment, name) {
|
|
348
|
-
return fragment.querySelector(`[data-stimeo--file-dropzone-target~="${name}"]`);
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Reports an unusable item template to the author, once per connection.
|
|
352
|
-
*
|
|
353
|
-
* The addition itself stays a no-op — nothing about the selection, the native
|
|
354
|
-
* input, the announcements, or the events changes. Without this line the only
|
|
355
|
-
* symptom is a picker that accepts no file at all, and the causes the Inspector
|
|
356
|
-
* cannot see statically (a server-rendered template, a name that renders empty
|
|
357
|
-
* from a missing translation) would have no diagnostic anywhere.
|
|
358
|
-
*/
|
|
359
|
-
#warnTemplate(missing) {
|
|
360
|
-
if (!this.#warnedTemplate) {
|
|
361
|
-
this.#warnedTemplate = true;
|
|
362
|
-
console.warn(
|
|
363
|
-
`Stimeo UI: "${this.identifier}" added no file because its item template lacks ${missing}.`
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
return false;
|
|
367
|
-
}
|
|
368
460
|
/** Removes entry `index`, revokes its preview, and re-homes focus. */
|
|
369
461
|
#removeAt(index) {
|
|
370
462
|
const entry = this.#entries[index];
|
|
@@ -473,7 +565,7 @@ var FileDropzoneController = class extends Controller {
|
|
|
473
565
|
this.#entries.length = 0;
|
|
474
566
|
this.#syncInput();
|
|
475
567
|
this.#endDrag();
|
|
476
|
-
if (this.hasZoneTarget) this.zoneTarget.removeAttribute(
|
|
568
|
+
if (this.hasZoneTarget) this.zoneTarget.removeAttribute(this.#invalidAttribute);
|
|
477
569
|
}
|
|
478
570
|
/** Releases the selection once the disconnect is known to be a real detach. */
|
|
479
571
|
#teardown() {
|
|
@@ -493,8 +585,9 @@ var FileDropzoneController = class extends Controller {
|
|
|
493
585
|
get #removeButtons() {
|
|
494
586
|
const buttons = [];
|
|
495
587
|
for (const entry of this.#entries) {
|
|
496
|
-
const button =
|
|
497
|
-
|
|
588
|
+
const button = matchingPart(
|
|
589
|
+
entry.item,
|
|
590
|
+
`button${this.#rows.selector("remove")}`
|
|
498
591
|
);
|
|
499
592
|
if (button) buttons.push(button);
|
|
500
593
|
}
|
|
@@ -9,6 +9,12 @@ var FilterController = class extends Controller {
|
|
|
9
9
|
#onChange = () => {
|
|
10
10
|
this.apply();
|
|
11
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* The outcome the last `change` reported, which the next evaluation is compared
|
|
14
|
+
* with; `null` until the first evaluation after `connect()`, which reports
|
|
15
|
+
* whatever it finds.
|
|
16
|
+
*/
|
|
17
|
+
#reported = null;
|
|
12
18
|
/**
|
|
13
19
|
* Gates the declaration callback to the connected window.
|
|
14
20
|
*
|
|
@@ -18,6 +24,7 @@ var FilterController = class extends Controller {
|
|
|
18
24
|
*/
|
|
19
25
|
#connected = false;
|
|
20
26
|
connect() {
|
|
27
|
+
this.#reported = null;
|
|
21
28
|
this.#evaluate();
|
|
22
29
|
this.element.addEventListener("change", this.#onChange);
|
|
23
30
|
this.#connected = true;
|
|
@@ -44,7 +51,10 @@ var FilterController = class extends Controller {
|
|
|
44
51
|
}
|
|
45
52
|
/**
|
|
46
53
|
* The evaluation itself: every item's visibility from the active tokens, then
|
|
47
|
-
* the groups and the empty element, then
|
|
54
|
+
* the groups and the empty element, then `change` when the outcome moved from
|
|
55
|
+
* the one last reported. Several triggers can land on the same outcome — a
|
|
56
|
+
* `clear` evaluates, and a toggle group wired to `apply` reports the presses it
|
|
57
|
+
* turned off — so an evaluation that changes nothing reports nothing.
|
|
48
58
|
*
|
|
49
59
|
* @stimeoRenderRoot
|
|
50
60
|
*/
|
|
@@ -62,9 +72,13 @@ var FilterController = class extends Controller {
|
|
|
62
72
|
for (const empty of this.emptyTargets) {
|
|
63
73
|
empty.hidden = visibleCount > 0;
|
|
64
74
|
}
|
|
65
|
-
this.
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
const total = this.itemTargets.length;
|
|
76
|
+
const reported = this.#reported;
|
|
77
|
+
if (reported !== null && reported.visibleCount === visibleCount && reported.total === total && reported.active.length === active.length && reported.active.every((token, index) => token === active[index])) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.#reported = { active, visibleCount, total };
|
|
81
|
+
this.dispatch("change", { detail: { active: [...active], visibleCount, total } });
|
|
68
82
|
}
|
|
69
83
|
/** Turns every control off (uncheck / aria-pressed="false") and re-applies. */
|
|
70
84
|
clear() {
|
|
@@ -92,7 +106,7 @@ var FilterController = class extends Controller {
|
|
|
92
106
|
}
|
|
93
107
|
/** A control's token: explicit `data-stimeo--filter-token`, else `data-value` / value. */
|
|
94
108
|
#tokenOf(control) {
|
|
95
|
-
const explicit = control.getAttribute(
|
|
109
|
+
const explicit = control.getAttribute(`data-${this.identifier}-token`) ?? control.dataset.value;
|
|
96
110
|
if (explicit) return explicit;
|
|
97
111
|
return control instanceof HTMLInputElement ? control.value : "";
|
|
98
112
|
}
|
|
@@ -104,7 +118,7 @@ var FilterController = class extends Controller {
|
|
|
104
118
|
}
|
|
105
119
|
/** An item's declared tokens (`data-stimeo--filter-tokens`, space-separated). */
|
|
106
120
|
#tokensOf(item) {
|
|
107
|
-
return (item.getAttribute(
|
|
121
|
+
return (item.getAttribute(`data-${this.identifier}-tokens`) ?? "").split(/\s+/).filter(Boolean);
|
|
108
122
|
}
|
|
109
123
|
/** Whether a group still contains at least one non-hidden item. */
|
|
110
124
|
#hasVisibleItem(group) {
|