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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (421) hide show
  1. package/README.md +218 -6
  2. package/dist/__tests__/components/ActionItem.test.d.ts +1 -0
  3. package/dist/__tests__/components/AppAvatarMenu.test.d.ts +1 -0
  4. package/dist/__tests__/components/AppPageSelector.test.d.ts +1 -0
  5. package/dist/__tests__/components/AppPillNav.test.d.ts +1 -0
  6. package/dist/__tests__/components/AppPluginSwitcher.test.d.ts +1 -0
  7. package/dist/__tests__/components/AppToastContainer.test.d.ts +1 -0
  8. package/dist/__tests__/components/BaseRadioGroup.test.d.ts +1 -0
  9. package/dist/__tests__/components/BaseSelect.test.d.ts +1 -0
  10. package/dist/__tests__/components/BaseTabs.test.d.ts +1 -0
  11. package/dist/__tests__/components/BatchProgressList.test.d.ts +1 -0
  12. package/dist/__tests__/components/BioTemplateExperimentWorkspaceView.test.d.ts +1 -0
  13. package/dist/__tests__/components/BioTemplatePackWorkspaceView.test.d.ts +1 -0
  14. package/dist/__tests__/components/BioTemplatePresetWorkspaceView.test.d.ts +1 -0
  15. package/dist/__tests__/components/BioTemplateRenderer.test.d.ts +1 -0
  16. package/dist/__tests__/components/Breadcrumb.test.d.ts +1 -0
  17. package/dist/__tests__/components/CalendarGridPanel.test.d.ts +1 -0
  18. package/dist/__tests__/components/ConcentrationInput.test.d.ts +1 -0
  19. package/dist/__tests__/components/ControlWorkspaceView.test.d.ts +1 -0
  20. package/dist/__tests__/components/DatePicker.test.d.ts +1 -0
  21. package/dist/__tests__/components/DateTimePicker.test.d.ts +1 -0
  22. package/dist/__tests__/components/EmptyState.test.d.ts +1 -0
  23. package/dist/__tests__/components/ExperimentPopover.test.d.ts +1 -0
  24. package/dist/__tests__/components/FormBuilder.test.d.ts +1 -0
  25. package/dist/__tests__/components/FormCompatibility.test.d.ts +1 -0
  26. package/dist/__tests__/components/GroupAssigner.test.d.ts +1 -0
  27. package/dist/__tests__/components/GroupingModal.test.d.ts +1 -0
  28. package/dist/__tests__/components/MultiSelect.test.d.ts +1 -0
  29. package/dist/__tests__/components/PluginIcon.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/SettingsButton.test.d.ts +1 -0
  36. package/dist/__tests__/components/SettingsModal.test.d.ts +1 -0
  37. package/dist/__tests__/components/TagsInput.test.d.ts +1 -0
  38. package/dist/__tests__/components/ThemeToggle.test.d.ts +1 -0
  39. package/dist/__tests__/components/TimePicker.test.d.ts +1 -0
  40. package/dist/__tests__/composables/useBioTemplatePackWorkspace.test.d.ts +1 -0
  41. package/dist/__tests__/composables/useBioTemplatePresetWorkspace.test.d.ts +1 -0
  42. package/dist/__tests__/composables/useBioTemplateWorkspace.test.d.ts +1 -0
  43. package/dist/__tests__/composables/useCalendarGrid.test.d.ts +1 -0
  44. package/dist/__tests__/composables/useControlSchema.test.d.ts +1 -0
  45. package/dist/__tests__/composables/useDebouncedWatch.test.d.ts +1 -0
  46. package/dist/__tests__/composables/useDropdownState.test.d.ts +1 -0
  47. package/dist/__tests__/composables/useEventListener.test.d.ts +1 -0
  48. package/dist/__tests__/composables/useExpansionSet.test.d.ts +1 -0
  49. package/dist/__tests__/composables/useExperimentData.test.d.ts +1 -0
  50. package/dist/__tests__/composables/useExperimentSelector.test.d.ts +1 -0
  51. package/dist/__tests__/composables/useGroupAssignment.test.d.ts +1 -0
  52. package/dist/__tests__/composables/useListSelection.test.d.ts +1 -0
  53. package/dist/__tests__/composables/usePluginClient.test.d.ts +1 -0
  54. package/dist/__tests__/composables/usePluginConfig.test.d.ts +1 -0
  55. package/dist/__tests__/composables/useRequestSyncState.test.d.ts +1 -0
  56. package/dist/__tests__/composables/useSampleGroups.test.d.ts +1 -0
  57. package/dist/__tests__/composables/useSelectionLimit.test.d.ts +1 -0
  58. package/dist/__tests__/composables/useSortedItems.test.d.ts +1 -0
  59. package/dist/__tests__/composables/useTemplateCollection.test.d.ts +1 -0
  60. package/dist/__tests__/composables/useTextSearch.test.d.ts +1 -0
  61. package/dist/__tests__/composables/useTheme.test.d.ts +1 -0
  62. package/dist/__tests__/composables/useTimeUtils.test.d.ts +1 -0
  63. package/dist/__tests__/docs/frontendDocsCatalog.test.d.ts +1 -0
  64. package/dist/__tests__/templates/templates.test.d.ts +1 -0
  65. package/dist/{auth-DsI0rQ7_.js → auth-QQj2kkze.js} +12 -5
  66. package/dist/auth-QQj2kkze.js.map +1 -0
  67. package/dist/components/ActionItem.vue.d.ts +32 -0
  68. package/dist/components/AppAvatarMenu.vue.d.ts +2 -7
  69. package/dist/components/AppPageSelector.vue.d.ts +3 -6
  70. package/dist/components/AppPillNav.vue.d.ts +2 -2
  71. package/dist/components/AppSidebar.vue.d.ts +56 -3
  72. package/dist/components/AppToastContainer.vue.d.ts +2 -0
  73. package/dist/components/AppTopBar.vue.d.ts +43 -10
  74. package/dist/components/BaseButton.vue.d.ts +2 -2
  75. package/dist/components/BaseCheckbox.vue.d.ts +1 -1
  76. package/dist/components/BaseInput.vue.d.ts +2 -2
  77. package/dist/components/BasePill.vue.d.ts +3 -3
  78. package/dist/components/BaseRadioGroup.vue.d.ts +4 -4
  79. package/dist/components/BaseSelect.vue.d.ts +4 -4
  80. package/dist/components/BaseSlider.vue.d.ts +1 -1
  81. package/dist/components/BaseTabs.vue.d.ts +2 -2
  82. package/dist/components/BaseTextarea.vue.d.ts +2 -2
  83. package/dist/components/BaseToggle.vue.d.ts +1 -1
  84. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +117 -0
  85. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +92 -0
  86. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +82 -0
  87. package/dist/components/BioTemplateRenderer.vue.d.ts +29 -0
  88. package/dist/components/Breadcrumb.vue.d.ts +2 -2
  89. package/dist/components/Calendar.vue.d.ts +1 -1
  90. package/dist/components/CalendarGridPanel.vue.d.ts +25 -0
  91. package/dist/components/CollapsibleCard.vue.d.ts +1 -1
  92. package/dist/components/ColorSlider.vue.d.ts +1 -1
  93. package/dist/components/ConcentrationInput.vue.d.ts +2 -2
  94. package/dist/components/ConfirmDialog.vue.d.ts +2 -2
  95. package/dist/components/ControlWorkspaceView.vue.d.ts +130 -0
  96. package/dist/components/DatePicker.vue.d.ts +2 -2
  97. package/dist/components/DateTimePicker.vue.d.ts +3 -3
  98. package/dist/components/DropdownButton.vue.d.ts +4 -4
  99. package/dist/components/EmptyState.vue.d.ts +1 -2
  100. package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
  101. package/dist/components/ExperimentTimeline.vue.d.ts +2 -2
  102. package/dist/components/FileUploader.vue.d.ts +2 -2
  103. package/dist/components/FitPanel.vue.d.ts +1 -1
  104. package/dist/components/FormActions.vue.d.ts +4 -4
  105. package/dist/components/FormBuilder.vue.d.ts +22 -8
  106. package/dist/components/FormFieldRenderer.vue.d.ts +7 -10
  107. package/dist/components/FormSection.vue.d.ts +11 -24
  108. package/dist/components/FormulaInput.vue.d.ts +2 -2
  109. package/dist/components/IconButton.vue.d.ts +1 -1
  110. package/dist/components/LoadingSpinner.vue.d.ts +1 -1
  111. package/dist/components/MoleculeInput.vue.d.ts +2 -2
  112. package/dist/components/MultiSelect.vue.d.ts +3 -3
  113. package/dist/components/NumberInput.vue.d.ts +2 -2
  114. package/dist/components/PluginIcon.vue.d.ts +11 -0
  115. package/dist/components/ProgressBar.vue.d.ts +2 -2
  116. package/dist/components/ProtocolStepEditor.vue.d.ts +3 -1
  117. package/dist/components/RackEditor.vue.d.ts +2 -2
  118. package/dist/components/ReagentEditor.vue.d.ts +1 -1
  119. package/dist/components/ResourceCard.vue.d.ts +1 -1
  120. package/dist/components/SampleLegend.vue.d.ts +2 -2
  121. package/dist/components/SampleSelector.vue.d.ts +1 -1
  122. package/dist/components/ScheduleCalendar.vue.d.ts +2 -2
  123. package/dist/components/ScientificNumber.vue.d.ts +1 -1
  124. package/dist/components/SegmentedControl.vue.d.ts +3 -3
  125. package/dist/components/SequenceInput.vue.d.ts +3 -3
  126. package/dist/components/SettingsButton.vue.d.ts +2 -2
  127. package/dist/components/SettingsModal.vue.d.ts +32 -4
  128. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  129. package/dist/components/TagsInput.vue.d.ts +3 -2
  130. package/dist/components/TimePicker.vue.d.ts +3 -3
  131. package/dist/components/TimeRangeInput.vue.d.ts +2 -2
  132. package/dist/components/UnitInput.vue.d.ts +2 -2
  133. package/dist/components/WellPlate.vue.d.ts +8 -8
  134. package/dist/components/index.d.ts +12 -1
  135. package/dist/components/index.js +3 -3
  136. package/dist/components/internal/FormFieldRendererInternal.vue.d.ts +31 -0
  137. package/dist/components/internal/FormSectionRenderer.vue.d.ts +43 -0
  138. package/dist/{components-CzbQQPCb.js → components-D_Sr0adg.js} +9629 -8647
  139. package/dist/components-D_Sr0adg.js.map +1 -0
  140. package/dist/composables/index.d.ts +21 -2
  141. package/dist/composables/index.js +4 -3
  142. package/dist/composables/platformContextHelpers.d.ts +14 -0
  143. package/dist/composables/useBioTemplateComponents.d.ts +20 -0
  144. package/dist/composables/useBioTemplateControls.d.ts +6 -0
  145. package/dist/composables/useBioTemplatePackWorkspace.d.ts +45 -0
  146. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +74 -0
  147. package/dist/composables/useBioTemplateWorkspace.d.ts +50 -0
  148. package/dist/composables/useCalendarGrid.d.ts +26 -0
  149. package/dist/composables/useControlSchema.d.ts +321 -0
  150. package/dist/composables/useDebouncedWatch.d.ts +20 -0
  151. package/dist/composables/useDropdownState.d.ts +19 -0
  152. package/dist/composables/useEventListener.d.ts +13 -0
  153. package/dist/composables/useExpansionSet.d.ts +21 -0
  154. package/dist/composables/useExperimentData.d.ts +10 -0
  155. package/dist/composables/useExperimentSave.d.ts +31 -2
  156. package/dist/composables/useExperimentSelector.d.ts +20 -0
  157. package/dist/composables/useForm.d.ts +2 -0
  158. package/dist/composables/useGroupAssignment.d.ts +31 -0
  159. package/dist/composables/useListSelection.d.ts +35 -0
  160. package/dist/composables/usePlatformContext.d.ts +24 -3
  161. package/dist/composables/usePluginApi.d.ts +7 -14
  162. package/dist/composables/usePluginClient.d.ts +109 -0
  163. package/dist/composables/usePluginConfig.d.ts +12 -0
  164. package/dist/composables/useRequestSyncState.d.ts +34 -0
  165. package/dist/composables/useSampleGroups.d.ts +32 -0
  166. package/dist/composables/useSelectionLimit.d.ts +17 -0
  167. package/dist/composables/useSortedItems.d.ts +32 -0
  168. package/dist/composables/useTemplateCollection.d.ts +58 -0
  169. package/dist/composables/useTextSearch.d.ts +18 -0
  170. package/dist/composables/useTimeUtils.d.ts +8 -0
  171. package/dist/{composables-BXklV5ii.js → composables-C3dpXQN5.js} +228 -146
  172. package/dist/composables-C3dpXQN5.js.map +1 -0
  173. package/dist/index.d.ts +12 -3
  174. package/dist/index.js +6 -5
  175. package/dist/install.d.ts +7 -2
  176. package/dist/install.js +2 -2
  177. package/dist/install.js.map +1 -1
  178. package/dist/stores/auth.d.ts +1 -1
  179. package/dist/stores/index.js +1 -1
  180. package/dist/stores/settings.d.ts +4 -1
  181. package/dist/styles.css +5255 -5654
  182. package/dist/templates/adapters.d.ts +43 -0
  183. package/dist/templates/builders.d.ts +63 -0
  184. package/dist/templates/catalog.d.ts +188 -0
  185. package/dist/templates/componentBindings.d.ts +58 -0
  186. package/dist/templates/controlSchemas.d.ts +25 -0
  187. package/dist/templates/index.d.ts +15 -0
  188. package/dist/templates/index.js +2 -0
  189. package/dist/templates/lookup.d.ts +4 -0
  190. package/dist/templates/packs.d.ts +18 -0
  191. package/dist/templates/presets.d.ts +90 -0
  192. package/dist/templates/types.d.ts +531 -0
  193. package/dist/templates-50NPjaxL.js +9333 -0
  194. package/dist/templates-50NPjaxL.js.map +1 -0
  195. package/dist/types/components.d.ts +62 -1
  196. package/dist/types/form-builder.d.ts +6 -8
  197. package/dist/types/index.d.ts +2 -2
  198. package/dist/types/platform.d.ts +8 -1
  199. package/dist/useScheduleDrag-D4oWdh41.js +4371 -0
  200. package/dist/useScheduleDrag-D4oWdh41.js.map +1 -0
  201. package/dist/utils/formModelSync.d.ts +5 -0
  202. package/dist/utils/items.d.ts +8 -0
  203. package/dist/utils/options.d.ts +6 -0
  204. package/dist/utils/pluginIcon.d.ts +9 -0
  205. package/package.json +7 -2
  206. package/src/__tests__/components/ActionItem.test.ts +99 -0
  207. package/src/__tests__/components/AppAvatarMenu.test.ts +27 -0
  208. package/src/__tests__/components/AppPageSelector.test.ts +134 -0
  209. package/src/__tests__/components/AppPillNav.test.ts +78 -0
  210. package/src/__tests__/components/AppPluginSwitcher.test.ts +44 -0
  211. package/src/__tests__/components/AppSidebar.test.ts +370 -0
  212. package/src/__tests__/components/AppToastContainer.test.ts +48 -0
  213. package/src/__tests__/components/AppTopBar.test.ts +414 -13
  214. package/src/__tests__/components/BaseRadioGroup.test.ts +25 -0
  215. package/src/__tests__/components/BaseSelect.test.ts +21 -0
  216. package/src/__tests__/components/BaseTabs.test.ts +25 -0
  217. package/src/__tests__/components/BatchProgressList.test.ts +52 -0
  218. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +153 -0
  219. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +161 -0
  220. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +281 -0
  221. package/src/__tests__/components/BioTemplateRenderer.test.ts +71 -0
  222. package/src/__tests__/components/Breadcrumb.test.ts +23 -0
  223. package/src/__tests__/components/CalendarGridPanel.test.ts +36 -0
  224. package/src/__tests__/components/ConcentrationInput.test.ts +45 -0
  225. package/src/__tests__/components/ControlWorkspaceView.test.ts +1031 -0
  226. package/src/__tests__/components/DataFrame.test.ts +11 -0
  227. package/src/__tests__/components/DatePicker.test.ts +45 -0
  228. package/src/__tests__/components/DateTimePicker.test.ts +48 -0
  229. package/src/__tests__/components/DropdownButton.test.ts +23 -0
  230. package/src/__tests__/components/EmptyState.test.ts +23 -0
  231. package/src/__tests__/components/ExperimentPopover.test.ts +56 -0
  232. package/src/__tests__/components/FormBuilder.test.ts +296 -0
  233. package/src/__tests__/components/FormCompatibility.test.ts +94 -0
  234. package/src/__tests__/components/GroupAssigner.test.ts +30 -0
  235. package/src/__tests__/components/GroupingModal.test.ts +73 -0
  236. package/src/__tests__/components/MultiSelect.test.ts +48 -0
  237. package/src/__tests__/components/PluginIcon.test.ts +119 -0
  238. package/src/__tests__/components/ProtocolStepEditor.test.ts +33 -0
  239. package/src/__tests__/components/ReagentList.test.ts +82 -0
  240. package/src/__tests__/components/SampleHierarchyTree.test.ts +53 -0
  241. package/src/__tests__/components/SampleSelector.test.ts +60 -0
  242. package/src/__tests__/components/SegmentedControl.test.ts +24 -0
  243. package/src/__tests__/components/SettingsButton.test.ts +44 -0
  244. package/src/__tests__/components/SettingsModal.test.ts +296 -0
  245. package/src/__tests__/components/TagsInput.test.ts +75 -0
  246. package/src/__tests__/components/ThemeToggle.test.ts +47 -0
  247. package/src/__tests__/components/TimePicker.test.ts +38 -0
  248. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +122 -0
  249. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +199 -0
  250. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +99 -0
  251. package/src/__tests__/composables/useCalendarGrid.test.ts +38 -0
  252. package/src/__tests__/composables/useControlSchema.test.ts +919 -0
  253. package/src/__tests__/composables/useDebouncedWatch.test.ts +93 -0
  254. package/src/__tests__/composables/useDropdownState.test.ts +95 -0
  255. package/src/__tests__/composables/useEventListener.test.ts +116 -0
  256. package/src/__tests__/composables/useExpansionSet.test.ts +62 -0
  257. package/src/__tests__/composables/useExperimentData.test.ts +4 -0
  258. package/src/__tests__/composables/useExperimentSave.test.ts +203 -8
  259. package/src/__tests__/composables/useExperimentSelector.test.ts +164 -0
  260. package/src/__tests__/composables/useForm.test.ts +58 -0
  261. package/src/__tests__/composables/useFormBuilder.test.ts +77 -0
  262. package/src/__tests__/composables/useGroupAssignment.test.ts +73 -0
  263. package/src/__tests__/composables/useListSelection.test.ts +66 -0
  264. package/src/__tests__/composables/usePluginClient.test.ts +444 -0
  265. package/src/__tests__/composables/usePluginConfig.test.ts +5 -0
  266. package/src/__tests__/composables/useRequestSyncState.test.ts +92 -0
  267. package/src/__tests__/composables/useSampleGroups.test.ts +66 -0
  268. package/src/__tests__/composables/useSelectionLimit.test.ts +41 -0
  269. package/src/__tests__/composables/useSortedItems.test.ts +87 -0
  270. package/src/__tests__/composables/useTemplateCollection.test.ts +147 -0
  271. package/src/__tests__/composables/useTextSearch.test.ts +55 -0
  272. package/src/__tests__/composables/useTheme.test.ts +91 -0
  273. package/src/__tests__/composables/useTimeUtils.test.ts +35 -0
  274. package/src/__tests__/docs/frontendDocsCatalog.test.ts +229 -0
  275. package/src/__tests__/fixtures/templates/dose-response.json +81 -0
  276. package/src/__tests__/fixtures/templates/plate-map.json +54 -0
  277. package/src/__tests__/fixtures/templates/qpcr-plate.json +96 -0
  278. package/src/__tests__/fixtures/templates/sample-sheet.json +71 -0
  279. package/src/__tests__/templates/templates.test.ts +1043 -0
  280. package/src/components/ActionItem.vue +82 -0
  281. package/src/components/AppAvatarMenu.vue +15 -69
  282. package/src/components/AppLayout.story.vue +25 -25
  283. package/src/components/AppPageSelector.vue +63 -94
  284. package/src/components/AppPillNav.vue +44 -39
  285. package/src/components/AppPluginSwitcher.vue +41 -145
  286. package/src/components/AppSidebar.story.vue +94 -0
  287. package/src/components/AppSidebar.vue +187 -12
  288. package/src/components/{ToastNotification.story.vue → AppToastContainer.story.vue} +6 -6
  289. package/src/components/AppToastContainer.vue +62 -0
  290. package/src/components/AppTopBar.story.vue +7 -30
  291. package/src/components/AppTopBar.vue +283 -84
  292. package/src/components/BaseModal.vue +3 -5
  293. package/src/components/BaseRadioGroup.vue +7 -3
  294. package/src/components/BaseSelect.vue +11 -7
  295. package/src/components/BaseTabs.vue +6 -4
  296. package/src/components/BatchProgressList.vue +5 -8
  297. package/src/components/BioTemplateExperimentWorkspaceView.story.vue +123 -0
  298. package/src/components/BioTemplateExperimentWorkspaceView.vue +337 -0
  299. package/src/components/BioTemplatePackWorkspaceView.story.vue +107 -0
  300. package/src/components/BioTemplatePackWorkspaceView.vue +176 -0
  301. package/src/components/BioTemplatePresetWorkspaceView.story.vue +151 -0
  302. package/src/components/BioTemplatePresetWorkspaceView.vue +392 -0
  303. package/src/components/BioTemplateRenderer.story.vue +57 -0
  304. package/src/components/BioTemplateRenderer.vue +269 -0
  305. package/src/components/Breadcrumb.vue +14 -8
  306. package/src/components/CalendarGridPanel.vue +120 -0
  307. package/src/components/ConcentrationInput.vue +27 -64
  308. package/src/components/ControlWorkspaceView.story.vue +336 -0
  309. package/src/components/ControlWorkspaceView.vue +347 -0
  310. package/src/components/DataFrame.vue +34 -50
  311. package/src/components/DatePicker.vue +59 -192
  312. package/src/components/DateTimePicker.vue +50 -171
  313. package/src/components/DropdownButton.vue +14 -32
  314. package/src/components/EmptyState.vue +4 -2
  315. package/src/components/ExperimentPopover.vue +5 -22
  316. package/src/components/FormBuilder.vue +124 -27
  317. package/src/components/FormFieldRenderer.vue +15 -38
  318. package/src/components/FormSection.vue +20 -73
  319. package/src/components/GroupAssigner.vue +24 -56
  320. package/src/components/GroupingModal.story.vue +3 -3
  321. package/src/components/GroupingModal.vue +30 -391
  322. package/src/components/MultiSelect.vue +17 -12
  323. package/src/components/PlateMapEditor.vue +3 -8
  324. package/src/components/PluginIcon.story.vue +71 -0
  325. package/src/components/PluginIcon.vue +68 -0
  326. package/src/components/ProtocolStepEditor.vue +13 -22
  327. package/src/components/ReagentList.vue +25 -33
  328. package/src/components/SampleHierarchyTree.vue +12 -23
  329. package/src/components/SampleSelector.vue +42 -122
  330. package/src/components/SegmentedControl.vue +7 -3
  331. package/src/components/SettingsButton.story.vue +1 -1
  332. package/src/components/SettingsButton.vue +15 -27
  333. package/src/components/SettingsModal.story.vue +337 -45
  334. package/src/components/SettingsModal.vue +344 -66
  335. package/src/components/TagsInput.vue +29 -14
  336. package/src/components/ThemeToggle.vue +9 -7
  337. package/src/components/TimePicker.vue +19 -41
  338. package/src/components/ToastNotification.vue +4 -57
  339. package/src/components/Tooltip.vue +7 -12
  340. package/src/components/WellEditPopup.vue +3 -8
  341. package/src/components/WellPlate.vue +4 -10
  342. package/src/components/index.ts +12 -1
  343. package/src/components/internal/FormFieldRendererInternal.vue +50 -0
  344. package/src/components/internal/FormSectionRenderer.vue +78 -0
  345. package/src/composables/index.ts +212 -0
  346. package/src/composables/platformContextHelpers.ts +74 -0
  347. package/src/composables/useBioTemplateComponents.ts +93 -0
  348. package/src/composables/useBioTemplateControls.ts +41 -0
  349. package/src/composables/useBioTemplatePackWorkspace.ts +181 -0
  350. package/src/composables/useBioTemplatePresetWorkspace.ts +337 -0
  351. package/src/composables/useBioTemplateWorkspace.ts +139 -0
  352. package/src/composables/useCalendarGrid.ts +140 -0
  353. package/src/composables/useControlSchema.ts +1274 -0
  354. package/src/composables/useDebouncedWatch.ts +119 -0
  355. package/src/composables/useDropdownState.ts +83 -0
  356. package/src/composables/useEventListener.ts +111 -0
  357. package/src/composables/useExpansionSet.ts +117 -0
  358. package/src/composables/useExperimentData.ts +20 -11
  359. package/src/composables/useExperimentSave.ts +202 -50
  360. package/src/composables/useExperimentSelector.ts +86 -72
  361. package/src/composables/useForm.ts +49 -4
  362. package/src/composables/useFormBuilder.ts +93 -42
  363. package/src/composables/useGroupAssignment.ts +148 -0
  364. package/src/composables/useListSelection.ts +158 -0
  365. package/src/composables/usePluginApi.ts +7 -14
  366. package/src/composables/usePluginClient.ts +425 -0
  367. package/src/composables/usePluginConfig.ts +34 -13
  368. package/src/composables/useRequestSyncState.ts +126 -0
  369. package/src/composables/useSampleGroups.ts +126 -0
  370. package/src/composables/useSelectionLimit.ts +57 -0
  371. package/src/composables/useSortedItems.ts +118 -0
  372. package/src/composables/useTemplateCollection.ts +229 -0
  373. package/src/composables/useTextSearch.ts +60 -0
  374. package/src/composables/useTheme.ts +2 -28
  375. package/src/composables/useTimeUtils.ts +26 -2
  376. package/src/composables/useWellPlateEditor.ts +13 -9
  377. package/src/index.ts +228 -4
  378. package/src/install.ts +11 -4
  379. package/src/stores/settings.ts +13 -9
  380. package/src/styles/components/app-page-selector.css +23 -0
  381. package/src/styles/components/app-pill-nav.css +8 -2
  382. package/src/styles/components/app-top-bar.css +35 -2
  383. package/src/styles/components/button.css +3 -7
  384. package/src/styles/components/concentration-input.css +3 -142
  385. package/src/styles/components/dropdown-button.css +4 -4
  386. package/src/styles/components/empty-state.css +0 -16
  387. package/src/styles/components/input.css +4 -5
  388. package/src/styles/components/number-input.css +3 -3
  389. package/src/styles/components/plugin-icon.css +38 -0
  390. package/src/styles/components/segmented-control.css +4 -7
  391. package/src/styles/components/settings-button.css +3 -66
  392. package/src/styles/components/settings-modal.css +184 -0
  393. package/src/styles/components/tabs.css +1 -2
  394. package/src/styles/components/textarea.css +4 -5
  395. package/src/styles/components/theme-toggle.css +3 -66
  396. package/src/styles/components/unit-input.css +3 -3
  397. package/src/styles/index.css +0 -1
  398. package/src/templates/adapters.ts +785 -0
  399. package/src/templates/builders.ts +2149 -0
  400. package/src/templates/catalog.ts +245 -0
  401. package/src/templates/componentBindings.ts +615 -0
  402. package/src/templates/controlSchemas.ts +718 -0
  403. package/src/templates/index.ts +314 -0
  404. package/src/templates/lookup.ts +18 -0
  405. package/src/templates/packs.ts +156 -0
  406. package/src/templates/presets.ts +146 -0
  407. package/src/templates/types.ts +668 -0
  408. package/src/types/components.ts +80 -1
  409. package/src/types/form-builder.ts +7 -2
  410. package/src/types/index.ts +17 -0
  411. package/src/types/platform.ts +8 -1
  412. package/src/utils/formModelSync.ts +52 -0
  413. package/src/utils/items.ts +28 -0
  414. package/src/utils/options.ts +23 -0
  415. package/src/utils/pluginIcon.ts +30 -0
  416. package/dist/auth-DsI0rQ7_.js.map +0 -1
  417. package/dist/components-CzbQQPCb.js.map +0 -1
  418. package/dist/composables-BXklV5ii.js.map +0 -1
  419. package/dist/useScheduleDrag-CxBeqYcu.js +0 -7181
  420. package/dist/useScheduleDrag-CxBeqYcu.js.map +0 -1
  421. package/src/styles/components/grouping-modal.css +0 -323
