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,19 +1,14 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/toolbar_controller.ts
4
-
5
- // src/utils/logical_scroll.ts
6
3
  function isRtl(element) {
7
4
  return window.getComputedStyle(element).direction === "rtl";
8
5
  }
9
6
 
10
- // src/utils/arrow_step.ts
11
7
  function isReservedArrowChord(event, allow = []) {
12
8
  if (!event.key.startsWith("Arrow")) return false;
13
9
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
14
10
  }
15
11
 
16
- // src/utils/composition_tracker.ts
17
12
  var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
18
13
  "insertCompositionText",
19
14
  "insertFromComposition",
@@ -25,13 +20,11 @@ var CompositionTracker = class {
25
20
  #activeTargets = /* @__PURE__ */ new Set();
26
21
  #onStart;
27
22
  #onEnd;
28
- /** The field whose confirming `input` is still owed, while the window is open. */
29
23
  #confirmedTarget = null;
30
24
  constructor(options = {}) {
31
25
  this.#onStart = options.onStart;
32
26
  this.#onEnd = options.onEnd;
33
27
  }
34
- /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
35
28
  observe(target) {
36
29
  if (this.#observedTargets.has(target)) return;
37
30
  target.addEventListener("compositionstart", this.#handleStart);
@@ -39,7 +32,6 @@ var CompositionTracker = class {
39
32
  target.addEventListener("keydown", this.#handleKeydown);
40
33
  this.#observedTargets.add(target);
41
34
  }
42
- /** Stops tracking one target and clears any active composition it owned. */
43
35
  unobserve(target) {
44
36
  if (!this.#observedTargets.delete(target)) return;
45
37
  target.removeEventListener("compositionstart", this.#handleStart);
@@ -48,7 +40,6 @@ var CompositionTracker = class {
48
40
  this.#activeTargets.delete(target);
49
41
  if (this.#confirmedTarget === target) this.#confirmedTarget = null;
50
42
  }
51
- /** Releases every listener and clears state so reconnect starts cleanly. */
52
43
  disconnect() {
53
44
  for (const target of this.#observedTargets) {
54
45
  target.removeEventListener("compositionstart", this.#handleStart);
@@ -59,16 +50,9 @@ var CompositionTracker = class {
59
50
  this.#activeTargets.clear();
60
51
  this.#confirmedTarget = null;
61
52
  }
62
- /** True when lifecycle tracking or the current event reports composition. */
63
53
  isComposing(event) {
64
54
  return this.#activeTargets.size > 0 || event?.isComposing === true;
65
55
  }
66
- /**
67
- * Whether `event` is the `input` echoing the composition just confirmed.
68
- *
69
- * Asking closes the window either way, so one confirmation is folded at most
70
- * once and a consumer asks once per `input`.
71
- */
72
56
  consumesConfirmedInput(event) {
73
57
  const confirmed = this.#confirmedTarget;
74
58
  this.#confirmedTarget = null;
@@ -86,19 +70,16 @@ var CompositionTracker = class {
86
70
  this.#confirmedTarget = event.target;
87
71
  this.#onEnd?.(event);
88
72
  };
89
- /** A key on an observed field opens an edit of its own, so no echo is owed. */
90
73
  #handleKeydown = () => {
91
74
  this.#confirmedTarget = null;
92
75
  };
93
76
  };
94
77
 
95
- // src/utils/event_owner.ts
96
78
  function ownerIndex(candidates, node) {
97
79
  if (!(node instanceof Node)) return -1;
98
80
  return candidates.findIndex((candidate) => candidate.contains(node));
99
81
  }
100
82
 
101
- // src/utils/focus_candidate.ts
102
83
  function inheritsFieldsetDisabled(control) {
103
84
  let fieldset = control.closest("fieldset[disabled]");
104
85
  while (fieldset) {
@@ -109,31 +90,78 @@ function inheritsFieldsetDisabled(control) {
109
90
  return false;
110
91
  }
111
92
 
112
- // src/utils/roving_tabindex.ts
93
+ var ListenerSet = class {
94
+ #abort = new AbortController();
95
+ add(target, type, handler, options) {
96
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
97
+ }
98
+ dispose() {
99
+ this.#abort.abort();
100
+ this.#abort = new AbortController();
101
+ }
102
+ };
103
+
104
+ var MicrotaskCoalescer = class {
105
+ #run;
106
+ #queued = false;
107
+ #active = false;
108
+ #generation = 0;
109
+ constructor(run) {
110
+ this.#run = run;
111
+ }
112
+ activate() {
113
+ this.#active = true;
114
+ }
115
+ cancel() {
116
+ this.#active = false;
117
+ this.#queued = false;
118
+ this.#generation += 1;
119
+ }
120
+ schedule() {
121
+ if (!this.#active || this.#queued) return;
122
+ this.#queued = true;
123
+ const generation = this.#generation;
124
+ queueMicrotask(() => {
125
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
126
+ this.#queued = false;
127
+ this.#run();
128
+ });
129
+ }
130
+ };
131
+
132
+ var MorphRenderWatcher = class {
133
+ #listeners = new ListenerSet();
134
+ #pass;
135
+ #element = null;
136
+ #onMorph = () => this.#pass.schedule();
137
+ constructor(run) {
138
+ this.#pass = new MicrotaskCoalescer(run);
139
+ }
140
+ observe(element) {
141
+ if (this.#element === element) return;
142
+ this.disconnect();
143
+ this.#element = element;
144
+ this.#pass.activate();
145
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
146
+ }
147
+ schedule() {
148
+ this.#pass.schedule();
149
+ }
150
+ disconnect() {
151
+ this.#listeners.dispose();
152
+ this.#pass.cancel();
153
+ this.#element = null;
154
+ }
155
+ };
156
+
113
157
  var RovingTabindex = class {
114
- /** Returns the current ordered item elements; called on every operation. */
115
158
  #getItems;
116
- /**
117
- * @param getItems - Returns the current ordered item elements. Called on every
118
- * operation so the live target list is always used.
119
- */
120
159
  constructor(getItems) {
121
160
  this.#getItems = getItems;
122
161
  }
123
- /** Index of the currently tabbable item (`tabindex="0"`), or `-1` if none. */
124
162
  get activeIndex() {
125
163
  return this.#getItems().findIndex((item) => item.tabIndex === 0);
126
164
  }
127
- /**
128
- * Makes exactly the item at `index` tabbable (`tabindex="0"`) and removes every
129
- * other item from the Tab sequence (`tabindex="-1"`). An out-of-range `index`
130
- * (e.g. `-1`) leaves all items at `-1`, which a controller can use to express
131
- * "nothing is currently tabbable".
132
- *
133
- * @param index - Position of the item to make tabbable.
134
- * @param options - Pass `{ focus: true }` to also move DOM focus to that item,
135
- * and `items` to reuse an event-scoped collection snapshot.
136
- */
137
165
  setActive(index, options = {}) {
138
166
  const { focus = false } = options;
139
167
  const items = options.items ?? this.#getItems();
@@ -150,7 +178,6 @@ function rovingMove(current, length, delta, wrap) {
150
178
  return Math.min(length - 1, Math.max(0, next));
151
179
  }
152
180
 
153
- // src/controllers/toolbar_controller.ts
154
181
  var STATE_ATTRIBUTES = ["disabled", "hidden"];
155
182
  var ToolbarController = class extends Controller {
156
183
  static targets = ["control"];
@@ -162,20 +189,16 @@ var ToolbarController = class extends Controller {
162
189
  #roving = new RovingTabindex(() => this.controlTargets);
163
190
  #composition = new CompositionTracker();
164
191
  #observer = null;
165
- /**
166
- * Live between `connect()` and `disconnect()`. Stimulus reports the *initial*
167
- * targets before `connect()` and re-reports them as disconnected after
168
- * `disconnect()`; gating on this keeps the target callbacks from clobbering
169
- * the authored Tab stop on mount and from resurrecting one after teardown.
170
- */
171
192
  #connected = false;
193
+ #morph = new MorphRenderWatcher(() => this.#ensureTabStop());
172
194
  connect() {
195
+ this.#morph.observe(this.element);
173
196
  this.#ensureTabStop();
174
197
  this.element.addEventListener("keydown", this.#onKeydown);
175
198
  this.element.addEventListener("focusin", this.#onFocusin);
176
199
  this.#composition.observe(this.element);
177
200
  if (typeof MutationObserver !== "undefined") {
178
- const observer = new MutationObserver(() => this.#ensureTabStop());
201
+ const observer = new MutationObserver(() => this.#morph.schedule());
179
202
  observer.observe(this.element, {
180
203
  subtree: true,
181
204
  childList: true,
@@ -190,6 +213,7 @@ var ToolbarController = class extends Controller {
190
213
  this.#connected = true;
191
214
  }
192
215
  disconnect() {
216
+ this.#morph.disconnect();
193
217
  this.#connected = false;
194
218
  this.element.removeEventListener("keydown", this.#onKeydown);
195
219
  this.element.removeEventListener("focusin", this.#onFocusin);
@@ -197,41 +221,21 @@ var ToolbarController = class extends Controller {
197
221
  this.#observer?.disconnect();
198
222
  this.#observer = null;
199
223
  }
200
- /**
201
- * A control added at runtime is dropped out of the Tab sequence first — a
202
- * fresh `<button>` is tabbable by default, which would make the group two Tab
203
- * stops — and then the lone stop is re-established.
204
- */
205
224
  controlTargetConnected(control) {
206
225
  if (!this.#connected) return;
207
226
  control.tabIndex = -1;
208
227
  this.#ensureTabStop();
209
228
  }
210
- /** Removing the control that held the Tab stop must not orphan the group. */
211
229
  controlTargetDisconnected() {
212
230
  if (!this.#connected) return;
213
231
  this.#ensureTabStop();
214
232
  }
215
- /**
216
- * Arrow/Home/End move focus and the single tab stop.
217
- *
218
- * Binding this per control with `data-action` is **optional** — the same
219
- * handling runs from the container's delegated listener. It stays a declared
220
- * action so per-control wiring keeps working; wiring both does not
221
- * double-move, because the delegated pass then yields on `defaultPrevented`.
222
- */
223
233
  onKeydown(event) {
224
234
  this.#handleKeydown(event);
225
235
  }
226
- /** Delegated counterpart of {@link onKeydown}; bound on the container. */
227
236
  #onKeydown = (event) => {
228
237
  this.#handleKeydown(event);
229
238
  };
230
- /**
231
- * Syncs the Tab stop to a control focused by other means (click, programmatic
232
- * `focus()`) so Tab re-entry returns there. A non-navigable control is ignored:
233
- * the lone Tab stop must never sit on something the user cannot operate.
234
- */
235
239
  #onFocusin = (event) => {
236
240
  const index = this.#indexOf(event.target);
237
241
  const control = index === -1 ? void 0 : this.controlTargets[index];
@@ -265,13 +269,6 @@ var ToolbarController = class extends Controller {
265
269
  event.preventDefault();
266
270
  if (target) this.#roving.setActive(this.controlTargets.indexOf(target), { focus: true });
267
271
  }
268
- /**
269
- * Re-establishes the single Tab stop: keep the current one while it is still
270
- * navigable, else hand it to the first navigable control. `-1` (no Tab stop at
271
- * all) is reached only when every control is unavailable, and is recovered
272
- * from as soon as one becomes navigable again. Idempotent by construction, so
273
- * connect, the target callbacks, the observer, and keydown can all call it.
274
- */
275
272
  #ensureTabStop() {
276
273
  const active = this.#roving.activeIndex;
277
274
  const activeEl = active === -1 ? null : this.controlTargets[active];
@@ -282,13 +279,6 @@ var ToolbarController = class extends Controller {
282
279
  const first = this.#navigableControls[0];
283
280
  this.#roving.setActive(first ? this.controlTargets.indexOf(first) : -1);
284
281
  }
285
- /**
286
- * First navigable control strictly in the `delta` direction from `fromIndex`,
287
- * scanning the **full** control list rather than the navigable subset so a
288
- * non-navigable origin is never a dead end. When the direction yields nothing
289
- * the origin keeps focus; when the origin itself is not navigable the first
290
- * navigable control is used instead, so an arrow key always escapes.
291
- */
292
282
  #nextNavigable(fromIndex, delta) {
293
283
  const controls = this.controlTargets;
294
284
  const length = controls.length;
@@ -304,37 +294,18 @@ var ToolbarController = class extends Controller {
304
294
  const origin = controls[fromIndex];
305
295
  return origin && this.#isNavigable(origin) ? origin : this.#navigableControls[0];
306
296
  }
307
- /** Index in `controlTargets` of the control owning `target` (it or a descendant). */
308
297
  #indexOf(target) {
309
298
  return ownerIndex(this.controlTargets, target);
310
299
  }
311
- /** Controls eligible for the roving tab stop (excludes native disabled / hidden). */
312
300
  get #navigableControls() {
313
301
  return this.controlTargets.filter((control) => this.#isNavigable(control));
314
302
  }
315
- /**
316
- * A control can hold the tab stop unless it is `hidden` or a disabled form
317
- * control. `aria-disabled` remains navigable per the APG; activation suppression
318
- * belongs to the control itself or its owning behavior. Native disabledness is
319
- * read from the `disabled` property, narrowed by an `in` check rather than
320
- * asserted since `<a>` and `<div role="button">` are legitimate controls that
321
- * are unaffected by a disabled fieldset. CSS-only visibility cannot be detected
322
- * headlessly and stays the consumer's responsibility.
323
- */
324
303
  #isNavigable(control) {
325
304
  if (this.#isHidden(control)) return false;
326
305
  if (!("disabled" in control)) return true;
327
306
  if (control.disabled === true) return false;
328
307
  return !inheritsFieldsetDisabled(control);
329
308
  }
330
- /**
331
- * Whether `control`, or anything between it and the toolbar root, is `hidden`.
332
- *
333
- * Reading only the control's own attribute lets an invisible control hold the
334
- * single Tab stop, which takes the *whole* toolbar out of the Tab sequence — an
335
- * ordinary `hidden` wrapper is enough. The walk stops at the root: a toolbar
336
- * inside a `hidden` region is already out of the page's Tab order.
337
- */
338
309
  #isHidden(control) {
339
310
  let node = control;
340
311
  while (node && node !== this.element) {
@@ -346,5 +317,4 @@ var ToolbarController = class extends Controller {
346
317
  };
347
318
 
348
319
  export { ToolbarController };
349
- //# sourceMappingURL=toolbar_controller.js.map
350
320
  //# sourceMappingURL=toolbar_controller.js.map