@morscherlab/mint-sdk 1.4.0-beta.8 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -2,7 +2,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
2
  import { createPinia, setActivePinia } from 'pinia'
3
3
  import { nextTick } from 'vue'
4
4
  import {
5
- paletteCssVariables,
6
5
  tableDensityToSize,
7
6
  tryUseSettingsStore,
8
7
  useSettingsStore,
@@ -54,6 +53,7 @@ describe('useSettingsStore', () => {
54
53
  document.documentElement.classList.remove('dark')
55
54
  document.documentElement.removeAttribute('style')
56
55
  delete document.documentElement.dataset.density
56
+ delete document.documentElement.dataset.palette
57
57
  mockMatchMedia(false)
58
58
  })
59
59
 
@@ -72,51 +72,51 @@ describe('useSettingsStore', () => {
72
72
  expect(settings.serverHost).toBe('127.0.0.1')
73
73
  expect(settings.serverPort).toBe(8001)
74
74
  expect(settings.theme).toBe('dark')
75
- expect(settings.colorPalette).toBe('viridis')
75
+ expect(settings.colorPalette).toBe('purple')
76
76
  expect(settings.tableDensity).toBe('compact')
77
77
  expect(localStorage.getItem('mld-settings')).toBeNull()
78
78
  expect(JSON.parse(localStorage.getItem('mint-settings') || '{}')).toEqual(expect.objectContaining({
79
79
  serverHost: '127.0.0.1',
80
80
  serverPort: 8001,
81
81
  theme: 'dark',
82
- colorPalette: 'viridis',
82
+ colorPalette: 'purple',
83
83
  tableDensity: 'compact',
84
84
  }))
85
85
  })
86
86
 
87
87
  describe('palette and density DOM sync', () => {
88
- it('writes no palette overrides for the default palette', () => {
88
+ it('mirrors the stored palette onto html[data-palette] without inline overrides', () => {
89
+ localStorage.setItem('mint-settings', JSON.stringify({ colorPalette: 'ocean' }))
90
+
89
91
  const settings = useSettingsStore()
90
92
  settings.initialize()
91
93
 
92
- expect(paletteCssVariables('default')).toEqual({})
94
+ expect(document.documentElement.dataset.palette).toBe('ocean')
93
95
  expect(document.documentElement.style.getPropertyValue('--color-primary')).toBe('')
94
96
  })
95
97
 
96
- it('applies the stored palette hues to html on initialize', () => {
97
- localStorage.setItem('mint-settings', JSON.stringify({ colorPalette: 'viridis' }))
98
-
98
+ it('updates html[data-palette] when the palette changes', async () => {
99
99
  const settings = useSettingsStore()
100
100
  settings.initialize()
101
+ expect(document.documentElement.dataset.palette).toBe('default')
101
102
 
102
- const style = document.documentElement.style
103
- expect(style.getPropertyValue('--color-primary')).toBe('hsl(280 84% 67%)')
104
- expect(style.getPropertyValue('--color-primary-hover')).toBe('hsl(280 75% 59%)')
105
- expect(style.getPropertyValue('--color-cta')).toBe('hsl(80 95% 53%)')
103
+ settings.colorPalette = 'forest'
104
+ await nextTick()
105
+ expect(document.documentElement.dataset.palette).toBe('forest')
106
106
  })
107
107
 
108
- it('re-applies the overrides when the palette changes and clears them on default', async () => {
108
+ it.each([
109
+ ['viridis', 'purple'],
110
+ ['pastel', 'default'],
111
+ ['not-a-palette', 'default'],
112
+ ])('maps the stored palette %s onto %s', (stored, expected) => {
113
+ localStorage.setItem('mint-settings', JSON.stringify({ colorPalette: stored }))
114
+
109
115
  const settings = useSettingsStore()
110
116
  settings.initialize()
111
117
 
112
- settings.colorPalette = 'pastel'
113
- await nextTick()
114
- expect(document.documentElement.style.getPropertyValue('--color-primary')).toBe('hsl(200 84% 67%)')
115
-
116
- settings.colorPalette = 'default'
117
- await nextTick()
118
- expect(document.documentElement.style.getPropertyValue('--color-primary')).toBe('')
119
- expect(document.documentElement.style.getPropertyValue('--color-primary-soft')).toBe('')
118
+ expect(settings.colorPalette).toBe(expected)
119
+ expect(document.documentElement.dataset.palette).toBe(expected)
120
120
  })
121
121
 
122
122
  it('mirrors the table density onto html[data-density]', async () => {
@@ -29,11 +29,11 @@ describe('SDK control refresh base-control rules', () => {
29
29
  })
30
30
 
31
31
  it('should find no soft or plain focus ring when scanning pressable controls (1a)', () => {
32
- const pressables = ['checkbox.css', 'radio-group.css', 'toggle.css', 'icon-button.css', 'multi-select.css']
32
+ const pressables = ['checkbox.css', 'radio-group.css', 'toggle.css', 'icon-button.css']
33
33
  expect(offenders(pressables, (l) => /var\(--focus-ring(-soft)?\)/.test(l))).toEqual([])
34
34
  })
35
35
 
36
- it.each(['input.css', 'select.css', 'textarea.css'])(
36
+ it.each(['input.css', 'select.css', 'textarea.css', 'multi-select.css'])(
37
37
  'should use the shared field hover border when scanning %s (1e)',
38
38
  (file) => {
39
39
  expect(css(file)).toContain('border-color: var(--_field-border-hover)')
@@ -77,6 +77,10 @@ describe('SDK control refresh base-control rules', () => {
77
77
  expect(offenders(['alert-box.css'], (l) => /\.mint-alert--\w+ \.mint-alert__title/.test(l))).toEqual([])
78
78
  })
79
79
 
80
+ it('should find no tone fill on the alert surface when scanning alert-box.css (v2 callout)', () => {
81
+ expect(offenders(['alert-box.css'], (l) => /^\.mint-alert--\w+\s*\{/.test(l))).toEqual([])
82
+ })
83
+
80
84
  it('should find no pill radius on the chip when scanning multi-select.css (1i)', () => {
81
85
  expect(offenders(['multi-select.css'], (l) => /9999px|999px/.test(l))).toEqual([])
82
86
  })
@@ -197,12 +197,6 @@ describe('SDK control refresh style rules', () => {
197
197
  expect(rule).toMatch(/padding:\s*0/)
198
198
  })
199
199
 
200
- it('should pad resource picker group bars to the modal gutter when scanning resource-picker-list.css', () => {
201
- const css = stripComments(readFileSync(component('resource-picker-list.css'), 'utf-8'))
202
- const rule = /\.mint-resource-picker__group\s*\{([^}]*)\}/.exec(css)?.[1] ?? ''
203
- expect(rule).toMatch(/padding:\s*0 1\.25rem/)
204
- })
205
-
206
200
  // The list sat in a bordered card inside the modal card.
207
201
  it('should draw no frame around the resource picker list when scanning resource-picker-list.css', () => {
208
202
  const css = stripComments(readFileSync(component('resource-picker-list.css'), 'utf-8'))
@@ -14,7 +14,10 @@ const variablesCss = readFileSync(join(stylesDir, 'variables.css'), 'utf-8')
14
14
  * component-local knobs declared in the same stylesheet that consumes them.
15
15
  * Anything else must be a canonical token from variables.css.
16
16
  */
17
- const RUNTIME_PROPERTY_ALLOWLIST = new Set<string>([])
17
+ const RUNTIME_PROPERTY_ALLOWLIST = new Set<string>([
18
+ // Consumer knob: the gap width an edge LayoutResizeHandle centres on (falls back to --mint-gutter).
19
+ '--mint-layout-resize-gap',
20
+ ])
18
21
 
19
22
  const SOURCE_EXTENSIONS = ['.vue', '.css', '.ts']
20
23
  const IGNORED_DIRS = new Set(['__tests__', 'generated-ui', 'node_modules'])
@@ -93,8 +96,8 @@ describe('CSS custom properties', () => {
93
96
 
94
97
  // 9a: PlotlyChart reads it through getComputedStyle('--font-sans'), which the
95
98
  // scan above counts as a declaration, so it slipped through as undefined.
96
- it('should declare --font-sans as Fira Sans when charts read the sans font token (9a)', () => {
97
- expect(variablesCss).toMatch(/--font-sans:\s*'Fira Sans'/)
99
+ it('should declare --font-sans as Geist when charts read the sans font token (9a)', () => {
100
+ expect(variablesCss).toMatch(/--font-sans:\s*'Geist'/)
98
101
  })
99
102
 
100
103
  it('does not use the platform-only --color-bg-*/--color-text-*/--color-border aliases', () => {
@@ -25,13 +25,19 @@ describe('menus and app shell styles', () => {
25
25
  expect(css('app-top-bar.css')).toMatch(/\.mint-topbar\s*\{[^}]*padding:\s*0\.75rem 1rem;/)
26
26
  })
27
27
 
28
- // 28px items + 3px track padding + 1px border on each side = the 36px top-bar control height.
29
- it('should size pill nav items so the track matches the 36px top-bar controls', () => {
30
- expect(css('app-pill-nav.css')).toMatch(/\.mint-pill-nav__item\s*\{[^}]*height:\s*1\.75rem;/)
28
+ // 26px items + 2px track padding + 1px border on each side = the 32px top-bar control height.
29
+ it('should size pill nav items so the track matches the 32px top-bar controls', () => {
30
+ expect(css('app-pill-nav.css')).toMatch(/\.mint-pill-nav__item\s*\{[^}]*height:\s*1\.625rem;/)
31
31
  })
32
32
 
33
- it('should size the page selector trigger to the 36px identity tile', () => {
34
- expect(css('app-page-selector.css')).toMatch(/\.mint-page-selector__trigger\s*\{[^}]*height:\s*2\.25rem;/)
33
+ // Design v2 option C: the page switcher sits in the crumb at the 32px top-bar control height.
34
+ it('should size the page selector trigger to the 32px top-bar control height', () => {
35
+ expect(css('app-page-selector.css')).toMatch(/\.mint-page-selector__trigger\s*\{[^}]*height:\s*2rem;/)
36
+ })
37
+
38
+ // A content-box menu measured 310px, and the platform's <a> rows (100% + padding) overflowed it.
39
+ it.each(['menu', 'item'])('should size the page selector %s as border-box', (part) => {
40
+ expect(css('app-page-selector.css')).toMatch(new RegExp(`\\.mint-page-selector__${part}\\s*\\{[^}]*box-sizing:\\s*border-box;`))
35
41
  })
36
42
 
37
43
  // Shrinking the tile beside the identity also shrank structured icons inside their solid chip, drawing a ring.
@@ -44,17 +50,21 @@ describe('menus and app shell styles', () => {
44
50
  expect(css('app-top-bar.css')).toMatch(/\.mint-topbar__identity > \*\s*\{[^}]*max-width:\s*100%;[^}]*max-height:\s*100%;/)
45
51
  })
46
52
 
47
- // 15px x 1.15 + 12px x 1.2 = 31.7px, so the title group fits the 32px control row.
48
- it('should fit the top-bar title and subtitle inside the control row (5a)', () => {
49
- expect(css('app-top-bar.css')).toMatch(/\.mint-topbar-title\s*\{[^}]*line-height:\s*1\.15;/)
53
+ // 18px name + 15px subtitle lines, spread over the 36px identity tile height (top and bottom aligned).
54
+ it('should fit the top-bar title and subtitle to the 36px identity tile (5a)', () => {
55
+ const topBar = css('app-top-bar.css')
56
+ expect(topBar).toMatch(/\.mint-topbar-title-group\s*\{[^}]*justify-content:\s*space-between;[^}]*height:\s*2\.25rem;/)
57
+ expect(topBar).toMatch(/\.mint-topbar-title\s*\{[^}]*font-size:\s*0\.9375rem;[^}]*line-height:\s*1\.125rem;/)
58
+ expect(topBar).toMatch(/\.mint-topbar-subtitle\s*\{[^}]*font-size:\s*0\.75rem;[^}]*line-height:\s*0\.9375rem;/)
50
59
  })
51
60
 
52
- it('should size the top-bar experiment trigger to the 32px control height (5a)', () => {
53
- expect(css('experiment-popover.css')).toMatch(/\.mint-experiment-popover__trigger\s*\{[^}]*height:\s*var\(--form-height-sm\);/)
61
+ // Design v2: the joined [trigger | Save] group is 30px inside its 1px border.
62
+ it('should size the top-bar experiment trigger to the 30px v2 control height (5a)', () => {
63
+ expect(css('experiment-popover.css')).toMatch(/\.mint-experiment-popover__trigger\s*\{[^}]*height:\s*30px;/)
54
64
  })
55
65
 
56
- it('should size the top-bar experiment save trigger to the 32px control height (5a)', () => {
57
- expect(css('experiment-popover.css')).toMatch(/\.mint-experiment-popover__save-trigger\s*\{[^}]*height:\s*var\(--form-height-sm\);/)
66
+ it('should size the top-bar experiment save button to the 30px v2 control height (5a)', () => {
67
+ expect(css('experiment-popover.css')).toMatch(/\.mint-experiment-popover__save\s*\{[^}]*height:\s*30px;/)
58
68
  })
59
69
 
60
70
  it('should give settings number rows a 120px control (5d)', () => {
@@ -0,0 +1,44 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { dirname, join, resolve } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { describe, expect, it } from 'vitest'
5
+ import { colorPalettes } from '../../stores/settings'
6
+
7
+ const here = dirname(fileURLToPath(import.meta.url))
8
+ const variablesCss = readFileSync(join(resolve(here, '../..'), 'styles/variables.css'), 'utf-8')
9
+
10
+ const ACCENT_TOKENS = [
11
+ '--color-primary',
12
+ '--color-primary-hover',
13
+ '--color-primary-pressed',
14
+ '--color-primary-soft',
15
+ '--color-primary-border',
16
+ ]
17
+ const STATUS_TOKENS = ['--mint-success', '--mint-error', '--mint-warning', '--mint-warning-text']
18
+
19
+ /** Declarations of every rule whose selector list has a selector that ends in `[data-palette="<key>"]`. */
20
+ function paletteDeclarations(key: string, dark: boolean): string {
21
+ const attr = `[data-palette="${key}"]`
22
+ return [...variablesCss.matchAll(/([^{}]+)\{([^}]*)\}/g)]
23
+ .filter(([, selector]) => selector.split(',').some((s) => {
24
+ const one = s.trim()
25
+ return one.endsWith(attr) && one.startsWith('html.dark') === dark
26
+ }))
27
+ .map(([, , body]) => body)
28
+ .join('\n')
29
+ }
30
+
31
+ describe('accent palettes', () => {
32
+ it.each(Object.keys(colorPalettes).flatMap((key) => [[key, 'light'], [key, 'dark']]))(
33
+ 'should define every accent token for the %s palette in %s mode',
34
+ (key, theme) => {
35
+ const body = paletteDeclarations(key, theme === 'dark')
36
+ for (const token of ACCENT_TOKENS) expect(body, `${key} ${theme} ${token}`).toMatch(new RegExp(`${token}\\s*:`))
37
+ },
38
+ )
39
+
40
+ it('should give the colour-blind palette Okabe-Ito status colours', () => {
41
+ const body = paletteDeclarations('colorblind', false)
42
+ for (const token of STATUS_TOKENS) expect(body, token).toMatch(new RegExp(`${token}\\s*:`))
43
+ })
44
+ })
@@ -0,0 +1,104 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { dirname, join, resolve } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { describe, expect, it } from 'vitest'
5
+
6
+ // v2 design audit (2026-10-28) fixes in the SDK stylesheets.
7
+
8
+ const srcRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..')
9
+ const stylesDir = join(srcRoot, 'styles')
10
+
11
+ function css(path: string): string {
12
+ return readFileSync(join(stylesDir, path), 'utf-8').replace(/\/\*[\s\S]*?\*\//g, '')
13
+ }
14
+
15
+ function rule(text: string, selector: string): string {
16
+ const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
17
+ return new RegExp(`(?:^|\\})\\s*${escaped}\\s*\\{([^}]*)\\}`).exec(text)?.[1] ?? ''
18
+ }
19
+
20
+ const rem = (value: string): number => Number(value.replace('rem', '')) * 16
21
+
22
+ describe('v2 audit style rules', () => {
23
+ it('should stop the page bounce in a tab vertically and in a PWA on both axes', () => {
24
+ const variables = css('variables.css')
25
+ expect(rule(variables, 'html,\nbody')).toMatch(/overscroll-behavior-y:\s*none/)
26
+ expect(variables).toMatch(
27
+ /@media \(display-mode: standalone\)\s*\{\s*html,\s*body\s*\{\s*overscroll-behavior:\s*none;\s*\}/,
28
+ )
29
+ })
30
+
31
+ it('should keep the outermost cards at least 16px from the window edge', () => {
32
+ const inset = /--mint-page-inset:\s*([\d.]+rem)/.exec(css('variables.css'))?.[1] ?? '0rem'
33
+ expect(rem(inset)).toBeGreaterThanOrEqual(16)
34
+ expect(rule(css('components/app-layout.css'), '.mint-layout--floating')).toMatch(/padding:\s*var\(--mint-page-inset\)/)
35
+ const topBarCard = rule(css('components/app-top-bar.css'), '.mint-topbar--card')
36
+ expect(topBarCard).toMatch(/width:\s*calc\(100% - 2 \* var\(--mint-page-inset\)\)/)
37
+ expect(topBarCard).toMatch(/margin:\s*var\(--mint-page-inset\) var\(--mint-page-inset\) 0/)
38
+ })
39
+
40
+ it('should put the SDK utility classes in the utilities layer so host variants beat them', () => {
41
+ const vars = css('variables.css')
42
+ const order = vars.indexOf('@layer theme, base, components, utilities;')
43
+ const layer = vars.indexOf('@layer utilities {')
44
+ expect(order).toBeGreaterThan(-1)
45
+ expect(order).toBeLessThan(vars.indexOf('@layer base {'))
46
+ const utilities = vars.slice(layer, vars.indexOf('@media (prefers-reduced-motion'))
47
+ expect(layer).toBeGreaterThan(-1)
48
+ for (const selector of ['.flex-col {', '.text-text-muted {', '.truncate {', '.max-w-sm {']) {
49
+ expect(utilities).toContain(selector)
50
+ }
51
+ })
52
+
53
+ it('should drop the last DataFrame row rule over the !important cell border', () => {
54
+ const body = rule(css('components/dataframe.css'), '.mint-dataframe__row:last-child .mint-dataframe__td')
55
+ expect(body).toMatch(/border-bottom:\s*none\s*!important/)
56
+ })
57
+
58
+ it('should round the modal close button with --radius-md', () => {
59
+ expect(rule(css('components/modal.css'), '.mint-modal__close')).toMatch(/border-radius:\s*var\(--radius-md\)/)
60
+ })
61
+
62
+ it('should draw no capsule on the progress rail or the sheet grab hint', () => {
63
+ expect(css('components/progress-bar.css')).not.toMatch(/9999px/)
64
+ expect(rule(css('components/modal.css'), '.mint-modal__grab-hint')).not.toMatch(/9999px/)
65
+ })
66
+
67
+ it('should keep a 28px header control inside the section card header strip', () => {
68
+ const text = css('components/section-card.css')
69
+ const cases: Array<[string, number]> = [
70
+ ['.mint-section-card__header', 28],
71
+ ['[data-density="comfortable"] .mint-section-card__header', 32],
72
+ ]
73
+ for (const [selector, control] of cases) {
74
+ const body = rule(text, selector)
75
+ const minHeight = rem(/min-height:\s*([\d.]+rem)/.exec(body)?.[1] ?? '0rem')
76
+ const padTop = rem(/padding:\s*([\d.]+rem)/.exec(body)?.[1] ?? '0rem')
77
+ expect(control + 2 * padTop + 1, selector).toBeLessThanOrEqual(minHeight)
78
+ }
79
+ })
80
+
81
+ it('should end the settings pane without a closing row hairline and draw no box around the density preview', () => {
82
+ const text = css('components/settings-modal.css')
83
+ const last = rule(
84
+ text,
85
+ '.mint-settings-modal__group:last-of-type > .mint-settings-modal__group-grid > .mint-settings-modal__row:last-child',
86
+ )
87
+ expect(last).toMatch(/border-bottom:\s*none/)
88
+ expect(rule(text, '.mint-settings-modal__density-preview')).not.toMatch(/(^|;)\s*border(-radius)?:/)
89
+ })
90
+
91
+ it('should set the md number input text no smaller than the sm one', () => {
92
+ const text = css('components/number-input.css')
93
+ const size = (variant: string) =>
94
+ rem(/font-size:\s*([\d.]+rem)/.exec(rule(text, `.mint-number-input__input--${variant}`))?.[1] ?? '0rem')
95
+ expect(size('md')).toBeGreaterThan(size('sm'))
96
+ })
97
+
98
+ it('should give the generated JSON block a token radius and no frame', () => {
99
+ const sfc = readFileSync(join(srcRoot, 'components/GeneratedResultRenderer.vue'), 'utf-8')
100
+ const body = /\.mint-generated-result__json\s*\{([^}]*overflow:[^}]*)\}/.exec(sfc)?.[1] ?? ''
101
+ expect(body).toMatch(/border-radius:\s*var\(--radius-md\)/)
102
+ expect(body).not.toMatch(/(^|;)\s*border:/)
103
+ })
104
+ })
@@ -10,8 +10,8 @@ const tokens: Record<string, string> = {
10
10
  '--mint-sample-6': '#666666',
11
11
  '--mint-sample-7': '#777777',
12
12
  '--mint-sample-8': '#888888',
13
- '--font-sans': 'Fira Sans',
14
- '--font-mono': 'Fira Code',
13
+ '--font-sans': 'Geist',
14
+ '--font-mono': 'Geist Mono',
15
15
  '--text-primary': '#1E293B',
16
16
  '--text-secondary': '#64748B',
17
17
  '--border-light': '#F3F4F6',
@@ -35,11 +35,11 @@ describe('mintPlotlyTemplate', () => {
35
35
  })
36
36
 
37
37
  it('should set 12px sans body text in the primary text color when tokens are defined', () => {
38
- expect(mintPlotlyTemplate().layout?.font).toEqual({ family: 'Fira Sans', size: 12, color: '#1E293B' })
38
+ expect(mintPlotlyTemplate().layout?.font).toEqual({ family: 'Geist', size: 12, color: '#1E293B' })
39
39
  })
40
40
 
41
41
  it('should set 11px mono ticks in the secondary text color when tokens are defined', () => {
42
- expect(mintPlotlyTemplate().layout?.yaxis?.tickfont).toEqual({ family: 'Fira Code', size: 11, color: '#64748B' })
42
+ expect(mintPlotlyTemplate().layout?.yaxis?.tickfont).toEqual({ family: 'Geist Mono', size: 11, color: '#64748B' })
43
43
  })
44
44
 
45
45
  it('should draw the grid with --border-light when tokens are defined', () => {
@@ -1,6 +1,7 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { afterEach, describe, expect, it, vi } from 'vitest'
2
2
  import {
3
3
  findNearestTimeSlotIndex,
4
+ formatRelativeTime,
4
5
  formatTimeSlot,
5
6
  fromMinutes,
6
7
  generateTimeSlots,
@@ -25,4 +26,20 @@ describe('time', () => {
25
26
  expect(findNearestTimeSlotIndex('08:00', [])).toBe(-1)
26
27
  })
27
28
 
29
+ describe('formatRelativeTime', () => {
30
+ afterEach(() => {
31
+ vi.useRealTimers()
32
+ })
33
+
34
+ it.each([
35
+ [{ weeks: true }, '2 weeks ago'],
36
+ [{}, new Date('2026-10-14T12:00:00Z').toLocaleDateString()],
37
+ ])('should format a 14-day-old time as a week count only when weeks is on (%o)', (options, expected) => {
38
+ vi.useFakeTimers({ toFake: ['Date'] })
39
+ vi.setSystemTime(new Date('2026-10-28T12:00:00Z'))
40
+
41
+ expect(formatRelativeTime('2026-10-14T12:00:00Z', options)).toBe(expected)
42
+ })
43
+ })
44
+
28
45
  })
@@ -1,9 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  /** Compact overflow actions with an icon-only BaseButton trigger and a viewport-safe teleported menu. */
3
- import { computePosition, flip, offset, shift } from '@floating-ui/dom'
4
3
  import { Check, Ellipsis } from 'lucide-vue-next'
5
- import { nextTick, onMounted, ref, watch } from 'vue'
4
+ import { nextTick, onMounted, ref } from 'vue'
6
5
  import { useDropdownState } from '../composables/useDropdownState'
6
+ import { useFloatingPanel } from '../composables/useFloatingPanel'
7
7
  import { useMenuKeyboard } from '../composables/useMenuKeyboard'
8
8
  import type { ActionMenuItem } from '../types/components'
9
9
  import BaseButton from './BaseButton.vue'
@@ -30,19 +30,15 @@ defineSlots<{
30
30
  }>()
31
31
 
32
32
  const panelRef = ref<HTMLElement | null>(null)
33
- const panelStyle = ref<Record<string, string>>({ visibility: 'hidden' })
34
33
 
35
- // Disclosure state (outside-click, Escape, scroll/resize closing, focus return)
36
- // comes from the shared composable; only floating-ui placement stays local.
34
+ // Disclosure state (outside-click, Escape, focus return) and placement come from
35
+ // the shared composables; the open menu follows its trigger on scroll.
37
36
  const { isOpen, rootRef, triggerRef, open, close } = useDropdownState({
38
- closeOnScroll: true,
39
37
  insideRefs: [panelRef],
40
- onOpen: () => {
41
- panelStyle.value = { visibility: 'hidden' }
42
- emit('open')
43
- },
38
+ onOpen: () => emit('open'),
44
39
  onClose: () => emit('close'),
45
40
  })
41
+ const { style: panelStyle } = useFloatingPanel(triggerRef, panelRef, isOpen, { placement: 'bottom-end' })
46
42
 
47
43
  // BaseButton owns the actual <button>, so the trigger element is resolved from
48
44
  // the wrapper once mounted rather than bound with a template ref.
@@ -57,27 +53,6 @@ function menuItems(): HTMLElement[] {
57
53
  )
58
54
  }
59
55
 
60
- async function positionPanel() {
61
- const trigger = triggerRef.value
62
- const panel = panelRef.value
63
- if (!trigger || !panel) return
64
-
65
- const position = await computePosition(trigger, panel, {
66
- placement: 'bottom-end',
67
- strategy: 'fixed',
68
- middleware: [
69
- offset(4),
70
- flip(),
71
- shift({ padding: 8 }),
72
- ],
73
- })
74
- panelStyle.value = {
75
- left: `${position.x}px`,
76
- top: `${position.y}px`,
77
- visibility: 'visible',
78
- }
79
- }
80
-
81
56
  /** `focusFirst` is false when the keyboard composable owns the initial focus. */
82
57
  function openMenu(focusFirst = true) {
83
58
  if (props.disabled || isOpen.value) return
@@ -105,12 +80,6 @@ function handleSelect(item: ActionMenuItem) {
105
80
  emit('select', item)
106
81
  close()
107
82
  }
108
-
109
- watch(isOpen, async (opened) => {
110
- if (!opened) return
111
- await nextTick()
112
- await positionPanel()
113
- })
114
83
  </script>
115
84
 
116
85
  <template>
@@ -12,6 +12,13 @@ import AdductText from './AdductText.vue'
12
12
  </div>
13
13
  </Variant>
14
14
 
15
+ <Variant title="Badges">
16
+ <div style="display: flex; align-items: center; gap: 0.5rem; padding: 2rem;">
17
+ <AdductText v-for="v in ['[M+H]+', '[M-H]-', '[M+Na]+', '[M-H2O+H]+', '[M+2H]2+']" :key="v" :value="v" variant="badge" />
18
+ <AdductText value="[M+H]+" variant="badge" font="mono" />
19
+ </div>
20
+ </Variant>
21
+
15
22
  <Variant title="Tones and inherited size">
16
23
  <div style="display: flex; flex-direction: column; gap: 0.75rem; padding: 2rem; max-width: 600px; color: var(--text-primary); font-size: 0.875rem;">
17
24
  <div style="display: flex; align-items: center; gap: 1rem;">
@@ -17,7 +17,7 @@ const props = withDefaults(defineProps<Props>(), {
17
17
  variant: 'inline',
18
18
  tone: 'default',
19
19
  size: 'xs',
20
- font: 'mono',
20
+ font: 'sans',
21
21
  })
22
22
 
23
23
  const value = computed(() => props.value?.trim() ?? '')
@@ -35,7 +35,7 @@ function handleAction(label: string) {
35
35
  </AlertBox>
36
36
  <p
37
37
  v-if="actionFired"
38
- style="margin-top: 0.75rem; font-size: 0.8rem; color: var(--mint-success, #10B981); font-family: 'Fira Code', monospace;"
38
+ style="margin-top: 0.75rem; font-size: 0.8rem; color: var(--mint-success, #10B981); font-family: var(--font-mono);"
39
39
  >
40
40
  Action fired: "{{ actionFired }}"
41
41
  </p>
@@ -111,7 +111,7 @@ function handleAction(label: string) {
111
111
 
112
112
  <p
113
113
  v-if="actionFired"
114
- style="font-size: 0.8rem; color: var(--mint-success, #10B981); font-family: 'Fira Code', monospace;"
114
+ style="font-size: 0.8rem; color: var(--mint-success, #10B981); font-family: var(--font-mono);"
115
115
  >
116
116
  Action fired: "{{ actionFired }}"
117
117
  </p>
@@ -31,22 +31,24 @@ const hasActions = () => !!props.actionLabel || !!slots.actions
31
31
  :class="`mint-alert--${props.tone}`"
32
32
  role="alert"
33
33
  >
34
- <!-- Lucide circle-check -->
35
- <svg v-if="props.tone === 'success'" class="mint-alert__icon mint-alert__icon--success" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
36
- <circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" />
37
- </svg>
38
- <!-- Lucide circle-x -->
39
- <svg v-else-if="props.tone === 'error'" class="mint-alert__icon mint-alert__icon--error" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
40
- <circle cx="12" cy="12" r="10" /><path d="m15 9-6 6" /><path d="m9 9 6 6" />
41
- </svg>
42
- <!-- Lucide triangle-alert -->
43
- <svg v-else-if="props.tone === 'warning'" class="mint-alert__icon mint-alert__icon--warning" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
44
- <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /><path d="M12 9v4" /><path d="M12 17h.01" />
45
- </svg>
46
- <!-- Lucide info -->
47
- <svg v-else :class="['mint-alert__icon', `mint-alert__icon--${props.tone}`]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
48
- <circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" />
49
- </svg>
34
+ <span :class="['mint-alert__icon', `mint-alert__icon--${props.tone}`]">
35
+ <!-- Lucide circle-check -->
36
+ <svg v-if="props.tone === 'success'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round">
37
+ <circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" />
38
+ </svg>
39
+ <!-- Lucide circle-x -->
40
+ <svg v-else-if="props.tone === 'error'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round">
41
+ <circle cx="12" cy="12" r="10" /><path d="m15 9-6 6" /><path d="m9 9 6 6" />
42
+ </svg>
43
+ <!-- Lucide triangle-alert -->
44
+ <svg v-else-if="props.tone === 'warning'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round">
45
+ <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /><path d="M12 9v4" /><path d="M12 17h.01" />
46
+ </svg>
47
+ <!-- Lucide info -->
48
+ <svg v-else viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round">
49
+ <circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" />
50
+ </svg>
51
+ </span>
50
52
 
51
53
  <div class="mint-alert__content">
52
54
  <h4 v-if="props.title" class="mint-alert__title">