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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (402) hide show
  1. package/README.md +60 -0
  2. package/dist/{ExperimentPopover-CfOdh5Rm.js → ExperimentPopover-CJdNlCZb.js} +24 -15
  3. package/dist/ExperimentPopover-CJdNlCZb.js.map +1 -0
  4. package/dist/{ExperimentSelectorModal-DvIagrxY.js → ExperimentSelectorModal-DCXmwKNS.js} +5 -5
  5. package/dist/ExperimentSelectorModal-DCXmwKNS.js.map +1 -0
  6. package/dist/{SettingsModal-W3TYfnP9.js → SettingsModal-BYR20I4c.js} +5 -5
  7. package/dist/SettingsModal-BYR20I4c.js.map +1 -0
  8. package/dist/{EmptyState-NBqf2_8z.js → Skeleton-BAF3CKY7.js} +111 -93
  9. package/dist/Skeleton-BAF3CKY7.js.map +1 -0
  10. package/dist/__tests__/components/DataStates.test.d.ts +1 -0
  11. package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
  12. package/dist/__tests__/components/FileBrowserModal.test.d.ts +1 -0
  13. package/dist/__tests__/components/FilePicker.state.test.d.ts +1 -0
  14. package/dist/__tests__/components/FilePicker.test.d.ts +1 -0
  15. package/dist/__tests__/components/PlotlyChart.test.d.ts +1 -0
  16. package/dist/__tests__/components/iconButtonIconSizing.test.d.ts +1 -0
  17. package/dist/__tests__/composables/useFileBrowser.test.d.ts +1 -0
  18. package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
  19. package/dist/__tests__/composables/usePlatformFilePickerAdapter.test.d.ts +1 -0
  20. package/dist/__tests__/styles/cssTokens.test.d.ts +1 -0
  21. package/dist/apiErrors.d.ts +26 -0
  22. package/dist/components/ActionMenu.vue.d.ts +1 -1
  23. package/dist/components/AppPluginSwitcher.vue.d.ts +2 -0
  24. package/dist/components/AuditTrail.vue.d.ts +13 -0
  25. package/dist/components/BaseInput.vue.d.ts +1 -1
  26. package/dist/components/BaseSelect.vue.d.ts +1 -1
  27. package/dist/components/BaseTextarea.vue.d.ts +1 -1
  28. package/dist/components/BatchProgressList.vue.d.ts +17 -0
  29. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
  30. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +2 -2
  31. package/dist/components/ConcentrationInput.vue.d.ts +1 -1
  32. package/dist/components/DataFrame.vue.d.ts +14 -2
  33. package/dist/components/DatePicker.vue.d.ts +2 -2
  34. package/dist/components/DateTimePicker.vue.d.ts +3 -1
  35. package/dist/components/ExperimentDataViewer.vue.d.ts +26 -1
  36. package/dist/components/ExperimentPopover.vue.d.ts +1 -0
  37. package/dist/components/ExperimentTimeline.vue.d.ts +29 -1
  38. package/dist/components/FileBrowserModal.vue.d.ts +99 -0
  39. package/dist/components/FilePicker.vue.d.ts +32 -0
  40. package/dist/components/FileUploader.vue.d.ts +5 -2
  41. package/dist/components/FormulaInput.vue.d.ts +1 -1
  42. package/dist/components/GeneratedPlotlyResult.vue.d.ts +1 -3
  43. package/dist/components/LcmsSequenceTable.vue.d.ts +29 -1
  44. package/dist/components/MobileSupportGate.vue.d.ts +1 -1
  45. package/dist/components/MoleculeInput.vue.d.ts +1 -1
  46. package/dist/components/NumberInput.vue.d.ts +1 -1
  47. package/dist/components/PlotlyChart.vue.d.ts +59 -0
  48. package/dist/components/ProgressBar.vue.d.ts +1 -1
  49. package/dist/components/ProtocolStepEditor.vue.d.ts +1 -0
  50. package/dist/components/ReagentList.vue.d.ts +29 -1
  51. package/dist/components/ResourceCard.vue.d.ts +1 -1
  52. package/dist/components/SampleHierarchyTree.vue.d.ts +29 -1
  53. package/dist/components/SampleSelector.drag.d.ts +13 -0
  54. package/dist/components/SampleSelector.tree.d.ts +2 -0
  55. package/dist/components/SampleSelector.vue.d.ts +34 -1
  56. package/dist/components/ScheduleCalendar.vue.d.ts +20 -0
  57. package/dist/components/SecretInput.vue.d.ts +1 -1
  58. package/dist/components/SequenceInput.vue.d.ts +1 -1
  59. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -0
  60. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  61. package/dist/components/TagsInput.vue.d.ts +1 -1
  62. package/dist/components/TimePicker.vue.d.ts +2 -1
  63. package/dist/components/TimeRangeInput.vue.d.ts +1 -1
  64. package/dist/components/UnitInput.vue.d.ts +1 -1
  65. package/dist/components/WellPlate.geometry.d.ts +15 -0
  66. package/dist/components/WellPlate.interaction.d.ts +13 -0
  67. package/dist/components/WellPlate.vue.d.ts +30 -1
  68. package/dist/components/fileBrowserPresentation.d.ts +10 -0
  69. package/dist/components/index.d.ts +4 -0
  70. package/dist/components/index.js +8 -8
  71. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -0
  72. package/dist/components/internal/DataStateInternal.vue.d.ts +43 -0
  73. package/dist/components/internal/FileBrowserDialogInternal.vue.d.ts +34 -0
  74. package/dist/components/internal/FileBrowserListingInternal.vue.d.ts +25 -0
  75. package/dist/components/internal/FileBrowserToolbarInternal.vue.d.ts +26 -0
  76. package/dist/components/internal/FilePickerPreviewInternal.vue.d.ts +18 -0
  77. package/dist/components/internal/FilePickerTreeInternal.vue.d.ts +31 -0
  78. package/dist/{components-B80gV0P6.js → components-DIum5hkx.js} +5415 -2753
  79. package/dist/components-DIum5hkx.js.map +1 -0
  80. package/dist/composables/controlSchemaTypes.d.ts +34 -0
  81. package/dist/composables/filePicker/localFiles.d.ts +3 -0
  82. package/dist/composables/filePicker/usePickerNavigation.d.ts +96 -0
  83. package/dist/composables/filePicker/usePickerSelection.d.ts +31 -0
  84. package/dist/composables/filePicker/validation.d.ts +5 -0
  85. package/dist/composables/index.d.ts +3 -0
  86. package/dist/composables/index.js +10 -10
  87. package/dist/composables/useApi.d.ts +14 -7
  88. package/dist/composables/useDropdownState.d.ts +18 -2
  89. package/dist/composables/useEventStream.d.ts +9 -0
  90. package/dist/composables/useFileBrowser.d.ts +55 -0
  91. package/dist/composables/useFilePicker.d.ts +189 -0
  92. package/dist/composables/useListReorder.d.ts +90 -0
  93. package/dist/composables/useMenuKeyboard.d.ts +2 -2
  94. package/dist/composables/usePlatformFilePickerAdapter.d.ts +11 -0
  95. package/dist/composables/usePluginConfig.d.ts +3 -1
  96. package/dist/composables/useRequestSyncState.d.ts +42 -2
  97. package/dist/composables/useScheduleDrag.d.ts +1 -0
  98. package/dist/{composables-C8mmD5ta.js → composables-mFXsTA4g.js} +307 -74
  99. package/dist/composables-mFXsTA4g.js.map +1 -0
  100. package/dist/{useApi-CDBq6z23.js → errors-BLuqMee5.js} +178 -16
  101. package/dist/errors-BLuqMee5.js.map +1 -0
  102. package/dist/{experiment-Y9Iz7KYi.js → experiment-CVDRAnqp.js} +3 -8
  103. package/dist/experiment-CVDRAnqp.js.map +1 -0
  104. package/dist/{experiment-utils-DV3DrvE_.js → experiment-utils-CV2Wza3z.js} +1 -1
  105. package/dist/experiment-utils-CV2Wza3z.js.map +1 -0
  106. package/dist/index.d.ts +1 -0
  107. package/dist/index.js +15 -15
  108. package/dist/install.js +2 -7
  109. package/dist/install.js.map +1 -1
  110. package/dist/permissions.d.ts +42 -1
  111. package/dist/permissions.js +20 -10
  112. package/dist/permissions.js.map +1 -1
  113. package/dist/stores/index.d.ts +1 -1
  114. package/dist/stores/index.js +3 -3
  115. package/dist/stores/settings.d.ts +31 -1
  116. package/dist/styles.css +15162 -32475
  117. package/dist/templates/index.js +2 -4
  118. package/dist/{templates-CK72aKO1.js → templates-CmGfmBRA.js} +291 -85
  119. package/dist/templates-CmGfmBRA.js.map +1 -0
  120. package/dist/types/fileBrowserTypes.d.ts +77 -0
  121. package/dist/types/filePicker.d.ts +77 -0
  122. package/dist/types/index.d.ts +2 -0
  123. package/dist/{useControlSchema-_OVhavKq.js → useControlSchema-DldpxNOP.js} +234 -1121
  124. package/dist/useControlSchema-DldpxNOP.js.map +1 -0
  125. package/dist/{useDropdownState-D5G4UvWd.js → useDropdownState-C7hRiVQf.js} +22 -6
  126. package/dist/useDropdownState-C7hRiVQf.js.map +1 -0
  127. package/dist/{useExperimentSelector-DSEckf_g.js → useExperimentSelector-CEcIaueJ.js} +4 -4
  128. package/dist/{useExperimentSelector-DSEckf_g.js.map → useExperimentSelector-CEcIaueJ.js.map} +1 -1
  129. package/dist/{useFormBuilder-Bd714C7y.js → useFormBuilder-z5L_dUIy.js} +2 -2
  130. package/dist/{useFormBuilder-Bd714C7y.js.map → useFormBuilder-z5L_dUIy.js.map} +1 -1
  131. package/dist/utils/color.d.ts +13 -0
  132. package/dist/utils/errors.d.ts +9 -0
  133. package/dist/{useProtocolTemplates-Dq5Pzwyd.js → validation-uwoJGEb6.js} +231 -158
  134. package/dist/validation-uwoJGEb6.js.map +1 -0
  135. package/package.json +1 -1
  136. package/src/__tests__/apiErrors.test.ts +81 -0
  137. package/src/__tests__/components/ActionMenu.test.ts +94 -0
  138. package/src/__tests__/components/DataFrame.test.ts +183 -11
  139. package/src/__tests__/components/DataStates.test.ts +176 -0
  140. package/src/__tests__/components/DatePicker.test.ts +76 -0
  141. package/src/__tests__/components/DateTimePicker.test.ts +36 -0
  142. package/src/__tests__/components/ExperimentPopover.test.ts +30 -0
  143. package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
  144. package/src/__tests__/components/FileBrowserModal.test.ts +460 -0
  145. package/src/__tests__/components/FilePicker.state.test.ts +287 -0
  146. package/src/__tests__/components/FilePicker.test.ts +487 -0
  147. package/src/__tests__/components/GeneratedPlotlyResult.test.ts +52 -5
  148. package/src/__tests__/components/GroupAssigner.test.ts +53 -0
  149. package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
  150. package/src/__tests__/components/PlotlyChart.test.ts +305 -0
  151. package/src/__tests__/components/RackEditor.test.ts +35 -0
  152. package/src/__tests__/components/ReagentList.test.ts +64 -2
  153. package/src/__tests__/components/RetiredStyles.test.ts +31 -0
  154. package/src/__tests__/components/SampleSelector.drag.test.ts +51 -1
  155. package/src/__tests__/components/ScheduleCalendar.test.ts +63 -0
  156. package/src/__tests__/components/SegmentedControl.test.ts +107 -0
  157. package/src/__tests__/components/TimePicker.test.ts +61 -0
  158. package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
  159. package/src/__tests__/components/iconButtonIconSizing.test.ts +18 -0
  160. package/src/__tests__/composables/useApi.test.ts +92 -9
  161. package/src/__tests__/composables/useDropdownState.test.ts +58 -1
  162. package/src/__tests__/composables/useFileBrowser.test.ts +305 -0
  163. package/src/__tests__/composables/useGeneratedAnalysis.test.ts +7 -2
  164. package/src/__tests__/composables/useListReorder.test.ts +145 -0
  165. package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +67 -0
  166. package/src/__tests__/composables/usePluginClient.test.ts +3 -0
  167. package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
  168. package/src/__tests__/stores/settings.test.ts +71 -1
  169. package/src/__tests__/styles/cssTokens.test.ts +131 -0
  170. package/src/__tests__/utils/permissions.test.ts +68 -0
  171. package/src/apiErrors.ts +85 -0
  172. package/src/components/ActionMenu.vue +45 -88
  173. package/src/components/AlertBox.vue +0 -4
  174. package/src/components/AppAvatarMenu.vue +2 -14
  175. package/src/components/AppLayout.vue +0 -4
  176. package/src/components/AppPluginSwitcher.story.vue +87 -0
  177. package/src/components/AppPluginSwitcher.vue +2 -5
  178. package/src/components/AppSidebar.vue +0 -4
  179. package/src/components/AppToastContainer.vue +6 -8
  180. package/src/components/AppTopBar.vue +0 -4
  181. package/src/components/ArtifactSaveDialog.vue +0 -4
  182. package/src/components/ArtifactSelectorModal.vue +0 -4
  183. package/src/components/AuditTrail.story.vue +18 -0
  184. package/src/components/AuditTrail.vue +27 -7
  185. package/src/components/AutoGroupModal.vue +0 -4
  186. package/src/components/Avatar.vue +0 -4
  187. package/src/components/BaseButton.vue +0 -4
  188. package/src/components/BaseCheckbox.vue +0 -4
  189. package/src/components/BaseInput.vue +0 -4
  190. package/src/components/BaseModal.vue +0 -4
  191. package/src/components/BasePill.vue +0 -4
  192. package/src/components/BaseRadioGroup.vue +0 -4
  193. package/src/components/BaseSelect.vue +0 -4
  194. package/src/components/BaseSlider.vue +0 -4
  195. package/src/components/BaseTabs.vue +0 -4
  196. package/src/components/BaseTextarea.vue +0 -4
  197. package/src/components/BaseToggle.vue +0 -4
  198. package/src/components/BatchProgressList.story.vue +18 -0
  199. package/src/components/BatchProgressList.vue +32 -4
  200. package/src/components/Breadcrumb.vue +0 -4
  201. package/src/components/Calendar.vue +0 -4
  202. package/src/components/ChartContainer.story.vue +1 -1
  203. package/src/components/ChartContainer.vue +0 -4
  204. package/src/components/ChemicalFormula.vue +0 -4
  205. package/src/components/CollapsibleCard.vue +0 -4
  206. package/src/components/ColorSlider.vue +0 -4
  207. package/src/components/ConcentrationInput.vue +0 -4
  208. package/src/components/ConfirmDialog.vue +0 -4
  209. package/src/components/DataFrame.story.vue +14 -0
  210. package/src/components/DataFrame.vue +97 -23
  211. package/src/components/DatePicker.vue +73 -27
  212. package/src/components/DateTimePicker.vue +45 -17
  213. package/src/components/Divider.vue +0 -4
  214. package/src/components/DoseCalculator.vue +0 -4
  215. package/src/components/DropdownButton.vue +2 -14
  216. package/src/components/EmptyState.vue +0 -4
  217. package/src/components/ExperimentCodeBadge.vue +0 -4
  218. package/src/components/ExperimentDataViewer.story.vue +3 -0
  219. package/src/components/ExperimentDataViewer.vue +33 -13
  220. package/src/components/ExperimentPopover.vue +12 -7
  221. package/src/components/ExperimentSelectorModal.vue +0 -4
  222. package/src/components/ExperimentTimeline.story.vue +18 -0
  223. package/src/components/ExperimentTimeline.vue +81 -75
  224. package/src/components/FileBrowserModal.story.vue +203 -0
  225. package/src/components/FileBrowserModal.vue +384 -0
  226. package/src/components/FilePicker.story.vue +44 -0
  227. package/src/components/FilePicker.vue +284 -0
  228. package/src/components/FileUploader.vue +32 -18
  229. package/src/components/FitPanel.vue +0 -4
  230. package/src/components/FormActions.story.vue +96 -0
  231. package/src/components/FormActions.vue +0 -4
  232. package/src/components/FormBuilder.vue +0 -4
  233. package/src/components/FormField.vue +0 -4
  234. package/src/components/FormulaInput.vue +0 -4
  235. package/src/components/GeneratedPathInput.story.vue +68 -0
  236. package/src/components/GeneratedPlotlyResult.story.vue +96 -0
  237. package/src/components/GeneratedPlotlyResult.vue +34 -69
  238. package/src/components/GeneratedPluginView.story.vue +145 -0
  239. package/src/components/GeneratedPluginView.vue +10 -10
  240. package/src/components/GeneratedResultRenderer.story.vue +117 -0
  241. package/src/components/GeneratedResultRenderer.vue +4 -9
  242. package/src/components/GroupAssigner.vue +67 -5
  243. package/src/components/IconButton.vue +0 -4
  244. package/src/components/InstrumentAlertLog.vue +0 -4
  245. package/src/components/InstrumentStateBadge.vue +0 -4
  246. package/src/components/InstrumentStatusCard.vue +0 -4
  247. package/src/components/JobsStatusTray.vue +0 -4
  248. package/src/components/LcmsSequenceTable.vue +81 -50
  249. package/src/components/LoadingSpinner.vue +0 -4
  250. package/src/components/MobileSupportGate.vue +0 -4
  251. package/src/components/MoleculeInput.vue +0 -4
  252. package/src/components/MultiSelect.vue +0 -4
  253. package/src/components/NumberInput.vue +0 -4
  254. package/src/components/PlateMapEditor.vue +3 -14
  255. package/src/components/PlotlyChart.story.vue +61 -0
  256. package/src/components/PlotlyChart.vue +233 -0
  257. package/src/components/PluginIcon.vue +0 -4
  258. package/src/components/ProgressBar.vue +0 -4
  259. package/src/components/ProtocolStepEditor.vue +2 -4
  260. package/src/components/RackEditor.vue +51 -70
  261. package/src/components/ReagentEditor.vue +0 -4
  262. package/src/components/ReagentList.story.vue +18 -0
  263. package/src/components/ReagentList.vue +127 -82
  264. package/src/components/ResourceCard.vue +0 -4
  265. package/src/components/RuntimeUpdateNotice.story.vue +49 -0
  266. package/src/components/RuntimeUpdateNotice.vue +4 -4
  267. package/src/components/SampleHierarchyTree.story.vue +18 -0
  268. package/src/components/SampleHierarchyTree.vue +32 -9
  269. package/src/components/SampleLegend.vue +2 -8
  270. package/src/components/SampleSelector.drag.ts +51 -0
  271. package/src/components/SampleSelector.story.vue +6 -0
  272. package/src/components/SampleSelector.tree.ts +2 -0
  273. package/src/components/SampleSelector.vue +49 -10
  274. package/src/components/SampleSelectorGroupNode.vue +19 -0
  275. package/src/components/ScheduleCalendar.story.vue +6 -0
  276. package/src/components/ScheduleCalendar.vue +82 -7
  277. package/src/components/ScientificNumber.vue +0 -4
  278. package/src/components/SectionCard.vue +0 -4
  279. package/src/components/SegmentedControl.vue +50 -7
  280. package/src/components/SequenceInput.vue +0 -4
  281. package/src/components/SequenceProgressBar.story.vue +110 -0
  282. package/src/components/SequenceProgressBar.vue +0 -4
  283. package/src/components/SettingsModal.vue +0 -4
  284. package/src/components/Skeleton.vue +0 -4
  285. package/src/components/SmartGroupFieldRecipe.vue +8 -6
  286. package/src/components/SmartGroupManual.vue +0 -4
  287. package/src/components/StatusIndicator.vue +0 -4
  288. package/src/components/StepWizard.vue +0 -4
  289. package/src/components/TagsInput.vue +4 -6
  290. package/src/components/ThemeToggle.vue +0 -4
  291. package/src/components/TimePicker.vue +36 -7
  292. package/src/components/TimeRangeInput.vue +0 -4
  293. package/src/components/Tooltip.story.vue +3 -3
  294. package/src/components/Tooltip.vue +0 -4
  295. package/src/components/UnitInput.vue +0 -4
  296. package/src/components/WellPlate.geometry.ts +60 -1
  297. package/src/components/WellPlate.interaction.ts +44 -0
  298. package/src/components/WellPlate.story.vue +8 -0
  299. package/src/components/WellPlate.vue +53 -7
  300. package/src/components/fileBrowserPresentation.ts +39 -0
  301. package/src/components/index.ts +5 -0
  302. package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -5
  303. package/src/components/internal/AppTopBarPillNavInternal.vue +0 -4
  304. package/src/components/internal/DataStateInternal.vue +74 -0
  305. package/src/components/internal/FileBrowserDialogInternal.vue +75 -0
  306. package/src/components/internal/FileBrowserListingInternal.vue +197 -0
  307. package/src/components/internal/FileBrowserToolbarInternal.vue +136 -0
  308. package/src/components/internal/FilePickerPreviewInternal.vue +100 -0
  309. package/src/components/internal/FilePickerTreeInternal.vue +238 -0
  310. package/src/components/internal/FormSectionRenderer.vue +0 -4
  311. package/src/components/internal/WellEditPopupInternal.vue +0 -4
  312. package/src/composables/controlSchemaTypes.ts +34 -0
  313. package/src/composables/filePicker/localFiles.ts +58 -0
  314. package/src/composables/filePicker/usePickerNavigation.ts +220 -0
  315. package/src/composables/filePicker/usePickerSelection.ts +259 -0
  316. package/src/composables/filePicker/validation.ts +41 -0
  317. package/src/composables/index.ts +17 -0
  318. package/src/composables/useApi.ts +57 -13
  319. package/src/composables/useDropdownState.ts +35 -5
  320. package/src/composables/useEventStream.ts +14 -2
  321. package/src/composables/useFileBrowser.ts +314 -0
  322. package/src/composables/useFileImport.ts +2 -7
  323. package/src/composables/useFilePicker.ts +366 -0
  324. package/src/composables/useGeneratedAnalysis.ts +18 -26
  325. package/src/composables/useListReorder.story.vue +143 -0
  326. package/src/composables/useListReorder.ts +242 -0
  327. package/src/composables/useMenuKeyboard.ts +2 -2
  328. package/src/composables/useOptimisticMutation.ts +1 -15
  329. package/src/composables/usePlatformFilePickerAdapter.ts +104 -0
  330. package/src/composables/usePluginClient.ts +3 -0
  331. package/src/composables/usePluginConfig.ts +3 -1
  332. package/src/composables/useRackEditor.ts +2 -11
  333. package/src/composables/useRequestSyncState.ts +98 -28
  334. package/src/composables/useResourceCrud.ts +1 -15
  335. package/src/composables/useScheduleDrag.ts +37 -0
  336. package/src/composables/useWellPainting.ts +5 -29
  337. package/src/composables/useWellPlateEditor.ts +1 -11
  338. package/src/index.ts +3 -0
  339. package/src/permissions.ts +48 -9
  340. package/src/stores/experiment.ts +2 -7
  341. package/src/stores/index.ts +9 -1
  342. package/src/stores/settings.ts +93 -1
  343. package/src/styles/components/audit-trail.css +5 -5
  344. package/src/styles/components/batch-progress-list.css +7 -0
  345. package/src/styles/components/color-slider.css +2 -2
  346. package/src/styles/components/data-state.css +30 -0
  347. package/src/styles/components/dataframe.css +29 -35
  348. package/src/styles/components/experiment-data-viewer.css +4 -4
  349. package/src/styles/components/experiment-timeline.css +3 -30
  350. package/src/styles/components/file-browser-modal.css +489 -0
  351. package/src/styles/components/file-picker.css +502 -0
  352. package/src/styles/components/form-builder.css +5 -5
  353. package/src/styles/components/icon-button.css +9 -0
  354. package/src/styles/components/lcms-sequence-table.css +16 -1
  355. package/src/styles/components/multi-select.css +1 -1
  356. package/src/styles/components/reagent-list.css +38 -31
  357. package/src/styles/components/sample-hierarchy-tree.css +5 -19
  358. package/src/styles/components/sample-selector.css +9 -2
  359. package/src/styles/components/schedule-calendar.css +8 -0
  360. package/src/styles/components/scientific-number.css +1 -1
  361. package/src/styles/components/well-plate.css +8 -0
  362. package/src/styles/index.css +11 -0
  363. package/src/styles/variables.css +29 -0
  364. package/src/templates/builderPresetControls.ts +1 -1
  365. package/src/templates/componentPlateHelpers.ts +3 -1
  366. package/src/templates/doseResponseAdapters.ts +2 -13
  367. package/src/templates/elisaAssayCollectionBuilder.ts +2 -2
  368. package/src/templates/plateMapBuilder.ts +2 -2
  369. package/src/templates/qpcrPlateBuilder.ts +1 -1
  370. package/src/templates/templateValidators.ts +1 -1
  371. package/src/templates/timeCourseBuilder.ts +2 -2
  372. package/src/templates/wellPlateScreenCollectionBuilder.ts +2 -2
  373. package/src/types/fileBrowserTypes.ts +85 -0
  374. package/src/types/filePicker.ts +72 -0
  375. package/src/types/index.ts +14 -0
  376. package/src/utils/color.ts +51 -0
  377. package/src/utils/errors.ts +29 -0
  378. package/dist/EmptyState-NBqf2_8z.js.map +0 -1
  379. package/dist/ExperimentPopover-CfOdh5Rm.js.map +0 -1
  380. package/dist/ExperimentSelectorModal-DvIagrxY.js.map +0 -1
  381. package/dist/SettingsModal-W3TYfnP9.js.map +0 -1
  382. package/dist/components-B80gV0P6.js.map +0 -1
  383. package/dist/composables/autoGroupConstants.d.ts +0 -1
  384. package/dist/composables/autoGroupGrouping.d.ts +0 -3
  385. package/dist/composables/useAutoGroupInputSources.d.ts +0 -32
  386. package/dist/composables-C8mmD5ta.js.map +0 -1
  387. package/dist/experiment-Y9Iz7KYi.js.map +0 -1
  388. package/dist/experiment-utils-DV3DrvE_.js.map +0 -1
  389. package/dist/templates/builderDefaults.d.ts +0 -1
  390. package/dist/templates/plateGeometry.d.ts +0 -4
  391. package/dist/templates-CK72aKO1.js.map +0 -1
  392. package/dist/useApi-CDBq6z23.js.map +0 -1
  393. package/dist/useControlSchema-_OVhavKq.js.map +0 -1
  394. package/dist/useDropdownState-D5G4UvWd.js.map +0 -1
  395. package/dist/useProtocolTemplates-Dq5Pzwyd.js.map +0 -1
  396. package/src/__tests__/composables/useAutoGroupInputSources.test.ts +0 -127
  397. package/src/composables/autoGroupConstants.ts +0 -4
  398. package/src/composables/autoGroupGrouping.ts +0 -148
  399. package/src/composables/useAutoGroupInputSources.ts +0 -147
  400. package/src/templates/builderDefaults.ts +0 -11
  401. package/src/templates/plateGeometry.ts +0 -62
  402. /package/dist/__tests__/{composables/useAutoGroupInputSources.test.d.ts → apiErrors.test.d.ts} +0 -0
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  /** Sortable, searchable table of experiment reagents showing lot number, expiry, storage conditions, and stock level with low-stock warnings. */
3
- import { ref, computed } from 'vue'
3
+ import { ref, computed, type ComponentPublicInstance } from 'vue'
4
4
  import type { ReagentColumn, Reagent } from '../types'