@@ -1,422 +1,61 @@
1
1
  <!-- @deprecated Use AutoGroupModal instead -->
2
2
  <script setup lang="ts">
3
- /** @deprecated CSV-based sample grouping modal; use AutoGroupModal instead. */
4
- import { ref, computed, watch } from 'vue'
5
- import BaseModal from './BaseModal.vue'
6
- import BaseButton from './BaseButton.vue'
7
- import BaseSelect from './BaseSelect.vue'
8
- import type { SelectOption } from '../types'
3
+ /** @deprecated Compatibility wrapper for the legacy CSV grouping API. Use AutoGroupModal instead. */
4
+ import AutoGroupModal from './AutoGroupModal.vue'
5
+ import type { AutoGroupResult } from '../types/auto-group'
9
6
 
10
7
  interface Props {
11
8
  open: boolean
12
9
  samples: string[]
13
10
  }
14
11
 
15
- interface ParsedRow {
16
- [key: string]: string
17
- }
18
-
19
- const props = defineProps<Props>()
12
+ defineProps<Props>()
20
13
 
21
14
  const emit = defineEmits<{
22
15
  close: []
23
16
  apply: [mapping: Record<string, string[]>, columns: string[]]
24
17
  }>()
25
18
 
26
- const csvFile = ref<File | null>(null)
27
- const csvData = ref<ParsedRow[]>([])
28
- const csvColumns = ref<string[]>([])
29
- const sampleColumn = ref<string>('')
30
- const groupColumns = ref<string[]>([])
31
- const isDragOver = ref(false)
32
- const parseError = ref<string | null>(null)
33
-
34
- const columnOptions = computed<SelectOption[]>(() =>
35
- csvColumns.value.map(col => ({ value: col, label: col }))
36
- )
37
-
38
- const availableGroupColumns = computed<SelectOption[]>(() =>
39
- csvColumns.value
40
- .filter(col => col !== sampleColumn.value && !groupColumns.value.includes(col))
41
- .map(col => ({ value: col, label: col }))
42
- )
43
-
44
- const matchedSamples = computed(() => {
45
- if (!sampleColumn.value || csvData.value.length === 0) return []
46
- const csvSampleValues = csvData.value.map(row => row[sampleColumn.value])
47
- return props.samples.filter(sample => csvSampleValues.includes(sample))
48
- })
49
-
50
- const unmatchedSamples = computed(() => {
51
- if (!sampleColumn.value || csvData.value.length === 0) return []
52
- const csvSampleValues = csvData.value.map(row => row[sampleColumn.value])
53
- return props.samples.filter(sample => !csvSampleValues.includes(sample))
54
- })
55
-
56
- const previewData = computed(() => {
57
- if (csvData.value.length === 0) return []
58
- return csvData.value.slice(0, 5)
59
- })
60
-
61
- const canApply = computed(() =>
62
- sampleColumn.value &&
63
- groupColumns.value.length > 0 &&
64
- matchedSamples.value.length > 0
65
- )
19
+ let closeEmittedFromApply = false
66
20
 
