stimeo-ui 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +209 -0
  3. data/README.md +128 -1
  4. data/dist/cable/index.js +392 -417
  5. data/dist/controllers/accordion_controller.js +228 -28
  6. data/dist/controllers/alert_dialog_controller.js +862 -221
  7. data/dist/controllers/announcer_controller.js +135 -191
  8. data/dist/controllers/aspect_ratio_controller.js +0 -10
  9. data/dist/controllers/auto_submit_controller.js +295 -110
  10. data/dist/controllers/avatar_controller.js +172 -106
  11. data/dist/controllers/breadcrumb_controller.js +0 -129
  12. data/dist/controllers/bulk_select_controller.js +264 -53
  13. data/dist/controllers/calendar_controller.js +346 -180
  14. data/dist/controllers/carousel_controller.js +587 -300
  15. data/dist/controllers/character_counter_controller.js +271 -143
  16. data/dist/controllers/checkbox_controller.js +77 -75
  17. data/dist/controllers/clipboard_controller.js +187 -108
  18. data/dist/controllers/collapsible_controller.js +374 -108
  19. data/dist/controllers/color_picker_controller.js +183 -127
  20. data/dist/controllers/combobox_controller.js +426 -109
  21. data/dist/controllers/command_palette_controller.js +994 -350
  22. data/dist/controllers/conditional_fields_controller.js +212 -125
  23. data/dist/controllers/confirm_controller.js +939 -245
  24. data/dist/controllers/context_menu_controller.js +275 -127
  25. data/dist/controllers/count_up_controller.js +73 -21
  26. data/dist/controllers/countdown_controller.js +235 -97
  27. data/dist/controllers/currency_input_controller.js +241 -118
  28. data/dist/controllers/data_grid_controller.js +229 -135
  29. data/dist/controllers/date_range_picker_controller.js +359 -165
  30. data/dist/controllers/dialog_controller.js +887 -225
  31. data/dist/controllers/direct_upload_controller.js +80 -142
  32. data/dist/controllers/dirty_form_controller.js +26 -47
  33. data/dist/controllers/dismissible_controller.js +175 -20
  34. data/dist/controllers/drawer_controller.js +963 -336
  35. data/dist/controllers/dropdown_controller.js +342 -85
  36. data/dist/controllers/editable_controller.js +182 -58
  37. data/dist/controllers/empty_state_controller.js +67 -53
  38. data/dist/controllers/file_dropzone_controller.js +426 -229
  39. data/dist/controllers/filter_controller.js +78 -36
  40. data/dist/controllers/flash_controller.js +328 -211
  41. data/dist/controllers/focus_controller.js +654 -239
  42. data/dist/controllers/form_field_controller.js +153 -136
  43. data/dist/controllers/form_validation_controller.js +29 -96
  44. data/dist/controllers/frame_loading_controller.js +263 -198
  45. data/dist/controllers/highlight_controller.js +119 -70
  46. data/dist/controllers/hover_card_controller.js +335 -118
  47. data/dist/controllers/idle_controller.js +316 -65
  48. data/dist/controllers/input_mask_controller.js +116 -61
  49. data/dist/controllers/intersection_controller.js +147 -103
  50. data/dist/controllers/lazy_frame_controller.js +9 -56
  51. data/dist/controllers/listbox_controller.js +413 -153
  52. data/dist/controllers/local_time_controller.js +54 -64
  53. data/dist/controllers/masonry_controller.js +70 -95
  54. data/dist/controllers/menu_controller.js +254 -167
  55. data/dist/controllers/menubar_controller.js +206 -331
  56. data/dist/controllers/meter_controller.js +159 -71
  57. data/dist/controllers/multi_select_controller.js +797 -388
  58. data/dist/controllers/navigation_menu_controller.js +156 -231
  59. data/dist/controllers/nested_form_controller.js +373 -174
  60. data/dist/controllers/network_status_controller.js +241 -45
  61. data/dist/controllers/number_input_controller.js +387 -227
  62. data/dist/controllers/optimistic_controller.js +28 -75
  63. data/dist/controllers/otp_controller.js +344 -196
  64. data/dist/controllers/overflow_indicator_controller.js +167 -93
  65. data/dist/controllers/overflow_menu_controller.js +408 -277
  66. data/dist/controllers/pagination_controller.js +342 -120
  67. data/dist/controllers/password_reveal_controller.js +424 -99
  68. data/dist/controllers/password_strength_controller.js +99 -153
  69. data/dist/controllers/persist_controller.js +252 -96
  70. data/dist/controllers/pointer_drag_controller.js +263 -164
  71. data/dist/controllers/popover_controller.js +267 -94
  72. data/dist/controllers/portal_controller.js +209 -72
  73. data/dist/controllers/preview_guard_controller.js +243 -112
  74. data/dist/controllers/progress_controller.js +132 -59
  75. data/dist/controllers/radio_group_controller.js +138 -133
  76. data/dist/controllers/range_slider_controller.js +245 -112
  77. data/dist/controllers/rating_controller.js +429 -194
  78. data/dist/controllers/read_more_controller.js +185 -47
  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 +294 -118
  83. data/dist/controllers/roving_controller.js +39 -84
  84. data/dist/controllers/scroll_area_controller.js +428 -221
  85. data/dist/controllers/scroll_restore_controller.js +0 -53
  86. data/dist/controllers/scroll_visibility_controller.js +395 -146
  87. data/dist/controllers/scrollspy_controller.js +181 -236
  88. data/dist/controllers/separator_controller.js +288 -132
  89. data/dist/controllers/sidebar_controller.js +1071 -375
  90. data/dist/controllers/skeleton_controller.js +248 -92
  91. data/dist/controllers/slider_controller.js +211 -96
  92. data/dist/controllers/smart_sticky_header_controller.js +163 -54
  93. data/dist/controllers/sortable_controller.js +13 -110
  94. data/dist/controllers/spinner_controller.js +277 -161
  95. data/dist/controllers/step_indicator_controller.js +107 -53
  96. data/dist/controllers/stepper_controller.js +171 -48
  97. data/dist/controllers/stick_to_bottom_controller.js +310 -116
  98. data/dist/controllers/sticky_observer_controller.js +214 -49
  99. data/dist/controllers/submit_once_controller.js +294 -192
  100. data/dist/controllers/switch_controller.js +107 -43
  101. data/dist/controllers/tabs_controller.js +50 -25
  102. data/dist/controllers/tags_input_controller.js +290 -149
  103. data/dist/controllers/textarea_autosize_controller.js +71 -58
  104. data/dist/controllers/theme_controller.js +148 -80
  105. data/dist/controllers/time_picker_controller.js +162 -81
  106. data/dist/controllers/toast_controller.js +365 -193
  107. data/dist/controllers/toggle_group_controller.js +194 -99
  108. data/dist/controllers/toolbar_controller.js +92 -90
  109. data/dist/controllers/tooltip_controller.js +185 -125
  110. data/dist/controllers/transition_controller.js +123 -115
  111. data/dist/controllers/tree_view_controller.js +248 -290
  112. data/dist/index.js +8649 -8572
  113. data/dist/positioning/index.js +148 -45
  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,13 +98,177 @@ var LayoutObserver = class {
139
98
  }
