@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
@@ -2,8 +2,9 @@
2
2
  /**
3
3
  * Tabbed settings modal with three usage modes:
4
4
  *
5
- * 1. Schema-driven (recommended) — pass `schema` + `v-model:values`. Each
6
- * group becomes a tab; fields auto-render via the SDK's FormFieldRenderer
5
+ * 1. Schema-driven (recommended) — pass `schema`, compact `controls`, or a
6
+ * complete `defineControlModel()` result plus `v-model:values`. Each
7
+ * group/section becomes a tab; fields auto-render via the SDK form field
7
8
  * registry (text, select, number, toggle, molecule, concentration, …).
8
9
  * Conditional visibility, validation, and dynamic options come for free.
9
10
  *
@@ -19,20 +20,37 @@
19
20
  */
20
21
  import { ref, computed, watch } from 'vue'
21
22
  import BaseModal from './BaseModal.vue'
22
- import FormFieldRenderer from './FormFieldRenderer.vue'
23
+ import FormFieldRendererInternal from './internal/FormFieldRendererInternal.vue'
23
24
  import { useFormBuilder } from '../composables/useFormBuilder'
25
+ import {
26
+ controlsToSettingsSchema,
27
+ defineControlModel,
28
+ mergeControlWorkspaceOptions,
29
+ type ControlModel,
30
+ type ControlModelBinding,
31
+ type ControlSchema,
32
+ type ControlWorkspaceOptions,
33
+ } from '../composables/useControlSchema'
24
34
  import { useSettingsStore, colorPalettes } from '../stores/settings'
35
+ import {
36
+ formSchemaFieldNames,
37
+ pickExistingRecordKeys,
38
+ pickRecordKeys,
39
+ recordValuesEqualForKeys,
40
+ } from '../utils/formModelSync'
25
41
  import type {
26
42
  ThemeMode,
27
43
  ColorPalette,
28
44
  TableDensity,
29
45
  SettingsTab,
46
+ SettingsTabInput,
30
47
  SettingsModalLayout,
31
48
  SettingsModalSchema,
32
49
  FormSchema,
33
50
  FormSectionSchema,
34
51
  FormEnhancements,
35
52
  } from '../types'
53
+ import { normalizeItemInput } from '../utils/items'
36
54
 
37
55
  // Map our settings groups onto the form-builder's flat-section shape.
38
56
  // `title: ''` because the rail/pane header already shows the group name.
