@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
@@ -13,7 +13,8 @@ import type {
13
13
  PluginJobCenterSource,
14
14
  } from './useJobsStatusTray'
15
15
  import { useEventStream, type EventStreamMessage } from './useEventStream'
16
- import { normalizeJobState } from '../jobs'
16
+ import { isActiveJobStatus, normalizeJobState } from '../jobs'
17
+ import { MintApiError, mintApiErrorFromResponse } from '../apiErrors'
17
18
  import type {
18
19
  GeneratedAnalysisJob,
19
20
  GeneratedAnalysisJobInput,
@@ -111,7 +112,6 @@ export interface UsePluginJobsReturn extends PluginJobCenterSource {
111
112
 
112
113
  export type UseGeneratedAnalysisReturn = UsePluginJobsReturn
113
114
 
114
- const ACTIVE_STATUSES = new Set(['awaiting_input', 'queued', 'running'])
115
115
  const AUTH_TOKEN_KEY = 'mint-auth-token'
116
116
  const SESSION_NOT_FOUND_DETAIL = 'Session not found'
117
117
  const MAX_RESULT_CACHE_ENTRIES = 32
@@ -130,16 +130,6 @@ type PluginJobStreamData = GeneratedAnalysisJobInput | {
130
130
  jobs: GeneratedAnalysisJobInput[]
131
131
  }
132
132
 
133
- class GeneratedAnalysisRequestError extends Error {
134
- constructor(
135
- readonly status: number,
136
- readonly detail: unknown,
137
- ) {
138
- super(typeof detail === 'string' ? detail : `Request failed (${status})`)
139
- this.name = 'GeneratedAnalysisRequestError'
140
- }
141
- }
142
-
143
133
  export function createGeneratedAnalysisTransport(
144
134
  options: GeneratedAnalysisTransportOptions = {},
145
135
  ): GeneratedAnalysisTransport {
@@ -473,12 +463,12 @@ function createPluginJobs(
473
463
  .then((payload) => {
474
464
  if (disposed) return jobs.value
475
465
  if (revision !== jobsRevision) {
476
- if (jobs.value.some(job => ACTIVE_STATUSES.has(job.status))) schedulePoll()
466
+ if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
477
467
  return jobs.value
478
468
  }
479
469
  replaceJobs(payload.jobs)
480
470
  error.value = null
481
- if (!paused && !jobStream.isConnected.value && jobs.value.some(job => ACTIVE_STATUSES.has(job.status))) {
471
+ if (!paused && !jobStream.isConnected.value && jobs.value.some(job => isActiveJobStatus(job.status))) {
482
472
  schedulePoll()
483
473
  }
484
474
  return jobs.value
@@ -530,7 +520,7 @@ function createPluginJobs(
530
520
  const current = typeof job === 'string'
531
521
  ? jobs.value.find(candidate => candidate.job_id === jobId)
532
522
  : jobs.value.find(candidate => candidate.job_id === jobId) ?? job
533
- if (current && !ACTIVE_STATUSES.has(current.status)) {
523
+ if (current && !isActiveJobStatus(current.status)) {
534
524
  return terminalWaitResult(current, options.rejectOnFailure === true)
535
525
  }
536
526
  if (paused) return Promise.reject(new Error('Plugin jobs runtime is paused'))
@@ -567,7 +557,7 @@ function createPluginJobs(
567
557
  }
568
558
 
569
559
  function settleJobWaiters(job: GeneratedAnalysisJob): void {
570
- if (ACTIVE_STATUSES.has(job.status)) return
560
+ if (isActiveJobStatus(job.status)) return
571
561
  const waiting = jobWaiters.get(job.job_id)
572
562
  if (!waiting) return
573
563
  jobWaiters.delete(job.job_id)
@@ -839,7 +829,7 @@ function createPluginJobs(
839
829
  job: GeneratedAnalysisJob,
840
830
  cause: unknown,
841
831
  ): void {
842
- if (cause instanceof GeneratedAnalysisRequestError) {
832
+ if (cause instanceof MintApiError) {
843
833
  if (cause.status === 404 || cause.status === 410) {
844
834
  discardCachedResult(job.job_id)
845
835
  jobs.value = jobs.value.map(candidate => (
@@ -915,7 +905,7 @@ function createPluginJobs(
915
905
  await refreshJobs()
916
906
  } catch (cause) {
917
907
  error.value = errorMessage(cause, 'Failed to refresh analysis jobs')
918
- if (jobs.value.some(job => ACTIVE_STATUSES.has(job.status))) schedulePoll()
908
+ if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
919
909
  }
920
910
  }, pollInterval)
921
911
  }
@@ -927,7 +917,7 @@ function createPluginJobs(
927
917
  }
928
918
 
929
919
  function schedulePollForActiveJobs(): void {
930
- if (jobs.value.some(job => ACTIVE_STATUSES.has(job.status))) schedulePoll()
920
+ if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
931
921
  }
932
922
 
933
923
  function applyJobStreamMessage(message: EventStreamMessage<PluginJobStreamData>): void {
@@ -1005,7 +995,7 @@ function createPluginJobs(
1005
995
  scheduleManagedDownloadRefresh(jobId, managedDownloadRefreshDelay(result))
1006
996
  }
1007
997
  if (streamEnabled) jobStream.start()
1008
- else if (jobs.value.some(job => ACTIVE_STATUSES.has(job.status))) schedulePoll()
998
+ else if (jobs.value.some(job => isActiveJobStatus(job.status))) schedulePoll()
1009
999
  }
1010
1000
 
1011
1001
  function dispose(): void {
@@ -1038,8 +1028,10 @@ function createPluginJobs(
1038
1028
  },
1039
1029
  })
1040
1030
  if (!response.ok) {
1041
- const payload = await response.json().catch(() => null) as { detail?: unknown } | null
1042
- throw new GeneratedAnalysisRequestError(response.status, payload?.detail)
1031
+ const payload: unknown = await response.json().catch(() => null)
1032
+ throw mintApiErrorFromResponse(response.status, payload, {
1033
+ requestId: response.headers.get('X-Request-ID'),
1034
+ })
1043
1035
  }
1044
1036
  return await response.json() as T
1045
1037
  }
@@ -1178,20 +1170,20 @@ function createGeneratedSessionStorage(candidate?: Storage): GeneratedSessionSto
1178
1170
  }
1179
1171
 
1180
1172
  function isSessionNotFoundError(error: unknown): boolean {
1181
- return error instanceof GeneratedAnalysisRequestError
1173
+ return error instanceof MintApiError
1182
1174
  && error.status === 404
1183
- && error.detail === SESSION_NOT_FOUND_DETAIL
1175
+ && (error.detail === SESSION_NOT_FOUND_DETAIL || error.message === SESSION_NOT_FOUND_DETAIL)
1184
1176
  }
1185
1177
 
1186
1178
  function isRetryableResultError(error: unknown): boolean {
1187
- return !(error instanceof GeneratedAnalysisRequestError)
1179
+ return !(error instanceof MintApiError)
1188
1180
  || error.status === 409
1189
1181
  || error.status === 429
1190
1182
  || error.status >= 500
1191
1183
  }
1192
1184
 
1193
1185
  function isRetryableJobLookupError(error: unknown): boolean {
1194
- return !(error instanceof GeneratedAnalysisRequestError)
1186
+ return !(error instanceof MintApiError)
1195
1187
  || error.status === 408
1196
1188
  || error.status === 429
1197
1189
  || error.status >= 500
@@ -0,0 +1,143 @@
1
+ <script setup lang="ts">
2
+ /** Demonstrates `useListReorder`: drag a row, or focus a handle and press Alt with the arrow keys. */
3
+ import { ref } from 'vue'
4
+ import { useListReorder } from './useListReorder'
5
+
6
+ interface Step { id: string; name: string }
7
+
8
+ const steps = ref<Step[]>([
9
+ { id: 's1', name: 'Thaw samples' },
10
+ { id: 's2', name: 'Quench metabolism' },
11
+ { id: 's3', name: 'Extract metabolites' },
12
+ { id: 's4', name: 'Dry down' },
13
+ ])
14
+
15
+ const handles = new Map<string, HTMLElement>()
16
+
17
+ function setHandle(id: string, el: Element | null) {
18
+ if (el instanceof HTMLElement) handles.set(id, el)
19
+ else handles.delete(id)
20
+ }
21
+
22
+ const {
23
+ draggingId,
24
+ dragOverId,
25
+ announcement,
26
+ announcerStyle,
27
+ onDragStart,
28
+ onDragOver,
29
+ onDragLeave,
30
+ onDrop,
31
+ onDragEnd,
32
+ onKeydown,
33
+ } = useListReorder<Step>({
34
+ items: () => steps.value,
35
+ getId: (step) => step.id,
36
+ getLabel: (step) => step.name,
37
+ getElement: (id) => handles.get(id),
38
+ itemNoun: 'Step',
39
+ onReorder: ({ items }) => {
40
+ steps.value = items
41
+ },
42
+ })
43
+
44
+ function initState() {
45
+ return { showAnnouncement: true }
46
+ }
47
+ </script>
48
+
49
+ <template>
50
+ <Story title="Composables/useListReorder">
51
+ <Variant title="Playground" :init-state="initState">
52
+ <template #default="{ state }">
53
+ <div style="padding: 2rem; max-width: 600px;">
54
+ <p class="mint-story-hint">
55
+ Drag a row, or focus its handle and press Alt with the up/down arrow keys.
56
+ </p>
57
+ <ul class="mint-story-list">
58
+ <li
59
+ v-for="(step, index) in steps"
60
+ :key="step.id"
61
+ class="mint-story-row"
62
+ :class="{
63
+ 'mint-story-row--dragging': draggingId === step.id,
64
+ 'mint-story-row--over': dragOverId === step.id,
65
+ }"
66
+ draggable="true"
67
+ @dragstart="onDragStart($event, step, index)"
68
+ @dragover="onDragOver($event, step, index)"
69
+ @dragleave="onDragLeave"
70
+ @drop="onDrop($event, step, index)"
71
+ @dragend="onDragEnd"
72
+ >
73
+ <button
74
+ :ref="(el) => setHandle(step.id, el as Element | null)"
75
+ type="button"
76
+ class="mint-story-handle"
77
+ :aria-label="`Reorder ${step.name}. Press Alt with the arrow keys to move it.`"
78
+ @keydown="onKeydown($event, step, index)"
79
+ >
80
+
81
+ </button>
82
+ <span>{{ index + 1 }}. {{ step.name }}</span>
83
+ </li>
84
+ </ul>
85
+
86
+ <p v-if="state.showAnnouncement" class="mint-story-hint">
87
+ Live region: <em>{{ announcement || '—' }}</em>
88
+ </p>
89
+ <div role="status" aria-live="polite" :style="announcerStyle">{{ announcement }}</div>
90
+ </div>
91
+ </template>
92
+
93
+ <template #controls="{ state }">
94
+ <HstCheckbox v-model="state.showAnnouncement" title="Show live-region text" />
95
+ </template>
96
+ </Variant>
97
+ </Story>
98
+ </template>
99
+
100
+ <style scoped>
101
+ .mint-story-list {
102
+ list-style: none;
103
+ margin: 0;
104
+ padding: 0;
105
+ display: flex;
106
+ flex-direction: column;
107
+ gap: 0.25rem;
108
+ }
109
+
110
+ .mint-story-row {
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 0.5rem;
114
+ padding: 0.5rem 0.75rem;
115
+ border: 1px solid var(--border-color);
116
+ border-radius: 0.5rem;
117
+ background: var(--bg-primary);
118
+ color: var(--text-primary);
119
+ }
120
+
121
+ .mint-story-row--dragging {
122
+ opacity: 0.5;
123
+ }
124
+
125
+ .mint-story-row--over {
126
+ border-color: var(--color-primary);
127
+ }
128
+
129
+ .mint-story-handle {
130
+ padding: 0;
131
+ border: 0;
132
+ background: none;
133
+ color: var(--text-muted);
134
+ cursor: grab;
135
+ font-size: 1rem;
136
+ }
137
+
138
+ .mint-story-hint {
139
+ margin: 0 0 0.75rem;
140
+ color: var(--text-secondary);
141
+ font-size: 0.8125rem;
142
+ }
143
+ </style>
@@ -0,0 +1,242 @@
1
+ import { nextTick, ref, type Ref } from 'vue'
2
+
3
+ /** Inline style for a visually hidden `aria-live` region (no global utility class in the SDK). */
4
+ export const REORDER_ANNOUNCER_STYLE = Object.freeze({
5
+ position: 'absolute',
6
+ width: '1px',
7
+ height: '1px',
8
+ padding: '0',
9
+ margin: '-1px',
10
+ overflow: 'hidden',
11
+ clip: 'rect(0, 0, 0, 0)',
12
+ whiteSpace: 'nowrap',
13
+ border: '0',
14
+ })
15
+
16
+ export interface UseReorderAnnouncerReturn {
17
+ /** Bind to a polite live region's text content. */
18
+ announcement: Ref<string>
19
+ /** Publishes a message to the live region. */
20
+ announce: (message: string) => void
21
+ /** Bind to the live region's `style` so it stays visually hidden. */
22
+ announcerStyle: typeof REORDER_ANNOUNCER_STYLE
23
+ }
24
+
25
+ /**
26
+ * Screen-reader announcements for drag/keyboard move interactions. Bind
27
+ * `announcement` to a `role="status" aria-live="polite"` element styled with
28
+ * `announcerStyle`.
29
+ */
30
+ export function useReorderAnnouncer(): UseReorderAnnouncerReturn {
31
+ const announcement = ref('')
32
+
33
+ function announce(message: string) {
34
+ // Re-announce identical consecutive messages by clearing first.
35
+ if (announcement.value === message) announcement.value = ''
36
+ void nextTick(() => {
37
+ announcement.value = message
38
+ })
39
+ }
40
+
41
+ return { announcement, announce, announcerStyle: REORDER_ANNOUNCER_STYLE }
42
+ }
43
+
44
+ export interface ListReorderMove<T> {
45
+ /** Index the item moved from, within `items()`. */
46
+ fromIndex: number
47
+ /** Index the item moved to, within `items()`. */
48
+ toIndex: number
49
+ /** The reordered array (a copy — `items()` is never mutated). */
50
+ items: T[]
51
+ /** Whether the move came from the keyboard or a pointer drag. */
52
+ source: 'keyboard' | 'drag'
53
+ }
54
+
55
+ export interface UseListReorderOptions<T> {
56
+ /** The list being reordered, in its canonical (not merely rendered) order. */
57
+ items: () => T[]
58
+ /** Stable id for an item; falls back to the render index when ids repeat. */
59
+ getId: (item: T, index: number) => string
60
+ /** Applies the move. The composable never mutates the source array. */
61
+ onReorder: (move: ListReorderMove<T>) => void
62
+ /** Human-readable name used in announcements. Defaults to the item's id. */
63
+ getLabel?: (item: T, index: number) => string
64
+ /** Blocks every reorder while it returns `true` (readonly / non-sortable lists). */
65
+ disabled?: () => boolean
66
+ /** Resolves the focusable element for an id so keyboard moves keep focus on the item. */
67
+ getElement?: (id: string) => HTMLElement | null | undefined
68
+ /** Noun used in announcements, e.g. `'Reagent'`. Default `'Item'`. */
69
+ itemNoun?: string
70
+ }
71
+
72
+ export interface UseListReorderReturn<T> {
73
+ /** Id of the item currently being dragged, or `null`. */
74
+ draggingId: Ref<string | null>
75
+ /** Id of the item the pointer is hovering as a drop target, or `null`. */
76
+ dragOverId: Ref<string | null>
77
+ isDragging: (id: string) => boolean
78
+ isDragOver: (id: string) => boolean
79
+ announcement: Ref<string>
80
+ announcerStyle: typeof REORDER_ANNOUNCER_STYLE
81
+ announce: (message: string) => void
82
+ /** Bind to the draggable element's `@dragstart`. */
83
+ onDragStart: (event: DragEvent, item: T, index: number) => void
84
+ /** Bind to `@dragover` — calls `preventDefault()` so the drop is accepted. */
85
+ onDragOver: (event: DragEvent, item: T, index: number) => void
86
+ /** Bind to `@dragleave`. */
87
+ onDragLeave: () => void
88
+ /** Bind to `@drop`. */
89
+ onDrop: (event: DragEvent, item: T, index: number) => void
90
+ /** Bind to `@dragend`. */
91
+ onDragEnd: () => void
92
+ /**
93
+ * Bind to the item's (or its drag handle's) `@keydown`: Alt+ArrowUp / Alt+ArrowLeft
94
+ * moves the item one position earlier, Alt+ArrowDown / Alt+ArrowRight one later.
95
+ */
96
+ onKeydown: (event: KeyboardEvent, item: T, index: number) => void
97
+ /** Moves an item between two indices of `items()` programmatically. */
98
+ move: (fromIndex: number, toIndex: number, source?: 'keyboard' | 'drag') => void
99
+ }
100
+
101
+ /** Pure helper: a copy of `items` with the entry at `from` moved to `to`. */
102
+ export function reorderList<T>(items: T[], from: number, to: number): T[] {
103
+ const next = [...items]
104
+ const [moved] = next.splice(from, 1)
105
+ next.splice(to, 0, moved)
106
+ return next
107
+ }
108
+
109
+ /**
110
+ * Keyboard-accessible list reordering shared by the SDK's drag-to-reorder lists.
111
+ * It owns the HTML5 drag state, the drop-index maths, and an Alt+Arrow keyboard
112
+ * alternative that announces every move through a polite live region — so a
113
+ * reorder never depends on a pointer.
114
+ */
115
+ export function useListReorder<T>(options: UseListReorderOptions<T>): UseListReorderReturn<T> {
116
+ const draggingId = ref<string | null>(null)
117
+ const dragOverId = ref<string | null>(null)
118
+ const { announcement, announce, announcerStyle } = useReorderAnnouncer()
119
+ const noun = options.itemNoun ?? 'Item'
120
+
121
+ function isDisabled(): boolean {
122
+ return options.disabled?.() === true
123
+ }
124
+
125
+ function idFor(item: T, index: number): string {
126
+ return options.getId(item, index)
127
+ }
128
+
129
+ /** Index within `items()`, resolved by id so filtered/sorted renders still map back. */
130
+ function indexOf(item: T, renderIndex: number): number {
131
+ const id = idFor(item, renderIndex)
132
+ const found = options.items().findIndex((candidate, index) => idFor(candidate, index) === id)
133
+ return found === -1 ? renderIndex : found
134
+ }
135
+
136
+ function labelFor(index: number): string {
137
+ const item = options.items()[index]
138
+ if (item === undefined) return noun
139
+ return options.getLabel?.(item, index) ?? idFor(item, index)
140
+ }
141
+
142
+ function clearDragState() {
143
+ draggingId.value = null
144
+ dragOverId.value = null
145
+ }
146
+
147
+ function move(fromIndex: number, toIndex: number, source: 'keyboard' | 'drag' = 'keyboard') {
148
+ if (isDisabled()) return
149
+ const items = options.items()
150
+ if (
151
+ fromIndex === toIndex ||
152
+ fromIndex < 0 ||
153
+ toIndex < 0 ||
154
+ fromIndex >= items.length ||
155
+ toIndex >= items.length
156
+ ) return
157
+
158
+ const movedId = idFor(items[fromIndex], fromIndex)
159
+ const label = labelFor(fromIndex)
160
+ options.onReorder({
161
+ fromIndex,
162
+ toIndex,
163
+ items: reorderList(items, fromIndex, toIndex),
164
+ source,
165
+ })
166
+ announce(`${label} moved to position ${toIndex + 1} of ${items.length}.`)
167
+ void nextTick(() => options.getElement?.(movedId)?.focus())
168
+ }
169
+
170
+ function onDragStart(event: DragEvent, item: T, index: number) {
171
+ if (isDisabled()) return
172
+ const id = idFor(item, index)
173
+ draggingId.value = id
174
+ if (event.dataTransfer) {
175
+ event.dataTransfer.effectAllowed = 'move'
176
+ event.dataTransfer.setData('text/plain', id)
177
+ }
178
+ }
179
+
180
+ function onDragOver(event: DragEvent, item: T, index: number) {
181
+ if (isDisabled() || draggingId.value === null) return
182
+ event.preventDefault()
183
+ if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'
184
+ const id = idFor(item, index)
185
+ dragOverId.value = id === draggingId.value ? null : id
186
+ }
187
+
188
+ function onDragLeave() {
189
+ dragOverId.value = null
190
+ }
191
+
192
+ function onDrop(event: DragEvent, item: T, index: number) {
193
+ if (isDisabled() || draggingId.value === null) {
194
+ clearDragState()
195
+ return
196
+ }
197
+ event.preventDefault()
198
+
199
+ const items = options.items()
200
+ const fromIndex = items.findIndex((candidate, i) => idFor(candidate, i) === draggingId.value)
201
+ const toIndex = indexOf(item, index)
202
+ clearDragState()
203
+ if (fromIndex === -1) return
204
+ move(fromIndex, toIndex, 'drag')
205
+ }
206
+
207
+ function onDragEnd() {
208
+ clearDragState()
209
+ }
210
+
211
+ function onKeydown(event: KeyboardEvent, item: T, index: number) {
212
+ if (isDisabled() || !event.altKey) return
213
+ const direction =
214
+ event.key === 'ArrowUp' || event.key === 'ArrowLeft'
215
+ ? -1
216
+ : event.key === 'ArrowDown' || event.key === 'ArrowRight'
217
+ ? 1
218
+ : 0
219
+ if (direction === 0) return
220
+
221
+ event.preventDefault()
222
+ const fromIndex = indexOf(item, index)
223
+ move(fromIndex, fromIndex + direction, 'keyboard')
224
+ }
225
+
226
+ return {
227
+ draggingId,
228
+ dragOverId,
229
+ isDragging: (id: string) => draggingId.value === id,
230
+ isDragOver: (id: string) => dragOverId.value === id,
231
+ announcement,
232
+ announcerStyle,
233
+ announce,
234
+ onDragStart,
235
+ onDragOver,
236
+ onDragLeave,
237
+ onDrop,
238
+ onDragEnd,
239
+ onKeydown,
240
+ move,
241
+ }
242
+ }
@@ -27,8 +27,8 @@ export interface UseMenuKeyboardReturn {
27
27
 
28
28
  /**
29
29
  * Shared keyboard contract for menu- and listbox-style disclosure panels (the
30
- * WAI-ARIA menu button pattern). Used by AppAvatarMenu and DropdownButton; ActionMenu
31
- * implements the same contract inline.
30
+ * WAI-ARIA menu button pattern). Used by ActionMenu, AppAvatarMenu and DropdownButton,
31
+ * each pairing it with `useDropdownState` for the disclosure state itself.
32
32
  */
33
33
  export function useMenuKeyboard(options: UseMenuKeyboardOptions): UseMenuKeyboardReturn {
34
34
  function focusItem(position: 'first' | 'last') {
@@ -1,4 +1,5 @@
1
1
  import { computed, ref, shallowRef, type ComputedRef, type Ref, type ShallowRef } from 'vue'
2
+ import { readErrorMessage } from '../utils/errors'
2
3
 
3
4
  export interface OptimisticMutationContext<TVariables, TSnapshot> {
4
5
  variables: TVariables
@@ -94,18 +95,3 @@ export function useOptimisticMutation<TVariables, TData, TSnapshot = unknown>(
94
95
  reset,
95
96
  }
96
97
  }
97
-
98
- function readErrorMessage(value: unknown): string {
99
- if (value instanceof Error && value.message) return value.message
100
- if (typeof value === 'string' && value.trim()) return value
101
- if (
102
- typeof value === 'object'
103
- && value !== null
104
- && 'message' in value
105
- && typeof (value as { message?: unknown }).message === 'string'
106
- && (value as { message: string }).message.trim()
107
- ) {
108
- return (value as { message: string }).message
109
- }
110
- return 'Request failed.'
111
- }
@@ -0,0 +1,104 @@
1
+ import type { PickerAdapter, PickerNode, PickerRequest } from '../types/filePicker'
2
+ import type { FileDirectoryListing, ServerMount } from '../types/fileBrowserTypes'
3
+ import { useApi } from './useApi'
4
+ import { getInjectedPlatformContext } from './platformContextHelpers'
5
+ import { pickerFormat } from './filePicker/validation'
6
+
7
+ /** Opaque picker identity; decode before passing a selection to a mount-scoped API. */
8
+ export function encodePlatformPickerPath(mountId: string, path = ''): string {
9
+ return JSON.stringify([mountId, path])
10
+ }
11
+ export function decodePlatformPickerPath(path: string): { mountId: string; path: string } {
12
+ const decoded: unknown = JSON.parse(path)
13
+ if (
14
+ !Array.isArray(decoded) ||
15
+ decoded.length !== 2 ||
16
+ !decoded.every((item) => typeof item === 'string') ||
17
+ !decoded[0]
18
+ ) {
19
+ throw new Error('Invalid platform picker path.')
20
+ }
21
+ return { mountId: decoded[0], path: decoded[1] }
22
+ }
23
+
24
+ /** Adapt the authenticated MINT mount API without exposing absolute host paths. */
25
+ export function usePlatformFilePickerAdapter(options: { apiBaseUrl?: string } = {}): PickerAdapter {
26
+ const api = useApi({
27
+ baseUrl: options.apiBaseUrl ?? getInjectedPlatformContext()?.platformApiUrl,
28
+ })
29
+ const mountLabels = new Map<string, string>()
30
+ async function listRoot(request?: PickerRequest): Promise<PickerNode[]> {
31
+ try {
32
+ const { mounts } = await api.get<{ mounts: ServerMount[] }>('/filesystem/mounts', {
33
+ signal: request?.signal,
34
+ })
35
+ return mounts.map((mount) => {
36
+ mountLabels.set(mount.id, mount.label)
37
+ return {
38
+ kind: 'folder',
39
+ path: encodePlatformPickerPath(mount.id),
40
+ name: mount.label,
41
+ parentPath: null,
42
+ parentName: 'Server mounts',
43
+ unavailableReason: mount.available === false ? 'This mount is offline.' : undefined,
44
+ }
45
+ })
46
+ } catch (cause) {
47
+ if ((cause as { response?: { status?: number } }).response?.status === 404) return []
48
+ throw cause
49
+ }
50
+ }
51
+ async function listChildren(path: string, request?: PickerRequest): Promise<PickerNode[]> {
52
+ const location = decodePlatformPickerPath(path)
53
+ const query = new URLSearchParams({ mount_id: location.mountId, path: location.path })
54
+ const listing = await api.get<FileDirectoryListing>(`/filesystem/browse?${query}`, {
55
+ signal: request?.signal,
56
+ })
57
+ if (listing.truncated)
58
+ throw new Error(
59
+ 'This directory exceeds the server listing limit. Choose a smaller data directory.',
60
+ )
61
+ return (listing.entries ?? []).map(
62
+ (entry) =>
63
+ ({
64
+ kind: entry.kind === 'directory' ? 'folder' : 'file',
65
+ path: encodePlatformPickerPath(location.mountId, entry.path),
66
+ parentPath: path,
67
+ parentName: `${mountLabels.get(location.mountId) ?? location.mountId}${location.path ? ` / ${location.path}` : ''}`,
68
+ name: entry.name,
69
+ bytes: entry.size_bytes,
70
+ ...(entry.kind === 'directory'
71
+ ? { hasChildren: entry.child_count !== 0 }
72
+ : { format: pickerFormat(entry.name) }),
73
+ }) as PickerNode,
74
+ )
75
+ }
76
+ return {
77
+ listRoot,
78
+ listChildren,
79
+ async search(query, scope, request) {
80
+ const matches: PickerNode[] = []
81
+ const queue = [scope.path]
82
+ const visited = new Set<string>()
83
+ while (queue.length) {
84
+ request?.signal?.throwIfAborted()
85
+ const path = queue.shift()!
86
+ if (visited.has(path)) continue
87
+ visited.add(path)
88
+ if (visited.size > 500)
89
+ throw new Error(
90
+ 'Search reached its folder limit. Search a smaller location or turn off Subfolders.',
91
+ )
92
+ const nodes = path ? await listChildren(path, request) : await listRoot(request)
93
+ for (const node of nodes) {
94
+ if (node.name.toLowerCase().includes(query.toLowerCase())) matches.push(node)
95
+ if (scope.recursive && node.kind === 'folder' && !node.unavailableReason)
96
+ queue.push(node.path)
97
+ }
98
+ if (matches.length > 10000)
99
+ throw new Error('Too many search results. Use a more specific search.')
100
+ }
101
+ return matches
102
+ },
103
+ }
104
+ }
@@ -423,6 +423,7 @@ export function createPluginClient<TClient extends object = GeneratedPluginClien
423
423
  const api = useApi({
424
424
  baseUrl: parts.baseUrl,
425
425
  withAuth: endpoint.requiresAuth !== false,
426
+ typedErrors: true,
426
427
  })
427
428
  const config = endpointRequestConfig(endpoint, parts.query, callOptions)
428
429
 
@@ -469,6 +470,7 @@ export async function uploadPluginEndpoint<TResponse = unknown>(
469
470
  const api = useApi({
470
471
  baseUrl: parts.baseUrl,
471
472
  withAuth: endpoint.requiresAuth !== false,
473
+ typedErrors: true,
472
474
  })
473
475
  const body = endpoint.hasBody
474
476
  ? requestBodyFromPayload(payload, parts.requestPayload, endpoint)
@@ -507,6 +509,7 @@ export async function downloadPluginEndpoint(
507
509
  const api = useApi({
508
510
  baseUrl: parts.baseUrl,
509
511
  withAuth: endpoint.requiresAuth !== false,
512
+ typedErrors: true,
510
513
  })
511
514
  const config = {
512
515
  ...(endpointRequestConfig(endpoint, parts.query, { signal: options.signal }) ?? {}),