@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
@@ -12,26 +12,19 @@
12
12
  import { computed, useSlots } from 'vue'
13
13
  import type {
14
14
  AccountMenuItem,
15
- ExperimentSummary,
16
- PageSelectorItem,
17
15
  PillNavItem,
18
16
  PluginSwitcherPlugin,
19
17
  PillNavOption,
20
18
  } from '../types'
21
19
  import { useAppExperiment } from '../composables/useAppExperiment'
22
- import AppLayout from './AppLayout.vue'
23
- import AppSidebar from './AppSidebar.vue'
24
- import AppTopBar from './AppTopBar.vue'
25
- import { usePluginWorkspaceControls } from './PluginWorkspaceView.controls'
26
- import { usePluginWorkspaceNavigation } from './PluginWorkspaceView.navigation'
27
- import {
28
- getPluginWorkspaceSidebarSlotNames,
29
- getPluginWorkspaceTopBarSlotNames,
30
- hasPluginWorkspaceSidebarSurface,
31
- } from './PluginWorkspaceView.shell'
20
+ import { usePluginWorkspace } from '../composables/usePluginWorkspace'
21
+ import WorkspaceControlsInternal from './internal/WorkspaceControlsInternal.vue'
22
+ import WorkspaceShellInternal from './internal/WorkspaceShellInternal.vue'
23
+ import WorkspaceSidebarInternal from './internal/WorkspaceSidebarInternal.vue'
32
24
  import type {
33
25
  PluginWorkspaceForwardedSlotProps,
34
26
  PluginWorkspaceSlotProps,
27
+ PluginWorkspaceViewEmits,
35
28
  PluginWorkspaceViewProps,
36
29
  } from './PluginWorkspaceView.props'
37
30
 
@@ -103,28 +96,7 @@ const props = withDefaults(defineProps<PluginWorkspaceViewProps>(), {
103
96
  sidebarCloseLabel: 'Close workspace sidebar',
104
97
  })
105
98
 
106
- const emit = defineEmits<{
107
- 'update:activeView': [value: string]
108
- 'page-selector-select': [page: PageSelectorItem]
109
- 'pill-select': [item: PillNavItem]
110
- 'pill-option-select': [option: PillNavOption, item: PillNavItem]
111
- 'plugin-switcher-select': [plugin: PluginSwitcherPlugin]
112
- 'plugin-switcher-install': []
113
- 'account-menu-select': [item: AccountMenuItem]
114
- 'sign-out': []
115
- 'notifications-click': []
116
- 'settings-values-change': [data: Record<string, unknown>]
117
- 'admin-click': []
118
- 'experiment-select': [experiment: ExperimentSummary]
119
- 'experiment-detach': []
120
- 'update:modelValue': [values: Record<string, unknown>]
121
- 'update:values': [values: Record<string, unknown>]
122
- 'update:sidebarCollapsed': [value: boolean]
123
- 'update:toggle': [sectionId: string, value: boolean]
124
- 'section-action': [sectionId: string, actionId: string]
125
- 'form-submit': [sectionId: string, values: Record<string, unknown>]
126
- 'form-cancel': [sectionId: string]
127
- }>()
99
+ const emit = defineEmits<PluginWorkspaceViewEmits>()
128
100
 
129
101
  defineSlots<{
130
102
  default?: (props: PluginWorkspaceSlotProps) => unknown
@@ -158,82 +130,19 @@ if (props.experimentShell) {
158
130
  })
159
131
  }
160
132
 
