@morscherlab/mint-sdk 1.2.10 → 1.3.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (841) hide show
  1. package/README.md +45 -137
  2. package/dist/ResourcePickerListInternal-CFAw4vTZ.js +605 -0
  3. package/dist/ResourcePickerListInternal-CFAw4vTZ.js.map +1 -0
  4. package/dist/__stories__/experiment-helpers.d.ts +12 -1
  5. package/dist/__tests__/components/ScientificNumber.test.d.ts +1 -0
  6. package/dist/__tests__/components/SequenceInput.test.d.ts +1 -0
  7. package/dist/__tests__/components/Skeleton.test.d.ts +1 -0
  8. package/dist/__tests__/components/SmartGroupFieldRecipe.test.d.ts +1 -0
  9. package/dist/__tests__/components/SmartGroupModal.test.d.ts +1 -0
  10. package/dist/__tests__/components/StepWizard.test.d.ts +1 -0
  11. package/dist/__tests__/components/TimeRangeInput.test.d.ts +1 -0
  12. package/dist/__tests__/components/UnitInput.test.d.ts +1 -0
  13. package/dist/__tests__/components/WellPlate.keyboard.test.d.ts +1 -0
  14. package/dist/__tests__/components/WellPlate.sampleTypeIndicator.test.d.ts +1 -0
  15. package/dist/__tests__/composables/useChemicalFormula.test.d.ts +1 -0
  16. package/dist/__tests__/composables/useRackEditor.test.d.ts +1 -0
  17. package/dist/__tests__/composables/useSequenceUtils.test.d.ts +1 -0
  18. package/dist/__tests__/styles/baseControlsRules.test.d.ts +1 -0
  19. package/dist/__tests__/styles/controlRefreshRules.test.d.ts +1 -0
  20. package/dist/__tests__/styles/darkModeRules.test.d.ts +1 -0
  21. package/dist/__tests__/styles/menusShellRefresh.test.d.ts +1 -0
  22. package/dist/__tests__/templates/templatesDeprecation.test.d.ts +1 -0
  23. package/dist/__tests__/utils/plotlyTemplate.test.d.ts +1 -0
  24. package/dist/__tests__/utils/sampleTypeMarker.test.d.ts +1 -0
  25. package/dist/{analysisArtifactTypes-DQ0MjwHZ.js → analysisArtifactTypes-Cy3D0Ipg.js} +1 -1
  26. package/dist/{analysisArtifactTypes-DQ0MjwHZ.js.map → analysisArtifactTypes-Cy3D0Ipg.js.map} +1 -1
  27. package/dist/components/ActionMenu.vue.d.ts +17 -5
  28. package/dist/components/AdductText.vue.d.ts +3 -2
  29. package/dist/components/AlertBox.vue.d.ts +11 -14
  30. package/dist/components/AppAvatarMenu.vue.d.ts +18 -28
  31. package/dist/components/AppContainer.vue.d.ts +7 -11
  32. package/dist/components/AppLayout.vue.d.ts +11 -13
  33. package/dist/components/AppSidebar.vue.d.ts +30 -28
  34. package/dist/components/AppToastContainer.vue.d.ts +2 -1
  35. package/dist/components/AppTopBar.vue.d.ts +45 -44
  36. package/dist/components/ArtifactSaveDialog.vue.d.ts +3 -2
  37. package/dist/components/ArtifactSelectorModal.vue.d.ts +5 -6
  38. package/dist/components/AuditTrail.vue.d.ts +17 -18
  39. package/dist/components/AutoGroupModal.vue.d.ts +4 -3
  40. package/dist/components/Avatar.vue.d.ts +2 -1
  41. package/dist/components/BaseButton.vue.d.ts +11 -13
  42. package/dist/components/BaseCheckbox.vue.d.ts +7 -1
  43. package/dist/components/BaseInput.vue.d.ts +15 -2
  44. package/dist/components/BaseModal.vue.d.ts +20 -23
  45. package/dist/components/BasePill.vue.d.ts +10 -13
  46. package/dist/components/BaseRadioGroup.vue.d.ts +2 -1
  47. package/dist/components/BaseSelect.vue.d.ts +3 -2
  48. package/dist/components/BaseSlider.vue.d.ts +18 -1
  49. package/dist/components/BaseTabs.vue.d.ts +2 -1
  50. package/dist/components/BaseTextarea.vue.d.ts +2 -1
  51. package/dist/components/BaseToggle.vue.d.ts +2 -1
  52. package/dist/components/BatchProgressList.vue.d.ts +22 -26
  53. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +7 -14
  54. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +6 -13
  55. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +7 -14
  56. package/dist/components/BioTemplateRenderer.vue.d.ts +2 -1
  57. package/dist/components/Breadcrumb.vue.d.ts +13 -16
  58. package/dist/components/Calendar.vue.d.ts +21 -23
  59. package/dist/components/ChartContainer.vue.d.ts +19 -25
  60. package/dist/components/ChemicalFormula.vue.d.ts +7 -11
  61. package/dist/components/CollapsibleCard.vue.d.ts +7 -11
  62. package/dist/components/ComponentBindingRenderer.vue.d.ts +2 -1
  63. package/dist/components/ConcentrationInput.vue.d.ts +2 -1
  64. package/dist/components/ConfirmDialog.vue.d.ts +10 -13
  65. package/dist/components/ControlWorkspaceView.vue.d.ts +9 -18
  66. package/dist/components/DataFrame.vue.d.ts +27 -24
  67. package/dist/components/DatePicker.vue.d.ts +3 -6
  68. package/dist/components/DateTimePicker.vue.d.ts +3 -6
  69. package/dist/components/Divider.vue.d.ts +3 -2
  70. package/dist/components/DoseCalculator.vue.d.ts +2 -1
  71. package/dist/components/DoseCalculatorVolumeField.vue.d.ts +2 -1
  72. package/dist/components/DoseDesignWorkspaceView.vue.d.ts +9 -18
  73. package/dist/components/EmptyState.vue.d.ts +9 -12
  74. package/dist/components/ExperimentCodeBadge.vue.d.ts +2 -1
  75. package/dist/components/ExperimentDataViewer.vue.d.ts +13 -15
  76. package/dist/components/ExperimentPopover.vue.d.ts +3 -5
  77. package/dist/components/ExperimentSelectorModal.vue.d.ts +3 -4
  78. package/dist/components/ExperimentTimeline.vue.d.ts +13 -15
  79. package/dist/components/FilePicker.vue.d.ts +10 -5
  80. package/dist/components/FileUploader.vue.d.ts +8 -16
  81. package/dist/components/FitPanel.vue.d.ts +26 -16
  82. package/dist/components/FormActions.vue.d.ts +2 -1
  83. package/dist/components/FormBuilder.vue.d.ts +54 -250
  84. package/dist/components/FormField.vue.d.ts +14 -16
  85. package/dist/components/FormulaInput.vue.d.ts +2 -1
  86. package/dist/components/GeneratedPathInput.vue.d.ts +6 -5
  87. package/dist/components/GeneratedPlotlyResult.vue.d.ts +2 -1
  88. package/dist/components/GeneratedPluginView.vue.d.ts +2 -1
  89. package/dist/components/GeneratedResultRenderer.vue.d.ts +2 -1
  90. package/dist/components/GroupAssigner.vue.d.ts +2 -1
  91. package/dist/components/IconButton.vue.d.ts +7 -11
  92. package/dist/components/InstrumentStateBadge.vue.d.ts +4 -3
  93. package/dist/components/JobsStatusTray.vue.d.ts +13 -29
  94. package/dist/components/LayoutResizeHandle.vue.d.ts +2 -1
  95. package/dist/components/LoadingSpinner.vue.d.ts +2 -1
  96. package/dist/components/MobileSupportGate.vue.d.ts +5 -13
  97. package/dist/components/MoleculeInput.vue.d.ts +3 -4
  98. package/dist/components/MultiSelect.vue.d.ts +2 -1
  99. package/dist/components/NumberInput.vue.d.ts +2 -1
  100. package/dist/components/PlateEditor.vue.d.ts +147 -0
  101. package/dist/components/PlotlyChart.vue.d.ts +21 -31
  102. package/dist/components/PluginIcon.vue.d.ts +2 -1
  103. package/dist/components/PluginWorkspaceView.props.d.ts +2 -6
  104. package/dist/components/PluginWorkspaceView.vue.d.ts +23 -51
  105. package/dist/components/ProgressBar.vue.d.ts +4 -9
  106. package/dist/components/ProtocolStepEditor.vue.d.ts +7 -5
  107. package/dist/components/ProtocolStepParameterField.vue.d.ts +2 -1
  108. package/dist/components/ReagentEditor.vue.d.ts +2 -1
  109. package/dist/components/ReagentList.presentation.d.ts +2 -1
  110. package/dist/components/ReagentList.vue.d.ts +15 -17
  111. package/dist/components/ResourceCard.vue.d.ts +18 -17
  112. package/dist/components/RuntimeUpdateNotice.vue.d.ts +3 -2
  113. package/dist/components/SampleHierarchyTree.vue.d.ts +21 -15
  114. package/dist/components/SampleLegend.vue.d.ts +2 -1
  115. package/dist/components/SampleSelector.vue.d.ts +15 -21
  116. package/dist/components/SampleSelectorGroupNode.vue.d.ts +2 -1
  117. package/dist/components/SampleSelectorSampleRow.vue.d.ts +2 -5
  118. package/dist/components/ScheduleCalendar.vue.d.ts +34 -36
  119. package/dist/components/ScientificNumber.vue.d.ts +3 -2
  120. package/dist/components/SearchableSelect.vue.d.ts +14 -32
  121. package/dist/components/SecretInput.vue.d.ts +2 -1
  122. package/dist/components/SectionCard.vue.d.ts +13 -14
  123. package/dist/components/SegmentedControl.vue.d.ts +2 -1
  124. package/dist/components/SequenceInput.vue.d.ts +10 -21
  125. package/dist/components/SequenceProgressBar.vue.d.ts +2 -1
  126. package/dist/components/SettingsModal.schema.d.ts +1 -1
  127. package/dist/components/SettingsModal.vue.d.ts +28 -25
  128. package/dist/components/Skeleton.vue.d.ts +2 -1
  129. package/dist/components/SmartGroup.types.d.ts +2 -2
  130. package/dist/components/SmartGroupFieldRecipe.groups.d.ts +2 -0
  131. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +4 -7
  132. package/dist/components/SmartGroupManual.vue.d.ts +3 -2
  133. package/dist/components/SmartGroupModal.vue.d.ts +6 -4
  134. package/dist/components/StatusIndicator.vue.d.ts +2 -1
  135. package/dist/components/StepWizard.vue.d.ts +26 -27
  136. package/dist/components/TagsInput.vue.d.ts +3 -5
  137. package/dist/components/ThemeToggle.vue.d.ts +2 -1
  138. package/dist/components/TimePicker.vue.d.ts +6 -6
  139. package/dist/components/TimeRangeInput.vue.d.ts +9 -2
  140. package/dist/components/Tooltip.vue.d.ts +8 -18
  141. package/dist/components/UnitInput.vue.d.ts +2 -1
  142. package/dist/components/WellPlate.colors.d.ts +2 -4
  143. package/dist/components/WellPlate.rendering.d.ts +7 -1
  144. package/dist/components/WellPlate.sampleDrop.d.ts +2 -0
  145. package/dist/components/WellPlate.vue.d.ts +22 -39
  146. package/dist/components/index.d.ts +34 -31
  147. package/dist/components/index.js +5 -8
  148. package/dist/components/internal/ActionItemInternal.vue.d.ts +11 -13
  149. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +14 -23
  150. package/dist/components/internal/AppTopBarPillNavInternal.vue.d.ts +2 -1
  151. package/dist/components/internal/CalendarGridPanelInternal.vue.d.ts +2 -1
  152. package/dist/components/internal/DataStateInternal.vue.d.ts +7 -11
  153. package/dist/components/internal/{FilePickerTreeInternal.vue.d.ts → FilePickerListInternal.vue.d.ts} +9 -6
  154. package/dist/components/internal/FilePickerPreviewInternal.vue.d.ts +4 -3
  155. package/dist/components/internal/FormFieldRendererInternal.vue.d.ts +16 -18
  156. package/dist/components/internal/FormSectionRenderer.vue.d.ts +36 -32
  157. package/dist/components/internal/PlateEditorBindingInternal.vue.d.ts +8 -0
  158. package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +65 -0
  159. package/dist/components/internal/SettingsPaneHeadInternal.vue.d.ts +15 -0
  160. package/dist/components/internal/WellEditPopupInternal.vue.d.ts +12 -4
  161. package/dist/components/internal/WorkspaceControlsInternal.vue.d.ts +6 -13
  162. package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +19 -42
  163. package/dist/components/internal/WorkspaceSidebarInternal.vue.d.ts +9 -19
  164. package/dist/components/internal/filePickerPresentation.d.ts +8 -0
  165. package/dist/components/internal/generatedJobsApi.d.ts +9 -0
  166. package/dist/{components-Bbg_gtzF.js → components-GDDvof8D.js} +7088 -5955
  167. package/dist/components-GDDvof8D.js.map +1 -0
  168. package/dist/composables/controlSchemaTypes.d.ts +4 -43
  169. package/dist/composables/createFilePickerAdapter.d.ts +5 -1
  170. package/dist/composables/filePicker/usePickerNavigation.d.ts +7 -2
  171. package/dist/composables/index.d.ts +2 -13
  172. package/dist/composables/index.js +7 -11
  173. package/dist/composables/pluginWorkspaceModel.d.ts +1 -3
  174. package/dist/composables/useAutoGroup.d.ts +10 -0
  175. package/dist/composables/useBioTemplateComponents.d.ts +15 -3
  176. package/dist/composables/useBioTemplateControls.d.ts +5 -1
  177. package/dist/composables/useBioTemplatePackWorkspace.d.ts +2 -0
  178. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +5 -1
  179. package/dist/composables/useBioTemplateWorkspace.d.ts +5 -1
  180. package/dist/composables/useChemicalFormula.d.ts +2 -1
  181. package/dist/composables/useDropdownState.d.ts +5 -0
  182. package/dist/composables/useExperimentSave.d.ts +5 -1
  183. package/dist/composables/useFileBrowser.d.ts +3 -2
  184. package/dist/composables/useFilePicker.d.ts +25 -13
  185. package/dist/composables/useFocusTrap.d.ts +2 -1
  186. package/dist/composables/useGroupAssignment.d.ts +5 -1
  187. package/dist/composables/useJobsStatusTray.d.ts +5 -5
  188. package/dist/composables/useMenuKeyboard.d.ts +3 -3
  189. package/dist/composables/usePlatformContext.d.ts +3 -0
  190. package/dist/composables/usePluginClient.d.ts +1 -1
  191. package/dist/composables/useRackEditor.d.ts +36 -1
  192. package/dist/composables/useSequenceUtils.d.ts +8 -0
  193. package/dist/composables/useTemplateCollection.d.ts +5 -1
  194. package/dist/composables/workspaceRecipes.d.ts +1 -0
  195. package/dist/composables-D6uqp0UF.js +591 -0
  196. package/dist/composables-D6uqp0UF.js.map +1 -0
  197. package/dist/{errors-BLuqMee5.js → experiment-DgllIc3I.js} +89 -333
  198. package/dist/experiment-DgllIc3I.js.map +1 -0
  199. package/dist/{experiment-utils-CV2Wza3z.js → experiment-utils-DTANyVKV.js} +68 -37
  200. package/dist/experiment-utils-DTANyVKV.js.map +1 -0
  201. package/dist/index.d.ts +2 -0
  202. package/dist/index.js +122 -17
  203. package/dist/index.js.map +1 -1
  204. package/dist/install.js +1 -2
  205. package/dist/install.js.map +1 -1
  206. package/dist/instrument.d.ts +6 -0
  207. package/dist/lcms.d.ts +11 -0
  208. package/dist/permissions.d.ts +2 -47
  209. package/dist/permissions.js +2 -26
  210. package/dist/permissions.js.map +1 -1
  211. package/dist/settings-BLr8lzw6.js +341 -0
  212. package/dist/settings-BLr8lzw6.js.map +1 -0
  213. package/dist/stores/auth.d.ts +2 -150
  214. package/dist/stores/experiment.d.ts +2 -106
  215. package/dist/stores/index.js +2 -2
  216. package/dist/stores/settings.d.ts +2 -44
  217. package/dist/styles.css +8608 -6950
  218. package/dist/tailwind.preset.js.map +1 -1
  219. package/dist/templates/adapters.d.ts +3 -0
  220. package/dist/templates/assayMatrixAdapters.d.ts +8 -1
  221. package/dist/templates/assayMatrixBuilder.d.ts +1 -0
  222. package/dist/templates/builderPresetControls.d.ts +1 -0
  223. package/dist/templates/calibrationCurveAdapters.d.ts +3 -0
  224. package/dist/templates/calibrationCurveBuilder.d.ts +1 -0
  225. package/dist/templates/catalog.d.ts +6 -2
  226. package/dist/templates/componentBindings.d.ts +31 -5
  227. package/dist/templates/componentPlateHelpers.d.ts +7 -1
  228. package/dist/templates/controlSchemas.d.ts +9 -0
  229. package/dist/templates/defaultBioTemplateBuilder.d.ts +1 -0
  230. package/dist/templates/doseResponseAdapters.d.ts +2 -0
  231. package/dist/templates/doseResponseBuilder.d.ts +1 -0
  232. package/dist/templates/elisaAssayCollectionBuilder.d.ts +1 -0
  233. package/dist/templates/flowCytometryAssayCollectionBuilder.d.ts +1 -0
  234. package/dist/templates/flowCytometryPanelBuilder.d.ts +1 -0
  235. package/dist/templates/flowPanelAdapters.d.ts +3 -0
  236. package/dist/templates/index.d.ts +1 -1
  237. package/dist/templates/index.js +2 -3
  238. package/dist/templates/instrumentRunAdapters.d.ts +13 -2
  239. package/dist/templates/instrumentRunBuilder.d.ts +1 -0
  240. package/dist/templates/lcmsBatchCollectionBuilder.d.ts +1 -0
  241. package/dist/templates/packs.d.ts +4 -0
  242. package/dist/templates/plateMapAdapters.d.ts +2 -0
  243. package/dist/templates/plateMapBuilder.d.ts +1 -0
  244. package/dist/templates/presets.d.ts +6 -2
  245. package/dist/templates/protocolAdapters.d.ts +4 -0
  246. package/dist/templates/protocolStepsBuilder.d.ts +1 -0
  247. package/dist/templates/qpcrAdapters.d.ts +4 -0
  248. package/dist/templates/qpcrExpressionCollectionBuilder.d.ts +1 -0
  249. package/dist/templates/qpcrPlateBuilder.d.ts +1 -0
  250. package/dist/templates/reagentAdapters.d.ts +4 -0
  251. package/dist/templates/reagentListBuilder.d.ts +1 -0
  252. package/dist/templates/samplePrepAdapters.d.ts +3 -0
  253. package/dist/templates/samplePrepBuilder.d.ts +1 -0
  254. package/dist/templates/sampleSheetAdapters.d.ts +4 -0
  255. package/dist/templates/sampleSheetBuilder.d.ts +1 -0
  256. package/dist/templates/targetedMetabolomicsCollectionBuilder.d.ts +1 -0
  257. package/dist/templates/templateEnvelopes.d.ts +8 -0
  258. package/dist/templates/templatePackCollectionBuilder.d.ts +1 -0
  259. package/dist/templates/templatePresetCollectionBuilder.d.ts +2 -0
  260. package/dist/templates/templateValidators.d.ts +12 -0
  261. package/dist/templates/timeCourseAdapters.d.ts +4 -0
  262. package/dist/templates/timeCourseBuilder.d.ts +1 -0
  263. package/dist/templates/wellPlateScreenCollectionBuilder.d.ts +1 -0
  264. package/dist/templates/westernBlotAssayCollectionBuilder.d.ts +1 -0
  265. package/dist/templates-BU1pet28.js +12304 -0
  266. package/dist/templates-BU1pet28.js.map +1 -0
  267. package/dist/types/auth.d.ts +0 -27
  268. package/dist/types/componentLabTypes.d.ts +14 -7
  269. package/dist/types/componentWorkflowTypes.d.ts +26 -0
  270. package/dist/types/components.d.ts +40 -17
  271. package/dist/types/fileBrowserTypes.d.ts +13 -0
  272. package/dist/types/filePicker.d.ts +19 -1
  273. package/dist/types/generated-ui.d.ts +13 -0
  274. package/dist/types/index.d.ts +4 -4
  275. package/dist/types/index.js +1 -1
  276. package/dist/types/instrument.d.ts +43 -15
  277. package/dist/types/platform.d.ts +2 -0
  278. package/dist/utils/color.d.ts +6 -1
  279. package/dist/utils/formModelSync.d.ts +1 -0
  280. package/dist/utils/plotlyTemplate.d.ts +3 -0
  281. package/dist/utils/rack.d.ts +16 -0
  282. package/dist/utils/sampleTypeMarker.d.ts +23 -0
  283. package/dist/{validation-ChMyixAf.js → validation-Dmp9Q5W0.js} +3032 -838
  284. package/dist/validation-Dmp9Q5W0.js.map +1 -0
  285. package/package.json +17 -23
  286. package/src/__stories__/experiment-helpers.ts +8 -4
  287. package/src/__tests__/components/ActionMenu.test.ts +66 -0
  288. package/src/__tests__/components/AlertBox.test.ts +21 -0
  289. package/src/__tests__/components/AppAvatarMenu.test.ts +25 -0
  290. package/src/__tests__/components/AppTopBar.test.ts +79 -0
  291. package/src/__tests__/components/AppTopBarPageSelector.test.ts +20 -0
  292. package/src/__tests__/components/AppTopBarPillNav.test.ts +74 -0
  293. package/src/__tests__/components/ArtifactSelectorModal.test.ts +20 -6
  294. package/src/__tests__/components/AuditTrail.test.ts +38 -0
  295. package/src/__tests__/components/BaseCheckbox.test.ts +43 -0
  296. package/src/__tests__/components/BaseInput.test.ts +43 -0
  297. package/src/__tests__/components/BaseModal.test.ts +90 -0
  298. package/src/__tests__/components/BaseRadioGroup.test.ts +8 -0
  299. package/src/__tests__/components/BaseSlider.test.ts +116 -0
  300. package/src/__tests__/components/BaseToggle.test.ts +7 -0
  301. package/src/__tests__/components/BatchProgressList.test.ts +22 -14
  302. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +4 -4
  303. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +1 -1
  304. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +8 -8
  305. package/src/__tests__/components/BioTemplateRenderer.test.ts +2 -2
  306. package/src/__tests__/components/Breadcrumb.test.ts +45 -0
  307. package/src/__tests__/components/CalendarGridPanel.test.ts +14 -0
  308. package/src/__tests__/components/ChartContainer.test.ts +23 -0
  309. package/src/__tests__/components/ChemicalFormula.test.ts +15 -0
  310. package/src/__tests__/components/CollapsibleCard.test.ts +12 -9
  311. package/src/__tests__/components/ComponentBindingRenderer.test.ts +45 -17
  312. package/src/__tests__/components/ConcentrationInput.test.ts +17 -2
  313. package/src/__tests__/components/DataFrame.test.ts +107 -4
  314. package/src/__tests__/components/DataStates.test.ts +0 -16
  315. package/src/__tests__/components/DatePicker.test.ts +10 -0
  316. package/src/__tests__/components/DoseCalculator.test.ts +47 -0
  317. package/src/__tests__/components/DoseCalculatorVolumeField.test.ts +37 -34
  318. package/src/__tests__/components/EmptyState.test.ts +16 -1
  319. package/src/__tests__/components/ExperimentSelectorModal.test.ts +31 -3
  320. package/src/__tests__/components/ExperimentTimeline.test.ts +45 -0
  321. package/src/__tests__/components/FilePicker.state.test.ts +2 -2
  322. package/src/__tests__/components/FilePicker.test.ts +360 -13
  323. package/src/__tests__/components/FileUploader.test.ts +15 -0
  324. package/src/__tests__/components/FitPanel.test.ts +96 -0
  325. package/src/__tests__/components/FormulaInput.test.ts +97 -0
  326. package/src/__tests__/components/GeneratedPathInput.test.ts +168 -41
  327. package/src/__tests__/components/GeneratedPlotlyResult.test.ts +1 -1
  328. package/src/__tests__/components/GeneratedResultRenderer.test.ts +47 -2
  329. package/src/__tests__/components/InstrumentStateBadge.test.ts +50 -0
  330. package/src/__tests__/components/JobsStatusTray.test.ts +27 -0
  331. package/src/__tests__/components/MoleculeInput.test.ts +78 -0
  332. package/src/__tests__/components/MultiSelect.test.ts +6 -0
  333. package/src/__tests__/components/PlateEditor.test.ts +1134 -0
  334. package/src/__tests__/components/PlateEditorBindingInternal.test.ts +64 -0
  335. package/src/__tests__/components/PlotlyChart.test.ts +147 -1
  336. package/src/__tests__/components/ProgressBar.test.ts +18 -0
  337. package/src/__tests__/components/ProtocolStepEditor.test.ts +106 -18
  338. package/src/__tests__/components/ProtocolStepParameterField.test.ts +74 -29
  339. package/src/__tests__/components/ReagentList.presentation.test.ts +10 -5
  340. package/src/__tests__/components/ReagentList.test.ts +23 -0
  341. package/src/__tests__/components/ResourcePickerListInternal.test.ts +141 -0
  342. package/src/__tests__/components/RetiredStyles.test.ts +0 -6
  343. package/src/__tests__/components/SampleHierarchyTree.test.ts +102 -0
  344. package/src/__tests__/components/SampleLegend.test.ts +18 -0
  345. package/src/__tests__/components/SampleSelector.test.ts +92 -1
  346. package/src/__tests__/components/SampleSelectorSampleRow.test.ts +5 -2
  347. package/src/__tests__/components/ScheduleCalendar.test.ts +32 -0
  348. package/src/__tests__/components/ScientificNumber.test.ts +113 -0
  349. package/src/__tests__/components/SearchableSelect.test.ts +21 -0
  350. package/src/__tests__/components/SecretInput.test.ts +6 -0
  351. package/src/__tests__/components/SegmentedControl.test.ts +14 -0
  352. package/src/__tests__/components/SequenceInput.test.ts +81 -0
  353. package/src/__tests__/components/SettingsModal.schema.test.ts +5 -5
  354. package/src/__tests__/components/SettingsModal.test.ts +371 -23
  355. package/src/__tests__/components/Skeleton.test.ts +23 -0
  356. package/src/__tests__/components/SmartGroupFieldRecipe.test.ts +12 -0
  357. package/src/__tests__/components/SmartGroupModal.test.ts +69 -0
  358. package/src/__tests__/components/StepWizard.test.ts +75 -0
  359. package/src/__tests__/components/TagsInput.test.ts +16 -0
  360. package/src/__tests__/components/TimePicker.test.ts +66 -0
  361. package/src/__tests__/components/TimeRangeInput.test.ts +83 -0
  362. package/src/__tests__/components/UnitInput.test.ts +147 -0
  363. package/src/__tests__/components/WellEditPopupInternal.test.ts +434 -2
  364. package/src/__tests__/components/WellPlate.decoration.test.ts +18 -0
  365. package/src/__tests__/components/WellPlate.geometry.test.ts +13 -0
  366. package/src/__tests__/components/WellPlate.interaction.test.ts +213 -0
  367. package/src/__tests__/components/WellPlate.keyboard.test.ts +24 -0
  368. package/src/__tests__/components/WellPlate.rendering.test.ts +38 -19
  369. package/src/__tests__/components/WellPlate.sampleDrop.test.ts +19 -0
  370. package/src/__tests__/components/WellPlate.sampleTypeIndicator.test.ts +58 -0
  371. package/src/__tests__/composables/createFilePickerAdapter.test.ts +114 -1
  372. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +1 -1
  373. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +1 -1
  374. package/src/__tests__/composables/useChemicalFormula.test.ts +60 -0
  375. package/src/__tests__/composables/useControlSchema.test.ts +15 -5
  376. package/src/__tests__/composables/useDropdownState.test.ts +20 -1
  377. package/src/__tests__/composables/useGeneratedAnalysis.test.ts +52 -0
  378. package/src/__tests__/composables/usePickerNavigation.test.ts +61 -1
  379. package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +18 -6
  380. package/src/__tests__/composables/useRackEditor.test.ts +270 -0
  381. package/src/__tests__/composables/useSequenceUtils.test.ts +28 -0
  382. package/src/__tests__/docs/frontendDocsCatalog.test.ts +44 -9
  383. package/src/__tests__/styles/baseControlsRules.test.ts +115 -0
  384. package/src/__tests__/styles/controlRefreshRules.test.ts +218 -0
  385. package/src/__tests__/styles/cssTokens.test.ts +7 -5
  386. package/src/__tests__/styles/darkModeRules.test.ts +47 -0
  387. package/src/__tests__/styles/menusShellRefresh.test.ts +75 -0
  388. package/src/__tests__/templates/templates.test.ts +65 -15
  389. package/src/__tests__/templates/templatesDeprecation.test.ts +78 -0
  390. package/src/__tests__/utils/permissions.test.ts +0 -68
  391. package/src/__tests__/utils/plotlyTemplate.test.ts +69 -0
  392. package/src/__tests__/utils/sampleTypeMarker.test.ts +54 -0
  393. package/src/components/ActionMenu.vue +44 -23
  394. package/src/components/AdductText.story.vue +18 -0
  395. package/src/components/AdductText.vue +7 -1
  396. package/src/components/AlertBox.vue +3 -7
  397. package/src/components/AppAvatarMenu.vue +12 -10
  398. package/src/components/AppSidebar.vue +1 -1
  399. package/src/components/AppTopBar.story.vue +47 -4
  400. package/src/components/AppTopBar.vue +16 -22
  401. package/src/components/ArtifactSelectorModal.story.vue +1 -1
  402. package/src/components/ArtifactSelectorModal.vue +48 -160
  403. package/src/components/AuditTrail.vue +26 -12
  404. package/src/components/AutoGroupModal.vue +1 -1
  405. package/src/components/BaseCheckbox.story.vue +58 -2
  406. package/src/components/BaseCheckbox.vue +21 -3
  407. package/src/components/BaseInput.story.vue +51 -0
  408. package/src/components/BaseInput.vue +62 -1
  409. package/src/components/BaseModal.story.vue +55 -0
  410. package/src/components/BaseModal.vue +83 -50
  411. package/src/components/BaseRadioGroup.vue +1 -1
  412. package/src/components/BaseSlider.story.vue +36 -0
  413. package/src/components/BaseSlider.vue +56 -8
  414. package/src/components/BaseToggle.vue +1 -7
  415. package/src/components/BatchProgressList.vue +50 -52
  416. package/src/components/BioTemplatePresetWorkspaceView.vue +1 -1
  417. package/src/components/Breadcrumb.story.vue +1 -1
  418. package/src/components/Breadcrumb.vue +50 -15
  419. package/src/components/ChartContainer.story.vue +42 -0
  420. package/src/components/ChartContainer.vue +22 -1
  421. package/src/components/ChemicalFormula.story.vue +11 -11
  422. package/src/components/ChemicalFormula.vue +1 -0
  423. package/src/components/CollapsibleCard.vue +12 -32
  424. package/src/components/ComponentBindingRenderer.vue +6 -8
  425. package/src/components/ConcentrationInput.story.vue +1 -1
  426. package/src/components/ConcentrationInput.vue +8 -14
  427. package/src/components/ControlWorkspaceView.vue +1 -1
  428. package/src/components/DataFrame.story.vue +45 -2
  429. package/src/components/DataFrame.vue +28 -13
  430. package/src/components/DatePicker.story.vue +11 -0
  431. package/src/components/DatePicker.vue +5 -5
  432. package/src/components/DateTimePicker.story.vue +1 -1
  433. package/src/components/DateTimePicker.vue +8 -5
  434. package/src/components/Divider.story.vue +1 -1
  435. package/src/components/DoseCalculator.vue +44 -51
  436. package/src/components/DoseCalculatorVolumeField.vue +22 -35
  437. package/src/components/DoseDesignWorkspaceView.vue +1 -1
  438. package/src/components/EmptyState.story.vue +4 -4
  439. package/src/components/EmptyState.vue +4 -2
  440. package/src/components/ExperimentPopover.story.vue +2 -2
  441. package/src/components/ExperimentPopover.vue +6 -4
  442. package/src/components/ExperimentSelectorModal.story.vue +2 -1
  443. package/src/components/ExperimentSelectorModal.vue +91 -261
  444. package/src/components/ExperimentTimeline.story.vue +29 -0
  445. package/src/components/ExperimentTimeline.vue +20 -0
  446. package/src/components/FilePicker.story.vue +65 -10
  447. package/src/components/FilePicker.vue +203 -121
  448. package/src/components/FitPanel.story.vue +72 -6
  449. package/src/components/FitPanel.vue +70 -18
  450. package/src/components/FormField.vue +2 -0
  451. package/src/components/FormulaInput.story.vue +6 -0
  452. package/src/components/FormulaInput.vue +49 -70
  453. package/src/components/GeneratedPathInput.story.vue +121 -8
  454. package/src/components/GeneratedPathInput.vue +155 -86
  455. package/src/components/GeneratedPlotlyResult.vue +1 -1
  456. package/src/components/GeneratedPluginView.vue +4 -4
  457. package/src/components/GeneratedResultRenderer.vue +19 -0
  458. package/src/components/InstrumentStateBadge.story.vue +41 -0
  459. package/src/components/InstrumentStateBadge.vue +24 -14
  460. package/src/components/JobsStatusTray.vue +30 -8
  461. package/src/components/MoleculeInput.vue +12 -7
  462. package/src/components/MultiSelect.vue +3 -3
  463. package/src/components/PlateEditor.story.vue +333 -0
  464. package/src/components/PlateEditor.vue +793 -0
  465. package/src/components/PlotlyChart.story.vue +37 -1
  466. package/src/components/PlotlyChart.vue +86 -16
  467. package/src/components/PluginWorkspaceView.props.ts +1 -7
  468. package/src/components/PluginWorkspaceView.vue +0 -4
  469. package/src/components/ProgressBar.story.vue +0 -30
  470. package/src/components/ProgressBar.vue +3 -53
  471. package/src/components/ProtocolStepEditor.story.vue +8 -0
  472. package/src/components/ProtocolStepEditor.vue +55 -145
  473. package/src/components/ProtocolStepParameterField.vue +55 -93
  474. package/src/components/ReagentList.presentation.ts +7 -6
  475. package/src/components/ReagentList.vue +18 -17
  476. package/src/components/SampleHierarchyTree.story.vue +19 -0
  477. package/src/components/SampleHierarchyTree.vue +47 -14
  478. package/src/components/SampleLegend.vue +12 -4
  479. package/src/components/SampleSelector.story.vue +5 -1
  480. package/src/components/SampleSelector.vue +25 -15
  481. package/src/components/SampleSelectorGroupNode.vue +10 -20
  482. package/src/components/SampleSelectorSampleRow.vue +7 -18
  483. package/src/components/ScheduleCalendar.story.vue +14 -0
  484. package/src/components/ScheduleCalendar.vue +13 -1
  485. package/src/components/ScientificNumber.story.vue +15 -2
  486. package/src/components/ScientificNumber.vue +22 -26
  487. package/src/components/SearchableSelect.vue +14 -9
  488. package/src/components/SecretInput.vue +1 -1
  489. package/src/components/SegmentedControl.story.vue +23 -0
  490. package/src/components/SegmentedControl.vue +6 -1
  491. package/src/components/SequenceInput.story.vue +27 -5
  492. package/src/components/SequenceInput.vue +74 -29
  493. package/src/components/SequenceProgressBar.vue +0 -1
  494. package/src/components/SettingsModal.schema.ts +4 -2
  495. package/src/components/SettingsModal.story.vue +83 -22
  496. package/src/components/SettingsModal.vue +338 -79
  497. package/src/components/Skeleton.vue +6 -35
  498. package/src/components/SmartGroup.types.ts +2 -2
  499. package/src/components/SmartGroupFieldRecipe.groups.ts +4 -1
  500. package/src/components/SmartGroupFieldRecipe.vue +14 -8
  501. package/src/components/SmartGroupManual.vue +14 -11
  502. package/src/components/StepWizard.vue +16 -19
  503. package/src/components/TagsInput.story.vue +16 -11
  504. package/src/components/TagsInput.vue +20 -3
  505. package/src/components/TimePicker.story.vue +38 -3
  506. package/src/components/TimePicker.vue +26 -11
  507. package/src/components/TimeRangeInput.story.vue +50 -1
  508. package/src/components/TimeRangeInput.vue +36 -17
  509. package/src/components/UnitInput.story.vue +1 -1
  510. package/src/components/UnitInput.vue +173 -107
  511. package/src/components/WellPlate.colors.ts +7 -6
  512. package/src/components/WellPlate.geometry.ts +3 -0
  513. package/src/components/WellPlate.interaction.ts +39 -5
  514. package/src/components/WellPlate.rendering.ts +21 -27
  515. package/src/components/WellPlate.sampleDrop.ts +20 -12
  516. package/src/components/WellPlate.story.vue +18 -7
  517. package/src/components/WellPlate.vue +28 -22
  518. package/src/components/index.ts +34 -31
  519. package/src/components/internal/AppTopBarPageSelectorInternal.vue +13 -7
  520. package/src/components/internal/AppTopBarPillNavInternal.vue +12 -7
  521. package/src/components/internal/CalendarGridPanelInternal.vue +1 -1
  522. package/src/components/internal/FilePickerListInternal.vue +318 -0
  523. package/src/components/internal/FilePickerPreviewInternal.vue +54 -52
  524. package/src/components/internal/PlateEditorBindingInternal.vue +37 -0
  525. package/src/components/internal/ResourcePickerListInternal.vue +237 -0
  526. package/src/components/internal/SettingsPaneHeadInternal.vue +41 -0
  527. package/src/components/internal/WellEditPopupInternal.vue +219 -146
  528. package/src/components/internal/WorkspaceShellInternal.vue +0 -6
  529. package/src/components/internal/filePickerPresentation.ts +30 -0
  530. package/src/components/internal/generatedJobsApi.ts +12 -0
  531. package/src/composables/autoGroup/index.ts +3 -0
  532. package/src/composables/controlSchemaAdapters.ts +0 -4
  533. package/src/composables/controlSchemaFormFields.ts +0 -4
  534. package/src/composables/controlSchemaTypes.ts +4 -43
  535. package/src/composables/createFilePickerAdapter.ts +90 -22
  536. package/src/composables/filePicker/localFiles.ts +1 -0
  537. package/src/composables/filePicker/usePickerNavigation.ts +29 -2
  538. package/src/composables/index.ts +2 -71
  539. package/src/composables/pluginWorkspaceModel.ts +0 -3
  540. package/src/composables/useAutoGroup.ts +10 -0
  541. package/src/composables/useBioTemplateComponents.ts +15 -3
  542. package/src/composables/useBioTemplateControls.ts +5 -1
  543. package/src/composables/useBioTemplatePackWorkspace.ts +2 -0
  544. package/src/composables/useBioTemplatePresetWorkspace.ts +5 -1
  545. package/src/composables/useBioTemplateWorkspace.ts +5 -1
  546. package/src/composables/useChemicalFormula.ts +39 -13
  547. package/src/composables/useDropdownState.ts +6 -1
  548. package/src/composables/useExperimentSave.ts +5 -1
  549. package/src/composables/useFileBrowser.ts +3 -2
  550. package/src/composables/useFilePicker.ts +98 -13
  551. package/src/composables/useFocusTrap.ts +9 -6
  552. package/src/composables/useGeneratedAnalysis.ts +38 -0
  553. package/src/composables/useGroupAssignment.ts +5 -1
  554. package/src/composables/useJobsStatusTray.ts +5 -5
  555. package/src/composables/useMenuKeyboard.ts +5 -3
  556. package/src/composables/usePlatformFilePickerAdapter.ts +26 -4
  557. package/src/composables/usePluginClient.ts +1 -1
  558. package/src/composables/usePluginWorkspace.ts +0 -1
  559. package/src/composables/useRackEditor.ts +202 -12
  560. package/src/composables/useSequenceUtils.ts +20 -2
  561. package/src/composables/useTemplateCollection.ts +5 -1
  562. package/src/composables/workspaceRecipes.ts +1 -0
  563. package/src/index.ts +9 -3
  564. package/src/instrument.ts +6 -0
  565. package/src/lcms.ts +11 -0
  566. package/src/permissions.ts +2 -59
  567. package/src/styles/components/action-menu.css +9 -38
  568. package/src/styles/components/adduct-text.css +15 -3
  569. package/src/styles/components/alert-box.css +19 -70
  570. package/src/styles/components/app-avatar-menu.css +22 -57
  571. package/src/styles/components/app-container.css +4 -4
  572. package/src/styles/components/app-layout.css +18 -18
  573. package/src/styles/components/app-page-selector.css +50 -105
  574. package/src/styles/components/app-pill-nav.css +24 -42
  575. package/src/styles/components/app-sidebar.css +2 -2
  576. package/src/styles/components/app-top-bar.css +78 -55
  577. package/src/styles/components/artifact-selector-modal.css +1 -211
  578. package/src/styles/components/audit-trail.css +45 -38
  579. package/src/styles/components/avatar.css +1 -1
  580. package/src/styles/components/batch-progress-list.css +179 -95
  581. package/src/styles/components/breadcrumb.css +34 -15
  582. package/src/styles/components/calendar.css +2 -8
  583. package/src/styles/components/chart-container.css +81 -38
  584. package/src/styles/components/checkbox.css +8 -6
  585. package/src/styles/components/chemical-formula.css +8 -6
  586. package/src/styles/components/collapsible-card.css +6 -46
  587. package/src/styles/components/concentration-input.css +1 -10
  588. package/src/styles/components/dataframe.css +48 -67
  589. package/src/styles/components/date-picker.css +124 -62
  590. package/src/styles/components/datetime-picker.css +81 -47
  591. package/src/styles/components/divider.css +2 -4
  592. package/src/styles/components/dose-calculator.css +9 -135
  593. package/src/styles/components/empty-state.css +26 -35
  594. package/src/styles/components/experiment-code-badge.css +1 -1
  595. package/src/styles/components/experiment-popover.css +16 -83
  596. package/src/styles/components/experiment-selector-modal.css +9 -226
  597. package/src/styles/components/experiment-timeline.css +120 -50
  598. package/src/styles/components/file-picker.css +323 -299
  599. package/src/styles/components/file-uploader.css +4 -6
  600. package/src/styles/components/fit-panel.css +108 -22
  601. package/src/styles/components/form-field.css +6 -4
  602. package/src/styles/components/formula-input.css +121 -48
  603. package/src/styles/components/group-assigner.css +1 -3
  604. package/src/styles/components/icon-button.css +15 -37
  605. package/src/styles/components/input.css +91 -2
  606. package/src/styles/components/instrument-monitor.css +37 -447
  607. package/src/styles/components/jobs-status-tray.css +82 -61
  608. package/src/styles/components/loading-spinner.css +18 -0
  609. package/src/styles/components/mobile-support-gate.css +15 -17
  610. package/src/styles/components/modal.css +44 -33
  611. package/src/styles/components/molecule-input.css +31 -16
  612. package/src/styles/components/multi-select.css +33 -29
  613. package/src/styles/components/number-input.css +1 -1
  614. package/src/styles/components/plate-editor.css +497 -0
  615. package/src/styles/components/popover.css +162 -0
  616. package/src/styles/components/progress-bar.css +1 -70
  617. package/src/styles/components/protocol-step-editor.css +21 -333
  618. package/src/styles/components/radio-group.css +9 -18
  619. package/src/styles/components/reagent-editor.css +3 -7
  620. package/src/styles/components/reagent-list.css +63 -69
  621. package/src/styles/components/resource-card.css +4 -6
  622. package/src/styles/components/resource-picker-list.css +212 -0
  623. package/src/styles/components/sample-hierarchy-tree.css +83 -65
  624. package/src/styles/components/sample-legend.css +18 -7
  625. package/src/styles/components/sample-selector.css +41 -68
  626. package/src/styles/components/schedule-calendar.css +59 -54
  627. package/src/styles/components/scientific-number.css +72 -18
  628. package/src/styles/components/searchable-select.css +17 -26
  629. package/src/styles/components/secret-input.css +20 -23
  630. package/src/styles/components/section-card.css +3 -5
  631. package/src/styles/components/segmented-control.css +14 -1
  632. package/src/styles/components/select.css +19 -8
  633. package/src/styles/components/sequence-input.css +85 -37
  634. package/src/styles/components/sequence-progress-bar.css +18 -37
  635. package/src/styles/components/settings-modal.css +683 -34
  636. package/src/styles/components/skeleton.css +24 -23
  637. package/src/styles/components/slider.css +74 -8
  638. package/src/styles/components/smart-group.css +27 -39
  639. package/src/styles/components/status-indicator.css +1 -1
  640. package/src/styles/components/step-wizard.css +62 -99
  641. package/src/styles/components/tabs.css +2 -6
  642. package/src/styles/components/tags-input.css +64 -54
  643. package/src/styles/components/textarea.css +2 -2
  644. package/src/styles/components/time-picker.css +67 -38
  645. package/src/styles/components/time-range-input.css +16 -18
  646. package/src/styles/components/toast.css +1 -1
  647. package/src/styles/components/toggle.css +21 -31
  648. package/src/styles/components/tooltip.css +13 -15
  649. package/src/styles/components/unit-input.css +86 -36
  650. package/src/styles/components/well-edit-popup.css +111 -189
  651. package/src/styles/components/well-plate.css +7 -7
  652. package/src/styles/index.css +5 -7
  653. package/src/styles/variables.css +35 -7
  654. package/src/templates/adapters.ts +4 -0
  655. package/src/templates/assayMatrixAdapters.ts +8 -1
  656. package/src/templates/assayMatrixBuilder.ts +1 -0
  657. package/src/templates/builderPresetControls.ts +1 -0
  658. package/src/templates/calibrationCurveAdapters.ts +3 -0
  659. package/src/templates/calibrationCurveBuilder.ts +1 -0
  660. package/src/templates/catalog.ts +6 -2
  661. package/src/templates/componentBindingCatalog.ts +1 -1
  662. package/src/templates/componentBindings.ts +31 -5
  663. package/src/templates/componentDoseResponseProps.ts +0 -1
  664. package/src/templates/componentPlateHelpers.ts +22 -1
  665. package/src/templates/componentPlateMapProps.ts +7 -5
  666. package/src/templates/componentQpcrPlateProps.ts +0 -1
  667. package/src/templates/controlSchemas.ts +9 -0
  668. package/src/templates/defaultBioTemplateBuilder.ts +1 -0
  669. package/src/templates/doseResponseAdapters.ts +2 -0
  670. package/src/templates/doseResponseBuilder.ts +1 -0
  671. package/src/templates/elisaAssayCollectionBuilder.ts +1 -0
  672. package/src/templates/flowCytometryAssayCollectionBuilder.ts +1 -0
  673. package/src/templates/flowCytometryPanelBuilder.ts +1 -0
  674. package/src/templates/flowPanelAdapters.ts +3 -0
  675. package/src/templates/index.ts +4 -0
  676. package/src/templates/instrumentRunAdapters.ts +13 -2
  677. package/src/templates/instrumentRunBuilder.ts +1 -0
  678. package/src/templates/lcmsBatchCollectionBuilder.ts +1 -0
  679. package/src/templates/packs.ts +4 -0
  680. package/src/templates/plateMapAdapters.ts +2 -0
  681. package/src/templates/plateMapBuilder.ts +1 -0
  682. package/src/templates/presets.ts +6 -2
  683. package/src/templates/protocolAdapters.ts +4 -0
  684. package/src/templates/protocolStepsBuilder.ts +1 -0
  685. package/src/templates/qpcrAdapters.ts +4 -0
  686. package/src/templates/qpcrExpressionCollectionBuilder.ts +1 -0
  687. package/src/templates/qpcrPlateBuilder.ts +1 -0
  688. package/src/templates/reagentAdapters.ts +4 -0
  689. package/src/templates/reagentListBuilder.ts +1 -0
  690. package/src/templates/samplePrepAdapters.ts +3 -0
  691. package/src/templates/samplePrepBuilder.ts +1 -0
  692. package/src/templates/sampleSheetAdapters.ts +4 -0
  693. package/src/templates/sampleSheetBuilder.ts +1 -0
  694. package/src/templates/targetedMetabolomicsCollectionBuilder.ts +1 -0
  695. package/src/templates/templateEnvelopes.ts +8 -0
  696. package/src/templates/templatePackCollectionBuilder.ts +1 -0
  697. package/src/templates/templatePresetCollectionBuilder.ts +2 -0
  698. package/src/templates/templateValidators.ts +12 -0
  699. package/src/templates/timeCourseAdapters.ts +4 -0
  700. package/src/templates/timeCourseBuilder.ts +1 -0
  701. package/src/templates/wellPlateScreenCollectionBuilder.ts +1 -0
  702. package/src/templates/westernBlotAssayCollectionBuilder.ts +1 -0
  703. package/src/types/auth.ts +0 -37
  704. package/src/types/componentLabTypes.ts +16 -13
  705. package/src/types/componentWorkflowTypes.ts +29 -0
  706. package/src/types/components.ts +43 -20
  707. package/src/types/fileBrowserTypes.ts +15 -0
  708. package/src/types/filePicker.ts +19 -1
  709. package/src/types/generated-ui.ts +15 -0
  710. package/src/types/index.ts +12 -11
  711. package/src/types/instrument.ts +47 -15
  712. package/src/types/platform.ts +2 -0
  713. package/src/utils/color.ts +16 -1
  714. package/src/utils/formModelSync.ts +1 -1
  715. package/src/utils/plotlyTemplate.ts +39 -0
  716. package/src/utils/rack.ts +16 -0
  717. package/src/utils/sampleTypeMarker.ts +44 -0
  718. package/dist/ExperimentPopover-CJdNlCZb.js +0 -451
  719. package/dist/ExperimentPopover-CJdNlCZb.js.map +0 -1
  720. package/dist/ExperimentSelectorModal-DHE9k8TP.js +0 -507
  721. package/dist/ExperimentSelectorModal-DHE9k8TP.js.map +0 -1
  722. package/dist/SettingsModal-DELMkUFR.js +0 -492
  723. package/dist/SettingsModal-DELMkUFR.js.map +0 -1
  724. package/dist/Skeleton-jhF9wUkU.js +0 -485
  725. package/dist/Skeleton-jhF9wUkU.js.map +0 -1
  726. package/dist/__tests__/composables/usePluginConfig.test.d.ts +0 -15
  727. package/dist/chunk-BoAXSpZd.js +0 -33
  728. package/dist/components/AppPluginSwitcher.vue.d.ts +0 -40
  729. package/dist/components/ColorSlider.vue.d.ts +0 -34
  730. package/dist/components/DropdownButton.vue.d.ts +0 -49
  731. package/dist/components/FileBrowserModal.vue.d.ts +0 -99
  732. package/dist/components/InstrumentAlertLog.vue.d.ts +0 -22
  733. package/dist/components/InstrumentStatusCard.vue.d.ts +0 -13
  734. package/dist/components/LcmsSequenceTable.vue.d.ts +0 -54
  735. package/dist/components/PlateMapEditor.vue.d.ts +0 -50
  736. package/dist/components/RackEditor.vue.d.ts +0 -78
  737. package/dist/components/fileBrowserPresentation.d.ts +0 -10
  738. package/dist/components/internal/FileBrowserDialogInternal.vue.d.ts +0 -34
  739. package/dist/components/internal/FileBrowserListingInternal.vue.d.ts +0 -25
  740. package/dist/components/internal/FileBrowserToolbarInternal.vue.d.ts +0 -26
  741. package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +0 -28
  742. package/dist/components-Bbg_gtzF.js.map +0 -1
  743. package/dist/composables/useAsync.d.ts +0 -132
  744. package/dist/composables/useAuth.d.ts +0 -47
  745. package/dist/composables/useCommandHistory.d.ts +0 -33
  746. package/dist/composables/useOptimisticMutation.d.ts +0 -32
  747. package/dist/composables/usePasskey.d.ts +0 -10
  748. package/dist/composables/usePluginConfig.d.ts +0 -27
  749. package/dist/composables/useResourceCrud.d.ts +0 -44
  750. package/dist/composables/useWellPainting.d.ts +0 -39
  751. package/dist/composables/useWellPlateAdapter.d.ts +0 -40
  752. package/dist/composables/useWellPlateEditor.d.ts +0 -33
  753. package/dist/composables/useWellPlateValidation.d.ts +0 -31
  754. package/dist/composables-BCbYb2lA.js +0 -2356
  755. package/dist/composables-BCbYb2lA.js.map +0 -1
  756. package/dist/errors-BLuqMee5.js.map +0 -1
  757. package/dist/experiment-CVDRAnqp.js +0 -90
  758. package/dist/experiment-CVDRAnqp.js.map +0 -1
  759. package/dist/experiment-utils-CV2Wza3z.js.map +0 -1
  760. package/dist/plotly.min-c2Sez60c.js +0 -180634
  761. package/dist/plotly.min-c2Sez60c.js.map +0 -1
  762. package/dist/templates-NuaNHohp.js +0 -5260
  763. package/dist/templates-NuaNHohp.js.map +0 -1
  764. package/dist/useControlSchema-vLiRPUmj.js +0 -7301
  765. package/dist/useControlSchema-vLiRPUmj.js.map +0 -1
  766. package/dist/useDropdownState-C7hRiVQf.js +0 -63
  767. package/dist/useDropdownState-C7hRiVQf.js.map +0 -1
  768. package/dist/useExperimentSelector-G_CAOFTX.js +0 -328
  769. package/dist/useExperimentSelector-G_CAOFTX.js.map +0 -1
  770. package/dist/useFormBuilder-D37ajgS8.js +0 -729
  771. package/dist/useFormBuilder-D37ajgS8.js.map +0 -1
  772. package/dist/validation-ChMyixAf.js.map +0 -1
  773. package/src/__tests__/components/AppPluginSwitcher.test.ts +0 -44
  774. package/src/__tests__/components/DropdownButton.test.ts +0 -628
  775. package/src/__tests__/components/FileBrowserModal.test.ts +0 -460
  776. package/src/__tests__/components/InstrumentAlertLog.test.ts +0 -59
  777. package/src/__tests__/components/LcmsSequenceTable.test.ts +0 -79
  778. package/src/__tests__/components/PlateMapEditorToolbarInternal.test.ts +0 -54
  779. package/src/__tests__/components/RackEditor.test.ts +0 -169
  780. package/src/__tests__/composables/useAuth.test.ts +0 -233
  781. package/src/__tests__/composables/useCommandHistory.test.ts +0 -43
  782. package/src/__tests__/composables/useOptimisticMutation.test.ts +0 -40
  783. package/src/__tests__/composables/usePluginConfig.test.ts +0 -497
  784. package/src/__tests__/composables/useResourceCrud.test.ts +0 -56
  785. package/src/__tests__/composables/useWellPainting.test.ts +0 -52
  786. package/src/__tests__/composables/useWellPlateAdapter.test.ts +0 -50
  787. package/src/__tests__/composables/useWellPlateValidation.test.ts +0 -42
  788. package/src/components/AppPluginSwitcher.story.vue +0 -87
  789. package/src/components/AppPluginSwitcher.vue +0 -134
  790. package/src/components/ColorSlider.story.vue +0 -120
  791. package/src/components/ColorSlider.vue +0 -160
  792. package/src/components/DropdownButton.story.vue +0 -102
  793. package/src/components/DropdownButton.vue +0 -181
  794. package/src/components/FileBrowserModal.story.vue +0 -203
  795. package/src/components/FileBrowserModal.vue +0 -384
  796. package/src/components/InstrumentAlertLog.vue +0 -187
  797. package/src/components/InstrumentStatusCard.vue +0 -184
  798. package/src/components/LcmsSequenceTable.vue +0 -222
  799. package/src/components/PlateMapEditor.story.vue +0 -92
  800. package/src/components/PlateMapEditor.vue +0 -423
  801. package/src/components/RackEditor.story.vue +0 -100
  802. package/src/components/RackEditor.vue +0 -429
  803. package/src/components/fileBrowserPresentation.ts +0 -39
  804. package/src/components/internal/FileBrowserDialogInternal.vue +0 -75
  805. package/src/components/internal/FileBrowserListingInternal.vue +0 -197
  806. package/src/components/internal/FileBrowserToolbarInternal.vue +0 -136
  807. package/src/components/internal/FilePickerTreeInternal.vue +0 -238
  808. package/src/components/internal/PlateMapEditorToolbarInternal.vue +0 -128
  809. package/src/composables/useAsync.ts +0 -327
  810. package/src/composables/useAuth.ts +0 -458
  811. package/src/composables/useCommandHistory.ts +0 -117
  812. package/src/composables/useOptimisticMutation.ts +0 -97
  813. package/src/composables/usePasskey.ts +0 -216
  814. package/src/composables/usePluginConfig.ts +0 -252
  815. package/src/composables/useResourceCrud.ts +0 -235
  816. package/src/composables/useWellPainting.ts +0 -167
  817. package/src/composables/useWellPlateAdapter.ts +0 -151
  818. package/src/composables/useWellPlateEditor.ts +0 -415
  819. package/src/composables/useWellPlateValidation.ts +0 -89
  820. package/src/plotly-dist.d.ts +0 -3
  821. package/src/styles/components/app-plugin-switcher.css +0 -196
  822. package/src/styles/components/color-slider.css +0 -110
  823. package/src/styles/components/dropdown-button.css +0 -280
  824. package/src/styles/components/file-browser-modal.css +0 -489
  825. package/src/styles/components/lcms-sequence-table.css +0 -204
  826. package/src/styles/components/plate-map-editor.css +0 -464
  827. package/src/styles/components/rack-editor.css +0 -287
  828. /package/dist/__tests__/components/{AppPluginSwitcher.test.d.ts → AlertBox.test.d.ts} +0 -0
  829. /package/dist/__tests__/components/{DropdownButton.test.d.ts → AuditTrail.test.d.ts} +0 -0
  830. /package/dist/__tests__/components/{FileBrowserModal.test.d.ts → BaseCheckbox.test.d.ts} +0 -0
  831. /package/dist/__tests__/components/{InstrumentAlertLog.test.d.ts → BaseSlider.test.d.ts} +0 -0
  832. /package/dist/__tests__/components/{LcmsSequenceTable.test.d.ts → DoseCalculator.test.d.ts} +0 -0
  833. /package/dist/__tests__/components/{PlateMapEditorToolbarInternal.test.d.ts → FileUploader.test.d.ts} +0 -0
  834. /package/dist/__tests__/components/{RackEditor.test.d.ts → FitPanel.test.d.ts} +0 -0
  835. /package/dist/__tests__/{composables/useAuth.test.d.ts → components/FormulaInput.test.d.ts} +0 -0
  836. /package/dist/__tests__/{composables/useCommandHistory.test.d.ts → components/InstrumentStateBadge.test.d.ts} +0 -0
  837. /package/dist/__tests__/{composables/useOptimisticMutation.test.d.ts → components/MoleculeInput.test.d.ts} +0 -0
  838. /package/dist/__tests__/{composables/useResourceCrud.test.d.ts → components/PlateEditor.test.d.ts} +0 -0
  839. /package/dist/__tests__/{composables/useWellPainting.test.d.ts → components/PlateEditorBindingInternal.test.d.ts} +0 -0
  840. /package/dist/__tests__/{composables/useWellPlateAdapter.test.d.ts → components/ResourcePickerListInternal.test.d.ts} +0 -0
  841. /package/dist/__tests__/{composables/useWellPlateValidation.test.d.ts → components/SampleLegend.test.d.ts} +0 -0
