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,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,265 @@ function announce(message, options = {}) {
|
|
|
13
10
|
);
|
|
14
11
|
}
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
function sharedRegistry(key, create) {
|
|
14
|
+
const symbol = Symbol.for(key);
|
|
15
|
+
const scope = globalThis;
|
|
16
|
+
const existing = scope[symbol];
|
|
17
|
+
if (existing !== void 0) return existing;
|
|
18
|
+
const registry = create();
|
|
19
|
+
Object.defineProperty(globalThis, symbol, { value: registry });
|
|
20
|
+
return registry;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isAttributeValue(value) {
|
|
24
|
+
return value === null || typeof value === "string";
|
|
25
|
+
}
|
|
26
|
+
function readLeaseRecord(element, record) {
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
29
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
30
|
+
const [original, written, ...rest] = parsed;
|
|
31
|
+
const linked = rest[0] !== false;
|
|
32
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
33
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
34
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
35
|
+
} catch {
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
var AttributeLease = class _AttributeLease {
|
|
40
|
+
static #registry = sharedRegistry(
|
|
41
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
42
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
43
|
+
);
|
|
18
44
|
#attribute;
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
45
|
+
#record;
|
|
46
|
+
#base;
|
|
47
|
+
#held = /* @__PURE__ */ new Set();
|
|
48
|
+
constructor(attribute, owner, options = {}) {
|
|
22
49
|
this.#attribute = attribute;
|
|
50
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
51
|
+
this.#base = options.base === true;
|
|
52
|
+
const names = _AttributeLease.#registry.names;
|
|
53
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
23
54
|
}
|
|
24
|
-
/** Writes or removes the leased attribute while preserving its authored value. */
|
|
25
55
|
write(element, value) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
this.#reflect(element, value);
|
|
56
|
+
const stack = this.#stack(element);
|
|
57
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
58
|
+
this.#held.add(element);
|
|
59
|
+
const shows = !this.#covered(element, stack, hold);
|
|
60
|
+
hold.written = value;
|
|
61
|
+
this.#mark(element, stack);
|
|
62
|
+
if (shows) this.#reflect(element, value);
|
|
36
63
|
}
|
|
37
|
-
/** Returns one lease without overwriting a value subsequently authored by a consumer. */
|
|
38
64
|
return(element) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
this.#
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
65
|
+
const held = this.#held.delete(element);
|
|
66
|
+
const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
|
|
67
|
+
const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
|
|
68
|
+
const hold = this.#take(stack);
|
|
69
|
+
if (!hold) return false;
|
|
70
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
71
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
72
|
+
const index = stack.indexOf(hold);
|
|
73
|
+
stack.splice(index, 1);
|
|
74
|
+
const above = stack[index];
|
|
75
|
+
if (above) above.linked &&= hold.linked;
|
|
76
|
+
this.#mark(element, stack);
|
|
77
|
+
if (!owns) return true;
|
|
78
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
79
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
returnAll() {
|
|
83
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
84
|
+
}
|
|
85
|
+
#stack(element) {
|
|
86
|
+
const stacks = _AttributeLease.#registry.stacks;
|
|
87
|
+
const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
88
|
+
stacks.set(element, byAttribute);
|
|
89
|
+
const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
|
|
90
|
+
byAttribute.set(this.#attribute, stack);
|
|
91
|
+
return stack;
|
|
92
|
+
}
|
|
93
|
+
#seed(element) {
|
|
94
|
+
const found = [];
|
|
95
|
+
for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
|
|
96
|
+
const read = readLeaseRecord(element, record);
|
|
97
|
+
if (read) found.push({ ...read, record });
|
|
98
|
+
}
|
|
99
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
100
|
+
return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
101
|
+
lease: null,
|
|
102
|
+
record,
|
|
103
|
+
written,
|
|
104
|
+
original,
|
|
105
|
+
linked: linked && index > 0
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
#take(stack) {
|
|
109
|
+
const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
|
|
110
|
+
if (hold) hold.lease = this;
|
|
111
|
+
return hold;
|
|
112
|
+
}
|
|
113
|
+
#acquire(element, stack) {
|
|
114
|
+
const current = element.getAttribute(this.#attribute);
|
|
115
|
+
const top = stack.at(-1);
|
|
116
|
+
const bottom = stack[0];
|
|
117
|
+
const over = top?.written === current ? top : void 0;
|
|
118
|
+
const from = this.#base ? bottom : over;
|
|
119
|
+
const hold = {
|
|
120
|
+
lease: this,
|
|
121
|
+
record: this.#record,
|
|
122
|
+
written: current,
|
|
123
|
+
original: from ? from.original : current,
|
|
124
|
+
linked: !this.#base && over !== void 0
|
|
125
|
+
};
|
|
126
|
+
if (this.#base && bottom) {
|
|
127
|
+
bottom.linked = true;
|
|
128
|
+
stack.unshift(hold);
|
|
129
|
+
} else stack.push(hold);
|
|
130
|
+
return hold;
|
|
131
|
+
}
|
|
132
|
+
#covered(element, stack, hold) {
|
|
133
|
+
const current = element.getAttribute(this.#attribute);
|
|
134
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
135
|
+
}
|
|
136
|
+
#mark(element, stack) {
|
|
137
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
138
|
+
stack.forEach((hold, index) => {
|
|
139
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
140
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
141
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
142
|
+
records.set(
|
|
143
|
+
hold.record,
|
|
144
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
for (const [name, recorded] of records) {
|
|
148
|
+
if (element.getAttribute(name) === recorded) continue;
|
|
149
|
+
if (recorded === null) element.removeAttribute(name);
|
|
150
|
+
else element.setAttribute(name, recorded);
|
|
151
|
+
}
|
|
44
152
|
}
|
|
45
|
-
/** Reflects only a real value transition, avoiding self-triggered mutation work. */
|
|
46
153
|
#reflect(element, value) {
|
|
47
154
|
if (element.getAttribute(this.#attribute) === value) return;
|
|
48
155
|
if (value === null) element.removeAttribute(this.#attribute);
|
|
49
156
|
else element.setAttribute(this.#attribute, value);
|
|
50
157
|
}
|
|
51
|
-
/** Returns every outstanding lease using the same ownership check as {@link return}. */
|
|
52
|
-
returnAll() {
|
|
53
|
-
for (const element of Array.from(this.#records.keys())) this.return(element);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// src/utils/before_cache_reset.ts
|
|
58
|
-
var BeforeCacheReset = class _BeforeCacheReset {
|
|
59
|
-
/** Every subscribed instance, iterated by the one shared document listener. */
|
|
60
|
-
static #subscribers = /* @__PURE__ */ new Set();
|
|
61
|
-
/** The shared listener; installed while at least one instance is subscribed. */
|
|
62
|
-
static #onBeforeCache = () => {
|
|
63
|
-
for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
|
|
64
|
-
};
|
|
65
|
-
#rewind;
|
|
66
|
-
/** @param rewind - the pass that returns this controller's state to its initial form. */
|
|
67
|
-
constructor(rewind) {
|
|
68
|
-
this.#rewind = rewind;
|
|
69
|
-
}
|
|
70
|
-
/** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
|
|
71
|
-
activate() {
|
|
72
|
-
const first = _BeforeCacheReset.#subscribers.size === 0;
|
|
73
|
-
_BeforeCacheReset.#subscribers.add(this);
|
|
74
|
-
if (first) {
|
|
75
|
-
document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
/** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
|
|
79
|
-
deactivate() {
|
|
80
|
-
_BeforeCacheReset.#subscribers.delete(this);
|
|
81
|
-
if (_BeforeCacheReset.#subscribers.size > 0) return;
|
|
82
|
-
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
83
|
-
}
|
|
84
158
|
};
|
|
85
159
|
|
|
86
|
-
// src/utils/detach_gate.ts
|
|
87
160
|
var DetachGate = class _DetachGate {
|
|
88
|
-
/** Set while a probe is queued, waiting for a reconnect to cancel it. */
|
|
89
161
|
#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
162
|
get pending() {
|
|
98
163
|
return this.#pending;
|
|
99
164
|
}
|
|
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
165
|
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
|
-
*/
|
|
166
|
+
return !host.element.isConnected || !_DetachGate.#listed(host);
|
|
167
|
+
}
|
|
117
168
|
disconnected(host, teardown) {
|
|
118
|
-
|
|
169
|
+
const carried = _DetachGate.#carried(host);
|
|
170
|
+
if (!carried && _DetachGate.isDetached(host)) {
|
|
119
171
|
this.#pending = false;
|
|
120
172
|
teardown();
|
|
121
173
|
return;
|
|
122
174
|
}
|
|
123
175
|
this.#pending = true;
|
|
124
|
-
|
|
176
|
+
const probe = () => {
|
|
125
177
|
if (!this.#pending) return;
|
|
126
178
|
this.#pending = false;
|
|
127
179
|
teardown();
|
|
128
|
-
}
|
|
180
|
+
};
|
|
181
|
+
if (carried) setTimeout(probe, 0);
|
|
182
|
+
else queueMicrotask(probe);
|
|
183
|
+
}
|
|
184
|
+
static #carried(host) {
|
|
185
|
+
const element = host.element;
|
|
186
|
+
return !element.isConnected && element.closest("[data-turbo-permanent]") !== null && _DetachGate.#listed(host);
|
|
187
|
+
}
|
|
188
|
+
static #listed(host) {
|
|
189
|
+
const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
|
|
190
|
+
return tokens.includes(host.identifier);
|
|
129
191
|
}
|
|
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
192
|
cancel() {
|
|
137
193
|
this.#pending = false;
|
|
138
194
|
}
|
|
139
195
|
};
|
|
140
196
|
|
|
141
|
-
// src/utils/listener_set.ts
|
|
142
197
|
var ListenerSet = class {
|
|
143
|
-
/** The generation every `add` joins until the next `dispose()`. */
|
|
144
198
|
#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
199
|
add(target, type, handler, options) {
|
|
153
200
|
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
154
201
|
}
|
|
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
202
|
dispose() {
|
|
160
203
|
this.#abort.abort();
|
|
161
204
|
this.#abort = new AbortController();
|
|
162
205
|
}
|
|
163
206
|
};
|
|
164
207
|
|
|
165
|
-
|
|
208
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
209
|
+
|
|
210
|
+
var NUMBER_BOUNDS = {
|
|
211
|
+
timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS }};
|
|
212
|
+
function matchesNumberBounds(value, bounds) {
|
|
213
|
+
if (!Number.isFinite(value)) {
|
|
214
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
215
|
+
if (direction === null) return false;
|
|
216
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
217
|
+
}
|
|
218
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
219
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
220
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
221
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
222
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function readNumber(raw, fallback, bounds) {
|
|
227
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
var NumberValueReader = class {
|
|
231
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
232
|
+
read(owner, name, raw, fallback, bounds) {
|
|
233
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
234
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
235
|
+
this.#lastRejected.delete(name);
|
|
236
|
+
return resolved;
|
|
237
|
+
}
|
|
238
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
239
|
+
const literal = owner.element.getAttribute(attribute);
|
|
240
|
+
if (literal === null) {
|
|
241
|
+
this.#lastRejected.delete(name);
|
|
242
|
+
return resolved;
|
|
243
|
+
}
|
|
244
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
245
|
+
this.#lastRejected.set(name, literal);
|
|
246
|
+
console.warn(
|
|
247
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return resolved;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
166
254
|
var TimerRegistry = class {
|
|
167
|
-
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
168
255
|
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
256
|
clear(id) {
|
|
176
257
|
if (this.ids.delete(id)) {
|
|
177
258
|
this.cancel(id);
|
|
178
259
|
}
|
|
179
260
|
}
|
|
180
|
-
/**
|
|
181
|
-
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
182
|
-
* to guarantee no timer outlives the element.
|
|
183
|
-
*/
|
|
184
261
|
clearAll() {
|
|
185
262
|
for (const id of this.ids) {
|
|
186
263
|
this.cancel(id);
|
|
187
264
|
}
|
|
188
265
|
this.ids.clear();
|
|
189
266
|
}
|
|
190
|
-
/** Number of timers currently tracked (pending). */
|
|
191
267
|
get size() {
|
|
192
268
|
return this.ids.size;
|
|
193
269
|
}
|
|
194
270
|
};
|
|
195
271
|
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
272
|
set(callback, delay) {
|
|
203
273
|
const id = this.schedule(() => {
|
|
204
274
|
this.ids.delete(id);
|
|
@@ -215,9 +285,11 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
215
285
|
}
|
|
216
286
|
};
|
|
217
287
|
|
|
218
|
-
// src/controllers/submit_once_controller.ts
|
|
219
288
|
var BUTTON_BUSY_LABEL = "data-submit-once-busy-label";
|
|
220
|
-
var
|
|
289
|
+
var LABEL_RECORD = "label";
|
|
290
|
+
var SUBMITTER_MARK = "submitter";
|
|
291
|
+
var SubmitOnceController = class _SubmitOnceController extends Controller {
|
|
292
|
+
#numbers = new NumberValueReader();
|
|
221
293
|
static targets = ["submit", "idle", "busy"];
|
|
222
294
|
static values = {
|
|
223
295
|
announceText: { type: String, default: "" },
|
|
@@ -226,26 +298,27 @@ var SubmitOnceController = class extends Controller {
|
|
|
226
298
|
timeout: { type: Number, default: 0 },
|
|
227
299
|
restoreFocus: { type: Boolean, default: false }
|
|
228
300
|
};
|
|
301
|
+
static valueConstraints = {
|
|
302
|
+
timeout: NUMBER_BOUNDS.timer
|
|
303
|
+
};
|
|
229
304
|
static actions = ["cancel", "finish", "start"];
|
|
230
305
|
static events = ["start", "end", "reconcile"];
|
|
231
306
|
#timers = new SafeTimeout();
|
|
232
307
|
#gate = new DetachGate();
|
|
233
308
|
#listeners = new ListenerSet();
|
|
234
|
-
#beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
|
|
235
309
|
#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");
|
|
310
|
+
#disabled = new AttributeLease("disabled", this.identifier);
|
|
311
|
+
#controlBusy = new AttributeLease("aria-busy", this.identifier);
|
|
312
|
+
#formBusy = new AttributeLease("aria-busy", this.identifier);
|
|
313
|
+
#submitting = new AttributeLease("data-submitting", this.identifier);
|
|
314
|
+
#ariaLabel = new AttributeLease("aria-label", this.identifier);
|
|
315
|
+
#hidden = new AttributeLease("hidden", this.identifier);
|
|
242
316
|
#onSubmitStart = (event) => {
|
|
243
317
|
this.start(event);
|
|
244
318
|
};
|
|
245
319
|
#onSubmitEnd = (event) => {
|
|
246
320
|
this.#complete(this.#eventForm(event), "turbo", this.#eventSuccess(event));
|
|
247
321
|
};
|
|
248
|
-
/** Blocks a later native submit while one for the same form is still active. */
|
|
249
322
|
#onNativeSubmit = (event) => {
|
|
250
323
|
const session = this.#sessionFor(event.target);
|
|
251
324
|
if (!session) return;
|
|
@@ -254,24 +327,80 @@ var SubmitOnceController = class extends Controller {
|
|
|
254
327
|
};
|
|
255
328
|
connect() {
|
|
256
329
|
this.#gate.cancel();
|
|
257
|
-
this.#beforeCache.activate();
|
|
258
330
|
this.#listeners.add(this.element, "submit", this.#onNativeSubmit, { capture: true });
|
|
259
331
|
this.#listeners.add(this.element, "turbo:submit-start", this.#onSubmitStart);
|
|
260
332
|
this.#listeners.add(this.element, "turbo:submit-end", this.#onSubmitEnd);
|
|
333
|
+
if (this.#sessions.size === 0) this.#releaseInherited();
|
|
261
334
|
}
|
|
262
335
|
disconnect() {
|
|
263
336
|
this.#listeners.dispose();
|
|
264
|
-
this.#beforeCache.deactivate();
|
|
265
337
|
this.#gate.disconnected(this, () => this.#teardown());
|
|
266
338
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
339
|
+
get #labelRecord() {
|
|
340
|
+
return `data-${this.identifier}-${LABEL_RECORD}`;
|
|
341
|
+
}
|
|
342
|
+
get #submitterMark() {
|
|
343
|
+
return `data-${this.identifier}-${SUBMITTER_MARK}`;
|
|
344
|
+
}
|
|
345
|
+
#releaseInherited() {
|
|
346
|
+
const forms = this.#ownedForms();
|
|
347
|
+
const controls = new Set(
|
|
348
|
+
this.element.querySelectorAll(`[${this.#labelRecord}]`)
|
|
349
|
+
);
|
|
350
|
+
for (const form of forms) {
|
|
351
|
+
for (const control of this.#submitControls(form)) {
|
|
352
|
+
if (control.hasAttribute(this.#labelRecord)) controls.add(control);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
for (const control of controls) {
|
|
356
|
+
if (control.form && !this.#ownsForm(control.form)) continue;
|
|
357
|
+
this.#restoreLabel(control);
|
|
358
|
+
}
|
|
359
|
+
const copied = forms.filter((form) => this.#returnCopied(form));
|
|
360
|
+
if (copied.length > 0) this.dispatch("reconcile", { detail: { forms: copied } });
|
|
361
|
+
}
|
|
362
|
+
#returnCopied(form) {
|
|
363
|
+
let copied = this.#submitting.return(form);
|
|
364
|
+
this.#formBusy.return(form);
|
|
365
|
+
const parts = [...this.idleTargets, ...this.busyTargets];
|
|
366
|
+
for (const control of this.#submitControls(form)) {
|
|
367
|
+
this.#disabled.return(control);
|
|
368
|
+
this.#controlBusy.return(control);
|
|
369
|
+
if (control instanceof HTMLButtonElement) this.#ariaLabel.return(control);
|
|
370
|
+
for (const part of parts) if (control.contains(part)) this.#hidden.return(part);
|
|
371
|
+
if (!control.hasAttribute(this.#submitterMark)) continue;
|
|
372
|
+
control.removeAttribute(this.#submitterMark);
|
|
373
|
+
control.disabled = false;
|
|
374
|
+
if (control.getAttribute("aria-disabled") === "true")
|
|
375
|
+
control.removeAttribute("aria-disabled");
|
|
376
|
+
copied = true;
|
|
377
|
+
}
|
|
378
|
+
if (copied && form.getAttribute("aria-busy") === "true") form.removeAttribute("aria-busy");
|
|
379
|
+
return copied;
|
|
380
|
+
}
|
|
381
|
+
#restoreLabel(control) {
|
|
382
|
+
const raw = control.getAttribute(this.#labelRecord);
|
|
383
|
+
control.removeAttribute(this.#labelRecord);
|
|
384
|
+
let record;
|
|
385
|
+
try {
|
|
386
|
+
record = JSON.parse(raw ?? "");
|
|
387
|
+
} catch {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (!Array.isArray(record) || record.length !== 2) return;
|
|
391
|
+
const [original, written] = record;
|
|
392
|
+
if (typeof original !== "string" || typeof written !== "string") return;
|
|
393
|
+
if (control instanceof HTMLInputElement) {
|
|
394
|
+
if (control.value === written) control.value = original;
|
|
395
|
+
} else if (control.childElementCount === 0 && control.textContent === written) {
|
|
396
|
+
control.textContent = original;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
#ownedForms() {
|
|
400
|
+
const forms = Array.from(this.element.querySelectorAll("form"));
|
|
401
|
+
if (this.element instanceof HTMLFormElement) forms.unshift(this.element);
|
|
402
|
+
return forms.filter((form) => this.#ownsForm(form));
|
|
403
|
+
}
|
|
275
404
|
start(event) {
|
|
276
405
|
if (event.defaultPrevented) return;
|
|
277
406
|
const form = this.#eventForm(event);
|
|
@@ -289,54 +418,33 @@ var SubmitOnceController = class extends Controller {
|
|
|
289
418
|
busyPart: null
|
|
290
419
|
};
|
|
291
420
|
this.#sessions.set(form, session);
|
|
421
|
+
submitter?.setAttribute(this.#submitterMark, "");
|
|
292
422
|
this.#formBusy.write(form, "true");
|
|
293
423
|
this.#submitting.write(form, "true");
|
|
294
424
|
this.#syncControls(session, controls);
|
|
295
425
|
this.#enterLabel(session);
|
|
296
426
|
this.dispatch("start", { detail: { form, submitter } });
|
|
297
|
-
if (this
|
|
427
|
+
if (this.#safeTimeout > 0) {
|
|
298
428
|
session.timeoutId = this.#timers.set(
|
|
299
429
|
() => this.#complete(form, "timeout", false),
|
|
300
|
-
this
|
|
430
|
+
this.#safeTimeout
|
|
301
431
|
);
|
|
302
432
|
}
|
|
303
433
|
announce(this.announceTextValue);
|
|
304
434
|
}
|
|
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
435
|
finish(event) {
|
|
311
436
|
const form = event ? this.#eventForm(event) : this.#defaultForm();
|
|
312
437
|
this.#complete(form, "manual", event ? this.#eventSuccess(event) : void 0);
|
|
313
438
|
}
|
|
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
439
|
cancel(event) {
|
|
325
440
|
const form = event ? this.#eventForm(event) : this.#defaultForm();
|
|
326
441
|
this.#complete(form, "canceled", false);
|
|
327
442
|
}
|
|
328
|
-
/** Disables a newly connected explicit submit target during its form's session. */
|
|
329
443
|
submitTargetConnected(target) {
|
|
330
444
|
const session = this.#sessionFor(target.form);
|
|
331
445
|
if (!session || !this.#isNativeSubmitControl(target)) return;
|
|
332
446
|
this.#enterControl(session, target);
|
|
333
447
|
}
|
|
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
448
|
#complete(form, reason, success) {
|
|
341
449
|
const session = this.#sessionFor(form);
|
|
342
450
|
if (!session) return;
|
|
@@ -347,23 +455,15 @@ var SubmitOnceController = class extends Controller {
|
|
|
347
455
|
this.#restoreFocus(session);
|
|
348
456
|
if (reason !== "canceled") announce(this.announceReadyTextValue);
|
|
349
457
|
}
|
|
350
|
-
/** Applies the busy state to every current native/explicit control exactly once. */
|
|
351
458
|
#syncControls(session, controls = this.#submitControls(session.form)) {
|
|
352
459
|
for (const control of controls) this.#enterControl(session, control);
|
|
353
460
|
}
|
|
354
|
-
/** Temporarily disables one enabled submit control while retaining authored state. */
|
|
355
461
|
#enterControl(session, control) {
|
|
356
462
|
if (session.controls.has(control) || control.matches(":disabled")) return;
|
|
357
463
|
session.controls.add(control);
|
|
358
464
|
this.#disabled.write(control, "");
|
|
359
465
|
this.#controlBusy.write(control, "true");
|
|
360
466
|
}
|
|
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
467
|
#enterLabel(session) {
|
|
368
468
|
const control = session.submitter;
|
|
369
469
|
if (!control) return;
|
|
@@ -385,6 +485,7 @@ var SubmitOnceController = class extends Controller {
|
|
|
385
485
|
original: control.value,
|
|
386
486
|
written: label
|
|
387
487
|
};
|
|
488
|
+
control.setAttribute(this.#labelRecord, JSON.stringify([control.value, label]));
|
|
388
489
|
control.value = label;
|
|
389
490
|
return;
|
|
390
491
|
}
|
|
@@ -399,24 +500,27 @@ var SubmitOnceController = class extends Controller {
|
|
|
399
500
|
original: control.textContent ?? "",
|
|
400
501
|
written: label
|
|
401
502
|
};
|
|
503
|
+
control.setAttribute(this.#labelRecord, JSON.stringify([control.textContent ?? "", label]));
|
|
402
504
|
control.textContent = label;
|
|
403
505
|
}
|
|
404
|
-
|
|
405
|
-
#returnSessionState(session) {
|
|
506
|
+
#returnSessionState(session, unfinished = false) {
|
|
406
507
|
if (session.timeoutId !== null) {
|
|
407
508
|
this.#timers.clear(session.timeoutId);
|
|
408
509
|
session.timeoutId = null;
|
|
409
510
|
}
|
|
410
511
|
if (session.labelWrite?.channel === "value") {
|
|
411
512
|
const { control, original, written } = session.labelWrite;
|
|
513
|
+
control.removeAttribute(this.#labelRecord);
|
|
412
514
|
if (control.value === written) control.value = original;
|
|
413
515
|
} else if (session.labelWrite?.channel === "text") {
|
|
414
516
|
const { control, original, written } = session.labelWrite;
|
|
517
|
+
control.removeAttribute(this.#labelRecord);
|
|
415
518
|
if (control.childElementCount === 0 && control.textContent === written) {
|
|
416
519
|
control.textContent = original;
|
|
417
520
|
}
|
|
418
521
|
}
|
|
419
522
|
session.labelWrite = null;
|
|
523
|
+
if (!unfinished) session.submitter?.removeAttribute(this.#submitterMark);
|
|
420
524
|
if (session.submitter instanceof HTMLButtonElement) {
|
|
421
525
|
this.#ariaLabel.return(session.submitter);
|
|
422
526
|
}
|
|
@@ -428,11 +532,9 @@ var SubmitOnceController = class extends Controller {
|
|
|
428
532
|
this.#controlBusy.return(control);
|
|
429
533
|
this.#disabled.return(control);
|
|
430
534
|
}
|
|
431
|
-
session.controls.clear();
|
|
432
535
|
this.#submitting.return(session.form);
|
|
433
536
|
this.#formBusy.return(session.form);
|
|
434
537
|
}
|
|
435
|
-
/** Restores focus only when disabling the focused submitter actually stranded it. */
|
|
436
538
|
#restoreFocus(session) {
|
|
437
539
|
const submitter = session.submitter;
|
|
438
540
|
if (!this.restoreFocusValue || !session.submitterHadFocus || !submitter?.isConnected) return;
|
|
@@ -441,30 +543,18 @@ var SubmitOnceController = class extends Controller {
|
|
|
441
543
|
submitter.focus();
|
|
442
544
|
}
|
|
443
545
|
}
|
|
444
|
-
/** Silently abandons every session before caching or on a real detach. */
|
|
445
546
|
#abandonSessions() {
|
|
446
547
|
this.#timers.clearAll();
|
|
548
|
+
const departed = !this.element.isConnected;
|
|
447
549
|
for (const session of this.#sessions.values()) {
|
|
448
550
|
session.timeoutId = null;
|
|
449
|
-
this.#returnSessionState(session);
|
|
551
|
+
this.#returnSessionState(session, departed);
|
|
450
552
|
}
|
|
451
553
|
this.#sessions.clear();
|
|
452
554
|
}
|
|
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
555
|
#teardown() {
|
|
464
|
-
this.#gate.cancel();
|
|
465
556
|
this.#abandonSessions();
|
|
466
557
|
}
|
|
467
|
-
/** All native controls associated with `form`, plus valid explicit targets. */
|
|
468
558
|
#submitControls(form) {
|
|
469
559
|
const controls = /* @__PURE__ */ new Set();
|
|
470
560
|
for (const element of Array.from(form.elements)) {
|
|
@@ -476,13 +566,11 @@ var SubmitOnceController = class extends Controller {
|
|
|
476
566
|
}
|
|
477
567
|
return Array.from(controls);
|
|
478
568
|
}
|
|
479
|
-
/** Resolves the real submitter, falling back to the first enabled submit control. */
|
|
480
569
|
#resolveSubmitter(event, controls) {
|
|
481
570
|
const submitter = this.#eventSubmitter(event);
|
|
482
571
|
if (submitter && controls.includes(submitter)) return submitter;
|
|
483
572
|
return controls.find((control) => !control.matches(":disabled")) ?? controls[0] ?? null;
|
|
484
573
|
}
|
|
485
|
-
/** Reads a native or Turbo submitter without assuming `SubmitEvent` exists. */
|
|
486
574
|
#eventSubmitter(event) {
|
|
487
575
|
if (typeof SubmitEvent !== "undefined" && event instanceof SubmitEvent) {
|
|
488
576
|
return this.#isNativeSubmitControl(event.submitter) ? event.submitter : null;
|
|
@@ -491,21 +579,18 @@ var SubmitOnceController = class extends Controller {
|
|
|
491
579
|
const submitter = detail?.formSubmission?.submitter;
|
|
492
580
|
return this.#isNativeSubmitControl(submitter) ? submitter : null;
|
|
493
581
|
}
|
|
494
|
-
/** Finds the form represented by an action/Turbo event and verifies this scope owns it. */
|
|
495
582
|
#eventForm(event) {
|
|
496
583
|
const detail = event.detail;
|
|
497
584
|
const target = event.target;
|
|
498
585
|
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
586
|
return candidate && this.#ownsForm(candidate) ? candidate : null;
|
|
500
587
|
}
|
|
501
|
-
/** The only unambiguous form for a direct `finish()` call. */
|
|
502
588
|
#defaultForm() {
|
|
503
589
|
if (this.element instanceof HTMLFormElement && this.#ownsForm(this.element))
|
|
504
590
|
return this.element;
|
|
505
591
|
if (this.#sessions.size === 1) return this.#sessions.keys().next().value ?? null;
|
|
506
592
|
return null;
|
|
507
593
|
}
|
|
508
|
-
/** Prevents an ancestor controller from also taking over a nested instance's form. */
|
|
509
594
|
#ownsForm(form) {
|
|
510
595
|
let candidate = form;
|
|
511
596
|
while (candidate) {
|
|
@@ -515,29 +600,33 @@ var SubmitOnceController = class extends Controller {
|
|
|
515
600
|
}
|
|
516
601
|
return false;
|
|
517
602
|
}
|
|
518
|
-
/** Returns the active session for a form-shaped external event value. */
|
|
519
603
|
#sessionFor(candidate) {
|
|
520
604
|
if (!(candidate instanceof HTMLFormElement)) return void 0;
|
|
521
605
|
return this.#sessions.get(candidate);
|
|
522
606
|
}
|
|
523
|
-
/** Effective native submit controls, including implicit buttons and image inputs. */
|
|
524
607
|
#isNativeSubmitControl(element) {
|
|
525
608
|
if (element instanceof HTMLButtonElement) return element.type === "submit";
|
|
526
609
|
return element instanceof HTMLInputElement && (element.type === "submit" || element.type === "image");
|
|
527
610
|
}
|
|
528
|
-
/** Finds a structured label part belonging to this button, not a nested control. */
|
|
529
611
|
#partInside(control, parts) {
|
|
530
612
|
return parts.find(
|
|
531
613
|
(part) => control.contains(part) && part.closest("button, input[type=submit], input[type=image]") === control
|
|
532
614
|
) ?? null;
|
|
533
615
|
}
|
|
534
|
-
/** Reads an optional boolean `success` from Turbo or a manual completion event. */
|
|
535
616
|
#eventSuccess(event) {
|
|
536
617
|
const success = event.detail?.success;
|
|
537
618
|
return typeof success === "boolean" ? success : void 0;
|
|
538
619
|
}
|
|
620
|
+
get #safeTimeout() {
|
|
621
|
+
return this.#numbers.read(
|
|
622
|
+
this,
|
|
623
|
+
"timeout",
|
|
624
|
+
this.timeoutValue,
|
|
625
|
+
_SubmitOnceController.values.timeout.default,
|
|
626
|
+
_SubmitOnceController.valueConstraints.timeout
|
|
627
|
+
);
|
|
628
|
+
}
|
|
539
629
|
};
|
|
540
630
|
|
|
541
631
|
export { SubmitOnceController };
|
|
542
|
-
//# sourceMappingURL=submit_once_controller.js.map
|
|
543
632
|
//# sourceMappingURL=submit_once_controller.js.map
|