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.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +136 -0
  3. data/README.md +27 -11
  4. data/dist/cable/index.js +384 -449
  5. data/dist/controllers/accordion_controller.js +174 -55
  6. data/dist/controllers/alert_dialog_controller.js +903 -235
  7. data/dist/controllers/announcer_controller.js +238 -205
  8. data/dist/controllers/aspect_ratio_controller.js +0 -10
  9. data/dist/controllers/auto_submit_controller.js +414 -145
  10. data/dist/controllers/avatar_controller.js +185 -107
  11. data/dist/controllers/breadcrumb_controller.js +0 -130
  12. data/dist/controllers/bulk_select_controller.js +282 -58
  13. data/dist/controllers/calendar_controller.js +228 -260
  14. data/dist/controllers/carousel_controller.js +536 -436
  15. data/dist/controllers/character_counter_controller.js +493 -207
  16. data/dist/controllers/checkbox_controller.js +38 -74
  17. data/dist/controllers/clipboard_controller.js +261 -149
  18. data/dist/controllers/collapsible_controller.js +304 -120
  19. data/dist/controllers/color_picker_controller.js +178 -146
  20. data/dist/controllers/combobox_controller.js +522 -156
  21. data/dist/controllers/command_palette_controller.js +1156 -395
  22. data/dist/controllers/conditional_fields_controller.js +300 -171
  23. data/dist/controllers/confirm_controller.js +980 -262
  24. data/dist/controllers/context_menu_controller.js +346 -153
  25. data/dist/controllers/count_up_controller.js +73 -21
  26. data/dist/controllers/countdown_controller.js +281 -158
  27. data/dist/controllers/currency_input_controller.js +575 -225
  28. data/dist/controllers/data_grid_controller.js +248 -214
  29. data/dist/controllers/date_range_picker_controller.js +338 -235
  30. data/dist/controllers/dialog_controller.js +899 -237
  31. data/dist/controllers/direct_upload_controller.js +97 -167
  32. data/dist/controllers/dirty_form_controller.js +72 -69
  33. data/dist/controllers/dismissible_controller.js +196 -24
  34. data/dist/controllers/drawer_controller.js +974 -355
  35. data/dist/controllers/dropdown_controller.js +339 -96
  36. data/dist/controllers/editable_controller.js +315 -88
  37. data/dist/controllers/empty_state_controller.js +79 -58
  38. data/dist/controllers/file_dropzone_controller.js +322 -211
  39. data/dist/controllers/filter_controller.js +77 -42
  40. data/dist/controllers/flash_controller.js +238 -347
  41. data/dist/controllers/focus_controller.js +679 -240
  42. data/dist/controllers/form_field_controller.js +156 -132
  43. data/dist/controllers/form_validation_controller.js +31 -97
  44. data/dist/controllers/frame_loading_controller.js +275 -217
  45. data/dist/controllers/highlight_controller.js +107 -75
  46. data/dist/controllers/hover_card_controller.js +327 -130
  47. data/dist/controllers/idle_controller.js +271 -101
  48. data/dist/controllers/input_mask_controller.js +483 -97
  49. data/dist/controllers/intersection_controller.js +150 -106
  50. data/dist/controllers/lazy_frame_controller.js +24 -68
  51. data/dist/controllers/listbox_controller.js +523 -240
  52. data/dist/controllers/local_time_controller.js +102 -77
  53. data/dist/controllers/masonry_controller.js +80 -96
  54. data/dist/controllers/menu_controller.js +302 -188
  55. data/dist/controllers/menubar_controller.js +232 -345
  56. data/dist/controllers/meter_controller.js +417 -87
  57. data/dist/controllers/multi_select_controller.js +785 -393
  58. data/dist/controllers/navigation_menu_controller.js +190 -250
  59. data/dist/controllers/nested_form_controller.js +396 -192
  60. data/dist/controllers/network_status_controller.js +268 -62
  61. data/dist/controllers/number_input_controller.js +610 -303
  62. data/dist/controllers/optimistic_controller.js +45 -87
  63. data/dist/controllers/otp_controller.js +658 -255
  64. data/dist/controllers/overflow_indicator_controller.js +122 -113
  65. data/dist/controllers/overflow_menu_controller.js +470 -295
  66. data/dist/controllers/pagination_controller.js +332 -123
  67. data/dist/controllers/password_reveal_controller.js +392 -121
  68. data/dist/controllers/password_strength_controller.js +111 -158
  69. data/dist/controllers/persist_controller.js +165 -111
  70. data/dist/controllers/pointer_drag_controller.js +434 -188
  71. data/dist/controllers/popover_controller.js +269 -113
  72. data/dist/controllers/portal_controller.js +215 -76
  73. data/dist/controllers/preview_guard_controller.js +243 -116
  74. data/dist/controllers/progress_controller.js +391 -78
  75. data/dist/controllers/radio_group_controller.js +278 -211
  76. data/dist/controllers/range_slider_controller.js +220 -113
  77. data/dist/controllers/rating_controller.js +458 -211
  78. data/dist/controllers/read_more_controller.js +138 -63
  79. data/dist/controllers/reading_progress_controller.js +166 -137
  80. data/dist/controllers/relative_time_controller.js +211 -112
  81. data/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
  82. data/dist/controllers/resizable_controller.js +299 -150
  83. data/dist/controllers/roving_controller.js +49 -88
  84. data/dist/controllers/scroll_area_controller.js +490 -246
  85. data/dist/controllers/scroll_restore_controller.js +14 -55
  86. data/dist/controllers/scroll_visibility_controller.js +432 -149
  87. data/dist/controllers/scrollspy_controller.js +193 -248
  88. data/dist/controllers/separator_controller.js +285 -142
  89. data/dist/controllers/sidebar_controller.js +1069 -404
  90. data/dist/controllers/skeleton_controller.js +259 -101
  91. data/dist/controllers/slider_controller.js +187 -86
  92. data/dist/controllers/smart_sticky_header_controller.js +173 -66
  93. data/dist/controllers/sortable_controller.js +26 -125
  94. data/dist/controllers/spinner_controller.js +288 -173
  95. data/dist/controllers/step_indicator_controller.js +105 -46
  96. data/dist/controllers/stepper_controller.js +120 -73
  97. data/dist/controllers/stick_to_bottom_controller.js +275 -140
  98. data/dist/controllers/sticky_observer_controller.js +220 -49
  99. data/dist/controllers/submit_once_controller.js +376 -227
  100. data/dist/controllers/switch_controller.js +201 -84
  101. data/dist/controllers/tabs_controller.js +41 -30
  102. data/dist/controllers/tags_input_controller.js +293 -173
  103. data/dist/controllers/textarea_autosize_controller.js +108 -74
  104. data/dist/controllers/theme_controller.js +140 -116
  105. data/dist/controllers/time_picker_controller.js +331 -127
  106. data/dist/controllers/toast_controller.js +244 -324
  107. data/dist/controllers/toggle_group_controller.js +252 -191
  108. data/dist/controllers/toolbar_controller.js +219 -120
  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 +283 -369
  112. data/dist/index.js +8573 -10410
  113. data/dist/positioning/index.js +154 -47
  114. data/lib/generators/stimeo/install/install_generator.rb +7 -2
  115. data/lib/generators/stimeo/install/templates/stimeo.js +19 -6
  116. data/lib/stimeo/ui/version.rb +1 -1
  117. 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,37 @@ function stateReasonFor(event) {
149
107
  return "user";
150
108
  }
