stimeo-ui 0.2.0 → 0.3.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +163 -0
  3. data/dist/controllers/accordion_controller.js +10 -0
  4. data/dist/controllers/alert_dialog_controller.js +318 -0
  5. data/dist/controllers/breadcrumb_controller.js +225 -13
  6. data/dist/controllers/calendar_controller.js +89 -22
  7. data/dist/controllers/carousel_controller.js +313 -0
  8. data/dist/controllers/clipboard_controller.js +144 -0
  9. data/dist/controllers/collapsible_controller.js +327 -0
  10. data/dist/controllers/color_picker_controller.js +252 -0
  11. data/dist/controllers/combobox_controller.js +162 -23
  12. data/dist/controllers/command_palette_controller.js +194 -17
  13. data/dist/controllers/context_menu_controller.js +32 -10
  14. data/dist/controllers/count_up_controller.js +8 -1
  15. data/dist/controllers/currency_input_controller.js +147 -0
  16. data/dist/controllers/data_grid_controller.js +246 -0
  17. data/dist/controllers/date_range_picker_controller.js +441 -0
  18. data/dist/controllers/dismissible_controller.js +117 -0
  19. data/dist/controllers/drawer_controller.js +630 -0
  20. data/dist/controllers/editable_controller.js +169 -0
  21. data/dist/controllers/file_dropzone_controller.js +165 -0
  22. data/dist/controllers/filter_controller.js +86 -0
  23. data/dist/controllers/flash_controller.js +36 -5
  24. data/dist/controllers/form_validation_controller.js +1 -1
  25. data/dist/controllers/highlight_controller.js +6 -4
  26. data/dist/controllers/intersection_controller.js +67 -19
  27. data/dist/controllers/lazy_frame_controller.js +54 -11
  28. data/dist/controllers/listbox_controller.js +257 -53
  29. data/dist/controllers/local_time_controller.js +2 -2
  30. data/dist/controllers/masonry_controller.js +142 -0
  31. data/dist/controllers/menu_controller.js +104 -17
  32. data/dist/controllers/menubar_controller.js +785 -0
  33. data/dist/controllers/multi_select_controller.js +755 -0
  34. data/dist/controllers/navigation_menu_controller.js +511 -0
  35. data/dist/controllers/number_input_controller.js +7 -0
  36. data/dist/controllers/otp_controller.js +18 -1
  37. data/dist/controllers/overflow_indicator_controller.js +246 -27
  38. data/dist/controllers/overflow_menu_controller.js +381 -57
  39. data/dist/controllers/pagination_controller.js +163 -32
  40. data/dist/controllers/password_reveal_controller.js +117 -0
  41. data/dist/controllers/persist_controller.js +6 -6
  42. data/dist/controllers/pointer_drag_controller.js +9 -1
  43. data/dist/controllers/popover_controller.js +2 -2
  44. data/dist/controllers/radio_group_controller.js +22 -3
  45. data/dist/controllers/range_slider_controller.js +192 -0
  46. data/dist/controllers/rating_controller.js +16 -2
  47. data/dist/controllers/read_more_controller.js +238 -0
  48. data/dist/controllers/resizable_controller.js +65 -1
  49. data/dist/controllers/roving_controller.js +17 -2
  50. data/dist/controllers/scroll_area_controller.js +101 -14
  51. data/dist/controllers/scroll_restore_controller.js +93 -0
  52. data/dist/controllers/scroll_visibility_controller.js +40 -6
  53. data/dist/controllers/scrollspy_controller.js +369 -74
  54. data/dist/controllers/separator_controller.js +96 -0
  55. data/dist/controllers/sidebar_controller.js +761 -0
  56. data/dist/controllers/skeleton_controller.js +1 -1
  57. data/dist/controllers/slider_controller.js +32 -6
  58. data/dist/controllers/sortable_controller.js +34 -3
  59. data/dist/controllers/spinner_controller.js +1 -1
  60. data/dist/controllers/stepper_controller.js +28 -12
  61. data/dist/controllers/stick_to_bottom_controller.js +9 -4
  62. data/dist/controllers/sticky_observer_controller.js +109 -20
  63. data/dist/controllers/switch_controller.js +1 -0
  64. data/dist/controllers/tabs_controller.js +26 -3
  65. data/dist/controllers/tags_input_controller.js +295 -0
  66. data/dist/controllers/theme_controller.js +42 -13
  67. data/dist/controllers/time_picker_controller.js +231 -0
  68. data/dist/controllers/toast_controller.js +40 -14
  69. data/dist/controllers/toggle_group_controller.js +23 -2
  70. data/dist/controllers/toolbar_controller.js +230 -31
  71. data/dist/controllers/transition_controller.js +153 -38
  72. data/dist/controllers/tree_view_controller.js +691 -0
  73. data/dist/index.js +4256 -915
  74. data/lib/stimeo/ui/version.rb +2 -3
  75. metadata +28 -2
