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,8 +1,5 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/progress_controller.ts
4
-
5
- // src/utils/announce.ts
6
3
  function announce(message, options = {}) {
7
4
  const text = message.trim();
8
5
  if (text.length === 0) return;
@@ -19,34 +16,59 @@ function fillTemplate(template, values) {
19
16
  });
20
17
  }
21
18
 
22
- // src/utils/coerce.ts
19
+ var NUMBER_BOUNDS = {
20
+ finite: { finite: true }};
21
+ function matchesNumberBounds(value, bounds) {
22
+ if (!Number.isFinite(value)) {
23
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
24
+ if (direction === null) return false;
25
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
26
+ }
27
+ if (bounds.min !== void 0 && value < bounds.min) return false;
28
+ if (bounds.max !== void 0 && value > bounds.max) return false;
29
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
30
+ if (bounds.integer && !Number.isInteger(value)) return false;
31
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
32
+ return true;
33
+ }
34
+
23
35
  function toFiniteNumber(raw) {
24
- if (raw === null || raw === void 0 || raw === "") return null;
25
- const value = typeof raw === "number" ? raw : Number(raw);
36
+ if (typeof raw !== "number" && typeof raw !== "string") return null;
37
+ if (typeof raw === "string" && raw.trim().length === 0) return null;
38
+ const value = Number(raw);
26
39
  return Number.isFinite(value) ? value : null;
27
40
  }
41
+ function readNumber(raw, fallback, bounds) {
42
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
43
+ }
44
+
45
+ var ListenerSet = class {
46
+ #abort = new AbortController();
47
+ add(target, type, handler, options) {
48
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
49
+ }
50
+ dispose() {
51
+ this.#abort.abort();
52
+ this.#abort = new AbortController();
53
+ }
54
+ };
28
55
 
