stimeo-ui 0.16.0 → 0.18.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 +136 -0
- package/README.md +27 -11
- package/dist/cable/index.d.ts +88 -31
- package/dist/cable/index.js +384 -449
- package/dist/cable/index.js.map +1 -1
- package/dist/coerce-JtZVNG5h.d.ts +20 -0
- package/dist/controllers/accordion_controller.d.ts +4 -2
- package/dist/controllers/accordion_controller.js +174 -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 +903 -235
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +37 -5
- package/dist/controllers/announcer_controller.js +238 -205
- 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 +414 -145
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +185 -107
- 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 +21 -3
- package/dist/controllers/bulk_select_controller.js +282 -58
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +38 -16
- package/dist/controllers/calendar_controller.js +228 -260
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +43 -12
- package/dist/controllers/carousel_controller.js +536 -436
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +44 -7
- package/dist/controllers/character_counter_controller.js +493 -207
- 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 +38 -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 +261 -149
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +17 -5
- package/dist/controllers/collapsible_controller.js +304 -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 +178 -146
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +24 -6
- package/dist/controllers/combobox_controller.js +522 -156
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +51 -20
- package/dist/controllers/command_palette_controller.js +1156 -395
- 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 +300 -171
- 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 +980 -262
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +80 -18
- package/dist/controllers/context_menu_controller.js +346 -153
- 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 +47 -3
- package/dist/controllers/countdown_controller.js +281 -158
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +93 -10
- package/dist/controllers/currency_input_controller.js +575 -225
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +25 -12
- package/dist/controllers/data_grid_controller.js +248 -214
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +42 -14
- package/dist/controllers/date_range_picker_controller.js +338 -235
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +17 -6
- package/dist/controllers/dialog_controller.js +899 -237
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +25 -8
- package/dist/controllers/direct_upload_controller.js +97 -167
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +6 -1
- package/dist/controllers/dirty_form_controller.js +72 -69
- 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 +196 -24
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +39 -11
- package/dist/controllers/drawer_controller.js +974 -355
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +31 -1
- package/dist/controllers/dropdown_controller.js +339 -96
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.d.ts +12 -1
- package/dist/controllers/editable_controller.js +315 -88
- package/dist/controllers/editable_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.js +79 -58
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +63 -18
- package/dist/controllers/file_dropzone_controller.js +322 -211
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.js +77 -42
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +35 -12
- package/dist/controllers/flash_controller.js +238 -347
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +15 -6
- package/dist/controllers/focus_controller.js +679 -240
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +156 -132
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.d.ts +3 -3
- package/dist/controllers/form_validation_controller.js +31 -97
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +47 -14
- package/dist/controllers/frame_loading_controller.js +275 -217
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +27 -6
- 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 +60 -4
- package/dist/controllers/hover_card_controller.js +327 -130
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +48 -6
- package/dist/controllers/idle_controller.js +271 -101
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +30 -13
- package/dist/controllers/input_mask_controller.js +483 -97
- 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 +150 -106
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +19 -2
- package/dist/controllers/lazy_frame_controller.js +24 -68
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +44 -9
- package/dist/controllers/listbox_controller.js +523 -240
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +9 -7
- package/dist/controllers/local_time_controller.js +102 -77
- 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 +80 -96
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +55 -11
- package/dist/controllers/menu_controller.js +302 -188
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +51 -9
- package/dist/controllers/menubar_controller.js +232 -345
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +33 -20
- package/dist/controllers/meter_controller.js +417 -87
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +48 -18
- package/dist/controllers/multi_select_controller.js +785 -393
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +27 -6
- package/dist/controllers/navigation_menu_controller.js +190 -250
- 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 +396 -192
- 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 +268 -62
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +50 -8
- package/dist/controllers/number_input_controller.js +610 -303
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.d.ts +8 -5
- package/dist/controllers/optimistic_controller.js +45 -87
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +55 -8
- package/dist/controllers/otp_controller.js +658 -255
- 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 +122 -113
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +58 -20
- package/dist/controllers/overflow_menu_controller.js +470 -295
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +37 -7
- package/dist/controllers/pagination_controller.js +332 -123
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +46 -8
- package/dist/controllers/password_reveal_controller.js +392 -121
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +15 -6
- package/dist/controllers/password_strength_controller.js +111 -158
- 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 +165 -111
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +35 -12
- package/dist/controllers/pointer_drag_controller.js +434 -188
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +41 -5
- package/dist/controllers/popover_controller.js +269 -113
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +44 -4
- package/dist/controllers/portal_controller.js +215 -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 +243 -116
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +20 -19
- package/dist/controllers/progress_controller.js +391 -78
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +21 -9
- package/dist/controllers/radio_group_controller.js +278 -211
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +47 -6
- package/dist/controllers/range_slider_controller.js +220 -113
- 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 +458 -211
- 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 +138 -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 +166 -137
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.d.ts +20 -6
- package/dist/controllers/relative_time_controller.js +211 -112
- 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} +103 -38
- package/dist/controllers/reset_on_restore_controller.js.map +1 -0
- package/dist/controllers/resizable_controller.d.ts +31 -2
- package/dist/controllers/resizable_controller.js +299 -150
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +3 -3
- package/dist/controllers/roving_controller.js +49 -88
- 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 +490 -246
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_restore_controller.d.ts +3 -3
- package/dist/controllers/scroll_restore_controller.js +14 -55
- 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 +432 -149
- 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 +193 -248
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +20 -5
- package/dist/controllers/separator_controller.js +285 -142
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +65 -8
- package/dist/controllers/sidebar_controller.js +1069 -404
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +23 -0
- package/dist/controllers/skeleton_controller.js +259 -101
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +29 -2
- package/dist/controllers/slider_controller.js +187 -86
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +22 -4
- package/dist/controllers/smart_sticky_header_controller.js +173 -66
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.d.ts +4 -4
- package/dist/controllers/sortable_controller.js +26 -125
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +39 -4
- package/dist/controllers/spinner_controller.js +288 -173
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +9 -4
- package/dist/controllers/step_indicator_controller.js +105 -46
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +14 -6
- package/dist/controllers/stepper_controller.js +120 -73
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +9 -1
- package/dist/controllers/stick_to_bottom_controller.js +275 -140
- 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 +220 -49
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +24 -8
- package/dist/controllers/submit_once_controller.js +376 -227
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +18 -4
- package/dist/controllers/switch_controller.js +201 -84
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +9 -6
- package/dist/controllers/tabs_controller.js +41 -30
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +16 -3
- package/dist/controllers/tags_input_controller.js +293 -173
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +11 -1
- package/dist/controllers/textarea_autosize_controller.js +108 -74
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +24 -11
- package/dist/controllers/theme_controller.js +140 -116
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +55 -5
- package/dist/controllers/time_picker_controller.js +331 -127
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +40 -16
- package/dist/controllers/toast_controller.js +244 -324
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +14 -6
- package/dist/controllers/toggle_group_controller.js +252 -191
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.d.ts +8 -6
- package/dist/controllers/toolbar_controller.js +219 -120
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +49 -4
- 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 +22 -12
- package/dist/controllers/tree_view_controller.js +283 -369
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.js +8573 -10410
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +38 -14
- package/dist/inspector/cli.js +108 -40
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +145 -64
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +20 -20
- package/dist/inspector/manifest.json +802 -101
- package/dist/positioning/index.d.ts +15 -7
- package/dist/positioning/index.js +154 -47
- package/dist/positioning/index.js.map +1 -1
- package/package.json +11 -10
- package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
- package/dist/controllers/reset_before_cache_controller.js.map +0 -1
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
// src/controllers/submit_once_controller.ts
|
|
4
|
-
|
|
5
|
-
// src/utils/announce.ts
|
|
6
3
|
function announce(message, options = {}) {
|
|
7
4
|
const text = message.trim();
|
|
8
5
|
if (text.length === 0) return;
|
|
@@ -13,192 +10,352 @@ function announce(message, options = {}) {
|
|
|
13
10
|
);
|
|
14
11
|
}
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
14
|
+
if (element.getAttribute(name) === value) return false;
|
|
15
|
+
if (value === null) element.removeAttribute(name);
|
|
16
|
+
else element.setAttribute(name, value);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sharedRegistry(key, create) {
|
|
21
|
+
const symbol = Symbol.for(key);
|
|
22
|
+
const scope = globalThis;
|
|
23
|
+
const existing = scope[symbol];
|
|
24
|
+
if (existing !== void 0) return existing;
|
|
25
|
+
const registry2 = create();
|
|
26
|
+
Object.defineProperty(globalThis, symbol, { value: registry2 });
|
|
27
|
+
return registry2;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var registry = sharedRegistry(
|
|
31
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
32
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
33
|
+
);
|
|
34
|
+
function isAttributeValue(value) {
|
|
35
|
+
return value === null || typeof value === "string";
|
|
36
|
+
}
|
|
37
|
+
function readLeaseRecord(element, record) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
40
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
41
|
+
const [original, written, ...rest] = parsed;
|
|
42
|
+
const linked = rest[0] !== false;
|
|
43
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
44
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
45
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
46
|
+
} catch {
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function attributeStack(element, attribute) {
|
|
51
|
+
const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
52
|
+
registry.stacks.set(element, byAttribute);
|
|
53
|
+
const known = byAttribute.get(attribute);
|
|
54
|
+
if (known) return known;
|
|
55
|
+
const found = [];
|
|
56
|
+
for (const record of registry.names.get(attribute) ?? []) {
|
|
57
|
+
const read = readLeaseRecord(element, record);
|
|
58
|
+
if (read) found.push({ ...read, record });
|
|
59
|
+
}
|
|
60
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
61
|
+
const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
62
|
+
lease: null,
|
|
63
|
+
record,
|
|
64
|
+
written,
|
|
65
|
+
original,
|
|
66
|
+
linked: linked && index > 0
|
|
67
|
+
}));
|
|
68
|
+
byAttribute.set(attribute, stack);
|
|
69
|
+
return stack;
|
|
70
|
+
}
|
|
71
|
+
function findHold(stack, lease, record) {
|
|
72
|
+
return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
|
|
73
|
+
}
|
|
17
74
|
var AttributeLease = class {
|
|
18
75
|
#attribute;
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
76
|
+
#record;
|
|
77
|
+
#base;
|
|
78
|
+
#preserveAuthor;
|
|
79
|
+
#held = /* @__PURE__ */ new Set();
|
|
80
|
+
constructor(attribute, owner, options = {}) {
|
|
22
81
|
this.#attribute = attribute;
|
|
82
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
83
|
+
this.#base = options.base === true;
|
|
84
|
+
this.#preserveAuthor = options.preserveAuthor === true;
|
|
85
|
+
const names = registry.names;
|
|
86
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
23
87
|
}
|
|
24
|
-
/** Writes or removes the leased attribute while preserving its authored value. */
|
|
25
88
|
write(element, value) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
this.#reflect(element, value);
|
|
89
|
+
const stack = attributeStack(element, this.#attribute);
|
|
90
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
91
|
+
this.#held.add(element);
|
|
92
|
+
const shows = !this.#covered(element, stack, hold);
|
|
93
|
+
hold.written = value;
|
|
94
|
+
this.#mark(element, stack);
|
|
95
|
+
if (shows) this.#reflect(element, value);
|
|
36
96
|
}
|
|
37
|
-
/** Returns one lease without overwriting a value subsequently authored by a consumer. */
|
|
38
97
|
return(element) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
this.#
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
98
|
+
const held = this.#held.delete(element);
|
|
99
|
+
const known = registry.stacks.get(element)?.has(this.#attribute);
|
|
100
|
+
const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
|
|
101
|
+
const hold = this.#take(stack);
|
|
102
|
+
if (!hold) return false;
|
|
103
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
104
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
105
|
+
const index = stack.indexOf(hold);
|
|
106
|
+
stack.splice(index, 1);
|
|
107
|
+
const above = stack[index];
|
|
108
|
+
if (above) above.linked &&= hold.linked;
|
|
109
|
+
this.#mark(element, stack);
|
|
110
|
+
if (!owns) return true;
|
|
111
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
112
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
113
|
+
return true;
|
|
50
114
|
}
|
|
51
|
-
/** Returns every outstanding lease using the same ownership check as {@link return}. */
|
|
52
115
|
returnAll() {
|
|
53
|
-
for (const element of Array.from(this.#
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
116
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
117
|
+
}
|
|
118
|
+
#take(stack) {
|
|
119
|
+
const hold = findHold(stack, this, this.#record);
|
|
120
|
+
if (hold) hold.lease = this;
|
|
121
|
+
return hold;
|
|
122
|
+
}
|
|
123
|
+
#acquire(element, stack) {
|
|
124
|
+
const current = element.getAttribute(this.#attribute);
|
|
125
|
+
const top = stack.at(-1);
|
|
126
|
+
const bottom = stack[0];
|
|
127
|
+
const over = top?.written === current ? top : void 0;
|
|
128
|
+
const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
|
|
129
|
+
const hold = {
|
|
130
|
+
lease: this,
|
|
131
|
+
record: this.#record,
|
|
132
|
+
written: current,
|
|
133
|
+
original: from ? from.original : current,
|
|
134
|
+
linked: !this.#base && over !== void 0
|
|
135
|
+
};
|
|
136
|
+
if (this.#base && bottom) {
|
|
137
|
+
bottom.linked = true;
|
|
138
|
+
stack.unshift(hold);
|
|
139
|
+
} else stack.push(hold);
|
|
140
|
+
return hold;
|
|
141
|
+
}
|
|
142
|
+
#covered(element, stack, hold) {
|
|
143
|
+
const current = element.getAttribute(this.#attribute);
|
|
144
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
145
|
+
}
|
|
146
|
+
#mark(element, stack) {
|
|
147
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
148
|
+
stack.forEach((hold, index) => {
|
|
149
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
150
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
151
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
152
|
+
records.set(
|
|
153
|
+
hold.record,
|
|
154
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
for (const [name, recorded] of records) {
|
|
158
|
+
setAttributeIfChanged(element, name, recorded);
|
|
76
159
|
}
|
|
77
160
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
_BeforeCacheReset.#subscribers.delete(this);
|
|
81
|
-
if (_BeforeCacheReset.#subscribers.size > 0) return;
|
|
82
|
-
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
161
|
+
#reflect(element, value) {
|
|
162
|
+
setAttributeIfChanged(element, this.#attribute, value);
|
|
83
163
|
}
|
|
84
164
|
};
|
|
85
165
|
|
|
86
|
-
// src/utils/detach_gate.ts
|
|
87
166
|
var DetachGate = class _DetachGate {
|
|
88
|
-
/** Set while a probe is queued, waiting for a reconnect to cancel it. */
|
|
89
167
|
#pending = false;
|
|
90
|
-
/**
|
|
91
|
-
* True while a probe is queued — the last disconnect was ambiguous and no
|
|
92
|
-
* reconnect has cancelled it yet. Read it from `connect()` to tell the
|
|
93
|
-
* reconnect half of an in-page move from a first connect: a controller whose
|
|
94
|
-
* initialisation restarts a measurement (a min-duration floor, an elapsed
|
|
95
|
-
* counter) must skip it for the move, where nothing actually restarted.
|
|
96
|
-
*/
|
|
97
168
|
get pending() {
|
|
98
169
|
return this.#pending;
|
|
99
170
|
}
|
|
100
|
-
/**
|
|
101
|
-
* True when the disconnect is definitely a real detach — the element left
|
|
102
|
-
* the document, or `data-controller` no longer lists the identifier. False
|
|
103
|
-
* means ambiguous (in-page move or observed-root exit), NOT "alive".
|
|
104
|
-
*/
|
|
105
171
|
static isDetached(host) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return !tokens.includes(host.identifier);
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Call from `disconnect()`: runs `teardown` synchronously on a definite
|
|
112
|
-
* detach (fast path), otherwise defers it one microtask — a reconnect
|
|
113
|
-
* ({@link cancel} from `connect()`) keeps the state, no reconnect runs it.
|
|
114
|
-
* One microtask is the whole probe window: Stimulus reconnects a moved
|
|
115
|
-
* element within the same mutation batch, before the checkpoint drains.
|
|
116
|
-
*/
|
|
172
|
+
return !host.element.isConnected || !_DetachGate.#listed(host);
|
|
173
|
+
}
|
|
117
174
|
disconnected(host, teardown) {
|
|
118
|
-
|
|
175
|
+
const carried = _DetachGate.#carried(host);
|
|
176
|
+
if (!carried && _DetachGate.isDetached(host)) {
|
|
119
177
|
this.#pending = false;
|
|
120
178
|
teardown();
|
|
121
179
|
return;
|
|
122
180
|
}
|
|
123
181
|
this.#pending = true;
|
|
124
|
-
|
|
182
|
+
const probe = () => {
|
|
125
183
|
if (!this.#pending) return;
|
|
126
184
|
this.#pending = false;
|
|
127
185
|
teardown();
|
|
128
|
-
}
|
|
186
|
+
};
|
|
187
|
+
if (carried) setTimeout(probe, 0);
|
|
188
|
+
else queueMicrotask(probe);
|
|
189
|
+
}
|
|
190
|
+
static #carried(host) {
|
|
191
|
+
const element = host.element;
|
|
192
|
+
return !element.isConnected && element.closest("[data-turbo-permanent]") !== null && _DetachGate.#listed(host);
|
|
193
|
+
}
|
|
194
|
+
static #listed(host) {
|
|
195
|
+
const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
|
|
196
|
+
return tokens.includes(host.identifier);
|
|
129
197
|
}
|
|
130
|
-
/**
|
|
131
|
-
* Disarms a pending probe. Call from `connect()` (the reconnect that proves
|
|
132
|
-
* an in-page move) and from the head of any teardown path not routed through
|
|
133
|
-
* {@link disconnected} (disabled-toggle, Escape), so an orphaned probe can
|
|
134
|
-
* never run the teardown a second time.
|
|
135
|
-
*/
|
|
136
198
|
cancel() {
|
|
137
199
|
this.#pending = false;
|
|
138
200
|
}
|
|
139
201
|
};
|
|
140
202
|
|
|
141
|
-
// src/utils/listener_set.ts
|
|
142
203
|
var ListenerSet = class {
|
|
143
|
-
/** The generation every `add` joins until the next `dispose()`. */
|
|
144
204
|
#abort = new AbortController();
|
|
145
|
-
/**
|
|
146
|
-
* Attaches `handler` to the open generation, exactly as the caller spelled it.
|
|
147
|
-
*
|
|
148
|
-
* The set supplies the signal, so `options` carries everything else the DOM
|
|
149
|
-
* accepts — `capture` included, which has to match at release time and no
|
|
150
|
-
* longer has a second place to drift from.
|
|
151
|
-
*/
|
|
152
205
|
add(target, type, handler, options) {
|
|
153
206
|
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
154
207
|
}
|
|
155
|
-
/**
|
|
156
|
-
* Releases every listener of the open generation, synchronously, and opens the
|
|
157
|
-
* next one. Idempotent, and safe before anything has been added.
|
|
158
|
-
*/
|
|
159
208
|
dispose() {
|
|
160
209
|
this.#abort.abort();
|
|
161
210
|
this.#abort = new AbortController();
|
|
162
211
|
}
|
|
163
212
|
};
|
|
164
213
|
|
|
165
|
-
|
|
214
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
215
|
+
|
|
216
|
+
var NUMBER_BOUNDS = {
|
|
217
|
+
timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS }};
|
|
218
|
+
function matchesNumberBounds(value, bounds) {
|
|
219
|
+
if (!Number.isFinite(value)) {
|
|
220
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
221
|
+
if (direction === null) return false;
|
|
222
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
223
|
+
}
|
|
224
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
225
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
226
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
227
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
228
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function readNumber(raw, fallback, bounds) {
|
|
233
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
var NumberValueReader = class {
|
|
237
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
238
|
+
read(owner, name, raw, fallback, bounds) {
|
|
239
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
240
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
241
|
+
this.#lastRejected.delete(name);
|
|
242
|
+
return resolved;
|
|
243
|
+
}
|
|
244
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
245
|
+
const literal = owner.element.getAttribute(attribute);
|
|
246
|
+
if (literal === null) {
|
|
247
|
+
this.#lastRejected.delete(name);
|
|
248
|
+
return resolved;
|
|
249
|
+
}
|
|
250
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
251
|
+
this.#lastRejected.set(name, literal);
|
|
252
|
+
console.warn(
|
|
253
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return resolved;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
var OwnedText = class {
|
|
261
|
+
#attribute;
|
|
262
|
+
#channel;
|
|
263
|
+
#guard;
|
|
264
|
+
#writes = /* @__PURE__ */ new WeakMap();
|
|
265
|
+
constructor(attribute, channel = "text", guard = () => true) {
|
|
266
|
+
this.#attribute = attribute;
|
|
267
|
+
this.#channel = channel;
|
|
268
|
+
this.#guard = guard;
|
|
269
|
+
}
|
|
270
|
+
hasRecord(element) {
|
|
271
|
+
return element.hasAttribute(this.#attribute);
|
|
272
|
+
}
|
|
273
|
+
read(element, evidence = {}) {
|
|
274
|
+
if (evidence.source === "live") return this.#writes.get(element) ?? null;
|
|
275
|
+
const raw = element.getAttribute(this.#attribute);
|
|
276
|
+
if (raw === null) return null;
|
|
277
|
+
try {
|
|
278
|
+
const record = JSON.parse(raw);
|
|
279
|
+
if (!Array.isArray(record) || record.length !== 3 || record[0] !== this.#channel || typeof record[1] !== "string" || typeof record[2] !== "string")
|
|
280
|
+
return null;
|
|
281
|
+
return { original: record[1], written: record[2] };
|
|
282
|
+
} catch {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
owns(element, evidence = {}) {
|
|
287
|
+
return this.#ownedRecord(element, evidence) !== null;
|
|
288
|
+
}
|
|
289
|
+
#ownedRecord(element, evidence) {
|
|
290
|
+
const record = this.read(element, evidence);
|
|
291
|
+
if (record === null || !this.#guard(element)) return null;
|
|
292
|
+
if (this.#channel === "text" && element.firstElementChild !== null) return null;
|
|
293
|
+
const current = this.#current(element);
|
|
294
|
+
return current === record.written || evidence.source === "live" && evidence.allowOriginal === true && current === record.original ? record : null;
|
|
295
|
+
}
|
|
296
|
+
write(element, written, original, force = false) {
|
|
297
|
+
const current = this.#current(element);
|
|
298
|
+
if (current === null) return;
|
|
299
|
+
const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
|
|
300
|
+
if (force || current !== written) this.#put(element, written);
|
|
301
|
+
this.remember(element, written, before);
|
|
302
|
+
}
|
|
303
|
+
remember(element, written, original) {
|
|
304
|
+
const current = this.#current(element);
|
|
305
|
+
if (current === null) return;
|
|
306
|
+
const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
|
|
307
|
+
this.#writes.set(element, { original: before, written });
|
|
308
|
+
setAttributeIfChanged(
|
|
309
|
+
element,
|
|
310
|
+
this.#attribute,
|
|
311
|
+
JSON.stringify([this.#channel, before, written])
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
clear(element, evidence = {}) {
|
|
315
|
+
if (!this.owns(element, evidence)) return false;
|
|
316
|
+
if (this.#current(element) !== "") this.#put(element, "");
|
|
317
|
+
this.forget(element);
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
restore(element, evidence = {}) {
|
|
321
|
+
const record = this.#ownedRecord(element, evidence);
|
|
322
|
+
if (record === null) return false;
|
|
323
|
+
if (this.#current(element) !== record.original) this.#put(element, record.original);
|
|
324
|
+
this.forget(element);
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
forget(element) {
|
|
328
|
+
setAttributeIfChanged(element, this.#attribute, null);
|
|
329
|
+
this.#writes.delete(element);
|
|
330
|
+
}
|
|
331
|
+
#current(element) {
|
|
332
|
+
if (this.#channel === "text") return element.textContent ?? "";
|
|
333
|
+
return element instanceof HTMLInputElement ? element.value : null;
|
|
334
|
+
}
|
|
335
|
+
#put(element, text) {
|
|
336
|
+
if (this.#channel === "text") element.textContent = text;
|
|
337
|
+
else if (element instanceof HTMLInputElement) element.value = text;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
166
341
|
var TimerRegistry = class {
|
|
167
|
-
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
168
342
|
ids = /* @__PURE__ */ new Set();
|
|
169
|
-
/**
|
|
170
|
-
* Cancels a single tracked timer.
|
|
171
|
-
*
|
|
172
|
-
* No-ops if the id is unknown (already cleared, fired, or never owned by this
|
|
173
|
-
* registry), so callers can clear defensively without guarding.
|
|
174
|
-
*/
|
|
175
343
|
clear(id) {
|
|
176
344
|
if (this.ids.delete(id)) {
|
|
177
345
|
this.cancel(id);
|
|
178
346
|
}
|
|
179
347
|
}
|
|
180
|
-
/**
|
|
181
|
-
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
182
|
-
* to guarantee no timer outlives the element.
|
|
183
|
-
*/
|
|
184
348
|
clearAll() {
|
|
185
349
|
for (const id of this.ids) {
|
|
186
350
|
this.cancel(id);
|
|
187
351
|
}
|
|
188
352
|
this.ids.clear();
|
|
189
353
|
}
|
|
190
|
-
/** Number of timers currently tracked (pending). */
|
|
191
354
|
get size() {
|
|
192
355
|
return this.ids.size;
|
|
193
356
|
}
|
|
194
357
|
};
|
|
195
358
|
var SafeTimeout = class extends TimerRegistry {
|
|
196
|
-
/**
|
|
197
|
-
* Schedules `callback` after `delay` ms and returns the timer id.
|
|
198
|
-
*
|
|
199
|
-
* The id is removed from the registry automatically when the timeout fires,
|
|
200
|
-
* so {@link TimerRegistry.size | size} reflects only still-pending timers.
|
|
201
|
-
*/
|
|
202
359
|
set(callback, delay) {
|
|
203
360
|
const id = this.schedule(() => {
|
|
204
361
|
this.ids.delete(id);
|
|
@@ -215,9 +372,13 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
215
372
|
}
|
|
216
373
|
};
|
|
217
374
|
|
|
218
|
-
// src/controllers/submit_once_controller.ts
|
|
219
375
|
var BUTTON_BUSY_LABEL = "data-submit-once-busy-label";
|
|
220
|
-
var
|
|
376
|
+
var LABEL_RECORD = "label";
|
|
377
|
+
var SUBMITTER_MARK = "submitter";
|
|
378
|
+
var SubmitOnceController = class _SubmitOnceController extends Controller {
|
|
379
|
+
#textLabel = new OwnedText(this.#labelRecord);
|
|
380
|
+
#valueLabel = new OwnedText(this.#labelRecord, "value");
|
|
381
|
+
#numbers = new NumberValueReader();
|
|
221
382
|
static targets = ["submit", "idle", "busy"];
|
|
222
383
|
static values = {
|
|
223
384
|
announceText: { type: String, default: "" },
|
|
@@ -226,26 +387,27 @@ var SubmitOnceController = class extends Controller {
|
|
|
226
387
|
timeout: { type: Number, default: 0 },
|
|
227
388
|
restoreFocus: { type: Boolean, default: false }
|
|
228
389
|
};
|
|
390
|
+
static valueConstraints = {
|
|
391
|
+
timeout: NUMBER_BOUNDS.timer
|
|
392
|
+
};
|
|
229
393
|
static actions = ["cancel", "finish", "start"];
|
|
230
394
|
static events = ["start", "end", "reconcile"];
|
|
231
395
|
#timers = new SafeTimeout();
|
|
232
396
|
#gate = new DetachGate();
|
|
233
397
|
#listeners = new ListenerSet();
|
|
234
|
-
#beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
|
|
235
398
|
#sessions = /* @__PURE__ */ new Map();
|
|
236
|
-
#disabled = new AttributeLease("disabled");
|
|
237
|
-
#controlBusy = new AttributeLease("aria-busy");
|
|
238
|
-
#formBusy = new AttributeLease("aria-busy");
|
|
239
|
-
#submitting = new AttributeLease("data-submitting");
|
|
240
|
-
#ariaLabel = new AttributeLease("aria-label");
|
|
241
|
-
#hidden = new AttributeLease("hidden");
|
|
399
|
+
#disabled = new AttributeLease("disabled", this.identifier);
|
|
400
|
+
#controlBusy = new AttributeLease("aria-busy", this.identifier);
|
|
401
|
+
#formBusy = new AttributeLease("aria-busy", this.identifier);
|
|
402
|
+
#submitting = new AttributeLease("data-submitting", this.identifier);
|
|
403
|
+
#ariaLabel = new AttributeLease("aria-label", this.identifier);
|
|
404
|
+
#hidden = new AttributeLease("hidden", this.identifier);
|
|
242
405
|
#onSubmitStart = (event) => {
|
|
243
406
|
this.start(event);
|
|
244
407
|
};
|
|
245
408
|
#onSubmitEnd = (event) => {
|
|
246
409
|
this.#complete(this.#eventForm(event), "turbo", this.#eventSuccess(event));
|
|
247
410
|
};
|
|
248
|
-
/** Blocks a later native submit while one for the same form is still active. */
|
|
249
411
|
#onNativeSubmit = (event) => {
|
|
250
412
|
const session = this.#sessionFor(event.target);
|
|
251
413
|
if (!session) return;
|
|
@@ -254,24 +416,67 @@ var SubmitOnceController = class extends Controller {
|
|
|
254
416
|
};
|
|
255
417
|
connect() {
|
|
256
418
|
this.#gate.cancel();
|
|
257
|
-
this.#beforeCache.activate();
|
|
258
419
|
this.#listeners.add(this.element, "submit", this.#onNativeSubmit, { capture: true });
|
|
259
420
|
this.#listeners.add(this.element, "turbo:submit-start", this.#onSubmitStart);
|
|
260
421
|
this.#listeners.add(this.element, "turbo:submit-end", this.#onSubmitEnd);
|
|
422
|
+
if (this.#sessions.size === 0) this.#releaseInherited();
|
|
261
423
|
}
|
|
262
424
|
disconnect() {
|
|
263
425
|
this.#listeners.dispose();
|
|
264
|
-
this.#beforeCache.deactivate();
|
|
265
426
|
this.#gate.disconnected(this, () => this.#teardown());
|
|
266
427
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
428
|
+
get #labelRecord() {
|
|
429
|
+
return `data-${this.identifier}-${LABEL_RECORD}`;
|
|
430
|
+
}
|
|
431
|
+
get #submitterMark() {
|
|
432
|
+
return `data-${this.identifier}-${SUBMITTER_MARK}`;
|
|
433
|
+
}
|
|
434
|
+
#releaseInherited() {
|
|
435
|
+
const forms = this.#ownedForms();
|
|
436
|
+
const controls = new Set(
|
|
437
|
+
this.element.querySelectorAll(`[${this.#labelRecord}]`)
|
|
438
|
+
);
|
|
439
|
+
for (const form of forms) {
|
|
440
|
+
for (const control of this.#submitControls(form)) {
|
|
441
|
+
if (control.hasAttribute(this.#labelRecord)) controls.add(control);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
for (const control of controls) {
|
|
445
|
+
if (control.form && !this.#ownsForm(control.form)) continue;
|
|
446
|
+
this.#restoreLabel(control);
|
|
447
|
+
}
|
|
448
|
+
const copied = forms.filter((form) => this.#returnCopied(form));
|
|
449
|
+
if (copied.length > 0) this.dispatch("reconcile", { detail: { forms: copied } });
|
|
450
|
+
}
|
|
451
|
+
#returnCopied(form) {
|
|
452
|
+
let copied = this.#submitting.return(form);
|
|
453
|
+
this.#formBusy.return(form);
|
|
454
|
+
const parts = [...this.idleTargets, ...this.busyTargets];
|
|
455
|
+
for (const control of this.#submitControls(form)) {
|
|
456
|
+
this.#disabled.return(control);
|
|
457
|
+
this.#controlBusy.return(control);
|
|
458
|
+
if (control instanceof HTMLButtonElement) this.#ariaLabel.return(control);
|
|
459
|
+
for (const part of parts) if (control.contains(part)) this.#hidden.return(part);
|
|
460
|
+
if (!control.hasAttribute(this.#submitterMark)) continue;
|
|
461
|
+
control.removeAttribute(this.#submitterMark);
|
|
462
|
+
control.disabled = false;
|
|
463
|
+
if (control.getAttribute("aria-disabled") === "true")
|
|
464
|
+
control.removeAttribute("aria-disabled");
|
|
465
|
+
copied = true;
|
|
466
|
+
}
|
|
467
|
+
if (copied && form.getAttribute("aria-busy") === "true") form.removeAttribute("aria-busy");
|
|
468
|
+
return copied;
|
|
469
|
+
}
|
|
470
|
+
#restoreLabel(control) {
|
|
471
|
+
const owner = control instanceof HTMLInputElement ? this.#valueLabel : this.#textLabel;
|
|
472
|
+
owner.restore(control);
|
|
473
|
+
owner.forget(control);
|
|
474
|
+
}
|
|
475
|
+
#ownedForms() {
|
|
476
|
+
const forms = Array.from(this.element.querySelectorAll("form"));
|
|
477
|
+
if (this.element instanceof HTMLFormElement) forms.unshift(this.element);
|
|
478
|
+
return forms.filter((form) => this.#ownsForm(form));
|
|
479
|
+
}
|
|
275
480
|
start(event) {
|
|
276
481
|
if (event.defaultPrevented) return;
|
|
277
482
|
const form = this.#eventForm(event);
|
|
@@ -289,54 +494,33 @@ var SubmitOnceController = class extends Controller {
|
|
|
289
494
|
busyPart: null
|
|
290
495
|
};
|
|
291
496
|
this.#sessions.set(form, session);
|
|
497
|
+
submitter?.setAttribute(this.#submitterMark, "");
|
|
292
498
|
this.#formBusy.write(form, "true");
|
|
293
499
|
this.#submitting.write(form, "true");
|
|
294
500
|
this.#syncControls(session, controls);
|
|
295
501
|
this.#enterLabel(session);
|
|
296
502
|
this.dispatch("start", { detail: { form, submitter } });
|
|
297
|
-
if (this
|
|
503
|
+
if (this.#safeTimeout > 0) {
|
|
298
504
|
session.timeoutId = this.#timers.set(
|
|
299
505
|
() => this.#complete(form, "timeout", false),
|
|
300
|
-
this
|
|
506
|
+
this.#safeTimeout
|
|
301
507
|
);
|
|
302
508
|
}
|
|
303
509
|
announce(this.announceTextValue);
|
|
304
510
|
}
|
|
305
|
-
/**
|
|
306
|
-
* Completes a non-Turbo async submission for the form associated with
|
|
307
|
-
* `event`. When called directly with no event, it completes the controller's
|
|
308
|
-
* sole active form (or the controller element itself when it is a form).
|
|
309
|
-
*/
|
|
310
511
|
finish(event) {
|
|
311
512
|
const form = event ? this.#eventForm(event) : this.#defaultForm();
|
|
312
513
|
this.#complete(form, "manual", event ? this.#eventSuccess(event) : void 0);
|
|
313
514
|
}
|
|
314
|
-
/**
|
|
315
|
-
* Abandons the submission for the form associated with `event` because it
|
|
316
|
-
* never reached the network — a veto from validation, or a request the
|
|
317
|
-
* consumer gave up before sending. With no event it targets the controller's
|
|
318
|
-
* sole active form, exactly like {@link finish}.
|
|
319
|
-
*
|
|
320
|
-
* The distinction from `finish` is observable: a request that ran and failed
|
|
321
|
-
* is `finish` with `success: false` and still announces completion, while a
|
|
322
|
-
* submission that never happened announces nothing.
|
|
323
|
-
*/
|
|
324
515
|
cancel(event) {
|
|
325
516
|
const form = event ? this.#eventForm(event) : this.#defaultForm();
|
|
326
517
|
this.#complete(form, "canceled", false);
|
|
327
518
|
}
|
|
328
|
-
/** Disables a newly connected explicit submit target during its form's session. */
|
|
329
519
|
submitTargetConnected(target) {
|
|
330
520
|
const session = this.#sessionFor(target.form);
|
|
331
521
|
if (!session || !this.#isNativeSubmitControl(target)) return;
|
|
332
522
|
this.#enterControl(session, target);
|
|
333
523
|
}
|
|
334
|
-
/**
|
|
335
|
-
* Ends one session, restores owned state, and reports the observable completion.
|
|
336
|
-
*
|
|
337
|
-
* @stimeoRuntimeOnly `announceReadyText` words the one announcement of this finished submission
|
|
338
|
-
* and `restoreFocus` decides where its focus returns.
|
|
339
|
-
*/
|
|
340
524
|
#complete(form, reason, success) {
|
|
341
525
|
const session = this.#sessionFor(form);
|
|
342
526
|
if (!session) return;
|
|
@@ -347,23 +531,15 @@ var SubmitOnceController = class extends Controller {
|
|
|
347
531
|
this.#restoreFocus(session);
|
|
348
532
|
if (reason !== "canceled") announce(this.announceReadyTextValue);
|
|
349
533
|
}
|
|
350
|
-
/** Applies the busy state to every current native/explicit control exactly once. */
|
|
351
534
|
#syncControls(session, controls = this.#submitControls(session.form)) {
|
|
352
535
|
for (const control of controls) this.#enterControl(session, control);
|
|
353
536
|
}
|
|
354
|
-
/** Temporarily disables one enabled submit control while retaining authored state. */
|
|
355
537
|
#enterControl(session, control) {
|
|
356
538
|
if (session.controls.has(control) || control.matches(":disabled")) return;
|
|
357
539
|
session.controls.add(control);
|
|
358
540
|
this.#disabled.write(control, "");
|
|
359
541
|
this.#controlBusy.write(control, "true");
|
|
360
542
|
}
|
|
361
|
-
/**
|
|
362
|
-
* Swaps only the triggering control's safe label channel.
|
|
363
|
-
*
|
|
364
|
-
* @stimeoRuntimeOnly `busyLabel` is the label this one submission shows until it ends; the idle
|
|
365
|
-
* label comes back with the state it returns.
|
|
366
|
-
*/
|
|
367
543
|
#enterLabel(session) {
|
|
368
544
|
const control = session.submitter;
|
|
369
545
|
if (!control) return;
|
|
@@ -379,13 +555,9 @@ var SubmitOnceController = class extends Controller {
|
|
|
379
555
|
if (label.length === 0) return;
|
|
380
556
|
if (control instanceof HTMLInputElement) {
|
|
381
557
|
if (control.type !== "submit") return;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
original: control.value,
|
|
386
|
-
written: label
|
|
387
|
-
};
|
|
388
|
-
control.value = label;
|
|
558
|
+
const owner2 = new OwnedText(this.#labelRecord, "value");
|
|
559
|
+
session.labelWrite = { control, owner: owner2 };
|
|
560
|
+
owner2.write(control, label, control.value);
|
|
389
561
|
return;
|
|
390
562
|
}
|
|
391
563
|
if (control.hasAttribute("aria-label")) {
|
|
@@ -393,30 +565,22 @@ var SubmitOnceController = class extends Controller {
|
|
|
393
565
|
return;
|
|
394
566
|
}
|
|
395
567
|
if (control.childElementCount > 0) return;
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
original: control.textContent ?? "",
|
|
400
|
-
written: label
|
|
401
|
-
};
|
|
402
|
-
control.textContent = label;
|
|
568
|
+
const owner = new OwnedText(this.#labelRecord);
|
|
569
|
+
session.labelWrite = { control, owner };
|
|
570
|
+
owner.write(control, label, control.textContent ?? "");
|
|
403
571
|
}
|
|
404
|
-
|
|
405
|
-
#returnSessionState(session) {
|
|
572
|
+
#returnSessionState(session, unfinished = false) {
|
|
406
573
|
if (session.timeoutId !== null) {
|
|
407
574
|
this.#timers.clear(session.timeoutId);
|
|
408
575
|
session.timeoutId = null;
|
|
409
576
|
}
|
|
410
|
-
if (session.labelWrite
|
|
411
|
-
const { control,
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const { control, original, written } = session.labelWrite;
|
|
415
|
-
if (control.childElementCount === 0 && control.textContent === written) {
|
|
416
|
-
control.textContent = original;
|
|
417
|
-
}
|
|
577
|
+
if (session.labelWrite) {
|
|
578
|
+
const { control, owner } = session.labelWrite;
|
|
579
|
+
owner.restore(control, { source: "live" });
|
|
580
|
+
owner.forget(control);
|
|
418
581
|
}
|
|
419
582
|
session.labelWrite = null;
|
|
583
|
+
if (!unfinished) session.submitter?.removeAttribute(this.#submitterMark);
|
|
420
584
|
if (session.submitter instanceof HTMLButtonElement) {
|
|
421
585
|
this.#ariaLabel.return(session.submitter);
|
|
422
586
|
}
|
|
@@ -428,11 +592,9 @@ var SubmitOnceController = class extends Controller {
|
|
|
428
592
|
this.#controlBusy.return(control);
|
|
429
593
|
this.#disabled.return(control);
|
|
430
594
|
}
|
|
431
|
-
session.controls.clear();
|
|
432
595
|
this.#submitting.return(session.form);
|
|
433
596
|
this.#formBusy.return(session.form);
|
|
434
597
|
}
|
|
435
|
-
/** Restores focus only when disabling the focused submitter actually stranded it. */
|
|
436
598
|
#restoreFocus(session) {
|
|
437
599
|
const submitter = session.submitter;
|
|
438
600
|
if (!this.restoreFocusValue || !session.submitterHadFocus || !submitter?.isConnected) return;
|
|
@@ -441,30 +603,18 @@ var SubmitOnceController = class extends Controller {
|
|
|
441
603
|
submitter.focus();
|
|
442
604
|
}
|
|
443
605
|
}
|
|
444
|
-
/** Silently abandons every session before caching or on a real detach. */
|
|
445
606
|
#abandonSessions() {
|
|
446
607
|
this.#timers.clearAll();
|
|
608
|
+
const departed = !this.element.isConnected;
|
|
447
609
|
for (const session of this.#sessions.values()) {
|
|
448
610
|
session.timeoutId = null;
|
|
449
|
-
this.#returnSessionState(session);
|
|
611
|
+
this.#returnSessionState(session, departed);
|
|
450
612
|
}
|
|
451
613
|
this.#sessions.clear();
|
|
452
614
|
}
|
|
453
|
-
/**
|
|
454
|
-
* Keeps Turbo's cached snapshot idle without `end`, announcements, or focus
|
|
455
|
-
* moves; the abandoned forms are reported once as `reconcile`.
|
|
456
|
-
*/
|
|
457
|
-
#rewindForCache() {
|
|
458
|
-
const forms = [...this.#sessions.keys()];
|
|
459
|
-
this.#abandonSessions();
|
|
460
|
-
if (forms.length > 0) this.dispatch("reconcile", { detail: { forms } });
|
|
461
|
-
}
|
|
462
|
-
/** Tears down a true detach; an in-page move is canceled by the next `connect()`. */
|
|
463
615
|
#teardown() {
|
|
464
|
-
this.#gate.cancel();
|
|
465
616
|
this.#abandonSessions();
|
|
466
617
|
}
|
|
467
|
-
/** All native controls associated with `form`, plus valid explicit targets. */
|
|
468
618
|
#submitControls(form) {
|
|
469
619
|
const controls = /* @__PURE__ */ new Set();
|
|
470
620
|
for (const element of Array.from(form.elements)) {
|
|
@@ -476,13 +626,11 @@ var SubmitOnceController = class extends Controller {
|
|
|
476
626
|
}
|
|
477
627
|
return Array.from(controls);
|
|
478
628
|
}
|
|
479
|
-
/** Resolves the real submitter, falling back to the first enabled submit control. */
|
|
480
629
|
#resolveSubmitter(event, controls) {
|
|
481
630
|
const submitter = this.#eventSubmitter(event);
|
|
482
631
|
if (submitter && controls.includes(submitter)) return submitter;
|
|
483
632
|
return controls.find((control) => !control.matches(":disabled")) ?? controls[0] ?? null;
|
|
484
633
|
}
|
|
485
|
-
/** Reads a native or Turbo submitter without assuming `SubmitEvent` exists. */
|
|
486
634
|
#eventSubmitter(event) {
|
|
487
635
|
if (typeof SubmitEvent !== "undefined" && event instanceof SubmitEvent) {
|
|
488
636
|
return this.#isNativeSubmitControl(event.submitter) ? event.submitter : null;
|
|
@@ -491,21 +639,18 @@ var SubmitOnceController = class extends Controller {
|
|
|
491
639
|
const submitter = detail?.formSubmission?.submitter;
|
|
492
640
|
return this.#isNativeSubmitControl(submitter) ? submitter : null;
|
|
493
641
|
}
|
|
494
|
-
/** Finds the form represented by an action/Turbo event and verifies this scope owns it. */
|
|
495
642
|
#eventForm(event) {
|
|
496
643
|
const detail = event.detail;
|
|
497
644
|
const target = event.target;
|
|
498
645
|
const candidate = (target instanceof HTMLFormElement ? target : null) ?? (target instanceof Element ? target.closest("form") : null) ?? detail?.formSubmission?.formElement ?? detail?.form ?? this.#eventSubmitter(event)?.form ?? (this.element instanceof HTMLFormElement ? this.element : null);
|
|
499
646
|
return candidate && this.#ownsForm(candidate) ? candidate : null;
|
|
500
647
|
}
|
|
501
|
-
/** The only unambiguous form for a direct `finish()` call. */
|
|
502
648
|
#defaultForm() {
|
|
503
649
|
if (this.element instanceof HTMLFormElement && this.#ownsForm(this.element))
|
|
504
650
|
return this.element;
|
|
505
651
|
if (this.#sessions.size === 1) return this.#sessions.keys().next().value ?? null;
|
|
506
652
|
return null;
|
|
507
653
|
}
|
|
508
|
-
/** Prevents an ancestor controller from also taking over a nested instance's form. */
|
|
509
654
|
#ownsForm(form) {
|
|
510
655
|
let candidate = form;
|
|
511
656
|
while (candidate) {
|
|
@@ -515,29 +660,33 @@ var SubmitOnceController = class extends Controller {
|
|
|
515
660
|
}
|
|
516
661
|
return false;
|
|
517
662
|
}
|
|
518
|
-
/** Returns the active session for a form-shaped external event value. */
|
|
519
663
|
#sessionFor(candidate) {
|
|
520
664
|
if (!(candidate instanceof HTMLFormElement)) return void 0;
|
|
521
665
|
return this.#sessions.get(candidate);
|
|
522
666
|
}
|
|
523
|
-
/** Effective native submit controls, including implicit buttons and image inputs. */
|
|
524
667
|
#isNativeSubmitControl(element) {
|
|
525
668
|
if (element instanceof HTMLButtonElement) return element.type === "submit";
|
|
526
669
|
return element instanceof HTMLInputElement && (element.type === "submit" || element.type === "image");
|
|
527
670
|
}
|
|
528
|
-
/** Finds a structured label part belonging to this button, not a nested control. */
|
|
529
671
|
#partInside(control, parts) {
|
|
530
672
|
return parts.find(
|
|
531
673
|
(part) => control.contains(part) && part.closest("button, input[type=submit], input[type=image]") === control
|
|
532
674
|
) ?? null;
|
|
533
675
|
}
|
|
534
|
-
/** Reads an optional boolean `success` from Turbo or a manual completion event. */
|
|
535
676
|
#eventSuccess(event) {
|
|
536
677
|
const success = event.detail?.success;
|
|
537
678
|
return typeof success === "boolean" ? success : void 0;
|
|
538
679
|
}
|
|
680
|
+
get #safeTimeout() {
|
|
681
|
+
return this.#numbers.read(
|
|
682
|
+
this,
|
|
683
|
+
"timeout",
|
|
684
|
+
this.timeoutValue,
|
|
685
|
+
_SubmitOnceController.values.timeout.default,
|
|
686
|
+
_SubmitOnceController.valueConstraints.timeout
|
|
687
|
+
);
|
|
688
|
+
}
|
|
539
689
|
};
|
|
540
690
|
|
|
541
691
|
export { SubmitOnceController };
|
|
542
|
-
//# sourceMappingURL=submit_once_controller.js.map
|
|
543
692
|
//# sourceMappingURL=submit_once_controller.js.map
|