stimeo-ui 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +8 -1
  4. data/dist/cable/index.js +363 -436
  5. data/dist/controllers/accordion_controller.js +166 -55
  6. data/dist/controllers/alert_dialog_controller.js +862 -221
  7. data/dist/controllers/announcer_controller.js +135 -198
  8. data/dist/controllers/aspect_ratio_controller.js +0 -10
  9. data/dist/controllers/auto_submit_controller.js +271 -127
  10. data/dist/controllers/avatar_controller.js +171 -105
  11. data/dist/controllers/breadcrumb_controller.js +0 -130
  12. data/dist/controllers/bulk_select_controller.js +262 -52
  13. data/dist/controllers/calendar_controller.js +175 -209
  14. data/dist/controllers/carousel_controller.js +461 -399
  15. data/dist/controllers/character_counter_controller.js +246 -156
  16. data/dist/controllers/checkbox_controller.js +32 -74
  17. data/dist/controllers/clipboard_controller.js +168 -147
  18. data/dist/controllers/collapsible_controller.js +301 -120
  19. data/dist/controllers/color_picker_controller.js +149 -139
  20. data/dist/controllers/combobox_controller.js +360 -133
  21. data/dist/controllers/command_palette_controller.js +970 -358
  22. data/dist/controllers/conditional_fields_controller.js +166 -124
  23. data/dist/controllers/confirm_controller.js +939 -248
  24. data/dist/controllers/context_menu_controller.js +256 -128
  25. data/dist/controllers/count_up_controller.js +73 -21
  26. data/dist/controllers/countdown_controller.js +183 -148
  27. data/dist/controllers/currency_input_controller.js +134 -158
  28. data/dist/controllers/data_grid_controller.js +114 -186
  29. data/dist/controllers/date_range_picker_controller.js +288 -206
  30. data/dist/controllers/dialog_controller.js +858 -223
  31. data/dist/controllers/direct_upload_controller.js +73 -153
  32. data/dist/controllers/dirty_form_controller.js +14 -48
  33. data/dist/controllers/dismissible_controller.js +175 -21
  34. data/dist/controllers/drawer_controller.js +933 -341
  35. data/dist/controllers/dropdown_controller.js +312 -82
  36. data/dist/controllers/editable_controller.js +158 -68
  37. data/dist/controllers/empty_state_controller.js +67 -53
  38. data/dist/controllers/file_dropzone_controller.js +311 -207
  39. data/dist/controllers/filter_controller.js +67 -39
  40. data/dist/controllers/flash_controller.js +172 -314
  41. data/dist/controllers/focus_controller.js +654 -240
  42. data/dist/controllers/form_field_controller.js +147 -132
  43. data/dist/controllers/form_validation_controller.js +13 -86
  44. data/dist/controllers/frame_loading_controller.js +264 -213
  45. data/dist/controllers/highlight_controller.js +107 -75
  46. data/dist/controllers/hover_card_controller.js +309 -118
  47. data/dist/controllers/idle_controller.js +267 -101
  48. data/dist/controllers/input_mask_controller.js +71 -72
  49. data/dist/controllers/intersection_controller.js +147 -106
  50. data/dist/controllers/lazy_frame_controller.js +9 -65
  51. data/dist/controllers/listbox_controller.js +306 -204
  52. data/dist/controllers/local_time_controller.js +54 -64
  53. data/dist/controllers/masonry_controller.js +70 -93
  54. data/dist/controllers/menu_controller.js +214 -156
  55. data/dist/controllers/menubar_controller.js +170 -329
  56. data/dist/controllers/meter_controller.js +151 -67
  57. data/dist/controllers/multi_select_controller.js +593 -358
  58. data/dist/controllers/navigation_menu_controller.js +124 -232
  59. data/dist/controllers/nested_form_controller.js +352 -182
  60. data/dist/controllers/network_status_controller.js +242 -54
  61. data/dist/controllers/number_input_controller.js +320 -263
  62. data/dist/controllers/optimistic_controller.js +16 -76
  63. data/dist/controllers/otp_controller.js +198 -193
  64. data/dist/controllers/overflow_indicator_controller.js +116 -113
  65. data/dist/controllers/overflow_menu_controller.js +343 -283
  66. data/dist/controllers/pagination_controller.js +295 -119
  67. data/dist/controllers/password_reveal_controller.js +389 -121
  68. data/dist/controllers/password_strength_controller.js +99 -153
  69. data/dist/controllers/persist_controller.js +237 -103
  70. data/dist/controllers/pointer_drag_controller.js +185 -165
  71. data/dist/controllers/popover_controller.js +236 -97
  72. data/dist/controllers/portal_controller.js +209 -76
  73. data/dist/controllers/preview_guard_controller.js +232 -116
  74. data/dist/controllers/progress_controller.js +125 -56
  75. data/dist/controllers/radio_group_controller.js +117 -137
  76. data/dist/controllers/range_slider_controller.js +192 -105
  77. data/dist/controllers/rating_controller.js +418 -207
  78. data/dist/controllers/read_more_controller.js +134 -63
  79. data/dist/controllers/reading_progress_controller.js +147 -135
  80. data/dist/controllers/relative_time_controller.js +145 -110
  81. data/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  82. data/dist/controllers/resizable_controller.js +255 -139
  83. data/dist/controllers/roving_controller.js +39 -84
  84. data/dist/controllers/scroll_area_controller.js +428 -225
  85. data/dist/controllers/scroll_restore_controller.js +0 -53
  86. data/dist/controllers/scroll_visibility_controller.js +395 -145
  87. data/dist/controllers/scrollspy_controller.js +181 -242
  88. data/dist/controllers/separator_controller.js +267 -140
  89. data/dist/controllers/sidebar_controller.js +1023 -386
  90. data/dist/controllers/skeleton_controller.js +248 -97
  91. data/dist/controllers/slider_controller.js +159 -79
  92. data/dist/controllers/smart_sticky_header_controller.js +162 -63
  93. data/dist/controllers/sortable_controller.js +0 -112
  94. data/dist/controllers/spinner_controller.js +277 -169
  95. data/dist/controllers/step_indicator_controller.js +99 -46
  96. data/dist/controllers/stepper_controller.js +114 -73
  97. data/dist/controllers/stick_to_bottom_controller.js +262 -138
  98. data/dist/controllers/sticky_observer_controller.js +214 -49
  99. data/dist/controllers/submit_once_controller.js +294 -205
  100. data/dist/controllers/switch_controller.js +33 -60
  101. data/dist/controllers/tabs_controller.js +35 -29
  102. data/dist/controllers/tags_input_controller.js +136 -145
  103. data/dist/controllers/textarea_autosize_controller.js +71 -58
  104. data/dist/controllers/theme_controller.js +130 -112
  105. data/dist/controllers/time_picker_controller.js +146 -80
  106. data/dist/controllers/toast_controller.js +186 -297
  107. data/dist/controllers/toggle_group_controller.js +95 -136
  108. data/dist/controllers/toolbar_controller.js +68 -98
  109. data/dist/controllers/tooltip_controller.js +158 -124
  110. data/dist/controllers/transition_controller.js +123 -119
  111. data/dist/controllers/tree_view_controller.js +150 -345
  112. data/dist/index.js +6370 -9346
  113. data/dist/positioning/index.js +148 -47
  114. data/lib/stimeo/ui/version.rb +1 -1
  115. metadata +3 -3