151
109
 
152
- // src/utils/state_regions.ts
153
- var StateRegions = class {
110
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
111
+ if (element.getAttribute(name) === value) return false;
112
+ if (value === null) element.removeAttribute(name);
113
+ else element.setAttribute(name, value);
114
+ return true;
115
+ }
116
+
117
+ function sharedRegistry(key, create) {
118
+ const symbol = Symbol.for(key);
119
+ const scope = globalThis;
120
+ const existing = scope[symbol];
121
+ if (existing !== void 0) return existing;
122
+ const registry = create();
123
+ Object.defineProperty(globalThis, symbol, { value: registry });
124
+ return registry;
125
+ }
126
+
127
+ var StateRegions = class _StateRegions {
128
+ static #registry = sharedRegistry(
129
+ "stimeo-ui.state-regions.registry.v1",
130
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
131
+ );
154
132
  #whenTrue;
155
133
  #whenFalse;
156
- #taken = /* @__PURE__ */ new WeakSet();
157
- constructor(options) {
134
+ #record;
135
+ constructor(options, owner) {
158
136
  this.#whenTrue = options.whenTrue;
159
137
  this.#whenFalse = options.whenFalse ?? null;
138
+ this.#record = `data-${owner}-hidden-region`;
139
+ _StateRegions.#registry.names.add(this.#record);
160
140
  }
161
- /** Shows the regions inside `host` that belong to `isTrue` and hides the others. */
162
141
  reflect(host, isTrue) {
163
142
  const shown = this.#inside(host, this.#whenTrue());
164
143
  if (!this.#whenFalse) {
@@ -174,23 +153,119 @@ var StateRegions = class {
174
153
  for (const region of shown) this.#write(region, !isTrue);
175
154
  for (const region of hiddenSide) this.#write(region, isTrue);
176
155
  }
177
- /** Writes `hidden` where it moves, noting which regions it takes out of view. */
156
+ release(host) {
157
+ for (const region of [host, ...host.querySelectorAll("*")]) {
158
+ if (!(region instanceof HTMLElement)) continue;
159
+ const stack = this.#held(region);
160
+ const hold = stack && this.#take(stack);
161
+ if (!stack || !hold) continue;
162
+ const owns = region.hidden === hold.written && !this.#covered(region, stack, hold);
163
+ const index = stack.indexOf(hold);
164
+ stack.splice(index, 1);
165
+ const above = stack[index];
166
+ if (above) above.linked &&= hold.linked;
167
+ if (owns) {
168
+ const below = hold.linked ? stack[index - 1] : void 0;
169
+ if (below) {
170
+ if (region.hidden !== below.written) region.hidden = below.written;
171
+ } else {
172
+ setAttributeIfChanged(region, "hidden", hold.authored);
173
+ }
174
+ }
175
+ this.#mark(region, stack);
176
+ }
177
+ }
178
178
  #write(region, hidden) {
179
- if (hidden) this.#taken.add(region);
180
- else this.#taken.delete(region);
181
- if (region.hidden !== hidden) region.hidden = hidden;
179
+ const stack = this.#stack(region);
180
+ const hold = this.#take(stack) ?? this.#acquire(region, stack);
181
+ const shows = !this.#covered(region, stack, hold);
182
+ hold.written = hidden;
183
+ if (shows && region.hidden !== hidden) region.hidden = hidden;
184
+ this.#mark(region, stack);
182
185
  }
183
- /** Returns a region this instance hid; one it never hid keeps what it carries. */
184
186
  #give(region) {
185
- if (this.#taken.delete(region)) region.hidden = false;
187
+ const stack = this.#held(region);
188
+ if (stack && this.#take(stack)?.written) this.#write(region, false);
189
+ }
190
+ #held(region) {
191
+ return _StateRegions.#registry.stacks.has(region) || region.hasAttribute(this.#record) ? this.#stack(region) : void 0;
192
+ }
193
+ #stack(region) {
194
+ const stacks = _StateRegions.#registry.stacks;
195
+ const stack = stacks.get(region) ?? this.#seed(region);
196
+ stacks.set(region, stack);
197
+ return stack;
198
+ }
199
+ #seed(region) {
200
+ const found = [];
201
+ for (const record of _StateRegions.#registry.names) {
202
+ const read = this.#recorded(region, record);
203
+ if (read) found.push({ ...read, record });
204
+ }
205
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
206
+ return found.sort((one, other) => depth(one) - depth(other)).map(({ record, authored, written, linked }, index) => ({
207
+ instance: null,
208
+ record,
209
+ authored,
210
+ written,
211
+ linked: linked && index > 0
212
+ }));
213
+ }
214
+ #take(stack) {
215
+ const hold = stack.find((held) => held.instance === this) ?? stack.find((held) => held.instance === null && held.record === this.#record);
216
+ if (hold) hold.instance = this;
217
+ return hold;
218
+ }
219
+ #covered(region, stack, hold) {
220
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === region.hidden);
221
+ }
222
+ #acquire(region, stack) {
223
+ const top = stack.at(-1);
224
+ const over = top && region.hidden === top.written ? top : void 0;
225
+ const hold = {
226
+ instance: this,
227
+ record: this.#record,
228
+ authored: over ? over.authored : region.getAttribute("hidden"),
229
+ written: false,
230
+ linked: over !== void 0
231
+ };
232
+ stack.push(hold);
233
+ return hold;
234
+ }
235
+ #mark(region, stack) {
236
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
237
+ stack.forEach((hold, index) => {
238
+ const names = stack.slice(0, index).map((under) => under.record);
239
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
240
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
241
+ records.set(
242
+ hold.record,
243
+ region.getAttribute("hidden") === hold.authored && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.authored, hold.written, ...marker, ...beneath])
244
+ );
245
+ });
246
+ for (const [name, recorded] of records) {
247
+ setAttributeIfChanged(region, name, recorded);
248
+ }
249
+ }
250
+ #recorded(region, name) {
251
+ try {
252
+ const parsed = JSON.parse(region.getAttribute(name) ?? "null");
253
+ if (!Array.isArray(parsed)) return null;
254
+ const [authored, written, ...rest] = parsed;
255
+ if (authored !== null && typeof authored !== "string") return null;
256
+ const linked = rest[0] !== false;
257
+ const beneath = linked ? rest : rest.slice(1);
258
+ if (!beneath.every((under) => typeof under === "string")) return null;
259
+ return typeof written === "boolean" ? { authored, written, linked, beneath } : null;
260
+ } catch {
261
+ return null;
262
+ }
186
263
  }
