stimeo-ui 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +8 -1
- package/dist/cable/index.d.ts +79 -23
- package/dist/cable/index.js +363 -436
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +4 -2
- package/dist/controllers/accordion_controller.js +166 -55
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +12 -2
- package/dist/controllers/alert_dialog_controller.js +862 -221
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +35 -5
- package/dist/controllers/announcer_controller.js +135 -198
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.js +0 -10
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.d.ts +30 -6
- package/dist/controllers/auto_submit_controller.js +271 -127
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +171 -105
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.js +0 -130
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +18 -0
- package/dist/controllers/bulk_select_controller.js +262 -52
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +24 -5
- package/dist/controllers/calendar_controller.js +175 -209
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +34 -7
- package/dist/controllers/carousel_controller.js +461 -399
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +17 -1
- package/dist/controllers/character_counter_controller.js +246 -156
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +7 -2
- package/dist/controllers/checkbox_controller.js +32 -74
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +16 -5
- package/dist/controllers/clipboard_controller.js +168 -147
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +16 -4
- package/dist/controllers/collapsible_controller.js +301 -120
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +18 -6
- package/dist/controllers/color_picker_controller.js +149 -139
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +23 -5
- package/dist/controllers/combobox_controller.js +360 -133
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +45 -14
- package/dist/controllers/command_palette_controller.js +970 -358
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.d.ts +15 -2
- package/dist/controllers/conditional_fields_controller.js +166 -124
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.d.ts +49 -12
- package/dist/controllers/confirm_controller.js +939 -248
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +31 -4
- package/dist/controllers/context_menu_controller.js +256 -128
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.d.ts +13 -1
- package/dist/controllers/count_up_controller.js +73 -21
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +45 -1
- package/dist/controllers/countdown_controller.js +183 -148
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +21 -2
- package/dist/controllers/currency_input_controller.js +134 -158
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +15 -4
- package/dist/controllers/data_grid_controller.js +114 -186
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +30 -5
- package/dist/controllers/date_range_picker_controller.js +288 -206
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +13 -1
- package/dist/controllers/dialog_controller.js +858 -223
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +24 -7
- package/dist/controllers/direct_upload_controller.js +73 -153
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.js +14 -48
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +19 -0
- package/dist/controllers/dismissible_controller.js +175 -21
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +39 -11
- package/dist/controllers/drawer_controller.js +933 -341
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +31 -1
- package/dist/controllers/dropdown_controller.js +312 -82
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.d.ts +12 -1
- package/dist/controllers/editable_controller.js +158 -68
- package/dist/controllers/editable_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.js +67 -53
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +60 -18
- package/dist/controllers/file_dropzone_controller.js +311 -207
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.js +67 -39
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +31 -8
- package/dist/controllers/flash_controller.js +172 -314
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +12 -3
- package/dist/controllers/focus_controller.js +654 -240
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +147 -132
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +13 -86
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +41 -9
- package/dist/controllers/frame_loading_controller.js +264 -213
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +22 -2
- package/dist/controllers/highlight_controller.js +107 -75
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +56 -3
- package/dist/controllers/hover_card_controller.js +309 -118
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +42 -1
- package/dist/controllers/idle_controller.js +267 -101
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.js +71 -72
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.d.ts +32 -13
- package/dist/controllers/intersection_controller.js +147 -106
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +16 -0
- package/dist/controllers/lazy_frame_controller.js +9 -65
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +34 -2
- package/dist/controllers/listbox_controller.js +306 -204
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +1 -1
- package/dist/controllers/local_time_controller.js +54 -64
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +11 -3
- package/dist/controllers/masonry_controller.js +70 -93
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +32 -7
- package/dist/controllers/menu_controller.js +214 -156
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +4 -4
- package/dist/controllers/menubar_controller.js +170 -329
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +20 -0
- package/dist/controllers/meter_controller.js +151 -67
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +32 -4
- package/dist/controllers/multi_select_controller.js +593 -358
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +8 -1
- package/dist/controllers/navigation_menu_controller.js +124 -232
- package/dist/controllers/navigation_menu_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.d.ts +10 -0
- package/dist/controllers/nested_form_controller.js +352 -182
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +23 -0
- package/dist/controllers/network_status_controller.js +242 -54
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +26 -2
- package/dist/controllers/number_input_controller.js +320 -263
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.js +16 -76
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +20 -4
- package/dist/controllers/otp_controller.js +198 -193
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
- package/dist/controllers/overflow_indicator_controller.js +116 -113
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +48 -11
- package/dist/controllers/overflow_menu_controller.js +343 -283
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +35 -6
- package/dist/controllers/pagination_controller.js +295 -119
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +45 -8
- package/dist/controllers/password_reveal_controller.js +389 -121
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +14 -5
- package/dist/controllers/password_strength_controller.js +99 -153
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +11 -4
- package/dist/controllers/persist_controller.js +237 -103
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +19 -7
- package/dist/controllers/pointer_drag_controller.js +185 -165
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +38 -2
- package/dist/controllers/popover_controller.js +236 -97
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +44 -4
- package/dist/controllers/portal_controller.js +209 -76
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.d.ts +10 -4
- package/dist/controllers/preview_guard_controller.js +232 -116
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +11 -0
- package/dist/controllers/progress_controller.js +125 -56
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +14 -5
- package/dist/controllers/radio_group_controller.js +117 -137
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +43 -3
- package/dist/controllers/range_slider_controller.js +192 -105
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +20 -4
- package/dist/controllers/rating_controller.js +418 -207
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +2 -0
- package/dist/controllers/read_more_controller.js +134 -63
- package/dist/controllers/read_more_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.d.ts +1 -1
- package/dist/controllers/reading_progress_controller.js +147 -135
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.d.ts +18 -4
- package/dist/controllers/relative_time_controller.js +145 -110
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
- package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
- package/dist/controllers/reset_on_restore_controller.js.map +1 -0
- package/dist/controllers/resizable_controller.d.ts +28 -2
- package/dist/controllers/resizable_controller.js +255 -139
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.js +39 -84
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +7 -3
- package/dist/controllers/scroll_area_controller.js +428 -225
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_restore_controller.js +0 -53
- package/dist/controllers/scroll_restore_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
- package/dist/controllers/scroll_visibility_controller.js +395 -145
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +6 -1
- package/dist/controllers/scrollspy_controller.js +181 -242
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +19 -4
- package/dist/controllers/separator_controller.js +267 -140
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +64 -7
- package/dist/controllers/sidebar_controller.js +1023 -386
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +23 -0
- package/dist/controllers/skeleton_controller.js +248 -97
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +27 -1
- package/dist/controllers/slider_controller.js +159 -79
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
- package/dist/controllers/smart_sticky_header_controller.js +162 -63
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.js +0 -112
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +39 -4
- package/dist/controllers/spinner_controller.js +277 -169
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +5 -0
- package/dist/controllers/step_indicator_controller.js +99 -46
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +12 -3
- package/dist/controllers/stepper_controller.js +114 -73
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +6 -0
- package/dist/controllers/stick_to_bottom_controller.js +262 -138
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +11 -0
- package/dist/controllers/sticky_observer_controller.js +214 -49
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +23 -8
- package/dist/controllers/submit_once_controller.js +294 -205
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +12 -1
- package/dist/controllers/switch_controller.js +33 -60
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +2 -2
- package/dist/controllers/tabs_controller.js +35 -29
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +12 -0
- package/dist/controllers/tags_input_controller.js +136 -145
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
- package/dist/controllers/textarea_autosize_controller.js +71 -58
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +21 -9
- package/dist/controllers/theme_controller.js +130 -112
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +18 -1
- package/dist/controllers/time_picker_controller.js +146 -80
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +36 -11
- package/dist/controllers/toast_controller.js +186 -297
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +12 -3
- package/dist/controllers/toggle_group_controller.js +95 -136
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +68 -98
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +45 -3
- package/dist/controllers/tooltip_controller.js +158 -124
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +22 -4
- package/dist/controllers/transition_controller.js +123 -119
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +21 -11
- package/dist/controllers/tree_view_controller.js +150 -345
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +6370 -9346
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +23 -3
- package/dist/inspector/cli.js +92 -37
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +92 -56
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +8 -8
- package/dist/inspector/manifest.json +787 -100
- package/dist/positioning/index.d.ts +8 -0
- package/dist/positioning/index.js +148 -47
- package/dist/positioning/index.js.map +1 -1
- package/package.json +6 -6
- package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
- package/dist/controllers/reset_before_cache_controller.js.map +0 -1
|
@@ -1,14 +1,179 @@
|
|
|
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/arrow_step.ts
|
|
6
25
|
function isReservedArrowChord(event, allow = []) {
|
|
7
26
|
if (!event.key.startsWith("Arrow")) return false;
|
|
8
27
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
9
28
|
}
|
|
10
29
|
|
|
11
|
-
|
|
30
|
+
function sharedRegistry(key, create) {
|
|
31
|
+
const symbol = Symbol.for(key);
|
|
32
|
+
const scope = globalThis;
|
|
33
|
+
const existing = scope[symbol];
|
|
34
|
+
if (existing !== void 0) return existing;
|
|
35
|
+
const registry = create();
|
|
36
|
+
Object.defineProperty(globalThis, symbol, { value: registry });
|
|
37
|
+
return registry;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isAttributeValue(value) {
|
|
41
|
+
return value === null || typeof value === "string";
|
|
42
|
+
}
|
|
43
|
+
function readLeaseRecord(element, record) {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
46
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
47
|
+
const [original, written, ...rest] = parsed;
|
|
48
|
+
const linked = rest[0] !== false;
|
|
49
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
50
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
51
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
52
|
+
} catch {
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
var AttributeLease = class _AttributeLease {
|
|
57
|
+
static #registry = sharedRegistry(
|
|
58
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
59
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
60
|
+
);
|
|
61
|
+
#attribute;
|
|
62
|
+
#record;
|
|
63
|
+
#base;
|
|
64
|
+
#held = /* @__PURE__ */ new Set();
|
|
65
|
+
constructor(attribute, owner, options = {}) {
|
|
66
|
+
this.#attribute = attribute;
|
|
67
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
68
|
+
this.#base = options.base === true;
|
|
69
|
+
const names = _AttributeLease.#registry.names;
|
|
70
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
71
|
+
}
|
|
72
|
+
write(element, value) {
|
|
73
|
+
const stack = this.#stack(element);
|
|
74
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
75
|
+
this.#held.add(element);
|
|
76
|
+
const shows = !this.#covered(element, stack, hold);
|
|
77
|
+
hold.written = value;
|
|
78
|
+
this.#mark(element, stack);
|
|
79
|
+
if (shows) this.#reflect(element, value);
|
|
80
|
+
}
|
|
81
|
+
return(element) {
|
|
82
|
+
const held = this.#held.delete(element);
|
|
83
|
+
const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
|
|
84
|
+
const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
|
|
85
|
+
const hold = this.#take(stack);
|
|
86
|
+
if (!hold) return false;
|
|
87
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
88
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
89
|
+
const index = stack.indexOf(hold);
|
|
90
|
+
stack.splice(index, 1);
|
|
91
|
+
const above = stack[index];
|
|
92
|
+
if (above) above.linked &&= hold.linked;
|
|
93
|
+
this.#mark(element, stack);
|
|
94
|
+
if (!owns) return true;
|
|
95
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
96
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
returnAll() {
|
|
100
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
101
|
+
}
|
|
102
|
+
#stack(element) {
|
|
103
|
+
const stacks = _AttributeLease.#registry.stacks;
|
|
104
|
+
const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
105
|
+
stacks.set(element, byAttribute);
|
|
106
|
+
const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
|
|
107
|
+
byAttribute.set(this.#attribute, stack);
|
|
108
|
+
return stack;
|
|
109
|
+
}
|
|
110
|
+
#seed(element) {
|
|
111
|
+
const found = [];
|
|
112
|
+
for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
|
|
113
|
+
const read = readLeaseRecord(element, record);
|
|
114
|
+
if (read) found.push({ ...read, record });
|
|
115
|
+
}
|
|
116
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
117
|
+
return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
118
|
+
lease: null,
|
|
119
|
+
record,
|
|
120
|
+
written,
|
|
121
|
+
original,
|
|
122
|
+
linked: linked && index > 0
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
#take(stack) {
|
|
126
|
+
const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
|
|
127
|
+
if (hold) hold.lease = this;
|
|
128
|
+
return hold;
|
|
129
|
+
}
|
|
130
|
+
#acquire(element, stack) {
|
|
131
|
+
const current = element.getAttribute(this.#attribute);
|
|
132
|
+
const top = stack.at(-1);
|
|
133
|
+
const bottom = stack[0];
|
|
134
|
+
const over = top?.written === current ? top : void 0;
|
|
135
|
+
const from = this.#base ? bottom : over;
|
|
136
|
+
const hold = {
|
|
137
|
+
lease: this,
|
|
138
|
+
record: this.#record,
|
|
139
|
+
written: current,
|
|
140
|
+
original: from ? from.original : current,
|
|
141
|
+
linked: !this.#base && over !== void 0
|
|
142
|
+
};
|
|
143
|
+
if (this.#base && bottom) {
|
|
144
|
+
bottom.linked = true;
|
|
145
|
+
stack.unshift(hold);
|
|
146
|
+
} else stack.push(hold);
|
|
147
|
+
return hold;
|
|
148
|
+
}
|
|
149
|
+
#covered(element, stack, hold) {
|
|
150
|
+
const current = element.getAttribute(this.#attribute);
|
|
151
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
152
|
+
}
|
|
153
|
+
#mark(element, stack) {
|
|
154
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
155
|
+
stack.forEach((hold, index) => {
|
|
156
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
157
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
158
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
159
|
+
records.set(
|
|
160
|
+
hold.record,
|
|
161
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
for (const [name, recorded] of records) {
|
|
165
|
+
if (element.getAttribute(name) === recorded) continue;
|
|
166
|
+
if (recorded === null) element.removeAttribute(name);
|
|
167
|
+
else element.setAttribute(name, recorded);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
#reflect(element, value) {
|
|
171
|
+
if (element.getAttribute(this.#attribute) === value) return;
|
|
172
|
+
if (value === null) element.removeAttribute(this.#attribute);
|
|
173
|
+
else element.setAttribute(this.#attribute, value);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
12
177
|
function writeField(field, value) {
|
|
13
178
|
if (field.value === value) return false;
|
|
14
179
|
field.value = value;
|
|
@@ -18,27 +183,33 @@ function commitField(target) {
|
|
|
18
183
|
target.dispatchEvent(new Event("change", { bubbles: true }));
|
|
19
184
|
}
|
|
20
185
|
|
|
21
|
-
|
|
186
|
+
var ListenerSet = class {
|
|
187
|
+
#abort = new AbortController();
|
|
188
|
+
add(target, type, handler, options) {
|
|
189
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
190
|
+
}
|
|
191
|
+
dispose() {
|
|
192
|
+
this.#abort.abort();
|
|
193
|
+
this.#abort = new AbortController();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
22
197
|
var MicrotaskCoalescer = class {
|
|
23
198
|
#run;
|
|
24
199
|
#queued = false;
|
|
25
200
|
#active = false;
|
|
26
201
|
#generation = 0;
|
|
27
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
28
202
|
constructor(run) {
|
|
29
203
|
this.#run = run;
|
|
30
204
|
}
|
|
31
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
32
205
|
activate() {
|
|
33
206
|
this.#active = true;
|
|
34
207
|
}
|
|
35
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
36
208
|
cancel() {
|
|
37
209
|
this.#active = false;
|
|
38
210
|
this.#queued = false;
|
|
39
211
|
this.#generation += 1;
|
|
40
212
|
}
|
|
41
|
-
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
42
213
|
schedule() {
|
|
43
214
|
if (!this.#active || this.#queued) return;
|
|
44
215
|
this.#queued = true;
|
|
@@ -51,7 +222,42 @@ var MicrotaskCoalescer = class {
|
|
|
51
222
|
}
|
|
52
223
|
};
|
|
53
224
|
|
|
54
|
-
|
|
225
|
+
var MorphRenderWatcher = class {
|
|
226
|
+
#listeners = new ListenerSet();
|
|
227
|
+
#pass;
|
|
228
|
+
#element = null;
|
|
229
|
+
#onMorph = () => this.#pass.schedule();
|
|
230
|
+
constructor(run) {
|
|
231
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
232
|
+
}
|
|
233
|
+
observe(element) {
|
|
234
|
+
if (this.#element === element) return;
|
|
235
|
+
this.disconnect();
|
|
236
|
+
this.#element = element;
|
|
237
|
+
this.#pass.activate();
|
|
238
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
239
|
+
}
|
|
240
|
+
schedule() {
|
|
241
|
+
this.#pass.schedule();
|
|
242
|
+
}
|
|
243
|
+
disconnect() {
|
|
244
|
+
this.#listeners.dispose();
|
|
245
|
+
this.#pass.cancel();
|
|
246
|
+
this.#element = null;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
var MoveCounter = class {
|
|
251
|
+
#sequence = 0;
|
|
252
|
+
record() {
|
|
253
|
+
this.#sequence += 1;
|
|
254
|
+
return this.#sequence;
|
|
255
|
+
}
|
|
256
|
+
isLatest(token) {
|
|
257
|
+
return token === this.#sequence;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
55
261
|
function scrollOptionIntoView(list, option) {
|
|
56
262
|
if (list.scrollHeight <= list.clientHeight) return;
|
|
57
263
|
const listRect = list.getBoundingClientRect();
|
|
@@ -63,43 +269,28 @@ function scrollOptionIntoView(list, option) {
|
|
|
63
269
|
}
|
|
64
270
|
}
|
|
65
271
|
|
|
66
|
-
|
|
272
|
+
function targetSelector(identifier, name) {
|
|
273
|
+
return `[data-${identifier}-target~="${name}"]`;
|
|
274
|
+
}
|
|
275
|
+
|
|
67
276
|
var TimerRegistry = class {
|
|
68
|
-
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
69
277
|
ids = /* @__PURE__ */ new Set();
|
|
70
|
-
/**
|
|
71
|
-
* Cancels a single tracked timer.
|
|
72
|
-
*
|
|
73
|
-
* No-ops if the id is unknown (already cleared, fired, or never owned by this
|
|
74
|
-
* registry), so callers can clear defensively without guarding.
|
|
75
|
-
*/
|
|
76
278
|
clear(id) {
|
|
77
279
|
if (this.ids.delete(id)) {
|
|
78
280
|
this.cancel(id);
|
|
79
281
|
}
|
|
80
282
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
83
|
-
* to guarantee no timer outlives the element.
|
|
84
|
-
*/
|
|
85
283
|
clearAll() {
|
|
86
284
|
for (const id of this.ids) {
|
|
87
285
|
this.cancel(id);
|
|
88
286
|
}
|
|
89
287
|
this.ids.clear();
|
|
90
288
|
}
|
|
91
|
-
/** Number of timers currently tracked (pending). */
|
|
92
289
|
get size() {
|
|
93
290
|
return this.ids.size;
|
|
94
291
|
}
|
|
95
292
|
};
|
|
96
293
|
var SafeTimeout = class extends TimerRegistry {
|
|
97
|
-
/**
|
|
98
|
-
* Schedules `callback` after `delay` ms and returns the timer id.
|
|
99
|
-
*
|
|
100
|
-
* The id is removed from the registry automatically when the timeout fires,
|
|
101
|
-
* so {@link TimerRegistry.size | size} reflects only still-pending timers.
|
|
102
|
-
*/
|
|
103
294
|
set(callback, delay) {
|
|
104
295
|
const id = this.schedule(() => {
|
|
105
296
|
this.ids.delete(id);
|
|
@@ -116,29 +307,18 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
116
307
|
}
|
|
117
308
|
};
|
|
118
309
|
|
|
119
|
-
// src/utils/typeahead.ts
|
|
120
310
|
var TYPEAHEAD_RESET_MS = 500;
|
|
121
311
|
var Typeahead = class {
|
|
122
|
-
/** Timer registry for the pending idle reset; private so `reset()` is the only exit. */
|
|
123
312
|
#timers = new SafeTimeout();
|
|
124
|
-
/** Idle window before the query resets, in milliseconds. */
|
|
125
313
|
#resetMs;
|
|
126
|
-
/** The accumulated lowercase query, empty when idle. */
|
|
127
314
|
#query = "";
|
|
128
|
-
/** Id of the pending reset timer, `0` when none is scheduled. */
|
|
129
315
|
#timerId = 0;
|
|
130
|
-
/** @param options - Overrides for the idle window. */
|
|
131
316
|
constructor({ resetMs = TYPEAHEAD_RESET_MS } = {}) {
|
|
132
317
|
this.#resetMs = resetMs;
|
|
133
318
|
}
|
|
134
|
-
/** The query a search would currently run with; empty while idle. */
|
|
135
319
|
get query() {
|
|
136
320
|
return this.#query;
|
|
137
321
|
}
|
|
138
|
-
/**
|
|
139
|
-
* Folds `key` into the query, restarts the idle window, and returns the query to
|
|
140
|
-
* search with. A repeated character collapses the query to that one character.
|
|
141
|
-
*/
|
|
142
322
|
push(key) {
|
|
143
323
|
const char = key.toLowerCase();
|
|
144
324
|
const repeated = this.#query.length > 0 && [...this.#query].every((c) => c === char);
|
|
@@ -147,7 +327,6 @@ var Typeahead = class {
|
|
|
147
327
|
this.#timerId = this.#timers.set(() => this.reset(), this.#resetMs);
|
|
148
328
|
return this.#query;
|
|
149
329
|
}
|
|
150
|
-
/** Clears the query and cancels the pending idle reset. */
|
|
151
330
|
reset() {
|
|
152
331
|
this.#query = "";
|
|
153
332
|
this.#timers.clear(this.#timerId);
|
|
@@ -174,149 +353,129 @@ function findTypeaheadMatch(items, from, query, label = (item) => typeaheadLabel
|
|
|
174
353
|
return -1;
|
|
175
354
|
}
|
|
176
355
|
|
|
177
|
-
// src/controllers/listbox_controller.ts
|
|
178
356
|
var OBSERVED_ATTRIBUTES = ["aria-selected", "data-value"];
|
|
179
357
|
function setAttributeIfChanged(element, name, value) {
|
|
180
358
|
if (element.getAttribute(name) !== value) element.setAttribute(name, value);
|
|
181
359
|
}
|
|
182
360
|
var ListboxController = class extends Controller {
|
|
361
|
+
#moves = new MoveCounter();
|
|
183
362
|
static targets = ["trigger", "value", "list", "option", "field"];
|
|
184
363
|
static values = {
|
|
185
364
|
placeholder: { type: String, default: "" }
|
|
186
365
|
};
|
|
187
366
|
static actions = ["close", "onTriggerKeydown", "open", "select", "toggle"];
|
|
188
367
|
static events = ["change", "reconcile"];
|
|
189
|
-
/** Stable ID of the active option; DOM targets are resolved afresh before use. */
|
|
190
368
|
#activeId = null;
|
|
191
|
-
/** Target ID order captured while an option is active, used only for removal fallback. */
|
|
192
369
|
#activeOrder = [];
|
|
193
|
-
|
|
194
|
-
* Collapses the target callbacks, Value changes and observed option writes of
|
|
195
|
-
* one DOM mutation into a single pass; refused before `connect()` and after
|
|
196
|
-
* `disconnect()`.
|
|
197
|
-
*/
|
|
198
|
-
#reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());
|
|
199
|
-
/** Watches the option attributes a page can rewrite in place; set while connected. */
|
|
370
|
+
#reconcile = new MorphRenderWatcher(() => this.#reconcileOptions());
|
|
200
371
|
#observer = null;
|
|
201
|
-
/** The selection last settled: on connect, by the user, or by a reported pass. */
|
|
202
372
|
#settled = { option: null, value: "" };
|
|
203
|
-
|
|
373
|
+
#shownList = null;
|
|
374
|
+
#listHidden = new AttributeLease("hidden", this.identifier);
|
|
375
|
+
#expanded = new AttributeLease("aria-expanded", this.identifier);
|
|
376
|
+
#activeDescendant = new AttributeLease(
|
|
377
|
+
"aria-activedescendant",
|
|
378
|
+
this.identifier
|
|
379
|
+
);
|
|
204
380
|
#typeahead = new Typeahead();
|
|
205
|
-
/**
|
|
206
|
-
* Establishes the ARIA baseline, settles the selection without reporting it,
|
|
207
|
-
* starts closed, and listens for outside clicks.
|
|
208
|
-
*/
|
|
209
381
|
connect() {
|
|
210
382
|
this.#normalizeSelection();
|
|
211
383
|
this.close();
|
|
212
384
|
document.addEventListener("click", this.#onOutsideClick, true);
|
|
213
385
|
this.#settled = this.#selection();
|
|
214
|
-
this.#reconcile.
|
|
386
|
+
this.#reconcile.observe(this.element);
|
|
215
387
|
this.#observeOptions();
|
|
216
388
|
}
|
|
217
|
-
/**
|
|
218
|
-
* Establishes an inactive baseline for a late option, then re-applies the
|
|
219
|
-
* selection baseline and re-resolves active identity once the batch is in.
|
|
220
|
-
*/
|
|
221
389
|
optionTargetConnected(option) {
|
|
222
390
|
option.removeAttribute("data-active");
|
|
223
391
|
this.#reconcile.schedule();
|
|
224
392
|
}
|
|
225
|
-
/**
|
|
226
|
-
* Removes controller-owned active state, then settles the selection and the
|
|
227
|
-
* active option among the surviving targets once the batch is in.
|
|
228
|
-
*/
|
|
229
393
|
optionTargetDisconnected(option) {
|
|
230
394
|
option.removeAttribute("data-active");
|
|
231
395
|
this.#reconcile.schedule();
|
|
232
396
|
}
|
|
233
|
-
|
|
397
|
+
triggerTargetConnected() {
|
|
398
|
+
this.#reconcile.schedule();
|
|
399
|
+
}
|
|
400
|
+
triggerTargetDisconnected(trigger) {
|
|
401
|
+
if (!this.triggerTargets.includes(trigger)) {
|
|
402
|
+
this.#expanded.return(trigger);
|
|
403
|
+
this.#activeDescendant.return(trigger);
|
|
404
|
+
}
|
|
405
|
+
this.#reconcile.schedule();
|
|
406
|
+
}
|
|
407
|
+
listTargetConnected() {
|
|
408
|
+
this.#reconcile.schedule();
|
|
409
|
+
}
|
|
410
|
+
listTargetDisconnected(list) {
|
|
411
|
+
if (!this.listTargets.includes(list)) {
|
|
412
|
+
if (list === this.#shownList) this.#adoptPageClose();
|
|
413
|
+
this.#listHidden.return(list);
|
|
414
|
+
}
|
|
415
|
+
this.#reconcile.schedule();
|
|
416
|
+
}
|
|
234
417
|
fieldTargetConnected() {
|
|
235
418
|
this.#reconcile.schedule();
|
|
236
419
|
}
|
|
237
|
-
|
|
420
|
+
fieldTargetDisconnected() {
|
|
421
|
+
this.#reconcile.schedule();
|
|
422
|
+
}
|
|
238
423
|
valueTargetConnected() {
|
|
239
424
|
this.#reconcile.schedule();
|
|
240
425
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
*/
|
|
426
|
+
valueTargetDisconnected() {
|
|
427
|
+
this.#reconcile.schedule();
|
|
428
|
+
}
|
|
245
429
|
placeholderValueChanged() {
|
|
246
430
|
this.#reconcile.schedule();
|
|
247
431
|
}
|
|
248
|
-
/**
|
|
249
|
-
* Brings the authored DOM to the shape the APG requires, and derives the state
|
|
250
|
-
* that follows from the selection.
|
|
251
|
-
*
|
|
252
|
-
* Three things happen, and only these three — which option is chosen is the
|
|
253
|
-
* author's, and is never changed:
|
|
254
|
-
*
|
|
255
|
-
* 1. Every option gets an explicit value. An absent `aria-selected` means "not
|
|
256
|
-
* selectable" in ARIA, so a forgotten attribute hides a selectable option
|
|
257
|
-
* from assistive technology.
|
|
258
|
-
* 2. At most one stays `true`. The first in DOM order wins, since that is the
|
|
259
|
-
* only deterministic reading of "which one did the author mean"; an option
|
|
260
|
-
* the page inserts ahead of the selection, already selected, takes it over.
|
|
261
|
-
* 3. The trigger label and the hidden field are derived from that selection,
|
|
262
|
-
* an empty one included. Without this the widget announces a choice it does
|
|
263
|
-
* not submit: the popup says "Banana", the trigger still says "Choose…", and
|
|
264
|
-
* the form posts "".
|
|
265
|
-
*
|
|
266
|
-
* Nothing is reported here: `connect()` describes the initial state, and a
|
|
267
|
-
* page-driven pass reports a moved selection itself. The scan is the `option`
|
|
268
|
-
* target set: a `role="option"` without the target is outside the contract and
|
|
269
|
-
* is neither counted nor written.
|
|
270
|
-
*/
|
|
271
432
|
#normalizeSelection() {
|
|
272
433
|
const selected = this.optionTargets.find(
|
|
273
434
|
(option) => option.getAttribute("aria-selected") === "true"
|
|
274
435
|
);
|
|
275
436
|
this.#applySelection(selected);
|
|
276
437
|
}
|
|
277
|
-
/**
|
|
278
|
-
* The pass the page's changes run: the selection is normalized and mirrored,
|
|
279
|
-
* the active option re-resolved, then a selection that moved is reported. The
|
|
280
|
-
* report comes last, so options a subscriber rewrites are the next pass's to
|
|
281
|
-
* settle.
|
|
282
|
-
*/
|
|
283
438
|
#reconcileOptions() {
|
|
284
439
|
this.#normalizeSelection();
|
|
440
|
+
this.#reconcileOpenState();
|
|
285
441
|
this.#reconcileActive();
|
|
286
442
|
this.#reportMove();
|
|
287
443
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
444
|
+
#reconcileOpenState() {
|
|
445
|
+
if (!this.hasListTarget) {
|
|
446
|
+
this.#shownList = null;
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (this.#shownList && this.listTargets.includes(this.#shownList)) this.#adoptPageClose();
|
|
450
|
+
const list = this.listTarget;
|
|
451
|
+
if (this.#shownList !== null && list !== this.#shownList) {
|
|
452
|
+
this.#listHidden.write(list, null);
|
|
453
|
+
this.#shownList = list;
|
|
454
|
+
}
|
|
455
|
+
if (this.hasTriggerTarget) {
|
|
456
|
+
this.#expanded.write(this.triggerTarget, list.hidden ? "false" : "true");
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
#adoptPageClose() {
|
|
460
|
+
if (this.#shownList?.hidden) this.#shownList = null;
|
|
461
|
+
}
|
|
293
462
|
#reportMove() {
|
|
294
463
|
const { option, value } = this.#selection();
|
|
295
464
|
if (option === this.#settled.option && value === this.#settled.value) return;
|
|
296
465
|
this.#settled = { option, value };
|
|
297
466
|
this.dispatch("reconcile", { detail: { value, option } });
|
|
298
467
|
}
|
|
299
|
-
/** The selected option target, if any, and the value its field submits. */
|
|
300
468
|
#selection() {
|
|
301
469
|
const option = this.optionTargets.find((candidate) => candidate.getAttribute("aria-selected") === "true") ?? null;
|
|
302
470
|
return { option, value: option ? this.#optionValue(option) : "" };
|
|
303
471
|
}
|
|
304
|
-
/**
|
|
305
|
-
* Releases the outside-click listener and the option observer, drops a queued
|
|
306
|
-
* pass, and clears the typeahead timer.
|
|
307
|
-
*/
|
|
308
472
|
disconnect() {
|
|
309
|
-
this.#reconcile.
|
|
473
|
+
this.#reconcile.disconnect();
|
|
310
474
|
this.#observer?.disconnect();
|
|
311
475
|
this.#observer = null;
|
|
312
476
|
document.removeEventListener("click", this.#onOutsideClick, true);
|
|
313
477
|
this.#typeahead.reset();
|
|
314
478
|
}
|
|
315
|
-
/**
|
|
316
|
-
* Toggles the list on a real mouse click. Keyboard activation of the
|
|
317
|
-
* `<button>` also fires a click (`detail === 0`); the keydown handler already
|
|
318
|
-
* drives that, so the synthetic click is ignored to avoid double-toggling.
|
|
319
|
-
*/
|
|
320
479
|
toggle(event) {
|
|
321
480
|
if (event.detail === 0) return;
|
|
322
481
|
if (this.#isClosed) {
|
|
@@ -325,7 +484,6 @@ var ListboxController = class extends Controller {
|
|
|
325
484
|
this.close();
|
|
326
485
|
}
|
|
327
486
|
}
|
|
328
|
-
/** Yields claimed keys; otherwise routes the APG select-only keyboard model. */
|
|
329
487
|
onTriggerKeydown(event) {
|
|
330
488
|
if (event.defaultPrevented) return;
|
|
331
489
|
if (isReservedArrowChord(event)) return;
|
|
@@ -351,7 +509,7 @@ var ListboxController = class extends Controller {
|
|
|
351
509
|
switch (event.key) {
|
|
352
510
|
case "ArrowDown":
|
|
353
511
|
event.preventDefault();
|
|
354
|
-
this.#setActive(
|
|
512
|
+
this.#setActive((activeIndex + 1) % length);
|
|
355
513
|
break;
|
|
356
514
|
case "ArrowUp":
|
|
357
515
|
event.preventDefault();
|
|
@@ -387,19 +545,24 @@ var ListboxController = class extends Controller {
|
|
|
387
545
|
break;
|
|
388
546
|
}
|
|
389
547
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
548
|
+
select(source) {
|
|
549
|
+
const { event, host, origin, reason } = actionSource(source);
|
|
550
|
+
if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
|
|
551
|
+
return;
|
|
552
|
+
const option = host?.closest(targetSelector(this.identifier, "option"));
|
|
393
553
|
if (!option || !this.optionTargets.includes(option)) return;
|
|
394
|
-
this
|
|
554
|
+
const focus = event !== null || this.element.contains(document.activeElement);
|
|
555
|
+
if (!this.#selectOption(option, reason)) return;
|
|
395
556
|
this.close();
|
|
396
|
-
this.
|
|
557
|
+
if (focus && (event !== null || this.element.contains(document.activeElement))) {
|
|
558
|
+
this.triggerTarget.focus();
|
|
559
|
+
}
|
|
397
560
|
}
|
|
398
|
-
/** Opens the list and activates the selected option (else the first). */
|
|
399
561
|
open() {
|
|
400
562
|
if (!this.hasListTarget) return;
|
|
401
|
-
this.listTarget
|
|
402
|
-
this.
|
|
563
|
+
this.#listHidden.write(this.listTarget, null);
|
|
564
|
+
this.#shownList = this.listTarget;
|
|
565
|
+
this.#expanded.write(this.triggerTarget, "true");
|
|
403
566
|
if (this.optionTargets.length === 0) {
|
|
404
567
|
this.#setActive(-1);
|
|
405
568
|
return;
|
|
@@ -409,37 +572,33 @@ var ListboxController = class extends Controller {
|
|
|
409
572
|
);
|
|
410
573
|
this.#setActive(selected === -1 ? 0 : selected);
|
|
411
574
|
}
|
|
412
|
-
/** Closes the list, clears the active option, and resets the typeahead buffer. */
|
|
413
575
|
close() {
|
|
414
576
|
if (!this.hasListTarget) return;
|
|
415
|
-
this.listTarget
|
|
416
|
-
this
|
|
577
|
+
this.#listHidden.write(this.listTarget, "");
|
|
578
|
+
this.#shownList = null;
|
|
579
|
+
this.#expanded.write(this.triggerTarget, "false");
|
|
417
580
|
this.#setActive(-1);
|
|
418
581
|
this.#typeahead.reset();
|
|
419
582
|
}
|
|
420
|
-
/** Commits the active option (keyboard) and closes, returning focus. */
|
|
421
583
|
#commitActive() {
|
|
422
|
-
this.#reconcileActive();
|
|
423
584
|
const options = this.optionTargets;
|
|
424
585
|
const activeIndex = this.#findActiveIndex(options);
|
|
425
|
-
const option =
|
|
586
|
+
const option = options[activeIndex];
|
|
426
587
|
if (option) this.#selectOption(option);
|
|
427
588
|
this.close();
|
|
428
589
|
this.triggerTarget.focus();
|
|
429
590
|
}
|
|
430
|
-
|
|
431
|
-
#selectOption(option) {
|
|
591
|
+
#selectOption(option, reason = "user") {
|
|
432
592
|
const { value, fieldChanged } = this.#applySelection(option);
|
|
593
|
+
const changed = option !== this.#settled.option || value !== this.#settled.value;
|
|
433
594
|
this.#settled = { option, value };
|
|
595
|
+
if (!changed) return true;
|
|
596
|
+
const token = this.#moves.record();
|
|
434
597
|
if (fieldChanged) commitField(this.fieldTarget);
|
|
435
|
-
this.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
* `aria-selected`, the trigger label and the hidden field. Emits nothing: the
|
|
440
|
-
* user's commit and a page-driven pass each report it their own way, and
|
|
441
|
-
* `connect()` only describes the state it finds.
|
|
442
|
-
*/
|
|
598
|
+
if (!this.#moves.isLatest(token)) return false;
|
|
599
|
+
this.dispatch("change", { detail: { value, option, reason } });
|
|
600
|
+
return this.#moves.isLatest(token);
|
|
601
|
+
}
|
|
443
602
|
#applySelection(option) {
|
|
444
603
|
for (const candidate of this.optionTargets) {
|
|
445
604
|
setAttributeIfChanged(candidate, "aria-selected", candidate === option ? "true" : "false");
|
|
@@ -450,25 +609,6 @@ var ListboxController = class extends Controller {
|
|
|
450
609
|
const fieldChanged = this.hasFieldTarget && writeField(this.fieldTarget, value);
|
|
451
610
|
return { value, fieldChanged };
|
|
452
611
|
}
|
|
453
|
-
/**
|
|
454
|
-
* Keeps the trigger value on the selection: the selected option's text, or, with
|
|
455
|
-
* nothing selected, `placeholder` when it is set, else the text the value held
|
|
456
|
-
* before this controller first wrote into it.
|
|
457
|
-
*
|
|
458
|
-
* The value is this controller's only while it holds the text last written into
|
|
459
|
-
* it and nothing else — the `owns-label` marker carries that text — and the text
|
|
460
|
-
* it held before is kept in the `original-label` marker. A value the page
|
|
461
|
-
* rewrote or added an element to after that — the marker is still there but no
|
|
462
|
-
* longer matches — is the page's: with nothing selected it is left exactly as it
|
|
463
|
-
* is, `placeholder` or not, and the next selection remembers its text before
|
|
464
|
-
* replacing it. A value with no marker at all — the author's, or one a morph put
|
|
465
|
-
* back without the markers — is taken over the same way by `placeholder` or by a
|
|
466
|
-
* selection. A value that only ever showed a selection has no text of its own to
|
|
467
|
-
* return to, so it is emptied rather than left naming an option that is not
|
|
468
|
-
* selected.
|
|
469
|
-
*
|
|
470
|
-
* @stimeoRenderRoot
|
|
471
|
-
*/
|
|
472
612
|
#renderLabel(option) {
|
|
473
613
|
if (!this.hasValueTarget) return;
|
|
474
614
|
const label = this.valueTarget;
|
|
@@ -493,20 +633,12 @@ var ListboxController = class extends Controller {
|
|
|
493
633
|
if (label.textContent !== text) label.textContent = text;
|
|
494
634
|
setAttributeIfChanged(label, ownsLabel, text);
|
|
495
635
|
}
|
|
496
|
-
/** An option's visible label: its own text, trimmed. */
|
|
497
636
|
#optionLabel(option) {
|
|
498
637
|
return (option.textContent ?? "").trim();
|
|
499
638
|
}
|
|
500
|
-
/** The value an option submits: its `data-value`, else its label. */
|
|
501
639
|
#optionValue(option) {
|
|
502
640
|
return option.dataset.value ?? this.#optionLabel(option);
|
|
503
641
|
}
|
|
504
|
-
/**
|
|
505
|
-
* Watches the options' `aria-selected` and `data-value`. The listbox's own writes
|
|
506
|
-
* to them are dropped from the queue as they happen, so each record the callback
|
|
507
|
-
* receives is the page's, and only a record on one of the listbox's own options
|
|
508
|
-
* schedules a pass.
|
|
509
|
-
*/
|
|
510
642
|
#observeOptions() {
|
|
511
643
|
const observer = new MutationObserver((records) => {
|
|
512
644
|
if (this.#concernsOptions(records)) this.#reconcile.schedule();
|
|
@@ -518,33 +650,16 @@ var ListboxController = class extends Controller {
|
|
|
518
650
|
});
|
|
519
651
|
this.#observer = observer;
|
|
520
652
|
}
|
|
521
|
-
/**
|
|
522
|
-
* Whether any record is about one of this listbox's own options. A widget nested
|
|
523
|
-
* inside it writes the same attributes on its own elements, and those are no
|
|
524
|
-
* reason to reconcile the listbox.
|
|
525
|
-
*/
|
|
526
653
|
#concernsOptions(records) {
|
|
527
654
|
const options = new Set(this.optionTargets);
|
|
528
655
|
return records.some((record) => options.has(record.target));
|
|
529
656
|
}
|
|
530
|
-
/**
|
|
531
|
-
* Drops the records the listbox's own writes just queued, so the observer never
|
|
532
|
-
* takes them for the page's. A page write queued just before goes with them and
|
|
533
|
-
* owes nothing: every such write leaves every option's `aria-selected` at the
|
|
534
|
-
* value the selection gives it, and the selection is then read back from the DOM,
|
|
535
|
-
* so it already holds what the page wrote.
|
|
536
|
-
*/
|
|
537
657
|
#dropOwnRecords() {
|
|
538
658
|
this.#observer?.takeRecords();
|
|
539
659
|
}
|
|
540
|
-
/**
|
|
541
|
-
* Marks the option at `index` active via `data-active` and the trigger's
|
|
542
|
-
* `aria-activedescendant`. Pass `-1` to clear it (the attribute is removed, not
|
|
543
|
-
* set to empty, per the APG).
|
|
544
|
-
*/
|
|
545
660
|
#setActive(index) {
|
|
546
661
|
const options = this.optionTargets;
|
|
547
|
-
const active =
|
|
662
|
+
const active = options[index] ?? null;
|
|
548
663
|
for (const option of options) {
|
|
549
664
|
const marked = option.hasAttribute("data-active");
|
|
550
665
|
if (option === active) {
|
|
@@ -555,15 +670,14 @@ var ListboxController = class extends Controller {
|
|
|
555
670
|
}
|
|
556
671
|
if (active?.id) {
|
|
557
672
|
this.#activeId = active.id;
|
|
558
|
-
this.triggerTarget
|
|
673
|
+
this.#activeDescendant.write(this.triggerTarget, active.id);
|
|
559
674
|
} else {
|
|
560
675
|
this.#activeId = null;
|
|
561
|
-
this.triggerTarget
|
|
676
|
+
this.#activeDescendant.write(this.triggerTarget, null);
|
|
562
677
|
}
|
|
563
678
|
this.#activeOrder = active ? options.map((option) => option.id).filter(Boolean) : [];
|
|
564
679
|
if (active && this.hasListTarget) scrollOptionIntoView(this.listTarget, active);
|
|
565
680
|
}
|
|
566
|
-
/** Resolves active state against the current target collection. */
|
|
567
681
|
#reconcileActive() {
|
|
568
682
|
if (!this.hasTriggerTarget || this.#isClosed) {
|
|
569
683
|
if (this.hasTriggerTarget) this.#setActive(-1);
|
|
@@ -583,17 +697,15 @@ var ListboxController = class extends Controller {
|
|
|
583
697
|
return;
|
|
584
698
|
}
|
|
585
699
|
const activeId = this.triggerTarget.getAttribute("aria-activedescendant") ?? this.#activeId;
|
|
586
|
-
this.#setActive(
|
|
700
|
+
this.#setActive(this.#findFallbackIndex(options, activeId));
|
|
587
701
|
}
|
|
588
|
-
/** Finds the live target carrying the stable ID, or the active marker for an ID-less option. */
|
|
589
702
|
#findActiveIndex(options) {
|
|
590
703
|
const activeId = (this.hasTriggerTarget ? this.triggerTarget.getAttribute("aria-activedescendant") : null) ?? this.#activeId;
|
|
591
704
|
if (activeId) return options.findIndex((option) => option.id === activeId);
|
|
592
705
|
return options.findIndex((option) => option.hasAttribute("data-active"));
|
|
593
706
|
}
|
|
594
|
-
/** Chooses a surviving former successor, then a former predecessor. */
|
|
595
707
|
#findFallbackIndex(options, activeId) {
|
|
596
|
-
const oldIndex = this.#activeOrder.indexOf(activeId);
|
|
708
|
+
const oldIndex = this.#activeOrder.indexOf(activeId ?? "");
|
|
597
709
|
if (oldIndex < 0) return -1;
|
|
598
710
|
const indexesById = new Map(options.map((option, index) => [option.id, index]));
|
|
599
711
|
for (let index = oldIndex + 1; index < this.#activeOrder.length; index += 1) {
|
|
@@ -606,27 +718,17 @@ var ListboxController = class extends Controller {
|
|
|
606
718
|
}
|
|
607
719
|
return -1;
|
|
608
720
|
}
|
|
609
|
-
/**
|
|
610
|
-
* Advances the typeahead query and activates the next matching option.
|
|
611
|
-
*
|
|
612
|
-
* The search resumes just after the active option so repeating a character
|
|
613
|
-
* cycles through the options starting with it, rather than re-activating the
|
|
614
|
-
* same first match on every press.
|
|
615
|
-
*/
|
|
616
721
|
#typeaheadTo(options, activeIndex, char) {
|
|
617
722
|
const index = findTypeaheadMatch(options, activeIndex, this.#typeahead.push(char));
|
|
618
723
|
if (index !== -1) this.#setActive(index);
|
|
619
724
|
}
|
|
620
|
-
/** Closes on an outside click before an inside handler can detach its target. */
|
|
621
725
|
#onOutsideClick = (event) => {
|
|
622
726
|
if (!this.#isClosed && !this.element.contains(event.target)) this.close();
|
|
623
727
|
};
|
|
624
|
-
/** Whether the list is currently hidden. */
|
|
625
728
|
get #isClosed() {
|
|
626
729
|
return !this.hasListTarget || this.listTarget.hidden !== false;
|
|
627
730
|
}
|
|
628
731
|
};
|
|
629
732
|
|
|
630
733
|
export { ListboxController };
|
|
631
|
-
//# sourceMappingURL=listbox_controller.js.map
|
|
632
734
|
//# sourceMappingURL=listbox_controller.js.map
|