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.
- package/CHANGELOG.md +212 -0
- package/README.md +120 -0
- package/dist/cable/index.js +123 -29
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +30 -3
- package/dist/controllers/accordion_controller.js +98 -9
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +8 -7
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +3 -2
- package/dist/controllers/announcer_controller.js +96 -62
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.js +83 -7
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +1 -1
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.d.ts +10 -7
- package/dist/controllers/breadcrumb_controller.js +38 -11
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +4 -4
- package/dist/controllers/bulk_select_controller.js +7 -6
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +101 -23
- package/dist/controllers/calendar_controller.js +340 -123
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +67 -22
- package/dist/controllers/carousel_controller.js +263 -38
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +1 -1
- package/dist/controllers/character_counter_controller.js +40 -2
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.js +81 -12
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +28 -2
- package/dist/controllers/clipboard_controller.js +63 -5
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +25 -1
- package/dist/controllers/collapsible_controller.js +99 -14
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +29 -8
- package/dist/controllers/color_picker_controller.js +80 -34
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +10 -1
- package/dist/controllers/combobox_controller.js +106 -16
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +3 -3
- package/dist/controllers/command_palette_controller.js +35 -3
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.js +85 -17
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.js +3 -0
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +6 -0
- package/dist/controllers/context_menu_controller.js +32 -12
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +30 -1
- package/dist/controllers/countdown_controller.js +129 -26
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +77 -16
- package/dist/controllers/currency_input_controller.js +221 -67
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +63 -18
- package/dist/controllers/data_grid_controller.js +195 -29
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +41 -6
- package/dist/controllers/date_range_picker_controller.js +151 -30
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +10 -3
- package/dist/controllers/dialog_controller.js +35 -8
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.js +22 -4
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +2 -2
- package/dist/controllers/dirty_form_controller.js +46 -13
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.js +1 -0
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +18 -10
- package/dist/controllers/drawer_controller.js +54 -19
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +9 -3
- package/dist/controllers/dropdown_controller.js +36 -9
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.js +34 -0
- package/dist/controllers/editable_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.js +144 -51
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.d.ts +10 -4
- package/dist/controllers/filter_controller.js +20 -6
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +26 -6
- package/dist/controllers/flash_controller.js +432 -71
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.js +1 -0
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +7 -5
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +19 -13
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.js +45 -8
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.js +82 -25
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +10 -2
- package/dist/controllers/hover_card_controller.js +40 -14
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +16 -3
- package/dist/controllers/idle_controller.js +90 -5
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +5 -2
- package/dist/controllers/input_mask_controller.js +65 -9
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.js +3 -0
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.js +11 -2
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +50 -8
- package/dist/controllers/listbox_controller.js +203 -45
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.js +10 -5
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +3 -3
- package/dist/controllers/masonry_controller.js +31 -15
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +9 -3
- package/dist/controllers/menu_controller.js +45 -16
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +11 -0
- package/dist/controllers/menubar_controller.js +58 -24
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.js +9 -5
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +18 -3
- package/dist/controllers/multi_select_controller.js +278 -104
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +11 -0
- package/dist/controllers/navigation_menu_controller.js +48 -15
- package/dist/controllers/navigation_menu_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.js +37 -8
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.js +9 -1
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +36 -8
- package/dist/controllers/number_input_controller.js +124 -21
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.js +42 -5
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +22 -7
- package/dist/controllers/otp_controller.js +198 -55
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +8 -12
- package/dist/controllers/overflow_indicator_controller.js +115 -21
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +26 -6
- package/dist/controllers/overflow_menu_controller.js +141 -44
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +19 -11
- package/dist/controllers/pagination_controller.js +74 -28
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +15 -1
- package/dist/controllers/password_reveal_controller.js +59 -2
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/persist_controller.js +30 -8
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.js +131 -52
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +9 -3
- package/dist/controllers/popover_controller.js +45 -11
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +1 -1
- package/dist/controllers/portal_controller.js +6 -2
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.js +16 -1
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.js +8 -4
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +6 -4
- package/dist/controllers/radio_group_controller.js +42 -17
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +49 -1
- package/dist/controllers/range_slider_controller.js +88 -42
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +14 -3
- package/dist/controllers/rating_controller.js +39 -15
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +24 -2
- package/dist/controllers/read_more_controller.js +100 -7
- package/dist/controllers/read_more_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.js +65 -19
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.js +10 -5
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.d.ts +16 -2
- package/dist/controllers/resizable_controller.js +82 -22
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.js +75 -27
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_restore_controller.js +37 -16
- package/dist/controllers/scroll_restore_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.js +49 -30
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +3 -2
- package/dist/controllers/scrollspy_controller.js +71 -26
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +41 -14
- package/dist/controllers/separator_controller.js +66 -37
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +20 -3
- package/dist/controllers/sidebar_controller.js +77 -18
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.js +6 -1
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +45 -7
- package/dist/controllers/slider_controller.js +82 -47
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.js +60 -26
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.js +17 -2
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.js +10 -2
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +19 -17
- package/dist/controllers/step_indicator_controller.js +18 -17
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +34 -9
- package/dist/controllers/stepper_controller.js +101 -19
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +19 -0
- package/dist/controllers/stick_to_bottom_controller.js +104 -8
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +3 -2
- package/dist/controllers/submit_once_controller.js +45 -9
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +29 -7
- package/dist/controllers/switch_controller.js +101 -10
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +12 -0
- package/dist/controllers/tabs_controller.js +21 -2
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +15 -3
- package/dist/controllers/tags_input_controller.js +209 -59
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.js +29 -3
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +20 -3
- package/dist/controllers/theme_controller.js +64 -14
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.js +23 -8
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +55 -15
- package/dist/controllers/toast_controller.js +451 -105
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +49 -7
- package/dist/controllers/toggle_group_controller.js +159 -23
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +32 -0
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +8 -0
- package/dist/controllers/tooltip_controller.js +39 -13
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.js +4 -0
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +39 -8
- package/dist/controllers/tree_view_controller.js +169 -16
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +5002 -1911
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +72 -6
- package/dist/inspector/cli.js +262 -51
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +309 -51
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +40 -40
- package/dist/inspector/manifest.json +529 -61
- package/dist/positioning/index.js +2 -0
- package/dist/positioning/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -2,6 +2,72 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
2
2
|
|
|
3
3
|
// src/controllers/toast_controller.ts
|
|
4
4
|
|
|
5
|
+
// src/utils/event_owner.ts
|
|
6
|
+
function ownerIndex(candidates, node) {
|
|
7
|
+
if (!(node instanceof Node)) return -1;
|
|
8
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
9
|
+
}
|
|
10
|
+
function ownerOf(candidates, node) {
|
|
11
|
+
return candidates[ownerIndex(candidates, node)] ?? null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/utils/listener_set.ts
|
|
15
|
+
var ListenerSet = class {
|
|
16
|
+
/** The generation every `add` joins until the next `dispose()`. */
|
|
17
|
+
#abort = new AbortController();
|
|
18
|
+
/**
|
|
19
|
+
* Attaches `handler` to the open generation, exactly as the caller spelled it.
|
|
20
|
+
*
|
|
21
|
+
* The set supplies the signal, so `options` carries everything else the DOM
|
|
22
|
+
* accepts — `capture` included, which has to match at release time and no
|
|
23
|
+
* longer has a second place to drift from.
|
|
24
|
+
*/
|
|
25
|
+
add(target, type, handler, options) {
|
|
26
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Releases every listener of the open generation, synchronously, and opens the
|
|
30
|
+
* next one. Idempotent, and safe before anything has been added.
|
|
31
|
+
*/
|
|
32
|
+
dispose() {
|
|
33
|
+
this.#abort.abort();
|
|
34
|
+
this.#abort = new AbortController();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/utils/microtask_coalescer.ts
|
|
39
|
+
var MicrotaskCoalescer = class {
|
|
40
|
+
#run;
|
|
41
|
+
#queued = false;
|
|
42
|
+
#active = false;
|
|
43
|
+
#generation = 0;
|
|
44
|
+
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
45
|
+
constructor(run) {
|
|
46
|
+
this.#run = run;
|
|
47
|
+
}
|
|
48
|
+
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
49
|
+
activate() {
|
|
50
|
+
this.#active = true;
|
|
51
|
+
}
|
|
52
|
+
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
53
|
+
cancel() {
|
|
54
|
+
this.#active = false;
|
|
55
|
+
this.#queued = false;
|
|
56
|
+
this.#generation += 1;
|
|
57
|
+
}
|
|
58
|
+
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
59
|
+
schedule() {
|
|
60
|
+
if (!this.#active || this.#queued) return;
|
|
61
|
+
this.#queued = true;
|
|
62
|
+
const generation = this.#generation;
|
|
63
|
+
queueMicrotask(() => {
|
|
64
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
65
|
+
this.#queued = false;
|
|
66
|
+
this.#run();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
5
71
|
// src/utils/safe_timeout.ts
|
|
6
72
|
var TimerRegistry = class {
|
|
7
73
|
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
@@ -55,6 +121,156 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
55
121
|
}
|
|
56
122
|
};
|
|
57
123
|
|
|
124
|
+
// src/utils/keyed_timers.ts
|
|
125
|
+
var KeyedTimers = class {
|
|
126
|
+
#timers = new SafeTimeout();
|
|
127
|
+
/** The pending timer of each key; an entry lives exactly as long as its timer. */
|
|
128
|
+
#pending = /* @__PURE__ */ new Map();
|
|
129
|
+
/**
|
|
130
|
+
* Arms `callback` after `delay` ms for `key`, cancelling the timer `key` had
|
|
131
|
+
* pending. The entry is dropped before the callback runs, so the callback sees
|
|
132
|
+
* the key unarmed and may arm it again for the next round.
|
|
133
|
+
*/
|
|
134
|
+
set(key, callback, delay) {
|
|
135
|
+
this.clear(key);
|
|
136
|
+
const id = this.#timers.set(() => {
|
|
137
|
+
this.#pending.delete(key);
|
|
138
|
+
callback();
|
|
139
|
+
}, delay);
|
|
140
|
+
this.#pending.set(key, id);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Cancels `key`'s pending timer, if it has one.
|
|
144
|
+
*
|
|
145
|
+
* A timer id is a positive integer, so `-1` stands for "nothing pending" and
|
|
146
|
+
* the registry ignores an id it does not own — the unarmed case needs no
|
|
147
|
+
* branch of its own, and no other key's timer can be reached from here.
|
|
148
|
+
*/
|
|
149
|
+
clear(key) {
|
|
150
|
+
this.#timers.clear(this.#pending.get(key) ?? -1);
|
|
151
|
+
this.#pending.delete(key);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Cancels every pending timer and forgets every key. Call this from a
|
|
155
|
+
* controller's `disconnect()` so no timer, and no entry, outlives the element.
|
|
156
|
+
*/
|
|
157
|
+
clearAll() {
|
|
158
|
+
this.#timers.clearAll();
|
|
159
|
+
this.#pending.clear();
|
|
160
|
+
}
|
|
161
|
+
/** Whether `key` has a timer pending. */
|
|
162
|
+
has(key) {
|
|
163
|
+
return this.#pending.has(key);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/utils/pausable_timers.ts
|
|
168
|
+
var PausableTimers = class {
|
|
169
|
+
#timers = new KeyedTimers();
|
|
170
|
+
/** Every key armed or held; an entry goes as its timer fires, or with the key. */
|
|
171
|
+
#entries = /* @__PURE__ */ new Map();
|
|
172
|
+
/**
|
|
173
|
+
* Arms `callback` after `delay` ms for `key`, replacing whatever `key` had.
|
|
174
|
+
* A held key stays held and banks `delay` for its resume.
|
|
175
|
+
*/
|
|
176
|
+
set(key, callback, delay) {
|
|
177
|
+
const reasons = this.#entries.get(key)?.reasons ?? /* @__PURE__ */ new Set();
|
|
178
|
+
const entry = { callback, startedAt: 0, remaining: delay, reasons };
|
|
179
|
+
this.#entries.set(key, entry);
|
|
180
|
+
if (reasons.size === 0) this.#arm(key, entry, callback);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Holds `key` for `reason` and banks the time left, with a floor of one
|
|
184
|
+
* millisecond. A key with no timer is held all the same. Reports whether this
|
|
185
|
+
* call is the one that stopped a running timer, so the caller can write its
|
|
186
|
+
* held-state hook exactly once.
|
|
187
|
+
*/
|
|
188
|
+
pause(key, reason) {
|
|
189
|
+
let entry = this.#entries.get(key);
|
|
190
|
+
if (entry === void 0) {
|
|
191
|
+
entry = { callback: null, startedAt: 0, remaining: 0, reasons: /* @__PURE__ */ new Set() };
|
|
192
|
+
this.#entries.set(key, entry);
|
|
193
|
+
}
|
|
194
|
+
entry.reasons.add(reason);
|
|
195
|
+
if (!this.#timers.has(key)) return false;
|
|
196
|
+
this.#timers.clear(key);
|
|
197
|
+
entry.remaining = Math.max(1, entry.remaining - (Date.now() - entry.startedAt));
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Releases `reason` on `key`, arming the banked time again once no reason is
|
|
202
|
+
* left; a key released with no timer is forgotten. Reports whether this call
|
|
203
|
+
* is the one that started the timer again.
|
|
204
|
+
*/
|
|
205
|
+
resume(key, reason) {
|
|
206
|
+
const entry = this.#entries.get(key);
|
|
207
|
+
if (entry === void 0) return false;
|
|
208
|
+
entry.reasons.delete(reason);
|
|
209
|
+
if (entry.reasons.size > 0 || this.#timers.has(key)) return false;
|
|
210
|
+
const { callback } = entry;
|
|
211
|
+
if (callback === null) {
|
|
212
|
+
this.#entries.delete(key);
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
this.#arm(key, entry, callback);
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
/** Whether any reason holds `key`, with or without a timer. */
|
|
219
|
+
isHeld(key) {
|
|
220
|
+
return (this.#entries.get(key)?.reasons.size ?? 0) > 0;
|
|
221
|
+
}
|
|
222
|
+
/** Whether `key` is armed or held — that is, whether this registry drives it at all. */
|
|
223
|
+
tracks(key) {
|
|
224
|
+
return this.#entries.has(key);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Cancels `key`'s timer and keeps its hold, so a release arms nothing; a key
|
|
228
|
+
* nothing holds is forgotten.
|
|
229
|
+
*/
|
|
230
|
+
disarm(key) {
|
|
231
|
+
this.#timers.clear(key);
|
|
232
|
+
const entry = this.#entries.get(key);
|
|
233
|
+
if (entry?.reasons.size) entry.callback = null;
|
|
234
|
+
else this.#entries.delete(key);
|
|
235
|
+
}
|
|
236
|
+
/** Cancels `key`'s timer and drops its hold. */
|
|
237
|
+
clear(key) {
|
|
238
|
+
this.#timers.clear(key);
|
|
239
|
+
this.#entries.delete(key);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Cancels every timer and forgets every key. Call this from a controller's
|
|
243
|
+
* `disconnect()` so neither a timer nor a hold outlives the element.
|
|
244
|
+
*/
|
|
245
|
+
clearAll() {
|
|
246
|
+
this.#timers.clearAll();
|
|
247
|
+
this.#entries.clear();
|
|
248
|
+
}
|
|
249
|
+
/** Starts `entry`'s banked time running for `key`, and drops it as `callback` fires. */
|
|
250
|
+
#arm(key, entry, callback) {
|
|
251
|
+
entry.startedAt = Date.now();
|
|
252
|
+
this.#timers.set(
|
|
253
|
+
key,
|
|
254
|
+
() => {
|
|
255
|
+
this.#entries.delete(key);
|
|
256
|
+
callback();
|
|
257
|
+
},
|
|
258
|
+
entry.remaining
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// src/utils/target_selector.ts
|
|
264
|
+
function targetSelector(identifier, name) {
|
|
265
|
+
return `[data-${identifier}-target~="${name}"]`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/utils/template_row.ts
|
|
269
|
+
function cloneTemplateRoot(template) {
|
|
270
|
+
const root = template.content.firstElementChild;
|
|
271
|
+
return root instanceof HTMLElement ? root.cloneNode(true) : null;
|
|
272
|
+
}
|
|
273
|
+
|
|
58
274
|
// src/utils/transition_completion.ts
|
|
59
275
|
function timeMs(value) {
|
|
60
276
|
const trimmed = value.trim();
|
|
@@ -99,43 +315,69 @@ var ToastController = class extends Controller {
|
|
|
99
315
|
};
|
|
100
316
|
static actions = ["dismiss", "onKeydown", "pause", "resume", "show"];
|
|
101
317
|
static events = ["dismiss", "show"];
|
|
102
|
-
/**
|
|
103
|
-
* Registry for every auto-dismiss and transition-finalize timer the controller
|
|
104
|
-
* schedules. `SafeTimeout` owns *registration and teardown only*; the
|
|
105
|
-
* pause/resume remaining-time accounting stays in `#activeTimeouts` so the
|
|
106
|
-
* per-widget WCAG 2.2.1 semantics are not flattened into the helper.
|
|
107
|
-
*/
|
|
318
|
+
/** Removal timers for the leaving transition; the auto-dismiss ones live below. */
|
|
108
319
|
#timers = new SafeTimeout();
|
|
320
|
+
/** Per-toast auto-dismiss, held open while the toast is hovered or focused. */
|
|
321
|
+
#dismiss = new PausableTimers();
|
|
322
|
+
/** Toasts taken on and not let go; a move within the list keeps a toast here. */
|
|
323
|
+
#taken = /* @__PURE__ */ new Set();
|
|
324
|
+
/** Applies the `max` limit again once the event that released a hold has run its course. */
|
|
325
|
+
#reapply = new MicrotaskCoalescer(() => this.#reapplyLimit());
|
|
326
|
+
/** Toasts the pointer or focus moved into since the limit was last applied again. */
|
|
327
|
+
#spared = [];
|
|
328
|
+
/** The next pointer movement, listened for while a hover hold waits on it. */
|
|
329
|
+
#pointer = new ListenerSet();
|
|
330
|
+
/** Toasts whose hover hold waits for the next pointer movement to be confirmed. */
|
|
331
|
+
#awaitingPointer = /* @__PURE__ */ new Set();
|
|
109
332
|
/**
|
|
110
333
|
* Pending one-shot `requestAnimationFrame` handles (the entering→visible flip).
|
|
111
334
|
* Tracked so {@link disconnect} can cancel any that have not fired, preventing a
|
|
112
335
|
* detached element from being mutated after it leaves the DOM (Turbo).
|
|
113
336
|
*/
|
|
114
337
|
#rafHandles = /* @__PURE__ */ new Map();
|
|
115
|
-
/** Track active timeouts mapped by each toast element for safe cancellation. */
|
|
116
|
-
#activeTimeouts = /* @__PURE__ */ new Map();
|
|
117
|
-
/** Track active pause reasons (hover/focus) per toast for WCAG 2.2.1 pause/resume. */
|
|
118
|
-
#pauseReasons = /* @__PURE__ */ new Map();
|
|
119
338
|
/** The stable list that owns delegated listeners for dynamically added items. */
|
|
120
339
|
#delegatedList = null;
|
|
340
|
+
/** Whether the controller is between `connect()` and `disconnect()`. */
|
|
341
|
+
#connected = false;
|
|
342
|
+
/**
|
|
343
|
+
* The `max` the list was last held to, or `null` before the first time. `connect()`
|
|
344
|
+
* applies the limit only when `max` is not that one, so a reconnect of the same instance
|
|
345
|
+
* leaves the list as it was unless `max` changed while it was away.
|
|
346
|
+
*/
|
|
347
|
+
#appliedMax = null;
|
|
348
|
+
/**
|
|
349
|
+
* Stimulus reports every toast the list already holds before this runs, and each is
|
|
350
|
+
* taken on then with its holds, meeting no limit. The limit is applied here once, over
|
|
351
|
+
* all of them, unless the list was already held to the current `max`.
|
|
352
|
+
*/
|
|
121
353
|
connect() {
|
|
354
|
+
this.#connected = true;
|
|
355
|
+
this.#reapply.activate();
|
|
122
356
|
this.#connectDelegatedEvents();
|
|
123
|
-
this.enforceMaxLimit();
|
|
124
357
|
for (const item of this.itemTargets) {
|
|
125
|
-
if (!this.#
|
|
358
|
+
if (!this.#dismiss.tracks(item) && item.dataset.state !== "leaving") {
|
|
126
359
|
this.#startTimer(item);
|
|
127
360
|
}
|
|
128
361
|
}
|
|
362
|
+
if (!Object.is(this.#appliedMax, this.maxValue)) {
|
|
363
|
+
this.#appliedMax = this.maxValue;
|
|
364
|
+
this.#enforceMax();
|
|
365
|
+
}
|
|
129
366
|
}
|
|
130
367
|
disconnect() {
|
|
368
|
+
this.#connected = false;
|
|
131
369
|
this.#disconnectDelegatedEvents();
|
|
132
370
|
this.#timers.clearAll();
|
|
371
|
+
this.#dismiss.clearAll();
|
|
372
|
+
this.#taken.clear();
|
|
373
|
+
this.#reapply.cancel();
|
|
374
|
+
this.#spared = [];
|
|
375
|
+
this.#pointer.dispose();
|
|
376
|
+
this.#awaitingPointer.clear();
|
|
133
377
|
for (const handle of this.#rafHandles.values()) {
|
|
134
378
|
window.cancelAnimationFrame(handle);
|
|
135
379
|
}
|
|
136
380
|
this.#rafHandles.clear();
|
|
137
|
-
this.#activeTimeouts.clear();
|
|
138
|
-
this.#pauseReasons.clear();
|
|
139
381
|
}
|
|
140
382
|
/** Rebinds delegated interaction when Turbo replaces the list target in place. */
|
|
141
383
|
listTargetConnected(element) {
|
|
@@ -148,34 +390,38 @@ var ToastController = class extends Controller {
|
|
|
148
390
|
durationValueChanged() {
|
|
149
391
|
for (const item of this.itemTargets) {
|
|
150
392
|
if (item.dataset.state === "leaving") continue;
|
|
151
|
-
const pauseReasons = this.#pauseReasons.get(item);
|
|
152
|
-
this.#clearTimer(item);
|
|
153
393
|
if (this.durationValue <= 0) {
|
|
394
|
+
this.#dismiss.disarm(item);
|
|
154
395
|
item.removeAttribute("data-paused");
|
|
155
|
-
} else if (pauseReasons && pauseReasons.size > 0) {
|
|
156
|
-
this.#pauseReasons.set(item, pauseReasons);
|
|
157
|
-
this.#activeTimeouts.set(item, {
|
|
158
|
-
id: 0,
|
|
159
|
-
startedAt: 0,
|
|
160
|
-
remaining: this.durationValue
|
|
161
|
-
});
|
|
162
|
-
item.setAttribute("data-paused", "true");
|
|
163
396
|
} else {
|
|
164
397
|
this.#startTimer(item);
|
|
165
398
|
}
|
|
166
399
|
}
|
|
167
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Holds the list to a changed `max`. Stimulus can also call this ahead of `connect()` —
|
|
403
|
+
* for an attribute that changed while the controller was away, and on every connect
|
|
404
|
+
* for an undeclared `max`, with its default; `connect()` decides then.
|
|
405
|
+
*/
|
|
168
406
|
maxValueChanged() {
|
|
407
|
+
if (!this.#connected) return;
|
|
408
|
+
this.#appliedMax = this.maxValue;
|
|
169
409
|
this.enforceMaxLimit();
|
|
170
410
|
}
|
|
171
411
|
/**
|
|
172
412
|
* Stimulus lifecycle callback triggered automatically when a new item target
|
|
173
|
-
* enters the DOM, from a client-side injection or a Turbo Stream append alike
|
|
174
|
-
*
|
|
413
|
+
* enters the DOM, from a client-side injection or a Turbo Stream append alike, and for
|
|
414
|
+
* every toast the list already holds ahead of `connect()`. An item already leaving,
|
|
415
|
+
* parented outside `list`, or already taken on (a move within the list) is skipped; any
|
|
416
|
+
* other is taken on with the holds it already has. On a connected controller the `max`
|
|
417
|
+
* limit is then applied, never to the arrival itself; the toasts reported ahead of
|
|
418
|
+
* `connect()` are left to it.
|
|
175
419
|
*/
|
|
176
420
|
itemTargetConnected(element) {
|
|
177
|
-
this.
|
|
421
|
+
if (this.#taken.has(element)) return;
|
|
178
422
|
if (element.dataset.state === "leaving" || element.parentNode !== this.listTarget) return;
|
|
423
|
+
this.#taken.add(element);
|
|
424
|
+
this.#readHolds(element);
|
|
179
425
|
this.#startTimer(element);
|
|
180
426
|
element.setAttribute("data-state", "entering");
|
|
181
427
|
this.#cancelAnimation(element);
|
|
@@ -185,10 +431,23 @@ var ToastController = class extends Controller {
|
|
|
185
431
|
element.setAttribute("data-state", "visible");
|
|
186
432
|
});
|
|
187
433
|
this.#rafHandles.set(element, handle);
|
|
434
|
+
if (this.#connected) this.#enforceMax([element]);
|
|
188
435
|
}
|
|
189
|
-
/**
|
|
436
|
+
/**
|
|
437
|
+
* Lets go of a toast that left the list or stopped being an item: its timer, holds,
|
|
438
|
+
* `data-paused` and pending frame. After a move within the list the node is already
|
|
439
|
+
* back, still an item, when Stimulus reports it, so it keeps all of those, and only the
|
|
440
|
+
* holds the move may have ended are read again.
|
|
441
|
+
*/
|
|
190
442
|
itemTargetDisconnected(element) {
|
|
191
|
-
this.#
|
|
443
|
+
const moved = this.#isShown(element) && element.matches(targetSelector(this.identifier, "item"));
|
|
444
|
+
if (moved) {
|
|
445
|
+
this.#afterMove(element);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
this.#taken.delete(element);
|
|
449
|
+
element.removeAttribute("data-paused");
|
|
450
|
+
this.#letGo(element);
|
|
192
451
|
this.#cancelAnimation(element);
|
|
193
452
|
}
|
|
194
453
|
/**
|
|
@@ -197,24 +456,31 @@ var ToastController = class extends Controller {
|
|
|
197
456
|
* (remote / Turbo trigger); the action param wins when both are present.
|
|
198
457
|
*
|
|
199
458
|
* <button data-action="click->stimeo--toast#show"
|
|
200
|
-
* data-stimeo--toast-
|
|
459
|
+
* data-stimeo--toast-message-param="Saved"
|
|
201
460
|
* data-stimeo--toast-type-param="status">Show</button>
|
|
202
461
|
*
|
|
203
|
-
* element.dispatchEvent(new CustomEvent("show", { detail: {
|
|
462
|
+
* element.dispatchEvent(new CustomEvent("show", { detail: { message, type } }))
|
|
204
463
|
*
|
|
205
|
-
* Clones the template slot, interpolates the
|
|
464
|
+
* Clones the template slot, interpolates the message, and appends to the list.
|
|
465
|
+
* `message` is the wording key the rest of the library dispatches under, so a
|
|
466
|
+
* part that already carries its own wording — `stimeo--clipboard:copy`,
|
|
467
|
+
* `stimeo--auto-submit:done` — wires straight into this action.
|
|
206
468
|
*/
|
|
207
469
|
show(event) {
|
|
208
470
|
if (!this.hasTemplateTarget || !this.hasListTarget) return;
|
|
209
|
-
const
|
|
210
|
-
if (!
|
|
211
|
-
const
|
|
212
|
-
const item =
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
471
|
+
const message = this.#readField(event, "message");
|
|
472
|
+
if (!message) return;
|
|
473
|
+
const template = this.templateTarget;
|
|
474
|
+
const item = cloneTemplateRoot(template);
|
|
475
|
+
if (template.content.children.length !== 1) return;
|
|
476
|
+
if (!item?.matches(targetSelector(this.identifier, "item"))) return;
|
|
477
|
+
const messageSlot = item.querySelector("[data-toast-slot='message']");
|
|
478
|
+
if (!messageSlot) return;
|
|
479
|
+
messageSlot.textContent = message;
|
|
480
|
+
messageSlot.setAttribute(
|
|
481
|
+
"role",
|
|
482
|
+
this.#readField(event, "type") === "alert" ? "alert" : "status"
|
|
483
|
+
);
|
|
218
484
|
this.listTarget.appendChild(item);
|
|
219
485
|
this.dispatch("show", { detail: { item } });
|
|
220
486
|
}
|
|
@@ -256,81 +522,139 @@ var ToastController = class extends Controller {
|
|
|
256
522
|
* Hover and focus are tracked as independent reasons: the timer is only
|
|
257
523
|
* snapshotted on the first active reason, and only resumed once *every*
|
|
258
524
|
* reason has been released (see {@link resume}). This keeps a toast paused
|
|
259
|
-
* while it is still hovered *or* focused, per WCAG 2.2.1.
|
|
525
|
+
* while it is still hovered *or* focused, per WCAG 2.2.1. The hold is recorded
|
|
526
|
+
* whether or not a timer runs, so the `max` limit passes over the toast either
|
|
527
|
+
* way; `data-paused` marks a timer that stopped, and only that.
|
|
260
528
|
*/
|
|
261
529
|
pause(event) {
|
|
262
530
|
const item = this.#itemFromEvent(event);
|
|
263
|
-
if (
|
|
264
|
-
|
|
265
|
-
if (!timeout) return;
|
|
266
|
-
const reasons = this.#pauseReasonsFor(item);
|
|
267
|
-
const wasActive = reasons.size > 0;
|
|
268
|
-
reasons.add(this.#pauseReason(event));
|
|
269
|
-
if (wasActive || timeout.id === 0) return;
|
|
270
|
-
this.#timers.clear(timeout.id);
|
|
271
|
-
const elapsed = Date.now() - timeout.startedAt;
|
|
272
|
-
const remaining = Math.max(0, timeout.remaining - elapsed);
|
|
273
|
-
if (remaining <= 0) {
|
|
274
|
-
this.#removeWithTransition(item, "timeout");
|
|
275
|
-
return;
|
|
531
|
+
if (item && this.#dismiss.pause(item, this.#pauseReason(event))) {
|
|
532
|
+
item.setAttribute("data-paused", "true");
|
|
276
533
|
}
|
|
277
|
-
this.#activeTimeouts.set(item, { id: 0, startedAt: 0, remaining });
|
|
278
|
-
item.setAttribute("data-paused", "true");
|
|
279
534
|
}
|
|
280
|
-
/**
|
|
535
|
+
/**
|
|
536
|
+
* Resumes the auto-dismiss timer once both hover and focus have been released,
|
|
537
|
+
* and then applies the `max` limit again, sparing the toast the pointer or focus is
|
|
538
|
+
* moving into: its own hold only arrives after this event.
|
|
539
|
+
*/
|
|
281
540
|
resume(event) {
|
|
282
541
|
const item = this.#itemFromEvent(event);
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
542
|
+
if (item) {
|
|
543
|
+
this.#release(
|
|
544
|
+
item,
|
|
545
|
+
this.#pauseReason(event),
|
|
546
|
+
ownerOf(this.itemTargets, event.relatedTarget)
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/** Releases one reason on `item`; releasing the last one applies the limit again. */
|
|
551
|
+
#release(item, reason, spare) {
|
|
552
|
+
const held = this.#dismiss.isHeld(item);
|
|
553
|
+
if (this.#dismiss.resume(item, reason)) item.removeAttribute("data-paused");
|
|
554
|
+
if (held && !this.#dismiss.isHeld(item)) this.#limitLater(spare);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Applies the limit again once the current event is over, sparing `spare`. A hold can
|
|
558
|
+
* end in a `focusout` the engine fires while it is itself taking the node out for a
|
|
559
|
+
* script's move or removal, and taking the toast away inside that event would pull
|
|
560
|
+
* the node from under the caller's own call.
|
|
561
|
+
*/
|
|
562
|
+
#limitLater(spare) {
|
|
563
|
+
if (spare) this.#spared.push(spare);
|
|
564
|
+
this.#reapply.schedule();
|
|
565
|
+
}
|
|
566
|
+
/** Applies the limit again, sparing every toast gathered since the last pass. */
|
|
567
|
+
#reapplyLimit() {
|
|
568
|
+
const spared = this.#spared;
|
|
569
|
+
this.#spared = [];
|
|
570
|
+
this.#enforceMax(spared);
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Reads the holds of a toast that moved within the list. Focus is read at once: an
|
|
574
|
+
* ordinary move ends it with a `focusout` and `moveBefore` keeps it. Where the pointer
|
|
575
|
+
* is, nothing says until it moves again: no `mouseout` reaches a moved node, and
|
|
576
|
+
* `:hover` can answer from before the move. A toast still held waits for that movement.
|
|
577
|
+
*/
|
|
578
|
+
#afterMove(item) {
|
|
579
|
+
if (!item.contains(document.activeElement)) this.#release(item, "focus", null);
|
|
580
|
+
if (this.#dismiss.isHeld(item)) this.#awaitPointer(item);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Takes the holds a toast already has as it is taken on: focus inside it, and the pointer
|
|
584
|
+
* over it by `:hover`. That answer can date from before the toast came here, so a hover
|
|
585
|
+
* hold taken on it waits for the next pointer movement to be confirmed.
|
|
586
|
+
*/
|
|
587
|
+
#readHolds(item) {
|
|
588
|
+
if (item.contains(document.activeElement)) this.#dismiss.pause(item, "focus");
|
|
589
|
+
if (!item.matches(":hover")) return;
|
|
590
|
+
this.#dismiss.pause(item, "hover");
|
|
591
|
+
this.#awaitPointer(item);
|
|
592
|
+
}
|
|
593
|
+
/** Leaves `item`'s hover hold to be confirmed or let go on the next pointer movement. */
|
|
594
|
+
#awaitPointer(item) {
|
|
595
|
+
this.#awaitingPointer.add(item);
|
|
596
|
+
this.#pointer.add(document, "pointermove", this.#onPointerMove, {
|
|
597
|
+
capture: true,
|
|
598
|
+
passive: true
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Reads `:hover` once the pointer has moved, when the answer is current, and lets go of
|
|
603
|
+
* the hover hold of every waiting toast the pointer is not over. Listens once.
|
|
604
|
+
*/
|
|
605
|
+
#onPointerMove = () => {
|
|
606
|
+
this.#pointer.dispose();
|
|
607
|
+
const waiting = [...this.#awaitingPointer];
|
|
608
|
+
this.#awaitingPointer.clear();
|
|
609
|
+
for (const item of waiting) {
|
|
610
|
+
if (!item.matches(":hover")) this.#release(item, "hover", null);
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
/**
|
|
614
|
+
* Drops a toast's timer and holds as it leaves, and stops waiting on the pointer for it.
|
|
615
|
+
* A toast something still held counts as released: the limit is applied again.
|
|
616
|
+
*/
|
|
617
|
+
#letGo(item) {
|
|
618
|
+
if (this.#dismiss.isHeld(item)) this.#limitLater(null);
|
|
619
|
+
this.#dismiss.clear(item);
|
|
620
|
+
this.#awaitingPointer.delete(item);
|
|
621
|
+
if (this.#awaitingPointer.size === 0) this.#pointer.dispose();
|
|
292
622
|
}
|
|
293
623
|
/** Resolves the toast item element a pause/resume event targets. */
|
|
294
624
|
#itemFromEvent(event) {
|
|
295
625
|
const target = event.target instanceof Element ? event.target : event.currentTarget;
|
|
296
626
|
if (!(target instanceof Element) || !this.hasListTarget) return null;
|
|
297
|
-
const item = target.closest("
|
|
627
|
+
const item = target.closest(targetSelector(this.identifier, "item"));
|
|
298
628
|
return item && this.listTarget.contains(item) ? item : null;
|
|
299
629
|
}
|
|
300
630
|
/** Classifies a pause/resume event as a hover or focus reason. */
|
|
301
631
|
#pauseReason(event) {
|
|
302
632
|
return event.type === "focusin" || event.type === "focusout" ? "focus" : "hover";
|
|
303
633
|
}
|
|
304
|
-
/**
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
this.#activeTimeouts.set(element, { id, startedAt: Date.now(), remaining: duration });
|
|
322
|
-
}
|
|
323
|
-
#clearTimer(element) {
|
|
324
|
-
const timeout = this.#activeTimeouts.get(element);
|
|
325
|
-
if (timeout) {
|
|
326
|
-
if (timeout.id) this.#timers.clear(timeout.id);
|
|
327
|
-
this.#activeTimeouts.delete(element);
|
|
634
|
+
/**
|
|
635
|
+
* Arms a toast's auto-dismiss; a non-positive `duration` means it never expires.
|
|
636
|
+
* On a toast hover or focus already holds, the timer waits for the release.
|
|
637
|
+
* `data-paused` is written from that state, so a mark the toast carried in with it,
|
|
638
|
+
* from a restored page, does not outlive it.
|
|
639
|
+
*
|
|
640
|
+
* @stimeoRuntimeOnly `duration` is the delay of the one dismissal timer this call arms.
|
|
641
|
+
*/
|
|
642
|
+
#startTimer(element) {
|
|
643
|
+
if (element.dataset.state === "leaving" || element.parentNode !== this.listTarget) return;
|
|
644
|
+
const armed = this.durationValue > 0;
|
|
645
|
+
if (armed) {
|
|
646
|
+
this.#dismiss.set(
|
|
647
|
+
element,
|
|
648
|
+
() => this.#removeWithTransition(element, "timeout"),
|
|
649
|
+
this.durationValue
|
|
650
|
+
);
|
|
328
651
|
}
|
|
329
|
-
this.#
|
|
652
|
+
if (armed && this.#dismiss.isHeld(element)) element.setAttribute("data-paused", "true");
|
|
653
|
+
else element.removeAttribute("data-paused");
|
|
330
654
|
}
|
|
331
655
|
#removeWithTransition(element, reason) {
|
|
332
656
|
if (element.dataset.state === "leaving" || element.parentNode !== this.listTarget) return;
|
|
333
|
-
this.#
|
|
657
|
+
this.#letGo(element);
|
|
334
658
|
this.#cancelAnimation(element);
|
|
335
659
|
element.setAttribute("data-state", "leaving");
|
|
336
660
|
const finalize = () => {
|
|
@@ -346,24 +670,46 @@ var ToastController = class extends Controller {
|
|
|
346
670
|
}
|
|
347
671
|
}
|
|
348
672
|
/**
|
|
349
|
-
* Removes the oldest toasts
|
|
673
|
+
* Removes the oldest toasts while the list shows more than `maxValue`, passing
|
|
674
|
+
* over every toast hover or focus holds.
|
|
350
675
|
*
|
|
351
676
|
* Public (not `#private`) as a deterministic seam: enforcement normally runs
|
|
352
|
-
*
|
|
353
|
-
* MutationObserver, which a DOM-only environment does not reliably fire —
|
|
354
|
-
* it can also be invoked directly. It is not a
|
|
677
|
+
* as each toast is taken on in `itemTargetConnected`, a Stimulus callback delivered
|
|
678
|
+
* through a MutationObserver, which a DOM-only environment does not reliably fire —
|
|
679
|
+
* so it can also be invoked directly. It counts only the toasts taken on. It is not a
|
|
680
|
+
* user-wired action.
|
|
355
681
|
*/
|
|
356
682
|
enforceMaxLimit() {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
683
|
+
this.#enforceMax();
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Removes the oldest toasts with reason `limit` while the list shows more than `max`
|
|
687
|
+
* (`0` or less, or not a finite number, means no limit), passing over every toast hover
|
|
688
|
+
* or focus holds and those in `spared` — the toast just taken on, or the ones the
|
|
689
|
+
* pointer or focus moved into. Only toasts taken on count or go, so one Stimulus has
|
|
690
|
+
* yet to report is neither; a toast already leaving does not count. With nothing else
|
|
691
|
+
* left to go the list stays over the limit. The count is read again on every step,
|
|
692
|
+
* because a `dismiss` listener may already have changed it.
|
|
693
|
+
*
|
|
694
|
+
* @stimeoRenderRoot
|
|
695
|
+
*/
|
|
696
|
+
#enforceMax(spared = []) {
|
|
697
|
+
if (!Number.isFinite(this.maxValue) || this.maxValue <= 0) return;
|
|
698
|
+
for (const item of this.itemTargets) {
|
|
699
|
+
if (this.#shownCount() <= this.maxValue) return;
|
|
700
|
+
if (this.#isShown(item) && !spared.includes(item) && !this.#dismiss.isHeld(item)) {
|
|
701
|
+
this.#removeWithTransition(item, "limit");
|
|
364
702
|
}
|
|
365
703
|
}
|
|
366
704
|
}
|
|
705
|
+
/** How many toasts the list shows: the ones taken on that are still shown. */
|
|
706
|
+
#shownCount() {
|
|
707
|
+
return this.itemTargets.filter((item) => this.#isShown(item)).length;
|
|
708
|
+
}
|
|
709
|
+
/** Whether the list shows `item`: a toast taken on, a child of `list`, not leaving. */
|
|
710
|
+
#isShown(item) {
|
|
711
|
+
return this.#taken.has(item) && this.hasListTarget && item.parentNode === this.listTarget && item.dataset.state !== "leaving";
|
|
712
|
+
}
|
|
367
713
|
/** Wires stable-container delegation so newly appended items work immediately. */
|
|
368
714
|
#connectDelegatedEvents(list = this.hasListTarget ? this.listTarget : null) {
|
|
369
715
|
this.#disconnectDelegatedEvents();
|