@@ -2,6 +2,12 @@ import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  // src/controllers/menu_controller.ts
4
4
 
5
+ // src/utils/arrow_step.ts
6
+ function isReservedArrowChord(event, allow = []) {
7
+ if (!event.key.startsWith("Arrow")) return false;
8
+ return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
9
+ }
10
+
5
11
  // src/utils/escape_layer.ts
6
12
  function claimsWhileFocusWithin(element) {
7
13
  return () => {
@@ -159,18 +165,30 @@ var MenuController = class extends Controller {
159
165
  #timers = new SafeTimeout();
160
166
  /** Escape-stack membership while open; the shared resolver dismisses via it. */
161
167
  #escapeLayer = new EscapeLayer();
162
- /** Starts closed and registers delegated listeners. */
168
+ /**
169
+ * The item a click started on, recorded in the capture pass. Read once by the
170
+ * delegated bubble listener, which must not re-derive it: consumer handlers run
171
+ * in between and may detach the item.
172
+ */
173
+ #clickOwner = null;
174
+ /** Clicks already turned into an activation, so the two paths run it once. */
175
+ #activated = /* @__PURE__ */ new WeakSet();
176
+ /** Starts closed and registers activation / outside-click listeners. */
163
177
  connect() {
164
178
  this.close();
165
179
  this.element.addEventListener("click", this.#onItemClickCapture, true);
166
- document.addEventListener("click", this.#onOutsideClick);
180
+ this.element.addEventListener("click", this.#onDelegatedItemClick);
181
+ this.element.addEventListener("keydown", this.#onDelegatedItemKeydown);
182
+ document.addEventListener("click", this.#onOutsideClick, true);
167
183
  }
168
184
  /** Releases the listeners, stack membership, and any pending Tab-close task. */
169
185
  disconnect() {
170
186
  this.#timers.clearAll();
171
187
  this.#escapeLayer.deactivate();
172
188
  this.element.removeEventListener("click", this.#onItemClickCapture, true);
173
- document.removeEventListener("click", this.#onOutsideClick);
189
+ this.element.removeEventListener("click", this.#onDelegatedItemClick);
190
+ this.element.removeEventListener("keydown", this.#onDelegatedItemKeydown);
191
+ document.removeEventListener("click", this.#onOutsideClick, true);
174
192
  }
175
193
  /** Toggles the menu open/closed. Bound via `data-action` (click). */
176
194
  toggle() {
@@ -209,6 +227,8 @@ var MenuController = class extends Controller {
209
227
  * open and then immediately re-toggle the menu.
210
228
  */
211
229
  onTriggerKeydown(event) {
230
+ if (event.defaultPrevented) return;
231
+ if (isReservedArrowChord(event)) return;
212
232
  if (event.key === "ArrowDown") {
213
233
  event.preventDefault();
214
234
  this.open();
@@ -221,8 +241,18 @@ var MenuController = class extends Controller {
221
241
  }
222
242
  /** Implements roving focus and closing keys inside the menu. */
223
243
  onItemKeydown(event) {
244
+ this.#handleItemKeydown(event, event.currentTarget);
245
+ }
246
+ /**
247
+ * The roving-focus body, shared by the per-element action and its delegated
248
+ * twin. `from` is the item the key belongs to: the bound element for the
249
+ * action, the resolved owner of the event target for the delegate.
250
+ */
251
+ #handleItemKeydown(event, from) {
252
+ if (event.defaultPrevented) return;
253
+ if (isReservedArrowChord(event)) return;
224
254
  const items = this.#navigableItems;
225
- const currentIndex = items.indexOf(event.currentTarget);
255
+ const currentIndex = items.indexOf(from);
226
256
  switch (event.key) {
227
257
  case "ArrowDown":
228
258
  event.preventDefault();
@@ -246,8 +276,23 @@ var MenuController = class extends Controller {
246
276
  break;
247
277
  }
248
278
  }
249
- /** Closes the menu after an item is activated. Bound via `data-action`. */
250
- activate() {
279
+ /**
280
+ * Closes the menu after an item is activated. Bound via `data-action`, and
281
+ * also reached from the delegated listener.
282
+ *
283
+ * Markup that carries the per-element action *and* gets the delegate would run
284
+ * this twice for one gesture. `close()` writes the state hooks (`hidden`,
285
+ * `aria-expanded`), and an identical reassign still queues a MutationRecord,
286
+ * so a second pass is observable to anyone watching them. The event is
287
+ * therefore claimed: the path that gets there first does the work, the other
288
+ * one finds it claimed and returns. A programmatic call with no event always
289
+ * runs.
290
+ */
291
+ activate(event) {
292
+ if (event !== void 0) {
293
+ if (this.#activated.has(event)) return;
294
+ this.#activated.add(event);
295
+ }
251
296
  this.#closeAndRestore();
252
297
  }
253
298
  /** Closes and returns focus to the trigger (Escape / item-activation path). */
@@ -260,16 +305,59 @@ var MenuController = class extends Controller {
260
305
  if (this.#isOpen && !this.element.contains(event.target)) this.close();
261
306
  };
262
307
  /**
263
- * Captures clicks so `aria-disabled` commands cannot reach consumer handlers.
264
- * Native Enter/Space activation also synthesizes a click and is blocked here.
308
+ * Delegated twin of {@link onItemKeydown}, bound on the controller element.
309
+ *
310
+ * An item that arrives *after* connect — Overflow Menu moves toolbar controls
311
+ * into this menu at runtime — carries whatever `data-action` its author wrote,
312
+ * and that is exactly the binding a consumer forgets, because the markup that
313
+ * declares the item lives nowhere near the menu. Listening on the container
314
+ * makes membership in the `item` target enough to be operable. The per-element
315
+ * action stays supported and is not double-handled: see
316
+ * {@link #handleItemKeydown}.
317
+ */
318
+ #onDelegatedItemKeydown = (event) => {
319
+ const item = this.#itemFrom(event.target);
320
+ if (item === null) return;
321
+ this.#handleItemKeydown(event, item);
322
+ };
323
+ /**
324
+ * Delegated twin of {@link activate}, for the same reason as the keydown one.
325
+ *
326
+ * It works from the item the capture pass recorded, not from a fresh lookup:
327
+ * by the time a click bubbles up here, a consumer handler may already have
328
+ * detached the row it lives in, and `itemTargets` is a live query that would
329
+ * then resolve nothing — the activation would be lost and the menu left open
330
+ * over an item that no longer exists. `aria-disabled` items never reach this
331
+ * listener at all: the capture pass stops the click outright.
332
+ */
333
+ #onDelegatedItemClick = (event) => {
334
+ const item = this.#clickOwner;
335
+ this.#clickOwner = null;
336
+ if (item === null) return;
337
+ this.activate(event);
338
+ };
339
+ /** The item target that is, or contains, `node`; `null` when it is neither. */
340
+ #itemFrom(node) {
341
+ if (!(node instanceof Node)) return null;
342
+ return this.itemTargets.find((item) => item === node || item.contains(node)) ?? null;
343
+ }
344
+ /**
345
+ * First look at every click inside the controller, and the only point at which
346
+ * the DOM is still the one the user clicked. Two jobs: record which item owns
347
+ * the click for the delegate above, and stop an `aria-disabled` command before
348
+ * it reaches consumer handlers (native Enter/Space activation synthesizes a
349
+ * click and is blocked here too).
265
350
  */
266
351
  #onItemClickCapture = (event) => {
267
352
  const target = event.target;
268
- if (!(target instanceof Node)) return;
269
- const disabled = this.itemTargets.some(
270
- (item) => item.getAttribute("aria-disabled") === "true" && item.contains(target)
271
- );
272
- if (!disabled) return;
353
+ if (!(target instanceof Node)) {
354
+ this.#clickOwner = null;
355
+ return;
356
+ }
357
+ const item = this.#itemFrom(target);
358
+ this.#clickOwner = item;
359
+ if (item === null || item.getAttribute("aria-disabled") !== "true") return;
360
+ this.#clickOwner = null;
273
361
  event.preventDefault();
274
362
  event.stopImmediatePropagation();
275
363
  };
@@ -287,13 +375,12 @@ var MenuController = class extends Controller {
287
375
  return this.itemTargets.filter((item) => this.#isNavigable(item));
288
376
  }
289
377
  /**
290
- * An item can take roving focus unless it is `hidden`, `aria-disabled="true"`,
291
- * or a natively `disabled` form control. CSS-only visibility is not detectable
292
- * here and is the consumer's responsibility.
378
+ * An item can take roving focus unless it is `hidden` or a natively `disabled`
379
+ * form control. `aria-disabled` remains focusable for discoverability; capture
380
+ * suppresses activation. CSS-only visibility is the consumer's responsibility.
293
381
  */
294
382
  #isNavigable(item) {
295
383
  if (item.hasAttribute("hidden")) return false;
296
- if (item.getAttribute("aria-disabled") === "true") return false;
297
384
  return !item.disabled;
298
385
  }
299
386
  /** Whether the menu is currently visible. */