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