5
+ import { useListReorder } from '../composables/useListReorder'
5
6
  import { useSortedItems } from '../composables/useSortedItems'
6
7
  import { useTextSearch } from '../composables/useTextSearch'
7
8
  import {
@@ -14,6 +15,13 @@ import {
14
15
  isReagentExpired as isExpired,
15
16
  isReagentExpiringSoon as isExpiringSoon,
16
17
  } from './ReagentList.presentation'
18
+ import DataStateInternal from './internal/DataStateInternal.vue'
19
+
20
+ /** Beaker glyph kept from the pre-1.2 hand-rolled empty state. */
21
+ const EMPTY_ICON_PATH
22
+ = 'M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0'
23
+ + ' 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26'
24
+ + ' 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z'
17
25
 
18
26
  interface Props {
19
27
  modelValue?: Reagent[]
@@ -23,6 +31,13 @@ interface Props {
23
31
  columns?: ReagentColumn[]
24
32
  sortable?: boolean
25
33
  searchable?: boolean
34
+ /** Replace the table with a loading placeholder. */
35
+ loading?: boolean
36
+ /**
37
+ * Error message. When set, the table is replaced by an error state.
38
+ * `null`/`undefined` renders normally.
39
+ */
40
+ error?: string | null
26
41
  }
27
42
 
28
43
  const props = withDefaults(defineProps<Props>(), {
@@ -33,6 +48,8 @@ const props = withDefaults(defineProps<Props>(), {
33
48
  columns: () => ['name', 'lot', 'expiry', 'storage', 'stock'],
34
49
  sortable: true,
35
50
  searchable: true,
51
+ loading: false,
52
+ error: null,
36
53
  })
37
54
 
38
55
  const emit = defineEmits<{
@@ -47,8 +64,7 @@ const emit = defineEmits<{
47
64
  const searchQuery = ref('')
48
65
  const sortColumn = ref<ReagentColumn | null>(null)
49
66
  const sortDirection = ref<'asc' | 'desc'>('asc')
50
- const draggedId = ref<string | null>(null)
51
- const dragOverId = ref<string | null>(null)
67
+ const dragHandleRefs = new Map<string, HTMLElement>()
52
68
 
53
69
  const reagentSearch = useTextSearch({
54
70
  items: () => props.modelValue || [],
@@ -78,14 +94,63 @@ const sortedReagents = useSortedItems<Reagent, ReagentColumn>({
78
94
  })
79
95
  const filteredReagents = sortedReagents.sortedItems
80
96
 
97
+ // Drag-and-drop plus the Alt+Arrow keyboard alternative live in the shared
98
+ // reorder composable, which also announces every move politely.
99
+ const {
100
+ draggingId,
101
+ dragOverId,
102
+ announcement: reorderAnnouncement,
103
+ announcerStyle,
104
+ onDragStart,
105
+ onDragOver,
106
+ onDragLeave,
107
+ onDrop,
108
+ onDragEnd,
109
+ onKeydown: onReorderKeydown,
110
+ } = useListReorder<Reagent>({
111
+ items: () => props.modelValue || [],
112
+ getId: (reagent) => reagent.id,
113
+ getLabel: (reagent) => reagent.name,
114
+ disabled: () => props.readonly || !props.sortable,
115
+ getElement: (id) => dragHandleRefs.get(id),
116
+ itemNoun: 'Reagent',
117
+ onReorder: ({ fromIndex, toIndex, items }) => {
118
+ emit('reorder', items[toIndex].id, fromIndex, toIndex)
119
+ emit('update:modelValue', items)
120
+ },
121
+ })
122
+
123
+ function setDragHandleRef(id: string, el: Element | ComponentPublicInstance | null) {
124
+ if (el instanceof HTMLElement) {
125
+ dragHandleRefs.set(id, el)
126
+ } else {
127
+ dragHandleRefs.delete(id)
128
+ }
129
+ }
130
+
81
131
  function getRowClass(reagent: Reagent): string[] {
82
132
  return getReagentRowClasses(reagent, {
83
133
  lowStockThreshold: props.lowStockThreshold,
84
- draggedId: draggedId.value,
134
+ draggedId: draggingId.value,
85
135
  dragOverId: dragOverId.value,
86
136
  })
87
137
  }
88
138
 
139
+ /** `aria-sort` for a column header, omitted while sorting is off. */
140
+ function columnAriaSort(column: ReagentColumn): 'ascending' | 'descending' | 'none' | undefined {
141
+ if (!props.sortable) return undefined
142
+ if (sortColumn.value !== column) return 'none'
143
+ return sortDirection.value === 'asc' ? 'ascending' : 'descending'
144
+ }
145
+
146
+ // Native buttons activate on Enter/Space; the default is prevented so a header
147
+ // sorts exactly once per key press in every engine.
148
+ function handleSortKeydown(event: KeyboardEvent, column: ReagentColumn) {
149
+ if (event.key !== 'Enter' && event.key !== ' ') return
150
+ event.preventDefault()
151
+ handleSort(column)
152
+ }
153
+
89
154
  // Actions
90
155
  function handleSort(column: ReagentColumn) {
91
156
  if (!props.sortable) return
@@ -111,54 +176,7 @@ function handleAdd() {
111
176
  emit('add')
112
177
  }
113
178
 
114
- // Drag and drop
115
- function handleDragStart(event: DragEvent, reagent: Reagent) {
116
- if (props.readonly || !props.sortable) return
117
- draggedId.value = reagent.id
118
- if (event.dataTransfer) {
119
- event.dataTransfer.effectAllowed = 'move'
120
- event.dataTransfer.setData('text/plain', reagent.id)
121
- }
122
- }
123
-
124
- function handleDragOver(event: DragEvent, reagent: Reagent) {
125
- if (props.readonly || !props.sortable || !draggedId.value) return
126
- event.preventDefault()
127
- dragOverId.value = reagent.id
128
- }
129
-
130
- function handleDragLeave() {
131
- dragOverId.value = null
132
- }
133
-
134
- function handleDrop(event: DragEvent, targetReagent: Reagent) {
135
- if (props.readonly || !props.sortable || !draggedId.value) return
136
- event.preventDefault()
137
-
138
- const reagents = [...(props.modelValue || [])]
139
- const fromIndex = reagents.findIndex((r) => r.id === draggedId.value)
140
- const toIndex = reagents.findIndex((r) => r.id === targetReagent.id)
141
-
142
- if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) {
143
- draggedId.value = null
144
- dragOverId.value = null
145
- return
146
- }
147
179
 
148
- const [removed] = reagents.splice(fromIndex, 1)
149
- reagents.splice(toIndex, 0, removed)
150
-
151
- emit('reorder', draggedId.value, fromIndex, toIndex)
152
- emit('update:modelValue', reagents)
153
-
154
- draggedId.value = null
155
- dragOverId.value = null
156
- }
157
-
158
- function handleDragEnd() {
159
- draggedId.value = null
160
- dragOverId.value = null
161
- }
162
180
  </script>
163
181
 
164
182
  <template>
@@ -193,7 +211,10 @@ function handleDragEnd() {
193
211
  </div>
194
212
 
195
213
  <!-- Table -->
196
- <table v-if="filteredReagents.length > 0" class="mint-reagent-list__table">
214
+ <table
215
+ v-if="!loading && !error && filteredReagents.length > 0"
216
+ class="mint-reagent-list__table"
217
+ >
197
218
  <thead>
198
219
  <tr>
199
220
  <th v-if="sortable && !readonly" style="width: 24px" />
@@ -201,8 +222,15 @@ function handleDragEnd() {
201
222
  v-for="col in columns"
202
223
  :key="col"
203
224
  :class="[sortable ? 'mint-reagent-list__col--sortable' : '']"
204
- @click="handleSort(col)"
225
+ :aria-sort="columnAriaSort(col)"
205
226
  >
227
+ <component
228
+ :is="sortable ? 'button' : 'span'"
229
+ :type="sortable ? 'button' : undefined"
230
+ class="mint-reagent-list__sort-btn"
231
+ @click="handleSort(col)"
232
+ @keydown="sortable ? handleSortKeydown($event, col) : undefined"
233
+ >
206
234
  {{ columnLabels[col] }}
207
235
  <svg
208
236
  v-if="sortable"
@@ -229,27 +257,36 @@ function handleDragEnd() {
229
257
  d="M5 15l7-7 7 7"
230
258
  />
231
259
  </svg>
260
+ </component>
232
261
  </th>
233
262
  <th v-if="!readonly" style="width: 80px" />
234
263
  </tr>
235
264
  </thead>
236
265
  <tbody>
237
266
  <tr
238
- v-for="reagent in filteredReagents"
267
+ v-for="(reagent, index) in filteredReagents"
239
268
  :key="reagent.id"
240
269
  :class="getRowClass(reagent)"
241
270
  :draggable="sortable && !readonly"
242
- @dragstart="handleDragStart($event, reagent)"
243
- @dragover="handleDragOver($event, reagent)"
244
- @dragleave="handleDragLeave"
245
- @drop="handleDrop($event, reagent)"
246
- @dragend="handleDragEnd"
271
+ @dragstart="onDragStart($event, reagent, index)"
272
+ @dragover="onDragOver($event, reagent, index)"
273
+ @dragleave="onDragLeave"
274
+ @drop="onDrop($event, reagent, index)"
275
+ @dragend="onDragEnd"
247
276
  >
248
- <!-- Drag handle -->
277
+ <!-- Drag handle: also the keyboard reorder target (Alt+Arrow keys). -->
249
278
  <td v-if="sortable && !readonly">
250
- <svg class="mint-reagent-list__drag-handle" fill="none" stroke="currentColor" viewBox="0 0 24 24">
251
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8h16M4 16h16" />
252
- </svg>
279
+ <button
280
+ :ref="(el) => setDragHandleRef(reagent.id, el)"
281
+ type="button"
282
+ class="mint-reagent-list__drag-handle-btn"
283
+ :aria-label="`Reorder ${reagent.name}. Press Alt with the arrow keys to move it.`"
284
+ @keydown="onReorderKeydown($event, reagent, index)"
285
+ >
286
+ <svg class="mint-reagent-list__drag-handle" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
287
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8h16M4 16h16" />
288
+ </svg>
289
+ </button>
253
290
  </td>
254
291
 
255
292
  <!-- Name -->
@@ -367,26 +404,34 @@ function handleDragEnd() {
367
404
  </tbody>
368
405
  </table>
369
406
 
407
+ <!-- Error state -->
408
+ <div v-else-if="error" class="mint-reagent-list__state">
409
+ <slot name="error" :error="error">
410
+ <DataStateInternal state="error" :title="error" />
411
+ </slot>
412
+ </div>
413
+
414
+ <!-- Loading state -->
415
+ <div v-else-if="loading" class="mint-reagent-list__state">
416
+ <slot name="loading">
417
+ <DataStateInternal state="loading" loading-label="Loading reagents" />
418
+ </slot>
419
+ </div>
420
+
370
421
  <!-- Empty state -->
371
- <div v-else class="mint-reagent-list__empty">
372
- <svg class="mint-reagent-list__empty-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
373
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
374
- </svg>
375
- <p class="mint-reagent-list__empty-text">
376
- {{ searchQuery ? 'No reagents found' : 'No reagents added yet' }}
377
- </p>
378
- <button
379
- v-if="!readonly && !searchQuery"
380
- type="button"
381
- class="mint-reagent-list__empty-btn"
382
- @click="handleAdd"
383
- >
384
- Add Reagent
385
- </button>
422
+ <div v-else class="mint-reagent-list__state">
423
+ <slot name="empty">
424
+ <DataStateInternal
425
+ state="empty"
426
+ :title="searchQuery ? 'No reagents found' : 'No reagents added yet'"
427
+ :icon-path="EMPTY_ICON_PATH"
428
+ :action-label="!readonly && !searchQuery ? 'Add Reagent' : undefined"
429
+ @action="handleAdd"
430
+ />
431
+ </slot>
386
432
  </div>
433
+
434
+ <!-- Reorder announcements for screen readers -->
435
+ <div role="status" aria-live="polite" :style="announcerStyle">{{ reorderAnnouncement }}</div>
387
436
  </div>
388
437
  </template>
389
-
390
- <style>
391
- @import '../styles/components/reagent-list.css';
392
- </style>
@@ -155,7 +155,3 @@ function handleClick() {
155
155
  </div>
156
156
  </div>
157
157
  </template>
158
-
159
- <style>
160
- @import '../styles/components/resource-card.css';
161
- </style>
@@ -0,0 +1,49 @@
1
+ <script setup lang="ts">
2
+ import RuntimeUpdateNotice from './RuntimeUpdateNotice.vue'
3
+
4
+ function handleReload() {
5
+ console.log('reload requested')
6
+ }
7
+
8
+ function initState() {
9
+ return {
10
+ visible: true,
11
+ }
12
+ }
13
+ </script>
14
+
15
+ <template>
16
+ <Story title="Feedback/RuntimeUpdateNotice">
17
+ <Variant title="Playground" :init-state="initState">
18
+ <template #default="{ state }">
19
+ <!-- The notice is `position: fixed`, so it anchors to the preview
20
+ viewport rather than this wrapper. -->
21
+ <div style="padding: 2rem; min-height: 14rem; background: var(--bg-primary);">
22
+ <p style="margin: 0; font-size: 0.8125rem; color: var(--text-secondary);">
23
+ Shown after the platform detects a new frontend build.
24
+ </p>
25
+ <RuntimeUpdateNotice :visible="state.visible" @reload="handleReload" />
26
+ </div>
27
+ </template>
28
+
29
+ <template #controls="{ state }">
30
+ <HstCheckbox v-model="state.visible" title="Visible" />
31
+ </template>
32
+ </Variant>
33
+
34
+ <Variant title="Visible">
35
+ <div style="padding: 2rem; min-height: 14rem; background: var(--bg-primary);">
36
+ <RuntimeUpdateNotice :visible="true" @reload="handleReload" />
37
+ </div>
38
+ </Variant>
39
+
40
+ <Variant title="Hidden">
41
+ <div style="padding: 2rem; min-height: 8rem; background: var(--bg-primary);">
42
+ <p style="margin: 0; font-size: 0.8125rem; color: var(--text-secondary);">
43
+ Nothing renders while <code>visible</code> is false.
44
+ </p>
45
+ <RuntimeUpdateNotice :visible="false" @reload="handleReload" />
46
+ </div>
47
+ </Variant>
48
+ </Story>
49
+ </template>
@@ -30,9 +30,9 @@ const emit = defineEmits<{
30
30
  align-items: center;
31
31
  gap: 10px;
32
32
  padding: 10px 12px;
33
- color: var(--mint-text-primary, #172033);
34
- background: var(--mint-bg-primary, #fff);
35
- border: 1px solid var(--mint-border-default, #d9e1ec);
33
+ color: var(--text-primary);
34
+ background: var(--bg-card);
35
+ border: 1px solid var(--border-color);
36
36
  border-radius: 10px;
37
37
  box-shadow: 0 10px 28px rgb(15 23 42 / 16%);
38
38
  font-size: 13px;
@@ -42,7 +42,7 @@ const emit = defineEmits<{
42
42
  .mint-runtime-update button {
43
43
  padding: 5px 9px;
44
44
  color: #fff;
45
- background: var(--mint-color-primary, #0f9f8f);
45
+ background: var(--color-primary);
46
46
  border: 0;
47
47
  border-radius: 6px;
48
48
  font: inherit;
@@ -84,6 +84,8 @@ const mockNodes: TreeNode[] = [
84
84
 
85
85
  function initState() {
86
86
  return {
87
+ loading: false,
88
+ error: '',
87
89
  nodes: mockNodes,
88
90
  expandAll: false,
89
91
  showIcons: true,
@@ -99,6 +101,8 @@ function initState() {
99
101
  <template #default="{ state }">
100
102
  <div style="padding: 2rem; max-width: 500px;">
101
103
  <SampleHierarchyTree
104
+ :loading="state.loading"
105
+ :error="state.error || null"
102
106
  :nodes="state.nodes"
103
107
  :expand-all="state.expandAll"
104
108
  :show-icons="state.showIcons"
@@ -108,6 +112,8 @@ function initState() {
108
112
  </div>
109
113
  </template>
110
114
  <template #controls="{ state }">
115
+ <HstCheckbox v-model="state.loading" title="Loading" />
116
+ <HstText v-model="state.error" title="Error Message" />
111
117
  <HstCheckbox v-model="state.expandAll" title="Expand All" />
112
118
  <HstCheckbox v-model="state.showIcons" title="Show Icons" />
113
119
  <HstCheckbox v-model="state.showCounts" title="Show Counts" />
@@ -157,5 +163,17 @@ function initState() {
157
163
  <SampleHierarchyTree :nodes="[]" />
158
164
  </div>
159
165
  </Variant>
166
+
167
+ <Variant title="Loading">
168
+ <div style="padding: 2rem; max-width: 600px;">
169
+ <SampleHierarchyTree :nodes="[]" loading />
170
+ </div>
171
+ </Variant>
172
+
173
+ <Variant title="Error">
174
+ <div style="padding: 2rem; max-width: 600px;">
175
+ <SampleHierarchyTree :nodes="[]" error="Could not load the sample hierarchy." />
176
+ </div>
177
+ </Variant>
160
178
  </Story>
161
179
  </template>
@@ -3,6 +3,11 @@
3
3
  import { h, Transition, type VNode } from 'vue'
4
4
  import type { TreeNodeType, BadgeVariant, TreeNode } from '../types'
5
5
  import { useExpansionSet } from '../composables/useExpansionSet'
6
+ import DataStateInternal from './internal/DataStateInternal.vue'
7
+
8
+ /** Folder glyph kept from the pre-1.2 hand-rolled empty state. */
9
+ const EMPTY_ICON_PATH
10
+ = 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z'
6
11
 
7
12
  interface Props {
8
13
  nodes: TreeNode[]
@@ -12,6 +17,13 @@ interface Props {
12
17
  showCounts?: boolean
13
18
  maxDepth?: number
14
19
  size?: 'sm' | 'md' | 'lg'
20
+ /** Replace the tree with a loading placeholder. */
21
+ loading?: boolean
22
+ /**
23
+ * Error message. When set, the tree is replaced by an error state.
24
+ * `null`/`undefined` renders normally.
25
+ */
26
+ error?: string | null
15
27
  }
16
28
 
17
29
  const props = withDefaults(defineProps<Props>(), {
@@ -20,6 +32,8 @@ const props = withDefaults(defineProps<Props>(), {
20
32
  showIcons: true,
21
33
  showCounts: true,
22
34
  size: 'md',
35
+ loading: false,
36
+ error: null,
23
37
  })
24
38
 
25
39
  const emit = defineEmits<{
@@ -226,12 +240,25 @@ function renderTree(): VNode[] {
226
240
 
227
241
  <template>
228
242
  <div :class="['mint-sample-tree', `mint-sample-tree--${size}`]" role="tree">
243
+ <!-- Error state -->
244
+ <div v-if="error" class="mint-sample-tree__state">
245
+ <slot name="error" :error="error">
246
+ <DataStateInternal state="error" size="sm" :title="error" />
247
+ </slot>
248
+ </div>
249
+
250
+ <!-- Loading state -->
251
+ <div v-else-if="loading" class="mint-sample-tree__state">
252
+ <slot name="loading">
253
+ <DataStateInternal state="loading" size="sm" loading-label="Loading samples" />
254
+ </slot>
255
+ </div>
256
+
229
257
  <!-- Empty state -->
230
- <div v-if="nodes.length === 0" class="mint-sample-tree__empty">
231
- <svg class="mint-sample-tree__empty-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
232
- <path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
233
- </svg>
234
- <p class="mint-sample-tree__empty-text">No items</p>
258
+ <div v-else-if="nodes.length === 0" class="mint-sample-tree__state">
259
+ <slot name="empty">
260
+ <DataStateInternal state="empty" size="sm" title="No items" :icon-path="EMPTY_ICON_PATH" />
261
+ </slot>
235
262
  </div>
236
263
 
237
264
  <!-- Tree nodes rendered via render function -->
@@ -240,7 +267,3 @@ function renderTree(): VNode[] {
240
267
  </template>
241
268
  </div>
242
269
  </template>
243
-
244
- <style>
245
- @import '../styles/components/sample-hierarchy-tree.css';
246
- </style>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  /** Clickable color-swatch legend for plate sample types, supporting selection, count display, add/remove, and custom color assignment. */
3
3
  import type { SampleType } from '../types'
4
+ import { SAMPLE_COLOR_SCALE } from '../utils/color'
4
5
 
5
6
  interface Props {
6
7
  modelValue?: string
@@ -17,10 +18,7 @@ const props = withDefaults(defineProps<Props>(), {
17
18
  samples: () => [],
18
19
  showCounts: true,
19
20
  editable: false,
20
- colorPalette: () => [
21
- '#3B82F6', '#10B981', '#EF4444', '#F59E0B', '#8B5CF6',
22
- '#F97316', '#06B6D4', '#14B8A6', '#6B7280',
23
- ],
21
+ colorPalette: () => [...SAMPLE_COLOR_SCALE],
24
22
  size: 'md',
25
23
  orientation: 'vertical',
26
24
  })
@@ -113,7 +111,3 @@ function handleAdd() {
113
111
  </button>
114
112
  </div>
115
113
  </template>
116
-
117
- <style>
118
- @import '../styles/components/sample-legend.css';
119
- </style>
@@ -1,5 +1,7 @@
1
1
  import { ref } from 'vue'
2
2
  import type { SampleGroup } from '../types'
3
+ import { useReorderAnnouncer } from '../composables/useListReorder'
4
+ import { getSampleGroupMajorPrefix } from '../utils/sampleGroupPath'
3
5
  import {
4
6
  detectSampleGroupSeparator,
5
7
  getSampleGroupParentPath,
@@ -23,6 +25,7 @@ export function useSampleSelectorDrag(groups: MutableSampleGroups) {
23
25
  const draggingGroupKind = ref<GroupDragKind | null>(null)
24
26
  const reorderTarget = ref<string | null>(null)
25
27
  const reorderPosition = ref<GroupReorderPosition | null>(null)
28
+ const { announcement, announce, announcerStyle } = useReorderAnnouncer()
26
29
 
27
30
  function handleDragStart(sample: string, sourceGroup: string | null, event: DragEvent) {
28
31
  draggingSample.value = sample
@@ -143,7 +146,45 @@ export function useSampleSelectorDrag(groups: MutableSampleGroups) {
143
146
  handleGroupDragEnd()
144
147
  }
145
148
 
149
+ /**
150
+ * Keyboard alternative to the group reorder drag. The hierarchy constrains a
151
+ * move to the dragged node's siblings (a leaf may not cross a parent), so this
152
+ * is not a flat list reorder and does not use `useListReorder`: Alt+ArrowUp /
153
+ * Alt+ArrowDown swap the node with its previous / next sibling instead.
154
+ */
155
+ function handleGroupReorderKeydown(name: string, kind: GroupDragKind, event: KeyboardEvent) {
156
+ if (!event.altKey) return
157
+ const direction = event.key === 'ArrowUp' ? -1 : event.key === 'ArrowDown' ? 1 : 0
158
+ if (direction === 0) return
159
+
160
+ event.preventDefault()
161
+ const separator = detectSampleGroupSeparator(groups.value)
162
+ const siblings = kind === 'major'
163
+ ? uniqueMajorPrefixes(groups.value, separator)
164
+ : groups.value
165
+ .filter(group =>
166
+ getSampleGroupParentPath(group.name, separator) ===
167
+ getSampleGroupParentPath(name, separator))
168
+ .map(group => group.name)
169
+
170
+ const currentIndex = siblings.indexOf(name)
171
+ const target = siblings[currentIndex + direction]
172
+ if (currentIndex === -1 || target === undefined) return
173
+
174
+ const position: GroupReorderPosition = direction === -1 ? 'before' : 'after'
175
+ const nextGroups = kind === 'major'
176
+ ? reorderSampleMajorGroup(groups.value, name, target, position)
177
+ : reorderSampleGroup(groups.value, name, target, position)
178
+
179
+ if (nextGroups === groups.value) return
180
+ groups.value = nextGroups
181
+ announce(`${name} moved ${direction === -1 ? 'up' : 'down'} to position ${currentIndex + direction + 1} of ${siblings.length}.`)
182
+ }
183
+
146
184
  return {
185
+ announcement,
186
+ announcerStyle,
187
+ handleGroupReorderKeydown,
147
188
  draggingSample,
148
189
  dragOverGroup,
149
190
  draggingGroup,
@@ -162,3 +203,13 @@ export function useSampleSelectorDrag(groups: MutableSampleGroups) {
162
203
  handleGroupDrop,
163
204
  }
164
205
  }
206
+
207
+ /** Major-group prefixes in tree order, de-duplicated. */
208
+ function uniqueMajorPrefixes(groups: SampleGroup[], separator: string): string[] {
209
+ const seen: string[] = []
210
+ for (const group of groups) {
211
+ const prefix = getSampleGroupMajorPrefix(group.name, separator)
212
+ if (!seen.includes(prefix)) seen.push(prefix)
213
+ }
214
+ return seen
215
+ }
@@ -72,6 +72,8 @@ const lcmsSamples = ['Liver', 'Kidney'].flatMap(tissue =>
72
72
 
73
73
  function initState() {
74
74
  return {
75
+ loading: false,
76
+ error: '',
75
77
  samples: mockSamples,
76
78
  selected: [] as string[],
77
79
  groups: [] as SampleGroup[],
@@ -100,6 +102,8 @@ function handleSmartGroup(result: AutoGroupResult, state: { groups: SampleGroup[
100
102
  <template #default="{ state }">
101
103
  <div style="padding: 2rem; max-width: 400px;">
102
104
  <SampleSelector
105
+ :loading="state.loading"
106
+ :error="state.error || null"
103
107
  v-model="state.selected"
104
108
  v-model:groups="state.groups"
105
109
  :samples="state.samples"
@@ -110,6 +114,8 @@ function handleSmartGroup(result: AutoGroupResult, state: { groups: SampleGroup[
110
114
  </div>
111
115
  </template>
112
116
  <template #controls="{ state }">
117
+ <HstCheckbox v-model="state.loading" title="Loading" />
118
+ <HstText v-model="state.error" title="Error Message" />
113
119
  <HstCheckbox v-model="state.enableGrouping" title="Enable Grouping" />
114
120
  <HstCheckbox v-model="state.enableSmartGroup" title="Enable Smart" />
115
121
  </template>
@@ -34,6 +34,8 @@ export interface SampleGroupTreeContext {
34
34
  onGroupDragOver: (name: string, kind: GroupDragKind, event: DragEvent) => void
35
35
  onGroupDragLeave: (event: DragEvent) => void
36
36
  onGroupDrop: (name: string, kind: GroupDragKind, event: DragEvent) => void
37
+ /** Alt+ArrowUp / Alt+ArrowDown reorder a group among its siblings. */
38
+ onGroupReorderKeydown: (name: string, kind: GroupDragKind, event: KeyboardEvent) => void
37
39
  }
38
40
 
39
41
  /** Every concrete group name beneath a node, in tree order. */