29
- // src/utils/microtask_coalescer.ts
30
56
  var MicrotaskCoalescer = class {
31
57
  #run;
32
58
  #queued = false;
33
59
  #active = false;
34
60
  #generation = 0;
35
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
36
61
  constructor(run) {
37
62
  this.#run = run;
38
63
  }
39
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
40
64
  activate() {
41
65
  this.#active = true;
42
66
  }
43
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
44
67
  cancel() {
45
68
  this.#active = false;
46
69
  this.#queued = false;
47
70
  this.#generation += 1;
48
71
  }
49
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
50
72
  schedule() {
51
73
  if (!this.#active || this.#queued) return;
52
74
  this.#queued = true;
@@ -59,7 +81,55 @@ var MicrotaskCoalescer = class {
59
81
  }
60
82
  };
61
83
 
62
- // src/utils/range.ts
84
+ var MorphRenderWatcher = class {
85
+ #listeners = new ListenerSet();
86
+ #pass;
87
+ #element = null;
88
+ #onMorph = () => this.#pass.schedule();
89
+ constructor(run) {
90
+ this.#pass = new MicrotaskCoalescer(run);
91
+ }
92
+ observe(element) {
93
+ if (this.#element === element) return;
94
+ this.disconnect();
95
+ this.#element = element;
96
+ this.#pass.activate();
97
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
98
+ }
99
+ schedule() {
100
+ this.#pass.schedule();
101
+ }
102
+ disconnect() {
103
+ this.#listeners.dispose();
104
+ this.#pass.cancel();
105
+ this.#element = null;
106
+ }
107
+ };
108
+
109
+ var NumberValueReader = class {
110
+ #lastRejected = /* @__PURE__ */ new Map();
111
+ read(owner, name, raw, fallback, bounds) {
112
+ const resolved = readNumber(raw, fallback, bounds);
113
+ if (matchesNumberBounds(raw, bounds)) {
114
+ this.#lastRejected.delete(name);
115
+ return resolved;
116
+ }
117
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
118
+ const literal = owner.element.getAttribute(attribute);
119
+ if (literal === null) {
120
+ this.#lastRejected.delete(name);
121
+ return resolved;
122
+ }
123
+ if (this.#lastRejected.get(name) !== literal) {
124
+ this.#lastRejected.set(name, literal);
125
+ console.warn(
126
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
127
+ );
128
+ }
129
+ return resolved;
130
+ }
131
+ };
132
+
63
133
  function rangeFraction(value, min, max) {
64
134
  const span = max - min;
65
135
  if (!(span > 0)) return 0;
@@ -75,9 +145,12 @@ function rangeFraction(value, min, max) {
75
145
  return Math.min(1, Math.max(0, fraction));
76
146
  }
77
147
 
78
- // src/controllers/progress_controller.ts
79
- var OWNED_VALUE_TEXT = "data-stimeo--progress-owns-valuetext";
80
- var ProgressController = class extends Controller {
148
+ var OWNED_VALUE_TEXT = "owns-valuetext";
149
+ var ProgressController = class _ProgressController extends Controller {
150
+ #numbers = new NumberValueReader();
151
+ get #ownedValueText() {
152
+ return `data-${this.identifier}-${OWNED_VALUE_TEXT}`;
153
+ }
81
154
  static targets = ["bar"];
82
155
  static values = {
83
156
  value: { type: Number, default: 0 },
@@ -87,29 +160,23 @@ var ProgressController = class extends Controller {
87
160
  valueText: { type: String, default: "" },
88
161
  announceText: { type: String, default: "" }
89
162
  };
163
+ static valueConstraints = {
164
+ value: NUMBER_BOUNDS.finite,
165
+ min: NUMBER_BOUNDS.finite,
166
+ max: NUMBER_BOUNDS.finite
167
+ };
90
168
  static actions = ["setValue"];
91
169
  static events = ["change", "complete"];
92
- /**
93
- * Collapses a morph that swaps several render inputs at once into one repaint.
94
- * A single update usually rewrites the whole set, and each Value would otherwise
95
- * repaint on its own.
96
- */
97
- #repaint = new MicrotaskCoalescer(() => {
170
+ #repaint = new MorphRenderWatcher(() => {
98
171
  this.#render();
99
172
  });
100
173
  connect() {
101
- this.#repaint.activate();
174
+ this.#repaint.observe(this.element);
102
175
  this.#render();
103
176
  }
104
- /** Closes the window in which a queued repaint may still run. */
105
177
  disconnect() {
106
- this.#repaint.cancel();
178
+ this.#repaint.disconnect();
107
179
  }
108
- /**
109
- * Updates the progress value from an action param (`amount`) or a
110
- * `detail.value` CustomEvent, normalizes it into range, syncs ARIA, and
111
- * dispatches `change` (always) plus `complete` when `max` is reached.
112
- */
113
180
  setValue(event) {
114
181
  const next = toFiniteNumber(event.params?.amount ?? event.detail?.value);
115
182
  if (next === null) return;
@@ -118,49 +185,37 @@ var ProgressController = class extends Controller {
118
185
  this.indeterminateValue = false;
119
186
  this.#render();
120
187
  this.dispatch("change", { detail: { value, ratio: this.#ratio } });
121
- if (value >= this.maxValue) {
188
+ if (value >= this.#safeMax) {
122
189
  this.dispatch("complete", { detail: { value } });
123
190
  announce(
124
191
  fillTemplate(this.announceTextValue, { value, percent: Math.round(this.#ratio * 100) })
125
192
  );
126
193
  }
127
194
  }
128
- /** Repaints when application code (or a Turbo morph) changes `indeterminate` at runtime. */
129
195
  indeterminateValueChanged() {
130
196
  this.#repaint.schedule();
131
197
  }
132
- /** Repaints when application code (or a Turbo morph) changes `value` at runtime. */
133
198
  valueValueChanged() {
134
199
  this.#repaint.schedule();
135
200
  }
136
- /** Repaints when application code (or a Turbo morph) changes `min` at runtime. */
137
201
  minValueChanged() {
138
202
  this.#repaint.schedule();
139
203
  }
140
- /** Repaints when application code (or a Turbo morph) changes `max` at runtime. */
141
204
  maxValueChanged() {
142
205
  this.#repaint.schedule();
143
206
  }
144
- /** Repaints when application code (or a Turbo morph) changes `valueText` at runtime. */
145
207
  valueTextValueChanged() {
146
208
  this.#repaint.schedule();
147
209
  }
148
- /** Clamps `raw` into the configured `[min, max]` range. */
149
210
  #clamp(raw) {
150
- return Math.min(this.maxValue, Math.max(this.minValue, raw));
211
+ return Math.min(this.#safeMax, Math.max(this.#safeMin, raw));
151
212
  }
152
- /** Current fraction of the range in `[0, 1]`; `0` when the range is empty. */
153
213
  get #ratio() {
154
- return rangeFraction(this.valueValue, this.minValue, this.maxValue);
214
+ return rangeFraction(this.#safeValue, this.#safeMin, this.#safeMax);
155
215
  }
156
- /**
157
- * Reflects value/range/indeterminate onto ARIA, `data-state`, and the ratio.
158
- *
159
- * @stimeoRenderRoot
160
- */
161
216
  #render() {
162
- this.element.setAttribute("aria-valuemin", String(this.minValue));
163
- this.element.setAttribute("aria-valuemax", String(this.maxValue));
217
+ this.element.setAttribute("aria-valuemin", String(this.#safeMin));
218
+ this.element.setAttribute("aria-valuemax", String(this.#safeMax));
164
219
  if (this.indeterminateValue) {
165
220
  this.element.removeAttribute("aria-valuenow");
166
221
  this.#clearOwnValueText();
@@ -168,19 +223,12 @@ var ProgressController = class extends Controller {
168
223
  this.element.setAttribute("data-state", "indeterminate");
169
224
  return;
170
225
  }
171
- const value = this.#clamp(this.valueValue);
226
+ const value = this.#clamp(this.#safeValue);
172
227
  this.element.setAttribute("aria-valuenow", String(value));
173
228
  this.element.style.setProperty("--stimeo--progress-ratio", String(this.#ratio));
174
229
  this.element.setAttribute("data-state", "determinate");
175
230
  this.#applyValueText(value);
176
231
  }
177
- /**
178
- * Sets `aria-valuetext` from the consumer-provided template, substituting
179
- * `{value}` and `{percent}`. Left to the consumer so the human-readable text
180
- * stays i18n-neutral in the library. With no template the attribute belongs to
181
- * the consumer, so only a text this controller wrote is taken back
182
- * ({@link OWNED_VALUE_TEXT}).
183
- */
184
232
  #applyValueText(value) {
185
233
  if (this.valueTextValue.length === 0) {
186
234
  this.#clearOwnValueText();
@@ -189,16 +237,41 @@ var ProgressController = class extends Controller {
189
237
  const percent = Math.round(this.#ratio * 100);
190
238
  const text = this.valueTextValue.replaceAll("{value}", String(value)).replaceAll("{percent}", String(percent));
191
239
  this.element.setAttribute("aria-valuetext", text);
192
- this.element.setAttribute(OWNED_VALUE_TEXT, "");
240
+ this.element.setAttribute(this.#ownedValueText, "");
193
241
  }
194
- /** Removes `aria-valuetext` only when this controller is the one that wrote it. */
195
242
  #clearOwnValueText() {
196
- if (!this.element.hasAttribute(OWNED_VALUE_TEXT)) return;
243
+ if (!this.element.hasAttribute(this.#ownedValueText)) return;
197
244
  this.element.removeAttribute("aria-valuetext");
198
- this.element.removeAttribute(OWNED_VALUE_TEXT);
245
+ this.element.removeAttribute(this.#ownedValueText);
246
+ }
247
+ get #safeValue() {
248
+ return this.#numbers.read(
249
+ this,
250
+ "value",
251
+ this.valueValue,
252
+ _ProgressController.values.value.default,
253
+ _ProgressController.valueConstraints.value
254
+ );
255
+ }
256
+ get #safeMin() {
257
+ return this.#numbers.read(
258
+ this,
259
+ "min",
260
+ this.minValue,
261
+ _ProgressController.values.min.default,
262
+ _ProgressController.valueConstraints.min
263
+ );
264
+ }
265
+ get #safeMax() {
266
+ return this.#numbers.read(
267
+ this,
268
+ "max",
269
+ this.maxValue,
270
+ _ProgressController.values.max.default,
271
+ _ProgressController.valueConstraints.max
272
+ );
199
273
  }
200
274
  };
201
275
 
202
276
  export { ProgressController };
203
- //# sourceMappingURL=progress_controller.js.map
204
277
  //# sourceMappingURL=progress_controller.js.map