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,8 +1,152 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/sticky_observer_controller.ts
3
+ function sharedRegistry(key, create) {
4
+ const symbol = Symbol.for(key);
5
+ const scope = globalThis;
6
+ const existing = scope[symbol];
7
+ if (existing !== void 0) return existing;
8
+ const registry = create();
9
+ Object.defineProperty(globalThis, symbol, { value: registry });
10
+ return registry;
11
+ }
12
+
13
+ function isAttributeValue(value) {
14
+ return value === null || typeof value === "string";
15
+ }
16
+ function readLeaseRecord(element, record) {
17
+ try {
18
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
19
+ if (!Array.isArray(parsed)) return void 0;
20
+ const [original, written, ...rest] = parsed;
21
+ const linked = rest[0] !== false;
22
+ const beneath = linked ? rest : rest.slice(1);
23
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
24
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
25
+ } catch {
26
+ return void 0;
27
+ }
28
+ }
29
+ var AttributeLease = class _AttributeLease {
30
+ static #registry = sharedRegistry(
31
+ "stimeo-ui.attribute-lease.registry.v1",
32
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
33
+ );
34
+ #attribute;
35
+ #record;
36
+ #base;
37
+ #held = /* @__PURE__ */ new Set();
38
+ constructor(attribute, owner, options = {}) {
39
+ this.#attribute = attribute;
40
+ this.#record = `data-${owner}-${attribute}-lease`;
41
+ this.#base = options.base === true;
42
+ const names = _AttributeLease.#registry.names;
43
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
44
+ }
45
+ write(element, value) {
46
+ const stack = this.#stack(element);
47
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
48
+ this.#held.add(element);
49
+ const shows = !this.#covered(element, stack, hold);
50
+ hold.written = value;
51
+ this.#mark(element, stack);
52
+ if (shows) this.#reflect(element, value);
53
+ }
54
+ return(element) {
55
+ const held = this.#held.delete(element);
56
+ const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
57
+ const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
58
+ const hold = this.#take(stack);
59
+ if (!hold) return false;
60
+ const carries = element.getAttribute(this.#attribute) === hold.written;
61
+ const owns = carries && !this.#covered(element, stack, hold);
62
+ const index = stack.indexOf(hold);
63
+ stack.splice(index, 1);
64
+ const above = stack[index];
65
+ if (above) above.linked &&= hold.linked;
66
+ this.#mark(element, stack);
67
+ if (!owns) return true;
68
+ const below = hold.linked ? stack[index - 1] : void 0;
69
+ this.#reflect(element, below ? below.written : hold.original);
70
+ return true;
71
+ }
72
+ returnAll() {
73
+ for (const element of Array.from(this.#held)) this.return(element);
74
+ }
75
+ #stack(element) {
76
+ const stacks = _AttributeLease.#registry.stacks;
77
+ const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
78
+ stacks.set(element, byAttribute);
79
+ const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
80
+ byAttribute.set(this.#attribute, stack);
81
+ return stack;
82
+ }
83
+ #seed(element) {
84
+ const found = [];
85
+ for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
86
+ const read = readLeaseRecord(element, record);
87
+ if (read) found.push({ ...read, record });
88
+ }
89
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
90
+ return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
91
+ lease: null,
92
+ record,
93
+ written,
94
+ original,
95
+ linked: linked && index > 0
96
+ }));
97
+ }
98
+ #take(stack) {
99
+ const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
100
+ if (hold) hold.lease = this;
101
+ return hold;
102
+ }
103
+ #acquire(element, stack) {
104
+ const current = element.getAttribute(this.#attribute);
105
+ const top = stack.at(-1);
106
+ const bottom = stack[0];
107
+ const over = top?.written === current ? top : void 0;
108
+ const from = this.#base ? bottom : over;
109
+ const hold = {
110
+ lease: this,
111
+ record: this.#record,
112
+ written: current,
113
+ original: from ? from.original : current,
114
+ linked: !this.#base && over !== void 0
115
+ };
116
+ if (this.#base && bottom) {
117
+ bottom.linked = true;
118
+ stack.unshift(hold);
119
+ } else stack.push(hold);
120
+ return hold;
121
+ }
122
+ #covered(element, stack, hold) {
123
+ const current = element.getAttribute(this.#attribute);
124
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
125
+ }
126
+ #mark(element, stack) {
127
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
128
+ stack.forEach((hold, index) => {
129
+ const names = stack.slice(0, index).map((under) => under.record);
130
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
131
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
132
+ records.set(
133
+ hold.record,
134
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
135
+ );
136
+ });
137
+ for (const [name, recorded] of records) {
138
+ if (element.getAttribute(name) === recorded) continue;
139
+ if (recorded === null) element.removeAttribute(name);
140
+ else element.setAttribute(name, recorded);
141
+ }
142
+ }
143
+ #reflect(element, value) {
144
+ if (element.getAttribute(this.#attribute) === value) return;
145
+ if (value === null) element.removeAttribute(this.#attribute);
146
+ else element.setAttribute(this.#attribute, value);
147
+ }
148
+ };
4
149
 
5
- // src/utils/declared_value.ts
6
150
  function parseDeclared(raw, parse, fallback) {
7
151
  try {
8
152
  return parse(raw);
@@ -11,7 +155,6 @@ function parseDeclared(raw, parse, fallback) {
11
155
  }
12
156
  }
13
157
 
14
- // src/utils/intersection_watcher.ts
15
158
  function isBeforeRootStart(entry) {
16
159
  const rect = entry.boundingClientRect;
17
160
  if (rect.width === 0 && rect.height === 0) return false;
@@ -30,30 +173,12 @@ var IntersectionWatcher = class {
30
173
  constructor(onEntries) {
31
174
  this.#onEntries = onEntries;
32
175
  }
33
- /** Whether an observer is live (started, `IntersectionObserver` supported). */
34
176
  get active() {
35
177
  return this.#active;
36
178
  }
37
- /** Whether the live observer discarded configured options after construction failed. */
38
179
  get usingPlatformDefaults() {
39
180
  return this.#usingPlatformDefaults;
40
181
  }
41
- /**
42
- * (Re)creates the observer and observes `targets`. Returns `false` — leaving
43
- * the watcher inert — without `IntersectionObserver` support (very old
44
- * browsers; the caller's no-JS fallback stays in charge) or with no targets.
45
- * If initial construction with the configured options fails, the watcher
46
- * warns and retries once with the same root and platform defaults. A
47
- * `rootSelector` that does not parse resolves to the viewport (see
48
- * {@link IntersectionWatchOptions.rootSelector}), so a typo never fails the
49
- * call.
50
- *
51
- * @throws The fallback constructor error if both construction attempts fail,
52
- * or whatever the platform throws from `observe()`. The exception is passed
53
- * through unchanged, but the watcher rolls back first: every target observed
54
- * so far is released and `active` stays `false`, so a caller that retries
55
- * starts from a clean slate.
56
- */
57
182
  start(targets, options = {}) {
58
183
  this.stop();
59
184
  if (typeof IntersectionObserver === "undefined") return false;
@@ -91,14 +216,6 @@ var IntersectionWatcher = class {
91
216
  throw error;
92
217
  }
93
218
  }
94
- /**
95
- * Re-delivers `target`'s CURRENT intersection state: `IntersectionObserver`
96
- * only reports *changes*, but `observe()` always reports the present state,
97
- * so unobserve→observe turns "still intersecting" into a fresh callback.
98
- *
99
- * @throws Whatever `unobserve()`/`observe()` throws. The watcher is stopped
100
- * first, so it never stays live with a half-rearmed target.
101
- */
102
219
  rearm(target) {
103
220
  if (!this.#observer) return;
104
221
  try {
@@ -109,7 +226,6 @@ var IntersectionWatcher = class {
109
226
  throw error;
110
227
  }
111
228
  }
112
- /** Severs the observer; late queued callbacks become no-ops via the guard. */
113
229
  stop() {
114
230
  this.#active = false;
115
231
  this.#observer?.disconnect();
@@ -118,19 +234,64 @@ var IntersectionWatcher = class {
118
234
  }
119
235
  };
120
236
 
121
- // src/controllers/sticky_observer_controller.ts
122
- var StickyObserverController = class extends Controller {
237
+ var NUMBER_BOUNDS = {
238
+ finite: { finite: true }};
239
+ function matchesNumberBounds(value, bounds) {
240
+ if (!Number.isFinite(value)) {
241
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
242
+ if (direction === null) return false;
243
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
244
+ }
245
+ if (bounds.min !== void 0 && value < bounds.min) return false;
246
+ if (bounds.max !== void 0 && value > bounds.max) return false;
247
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
248
+ if (bounds.integer && !Number.isInteger(value)) return false;
249
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
250
+ return true;
251
+ }
252
+
253
+ function readNumber(raw, fallback, bounds) {
254
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
255
+ }
256
+
257
+ var NumberValueReader = class {
258
+ #lastRejected = /* @__PURE__ */ new Map();
259
+ read(owner, name, raw, fallback, bounds) {
260
+ const resolved = readNumber(raw, fallback, bounds);
261
+ if (matchesNumberBounds(raw, bounds)) {
262
+ this.#lastRejected.delete(name);
263
+ return resolved;
264
+ }
265
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
266
+ const literal = owner.element.getAttribute(attribute);
267
+ if (literal === null) {
268
+ this.#lastRejected.delete(name);
269
+ return resolved;
270
+ }
271
+ if (this.#lastRejected.get(name) !== literal) {
272
+ this.#lastRejected.set(name, literal);
273
+ console.warn(
274
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
275
+ );
276
+ }
277
+ return resolved;
278
+ }
279
+ };
280
+
281
+ var StickyObserverController = class _StickyObserverController extends Controller {
282
+ #numbers = new NumberValueReader();
123
283
  static targets = ["sentinel", "element"];
124
284
  static values = {
125
285
  rootSelector: { type: String, default: "" },
126
286
  offset: { type: Number, default: 0 }
127
287
  };
288
+ static valueConstraints = {
289
+ offset: NUMBER_BOUNDS.finite
290
+ };
128
291
  static events = ["change"];
129
- /** Shared IO plumbing (support guard, root resolution, active guard). */
130
292
  #watcher = new IntersectionWatcher((entries) => this.#onIntersect(entries));
131
- /** Last reported stuck state, so `change` fires only on transitions. */
293
+ #stuckLease = new AttributeLease("data-stuck", this.identifier);
132
294
  #stuck = null;
133
- /** Target currently owned by the watcher; comparing it avoids duplicate restarts. */
134
295
  #observedSentinel = null;
135
296
  #connected = false;
136
297
  #onIntersect(entries) {
@@ -150,25 +311,24 @@ var StickyObserverController = class extends Controller {
150
311
  this.#observedSentinel = null;
151
312
  this.#stuck = null;
152
313
  }
153
- /** Starts observation when a sentinel is inserted after connection. */
154
314
  sentinelTargetConnected() {
155
315
  if (this.#connected) this.#syncObserver();
156
316
  }
157
- /** Stops or transfers observation when the current sentinel is removed. */
158
317
  sentinelTargetDisconnected() {
159
318
  if (this.#connected) this.#syncObserver();
160
319
  }
161
- /** Reflects the last snapshot onto an element inserted after that snapshot. */
162
320
  elementTargetConnected(element) {
163
- if (this.#stuck !== null) {
164
- element.setAttribute("data-stuck", this.#stuck ? "true" : "false");
321
+ if (this.#stuck !== null) this.#stuckLease.write(element, String(this.#stuck));
322
+ }
323
+ elementTargetDisconnected(element) {
324
+ if (!this.elementTargets.includes(element)) this.#stuckLease.return(element);
325
+ if (this.#connected && this.#stuck !== null && this.hasElementTarget) {
326
+ this.#stuckLease.write(this.elementTarget, String(this.#stuck));
165
327
  }
166
328
  }
167
- /** Rebuilds the observer when Turbo morphs the configured root. */
168
329
  rootSelectorValueChanged() {
169
330
  if (this.#connected) this.#syncObserver(true);
170
331
  }
171
- /** Rebuilds the observer when Turbo morphs the configured top offset. */
172
332
  offsetValueChanged() {
173
333
  if (this.#connected) this.#syncObserver(true);
174
334
  }
@@ -178,8 +338,8 @@ var StickyObserverController = class extends Controller {
178
338
  this.#watcher.stop();
179
339
  this.#observedSentinel = null;
180
340
  if (!sentinel) return;
181
- const configuredOffset = this.offsetValue;
182
- const offset = Number.isFinite(configuredOffset) ? configuredOffset : 0;
341
+ const configuredOffset = this.#safeOffset;
342
+ const offset = configuredOffset;
183
343
  const started = this.#watcher.start(sentinel, {
184
344
  rootSelector: this.rootSelectorValue,
185
345
  rootMargin: `${-offset}px 0px 0px 0px`,
@@ -187,17 +347,22 @@ var StickyObserverController = class extends Controller {
187
347
  });
188
348
  if (started) this.#observedSentinel = sentinel;
189
349
  }
190
- /** Reflects the stuck state onto the sticky element and emits `change`. */
191
350
  #setStuck(next) {
192
351
  if (next === this.#stuck) return;
193
352
  this.#stuck = next;
194
- if (this.hasElementTarget) {
195
- this.elementTarget.setAttribute("data-stuck", next ? "true" : "false");
196
- }
353
+ if (this.hasElementTarget) this.#stuckLease.write(this.elementTarget, String(next));
197
354
  this.dispatch("change", { detail: { stuck: next } });
198
355
  }
356
+ get #safeOffset() {
357
+ return this.#numbers.read(
358
+ this,
359
+ "offset",
360
+ this.offsetValue,
361
+ _StickyObserverController.values.offset.default,
362
+ _StickyObserverController.valueConstraints.offset
363
+ );
364
+ }
199
365
  };
200
366
 
201
367
  export { StickyObserverController };
202
- //# sourceMappingURL=sticky_observer_controller.js.map
203
368
  //# sourceMappingURL=sticky_observer_controller.js.map