@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,4 +1,4 @@
1
- import { ref, reactive, computed, watch, type Ref } from 'vue'
1
+ import { ref, reactive, computed, toRaw, watch, type Ref } from 'vue'
2
2
 
3
3
  /**
4
4
  * Validation rule function type.
@@ -58,6 +58,8 @@ export interface UseFormReturn<T extends Record<string, unknown>> {
58
58
  validateField: (field: string) => boolean
59
59
  validate: () => boolean
60
60
  reset: (values?: Partial<T>) => void
61
+ /** Replace the full form state and treat the provided values as the new clean baseline. */
62
+ replaceState: (values: T) => void
61
63
  handleSubmit: (onSubmit: (data: T) => Promise<void> | void) => (e?: Event) => Promise<void>
62
64
  getFieldProps: <K extends keyof T>(field: K) => {
63
65
  modelValue: T[K]
@@ -156,11 +158,13 @@ export function useForm<T extends Record<string, unknown>>(
156
158
  initialValues: T,
157
159
  rules: Partial<Record<keyof T, FieldRules>> = {}
158
160
  ): UseFormReturn<T> {
161
+ const cloneableInitialValues = deepToRaw(initialValues) as T
162
+
159
163
  // Deep copy initial values so nested objects are not shared
160
- const _initialValues = structuredClone(initialValues)
164
+ let _initialValues = structuredClone(cloneableInitialValues)
161
165
 
162
166
  // Reactive form data
163
- const data = reactive(structuredClone(initialValues)) as T
167
+ const data = reactive(structuredClone(cloneableInitialValues)) as T
164
168
 
165
169
  // Field state - use simple Record types for better TS compatibility
166
170
  const errors = reactive<Record<string, string | null>>(
@@ -355,7 +359,27 @@ export function useForm<T extends Record<string, unknown>>(
355
359
  function reset(values?: Partial<T>): void {
356
360
  const resetValues = values ? { ..._initialValues, ...values } : _initialValues
357
361
  for (const key of Object.keys(data)) {
358
- ;(data as Record<string, unknown>)[key] = structuredClone(resetValues[key as keyof T])
362
+ ;(data as Record<string, unknown>)[key] = structuredClone(deepToRaw(resetValues[key as keyof T]))
363
+ errors[key] = null
364
+ touched[key] = false
365
+ dirty[key] = false
366
+ }
367
+ }
368
+
369
+ function replaceState(values: T): void {
370
+ const nextValues = structuredClone(deepToRaw(values)) as T
371
+ _initialValues = structuredClone(nextValues)
372
+
373
+ for (const key of Object.keys(data)) {
374
+ if (key in nextValues) continue
375
+ delete (data as Record<string, unknown>)[key]
376
+ delete errors[key]
377
+ delete touched[key]
378
+ delete dirty[key]
379
+ }
380
+
381
+ for (const key of Object.keys(nextValues)) {
382
+ ;(data as Record<string, unknown>)[key] = structuredClone(deepToRaw(nextValues[key as keyof T]))
359
383
  errors[key] = null
360
384
  touched[key] = false
361
385
  dirty[key] = false
@@ -410,7 +434,28 @@ export function useForm<T extends Record<string, unknown>>(
410
434
  validateField,
411
435
  validate,
412
436
  reset,
437
+ replaceState,
413
438
  handleSubmit,
414
439
  getFieldProps,
415
440
  }
416
441
  }
442
+
443
+ function deepToRaw(value: unknown): unknown {
444
+ const raw = toRaw(value)
445
+
446
+ if (Array.isArray(raw)) {
447
+ return raw.map(item => deepToRaw(item))
448
+ }
449
+
450
+ if (isPlainRecord(raw)) {
451
+ return Object.fromEntries(
452
+ Object.entries(raw).map(([key, item]) => [key, deepToRaw(item)])
453
+ )
454
+ }
455
+
456
+ return raw
457
+ }
458
+
459
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
460
+ return Object.prototype.toString.call(value) === '[object Object]'
461
+ }
@@ -1,4 +1,4 @@
1
- import { ref, computed, watch } from 'vue'
1
+ import { reactive, ref, computed, shallowRef, watch } from 'vue'
2
2
  import { useForm, type FieldRules, type UseFormReturn } from './useForm'
3
3
  import { getFieldRegistryEntry, getTypeDefault } from './formBuilderRegistry'
4
4
  import type {
@@ -7,6 +7,7 @@ import type {
7
7
  FormSectionSchema,
8
8
  FieldCondition,
9
9
  FieldValidation,
10
+ FormOptionInput,
10
11
  FormEnhancements,
11
12
  UseFormBuilderReturn,
12
13
  } from '../types/form-builder'
@@ -92,37 +93,15 @@ function convertValidation(v: FieldValidation): FieldRules {
92
93
  return rules
93
94
  }
94
95
 
95
- // ---------------------------------------------------------------------------
96
- // useFormBuilder
97
- // ---------------------------------------------------------------------------
98
-
99
- /**
100
- * Drive a `FormSchema` as reactive form state.
101
- *
102
- * Builds initial values from schema defaults and `initialData`, derives
103
- * validation rules from `FieldValidation` descriptors and enhancement
104
- * validators, evaluates `FieldCondition` expressions for field/section
105
- * visibility, and wires wizard step navigation when `schema.steps` is set.
106
- *
107
- * @param schema - Declarative form or wizard schema.
108
- * @param initialData - Values that override schema defaults.
109
- * @param enhancements - TypeScript-only callbacks (dynamic options, validators,
110
- * submit handler, transform, field-change watcher).
111
- */
112
- /** Drives a declarative FormSchema as reactive state with conditional fields, wizard steps, and validation. */
113
- export function useFormBuilder<T extends Record<string, unknown> = Record<string, unknown>>(
114
- schema: FormSchema,
96
+ function buildInitialValues<T extends Record<string, unknown>>(
97
+ fields: readonly FormFieldSchema[],
115
98
  initialData?: Partial<T>,
116
- enhancements?: FormEnhancements<T>,
117
- ): UseFormBuilderReturn<T> {
118
- const fields = flattenFields(schema)
119
- const sections = collectSections(schema)
120
-
121
- // -- Build initial values --------------------------------------------------
99
+ ): Record<string, unknown> {
122
100
  const initialValues = {} as Record<string, unknown>
101
+
123
102
  for (const field of fields) {
124
103
  const key = field.name
125
- if (initialData && key in initialData) {
104
+ if (initialData && hasOwnKey(initialData, key)) {
126
105
  initialValues[key] = (initialData as Record<string, unknown>)[key]
127
106
  } else if (field.defaultValue !== undefined) {
128
107
  initialValues[key] = field.defaultValue
@@ -131,14 +110,23 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
131
110
  }
132
111
  }
133
112
 
134
- // -- Build validation rules ------------------------------------------------
113
+ return initialValues
114
+ }
115
+
116
+ function buildRules<T extends Record<string, unknown>>(
117
+ fields: readonly FormFieldSchema[],
118
+ enhancements?: FormEnhancements<T>,
119
+ ): Partial<Record<string, FieldRules>> {
135
120
  const rules: Partial<Record<string, FieldRules>> = {}
121
+
136
122
  for (const field of fields) {
137
123
  const base: FieldRules = field.validation ? convertValidation(field.validation) : {}
138
124
  const enhancement = enhancements?.fields?.[field.name as keyof T]
139
125
 
140
- // Wrap validators to skip hidden fields
141
- const customValidators: Array<(value: unknown, formData: Record<string, unknown>) => string | undefined | null> = []
126
+ const customValidators: Array<(
127
+ value: unknown,
128
+ formData: Record<string, unknown>,
129
+ ) => string | undefined | null> = []
142
130
 
143
131
  if (enhancement?.validate) {
144
132
  const fn = enhancement.validate
@@ -149,14 +137,65 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
149
137
  base.custom = customValidators
150
138
  }
151
139
 
152
- // Wrap all rules to skip hidden fields
153
140
  if (Object.keys(base).length > 0) {
154
141
  rules[field.name] = base
155
142
  }
156
143
  }
157
144
 
145
+ return rules
146
+ }
147
+
148
+ function replaceArray<T>(target: T[], values: readonly T[]): void {
149
+ target.splice(0, target.length, ...values)
150
+ }
151
+
152
+ function replaceRecord<TValue>(
153
+ target: Partial<Record<string, TValue>>,
154
+ source: Partial<Record<string, TValue>>,
155
+ ): void {
156
+ for (const key of Object.keys(target)) {
157
+ delete target[key]
158
+ }
159
+ Object.assign(target, source)
160
+ }
161
+
162
+ function hasOwnKey(source: object, key: string): boolean {
163
+ return Object.prototype.hasOwnProperty.call(source, key)
164
+ }
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // useFormBuilder
168
+ // ---------------------------------------------------------------------------
169
+
170
+ /**
171
+ * Drive a `FormSchema` as reactive form state.
172
+ *
173
+ * Builds initial values from schema defaults and `initialData`, derives
174
+ * validation rules from `FieldValidation` descriptors and enhancement
175
+ * validators, evaluates `FieldCondition` expressions for field/section
176
+ * visibility, and wires wizard step navigation when `schema.steps` is set.
177
+ *
178
+ * @param schema - Declarative form or wizard schema.
179
+ * @param initialData - Values that override schema defaults.
180
+ * @param enhancements - TypeScript-only callbacks (dynamic options, validators,
181
+ * submit handler, transform, field-change watcher).
182
+ */
183
+ /** Drives a declarative FormSchema as reactive state with conditional fields, wizard steps, and validation. */
184
+ export function useFormBuilder<T extends Record<string, unknown> = Record<string, unknown>>(
185
+ schema: FormSchema,
186
+ initialData?: Partial<T>,
187
+ enhancements?: FormEnhancements<T>,
188
+ ): UseFormBuilderReturn<T> {
189
+ const currentSchema = shallowRef(schema)
190
+ const fields = reactive<FormFieldSchema[]>(flattenFields(schema))
191
+ const sections = reactive<FormSectionSchema[]>(collectSections(schema))
192
+ const rules = reactive<Partial<Record<string, FieldRules>>>(buildRules(fields, enhancements))
193
+
158
194
  // -- Create form -----------------------------------------------------------
159
- const form = useForm(initialValues as T, rules as Partial<Record<keyof T, FieldRules>>)
195
+ const form = useForm(
196
+ buildInitialValues<T>(fields, initialData) as T,
197
+ rules as Partial<Record<keyof T, FieldRules>>,
198
+ )
160
199
 
161
200
  // -- Visibility ------------------------------------------------------------
162
201
  function isFieldVisible(name: string): boolean {
@@ -230,7 +269,7 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
230
269
  return merged
231
270
  }
232
271
 
233
- function getFieldOptions(name: string): { label: string; value: unknown }[] | undefined {
272
+ function getFieldOptions(name: string): FormOptionInput[] | undefined {
234
273
  const enhancement = enhancements?.fields?.[name as keyof T]
235
274
  if (enhancement?.options) {
236
275
  return enhancement.options(form.data as T)
@@ -238,7 +277,7 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
238
277
 
239
278
  const field = fields.find((f) => f.name === name)
240
279
  if (field?.props?.options) {
241
- return field.props.options as { label: string; value: unknown }[]
280
+ return field.props.options as FormOptionInput[]
242
281
  }
243
282
 
244
283
  return undefined
@@ -248,9 +287,9 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
248
287
  const currentStep = ref(0)
249
288
 
250
289
  const isCurrentStepValid = computed(() => {
251
- if (!schema.steps) return form.isValid.value
290
+ if (!currentSchema.value.steps) return form.isValid.value
252
291
 
253
- const step = schema.steps[currentStep.value]
292
+ const step = currentSchema.value.steps[currentStep.value]
254
293
  if (!step) return true
255
294
 
256
295
  for (const section of step.sections) {
@@ -264,10 +303,10 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
264
303
  })
265
304
 
266
305
  function goNext(): boolean {
267
- if (!schema.steps) return false
306
+ if (!currentSchema.value.steps) return false
268
307
 
269
308
  // Touch and validate all visible fields in current step
270
- const step = schema.steps[currentStep.value]
309
+ const step = currentSchema.value.steps[currentStep.value]
271
310
  if (!step) return false
272
311
 
273
312
  let valid = true
@@ -282,7 +321,7 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
282
321
 
283
322
  if (!valid) return false
284
323
 
285
- if (currentStep.value < schema.steps.length - 1) {
324
+ if (currentStep.value < currentSchema.value.steps.length - 1) {
286
325
  currentStep.value++
287
326
  }
288
327
  return true
@@ -295,8 +334,8 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
295
334
  }
296
335
 
297
336
  function goToStep(index: number): void {
298
- if (!schema.steps) return
299
- if (index >= 0 && index < schema.steps.length) {
337
+ if (!currentSchema.value.steps) return
338
+ if (index >= 0 && index < currentSchema.value.steps.length) {
300
339
  currentStep.value = index
301
340
  }
302
341
  }
@@ -346,6 +385,17 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
346
385
  currentStep.value = 0
347
386
  }
348
387
 
388
+ function updateSchema(nextSchema: FormSchema, nextData?: Partial<T>): void {
389
+ currentSchema.value = nextSchema
390
+
391
+ const nextFields = flattenFields(nextSchema)
392
+ replaceArray(fields, nextFields)
393
+ replaceArray(sections, collectSections(nextSchema))
394
+ replaceRecord(rules, buildRules(nextFields, enhancements))
395
+ form.replaceState(buildInitialValues<T>(nextFields, nextData) as T)
396
+ currentStep.value = 0
397
+ }
398
+
349
399
  // -- onFieldChange wiring --------------------------------------------------
350
400
  if (enhancements?.onFieldChange) {
351
401
  const callback = enhancements.onFieldChange
@@ -379,5 +429,6 @@ export function useFormBuilder<T extends Record<string, unknown> = Record<string
379
429
  validate,
380
430
  reset,
381
431
  submit,
432
+ updateSchema,
382
433
  }
383
434
  }
@@ -0,0 +1,148 @@
1
+ import { computed, toValue, type ComputedRef, type Ref } from 'vue'
2
+ import type { GroupItem } from '../types'
3
+
4
+ export type GroupAssignmentZone = 'zone1' | 'zone2'
5
+
6
+ export type GroupAssignmentSource<T> =
7
+ | T
8
+ | Ref<T>
9
+ | ComputedRef<T>
10
+ | (() => T)
11
+
12
+ export interface GroupAssignmentState {
13
+ group1: string[]
14
+ group2: string[]
15
+ }
16
+
17
+ export interface UseGroupAssignmentOptions {
18
+ groups: GroupAssignmentSource<readonly GroupItem[]>
19
+ group1: GroupAssignmentSource<readonly string[]>
20
+ group2: GroupAssignmentSource<readonly string[]>
21
+ label1?: GroupAssignmentSource<string | null | undefined>
22
+ label2?: GroupAssignmentSource<string | null | undefined>
23
+ minPerGroup?: GroupAssignmentSource<number | null | undefined>
24
+ }
25
+
26
+ export interface UseGroupAssignmentReturn {
27
+ unassignedGroups: ComputedRef<GroupItem[]>
28
+ zone1Groups: ComputedRef<GroupItem[]>
29
+ zone2Groups: ComputedRef<GroupItem[]>
30
+ zone1Count: ComputedRef<number>
31
+ zone2Count: ComputedRef<number>
32
+ isValid: ComputedRef<boolean>
33
+ validationMessage: ComputedRef<string | null>
34
+ getZoneForGroup: (groupName: string) => GroupAssignmentZone | null
35
+ assignToZone: (groupName: string, zone: GroupAssignmentZone) => GroupAssignmentState
36
+ removeFromZone: (groupName: string, zone: GroupAssignmentZone) => GroupAssignmentState
37
+ clearAll: () => GroupAssignmentState
38
+ }
39
+
40
+ /** Shared two-zone group assignment model for control/treatment style workflows. */
41
+ export function useGroupAssignment(options: UseGroupAssignmentOptions): UseGroupAssignmentReturn {
42
+ const groups = computed(() => [...toValue(options.groups)])
43
+ const group1 = computed(() => [...toValue(options.group1)])
44
+ const group2 = computed(() => [...toValue(options.group2)])
45
+ const label1 = computed(() => toValue(options.label1) || 'Control')
46
+ const label2 = computed(() => toValue(options.label2) || 'Treatment')
47
+ const minPerGroup = computed(() => normalizeMinimum(toValue(options.minPerGroup)))
48
+ const group1Set = computed(() => new Set(group1.value))
49
+ const group2Set = computed(() => new Set(group2.value))
50
+
51
+ const unassignedGroups = computed(() =>
52
+ groups.value.filter(group => !group1Set.value.has(group.name) && !group2Set.value.has(group.name))
53
+ )
54
+
55
+ const zone1Groups = computed(() =>
56
+ groups.value.filter(group => group1Set.value.has(group.name))
57
+ )
58
+
59
+ const zone2Groups = computed(() =>
60
+ groups.value.filter(group => group2Set.value.has(group.name))
61
+ )
62
+
63
+ const zone1Count = computed(() =>
64
+ zone1Groups.value.reduce((sum, group) => sum + group.count, 0)
65
+ )
66
+
67
+ const zone2Count = computed(() =>
68
+ zone2Groups.value.reduce((sum, group) => sum + group.count, 0)
69
+ )
70
+
71
+ const isValid = computed(() =>
72
+ group1.value.length >= minPerGroup.value &&
73
+ group2.value.length >= minPerGroup.value
74
+ )
75
+
76
+ const validationMessage = computed(() => {
77
+ if (isValid.value) return null
78
+ const missing1 = Math.max(0, minPerGroup.value - group1.value.length)
79
+ const missing2 = Math.max(0, minPerGroup.value - group2.value.length)
80
+ const parts: string[] = []
81
+ if (missing1 > 0) parts.push(`${missing1} more to ${label1.value}`)
82
+ if (missing2 > 0) parts.push(`${missing2} more to ${label2.value}`)
83
+ return `Add ${parts.join(' and ')}`
84
+ })
85
+
86
+ function getZoneForGroup(groupName: string): GroupAssignmentZone | null {
87
+ if (group1Set.value.has(groupName)) return 'zone1'
88
+ if (group2Set.value.has(groupName)) return 'zone2'
89
+ return null
90
+ }
91
+
92
+ function assignToZone(groupName: string, zone: GroupAssignmentZone): GroupAssignmentState {
93
+ const next = removeFromBoth(groupName)
94
+ if (zone === 'zone1') {
95
+ return {
96
+ group1: [...next.group1, groupName],
97
+ group2: next.group2,
98
+ }
99
+ }
100
+ return {
101
+ group1: next.group1,
102
+ group2: [...next.group2, groupName],
103
+ }
104
+ }
105
+
106
+ function removeFromZone(groupName: string, zone: GroupAssignmentZone): GroupAssignmentState {
107
+ if (zone === 'zone1') {
108
+ return {
109
+ group1: group1.value.filter(name => name !== groupName),
110
+ group2: group2.value,
111
+ }
112
+ }
113
+ return {
114
+ group1: group1.value,
115
+ group2: group2.value.filter(name => name !== groupName),
116
+ }
117
+ }
118
+
119
+ function clearAll(): GroupAssignmentState {
120
+ return { group1: [], group2: [] }
121
+ }
122
+
123
+ function removeFromBoth(groupName: string): GroupAssignmentState {
124
+ return {
125
+ group1: group1.value.filter(name => name !== groupName),
126
+ group2: group2.value.filter(name => name !== groupName),
127
+ }
128
+ }
129
+
130
+ return {
131
+ unassignedGroups,
132
+ zone1Groups,
133
+ zone2Groups,
134
+ zone1Count,
135
+ zone2Count,
136
+ isValid,
137
+ validationMessage,
138
+ getZoneForGroup,
139
+ assignToZone,
140
+ removeFromZone,
141
+ clearAll,
142
+ }
143
+ }
144
+
145
+ function normalizeMinimum(value: number | null | undefined): number {
146
+ if (value === null || value === undefined || !Number.isFinite(value)) return 1
147
+ return Math.max(0, Math.floor(value))
148
+ }
@@ -0,0 +1,158 @@
1
+ import { computed, toValue, type ComputedRef, type Ref } from 'vue'
2
+ import { useSelectionLimit, type SelectionLimitSource } from './useSelectionLimit'
3
+
4
+ export type ListSelectionValue = string | number
5
+
6
+ export type ListSelectionSource<T> =
7
+ | T
8
+ | Ref<T>
9
+ | ComputedRef<T>
10
+ | (() => T)
11
+
12
+ export type WidenListSelectionValue<TValue extends ListSelectionValue> =
13
+ TValue extends string ? string : TValue extends number ? number : TValue
14
+
15
+ export interface UseListSelectionOptions<
16
+ TItem,
17
+ TValue extends ListSelectionValue = ListSelectionValue,
18
+ > {
19
+ selected: ListSelectionSource<readonly TValue[]>
20
+ items?: ListSelectionSource<readonly TItem[]>
21
+ getValue?: (item: TItem) => TValue
22
+ disabled?: ListSelectionSource<boolean | null | undefined>
23
+ max?: SelectionLimitSource<number | null | undefined>
24
+ }
25
+
26
+ export interface UseListSelectionReturn<TValue extends ListSelectionValue = ListSelectionValue> {
27
+ selectedValues: ComputedRef<TValue[]>
28
+ selectedSet: ComputedRef<Set<TValue>>
29
+ itemValues: ComputedRef<TValue[]>
30
+ selectedCount: ComputedRef<number>
31
+ itemCount: ComputedRef<number>
32
+ isAllSelected: ComputedRef<boolean>
33
+ isLimited: ComputedRef<boolean>
34
+ isAtLimit: ComputedRef<boolean>
35
+ canAddMore: ComputedRef<boolean>
36
+ remaining: ComputedRef<number | undefined>
37
+ isSelected: (value: TValue) => boolean
38
+ isFullySelected: (values: readonly TValue[]) => boolean
39
+ isPartiallySelected: (values: readonly TValue[]) => boolean
40
+ selectValues: (values: readonly TValue[]) => TValue[]
41
+ toggleValue: (value: TValue) => TValue[]
42
+ toggleValues: (values: readonly TValue[]) => TValue[]
43
+ selectAll: () => TValue[]
44
+ toggleAll: () => TValue[]
45
+ clear: () => TValue[]
46
+ }
47
+
48
+ /** Shared list-selection state for checkbox lists, sample selectors, and chip selectors. */
49
+ export function useListSelection<
50
+ TItem = ListSelectionValue,
51
+ TValue extends ListSelectionValue = TItem extends ListSelectionValue ? TItem : ListSelectionValue,
52
+ >(options: UseListSelectionOptions<TItem, TValue>): UseListSelectionReturn<WidenListSelectionValue<TValue>> {
53
+ type ResolvedValue = WidenListSelectionValue<TValue>
54
+
55
+ const disabled = computed(() => toValue(options.disabled) ?? false)
56
+ const selectedValues = computed<ResolvedValue[]>(() => [...toValue(options.selected)] as ResolvedValue[])
57
+ const selectedSet = computed(() => new Set(selectedValues.value))
58
+ const itemValues = computed<ResolvedValue[]>(() => {
59
+ const items = toValue(options.items) ?? []
60
+ return items.map(item => getItemValue(item, options.getValue) as ResolvedValue)
61
+ })
62
+
63
+ const selectedCount = computed(() => selectedValues.value.length)
64
+ const itemCount = computed(() => itemValues.value.length)
65
+ const isAllSelected = computed(() => (
66
+ itemValues.value.length > 0 && itemValues.value.every(value => selectedSet.value.has(value))
67
+ ))
68
+ const selectionLimit = useSelectionLimit({
69
+ count: selectedCount,
70
+ max: options.max,
71
+ })
72
+
73
+ function isSelected(value: ResolvedValue): boolean {
74
+ return selectedSet.value.has(value)
75
+ }
76
+
77
+ function isFullySelected(values: readonly ResolvedValue[]): boolean {
78
+ return values.length > 0 && values.every(value => selectedSet.value.has(value))
79
+ }
80
+
81
+ function isPartiallySelected(values: readonly ResolvedValue[]): boolean {
82
+ if (values.length === 0) return false
83
+ const selected = values.filter(value => selectedSet.value.has(value)).length
84
+ return selected > 0 && selected < values.length
85
+ }
86
+
87
+ function selectValues(values: readonly ResolvedValue[]): ResolvedValue[] {
88
+ if (disabled.value) return selectedValues.value
89
+ const missing = uniqueValues(values).filter(value => !selectedSet.value.has(value))
90
+ if (!selectionLimit.canAdd(missing.length)) return selectedValues.value
91
+ return [...selectedValues.value, ...missing]
92
+ }
93
+
94
+ function toggleValue(value: ResolvedValue): ResolvedValue[] {
95
+ if (disabled.value) return selectedValues.value
96
+ if (selectedSet.value.has(value)) {
97
+ return selectedValues.value.filter(selected => selected !== value)
98
+ }
99
+ if (!selectionLimit.canAddMore.value) return selectedValues.value
100
+ return [...selectedValues.value, value]
101
+ }
102
+
103
+ function toggleValues(values: readonly ResolvedValue[]): ResolvedValue[] {
104
+ if (disabled.value) return selectedValues.value
105
+ const unique = uniqueValues(values)
106
+ if (isFullySelected(unique)) {
107
+ const remove = new Set(unique)
108
+ return selectedValues.value.filter(value => !remove.has(value))
109
+ }
110
+ return selectValues(unique)
111
+ }
112
+
113
+ function selectAll(): ResolvedValue[] {
114
+ return selectValues(itemValues.value)
115
+ }
116
+
117
+ function toggleAll(): ResolvedValue[] {
118
+ if (isAllSelected.value) return clear()
119
+ return selectAll()
120
+ }
121
+
122
+ function clear(): ResolvedValue[] {
123
+ return disabled.value ? selectedValues.value : []
124
+ }
125
+
126
+ return {
127
+ selectedValues,
128
+ selectedSet,
129
+ itemValues,
130
+ selectedCount,
131
+ itemCount,
132
+ isAllSelected,
133
+ isLimited: selectionLimit.isLimited,
134
+ isAtLimit: selectionLimit.isAtLimit,
135
+ canAddMore: selectionLimit.canAddMore,
136
+ remaining: selectionLimit.remaining,
137
+ isSelected,
138
+ isFullySelected,
139
+ isPartiallySelected,
140
+ selectValues,
141
+ toggleValue,
142
+ toggleValues,
143
+ selectAll,
144
+ toggleAll,
145
+ clear,
146
+ }
147
+ }
148
+
149
+ function getItemValue<TItem, TValue extends ListSelectionValue>(
150
+ item: TItem,
151
+ getValue: ((item: TItem) => TValue) | undefined,
152
+ ): TValue {
153
+ return getValue ? getValue(item) : item as unknown as TValue
154
+ }
155
+
156
+ function uniqueValues<TValue extends ListSelectionValue>(values: readonly TValue[]): TValue[] {
157
+ return [...new Set(values)]
158
+ }