@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
@@ -0,0 +1,176 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import AuditTrail from '../../components/AuditTrail.vue'
4
+ import BatchProgressList from '../../components/BatchProgressList.vue'
5
+ import ExperimentTimeline from '../../components/ExperimentTimeline.vue'
6
+ import LcmsSequenceTable from '../../components/LcmsSequenceTable.vue'
7
+ import ReagentList from '../../components/ReagentList.vue'
8
+ import SampleHierarchyTree from '../../components/SampleHierarchyTree.vue'
9
+ import ScheduleCalendar from '../../components/ScheduleCalendar.vue'
10
+ import WellPlate from '../../components/WellPlate.vue'
11
+
12
+ /**
13
+ * The `loading` / `error` props and the `#empty` / `#error` / `#loading` slots
14
+ * are one contract shared by the data components, so they are asserted in one
15
+ * place. Each entry pairs a component with the props that leave it empty and a
16
+ * selector that only its populated rendering produces.
17
+ */
18
+ const cases = [
19
+ {
20
+ name: 'AuditTrail',
21
+ component: AuditTrail,
22
+ emptyProps: { entries: [] },
23
+ filledProps: {
24
+ entries: [{ id: 'a', type: 'created', action: 'Created', timestamp: '2026-01-01T00:00:00Z' }],
25
+ },
26
+ contentSelector: '.mint-audit-trail__list',
27
+ },
28
+ {
29
+ name: 'BatchProgressList',
30
+ component: BatchProgressList,
31
+ emptyProps: { items: [] },
32
+ filledProps: { items: [{ id: '1', label: 'One', status: 'pending' }] },
33
+ contentSelector: '.mint-batch-progress__list',
34
+ },
35
+ {
36
+ name: 'ExperimentTimeline',
37
+ component: ExperimentTimeline,
38
+ emptyProps: { modelValue: [] },
39
+ filledProps: { modelValue: [{ id: 's1', name: 'Step 1', order: 1, type: 'incubation' }] },
40
+ contentSelector: '.mint-timeline__step',
41
+ },
42
+ {
43
+ name: 'LcmsSequenceTable',
44
+ component: LcmsSequenceTable,
45
+ emptyProps: { items: [] },
46
+ filledProps: {
47
+ items: [{
48
+ file_name: 'a.raw',
49
+ position: 'A1',
50
+ sample_type: 'Unknown',
51
+ instrument_method: 'm',
52
+ injection_volume: 1,
53
+ }],
54
+ },
55
+ contentSelector: '.mint-lcms-sequence-table__scroller',
56
+ },
57
+ {
58
+ name: 'ReagentList',
59
+ component: ReagentList,
60
+ emptyProps: { modelValue: [] },
61
+ filledProps: { modelValue: [{ id: 'r1', name: 'Water' }] },
62
+ contentSelector: '.mint-reagent-list__table',
63
+ },
64
+ {
65
+ name: 'SampleHierarchyTree',
66
+ component: SampleHierarchyTree,
67
+ emptyProps: { nodes: [] },
68
+ filledProps: { nodes: [{ id: 'n1', label: 'Study', type: 'study' }] },
69
+ contentSelector: '.mint-sample-tree__node',
70
+ },
71
+ ] as const
72
+
73
+ describe('shared data component states', () => {
74
+ describe.each(cases)('$name', ({ component, emptyProps, filledProps, contentSelector }) => {
75
+ it('renders its content when neither loading nor error is passed', () => {
76
+ const wrapper = mount(component, { props: { ...filledProps } as never })
77
+
78
+ expect(wrapper.find(contentSelector).exists()).toBe(true)
79
+ expect(wrapper.find('[data-state]').exists()).toBe(false)
80
+ })
81
+
82
+ it('renders an empty state instead of content when there is nothing to show', () => {
83
+ const wrapper = mount(component, { props: { ...emptyProps } as never })
84
+
85
+ expect(wrapper.find('[data-state="empty"]').exists()).toBe(true)
86
+ expect(wrapper.find(contentSelector).exists()).toBe(false)
87
+ })
88
+
89
+ it('renders a loading state that replaces the content', () => {
90
+ const wrapper = mount(component, { props: { ...filledProps, loading: true } as never })
91
+
92
+ expect(wrapper.find('[data-state="loading"]').exists()).toBe(true)
93
+ expect(wrapper.find('.mint-spinner').exists()).toBe(true)
94
+ expect(wrapper.find(contentSelector).exists()).toBe(false)
95
+ })
96
+
97
+ it('renders an error state that wins over content and loading', () => {
98
+ const wrapper = mount(component, {
99
+ props: { ...filledProps, loading: true, error: 'Request failed' } as never,
100
+ })
101
+
102
+ expect(wrapper.find('[data-state="error"]').exists()).toBe(true)
103
+ expect(wrapper.find('[data-state="loading"]').exists()).toBe(false)
104
+ expect(wrapper.find('.mint-empty-state__title').text()).toBe('Request failed')
105
+ expect(wrapper.find(contentSelector).exists()).toBe(false)
106
+ })
107
+
108
+ it('lets the empty slot replace the default placeholder', () => {
109
+ const wrapper = mount(component, {
110
+ props: { ...emptyProps } as never,
111
+ slots: { empty: '<p class="slot-empty">custom</p>' },
112
+ })
113
+
114
+ expect(wrapper.find('.slot-empty').exists()).toBe(true)
115
+ expect(wrapper.find('[data-state="empty"]').exists()).toBe(false)
116
+ })
117
+
118
+ it('lets the error slot replace the default placeholder', () => {
119
+ const wrapper = mount(component, {
120
+ props: { ...filledProps, error: 'Boom' } as never,
121
+ slots: { error: '<p class="slot-error">custom</p>' },
122
+ })
123
+
124
+ expect(wrapper.find('.slot-error').exists()).toBe(true)
125
+ expect(wrapper.find('[data-state="error"]').exists()).toBe(false)
126
+ })
127
+ })
128
+
129
+ describe('opt-in empty states', () => {
130
+ it('keeps the WellPlate grid when no emptyMessage is given', () => {
131
+ const wrapper = mount(WellPlate, { props: { wells: {} } })
132
+
133
+ expect(wrapper.find('.mint-well-plate__scroll').exists()).toBe(true)
134
+ expect(wrapper.find('[data-state]').exists()).toBe(false)
135
+ })
136
+
137
+ it('replaces the WellPlate grid once an emptyMessage is given', () => {
138
+ const wrapper = mount(WellPlate, {
139
+ props: { wells: {}, emptyMessage: 'No plate loaded' },
140
+ })
141
+
142
+ expect(wrapper.find('[data-state="empty"]').exists()).toBe(true)
143
+ expect(wrapper.find('.mint-well-plate__scroll').exists()).toBe(false)
144
+ })
145
+
146
+ it('replaces the WellPlate grid on error', () => {
147
+ const wrapper = mount(WellPlate, { props: { error: 'Boom' } })
148
+
149
+ expect(wrapper.find('[data-state="error"]').exists()).toBe(true)
150
+ expect(wrapper.find('.mint-well-plate__scroll').exists()).toBe(false)
151
+ })
152
+
153
+ it('keeps the ScheduleCalendar grid when no emptyMessage is given', () => {
154
+ const wrapper = mount(ScheduleCalendar, { props: { events: [] } })
155
+
156
+ expect(wrapper.find('.mint-schedule__body').exists()).toBe(true)
157
+ expect(wrapper.find('[data-state]').exists()).toBe(false)
158
+ })
159
+
160
+ it('replaces the ScheduleCalendar grid once an emptyMessage is given', () => {
161
+ const wrapper = mount(ScheduleCalendar, {
162
+ props: { events: [], emptyMessage: 'Nothing scheduled' },
163
+ })
164
+
165
+ expect(wrapper.find('[data-state="empty"]').exists()).toBe(true)
166
+ expect(wrapper.find('.mint-schedule__body').exists()).toBe(false)
167
+ })
168
+
169
+ it('replaces the ScheduleCalendar grid while loading', () => {
170
+ const wrapper = mount(ScheduleCalendar, { props: { loading: true } })
171
+
172
+ expect(wrapper.find('[data-state="loading"]').exists()).toBe(true)
173
+ expect(wrapper.find('.mint-schedule__body').exists()).toBe(false)
174
+ })
175
+ })
176
+ })
@@ -42,4 +42,80 @@ describe('DatePicker', () => {
42
42
 
43
43
  wrapper.unmount()
44
44
  })
