@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
@@ -0,0 +1,126 @@
1
+ import { computed, toValue, type ComputedRef, type Ref } from 'vue'
2
+ import type { SampleGroup } from '../types'
3
+ import { deriveShade } from '../utils/color'
4
+
5
+ export type SampleGroupSource<T> =
6
+ | T
7
+ | Ref<T>
8
+ | ComputedRef<T>
9
+ | (() => T)
10
+
11
+ export interface DisplaySampleSubGroup extends SampleGroup {
12
+ displayColor: string
13
+ displayBg: string
14
+ displayBorder: string
15
+ }
16
+
17
+ export interface SampleMajorGroup {
18
+ name: string
19
+ color: string
20
+ subGroups: DisplaySampleSubGroup[]
21
+ allSamples: string[]
22
+ }
23
+
24
+ export interface UseSampleGroupsOptions {
25
+ groups: SampleGroupSource<readonly SampleGroup[]>
26
+ samples?: SampleGroupSource<readonly string[]>
27
+ separator?: SampleGroupSource<string | null | undefined>
28
+ fallbackColor?: SampleGroupSource<string | null | undefined>
29
+ }
30
+
31
+ export interface UseSampleGroupsReturn {
32
+ groups: ComputedRef<SampleGroup[]>
33
+ separator: ComputedRef<string>
34
+ hierarchicalGroups: ComputedRef<SampleMajorGroup[]>
35
+ showHierarchy: ComputedRef<boolean>
36
+ groupedSamples: ComputedRef<Set<string>>
37
+ ungroupedSamples: ComputedRef<string[]>
38
+ findGroup: (groupName: string) => SampleGroup | undefined
39
+ getGroupColor: (groupName: string) => string
40
+ }
41
+
42
+ /** Shared sample-group hierarchy, color, and ungrouped-sample derivation for lab selectors. */
43
+ export function useSampleGroups(options: UseSampleGroupsOptions): UseSampleGroupsReturn {
44
+ const groups = computed(() => [...toValue(options.groups)])
45
+ const samples = computed(() => [...(toValue(options.samples) ?? [])])
46
+ const fallbackColor = computed(() => toValue(options.fallbackColor) || '#3B82F6')
47
+ const separator = computed(() => {
48
+ const explicit = toValue(options.separator)
49
+ if (explicit) return explicit
50
+ return groups.value.some(group => group.name.includes('/')) ? '/' : '_'
51
+ })
52
+
53
+ const hierarchicalGroups = computed<SampleMajorGroup[]>(() => {
54
+ if (groups.value.length === 0) return []
55
+ const groupedByMajor = new Map<string, SampleGroup[]>()
56
+
57
+ for (const group of groups.value) {
58
+ const majorName = getMajorGroupName(group.name, separator.value)
59
+ const existing = groupedByMajor.get(majorName)
60
+ if (existing) {
61
+ existing.push(group)
62
+ } else {
63
+ groupedByMajor.set(majorName, [group])
64
+ }
65
+ }
66
+
67
+ return [...groupedByMajor.entries()].map(([majorName, subGroups]) => {
68
+ const color = subGroups[0]?.color || fallbackColor.value
69
+ const displaySubs = subGroups.map((subGroup, index) => {
70
+ const displayColor = deriveShade(color, index, subGroups.length)
71
+ return {
72
+ ...subGroup,
73
+ displayColor,
74
+ displayBg: displayColor + '20',
75
+ displayBorder: displayColor + '40',
76
+ }
77
+ })
78
+
79
+ return {
80
+ name: majorName,
81
+ color,
82
+ subGroups: displaySubs,
83
+ allSamples: subGroups.flatMap(group => group.samples),
84
+ }
85
+ })
86
+ })
87
+
88
+ const showHierarchy = computed(() =>
89
+ hierarchicalGroups.value.some(major =>
90
+ major.subGroups.length > 1 ||
91
+ (major.subGroups.length === 1 && major.name !== major.subGroups[0].name)
92
+ )
93
+ )
94
+
95
+ const groupedSamples = computed(() =>
96
+ new Set(groups.value.flatMap(group => group.samples))
97
+ )
98
+
99
+ const ungroupedSamples = computed(() =>
100
+ samples.value.filter(sample => !groupedSamples.value.has(sample))
101
+ )
102
+
103
+ function findGroup(groupName: string): SampleGroup | undefined {
104
+ return groups.value.find(group => group.name === groupName)
105
+ }
106
+
107
+ function getGroupColor(groupName: string): string {
108
+ return findGroup(groupName)?.color || fallbackColor.value
109
+ }
110
+
111
+ return {
112
+ groups,
113
+ separator,
114
+ hierarchicalGroups,
115
+ showHierarchy,
116
+ groupedSamples,
117
+ ungroupedSamples,
118
+ findGroup,
119
+ getGroupColor,
120
+ }
121
+ }
122
+
123
+ function getMajorGroupName(groupName: string, separator: string): string {
124
+ const parts = groupName.split(separator)
125
+ return parts.length > 1 ? parts[0] : groupName
126
+ }
@@ -0,0 +1,57 @@
1
+ import { computed, toValue, type ComputedRef, type Ref } from 'vue'
2
+
3
+ export type SelectionLimitSource<T> =
4
+ | T
5
+ | Ref<T>
6
+ | ComputedRef<T>
7
+ | (() => T)
8
+
9
+ export interface UseSelectionLimitOptions {
10
+ count: SelectionLimitSource<number>
11
+ max?: SelectionLimitSource<number | null | undefined>
12
+ }
13
+
14
+ export interface UseSelectionLimitReturn {
15
+ count: ComputedRef<number>
16
+ max: ComputedRef<number | undefined>
17
+ isLimited: ComputedRef<boolean>
18
+ isAtLimit: ComputedRef<boolean>
19
+ canAddMore: ComputedRef<boolean>
20
+ remaining: ComputedRef<number | undefined>
21
+ canAdd: (amount?: number, fromCount?: number) => boolean
22
+ }
23
+
24
+ /** Shared cap logic for chip, tag, and multi-select controls. */
25
+ export function useSelectionLimit(options: UseSelectionLimitOptions): UseSelectionLimitReturn {
26
+ const count = computed(() => Math.max(0, Math.floor(toValue(options.count) || 0)))
27
+ const max = computed(() => normalizeMax(toValue(options.max)))
28
+ const isLimited = computed(() => max.value !== undefined)
29
+ const remaining = computed(() => {
30
+ if (max.value === undefined) return undefined
31
+ return Math.max(0, max.value - count.value)
32
+ })
33
+ const isAtLimit = computed(() => remaining.value === 0)
34
+ const canAddMore = computed(() => canAdd(1))
35
+
36
+ function canAdd(amount = 1, fromCount = count.value): boolean {
37
+ const normalizedAmount = Math.max(0, Math.floor(amount))
38
+ const normalizedCount = Math.max(0, Math.floor(fromCount))
39
+ if (max.value === undefined) return true
40
+ return normalizedCount + normalizedAmount <= max.value
41
+ }
42
+
43
+ return {
44
+ count,
45
+ max,
46
+ isLimited,
47
+ isAtLimit,
48
+ canAddMore,
49
+ remaining,
50
+ canAdd,
51
+ }
52
+ }
53
+
54
+ function normalizeMax(value: number | null | undefined): number | undefined {
55
+ if (value === null || value === undefined || !Number.isFinite(value)) return undefined
56
+ return Math.max(0, Math.floor(value))
57
+ }
@@ -0,0 +1,118 @@
1
+ import { computed, toValue, type ComputedRef, type Ref } from 'vue'
2
+
3
+ export type SortedItemsSource<T> =
4
+ | T
5
+ | Ref<T>
6
+ | ComputedRef<T>
7
+ | (() => T)
8
+
9
+ export type SortOrder = 'asc' | 'desc'
10
+
11
+ export interface SortDescriptor<TKey extends string = string> {
12
+ key: TKey
13
+ direction?: SortOrder | null
14
+ }
15
+
16
+ export interface CompareSortValuesOptions {
17
+ caseSensitive?: boolean
18
+ }
19
+
20
+ export interface SortComparatorContext<TItem, TKey extends string = string> {
21
+ key: TKey
22
+ direction: SortOrder
23
+ a: TItem
24
+ b: TItem
25
+ }
26
+
27
+ export type SortComparator<TItem, TKey extends string = string> = (
28
+ aValue: unknown,
29
+ bValue: unknown,
30
+ context: SortComparatorContext<TItem, TKey>,
31
+ ) => number
32
+
33
+ export interface UseSortedItemsOptions<TItem, TKey extends string = string> {
34
+ items: SortedItemsSource<readonly TItem[]>
35
+ sort: SortedItemsSource<SortDescriptor<TKey> | null | undefined>
36
+ enabled?: SortedItemsSource<boolean | null | undefined>
37
+ caseSensitive?: SortedItemsSource<boolean | null | undefined>
38
+ getValue: (item: TItem, key: TKey) => unknown
39
+ compare?: SortComparator<TItem, TKey>
40
+ }
41
+
42
+ export interface UseSortedItemsReturn<TItem, TKey extends string = string> {
43
+ sort: ComputedRef<SortDescriptor<TKey> | null | undefined>
44
+ sortedItems: ComputedRef<TItem[]>
45
+ }
46
+
47
+ /** Shared sorting for SDK tables and lists with stable empty-value handling. */
48
+ export function useSortedItems<TItem, TKey extends string = string>(
49
+ options: UseSortedItemsOptions<TItem, TKey>,
50
+ ): UseSortedItemsReturn<TItem, TKey> {
51
+ const sort = computed(() => toValue(options.sort))
52
+ const enabled = computed(() => toValue(options.enabled) ?? true)
53
+
54
+ const sortedItems = computed(() => {
55
+ const items = [...toValue(options.items)]
56
+ const activeSort = sort.value
57
+
58
+ if (!enabled.value || !activeSort?.key || !activeSort.direction) return items
59
+
60
+ const direction = activeSort.direction
61
+ const caseSensitive = toValue(options.caseSensitive) ?? true
62
+
63
+ return items.sort((a, b) => {
64
+ const aValue = options.getValue(a, activeSort.key)
65
+ const bValue = options.getValue(b, activeSort.key)
66
+
67
+ if (options.compare) {
68
+ return options.compare(aValue, bValue, {
69
+ key: activeSort.key,
70
+ direction,
71
+ a,
72
+ b,
73
+ })
74
+ }
75
+
76
+ return compareSortValues(aValue, bValue, direction, { caseSensitive })
77
+ })
78
+ })
79
+
80
+ return {
81
+ sort,
82
+ sortedItems,
83
+ }
84
+ }
85
+
86
+ export function compareSortValues(
87
+ aValue: unknown,
88
+ bValue: unknown,
89
+ direction: SortOrder = 'asc',
90
+ options: CompareSortValuesOptions = {},
91
+ ): number {
92
+ const aEmpty = aValue === null || aValue === undefined
93
+ const bEmpty = bValue === null || bValue === undefined
94
+
95
+ if (aEmpty && bEmpty) return 0
96
+ if (aEmpty) return 1
97
+ if (bEmpty) return -1
98
+
99
+ const aComparable = toComparableSortValue(aValue, options)
100
+ const bComparable = toComparableSortValue(bValue, options)
101
+
102
+ let comparison = 0
103
+ if (typeof aComparable === 'number' && typeof bComparable === 'number') {
104
+ comparison = aComparable - bComparable
105
+ } else {
106
+ comparison = String(aComparable).localeCompare(String(bComparable))
107
+ }
108
+
109
+ return direction === 'asc' ? comparison : -comparison
110
+ }
111
+
112
+ function toComparableSortValue(value: unknown, options: CompareSortValuesOptions): unknown {
113
+ if (value instanceof Date) return value.getTime()
114
+ if (typeof value === 'string' && options.caseSensitive === false) {
115
+ return value.toLowerCase()
116
+ }
117
+ return value
118
+ }
@@ -0,0 +1,229 @@
1
+ import { computed, ref, type ComputedRef, type Ref } from 'vue'
2
+ import { useExperimentSave, type UseExperimentSaveOptions } from './useExperimentSave'
3
+ import {
4
+ createTemplateCollection,
5
+ ensureTemplateFromCollection,
6
+ extractTemplateCollection,
7
+ type BioTemplateEnvelope,
8
+ type TemplateCollection,
9
+ type TemplateCollectionEnvelope,
10
+ } from '../templates'
11
+
12
+ export type TemplateCollectionInput =
13
+ | TemplateCollectionEnvelope
14
+ | BioTemplateEnvelope<unknown>
15
+ | Array<BioTemplateEnvelope<unknown>>
16
+
17
+ export interface UseTemplateCollectionOptions extends UseExperimentSaveOptions {
18
+ /** Initial template collection or factory; defaults to an empty collection. */
19
+ initial?: TemplateCollectionInput | (() => TemplateCollectionInput)
20
+ }
21
+
22
+ export interface UseTemplateCollectionReturn {
23
+ /** Current experiment id from platform injection, URL query, or URL path. */
24
+ currentExperimentId: ComputedRef<number | undefined>
25
+ /** Whether a current experiment id is available. */
26
+ hasCurrentExperiment: ComputedRef<boolean>
27
+ /** Full template collection envelope ready to save into design_data. */
28
+ templateCollection: Ref<TemplateCollectionEnvelope>
29
+ /** Template map keyed by template_id. */
30
+ templates: ComputedRef<TemplateCollection>
31
+ /** Collection-level metadata, or an empty object. */
32
+ metadata: ComputedRef<Record<string, unknown>>
33
+ /** Template ids currently present in the collection. */
34
+ templateIds: ComputedRef<string[]>
35
+ /** Whether the collection contains at least one template. */
36
+ hasTemplates: ComputedRef<boolean>
37
+ /** Whether a load or delete operation is in progress. */
38
+ isLoading: Ref<boolean>
39
+ /** Whether a save operation is in progress. */
40
+ isSaving: Ref<boolean>
41
+ /** Error message from the last failed operation, or null. */
42
+ error: Ref<string | null>
43
+ /** Timestamp of the last successful load, or null. */
44
+ lastLoadedAt: Ref<Date | null>
45
+ /** Timestamp of the last successful save, or null. */
46
+ lastSavedAt: Ref<Date | null>
47
+ /** Replace local state from a template envelope, collection envelope, or raw design_data. */
48
+ apply: (value: unknown, metadata?: Record<string, unknown>) => TemplateCollectionEnvelope
49
+ /** Reset local state to the configured initial collection. */
50
+ reset: () => TemplateCollectionEnvelope
51
+ /** Add or replace one template in the local collection. */
52
+ setTemplate: (template: BioTemplateEnvelope<unknown>) => TemplateCollectionEnvelope
53
+ /** Remove one template from the local collection. */
54
+ removeTemplate: (templateId: string) => boolean
55
+ /** Read one template from the local collection, returning null when missing. */
56
+ getTemplate: <TTemplate extends BioTemplateEnvelope<unknown> = BioTemplateEnvelope<unknown>>(
57
+ templateId: string,
58
+ ) => TTemplate | null
59
+ /** Read one template from the local collection, throwing when missing or mismatched. */
60
+ requireTemplate: <TTemplate extends BioTemplateEnvelope<unknown> = BioTemplateEnvelope<unknown>>(
61
+ templateId: string,
62
+ ) => TTemplate
63
+ /** Load template design_data for an experiment and apply it locally. */
64
+ load: (experimentId: number) => Promise<TemplateCollectionEnvelope | null>
65
+ /** Load template design_data for the current experiment and apply it locally. */
66
+ loadCurrent: () => Promise<TemplateCollectionEnvelope | null>
67
+ /** Save the local template collection as experiment design_data. */
68
+ save: (experimentId: number) => Promise<boolean>
69
+ /** Save the local template collection as current experiment design_data. */
70
+ saveCurrent: () => Promise<boolean>
71
+ /** Require and return the current experiment id, or throw when missing. */
72
+ requireCurrentExperimentId: () => number
73
+ }
74
+
75
+ /** Manages a biology template collection in the current experiment's design_data. */
76
+ export function useTemplateCollection(
77
+ options: UseTemplateCollectionOptions = {},
78
+ ): UseTemplateCollectionReturn {
79
+ const experimentSave = useExperimentSave(options)
80
+ const templateCollection = ref<TemplateCollectionEnvelope>(createInitialCollection(options))
81
+ const templates = computed(() => extractTemplateCollection(templateCollection.value))
82
+ const metadata = computed(() => templateCollection.value.metadata ?? {})
83
+ const templateIds = computed(() => Object.keys(templates.value))
84
+ const hasTemplates = computed(() => templateIds.value.length > 0)
85
+
86
+ function apply(value: unknown, metadata?: Record<string, unknown>): TemplateCollectionEnvelope {
87
+ templateCollection.value = normalizeTemplateCollection(value, metadata)
88
+ return templateCollection.value
89
+ }
90
+
91
+ function reset(): TemplateCollectionEnvelope {
92
+ templateCollection.value = createInitialCollection(options)
93
+ return templateCollection.value
94
+ }
95
+
96
+ function setTemplate(template: BioTemplateEnvelope<unknown>): TemplateCollectionEnvelope {
97
+ const nextTemplates = {
98
+ ...templates.value,
99
+ [template.template_id]: template,
100
+ }
101
+ templateCollection.value = createTemplateCollectionEnvelope(
102
+ Object.values(nextTemplates),
103
+ templateCollection.value.metadata,
104
+ )
105
+ return templateCollection.value
106
+ }
107
+
108
+ function removeTemplate(templateId: string): boolean {
109
+ if (!templates.value[templateId]) return false
110
+ const nextTemplates = { ...templates.value }
111
+ delete nextTemplates[templateId]
112
+ templateCollection.value = createTemplateCollectionEnvelope(
113
+ Object.values(nextTemplates),
114
+ templateCollection.value.metadata,
115
+ )
116
+ return true
117
+ }
118
+
119
+ function getTemplate<
120
+ TTemplate extends BioTemplateEnvelope<unknown> = BioTemplateEnvelope<unknown>,
121
+ >(templateId: string): TTemplate | null {
122
+ return (templates.value[templateId] as TTemplate | undefined) ?? null
123
+ }
124
+
125
+ function requireTemplate<
126
+ TTemplate extends BioTemplateEnvelope<unknown> = BioTemplateEnvelope<unknown>,
127
+ >(templateId: string): TTemplate {
128
+ return ensureTemplateFromCollection<TTemplate>(templateCollection.value, templateId)
129
+ }
130
+
131
+ async function load(experimentId: number): Promise<TemplateCollectionEnvelope | null> {
132
+ const data = await experimentSave.loadDesign(experimentId)
133
+ return data === null ? null : apply(data)
134
+ }
135
+
136
+ async function loadCurrent(): Promise<TemplateCollectionEnvelope | null> {
137
+ const data = await experimentSave.loadCurrentDesign()
138
+ return data === null ? null : apply(data)
139
+ }
140
+
141
+ async function save(experimentId: number): Promise<boolean> {
142
+ return experimentSave.saveDesign(
143
+ experimentId,
144
+ templateCollection.value as unknown as Record<string, unknown>,
145
+ )
146
+ }
147
+
148
+ async function saveCurrent(): Promise<boolean> {
149
+ return experimentSave.saveCurrentDesign(
150
+ templateCollection.value as unknown as Record<string, unknown>,
151
+ )
152
+ }
153
+
154
+ return {
155
+ currentExperimentId: experimentSave.currentExperimentId,
156
+ hasCurrentExperiment: experimentSave.hasCurrentExperiment,
157
+ templateCollection,
158
+ templates,
159
+ metadata,
160
+ templateIds,
161
+ hasTemplates,
162
+ isLoading: experimentSave.isLoading,
163
+ isSaving: experimentSave.isSaving,
164
+ error: experimentSave.error,
165
+ lastLoadedAt: experimentSave.lastLoadedAt,
166
+ lastSavedAt: experimentSave.lastSavedAt,
167
+ apply,
168
+ reset,
169
+ setTemplate,
170
+ removeTemplate,
171
+ getTemplate,
172
+ requireTemplate,
173
+ load,
174
+ loadCurrent,
175
+ save,
176
+ saveCurrent,
177
+ requireCurrentExperimentId: experimentSave.requireCurrentExperimentId,
178
+ }
179
+ }
180
+
181
+ function createInitialCollection(options: UseTemplateCollectionOptions): TemplateCollectionEnvelope {
182
+ const initial = typeof options.initial === 'function' ? options.initial() : options.initial
183
+ if (initial === undefined) return { templates: {} }
184
+ return normalizeTemplateCollection(initial)
185
+ }
186
+
187
+ function normalizeTemplateCollection(
188
+ value: unknown,
189
+ metadata?: Record<string, unknown>,
190
+ ): TemplateCollectionEnvelope {
191
+ if (Array.isArray(value)) {
192
+ return createTemplateCollectionEnvelope(value, metadata)
193
+ }
194
+
195
+ return createTemplateCollectionEnvelope(
196
+ Object.values(extractTemplateCollection(value)),
197
+ readCollectionMetadata(value) ?? metadata,
198
+ )
199
+ }
200
+
201
+ function createTemplateCollectionEnvelope(
202
+ templates: Array<BioTemplateEnvelope<unknown>>,
203
+ metadata?: Record<string, unknown>,
204
+ ): TemplateCollectionEnvelope {
205
+ return metadata === undefined
206
+ ? createTemplateCollection(templates)
207
+ : createTemplateCollection(templates, metadata)
208
+ }
209
+
210
+ function readCollectionMetadata(value: unknown): Record<string, unknown> | undefined {
211
+ if (
212
+ typeof value !== 'object'
213
+ || value === null
214
+ || Array.isArray(value)
215
+ || !('metadata' in value)
216
+ ) {
217
+ return undefined
218
+ }
219
+
220
+ const metadata = (value as { metadata?: unknown }).metadata
221
+ if (
222
+ typeof metadata === 'object'
223
+ && metadata !== null
224
+ && !Array.isArray(metadata)
225
+ ) {
226
+ return metadata as Record<string, unknown>
227
+ }
228
+ return undefined
229
+ }
@@ -0,0 +1,60 @@
1
+ import { computed, toValue, type ComputedRef, type Ref } from 'vue'
2
+
3
+ export type TextSearchSource<T> =
4
+ | T
5
+ | Ref<T>
6
+ | ComputedRef<T>
7
+ | (() => T)
8
+
9
+ export type TextSearchCandidate = unknown | readonly unknown[]
10
+
11
+ export interface UseTextSearchOptions<TItem> {
12
+ items: TextSearchSource<readonly TItem[]>
13
+ query: TextSearchSource<string | null | undefined>
14
+ enabled?: TextSearchSource<boolean | null | undefined>
15
+ getText: (item: TItem) => TextSearchCandidate
16
+ }
17
+
18
+ export interface UseTextSearchReturn<TItem> {
19
+ query: ComputedRef<string>
20
+ filteredItems: ComputedRef<TItem[]>
21
+ matches: (item: TItem, query?: string | null | undefined) => boolean
22
+ }
23
+
24
+ /** Shared text-search filtering for tables, lists, selectors, and chip inputs. */
25
+ export function useTextSearch<TItem>(options: UseTextSearchOptions<TItem>): UseTextSearchReturn<TItem> {
26
+ const query = computed(() => normalizeSearchQuery(toValue(options.query)))
27
+ const enabled = computed(() => toValue(options.enabled) ?? true)
28
+
29
+ function matches(item: TItem, overrideQuery?: string | null | undefined): boolean {
30
+ const activeQuery = overrideQuery === undefined ? query.value : normalizeSearchQuery(overrideQuery)
31
+ if (!activeQuery) return true
32
+ return candidateMatchesSearch(options.getText(item), activeQuery)
33
+ }
34
+
35
+ const filteredItems = computed(() => {
36
+ const items = [...toValue(options.items)]
37
+ if (!enabled.value || !query.value) return items
38
+ return items.filter(item => matches(item))
39
+ })
40
+
41
+ return {
42
+ query,
43
+ filteredItems,
44
+ matches,
45
+ }
46
+ }
47
+
48
+ export function normalizeSearchQuery(value: string | null | undefined): string {
49
+ return (value ?? '').trim().toLowerCase()
50
+ }
51
+
52
+ export function candidateMatchesSearch(candidate: TextSearchCandidate, query: string): boolean {
53
+ const normalizedQuery = normalizeSearchQuery(query)
54
+ if (!normalizedQuery) return true
55
+ const values = Array.isArray(candidate) ? candidate : [candidate]
56
+ return values.some((value) => {
57
+ if (value === null || value === undefined) return false
58
+ return String(value).toLowerCase().includes(normalizedQuery)
59
+ })
60
+ }
@@ -1,4 +1,4 @@
1
- import { computed, onUnmounted, getCurrentInstance, type Ref } from 'vue'
1
+ import { computed, type Ref } from 'vue'
2
2
  import { useSettingsStore } from '../stores/settings'