@@ -1,9 +1,7 @@
1
- import { d as useSettingsStore, i as useApi, t as readErrorMessage } from "./errors-BLuqMee5.js";
2
- import { d as resolveExperimentCode } from "./experiment-utils-CV2Wza3z.js";
3
- import { n as useRequestSyncState } from "./Skeleton-jhF9wUkU.js";
4
- import { A as useConcentrationUnits, H as parseExperimentId, J as isActiveJobStatus, Q as normalizeJobState, T as getDefaultControlView, U as resolveCurrentExperimentId, V as getInjectedPlatformContext, f as resolveControlModel, g as mergeControlWorkspaceOptions, h as controlValuesToComponentProps, m as controlValuesToComponentBindingsById, p as controlValuesToComponentBindings, r as getControlDefaults, rt as isRecord, u as useControlWorkspace, w as controlsToViewItems } from "./useControlSchema-vLiRPUmj.js";
5
- import { t as useExperimentStore } from "./experiment-CVDRAnqp.js";
6
- import { B as toBioTemplateComponentProps, F as getBioTemplateComponentProps$1, H as toBioTemplateComponentPropsById, Jt as getBioTemplatePresetInfo, L as toBioTemplateComponentBindings, P as getBioTemplateComponentBindings, Qt as getBioTemplatePackInfo, R as toBioTemplateComponentBindingsById, U as toBioTemplateComponentSnippets, V as toBioTemplateComponentPropsByComponent$1, W as toBioTemplateComponentUsage, bn as rowLabel, d as createBioTemplatePresetCollectionFromControls, dn as extractTemplateCollection, i as createBioTemplateControlToolkit, p as createBioTemplatePackCollection, pn as PLATE_CONFIGS, sn as createTemplateCollection, u as createBioTemplatePresetCollection, un as ensureTemplateFromCollection, wn as deriveShade, xn as SAMPLE_COLOR_SCALE, z as toBioTemplateComponentImports } from "./templates-NuaNHohp.js";
1
+ import { o as useSettingsStore } from "./settings-BLr8lzw6.js";
2
+ import { d as resolveExperimentCode, o as datePresetToISO } from "./experiment-utils-DTANyVKV.js";
3
+ import { $t as getBioTemplatePackInfo, An as getControlDefaults, B as toBioTemplateComponentProps, Bn as getDefaultControlView, Er as PLUGIN_SECRET_FORMAT_KEY, F as getBioTemplateComponentProps$1, Gn as getFieldRegistryEntry, H as toBioTemplateComponentPropsById, Hn as omitUndefined, Kn as getTypeDefault, L as toBioTemplateComponentBindings, P as getBioTemplateComponentBindings, R as toBioTemplateComponentBindingsById, Tn as deriveShade, U as toBioTemplateComponentSnippets, Un as recordValue, V as toBioTemplateComponentPropsByComponent$1, Vn as normalizeControlDefinition, W as toBioTemplateComponentUsage, Wn as replaceRecord$1, Yn as useConcentrationUnits, Yt as getBioTemplatePresetInfo, ar as getInjectedPlatformContext, bn as rowIndex, br as isRecord, cn as createTemplateCollection, d as createBioTemplatePresetCollectionFromControls, dn as ensureTemplateFromCollection, fn as extractTemplateCollection, fr as isActiveJobStatus, gr as normalizeJobState, i as createBioTemplateControlToolkit, ir as currentExperimentFromContext, jn as useControlSchema, mn as PLATE_CONFIGS, or as parseExperimentId, p as createBioTemplatePackCollection, sr as resolveCurrentExperimentId, u as createBioTemplatePresetCollection, vr as useEventStream, xn as rowLabel, z as toBioTemplateComponentImports, zn as controlsToViewItems } from "./templates-BU1pet28.js";
4
+ import { a as useApi, i as authenticationRequiredError, n as readErrorMessage, o as useAuthStore, r as AuthenticationRequiredError, t as useExperimentStore } from "./experiment-DgllIc3I.js";
7
5
  import { computed, effectScope, getCurrentScope, inject, nextTick, onMounted, onScopeDispose, onUnmounted, provide, reactive, readonly, ref, shallowRef, toRaw, toValue, unref, watch } from "vue";
8
6
  //#region src/composables/useRovingFocus.ts
