@morscherlab/mint-sdk 1.5.0-beta.7 → 1.5.0-beta.9

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 (105) hide show
  1. package/dist/{AlertBox-CexONPhA.js → AlertBox-Cai0w7gF.js} +3 -3
  2. package/dist/{AlertBox-CexONPhA.js.map → AlertBox-Cai0w7gF.js.map} +1 -1
  3. package/dist/{AppSidebar-C4qQLHcH.js → AppSidebar-C-Vq5x4I.js} +361 -93
  4. package/dist/AppSidebar-C-Vq5x4I.js.map +1 -0
  5. package/dist/{ConfirmDialog-D9QXFpsy.js → ConfirmDialog-BjFsOpBe.js} +2 -2
  6. package/dist/{ConfirmDialog-D9QXFpsy.js.map → ConfirmDialog-BjFsOpBe.js.map} +1 -1
  7. package/dist/{ExperimentPopover-DyjoXwry.js → ExperimentPopover-DYy_s0kI.js} +21 -20
  8. package/dist/ExperimentPopover-DYy_s0kI.js.map +1 -0
  9. package/dist/{ExperimentSelectorModal-CUkKmgfr.js → ExperimentSelectorModal-Bfh5bdkv.js} +5 -5
  10. package/dist/{ExperimentSelectorModal-CUkKmgfr.js.map → ExperimentSelectorModal-Bfh5bdkv.js.map} +1 -1
  11. package/dist/{SegmentedControl-C-sckoJZ.js → SegmentedControl-B8KgyOjd.js} +2 -2
  12. package/dist/{SegmentedControl-C-sckoJZ.js.map → SegmentedControl-B8KgyOjd.js.map} +1 -1
  13. package/dist/{SettingsModal-B1ltysmg.js → SettingsModal-DwfrD1ER.js} +7 -7
  14. package/dist/{SettingsModal-B1ltysmg.js.map → SettingsModal-DwfrD1ER.js.map} +1 -1
  15. package/dist/__tests__/components/AppTopBarCompact.test.d.ts +1 -0
  16. package/dist/__tests__/components/AppTopBarCompactReset.test.d.ts +1 -0
  17. package/dist/__tests__/components/AutoGroupModal.experimentLoad.test.d.ts +1 -0
  18. package/dist/components/AppTopBar.compact.d.ts +5 -0
  19. package/dist/components/AppTopBar.vue.d.ts +17 -17
  20. package/dist/components/PluginWorkspaceView.vue.d.ts +4 -0
  21. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -2
  22. package/dist/components/internal/LucideIconInternal.vue.d.ts +17 -0
  23. package/dist/composables/appLayoutSidebar.d.ts +9 -0
  24. package/dist/composables/usePluginWorkspace.d.ts +2 -0
  25. package/dist/composables/useVersionedSave.d.ts +5 -2
  26. package/dist/{controlSchemaModel-C_XqpkgE.js → controlSchemaModel-BtFNZj8t.js} +2 -2
  27. package/dist/{controlSchemaModel-C_XqpkgE.js.map → controlSchemaModel-BtFNZj8t.js.map} +1 -1
  28. package/dist/{data-CgIcnf9D.js → data-CJztFns8.js} +31 -15
  29. package/dist/{data-CgIcnf9D.js.map → data-CJztFns8.js.map} +1 -1
  30. package/dist/data.js +8 -8
  31. package/dist/{experimentStatus-DCn2QKG7.js → experimentStatus-Deokk7Br.js} +2 -2
  32. package/dist/{experimentStatus-DCn2QKG7.js.map → experimentStatus-Deokk7Br.js.map} +1 -1
  33. package/dist/index.js +14 -14
  34. package/dist/{install-DmyjbTJ5.js → install-Bk02tJ5M.js} +14 -15
  35. package/dist/{install-DmyjbTJ5.js.map → install-Bk02tJ5M.js.map} +1 -1
  36. package/dist/install.js +1 -1
  37. package/dist/{lab-BcX5JBSY.js → lab-BYhb_cJI.js} +22 -14
  38. package/dist/lab-BYhb_cJI.js.map +1 -0
  39. package/dist/lab.js +4 -4
  40. package/dist/{records-BAymyHAn.js → records-GtiVMlTQ.js} +198 -62
  41. package/dist/records-GtiVMlTQ.js.map +1 -0
  42. package/dist/{runtime-CjfAxufu.js → runtime-aZPUPxoB.js} +40 -19
  43. package/dist/runtime-aZPUPxoB.js.map +1 -0
  44. package/dist/runtime.js +6 -6
  45. package/dist/{shell-XDqDu_UD.js → shell-Bk9mD4Lc.js} +5 -5
  46. package/dist/{shell-XDqDu_UD.js.map → shell-Bk9mD4Lc.js.map} +1 -1
  47. package/dist/shell.js +5 -5
  48. package/dist/styles.css +138 -13
  49. package/dist/types/components.d.ts +22 -2
  50. package/dist/types/index.d.ts +1 -1
  51. package/dist/{useDropdownState-uFIz6Yv8.js → useDropdownState-CcBzUYZ6.js} +14 -5
  52. package/dist/useDropdownState-CcBzUYZ6.js.map +1 -0
  53. package/dist/{useExperimentSamples-DEDAKa2E.js → useExperimentSamples-CpNXvpUn.js} +3 -3
  54. package/dist/{useExperimentSamples-DEDAKa2E.js.map → useExperimentSamples-CpNXvpUn.js.map} +1 -1
  55. package/dist/{useListReorder-W1bzK_bo.js → useListReorder-Bp4EAI2w.js} +3 -3
  56. package/dist/{useListReorder-W1bzK_bo.js.map → useListReorder-Bp4EAI2w.js.map} +1 -1
  57. package/dist/{useRackEditor-Bmtuo0aj.js → useRackEditor-i2bdPUFf.js} +8 -8
  58. package/dist/{useRackEditor-Bmtuo0aj.js.map → useRackEditor-i2bdPUFf.js.map} +1 -1
  59. package/dist/{useRealtime-jOetF2mM.js → useRealtime-BLQEjbuq.js} +3 -3
  60. package/dist/{useRealtime-jOetF2mM.js.map → useRealtime-BLQEjbuq.js.map} +1 -1
  61. package/dist/useRequest-CXyPw3eh.js +76 -0
  62. package/dist/useRequest-CXyPw3eh.js.map +1 -0
  63. package/dist/utils/deprecation.d.ts +2 -0
  64. package/dist/utils/lucideIcon.d.ts +4 -0
  65. package/package.json +1 -1
  66. package/src/__tests__/components/AppTopBar.test.ts +102 -20
  67. package/src/__tests__/components/AppTopBarCompact.test.ts +65 -0
  68. package/src/__tests__/components/AppTopBarCompactReset.test.ts +24 -0
  69. package/src/__tests__/components/AutoGroupModal.experimentLoad.test.ts +58 -0
  70. package/src/__tests__/components/BaseModal.test.ts +33 -2
  71. package/src/__tests__/components/PluginWorkspaceView.test.ts +20 -1
  72. package/src/__tests__/composables/useJobCenter.test.ts +35 -0
  73. package/src/__tests__/composables/useVersionedSave.test.ts +71 -6
  74. package/src/__tests__/styles/menusShellRefresh.test.ts +10 -0
  75. package/src/components/AppLayout.story.vue +5 -5
  76. package/src/components/AppLayout.vue +13 -1
  77. package/src/components/AppTopBar.compact.ts +60 -0
  78. package/src/components/AppTopBar.story.vue +39 -14
  79. package/src/components/AppTopBar.vue +222 -89
  80. package/src/components/BaseModal.vue +16 -5
  81. package/src/components/ExperimentPopover.vue +1 -1
  82. package/src/components/GeneratedPluginView.vue +0 -1
  83. package/src/components/PluginWorkspaceView.vue +10 -1
  84. package/src/components/internal/AppTopBarPageSelectorInternal.vue +12 -0
  85. package/src/components/internal/AutoGroupModalInternal.vue +14 -3
  86. package/src/components/internal/LucideIconInternal.vue +23 -0
  87. package/src/composables/appLayoutSidebar.ts +11 -0
  88. package/src/composables/useJobCenter.ts +21 -1
  89. package/src/composables/usePluginWorkspace.ts +15 -4
  90. package/src/composables/useVersionedSave.ts +32 -7
  91. package/src/styles/components/app-layout.css +5 -0
  92. package/src/styles/components/app-top-bar.css +130 -9
  93. package/src/types/components.ts +23 -2
  94. package/src/types/index.ts +1 -0
  95. package/src/utils/deprecation.ts +8 -0
  96. package/src/utils/lucideIcon.ts +25 -0
  97. package/src/utils/lucideIconIndex.d.ts +2 -0
  98. package/dist/AppSidebar-C4qQLHcH.js.map +0 -1
  99. package/dist/ExperimentPopover-DyjoXwry.js.map +0 -1
  100. package/dist/lab-BcX5JBSY.js.map +0 -1
  101. package/dist/records-BAymyHAn.js.map +0 -1
  102. package/dist/runtime-CjfAxufu.js.map +0 -1
  103. package/dist/useDropdownState-uFIz6Yv8.js.map +0 -1
  104. package/dist/useRequest-D9-3BS-3.js +0 -215
  105. package/dist/useRequest-D9-3BS-3.js.map +0 -1
