stimeo-ui 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -0
- package/README.md +27 -11
- package/dist/cable/index.d.ts +88 -31
- package/dist/cable/index.js +384 -449
- package/dist/cable/index.js.map +1 -1
- package/dist/coerce-JtZVNG5h.d.ts +20 -0
- package/dist/controllers/accordion_controller.d.ts +4 -2
- package/dist/controllers/accordion_controller.js +174 -55
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +12 -2
- package/dist/controllers/alert_dialog_controller.js +903 -235
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +37 -5
- package/dist/controllers/announcer_controller.js +238 -205
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.js +0 -10
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.d.ts +30 -6
- package/dist/controllers/auto_submit_controller.js +414 -145
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +185 -107
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.js +0 -130
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +21 -3
- package/dist/controllers/bulk_select_controller.js +282 -58
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +38 -16
- package/dist/controllers/calendar_controller.js +228 -260
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +43 -12
- package/dist/controllers/carousel_controller.js +536 -436
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +44 -7
- package/dist/controllers/character_counter_controller.js +493 -207
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +7 -2
- package/dist/controllers/checkbox_controller.js +38 -74
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +16 -5
- package/dist/controllers/clipboard_controller.js +261 -149
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +17 -5
- package/dist/controllers/collapsible_controller.js +304 -120
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +18 -6
- package/dist/controllers/color_picker_controller.js +178 -146
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +24 -6
- package/dist/controllers/combobox_controller.js +522 -156
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +51 -20
- package/dist/controllers/command_palette_controller.js +1156 -395
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.d.ts +15 -2
- package/dist/controllers/conditional_fields_controller.js +300 -171
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.d.ts +49 -12
- package/dist/controllers/confirm_controller.js +980 -262
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +80 -18
- package/dist/controllers/context_menu_controller.js +346 -153
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.d.ts +13 -1
- package/dist/controllers/count_up_controller.js +73 -21
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +47 -3
- package/dist/controllers/countdown_controller.js +281 -158
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +93 -10
- package/dist/controllers/currency_input_controller.js +575 -225
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +25 -12
- package/dist/controllers/data_grid_controller.js +248 -214
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +42 -14
- package/dist/controllers/date_range_picker_controller.js +338 -235
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +17 -6
- package/dist/controllers/dialog_controller.js +899 -237
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +25 -8
- package/dist/controllers/direct_upload_controller.js +97 -167
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +6 -1
- package/dist/controllers/dirty_form_controller.js +72 -69
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +19 -0
- package/dist/controllers/dismissible_controller.js +196 -24
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +39 -11
- package/dist/controllers/drawer_controller.js +974 -355
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +31 -1
- package/dist/controllers/dropdown_controller.js +339 -96
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.d.ts +12 -1
- package/dist/controllers/editable_controller.js +315 -88
- package/dist/controllers/editable_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.js +79 -58
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +63 -18
- package/dist/controllers/file_dropzone_controller.js +322 -211
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.js +77 -42
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +35 -12
- package/dist/controllers/flash_controller.js +238 -347
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +15 -6
- package/dist/controllers/focus_controller.js +679 -240
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +156 -132
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.d.ts +3 -3
- package/dist/controllers/form_validation_controller.js +31 -97
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +47 -14
- package/dist/controllers/frame_loading_controller.js +275 -217
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +27 -6
- package/dist/controllers/highlight_controller.js +107 -75
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +60 -4
- package/dist/controllers/hover_card_controller.js +327 -130
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +48 -6
- package/dist/controllers/idle_controller.js +271 -101
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +30 -13
- package/dist/controllers/input_mask_controller.js +483 -97
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.d.ts +32 -13
- package/dist/controllers/intersection_controller.js +150 -106
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +19 -2
- package/dist/controllers/lazy_frame_controller.js +24 -68
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +44 -9
- package/dist/controllers/listbox_controller.js +523 -240
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +9 -7
- package/dist/controllers/local_time_controller.js +102 -77
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +11 -3
- package/dist/controllers/masonry_controller.js +80 -96
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +55 -11
- package/dist/controllers/menu_controller.js +302 -188
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +51 -9
- package/dist/controllers/menubar_controller.js +232 -345
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +33 -20
- package/dist/controllers/meter_controller.js +417 -87
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +48 -18
- package/dist/controllers/multi_select_controller.js +785 -393
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +27 -6
- package/dist/controllers/navigation_menu_controller.js +190 -250
- package/dist/controllers/navigation_menu_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.d.ts +10 -0
- package/dist/controllers/nested_form_controller.js +396 -192
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +23 -0
- package/dist/controllers/network_status_controller.js +268 -62
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +50 -8
- package/dist/controllers/number_input_controller.js +610 -303
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.d.ts +8 -5
- package/dist/controllers/optimistic_controller.js +45 -87
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +55 -8
- package/dist/controllers/otp_controller.js +658 -255
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
- package/dist/controllers/overflow_indicator_controller.js +122 -113
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +58 -20
- package/dist/controllers/overflow_menu_controller.js +470 -295
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +37 -7
- package/dist/controllers/pagination_controller.js +332 -123
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +46 -8
- package/dist/controllers/password_reveal_controller.js +392 -121
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +15 -6
- package/dist/controllers/password_strength_controller.js +111 -158
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +11 -4
- package/dist/controllers/persist_controller.js +165 -111
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +35 -12
- package/dist/controllers/pointer_drag_controller.js +434 -188
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +41 -5
- package/dist/controllers/popover_controller.js +269 -113
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +44 -4
- package/dist/controllers/portal_controller.js +215 -76
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.d.ts +10 -4
- package/dist/controllers/preview_guard_controller.js +243 -116
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +20 -19
- package/dist/controllers/progress_controller.js +391 -78
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +21 -9
- package/dist/controllers/radio_group_controller.js +278 -211
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +47 -6
- package/dist/controllers/range_slider_controller.js +220 -113
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +20 -4
- package/dist/controllers/rating_controller.js +458 -211
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +2 -0
- package/dist/controllers/read_more_controller.js +138 -63
- package/dist/controllers/read_more_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.d.ts +1 -1
- package/dist/controllers/reading_progress_controller.js +166 -137
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.d.ts +20 -6
- package/dist/controllers/relative_time_controller.js +211 -112
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
- package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
- package/dist/controllers/reset_on_restore_controller.js.map +1 -0
- package/dist/controllers/resizable_controller.d.ts +31 -2
- package/dist/controllers/resizable_controller.js +299 -150
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +3 -3
- package/dist/controllers/roving_controller.js +49 -88
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +7 -3
- package/dist/controllers/scroll_area_controller.js +490 -246
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_restore_controller.d.ts +3 -3
- package/dist/controllers/scroll_restore_controller.js +14 -55
- package/dist/controllers/scroll_restore_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
- package/dist/controllers/scroll_visibility_controller.js +432 -149
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +6 -1
- package/dist/controllers/scrollspy_controller.js +193 -248
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +20 -5
- package/dist/controllers/separator_controller.js +285 -142
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +65 -8
- package/dist/controllers/sidebar_controller.js +1069 -404
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +23 -0
- package/dist/controllers/skeleton_controller.js +259 -101
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +29 -2
- package/dist/controllers/slider_controller.js +187 -86
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +22 -4
- package/dist/controllers/smart_sticky_header_controller.js +173 -66
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.d.ts +4 -4
- package/dist/controllers/sortable_controller.js +26 -125
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +39 -4
- package/dist/controllers/spinner_controller.js +288 -173
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +9 -4
- package/dist/controllers/step_indicator_controller.js +105 -46
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +14 -6
- package/dist/controllers/stepper_controller.js +120 -73
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +9 -1
- package/dist/controllers/stick_to_bottom_controller.js +275 -140
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +11 -0
- package/dist/controllers/sticky_observer_controller.js +220 -49
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +24 -8
- package/dist/controllers/submit_once_controller.js +376 -227
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +18 -4
- package/dist/controllers/switch_controller.js +201 -84
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +9 -6
- package/dist/controllers/tabs_controller.js +41 -30
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +16 -3
- package/dist/controllers/tags_input_controller.js +293 -173
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +11 -1
- package/dist/controllers/textarea_autosize_controller.js +108 -74
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +24 -11
- package/dist/controllers/theme_controller.js +140 -116
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +55 -5
- package/dist/controllers/time_picker_controller.js +331 -127
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +40 -16
- package/dist/controllers/toast_controller.js +244 -324
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +14 -6
- package/dist/controllers/toggle_group_controller.js +252 -191
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.d.ts +8 -6
- package/dist/controllers/toolbar_controller.js +219 -120
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +49 -4
- package/dist/controllers/tooltip_controller.js +158 -124
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +22 -4
- package/dist/controllers/transition_controller.js +123 -119
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +22 -12
- package/dist/controllers/tree_view_controller.js +283 -369
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.js +8573 -10410
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +38 -14
- package/dist/inspector/cli.js +108 -40
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +145 -64
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +20 -20
- package/dist/inspector/manifest.json +802 -101
- package/dist/positioning/index.d.ts +15 -7
- package/dist/positioning/index.js +154 -47
- package/dist/positioning/index.js.map +1 -1
- package/package.json +11 -10
- package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
- package/dist/controllers/reset_before_cache_controller.js.map +0 -1
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function fillTemplate(template, values) {
|
|
4
|
+
return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
|
|
5
|
+
const replacement = Object.hasOwn(values, name) ? values[name] : void 0;
|
|
6
|
+
return replacement === void 0 ? match : String(replacement);
|
|
7
|
+
});
|
|
8
|
+
}
|
|
4
9
|
|
|
5
|
-
// src/utils/announce.ts
|
|
6
10
|
function announce(message, options = {}) {
|
|
7
11
|
const text = message.trim();
|
|
8
12
|
if (text.length === 0) return;
|
|
@@ -12,98 +16,197 @@ function announce(message, options = {}) {
|
|
|
12
16
|
})
|
|
13
17
|
);
|
|
14
18
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
21
|
+
if (element.getAttribute(name) === value) return false;
|
|
22
|
+
if (value === null) element.removeAttribute(name);
|
|
23
|
+
else element.setAttribute(name, value);
|
|
24
|
+
return true;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
27
|
+
function sharedRegistry(key, create) {
|
|
28
|
+
const symbol = Symbol.for(key);
|
|
29
|
+
const scope = globalThis;
|
|
30
|
+
const existing = scope[symbol];
|
|
31
|
+
if (existing !== void 0) return existing;
|
|
32
|
+
const registry2 = create();
|
|
33
|
+
Object.defineProperty(globalThis, symbol, { value: registry2 });
|
|
34
|
+
return registry2;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
var registry = sharedRegistry(
|
|
38
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
39
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
40
|
+
);
|
|
41
|
+
function isAttributeValue(value) {
|
|
42
|
+
return value === null || typeof value === "string";
|
|
43
|
+
}
|
|
44
|
+
function readLeaseRecord(element, record) {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
47
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
48
|
+
const [original, written, ...rest] = parsed;
|
|
49
|
+
const linked = rest[0] !== false;
|
|
50
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
51
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
52
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
53
|
+
} catch {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function attributeStack(element, attribute) {
|
|
58
|
+
const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
59
|
+
registry.stacks.set(element, byAttribute);
|
|
60
|
+
const known = byAttribute.get(attribute);
|
|
61
|
+
if (known) return known;
|
|
62
|
+
const found = [];
|
|
63
|
+
for (const record of registry.names.get(attribute) ?? []) {
|
|
64
|
+
const read = readLeaseRecord(element, record);
|
|
65
|
+
if (read) found.push({ ...read, record });
|
|
66
|
+
}
|
|
67
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
68
|
+
const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
69
|
+
lease: null,
|
|
70
|
+
record,
|
|
71
|
+
written,
|
|
72
|
+
original,
|
|
73
|
+
linked: linked && index > 0
|
|
74
|
+
}));
|
|
75
|
+
byAttribute.set(attribute, stack);
|
|
76
|
+
return stack;
|
|
77
|
+
}
|
|
78
|
+
function findHold(stack, lease, record) {
|
|
79
|
+
return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
|
|
80
|
+
}
|
|
81
|
+
var AttributeLease = class {
|
|
82
|
+
#attribute;
|
|
83
|
+
#record;
|
|
84
|
+
#base;
|
|
85
|
+
#preserveAuthor;
|
|
86
|
+
#held = /* @__PURE__ */ new Set();
|
|
87
|
+
constructor(attribute, owner, options = {}) {
|
|
88
|
+
this.#attribute = attribute;
|
|
89
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
90
|
+
this.#base = options.base === true;
|
|
91
|
+
this.#preserveAuthor = options.preserveAuthor === true;
|
|
92
|
+
const names = registry.names;
|
|
93
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
94
|
+
}
|
|
95
|
+
write(element, value) {
|
|
96
|
+
const stack = attributeStack(element, this.#attribute);
|
|
97
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
98
|
+
this.#held.add(element);
|
|
99
|
+
const shows = !this.#covered(element, stack, hold);
|
|
100
|
+
hold.written = value;
|
|
101
|
+
this.#mark(element, stack);
|
|
102
|
+
if (shows) this.#reflect(element, value);
|
|
103
|
+
}
|
|
104
|
+
return(element) {
|
|
105
|
+
const held = this.#held.delete(element);
|
|
106
|
+
const known = registry.stacks.get(element)?.has(this.#attribute);
|
|
107
|
+
const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
|
|
108
|
+
const hold = this.#take(stack);
|
|
109
|
+
if (!hold) return false;
|
|
110
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
111
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
112
|
+
const index = stack.indexOf(hold);
|
|
113
|
+
stack.splice(index, 1);
|
|
114
|
+
const above = stack[index];
|
|
115
|
+
if (above) above.linked &&= hold.linked;
|
|
116
|
+
this.#mark(element, stack);
|
|
117
|
+
if (!owns) return true;
|
|
118
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
119
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
returnAll() {
|
|
123
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
124
|
+
}
|
|
125
|
+
#take(stack) {
|
|
126
|
+
const hold = findHold(stack, this, this.#record);
|
|
127
|
+
if (hold) hold.lease = this;
|
|
128
|
+
return hold;
|
|
129
|
+
}
|
|
130
|
+
#acquire(element, stack) {
|
|
131
|
+
const current = element.getAttribute(this.#attribute);
|
|
132
|
+
const top = stack.at(-1);
|
|
133
|
+
const bottom = stack[0];
|
|
134
|
+
const over = top?.written === current ? top : void 0;
|
|
135
|
+
const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
|
|
136
|
+
const hold = {
|
|
137
|
+
lease: this,
|
|
138
|
+
record: this.#record,
|
|
139
|
+
written: current,
|
|
140
|
+
original: from ? from.original : current,
|
|
141
|
+
linked: !this.#base && over !== void 0
|
|
142
|
+
};
|
|
143
|
+
if (this.#base && bottom) {
|
|
144
|
+
bottom.linked = true;
|
|
145
|
+
stack.unshift(hold);
|
|
146
|
+
} else stack.push(hold);
|
|
147
|
+
return hold;
|
|
148
|
+
}
|
|
149
|
+
#covered(element, stack, hold) {
|
|
150
|
+
const current = element.getAttribute(this.#attribute);
|
|
151
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
152
|
+
}
|
|
153
|
+
#mark(element, stack) {
|
|
154
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
155
|
+
stack.forEach((hold, index) => {
|
|
156
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
157
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
158
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
159
|
+
records.set(
|
|
160
|
+
hold.record,
|
|
161
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
for (const [name, recorded] of records) {
|
|
165
|
+
setAttributeIfChanged(element, name, recorded);
|
|
41
166
|
}
|
|
42
167
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
_BeforeCacheReset.#subscribers.delete(this);
|
|
46
|
-
if (_BeforeCacheReset.#subscribers.size > 0) return;
|
|
47
|
-
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
168
|
+
#reflect(element, value) {
|
|
169
|
+
setAttributeIfChanged(element, this.#attribute, value);
|
|
48
170
|
}
|
|
49
171
|
};
|
|
50
172
|
|
|
51
|
-
// src/utils/detach_gate.ts
|
|
52
173
|
var DetachGate = class _DetachGate {
|
|
53
|
-
/** Set while a probe is queued, waiting for a reconnect to cancel it. */
|
|
54
174
|
#pending = false;
|
|
55
|
-
/**
|
|
56
|
-
* True while a probe is queued — the last disconnect was ambiguous and no
|
|
57
|
-
* reconnect has cancelled it yet. Read it from `connect()` to tell the
|
|
58
|
-
* reconnect half of an in-page move from a first connect: a controller whose
|
|
59
|
-
* initialisation restarts a measurement (a min-duration floor, an elapsed
|
|
60
|
-
* counter) must skip it for the move, where nothing actually restarted.
|
|
61
|
-
*/
|
|
62
175
|
get pending() {
|
|
63
176
|
return this.#pending;
|
|
64
177
|
}
|
|
65
|
-
/**
|
|
66
|
-
* True when the disconnect is definitely a real detach — the element left
|
|
67
|
-
* the document, or `data-controller` no longer lists the identifier. False
|
|
68
|
-
* means ambiguous (in-page move or observed-root exit), NOT "alive".
|
|
69
|
-
*/
|
|
70
178
|
static isDetached(host) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return !tokens.includes(host.identifier);
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Call from `disconnect()`: runs `teardown` synchronously on a definite
|
|
77
|
-
* detach (fast path), otherwise defers it one microtask — a reconnect
|
|
78
|
-
* ({@link cancel} from `connect()`) keeps the state, no reconnect runs it.
|
|
79
|
-
* One microtask is the whole probe window: Stimulus reconnects a moved
|
|
80
|
-
* element within the same mutation batch, before the checkpoint drains.
|
|
81
|
-
*/
|
|
179
|
+
return !host.element.isConnected || !_DetachGate.#listed(host);
|
|
180
|
+
}
|
|
82
181
|
disconnected(host, teardown) {
|
|
83
|
-
|
|
182
|
+
const carried = _DetachGate.#carried(host);
|
|
183
|
+
if (!carried && _DetachGate.isDetached(host)) {
|
|
84
184
|
this.#pending = false;
|
|
85
185
|
teardown();
|
|
86
186
|
return;
|
|
87
187
|
}
|
|
88
188
|
this.#pending = true;
|
|
89
|
-
|
|
189
|
+
const probe = () => {
|
|
90
190
|
if (!this.#pending) return;
|
|
91
191
|
this.#pending = false;
|
|
92
192
|
teardown();
|
|
93
|
-
}
|
|
193
|
+
};
|
|
194
|
+
if (carried) setTimeout(probe, 0);
|
|
195
|
+
else queueMicrotask(probe);
|
|
196
|
+
}
|
|
197
|
+
static #carried(host) {
|
|
198
|
+
const element = host.element;
|
|
199
|
+
return !element.isConnected && element.closest("[data-turbo-permanent]") !== null && _DetachGate.#listed(host);
|
|
200
|
+
}
|
|
201
|
+
static #listed(host) {
|
|
202
|
+
const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
|
|
203
|
+
return tokens.includes(host.identifier);
|
|
94
204
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Disarms a pending probe. Call from `connect()` (the reconnect that proves
|
|
97
|
-
* an in-page move) and from the head of any teardown path not routed through
|
|
98
|
-
* {@link disconnected} (disabled-toggle, Escape), so an orphaned probe can
|
|
99
|
-
* never run the teardown a second time.
|
|
100
|
-
*/
|
|
101
205
|
cancel() {
|
|
102
206
|
this.#pending = false;
|
|
103
207
|
}
|
|
104
208
|
};
|
|
105
209
|
|
|
106
|
-
// src/utils/element_part.ts
|
|
107
210
|
function matchingPart(root, selector) {
|
|
108
211
|
return root.matches(selector) ? root : root.querySelector(selector);
|
|
109
212
|
}
|
|
@@ -122,7 +225,14 @@ function writeLabel(slot, text) {
|
|
|
122
225
|
else slot.prepend(text);
|
|
123
226
|
}
|
|
124
227
|
|
|
125
|
-
|
|
228
|
+
function ownerIndex(candidates, node) {
|
|
229
|
+
if (!(node instanceof Node)) return -1;
|
|
230
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
231
|
+
}
|
|
232
|
+
function ownerOf(candidates, node) {
|
|
233
|
+
return candidates[ownerIndex(candidates, node)] ?? null;
|
|
234
|
+
}
|
|
235
|
+
|
|
126
236
|
function inheritsFieldsetDisabled(control) {
|
|
127
237
|
let fieldset = control.closest("fieldset[disabled]");
|
|
128
238
|
while (fieldset) {
|
|
@@ -133,12 +243,54 @@ function inheritsFieldsetDisabled(control) {
|
|
|
133
243
|
return false;
|
|
134
244
|
}
|
|
135
245
|
|
|
136
|
-
|
|
246
|
+
var NUMBER_BOUNDS = {
|
|
247
|
+
nonNegative: { finite: true, min: 0 }};
|
|
248
|
+
function matchesNumberBounds(value, bounds) {
|
|
249
|
+
if (!Number.isFinite(value)) {
|
|
250
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
251
|
+
if (direction === null) return false;
|
|
252
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
253
|
+
}
|
|
254
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
255
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
256
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
257
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
258
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function readNumber(raw, fallback, bounds) {
|
|
263
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
var NumberValueReader = class {
|
|
267
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
268
|
+
read(owner, name, raw, fallback, bounds) {
|
|
269
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
270
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
271
|
+
this.#lastRejected.delete(name);
|
|
272
|
+
return resolved;
|
|
273
|
+
}
|
|
274
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
275
|
+
const literal = owner.element.getAttribute(attribute);
|
|
276
|
+
if (literal === null) {
|
|
277
|
+
this.#lastRejected.delete(name);
|
|
278
|
+
return resolved;
|
|
279
|
+
}
|
|
280
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
281
|
+
this.#lastRejected.set(name, literal);
|
|
282
|
+
console.warn(
|
|
283
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return resolved;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
137
290
|
function targetSelector(identifier, name) {
|
|
138
291
|
return `[data-${identifier}-target~="${name}"]`;
|
|
139
292
|
}
|
|
140
293
|
|
|
141
|
-
// src/utils/template_row.ts
|
|
142
294
|
function cloneTemplateRoot(template) {
|
|
143
295
|
const root = template.content.firstElementChild;
|
|
144
296
|
return root instanceof HTMLElement ? root.cloneNode(true) : null;
|
|
@@ -149,22 +301,15 @@ var TemplateRow = class {
|
|
|
149
301
|
constructor(options) {
|
|
150
302
|
this.#options = options;
|
|
151
303
|
}
|
|
152
|
-
/** The attribute selector for one declared part, in this controller's namespace. */
|
|
153
304
|
selector(name) {
|
|
154
305
|
return targetSelector(this.#options.identifier, name);
|
|
155
306
|
}
|
|
156
|
-
/** Re-arms the once-per-connection diagnostic. */
|
|
157
307
|
connect() {
|
|
158
308
|
this.#warned = false;
|
|
159
309
|
}
|
|
160
|
-
/**
|
|
161
|
-
* Names one missing part on the console, at most once per connection, and
|
|
162
|
-
* returns `null` so a caller can hand it straight back.
|
|
163
|
-
*/
|
|
164
310
|
report(missing) {
|
|
165
311
|
return this.#say(`lacks ${missing}`);
|
|
166
312
|
}
|
|
167
|
-
/** Writes one diagnostic per connection, saying what the template got wrong. */
|
|
168
313
|
#say(problem) {
|
|
169
314
|
if (this.#warned) return null;
|
|
170
315
|
this.#warned = true;
|
|
@@ -172,10 +317,6 @@ var TemplateRow = class {
|
|
|
172
317
|
console.warn(`Stimeo UI: "${identifier}" ${outcome} because its ${noun} ${problem}.`);
|
|
173
318
|
return null;
|
|
174
319
|
}
|
|
175
|
-
/**
|
|
176
|
-
* Clones the row and resolves its parts, or names what the template got wrong
|
|
177
|
-
* and returns `null`. `values` fills the button's authored `aria-label`.
|
|
178
|
-
*/
|
|
179
320
|
instantiate(template, values) {
|
|
180
321
|
const { root: rootName, required, optional, button: buttonName } = this.#options;
|
|
181
322
|
const root = cloneTemplateRoot(template);
|
|
@@ -203,7 +344,6 @@ var TemplateRow = class {
|
|
|
203
344
|
button.setAttribute("aria-label", fillTemplate(name, values));
|
|
204
345
|
return { root, slots, button };
|
|
205
346
|
}
|
|
206
|
-
/** The row may be its own part; otherwise the first descendant carrying the target. */
|
|
207
347
|
#resolve(root, name) {
|
|
208
348
|
return matchingPart(root, this.selector(name));
|
|
209
349
|
}
|
|
@@ -212,11 +352,11 @@ var TemplateRow = class {
|
|
|
212
352
|
}
|
|
213
353
|
};
|
|
214
354
|
|
|
215
|
-
// src/controllers/file_dropzone_controller.ts
|
|
216
355
|
var DRAGOVER_ATTRIBUTE = "data-dragover";
|
|
217
356
|
var INVALID_ATTRIBUTE = "invalid";
|
|
218
|
-
var
|
|
219
|
-
|
|
357
|
+
var GENERATED_ATTRIBUTE = "generated";
|
|
358
|
+
var FileDropzoneController = class _FileDropzoneController extends Controller {
|
|
359
|
+
#numbers = new NumberValueReader();
|
|
220
360
|
get #invalidAttribute() {
|
|
221
361
|
return `data-${this.identifier}-${INVALID_ATTRIBUTE}`;
|
|
222
362
|
}
|
|
@@ -243,13 +383,17 @@ var FileDropzoneController = class extends Controller {
|
|
|
243
383
|
announceRejectedDuplicateText: { type: String, default: "" },
|
|
244
384
|
announceRejectedCountText: { type: String, default: "" }
|
|
245
385
|
};
|
|
386
|
+
static valueConstraints = {
|
|
387
|
+
maxSize: NUMBER_BOUNDS.nonNegative,
|
|
388
|
+
maxFiles: NUMBER_BOUNDS.nonNegative
|
|
389
|
+
};
|
|
246
390
|
static actions = ["onChange", "onDragLeave", "onDragOver", "onDrop", "openDialog"];
|
|
247
391
|
static events = ["change", "reject", "reconcile"];
|
|
248
|
-
/** Selected files paired with their rendered item and any preview objectURL. */
|
|
249
392
|
#entries = [];
|
|
250
|
-
/** Whether a drag is currently over the zone; the source for `data-dragover`. */
|
|
251
393
|
#dragging = false;
|
|
252
|
-
|
|
394
|
+
#invalid = false;
|
|
395
|
+
#dragoverHook = new AttributeLease(DRAGOVER_ATTRIBUTE, this.identifier);
|
|
396
|
+
#invalidHook = new AttributeLease(this.#invalidAttribute, this.identifier);
|
|
253
397
|
#rows = new TemplateRow({
|
|
254
398
|
identifier: this.identifier,
|
|
255
399
|
root: "item",
|
|
@@ -260,84 +404,97 @@ var FileDropzoneController = class extends Controller {
|
|
|
260
404
|
noun: "item template"
|
|
261
405
|
});
|
|
262
406
|
#gate = new DetachGate();
|
|
263
|
-
#
|
|
264
|
-
|
|
407
|
+
#listWatch = new MutationObserver((records) => {
|
|
408
|
+
const lost = records.some(
|
|
409
|
+
(record) => Array.from(record.removedNodes).some(
|
|
410
|
+
(node) => this.#entries.some((entry) => entry.item === node)
|
|
411
|
+
)
|
|
412
|
+
);
|
|
413
|
+
if (!lost) return;
|
|
414
|
+
this.#rehome();
|
|
415
|
+
this.#paintZone();
|
|
416
|
+
});
|
|
417
|
+
#connected = false;
|
|
418
|
+
get #generatedAttribute() {
|
|
419
|
+
return `data-${this.identifier}-${GENERATED_ATTRIBUTE}`;
|
|
420
|
+
}
|
|
265
421
|
connect() {
|
|
422
|
+
const moved = this.#gate.pending;
|
|
266
423
|
this.#gate.cancel();
|
|
424
|
+
this.#connected = true;
|
|
267
425
|
this.#rows.connect();
|
|
268
|
-
this.#
|
|
426
|
+
if (!moved) this.#discardInherited();
|
|
269
427
|
}
|
|
270
|
-
/**
|
|
271
|
-
* Releases the delegated listeners. The selection itself survives an in-page
|
|
272
|
-
* move and is released only once the gate proves a real detach — revoking a
|
|
273
|
-
* preview URL on a move would leave a live item pointing at a dead `blob:`.
|
|
274
|
-
*/
|
|
275
428
|
disconnect() {
|
|
429
|
+
this.#connected = false;
|
|
276
430
|
for (const list of this.listTargets) list.removeEventListener("click", this.#onItemClick);
|
|
277
|
-
this.#
|
|
431
|
+
this.#listWatch.disconnect();
|
|
278
432
|
this.#gate.disconnected(this, () => this.#teardown());
|
|
279
433
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
434
|
+
#discardInherited() {
|
|
435
|
+
let discarded = false;
|
|
436
|
+
for (const list of this.listTargets) {
|
|
437
|
+
for (const item of Array.from(list.children)) {
|
|
438
|
+
if (!item.hasAttribute(this.#generatedAttribute)) continue;
|
|
439
|
+
item.remove();
|
|
440
|
+
discarded = true;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
for (const zone of this.zoneTargets) {
|
|
444
|
+
this.#dragoverHook.return(zone);
|
|
445
|
+
this.#invalidHook.return(zone);
|
|
446
|
+
}
|
|
447
|
+
if (!discarded) return;
|
|
448
|
+
this.#dragging = false;
|
|
449
|
+
this.#invalid = false;
|
|
450
|
+
this.#syncInput();
|
|
451
|
+
this.dispatch("reconcile", { detail: { files: this.#files } });
|
|
452
|
+
}
|
|
286
453
|
listTargetConnected(list) {
|
|
287
|
-
this.#
|
|
454
|
+
list.addEventListener("click", this.#onItemClick);
|
|
455
|
+
this.#listWatch.observe(list, { childList: true });
|
|
456
|
+
this.#rehome();
|
|
288
457
|
}
|
|
289
|
-
/** Releases only the list target that actually disconnected. */
|
|
290
458
|
listTargetDisconnected(list) {
|
|
291
459
|
list.removeEventListener("click", this.#onItemClick);
|
|
460
|
+
if (this.#connected) this.#rehome();
|
|
292
461
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
462
|
+
inputTargetConnected() {
|
|
463
|
+
if (this.#connected) this.#syncInput();
|
|
464
|
+
}
|
|
465
|
+
inputTargetDisconnected() {
|
|
466
|
+
if (this.#connected) this.#syncInput();
|
|
467
|
+
}
|
|
468
|
+
zoneTargetConnected() {
|
|
469
|
+
if (this.#connected) this.#paintZone();
|
|
470
|
+
}
|
|
471
|
+
zoneTargetDisconnected(zone) {
|
|
472
|
+
if (this.zoneTargets.includes(zone)) return;
|
|
473
|
+
this.#dragoverHook.return(zone);
|
|
474
|
+
this.#invalidHook.return(zone);
|
|
475
|
+
if (this.#connected) this.#paintZone();
|
|
299
476
|
}
|
|
300
|
-
/** Opens the native file dialog. Bound via `data-action` (trigger click). */
|
|
301
477
|
openDialog() {
|
|
302
478
|
if (this.#isDisabled) return;
|
|
303
479
|
this.inputTarget.click();
|
|
304
480
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
* the dialog just returned, so the accepted set is written back over it once
|
|
308
|
-
* the batch is validated.
|
|
309
|
-
*/
|
|
310
|
-
onChange() {
|
|
311
|
-
const files = this.inputTarget.files;
|
|
481
|
+
onChange(event) {
|
|
482
|
+
const files = (ownerOf(this.inputTargets, event?.target) ?? this.inputTarget).files;
|
|
312
483
|
if (files) this.#addFiles(files);
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Marks the zone as a drop target and announces the affordance once per drag.
|
|
317
|
-
* Bound via `data-action` (dragover). Leaving the default alone is what refuses
|
|
318
|
-
* the drop, so a disabled field and a drag an inner dropzone already claimed
|
|
319
|
-
* both fall through untouched.
|
|
320
|
-
*/
|
|
484
|
+
}
|
|
321
485
|
onDragOver(event) {
|
|
322
486
|
if (event.defaultPrevented || this.#isDisabled) return;
|
|
323
487
|
event.preventDefault();
|
|
324
488
|
if (this.#dragging) return;
|
|
325
489
|
this.#dragging = true;
|
|
326
|
-
|
|
490
|
+
this.#paintZone();
|
|
327
491
|
announce(fillTemplate(this.announceDragTextValue, { total: this.#entries.length }));
|
|
328
492
|
}
|
|
329
|
-
/**
|
|
330
|
-
* Clears the drag-over flag when the pointer leaves the zone. Bound via
|
|
331
|
-
* `data-action` (dragleave). `dragleave` bubbles from every descendant the
|
|
332
|
-
* pointer crosses, so the flag only drops when the element being entered is
|
|
333
|
-
* outside the zone (or there is none, the pointer having left the window).
|
|
334
|
-
*/
|
|
335
493
|
onDragLeave(event) {
|
|
336
494
|
const next = event.relatedTarget;
|
|
337
495
|
if (this.hasZoneTarget && next instanceof Node && this.zoneTarget.contains(next)) return;
|
|
338
496
|
this.#endDrag();
|
|
339
497
|
}
|
|
340
|
-
/** Accepts dropped files, clearing the drag-over state. Bound via `data-action` (drop). */
|
|
341
498
|
onDrop(event) {
|
|
342
499
|
if (event.defaultPrevented) return;
|
|
343
500
|
this.#endDrag();
|
|
@@ -345,15 +502,6 @@ var FileDropzoneController = class extends Controller {
|
|
|
345
502
|
event.preventDefault();
|
|
346
503
|
if (event.dataTransfer?.files) this.#addFiles(event.dataTransfer.files);
|
|
347
504
|
}
|
|
348
|
-
/**
|
|
349
|
-
* Removes the file whose remove button was clicked. Delegated on the list
|
|
350
|
-
* container rather than bound per item via `data-action`, so it works the instant
|
|
351
|
-
* an item is appended without waiting on Stimulus to wire a freshly created element.
|
|
352
|
-
* Only the item's declared `remove` target counts, so an authored second control
|
|
353
|
-
* inside an item does what it says instead of silently discarding the file. The
|
|
354
|
-
* button has to belong to a tracked item, which is what makes a list this
|
|
355
|
-
* controller no longer renders into inert — its items moved out with it.
|
|
356
|
-
*/
|
|
357
505
|
#onItemClick = (event) => {
|
|
358
506
|
const button = event.target.closest(
|
|
359
507
|
`button${this.#rows.selector("remove")}`
|
|
@@ -361,15 +509,9 @@ var FileDropzoneController = class extends Controller {
|
|
|
361
509
|
const index = this.#entries.findIndex((entry) => entry.item.contains(button));
|
|
362
510
|
if (index !== -1) this.#removeAt(index);
|
|
363
511
|
};
|
|
364
|
-
/**
|
|
365
|
-
* Validates each incoming file, renders the accepted ones, and reports the batch.
|
|
366
|
-
*
|
|
367
|
-
* @stimeoRuntimeOnly The limits decide which files of this one drop are taken, and the texts word
|
|
368
|
-
* the announcements it makes.
|
|
369
|
-
*/
|
|
370
512
|
#addFiles(files) {
|
|
371
513
|
this.#rehome();
|
|
372
|
-
|
|
514
|
+
this.#invalid = false;
|
|
373
515
|
const rejected = /* @__PURE__ */ new Map();
|
|
374
516
|
const turnedAway = [];
|
|
375
517
|
let addedName = "";
|
|
@@ -377,7 +519,7 @@ var FileDropzoneController = class extends Controller {
|
|
|
377
519
|
for (const file of Array.from(files)) {
|
|
378
520
|
const reason = this.#validate(file);
|
|
379
521
|
if (reason !== null) {
|
|
380
|
-
|
|
522
|
+
this.#invalid = true;
|
|
381
523
|
const batch = rejected.get(reason);
|
|
382
524
|
if (batch) batch.count += 1;
|
|
383
525
|
else rejected.set(reason, { name: file.name, count: 1 });
|
|
@@ -388,6 +530,7 @@ var FileDropzoneController = class extends Controller {
|
|
|
388
530
|
if (added === 0) addedName = file.name;
|
|
389
531
|
added += 1;
|
|
390
532
|
}
|
|
533
|
+
this.#paintZone();
|
|
391
534
|
this.#syncInput();
|
|
392
535
|
if (added > 0) {
|
|
393
536
|
this.#announce(this.announceAddedTextValue, addedName, added);
|
|
@@ -400,14 +543,9 @@ var FileDropzoneController = class extends Controller {
|
|
|
400
543
|
this.#announce(this.#rejectText(reason), batch.name, batch.count);
|
|
401
544
|
}
|
|
402
545
|
}
|
|
403
|
-
/**
|
|
404
|
-
* Returns the rejection reason for `file`, or `null` when it is acceptable.
|
|
405
|
-
* The file's own defects are decided first, so a full list still tells the user
|
|
406
|
-
* which files it would never have taken.
|
|
407
|
-
*/
|
|
408
546
|
#validate(file) {
|
|
409
547
|
if (!this.#matchesAccept(file)) return "type";
|
|
410
|
-
if (this
|
|
548
|
+
if (this.#safeMaxSize > 0 && file.size > this.#safeMaxSize) return "size";
|
|
411
549
|
if (!this.allowDuplicatesValue && this.#entries.some((entry) => this.#isSame(entry.file, file))) {
|
|
412
550
|
return "duplicate";
|
|
413
551
|
}
|
|
@@ -415,21 +553,9 @@ var FileDropzoneController = class extends Controller {
|
|
|
415
553
|
if (limit > 0 && this.#entries.length >= limit) return "count";
|
|
416
554
|
return null;
|
|
417
555
|
}
|
|
418
|
-
/**
|
|
419
|
-
* Whether two files are the same selection. `File` objects from separate picks
|
|
420
|
-
* are never the same reference, so identity is the triple the platform exposes.
|
|
421
|
-
*/
|
|
422
556
|
#isSame(a, b) {
|
|
423
557
|
return a.name === b.name && a.size === b.size && a.lastModified === b.lastModified;
|
|
424
558
|
}
|
|
425
|
-
/**
|
|
426
|
-
* Builds one preview item (name, optional thumbnail, remove button) and reports
|
|
427
|
-
* whether it was rendered.
|
|
428
|
-
*
|
|
429
|
-
* A refused row leaves the addition a no-op — nothing about the selection, the
|
|
430
|
-
* native input, the announcements, or the events changes — and the row reports
|
|
431
|
-
* why on the console once per connection.
|
|
432
|
-
*/
|
|
433
559
|
#appendFile(file) {
|
|
434
560
|
if (!this.hasListTarget) {
|
|
435
561
|
this.#rows.report('a "list" target to render into');
|
|
@@ -442,6 +568,7 @@ var FileDropzoneController = class extends Controller {
|
|
|
442
568
|
const row = this.#rows.instantiate(this.itemTemplateTarget, { name: file.name });
|
|
443
569
|
if (!row) return false;
|
|
444
570
|
const item = row.root;
|
|
571
|
+
item.setAttribute(this.#generatedAttribute, "");
|
|
445
572
|
writeLabel(row.slots.name, file.name);
|
|
446
573
|
const thumb = row.slots.thumb;
|
|
447
574
|
let url;
|
|
@@ -457,7 +584,6 @@ var FileDropzoneController = class extends Controller {
|
|
|
457
584
|
this.#entries.push({ file, item, url });
|
|
458
585
|
return true;
|
|
459
586
|
}
|
|
460
|
-
/** Removes entry `index`, revokes its preview, and re-homes focus. */
|
|
461
587
|
#removeAt(index) {
|
|
462
588
|
const entry = this.#entries[index];
|
|
463
589
|
if (!entry) return;
|
|
@@ -474,7 +600,6 @@ var FileDropzoneController = class extends Controller {
|
|
|
474
600
|
(buttons[index] ?? buttons[buttons.length - 1])?.focus();
|
|
475
601
|
}
|
|
476
602
|
}
|
|
477
|
-
/** Whether `file` satisfies the input's `accept` list (empty accepts all). */
|
|
478
603
|
#matchesAccept(file) {
|
|
479
604
|
const accept = this.inputTarget.accept.trim();
|
|
480
605
|
if (accept === "") return true;
|
|
@@ -488,15 +613,9 @@ var FileDropzoneController = class extends Controller {
|
|
|
488
613
|
return type === token;
|
|
489
614
|
});
|
|
490
615
|
}
|
|
491
|
-
/**
|
|
492
|
-
* Sends one consumer-worded message to the page's shared announcer. `{name}` is
|
|
493
|
-
* the file the message is about, `{count}` how many files it covers, and
|
|
494
|
-
* `{total}` how many are selected once the batch has settled.
|
|
495
|
-
*/
|
|
496
616
|
#announce(template, name, count) {
|
|
497
617
|
announce(fillTemplate(template, { name, count, total: this.#entries.length }));
|
|
498
618
|
}
|
|
499
|
-
/** The consumer's wording for one rejection reason. */
|
|
500
619
|
#rejectText(reason) {
|
|
501
620
|
switch (reason) {
|
|
502
621
|
case "type":
|
|
@@ -509,11 +628,6 @@ var FileDropzoneController = class extends Controller {
|
|
|
509
628
|
return this.announceRejectedCountTextValue;
|
|
510
629
|
}
|
|
511
630
|
}
|
|
512
|
-
/**
|
|
513
|
-
* Mirrors the accepted set onto the native input, so a plain form submit carries
|
|
514
|
-
* the dropped files. Skipped where `DataTransfer` cannot be constructed: the
|
|
515
|
-
* widget keeps working and the consumer still receives every `File` on `change`.
|
|
516
|
-
*/
|
|
517
631
|
#syncInput() {
|
|
518
632
|
if (!this.hasInputTarget) return;
|
|
519
633
|
const transfer = this.#newTransfer();
|
|
@@ -521,7 +635,6 @@ var FileDropzoneController = class extends Controller {
|
|
|
521
635
|
for (const entry of this.#entries) transfer.items.add(entry.file);
|
|
522
636
|
this.inputTarget.files = transfer.files;
|
|
523
637
|
}
|
|
524
|
-
/** A usable empty `DataTransfer`, or `null` where the platform has none. */
|
|
525
638
|
#newTransfer() {
|
|
526
639
|
try {
|
|
527
640
|
return new DataTransfer();
|
|
@@ -529,35 +642,23 @@ var FileDropzoneController = class extends Controller {
|
|
|
529
642
|
return null;
|
|
530
643
|
}
|
|
531
644
|
}
|
|
532
|
-
/**
|
|
533
|
-
* Moves surviving preview items back under the current list. A morph that
|
|
534
|
-
* empties the list in place leaves the selection with no rendering, and the
|
|
535
|
-
* files it holds cannot be rebuilt from the DOM, so the items are re-homed
|
|
536
|
-
* rather than forgotten.
|
|
537
|
-
*/
|
|
538
645
|
#rehome() {
|
|
539
646
|
if (!this.hasListTarget) return;
|
|
540
647
|
for (const entry of this.#entries) {
|
|
541
648
|
if (!this.listTarget.contains(entry.item)) this.listTarget.appendChild(entry.item);
|
|
542
649
|
}
|
|
543
650
|
}
|
|
544
|
-
/** Drops the drag-over state, whether the drag ended in a drop or left the zone. */
|
|
545
651
|
#endDrag() {
|
|
546
652
|
this.#dragging = false;
|
|
547
|
-
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
* rewind reports itself as `reconcile` instead.
|
|
554
|
-
*/
|
|
555
|
-
#rewindForCache() {
|
|
556
|
-
const had = this.#entries.length > 0;
|
|
557
|
-
this.#reset();
|
|
558
|
-
if (had) this.dispatch("reconcile", { detail: { files: this.#files } });
|
|
653
|
+
this.#paintZone();
|
|
654
|
+
}
|
|
655
|
+
#paintZone() {
|
|
656
|
+
if (!this.hasZoneTarget) return;
|
|
657
|
+
this.#dragoverHook.write(this.zoneTarget, this.#dragging ? "" : null);
|
|
658
|
+
this.#invalidHook.write(this.zoneTarget, this.#invalid ? "" : null);
|
|
559
659
|
}
|
|
560
660
|
#reset() {
|
|
661
|
+
this.#invalid = false;
|
|
561
662
|
for (const entry of this.#entries) {
|
|
562
663
|
if (entry.url) URL.revokeObjectURL(entry.url);
|
|
563
664
|
entry.item.remove();
|
|
@@ -565,23 +666,17 @@ var FileDropzoneController = class extends Controller {
|
|
|
565
666
|
this.#entries.length = 0;
|
|
566
667
|
this.#syncInput();
|
|
567
668
|
this.#endDrag();
|
|
568
|
-
if (this.hasZoneTarget) this.zoneTarget.removeAttribute(this.#invalidAttribute);
|
|
569
669
|
}
|
|
570
|
-
/** Releases the selection once the disconnect is known to be a real detach. */
|
|
571
670
|
#teardown() {
|
|
572
|
-
this.#gate.cancel();
|
|
573
671
|
this.#reset();
|
|
574
672
|
}
|
|
575
|
-
/** Whether the field refuses input, natively or through an ancestor `fieldset`. */
|
|
576
673
|
get #isDisabled() {
|
|
577
674
|
return this.inputTarget.disabled || inheritsFieldsetDisabled(this.inputTarget);
|
|
578
675
|
}
|
|
579
|
-
/** Effective file cap: `maxFiles`, or 1 when the input is single-select. */
|
|
580
676
|
get #effectiveMaxFiles() {
|
|
581
|
-
if (this
|
|
677
|
+
if (this.#safeMaxFiles > 0) return this.#safeMaxFiles;
|
|
582
678
|
return this.inputTarget.multiple ? 0 : 1;
|
|
583
679
|
}
|
|
584
|
-
/** The declared remove button of each rendered item, in selection order. */
|
|
585
680
|
get #removeButtons() {
|
|
586
681
|
const buttons = [];
|
|
587
682
|
for (const entry of this.#entries) {
|
|
@@ -593,12 +688,28 @@ var FileDropzoneController = class extends Controller {
|
|
|
593
688
|
}
|
|
594
689
|
return buttons;
|
|
595
690
|
}
|
|
596
|
-
/** The accepted files in selection order. */
|
|
597
691
|
get #files() {
|
|
598
692
|
return this.#entries.map((entry) => entry.file);
|
|
599
693
|
}
|
|
694
|
+
get #safeMaxSize() {
|
|
695
|
+
return this.#numbers.read(
|
|
696
|
+
this,
|
|
697
|
+
"maxSize",
|
|
698
|
+
this.maxSizeValue,
|
|
699
|
+
_FileDropzoneController.values.maxSize.default,
|
|
700
|
+
_FileDropzoneController.valueConstraints.maxSize
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
get #safeMaxFiles() {
|
|
704
|
+
return this.#numbers.read(
|
|
705
|
+
this,
|
|
706
|
+
"maxFiles",
|
|
707
|
+
this.maxFilesValue,
|
|
708
|
+
_FileDropzoneController.values.maxFiles.default,
|
|
709
|
+
_FileDropzoneController.valueConstraints.maxFiles
|
|
710
|
+
);
|
|
711
|
+
}
|
|
600
712
|
};
|
|
601
713
|
|
|
602
714
|
export { FileDropzoneController };
|
|
603
|
-
//# sourceMappingURL=file_dropzone_controller.js.map
|
|
604
715
|
//# sourceMappingURL=file_dropzone_controller.js.map
|