@morscherlab/mint-sdk 1.0.0-beta.2 → 1.0.0-beta.3

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 (399) hide show
  1. package/README.md +218 -6
  2. package/dist/__tests__/components/ActionItem.test.d.ts +1 -0
  3. package/dist/__tests__/components/AppAvatarMenu.test.d.ts +1 -0
  4. package/dist/__tests__/components/AppPageSelector.test.d.ts +1 -0
  5. package/dist/__tests__/components/AppPillNav.test.d.ts +1 -0
  6. package/dist/__tests__/components/AppPluginSwitcher.test.d.ts +1 -0
  7. package/dist/__tests__/components/AppToastContainer.test.d.ts +1 -0
  8. package/dist/__tests__/components/BaseRadioGroup.test.d.ts +1 -0
  9. package/dist/__tests__/components/BaseSelect.test.d.ts +1 -0
  10. package/dist/__tests__/components/BaseTabs.test.d.ts +1 -0
  11. package/dist/__tests__/components/BatchProgressList.test.d.ts +1 -0
  12. package/dist/__tests__/components/BioTemplateExperimentWorkspaceView.test.d.ts +1 -0
  13. package/dist/__tests__/components/BioTemplatePackWorkspaceView.test.d.ts +1 -0
  14. package/dist/__tests__/components/BioTemplatePresetWorkspaceView.test.d.ts +1 -0
  15. package/dist/__tests__/components/BioTemplateRenderer.test.d.ts +1 -0
  16. package/dist/__tests__/components/Breadcrumb.test.d.ts +1 -0
  17. package/dist/__tests__/components/CalendarGridPanel.test.d.ts +1 -0
  18. package/dist/__tests__/components/ConcentrationInput.test.d.ts +1 -0
  19. package/dist/__tests__/components/ControlWorkspaceView.test.d.ts +1 -0
  20. package/dist/__tests__/components/DatePicker.test.d.ts +1 -0
  21. package/dist/__tests__/components/DateTimePicker.test.d.ts +1 -0
  22. package/dist/__tests__/components/EmptyState.test.d.ts +1 -0
  23. package/dist/__tests__/components/ExperimentPopover.test.d.ts +1 -0
  24. package/dist/__tests__/components/FormBuilder.test.d.ts +1 -0
  25. package/dist/__tests__/components/FormCompatibility.test.d.ts +1 -0
  26. package/dist/__tests__/components/GroupAssigner.test.d.ts +1 -0
  27. package/dist/__tests__/components/GroupingModal.test.d.ts +1 -0
  28. package/dist/__tests__/components/MultiSelect.test.d.ts +1 -0
  29. package/dist/__tests__/components/ProtocolStepEditor.test.d.ts +1 -0
  30. package/dist/__tests__/components/ReagentList.test.d.ts +1 -0
  31. package/dist/__tests__/components/SampleHierarchyTree.test.d.ts +1 -0
  32. package/dist/__tests__/components/SampleSelector.test.d.ts +1 -0
  33. package/dist/__tests__/components/SegmentedControl.test.d.ts +1 -0
  34. package/dist/__tests__/components/SettingsButton.test.d.ts +1 -0
  35. package/dist/__tests__/components/SettingsModal.test.d.ts +1 -0
  36. package/dist/__tests__/components/TagsInput.test.d.ts +1 -0
  37. package/dist/__tests__/components/ThemeToggle.test.d.ts +1 -0
  38. package/dist/__tests__/components/TimePicker.test.d.ts +1 -0
  39. package/dist/__tests__/composables/useBioTemplatePackWorkspace.test.d.ts +1 -0
  40. package/dist/__tests__/composables/useBioTemplatePresetWorkspace.test.d.ts +1 -0
  41. package/dist/__tests__/composables/useBioTemplateWorkspace.test.d.ts +1 -0
  42. package/dist/__tests__/composables/useCalendarGrid.test.d.ts +1 -0
  43. package/dist/__tests__/composables/useControlSchema.test.d.ts +1 -0
  44. package/dist/__tests__/composables/useDebouncedWatch.test.d.ts +1 -0
  45. package/dist/__tests__/composables/useDropdownState.test.d.ts +1 -0
  46. package/dist/__tests__/composables/useEventListener.test.d.ts +1 -0
  47. package/dist/__tests__/composables/useExpansionSet.test.d.ts +1 -0
  48. package/dist/__tests__/composables/useExperimentData.test.d.ts +1 -0
  49. package/dist/__tests__/composables/useExperimentSelector.test.d.ts +1 -0
  50. package/dist/__tests__/composables/useGroupAssignment.test.d.ts +1 -0
  51. package/dist/__tests__/composables/useListSelection.test.d.ts +1 -0
  52. package/dist/__tests__/composables/usePluginClient.test.d.ts +1 -0
  53. package/dist/__tests__/composables/usePluginConfig.test.d.ts +1 -0
  54. package/dist/__tests__/composables/useRequestSyncState.test.d.ts +1 -0
  55. package/dist/__tests__/composables/useSampleGroups.test.d.ts +1 -0
  56. package/dist/__tests__/composables/useSelectionLimit.test.d.ts +1 -0
  57. package/dist/__tests__/composables/useSortedItems.test.d.ts +1 -0
  58. package/dist/__tests__/composables/useTemplateCollection.test.d.ts +1 -0
  59. package/dist/__tests__/composables/useTextSearch.test.d.ts +1 -0
  60. package/dist/__tests__/composables/useTheme.test.d.ts +1 -0
  61. package/dist/__tests__/composables/useTimeUtils.test.d.ts +1 -0
  62. package/dist/__tests__/docs/frontendDocsCatalog.test.d.ts +1 -0
  63. package/dist/__tests__/templates/templates.test.d.ts +1 -0
  64. package/dist/{auth-DsI0rQ7_.js → auth-QQj2kkze.js} +12 -5
  65. package/dist/auth-QQj2kkze.js.map +1 -0
  66. package/dist/components/ActionItem.vue.d.ts +32 -0
  67. package/dist/components/AppAvatarMenu.vue.d.ts +2 -7
  68. package/dist/components/AppPageSelector.vue.d.ts +3 -6
  69. package/dist/components/AppPillNav.vue.d.ts +2 -2
  70. package/dist/components/AppSidebar.vue.d.ts +56 -3
  71. package/dist/components/AppToastContainer.vue.d.ts +2 -0
  72. package/dist/components/AppTopBar.vue.d.ts +41 -10
  73. package/dist/components/BaseButton.vue.d.ts +1 -1
  74. package/dist/components/BaseCheckbox.vue.d.ts +1 -1
  75. package/dist/components/BaseInput.vue.d.ts +2 -2
  76. package/dist/components/BasePill.vue.d.ts +2 -2
  77. package/dist/components/BaseRadioGroup.vue.d.ts +3 -3
  78. package/dist/components/BaseSelect.vue.d.ts +3 -3
  79. package/dist/components/BaseTabs.vue.d.ts +2 -2
  80. package/dist/components/BaseTextarea.vue.d.ts +1 -1
  81. package/dist/components/BaseToggle.vue.d.ts +1 -1
  82. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +117 -0
  83. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +92 -0
  84. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +82 -0
  85. package/dist/components/BioTemplateRenderer.vue.d.ts +29 -0
  86. package/dist/components/Breadcrumb.vue.d.ts +2 -2
  87. package/dist/components/Calendar.vue.d.ts +1 -1
  88. package/dist/components/CalendarGridPanel.vue.d.ts +25 -0
  89. package/dist/components/CollapsibleCard.vue.d.ts +1 -1
  90. package/dist/components/ConcentrationInput.vue.d.ts +2 -2
  91. package/dist/components/ConfirmDialog.vue.d.ts +2 -2
  92. package/dist/components/ControlWorkspaceView.vue.d.ts +130 -0
  93. package/dist/components/DatePicker.vue.d.ts +1 -1
  94. package/dist/components/DateTimePicker.vue.d.ts +3 -3
  95. package/dist/components/Divider.vue.d.ts +1 -1
  96. package/dist/components/DropdownButton.vue.d.ts +3 -3
  97. package/dist/components/EmptyState.vue.d.ts +1 -2
  98. package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
  99. package/dist/components/ExperimentTimeline.vue.d.ts +2 -2
  100. package/dist/components/FileUploader.vue.d.ts +1 -1
  101. package/dist/components/FitPanel.vue.d.ts +1 -1
  102. package/dist/components/FormActions.vue.d.ts +4 -4
  103. package/dist/components/FormBuilder.vue.d.ts +22 -8
  104. package/dist/components/FormFieldRenderer.vue.d.ts +7 -10
  105. package/dist/components/FormSection.vue.d.ts +11 -24
  106. package/dist/components/FormulaInput.vue.d.ts +2 -2
  107. package/dist/components/MoleculeInput.vue.d.ts +2 -2
  108. package/dist/components/MultiSelect.vue.d.ts +3 -3
  109. package/dist/components/NumberInput.vue.d.ts +1 -1
  110. package/dist/components/ProgressBar.vue.d.ts +1 -1
  111. package/dist/components/ProtocolStepEditor.vue.d.ts +3 -1
  112. package/dist/components/RackEditor.vue.d.ts +2 -2
  113. package/dist/components/SampleLegend.vue.d.ts +2 -2
  114. package/dist/components/ScheduleCalendar.vue.d.ts +2 -2
  115. package/dist/components/SegmentedControl.vue.d.ts +2 -2
  116. package/dist/components/SequenceInput.vue.d.ts +3 -3
  117. package/dist/components/SettingsButton.vue.d.ts +2 -2
  118. package/dist/components/SettingsModal.vue.d.ts +13 -5
  119. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  120. package/dist/components/TagsInput.vue.d.ts +3 -2
  121. package/dist/components/TimePicker.vue.d.ts +3 -3
  122. package/dist/components/TimeRangeInput.vue.d.ts +1 -1
  123. package/dist/components/UnitInput.vue.d.ts +2 -2
  124. package/dist/components/WellPlate.vue.d.ts +8 -8
  125. package/dist/components/index.d.ts +11 -1
  126. package/dist/components/index.js +3 -3
  127. package/dist/components/internal/FormFieldRendererInternal.vue.d.ts +31 -0
  128. package/dist/components/internal/FormSectionRenderer.vue.d.ts +43 -0
  129. package/dist/{components-_XqPEhP9.js → components-D_Sr0adg.js} +7290 -6518
  130. package/dist/components-D_Sr0adg.js.map +1 -0
  131. package/dist/composables/index.d.ts +21 -2
  132. package/dist/composables/index.js +4 -3
  133. package/dist/composables/platformContextHelpers.d.ts +14 -0
  134. package/dist/composables/useBioTemplateComponents.d.ts +20 -0
  135. package/dist/composables/useBioTemplateControls.d.ts +6 -0
  136. package/dist/composables/useBioTemplatePackWorkspace.d.ts +45 -0
  137. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +74 -0
  138. package/dist/composables/useBioTemplateWorkspace.d.ts +50 -0
  139. package/dist/composables/useCalendarGrid.d.ts +26 -0
  140. package/dist/composables/useControlSchema.d.ts +321 -0
  141. package/dist/composables/useDebouncedWatch.d.ts +20 -0
  142. package/dist/composables/useDropdownState.d.ts +19 -0
  143. package/dist/composables/useEventListener.d.ts +13 -0
  144. package/dist/composables/useExpansionSet.d.ts +21 -0
  145. package/dist/composables/useExperimentData.d.ts +10 -0
  146. package/dist/composables/useExperimentSave.d.ts +31 -2
  147. package/dist/composables/useExperimentSelector.d.ts +20 -0
  148. package/dist/composables/useForm.d.ts +2 -0
  149. package/dist/composables/useGroupAssignment.d.ts +31 -0
  150. package/dist/composables/useListSelection.d.ts +35 -0
  151. package/dist/composables/usePlatformContext.d.ts +21 -3
  152. package/dist/composables/usePluginApi.d.ts +7 -14
  153. package/dist/composables/usePluginClient.d.ts +109 -0
  154. package/dist/composables/usePluginConfig.d.ts +12 -0
  155. package/dist/composables/useRequestSyncState.d.ts +34 -0
  156. package/dist/composables/useSampleGroups.d.ts +32 -0
  157. package/dist/composables/useSelectionLimit.d.ts +17 -0
  158. package/dist/composables/useSortedItems.d.ts +32 -0
  159. package/dist/composables/useTemplateCollection.d.ts +58 -0
  160. package/dist/composables/useTextSearch.d.ts +18 -0
  161. package/dist/composables/useTimeUtils.d.ts +8 -0
  162. package/dist/{composables-tiZqLu1M.js → composables-C3dpXQN5.js} +228 -146
  163. package/dist/composables-C3dpXQN5.js.map +1 -0
  164. package/dist/index.d.ts +12 -3
  165. package/dist/index.js +6 -5
  166. package/dist/install.d.ts +7 -2
  167. package/dist/install.js +2 -2
  168. package/dist/install.js.map +1 -1
  169. package/dist/stores/index.js +1 -1
  170. package/dist/stores/settings.d.ts +4 -1
  171. package/dist/styles.css +5235 -5977
  172. package/dist/templates/adapters.d.ts +43 -0
  173. package/dist/templates/builders.d.ts +63 -0
  174. package/dist/templates/catalog.d.ts +188 -0
  175. package/dist/templates/componentBindings.d.ts +58 -0
  176. package/dist/templates/controlSchemas.d.ts +25 -0
  177. package/dist/templates/index.d.ts +15 -0
  178. package/dist/templates/index.js +2 -0
  179. package/dist/templates/lookup.d.ts +4 -0
  180. package/dist/templates/packs.d.ts +18 -0
  181. package/dist/templates/presets.d.ts +90 -0
  182. package/dist/templates/types.d.ts +531 -0
  183. package/dist/templates-50NPjaxL.js +9333 -0
  184. package/dist/templates-50NPjaxL.js.map +1 -0
  185. package/dist/types/components.d.ts +26 -4
  186. package/dist/types/form-builder.d.ts +6 -8
  187. package/dist/types/index.d.ts +2 -2
  188. package/dist/types/platform.d.ts +7 -1
  189. package/dist/useScheduleDrag-D4oWdh41.js +4371 -0
  190. package/dist/useScheduleDrag-D4oWdh41.js.map +1 -0
  191. package/dist/utils/formModelSync.d.ts +5 -0
  192. package/dist/utils/items.d.ts +8 -0
  193. package/dist/utils/options.d.ts +6 -0
  194. package/dist/utils/pluginIcon.d.ts +9 -0
  195. package/package.json +7 -2
  196. package/src/__tests__/components/ActionItem.test.ts +99 -0
  197. package/src/__tests__/components/AppAvatarMenu.test.ts +27 -0
  198. package/src/__tests__/components/AppPageSelector.test.ts +134 -0
  199. package/src/__tests__/components/AppPillNav.test.ts +78 -0
  200. package/src/__tests__/components/AppPluginSwitcher.test.ts +44 -0
  201. package/src/__tests__/components/AppSidebar.test.ts +370 -0
  202. package/src/__tests__/components/AppToastContainer.test.ts +48 -0
  203. package/src/__tests__/components/AppTopBar.test.ts +383 -0
  204. package/src/__tests__/components/BaseRadioGroup.test.ts +25 -0
  205. package/src/__tests__/components/BaseSelect.test.ts +21 -0
  206. package/src/__tests__/components/BaseTabs.test.ts +25 -0
  207. package/src/__tests__/components/BatchProgressList.test.ts +52 -0
  208. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +153 -0
  209. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +161 -0
  210. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +281 -0
  211. package/src/__tests__/components/BioTemplateRenderer.test.ts +71 -0
  212. package/src/__tests__/components/Breadcrumb.test.ts +23 -0
  213. package/src/__tests__/components/CalendarGridPanel.test.ts +36 -0
  214. package/src/__tests__/components/ConcentrationInput.test.ts +45 -0
  215. package/src/__tests__/components/ControlWorkspaceView.test.ts +1031 -0
  216. package/src/__tests__/components/DataFrame.test.ts +11 -0
  217. package/src/__tests__/components/DatePicker.test.ts +45 -0
  218. package/src/__tests__/components/DateTimePicker.test.ts +48 -0
  219. package/src/__tests__/components/DropdownButton.test.ts +23 -0
  220. package/src/__tests__/components/EmptyState.test.ts +23 -0
  221. package/src/__tests__/components/ExperimentPopover.test.ts +56 -0
  222. package/src/__tests__/components/FormBuilder.test.ts +296 -0
  223. package/src/__tests__/components/FormCompatibility.test.ts +94 -0
  224. package/src/__tests__/components/GroupAssigner.test.ts +30 -0
  225. package/src/__tests__/components/GroupingModal.test.ts +73 -0
  226. package/src/__tests__/components/MultiSelect.test.ts +48 -0
  227. package/src/__tests__/components/ProtocolStepEditor.test.ts +33 -0
  228. package/src/__tests__/components/ReagentList.test.ts +82 -0
  229. package/src/__tests__/components/SampleHierarchyTree.test.ts +53 -0
  230. package/src/__tests__/components/SampleSelector.test.ts +60 -0
  231. package/src/__tests__/components/SegmentedControl.test.ts +24 -0
  232. package/src/__tests__/components/SettingsButton.test.ts +44 -0
  233. package/src/__tests__/components/SettingsModal.test.ts +296 -0
  234. package/src/__tests__/components/TagsInput.test.ts +75 -0
  235. package/src/__tests__/components/ThemeToggle.test.ts +47 -0
  236. package/src/__tests__/components/TimePicker.test.ts +38 -0
  237. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +122 -0
  238. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +199 -0
  239. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +99 -0
  240. package/src/__tests__/composables/useCalendarGrid.test.ts +38 -0
  241. package/src/__tests__/composables/useControlSchema.test.ts +919 -0
  242. package/src/__tests__/composables/useDebouncedWatch.test.ts +93 -0
  243. package/src/__tests__/composables/useDropdownState.test.ts +95 -0
  244. package/src/__tests__/composables/useEventListener.test.ts +116 -0
  245. package/src/__tests__/composables/useExpansionSet.test.ts +62 -0
  246. package/src/__tests__/composables/useExperimentData.test.ts +4 -0
  247. package/src/__tests__/composables/useExperimentSave.test.ts +203 -8
  248. package/src/__tests__/composables/useExperimentSelector.test.ts +164 -0
  249. package/src/__tests__/composables/useForm.test.ts +58 -0
  250. package/src/__tests__/composables/useFormBuilder.test.ts +77 -0
  251. package/src/__tests__/composables/useGroupAssignment.test.ts +73 -0
  252. package/src/__tests__/composables/useListSelection.test.ts +66 -0
  253. package/src/__tests__/composables/usePluginClient.test.ts +444 -0
  254. package/src/__tests__/composables/usePluginConfig.test.ts +5 -0
  255. package/src/__tests__/composables/useRequestSyncState.test.ts +92 -0
  256. package/src/__tests__/composables/useSampleGroups.test.ts +66 -0
  257. package/src/__tests__/composables/useSelectionLimit.test.ts +41 -0
  258. package/src/__tests__/composables/useSortedItems.test.ts +87 -0
  259. package/src/__tests__/composables/useTemplateCollection.test.ts +147 -0
  260. package/src/__tests__/composables/useTextSearch.test.ts +55 -0
  261. package/src/__tests__/composables/useTheme.test.ts +91 -0
  262. package/src/__tests__/composables/useTimeUtils.test.ts +35 -0
  263. package/src/__tests__/docs/frontendDocsCatalog.test.ts +229 -0
  264. package/src/__tests__/fixtures/templates/dose-response.json +81 -0
  265. package/src/__tests__/fixtures/templates/plate-map.json +54 -0
  266. package/src/__tests__/fixtures/templates/qpcr-plate.json +96 -0
  267. package/src/__tests__/fixtures/templates/sample-sheet.json +71 -0
  268. package/src/__tests__/templates/templates.test.ts +1043 -0
  269. package/src/components/ActionItem.vue +82 -0
  270. package/src/components/AppAvatarMenu.vue +15 -69
  271. package/src/components/AppLayout.story.vue +25 -25
  272. package/src/components/AppPageSelector.vue +63 -94
  273. package/src/components/AppPillNav.vue +44 -39
  274. package/src/components/AppPluginSwitcher.vue +41 -145
  275. package/src/components/AppSidebar.story.vue +94 -0
  276. package/src/components/AppSidebar.vue +187 -12
  277. package/src/components/{ToastNotification.story.vue → AppToastContainer.story.vue} +6 -6
  278. package/src/components/AppToastContainer.vue +62 -0
  279. package/src/components/AppTopBar.story.vue +7 -30
  280. package/src/components/AppTopBar.vue +251 -57
  281. package/src/components/BaseModal.vue +3 -5
  282. package/src/components/BaseRadioGroup.vue +7 -3
  283. package/src/components/BaseSelect.vue +11 -7
  284. package/src/components/BaseTabs.vue +6 -4
  285. package/src/components/BatchProgressList.vue +5 -8
  286. package/src/components/BioTemplateExperimentWorkspaceView.story.vue +123 -0
  287. package/src/components/BioTemplateExperimentWorkspaceView.vue +337 -0
  288. package/src/components/BioTemplatePackWorkspaceView.story.vue +107 -0
  289. package/src/components/BioTemplatePackWorkspaceView.vue +176 -0
  290. package/src/components/BioTemplatePresetWorkspaceView.story.vue +151 -0
  291. package/src/components/BioTemplatePresetWorkspaceView.vue +392 -0
  292. package/src/components/BioTemplateRenderer.story.vue +57 -0
  293. package/src/components/BioTemplateRenderer.vue +269 -0
  294. package/src/components/Breadcrumb.vue +14 -8
  295. package/src/components/CalendarGridPanel.vue +120 -0
  296. package/src/components/ConcentrationInput.vue +27 -64
  297. package/src/components/ControlWorkspaceView.story.vue +336 -0
  298. package/src/components/ControlWorkspaceView.vue +347 -0
  299. package/src/components/DataFrame.vue +34 -50
  300. package/src/components/DatePicker.vue +59 -192
  301. package/src/components/DateTimePicker.vue +50 -171
  302. package/src/components/DropdownButton.vue +14 -32
  303. package/src/components/EmptyState.vue +4 -2
  304. package/src/components/ExperimentPopover.vue +5 -22
  305. package/src/components/FormBuilder.vue +124 -27
  306. package/src/components/FormFieldRenderer.vue +15 -38
  307. package/src/components/FormSection.vue +20 -73
  308. package/src/components/GroupAssigner.vue +24 -56
  309. package/src/components/GroupingModal.story.vue +3 -3
  310. package/src/components/GroupingModal.vue +30 -391
  311. package/src/components/MultiSelect.vue +17 -12
  312. package/src/components/PlateMapEditor.vue +3 -8
  313. package/src/components/PluginIcon.vue +2 -22
  314. package/src/components/ProtocolStepEditor.vue +13 -22
  315. package/src/components/ReagentList.vue +25 -33
  316. package/src/components/SampleHierarchyTree.vue +12 -23
  317. package/src/components/SampleSelector.vue +42 -122
  318. package/src/components/SegmentedControl.vue +7 -3
  319. package/src/components/SettingsButton.story.vue +1 -1
  320. package/src/components/SettingsButton.vue +15 -27
  321. package/src/components/SettingsModal.story.vue +1 -1
  322. package/src/components/SettingsModal.vue +120 -29
  323. package/src/components/TagsInput.vue +29 -14
  324. package/src/components/ThemeToggle.vue +9 -7
  325. package/src/components/TimePicker.vue +19 -41
  326. package/src/components/ToastNotification.vue +4 -57
  327. package/src/components/Tooltip.vue +7 -12
  328. package/src/components/WellEditPopup.vue +3 -8
  329. package/src/components/WellPlate.vue +4 -10
  330. package/src/components/index.ts +11 -1
  331. package/src/components/internal/FormFieldRendererInternal.vue +50 -0
  332. package/src/components/internal/FormSectionRenderer.vue +78 -0
  333. package/src/composables/index.ts +212 -0
  334. package/src/composables/platformContextHelpers.ts +74 -0
  335. package/src/composables/useBioTemplateComponents.ts +93 -0
  336. package/src/composables/useBioTemplateControls.ts +41 -0
  337. package/src/composables/useBioTemplatePackWorkspace.ts +181 -0
  338. package/src/composables/useBioTemplatePresetWorkspace.ts +337 -0
  339. package/src/composables/useBioTemplateWorkspace.ts +139 -0
  340. package/src/composables/useCalendarGrid.ts +140 -0
  341. package/src/composables/useControlSchema.ts +1274 -0
  342. package/src/composables/useDebouncedWatch.ts +119 -0
  343. package/src/composables/useDropdownState.ts +83 -0
  344. package/src/composables/useEventListener.ts +111 -0
  345. package/src/composables/useExpansionSet.ts +117 -0
  346. package/src/composables/useExperimentData.ts +20 -11
  347. package/src/composables/useExperimentSave.ts +202 -50
  348. package/src/composables/useExperimentSelector.ts +86 -72
  349. package/src/composables/useForm.ts +49 -4
  350. package/src/composables/useFormBuilder.ts +93 -42
  351. package/src/composables/useGroupAssignment.ts +148 -0
  352. package/src/composables/useListSelection.ts +158 -0
  353. package/src/composables/usePluginApi.ts +7 -14
  354. package/src/composables/usePluginClient.ts +425 -0
  355. package/src/composables/usePluginConfig.ts +34 -13
  356. package/src/composables/useRequestSyncState.ts +126 -0
  357. package/src/composables/useSampleGroups.ts +126 -0
  358. package/src/composables/useSelectionLimit.ts +57 -0
  359. package/src/composables/useSortedItems.ts +118 -0
  360. package/src/composables/useTemplateCollection.ts +229 -0
  361. package/src/composables/useTextSearch.ts +60 -0
  362. package/src/composables/useTheme.ts +2 -28
  363. package/src/composables/useTimeUtils.ts +26 -2
  364. package/src/composables/useWellPlateEditor.ts +13 -9
  365. package/src/index.ts +224 -4
  366. package/src/install.ts +11 -4
  367. package/src/stores/settings.ts +13 -9
  368. package/src/styles/components/app-page-selector.css +23 -0
  369. package/src/styles/components/app-pill-nav.css +7 -0
  370. package/src/styles/components/app-top-bar.css +34 -0
  371. package/src/styles/components/concentration-input.css +3 -142
  372. package/src/styles/components/empty-state.css +0 -16
  373. package/src/styles/components/settings-button.css +3 -66
  374. package/src/styles/components/theme-toggle.css +3 -66
  375. package/src/styles/index.css +0 -1
  376. package/src/templates/adapters.ts +785 -0
  377. package/src/templates/builders.ts +2149 -0
  378. package/src/templates/catalog.ts +245 -0
  379. package/src/templates/componentBindings.ts +615 -0
  380. package/src/templates/controlSchemas.ts +718 -0
  381. package/src/templates/index.ts +314 -0
  382. package/src/templates/lookup.ts +18 -0
  383. package/src/templates/packs.ts +156 -0
  384. package/src/templates/presets.ts +146 -0
  385. package/src/templates/types.ts +668 -0
  386. package/src/types/components.ts +41 -4
  387. package/src/types/form-builder.ts +7 -2
  388. package/src/types/index.ts +14 -0
  389. package/src/types/platform.ts +7 -1
  390. package/src/utils/formModelSync.ts +52 -0
  391. package/src/utils/items.ts +28 -0
  392. package/src/utils/options.ts +23 -0
  393. package/src/utils/pluginIcon.ts +30 -0
  394. package/dist/auth-DsI0rQ7_.js.map +0 -1
  395. package/dist/components-_XqPEhP9.js.map +0 -1
  396. package/dist/composables-tiZqLu1M.js.map +0 -1
  397. package/dist/useScheduleDrag-CA9sGNJG.js +0 -7181
  398. package/dist/useScheduleDrag-CA9sGNJG.js.map +0 -1
  399. package/src/styles/components/grouping-modal.css +0 -323