9
7
  /**
@@ -49,66 +47,6 @@ function useRovingFocus(options) {
49
47
  return { handleKeydown };
50
48
  }
51
49
  //#endregion
52
- //#region src/composables/useMenuKeyboard.ts
53
- /**
54
- * Shared keyboard contract for menu- and listbox-style disclosure panels (the
55
- * WAI-ARIA menu button pattern). Used by ActionMenu, AppAvatarMenu and DropdownButton,
56
- * each pairing it with `useDropdownState` for the disclosure state itself.
57
- */
58
- function useMenuKeyboard(options) {
59
- function focusItem(position) {
60
- nextTick(() => {
61
- const items = options.items();
62
- (position === "first" ? items[0] : items[items.length - 1])?.focus();
63
- });
64
- }
65
- function handleTriggerKeydown(event) {
66
- if (options.isOpen()) return;
67
- if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
68
- event.preventDefault();
69
- options.open();
70
- focusItem("first");
71
- return;
72
- }
73
- if (event.key === "ArrowUp") {
74
- event.preventDefault();
75
- options.open();
76
- focusItem("last");
77
- }
78
- }
79
- function handlePanelKeydown(event) {
80
- const items = options.items();
81
- const currentIndex = items.indexOf(document.activeElement);
82
- if (event.key === "Escape") {
83
- event.preventDefault();
84
- options.close(true);
85
- return;
86
- }
87
- if (event.key === "Tab") {
88
- options.close();
89
- return;
90
- }
91
- if (event.key === "Home") {
92
- event.preventDefault();
93
- items[0]?.focus();
94
- return;
95
- }
96
- if (event.key === "End") {
97
- event.preventDefault();
98
- items[items.length - 1]?.focus();
99
- return;
100
- }
101
- if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
102
- event.preventDefault();
103
- const direction = event.key === "ArrowDown" ? 1 : -1;
104
- items[(Math.max(currentIndex, 0) + direction + items.length) % items.length]?.focus();
105
- }
106
- return {
107
- handleTriggerKeydown,
108
- handlePanelKeydown
109
- };
110
- }
111
- //#endregion
112
50
  //#region src/composables/useSortedItems.ts
113
51
  /** Shared sorting for SDK tables and lists with stable empty-value handling. */
114
52
  function useSortedItems(options) {
@@ -264,6 +202,221 @@ function useTheme() {
264
202
  };
265
203
  }
266
204
  //#endregion
205
+ //#region src/composables/usePlatformContext.ts
206
+ var DEFAULT_CONTEXT = {
207
+ isIntegrated: false,
208
+ theme: "system"
209
+ };
210
+ var platformContext = ref({ ...DEFAULT_CONTEXT });
211
+ var inferredOrigins = /* @__PURE__ */ new Set();
212
+ var allowedOrigins = /* @__PURE__ */ new Set();
213
+ var allowAnyOrigin = false;
214
+ var initialized = false;
215
+ var listenerCount = 0;
216
+ var nextConsumerId = 0;
217
+ var currentHandler = null;
218
+ var consumerOrigins = /* @__PURE__ */ new Map();
219
+ var allowAnyOriginConsumers = /* @__PURE__ */ new Set();
220
+ /**
221
+ * Derive origin from URL (protocol + host)
222
+ */
223
+ function getOriginFromUrl(url) {
224
+ try {
225
+ return new URL(url).origin;
226
+ } catch {
227
+ return null;
228
+ }
229
+ }
230
+ function normalizeAllowedOrigins(origins) {
231
+ const normalized = /* @__PURE__ */ new Set();
232
+ if (!origins) return normalized;
233
+ for (const origin of origins) normalized.add(getOriginFromUrl(origin) || origin);
234
+ return normalized;
235
+ }
236
+ function recomputeOriginPolicy() {
237
+ allowedOrigins = new Set(inferredOrigins);
238
+ for (const origins of consumerOrigins.values()) for (const origin of origins) allowedOrigins.add(origin);
239
+ allowAnyOrigin = allowAnyOriginConsumers.size > 0;
240
+ }
241
+ function resetPlatformContextState() {
242
+ inferredOrigins = /* @__PURE__ */ new Set();
243
+ allowedOrigins = /* @__PURE__ */ new Set();
244
+ allowAnyOrigin = false;
245
+ initialized = false;
246
+ listenerCount = 0;
247
+ currentHandler = null;
248
+ consumerOrigins.clear();
249
+ allowAnyOriginConsumers.clear();
250
+ platformContext.value = { ...DEFAULT_CONTEXT };
251
+ }
252
+ /**
253
+ * Check if an origin is allowed for postMessage communication
254
+ */
255
+ function isOriginAllowed(origin) {
256
+ if (allowAnyOrigin) {
257
+ console.warn("[MINT SDK] postMessage origin validation disabled - only use in development");
258
+ return true;
259
+ }
260
+ if (origin === window.location.origin) return true;
261
+ return allowedOrigins.has(origin);
262
+ }
263
+ /**
264
+ * Platform context composable for plugin integration with MINT Platform.
265
+ *
266
+ * Provides secure communication with the parent platform via postMessage.
267
+ *
268
+ * @param options - Configuration options
269
+ * @param options.allowedOrigins - List of allowed origins for postMessage
270
+ * @param options.allowAnyOrigin - Allow any origin (UNSAFE, development only)
271
+ *
272
+ * @example
273
+ * ```typescript
274
+ * // Basic usage - derives origin from platform injection
275
+ * const { isIntegrated, user, theme } = usePlatformContext()
276
+ *
277
+ * // With explicit allowed origins
278
+ * const { isIntegrated } = usePlatformContext({
279
+ * allowedOrigins: ['https://mint.example.com']
280
+ * })
281
+ *
282
+ * // Development mode (UNSAFE)
283
+ * const { isIntegrated } = usePlatformContext({
284
+ * allowAnyOrigin: import.meta.env.DEV
285
+ * })
286
+ * ```
287
+ */
288
+ /** Connects a plugin to the MINT platform via postMessage, exposing user, theme, and experiment context. */
289
+ function usePlatformContext(options = {}) {
290
+ const consumerId = ++nextConsumerId;
291
+ const instanceOrigins = normalizeAllowedOrigins(options.allowedOrigins);
292
+ const instanceAllowAnyOrigin = options.allowAnyOrigin === true;
293
+ function detectPlatform() {
294
+ const detectedOrigins = /* @__PURE__ */ new Set();
295
+ const platformData = window.__MINT_PLATFORM__;
296
+ if (platformData) {
297
+ platformContext.value = {
298
+ ...platformData,
299
+ isIntegrated: true
300
+ };
301
+ if (platformData.platformOrigin) detectedOrigins.add(platformData.platformOrigin);
302
+ else if (platformData.platformApiUrl) {
303
+ const origin = getOriginFromUrl(platformData.platformApiUrl);
304
+ if (origin) detectedOrigins.add(origin);
305
+ }
306
+ } else {
307
+ const urlParams = new URLSearchParams(window.location.search);
308
+ const hasPluginParam = urlParams.has("mint-plugin");
309
+ const platformOrigin = urlParams.get("mint-origin");
310
+ if (platformOrigin) {
311
+ const origin = getOriginFromUrl(platformOrigin);
312
+ if (origin) detectedOrigins.add(origin);
313
+ }
314
+ platformContext.value = {
315
+ isIntegrated: hasPluginParam,
316
+ theme: (() => {
317
+ try {
318
+ const s = localStorage.getItem("mint-settings");
319
+ if (s) return JSON.parse(s).theme || "system";
320
+ } catch {}
321
+ return "system";
322
+ })(),
323
+ platformOrigin: platformOrigin || void 0
324
+ };
325
+ }
326
+ inferredOrigins = detectedOrigins;
327
+ }
328
+ function handlePlatformMessage(event) {
329
+ if (event.source !== window.parent) return;
330
+ if (!isOriginAllowed(event.origin)) {
331
+ console.warn(`[MINT SDK] Rejected postMessage from untrusted origin: ${event.origin}`);
332
+ return;
333
+ }
334
+ try {
335
+ const platformEvent = event.data;
336
+ if (!platformEvent.type?.startsWith("mint:")) return;
337
+ switch (platformEvent.type) {
338
+ case "mint:theme-changed":
339
+ platformContext.value.theme = platformEvent.payload;
340
+ break;
341
+ case "mint:user-changed": platformContext.value.user = platformEvent.payload;
342
+ }
343
+ } catch {}
344
+ }
345
+ /**
346
+ * Send a message to the parent platform.
347
+ * Uses validated target origin for security.
348
+ */
349
+ function sendToPlatform(event) {
350
+ if (!platformContext.value.isIntegrated || window.parent === window) return;
351
+ let targetOrigin;
352
+ if (platformContext.value.platformOrigin) targetOrigin = platformContext.value.platformOrigin;
353
+ else if (allowedOrigins.size > 0) targetOrigin = allowedOrigins.values().next().value;
354
+ else if (allowAnyOrigin) {
355
+ targetOrigin = "*";
356
+ console.warn("[MINT SDK] Using wildcard origin for postMessage - only use in development");
357
+ } else {
358
+ console.warn("[MINT SDK] Cannot send postMessage: no platform origin configured");
359
+ return;
360
+ }
361
+ window.parent.postMessage(event, targetOrigin);
362
+ }
363
+ /**
364
+ * Request navigation to a path in the platform.
365
+ */
366
+ function navigate(path) {
367
+ sendToPlatform({
368
+ type: "mint:navigate",
369
+ payload: path
370
+ });
371
+ }
372
+ /**
373
+ * Show a notification in the platform.
374
+ */
375
+ function notify(message, type = "info") {
376
+ sendToPlatform({
377
+ type: "mint:notification",
378
+ payload: {
379
+ message,
380
+ type
381
+ }
382
+ });
383
+ }
384
+ onMounted(() => {
385
+ consumerOrigins.set(consumerId, instanceOrigins);
386
+ if (instanceAllowAnyOrigin) allowAnyOriginConsumers.add(consumerId);
387
+ if (!initialized) {
388
+ detectPlatform();
389
+ currentHandler = handlePlatformMessage;
390
+ window.addEventListener("message", handlePlatformMessage);
391
+ initialized = true;
392
+ }
393
+ listenerCount++;
394
+ recomputeOriginPolicy();
395
+ });
396
+ onUnmounted(() => {
397
+ consumerOrigins.delete(consumerId);
398
+ allowAnyOriginConsumers.delete(consumerId);
399
+ listenerCount = Math.max(0, listenerCount - 1);
400
+ if (listenerCount === 0) {
401
+ if (currentHandler) window.removeEventListener("message", currentHandler);
402
+ resetPlatformContextState();
403
+ return;
404
+ }
405
+ recomputeOriginPolicy();
406
+ });
407
+ return {
408
+ context: platformContext,
409
+ isIntegrated: computed(() => platformContext.value.isIntegrated),
410
+ plugin: computed(() => platformContext.value.plugin),
411
+ user: computed(() => platformContext.value.user),
412
+ theme: computed(() => platformContext.value.theme),
413
+ features: computed(() => platformContext.value.features),
414
+ navigate,
415
+ notify,
416
+ sendToPlatform
417
+ };
418
+ }
419
+ //#endregion
267
420
  //#region src/composables/usePresenceHeartbeat.ts
268
421
  var PRESENCE_CLIENT_ID_KEY = "mint:presence:client-id";
269
422
  var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
@@ -595,137 +748,1082 @@ function useAppExperiment(options = {}) {
595
748
  };
596
749
  }
597
750
  //#endregion