package/dist/shell.js CHANGED
@@ -1,8 +1,8 @@
1
- import { i as useEventListener, r as useFocusTrap } from "./useDropdownState-uFIz6Yv8.js";
2
- import { E as useMenuKeyboard, H as useListSelection, R as useRovingFocus, U as useSelectionLimit } from "./records-BAymyHAn.js";
3
- import { B as compareSortValues, V as useSortedItems, i as useReorderAnnouncer, n as reorderList, r as useListReorder, t as REORDER_ANNOUNCER_STYLE } from "./useListReorder-W1bzK_bo.js";
4
- import { C as usePluginWorkspace, S as AppLayout_default, _ as provideOnboarding, f as AppTopBar_default, g as defineOnboarding, h as ONBOARDING_KEY, m as ANCHOR_WAIT_MS, t as AppSidebar_default, v as useOnboarding } from "./AppSidebar-C4qQLHcH.js";
1
+ import { i as useEventListener, r as useFocusTrap } from "./useDropdownState-CcBzUYZ6.js";
2
+ import { D as useMenuKeyboard, U as useListSelection, W as useSelectionLimit, z as useRovingFocus } from "./records-GtiVMlTQ.js";
3
+ import { B as compareSortValues, V as useSortedItems, i as useReorderAnnouncer, n as reorderList, r as useListReorder, t as REORDER_ANNOUNCER_STYLE } from "./useListReorder-Bp4EAI2w.js";
4
+ import { S as AppLayout_default, _ as provideOnboarding, f as AppTopBar_default, g as defineOnboarding, h as ONBOARDING_KEY, m as ANCHOR_WAIT_MS, t as AppSidebar_default, v as useOnboarding, w as usePluginWorkspace } from "./AppSidebar-C-Vq5x4I.js";
5
5
  import { t as useDebouncedWatch } from "./useDebouncedWatch-BPwyl6eS.js";
6
6
  import { t as useExpansionSet } from "./useExpansionSet-CvoNMZJi.js";
7
- import { a as useManualLayoutResize, i as resizedVerticalSplitPercent, n as resizedLeadingPanelWidth, o as LayoutResizeHandle_default, r as resizedTrailingPanelWidth } from "./shell-XDqDu_UD.js";
7
+ import { a as useManualLayoutResize, i as resizedVerticalSplitPercent, n as resizedLeadingPanelWidth, o as LayoutResizeHandle_default, r as resizedTrailingPanelWidth } from "./shell-Bk9mD4Lc.js";
8
8
  export { ANCHOR_WAIT_MS, AppLayout_default as AppLayout, AppSidebar_default as AppSidebar, AppTopBar_default as AppTopBar, LayoutResizeHandle_default as LayoutResizeHandle, ONBOARDING_KEY, REORDER_ANNOUNCER_STYLE, compareSortValues, defineOnboarding, provideOnboarding, reorderList, resizedLeadingPanelWidth, resizedTrailingPanelWidth, resizedVerticalSplitPercent, useDebouncedWatch, useEventListener, useExpansionSet, useFocusTrap, useListReorder, useListSelection, useManualLayoutResize, useMenuKeyboard, useOnboarding, usePluginWorkspace, useReorderAnnouncer, useRovingFocus, useSelectionLimit, useSortedItems };
package/dist/styles.css CHANGED
@@ -122,12 +122,12 @@
122
122
  overflow-wrap: anywhere;
123
123
  }
124
124
 
