stimeo-ui 0.14.0 → 0.16.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +212 -0
  3. data/README.md +120 -0
  4. data/dist/cable/index.js +123 -29
  5. data/dist/controllers/accordion_controller.js +98 -9
  6. data/dist/controllers/announcer_controller.js +96 -62
  7. data/dist/controllers/auto_submit_controller.js +83 -7
  8. data/dist/controllers/avatar_controller.js +1 -1
  9. data/dist/controllers/breadcrumb_controller.js +38 -11
  10. data/dist/controllers/bulk_select_controller.js +7 -6
  11. data/dist/controllers/calendar_controller.js +340 -123
  12. data/dist/controllers/carousel_controller.js +263 -38
  13. data/dist/controllers/character_counter_controller.js +40 -2
  14. data/dist/controllers/checkbox_controller.js +81 -12
  15. data/dist/controllers/clipboard_controller.js +63 -5
  16. data/dist/controllers/collapsible_controller.js +99 -14
  17. data/dist/controllers/color_picker_controller.js +80 -34
  18. data/dist/controllers/combobox_controller.js +106 -16
  19. data/dist/controllers/command_palette_controller.js +35 -3
  20. data/dist/controllers/conditional_fields_controller.js +85 -17
  21. data/dist/controllers/confirm_controller.js +3 -0
  22. data/dist/controllers/context_menu_controller.js +32 -12
  23. data/dist/controllers/countdown_controller.js +129 -26
  24. data/dist/controllers/currency_input_controller.js +221 -67
  25. data/dist/controllers/data_grid_controller.js +195 -29
  26. data/dist/controllers/date_range_picker_controller.js +151 -30
  27. data/dist/controllers/dialog_controller.js +35 -8
  28. data/dist/controllers/direct_upload_controller.js +22 -4
  29. data/dist/controllers/dirty_form_controller.js +46 -13
  30. data/dist/controllers/dismissible_controller.js +1 -0
  31. data/dist/controllers/drawer_controller.js +54 -19
  32. data/dist/controllers/dropdown_controller.js +36 -9
  33. data/dist/controllers/editable_controller.js +34 -0
  34. data/dist/controllers/file_dropzone_controller.js +144 -51
  35. data/dist/controllers/filter_controller.js +20 -6
  36. data/dist/controllers/flash_controller.js +432 -71
  37. data/dist/controllers/focus_controller.js +1 -0
  38. data/dist/controllers/form_field_controller.js +7 -5
  39. data/dist/controllers/form_validation_controller.js +19 -13
  40. data/dist/controllers/frame_loading_controller.js +45 -8
  41. data/dist/controllers/highlight_controller.js +82 -25
  42. data/dist/controllers/hover_card_controller.js +40 -14
  43. data/dist/controllers/idle_controller.js +90 -5
  44. data/dist/controllers/input_mask_controller.js +65 -9
  45. data/dist/controllers/intersection_controller.js +3 -0
  46. data/dist/controllers/lazy_frame_controller.js +11 -2
  47. data/dist/controllers/listbox_controller.js +203 -45
  48. data/dist/controllers/local_time_controller.js +10 -5
  49. data/dist/controllers/masonry_controller.js +31 -15
  50. data/dist/controllers/menu_controller.js +45 -16
  51. data/dist/controllers/menubar_controller.js +58 -24
  52. data/dist/controllers/meter_controller.js +9 -5
  53. data/dist/controllers/multi_select_controller.js +278 -104
  54. data/dist/controllers/navigation_menu_controller.js +48 -15
  55. data/dist/controllers/nested_form_controller.js +37 -8
  56. data/dist/controllers/network_status_controller.js +9 -1
  57. data/dist/controllers/number_input_controller.js +124 -21
  58. data/dist/controllers/optimistic_controller.js +42 -5
  59. data/dist/controllers/otp_controller.js +198 -55
  60. data/dist/controllers/overflow_indicator_controller.js +115 -21
  61. data/dist/controllers/overflow_menu_controller.js +141 -44
  62. data/dist/controllers/pagination_controller.js +74 -28
  63. data/dist/controllers/password_reveal_controller.js +59 -2
  64. data/dist/controllers/persist_controller.js +30 -8
  65. data/dist/controllers/pointer_drag_controller.js +131 -52
  66. data/dist/controllers/popover_controller.js +45 -11
  67. data/dist/controllers/portal_controller.js +6 -2
  68. data/dist/controllers/preview_guard_controller.js +16 -1
  69. data/dist/controllers/progress_controller.js +8 -4
  70. data/dist/controllers/radio_group_controller.js +42 -17
  71. data/dist/controllers/range_slider_controller.js +88 -42
  72. data/dist/controllers/rating_controller.js +39 -15
  73. data/dist/controllers/read_more_controller.js +100 -7
  74. data/dist/controllers/reading_progress_controller.js +65 -19
  75. data/dist/controllers/relative_time_controller.js +10 -5
  76. data/dist/controllers/resizable_controller.js +82 -22
  77. data/dist/controllers/scroll_area_controller.js +75 -27
  78. data/dist/controllers/scroll_restore_controller.js +37 -16
  79. data/dist/controllers/scroll_visibility_controller.js +49 -30
  80. data/dist/controllers/scrollspy_controller.js +71 -26
  81. data/dist/controllers/separator_controller.js +66 -37
  82. data/dist/controllers/sidebar_controller.js +77 -18
  83. data/dist/controllers/skeleton_controller.js +6 -1
  84. data/dist/controllers/slider_controller.js +82 -47
  85. data/dist/controllers/smart_sticky_header_controller.js +60 -26
  86. data/dist/controllers/sortable_controller.js +17 -2
  87. data/dist/controllers/spinner_controller.js +10 -2
  88. data/dist/controllers/step_indicator_controller.js +18 -17
  89. data/dist/controllers/stepper_controller.js +101 -19
  90. data/dist/controllers/stick_to_bottom_controller.js +104 -8
  91. data/dist/controllers/submit_once_controller.js +45 -9
  92. data/dist/controllers/switch_controller.js +101 -10
  93. data/dist/controllers/tabs_controller.js +21 -2
  94. data/dist/controllers/tags_input_controller.js +209 -59
  95. data/dist/controllers/textarea_autosize_controller.js +29 -3
  96. data/dist/controllers/theme_controller.js +64 -14
  97. data/dist/controllers/time_picker_controller.js +23 -8
  98. data/dist/controllers/toast_controller.js +451 -105
  99. data/dist/controllers/toggle_group_controller.js +159 -23
  100. data/dist/controllers/toolbar_controller.js +32 -0
  101. data/dist/controllers/tooltip_controller.js +39 -13
  102. data/dist/controllers/transition_controller.js +4 -0
  103. data/dist/controllers/tree_view_controller.js +169 -16
  104. data/dist/index.js +5002 -1911
  105. data/dist/positioning/index.js +2 -0
  106. data/lib/stimeo/ui/version.rb +1 -1
  107. metadata +2 -2