598
- //#region src/composables/useMobileSupportGate.ts
599
- var DEFAULT_MOBILE_VIEWPORT_QUERY = "(max-width: 767px)";
600
- function useMobileSupportGate(options = {}) {
601
- const isMobileViewport = ref(false);
602
- const mediaQuery = computed(() => {
603
- return unref(options.mediaQuery)?.trim() || "(max-width: 767px)";
604
- });
605
- const isSupported = computed(() => {
606
- if (options.supported === void 0) return true;
607
- return Boolean(unref(options.supported));
608
- });
609
- const shouldShowUnsupported = computed(() => isMobileViewport.value && !isSupported.value);
610
- let mediaQueryList = null;
611
- let removeMediaListener;
612
- const syncMobileState = (event) => {
613
- isMobileViewport.value = Boolean(event?.matches ?? mediaQueryList?.matches);
614
- };
615
- const unbindMediaQuery = () => {
616
- removeMediaListener?.();
617
- removeMediaListener = void 0;
618
- mediaQueryList = null;
751
+ //#region src/composables/controlWorkspaceOptions.ts
752
+ function mergeControlWorkspaceOptions(base, override) {
753
+ return {
754
+ ...base ?? {},
755
+ ...override ?? {},
756
+ initialValues: {
757
+ ...base?.initialValues ?? {},
758
+ ...override?.initialValues ?? {}
759
+ },
760
+ topBarSettings: {
761
+ ...base?.topBarSettings ?? {},
762
+ ...override?.topBarSettings ?? {}
763
+ }
619
764
  };
620
- const bindMediaQuery = () => {
621
- unbindMediaQuery();
622
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
623
- isMobileViewport.value = false;
624
- return;
765
+ }
766
+ //#endregion
767
+ //#region src/composables/controlComponentBindings.ts
768
+ /** Map control workspace values into component props for direct `v-bind` usage. */
769
+ function controlValuesToComponentProps(values, mapping) {
770
+ if (mapping === void 0) return { ...values };
771
+ if (Array.isArray(mapping)) {
772
+ const props = {};
773
+ for (const key of mapping) props[key] = values[key];
774
+ return props;
775
+ }
776
+ const props = {};
777
+ for (const [prop, source] of Object.entries(mapping)) props[prop] = typeof source === "function" ? source(values) : values[source];
778
+ return props;
779
+ }
780
+ /** Map control workspace values into named SDK component bindings for direct slot rendering. */
781
+ function controlValuesToComponentBindings(values, bindings) {
782
+ if (bindings === void 0) return [];
783
+ return normalizeControlComponentBindingConfigs(bindings).map((binding) => ({
784
+ id: binding.id,
785
+ component: binding.component,
786
+ props: controlValuesToComponentProps(values, binding.props)
787
+ }));
788
+ }
789
+ /** Map control workspace values into SDK component bindings keyed by binding id. */
790
+ function controlValuesToComponentBindingsById(values, bindings) {
791
+ return Object.fromEntries(controlValuesToComponentBindings(values, bindings).map((binding) => [binding.id, binding]));
792
+ }
793
+ function normalizeControlComponentBindingConfigs(bindings) {
794
+ if (Array.isArray(bindings)) {
795
+ const usedIds = /* @__PURE__ */ new Map();
796
+ return bindings.map((binding) => ({
797
+ id: uniqueComponentBindingId(binding.id ?? binding.component, usedIds),
798
+ component: binding.component,
799
+ props: binding.props
800
+ }));
801
+ }
802
+ return Object.entries(bindings).map(([id, binding]) => ({
803
+ id,
804
+ component: binding.component,
805
+ props: binding.props
806
+ }));
807
+ }
808
+ function uniqueComponentBindingId(id, usedIds) {
809
+ const count = usedIds.get(id) ?? 0;
810
+ usedIds.set(id, count + 1);
811
+ return count === 0 ? id : `${id}-${count + 1}`;
812
+ }
813
+ //#endregion
814
+ //#region src/composables/controlSchemaModel.ts
815
+ function isControlModelBinding(model) {
816
+ return typeof model === "object" && model !== null && "controls" in model && "controlOptions" in model;
817
+ }
818
+ function resolveControlModel(model) {
819
+ if (model === void 0) return void 0;
820
+ return isControlModelBinding(model) ? model : defineControlModel(model);
821
+ }
822
+ /**
823
+ * Create a complete workspace component binding from a simple controls data model for ControlWorkspaceView, generated forms, and sidebars.
824
+ *
825
+ * @example Recommended
826
+ * ```ts
827
+ * const workspaceModel = defineControlModel({
828
+ * controls: { threshold: { type: 'number', default: 0.05 } },
829
+ * })
830
+ * ```
831
+ */
832
+ function defineControlModel(model) {
833
+ const { controls: rootControls, sections: rootSections, views: modelViews, components, componentBindings, componentProps, componentPropsById, ...baseOptions } = model;
834
+ const resolvedComponentBindings = componentBindings ?? components;
835
+ const controls = {};
836
+ const views = {};
837
+ const sections = {};
838
+ const defaultViewId = model.view ?? "default";
839
+ const defaultSectionId = model.section ?? "controls";
840
+ if (rootControls) {
841
+ registerSectionOptions(sections, defaultSectionId, {
842
+ id: defaultSectionId,
843
+ label: model.sectionLabel,
844
+ title: model.sectionLabel,
845
+ columns: model.columns
846
+ });
847
+ appendModelControls(controls, rootControls, defaultViewId, defaultSectionId);
848
+ }
849
+ for (const [sectionId, section] of Object.entries(rootSections ?? {})) {
850
+ registerSectionOptions(sections, sectionId, controlModelSectionOptions(section));
851
+ appendModelControls(controls, section.controls, defaultViewId, sectionId, section.sidebar);
852
+ }
853
+ for (const [viewId, view] of Object.entries(modelViews ?? {})) {
854
+ const { controls: viewControls, sections: viewSections, section, sectionLabel, sectionDescription, ...viewConfig } = view;
855
+ views[viewId] = omitUndefined(viewConfig);
856
+ if (viewControls) {
857
+ const sectionId = section ?? `${viewId}-controls`;
858
+ const label = sectionLabel ?? view.label;
859
+ registerSectionOptions(sections, sectionId, {
860
+ id: sectionId,
861
+ label,
862
+ title: label,
863
+ description: sectionDescription,
864
+ columns: model.columns
865
+ });
866
+ appendModelControls(controls, viewControls, viewId, sectionId);
625
867
  }
626
- const queryList = window.matchMedia(mediaQuery.value);
627
- mediaQueryList = queryList;
628
- syncMobileState(queryList);
629
- const handleChange = (event) => syncMobileState(event);
630
- if (typeof queryList.addEventListener === "function") {
631
- queryList.addEventListener("change", handleChange);
632
- removeMediaListener = () => queryList.removeEventListener("change", handleChange);
633
- return;
868
+ for (const [sectionKey, sectionConfig] of Object.entries(viewSections ?? {})) {
869
+ const sectionId = sectionConfig.id ?? `${viewId}-${sectionKey}`;
870
+ registerSectionOptions(sections, sectionId, controlModelSectionOptions(sectionConfig));
871
+ appendModelControls(controls, sectionConfig.controls, viewId, sectionId, sectionConfig.sidebar);
634
872
  }
635
- queryList.addListener(handleChange);
636
- removeMediaListener = () => queryList.removeListener(handleChange);
637
- };
638
- onMounted(bindMediaQuery);
639
- watch(mediaQuery, bindMediaQuery, { flush: "post" });
640
- onUnmounted(unbindMediaQuery);
873
+ }
874
+ const controlOptions = { ...baseOptions };
875
+ if (Object.keys(views).length > 0) controlOptions.views = views;
876
+ if (Object.keys(sections).length > 0) controlOptions.sections = sections;
641
877
  return {
642
- isMobileViewport,
643
- isSupported,
644
- shouldShowUnsupported
878
+ controls,
879
+ controlOptions,
880
+ ...resolvedComponentBindings === void 0 ? {} : { componentBindings: resolvedComponentBindings },
881
+ ...componentProps === void 0 ? {} : { componentProps },
882
+ ...componentPropsById === void 0 ? {} : { componentPropsById }
645
883
  };
646
884
  }
885
+ function appendModelControls(target, source, viewId, sectionId, sidebar) {
886
+ for (const [name, input] of Object.entries(source)) {
887
+ if (Object.prototype.hasOwnProperty.call(target, name)) throw new Error(`Duplicate control "${name}" in defineControlModel(). Control names must be unique across views and sections.`);
888
+ const definition = normalizeControlDefinition(input);
889
+ target[name] = omitUndefined({
890
+ ...definition,
891
+ view: definition.view ?? viewId,
892
+ section: definition.section ?? sectionId,
893
+ sidebar: definition.sidebar ?? sidebar
894
+ });
895
+ }
896
+ }
897
+ function controlModelSectionOptions(section) {
898
+ const { controls: _controls, sidebar: _sidebar, ...options } = section;
899
+ return omitUndefined(options);
900
+ }
901
+ function registerSectionOptions(target, sectionId, options) {
902
+ target[sectionId] = omitUndefined({
903
+ ...target[sectionId] ?? {},
904
+ ...options,
905
+ id: sectionId
906
+ });
907
+ }
647
908
  //#endregion
648
- //#region src/components/PluginWorkspaceView.controls.ts
649
- function usePluginWorkspaceControls(options) {
650
- const resolvedModel = computed(() => resolveControlModel(options.model()));
651
- const resolvedControls = computed(() => options.controls() ?? resolvedModel.value?.controls);
652
- const resolvedControlOptions = computed(() => mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, options.controlOptions()));
653
- const externalControlValues = computed(() => options.modelValue() ?? options.values());
654
- const internalControlValues = reactive({});
655
- let syncingControlValues = false;
656
- const generatedControlDefaults = computed(() => {
657
- if (!resolvedControls.value) return {};
658
- return {
659
- ...getControlDefaults(resolvedControls.value),
660
- ...resolvedControlOptions.value.initialValues ?? {}
661
- };
909
+ //#region src/composables/useControlWorkspace.ts
910
+ /**
911
+ * Prepare shared reactive values plus AppTopBar/AppSidebar/FormBuilder bindings from one simple controls data model.
912
+ *
913
+ * @example Recommended
914
+ * ```ts
915
+ * const workspace = useControlWorkspace(workspaceModel)
916
+ * const { form, sidebar, topBar } = workspace.bindings
917
+ * ```
918
+ */
919
+ function useControlWorkspace(controlsOrModel, options = {}) {
920
+ const model = isControlModelBinding(controlsOrModel) ? controlsOrModel : void 0;
921
+ const controls = model ? model.controls : controlsOrModel;
922
+ const { initialValues, topBarSettings, ...schemaOptions } = model ? mergeControlWorkspaceOptions(model.controlOptions, options) : options;
923
+ const schema = useControlSchema(controls, schemaOptions);
924
+ const activeView = ref(schema.defaultView);
925
+ const values = reactive({
926
+ ...schema.initialValues,
927
+ ...initialValues ?? {}
662
928
  });
663
- function replaceControlValues(values) {
664
- for (const key of Object.keys(internalControlValues)) if (!(key in values)) delete internalControlValues[key];
665
- Object.assign(internalControlValues, values);
929
+ function setActiveView(viewId) {
930
+ syncActiveView(viewId);
666
931
  }
667
- function emitControlValues() {
668
- options.emitValues({ ...internalControlValues });
932
+ function syncActiveView(viewId) {
933
+ if (!schema.viewIds.includes(viewId)) return;
934
+ if (activeView.value !== viewId) activeView.value = viewId;
935
+ if (sidebar.activeView !== viewId) sidebar.activeView = viewId;
936
+ if (pillNav.currentItemId !== viewId) pillNav.currentItemId = viewId;
669
937
  }
670
- function syncResolvedControlValues() {
671
- syncingControlValues = true;
672
- replaceControlValues({
673
- ...generatedControlDefaults.value,
674
- ...externalControlValues.value === void 0 ? internalControlValues : externalControlValues.value
675
- });
676
- syncingControlValues = false;
938
+ function setValues(nextValues) {
939
+ Object.assign(values, nextValues);
677
940
  }
678
- function handleControlValuesUpdate(values) {
679
- syncingControlValues = true;
680
- replaceControlValues({
681
- ...generatedControlDefaults.value,
682
- ...values
941
+ function resetValues(nextValues = {}) {
942
+ replaceRecord$1(values, {
943
+ ...schema.initialValues,
944
+ ...nextValues
683
945
  });
684
- syncingControlValues = false;
685
- emitControlValues();
686
946
  }
687
- const resolvedComponentBindings = computed(() => controlValuesToComponentBindings(internalControlValues, options.componentBindings() ?? resolvedModel.value?.componentBindings));
688
- const resolvedComponentBindingsById = computed(() => controlValuesToComponentBindingsById(internalControlValues, options.componentBindings() ?? resolvedModel.value?.componentBindings));
689
- const resolvedComponentProps = computed(() => {
690
- const mapping = options.componentProps() ?? resolvedModel.value?.componentProps;
691
- return mapping === void 0 ? {} : controlValuesToComponentProps(internalControlValues, mapping);
692
- });
693
- const resolvedComponentPropsById = computed(() => {
694
- const mappings = options.componentPropsById() ?? resolvedModel.value?.componentPropsById;
947
+ function getComponentProps(mapping) {
948
+ return controlValuesToComponentProps(values, mapping);
949
+ }
950
+ function getComponentPropsById(mappings) {
695
951
  if (mappings === void 0) return {};
696
- return Object.fromEntries(Object.entries(mappings).map(([id, mapping]) => [id, controlValuesToComponentProps(internalControlValues, mapping)]));
697
- });
698
- const generatedPillNav = computed(() => {
699
- if (!resolvedControls.value) return [];
700
- return controlsToViewItems(resolvedControls.value, resolvedControlOptions.value);
701
- });
702
- const generatedDefaultView = computed(() => {
703
- if (!resolvedControls.value) return "";
704
- return getDefaultControlView(resolvedControls.value, resolvedControlOptions.value);
952
+ return Object.fromEntries(Object.entries(mappings).map(([id, mapping]) => [id, controlValuesToComponentProps(values, mapping)]));
953
+ }
954
+ function getComponentBindings(bindings) {
955
+ return controlValuesToComponentBindings(values, bindings);
956
+ }
957
+ function getComponentBindingsById(bindings) {
958
+ return controlValuesToComponentBindingsById(values, bindings);
959
+ }
960
+ const componentProps = computed(() => model?.componentProps === void 0 ? {} : getComponentProps(model.componentProps));
961
+ const componentPropsById = computed(() => getComponentPropsById(model?.componentPropsById));
962
+ const componentBindings = computed(() => getComponentBindings(model?.componentBindings));
963
+ const componentBindingsById = computed(() => getComponentBindingsById(model?.componentBindings));
964
+ const form = {
965
+ ...schema.form,
966
+ modelValue: values,
967
+ "onUpdate:modelValue": setValues
968
+ };
969
+ const topBarSettingsConfig = {
970
+ ...schema.topBarSettingsConfig,
971
+ ...topBarSettings ?? {},
972
+ values
973
+ };
974
+ const sidebar = reactive({
975
+ ...schema.sidebar,
976
+ activeView: activeView.value,
977
+ modelValue: values,
978
+ "onUpdate:modelValue": setValues,
979
+ values,
980
+ "onUpdate:values": setValues
705
981
  });
706
- syncResolvedControlValues();
707
- watch([generatedControlDefaults, externalControlValues], syncResolvedControlValues, { deep: true });
708
- watch(internalControlValues, () => {
709
- if (syncingControlValues) return;
710
- emitControlValues();
711
- }, {
712
- deep: true,
713
- flush: "sync"
982
+ const pillNav = reactive({
983
+ items: schema.viewItems,
984
+ currentItemId: activeView.value,
985
+ onSelect: (item) => setActiveView(item.id)
714
986
  });
715
- return {
716
- resolvedModel,
717
- resolvedControls,
718
- resolvedControlOptions,
719
- generatedPillNav,
720
- generatedDefaultView,
721
- internalControlValues,
722
- resolvedComponentBindings,
723
- resolvedComponentBindingsById,
724
- resolvedComponentProps,
725
- resolvedComponentPropsById,
726
- handleControlValuesUpdate
987
+ const topBarSettingsBinding = {
988
+ showSettings: true,
989
+ settingsConfig: topBarSettingsConfig,
990
+ onSettingsValuesChange: setValues
727
991
  };
728
- }
992
+ const topBarProps = computed(() => ({
993
+ pillNav: pillNav.items,
994
+ currentPillId: pillNav.currentItemId,
995
+ onPillSelect: pillNav.onSelect,
996
+ ...topBarSettingsBinding
997
+ }));
998
+ const bindings = {
999
+ form,
1000
+ sidebar,
1001
+ topBar: topBarProps,
1002
+ topBarSettings: topBarSettingsBinding,
1003
+ pillNav,
1004
+ componentBindings,
1005
+ componentBindingsById,
1006
+ componentProps,
1007
+ componentPropsById
1008
+ };
1009
+ watch(activeView, syncActiveView, { flush: "sync" });
1010
+ watch(() => sidebar.activeView, syncActiveView, { flush: "sync" });
1011
+ watch(() => pillNav.currentItemId, syncActiveView, { flush: "sync" });
1012
+ return {
1013
+ ...schema,
1014
+ schema,
1015
+ values,
1016
+ activeView,
1017
+ topBarSettingsConfig,
1018
+ form,
1019
+ sidebar,
1020
+ topBar: topBarProps,
1021
+ pillNav,
1022
+ topBarSettings: topBarSettingsBinding,
1023
+ bindings,
1024
+ componentBindings,
1025
+ componentBindingsById,
1026
+ componentProps,
1027
+ componentPropsById,
1028
+ setActiveView,
1029
+ setValues,
1030
+ resetValues,
1031
+ getComponentProps,
1032
+ getComponentPropsById,
1033
+ getComponentBindings,
1034
+ getComponentBindingsById
1035
+ };
1036
+ }
1037
+ //#endregion
1038
+ //#region src/composables/controlSchemaDoseDesign.ts
1039
+ /** Return a default WellPlate prop mapping for generated control workspaces. */
1040
+ function defineWellPlateControlProps(options = {}) {
1041
+ const selectedWells = options.selectedWells ?? sourceKey("selectedWells");
1042
+ return compactComponentPropsMap({
1043
+ modelValue: selectedWells,
1044
+ "onUpdate:modelValue": options.onUpdateModelValue === false ? void 0 : options.onUpdateModelValue ?? updateControlValueSource(selectedWells),
1045
+ format: options.format ?? sourceKey("plateFormat"),
1046
+ wells: options.wells ?? sourceKey("wells"),
1047
+ disabled: options.disabled ?? sourceKey("disabled"),
1048
+ readonly: options.readonly ?? sourceKey("readonly"),
1049
+ showLegend: options.showLegend,
1050
+ showSampleTypeIndicator: options.showSampleTypeIndicator
1051
+ });
1052
+ }
1053
+ /** Return a default DoseCalculator prop mapping for generated control workspaces. */
1054
+ function defineDoseCalculatorControlProps(options = {}) {
1055
+ const targetWells = options.targetWells ?? sourceKey("selectedWells");
1056
+ const wells = options.wells ?? sourceKey("wells");
1057
+ const onApplyToWells = options.onApplyToWells === false ? void 0 : options.onApplyToWells ?? defaultDoseApplyHandler(wells, targetWells);
1058
+ return compactComponentPropsMap({
1059
+ mode: options.mode ?? sourceKey("doseMode"),
1060
+ targetWells,
1061
+ disabled: options.disabled ?? sourceKey("disabled"),
1062
+ molecularWeight: options.molecularWeight,
1063
+ onApplyToWells
1064
+ });
1065
+ }
1066
+ /** Return named WellPlate + DoseCalculator prop mappings for one dose-design control model. */
1067
+ function defineWellPlateDoseControlProps(options = {}) {
1068
+ return {
1069
+ [options.plateId ?? "plate"]: defineWellPlateControlProps(options.plate),
1070
+ [options.doseId ?? "dose"]: defineDoseCalculatorControlProps(options.dose)
1071
+ };
1072
+ }
1073
+ /** Return named WellPlate + DoseCalculator component bindings for one dose-design control model. */
1074
+ function defineWellPlateDoseComponentBindings(options = {}) {
1075
+ return [{
1076
+ id: options.plateId ?? "plate",
1077
+ component: "WellPlate",
1078
+ props: defineWellPlateControlProps(options.plate)
1079
+ }, {
1080
+ id: options.doseId ?? "dose",
1081
+ component: "DoseCalculator",
1082
+ props: defineDoseCalculatorControlProps(options.dose)
1083
+ }];
1084
+ }
1085
+ /**
1086
+ * Return a complete ControlWorkspaceView model for WellPlate + DoseCalculator dose design.
1087
+ *
1088
+ * @example Recommended
1089
+ * ```ts
1090
+ * const workspaceModel = defineDoseDesignControlModel({ selectedWells: ['A1', 'A2'] })
1091
+ * ```
1092
+ */
1093
+ function defineDoseDesignControlModel(options = {}) {
1094
+ const viewId = options.viewId ?? "design";
1095
+ const sectionId = options.sectionId ?? "dose";
1096
+ const doseControls = {
1097
+ selectedWells: {
1098
+ type: "tags",
1099
+ label: "Selected wells",
1100
+ default: [...options.selectedWells ?? ["A1", "A2"]]
1101
+ },
1102
+ plateFormat: {
1103
+ label: "Plate format",
1104
+ default: options.plateFormat ?? 96,
1105
+ options: options.plateFormats ?? [96, 384]
1106
+ },
1107
+ doseMode: {
1108
+ label: "Dose mode",
1109
+ default: options.doseMode ?? "serial",
1110
+ options: options.doseModes ?? [
1111
+ "serial",
1112
+ "dilution",
1113
+ "conversion"
1114
+ ]
1115
+ },
1116
+ disabled: {
1117
+ label: "Disable tools",
1118
+ default: options.disabled ?? false
1119
+ }
1120
+ };
1121
+ if (options.includeMolecularWeight) doseControls.molecularWeight = {
1122
+ type: "number",
1123
+ label: "Molecular weight",
1124
+ default: options.molecularWeight ?? 300,
1125
+ min: 0
1126
+ };
1127
+ const componentProps = {
1128
+ ...options.componentProps,
1129
+ dose: {
1130
+ ...options.componentProps?.dose ?? {},
1131
+ ...options.includeMolecularWeight && options.componentProps?.dose?.molecularWeight === void 0 ? { molecularWeight: "molecularWeight" } : {}
1132
+ }
1133
+ };
1134
+ return defineControlModel({
1135
+ componentBindings: defineWellPlateDoseComponentBindings(componentProps),
1136
+ componentPropsById: defineWellPlateDoseControlProps(componentProps),
1137
+ views: { [viewId]: {
1138
+ label: options.viewLabel ?? "Design",
1139
+ sections: { [sectionId]: {
1140
+ label: options.sectionLabel ?? "Dose design",
1141
+ description: options.sectionDescription ?? "Well selection and dose calculator controls",
1142
+ controls: doseControls
1143
+ } }
1144
+ } }
1145
+ });
1146
+ }
1147
+ function compactComponentPropsMap(mapping) {
1148
+ return Object.fromEntries(Object.entries(mapping).filter((entry) => entry[1] !== void 0));
1149
+ }
1150
+ function sourceKey(key) {
1151
+ return key;
1152
+ }
1153
+ function updateControlValueSource(source) {
1154
+ if (typeof source !== "string") return void 0;
1155
+ return (values) => (value) => {
1156
+ const writableValues = values;
1157
+ writableValues[source] = value;
1158
+ };
1159
+ }
1160
+ function defaultDoseApplyHandler(wells, targetWells) {
1161
+ if (wells === targetWells) return void 0;
1162
+ return updateWellsFromDoseResults(wells);
1163
+ }
1164
+ function updateWellsFromDoseResults(source) {
1165
+ if (typeof source !== "string") return void 0;
1166
+ return (values) => (results) => {
1167
+ const writableValues = values;
1168
+ const currentWells = recordValue(writableValues[source]);
1169
+ const nextWells = {};
1170
+ for (const [wellId, well] of Object.entries(currentWells)) nextWells[wellId] = recordValue(well);
1171
+ for (const result of results) {
1172
+ const existing = recordValue(nextWells[result.wellId]);
1173
+ const metadata = recordValue(existing.metadata);
1174
+ nextWells[result.wellId] = {
1175
+ ...existing,
1176
+ state: "filled",
1177
+ value: result.concentration.value,
1178
+ metadata: {
1179
+ ...metadata,
1180
+ concentration: result.concentration,
1181
+ ...result.volume === void 0 ? {} : { volume: result.volume }
1182
+ }
1183
+ };
1184
+ }
1185
+ writableValues[source] = nextWells;
1186
+ };
1187
+ }
1188
+ //#endregion
1189
+ //#region src/composables/useFormValidation.ts
1190
+ var validators = {
1191
+ required: (value, message = "This field is required") => {
1192
+ if (value === null || value === void 0 || value === "") return message;
1193
+ if (Array.isArray(value) && value.length === 0) return message;
1194
+ return null;
1195
+ },
1196
+ minLength: (value, min, message) => {
1197
+ if (typeof value !== "string") return null;
1198
+ if (value.length < min) return message || `Must be at least ${min} characters`;
1199
+ return null;
1200
+ },
1201
+ maxLength: (value, max, message) => {
1202
+ if (typeof value !== "string") return null;
1203
+ if (value.length > max) return message || `Must be at most ${max} characters`;
1204
+ return null;
1205
+ },
1206
+ min: (value, min, message) => {
1207
+ if (typeof value !== "number") return null;
1208
+ if (value < min) return message || `Must be at least ${min}`;
1209
+ return null;
1210
+ },
1211
+ max: (value, max, message) => {
1212
+ if (typeof value !== "number") return null;
1213
+ if (value > max) return message || `Must be at most ${max}`;
1214
+ return null;
1215
+ },
1216
+ pattern: (value, pattern, message) => {
1217
+ if (typeof value !== "string") return null;
1218
+ if (!pattern.test(value)) return message || "Invalid format";
1219
+ return null;
1220
+ },
1221
+ email: (value, message = "Invalid email address") => {
1222
+ if (typeof value !== "string" || !value) return null;
1223
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return message;
1224
+ return null;
1225
+ }
1226
+ };
1227
+ function validateFieldValue(value, fieldRules, formData) {
1228
+ if (!fieldRules) return null;
1229
+ if (fieldRules.required) {
1230
+ const message = typeof fieldRules.required === "string" ? fieldRules.required : void 0;
1231
+ const error = validators.required(value, message);
1232
+ if (error) return error;
1233
+ }
1234
+ if (value === null || value === void 0 || value === "") return null;
1235
+ if (fieldRules.minLength !== void 0) {
1236
+ const config = typeof fieldRules.minLength === "number" ? {
1237
+ value: fieldRules.minLength,
1238
+ message: void 0
1239
+ } : fieldRules.minLength;
1240
+ const error = validators.minLength(value, config.value, config.message);
1241
+ if (error) return error;
1242
+ }
1243
+ if (fieldRules.maxLength !== void 0) {
1244
+ const config = typeof fieldRules.maxLength === "number" ? {
1245
+ value: fieldRules.maxLength,
1246
+ message: void 0
1247
+ } : fieldRules.maxLength;
1248
+ const error = validators.maxLength(value, config.value, config.message);
1249
+ if (error) return error;
1250
+ }
1251
+ if (fieldRules.min !== void 0) {
1252
+ const config = typeof fieldRules.min === "number" ? {
1253
+ value: fieldRules.min,
1254
+ message: void 0
1255
+ } : fieldRules.min;
1256
+ const error = validators.min(value, config.value, config.message);
1257
+ if (error) return error;
1258
+ }
1259
+ if (fieldRules.max !== void 0) {
1260
+ const config = typeof fieldRules.max === "number" ? {
1261
+ value: fieldRules.max,
1262
+ message: void 0
1263
+ } : fieldRules.max;
1264
+ const error = validators.max(value, config.value, config.message);
1265
+ if (error) return error;
1266
+ }
1267
+ if (fieldRules.pattern !== void 0) {
1268
+ const config = fieldRules.pattern instanceof RegExp ? {
1269
+ value: fieldRules.pattern,
1270
+ message: void 0
1271
+ } : fieldRules.pattern;
1272
+ const error = validators.pattern(value, config.value, config.message);
1273
+ if (error) return error;
1274
+ }
1275
+ if (fieldRules.email) {
1276
+ const message = typeof fieldRules.email === "string" ? fieldRules.email : void 0;
1277
+ const error = validators.email(value, message);
1278
+ if (error) return error;
1279
+ }
1280
+ if (fieldRules.custom) {
1281
+ const customRules = Array.isArray(fieldRules.custom) ? fieldRules.custom : [fieldRules.custom];
1282
+ for (const rule of customRules) {
1283
+ const error = rule(value, formData);
1284
+ if (error) return error;
1285
+ }
1286
+ }
1287
+ return null;
1288
+ }
1289
+ //#endregion
1290
+ //#region src/composables/useForm.ts
1291
+ /**
1292
+ * Form state management composable with validation.
1293
+ *
1294
+ * @param initialValues - Initial form values
1295
+ * @param rules - Validation rules for each field
1296
+ *
1297
+ * @example
1298
+ * ```typescript
1299
+ * const { data, errors, isValid, handleSubmit, getFieldProps } = useForm(
1300
+ * { email: '', password: '' },
1301
+ * {
1302
+ * email: { required: true, email: true },
1303
+ * password: { required: true, minLength: 8 },
1304
+ * }
1305
+ * )
1306
+ *
1307
+ * // In template
1308
+ * <BaseInput v-bind="getFieldProps('email')" label="Email" />
1309
+ * <BaseInput v-bind="getFieldProps('password')" type="password" label="Password" />
1310
+ * <BaseButton @click="handleSubmit(onSubmit)" :disabled="!isValid">Submit</BaseButton>
1311
+ * ```
1312
+ */
1313
+ /** Reactive form state with field-level validation, dirty tracking, and submit handling. */
1314
+ function useForm(initialValues, rules = {}) {
1315
+ const cloneableInitialValues = deepToRaw(initialValues);
1316
+ let _initialValues = structuredClone(cloneableInitialValues);
1317
+ const data = reactive(structuredClone(cloneableInitialValues));
1318
+ const errors = reactive(Object.keys(initialValues).reduce((acc, key) => {
1319
+ acc[key] = null;
1320
+ return acc;
1321
+ }, {}));
1322
+ const touched = reactive(Object.keys(initialValues).reduce((acc, key) => {
1323
+ acc[key] = false;
1324
+ return acc;
1325
+ }, {}));
1326
+ const dirty = reactive(Object.keys(initialValues).reduce((acc, key) => {
1327
+ acc[key] = false;
1328
+ return acc;
1329
+ }, {}));
1330
+ const isSubmitting = ref(false);
1331
+ watch(() => ({ ...data }), (newData) => {
1332
+ for (const key of Object.keys(newData)) dirty[key] = newData[key] !== _initialValues[key];
1333
+ }, { deep: true });
1334
+ function validateField(field) {
1335
+ const value = data[field];
1336
+ const fieldRules = rules[field];
1337
+ const error = validateFieldValue(value, fieldRules, data);
1338
+ errors[field] = error;
1339
+ return error === null;
1340
+ }
1341
+ function validate() {
1342
+ let isAllValid = true;
1343
+ for (const field of Object.keys(data)) if (!validateField(field)) isAllValid = false;
1344
+ return isAllValid;
1345
+ }
1346
+ const isValid = computed(() => {
1347
+ return Object.values(errors).every((error) => error === null);
1348
+ });
1349
+ const isDirty = computed(() => {
1350
+ return Object.values(dirty).some((d) => d);
1351
+ });
1352
+ function setFieldValue(field, value) {
1353
+ data[field] = value;
1354
+ if (touched[field]) validateField(field);
1355
+ }
1356
+ function setFieldError(field, error) {
1357
+ errors[field] = error;
1358
+ }
1359
+ function setFieldTouched(field, isTouched = true) {
1360
+ touched[field] = isTouched;
1361
+ if (isTouched) validateField(field);
1362
+ }
1363
+ function reset(values) {
1364
+ const resetValues = values ? {
1365
+ ..._initialValues,
1366
+ ...values
1367
+ } : _initialValues;
1368
+ for (const key of Object.keys(data)) {
1369
+ data[key] = structuredClone(deepToRaw(resetValues[key]));
1370
+ errors[key] = null;
1371
+ touched[key] = false;
1372
+ dirty[key] = false;
1373
+ }
1374
+ }
1375
+ function replaceState(values) {
1376
+ const nextValues = structuredClone(deepToRaw(values));
1377
+ _initialValues = structuredClone(nextValues);
1378
+ for (const key of Object.keys(data)) {
1379
+ if (key in nextValues) continue;
1380
+ delete data[key];
1381
+ delete errors[key];
1382
+ delete touched[key];
1383
+ delete dirty[key];
1384
+ }
1385
+ for (const key of Object.keys(nextValues)) {
1386
+ data[key] = structuredClone(deepToRaw(nextValues[key]));
1387
+ errors[key] = null;
1388
+ touched[key] = false;
1389
+ dirty[key] = false;
1390
+ }
1391
+ }
1392
+ function handleSubmit(onSubmit) {
1393
+ return async (e) => {
1394
+ e?.preventDefault();
1395
+ for (const field of Object.keys(data)) touched[field] = true;
1396
+ if (!validate()) return;
1397
+ isSubmitting.value = true;
1398
+ try {
1399
+ await onSubmit(data);
1400
+ } finally {
1401
+ isSubmitting.value = false;
1402
+ }
1403
+ };
1404
+ }
1405
+ function getFieldProps(field) {
1406
+ const fieldStr = field;
1407
+ return {
1408
+ modelValue: data[field],
1409
+ "onUpdate:modelValue": (value) => setFieldValue(field, value),
1410
+ onBlur: () => setFieldTouched(fieldStr),
1411
+ error: touched[fieldStr] ? errors[fieldStr] : null
1412
+ };
1413
+ }
1414
+ return {
1415
+ data,
1416
+ errors,
1417
+ touched,
1418
+ dirty,
1419
+ isValid,
1420
+ isDirty,
1421
+ isSubmitting,
1422
+ setFieldValue,
1423
+ setFieldError,
1424
+ setFieldTouched,
1425
+ validateField,
1426
+ validate,
1427
+ reset,
1428
+ replaceState,
1429
+ handleSubmit,
1430
+ getFieldProps
1431
+ };
1432
+ }
1433
+ function deepToRaw(value) {
1434
+ const raw = toRaw(value);
1435
+ if (Array.isArray(raw)) return raw.map((item) => deepToRaw(item));
1436
+ if (isPlainRecord(raw)) return Object.fromEntries(Object.entries(raw).map(([key, item]) => [key, deepToRaw(item)]));
1437
+ return raw;
1438
+ }
1439
+ function isPlainRecord(value) {
1440
+ return Object.prototype.toString.call(value) === "[object Object]";
1441
+ }
1442
+ //#endregion
1443
+ //#region src/composables/formBuilderSchema.ts
1444
+ /**
1445
+ * Evaluate a JSON-serializable field condition against the current form data.
1446
+ *
1447
+ * Supports logical operators (`and`, `or`, `not`) and comparison operators
1448
+ * (`eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `in`, `notIn`, `truthy`, `falsy`,
1449
+ * `contains`). Returns `true` if the condition passes.
1450
+ */
1451
+ function evaluateCondition(condition, data) {
1452
+ if ("and" in condition) return condition.and.every((c) => evaluateCondition(c, data));
1453
+ if ("or" in condition) return condition.or.some((c) => evaluateCondition(c, data));
1454
+ if ("not" in condition) return !evaluateCondition(condition.not, data);
1455
+ const value = data[condition.field];
1456
+ if ("eq" in condition) return value === condition.eq;
1457
+ if ("neq" in condition) return value !== condition.neq;
1458
+ if ("gt" in condition) return typeof value === "number" && value > condition.gt;
1459
+ if ("lt" in condition) return typeof value === "number" && value < condition.lt;
1460
+ if ("gte" in condition) return typeof value === "number" && value >= condition.gte;
1461
+ if ("lte" in condition) return typeof value === "number" && value <= condition.lte;
1462
+ if ("in" in condition) return condition.in.includes(value);
1463
+ if ("notIn" in condition) return !condition.notIn.includes(value);
1464
+ if ("truthy" in condition) return !!value;
1465
+ if ("falsy" in condition) return !value;
1466
+ if ("contains" in condition) {
1467
+ if (typeof value === "string") return value.includes(condition.contains);
1468
+ if (Array.isArray(value)) return value.includes(condition.contains);
1469
+ return false;
1470
+ }
1471
+ return true;
1472
+ }
1473
+ /** Return all sections across steps (wizard) or directly from a flat schema. */
1474
+ function collectSections(schema) {
1475
+ return schema.steps ? schema.steps.flatMap((step) => step.sections) : schema.sections;
1476
+ }
1477
+ /** Return all field schemas in schema order, flattening sections and steps. */
1478
+ function flattenFields(schema) {
1479
+ return collectSections(schema).flatMap((section) => section.fields);
1480
+ }
1481
+ /** Convert a JSON-safe `FieldValidation` descriptor to a runtime `FieldRules` object. */
1482
+ function convertValidation(v) {
1483
+ const rules = {};
1484
+ if (v.required !== void 0) rules.required = v.required;
1485
+ if (v.minLength !== void 0) rules.minLength = v.minLength;
1486
+ if (v.maxLength !== void 0) rules.maxLength = v.maxLength;
1487
+ if (v.min !== void 0) rules.min = v.min;
1488
+ if (v.max !== void 0) rules.max = v.max;
1489
+ if (v.email !== void 0) rules.email = v.email;
1490
+ if (v.pattern !== void 0) rules.pattern = typeof v.pattern === "string" ? new RegExp(v.pattern) : {
1491
+ value: new RegExp(v.pattern.value),
1492
+ message: v.pattern.message
1493
+ };
1494
+ return rules;
1495
+ }
1496
+ function buildInitialValues(fields, initialData) {
1497
+ const initialValues = {};
1498
+ for (const field of fields) {
1499
+ const key = field.name;
1500
+ if (initialData && hasOwnKey(initialData, key)) initialValues[key] = initialData[key];
1501
+ else if (field.defaultValue !== void 0) initialValues[key] = field.defaultValue;
1502
+ else initialValues[key] = getTypeDefault(field.type);
1503
+ }
1504
+ return initialValues;
1505
+ }
1506
+ function buildRules(fields, enhancements) {
1507
+ const rules = {};
1508
+ for (const field of fields) {
1509
+ const base = field.validation ? convertValidation(field.validation) : {};
1510
+ const enhancement = enhancements?.fields?.[field.name];
1511
+ const customValidators = [];
1512
+ if (enhancement?.validate) {
1513
+ const fn = enhancement.validate;
1514
+ customValidators.push((value, formData) => fn(value, formData));
1515
+ }
1516
+ if (customValidators.length > 0) base.custom = customValidators;
1517
+ if (Object.keys(base).length > 0) rules[field.name] = base;
1518
+ }
1519
+ return rules;
1520
+ }
1521
+ function replaceArray(target, values) {
1522
+ target.splice(0, target.length, ...values);
1523
+ }
1524
+ function replaceRecord(target, source) {
1525
+ for (const key of Object.keys(target)) delete target[key];
1526
+ Object.assign(target, source);
1527
+ }
1528
+ function hasOwnKey(source, key) {
1529
+ return Object.prototype.hasOwnProperty.call(source, key);
1530
+ }
1531
+ //#endregion
1532
+ //#region src/composables/useFormBuilder.ts
1533
+ /**
1534
+ * Drive a `FormSchema` as reactive form state.
1535
+ *
1536
+ * Builds initial values from schema defaults and `initialData`, derives
1537
+ * validation rules from `FieldValidation` descriptors and enhancement
1538
+ * validators, evaluates `FieldCondition` expressions for field/section
1539
+ * visibility, and wires wizard step navigation when `schema.steps` is set.
1540
+ *
1541
+ * @param schema - Declarative form or wizard schema.
1542
+ * @param initialData - Values that override schema defaults.
1543
+ * @param enhancements - TypeScript-only callbacks (dynamic options, validators,
1544
+ * submit handler, transform, field-change watcher).
1545
+ */
1546
+ /** Drives a declarative FormSchema as reactive state with conditional fields, wizard steps, and validation. */
1547
+ function useFormBuilder(schema, initialData, enhancements) {
1548
+ const currentSchema = shallowRef(schema);
1549
+ const fields = reactive(flattenFields(schema));
1550
+ const sections = reactive(collectSections(schema));
1551
+ const rules = reactive(buildRules(fields, enhancements));
1552
+ const form = useForm(buildInitialValues(fields, initialData), rules);
1553
+ function isFieldVisible(name) {
1554
+ const enhancement = enhancements?.fields?.[name];
1555
+ if (enhancement?.visible) return enhancement.visible(form.data);
1556
+ const field = fields.find((f) => f.name === name);
1557
+ if (field?.condition) return evaluateCondition(field.condition, form.data);
1558
+ return true;
1559
+ }
1560
+ function isSectionVisible(id) {
1561
+ const section = sections.find((s) => s.id === id);
1562
+ if (section?.condition) return evaluateCondition(section.condition, form.data);
1563
+ return true;
1564
+ }
1565
+ function getResolvedFieldProps(field) {
1566
+ const entry = getFieldRegistryEntry(field.type);
1567
+ const formProps = form.getFieldProps(field.name);
1568
+ const merged = {
1569
+ ...entry.defaults,
1570
+ ...field.props ?? {}
1571
+ };
1572
+ const enhancement = enhancements?.fields?.[field.name];
1573
+ if (enhancement?.props) Object.assign(merged, enhancement.props(form.data));
1574
+ const options = getFieldOptions(field.name);
1575
+ if (options) merged.options = options;
1576
+ if (entry.vModel) {
1577
+ merged.modelValue = formProps.modelValue;
1578
+ merged["onUpdate:modelValue"] = formProps["onUpdate:modelValue"];
1579
+ }
1580
+ merged.onBlur = formProps.onBlur;
1581
+ if (formProps.error) merged.error = true;
1582
+ if (field.placeholder) merged.placeholder = field.placeholder;
1583
+ if (field.size) merged.size = field.size;
1584
+ if (field.disabled) merged.disabled = true;
1585
+ if (field.readonly) merged.readonly = true;
1586
+ if (field.type === "radio" && !merged.name) merged.name = field.name;
1587
+ return merged;
1588
+ }
1589
+ function getFieldOptions(name) {
1590
+ const enhancement = enhancements?.fields?.[name];
1591
+ if (enhancement?.options) return enhancement.options(form.data);
1592
+ const field = fields.find((f) => f.name === name);
1593
+ if (field?.props?.options) return field.props.options;
1594
+ }
1595
+ const currentStep = ref(0);
1596
+ const isCurrentStepValid = computed(() => {
1597
+ if (!currentSchema.value.steps) return form.isValid.value;
1598
+ const step = currentSchema.value.steps[currentStep.value];
1599
+ if (!step) return true;
1600
+ for (const section of step.sections) {
1601
+ if (!isSectionVisible(section.id)) continue;
1602
+ for (const field of section.fields) {
1603
+ if (!isFieldVisible(field.name)) continue;
1604
+ if (!form.validateField(field.name)) return false;
1605
+ }
1606
+ }
1607
+ return true;
1608
+ });
1609
+ function goNext() {
1610
+ if (!currentSchema.value.steps) return false;
1611
+ const step = currentSchema.value.steps[currentStep.value];
1612
+ if (!step) return false;
1613
+ let valid = true;
1614
+ for (const section of step.sections) {
1615
+ if (!isSectionVisible(section.id)) continue;
1616
+ for (const field of section.fields) {
1617
+ if (!isFieldVisible(field.name)) continue;
1618
+ form.setFieldTouched(field.name, true);
1619
+ if (!form.validateField(field.name)) valid = false;
1620
+ }
1621
+ }
1622
+ if (!valid) return false;
1623
+ if (currentStep.value < currentSchema.value.steps.length - 1) currentStep.value++;
1624
+ return true;
1625
+ }
1626
+ function goBack() {
1627
+ if (currentStep.value > 0) currentStep.value--;
1628
+ }
1629
+ function goToStep(index) {
1630
+ if (!currentSchema.value.steps) return;
1631
+ if (index >= 0 && index < currentSchema.value.steps.length) currentStep.value = index;
1632
+ }
1633
+ function validate() {
1634
+ let allValid = true;
1635
+ for (const field of fields) {
1636
+ if (!isFieldVisible(field.name)) continue;
1637
+ form.setFieldTouched(field.name, true);
1638
+ if (!form.validateField(field.name)) allValid = false;
1639
+ }
1640
+ return allValid;
1641
+ }
1642
+ async function submit() {
1643
+ if (!validate()) return;
1644
+ let submitData = {};
1645
+ for (const field of fields) if (isFieldVisible(field.name)) submitData[field.name] = form.data[field.name];
1646
+ if (enhancements?.transform) submitData = enhancements.transform(submitData);
1647
+ if (enhancements?.onSubmit) {
1648
+ form.isSubmitting.value = true;
1649
+ try {
1650
+ await enhancements.onSubmit(submitData);
1651
+ } finally {
1652
+ form.isSubmitting.value = false;
1653
+ }
1654
+ }
1655
+ }
1656
+ function reset(values) {
1657
+ form.reset(values);
1658
+ currentStep.value = 0;
1659
+ }
1660
+ function updateSchema(nextSchema, nextData) {
1661
+ currentSchema.value = nextSchema;
1662
+ const nextFields = flattenFields(nextSchema);
1663
+ replaceArray(fields, nextFields);
1664
+ replaceArray(sections, collectSections(nextSchema));
1665
+ replaceRecord(rules, buildRules(nextFields, enhancements));
1666
+ form.replaceState(buildInitialValues(nextFields, nextData));
1667
+ currentStep.value = 0;
1668
+ }
1669
+ if (enhancements?.onFieldChange) {
1670
+ const callback = enhancements.onFieldChange;
1671
+ watch(() => ({ ...form.data }), (newData, oldData) => {
1672
+ if (!oldData) return;
1673
+ for (const key of Object.keys(newData)) if (newData[key] !== oldData[key]) callback(key, newData[key], newData);
1674
+ }, { deep: true });
1675
+ }
1676
+ return {
1677
+ form,
1678
+ rules,
1679
+ isFieldVisible,
1680
+ isSectionVisible,
1681
+ fields,
1682
+ getResolvedFieldProps,
1683
+ getFieldOptions,
1684
+ currentStep,
1685
+ isCurrentStepValid,
1686
+ goNext,
1687
+ goBack,
1688
+ goToStep,
1689
+ validate,
1690
+ reset,
1691
+ submit,
1692
+ updateSchema
1693
+ };
1694
+ }
1695
+ //#endregion
1696
+ //#region src/composables/useMobileSupportGate.ts
1697
+ var DEFAULT_MOBILE_VIEWPORT_QUERY = "(max-width: 767px)";
1698
+ function useMobileSupportGate(options = {}) {
1699
+ const isMobileViewport = ref(false);
1700
+ const mediaQuery = computed(() => {
1701
+ return unref(options.mediaQuery)?.trim() || "(max-width: 767px)";
1702
+ });
1703
+ const isSupported = computed(() => {
1704
+ if (options.supported === void 0) return true;
1705
+ return Boolean(unref(options.supported));
1706
+ });
1707
+ const shouldShowUnsupported = computed(() => isMobileViewport.value && !isSupported.value);
1708
+ let mediaQueryList = null;
1709
+ let removeMediaListener;
1710
+ const syncMobileState = (event) => {
1711
+ isMobileViewport.value = Boolean(event?.matches ?? mediaQueryList?.matches);
1712
+ };
1713
+ const unbindMediaQuery = () => {
1714
+ removeMediaListener?.();
1715
+ removeMediaListener = void 0;
1716
+ mediaQueryList = null;
1717
+ };
1718
+ const bindMediaQuery = () => {
1719
+ unbindMediaQuery();
1720
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
1721
+ isMobileViewport.value = false;
1722
+ return;
1723
+ }
1724
+ const queryList = window.matchMedia(mediaQuery.value);
1725
+ mediaQueryList = queryList;
1726
+ syncMobileState(queryList);
1727
+ const handleChange = (event) => syncMobileState(event);
1728
+ if (typeof queryList.addEventListener === "function") {
1729
+ queryList.addEventListener("change", handleChange);
1730
+ removeMediaListener = () => queryList.removeEventListener("change", handleChange);
1731
+ return;
1732
+ }
1733
+ queryList.addListener(handleChange);
1734
+ removeMediaListener = () => queryList.removeListener(handleChange);
1735
+ };
1736
+ onMounted(bindMediaQuery);
1737
+ watch(mediaQuery, bindMediaQuery, { flush: "post" });
1738
+ onUnmounted(unbindMediaQuery);
1739
+ return {
1740
+ isMobileViewport,
1741
+ isSupported,
1742
+ shouldShowUnsupported
1743
+ };
1744
+ }
1745
+ //#endregion
1746
+ //#region src/components/PluginWorkspaceView.controls.ts
1747
+ function usePluginWorkspaceControls(options) {
1748
+ const resolvedModel = computed(() => resolveControlModel(options.model()));
1749
+ const resolvedControls = computed(() => options.controls() ?? resolvedModel.value?.controls);
1750
+ const resolvedControlOptions = computed(() => mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, options.controlOptions()));
1751
+ const externalControlValues = computed(() => options.modelValue() ?? options.values());
1752
+ const internalControlValues = reactive({});
1753
+ let syncingControlValues = false;
1754
+ const generatedControlDefaults = computed(() => {
1755
+ if (!resolvedControls.value) return {};
1756
+ return {
1757
+ ...getControlDefaults(resolvedControls.value),
1758
+ ...resolvedControlOptions.value.initialValues ?? {}
1759
+ };
1760
+ });
1761
+ function replaceControlValues(values) {
1762
+ for (const key of Object.keys(internalControlValues)) if (!(key in values)) delete internalControlValues[key];
1763
+ Object.assign(internalControlValues, values);
1764
+ }
1765
+ function emitControlValues() {
1766
+ options.emitValues({ ...internalControlValues });
1767
+ }
1768
+ function syncResolvedControlValues() {
1769
+ syncingControlValues = true;
1770
+ replaceControlValues({
1771
+ ...generatedControlDefaults.value,
1772
+ ...externalControlValues.value === void 0 ? internalControlValues : externalControlValues.value
1773
+ });
1774
+ syncingControlValues = false;
1775
+ }
1776
+ function handleControlValuesUpdate(values) {
1777
+ syncingControlValues = true;
1778
+ replaceControlValues({
1779
+ ...generatedControlDefaults.value,
1780
+ ...values
1781
+ });
1782
+ syncingControlValues = false;
1783
+ emitControlValues();
1784
+ }
1785
+ const resolvedComponentBindings = computed(() => controlValuesToComponentBindings(internalControlValues, options.componentBindings() ?? resolvedModel.value?.componentBindings));
1786
+ const resolvedComponentBindingsById = computed(() => controlValuesToComponentBindingsById(internalControlValues, options.componentBindings() ?? resolvedModel.value?.componentBindings));
1787
+ const resolvedComponentProps = computed(() => {
1788
+ const mapping = options.componentProps() ?? resolvedModel.value?.componentProps;
1789
+ return mapping === void 0 ? {} : controlValuesToComponentProps(internalControlValues, mapping);
1790
+ });
1791
+ const resolvedComponentPropsById = computed(() => {
1792
+ const mappings = options.componentPropsById() ?? resolvedModel.value?.componentPropsById;
1793
+ if (mappings === void 0) return {};
1794
+ return Object.fromEntries(Object.entries(mappings).map(([id, mapping]) => [id, controlValuesToComponentProps(internalControlValues, mapping)]));
1795
+ });
1796
+ const generatedPillNav = computed(() => {
1797
+ if (!resolvedControls.value) return [];
1798
+ return controlsToViewItems(resolvedControls.value, resolvedControlOptions.value);
1799
+ });
1800
+ const generatedDefaultView = computed(() => {
1801
+ if (!resolvedControls.value) return "";
1802
+ return getDefaultControlView(resolvedControls.value, resolvedControlOptions.value);
1803
+ });
1804
+ syncResolvedControlValues();
1805
+ watch([generatedControlDefaults, externalControlValues], syncResolvedControlValues, { deep: true });
1806
+ watch(internalControlValues, () => {
1807
+ if (syncingControlValues) return;
1808
+ emitControlValues();
1809
+ }, {
1810
+ deep: true,
1811
+ flush: "sync"
1812
+ });
1813
+ return {
1814
+ resolvedModel,
1815
+ resolvedControls,
1816
+ resolvedControlOptions,
1817
+ generatedPillNav,
1818
+ generatedDefaultView,
1819
+ internalControlValues,
1820
+ resolvedComponentBindings,
1821
+ resolvedComponentBindingsById,
1822
+ resolvedComponentProps,
1823
+ resolvedComponentPropsById,
1824
+ handleControlValuesUpdate
1825
+ };
1826
+ }
729
1827
  //#endregion
730
1828
  //#region src/components/PluginWorkspaceView.navigation.ts
731
1829
  function usePluginWorkspaceNavigation(options) {
@@ -773,7 +1871,7 @@ function firstItemId(items) {
773
1871
  }
774
1872
  //#endregion
775
1873
  //#region src/components/PluginWorkspaceView.shell.ts
776
- var topBarForwardedSlots = new Set([
1874
+ var topBarForwardedSlots = /* @__PURE__ */ new Set([
777
1875
  "icon",
778
1876
  "logo",
779
1877
  "page-selector-icon",
@@ -785,7 +1883,7 @@ var topBarForwardedSlots = new Set([
785
1883
  "account-menu-item-icon",
786
1884
  "settings-appearance"
787
1885
  ]);
788
- var sidebarForwardedSlots = new Set([
1886
+ var sidebarForwardedSlots = /* @__PURE__ */ new Set([
789
1887
  "header",
790
1888
  "collapsed",
791
1889
  "collapsed-footer",
@@ -835,116 +1933,818 @@ function usePluginWorkspace(options) {
835
1933
  emit("update:values", values);
836
1934
  }
837
1935
  });
838
- const navigationState = usePluginWorkspaceNavigation({
839
- activeView: () => props.activeView,
840
- defaultActiveView: () => props.defaultActiveView,
841
- currentPillId: () => props.currentPillId,
842
- currentPageSelectorId: () => props.currentPageSelectorId,
843
- pillNav: () => props.pillNav,
844
- pageSelector: () => props.pageSelector,
845
- panelIds: () => Object.keys(props.panels ?? {}),
846
- generatedPillNav: controlsState.generatedPillNav,
847
- generatedDefaultView: controlsState.generatedDefaultView,
848
- emitActiveView: (viewId) => emit("update:activeView", viewId),
849
- emitPageSelectorSelect: (page) => emit("page-selector-select", page),
850
- emitPillSelect: (item) => emit("pill-select", item)
1936
+ const navigationState = usePluginWorkspaceNavigation({
1937
+ activeView: () => props.activeView,
1938
+ defaultActiveView: () => props.defaultActiveView,
1939
+ currentPillId: () => props.currentPillId,
1940
+ currentPageSelectorId: () => props.currentPageSelectorId,
1941
+ pillNav: () => props.pillNav,
1942
+ pageSelector: () => props.pageSelector,
1943
+ panelIds: () => Object.keys(props.panels ?? {}),
1944
+ generatedPillNav: controlsState.generatedPillNav,
1945
+ generatedDefaultView: controlsState.generatedDefaultView,
1946
+ emitActiveView: (viewId) => emit("update:activeView", viewId),
1947
+ emitPageSelectorSelect: (page) => emit("page-selector-select", page),
1948
+ emitPillSelect: (item) => emit("pill-select", item)
1949
+ });
1950
+ const topBarSlotNames = computed(() => getPluginWorkspaceTopBarSlotNames(slots));
1951
+ const sidebarSlotNames = computed(() => getPluginWorkspaceSidebarSlotNames(slots));
1952
+ const hasSidebarSurface = computed(() => hasPluginWorkspaceSidebarSurface({
1953
+ showSidebar: props.showSidebar ?? true,
1954
+ panels: props.panels ?? {},
1955
+ forms: props.forms ?? {},
1956
+ model: props.model,
1957
+ controls: props.controls,
1958
+ showSidebarWhenEmpty: props.showSidebarWhenEmpty ?? false,
1959
+ sidebarContentId: props.sidebarContentId,
1960
+ sidebarTitle: props.sidebarTitle,
1961
+ sidebarSubtitle: props.sidebarSubtitle,
1962
+ sidebarBadge: props.sidebarBadge,
1963
+ hasSidebarSlot: Boolean(slots.sidebar),
1964
+ sidebarSlotCount: sidebarSlotNames.value.length
1965
+ }));
1966
+ const controls = computed(() => ({
1967
+ activeView: navigationState.resolvedActiveView.value,
1968
+ setActiveView: navigationState.setActiveView,
1969
+ values: controlsState.internalControlValues,
1970
+ componentBindings: controlsState.resolvedComponentBindings.value,
1971
+ componentBindingsById: controlsState.resolvedComponentBindingsById.value,
1972
+ componentProps: controlsState.resolvedComponentProps.value,
1973
+ componentPropsById: controlsState.resolvedComponentPropsById.value
1974
+ }));
1975
+ return {
1976
+ shell: computed(() => ({
1977
+ sidebarPosition: props.sidebarPosition ?? "left",
1978
+ sidebarWidth: props.sidebarWidth,
1979
+ floating: props.floating ?? true,
1980
+ responsiveSidebar: props.responsiveSidebar ?? true,
1981
+ sidebarToggleLabel: props.sidebarToggleLabel ?? "Open workspace sidebar",
1982
+ sidebarCloseLabel: props.sidebarCloseLabel ?? "Close workspace sidebar",
1983
+ hasSidebarSurface: hasSidebarSurface.value,
1984
+ title: props.title ?? "Workspace",
1985
+ subtitle: props.subtitle,
1986
+ topBarVariant: props.topBarVariant ?? "card",
1987
+ homePath: props.homePath ?? "/",
1988
+ showLogo: props.showLogo ?? true,
1989
+ pageSelector: props.pageSelector,
1990
+ currentPageSelectorId: navigationState.resolvedCurrentPageSelectorId.value,
1991
+ pillNav: navigationState.resolvedPillNav.value,
1992
+ currentPillId: navigationState.resolvedCurrentPillId.value,
1993
+ showThemeToggle: props.showThemeToggle ?? true,
1994
+ showSettings: props.showSettings ?? true,
1995
+ settingsConfig: props.settingsConfig,
1996
+ showStandaloneLabel: props.showStandaloneLabel ?? false,
1997
+ standaloneLabel: props.standaloneLabel ?? "Standalone",
1998
+ accountMenu: props.accountMenu,
1999
+ showNotifications: props.showNotifications ?? false,
2000
+ hasNotificationDot: props.hasNotificationDot ?? false,
2001
+ showAdmin: props.showAdmin ?? false,
2002
+ adminPath: props.adminPath ?? "/admin",
2003
+ userName: props.userName,
2004
+ userInitial: props.userInitial,
2005
+ userEmail: props.userEmail,
2006
+ topBarSlotNames: topBarSlotNames.value,
2007
+ controls: controls.value
2008
+ })),
2009
+ sidebar: computed(() => ({
2010
+ title: props.sidebarTitle,
2011
+ subtitle: props.sidebarSubtitle,
2012
+ badge: props.sidebarBadge,
2013
+ panels: props.panels ?? {},
2014
+ activeView: navigationState.resolvedActiveView.value,
2015
+ floating: false,
2016
+ dense: props.dense ?? true,
2017
+ width: props.sidebarWidth,
2018
+ side: props.sidebarPosition ?? "left",
2019
+ forms: props.forms ?? {},
2020
+ contentId: props.sidebarContentId,
2021
+ showWhenEmpty: (props.showSidebarWhenEmpty ?? false) || props.sidebarContentId !== void 0,
2022
+ model: props.model,
2023
+ controlSchema: props.controls,
2024
+ controlOptions: props.controlOptions ?? {},
2025
+ values: controlsState.internalControlValues,
2026
+ formEnhancements: props.formEnhancements,
2027
+ showFormActions: props.showFormActions ?? false,
2028
+ formLoading: props.formLoading ?? false,
2029
+ formDisabled: props.formDisabled ?? false,
2030
+ formReadonly: props.formReadonly ?? false,
2031
+ formSize: props.formSize ?? "sm",
2032
+ collapsible: props.sidebarCollapsible ?? true,
2033
+ collapsed: props.sidebarCollapsed,
2034
+ defaultCollapsed: props.defaultSidebarCollapsed ?? false,
2035
+ collapsedWidth: props.sidebarCollapsedWidth ?? "3rem",
2036
+ collapseButtonLabel: props.sidebarCollapseButtonLabel ?? "Collapse sidebar",
2037
+ expandButtonLabel: props.sidebarExpandButtonLabel ?? "Expand sidebar",
2038
+ slotNames: sidebarSlotNames.value
2039
+ })),
2040
+ controls,
2041
+ hasSidebarSurface,
2042
+ handleControlValuesUpdate: controlsState.handleControlValuesUpdate,
2043
+ handlePageSelectorSelect: navigationState.handlePageSelectorSelect,
2044
+ handlePillSelect: navigationState.handlePillSelect
2045
+ };
2046
+ }
2047
+ //#endregion
2048
+ //#region src/composables/useRequestSyncState.ts
2049
+ /** Shared loading/error/timestamp state for generated plugin request helpers. */
2050
+ function useRequestSyncState(defaultErrorMessage = "Request failed.") {
2051
+ const loading = ref(false);
2052
+ const error = ref(null);
2053
+ const lastLoadedAt = ref(null);
2054
+ const lastSavedAt = ref(null);
2055
+ const lastRunAt = ref(null);
2056
+ function clearError() {
2057
+ error.value = null;
2058
+ }
2059
+ function readErrorMessage$1(value, fallback = defaultErrorMessage) {
2060
+ return readErrorMessage(value, fallback);
2061
+ }
2062
+ function setError(value, fallback) {
2063
+ const message = readErrorMessage$1(value, fallback);
2064
+ error.value = message;
2065
+ return message;
2066
+ }
2067
+ function markLoaded(date = /* @__PURE__ */ new Date()) {
2068
+ lastLoadedAt.value = date;
2069
+ }
2070
+ function markSaved(date = /* @__PURE__ */ new Date()) {
2071
+ lastSavedAt.value = date;
2072
+ }
2073
+ function markRun(date = /* @__PURE__ */ new Date()) {
2074
+ lastRunAt.value = date;
2075
+ }
2076
+ let runToken = 0;
2077
+ let activeController = null;
2078
+ function cancel() {
2079
+ runToken += 1;
2080
+ activeController?.abort();
2081
+ activeController = null;
2082
+ loading.value = false;
2083
+ }
2084
+ async function run(operation, options = {}) {
2085
+ if (options.abortPrevious !== false) activeController?.abort();
2086
+ const token = runToken += 1;
2087
+ const controller = new AbortController();
2088
+ activeController = controller;
2089
+ const callerSignal = options.signal;
2090
+ if (callerSignal) {
2091
+ if (callerSignal.aborted) controller.abort(callerSignal.reason);
2092
+ else callerSignal.addEventListener("abort", () => controller.abort(callerSignal.reason), { once: true });
2093
+ }
2094
+ const isCurrent = () => token === runToken;
2095
+ loading.value = true;
2096
+ clearError();
2097
+ try {
2098
+ const response = await operation(controller.signal);
2099
+ if (isCurrent()) {
2100
+ if (options.success === "load") markLoaded();
2101
+ else if (options.success === "save") markSaved();
2102
+ else if (options.success === "run") markRun();
2103
+ }
2104
+ return response;
2105
+ } catch (err) {
2106
+ if (isCurrent()) setError(err, options.errorMessage);
2107
+ throw err;
2108
+ } finally {
2109
+ if (isCurrent()) loading.value = false;
2110
+ if (activeController === controller) activeController = null;
2111
+ }
2112
+ }
2113
+ return {
2114
+ loading,
2115
+ error,
2116
+ lastLoadedAt,
2117
+ lastSavedAt,
2118
+ lastRunAt,
2119
+ clearError,
2120
+ readErrorMessage: readErrorMessage$1,
2121
+ setError,
2122
+ markLoaded,
2123
+ markSaved,
2124
+ markRun,
2125
+ run,
2126
+ cancel
2127
+ };
2128
+ }
2129
+ //#endregion
2130
+ //#region src/composables/pluginEndpointBuilder.ts
2131
+ function normalizeApiBaseUrl(baseUrl) {
2132
+ if (!baseUrl) return void 0;
2133
+ return baseUrl.length > 1 ? baseUrl.replace(/\/+$/, "") : baseUrl;
2134
+ }
2135
+ function normalizeApiPrefix(prefix) {
2136
+ if (!prefix) return void 0;
2137
+ if (prefix.startsWith("/api/")) return normalizeApiBaseUrl(prefix);
2138
+ if (prefix === "/api") return prefix;
2139
+ if (prefix.startsWith("/")) return normalizeApiBaseUrl(`/api${prefix}`);
2140
+ return normalizeApiBaseUrl(`/api/${prefix}`);
2141
+ }
2142
+ function endpointParamName(param) {
2143
+ return typeof param === "string" ? param : param.name;
2144
+ }
2145
+ function endpointParamFieldName(param) {
2146
+ return typeof param === "string" ? param : param.fieldName ?? param.name;
2147
+ }
2148
+ function escapeRegExp(value) {
2149
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2150
+ }
2151
+ function replacePathParam(path, name, encoded) {
2152
+ const escapedName = escapeRegExp(name);
2153
+ return path.replace(new RegExp(`\\{${escapedName}(?::[^{}]+)?\\}`, "g"), () => encoded).replace(new RegExp(`:${escapedName}(?=/|$)`, "g"), () => encoded);
2154
+ }
2155
+ /** Resolve the runtime plugin API base URL from env, explicit options, platform injection, or contract metadata. */
2156
+ function resolvePluginBaseUrl(contract, explicitBaseUrl) {
2157
+ if (explicitBaseUrl) return normalizeApiBaseUrl(explicitBaseUrl) ?? explicitBaseUrl;
2158
+ const injected = getInjectedPlatformContext();
2159
+ const platformPrefix = normalizeApiBaseUrl(injected?.plugin?.api_prefix) || normalizeApiPrefix(injected?.plugin?.route_prefix);
2160
+ if (platformPrefix) return platformPrefix;
2161
+ return normalizeApiBaseUrl(contract.plugin.apiPrefix) || "/api";
2162
+ }
2163
+ function encodePath(path, payload, pathParams) {
2164
+ let nextPath = path;
2165
+ for (const param of pathParams) {
2166
+ const name = endpointParamName(param);
2167
+ const fieldName = endpointParamFieldName(param);
2168
+ const value = payload[fieldName] ?? payload[name];
2169
+ if (value === void 0 || value === null) throw new Error(`[MINT SDK] Missing path parameter '${fieldName}' for plugin endpoint ${path}`);
2170
+ const encoded = encodeURIComponent(String(value));
2171
+ nextPath = replacePathParam(nextPath, name, encoded);
2172
+ if (fieldName !== name) nextPath = replacePathParam(nextPath, fieldName, encoded);
2173
+ }
2174
+ return nextPath;
2175
+ }
2176
+ function withDefaultPathParams(payload, pathParams) {
2177
+ if (!pathParams.length) return payload;
2178
+ const nextPayload = { ...payload };
2179
+ for (const param of pathParams) {
2180
+ const name = endpointParamName(param);
2181
+ const fieldName = endpointParamFieldName(param);
2182
+ if (nextPayload[fieldName] !== void 0 && nextPayload[fieldName] !== null || nextPayload[name] !== void 0 && nextPayload[name] !== null) continue;
2183
+ if (isExperimentPathParam(fieldName) || isExperimentPathParam(name)) {
2184
+ const experimentId = resolveCurrentExperimentId();
2185
+ if (experimentId !== void 0) nextPayload[fieldName] = experimentId;
2186
+ }
2187
+ }
2188
+ return nextPayload;
2189
+ }
2190
+ function isExperimentPathParam(param) {
2191
+ return param === "experimentId" || param === "experiment_id";
2192
+ }
2193
+ function withoutPathParams(payload, pathParams) {
2194
+ const copy = { ...payload };
2195
+ for (const param of pathParams) {
2196
+ delete copy[endpointParamFieldName(param)];
2197
+ delete copy[endpointParamName(param)];
2198
+ }
2199
+ return copy;
2200
+ }
2201
+ function queryParamsFromPayload(payload, pathParams, queryParams, endpointPath, includeUnspecifiedParams = false) {
2202
+ if (!queryParams?.length) return includeUnspecifiedParams ? withoutPathParams(payload, pathParams) : {};
2203
+ const query = {};
2204
+ for (const param of queryParams) {
2205
+ const name = typeof param === "string" ? param : param.name;
2206
+ const fieldName = typeof param === "string" ? param : param.fieldName ?? param.name;
2207
+ const required = typeof param !== "string" && param.required === true;
2208
+ const value = payload[fieldName];
2209
+ if (value === void 0 || value === null) {
2210
+ if (required) throw new Error(`[MINT SDK] Missing query parameter '${fieldName}' for plugin endpoint ${endpointPath ?? ""}`);
2211
+ continue;
2212
+ }
2213
+ query[name] = value;
2214
+ }
2215
+ return query;
2216
+ }
2217
+ function hasKeys(value) {
2218
+ return Object.keys(value).length > 0;
2219
+ }
2220
+ function isPlainPayloadObject(value) {
2221
+ return !!value && typeof value === "object" && !Array.isArray(value);
2222
+ }
2223
+ function hasOwn(value, key) {
2224
+ return Object.prototype.hasOwnProperty.call(value, key);
2225
+ }
2226
+ function normalizeStructuredEndpointPayload(payload) {
2227
+ const pathParamPayload = payload.pathParams;
2228
+ const queryPayload = payload.query;
2229
+ const hasStructuredPathParams = hasOwn(payload, "pathParams") && isPlainPayloadObject(pathParamPayload);
2230
+ const hasStructuredQuery = hasOwn(payload, "query") && isPlainPayloadObject(queryPayload);
2231
+ if (!hasStructuredPathParams && !hasStructuredQuery) return payload;
2232
+ const nextPayload = { ...payload };
2233
+ delete nextPayload.pathParams;
2234
+ delete nextPayload.query;
2235
+ if (hasStructuredPathParams) Object.assign(nextPayload, pathParamPayload);
2236
+ if (hasStructuredQuery) Object.assign(nextPayload, queryPayload);
2237
+ return nextPayload;
2238
+ }
2239
+ function requestBodyFromPayload(payload, requestPayload, endpoint) {
2240
+ if (!endpoint.hasBody) return void 0;
2241
+ const body = endpoint.pathParams?.length || endpoint.queryParams?.length ? requestPayload.body : payload;
2242
+ if (body === void 0 || body === null) throw new Error(`[MINT SDK] Missing request body for plugin endpoint ${endpoint.path}`);
2243
+ return body;
2244
+ }
2245
+ function pluginEndpointRequestParts(contract, endpoint, payload, explicitBaseUrl) {
2246
+ const payloadObject = isPlainPayloadObject(payload) ? normalizeStructuredEndpointPayload(payload) : {};
2247
+ const pathParams = endpoint.pathParams ?? [];
2248
+ const requestPayload = withDefaultPathParams(payloadObject, pathParams);
2249
+ const path = encodePath(endpoint.path, requestPayload, pathParams);
2250
+ const query = queryParamsFromPayload(requestPayload, pathParams, endpoint.queryParams, endpoint.path, endpoint.method === "get");
2251
+ return {
2252
+ baseUrl: resolvePluginBaseUrl(contract, explicitBaseUrl),
2253
+ path,
2254
+ query,
2255
+ requestPayload
2256
+ };
2257
+ }
2258
+ function appendQueryValue(params, key, value) {
2259
+ if (value === void 0 || value === null) return;
2260
+ if (Array.isArray(value)) {
2261
+ for (const item of value) appendQueryValue(params, key, item);
2262
+ return;
2263
+ }
2264
+ if (typeof value === "object") {
2265
+ params.append(key, JSON.stringify(value));
2266
+ return;
2267
+ }
2268
+ params.append(key, String(value));
2269
+ }
2270
+ function queryString(query) {
2271
+ const params = new URLSearchParams();
2272
+ for (const [key, value] of Object.entries(query)) appendQueryValue(params, key, value);
2273
+ const serialized = params.toString();
2274
+ return serialized ? `?${serialized}` : "";
2275
+ }
2276
+ function joinBaseUrl(baseUrl, path) {
2277
+ if (!baseUrl) return path;
2278
+ return `${baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
2279
+ }
2280
+ /** Build the concrete URL for a generated plugin endpoint without making a request. */
2281
+ function buildPluginEndpointUrl(contract, endpoint, payload, options = {}) {
2282
+ const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl);
2283
+ const pathWithQuery = `${parts.path}${queryString(parts.query)}`;
2284
+ if (options.includeBaseUrl === false) return pathWithQuery;
2285
+ return joinBaseUrl(parts.baseUrl, pathWithQuery);
2286
+ }
2287
+ //#endregion
2288
+ //#region src/composables/usePluginClient.ts
2289
+ function queryConfig(query) {
2290
+ return hasKeys(query) ? { params: query } : void 0;
2291
+ }
2292
+ var OCTET_STREAM = "application/octet-stream";
2293
+ var MULTIPART_FORM_DATA = "multipart/form-data";
2294
+ function endpointRequestConfig(endpoint, query, options) {
2295
+ const config = {};
2296
+ if (hasKeys(query)) config.params = query;
2297
+ if (endpoint.requestContentType === OCTET_STREAM) config.headers = { "Content-Type": OCTET_STREAM };
2298
+ if (endpoint.requestContentType === MULTIPART_FORM_DATA) config.headers = { "Content-Type": void 0 };
2299
+ if (endpoint.responseContentType === OCTET_STREAM) config.responseType = "blob";
2300
+ if (options?.signal) config.signal = options.signal;
2301
+ return hasKeys(config) ? config : void 0;
2302
+ }
2303
+ function isFormData(value) {
2304
+ return typeof FormData !== "undefined" && value instanceof FormData;
2305
+ }
2306
+ function endpointRequestBody(endpoint, body) {
2307
+ return endpoint.requestContentType === MULTIPART_FORM_DATA ? pluginFormDataFromPayload(body) : body;
2308
+ }
2309
+ function endpointHasPayload(endpoint) {
2310
+ return Boolean(endpoint.hasBody || endpoint.pathParams?.length || endpoint.queryParams?.length);
2311
+ }
2312
+ function isBlob(value) {
2313
+ return typeof Blob !== "undefined" && value instanceof Blob;
2314
+ }
2315
+ function appendFormDataValue(formData, key, value) {
2316
+ if (value === void 0 || value === null) return;
2317
+ if (Array.isArray(value)) {
2318
+ for (const item of value) appendFormDataValue(formData, key, item);
2319
+ return;
2320
+ }
2321
+ if (isBlob(value)) {
2322
+ formData.append(key, value);
2323
+ return;
2324
+ }
2325
+ if (typeof value === "object") {
2326
+ formData.append(key, JSON.stringify(value));
2327
+ return;
2328
+ }
2329
+ formData.append(key, String(value));
2330
+ }
2331
+ /** Convert plain upload payloads into FormData using the same encoding across generated plugin clients. */
2332
+ function pluginFormDataFromPayload(payload) {
2333
+ if (isFormData(payload)) return payload;
2334
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("[MINT SDK] Plugin upload endpoints require a FormData or plain object payload.");
2335
+ const formData = new FormData();
2336
+ for (const [key, value] of Object.entries(payload)) appendFormDataValue(formData, key, value);
2337
+ return formData;
2338
+ }
2339
+ /** Trigger a browser download for an already-created Blob. */
2340
+ function downloadBlob(blob, filename, options = {}) {
2341
+ if (typeof document === "undefined" || typeof URL === "undefined") throw new Error("[MINT SDK] Cannot trigger a file download outside a browser.");
2342
+ const blobUrl = URL.createObjectURL(blob);
2343
+ const link = document.createElement("a");
2344
+ link.href = blobUrl;
2345
+ link.download = filename;
2346
+ document.body.appendChild(link);
2347
+ link.click();
2348
+ document.body.removeChild(link);
2349
+ setTimeout(() => URL.revokeObjectURL(blobUrl), options.revokeObjectUrlDelayMs ?? 100);
2350
+ }
2351
+ function normalizePluginNavPath(path) {
2352
+ const raw = path.trim() || "/";
2353
+ return (raw.startsWith("/") ? raw : `/${raw}`).replace(/\/+$/, "") || "/";
2354
+ }
2355
+ function pluginPageIdFromPath(path, fallbackIndex) {
2356
+ const normalizedPath = normalizePluginNavPath(path);
2357
+ if (normalizedPath === "/") return "dashboard";
2358
+ return normalizedPath.replace(/^\/+/, "").replace(/\/+/g, "-") || `page-${fallbackIndex + 1}`;
2359
+ }
2360
+ /** Convert resolved @mint_plugin nav_items into page selectors for the topbar and homepage PluginCards. */
2361
+ function getPluginPageSelectorItems(contract) {
2362
+ const pluginIcon = contract.plugin.icon ?? "";
2363
+ return (contract.plugin.navItems ?? []).map((item, index) => ({
2364
+ id: item.id || pluginPageIdFromPath(item.path, index),
2365
+ label: item.label,
2366
+ to: normalizePluginNavPath(item.path),
2367
+ icon: item.icon || pluginIcon || void 0
2368
+ }));
2369
+ }
2370
+ /** Create a typed plugin API client from a generated MINT plugin contract. */
2371
+ function createPluginClient(contract, options) {
2372
+ const client = {};
2373
+ for (const [name, endpoint] of Object.entries(options.endpoints)) client[name] = async (payloadOrOptions, explicitOptions) => {
2374
+ const hasPayload = endpointHasPayload(endpoint);
2375
+ const payload = hasPayload ? payloadOrOptions : void 0;
2376
+ const callOptions = hasPayload ? explicitOptions : payloadOrOptions ?? explicitOptions;
2377
+ const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl);
2378
+ const api = useApi({
2379
+ baseUrl: parts.baseUrl,
2380
+ withAuth: endpoint.requiresAuth !== false,
2381
+ typedErrors: true
2382
+ });
2383
+ const config = endpointRequestConfig(endpoint, parts.query, callOptions);
2384
+ if (endpoint.method === "get") return api.get(parts.path, config);
2385
+ if (endpoint.method === "delete") {
2386
+ const rawBody = requestBodyFromPayload(payload, parts.requestPayload, endpoint);
2387
+ const body = rawBody === void 0 ? void 0 : endpointRequestBody(endpoint, rawBody);
2388
+ const config = body === void 0 ? endpointRequestConfig(endpoint, parts.query, callOptions) : {
2389
+ ...endpointRequestConfig(endpoint, parts.query, callOptions),
2390
+ data: body
2391
+ };
2392
+ return api.delete(parts.path, config);
2393
+ }
2394
+ const body = endpointRequestBody(endpoint, requestBodyFromPayload(payload, parts.requestPayload, endpoint));
2395
+ if (endpoint.method === "post") return api.post(parts.path, body, config);
2396
+ if (endpoint.method === "put") return api.put(parts.path, body, config);
2397
+ if (endpoint.method === "patch") return api.patch(parts.path, body, config);
2398
+ throw new Error(`[MINT SDK] Unsupported plugin endpoint method: ${endpoint.method}`);
2399
+ };
2400
+ return client;
2401
+ }
2402
+ /** Send multipart or raw binary data to a generated endpoint with contract URL, cancellation, and auth handling. */
2403
+ async function uploadPluginEndpoint(contract, endpoint, payload, options = {}) {
2404
+ if (![
2405
+ "post",
2406
+ "put",
2407
+ "patch"
2408
+ ].includes(endpoint.method)) throw new Error(`[MINT SDK] Plugin upload endpoints must use POST, PUT, or PATCH; got ${endpoint.method}`);
2409
+ const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl);
2410
+ const api = useApi({
2411
+ baseUrl: parts.baseUrl,
2412
+ withAuth: endpoint.requiresAuth !== false,
2413
+ typedErrors: true
2414
+ });
2415
+ const body = endpoint.hasBody ? requestBodyFromPayload(payload, parts.requestPayload, endpoint) : payload;
2416
+ if (endpoint.requestContentType === OCTET_STREAM) {
2417
+ const config = endpointRequestConfig(endpoint, parts.query, { signal: options.signal });
2418
+ if (endpoint.method === "post") return api.post(parts.path, body, config);
2419
+ if (endpoint.method === "put") return api.put(parts.path, body, config);
2420
+ return api.patch(parts.path, body, config);
2421
+ }
2422
+ const formData = pluginFormDataFromPayload(body);
2423
+ const config = {
2424
+ ...queryConfig(parts.query) ?? {},
2425
+ ...options.signal ? { signal: options.signal } : {},
2426
+ headers: { "Content-Type": void 0 }
2427
+ };
2428
+ if (endpoint.method === "post") return api.post(parts.path, formData, config);
2429
+ if (endpoint.method === "put") return api.put(parts.path, formData, config);
2430
+ return api.patch(parts.path, formData, config);
2431
+ }
2432
+ /** Fetch a generated plugin endpoint as a Blob and optionally trigger a browser download. */
2433
+ async function downloadPluginEndpoint(contract, endpoint, payload, options = {}) {
2434
+ if (endpoint.requestContentType === MULTIPART_FORM_DATA) throw new Error("[MINT SDK] Plugin download endpoints do not support multipart request bodies");
2435
+ const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl);
2436
+ const api = useApi({
2437
+ baseUrl: parts.baseUrl,
2438
+ withAuth: endpoint.requiresAuth !== false,
2439
+ typedErrors: true
2440
+ });
2441
+ const config = {
2442
+ ...endpointRequestConfig(endpoint, parts.query, { signal: options.signal }) ?? {},
2443
+ responseType: "blob"
2444
+ };
2445
+ const rawBody = requestBodyFromPayload(payload, parts.requestPayload, endpoint);
2446
+ const body = rawBody === void 0 ? void 0 : endpointRequestBody(endpoint, rawBody);
2447
+ let blob;
2448
+ if (endpoint.method === "get") blob = await api.get(parts.path, config);
2449
+ else if (endpoint.method === "post") blob = await api.post(parts.path, body, config);
2450
+ else if (endpoint.method === "put") blob = await api.put(parts.path, body, config);
2451
+ else if (endpoint.method === "patch") blob = await api.patch(parts.path, body, config);
2452
+ else if (endpoint.method === "delete") blob = await api.delete(parts.path, body === void 0 ? config : {
2453
+ ...config,
2454
+ data: body
2455
+ });
2456
+ else throw new Error(`[MINT SDK] Unsupported plugin endpoint method: ${endpoint.method}`);
2457
+ if (options.filename) downloadBlob(blob, options.filename, { revokeObjectUrlDelayMs: options.revokeObjectUrlDelayMs });
2458
+ return blob;
2459
+ }
2460
+ function normalizePluginSettingsOptions(options = {}) {
2461
+ return typeof options === "string" ? { pluginName: options } : options;
2462
+ }
2463
+ /** @deprecated Removed in MINT 1.4; use `useGeneratedPluginClient()` for generated clients. */
2464
+ function usePluginClient(client) {
2465
+ return client;
2466
+ }
2467
+ var PLATFORM_OWNED_SETTINGS_KEY = "allowed_experiment_types";
2468
+ /**
2469
+ * Secret settings travel as `{ "$secret": "<id>" }` references, so an untouched
2470
+ * secret round-trips without ever exposing its value. Declaring the format on
2471
+ * every write keeps a `null` unambiguous: it clears the stored secret instead of
2472
+ * meaning "unchanged", which is what the deprecated placeholder form meant.
2473
+ */
2474
+ function withSecretFormat(payload, secretFormat) {
2475
+ if (!secretFormat) return payload;
2476
+ return {
2477
+ ...payload,
2478
+ [PLUGIN_SECRET_FORMAT_KEY]: secretFormat
2479
+ };
2480
+ }
2481
+ function responseSecretFormat(response) {
2482
+ if (!response || typeof response !== "object") return null;
2483
+ const format = response[PLUGIN_SECRET_FORMAT_KEY];
2484
+ return typeof format === "string" && format ? format : null;
2485
+ }
2486
+ function cloneRecord(value) {
2487
+ return JSON.parse(JSON.stringify(value));
2488
+ }
2489
+ function hasOwnSetting(settings, key) {
2490
+ return Object.prototype.hasOwnProperty.call(settings, key);
2491
+ }
2492
+ function responseSettingsPayload(response) {
2493
+ if (!response || typeof response !== "object") return {};
2494
+ const data = response;
2495
+ if (data.config && typeof data.config === "object") return cloneRecord(data.config);
2496
+ if (data.settings && typeof data.settings === "object") return cloneRecord(data.settings);
2497
+ return {};
2498
+ }
2499
+ function responseSettingsRevision(response) {
2500
+ if (!response || typeof response !== "object") return null;
2501
+ const revision = response.revision;
2502
+ return typeof revision === "string" && revision ? revision : null;
2503
+ }
2504
+ function topLevelSettingsPatch(current, keys) {
2505
+ return Object.fromEntries([...keys].map((key) => [key, hasOwnSetting(current, key) ? current[key] : null]));
2506
+ }
2507
+ function changedTopLevelSettingKeys(current, saved) {
2508
+ return new Set([.../* @__PURE__ */ new Set([...Object.keys(current), ...Object.keys(saved)])].filter((key) => JSON.stringify(current[key]) !== JSON.stringify(saved[key])));
2509
+ }
2510
+ function platformOwnedSettingsPatch(settings) {
2511
+ return { [PLATFORM_OWNED_SETTINGS_KEY]: hasOwnSetting(settings, PLATFORM_OWNED_SETTINGS_KEY) ? settings[PLATFORM_OWNED_SETTINGS_KEY] : null };
2512
+ }
2513
+ /** Load, edit, and persist plugin settings from platform config or the plugin-local settings router. */
2514
+ function usePluginSettings(options = {}) {
2515
+ const resolvedOptions = normalizePluginSettingsOptions(options);
2516
+ const injectedContext = getInjectedPlatformContext();
2517
+ const { plugin, isIntegrated } = usePlatformContext();
2518
+ const platformApi = useApi({ baseUrl: resolvedOptions.platformApiBaseUrl ?? injectedContext?.platformApiUrl });
2519
+ const pluginApi = useApi({ baseUrl: resolvedOptions.apiBaseUrl ?? injectedContext?.plugin?.api_prefix });
2520
+ const values = ref({});
2521
+ const savedValues = shallowRef({});
2522
+ const revision = ref(null);
2523
+ const secretFormat = ref(null);
2524
+ const request = useRequestSyncState("Plugin settings request failed.");
2525
+ const isLoading = ref(false);
2526
+ const isSaving = ref(false);
2527
+ const resolvedPluginName = computed(() => resolvedOptions.pluginName ?? plugin.value?.name ?? injectedContext?.plugin?.name ?? "");
2528
+ const usesPluginSettingsApi = computed(() => {
2529
+ const currentPlugin = injectedContext?.plugin ?? plugin.value;
2530
+ return currentPlugin?.settings_api === "plugin" && resolvedPluginName.value === currentPlugin.name;
2531
+ });
2532
+ const usesPlatformConfig = computed(() => Boolean((isIntegrated.value || injectedContext?.isIntegrated) && resolvedPluginName.value && !usesPluginSettingsApi.value));
2533
+ const settings = computed(() => values.value);
2534
+ const isDirty = computed(() => JSON.stringify(values.value) !== JSON.stringify(savedValues.value));
2535
+ const settingsConfig = computed(() => ({
2536
+ title: resolvedOptions.title,
2537
+ schema: resolvedOptions.schema ?? void 0,
2538
+ showAppearance: resolvedOptions.showAppearance,
2539
+ values: values.value
2540
+ }));
2541
+ function setValues(nextValues) {
2542
+ values.value = cloneRecord(nextValues);
2543
+ }
2544
+ function applyLoaded(nextValues, nextRevision, nextSecretFormat = secretFormat.value) {
2545
+ const cloned = cloneRecord(nextValues);
2546
+ values.value = cloned;
2547
+ savedValues.value = cloneRecord(cloned);
2548
+ revision.value = nextRevision;
2549
+ secretFormat.value = nextSecretFormat;
2550
+ }
2551
+ async function load() {
2552
+ isLoading.value = true;
2553
+ try {
2554
+ const response = usesPlatformConfig.value ? await request.run(() => platformApi.get(`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`), {
2555
+ success: "load",
2556
+ errorMessage: "Failed to load plugin settings"
2557
+ }) : await request.run(() => pluginApi.get("/settings"), {
2558
+ success: "load",
2559
+ errorMessage: "Failed to load plugin settings"
2560
+ });
2561
+ applyLoaded(responseSettingsPayload(response), responseSettingsRevision(response), responseSecretFormat(response));
2562
+ } catch {} finally {
2563
+ isLoading.value = false;
2564
+ }
2565
+ }
2566
+ async function save(nextValues) {
2567
+ if (nextValues) setValues(nextValues);
2568
+ isSaving.value = true;
2569
+ try {
2570
+ const payload = cloneRecord(values.value);
2571
+ let response;
2572
+ if (usesPlatformConfig.value) {
2573
+ const dirtyKeys = changedTopLevelSettingKeys(payload, savedValues.value);
2574
+ const platformOwnedChanged = dirtyKeys.has(PLATFORM_OWNED_SETTINGS_KEY);
2575
+ const typedKeys = [...dirtyKeys].filter((key) => key !== PLATFORM_OWNED_SETTINGS_KEY);
2576
+ let nextRevision = revision.value;
2577
+ if (typedKeys.length) {
2578
+ response = await request.run(() => platformApi.patch(`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`, { config: withSecretFormat(topLevelSettingsPatch(payload, typedKeys), secretFormat.value) }), {
2579
+ success: "save",
2580
+ errorMessage: "Failed to save plugin settings"
2581
+ });
2582
+ nextRevision = responseSettingsRevision(response) ?? nextRevision;
2583
+ }
2584
+ if (platformOwnedChanged) {
2585
+ response = await request.run(() => platformApi.patch(`/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`, { config: platformOwnedSettingsPatch(payload) }), {
2586
+ success: "save",
2587
+ errorMessage: "Failed to save plugin settings"
2588
+ });
2589
+ nextRevision = responseSettingsRevision(response) ?? nextRevision;
2590
+ }
2591
+ if (!response) return true;
2592
+ applyLoaded(responseSettingsPayload(response), nextRevision, responseSecretFormat(response) ?? secretFormat.value);
2593
+ return true;
2594
+ } else {
2595
+ const ifMatch = revision.value ? { headers: { "If-Match": `"${revision.value}"` } } : void 0;
2596
+ response = await request.run(() => pluginApi.put("/settings", withSecretFormat(payload, secretFormat.value), ifMatch), {
2597
+ success: "save",
2598
+ errorMessage: "Failed to save plugin settings"
2599
+ });
2600
+ }
2601
+ applyLoaded(responseSettingsPayload(response), responseSettingsRevision(response) ?? revision.value, responseSecretFormat(response) ?? secretFormat.value);
2602
+ return true;
2603
+ } catch {
2604
+ return false;
2605
+ } finally {
2606
+ isSaving.value = false;
2607
+ }
2608
+ }
2609
+ function reset() {
2610
+ values.value = cloneRecord(savedValues.value);
2611
+ request.clearError();
2612
+ }
2613
+ onMounted(() => {
2614
+ if (resolvedOptions.loadOnMount !== false) load();
2615
+ });
2616
+ return {
2617
+ settings,
2618
+ values,
2619
+ config: values,
2620
+ settingsConfig,
2621
+ isLoading,
2622
+ isSaving,
2623
+ error: request.error,
2624
+ lastLoadedAt: request.lastLoadedAt,
2625
+ lastSavedAt: request.lastSavedAt,
2626
+ isDirty,
2627
+ setValues,
2628
+ load,
2629
+ save,
2630
+ reset
2631
+ };
2632
+ }
2633
+ function streamRequestAuth(requestHeaders, policy) {
2634
+ const headers = new Headers(requestHeaders);
2635
+ const authStore = useAuthStore();
2636
+ if (!authStore.isInitialized) authStore.initialize();
2637
+ if (policy.requiresAuth && authStore.token && !headers.has("Authorization")) headers.set("Authorization", `Bearer ${authStore.token}`);
2638
+ const token = policy.requiresAuth ? authStore.token : void 0;
2639
+ return {
2640
+ headers,
2641
+ token: token && headers.get("Authorization") === `Bearer ${token}` ? token : void 0
2642
+ };
2643
+ }
2644
+ function pluginEventStreamTransport(endpoint) {
2645
+ return async (url, init = {}) => {
2646
+ if (typeof fetch !== "function") throw new Error("[MINT SDK] fetch is required for plugin event streams.");
2647
+ const requestAuth = streamRequestAuth(init.headers, { requiresAuth: endpoint.requiresAuth !== false });
2648
+ const response = await fetch(url, {
2649
+ ...init,
2650
+ headers: requestAuth.headers
2651
+ });
2652
+ if (response.status === 401) throw authenticationRequiredError(response, requestAuth.token);
2653
+ return response;
2654
+ };
2655
+ }
2656
+ function looksLikeEventStreamOptions(value) {
2657
+ if (!value || typeof value !== "object") return false;
2658
+ const candidate = value;
2659
+ return [
2660
+ "baseUrl",
2661
+ "immediate",
2662
+ "reconnect",
2663
+ "reconnectDelayMs",
2664
+ "parseJson",
2665
+ "credentials",
2666
+ "headers",
2667
+ "onOpen",
2668
+ "onMessage",
2669
+ "onError"
2670
+ ].some((key) => key in candidate);
2671
+ }
2672
+ function usePluginEventStream(contract, endpoint, payloadOrOptions, maybeOptions) {
2673
+ if (endpoint.method !== "get") throw new Error(`[MINT SDK] Plugin event streams must use GET; got ${endpoint.method}`);
2674
+ const payload = maybeOptions === void 0 && looksLikeEventStreamOptions(payloadOrOptions) ? void 0 : payloadOrOptions;
2675
+ const options = maybeOptions === void 0 && looksLikeEventStreamOptions(payloadOrOptions) ? payloadOrOptions : maybeOptions ?? {};
2676
+ const responseType = endpoint.responseType?.trim();
2677
+ const parseJson = options.parseJson ?? Boolean(responseType && responseType !== "string");
2678
+ return useEventStream(() => buildPluginEndpointUrl(contract, endpoint, payload, { baseUrl: options.baseUrl }), {
2679
+ transport: pluginEventStreamTransport(endpoint),
2680
+ immediate: options.immediate,
2681
+ reconnect: options.reconnect,
2682
+ reconnectDelayMs: options.reconnectDelayMs,
2683
+ parseJson,
2684
+ credentials: options.credentials,
2685
+ headers: options.headers,
2686
+ shouldReconnect: (cause) => !(cause instanceof AuthenticationRequiredError),
2687
+ onOpen: options.onOpen,
2688
+ onMessage: options.onMessage,
2689
+ onError: options.onError,
2690
+ errorLabel: "Plugin event stream"
2691
+ });
2692
+ }
2693
+ /** Read and optionally load the current platform experiment for integrated plugin views. */
2694
+ function useCurrentExperiment(options = {}) {
2695
+ const api = useApi({ baseUrl: options.apiBaseUrl ?? getInjectedPlatformContext()?.platformApiUrl });
2696
+ const experiment = shallowRef(currentExperimentFromContext());
2697
+ const request = useRequestSyncState("Failed to load current experiment");
2698
+ const isLoading = request.loading;
2699
+ const error = request.error;
2700
+ const lastLoadedAt = request.lastLoadedAt;
2701
+ const experimentId = computed(() => {
2702
+ return resolveCurrentExperimentId();
851
2703
  });
852
- const topBarSlotNames = computed(() => getPluginWorkspaceTopBarSlotNames(slots));
853
- const sidebarSlotNames = computed(() => getPluginWorkspaceSidebarSlotNames(slots));
854
- const hasSidebarSurface = computed(() => hasPluginWorkspaceSidebarSurface({
855
- showSidebar: props.showSidebar ?? true,
856
- panels: props.panels ?? {},
857
- forms: props.forms ?? {},
858
- model: props.model,
859
- controls: props.controls,
860
- showSidebarWhenEmpty: props.showSidebarWhenEmpty ?? false,
861
- sidebarContentId: props.sidebarContentId,
862
- sidebarTitle: props.sidebarTitle,
863
- sidebarSubtitle: props.sidebarSubtitle,
864
- sidebarBadge: props.sidebarBadge,
865
- hasSidebarSlot: Boolean(slots.sidebar),
866
- sidebarSlotCount: sidebarSlotNames.value.length
867
- }));
868
- const controls = computed(() => ({
869
- activeView: navigationState.resolvedActiveView.value,
870
- setActiveView: navigationState.setActiveView,
871
- values: controlsState.internalControlValues,
872
- componentBindings: controlsState.resolvedComponentBindings.value,
873
- componentBindingsById: controlsState.resolvedComponentBindingsById.value,
874
- componentProps: controlsState.resolvedComponentProps.value,
875
- componentPropsById: controlsState.resolvedComponentPropsById.value
876
- }));
2704
+ const hasExperiment = computed(() => experimentId.value !== void 0);
2705
+ function requireExperimentId() {
2706
+ const id = experimentId.value;
2707
+ if (id === void 0) throw new Error("[MINT SDK] No current experiment is selected.");
2708
+ return id;
2709
+ }
2710
+ async function fetchExperiment(id = experimentId.value) {
2711
+ if (id === void 0) {
2712
+ experiment.value = currentExperimentFromContext();
2713
+ return experiment.value;
2714
+ }
2715
+ try {
2716
+ const result = await request.run(() => api.get(`/experiments/${id}`), {
2717
+ success: "load",
2718
+ errorMessage: "Failed to load current experiment"
2719
+ });
2720
+ experiment.value = result;
2721
+ return result;
2722
+ } catch (e) {
2723
+ experiment.value = void 0;
2724
+ return;
2725
+ }
2726
+ }
2727
+ async function refresh() {
2728
+ return fetchExperiment();
2729
+ }
2730
+ watch(experimentId, (id) => {
2731
+ if (options.immediate === false) return;
2732
+ if (id === void 0) {
2733
+ experiment.value = currentExperimentFromContext();
2734
+ return;
2735
+ }
2736
+ fetchExperiment(id);
2737
+ }, { immediate: options.immediate !== false });
877
2738
  return {
878
- shell: computed(() => ({
879
- sidebarPosition: props.sidebarPosition ?? "left",
880
- sidebarWidth: props.sidebarWidth,
881
- floating: props.floating ?? true,
882
- responsiveSidebar: props.responsiveSidebar ?? true,
883
- sidebarToggleLabel: props.sidebarToggleLabel ?? "Open workspace sidebar",
884
- sidebarCloseLabel: props.sidebarCloseLabel ?? "Close workspace sidebar",
885
- hasSidebarSurface: hasSidebarSurface.value,
886
- title: props.title ?? "Workspace",
887
- subtitle: props.subtitle,
888
- topBarVariant: props.topBarVariant ?? "card",
889
- homePath: props.homePath ?? "/",
890
- showLogo: props.showLogo ?? true,
891
- pageSelector: props.pageSelector,
892
- currentPageSelectorId: navigationState.resolvedCurrentPageSelectorId.value,
893
- pluginSwitcher: props.pluginSwitcher,
894
- pillNav: navigationState.resolvedPillNav.value,
895
- currentPillId: navigationState.resolvedCurrentPillId.value,
896
- showThemeToggle: props.showThemeToggle ?? true,
897
- showSettings: props.showSettings ?? true,
898
- settingsConfig: props.settingsConfig,
899
- showStandaloneLabel: props.showStandaloneLabel ?? false,
900
- standaloneLabel: props.standaloneLabel ?? "Standalone",
901
- accountMenu: props.accountMenu,
902
- showNotifications: props.showNotifications ?? false,
903
- hasNotificationDot: props.hasNotificationDot ?? false,
904
- showAdmin: props.showAdmin ?? false,
905
- adminPath: props.adminPath ?? "/admin",
906
- userName: props.userName,
907
- userInitial: props.userInitial,
908
- userEmail: props.userEmail,
909
- topBarSlotNames: topBarSlotNames.value,
910
- controls: controls.value
911
- })),
912
- sidebar: computed(() => ({
913
- title: props.sidebarTitle,
914
- subtitle: props.sidebarSubtitle,
915
- badge: props.sidebarBadge,
916
- panels: props.panels ?? {},
917
- activeView: navigationState.resolvedActiveView.value,
918
- floating: false,
919
- dense: props.dense ?? true,
920
- width: props.sidebarWidth,
921
- side: props.sidebarPosition ?? "left",
922
- forms: props.forms ?? {},
923
- contentId: props.sidebarContentId,
924
- showWhenEmpty: (props.showSidebarWhenEmpty ?? false) || props.sidebarContentId !== void 0,
925
- model: props.model,
926
- controlSchema: props.controls,
927
- controlOptions: props.controlOptions ?? {},
928
- values: controlsState.internalControlValues,
929
- formEnhancements: props.formEnhancements,
930
- showFormActions: props.showFormActions ?? false,
931
- formLoading: props.formLoading ?? false,
932
- formDisabled: props.formDisabled ?? false,
933
- formReadonly: props.formReadonly ?? false,
934
- formSize: props.formSize ?? "sm",
935
- collapsible: props.sidebarCollapsible ?? true,
936
- collapsed: props.sidebarCollapsed,
937
- defaultCollapsed: props.defaultSidebarCollapsed ?? false,
938
- collapsedWidth: props.sidebarCollapsedWidth ?? "3rem",
939
- collapseButtonLabel: props.sidebarCollapseButtonLabel ?? "Collapse sidebar",
940
- expandButtonLabel: props.sidebarExpandButtonLabel ?? "Expand sidebar",
941
- slotNames: sidebarSlotNames.value
942
- })),
943
- controls,
944
- hasSidebarSurface,
945
- handleControlValuesUpdate: controlsState.handleControlValuesUpdate,
946
- handlePageSelectorSelect: navigationState.handlePageSelectorSelect,
947
- handlePillSelect: navigationState.handlePillSelect
2739
+ experimentId,
2740
+ hasExperiment,
2741
+ experiment,
2742
+ isLoading,
2743
+ error,
2744
+ lastLoadedAt,
2745
+ requireExperimentId,
2746
+ fetch: fetchExperiment,
2747
+ refresh
948
2748
  };
949
2749
  }
950
2750
  //#endregion
@@ -1070,7 +2870,7 @@ function usePluginJobCenter(options = {}) {
1070
2870
  }
1071
2871
  function replaceLegacyJobs(nextJobs) {
1072
2872
  const normalized = nextJobs.map(normalizeJobState);
1073
- new Set([...jobs.value.map((job) => job.job_id), ...normalized.map((job) => job.job_id)]).forEach(bumpJobRevision);
2873
+ (/* @__PURE__ */ new Set([...jobs.value.map((job) => job.job_id), ...normalized.map((job) => job.job_id)])).forEach(bumpJobRevision);
1074
2874
  legacyOverride.value = {
1075
2875
  input: toValue(options.jobs),
1076
2876
  jobs: normalized
@@ -1142,8 +2942,10 @@ function usePluginJobCenter(options = {}) {
1142
2942
  try {
1143
2943
  const updated = sourceCancel ? await sourceCancel(job.job_id) : await adapterCancel(job);
1144
2944
  if (!active) return false;
1145
- if (!sourceCancel && revision === (jobRevisions.get(job.job_id) ?? 0)) if (updated) upsertLegacyJob(updated);
1146
- else await refresh();
2945
+ if (!sourceCancel && revision === (jobRevisions.get(job.job_id) ?? 0)) {
2946
+ if (updated) upsertLegacyJob(updated);
2947
+ else await refresh();
2948
+ }
1147
2949
  return true;
1148
2950
  } catch (cause) {
1149
2951
  if (active) error.value = errorMessage(cause, "Failed to cancel job");
@@ -1161,8 +2963,10 @@ function usePluginJobCenter(options = {}) {
1161
2963
  try {
1162
2964
  const deleted = sourceDelete ? await sourceDelete(job.job_id) : await adapterDelete(job);
1163
2965
  if (!active) return false;
1164
- if (!sourceDelete) if (deleted === false) await refresh();
1165
- else removeLegacyJobs(new Set([job.job_id]));
2966
+ if (!sourceDelete) {
2967
+ if (deleted === false) await refresh();
2968
+ else removeLegacyJobs(/* @__PURE__ */ new Set([job.job_id]));
2969
+ }
1166
2970
  return deleted !== false;
1167
2971
  } catch (cause) {
1168
2972
  if (active) error.value = errorMessage(cause, "Failed to delete job");
@@ -1243,7 +3047,7 @@ function usePluginJobCenter(options = {}) {
1243
3047
  isPending: (job) => pendingJobIds.value.has(job.job_id)
1244
3048
  };
1245
3049
  }
1246
- /** @deprecated Use `usePluginJobCenter()` for the plugin job-center controller. */
3050
+ /** @deprecated Removed in MINT 1.4; use `usePluginJobCenter()` for the plugin job-center controller. */
1247
3051
  function useJobsStatusTray(options = {}) {
1248
3052
  return usePluginJobCenter(options);
1249
3053
  }
@@ -1640,266 +3444,301 @@ function useListReorder(options) {
1640
3444
  };
1641
3445
  }
1642
3446
  //#endregion
1643
- //#region src/composables/useWellPlateEditor.ts
1644
- var MAX_HISTORY = 50;
1645
- function createEmptyPlate(name, format, id) {
3447
+ //#region src/composables/useRackEditor.ts
3448
+ var SLOT_CYCLE = [
3449
+ "R",
3450
+ "G",
3451
+ "B",
3452
+ "Y"
3453
+ ];
3454
+ function wellFromEditData(data) {
3455
+ return {
3456
+ state: "filled",
3457
+ sampleType: data.sampleType || void 0,
3458
+ metadata: {
3459
+ label: data.label,
3460
+ injectionVolume: data.injectionVolume,
3461
+ injectionCount: data.injectionCount,
3462
+ customMethod: data.customMethod || null
3463
+ }
3464
+ };
3465
+ }
3466
+ function wellFromDrop(data, rack) {
3467
+ const label = (data.label ?? data.sampleName ?? data.id ?? "").trim();
3468
+ if (!label) return null;
1646
3469
  return {
1647
- id: id || `plate-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
1648
- name,
1649
- format,
1650
- wells: {}
3470
+ state: "filled",
3471
+ sampleType: data.sampleType ?? "sample",
3472
+ metadata: {
3473
+ label,
3474
+ injectionVolume: data.injectionVolume ?? rack.injectionVolume,
3475
+ injectionCount: data.injectionCount ?? 1,
3476
+ customMethod: data.customMethod ?? null
3477
+ }
1651
3478
  };
1652
3479
  }
1653
- function generateSampleId() {
1654
- return `sample-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1655
- }
1656
- function cloneValue(value) {
1657
- return structuredClone(toRaw(value));
1658
- }
1659
- /** Manages multi-plate well-plate state with sample assignment, selection, and undo/redo history. */
1660
- function useWellPlateEditor(initialState, options = {}) {
1661
- const { maxHistory = MAX_HISTORY, defaultFormat = 96 } = options;
1662
- const defaultPlate = createEmptyPlate("Plate 1", defaultFormat);
1663
- const internalState = ref({
1664
- plates: initialState?.plates || [defaultPlate],
1665
- activePlateId: initialState?.activePlateId || defaultPlate.id,
1666
- samples: initialState?.samples || [],
1667
- selectedWells: initialState?.selectedWells || [],
1668
- activeSampleId: initialState?.activeSampleId
1669
- });
1670
- const history = ref([]);
1671
- const historyIndex = ref(-1);
1672
- const state = computed(() => internalState.value);
1673
- const plates = computed(() => internalState.value.plates);
1674
- const activePlate = computed(() => internalState.value.plates.find((p) => p.id === internalState.value.activePlateId));
1675
- const samples = computed(() => internalState.value.samples);
1676
- const selectedWells = computed(() => internalState.value.selectedWells);
1677
- const activeSampleId = computed(() => internalState.value.activeSampleId);
1678
- const canUndo = computed(() => historyIndex.value >= 0);
1679
- const canRedo = computed(() => historyIndex.value < history.value.length - 1);
1680
- function saveToHistory() {
1681
- const entry = {
1682
- plates: cloneValue(internalState.value.plates),
1683
- samples: cloneValue(internalState.value.samples)
3480
+ var globalIdCounter = 0;
3481
+ function generateId() {
3482
+ globalIdCounter++;
3483
+ return `rack-${Date.now()}-${globalIdCounter}`;
3484
+ }
3485
+ /** Manages a set of sample racks with well-level assignment, series fill, and multi-rack reordering. */
3486
+ function useRackEditor(initialRacks, options) {
3487
+ const defaultFormat = options?.defaultFormat ?? 54;
3488
+ const defaultInjVol = options?.defaultInjectionVolume ?? 5;
3489
+ const maxRacks = options?.maxRacks ?? 10;
3490
+ const minRacks = options?.minRacks ?? 1;
3491
+ function createDefaultRack(name, slotIndex) {
3492
+ const slot = SLOT_CYCLE[(slotIndex ?? 0) % SLOT_CYCLE.length];
3493
+ return {
3494
+ id: generateId(),
3495
+ name: name ?? `Rack ${racks.value.length + 1}`,
3496
+ format: defaultFormat,
3497
+ slot,
3498
+ injectionVolume: defaultInjVol,
3499
+ wells: {}
1684
3500
  };
1685
- if (historyIndex.value < history.value.length - 1) history.value = history.value.slice(0, historyIndex.value + 1);
1686
- history.value.push(entry);
1687
- if (history.value.length > maxHistory) history.value.shift();
1688
- else historyIndex.value++;
1689
- }
1690
- function setActivePlate(plateId) {
1691
- if (internalState.value.plates.some((p) => p.id === plateId)) {
1692
- internalState.value.activePlateId = plateId;
1693
- internalState.value.selectedWells = [];
1694
- }
1695
- }
1696
- function setActiveSample(sampleId) {
1697
- internalState.value.activeSampleId = sampleId;
1698
- }
1699
- function setSelectedWells(wellIds) {
1700
- internalState.value.selectedWells = wellIds;
1701
- }
1702
- function addPlate(name, format) {
1703
- saveToHistory();
1704
- const plateNumber = internalState.value.plates.length + 1;
1705
- const plate = createEmptyPlate(name || `Plate ${plateNumber}`, format || defaultFormat);
1706
- internalState.value.plates.push(plate);
1707
- internalState.value.activePlateId = plate.id;
1708
- internalState.value.selectedWells = [];
1709
- return plate;
1710
- }
1711
- function removePlate(plateId) {
1712
- if (internalState.value.plates.length <= 1) return;
1713
- saveToHistory();
1714
- const index = internalState.value.plates.findIndex((p) => p.id === plateId);
3501
+ }
3502
+ const racks = ref(initialRacks && initialRacks.length > 0 ? initialRacks.map((r) => ({
3503
+ ...r,
3504
+ wells: { ...r.wells }
3505
+ })) : [createDefaultRack("Rack 1", 0)]);
3506
+ const activeRackId = ref(racks.value[0]?.id ?? "");
3507
+ const activeRack = computed(() => racks.value.find((r) => r.id === activeRackId.value) ?? racks.value[0]);
3508
+ function addRack(name) {
3509
+ if (racks.value.length >= maxRacks) return racks.value[racks.value.length - 1];
3510
+ const rack = createDefaultRack(name, racks.value.length);
3511
+ racks.value.push(rack);
3512
+ activeRackId.value = rack.id;
3513
+ return rack;
3514
+ }
3515
+ function removeRack(rackId) {
3516
+ if (racks.value.length <= minRacks) return;
3517
+ const index = racks.value.findIndex((r) => r.id === rackId);
1715
3518
  if (index === -1) return;
1716
- internalState.value.plates.splice(index, 1);
1717
- if (internalState.value.activePlateId === plateId) {
1718
- internalState.value.activePlateId = internalState.value.plates[0].id;
1719
- internalState.value.selectedWells = [];
1720
- }
1721
- }
1722
- function addSample(name, color) {
1723
- saveToHistory();
1724
- const sample = {
1725
- id: generateSampleId(),
1726
- name,
1727
- color: color || SAMPLE_COLOR_SCALE[internalState.value.samples.length % SAMPLE_COLOR_SCALE.length],
1728
- count: 0
3519
+ racks.value.splice(index, 1);
3520
+ if (activeRackId.value === rackId && racks.value[0]) activeRackId.value = racks.value[0].id;
3521
+ }
3522
+ function reorderRacks(fromIndex, toIndex) {
3523
+ if (fromIndex < 0 || fromIndex >= racks.value.length) return;
3524
+ if (toIndex < 0 || toIndex >= racks.value.length) return;
3525
+ if (fromIndex === toIndex) return;
3526
+ const [moved] = racks.value.splice(fromIndex, 1);
3527
+ if (moved) racks.value.splice(toIndex, 0, moved);
3528
+ }
3529
+ function updateRack(rackId, data) {
3530
+ const rack = racks.value.find((r) => r.id === rackId);
3531
+ if (!rack) return;
3532
+ if (data.name !== void 0) rack.name = data.name;
3533
+ if (data.format !== void 0) rack.format = data.format;
3534
+ if (data.slot !== void 0) rack.slot = data.slot;
3535
+ if (data.injectionVolume !== void 0) rack.injectionVolume = data.injectionVolume;
3536
+ if (data.wells !== void 0) rack.wells = data.wells;
3537
+ }
3538
+ function setActiveRack(rackId) {
3539
+ if (racks.value.some((r) => r.id === rackId)) activeRackId.value = rackId;
3540
+ }
3541
+ function setWellData(rackId, wellId, data) {
3542
+ const rack = racks.value.find((r) => r.id === rackId);
3543
+ if (!rack) return;
3544
+ rack.wells[wellId] = {
3545
+ ...rack.wells[wellId],
3546
+ ...data
1729
3547
  };
1730
- internalState.value.samples.push(sample);
1731
- return sample;
1732
3548
  }
1733
- function removeSample(sampleId) {
1734
- saveToHistory();
1735
- const index = internalState.value.samples.findIndex((s) => s.id === sampleId);
1736
- if (index === -1) return;
1737
- internalState.value.samples.splice(index, 1);
1738
- for (const plate of internalState.value.plates) for (const well of Object.values(plate.wells)) if (well.sampleType === sampleId) {
1739
- delete well.sampleType;
1740
- well.state = "empty";
1741
- }
1742
- if (internalState.value.activeSampleId === sampleId) internalState.value.activeSampleId = void 0;
1743
- updateSampleCounts();
1744
- }
1745
- function assignSample(wellIds, sampleId) {
1746
- if (wellIds.length === 0) return;
1747
- saveToHistory();
1748
- const plate = activePlate.value;
1749
- if (!plate) return;
1750
- for (const wellId of wellIds) {
1751
- const well = plate.wells[wellId] || {
1752
- id: wellId,
1753
- row: wellId.charCodeAt(0) - 65,
1754
- col: parseInt(wellId.slice(1)) - 1,
1755
- state: "empty"
1756
- };
1757
- if (sampleId) {
1758
- well.sampleType = sampleId;
1759
- well.state = "filled";
1760
- } else {
1761
- delete well.sampleType;
1762
- well.state = "empty";
1763
- }
1764
- plate.wells[wellId] = well;
1765
- }
1766
- updateSampleCounts();
1767
- }
1768
- function clearWells(wellIds) {
1769
- assignSample(wellIds, void 0);
1770
- }
1771
- function updateSampleCounts() {
1772
- const counts = {};
1773
- for (const plate of internalState.value.plates) for (const well of Object.values(plate.wells)) if (well.sampleType) counts[well.sampleType] = (counts[well.sampleType] || 0) + 1;
1774
- for (const sample of internalState.value.samples) sample.count = counts[sample.id] || 0;
1775
- }
1776
- function undo() {
1777
- if (!canUndo.value) return;
1778
- const entry = history.value[historyIndex.value];
1779
- historyIndex.value--;
1780
- internalState.value.plates = cloneValue(entry.plates);
1781
- internalState.value.samples = cloneValue(entry.samples);
1782
- if (!internalState.value.plates.some((p) => p.id === internalState.value.activePlateId)) internalState.value.activePlateId = internalState.value.plates[0]?.id || "";
1783
- internalState.value.selectedWells = [];
1784
- }
1785
- function redo() {
1786
- if (!canRedo.value) return;
1787
- historyIndex.value++;
1788
- const entry = history.value[historyIndex.value];
1789
- internalState.value.plates = cloneValue(entry.plates);
1790
- internalState.value.samples = cloneValue(entry.samples);
1791
- internalState.value.selectedWells = [];
1792
- }
1793
- function exportData(format) {
1794
- if (format === "json") return JSON.stringify({
1795
- plates: internalState.value.plates,
1796
- samples: internalState.value.samples
1797
- }, null, 2);
1798
- const sampleMap = new Map(internalState.value.samples.map((s) => [s.id, s.name]));
1799
- const rows = ["Plate,Well,Sample Type,Sample Name"];
1800
- for (const plate of internalState.value.plates) for (const [wellId, well] of Object.entries(plate.wells)) if (well.sampleType) {
1801
- const sampleName = sampleMap.get(well.sampleType) || "";
1802
- rows.push(`"${plate.name}","${wellId}","${well.sampleType}","${sampleName}"`);
1803
- }
1804
- return rows.join("\n");
1805
- }
1806
- function importData(data, format) {
1807
- try {
1808
- saveToHistory();
1809
- if (format === "json") {
1810
- const parsed = JSON.parse(data);
1811
- if (parsed.plates && Array.isArray(parsed.plates)) {
1812
- internalState.value.plates = parsed.plates;
1813
- internalState.value.activePlateId = parsed.plates[0]?.id || "";
1814
- }
1815
- if (parsed.samples && Array.isArray(parsed.samples)) internalState.value.samples = parsed.samples;
1816
- internalState.value.selectedWells = [];
1817
- updateSampleCounts();
1818
- return true;
1819
- }
1820
- const lines = data.trim().split("\n");
1821
- if (lines.length < 2) return false;
1822
- const plateMap = /* @__PURE__ */ new Map();
1823
- const sampleMap = /* @__PURE__ */ new Map();
1824
- for (let i = 1; i < lines.length; i++) {
1825
- const parts = lines[i].match(/(?:[^",]+|"[^"]*")+/g);
1826
- if (!parts || parts.length < 4) continue;
1827
- const plateName = parts[0].replace(/^"|"$/g, "");
1828
- const wellId = parts[1].replace(/^"|"$/g, "");
1829
- const sampleId = parts[2].replace(/^"|"$/g, "");
1830
- const sampleName = parts[3].replace(/^"|"$/g, "");
1831
- if (!plateMap.has(plateName)) plateMap.set(plateName, createEmptyPlate(plateName, defaultFormat));
1832
- if (sampleId && !sampleMap.has(sampleId)) sampleMap.set(sampleId, {
1833
- id: sampleId,
1834
- name: sampleName || sampleId,
1835
- color: SAMPLE_COLOR_SCALE[sampleMap.size % SAMPLE_COLOR_SCALE.length],
1836
- count: 0
1837
- });
1838
- const plate = plateMap.get(plateName);
1839
- plate.wells[wellId] = {
3549
+ function clearWell(rackId, wellId) {
3550
+ const rack = racks.value.find((r) => r.id === rackId);
3551
+ if (!rack) return;
3552
+ delete rack.wells[wellId];
3553
+ }
3554
+ function clearAllWells(rackId) {
3555
+ const rack = racks.value.find((r) => r.id === rackId);
3556
+ if (!rack) return;
3557
+ rack.wells = {};
3558
+ }
3559
+ function fillSeries(rackId, prefix = "S") {
3560
+ const rack = racks.value.find((r) => r.id === rackId);
3561
+ if (!rack) return;
3562
+ const config = PLATE_CONFIGS[rack.format];
3563
+ if (!config) return;
3564
+ let counter = 1;
3565
+ for (let row = 0; row < config.rows; row++) for (let col = 0; col < config.cols; col++) {
3566
+ const wellId = `${rowLabel(row)}${col + 1}`;
3567
+ if (!rack.wells[wellId]?.sampleType) {
3568
+ const paddedNum = String(counter).padStart(3, "0");
3569
+ rack.wells[wellId] = {
1840
3570
  id: wellId,
1841
- row: wellId.charCodeAt(0) - 65,
1842
- col: parseInt(wellId.slice(1)) - 1,
1843
- state: sampleId ? "filled" : "empty",
1844
- sampleType: sampleId || void 0
3571
+ row,
3572
+ col,
3573
+ state: "filled",
3574
+ sampleType: "sample",
3575
+ metadata: {
3576
+ label: `${prefix}${paddedNum}`,
3577
+ injectionVolume: rack.injectionVolume,
3578
+ injectionCount: 1
3579
+ }
1845
3580
  };
1846
3581
  }
1847
- internalState.value.plates = Array.from(plateMap.values());
1848
- internalState.value.samples = Array.from(sampleMap.values());
1849
- internalState.value.activePlateId = internalState.value.plates[0]?.id || "";
1850
- internalState.value.selectedWells = [];
1851
- updateSampleCounts();
1852
- return true;
1853
- } catch {
1854
- return false;
3582
+ counter++;
1855
3583
  }
1856
3584
  }
1857
- function loadState(state) {
1858
- saveToHistory();
1859
- if (state.plates && state.plates.length > 0) {
1860
- internalState.value.plates = cloneValue(state.plates);
1861
- internalState.value.activePlateId = state.activePlateId ?? state.plates[0].id;
3585
+ function moveWell(rackId, sourceWellId, targetWellId) {
3586
+ const rack = racks.value.find((r) => r.id === rackId);
3587
+ if (!rack) return;
3588
+ const { [sourceWellId]: source, [targetWellId]: target } = rack.wells;
3589
+ delete rack.wells[sourceWellId];
3590
+ delete rack.wells[targetWellId];
3591
+ if (target) rack.wells[sourceWellId] = placed(target, sourceWellId);
3592
+ if (source) rack.wells[targetWellId] = placed(source, targetWellId);
3593
+ }
3594
+ /** A moved well keeps its position fields in step with its new key. */
3595
+ function placed(well, wellId) {
3596
+ if (well.id === void 0) return well;
3597
+ const [, row = "", col = ""] = /^([A-Z]+)(\d+)$/.exec(wellId) ?? [];
3598
+ return {
3599
+ ...well,
3600
+ id: wellId,
3601
+ row: rowIndex(row),
3602
+ col: Number(col) - 1
3603
+ };
3604
+ }
3605
+ function assignWells(rackId, wellIds, groupId) {
3606
+ const rack = racks.value.find((r) => r.id === rackId);
3607
+ for (const wellId of wellIds) {
3608
+ const sampleType = rack?.wells[wellId]?.sampleType ?? "sample";
3609
+ if (groupId) setWellData(rackId, wellId, {
3610
+ state: "filled",
3611
+ sampleType,
3612
+ group: groupId
3613
+ });
3614
+ else clearWell(rackId, wellId);
1862
3615
  }
1863
- if (state.samples) internalState.value.samples = cloneValue(state.samples);
1864
- internalState.value.selectedWells = state.selectedWells ?? [];
1865
- internalState.value.activeSampleId = state.activeSampleId;
1866
- updateSampleCounts();
1867
3616
  }
1868
- function reset() {
1869
- const plate = createEmptyPlate("Plate 1", defaultFormat);
1870
- internalState.value = {
1871
- plates: [plate],
1872
- activePlateId: plate.id,
1873
- samples: [],
1874
- selectedWells: [],
1875
- activeSampleId: void 0
3617
+ const undoStack = ref([]);
3618
+ const redoStack = ref([]);
3619
+ const canUndo = computed(() => undoStack.value.length > 0);
3620
+ const canRedo = computed(() => redoStack.value.length > 0);
3621
+ /** The racks as the history last saw them. */
3622
+ let known = JSON.stringify(racks.value);
3623
+ function pushUndo(snapshot) {
3624
+ undoStack.value = [...undoStack.value, snapshot].slice(-50);
3625
+ redoStack.value = [];
3626
+ }
3627
+ /** Records a change made since the history last looked, as its own step. */
3628
+ function absorb() {
3629
+ const now = JSON.stringify(racks.value);
3630
+ if (now === known) return;
3631
+ pushUndo({
3632
+ racks: known,
3633
+ activeRackId: activeRackId.value
3634
+ });
3635
+ known = now;
3636
+ }
3637
+ watch(racks, absorb, { deep: true });
3638
+ function step(from, to) {
3639
+ absorb();
3640
+ const snapshot = from.value.pop();
3641
+ if (snapshot === void 0) return;
3642
+ to.value.push({
3643
+ racks: known,
3644
+ activeRackId: activeRackId.value
3645
+ });
3646
+ racks.value = JSON.parse(snapshot.racks);
3647
+ known = snapshot.racks;
3648
+ activeRackId.value = racks.value.some((rack) => rack.id === snapshot.activeRackId) ? snapshot.activeRackId : racks.value[0]?.id ?? "";
3649
+ }
3650
+ const undo = () => step(undoStack, redoStack);
3651
+ const redo = () => step(redoStack, undoStack);
3652
+ function clearHistory() {
3653
+ undoStack.value = [];
3654
+ redoStack.value = [];
3655
+ known = JSON.stringify(racks.value);
3656
+ }
3657
+ /** Runs `apply` and records the prior state when it changed anything. */
3658
+ function recorded(apply) {
3659
+ return (...args) => {
3660
+ absorb();
3661
+ const before = {
3662
+ racks: known,
3663
+ activeRackId: activeRackId.value
3664
+ };
3665
+ const result = apply(...args);
3666
+ const after = JSON.stringify(racks.value);
3667
+ if (after !== known) {
3668
+ pushUndo(before);
3669
+ known = after;
3670
+ }
3671
+ return result;
1876
3672
  };
1877
- history.value = [];
1878
- historyIndex.value = -1;
3673
+ }
3674
+ const plateEditorListeners = {
3675
+ "update:activeRackId": setActiveRack,
3676
+ "rack-add": recorded(() => {
3677
+ addRack();
3678
+ }),
3679
+ "rack-remove": recorded(removeRack),
3680
+ "rack-reorder": recorded((rackIds) => {
3681
+ racks.value = rackIds.flatMap((id) => racks.value.filter((rack) => rack.id === id));
3682
+ }),
3683
+ "update:format": recorded((rackId, format) => updateRack(rackId, { format })),
3684
+ "update:slot": recorded((rackId, slot) => updateRack(rackId, { slot })),
3685
+ "well-edit": recorded((rackId, wellId, data) => setWellData(rackId, wellId, wellFromEditData(data))),
3686
+ "well-clear": recorded(clearWell),
3687
+ "well-move": recorded(moveWell),
3688
+ "sample-drop": recorded((rackId, wellId, data) => {
3689
+ const rack = racks.value.find((r) => r.id === rackId);
3690
+ const well = rack && wellFromDrop(data, rack);
3691
+ if (well) setWellData(rackId, wellId, well);
3692
+ }),
3693
+ "assign": recorded(assignWells),
3694
+ "clear": recorded(clearAllWells),
3695
+ "fill-series": recorded((rackId) => fillSeries(rackId)),
3696
+ "undo": undo,
3697
+ "redo": redo
3698
+ };
3699
+ function getAllWells() {
3700
+ const result = [];
3701
+ for (const rack of racks.value) for (const [wellId, well] of Object.entries(rack.wells)) result.push({
3702
+ rackId: rack.id,
3703
+ wellId,
3704
+ well
3705
+ });
3706
+ return result;
3707
+ }
3708
+ const totalSampleCount = computed(() => {
3709
+ let count = 0;
3710
+ for (const rack of racks.value) count += Object.keys(rack.wells).length;
3711
+ return count;
3712
+ });
3713
+ function reset() {
3714
+ const rack = createDefaultRack("Rack 1", 0);
3715
+ racks.value = [rack];
3716
+ activeRackId.value = rack.id;
3717
+ clearHistory();
1879
3718
  }
1880
3719
  return {
1881
- state,
1882
- plates,
1883
- activePlate,
1884
- samples,
1885
- selectedWells,
1886
- activeSampleId,
1887
- canUndo,
1888
- canRedo,
1889
- setActivePlate,
1890
- setActiveSample,
1891
- setSelectedWells,
1892
- addPlate,
1893
- removePlate,
1894
- addSample,
1895
- removeSample,
1896
- assignSample,
1897
- clearWells,
3720
+ racks,
3721
+ activeRack,
3722
+ activeRackId,
3723
+ addRack: recorded(addRack),
3724
+ removeRack: recorded(removeRack),
3725
+ reorderRacks: recorded(reorderRacks),
3726
+ updateRack: recorded(updateRack),
3727
+ setActiveRack,
3728
+ setWellData: recorded(setWellData),
3729
+ clearWell: recorded(clearWell),
3730
+ clearAllWells: recorded(clearAllWells),
3731
+ fillSeries: recorded(fillSeries),
3732
+ moveWell: recorded(moveWell),
3733
+ assignWells: recorded(assignWells),
3734
+ plateEditorListeners,
1898
3735
  undo,
1899
3736
  redo,
1900
- exportData,
1901
- importData,
1902
- loadState,
3737
+ canUndo,
3738
+ canRedo,
3739
+ clearHistory,
3740
+ getAllWells,
3741
+ totalSampleCount,
1903
3742
  reset
1904
3743
  };
1905
3744
  }
@@ -2328,8 +4167,10 @@ function buildClassSchema(tokenizedMembers, classKind, subKind, classTagPosition
2328
4167
  });
2329
4168
  }
2330
4169
  let usernameAssigned = false;
2331
- for (const c of columns) if (c.name === "Username") if (usernameAssigned) c.name = "Project";
2332
- else usernameAssigned = true;
4170
+ for (const c of columns) if (c.name === "Username") {
4171
+ if (usernameAssigned) c.name = "Project";
4172
+ else usernameAssigned = true;
4173
+ }
2333
4174
  return {
2334
4175
  classKind,
2335
4176
  subKind,
@@ -2348,7 +4189,7 @@ function functionalDependency(rows, srcIdx, dstIdx) {
2348
4189
  const b = row[dstIdx] ?? "";
2349
4190
  const set = map.get(a);
2350
4191
  if (set) set.add(b);
2351
- else map.set(a, new Set([b]));
4192
+ else map.set(a, /* @__PURE__ */ new Set([b]));
2352
4193
  }
2353
4194
  if (map.size === 0) return 0;
2354
4195
  let functional = 0;
@@ -2923,59 +4764,6 @@ function extractSampleNamesFromDesignData(rawData, options = {}) {
2923
4764
  return extractSampleOptionsFromDesignData(rawData, options).map((sample) => sample.value);
2924
4765
  }
2925
4766
  //#endregion
2926
- //#region src/composables/autoGroup/csv-shim.ts
2927
- function parseCSVLine(line, delimiter = ",") {
2928
- const result = [];
2929
- let current = "";
2930
- let inQuotes = false;
2931
- for (let i = 0; i < line.length; i++) {
2932
- const char = line[i];
2933
- if (char === "\"") if (inQuotes && line[i + 1] === "\"") {
2934
- current += "\"";
2935
- i++;
2936
- } else inQuotes = !inQuotes;
2937
- else if (char === delimiter && !inQuotes) {
2938
- result.push(current.trim());
2939
- current = "";
2940
- } else current += char;
2941
- }
2942
- result.push(current.trim());
2943
- return result;
2944
- }
2945
- function parseCSV(text) {
2946
- const lines = text.trim().split("\n");
2947
- if (lines.length < 2) throw new Error("CSV must have at least a header and one data row");
2948
- const firstLine = lines[0];
2949
- const csvDelimiter = firstLine.includes(" ") ? " " : ",";
2950
- const headers = parseCSVLine(firstLine, csvDelimiter);
2951
- const rows = [];
2952
- for (let i = 1; i < lines.length; i++) {
2953
- const values = parseCSVLine(lines[i], csvDelimiter);
2954
- if (values.length !== headers.length) continue;
2955
- const row = {};
2956
- headers.forEach((h, idx) => {
2957
- row[h] = values[idx];
2958
- });
2959
- rows.push(row);
2960
- }
2961
- const sampleKeywords = [
2962
- "sample",
2963
- "name",
2964
- "id",
2965
- "sample_name",
2966
- "samplename",
2967
- "file name",
2968
- "filename",
2969
- "file_name"
2970
- ];
2971
- return {
2972
- columns: headers,
2973
- rows,
2974
- sampleColumn: headers.find((h) => sampleKeywords.includes(h.toLowerCase())) ?? headers[0],
2975
- delimiter: csvDelimiter
2976
- };
2977
- }
2978
- //#endregion
2979
4767
  //#region src/composables/useAutoGroup.ts
2980
4768
  var STRUCTURED_MISSING_GROUP = "Outlier";
2981
4769
  var deprecationWarned = false;
@@ -2986,6 +4774,11 @@ function warnDeprecated(api) {
2986
4774
  console.warn(`[useAutoGroup] ${api} is deprecated; use the new class-first API (setClassDisposition / toggleGroupBy / setValueOps).`);
2987
4775
  deprecationWarned = true;
2988
4776
  }
4777
+ /**
4778
+ * Sample-grouping engine behind `AutoGroupModal`.
4779
+ *
4780
+ * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
4781
+ */
2989
4782
  function useAutoGroup() {
2990
4783
  const inputMode = ref("paste");
2991
4784
  const rawText = ref("");
@@ -3031,13 +4824,14 @@ function useAutoGroup() {
3031
4824
  }
3032
4825
  const tokens = csv.rows.map((r) => nonSampleCols.map((col) => r[col] ?? ""));
3033
4826
  tokenized.value = tokens;
3034
- classes.value = [{
4827
+ const structuredClass = {
3035
4828
  kind: "unknown",
3036
4829
  label: "Unknown",
3037
4830
  members: csv.rows.map((_, i) => i),
3038
4831
  classTagPositions: [],
3039
4832
  disposition: "group"
3040
- }];
4833
+ };
4834
+ classes.value = [structuredClass];
3041
4835
  const schema = buildClassSchema(tokens, "unknown", void 0, []);
3042
4836
  schema.columns = schema.columns.map((c, i) => ({
3043
4837
  ...c,
@@ -3438,6 +5232,11 @@ function useAutoGroup() {
3438
5232
  renameField
3439
5233
  };
3440
5234
  }
5235
+ /**
5236
+ * Reads experiment `design_data` samples into the `useAutoGroup` CSV shape.
5237
+ *
5238
+ * @deprecated Scheduled for removal in MINT 1.4 together with `useAutoGroup`.
5239
+ */
3441
5240
  function extractSamplesFromDesignData(rawData, options = {}) {
3442
5241
  const designData = unwrapExperimentDesignData(rawData);
3443
5242
  if (!designData) return null;
@@ -3538,7 +5337,8 @@ function useFileImport(options = {}) {
3538
5337
  result.value = parsed;
3539
5338
  return parsed;
3540
5339
  } catch (err) {
3541
- error.value = readErrorMessage(err, "Failed to import file.");
5340
+ const message = readErrorMessage(err, "Failed to import file.");
5341
+ error.value = message;
3542
5342
  throw err;
3543
5343
  } finally {
3544
5344
  loading.value = false;
@@ -3645,8 +5445,8 @@ function countChar(value, char) {
3645
5445
  }
3646
5446
  function formatBytes(bytes) {
3647
5447
  if (bytes < 1024) return `${bytes} B`;
3648
- if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
3649
- return `${Math.round(bytes / (1024 * 1024))} MB`;
5448
+ if (bytes < 1048576) return `${Math.round(bytes / 1024)} KB`;
5449
+ return `${Math.round(bytes / 1048576)} MB`;
3650
5450
  }
3651
5451
  //#endregion
3652
5452
  //#region src/utils/sampleGroupPath.ts
@@ -3797,7 +5597,7 @@ function useExpansionSet(options = {}) {
3797
5597
  }
3798
5598
  function expand(id) {
3799
5599
  if (expandedIds.value.has(id)) return;
3800
- expandedIds.value = new Set([...expandedIds.value, id]);
5600
+ expandedIds.value = /* @__PURE__ */ new Set([...expandedIds.value, id]);
3801
5601
  }
3802
5602
  function collapse(id) {
3803
5603
  if (!expandedIds.value.has(id)) return;
@@ -3814,7 +5614,7 @@ function useExpansionSet(options = {}) {
3814
5614
  return true;
3815
5615
  }
3816
5616
  function expandMany(ids) {
3817
- expandedIds.value = new Set([...expandedIds.value, ...normalizeIds(ids)]);
5617
+ expandedIds.value = /* @__PURE__ */ new Set([...expandedIds.value, ...normalizeIds(ids)]);
3818
5618
  }
3819
5619
  function setExpanded(ids) {
3820
5620
  expandedIds.value = new Set(normalizeIds(ids));
@@ -4165,15 +5965,18 @@ function minutesToDate(baseDate, minutes) {
4165
5965
  }
4166
5966
  function clampRange(start, end, min, max) {
4167
5967
  const s = Math.max(start, min);
4168
- const e = Math.min(end, max);
4169
5968
  return {
4170
5969
  start: s,
4171
- end: Math.max(e, s)
5970
+ end: Math.max(Math.min(end, max), s)
4172
5971
  };
4173
5972
  }
4174
5973
  //#endregion
4175
5974
  //#region src/composables/useExperimentSave.ts
4176
- /** Persists experiment design, analysis, and built-in template preset data through the platform API. */
5975
+ /**
5976
+ * Persists experiment design, analysis, and built-in template preset data through the platform API.
5977
+ *
5978
+ * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
5979
+ */
4177
5980
  function useExperimentSave(options = {}) {
4178
5981
  const injectedContext = getInjectedPlatformContext();
4179
5982
  const api = useApi({ baseUrl: options.apiBaseUrl ?? injectedContext?.platformApiUrl });
@@ -4371,7 +6174,11 @@ function useExperimentSave(options = {}) {
4371
6174
  }
4372
6175
  //#endregion
4373
6176
  //#region src/composables/useTemplateCollection.ts
4374
- /** Manages a biology template collection in the current experiment's design_data. */
6177
+ /**
6178
+ * Manages a biology template collection in the current experiment's design_data.
6179
+ *
6180
+ * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
6181
+ */
4375
6182
  function useTemplateCollection(options = {}) {
4376
6183
  const experimentSave = useExperimentSave(options);
4377
6184
  const templateCollection = ref(createInitialCollection(options));
@@ -4467,7 +6274,11 @@ function readCollectionMetadata(value) {
4467
6274
  }
4468
6275
  //#endregion
4469
6276
  //#region src/composables/useBioTemplateControls.ts
4470
- /** Build FormBuilder schema, AppSidebar panels, and defaults for a built-in biology template or preset. */
6277
+ /**
6278
+ * Build FormBuilder schema, AppSidebar panels, and defaults for a built-in biology template or preset.
6279
+ *
6280
+ * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
6281
+ */
4471
6282
  function useBioTemplateControls(target, options = {}) {
4472
6283
  const { initialValues, topBarSettings: _topBarSettings, ...schemaOptions } = options;
4473
6284
  const controls = createBioTemplateControlToolkit(target, schemaOptions);
@@ -4482,15 +6293,27 @@ function useBioTemplateControls(target, options = {}) {
4482
6293
  }
4483
6294
  //#endregion
4484
6295
  //#region src/composables/useBioTemplateComponents.ts
4485
- /** Return component props grouped by SDK component name for direct WellPlate/DoseCalculator binding. */
6296
+ /**
6297
+ * Return component props grouped by SDK component name for direct WellPlate/DoseCalculator binding.
6298
+ *
6299
+ * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
6300
+ */
4486
6301
  function toBioTemplateComponentPropsByComponent(target) {
4487
6302
  return toBioTemplateComponentPropsByComponent$1(target);
4488
6303
  }
4489
- /** Return the first props object for an SDK component, optionally restricted by template id or binding id. */
6304
+ /**
6305
+ * Return the first props object for an SDK component, optionally restricted by template id or binding id.
6306
+ *
6307
+ * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
6308
+ */
4490
6309
  function getBioTemplateComponentProps(target, component, options = {}) {
4491
6310
  return getBioTemplateComponentProps$1(target, component, options);
4492
6311
  }
4493
- /** Map built-in biology templates to SDK components such as WellPlate, DataFrame, DoseCalculator, and PlateMapEditor. */
6312
+ /**
6313
+ * Map built-in biology templates to SDK components such as WellPlate, DataFrame, DoseCalculator, and PlateEditor.
6314
+ *
6315
+ * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
6316
+ */
4494
6317
  function useBioTemplateComponents(target) {
4495
6318
  return {
4496
6319
  bindings: getBioTemplateComponentBindings(target),
@@ -4558,7 +6381,11 @@ function liftWorkspaceComponentBindings(workspace) {
4558
6381
  }
4559
6382
  //#endregion
4560
6383
  //#region src/composables/useBioTemplateWorkspace.ts
4561
- /** One-stop wiring for BioTemplateRenderer, FormBuilder, AppSidebar, and template-aware SDK components. */
6384
+ /**
6385
+ * One-stop wiring for BioTemplateRenderer, FormBuilder, AppSidebar, and template-aware SDK components.
6386
+ *
6387
+ * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
6388
+ */
4562
6389
  function useBioTemplateWorkspace(target, options = {}) {
4563
6390
  const controls = useBioTemplateControls(target, options);
4564
6391
  const workspace = useControlWorkspace(controls.controls, options);
@@ -4615,6 +6442,8 @@ function isTemplateObjectTarget(target) {
4615
6442
  * const pack = useBioTemplatePackWorkspace('cell-culture-screen')
4616
6443
  * await pack.loadCurrent()
4617
6444
  * ```
6445
+ *
6446
+ * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
4618
6447
  */
4619
6448
  function useBioTemplatePackWorkspace(packId, options = {}) {
4620
6449
  const pack = getBioTemplatePackInfo(packId);
@@ -4695,7 +6524,11 @@ function useBioTemplatePackWorkspace(packId, options = {}) {
4695
6524
  }
4696
6525
  //#endregion
4697
6526
  //#region src/composables/useBioTemplatePresetWorkspace.ts
4698
- /** Create a complete editable WellPlate/DoseCalculator workspace for dose design from a built-in biology template preset with current-experiment save wiring. */
6527
+ /**
6528
+ * Create a complete editable WellPlate/DoseCalculator workspace for dose design from a built-in biology template preset with current-experiment save wiring.
6529
+ *
6530
+ * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
6531
+ */
4699
6532
  function useBioTemplatePresetWorkspace(presetId, options = {}) {
4700
6533
  const { controlOptions, initialValues, ...collectionOptions } = options;
4701
6534
  const resolvedControlOptions = mergeInitialValues(controlOptions, initialValues);
@@ -4779,241 +6612,98 @@ function useBioTemplatePresetWorkspace(presetId, options = {}) {
4779
6612
  function applyCollection(value) {
4780
6613
  return templateCollection.apply(value);
4781
6614
  }
4782
- function setActiveControlView(viewId) {
4783
- if (!controlViewIds.value.includes(viewId)) return;
4784
- activeControlView.value = viewId;
4785
- }
4786
- function setControlValues(values) {
4787
- const nextValues = { ...values };
4788
- controlValues.value = nextValues;
4789
- syncControlValueBindings(nextValues);
4790
- return templateCollection.apply(createBioTemplatePresetCollectionFromControls(presetId, nextValues));
4791
- }
4792
- function updateControlValues(values) {
4793
- return setControlValues({
4794
- ...controlValues.value,
4795
- ...values
4796
- });
4797
- }
4798
- function resetToDefaultCollection() {
4799
- return setControlValues({ ...controls.initialValues });
4800
- }
4801
- function syncControlValueBindings(values) {
4802
- form.modelValue = values;
4803
- sidebar.modelValue = values;
4804
- sidebar.values = values;
4805
- topBarSettings.settingsConfig = {
4806
- ...topBarSettings.settingsConfig,
4807
- values
4808
- };
4809
- }
4810
- return {
4811
- presetId,
4812
- presetInfo,
4813
- presetLabel,
4814
- collection,
4815
- templates: templateCollection.templates,
4816
- templateIds: templateCollection.templateIds,
4817
- currentExperimentId: templateCollection.currentExperimentId,
4818
- hasCurrentExperiment: templateCollection.hasCurrentExperiment,
4819
- saving: templateCollection.isSaving,
4820
- error: templateCollection.error,
4821
- lastSavedAt: templateCollection.lastSavedAt,
4822
- controls,
4823
- controlSchema: controls.controls,
4824
- controlValues,
4825
- controlViewIds,
4826
- controlViewItems,
4827
- activeControlView,
4828
- form,
4829
- sidebar,
4830
- topBar: topBarProps,
4831
- pillNav,
4832
- topBarSettings,
4833
- bindings,
4834
- renderer,
4835
- workspace,
4836
- componentBindings,
4837
- componentBindingsById,
4838
- componentImports,
4839
- componentProps,
4840
- componentPropsById,
4841
- componentPropsByComponent,
4842
- componentSnippets,
4843
- componentUsage,
4844
- getComponentProps,
4845
- setActiveControlView,
4846
- setControlValues,
4847
- updateControlValues,
4848
- applyCollection,
4849
- resetToDefaultCollection,
4850
- save: templateCollection.save,
4851
- saveCurrent: templateCollection.saveCurrent
4852
- };
4853
- }
4854
- function humanizePresetId(value) {
4855
- return value.replace(/[-_]+/g, " ").replace(/\b\w/g, (match) => match.toUpperCase());
4856
- }
4857
- function mergeInitialValues(controlOptions, initialValues) {
4858
- if (initialValues === void 0) return controlOptions;
4859
- return {
4860
- ...controlOptions ?? {},
4861
- initialValues: {
4862
- ...controlOptions?.initialValues ?? {},
4863
- ...initialValues
4864
- }
4865
- };
4866
- }
4867
- //#endregion
4868
- //#region src/composables/useRackEditor.ts
4869
- var SLOT_CYCLE = [
4870
- "R",
4871
- "G",
4872
- "B",
4873
- "Y"
4874
- ];
4875
- var globalIdCounter = 0;
4876
- function generateId() {
4877
- globalIdCounter++;
4878
- return `rack-${Date.now()}-${globalIdCounter}`;
4879
- }
4880
- /** Manages a set of sample racks with well-level assignment, series fill, and multi-rack reordering. */
4881
- function useRackEditor(initialRacks, options) {
4882
- const defaultFormat = options?.defaultFormat ?? 54;
4883
- const defaultInjVol = options?.defaultInjectionVolume ?? 5;
4884
- const maxRacks = options?.maxRacks ?? 10;
4885
- const minRacks = options?.minRacks ?? 1;
4886
- function createDefaultRack(name, slotIndex) {
4887
- const slot = SLOT_CYCLE[(slotIndex ?? 0) % SLOT_CYCLE.length];
4888
- return {
4889
- id: generateId(),
4890
- name: name ?? `Rack ${racks.value.length + 1}`,
4891
- format: defaultFormat,
4892
- slot,
4893
- injectionVolume: defaultInjVol,
4894
- wells: {}
4895
- };
4896
- }
4897
- const racks = ref(initialRacks && initialRacks.length > 0 ? initialRacks.map((r) => ({ ...r })) : [createDefaultRack("Rack 1", 0)]);
4898
- const activeRackId = ref(racks.value[0]?.id ?? "");
4899
- const activeRack = computed(() => racks.value.find((r) => r.id === activeRackId.value) ?? racks.value[0]);
4900
- function addRack(name) {
4901
- if (racks.value.length >= maxRacks) return racks.value[racks.value.length - 1];
4902
- const rack = createDefaultRack(name, racks.value.length);
4903
- racks.value.push(rack);
4904
- activeRackId.value = rack.id;
4905
- return rack;
4906
- }
4907
- function removeRack(rackId) {
4908
- if (racks.value.length <= minRacks) return;
4909
- const index = racks.value.findIndex((r) => r.id === rackId);
4910
- if (index === -1) return;
4911
- racks.value.splice(index, 1);
4912
- if (activeRackId.value === rackId && racks.value[0]) activeRackId.value = racks.value[0].id;
4913
- }
4914
- function reorderRacks(fromIndex, toIndex) {
4915
- if (fromIndex < 0 || fromIndex >= racks.value.length) return;
4916
- if (toIndex < 0 || toIndex >= racks.value.length) return;
4917
- if (fromIndex === toIndex) return;
4918
- const [moved] = racks.value.splice(fromIndex, 1);
4919
- if (moved) racks.value.splice(toIndex, 0, moved);
4920
- }
4921
- function updateRack(rackId, data) {
4922
- const rack = racks.value.find((r) => r.id === rackId);
4923
- if (!rack) return;
4924
- if (data.name !== void 0) rack.name = data.name;
4925
- if (data.format !== void 0) rack.format = data.format;
4926
- if (data.slot !== void 0) rack.slot = data.slot;
4927
- if (data.injectionVolume !== void 0) rack.injectionVolume = data.injectionVolume;
4928
- if (data.wells !== void 0) rack.wells = data.wells;
4929
- }
4930
- function setActiveRack(rackId) {
4931
- if (racks.value.some((r) => r.id === rackId)) activeRackId.value = rackId;
4932
- }
4933
- function setWellData(rackId, wellId, data) {
4934
- const rack = racks.value.find((r) => r.id === rackId);
4935
- if (!rack) return;
4936
- rack.wells[wellId] = {
4937
- ...rack.wells[wellId],
4938
- ...data
4939
- };
4940
- }
4941
- function clearWell(rackId, wellId) {
4942
- const rack = racks.value.find((r) => r.id === rackId);
4943
- if (!rack) return;
4944
- delete rack.wells[wellId];
4945
- }
4946
- function clearAllWells(rackId) {
4947
- const rack = racks.value.find((r) => r.id === rackId);
4948
- if (!rack) return;
4949
- rack.wells = {};
4950
- }
4951
- function fillSeries(rackId, prefix = "S") {
4952
- const rack = racks.value.find((r) => r.id === rackId);
4953
- if (!rack) return;
4954
- const config = PLATE_CONFIGS[rack.format];
4955
- if (!config) return;
4956
- let counter = 1;
4957
- for (let row = 0; row < config.rows; row++) for (let col = 0; col < config.cols; col++) {
4958
- const wellId = `${rowLabel(row)}${col + 1}`;
4959
- if (!rack.wells[wellId]?.sampleType) {
4960
- const paddedNum = String(counter).padStart(3, "0");
4961
- rack.wells[wellId] = {
4962
- id: wellId,
4963
- row,
4964
- col,
4965
- state: "filled",
4966
- sampleType: "sample",
4967
- metadata: {
4968
- label: `${prefix}${paddedNum}`,
4969
- injectionVolume: rack.injectionVolume,
4970
- injectionCount: 1
4971
- }
4972
- };
4973
- }
4974
- counter++;
4975
- }
6615
+ function setActiveControlView(viewId) {
6616
+ if (!controlViewIds.value.includes(viewId)) return;
6617
+ activeControlView.value = viewId;
4976
6618
  }
4977
- function getAllWells() {
4978
- const result = [];
4979
- for (const rack of racks.value) for (const [wellId, well] of Object.entries(rack.wells)) result.push({
4980
- rackId: rack.id,
4981
- wellId,
4982
- well
6619
+ function setControlValues(values) {
6620
+ const nextValues = { ...values };
6621
+ controlValues.value = nextValues;
6622
+ syncControlValueBindings(nextValues);
6623
+ return templateCollection.apply(createBioTemplatePresetCollectionFromControls(presetId, nextValues));
6624
+ }
6625
+ function updateControlValues(values) {
6626
+ return setControlValues({
6627
+ ...controlValues.value,
6628
+ ...values
4983
6629
  });
4984
- return result;
4985
6630
  }
4986
- const totalSampleCount = computed(() => {
4987
- let count = 0;
4988
- for (const rack of racks.value) count += Object.keys(rack.wells).length;
4989
- return count;
4990
- });
4991
- function reset() {
4992
- const rack = createDefaultRack("Rack 1", 0);
4993
- racks.value = [rack];
4994
- activeRackId.value = rack.id;
6631
+ function resetToDefaultCollection() {
6632
+ return setControlValues({ ...controls.initialValues });
6633
+ }
6634
+ function syncControlValueBindings(values) {
6635
+ form.modelValue = values;
6636
+ sidebar.modelValue = values;
6637
+ sidebar.values = values;
6638
+ topBarSettings.settingsConfig = {
6639
+ ...topBarSettings.settingsConfig,
6640
+ values
6641
+ };
4995
6642
  }
4996
6643
  return {
4997
- racks,
4998
- activeRack,
4999
- activeRackId,
5000
- addRack,
5001
- removeRack,
5002
- reorderRacks,
5003
- updateRack,
5004
- setActiveRack,
5005
- setWellData,
5006
- clearWell,
5007
- clearAllWells,
5008
- fillSeries,
5009
- getAllWells,
5010
- totalSampleCount,
5011
- reset
6644
+ presetId,
6645
+ presetInfo,
6646
+ presetLabel,
6647
+ collection,
6648
+ templates: templateCollection.templates,
6649
+ templateIds: templateCollection.templateIds,
6650
+ currentExperimentId: templateCollection.currentExperimentId,
6651
+ hasCurrentExperiment: templateCollection.hasCurrentExperiment,
6652
+ saving: templateCollection.isSaving,
6653
+ error: templateCollection.error,
6654
+ lastSavedAt: templateCollection.lastSavedAt,
6655
+ controls,
6656
+ controlSchema: controls.controls,
6657
+ controlValues,
6658
+ controlViewIds,
6659
+ controlViewItems,
6660
+ activeControlView,
6661
+ form,
6662
+ sidebar,
6663
+ topBar: topBarProps,
6664
+ pillNav,
6665
+ topBarSettings,
6666
+ bindings,
6667
+ renderer,
6668
+ workspace,
6669
+ componentBindings,
6670
+ componentBindingsById,
6671
+ componentImports,
6672
+ componentProps,
6673
+ componentPropsById,
6674
+ componentPropsByComponent,
6675
+ componentSnippets,
6676
+ componentUsage,
6677
+ getComponentProps,
6678
+ setActiveControlView,
6679
+ setControlValues,
6680
+ updateControlValues,
6681
+ applyCollection,
6682
+ resetToDefaultCollection,
6683
+ save: templateCollection.save,
6684
+ saveCurrent: templateCollection.saveCurrent
6685
+ };
6686
+ }
6687
+ function humanizePresetId(value) {
6688
+ return value.replace(/[-_]+/g, " ").replace(/\b\w/g, (match) => match.toUpperCase());
6689
+ }
6690
+ function mergeInitialValues(controlOptions, initialValues) {
6691
+ if (initialValues === void 0) return controlOptions;
6692
+ return {
6693
+ ...controlOptions ?? {},
6694
+ initialValues: {
6695
+ ...controlOptions?.initialValues ?? {},
6696
+ ...initialValues
6697
+ }
5012
6698
  };
5013
6699
  }
5014
6700
  //#endregion
5015
6701
  //#region src/composables/useGroupAssignment.ts
5016
- /** Shared two-zone group assignment model for control/treatment style workflows. */
6702
+ /**
6703
+ * Shared two-zone group assignment model for control/treatment style workflows.
6704
+ *
6705
+ * @deprecated Scheduled for removal in MINT 1.4; no SDK replacement.
6706
+ */
5017
6707
  function useGroupAssignment(options) {
5018
6708
  const groups = computed(() => [...toValue(options.groups)]);
5019
6709
  const group1 = computed(() => [...toValue(options.group1)]);
@@ -5619,6 +7309,510 @@ function useProtocolTemplates() {
5619
7309
  };
5620
7310
  }
5621
7311
  //#endregion
7312
+ //#region src/composables/useDebouncedWatch.ts
7313
+ /** Watch a Vue source with debounced callback execution and explicit cancel/flush controls. */
7314
+ function useDebouncedWatch(source, callback, options = {}) {
7315
+ const { delay = 300, ...watchOptions } = options;
7316
+ const isPending = ref(false);
7317
+ let timer = null;
7318
+ let hasLatestValue = false;
7319
+ let latestValue;
7320
+ let latestOldValue;
7321
+ let callbackCleanup = null;
7322
+ let stopped = false;
7323
+ function clearTimer() {
7324
+ if (timer) {
7325
+ clearTimeout(timer);
7326
+ timer = null;
7327
+ }
7328
+ isPending.value = false;
7329
+ }
7330
+ function runCallbackCleanup() {
7331
+ if (!callbackCleanup) return;
7332
+ callbackCleanup();
7333
+ callbackCleanup = null;
7334
+ }
7335
+ function cancel() {
7336
+ clearTimer();
7337
+ hasLatestValue = false;
7338
+ latestOldValue = void 0;
7339
+ }
7340
+ function flush() {
7341
+ if (!hasLatestValue) return;
7342
+ const value = latestValue;
7343
+ const oldValue = latestOldValue;
7344
+ cancel();
7345
+ runCallbackCleanup();
7346
+ callback(value, oldValue, (cleanup) => {
7347
+ callbackCleanup = cleanup;
7348
+ });
7349
+ }
7350
+ function schedule(value, oldValue) {
7351
+ cancel();
7352
+ hasLatestValue = true;
7353
+ latestValue = value;
7354
+ latestOldValue = oldValue;
7355
+ isPending.value = true;
7356
+ timer = setTimeout(flush, delay);
7357
+ }
7358
+ const stopWatch = watch(source, (value, oldValue, onCleanup) => {
7359
+ schedule(value, oldValue);
7360
+ onCleanup(() => {
7361
+ cancel();
7362
+ runCallbackCleanup();
7363
+ });
7364
+ }, watchOptions);
7365
+ function stop() {
7366
+ if (stopped) return;
7367
+ stopped = true;
7368
+ stopWatch();
7369
+ cancel();
7370
+ runCallbackCleanup();
7371
+ }
7372
+ onScopeDispose(stop);
7373
+ return {
7374
+ isPending,
7375
+ cancel,
7376
+ flush,
7377
+ stop
7378
+ };
7379
+ }
7380
+ //#endregion
7381
+ //#region src/composables/useExperimentSelector.ts
7382
+ function getPlatformContext() {
7383
+ if (typeof window === "undefined") return void 0;
7384
+ return window.__MINT_PLATFORM__;
7385
+ }
7386
+ function getPlatformApiUrl() {
7387
+ return getPlatformContext()?.platformApiUrl;
7388
+ }
7389
+ /** Fetches a paginated, filtered experiment list from the platform API for picker and selector UIs. */
7390
+ function useExperimentSelector(options = {}) {
7391
+ const { limit = 100, immediate = false, experimentType, allowedExperimentTypes: optionAllowedExperimentTypes, apiBaseUrl } = options;
7392
+ const platformBase = apiBaseUrl ?? getPlatformApiUrl();
7393
+ const api = useApi();
7394
+ const experiments = ref([]);
7395
+ const total = ref(0);
7396
+ const selectedExperiment = ref(null);
7397
+ const request = useRequestSyncState("Failed to fetch experiments");
7398
+ const isLoading = request.loading;
7399
+ const error = request.error;
7400
+ const lastLoadedAt = request.lastLoadedAt;
7401
+ const page = ref(0);
7402
+ let fetchGeneration = 0;
7403
+ const sortKey = ref("created_at:desc");
7404
+ const experimentTypes = ref([]);
7405
+ const projects = ref([]);
7406
+ let filterOptionsFetched = false;
7407
+ const hasMore = computed(() => experiments.value.length < total.value);
7408
+ const filters = reactive({
7409
+ search: void 0,
7410
+ status: void 0,
7411
+ project: void 0,
7412
+ experimentType: void 0,
7413
+ datePreset: void 0
7414
+ });
7415
+ function normalizeExperimentTypes(values) {
7416
+ if (values == null) return null;
7417
+ return [...new Set(values.filter((value) => typeof value === "string" && value.length > 0))];
7418
+ }
7419
+ function currentAllowedExperimentTypes() {
7420
+ if (optionAllowedExperimentTypes !== void 0) return normalizeExperimentTypes(optionAllowedExperimentTypes);
7421
+ return normalizeExperimentTypes(getPlatformContext()?.allowedExperimentTypes);
7422
+ }
7423
+ function parseSortKey() {
7424
+ const [field, order] = sortKey.value.split(":");
7425
+ return {
7426
+ sortBy: field || "created_at",
7427
+ sortOrder: order || "desc"
7428
+ };
7429
+ }
7430
+ function resolveExperimentTypeScope() {
7431
+ const allowedTypes = currentAllowedExperimentTypes();
7432
+ const selectedType = experimentType ?? filters.experimentType ?? void 0;
7433
+ if (selectedType) {
7434
+ if (allowedTypes && !allowedTypes.includes(selectedType)) return { kind: "blocked" };
7435
+ return {
7436
+ kind: "single",
7437
+ experimentType: selectedType
7438
+ };
7439
+ }
7440
+ if (allowedTypes) {
7441
+ if (allowedTypes.length === 0) return { kind: "blocked" };
7442
+ if (allowedTypes.length === 1) return {
7443
+ kind: "single",
7444
+ experimentType: allowedTypes[0]
7445
+ };
7446
+ return {
7447
+ kind: "multi",
7448
+ experimentTypes: allowedTypes
7449
+ };
7450
+ }
7451
+ return { kind: "all" };
7452
+ }
7453
+ function buildExperimentListUrl(typeFilter, skip, pageLimit) {
7454
+ const params = new URLSearchParams();
7455
+ if (typeFilter) params.set("experiment_type", typeFilter);
7456
+ if (filters.status) params.set("status", filters.status);
7457
+ if (filters.search) params.set("search", filters.search);
7458
+ if (filters.project) params.set("project", filters.project);
7459
+ const { sortBy, sortOrder } = parseSortKey();
7460
+ params.set("sort_by", sortBy);
7461
+ params.set("sort_order", sortOrder);
7462
+ if (filters.datePreset) params.set("created_after", datePresetToISO(filters.datePreset));
7463
+ params.set("limit", String(pageLimit));
7464
+ params.set("skip", String(skip));
7465
+ const query = params.toString();
7466
+ return `${platformBase ?? ""}/experiments${query ? `?${query}` : ""}`;
7467
+ }
7468
+ function sortExperiments(items) {
7469
+ const { sortBy, sortOrder } = parseSortKey();
7470
+ const direction = sortOrder === "desc" ? -1 : 1;
7471
+ return [...items].sort((a, b) => {
7472
+ const aValue = a[sortBy] ?? "";
7473
+ const bValue = b[sortBy] ?? "";
7474
+ if (aValue < bValue) return -1 * direction;
7475
+ if (aValue > bValue) return 1 * direction;
7476
+ return 0;
7477
+ });
7478
+ }
7479
+ async function fetchExperimentList(typeFilter, skip, pageLimit) {
7480
+ return await api.get(buildExperimentListUrl(typeFilter, skip, pageLimit));
7481
+ }
7482
+ async function fetchScopedExperimentPage(requestedPage) {
7483
+ const scope = resolveExperimentTypeScope();
7484
+ const pageSkip = requestedPage * limit;
7485
+ if (scope.kind === "blocked") return {
7486
+ experiments: [],
7487
+ total: 0
7488
+ };
7489
+ if (scope.kind === "multi") {
7490
+ const perTypeLimit = pageSkip + limit;
7491
+ const responses = await Promise.all(scope.experimentTypes.map((type) => fetchExperimentList(type, 0, perTypeLimit)));
7492
+ return {
7493
+ experiments: sortExperiments(responses.flatMap((response) => response.experiments)).slice(pageSkip, pageSkip + limit),
7494
+ total: responses.reduce((sum, response) => sum + response.total, 0)
7495
+ };
7496
+ }
7497
+ return await fetchExperimentList(scope.kind === "single" ? scope.experimentType : void 0, pageSkip, limit);
7498
+ }
7499
+ async function fetchExperiments() {
7500
+ const generation = ++fetchGeneration;
7501
+ const requestedPage = page.value;
7502
+ isLoading.value = true;
7503
+ request.clearError();
7504
+ try {
7505
+ const data = await fetchScopedExperimentPage(requestedPage);
7506
+ if (generation !== fetchGeneration) return;
7507
+ if (requestedPage === 0) experiments.value = data.experiments;
7508
+ else experiments.value = [...experiments.value, ...data.experiments];
7509
+ total.value = data.total;
7510
+ request.markLoaded();
7511
+ } catch (err) {
7512
+ if (generation !== fetchGeneration) return;
7513
+ request.setError(err, "Failed to fetch experiments");
7514
+ if (requestedPage === 0) {
7515
+ experiments.value = [];
7516
+ total.value = 0;
7517
+ }
7518
+ } finally {
7519
+ if (generation === fetchGeneration) isLoading.value = false;
7520
+ }
7521
+ }
7522
+ function invalidateFetch() {
7523
+ fetchGeneration += 1;
7524
+ isLoading.value = false;
7525
+ }
7526
+ async function fetchFilterOptions() {
7527
+ if (filterOptionsFetched) return;
7528
+ filterOptionsFetched = true;
7529
+ const base = platformBase ?? "";
7530
+ const [typesRes, projectsRes] = await Promise.allSettled([api.get(`${base}/experiments/experiment-types`), api.get(`${base}/projects`)]);
7531
+ if (typesRes.status === "fulfilled" && Array.isArray(typesRes.value)) {
7532
+ const allowedTypes = currentAllowedExperimentTypes();
7533
+ const allowedSet = allowedTypes ? new Set(allowedTypes) : null;
7534
+ experimentTypes.value = typesRes.value.filter((t) => !allowedSet || allowedSet.has(t.value)).map((t) => ({
7535
+ value: t.value,
7536
+ label: t.label,
7537
+ color: t.color
7538
+ }));
7539
+ if (filters.experimentType && allowedSet && !allowedSet.has(filters.experimentType)) filters.experimentType = void 0;
7540
+ }
7541
+ if (projectsRes.status === "fulfilled" && projectsRes.value?.projects && Array.isArray(projectsRes.value.projects)) projects.value = projectsRes.value.projects.map((p) => ({
7542
+ value: p.name,
7543
+ label: p.name
7544
+ }));
7545
+ }
7546
+ async function loadMore() {
7547
+ if (!hasMore.value || isLoading.value) return;
7548
+ page.value++;
7549
+ await fetchExperiments();
7550
+ }
7551
+ function reset() {
7552
+ page.value = 0;
7553
+ experiments.value = [];
7554
+ total.value = 0;
7555
+ fetchExperiments();
7556
+ }
7557
+ function select(experiment) {
7558
+ selectedExperiment.value = experiment;
7559
+ }
7560
+ function clear() {
7561
+ invalidateFetch();
7562
+ selectedExperiment.value = null;
7563
+ filters.search = void 0;
7564
+ filters.status = void 0;
7565
+ filters.project = void 0;
7566
+ filters.experimentType = void 0;
7567
+ filters.datePreset = void 0;
7568
+ sortKey.value = "created_at:desc";
7569
+ page.value = 0;
7570
+ request.clearError();
7571
+ }
7572
+ const groupedByProject = computed(() => {
7573
+ const groups = /* @__PURE__ */ new Map();
7574
+ for (const exp of experiments.value) {
7575
+ const key = exp.project_name ?? exp.project ?? "No project";
7576
+ const list = groups.get(key);
7577
+ if (list) list.push(exp);
7578
+ else groups.set(key, [exp]);
7579
+ }
7580
+ return [...groups.entries()].sort(([a], [b]) => {
7581
+ if (a === "No project") return 1;
7582
+ if (b === "No project") return -1;
7583
+ return a.localeCompare(b);
7584
+ });
7585
+ });
7586
+ watch(() => [
7587
+ filters.search,
7588
+ filters.status,
7589
+ filters.project,
7590
+ filters.experimentType,
7591
+ filters.datePreset,
7592
+ sortKey.value
7593
+ ], () => invalidateFetch(), { flush: "sync" });
7594
+ const searchWatch = useDebouncedWatch(() => filters.search, () => {
7595
+ page.value = 0;
7596
+ fetchExperiments();
7597
+ }, { delay: 300 });
7598
+ watch(() => [
7599
+ filters.status,
7600
+ filters.project,
7601
+ filters.experimentType,
7602
+ filters.datePreset,
7603
+ sortKey.value
7604
+ ], () => {
7605
+ searchWatch.cancel();
7606
+ page.value = 0;
7607
+ fetchExperiments();
7608
+ });
7609
+ if (immediate) fetchExperiments();
7610
+ return {
7611
+ experiments,
7612
+ total,
7613
+ selectedExperiment,
7614
+ filters,
7615
+ isLoading,
7616
+ error,
7617
+ lastLoadedAt,
7618
+ page,
7619
+ hasMore,
7620
+ sortKey,
7621
+ experimentTypes,
7622
+ projects,
7623
+ groupedByProject,
7624
+ fetch: fetchExperiments,
7625
+ loadMore,
7626
+ reset,
7627
+ select,
7628
+ clear,
7629
+ fetchFilterOptions
7630
+ };
7631
+ }
7632
+ //#endregion
7633
+ //#region src/composables/useAnalysisArtifacts.ts
7634
+ /**
7635
+ * Lists and manages analysis artifacts through the user-facing platform API.
7636
+ *
7637
+ * @example Recommended
7638
+ * ```ts
7639
+ * const artifacts = useAnalysisArtifacts({ pluginId: 'LEAF' })
7640
+ * await artifacts.list()
7641
+ * ```
7642
+ */
7643
+ function useAnalysisArtifacts(options = {}) {
7644
+ const injectedContext = getInjectedPlatformContext();
7645
+ const api = useApi({ baseUrl: options.apiBaseUrl ?? injectedContext?.platformApiUrl });
7646
+ const currentExperimentId = computed(() => toValue(options.experimentId) ?? resolveCurrentExperimentId(injectedContext));
7647
+ const hasCurrentExperiment = computed(() => currentExperimentId.value !== void 0);
7648
+ const scopePluginId = computed(() => toValue(options.pluginId) ?? null);
7649
+ const request = useRequestSyncState("Analysis artifact request failed.");
7650
+ const fetchedArtifacts = ref([]);
7651
+ const selectedDetail = ref(null);
7652
+ const loadingCount = ref(0);
7653
+ const mutatingCount = ref(0);
7654
+ const isLoading = computed(() => loadingCount.value > 0);
7655
+ const isMutating = computed(() => mutatingCount.value > 0);
7656
+ const search = ref("");
7657
+ const statusFilter = ref("active");
7658
+ const pluginFilter = ref(null);
7659
+ const needsArchived = computed(() => Boolean(toValue(options.includeArchived)) || statusFilter.value !== "active");
7660
+ const artifacts = computed(() => scopePluginId.value ? fetchedArtifacts.value.filter((artifact) => artifact.plugin_id === scopePluginId.value) : fetchedArtifacts.value);
7661
+ const visibleArtifacts = computed(() => {
7662
+ const query = search.value.trim().toLowerCase();
7663
+ return artifacts.value.filter((artifact) => {
7664
+ if (statusFilter.value !== "all" && artifact.status !== statusFilter.value) return false;
7665
+ if (pluginFilter.value && artifact.plugin_id !== pluginFilter.value) return false;
7666
+ if (!query) return true;
7667
+ return artifact.display_name.toLowerCase().includes(query) || artifact.artifact_key.toLowerCase().includes(query);
7668
+ });
7669
+ });
7670
+ let listToken = 0;
7671
+ let detailToken = 0;
7672
+ const hasRequestedList = ref(false);
7673
+ function currentExperimentIdOrError(action) {
7674
+ const id = currentExperimentId.value;
7675
+ if (id === void 0) {
7676
+ request.setError(`No current experiment is selected for ${action}`);
7677
+ return;
7678
+ }
7679
+ return id;
7680
+ }
7681
+ function replaceArtifact(summary) {
7682
+ const index = fetchedArtifacts.value.findIndex((artifact) => artifact.id === summary.id);
7683
+ if (index >= 0) fetchedArtifacts.value.splice(index, 1, summary);
7684
+ }
7685
+ async function list() {
7686
+ const experimentId = currentExperimentIdOrError("listing analysis artifacts");
7687
+ if (experimentId === void 0) {
7688
+ fetchedArtifacts.value = [];
7689
+ return [];
7690
+ }
7691
+ const token = ++listToken;
7692
+ hasRequestedList.value = true;
7693
+ loadingCount.value += 1;
7694
+ request.clearError();
7695
+ try {
7696
+ const data = await api.get(`/experiments/${experimentId}/analysis-artifacts?include_archived=${needsArchived.value}`);
7697
+ if (token !== listToken) return artifacts.value;
7698
+ fetchedArtifacts.value = [...data?.artifacts ?? []];
7699
+ request.markLoaded();
7700
+ return artifacts.value;
7701
+ } catch (err) {
7702
+ if (token !== listToken) return [];
7703
+ request.setError(err);
7704
+ fetchedArtifacts.value = [];
7705
+ return [];
7706
+ } finally {
7707
+ loadingCount.value -= 1;
7708
+ }
7709
+ }
7710
+ async function getDetail(artifactId) {
7711
+ const experimentId = currentExperimentIdOrError("loading an analysis artifact");
7712
+ if (experimentId === void 0) return null;
7713
+ const token = ++detailToken;
7714
+ loadingCount.value += 1;
7715
+ request.clearError();
7716
+ try {
7717
+ const detail = await api.get(`/experiments/${experimentId}/analysis-artifacts/${artifactId}`);
7718
+ if (token !== detailToken) return null;
7719
+ selectedDetail.value = detail;
7720
+ request.markLoaded();
7721
+ return detail;
7722
+ } catch (err) {
7723
+ if (token !== detailToken) return null;
7724
+ request.setError(err);
7725
+ return null;
7726
+ } finally {
7727
+ loadingCount.value -= 1;
7728
+ }
7729
+ }
7730
+ async function resolveTarget(target) {
7731
+ const experimentId = currentExperimentId.value;
7732
+ if (experimentId !== target.experimentId) {
7733
+ request.setError(`Analysis artifact target belongs to experiment ${target.experimentId}, not ${experimentId ?? "the current selection"}.`);
7734
+ return null;
7735
+ }
7736
+ const summary = (await list()).find((artifact) => artifact.plugin_id === target.artifactPluginId && artifact.artifact_key === target.artifactKey && artifact.status === "active");
7737
+ if (!summary) {
7738
+ request.setError(`Analysis artifact ${target.artifactPluginId}/${target.artifactKey} is not available.`);
7739
+ return null;
7740
+ }
7741
+ const detail = await getDetail(summary.id);
7742
+ if (detail && (detail.experiment_id !== target.experimentId || detail.plugin_id !== target.artifactPluginId || detail.artifact_key !== target.artifactKey)) {
7743
+ selectedDetail.value = null;
7744
+ request.setError(`Analysis artifact ${target.artifactPluginId}/${target.artifactKey} did not match the requested identity.`);
7745
+ return null;
7746
+ }
7747
+ return detail;
7748
+ }
7749
+ async function mutate(action, operation) {
7750
+ const experimentId = currentExperimentIdOrError(action);
7751
+ if (experimentId === void 0) return null;
7752
+ mutatingCount.value += 1;
7753
+ request.clearError();
7754
+ try {
7755
+ const summary = await operation(experimentId);
7756
+ replaceArtifact(summary);
7757
+ request.markSaved();
7758
+ return summary;
7759
+ } catch (err) {
7760
+ request.setError(err);
7761
+ return null;
7762
+ } finally {
7763
+ mutatingCount.value -= 1;
7764
+ }
7765
+ }
7766
+ async function updateMetadata(artifactId, updates) {
7767
+ return mutate("updating artifact metadata", (experimentId) => api.patch(`/experiments/${experimentId}/analysis-artifacts/${artifactId}`, updates));
7768
+ }
7769
+ async function archive(artifactId) {
7770
+ return mutate("archiving an analysis artifact", (experimentId) => api.post(`/experiments/${experimentId}/analysis-artifacts/${artifactId}/archive`));
7771
+ }
7772
+ async function restore(artifactId) {
7773
+ return mutate("restoring an analysis artifact", (experimentId) => api.post(`/experiments/${experimentId}/analysis-artifacts/${artifactId}/restore`));
7774
+ }
7775
+ function hasArtifactKey(artifactKey) {
7776
+ return artifacts.value.some((artifact) => artifact.artifact_key === artifactKey);
7777
+ }
7778
+ /** Drop experiment-scoped state and orphan every in-flight response. */
7779
+ function clear() {
7780
+ listToken += 1;
7781
+ detailToken += 1;
7782
+ fetchedArtifacts.value = [];
7783
+ selectedDetail.value = null;
7784
+ request.clearError();
7785
+ }
7786
+ watch([currentExperimentId, needsArchived], ([experimentId], [previousExperimentId]) => {
7787
+ if (experimentId !== previousExperimentId) clear();
7788
+ if (experimentId === void 0) return;
7789
+ if (options.immediate || hasRequestedList.value) list();
7790
+ }, { immediate: options.immediate });
7791
+ return {
7792
+ artifacts,
7793
+ visibleArtifacts,
7794
+ selectedDetail,
7795
+ currentExperimentId,
7796
+ hasCurrentExperiment,
7797
+ isLoading,
7798
+ isMutating,
7799
+ error: request.error,
7800
+ lastLoadedAt: request.lastLoadedAt,
7801
+ search,
7802
+ statusFilter,
7803
+ pluginFilter,
7804
+ list,
7805
+ refresh: list,
7806
+ getDetail,
7807
+ resolveTarget,
7808
+ updateMetadata,
7809
+ archive,
7810
+ restore,
7811
+ hasArtifactKey,
7812
+ clear
7813
+ };
7814
+ }
7815
+ //#endregion
5622
7816
  //#region src/composables/filePicker/validation.ts
5623
7817
  function normalizePickerFormat(format) {
5624
7818
  return format.trim().replace(/^\./, "").toLowerCase();
@@ -5664,6 +7858,6 @@ function pickerFormat(name) {
5664
7858
  return compound ? `${compound[1]}.gz` : name.includes(".") ? name.split(".").pop() : "";
5665
7859
  }
5666
7860
  //#endregion
5667
- export { getInjectedFrontendRevision as $, useFileImport as A, REORDER_ANNOUNCER_STYLE as B, useExpansionSet as C, detectDelimiter as D, getSampleGroupParentPath as E, extractSampleNamesFromDesignData as F, combinePluginJobSources as G, useListReorder as H, extractSampleOptionsFromDesignData as I, usePluginWorkspace as J, useJobsStatusTray as K, unwrapExperimentDesignData as L, extractSamplesFromDesignData as M, useAutoGroup as N, parseDelimitedText as O, parseCSV as P, useAppExperiment as Q, DEFAULT_COLORS as R, useExperimentData as S, getSampleGroupMajorPrefix as T, useReorderAnnouncer as U, reorderList as V, useDoseCalculator as W, useMobileSupportGate as X, DEFAULT_MOBILE_VIEWPORT_QUERY as Y, APP_EXPERIMENT_KEY as Z, useBioTemplateControls as _, DEFAULT_PRESETS as a, candidateMatchesSearch as at, useScheduleDrag as b, useReagentSeries as c, compareSortValues as ct, useBioTemplatePresetWorkspace as d, useRovingFocus as dt, useRuntimeAlignment as et, useBioTemplatePackWorkspace as f, useBioTemplateComponents as g, toBioTemplateComponentPropsByComponent as h, useProtocolTemplates as i, useToast as it, validateImportFile as j, readFileAsText as k, useGroupAssignment as l, useSortedItems as lt, getBioTemplateComponentProps as m, pickerFormat as n, usePresenceHeartbeat as nt, DEFAULT_UNITS as o, normalizeSearchQuery as ot, useBioTemplateWorkspace as p, usePluginJobCenter as q, pickerReason as r, useTheme as rt, generateDilutionSeries as s, useTextSearch as st, pickerExclusion as t, getPresenceClientId as tt, useRackEditor as u, useMenuKeyboard as ut, useTemplateCollection as v, useSampleGroups as w, useExperimentSamples as x, useExperimentSave as y, useWellPlateEditor as z };
7861
+ export { pluginFormDataFromPayload as $, parseDelimitedText as A, getPresenceClientId as At, useRackEditor as B, useRovingFocus as Bt, useExperimentSamples as C, controlValuesToComponentBindingsById as Ct, getSampleGroupMajorPrefix as D, useAppExperiment as Dt, useSampleGroups as E, APP_EXPERIMENT_KEY as Et, useAutoGroup as F, candidateMatchesSearch as Ft, useDoseCalculator as G, reorderList as H, extractSampleNamesFromDesignData as I, normalizeSearchQuery as It, usePluginJobCenter as J, combinePluginJobSources as K, extractSampleOptionsFromDesignData as L, useTextSearch as Lt, useFileImport as M, usePlatformContext as Mt, validateImportFile as N, useTheme as Nt, getSampleGroupParentPath as O, getInjectedFrontendRevision as Ot, extractSamplesFromDesignData as P, useToast as Pt, getPluginPageSelectorItems as Q, unwrapExperimentDesignData as R, compareSortValues as Rt, useScheduleDrag as S, controlValuesToComponentBindings as St, useExpansionSet as T, mergeControlWorkspaceOptions as Tt, useListReorder as U, REORDER_ANNOUNCER_STYLE as V, useReorderAnnouncer as W, downloadBlob as X, createPluginClient as Y, downloadPluginEndpoint as Z, toBioTemplateComponentPropsByComponent as _, defineWellPlateDoseComponentBindings as _t, useExperimentSelector as a, buildPluginEndpointUrl as at, useTemplateCollection as b, defineControlModel as bt, DEFAULT_PRESETS as c, usePluginWorkspace as ct, useReagentSeries as d, useFormBuilder as dt, uploadPluginEndpoint as et, useGroupAssignment as f, evaluateCondition as ft, getBioTemplateComponentProps as g, defineWellPlateControlProps as gt, useBioTemplateWorkspace as h, defineDoseDesignControlModel as ht, useAnalysisArtifacts as i, usePluginSettings as it, readFileAsText as j, usePresenceHeartbeat as jt, detectDelimiter as k, useRuntimeAlignment as kt, DEFAULT_UNITS as l, DEFAULT_MOBILE_VIEWPORT_QUERY as lt, useBioTemplatePackWorkspace as m, defineDoseCalculatorControlProps as mt, pickerFormat as n, usePluginClient as nt, useDebouncedWatch as o, resolvePluginBaseUrl as ot, useBioTemplatePresetWorkspace as p, useForm as pt, useJobsStatusTray as q, pickerReason as r, usePluginEventStream as rt, useProtocolTemplates as s, useRequestSyncState as st, pickerExclusion as t, useCurrentExperiment as tt, generateDilutionSeries as u, useMobileSupportGate as ut, useBioTemplateComponents as v, defineWellPlateDoseControlProps as vt, useExperimentData as w, controlValuesToComponentProps as wt, useExperimentSave as x, resolveControlModel as xt, useBioTemplateControls as y, useControlWorkspace as yt, DEFAULT_COLORS as z, useSortedItems as zt };
5668
7862
 
5669
- //# sourceMappingURL=validation-ChMyixAf.js.map
7863
+ //# sourceMappingURL=validation-Dmp9Q5W0.js.map