161
- const topBarSlotNames = computed<string[]>(() =>
162
- getPluginWorkspaceTopBarSlotNames(slots),
163
- )
164
-
165
- const sidebarSlotNames = computed<string[]>(() =>
166
- getPluginWorkspaceSidebarSlotNames(slots),
167
- )
168
-
169
133
  const {
170
- generatedPillNav,
171
- generatedDefaultView,
172
- internalControlValues,
173
- resolvedComponentBindings,
174
- resolvedComponentBindingsById,
175
- resolvedComponentProps,
176
- resolvedComponentPropsById,
134
+ shell,
135
+ sidebar,
136
+ controls,
177
137
  handleControlValuesUpdate,
178
- } = usePluginWorkspaceControls({
179
- model: () => props.model,
180
- controls: () => props.controls,
181
- controlOptions: () => props.controlOptions,
182
- modelValue: () => props.modelValue,
183
- values: () => props.values,
184
- componentBindings: () => props.componentBindings,
185
- componentProps: () => props.componentProps,
186
- componentPropsById: () => props.componentPropsById,
187
- emitValues: (values) => {
188
- emit('update:modelValue', values)
189
- emit('update:values', values)
190
- },
191
- })
192
-
193
- const {
194
- resolvedPillNav,
195
- resolvedActiveView,
196
- resolvedCurrentPillId,
197
- resolvedCurrentPageSelectorId,
198
- setActiveView,
199
138
  handlePageSelectorSelect,
200
139
  handlePillSelect,
201
- } = usePluginWorkspaceNavigation({
202
- activeView: () => props.activeView,
203
- defaultActiveView: () => props.defaultActiveView,
204
- currentPillId: () => props.currentPillId,
205
- currentPageSelectorId: () => props.currentPageSelectorId,
206
- pillNav: () => props.pillNav,
207
- pageSelector: () => props.pageSelector,
208
- panelIds: () => Object.keys(props.panels),
209
- generatedPillNav,
210
- generatedDefaultView,
211
- emitActiveView: (viewId) => emit('update:activeView', viewId),
212
- emitPageSelectorSelect: (page) => emit('page-selector-select', page),
213
- emitPillSelect: (item) => emit('pill-select', item),
140
+ } = usePluginWorkspace({
141
+ props,
142
+ slots,
143
+ emit,
214
144
  })
215
145
 
216
- const hasSidebarSurface = computed(() => {
217
- return hasPluginWorkspaceSidebarSurface({
218
- showSidebar: props.showSidebar,
219
- panels: props.panels,
220
- forms: props.forms,
221
- model: props.model,
222
- controls: props.controls,
223
- showSidebarWhenEmpty: props.showSidebarWhenEmpty,
224
- sidebarContentId: props.sidebarContentId,
225
- sidebarTitle: props.sidebarTitle,
226
- sidebarSubtitle: props.sidebarSubtitle,
227
- sidebarBadge: props.sidebarBadge,
228
- hasSidebarSlot: Boolean(slots.sidebar),
229
- sidebarSlotCount: sidebarSlotNames.value.length,
230
- })
231
- })
232
-
233
- function handlePillOptionSelect(option: PillNavOption, item: PillNavItem) {
234
- emit('pill-option-select', option, item)
235
- }
236
-
237
146
  function handleToggle(sectionId: string, value: boolean) {
238
147
  emit('update:toggle', sectionId, value)
239
148
  }
@@ -249,126 +158,48 @@ function handleFormCancel(sectionId: string) {
249
158
  </script>
250
159
 
251
160
  <template>
252
- <AppLayout
253
- class="mint-plugin-workspace-view"
254
- :sidebar-position="sidebarPosition"
255
- :sidebar-width="sidebarWidth"
256
- :floating="floating"
257
- :responsive-sidebar="responsiveSidebar"
258
- :sidebar-toggle-label="sidebarToggleLabel"
259
- :sidebar-close-label="sidebarCloseLabel"
161
+ <WorkspaceShellInternal
162
+ :shell="shell"
163
+ @page-selector-select="handlePageSelectorSelect"
164
+ @pill-select="handlePillSelect"
165
+ @pill-option-select="(option: PillNavOption, item: PillNavItem) => emit('pill-option-select', option, item)"
166
+ @plugin-switcher-select="(plugin: PluginSwitcherPlugin) => emit('plugin-switcher-select', plugin)"
167
+ @plugin-switcher-install="emit('plugin-switcher-install')"
168
+ @account-menu-select="(item: AccountMenuItem) => emit('account-menu-select', item)"
169
+ @sign-out="emit('sign-out')"
170
+ @notifications-click="emit('notifications-click')"
171
+ @settings-values-change="emit('settings-values-change', $event)"
172
+ @admin-click="emit('admin-click')"
260
173
  >
261
- <template #topbar>
262
- <slot
263
- name="topbar"
264
- :active-view="resolvedActiveView"
265
- :set-active-view="setActiveView"
266
- :values="internalControlValues"
267
- :component-bindings="resolvedComponentBindings"
268
- :component-bindings-by-id="resolvedComponentBindingsById"
269
- :component-props="resolvedComponentProps"
270
- :component-props-by-id="resolvedComponentPropsById"
271
- >
272
- <AppTopBar
273
- :title="title"
274
- :subtitle="subtitle"
275
- :variant="topBarVariant"
276
- :home-path="homePath"
277
- :show-logo="showLogo"
278
- :page-selector="pageSelector"
279
- :current-page-selector-id="resolvedCurrentPageSelectorId"
280
- :plugin-switcher="pluginSwitcher"
281
- :pill-nav="resolvedPillNav"
282
- :current-pill-id="resolvedCurrentPillId"
283
- :show-theme-toggle="showThemeToggle"
284
- :show-settings="showSettings"
285
- :settings-config="settingsConfig"
286
- :show-standalone-label="showStandaloneLabel"
287
- :standalone-label="standaloneLabel"
288
- :account-menu="accountMenu"
289
- :show-notifications="showNotifications"
290
- :has-notification-dot="hasNotificationDot"
291
- :show-admin="showAdmin"
292
- :admin-path="adminPath"
293
- :user-name="userName"
294
- :user-initial="userInitial"
295
- :user-email="userEmail"
296
- @page-selector-select="handlePageSelectorSelect"
297
- @pill-select="handlePillSelect"
298
- @pill-option-select="handlePillOptionSelect"
299
- @plugin-switcher-select="emit('plugin-switcher-select', $event)"
300
- @plugin-switcher-install="emit('plugin-switcher-install')"
301
- @account-menu-select="emit('account-menu-select', $event)"
302
- @sign-out="emit('sign-out')"
303
- @notifications-click="emit('notifications-click')"
304
- @settings-values-change="emit('settings-values-change', $event)"
305
- @admin-click="emit('admin-click')"
306
- >
307
- <template
308
- v-for="slotName in topBarSlotNames"
309
- :key="slotName"
310
- #[slotName]="slotProps"
311
- >
312
- <!-- @vue-ignore dynamic SDK slot forwarding for AppTopBar passthrough slots -->
313
- <slot
314
- :name="slotName"
315
- v-bind="slotProps ?? {}"
316
- />
317
- </template>
318
- </AppTopBar>
319
- </slot>
174
+ <template v-if="slots.topbar" #topbar="scope">
175
+ <slot name="topbar" v-bind="scope" />
320
176
  </template>
321
177
 
322
- <template v-if="hasSidebarSurface" #sidebar>
178
+ <template
179
+ v-for="slotName in shell.topBarSlotNames"
180
+ :key="slotName"
181
+ #[slotName]="slotProps"
182
+ >
183
+ <!-- @vue-ignore dynamic SDK slot forwarding for AppTopBar passthrough slots -->
323
184
  <slot
324
- name="sidebar"
325
- :active-view="resolvedActiveView"
326
- :set-active-view="setActiveView"
327
- :values="internalControlValues"
328
- :component-bindings="resolvedComponentBindings"
329
- :component-bindings-by-id="resolvedComponentBindingsById"
330
- :component-props="resolvedComponentProps"
331
- :component-props-by-id="resolvedComponentPropsById"
332
- >
333
- <AppSidebar
334
- :title="sidebarTitle"
335
- :subtitle="sidebarSubtitle"
336
- :badge="sidebarBadge"
337
- :variant="sidebarVariant"
338
- :panels="panels"
339
- :active-view="resolvedActiveView"
340
- :floating="false"
341
- :dense="dense"
342
- :width="sidebarWidth"
343
- :side="sidebarPosition"
344
- :forms="forms"
345
- :content-id="sidebarContentId"
346
- :show-when-empty="showSidebarWhenEmpty || sidebarContentId !== undefined"
347
- :model="model"
348
- :controls="controls"
349
- :control-options="controlOptions"
350
- :values="internalControlValues"
351
- :form-enhancements="formEnhancements"
352
- :show-form-actions="showFormActions"
353
- :form-loading="formLoading"
354
- :form-disabled="formDisabled"
355
- :form-readonly="formReadonly"
356
- :form-size="formSize"
357
- :collapsible="sidebarCollapsible"
358
- :collapsed="sidebarCollapsed"
359
- :default-collapsed="defaultSidebarCollapsed"
360
- :collapsed-width="sidebarCollapsedWidth"
361
- :collapse-button-label="sidebarCollapseButtonLabel"
362
- :expand-button-label="sidebarExpandButtonLabel"
185
+ :name="slotName"
186
+ v-bind="slotProps ?? {}"
187
+ />
188
+ </template>
189
+
190
+ <template v-if="shell.hasSidebarSurface" #sidebar="scope">
191
+ <slot name="sidebar" v-bind="scope">
192
+ <WorkspaceSidebarInternal
193
+ :sidebar="sidebar"
363
194
  @update:model-value="handleControlValuesUpdate"
364
195
  @update:collapsed="emit('update:sidebarCollapsed', $event)"
365
196
  @update:toggle="handleToggle"
366
- @section-action="(sectionId, actionId) => emit('section-action', sectionId, actionId)"
197
+ @section-action="(sectionId: string, actionId: string) => emit('section-action', sectionId, actionId)"
367
198
  @form-submit="handleFormSubmit"
368
199
  @form-cancel="handleFormCancel"
369
200
  >
370
201
  <template
371
- v-for="slotName in sidebarSlotNames"
202
+ v-for="slotName in sidebar.slotNames"
372
203
  :key="slotName"
373
204
  #[slotName]="slotProps"
374
205
  >
@@ -378,18 +209,16 @@ function handleFormCancel(sectionId: string) {
378
209
  v-bind="slotProps ?? {}"
379
210
  />
380
211
  </template>
381
- </AppSidebar>
212
+ </WorkspaceSidebarInternal>
382
213
  </slot>
383
214
  </template>
384
215
 
385
- <slot
386
- :active-view="resolvedActiveView"
387
- :set-active-view="setActiveView"
388
- :values="internalControlValues"
389
- :component-bindings="resolvedComponentBindings"
390
- :component-bindings-by-id="resolvedComponentBindingsById"
391
- :component-props="resolvedComponentProps"
392
- :component-props-by-id="resolvedComponentPropsById"
393
- />
394
- </AppLayout>
216
+ <template #default>
217
+ <WorkspaceControlsInternal :controls="controls">
218
+ <template #default="controlsScope">
219
+ <slot v-bind="controlsScope" />
220
+ </template>
221
+ </WorkspaceControlsInternal>
222
+ </template>
223
+ </WorkspaceShellInternal>
395
224
  </template>
@@ -111,7 +111,3 @@ const segmentCount = computed(() => props.steps.length || 0)
111
111
  </div>
112
112
  </div>
113
113
  </template>
114
-
115
- <style>
116
- @import '../styles/components/progress-bar.css';
117
- </style>
@@ -53,6 +53,7 @@ const selectedTemplateId = ref<string | null>(null)
53
53
  const {
54
54
  isOpen: templateDropdownOpen,
55
55
  rootRef: templateDropdownRef,
56
+ triggerRef: templateTriggerRef,
56
57
  close: closeTemplateDropdown,
57
58
  toggle: toggleTemplateDropdown,
58
59
  } = useDropdownState()
@@ -204,6 +205,7 @@ watch(
204
205
  <label class="mint-protocol-editor__template-label">Template</label>
205
206
  <div ref="templateDropdownRef" class="mint-protocol-editor__template-dropdown">
206
207
  <button
208
+ ref="templateTriggerRef"
207
209
  type="button"
208
210
  class="mint-protocol-editor__template-btn"
209
211
  @click="toggleTemplateDropdown"
@@ -376,7 +378,3 @@ watch(
376
378
  </div>
377
379
  </div>
378
380
  </template>
379
-
380
- <style>
381
- @import '../styles/components/protocol-step-editor.css';
382
- </style>
@@ -1,7 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  /** Multi-rack editor for managing collections of well-plate racks with add/remove/reorder controls and per-well editing. */
3
- import { ref, computed, watch } from 'vue'
3
+ import { computed, watch, type ComponentPublicInstance } from 'vue'
4
+ import { SLOT_COLORS, SLOT_ORDER } from '../utils/color'
4
5
  import type { Rack, SlotPosition, WellPlateFormat, WellPlateSize, WellEditData, Well, WellEditField, WellSampleDropData, RackSampleDropMapper } from '../types'
6
+ import { useListReorder } from '../composables/useListReorder'
5
7
  import { useRackEditor } from '../composables/useRackEditor'
6
8
  import WellPlate from './WellPlate.vue'
7
9
 
@@ -104,23 +106,48 @@ watch(
104
106
  },
105
107
  )
106
108
 
107
- const SLOT_COLORS: Record<SlotPosition, string> = {
108
- R: '#ef4444',
109
- G: '#22c55e',
110
- B: '#3b82f6',
111
- Y: '#eab308',
112
- }
113
-
114
- const SLOT_OPTIONS: SlotPosition[] = ['R', 'G', 'B', 'Y']
109
+ const SLOT_OPTIONS: readonly SlotPosition[] = SLOT_ORDER
115
110
 
116
111
  const FORMAT_OPTIONS: { value: WellPlateFormat; label: string }[] = [
117
112
  { value: 54, label: '54' },
118
113
  { value: 96, label: '96' },
119
114
  ]
120
115
 
121
- // Drag state for rack reordering
122
- const draggingRackId = ref<string | null>(null)
123
- const dragOverRackId = ref<string | null>(null)
116
+ // Rack tab reordering: HTML5 drag for pointers, Alt+Arrow for the keyboard,
117
+ // both owned by the shared reorder composable.
118
+ const tabRefs = new Map<string, HTMLElement>()
119
+
120
+ const {
121
+ draggingId: draggingRackId,
122
+ dragOverId: dragOverRackId,
123
+ announcement: reorderAnnouncement,
124
+ announcerStyle,
125
+ onDragStart: onRackDragStart,
126
+ onDragOver: onRackDragOver,
127
+ onDragLeave: onRackDragLeave,
128
+ onDrop: onRackDrop,
129
+ onDragEnd: onRackDragEnd,
130
+ onKeydown: onRackReorderKeydown,
131
+ } = useListReorder<Rack>({
132
+ items: () => editor.racks.value,
133
+ getId: (rack) => rack.id,
134
+ getLabel: (rack) => rack.name,
135
+ disabled: () => !props.allowReorder || props.readonly,
136
+ getElement: (id) => tabRefs.get(id),
137
+ itemNoun: 'Rack',
138
+ onReorder: ({ fromIndex, toIndex }) => {
139
+ editor.reorderRacks(fromIndex, toIndex)
140
+ emit('rack-reorder', editor.racks.value.map(r => r.id))
141
+ },
142
+ })
143
+
144
+ function setTabRef(id: string, el: Element | ComponentPublicInstance | null) {
145
+ if (el instanceof HTMLElement) {
146
+ tabRefs.set(id, el)
147
+ } else {
148
+ tabRefs.delete(id)
149
+ }
150
+ }
124
151
 
125
152
  function handleAddRack() {
126
153
  const rack = editor.addRack()
@@ -136,54 +163,6 @@ function handleTabClick(rackId: string) {
136
163
  editor.setActiveRack(rackId)
137
164
  }
138
165
 
139
- // Rack drag handlers
140
- function handleRackDragStart(event: DragEvent, rackId: string) {
141
- if (!props.allowReorder) return
142
- draggingRackId.value = rackId
143
- if (event.dataTransfer) {
144
- event.dataTransfer.effectAllowed = 'move'
145
- event.dataTransfer.setData('text/plain', rackId)
146
- }
147
- }
148
-
149
- function handleRackDragOver(event: DragEvent, rackId: string) {
150
- event.preventDefault()
151
- if (draggingRackId.value && draggingRackId.value !== rackId) {
152
- dragOverRackId.value = rackId
153
- }
154
- }
155
-
156
- function handleRackDragLeave() {
157
- dragOverRackId.value = null
158
- }
159
-
160
- function handleRackDrop(event: DragEvent, targetRackId: string) {
161
- event.preventDefault()
162
- if (!draggingRackId.value || draggingRackId.value === targetRackId) {
163
- clearDragState()
164
- return
165
- }
166
-
167
- const fromIndex = editor.racks.value.findIndex(r => r.id === draggingRackId.value)
168
- const toIndex = editor.racks.value.findIndex(r => r.id === targetRackId)
169
-
170
- if (fromIndex !== -1 && toIndex !== -1) {
171
- editor.reorderRacks(fromIndex, toIndex)
172
- emit('rack-reorder', editor.racks.value.map(r => r.id))
173
- }
174
-
175
- clearDragState()
176
- }
177
-
178
- function handleRackDragEnd() {
179
- clearDragState()
180
- }
181
-
182
- function clearDragState() {
183
- draggingRackId.value = null
184
- dragOverRackId.value = null
185
- }
186
-
187
166
  // Toolbar actions
188
167
  function handleFormatChange(format: WellPlateFormat) {
189
168
  if (!editor.activeRack.value) return
@@ -281,9 +260,11 @@ const activeRackWells = computed(() => editor.activeRack.value?.wells ?? {})
281
260
  <div class="mint-rack-editor__tabs">
282
261
  <div class="mint-rack-editor__tabs-inner">
283
262
  <button
284
- v-for="rack in editor.racks.value"
263
+ v-for="(rack, rackIndex) in editor.racks.value"
285
264
  :key="rack.id"
265
+ :ref="(el) => setTabRef(rack.id, el)"
286
266
  :draggable="allowReorder && !readonly"
267
+ :title="allowReorder && !readonly ? `${rack.name} — press Alt with the arrow keys to reorder` : undefined"
287
268
  :class="[
288
269
  'mint-rack-editor__tab',
289
270
  {
@@ -293,11 +274,12 @@ const activeRackWells = computed(() => editor.activeRack.value?.wells ?? {})
293
274
  },
294
275
  ]"
295
276
  @click="handleTabClick(rack.id)"
296
- @dragstart="handleRackDragStart($event, rack.id)"
297
- @dragover="handleRackDragOver($event, rack.id)"
298
- @dragleave="handleRackDragLeave"
299
- @drop="handleRackDrop($event, rack.id)"
300
- @dragend="handleRackDragEnd"
277
+ @keydown="onRackReorderKeydown($event, rack, rackIndex)"
278
+ @dragstart="onRackDragStart($event, rack, rackIndex)"
279
+ @dragover="onRackDragOver($event, rack, rackIndex)"
280
+ @dragleave="onRackDragLeave"
281
+ @drop="onRackDrop($event, rack, rackIndex)"
282
+ @dragend="onRackDragEnd"
301
283
  >
302
284
  <span
303
285
  class="mint-rack-editor__slot-dot"
@@ -434,9 +416,8 @@ const activeRackWells = computed(() => editor.activeRack.value?.wells ?? {})
434
416
  </template>
435
417
  </WellPlate>
436
418
  </div>
419
+
420
+ <!-- Reorder announcements for screen readers -->
421
+ <div role="status" aria-live="polite" :style="announcerStyle">{{ reorderAnnouncement }}</div>
437
422
  </div>
438
423
  </template>
439
-
440
- <style>
441
- @import '../styles/components/rack-editor.css';
442
- </style>
@@ -412,7 +412,3 @@ function formatLevel(value: number): string {
412
412
  </div>
413
413
  </div>
414
414
  </template>
415
-
416
- <style>
417
- @import '../styles/components/reagent-editor.css';
418
- </style>
@@ -76,6 +76,8 @@ const mockReagents: Reagent[] = [
76
76
 
77
77
  function initState() {
78
78
  return {
79
+ loading: false,
80
+ error: '',
79
81
  modelValue: mockReagents,
80
82
  readonly: false,
81
83
  showStockLevel: true,
@@ -92,6 +94,8 @@ function initState() {
92
94
  <template #default="{ state }">
93
95
  <div style="padding: 2rem;">
94
96
  <ReagentList
97
+ :loading="state.loading"
98
+ :error="state.error || null"
95
99
  v-model="state.modelValue"
96
100
  :readonly="state.readonly"
97
101
  :show-stock-level="state.showStockLevel"
@@ -102,6 +106,8 @@ function initState() {
102
106
  </div>
103
107
  </template>
104
108
  <template #controls="{ state }">
109
+ <HstCheckbox v-model="state.loading" title="Loading" />
110
+ <HstText v-model="state.error" title="Error Message" />
105
111
  <HstCheckbox v-model="state.readonly" title="Readonly" />
106
112
  <HstCheckbox v-model="state.showStockLevel" title="Show Stock Level" />
107
113
  <HstSlider v-model="state.lowStockThreshold" title="Low Stock Threshold (%)" :min="0" :max="50" />
@@ -133,5 +139,17 @@ function initState() {
133
139
  <ReagentList :model-value="[]" />
134
140
  </div>
135
141
  </Variant>
142
+
143
+ <Variant title="Loading">
144
+ <div style="padding: 2rem;">
145
+ <ReagentList :model-value="[]" loading />
146
+ </div>
147
+ </Variant>
148
+
149
+ <Variant title="Error">
150
+ <div style="padding: 2rem;">
151
+ <ReagentList :model-value="[]" error="Could not load reagents." />
152
+ </div>
153
+ </Variant>
136
154
  </Story>
137
155
  </template>