stimeo-ui 0.6.0 → 0.8.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +124 -0
  3. data/dist/controllers/alert_dialog_controller.js +75 -4
  4. data/dist/controllers/auto_submit_controller.js +94 -16
  5. data/dist/controllers/avatar_controller.js +47 -5
  6. data/dist/controllers/carousel_controller.js +451 -100
  7. data/dist/controllers/character_counter_controller.js +338 -63
  8. data/dist/controllers/command_palette_controller.js +75 -4
  9. data/dist/controllers/conditional_fields_controller.js +345 -51
  10. data/dist/controllers/confirm_controller.js +75 -4
  11. data/dist/controllers/currency_input_controller.js +305 -74
  12. data/dist/controllers/dialog_controller.js +75 -4
  13. data/dist/controllers/direct_upload_controller.js +212 -46
  14. data/dist/controllers/dirty_form_controller.js +192 -29
  15. data/dist/controllers/dismissible_controller.js +83 -18
  16. data/dist/controllers/drawer_controller.js +75 -4
  17. data/dist/controllers/file_dropzone_controller.js +386 -63
  18. data/dist/controllers/flash_controller.js +3 -1
  19. data/dist/controllers/focus_controller.js +75 -4
  20. data/dist/controllers/form_field_controller.js +280 -62
  21. data/dist/controllers/form_validation_controller.js +208 -83
  22. data/dist/controllers/frame_loading_controller.js +2 -1
  23. data/dist/controllers/input_mask_controller.js +251 -76
  24. data/dist/controllers/nested_form_controller.js +450 -42
  25. data/dist/controllers/number_input_controller.js +47 -5
  26. data/dist/controllers/otp_controller.js +472 -114
  27. data/dist/controllers/overflow_menu_controller.js +2 -1
  28. data/dist/controllers/pagination_controller.js +2 -1
  29. data/dist/controllers/persist_controller.js +432 -122
  30. data/dist/controllers/popover_controller.js +77 -4
  31. data/dist/controllers/rating_controller.js +9 -5
  32. data/dist/controllers/scroll_area_controller.js +462 -162
  33. data/dist/controllers/separator_controller.js +354 -38
  34. data/dist/controllers/sidebar_controller.js +83 -10
  35. data/dist/controllers/spinner_controller.js +7 -5
  36. data/dist/controllers/submit_once_controller.js +402 -122
  37. data/dist/controllers/textarea_autosize_controller.js +131 -3
  38. data/dist/controllers/theme_controller.js +8 -6
  39. data/dist/controllers/tree_view_controller.js +2 -1
  40. data/dist/index.js +4293 -1447
  41. data/lib/stimeo/ui/version.rb +1 -1
  42. metadata +2 -2
