stimeo-ui 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +8 -1
- package/dist/cable/index.d.ts +79 -23
- package/dist/cable/index.js +363 -436
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +4 -2
- package/dist/controllers/accordion_controller.js +166 -55
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +12 -2
- package/dist/controllers/alert_dialog_controller.js +862 -221
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +35 -5
- package/dist/controllers/announcer_controller.js +135 -198
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.js +0 -10
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.d.ts +30 -6
- package/dist/controllers/auto_submit_controller.js +271 -127
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +171 -105
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.js +0 -130
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +18 -0
- package/dist/controllers/bulk_select_controller.js +262 -52
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +24 -5
- package/dist/controllers/calendar_controller.js +175 -209
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +34 -7
- package/dist/controllers/carousel_controller.js +461 -399
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +17 -1
- package/dist/controllers/character_counter_controller.js +246 -156
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +7 -2
- package/dist/controllers/checkbox_controller.js +32 -74
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +16 -5
- package/dist/controllers/clipboard_controller.js +168 -147
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +16 -4
- package/dist/controllers/collapsible_controller.js +301 -120
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +18 -6
- package/dist/controllers/color_picker_controller.js +149 -139
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +23 -5
- package/dist/controllers/combobox_controller.js +360 -133
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +45 -14
- package/dist/controllers/command_palette_controller.js +970 -358
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.d.ts +15 -2
- package/dist/controllers/conditional_fields_controller.js +166 -124
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.d.ts +49 -12
- package/dist/controllers/confirm_controller.js +939 -248
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +31 -4
- package/dist/controllers/context_menu_controller.js +256 -128
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.d.ts +13 -1
- package/dist/controllers/count_up_controller.js +73 -21
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +45 -1
- package/dist/controllers/countdown_controller.js +183 -148
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +21 -2
- package/dist/controllers/currency_input_controller.js +134 -158
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +15 -4
- package/dist/controllers/data_grid_controller.js +114 -186
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +30 -5
- package/dist/controllers/date_range_picker_controller.js +288 -206
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +13 -1
- package/dist/controllers/dialog_controller.js +858 -223
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +24 -7
- package/dist/controllers/direct_upload_controller.js +73 -153
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.js +14 -48
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +19 -0
- package/dist/controllers/dismissible_controller.js +175 -21
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +39 -11
- package/dist/controllers/drawer_controller.js +933 -341
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +31 -1
- package/dist/controllers/dropdown_controller.js +312 -82
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.d.ts +12 -1
- package/dist/controllers/editable_controller.js +158 -68
- package/dist/controllers/editable_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.js +67 -53
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +60 -18
- package/dist/controllers/file_dropzone_controller.js +311 -207
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.js +67 -39
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +31 -8
- package/dist/controllers/flash_controller.js +172 -314
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +12 -3
- package/dist/controllers/focus_controller.js +654 -240
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +147 -132
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +13 -86
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +41 -9
- package/dist/controllers/frame_loading_controller.js +264 -213
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +22 -2
- package/dist/controllers/highlight_controller.js +107 -75
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +56 -3
- package/dist/controllers/hover_card_controller.js +309 -118
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +42 -1
- package/dist/controllers/idle_controller.js +267 -101
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.js +71 -72
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.d.ts +32 -13
- package/dist/controllers/intersection_controller.js +147 -106
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +16 -0
- package/dist/controllers/lazy_frame_controller.js +9 -65
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +34 -2
- package/dist/controllers/listbox_controller.js +306 -204
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +1 -1
- package/dist/controllers/local_time_controller.js +54 -64
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +11 -3
- package/dist/controllers/masonry_controller.js +70 -93
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +32 -7
- package/dist/controllers/menu_controller.js +214 -156
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +4 -4
- package/dist/controllers/menubar_controller.js +170 -329
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +20 -0
- package/dist/controllers/meter_controller.js +151 -67
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +32 -4
- package/dist/controllers/multi_select_controller.js +593 -358
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +8 -1
- package/dist/controllers/navigation_menu_controller.js +124 -232
- package/dist/controllers/navigation_menu_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.d.ts +10 -0
- package/dist/controllers/nested_form_controller.js +352 -182
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +23 -0
- package/dist/controllers/network_status_controller.js +242 -54
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +26 -2
- package/dist/controllers/number_input_controller.js +320 -263
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.js +16 -76
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +20 -4
- package/dist/controllers/otp_controller.js +198 -193
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
- package/dist/controllers/overflow_indicator_controller.js +116 -113
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +48 -11
- package/dist/controllers/overflow_menu_controller.js +343 -283
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +35 -6
- package/dist/controllers/pagination_controller.js +295 -119
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +45 -8
- package/dist/controllers/password_reveal_controller.js +389 -121
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +14 -5
- package/dist/controllers/password_strength_controller.js +99 -153
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +11 -4
- package/dist/controllers/persist_controller.js +237 -103
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +19 -7
- package/dist/controllers/pointer_drag_controller.js +185 -165
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +38 -2
- package/dist/controllers/popover_controller.js +236 -97
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +44 -4
- package/dist/controllers/portal_controller.js +209 -76
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.d.ts +10 -4
- package/dist/controllers/preview_guard_controller.js +232 -116
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +11 -0
- package/dist/controllers/progress_controller.js +125 -56
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +14 -5
- package/dist/controllers/radio_group_controller.js +117 -137
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +43 -3
- package/dist/controllers/range_slider_controller.js +192 -105
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +20 -4
- package/dist/controllers/rating_controller.js +418 -207
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +2 -0
- package/dist/controllers/read_more_controller.js +134 -63
- package/dist/controllers/read_more_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.d.ts +1 -1
- package/dist/controllers/reading_progress_controller.js +147 -135
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.d.ts +18 -4
- package/dist/controllers/relative_time_controller.js +145 -110
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
- package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
- package/dist/controllers/reset_on_restore_controller.js.map +1 -0
- package/dist/controllers/resizable_controller.d.ts +28 -2
- package/dist/controllers/resizable_controller.js +255 -139
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.js +39 -84
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +7 -3
- package/dist/controllers/scroll_area_controller.js +428 -225
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_restore_controller.js +0 -53
- package/dist/controllers/scroll_restore_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
- package/dist/controllers/scroll_visibility_controller.js +395 -145
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +6 -1
- package/dist/controllers/scrollspy_controller.js +181 -242
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +19 -4
- package/dist/controllers/separator_controller.js +267 -140
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +64 -7
- package/dist/controllers/sidebar_controller.js +1023 -386
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +23 -0
- package/dist/controllers/skeleton_controller.js +248 -97
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +27 -1
- package/dist/controllers/slider_controller.js +159 -79
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
- package/dist/controllers/smart_sticky_header_controller.js +162 -63
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.js +0 -112
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +39 -4
- package/dist/controllers/spinner_controller.js +277 -169
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +5 -0
- package/dist/controllers/step_indicator_controller.js +99 -46
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +12 -3
- package/dist/controllers/stepper_controller.js +114 -73
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +6 -0
- package/dist/controllers/stick_to_bottom_controller.js +262 -138
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +11 -0
- package/dist/controllers/sticky_observer_controller.js +214 -49
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +23 -8
- package/dist/controllers/submit_once_controller.js +294 -205
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +12 -1
- package/dist/controllers/switch_controller.js +33 -60
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +2 -2
- package/dist/controllers/tabs_controller.js +35 -29
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +12 -0
- package/dist/controllers/tags_input_controller.js +136 -145
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
- package/dist/controllers/textarea_autosize_controller.js +71 -58
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +21 -9
- package/dist/controllers/theme_controller.js +130 -112
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +18 -1
- package/dist/controllers/time_picker_controller.js +146 -80
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +36 -11
- package/dist/controllers/toast_controller.js +186 -297
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +12 -3
- package/dist/controllers/toggle_group_controller.js +95 -136
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +68 -98
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +45 -3
- package/dist/controllers/tooltip_controller.js +158 -124
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +22 -4
- package/dist/controllers/transition_controller.js +123 -119
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +21 -11
- package/dist/controllers/tree_view_controller.js +150 -345
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +6370 -9346
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +23 -3
- package/dist/inspector/cli.js +92 -37
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +92 -56
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +8 -8
- package/dist/inspector/manifest.json +787 -100
- package/dist/positioning/index.d.ts +8 -0
- package/dist/positioning/index.js +148 -47
- package/dist/positioning/index.js.map +1 -1
- package/package.json +6 -6
- package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
- package/dist/controllers/reset_before_cache_controller.js.map +0 -1
|
@@ -1,6 +1,69 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
var ListenerSet = class {
|
|
4
|
+
#abort = new AbortController();
|
|
5
|
+
add(target, type, handler, options) {
|
|
6
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
7
|
+
}
|
|
8
|
+
dispose() {
|
|
9
|
+
this.#abort.abort();
|
|
10
|
+
this.#abort = new AbortController();
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
var MicrotaskCoalescer = class {
|
|
15
|
+
#run;
|
|
16
|
+
#queued = false;
|
|
17
|
+
#active = false;
|
|
18
|
+
#generation = 0;
|
|
19
|
+
constructor(run) {
|
|
20
|
+
this.#run = run;
|
|
21
|
+
}
|
|
22
|
+
activate() {
|
|
23
|
+
this.#active = true;
|
|
24
|
+
}
|
|
25
|
+
cancel() {
|
|
26
|
+
this.#active = false;
|
|
27
|
+
this.#queued = false;
|
|
28
|
+
this.#generation += 1;
|
|
29
|
+
}
|
|
30
|
+
schedule() {
|
|
31
|
+
if (!this.#active || this.#queued) return;
|
|
32
|
+
this.#queued = true;
|
|
33
|
+
const generation = this.#generation;
|
|
34
|
+
queueMicrotask(() => {
|
|
35
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
36
|
+
this.#queued = false;
|
|
37
|
+
this.#run();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
var MorphRenderWatcher = class {
|
|
43
|
+
#listeners = new ListenerSet();
|
|
44
|
+
#pass;
|
|
45
|
+
#element = null;
|
|
46
|
+
#onMorph = () => this.#pass.schedule();
|
|
47
|
+
constructor(run) {
|
|
48
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
49
|
+
}
|
|
50
|
+
observe(element) {
|
|
51
|
+
if (this.#element === element) return;
|
|
52
|
+
this.disconnect();
|
|
53
|
+
this.#element = element;
|
|
54
|
+
this.#pass.activate();
|
|
55
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
56
|
+
}
|
|
57
|
+
schedule() {
|
|
58
|
+
this.#pass.schedule();
|
|
59
|
+
}
|
|
60
|
+
disconnect() {
|
|
61
|
+
this.#listeners.dispose();
|
|
62
|
+
this.#pass.cancel();
|
|
63
|
+
this.#element = null;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
4
67
|
var FilterController = class extends Controller {
|
|
5
68
|
static targets = ["item", "control", "group", "empty"];
|
|
6
69
|
static values = { match: { type: String, default: "all" } };
|
|
@@ -9,55 +72,28 @@ var FilterController = class extends Controller {
|
|
|
9
72
|
#onChange = () => {
|
|
10
73
|
this.apply();
|
|
11
74
|
};
|
|
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
75
|
#reported = null;
|
|
18
|
-
/**
|
|
19
|
-
* Gates the declaration callback to the connected window.
|
|
20
|
-
*
|
|
21
|
-
* Stimulus delivers a Value callback ahead of `connect()` and again for every
|
|
22
|
-
* runtime change; without the gate, merely connecting would emit an evaluation
|
|
23
|
-
* — and its `change` — before `connect()` runs its own.
|
|
24
|
-
*/
|
|
25
76
|
#connected = false;
|
|
77
|
+
#morphRender = new MorphRenderWatcher(() => this.#evaluate());
|
|
26
78
|
connect() {
|
|
79
|
+
this.#morphRender.observe(this.element);
|
|
27
80
|
this.#reported = null;
|
|
28
81
|
this.#evaluate();
|
|
29
82
|
this.element.addEventListener("change", this.#onChange);
|
|
30
83
|
this.#connected = true;
|
|
31
84
|
}
|
|
32
85
|
disconnect() {
|
|
86
|
+
this.#morphRender.disconnect();
|
|
33
87
|
this.#connected = false;
|
|
34
88
|
this.element.removeEventListener("change", this.#onChange);
|
|
35
89
|
}
|
|
36
|
-
/**
|
|
37
|
-
* Re-evaluates when the match declaration changes at runtime.
|
|
38
|
-
*
|
|
39
|
-
* The declaration decides which items are shown, so an element kept across a
|
|
40
|
-
* morph — where `connect()` does not run again — still has to follow it instead
|
|
41
|
-
* of waiting for the next control interaction. The evaluation is the ordinary
|
|
42
|
-
* one, `change` included: a declaration swap is an evaluation like any other.
|
|
43
|
-
*/
|
|
44
90
|
matchValueChanged() {
|
|
45
91
|
if (!this.#connected) return;
|
|
46
92
|
this.#evaluate();
|
|
47
93
|
}
|
|
48
|
-
/** Re-derives every item's visibility from the active tokens and syncs groups/empty. */
|
|
49
94
|
apply() {
|
|
50
95
|
this.#evaluate();
|
|
51
96
|
}
|
|
52
|
-
/**
|
|
53
|
-
* The evaluation itself: every item's visibility from the active tokens, 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.
|
|
58
|
-
*
|
|
59
|
-
* @stimeoRenderRoot
|
|
60
|
-
*/
|
|
61
97
|
#evaluate() {
|
|
62
98
|
const active = this.#activeTokens();
|
|
63
99
|
let visibleCount = 0;
|
|
@@ -80,7 +116,6 @@ var FilterController = class extends Controller {
|
|
|
80
116
|
this.#reported = { active, visibleCount, total };
|
|
81
117
|
this.dispatch("change", { detail: { active: [...active], visibleCount, total } });
|
|
82
118
|
}
|
|
83
|
-
/** Turns every control off (uncheck / aria-pressed="false") and re-applies. */
|
|
84
119
|
clear() {
|
|
85
120
|
for (const control of this.controlTargets) {
|
|
86
121
|
if (control instanceof HTMLInputElement) {
|
|
@@ -91,7 +126,6 @@ var FilterController = class extends Controller {
|
|
|
91
126
|
}
|
|
92
127
|
this.apply();
|
|
93
128
|
}
|
|
94
|
-
/** The tokens of every control currently "on" (checked or aria-pressed="true"). */
|
|
95
129
|
#activeTokens() {
|
|
96
130
|
const tokens = [];
|
|
97
131
|
for (const control of this.controlTargets) {
|
|
@@ -99,33 +133,27 @@ var FilterController = class extends Controller {
|
|
|
99
133
|
}
|
|
100
134
|
return tokens.filter(Boolean);
|
|
101
135
|
}
|
|
102
|
-
/** Whether a control is in its "on" state. */
|
|
103
136
|
#isOn(control) {
|
|
104
137
|
if (control instanceof HTMLInputElement) return control.checked;
|
|
105
138
|
return control.getAttribute("aria-pressed") === "true";
|
|
106
139
|
}
|
|
107
|
-
/** A control's token: explicit `data-stimeo--filter-token`, else `data-value` / value. */
|
|
108
140
|
#tokenOf(control) {
|
|
109
141
|
const explicit = control.getAttribute(`data-${this.identifier}-token`) ?? control.dataset.value;
|
|
110
142
|
if (explicit) return explicit;
|
|
111
143
|
return control instanceof HTMLInputElement ? control.value : "";
|
|
112
144
|
}
|
|
113
|
-
/** Whether an item satisfies the active token set per `match` (empty active → shown). */
|
|
114
145
|
#matches(item, active) {
|
|
115
146
|
if (active.length === 0) return true;
|
|
116
147
|
const tokens = this.#tokensOf(item);
|
|
117
148
|
return this.matchValue === "any" ? active.some((token) => tokens.includes(token)) : active.every((token) => tokens.includes(token));
|
|
118
149
|
}
|
|
119
|
-
/** An item's declared tokens (`data-stimeo--filter-tokens`, space-separated). */
|
|
120
150
|
#tokensOf(item) {
|
|
121
151
|
return (item.getAttribute(`data-${this.identifier}-tokens`) ?? "").split(/\s+/).filter(Boolean);
|
|
122
152
|
}
|
|
123
|
-
/** Whether a group still contains at least one non-hidden item. */
|
|
124
153
|
#hasVisibleItem(group) {
|
|
125
154
|
return this.itemTargets.some((item) => !item.hidden && group.contains(item));
|
|
126
155
|
}
|
|
127
156
|
};
|
|
128
157
|
|
|
129
158
|
export { FilterController };
|
|
130
|
-
//# sourceMappingURL=filter_controller.js.map
|
|
131
159
|
//# sourceMappingURL=filter_controller.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/controllers/filter_controller.ts"],"names":[],"mappings":"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","file":"filter_controller.js","sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\n\n/** A control whose \"on\" state contributes its token to the active filter set. */\ntype FilterControl = HTMLElement;\n\n/** What an evaluation settles on and `change` reports. */\ninterface FilterOutcome {\n readonly active: readonly string[];\n readonly visibleCount: number;\n readonly total: number;\n}\n\n/**\n * Headless faceted-filter behavior: shows or hides a collection of items based on the\n * set of currently-active facet tokens, decoupled from how those tokens are toggled\n * (native checkboxes/radios, or button toggles such as `stimeo--toggle-group`).\n *\n * Markup contract (identifier: `stimeo--filter`):\n * <div data-controller=\"stimeo--filter\" data-stimeo--filter-match-value=\"all\">\n * <!-- controls: each contributes its token while \"on\" (aria-pressed / checked) -->\n * <button type=\"button\" aria-pressed=\"false\" data-value=\"apg:dialog\"\n * data-stimeo--filter-target=\"control\">Dialog</button>\n * <!-- items: filtered in/out by their space-separated tokens -->\n * <li data-stimeo--filter-target=\"item\"\n * data-stimeo--filter-tokens=\"apg:dialog overlay\">…</li>\n * <!-- optional: a container hidden once it holds no visible item -->\n * <section data-stimeo--filter-target=\"group\">…</section>\n * <!-- optional: an element revealed when nothing matches -->\n * <p data-stimeo--filter-target=\"empty\" hidden>No matches</p>\n * </div>\n *\n * `match` decides how multiple active tokens combine: `all` (default — an item must\n * carry every active token) or `any` (at least one). With no active token every item\n * is shown. Items are toggled via `hidden`; groups with zero visible items and the\n * `empty` element are kept in sync, and `stimeo--filter:change` reports the outcome\n * when it moves.\n *\n * `change` dispatches `{ active, visibleCount, total }` — the active tokens, how\n * many items they leave visible, and how many items there are — whenever an\n * evaluation lands on an outcome other than the one it last reported. The first\n * evaluation after `connect()` always reports.\n *\n * @remarks\n * Behavior only — the consumer owns all styling and the controls' own accessible\n * state (e.g. `aria-pressed`); the one exception is `clear`, which writes those\n * controls one way, to off. Visibility is re-derived from the live DOM on\n * `connect()`, on every native `change` that bubbles to the root, on the `apply`\n * and `clear` actions, and when `match` changes at runtime — the declaration\n * decides what is shown, so an element kept across a morph still follows it.\n * Button toggles that emit no native `change` wire their events to the `apply`\n * action: `stimeo--toggle-group:change->stimeo--filter#apply` for a press, and\n * `stimeo--toggle-group:reconcile->stimeo--filter#apply` for a pressed set the\n * page moves.\n */\nexport class FilterController extends Controller<HTMLElement> {\n static override targets = [\"item\", \"control\", \"group\", \"empty\"];\n static override values = { match: { type: String, default: \"all\" } };\n static actions = [\"apply\", \"clear\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly itemTargets: HTMLElement[];\n declare readonly controlTargets: FilterControl[];\n declare readonly groupTargets: HTMLElement[];\n declare readonly emptyTargets: HTMLElement[];\n\n declare matchValue: string;\n\n readonly #onChange = (): void => {\n this.apply();\n };\n\n /**\n * The outcome the last `change` reported, which the next evaluation is compared\n * with; `null` until the first evaluation after `connect()`, which reports\n * whatever it finds.\n */\n #reported: FilterOutcome | null = null;\n\n /**\n * Gates the declaration callback to the connected window.\n *\n * Stimulus delivers a Value callback ahead of `connect()` and again for every\n * runtime change; without the gate, merely connecting would emit an evaluation\n * — and its `change` — before `connect()` runs its own.\n */\n #connected = false;\n\n override connect(): void {\n this.#reported = null;\n this.#evaluate();\n this.element.addEventListener(\"change\", this.#onChange);\n this.#connected = true;\n }\n\n override disconnect(): void {\n this.#connected = false;\n this.element.removeEventListener(\"change\", this.#onChange);\n }\n\n /**\n * Re-evaluates when the match declaration changes at runtime.\n *\n * The declaration decides which items are shown, so an element kept across a\n * morph — where `connect()` does not run again — still has to follow it instead\n * of waiting for the next control interaction. The evaluation is the ordinary\n * one, `change` included: a declaration swap is an evaluation like any other.\n */\n matchValueChanged(): void {\n if (!this.#connected) return;\n this.#evaluate();\n }\n\n /** Re-derives every item's visibility from the active tokens and syncs groups/empty. */\n apply(): void {\n this.#evaluate();\n }\n\n /**\n * The evaluation itself: every item's visibility from the active tokens, then\n * the groups and the empty element, then `change` when the outcome moved from\n * the one last reported. Several triggers can land on the same outcome — a\n * `clear` evaluates, and a toggle group wired to `apply` reports the presses it\n * turned off — so an evaluation that changes nothing reports nothing.\n *\n * @stimeoRenderRoot\n */\n #evaluate(): void {\n const active = this.#activeTokens();\n let visibleCount = 0;\n for (const item of this.itemTargets) {\n const visible = this.#matches(item, active);\n item.hidden = !visible;\n if (visible) visibleCount += 1;\n }\n\n for (const group of this.groupTargets) {\n group.hidden = !this.#hasVisibleItem(group);\n }\n for (const empty of this.emptyTargets) {\n empty.hidden = visibleCount > 0;\n }\n\n const total = this.itemTargets.length;\n const reported = this.#reported;\n if (\n reported !== null &&\n reported.visibleCount === visibleCount &&\n reported.total === total &&\n reported.active.length === active.length &&\n reported.active.every((token, index) => token === active[index])\n ) {\n return;\n }\n this.#reported = { active, visibleCount, total };\n this.dispatch(\"change\", { detail: { active: [...active], visibleCount, total } });\n }\n\n /** Turns every control off (uncheck / aria-pressed=\"false\") and re-applies. */\n clear(): void {\n for (const control of this.controlTargets) {\n if (control instanceof HTMLInputElement) {\n control.checked = false;\n } else if (control.hasAttribute(\"aria-pressed\")) {\n control.setAttribute(\"aria-pressed\", \"false\");\n }\n }\n this.apply();\n }\n\n /** The tokens of every control currently \"on\" (checked or aria-pressed=\"true\"). */\n #activeTokens(): string[] {\n const tokens: string[] = [];\n for (const control of this.controlTargets) {\n if (this.#isOn(control)) tokens.push(this.#tokenOf(control));\n }\n return tokens.filter(Boolean);\n }\n\n /** Whether a control is in its \"on\" state. */\n #isOn(control: FilterControl): boolean {\n if (control instanceof HTMLInputElement) return control.checked;\n return control.getAttribute(\"aria-pressed\") === \"true\";\n }\n\n /** A control's token: explicit `data-stimeo--filter-token`, else `data-value` / value. */\n #tokenOf(control: FilterControl): string {\n const explicit = control.getAttribute(`data-${this.identifier}-token`) ?? control.dataset.value;\n if (explicit) return explicit;\n return control instanceof HTMLInputElement ? control.value : \"\";\n }\n\n /** Whether an item satisfies the active token set per `match` (empty active → shown). */\n #matches(item: HTMLElement, active: string[]): boolean {\n if (active.length === 0) return true;\n const tokens = this.#tokensOf(item);\n return this.matchValue === \"any\"\n ? active.some((token) => tokens.includes(token))\n : active.every((token) => tokens.includes(token));\n }\n\n /** An item's declared tokens (`data-stimeo--filter-tokens`, space-separated). */\n #tokensOf(item: HTMLElement): string[] {\n return (item.getAttribute(`data-${this.identifier}-tokens`) ?? \"\").split(/\\s+/).filter(Boolean);\n }\n\n /** Whether a group still contains at least one non-hidden item. */\n #hasVisibleItem(group: HTMLElement): boolean {\n return this.itemTargets.some((item) => !item.hidden && group.contains(item));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/listener_set.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/morph_render_watcher.ts","../../src/controllers/filter_controller.ts"],"names":[],"mappings":"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","file":"filter_controller.js","sourcesContent":["/**\n * Owns every listener a controller attaches for one connected lifetime and\n * releases them together, so an event name is spelled once and the release\n * cannot drift from the registration.\n *\n * Detaching by hand repeats three things at the release site — the type, the\n * handler reference and the capture flag — and any one of them drifting leaves\n * a listener alive on an element the controller no longer drives. Aborting the\n * signal a registration was created with releases it whatever it was made of,\n * so the release site names nothing.\n *\n * One set per lifetime, held as a field and never rebuilt. {@link\n * ListenerSet.dispose} is the only thing that closes a generation, and it opens\n * the next one in the same step, so the lifecycle hooks can call `add` and\n * `dispose` in any order any number of times, and no `add` ever meets an\n * already aborted signal — a DOM that registers a listener on such a signal\n * would keep it forever. Rebuilding the set instead would leak:\n * `addEventListener` matches an existing registration on target, type, callback\n * and capture alone, so re-adding the same handler while the first registration\n * is live is discarded, and that live registration keeps the signal it was\n * attached with — a set built by a second `connect()` can never release what it\n * appears to own.\n *\n * Scope is the registration only. Timers, observers, leases and the order they\n * are unwound in stay with the controller, which places `dispose()` in its\n * cleanup sequence. A receiver that changes at runtime (a\n * swapped target) is rebound by calling `dispose()` and then `add()` again from\n * the rebind, not by a second set.\n *\n * The set must own every registration of a tuple it holds: a listener the same\n * target already carries for the same type, callback and capture flag keeps the\n * right to remove it, and this set's `add` for that tuple is discarded. A\n * subscription shared between instances behind a participant count, or one that\n * lives for less than the connection, keeps its own pair of calls.\n *\n * @example\n * ```ts\n * readonly #listeners = new ListenerSet();\n *\n * connect(): void {\n * this.#listeners.add(this.element, \"submit\", this.#onNativeSubmit, { capture: true });\n * this.#listeners.add(this.element, \"turbo:submit-start\", this.#onSubmitStart);\n * this.#listeners.add(this.element, \"turbo:submit-end\", this.#onSubmitEnd);\n * }\n *\n * disconnect(): void {\n * this.#listeners.dispose();\n * }\n * ```\n */\nexport class ListenerSet {\n /** The generation every `add` joins until the next `dispose()`. */\n #abort = new AbortController();\n\n /**\n * Attaches `handler` to the open generation, exactly as the caller spelled it.\n *\n * The set supplies the signal, so `options` carries everything else the DOM\n * accepts — `capture` included, which has to match at release time and no\n * longer has a second place to drift from.\n */\n add(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: Omit<AddEventListenerOptions, \"signal\">,\n ): void {\n target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });\n }\n\n /**\n * Releases every listener of the open generation, synchronously, and opens the\n * next one. Idempotent, and safe before anything has been added.\n */\n dispose(): void {\n this.#abort.abort();\n this.#abort = new AbortController();\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","import { ListenerSet } from \"./listener_set\";\nimport { MicrotaskCoalescer } from \"./microtask_coalescer\";\n\n/**\n * Coalesces retained-element morphs and explicit lifecycle requests into one pass.\n *\n * Observe the controller's element after its initial state is settled. Morphs on\n * that element and bubbling morphs on its descendants share the same microtask\n * as {@link schedule}. The callback repairs derived output; the controller owns\n * any logical transition reporting. {@link disconnect} releases the listener and\n * cancels pending work, including work from an earlier connected lifetime.\n */\nexport class MorphRenderWatcher {\n readonly #listeners = new ListenerSet();\n readonly #pass: MicrotaskCoalescer;\n #element: Element | null = null;\n readonly #onMorph = (): void => this.#pass.schedule();\n\n /** @param run - the single pass that repairs the settled controller's output. */\n constructor(run: () => void) {\n this.#pass = new MicrotaskCoalescer(run);\n }\n\n /** Opens observation; observing the same element again preserves queued work. */\n observe(element: Element): void {\n if (this.#element === element) return;\n this.disconnect();\n this.#element = element;\n this.#pass.activate();\n this.#listeners.add(element, \"turbo:morph-element\", this.#onMorph);\n }\n\n /** Requests one pass; requests outside an observed lifetime are ignored. */\n schedule(): void {\n this.#pass.schedule();\n }\n\n /** Releases observation and pending work synchronously; safe to repeat. */\n disconnect(): void {\n this.#listeners.dispose();\n this.#pass.cancel();\n this.#element = null;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { MorphRenderWatcher } from \"../utils/morph_render_watcher\";\n\n/** A control whose \"on\" state contributes its token to the active filter set. */\ntype FilterControl = HTMLElement;\n\n/** What an evaluation settles on and `change` reports. */\ninterface FilterOutcome {\n readonly active: readonly string[];\n readonly visibleCount: number;\n readonly total: number;\n}\n\n/**\n * Headless faceted-filter behavior: shows or hides a collection of items based on the\n * set of currently-active facet tokens, decoupled from how those tokens are toggled\n * (native checkboxes/radios, or button toggles such as `stimeo--toggle-group`).\n *\n * Markup contract (identifier: `stimeo--filter`):\n * <div data-controller=\"stimeo--filter\" data-stimeo--filter-match-value=\"all\">\n * <!-- controls: each contributes its token while \"on\" (aria-pressed / checked) -->\n * <button type=\"button\" aria-pressed=\"false\" data-value=\"apg:dialog\"\n * data-stimeo--filter-target=\"control\">Dialog</button>\n * <!-- items: filtered in/out by their space-separated tokens -->\n * <li data-stimeo--filter-target=\"item\"\n * data-stimeo--filter-tokens=\"apg:dialog overlay\">…</li>\n * <!-- optional: a container hidden once it holds no visible item -->\n * <section data-stimeo--filter-target=\"group\">…</section>\n * <!-- optional: an element revealed when nothing matches -->\n * <p data-stimeo--filter-target=\"empty\" hidden>No matches</p>\n * </div>\n *\n * `match` decides how multiple active tokens combine: `all` (default — an item must\n * carry every active token) or `any` (at least one). With no active token every item\n * is shown. Items are toggled via `hidden`; groups with zero visible items and the\n * `empty` element are kept in sync, and `stimeo--filter:change` reports the outcome\n * when it moves.\n *\n * `change` dispatches `{ active, visibleCount, total }` — the active tokens, how\n * many items they leave visible, and how many items there are — whenever an\n * evaluation lands on an outcome other than the one it last reported. The first\n * evaluation after `connect()` always reports.\n *\n * @remarks\n * Behavior only — the consumer owns all styling and the controls' own accessible\n * state (e.g. `aria-pressed`); the one exception is `clear`, which writes those\n * controls one way, to off. Visibility is re-derived from the live DOM on\n * `connect()`, on every native `change` that bubbles to the root, on the `apply`\n * and `clear` actions, and when `match` changes at runtime — the declaration\n * decides what is shown, so an element kept across a morph still follows it.\n * Button toggles that emit no native `change` wire their events to the `apply`\n * action: `stimeo--toggle-group:change->stimeo--filter#apply` for a press, and\n * `stimeo--toggle-group:reconcile->stimeo--filter#apply` for a pressed set the\n * page moves.\n */\nexport class FilterController extends Controller<HTMLElement> {\n static override targets = [\"item\", \"control\", \"group\", \"empty\"];\n static override values = { match: { type: String, default: \"all\" } };\n static actions = [\"apply\", \"clear\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly itemTargets: HTMLElement[];\n declare readonly controlTargets: FilterControl[];\n declare readonly groupTargets: HTMLElement[];\n declare readonly emptyTargets: HTMLElement[];\n\n declare matchValue: string;\n\n readonly #onChange = (): void => {\n this.apply();\n };\n\n /**\n * The outcome the last `change` reported, which the next evaluation is compared\n * with; `null` until the first evaluation after `connect()`, which reports\n * whatever it finds.\n */\n #reported: FilterOutcome | null = null;\n\n /**\n * Gates the declaration callback to the connected window.\n *\n * Stimulus delivers a Value callback ahead of `connect()` and again for every\n * runtime change; without the gate, merely connecting would emit an evaluation\n * — and its `change` — before `connect()` runs its own.\n */\n #connected = false;\n\n readonly #morphRender = new MorphRenderWatcher(() => this.#evaluate());\n\n override connect(): void {\n this.#morphRender.observe(this.element);\n this.#reported = null;\n this.#evaluate();\n this.element.addEventListener(\"change\", this.#onChange);\n this.#connected = true;\n }\n\n override disconnect(): void {\n this.#morphRender.disconnect();\n this.#connected = false;\n this.element.removeEventListener(\"change\", this.#onChange);\n }\n\n /**\n * Re-evaluates when the match declaration changes at runtime.\n *\n * The declaration decides which items are shown, so an element kept across a\n * morph — where `connect()` does not run again — still has to follow it instead\n * of waiting for the next control interaction. The evaluation is the ordinary\n * one, `change` included: a declaration swap is an evaluation like any other.\n */\n matchValueChanged(): void {\n if (!this.#connected) return;\n this.#evaluate();\n }\n\n /** Re-derives every item's visibility from the active tokens and syncs groups/empty. */\n apply(): void {\n this.#evaluate();\n }\n\n /**\n * The evaluation itself: every item's visibility from the active tokens, then\n * the groups and the empty element, then `change` when the outcome moved from\n * the one last reported. Several triggers can land on the same outcome — a\n * `clear` evaluates, and a toggle group wired to `apply` reports the presses it\n * turned off — so an evaluation that changes nothing reports nothing.\n *\n * @stimeoRenderRoot\n */\n #evaluate(): void {\n const active = this.#activeTokens();\n let visibleCount = 0;\n for (const item of this.itemTargets) {\n const visible = this.#matches(item, active);\n item.hidden = !visible;\n if (visible) visibleCount += 1;\n }\n\n for (const group of this.groupTargets) {\n group.hidden = !this.#hasVisibleItem(group);\n }\n for (const empty of this.emptyTargets) {\n empty.hidden = visibleCount > 0;\n }\n\n const total = this.itemTargets.length;\n const reported = this.#reported;\n if (\n reported !== null &&\n reported.visibleCount === visibleCount &&\n reported.total === total &&\n reported.active.length === active.length &&\n reported.active.every((token, index) => token === active[index])\n ) {\n return;\n }\n this.#reported = { active, visibleCount, total };\n this.dispatch(\"change\", { detail: { active: [...active], visibleCount, total } });\n }\n\n /** Turns every control off (uncheck / aria-pressed=\"false\") and re-applies. */\n clear(): void {\n for (const control of this.controlTargets) {\n if (control instanceof HTMLInputElement) {\n control.checked = false;\n } else if (control.hasAttribute(\"aria-pressed\")) {\n control.setAttribute(\"aria-pressed\", \"false\");\n }\n }\n this.apply();\n }\n\n /** The tokens of every control currently \"on\" (checked or aria-pressed=\"true\"). */\n #activeTokens(): string[] {\n const tokens: string[] = [];\n for (const control of this.controlTargets) {\n if (this.#isOn(control)) tokens.push(this.#tokenOf(control));\n }\n return tokens.filter(Boolean);\n }\n\n /** Whether a control is in its \"on\" state. */\n #isOn(control: FilterControl): boolean {\n if (control instanceof HTMLInputElement) return control.checked;\n return control.getAttribute(\"aria-pressed\") === \"true\";\n }\n\n /** A control's token: explicit `data-stimeo--filter-token`, else `data-value` / value. */\n #tokenOf(control: FilterControl): string {\n const explicit = control.getAttribute(`data-${this.identifier}-token`) ?? control.dataset.value;\n if (explicit) return explicit;\n return control instanceof HTMLInputElement ? control.value : \"\";\n }\n\n /** Whether an item satisfies the active token set per `match` (empty active → shown). */\n #matches(item: HTMLElement, active: string[]): boolean {\n if (active.length === 0) return true;\n const tokens = this.#tokensOf(item);\n return this.matchValue === \"any\"\n ? active.some((token) => tokens.includes(token))\n : active.every((token) => tokens.includes(token));\n }\n\n /** An item's declared tokens (`data-stimeo--filter-tokens`, space-separated). */\n #tokensOf(item: HTMLElement): string[] {\n return (item.getAttribute(`data-${this.identifier}-tokens`) ?? \"\").split(/\\s+/).filter(Boolean);\n }\n\n /** Whether a group still contains at least one non-hidden item. */\n #hasVisibleItem(group: HTMLElement): boolean {\n return this.itemTargets.some((item) => !item.hidden && group.contains(item));\n }\n}\n"]}
|
|
@@ -7,6 +7,9 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
7
7
|
* 2.2 **4.1.3 Status Messages**). The general-purpose sibling is Toast; this one is
|
|
8
8
|
* specialized to the Rails `flash` convention.
|
|
9
9
|
*
|
|
10
|
+
* Capacity evicts by arrival order, independent of DOM order. The same controller
|
|
11
|
+
* instance preserves that order across reconnects; a new instance uses DOM order.
|
|
12
|
+
*
|
|
10
13
|
* Markup contract (identifier: `stimeo--flash`):
|
|
11
14
|
* <div data-controller="stimeo--flash" data-stimeo--flash-target="region">
|
|
12
15
|
* <!-- server-rendered or Turbo Stream-inserted -->
|
|
@@ -27,11 +30,13 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
27
30
|
* those holds is released.
|
|
28
31
|
* A close button wired to the `dismiss` action removes one manually.
|
|
29
32
|
*
|
|
30
|
-
* `dismiss` dispatches `{ element, reason }`.
|
|
33
|
+
* `dismiss` dispatches `{ element, reason: StateReason }`.
|
|
31
34
|
*
|
|
32
|
-
* `show` dispatches `{ type, message }
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
+
* `show` dispatches `{ type, message }`, once per message: a reconnection of the same
|
|
36
|
+
* instance — an in-page move, a `data-turbo-permanent` region carried to the next page —
|
|
37
|
+
* takes its messages on again without showing or announcing them a second time.
|
|
38
|
+
* `reconcile` dispatches `{ removed: number }` — the messages a page restored from the
|
|
39
|
+
* Turbo cache still carried, which no `dismiss` will report because nobody dismissed them.
|
|
35
40
|
*
|
|
36
41
|
* @remarks
|
|
37
42
|
* Reading is **delegated to the shared Announcer** — but only for the *initial*,
|
|
@@ -48,8 +53,16 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
48
53
|
* subtree, and a message that leaves it gives up its stacking slot, its pending
|
|
49
54
|
* auto-dismiss, and any removal already scheduled. The observer, timers,
|
|
50
55
|
* and per-message listeners are torn down on `disconnect()` (Turbo navigation
|
|
51
|
-
* included)
|
|
52
|
-
*
|
|
56
|
+
* included).
|
|
57
|
+
*
|
|
58
|
+
* **A restored page does not replay a notification.** Every message taken on carries
|
|
59
|
+
* `data-<identifier>-shown`. A page Turbo restores from its cache is a copy taken while
|
|
60
|
+
* its messages were on screen, so a connection that finds marked messages it never took
|
|
61
|
+
* on itself removes them, silently, before it takes anything else on, and reports how
|
|
62
|
+
* many as `reconcile`. Server-rendered messages carry no mark and are announced as
|
|
63
|
+
* usual. Nothing is removed on `turbo:before-cache`, which Turbo also dispatches on
|
|
64
|
+
* pages that stay — a promoted frame navigation, a state-less `popstate`, a refresh of
|
|
65
|
+
* a cached URL, a permanent region — where the message is still being read.
|
|
53
66
|
*/
|
|
54
67
|
declare class FlashController extends Controller<HTMLElement> {
|
|
55
68
|
#private;
|
|
@@ -68,6 +81,16 @@ declare class FlashController extends Controller<HTMLElement> {
|
|
|
68
81
|
default: number;
|
|
69
82
|
};
|
|
70
83
|
};
|
|
84
|
+
static valueConstraints: {
|
|
85
|
+
duration: {
|
|
86
|
+
readonly finite: true;
|
|
87
|
+
readonly min: 0;
|
|
88
|
+
readonly max: 2147483647;
|
|
89
|
+
};
|
|
90
|
+
max: {
|
|
91
|
+
readonly finite: true;
|
|
92
|
+
};
|
|
93
|
+
};
|
|
71
94
|
static actions: readonly ["dismiss"];
|
|
72
95
|
static events: readonly ["show", "dismiss", "reconcile"];
|
|
73
96
|
readonly regionTarget: HTMLElement;
|
|
@@ -102,8 +125,8 @@ declare class FlashController extends Controller<HTMLElement> {
|
|
|
102
125
|
* ended a hold, so those are read again.
|
|
103
126
|
*/
|
|
104
127
|
messageTargetDisconnected(message: HTMLElement): void;
|
|
105
|
-
/** Dismisses
|
|
106
|
-
dismiss(
|
|
128
|
+
/** Dismisses an owned message or descendant, or the message a DOM action operated. */
|
|
129
|
+
dismiss(input: Event | HTMLElement): void;
|
|
107
130
|
}
|
|
108
131
|
|
|
109
132
|
export { FlashController };
|