@@ -0,0 +1,1031 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { createPinia } from 'pinia'
3
+ import { computed, h, nextTick, ref } from 'vue'
4
+ import { describe, expect, it, vi } from 'vitest'
5
+ import AppSidebar from '../../components/AppSidebar.vue'
6
+ import AppTopBar from '../../components/AppTopBar.vue'
7
+ import ControlWorkspaceView from '../../components/ControlWorkspaceView.vue'
8
+ import FormActions from '../../components/FormActions.vue'
9
+ import FormBuilder from '../../components/FormBuilder.vue'
10
+ import {
11
+ defineControlModel,
12
+ defineControls,
13
+ defineDoseDesignControlModel,
14
+ useControlWorkspace,
15
+ } from '../../composables/useControlSchema'
16
+
17
+ vi.mock('../../composables/usePlatformContext', () => ({
18
+ usePlatformContext: vi.fn(() => ({
19
+ isIntegrated: computed(() => false),
20
+ context: ref({ isIntegrated: false, theme: 'system' }),
21
+ plugin: computed(() => undefined),
22
+ user: computed(() => undefined),
23
+ theme: computed(() => 'system' as const),
24
+ features: computed(() => undefined),
25
+ navigate: vi.fn(),
26
+ notify: vi.fn(),
27
+ sendToPlatform: vi.fn(),
28
+ })),
29
+ }))
30
+
31
+ vi.mock('../../composables/useTheme', () => ({
32
+ useTheme: vi.fn(() => ({
33
+ isDark: ref(false),
34
+ theme: ref('light'),
35
+ toggleTheme: vi.fn(),
36
+ })),
37
+ }))
38
+
39
+ function createWorkspace() {
40
+ return useControlWorkspace(defineControls({
41
+ threshold: {
42
+ type: 'number',
43
+ default: 0.05,
44
+ section: 'parameters',
45
+ view: 'analysis',
46
+ },
47
+ chartScale: {
48
+ default: 'linear',
49
+ options: ['linear', 'log'],
50
+ section: 'display',
51
+ view: 'results',
52
+ },
53
+ }), {
54
+ views: {
55
+ analysis: { label: 'Run' },
56
+ results: { label: 'Results' },
57
+ },
58
+ })
59
+ }
60
+
61
+ function mountView(workspace = createWorkspace()) {
62
+ return mount(ControlWorkspaceView, {
63
+ props: {
64
+ workspace,
65
+ title: 'Analysis Workspace',
66
+ },
67
+ global: {
68
+ plugins: [createPinia()],
69
+ stubs: {
70
+ 'router-link': {
71
+ template: '<a><slot /></a>',
72
+ },
73
+ BaseModal: {
74
+ template: '<div><slot /></div>',
75
+ },
76
+ },
77
+ },
78
+ })
79
+ }
80
+
81
+ const globalOptions = {
82
+ plugins: [createPinia()],
83
+ stubs: {
84
+ 'router-link': {
85
+ template: '<a><slot /></a>',
86
+ },
87
+ BaseModal: {
88
+ template: '<div><slot /></div>',
89
+ },
90
+ },
91
+ }
92
+
93
+ describe('ControlWorkspaceView', () => {
94
+ it('renders AppTopBar, AppSidebar, and FormBuilder from one workspace object', () => {
95
+ const workspace = createWorkspace()
96
+ const wrapper = mountView(workspace)
97
+
98
+ expect(wrapper.findComponent(AppTopBar).props('title')).toBe('Analysis Workspace')
99
+ expect(wrapper.findComponent(AppTopBar).props('tabs')).toBeUndefined()
100
+ expect(wrapper.findComponent(AppTopBar).props('pillNav')).toEqual([
101
+ { id: 'analysis', label: 'Run' },
102
+ { id: 'results', label: 'Results' },
103
+ ])
104
+ expect(wrapper.findComponent(AppTopBar).props('currentPillId')).toBe('analysis')
105
+ expect(wrapper.findComponent(AppSidebar).props('activeView')).toBe('analysis')
106
+ expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject(workspace.values)
107
+ })
108
+
109
+ it('can create the workspace internally from direct controls', async () => {
110
+ const controls = defineControls({
111
+ threshold: {
112
+ type: 'number',
113
+ default: 0.05,
114
+ section: 'parameters',
115
+ view: 'analysis',
116
+ },
117
+ chartScale: {
118
+ default: 'linear',
119
+ options: ['linear', 'log'],
120
+ section: 'display',
121
+ view: 'results',
122
+ },
123
+ })
124
+
125
+ const wrapper = mount(ControlWorkspaceView, {
126
+ props: {
127
+ controls,
128
+ controlOptions: {
129
+ views: {
130
+ analysis: { label: 'Run' },
131
+ results: { label: 'Results' },
132
+ },
133
+ },
134
+ title: 'Direct Workspace',
135
+ },
136
+ global: globalOptions,
137
+ })
138
+
139
+ expect(wrapper.findComponent(AppTopBar).props('title')).toBe('Direct Workspace')
140
+ expect(wrapper.findComponent(AppTopBar).props('pillNav')).toEqual([
141
+ { id: 'analysis', label: 'Run' },
142
+ { id: 'results', label: 'Results' },
143
+ ])
144
+ expect(wrapper.findComponent(AppSidebar).props('activeView')).toBe('analysis')
145
+ expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject({
146
+ threshold: 0.05,
147
+ chartScale: 'linear',
148
+ })
149
+
150
+ wrapper.findComponent(AppTopBar).vm.$emit('pill-select', { id: 'results', label: 'Results' })
151
+ await nextTick()
152
+
153
+ expect(wrapper.findComponent(AppTopBar).props('currentPillId')).toBe('results')
154
+ expect(wrapper.findComponent(AppSidebar).props('activeView')).toBe('results')
155
+ })
156
+
157
+ it('accepts a nested control model through direct v-bind props', () => {
158
+ const model = defineControlModel({
159
+ views: {
160
+ design: {
161
+ label: 'Design',
162
+ sections: {
163
+ dose: {
164
+ label: 'Dose design',
165
+ controls: {
166
+ replicates: {
167
+ type: 'number',
168
+ default: 3,
169
+ min: 1,
170
+ },
171
+ },
172
+ },
173
+ },
174
+ },
175
+ results: {
176
+ label: 'Results',
177
+ section: 'plots',
178
+ sectionLabel: 'Plots',
179
+ controls: {
180
+ chartScale: ['linear', 'log'],
181
+ },
182
+ },
183
+ },
184
+ })
185
+
186
+ const wrapper = mount(ControlWorkspaceView, {
187
+ props: {
188
+ ...model,
189
+ title: 'Nested Model Workspace',
190
+ },
191
+ global: globalOptions,
192
+ })
193
+
194
+ expect(wrapper.findComponent(AppTopBar).props('pillNav')).toEqual([
195
+ { id: 'design', label: 'Design' },
196
+ { id: 'results', label: 'Results' },
197
+ ])
198
+ expect(wrapper.findComponent(AppSidebar).props('activeView')).toBe('design')
199
+ expect(wrapper.findComponent(AppSidebar).props('panels')).toMatchObject({
200
+ design: [{ id: 'design-dose', label: 'Dose design' }],
201
+ results: [{ id: 'plots', label: 'Plots' }],
202
+ })
203
+ expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject({
204
+ replicates: 3,
205
+ chartScale: 'linear',
206
+ })
207
+ })
208
+
209
+ it('accepts a nested control model through one model prop', () => {
210
+ const model = defineControlModel({
211
+ views: {
212
+ design: {
213
+ label: 'Design',
214
+ sections: {
215
+ dose: {
216
+ label: 'Dose design',
217
+ controls: {
218
+ replicates: {
219
+ type: 'number',
220
+ default: 3,
221
+ min: 1,
222
+ },
223
+ },
224
+ },
225
+ },
226
+ },
227
+ results: {
228
+ label: 'Results',
229
+ section: 'plots',
230
+ sectionLabel: 'Plots',
231
+ controls: {
232
+ chartScale: ['linear', 'log'],
233
+ },
234
+ },
235
+ },
236
+ })
237
+
238
+ const wrapper = mount(ControlWorkspaceView, {
239
+ props: {
240
+ model,
241
+ title: 'Model Workspace',
242
+ },
243
+ global: globalOptions,
244
+ })
245
+
246
+ expect(wrapper.findComponent(AppTopBar).props('pillNav')).toEqual([
247
+ { id: 'design', label: 'Design' },
248
+ { id: 'results', label: 'Results' },
249
+ ])
250
+ expect(wrapper.findComponent(AppSidebar).props('panels')).toMatchObject({
251
+ design: [{ id: 'design-dose', label: 'Dose design' }],
252
+ results: [{ id: 'plots', label: 'Plots' }],
253
+ })
254
+ expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject({
255
+ replicates: 3,
256
+ chartScale: 'linear',
257
+ })
258
+ })
259
+
260
+ it('merges model and prop controlOptions initial values', () => {
261
+ const model = defineControlModel({
262
+ initialValues: {
263
+ replicates: 4,
264
+ chartScale: 'log',
265
+ },
266
+ views: {
267
+ design: {
268
+ label: 'Design',
269
+ sections: {
270
+ dose: {
271
+ label: 'Dose design',
272
+ controls: {
273
+ replicates: {
274
+ type: 'number',
275
+ default: 3,
276
+ min: 1,
277
+ },
278
+ },
279
+ },
280
+ },
281
+ },
282
+ results: {
283
+ label: 'Results',
284
+ section: 'plots',
285
+ sectionLabel: 'Plots',
286
+ controls: {
287
+ chartScale: ['linear', 'log'],
288
+ },
289
+ },
290
+ },
291
+ })
292
+
293
+ const wrapper = mount(ControlWorkspaceView, {
294
+ props: {
295
+ model,
296
+ controlOptions: {
297
+ initialValues: {
298
+ chartScale: 'linear',
299
+ },
300
+ },
301
+ },
302
+ global: globalOptions,
303
+ })
304
+
305
+ expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject({
306
+ replicates: 4,
307
+ chartScale: 'linear',
308
+ })
309
+ })
310
+
311
+ it('accepts a raw nested control model through one model prop', () => {
312
+ const wrapper = mount(ControlWorkspaceView, {
313
+ props: {
314
+ model: {
315
+ views: {
316
+ setup: {
317
+ label: 'Setup',
318
+ sections: {
319
+ parameters: {
320
+ label: 'Parameters',
321
+ controls: {
322
+ threshold: { type: 'number', default: 0.05 },
323
+ normalize: true,
324
+ },
325
+ },
326
+ },
327
+ },
328
+ review: {
329
+ label: 'Review',
330
+ controls: {
331
+ chartScale: ['linear', 'log'],
332
+ },
333
+ },
334
+ },
335
+ },
336
+ title: 'Raw Model Workspace',
337
+ },
338
+ global: globalOptions,
339
+ })
340
+
341
+ expect(wrapper.findComponent(AppTopBar).props('title')).toBe('Raw Model Workspace')
342
+ expect(wrapper.findComponent(AppTopBar).props('pillNav')).toEqual([
343
+ { id: 'setup', label: 'Setup' },
344
+ { id: 'review', label: 'Review' },
345
+ ])
346
+ expect(wrapper.findComponent(AppSidebar).props('panels')).toMatchObject({
347
+ setup: [{ id: 'setup-parameters', label: 'Parameters' }],
348
+ review: [{ id: 'review-controls', label: 'Review' }],
349
+ })
350
+ expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject({
351
+ threshold: 0.05,
352
+ normalize: true,
353
+ chartScale: 'linear',
354
+ })
355
+ })
356
+
357
+ it('resolves component props from a dose design model prop', () => {
358
+ const model = defineDoseDesignControlModel({
359
+ selectedWells: ['A1', 'A2'],
360
+ includeMolecularWeight: true,
361
+ })
362
+
363
+ const wrapper = mount(ControlWorkspaceView, {
364
+ props: {
365
+ model,
366
+ },
367
+ slots: {
368
+ default: ({ componentPropsById }) => h(
369
+ 'pre',
370
+ { class: 'component-props-by-id' },
371
+ JSON.stringify(componentPropsById),
372
+ ),
373
+ },
374
+ global: globalOptions,
375
+ })
376
+
377
+ expect(JSON.parse(wrapper.find('.component-props-by-id').text())).toEqual({
378
+ plate: {
379
+ modelValue: ['A1', 'A2'],
380
+ format: 96,
381
+ disabled: false,
382
+ },
383
+ dose: {
384
+ mode: 'serial',
385
+ targetWells: ['A1', 'A2'],
386
+ disabled: false,
387
+ molecularWeight: 300,
388
+ },
389
+ })
390
+ })
391
+
392
+ it('accepts component prop mappings from a nested control model', async () => {
393
+ const model = defineControlModel({
394
+ views: {
395
+ design: {
396
+ label: 'Dose design',
397
+ controls: {
398
+ doseMode: {
399
+ default: 'serial',
400
+ options: ['serial', 'dilution'],
401
+ },
402
+ targetWells: {
403
+ type: 'tags',
404
+ default: ['A1', 'A2'],
405
+ },
406
+ enabled: true,
407
+ },
408
+ },
409
+ },
410
+ componentProps: {
411
+ mode: 'doseMode',
412
+ targetWells: 'targetWells',
413
+ disabled: values => !values.enabled,
414
+ },
415
+ componentPropsById: {
416
+ dose: {
417
+ mode: 'doseMode',
418
+ targetWells: 'targetWells',
419
+ disabled: values => !values.enabled,
420
+ },
421
+ plate: {
422
+ modelValue: 'targetWells',
423
+ disabled: values => !values.enabled,
424
+ },
425
+ },
426
+ })
427
+
428
+ const wrapper = mount(ControlWorkspaceView, {
429
+ props: {
430
+ ...model,
431
+ },
432
+ slots: {
433
+ default: ({ componentProps, componentPropsById }) => h(
434
+ 'pre',
435
+ { class: 'component-props' },
436
+ JSON.stringify({ componentProps, componentPropsById }),
437
+ ),
438
+ },
439
+ global: globalOptions,
440
+ })
441
+
442
+ expect(JSON.parse(wrapper.find('.component-props').text())).toEqual({
443
+ componentProps: {
444
+ mode: 'serial',
445
+ targetWells: ['A1', 'A2'],
446
+ disabled: false,
447
+ },
448
+ componentPropsById: {
449
+ dose: {
450
+ mode: 'serial',
451
+ targetWells: ['A1', 'A2'],
452
+ disabled: false,
453
+ },
454
+ plate: {
455
+ modelValue: ['A1', 'A2'],
456
+ disabled: false,
457
+ },
458
+ },
459
+ })
460
+
461
+ wrapper.findComponent(FormBuilder).vm.$emit('update:modelValue', {
462
+ doseMode: 'dilution',
463
+ targetWells: ['B1'],
464
+ enabled: false,
465
+ })
466
+ await nextTick()
467
+
468
+ expect(JSON.parse(wrapper.find('.component-props').text())).toEqual({
469
+ componentProps: {
470
+ mode: 'dilution',
471
+ targetWells: ['B1'],
472
+ disabled: true,
473
+ },
474
+ componentPropsById: {
475
+ dose: {
476
+ mode: 'dilution',
477
+ targetWells: ['B1'],
478
+ disabled: true,
479
+ },
480
+ plate: {
481
+ modelValue: ['B1'],
482
+ disabled: true,
483
+ },
484
+ },
485
+ })
486
+ })
487
+
488
+ it('accepts initial values directly for internally generated workspaces', () => {
489
+ const controls = defineControls({
490
+ threshold: {
491
+ type: 'number',
492
+ default: 0.05,
493
+ section: 'parameters',
494
+ },
495
+ chartScale: {
496
+ default: 'linear',
497
+ options: ['linear', 'log'],
498
+ section: 'display',
499
+ },
500
+ sampleNames: {
501
+ type: 'tags',
502
+ default: ['Control'],
503
+ section: 'samples',
504
+ },
505
+ enabled: true,
506
+ })
507
+
508
+ const wrapper = mount(ControlWorkspaceView, {
509
+ props: {
510
+ controls,
511
+ controlOptions: {
512
+ initialValues: {
513
+ threshold: 0.2,
514
+ chartScale: 'linear',
515
+ },
516
+ },
517
+ initialValues: {
518
+ chartScale: 'log',
519
+ },
520
+ },
521
+ global: globalOptions,
522
+ })
523
+
524
+ expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject({
525
+ threshold: 0.2,
526
+ chartScale: 'log',
527
+ enabled: true,
528
+ })
529
+ })
530
+
531
+ it('syncs internally generated workspace values with v-model:values', async () => {
532
+ const controls = defineControls({
533
+ threshold: {
534
+ type: 'number',
535
+ default: 0.05,
536
+ section: 'parameters',
537
+ },
538
+ chartScale: {
539
+ default: 'linear',
540
+ options: ['linear', 'log'],
541
+ section: 'display',
542
+ },
543
+ enabled: true,
544
+ })
545
+
546
+ const wrapper = mount(ControlWorkspaceView, {
547
+ props: {
548
+ controls,
549
+ values: {
550
+ threshold: 0.2,
551
+ chartScale: 'log',
552
+ sampleNames: ['Vehicle', 'Drug'],
553
+ enabled: true,
554
+ },
555
+ },
556
+ global: globalOptions,
557
+ })
558
+
559
+ expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject({
560
+ threshold: 0.2,
561
+ chartScale: 'log',
562
+ sampleNames: ['Vehicle', 'Drug'],
563
+ enabled: true,
564
+ })
565
+
566
+ wrapper.findComponent(FormBuilder).vm.$emit('update:modelValue', {
567
+ threshold: 0.5,
568
+ chartScale: 'linear',
569
+ sampleNames: ['Vehicle', 'Dose A'],
570
+ enabled: false,
571
+ })
572
+ await nextTick()
573
+
574
+ expect(wrapper.emitted('update:values')?.at(-1)).toEqual([{
575
+ threshold: 0.5,
576
+ chartScale: 'linear',
577
+ sampleNames: ['Vehicle', 'Dose A'],
578
+ enabled: false,
579
+ }])
580
+
581
+ await wrapper.setProps({
582
+ values: {
583
+ threshold: 0.8,
584
+ chartScale: 'log',
585
+ sampleNames: ['Vehicle', 'Dose A'],
586
+ enabled: true,
587
+ },
588
+ })
589
+
590
+ expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject({
591
+ threshold: 0.8,
592
+ chartScale: 'log',
593
+ sampleNames: ['Vehicle', 'Dose A'],
594
+ enabled: true,
595
+ })
596
+ })
597
+
598
+ it('syncs internally generated workspace values with default v-model', async () => {
599
+ const controls = defineControls({
600
+ threshold: {
601
+ type: 'number',
602
+ default: 0.05,
603
+ section: 'parameters',
604
+ },
605
+ chartScale: {
606
+ default: 'linear',
607
+ options: ['linear', 'log'],
608
+ section: 'display',
609
+ },
610
+ })
611
+
612
+ const wrapper = mount(ControlWorkspaceView, {
613
+ props: {
614
+ controls,
615
+ modelValue: {
616
+ threshold: 0.2,
617
+ chartScale: 'log',
618
+ },
619
+ },
620
+ global: globalOptions,
621
+ })
622
+
623
+ expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject({
624
+ threshold: 0.2,
625
+ chartScale: 'log',
626
+ })
627
+
628
+ wrapper.findComponent(FormBuilder).vm.$emit('update:modelValue', {
629
+ threshold: 0.5,
630
+ chartScale: 'linear',
631
+ })
632
+ await nextTick()
633
+
634
+ expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([{
635
+ threshold: 0.5,
636
+ chartScale: 'linear',
637
+ }])
638
+ expect(wrapper.emitted('update:values')?.at(-1)).toEqual([{
639
+ threshold: 0.5,
640
+ chartScale: 'linear',
641
+ }])
642
+
643
+ await wrapper.setProps({
644
+ modelValue: {
645
+ threshold: 0.8,
646
+ chartScale: 'log',
647
+ },
648
+ })
649
+
650
+ expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject({
651
+ threshold: 0.8,
652
+ chartScale: 'log',
653
+ })
654
+ })
655
+
656
+ it('activates direct controls that are loaded after mount', async () => {
657
+ const controls = defineControls({
658
+ threshold: {
659
+ type: 'number',
660
+ default: 0.05,
661
+ section: 'parameters',
662
+ view: 'analysis',
663
+ },
664
+ method: {
665
+ default: 'linear',
666
+ options: ['linear', 'logistic'],
667
+ section: 'parameters',
668
+ view: 'analysis',
669
+ },
670
+ })
671
+
672
+ const wrapper = mount(ControlWorkspaceView, {
673
+ props: {
674
+ title: 'Async Workspace',
675
+ },
676
+ global: globalOptions,
677
+ })
678
+
679
+ expect(wrapper.findComponent(AppSidebar).props('activeView')).toBe('')
680
+
681
+ await wrapper.setProps({
682
+ controls,
683
+ controlOptions: {
684
+ views: {
685
+ analysis: { label: 'Run' },
686
+ },
687
+ },
688
+ })
689
+
690
+ expect(wrapper.findComponent(AppTopBar).props('pillNav')).toEqual([
691
+ { id: 'analysis', label: 'Run' },
692
+ ])
693
+ expect(wrapper.findComponent(AppSidebar).props('activeView')).toBe('analysis')
694
+ expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject({
695
+ threshold: 0.05,
696
+ method: 'linear',
697
+ })
698
+ })
699
+
700
+ it('preserves generated values and active view when direct controls change', async () => {
701
+ const controls = defineControls({
702
+ threshold: {
703
+ type: 'number',
704
+ default: 0.05,
705
+ section: 'parameters',
706
+ view: 'analysis',
707
+ },
708
+ chartScale: {
709
+ default: 'linear',
710
+ options: ['linear', 'log'],
711
+ section: 'display',
712
+ view: 'results',
713
+ },
714
+ })
715
+ const nextControls = defineControls({
716
+ threshold: {
717
+ type: 'number',
718
+ default: 0.05,
719
+ section: 'parameters',
720
+ view: 'analysis',
721
+ },
722
+ chartScale: {
723
+ default: 'linear',
724
+ options: ['linear', 'log'],
725
+ section: 'display',
726
+ view: 'results',
727
+ },
728
+ normalize: {
729
+ default: true,
730
+ section: 'display',
731
+ view: 'results',
732
+ },
733
+ })
734
+
735
+ const wrapper = mount(ControlWorkspaceView, {
736
+ props: {
737
+ controls,
738
+ controlOptions: {
739
+ views: {
740
+ analysis: { label: 'Run' },
741
+ results: { label: 'Results' },
742
+ },
743
+ },
744
+ },
745
+ global: globalOptions,
746
+ })
747
+
748
+ wrapper.findComponent(FormBuilder).vm.$emit('update:modelValue', {
749
+ threshold: 0.5,
750
+ chartScale: 'log',
751
+ })
752
+ wrapper.findComponent(AppTopBar).vm.$emit('pill-select', { id: 'results', label: 'Results' })
753
+ await nextTick()
754
+
755
+ await wrapper.setProps({ controls: nextControls })
756
+
757
+ expect(wrapper.findComponent(AppTopBar).props('currentPillId')).toBe('results')
758
+ expect(wrapper.findComponent(AppSidebar).props('activeView')).toBe('results')
759
+ expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject({
760
+ threshold: 0.5,
761
+ chartScale: 'log',
762
+ normalize: true,
763
+ })
764
+ })
765
+
766
+ it('forwards default FormBuilder submit and cancel events', async () => {
767
+ const controls = defineControls({
768
+ threshold: {
769
+ type: 'number',
770
+ default: 0.05,
771
+ section: 'parameters',
772
+ },
773
+ })
774
+ const wrapper = mount(ControlWorkspaceView, {
775
+ props: {
776
+ controls,
777
+ controlOptions: {
778
+ showCancel: true,
779
+ },
780
+ showFormActions: true,
781
+ },
782
+ global: globalOptions,
783
+ })
784
+
785
+ wrapper.findComponent(FormActions).vm.$emit('submit')
786
+ wrapper.findComponent(FormActions).vm.$emit('cancel')
787
+ await nextTick()
788
+
789
+ expect(wrapper.emitted('submit')).toEqual([[{ threshold: 0.05 }]])
790
+ expect(wrapper.emitted('cancel')).toEqual([[]])
791
+ })
792
+
793
+ it('passes generated form state and enhancements to sidebar and content forms', () => {
794
+ const controls = defineControls({
795
+ threshold: {
796
+ type: 'number',
797
+ default: 0.05,
798
+ section: 'parameters',
799
+ view: 'analysis',
800
+ },
801
+ })
802
+ const formEnhancements = {
803
+ onFieldChange: vi.fn(),
804
+ }
805
+ const wrapper = mount(ControlWorkspaceView, {
806
+ props: {
807
+ controls,
808
+ formEnhancements,
809
+ formLoading: true,
810
+ formDisabled: true,
811
+ formReadonly: true,
812
+ },
813
+ global: globalOptions,
814
+ })
815
+
816
+ const sidebar = wrapper.findComponent(AppSidebar)
817
+ expect(sidebar.props('formEnhancements')).toMatchObject(formEnhancements)
818
+ expect(sidebar.props('formLoading')).toBe(true)
819
+ expect(sidebar.props('formDisabled')).toBe(true)
820
+ expect(sidebar.props('formReadonly')).toBe(true)
821
+
822
+ for (const form of wrapper.findAllComponents(FormBuilder)) {
823
+ expect(form.props('enhancements')).toMatchObject(formEnhancements)
824
+ expect(form.props('loading')).toBe(true)
825
+ expect(form.props('disabled')).toBe(true)
826
+ expect(form.props('readonly')).toBe(true)
827
+ }
828
+ })
829
+
830
+ it('keeps topbar pill navigation and sidebar view synchronized', async () => {
831
+ const workspace = createWorkspace()
832
+ const wrapper = mountView(workspace)
833
+
834
+ wrapper.findComponent(AppTopBar).vm.$emit('pill-select', { id: 'results', label: 'Results' })
835
+ await nextTick()
836
+
837
+ expect(workspace.activeView.value).toBe('results')
838
+ expect(wrapper.findComponent(AppTopBar).props('currentPillId')).toBe('results')
839
+ expect(wrapper.findComponent(AppSidebar).props('activeView')).toBe('results')
840
+ })
841
+
842
+ it('can opt into legacy topbar tab navigation from the generated workspace', async () => {
843
+ const controls = defineControls({
844
+ threshold: {
845
+ type: 'number',
846
+ default: 0.05,
847
+ section: 'parameters',
848
+ view: 'analysis',
849
+ },
850
+ chartScale: {
851
+ default: 'linear',
852
+ options: ['linear', 'log'],
853
+ section: 'display',
854
+ view: 'results',
855
+ },
856
+ })
857
+
858
+ const wrapper = mount(ControlWorkspaceView, {
859
+ props: {
860
+ controls,
861
+ controlOptions: {
862
+ views: {
863
+ analysis: { label: 'Run' },
864
+ results: { label: 'Results' },
865
+ },
866
+ },
867
+ navigation: 'tabs',
868
+ },
869
+ global: globalOptions,
870
+ })
871
+
872
+ const topBar = wrapper.findComponent(AppTopBar)
873
+ expect(topBar.props('pillNav')).toBeUndefined()
874
+ expect(topBar.props('tabs')).toEqual([
875
+ { id: 'analysis', label: 'Run' },
876
+ { id: 'results', label: 'Results' },
877
+ ])
878
+ expect(topBar.props('currentTabId')).toBe('analysis')
879
+
880
+ topBar.vm.$emit('tab-select', { id: 'results', label: 'Results' })
881
+ await nextTick()
882
+
883
+ expect(wrapper.findComponent(AppTopBar).props('currentTabId')).toBe('results')
884
+ expect(wrapper.findComponent(AppSidebar).props('activeView')).toBe('results')
885
+ })
886
+
887
+ it('exposes workspace data to the default slot', () => {
888
+ const workspace = createWorkspace()
889
+ const wrapper = mount(ControlWorkspaceView, {
890
+ props: { workspace },
891
+ slots: {
892
+ default: '<p class="custom-content">Custom analysis content</p>',
893
+ },
894
+ global: {
895
+ plugins: [createPinia()],
896
+ stubs: {
897
+ 'router-link': {
898
+ template: '<a><slot /></a>',
899
+ },
900
+ BaseModal: {
901
+ template: '<div><slot /></div>',
902
+ },
903
+ },
904
+ },
905
+ })
906
+
907
+ expect(wrapper.find('.custom-content').text()).toBe('Custom analysis content')
908
+ expect(wrapper.findAllComponents(FormBuilder)).toHaveLength(1)
909
+ })
910
+
911
+ it('exposes mapped component props to the default slot', async () => {
912
+ const controls = defineControls({
913
+ sampleNames: {
914
+ type: 'tags',
915
+ default: ['Control', 'Treatment'],
916
+ section: 'samples',
917
+ },
918
+ enabled: true,
919
+ })
920
+ const wrapper = mount(ControlWorkspaceView, {
921
+ props: {
922
+ controls,
923
+ componentProps: {
924
+ samples: 'sampleNames',
925
+ disabled: values => !values.enabled,
926
+ },
927
+ },
928
+ slots: {
929
+ default: ({ componentProps }) => h(
930
+ 'pre',
931
+ { class: 'component-props' },
932
+ JSON.stringify(componentProps),
933
+ ),
934
+ },
935
+ global: globalOptions,
936
+ })
937
+
938
+ expect(JSON.parse(wrapper.find('.component-props').text())).toEqual({
939
+ samples: ['Control', 'Treatment'],
940
+ disabled: false,
941
+ })
942
+
943
+ wrapper.findComponent(FormBuilder).vm.$emit('update:modelValue', {
944
+ sampleNames: ['Vehicle'],
945
+ enabled: false,
946
+ })
947
+ await nextTick()
948
+
949
+ expect(JSON.parse(wrapper.find('.component-props').text())).toEqual({
950
+ samples: ['Vehicle'],
951
+ disabled: true,
952
+ })
953
+ })
954
+
955
+ it('exposes resolved workspace bindings to custom slots', () => {
956
+ const model = defineControlModel({
957
+ views: {
958
+ analysis: {
959
+ label: 'Run',
960
+ controls: {
961
+ threshold: {
962
+ type: 'number',
963
+ default: 0.05,
964
+ },
965
+ enabled: true,
966
+ },
967
+ },
968
+ },
969
+ componentProps: {
970
+ score: 'threshold',
971
+ disabled: values => !values.enabled,
972
+ },
973
+ componentPropsById: {
974
+ chart: {
975
+ score: 'threshold',
976
+ disabled: values => !values.enabled,
977
+ },
978
+ },
979
+ })
980
+
981
+ const wrapper = mount(ControlWorkspaceView, {
982
+ props: {
983
+ model,
984
+ },
985
+ slots: {
986
+ topbar: ({ bindings, topBar }) => h(
987
+ 'pre',
988
+ { class: 'topbar-bindings' },
989
+ JSON.stringify({
990
+ legacyTabId: topBar.currentTabId,
991
+ pillId: bindings.topBar.value.currentPillId,
992
+ }),
993
+ ),
994
+ sidebar: ({ bindings, sidebar }) => h(
995
+ 'pre',
996
+ { class: 'sidebar-bindings' },
997
+ JSON.stringify({
998
+ activeView: sidebar.activeView,
999
+ bindingActiveView: bindings.sidebar.activeView,
1000
+ }),
1001
+ ),
1002
+ default: ({ bindings, componentPropsById }) => h(
1003
+ 'pre',
1004
+ { class: 'default-bindings' },
1005
+ JSON.stringify({
1006
+ formThreshold: bindings.form.modelValue.threshold,
1007
+ bindingScore: bindings.componentProps.value.score,
1008
+ slotScore: componentPropsById.chart.score,
1009
+ bindingSlotScore: bindings.componentPropsById.value.chart.score,
1010
+ }),
1011
+ ),
1012
+ },
1013
+ global: globalOptions,
1014
+ })
1015
+
1016
+ expect(JSON.parse(wrapper.find('.topbar-bindings').text())).toEqual({
1017
+ legacyTabId: 'analysis',
1018
+ pillId: 'analysis',
1019
+ })
1020
+ expect(JSON.parse(wrapper.find('.sidebar-bindings').text())).toEqual({
1021
+ activeView: 'analysis',
1022
+ bindingActiveView: 'analysis',
1023
+ })
1024
+ expect(JSON.parse(wrapper.find('.default-bindings').text())).toEqual({
1025
+ formThreshold: 0.05,
1026
+ bindingScore: 0.05,
1027
+ slotScore: 0.05,
1028
+ bindingSlotScore: 0.05,
1029
+ })
1030
+ })
1031
+ })