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,13 +1,31 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
|
|
4
|
+
var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
|
|
5
|
+
function stateReasonFor(event) {
|
|
6
|
+
if (!event) return "api";
|
|
7
|
+
if (FOCUS_EVENTS.has(event.type)) return "focus";
|
|
8
|
+
if (POINTER_EVENTS.has(event.type)) return "pointer";
|
|
9
|
+
return "user";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function actionSource(source) {
|
|
13
|
+
if (source instanceof HTMLElement) {
|
|
14
|
+
return { event: null, host: source, origin: source, reason: "api" };
|
|
15
|
+
}
|
|
16
|
+
const event = source ?? null;
|
|
17
|
+
return {
|
|
18
|
+
event,
|
|
19
|
+
host: event?.currentTarget instanceof Element ? event.currentTarget : null,
|
|
20
|
+
origin: event?.target instanceof Element ? event.target : null,
|
|
21
|
+
reason: stateReasonFor(event)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
4
24
|
|
|
5
|
-
// src/utils/logical_scroll.ts
|
|
6
25
|
function isRtl(element) {
|
|
7
26
|
return window.getComputedStyle(element).direction === "rtl";
|
|
8
27
|
}
|
|
9
28
|
|
|
10
|
-
// src/utils/arrow_step.ts
|
|
11
29
|
function logicalArrowStep(key, element) {
|
|
12
30
|
if (key === "ArrowDown") return 1;
|
|
13
31
|
if (key === "ArrowUp") return -1;
|
|
@@ -19,8 +37,108 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
19
37
|
if (!event.key.startsWith("Arrow")) return false;
|
|
20
38
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
21
39
|
}
|
|
40
|
+
function hasModifierChord(event) {
|
|
41
|
+
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
45
|
+
if (element.getAttribute(name) === value) return false;
|
|
46
|
+
beforeWrite?.();
|
|
47
|
+
if (value === null) element.removeAttribute(name);
|
|
48
|
+
else element.setAttribute(name, value);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
var AuthoredMutationWatcher = class {
|
|
53
|
+
#onAuthored;
|
|
54
|
+
#claims = null;
|
|
55
|
+
#observer = null;
|
|
56
|
+
constructor(onAuthored) {
|
|
57
|
+
this.#onAuthored = onAuthored;
|
|
58
|
+
}
|
|
59
|
+
observe(root, options) {
|
|
60
|
+
this.disconnect();
|
|
61
|
+
const observer = new MutationObserver((records) => {
|
|
62
|
+
if (this.#observer !== observer) return;
|
|
63
|
+
const authored = this.#filter(records);
|
|
64
|
+
if (authored.length > 0) this.#onAuthored(authored);
|
|
65
|
+
});
|
|
66
|
+
this.#observer = observer;
|
|
67
|
+
this.#claims = { attributes: /* @__PURE__ */ new Map(), children: /* @__PURE__ */ new Map() };
|
|
68
|
+
observer.observe(root, {
|
|
69
|
+
attributes: true,
|
|
70
|
+
attributeFilter: [...options.attributes],
|
|
71
|
+
subtree: options.subtree ?? false,
|
|
72
|
+
childList: options.childList ?? false
|
|
73
|
+
});
|
|
74
|
+
if (options.ancestorAttributes) {
|
|
75
|
+
let ancestor = root.parentElement;
|
|
76
|
+
while (ancestor) {
|
|
77
|
+
observer.observe(ancestor, {
|
|
78
|
+
attributes: true,
|
|
79
|
+
attributeFilter: [...options.ancestorAttributes]
|
|
80
|
+
});
|
|
81
|
+
ancestor = ancestor.parentElement;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
claim(target, name, value) {
|
|
86
|
+
const claims = this.#claims?.attributes;
|
|
87
|
+
let attributes = claims?.get(target);
|
|
88
|
+
if (!attributes) {
|
|
89
|
+
attributes = /* @__PURE__ */ new Map();
|
|
90
|
+
claims?.set(target, attributes);
|
|
91
|
+
}
|
|
92
|
+
const pending = attributes.get(name);
|
|
93
|
+
attributes.set(name, { value, count: (pending?.count ?? 0) + 1 });
|
|
94
|
+
}
|
|
95
|
+
claimCurrent(target, name) {
|
|
96
|
+
this.claim(target, name, target.getAttribute(name));
|
|
97
|
+
}
|
|
98
|
+
claimChildList(target, added, removed) {
|
|
99
|
+
const claims = this.#claims?.children;
|
|
100
|
+
const pending = claims?.get(target) ?? { added: /* @__PURE__ */ new Map(), removed: /* @__PURE__ */ new Map() };
|
|
101
|
+
for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) + 1);
|
|
102
|
+
for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) + 1);
|
|
103
|
+
claims?.set(target, pending);
|
|
104
|
+
}
|
|
105
|
+
take() {
|
|
106
|
+
return this.#filter(this.#observer?.takeRecords() ?? []);
|
|
107
|
+
}
|
|
108
|
+
disconnect() {
|
|
109
|
+
this.#observer?.disconnect();
|
|
110
|
+
this.#observer = null;
|
|
111
|
+
this.#claims = null;
|
|
112
|
+
}
|
|
113
|
+
#filter(records) {
|
|
114
|
+
const authored = records.filter((record) => !this.#consume(record));
|
|
115
|
+
this.#claims?.attributes.clear();
|
|
116
|
+
this.#claims?.children.clear();
|
|
117
|
+
return authored;
|
|
118
|
+
}
|
|
119
|
+
#consume(record) {
|
|
120
|
+
if (record.type === "attributes") {
|
|
121
|
+
const target = record.target;
|
|
122
|
+
const pending2 = this.#claims?.attributes.get(target)?.get(record.attributeName);
|
|
123
|
+
if (!pending2 || pending2.count === 0 || target.getAttribute(record.attributeName) !== pending2.value) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
pending2.count -= 1;
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
const pending = this.#claims?.children.get(record.target);
|
|
130
|
+
if (!pending) return false;
|
|
131
|
+
const added = [...record.addedNodes];
|
|
132
|
+
const removed = [...record.removedNodes];
|
|
133
|
+
if (added.some((node) => !pending.added.get(node)) || removed.some((node) => !pending.removed.get(node))) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) - 1);
|
|
137
|
+
for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) - 1);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
22
141
|
|
|
23
|
-
// src/utils/event_owner.ts
|
|
24
142
|
function ownerIndex(candidates, node) {
|
|
25
143
|
if (!(node instanceof Node)) return -1;
|
|
26
144
|
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
@@ -29,7 +147,6 @@ function ownerOf(candidates, node) {
|
|
|
29
147
|
return candidates[ownerIndex(candidates, node)] ?? null;
|
|
30
148
|
}
|
|
31
149
|
|
|
32
|
-
// src/utils/field_mirror.ts
|
|
33
150
|
function writeField(field, value) {
|
|
34
151
|
if (field.value === value) return false;
|
|
35
152
|
field.value = value;
|
|
@@ -39,7 +156,6 @@ function commitField(target) {
|
|
|
39
156
|
target.dispatchEvent(new Event("change", { bubbles: true }));
|
|
40
157
|
}
|
|
41
158
|
|
|
42
|
-
// src/utils/focus_candidate.ts
|
|
43
159
|
function inheritsFieldsetDisabled(control) {
|
|
44
160
|
let fieldset = control.closest("fieldset[disabled]");
|
|
45
161
|
while (fieldset) {
|
|
@@ -50,7 +166,6 @@ function inheritsFieldsetDisabled(control) {
|
|
|
50
166
|
return false;
|
|
51
167
|
}
|
|
52
168
|
|
|
53
|
-
// src/utils/interactive_host.ts
|
|
54
169
|
var INTERACTIVE_HOST_SELECTOR = "button, input, select, textarea, label, a[href], area[href], summary, details, audio[controls], video[controls], iframe, object, embed";
|
|
55
170
|
function isInteractiveHost(element) {
|
|
56
171
|
if (element.matches(INTERACTIVE_HOST_SELECTOR)) return true;
|
|
@@ -67,27 +182,36 @@ function isInteractiveHost(element) {
|
|
|
67
182
|
return false;
|
|
68
183
|
}
|
|
69
184
|
|
|
70
|
-
|
|
185
|
+
var ListenerSet = class {
|
|
186
|
+
#abort = new AbortController();
|
|
187
|
+
add(target, type, handler, options) {
|
|
188
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
189
|
+
}
|
|
190
|
+
dispose() {
|
|
191
|
+
this.#abort.abort();
|
|
192
|
+
this.#abort = new AbortController();
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
71
196
|
var MicrotaskCoalescer = class {
|
|
72
197
|
#run;
|
|
73
198
|
#queued = false;
|
|
74
199
|
#active = false;
|
|
75
200
|
#generation = 0;
|
|
76
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
77
201
|
constructor(run) {
|
|
78
202
|
this.#run = run;
|
|
79
203
|
}
|
|
80
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
81
204
|
activate() {
|
|
82
205
|
this.#active = true;
|
|
83
206
|
}
|
|
84
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
85
207
|
cancel() {
|
|
86
208
|
this.#active = false;
|
|
87
209
|
this.#queued = false;
|
|
88
210
|
this.#generation += 1;
|
|
89
211
|
}
|
|
90
|
-
|
|
212
|
+
get pending() {
|
|
213
|
+
return this.#queued;
|
|
214
|
+
}
|
|
91
215
|
schedule() {
|
|
92
216
|
if (!this.#active || this.#queued) return;
|
|
93
217
|
this.#queued = true;
|
|
@@ -100,31 +224,53 @@ var MicrotaskCoalescer = class {
|
|
|
100
224
|
}
|
|
101
225
|
};
|
|
102
226
|
|
|
103
|
-
|
|
227
|
+
var MorphRenderWatcher = class {
|
|
228
|
+
#listeners = new ListenerSet();
|
|
229
|
+
#pass;
|
|
230
|
+
#element = null;
|
|
231
|
+
#onMorph = () => this.#pass.schedule();
|
|
232
|
+
constructor(run) {
|
|
233
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
234
|
+
}
|
|
235
|
+
observe(element) {
|
|
236
|
+
if (this.#element === element) return;
|
|
237
|
+
this.disconnect();
|
|
238
|
+
this.#element = element;
|
|
239
|
+
this.#pass.activate();
|
|
240
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
241
|
+
}
|
|
242
|
+
schedule() {
|
|
243
|
+
this.#pass.schedule();
|
|
244
|
+
}
|
|
245
|
+
get pending() {
|
|
246
|
+
return this.#pass.pending;
|
|
247
|
+
}
|
|
248
|
+
disconnect() {
|
|
249
|
+
this.#listeners.dispose();
|
|
250
|
+
this.#pass.cancel();
|
|
251
|
+
this.#element = null;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
var MoveCounter = class {
|
|
256
|
+
#sequence = 0;
|
|
257
|
+
record() {
|
|
258
|
+
this.#sequence += 1;
|
|
259
|
+
return this.#sequence;
|
|
260
|
+
}
|
|
261
|
+
isLatest(token) {
|
|
262
|
+
return token === this.#sequence;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
104
266
|
var RovingTabindex = class {
|
|
105
|
-
/** Returns the current ordered item elements; called on every operation. */
|
|
106
267
|
#getItems;
|
|
107
|
-
/**
|
|
108
|
-
* @param getItems - Returns the current ordered item elements. Called on every
|
|
109
|
-
* operation so the live target list is always used.
|
|
110
|
-
*/
|
|
111
268
|
constructor(getItems) {
|
|
112
269
|
this.#getItems = getItems;
|
|
113
270
|
}
|
|
114
|
-
/** Index of the currently tabbable item (`tabindex="0"`), or `-1` if none. */
|
|
115
271
|
get activeIndex() {
|
|
116
272
|
return this.#getItems().findIndex((item) => item.tabIndex === 0);
|
|
117
273
|
}
|
|
118
|
-
/**
|
|
119
|
-
* Makes exactly the item at `index` tabbable (`tabindex="0"`) and removes every
|
|
120
|
-
* other item from the Tab sequence (`tabindex="-1"`). An out-of-range `index`
|
|
121
|
-
* (e.g. `-1`) leaves all items at `-1`, which a controller can use to express
|
|
122
|
-
* "nothing is currently tabbable".
|
|
123
|
-
*
|
|
124
|
-
* @param index - Position of the item to make tabbable.
|
|
125
|
-
* @param options - Pass `{ focus: true }` to also move DOM focus to that item,
|
|
126
|
-
* and `items` to reuse an event-scoped collection snapshot.
|
|
127
|
-
*/
|
|
128
274
|
setActive(index, options = {}) {
|
|
129
275
|
const { focus = false } = options;
|
|
130
276
|
const items = options.items ?? this.#getItems();
|
|
@@ -140,7 +286,10 @@ function rovingMove(current, length, delta, wrap) {
|
|
|
140
286
|
return (next + length) % length;
|
|
141
287
|
}
|
|
142
288
|
|
|
143
|
-
|
|
289
|
+
function targetSelector(identifier, name) {
|
|
290
|
+
return `[data-${identifier}-target~="${name}"]`;
|
|
291
|
+
}
|
|
292
|
+
|
|
144
293
|
var NO_SELECTION = { radio: null, value: "" };
|
|
145
294
|
var OBSERVED_ATTRIBUTES = [
|
|
146
295
|
"aria-checked",
|
|
@@ -155,32 +304,32 @@ var OBSERVED_ATTRIBUTES = [
|
|
|
155
304
|
"type",
|
|
156
305
|
"value"
|
|
157
306
|
];
|
|
158
|
-
var hasModifier = (event) => event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
159
307
|
var RadioGroupController = class extends Controller {
|
|
308
|
+
#moves = new MoveCounter();
|
|
309
|
+
#connectionGeneration = 0;
|
|
310
|
+
#moveToken = 0;
|
|
160
311
|
static targets = ["radio", "field"];
|
|
161
312
|
static actions = ["onKeydown", "select"];
|
|
162
313
|
static events = ["change", "reconcile"];
|
|
163
314
|
#roving = new RovingTabindex(() => this.#managedTargets);
|
|
164
|
-
#reconcile = new
|
|
315
|
+
#reconcile = new MorphRenderWatcher(() => this.#reconcileDom());
|
|
165
316
|
#handledEvents = /* @__PURE__ */ new WeakSet();
|
|
166
317
|
#managedRadios = /* @__PURE__ */ new Set();
|
|
167
318
|
#originalTabindex = /* @__PURE__ */ new Map();
|
|
168
|
-
#ownedChecked = /* @__PURE__ */ new
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
|
|
319
|
+
#ownedChecked = /* @__PURE__ */ new Map();
|
|
320
|
+
#writtenTabindex = /* @__PURE__ */ new WeakMap();
|
|
321
|
+
#observer = new AuthoredMutationWatcher(
|
|
322
|
+
(records) => this.#handleMutationRecords(records)
|
|
323
|
+
);
|
|
173
324
|
#committed = NO_SELECTION;
|
|
174
325
|
#connected = false;
|
|
175
326
|
#lastOrder = [];
|
|
176
327
|
#focusedRadio = null;
|
|
177
328
|
#pendingFocusIndex = null;
|
|
178
329
|
#preferChecked = false;
|
|
179
|
-
|
|
180
|
-
* Normalizes authored selection, establishes the APG Tab entry point, and
|
|
181
|
-
* reflects derived form state without reporting a user edit.
|
|
182
|
-
*/
|
|
330
|
+
#listeners = new ListenerSet();
|
|
183
331
|
connect() {
|
|
332
|
+
this.#releaseDefaultsRewrittenWhileAway();
|
|
184
333
|
const currentRadios = new Set(this.radioTargets);
|
|
185
334
|
for (const radio of this.#managedRadios) {
|
|
186
335
|
if (!currentRadios.has(radio)) this.#releaseRadio(radio);
|
|
@@ -188,49 +337,37 @@ var RadioGroupController = class extends Controller {
|
|
|
188
337
|
for (const radio of this.radioTargets) this.#reconcileHost(radio, false);
|
|
189
338
|
this.#normalizeSelection();
|
|
190
339
|
this.#ensureTabStop(true);
|
|
191
|
-
this.#reflectField(this.#selectedRadio
|
|
340
|
+
this.#reflectField(this.#selectedRadio);
|
|
192
341
|
this.#committed = this.#settled;
|
|
193
342
|
this.#lastOrder = this.#managedTargets;
|
|
194
|
-
this.element
|
|
195
|
-
this.element
|
|
196
|
-
this.element
|
|
197
|
-
this.element
|
|
198
|
-
this.element
|
|
199
|
-
this.element
|
|
343
|
+
this.#listeners.add(this.element, "click", this.#onClickCapture, { capture: true });
|
|
344
|
+
this.#listeners.add(this.element, "keydown", this.#onKeydownCapture, { capture: true });
|
|
345
|
+
this.#listeners.add(this.element, "click", this.#onClick);
|
|
346
|
+
this.#listeners.add(this.element, "keydown", this.#onKeydown);
|
|
347
|
+
this.#listeners.add(this.element, "focusin", this.#onFocusin);
|
|
348
|
+
this.#listeners.add(this.element, "focusout", this.#onFocusout);
|
|
200
349
|
this.#connected = true;
|
|
201
350
|
this.#preferChecked = false;
|
|
202
|
-
this.#
|
|
203
|
-
this.#internalTabindexValues.clear();
|
|
204
|
-
this.#reconcile.activate();
|
|
351
|
+
this.#reconcile.observe(this.element);
|
|
205
352
|
this.#observeMutations();
|
|
206
353
|
}
|
|
207
|
-
/** Releases every listener, observer, and queued pass while retaining live DOM state. */
|
|
208
354
|
disconnect() {
|
|
355
|
+
this.#connectionGeneration += 1;
|
|
209
356
|
this.#connected = false;
|
|
210
|
-
this.#reconcile.
|
|
211
|
-
this.
|
|
212
|
-
this.
|
|
213
|
-
this.element.removeEventListener("click", this.#onClick);
|
|
214
|
-
this.element.removeEventListener("keydown", this.#onKeydown);
|
|
215
|
-
this.element.removeEventListener("focusin", this.#onFocusin);
|
|
216
|
-
this.element.removeEventListener("focusout", this.#onFocusout);
|
|
217
|
-
this.#observer?.disconnect();
|
|
218
|
-
this.#observer = null;
|
|
219
|
-
this.#internalCheckedValues.clear();
|
|
220
|
-
this.#internalTabindexValues.clear();
|
|
357
|
+
this.#reconcile.disconnect();
|
|
358
|
+
this.#listeners.dispose();
|
|
359
|
+
this.#observer.disconnect();
|
|
221
360
|
this.#focusedRadio = null;
|
|
222
361
|
this.#pendingFocusIndex = null;
|
|
223
362
|
this.#committed = NO_SELECTION;
|
|
224
363
|
this.#preferChecked = false;
|
|
225
364
|
}
|
|
226
|
-
/** Removes a newly connected radio from the Tab sequence before batch reconciliation. */
|
|
227
365
|
radioTargetConnected(radio) {
|
|
228
366
|
if (this.#connected === false) return;
|
|
229
367
|
this.#reconcileHost(radio, true);
|
|
230
368
|
this.#preferChecked = true;
|
|
231
369
|
this.#reconcile.schedule();
|
|
232
370
|
}
|
|
233
|
-
/** Releases target-only attributes and repairs selection, form state, focus, and roving. */
|
|
234
371
|
radioTargetDisconnected(radio) {
|
|
235
372
|
if (!this.#connected) return;
|
|
236
373
|
if (this.#focusedRadio === radio) {
|
|
@@ -241,36 +378,33 @@ var RadioGroupController = class extends Controller {
|
|
|
241
378
|
this.#preferChecked = true;
|
|
242
379
|
this.#reconcile.schedule();
|
|
243
380
|
}
|
|
244
|
-
/** Reflects the current selection into a field added or replaced at runtime. */
|
|
245
381
|
fieldTargetConnected() {
|
|
246
382
|
this.#reconcile.schedule();
|
|
247
383
|
}
|
|
248
|
-
/** Reconciles after a field target is removed from a retained group. */
|
|
249
384
|
fieldTargetDisconnected() {
|
|
250
385
|
this.#reconcile.schedule();
|
|
251
386
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const radio = event.currentTarget;
|
|
387
|
+
select(source) {
|
|
388
|
+
const { event, host, origin, reason } = actionSource(source);
|
|
389
|
+
const radio = host?.closest(targetSelector(this.identifier, "radio"));
|
|
256
390
|
if (!radio || !this.radioTargets.includes(radio)) return;
|
|
257
|
-
this
|
|
258
|
-
|
|
391
|
+
if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
|
|
392
|
+
return;
|
|
393
|
+
if (event?.defaultPrevented || event && !this.#ownsEventTarget(origin)) return;
|
|
394
|
+
if (event) this.#handledEvents.add(event);
|
|
395
|
+
this.#selectRadio(radio, { focus: false }, reason);
|
|
259
396
|
}
|
|
260
|
-
/** Applies the APG key map to the action's radio. Per-radio action wiring is optional. */
|
|
261
397
|
onKeydown(event) {
|
|
262
398
|
const radio = event.currentTarget;
|
|
263
399
|
if (!radio || !this.radioTargets.includes(radio)) return;
|
|
264
400
|
this.#handleKeydown(event, radio);
|
|
265
401
|
}
|
|
266
|
-
/** Blocks disabled pointer activation before per-radio and consumer handlers run. */
|
|
267
402
|
#onClickCapture = (event) => {
|
|
268
403
|
const radio = this.#radioForEventTarget(event.target);
|
|
269
404
|
if (!radio || !this.#isSupportedHost(radio) || !this.#isActivationDisabled(radio)) return;
|
|
270
405
|
event.preventDefault();
|
|
271
406
|
event.stopImmediatePropagation();
|
|
272
407
|
};
|
|
273
|
-
/** Blocks disabled activation and suppresses a button's non-APG Enter activation. */
|
|
274
408
|
#onKeydownCapture = (event) => {
|
|
275
409
|
if (event.key !== " " && event.key !== "Enter") return;
|
|
276
410
|
const radio = this.#radioForEventTarget(event.target);
|
|
@@ -280,25 +414,21 @@ var RadioGroupController = class extends Controller {
|
|
|
280
414
|
event.stopImmediatePropagation();
|
|
281
415
|
}
|
|
282
416
|
};
|
|
283
|
-
/** Delegated click path for static and runtime-added radios without actions. */
|
|
284
417
|
#onClick = (event) => {
|
|
285
418
|
if (this.#handledEvents.delete(event) || event.defaultPrevented) return;
|
|
286
419
|
const radio = this.#radioForEventTarget(event.target);
|
|
287
420
|
if (radio) this.#selectRadio(radio, { focus: false });
|
|
288
421
|
};
|
|
289
|
-
/** Delegated keydown path for static and runtime-added radios without actions. */
|
|
290
422
|
#onKeydown = (event) => {
|
|
291
423
|
const radio = this.#radioForEventTarget(event.target);
|
|
292
424
|
if (radio) this.#handleKeydown(event, radio);
|
|
293
425
|
};
|
|
294
|
-
/** Keeps programmatic and pointer focus as the group's current roving position. */
|
|
295
426
|
#onFocusin = (event) => {
|
|
296
427
|
const radio = this.#radioForEventTarget(event.target);
|
|
297
428
|
if (!radio || !this.#isNavigable(radio)) return;
|
|
298
429
|
this.#focusedRadio = radio;
|
|
299
430
|
this.#setActive(radio);
|
|
300
431
|
};
|
|
301
|
-
/** Distinguishes ordinary focus departure from focus lost because its radio was removed. */
|
|
302
432
|
#onFocusout = (event) => {
|
|
303
433
|
const radio = this.#radioForEventTarget(event.target);
|
|
304
434
|
if (!radio) return;
|
|
@@ -308,11 +438,10 @@ var RadioGroupController = class extends Controller {
|
|
|
308
438
|
if (!(active instanceof Node) || !radio.contains(active)) this.#focusedRadio = null;
|
|
309
439
|
});
|
|
310
440
|
};
|
|
311
|
-
/** Applies navigation and Space activation to one supported radio. */
|
|
312
441
|
#handleKeydown(event, radio) {
|
|
313
442
|
if (event.defaultPrevented || !this.#isSupportedHost(radio)) return;
|
|
314
443
|
if (event.isComposing || isReservedArrowChord(event)) return;
|
|
315
|
-
if ((event.key === "Home" || event.key === "End") &&
|
|
444
|
+
if ((event.key === "Home" || event.key === "End") && hasModifierChord(event)) return;
|
|
316
445
|
if (event.key === " ") {
|
|
317
446
|
if (this.#isActivationDisabled(radio)) {
|
|
318
447
|
event.preventDefault();
|
|
@@ -346,65 +475,54 @@ var RadioGroupController = class extends Controller {
|
|
|
346
475
|
if (this.#isActivationDisabled(destination)) this.#setActive(destination, true);
|
|
347
476
|
else this.#selectRadio(destination, { focus: true });
|
|
348
477
|
}
|
|
349
|
-
|
|
350
|
-
* Applies one user selection and emits only when selected identity changes. A
|
|
351
|
-
* new selection is taken as published before either report goes out, so a
|
|
352
|
-
* listener that selects again is measured from it. Activating the radio that is
|
|
353
|
-
* already checked publishes nothing: if the page checked it, the next pass
|
|
354
|
-
* reports that move.
|
|
355
|
-
*/
|
|
356
|
-
#selectRadio(radio, { focus }) {
|
|
478
|
+
#selectRadio(radio, { focus }, reason = "user") {
|
|
357
479
|
if (!this.#isSupportedHost(radio) || this.#isActivationDisabled(radio)) return;
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
this.#
|
|
365
|
-
this.#
|
|
366
|
-
|
|
367
|
-
|
|
480
|
+
const generation = this.#connectionGeneration;
|
|
481
|
+
const value = this.#radioValue(radio);
|
|
482
|
+
const changed = this.#committed.radio !== radio || this.#committed.value !== value;
|
|
483
|
+
for (const item of this.#managedTargets) this.#setChecked(item, item === radio);
|
|
484
|
+
this.#committed = { radio, value };
|
|
485
|
+
if (changed) this.#moveToken = this.#moves.record();
|
|
486
|
+
const token = this.#moveToken;
|
|
487
|
+
this.#setActive(radio);
|
|
488
|
+
const fieldChanged = this.#writeField(value);
|
|
489
|
+
this.#handleMutationRecords(this.#observer.take());
|
|
490
|
+
if (focus) radio.focus();
|
|
491
|
+
if (generation !== this.#connectionGeneration || !this.#connected || !this.#moves.isLatest(token))
|
|
492
|
+
return;
|
|
493
|
+
if (changed && fieldChanged) commitField(this.fieldTarget);
|
|
494
|
+
if (changed && generation === this.#connectionGeneration && this.#connected && this.#moves.isLatest(token)) {
|
|
495
|
+
this.dispatch("change", { detail: { value, radio, reason } });
|
|
368
496
|
}
|
|
369
497
|
}
|
|
370
|
-
/** Reconciles target ownership, selection, roving, focus continuity, and form state. */
|
|
371
498
|
#reconcileDom() {
|
|
372
|
-
|
|
373
|
-
observer?.disconnect();
|
|
499
|
+
this.#handleMutationRecords(this.#observer.take());
|
|
374
500
|
for (const radio of this.radioTargets) this.#reconcileHost(radio, true);
|
|
375
501
|
this.#normalizeSelection();
|
|
376
502
|
const pendingFocusIndex = this.#pendingFocusIndex;
|
|
377
503
|
this.#pendingFocusIndex = null;
|
|
504
|
+
let destination = null;
|
|
378
505
|
if (pendingFocusIndex !== null) {
|
|
379
|
-
|
|
380
|
-
this.#setActive(destination
|
|
506
|
+
destination = this.#nearestNavigable(pendingFocusIndex);
|
|
507
|
+
this.#setActive(destination);
|
|
381
508
|
} else {
|
|
382
509
|
this.#ensureTabStop(this.#preferChecked);
|
|
383
510
|
}
|
|
384
|
-
this.#reflectField(this.#selectedRadio
|
|
511
|
+
this.#reflectField(this.#selectedRadio);
|
|
385
512
|
this.#lastOrder = this.#managedTargets;
|
|
386
513
|
this.#preferChecked = false;
|
|
387
|
-
this.#
|
|
388
|
-
this.#internalTabindexValues.clear();
|
|
389
|
-
if (this.#connected && observer) this.#observeWith(observer);
|
|
390
|
-
this.#reportReconciledSelection();
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* Announces a selection that moved since the one this group last published —
|
|
394
|
-
* another radio, or the same radio submitting another value. Dispatched after
|
|
395
|
-
* observation resumes so a consumer's own DOM edits are seen by the next pass,
|
|
396
|
-
* and only while connected: moving focus inside the pass runs listeners that
|
|
397
|
-
* can disconnect the group.
|
|
398
|
-
*/
|
|
399
|
-
#reportReconciledSelection() {
|
|
400
|
-
if (!this.#connected) return;
|
|
514
|
+
this.#handleMutationRecords(this.#observer.take());
|
|
401
515
|
const settled = this.#settled;
|
|
402
516
|
const committed = this.#committed;
|
|
403
|
-
|
|
517
|
+
const changed = settled.radio !== committed.radio || settled.value !== committed.value;
|
|
404
518
|
this.#committed = settled;
|
|
405
|
-
|
|
519
|
+
const token = this.#moveToken;
|
|
520
|
+
destination?.focus();
|
|
521
|
+
if (!this.#connected || !this.#moves.isLatest(token)) return;
|
|
522
|
+
if (changed) {
|
|
523
|
+
this.dispatch("reconcile", { detail: { value: settled.value, radio: settled.radio } });
|
|
524
|
+
}
|
|
406
525
|
}
|
|
407
|
-
/** Begins or ends ownership according to a radio's current host semantics. */
|
|
408
526
|
#reconcileHost(radio, dropFromTabSequence) {
|
|
409
527
|
if (!this.#isSupportedHost(radio)) {
|
|
410
528
|
this.#releaseRadio(radio);
|
|
@@ -417,44 +535,36 @@ var RadioGroupController = class extends Controller {
|
|
|
417
535
|
}
|
|
418
536
|
this.#normalizeChecked(radio);
|
|
419
537
|
}
|
|
420
|
-
/** Restores only defaults owned while an element belonged to this group. */
|
|
421
538
|
#releaseRadio(radio) {
|
|
422
539
|
this.#managedRadios.delete(radio);
|
|
423
540
|
const original = this.#originalTabindex.get(radio);
|
|
424
541
|
if (original === null) {
|
|
425
|
-
this.#
|
|
426
|
-
radio.removeAttribute("tabindex");
|
|
542
|
+
this.#writeAttribute(radio, "tabindex", null);
|
|
427
543
|
} else if (original !== void 0) {
|
|
428
|
-
this.#
|
|
429
|
-
radio.setAttribute("tabindex", original);
|
|
544
|
+
this.#writeAttribute(radio, "tabindex", original);
|
|
430
545
|
}
|
|
431
546
|
this.#originalTabindex.delete(radio);
|
|
432
547
|
if (this.#ownedChecked.delete(radio)) {
|
|
433
|
-
this.#
|
|
434
|
-
radio.removeAttribute("aria-checked");
|
|
548
|
+
this.#writeAttribute(radio, "aria-checked", null);
|
|
435
549
|
}
|
|
436
550
|
}
|
|
437
|
-
/** Supplies a missing checked state and normalizes invalid ARIA tokens. */
|
|
438
551
|
#normalizeChecked(radio) {
|
|
439
552
|
const value = radio.getAttribute("aria-checked");
|
|
440
553
|
if (value === null) {
|
|
441
|
-
this.#ownedChecked.
|
|
554
|
+
this.#ownedChecked.set(radio, "false");
|
|
442
555
|
this.#setChecked(radio, false);
|
|
443
556
|
} else if (value !== "true" && value !== "false") {
|
|
444
557
|
this.#setChecked(radio, false);
|
|
445
558
|
}
|
|
446
559
|
}
|
|
447
|
-
/** Makes the first DOM-ordered checked radio the sole checked radio. */
|
|
448
560
|
#normalizeSelection() {
|
|
449
561
|
let found = false;
|
|
450
562
|
for (const radio of this.#managedTargets) {
|
|
451
|
-
this.#normalizeChecked(radio);
|
|
452
563
|
if (!this.#isChecked(radio)) continue;
|
|
453
564
|
if (!found) found = true;
|
|
454
565
|
else this.#setChecked(radio, false);
|
|
455
566
|
}
|
|
456
567
|
}
|
|
457
|
-
/** Keeps one navigable Tab stop, preferring checked state when selection changed externally. */
|
|
458
568
|
#ensureTabStop(preferChecked) {
|
|
459
569
|
const navigable = this.#navigableRadios;
|
|
460
570
|
let active;
|
|
@@ -469,12 +579,6 @@ var RadioGroupController = class extends Controller {
|
|
|
469
579
|
active ??= navigable[0];
|
|
470
580
|
this.#setActive(active ?? null);
|
|
471
581
|
}
|
|
472
|
-
/**
|
|
473
|
-
* The surviving navigable radio closest to a position in the order captured
|
|
474
|
-
* before the removal: the first one at or after it, else the last one before it.
|
|
475
|
-
* The saved position and this search read the same population, so a `hidden` or
|
|
476
|
-
* natively disabled sibling never shifts the destination past its neighbor.
|
|
477
|
-
*/
|
|
478
582
|
#nearestNavigable(index) {
|
|
479
583
|
const navigable = new Set(this.#navigableRadios);
|
|
480
584
|
for (let i = index; i < this.#lastOrder.length; i++) {
|
|
@@ -487,7 +591,6 @@ var RadioGroupController = class extends Controller {
|
|
|
487
591
|
}
|
|
488
592
|
return this.#navigableRadios[0] ?? null;
|
|
489
593
|
}
|
|
490
|
-
/** Moves through full DOM order until a navigable radio is found. */
|
|
491
594
|
#nextNavigable(fromIndex, delta) {
|
|
492
595
|
const items = this.#managedTargets;
|
|
493
596
|
let index = fromIndex;
|
|
@@ -498,36 +601,32 @@ var RadioGroupController = class extends Controller {
|
|
|
498
601
|
}
|
|
499
602
|
return void 0;
|
|
500
603
|
}
|
|
501
|
-
/** Assigns the single roving Tab stop and optionally moves DOM focus. */
|
|
502
604
|
#setActive(radio, focus = false) {
|
|
503
605
|
const items = this.#managedTargets;
|
|
504
606
|
const index = items.indexOf(radio);
|
|
505
|
-
this.#roving.setActive(index
|
|
607
|
+
this.#roving.setActive(index);
|
|
506
608
|
for (const item of items) {
|
|
507
|
-
this.#
|
|
609
|
+
this.#observer.claimCurrent(item, "tabindex");
|
|
610
|
+
this.#writtenTabindex.set(item, item.getAttribute("tabindex"));
|
|
508
611
|
}
|
|
612
|
+
if (focus) radio?.focus();
|
|
509
613
|
}
|
|
510
|
-
/** Finds this group's radio containing an event target, excluding nested groups. */
|
|
511
614
|
#radioForEventTarget(target) {
|
|
512
615
|
if (!this.#ownsEventTarget(target)) return null;
|
|
513
616
|
return ownerOf(this.radioTargets, target);
|
|
514
617
|
}
|
|
515
|
-
/** Whether the closest Radio Group scope around a target is this instance. */
|
|
516
618
|
#ownsEventTarget(target) {
|
|
517
619
|
return target instanceof Element && target.closest(`[data-controller~="${this.identifier}"]`) === this.element;
|
|
518
620
|
}
|
|
519
|
-
/** Hosts whose activation model can be owned without conflicting native behavior. */
|
|
520
621
|
#isSupportedHost(radio) {
|
|
521
622
|
if (radio instanceof HTMLButtonElement) return radio.type === "button";
|
|
522
623
|
return !isInteractiveHost(radio);
|
|
523
624
|
}
|
|
524
|
-
/** Radios eligible for roving focus; ARIA-disabled deliberately remains eligible. */
|
|
525
625
|
#isNavigable(radio) {
|
|
526
626
|
if (!this.#isSupportedHost(radio) || this.#isHidden(radio)) return false;
|
|
527
627
|
if (!(radio instanceof HTMLButtonElement)) return true;
|
|
528
628
|
return !radio.disabled && !inheritsFieldsetDisabled(radio);
|
|
529
629
|
}
|
|
530
|
-
/** Whether hidden applies on the path from a radio up to the group root. */
|
|
531
630
|
#isHidden(radio) {
|
|
532
631
|
let current = radio;
|
|
533
632
|
while (current && current !== this.element) {
|
|
@@ -536,7 +635,6 @@ var RadioGroupController = class extends Controller {
|
|
|
536
635
|
}
|
|
537
636
|
return false;
|
|
538
637
|
}
|
|
539
|
-
/** Whether ARIA, visibility, or native HTML semantics suppress selection. */
|
|
540
638
|
#isActivationDisabled(radio) {
|
|
541
639
|
if (!this.#isNavigable(radio)) return true;
|
|
542
640
|
let current = radio;
|
|
@@ -546,119 +644,88 @@ var RadioGroupController = class extends Controller {
|
|
|
546
644
|
}
|
|
547
645
|
return false;
|
|
548
646
|
}
|
|
549
|
-
/** Distinguishes external retained-element changes from this controller's own writes. */
|
|
550
647
|
#handleMutationRecords(records) {
|
|
551
648
|
let external = false;
|
|
552
649
|
for (const record of records) {
|
|
553
650
|
const target = record.target;
|
|
554
651
|
const attribute = record.attributeName;
|
|
555
652
|
if (attribute === "aria-checked") {
|
|
556
|
-
const value = target.getAttribute(attribute);
|
|
557
|
-
if (this.#matchesInternal(this.#internalCheckedValues, target, value)) continue;
|
|
558
653
|
this.#ownedChecked.delete(target);
|
|
559
654
|
this.#preferChecked = true;
|
|
560
655
|
external = true;
|
|
561
656
|
} else if (attribute === "tabindex") {
|
|
562
657
|
const value = target.getAttribute(attribute);
|
|
563
|
-
if (this.#matchesInternal(this.#internalTabindexValues, target, value)) continue;
|
|
564
658
|
if (this.#managedRadios.has(target)) this.#originalTabindex.set(target, value);
|
|
565
659
|
external = true;
|
|
566
660
|
} else {
|
|
567
661
|
external = true;
|
|
568
662
|
}
|
|
569
663
|
}
|
|
570
|
-
this.#internalCheckedValues.clear();
|
|
571
|
-
this.#internalTabindexValues.clear();
|
|
572
664
|
if (external) this.#reconcile.schedule();
|
|
573
665
|
}
|
|
574
|
-
/** Records an attribute write this pass made, keeping one claim per record it will produce. */
|
|
575
|
-
#markInternal(values, target, value) {
|
|
576
|
-
const pending = values.get(target);
|
|
577
|
-
if (pending) {
|
|
578
|
-
pending.value = value;
|
|
579
|
-
pending.count += 1;
|
|
580
|
-
} else {
|
|
581
|
-
values.set(target, { value, count: 1 });
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
/** Consumes one claim on a record whose value this pass wrote. */
|
|
585
|
-
#matchesInternal(values, target, value) {
|
|
586
|
-
const pending = values.get(target);
|
|
587
|
-
if (!pending || pending.value !== value) return false;
|
|
588
|
-
pending.count -= 1;
|
|
589
|
-
if (pending.count === 0) values.delete(target);
|
|
590
|
-
return true;
|
|
591
|
-
}
|
|
592
|
-
/** Watches target membership, retained state/hosts, form state, and fieldset ancestry. */
|
|
593
666
|
#observeMutations() {
|
|
594
|
-
|
|
595
|
-
this.#observer = observer;
|
|
596
|
-
this.#observeWith(observer);
|
|
597
|
-
}
|
|
598
|
-
/** Registers root and ancestor observation on one observer instance. */
|
|
599
|
-
#observeWith(observer) {
|
|
600
|
-
observer.observe(this.element, {
|
|
667
|
+
this.#observer.observe(this.element, {
|
|
601
668
|
subtree: true,
|
|
602
669
|
childList: true,
|
|
603
|
-
attributes:
|
|
604
|
-
|
|
670
|
+
attributes: OBSERVED_ATTRIBUTES,
|
|
671
|
+
ancestorAttributes: ["contenteditable", "disabled"]
|
|
605
672
|
});
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
673
|
+
}
|
|
674
|
+
#releaseDefaultsRewrittenWhileAway() {
|
|
675
|
+
for (const [radio, written] of this.#ownedChecked) {
|
|
676
|
+
const current = radio.getAttribute("aria-checked");
|
|
677
|
+
if (current !== null && current !== written) this.#ownedChecked.delete(radio);
|
|
678
|
+
}
|
|
679
|
+
for (const radio of this.#managedRadios) {
|
|
680
|
+
const written = this.#writtenTabindex.get(radio);
|
|
681
|
+
const current = radio.getAttribute("tabindex");
|
|
682
|
+
if (current !== written) this.#originalTabindex.set(radio, current);
|
|
613
683
|
}
|
|
614
684
|
}
|
|
615
|
-
/** Supported targets currently managed by the roving primitive. */
|
|
616
685
|
get #managedTargets() {
|
|
617
686
|
return this.radioTargets.filter((radio) => this.#managedRadios.has(radio));
|
|
618
687
|
}
|
|
619
|
-
/** Managed radios eligible for the roving Tab stop. */
|
|
620
688
|
get #navigableRadios() {
|
|
621
689
|
return this.#managedTargets.filter((radio) => this.#isNavigable(radio));
|
|
622
690
|
}
|
|
623
|
-
/** The currently checked managed radio, if any. */
|
|
624
691
|
get #selectedRadio() {
|
|
625
692
|
return this.#managedTargets.find((radio) => this.#isChecked(radio));
|
|
626
693
|
}
|
|
627
|
-
/** The current selection as the group publishes it. */
|
|
628
694
|
get #settled() {
|
|
629
695
|
const radio = this.#selectedRadio;
|
|
630
696
|
return radio ? { radio, value: this.#radioValue(radio) } : NO_SELECTION;
|
|
631
697
|
}
|
|
632
|
-
/** Whether a radio is currently checked. */
|
|
633
698
|
#isChecked(radio) {
|
|
634
699
|
return radio.getAttribute("aria-checked") === "true";
|
|
635
700
|
}
|
|
636
|
-
/** Reflects checked state while distinguishing controller writes from authored morphs. */
|
|
637
701
|
#setChecked(radio, checked) {
|
|
638
702
|
const value = checked ? "true" : "false";
|
|
639
|
-
if (
|
|
640
|
-
this.#
|
|
641
|
-
radio.setAttribute("aria-checked", value);
|
|
703
|
+
if (this.#ownedChecked.has(radio)) this.#ownedChecked.set(radio, value);
|
|
704
|
+
this.#writeAttribute(radio, "aria-checked", value);
|
|
642
705
|
}
|
|
643
|
-
/** Writes a roving value before the radio is visible to the shared primitive. */
|
|
644
706
|
#writeTabindex(radio, value) {
|
|
645
707
|
const serialized = String(value);
|
|
646
|
-
this.#
|
|
647
|
-
radio
|
|
708
|
+
this.#writtenTabindex.set(radio, serialized);
|
|
709
|
+
this.#writeAttribute(radio, "tabindex", serialized);
|
|
648
710
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
711
|
+
#writeAttribute(target, name, value) {
|
|
712
|
+
setAttributeIfChanged(target, name, value, () => this.#observer.claim(target, name, value));
|
|
713
|
+
}
|
|
714
|
+
#reflectField(radio) {
|
|
652
715
|
const value = radio ? this.#radioValue(radio) : "";
|
|
653
|
-
|
|
654
|
-
if (!silent) commitField(this.fieldTarget);
|
|
716
|
+
this.#writeField(value);
|
|
655
717
|
}
|
|
656
|
-
/** A radio's submitted value (`data-value`, defaulting to empty). */
|
|
657
718
|
#radioValue(radio) {
|
|
658
719
|
return radio.getAttribute("data-value") ?? "";
|
|
659
720
|
}
|
|
721
|
+
#writeField(value) {
|
|
722
|
+
if (!this.hasFieldTarget) return false;
|
|
723
|
+
const changed = writeField(this.fieldTarget, value);
|
|
724
|
+
if (changed && this.fieldTarget.type === "hidden")
|
|
725
|
+
this.#observer.claimCurrent(this.fieldTarget, "value");
|
|
726
|
+
return changed;
|
|
727
|
+
}
|
|
660
728
|
};
|
|
661
729
|
|
|
662
730
|
export { RadioGroupController };
|
|
663
|
-
//# sourceMappingURL=radio_group_controller.js.map
|
|
664
731
|
//# sourceMappingURL=radio_group_controller.js.map
|