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

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 (427) hide show
  1. package/README.md +225 -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/ComponentBindingRenderer.test.d.ts +1 -0
  19. package/dist/__tests__/components/ConcentrationInput.test.d.ts +1 -0
  20. package/dist/__tests__/components/ControlWorkspaceView.test.d.ts +1 -0
  21. package/dist/__tests__/components/DatePicker.test.d.ts +1 -0
  22. package/dist/__tests__/components/DateTimePicker.test.d.ts +1 -0
  23. package/dist/__tests__/components/DoseDesignWorkspaceView.test.d.ts +1 -0
  24. package/dist/__tests__/components/EmptyState.test.d.ts +1 -0
  25. package/dist/__tests__/components/ExperimentPopover.test.d.ts +1 -0
  26. package/dist/__tests__/components/FormBuilder.test.d.ts +1 -0
  27. package/dist/__tests__/components/GroupAssigner.test.d.ts +1 -0
  28. package/dist/__tests__/components/MultiSelect.test.d.ts +1 -0
  29. package/dist/__tests__/components/PluginWorkspaceView.test.d.ts +1 -0
  30. package/dist/__tests__/components/ProtocolStepEditor.test.d.ts +1 -0
  31. package/dist/__tests__/components/ReagentList.test.d.ts +1 -0
  32. package/dist/__tests__/components/SampleHierarchyTree.test.d.ts +1 -0
  33. package/dist/__tests__/components/SampleSelector.test.d.ts +1 -0
  34. package/dist/__tests__/components/SegmentedControl.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/experiment-utils.test.d.ts +1 -0
  40. package/dist/__tests__/composables/useApi.test.d.ts +1 -0
  41. package/dist/__tests__/composables/useBioTemplatePackWorkspace.test.d.ts +1 -0
  42. package/dist/__tests__/composables/useBioTemplatePresetWorkspace.test.d.ts +1 -0
  43. package/dist/__tests__/composables/useBioTemplateWorkspace.test.d.ts +1 -0
  44. package/dist/__tests__/composables/useCalendarGrid.test.d.ts +1 -0
  45. package/dist/__tests__/composables/useControlSchema.test.d.ts +1 -0
  46. package/dist/__tests__/composables/useDebouncedWatch.test.d.ts +1 -0
  47. package/dist/__tests__/composables/useDropdownState.test.d.ts +1 -0
  48. package/dist/__tests__/composables/useEventListener.test.d.ts +1 -0
  49. package/dist/__tests__/composables/useExpansionSet.test.d.ts +1 -0
  50. package/dist/__tests__/composables/useExperimentData.test.d.ts +1 -0
  51. package/dist/__tests__/composables/useExperimentSelector.test.d.ts +1 -0
  52. package/dist/__tests__/composables/useGroupAssignment.test.d.ts +1 -0
  53. package/dist/__tests__/composables/useListSelection.test.d.ts +1 -0
  54. package/dist/__tests__/composables/usePluginClient.test.d.ts +1 -0
  55. package/dist/__tests__/composables/usePluginConfig.test.d.ts +1 -0
  56. package/dist/__tests__/composables/useRequestSyncState.test.d.ts +1 -0
  57. package/dist/__tests__/composables/useSampleGroups.test.d.ts +1 -0
  58. package/dist/__tests__/composables/useSelectionLimit.test.d.ts +1 -0
  59. package/dist/__tests__/composables/useSortedItems.test.d.ts +1 -0
  60. package/dist/__tests__/composables/useTemplateCollection.test.d.ts +1 -0
  61. package/dist/__tests__/composables/useTextSearch.test.d.ts +1 -0
  62. package/dist/__tests__/composables/useTheme.test.d.ts +1 -0
  63. package/dist/__tests__/composables/useTimeUtils.test.d.ts +1 -0
  64. package/dist/__tests__/docs/frontendDocsCatalog.test.d.ts +1 -0
  65. package/dist/__tests__/templates/templates.test.d.ts +1 -0
  66. package/dist/{auth-DsI0rQ7_.js → auth-QQj2kkze.js} +12 -5
  67. package/dist/auth-QQj2kkze.js.map +1 -0
  68. package/dist/components/AppAvatarMenu.vue.d.ts +2 -7
  69. package/dist/components/AppContainer.vue.d.ts +1 -1
  70. package/dist/components/AppLayout.vue.d.ts +20 -1
  71. package/dist/components/AppSidebar.vue.d.ts +111 -6
  72. package/dist/components/AppTopBar.vue.d.ts +35 -22
  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 +119 -0
  83. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +93 -0
  84. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +87 -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/CollapsibleCard.vue.d.ts +1 -1
  89. package/dist/components/ComponentBindingRenderer.vue.d.ts +44 -0
  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 +147 -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/DoseDesignWorkspaceView.vue.d.ts +149 -0
  97. package/dist/components/DropdownButton.vue.d.ts +3 -3
  98. package/dist/components/EmptyState.vue.d.ts +1 -2
  99. package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
  100. package/dist/components/ExperimentTimeline.vue.d.ts +2 -2
  101. package/dist/components/FileUploader.vue.d.ts +1 -1
  102. package/dist/components/FitPanel.vue.d.ts +1 -1
  103. package/dist/components/FormActions.vue.d.ts +4 -4
  104. package/dist/components/FormBuilder.vue.d.ts +31 -17
  105. package/dist/components/FormulaInput.vue.d.ts +2 -2
  106. package/dist/components/MoleculeInput.vue.d.ts +2 -2
  107. package/dist/components/MultiSelect.vue.d.ts +3 -3
  108. package/dist/components/NumberInput.vue.d.ts +1 -1
  109. package/dist/components/PlateMapEditor.vue.d.ts +1 -1
  110. package/dist/components/PluginWorkspaceView.vue.d.ts +310 -0
  111. package/dist/components/ProgressBar.vue.d.ts +1 -1
  112. package/dist/components/ProtocolStepEditor.vue.d.ts +3 -1
  113. package/dist/components/RackEditor.vue.d.ts +2 -2
  114. package/dist/components/SampleLegend.vue.d.ts +2 -2
  115. package/dist/components/ScheduleCalendar.vue.d.ts +2 -2
  116. package/dist/components/SegmentedControl.vue.d.ts +2 -2
  117. package/dist/components/SequenceInput.vue.d.ts +3 -3
  118. package/dist/components/SettingsModal.vue.d.ts +14 -6
  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 +6 -6
  125. package/dist/components/index.d.ts +9 -8
  126. package/dist/components/index.js +3 -3
  127. package/dist/components/{SettingsButton.vue.d.ts → internal/ActionItemInternal.vue.d.ts} +11 -9
  128. package/dist/components/{AppPageSelector.vue.d.ts → internal/AppPageSelectorInternal.vue.d.ts} +3 -6
  129. package/dist/components/{AppPillNav.vue.d.ts → internal/AppPillNavInternal.vue.d.ts} +4 -2
  130. package/dist/components/internal/CalendarGridPanelInternal.vue.d.ts +25 -0
  131. package/dist/components/{FormFieldRenderer.vue.d.ts → internal/FormFieldRendererInternal.vue.d.ts} +2 -2
  132. package/dist/components/{FormSection.vue.d.ts → internal/FormSectionRenderer.vue.d.ts} +7 -7
  133. package/dist/components/{WellEditPopup.vue.d.ts → internal/WellEditPopupInternal.vue.d.ts} +1 -1
  134. package/dist/{components-_XqPEhP9.js → components-BkGF4B4y.js} +9760 -8471
  135. package/dist/components-BkGF4B4y.js.map +1 -0
  136. package/dist/composables/experiment-utils.d.ts +8 -0
  137. package/dist/composables/index.d.ts +22 -5
  138. package/dist/composables/index.js +4 -3
  139. package/dist/composables/platformContextHelpers.d.ts +14 -0
  140. package/dist/composables/useAppExperiment.d.ts +31 -2
  141. package/dist/composables/useBioTemplateComponents.d.ts +22 -0
  142. package/dist/composables/useBioTemplateControls.d.ts +6 -0
  143. package/dist/composables/useBioTemplatePackWorkspace.d.ts +46 -0
  144. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +75 -0
  145. package/dist/composables/useBioTemplateWorkspace.d.ts +51 -0
  146. package/dist/composables/useCalendarGrid.d.ts +26 -0
  147. package/dist/composables/useControlSchema.d.ts +343 -0
  148. package/dist/composables/useDebouncedWatch.d.ts +20 -0
  149. package/dist/composables/useDropdownState.d.ts +19 -0
  150. package/dist/composables/useEventListener.d.ts +13 -0
  151. package/dist/composables/useExpansionSet.d.ts +21 -0
  152. package/dist/composables/useExperimentData.d.ts +10 -0
  153. package/dist/composables/useExperimentSave.d.ts +31 -2
  154. package/dist/composables/useExperimentSelector.d.ts +20 -0
  155. package/dist/composables/useForm.d.ts +2 -0
  156. package/dist/composables/useGroupAssignment.d.ts +31 -0
  157. package/dist/composables/useListSelection.d.ts +35 -0
  158. package/dist/composables/usePlatformContext.d.ts +21 -3
  159. package/dist/composables/usePluginClient.d.ts +112 -0
  160. package/dist/composables/usePluginConfig.d.ts +12 -0
  161. package/dist/composables/useRequestSyncState.d.ts +34 -0
  162. package/dist/composables/useSampleGroups.d.ts +32 -0
  163. package/dist/composables/useSelectionLimit.d.ts +17 -0
  164. package/dist/composables/useSortedItems.d.ts +32 -0
  165. package/dist/composables/useTemplateCollection.d.ts +58 -0
  166. package/dist/composables/useTextSearch.d.ts +18 -0
  167. package/dist/composables/useTimeUtils.d.ts +8 -0
  168. package/dist/{composables-tiZqLu1M.js → composables-CHsME9H1.js} +240 -146
  169. package/dist/composables-CHsME9H1.js.map +1 -0
  170. package/dist/index.d.ts +6 -4
  171. package/dist/index.js +6 -5
  172. package/dist/install.d.ts +7 -2
  173. package/dist/install.js +2 -2
  174. package/dist/install.js.map +1 -1
  175. package/dist/stores/index.js +1 -1
  176. package/dist/stores/settings.d.ts +4 -1
  177. package/dist/styles.css +4746 -5514
  178. package/dist/templates/adapters.d.ts +43 -0
  179. package/dist/templates/builders.d.ts +63 -0
  180. package/dist/templates/catalog.d.ts +188 -0
  181. package/dist/templates/componentBindings.d.ts +71 -0
  182. package/dist/templates/controlSchemas.d.ts +25 -0
  183. package/dist/templates/index.d.ts +15 -0
  184. package/dist/templates/index.js +2 -0
  185. package/dist/templates/lookup.d.ts +4 -0
  186. package/dist/templates/packs.d.ts +18 -0
  187. package/dist/templates/presets.d.ts +90 -0
  188. package/dist/templates/types.d.ts +531 -0
  189. package/dist/templates-B5jmTWuk.js +9388 -0
  190. package/dist/templates-B5jmTWuk.js.map +1 -0
  191. package/dist/types/components.d.ts +26 -23
  192. package/dist/types/form-builder.d.ts +6 -8
  193. package/dist/types/index.d.ts +2 -2
  194. package/dist/types/platform.d.ts +7 -1
  195. package/dist/useScheduleDrag-BgzpQT53.js +4414 -0
  196. package/dist/useScheduleDrag-BgzpQT53.js.map +1 -0
  197. package/dist/utils/formModelSync.d.ts +5 -0
  198. package/dist/utils/items.d.ts +8 -0
  199. package/dist/utils/options.d.ts +6 -0
  200. package/dist/utils/pluginIcon.d.ts +9 -0
  201. package/package.json +7 -2
  202. package/src/__tests__/components/ActionItem.test.ts +99 -0
  203. package/src/__tests__/components/AppAvatarMenu.test.ts +27 -0
  204. package/src/__tests__/components/AppLayout.test.ts +44 -0
  205. package/src/__tests__/components/AppPageSelector.test.ts +134 -0
  206. package/src/__tests__/components/AppPillNav.test.ts +125 -0
  207. package/src/__tests__/components/AppPluginSwitcher.test.ts +44 -0
  208. package/src/__tests__/components/AppSidebar.test.ts +496 -0
  209. package/src/__tests__/components/AppToastContainer.test.ts +37 -0
  210. package/src/__tests__/components/AppTopBar.test.ts +455 -9
  211. package/src/__tests__/components/BaseRadioGroup.test.ts +25 -0
  212. package/src/__tests__/components/BaseSelect.test.ts +21 -0
  213. package/src/__tests__/components/BaseTabs.test.ts +25 -0
  214. package/src/__tests__/components/BatchProgressList.test.ts +52 -0
  215. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +159 -0
  216. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +175 -0
  217. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +306 -0
  218. package/src/__tests__/components/BioTemplateRenderer.test.ts +71 -0
  219. package/src/__tests__/components/Breadcrumb.test.ts +23 -0
  220. package/src/__tests__/components/CalendarGridPanel.test.ts +36 -0
  221. package/src/__tests__/components/ComponentBindingRenderer.test.ts +161 -0
  222. package/src/__tests__/components/ConcentrationInput.test.ts +45 -0
  223. package/src/__tests__/components/ControlWorkspaceView.test.ts +1102 -0
  224. package/src/__tests__/components/DataFrame.test.ts +11 -0
  225. package/src/__tests__/components/DatePicker.test.ts +45 -0
  226. package/src/__tests__/components/DateTimePicker.test.ts +48 -0
  227. package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +185 -0
  228. package/src/__tests__/components/DropdownButton.test.ts +23 -0
  229. package/src/__tests__/components/EmptyState.test.ts +23 -0
  230. package/src/__tests__/components/ExperimentPopover.test.ts +56 -0
  231. package/src/__tests__/components/FormBuilder.test.ts +296 -0
  232. package/src/__tests__/components/GroupAssigner.test.ts +30 -0
  233. package/src/__tests__/components/MultiSelect.test.ts +48 -0
  234. package/src/__tests__/components/PluginWorkspaceView.test.ts +548 -0
  235. package/src/__tests__/components/ProtocolStepEditor.test.ts +33 -0
  236. package/src/__tests__/components/ReagentList.test.ts +82 -0
  237. package/src/__tests__/components/SampleHierarchyTree.test.ts +53 -0
  238. package/src/__tests__/components/SampleSelector.test.ts +60 -0
  239. package/src/__tests__/components/SegmentedControl.test.ts +24 -0
  240. package/src/__tests__/components/SettingsModal.test.ts +296 -0
  241. package/src/__tests__/components/TagsInput.test.ts +75 -0
  242. package/src/__tests__/components/ThemeToggle.test.ts +47 -0
  243. package/src/__tests__/components/TimePicker.test.ts +38 -0
  244. package/src/__tests__/composables/experiment-utils.test.ts +30 -0
  245. package/src/__tests__/composables/useApi.test.ts +30 -0
  246. package/src/__tests__/composables/useAppExperiment.test.ts +100 -1
  247. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +125 -0
  248. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +199 -0
  249. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +104 -0
  250. package/src/__tests__/composables/useCalendarGrid.test.ts +38 -0
  251. package/src/__tests__/composables/useControlSchema.test.ts +1033 -0
  252. package/src/__tests__/composables/useDebouncedWatch.test.ts +93 -0
  253. package/src/__tests__/composables/useDropdownState.test.ts +95 -0
  254. package/src/__tests__/composables/useEventListener.test.ts +116 -0
  255. package/src/__tests__/composables/useExpansionSet.test.ts +62 -0
  256. package/src/__tests__/composables/useExperimentData.test.ts +4 -0
  257. package/src/__tests__/composables/useExperimentSave.test.ts +203 -8
  258. package/src/__tests__/composables/useExperimentSelector.test.ts +164 -0
  259. package/src/__tests__/composables/useForm.test.ts +58 -0
  260. package/src/__tests__/composables/useFormBuilder.test.ts +77 -0
  261. package/src/__tests__/composables/useGroupAssignment.test.ts +73 -0
  262. package/src/__tests__/composables/useListSelection.test.ts +66 -0
  263. package/src/__tests__/composables/usePluginClient.test.ts +541 -0
  264. package/src/__tests__/composables/usePluginConfig.test.ts +5 -0
  265. package/src/__tests__/composables/useRequestSyncState.test.ts +92 -0
  266. package/src/__tests__/composables/useSampleGroups.test.ts +66 -0
  267. package/src/__tests__/composables/useSelectionLimit.test.ts +41 -0
  268. package/src/__tests__/composables/useSortedItems.test.ts +87 -0
  269. package/src/__tests__/composables/useTemplateCollection.test.ts +147 -0
  270. package/src/__tests__/composables/useTextSearch.test.ts +55 -0
  271. package/src/__tests__/composables/useTheme.test.ts +91 -0
  272. package/src/__tests__/composables/useTimeUtils.test.ts +35 -0
  273. package/src/__tests__/docs/frontendDocsCatalog.test.ts +324 -0
  274. package/src/__tests__/fixtures/templates/dose-response.json +81 -0
  275. package/src/__tests__/fixtures/templates/plate-map.json +54 -0
  276. package/src/__tests__/fixtures/templates/qpcr-plate.json +96 -0
  277. package/src/__tests__/fixtures/templates/sample-sheet.json +71 -0
  278. package/src/__tests__/templates/templates.test.ts +1055 -0
  279. package/src/components/AppAvatarMenu.vue +15 -69
  280. package/src/components/AppLayout.story.vue +64 -25
  281. package/src/components/AppLayout.vue +83 -2
  282. package/src/components/AppPluginSwitcher.vue +41 -145
  283. package/src/components/AppSidebar.story.vue +203 -1
  284. package/src/components/AppSidebar.vue +320 -25
  285. package/src/components/{ToastNotification.story.vue → AppToastContainer.story.vue} +6 -6
  286. package/src/components/{ToastNotification.vue → AppToastContainer.vue} +1 -1
  287. package/src/components/AppTopBar.story.vue +7 -33
  288. package/src/components/AppTopBar.vue +104 -300
  289. package/src/components/BaseModal.vue +3 -5
  290. package/src/components/BaseRadioGroup.vue +7 -3
  291. package/src/components/BaseSelect.vue +11 -7
  292. package/src/components/BaseTabs.vue +6 -4
  293. package/src/components/BatchProgressList.vue +5 -8
  294. package/src/components/BioTemplateExperimentWorkspaceView.story.vue +123 -0
  295. package/src/components/BioTemplateExperimentWorkspaceView.vue +343 -0
  296. package/src/components/BioTemplatePackWorkspaceView.story.vue +107 -0
  297. package/src/components/BioTemplatePackWorkspaceView.vue +177 -0
  298. package/src/components/BioTemplatePresetWorkspaceView.story.vue +163 -0
  299. package/src/components/BioTemplatePresetWorkspaceView.vue +401 -0
  300. package/src/components/BioTemplateRenderer.story.vue +57 -0
  301. package/src/components/BioTemplateRenderer.vue +57 -0
  302. package/src/components/Breadcrumb.vue +14 -8
  303. package/src/components/ComponentBindingRenderer.story.vue +57 -0
  304. package/src/components/ComponentBindingRenderer.vue +308 -0
  305. package/src/components/ConcentrationInput.vue +27 -64
  306. package/src/components/ControlWorkspaceView.story.vue +347 -0
  307. package/src/components/ControlWorkspaceView.vue +378 -0
  308. package/src/components/DataFrame.vue +34 -50
  309. package/src/components/DatePicker.vue +59 -192
  310. package/src/components/DateTimePicker.vue +50 -171
  311. package/src/components/DoseDesignWorkspaceView.story.vue +77 -0
  312. package/src/components/DoseDesignWorkspaceView.vue +255 -0
  313. package/src/components/DropdownButton.vue +14 -32
  314. package/src/components/EmptyState.vue +4 -2
  315. package/src/components/ExperimentPopover.vue +7 -28
  316. package/src/components/ExperimentSelectorModal.vue +6 -5
  317. package/src/components/FormBuilder.story.vue +190 -0
  318. package/src/components/FormBuilder.vue +124 -27
  319. package/src/components/GroupAssigner.vue +24 -56
  320. package/src/components/MultiSelect.vue +17 -12
  321. package/src/components/PlateMapEditor.vue +3 -8
  322. package/src/components/PluginIcon.vue +2 -22
  323. package/src/components/PluginWorkspaceView.story.vue +334 -0
  324. package/src/components/PluginWorkspaceView.vue +708 -0
  325. package/src/components/ProtocolStepEditor.vue +13 -22
  326. package/src/components/ReagentList.vue +25 -33
  327. package/src/components/SampleHierarchyTree.vue +12 -23
  328. package/src/components/SampleSelector.vue +42 -122
  329. package/src/components/SegmentedControl.vue +7 -3
  330. package/src/components/SettingsModal.story.vue +88 -1
  331. package/src/components/SettingsModal.vue +120 -29
  332. package/src/components/TagsInput.vue +29 -14
  333. package/src/components/ThemeToggle.vue +9 -7
  334. package/src/components/TimePicker.vue +19 -41
  335. package/src/components/Tooltip.vue +7 -12
  336. package/src/components/WellPlate.vue +6 -12
  337. package/src/components/index.ts +9 -8
  338. package/src/components/internal/ActionItemInternal.vue +82 -0
  339. package/src/components/internal/AppPageSelectorInternal.vue +128 -0
  340. package/src/components/internal/AppPillNavInternal.vue +194 -0
  341. package/src/components/internal/CalendarGridPanelInternal.vue +120 -0
  342. package/src/components/{FormFieldRenderer.vue → internal/FormFieldRendererInternal.vue} +4 -12
  343. package/src/components/{FormSection.vue → internal/FormSectionRenderer.vue} +6 -18
  344. package/src/components/{WellEditPopup.vue → internal/WellEditPopupInternal.vue} +5 -10
  345. package/src/composables/experiment-utils.ts +26 -0
  346. package/src/composables/index.ts +229 -3
  347. package/src/composables/platformContextHelpers.ts +74 -0
  348. package/src/composables/useApi.ts +9 -2
  349. package/src/composables/useAppExperiment.ts +85 -13
  350. package/src/composables/useBioTemplateComponents.ts +105 -0
  351. package/src/composables/useBioTemplateControls.ts +41 -0
  352. package/src/composables/useBioTemplatePackWorkspace.ts +185 -0
  353. package/src/composables/useBioTemplatePresetWorkspace.ts +326 -0
  354. package/src/composables/useBioTemplateWorkspace.ts +141 -0
  355. package/src/composables/useCalendarGrid.ts +140 -0
  356. package/src/composables/useControlSchema.ts +1362 -0
  357. package/src/composables/useDebouncedWatch.ts +119 -0
  358. package/src/composables/useDropdownState.ts +83 -0
  359. package/src/composables/useEventListener.ts +111 -0
  360. package/src/composables/useExpansionSet.ts +117 -0
  361. package/src/composables/useExperimentData.ts +20 -11
  362. package/src/composables/useExperimentSave.ts +202 -50
  363. package/src/composables/useExperimentSelector.ts +86 -72
  364. package/src/composables/useForm.ts +49 -4
  365. package/src/composables/useFormBuilder.ts +93 -42
  366. package/src/composables/useGroupAssignment.ts +148 -0
  367. package/src/composables/useListSelection.ts +158 -0
  368. package/src/composables/usePluginClient.ts +466 -0
  369. package/src/composables/usePluginConfig.ts +34 -13
  370. package/src/composables/useRequestSyncState.ts +126 -0
  371. package/src/composables/useSampleGroups.ts +126 -0
  372. package/src/composables/useSelectionLimit.ts +57 -0
  373. package/src/composables/useSortedItems.ts +118 -0
  374. package/src/composables/useTemplateCollection.ts +229 -0
  375. package/src/composables/useTextSearch.ts +60 -0
  376. package/src/composables/useTheme.ts +2 -28
  377. package/src/composables/useTimeUtils.ts +26 -2
  378. package/src/composables/useWellPlateEditor.ts +13 -9
  379. package/src/index.ts +11 -348
  380. package/src/install.ts +11 -4
  381. package/src/stores/settings.ts +13 -9
  382. package/src/styles/components/app-layout.css +82 -0
  383. package/src/styles/components/app-page-selector.css +23 -0
  384. package/src/styles/components/app-pill-nav.css +77 -0
  385. package/src/styles/components/app-sidebar.css +119 -0
  386. package/src/styles/components/app-top-bar.css +0 -201
  387. package/src/styles/components/concentration-input.css +3 -142
  388. package/src/styles/components/empty-state.css +0 -16
  389. package/src/styles/components/theme-toggle.css +3 -66
  390. package/src/styles/index.css +0 -2
  391. package/src/templates/adapters.ts +785 -0
  392. package/src/templates/builders.ts +2149 -0
  393. package/src/templates/catalog.ts +245 -0
  394. package/src/templates/componentBindings.ts +653 -0
  395. package/src/templates/controlSchemas.ts +718 -0
  396. package/src/templates/index.ts +318 -0
  397. package/src/templates/lookup.ts +18 -0
  398. package/src/templates/packs.ts +156 -0
  399. package/src/templates/presets.ts +146 -0
  400. package/src/templates/types.ts +668 -0
  401. package/src/types/components.ts +39 -27
  402. package/src/types/form-builder.ts +7 -2
  403. package/src/types/index.ts +13 -3
  404. package/src/types/platform.ts +7 -1
  405. package/src/utils/formModelSync.ts +52 -0
  406. package/src/utils/items.ts +28 -0
  407. package/src/utils/options.ts +23 -0
  408. package/src/utils/pluginIcon.ts +30 -0
  409. package/dist/__tests__/composables/usePluginApi.test.d.ts +0 -13
  410. package/dist/auth-DsI0rQ7_.js.map +0 -1
  411. package/dist/components/GroupingModal.vue.d.ts +0 -12
  412. package/dist/components-_XqPEhP9.js.map +0 -1
  413. package/dist/composables/usePluginApi.d.ts +0 -29
  414. package/dist/composables-tiZqLu1M.js.map +0 -1
  415. package/dist/useScheduleDrag-CA9sGNJG.js +0 -7181
  416. package/dist/useScheduleDrag-CA9sGNJG.js.map +0 -1
  417. package/src/__tests__/composables/usePluginApi.test.ts +0 -81
  418. package/src/components/AppPageSelector.vue +0 -159
  419. package/src/components/AppPillNav.vue +0 -66
  420. package/src/components/GroupingModal.story.vue +0 -52
  421. package/src/components/GroupingModal.vue +0 -422
  422. package/src/components/SettingsButton.story.vue +0 -58
  423. package/src/components/SettingsButton.vue +0 -76
  424. package/src/composables/usePluginApi.ts +0 -39
  425. package/src/styles/components/grouping-modal.css +0 -323
  426. package/src/styles/components/settings-button.css +0 -94
  427. /package/dist/components/{ToastNotification.vue.d.ts → AppToastContainer.vue.d.ts} +0 -0
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  /** Form for creating or editing a single protocol step (incubation, wash, addition, centrifuge, etc.) with template picker, typed parameters, and duration. */
3
- import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
3
+ import { ref, computed, watch, onMounted } from 'vue'
4
4
  import type { ProtocolStep, ProtocolStepType, ProtocolStepStatus } from '../types'