45
+
46
+ it('opens from the input with Enter, Space and ArrowDown', async () => {
47
+ const wrapper = mount(DatePicker, { attachTo: document.body })
48
+
49
+ for (const key of ['Enter', ' ', 'ArrowDown']) {
50
+ await wrapper.get('input').trigger('keydown', { key })
51
+ expect(dropdown()).not.toBeNull()
52
+
53
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
54
+ await nextTick()
55
+ expect(dropdown()).toBeNull()
56
+ }
57
+
58
+ wrapper.unmount()
59
+ })
60
+
61
+ it('moves focus into the calendar grid and back to the input on close', async () => {
62
+ const wrapper = mount(DatePicker, {
63
+ props: { modelValue: '2026-04-22' },
64
+ attachTo: document.body,
65
+ })
66
+ const input = wrapper.get<HTMLInputElement>('input').element
67
+
68
+ await wrapper.get('input').trigger('click')
69
+ await nextTick()
70
+ await nextTick()
71
+ await nextTick()
72
+
73
+ expect(document.activeElement).toBe(
74
+ document.body.querySelector('.mint-date-picker__day--selected'),
75
+ )
76
+
77
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
78
+ await nextTick()
79
+ await nextTick()
80
+
81
+ expect(document.activeElement).toBe(input)
82
+
83
+ wrapper.unmount()
84
+ })
85
+
86
+ it('marks the panel as a non-modal dialog', async () => {
87
+ const wrapper = mount(DatePicker, { attachTo: document.body })
88
+
89
+ await wrapper.get('input').trigger('click')
90
+
91
+ const panel = dropdown()
92
+ expect(panel?.getAttribute('role')).toBe('dialog')
93
+ expect(panel?.hasAttribute('aria-modal')).toBe(false)
94
+ expect(wrapper.get('input').attributes('aria-haspopup')).toBe('dialog')
95
+ expect(wrapper.get('input').attributes('aria-expanded')).toBe('true')
96
+
97
+ wrapper.unmount()
98
+ })
99
+
100
+ it('keeps the panel open while the page scrolls, repositioning instead', async () => {
101
+ const wrapper = mount(DatePicker, { attachTo: document.body })
102
+
103
+ await wrapper.get('input').trigger('click')
104
+ window.dispatchEvent(new Event('scroll'))
105
+ await nextTick()
106
+
107
+ expect(dropdown()).not.toBeNull()
108
+
109
+ wrapper.unmount()
110
+ })
111
+
112
+ it('does not open from the keyboard while disabled', async () => {
113
+ const wrapper = mount(DatePicker, { props: { disabled: true }, attachTo: document.body })
114
+
115
+ await wrapper.get('input').trigger('keydown', { key: 'Enter' })
116
+
117
+ expect(dropdown()).toBeNull()
118
+
119
+ wrapper.unmount()
120
+ })
45
121
  })