@@ -52,13 +70,19 @@ interface Props {
52
70
  modelValue: boolean
53
71
  title?: string
54
72
  /** Manual tab descriptors. Ignored when `schema` is set (groups become tabs). */
55
- tabs?: SettingsTab[]
73
+ tabs?: SettingsTabInput[]
56
74
  showAppearance?: boolean
57
75
  size?: 'md' | 'lg' | 'xl'
58
76
  layout?: SettingsModalLayout
59
77
  /** Declarative schema — fields auto-render via SDK form components. */
60
78
  schema?: SettingsModalSchema
61
- /** Two-way bound values when `schema` is set. */
79
+ /** Model returned by defineControlModel(), or a raw nested ControlModel for one-step settings generation. */
80
+ model?: ControlModel | ControlModelBinding
81
+ /** Compact controls model — converted to `schema` when `schema` is not set. */
82
+ controls?: ControlSchema
83
+ /** Conversion options for `controls`, such as section labels, columns, and shared initialValues. */
84
+ controlOptions?: ControlWorkspaceOptions
85
+ /** Two-way bound values when `schema`, `model`, or `controls` is set. */
62
86
  values?: Record<string, unknown>
63
87
  /** Optional dynamic enhancements (validators, dynamic options, callbacks). */
64
88
  enhancements?: FormEnhancements<Record<string, unknown>>
@@ -70,6 +94,7 @@ const props = withDefaults(defineProps<Props>(), {
70
94
  showAppearance: true,
71
95
  size: 'lg',
72
96
  layout: 'horizontal',
97
+ model: undefined,
73
98
  })
74
99
 
75
100
  const emit = defineEmits<{
@@ -95,35 +120,93 @@ const uid = `mint-settings-${Math.random().toString(36).slice(2, 9)}`
95
120
  const tabId = (id: string) => `${uid}-tab-${id}`
96
121
  const panelId = (id: string) => `${uid}-panel-${id}`
97
122
 
98
- // `props.schema` is read once at setup. Pass a stable schema object (declare it
99
- // at module scope, not inside a render function) the form-builder is not
100
- // re-initialized when the prop changes. To swap schemas, key the modal:
101
- // `<SettingsModal :key="schemaId" :schema="schemaA" />`.
102
- const builder = props.schema
103
- ? useFormBuilder(buildFlatSchema(props.schema), props.values, props.enhancements)
104
- : null
105
-
106
- if (builder) {
107
- watch(
108
- () => ({ ...builder.form.data }),
109
- (data) => emit('update:values', data as Record<string, unknown>),
110
- { deep: true },
111
- )
112
- }
123
+ const resolvedModel = computed<ControlModelBinding | undefined>(() => {
124
+ if (props.model === undefined) return undefined
125
+ return isControlModelBinding(props.model) ? props.model : defineControlModel(props.model)
126
+ })
127
+ const resolvedControls = computed<ControlSchema | undefined>(() =>
128
+ props.controls ?? resolvedModel.value?.controls,
129
+ )
130
+ const resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>
131
+ mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)
132
+ )
133
+ const settingsSchema = computed<SettingsModalSchema | undefined>(() =>
134
+ props.schema ?? (
135
+ resolvedControls.value
136
+ ? controlsToSettingsSchema(resolvedControls.value, resolvedControlOptions.value)
137
+ : undefined
138
+ ),
139
+ )
140
+ const isSchemaDriven = computed(() => !!settingsSchema.value)
141
+ const resolvedValues = computed<Record<string, unknown>>(() => ({
142
+ ...(resolvedControlOptions.value.initialValues ?? {}),
143
+ ...(props.values ?? {}),
144
+ }))
145
+
146
+ // Schema-driven mode keeps one form-builder instance and syncs schema changes
147
+ // into it, so callers can swap groups/fields without remounting the modal.
148
+ const builder = useFormBuilder(
149
+ settingsSchema.value ? buildFlatSchema(settingsSchema.value) : { sections: [] },
150
+ resolvedValues.value,
151
+ props.enhancements,
152
+ )
153
+
154
+ watch(
155
+ () => settingsSchema.value,
156
+ (schema) => {
157
+ if (!schema) {
158
+ builder.updateSchema({ sections: [] }, {})
159
+ return
160
+ }
161
+
162
+ const flatSchema = buildFlatSchema(schema)
163
+ const fieldNames = formSchemaFieldNames(flatSchema)
164
+ const sourceValues = props.values === undefined
165
+ ? {
166
+ ...resolvedValues.value,
167
+ ...(builder.form.data as Record<string, unknown>),
168
+ }
169
+ : resolvedValues.value
170
+
171
+ builder.updateSchema(flatSchema, pickExistingRecordKeys(sourceValues ?? {}, fieldNames))
172
+ },
173
+ { deep: true },
174
+ )
175
+
176
+ watch(
177
+ () => ({ ...resolvedValues.value }),
178
+ (data) => {
179
+ if (!settingsSchema.value) return
180
+ const fieldNames = builderFieldNames()
181
+ if (recordValuesEqualForKeys(data, builder.form.data as Record<string, unknown>, fieldNames)) return
182
+ builder.reset(pickRecordKeys(data, fieldNames))
183
+ },
184
+ { deep: true },
185
+ )
186
+
187
+ watch(
188
+ () => ({ ...builder.form.data }),
189
+ (data) => {
190
+ if (settingsSchema.value) emit('update:values', data as Record<string, unknown>)
191
+ },
192
+ { deep: true },
193
+ )
113
194
 