@@ -1,5 +1,63 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
+ // src/controllers/textarea_autosize_controller.ts
4
+
5
+ // src/utils/layout_observer.ts
6
+ var LayoutObserver = class {
7
+ #callback;
8
+ #resizeObserverFactory;
9
+ #resizeObserver = null;
10
+ #observingViewport = false;
11
+ /** Stable bound handler so add/removeEventListener target the same reference. */
12
+ #handleViewportResize = () => {
13
+ this.#callback();
14
+ };
15
+ constructor(callback, options = {}) {
16
+ this.#callback = callback;
17
+ this.#resizeObserverFactory = options.resizeObserverFactory ?? (typeof ResizeObserver === "undefined" ? null : (cb) => new ResizeObserver(cb));
18
+ }
19
+ /**
20
+ * Starts observing an element's size. Repeated calls observe additional
21
+ * elements through the same shared observer. No-ops when no
22
+ * `ResizeObserver` implementation is available.
23
+ */
24
+ observe(element) {
25
+ if (!this.#resizeObserverFactory) return;
26
+ if (!this.#resizeObserver) {
27
+ this.#resizeObserver = this.#resizeObserverFactory(() => {
28
+ this.#callback();
29
+ });
30
+ }
31
+ this.#resizeObserver.observe(element);
32
+ }
33
+ /** Stops observing a single element while leaving any others in place. */
34
+ unobserve(element) {
35
+ this.#resizeObserver?.unobserve(element);
36
+ }
37
+ /** Starts observing viewport resizes. Idempotent: the listener is added once. */
38
+ observeViewport() {
39
+ if (this.#observingViewport) return;
40
+ this.#observingViewport = true;
41
+ window.addEventListener("resize", this.#handleViewportResize);
42
+ }
43
+ /** Stops observing viewport resizes without affecting element observation. */
44
+ unobserveViewport() {
45
+ if (!this.#observingViewport) return;
46
+ this.#observingViewport = false;
47
+ window.removeEventListener("resize", this.#handleViewportResize);
48
+ }
49
+ /**
50
+ * Releases every observation: disconnects the {@link ResizeObserver} and
51
+ * removes the viewport listener. Safe to call multiple times. Call this from a
52
+ * controller's `disconnect()`.
53
+ */
54
+ disconnect() {
55
+ this.#resizeObserver?.disconnect();
56
+ this.#resizeObserver = null;
57
+ this.unobserveViewport();
58
+ }
59
+ };
60
+
3
61
  // src/controllers/textarea_autosize_controller.ts
4
62
  function px(value) {
5
63
  const n = Number.parseFloat(value);
@@ -13,18 +71,69 @@ var TextareaAutosizeController = class extends Controller {
13
71
  static actions = ["resize"];
14
72
  static events = ["resize"];
15
73
  #lastHeight = -1;
16
- #onInput = () => {
74
+ #lastWidth = -1;
75
+ #started = false;
76
+ #pending = false;
77
+ #fonts = null;
78
+ /**
79
+ * Content triggers arriving while the box is collapsed (`display: none`
80
+ * ancestors) are deferred: `scrollHeight` is 0 there, so measuring would
81
+ * clamp the kept height to the `minRows` floor and dispatch a bogus
82
+ * `resize`. The next report from a rendered box flushes the deferral.
83
+ */
84
+ #remeasure = () => {
85
+ if (this.element.clientWidth === 0) {
86
+ this.#pending = true;
87
+ return;
88
+ }
17
89
  this.resize();
18
90
  };
91
+ /**
92
+ * Re-measures when the content width changed (re-wrapping, including a
93
+ * horizontal padding change under `border-box`), the applied inline height
94
+ * was stripped (a Turbo morph syncing attributes from server HTML that has no
95
+ * `style`), or a content trigger was deferred while the box was collapsed.
96
+ * This instance's own height writes match none of these signals, so they
97
+ * cannot re-enter; an authored inline height (e.g. a user dragging the native
98
+ * resize handle) is left alone until the next content trigger. Reports from a
99
+ * still-collapsed box are skipped (nothing is measurable there).
100
+ */
101
+ #layout = new LayoutObserver(() => {
102
+ if (this.element.clientWidth === 0) return;
103
+ const width = this.#contentWidth();
104
+ const stripped = this.element.style.height === "";
105
+ if (width === this.#lastWidth && !stripped && !this.#pending) return;
106
+ this.#lastWidth = width;
107
+ this.resize();
108
+ });
19
109
  connect() {
20
- this.element.addEventListener("input", this.#onInput);
110
+ this.#lastHeight = Math.round(px(this.element.style.height));
111
+ this.#lastWidth = this.#contentWidth();
112
+ this.element.addEventListener("input", this.#remeasure);
113
+ this.element.addEventListener("change", this.#remeasure);
114
+ this.#layout.observe(this.element);
115
+ this.#bindFonts();
116
+ this.#started = true;
21
117
  this.resize();
22
118
  }
23
119
  disconnect() {
24
- this.element.removeEventListener("input", this.#onInput);
120
+ this.#started = false;
121
+ this.element.removeEventListener("input", this.#remeasure);
122
+ this.element.removeEventListener("change", this.#remeasure);
123
+ this.#layout.disconnect();
124
+ this.#unbindFonts();
125
+ }
126
+ /** Re-clamps when application code or a Turbo morph changes `minRows`. */
127
+ minRowsValueChanged() {
128
+ if (this.#started) this.#remeasure();
129
+ }
130
+ /** Re-clamps when application code or a Turbo morph changes `maxRows`. */
131
+ maxRowsValueChanged() {
132
+ if (this.#started) this.#remeasure();
25
133
  }
26
134
  /** Re-measures the content and applies the clamped height. */
27
135
  resize() {
136
+ this.#pending = false;
28
137
  const el = this.element;
29
138
  const style = window.getComputedStyle(el);
30
139
  const lineHeight = this.#lineHeight(style);
@@ -58,6 +167,11 @@ var TextareaAutosizeController = class extends Controller {
58
167
  this.dispatch("resize", { detail: { height, rows } });
59
168
  }
60
169
  }
170
+ /** Content-box width — the wrapping input, unaffected by this instance's height writes. */
171
+ #contentWidth() {
172
+ const style = window.getComputedStyle(this.element);
173
+ return Math.max(0, this.element.clientWidth - px(style.paddingLeft) - px(style.paddingRight));
174
+ }
61
175
  /** Resolved line height, falling back to ~1.2× font-size when `normal`. */
62
176
  #lineHeight(style) {
63
177
  const lh = px(style.lineHeight);
@@ -65,6 +179,20 @@ var TextareaAutosizeController = class extends Controller {
65
179
  const fontSize = px(style.fontSize);
66
180
  return fontSize > 0 ? fontSize * 1.2 : 16;
67
181
  }
182
+ /** Subscribes to font completion when the current engine exposes `document.fonts`. */
183
+ #bindFonts() {
184
+ const fonts = this.element.ownerDocument.fonts;
185
+ if (!fonts) return;
186
+ this.#fonts = fonts;
187
+ fonts.addEventListener("loadingdone", this.#remeasure);
188
+ fonts.addEventListener("loadingerror", this.#remeasure);
189
+ }
190
+ /** Removes the font completion subscriptions. */
191
+ #unbindFonts() {
192
+ this.#fonts?.removeEventListener("loadingdone", this.#remeasure);
193
+ this.#fonts?.removeEventListener("loadingerror", this.#remeasure);
194
+ this.#fonts = null;
195
+ }
68
196
  };
69
197
 
70
198
  export { TextareaAutosizeController };
@@ -23,15 +23,17 @@ function isReservedArrowChord(event, allow = []) {
23
23
  // src/utils/safe_storage.ts
24
24
  function readLocalStorage(key) {
25
25
  try {
26
- return window.localStorage.getItem(key);
27
- } catch {
28
- return null;
26
+ return { ok: true, value: window.localStorage.getItem(key) };
27
+ } catch (error) {
28
+ return { ok: false, error };
29
29
  }
30
30
  }
31
31
  function writeLocalStorage(key, value) {
32
32
  try {
33
33
  window.localStorage.setItem(key, value);
34
- } catch {
34
+ return { ok: true, value: void 0 };
35
+ } catch (error) {
36
+ return { ok: false, error };
35
37
  }
36
38
  }
37
39
 
@@ -169,8 +171,8 @@ var ThemeController = class extends Controller {
169
171
  }
170
172
  /** Reads a persisted, validated mode from `localStorage` (null when absent/blocked). */
171
173
  #readStored() {
172
- const value = readLocalStorage(this.storageKeyValue);
173
- return isMode(value) ? value : null;
174
+ const result = readLocalStorage(this.storageKeyValue);
175
+ return result.ok && isMode(result.value) ? result.value : null;
174
176
  }
175
177
  /** Persists the mode, swallowing storage errors (private mode / quota). */
176
178
  #writeStored(mode) {
@@ -24,7 +24,8 @@ function inheritsFieldsetDisabled(control) {
24
24
  return false;
25
25
  }
26
26
  function canTakeFocus(element) {
27
- if (element.closest("[hidden]")) return false;
27
+ if (element.closest("[hidden], [inert]")) return false;
28
+ if (element instanceof HTMLInputElement && element.type === "hidden") return false;
28
29
  if (!("disabled" in element)) return true;
29
30
  if (element.disabled) return false;
30
31
  return !inheritsFieldsetDisabled(element);