@@ -45,4 +45,40 @@ describe('DateTimePicker', () => {
45
45
 
46
46
  wrapper.unmount()
47
47
  })
48
+
49
+ it('opens from the keyboard, focuses the calendar, and restores focus on close', async () => {
50
+ const wrapper = mount(DateTimePicker, {
51
+ props: { modelValue: '2026-04-22T14:30' },
52
+ attachTo: document.body,
53
+ })
54
+ const input = wrapper.get<HTMLInputElement>('input').element
55
+
56
+ await wrapper.get('input').trigger('keydown', { key: 'ArrowDown' })
57
+ await nextTick()
58
+ await nextTick()
59
+ await nextTick()
60
+
61
+ expect(wrapper.find('.mint-datetime-picker__dropdown').exists()).toBe(true)
62
+ expect(document.activeElement).toBe(wrapper.get('.mint-date-picker__day--selected').element)
63
+
64
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
65
+ await nextTick()
66
+ await nextTick()
67
+
68
+ expect(document.activeElement).toBe(input)
69
+
70
+ wrapper.unmount()
71
+ })
72
+
73
+ it('marks the panel as a non-modal dialog', async () => {
74
+ const wrapper = mount(DateTimePicker, { attachTo: document.body })
75
+
76
+ await wrapper.get('input').trigger('click')
77
+
78
+ const panel = wrapper.get('.mint-datetime-picker__dropdown')
79
+ expect(panel.attributes('role')).toBe('dialog')
80
+ expect(panel.attributes('aria-modal')).toBeUndefined()
81
+
82
+ wrapper.unmount()
83
+ })
48
84
  })
@@ -458,6 +458,140 @@ describe('DropdownButton', () => {
458
458
  })
459
459
  })
460
460
 
