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,7 +1,114 @@
1
1
  import { computePosition, autoUpdate, offset, flip, shift } from '@floating-ui/dom';
2
2
  import { Controller } from '@hotwired/stimulus';
3
3
 
4
- // src/positioning/index.ts
4
+ var ListenerSet = class {
5
+ #abort = new AbortController();
6
+ add(target, type, handler, options) {
7
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
8
+ }
9
+ dispose() {
10
+ this.#abort.abort();
11
+ this.#abort = new AbortController();
12
+ }
13
+ };
14
+
15
+ var MicrotaskCoalescer = class {
16
+ #run;
17
+ #queued = false;
18
+ #active = false;
19
+ #generation = 0;
20
+ constructor(run) {
21
+ this.#run = run;
22
+ }
23
+ activate() {
24
+ this.#active = true;
25
+ }
26
+ cancel() {
27
+ this.#active = false;
28
+ this.#queued = false;
29
+ this.#generation += 1;
30
+ }
31
+ schedule() {
32
+ if (!this.#active || this.#queued) return;
33
+ this.#queued = true;
34
+ const generation = this.#generation;
35
+ queueMicrotask(() => {
36
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
37
+ this.#queued = false;
38
+ this.#run();
39
+ });
40
+ }
41
+ };
42
+
43
+ var MorphRenderWatcher = class {
44
+ #listeners = new ListenerSet();
45
+ #pass;
46
+ #element = null;
47
+ #onMorph = () => this.#pass.schedule();
48
+ constructor(run) {
49
+ this.#pass = new MicrotaskCoalescer(run);
50
+ }
51
+ observe(element) {
52
+ if (this.#element === element) return;
53
+ this.disconnect();
54
+ this.#element = element;
55
+ this.#pass.activate();
56
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
57
+ }
58
+ schedule() {
59
+ this.#pass.schedule();
60
+ }
61
+ disconnect() {
62
+ this.#listeners.dispose();
63
+ this.#pass.cancel();
64
+ this.#element = null;
65
+ }
66
+ };
67
+
68
+ var NUMBER_BOUNDS = {
69
+ finite: { finite: true }};
70
+ function matchesNumberBounds(value, bounds) {
71
+ if (!Number.isFinite(value)) {
72
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
73
+ if (direction === null) return false;
74
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
75
+ }
76
+ if (bounds.min !== void 0 && value < bounds.min) return false;
77
+ if (bounds.max !== void 0 && value > bounds.max) return false;
78
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
79
+ if (bounds.integer && !Number.isInteger(value)) return false;
80
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
81
+ return true;
82
+ }
83
+
84
+ function readNumber(raw, fallback, bounds) {
85
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
86
+ }
87
+
88
+ var NumberValueReader = class {
89
+ #lastRejected = /* @__PURE__ */ new Map();
90
+ read(owner, name, raw, fallback, bounds) {
91
+ const resolved = readNumber(raw, fallback, bounds);
92
+ if (matchesNumberBounds(raw, bounds)) {
93
+ this.#lastRejected.delete(name);
94
+ return resolved;
95
+ }
96
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
97
+ const literal = owner.element.getAttribute(attribute);
98
+ if (literal === null) {
99
+ this.#lastRejected.delete(name);
100
+ return resolved;
101
+ }
102
+ if (this.#lastRejected.get(name) !== literal) {
103
+ this.#lastRejected.set(name, literal);
104
+ console.warn(
105
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
106
+ );
107
+ }
108
+ return resolved;
109
+ }
110
+ };
111
+
5
112
  var PLACEMENTS = /* @__PURE__ */ new Set([
6
113
  "top",
7
114
  "top-start",
@@ -16,7 +123,8 @@ var PLACEMENTS = /* @__PURE__ */ new Set([
16
123
  "left-start",
17
124
  "left-end"
18
125
  ]);
19
- var AnchoredController = class extends Controller {
126
+ var AnchoredController = class _AnchoredController extends Controller {
127
+ #numbers = new NumberValueReader();
20
128
  static targets = ["anchor", "floating"];
21
129
  static values = {
22
130
  placement: { type: String, default: "bottom" },
@@ -27,32 +135,29 @@ var AnchoredController = class extends Controller {
27
135
  strategy: { type: String, default: "absolute" },
28
136
  active: { type: Boolean, default: true }
29
137
  };
138
+ static valueConstraints = {
139
+ offset: NUMBER_BOUNDS.finite,
140
+ padding: NUMBER_BOUNDS.finite
141
+ };
30
142
  static events = ["position"];
31
- /** `autoUpdate` cleanup while tracking; `null` when detached. */
32
143
  #stop = null;
33
- /** Identity of the live attach; `null` when detached — see {@link #onComputed}. */
34
144
  #attachId = null;
35
- /** True between connect and disconnect (Stimulus may fire value callbacks before connect). */
36
145
  #connected = false;
37
- /**
38
- * Serialized options as of the last reconcile, empty only before the first one —
39
- * a value no serialization produces, so it can never match a real key.
40
- */
146
+ #position = null;
147
+ #morphRender = new MorphRenderWatcher(() => this.#sync());
41
148
  #appliedKey = "";
42
- /** Elements the live observer holds, or `null` when detached — see {@link #sync}. */
43
149
  #appliedAnchor = null;
44
150
  #appliedFloating = null;
45
151
  connect() {
152
+ this.#morphRender.observe(this.element);
46
153
  this.#connected = true;
47
154
  this.#sync();
48
155
  }
49
156
  disconnect() {
157
+ this.#morphRender.disconnect();
50
158
  this.#connected = false;
51
159
  this.#sync();
52
160
  }
53
- // Every value change (active or an option) and every target arrival or
54
- // departure re-syncs. `#sync` is idempotent and order-independent, so no Value
55
- // has to be declared in a particular position.
56
161
  anchorTargetConnected() {
57
162
  this.#sync();
58
163
  }
@@ -86,41 +191,23 @@ var AnchoredController = class extends Controller {
86
191
  strategyValueChanged() {
87
192
  this.#sync();
88
193
  }
89
- /** Current Values mapped to the positioning engine's options. */
90
194
  get #options() {
91
195
  return {
92
- // Narrow every free-form Value to what the engine can use. A placement
93
- // outside the set would be published on the hook as a resolved side it is
94
- // not, and a non-finite distance poisons the coordinate it feeds until the
95
- // browser drops the whole declaration and leaves that axis unplaced.
96
196
  placement: PLACEMENTS.has(this.placementValue) ? this.placementValue : "bottom",
97
- offset: Number.isFinite(this.offsetValue) ? this.offsetValue : 0,
197
+ offset: this.#safeOffset,
98
198
  flip: this.flipValue,
99
199
  shift: this.shiftValue,
100
- padding: Number.isFinite(this.paddingValue) ? this.paddingValue : 0,
200
+ padding: this.#safePadding,
101
201
  strategy: this.strategyValue === "fixed" ? "fixed" : "absolute"
102
202
  };
103
203
  }
104
- /**
105
- * Reconciles the live observer with the desired state — track iff connected,
106
- * `active`, and both targets exist — re-attaching only when that state, the
107
- * options, or the elements actually changed. Stimulus fires the value-changed
108
- * callbacks on connect in declaration order and may run them before or after
109
- * `connect()`; keying on what is applied collapses that whole burst (in any
110
- * order) to a single attach, while an option or target change at runtime
111
- * re-attaches exactly once. So correctness never depends on `active` being
112
- * declared last. The elements are part of the key because the engine holds the
113
- * pair it was handed: a swap leaves the same options behind and would otherwise
114
- * keep measuring the node that just left the document.
115
- *
116
- * @stimeoRenderRoot
117
- */
118
204
  #sync() {
119
205
  const shouldTrack = this.#connected && this.activeValue && this.hasAnchorTarget && this.hasFloatingTarget;
120
206
  const anchor = shouldTrack ? this.anchorTarget : null;
121
207
  const floating = shouldTrack ? this.floatingTarget : null;
122
208
  const key = JSON.stringify(this.#options);
123
209
  if (key === this.#appliedKey && anchor === this.#appliedAnchor && floating === this.#appliedFloating) {
210
+ if (floating && this.#position) this.#reflectPosition(floating, this.#position);
124
211
  return;
125
212
  }
126
213
  this.#detach();
@@ -143,27 +230,42 @@ var AnchoredController = class extends Controller {
143
230
  this.#stop?.();
144
231
  this.#stop = null;
145
232
  this.#attachId = null;
233
+ this.#position = null;
146
234
  }
147
- /**
148
- * Reflects the resolved side onto the CSS hook and announces the placement.
149
- *
150
- * The pass carries the attach that started it and the element it positioned,
151
- * and lands only while that attach is still the live one: `autoUpdate`'s
152
- * cleanup stops further updates but cannot cancel one already computing, so a
153
- * pass superseded mid-flight would otherwise write and dispatch afterwards.
154
- * The element cannot stand in for that identity — an option change re-attaches
155
- * against the very same pair.
156
- */
157
235
  #onComputed(id, floating, result) {
158
236
  if (id !== this.#attachId) return;
159
- floating.setAttribute("data-anchored-placement", result.placement);
237
+ this.#position = result;
238
+ this.#reflectPosition(floating, result);
160
239
  this.dispatch("position", {
161
240
  detail: { placement: result.placement, x: result.x, y: result.y }
162
241
  });
163
242
  }
243
+ #reflectPosition(floating, result) {
244
+ floating.setAttribute("data-anchored-placement", result.placement);
245
+ floating.style.position = this.#options.strategy ?? "absolute";
246
+ floating.style.left = `${result.x}px`;
247
+ floating.style.top = `${result.y}px`;
248
+ }
249
+ get #safeOffset() {
250
+ return this.#numbers.read(
251
+ this,
252
+ "offset",
253
+ this.offsetValue,
254
+ _AnchoredController.values.offset.default,
255
+ _AnchoredController.valueConstraints.offset
256
+ );
257
+ }
258
+ get #safePadding() {
259
+ return this.#numbers.read(
260
+ this,
261
+ "padding",
262
+ this.paddingValue,
263
+ _AnchoredController.values.padding.default,
264
+ _AnchoredController.valueConstraints.padding
265
+ );
266
+ }
164
267
  };
165
268
 
166
- // src/positioning/index.ts
167
269
  function buildMiddleware(options) {
168
270
  const padding = options.padding ?? 0;
169
271
  const middleware = [];
@@ -202,5 +304,4 @@ function registerPositioning(application) {
202
304
  }
203
305
 
204
306
  export { AnchoredController, attachPositioning, position, positioningControllers, registerPositioning };
205
- //# sourceMappingURL=index.js.map
206
307
  //# sourceMappingURL=index.js.map
@@ -5,6 +5,6 @@ module Stimeo
5
5
  # Kept in lockstep with package.json (stable versions share the same
6
6
  # notation; a prerelease maps npm `-beta.N` ⇔ gem `.pre.beta.N` because
7
7
  # RubyGems forbids dashes). Bump both together.
8
- VERSION = "0.16.0"
8
+ VERSION = "0.17.0"
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stimeo-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stimeo Labs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-26 00:00:00.000000000 Z
11
+ date: 2026-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -119,7 +119,7 @@ files:
119
119
  - dist/controllers/read_more_controller.js
120
120
  - dist/controllers/reading_progress_controller.js
121
121
  - dist/controllers/relative_time_controller.js
122
- - dist/controllers/reset_before_cache_controller.js
122
+ - dist/controllers/reset_on_restore_controller.js
123
123
  - dist/controllers/resizable_controller.js
124
124
  - dist/controllers/roving_controller.js
125
125
  - dist/controllers/scroll_area_controller.js