114
195
  // Schema groups whose `condition` evaluates false against the current data are
115
196
  // dropped from the rail entirely — same semantics as section visibility in
116
197
  // FormBuilder. Manual `tabs` have no condition mechanism, so they pass through.
117
198
  const visibleSchemaGroups = computed(() =>
118
- builder && props.schema
119
- ? props.schema.groups.filter((g) => builder.isSectionVisible(g.id))
199
+ settingsSchema.value
200
+ ? settingsSchema.value.groups.filter((g) => builder.isSectionVisible(g.id))
120
201
  : [],
121
202
  )
122
203
 
204
+ const manualTabs = computed<SettingsTab[]>(() => props.tabs.map(normalizeItemInput))
205
+
123
206
  const allTabs = computed<SettingsTab[]>(() => {
124
- const base: SettingsTab[] = props.schema
207
+ const base: SettingsTab[] = settingsSchema.value
125
208
  ? visibleSchemaGroups.value.map((g) => ({ id: g.id, label: g.label, icon: g.icon, description: g.description }))
126
- : props.tabs
209
+ : manualTabs.value
127
210
  return props.showAppearance ? [...base, APPEARANCE_TAB] : base
128
211
  })
129
212
 
@@ -145,7 +228,7 @@ const activeGroup = computed(() =>
145
228
  )
146
229
 
147
230
  const activeGroupVisibleFields = computed(() => {
148
- if (!activeGroup.value || !builder) return []
231
+ if (!activeGroup.value) return []
149
232
  return activeGroup.value.fields.filter((f) => builder.isFieldVisible(f.name))
150
233
  })
151
234
 
@@ -165,6 +248,14 @@ function handleClose() {
165
248
  emit('update:modelValue', false)
166
249
  emit('close')
167
250
  }
251
+
252
+ function builderFieldNames(): string[] {
253
+ return builder.fields.map(field => field.name)
254
+ }
255
+
256
+ function isControlModelBinding(model: ControlModel | ControlModelBinding): model is ControlModelBinding {
257
+ return 'controls' in model && 'controlOptions' in model
258
+ }
168
259
  </script>
169
260
 
170
261
  <template>