140
99
  };
141
100
 
142
- // src/controllers/read_more_controller.ts
101
+ var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
102
+ var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
103
+ function stateReasonFor(event) {
104
+ if (!event) return "api";
105
+ if (FOCUS_EVENTS.has(event.type)) return "focus";
106
+ if (POINTER_EVENTS.has(event.type)) return "pointer";
107
+ return "user";
108
+ }
109
+
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
+ );
125
+ #whenTrue;
126
+ #whenFalse;
127
+ #record;
128
+ constructor(options, owner) {
129
+ this.#whenTrue = options.whenTrue;
130
+ this.#whenFalse = options.whenFalse ?? null;
131
+ this.#record = `data-${owner}-hidden-region`;
132
+ _StateRegions.#registry.names.add(this.#record);
133
+ }
134
+ reflect(host, isTrue) {
135
+ const shown = this.#inside(host, this.#whenTrue());
136
+ if (!this.#whenFalse) {
137
+ for (const region of shown) this.#write(region, !isTrue);
138
+ return;
139
+ }
140
+ const hiddenSide = this.#inside(host, this.#whenFalse());
141
+ if (shown.length === 0 || hiddenSide.length === 0) {
142
+ for (const region of shown) this.#give(region);
143
+ for (const region of hiddenSide) this.#give(region);
144
+ return;
145
+ }
146
+ for (const region of shown) this.#write(region, !isTrue);
147
+ for (const region of hiddenSide) this.#write(region, isTrue);
148
+ }
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
+ }
172
+ #write(region, 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);
179
+ }
180
+ #give(region) {
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
+ }
259
+ }
260
+ #inside(host, regions) {
261
+ return regions.filter((region) => host.contains(region));
262
+ }
263
+ };
264
+
143
265
  var ReadMoreController = class extends Controller {
144
- static targets = ["content", "trigger"];
266
+ static targets = ["content", "trigger", "expandedLabel", "collapsedLabel"];
145
267
  static values = {
146
268
  collapsed: { type: Boolean, default: true }
147
269
  };
148
270
  static actions = ["toggle"];
271
+ static events = ["close", "open"];
149
272
  #connected = false;
150
273
  #collapsed = true;
151
274
  #observedContent = null;
@@ -154,7 +277,13 @@ var ReadMoreController = class extends Controller {
154
277
  if (this.#connected) this.#evaluateOverflow();
155
278
  };
156
279
  #layout = new LayoutObserver(this.#update);
157
- /** 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
+ );
158
287
  #deferredHide = new BlurDeferral(() => {
159
288
  this.#update();
160
289
  });
@@ -179,14 +308,23 @@ var ReadMoreController = class extends Controller {
179
308
  }
180
309
  triggerTargetDisconnected(trigger) {
181
310
  this.#deferredHide.release(trigger);
311
+ if (!this.triggerTargets.includes(trigger)) this.#labels.release(trigger);
312
+ this.#syncTargets();
313
+ }
314
+ expandedLabelTargetConnected() {
315
+ this.#syncTargets();
316
+ }
317
+ collapsedLabelTargetConnected() {
182
318
  this.#syncTargets();
183
319
  }
184
- /** Toggles between the collapsed (clamped) and expanded states. */
185
- toggle() {
320
+ toggle(event) {
186
321
  if (!this.#connected) return;
187
322
  this.#collapsed = !this.#collapsed;
188
323
  this.#reflect();
189
324
  this.#evaluateOverflow();
325
+ const detail = { reason: stateReasonFor(event) };
326
+ if (this.#collapsed) this.dispatch("close", { detail, cancelable: false });
327
+ else this.dispatch("open", { detail, cancelable: false });
190
328
  }
191
329
  #initialCollapsed() {
192
330
  if (this.hasContentTarget) {
@@ -202,6 +340,7 @@ var ReadMoreController = class extends Controller {
202
340
  }
203
341
  if (this.hasTriggerTarget) {
204
342
  this.triggerTarget.setAttribute("aria-expanded", this.#collapsed ? "false" : "true");
343
+ this.#labels.reflect(this.triggerTarget, !this.#collapsed);
205
344
  }
206
345
  }
207
346
  #syncTargets() {
@@ -260,5 +399,4 @@ var ReadMoreController = class extends Controller {
260
399
  };
261
400
 
262
401
  export { ReadMoreController };
263
- //# sourceMappingURL=read_more_controller.js.map
264
402
  //# sourceMappingURL=read_more_controller.js.map