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,14 +1,21 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function sharedRegistry(key, create) {
|
|
4
|
+
const symbol = Symbol.for(key);
|
|
5
|
+
const scope = globalThis;
|
|
6
|
+
const existing = scope[symbol];
|
|
7
|
+
if (existing !== void 0) return existing;
|
|
8
|
+
const registry2 = create();
|
|
9
|
+
Object.defineProperty(globalThis, symbol, { value: registry2 });
|
|
10
|
+
return registry2;
|
|
11
|
+
}
|
|
4
12
|
|
|
5
|
-
|
|
6
|
-
var counter = 0;
|
|
13
|
+
var ids = sharedRegistry("stimeo-ui.aria-ids.registry.v1", () => ({ counter: 0 }));
|
|
7
14
|
function uniqueId(prefix = "stimeo") {
|
|
8
15
|
let candidate;
|
|
9
16
|
do {
|
|
10
|
-
counter += 1;
|
|
11
|
-
candidate = `${prefix}-${counter}`;
|
|
17
|
+
ids.counter += 1;
|
|
18
|
+
candidate = `${prefix}-${ids.counter}`;
|
|
12
19
|
} while (typeof document !== "undefined" && document.getElementById(candidate) !== null);
|
|
13
20
|
return candidate;
|
|
14
21
|
}
|
|
@@ -19,12 +26,10 @@ function ensureId(element, prefix = "stimeo") {
|
|
|
19
26
|
return id;
|
|
20
27
|
}
|
|
21
28
|
|
|
22
|
-
// src/utils/logical_scroll.ts
|
|
23
29
|
function isRtl(element) {
|
|
24
30
|
return window.getComputedStyle(element).direction === "rtl";
|
|
25
31
|
}
|
|
26
32
|
|
|
27
|
-
// src/utils/arrow_step.ts
|
|
28
33
|
function logicalArrowKey(key, element) {
|
|
29
34
|
if (key !== "ArrowRight" && key !== "ArrowLeft") return key;
|
|
30
35
|
if (!isRtl(element)) return key;
|
|
@@ -34,157 +39,338 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
34
39
|
if (!event.key.startsWith("Arrow")) return false;
|
|
35
40
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
36
41
|
}
|
|
42
|
+
function hasModifierChord(event) {
|
|
43
|
+
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
47
|
+
if (element.getAttribute(name) === value) return false;
|
|
48
|
+
if (value === null) element.removeAttribute(name);
|
|
49
|
+
else element.setAttribute(name, value);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
37
52
|
|
|
38
|
-
|
|
53
|
+
var registry = sharedRegistry(
|
|
54
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
55
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
56
|
+
);
|
|
57
|
+
function isAttributeValue(value) {
|
|
58
|
+
return value === null || typeof value === "string";
|
|
59
|
+
}
|
|
60
|
+
function readLeaseRecord(element, record) {
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
63
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
64
|
+
const [original, written, ...rest] = parsed;
|
|
65
|
+
const linked = rest[0] !== false;
|
|
66
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
67
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
68
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
69
|
+
} catch {
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function attributeStack(element, attribute) {
|
|
74
|
+
const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
75
|
+
registry.stacks.set(element, byAttribute);
|
|
76
|
+
const known = byAttribute.get(attribute);
|
|
77
|
+
if (known) return known;
|
|
78
|
+
const found = [];
|
|
79
|
+
for (const record of registry.names.get(attribute) ?? []) {
|
|
80
|
+
const read = readLeaseRecord(element, record);
|
|
81
|
+
if (read) found.push({ ...read, record });
|
|
82
|
+
}
|
|
83
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
84
|
+
const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
85
|
+
lease: null,
|
|
86
|
+
record,
|
|
87
|
+
written,
|
|
88
|
+
original,
|
|
89
|
+
linked: linked && index > 0
|
|
90
|
+
}));
|
|
91
|
+
byAttribute.set(attribute, stack);
|
|
92
|
+
return stack;
|
|
93
|
+
}
|
|
94
|
+
function findHold(stack, lease, record) {
|
|
95
|
+
return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
|
|
96
|
+
}
|
|
39
97
|
var AttributeLease = class {
|
|
40
98
|
#attribute;
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
99
|
+
#record;
|
|
100
|
+
#base;
|
|
101
|
+
#preserveAuthor;
|
|
102
|
+
#held = /* @__PURE__ */ new Set();
|
|
103
|
+
constructor(attribute, owner, options = {}) {
|
|
44
104
|
this.#attribute = attribute;
|
|
105
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
106
|
+
this.#base = options.base === true;
|
|
107
|
+
this.#preserveAuthor = options.preserveAuthor === true;
|
|
108
|
+
const names = registry.names;
|
|
109
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
45
110
|
}
|
|
46
|
-
/** Writes or removes the leased attribute while preserving its authored value. */
|
|
47
111
|
write(element, value) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
this.#reflect(element, value);
|
|
112
|
+
const stack = attributeStack(element, this.#attribute);
|
|
113
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
114
|
+
this.#held.add(element);
|
|
115
|
+
const shows = !this.#covered(element, stack, hold);
|
|
116
|
+
hold.written = value;
|
|
117
|
+
this.#mark(element, stack);
|
|
118
|
+
if (shows) this.#reflect(element, value);
|
|
58
119
|
}
|
|
59
|
-
/** Returns one lease without overwriting a value subsequently authored by a consumer. */
|
|
60
120
|
return(element) {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
this.#
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
121
|
+
const held = this.#held.delete(element);
|
|
122
|
+
const known = registry.stacks.get(element)?.has(this.#attribute);
|
|
123
|
+
const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
|
|
124
|
+
const hold = this.#take(stack);
|
|
125
|
+
if (!hold) return false;
|
|
126
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
127
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
128
|
+
const index = stack.indexOf(hold);
|
|
129
|
+
stack.splice(index, 1);
|
|
130
|
+
const above = stack[index];
|
|
131
|
+
if (above) above.linked &&= hold.linked;
|
|
132
|
+
this.#mark(element, stack);
|
|
133
|
+
if (!owns) return true;
|
|
134
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
135
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
136
|
+
return true;
|
|
72
137
|
}
|
|
73
|
-
/** Returns every outstanding lease using the same ownership check as {@link return}. */
|
|
74
138
|
returnAll() {
|
|
75
|
-
for (const element of Array.from(this.#
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
139
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
140
|
+
}
|
|
141
|
+
#take(stack) {
|
|
142
|
+
const hold = findHold(stack, this, this.#record);
|
|
143
|
+
if (hold) hold.lease = this;
|
|
144
|
+
return hold;
|
|
145
|
+
}
|
|
146
|
+
#acquire(element, stack) {
|
|
147
|
+
const current = element.getAttribute(this.#attribute);
|
|
148
|
+
const top = stack.at(-1);
|
|
149
|
+
const bottom = stack[0];
|
|
150
|
+
const over = top?.written === current ? top : void 0;
|
|
151
|
+
const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
|
|
152
|
+
const hold = {
|
|
153
|
+
lease: this,
|
|
154
|
+
record: this.#record,
|
|
155
|
+
written: current,
|
|
156
|
+
original: from ? from.original : current,
|
|
157
|
+
linked: !this.#base && over !== void 0
|
|
158
|
+
};
|
|
159
|
+
if (this.#base && bottom) {
|
|
160
|
+
bottom.linked = true;
|
|
161
|
+
stack.unshift(hold);
|
|
162
|
+
} else stack.push(hold);
|
|
163
|
+
return hold;
|
|
164
|
+
}
|
|
165
|
+
#covered(element, stack, hold) {
|
|
166
|
+
const current = element.getAttribute(this.#attribute);
|
|
167
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
168
|
+
}
|
|
169
|
+
#mark(element, stack) {
|
|
170
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
171
|
+
stack.forEach((hold, index) => {
|
|
172
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
173
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
174
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
175
|
+
records.set(
|
|
176
|
+
hold.record,
|
|
177
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
for (const [name, recorded] of records) {
|
|
181
|
+
setAttributeIfChanged(element, name, recorded);
|
|
98
182
|
}
|
|
99
183
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
_BeforeCacheReset.#subscribers.delete(this);
|
|
103
|
-
if (_BeforeCacheReset.#subscribers.size > 0) return;
|
|
104
|
-
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
184
|
+
#reflect(element, value) {
|
|
185
|
+
setAttributeIfChanged(element, this.#attribute, value);
|
|
105
186
|
}
|
|
106
187
|
};
|
|
107
188
|
|
|
108
|
-
// src/utils/composition_tracker.ts
|
|
109
189
|
var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
110
190
|
"insertCompositionText",
|
|
111
191
|
"insertFromComposition",
|
|
112
192
|
"deleteCompositionText",
|
|
113
193
|
"deleteByComposition"
|
|
114
194
|
]);
|
|
195
|
+
function isCompositionInputType(inputType) {
|
|
196
|
+
return inputType !== void 0 && COMPOSITION_INPUT_TYPES.has(inputType);
|
|
197
|
+
}
|
|
198
|
+
var BEFORE_MORPH_ATTRIBUTE = "turbo:before-morph-attribute";
|
|
199
|
+
var textOf = (element) => element.value;
|
|
200
|
+
var holdsFocus = (element) => element instanceof Node && element.getRootNode().activeElement === element;
|
|
115
201
|
var CompositionTracker = class {
|
|
116
202
|
#observedTargets = /* @__PURE__ */ new Set();
|
|
117
|
-
#
|
|
203
|
+
#composition = null;
|
|
118
204
|
#onStart;
|
|
119
205
|
#onEnd;
|
|
120
|
-
|
|
206
|
+
#onAbandon;
|
|
121
207
|
#confirmedTarget = null;
|
|
208
|
+
#keyComposing = false;
|
|
209
|
+
#connected = false;
|
|
210
|
+
#abandoned = null;
|
|
122
211
|
constructor(options = {}) {
|
|
123
212
|
this.#onStart = options.onStart;
|
|
124
213
|
this.#onEnd = options.onEnd;
|
|
214
|
+
this.#onAbandon = options.onAbandon;
|
|
125
215
|
}
|
|
126
|
-
/** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
|
|
127
216
|
observe(target) {
|
|
217
|
+
this.#connected = true;
|
|
128
218
|
if (this.#observedTargets.has(target)) return;
|
|
129
219
|
target.addEventListener("compositionstart", this.#handleStart);
|
|
130
220
|
target.addEventListener("compositionend", this.#handleEnd);
|
|
131
|
-
target.addEventListener("keydown", this.#handleKeydown);
|
|
221
|
+
target.addEventListener("keydown", this.#handleKeydown, true);
|
|
222
|
+
target.addEventListener("beforeinput", this.#handleBeforeInput, true);
|
|
223
|
+
target.addEventListener("input", this.#handleInput, true);
|
|
224
|
+
target.addEventListener("blur", this.#handleBlur, true);
|
|
225
|
+
target.addEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
|
|
132
226
|
this.#observedTargets.add(target);
|
|
133
227
|
}
|
|
134
|
-
/** Stops tracking one target and clears any active composition it owned. */
|
|
135
228
|
unobserve(target) {
|
|
136
229
|
if (!this.#observedTargets.delete(target)) return;
|
|
137
|
-
|
|
138
|
-
target
|
|
139
|
-
target.removeEventListener("keydown", this.#handleKeydown);
|
|
140
|
-
this.#activeTargets.delete(target);
|
|
230
|
+
this.#release(target);
|
|
231
|
+
if (this.#composition?.target === target) this.#letGo();
|
|
141
232
|
if (this.#confirmedTarget === target) this.#confirmedTarget = null;
|
|
142
233
|
}
|
|
143
|
-
/** Releases every listener and clears state so reconnect starts cleanly. */
|
|
144
234
|
disconnect() {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
target.removeEventListener("compositionend", this.#handleEnd);
|
|
148
|
-
target.removeEventListener("keydown", this.#handleKeydown);
|
|
149
|
-
}
|
|
235
|
+
this.#connected = false;
|
|
236
|
+
for (const target of this.#observedTargets) this.#release(target);
|
|
150
237
|
this.#observedTargets.clear();
|
|
151
|
-
this.#
|
|
238
|
+
this.#letGo();
|
|
152
239
|
this.#confirmedTarget = null;
|
|
240
|
+
this.#keyComposing = false;
|
|
241
|
+
this.#abandoned = null;
|
|
153
242
|
}
|
|
154
|
-
/** True when lifecycle tracking or the current event reports composition. */
|
|
155
243
|
isComposing(event) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
244
|
+
this.#settle();
|
|
245
|
+
return event?.isComposing === true || this.#composition !== null;
|
|
246
|
+
}
|
|
247
|
+
composingElement() {
|
|
248
|
+
this.#settle();
|
|
249
|
+
return this.#composition?.element ?? null;
|
|
250
|
+
}
|
|
251
|
+
owns(event) {
|
|
252
|
+
const element = this.composingElement();
|
|
253
|
+
return event.isComposing === true || element !== null && element === event.target;
|
|
254
|
+
}
|
|
164
255
|
consumesConfirmedInput(event) {
|
|
165
256
|
const confirmed = this.#confirmedTarget;
|
|
166
257
|
this.#confirmedTarget = null;
|
|
167
258
|
if (confirmed === null || confirmed !== event.target) return false;
|
|
168
259
|
const inputType = event.inputType;
|
|
169
|
-
return !inputType ||
|
|
260
|
+
return !inputType || isCompositionInputType(inputType);
|
|
170
261
|
}
|
|
262
|
+
#release(target) {
|
|
263
|
+
target.removeEventListener("compositionstart", this.#handleStart);
|
|
264
|
+
target.removeEventListener("compositionend", this.#handleEnd);
|
|
265
|
+
target.removeEventListener("keydown", this.#handleKeydown, true);
|
|
266
|
+
target.removeEventListener("beforeinput", this.#handleBeforeInput, true);
|
|
267
|
+
target.removeEventListener("input", this.#handleInput, true);
|
|
268
|
+
target.removeEventListener("blur", this.#handleBlur, true);
|
|
269
|
+
target.removeEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
|
|
270
|
+
}
|
|
271
|
+
#letGo() {
|
|
272
|
+
const composition = this.#composition;
|
|
273
|
+
if (composition === null) return;
|
|
274
|
+
if (!holdsFocus(composition.element)) {
|
|
275
|
+
this.#composition = null;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
queueMicrotask(() => {
|
|
279
|
+
if (this.#composition !== composition || this.#observedTargets.has(composition.target)) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
this.#composition = null;
|
|
283
|
+
if (!this.#connected) return;
|
|
284
|
+
this.#abandoned = { element: composition.element, inPlace: true };
|
|
285
|
+
this.#runAbandon();
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
#settle() {
|
|
289
|
+
const composition = this.#composition;
|
|
290
|
+
if (composition === null) return;
|
|
291
|
+
const { target, element, text } = composition;
|
|
292
|
+
if (target instanceof Node && element instanceof Node && !target.contains(element)) {
|
|
293
|
+
this.#composition = null;
|
|
294
|
+
} else if (textOf(element) !== text) {
|
|
295
|
+
this.#abandon(false);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
#abandon(inPlace) {
|
|
299
|
+
this.#owe(inPlace);
|
|
300
|
+
this.#composition = null;
|
|
301
|
+
queueMicrotask(this.#runAbandon);
|
|
302
|
+
}
|
|
303
|
+
#owe(inPlace) {
|
|
304
|
+
const composition = this.#composition;
|
|
305
|
+
if (composition !== null) this.#abandoned = { element: composition.element, inPlace };
|
|
306
|
+
}
|
|
307
|
+
#runAbandon = () => {
|
|
308
|
+
const abandoned = this.#abandoned;
|
|
309
|
+
if (abandoned === null) return;
|
|
310
|
+
this.#abandoned = null;
|
|
311
|
+
this.#onAbandon?.(abandoned.element, abandoned.inPlace);
|
|
312
|
+
};
|
|
171
313
|
#handleStart = (event) => {
|
|
172
314
|
this.#confirmedTarget = null;
|
|
173
|
-
|
|
315
|
+
this.#settle();
|
|
316
|
+
this.#owe(true);
|
|
317
|
+
const target = event.currentTarget;
|
|
318
|
+
const element = event.target;
|
|
319
|
+
this.#composition = { target, element, text: textOf(element) };
|
|
320
|
+
this.#runAbandon();
|
|
174
321
|
this.#onStart?.(event);
|
|
175
322
|
};
|
|
176
323
|
#handleEnd = (event) => {
|
|
177
|
-
|
|
324
|
+
this.#runAbandon();
|
|
325
|
+
if (this.#composition?.target === event.currentTarget) this.#composition = null;
|
|
178
326
|
this.#confirmedTarget = event.target;
|
|
179
327
|
this.#onEnd?.(event);
|
|
180
328
|
};
|
|
181
|
-
|
|
182
|
-
|
|
329
|
+
#handleKeydown = (event) => {
|
|
330
|
+
this.#settle();
|
|
331
|
+
this.#runAbandon();
|
|
183
332
|
this.#confirmedTarget = null;
|
|
333
|
+
this.#keyComposing = event.isComposing === true;
|
|
334
|
+
};
|
|
335
|
+
#handleBeforeInput = (event) => {
|
|
336
|
+
const keyComposing = this.#keyComposing;
|
|
337
|
+
this.#keyComposing = false;
|
|
338
|
+
this.#settle();
|
|
339
|
+
const { isComposing, inputType } = event;
|
|
340
|
+
if (this.#composition !== null && !keyComposing && !isComposing && inputType && !isCompositionInputType(inputType)) {
|
|
341
|
+
this.#abandon(true);
|
|
342
|
+
}
|
|
343
|
+
this.#runAbandon();
|
|
344
|
+
};
|
|
345
|
+
#handleInput = (event) => {
|
|
346
|
+
const composition = this.#composition;
|
|
347
|
+
const { isTrusted, isComposing, inputType } = event;
|
|
348
|
+
if (composition !== null && event.target === composition.element && (isTrusted || isComposing || isCompositionInputType(inputType))) {
|
|
349
|
+
composition.text = textOf(composition.element);
|
|
350
|
+
} else {
|
|
351
|
+
this.#settle();
|
|
352
|
+
}
|
|
353
|
+
this.#runAbandon();
|
|
354
|
+
};
|
|
355
|
+
#handleBlur = (event) => {
|
|
356
|
+
this.#settle();
|
|
357
|
+
this.#runAbandon();
|
|
358
|
+
const composition = this.#composition;
|
|
359
|
+
if (composition === null || composition.target === composition.element) return;
|
|
360
|
+
const { element } = composition;
|
|
361
|
+
if (!(element instanceof Node) || event.target !== element) return;
|
|
362
|
+
const root = element.getRootNode();
|
|
363
|
+
if (root.activeElement !== element) this.#composition = null;
|
|
364
|
+
};
|
|
365
|
+
#handleBeforeMorphAttribute = (event) => {
|
|
366
|
+
this.#settle();
|
|
367
|
+
const composition = this.#composition;
|
|
368
|
+
if (composition?.target !== event.currentTarget || event.target !== composition.element) return;
|
|
369
|
+
const detail = event.detail;
|
|
370
|
+
if (detail?.attributeName === "value") event.preventDefault();
|
|
184
371
|
};
|
|
185
372
|
};
|
|
186
373
|
|
|
187
|
-
// src/utils/declared_value.ts
|
|
188
374
|
function parseDeclared(raw, parse, fallback) {
|
|
189
375
|
try {
|
|
190
376
|
return parse(raw);
|
|
@@ -203,25 +389,18 @@ function compileRegExp(source, anchor = "none") {
|
|
|
203
389
|
);
|
|
204
390
|
}
|
|
205
391
|
|
|
206
|
-
// src/utils/form_reset_watcher.ts
|
|
207
392
|
var FormResetWatcher = class {
|
|
208
393
|
#owns;
|
|
209
394
|
#onReset;
|
|
210
395
|
#frames = /* @__PURE__ */ new Set();
|
|
211
396
|
#generation = 0;
|
|
212
|
-
/**
|
|
213
|
-
* @param owns - whether `form` owns a control this controller derives from.
|
|
214
|
-
* @param onReset - the reconciliation pass, run once per honoured reset.
|
|
215
|
-
*/
|
|
216
397
|
constructor(owns, onReset) {
|
|
217
398
|
this.#owns = owns;
|
|
218
399
|
this.#onReset = onReset;
|
|
219
400
|
}
|
|
220
|
-
/** Subscribes to `reset` at the document; call from `connect()`. Idempotent. */
|
|
221
401
|
observe() {
|
|
222
402
|
document.addEventListener("reset", this.#handleReset, true);
|
|
223
403
|
}
|
|
224
|
-
/** Unsubscribes and drops every pending pass; call from `disconnect()`. */
|
|
225
404
|
disconnect() {
|
|
226
405
|
document.removeEventListener("reset", this.#handleReset, true);
|
|
227
406
|
this.#generation += 1;
|
|
@@ -245,13 +424,11 @@ var FormResetWatcher = class {
|
|
|
245
424
|
this.#frames.add(frame);
|
|
246
425
|
});
|
|
247
426
|
};
|
|
248
|
-
/** Runs the pass unless a listener cancelled the reset before it landed. */
|
|
249
427
|
#settle(event) {
|
|
250
428
|
if (!event.defaultPrevented) this.#onReset();
|
|
251
429
|
}
|
|
252
430
|
};
|
|
253
431
|
|
|
254
|
-
// src/utils/half_width.ts
|
|
255
432
|
var FULL_WIDTH_SHIFT = 65248;
|
|
256
433
|
function halfWidthChar(char) {
|
|
257
434
|
if (char >= "\uFF01" && char <= "\uFF5E") {
|
|
@@ -265,27 +442,94 @@ function toHalfWidth(text) {
|
|
|
265
442
|
return out;
|
|
266
443
|
}
|
|
267
444
|
|
|
268
|
-
|
|
445
|
+
var TimerRegistry = class {
|
|
446
|
+
ids = /* @__PURE__ */ new Set();
|
|
447
|
+
clear(id) {
|
|
448
|
+
if (this.ids.delete(id)) {
|
|
449
|
+
this.cancel(id);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
clearAll() {
|
|
453
|
+
for (const id of this.ids) {
|
|
454
|
+
this.cancel(id);
|
|
455
|
+
}
|
|
456
|
+
this.ids.clear();
|
|
457
|
+
}
|
|
458
|
+
get size() {
|
|
459
|
+
return this.ids.size;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
var SafeTimeout = class extends TimerRegistry {
|
|
463
|
+
set(callback, delay) {
|
|
464
|
+
const id = this.schedule(() => {
|
|
465
|
+
this.ids.delete(id);
|
|
466
|
+
callback();
|
|
467
|
+
}, delay);
|
|
468
|
+
this.ids.add(id);
|
|
469
|
+
return id;
|
|
470
|
+
}
|
|
471
|
+
schedule(callback, delay) {
|
|
472
|
+
return window.setTimeout(callback, delay);
|
|
473
|
+
}
|
|
474
|
+
cancel(id) {
|
|
475
|
+
window.clearTimeout(id);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
var KeyedTimers = class {
|
|
480
|
+
#timers = new SafeTimeout();
|
|
481
|
+
#pending = /* @__PURE__ */ new Map();
|
|
482
|
+
set(key, callback, delay) {
|
|
483
|
+
this.clear(key);
|
|
484
|
+
const id = this.#timers.set(() => {
|
|
485
|
+
this.#pending.delete(key);
|
|
486
|
+
callback();
|
|
487
|
+
}, delay);
|
|
488
|
+
this.#pending.set(key, id);
|
|
489
|
+
}
|
|
490
|
+
clear(key) {
|
|
491
|
+
this.#timers.clear(this.#pending.get(key) ?? -1);
|
|
492
|
+
this.#pending.delete(key);
|
|
493
|
+
}
|
|
494
|
+
clearAll() {
|
|
495
|
+
this.#timers.clearAll();
|
|
496
|
+
this.#pending.clear();
|
|
497
|
+
}
|
|
498
|
+
has(key) {
|
|
499
|
+
return this.#pending.has(key);
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
var ListenerSet = class {
|
|
504
|
+
#abort = new AbortController();
|
|
505
|
+
add(target, type, handler, options) {
|
|
506
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
507
|
+
}
|
|
508
|
+
dispose() {
|
|
509
|
+
this.#abort.abort();
|
|
510
|
+
this.#abort = new AbortController();
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
269
514
|
var MicrotaskCoalescer = class {
|
|
270
515
|
#run;
|
|
271
516
|
#queued = false;
|
|
272
517
|
#active = false;
|
|
273
518
|
#generation = 0;
|
|
274
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
275
519
|
constructor(run) {
|
|
276
520
|
this.#run = run;
|
|
277
521
|
}
|
|
278
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
279
522
|
activate() {
|
|
280
523
|
this.#active = true;
|
|
281
524
|
}
|
|
282
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
283
525
|
cancel() {
|
|
284
526
|
this.#active = false;
|
|
285
527
|
this.#queued = false;
|
|
286
528
|
this.#generation += 1;
|
|
287
529
|
}
|
|
288
|
-
|
|
530
|
+
get pending() {
|
|
531
|
+
return this.#queued;
|
|
532
|
+
}
|
|
289
533
|
schedule() {
|
|
290
534
|
if (!this.#active || this.#queued) return;
|
|
291
535
|
this.#queued = true;
|
|
@@ -298,7 +542,45 @@ var MicrotaskCoalescer = class {
|
|
|
298
542
|
}
|
|
299
543
|
};
|
|
300
544
|
|
|
301
|
-
|
|
545
|
+
var MorphRenderWatcher = class {
|
|
546
|
+
#listeners = new ListenerSet();
|
|
547
|
+
#pass;
|
|
548
|
+
#element = null;
|
|
549
|
+
#onMorph = () => this.#pass.schedule();
|
|
550
|
+
constructor(run) {
|
|
551
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
552
|
+
}
|
|
553
|
+
observe(element) {
|
|
554
|
+
if (this.#element === element) return;
|
|
555
|
+
this.disconnect();
|
|
556
|
+
this.#element = element;
|
|
557
|
+
this.#pass.activate();
|
|
558
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
559
|
+
}
|
|
560
|
+
schedule() {
|
|
561
|
+
this.#pass.schedule();
|
|
562
|
+
}
|
|
563
|
+
get pending() {
|
|
564
|
+
return this.#pass.pending;
|
|
565
|
+
}
|
|
566
|
+
disconnect() {
|
|
567
|
+
this.#listeners.dispose();
|
|
568
|
+
this.#pass.cancel();
|
|
569
|
+
this.#element = null;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
var MoveCounter = class {
|
|
574
|
+
#sequence = 0;
|
|
575
|
+
record() {
|
|
576
|
+
this.#sequence += 1;
|
|
577
|
+
return this.#sequence;
|
|
578
|
+
}
|
|
579
|
+
isLatest(token) {
|
|
580
|
+
return token === this.#sequence;
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
|
|
302
584
|
var LEASE_MARKERS = {
|
|
303
585
|
"aria-invalid": "data-otp-invalid-lease",
|
|
304
586
|
"aria-errormessage": "data-otp-errormessage-lease",
|
|
@@ -316,49 +598,39 @@ function readLease(field, marker) {
|
|
|
316
598
|
return null;
|
|
317
599
|
}
|
|
318
600
|
}
|
|
319
|
-
function describedByWith(described, errorId) {
|
|
320
|
-
const tokens = (described ?? "").split(/\s+/).filter((token) => token.length > 0 && token !== errorId);
|
|
601
|
+
function describedByWith(described, errorId, earlier) {
|
|
602
|
+
const tokens = (described ?? "").split(/\s+/).filter((token) => token.length > 0 && token !== errorId && token !== earlier);
|
|
321
603
|
return [...tokens, errorId].join(" ");
|
|
322
604
|
}
|
|
323
605
|
var DEFAULT_PATTERN = "[0-9]";
|
|
324
606
|
var DEFAULT_MATCHER = new RegExp(`^(?:${DEFAULT_PATTERN})$`);
|
|
325
|
-
function hasModifier(event) {
|
|
326
|
-
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
327
|
-
}
|
|
328
607
|
function statesDiffer(left, right) {
|
|
329
608
|
return left.value !== right.value || left.state !== right.state;
|
|
330
609
|
}
|
|
331
610
|
var OtpController = class extends Controller {
|
|
611
|
+
#moves = new MoveCounter();
|
|
332
612
|
static targets = ["field", "value", "error"];
|
|
333
613
|
static values = {
|
|
334
614
|
pattern: { type: String, default: DEFAULT_PATTERN }
|
|
335
615
|
};
|
|
336
616
|
static actions = ["onInput", "onKeydown", "onPaste", "onPointerDown", "clear"];
|
|
337
617
|
static events = ["change", "complete", "invalid", "reconcile"];
|
|
338
|
-
/** Validated matcher; the hot path never compiles a raw declaration. */
|
|
339
618
|
#pattern = DEFAULT_MATCHER;
|
|
340
|
-
/** Source of {@link #pattern}, reported in `invalid` so consumers can word it. */
|
|
341
619
|
#patternSource = DEFAULT_PATTERN;
|
|
342
|
-
/** Public state carried by the last dispatch; keeps a no-op sync silent. */
|
|
343
620
|
#published = null;
|
|
344
|
-
/** True between connect and disconnect, so pre-connect Value changes stay silent. */
|
|
345
621
|
#connected = false;
|
|
346
|
-
/** Digit each field last committed, restored when rejected input replaced it. */
|
|
347
622
|
#committed = /* @__PURE__ */ new WeakMap();
|
|
348
|
-
|
|
623
|
+
#retained = /* @__PURE__ */ new WeakMap();
|
|
624
|
+
#inputBaselines = /* @__PURE__ */ new Map();
|
|
625
|
+
#inputExpiry = new KeyedTimers();
|
|
626
|
+
#endBaselines = /* @__PURE__ */ new Map();
|
|
627
|
+
#away = /* @__PURE__ */ new Set();
|
|
349
628
|
#composing = null;
|
|
350
|
-
/** A `pattern` change whose rejected characters wait for the composition to end. */
|
|
351
629
|
#dropHeld = false;
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
#reconcile = new MicrotaskCoalescer(() => this.#reconcileFields());
|
|
357
|
-
#errorHidden = new AttributeLease("hidden");
|
|
358
|
-
#state = new AttributeLease("data-state");
|
|
359
|
-
/** Rewinds the transient error surface before Turbo freezes the page. */
|
|
360
|
-
#beforeCache = new BeforeCacheReset(() => this.#clearError());
|
|
361
|
-
/** Reads the fields back once a native reset restored them, as a reconciliation. */
|
|
630
|
+
#judgedBy = null;
|
|
631
|
+
#reconcile = new MorphRenderWatcher(() => this.#reconcileFields());
|
|
632
|
+
#errorHidden = new AttributeLease("hidden", this.identifier);
|
|
633
|
+
#state = new AttributeLease("data-state", this.identifier);
|
|
362
634
|
#formReset = new FormResetWatcher(
|
|
363
635
|
(form) => this.#ownedBy(form),
|
|
364
636
|
() => {
|
|
@@ -366,114 +638,150 @@ var OtpController = class extends Controller {
|
|
|
366
638
|
this.#reconcile.schedule();
|
|
367
639
|
}
|
|
368
640
|
);
|
|
369
|
-
/**
|
|
370
|
-
* Owns IME lifecycle state across every digit field. When a composition ends, a
|
|
371
|
-
* `pattern` drop it held back applies first — it was declared first — and the
|
|
372
|
-
* user's commit is taken after it.
|
|
373
|
-
*/
|
|
374
641
|
#composition = new CompositionTracker({
|
|
375
642
|
onStart: (event) => {
|
|
376
643
|
const field = this.#fieldFrom(event);
|
|
377
|
-
|
|
644
|
+
this.#clearInputBaseline();
|
|
645
|
+
if (field) this.#endBaselines.delete(field);
|
|
646
|
+
if (field && this.#composition.composingElement() === field) {
|
|
647
|
+
this.#composing = { field, before: field.value };
|
|
648
|
+
}
|
|
378
649
|
},
|
|
379
650
|
onEnd: (event) => {
|
|
380
|
-
const composing = this.#composing;
|
|
381
|
-
this.#composing = null;
|
|
382
651
|
const input = this.#fieldFrom(event);
|
|
652
|
+
if (!input) return;
|
|
653
|
+
const baseline = this.#inputBaselines.get(input) ?? this.#endBaselines.get(input)?.baseline;
|
|
654
|
+
this.#clearEdits(input);
|
|
655
|
+
const composing = this.#composing;
|
|
656
|
+
if (!this.#composition.isComposing()) this.#composing = null;
|
|
383
657
|
const data = event.data ?? "";
|
|
384
|
-
const text =
|
|
385
|
-
const commit =
|
|
386
|
-
if (this.#
|
|
387
|
-
|
|
658
|
+
const text = data.length <= input.value.length ? input.value : data;
|
|
659
|
+
const commit = composing?.field !== input || text !== composing.before;
|
|
660
|
+
if (commit) this.#accept(input, text, baseline?.interrupted ? baseline : void 0);
|
|
661
|
+
else {
|
|
662
|
+
this.#restoreRetained(input);
|
|
663
|
+
if (this.#dropHeld) this.#reconcileFields();
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
onAbandon: (element, inPlace) => {
|
|
667
|
+
this.#composing = null;
|
|
668
|
+
if (inPlace && element instanceof HTMLInputElement && this.fieldTargets.includes(element)) {
|
|
669
|
+
const baseline = this.#inputBaselines.get(element) ?? {
|
|
670
|
+
field: element,
|
|
671
|
+
before: this.#committed.get(element) ?? "",
|
|
672
|
+
dropHeld: this.#dropHeld,
|
|
673
|
+
interrupted: true
|
|
674
|
+
};
|
|
675
|
+
baseline.interrupted = true;
|
|
676
|
+
this.#settleResidual(element);
|
|
677
|
+
this.#endBaselines.set(element, { text: element.value, baseline });
|
|
388
678
|
this.#reconcileFields();
|
|
679
|
+
} else if (this.#dropHeld) {
|
|
680
|
+
this.#reconcile.schedule();
|
|
389
681
|
}
|
|
390
|
-
if (input && commit) this.#accept(input, text);
|
|
391
682
|
}
|
|
392
683
|
});
|
|
393
684
|
connect() {
|
|
394
685
|
this.#connected = true;
|
|
395
686
|
for (const field of this.fieldTargets) this.#bind(field);
|
|
396
687
|
this.#formReset.observe();
|
|
397
|
-
this.#
|
|
398
|
-
this.#reconcile.activate();
|
|
688
|
+
this.#reconcile.observe(this.element);
|
|
399
689
|
this.#adopt();
|
|
400
690
|
this.#sync();
|
|
691
|
+
this.#judgedBy ??= this.#patternSource;
|
|
692
|
+
if (this.#judgedBy !== this.#patternSource) {
|
|
693
|
+
if (this.#composition.isComposing()) this.#dropHeld = true;
|
|
694
|
+
else if (this.#dropRejected()) this.#reconcile.schedule();
|
|
695
|
+
}
|
|
401
696
|
}
|
|
402
697
|
disconnect() {
|
|
403
698
|
this.#connected = false;
|
|
404
699
|
for (const field of this.fieldTargets) this.#unbind(field);
|
|
405
700
|
this.#composition.disconnect();
|
|
701
|
+
this.#clearInputBaseline();
|
|
702
|
+
for (const field of this.#endBaselines.keys())
|
|
703
|
+
if (!this.#away.has(field)) this.#endBaselines.delete(field);
|
|
406
704
|
this.#formReset.disconnect();
|
|
407
|
-
this.#
|
|
408
|
-
this.#reconcile.cancel();
|
|
705
|
+
this.#reconcile.disconnect();
|
|
409
706
|
this.#dropHeld = false;
|
|
410
707
|
this.#clearError();
|
|
411
708
|
this.#state.return(this.element);
|
|
412
709
|
}
|
|
413
|
-
/**
|
|
414
|
-
* Stimulus lifecycle callback when a new field target enters the DOM.
|
|
415
|
-
* Wires the new field and folds the wider digit count into one reconciliation,
|
|
416
|
-
* which stays inert until `connect()` opens the window.
|
|
417
|
-
*/
|
|
418
710
|
fieldTargetConnected(element) {
|
|
419
711
|
this.#bind(element);
|
|
420
712
|
this.#adoptField(element);
|
|
421
713
|
this.#reconcile.schedule();
|
|
422
714
|
}
|
|
423
|
-
/** Releases a dropped field's listeners and leases, then reconciles the rest. */
|
|
424
715
|
fieldTargetDisconnected(element) {
|
|
425
716
|
this.#unbind(element);
|
|
426
|
-
this.#returnLeases(element);
|
|
717
|
+
if (!this.fieldTargets.includes(element)) this.#returnLeases(element);
|
|
718
|
+
this.#reconcile.schedule();
|
|
719
|
+
}
|
|
720
|
+
valueTargetConnected() {
|
|
427
721
|
this.#reconcile.schedule();
|
|
428
722
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
723
|
+
valueTargetDisconnected() {
|
|
724
|
+
this.#reconcile.schedule();
|
|
725
|
+
}
|
|
726
|
+
errorTargetConnected() {
|
|
727
|
+
this.#followError();
|
|
728
|
+
}
|
|
729
|
+
errorTargetDisconnected(element) {
|
|
730
|
+
if (!this.errorTargets.includes(element)) this.#errorHidden.return(element);
|
|
731
|
+
this.#followError();
|
|
732
|
+
}
|
|
436
733
|
patternValueChanged() {
|
|
437
734
|
const compiled = compileRegExp(this.patternValue, "exact");
|
|
438
735
|
this.#patternSource = compiled ? this.patternValue : DEFAULT_PATTERN;
|
|
439
736
|
this.#pattern = compiled ?? DEFAULT_MATCHER;
|
|
440
737
|
if (!this.#connected) return;
|
|
441
|
-
if (this.#
|
|
738
|
+
if (this.#composition.isComposing()) {
|
|
442
739
|
this.#dropHeld = true;
|
|
443
740
|
return;
|
|
444
741
|
}
|
|
445
742
|
if (this.#dropRejected()) this.#reconcile.schedule();
|
|
446
743
|
}
|
|
447
|
-
|
|
448
|
-
|
|
744
|
+
#dropRejected(composing = null) {
|
|
745
|
+
if (composing === null) this.#judgedBy = this.#patternSource;
|
|
449
746
|
let dropped = false;
|
|
450
747
|
for (const field of this.fieldTargets) {
|
|
451
|
-
if (field
|
|
452
|
-
|
|
453
|
-
|
|
748
|
+
if (field === composing) {
|
|
749
|
+
const committed = this.#committed.get(field) ?? "";
|
|
750
|
+
if (committed === "" || !this.#isWritable(field) || this.#pattern.test(committed)) continue;
|
|
751
|
+
this.#committed.set(field, "");
|
|
752
|
+
dropped = true;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
const value = this.#settledText(field);
|
|
756
|
+
if (value === "" || !this.#isWritable(field)) continue;
|
|
757
|
+
if (this.#pattern.test(value)) continue;
|
|
758
|
+
if (this.#retained.has(field)) this.#holdText(field, "");
|
|
759
|
+
else this.#writeField(field, "");
|
|
454
760
|
dropped = true;
|
|
455
761
|
}
|
|
456
762
|
return dropped;
|
|
457
763
|
}
|
|
458
|
-
/** Handles keystroke inputs, distributes autofilled text, and advances focus. */
|
|
459
764
|
onInput(event) {
|
|
460
765
|
const input = this.#fieldFrom(event);
|
|
461
766
|
if (!input) return;
|
|
767
|
+
const baseline = this.#inputBaselines.get(input);
|
|
768
|
+
this.#clearEdits(input);
|
|
462
769
|
if (this.#composition.consumesConfirmedInput(event)) return;
|
|
463
|
-
if (this.#composition.
|
|
464
|
-
this.#accept(input);
|
|
770
|
+
if (this.#composition.owns(event)) return;
|
|
771
|
+
this.#accept(input, input.value, baseline?.interrupted ? baseline : void 0);
|
|
465
772
|
}
|
|
466
|
-
/** Handles Backspace clearing, arrows, and home/end navigation. */
|
|
467
773
|
onKeydown(event) {
|
|
774
|
+
this.#clearInputBaseline();
|
|
468
775
|
if (isReservedArrowChord(event)) return;
|
|
469
776
|
const input = this.#fieldFrom(event);
|
|
470
777
|
if (!input) return;
|
|
471
|
-
if (this.#composition.
|
|
778
|
+
if (this.#composition.owns(event)) return;
|
|
779
|
+
if (!this.#prepareEdit(input)) return;
|
|
472
780
|
const index = this.fieldTargets.indexOf(input);
|
|
473
781
|
const fields = this.fieldTargets;
|
|
474
782
|
switch (logicalArrowKey(event.key, this.element)) {
|
|
475
783
|
case "Backspace":
|
|
476
|
-
if (
|
|
784
|
+
if (hasModifierChord(event)) break;
|
|
477
785
|
if (input.value && this.#isWritable(input)) {
|
|
478
786
|
event.preventDefault();
|
|
479
787
|
this.#writeField(input, "");
|
|
@@ -507,7 +815,7 @@ var OtpController = class extends Controller {
|
|
|
507
815
|
break;
|
|
508
816
|
}
|
|
509
817
|
case "Home": {
|
|
510
|
-
if (
|
|
818
|
+
if (hasModifierChord(event)) break;
|
|
511
819
|
const first = fields.find((field) => this.#isFocusable(field));
|
|
512
820
|
if (first) {
|
|
513
821
|
event.preventDefault();
|
|
@@ -516,7 +824,7 @@ var OtpController = class extends Controller {
|
|
|
516
824
|
break;
|
|
517
825
|
}
|
|
518
826
|
case "End": {
|
|
519
|
-
if (
|
|
827
|
+
if (hasModifierChord(event)) break;
|
|
520
828
|
const last = fields.slice().reverse().find((field) => this.#isFocusable(field));
|
|
521
829
|
if (last) {
|
|
522
830
|
event.preventDefault();
|
|
@@ -526,18 +834,13 @@ var OtpController = class extends Controller {
|
|
|
526
834
|
}
|
|
527
835
|
}
|
|
528
836
|
}
|
|
529
|
-
/** Divides pasted string characters across the available input fields. */
|
|
530
837
|
onPaste(event) {
|
|
531
838
|
const input = this.#fieldFrom(event);
|
|
532
839
|
if (!input) return;
|
|
533
840
|
event.preventDefault();
|
|
534
|
-
this.#
|
|
841
|
+
if (this.#prepareEdit(input))
|
|
842
|
+
this.#distribute(input, toHalfWidth(event.clipboardData?.getData("text") ?? ""), false);
|
|
535
843
|
}
|
|
536
|
-
/**
|
|
537
|
-
* Redirects a pointer landing on an empty field to the earliest empty one, so
|
|
538
|
-
* a passcode is entered in order. Filled fields stay directly reachable for
|
|
539
|
-
* correction, and keyboard focus is left alone.
|
|
540
|
-
*/
|
|
541
844
|
onPointerDown(event) {
|
|
542
845
|
const input = this.#fieldFrom(event);
|
|
543
846
|
if (input?.value !== "") return;
|
|
@@ -546,7 +849,6 @@ var OtpController = class extends Controller {
|
|
|
546
849
|
event.preventDefault();
|
|
547
850
|
first.focus();
|
|
548
851
|
}
|
|
549
|
-
/** Empties every writable field and restarts entry at the first of them. */
|
|
550
852
|
clear() {
|
|
551
853
|
for (const field of this.fieldTargets) {
|
|
552
854
|
if (this.#isWritable(field)) this.#writeField(field, "");
|
|
@@ -555,33 +857,66 @@ var OtpController = class extends Controller {
|
|
|
555
857
|
this.fieldTargets.find((field) => this.#isWritable(field))?.focus();
|
|
556
858
|
this.#syncAndDispatch();
|
|
557
859
|
}
|
|
860
|
+
#onBeforeInput = (event) => {
|
|
861
|
+
const field = event.currentTarget;
|
|
862
|
+
this.#clearEdits(field);
|
|
863
|
+
if (event.isComposing || !event.inputType || isCompositionInputType(event.inputType)) return;
|
|
864
|
+
this.#inputBaselines.set(field, {
|
|
865
|
+
field,
|
|
866
|
+
before: this.#committed.get(field) ?? "",
|
|
867
|
+
dropHeld: this.#dropHeld,
|
|
868
|
+
interrupted: false
|
|
869
|
+
});
|
|
870
|
+
this.#inputExpiry.set(field, () => this.#clearInputBaseline(field), 0);
|
|
871
|
+
};
|
|
872
|
+
#clearInputBaseline(field) {
|
|
873
|
+
if (field) {
|
|
874
|
+
this.#inputBaselines.delete(field);
|
|
875
|
+
this.#inputExpiry.clear(field);
|
|
876
|
+
} else {
|
|
877
|
+
this.#inputBaselines.clear();
|
|
878
|
+
this.#inputExpiry.clearAll();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
#clearEdits(field) {
|
|
882
|
+
this.#clearInputBaseline(field);
|
|
883
|
+
this.#endBaselines.delete(field);
|
|
884
|
+
}
|
|
558
885
|
#onFieldFocus = (event) => {
|
|
559
886
|
const input = event.currentTarget;
|
|
560
887
|
if (input) {
|
|
561
888
|
input.select();
|
|
562
889
|
}
|
|
563
890
|
};
|
|
564
|
-
/** Whether a form owns at least one field or the hidden combined value. */
|
|
565
891
|
#ownedBy(form) {
|
|
566
892
|
if (this.fieldTargets.some((field) => field.form === form)) return true;
|
|
567
893
|
return this.hasValueTarget && this.valueTarget.form === form;
|
|
568
894
|
}
|
|
569
895
|
#bind(field) {
|
|
570
896
|
field.addEventListener("focus", this.#onFieldFocus);
|
|
897
|
+
field.addEventListener("beforeinput", this.#onBeforeInput, true);
|
|
898
|
+
if (this.#away.delete(field) && document.activeElement !== field)
|
|
899
|
+
this.#endBaselines.delete(field);
|
|
571
900
|
this.#composition.observe(field);
|
|
572
901
|
}
|
|
573
902
|
#unbind(field) {
|
|
574
903
|
field.removeEventListener("focus", this.#onFieldFocus);
|
|
904
|
+
field.removeEventListener("beforeinput", this.#onBeforeInput, true);
|
|
905
|
+
this.#clearInputBaseline(field);
|
|
906
|
+
if (document.activeElement === field) this.#holdAway(field);
|
|
907
|
+
else this.#endBaselines.delete(field);
|
|
575
908
|
this.#composition.unobserve(field);
|
|
576
|
-
if (this.#composing?.field === field) this.#composing = null;
|
|
577
909
|
}
|
|
578
|
-
|
|
910
|
+
#holdAway(field) {
|
|
911
|
+
this.#away.add(field);
|
|
912
|
+
queueMicrotask(() => {
|
|
913
|
+
if (this.#away.delete(field)) this.#endBaselines.delete(field);
|
|
914
|
+
});
|
|
915
|
+
}
|
|
579
916
|
#adopt() {
|
|
580
917
|
for (const field of this.fieldTargets) this.#adoptField(field);
|
|
581
918
|
this.#clearError();
|
|
582
|
-
if (this.hasErrorTarget) this.errorTarget.setAttribute("hidden", "");
|
|
583
919
|
}
|
|
584
|
-
/** Writes one error attribute, keeping the first authored value it displaces. */
|
|
585
920
|
#lease(field, attribute, value) {
|
|
586
921
|
const marker = LEASE_MARKERS[attribute];
|
|
587
922
|
const held = readLease(field, marker);
|
|
@@ -589,7 +924,6 @@ var OtpController = class extends Controller {
|
|
|
589
924
|
field.setAttribute(marker, JSON.stringify({ authored, written: value }));
|
|
590
925
|
field.setAttribute(attribute, value);
|
|
591
926
|
}
|
|
592
|
-
/** Gives one field's error attributes back where they still hold what this wrote. */
|
|
593
927
|
#returnLeases(field) {
|
|
594
928
|
for (const [attribute, marker] of Object.entries(LEASE_MARKERS)) {
|
|
595
929
|
const held = readLease(field, marker);
|
|
@@ -600,66 +934,50 @@ var OtpController = class extends Controller {
|
|
|
600
934
|
else field.setAttribute(attribute, held.authored);
|
|
601
935
|
}
|
|
602
936
|
}
|
|
603
|
-
/** Takes one field's current value as the truth behind its derived state. */
|
|
604
937
|
#adoptField(field) {
|
|
605
|
-
this.#
|
|
606
|
-
this.#
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
* new `pattern` rejects, fields a native form reset restored — as one state
|
|
611
|
-
* transition.
|
|
612
|
-
*
|
|
613
|
-
* Nobody edited the passcode here, so the move is reported as `reconcile`:
|
|
614
|
-
* automation listening for `change` must not read a re-render or a reset as an
|
|
615
|
-
* edit, and a passcode that happens to end up full must not fire the
|
|
616
|
-
* `complete` that submits it.
|
|
617
|
-
*
|
|
618
|
-
* Completeness moves on its own when the field count changes: dropping a
|
|
619
|
-
* trailing empty field completes a passcode whose combined value never moved,
|
|
620
|
-
* and adding one un-completes it. Comparing the whole derived state, not the
|
|
621
|
-
* string it contains, is what makes those transitions reportable.
|
|
622
|
-
*
|
|
623
|
-
* A change already in the DOM — a field added or removed, a reset — is
|
|
624
|
-
* published at once, even while a field is composing: that field counts by the
|
|
625
|
-
* character it had committed, never by the IME's uncommitted text. Only a
|
|
626
|
-
* `pattern` drop waits for the composition to end.
|
|
627
|
-
*/
|
|
938
|
+
if (this.#composition.composingElement() === field) return;
|
|
939
|
+
const value = this.#settledText(field);
|
|
940
|
+
this.#committed.set(field, value);
|
|
941
|
+
this.#markFilled(field, value);
|
|
942
|
+
}
|
|
628
943
|
#reconcileFields() {
|
|
629
|
-
|
|
944
|
+
const composing = this.#composition.composingElement();
|
|
945
|
+
if (this.#dropHeld && composing === null) {
|
|
630
946
|
this.#dropHeld = false;
|
|
631
947
|
this.#dropRejected();
|
|
632
948
|
}
|
|
949
|
+
for (const field of this.fieldTargets) {
|
|
950
|
+
if (field === composing) this.#markFilled(field, this.#publishedText(field));
|
|
951
|
+
else this.#adoptField(field);
|
|
952
|
+
}
|
|
633
953
|
const previous = this.#published;
|
|
634
954
|
const current = this.#sync();
|
|
635
|
-
if (previous && !statesDiffer(previous, current)) return;
|
|
955
|
+
if (previous && !statesDiffer(previous, current)) return true;
|
|
956
|
+
const token = this.#moves.record();
|
|
636
957
|
this.dispatch("reconcile", { detail: { value: current.value } });
|
|
958
|
+
return this.#moves.isLatest(token);
|
|
637
959
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
#accept(input, text = input.value) {
|
|
960
|
+
#accept(input, text = input.value, baseline) {
|
|
961
|
+
if (!this.#prepareEdit(input, true)) return;
|
|
962
|
+
const before = baseline ? this.fieldTargets.map(
|
|
963
|
+
(field) => field === input ? baseline.dropHeld && !this.#pattern.test(baseline.before) ? "" : baseline.before : this.#publishedText(field)
|
|
964
|
+
).join("") : void 0;
|
|
644
965
|
const raw = toHalfWidth(text);
|
|
645
966
|
if (raw === "") {
|
|
646
967
|
this.#writeField(input, "");
|
|
647
968
|
this.#clearError();
|
|
648
|
-
this.#syncAndDispatch();
|
|
969
|
+
this.#syncAndDispatch(before);
|
|
649
970
|
return;
|
|
650
971
|
}
|
|
651
|
-
this.#distribute(input, raw);
|
|
972
|
+
this.#distribute(input, raw, true, before);
|
|
652
973
|
}
|
|
653
|
-
|
|
654
|
-
* Fills `text`'s accepted characters into the writable fields at and after the
|
|
655
|
-
* entry point, then leaves focus on the field after the last one filled.
|
|
656
|
-
*/
|
|
657
|
-
#distribute(from, text) {
|
|
974
|
+
#distribute(from, text, spare = true, before) {
|
|
658
975
|
const accepted = Array.from(text).filter((char) => this.#pattern.test(char));
|
|
659
976
|
let reached = false;
|
|
977
|
+
const composing = spare ? this.#composition.composingElement() : null;
|
|
660
978
|
const slots = this.fieldTargets.filter((field) => {
|
|
661
979
|
reached ||= field === from;
|
|
662
|
-
return reached && this.#isWritable(field);
|
|
980
|
+
return reached && field !== composing && this.#isWritable(field);
|
|
663
981
|
});
|
|
664
982
|
const filled = Math.min(accepted.length, slots.length);
|
|
665
983
|
if (filled === 0) {
|
|
@@ -674,16 +992,76 @@ var OtpController = class extends Controller {
|
|
|
674
992
|
if (field && char) this.#writeField(field, char);
|
|
675
993
|
}
|
|
676
994
|
const last = slots[filled - 1];
|
|
677
|
-
if (last) (this.#writableAfter(last) ?? last).focus();
|
|
995
|
+
if (last && composing === null) (this.#writableAfter(last) ?? last).focus();
|
|
678
996
|
this.#clearError();
|
|
679
|
-
this.#syncAndDispatch();
|
|
997
|
+
this.#syncAndDispatch(before);
|
|
998
|
+
}
|
|
999
|
+
#prepareEdit(input, edit = false) {
|
|
1000
|
+
if (!this.#dropHeld) return true;
|
|
1001
|
+
if (edit) this.#holdText(input, this.#committed.get(input) ?? "");
|
|
1002
|
+
const composing = this.#composition.composingElement();
|
|
1003
|
+
if (composing !== null) this.#dropRejected(composing);
|
|
1004
|
+
return this.#reconcileFields() && this.#connected && this.fieldTargets.includes(input) && this.#composition.composingElement() === composing;
|
|
1005
|
+
}
|
|
1006
|
+
#settleResidual(field) {
|
|
1007
|
+
const raw = toHalfWidth(field.value);
|
|
1008
|
+
const previous = this.#committed.get(field) ?? "";
|
|
1009
|
+
const accepts = (text) => Array.from(text).length === 1 && this.#pattern.test(text);
|
|
1010
|
+
const value = raw === "" || accepts(raw) ? raw : accepts(previous) ? previous : "";
|
|
1011
|
+
if (this.#isWritable(field) && this.#composition.composingElement() !== field) {
|
|
1012
|
+
const { selectionStart, selectionEnd } = field;
|
|
1013
|
+
this.#writeField(field, value);
|
|
1014
|
+
if (selectionStart !== null && selectionEnd !== null)
|
|
1015
|
+
field.setSelectionRange(selectionStart, selectionEnd);
|
|
1016
|
+
} else this.#holdText(field, value);
|
|
1017
|
+
}
|
|
1018
|
+
#holdText(field, value) {
|
|
1019
|
+
const retained = { text: field.value, value };
|
|
1020
|
+
this.#retained.set(field, retained);
|
|
1021
|
+
field.setAttribute(this.#retainedMarker, JSON.stringify(retained));
|
|
1022
|
+
this.#committed.set(field, value);
|
|
1023
|
+
}
|
|
1024
|
+
#settledText(field) {
|
|
1025
|
+
const ending = this.#endBaselines.get(field);
|
|
1026
|
+
if (ending && ending.text !== field.value) this.#clearEdits(field);
|
|
1027
|
+
const retained = this.#retained.get(field) ?? this.#readRetained(field);
|
|
1028
|
+
if (retained?.text === field.value) {
|
|
1029
|
+
this.#retained.set(field, retained);
|
|
1030
|
+
const marker = JSON.stringify(retained);
|
|
1031
|
+
if (field.getAttribute(this.#retainedMarker) !== marker)
|
|
1032
|
+
field.setAttribute(this.#retainedMarker, marker);
|
|
1033
|
+
return retained.value;
|
|
1034
|
+
}
|
|
1035
|
+
this.#retained.delete(field);
|
|
1036
|
+
field.removeAttribute(this.#retainedMarker);
|
|
1037
|
+
return field.value;
|
|
1038
|
+
}
|
|
1039
|
+
get #retainedMarker() {
|
|
1040
|
+
return `data-${this.identifier}-interrupted`;
|
|
1041
|
+
}
|
|
1042
|
+
#readRetained(field) {
|
|
1043
|
+
try {
|
|
1044
|
+
const record = JSON.parse(field.getAttribute(this.#retainedMarker) ?? "null");
|
|
1045
|
+
if (typeof record === "object" && record !== null && "text" in record && "value" in record && typeof record.text === "string" && typeof record.value === "string") {
|
|
1046
|
+
return { text: record.text, value: record.value };
|
|
1047
|
+
}
|
|
1048
|
+
} catch {
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
return null;
|
|
1052
|
+
}
|
|
1053
|
+
#restoreRetained(field) {
|
|
1054
|
+
const retained = this.#retained.get(field);
|
|
1055
|
+
if (retained?.text === field.value && this.#isWritable(field))
|
|
1056
|
+
this.#writeField(field, retained.value);
|
|
680
1057
|
}
|
|
681
|
-
/** Restores the digit a field committed before rejected input replaced it. */
|
|
682
1058
|
#restore(field) {
|
|
683
1059
|
this.#writeField(field, this.#committed.get(field) ?? "");
|
|
684
1060
|
}
|
|
685
|
-
/** Commits one field's value and the derived hook that reports it as entered. */
|
|
686
1061
|
#writeField(field, value) {
|
|
1062
|
+
this.#retained.delete(field);
|
|
1063
|
+
this.#endBaselines.delete(field);
|
|
1064
|
+
field.removeAttribute(this.#retainedMarker);
|
|
687
1065
|
field.value = value;
|
|
688
1066
|
this.#committed.set(field, value);
|
|
689
1067
|
this.#markFilled(field, value);
|
|
@@ -711,11 +1089,9 @@ var OtpController = class extends Controller {
|
|
|
711
1089
|
#focusableBefore(index) {
|
|
712
1090
|
return this.#before(index).find((field) => this.#isFocusable(field)) ?? null;
|
|
713
1091
|
}
|
|
714
|
-
/** Fields before `index`, nearest first; empty at the first field. */
|
|
715
1092
|
#before(index) {
|
|
716
1093
|
return this.fieldTargets.slice(0, Math.max(index, 0)).reverse();
|
|
717
1094
|
}
|
|
718
|
-
/** The event's field target, or `null` when the wiring points somewhere else. */
|
|
719
1095
|
#fieldFrom(event) {
|
|
720
1096
|
const input = event.currentTarget;
|
|
721
1097
|
return this.fieldTargets.find((field) => field === input) ?? null;
|
|
@@ -723,19 +1099,13 @@ var OtpController = class extends Controller {
|
|
|
723
1099
|
#combinedValue() {
|
|
724
1100
|
return this.fieldTargets.map((field) => this.#publishedText(field)).join("");
|
|
725
1101
|
}
|
|
726
|
-
/** Every field carries a character, and there is at least one field. */
|
|
727
1102
|
#isComplete() {
|
|
728
1103
|
const fields = this.fieldTargets;
|
|
729
1104
|
return fields.length > 0 && fields.every((field) => this.#publishedText(field).length > 0);
|
|
730
1105
|
}
|
|
731
|
-
/** A field's text as the combined value counts it: a composing field by its committed character. */
|
|
732
1106
|
#publishedText(field) {
|
|
733
|
-
return field === this.#
|
|
1107
|
+
return field === this.#composition.composingElement() ? this.#committed.get(field) ?? "" : this.#settledText(field);
|
|
734
1108
|
}
|
|
735
|
-
/**
|
|
736
|
-
* Mirrors the combined value into the form and the root's readable state, and
|
|
737
|
-
* records what was published so the next pass can compare against it.
|
|
738
|
-
*/
|
|
739
1109
|
#sync() {
|
|
740
1110
|
const combined = this.#combinedValue();
|
|
741
1111
|
if (this.hasValueTarget) {
|
|
@@ -751,35 +1121,68 @@ var OtpController = class extends Controller {
|
|
|
751
1121
|
if (combined.length === 0) return "empty";
|
|
752
1122
|
return this.#isComplete() ? "complete" : "partial";
|
|
753
1123
|
}
|
|
754
|
-
#syncAndDispatch() {
|
|
1124
|
+
#syncAndDispatch(before) {
|
|
755
1125
|
const previous = this.#published;
|
|
756
|
-
const
|
|
757
|
-
|
|
1126
|
+
const current = this.#sync();
|
|
1127
|
+
const combined = current.value;
|
|
1128
|
+
if ((before ?? previous?.value) === combined) {
|
|
1129
|
+
if (previous && statesDiffer(previous, current)) {
|
|
1130
|
+
this.#moves.record();
|
|
1131
|
+
this.dispatch("reconcile", { detail: { value: combined } });
|
|
1132
|
+
}
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
const token = this.#moves.record();
|
|
1136
|
+
this.#endBaselines.clear();
|
|
758
1137
|
this.dispatch("change", { detail: { value: combined } });
|
|
1138
|
+
if (!this.#moves.isLatest(token)) return;
|
|
759
1139
|
if (this.#isComplete()) {
|
|
760
1140
|
this.dispatch("complete", { detail: { value: combined } });
|
|
761
1141
|
}
|
|
762
1142
|
}
|
|
763
|
-
/** Surfaces rejected input on every field and on the optional error target. */
|
|
764
1143
|
#showError() {
|
|
765
1144
|
const errorId = this.hasErrorTarget ? ensureId(this.errorTarget, "stimeo--otp-error") : null;
|
|
766
1145
|
for (const field of this.fieldTargets) {
|
|
767
1146
|
this.#lease(field, "aria-invalid", "true");
|
|
768
|
-
if (
|
|
769
|
-
this.#lease(field, "aria-errormessage", errorId);
|
|
770
|
-
const described = field.getAttribute("aria-describedby");
|
|
771
|
-
this.#lease(field, "aria-describedby", describedByWith(described, errorId));
|
|
1147
|
+
if (errorId) this.#pointAtError(field, errorId);
|
|
772
1148
|
}
|
|
773
1149
|
if (this.hasErrorTarget) this.#errorHidden.write(this.errorTarget, null);
|
|
774
1150
|
this.dispatch("invalid", { detail: { pattern: this.#patternSource } });
|
|
775
1151
|
}
|
|
776
|
-
|
|
1152
|
+
#pointAtError(field, errorId) {
|
|
1153
|
+
const earlier = readLease(field, LEASE_MARKERS["aria-errormessage"])?.written ?? null;
|
|
1154
|
+
this.#lease(field, "aria-errormessage", errorId);
|
|
1155
|
+
const described = field.getAttribute("aria-describedby");
|
|
1156
|
+
this.#lease(field, "aria-describedby", describedByWith(described, errorId, earlier));
|
|
1157
|
+
}
|
|
1158
|
+
#followError() {
|
|
1159
|
+
if (!this.#connected) return;
|
|
1160
|
+
const reported = this.fieldTargets.filter(
|
|
1161
|
+
(field) => readLease(field, LEASE_MARKERS["aria-invalid"]) !== null
|
|
1162
|
+
);
|
|
1163
|
+
if (reported.length === 0) {
|
|
1164
|
+
this.#hideErrors();
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
if (!this.hasErrorTarget) return;
|
|
1168
|
+
const errorId = ensureId(this.errorTarget, "stimeo--otp-error");
|
|
1169
|
+
for (const field of reported) this.#pointAtError(field, errorId);
|
|
1170
|
+
this.#errorHidden.write(this.errorTarget, null);
|
|
1171
|
+
}
|
|
777
1172
|
#clearError() {
|
|
778
1173
|
for (const field of this.fieldTargets) this.#returnLeases(field);
|
|
779
|
-
this.#
|
|
1174
|
+
if (!this.#connected) {
|
|
1175
|
+
this.#errorHidden.returnAll();
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
this.#hideErrors();
|
|
1179
|
+
}
|
|
1180
|
+
#hideErrors() {
|
|
1181
|
+
const [first, ...others] = this.errorTargets;
|
|
1182
|
+
for (const other of others) this.#errorHidden.return(other);
|
|
1183
|
+
if (first) this.#errorHidden.write(first, "");
|
|
780
1184
|
}
|
|
781
1185
|
};
|
|
782
1186
|
|
|
783
1187
|
export { OtpController };
|
|
784
|
-
//# sourceMappingURL=otp_controller.js.map
|
|
785
1188
|
//# sourceMappingURL=otp_controller.js.map
|