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,19 +1,47 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
var ListenerSet = class {
|
|
4
|
+
#abort = new AbortController();
|
|
5
|
+
add(target, type, handler, options) {
|
|
6
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
7
|
+
}
|
|
8
|
+
dispose() {
|
|
9
|
+
this.#abort.abort();
|
|
10
|
+
this.#abort = new AbortController();
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
var FontLoadWatcher = class {
|
|
15
|
+
#listeners = new ListenerSet();
|
|
16
|
+
#onSettled;
|
|
17
|
+
#fonts = null;
|
|
18
|
+
constructor(run) {
|
|
19
|
+
this.#onSettled = () => run();
|
|
20
|
+
}
|
|
21
|
+
observe(element) {
|
|
22
|
+
const fonts = element.ownerDocument.fonts ?? null;
|
|
23
|
+
if (fonts === this.#fonts) return;
|
|
24
|
+
this.disconnect();
|
|
25
|
+
if (!fonts) return;
|
|
26
|
+
this.#fonts = fonts;
|
|
27
|
+
this.#listeners.add(fonts, "loadingdone", this.#onSettled);
|
|
28
|
+
this.#listeners.add(fonts, "loadingerror", this.#onSettled);
|
|
29
|
+
}
|
|
30
|
+
disconnect() {
|
|
31
|
+
this.#listeners.dispose();
|
|
32
|
+
this.#fonts = null;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
4
35
|
|
|
5
|
-
// src/utils/layout_observer.ts
|
|
6
36
|
var LayoutObserver = class {
|
|
7
37
|
#callback;
|
|
8
38
|
#resizeObserverFactory;
|
|
9
39
|
#resizeObserver = null;
|
|
10
40
|
#observingViewport = false;
|
|
11
41
|
#loadContainer = null;
|
|
12
|
-
/** Stable bound handler so add/removeEventListener target the same reference. */
|
|
13
42
|
#handleViewportResize = () => {
|
|
14
43
|
this.#callback();
|
|
15
44
|
};
|
|
16
|
-
/** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */
|
|
17
45
|
#handleDescendantLoad = () => {
|
|
18
46
|
this.#callback();
|
|
19
47
|
};
|
|
@@ -21,11 +49,6 @@ var LayoutObserver = class {
|
|
|
21
49
|
this.#callback = callback;
|
|
22
50
|
this.#resizeObserverFactory = options.resizeObserverFactory ?? (typeof ResizeObserver === "undefined" ? null : (cb) => new ResizeObserver(cb));
|
|
23
51
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Starts observing an element's size. Repeated calls observe additional
|
|
26
|
-
* elements through the same shared observer. No-ops when no
|
|
27
|
-
* `ResizeObserver` implementation is available.
|
|
28
|
-
*/
|
|
29
52
|
observe(element) {
|
|
30
53
|
if (!this.#resizeObserverFactory) return;
|
|
31
54
|
if (!this.#resizeObserver) {
|
|
@@ -35,47 +58,28 @@ var LayoutObserver = class {
|
|
|
35
58
|
}
|
|
36
59
|
this.#resizeObserver.observe(element);
|
|
37
60
|
}
|
|
38
|
-
/** Stops observing a single element while leaving any others in place. */
|
|
39
61
|
unobserve(element) {
|
|
40
62
|
this.#resizeObserver?.unobserve(element);
|
|
41
63
|
}
|
|
42
|
-
/** Starts observing viewport resizes. Idempotent: the listener is added once. */
|
|
43
64
|
observeViewport() {
|
|
44
65
|
if (this.#observingViewport) return;
|
|
45
66
|
this.#observingViewport = true;
|
|
46
67
|
window.addEventListener("resize", this.#handleViewportResize);
|
|
47
68
|
}
|
|
48
|
-
/** Stops observing viewport resizes without affecting element observation. */
|
|
49
69
|
unobserveViewport() {
|
|
50
70
|
if (!this.#observingViewport) return;
|
|
51
71
|
this.#observingViewport = false;
|
|
52
72
|
window.removeEventListener("resize", this.#handleViewportResize);
|
|
53
73
|
}
|
|
54
|
-
/**
|
|
55
|
-
* Starts reporting a `load` from anywhere inside `container` — an image or a
|
|
56
|
-
* frame settling changes the box it sits in, and it measures as zero high until
|
|
57
|
-
* then. `load` does not bubble, so the subscription is a capture-phase listener
|
|
58
|
-
* on the container itself and nothing the caller spells.
|
|
59
|
-
*
|
|
60
|
-
* **One container at a time.** A further call moves the observation, so a widget
|
|
61
|
-
* whose content element is swapped at runtime releases the element it let go by
|
|
62
|
-
* naming the new one — there is no second place for the release to drift from.
|
|
63
|
-
*/
|
|
64
74
|
observeDescendantLoads(container) {
|
|
65
75
|
this.unobserveDescendantLoads();
|
|
66
76
|
this.#loadContainer = container;
|
|
67
77
|
container.addEventListener("load", this.#handleDescendantLoad, true);
|
|
68
78
|
}
|
|
69
|
-
/** Stops reporting descendant loads without affecting element or viewport observation. */
|
|
70
79
|
unobserveDescendantLoads() {
|
|
71
80
|
this.#loadContainer?.removeEventListener("load", this.#handleDescendantLoad, true);
|
|
72
81
|
this.#loadContainer = null;
|
|
73
82
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Releases every observation: disconnects the {@link ResizeObserver} and removes
|
|
76
|
-
* the viewport and descendant-load listeners. Safe to call multiple times. Call
|
|
77
|
-
* this from a controller's `disconnect()`.
|
|
78
|
-
*/
|
|
79
83
|
disconnect() {
|
|
80
84
|
this.#resizeObserver?.disconnect();
|
|
81
85
|
this.#resizeObserver = null;
|
|
@@ -84,29 +88,70 @@ var LayoutObserver = class {
|
|
|
84
88
|
}
|
|
85
89
|
};
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
var NUMBER_BOUNDS = {
|
|
92
|
+
nonNegative: { finite: true, min: 0 }};
|
|
93
|
+
function matchesNumberBounds(value, bounds) {
|
|
94
|
+
if (!Number.isFinite(value)) {
|
|
95
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
96
|
+
if (direction === null) return false;
|
|
97
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
98
|
+
}
|
|
99
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
100
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
101
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
102
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
103
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function readNumber(raw, fallback, bounds) {
|
|
108
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
var NumberValueReader = class {
|
|
112
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
113
|
+
read(owner, name, raw, fallback, bounds) {
|
|
114
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
115
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
116
|
+
this.#lastRejected.delete(name);
|
|
117
|
+
return resolved;
|
|
118
|
+
}
|
|
119
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
120
|
+
const literal = owner.element.getAttribute(attribute);
|
|
121
|
+
if (literal === null) {
|
|
122
|
+
this.#lastRejected.delete(name);
|
|
123
|
+
return resolved;
|
|
124
|
+
}
|
|
125
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
126
|
+
this.#lastRejected.set(name, literal);
|
|
127
|
+
console.warn(
|
|
128
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return resolved;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
88
135
|
function px(value) {
|
|
89
136
|
const n = Number.parseFloat(value);
|
|
90
137
|
return Number.isNaN(n) ? 0 : n;
|
|
91
138
|
}
|
|
92
|
-
var TextareaAutosizeController = class extends Controller {
|
|
139
|
+
var TextareaAutosizeController = class _TextareaAutosizeController extends Controller {
|
|
140
|
+
#numbers = new NumberValueReader();
|
|
93
141
|
static values = {
|
|
94
142
|
minRows: { type: Number, default: 1 },
|
|
95
143
|
maxRows: { type: Number, default: 0 }
|
|
96
144
|
};
|
|
145
|
+
static valueConstraints = {
|
|
146
|
+
minRows: { finite: true, min: 1 },
|
|
147
|
+
maxRows: NUMBER_BOUNDS.nonNegative
|
|
148
|
+
};
|
|
97
149
|
static actions = ["resize"];
|
|
98
150
|
static events = ["resize"];
|
|
99
151
|
#lastHeight = -1;
|
|
100
152
|
#lastWidth = -1;
|
|
101
153
|
#started = false;
|
|
102
154
|
#pending = false;
|
|
103
|
-
#fonts = null;
|
|
104
|
-
/**
|
|
105
|
-
* Content triggers arriving while the box is collapsed (`display: none`
|
|
106
|
-
* ancestors) are deferred: `scrollHeight` is 0 there, so measuring would
|
|
107
|
-
* clamp the kept height to the `minRows` floor and dispatch a bogus
|
|
108
|
-
* `resize`. The next report from a rendered box flushes the deferral.
|
|
109
|
-
*/
|
|
110
155
|
#remeasure = () => {
|
|
111
156
|
if (this.element.clientWidth === 0) {
|
|
112
157
|
this.#pending = true;
|
|
@@ -114,16 +159,7 @@ var TextareaAutosizeController = class extends Controller {
|
|
|
114
159
|
}
|
|
115
160
|
this.resize();
|
|
116
161
|
};
|
|
117
|
-
|
|
118
|
-
* Re-measures when the content width changed (re-wrapping, including a
|
|
119
|
-
* horizontal padding change under `border-box`), the applied inline height
|
|
120
|
-
* was stripped (a Turbo morph syncing attributes from server HTML that has no
|
|
121
|
-
* `style`), or a content trigger was deferred while the box was collapsed.
|
|
122
|
-
* This instance's own height writes match none of these signals, so they
|
|
123
|
-
* cannot re-enter; an authored inline height (e.g. a user dragging the native
|
|
124
|
-
* resize handle) is left alone until the next content trigger. Reports from a
|
|
125
|
-
* still-collapsed box are skipped (nothing is measurable there).
|
|
126
|
-
*/
|
|
162
|
+
#fonts = new FontLoadWatcher(this.#remeasure);
|
|
127
163
|
#layout = new LayoutObserver(() => {
|
|
128
164
|
if (this.element.clientWidth === 0) return;
|
|
129
165
|
const width = this.#contentWidth();
|
|
@@ -132,32 +168,29 @@ var TextareaAutosizeController = class extends Controller {
|
|
|
132
168
|
this.#lastWidth = width;
|
|
133
169
|
this.resize();
|
|
134
170
|
});
|
|
171
|
+
#listeners = new ListenerSet();
|
|
135
172
|
connect() {
|
|
136
173
|
this.#lastHeight = Math.round(px(this.element.style.height));
|
|
137
174
|
this.#lastWidth = this.#contentWidth();
|
|
138
|
-
this.element
|
|
139
|
-
this.element
|
|
175
|
+
this.#listeners.add(this.element, "input", this.#remeasure);
|
|
176
|
+
this.#listeners.add(this.element, "change", this.#remeasure);
|
|
140
177
|
this.#layout.observe(this.element);
|
|
141
|
-
this.#
|
|
178
|
+
this.#fonts.observe(this.element);
|
|
142
179
|
this.#started = true;
|
|
143
180
|
this.resize();
|
|
144
181
|
}
|
|
145
182
|
disconnect() {
|
|
146
183
|
this.#started = false;
|
|
147
|
-
this.
|
|
148
|
-
this.element.removeEventListener("change", this.#remeasure);
|
|
184
|
+
this.#listeners.dispose();
|
|
149
185
|
this.#layout.disconnect();
|
|
150
|
-
this.#
|
|
186
|
+
this.#fonts.disconnect();
|
|
151
187
|
}
|
|
152
|
-
/** Re-clamps when application code or a Turbo morph changes `minRows`. */
|
|
153
188
|
minRowsValueChanged() {
|
|
154
189
|
if (this.#started) this.#remeasure();
|
|
155
190
|
}
|
|
156
|
-
/** Re-clamps when application code or a Turbo morph changes `maxRows`. */
|
|
157
191
|
maxRowsValueChanged() {
|
|
158
192
|
if (this.#started) this.#remeasure();
|
|
159
193
|
}
|
|
160
|
-
/** Re-measures the content and applies the clamped height. */
|
|
161
194
|
resize() {
|
|
162
195
|
this.#pending = false;
|
|
163
196
|
const el = this.element;
|
|
@@ -169,10 +202,10 @@ var TextareaAutosizeController = class extends Controller {
|
|
|
169
202
|
el.style.height = "auto";
|
|
170
203
|
const contentHeight = Math.max(0, el.scrollHeight - paddingV);
|
|
171
204
|
const rows = Math.max(1, Math.round(contentHeight / lineHeight));
|
|
172
|
-
let targetContent = Math.max(contentHeight, this
|
|
205
|
+
let targetContent = Math.max(contentHeight, this.#safeMinRows * lineHeight);
|
|
173
206
|
let atMax = false;
|
|
174
|
-
if (this
|
|
175
|
-
const maxContent = this
|
|
207
|
+
if (this.#safeMaxRows > 0) {
|
|
208
|
+
const maxContent = this.#safeMaxRows * lineHeight;
|
|
176
209
|
if (targetContent > maxContent) {
|
|
177
210
|
targetContent = maxContent;
|
|
178
211
|
atMax = true;
|
|
@@ -193,34 +226,35 @@ var TextareaAutosizeController = class extends Controller {
|
|
|
193
226
|
this.dispatch("resize", { detail: { height, rows } });
|
|
194
227
|
}
|
|
195
228
|
}
|
|
196
|
-
/** Content-box width — the wrapping input, unaffected by this instance's height writes. */
|
|
197
229
|
#contentWidth() {
|
|
198
230
|
const style = window.getComputedStyle(this.element);
|
|
199
231
|
return Math.max(0, this.element.clientWidth - px(style.paddingLeft) - px(style.paddingRight));
|
|
200
232
|
}
|
|
201
|
-
/** Resolved line height, falling back to ~1.2× font-size when `normal`. */
|
|
202
233
|
#lineHeight(style) {
|
|
203
234
|
const lh = px(style.lineHeight);
|
|
204
235
|
if (lh > 0) return lh;
|
|
205
236
|
const fontSize = px(style.fontSize);
|
|
206
237
|
return fontSize > 0 ? fontSize * 1.2 : 16;
|
|
207
238
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
239
|
+
get #safeMinRows() {
|
|
240
|
+
return this.#numbers.read(
|
|
241
|
+
this,
|
|
242
|
+
"minRows",
|
|
243
|
+
this.minRowsValue,
|
|
244
|
+
_TextareaAutosizeController.values.minRows.default,
|
|
245
|
+
_TextareaAutosizeController.valueConstraints.minRows
|
|
246
|
+
);
|
|
215
247
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
248
|
+
get #safeMaxRows() {
|
|
249
|
+
return this.#numbers.read(
|
|
250
|
+
this,
|
|
251
|
+
"maxRows",
|
|
252
|
+
this.maxRowsValue,
|
|
253
|
+
_TextareaAutosizeController.values.maxRows.default,
|
|
254
|
+
_TextareaAutosizeController.valueConstraints.maxRows
|
|
255
|
+
);
|
|
221
256
|
}
|
|
222
257
|
};
|
|
223
258
|
|
|
224
259
|
export { TextareaAutosizeController };
|
|
225
|
-
//# sourceMappingURL=textarea_autosize_controller.js.map
|
|
226
260
|
//# sourceMappingURL=textarea_autosize_controller.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/layout_observer.ts","../../src/controllers/textarea_autosize_controller.ts"],"names":[],"mappings":"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","file":"textarea_autosize_controller.js","sourcesContent":["/**\n * Unified layout observation for Stimeo controllers.\n *\n * Widgets whose output is measured — an overflow boundary, a masonry column count,\n * an autosized textarea — have three sources that move the layout under them: their\n * *own* box changing — via {@link ResizeObserver} — the *viewport* changing — via the\n * `window` `resize` event — and a descendant *resource settling*, because an image or\n * a frame reports a height of zero until it has loaded. Wiring those three by hand in\n * every controller risks leaked listeners on `disconnect()`. {@link LayoutObserver}\n * owns all three behind one callback and one\n * {@link LayoutObserver.disconnect | disconnect()} that releases everything.\n *\n * Behavior only: the helper reports *that* layout changed; it never reads or\n * writes styles. Consumers decide what to recompute.\n */\n\n/** Invoked whenever an observed element or the viewport changes size. */\nexport type LayoutCallback = () => void;\n\n/** Constructs a {@link ResizeObserver}; injectable so tests stay deterministic. */\nexport type ResizeObserverFactory = (callback: ResizeObserverCallback) => ResizeObserver;\n\n/** Options for {@link LayoutObserver}. */\nexport interface LayoutObserverOptions {\n /**\n * Factory for the {@link ResizeObserver} used by {@link LayoutObserver.observe}.\n * Defaults to the global constructor; override it in tests, or to no-op in\n * environments where `ResizeObserver` is unavailable.\n */\n resizeObserverFactory?: ResizeObserverFactory;\n}\n\n/**\n * Observes element resizes and/or viewport resizes through a single callback,\n * with guaranteed teardown.\n *\n * @example\n * ```ts\n * #layout = new LayoutObserver(() => this.#reposition());\n *\n * connect() {\n * this.#layout.observe(this.panelTarget);\n * this.#layout.observeViewport();\n * this.#layout.observeDescendantLoads(this.panelTarget);\n * }\n *\n * disconnect() {\n * this.#layout.disconnect();\n * }\n * ```\n */\nexport class LayoutObserver {\n readonly #callback: LayoutCallback;\n readonly #resizeObserverFactory: ResizeObserverFactory | null;\n #resizeObserver: ResizeObserver | null = null;\n #observingViewport = false;\n #loadContainer: Element | null = null;\n\n /** Stable bound handler so add/removeEventListener target the same reference. */\n readonly #handleViewportResize = (): void => {\n this.#callback();\n };\n\n /** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */\n readonly #handleDescendantLoad = (): void => {\n this.#callback();\n };\n\n constructor(callback: LayoutCallback, options: LayoutObserverOptions = {}) {\n this.#callback = callback;\n this.#resizeObserverFactory =\n options.resizeObserverFactory ??\n (typeof ResizeObserver === \"undefined\" ? null : (cb) => new ResizeObserver(cb));\n }\n\n /**\n * Starts observing an element's size. Repeated calls observe additional\n * elements through the same shared observer. No-ops when no\n * `ResizeObserver` implementation is available.\n */\n observe(element: Element): void {\n if (!this.#resizeObserverFactory) return;\n if (!this.#resizeObserver) {\n this.#resizeObserver = this.#resizeObserverFactory(() => {\n this.#callback();\n });\n }\n this.#resizeObserver.observe(element);\n }\n\n /** Stops observing a single element while leaving any others in place. */\n unobserve(element: Element): void {\n this.#resizeObserver?.unobserve(element);\n }\n\n /** Starts observing viewport resizes. Idempotent: the listener is added once. */\n observeViewport(): void {\n if (this.#observingViewport) return;\n this.#observingViewport = true;\n window.addEventListener(\"resize\", this.#handleViewportResize);\n }\n\n /** Stops observing viewport resizes without affecting element observation. */\n unobserveViewport(): void {\n if (!this.#observingViewport) return;\n this.#observingViewport = false;\n window.removeEventListener(\"resize\", this.#handleViewportResize);\n }\n\n /**\n * Starts reporting a `load` from anywhere inside `container` — an image or a\n * frame settling changes the box it sits in, and it measures as zero high until\n * then. `load` does not bubble, so the subscription is a capture-phase listener\n * on the container itself and nothing the caller spells.\n *\n * **One container at a time.** A further call moves the observation, so a widget\n * whose content element is swapped at runtime releases the element it let go by\n * naming the new one — there is no second place for the release to drift from.\n */\n observeDescendantLoads(container: Element): void {\n this.unobserveDescendantLoads();\n this.#loadContainer = container;\n container.addEventListener(\"load\", this.#handleDescendantLoad, true);\n }\n\n /** Stops reporting descendant loads without affecting element or viewport observation. */\n unobserveDescendantLoads(): void {\n this.#loadContainer?.removeEventListener(\"load\", this.#handleDescendantLoad, true);\n this.#loadContainer = null;\n }\n\n /**\n * Releases every observation: disconnects the {@link ResizeObserver} and removes\n * the viewport and descendant-load listeners. Safe to call multiple times. Call\n * this from a controller's `disconnect()`.\n */\n disconnect(): void {\n this.#resizeObserver?.disconnect();\n this.#resizeObserver = null;\n this.unobserveViewport();\n this.unobserveDescendantLoads();\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { LayoutObserver } from \"../utils/layout_observer\";\n\n/** Parses a CSS pixel length, defaulting to 0 for `auto` / empty / `normal`. */\nfunction px(value: string): number {\n const n = Number.parseFloat(value);\n return Number.isNaN(n) ? 0 : n;\n}\n\n/** The event surface used by `document.fonts` without requiring it in older engines. */\ninterface FontEventSource {\n addEventListener(type: string, listener: EventListener): void;\n removeEventListener(type: string, listener: EventListener): void;\n}\n\n/**\n * Headless autosize behavior for a `<textarea>`: grows the element to fit its\n * content (and clamps to `minRows` / `maxRows`, scrolling past the max). No APG\n * pattern; supports WCAG 1.4.4 by following the text as it grows.\n *\n * Markup contract (identifier: `stimeo--textarea-autosize`):\n * <textarea\n * data-controller=\"stimeo--textarea-autosize\"\n * data-stimeo--textarea-autosize-max-rows-value=\"10\"></textarea>\n *\n * Each re-measure collapses the element (`height:auto`), reads `scrollHeight`,\n * and sets an explicit pixel height clamped between `minRows` and `maxRows` (in\n * line-height units), toggling internal scrolling and the `data-at-max-rows`\n * hook at the cap. Height-only changes preserve focus and caret.\n *\n * Re-measure triggers: `connect()`; `input` and `change` on the element\n * (`change` covers programmatic writers that announce the write with a bubbling\n * event, the convention Stimeo's own field-writing controllers follow); element\n * size reports via the shared `LayoutObserver` — width changes\n * (re-wrapping) and the applied inline height being stripped (a Turbo morph\n * syncing attributes); font loading settling on `document.fonts`; runtime\n * `minRows` / `maxRows` changes; and the explicit `resize` action. A silent\n * `.value` assignment produces no DOM signal, so callers follow it with\n * `resize` or dispatch `input` / `change`.\n *\n * `resize` dispatches `{ height, rows }` only when the applied height differs\n * from the height this instance last applied. `connect()` adopts a height left\n * on the element by a previous connection (Turbo restore / morph), so restoring\n * an unchanged element stays silent.\n *\n * @remarks\n * Behavior only — the height is written to the element's own inline style, never\n * a CSS class. State lives on the element (no module-scope state), so `connect()`\n * re-measures after a Turbo navigation / morph. Every subscription (`input` /\n * `change` listeners, the layout observer, the font listeners) is released on\n * `disconnect()` (Turbo navigation included). Measurement is synchronous, so\n * there is no pending animation frame to tear down. The layout callback\n * re-measures only when the element's width changed or the applied inline\n * height vanished, so the controller's own height writes do not re-enter it.\n */\nexport class TextareaAutosizeController extends Controller<HTMLTextAreaElement> {\n static override values = {\n minRows: { type: Number, default: 1 },\n maxRows: { type: Number, default: 0 },\n };\n static actions = [\"resize\"] as const;\n static events = [\"resize\"] as const;\n\n declare minRowsValue: number;\n declare maxRowsValue: number;\n\n #lastHeight = -1;\n #lastWidth = -1;\n #started = false;\n #pending = false;\n #fonts: FontEventSource | null = null;\n\n /**\n * Content triggers arriving while the box is collapsed (`display: none`\n * ancestors) are deferred: `scrollHeight` is 0 there, so measuring would\n * clamp the kept height to the `minRows` floor and dispatch a bogus\n * `resize`. The next report from a rendered box flushes the deferral.\n */\n readonly #remeasure = (): void => {\n if (this.element.clientWidth === 0) {\n this.#pending = true;\n return;\n }\n this.resize();\n };\n\n /**\n * Re-measures when the content width changed (re-wrapping, including a\n * horizontal padding change under `border-box`), the applied inline height\n * was stripped (a Turbo morph syncing attributes from server HTML that has no\n * `style`), or a content trigger was deferred while the box was collapsed.\n * This instance's own height writes match none of these signals, so they\n * cannot re-enter; an authored inline height (e.g. a user dragging the native\n * resize handle) is left alone until the next content trigger. Reports from a\n * still-collapsed box are skipped (nothing is measurable there).\n */\n readonly #layout = new LayoutObserver(() => {\n if (this.element.clientWidth === 0) return;\n const width = this.#contentWidth();\n const stripped = this.element.style.height === \"\";\n if (width === this.#lastWidth && !stripped && !this.#pending) return;\n this.#lastWidth = width;\n this.resize();\n });\n\n override connect(): void {\n // Adopt a height applied by a previous connection (kept by Turbo restore /\n // morph) so re-measuring an unchanged element does not re-dispatch `resize`.\n this.#lastHeight = Math.round(px(this.element.style.height));\n this.#lastWidth = this.#contentWidth();\n this.element.addEventListener(\"input\", this.#remeasure);\n this.element.addEventListener(\"change\", this.#remeasure);\n this.#layout.observe(this.element);\n this.#bindFonts();\n this.#started = true;\n this.resize();\n }\n\n override disconnect(): void {\n this.#started = false;\n this.element.removeEventListener(\"input\", this.#remeasure);\n this.element.removeEventListener(\"change\", this.#remeasure);\n this.#layout.disconnect();\n this.#unbindFonts();\n }\n\n /** Re-clamps when application code or a Turbo morph changes `minRows`. */\n minRowsValueChanged(): void {\n if (this.#started) this.#remeasure();\n }\n\n /** Re-clamps when application code or a Turbo morph changes `maxRows`. */\n maxRowsValueChanged(): void {\n if (this.#started) this.#remeasure();\n }\n\n /** Re-measures the content and applies the clamped height. */\n resize(): void {\n this.#pending = false;\n const el = this.element;\n const style = window.getComputedStyle(el);\n const lineHeight = this.#lineHeight(style);\n const paddingV = px(style.paddingTop) + px(style.paddingBottom);\n const borderV = px(style.borderTopWidth) + px(style.borderBottomWidth);\n const borderBox = style.boxSizing === \"border-box\";\n\n // Collapse first so scrollHeight reflects the content, not the prior height.\n el.style.height = \"auto\";\n const contentHeight = Math.max(0, el.scrollHeight - paddingV);\n const rows = Math.max(1, Math.round(contentHeight / lineHeight));\n\n let targetContent = Math.max(contentHeight, this.minRowsValue * lineHeight);\n let atMax = false;\n if (this.maxRowsValue > 0) {\n const maxContent = this.maxRowsValue * lineHeight;\n if (targetContent > maxContent) {\n targetContent = maxContent;\n atMax = true;\n }\n }\n\n const boxExtra = borderBox ? paddingV + borderV : 0;\n const height = Math.round(targetContent + boxExtra);\n el.style.height = `${height}px`;\n el.style.overflowY = atMax ? \"auto\" : \"hidden\";\n\n if (atMax) {\n el.setAttribute(\"data-at-max-rows\", \"true\");\n } else {\n el.removeAttribute(\"data-at-max-rows\");\n }\n el.style.setProperty(\"--stimeo--textarea-rows\", String(rows));\n\n if (height !== this.#lastHeight) {\n this.#lastHeight = height;\n this.dispatch(\"resize\", { detail: { height, rows } });\n }\n }\n\n /** Content-box width — the wrapping input, unaffected by this instance's height writes. */\n #contentWidth(): number {\n const style = window.getComputedStyle(this.element);\n return Math.max(0, this.element.clientWidth - px(style.paddingLeft) - px(style.paddingRight));\n }\n\n /** Resolved line height, falling back to ~1.2× font-size when `normal`. */\n #lineHeight(style: CSSStyleDeclaration): number {\n const lh = px(style.lineHeight);\n if (lh > 0) return lh;\n const fontSize = px(style.fontSize);\n return fontSize > 0 ? fontSize * 1.2 : 16;\n }\n\n /** Subscribes to font completion when the current engine exposes `document.fonts`. */\n #bindFonts(): void {\n const fonts = (this.element.ownerDocument as Document & { fonts?: FontEventSource }).fonts;\n if (!fonts) return;\n this.#fonts = fonts;\n fonts.addEventListener(\"loadingdone\", this.#remeasure);\n fonts.addEventListener(\"loadingerror\", this.#remeasure);\n }\n\n /** Removes the font completion subscriptions. */\n #unbindFonts(): void {\n this.#fonts?.removeEventListener(\"loadingdone\", this.#remeasure);\n this.#fonts?.removeEventListener(\"loadingerror\", this.#remeasure);\n this.#fonts = null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/listener_set.ts","../../src/utils/font_load_watcher.ts","../../src/utils/layout_observer.ts","../../src/utils/number_bounds.ts","../../src/utils/coerce.ts","../../src/utils/number_value.ts","../../src/controllers/textarea_autosize_controller.ts"],"names":[],"mappings":"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","file":"textarea_autosize_controller.js","sourcesContent":["/** What {@link ListenerSet.add} passes on besides the signal it supplies itself. */\nexport type ListenerOptions = Omit<AddEventListenerOptions, \"signal\">;\n\n/**\n * The event map a target's own `addEventListener` types its handlers with: the\n * window's, a document's, an HTML or SVG element's, any other element's, or a\n * media query list's. Other targets name none.\n */\ntype EventMapOf<T extends EventTarget> = T extends Window\n ? WindowEventMap\n : T extends Document\n ? DocumentEventMap\n : T extends HTMLElement\n ? HTMLElementEventMap\n : T extends SVGElement\n ? SVGElementEventMap\n : T extends Element\n ? ElementEventMap\n : T extends MediaQueryList\n ? MediaQueryListEventMap\n : Record<never, never>;\n\n/**\n * Owns every listener a controller attaches for one connected lifetime and\n * releases them together, so an event name is spelled once and the release\n * cannot drift from the registration.\n *\n * Detaching by hand repeats three things at the release site — the type, the\n * handler reference and the capture flag — and any one of them drifting leaves\n * a listener alive on an element the controller no longer drives. Aborting the\n * signal a registration was created with releases it whatever it was made of,\n * so the release site names nothing.\n *\n * One set per lifetime, held as a field and never rebuilt.\n * {@link ListenerSet.dispose} is the only thing that closes a generation, and it opens\n * the next one in the same step, so the lifecycle hooks can call `add` and\n * `dispose` in any order any number of times, and no `add` ever meets an\n * already aborted signal — a DOM that registers a listener on such a signal\n * would keep it forever. Rebuilding the set instead would leak:\n * `addEventListener` matches an existing registration on target, type, callback\n * and capture alone, so re-adding the same handler while the first registration\n * is live is discarded, and that live registration keeps the signal it was\n * attached with — a set built by a second `connect()` can never release what it\n * appears to own.\n *\n * Scope is the registration only. Timers, observers, leases and the order they\n * are unwound in stay with the controller, which places `dispose()` in its\n * cleanup sequence. A receiver that changes at runtime (a\n * swapped target) is rebound by calling `dispose()` and then `add()` again from\n * the rebind, not by a second set.\n *\n * A lifetime shorter than the connection — while a Value holds, until a first\n * load, while a subscription is open — gets a set of its own, which the end of\n * that lifetime disposes. A controller therefore holds one set per lifetime: the\n * connection's, and one for each swapped receiver or shorter span.\n *\n * The set must own every registration of a tuple it holds: a listener the same\n * target already carries for the same type, callback and capture flag keeps the\n * right to remove it, and this set's `add` for that tuple is discarded. A\n * subscription shared between instances behind a participant count, listeners of\n * several elements that come and go one at a time, and those one gesture holds\n * keep their own pair of calls or a signal of their own.\n *\n * @example\n * ```ts\n * readonly #listeners = new ListenerSet();\n *\n * connect(): void {\n * this.#listeners.add(this.element, \"submit\", this.#onNativeSubmit, { capture: true });\n * this.#listeners.add(this.element, \"turbo:submit-start\", this.#onSubmitStart);\n * this.#listeners.add(this.element, \"turbo:submit-end\", this.#onSubmitEnd);\n * }\n *\n * disconnect(): void {\n * this.#listeners.dispose();\n * }\n * ```\n */\nexport class ListenerSet {\n /** The generation every `add` joins until the next `dispose()`. */\n #abort = new AbortController();\n\n /**\n * Attaches `handler` to the open generation, exactly as the caller spelled it.\n *\n * The set supplies the signal, so `options` carries everything else the DOM\n * accepts — `capture` included. A removal by hand would have to repeat that\n * flag; aborting the signal releases the listener without it.\n *\n * A `type` the target's own event map names types `handler` with that event,\n * as the target's `addEventListener` does, so a handler written for a\n * `KeyboardEvent` is passed as it is; any other `type` takes an\n * `EventListener`.\n */\n add<T extends EventTarget, K extends keyof EventMapOf<T> & string>(\n target: T,\n type: K,\n handler: (event: EventMapOf<T>[K]) => void,\n options?: ListenerOptions,\n ): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): void {\n target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });\n }\n\n /**\n * Releases every listener of the open generation, synchronously, and opens the\n * next one. Idempotent, and safe before anything has been added.\n */\n dispose(): void {\n this.#abort.abort();\n this.#abort = new AbortController();\n }\n}\n","import { ListenerSet } from \"./listener_set\";\n\n/**\n * Follows font loading completion in an element's current owner document.\n *\n * Both `loadingdone` and `loadingerror` run the supplied callback synchronously.\n * Measurement and notification policy stay with the caller. Observing the same\n * source preserves its registrations; changing the source releases the former\n * one, including when the next document has no fonts API. Each instance owns its\n * callback identity so independent watchers can safely share a callback.\n */\nexport class FontLoadWatcher {\n readonly #listeners = new ListenerSet();\n readonly #onSettled: EventListener;\n #fonts: EventTarget | null = null;\n\n /** @param run - the consumer's synchronous font-completion measurement. */\n constructor(run: () => void) {\n this.#onSettled = () => run();\n }\n\n /** Follows the element's current font source; a missing API leaves no subscription. */\n observe(element: Element): void {\n const fonts = element.ownerDocument.fonts ?? null;\n if (fonts === this.#fonts) return;\n this.disconnect();\n if (!fonts) return;\n this.#fonts = fonts;\n this.#listeners.add(fonts, \"loadingdone\", this.#onSettled);\n this.#listeners.add(fonts, \"loadingerror\", this.#onSettled);\n }\n\n /** Releases the held source synchronously; repeated calls and later observation are safe. */\n disconnect(): void {\n this.#listeners.dispose();\n this.#fonts = null;\n }\n}\n","/**\n * Unified layout observation for Stimeo controllers.\n *\n * Widgets whose output is measured — an overflow boundary, a masonry column count,\n * an autosized textarea — have three sources that move the layout under them: their\n * *own* box changing — via {@link ResizeObserver} — the *viewport* changing — via the\n * `window` `resize` event — and a descendant *resource settling*, because an image or\n * a frame reports a height of zero until it has loaded. Wiring those three by hand in\n * every controller risks leaked listeners on `disconnect()`. {@link LayoutObserver}\n * owns all three behind one callback and one\n * {@link LayoutObserver.disconnect | disconnect()} that releases everything.\n *\n * Behavior only: the helper reports *that* layout changed; it never reads or\n * writes styles. Consumers decide what to recompute.\n */\n\n/** Invoked whenever an observed element or the viewport changes size. */\nexport type LayoutCallback = () => void;\n\n/** Constructs a {@link ResizeObserver}; injectable so tests stay deterministic. */\nexport type ResizeObserverFactory = (callback: ResizeObserverCallback) => ResizeObserver;\n\n/** Options for {@link LayoutObserver}. */\nexport interface LayoutObserverOptions {\n /**\n * Factory for the {@link ResizeObserver} used by {@link LayoutObserver.observe}.\n * Defaults to the global constructor; override it in tests, or to no-op in\n * environments where `ResizeObserver` is unavailable.\n */\n resizeObserverFactory?: ResizeObserverFactory;\n}\n\n/**\n * Observes element resizes and/or viewport resizes through a single callback,\n * with guaranteed teardown.\n *\n * @example\n * ```ts\n * #layout = new LayoutObserver(() => this.#reposition());\n *\n * connect() {\n * this.#layout.observe(this.panelTarget);\n * this.#layout.observeViewport();\n * this.#layout.observeDescendantLoads(this.panelTarget);\n * }\n *\n * disconnect() {\n * this.#layout.disconnect();\n * }\n * ```\n */\nexport class LayoutObserver {\n readonly #callback: LayoutCallback;\n readonly #resizeObserverFactory: ResizeObserverFactory | null;\n #resizeObserver: ResizeObserver | null = null;\n #observingViewport = false;\n #loadContainer: Element | null = null;\n\n /** Stable bound handler so add/removeEventListener target the same reference. */\n readonly #handleViewportResize = (): void => {\n this.#callback();\n };\n\n /** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */\n readonly #handleDescendantLoad = (): void => {\n this.#callback();\n };\n\n constructor(callback: LayoutCallback, options: LayoutObserverOptions = {}) {\n this.#callback = callback;\n this.#resizeObserverFactory =\n options.resizeObserverFactory ??\n (typeof ResizeObserver === \"undefined\" ? null : (cb) => new ResizeObserver(cb));\n }\n\n /**\n * Starts observing an element's size. Repeated calls observe additional\n * elements through the same shared observer. No-ops when no\n * `ResizeObserver` implementation is available.\n */\n observe(element: Element): void {\n if (!this.#resizeObserverFactory) return;\n if (!this.#resizeObserver) {\n this.#resizeObserver = this.#resizeObserverFactory(() => {\n this.#callback();\n });\n }\n this.#resizeObserver.observe(element);\n }\n\n /** Stops observing a single element while leaving any others in place. */\n unobserve(element: Element): void {\n this.#resizeObserver?.unobserve(element);\n }\n\n /** Starts observing viewport resizes. Idempotent: the listener is added once. */\n observeViewport(): void {\n if (this.#observingViewport) return;\n this.#observingViewport = true;\n window.addEventListener(\"resize\", this.#handleViewportResize);\n }\n\n /** Stops observing viewport resizes without affecting element observation. */\n unobserveViewport(): void {\n if (!this.#observingViewport) return;\n this.#observingViewport = false;\n window.removeEventListener(\"resize\", this.#handleViewportResize);\n }\n\n /**\n * Starts reporting a `load` from anywhere inside `container` — an image or a\n * frame settling changes the box it sits in, and it measures as zero high until\n * then. `load` does not bubble, so the subscription is a capture-phase listener\n * on the container itself and nothing the caller spells.\n *\n * **One container at a time.** A further call moves the observation, so a widget\n * whose content element is swapped at runtime releases the element it let go by\n * naming the new one — there is no second place for the release to drift from.\n */\n observeDescendantLoads(container: Element): void {\n this.unobserveDescendantLoads();\n this.#loadContainer = container;\n container.addEventListener(\"load\", this.#handleDescendantLoad, true);\n }\n\n /** Stops reporting descendant loads without affecting element or viewport observation. */\n unobserveDescendantLoads(): void {\n this.#loadContainer?.removeEventListener(\"load\", this.#handleDescendantLoad, true);\n this.#loadContainer = null;\n }\n\n /**\n * Releases every observation: disconnects the {@link ResizeObserver} and removes\n * the viewport and descendant-load listeners. Safe to call multiple times. Call\n * this from a controller's `disconnect()`.\n */\n disconnect(): void {\n this.#resizeObserver?.disconnect();\n this.#resizeObserver = null;\n this.unobserveViewport();\n this.unobserveDescendantLoads();\n }\n}\n","import { MAX_TIMER_DELAY_MS } from \"./timer_bounds\";\n\n/** Semantic constraints on an already decoded number, independent of the DOM. */\nexport interface NumberBounds {\n /** Finite numbers are required, including when this field is omitted. */\n readonly finite?: true;\n /** Inclusive lower endpoint. */\n readonly min?: number;\n /** Inclusive upper endpoint. */\n readonly max?: number;\n /** Exclusive lower endpoint. */\n readonly exclusiveMin?: number;\n /** Require a number without a fractional part. */\n readonly integer?: boolean;\n /** Exact decoded numbers accepted by a discrete contract. */\n readonly allowedValues?: readonly number[];\n /** JSON-safe exceptions to finite-number checking for unbounded endpoints. */\n readonly allowInfinity?: \"negative\" | \"positive\" | \"both\";\n}\n\n/** One constraint for each Number Value, including shorthand declarations. */\nexport type NumberValueConstraints<Values> = {\n readonly [Key in keyof Values as Values[Key] extends\n | NumberConstructor\n | { type: NumberConstructor }\n ? Key\n : never]: NumberBounds;\n};\n\n/** Reusable semantic domains; fractional inputs remain fractional unless excluded. */\nexport const NUMBER_BOUNDS = {\n finite: { finite: true },\n nonNegative: { finite: true, min: 0 },\n positive: { finite: true, exclusiveMin: 0 },\n nonNegativeInteger: { finite: true, min: 0, integer: true },\n positiveInteger: { finite: true, exclusiveMin: 0, integer: true },\n lowerBound: { finite: true, allowInfinity: \"negative\" },\n upperBound: { finite: true, allowInfinity: \"positive\" },\n timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS },\n positiveTimer: { finite: true, exclusiveMin: 0, max: MAX_TIMER_DELAY_MS },\n} as const satisfies Record<string, NumberBounds>;\n\n/**\n * Checks all declared bounds without rounding, clamping, or coercing the input.\n * Infinity exceptions relax only finite checking; every other bound still applies.\n */\nexport function matchesNumberBounds(value: number, bounds: NumberBounds): boolean {\n if (!Number.isFinite(value)) {\n const direction = value === Infinity ? \"positive\" : value === -Infinity ? \"negative\" : null;\n if (direction === null) return false;\n if (bounds.allowInfinity !== \"both\" && bounds.allowInfinity !== direction) return false;\n }\n if (bounds.min !== undefined && value < bounds.min) return false;\n if (bounds.max !== undefined && value > bounds.max) return false;\n if (bounds.exclusiveMin !== undefined && value <= bounds.exclusiveMin) return false;\n if (bounds.integer && !Number.isInteger(value)) return false;\n if (bounds.allowedValues !== undefined && !bounds.allowedValues.includes(value)) return false;\n return true;\n}\n\n/** Decodes a Number Value literal; action params use their own JSON decoder. */\nexport function decodeNumberValue(raw: string): number {\n return Number(raw.replace(/_/g, \"\"));\n}\n","/**\n * Numeric coercion for controllers that accept a number through a Value, an action\n * param, or an event detail.\n *\n * Stimulus decodes action params as JSON when possible and leaves other text\n * untouched. CustomEvent detail can also supply a number or numeric string.\n * The reader accepts those two types while rejecting blank and non-finite input.\n */\n\nimport { matchesNumberBounds, type NumberBounds } from \"./number_bounds\";\n\n/** Numeric update input, accepting decoded action params before CustomEvent detail. */\nexport type NumericValueEvent = Event & {\n params?: { amount?: unknown };\n detail?: { value?: unknown };\n};\n\n/** Reads the nullish action-param/detail precedence without falling back from invalid input. */\nexport function eventNumber(event: NumericValueEvent): number | null {\n return toFiniteNumber(event.params?.amount ?? event.detail?.value);\n}\n\n/**\n * Reads a finite number or nonblank numeric string; other input returns `null`.\n * Blank strings cannot silently reset a value to zero.\n */\nexport function toFiniteNumber(raw: unknown): number | null {\n if (typeof raw !== \"number\" && typeof raw !== \"string\") return null;\n if (typeof raw === \"string\" && raw.trim().length === 0) return null;\n const value = Number(raw);\n return Number.isFinite(value) ? value : null;\n}\n\n/**\n * Returns an accepted decoded Number Value or the caller's fallback unchanged.\n * The caller owns the fallback contract; this reader does not parse or write a Value.\n */\nexport function readNumber(raw: number, fallback: number, bounds: NumberBounds): number {\n return matchesNumberBounds(raw, bounds) ? raw : fallback;\n}\n","import { readNumber } from \"./coerce\";\nimport { matchesNumberBounds, type NumberBounds } from \"./number_bounds\";\n\n/** The instance identity carried by a Number Value read. */\nexport interface NumberValueOwner {\n readonly identifier: string;\n readonly element: Element;\n}\n\n/** Reads current declarations at the point of use without rewriting their attributes. */\nexport class NumberValueReader {\n /** The last rejected literal per Value; no history grows for an unchanged instance. */\n readonly #lastRejected = new Map<string, string>();\n\n /** Resolves the declared number using its own fallback and shared class contract. */\n read(\n owner: NumberValueOwner,\n name: string,\n raw: number,\n fallback: number,\n bounds: NumberBounds,\n ): number {\n const resolved = readNumber(raw, fallback, bounds);\n if (matchesNumberBounds(raw, bounds)) {\n this.#lastRejected.delete(name);\n return resolved;\n }\n const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;\n const literal = owner.element.getAttribute(attribute);\n if (literal === null) {\n this.#lastRejected.delete(name);\n return resolved;\n }\n if (this.#lastRejected.get(name) !== literal) {\n this.#lastRejected.set(name, literal);\n console.warn(\n `Stimeo UI: \"${owner.identifier}\" has an invalid number Value \"${name}\" declaration ${JSON.stringify(literal)}; using ${fallback}.`,\n );\n }\n return resolved;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { FontLoadWatcher } from \"../utils/font_load_watcher\";\nimport { LayoutObserver } from \"../utils/layout_observer\";\nimport { ListenerSet } from \"../utils/listener_set\";\nimport { NUMBER_BOUNDS, type NumberValueConstraints } from \"../utils/number_bounds\";\nimport { NumberValueReader } from \"../utils/number_value\";\n\n/** Parses a CSS pixel length, defaulting to 0 for `auto` / empty / `normal`. */\nfunction px(value: string): number {\n const n = Number.parseFloat(value);\n return Number.isNaN(n) ? 0 : n;\n}\n\n/**\n * Headless autosize behavior for a `<textarea>`: grows the element to fit its\n * content (and clamps to `minRows` / `maxRows`, scrolling past the max). No APG\n * pattern; supports WCAG 1.4.4 by following the text as it grows.\n *\n * Markup contract (identifier: `stimeo--textarea-autosize`):\n * <textarea\n * data-controller=\"stimeo--textarea-autosize\"\n * data-stimeo--textarea-autosize-max-rows-value=\"10\"></textarea>\n *\n * Each re-measure collapses the element (`height:auto`), reads `scrollHeight`,\n * and sets an explicit pixel height clamped between `minRows` and `maxRows` (in\n * line-height units), toggling internal scrolling and the `data-at-max-rows`\n * hook at the cap. Height-only changes preserve focus and caret.\n *\n * Re-measure triggers: `connect()`; `input` and `change` on the element\n * (`change` covers programmatic writers that announce the write with a bubbling\n * event, the convention Stimeo's own field-writing controllers follow); element\n * size reports via the shared `LayoutObserver` — width changes\n * (re-wrapping) and the applied inline height being stripped (a Turbo morph\n * syncing attributes); font loading settling on `ownerDocument.fonts`; runtime\n * `minRows` / `maxRows` changes; and the explicit `resize` action. A silent\n * `.value` assignment produces no DOM signal, so callers follow it with\n * `resize` or dispatch `input` / `change`.\n *\n * `resize` dispatches `{ height, rows }` only when the applied height differs\n * from the height this instance last applied. `connect()` adopts a height left\n * on the element by a previous connection (Turbo restore / morph), so restoring\n * an unchanged element stays silent.\n *\n * @remarks\n * Behavior only — the height is written to the element's own inline style, never\n * a CSS class. State lives on the element (no module-scope state), so `connect()`\n * re-measures after a Turbo navigation / morph. Every subscription (`input` /\n * `change` listeners, the layout observer, the font listeners) is released on\n * `disconnect()` (Turbo navigation included). Measurement is synchronous, so\n * there is no pending animation frame to tear down. The layout callback\n * re-measures only when the element's width changed or the applied inline\n * height vanished, so the controller's own height writes do not re-enter it.\n */\nexport class TextareaAutosizeController extends Controller<HTMLTextAreaElement> {\n /** Numeric read boundaries share one reader for this controller instance. */\n readonly #numbers = new NumberValueReader();\n\n static override values = {\n minRows: { type: Number, default: 1 },\n maxRows: { type: Number, default: 0 },\n };\n\n static valueConstraints = {\n minRows: { finite: true, min: 1 },\n maxRows: NUMBER_BOUNDS.nonNegative,\n } satisfies NumberValueConstraints<typeof TextareaAutosizeController.values>;\n static actions = [\"resize\"] as const;\n static events = [\"resize\"] as const;\n\n declare minRowsValue: number;\n declare maxRowsValue: number;\n\n #lastHeight = -1;\n #lastWidth = -1;\n #started = false;\n #pending = false;\n\n /**\n * Content triggers arriving while the box is collapsed (`display: none`\n * ancestors) are deferred: `scrollHeight` is 0 there, so measuring would\n * clamp the kept height to the `minRows` floor and dispatch a bogus\n * `resize`. The next report from a rendered box flushes the deferral.\n */\n readonly #remeasure = (): void => {\n if (this.element.clientWidth === 0) {\n this.#pending = true;\n return;\n }\n this.resize();\n };\n readonly #fonts = new FontLoadWatcher(this.#remeasure);\n\n /**\n * Re-measures when the content width changed (re-wrapping, including a\n * horizontal padding change under `border-box`), the applied inline height\n * was stripped (a Turbo morph syncing attributes from server HTML that has no\n * `style`), or a content trigger was deferred while the box was collapsed.\n * This instance's own height writes match none of these signals, so they\n * cannot re-enter; an authored inline height (e.g. a user dragging the native\n * resize handle) is left alone until the next content trigger. Reports from a\n * still-collapsed box are skipped (nothing is measurable there).\n */\n readonly #layout = new LayoutObserver(() => {\n if (this.element.clientWidth === 0) return;\n const width = this.#contentWidth();\n const stripped = this.element.style.height === \"\";\n if (width === this.#lastWidth && !stripped && !this.#pending) return;\n this.#lastWidth = width;\n this.resize();\n });\n\n /** The listeners of one connection, released together on `disconnect()`. */\n readonly #listeners = new ListenerSet();\n\n override connect(): void {\n // Adopt a height applied by a previous connection (kept by Turbo restore /\n // morph) so re-measuring an unchanged element does not re-dispatch `resize`.\n this.#lastHeight = Math.round(px(this.element.style.height));\n this.#lastWidth = this.#contentWidth();\n this.#listeners.add(this.element, \"input\", this.#remeasure);\n this.#listeners.add(this.element, \"change\", this.#remeasure);\n this.#layout.observe(this.element);\n this.#fonts.observe(this.element);\n this.#started = true;\n this.resize();\n }\n\n override disconnect(): void {\n this.#started = false;\n this.#listeners.dispose();\n this.#layout.disconnect();\n this.#fonts.disconnect();\n }\n\n /** Re-clamps when application code or a Turbo morph changes `minRows`. */\n minRowsValueChanged(): void {\n if (this.#started) this.#remeasure();\n }\n\n /** Re-clamps when application code or a Turbo morph changes `maxRows`. */\n maxRowsValueChanged(): void {\n if (this.#started) this.#remeasure();\n }\n\n /** Re-measures the content and applies the clamped height. */\n resize(): void {\n this.#pending = false;\n const el = this.element;\n const style = window.getComputedStyle(el);\n const lineHeight = this.#lineHeight(style);\n const paddingV = px(style.paddingTop) + px(style.paddingBottom);\n const borderV = px(style.borderTopWidth) + px(style.borderBottomWidth);\n const borderBox = style.boxSizing === \"border-box\";\n\n // Collapse first so scrollHeight reflects the content, not the prior height.\n el.style.height = \"auto\";\n const contentHeight = Math.max(0, el.scrollHeight - paddingV);\n const rows = Math.max(1, Math.round(contentHeight / lineHeight));\n\n let targetContent = Math.max(contentHeight, this.#safeMinRows * lineHeight);\n let atMax = false;\n if (this.#safeMaxRows > 0) {\n const maxContent = this.#safeMaxRows * lineHeight;\n if (targetContent > maxContent) {\n targetContent = maxContent;\n atMax = true;\n }\n }\n\n const boxExtra = borderBox ? paddingV + borderV : 0;\n const height = Math.round(targetContent + boxExtra);\n el.style.height = `${height}px`;\n el.style.overflowY = atMax ? \"auto\" : \"hidden\";\n\n if (atMax) {\n el.setAttribute(\"data-at-max-rows\", \"true\");\n } else {\n el.removeAttribute(\"data-at-max-rows\");\n }\n el.style.setProperty(\"--stimeo--textarea-rows\", String(rows));\n\n if (height !== this.#lastHeight) {\n this.#lastHeight = height;\n this.dispatch(\"resize\", { detail: { height, rows } });\n }\n }\n\n /** Content-box width — the wrapping input, unaffected by this instance's height writes. */\n #contentWidth(): number {\n const style = window.getComputedStyle(this.element);\n return Math.max(0, this.element.clientWidth - px(style.paddingLeft) - px(style.paddingRight));\n }\n\n /** Resolved line height, falling back to ~1.2× font-size when `normal`. */\n #lineHeight(style: CSSStyleDeclaration): number {\n const lh = px(style.lineHeight);\n if (lh > 0) return lh;\n const fontSize = px(style.fontSize);\n return fontSize > 0 ? fontSize * 1.2 : 16;\n }\n\n /** Current `minRows` declaration resolved against its numeric contract. */\n get #safeMinRows(): number {\n return this.#numbers.read(\n this,\n \"minRows\",\n this.minRowsValue,\n TextareaAutosizeController.values.minRows.default,\n TextareaAutosizeController.valueConstraints.minRows,\n );\n }\n\n /** Current `maxRows` declaration resolved against its numeric contract. */\n get #safeMaxRows(): number {\n return this.#numbers.read(\n this,\n \"maxRows\",\n this.maxRowsValue,\n TextareaAutosizeController.values.maxRows.default,\n TextareaAutosizeController.valueConstraints.maxRows,\n );\n }\n}\n"]}
|
|
@@ -8,6 +8,9 @@ type ThemeMode = "light" | "dark" | "system";
|
|
|
8
8
|
* effective theme onto the root for the consumer's CSS. Ships no colors; only state
|
|
9
9
|
* hooks.
|
|
10
10
|
*
|
|
11
|
+
* Toggle resolves persisted preference before the declared mode, including system
|
|
12
|
+
* preference, so it inverts the effective current theme.
|
|
13
|
+
*
|
|
11
14
|
* Two markup contracts (identifier: `stimeo--theme`):
|
|
12
15
|
*
|
|
13
16
|
* Canonical 3-value radiogroup (use this when `system` is offered):
|
|
@@ -28,30 +31,33 @@ type ThemeMode = "light" | "dark" | "system";
|
|
|
28
31
|
* <button data-controller="stimeo--theme" data-action="click->stimeo--theme#toggle"
|
|
29
32
|
* aria-pressed="false">Dark mode</button>
|
|
30
33
|
*
|
|
31
|
-
* `change` dispatches `{ mode, resolved }` when a selection or the OS preference moves
|
|
32
|
-
* one of the two. `reconcile` dispatches
|
|
34
|
+
* `change` dispatches `{ mode, resolved, reason }` when a selection or the OS preference moves
|
|
35
|
+
* one of the two. `reconcile` dispatches `{ mode, resolved }` when a `mode`
|
|
33
36
|
* declaration that changes after connect moves one of them — the declaration
|
|
34
|
-
* itself, or a stored choice that outranks it
|
|
37
|
+
* itself, or a stored choice that outranks it — or a `storageKey` change brings the
|
|
38
|
+
* choice saved under the new key. Connecting is silent.
|
|
35
39
|
*
|
|
36
40
|
* @remarks
|
|
37
41
|
* Behavior only — the actual palette is the consumer's CSS keyed off `data-theme`
|
|
38
42
|
* on the root. It applies `data-theme` (the *resolved* light/dark) and a matching
|
|
39
43
|
* `color-scheme` to the `target` element (`<html>` by default), keeps the radiogroup
|
|
40
44
|
* `aria-checked` + roving tabindex (APG radio) or the single button's `aria-pressed`
|
|
41
|
-
* in sync. Arrow
|
|
42
|
-
* requires
|
|
45
|
+
* in sync. Arrow keys move focus and the selection across the radiogroup as the APG radio
|
|
46
|
+
* pattern requires, and Home/End move both to the first/last option; applying a theme never
|
|
47
|
+
* moves focus. The `prefers-color-scheme` listener is attached on
|
|
43
48
|
* `connect()` and removed on `disconnect()` (Turbo included). FOUC avoidance for the
|
|
44
49
|
* very first paint is an inline `<head>` snippet, not this controller. A `mode`
|
|
45
50
|
* declaration rewritten after connect is followed, but never over a stored choice
|
|
46
|
-
* ({@link ThemeController.modeValueChanged})
|
|
51
|
+
* ({@link ThemeController.modeValueChanged}), and so is a `storageKey`
|
|
52
|
+
* ({@link ThemeController.storageKeyValueChanged}).
|
|
47
53
|
*
|
|
48
54
|
* Every declaration is validated where it enters, and an unreadable one falls back
|
|
49
55
|
* to that Value's default rather than taking the widget with it: a `mode` outside
|
|
50
56
|
* the three modes reads as `system`, and a `target` that is not a parsable selector
|
|
51
57
|
* reads as `html`. An option whose `data-value` is outside the three can be focused
|
|
52
|
-
* but never becomes the selection
|
|
53
|
-
* and
|
|
54
|
-
* keeps the Tab stop.
|
|
58
|
+
* but never becomes the selection. Every option matching the selected mode is
|
|
59
|
+
* checked, and the first matching option keeps the Tab stop. When no option
|
|
60
|
+
* matches, none is checked and the first option keeps the Tab stop.
|
|
55
61
|
*
|
|
56
62
|
* The radiogroup stays one Tab stop through `RovingTabindex`, re-derived
|
|
57
63
|
* whenever an option enters or leaves, so a set rendered after connect or swapped
|
|
@@ -97,6 +103,13 @@ declare class ThemeController extends Controller<HTMLElement> {
|
|
|
97
103
|
* first, so the next selection is compared with what is on screen.
|
|
98
104
|
*/
|
|
99
105
|
modeValueChanged(): void;
|
|
106
|
+
/**
|
|
107
|
+
* Follows a `storageKey` rewritten after connect. A valid choice saved under the new
|
|
108
|
+
* key outranks the mode on screen as at connect — `mode` is written to it and a moved
|
|
109
|
+
* pair is reported as `reconcile` — and without one the mode stays. Nothing is copied
|
|
110
|
+
* or written; the next selection is saved under the new key.
|
|
111
|
+
*/
|
|
112
|
+
storageKeyValueChanged(): void;
|
|
100
113
|
/** Re-derives the single Tab stop and ARIA for an option set that changed. */
|
|
101
114
|
optionTargetConnected(): void;
|
|
102
115
|
/** Re-derives them again when an option leaves, so a Tab stop always remains. */
|
|
@@ -107,9 +120,9 @@ declare class ThemeController extends Controller<HTMLElement> {
|
|
|
107
120
|
* Read through the same lane that decides which option is checked, so the two
|
|
108
121
|
* can never disagree about what an option declares.
|
|
109
122
|
*/
|
|
110
|
-
set(
|
|
123
|
+
set(source: Event | HTMLElement): void;
|
|
111
124
|
/** Toggles light↔dark for the 2-value single-button contract. */
|
|
112
|
-
toggle(): void;
|
|
125
|
+
toggle(event?: Event): void;
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
export { ThemeController };
|