@morscherlab/mint-sdk 1.1.25 → 1.2.0-beta.10

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 (403) hide show
  1. package/README.md +44 -12
  2. package/dist/{ExperimentPopover-tLwJ7XF4.js → ExperimentPopover-oL0fg_A0.js} +6 -4
  3. package/dist/ExperimentPopover-oL0fg_A0.js.map +1 -0
  4. package/dist/{ExperimentSelectorModal-B6P2msT4.js → ExperimentSelectorModal-BeEOomw_.js} +5 -5
  5. package/dist/ExperimentSelectorModal-BeEOomw_.js.map +1 -0
  6. package/dist/{SettingsModal-BW3Ar-uy.js → SettingsModal-Dphh3RrI.js} +5 -8
  7. package/dist/SettingsModal-Dphh3RrI.js.map +1 -0
  8. package/dist/{EmptyState-p90quq8Z.js → Skeleton-DxtwJ5gt.js} +111 -93
  9. package/dist/Skeleton-DxtwJ5gt.js.map +1 -0
  10. package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
  11. package/dist/__tests__/components/PluginWorkspaceView.internal.test.d.ts +1 -0
  12. package/dist/__tests__/components/SecretInput.test.d.ts +1 -0
  13. package/dist/__tests__/components/WellEditPopupInternal.test.d.ts +1 -0
  14. package/dist/__tests__/composables/useEventStream.test.d.ts +1 -0
  15. package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
  16. package/dist/__tests__/styles/cssTokens.test.d.ts +1 -0
  17. package/dist/components/ActionMenu.vue.d.ts +1 -1
  18. package/dist/components/AppAvatarMenu.vue.d.ts +4 -0
  19. package/dist/components/AppPluginSwitcher.vue.d.ts +2 -0
  20. package/dist/components/AppSidebar.vue.d.ts +1 -1
  21. package/dist/components/AuditTrail.vue.d.ts +13 -0
  22. package/dist/components/BatchProgressList.vue.d.ts +17 -0
  23. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
  24. package/dist/components/DataFrame.vue.d.ts +14 -2
  25. package/dist/components/DatePicker.vue.d.ts +1 -1
  26. package/dist/components/DateTimePicker.vue.d.ts +2 -0
  27. package/dist/components/DropdownButton.vue.d.ts +2 -0
  28. package/dist/components/ExperimentDataViewer.vue.d.ts +26 -1
  29. package/dist/components/ExperimentPopover.vue.d.ts +1 -0
  30. package/dist/components/ExperimentTimeline.vue.d.ts +29 -1
  31. package/dist/components/FitPanel.vue.d.ts +1 -1
  32. package/dist/components/LcmsSequenceTable.vue.d.ts +29 -1
  33. package/dist/components/PluginWorkspaceView.props.d.ts +29 -1
  34. package/dist/components/PluginWorkspaceView.vue.d.ts +5 -5
  35. package/dist/components/ProgressBar.vue.d.ts +1 -1
  36. package/dist/components/ProtocolStepEditor.vue.d.ts +1 -0
  37. package/dist/components/ReagentList.vue.d.ts +29 -1
  38. package/dist/components/SampleHierarchyTree.vue.d.ts +29 -1
  39. package/dist/components/SampleSelector.drag.d.ts +13 -0
  40. package/dist/components/SampleSelector.tree.d.ts +2 -0
  41. package/dist/components/SampleSelector.vue.d.ts +34 -1
  42. package/dist/components/ScheduleCalendar.vue.d.ts +20 -0
  43. package/dist/components/SecretInput.vue.d.ts +28 -0
  44. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -1
  45. package/dist/components/TimePicker.vue.d.ts +1 -0
  46. package/dist/components/WellPlate.geometry.d.ts +15 -0
  47. package/dist/components/WellPlate.interaction.d.ts +13 -0
  48. package/dist/components/WellPlate.vue.d.ts +29 -0
  49. package/dist/components/index.d.ts +9 -0
  50. package/dist/components/index.js +8 -8
  51. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -0
  52. package/dist/components/internal/DataStateInternal.vue.d.ts +43 -0
  53. package/dist/components/internal/WellEditPopupInternal.vue.d.ts +3 -1
  54. package/dist/components/internal/WorkspaceControlsInternal.vue.d.ts +23 -0
  55. package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +70 -0
  56. package/dist/components/internal/WorkspaceSidebarInternal.vue.d.ts +43 -0
  57. package/dist/{components-DQt0tcpY.js → components-CgAUzF8F.js} +2491 -2235
  58. package/dist/components-CgAUzF8F.js.map +1 -0
  59. package/dist/composables/controlSchemaTypes.d.ts +34 -0
  60. package/dist/composables/flattenWorkspaceBindings.d.ts +61 -0
  61. package/dist/composables/index.d.ts +8 -2
  62. package/dist/composables/index.js +10 -10
  63. package/dist/composables/pluginWorkspaceModel.d.ts +164 -0
  64. package/dist/composables/useAsync.d.ts +5 -1
  65. package/dist/composables/useBioTemplateWorkspace.d.ts +4 -20
  66. package/dist/composables/useCommandHistory.d.ts +5 -1
  67. package/dist/composables/useDropdownState.d.ts +18 -2
  68. package/dist/composables/useEventStream.d.ts +52 -0
  69. package/dist/composables/useFocusTrap.d.ts +21 -0
  70. package/dist/composables/useJobsStatusTray.d.ts +1 -1
  71. package/dist/composables/useListReorder.d.ts +90 -0
  72. package/dist/composables/useMenuKeyboard.d.ts +29 -0
  73. package/dist/composables/useOptimisticMutation.d.ts +5 -1
  74. package/dist/composables/usePluginClient.d.ts +4 -17
  75. package/dist/composables/usePluginConfig.d.ts +3 -1
  76. package/dist/composables/usePluginWorkspace.d.ts +51 -0
  77. package/dist/composables/useRequestSyncState.d.ts +42 -2
  78. package/dist/composables/useResourceCrud.d.ts +5 -1
  79. package/dist/composables/useRovingFocus.d.ts +24 -0
  80. package/dist/composables/useScheduleDrag.d.ts +1 -0
  81. package/dist/composables/useWellPainting.d.ts +5 -1
  82. package/dist/composables/useWellPlateAdapter.d.ts +5 -1
  83. package/dist/composables/useWellPlateValidation.d.ts +5 -1
  84. package/dist/composables/workspaceRecipes.d.ts +188 -0
  85. package/dist/{composables-CkXDe7nm.js → composables-B_pia3hO.js} +105 -254
  86. package/dist/composables-B_pia3hO.js.map +1 -0
  87. package/dist/{useApi-CDBq6z23.js → errors-_W0-LbA6.js} +102 -3
  88. package/dist/errors-_W0-LbA6.js.map +1 -0
  89. package/dist/{experiment-Y9Iz7KYi.js → experiment-Cwl9AYxw.js} +3 -8
  90. package/dist/experiment-Cwl9AYxw.js.map +1 -0
  91. package/dist/{experiment-utils-CLnzPPq5.js → experiment-utils-DV3DrvE_.js} +70 -51
  92. package/dist/experiment-utils-DV3DrvE_.js.map +1 -0
  93. package/dist/index.d.ts +1 -0
  94. package/dist/index.js +15 -15
  95. package/dist/install.js +2 -7
  96. package/dist/install.js.map +1 -1
  97. package/dist/permissions.d.ts +42 -1
  98. package/dist/permissions.js +20 -10
  99. package/dist/permissions.js.map +1 -1
  100. package/dist/stores/index.d.ts +1 -1
  101. package/dist/stores/index.js +3 -3
  102. package/dist/stores/settings.d.ts +31 -1
  103. package/dist/styles.css +11247 -29501
  104. package/dist/templates/builderReadUtils.d.ts +1 -1
  105. package/dist/templates/index.js +2 -4
  106. package/dist/{templates-CovvRZ-9.js → templates-Qq113pyZ.js} +303 -106
  107. package/dist/templates-Qq113pyZ.js.map +1 -0
  108. package/dist/types/form-builder.d.ts +1 -1
  109. package/dist/{useControlSchema-BhoJzWzx.js → useControlSchema-aMZlb4Sc.js} +684 -1216
  110. package/dist/useControlSchema-aMZlb4Sc.js.map +1 -0
  111. package/dist/{useDropdownState-DSb3BC3N.js → useDropdownState-DitCtrTT.js} +22 -6
  112. package/dist/useDropdownState-DitCtrTT.js.map +1 -0
  113. package/dist/{useExperimentSelector-D0pjj9WH.js → useExperimentSelector-I8HC8hUu.js} +4 -4
  114. package/dist/{useExperimentSelector-D0pjj9WH.js.map → useExperimentSelector-I8HC8hUu.js.map} +1 -1
  115. package/dist/{useFormBuilder-DiEcDis2.js → useFormBuilder-TCxNnqeb.js} +2 -2
  116. package/dist/{useFormBuilder-DiEcDis2.js.map → useFormBuilder-TCxNnqeb.js.map} +1 -1
  117. package/dist/{useProtocolTemplates-DHwSveE6.js → useProtocolTemplates-BmX_iVBR.js} +660 -197
  118. package/dist/useProtocolTemplates-BmX_iVBR.js.map +1 -0
  119. package/dist/utils/color.d.ts +13 -0
  120. package/dist/utils/errors.d.ts +9 -0
  121. package/dist/utils/pluginSecrets.d.ts +53 -0
  122. package/dist/utils/records.d.ts +4 -0
  123. package/package.json +1 -1
  124. package/src/__tests__/components/ActionMenu.test.ts +94 -0
  125. package/src/__tests__/components/AppAvatarMenu.test.ts +123 -0
  126. package/src/__tests__/components/BaseTabs.test.ts +67 -0
  127. package/src/__tests__/components/DataFrame.test.ts +183 -11
  128. package/src/__tests__/components/DataStates.test.ts +176 -0
  129. package/src/__tests__/components/DatePicker.test.ts +76 -0
  130. package/src/__tests__/components/DateTimePicker.test.ts +36 -0
  131. package/src/__tests__/components/DropdownButton.test.ts +134 -0
  132. package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
  133. package/src/__tests__/components/GeneratedPluginView.test.ts +0 -5
  134. package/src/__tests__/components/GroupAssigner.test.ts +53 -0
  135. package/src/__tests__/components/JobsStatusTray.test.ts +20 -18
  136. package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
  137. package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +230 -0
  138. package/src/__tests__/components/RackEditor.test.ts +35 -0
  139. package/src/__tests__/components/ReagentList.test.ts +64 -2
  140. package/src/__tests__/components/RetiredStyles.test.ts +31 -0
  141. package/src/__tests__/components/SampleSelector.drag.test.ts +51 -1
  142. package/src/__tests__/components/ScheduleCalendar.test.ts +63 -0
  143. package/src/__tests__/components/SecretInput.test.ts +87 -0
  144. package/src/__tests__/components/SegmentedControl.test.ts +88 -0
  145. package/src/__tests__/components/TimePicker.test.ts +61 -0
  146. package/src/__tests__/components/WellEditPopupInternal.test.ts +108 -0
  147. package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
  148. package/src/__tests__/composables/useAuth.test.ts +25 -0
  149. package/src/__tests__/composables/useDropdownState.test.ts +58 -1
  150. package/src/__tests__/composables/useEventStream.test.ts +140 -0
  151. package/src/__tests__/composables/useGeneratedAnalysis.test.ts +103 -0
  152. package/src/__tests__/composables/useJobsStatusTray.test.ts +25 -161
  153. package/src/__tests__/composables/useListReorder.test.ts +145 -0
  154. package/src/__tests__/composables/usePluginClient.test.ts +148 -13
  155. package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
  156. package/src/__tests__/docs/frontendDocsCatalog.test.ts +55 -2
  157. package/src/__tests__/stores/settings.test.ts +71 -1
  158. package/src/__tests__/styles/cssTokens.test.ts +131 -0
  159. package/src/__tests__/utils/permissions.test.ts +68 -0
  160. package/src/components/ActionMenu.vue +45 -88
  161. package/src/components/AlertBox.vue +0 -4
  162. package/src/components/AppAvatarMenu.vue +26 -7
  163. package/src/components/AppLayout.vue +0 -4
  164. package/src/components/AppPluginSwitcher.story.vue +87 -0
  165. package/src/components/AppPluginSwitcher.vue +2 -5
  166. package/src/components/AppSidebar.vue +0 -4
  167. package/src/components/AppToastContainer.vue +6 -8
  168. package/src/components/AppTopBar.vue +0 -4
  169. package/src/components/ArtifactSaveDialog.vue +0 -4
  170. package/src/components/ArtifactSelectorModal.vue +0 -4
  171. package/src/components/AuditTrail.story.vue +18 -0
  172. package/src/components/AuditTrail.vue +27 -7
  173. package/src/components/AutoGroupModal.vue +0 -4
  174. package/src/components/Avatar.vue +0 -4
  175. package/src/components/BaseButton.vue +0 -4
  176. package/src/components/BaseCheckbox.vue +0 -4
  177. package/src/components/BaseInput.vue +0 -4
  178. package/src/components/BaseModal.vue +8 -60
  179. package/src/components/BasePill.vue +0 -4
  180. package/src/components/BaseRadioGroup.vue +0 -4
  181. package/src/components/BaseSelect.vue +0 -4
  182. package/src/components/BaseSlider.vue +0 -4
  183. package/src/components/BaseTabs.vue +26 -5
  184. package/src/components/BaseTextarea.vue +0 -4
  185. package/src/components/BaseToggle.vue +0 -4
  186. package/src/components/BatchProgressList.story.vue +18 -0
  187. package/src/components/BatchProgressList.vue +32 -4
  188. package/src/components/BioTemplateExperimentWorkspaceView.vue +10 -21
  189. package/src/components/BioTemplatePackWorkspaceView.vue +11 -15
  190. package/src/components/BioTemplatePresetWorkspaceView.vue +8 -13
  191. package/src/components/Breadcrumb.vue +0 -4
  192. package/src/components/Calendar.vue +0 -4
  193. package/src/components/ChartContainer.story.vue +1 -1
  194. package/src/components/ChartContainer.vue +0 -4
  195. package/src/components/ChemicalFormula.vue +0 -4
  196. package/src/components/CollapsibleCard.vue +0 -4
  197. package/src/components/ColorSlider.vue +0 -4
  198. package/src/components/ComponentBindingRenderer.vue +1 -3
  199. package/src/components/ConcentrationInput.vue +0 -4
  200. package/src/components/ConfirmDialog.vue +0 -4
  201. package/src/components/ControlWorkspaceView.vue +11 -22
  202. package/src/components/DataFrame.story.vue +14 -0
  203. package/src/components/DataFrame.vue +97 -23
  204. package/src/components/DatePicker.vue +73 -27
  205. package/src/components/DateTimePicker.vue +45 -17
  206. package/src/components/Divider.vue +0 -4
  207. package/src/components/DoseCalculator.vue +0 -4
  208. package/src/components/DoseDesignWorkspaceView.vue +6 -25
  209. package/src/components/DropdownButton.vue +24 -6
  210. package/src/components/EmptyState.vue +0 -4
  211. package/src/components/ExperimentCodeBadge.vue +0 -4
  212. package/src/components/ExperimentDataViewer.story.vue +3 -0
  213. package/src/components/ExperimentDataViewer.vue +33 -13
  214. package/src/components/ExperimentPopover.vue +2 -5
  215. package/src/components/ExperimentSelectorModal.vue +0 -4
  216. package/src/components/ExperimentTimeline.story.vue +18 -0
  217. package/src/components/ExperimentTimeline.vue +81 -75
  218. package/src/components/FileUploader.vue +0 -4
  219. package/src/components/FitPanel.vue +0 -4
  220. package/src/components/FormActions.story.vue +96 -0
  221. package/src/components/FormActions.vue +0 -4
  222. package/src/components/FormBuilder.vue +0 -4
  223. package/src/components/FormField.vue +0 -4
  224. package/src/components/FormulaInput.vue +0 -4
  225. package/src/components/GeneratedPathInput.story.vue +68 -0
  226. package/src/components/GeneratedPlotlyResult.story.vue +96 -0
  227. package/src/components/GeneratedPlotlyResult.vue +3 -3
  228. package/src/components/GeneratedPluginView.story.vue +145 -0
  229. package/src/components/GeneratedPluginView.vue +10 -10
  230. package/src/components/GeneratedResultRenderer.story.vue +117 -0
  231. package/src/components/GeneratedResultRenderer.vue +4 -4
  232. package/src/components/GroupAssigner.vue +67 -5
  233. package/src/components/IconButton.vue +0 -4
  234. package/src/components/InstrumentAlertLog.vue +0 -4
  235. package/src/components/InstrumentStateBadge.vue +0 -4
  236. package/src/components/InstrumentStatusCard.vue +0 -4
  237. package/src/components/JobsStatusTray.story.vue +6 -10
  238. package/src/components/JobsStatusTray.vue +0 -4
  239. package/src/components/LcmsSequenceTable.vue +81 -50
  240. package/src/components/LoadingSpinner.vue +0 -4
  241. package/src/components/MobileSupportGate.vue +0 -4
  242. package/src/components/MoleculeInput.vue +0 -4
  243. package/src/components/MultiSelect.vue +0 -4
  244. package/src/components/NumberInput.vue +0 -4
  245. package/src/components/PlateMapEditor.vue +3 -14
  246. package/src/components/PluginIcon.vue +0 -4
  247. package/src/components/PluginWorkspaceView.props.ts +38 -0
  248. package/src/components/PluginWorkspaceView.vue +53 -224
  249. package/src/components/ProgressBar.vue +0 -4
  250. package/src/components/ProtocolStepEditor.vue +2 -4
  251. package/src/components/RackEditor.vue +51 -70
  252. package/src/components/ReagentEditor.vue +0 -4
  253. package/src/components/ReagentList.story.vue +18 -0
  254. package/src/components/ReagentList.vue +127 -82
  255. package/src/components/ResourceCard.vue +0 -4
  256. package/src/components/RuntimeUpdateNotice.story.vue +49 -0
  257. package/src/components/RuntimeUpdateNotice.vue +4 -4
  258. package/src/components/SampleHierarchyTree.story.vue +18 -0
  259. package/src/components/SampleHierarchyTree.vue +32 -9
  260. package/src/components/SampleLegend.vue +2 -8
  261. package/src/components/SampleSelector.drag.ts +51 -0
  262. package/src/components/SampleSelector.story.vue +6 -0
  263. package/src/components/SampleSelector.tree.ts +2 -0
  264. package/src/components/SampleSelector.vue +49 -10
  265. package/src/components/SampleSelectorGroupNode.vue +19 -0
  266. package/src/components/ScheduleCalendar.story.vue +6 -0
  267. package/src/components/ScheduleCalendar.vue +82 -7
  268. package/src/components/ScientificNumber.vue +0 -4
  269. package/src/components/SecretInput.story.vue +79 -0
  270. package/src/components/SecretInput.vue +150 -0
  271. package/src/components/SectionCard.vue +0 -4
  272. package/src/components/SegmentedControl.vue +48 -5
  273. package/src/components/SequenceInput.vue +0 -4
  274. package/src/components/SequenceProgressBar.story.vue +110 -0
  275. package/src/components/SequenceProgressBar.vue +0 -4
  276. package/src/components/SettingsModal.vue +0 -4
  277. package/src/components/Skeleton.vue +0 -4
  278. package/src/components/SmartGroupFieldRecipe.vue +8 -6
  279. package/src/components/SmartGroupManual.vue +0 -4
  280. package/src/components/StatusIndicator.vue +0 -4
  281. package/src/components/StepWizard.vue +0 -4
  282. package/src/components/TagsInput.vue +4 -6
  283. package/src/components/ThemeToggle.vue +0 -4
  284. package/src/components/TimePicker.vue +36 -7
  285. package/src/components/TimeRangeInput.vue +0 -4
  286. package/src/components/Tooltip.story.vue +3 -3
  287. package/src/components/Tooltip.vue +0 -4
  288. package/src/components/UnitInput.vue +0 -4
  289. package/src/components/WellPlate.geometry.ts +60 -1
  290. package/src/components/WellPlate.interaction.ts +44 -0
  291. package/src/components/WellPlate.story.vue +8 -0
  292. package/src/components/WellPlate.vue +53 -7
  293. package/src/components/index.ts +9 -0
  294. package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -5
  295. package/src/components/internal/AppTopBarPillNavInternal.vue +0 -4
  296. package/src/components/internal/DataStateInternal.vue +74 -0
  297. package/src/components/internal/FormSectionRenderer.vue +0 -4
  298. package/src/components/internal/WellEditPopupInternal.vue +20 -5
  299. package/src/components/internal/WorkspaceControlsInternal.vue +20 -0
  300. package/src/components/internal/WorkspaceShellInternal.vue +125 -0
  301. package/src/components/internal/WorkspaceSidebarInternal.vue +82 -0
  302. package/src/composables/controlSchemaTypes.ts +34 -0
  303. package/src/composables/experimentDesignData.ts +1 -3
  304. package/src/composables/flattenWorkspaceBindings.ts +104 -0
  305. package/src/composables/formBuilderRegistry.ts +5 -0
  306. package/src/composables/index.ts +41 -5
  307. package/src/composables/pluginWorkspaceModel.ts +182 -0
  308. package/src/composables/useAsync.ts +5 -1
  309. package/src/composables/useBioTemplatePackWorkspace.ts +15 -9
  310. package/src/composables/useBioTemplatePresetWorkspace.ts +14 -15
  311. package/src/composables/useBioTemplateWorkspace.ts +18 -43
  312. package/src/composables/useCommandHistory.ts +5 -1
  313. package/src/composables/useDropdownState.ts +35 -5
  314. package/src/composables/useEventStream.ts +326 -0
  315. package/src/composables/useFileImport.ts +2 -7
  316. package/src/composables/useFocusTrap.ts +91 -0
  317. package/src/composables/useGeneratedAnalysis.ts +46 -103
  318. package/src/composables/useJobsStatusTray.ts +1 -1
  319. package/src/composables/useListReorder.story.vue +143 -0
  320. package/src/composables/useListReorder.ts +242 -0
  321. package/src/composables/useMenuKeyboard.ts +90 -0
  322. package/src/composables/useOptimisticMutation.ts +6 -16
  323. package/src/composables/usePluginClient.ts +91 -252
  324. package/src/composables/usePluginConfig.ts +3 -1
  325. package/src/composables/usePluginWorkspace.ts +208 -0
  326. package/src/composables/useRackEditor.ts +2 -11
  327. package/src/composables/useRequestSyncState.ts +98 -28
  328. package/src/composables/useResourceCrud.ts +6 -16
  329. package/src/composables/useRovingFocus.ts +66 -0
  330. package/src/composables/useScheduleDrag.ts +37 -0
  331. package/src/composables/useWellPainting.ts +10 -30
  332. package/src/composables/useWellPlateAdapter.ts +5 -1
  333. package/src/composables/useWellPlateEditor.ts +1 -11
  334. package/src/composables/useWellPlateValidation.ts +5 -1
  335. package/src/composables/workspaceRecipes.ts +267 -0
  336. package/src/index.ts +16 -0
  337. package/src/permissions.ts +48 -9
  338. package/src/stores/experiment.ts +2 -7
  339. package/src/stores/index.ts +9 -1
  340. package/src/stores/settings.ts +93 -1
  341. package/src/styles/components/audit-trail.css +5 -5
  342. package/src/styles/components/batch-progress-list.css +7 -0
  343. package/src/styles/components/color-slider.css +2 -2
  344. package/src/styles/components/data-state.css +30 -0
  345. package/src/styles/components/dataframe.css +29 -35
  346. package/src/styles/components/experiment-data-viewer.css +4 -4
  347. package/src/styles/components/experiment-timeline.css +3 -30
  348. package/src/styles/components/form-builder.css +5 -5
  349. package/src/styles/components/lcms-sequence-table.css +16 -1
  350. package/src/styles/components/multi-select.css +1 -1
  351. package/src/styles/components/reagent-list.css +38 -31
  352. package/src/styles/components/sample-hierarchy-tree.css +5 -19
  353. package/src/styles/components/sample-selector.css +9 -2
  354. package/src/styles/components/schedule-calendar.css +8 -0
  355. package/src/styles/components/scientific-number.css +1 -1
  356. package/src/styles/components/secret-input.css +60 -0
  357. package/src/styles/components/well-plate.css +8 -0
  358. package/src/styles/index.css +9 -0
  359. package/src/styles/variables.css +29 -0
  360. package/src/templates/builderPresetControls.ts +1 -1
  361. package/src/templates/builderReadUtils.ts +1 -3
  362. package/src/templates/componentPlateHelpers.ts +3 -1
  363. package/src/templates/componentTargetResolvers.ts +1 -3
  364. package/src/templates/controlSchemaTargets.ts +1 -3
  365. package/src/templates/doseResponseAdapters.ts +2 -13
  366. package/src/templates/elisaAssayCollectionBuilder.ts +2 -2
  367. package/src/templates/plateMapBuilder.ts +2 -2
  368. package/src/templates/qpcrPlateBuilder.ts +1 -1
  369. package/src/templates/templateValidators.ts +1 -1
  370. package/src/templates/timeCourseBuilder.ts +2 -2
  371. package/src/templates/wellPlateScreenCollectionBuilder.ts +2 -2
  372. package/src/types/form-builder.ts +1 -0
  373. package/src/utils/color.ts +51 -0
  374. package/src/utils/errors.ts +29 -0
  375. package/src/utils/formModelSync.ts +1 -3
  376. package/src/utils/pluginIcon.ts +1 -3
  377. package/src/utils/pluginSecrets.ts +102 -0
  378. package/src/utils/records.ts +9 -0
  379. package/dist/EmptyState-p90quq8Z.js.map +0 -1
  380. package/dist/ExperimentPopover-tLwJ7XF4.js.map +0 -1
  381. package/dist/ExperimentSelectorModal-B6P2msT4.js.map +0 -1
  382. package/dist/SettingsModal-BW3Ar-uy.js.map +0 -1
  383. package/dist/components-DQt0tcpY.js.map +0 -1
  384. package/dist/composables/autoGroupConstants.d.ts +0 -1
  385. package/dist/composables/autoGroupGrouping.d.ts +0 -3
  386. package/dist/composables/useAutoGroupInputSources.d.ts +0 -32
  387. package/dist/composables-CkXDe7nm.js.map +0 -1
  388. package/dist/experiment-Y9Iz7KYi.js.map +0 -1
  389. package/dist/experiment-utils-CLnzPPq5.js.map +0 -1
  390. package/dist/templates/builderDefaults.d.ts +0 -1
  391. package/dist/templates/plateGeometry.d.ts +0 -4
  392. package/dist/templates-CovvRZ-9.js.map +0 -1
  393. package/dist/useApi-CDBq6z23.js.map +0 -1
  394. package/dist/useControlSchema-BhoJzWzx.js.map +0 -1
  395. package/dist/useDropdownState-DSb3BC3N.js.map +0 -1
  396. package/dist/useProtocolTemplates-DHwSveE6.js.map +0 -1
  397. package/src/__tests__/composables/useAutoGroupInputSources.test.ts +0 -127
  398. package/src/composables/autoGroupConstants.ts +0 -4
  399. package/src/composables/autoGroupGrouping.ts +0 -148
  400. package/src/composables/useAutoGroupInputSources.ts +0 -147
  401. package/src/templates/builderDefaults.ts +0 -11
  402. package/src/templates/plateGeometry.ts +0 -62
  403. /package/dist/__tests__/{composables/useAutoGroupInputSources.test.d.ts → components/DataStates.test.d.ts} +0 -0