3
3
 
4
4
  export interface UseThemeReturn {
@@ -14,33 +14,7 @@ export function useTheme(): UseThemeReturn {
14
14
  // Load persisted theme from localStorage (idempotent — safe to call multiple times)
15
15
  settings.initialize()
16
16
 
17
- const mql = typeof window !== 'undefined'
18
- ? window.matchMedia('(prefers-color-scheme: dark)')
19
- : null
20
-
21
- const isDark = computed(() => {
22
- if (settings.theme === 'system') {
23
- return mql?.matches ?? false
24
- }
25
- return settings.theme === 'dark'
26
- })
27
-
28
- // React to system preference changes when in 'system' mode
29
- function onSystemChange() {
30
- if (settings.theme === 'system') {
31
- // Force reactivity by toggling theme to itself — the computed re-evaluates
32
- // because mql.matches changed, but Vue needs a trigger. We nudge the store.
33
- settings.theme = 'system'
34
- }
35
- }
36
-
37
- mql?.addEventListener('change', onSystemChange)
38
-
39
- if (getCurrentInstance()) {
40
- onUnmounted(() => {
41
- mql?.removeEventListener('change', onSystemChange)
42
- })
43
- }
17
+ const isDark = computed(() => settings.isDark())
44
18
 
45
19
  function toggleTheme() {
46
20
  settings.theme = isDark.value ? 'light' : 'dark'
@@ -14,6 +14,11 @@ export function formatTime(hour: number, minute: number, format: '12h' | '24h' =
14
14
  return `${h12}:${String(minute).padStart(2, '0')} ${period}`
15
15
  }
16
16
 
17
+ export function formatTimeSlot(time: string, format: '12h' | '24h' = '24h'): string {
18
+ const { hour, minute } = parseTime(time)
19
+ return formatTime(hour, minute, format)
20
+ }
21
+
17
22
  export function generateTimeSlots(start: string, end: string, stepMinutes: number): string[] {
18
23
  const slots: string[] = []
19
24
  const s = parseTime(start)
@@ -102,12 +107,27 @@ export function compareTime(a: string, b: string): number {
102
107
  return 0
103
108
  }
104
109
 
105
- function toMinutes(time: string): number {
110
+ export function findNearestTimeSlotIndex(time: string, slots: readonly string[]): number {
111
+ if (slots.length === 0) return -1
112
+ const target = toMinutes(time)
113
+ let bestIndex = 0
114
+ let bestDiff = Infinity
115
+ for (let index = 0; index < slots.length; index++) {
116
+ const diff = Math.abs(toMinutes(slots[index]) - target)
117
+ if (diff < bestDiff) {
118
+ bestDiff = diff
119
+ bestIndex = index
120
+ }
121
+ }
122
+ return bestIndex
123
+ }
124
+
125
+ export function toMinutes(time: string): number {
106
126
  const { hour, minute } = parseTime(time)
107
127
  return hour * 60 + minute
108
128
  }
109
129
 
110
- function fromMinutes(total: number): string {
130
+ export function fromMinutes(total: number): string {
111
131
  const hour = Math.floor(total / 60) % 24
112
132
  const minute = total % 60
113
133
  return formatTime(hour, minute)
@@ -118,6 +138,7 @@ export function useTimeUtils() {
118
138
  return {
119
139
  parseTime,
120
140
  formatTime,
141
+ formatTimeSlot,
121
142
  generateTimeSlots,
122
143
  rangesOverlap,
123
144
  durationMinutes,
@@ -127,5 +148,8 @@ export function useTimeUtils() {
127
148
  snapToSlot,
128
149
  addMinutes,
129
150
  compareTime,
151
+ findNearestTimeSlotIndex,
152
+ toMinutes,
153
+ fromMinutes,
130
154
  }
131
155
  }