461
+ describe('listbox keyboard handling', () => {
462
+ it('opens and focuses the first enabled option on ArrowDown', async () => {
463
+ const wrapper = mount(DropdownButton, {
464
+ props: { options: mockOptions },
465
+ attachTo: document.body,
466
+ })
467
+
468
+ await wrapper.get('.mint-dropdown-button__trigger').trigger('keydown', { key: 'ArrowDown' })
469
+ await wrapper.vm.$nextTick()
470
+
471
+ expect(wrapper.find('.mint-dropdown-button__menu').exists()).toBe(true)
472
+ expect(document.activeElement).toBe(wrapper.findAll('.mint-dropdown-button__option')[0].element)
473
+
474
+ wrapper.unmount()
475
+ })
476
+
477
+ it('opens and focuses the last option on ArrowUp', async () => {
478
+ const wrapper = mount(DropdownButton, {
479
+ props: { options: mockOptions },
480
+ attachTo: document.body,
481
+ })
482
+
483
+ await wrapper.get('.mint-dropdown-button__trigger').trigger('keydown', { key: 'ArrowUp' })
484
+ await wrapper.vm.$nextTick()
485
+
486
+ // Option C is disabled and excluded from the navigable set, so the last
487
+ // navigable option is Option B even though it renders second-to-last.
488
+ const options = wrapper.findAll('.mint-dropdown-button__option')
489
+ expect(document.activeElement).toBe(options[1].element)
490
+
491
+ wrapper.unmount()
492
+ })
493
+
494
+ it('cycles option focus with ArrowDown/ArrowUp, skipping disabled options, wrapping at the ends', async () => {
495
+ const wrapper = mount(DropdownButton, {
496
+ props: { options: mockOptions },
497
+ attachTo: document.body,
498
+ })
499
+
500
+ await wrapper.get('.mint-dropdown-button__trigger').trigger('keydown', { key: 'ArrowDown' })
501
+ await wrapper.vm.$nextTick()
502
+
503
+ const options = wrapper.findAll('.mint-dropdown-button__option')
504
+ const menu = wrapper.get('.mint-dropdown-button__menu')
505
+
506
+ expect(document.activeElement).toBe(options[0].element)
507
+
508
+ // Option C is disabled and excluded from the navigable set entirely.
509
+ await menu.trigger('keydown', { key: 'ArrowDown' })
510
+ expect(document.activeElement).toBe(options[1].element)
511
+
512
+ await menu.trigger('keydown', { key: 'ArrowDown' })
513
+ expect(document.activeElement).toBe(options[0].element)
514
+
515
+ await menu.trigger('keydown', { key: 'ArrowUp' })
516
+ expect(document.activeElement).toBe(options[1].element)
517
+
518
+ wrapper.unmount()
519
+ })
520
+
521
+ it('jumps to the first option on Home and the last on End', async () => {
522
+ const wrapper = mount(DropdownButton, {
523
+ props: { options: mockOptions },
524
+ attachTo: document.body,
525
+ })
526
+
527
+ await wrapper.get('.mint-dropdown-button__trigger').trigger('keydown', { key: 'ArrowDown' })
528
+ await wrapper.vm.$nextTick()
529
+
530
+ const options = wrapper.findAll('.mint-dropdown-button__option')
531
+ const menu = wrapper.get('.mint-dropdown-button__menu')
532
+
533
+ await menu.trigger('keydown', { key: 'End' })
534
+ expect(document.activeElement).toBe(options[1].element)
535
+
536
+ await menu.trigger('keydown', { key: 'Home' })
537
+ expect(document.activeElement).toBe(options[0].element)
538
+
539
+ wrapper.unmount()
540
+ })
541
+
542
+ it('selects the focused option on Enter', async () => {
543
+ const wrapper = mount(DropdownButton, {
544
+ props: { options: mockOptions },
545
+ attachTo: document.body,
546
+ })
547
+
548
+ await wrapper.get('.mint-dropdown-button__trigger').trigger('keydown', { key: 'ArrowDown' })
549
+ await wrapper.vm.$nextTick()
550
+ await wrapper.get('.mint-dropdown-button__option').trigger('keydown', { key: 'Enter' })
551
+
552
+ expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['a'])
553
+ expect(wrapper.find('.mint-dropdown-button__menu').exists()).toBe(false)
554
+
555
+ wrapper.unmount()
556
+ })
557
+
558
+ it('closes on Escape and returns focus to the trigger', async () => {
559
+ const wrapper = mount(DropdownButton, {
560
+ props: { options: mockOptions },
561
+ attachTo: document.body,
562
+ })
563
+
564
+ const trigger = wrapper.get('.mint-dropdown-button__trigger')
565
+ await trigger.trigger('keydown', { key: 'ArrowDown' })
566
+ await wrapper.vm.$nextTick()
567
+
568
+ await wrapper.get('.mint-dropdown-button__menu').trigger('keydown', { key: 'Escape' })
569
+ await wrapper.vm.$nextTick()
570
+
571
+ expect(wrapper.find('.mint-dropdown-button__menu').exists()).toBe(false)
572
+ expect(document.activeElement).toBe(trigger.element)
573
+
574
+ wrapper.unmount()
575
+ })
576
+
577
+ it('closes on Tab without forcing focus', async () => {
578
+ const wrapper = mount(DropdownButton, {
579
+ props: { options: mockOptions },
580
+ attachTo: document.body,
581
+ })
582
+
583
+ await wrapper.get('.mint-dropdown-button__trigger').trigger('keydown', { key: 'ArrowDown' })
584
+ await wrapper.vm.$nextTick()
585
+
586
+ await wrapper.get('.mint-dropdown-button__menu').trigger('keydown', { key: 'Tab' })
587
+ await wrapper.vm.$nextTick()
588
+
589
+ expect(wrapper.find('.mint-dropdown-button__menu').exists()).toBe(false)
590
+
591
+ wrapper.unmount()
592
+ })
593
+ })
594
+
461
595
  describe('edge cases', () => {
462
596
  it('should handle empty options array', () => {
463
597
  const wrapper = mount(DropdownButton, {
@@ -0,0 +1,63 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { describe, expect, it } from 'vitest'
3
+ import ExperimentTimeline from '../../components/ExperimentTimeline.vue'
4
+ import type { ProtocolStep } from '../../types'
5
+
6
+ function makeSteps(): ProtocolStep[] {
7
+ return [
8
+ { id: 's1', name: 'Thaw', type: 'incubation', status: 'pending', order: 0 },
9
+ { id: 's2', name: 'Quench', type: 'incubation', status: 'pending', order: 1 },
10
+ { id: 's3', name: 'Extract', type: 'incubation', status: 'pending', order: 2 },
11
+ ]
12
+ }
13
+
14
+ describe('ExperimentTimeline', () => {
15
+ it('renders steps in order', () => {
16
+ const wrapper = mount(ExperimentTimeline, { props: { modelValue: makeSteps() } })
17
+
18
+ expect(wrapper.findAll('.mint-timeline__title').map(title => title.text()))
19
+ .toEqual(['Thaw', 'Quench', 'Extract'])
20
+ })
21
+
22
+ it('reorders a step with Alt and the arrow keys while editable', async () => {
23
+ const wrapper = mount(ExperimentTimeline, {
24
+ props: { modelValue: makeSteps(), editable: true },
25
+ })
26
+
27
+ const first = wrapper.findAll('.mint-timeline__step')[0]
28
+ expect(first.attributes('tabindex')).toBe('0')
29
+ expect(first.attributes('aria-label')).toContain('Press Alt with the arrow keys')
30
+
31
+ await first.trigger('keydown', { key: 'ArrowDown', altKey: true })
32
+
33
+ expect(wrapper.emitted('step-reorder')?.[0]).toEqual(['s1', 1])
34
+ const steps = wrapper.emitted('update:modelValue')?.[0]?.[0] as ProtocolStep[]
35
+ expect(steps.map(step => step.id)).toEqual(['s2', 's1', 's3'])
36
+ expect(steps.map(step => step.order)).toEqual([0, 1, 2])
37
+ })
38
+
39
+ it('announces the move politely', async () => {
40
+ const wrapper = mount(ExperimentTimeline, {
41
+ props: { modelValue: makeSteps(), editable: true },
42
+ })
43
+
44
+ await wrapper.findAll('.mint-timeline__step')[2]
45
+ .trigger('keydown', { key: 'ArrowUp', altKey: true })
46
+ await wrapper.vm.$nextTick()
47
+
48
+ const liveRegion = wrapper.get('[role="status"]')
49
+ expect(liveRegion.attributes('aria-live')).toBe('polite')
50
+ expect(liveRegion.text()).toBe('Extract moved to position 2 of 3.')
51
+ })
52
+
53
+ it('is not keyboard reorderable when not editable', async () => {
54
+ const wrapper = mount(ExperimentTimeline, { props: { modelValue: makeSteps() } })
55
+
56
+ const first = wrapper.findAll('.mint-timeline__step')[0]
57
+ expect(first.attributes('tabindex')).toBeUndefined()
58
+
59
+ await first.trigger('keydown', { key: 'ArrowDown', altKey: true })
60
+
61
+ expect(wrapper.emitted('step-reorder')).toBeUndefined()
62
+ })
63
+ })
@@ -65,7 +65,6 @@ vi.mock('../../composables/useGeneratedAnalysis', async () => {
65
65
  loadResult: generated.loadResult,
66
66
  resultForJob: generated.resultForJob,
67
67
  refreshJobs: vi.fn(),
68
- jobsAdapter: {},
69
68
  })
70
69
  },
71
70
  }
@@ -146,8 +145,6 @@ describe('GeneratedPluginView', () => {
146
145
  result: wrapper.findComponent(GeneratedResultRenderer).text(),
147
146
  jobs: wrapper.findComponent(JobsStatusTray).exists(),
148
147
  jobSource: wrapper.findComponent(JobsStatusTray).props('source'),
149
- legacyJobs: wrapper.findComponent(JobsStatusTray).props('jobs'),
150
- legacyAdapter: wrapper.findComponent(JobsStatusTray).props('adapter'),
151
148
  }).toMatchObject({
152
149
  workspace: {
153
150
  title: 'Demo analysis',
@@ -159,8 +156,6 @@ describe('GeneratedPluginView', () => {
159
156
  result: 'complete',
160
157
  jobs: true,
161
158
  jobSource: expect.any(Object),
162
- legacyJobs: undefined,
163
- legacyAdapter: undefined,
164
159
  })
165
160
  })
166
161
 
@@ -45,4 +45,57 @@ describe('GroupAssigner', () => {
45
45
  wrapper.find('.mint-group-assigner__zone').trigger('dragover', { dataTransfer: null }),
46
46
  ).resolves.toBeUndefined()
47
47
  })
48
+
49
+ it('assigns and unassigns groups with Alt and the arrow keys', async () => {
50
+ const wrapper = mount(GroupAssigner, {
51
+ props: { groups, group1: [], group2: [] },
52
+ })
53
+
54
+ const pill = wrapper.get('.mint-group-assigner__pill--unassigned')
55
+ expect(pill.attributes('tabindex')).toBe('0')
56
+ expect(pill.attributes('aria-label')).toContain('Available Groups')
57
+
58
+ await pill.trigger('keydown', { key: 'ArrowLeft', altKey: true })
59
+ expect(wrapper.emitted('update:group1')?.[0]).toEqual([['control-a']])
60
+
61
+ await wrapper.get('.mint-group-assigner__pill--unassigned')
62
+ .trigger('keydown', { key: 'ArrowRight', altKey: true })
63
+ expect(wrapper.emitted('update:group2')?.at(-1)).toEqual([['control-a']])
64
+ })
65
+
66
+ it('moves an assigned group back to the unassigned pool', async () => {
67
+ const wrapper = mount(GroupAssigner, {
68
+ props: { groups, group1: ['control-a'], group2: [] },
69
+ })
70
+
71
+ await wrapper.get('.mint-group-assigner__zone--1 .mint-group-assigner__pill')
72
+ .trigger('keydown', { key: 'ArrowRight', altKey: true })
73
+
74
+ expect(wrapper.emitted('update:group1')?.[0]).toEqual([[]])
75
+ })
76
+
77
+ it('announces keyboard assignments politely', async () => {
78
+ const wrapper = mount(GroupAssigner, {
79
+ props: { groups, group1: [], group2: [] },
80
+ })
81
+
82
+ await wrapper.get('.mint-group-assigner__pill--unassigned')
83
+ .trigger('keydown', { key: 'ArrowLeft', altKey: true })
84
+ await wrapper.vm.$nextTick()
85
+
86
+ const liveRegion = wrapper.get('[role="status"]')
87
+ expect(liveRegion.attributes('aria-live')).toBe('polite')
88
+ expect(liveRegion.text()).toBe('control-a moved to Control.')
89
+ })
90
+
91
+ it('ignores arrow keys without Alt', async () => {
92
+ const wrapper = mount(GroupAssigner, {
93
+ props: { groups, group1: [], group2: [] },
94
+ })
95
+
96
+ await wrapper.get('.mint-group-assigner__pill--unassigned')
97
+ .trigger('keydown', { key: 'ArrowLeft' })
98
+
99
+ expect(wrapper.emitted('update:group1')).toBeUndefined()
100
+ })
48
101
  })