5
+ import { useDropdownState } from '../composables/useDropdownState'
5
6
  import {
6
7
  useProtocolTemplates,
7
8
  type StepTemplate,
@@ -38,7 +39,12 @@ const {
38
39
 
39
40
  // State
40
41
  const selectedTemplateId = ref<string | null>(null)
41
- const dropdownOpen = ref(false)
42
+ const {
43
+ isOpen: templateDropdownOpen,
44
+ rootRef: templateDropdownRef,
45
+ close: closeTemplateDropdown,
46
+ toggle: toggleTemplateDropdown,
47
+ } = useDropdownState()
42
48
  const stepName = ref('')
43
49
  const stepDescription = ref('')
44
50
  const stepDuration = ref<number | undefined>()
@@ -113,7 +119,7 @@ function initFromStep(step: ProtocolStep) {
113
119
  // Handle template selection
114
120
  function selectTemplate(template: StepTemplate) {
115
121
  selectedTemplateId.value = template.id
116
- dropdownOpen.value = false
122
+ closeTemplateDropdown()
117
123
 
118
124
  // Reset to template defaults if creating new
119
125
  if (props.mode === 'create') {
@@ -185,14 +191,6 @@ function handleCancel() {
185
191
  emit('cancel')
186
192
  }
187
193
 
188
- // Close dropdown on click outside
189
- function handleClickOutside(event: MouseEvent) {
190
- const target = event.target as HTMLElement
191
- if (!target.closest('.mint-protocol-editor__template-dropdown')) {
192
- dropdownOpen.value = false
193
- }
194
- }
195
-
196
194
  // Format duration
197
195
  function formatDuration(minutes: number | undefined): string {
198
196
  if (minutes === undefined) return ''
@@ -202,10 +200,7 @@ function formatDuration(minutes: number | undefined): string {
202
200
  return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`
203
201
  }
204
202
 
205
- // Lifecycle
206
203
  onMounted(() => {
207
- document.addEventListener('click', handleClickOutside)
208
-
209
204
  if (props.modelValue) {
210
205
  initFromStep(props.modelValue)
211
206
  } else if (availableTemplates.value.length > 0) {
@@ -213,10 +208,6 @@ onMounted(() => {
213
208
  }
214
209
  })
215
210
 
216
- onUnmounted(() => {
217
- document.removeEventListener('click', handleClickOutside)
218
- })
219
-
220
211
  // Watch for external changes
221
212
  watch(
222
213
  () => props.modelValue,
@@ -250,11 +241,11 @@ watch(
250
241
  <!-- Template selector -->
251
242
  <div class="mint-protocol-editor__template-select">
252
243
  <label class="mint-protocol-editor__template-label">Template</label>
253
- <div class="mint-protocol-editor__template-dropdown">
244
+ <div ref="templateDropdownRef" class="mint-protocol-editor__template-dropdown">
254
245
  <button
255
246
  type="button"
256
247
  class="mint-protocol-editor__template-btn"
257
- @click.stop="dropdownOpen = !dropdownOpen"
248
+ @click="toggleTemplateDropdown"
258
249
  >
259
250
  <svg
260
251
  v-if="selectedTemplate"
@@ -276,7 +267,7 @@ watch(
276
267
  <svg
277
268
  :class="[
278
269
  'mint-protocol-editor__template-arrow',
279
- dropdownOpen ? 'mint-protocol-editor__template-arrow--open' : '',
270
+ templateDropdownOpen ? 'mint-protocol-editor__template-arrow--open' : '',
280
271
  ]"
281
272
  fill="none"
282
273
  stroke="currentColor"
@@ -286,7 +277,7 @@ watch(
286
277
  </svg>
287
278
  </button>
288
279
 
289
- <div v-if="dropdownOpen" class="mint-protocol-editor__template-menu">
280
+ <div v-if="templateDropdownOpen" class="mint-protocol-editor__template-menu">
290
281
  <button
291
282
  v-for="template in availableTemplates"
292
283
  :key="template.id"
@@ -2,6 +2,8 @@
2
2
  /** Sortable, searchable table of experiment reagents showing lot number, expiry, storage conditions, and stock level with low-stock warnings. */
3
3
  import { ref, computed } from 'vue'
4
4
  import type { ReagentColumn, Reagent } from '../types'
5
+ import { useSortedItems } from '../composables/useSortedItems'
6
+ import { useTextSearch } from '../composables/useTextSearch'
5
7
 
6
8
  interface Props {
7
9
  modelValue?: Reagent[]
@@ -50,43 +52,33 @@ const columnLabels: Record<ReagentColumn, string> = {
50
52
  supplier: 'Supplier',
51
53
  }
52
54
 
53
- // Computed
54
- const filteredReagents = computed(() => {
55
- let result = [...(props.modelValue || [])]
56
-
57
- // Filter by search query
58
- if (searchQuery.value) {
59
- const query = searchQuery.value.toLowerCase()
60
- result = result.filter(
61
- (r) =>
62
- r.name.toLowerCase().includes(query) ||
63
- r.catalogNumber?.toLowerCase().includes(query) ||
64
- r.lotNumber?.toLowerCase().includes(query) ||
65
- r.supplier?.toLowerCase().includes(query)
66
- )
67
- }
68
-
69
- // Sort
70
- if (sortColumn.value) {
71
- const col = sortColumn.value
72
- result.sort((a, b) => {
73
- let aVal: string | number | null | undefined = getColumnValue(a, col) as string | number | null | undefined
74
- let bVal: string | number | null | undefined = getColumnValue(b, col) as string | number | null | undefined
75
-
76
- if (aVal === null || aVal === undefined) return 1
77
- if (bVal === null || bVal === undefined) return -1
78
-
79
- if (typeof aVal === 'string') aVal = aVal.toLowerCase()
80
- if (typeof bVal === 'string') bVal = bVal.toLowerCase()
55
+ const reagentSearch = useTextSearch({
56
+ items: () => props.modelValue || [],
57
+ query: searchQuery,
58
+ enabled: () => props.searchable,
59
+ getText: (reagent) => [
60
+ reagent.name,
61
+ reagent.catalogNumber,
62
+ reagent.lotNumber,
63
+ reagent.supplier,
64
+ ],
65
+ })
81
66
 
82
- if (aVal < bVal) return sortDirection.value === 'asc' ? -1 : 1
83
- if (aVal > bVal) return sortDirection.value === 'asc' ? 1 : -1
84
- return 0
85
- })
67
+ const reagentSort = computed(() => {
68
+ if (!sortColumn.value) return null
69
+ return {
70
+ key: sortColumn.value,
71
+ direction: sortDirection.value,
86
72
  }
73
+ })
87
74
 
88
- return result
75
+ const sortedReagents = useSortedItems<Reagent, ReagentColumn>({
76
+ items: reagentSearch.filteredItems,
77
+ sort: reagentSort,
78
+ caseSensitive: false,
79
+ getValue: (reagent, column) => getColumnValue(reagent, column),
89
80
  })
81
+ const filteredReagents = sortedReagents.sortedItems
90
82
 
91
83
  function getColumnValue(reagent: Reagent, column: ReagentColumn): unknown {
92
84
  switch (column) {
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  /** Collapsible tree visualizing the biological sample hierarchy (study → experiment → plate → sample → cell line → passage → clone → treatment). */
3
- import { ref, watch, type VNode } from 'vue'
4
- import { h, Transition } from 'vue'
3
+ import { h, Transition, type VNode } from 'vue'
5
4
  import type { TreeNodeType, BadgeVariant, TreeNode } from '../types'
5
+ import { useExpansionSet } from '../composables/useExpansionSet'
6
6
 
7
7
  interface Props {
8
8
  nodes: TreeNode[]
@@ -28,9 +28,6 @@ const emit = defineEmits<{
28
28
  'collapse': [nodeId: string]
29
29
  }>()
30
30
 
31
- // Track expanded nodes
32
- const expandedIds = ref<Set<string>>(new Set(props.defaultExpandedIds))
33
-
34
31
  // Default icons by type (Lucide SVG paths - arrays to support multi-path icons)
35
32
  interface IconElement {
36
33
  tag: 'path' | 'circle' | 'rect'
@@ -100,30 +97,22 @@ function collectAllIds(nodes: TreeNode[]): string[] {
100
97
  return ids
101
98
  }
102
99
 
103
- // Watch for expandAll changes
104
- watch(
105
- () => props.expandAll,
106
- (shouldExpandAll) => {
107
- if (shouldExpandAll) {
108
- expandedIds.value = new Set(collectAllIds(props.nodes))
109
- } else {
110
- expandedIds.value = new Set(props.defaultExpandedIds)
111
- }
112
- },
113
- { immediate: true }
114
- )
100
+ const expansion = useExpansionSet({
101
+ defaultIds: () => props.defaultExpandedIds,
102
+ allIds: () => collectAllIds(props.nodes),
103
+ expandAll: () => props.expandAll,
104
+ })
115
105
 
116
106
  function isExpanded(nodeId: string): boolean {
117
- return expandedIds.value.has(nodeId)
107
+ return expansion.isExpanded(nodeId)
118
108
  }
119
109
 
120
110
  function toggleExpand(node: TreeNode) {
121
- if (isExpanded(node.id)) {
122
- expandedIds.value.delete(node.id)
123
- emit('collapse', node.id)
124
- } else {
125
- expandedIds.value.add(node.id)
111
+ const expanded = expansion.toggle(node.id)
112
+ if (expanded) {
126
113
  emit('expand', node.id)
114
+ } else {
115
+ emit('collapse', node.id)
127
116
  }
128
117
  }
129
118
 
@@ -6,8 +6,11 @@ import BaseInput from './BaseInput.vue'
6
6
  import AutoGroupModal from './AutoGroupModal.vue'
7
7
  import type { SampleGroup } from '../types'
8
8
  import type { AutoGroupResult } from '../types/auto-group'
9
- import { deriveShade } from '../utils/color'
10
9
  import { DEFAULT_COLORS } from '../composables/useAutoGroup'
10
+ import { useTextSearch } from '../composables/useTextSearch'
11
+ import { useListSelection } from '../composables/useListSelection'
12
+ import { useSampleGroups, type SampleMajorGroup } from '../composables/useSampleGroups'
13
+ import { useExpansionSet } from '../composables/useExpansionSet'
11
14
 
12
15
  interface Props {
13
16
  samples: string[]
@@ -42,8 +45,8 @@ const showSmartGroupModal = ref(false)
42
45
  const newGroupName = ref('')
43
46
  const editingColor = ref<ColorEdit | null>(null)
44
47
  const colorPickerInput = ref<HTMLInputElement | null>(null)
45
- const expandedGroups = ref<Record<string, boolean>>({})
46
48
  const searchQuery = ref('')
49
+ const groupExpansion = useExpansionSet()
47
50
 
48
51
  // Sample Drag State
49
52
  const draggingSample = ref<string | null>(null)
@@ -63,184 +66,102 @@ const internalGroups = computed({
63
66
  set: (value) => emit('update:groups', value),
64
67
  })
65
68
 
66
- interface DisplaySubGroup extends SampleGroup {
67
- displayColor: string
68
- // Pre-built `displayColor + alpha` strings hoisted out of the template so
69
- // each render re-uses the same string reference instead of re-allocating.
70
- displayBg: string
71
- displayBorder: string
72
- }
73
-
74
- interface MajorGroup {
75
- name: string
76
- color: string
77
- subGroups: DisplaySubGroup[]
78
- allSamples: string[]
79
- }
80
-
81
- const hierarchicalGroups = computed<MajorGroup[]>(() => {
82
- const groups = internalGroups.value
83
- if (groups.length === 0) return []
84
-
85
- // Detect separator: use '/' if any group name contains it, otherwise '_'
86
- const separator = groups.some(g => g.name.includes('/')) ? '/' : '_'
87
- const majorGroupMap: Record<string, SampleGroup[]> = {}
88
-
89
- for (const group of groups) {
90
- const parts = group.name.split(separator)
91
- // Use first part as major group, or full name if no separator
92
- const majorPrefix = parts.length > 1 ? parts[0] : group.name
93
-
94
- if (!majorGroupMap[majorPrefix]) {
95
- majorGroupMap[majorPrefix] = []
96
- }
97
- majorGroupMap[majorPrefix].push(group)
98
- }
99
-
100
- const result: MajorGroup[] = []
101
-
102
- // Preserve insertion order from internalGroups so manual drag-reorder sticks.
103
- for (const [majorName, subGroups] of Object.entries(majorGroupMap)) {
104
- const allSamples = subGroups.flatMap(g => g.samples)
105
- const color = subGroups[0]?.color || '#3B82F6'
106
- const displaySubs: DisplaySubGroup[] = subGroups.map((sub, i) => {
107
- const displayColor = deriveShade(color, i, subGroups.length)
108
- return {
109
- ...sub,
110
- displayColor,
111
- displayBg: displayColor + '20',
112
- displayBorder: displayColor + '40',
113
- }
114
- })
115
-
116
- result.push({ name: majorName, color, subGroups: displaySubs, allSamples })
117
- }
118
-
119
- return result
120
- })
121
-
122
- // Check if hierarchy is meaningful (major groups have multiple sub-groups)
123
- // If each major group only has 1 sub-group with same name, show flat view instead
124
- const showHierarchy = computed(() => {
125
- const groups = hierarchicalGroups.value
126
- if (groups.length === 0) return false
127
-
128
- // Show hierarchy if any major group has multiple sub-groups
129
- // OR if major group name differs from its sub-group name
130
- return groups.some(major =>
131
- major.subGroups.length > 1 ||
132
- (major.subGroups.length === 1 && major.name !== major.subGroups[0].name)
133
- )
69
+ const sampleGroups = useSampleGroups({
70
+ samples: () => props.samples,
71
+ groups: internalGroups,
134
72
  })
73
+ const hierarchicalGroups = sampleGroups.hierarchicalGroups
74
+ const showHierarchy = sampleGroups.showHierarchy
135
75
 
136
76
  const groupingEnabled = computed(() => internalGroups.value.length > 0)
77
+ const ungroupedSamples = sampleGroups.ungroupedSamples
137
78
 
138
- const ungroupedSamples = computed(() => {
139
- const groupedSamples = new Set(
140
- internalGroups.value.flatMap(g => g.samples)
141
- )
142
- return props.samples.filter(s => !groupedSamples.has(s))
79
+ const sampleSearch = useTextSearch({
80
+ items: () => props.samples,
81
+ query: searchQuery,
82
+ getText: sample => sample,
143
83
  })
84
+ const filteredSamples = sampleSearch.filteredItems
144
85
 
145
- const filteredSamples = computed(() => {
146
- if (!searchQuery.value.trim()) return props.samples
147
- const query = searchQuery.value.toLowerCase()
148
- return props.samples.filter(s => s.toLowerCase().includes(query))
86
+ const sampleSelection = useListSelection({
87
+ selected: () => props.modelValue,
88
+ items: () => props.samples,
149
89
  })
150
-
151
-
152
- // Selection state
153
- const isAllSelected = computed(() =>
154
- props.samples.length > 0 && props.modelValue.length === props.samples.length
155
- )
90
+ const isAllSelected = sampleSelection.isAllSelected
156
91
 
157
92
  // Toggle functions
158
93
  function toggleSelectAll() {
159
- if (isAllSelected.value) {
160
- emit('update:modelValue', [])
161
- } else {
162
- emit('update:modelValue', [...props.samples])
163
- }
94
+ emit('update:modelValue', sampleSelection.toggleAll())
164
95
  }
165
96
 
166
97
  function toggleSample(sample: string) {
167
- const newSelection = props.modelValue.includes(sample)
168
- ? props.modelValue.filter(s => s !== sample)
169
- : [...props.modelValue, sample]
170
- emit('update:modelValue', newSelection)
98
+ emit('update:modelValue', sampleSelection.toggleValue(sample))
171
99
  }
172
100
 
173
101
  function toggleSamplesSelection(samples: string[]) {
174
- const allSelected = samples.every(s => props.modelValue.includes(s))
175
- const newSelection = allSelected
176
- ? props.modelValue.filter(s => !samples.includes(s))
177
- : [...props.modelValue, ...samples.filter(s => !props.modelValue.includes(s))]
178
-
179
- emit('update:modelValue', newSelection)
102
+ emit('update:modelValue', sampleSelection.toggleValues(samples))
180
103
  }
181
104
 
182
105
  function toggleGroupSamples(groupName: string) {
183
- const group = internalGroups.value.find(g => g.name === groupName)
106
+ const group = sampleGroups.findGroup(groupName)
184
107
  if (!group) return
185
108
  toggleSamplesSelection(group.samples)
186
109
  }
187
110
 
188
- function toggleMajorGroupSamples(majorGroup: MajorGroup) {
111
+ function toggleMajorGroupSamples(majorGroup: SampleMajorGroup) {
189
112
  toggleSamplesSelection(majorGroup.allSamples)
190
113
  }
191
114
 
192
115
  // Selection state checks
193
116
  function isFullySelected(samples: string[]): boolean {
194
- return samples.length > 0 && samples.every(s => props.modelValue.includes(s))
117
+ return sampleSelection.isFullySelected(samples)
195
118
  }
196
119
 
197
120
  function isPartiallySelected(samples: string[]): boolean {
198
- if (samples.length === 0) return false
199
- const selectedCount = samples.filter(s => props.modelValue.includes(s)).length
200
- return selectedCount > 0 && selectedCount < samples.length
121
+ return sampleSelection.isPartiallySelected(samples)
201
122
  }
202
123
 
203
124
  function isGroupFullySelected(groupName: string): boolean {
204
- const group = internalGroups.value.find(g => g.name === groupName)
125
+ const group = sampleGroups.findGroup(groupName)
205
126
  return group ? isFullySelected(group.samples) : false
206
127
  }
207
128
 
208
129
  function isGroupPartiallySelected(groupName: string): boolean {
209
- const group = internalGroups.value.find(g => g.name === groupName)
130
+ const group = sampleGroups.findGroup(groupName)
210
131
  return group ? isPartiallySelected(group.samples) : false
211
132
  }
212
133
 
213
- function isMajorGroupFullySelected(majorGroup: MajorGroup): boolean {
134
+ function isMajorGroupFullySelected(majorGroup: SampleMajorGroup): boolean {
214
135
  return isFullySelected(majorGroup.allSamples)
215
136
  }
216
137
 
217
- function isMajorGroupPartiallySelected(majorGroup: MajorGroup): boolean {
138
+ function isMajorGroupPartiallySelected(majorGroup: SampleMajorGroup): boolean {
218
139
  return isPartiallySelected(majorGroup.allSamples)
219
140
  }
220
141
 
221
142
  // Expand/collapse
222
143
  function toggleGroupExpanded(groupName: string) {
223
- expandedGroups.value[groupName] = !expandedGroups.value[groupName]
144
+ groupExpansion.toggle(groupName)
224
145
  }
225
146
 
226
147
  function isGroupExpanded(groupName: string): boolean {
227
- return !!expandedGroups.value[groupName]
148
+ return groupExpansion.isExpanded(groupName)
228
149
  }
229
150
 
230
151
  function expandAllGroups() {
231
- const expanded: Record<string, boolean> = {}
152
+ const expanded: string[] = []
232
153
  for (const major of hierarchicalGroups.value) {
233
- expanded[`major:${major.name}`] = true
154
+ expanded.push(`major:${major.name}`)
234
155
  for (const sub of major.subGroups) {
235
- expanded[sub.name] = true
156
+ expanded.push(sub.name)
236
157
  }
237
158
  }
238
- expanded['__ungrouped__'] = true
239
- expandedGroups.value = expanded
159
+ expanded.push('__ungrouped__')
160
+ groupExpansion.setExpanded(expanded)
240
161
  }
241
162
 
242
163
  function collapseAllGroups() {
243
- expandedGroups.value = {}
164
+ groupExpansion.collapseAll()
244
165
  }
245
166
 
246
167
  // Smart group
@@ -254,7 +175,7 @@ function clearGroups() {
254
175
  internalGroups.value = []
255
176
  }
256
177
 
257
- function deleteMajorGroup(majorGroup: MajorGroup) {
178
+ function deleteMajorGroup(majorGroup: SampleMajorGroup) {
258
179
  internalGroups.value = internalGroups.value.filter(
259
180
  g => !majorGroup.subGroups.some(sg => sg.name === g.name)
260
181
  )
@@ -452,7 +373,7 @@ function openColorPicker(groupName: string, event: Event) {
452
373
  colorPickerInput.value?.click()
453
374
  }
454
375
 
455
- function openMajorGroupColorPicker(majorGroup: MajorGroup, event: Event) {
376
+ function openMajorGroupColorPicker(majorGroup: SampleMajorGroup, event: Event) {
456
377
  event.stopPropagation()
457
378
  editingColor.value = { kind: 'family', names: majorGroup.subGroups.map(sg => sg.name) }
458
379
  colorPickerInput.value?.click()
@@ -473,8 +394,7 @@ function handleColorChange(event: Event) {
473
394
  }
474
395
 
475
396
  function getGroupColor(groupName: string): string {
476
- const group = internalGroups.value.find(g => g.name === groupName)
477
- return group?.color || '#3B82F6'
397
+ return sampleGroups.getGroupColor(groupName)
478
398
  }
479
399
 
480
400
  const colorPickerSeed = computed(() => {
@@ -1,10 +1,12 @@
1
1
  <script setup lang="ts">
2
2
  /** Renders a segmented button group for single-option selection, with simple or solid variants. */
3
- import type { SegmentedOption, SegmentedControlVariant, SegmentedControlSize } from '../types'
3
+ import { computed } from 'vue'
4
+ import type { SegmentedOption, SegmentedOptionInput, SegmentedControlVariant, SegmentedControlSize } from '../types'
5
+ import { normalizeOptionInput } from '../utils/options'
4
6
 
5
7
  interface Props {
6
8
  modelValue: string | number
7
- options: SegmentedOption[]
9
+ options: SegmentedOptionInput[]
8
10
  variant?: SegmentedControlVariant
9
11
  size?: SegmentedControlSize
10
12
  fullWidth?: boolean
@@ -22,6 +24,8 @@ const emit = defineEmits<{
22
24
  'update:modelValue': [value: string | number]
23
25
  }>()
24
26
 
27
+ const normalizedOptions = computed<SegmentedOption[]>(() => props.options.map(normalizeOptionInput))
28
+
25
29
  function handleSelect(option: SegmentedOption) {
26
30
  if (props.disabled || option.disabled) return
27
31
  emit('update:modelValue', option.value)
@@ -47,7 +51,7 @@ function handleKeydown(event: KeyboardEvent, option: SegmentedOption) {
47
51
  role="radiogroup"
48
52
  >
49
53
  <button
50
- v-for="option in options"
54
+ v-for="option in normalizedOptions"
51
55
  :key="String(option.value)"
52
56
  type="button"
53
57
  role="radio"
@@ -7,6 +7,7 @@ import BaseSelect from './BaseSelect.vue'
7
7
  import BaseCheckbox from './BaseCheckbox.vue'
8
8
  import BaseToggle from './BaseToggle.vue'
9
9
  import NumberInput from './NumberInput.vue'
10
+ import { defineControlModel } from '../composables/useControlSchema'
10
11
  import type {
11
12
  SettingsTab,
12
13
  SettingsModalLayout,
@@ -19,6 +20,7 @@ const customOpen = ref(false)
19
20
  const appearanceOnlyOpen = ref(false)
20
21
  const verticalOpen = ref(false)
21
22
  const schemaOpen = ref(false)
23
+ const modelOpen = ref(false)
22
24
 
23
25
  const customTabs: SettingsTab[] = [
24
26
  { id: 'general', label: 'General' },
@@ -109,7 +111,7 @@ const outlierOptions: SelectOption<string>[] = [
109
111
 
110
112
  // ─────────── Schema-driven (recommended) ───────────
111
113
  // Plugin authors describe parameters once; the modal auto-renders every field
112
- // via the SDK's FormFieldRenderer registry. Compare to the "Vertical Layout"
114
+ // via the SDK form field registry. Compare to the "Vertical Layout"
113
115
  // variant above, which builds the same surface manually.
114
116
  const schemaSettings = ref<Record<string, unknown>>({
115
117
  pluginName: 'IC50 Calculator',
@@ -208,6 +210,66 @@ const settingsSchema: SettingsModalSchema = {
208
210
  ],
209
211
  }
210
212
 
213
+ const settingsModel = defineControlModel({
214
+ views: {
215
+ settings: {
216
+ label: 'Settings',
217
+ sections: {
218
+ general: {
219
+ label: 'General',
220
+ description: 'Plugin name, defaults, locale',
221
+ icon: iconGeneral,
222
+ controls: {
223
+ pluginName: {
224
+ label: 'Plugin Display Name',
225
+ default: 'IC50 Calculator',
226
+ required: true,
227
+ },
228
+ locale: {
229
+ label: 'Locale',
230
+ default: 'en-US',
231
+ options: localeOptions,
232
+ },
233
+ },
234
+ },
235
+ model: {
236
+ label: 'Model Parameters',
237
+ description: 'Curve choice and bounds',
238
+ icon: iconModel,
239
+ columns: 2,
240
+ controls: {
241
+ curveModel: {
242
+ label: 'Default Curve Model',
243
+ default: '4pl',
244
+ options: curveModelOptions,
245
+ },
246
+ bottom: { type: 'number', label: 'Bottom bound', default: 0 },
247
+ top: { type: 'number', label: 'Top bound', default: 100 },
248
+ },
249
+ },
250
+ fitting: {
251
+ label: 'Curve Fitting',
252
+ description: 'Optimizer and transforms',
253
+ icon: iconCurve,
254
+ controls: {
255
+ optimizer: {
256
+ label: 'Optimizer',
257
+ default: 'lm',
258
+ options: optimizerOptions,
259
+ },
260
+ logTransform: {
261
+ label: 'Apply log-dose transform',
262
+ default: true,
263
+ type: 'toggle',
264
+ },
265
+ },
266
+ },
267
+ },
268
+ },
269
+ },
270
+ })
271
+ const modelSettings = ref<Record<string, unknown>>({})
272
+
211
273
  const sizes: Array<'md' | 'lg' | 'xl'> = ['md', 'lg', 'xl']
212
274
  const layouts: SettingsModalLayout[] = ['horizontal', 'vertical']
213
275
  </script>
@@ -406,6 +468,31 @@ const layouts: SettingsModalLayout[] = ['horizontal', 'vertical']
406
468
  </div>
407
469
  </Variant>
408
470
 
471
+ <Variant title="Model Driven Settings">
472
+ <div style="padding: 2rem; display: flex; flex-direction: column; gap: 1rem; align-items: center;">
473
+ <button
474
+ type="button"
475
+ style="padding: 0.5rem 1rem; border: 1px solid var(--border-color, #e2e8f0); border-radius: 0.375rem; background: var(--bg-card, #fff); color: var(--text-primary, #1e293b); cursor: pointer; font-size: 0.875rem;"
476
+ @click="modelOpen = true"
477
+ >
478
+ Open Settings (from model)
479
+ </button>
480
+ <details style="font-size: 0.75rem; color: var(--text-secondary); max-width: 36rem; width: 100%;">
481
+ <summary style="cursor: pointer; user-select: none;">Live values</summary>
482
+ <pre style="margin: 0.5rem 0 0; padding: 0.75rem; background: var(--bg-tertiary); border-radius: 0.375rem; font-size: 0.6875rem; overflow-x: auto;">{{ JSON.stringify(modelSettings, null, 2) }}</pre>
483
+ </details>
484
+ <SettingsModal
485
+ v-model="modelOpen"
486
+ v-model:values="modelSettings"
487
+ title="IC50 Plugin Settings"
488
+ :model="settingsModel"
489
+ :show-appearance="true"
490
+ size="xl"
491
+ layout="vertical"
492
+ />
493
+ </div>
494
+ </Variant>
495
+
409
496
  <Variant title="Schema-Driven (Auto-Render)">
410
497
  <div style="padding: 2rem; display: flex; flex-direction: column; gap: 1rem; align-items: center;">
411
498
  <button