@@ -3,6 +3,7 @@ import { describe, expect, it, vi } from 'vitest'
3
3
  import { h } from 'vue'
4
4
 
5
5
  import JobsStatusTray from '../../components/JobsStatusTray.vue'
6
+ import type { PluginJobCenterSource } from '../../composables/useJobsStatusTray'
6
7
  import type { JobState, JobStateInput, JobStatus } from '../../types/jobs'
7
8
 
8
9
  function job(status: JobStatus, id: string = status): JobStateInput {
@@ -40,8 +41,7 @@ function job(status: JobStatus, id: string = status): JobStateInput {
40
41
  }
41
42
 
42
43
  async function openTray(options: {
43
- jobs: JobStateInput[]
44
- adapter?: Record<string, unknown>
44
+ source: PluginJobCenterSource
45
45
  slots?: Record<string, (props: { job: JobState }) => unknown>
46
46
  }) {
47
47
  const { slots, ...props } = options
@@ -60,7 +60,7 @@ async function openTray(options: {
60
60
 
61
61
  describe('JobsStatusTray', () => {
62
62
  it('shows awaiting input as active without default actions', async () => {
63
- const wrapper = await openTray({ jobs: [job('awaiting_input')] })
63
+ const wrapper = await openTray({ source: { jobs: [job('awaiting_input')] } })
64
64
 
65
65
  expect([
66
66
  wrapper.text().includes('Waiting for input'),
@@ -69,15 +69,15 @@ describe('JobsStatusTray', () => {
69
69
  ]).toEqual([true, false, false])
70
70
  })
71
71
 
72
- it('shows only actions supported by both capability and adapter', async () => {
72
+ it('shows only actions supported by both capability and source', async () => {
73
73
  const wrapper = await openTray({
74
- jobs: [
75
- { ...job('awaiting_input', 'input'), can_cancel: true },
76
- { ...job('completed', 'done'), can_delete: true },
77
- ],
78
- adapter: {
79
- cancelJob: vi.fn(),
80
- deleteJob: vi.fn(),
74
+ source: {
75
+ jobs: [
76
+ { ...job('awaiting_input', 'input'), can_cancel: true },
77
+ { ...job('completed', 'done'), can_delete: true },
78
+ ],
79
+ cancelJob: vi.fn(async () => undefined),
80
+ deleteJob: vi.fn(async () => true),
81
81
  },
82
82
  })
83
83
 
@@ -91,7 +91,7 @@ describe('JobsStatusTray', () => {
91
91
  })
92
92
 
93
93
  it('exposes dialog state and closes on Escape', async () => {
94
- const wrapper = await openTray({ jobs: [job('running')] })
94
+ const wrapper = await openTray({ source: { jobs: [job('running')] } })
95
95
  const trigger = wrapper.get('.mint-jobs-tray__trigger')
96
96
 
97
97
  await wrapper.get('[role="dialog"]').trigger('keydown', { key: 'Escape' })
@@ -104,15 +104,17 @@ describe('JobsStatusTray', () => {
104
104
 
105
105
  it('renders an individual delete action for an active capable job', async () => {
106
106
  const wrapper = await openTray({
107
- jobs: [{ ...job('awaiting_input'), can_delete: true }],
108
- adapter: { deleteJob: vi.fn() },
107
+ source: {
108
+ jobs: [{ ...job('awaiting_input'), can_delete: true }],
109
+ deleteJob: vi.fn(async () => true),
110
+ },
109
111
  })
110
112
 
111
113
  expect(wrapper.find('[aria-label="Delete job"]').exists()).toBe(true)
112
114
  })
113
115
 
114
116
  it('traps forward Tab navigation inside the modal dialog', async () => {
115
- const wrapper = await openTray({ jobs: [job('running')] })
117
+ const wrapper = await openTray({ source: { jobs: [job('running')] } })
116
118
  const focusable = wrapper.findAll('button')
117
119
  const firstInsidePanel = wrapper.get('[aria-label="Close jobs"]')
118
120
  const lastInsidePanel = focusable[focusable.length - 1]!
@@ -124,7 +126,7 @@ describe('JobsStatusTray', () => {
124
126
  })
125
127
 
126
128
  it('traps reverse Tab navigation from the dialog container', async () => {
127
- const wrapper = await openTray({ jobs: [job('running')] })
129
+ const wrapper = await openTray({ source: { jobs: [job('running')] } })
128
130
  const panel = wrapper.get('[role="dialog"]')
129
131
  const focusable = panel.findAll('button')
130
132
  const panelElement = panel.element as HTMLElement
@@ -136,7 +138,7 @@ describe('JobsStatusTray', () => {
136
138
  })
137
139
 
138
140
  it('exposes job selection through a keyboard-accessible button', async () => {
139
- const wrapper = await openTray({ jobs: [job('running')] })
141
+ const wrapper = await openTray({ source: { jobs: [job('running')] } })
140
142
 
141
143
  await wrapper.get('.mint-jobs-tray__job-select').trigger('click')
142
144
 
@@ -145,7 +147,7 @@ describe('JobsStatusTray', () => {
145
147
 
146
148
  it('renders the job-actions slot once per row in both tabs with the row job', async () => {
147
149
  const wrapper = await openTray({
148
- jobs: [job('running', 'R-1'), job('completed', 'C-1')],
150
+ source: { jobs: [job('running', 'R-1'), job('completed', 'C-1')] },
149
151
  slots: {
150
152
  'job-actions': ({ job: row }) =>
151
153
  h('button', { 'data-testid': 'open', 'data-job': row.job_id }, 'Open'),
@@ -54,4 +54,26 @@ describe('LcmsSequenceTable', () => {
54
54
  expect(wrapper.emitted('duplicate')?.[0]).toEqual([0])
55
55
  expect(wrapper.emitted('remove')?.[0]).toEqual([0])
56
56
  })
57
+
58
+ it('reorders rows from the drag handle with Alt and the arrow keys', async () => {
59
+ const wrapper = mount(LcmsSequenceTable, {
60
+ props: { items: makeItems(), editable: true },
61
+ })
62
+
63
+ const handle = wrapper.findAll('.mint-lcms-sequence-table__drag-handle')[0]
64
+ expect(handle.element.tagName).toBe('BUTTON')
65
+ expect(handle.attributes('aria-label')).toContain('Reorder Batch_NEG_S1_001')
66
+
67
+ await handle.trigger('keydown', { key: 'ArrowDown', altKey: true })
68
+
69
+ expect(wrapper.emitted('reorder')?.[0]).toEqual([0, 1])
70
+ })
71
+
72
+ it('does not reorder from the keyboard when not editable', async () => {
73
+ const wrapper = mount(LcmsSequenceTable, {
74
+ props: { items: makeItems() },
75
+ })
76
+
77
+ expect(wrapper.find('.mint-lcms-sequence-table__drag-handle').exists()).toBe(false)
78
+ })
57
79
  })
@@ -0,0 +1,230 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { computed, h, nextTick } from 'vue'
3
+ import { createPinia } from 'pinia'
4
+ import { describe, expect, it, vi } from 'vitest'
5
+ import AppLayout from '../../components/AppLayout.vue'
6
+ import AppSidebar from '../../components/AppSidebar.vue'
7
+ import AppTopBar from '../../components/AppTopBar.vue'
8
+ import PluginWorkspaceView from '../../components/PluginWorkspaceView.vue'
9
+ import WorkspaceControlsInternal from '../../components/internal/WorkspaceControlsInternal.vue'
10
+ import WorkspaceShellInternal from '../../components/internal/WorkspaceShellInternal.vue'
11
+ import WorkspaceSidebarInternal from '../../components/internal/WorkspaceSidebarInternal.vue'
12
+ import type { PillNavItem, SidebarToolSection } from '../../types'
13
+
14
+ vi.mock('../../composables/usePlatformContext', () => ({
15
+ usePlatformContext: vi.fn(() => ({
16
+ isIntegrated: computed(() => false),
17
+ context: { value: { isIntegrated: false, theme: 'system' } },
18
+ plugin: computed(() => undefined),
19
+ user: computed(() => undefined),
20
+ theme: computed(() => 'system' as const),
21
+ features: computed(() => undefined),
22
+ navigate: vi.fn(),
23
+ notify: vi.fn(),
24
+ sendToPlatform: vi.fn(),
25
+ })),
26
+ }))
27
+
28
+ vi.mock('../../composables/useTheme', () => ({
29
+ useTheme: vi.fn(() => ({
30
+ isDark: computed(() => false),
31
+ theme: computed(() => 'light'),
32
+ toggleTheme: vi.fn(),
33
+ })),
34
+ }))
35
+
36
+ const pillNav: PillNavItem[] = [
37
+ { id: 'data', label: 'Data' },
38
+ { id: 'results', label: 'Results' },
39
+ ]
40
+
41
+ const panels: Record<string, SidebarToolSection[]> = {
42
+ data: [
43
+ { id: 'import', label: 'Import', defaultOpen: true },
44
+ ],
45
+ results: [
46
+ { id: 'display', label: 'Display', defaultOpen: true },
47
+ ],
48
+ }
49
+
50
+ interface WorkspaceSlotProps {
51
+ activeView: string
52
+ setActiveView: (viewId: string) => void
53
+ }
54
+
55
+ const globalOptions = {
56
+ plugins: [createPinia()],
57
+ stubs: {
58
+ 'router-link': {
59
+ props: ['to', 'custom'],
60
+ template: `
61
+ <slot
62
+ v-if="custom"
63
+ :href="typeof to === 'string' ? to : '#'"
64
+ :navigate="() => {}"
65
+ />
66
+ <a v-else :href="typeof to === 'string' ? to : '#'"><slot /></a>
67
+ `,
68
+ },
69
+ BaseModal: {
70
+ template: '<div><slot /></div>',
71
+ },
72
+ },
73
+ }
74
+
75
+ describe('PluginWorkspaceView internal split', () => {
76
+ it('renders through WorkspaceShellInternal, WorkspaceSidebarInternal and WorkspaceControlsInternal', () => {
77
+ const wrapper = mount(PluginWorkspaceView, {
78
+ props: {
79
+ title: 'MS Uploader',
80
+ subtitle: 'Import and visualize',
81
+ pillNav,
82
+ panels,
83
+ sidebarTitle: 'Tools',
84
+ sidebarSubtitle: 'Current file',
85
+ },
86
+ slots: {
87
+ actions: () => h('button', { 'data-test': 'topbar-action' }, 'Run'),
88
+ 'section-import': () => h('button', { 'data-test': 'import-action' }, 'Upload'),
89
+ default: ({ activeView }: WorkspaceSlotProps) => h('main', { 'data-test': 'content' }, activeView),
90
+ },
91
+ global: globalOptions,
92
+ })
93
+
94
+ // The public adapter renders its three internal pieces...
95
+ expect(wrapper.findComponent(WorkspaceShellInternal).exists()).toBe(true)
96
+ expect(wrapper.findComponent(WorkspaceSidebarInternal).exists()).toBe(true)
97
+ expect(wrapper.findComponent(WorkspaceControlsInternal).exists()).toBe(true)
98
+
99
+ // ...each of which wraps the same underlying SDK components with the same DOM output
100
+ // the pre-split component produced.
101
+ expect(wrapper.findComponent(AppLayout).props()).toMatchObject({
102
+ floating: true,
103
+ responsiveSidebar: true,
104
+ sidebarPosition: 'left',
105
+ })
106
+ expect(wrapper.findComponent(AppTopBar).props()).toMatchObject({
107
+ title: 'MS Uploader',
108
+ subtitle: 'Import and visualize',
109
+ pillNav,
110
+ currentPillId: 'data',
111
+ })
112
+ expect(wrapper.findComponent(AppSidebar).props()).toMatchObject({
113
+ panels,
114
+ activeView: 'data',
115
+ title: 'Tools',
116
+ subtitle: 'Current file',
117
+ variant: 'analysis',
118
+ floating: false,
119
+ dense: true,
120
+ })
121
+ expect(wrapper.get('[data-test="topbar-action"]').text()).toBe('Run')
122
+ expect(wrapper.get('[data-test="import-action"]').text()).toBe('Upload')
123
+ expect(wrapper.get('[data-test="content"]').text()).toBe('data')
124
+
125
+ // WorkspaceControlsInternal receives exactly the scoped-slot payload the default slot sees.
126
+ expect(wrapper.findComponent(WorkspaceControlsInternal).props('controls')).toMatchObject({
127
+ activeView: 'data',
128
+ values: {},
129
+ })
130
+ })
131
+
132
+ it('keeps topbar navigation and sidebar panels synchronized through the internal split', async () => {
133
+ const wrapper = mount(PluginWorkspaceView, {
134
+ props: {
135
+ title: 'Analysis',
136
+ pillNav,
137
+ panels,
138
+ },
139
+ slots: {
140
+ default: ({ activeView }: WorkspaceSlotProps) => h('main', { 'data-test': 'content' }, activeView),
141
+ },
142
+ global: globalOptions,
143
+ })
144
+
145
+ wrapper.findComponent(AppTopBar).vm.$emit('pill-select', { id: 'results', label: 'Results' })
146
+ await nextTick()
147
+
148
+ expect(wrapper.findComponent(AppTopBar).props('currentPillId')).toBe('results')
149
+ expect(wrapper.findComponent(AppSidebar).props('activeView')).toBe('results')
150
+ expect(wrapper.findComponent(WorkspaceSidebarInternal).props('sidebar')).toMatchObject({ activeView: 'results' })
151
+ expect(wrapper.get('[data-test="content"]').text()).toBe('results')
152
+ expect(wrapper.emitted('update:activeView')).toEqual([['results']])
153
+ expect(wrapper.emitted('pill-select')).toEqual([[{ id: 'results', label: 'Results' }]])
154
+ })
155
+
156
+ it('relays sidebar collapse, toggle, and form events unchanged through WorkspaceSidebarInternal', async () => {
157
+ const wrapper = mount(PluginWorkspaceView, {
158
+ props: {
159
+ title: 'Analysis',
160
+ panels,
161
+ forms: {
162
+ data: { sections: [] },
163
+ },
164
+ },
165
+ global: globalOptions,
166
+ })
167
+
168
+ const sidebar = wrapper.findComponent(AppSidebar)
169
+ sidebar.vm.$emit('update:collapsed', true)
170
+ sidebar.vm.$emit('update:toggle', 'import', false)
171
+ sidebar.vm.$emit('section-action', 'import', 'refresh')
172
+ sidebar.vm.$emit('form-submit', 'data', { value: 1 })
173
+ sidebar.vm.$emit('form-cancel', 'data')
174
+ await nextTick()
175
+
176
+ expect(wrapper.emitted('update:sidebarCollapsed')).toEqual([[true]])
177
+ expect(wrapper.emitted('update:toggle')).toEqual([['import', false]])
178
+ expect(wrapper.emitted('section-action')).toEqual([['import', 'refresh']])
179
+ expect(wrapper.emitted('form-submit')).toEqual([['data', { value: 1 }]])
180
+ expect(wrapper.emitted('form-cancel')).toEqual([['data']])
181
+ })
182
+
183
+ it('can render without a sidebar surface, omitting WorkspaceSidebarInternal entirely', () => {
184
+ const wrapper = mount(PluginWorkspaceView, {
185
+ props: {
186
+ title: 'Simple Plugin',
187
+ showSidebar: false,
188
+ },
189
+ slots: {
190
+ default: () => h('main', { 'data-test': 'content' }, 'Ready'),
191
+ },
192
+ global: globalOptions,
193
+ })
194
+
195
+ expect(wrapper.findComponent(WorkspaceSidebarInternal).exists()).toBe(false)
196
+ expect(wrapper.findComponent(AppSidebar).exists()).toBe(false)
197
+ expect(wrapper.findComponent(AppTopBar).props('title')).toBe('Simple Plugin')
198
+ expect(wrapper.get('[data-test="content"]').text()).toBe('Ready')
199
+ })
200
+
201
+ it('lets a consumer-supplied sidebar slot override WorkspaceSidebarInternal', () => {
202
+ const wrapper = mount(PluginWorkspaceView, {
203
+ props: {
204
+ title: 'Custom Sidebar',
205
+ panels,
206
+ },
207
+ slots: {
208
+ sidebar: () => h('aside', { 'data-test': 'custom-sidebar' }, 'Custom'),
209
+ },
210
+ global: globalOptions,
211
+ })
212
+
213
+ expect(wrapper.findComponent(WorkspaceSidebarInternal).exists()).toBe(false)
214
+ expect(wrapper.findComponent(AppSidebar).exists()).toBe(false)
215
+ expect(wrapper.get('[data-test="custom-sidebar"]').text()).toBe('Custom')
216
+ })
217
+
218
+ it('lets a consumer-supplied topbar slot override WorkspaceShellInternal default AppTopBar rendering', () => {
219
+ const wrapper = mount(PluginWorkspaceView, {
220
+ props: { title: 'Custom Top Bar' },
221
+ slots: {
222
+ topbar: () => h('header', { 'data-test': 'custom-topbar' }, 'Custom'),
223
+ },
224
+ global: globalOptions,
225
+ })
226
+
227
+ expect(wrapper.findComponent(AppTopBar).exists()).toBe(false)
228
+ expect(wrapper.get('[data-test="custom-topbar"]').text()).toBe('Custom')
229
+ })
230
+ })
@@ -122,4 +122,39 @@ describe('RackEditor', () => {
122
122
  }),
123
123
  ])
124
124
  })
125
+
126
+ it('reorders rack tabs with Alt and the arrow keys', async () => {
127
+ const racks = makeRacks()
128
+ racks.push({ ...racks[0], id: 'rack-2', name: 'Rack 2', slot: 'G', wells: {} })
129
+
130
+ const wrapper = mount(RackEditor, { props: { modelValue: racks } })
131
+
132
+ await wrapper.findAll('.mint-rack-editor__tab')[0].trigger('keydown', {
133
+ key: 'ArrowRight',
134
+ altKey: true,
135
+ })
136
+ await nextTick()
137
+
138
+ expect(wrapper.emitted('rack-reorder')?.at(-1)?.[0]).toEqual(['rack-2', 'rack-1'])
139
+ expect(wrapper.findAll('.mint-rack-editor__tab').map(tab => tab.text()))
140
+ .toEqual([expect.stringContaining('Rack 2'), expect.stringContaining('Rack 1')])
141
+ expect(wrapper.get('[role="status"]').attributes('aria-live')).toBe('polite')
142
+ })
143
+
144
+ it('does not reorder rack tabs from the keyboard when reordering is off', async () => {
145
+ const racks = makeRacks()
146
+ racks.push({ ...racks[0], id: 'rack-2', name: 'Rack 2', slot: 'G', wells: {} })
147
+
148
+ const wrapper = mount(RackEditor, {
149
+ props: { modelValue: racks, allowReorder: false },
150
+ })
151
+
152
+ await wrapper.findAll('.mint-rack-editor__tab')[0].trigger('keydown', {
153
+ key: 'ArrowRight',
154
+ altKey: true,
155
+ })
156
+ await nextTick()
157
+
158
+ expect(wrapper.emitted('rack-reorder')).toBeUndefined()
159
+ })
125
160
  })
@@ -63,7 +63,7 @@ describe('ReagentList', () => {
63
63
  },
64
64
  })
65
65
 
66
- await wrapper.findAll('th')[1].trigger('click')
66
+ await wrapper.findAll('th')[1].get('.mint-reagent-list__sort-btn').trigger('click')
67
67
 
68
68
  const rows = wrapper.findAll('tbody tr')
69
69
  expect(rows.map(row => row.text())).toEqual([
@@ -72,11 +72,73 @@ describe('ReagentList', () => {
72
72
  expect.stringContaining('Pyruvate'),
73
73
  ])
74
74
 
75
- await wrapper.findAll('th')[1].trigger('click')
75
+ await wrapper.findAll('th')[1].get('.mint-reagent-list__sort-btn').trigger('click')
76
76
  expect(wrapper.findAll('tbody tr').map(row => row.text())).toEqual([
77
77
  expect.stringContaining('Lactate'),
78
78
  expect.stringContaining('Glucose'),
79
79
  expect.stringContaining('Pyruvate'),
80
80
  ])
81
81
  })
82
+
83
+ it('exposes sortable headers as buttons that sort from the keyboard', async () => {
84
+ const wrapper = mount(ReagentList, {
85
+ props: { modelValue: reagents, readonly: true, columns: ['name', 'catalog'] },
86
+ })
87
+
88
+ const nameHeader = () => wrapper.findAll('th')[0]
89
+ expect(nameHeader().attributes('aria-sort')).toBe('none')
90
+ expect(nameHeader().get('.mint-reagent-list__sort-btn').element.tagName).toBe('BUTTON')
91
+
92
+ await nameHeader().get('.mint-reagent-list__sort-btn').trigger('keydown', { key: 'Enter' })
93
+ expect(nameHeader().attributes('aria-sort')).toBe('ascending')
94
+
95
+ await nameHeader().get('.mint-reagent-list__sort-btn').trigger('keydown', { key: ' ' })
96
+ expect(nameHeader().attributes('aria-sort')).toBe('descending')
97
+ })
98
+
99
+ it('omits aria-sort when sorting is disabled', () => {
100
+ const wrapper = mount(ReagentList, {
101
+ props: { modelValue: reagents, readonly: true, sortable: false, columns: ['name'] },
102
+ })
103
+
104
+ expect(wrapper.findAll('th')[0].attributes('aria-sort')).toBeUndefined()
105
+ })
106
+
107
+ it('reorders reagents from the drag handle with Alt and the arrow keys', async () => {
108
+ const wrapper = mount(ReagentList, {
109
+ props: { modelValue: reagents, columns: ['name'] },
110
+ })
111
+
112
+ const handle = wrapper.findAll('.mint-reagent-list__drag-handle-btn')[0]
113
+ expect(handle.attributes('aria-label')).toContain('Reorder Glucose')
114
+
115
+ await handle.trigger('keydown', { key: 'ArrowDown', altKey: true })
116
+
117
+ expect(wrapper.emitted('reorder')?.[0]).toEqual(['reagent-1', 0, 1])
118
+ expect(
119
+ (wrapper.emitted('update:modelValue')?.[0]?.[0] as Reagent[]).map(reagent => reagent.id),
120
+ ).toEqual(['reagent-2', 'reagent-1'])
121
+ })
122
+
123
+ it('does not reorder from the keyboard while readonly', async () => {
124
+ const wrapper = mount(ReagentList, {
125
+ props: { modelValue: reagents, readonly: true, columns: ['name'] },
126
+ })
127
+
128
+ expect(wrapper.find('.mint-reagent-list__drag-handle-btn').exists()).toBe(false)
129
+ })
130
+
131
+ it('announces a keyboard reorder in a polite live region', async () => {
132
+ const wrapper = mount(ReagentList, {
133
+ props: { modelValue: reagents, columns: ['name'] },
134
+ })
135
+
136
+ await wrapper.findAll('.mint-reagent-list__drag-handle-btn')[0]
137
+ .trigger('keydown', { key: 'ArrowDown', altKey: true })
138
+ await wrapper.vm.$nextTick()
139
+
140
+ const liveRegion = wrapper.get('[role="status"]')
141
+ expect(liveRegion.attributes('aria-live')).toBe('polite')
142
+ expect(liveRegion.text()).toBe('Glucose moved to position 2 of 2.')
143
+ })
82
144
  })
@@ -6,9 +6,21 @@ const retiredSelectors: Record<string, readonly string[]> = {
6
6
  'app-plugin-switcher.css': [
7
7
  'mint-plugin-switcher__swatch',
8
8
  ],
9
+ 'audit-trail.css': [
10
+ 'mint-audit-trail__empty',
11
+ ],
12
+ 'dataframe.css': [
13
+ 'mint-dataframe__empty',
14
+ 'mint-dataframe__empty-icon',
15
+ 'mint-dataframe__empty-text',
16
+ 'mint-dataframe__loading-spinner',
17
+ ],
9
18
  'dose-calculator.css': [
10
19
  'mint-dose-calculator__mw-input',
11
20
  ],
21
+ 'experiment-data-viewer.css': [
22
+ 'mint-data-viewer__empty',
23
+ ],
12
24
  'experiment-popover.css': [
13
25
  'mint-experiment-popover__check-icon',
14
26
  'mint-experiment-popover__divider',
@@ -24,13 +36,31 @@ const retiredSelectors: Record<string, readonly string[]> = {
24
36
  'mint-experiment-selector__filters',
25
37
  'mint-experiment-selector__status-filter',
26
38
  ],
39
+ 'experiment-timeline.css': [
40
+ 'mint-timeline__empty',
41
+ 'mint-timeline__empty-btn',
42
+ 'mint-timeline__empty-content',
43
+ 'mint-timeline__empty-text',
44
+ ],
45
+ 'lcms-sequence-table.css': [
46
+ 'mint-lcms-sequence-table__empty',
47
+ ],
27
48
  'protocol-step-editor.css': [
28
49
  'mint-protocol-editor__field-row',
29
50
  ],
30
51
  'reagent-editor.css': [
31
52
  'mint-reagent-editor__row',
32
53
  ],
54
+ 'reagent-list.css': [
55
+ 'mint-reagent-list__empty',
56
+ 'mint-reagent-list__empty-btn',
57
+ 'mint-reagent-list__empty-icon',
58
+ 'mint-reagent-list__empty-text',
59
+ ],
33
60
  'sample-hierarchy-tree.css': [
61
+ 'mint-sample-tree__empty',
62
+ 'mint-sample-tree__empty-icon',
63
+ 'mint-sample-tree__empty-text',
34
64
  'mint-sample-tree__node--selected',
35
65
  ],
36
66
  'sample-selector.css': [
@@ -38,6 +68,7 @@ const retiredSelectors: Record<string, readonly string[]> = {
38
68
  'mint-popover-enter-from',
39
69
  'mint-popover-leave-active',
40
70
  'mint-popover-leave-to',
71
+ 'mint-sample-selector__empty',
41
72
  'mint-sample-selector__level-badge',
42
73
  'mint-sample-selector__level-btn',
43
74
  'mint-sample-selector__level-desc',
@@ -1,4 +1,4 @@
1
- import { ref } from 'vue'
1
+ import { nextTick, ref } from 'vue'
2
2
  import { describe, expect, it } from 'vitest'
3
3
  import type { SampleGroup } from '../../types'
4
4
  import { useSampleSelectorDrag } from '../../components/SampleSelector.drag'
@@ -9,6 +9,10 @@ type TestDragEvent = Omit<DragEvent, 'dataTransfer'> & {
9
9
  defaultPrevented: boolean
10
10
  }
11
11
 
12
+ function keyboardEvent(key: string, altKey = true): KeyboardEvent {
13
+ return { key, altKey, preventDefault: () => {} } as unknown as KeyboardEvent
14
+ }
15
+
12
16
  function createDragEvent(options: { clientY?: number; top?: number; height?: number } = {}) {
13
17
  const data: Record<string, string> = {}
14
18
  const transfer = {
@@ -123,4 +127,50 @@ describe('SampleSelector drag helpers', () => {
123
127
 
124
128
  expect(drag.reorderTarget.value).toBe('Liver / High / 10min')
125
129
  })
130
+
131
+ it('reorders sibling groups with Alt and the arrow keys', async () => {
132
+ const state = ref<SampleGroup[]>([
133
+ { name: 'Liver / High / 5min', color: '#111111', samples: ['L1'] },
134
+ { name: 'Liver / High / 10min', color: '#222222', samples: ['L2'] },
135
+ { name: 'Liver / High / 20min', color: '#333333', samples: ['L3'] },
136
+ ])
137
+ const drag = useSampleSelectorDrag(state)
138
+
139
+ drag.handleGroupReorderKeydown('Liver / High / 5min', 'sub', keyboardEvent('ArrowDown'))
140
+
141
+ expect(state.value.map(group => group.name)).toEqual([
142
+ 'Liver / High / 10min',
143
+ 'Liver / High / 5min',
144
+ 'Liver / High / 20min',
145
+ ])
146
+
147
+ await nextTick()
148
+ expect(drag.announcement.value).toBe('Liver / High / 5min moved down to position 2 of 3.')
149
+ })
150
+
151
+ it('keeps a group inside its own family and ignores arrows without Alt', () => {
152
+ const state = ref<SampleGroup[]>([
153
+ { name: 'Liver / High / 5min', color: '#111111', samples: ['L1'] },
154
+ { name: 'Muscle / High / 5min', color: '#222222', samples: ['M1'] },
155
+ ])
156
+ const drag = useSampleSelectorDrag(state)
157
+ const before = state.value
158
+
159
+ drag.handleGroupReorderKeydown('Liver / High / 5min', 'sub', keyboardEvent('ArrowDown'))
160
+ drag.handleGroupReorderKeydown('Liver / High / 5min', 'sub', keyboardEvent('ArrowDown', false))
161
+
162
+ expect(state.value).toBe(before)
163
+ })
164
+
165
+ it('reorders whole major families with Alt and the arrow keys', () => {
166
+ const state = ref<SampleGroup[]>([
167
+ { name: 'Liver / High', color: '#111111', samples: ['L1'] },
168
+ { name: 'Muscle / High', color: '#222222', samples: ['M1'] },
169
+ ])
170
+ const drag = useSampleSelectorDrag(state)
171
+
172
+ drag.handleGroupReorderKeydown('Muscle', 'major', keyboardEvent('ArrowUp'))
173
+
174
+ expect(state.value.map(group => group.name)).toEqual(['Muscle / High', 'Liver / High'])
175
+ })
126
176
  })