@morscherlab/mint-sdk 1.4.0-beta.2 → 1.4.0-beta.21

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 (353) hide show
  1. package/README.md +14 -3
  2. package/dist/{ActionMenu-CScINahU.js → AlertBox-DwgNTEqf.js} +40 -209
  3. package/dist/AlertBox-DwgNTEqf.js.map +1 -0
  4. package/dist/{AppSidebar-BmiiPfz9.js → AppSidebar-CZZaxni0.js} +1249 -239
  5. package/dist/AppSidebar-CZZaxni0.js.map +1 -0
  6. package/dist/{ConfirmDialog-CJ7hqYUP.js → ConfirmDialog-CjWoxAD1.js} +2 -2
  7. package/dist/{ConfirmDialog-CJ7hqYUP.js.map → ConfirmDialog-CjWoxAD1.js.map} +1 -1
  8. package/dist/ExperimentPopover-DujiuQIO.js +337 -0
  9. package/dist/ExperimentPopover-DujiuQIO.js.map +1 -0
  10. package/dist/{ExperimentSelectorModal-D-9z7cNx.js → ExperimentSelectorModal-B4j4eUOJ.js} +244 -211
  11. package/dist/ExperimentSelectorModal-B4j4eUOJ.js.map +1 -0
  12. package/dist/{SegmentedControl-DERBN-Xd.js → SegmentedControl-DyUPH3I5.js} +2 -2
  13. package/dist/{SegmentedControl-DERBN-Xd.js.map → SegmentedControl-DyUPH3I5.js.map} +1 -1
  14. package/dist/{SettingsModal-CSLE31yg.js → SettingsModal-D2MOOtuO.js} +28 -27
  15. package/dist/SettingsModal-D2MOOtuO.js.map +1 -0
  16. package/dist/__stories__/experiment-helpers.d.ts +10 -0
  17. package/dist/__tests__/components/AppTopBarOnboarding.test.d.ts +1 -0
  18. package/dist/__tests__/components/FloatingPanelsInModal.test.d.ts +1 -0
  19. package/dist/__tests__/composables/useFloatingPanel.test.d.ts +1 -0
  20. package/dist/__tests__/composables/useOnboarding.test.d.ts +1 -0
  21. package/dist/__tests__/styles/paletteTokens.test.d.ts +1 -0
  22. package/dist/__tests__/styles/v2AuditRules.test.d.ts +1 -0
  23. package/dist/components/AppSidebar.vue.d.ts +1 -1
  24. package/dist/components/AppTopBar.vue.d.ts +20 -5
  25. package/dist/components/ArtifactSaveDialog.vue.d.ts +9 -1
  26. package/dist/components/AuditTrail.vue.d.ts +1 -1
  27. package/dist/components/BaseButton.vue.d.ts +4 -2
  28. package/dist/components/BaseSelect.vue.d.ts +2 -2
  29. package/dist/components/ChartContainer.vue.d.ts +2 -0
  30. package/dist/components/DatePicker.vue.d.ts +1 -1
  31. package/dist/components/ExperimentPopover.vue.d.ts +18 -0
  32. package/dist/components/ExperimentSelectorModal.vue.d.ts +15 -5
  33. package/dist/components/FilePicker.vue.d.ts +1 -1
  34. package/dist/components/FileUploader.vue.d.ts +2 -2
  35. package/dist/components/JobsStatusTray.vue.d.ts +15 -4
  36. package/dist/components/PluginIcon.vue.d.ts +2 -2
  37. package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
  38. package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
  39. package/dist/components/SampleSelector.vue.d.ts +6 -6
  40. package/dist/components/SequenceProgressBar.vue.d.ts +3 -0
  41. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +3 -3
  42. package/dist/components/StepWizard.vue.d.ts +3 -0
  43. package/dist/components/internal/AppTopBarOnboardingInternal.vue.d.ts +7 -0
  44. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -1
  45. package/dist/components/internal/AutoGroupModalInternal.vue.d.ts +3 -3
  46. package/dist/components/internal/DatePickerTimeInternal.vue.d.ts +1 -1
  47. package/dist/components/internal/OnboardingLayerInternal.vue.d.ts +7 -0
  48. package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +2 -7
  49. package/dist/components/internal/SelectMultipleInternal.vue.d.ts +4 -0
  50. package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
  51. package/dist/composables/createClient.d.ts +2 -0
  52. package/dist/composables/index.d.ts +1 -0
  53. package/dist/composables/pluginIdentity.d.ts +24 -0
  54. package/dist/composables/pluginWorkspaceModel.d.ts +2 -2
  55. package/dist/composables/useAppExperiment.d.ts +8 -0
  56. package/dist/composables/useCalendarGrid.d.ts +1 -1
  57. package/dist/composables/useExperimentSelector.d.ts +6 -2
  58. package/dist/composables/useFileImport.d.ts +7 -0
  59. package/dist/composables/useFloatingPanel.d.ts +29 -0
  60. package/dist/composables/useJobCenter.d.ts +41 -3
  61. package/dist/composables/useOnboarding.d.ts +119 -0
  62. package/dist/composables/usePlatform.d.ts +1 -0
  63. package/dist/{controlSchemaModel-CP5-_nAj.js → controlSchemaModel-Bsg0t5Ab.js} +2 -2
  64. package/dist/{controlSchemaModel-CP5-_nAj.js.map → controlSchemaModel-Bsg0t5Ab.js.map} +1 -1
  65. package/dist/{data-C2PwI_dn.js → data-Boeh4uPI.js} +180 -125
  66. package/dist/data-Boeh4uPI.js.map +1 -0
  67. package/dist/data.d.ts +1 -1
  68. package/dist/data.js +9 -9
  69. package/dist/{experiment-43OJtH7R.js → experiment-DyO54FQB.js} +25 -61
  70. package/dist/experiment-DyO54FQB.js.map +1 -0
  71. package/dist/{experimentStatus-BZmU4czV.js → experimentStatus-DCn2QKG7.js} +3 -3
  72. package/dist/{experimentStatus-BZmU4czV.js.map → experimentStatus-DCn2QKG7.js.map} +1 -1
  73. package/dist/index.js +20 -20
  74. package/dist/{install-DAID6c2-.js → install-BEHp7Mo-.js} +15 -14
  75. package/dist/{install-DAID6c2-.js.map → install-BEHp7Mo-.js.map} +1 -1
  76. package/dist/install.js +1 -1
  77. package/dist/{lab-DLwRl_Ql.js → lab-D61eaX6f.js} +136 -148
  78. package/dist/lab-D61eaX6f.js.map +1 -0
  79. package/dist/lab.js +4 -3
  80. package/dist/{records-BzTcxekG.js → records-D4aQXyZu.js} +647 -389
  81. package/dist/records-D4aQXyZu.js.map +1 -0
  82. package/dist/{runtime-D6gVat6r.js → runtime-q-R0CNLh.js} +417 -54
  83. package/dist/runtime-q-R0CNLh.js.map +1 -0
  84. package/dist/runtime.d.ts +1 -1
  85. package/dist/runtime.js +7 -7
  86. package/dist/{shell-5ju97nOS.js → shell-6CGFvUUw.js} +10 -5
  87. package/dist/{shell-5ju97nOS.js.map → shell-6CGFvUUw.js.map} +1 -1
  88. package/dist/shell.d.ts +1 -0
  89. package/dist/shell.js +6 -6
  90. package/dist/stores/index.d.ts +1 -1
  91. package/dist/stores/index.js +2 -2
  92. package/dist/stores/settings.d.ts +4 -12
  93. package/dist/styles.css +2753 -2101
  94. package/dist/types/componentWorkflowTypes.d.ts +2 -0
  95. package/dist/types/components.d.ts +2 -0
  96. package/dist/types/generated/platformContext.d.ts +1 -0
  97. package/dist/types/generated-ui.d.ts +4 -0
  98. package/dist/types/index.d.ts +1 -1
  99. package/dist/types/jobs.d.ts +9 -0
  100. package/dist/types/platform.d.ts +1 -1
  101. package/dist/{BaseModal-DfRIuVoj.js → useDropdownState-uFIz6Yv8.js} +75 -5
  102. package/dist/useDropdownState-uFIz6Yv8.js.map +1 -0
  103. package/dist/{useExperimentSamples-CxcnxZ8h.js → useExperimentSamples-QEFoq4tf.js} +48 -6
  104. package/dist/useExperimentSamples-QEFoq4tf.js.map +1 -0
  105. package/dist/{useListReorder-CcbEYxX-.js → useListReorder-DsuzwWGS.js} +219 -437
  106. package/dist/useListReorder-DsuzwWGS.js.map +1 -0
  107. package/dist/{useRackEditor-BD5NIo5s.js → useRackEditor-KpZ4iJ6o.js} +85 -81
  108. package/dist/useRackEditor-KpZ4iJ6o.js.map +1 -0
  109. package/dist/useRealtime-B58Lo-Tm.js +508 -0
  110. package/dist/useRealtime-B58Lo-Tm.js.map +1 -0
  111. package/dist/useRequest-BuDmW8Jc.js +215 -0
  112. package/dist/useRequest-BuDmW8Jc.js.map +1 -0
  113. package/dist/utils/time.d.ts +7 -0
  114. package/package.json +3 -2
  115. package/src/__stories__/experiment-helpers.ts +3 -3
  116. package/src/__tests__/components/ActionMenu.test.ts +2 -2
  117. package/src/__tests__/components/AppTopBar.test.ts +257 -3
  118. package/src/__tests__/components/AppTopBarOnboarding.test.ts +160 -0
  119. package/src/__tests__/components/AppTopBarPageSelector.test.ts +125 -9
  120. package/src/__tests__/components/AppTopBarPillNav.test.ts +35 -0
  121. package/src/__tests__/components/ArtifactSaveDialog.test.ts +78 -0
  122. package/src/__tests__/components/BaseButton.test.ts +19 -0
  123. package/src/__tests__/components/BaseModal.test.ts +13 -0
  124. package/src/__tests__/components/BaseSelect.multiple.test.ts +87 -32
  125. package/src/__tests__/components/BaseSelect.test.ts +2 -1
  126. package/src/__tests__/components/BatchProgressList.test.ts +16 -0
  127. package/src/__tests__/components/CalendarGridPanel.test.ts +2 -2
  128. package/src/__tests__/components/ChartContainer.test.ts +11 -0
  129. package/src/__tests__/components/ChemicalFormula.test.ts +6 -3
  130. package/src/__tests__/components/DatePicker.datetime.test.ts +6 -0
  131. package/src/__tests__/components/DatePicker.test.ts +21 -0
  132. package/src/__tests__/components/DatePicker.time.test.ts +2 -2
  133. package/src/__tests__/components/EmptyState.test.ts +1 -1
  134. package/src/__tests__/components/ExperimentPopover.test.ts +125 -16
  135. package/src/__tests__/components/ExperimentSelectorModal.test.ts +153 -14
  136. package/src/__tests__/components/FloatingPanelsInModal.test.ts +94 -0
  137. package/src/__tests__/components/GeneratedPluginView.test.ts +23 -2
  138. package/src/__tests__/components/InstrumentStateBadge.test.ts +4 -4
  139. package/src/__tests__/components/JobsStatusTray.test.ts +373 -71
  140. package/src/__tests__/components/PluginWorkspaceView.test.ts +63 -1
  141. package/src/__tests__/components/ResourcePickerListInternal.test.ts +0 -20
  142. package/src/__tests__/components/SampleSelector.test.ts +33 -8
  143. package/src/__tests__/components/SequenceProgressBar.test.ts +12 -0
  144. package/src/__tests__/components/SettingsModal.test.ts +9 -2
  145. package/src/__tests__/components/StepWizard.test.ts +11 -0
  146. package/src/__tests__/components/WellPlate.decoration.test.ts +11 -4
  147. package/src/__tests__/components/deprecationTags.test.ts +1 -1
  148. package/src/__tests__/composables/useAppExperiment.test.ts +27 -1
  149. package/src/__tests__/composables/useCalendarGrid.test.ts +4 -2
  150. package/src/__tests__/composables/useExperimentSelector.test.ts +111 -0
  151. package/src/__tests__/composables/useFileImport.test.ts +36 -0
  152. package/src/__tests__/composables/useFloatingPanel.test.ts +68 -0
  153. package/src/__tests__/composables/useJobCenter.test.ts +262 -3
  154. package/src/__tests__/composables/useOnboarding.test.ts +317 -0
  155. package/src/__tests__/docs/frontendDocsCatalog.test.ts +4 -3
  156. package/src/__tests__/fixtures/table-import/cells.xlsx +0 -0
  157. package/src/__tests__/fixtures/table-import/samples.xlsx +0 -0
  158. package/src/__tests__/stores/settings.test.ts +21 -21
  159. package/src/__tests__/styles/baseControlsRules.test.ts +6 -2
  160. package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
  161. package/src/__tests__/styles/cssTokens.test.ts +6 -3
  162. package/src/__tests__/styles/menusShellRefresh.test.ts +22 -12
  163. package/src/__tests__/styles/paletteTokens.test.ts +44 -0
  164. package/src/__tests__/styles/v2AuditRules.test.ts +104 -0
  165. package/src/__tests__/utils/plotlyTemplate.test.ts +4 -4
  166. package/src/__tests__/utils/time.test.ts +18 -1
  167. package/src/components/ActionMenu.vue +6 -37
  168. package/src/components/AdductText.story.vue +7 -0
  169. package/src/components/AdductText.vue +1 -1
  170. package/src/components/AlertBox.story.vue +2 -2
  171. package/src/components/AlertBox.vue +18 -16
  172. package/src/components/AppSidebar.story.vue +0 -28
  173. package/src/components/AppTopBar.onboarding.story.vue +69 -0
  174. package/src/components/AppTopBar.story.vue +105 -15
  175. package/src/components/AppTopBar.vue +248 -122
  176. package/src/components/ArtifactSaveDialog.story.vue +23 -0
  177. package/src/components/ArtifactSaveDialog.vue +102 -12
  178. package/src/components/AuditTrail.story.vue +2 -1
  179. package/src/components/AuditTrail.vue +2 -21
  180. package/src/components/Avatar.vue +10 -14
  181. package/src/components/BaseButton.vue +16 -2
  182. package/src/components/BaseModal.vue +2 -1
  183. package/src/components/BaseSelect.multiple.story.vue +13 -2
  184. package/src/components/BaseSelect.vue +4 -3
  185. package/src/components/BatchProgressList.vue +35 -25
  186. package/src/components/ChartContainer.story.vue +2 -1
  187. package/src/components/ChartContainer.vue +8 -6
  188. package/src/components/ConfirmDialog.story.vue +1 -1
  189. package/src/components/EmptyState.vue +1 -1
  190. package/src/components/ExperimentPopover.story.vue +165 -53
  191. package/src/components/ExperimentPopover.vue +169 -187
  192. package/src/components/ExperimentSelectorModal.story.vue +73 -31
  193. package/src/components/ExperimentSelectorModal.vue +144 -111
  194. package/src/components/FormField.story.vue +8 -48
  195. package/src/components/GeneratedPluginView.vue +11 -10
  196. package/src/components/GeneratedResultRenderer.vue +3 -4
  197. package/src/components/InstrumentStateBadge.vue +1 -1
  198. package/src/components/JobsStatusTray.story.vue +97 -43
  199. package/src/components/JobsStatusTray.vue +287 -290
  200. package/src/components/LayoutResizeHandle.story.vue +65 -4
  201. package/src/components/LoadingSpinner.vue +1 -1
  202. package/src/components/NumberInput.vue +3 -6
  203. package/src/components/PluginIcon.story.vue +2 -10
  204. package/src/components/PluginIcon.vue +2 -2
  205. package/src/components/PluginWorkspaceView.props.ts +3 -0
  206. package/src/components/PluginWorkspaceView.story.vue +1 -1
  207. package/src/components/PluginWorkspaceView.vue +17 -0
  208. package/src/components/RuntimeUpdateNotice.vue +1 -1
  209. package/src/components/SampleSelector.story.vue +22 -7
  210. package/src/components/SampleSelector.vue +34 -37
  211. package/src/components/SampleSelectorGroupNode.vue +1 -1
  212. package/src/components/SectionCard.story.vue +35 -2
  213. package/src/components/SequenceProgressBar.story.vue +9 -0
  214. package/src/components/SequenceProgressBar.vue +6 -1
  215. package/src/components/SettingsModal.schema.ts +1 -1
  216. package/src/components/SettingsModal.vue +9 -14
  217. package/src/components/SmartGroupFieldRecipe.story.vue +1 -1
  218. package/src/components/SmartGroupManual.story.vue +1 -1
  219. package/src/components/StepWizard.story.vue +33 -0
  220. package/src/components/StepWizard.vue +8 -1
  221. package/src/components/Tooltip.vue +9 -32
  222. package/src/components/WellPlate.story.vue +3 -1
  223. package/src/components/WellPlate.vue +7 -0
  224. package/src/components/internal/AppTopBarOnboardingInternal.vue +172 -0
  225. package/src/components/internal/AppTopBarPageSelectorInternal.vue +140 -72
  226. package/src/components/internal/AppTopBarPillNavInternal.vue +33 -2
  227. package/src/components/internal/DatePickerDateInternal.vue +23 -85
  228. package/src/components/internal/DatePickerDateTimeInternal.vue +10 -14
  229. package/src/components/internal/DatePickerTimeInternal.vue +13 -9
  230. package/src/components/internal/NumberUnitInputInternal.vue +4 -18
  231. package/src/components/internal/OnboardingLayerInternal.vue +173 -0
  232. package/src/components/internal/ResourcePickerListInternal.vue +45 -85
  233. package/src/components/internal/SelectMultipleInternal.vue +187 -65
  234. package/src/components/internal/SelectSearchableInternal.vue +8 -4
  235. package/src/components/internal/WellEditPopupInternal.vue +11 -22
  236. package/src/components/internal/WorkspaceShellInternal.vue +12 -0
  237. package/src/composables/createClient.ts +2 -0
  238. package/src/composables/index.ts +1 -0
  239. package/src/composables/pluginIdentity.ts +31 -0
  240. package/src/composables/pluginWorkspaceModel.ts +2 -2
  241. package/src/composables/useAppExperiment.ts +20 -0
  242. package/src/composables/useCalendarGrid.ts +4 -3
  243. package/src/composables/useExperimentSelector.ts +77 -31
  244. package/src/composables/useFileImport.ts +51 -1
  245. package/src/composables/useFloatingPanel.ts +107 -0
  246. package/src/composables/useJobCenter.ts +188 -6
  247. package/src/composables/useOnboarding.ts +465 -0
  248. package/src/composables/usePluginWorkspace.ts +1 -1
  249. package/src/data.ts +1 -0
  250. package/src/runtime.ts +2 -1
  251. package/src/shell.ts +17 -0
  252. package/src/stores/index.ts +0 -2
  253. package/src/stores/settings.ts +20 -50
  254. package/src/styles/components/action-menu.css +1 -1
  255. package/src/styles/components/adduct-text.css +13 -5
  256. package/src/styles/components/alert-box.css +39 -58
  257. package/src/styles/components/app-layout.css +10 -6
  258. package/src/styles/components/app-page-selector.css +121 -44
  259. package/src/styles/components/app-pill-nav.css +11 -12
  260. package/src/styles/components/app-sidebar.css +9 -18
  261. package/src/styles/components/app-top-bar.css +219 -55
  262. package/src/styles/components/artifact-save-dialog.css +153 -2
  263. package/src/styles/components/audit-trail.css +16 -19
  264. package/src/styles/components/avatar.css +6 -4
  265. package/src/styles/components/batch-progress-list.css +38 -36
  266. package/src/styles/components/button.css +36 -59
  267. package/src/styles/components/calendar.css +12 -12
  268. package/src/styles/components/chart-container.css +34 -23
  269. package/src/styles/components/checkbox.css +2 -12
  270. package/src/styles/components/collapsible-card.css +27 -29
  271. package/src/styles/components/confirm-dialog.css +13 -14
  272. package/src/styles/components/dataframe.css +13 -5
  273. package/src/styles/components/date-picker.css +84 -94
  274. package/src/styles/components/datetime-picker.css +35 -47
  275. package/src/styles/components/divider.css +20 -20
  276. package/src/styles/components/dose-calculator.css +3 -3
  277. package/src/styles/components/empty-state.css +20 -17
  278. package/src/styles/components/experiment-code-badge.css +2 -0
  279. package/src/styles/components/experiment-popover.css +174 -329
  280. package/src/styles/components/experiment-selector-modal.css +88 -56
  281. package/src/styles/components/file-picker.css +3 -3
  282. package/src/styles/components/file-uploader.css +38 -49
  283. package/src/styles/components/form-builder.css +3 -3
  284. package/src/styles/components/formula-input.css +1 -1
  285. package/src/styles/components/icon-button.css +18 -24
  286. package/src/styles/components/input.css +5 -4
  287. package/src/styles/components/instrument-monitor.css +13 -15
  288. package/src/styles/components/jobs-status-tray.css +140 -296
  289. package/src/styles/components/layout-resize-handle.css +59 -16
  290. package/src/styles/components/modal.css +5 -7
  291. package/src/styles/components/molecule-input.css +2 -2
  292. package/src/styles/components/multi-select.css +65 -117
  293. package/src/styles/components/number-input.css +26 -19
  294. package/src/styles/components/onboarding.css +368 -0
  295. package/src/styles/components/pill.css +3 -2
  296. package/src/styles/components/plate-editor.css +0 -1
  297. package/src/styles/components/plugin-icon.css +26 -23
  298. package/src/styles/components/popover.css +2 -2
  299. package/src/styles/components/presence-avatars.css +1 -1
  300. package/src/styles/components/progress-bar.css +3 -3
  301. package/src/styles/components/protocol-step-editor.css +1 -3
  302. package/src/styles/components/resource-card.css +2 -2
  303. package/src/styles/components/resource-picker-list.css +6 -57
  304. package/src/styles/components/sample-hierarchy-tree.css +4 -4
  305. package/src/styles/components/sample-selector.css +138 -79
  306. package/src/styles/components/schedule-calendar.css +8 -8
  307. package/src/styles/components/searchable-select.css +15 -19
  308. package/src/styles/components/section-card.css +54 -49
  309. package/src/styles/components/segmented-control.css +18 -15
  310. package/src/styles/components/select.css +3 -3
  311. package/src/styles/components/sequence-input.css +2 -2
  312. package/src/styles/components/sequence-progress-bar.css +17 -8
  313. package/src/styles/components/settings-modal.css +7 -2
  314. package/src/styles/components/smart-group.css +18 -18
  315. package/src/styles/components/step-wizard.css +52 -16
  316. package/src/styles/components/tabs.css +2 -2
  317. package/src/styles/components/textarea.css +3 -3
  318. package/src/styles/components/time-picker.css +37 -25
  319. package/src/styles/components/toast.css +13 -11
  320. package/src/styles/components/toggle.css +1 -11
  321. package/src/styles/components/tooltip.css +12 -11
  322. package/src/styles/components/unit-input.css +9 -8
  323. package/src/styles/components/well-edit-popup.css +2 -2
  324. package/src/styles/components/well-plate.css +9 -2
  325. package/src/styles/index.css +1 -0
  326. package/src/styles/variables.css +247 -78
  327. package/src/types/componentWorkflowTypes.ts +2 -0
  328. package/src/types/components.ts +2 -0
  329. package/src/types/generated/platformContext.ts +1 -0
  330. package/src/types/generated-ui.ts +4 -0
  331. package/src/types/index.ts +1 -0
  332. package/src/types/jobs.ts +10 -0
  333. package/src/types/platform.ts +1 -1
  334. package/src/utils/time.ts +21 -0
  335. package/dist/ActionMenu-CScINahU.js.map +0 -1
  336. package/dist/AppSidebar-BmiiPfz9.js.map +0 -1
  337. package/dist/BaseModal-DfRIuVoj.js.map +0 -1
  338. package/dist/ExperimentPopover-C4sGw-is.js +0 -359
  339. package/dist/ExperimentPopover-C4sGw-is.js.map +0 -1
  340. package/dist/ExperimentSelectorModal-D-9z7cNx.js.map +0 -1
  341. package/dist/SettingsModal-CSLE31yg.js.map +0 -1
  342. package/dist/data-C2PwI_dn.js.map +0 -1
  343. package/dist/experiment-43OJtH7R.js.map +0 -1
  344. package/dist/lab-DLwRl_Ql.js.map +0 -1
  345. package/dist/records-BzTcxekG.js.map +0 -1
  346. package/dist/runtime-D6gVat6r.js.map +0 -1
  347. package/dist/useExperimentSamples-CxcnxZ8h.js.map +0 -1
  348. package/dist/useListReorder-CcbEYxX-.js.map +0 -1
  349. package/dist/useRackEditor-BD5NIo5s.js.map +0 -1
  350. package/dist/useRealtime-PalKue84.js +0 -696
  351. package/dist/useRealtime-PalKue84.js.map +0 -1
  352. package/dist/useRequest-D5JDfsgZ.js +0 -76
  353. package/dist/useRequest-D5JDfsgZ.js.map +0 -1