@@ -160,6 +160,16 @@ var SafeTimeout = class extends TimerRegistry {
160
160
  }
161
161
  };
162
162
 
163
+ // src/utils/state_reason.ts
164
+ var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
165
+ var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
166
+ function stateReasonFor(event) {
167
+ if (!event) return "api";
168
+ if (FOCUS_EVENTS.has(event.type)) return "focus";
169
+ if (POINTER_EVENTS.has(event.type)) return "pointer";
170
+ return "user";
171
+ }
172
+
163
173
  // src/controllers/menu_controller.ts
164
174
  var MenuController = class extends Controller {
165
175
  static targets = ["trigger", "menu", "item"];
@@ -171,6 +181,7 @@ var MenuController = class extends Controller {
171
181
  "open",
172
182
  "toggle"
173
183
  ];
184
+ static events = ["close", "open"];
174
185
  #timers = new SafeTimeout();
175
186
  /** Escape-stack membership while open; the shared resolver dismisses via it. */
176
187
  #escapeLayer = new EscapeLayer();
@@ -182,16 +193,20 @@ var MenuController = class extends Controller {
182
193
  #clickOwner = null;
183
194
  /** Clicks already turned into an activation, so the two paths run it once. */
184
195
  #activated = /* @__PURE__ */ new WeakSet();
196
+ /** Whether state moves are reported: set once `connect()` settled the baseline. */
197
+ #reporting = false;
185
198
  /** Starts closed and registers activation / outside-click listeners. */
186
199
  connect() {
187
- this.close();
200
+ this.#close("api");
188
201
  this.element.addEventListener("click", this.#onItemClickCapture, true);
189
202
  this.element.addEventListener("click", this.#onDelegatedItemClick);
190
203
  this.element.addEventListener("keydown", this.#onDelegatedItemKeydown);
191
204
  document.addEventListener("click", this.#onOutsideClick, true);
205
+ this.#reporting = true;
192
206
  }
193
207
  /** Releases the listeners, stack membership, and any pending Tab-close task. */
194
208
  disconnect() {
209
+ this.#reporting = false;
195
210
  this.#timers.clearAll();
196
211
  this.#escapeLayer.deactivate();
197
212
  this.element.removeEventListener("click", this.#onItemClickCapture, true);
@@ -200,32 +215,44 @@ var MenuController = class extends Controller {
200
215
  document.removeEventListener("click", this.#onOutsideClick, true);
201
216
  }
202
217
  /** Toggles the menu open/closed. Bound via `data-action` (click). */
203
- toggle() {
218
+ toggle(event) {
204
219
  if (this.#isOpen) {
205
- this.close();
220
+ this.close(event);
206
221
  } else {
207
- this.open();
222
+ this.open(event);
208
223
  this.#focusFirst();
209
224
  }
210
225
  }
211
226
  /** Opens the menu and reflects the expanded state on the trigger. */
212
- open() {
227
+ open(event) {
228
+ this.#open(stateReasonFor(event));
229
+ }
230
+ /** Closes the menu and reflects the collapsed state on the trigger. */
231
+ close(event) {
232
+ this.#close(stateReasonFor(event));
233
+ }
234
+ /** Opens the menu, reflects the expanded state, and reports a move. */
235
+ #open(reason) {
213
236
  this.#timers.clearAll();
214
237
  if (!this.hasMenuTarget) return;
238
+ const was = this.#isOpen;
215
239
  this.#escapeLayer.activate(document, {
216
- onDismiss: () => this.#closeAndRestore(),
240
+ onDismiss: () => this.#closeAndRestore("escape"),
217
241
  claims: claimsWhileFocusWithin(this.element)
218
242
  });
219
243
  this.menuTarget.hidden = false;
220
244
  if (this.hasTriggerTarget) this.triggerTarget.setAttribute("aria-expanded", "true");
245
+ if (!was && this.#reporting) this.dispatch("open", { detail: { reason }, cancelable: false });
221
246
  }
222
- /** Closes the menu and reflects the collapsed state on the trigger. */
223
- close() {
247
+ /** Closes the menu, reflects the collapsed state, and reports a move. */
248
+ #close(reason) {
224
249
  this.#timers.clearAll();
225
250
  this.#escapeLayer.deactivate();
226
251
  if (!this.hasMenuTarget) return;
252
+ const was = this.#isOpen;
227
253
  this.menuTarget.hidden = true;
228
254
  if (this.hasTriggerTarget) this.triggerTarget.setAttribute("aria-expanded", "false");
255
+ if (was && this.#reporting) this.dispatch("close", { detail: { reason }, cancelable: false });
229
256
  }
230
257
  /**
231
258
  * Opens the menu with the keyboard per the APG (Down → first, Up → last).
@@ -240,11 +267,11 @@ var MenuController = class extends Controller {
240
267
  if (isReservedArrowChord(event)) return;
241
268
  if (event.key === "ArrowDown") {
242
269
  event.preventDefault();
243
- this.open();
270
+ this.open(event);
244
271
  this.#focusFirst();
245
272
  } else if (event.key === "ArrowUp") {
246
273
  event.preventDefault();
247
- this.open();
274
+ this.open(event);
248
275
  this.#focusLast();
249
276
  }
250
277
  }
@@ -281,7 +308,7 @@ var MenuController = class extends Controller {
281
308
  break;
282
309
  case "Tab":
283
310
  this.#timers.clearAll();
284
- this.#timers.set(() => this.close(), 0);
311
+ this.#timers.set(() => this.#close("focus"), 0);
285
312
  break;
286
313
  }
287
314
  }
@@ -302,21 +329,21 @@ var MenuController = class extends Controller {
302
329
  if (this.#activated.has(event)) return;
303
330
  this.#activated.add(event);
304
331
  }
305
- this.#closeAndRestore();
332
+ this.#closeAndRestore("select");
306
333
  }
307
334
  /** Closes and returns focus to the trigger (Escape / item-activation path). */
308
- #closeAndRestore() {
309
- this.close();
335
+ #closeAndRestore(reason) {
336
+ this.#close(reason);
310
337
  if (this.hasTriggerTarget) this.triggerTarget.focus();
311
338
  }
312
339
  /** Closes the menu when a click lands outside the controller's element. */
313
340
  #onOutsideClick = (event) => {
314
- if (this.#isOpen && !this.element.contains(event.target)) this.close();
341
+ if (this.#isOpen && !this.element.contains(event.target)) this.#close("outside");
315
342
  };
316
343
  /**
317
344
  * Delegated twin of {@link onItemKeydown}, bound on the controller element.
318
345
  *
319
- * An item that arrives *after* connect — Overflow Menu moves toolbar controls
346
+ * An item that arrives *after* connect — `stimeo--overflow-menu` moves toolbar controls
320
347
  * into this menu at runtime — carries whatever `data-action` its author wrote,
321
348
  * and that is exactly the binding a consumer forgets, because the markup that
322
349
  * declares the item lives nowhere near the menu. Listening on the container
@@ -371,10 +398,12 @@ var MenuController = class extends Controller {
371
398
  };
372
399
  /** Moves focus to the first navigable item (no-op if none). */
373
400
  #focusFirst() {
401
+ if (!this.#isOpen) return;
374
402
  this.#navigableItems[0]?.focus();
375
403
  }
376
404
  /** Moves focus to the last navigable item (no-op if none). */
377
405
  #focusLast() {
406
+ if (!this.#isOpen) return;
378
407
  const items = this.#navigableItems;
379
408
  items[items.length - 1]?.focus();
380
409
  }
@@ -200,6 +200,16 @@ var SafeTimeout = class extends TimerRegistry {
200
200
  }
201
201
  };
202
202
 
203
+ // src/utils/state_reason.ts
204
+ var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
205
+ var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
206
+ function stateReasonFor(event) {
207
+ if (!event) return "api";
208
+ if (FOCUS_EVENTS.has(event.type)) return "focus";
209
+ if (POINTER_EVENTS.has(event.type)) return "pointer";
210
+ return "user";
211
+ }
212
+
203
213
  // src/utils/typeahead.ts
204
214
  var TYPEAHEAD_RESET_MS = 500;
205
215
  var Typeahead = class {
@@ -263,6 +273,7 @@ var STATE_ATTRIBUTES = ["disabled", "hidden"];
263
273
  var MenubarController = class extends Controller {
264
274
  static targets = ["top", "menu", "item"];
265
275
  static actions = ["activate", "onItemKeydown", "onTopKeydown", "toggle"];
276
+ static events = ["close", "open"];
266
277
  /** Roving tabindex across the top-level menuitems (one Tab stop). */
267
278
  #roving = new RovingTabindex(() => this.topTargets);
268
279
  /** Typeahead query and its idle-reset timer (scoped to the open menu). */
@@ -281,6 +292,8 @@ var MenubarController = class extends Controller {
281
292
  * stop on mount and from resurrecting one after teardown.
282
293
  */
283
294
  #connected = false;
295
+ /** Whether state moves are reported: set once `connect()` settled the baseline. */
296
+ #reporting = false;
284
297
  /** The element inside the menubar that last took DOM focus, if any. */
285
298
  #focused = null;
286
299
  /**
@@ -314,7 +327,7 @@ var MenubarController = class extends Controller {
314
327
  * close the menu.
315
328
  */
316
329
  connect() {
317
- this.#closeAllMenus();
330
+ this.#closeAllMenus("api");
318
331
  this.#reconcile();
319
332
  this.element.addEventListener("click", this.#onDisabledClickCapture, true);
320
333
  this.element.addEventListener("focusin", this.#onFocusIn);
@@ -330,10 +343,12 @@ var MenubarController = class extends Controller {
330
343
  });
331
344
  }
332
345
  this.#connected = true;
346
+ this.#reporting = true;
333
347
  }
334
348
  /** Removes the listeners, stack membership, and any pending timer (typeahead / Tab close). */
335
349
  disconnect() {
336
350
  this.#connected = false;
351
+ this.#reporting = false;
337
352
  this.#escapeLayer.deactivate();
338
353
  this.#layerActive = false;
339
354
  this.element.removeEventListener("click", this.#onDisabledClickCapture, true);
@@ -395,10 +410,11 @@ var MenubarController = class extends Controller {
395
410
  /** Toggles a top item's menu. Bound via `data-action` (click on the top item). */
396
411
  toggle(event) {
397
412
  const top = event.currentTarget;
413
+ const reason = stateReasonFor(event);
398
414
  if (this.#isExpanded(top)) {
399
- this.#closeMenu(top);
415
+ this.#closeMenu(top, reason);
400
416
  } else {
401
- this.#openMenu(top, "first");
417
+ this.#openMenu(top, "first", reason);
402
418
  }
403
419
  }
404
420
  /** Keyboard handling while focus is on a top item. */
@@ -414,27 +430,27 @@ var MenubarController = class extends Controller {
414
430
  switch (event.key) {
415
431
  case "ArrowRight":
416
432
  event.preventDefault();
417
- this.#gotoTop(tops[(index + step + length) % length], anyOpen);
433
+ this.#gotoTop(tops[(index + step + length) % length], anyOpen, "user");
418
434
  break;
419
435
  case "ArrowLeft":
420
436
  event.preventDefault();
421
- this.#gotoTop(tops[(index - step + length) % length], anyOpen);
437
+ this.#gotoTop(tops[(index - step + length) % length], anyOpen, "user");
422
438
  break;
423
439
  case "ArrowDown":
424
440
  event.preventDefault();
425
- this.#openMenu(tops[index], "first");
441
+ this.#openMenu(tops[index], "first", "user");
426
442
  break;
427
443
  case "ArrowUp":
428
444
  event.preventDefault();
429
- this.#openMenu(tops[index], "last");
445
+ this.#openMenu(tops[index], "last", "user");
430
446
  break;
431
447
  case "Home":
432
448
  event.preventDefault();
433
- this.#gotoTop(tops[0], anyOpen);
449
+ this.#gotoTop(tops[0], anyOpen, "user");
434
450
  break;
435
451
  case "End":
436
452
  event.preventDefault();
437
- this.#gotoTop(tops[length - 1], anyOpen);
453
+ this.#gotoTop(tops[length - 1], anyOpen, "user");
438
454
  break;
439
455
  case "Tab":
440
456
  this.#closeMenusSoon();
@@ -492,14 +508,14 @@ var MenubarController = class extends Controller {
492
508
  const item = event.currentTarget;
493
509
  const menu = item.closest("[role='menu']");
494
510
  const top = this.#topFor(menu);
495
- this.#closeAllMenus();
511
+ this.#closeAllMenus("select");
496
512
  this.#focusTop(top);
497
513
  }
498
514
  /** Moves the roving focus to `top`, opening its menu when one was open. */
499
- #gotoTop(top, reopen) {
515
+ #gotoTop(top, reopen, reason) {
500
516
  if (!top) return;
501
517
  if (reopen) {
502
- this.#openMenu(top, "first");
518
+ this.#openMenu(top, "first", reason);
503
519
  } else {
504
520
  this.#roving.setActive(this.topTargets.indexOf(top), { focus: true });
505
521
  }
@@ -526,20 +542,23 @@ var MenubarController = class extends Controller {
526
542
  * this "open a menu" path. A dangling top is still an ordinary roving
527
543
  * destination while nothing is open.
528
544
  */
529
- #openMenu(top, focus) {
545
+ #openMenu(top, focus, reason) {
530
546
  if (!top) return;
531
547
  const menu = this.#menuFor(top);
532
548
  if (!menu || this.#isActivationBlocked(top)) {
533
549
  if (menu || !top.hasAttribute("aria-controls")) {
534
- this.#closeAllMenus();
550
+ this.#closeAllMenus(reason);
535
551
  this.#focusTop(top);
536
552
  }
537
553
  return;
538
554
  }
539
555
  this.#timers.clearAll();
540
- this.#closeAllMenus();
556
+ const was = this.#isExpanded(top);
557
+ this.#closeAllMenus(reason, top);
541
558
  menu.hidden = false;
542
559
  top.setAttribute("aria-expanded", "true");
560
+ if (!was) this.#report("open", top, menu, reason);
561
+ if (!this.#isExpanded(top)) return;
543
562
  this.#escapeLayer.activate(document, {
544
563
  onDismiss: () => this.#dismissOpenMenu(),
545
564
  claims: claimsWhileFocusWithin(this.element)
@@ -554,19 +573,32 @@ var MenubarController = class extends Controller {
554
573
  * no menu (a plain command in an otherwise popup-bearing menubar) is left alone
555
574
  * — stamping `aria-expanded="false"` on it would announce a popup it lacks.
556
575
  */
557
- #closeMenu(top) {
576
+ #closeMenu(top, reason, silent = false) {
558
577
  if (!top) return;
559
578
  const menu = this.#menuFor(top);
560
579
  if (!menu) return;
580
+ const was = this.#isExpanded(top);
561
581
  menu.hidden = true;
562
582
  top.setAttribute("aria-expanded", "false");
583
+ if (was && !silent) this.#report("close", top, menu, reason);
563
584
  this.#syncEscapeLayer();
564
585
  }
565
- /** Closes every menu and resets the typeahead query. */
566
- #closeAllMenus() {
567
- for (const top of this.topTargets) this.#closeMenu(top);
586
+ /**
587
+ * Closes every menu and resets the typeahead query. `except` still has its
588
+ * attributes rewritten — the caller is about to reopen it — but its move is
589
+ * left for that caller to report.
590
+ */
591
+ #closeAllMenus(reason, except = null) {
592
+ for (const top of this.topTargets) this.#closeMenu(top, reason, top === except);
568
593
  this.#typeahead.reset();
569
594
  }
595
+ /** Names the menu that moved, so a subscriber does not have to re-derive it. */
596
+ #report(name, top, menu, reason) {
597
+ if (!this.#reporting) return;
598
+ const detail = { reason, index: this.topTargets.indexOf(top), menu };
599
+ if (name === "open") this.dispatch("open", { detail, cancelable: false });
600
+ else this.dispatch("close", { detail, cancelable: false });
601
+ }
570
602
  /**
571
603
  * Closes every menu on the next task instead of synchronously.
572
604
  *
@@ -577,7 +609,7 @@ var MenubarController = class extends Controller {
577
609
  #closeMenusSoon() {
578
610
  if (!this.#isAnyOpen) return;
579
611
  this.#timers.clearAll();
580
- this.#timers.set(() => this.#closeAllMenus(), 0);
612
+ this.#timers.set(() => this.#closeAllMenus("focus"), 0);
581
613
  }
582
614
  /**
583
615
  * Escape path, invoked by the shared resolver: pressed inside an open menu it
@@ -589,11 +621,11 @@ var MenubarController = class extends Controller {
589
621
  const menu = ownerOf(this.menuTargets, document.activeElement);
590
622
  if (menu) {
591
623
  const top = this.#topFor(menu);
592
- this.#closeMenu(top);
624
+ this.#closeMenu(top, "escape");
593
625
  this.#focusTop(top);
594
626
  return;
595
627
  }
596
- this.#closeAllMenus();
628
+ this.#closeAllMenus("escape");
597
629
  }
598
630
  /** Opens the menu of the navigable top item `delta` steps from `menu`'s owner. */
599
631
  #moveToAdjacentMenu(menu, delta) {
@@ -603,7 +635,7 @@ var MenubarController = class extends Controller {
603
635
  const current = tops.indexOf(top);
604
636
  if (current === -1) return;
605
637
  const next = (current + delta + tops.length) % tops.length;
606
- this.#openMenu(tops[next], "first");
638
+ this.#openMenu(tops[next], "first", "user");
607
639
  }
608
640
  /**
609
641
  * Closes when a click lands outside the controller's element. Focus is left on
@@ -611,7 +643,9 @@ var MenubarController = class extends Controller {
611
643
  * docs.
612
644
  */
613
645
  #onOutsideClick = (event) => {
614
- if (this.#isAnyOpen && !this.element.contains(event.target)) this.#closeAllMenus();
646
+ if (this.#isAnyOpen && !this.element.contains(event.target)) {
647
+ this.#closeAllMenus("outside");
648
+ }
615
649
  };
616
650
  /**
617
651
  * Captures clicks so `aria-disabled` top items and commands cannot reach
@@ -76,8 +76,12 @@ function rangeFraction(value, min, max) {
76
76
  }
77
77
 
78
78
  // src/controllers/meter_controller.ts
79
- var OWNED_VALUE_TEXT = "data-stimeo--meter-owns-valuetext";
79
+ var OWNED_VALUE_TEXT = "owns-valuetext";
80
80
  var MeterController = class extends Controller {
81
+ /** The marker above, in the namespace this controller is registered under. */
82
+ get #ownedValueText() {
83
+ return `data-${this.identifier}-${OWNED_VALUE_TEXT}`;
84
+ }
81
85
  static targets = ["bar"];
82
86
  static values = {
83
87
  announceText: { type: String, default: "" },
@@ -157,7 +161,7 @@ var MeterController = class extends Controller {
157
161
  }
158
162
  /** Whether a threshold attribute is present (absent = no threshold). */
159
163
  #hasThreshold(name) {
160
- return this.element.hasAttribute(`data-stimeo--meter-${name}-value`);
164
+ return this.element.hasAttribute(`data-${this.identifier}-${name}-value`);
161
165
  }
162
166
  /**
163
167
  * Classifies `value` into a `low`/`medium`/`high` segment. Values at or below
@@ -199,16 +203,16 @@ var MeterController = class extends Controller {
199
203
  */
200
204
  #applyValueText({ value, ratio, state }) {
201
205
  if (this.valueTextValue.length === 0) {
202
- if (this.element.hasAttribute(OWNED_VALUE_TEXT)) {
206
+ if (this.element.hasAttribute(this.#ownedValueText)) {
203
207
  this.element.removeAttribute("aria-valuetext");
204
- this.element.removeAttribute(OWNED_VALUE_TEXT);
208
+ this.element.removeAttribute(this.#ownedValueText);
205
209
  }
206
210
  return;
207
211
  }
208
212
  const percent = Math.round(ratio * 100);
209
213
  const text = this.valueTextValue.replaceAll("{value}", String(value)).replaceAll("{percent}", String(percent)).replaceAll("{state}", state);
210
214
  this.element.setAttribute("aria-valuetext", text);
211
- this.element.setAttribute(OWNED_VALUE_TEXT, "");
215
+ this.element.setAttribute(this.#ownedValueText, "");
212
216
  }
213
217
  };
214
218