@@ -243,7 +334,7 @@ function handleClose() {
243
334
 
244
335
  <div :class="isVertical ? 'mint-settings-modal__pane-body' : null">
245
336
  <div
246
- v-if="schema && builder && activeGroup"
337
+ v-if="isSchemaDriven && activeGroup"
247
338
  class="mint-settings-modal__group-grid"
248
339
  :style="{ '--mint-settings-cols': activeGroup.columns ?? 1 }"
249
340
  >
@@ -255,7 +346,7 @@ function handleClose() {
255
346
  :form="builder.form"
256
347
  :field-props="builder.form.getFieldProps(field.name)"
257
348
  >
258
- <FormFieldRenderer
349
+ <FormFieldRendererInternal
259
350
  :field="field"
260
351
  :resolved-props="builder.getResolvedFieldProps(field)"
261
352
  :form="builder.form"
@@ -265,8 +356,8 @@ function handleClose() {
265
356
  </template>
266
357
  </div>
267
358
 
268
- <template v-else-if="!builder">
269
- <template v-for="tab in tabs" :key="tab.id">
359
+ <template v-else-if="!isSchemaDriven">
360
+ <template v-for="tab in manualTabs" :key="tab.id">
270
361
  <div v-show="activeTab === tab.id">
271
362
  <slot :name="`tab-${tab.id}`" />
272
363
  </div>
@@ -1,6 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  /** Lets users type and add tags with autocomplete suggestions, categories, and a max-tag cap. */
3
3
  import { ref, computed } from 'vue'
4
+ import { useSelectionLimit } from '../composables/useSelectionLimit'
5
+ import { useDropdownState } from '../composables/useDropdownState'
4
6
 
5
7
  interface TagSuggestion {
6
8
  value: string
@@ -43,13 +45,23 @@ const emit = defineEmits<{
43
45
 
44
46
  const inputValue = ref('')
45
47
  const inputRef = ref<HTMLInputElement>()
46
- const dropdownOpen = ref(false)
47
48
  const highlightedIndex = ref(-1)
49
+ const {
50
+ isOpen: dropdownOpen,
51
+ rootRef,
52
+ open: openDropdown,
53
+ close: closeDropdown,
54
+ } = useDropdownState({
55
+ onClose: () => {
56
+ highlightedIndex.value = -1
57
+ },
58
+ })
48
59
 
49
- const canAddMore = computed(() => {
50
- if (props.maxTags === undefined) return true
51
- return props.modelValue.length < props.maxTags
60
+ const selectionLimit = useSelectionLimit({
61
+ count: () => props.modelValue.length,
62
+ max: () => props.maxTags,
52
63
  })
64
+ const canAddMore = selectionLimit.canAddMore
53
65
 
54
66
  const normalizedSuggestions = computed<TagSuggestion[]>(() => {
55
67
  return props.suggestions.map(s => typeof s === 'string' ? { value: s } : s)
@@ -73,7 +85,7 @@ function categoryFor(tag: string): TagCategory | undefined {
73
85
  function addTag(value: string) {
74
86
  const trimmed = value.trim()
75
87
  if (!trimmed) return
76
- if (!canAddMore.value) return
88
+ if (!selectionLimit.canAdd()) return
77
89
 
78
90
  // If a category is active and user didn't already type a prefix, prepend it
79
91
  let final = trimmed
@@ -85,8 +97,7 @@ function addTag(value: string) {
85
97
 
86
98
  emit('update:modelValue', [...props.modelValue, final])
87
99
  inputValue.value = ''
88
- dropdownOpen.value = false
89
- highlightedIndex.value = -1
100
+ closeDropdown()
90
101
  }
91
102
 
92
103
  function removeTag(index: number) {
@@ -100,7 +111,7 @@ function handleKeydown(event: KeyboardEvent) {
100
111
  if (event.key === 'ArrowDown' && filteredSuggestions.value.length) {
101
112
  event.preventDefault()
102
113
  highlightedIndex.value = Math.min(highlightedIndex.value + 1, filteredSuggestions.value.length - 1)
103
- dropdownOpen.value = true
114
+ openDropdown()
104
115
  return
105
116
  }
106
117
  if (event.key === 'ArrowUp' && filteredSuggestions.value.length) {
@@ -118,8 +129,7 @@ function handleKeydown(event: KeyboardEvent) {
118
129
  return
119
130
  }
120
131
  if (event.key === 'Escape') {
121
- dropdownOpen.value = false
122
- highlightedIndex.value = -1
132
+ closeDropdown()
123
133
  return
124
134
  }
125
135
  if (event.key === 'Backspace' && !inputValue.value && props.modelValue.length > 0) {
@@ -128,13 +138,13 @@ function handleKeydown(event: KeyboardEvent) {
128
138
  }
129
139
 
130
140
  function handleFocus() {
131
- if (filteredSuggestions.value.length) dropdownOpen.value = true
141
+ if (filteredSuggestions.value.length) openDropdown()
132
142
  }
133
143
 
134
144
  function handleBlur() {
135
145
  // Delay so a click on a suggestion fires first
136
146
  setTimeout(() => {
137
- dropdownOpen.value = false
147
+ closeDropdown()
138
148
  }, 120)
139
149
  }
140
150
 
@@ -147,7 +157,7 @@ function handlePaste(event: ClipboardEvent) {
147
157
  const newTags = [...props.modelValue]
148
158
 
149
159
  for (const tag of tags) {
150
- if (!canAddMore.value) break
160
+ if (!selectionLimit.canAdd(1, newTags.length)) break
151
161
  if (!props.allowDuplicates && newTags.includes(tag)) continue
152
162
  newTags.push(tag)
153
163
  }
@@ -164,13 +174,18 @@ function setCategory(prefix: string) {
164
174
  }
165
175
 
166
176
  function onInputEvent() {
167
- dropdownOpen.value = filteredSuggestions.value.length > 0
177
+ if (filteredSuggestions.value.length > 0) {
178
+ openDropdown()
179
+ } else {
180
+ closeDropdown()
181
+ }
168
182
  highlightedIndex.value = -1
169
183
  }
170
184
  </script>
171
185
 
172
186
  <template>
173
187
  <div
188
+ ref="rootRef"
174
189
  :class="[
175
190
  'mint-tags-input',
176
191
  `mint-tags-input--${size}`,
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  /** Icon button that toggles between light and dark mode, swapping sun/moon icons to reflect the active theme. */
3
3
  import { useTheme } from '../composables/useTheme'
4
+ import IconButton from './IconButton.vue'
4
5
 
5
6
  interface Props {
6
7
  size?: 'sm' | 'md' | 'lg'
@@ -14,16 +15,17 @@ const { isDark, toggleTheme } = useTheme()
14
15
  </script>
15
16
 
16
17
  <template>
17
- <button
18
- type="button"
19
- :class="['mint-theme-toggle', `mint-theme-toggle--${size}`]"
20
- :aria-label="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
18
+ <IconButton
19
+ class="mint-theme-toggle"
20
+ variant="ghost"
21
+ :size="size"
22
+ :label="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
21
23
  @click="toggleTheme"
22
24
  >
23
25
  <!-- Lucide sun -->
24
26
  <svg
25
27
  v-if="isDark"
26
- :class="`mint-theme-toggle__icon--${size}`"
28
+ class="mint-theme-toggle__icon"
27
29
  viewBox="0 0 24 24"
28
30
  fill="none"
29
31
  stroke="currentColor"
@@ -36,7 +38,7 @@ const { isDark, toggleTheme } = useTheme()
36
38
  <!-- Lucide moon -->
37
39
  <svg
38
40
  v-else
39
- :class="`mint-theme-toggle__icon--${size}`"
41
+ class="mint-theme-toggle__icon"
40
42
  viewBox="0 0 24 24"
41
43
  fill="none"
42
44
  stroke="currentColor"
@@ -46,7 +48,7 @@ const { isDark, toggleTheme } = useTheme()
46
48
  >
47
49
  <path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401" />
48
50
  </svg>
49
- </button>
51
+ </IconButton>
50
52
  </template>
51
53
 
52
54
  <style>
@@ -1,7 +1,13 @@
1
1
  <script setup lang="ts">
2
2
  /** Input that opens a scrollable time-slot dropdown in 12h or 24h format, with min/max range. */
3
- import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
4
- import { generateTimeSlots, formatTime, parseTime, compareTime } from '../composables/useTimeUtils'
3
+ import { ref, computed, watch, nextTick } from 'vue'
4
+ import { useDropdownState } from '../composables/useDropdownState'
5
+ import {
6
+ compareTime,
7
+ findNearestTimeSlotIndex,
8
+ formatTimeSlot,
9
+ generateTimeSlots,
10
+ } from '../composables/useTimeUtils'
5
11
 
6
12
  interface Props {
7
13
  modelValue?: string
@@ -30,10 +36,9 @@ const emit = defineEmits<{
30
36
  'update:modelValue': [value: string | undefined]
31
37
  }>()
32
38
 
33
- const isOpen = ref(false)
34
- const containerRef = ref<HTMLDivElement>()
35
- const listRef = ref<HTMLUListElement>()
39
+ const listRef = ref<HTMLUListElement | null>(null)
36
40
  const highlightedIndex = ref(-1)
41
+ const { isOpen, rootRef, open, close, toggle } = useDropdownState()
37
42
 
38
43
  const slots = computed(() => {
39
44
  const start = props.min ?? '00:00'
@@ -43,8 +48,7 @@ const slots = computed(() => {
43
48
 
44
49
  const displayValue = computed(() => {
45
50
  if (!props.modelValue) return ''
46
- const { hour, minute } = parseTime(props.modelValue)
47
- return formatTime(hour, minute, props.format)
51
+ return formatTimeSlot(props.modelValue, props.format)
48
52
  })
49
53
 
50
54
  function isSlotDisabled(slot: string): boolean {
@@ -56,23 +60,17 @@ function isSlotDisabled(slot: string): boolean {
56
60
  function selectSlot(slot: string) {
57
61
  if (isSlotDisabled(slot) || props.disabled) return
58
62
  emit('update:modelValue', slot)
59
- isOpen.value = false
63
+ close()
60
64
  }
61
65
 
62
66
  function clear() {
63
67
  emit('update:modelValue', undefined)
64
- isOpen.value = false
68
+ close()
65
69
  }
66
70
 
67
71
  function toggleDropdown() {
68
72
  if (props.disabled) return
69
- isOpen.value = !isOpen.value
70
- }
71
-
72
- function handleClickOutside(event: MouseEvent) {
73
- if (containerRef.value && !containerRef.value.contains(event.target as Node)) {
74
- isOpen.value = false
75
- }
73
+ toggle()
76
74
  }
77
75
 
78
76
  function scrollToActiveSlot() {
@@ -95,26 +93,14 @@ function scrollToActiveSlot() {
95
93
  }
96
94
 
97
95
  function findNearestSlotIndex(time: string): number {
98
- if (slots.value.length === 0) return -1
99
- let bestIndex = 0
100
- let bestDiff = Infinity
101
- for (let i = 0; i < slots.value.length; i++) {
102
- const { hour: h1, minute: m1 } = parseTime(slots.value[i])
103
- const { hour: h2, minute: m2 } = parseTime(time)
104
- const minuteDiff = Math.abs((h1 * 60 + m1) - (h2 * 60 + m2))
105
- if (minuteDiff < bestDiff) {
106
- bestDiff = minuteDiff
107
- bestIndex = i
108
- }
109
- }
110
- return bestIndex
96
+ return findNearestTimeSlotIndex(time, slots.value)
111
97
  }
112
98
 
113
99
  function handleKeydown(event: KeyboardEvent) {
114
100
  if (!isOpen.value) {
115
101
  if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {
116
102
  event.preventDefault()
117
- isOpen.value = true
103
+ open()
118
104
  return
119
105
  }
120
106
  return
@@ -150,7 +136,7 @@ function handleKeydown(event: KeyboardEvent) {
150
136
  }
151
137
  case 'Escape': {
152
138
  event.preventDefault()
153
- isOpen.value = false
139
+ close()
154
140
  break
155
141
  }
156
142
  }
@@ -180,18 +166,10 @@ watch(isOpen, (open) => {
180
166
  highlightedIndex.value = -1
181
167
  }
182
168
  })
183
-
184
- onMounted(() => {
185
- document.addEventListener('click', handleClickOutside)
186
- })
187
-
188
- onUnmounted(() => {
189
- document.removeEventListener('click', handleClickOutside)
190
- })
191
169
  </script>
192
170
 
193
171
  <template>
194
- <div ref="containerRef" class="mint-time-picker" @keydown="handleKeydown">
172
+ <div ref="rootRef" class="mint-time-picker" @keydown="handleKeydown">
195
173
  <div class="mint-time-picker__input-wrapper">
196
174
  <input
197
175
  type="text"
@@ -260,7 +238,7 @@ onUnmounted(() => {
260
238
  ]"
261
239
  @click="selectSlot(slot)"
262
240
  >
263
- {{ formatTime(parseTime(slot).hour, parseTime(slot).minute, format) }}
241
+ {{ formatTimeSlot(slot, format) }}
264
242
  </li>
265
243
  </ul>
266
244
  </div>
@@ -2,6 +2,7 @@
2
2
  /** Floating tooltip using @floating-ui with auto-flip, delay, optional keyboard shortcut badge, and body Teleport. */
3
3
  import { ref, onBeforeUnmount, nextTick } from 'vue'
4
4
  import { computePosition, flip, offset, shift, type Placement } from '@floating-ui/dom'
5
+ import { useEventListener } from '../composables/useEventListener'
5
6
 
6
7
  interface Props {
7
8
  text: string
@@ -42,22 +43,18 @@ async function updatePosition() {
42
43
  actualPlacement.value = placement
43
44
  }
44
45
 
45
- function addListeners() {
46
- window.addEventListener('scroll', updatePosition, { passive: true, capture: true })
47
- window.addEventListener('resize', updatePosition)
48
- }
49
-
50
- function removeListeners() {
51
- window.removeEventListener('scroll', updatePosition, { capture: true })
52
- window.removeEventListener('resize', updatePosition)
53
- }
46
+ useEventListener(() => window, 'scroll', updatePosition, {
47
+ passive: true,
48
+ capture: true,
49
+ enabled: visible,
50
+ })
51
+ useEventListener(() => window, 'resize', updatePosition, { enabled: visible })
54
52
 
55
53
  async function show() {
56
54
  timeoutId = setTimeout(async () => {
57
55
  visible.value = true
58
56
  await nextTick()
59
57
  await updatePosition()
60
- addListeners()
61
58
  }, props.delay)
62
59
  }
63
60
 
@@ -65,12 +62,10 @@ function hide() {
65
62
  clearTimeout(timeoutId)
66
63
  timeoutId = undefined
67
64
  visible.value = false
68
- removeListeners()
69
65
  }
70
66
 
71
67
  onBeforeUnmount(() => {
72
68
  clearTimeout(timeoutId)
73
- removeListeners()
74
69
  })
75
70
 
76
71
  function resolvedMaxWidth(value: string | number | undefined): string | undefined {
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  /** Interactive 96/384-well plate grid with drag-to-select, heatmap overlays, per-well editing, and sample-color mapping. */
3
- import { ref, computed, onMounted, onUnmounted } from 'vue'
3
+ import { ref, computed } from 'vue'
4
4
  import type { WellPlateFormat, WellPlateSelectionMode, WellPlateSize, Well, HeatmapConfig, WellShape, WellEditField, WellEditData, WellLegendItem, ColumnCondition, RowCondition } from '../types'
5
- import WellEditPopup from './WellEditPopup.vue'
5
+ import { useEventListener } from '../composables/useEventListener'
6
+ import WellEditPopupInternal from './internal/WellEditPopupInternal.vue'
6
7
 
7
8
  interface Props {
8
9
  modelValue?: string[]
@@ -562,15 +563,8 @@ function handleKeyDown(event: KeyboardEvent) {
562
563
  }
563
564
  }
564
565
 
565
- onMounted(() => {
566
- document.addEventListener('mouseup', handleMouseUp)
567
- document.addEventListener('keydown', handleKeyDown)
568
- })
569
-
570
- onUnmounted(() => {
571
- document.removeEventListener('mouseup', handleMouseUp)
572
- document.removeEventListener('keydown', handleKeyDown)
573
- })
566
+ useEventListener(() => document, 'mouseup', handleMouseUp)
567
+ useEventListener(() => document, 'keydown', handleKeyDown)
574
568
 
575
569
  const containerStyle = computed(() =>
576
570
  isFillMode.value ? {} : {
@@ -811,7 +805,7 @@ const tableStyle = computed(() => ({
811
805
  </div>
812
806
 
813
807
  <!-- Edit popup -->
814
- <WellEditPopup
808
+ <WellEditPopupInternal
815
809
  v-if="editable && editingWellId"
816
810
  :well-id="editingWellId"
817
811
  :well-data="wells[editingWellId]"