125
- .mint-generated-workspace[data-v-90271c34] {
125
+ .mint-generated-workspace[data-v-7ba9e342] {
126
126
  min-width: 0;
127
127
  min-height: 0;
128
128
  overflow-y: auto;
129
129
  }
130
- .mint-generated-workspace__loading[data-v-90271c34] {
130
+ .mint-generated-workspace__loading[data-v-7ba9e342] {
131
131
  display: flex;
132
132
  flex: 1;
133
133
  align-items: center;
@@ -135,12 +135,12 @@
135
135
  gap: 0.75rem;
136
136
  color: var(--text-secondary);
137
137
  }
138
- .mint-generated-workspace__resources[data-v-90271c34] {
138
+ .mint-generated-workspace__resources[data-v-7ba9e342] {
139
139
  color: var(--text-muted);
140
140
  font-family: var(--font-mono);
141
141
  font-size: 0.75rem;
142
142
  }
143
- .mint-generated-workspace__description[data-v-90271c34] {
143
+ .mint-generated-workspace__description[data-v-7ba9e342] {
144
144
  margin: 0 0 var(--mint-gutter);
145
145
  color: var(--text-secondary);
146
146
  }
@@ -1707,6 +1707,10 @@ button, input, select, textarea {
1707
1707
  }
1708
1708
  }
1709
1709
 
1710
+ /* An AppTopBar in the layout renders the drawer button itself. */
1711
+ .mint-layout--responsive-sidebar:has(.mint-topbar__sidebar-toggle) .mint-layout__sidebar-toggle {
1712
+ display: none;
1713
+ }
1710
1714
  /* AppSidebar Component Styles */
1711
1715
 
1712
1716
  .mint-sidebar {
@@ -2266,6 +2270,8 @@ button, input, select, textarea {
2266
2270
  padding: 0.75rem 1rem;
2267
2271
  background-color: var(--bg-secondary);
2268
2272
  box-sizing: border-box;
2273
+ /* The two-row layout follows the bar's own width, not the window's. */
2274
+ container: mint-topbar / inline-size;
2269
2275
  }
2270
2276
 
2271
2277
  /* Main container layout — equal side columns keep the centre nav on the bar
@@ -2326,11 +2332,62 @@ button, input, select, textarea {
2326
2332
  grid-column: 3;
2327
2333
  display: flex;
2328
2334
  align-items: center;
2329
- gap: 0.125rem;
2335
+ gap: 0.375rem;
2330
2336
  min-width: 0;
2331
2337
  justify-content: flex-end;
2332
2338
  }
2333
2339
 
2340
+ /* Context group (standalone badge, run slot, onboarding, plugin buttons), then
2341
+ the fixed tail: theme, settings and the host's account controls, always last. */
2342
+ .mint-topbar__context,
2343
+ .mint-topbar__tail {
2344
+ display: flex;
2345
+ align-items: center;
2346
+ gap: 0.125rem;
2347
+ min-width: 0;
2348
+ }
2349
+
2350
+ .mint-topbar__tail {
2351
+ flex-shrink: 0;
2352
+ }
2353
+
2354
+ /* The deprecated #actions slot keeps a fixed width, so its content cannot push the bar. */
2355
+ .mint-topbar__legacy-actions {
2356
+ display: inline-flex;
2357
+ align-items: center;
2358
+ max-width: 16rem;
2359
+ overflow: hidden;
2360
+ }
2361
+
2362
+ /* Below 1024px of bar width: row 1 is identity + tail, row 2 the pills + context.
2363
+ Row 1 never holds more than the identity, so the tail never moves. */
2364
+ @container mint-topbar (max-width: 1023.98px) {
2365
+ .mint-topbar__container--stackable {
2366
+ /* The pills keep their width while it fits; the context group takes the rest and its experiment
2367
+ name truncates. On a bar narrower than the pills they overflow their cell, never the tail. */
2368
+ grid-template-columns: minmax(0, max-content) minmax(0, 1fr) auto;
2369
+ grid-template-areas:
2370
+ "start start tail"
2371
+ "center context context";
2372
+ row-gap: 0.375rem;
2373
+ }
2374
+
2375
+ .mint-topbar__container--stackable .mint-topbar__start { grid-area: start; }
2376
+ .mint-topbar__container--stackable .mint-topbar__right { display: contents; }
2377
+ .mint-topbar__container--stackable .mint-topbar__tail { grid-area: tail; }
2378
+
2379
+ .mint-topbar__container--stackable .mint-topbar__center {
2380
+ grid-area: center;
2381
+ justify-content: flex-start;
2382
+ }
2383
+
2384
+ .mint-topbar__container--stackable .mint-topbar__context {
2385
+ /* Stretch, not justify-self: end, which would size the group to its nowrap min-content. */
2386
+ grid-area: context;
2387
+ justify-content: flex-end;
2388
+ }
2389
+ }
2390
+
2334
2391
  /* Logo styles */
2335
2392
  .mint-topbar__logo {
2336
2393
  display: flex;
@@ -2424,17 +2481,20 @@ button, input, select, textarea {
2424
2481
  .mint-topbar__plugin-name,
2425
2482
  .mint-topbar__plugin-subtitle,
2426
2483
  .mint-topbar-title,
2427
- .mint-topbar-subtitle {
2484
+ .mint-topbar-subtitle,
2485
+ .mint-topbar__title-only {
2428
2486
  overflow: hidden;
2429
2487
  text-overflow: ellipsis;
2430
2488
  white-space: nowrap;
2431
2489
  }
2432
2490
 
2433
- /* The settings button has the IconButton ghost look: 32px, --radius-md,
2434
- 16px icon, neutral ink, offset focus ring. */
2435
- .mint-topbar__settings-btn {
2491
+ /* The settings button, plugin icon buttons and the drawer button have the
2492
+ IconButton ghost look: 32px, --radius-md, 16px icon, neutral ink, offset focus ring. */
2493
+ .mint-topbar__settings-btn,
2494
+ .mint-topbar__icon-btn {
2436
2495
  position: relative;
2437
2496
  display: inline-flex;
2497
+ flex-shrink: 0;
2438
2498
  align-items: center;
2439
2499
  justify-content: center;
2440
2500
  width: 2rem;
@@ -2449,23 +2509,62 @@ button, input, select, textarea {
2449
2509
  transition: background-color var(--mint-transition), color var(--mint-transition);
2450
2510
  }
2451
2511
 
2452
- .mint-topbar__settings-btn:hover {
2512
+ .mint-topbar__settings-btn:hover,
2513
+ .mint-topbar__icon-btn:hover:not(:disabled) {
2453
2514
  background: var(--bg-hover);
2454
2515
  color: var(--text-primary);
2455
2516
  }
2456
2517
 
2457
- .mint-topbar__settings-btn:focus-visible {
2518
+ .mint-topbar__settings-btn:focus-visible,
2519
+ .mint-topbar__icon-btn:focus-visible {
2458
2520
  outline: none;
2459
2521
  box-shadow: var(--focus-ring-offset);
2460
2522
  }
2461
2523
 
2462
- .mint-topbar__settings-icon {
2524
+ .mint-topbar__icon-btn:disabled {
2525
+ cursor: not-allowed;
2526
+ opacity: 0.5;
2527
+ }
2528
+
2529
+ .mint-topbar__settings-icon,
2530
+ .mint-topbar__icon-btn-icon {
2463
2531
  width: 1rem;
2464
2532
  height: 1rem;
2465
2533
  }
2466
2534
 
2535
+ /* Status dot of a plugin icon button (TopBarAction.dot). */
2536
+ .mint-topbar__icon-btn-dot {
2537
+ position: absolute;
2538
+ top: 0.3125rem;
2539
+ right: 0.3125rem;
2540
+ width: 0.4375rem;
2541
+ height: 0.4375rem;
2542
+ border-radius: 50%;
2543
+ box-shadow: 0 0 0 2px var(--bg-secondary);
2544
+ background: var(--text-muted);
2545
+ }
2546
+
2547
+ .mint-topbar__icon-btn-dot--primary,
2548
+ .mint-topbar__icon-btn-dot--cta { background: var(--color-primary); }
2549
+ .mint-topbar__icon-btn-dot--success { background: var(--mint-success); }
2550
+ .mint-topbar__icon-btn-dot--warning { background: var(--mint-warning); }
2551
+ .mint-topbar__icon-btn-dot--error { background: var(--mint-error); }
2552
+ .mint-topbar__icon-btn-dot--info { background: var(--mint-info); }
2553
+
2554
+ /* The drawer button shows only while the AppLayout sidebar is a drawer. */
2555
+ .mint-topbar__sidebar-toggle {
2556
+ display: none;
2557
+ }
2558
+
2559
+ @media (max-width: 1023.98px) {
2560
+ .mint-topbar__sidebar-toggle {
2561
+ display: inline-flex;
2562
+ }
2563
+ }
2564
+
2467
2565
  @media (prefers-reduced-motion: reduce) {
2468
- .mint-topbar__settings-btn {
2566
+ .mint-topbar__settings-btn,
2567
+ .mint-topbar__icon-btn {
2469
2568
  transition: none;
2470
2569
  }
2471
2570
  }
@@ -2647,6 +2746,32 @@ button, input, select, textarea {
2647
2746
  clip-path: inset(50%);
2648
2747
  white-space: nowrap;
2649
2748
  }
2749
+
2750
+ /* ─── Labels or icons, by fit ───
2751
+ Save and Jobs show their words while the content fits; AppTopBar.compact.ts
2752
+ sets --compact when it does not (a name is cut, the experiment chip is
2753
+ squeezed or a group overflows), and they keep only their icons. Plugin
2754
+ buttons with an icon are always icon-only. Accessible names and tooltips
2755
+ keep the words. */
2756
+ .mint-topbar--compact .mint-experiment-popover__save {
2757
+ width: 32px;
2758
+ padding: 0;
2759
+ justify-content: center;
2760
+ }
2761
+
2762
+ .mint-topbar--compact .mint-experiment-popover__save-label,
2763
+ .mint-topbar--compact .mint-topbar__job-label {
2764
+ display: none;
2765
+ }
2766
+
2767
+ .mint-topbar--compact .mint-topbar__job--idle {
2768
+ padding-inline: 0.5rem;
2769
+ }
2770
+
2771
+ /* One total width cap for the experiment chip, code and chevron included. */
2772
+ .mint-topbar .mint-experiment-popover__trigger {
2773
+ max-width: 16rem;
2774
+ }
2650
2775
  /* BaseButton Component Styles */
2651
2776
 
2652
2777
  .mint-button {
@@ -169,6 +169,7 @@ export type PillNavItemInput = (Omit<PillNavItem, 'children'> & {
169
169
  export interface PageSelectorItem {
170
170
  id: string;
171
171
  label: string;
172
+ /** A Lucide icon name (`'activity'`, `'flask-conical'`) or a plugin icon (path, data URL, `mint-icon` JSON). Without one the page shows its first letter. */
172
173
  icon?: string;
173
174
  hint?: string;
174
175
  to?: string;
@@ -186,12 +187,31 @@ export interface PluginWorkspaceTopBarSpec {
186
187
  logo?: boolean;
187
188
  /** Centered pill navigation for the local modes of the current route (bind `v-model:pill`). */
188
189
  pills?: PillNavItemInput[];
189
- /** Show the theme toggle. */
190
+ /** @deprecated Ignored: the theme toggle always shows. Removed in 2.0. */
190
191
  themeToggle?: boolean;
191
- /** Settings button and modal; `false` hides the button. A getter can switch it per user. */
192
+ /**
193
+ * Settings modal of the always-present settings button. `false` (or a getter that returns it)
194
+ * gives the modal only its Appearance tab; it no longer hides the button.
195
+ */
192
196
  settings?: import('vue').MaybeRefOrGetter<false | TopBarSettingsConfig>;
193
197
  /** Standalone badge text when the plugin runs outside the platform; `false` hides it. */
194
198
  standaloneLabel?: false | string;
199
+ /** Plugin buttons, before the theme and settings buttons. The first two show; the rest go into a More menu. */
200
+ actions?: TopBarAction[];
201
+ }
202
+ /** One plugin button of the top bar. With `icon` it is an icon button and `label` is its accessible name and tooltip. */
203
+ export interface TopBarAction {
204
+ id: string;
205
+ label: string;
206
+ /** A Lucide icon name, such as `'database'`. */
207
+ icon?: string;
208
+ /** A small status dot on the button. */
209
+ dot?: Tone;
210
+ /** Tooltip; defaults to `label`. */
211
+ title?: string;
212
+ loading?: boolean;
213
+ disabled?: boolean;
214
+ onSelect: () => void;
195
215
  }
196
216
  /** Sidebar part of a PluginWorkspaceView spec; also the `:spec` of AppSidebar. */
197
217
  export interface PluginWorkspaceSidebarSpec {
@@ -1,4 +1,4 @@
1
- export type { Tone, ContainerDirection, ButtonVariant, ButtonSize, ActionMenuItem, InputType, ModalSize, ModalVariant, ModalLayout, ModalTab, Toast, ToastAction, ToastOptions, OptionPrimitive, SelectOption, SelectOptionInput, FormFieldProps, SectionCardAccent, SectionCardDivider, SidebarToolSection, SidebarToolSectionAction, CollapsibleState, TopBarVariant, TopBarSettingsConfig, PillNavOption, PillNavOptionInput, PillNavItem, PillNavItemInput, PageSelectorItem, PageSelectorItemInput, PluginWorkspaceTopBarSpec, PluginWorkspaceSidebarSpec, PluginWorkspaceViewSpec, WellPlateFormat, WellState, WellPlateSelectionMode, WellPlateSize, WellShape, Well, WellClassValue, WellClassResolver, WellStyleResolver, HeatmapColorScale, HeatmapConfig, SlotPosition, WellExtendedData, WellEditData, WellEditField, WellSampleDropData, WellSampleDropContext, WellSampleDropParser, WellLegendItem, PlateCondition, ColumnCondition, RowCondition, Rack, PlateEditorClearConfirm, WellPlateSpec, PlateEditorSpec, SampleType, ProtocolStepType, ProtocolStepStatus, ProtocolStep, SampleGroup, FileUploaderMode, ChartLegendItem, SegmentedControlVariant, SegmentedControlSize, PillVariant, PillSize, CalendarSelectionMode, CalendarMarker, CalendarDayContext, SortDirection, SortState, DataFrameColumn, DataFrameSpec, SpinnerSize, DividerSpacing, ProgressSize, AvatarSize, EmptyStateSize, BreadcrumbItem, BreadcrumbItemInput, TooltipPosition, SettingsTab, SettingsTabInput, SettingsModalLayout, SettingsGroup, SettingsModalSchema, SettingsUserType, SettingsAudience, SettingsAudienceInput, NumberNotation, UnitOption, WizardStep, WizardStepState, AuditEntryType, AuditEntry, BatchItemStatus, BatchItem, BatchSummary, TimeSlotDisabledFn, TimeRange, ScheduleView, ScheduleEventStatus, ScheduleEvent, ScheduleBlockedSlot, ScheduleSlotContext, ScheduleEventCreateContext, ScheduleEventUpdateContext, SummarySectionItem, SummarySection, SummaryData, ResourceStatus, ResourceSpec, MoleculeData, ExperimentStatus, DatePreset, ExperimentSortField, ExperimentTypeOption, ExperimentSummary, ExperimentListResponse, ExperimentFilters, TreeNodeType, BadgeVariant, TreeNode, } from './components';
1
+ export type { Tone, ContainerDirection, ButtonVariant, ButtonSize, ActionMenuItem, InputType, ModalSize, ModalVariant, ModalLayout, ModalTab, Toast, ToastAction, ToastOptions, OptionPrimitive, SelectOption, SelectOptionInput, FormFieldProps, SectionCardAccent, SectionCardDivider, SidebarToolSection, SidebarToolSectionAction, CollapsibleState, TopBarVariant, TopBarSettingsConfig, PillNavOption, PillNavOptionInput, PillNavItem, PillNavItemInput, PageSelectorItem, PageSelectorItemInput, PluginWorkspaceTopBarSpec, TopBarAction, PluginWorkspaceSidebarSpec, PluginWorkspaceViewSpec, WellPlateFormat, WellState, WellPlateSelectionMode, WellPlateSize, WellShape, Well, WellClassValue, WellClassResolver, WellStyleResolver, HeatmapColorScale, HeatmapConfig, SlotPosition, WellExtendedData, WellEditData, WellEditField, WellSampleDropData, WellSampleDropContext, WellSampleDropParser, WellLegendItem, PlateCondition, ColumnCondition, RowCondition, Rack, PlateEditorClearConfirm, WellPlateSpec, PlateEditorSpec, SampleType, ProtocolStepType, ProtocolStepStatus, ProtocolStep, SampleGroup, FileUploaderMode, ChartLegendItem, SegmentedControlVariant, SegmentedControlSize, PillVariant, PillSize, CalendarSelectionMode, CalendarMarker, CalendarDayContext, SortDirection, SortState, DataFrameColumn, DataFrameSpec, SpinnerSize, DividerSpacing, ProgressSize, AvatarSize, EmptyStateSize, BreadcrumbItem, BreadcrumbItemInput, TooltipPosition, SettingsTab, SettingsTabInput, SettingsModalLayout, SettingsGroup, SettingsModalSchema, SettingsUserType, SettingsAudience, SettingsAudienceInput, NumberNotation, UnitOption, WizardStep, WizardStepState, AuditEntryType, AuditEntry, BatchItemStatus, BatchItem, BatchSummary, TimeSlotDisabledFn, TimeRange, ScheduleView, ScheduleEventStatus, ScheduleEvent, ScheduleBlockedSlot, ScheduleSlotContext, ScheduleEventCreateContext, ScheduleEventUpdateContext, SummarySectionItem, SummarySection, SummaryData, ResourceStatus, ResourceSpec, MoleculeData, ExperimentStatus, DatePreset, ExperimentSortField, ExperimentTypeOption, ExperimentSummary, ExperimentListResponse, ExperimentFilters, TreeNodeType, BadgeVariant, TreeNode, } from './components';
2
2
  export type * from './generated-ui';
3
3
  export type { AnalysisArtifactStatus, AnalysisArtifactStatusFilter, AnalysisArtifactSummary, AnalysisArtifactDetail, AnalysisArtifactListResponse, AnalysisArtifactTarget, AnalysisArtifactMetadataUpdate, ArtifactSaveMode, ArtifactFileSource, JsonArtifactSavePayload, FileArtifactCreatePayload, FileArtifactUpdatePayload, HandlerFileArtifactCreatePayload, HandlerFileArtifactUpdatePayload, ArtifactSavePayload, HandlerArtifactSavePayload, ArtifactSaveDialogPayload, ArtifactSaveResult, ArtifactSaveHandler, HandlerArtifactSaveHandler, ArtifactSaveDialogHandler, } from './analysisArtifactTypes';
4
4
  export { ANALYSIS_FILE_ARTIFACT_SCHEMA } from './analysisArtifactTypes';
@@ -345,6 +345,15 @@ var _hoisted_29 = {
345
345
  };
346
346
  /** Open modals, oldest first; shared by every BaseModal instance. */
347
347
  var openModals = [];
348
+ /** Body overflow from before the first modal opened, restored when the last one closes. */
349
+ var bodyOverflowBeforeLock = "";
350
+ /** Body scroll follows the stack: locked while any modal is open. */
351
+ function syncBodyScrollLock(wasEmpty) {
352
+ if (wasEmpty && openModals.length > 0) {
353
+ bodyOverflowBeforeLock = document.body.style.overflow;
354
+ document.body.style.overflow = "hidden";
355
+ } else if (!wasEmpty && openModals.length === 0) document.body.style.overflow = bodyOverflowBeforeLock;
356
+ }
348
357
  //#endregion
349
358
  //#region src/components/BaseModal.vue
350
359
  var BaseModal_default = /* @__PURE__ */ defineComponent({
@@ -417,14 +426,13 @@ var BaseModal_default = /* @__PURE__ */ defineComponent({
417
426
  containerRef,
418
427
  active: toRef(props, "open")
419
428
  });
420
- watch(() => props.open, (isOpen) => {
421
- document.body.style.overflow = isOpen ? "hidden" : "";
422
- });
423
429
  const self = {};
424
430
  watch(() => props.open, (isOpen) => {
431
+ const wasEmpty = openModals.length === 0;
425
432
  const index = openModals.indexOf(self);
426
433
  if (isOpen && index === -1) openModals.push(self);
427
434
  if (!isOpen && index !== -1) openModals.splice(index, 1);
435
+ syncBodyScrollLock(wasEmpty);
428
436
  }, { immediate: true });
429
437
  function handleEscapeKeydown(event) {
430
438
  if (event.key === "Escape" && !event.defaultPrevented && props.closeOnEscape && props.open && openModals[openModals.length - 1] === self) close();
@@ -440,9 +448,10 @@ var BaseModal_default = /* @__PURE__ */ defineComponent({
440
448
  }
441
449
  useEventListener(() => document, "keydown", handleEscapeKeydown);
442
450
  onUnmounted(() => {
443
- document.body.style.overflow = "";
451
+ const wasEmpty = openModals.length === 0;
444
452
  const index = openModals.indexOf(self);
445
453
  if (index !== -1) openModals.splice(index, 1);
454
+ syncBodyScrollLock(wasEmpty);
446
455
  });
447
456
  return (_ctx, _cache) => {
448
457
  return openBlock(), createBlock(Teleport, { to: "body" }, [createVNode(Transition, { name: `modal-${__props.variant}` }, {
@@ -605,4 +614,4 @@ function useDropdownState(options = {}) {
605
614
  //#endregion
606
615
  export { BaseButton_default as a, useEventListener as i, BaseModal_default as n, __exportAll as o, useFocusTrap as r, useDropdownState as t };
607
616
 
608
- //# sourceMappingURL=useDropdownState-uFIz6Yv8.js.map
617
+ //# sourceMappingURL=useDropdownState-CcBzUYZ6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDropdownState-CcBzUYZ6.js","names":["$slots"],"sources":["../src/components/BaseButton.vue","../src/components/BaseButton.vue","../src/composables/useEventListener.ts","../src/composables/useFocusTrap.ts","../src/components/BaseModal.vue","../src/components/BaseModal.vue","../src/composables/useDropdownState.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Renders a styled `<button>` with variant, size, loading spinner, and full-width options.\n * With `icon` and no default slot it is a square icon-only button; `label` is then its accessible name.\n *\n * @example\n * ```vue\n * <BaseButton variant=\"primary\" :loading=\"busy\">Save</BaseButton>\n * <BaseButton :icon=\"Plus\">Add sample</BaseButton>\n * <BaseButton variant=\"ghost\" :icon=\"Trash2\" label=\"Delete\" />\n * ```\n */\nimport { Comment, Fragment, Text, computed, useSlots, type Component, type VNode } from 'vue'\nimport type { ButtonVariant, ButtonSize } from '../types'\n\ninterface Props {\n variant?: ButtonVariant\n size?: ButtonSize\n disabled?: boolean\n loading?: boolean\n type?: 'button' | 'submit' | 'reset'\n fullWidth?: boolean\n /** Icon component (for example a lucide icon) rendered before the label, or alone when there is no default slot. */\n icon?: Component\n /** Accessible name and tooltip of an icon-only button. */\n label?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'primary',\n size: 'md',\n disabled: false,\n loading: false,\n type: 'button',\n fullWidth: false,\n icon: undefined,\n label: undefined,\n})\n\nconst emit = defineEmits<{\n click: [event: MouseEvent]\n}>()\n\nconst slots = useSlots()\nconst iconOnly = computed(() => Boolean(props.icon) && !slots.default)\nconst block = computed(() => (iconOnly.value ? 'mint-icon-button' : 'mint-button'))\n\n// A text-only label gets its own span so the stylesheet can centre the cap height.\n// Element children are left as flex items, so their gap and alignment do not change.\nfunction isTextNode(node: VNode): boolean {\n if (node.type === Text || node.type === Comment) return true\n return node.type === Fragment && Array.isArray(node.children)\n && (node.children as VNode[]).every(isTextNode)\n}\n\nfunction hasTextLabel(): boolean {\n const nodes = slots.default?.()\n return !!nodes && nodes.length > 0 && nodes.every(isTextNode)\n}\n\nfunction handleClick(event: MouseEvent) {\n if (!props.disabled && !props.loading) {\n emit('click', event)\n }\n}\n</script>\n\n<template>\n <button\n :type=\"type\"\n :disabled=\"disabled || loading\"\n :aria-label=\"iconOnly ? label : undefined\"\n :title=\"iconOnly ? label : undefined\"\n :class=\"[\n block,\n `${block}--${variant}`,\n `${block}--${size}`,\n fullWidth && !iconOnly ? 'mint-button--full-width' : '',\n (disabled || loading) ? `${block}--disabled` : '',\n ]\"\n @click=\"handleClick\"\n >\n <svg\n v-if=\"loading\"\n :class=\"iconOnly ? ['mint-icon-button__spinner', `mint-icon-button__icon--${size}`] : 'mint-button__spinner'\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n style=\"opacity: 0.25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"4\"\n />\n <path\n style=\"opacity: 0.75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n <span v-if=\"iconOnly && !loading\" :class=\"`mint-icon-button__icon--${size}`\">\n <component :is=\"icon\" aria-hidden=\"true\" />\n </span>\n <template v-if=\"!iconOnly\">\n <component :is=\"icon\" v-if=\"icon\" class=\"mint-button__icon\" aria-hidden=\"true\" />\n <slot name=\"icon-left\" />\n <span v-if=\"hasTextLabel()\" class=\"mint-button__label\"><slot /></span>\n <slot v-else />\n <slot name=\"icon-right\" />\n </template>\n </button>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Renders a styled `<button>` with variant, size, loading spinner, and full-width options.\n * With `icon` and no default slot it is a square icon-only button; `label` is then its accessible name.\n *\n * @example\n * ```vue\n * <BaseButton variant=\"primary\" :loading=\"busy\">Save</BaseButton>\n * <BaseButton :icon=\"Plus\">Add sample</BaseButton>\n * <BaseButton variant=\"ghost\" :icon=\"Trash2\" label=\"Delete\" />\n * ```\n */\nimport { Comment, Fragment, Text, computed, useSlots, type Component, type VNode } from 'vue'\nimport type { ButtonVariant, ButtonSize } from '../types'\n\ninterface Props {\n variant?: ButtonVariant\n size?: ButtonSize\n disabled?: boolean\n loading?: boolean\n type?: 'button' | 'submit' | 'reset'\n fullWidth?: boolean\n /** Icon component (for example a lucide icon) rendered before the label, or alone when there is no default slot. */\n icon?: Component\n /** Accessible name and tooltip of an icon-only button. */\n label?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'primary',\n size: 'md',\n disabled: false,\n loading: false,\n type: 'button',\n fullWidth: false,\n icon: undefined,\n label: undefined,\n})\n\nconst emit = defineEmits<{\n click: [event: MouseEvent]\n}>()\n\nconst slots = useSlots()\nconst iconOnly = computed(() => Boolean(props.icon) && !slots.default)\nconst block = computed(() => (iconOnly.value ? 'mint-icon-button' : 'mint-button'))\n\n// A text-only label gets its own span so the stylesheet can centre the cap height.\n// Element children are left as flex items, so their gap and alignment do not change.\nfunction isTextNode(node: VNode): boolean {\n if (node.type === Text || node.type === Comment) return true\n return node.type === Fragment && Array.isArray(node.children)\n && (node.children as VNode[]).every(isTextNode)\n}\n\nfunction hasTextLabel(): boolean {\n const nodes = slots.default?.()\n return !!nodes && nodes.length > 0 && nodes.every(isTextNode)\n}\n\nfunction handleClick(event: MouseEvent) {\n if (!props.disabled && !props.loading) {\n emit('click', event)\n }\n}\n</script>\n\n<template>\n <button\n :type=\"type\"\n :disabled=\"disabled || loading\"\n :aria-label=\"iconOnly ? label : undefined\"\n :title=\"iconOnly ? label : undefined\"\n :class=\"[\n block,\n `${block}--${variant}`,\n `${block}--${size}`,\n fullWidth && !iconOnly ? 'mint-button--full-width' : '',\n (disabled || loading) ? `${block}--disabled` : '',\n ]\"\n @click=\"handleClick\"\n >\n <svg\n v-if=\"loading\"\n :class=\"iconOnly ? ['mint-icon-button__spinner', `mint-icon-button__icon--${size}`] : 'mint-button__spinner'\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n style=\"opacity: 0.25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"4\"\n />\n <path\n style=\"opacity: 0.75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n <span v-if=\"iconOnly && !loading\" :class=\"`mint-icon-button__icon--${size}`\">\n <component :is=\"icon\" aria-hidden=\"true\" />\n </span>\n <template v-if=\"!iconOnly\">\n <component :is=\"icon\" v-if=\"icon\" class=\"mint-button__icon\" aria-hidden=\"true\" />\n <slot name=\"icon-left\" />\n <span v-if=\"hasTextLabel()\" class=\"mint-button__label\"><slot /></span>\n <slot v-else />\n <slot name=\"icon-right\" />\n </template>\n </button>\n</template>\n","import { isRef, onMounted, onUnmounted, watch, type Ref } from 'vue'\n\nexport type EventTargetGetter<TTarget extends EventTarget = EventTarget> =\n () => TTarget | null | undefined\n\nexport type EventTargetLike<TTarget extends EventTarget = EventTarget> =\n | TTarget\n | Ref<TTarget | null | undefined>\n | EventTargetGetter<TTarget>\n | null\n | undefined\n\nexport type EventListenerEnabled = boolean | Ref<boolean>\n\nexport interface UseEventListenerObjectOptions extends AddEventListenerOptions {\n enabled?: EventListenerEnabled\n}\n\nexport type UseEventListenerOptions = boolean | UseEventListenerObjectOptions\nexport type EventListenerStop = () => void\n\nexport type EventMapFor<TTarget extends EventTarget> =\n TTarget extends Window\n ? WindowEventMap\n : TTarget extends Document\n ? DocumentEventMap\n : TTarget extends HTMLElement\n ? HTMLElementEventMap\n : Record<string, Event>\n\nexport type EventFor<\n TTarget extends EventTarget,\n TType extends string,\n> = TType extends keyof EventMapFor<TTarget>\n ? EventMapFor<TTarget>[TType]\n : Event\n\n/** Bind a DOM event listener on mount, remove it on unmount, and rebind when a ref target changes. */\nexport function useEventListener<\n TTarget extends EventTarget = EventTarget,\n TType extends string = string,\n>(\n target: EventTargetLike<TTarget>,\n type: TType,\n listener: (event: EventFor<TTarget, TType>) => void,\n options?: UseEventListenerOptions,\n): EventListenerStop {\n let stopCurrent: EventListenerStop | undefined\n let mounted = false\n\n function stop(): void {\n stopCurrent?.()\n stopCurrent = undefined\n }\n\n function start(): void {\n stop()\n if (!mounted || !isEnabled(options)) return\n const resolvedTarget = resolveEventTarget(target)\n if (!resolvedTarget) return\n\n const eventListener = listener as EventListener\n const eventOptions = getEventOptions(options)\n resolvedTarget.addEventListener(type, eventListener, eventOptions)\n stopCurrent = () => {\n resolvedTarget.removeEventListener(type, eventListener, eventOptions)\n }\n }\n\n onMounted(() => {\n mounted = true\n start()\n })\n\n if (isRef(target)) {\n watch(target, start, { flush: 'post' })\n }\n const enabled = getEnabledOption(options)\n if (isRef(enabled)) {\n watch(enabled, start, { flush: 'post' })\n }\n\n onUnmounted(() => {\n mounted = false\n stop()\n })\n\n return stop\n}\n\nfunction resolveEventTarget(target: EventTargetLike): EventTarget | null | undefined {\n if (isRef(target)) return target.value\n if (typeof target === 'function') return target()\n return target\n}\n\nfunction getEnabledOption(options: UseEventListenerOptions | undefined): EventListenerEnabled | undefined {\n return typeof options === 'object' ? options.enabled : undefined\n}\n\nfunction isEnabled(options: UseEventListenerOptions | undefined): boolean {\n const enabled = getEnabledOption(options)\n if (isRef(enabled)) return enabled.value\n return enabled ?? true\n}\n\nfunction getEventOptions(options: UseEventListenerOptions | undefined): boolean | AddEventListenerOptions | undefined {\n if (typeof options !== 'object') return options\n const { enabled: _enabled, ...eventOptions } = options\n return eventOptions\n}\n","import { nextTick, watch, type Ref } from 'vue'\nimport { useEventListener } from './useEventListener'\n\nexport interface UseFocusTrapOptions {\n /** Ref to the trap's container element. Focusable elements are queried within it. */\n containerRef: Ref<HTMLElement | null>\n /** Reactive flag — the trap activates when this becomes `true` and releases when it becomes `false`. */\n active: Ref<boolean>\n /** Restore focus to the element that was focused before activation, when the trap releases. Default `true`. */\n restoreFocus?: boolean\n}\n\nexport interface UseFocusTrapReturn {\n /** Focusable descendants of the container, in DOM order, excluding roving-tabindex (`tabindex=\"-1\"`) elements. */\n getFocusableElements: () => HTMLElement[]\n}\n\nconst FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(', ')\n\n/**\n * Traps Tab focus within a container while `active` is true: Tab wraps from the last\n * focusable element back to the first (and Shift+Tab from the first to the last), the\n * first focusable element (or the container itself, if none) receives focus when the\n * trap activates, an `[autofocus]` descendant taking precedence, and focus returns to\n * whatever was focused beforehand when it releases.\n * Used by BaseModal and WellEditPopupInternal.\n */\nexport function useFocusTrap(options: UseFocusTrapOptions): UseFocusTrapReturn {\n const { containerRef, active } = options\n const restoreFocus = options.restoreFocus ?? true\n let previouslyFocused: HTMLElement | null = null\n\n function getFocusableElements(): HTMLElement[] {\n if (!containerRef.value) return []\n // Roving tabindex (inactive tabs/rail items) stays out of the Tab cycle. Checking\n // the attribute directly (rather than the `.tabIndex` IDL property) avoids relying\n // on a browser/engine's default-tabindex computation for freshly-patched elements.\n return Array.from(containerRef.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR))\n .filter((el) => el.getAttribute('tabindex') !== '-1')\n }\n\n function handleKeydown(event: KeyboardEvent) {\n if (event.key !== 'Tab' || !active.value || !containerRef.value) return\n\n const focusable = getFocusableElements()\n if (focusable.length === 0) {\n event.preventDefault()\n return\n }\n\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n\n if (event.shiftKey && document.activeElement === first) {\n event.preventDefault()\n last.focus()\n } else if (!event.shiftKey && document.activeElement === last) {\n event.preventDefault()\n first.focus()\n }\n }\n\n // `immediate` so a container mounted already active (`v-if` + a constant\n // `modelValue`) still takes focus. `flush: 'sync'` so releasing the trap from `onBeforeUnmount` restores focus\n // before the component's reactive scope is torn down — a batched (default\n // 'pre') watcher would be scheduled but never flushed once that scope stops.\n watch(active, async (isActive) => {\n if (isActive) {\n previouslyFocused = document.activeElement as HTMLElement | null\n await nextTick()\n // Like <dialog>: an [autofocus] descendant wins over the first focusable element.\n const target = containerRef.value?.querySelector<HTMLElement>('[autofocus]') ?? getFocusableElements()[0]\n if (target) {\n target.focus()\n } else {\n containerRef.value?.focus()\n }\n } else {\n if (restoreFocus) previouslyFocused?.focus()\n previouslyFocused = null\n }\n }, { flush: 'sync', immediate: true })\n\n useEventListener(() => document, 'keydown', handleKeydown)\n\n return { getFocusableElements }\n}\n","<script lang=\"ts\">\n/** Open modals, oldest first; shared by every BaseModal instance. */\nconst openModals: object[] = []\n/** Body overflow from before the first modal opened, restored when the last one closes. */\nlet bodyOverflowBeforeLock = ''\n\n/** Body scroll follows the stack: locked while any modal is open. */\nfunction syncBodyScrollLock(wasEmpty: boolean) {\n if (wasEmpty && openModals.length > 0) {\n bodyOverflowBeforeLock = document.body.style.overflow\n document.body.style.overflow = 'hidden'\n } else if (!wasEmpty && openModals.length === 0) {\n document.body.style.overflow = bodyOverflowBeforeLock\n }\n}\n</script>\n\n<script setup lang=\"ts\">\n/** Modal dialog with backdrop, focus trap, Escape-to-close, and configurable size/variant/layout. */\nimport { computed, ref, toRef, watch, onUnmounted, type ComponentPublicInstance } from 'vue'\nimport type { ModalLayout, ModalSize, ModalTab, ModalVariant } from '../types'\nimport { useEventListener } from '../composables/useEventListener'\nimport { useFocusTrap } from '../composables/useFocusTrap'\n\nexport interface Props {\n open: boolean\n title?: string\n subtitle?: string\n size?: ModalSize\n variant?: ModalVariant\n closable?: boolean\n closeOnOverlay?: boolean\n closeOnEscape?: boolean\n /**\n * Shell layout. 'tabs' renders a tab strip between header and body;\n * 'rail' renders a sidebar rail + pane as the shell's middle row.\n * Both are shell regions — never part of the scrolling body content.\n */\n layout?: ModalLayout\n /** Sections for the tab strip / rail. The strip renders only with 2+ tabs. */\n tabs?: ModalTab[]\n /** Id of the selected tab. Two-way bindable via update:activeTab. */\n activeTab?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 'md',\n variant: 'centered',\n closable: true,\n closeOnOverlay: false,\n closeOnEscape: true,\n layout: 'plain',\n tabs: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:open': [value: boolean]\n 'update:activeTab': [id: string]\n close: []\n}>()\n\nconst activeTabMeta = computed(() => props.tabs.find((tab) => tab.id === props.activeTab))\n\n/** Roving tabindex; with no active tab (e.g. a search is showing) the first tab stays reachable. */\nfunction isTabStop(tab: ModalTab, index: number): boolean {\n return activeTabMeta.value ? tab.id === props.activeTab : index === 0\n}\n\n/** A rail section heading goes before the first tab of each run of one `section`. */\nfunction startsSection(index: number): boolean {\n const section = props.tabs[index].section\n return !!section && section !== props.tabs[index - 1]?.section\n}\n\nconst tabButtonRefs = new Map<string, HTMLElement>()\n\nfunction setTabButtonRef(id: string, el: Element | ComponentPublicInstance | null) {\n if (el instanceof HTMLElement) {\n tabButtonRefs.set(id, el)\n } else {\n tabButtonRefs.delete(id)\n }\n}\n\nfunction selectTab(tab: ModalTab) {\n if (tab.disabled || tab.id === props.activeTab) return\n emit('update:activeTab', tab.id)\n}\n\n/** ARIA tablist keyboard contract: arrows move selection to the next non-disabled tab, wrapping. */\nfunction focusTab(delta: number) {\n const enabled = props.tabs.filter((tab) => !tab.disabled)\n if (enabled.length === 0) return\n const currentIndex = enabled.findIndex((tab) => tab.id === props.activeTab)\n const next = enabled[(currentIndex + delta + enabled.length) % enabled.length]\n if (!next || next.id === props.activeTab) return\n emit('update:activeTab', next.id)\n tabButtonRefs.get(next.id)?.focus()\n}\n\nfunction isPathIcon(icon: ModalTab['icon']): icon is string | string[] {\n if (!icon) return false\n return Array.isArray(icon) || icon.trim().startsWith('M') || icon.trim().startsWith('m')\n}\n\nconst containerRef = ref<HTMLElement | null>(null)\n\nuseFocusTrap({ containerRef, active: toRef(props, 'open') })\n\n// Every open modal listens on document, so only the most recently opened one answers Escape.\nconst self = {}\nwatch(() => props.open, (isOpen) => {\n const wasEmpty = openModals.length === 0\n const index = openModals.indexOf(self)\n if (isOpen && index === -1) openModals.push(self)\n if (!isOpen && index !== -1) openModals.splice(index, 1)\n syncBodyScrollLock(wasEmpty)\n}, { immediate: true })\n\n// A control inside the modal that handles Escape (an open select) prevents the default first.\nfunction handleEscapeKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape' && !event.defaultPrevented && props.closeOnEscape && props.open && openModals[openModals.length - 1] === self) {\n close()\n }\n}\n\nfunction close() {\n if (props.closable) {\n emit('update:open', false)\n emit('close')\n }\n}\n\nfunction handleOverlayClick(event: MouseEvent) {\n if (props.closeOnOverlay && event.target === event.currentTarget) {\n close()\n }\n}\n\nuseEventListener(() => document, 'keydown', handleEscapeKeydown)\n\nonUnmounted(() => {\n const wasEmpty = openModals.length === 0\n const index = openModals.indexOf(self)\n if (index !== -1) openModals.splice(index, 1)\n syncBodyScrollLock(wasEmpty)\n})\n</script>\n\n<template>\n <Teleport to=\"body\">\n <Transition :name=\"`modal-${variant}`\">\n <div\n v-if=\"open\"\n :class=\"['mint-modal', `mint-modal--${variant}`]\"\n @click=\"handleOverlayClick\"\n >\n <!-- Overlay -->\n <div class=\"mint-modal__overlay\" />\n\n <!-- Modal -->\n <div\n ref=\"containerRef\"\n :class=\"[\n 'mint-modal__container',\n `mint-modal__container--${size}`,\n `mint-modal__container--${variant}`,\n `mint-modal__container--${layout}`,\n ]\"\n role=\"dialog\"\n aria-modal=\"true\"\n tabindex=\"-1\"\n >\n <!-- Sheet grab-hint bar (sheet variant only) -->\n <div v-if=\"variant === 'sheet'\" class=\"mint-modal__grab-hint\" aria-hidden=\"true\" />\n\n <!-- Header -->\n <div v-if=\"title || subtitle || closable || $slots.header\" class=\"mint-modal__header\">\n <div v-if=\"title || subtitle || $slots.header\" class=\"mint-modal__header-text\">\n <slot name=\"header\">\n <h3 v-if=\"title\" class=\"mint-modal__title\">\n {{ title }}\n </h3>\n <p v-if=\"subtitle\" class=\"mint-modal__subtitle\">\n {{ subtitle }}\n </p>\n </slot>\n </div>\n <button\n v-if=\"closable\"\n type=\"button\"\n class=\"mint-modal__close\"\n aria-label=\"Close modal\"\n @click=\"close\"\n >\n <svg class=\"mint-modal__close-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- Tab strip — a shell region between header and body, flush with the modal edges -->\n <div\n v-if=\"layout === 'tabs' && tabs.length > 1\"\n class=\"mint-modal__tabs\"\n role=\"tablist\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :key=\"tab.id\"\n :ref=\"(el) => setTabButtonRef(tab.id, el)\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"activeTab === tab.id\"\n :aria-disabled=\"tab.disabled || undefined\"\n :tabindex=\"isTabStop(tab, index) ? 0 : -1\"\n :class=\"['mint-modal__tab', {\n 'mint-modal__tab--active': activeTab === tab.id,\n 'mint-modal__tab--disabled': tab.disabled,\n }]\"\n @click=\"selectTab(tab)\"\n @keydown.left.prevent=\"focusTab(-1)\"\n @keydown.right.prevent=\"focusTab(1)\"\n >\n {{ tab.label }}\n <span v-if=\"tab.meta\" class=\"mint-modal__tab-meta\">{{ tab.meta }}</span>\n </button>\n </div>\n\n <!-- Rail layout: rail column + pane are the shell's middle row -->\n <div v-if=\"layout === 'rail'\" class=\"mint-modal__split\">\n <div class=\"mint-modal__rail\" role=\"tablist\" aria-orientation=\"vertical\">\n <div class=\"mint-modal__rail-items\">\n <template v-for=\"(tab, index) in tabs\" :key=\"tab.id\">\n <template v-if=\"startsSection(index)\">\n <div v-if=\"index > 0\" class=\"mint-modal__rail-divider\" aria-hidden=\"true\" />\n <span class=\"mint-modal__rail-section\" aria-hidden=\"true\">{{ tab.section }}</span>\n </template>\n <button\n :ref=\"(el) => setTabButtonRef(tab.id, el)\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"activeTab === tab.id\"\n :aria-disabled=\"tab.disabled || undefined\"\n :tabindex=\"isTabStop(tab, index) ? 0 : -1\"\n :class=\"['mint-modal__rail-item', {\n 'mint-modal__rail-item--active': activeTab === tab.id,\n 'mint-modal__rail-item--disabled': tab.disabled,\n }]\"\n @click=\"selectTab(tab)\"\n @keydown.up.prevent=\"focusTab(-1)\"\n @keydown.down.prevent=\"focusTab(1)\"\n >\n <span v-if=\"tab.icon\" class=\"mint-modal__rail-item-icon\" aria-hidden=\"true\">\n <svg\n v-if=\"isPathIcon(tab.icon)\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <template v-if=\"Array.isArray(tab.icon)\">\n <path v-for=\"(d, index) in tab.icon\" :key=\"index\" :d=\"d\" />\n </template>\n <path v-else :d=\"tab.icon\" />\n </svg>\n <!-- Trusted, author-controlled markup only — same contract as SettingsTab.icon. -->\n <span v-else v-html=\"tab.icon\" />\n </span>\n <span class=\"mint-modal__rail-item-text\">\n <span class=\"mint-modal__rail-item-label\">{{ tab.label }}</span>\n <span v-if=\"tab.description\" class=\"mint-modal__rail-item-description\">{{ tab.description }}</span>\n </span>\n <span v-if=\"tab.meta\" class=\"mint-modal__rail-item-meta\">{{ tab.meta }}</span>\n </button>\n </template>\n </div>\n <!-- Quiet rail-bottom action (e.g. Log out) -->\n <slot name=\"rail-footer\" />\n </div>\n\n <section\n class=\"mint-modal__pane\"\n role=\"tabpanel\"\n :aria-label=\"activeTabMeta?.label\"\n tabindex=\"0\"\n >\n <header v-if=\"$slots['pane-header'] || activeTabMeta?.label\" class=\"mint-modal__pane-header\">\n <!-- Replaces the default title + subtitle, e.g. to add pane actions. -->\n <slot name=\"pane-header\" :tab=\"activeTabMeta\">\n <h4 class=\"mint-modal__pane-title\">{{ activeTabMeta?.label }}</h4>\n <p v-if=\"activeTabMeta?.description\" class=\"mint-modal__pane-subtitle\">\n {{ activeTabMeta.description }}\n </p>\n </slot>\n </header>\n <div class=\"mint-modal__pane-body\">\n <slot />\n </div>\n </section>\n </div>\n\n <!-- Body -->\n <div\n v-else\n :class=\"['mint-modal__body', layout === 'tabs' ? 'mint-modal__body--tabbed' : null]\"\n >\n <slot />\n </div>\n\n <!-- Footer -->\n <div v-if=\"$slots.footer\" class=\"mint-modal__footer\">\n <slot name=\"footer\" />\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","<script lang=\"ts\">\n/** Open modals, oldest first; shared by every BaseModal instance. */\nconst openModals: object[] = []\n/** Body overflow from before the first modal opened, restored when the last one closes. */\nlet bodyOverflowBeforeLock = ''\n\n/** Body scroll follows the stack: locked while any modal is open. */\nfunction syncBodyScrollLock(wasEmpty: boolean) {\n if (wasEmpty && openModals.length > 0) {\n bodyOverflowBeforeLock = document.body.style.overflow\n document.body.style.overflow = 'hidden'\n } else if (!wasEmpty && openModals.length === 0) {\n document.body.style.overflow = bodyOverflowBeforeLock\n }\n}\n</script>\n\n<script setup lang=\"ts\">\n/** Modal dialog with backdrop, focus trap, Escape-to-close, and configurable size/variant/layout. */\nimport { computed, ref, toRef, watch, onUnmounted, type ComponentPublicInstance } from 'vue'\nimport type { ModalLayout, ModalSize, ModalTab, ModalVariant } from '../types'\nimport { useEventListener } from '../composables/useEventListener'\nimport { useFocusTrap } from '../composables/useFocusTrap'\n\nexport interface Props {\n open: boolean\n title?: string\n subtitle?: string\n size?: ModalSize\n variant?: ModalVariant\n closable?: boolean\n closeOnOverlay?: boolean\n closeOnEscape?: boolean\n /**\n * Shell layout. 'tabs' renders a tab strip between header and body;\n * 'rail' renders a sidebar rail + pane as the shell's middle row.\n * Both are shell regions — never part of the scrolling body content.\n */\n layout?: ModalLayout\n /** Sections for the tab strip / rail. The strip renders only with 2+ tabs. */\n tabs?: ModalTab[]\n /** Id of the selected tab. Two-way bindable via update:activeTab. */\n activeTab?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 'md',\n variant: 'centered',\n closable: true,\n closeOnOverlay: false,\n closeOnEscape: true,\n layout: 'plain',\n tabs: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:open': [value: boolean]\n 'update:activeTab': [id: string]\n close: []\n}>()\n\nconst activeTabMeta = computed(() => props.tabs.find((tab) => tab.id === props.activeTab))\n\n/** Roving tabindex; with no active tab (e.g. a search is showing) the first tab stays reachable. */\nfunction isTabStop(tab: ModalTab, index: number): boolean {\n return activeTabMeta.value ? tab.id === props.activeTab : index === 0\n}\n\n/** A rail section heading goes before the first tab of each run of one `section`. */\nfunction startsSection(index: number): boolean {\n const section = props.tabs[index].section\n return !!section && section !== props.tabs[index - 1]?.section\n}\n\nconst tabButtonRefs = new Map<string, HTMLElement>()\n\nfunction setTabButtonRef(id: string, el: Element | ComponentPublicInstance | null) {\n if (el instanceof HTMLElement) {\n tabButtonRefs.set(id, el)\n } else {\n tabButtonRefs.delete(id)\n }\n}\n\nfunction selectTab(tab: ModalTab) {\n if (tab.disabled || tab.id === props.activeTab) return\n emit('update:activeTab', tab.id)\n}\n\n/** ARIA tablist keyboard contract: arrows move selection to the next non-disabled tab, wrapping. */\nfunction focusTab(delta: number) {\n const enabled = props.tabs.filter((tab) => !tab.disabled)\n if (enabled.length === 0) return\n const currentIndex = enabled.findIndex((tab) => tab.id === props.activeTab)\n const next = enabled[(currentIndex + delta + enabled.length) % enabled.length]\n if (!next || next.id === props.activeTab) return\n emit('update:activeTab', next.id)\n tabButtonRefs.get(next.id)?.focus()\n}\n\nfunction isPathIcon(icon: ModalTab['icon']): icon is string | string[] {\n if (!icon) return false\n return Array.isArray(icon) || icon.trim().startsWith('M') || icon.trim().startsWith('m')\n}\n\nconst containerRef = ref<HTMLElement | null>(null)\n\nuseFocusTrap({ containerRef, active: toRef(props, 'open') })\n\n// Every open modal listens on document, so only the most recently opened one answers Escape.\nconst self = {}\nwatch(() => props.open, (isOpen) => {\n const wasEmpty = openModals.length === 0\n const index = openModals.indexOf(self)\n if (isOpen && index === -1) openModals.push(self)\n if (!isOpen && index !== -1) openModals.splice(index, 1)\n syncBodyScrollLock(wasEmpty)\n}, { immediate: true })\n\n// A control inside the modal that handles Escape (an open select) prevents the default first.\nfunction handleEscapeKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape' && !event.defaultPrevented && props.closeOnEscape && props.open && openModals[openModals.length - 1] === self) {\n close()\n }\n}\n\nfunction close() {\n if (props.closable) {\n emit('update:open', false)\n emit('close')\n }\n}\n\nfunction handleOverlayClick(event: MouseEvent) {\n if (props.closeOnOverlay && event.target === event.currentTarget) {\n close()\n }\n}\n\nuseEventListener(() => document, 'keydown', handleEscapeKeydown)\n\nonUnmounted(() => {\n const wasEmpty = openModals.length === 0\n const index = openModals.indexOf(self)\n if (index !== -1) openModals.splice(index, 1)\n syncBodyScrollLock(wasEmpty)\n})\n</script>\n\n<template>\n <Teleport to=\"body\">\n <Transition :name=\"`modal-${variant}`\">\n <div\n v-if=\"open\"\n :class=\"['mint-modal', `mint-modal--${variant}`]\"\n @click=\"handleOverlayClick\"\n >\n <!-- Overlay -->\n <div class=\"mint-modal__overlay\" />\n\n <!-- Modal -->\n <div\n ref=\"containerRef\"\n :class=\"[\n 'mint-modal__container',\n `mint-modal__container--${size}`,\n `mint-modal__container--${variant}`,\n `mint-modal__container--${layout}`,\n ]\"\n role=\"dialog\"\n aria-modal=\"true\"\n tabindex=\"-1\"\n >\n <!-- Sheet grab-hint bar (sheet variant only) -->\n <div v-if=\"variant === 'sheet'\" class=\"mint-modal__grab-hint\" aria-hidden=\"true\" />\n\n <!-- Header -->\n <div v-if=\"title || subtitle || closable || $slots.header\" class=\"mint-modal__header\">\n <div v-if=\"title || subtitle || $slots.header\" class=\"mint-modal__header-text\">\n <slot name=\"header\">\n <h3 v-if=\"title\" class=\"mint-modal__title\">\n {{ title }}\n </h3>\n <p v-if=\"subtitle\" class=\"mint-modal__subtitle\">\n {{ subtitle }}\n </p>\n </slot>\n </div>\n <button\n v-if=\"closable\"\n type=\"button\"\n class=\"mint-modal__close\"\n aria-label=\"Close modal\"\n @click=\"close\"\n >\n <svg class=\"mint-modal__close-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- Tab strip — a shell region between header and body, flush with the modal edges -->\n <div\n v-if=\"layout === 'tabs' && tabs.length > 1\"\n class=\"mint-modal__tabs\"\n role=\"tablist\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :key=\"tab.id\"\n :ref=\"(el) => setTabButtonRef(tab.id, el)\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"activeTab === tab.id\"\n :aria-disabled=\"tab.disabled || undefined\"\n :tabindex=\"isTabStop(tab, index) ? 0 : -1\"\n :class=\"['mint-modal__tab', {\n 'mint-modal__tab--active': activeTab === tab.id,\n 'mint-modal__tab--disabled': tab.disabled,\n }]\"\n @click=\"selectTab(tab)\"\n @keydown.left.prevent=\"focusTab(-1)\"\n @keydown.right.prevent=\"focusTab(1)\"\n >\n {{ tab.label }}\n <span v-if=\"tab.meta\" class=\"mint-modal__tab-meta\">{{ tab.meta }}</span>\n </button>\n </div>\n\n <!-- Rail layout: rail column + pane are the shell's middle row -->\n <div v-if=\"layout === 'rail'\" class=\"mint-modal__split\">\n <div class=\"mint-modal__rail\" role=\"tablist\" aria-orientation=\"vertical\">\n <div class=\"mint-modal__rail-items\">\n <template v-for=\"(tab, index) in tabs\" :key=\"tab.id\">\n <template v-if=\"startsSection(index)\">\n <div v-if=\"index > 0\" class=\"mint-modal__rail-divider\" aria-hidden=\"true\" />\n <span class=\"mint-modal__rail-section\" aria-hidden=\"true\">{{ tab.section }}</span>\n </template>\n <button\n :ref=\"(el) => setTabButtonRef(tab.id, el)\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"activeTab === tab.id\"\n :aria-disabled=\"tab.disabled || undefined\"\n :tabindex=\"isTabStop(tab, index) ? 0 : -1\"\n :class=\"['mint-modal__rail-item', {\n 'mint-modal__rail-item--active': activeTab === tab.id,\n 'mint-modal__rail-item--disabled': tab.disabled,\n }]\"\n @click=\"selectTab(tab)\"\n @keydown.up.prevent=\"focusTab(-1)\"\n @keydown.down.prevent=\"focusTab(1)\"\n >\n <span v-if=\"tab.icon\" class=\"mint-modal__rail-item-icon\" aria-hidden=\"true\">\n <svg\n v-if=\"isPathIcon(tab.icon)\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <template v-if=\"Array.isArray(tab.icon)\">\n <path v-for=\"(d, index) in tab.icon\" :key=\"index\" :d=\"d\" />\n </template>\n <path v-else :d=\"tab.icon\" />\n </svg>\n <!-- Trusted, author-controlled markup only — same contract as SettingsTab.icon. -->\n <span v-else v-html=\"tab.icon\" />\n </span>\n <span class=\"mint-modal__rail-item-text\">\n <span class=\"mint-modal__rail-item-label\">{{ tab.label }}</span>\n <span v-if=\"tab.description\" class=\"mint-modal__rail-item-description\">{{ tab.description }}</span>\n </span>\n <span v-if=\"tab.meta\" class=\"mint-modal__rail-item-meta\">{{ tab.meta }}</span>\n </button>\n </template>\n </div>\n <!-- Quiet rail-bottom action (e.g. Log out) -->\n <slot name=\"rail-footer\" />\n </div>\n\n <section\n class=\"mint-modal__pane\"\n role=\"tabpanel\"\n :aria-label=\"activeTabMeta?.label\"\n tabindex=\"0\"\n >\n <header v-if=\"$slots['pane-header'] || activeTabMeta?.label\" class=\"mint-modal__pane-header\">\n <!-- Replaces the default title + subtitle, e.g. to add pane actions. -->\n <slot name=\"pane-header\" :tab=\"activeTabMeta\">\n <h4 class=\"mint-modal__pane-title\">{{ activeTabMeta?.label }}</h4>\n <p v-if=\"activeTabMeta?.description\" class=\"mint-modal__pane-subtitle\">\n {{ activeTabMeta.description }}\n </p>\n </slot>\n </header>\n <div class=\"mint-modal__pane-body\">\n <slot />\n </div>\n </section>\n </div>\n\n <!-- Body -->\n <div\n v-else\n :class=\"['mint-modal__body', layout === 'tabs' ? 'mint-modal__body--tabbed' : null]\"\n >\n <slot />\n </div>\n\n <!-- Footer -->\n <div v-if=\"$slots.footer\" class=\"mint-modal__footer\">\n <slot name=\"footer\" />\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","import { nextTick, ref, type Ref } from 'vue'\nimport { useEventListener } from './useEventListener'\n\nexport interface UseDropdownStateOptions {\n closeOnOutsideClick?: boolean\n closeOnEscape?: boolean\n /** Close the panel when the page scrolls or the viewport resizes. Default `false`. */\n closeOnScroll?: boolean\n insideRefs?: Array<Ref<HTMLElement | null>>\n /**\n * Element that counts as \"inside\" for outside-click closing, typically a\n * `useTemplateRef()`. Without one, bind the returned `rootRef` instead.\n */\n rootRef?: Ref<HTMLElement | null>\n /**\n * Element focus returns to when the panel closes with `close(true)` (Escape).\n * Pass one when the trigger lives outside `rootRef`; otherwise bind the\n * returned `triggerRef` to the trigger element.\n */\n triggerRef?: Ref<HTMLElement | null>\n onOpen?: () => void\n onClose?: () => void\n}\n\nexport interface UseDropdownStateReturn {\n isOpen: Ref<boolean>\n rootRef: Ref<HTMLElement | null>\n /** Bind to the trigger element so Escape can return focus to it. */\n triggerRef: Ref<HTMLElement | null>\n open: () => void\n /** Closes the panel; `returnFocus` moves focus back to `triggerRef`. */\n close: (returnFocus?: boolean) => void\n toggle: () => void\n handleClickOutside: (event: MouseEvent) => void\n handleKeydown: (event: KeyboardEvent) => void\n}\n\n/**\n * Shared disclosure state for dropdown-style SDK components: open/close state,\n * outside-click and Escape closing, optional scroll/resize closing, and focus\n * return to the trigger. `close(returnFocus)` mirrors `useMenuKeyboard`'s\n * contract, so the two compose directly.\n */\nexport function useDropdownState(options: UseDropdownStateOptions = {}): UseDropdownStateReturn {\n const isOpen = ref(false)\n const rootRef = options.rootRef ?? ref<HTMLElement | null>(null)\n const triggerRef = options.triggerRef ?? ref<HTMLElement | null>(null)\n const closeOnOutsideClick = options.closeOnOutsideClick ?? true\n const closeOnEscape = options.closeOnEscape ?? true\n const closeOnScroll = options.closeOnScroll ?? false\n\n function open(): void {\n if (isOpen.value) return\n isOpen.value = true\n options.onOpen?.()\n }\n\n function close(returnFocus = false): void {\n if (!isOpen.value) return\n isOpen.value = false\n options.onClose?.()\n if (returnFocus) {\n void nextTick(() => triggerRef.value?.focus())\n }\n }\n\n function toggle(): void {\n if (isOpen.value) {\n close()\n } else {\n open()\n }\n }\n\n function handleClickOutside(event: MouseEvent): void {\n if (!closeOnOutsideClick || !isOpen.value || !rootRef.value) return\n const target = event.target\n if (\n target instanceof Node &&\n !rootRef.value.contains(target) &&\n !options.insideRefs?.some(ref => ref.value?.contains(target))\n ) {\n close()\n }\n }\n\n function handleKeydown(event: KeyboardEvent): void {\n if (closeOnEscape && event.key === 'Escape') {\n close(true)\n }\n }\n\n function handleViewportChange(): void {\n if (isOpen.value) close()\n }\n\n if (closeOnOutsideClick) {\n useEventListener(() => document, 'click', handleClickOutside)\n }\n if (closeOnEscape) {\n useEventListener(() => document, 'keydown', handleKeydown)\n }\n if (closeOnScroll) {\n useEventListener(() => window, 'scroll', handleViewportChange, true)\n useEventListener(() => window, 'resize', handleViewportChange)\n }\n\n return {\n isOpen,\n rootRef,\n triggerRef,\n open,\n close,\n toggle,\n handleClickOutside,\n handleKeydown,\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { o as useApi, r as readErrorMessage } from "./experiment-DyO54FQB.js";
2
- import { _ as APP_EXPERIMENT_KEY, a as parseExperimentId, t as isRecord } from "./records-BAymyHAn.js";
3
- import { t as useRequest } from "./useRequest-D9-3BS-3.js";
2
+ import { _ as APP_EXPERIMENT_KEY, a as parseExperimentId, t as isRecord } from "./records-GtiVMlTQ.js";
3
+ import { t as useRequest } from "./useRequest-CXyPw3eh.js";
4
4
  import { computed, inject, ref, shallowRef, toValue, watch } from "vue";
5
5
  //#region src/composables/experimentDesignData.ts
6
6
  function isPlatformDesignDataResponse(value) {
@@ -405,4 +405,4 @@ function useExperimentSamples(options = {}) {
405
405
  //#endregion
406
406
  export { readTableFileAsCsv as a, useExperimentData as c, unwrapExperimentDesignData as d, readFileAsText as i, extractSampleNamesFromDesignData as l, detectDelimiter as n, useFileImport as o, parseDelimitedText as r, validateImportFile as s, useExperimentSamples as t, extractSampleOptionsFromDesignData as u };
407
407
 
408
- //# sourceMappingURL=useExperimentSamples-DEDAKa2E.js.map
408
+ //# sourceMappingURL=useExperimentSamples-CpNXvpUn.js.map