@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
@@ -0,0 +1,238 @@
1
+ <script setup lang="ts">
2
+ import { computed, nextTick, ref } from 'vue'
3
+ import IconButton from '../IconButton.vue'
4
+ import BaseButton from '../BaseButton.vue'
5
+ import Skeleton from '../Skeleton.vue'
6
+ import { FILE_BROWSER_ICONS as ICON, formatBytes } from '../fileBrowserPresentation'
7
+ import type { PickerNode, PickerTreeRow, PickerSelectionMode } from '../../types/filePicker'
8
+
9
+ const props = defineProps<{
10
+ emptyTitle?: string
11
+ rows: PickerTreeRow[]
12
+ loading: boolean
13
+ error: string
14
+ query: string
15
+ mode: PickerSelectionMode
16
+ selected: (node: PickerNode) => boolean
17
+ reason: (node: PickerNode) => string
18
+ progress: (node: PickerNode) => string
19
+ focusPath: string
20
+ }>()
21
+ const emit = defineEmits<{
22
+ select: [node: PickerNode, event: MouseEvent | KeyboardEvent]
23
+ enter: [node: PickerNode]
24
+ expand: [node: PickerNode]
25
+ up: []
26
+ retry: []
27
+ parent: [node: PickerNode]
28
+ }>()
29
+ const list = ref<HTMLElement | null>(null)
30
+ const focusablePath = computed(() => {
31
+ const available = props.rows.filter((row) => !props.reason(row.node))
32
+ return (
33
+ available.find((row) => row.node.path === props.focusPath)?.node.path ?? available[0]?.node.path
34
+ )
35
+ })
36
+ function focusRow(path: string) {
37
+ nextTick(() => {
38
+ const target = [...(list.value?.querySelectorAll<HTMLElement>('[role="treeitem"]') ?? [])].find(
39
+ (el) => el.dataset.path === path,
40
+ )
41
+ target?.focus()
42
+ target?.scrollIntoView({ block: 'nearest' })
43
+ })
44
+ }
45
+ function keydown(event: KeyboardEvent, row: PickerTreeRow) {
46
+ if (props.reason(row.node)) return
47
+ const available = props.rows.filter((item) => !props.reason(item.node))
48
+ const index = available.findIndex((item) => item.node.path === row.node.path)
49
+ if (['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) {
50
+ event.preventDefault()
51
+ const target =
52
+ event.key === 'Home'
53
+ ? available[0]
54
+ : event.key === 'End'
55
+ ? available[available.length - 1]
56
+ : available[
57
+ Math.max(
58
+ 0,
59
+ Math.min(available.length - 1, index + (event.key === 'ArrowDown' ? 1 : -1)),
60
+ )
61
+ ]
62
+ if (target) {
63
+ emit('select', target.node, event)
64
+ focusRow(target.node.path)
65
+ }
66
+ } else if (event.key === 'ArrowRight') {
67
+ event.preventDefault()
68
+ if (row.node.kind === 'folder' && !row.expanded) emit('expand', row.node)
69
+ else if (row.expanded) {
70
+ const next = available[index + 1]
71
+ if (next && next.depth > row.depth) {
72
+ emit('select', next.node, event)
73
+ focusRow(next.node.path)
74
+ }
75
+ }
76
+ } else if (event.key === 'ArrowLeft') {
77
+ event.preventDefault()
78
+ if (row.expanded) emit('expand', row.node)
79
+ else {
80
+ const parent = props.rows
81
+ .slice(0, props.rows.indexOf(row))
82
+ .reverse()
83
+ .find((item) => item.depth < row.depth)
84
+ if (parent) {
85
+ emit('select', parent.node, event)
86
+ focusRow(parent.node.path)
87
+ } else emit('up')
88
+ }
89
+ } else if (event.key === 'Enter') {
90
+ event.preventDefault()
91
+ emit('enter', row.node)
92
+ } else if (event.key === ' ') {
93
+ event.preventDefault()
94
+ emit('select', row.node, event)
95
+ }
96
+ }
97
+ function parts(name: string) {
98
+ const index = props.query ? name.toLowerCase().indexOf(props.query.toLowerCase()) : -1
99
+ return index < 0
100
+ ? [name, '', '']
101
+ : [
102
+ name.slice(0, index),
103
+ name.slice(index, index + props.query.length),
104
+ name.slice(index + props.query.length),
105
+ ]
106
+ }
107
+ </script>
108
+
109
+ <template>
110
+ <div
111
+ ref="list"
112
+ class="mint-picker__tree"
113
+ role="tree"
114
+ aria-label="Folders and files"
115
+ :aria-multiselectable="mode === 'multi-file' || mode === 'folder+files'"
116
+ :aria-busy="loading"
117
+ >
118
+ <div v-if="loading" class="mint-picker__skeleton">
119
+ <Skeleton v-for="n in 6" :key="n" width="100%" height="36px" />
120
+ </div>
121
+ <div v-else-if="error" role="alert" class="mint-picker__empty">
122
+ <span>{{ error }}</span
123
+ ><BaseButton variant="secondary" size="sm" @click="emit('retry')">Try again</BaseButton>
124
+ </div>
125
+ <div v-else-if="!rows.length" class="mint-picker__empty" role="status">
126
+ <strong>{{
127
+ query ? 'No matching folders or files' : emptyTitle || 'No readable files'
128
+ }}</strong>
129
+ <span>{{
130
+ query
131
+ ? 'Try a different search or include subfolders.'
132
+ : 'Choose a location, go up a level, or choose local files.'
133
+ }}</span>
134
+ </div>
135
+ <template v-else v-for="row in rows" :key="row.node.path">
136
+ <div
137
+ role="treeitem"
138
+ :data-path="row.node.path"
139
+ :aria-level="row.depth + 1"
140
+ :aria-selected="selected(row.node)"
141
+ :aria-disabled="!!reason(row.node)"
142
+ :aria-expanded="row.node.kind === 'folder' && !reason(row.node) ? row.expanded : undefined"
143
+ :tabindex="!reason(row.node) && row.node.path === focusablePath ? 0 : -1"
144
+ class="mint-picker__row"
145
+ :class="{
146
+ 'mint-picker__row--selected': selected(row.node),
147
+ 'mint-picker__row--unreadable': !!reason(row.node),
148
+ 'mint-picker__row--child': row.depth > 0,
149
+ }"
150
+ :style="{ '--picker-depth': row.depth }"
151
+ :title="reason(row.node) || row.node.name"
152
+ @click="!reason(row.node) && emit('select', row.node, $event)"
153
+ @dblclick="!reason(row.node) && emit('enter', row.node)"
154
+ @keydown="keydown($event, row)"
155
+ >
156
+ <IconButton
157
+ v-if="row.node.kind === 'folder' && row.node.hasChildren !== false && !reason(row.node)"
158
+ size="sm"
159
+ class="mint-picker__disclosure"
160
+ :label="`${row.expanded ? 'Collapse' : 'Expand'} ${row.node.name}`"
161
+ tabindex="-1"
162
+ :loading="row.loading"
163
+ @click.stop="emit('expand', row.node)"
164
+ @dblclick.stop
165
+ >
166
+ <svg
167
+ viewBox="0 0 16 16"
168
+ fill="currentColor"
169
+ aria-hidden="true"
170
+ :class="{ 'mint-picker__chevron--open': row.expanded }"
171
+ >
172
+ <path d="m6 3 5 5-5 5z" />
173
+ </svg>
174
+ </IconButton>
175
+ <span v-else class="mint-picker__disclosure-spacer" />
176
+ <svg
177
+ class="mint-picker__node-icon"
178
+ viewBox="0 0 24 24"
179
+ fill="none"
180
+ stroke="currentColor"
181
+ stroke-width="1.7"
182
+ aria-hidden="true"
183
+ >
184
+ <path :d="row.node.kind === 'folder' ? ICON.folder : ICON.file" />
185
+ </svg>
186
+ <span class="mint-picker__row-name">
187
+ <span class="mint-picker__name"
188
+ >{{ parts(row.node.name)[0]
189
+ }}<mark v-if="parts(row.node.name)[1]">{{ parts(row.node.name)[1] }}</mark
190
+ >{{ parts(row.node.name)[2] }}</span
191
+ >
192
+ <span v-if="row.node.kind === 'folder' && row.node.formats" class="mint-picker__formats">
193
+ <span v-for="(count, format) in row.node.formats" :key="format"
194
+ >{{ format.toUpperCase() }} ×{{ count }}</span
195
+ >
196
+ </span>
197
+ <span v-else-if="query && row.node.parentName" class="mint-picker__parent-name">{{
198
+ row.node.parentName
199
+ }}</span>
200
+ </span>
201
+ <span class="mint-picker__row-meta">
202
+ <span
203
+ v-if="row.node.kind === 'folder' && progress(row.node)"
204
+ :class="{
205
+ 'mint-picker__partial':
206
+ selected(row.node) &&
207
+ progress(row.node).split(' / ')[0] !== String(row.node.fileCount),
208
+ }"
209
+ >{{ progress(row.node) }}</span
210
+ >
211
+ <span v-if="!reason(row.node) && row.node.bytes != null">{{
212
+ formatBytes(row.node.bytes)
213
+ }}</span>
214
+ <span v-if="reason(row.node)" aria-label="Unavailable">ⓘ</span>
215
+ </span>
216
+ <BaseButton
217
+ v-if="
218
+ query &&
219
+ mode === 'folder' &&
220
+ row.node.kind === 'file' &&
221
+ row.node.parentPath &&
222
+ !reason(row.node)
223
+ "
224
+ variant="ghost"
225
+ size="sm"
226
+ @click.stop="emit('parent', row.node)"
227
+ >Select its folder</BaseButton
228
+ >
229
+ </div>
230
+ <div v-if="row.error" class="mint-picker__branch-error" role="alert">
231
+ {{ row.error
232
+ }}<BaseButton variant="ghost" size="sm" @click="emit('expand', row.node)"
233
+ >Close branch</BaseButton
234
+ >
235
+ </div>
236
+ </template>
237
+ </div>
238
+ </template>
@@ -72,7 +72,3 @@ const gridStyle = computed(() => ({
72
72
  <slot :name="`section:${section.id}:after`" :form="builder.form" />
73
73
  </div>
74
74
  </template>
75
-
76
- <style>
77
- @import '../../styles/components/form-builder.css';
78
- </style>
@@ -243,7 +243,3 @@ const sampleTypeButtons = [
243
243
  </div>
244
244
  </Teleport>
245
245
  </template>
246
-
247
- <style>
248
- @import '../../styles/components/well-edit-popup.css';
249
- </style>
@@ -42,10 +42,27 @@ export interface ControlSectionConfig {
42
42
  actions?: SidebarToolSectionAction[]
43
43
  columns?: 1 | 2 | 3
44
44
  condition?: FieldCondition
45
+ /** The access policy. Preferred over the flat fields below. */
45
46
  access?: AccessPolicy
47
+ /**
48
+ * @deprecated Since MINT 1.2; use `access.visibleFor` instead. The flat
49
+ * duplicate is removed in 1.3.
50
+ */
46
51
  visibleFor?: AccessAudienceInput
52
+ /**
53
+ * @deprecated Since MINT 1.2; use `access.requiresAdmin` instead. The flat
54
+ * duplicate is removed in 1.3.
55
+ */
47
56
  requiresAdmin?: boolean
57
+ /**
58
+ * @deprecated Since MINT 1.2; use `access.permissions` instead. The flat
59
+ * duplicate is removed in 1.3.
60
+ */
48
61
  permissions?: readonly string[]
62
+ /**
63
+ * @deprecated Since MINT 1.2; use `access.anyPermissions` instead. The flat
64
+ * duplicate is removed in 1.3.
65
+ */
49
66
  anyPermissions?: readonly string[]
50
67
  defaultOpen?: boolean
51
68
  showToggle?: boolean
@@ -86,10 +103,27 @@ export interface ControlDefinition {
86
103
  pattern?: string | { value: string; message: string }
87
104
  validation?: FieldValidation
88
105
  condition?: FieldCondition
106
+ /** The access policy. Preferred over the flat fields below. */
89
107
  access?: AccessPolicy
108
+ /**
109
+ * @deprecated Since MINT 1.2; use `access.visibleFor` instead. The flat
110
+ * duplicate is removed in 1.3.
111
+ */
90
112
  visibleFor?: AccessAudienceInput
113
+ /**
114
+ * @deprecated Since MINT 1.2; use `access.requiresAdmin` instead. The flat
115
+ * duplicate is removed in 1.3.
116
+ */
91
117
  requiresAdmin?: boolean
118
+ /**
119
+ * @deprecated Since MINT 1.2; use `access.permissions` instead. The flat
120
+ * duplicate is removed in 1.3.
121
+ */
92
122
  permissions?: readonly string[]
123
+ /**
124
+ * @deprecated Since MINT 1.2; use `access.anyPermissions` instead. The flat
125
+ * duplicate is removed in 1.3.
126
+ */
93
127
  anyPermissions?: readonly string[]
94
128
  options?: readonly ControlOption[]
95
129
  section?: string
@@ -0,0 +1,58 @@
1
+ import type { PickerAdapter, PickerFile, PickerNode } from '../../types/filePicker'
2
+ import { pickerFormat } from './validation'
3
+
4
+ /** Construct metadata only: no FileReader, arrayBuffer or upload. */
5
+ export function localFilesAdapter(
6
+ files: File[],
7
+ relativePaths?: (string | undefined)[],
8
+ ): PickerAdapter {
9
+ const nodes = new Map<string, PickerNode>()
10
+ for (const [index, file] of files.entries()) {
11
+ const relative = relativePaths?.[index] || file.webkitRelativePath || file.name
12
+ const parts = relative.split('/')
13
+ let parent: string | null = null
14
+ for (let i = 0; i < parts.length - 1; i++) {
15
+ const path = parts.slice(0, i + 1).join('/')
16
+ if (!nodes.has(path))
17
+ nodes.set(path, { kind: 'folder', name: parts[i]!, path, parentPath: parent })
18
+ parent = path
19
+ }
20
+ // Plain file picks can have equal names; preserve both File objects.
21
+ let path = relative
22
+ for (let n = 2; nodes.has(path); n++) path = `${relative} (${n})`
23
+ nodes.set(path, {
24
+ kind: 'file',
25
+ name: file.name,
26
+ path,
27
+ parentPath: parent,
28
+ bytes: file.size,
29
+ format: pickerFormat(file.name),
30
+ file,
31
+ })
32
+ }
33
+ for (const node of nodes.values()) {
34
+ if (node.kind !== 'folder') continue
35
+ const descendants = [...nodes.values()].filter(
36
+ (item): item is PickerFile => item.kind === 'file' && item.path.startsWith(`${node.path}/`),
37
+ )
38
+ node.fileCount = descendants.length
39
+ node.bytes = descendants.reduce((sum, file) => sum + (file.bytes ?? 0), 0)
40
+ node.formats = {}
41
+ descendants.forEach((file) => {
42
+ node.formats![file.format] = (node.formats![file.format] ?? 0) + 1
43
+ })
44
+ }
45
+ const list = (path: string) =>
46
+ [...nodes.values()].filter((node) => (node.parentPath ?? '') === path)
47
+ return {
48
+ listRoot: async () => list(''),
49
+ listChildren: async (path) => list(path),
50
+ search: async (query, scope) =>
51
+ [...nodes.values()].filter((node) => {
52
+ const inScope = scope.recursive
53
+ ? !scope.path || node.path.startsWith(`${scope.path}/`)
54
+ : (node.parentPath ?? '') === scope.path
55
+ return inScope && node.name.toLowerCase().includes(query.toLowerCase())
56
+ }),
57
+ }
58
+ }
@@ -0,0 +1,220 @@
1
+ import { computed, onScopeDispose, ref, shallowRef } from 'vue'
2
+ import type {
3
+ PickerAdapter,
4
+ PickerNode,
5
+ PickerRequest,
6
+ PickerTreeRow,
7
+ } from '../../types/filePicker'
8
+
9
+ export function usePickerNavigation(
10
+ adapter: () => PickerAdapter,
11
+ request: () => PickerRequest,
12
+ systemFilter: (node: PickerNode) => boolean = () => true,
13
+ ) {
14
+ const path = shallowRef('')
15
+ const trail = ref<{ path: string; label: string }[]>([])
16
+ const entries = shallowRef<PickerNode[]>([])
17
+ const nodes = ref(new Map<string, PickerNode>())
18
+ const children = shallowRef(new Map<string, PickerNode[]>())
19
+ const expanded = ref(new Set<string>())
20
+ const expanding = ref(new Set<string>())
21
+ const expansionErrors = ref(new Map<string, string>())
22
+ const loading = shallowRef(false)
23
+ const error = shallowRef('')
24
+ let epoch = 0
25
+ let navigation = 0
26
+ let controller: AbortController | undefined
27
+ const pending = new Map<string, Promise<PickerNode[]>>()
28
+ const childControllers = new Set<AbortController>()
29
+
30
+ function isAllowed(node: PickerNode): boolean {
31
+ const seen = new Set<string>()
32
+ let current: PickerNode | undefined = node
33
+ while (current && !seen.has(current.path)) {
34
+ if (!systemFilter(current)) return false
35
+ seen.add(current.path)
36
+ current = current.parentPath ? nodes.value.get(current.parentPath) : undefined
37
+ }
38
+ return true
39
+ }
40
+
41
+ function register(items: PickerNode[], parent?: string) {
42
+ for (const item of items)
43
+ nodes.value.set(item.path, {
44
+ ...item,
45
+ parentPath:
46
+ item.parentPath !== undefined
47
+ ? item.parentPath
48
+ : parent !== undefined
49
+ ? parent || null
50
+ : (nodes.value.get(item.path)?.parentPath ?? null),
51
+ })
52
+ }
53
+ function invalidate() {
54
+ ++epoch
55
+ ++navigation
56
+ controller?.abort()
57
+ childControllers.forEach((item) => item.abort())
58
+ childControllers.clear()
59
+ pending.clear()
60
+ children.value = new Map()
61
+ expanding.value.clear()
62
+ expansionErrors.value.clear()
63
+ }
64
+ function clear() {
65
+ invalidate()
66
+ nodes.value.clear()
67
+ expanded.value.clear()
68
+ expanding.value.clear()
69
+ expansionErrors.value.clear()
70
+ entries.value = []
71
+ loading.value = false
72
+ error.value = ''
73
+ }
74
+ async function loadChildren(
75
+ folder: string,
76
+ ancestors: string[] = [],
77
+ refresh = false,
78
+ ): Promise<PickerNode[]> {
79
+ if (ancestors.includes(folder)) throw new Error('The adapter returned a circular folder tree.')
80
+ const cached = children.value.get(folder)
81
+ if (cached) return cached
82
+ const existing = pending.get(folder)
83
+ if (existing) return existing
84
+ const token = epoch
85
+ const abort = new AbortController()
86
+ childControllers.add(abort)
87
+ const task = adapter()
88
+ .listChildren(folder, { ...request(), ...(refresh && { refresh }), signal: abort.signal })
89
+ .then((items) => {
90
+ if (token !== epoch) throw new DOMException('Cancelled', 'AbortError')
91
+ const retained = new Set(items.map((item) => item.path))
92
+ function remove(path: string, seen = new Set<string>()) {
93
+ if (seen.has(path) || path === folder) return
94
+ seen.add(path)
95
+ for (const node of nodes.value.values())
96
+ if (node.parentPath === path) remove(node.path, seen)
97
+ nodes.value.delete(path)
98
+ children.value.delete(path)
99
+ }
100
+ for (const node of nodes.value.values())
101
+ if (node.parentPath === folder && !retained.has(node.path)) remove(node.path)
102
+ register(items, folder)
103
+ children.value = new Map(children.value).set(folder, items)
104
+ return items
105
+ })
106
+ .finally(() => {
107
+ childControllers.delete(abort)
108
+ if (token === epoch) pending.delete(folder)
109
+ })
110
+ pending.set(folder, task)
111
+ return task
112
+ }
113
+ async function load(query = '', recursive = false, refresh = false) {
114
+ const token = ++navigation
115
+ controller?.abort()
116
+ controller = new AbortController()
117
+ const options = { ...request(), ...(refresh && { refresh }), signal: controller.signal }
118
+ loading.value = true
119
+ error.value = ''
120
+ entries.value = []
121
+ try {
122
+ const items = query.trim()
123
+ ? await adapter().search(query.trim(), { path: path.value, recursive }, options)
124
+ : path.value
125
+ ? await loadChildren(path.value, [], refresh)
126
+ : await adapter().listRoot(options)
127
+ if (token !== navigation) return
128
+ register(items, query.trim() ? undefined : path.value)
129
+ entries.value = items
130
+ } catch (cause) {
131
+ if (token === navigation)
132
+ error.value = cause instanceof Error ? cause.message : 'Could not read this location.'
133
+ } finally {
134
+ if (token === navigation) loading.value = false
135
+ }
136
+ }
137
+ async function expand(node: PickerNode) {
138
+ if (node.kind !== 'folder' || node.unavailableReason || !isAllowed(node)) return
139
+ if (expanded.value.has(node.path)) {
140
+ expanded.value.delete(node.path)
141
+ return
142
+ }
143
+ expanded.value.add(node.path)
144
+ expanding.value.add(node.path)
145
+ expansionErrors.value.delete(node.path)
146
+ const token = epoch
147
+ try {
148
+ await loadChildren(node.path)
149
+ } catch (cause) {
150
+ if (token === epoch)
151
+ expansionErrors.value.set(
152
+ node.path,
153
+ cause instanceof Error ? cause.message : 'Could not read this folder.',
154
+ )
155
+ } finally {
156
+ if (token === epoch) expanding.value.delete(node.path)
157
+ }
158
+ }
159
+ function enter(node: PickerNode) {
160
+ if (node.kind !== 'folder' || node.unavailableReason || !isAllowed(node)) return
161
+ const index = trail.value.findIndex((crumb) => crumb.path === node.path)
162
+ trail.value =
163
+ index >= 0
164
+ ? trail.value.slice(0, index + 1)
165
+ : [...trail.value, { path: node.path, label: node.name }]
166
+ path.value = node.path
167
+ }
168
+ function up(target?: string) {
169
+ const index =
170
+ target !== undefined
171
+ ? trail.value.findIndex((crumb) => crumb.path === target)
172
+ : trail.value.length - 2
173
+ trail.value = index < 0 ? [] : trail.value.slice(0, index + 1)
174
+ path.value = trail.value[trail.value.length - 1]?.path ?? ''
175
+ }
176
+ const rows = computed(() => {
177
+ const result: PickerTreeRow[] = []
178
+ function visit(items: PickerNode[], depth: number, ancestors: Set<string>) {
179
+ for (const item of items) {
180
+ if (ancestors.has(item.path) || depth > 30) continue
181
+ const node = nodes.value.get(item.path) ?? item
182
+ if (!isAllowed(node)) continue
183
+ result.push({
184
+ node,
185
+ depth,
186
+ expanded: expanded.value.has(item.path),
187
+ loading: expanding.value.has(item.path),
188
+ error: expansionErrors.value.get(item.path),
189
+ })
190
+ if (expanded.value.has(item.path))
191
+ visit(children.value.get(item.path) ?? [], depth + 1, new Set([...ancestors, item.path]))
192
+ }
193
+ }
194
+ visit(entries.value, 0, new Set())
195
+ return result
196
+ })
197
+ onScopeDispose(clear)
198
+ return {
199
+ path,
200
+ trail,
201
+ entries,
202
+ nodes,
203
+ children,
204
+ expanded,
205
+ expanding,
206
+ expansionErrors,
207
+ loading,
208
+ error,
209
+ rows,
210
+ isAllowed,
211
+ clear,
212
+ invalidate,
213
+ register,
214
+ loadChildren,
215
+ load,
216
+ expand,
217
+ enter,
218
+ up,
219
+ }
220
+ }