67
- watch(() => props.open, (isOpen) => {
68
- if (!isOpen) {
69
- resetState()
70
- }
71
- })
72
-
73
- function resetState() {
74
- csvFile.value = null
75
- csvData.value = []
76
- csvColumns.value = []
77
- sampleColumn.value = ''
78
- groupColumns.value = []
79
- parseError.value = null
80
- }
81
-
82
- function handleDrop(event: DragEvent) {
83
- event.preventDefault()
84
- isDragOver.value = false
85
- const files = event.dataTransfer?.files
86
- if (files && files.length > 0) {
87
- handleFile(files[0])
88
- }
89
- }
90
-
91
- function handleFileInput(event: Event) {
92
- const target = event.target as HTMLInputElement
93
- if (target.files && target.files.length > 0) {
94
- handleFile(target.files[0])
95
- }
96
- }
97
-
98
- async function handleFile(file: File) {
99
- if (!file.name.endsWith('.csv')) {
100
- parseError.value = 'Please upload a CSV file'
101
- return
102
- }
103
-
104
- csvFile.value = file
105
- parseError.value = null
106
-
107
- try {
108
- const text = await file.text()
109
- parseCSV(text)
110
- } catch {
111
- parseError.value = 'Failed to read file'
112
- }
21
+ function handleApply(result: AutoGroupResult) {
22
+ emit('apply', groupsToLegacyMapping(result), metadataColumns(result))
23
+ closeEmittedFromApply = true
24
+ emit('close')
113
25
  }
114
26
 
115
- function parseCSV(text: string) {
116
- const lines = text.trim().split('\n')
117
- if (lines.length < 2) {
118
- parseError.value = 'CSV must have at least a header and one data row'
27
+ function handleOpenChange(isOpen: boolean) {
28
+ if (isOpen) return
29
+ if (closeEmittedFromApply) {
30
+ closeEmittedFromApply = false
119
31
  return
120
32
  }
121
-
122
- const headers = parseCSVLine(lines[0])
123
- csvColumns.value = headers
124
-
125
- const data: ParsedRow[] = []
126
- for (let i = 1; i < lines.length; i++) {
127
- const values = parseCSVLine(lines[i])
128
- if (values.length !== headers.length) continue
129
-
130
- const row: ParsedRow = {}
131
- headers.forEach((header, idx) => {
132
- row[header] = values[idx]
133
- })
134
- data.push(row)
135
- }
136
-
137
- csvData.value = data
138
-
139
- // Auto-select sample column if there's a likely match
140
- const sampleColGuess = headers.find(h =>
141
- h.toLowerCase().includes('sample') ||
142
- h.toLowerCase().includes('name') ||
143
- h.toLowerCase().includes('id')
144
- )
145
- if (sampleColGuess) {
146
- sampleColumn.value = sampleColGuess
147
- }
148
- }
149
-
150
- function parseCSVLine(line: string): string[] {
151
- const result: string[] = []
152
- let current = ''
153
- let inQuotes = false
154
-
155
- for (let i = 0; i < line.length; i++) {
156
- const char = line[i]
157
- if (char === '"') {
158
- inQuotes = !inQuotes
159
- } else if (char === ',' && !inQuotes) {
160
- result.push(current.trim())
161
- current = ''
162
- } else {
163
- current += char
164
- }
165
- }
166
- result.push(current.trim())
167
-
168
- return result
169
- }
170
-
171
- function addGroupColumn(column: string) {
172
- if (column && !groupColumns.value.includes(column)) {
173
- groupColumns.value.push(column)
174
- }
175
- }
176
-
177
- function removeGroupColumn(column: string) {
178
- groupColumns.value = groupColumns.value.filter(c => c !== column)
33
+ emit('close')
179
34
  }
180
35
 
181
- function moveGroupColumn(index: number, direction: 'up' | 'down') {
182
- const newIndex = direction === 'up' ? index - 1 : index + 1
183
- if (newIndex < 0 || newIndex >= groupColumns.value.length) return
184
-
185
- const cols = [...groupColumns.value]
186
- const temp = cols[index]
187
- cols[index] = cols[newIndex]
188
- cols[newIndex] = temp
189
- groupColumns.value = cols
36
+ function groupsToLegacyMapping(result: AutoGroupResult): Record<string, string[]> {
37
+ return Object.fromEntries(result.groups.map(group => [group.name, group.samples]))
190
38
  }
191
39
 
192
- function applyGrouping() {
193
- if (!canApply.value) return
194
-
195
- const mapping: Record<string, string[]> = {}
196
-
197
- for (const row of csvData.value) {
198
- const sampleName = row[sampleColumn.value]
199
- if (!props.samples.includes(sampleName)) continue
200
-
201
- // Build hierarchical group key
202
- const groupKey = groupColumns.value.map(col => row[col]).join(' / ')
203
-
204
- if (!mapping[groupKey]) {
205
- mapping[groupKey] = []
40
+ function metadataColumns(result: AutoGroupResult): string[] {
41
+ const columns: string[] = []
42
+ const seen = new Set<string>()
43
+ for (const row of result.metadata) {
44
+ for (const column of Object.keys(row.fields)) {
45
+ if (seen.has(column)) continue
46
+ seen.add(column)
47
+ columns.push(column)
206
48
  }
207
- mapping[groupKey].push(sampleName)
208
49
  }
209
-
210
- emit('apply', mapping, groupColumns.value)
211
- emit('close')
212
- }
213
-
214
- function close() {
215
- emit('close')
50
+ return columns
216
51
  }
217
52
  </script>
218
53
 
219
54
  <template>
220
- <BaseModal
55
+ <AutoGroupModal
221
56
  :model-value="open"
222
- title="Group by Metadata"
223
- size="lg"
224
- @update:model-value="!$event && close()"
225
- @close="close"
226
- >
227
- <div class="mint-grouping-modal">
228
- <!-- File upload area -->
229
- <div
230
- v-if="!csvFile"
231
- :class="[
232
- 'mint-grouping-modal__dropzone',
233
- isDragOver ? 'mint-grouping-modal__dropzone--dragover' : '',
234
- ]"
235
- @drop="handleDrop"
236
- @dragover.prevent="isDragOver = true"
237
- @dragleave="isDragOver = false"
238
- >
239
- <input
240
- type="file"
241
- accept=".csv"
242
- class="mint-grouping-modal__file-input"
243
- @change="handleFileInput"
244
- />
245
- <svg class="mint-grouping-modal__upload-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
246
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
247
- </svg>
248
- <p class="mint-grouping-modal__upload-text">
249
- <span class="mint-grouping-modal__upload-highlight">Click to upload</span>
250
- or drag and drop a CSV file
251
- </p>
252
- <p class="mint-grouping-modal__upload-hint">
253
- CSV should contain sample names and grouping columns
254
- </p>
255
- </div>
256
-
257
- <!-- Error message -->
258
- <div v-if="parseError" class="mint-grouping-modal__error">
259
- {{ parseError }}
260
- </div>
261
-
262
- <!-- Configuration section -->
263
- <template v-if="csvFile && csvData.length > 0">
264
- <div class="mint-grouping-modal__file-info">
265
- <svg class="mint-grouping-modal__file-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
266
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
267
- </svg>
268
- <span class="mint-grouping-modal__file-name">{{ csvFile.name }}</span>
269
- <button
270
- type="button"
271
- class="mint-grouping-modal__file-clear"
272
- @click="resetState"
273
- >
274
- Change file
275
- </button>
276
- </div>
277
-
278
- <!-- Column selection -->
279
- <div class="mint-grouping-modal__config">
280
- <div class="mint-grouping-modal__config-row">
281
- <label class="mint-grouping-modal__label">Sample Column</label>
282
- <BaseSelect
283
- v-model="sampleColumn"
284
- :options="columnOptions"
285
- placeholder="Select sample column..."
286
- />
287
- </div>
288
-
289
- <div class="mint-grouping-modal__config-row">
290
- <label class="mint-grouping-modal__label">Group Columns</label>
291
- <div class="mint-grouping-modal__group-columns">
292
- <!-- Selected group columns with reordering -->
293
- <div v-if="groupColumns.length > 0" class="mint-grouping-modal__selected-columns">
294
- <div
295
- v-for="(col, index) in groupColumns"
296
- :key="col"
297
- class="mint-grouping-modal__selected-column"
298
- >
299
- <span class="mint-grouping-modal__column-name">{{ col }}</span>
300
- <div class="mint-grouping-modal__column-actions">
301
- <button
302
- type="button"
303
- :disabled="index === 0"
304
- class="mint-grouping-modal__column-btn"
305
- @click="moveGroupColumn(index, 'up')"
306
- >
307
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
308
- <path d="M18 15l-6-6-6 6" />
309
- </svg>
310
- </button>
311
- <button
312
- type="button"
313
- :disabled="index === groupColumns.length - 1"
314
- class="mint-grouping-modal__column-btn"
315
- @click="moveGroupColumn(index, 'down')"
316
- >
317
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
318
- <path d="M6 9l6 6 6-6" />
319
- </svg>
320
- </button>
321
- <button
322
- type="button"
323
- class="mint-grouping-modal__column-btn mint-grouping-modal__column-btn--remove"
324
- @click="removeGroupColumn(col)"
325
- >
326
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
327
- <path d="M6 18L18 6M6 6l12 12" />
328
- </svg>
329
- </button>
330
- </div>
331
- </div>
332
- </div>
333
-
334
- <!-- Add column dropdown -->
335
- <BaseSelect
336
- v-if="availableGroupColumns.length > 0"
337
- model-value=""
338
- :options="availableGroupColumns"
339
- placeholder="Add group column..."
340
- @update:model-value="addGroupColumn($event as string)"
341
- />
342
- </div>
343
- </div>
344
- </div>
345
-
346
- <!-- Match status -->
347
- <div v-if="sampleColumn" class="mint-grouping-modal__match-status">
348
- <div class="mint-grouping-modal__match-stat mint-grouping-modal__match-stat--matched">
349
- <span class="mint-grouping-modal__match-count">{{ matchedSamples.length }}</span>
350
- <span class="mint-grouping-modal__match-label">samples matched</span>
351
- </div>
352
- <div
353
- v-if="unmatchedSamples.length > 0"
354
- class="mint-grouping-modal__match-stat mint-grouping-modal__match-stat--unmatched"
355
- >
356
- <span class="mint-grouping-modal__match-count">{{ unmatchedSamples.length }}</span>
357
- <span class="mint-grouping-modal__match-label">samples not found in CSV</span>
358
- </div>
359
- </div>
360
-
361
- <!-- Preview table -->
362
- <div v-if="previewData.length > 0" class="mint-grouping-modal__preview">
363
- <h4 class="mint-grouping-modal__preview-title">Data Preview</h4>
364
- <div class="mint-grouping-modal__table-wrapper">
365
- <table class="mint-grouping-modal__table">
366
- <thead>
367
- <tr>
368
- <th
369
- v-for="col in csvColumns"
370
- :key="col"
371
- :class="[
372
- 'mint-grouping-modal__th',
373
- col === sampleColumn ? 'mint-grouping-modal__th--sample' : '',
374
- groupColumns.includes(col) ? 'mint-grouping-modal__th--group' : '',
375
- ]"
376
- >
377
- {{ col }}
378
- </th>
379
- </tr>
380
- </thead>
381
- <tbody>
382
- <tr v-for="(row, idx) in previewData" :key="idx">
383
- <td
384
- v-for="col in csvColumns"
385
- :key="col"
386
- :class="[
387
- 'mint-grouping-modal__td',
388
- col === sampleColumn ? 'mint-grouping-modal__td--sample' : '',
389
- groupColumns.includes(col) ? 'mint-grouping-modal__td--group' : '',
390
- ]"
391
- >
392
- {{ row[col] }}
393
- </td>
394
- </tr>
395
- </tbody>
396
- </table>
397
- </div>
398
- <p v-if="csvData.length > 5" class="mint-grouping-modal__preview-note">
399
- Showing 5 of {{ csvData.length }} rows
400
- </p>
401
- </div>
402
- </template>
403
- </div>
404
-
405
- <template #footer>
406
- <div class="mint-grouping-modal__footer">
407
- <BaseButton variant="secondary" @click="close">Cancel</BaseButton>
408
- <BaseButton
409
- variant="primary"
410
- :disabled="!canApply"
411
- @click="applyGrouping"
412
- >
413
- Apply Grouping
414
- </BaseButton>
415
- </div>
416
- </template>
417
- </BaseModal>
57
+ :samples="samples"
58
+ @update:model-value="handleOpenChange"
59
+ @apply="handleApply"
60
+ />
418
61
  </template>
419
-
420
- <style>
421
- @import '../styles/components/grouping-modal.css';
422
- </style>
@@ -1,11 +1,13 @@
1
1
  <script setup lang="ts">
2
2
  /** Lets users select multiple options from a checkbox list with optional selection cap. */
3
3
  import { computed } from 'vue'
4
- import type { MultiSelectOption, MultiSelectSize } from '../types'
4
+ import type { MultiSelectOption, MultiSelectOptionInput, MultiSelectSize } from '../types'
5
+ import { useListSelection } from '../composables/useListSelection'
6
+ import { normalizeOptionInput } from '../utils/options'
5
7
 
6
8
  interface Props {
7
9
  modelValue: (string | number)[]
8
- options: MultiSelectOption[]
10
+ options: MultiSelectOptionInput[]
9
11
  placeholder?: string
10
12
  disabled?: boolean
11
13
  maxSelections?: number
@@ -22,22 +24,25 @@ const emit = defineEmits<{
22
24
  'update:modelValue': [value: (string | number)[]]
23
25
  }>()
24
26
 
25
- const canSelectMore = computed(() => {
26
- if (props.maxSelections === undefined) return true
27
- return props.modelValue.length < props.maxSelections
27
+ const normalizedOptions = computed<MultiSelectOption[]>(() => props.options.map(normalizeOptionInput))
28
+ const optionSelection = useListSelection({
29
+ selected: () => props.modelValue,
30
+ items: normalizedOptions,
31
+ getValue: option => option.value,
32
+ disabled: () => props.disabled,
33
+ max: () => props.maxSelections,
28
34
  })
35
+ const canSelectMore = optionSelection.canAddMore
29
36
 
30
37
  function isSelected(value: string | number): boolean {
31
- return props.modelValue.includes(value)
38
+ return optionSelection.isSelected(value)
32
39
  }
33
40
 
34
41
  function toggleOption(option: MultiSelectOption) {
35
42
  if (props.disabled || option.disabled) return
36
43
 
37
- if (isSelected(option.value)) {
38
- emit('update:modelValue', props.modelValue.filter(v => v !== option.value))
39
- } else if (canSelectMore.value) {
40
- emit('update:modelValue', [...props.modelValue, option.value])
44
+ if (isSelected(option.value) || canSelectMore.value) {
45
+ emit('update:modelValue', optionSelection.toggleValue(option.value))
41
46
  }
42
47
  }
43
48
 
@@ -50,7 +55,7 @@ function handleKeydown(event: KeyboardEvent, option: MultiSelectOption) {
50
55
 
51
56
  const selectedLabels = computed(() => {
52
57
  return props.modelValue
53
- .map(v => props.options.find(o => o.value === v)?.label)
58
+ .map(v => normalizedOptions.value.find(o => o.value === v)?.label)
54
59
  .filter(Boolean)
55
60
  })
56
61
  </script>
@@ -72,7 +77,7 @@ const selectedLabels = computed(() => {
72
77
 
73
78
  <div class="mint-multi-select__options" role="group">
74
79
  <button
75
- v-for="option in options"
80
+ v-for="option in normalizedOptions"
76
81
  :key="String(option.value)"
77
82
  type="button"
78
83
  role="checkbox"
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  /** Full plate-map design environment for 96/384-well plates with sample assignment, slot-color coding, undo/redo, and CSV/JSON import-export. */
3
- import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
3
+ import { ref, computed, watch } from 'vue'
4
4
  import type { PlateMapEditorState, WellPlateFormat, SampleType, Well } from '../types'
5
5
  import { useWellPlateEditor } from '../composables/useWellPlateEditor'
6
+ import { useEventListener } from '../composables/useEventListener'
6
7
  import WellPlate from './WellPlate.vue'
7
8
  import SampleLegend from './SampleLegend.vue'
8
9
 
@@ -221,13 +222,7 @@ function handleKeyDown(event: KeyboardEvent) {
221
222
  }
222
223
  }
223
224
 
224
- onMounted(() => {
225
- document.addEventListener('keydown', handleKeyDown)
226
- })
227
-
228
- onUnmounted(() => {
229
- document.removeEventListener('keydown', handleKeyDown)
230
- })
225
+ useEventListener(() => document, 'keydown', handleKeyDown)
231
226
  </script>
232
227
 
233
228
  <template>
@@ -0,0 +1,71 @@
1
+ <script setup lang="ts">
2
+ import { reactive } from 'vue'
3
+ import PluginIcon from './PluginIcon.vue'
4
+
5
+ const SAMPLE_PATH = 'M13 10V3L4 14h7v7l9-11h-7z'
6
+ // Tiny 1x1 transparent PNG.
7
+ const SAMPLE_PNG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII='
8
+ const SAMPLE_HTTPS = 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4f/Vue.js_Logo_2.svg/64px-Vue.js_Logo_2.svg.png'
9
+
10
+ function initState() {
11
+ return reactive({
12
+ icon: SAMPLE_PATH,
13
+ size: 'md' as 'sm' | 'md' | 'lg',
14
+ variant: 'solid' as 'solid' | 'tinted',
15
+ tone: '',
16
+ })
17
+ }
18
+ </script>
19
+
20
+ <template>
21
+ <Story title="Brand/PluginIcon" :layout="{ type: 'grid', width: 320 }">
22
+ <Variant title="Playground" :init-state="initState">
23
+ <template #default="{ state }">
24
+ <div style="padding: 2rem; max-width: 600px;">
25
+ <PluginIcon :icon="state.icon" :size="state.size" :variant="state.variant" :tone="state.tone || undefined" />
26
+ </div>
27
+ </template>
28
+ <template #controls="{ state }">
29
+ <HstText v-model="state.icon" title="icon" />
30
+ <HstSelect v-model="state.size" title="size" :options="['sm', 'md', 'lg']" />
31
+ <HstSelect v-model="state.variant" title="variant" :options="['solid', 'tinted']" />
32
+ <HstText v-model="state.tone" title="tone (hex)" />
33
+ </template>
34
+ </Variant>
35
+
36
+ <Variant title="Sizes">
37
+ <div style="padding: 2rem; display: flex; gap: 1rem; align-items: center;">
38
+ <PluginIcon :icon="SAMPLE_PATH" size="sm" />
39
+ <PluginIcon :icon="SAMPLE_PATH" size="md" />
40
+ <PluginIcon :icon="SAMPLE_PATH" size="lg" />
41
+ </div>
42
+ </Variant>
43
+
44
+ <Variant title="Variants">
45
+ <div style="padding: 2rem; display: flex; gap: 1rem; align-items: center;">
46
+ <PluginIcon :icon="SAMPLE_PATH" variant="solid" />
47
+ <PluginIcon :icon="SAMPLE_PATH" variant="tinted" />
48
+ </div>
49
+ </Variant>
50
+
51
+ <Variant title="Format showcase">
52
+ <div style="padding: 2rem; display: flex; gap: 1rem; align-items: center;">
53
+ <PluginIcon :icon="SAMPLE_PATH" size="lg" variant="tinted" />
54
+ <PluginIcon :icon="SAMPLE_PNG" size="lg" variant="tinted" />
55
+ <PluginIcon :icon="SAMPLE_HTTPS" size="lg" variant="tinted" />
56
+ <PluginIcon icon="" size="lg" variant="tinted" />
57
+ </div>
58
+ <p style="padding: 0 2rem; color: var(--text-muted); font-size: 0.875rem;">
59
+ SVG path · data URL (PNG) · https URL · empty (fallback)
60
+ </p>
61
+ </Variant>
62
+
63
+ <Variant title="Tone overrides">
64
+ <div style="padding: 2rem; display: flex; gap: 1rem; align-items: center;">
65
+ <PluginIcon :icon="SAMPLE_PATH" size="lg" variant="solid" tone="#0EA5E9" />
66
+ <PluginIcon :icon="SAMPLE_PATH" size="lg" variant="solid" tone="#F97316" />
67
+ <PluginIcon :icon="SAMPLE_PATH" size="lg" variant="solid" tone="#22C55E" />
68
+ </div>
69
+ </Variant>
70
+ </Story>
71
+ </template>
@@ -0,0 +1,68 @@
1
+ <script setup lang="ts">
2
+ /** Renders a plugin's icon as a sized chip. Auto-detects format:
3
+ * - SVG path data (e.g. "M13 10V3...") → inline <svg><path>
4
+ * - Raster data URL (data:image/png|jpeg|jpg|gif|webp;...) → <img>
5
+ * - https:// URL → <img> with no-referrer + lazy loading
6
+ * - Anything else → MINT canonical plugin placeholder path
7
+ *
8
+ * http:// URLs and data:image/svg+xml URLs are deliberately rejected
9
+ * (mixed-content + XSS, see spec 2026-05-04-plugin-icon-component-design.md). */
10
+ import { computed } from 'vue'
11
+ import { detectPluginIcon, type DetectedPluginIcon } from '../utils/pluginIcon'
12
+
13
+ defineOptions({ name: 'PluginIcon' })
14
+
15
+ interface Props {
16
+ icon?: string
17
+ size?: 'sm' | 'md' | 'lg'
18
+ variant?: 'solid' | 'tinted'
19
+ tone?: string
20
+ }
21
+
22
+ const props = withDefaults(defineProps<Props>(), {
23
+ size: 'md',
24
+ variant: 'solid',
25
+ })
26
+
27
+ const detected = computed<DetectedPluginIcon>(() => detectPluginIcon(props.icon))
28
+
29
+ const rootClasses = computed(() => [
30
+ 'mint-plugin-icon',
31
+ `mint-plugin-icon--${props.size}`,
32
+ `mint-plugin-icon--${props.variant}`,
33
+ ])
34
+
35
+ const rootStyle = computed(() =>
36
+ props.tone ? { '--mint-plugin-icon-tone': props.tone } : undefined,
37
+ )
38
+ </script>
39
+
40
+ <template>
41
+ <span :class="rootClasses" :style="rootStyle">
42
+ <svg
43
+ v-if="detected.format === 'path' || detected.format === 'fallback'"
44
+ class="mint-plugin-icon__svg"
45
+ viewBox="0 0 24 24"
46
+ fill="none"
47
+ stroke="currentColor"
48
+ stroke-width="2"
49
+ stroke-linecap="round"
50
+ stroke-linejoin="round"
51
+ aria-hidden="true"
52
+ >
53
+ <path :d="detected.value" />
54
+ </svg>
55
+ <img
56
+ v-else
57
+ class="mint-plugin-icon__img"
58
+ :src="detected.value"
59
+ alt=""
60
+ referrerpolicy="no-referrer"
61
+ loading="lazy"
62
+ />
63
+ </span>
64
+ </template>
65
+
66
+ <style>
67
+ @import '../styles/components/plugin-icon.css';
68
+ </style>