@@ -1,30 +1,20 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/read_more_controller.ts
4
-
5
- // src/utils/blur_deferral.ts
6
3
  var BlurDeferral = class {
7
- /** Elements currently holding an update back, mapped to their `blur` listener. */
8
4
  #pending = /* @__PURE__ */ new Map();
9
- /** Called after a pending element blurs and has been detached. */
10
5
  #onRelease;
11
- /** @param onRelease - Invoked once `element` actually blurs; never on `release`. */
12
6
  constructor(onRelease) {
13
7
  this.#onRelease = onRelease;
14
8
  }
15
- /** Number of elements currently holding an update back. */
16
9
  get size() {
17
10
  return this.#pending.size;
18
11
  }
19
- /** Snapshot of the pending elements, safe to iterate while releasing them. */
20
12
  get elements() {
21
13
  return [...this.#pending.keys()];
22
14
  }
23
- /** Whether `element` is currently holding an update back. */
24
15
  has(element) {
25
16
  return this.#pending.has(element);
26
17
  }
27
- /** Holds an update back until `element` blurs. Idempotent (no stacked listeners). */
28
18
  defer(element) {
29
19
  if (this.#pending.has(element)) return;
30
20
  const onBlur = () => {
@@ -34,22 +24,18 @@ var BlurDeferral = class {
34
24
  this.#pending.set(element, onBlur);
35
25
  element.addEventListener("blur", onBlur);
36
26
  }
37
- /** Defers `element` as the only pending entry, cancelling any others. */
38
27
  deferOnly(element) {
39
28
  for (const pending of this.elements) {
40
29
  if (pending !== element) this.#detach(pending);
41
30
  }
42
31
  this.defer(element);
43
32
  }
44
- /** Cancels `element`'s deferral without completing it; no-ops when not pending. */
45
33
  release(element) {
46
34
  this.#detach(element);
47
35
  }
48
- /** Cancels every deferral without completing any of them. */
49
36
  releaseAll() {
50
37
  for (const element of this.elements) this.#detach(element);
51
38
  }
52
- /** Removes the `blur` listener for `element` and forgets it. */
53
39
  #detach(element) {
54
40
  const onBlur = this.#pending.get(element);
55
41
  if (onBlur) element.removeEventListener("blur", onBlur);
@@ -57,18 +43,15 @@ var BlurDeferral = class {
57
43
  }
58
44
  };
59
45
 
60
- // src/utils/layout_observer.ts
61
46
  var LayoutObserver = class {
62
47
  #callback;
63
48
  #resizeObserverFactory;
64
49
  #resizeObserver = null;
65
50
  #observingViewport = false;
66
51
  #loadContainer = null;
67
- /** Stable bound handler so add/removeEventListener target the same reference. */
68
52
  #handleViewportResize = () => {
69
53
  this.#callback();
70
54
  };
71
- /** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */
72
55
  #handleDescendantLoad = () => {
73
56
  this.#callback();
74
57
  };
@@ -76,11 +59,6 @@ var LayoutObserver = class {
76
59
  this.#callback = callback;
77
60
  this.#resizeObserverFactory = options.resizeObserverFactory ?? (typeof ResizeObserver === "undefined" ? null : (cb) => new ResizeObserver(cb));
78
61
  }
79
- /**
80
- * Starts observing an element's size. Repeated calls observe additional
81
- * elements through the same shared observer. No-ops when no
82
- * `ResizeObserver` implementation is available.
83
- */
84
62
  observe(element) {
85
63
  if (!this.#resizeObserverFactory) return;
86
64
  if (!this.#resizeObserver) {
@@ -90,47 +68,28 @@ var LayoutObserver = class {
90
68
  }
91
69
  this.#resizeObserver.observe(element);
92
70
  }
93
- /** Stops observing a single element while leaving any others in place. */
94
71
  unobserve(element) {
95
72
  this.#resizeObserver?.unobserve(element);
96
73
  }
97
- /** Starts observing viewport resizes. Idempotent: the listener is added once. */
98
74
  observeViewport() {
99
75
  if (this.#observingViewport) return;
100
76
  this.#observingViewport = true;
101
77
  window.addEventListener("resize", this.#handleViewportResize);
102
78
  }
103
- /** Stops observing viewport resizes without affecting element observation. */
104
79
  unobserveViewport() {
105
80
  if (!this.#observingViewport) return;
106
81
  this.#observingViewport = false;
107
82
  window.removeEventListener("resize", this.#handleViewportResize);
108
83
  }
109
- /**
110
- * Starts reporting a `load` from anywhere inside `container` — an image or a
111
- * frame settling changes the box it sits in, and it measures as zero high until
112
- * then. `load` does not bubble, so the subscription is a capture-phase listener
113
- * on the container itself and nothing the caller spells.
114
- *
115
- * **One container at a time.** A further call moves the observation, so a widget
116
- * whose content element is swapped at runtime releases the element it let go by
117
- * naming the new one — there is no second place for the release to drift from.
118
- */
119
84
  observeDescendantLoads(container) {
120
85
  this.unobserveDescendantLoads();
121
86
  this.#loadContainer = container;
122
87
  container.addEventListener("load", this.#handleDescendantLoad, true);
123
88
  }
124
- /** Stops reporting descendant loads without affecting element or viewport observation. */
125
89
  unobserveDescendantLoads() {
126
90
  this.#loadContainer?.removeEventListener("load", this.#handleDescendantLoad, true);
127
91
  this.#loadContainer = null;
128
92
  }
129
- /**
130
- * Releases every observation: disconnects the {@link ResizeObserver} and removes
131
- * the viewport and descendant-load listeners. Safe to call multiple times. Call
132
- * this from a controller's `disconnect()`.
133
- */
134
93
  disconnect() {
135
94
  this.#resizeObserver?.disconnect();
136
95
  this.#resizeObserver = null;
@@ -139,7 +98,6 @@ var LayoutObserver = class {
139
98
  }
140
99
  };
141
100
 
142
- // src/utils/state_reason.ts
143
101
  var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
144
102
  var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
145
103
  function stateReasonFor(event) {
@@ -149,16 +107,30 @@ function stateReasonFor(event) {
149
107
  return "user";
150
108
  }
151
109
 
152
- // src/utils/state_regions.ts
153
- var StateRegions = class {
110
+ function sharedRegistry(key, create) {
111
+ const symbol = Symbol.for(key);
112
+ const scope = globalThis;
113
+ const existing = scope[symbol];
114
+ if (existing !== void 0) return existing;
115
+ const registry = create();
116
+ Object.defineProperty(globalThis, symbol, { value: registry });
117
+ return registry;
118
+ }
119
+
120
+ var StateRegions = class _StateRegions {
121
+ static #registry = sharedRegistry(
122
+ "stimeo-ui.state-regions.registry.v1",
123
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
124
+ );
154
125
  #whenTrue;
155
126
  #whenFalse;
156
- #taken = /* @__PURE__ */ new WeakSet();
157
- constructor(options) {
127
+ #record;
128
+ constructor(options, owner) {
158
129
  this.#whenTrue = options.whenTrue;
159
130
  this.#whenFalse = options.whenFalse ?? null;
131
+ this.#record = `data-${owner}-hidden-region`;
132
+ _StateRegions.#registry.names.add(this.#record);
160
133
  }
161
- /** Shows the regions inside `host` that belong to `isTrue` and hides the others. */
162
134
  reflect(host, isTrue) {
163
135
  const shown = this.#inside(host, this.#whenTrue());
164
136
  if (!this.#whenFalse) {
@@ -174,23 +146,122 @@ var StateRegions = class {
174
146
  for (const region of shown) this.#write(region, !isTrue);
175
147
  for (const region of hiddenSide) this.#write(region, isTrue);
176
148
  }
177
- /** Writes `hidden` where it moves, noting which regions it takes out of view. */
149
+ release(host) {
150
+ for (const region of [host, ...host.querySelectorAll("*")]) {
151
+ if (!(region instanceof HTMLElement)) continue;
152
+ const stack = this.#held(region);
153
+ const hold = stack && this.#take(stack);
154
+ if (!stack || !hold) continue;
155
+ const owns = region.hidden === hold.written && !this.#covered(region, stack, hold);
156
+ const index = stack.indexOf(hold);
157
+ stack.splice(index, 1);
158
+ const above = stack[index];
159
+ if (above) above.linked &&= hold.linked;
160
+ if (owns) {
161
+ const below = hold.linked ? stack[index - 1] : void 0;
162
+ if (below) {
163
+ if (region.hidden !== below.written) region.hidden = below.written;
164
+ } else if (region.getAttribute("hidden") !== hold.authored) {
165
+ if (hold.authored === null) region.removeAttribute("hidden");
166
+ else region.setAttribute("hidden", hold.authored);
167
+ }
168
+ }
169
+ this.#mark(region, stack);
170
+ }
171
+ }
178
172
  #write(region, hidden) {
179
- if (hidden) this.#taken.add(region);
180
- else this.#taken.delete(region);
181
- if (region.hidden !== hidden) region.hidden = hidden;
173
+ const stack = this.#stack(region);
174
+ const hold = this.#take(stack) ?? this.#acquire(region, stack);
175
+ const shows = !this.#covered(region, stack, hold);
176
+ hold.written = hidden;
177
+ if (shows && region.hidden !== hidden) region.hidden = hidden;
178
+ this.#mark(region, stack);
182
179
  }
183
- /** Returns a region this instance hid; one it never hid keeps what it carries. */
184
180
  #give(region) {
185
- if (this.#taken.delete(region)) region.hidden = false;
181
+ const stack = this.#held(region);
182
+ if (stack && this.#take(stack)?.written) this.#write(region, false);
183
+ }
184
+ #held(region) {
185
+ return _StateRegions.#registry.stacks.has(region) || region.hasAttribute(this.#record) ? this.#stack(region) : void 0;
186
+ }
187
+ #stack(region) {
188
+ const stacks = _StateRegions.#registry.stacks;
189
+ const stack = stacks.get(region) ?? this.#seed(region);
190
+ stacks.set(region, stack);
191
+ return stack;
192
+ }
193
+ #seed(region) {
194
+ const found = [];
195
+ for (const record of _StateRegions.#registry.names) {
196
+ const read = this.#recorded(region, record);
197
+ if (read) found.push({ ...read, record });
198
+ }
199
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
200
+ return found.sort((one, other) => depth(one) - depth(other)).map(({ record, authored, written, linked }, index) => ({
201
+ instance: null,
202
+ record,
203
+ authored,
204
+ written,
205
+ linked: linked && index > 0
206
+ }));
207
+ }
208
+ #take(stack) {
209
+ const hold = stack.find((held) => held.instance === this) ?? stack.find((held) => held.instance === null && held.record === this.#record);
210
+ if (hold) hold.instance = this;
211
+ return hold;
212
+ }
213
+ #covered(region, stack, hold) {
214
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === region.hidden);
215
+ }
216
+ #acquire(region, stack) {
217
+ const top = stack.at(-1);
218
+ const over = top && region.hidden === top.written ? top : void 0;
219
+ const hold = {
220
+ instance: this,
221
+ record: this.#record,
222
+ authored: over ? over.authored : region.getAttribute("hidden"),
223
+ written: false,
224
+ linked: over !== void 0
225
+ };
226
+ stack.push(hold);
227
+ return hold;
228
+ }
229
+ #mark(region, stack) {
230
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
231
+ stack.forEach((hold, index) => {
232
+ const names = stack.slice(0, index).map((under) => under.record);
233
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
234
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
235
+ records.set(
236
+ hold.record,
237
+ region.getAttribute("hidden") === hold.authored && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.authored, hold.written, ...marker, ...beneath])
238
+ );
239
+ });
240
+ for (const [name, recorded] of records) {
241
+ if (region.getAttribute(name) === recorded) continue;
242
+ if (recorded === null) region.removeAttribute(name);
243
+ else region.setAttribute(name, recorded);
244
+ }
245
+ }
246
+ #recorded(region, name) {
247
+ try {
248
+ const parsed = JSON.parse(region.getAttribute(name) ?? "null");
249
+ if (!Array.isArray(parsed)) return null;
250
+ const [authored, written, ...rest] = parsed;
251
+ if (authored !== null && typeof authored !== "string") return null;
252
+ const linked = rest[0] !== false;
253
+ const beneath = linked ? rest : rest.slice(1);
254
+ if (!beneath.every((under) => typeof under === "string")) return null;
255
+ return typeof written === "boolean" ? { authored, written, linked, beneath } : null;
256
+ } catch {
257
+ return null;
258
+ }
186
259
  }
187
- /** The declared regions that sit within `host`, which may be the host itself. */
188
260
  #inside(host, regions) {
189
261
  return regions.filter((region) => host.contains(region));
190
262
  }
191
263
  };
192
264
 
193
- // src/controllers/read_more_controller.ts
194
265
  var ReadMoreController = class extends Controller {
195
266
  static targets = ["content", "trigger", "expandedLabel", "collapsedLabel"];
196
267
  static values = {
@@ -206,12 +277,13 @@ var ReadMoreController = class extends Controller {
206
277
  if (this.#connected) this.#evaluateOverflow();
207
278
  };
208
279
  #layout = new LayoutObserver(this.#update);
209
- /** Owns `hidden` on the trigger's two labels. */
210
- #labels = new StateRegions({
211
- whenTrue: () => this.expandedLabelTargets,
212
- whenFalse: () => this.collapsedLabelTargets
213
- });
214
- /** Holds the trigger's hide back while it has focus; re-evaluates on blur. */
280
+ #labels = new StateRegions(
281
+ {
282
+ whenTrue: () => this.expandedLabelTargets,
283
+ whenFalse: () => this.collapsedLabelTargets
284
+ },
285
+ this.identifier
286
+ );
215
287
  #deferredHide = new BlurDeferral(() => {
216
288
  this.#update();
217
289
  });
@@ -236,6 +308,7 @@ var ReadMoreController = class extends Controller {
236
308
  }
237
309
  triggerTargetDisconnected(trigger) {
238
310
  this.#deferredHide.release(trigger);
311
+ if (!this.triggerTargets.includes(trigger)) this.#labels.release(trigger);
239
312
  this.#syncTargets();
240
313
  }
241
314
  expandedLabelTargetConnected() {
@@ -244,7 +317,6 @@ var ReadMoreController = class extends Controller {
244
317
  collapsedLabelTargetConnected() {
245
318
  this.#syncTargets();
246
319
  }
247
- /** Toggles between the collapsed (clamped) and expanded states. */
248
320
  toggle(event) {
249
321
  if (!this.#connected) return;
250
322
  this.#collapsed = !this.#collapsed;
@@ -327,5 +399,4 @@ var ReadMoreController = class extends Controller {
327
399
  };
328
400
 
329
401
  export { ReadMoreController };
330
- //# sourceMappingURL=read_more_controller.js.map
331
402
  //# sourceMappingURL=read_more_controller.js.map