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/file_dropzone_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,91 +16,216 @@ function fillTemplate(template, values) {
|
|
|
19
16
|
});
|
|
20
17
|
}
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
+
);
|
|
50
|
+
#attribute;
|
|
51
|
+
#record;
|
|
52
|
+
#base;
|
|
53
|
+
#held = /* @__PURE__ */ new Set();
|
|
54
|
+
constructor(attribute, owner, options = {}) {
|
|
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));
|
|
60
|
+
}
|
|
61
|
+
write(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);
|
|
69
|
+
}
|
|
70
|
+
return(element) {
|
|
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);
|
|
41
157
|
}
|
|
42
158
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
159
|
+
#reflect(element, value) {
|
|
160
|
+
if (element.getAttribute(this.#attribute) === value) return;
|
|
161
|
+
if (value === null) element.removeAttribute(this.#attribute);
|
|
162
|
+
else element.setAttribute(this.#attribute, value);
|
|
48
163
|
}
|
|
49
164
|
};
|
|
50
165
|
|
|
51
|
-
// src/utils/detach_gate.ts
|
|
52
166
|
var DetachGate = class _DetachGate {
|
|
53
|
-
/** Set while a probe is queued, waiting for a reconnect to cancel it. */
|
|
54
167
|
#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
168
|
get pending() {
|
|
63
169
|
return this.#pending;
|
|
64
170
|
}
|
|
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
171
|
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
|
-
*/
|
|
172
|
+
return !host.element.isConnected || !_DetachGate.#listed(host);
|
|
173
|
+
}
|
|
82
174
|
disconnected(host, teardown) {
|
|
83
|
-
|
|
175
|
+
const carried = _DetachGate.#carried(host);
|
|
176
|
+
if (!carried && _DetachGate.isDetached(host)) {
|
|
84
177
|
this.#pending = false;
|
|
85
178
|
teardown();
|
|
86
179
|
return;
|
|
87
180
|
}
|
|
88
181
|
this.#pending = true;
|
|
89
|
-
|
|
182
|
+
const probe = () => {
|
|
90
183
|
if (!this.#pending) return;
|
|
91
184
|
this.#pending = false;
|
|
92
185
|
teardown();
|
|
93
|
-
}
|
|
186
|
+
};
|
|
187
|
+
if (carried) setTimeout(probe, 0);
|
|
188
|
+
else queueMicrotask(probe);
|
|
189
|
+
}
|
|
190
|
+
static #carried(host) {
|
|
191
|
+
const element = host.element;
|
|
192
|
+
return !element.isConnected && element.closest("[data-turbo-permanent]") !== null && _DetachGate.#listed(host);
|
|
193
|
+
}
|
|
194
|
+
static #listed(host) {
|
|
195
|
+
const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
|
|
196
|
+
return tokens.includes(host.identifier);
|
|
94
197
|
}
|
|
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
198
|
cancel() {
|
|
102
199
|
this.#pending = false;
|
|
103
200
|
}
|
|
104
201
|
};
|
|
105
202
|
|
|
106
|
-
|
|
203
|
+
function matchingPart(root, selector) {
|
|
204
|
+
return root.matches(selector) ? root : root.querySelector(selector);
|
|
205
|
+
}
|
|
206
|
+
function writeLabel(slot, text) {
|
|
207
|
+
if (slot.childElementCount === 0) {
|
|
208
|
+
slot.textContent = text;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
let first = null;
|
|
212
|
+
for (const node of Array.from(slot.childNodes)) {
|
|
213
|
+
if (node.nodeType !== Node.TEXT_NODE) continue;
|
|
214
|
+
if (first) node.remove();
|
|
215
|
+
else first = node;
|
|
216
|
+
}
|
|
217
|
+
if (first) first.nodeValue = text;
|
|
218
|
+
else slot.prepend(text);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function ownerIndex(candidates, node) {
|
|
222
|
+
if (!(node instanceof Node)) return -1;
|
|
223
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
224
|
+
}
|
|
225
|
+
function ownerOf(candidates, node) {
|
|
226
|
+
return candidates[ownerIndex(candidates, node)] ?? null;
|
|
227
|
+
}
|
|
228
|
+
|
|
107
229
|
function inheritsFieldsetDisabled(control) {
|
|
108
230
|
let fieldset = control.closest("fieldset[disabled]");
|
|
109
231
|
while (fieldset) {
|
|
@@ -114,10 +236,123 @@ function inheritsFieldsetDisabled(control) {
|
|
|
114
236
|
return false;
|
|
115
237
|
}
|
|
116
238
|
|
|
117
|
-
|
|
239
|
+
var NUMBER_BOUNDS = {
|
|
240
|
+
nonNegative: { finite: true, min: 0 }};
|
|
241
|
+
function matchesNumberBounds(value, bounds) {
|
|
242
|
+
if (!Number.isFinite(value)) {
|
|
243
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
244
|
+
if (direction === null) return false;
|
|
245
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
246
|
+
}
|
|
247
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
248
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
249
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
250
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
251
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function readNumber(raw, fallback, bounds) {
|
|
256
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
var NumberValueReader = class {
|
|
260
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
261
|
+
read(owner, name, raw, fallback, bounds) {
|
|
262
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
263
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
264
|
+
this.#lastRejected.delete(name);
|
|
265
|
+
return resolved;
|
|
266
|
+
}
|
|
267
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
268
|
+
const literal = owner.element.getAttribute(attribute);
|
|
269
|
+
if (literal === null) {
|
|
270
|
+
this.#lastRejected.delete(name);
|
|
271
|
+
return resolved;
|
|
272
|
+
}
|
|
273
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
274
|
+
this.#lastRejected.set(name, literal);
|
|
275
|
+
console.warn(
|
|
276
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
return resolved;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
function targetSelector(identifier, name) {
|
|
284
|
+
return `[data-${identifier}-target~="${name}"]`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function cloneTemplateRoot(template) {
|
|
288
|
+
const root = template.content.firstElementChild;
|
|
289
|
+
return root instanceof HTMLElement ? root.cloneNode(true) : null;
|
|
290
|
+
}
|
|
291
|
+
var TemplateRow = class {
|
|
292
|
+
#options;
|
|
293
|
+
#warned = false;
|
|
294
|
+
constructor(options) {
|
|
295
|
+
this.#options = options;
|
|
296
|
+
}
|
|
297
|
+
selector(name) {
|
|
298
|
+
return targetSelector(this.#options.identifier, name);
|
|
299
|
+
}
|
|
300
|
+
connect() {
|
|
301
|
+
this.#warned = false;
|
|
302
|
+
}
|
|
303
|
+
report(missing) {
|
|
304
|
+
return this.#say(`lacks ${missing}`);
|
|
305
|
+
}
|
|
306
|
+
#say(problem) {
|
|
307
|
+
if (this.#warned) return null;
|
|
308
|
+
this.#warned = true;
|
|
309
|
+
const { identifier, outcome, noun } = this.#options;
|
|
310
|
+
console.warn(`Stimeo UI: "${identifier}" ${outcome} because its ${noun} ${problem}.`);
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
instantiate(template, values) {
|
|
314
|
+
const { root: rootName, required, optional, button: buttonName } = this.#options;
|
|
315
|
+
const root = cloneTemplateRoot(template);
|
|
316
|
+
if (!root?.matches(this.selector(rootName))) {
|
|
317
|
+
return this.report(`${this.#article(rootName)} "${rootName}" root`);
|
|
318
|
+
}
|
|
319
|
+
const count = template.content.children.length;
|
|
320
|
+
if (count !== 1) {
|
|
321
|
+
return this.#say(`holds ${count} elements, and the row has to be its only one`);
|
|
322
|
+
}
|
|
323
|
+
const slots = {};
|
|
324
|
+
for (const name2 of required) {
|
|
325
|
+
const part = this.#resolve(root, name2);
|
|
326
|
+
if (!part) return this.report(`${this.#article(name2)} "${name2}" target`);
|
|
327
|
+
slots[name2] = part;
|
|
328
|
+
}
|
|
329
|
+
for (const name2 of optional ?? []) slots[name2] = this.#resolve(root, name2);
|
|
330
|
+
const named = this.#resolve(root, buttonName);
|
|
331
|
+
if (!(named instanceof HTMLButtonElement)) {
|
|
332
|
+
return this.report(`${this.#article(buttonName)} "${buttonName}" target <button>`);
|
|
333
|
+
}
|
|
334
|
+
const button = named;
|
|
335
|
+
const name = button.getAttribute("aria-label")?.trim() ?? "";
|
|
336
|
+
if (name === "") return this.report(`a non-empty aria-label on its "${buttonName}" target`);
|
|
337
|
+
button.setAttribute("aria-label", fillTemplate(name, values));
|
|
338
|
+
return { root, slots, button };
|
|
339
|
+
}
|
|
340
|
+
#resolve(root, name) {
|
|
341
|
+
return matchingPart(root, this.selector(name));
|
|
342
|
+
}
|
|
343
|
+
#article(name) {
|
|
344
|
+
return /^[aeiou]/i.test(name) ? "an" : "a";
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
118
348
|
var DRAGOVER_ATTRIBUTE = "data-dragover";
|
|
119
|
-
var INVALID_ATTRIBUTE = "
|
|
120
|
-
var
|
|
349
|
+
var INVALID_ATTRIBUTE = "invalid";
|
|
350
|
+
var GENERATED_ATTRIBUTE = "generated";
|
|
351
|
+
var FileDropzoneController = class _FileDropzoneController extends Controller {
|
|
352
|
+
#numbers = new NumberValueReader();
|
|
353
|
+
get #invalidAttribute() {
|
|
354
|
+
return `data-${this.identifier}-${INVALID_ATTRIBUTE}`;
|
|
355
|
+
}
|
|
121
356
|
static targets = [
|
|
122
357
|
"zone",
|
|
123
358
|
"trigger",
|
|
@@ -141,93 +376,118 @@ var FileDropzoneController = class extends Controller {
|
|
|
141
376
|
announceRejectedDuplicateText: { type: String, default: "" },
|
|
142
377
|
announceRejectedCountText: { type: String, default: "" }
|
|
143
378
|
};
|
|
379
|
+
static valueConstraints = {
|
|
380
|
+
maxSize: NUMBER_BOUNDS.nonNegative,
|
|
381
|
+
maxFiles: NUMBER_BOUNDS.nonNegative
|
|
382
|
+
};
|
|
144
383
|
static actions = ["onChange", "onDragLeave", "onDragOver", "onDrop", "openDialog"];
|
|
145
384
|
static events = ["change", "reject", "reconcile"];
|
|
146
|
-
/** Selected files paired with their rendered item and any preview objectURL. */
|
|
147
385
|
#entries = [];
|
|
148
|
-
/** Whether a drag is currently over the zone; the source for `data-dragover`. */
|
|
149
386
|
#dragging = false;
|
|
150
|
-
|
|
151
|
-
#
|
|
387
|
+
#invalid = false;
|
|
388
|
+
#dragoverHook = new AttributeLease(DRAGOVER_ATTRIBUTE, this.identifier);
|
|
389
|
+
#invalidHook = new AttributeLease(this.#invalidAttribute, this.identifier);
|
|
390
|
+
#rows = new TemplateRow({
|
|
391
|
+
identifier: this.identifier,
|
|
392
|
+
root: "item",
|
|
393
|
+
required: ["name"],
|
|
394
|
+
optional: ["thumb"],
|
|
395
|
+
button: "remove",
|
|
396
|
+
outcome: "added no file",
|
|
397
|
+
noun: "item template"
|
|
398
|
+
});
|
|
152
399
|
#gate = new DetachGate();
|
|
153
|
-
#
|
|
154
|
-
|
|
400
|
+
#listWatch = new MutationObserver((records) => {
|
|
401
|
+
const lost = records.some(
|
|
402
|
+
(record) => Array.from(record.removedNodes).some(
|
|
403
|
+
(node) => this.#entries.some((entry) => entry.item === node)
|
|
404
|
+
)
|
|
405
|
+
);
|
|
406
|
+
if (!lost) return;
|
|
407
|
+
this.#rehome();
|
|
408
|
+
this.#paintZone();
|
|
409
|
+
});
|
|
410
|
+
#connected = false;
|
|
411
|
+
get #generatedAttribute() {
|
|
412
|
+
return `data-${this.identifier}-${GENERATED_ATTRIBUTE}`;
|
|
413
|
+
}
|
|
155
414
|
connect() {
|
|
415
|
+
const moved = this.#gate.pending;
|
|
156
416
|
this.#gate.cancel();
|
|
157
|
-
this.#
|
|
158
|
-
this.#
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
* Releases the delegated listeners. The selection itself survives an in-page
|
|
162
|
-
* move and is released only once the gate proves a real detach — revoking a
|
|
163
|
-
* preview URL on a move would leave a live item pointing at a dead `blob:`.
|
|
164
|
-
*/
|
|
417
|
+
this.#connected = true;
|
|
418
|
+
this.#rows.connect();
|
|
419
|
+
if (!moved) this.#discardInherited();
|
|
420
|
+
}
|
|
165
421
|
disconnect() {
|
|
422
|
+
this.#connected = false;
|
|
166
423
|
for (const list of this.listTargets) list.removeEventListener("click", this.#onItemClick);
|
|
167
|
-
this.#
|
|
424
|
+
this.#listWatch.disconnect();
|
|
168
425
|
this.#gate.disconnected(this, () => this.#teardown());
|
|
169
426
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
427
|
+
#discardInherited() {
|
|
428
|
+
let discarded = false;
|
|
429
|
+
for (const list of this.listTargets) {
|
|
430
|
+
for (const item of Array.from(list.children)) {
|
|
431
|
+
if (!item.hasAttribute(this.#generatedAttribute)) continue;
|
|
432
|
+
item.remove();
|
|
433
|
+
discarded = true;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
for (const zone of this.zoneTargets) {
|
|
437
|
+
this.#dragoverHook.return(zone);
|
|
438
|
+
this.#invalidHook.return(zone);
|
|
439
|
+
}
|
|
440
|
+
if (!discarded) return;
|
|
441
|
+
this.#dragging = false;
|
|
442
|
+
this.#invalid = false;
|
|
443
|
+
this.#syncInput();
|
|
444
|
+
this.dispatch("reconcile", { detail: { files: this.#files } });
|
|
445
|
+
}
|
|
176
446
|
listTargetConnected(list) {
|
|
177
|
-
this.#
|
|
447
|
+
list.addEventListener("click", this.#onItemClick);
|
|
448
|
+
this.#listWatch.observe(list, { childList: true });
|
|
449
|
+
this.#rehome();
|
|
178
450
|
}
|
|
179
|
-
/** Releases only the list target that actually disconnected. */
|
|
180
451
|
listTargetDisconnected(list) {
|
|
181
452
|
list.removeEventListener("click", this.#onItemClick);
|
|
453
|
+
if (this.#connected) this.#rehome();
|
|
182
454
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
455
|
+
inputTargetConnected() {
|
|
456
|
+
if (this.#connected) this.#syncInput();
|
|
457
|
+
}
|
|
458
|
+
inputTargetDisconnected() {
|
|
459
|
+
if (this.#connected) this.#syncInput();
|
|
460
|
+
}
|
|
461
|
+
zoneTargetConnected() {
|
|
462
|
+
if (this.#connected) this.#paintZone();
|
|
463
|
+
}
|
|
464
|
+
zoneTargetDisconnected(zone) {
|
|
465
|
+
if (this.zoneTargets.includes(zone)) return;
|
|
466
|
+
this.#dragoverHook.return(zone);
|
|
467
|
+
this.#invalidHook.return(zone);
|
|
468
|
+
if (this.#connected) this.#paintZone();
|
|
189
469
|
}
|
|
190
|
-
/** Opens the native file dialog. Bound via `data-action` (trigger click). */
|
|
191
470
|
openDialog() {
|
|
192
471
|
if (this.#isDisabled) return;
|
|
193
472
|
this.inputTarget.click();
|
|
194
473
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
* the dialog just returned, so the accepted set is written back over it once
|
|
198
|
-
* the batch is validated.
|
|
199
|
-
*/
|
|
200
|
-
onChange() {
|
|
201
|
-
const files = this.inputTarget.files;
|
|
474
|
+
onChange(event) {
|
|
475
|
+
const files = (ownerOf(this.inputTargets, event?.target) ?? this.inputTarget).files;
|
|
202
476
|
if (files) this.#addFiles(files);
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Marks the zone as a drop target and announces the affordance once per drag.
|
|
207
|
-
* Bound via `data-action` (dragover). Leaving the default alone is what refuses
|
|
208
|
-
* the drop, so a disabled field and a drag an inner dropzone already claimed
|
|
209
|
-
* both fall through untouched.
|
|
210
|
-
*/
|
|
477
|
+
}
|
|
211
478
|
onDragOver(event) {
|
|
212
479
|
if (event.defaultPrevented || this.#isDisabled) return;
|
|
213
480
|
event.preventDefault();
|
|
214
481
|
if (this.#dragging) return;
|
|
215
482
|
this.#dragging = true;
|
|
216
|
-
|
|
483
|
+
this.#paintZone();
|
|
217
484
|
announce(fillTemplate(this.announceDragTextValue, { total: this.#entries.length }));
|
|
218
485
|
}
|
|
219
|
-
/**
|
|
220
|
-
* Clears the drag-over flag when the pointer leaves the zone. Bound via
|
|
221
|
-
* `data-action` (dragleave). `dragleave` bubbles from every descendant the
|
|
222
|
-
* pointer crosses, so the flag only drops when the element being entered is
|
|
223
|
-
* outside the zone (or there is none, the pointer having left the window).
|
|
224
|
-
*/
|
|
225
486
|
onDragLeave(event) {
|
|
226
487
|
const next = event.relatedTarget;
|
|
227
488
|
if (this.hasZoneTarget && next instanceof Node && this.zoneTarget.contains(next)) return;
|
|
228
489
|
this.#endDrag();
|
|
229
490
|
}
|
|
230
|
-
/** Accepts dropped files, clearing the drag-over state. Bound via `data-action` (drop). */
|
|
231
491
|
onDrop(event) {
|
|
232
492
|
if (event.defaultPrevented) return;
|
|
233
493
|
this.#endDrag();
|
|
@@ -235,26 +495,16 @@ var FileDropzoneController = class extends Controller {
|
|
|
235
495
|
event.preventDefault();
|
|
236
496
|
if (event.dataTransfer?.files) this.#addFiles(event.dataTransfer.files);
|
|
237
497
|
}
|
|
238
|
-
/**
|
|
239
|
-
* Removes the file whose remove button was clicked. Delegated on the list
|
|
240
|
-
* container rather than bound per item via `data-action`, so it works the instant
|
|
241
|
-
* an item is appended without waiting on Stimulus to wire a freshly created element.
|
|
242
|
-
* Only the item's declared `remove` target counts, so an authored second control
|
|
243
|
-
* inside an item does what it says instead of silently discarding the file. The
|
|
244
|
-
* button has to belong to a tracked item, which is what makes a list this
|
|
245
|
-
* controller no longer renders into inert — its items moved out with it.
|
|
246
|
-
*/
|
|
247
498
|
#onItemClick = (event) => {
|
|
248
499
|
const button = event.target.closest(
|
|
249
|
-
|
|
500
|
+
`button${this.#rows.selector("remove")}`
|
|
250
501
|
);
|
|
251
502
|
const index = this.#entries.findIndex((entry) => entry.item.contains(button));
|
|
252
503
|
if (index !== -1) this.#removeAt(index);
|
|
253
504
|
};
|
|
254
|
-
/** Validates each incoming file, renders the accepted ones, and reports the batch. */
|
|
255
505
|
#addFiles(files) {
|
|
256
506
|
this.#rehome();
|
|
257
|
-
|
|
507
|
+
this.#invalid = false;
|
|
258
508
|
const rejected = /* @__PURE__ */ new Map();
|
|
259
509
|
const turnedAway = [];
|
|
260
510
|
let addedName = "";
|
|
@@ -262,7 +512,7 @@ var FileDropzoneController = class extends Controller {
|
|
|
262
512
|
for (const file of Array.from(files)) {
|
|
263
513
|
const reason = this.#validate(file);
|
|
264
514
|
if (reason !== null) {
|
|
265
|
-
|
|
515
|
+
this.#invalid = true;
|
|
266
516
|
const batch = rejected.get(reason);
|
|
267
517
|
if (batch) batch.count += 1;
|
|
268
518
|
else rejected.set(reason, { name: file.name, count: 1 });
|
|
@@ -273,6 +523,7 @@ var FileDropzoneController = class extends Controller {
|
|
|
273
523
|
if (added === 0) addedName = file.name;
|
|
274
524
|
added += 1;
|
|
275
525
|
}
|
|
526
|
+
this.#paintZone();
|
|
276
527
|
this.#syncInput();
|
|
277
528
|
if (added > 0) {
|
|
278
529
|
this.#announce(this.announceAddedTextValue, addedName, added);
|
|
@@ -285,14 +536,9 @@ var FileDropzoneController = class extends Controller {
|
|
|
285
536
|
this.#announce(this.#rejectText(reason), batch.name, batch.count);
|
|
286
537
|
}
|
|
287
538
|
}
|
|
288
|
-
/**
|
|
289
|
-
* Returns the rejection reason for `file`, or `null` when it is acceptable.
|
|
290
|
-
* The file's own defects are decided first, so a full list still tells the user
|
|
291
|
-
* which files it would never have taken.
|
|
292
|
-
*/
|
|
293
539
|
#validate(file) {
|
|
294
540
|
if (!this.#matchesAccept(file)) return "type";
|
|
295
|
-
if (this
|
|
541
|
+
if (this.#safeMaxSize > 0 && file.size > this.#safeMaxSize) return "size";
|
|
296
542
|
if (!this.allowDuplicatesValue && this.#entries.some((entry) => this.#isSame(entry.file, file))) {
|
|
297
543
|
return "duplicate";
|
|
298
544
|
}
|
|
@@ -300,36 +546,24 @@ var FileDropzoneController = class extends Controller {
|
|
|
300
546
|
if (limit > 0 && this.#entries.length >= limit) return "count";
|
|
301
547
|
return null;
|
|
302
548
|
}
|
|
303
|
-
/**
|
|
304
|
-
* Whether two files are the same selection. `File` objects from separate picks
|
|
305
|
-
* are never the same reference, so identity is the triple the platform exposes.
|
|
306
|
-
*/
|
|
307
549
|
#isSame(a, b) {
|
|
308
550
|
return a.name === b.name && a.size === b.size && a.lastModified === b.lastModified;
|
|
309
551
|
}
|
|
310
|
-
/**
|
|
311
|
-
* Builds one preview item (name, optional thumbnail, remove button) and reports
|
|
312
|
-
* whether it was rendered.
|
|
313
|
-
*/
|
|
314
552
|
#appendFile(file) {
|
|
315
|
-
if (!this.hasListTarget)
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
'button[data-stimeo--file-dropzone-target~="remove"]'
|
|
323
|
-
);
|
|
324
|
-
const removeName = button?.getAttribute("aria-label")?.trim() ?? "";
|
|
325
|
-
if (!item) return this.#warnTemplate('an "item" root');
|
|
326
|
-
if (!name) return this.#warnTemplate('a "name" element');
|
|
327
|
-
if (!button) return this.#warnTemplate('a "remove" target <button>');
|
|
328
|
-
if (removeName === "") {
|
|
329
|
-
return this.#warnTemplate('a non-empty aria-label on its "remove" target');
|
|
553
|
+
if (!this.hasListTarget) {
|
|
554
|
+
this.#rows.report('a "list" target to render into');
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
if (!this.hasItemTemplateTarget) {
|
|
558
|
+
this.#rows.report('an "itemTemplate" target');
|
|
559
|
+
return false;
|
|
330
560
|
}
|
|
331
|
-
|
|
332
|
-
|
|
561
|
+
const row = this.#rows.instantiate(this.itemTemplateTarget, { name: file.name });
|
|
562
|
+
if (!row) return false;
|
|
563
|
+
const item = row.root;
|
|
564
|
+
item.setAttribute(this.#generatedAttribute, "");
|
|
565
|
+
writeLabel(row.slots.name, file.name);
|
|
566
|
+
const thumb = row.slots.thumb;
|
|
333
567
|
let url;
|
|
334
568
|
if (thumb && file.type.startsWith("image/")) {
|
|
335
569
|
url = URL.createObjectURL(file);
|
|
@@ -339,33 +573,10 @@ var FileDropzoneController = class extends Controller {
|
|
|
339
573
|
} else if (thumb) {
|
|
340
574
|
thumb.hidden = true;
|
|
341
575
|
}
|
|
342
|
-
this.listTarget.appendChild(
|
|
576
|
+
this.listTarget.appendChild(item);
|
|
343
577
|
this.#entries.push({ file, item, url });
|
|
344
578
|
return true;
|
|
345
579
|
}
|
|
346
|
-
/** Resolves one declared part inside a cloned item template. */
|
|
347
|
-
#slot(fragment, name) {
|
|
348
|
-
return fragment.querySelector(`[data-stimeo--file-dropzone-target~="${name}"]`);
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Reports an unusable item template to the author, once per connection.
|
|
352
|
-
*
|
|
353
|
-
* The addition itself stays a no-op — nothing about the selection, the native
|
|
354
|
-
* input, the announcements, or the events changes. Without this line the only
|
|
355
|
-
* symptom is a picker that accepts no file at all, and the causes the Inspector
|
|
356
|
-
* cannot see statically (a server-rendered template, a name that renders empty
|
|
357
|
-
* from a missing translation) would have no diagnostic anywhere.
|
|
358
|
-
*/
|
|
359
|
-
#warnTemplate(missing) {
|
|
360
|
-
if (!this.#warnedTemplate) {
|
|
361
|
-
this.#warnedTemplate = true;
|
|
362
|
-
console.warn(
|
|
363
|
-
`Stimeo UI: "${this.identifier}" added no file because its item template lacks ${missing}.`
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
return false;
|
|
367
|
-
}
|
|
368
|
-
/** Removes entry `index`, revokes its preview, and re-homes focus. */
|
|
369
580
|
#removeAt(index) {
|
|
370
581
|
const entry = this.#entries[index];
|
|
371
582
|
if (!entry) return;
|
|
@@ -382,7 +593,6 @@ var FileDropzoneController = class extends Controller {
|
|
|
382
593
|
(buttons[index] ?? buttons[buttons.length - 1])?.focus();
|
|
383
594
|
}
|
|
384
595
|
}
|
|
385
|
-
/** Whether `file` satisfies the input's `accept` list (empty accepts all). */
|
|
386
596
|
#matchesAccept(file) {
|
|
387
597
|
const accept = this.inputTarget.accept.trim();
|
|
388
598
|
if (accept === "") return true;
|
|
@@ -396,15 +606,9 @@ var FileDropzoneController = class extends Controller {
|
|
|
396
606
|
return type === token;
|
|
397
607
|
});
|
|
398
608
|
}
|
|
399
|
-
/**
|
|
400
|
-
* Sends one consumer-worded message to the page's shared announcer. `{name}` is
|
|
401
|
-
* the file the message is about, `{count}` how many files it covers, and
|
|
402
|
-
* `{total}` how many are selected once the batch has settled.
|
|
403
|
-
*/
|
|
404
609
|
#announce(template, name, count) {
|
|
405
610
|
announce(fillTemplate(template, { name, count, total: this.#entries.length }));
|
|
406
611
|
}
|
|
407
|
-
/** The consumer's wording for one rejection reason. */
|
|
408
612
|
#rejectText(reason) {
|
|
409
613
|
switch (reason) {
|
|
410
614
|
case "type":
|
|
@@ -417,11 +621,6 @@ var FileDropzoneController = class extends Controller {
|
|
|
417
621
|
return this.announceRejectedCountTextValue;
|
|
418
622
|
}
|
|
419
623
|
}
|
|
420
|
-
/**
|
|
421
|
-
* Mirrors the accepted set onto the native input, so a plain form submit carries
|
|
422
|
-
* the dropped files. Skipped where `DataTransfer` cannot be constructed: the
|
|
423
|
-
* widget keeps working and the consumer still receives every `File` on `change`.
|
|
424
|
-
*/
|
|
425
624
|
#syncInput() {
|
|
426
625
|
if (!this.hasInputTarget) return;
|
|
427
626
|
const transfer = this.#newTransfer();
|
|
@@ -429,7 +628,6 @@ var FileDropzoneController = class extends Controller {
|
|
|
429
628
|
for (const entry of this.#entries) transfer.items.add(entry.file);
|
|
430
629
|
this.inputTarget.files = transfer.files;
|
|
431
630
|
}
|
|
432
|
-
/** A usable empty `DataTransfer`, or `null` where the platform has none. */
|
|
433
631
|
#newTransfer() {
|
|
434
632
|
try {
|
|
435
633
|
return new DataTransfer();
|
|
@@ -437,35 +635,23 @@ var FileDropzoneController = class extends Controller {
|
|
|
437
635
|
return null;
|
|
438
636
|
}
|
|
439
637
|
}
|
|
440
|
-
/**
|
|
441
|
-
* Moves surviving preview items back under the current list. A morph that
|
|
442
|
-
* empties the list in place leaves the selection with no rendering, and the
|
|
443
|
-
* files it holds cannot be rebuilt from the DOM, so the items are re-homed
|
|
444
|
-
* rather than forgotten.
|
|
445
|
-
*/
|
|
446
638
|
#rehome() {
|
|
447
639
|
if (!this.hasListTarget) return;
|
|
448
640
|
for (const entry of this.#entries) {
|
|
449
641
|
if (!this.listTarget.contains(entry.item)) this.listTarget.appendChild(entry.item);
|
|
450
642
|
}
|
|
451
643
|
}
|
|
452
|
-
/** Drops the drag-over state, whether the drag ended in a drop or left the zone. */
|
|
453
644
|
#endDrag() {
|
|
454
645
|
this.#dragging = false;
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
* rewind reports itself as `reconcile` instead.
|
|
462
|
-
*/
|
|
463
|
-
#rewindForCache() {
|
|
464
|
-
const had = this.#entries.length > 0;
|
|
465
|
-
this.#reset();
|
|
466
|
-
if (had) this.dispatch("reconcile", { detail: { files: this.#files } });
|
|
646
|
+
this.#paintZone();
|
|
647
|
+
}
|
|
648
|
+
#paintZone() {
|
|
649
|
+
if (!this.hasZoneTarget) return;
|
|
650
|
+
this.#dragoverHook.write(this.zoneTarget, this.#dragging ? "" : null);
|
|
651
|
+
this.#invalidHook.write(this.zoneTarget, this.#invalid ? "" : null);
|
|
467
652
|
}
|
|
468
653
|
#reset() {
|
|
654
|
+
this.#invalid = false;
|
|
469
655
|
for (const entry of this.#entries) {
|
|
470
656
|
if (entry.url) URL.revokeObjectURL(entry.url);
|
|
471
657
|
entry.item.remove();
|
|
@@ -473,39 +659,50 @@ var FileDropzoneController = class extends Controller {
|
|
|
473
659
|
this.#entries.length = 0;
|
|
474
660
|
this.#syncInput();
|
|
475
661
|
this.#endDrag();
|
|
476
|
-
if (this.hasZoneTarget) this.zoneTarget.removeAttribute(INVALID_ATTRIBUTE);
|
|
477
662
|
}
|
|
478
|
-
/** Releases the selection once the disconnect is known to be a real detach. */
|
|
479
663
|
#teardown() {
|
|
480
|
-
this.#gate.cancel();
|
|
481
664
|
this.#reset();
|
|
482
665
|
}
|
|
483
|
-
/** Whether the field refuses input, natively or through an ancestor `fieldset`. */
|
|
484
666
|
get #isDisabled() {
|
|
485
667
|
return this.inputTarget.disabled || inheritsFieldsetDisabled(this.inputTarget);
|
|
486
668
|
}
|
|
487
|
-
/** Effective file cap: `maxFiles`, or 1 when the input is single-select. */
|
|
488
669
|
get #effectiveMaxFiles() {
|
|
489
|
-
if (this
|
|
670
|
+
if (this.#safeMaxFiles > 0) return this.#safeMaxFiles;
|
|
490
671
|
return this.inputTarget.multiple ? 0 : 1;
|
|
491
672
|
}
|
|
492
|
-
/** The declared remove button of each rendered item, in selection order. */
|
|
493
673
|
get #removeButtons() {
|
|
494
674
|
const buttons = [];
|
|
495
675
|
for (const entry of this.#entries) {
|
|
496
|
-
const button =
|
|
497
|
-
|
|
676
|
+
const button = matchingPart(
|
|
677
|
+
entry.item,
|
|
678
|
+
`button${this.#rows.selector("remove")}`
|
|
498
679
|
);
|
|
499
680
|
if (button) buttons.push(button);
|
|
500
681
|
}
|
|
501
682
|
return buttons;
|
|
502
683
|
}
|
|
503
|
-
/** The accepted files in selection order. */
|
|
504
684
|
get #files() {
|
|
505
685
|
return this.#entries.map((entry) => entry.file);
|
|
506
686
|
}
|
|
687
|
+
get #safeMaxSize() {
|
|
688
|
+
return this.#numbers.read(
|
|
689
|
+
this,
|
|
690
|
+
"maxSize",
|
|
691
|
+
this.maxSizeValue,
|
|
692
|
+
_FileDropzoneController.values.maxSize.default,
|
|
693
|
+
_FileDropzoneController.valueConstraints.maxSize
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
get #safeMaxFiles() {
|
|
697
|
+
return this.#numbers.read(
|
|
698
|
+
this,
|
|
699
|
+
"maxFiles",
|
|
700
|
+
this.maxFilesValue,
|
|
701
|
+
_FileDropzoneController.values.maxFiles.default,
|
|
702
|
+
_FileDropzoneController.valueConstraints.maxFiles
|
|
703
|
+
);
|
|
704
|
+
}
|
|
507
705
|
};
|
|
508
706
|
|
|
509
707
|
export { FileDropzoneController };
|
|
510
|
-
//# sourceMappingURL=file_dropzone_controller.js.map
|
|
511
708
|
//# sourceMappingURL=file_dropzone_controller.js.map
|