187
- /** The declared regions that sit within `host`, which may be the host itself. */
188
264
  #inside(host, regions) {
189
265
  return regions.filter((region) => host.contains(region));
190
266
  }
191
267
  };
192
268
 
193
- // src/controllers/read_more_controller.ts
194
269
  var ReadMoreController = class extends Controller {
195
270
  static targets = ["content", "trigger", "expandedLabel", "collapsedLabel"];
196
271
  static values = {
@@ -206,12 +281,13 @@ var ReadMoreController = class extends Controller {
206
281
  if (this.#connected) this.#evaluateOverflow();
207
282
  };
208
283
  #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. */
284
+ #labels = new StateRegions(
285
+ {
286
+ whenTrue: () => this.expandedLabelTargets,
287
+ whenFalse: () => this.collapsedLabelTargets
288
+ },
289
+ this.identifier
290
+ );
215
291
  #deferredHide = new BlurDeferral(() => {
216
292
  this.#update();
217
293
  });
@@ -236,6 +312,7 @@ var ReadMoreController = class extends Controller {
236
312
  }
237
313
  triggerTargetDisconnected(trigger) {
238
314
  this.#deferredHide.release(trigger);
315
+ if (!this.triggerTargets.includes(trigger)) this.#labels.release(trigger);
239
316
  this.#syncTargets();
240
317
  }
241
318
  expandedLabelTargetConnected() {
@@ -244,7 +321,6 @@ var ReadMoreController = class extends Controller {
244
321
  collapsedLabelTargetConnected() {
245
322
  this.#syncTargets();
246
323
  }
247
- /** Toggles between the collapsed (clamped) and expanded states. */
248
324
  toggle(event) {
249
325
  if (!this.#connected) return;
250
326
  this.#collapsed = !this.#collapsed;
@@ -327,5 +403,4 @@ var ReadMoreController = class extends Controller {
327
403
  };
328
404
 
329
405
  export { ReadMoreController };
330
- //# sourceMappingURL=read_more_controller.js.map
331
406
  //# sourceMappingURL=read_more_controller.js.map