stimeo-ui 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +8 -1
- package/dist/cable/index.d.ts +79 -23
- package/dist/cable/index.js +363 -436
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +4 -2
- package/dist/controllers/accordion_controller.js +166 -55
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +12 -2
- package/dist/controllers/alert_dialog_controller.js +862 -221
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +35 -5
- package/dist/controllers/announcer_controller.js +135 -198
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.js +0 -10
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.d.ts +30 -6
- package/dist/controllers/auto_submit_controller.js +271 -127
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +171 -105
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.js +0 -130
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +18 -0
- package/dist/controllers/bulk_select_controller.js +262 -52
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +24 -5
- package/dist/controllers/calendar_controller.js +175 -209
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +34 -7
- package/dist/controllers/carousel_controller.js +461 -399
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +17 -1
- package/dist/controllers/character_counter_controller.js +246 -156
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +7 -2
- package/dist/controllers/checkbox_controller.js +32 -74
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +16 -5
- package/dist/controllers/clipboard_controller.js +168 -147
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +16 -4
- package/dist/controllers/collapsible_controller.js +301 -120
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +18 -6
- package/dist/controllers/color_picker_controller.js +149 -139
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +23 -5
- package/dist/controllers/combobox_controller.js +360 -133
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +45 -14
- package/dist/controllers/command_palette_controller.js +970 -358
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.d.ts +15 -2
- package/dist/controllers/conditional_fields_controller.js +166 -124
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.d.ts +49 -12
- package/dist/controllers/confirm_controller.js +939 -248
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +31 -4
- package/dist/controllers/context_menu_controller.js +256 -128
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.d.ts +13 -1
- package/dist/controllers/count_up_controller.js +73 -21
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +45 -1
- package/dist/controllers/countdown_controller.js +183 -148
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +21 -2
- package/dist/controllers/currency_input_controller.js +134 -158
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +15 -4
- package/dist/controllers/data_grid_controller.js +114 -186
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +30 -5
- package/dist/controllers/date_range_picker_controller.js +288 -206
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +13 -1
- package/dist/controllers/dialog_controller.js +858 -223
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +24 -7
- package/dist/controllers/direct_upload_controller.js +73 -153
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.js +14 -48
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +19 -0
- package/dist/controllers/dismissible_controller.js +175 -21
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +39 -11
- package/dist/controllers/drawer_controller.js +933 -341
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +31 -1
- package/dist/controllers/dropdown_controller.js +312 -82
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.d.ts +12 -1
- package/dist/controllers/editable_controller.js +158 -68
- package/dist/controllers/editable_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.js +67 -53
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +60 -18
- package/dist/controllers/file_dropzone_controller.js +311 -207
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.js +67 -39
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +31 -8
- package/dist/controllers/flash_controller.js +172 -314
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +12 -3
- package/dist/controllers/focus_controller.js +654 -240
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +147 -132
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +13 -86
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +41 -9
- package/dist/controllers/frame_loading_controller.js +264 -213
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +22 -2
- package/dist/controllers/highlight_controller.js +107 -75
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +56 -3
- package/dist/controllers/hover_card_controller.js +309 -118
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +42 -1
- package/dist/controllers/idle_controller.js +267 -101
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.js +71 -72
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.d.ts +32 -13
- package/dist/controllers/intersection_controller.js +147 -106
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +16 -0
- package/dist/controllers/lazy_frame_controller.js +9 -65
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +34 -2
- package/dist/controllers/listbox_controller.js +306 -204
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +1 -1
- package/dist/controllers/local_time_controller.js +54 -64
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +11 -3
- package/dist/controllers/masonry_controller.js +70 -93
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +32 -7
- package/dist/controllers/menu_controller.js +214 -156
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +4 -4
- package/dist/controllers/menubar_controller.js +170 -329
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +20 -0
- package/dist/controllers/meter_controller.js +151 -67
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +32 -4
- package/dist/controllers/multi_select_controller.js +593 -358
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +8 -1
- package/dist/controllers/navigation_menu_controller.js +124 -232
- package/dist/controllers/navigation_menu_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.d.ts +10 -0
- package/dist/controllers/nested_form_controller.js +352 -182
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +23 -0
- package/dist/controllers/network_status_controller.js +242 -54
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +26 -2
- package/dist/controllers/number_input_controller.js +320 -263
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.js +16 -76
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +20 -4
- package/dist/controllers/otp_controller.js +198 -193
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
- package/dist/controllers/overflow_indicator_controller.js +116 -113
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +48 -11
- package/dist/controllers/overflow_menu_controller.js +343 -283
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +35 -6
- package/dist/controllers/pagination_controller.js +295 -119
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +45 -8
- package/dist/controllers/password_reveal_controller.js +389 -121
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +14 -5
- package/dist/controllers/password_strength_controller.js +99 -153
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +11 -4
- package/dist/controllers/persist_controller.js +237 -103
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +19 -7
- package/dist/controllers/pointer_drag_controller.js +185 -165
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +38 -2
- package/dist/controllers/popover_controller.js +236 -97
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +44 -4
- package/dist/controllers/portal_controller.js +209 -76
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.d.ts +10 -4
- package/dist/controllers/preview_guard_controller.js +232 -116
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +11 -0
- package/dist/controllers/progress_controller.js +125 -56
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +14 -5
- package/dist/controllers/radio_group_controller.js +117 -137
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +43 -3
- package/dist/controllers/range_slider_controller.js +192 -105
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +20 -4
- package/dist/controllers/rating_controller.js +418 -207
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +2 -0
- package/dist/controllers/read_more_controller.js +134 -63
- package/dist/controllers/read_more_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.d.ts +1 -1
- package/dist/controllers/reading_progress_controller.js +147 -135
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.d.ts +18 -4
- package/dist/controllers/relative_time_controller.js +145 -110
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
- package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
- package/dist/controllers/reset_on_restore_controller.js.map +1 -0
- package/dist/controllers/resizable_controller.d.ts +28 -2
- package/dist/controllers/resizable_controller.js +255 -139
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.js +39 -84
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +7 -3
- package/dist/controllers/scroll_area_controller.js +428 -225
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_restore_controller.js +0 -53
- package/dist/controllers/scroll_restore_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
- package/dist/controllers/scroll_visibility_controller.js +395 -145
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +6 -1
- package/dist/controllers/scrollspy_controller.js +181 -242
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +19 -4
- package/dist/controllers/separator_controller.js +267 -140
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +64 -7
- package/dist/controllers/sidebar_controller.js +1023 -386
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +23 -0
- package/dist/controllers/skeleton_controller.js +248 -97
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +27 -1
- package/dist/controllers/slider_controller.js +159 -79
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
- package/dist/controllers/smart_sticky_header_controller.js +162 -63
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.js +0 -112
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +39 -4
- package/dist/controllers/spinner_controller.js +277 -169
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +5 -0
- package/dist/controllers/step_indicator_controller.js +99 -46
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +12 -3
- package/dist/controllers/stepper_controller.js +114 -73
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +6 -0
- package/dist/controllers/stick_to_bottom_controller.js +262 -138
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +11 -0
- package/dist/controllers/sticky_observer_controller.js +214 -49
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +23 -8
- package/dist/controllers/submit_once_controller.js +294 -205
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +12 -1
- package/dist/controllers/switch_controller.js +33 -60
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +2 -2
- package/dist/controllers/tabs_controller.js +35 -29
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +12 -0
- package/dist/controllers/tags_input_controller.js +136 -145
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
- package/dist/controllers/textarea_autosize_controller.js +71 -58
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +21 -9
- package/dist/controllers/theme_controller.js +130 -112
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +18 -1
- package/dist/controllers/time_picker_controller.js +146 -80
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +36 -11
- package/dist/controllers/toast_controller.js +186 -297
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +12 -3
- package/dist/controllers/toggle_group_controller.js +95 -136
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +68 -98
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +45 -3
- package/dist/controllers/tooltip_controller.js +158 -124
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +22 -4
- package/dist/controllers/transition_controller.js +123 -119
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +21 -11
- package/dist/controllers/tree_view_controller.js +150 -345
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +6370 -9346
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +23 -3
- package/dist/inspector/cli.js +92 -37
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +92 -56
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +8 -8
- package/dist/inspector/manifest.json +787 -100
- package/dist/positioning/index.d.ts +8 -0
- package/dist/positioning/index.js +148 -47
- package/dist/positioning/index.js.map +1 -1
- package/package.json +6 -6
- package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
- package/dist/controllers/reset_before_cache_controller.js.map +0 -1
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
// src/controllers/nested_form_controller.ts
|
|
4
|
-
|
|
5
|
-
// src/utils/announce.ts
|
|
6
3
|
function announce(message, options = {}) {
|
|
7
4
|
const text = message.trim();
|
|
8
5
|
if (text.length === 0) return;
|
|
@@ -19,77 +16,153 @@ function fillTemplate(template, values) {
|
|
|
19
16
|
});
|
|
20
17
|
}
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
function sharedRegistry(key, create) {
|
|
20
|
+
const symbol = Symbol.for(key);
|
|
21
|
+
const scope = globalThis;
|
|
22
|
+
const existing = scope[symbol];
|
|
23
|
+
if (existing !== void 0) return existing;
|
|
24
|
+
const registry = create();
|
|
25
|
+
Object.defineProperty(globalThis, symbol, { value: registry });
|
|
26
|
+
return registry;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isAttributeValue(value) {
|
|
30
|
+
return value === null || typeof value === "string";
|
|
31
|
+
}
|
|
32
|
+
function readLeaseRecord(element, record) {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
35
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
36
|
+
const [original, written, ...rest] = parsed;
|
|
37
|
+
const linked = rest[0] !== false;
|
|
38
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
39
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
40
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
41
|
+
} catch {
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
var AttributeLease = class _AttributeLease {
|
|
46
|
+
static #registry = sharedRegistry(
|
|
47
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
48
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
49
|
+
);
|
|
24
50
|
#attribute;
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
|
|
51
|
+
#record;
|
|
52
|
+
#base;
|
|
53
|
+
#held = /* @__PURE__ */ new Set();
|
|
54
|
+
constructor(attribute, owner, options = {}) {
|
|
28
55
|
this.#attribute = attribute;
|
|
56
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
57
|
+
this.#base = options.base === true;
|
|
58
|
+
const names = _AttributeLease.#registry.names;
|
|
59
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
29
60
|
}
|
|
30
|
-
/** Writes or removes the leased attribute while preserving its authored value. */
|
|
31
61
|
write(element, value) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
this.#reflect(element, value);
|
|
62
|
+
const stack = this.#stack(element);
|
|
63
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
64
|
+
this.#held.add(element);
|
|
65
|
+
const shows = !this.#covered(element, stack, hold);
|
|
66
|
+
hold.written = value;
|
|
67
|
+
this.#mark(element, stack);
|
|
68
|
+
if (shows) this.#reflect(element, value);
|
|
42
69
|
}
|
|
43
|
-
/** Returns one lease without overwriting a value subsequently authored by a consumer. */
|
|
44
70
|
return(element) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
this.#
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
71
|
+
const held = this.#held.delete(element);
|
|
72
|
+
const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
|
|
73
|
+
const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
|
|
74
|
+
const hold = this.#take(stack);
|
|
75
|
+
if (!hold) return false;
|
|
76
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
77
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
78
|
+
const index = stack.indexOf(hold);
|
|
79
|
+
stack.splice(index, 1);
|
|
80
|
+
const above = stack[index];
|
|
81
|
+
if (above) above.linked &&= hold.linked;
|
|
82
|
+
this.#mark(element, stack);
|
|
83
|
+
if (!owns) return true;
|
|
84
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
85
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
returnAll() {
|
|
89
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
90
|
+
}
|
|
91
|
+
#stack(element) {
|
|
92
|
+
const stacks = _AttributeLease.#registry.stacks;
|
|
93
|
+
const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
94
|
+
stacks.set(element, byAttribute);
|
|
95
|
+
const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
|
|
96
|
+
byAttribute.set(this.#attribute, stack);
|
|
97
|
+
return stack;
|
|
98
|
+
}
|
|
99
|
+
#seed(element) {
|
|
100
|
+
const found = [];
|
|
101
|
+
for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
|
|
102
|
+
const read = readLeaseRecord(element, record);
|
|
103
|
+
if (read) found.push({ ...read, record });
|
|
104
|
+
}
|
|
105
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
106
|
+
return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
107
|
+
lease: null,
|
|
108
|
+
record,
|
|
109
|
+
written,
|
|
110
|
+
original,
|
|
111
|
+
linked: linked && index > 0
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
#take(stack) {
|
|
115
|
+
const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
|
|
116
|
+
if (hold) hold.lease = this;
|
|
117
|
+
return hold;
|
|
118
|
+
}
|
|
119
|
+
#acquire(element, stack) {
|
|
120
|
+
const current = element.getAttribute(this.#attribute);
|
|
121
|
+
const top = stack.at(-1);
|
|
122
|
+
const bottom = stack[0];
|
|
123
|
+
const over = top?.written === current ? top : void 0;
|
|
124
|
+
const from = this.#base ? bottom : over;
|
|
125
|
+
const hold = {
|
|
126
|
+
lease: this,
|
|
127
|
+
record: this.#record,
|
|
128
|
+
written: current,
|
|
129
|
+
original: from ? from.original : current,
|
|
130
|
+
linked: !this.#base && over !== void 0
|
|
131
|
+
};
|
|
132
|
+
if (this.#base && bottom) {
|
|
133
|
+
bottom.linked = true;
|
|
134
|
+
stack.unshift(hold);
|
|
135
|
+
} else stack.push(hold);
|
|
136
|
+
return hold;
|
|
137
|
+
}
|
|
138
|
+
#covered(element, stack, hold) {
|
|
139
|
+
const current = element.getAttribute(this.#attribute);
|
|
140
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
141
|
+
}
|
|
142
|
+
#mark(element, stack) {
|
|
143
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
144
|
+
stack.forEach((hold, index) => {
|
|
145
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
146
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
147
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
148
|
+
records.set(
|
|
149
|
+
hold.record,
|
|
150
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
for (const [name, recorded] of records) {
|
|
154
|
+
if (element.getAttribute(name) === recorded) continue;
|
|
155
|
+
if (recorded === null) element.removeAttribute(name);
|
|
156
|
+
else element.setAttribute(name, recorded);
|
|
157
|
+
}
|
|
50
158
|
}
|
|
51
|
-
/** Reflects only a real value transition, avoiding self-triggered mutation work. */
|
|
52
159
|
#reflect(element, value) {
|
|
53
160
|
if (element.getAttribute(this.#attribute) === value) return;
|
|
54
161
|
if (value === null) element.removeAttribute(this.#attribute);
|
|
55
162
|
else element.setAttribute(this.#attribute, value);
|
|
56
163
|
}
|
|
57
|
-
/** Returns every outstanding lease using the same ownership check as {@link return}. */
|
|
58
|
-
returnAll() {
|
|
59
|
-
for (const element of Array.from(this.#records.keys())) this.return(element);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// src/utils/before_cache_reset.ts
|
|
64
|
-
var BeforeCacheReset = class _BeforeCacheReset {
|
|
65
|
-
/** Every subscribed instance, iterated by the one shared document listener. */
|
|
66
|
-
static #subscribers = /* @__PURE__ */ new Set();
|
|
67
|
-
/** The shared listener; installed while at least one instance is subscribed. */
|
|
68
|
-
static #onBeforeCache = () => {
|
|
69
|
-
for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
|
|
70
|
-
};
|
|
71
|
-
#rewind;
|
|
72
|
-
/** @param rewind - the pass that returns this controller's state to its initial form. */
|
|
73
|
-
constructor(rewind) {
|
|
74
|
-
this.#rewind = rewind;
|
|
75
|
-
}
|
|
76
|
-
/** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
|
|
77
|
-
activate() {
|
|
78
|
-
const first = _BeforeCacheReset.#subscribers.size === 0;
|
|
79
|
-
_BeforeCacheReset.#subscribers.add(this);
|
|
80
|
-
if (first) {
|
|
81
|
-
document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
/** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
|
|
85
|
-
deactivate() {
|
|
86
|
-
_BeforeCacheReset.#subscribers.delete(this);
|
|
87
|
-
if (_BeforeCacheReset.#subscribers.size > 0) return;
|
|
88
|
-
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
89
|
-
}
|
|
90
164
|
};
|
|
91
165
|
|
|
92
|
-
// src/utils/focus_candidate.ts
|
|
93
166
|
function inheritsFieldsetDisabled(control) {
|
|
94
167
|
let fieldset = control.closest("fieldset[disabled]");
|
|
95
168
|
while (fieldset) {
|
|
@@ -99,8 +172,19 @@ function inheritsFieldsetDisabled(control) {
|
|
|
99
172
|
}
|
|
100
173
|
return false;
|
|
101
174
|
}
|
|
175
|
+
function unslotted(element) {
|
|
176
|
+
return element.assignedSlot === null && element.parentElement?.shadowRoot != null;
|
|
177
|
+
}
|
|
178
|
+
function flatTreeParent(element) {
|
|
179
|
+
if (unslotted(element)) return null;
|
|
180
|
+
const parent = element.assignedSlot ?? element.parentNode;
|
|
181
|
+
if (parent instanceof ShadowRoot) return parent.host;
|
|
182
|
+
return parent instanceof Element ? parent : null;
|
|
183
|
+
}
|
|
102
184
|
function canTakeFocus(element) {
|
|
103
|
-
|
|
185
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
186
|
+
if (unslotted(node) || node.matches("[hidden], [inert]")) return false;
|
|
187
|
+
}
|
|
104
188
|
if (element instanceof HTMLInputElement && element.type === "hidden") return false;
|
|
105
189
|
if (!("disabled" in element)) return true;
|
|
106
190
|
if (element.disabled) return false;
|
|
@@ -164,27 +248,33 @@ function firstTabStop(root) {
|
|
|
164
248
|
return null;
|
|
165
249
|
}
|
|
166
250
|
|
|
167
|
-
|
|
251
|
+
var ListenerSet = class {
|
|
252
|
+
#abort = new AbortController();
|
|
253
|
+
add(target, type, handler, options) {
|
|
254
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
255
|
+
}
|
|
256
|
+
dispose() {
|
|
257
|
+
this.#abort.abort();
|
|
258
|
+
this.#abort = new AbortController();
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
168
262
|
var MicrotaskCoalescer = class {
|
|
169
263
|
#run;
|
|
170
264
|
#queued = false;
|
|
171
265
|
#active = false;
|
|
172
266
|
#generation = 0;
|
|
173
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
174
267
|
constructor(run) {
|
|
175
268
|
this.#run = run;
|
|
176
269
|
}
|
|
177
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
178
270
|
activate() {
|
|
179
271
|
this.#active = true;
|
|
180
272
|
}
|
|
181
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
182
273
|
cancel() {
|
|
183
274
|
this.#active = false;
|
|
184
275
|
this.#queued = false;
|
|
185
276
|
this.#generation += 1;
|
|
186
277
|
}
|
|
187
|
-
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
188
278
|
schedule() {
|
|
189
279
|
if (!this.#active || this.#queued) return;
|
|
190
280
|
this.#queued = true;
|
|
@@ -197,56 +287,195 @@ var MicrotaskCoalescer = class {
|
|
|
197
287
|
}
|
|
198
288
|
};
|
|
199
289
|
|
|
200
|
-
|
|
201
|
-
|
|
290
|
+
var MorphRenderWatcher = class {
|
|
291
|
+
#listeners = new ListenerSet();
|
|
292
|
+
#pass;
|
|
293
|
+
#element = null;
|
|
294
|
+
#onMorph = () => this.#pass.schedule();
|
|
295
|
+
constructor(run) {
|
|
296
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
297
|
+
}
|
|
298
|
+
observe(element) {
|
|
299
|
+
if (this.#element === element) return;
|
|
300
|
+
this.disconnect();
|
|
301
|
+
this.#element = element;
|
|
302
|
+
this.#pass.activate();
|
|
303
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
304
|
+
}
|
|
305
|
+
schedule() {
|
|
306
|
+
this.#pass.schedule();
|
|
307
|
+
}
|
|
308
|
+
disconnect() {
|
|
309
|
+
this.#listeners.dispose();
|
|
310
|
+
this.#pass.cancel();
|
|
311
|
+
this.#element = null;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
var NUMBER_BOUNDS = {
|
|
316
|
+
nonNegative: { finite: true, min: 0 }};
|
|
317
|
+
function matchesNumberBounds(value, bounds) {
|
|
318
|
+
if (!Number.isFinite(value)) {
|
|
319
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
320
|
+
if (direction === null) return false;
|
|
321
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
322
|
+
}
|
|
323
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
324
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
325
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
326
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
327
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function readNumber(raw, fallback, bounds) {
|
|
332
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
var NumberValueReader = class {
|
|
336
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
337
|
+
read(owner, name, raw, fallback, bounds) {
|
|
338
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
339
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
340
|
+
this.#lastRejected.delete(name);
|
|
341
|
+
return resolved;
|
|
342
|
+
}
|
|
343
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
344
|
+
const literal = owner.element.getAttribute(attribute);
|
|
345
|
+
if (literal === null) {
|
|
346
|
+
this.#lastRejected.delete(name);
|
|
347
|
+
return resolved;
|
|
348
|
+
}
|
|
349
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
350
|
+
this.#lastRejected.set(name, literal);
|
|
351
|
+
console.warn(
|
|
352
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
return resolved;
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
function queryOpenDescendants(root, selector) {
|
|
360
|
+
const matches = [];
|
|
361
|
+
const roots = [root];
|
|
362
|
+
for (const scope of roots) {
|
|
363
|
+
matches.push(...scope.querySelectorAll(selector));
|
|
364
|
+
if (scope.nodeType === Node.ELEMENT_NODE) {
|
|
365
|
+
const shadow = scope.shadowRoot;
|
|
366
|
+
if (shadow) roots.push(shadow);
|
|
367
|
+
}
|
|
368
|
+
for (const element of scope.querySelectorAll("*")) {
|
|
369
|
+
if (element.shadowRoot) roots.push(element.shadowRoot);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return matches;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
var TabindexLoan = class _TabindexLoan {
|
|
376
|
+
static #registry = sharedRegistry(
|
|
377
|
+
"stimeo-ui.tabindex-loan.registry.v1",
|
|
378
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
|
|
379
|
+
);
|
|
202
380
|
#value;
|
|
381
|
+
#record;
|
|
203
382
|
#lent = /* @__PURE__ */ new Set();
|
|
204
|
-
|
|
205
|
-
#beforeCache = new BeforeCacheReset(() => this.returnAll());
|
|
206
|
-
/**
|
|
207
|
-
* @param value - the `tabindex` to lend. `"-1"` (the default) is
|
|
208
|
-
* programmatically focusable but not a Tab stop; `"0"` is a real Tab stop,
|
|
209
|
-
* which a scroll region with no focusable content of its own needs.
|
|
210
|
-
*/
|
|
211
|
-
constructor(value = "-1") {
|
|
383
|
+
constructor(value, owner) {
|
|
212
384
|
this.#value = value;
|
|
385
|
+
this.#record = `data-${owner}-tabindex-loan`;
|
|
386
|
+
_TabindexLoan.#registry.names.add(this.#record);
|
|
213
387
|
}
|
|
214
|
-
/** Lends `element` the value; no-ops when it already carries a `tabindex`. */
|
|
215
388
|
lend(element) {
|
|
216
|
-
|
|
217
|
-
element.
|
|
389
|
+
const stack = this.#stack(element);
|
|
390
|
+
const current = element.getAttribute("tabindex");
|
|
391
|
+
const held = stack.find((hold) => hold.loan === this) ?? stack.find(
|
|
392
|
+
(hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
|
|
393
|
+
);
|
|
394
|
+
const linked = stack.at(-1)?.value === current;
|
|
395
|
+
if (!held && current !== null && !linked) return;
|
|
218
396
|
this.#lent.add(element);
|
|
219
|
-
|
|
397
|
+
if (held) held.loan = this;
|
|
398
|
+
else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
|
|
399
|
+
this.#mark(element, stack);
|
|
400
|
+
if (current === null || !held && current !== this.#value) {
|
|
401
|
+
element.setAttribute("tabindex", this.#value);
|
|
402
|
+
}
|
|
220
403
|
}
|
|
221
|
-
/** Takes back every loan whose value is still the one that was lent. */
|
|
222
404
|
returnAll() {
|
|
223
405
|
for (const element of this.#lent) {
|
|
224
|
-
|
|
406
|
+
const stack = this.#stack(element);
|
|
407
|
+
const hold = stack.find((held) => held.loan === this);
|
|
408
|
+
if (hold) this.#release(element, stack, hold);
|
|
225
409
|
}
|
|
226
410
|
this.#lent.clear();
|
|
227
|
-
|
|
411
|
+
}
|
|
412
|
+
reclaim(element) {
|
|
413
|
+
if (element.getAttribute(this.#record) !== this.#value) return;
|
|
414
|
+
const stack = this.#stack(element);
|
|
415
|
+
const hold = stack.find((held) => held.loan === null && held.record === this.#record);
|
|
416
|
+
if (hold && !element.matches(":focus")) this.#release(element, stack, hold);
|
|
417
|
+
}
|
|
418
|
+
reclaimWithin(root) {
|
|
419
|
+
for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
|
|
420
|
+
this.reclaim(element);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
#stack(element) {
|
|
424
|
+
const stacks = _TabindexLoan.#registry.stacks;
|
|
425
|
+
const stack = stacks.get(element) ?? this.#seed(element);
|
|
426
|
+
stacks.set(element, stack);
|
|
427
|
+
return stack;
|
|
428
|
+
}
|
|
429
|
+
#seed(element) {
|
|
430
|
+
const current = element.getAttribute("tabindex");
|
|
431
|
+
const holds = [];
|
|
432
|
+
for (const record of _TabindexLoan.#registry.names) {
|
|
433
|
+
const value = element.getAttribute(record);
|
|
434
|
+
if (value !== null) holds.push({ loan: null, record, value, linked: true });
|
|
435
|
+
}
|
|
436
|
+
holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
|
|
437
|
+
for (const [index, hold] of holds.entries()) hold.linked = index > 0;
|
|
438
|
+
return holds;
|
|
439
|
+
}
|
|
440
|
+
#release(element, stack, hold) {
|
|
441
|
+
const current = element.getAttribute("tabindex");
|
|
442
|
+
const index = stack.indexOf(hold);
|
|
443
|
+
const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
|
|
444
|
+
stack.splice(index, 1);
|
|
445
|
+
const above = stack[index];
|
|
446
|
+
if (above) above.linked &&= hold.linked;
|
|
447
|
+
this.#mark(element, stack);
|
|
448
|
+
if (!owns) return;
|
|
449
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
450
|
+
if (!below) element.removeAttribute("tabindex");
|
|
451
|
+
else if (below.value !== current) element.setAttribute("tabindex", below.value);
|
|
452
|
+
}
|
|
453
|
+
#mark(element, stack) {
|
|
454
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
455
|
+
for (const hold of stack) records.set(hold.record, hold.value);
|
|
456
|
+
for (const [name, recorded] of records) {
|
|
457
|
+
if (element.getAttribute(name) === recorded) continue;
|
|
458
|
+
if (recorded === null) element.removeAttribute(name);
|
|
459
|
+
else element.setAttribute(name, recorded);
|
|
460
|
+
}
|
|
228
461
|
}
|
|
229
462
|
};
|
|
230
463
|
|
|
231
|
-
// src/utils/target_selector.ts
|
|
232
464
|
function targetSelector(identifier, name) {
|
|
233
465
|
return `[data-${identifier}-target~="${name}"]`;
|
|
234
466
|
}
|
|
235
467
|
|
|
236
|
-
// src/controllers/nested_form_controller.ts
|
|
237
468
|
var REMOVE_PART = "remove";
|
|
238
469
|
var DESTROY_FLAG_PART = "destroyFlag";
|
|
239
470
|
var DESTROYED_VALUES = /* @__PURE__ */ new Set(["1", "true"]);
|
|
240
|
-
var NestedFormController = class extends Controller {
|
|
241
|
-
|
|
471
|
+
var NestedFormController = class _NestedFormController extends Controller {
|
|
472
|
+
#numbers = new NumberValueReader();
|
|
242
473
|
get #rootSelector() {
|
|
243
474
|
return `[data-controller~="${this.identifier}"]`;
|
|
244
475
|
}
|
|
245
|
-
/** Selects per-row remove buttons, in the namespace this controller is registered under. */
|
|
246
476
|
get #removeSelector() {
|
|
247
477
|
return targetSelector(this.identifier, REMOVE_PART);
|
|
248
478
|
}
|
|
249
|
-
/** Selects the hidden `_destroy` input a persisted row carries. */
|
|
250
479
|
get #destroyFlagSelector() {
|
|
251
480
|
return targetSelector(this.identifier, DESTROY_FLAG_PART);
|
|
252
481
|
}
|
|
@@ -258,27 +487,20 @@ var NestedFormController = class extends Controller {
|
|
|
258
487
|
announce: { type: Boolean, default: true },
|
|
259
488
|
countMessage: { type: String, default: "" }
|
|
260
489
|
};
|
|
490
|
+
static valueConstraints = {
|
|
491
|
+
min: NUMBER_BOUNDS.nonNegative,
|
|
492
|
+
max: NUMBER_BOUNDS.nonNegative
|
|
493
|
+
};
|
|
261
494
|
static actions = ["add"];
|
|
262
495
|
static events = ["add", "remove", "reconcile"];
|
|
263
|
-
/** Monotonic source for unique row indices; never a row-state counter. */
|
|
264
496
|
#lastIndex = 0;
|
|
265
497
|
#warnedMissing = false;
|
|
266
498
|
#warnedTemplate = false;
|
|
267
|
-
/** The state last written to the hooks; reconciliation reports only real moves. */
|
|
268
499
|
#published = null;
|
|
269
|
-
/** Watches the list for row changes the controller did not perform itself. */
|
|
270
500
|
#observer = null;
|
|
271
|
-
#reconcile = new
|
|
272
|
-
|
|
273
|
-
#
|
|
274
|
-
#beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
|
|
275
|
-
/** Makes the root a programmatic focus destination when no other candidate survives. */
|
|
276
|
-
#tabindex = new TabindexLoan();
|
|
277
|
-
/**
|
|
278
|
-
* Delegated click handler for the per-row remove buttons (dynamic-safe). Only
|
|
279
|
-
* buttons whose nearest nested-form root is this instance are acted on, so a
|
|
280
|
-
* nested inner form's buttons never remove an outer row.
|
|
281
|
-
*/
|
|
501
|
+
#reconcile = new MorphRenderWatcher(() => this.#reconcileNow());
|
|
502
|
+
#addDisabled = new AttributeLease("disabled", this.identifier);
|
|
503
|
+
#tabindex = new TabindexLoan("-1", this.identifier);
|
|
282
504
|
#onClick = (event) => {
|
|
283
505
|
const target = event.target;
|
|
284
506
|
const button = target?.closest(this.#removeSelector);
|
|
@@ -287,11 +509,11 @@ var NestedFormController = class extends Controller {
|
|
|
287
509
|
if (row) this.#removeRow(row);
|
|
288
510
|
};
|
|
289
511
|
connect() {
|
|
512
|
+
this.#tabindex.reclaim(this.element);
|
|
290
513
|
this.#warnedMissing = false;
|
|
291
514
|
this.#warnedTemplate = false;
|
|
292
515
|
this.element.addEventListener("click", this.#onClick);
|
|
293
|
-
this.#reconcile.
|
|
294
|
-
this.#beforeCache.activate();
|
|
516
|
+
this.#reconcile.observe(this.element);
|
|
295
517
|
if (!this.hasListTarget || !this.hasTemplateTarget) this.#warnMissing();
|
|
296
518
|
this.#refresh();
|
|
297
519
|
}
|
|
@@ -299,28 +521,19 @@ var NestedFormController = class extends Controller {
|
|
|
299
521
|
this.element.removeEventListener("click", this.#onClick);
|
|
300
522
|
this.#observer?.disconnect();
|
|
301
523
|
this.#observer = null;
|
|
302
|
-
this.#reconcile.
|
|
303
|
-
this.#
|
|
304
|
-
this.#rewindForCache();
|
|
524
|
+
this.#reconcile.disconnect();
|
|
525
|
+
this.#addDisabled.returnAll();
|
|
305
526
|
this.#tabindex.returnAll();
|
|
306
527
|
this.#published = null;
|
|
307
528
|
}
|
|
308
|
-
/** Follows an arriving or swapped-in list: rebind to the primary, then reconcile. */
|
|
309
529
|
listTargetConnected() {
|
|
310
530
|
this.#rebindObserver();
|
|
311
531
|
this.#reconcile.schedule();
|
|
312
532
|
}
|
|
313
|
-
/** Follows a departing list the same way — the primary may have changed. */
|
|
314
533
|
listTargetDisconnected() {
|
|
315
534
|
this.#rebindObserver();
|
|
316
535
|
this.#reconcile.schedule();
|
|
317
536
|
}
|
|
318
|
-
/**
|
|
319
|
-
* Points the observer at the current primary list. Re-deriving on every list
|
|
320
|
-
* arrival and departure makes the binding independent of the order Stimulus
|
|
321
|
-
* reports an overlapping swap in — a staggered swap (successor appended before
|
|
322
|
-
* the old list leaves) ends observed and reconciled either way.
|
|
323
|
-
*/
|
|
324
537
|
#rebindObserver() {
|
|
325
538
|
this.#observer?.disconnect();
|
|
326
539
|
this.#observer = null;
|
|
@@ -333,7 +546,6 @@ var NestedFormController = class extends Controller {
|
|
|
333
546
|
attributeFilter: ["value"]
|
|
334
547
|
});
|
|
335
548
|
}
|
|
336
|
-
/** Returns the lease with a departing add button; a new one re-arms on refresh. */
|
|
337
549
|
addTargetDisconnected(target) {
|
|
338
550
|
this.#addDisabled.return(target);
|
|
339
551
|
this.#reconcile.schedule();
|
|
@@ -341,21 +553,12 @@ var NestedFormController = class extends Controller {
|
|
|
341
553
|
addTargetConnected() {
|
|
342
554
|
this.#reconcile.schedule();
|
|
343
555
|
}
|
|
344
|
-
/** Re-clamps when application code or a Turbo morph changes `min`. */
|
|
345
556
|
minValueChanged() {
|
|
346
557
|
this.#reconcile.schedule();
|
|
347
558
|
}
|
|
348
|
-
/** Re-clamps when application code or a Turbo morph changes `max`. */
|
|
349
559
|
maxValueChanged() {
|
|
350
560
|
this.#reconcile.schedule();
|
|
351
561
|
}
|
|
352
|
-
/**
|
|
353
|
-
* Clones the template row, replaces the index placeholder with a unique value,
|
|
354
|
-
* appends it, focuses its first tab stop, and announces the new count. No-ops at
|
|
355
|
-
* `max`, when the required targets are missing (named on the console once per
|
|
356
|
-
* connection), or when the template does not produce exactly one root element
|
|
357
|
-
* (also named once; the insertion is rolled back so nothing accumulates).
|
|
358
|
-
*/
|
|
359
562
|
add() {
|
|
360
563
|
if (!this.hasListTarget || !this.hasTemplateTarget) {
|
|
361
564
|
this.#warnMissing();
|
|
@@ -386,13 +589,6 @@ var NestedFormController = class extends Controller {
|
|
|
386
589
|
this.dispatch("add", { detail: { index, element: row } });
|
|
387
590
|
this.#announce();
|
|
388
591
|
}
|
|
389
|
-
/**
|
|
390
|
-
* Removes a row: a persisted row (one carrying its own `destroyFlag`) has the
|
|
391
|
-
* flag set to `1` and is hidden so Rails destroys it on submit; an unsaved row
|
|
392
|
-
* is dropped from the DOM. Returns focus to a surviving row. No-ops at `min`.
|
|
393
|
-
*
|
|
394
|
-
* @stimeoRuntimeOnly `min` decides whether this one removal is allowed.
|
|
395
|
-
*/
|
|
396
592
|
#removeRow(row) {
|
|
397
593
|
const rows = this.#effectiveRows;
|
|
398
594
|
if (this.#destroyed(row)) {
|
|
@@ -400,7 +596,7 @@ var NestedFormController = class extends Controller {
|
|
|
400
596
|
this.#focusAfterRemove(this.#positionAmong(rows, row));
|
|
401
597
|
return;
|
|
402
598
|
}
|
|
403
|
-
if (rows.length <= this
|
|
599
|
+
if (rows.length <= this.#safeMin) return;
|
|
404
600
|
const position = rows.indexOf(row);
|
|
405
601
|
const flag = this.#destroyFlagOf(row);
|
|
406
602
|
const persisted = flag !== null;
|
|
@@ -415,21 +611,11 @@ var NestedFormController = class extends Controller {
|
|
|
415
611
|
this.dispatch("remove", { detail: { element: row, persisted } });
|
|
416
612
|
this.#announce();
|
|
417
613
|
}
|
|
418
|
-
/**
|
|
419
|
-
* The index of the first effective row following `row` in document order.
|
|
420
|
-
* -1 (no following row) feeds the focus slices as a negative index, which
|
|
421
|
-
* yields the same fully-reversed nearest-first order as `rows.length` would.
|
|
422
|
-
*/
|
|
423
614
|
#positionAmong(rows, row) {
|
|
424
615
|
return rows.findIndex(
|
|
425
616
|
(candidate) => (row.compareDocumentPosition(candidate) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0
|
|
426
617
|
);
|
|
427
618
|
}
|
|
428
|
-
/**
|
|
429
|
-
* Moves focus to the first tab stop of the nearest surviving row — following
|
|
430
|
-
* rows first, then preceding ones — falling back to the add button and finally
|
|
431
|
-
* to the root via a temporary `tabindex`.
|
|
432
|
-
*/
|
|
433
619
|
#focusAfterRemove(position) {
|
|
434
620
|
const rows = this.#effectiveRows;
|
|
435
621
|
const ordered = [...rows.slice(position), ...rows.slice(0, position).reverse()];
|
|
@@ -447,31 +633,20 @@ var NestedFormController = class extends Controller {
|
|
|
447
633
|
this.#tabindex.lend(this.element);
|
|
448
634
|
this.element.focus();
|
|
449
635
|
}
|
|
450
|
-
/**
|
|
451
|
-
* Recomputes the count and min/max hooks from the DOM and records them as published.
|
|
452
|
-
*
|
|
453
|
-
* @stimeoRenderRoot
|
|
454
|
-
*/
|
|
455
636
|
#refresh() {
|
|
456
637
|
if (!this.hasListTarget) return;
|
|
457
638
|
const count = this.#effectiveRows.length;
|
|
458
|
-
const atMin = count <= this
|
|
459
|
-
const atMax = this
|
|
639
|
+
const atMin = count <= this.#safeMin;
|
|
640
|
+
const atMax = this.#safeMax > 0 && count >= this.#safeMax;
|
|
460
641
|
this.element.setAttribute("data-nested-count", String(count));
|
|
461
642
|
this.#reflect("data-nested-at-max", atMax);
|
|
462
643
|
this.#reflect("data-nested-at-min", atMin);
|
|
463
644
|
if (this.hasAddTarget) {
|
|
464
|
-
if (this
|
|
645
|
+
if (this.#safeMax > 0) this.#addDisabled.write(this.addTarget, atMax ? "" : null);
|
|
465
646
|
else this.#addDisabled.return(this.addTarget);
|
|
466
647
|
}
|
|
467
648
|
this.#published = { count, atMin, atMax };
|
|
468
649
|
}
|
|
469
|
-
/**
|
|
470
|
-
* Applies row changes the controller did not perform itself (Turbo Streams,
|
|
471
|
-
* morphs, runtime Value changes): refreshes the hooks and reports a moved
|
|
472
|
-
* public state as `reconcile`. The controller's own operations refresh
|
|
473
|
-
* synchronously first, so their observer echo arrives here as a no-move.
|
|
474
|
-
*/
|
|
475
650
|
#reconcileNow() {
|
|
476
651
|
const previous = this.#published;
|
|
477
652
|
this.#refresh();
|
|
@@ -480,25 +655,14 @@ var NestedFormController = class extends Controller {
|
|
|
480
655
|
const moved = previous.count !== current.count || previous.atMin !== current.atMin || previous.atMax !== current.atMax;
|
|
481
656
|
if (moved) this.dispatch("reconcile", { detail: { ...current } });
|
|
482
657
|
}
|
|
483
|
-
/**
|
|
484
|
-
* Bridges the count change to the shared announcer when configured.
|
|
485
|
-
*
|
|
486
|
-
* @stimeoRuntimeOnly `announce` and `countMessage` word the one announcement of this change.
|
|
487
|
-
*/
|
|
488
658
|
#announce() {
|
|
489
659
|
if (!this.announceValue || this.countMessageValue === "") return;
|
|
490
660
|
announce(fillTemplate(this.countMessageValue, { count: this.#effectiveRows.length }));
|
|
491
661
|
}
|
|
492
|
-
/** Sets `attribute` to `"true"` when `on`, else removes it. */
|
|
493
662
|
#reflect(attribute, on) {
|
|
494
663
|
if (on) this.element.setAttribute(attribute, "true");
|
|
495
664
|
else this.element.removeAttribute(attribute);
|
|
496
665
|
}
|
|
497
|
-
/** Returns the disabled lease so an authored value never leaks into a snapshot. */
|
|
498
|
-
#rewindForCache() {
|
|
499
|
-
if (this.hasAddTarget) this.#addDisabled.return(this.addTarget);
|
|
500
|
-
}
|
|
501
|
-
/** Names the missing required target(s) once per connection. */
|
|
502
666
|
#warnMissing() {
|
|
503
667
|
if (this.#warnedMissing) return;
|
|
504
668
|
this.#warnedMissing = true;
|
|
@@ -510,49 +674,38 @@ var NestedFormController = class extends Controller {
|
|
|
510
674
|
`Stimeo UI: "${this.identifier}" cannot manage rows because its markup lacks ${missing}.`
|
|
511
675
|
);
|
|
512
676
|
}
|
|
513
|
-
/** Names a template that does not produce exactly one element, once per connection. */
|
|
514
677
|
#warnBadTemplate(produced) {
|
|
515
678
|
if (this.#warnedTemplate) return;
|
|
516
679
|
this.#warnedTemplate = true;
|
|
517
680
|
const reason = produced === 0 ? "produces no element" : "must produce exactly one root element";
|
|
518
681
|
console.warn(`Stimeo UI: "${this.identifier}" added no row because its template ${reason}.`);
|
|
519
682
|
}
|
|
520
|
-
/** A strictly-increasing unique index (collision-free even on rapid adds). */
|
|
521
683
|
#nextIndex() {
|
|
522
684
|
const index = Math.max(Date.now(), this.#lastIndex + 1);
|
|
523
685
|
this.#lastIndex = index;
|
|
524
686
|
return index;
|
|
525
687
|
}
|
|
526
688
|
get #atMax() {
|
|
527
|
-
return this
|
|
689
|
+
return this.#safeMax > 0 && this.#effectiveRows.length >= this.#safeMax;
|
|
528
690
|
}
|
|
529
|
-
/**
|
|
530
|
-
* Direct child rows of the list whose own destroy flag is not set. Callers
|
|
531
|
-
* reach this only behind a list-presence gate (`add`, `#refresh`, and the
|
|
532
|
-
* click path through `#rowContaining`).
|
|
533
|
-
*/
|
|
534
691
|
get #effectiveRows() {
|
|
535
692
|
return Array.from(this.listTarget.children).filter(
|
|
536
693
|
(row) => !this.#destroyed(row)
|
|
537
694
|
);
|
|
538
695
|
}
|
|
539
|
-
/** Whether `row` is flagged for destruction; the flag value is the truth source. */
|
|
540
696
|
#destroyed(row) {
|
|
541
697
|
const flag = this.#destroyFlagOf(row);
|
|
542
698
|
return flag !== null && DESTROYED_VALUES.has(flag.value);
|
|
543
699
|
}
|
|
544
|
-
/** The row's own destroy flag, skipping flags owned by a nested inner form. */
|
|
545
700
|
#destroyFlagOf(row) {
|
|
546
701
|
for (const flag of row.querySelectorAll(this.#destroyFlagSelector)) {
|
|
547
702
|
if (this.#ownerOf(flag) === this.element) return flag;
|
|
548
703
|
}
|
|
549
704
|
return null;
|
|
550
705
|
}
|
|
551
|
-
/** The nearest nested-form root that owns `el`. */
|
|
552
706
|
#ownerOf(el) {
|
|
553
707
|
return el.closest(this.#rootSelector);
|
|
554
708
|
}
|
|
555
|
-
/** The nearest ancestor of `el` that is a direct child of the list, else null. */
|
|
556
709
|
#rowContaining(el) {
|
|
557
710
|
if (!this.hasListTarget) return null;
|
|
558
711
|
let node = el;
|
|
@@ -561,8 +714,25 @@ var NestedFormController = class extends Controller {
|
|
|
561
714
|
}
|
|
562
715
|
return node;
|
|
563
716
|
}
|
|
717
|
+
get #safeMin() {
|
|
718
|
+
return this.#numbers.read(
|
|
719
|
+
this,
|
|
720
|
+
"min",
|
|
721
|
+
this.minValue,
|
|
722
|
+
_NestedFormController.values.min.default,
|
|
723
|
+
_NestedFormController.valueConstraints.min
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
get #safeMax() {
|
|
727
|
+
return this.#numbers.read(
|
|
728
|
+
this,
|
|
729
|
+
"max",
|
|
730
|
+
this.maxValue,
|
|
731
|
+
_NestedFormController.values.max.default,
|
|
732
|
+
_NestedFormController.valueConstraints.max
|
|
733
|
+
);
|
|
734
|
+
}
|
|
564
735
|
};
|
|
565
736
|
|
|
566
737
|
export { NestedFormController };
|
|
567
|
-
//# sourceMappingURL=nested_form_controller.js.map
|
|
568
738
|
//# sourceMappingURL=nested_form_controller.js.map
|