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
|
@@ -230,24 +230,37 @@ var SeparatorController = class extends Controller {
|
|
|
230
230
|
value: { type: Number, default: DEFAULT_MIN }
|
|
231
231
|
};
|
|
232
232
|
static actions = ["onKeydown"];
|
|
233
|
-
static events = ["change"];
|
|
233
|
+
static events = ["change", "reconcile"];
|
|
234
234
|
#role = new AttributeLease("role");
|
|
235
235
|
#tabindex = new AttributeLease("tabindex");
|
|
236
236
|
#orientation = new AttributeLease("aria-orientation");
|
|
237
237
|
#minimum = new AttributeLease("aria-valuemin");
|
|
238
238
|
#maximum = new AttributeLease("aria-valuemax");
|
|
239
239
|
#current = new AttributeLease("aria-valuenow");
|
|
240
|
-
/** Collapses a morph that changes several render Values into one
|
|
240
|
+
/** Collapses a morph that changes several render Values into one repaint. */
|
|
241
241
|
#repaint = new MicrotaskCoalescer(() => this.#render());
|
|
242
242
|
/** Restores authored semantics before Turbo snapshots the element. */
|
|
243
243
|
#beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
|
|
244
|
-
/**
|
|
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
|
+
*/
|
|
253
|
+
#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
|
+
*/
|
|
245
259
|
connect() {
|
|
246
260
|
this.#hydrateValues();
|
|
247
261
|
this.#repaint.activate();
|
|
248
262
|
this.#beforeCache.activate();
|
|
249
|
-
|
|
250
|
-
if (!Object.is(this.valueValue, value)) this.valueValue = value;
|
|
263
|
+
this.#settled = null;
|
|
251
264
|
this.#render();
|
|
252
265
|
}
|
|
253
266
|
/** Cancels repaint work and returns every semantic attribute this instance controlled. */
|
|
@@ -256,27 +269,27 @@ var SeparatorController = class extends Controller {
|
|
|
256
269
|
this.#beforeCache.deactivate();
|
|
257
270
|
this.#returnAttributes();
|
|
258
271
|
}
|
|
259
|
-
/**
|
|
272
|
+
/** Repaints an orientation changed by application code or a Turbo morph. */
|
|
260
273
|
orientationValueChanged() {
|
|
261
274
|
this.#repaint.schedule();
|
|
262
275
|
}
|
|
263
|
-
/**
|
|
276
|
+
/** Adds or removes the focusable range semantics at runtime. */
|
|
264
277
|
focusableValueChanged() {
|
|
265
278
|
this.#repaint.schedule();
|
|
266
279
|
}
|
|
267
|
-
/**
|
|
280
|
+
/** Repaints a minimum changed by application code or a Turbo morph. */
|
|
268
281
|
minValueChanged() {
|
|
269
282
|
this.#repaint.schedule();
|
|
270
283
|
}
|
|
271
|
-
/**
|
|
284
|
+
/** Repaints a maximum changed by application code or a Turbo morph. */
|
|
272
285
|
maxValueChanged() {
|
|
273
286
|
this.#repaint.schedule();
|
|
274
287
|
}
|
|
275
|
-
/**
|
|
288
|
+
/** Repaints a step changed by application code or a Turbo morph. */
|
|
276
289
|
stepValueChanged() {
|
|
277
290
|
this.#repaint.schedule();
|
|
278
291
|
}
|
|
279
|
-
/**
|
|
292
|
+
/** Repaints a current value changed by application code or a Turbo morph. */
|
|
280
293
|
valueValueChanged() {
|
|
281
294
|
this.#repaint.schedule();
|
|
282
295
|
}
|
|
@@ -285,20 +298,20 @@ var SeparatorController = class extends Controller {
|
|
|
285
298
|
if (isReservedArrowChord(event) || !this.focusableValue) return;
|
|
286
299
|
const horizontal = this.#effectiveOrientation === "horizontal";
|
|
287
300
|
const range = this.#effectiveRange;
|
|
288
|
-
const
|
|
301
|
+
const declared = this.valueValue;
|
|
289
302
|
let next = null;
|
|
290
303
|
switch (event.key) {
|
|
291
304
|
case "ArrowUp":
|
|
292
|
-
if (horizontal) next = stepSteppedValue(
|
|
305
|
+
if (horizontal) next = stepSteppedValue(declared, 1, range);
|
|
293
306
|
break;
|
|
294
307
|
case "ArrowDown":
|
|
295
|
-
if (horizontal) next = stepSteppedValue(
|
|
308
|
+
if (horizontal) next = stepSteppedValue(declared, -1, range);
|
|
296
309
|
break;
|
|
297
310
|
case "ArrowRight":
|
|
298
|
-
if (!horizontal) next = stepSteppedValue(
|
|
311
|
+
if (!horizontal) next = stepSteppedValue(declared, 1, range);
|
|
299
312
|
break;
|
|
300
313
|
case "ArrowLeft":
|
|
301
|
-
if (!horizontal) next = stepSteppedValue(
|
|
314
|
+
if (!horizontal) next = stepSteppedValue(declared, -1, range);
|
|
302
315
|
break;
|
|
303
316
|
case "Home":
|
|
304
317
|
next = range.min;
|
|
@@ -311,36 +324,51 @@ var SeparatorController = class extends Controller {
|
|
|
311
324
|
}
|
|
312
325
|
if (next === null) return;
|
|
313
326
|
event.preventDefault();
|
|
314
|
-
this.#commit(next
|
|
327
|
+
this.#commit(next);
|
|
315
328
|
}
|
|
316
|
-
/**
|
|
317
|
-
|
|
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
|
+
#commit(raw) {
|
|
318
340
|
const value = snapSteppedValue(raw, this.#effectiveRange);
|
|
341
|
+
const reported = this.#settled;
|
|
342
|
+
this.#settled = value;
|
|
319
343
|
if (!Object.is(this.valueValue, value)) this.valueValue = value;
|
|
320
344
|
this.#render();
|
|
321
|
-
if (value !==
|
|
345
|
+
if (value !== reported) this.dispatch("change", { detail: { value } });
|
|
322
346
|
}
|
|
323
347
|
/**
|
|
324
|
-
* Reflects live Values into the connected element without
|
|
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.
|
|
325
354
|
*
|
|
326
355
|
* @stimeoRenderRoot
|
|
327
356
|
*/
|
|
328
357
|
#render() {
|
|
329
358
|
const range = this.#effectiveRange;
|
|
330
|
-
const
|
|
359
|
+
const focusable = this.focusableValue;
|
|
360
|
+
const published = focusable ? snapSteppedValue(this.valueValue, range) : null;
|
|
331
361
|
this.#role.write(this.element, "separator");
|
|
332
362
|
this.#orientation.write(this.element, this.#effectiveOrientation);
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
363
|
+
this.#tabindex.write(this.element, focusable ? "0" : null);
|
|
364
|
+
this.#minimum.write(this.element, focusable ? String(range.min) : null);
|
|
365
|
+
this.#maximum.write(this.element, focusable ? String(range.max) : null);
|
|
366
|
+
this.#current.write(this.element, published === null ? null : String(published));
|
|
367
|
+
const previous = this.#settled;
|
|
368
|
+
if (published !== null) this.#settled = published;
|
|
369
|
+
if (previous !== null && published !== null && published !== previous) {
|
|
370
|
+
this.dispatch("reconcile", { detail: { value: published } });
|
|
339
371
|
}
|
|
340
|
-
this.#tabindex.write(this.element, "0");
|
|
341
|
-
this.#minimum.write(this.element, String(range.min));
|
|
342
|
-
this.#maximum.write(this.element, String(range.max));
|
|
343
|
-
this.#current.write(this.element, String(value));
|
|
344
372
|
}
|
|
345
373
|
/** Effective orientation; unknown strings fall back to the horizontal contract. */
|
|
346
374
|
get #effectiveOrientation() {
|
|
@@ -357,11 +385,12 @@ var SeparatorController = class extends Controller {
|
|
|
357
385
|
base: min
|
|
358
386
|
};
|
|
359
387
|
}
|
|
360
|
-
/**
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
+
*/
|
|
365
394
|
#hydrateValues() {
|
|
366
395
|
if (!this.#hasInput("orientation")) {
|
|
367
396
|
const orientation = this.element.getAttribute("aria-orientation");
|
|
@@ -382,6 +382,16 @@ function writeLocalStorage(key, value) {
|
|
|
382
382
|
}
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
+
// src/utils/state_reason.ts
|
|
386
|
+
var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
|
|
387
|
+
var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
|
|
388
|
+
function stateReasonFor(event) {
|
|
389
|
+
if (!event) return "api";
|
|
390
|
+
if (FOCUS_EVENTS.has(event.type)) return "focus";
|
|
391
|
+
if (POINTER_EVENTS.has(event.type)) return "pointer";
|
|
392
|
+
return "user";
|
|
393
|
+
}
|
|
394
|
+
|
|
385
395
|
// src/utils/safe_timeout.ts
|
|
386
396
|
var TimerRegistry = class {
|
|
387
397
|
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
@@ -585,11 +595,12 @@ var SidebarController = class extends Controller {
|
|
|
585
595
|
collapsed: { type: Boolean, default: false }
|
|
586
596
|
};
|
|
587
597
|
static actions = ["close", "open", "toggle"];
|
|
598
|
+
static events = ["close", "open", "reconcile"];
|
|
588
599
|
/** Exact panel currently owned by the modal lifecycle (survives target churn safely). */
|
|
589
600
|
#activePanel = null;
|
|
590
601
|
/** Owns the overlay modal side effects; Escape closes, focus falls to trigger. */
|
|
591
602
|
#trap = new FocusTrap(() => this.#activePanel ?? this.panelTarget, {
|
|
592
|
-
onEscape: () => this
|
|
603
|
+
onEscape: () => this.#closeOverlay("escape"),
|
|
593
604
|
fallbackFocus: () => this.hasTriggerTarget ? this.triggerTarget : null
|
|
594
605
|
});
|
|
595
606
|
/** Current responsive mode. */
|
|
@@ -604,6 +615,8 @@ var SidebarController = class extends Controller {
|
|
|
604
615
|
#transition = new TransitionCompletion();
|
|
605
616
|
/** Distinguishes dynamic target churn from callbacks around controller teardown. */
|
|
606
617
|
#connected = false;
|
|
618
|
+
/** Whether state moves are reported: set once `connect()` settled the baseline. */
|
|
619
|
+
#reporting = false;
|
|
607
620
|
connect() {
|
|
608
621
|
this.#connected = true;
|
|
609
622
|
this.#beforeCache.activate();
|
|
@@ -612,10 +625,12 @@ var SidebarController = class extends Controller {
|
|
|
612
625
|
this.#mqlQuery = this.#breakpointQuery;
|
|
613
626
|
this.#mql = this.#matchBreakpoint(this.#mqlQuery);
|
|
614
627
|
this.#mql?.addEventListener("change", this.#onMediaChange);
|
|
615
|
-
this.#applyMode(this.#computeMode());
|
|
628
|
+
this.#applyMode(this.#computeMode(), false);
|
|
629
|
+
this.#reporting = true;
|
|
616
630
|
}
|
|
617
631
|
disconnect() {
|
|
618
632
|
this.#connected = false;
|
|
633
|
+
this.#reporting = false;
|
|
619
634
|
this.#beforeCache.deactivate();
|
|
620
635
|
this.#mql?.removeEventListener("change", this.#onMediaChange);
|
|
621
636
|
this.#mql = null;
|
|
@@ -658,7 +673,7 @@ var SidebarController = class extends Controller {
|
|
|
658
673
|
this.#mql = this.#matchBreakpoint(query);
|
|
659
674
|
this.#mql?.addEventListener("change", this.#onMediaChange);
|
|
660
675
|
const next = this.#computeMode();
|
|
661
|
-
if (next !== this.#mode) this.#applyMode(next);
|
|
676
|
+
if (next !== this.#mode) this.#applyMode(next, true);
|
|
662
677
|
}
|
|
663
678
|
/**
|
|
664
679
|
* Sanitizes overlay markup before Turbo snapshots the page.
|
|
@@ -675,26 +690,44 @@ var SidebarController = class extends Controller {
|
|
|
675
690
|
this.#setOverlayClosedImmediate();
|
|
676
691
|
});
|
|
677
692
|
/** Toggles the panel: inline flips collapsed/expanded, overlay flips open/closed. */
|
|
678
|
-
toggle() {
|
|
693
|
+
toggle(event) {
|
|
694
|
+
const reason = stateReasonFor(event);
|
|
679
695
|
if (this.#isOverlay) {
|
|
680
|
-
this.#isOverlayOpen ? this.#closeOverlay() : this.#openOverlay();
|
|
696
|
+
this.#isOverlayOpen ? this.#closeOverlay(reason) : this.#openOverlay(reason);
|
|
681
697
|
} else {
|
|
682
|
-
this.#setCollapsed(!this.#collapsed);
|
|
698
|
+
this.#setCollapsed(!this.#collapsed, reason);
|
|
683
699
|
}
|
|
684
700
|
}
|
|
685
701
|
/** Shows the panel (inline: expand; overlay: open). */
|
|
686
|
-
open() {
|
|
687
|
-
|
|
688
|
-
|
|
702
|
+
open(event) {
|
|
703
|
+
const reason = stateReasonFor(event);
|
|
704
|
+
if (this.#isOverlay) this.#openOverlay(reason);
|
|
705
|
+
else this.#setCollapsed(false, reason);
|
|
689
706
|
}
|
|
690
707
|
/** Hides the panel (inline: collapse; overlay: close). */
|
|
691
|
-
close() {
|
|
692
|
-
|
|
693
|
-
|
|
708
|
+
close(event) {
|
|
709
|
+
const reason = this.#closeReason(event);
|
|
710
|
+
if (this.#isOverlay) this.#closeOverlay(reason);
|
|
711
|
+
else this.#setCollapsed(true, reason);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Reads why a close happened. The backdrop has no action of its own — the
|
|
715
|
+
* consumer wires this one onto it — so the shared `"outside"` vocabulary is
|
|
716
|
+
* only reachable by recognising that target here.
|
|
717
|
+
*/
|
|
718
|
+
#closeReason(event) {
|
|
719
|
+
if (this.hasBackdropTarget && event?.currentTarget === this.backdropTarget) return "outside";
|
|
720
|
+
return stateReasonFor(event);
|
|
694
721
|
}
|
|
695
722
|
// --- Mode handling ---------------------------------------------------------
|
|
696
|
-
/**
|
|
697
|
-
|
|
723
|
+
/**
|
|
724
|
+
* Re-renders the closed/default state for `mode` and records it.
|
|
725
|
+
*
|
|
726
|
+
* @param report - Whether the mode change is one the consumer can observe: a
|
|
727
|
+
* viewport change re-derives the state under them, the connect-time baseline
|
|
728
|
+
* does not.
|
|
729
|
+
*/
|
|
730
|
+
#applyMode(mode, report) {
|
|
698
731
|
this.#mode = mode;
|
|
699
732
|
if (this.hasPanelTarget) this.panelTarget.setAttribute("data-mode", mode);
|
|
700
733
|
if (mode === "inline") {
|
|
@@ -708,6 +741,12 @@ var SidebarController = class extends Controller {
|
|
|
708
741
|
this.#trap.deactivate({ restoreFocus: false });
|
|
709
742
|
this.#setOverlayClosedImmediate();
|
|
710
743
|
}
|
|
744
|
+
if (report && this.#reporting) {
|
|
745
|
+
this.dispatch("reconcile", {
|
|
746
|
+
detail: { mode, open: this.#isExpanded },
|
|
747
|
+
cancelable: false
|
|
748
|
+
});
|
|
749
|
+
}
|
|
711
750
|
}
|
|
712
751
|
/** Reconciles a replacement panel with the current responsive mode and DOM state. */
|
|
713
752
|
#adoptPanel(panel) {
|
|
@@ -742,7 +781,7 @@ var SidebarController = class extends Controller {
|
|
|
742
781
|
}
|
|
743
782
|
#onMediaChange = (event) => {
|
|
744
783
|
const next = event.matches ? "inline" : "overlay";
|
|
745
|
-
if (next !== this.#mode) this.#applyMode(next);
|
|
784
|
+
if (next !== this.#mode) this.#applyMode(next, true);
|
|
746
785
|
};
|
|
747
786
|
#computeMode() {
|
|
748
787
|
return this.#mql?.matches ?? true ? "inline" : "overlay";
|
|
@@ -753,10 +792,11 @@ var SidebarController = class extends Controller {
|
|
|
753
792
|
}
|
|
754
793
|
// --- Inline (rail) ---------------------------------------------------------
|
|
755
794
|
/** Sets, reflects, and persists the inline collapsed preference. */
|
|
756
|
-
#setCollapsed(collapsed) {
|
|
795
|
+
#setCollapsed(collapsed, reason) {
|
|
757
796
|
if (collapsed === this.#collapsed) return;
|
|
758
797
|
this.#collapsed = collapsed;
|
|
759
798
|
this.#applyInlineState(collapsed);
|
|
799
|
+
this.#report(!collapsed, reason);
|
|
760
800
|
this.#persistCollapsed(collapsed);
|
|
761
801
|
}
|
|
762
802
|
/** Reflects the inline rail state onto the panel and trigger. */
|
|
@@ -768,7 +808,7 @@ var SidebarController = class extends Controller {
|
|
|
768
808
|
}
|
|
769
809
|
// --- Overlay (off-canvas modal) -------------------------------------------
|
|
770
810
|
/** Opens the overlay: reveal it, commit a starting frame, then trap focus. */
|
|
771
|
-
#openOverlay() {
|
|
811
|
+
#openOverlay(reason) {
|
|
772
812
|
if (!this.hasPanelTarget || this.#isOverlayOpen) return;
|
|
773
813
|
this.#transition.cancel();
|
|
774
814
|
this.#activePanel = this.panelTarget;
|
|
@@ -777,13 +817,17 @@ var SidebarController = class extends Controller {
|
|
|
777
817
|
void this.panelTarget.offsetWidth;
|
|
778
818
|
this.#setOverlayState("open");
|
|
779
819
|
this.#setExpandedAttr(true);
|
|
820
|
+
this.#report(true, reason);
|
|
821
|
+
if (!this.#isOverlayOpen) return;
|
|
780
822
|
this.#trap.activate();
|
|
781
823
|
}
|
|
782
824
|
/** Closes the overlay: start the exit transition, defer hide + trap teardown. */
|
|
783
|
-
#closeOverlay() {
|
|
825
|
+
#closeOverlay(reason) {
|
|
784
826
|
if (!this.hasPanelTarget || !this.#isOverlayOpen) return;
|
|
785
827
|
this.#setOverlayState("closed");
|
|
786
828
|
this.#setExpandedAttr(false);
|
|
829
|
+
this.#report(false, reason);
|
|
830
|
+
if (this.#isOverlayOpen) return;
|
|
787
831
|
this.#hideAfterTransition();
|
|
788
832
|
}
|
|
789
833
|
/** Renders the overlay closed state up front (used when entering overlay mode). */
|
|
@@ -826,6 +870,17 @@ var SidebarController = class extends Controller {
|
|
|
826
870
|
this.triggerTarget.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
827
871
|
}
|
|
828
872
|
}
|
|
873
|
+
/** Reports a move of the expanded state, naming the mode it happened in. */
|
|
874
|
+
#report(open, reason) {
|
|
875
|
+
if (!this.#reporting) return;
|
|
876
|
+
const detail = { reason, mode: this.#mode };
|
|
877
|
+
if (open) this.dispatch("open", { detail, cancelable: false });
|
|
878
|
+
else this.dispatch("close", { detail, cancelable: false });
|
|
879
|
+
}
|
|
880
|
+
/** Whether the panel currently counts as expanded in the mode it is in. */
|
|
881
|
+
get #isExpanded() {
|
|
882
|
+
return this.#isOverlay ? this.#isOverlayOpen : !this.#collapsed;
|
|
883
|
+
}
|
|
829
884
|
/**
|
|
830
885
|
* Resolves the inline collapsed preference, in priority order:
|
|
831
886
|
* 1. the persisted value (when a `key` is set and storage is readable),
|
|
@@ -851,6 +906,10 @@ var SidebarController = class extends Controller {
|
|
|
851
906
|
if (!key) return;
|
|
852
907
|
writeLocalStorage(key, collapsed ? "1" : "0");
|
|
853
908
|
}
|
|
909
|
+
/**
|
|
910
|
+
* @stimeoRuntimeOnly `key` names the storage slot the collapsed state is saved under; the state
|
|
911
|
+
* itself comes from the user.
|
|
912
|
+
*/
|
|
854
913
|
get #storageKey() {
|
|
855
914
|
return this.keyValue ? `stimeo--sidebar:${this.keyValue}` : "";
|
|
856
915
|
}
|
|
@@ -215,7 +215,12 @@ var SkeletonController = class extends Controller {
|
|
|
215
215
|
this.element.setAttribute("aria-busy", "true");
|
|
216
216
|
this.element.setAttribute("data-state", "loading");
|
|
217
217
|
}
|
|
218
|
-
/**
|
|
218
|
+
/**
|
|
219
|
+
* Hides the placeholder, shows content, and clears the busy state.
|
|
220
|
+
*
|
|
221
|
+
* @stimeoRuntimeOnly `announceReadyText` is the wording of the one announcement this reveal
|
|
222
|
+
* makes.
|
|
223
|
+
*/
|
|
219
224
|
#reveal() {
|
|
220
225
|
if (this.hasPlaceholderTarget) this.placeholderTarget.hidden = true;
|
|
221
226
|
if (this.hasContentTarget) this.contentTarget.hidden = false;
|