stimeo-ui 0.15.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 +209 -0
- package/README.md +128 -1
- package/dist/cable/index.d.ts +79 -23
- package/dist/cable/index.js +392 -417
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +34 -5
- package/dist/controllers/accordion_controller.js +228 -28
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +20 -9
- 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 +38 -7
- package/dist/controllers/announcer_controller.js +135 -191
- 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 +295 -110
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +172 -106
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.d.ts +10 -7
- package/dist/controllers/breadcrumb_controller.js +0 -129
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +22 -4
- package/dist/controllers/bulk_select_controller.js +264 -53
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +109 -28
- package/dist/controllers/calendar_controller.js +346 -180
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +99 -27
- package/dist/controllers/carousel_controller.js +587 -300
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +18 -2
- package/dist/controllers/character_counter_controller.js +271 -143
- 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 +77 -75
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +44 -7
- package/dist/controllers/clipboard_controller.js +187 -108
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +41 -5
- package/dist/controllers/collapsible_controller.js +374 -108
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +47 -14
- package/dist/controllers/color_picker_controller.js +183 -127
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +33 -6
- package/dist/controllers/combobox_controller.js +426 -109
- 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 +994 -350
- 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 +212 -125
- 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 -245
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +36 -3
- package/dist/controllers/context_menu_controller.js +275 -127
- 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 +75 -2
- package/dist/controllers/countdown_controller.js +235 -97
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +93 -15
- package/dist/controllers/currency_input_controller.js +241 -118
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +78 -22
- package/dist/controllers/data_grid_controller.js +229 -135
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +64 -11
- package/dist/controllers/date_range_picker_controller.js +359 -165
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +22 -3
- package/dist/controllers/dialog_controller.js +887 -225
- 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 +80 -142
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +2 -2
- package/dist/controllers/dirty_form_controller.js +26 -47
- 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 -20
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +52 -16
- package/dist/controllers/drawer_controller.js +963 -336
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +39 -3
- package/dist/controllers/dropdown_controller.js +342 -85
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.d.ts +12 -1
- package/dist/controllers/editable_controller.js +182 -58
- 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 +426 -229
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.d.ts +10 -4
- package/dist/controllers/filter_controller.js +78 -36
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +57 -14
- package/dist/controllers/flash_controller.js +328 -211
- 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 -239
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +153 -136
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +29 -96
- 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 +263 -198
- 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 +119 -70
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +65 -4
- package/dist/controllers/hover_card_controller.js +335 -118
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +58 -4
- package/dist/controllers/idle_controller.js +316 -65
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +5 -2
- package/dist/controllers/input_mask_controller.js +116 -61
- 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 -103
- 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 -56
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +84 -10
- package/dist/controllers/listbox_controller.js +413 -153
- 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 -95
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +40 -9
- package/dist/controllers/menu_controller.js +254 -167
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +15 -4
- package/dist/controllers/menubar_controller.js +206 -331
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +20 -0
- package/dist/controllers/meter_controller.js +159 -71
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +50 -7
- package/dist/controllers/multi_select_controller.js +797 -388
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +19 -1
- package/dist/controllers/navigation_menu_controller.js +156 -231
- 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 +373 -174
- 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 +241 -45
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +62 -10
- package/dist/controllers/number_input_controller.js +387 -227
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.js +28 -75
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +38 -7
- package/dist/controllers/otp_controller.js +344 -196
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +13 -12
- package/dist/controllers/overflow_indicator_controller.js +167 -93
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +66 -9
- package/dist/controllers/overflow_menu_controller.js +408 -277
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +52 -15
- package/dist/controllers/pagination_controller.js +342 -120
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +60 -9
- package/dist/controllers/password_reveal_controller.js +424 -99
- 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 +252 -96
- 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 +263 -164
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +46 -4
- package/dist/controllers/popover_controller.js +267 -94
- 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 -72
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.d.ts +10 -4
- package/dist/controllers/preview_guard_controller.js +243 -112
- 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 +132 -59
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +19 -8
- package/dist/controllers/radio_group_controller.js +138 -133
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +92 -4
- package/dist/controllers/range_slider_controller.js +245 -112
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +33 -6
- package/dist/controllers/rating_controller.js +429 -194
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +26 -2
- package/dist/controllers/read_more_controller.js +185 -47
- 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 +44 -4
- package/dist/controllers/resizable_controller.js +294 -118
- 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 -221
- 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 -146
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +9 -3
- package/dist/controllers/scrollspy_controller.js +181 -236
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +59 -17
- package/dist/controllers/separator_controller.js +288 -132
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +81 -7
- package/dist/controllers/sidebar_controller.js +1071 -375
- 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 -92
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +72 -8
- package/dist/controllers/slider_controller.js +211 -96
- 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 +163 -54
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.js +13 -110
- 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 -161
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +24 -17
- package/dist/controllers/step_indicator_controller.js +107 -53
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +43 -9
- package/dist/controllers/stepper_controller.js +171 -48
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +25 -0
- package/dist/controllers/stick_to_bottom_controller.js +310 -116
- 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 +22 -6
- package/dist/controllers/submit_once_controller.js +294 -192
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +40 -7
- package/dist/controllers/switch_controller.js +107 -43
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +13 -1
- package/dist/controllers/tabs_controller.js +50 -25
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +27 -3
- package/dist/controllers/tags_input_controller.js +290 -149
- 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 +37 -8
- package/dist/controllers/theme_controller.js +148 -80
- 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 +162 -81
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +85 -23
- package/dist/controllers/toast_controller.js +365 -193
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +60 -9
- package/dist/controllers/toggle_group_controller.js +194 -99
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +92 -90
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +52 -2
- package/dist/controllers/tooltip_controller.js +185 -125
- 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 -115
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +59 -18
- package/dist/controllers/tree_view_controller.js +248 -290
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +33 -3
- package/dist/index.js +8649 -8572
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +95 -9
- package/dist/inspector/cli.js +343 -77
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +390 -96
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +45 -45
- package/dist/inspector/manifest.json +1308 -155
- package/dist/positioning/index.d.ts +8 -0
- package/dist/positioning/index.js +148 -45
- package/dist/positioning/index.js.map +1 -1
- package/package.json +7 -7
- 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
164
|
};
|
|
62
165
|
|
|
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
|
-
};
|
|
91
|
-
|
|
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,43 +287,198 @@ 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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
464
|
+
function targetSelector(identifier, name) {
|
|
465
|
+
return `[data-${identifier}-target~="${name}"]`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
var REMOVE_PART = "remove";
|
|
469
|
+
var DESTROY_FLAG_PART = "destroyFlag";
|
|
235
470
|
var DESTROYED_VALUES = /* @__PURE__ */ new Set(["1", "true"]);
|
|
236
|
-
var NestedFormController = class extends Controller {
|
|
471
|
+
var NestedFormController = class _NestedFormController extends Controller {
|
|
472
|
+
#numbers = new NumberValueReader();
|
|
473
|
+
get #rootSelector() {
|
|
474
|
+
return `[data-controller~="${this.identifier}"]`;
|
|
475
|
+
}
|
|
476
|
+
get #removeSelector() {
|
|
477
|
+
return targetSelector(this.identifier, REMOVE_PART);
|
|
478
|
+
}
|
|
479
|
+
get #destroyFlagSelector() {
|
|
480
|
+
return targetSelector(this.identifier, DESTROY_FLAG_PART);
|
|
481
|
+
}
|
|
237
482
|
static targets = ["list", "template", "add", "remove", "destroyFlag"];
|
|
238
483
|
static values = {
|
|
239
484
|
min: { type: Number, default: 0 },
|
|
@@ -242,40 +487,33 @@ var NestedFormController = class extends Controller {
|
|
|
242
487
|
announce: { type: Boolean, default: true },
|
|
243
488
|
countMessage: { type: String, default: "" }
|
|
244
489
|
};
|
|
490
|
+
static valueConstraints = {
|
|
491
|
+
min: NUMBER_BOUNDS.nonNegative,
|
|
492
|
+
max: NUMBER_BOUNDS.nonNegative
|
|
493
|
+
};
|
|
245
494
|
static actions = ["add"];
|
|
246
495
|
static events = ["add", "remove", "reconcile"];
|
|
247
|
-
/** Monotonic source for unique row indices; never a row-state counter. */
|
|
248
496
|
#lastIndex = 0;
|
|
249
497
|
#warnedMissing = false;
|
|
250
498
|
#warnedTemplate = false;
|
|
251
|
-
/** The state last written to the hooks; reconciliation reports only real moves. */
|
|
252
499
|
#published = null;
|
|
253
|
-
/** Watches the list for row changes the controller did not perform itself. */
|
|
254
500
|
#observer = null;
|
|
255
|
-
#reconcile = new
|
|
256
|
-
|
|
257
|
-
#
|
|
258
|
-
#beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
|
|
259
|
-
/** Makes the root a programmatic focus destination when no other candidate survives. */
|
|
260
|
-
#tabindex = new TabindexLoan();
|
|
261
|
-
/**
|
|
262
|
-
* Delegated click handler for the per-row remove buttons (dynamic-safe). Only
|
|
263
|
-
* buttons whose nearest nested-form root is this instance are acted on, so a
|
|
264
|
-
* nested inner form's buttons never remove an outer row.
|
|
265
|
-
*/
|
|
501
|
+
#reconcile = new MorphRenderWatcher(() => this.#reconcileNow());
|
|
502
|
+
#addDisabled = new AttributeLease("disabled", this.identifier);
|
|
503
|
+
#tabindex = new TabindexLoan("-1", this.identifier);
|
|
266
504
|
#onClick = (event) => {
|
|
267
505
|
const target = event.target;
|
|
268
|
-
const button = target?.closest(
|
|
506
|
+
const button = target?.closest(this.#removeSelector);
|
|
269
507
|
if (!button || this.#ownerOf(button) !== this.element) return;
|
|
270
508
|
const row = this.#rowContaining(button);
|
|
271
509
|
if (row) this.#removeRow(row);
|
|
272
510
|
};
|
|
273
511
|
connect() {
|
|
512
|
+
this.#tabindex.reclaim(this.element);
|
|
274
513
|
this.#warnedMissing = false;
|
|
275
514
|
this.#warnedTemplate = false;
|
|
276
515
|
this.element.addEventListener("click", this.#onClick);
|
|
277
|
-
this.#reconcile.
|
|
278
|
-
this.#beforeCache.activate();
|
|
516
|
+
this.#reconcile.observe(this.element);
|
|
279
517
|
if (!this.hasListTarget || !this.hasTemplateTarget) this.#warnMissing();
|
|
280
518
|
this.#refresh();
|
|
281
519
|
}
|
|
@@ -283,28 +521,19 @@ var NestedFormController = class extends Controller {
|
|
|
283
521
|
this.element.removeEventListener("click", this.#onClick);
|
|
284
522
|
this.#observer?.disconnect();
|
|
285
523
|
this.#observer = null;
|
|
286
|
-
this.#reconcile.
|
|
287
|
-
this.#
|
|
288
|
-
this.#rewindForCache();
|
|
524
|
+
this.#reconcile.disconnect();
|
|
525
|
+
this.#addDisabled.returnAll();
|
|
289
526
|
this.#tabindex.returnAll();
|
|
290
527
|
this.#published = null;
|
|
291
528
|
}
|
|
292
|
-
/** Follows an arriving or swapped-in list: rebind to the primary, then reconcile. */
|
|
293
529
|
listTargetConnected() {
|
|
294
530
|
this.#rebindObserver();
|
|
295
531
|
this.#reconcile.schedule();
|
|
296
532
|
}
|
|
297
|
-
/** Follows a departing list the same way — the primary may have changed. */
|
|
298
533
|
listTargetDisconnected() {
|
|
299
534
|
this.#rebindObserver();
|
|
300
535
|
this.#reconcile.schedule();
|
|
301
536
|
}
|
|
302
|
-
/**
|
|
303
|
-
* Points the observer at the current primary list. Re-deriving on every list
|
|
304
|
-
* arrival and departure makes the binding independent of the order Stimulus
|
|
305
|
-
* reports an overlapping swap in — a staggered swap (successor appended before
|
|
306
|
-
* the old list leaves) ends observed and reconciled either way.
|
|
307
|
-
*/
|
|
308
537
|
#rebindObserver() {
|
|
309
538
|
this.#observer?.disconnect();
|
|
310
539
|
this.#observer = null;
|
|
@@ -317,7 +546,6 @@ var NestedFormController = class extends Controller {
|
|
|
317
546
|
attributeFilter: ["value"]
|
|
318
547
|
});
|
|
319
548
|
}
|
|
320
|
-
/** Returns the lease with a departing add button; a new one re-arms on refresh. */
|
|
321
549
|
addTargetDisconnected(target) {
|
|
322
550
|
this.#addDisabled.return(target);
|
|
323
551
|
this.#reconcile.schedule();
|
|
@@ -325,21 +553,12 @@ var NestedFormController = class extends Controller {
|
|
|
325
553
|
addTargetConnected() {
|
|
326
554
|
this.#reconcile.schedule();
|
|
327
555
|
}
|
|
328
|
-
/** Re-clamps when application code or a Turbo morph changes `min`. */
|
|
329
556
|
minValueChanged() {
|
|
330
557
|
this.#reconcile.schedule();
|
|
331
558
|
}
|
|
332
|
-
/** Re-clamps when application code or a Turbo morph changes `max`. */
|
|
333
559
|
maxValueChanged() {
|
|
334
560
|
this.#reconcile.schedule();
|
|
335
561
|
}
|
|
336
|
-
/**
|
|
337
|
-
* Clones the template row, replaces the index placeholder with a unique value,
|
|
338
|
-
* appends it, focuses its first tab stop, and announces the new count. No-ops at
|
|
339
|
-
* `max`, when the required targets are missing (named on the console once per
|
|
340
|
-
* connection), or when the template does not produce exactly one root element
|
|
341
|
-
* (also named once; the insertion is rolled back so nothing accumulates).
|
|
342
|
-
*/
|
|
343
562
|
add() {
|
|
344
563
|
if (!this.hasListTarget || !this.hasTemplateTarget) {
|
|
345
564
|
this.#warnMissing();
|
|
@@ -362,16 +581,14 @@ var NestedFormController = class extends Controller {
|
|
|
362
581
|
return;
|
|
363
582
|
}
|
|
364
583
|
const row = added[0];
|
|
584
|
+
for (const node of Array.from(list.childNodes).slice(beforeNodes)) {
|
|
585
|
+
if (node !== row) node.remove();
|
|
586
|
+
}
|
|
365
587
|
this.#refresh();
|
|
366
588
|
firstTabStop(row)?.focus();
|
|
367
589
|
this.dispatch("add", { detail: { index, element: row } });
|
|
368
590
|
this.#announce();
|
|
369
591
|
}
|
|
370
|
-
/**
|
|
371
|
-
* Removes a row: a persisted row (one carrying its own `destroyFlag`) has the
|
|
372
|
-
* flag set to `1` and is hidden so Rails destroys it on submit; an unsaved row
|
|
373
|
-
* is dropped from the DOM. Returns focus to a surviving row. No-ops at `min`.
|
|
374
|
-
*/
|
|
375
592
|
#removeRow(row) {
|
|
376
593
|
const rows = this.#effectiveRows;
|
|
377
594
|
if (this.#destroyed(row)) {
|
|
@@ -379,7 +596,7 @@ var NestedFormController = class extends Controller {
|
|
|
379
596
|
this.#focusAfterRemove(this.#positionAmong(rows, row));
|
|
380
597
|
return;
|
|
381
598
|
}
|
|
382
|
-
if (rows.length <= this
|
|
599
|
+
if (rows.length <= this.#safeMin) return;
|
|
383
600
|
const position = rows.indexOf(row);
|
|
384
601
|
const flag = this.#destroyFlagOf(row);
|
|
385
602
|
const persisted = flag !== null;
|
|
@@ -394,21 +611,11 @@ var NestedFormController = class extends Controller {
|
|
|
394
611
|
this.dispatch("remove", { detail: { element: row, persisted } });
|
|
395
612
|
this.#announce();
|
|
396
613
|
}
|
|
397
|
-
/**
|
|
398
|
-
* The index of the first effective row following `row` in document order.
|
|
399
|
-
* -1 (no following row) feeds the focus slices as a negative index, which
|
|
400
|
-
* yields the same fully-reversed nearest-first order as `rows.length` would.
|
|
401
|
-
*/
|
|
402
614
|
#positionAmong(rows, row) {
|
|
403
615
|
return rows.findIndex(
|
|
404
616
|
(candidate) => (row.compareDocumentPosition(candidate) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0
|
|
405
617
|
);
|
|
406
618
|
}
|
|
407
|
-
/**
|
|
408
|
-
* Moves focus to the first tab stop of the nearest surviving row — following
|
|
409
|
-
* rows first, then preceding ones — falling back to the add button and finally
|
|
410
|
-
* to the root via a temporary `tabindex`.
|
|
411
|
-
*/
|
|
412
619
|
#focusAfterRemove(position) {
|
|
413
620
|
const rows = this.#effectiveRows;
|
|
414
621
|
const ordered = [...rows.slice(position), ...rows.slice(0, position).reverse()];
|
|
@@ -426,27 +633,20 @@ var NestedFormController = class extends Controller {
|
|
|
426
633
|
this.#tabindex.lend(this.element);
|
|
427
634
|
this.element.focus();
|
|
428
635
|
}
|
|
429
|
-
/** Recomputes the count and min/max hooks from the DOM and records them as published. */
|
|
430
636
|
#refresh() {
|
|
431
637
|
if (!this.hasListTarget) return;
|
|
432
638
|
const count = this.#effectiveRows.length;
|
|
433
|
-
const atMin = count <= this
|
|
434
|
-
const atMax = this
|
|
639
|
+
const atMin = count <= this.#safeMin;
|
|
640
|
+
const atMax = this.#safeMax > 0 && count >= this.#safeMax;
|
|
435
641
|
this.element.setAttribute("data-nested-count", String(count));
|
|
436
642
|
this.#reflect("data-nested-at-max", atMax);
|
|
437
643
|
this.#reflect("data-nested-at-min", atMin);
|
|
438
644
|
if (this.hasAddTarget) {
|
|
439
|
-
if (this
|
|
645
|
+
if (this.#safeMax > 0) this.#addDisabled.write(this.addTarget, atMax ? "" : null);
|
|
440
646
|
else this.#addDisabled.return(this.addTarget);
|
|
441
647
|
}
|
|
442
648
|
this.#published = { count, atMin, atMax };
|
|
443
649
|
}
|
|
444
|
-
/**
|
|
445
|
-
* Applies row changes the controller did not perform itself (Turbo Streams,
|
|
446
|
-
* morphs, runtime Value changes): refreshes the hooks and reports a moved
|
|
447
|
-
* public state as `reconcile`. The controller's own operations refresh
|
|
448
|
-
* synchronously first, so their observer echo arrives here as a no-move.
|
|
449
|
-
*/
|
|
450
650
|
#reconcileNow() {
|
|
451
651
|
const previous = this.#published;
|
|
452
652
|
this.#refresh();
|
|
@@ -455,21 +655,14 @@ var NestedFormController = class extends Controller {
|
|
|
455
655
|
const moved = previous.count !== current.count || previous.atMin !== current.atMin || previous.atMax !== current.atMax;
|
|
456
656
|
if (moved) this.dispatch("reconcile", { detail: { ...current } });
|
|
457
657
|
}
|
|
458
|
-
/** Bridges the count change to the shared announcer when configured. */
|
|
459
658
|
#announce() {
|
|
460
659
|
if (!this.announceValue || this.countMessageValue === "") return;
|
|
461
660
|
announce(fillTemplate(this.countMessageValue, { count: this.#effectiveRows.length }));
|
|
462
661
|
}
|
|
463
|
-
/** Sets `attribute` to `"true"` when `on`, else removes it. */
|
|
464
662
|
#reflect(attribute, on) {
|
|
465
663
|
if (on) this.element.setAttribute(attribute, "true");
|
|
466
664
|
else this.element.removeAttribute(attribute);
|
|
467
665
|
}
|
|
468
|
-
/** Returns the disabled lease so an authored value never leaks into a snapshot. */
|
|
469
|
-
#rewindForCache() {
|
|
470
|
-
if (this.hasAddTarget) this.#addDisabled.return(this.addTarget);
|
|
471
|
-
}
|
|
472
|
-
/** Names the missing required target(s) once per connection. */
|
|
473
666
|
#warnMissing() {
|
|
474
667
|
if (this.#warnedMissing) return;
|
|
475
668
|
this.#warnedMissing = true;
|
|
@@ -481,49 +674,38 @@ var NestedFormController = class extends Controller {
|
|
|
481
674
|
`Stimeo UI: "${this.identifier}" cannot manage rows because its markup lacks ${missing}.`
|
|
482
675
|
);
|
|
483
676
|
}
|
|
484
|
-
/** Names a template that does not produce exactly one element, once per connection. */
|
|
485
677
|
#warnBadTemplate(produced) {
|
|
486
678
|
if (this.#warnedTemplate) return;
|
|
487
679
|
this.#warnedTemplate = true;
|
|
488
680
|
const reason = produced === 0 ? "produces no element" : "must produce exactly one root element";
|
|
489
681
|
console.warn(`Stimeo UI: "${this.identifier}" added no row because its template ${reason}.`);
|
|
490
682
|
}
|
|
491
|
-
/** A strictly-increasing unique index (collision-free even on rapid adds). */
|
|
492
683
|
#nextIndex() {
|
|
493
684
|
const index = Math.max(Date.now(), this.#lastIndex + 1);
|
|
494
685
|
this.#lastIndex = index;
|
|
495
686
|
return index;
|
|
496
687
|
}
|
|
497
688
|
get #atMax() {
|
|
498
|
-
return this
|
|
689
|
+
return this.#safeMax > 0 && this.#effectiveRows.length >= this.#safeMax;
|
|
499
690
|
}
|
|
500
|
-
/**
|
|
501
|
-
* Direct child rows of the list whose own destroy flag is not set. Callers
|
|
502
|
-
* reach this only behind a list-presence gate (`add`, `#refresh`, and the
|
|
503
|
-
* click path through `#rowContaining`).
|
|
504
|
-
*/
|
|
505
691
|
get #effectiveRows() {
|
|
506
692
|
return Array.from(this.listTarget.children).filter(
|
|
507
693
|
(row) => !this.#destroyed(row)
|
|
508
694
|
);
|
|
509
695
|
}
|
|
510
|
-
/** Whether `row` is flagged for destruction; the flag value is the truth source. */
|
|
511
696
|
#destroyed(row) {
|
|
512
697
|
const flag = this.#destroyFlagOf(row);
|
|
513
698
|
return flag !== null && DESTROYED_VALUES.has(flag.value);
|
|
514
699
|
}
|
|
515
|
-
/** The row's own destroy flag, skipping flags owned by a nested inner form. */
|
|
516
700
|
#destroyFlagOf(row) {
|
|
517
|
-
for (const flag of row.querySelectorAll(
|
|
701
|
+
for (const flag of row.querySelectorAll(this.#destroyFlagSelector)) {
|
|
518
702
|
if (this.#ownerOf(flag) === this.element) return flag;
|
|
519
703
|
}
|
|
520
704
|
return null;
|
|
521
705
|
}
|
|
522
|
-
/** The nearest nested-form root that owns `el`. */
|
|
523
706
|
#ownerOf(el) {
|
|
524
|
-
return el.closest(
|
|
707
|
+
return el.closest(this.#rootSelector);
|
|
525
708
|
}
|
|
526
|
-
/** The nearest ancestor of `el` that is a direct child of the list, else null. */
|
|
527
709
|
#rowContaining(el) {
|
|
528
710
|
if (!this.hasListTarget) return null;
|
|
529
711
|
let node = el;
|
|
@@ -532,8 +714,25 @@ var NestedFormController = class extends Controller {
|
|
|
532
714
|
}
|
|
533
715
|
return node;
|
|
534
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
|
+
}
|
|
535
735
|
};
|
|
536
736
|
|
|
537
737
|
export { NestedFormController };
|
|
538
|
-
//# sourceMappingURL=nested_form_controller.js.map
|
|
539
738
|
//# sourceMappingURL=nested_form_controller.js.map
|