@@ -21,7 +21,26 @@ const emit = defineEmits<{
21
21
  const openItemId = ref<string | null>(null)
22
22
  const itemRefs = ref<Map<string, HTMLElement>>(new Map())
23
23
 
24
- const normalizedItems = computed<PillNavItem[]>(() => props.items.map(normalizePillNavItem))
24
+ /** The bar shows this many items; the rest fold into a More dropdown. */
25
+ const MAX_VISIBLE_ITEMS = 4
26
+ const MORE_ITEM_ID = '__mint-pill-nav-more'
27
+
28
+ const allItems = computed<PillNavItem[]>(() => props.items.map(normalizePillNavItem))
29
+
30
+ const normalizedItems = computed<PillNavItem[]>(() => {
31
+ if (allItems.value.length <= MAX_VISIBLE_ITEMS) return allItems.value
32
+ const folded = allItems.value.slice(MAX_VISIBLE_ITEMS)
33
+ return [
34
+ ...allItems.value.slice(0, MAX_VISIBLE_ITEMS),
35
+ {
36
+ id: MORE_ITEM_ID,
37
+ label: 'More',
38
+ dot: folded.some(item => item.dot),
39
+ // A folded group contributes its options; a plain item becomes one option.
40
+ children: folded.flatMap(item => item.children?.length ? item.children : [item]),
41
+ },
42
+ ]
43
+ })
25
44
 
26
45
  function handleClick(item: PillNavItem) {
27
46
  if (item.disabled) return
@@ -35,8 +54,20 @@ function handleClick(item: PillNavItem) {
35
54
 
36
55
  function handleOptionClick(option: PillNavOption, item: PillNavItem) {
37
56
  if (option.disabled) return
38
- if (!option.href && !option.to) emit('option-select', option, item)
39
57
  openItemId.value = null
58
+ if (option.href || option.to) return
59
+ if (item.id !== MORE_ITEM_ID) {
60
+ emit('option-select', option, item)
61
+ return
62
+ }
63
+ // A folded entry reports as it would from its own pill.
64
+ const folded = allItems.value.find(candidate => candidate.id === option.id && !candidate.children?.length)
65
+ if (folded) {
66
+ emit('select', folded)
67
+ return
68
+ }
69
+ const group = allItems.value.find(candidate => candidate.children?.some(child => child.id === option.id))
70
+ if (group) emit('option-select', option, group)
40
71
  }
41
72
 
42
73
  function normalizePillNavItem(item: PillNavItemInput): PillNavItem {
@@ -1,9 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  /** DatePicker type="date": input that opens a calendar dropdown for selecting a single date, with clearable support. */
3
- import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom'
4
- import { ref, computed, nextTick, onBeforeUnmount, watch, useTemplateRef } from 'vue'
3
+ import { ref, computed, nextTick, watch, useTemplateRef } from 'vue'
5
4
  import { useCalendarGrid, type CalendarGridDay } from '../../composables/useCalendarGrid'
6
5
  import { useDropdownState } from '../../composables/useDropdownState'
6
+ import { useFloatingPanel } from '../../composables/useFloatingPanel'
7
7
  import { useFocusTrap } from '../../composables/useFocusTrap'
8
8
  import CalendarGridPanelInternal from './CalendarGridPanelInternal.vue'
9
9
 
@@ -30,11 +30,13 @@ const emit = defineEmits<{
30
30
  }>()
31
31
 
32
32
  const dropdownRef = ref<HTMLElement | null>(null)
33
- const dropdownStyle = ref<Record<string, string>>({})
34
33
  const { isOpen, triggerRef, open, close, toggle } = useDropdownState({
35
34
  rootRef: useTemplateRef<HTMLElement>('container'),
36
35
  insideRefs: [dropdownRef],
37
36
  })
37
+ // floating-ui keeps the panel anchored (flip/shift) while the page scrolls,
38
+ // instead of the panel closing on every scroll or resize.
39
+ const { style: dropdownStyle } = useFloatingPanel(triggerRef, dropdownRef, isOpen)
38
40
  // The panel is a non-modal dialog: Tab cycles inside it while it is open, and
39
41
  // focus returns to the input when it closes.
40
42
  useFocusTrap({ containerRef: dropdownRef, active: isOpen })
@@ -42,7 +44,6 @@ const {
42
44
  weekDays,
43
45
  monthYear,
44
46
  calendarDays,
45
- isDateDisabled,
46
47
  formatDateValue,
47
48
  setCurrentMonthFromDate,
48
49
  prevMonth,
@@ -58,14 +59,7 @@ const selectedDate = computed(() => {
58
59
  return isNaN(date.getTime()) ? null : date
59
60
  })
60
61
 
61
- const displayValue = computed(() => {
62
- if (!selectedDate.value) return ''
63
- return selectedDate.value.toLocaleDateString('en-US', {
64
- year: 'numeric',
65
- month: 'short',
66
- day: 'numeric',
67
- })
68
- })
62
+ const displayValue = computed(() => (selectedDate.value ? formatDateValue(selectedDate.value) : ''))
69
63
 
70
64
  function selectDate(day: CalendarGridDay) {
71
65
  if (day.isDisabled || props.disabled) return
@@ -73,15 +67,6 @@ function selectDate(day: CalendarGridDay) {
73
67
  close()
74
68
  }
75
69
 
76
- function goToToday() {
77
- const today = new Date()
78
- setCurrentMonthFromDate(today)
79
- if (!isDateDisabled(today)) {
80
- emit('update:modelValue', formatDateValue(today))
81
- close()
82
- }
83
- }
84
-
85
70
  function clear() {
86
71
  emit('update:modelValue', undefined)
87
72
  close()
@@ -100,41 +85,6 @@ function handleTriggerKeydown(event: KeyboardEvent) {
100
85
  }
101
86
  }
102
87
 
103
- // floating-ui keeps the panel anchored (flip/shift) while the page scrolls,
104
- // instead of the panel closing on every scroll or resize.
105
- let stopAutoUpdate: (() => void) | null = null
106
-
107
- async function updateDropdownPosition() {
108
- const trigger = triggerRef.value
109
- const panel = dropdownRef.value
110
- if (!trigger || !panel) return
111
-
112
- const { x, y } = await computePosition(trigger, panel, {
113
- placement: 'bottom-start',
114
- strategy: 'fixed',
115
- middleware: [offset(4), flip(), shift({ padding: 8 })],
116
- })
117
- dropdownStyle.value = {
118
- position: 'fixed',
119
- top: `${y}px`,
120
- left: `${x}px`,
121
- zIndex: '9999',
122
- }
123
- }
124
-
125
- function stopPositioning() {
126
- stopAutoUpdate?.()
127
- stopAutoUpdate = null
128
- }
129
-
130
- function startPositioning() {
131
- const trigger = triggerRef.value
132
- const panel = dropdownRef.value
133
- if (!trigger || !panel) return
134
- stopPositioning()
135
- stopAutoUpdate = autoUpdate(trigger, panel, () => void updateDropdownPosition())
136
- }
137
-
138
88
  /** Moves focus into the calendar grid: selected day, else today, else the first day. */
139
89
  function focusCalendar() {
140
90
  const panel = dropdownRef.value
@@ -147,26 +97,25 @@ function focusCalendar() {
147
97
  }
148
98
 
149
99
  watch(isOpen, async (opened) => {
150
- if (!opened) {
151
- stopPositioning()
152
- return
153
- }
100
+ if (!opened) return
154
101
  if (selectedDate.value) {
155
102
  setCurrentMonthFromDate(selectedDate.value)
156
103
  }
157
104
  await nextTick()
158
- startPositioning()
159
105
  // A second tick so this focus lands after the focus trap's initial focus.
160
106
  await nextTick()
161
107
  focusCalendar()
162
108
  })
163
-
164
- onBeforeUnmount(stopPositioning)
165
109
  </script>
166
110
 
167
111
  <template>
168
112
  <div ref="container" class="mint-date-picker">
169
113
  <div class="mint-date-picker__input-wrapper">
114
+ <div class="mint-date-picker__icon">
115
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
116
+ <path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" />
117
+ </svg>
118
+ </div>
170
119
  <input
171
120
  ref="triggerRef"
172
121
  type="text"
@@ -185,11 +134,17 @@ onBeforeUnmount(stopPositioning)
185
134
  @click="toggleCalendar"
186
135
  @keydown="handleTriggerKeydown"
187
136
  />
188
- <div class="mint-date-picker__icon">
189
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
190
- <path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" />
137
+ <button
138
+ v-if="clearable && modelValue && !disabled"
139
+ type="button"
140
+ class="mint-date-picker__clear-btn"
141
+ aria-label="Clear date"
142
+ @click.stop="clear"
143
+ >
144
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
145
+ <path d="M18 6 6 18" /><path d="m6 6 12 12" />
191
146
  </svg>
192
- </div>
147
+ </button>
193
148
  </div>
194
149
 
195
150
  <Teleport to="body">
@@ -208,6 +163,7 @@ onBeforeUnmount(stopPositioning)
208
163
  :style="dropdownStyle"
209
164
  role="dialog"
210
165
  aria-label="Date picker"
166
+ @keydown.escape.prevent="close(true)"
211
167
  >
212
168
  <CalendarGridPanelInternal
213
169
  :week-days="weekDays"
@@ -218,24 +174,6 @@ onBeforeUnmount(stopPositioning)
218
174
  @next-month="nextMonth"
219
175
  @select="selectDate"
220
176
  />
221
-
222
- <div class="mint-date-picker__footer">
223
- <button
224
- type="button"
225
- class="mint-date-picker__footer-btn mint-date-picker__today-btn"
226
- @click="goToToday"
227
- >
228
- Today
229
- </button>
230
- <button
231
- v-if="clearable && modelValue"
232
- type="button"
233
- class="mint-date-picker__footer-btn mint-date-picker__clear-btn"
234
- @click="clear"
235
- >
236
- Clear
237
- </button>
238
- </div>
239
177
  </div>
240
178
  </Transition>
241
179
  </Teleport>
@@ -3,6 +3,7 @@
3
3
  import { computed, ref, watch, nextTick, useTemplateRef } from 'vue'
4
4
  import { useCalendarGrid, type CalendarGridDay } from '../../composables/useCalendarGrid'
5
5
  import { useDropdownState } from '../../composables/useDropdownState'
6
+ import { useFloatingPanel } from '../../composables/useFloatingPanel'
6
7
  import { useFocusTrap } from '../../composables/useFocusTrap'
7
8
  import { formatTime, formatTimeSlot, generateTimeSlots, toMinutes } from '../../utils/time'
8
9
  import CalendarGridPanelInternal from './CalendarGridPanelInternal.vue'
@@ -37,10 +38,13 @@ const emit = defineEmits<{
37
38
  }>()
38
39
 
39
40
  const dropdownRef = ref<HTMLElement | null>(null)
41
+ const rootRef = useTemplateRef<HTMLElement>('root')
40
42
  const { isOpen, open, close, toggle } = useDropdownState({
41
- rootRef: useTemplateRef<HTMLElement>('root'),
43
+ rootRef,
42
44
  triggerRef: useTemplateRef<HTMLElement>('trigger'),
43
45
  })
46
+ // Fixed below the field, so a modal body's overflow does not clip it; it stays in place in the DOM.
47
+ const { style: dropdownStyle } = useFloatingPanel(rootRef, dropdownRef, isOpen)
44
48
  // Non-modal dialog: Tab stays inside the panel, focus returns to the input on close.
45
49
  useFocusTrap({ containerRef: dropdownRef, active: isOpen })
46
50
  const {
@@ -76,17 +80,12 @@ const selectedTimeStr = computed(() => {
76
80
 
77
81
  const displayValue = computed(() => {
78
82
  if (!selectedDate.value) return ''
79
- const dateStr = selectedDate.value.toLocaleDateString(props.locale, {
80
- year: 'numeric',
81
- month: 'short',
82
- day: 'numeric',
83
- })
84
83
  const timeStr = formatTime(
85
84
  selectedDate.value.getHours(),
86
85
  selectedDate.value.getMinutes(),
87
86
  props.timeFormat,
88
87
  )
89
- return `${dateStr} ${timeStr}`
88
+ return `${formatDateValue(selectedDate.value)} ${timeStr}`
90
89
  })
91
90
 
92
91
  // Time slots
@@ -198,8 +197,8 @@ watch(isOpen, async (opened) => {
198
197
  <div ref="root" class="mint-datetime-picker">
199
198
  <div class="mint-datetime-picker__input-wrapper">
200
199
  <div class="mint-datetime-picker__icon-calendar">
201
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
202
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
200
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
201
+ <path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" />
203
202
  </svg>
204
203
  </div>
205
204
  <input
@@ -221,11 +220,6 @@ watch(isOpen, async (opened) => {
221
220
  @click="toggleDropdown"
222
221
  @keydown="handleTriggerKeydown"
223
222
  />
224
- <div class="mint-datetime-picker__icon-clock">
225
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
226
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
227
- </svg>
228
- </div>
229
223
  </div>
230
224
 
231
225
  <Transition
@@ -240,8 +234,10 @@ watch(isOpen, async (opened) => {
240
234
  v-if="isOpen"
241
235
  ref="dropdownRef"
242
236
  class="mint-popover mint-datetime-picker__dropdown"
237
+ :style="dropdownStyle"
243
238
  role="dialog"
244
239
  aria-label="Date and time picker"
240
+ @keydown.escape.prevent="close(true)"
245
241
  >
246
242
  <!-- Calendar section -->
247
243
  <div class="mint-datetime-picker__calendar-section">
@@ -3,6 +3,7 @@
3
3
  import { ref, computed, watch, nextTick, useId, useTemplateRef } from 'vue'
4
4
  import type { TimeSlotDisabledFn } from '../../types'
5
5
  import { useDropdownState } from '../../composables/useDropdownState'
6
+ import { useFloatingPanel } from '../../composables/useFloatingPanel'
6
7
  import {
7
8
  compareTime,
8
9
  findNearestTimeSlotIndex,
@@ -40,12 +41,16 @@ const emit = defineEmits<{
40
41
  }>()
41
42
 
42
43
  const listRef = ref<HTMLUListElement | null>(null)
44
+ const dropdownRef = ref<HTMLElement | null>(null)
43
45
  const highlightedIndex = ref(-1)
44
46
  const listboxId = `mint-time-picker-listbox-${useId().replace(/:/g, '')}`
47
+ const rootRef = useTemplateRef<HTMLElement>('root')
45
48
  const { isOpen, open, close, toggle } = useDropdownState({
46
- rootRef: useTemplateRef<HTMLElement>('root'),
49
+ rootRef,
47
50
  triggerRef: useTemplateRef<HTMLElement>('trigger'),
48
51
  })
52
+ // Fixed below the field and at least as wide, so a modal body's overflow does not clip it; it stays in place in the DOM.
53
+ const { style: dropdownStyle } = useFloatingPanel(rootRef, dropdownRef, isOpen, { matchWidth: true })
49
54
 
50
55
  /** Id of the highlighted slot, published to the input as `aria-activedescendant`. */
51
56
  const activeSlotId = computed(() =>
@@ -210,6 +215,11 @@ watch(isOpen, (open) => {
210
215
  <template>
211
216
  <div ref="root" class="mint-time-picker" @keydown="handleKeydown">
212
217
  <div class="mint-time-picker__input-wrapper">
218
+ <div class="mint-time-picker__icon">
219
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
220
+ <circle cx="12" cy="12" r="10" /><path d="M12 6v6l4 2" />
221
+ </svg>
222
+ </div>
213
223
  <input
214
224
  ref="trigger"
215
225
  type="text"
@@ -241,11 +251,6 @@ watch(isOpen, (open) => {
241
251
  <path d="M18 6 6 18" /><path d="m6 6 12 12" />
242
252
  </svg>
243
253
  </button>
244
- <div class="mint-time-picker__icon">
245
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
246
- <circle cx="12" cy="12" r="10" /><path d="M12 6v6l4 2" />
247
- </svg>
248
- </div>
249
254
  </div>
250
255
 
251
256
  <Transition
@@ -258,7 +263,9 @@ watch(isOpen, (open) => {
258
263
  >
259
264
  <div
260
265
  v-if="isOpen"
266
+ ref="dropdownRef"
261
267
  class="mint-popover mint-time-picker__dropdown"
268
+ :style="dropdownStyle"
262
269
  >
263
270
  <ul
264
271
  :id="listboxId"
@@ -284,9 +291,6 @@ watch(isOpen, (open) => {
284
291
  >
285
292
  <span class="mint-time-picker__slot-time">{{ formatTimeSlot(slot, format) }}</span>
286
293
  <span v-if="slotReason(slot)" class="mint-time-picker__slot-reason">{{ slotReason(slot) }}</span>
287
- <svg class="mint-menu-item__check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
288
- <path d="M3.5 8.5l3 3 6-7" />
289
- </svg>
290
294
  </li>
291
295
  </ul>
292
296
  </div>
@@ -4,9 +4,9 @@
4
4
  * previews the value in each unit; units with a `factor` also get a hint in the neighbour unit
5
5
  * nearest to 1–1000.
6
6
  */
7
- import { computePosition, flip, offset, shift } from '@floating-ui/dom'
8
- import { computed, nextTick, ref, useTemplateRef, watch } from 'vue'
7
+ import { computed, nextTick, ref, useTemplateRef } from 'vue'
9
8
  import { useDropdownState } from '../../composables/useDropdownState'
9
+ import { useFloatingPanel } from '../../composables/useFloatingPanel'
10
10
  import { useMenuKeyboard } from '../../composables/useMenuKeyboard'
11
11
  import type { UnitOption } from '../../types'
12
12
 
@@ -148,27 +148,13 @@ function selectUnit(newUnitValue: string) {
148
148
 
149
149
  // ---- Unit menu (disclosure + WAI-ARIA keyboard, shared with ActionMenu) ----
150
150
  const menuRef = ref<HTMLElement | null>(null)
151
- // Transparent (not visibility:hidden) until placed, so the open-time focus can land on an option.
152
- const menuStyle = ref<Record<string, string>>({ opacity: '0' })
153
151
  const { isOpen, rootRef, open, close } = useDropdownState({
154
152
  triggerRef: useTemplateRef<HTMLElement>('trigger'),
155
- closeOnScroll: true,
156
153
  insideRefs: [menuRef],
157
- onOpen: () => { menuStyle.value = { opacity: '0' } },
158
154
  })
159
155
 
160
- /** Teleported + fixed like ActionMenu, so overflow:hidden/auto ancestors never clip the menu. */
161
- watch(isOpen, async (opened) => {
162
- if (!opened) return
163
- await nextTick()
164
- if (!rootRef.value || !menuRef.value) return
165
- const { x, y } = await computePosition(rootRef.value, menuRef.value, {
166
- placement: 'bottom-end',
167
- strategy: 'fixed',
168
- middleware: [offset(4), flip(), shift({ padding: 8 })],
169
- })
170
- menuStyle.value = { left: `${x}px`, top: `${y}px` }
171
- })
156
+ /** Teleported + fixed like ActionMenu, so overflow:hidden/auto ancestors never clip the menu; it follows the field on scroll. */
157
+ const { style: menuStyle } = useFloatingPanel(rootRef, menuRef, isOpen, { placement: 'bottom-end' })
172
158
 
173
159
  function menuItems(): HTMLElement[] {
174
160
  return menuRef.value ? Array.from(menuRef.value.querySelectorAll<HTMLElement>('[role="option"]')) : []
@@ -0,0 +1,173 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * The tour layer: a scrim with a hole over the anchor, the step card, and the
4
+ * what's-new dots. AppTopBar renders it when the app provides onboarding.
5
+ */
6
+ import { computed, nextTick, onBeforeUnmount, ref, useId, watch } from 'vue'
7
+ import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom'
8
+ import { X } from 'lucide-vue-next'
9
+ import BaseButton from '../BaseButton.vue'
10
+ import { useEventListener } from '../../composables/useEventListener'
11
+ import type { OnboardingState } from '../../composables/useOnboarding'
12
+
13
+ const props = defineProps<{ state: OnboardingState }>()
14
+
15
+ // Space between the anchor and the edge of the hole.
16
+ const HOLE_PAD = 4
17
+
18
+ const active = computed(() => props.state.active.value)
19
+ const card = ref<HTMLElement | null>(null)
20
+ const hole = ref<DOMRect | null>(null)
21
+ const cardPosition = ref({ x: 0, y: 0 })
22
+ const titleId = useId()
23
+ let stopAutoUpdate: (() => void) | undefined
24
+
25
+ async function position() {
26
+ const current = active.value
27
+ if (!current || !card.value) return
28
+ hole.value = current.element.getBoundingClientRect()
29
+ const { x, y } = await computePosition(current.element, card.value, {
30
+ strategy: 'fixed',
31
+ placement: current.step.placement ?? 'bottom',
32
+ middleware: [offset(12), flip(), shift({ padding: 12 })],
33
+ })
34
+ // The step may have changed while the position was computed.
35
+ if (active.value === current) cardPosition.value = { x, y }
36
+ }
37
+
38
+ // Focus goes back where it was when the run ends.
39
+ let returnFocus: HTMLElement | null = null
40
+ watch(active, async (current, previous) => {
41
+ stopAutoUpdate?.()
42
+ stopAutoUpdate = undefined
43
+ hole.value = null
44
+ if (current && !previous) returnFocus = document.activeElement as HTMLElement | null
45
+ if (!current) {
46
+ if (returnFocus?.isConnected) returnFocus.focus()
47
+ returnFocus = null
48
+ return
49
+ }
50
+ await nextTick()
51
+ if (!card.value || active.value !== current) return
52
+ current.element.scrollIntoView({ block: 'nearest', inline: 'nearest' })
53
+ stopAutoUpdate = autoUpdate(current.element, card.value, () => void position())
54
+ // A click step: the keyboard user presses the control itself.
55
+ if (current.step.advanceOn === 'click') current.element.focus()
56
+ // Otherwise the card itself: no focus ring on load, and Tab reaches Back and Next.
57
+ else card.value.focus()
58
+ }, { immediate: true })
59
+ onBeforeUnmount(() => stopAutoUpdate?.())
60
+
61
+ // The layer is its own focus trap: Tab cycles over the card's buttons (and the
62
+ // anchor on a click step), never the page behind; from the card itself Tab goes
63
+ // to the first stop and Shift+Tab to the last.
64
+ useEventListener(document, 'keydown', (event) => {
65
+ const current = active.value
66
+ if (!current || !card.value) return
67
+ // Keys belong to the tour only while focus is on its card, its anchor, or nowhere.
68
+ const focused = document.activeElement
69
+ if (focused && focused !== document.body && !card.value.contains(focused) && focused !== current.element) return
70
+ const onCard = focused === card.value
71
+ if (event.key === 'Escape') {
72
+ event.stopPropagation()
73
+ props.state.dismiss()
74
+ } else if (event.key === 'Tab') {
75
+ const buttons = [...card.value.querySelectorAll<HTMLElement>('button')]
76
+ const stops = current.step.advanceOn === 'click' ? [current.element, ...buttons] : buttons
77
+ const at = stops.indexOf(document.activeElement as HTMLElement)
78
+ const target = at === -1 ? (event.shiftKey ? stops.length - 1 : 0) : (at + (event.shiftKey ? -1 : 1) + stops.length) % stops.length
79
+ event.preventDefault()
80
+ stops[target].focus()
81
+ } else if (event.key === 'Enter' && onCard && current.step.advanceOn !== 'click') {
82
+ event.preventDefault()
83
+ props.state.next()
84
+ }
85
+ }, { capture: true })
86
+
87
+ const holeStyle = computed(() => hole.value && {
88
+ left: `${hole.value.left - HOLE_PAD}px`,
89
+ top: `${hole.value.top - HOLE_PAD}px`,
90
+ width: `${hole.value.width + 2 * HOLE_PAD}px`,
91
+ height: `${hole.value.height + 2 * HOLE_PAD}px`,
92
+ })
93
+ // advanceOn 'click': cut the hole out of the click blocker, so the anchor takes the click.
94
+ const blockerStyle = computed(() => {
95
+ const rect = hole.value
96
+ if (!rect || active.value?.step.advanceOn !== 'click') return undefined
97
+ const [x, y, w, h] = [rect.left - HOLE_PAD, rect.top - HOLE_PAD, rect.width + 2 * HOLE_PAD, rect.height + 2 * HOLE_PAD]
98
+ return { clipPath: `path(evenodd, 'M0 0H${window.innerWidth}V${window.innerHeight}H0Z M${x} ${y}h${w}v${h}h${-w}Z')` }
99
+ })
100
+ const isLast = computed(() => !!active.value && active.value.index === active.value.count - 1)
101
+ const nextLabel = computed(() => active.value?.news ? 'Got it' : isLast.value ? 'Done' : 'Next')
102
+
103
+ // What's-new dots: one on each element with unseen news, hidden while a card shows.
104
+ const dots = ref<Array<{ id: string; title: string; x: number; y: number }>>([])
105
+ let frame = 0
106
+ function placeDots() {
107
+ cancelAnimationFrame(frame)
108
+ frame = requestAnimationFrame(() => {
109
+ dots.value = active.value ? [] : props.state.news.value.flatMap((item) => {
110
+ if (!item.unseen) return []
111
+ const element = document.querySelector(`[data-onboarding="${CSS.escape(item.anchor)}"]`)
112
+ if (!element) return []
113
+ const rect = element.getBoundingClientRect()
114
+ return rect.width ? [{ id: item.id, title: item.title, x: rect.right, y: rect.top }] : []
115
+ })
116
+ })
117
+ }
118
+ const observer = new MutationObserver(placeDots)
119
+ watch(() => props.state.unseenNews.value > 0, (watching) => {
120
+ observer.disconnect()
121
+ if (watching) observer.observe(document.body, { childList: true, subtree: true })
122
+ placeDots()
123
+ }, { immediate: true })
124
+ watch(active, placeDots)
125
+ useEventListener(window, 'resize', placeDots)
126
+ useEventListener(window, 'scroll', placeDots, { capture: true, passive: true })
127
+ onBeforeUnmount(() => {
128
+ observer.disconnect()
129
+ cancelAnimationFrame(frame)
130
+ })
131
+ </script>
132
+
133
+ <template>
134
+ <Teleport to="body">
135
+ <button
136
+ v-for="dot in dots"
137
+ :key="dot.id"
138
+ type="button"
139
+ class="mint-onboarding__dot"
140
+ :style="{ left: `${dot.x}px`, top: `${dot.y}px` }"
141
+ :aria-label="`New: ${dot.title}`"
142
+ @click="state.showNews(dot.id)"
143
+ />
144
+ <div v-if="active" class="mint-onboarding">
145
+ <div class="mint-onboarding__blocker" :style="blockerStyle" />
146
+ <div v-if="holeStyle" class="mint-onboarding__hole" :style="holeStyle" />
147
+ <div
148
+ ref="card"
149
+ class="mint-onboarding__card"
150
+ role="dialog"
151
+ aria-modal="true"
152
+ :aria-labelledby="titleId"
153
+ tabindex="-1"
154
+ :style="{ left: `${cardPosition.x}px`, top: `${cardPosition.y}px` }"
155
+ >
156
+ <button type="button" class="mint-onboarding__close" :aria-label="active.news ? 'Close' : 'Close tour'" @click="state.dismiss()">
157
+ <X :size="14" aria-hidden="true" />
158
+ </button>
159
+ <span v-if="active.count > 1" class="mint-onboarding__count">
160
+ <span aria-hidden="true">{{ active.index + 1 }} / {{ active.count }}</span>
161
+ <span class="mint-onboarding__sr">Step {{ active.index + 1 }} of {{ active.count }}</span>
162
+ </span>
163
+ <p :id="titleId" class="mint-onboarding__title">{{ active.step.title }}</p>
164
+ <p class="mint-onboarding__body">{{ active.step.body }}</p>
165
+ <div class="mint-onboarding__footer">
166
+ <span v-if="active.step.advanceOn === 'click'" class="mint-onboarding__hint">Click the highlighted control.</span>
167
+ <BaseButton v-if="active.index > 0" variant="ghost" size="sm" @click="state.back()">Back</BaseButton>
168
+ <BaseButton v-if="active.step.advanceOn !== 'click'" size="sm" @click="state.next()">{{ nextLabel }}</BaseButton>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ </Teleport>
173
+ </template>