@morscherlab/mint-sdk 1.0.0-alpha.2

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 (491) hide show
  1. package/README.md +326 -0
  2. package/dist/__stories__/experiment-helpers.d.ts +25 -0
  3. package/dist/__tests__/components/AppLayout.test.d.ts +1 -0
  4. package/dist/__tests__/components/AppSidebar.test.d.ts +1 -0
  5. package/dist/__tests__/components/AppTopBar.test.d.ts +1 -0
  6. package/dist/__tests__/components/BaseInput.test.d.ts +1 -0
  7. package/dist/__tests__/components/BasePill.test.d.ts +1 -0
  8. package/dist/__tests__/components/Calendar.test.d.ts +1 -0
  9. package/dist/__tests__/components/CollapsibleCard.test.d.ts +1 -0
  10. package/dist/__tests__/components/DataFrame.test.d.ts +1 -0
  11. package/dist/__tests__/components/DropdownButton.test.d.ts +1 -0
  12. package/dist/__tests__/composables/formBuilderRegistry.test.d.ts +1 -0
  13. package/dist/__tests__/composables/useAppExperiment.test.d.ts +1 -0
  14. package/dist/__tests__/composables/useAuth.test.d.ts +1 -0
  15. package/dist/__tests__/composables/useAutoGroup.test.d.ts +1 -0
  16. package/dist/__tests__/composables/useExperimentData.test.d.ts +13 -0
  17. package/dist/__tests__/composables/useExperimentSave.test.d.ts +1 -0
  18. package/dist/__tests__/composables/useForm.test.d.ts +1 -0
  19. package/dist/__tests__/composables/useFormBuilder.test.d.ts +1 -0
  20. package/dist/__tests__/composables/usePlatformContext.test.d.ts +1 -0
  21. package/dist/__tests__/composables/usePluginApi.test.d.ts +13 -0
  22. package/dist/__tests__/composables/usePluginConfig.test.d.ts +14 -0
  23. package/dist/__tests__/utils/color.test.d.ts +1 -0
  24. package/dist/auth-BYmxZdJl.js +297 -0
  25. package/dist/auth-BYmxZdJl.js.map +1 -0
  26. package/dist/components/AlertBox.vue.d.ts +34 -0
  27. package/dist/components/AppAvatarMenu.vue.d.ts +58 -0
  28. package/dist/components/AppContainer.vue.d.ts +28 -0
  29. package/dist/components/AppLayout.vue.d.ts +31 -0
  30. package/dist/components/AppPageSelector.vue.d.ts +43 -0
  31. package/dist/components/AppPillNav.vue.d.ts +11 -0
  32. package/dist/components/AppPluginSwitcher.vue.d.ts +38 -0
  33. package/dist/components/AppSidebar.vue.d.ts +47 -0
  34. package/dist/components/AppTopBar.vue.d.ts +111 -0
  35. package/dist/components/AuditTrail.vue.d.ts +38 -0
  36. package/dist/components/AutoGroupModal.vue.d.ts +124 -0
  37. package/dist/components/Avatar.vue.d.ts +14 -0
  38. package/dist/components/BaseButton.vue.d.ts +37 -0
  39. package/dist/components/BaseCheckbox.vue.d.ts +17 -0
  40. package/dist/components/BaseInput.vue.d.ts +34 -0
  41. package/dist/components/BaseModal.vue.d.ts +46 -0
  42. package/dist/components/BasePill.vue.d.ts +57 -0
  43. package/dist/components/BaseRadioGroup.vue.d.ts +21 -0
  44. package/dist/components/BaseSelect.vue.d.ts +20 -0
  45. package/dist/components/BaseSlider.vue.d.ts +22 -0
  46. package/dist/components/BaseTabs.vue.d.ts +14 -0
  47. package/dist/components/BaseTextarea.vue.d.ts +30 -0
  48. package/dist/components/BaseToggle.vue.d.ts +19 -0
  49. package/dist/components/BatchProgressList.vue.d.ts +43 -0
  50. package/dist/components/Breadcrumb.vue.d.ts +33 -0
  51. package/dist/components/Calendar.vue.d.ts +107 -0
  52. package/dist/components/ChartContainer.vue.d.ts +31 -0
  53. package/dist/components/ChemicalFormula.vue.d.ts +8 -0
  54. package/dist/components/CollapsibleCard.vue.d.ts +41 -0
  55. package/dist/components/ColorSlider.vue.d.ts +34 -0
  56. package/dist/components/ConcentrationInput.vue.d.ts +25 -0
  57. package/dist/components/ConfirmDialog.vue.d.ts +42 -0
  58. package/dist/components/DataFrame.vue.d.ts +107 -0
  59. package/dist/components/DatePicker.vue.d.ts +25 -0
  60. package/dist/components/DateTimePicker.vue.d.ts +30 -0
  61. package/dist/components/Divider.vue.d.ts +14 -0
  62. package/dist/components/DoseCalculator.vue.d.ts +19 -0
  63. package/dist/components/DropdownButton.vue.d.ts +47 -0
  64. package/dist/components/EmptyState.vue.d.ts +36 -0
  65. package/dist/components/ExperimentCodeBadge.vue.d.ts +14 -0
  66. package/dist/components/ExperimentDataViewer.vue.d.ts +29 -0
  67. package/dist/components/ExperimentPopover.vue.d.ts +32 -0
  68. package/dist/components/ExperimentSelectorModal.vue.d.ts +28 -0
  69. package/dist/components/ExperimentTimeline.vue.d.ts +44 -0
  70. package/dist/components/FileUploader.vue.d.ts +40 -0
  71. package/dist/components/FitPanel.vue.d.ts +46 -0
  72. package/dist/components/FormActions.vue.d.ts +33 -0
  73. package/dist/components/FormBuilder.vue.d.ts +287 -0
  74. package/dist/components/FormField.vue.d.ts +28 -0
  75. package/dist/components/FormFieldRenderer.vue.d.ts +31 -0
  76. package/dist/components/FormSection.vue.d.ts +43 -0
  77. package/dist/components/FormulaInput.vue.d.ts +25 -0
  78. package/dist/components/GroupAssigner.vue.d.ts +25 -0
  79. package/dist/components/GroupingModal.vue.d.ts +12 -0
  80. package/dist/components/IconButton.vue.d.ts +34 -0
  81. package/dist/components/LoadingSpinner.vue.d.ts +12 -0
  82. package/dist/components/MoleculeInput.vue.d.ts +27 -0
  83. package/dist/components/MultiSelect.vue.d.ts +19 -0
  84. package/dist/components/NumberInput.vue.d.ts +22 -0
  85. package/dist/components/PlateMapEditor.vue.d.ts +50 -0
  86. package/dist/components/ProgressBar.vue.d.ts +23 -0
  87. package/dist/components/ProtocolStepEditor.vue.d.ts +24 -0
  88. package/dist/components/RackEditor.vue.d.ts +40 -0
  89. package/dist/components/ReagentEditor.vue.d.ts +30 -0
  90. package/dist/components/ReagentList.vue.d.ts +32 -0
  91. package/dist/components/ResourceCard.vue.d.ts +50 -0
  92. package/dist/components/SampleHierarchyTree.vue.d.ts +26 -0
  93. package/dist/components/SampleLegend.vue.d.ts +32 -0
  94. package/dist/components/SampleSelector.vue.d.ts +29 -0
  95. package/dist/components/ScheduleCalendar.vue.d.ts +110 -0
  96. package/dist/components/ScientificNumber.vue.d.ts +14 -0
  97. package/dist/components/SegmentedControl.vue.d.ts +20 -0
  98. package/dist/components/SequenceInput.vue.d.ts +54 -0
  99. package/dist/components/SettingsButton.vue.d.ts +30 -0
  100. package/dist/components/SettingsModal.vue.d.ts +36 -0
  101. package/dist/components/Skeleton.vue.d.ts +11 -0
  102. package/dist/components/StatusIndicator.vue.d.ts +13 -0
  103. package/dist/components/StepWizard.vue.d.ts +65 -0
  104. package/dist/components/TagsInput.vue.d.ts +39 -0
  105. package/dist/components/ThemeToggle.vue.d.ts +7 -0
  106. package/dist/components/TimePicker.vue.d.ts +29 -0
  107. package/dist/components/TimeRangeInput.vue.d.ts +27 -0
  108. package/dist/components/ToastNotification.vue.d.ts +2 -0
  109. package/dist/components/Tooltip.vue.d.ts +35 -0
  110. package/dist/components/UnitInput.vue.d.ts +39 -0
  111. package/dist/components/WellEditPopup.vue.d.ts +25 -0
  112. package/dist/components/WellPlate.vue.d.ts +73 -0
  113. package/dist/components/index.d.ts +87 -0
  114. package/dist/components/index.js +3 -0
  115. package/dist/components-CKf-UpGi.js +15089 -0
  116. package/dist/components-CKf-UpGi.js.map +1 -0
  117. package/dist/composables/experiment-utils.d.ts +8 -0
  118. package/dist/composables/formBuilderRegistry.d.ts +13 -0
  119. package/dist/composables/index.d.ts +28 -0
  120. package/dist/composables/index.js +3 -0
  121. package/dist/composables/useApi.d.ts +20 -0
  122. package/dist/composables/useAppExperiment.d.ts +37 -0
  123. package/dist/composables/useAsync.d.ts +128 -0
  124. package/dist/composables/useAuth.d.ts +47 -0
  125. package/dist/composables/useAutoGroup.d.ts +106 -0
  126. package/dist/composables/useChemicalFormula.d.ts +21 -0
  127. package/dist/composables/useConcentrationUnits.d.ts +29 -0
  128. package/dist/composables/useDoseCalculator.d.ts +58 -0
  129. package/dist/composables/useExperimentData.d.ts +18 -0
  130. package/dist/composables/useExperimentSave.d.ts +36 -0
  131. package/dist/composables/useExperimentSelector.d.ts +30 -0
  132. package/dist/composables/useForm.d.ts +92 -0
  133. package/dist/composables/useFormBuilder.d.ts +24 -0
  134. package/dist/composables/usePasskey.d.ts +10 -0
  135. package/dist/composables/usePlatformContext.d.ts +131 -0
  136. package/dist/composables/usePluginApi.d.ts +29 -0
  137. package/dist/composables/usePluginConfig.d.ts +13 -0
  138. package/dist/composables/useProtocolTemplates.d.ts +44 -0
  139. package/dist/composables/useRackEditor.d.ts +31 -0
  140. package/dist/composables/useReagentSeries.d.ts +23 -0
  141. package/dist/composables/useScheduleDrag.d.ts +78 -0
  142. package/dist/composables/useSequenceUtils.d.ts +14 -0
  143. package/dist/composables/useTheme.d.ts +8 -0
  144. package/dist/composables/useTimeUtils.d.ts +29 -0
  145. package/dist/composables/useToast.d.ts +22 -0
  146. package/dist/composables/useWellPlateEditor.d.ts +33 -0
  147. package/dist/composables-D0QfFzq1.js +805 -0
  148. package/dist/composables-D0QfFzq1.js.map +1 -0
  149. package/dist/histoire.setup.d.ts +1 -0
  150. package/dist/index.d.ts +6 -0
  151. package/dist/index.js +7 -0
  152. package/dist/install.d.ts +16 -0
  153. package/dist/install.js +23 -0
  154. package/dist/install.js.map +1 -0
  155. package/dist/stores/auth.d.ts +146 -0
  156. package/dist/stores/index.d.ts +2 -0
  157. package/dist/stores/index.js +2 -0
  158. package/dist/stores/settings.d.ts +75 -0
  159. package/dist/styles.css +29728 -0
  160. package/dist/tailwind.preset.d.ts +58 -0
  161. package/dist/tailwind.preset.js +66 -0
  162. package/dist/tailwind.preset.js.map +1 -0
  163. package/dist/types/auth.d.ts +42 -0
  164. package/dist/types/auto-group.d.ts +34 -0
  165. package/dist/types/components.d.ts +528 -0
  166. package/dist/types/form-builder.d.ts +167 -0
  167. package/dist/types/index.d.ts +5 -0
  168. package/dist/types/index.js +0 -0
  169. package/dist/types/platform.d.ts +75 -0
  170. package/dist/useScheduleDrag-DAJueTbK.js +7181 -0
  171. package/dist/useScheduleDrag-DAJueTbK.js.map +1 -0
  172. package/dist/utils/color.d.ts +24 -0
  173. package/package.json +114 -0
  174. package/src/__stories__/experiment-helpers.ts +83 -0
  175. package/src/__tests__/components/AppLayout.test.ts +163 -0
  176. package/src/__tests__/components/AppSidebar.test.ts +292 -0
  177. package/src/__tests__/components/AppTopBar.test.ts +683 -0
  178. package/src/__tests__/components/BaseInput.test.ts +99 -0
  179. package/src/__tests__/components/BasePill.test.ts +291 -0
  180. package/src/__tests__/components/Calendar.test.ts +566 -0
  181. package/src/__tests__/components/CollapsibleCard.test.ts +524 -0
  182. package/src/__tests__/components/DataFrame.test.ts +767 -0
  183. package/src/__tests__/components/DropdownButton.test.ts +471 -0
  184. package/src/__tests__/composables/formBuilderRegistry.test.ts +187 -0
  185. package/src/__tests__/composables/useAppExperiment.test.ts +560 -0
  186. package/src/__tests__/composables/useAuth.test.ts +188 -0
  187. package/src/__tests__/composables/useAutoGroup.test.ts +860 -0
  188. package/src/__tests__/composables/useExperimentData.test.ts +127 -0
  189. package/src/__tests__/composables/useExperimentSave.test.ts +347 -0
  190. package/src/__tests__/composables/useForm.test.ts +205 -0
  191. package/src/__tests__/composables/useFormBuilder.test.ts +917 -0
  192. package/src/__tests__/composables/usePlatformContext.test.ts +116 -0
  193. package/src/__tests__/composables/usePluginApi.test.ts +81 -0
  194. package/src/__tests__/composables/usePluginConfig.test.ts +176 -0
  195. package/src/__tests__/utils/color.test.ts +96 -0
  196. package/src/components/AlertBox.story.vue +204 -0
  197. package/src/components/AlertBox.vue +88 -0
  198. package/src/components/AppAvatarMenu.story.vue +155 -0
  199. package/src/components/AppAvatarMenu.vue +184 -0
  200. package/src/components/AppContainer.story.vue +104 -0
  201. package/src/components/AppContainer.vue +34 -0
  202. package/src/components/AppLayout.story.vue +292 -0
  203. package/src/components/AppLayout.vue +75 -0
  204. package/src/components/AppPageSelector.vue +159 -0
  205. package/src/components/AppPillNav.vue +66 -0
  206. package/src/components/AppPluginSwitcher.vue +241 -0
  207. package/src/components/AppSidebar.story.vue +309 -0
  208. package/src/components/AppSidebar.vue +119 -0
  209. package/src/components/AppTopBar.story.vue +304 -0
  210. package/src/components/AppTopBar.vue +661 -0
  211. package/src/components/AuditTrail.story.vue +163 -0
  212. package/src/components/AuditTrail.vue +151 -0
  213. package/src/components/AutoGroupModal.story.vue +273 -0
  214. package/src/components/AutoGroupModal.vue +566 -0
  215. package/src/components/Avatar.story.vue +115 -0
  216. package/src/components/Avatar.vue +79 -0
  217. package/src/components/BaseButton.story.vue +96 -0
  218. package/src/components/BaseButton.vue +73 -0
  219. package/src/components/BaseCheckbox.story.vue +73 -0
  220. package/src/components/BaseCheckbox.vue +69 -0
  221. package/src/components/BaseInput.story.vue +98 -0
  222. package/src/components/BaseInput.vue +74 -0
  223. package/src/components/BaseModal.story.vue +237 -0
  224. package/src/components/BaseModal.vue +182 -0
  225. package/src/components/BasePill.story.vue +142 -0
  226. package/src/components/BasePill.vue +89 -0
  227. package/src/components/BaseRadioGroup.story.vue +145 -0
  228. package/src/components/BaseRadioGroup.vue +124 -0
  229. package/src/components/BaseSelect.story.vue +120 -0
  230. package/src/components/BaseSelect.vue +71 -0
  231. package/src/components/BaseSlider.story.vue +122 -0
  232. package/src/components/BaseSlider.vue +126 -0
  233. package/src/components/BaseTabs.story.vue +127 -0
  234. package/src/components/BaseTabs.vue +59 -0
  235. package/src/components/BaseTextarea.story.vue +91 -0
  236. package/src/components/BaseTextarea.vue +62 -0
  237. package/src/components/BaseToggle.story.vue +81 -0
  238. package/src/components/BaseToggle.vue +76 -0
  239. package/src/components/BatchProgressList.story.vue +92 -0
  240. package/src/components/BatchProgressList.vue +184 -0
  241. package/src/components/Breadcrumb.story.vue +106 -0
  242. package/src/components/Breadcrumb.vue +75 -0
  243. package/src/components/Calendar.story.vue +106 -0
  244. package/src/components/Calendar.vue +363 -0
  245. package/src/components/ChartContainer.story.vue +113 -0
  246. package/src/components/ChartContainer.vue +64 -0
  247. package/src/components/ChemicalFormula.story.vue +102 -0
  248. package/src/components/ChemicalFormula.vue +39 -0
  249. package/src/components/CollapsibleCard.story.vue +135 -0
  250. package/src/components/CollapsibleCard.vue +167 -0
  251. package/src/components/ColorSlider.story.vue +120 -0
  252. package/src/components/ColorSlider.vue +164 -0
  253. package/src/components/ConcentrationInput.story.vue +77 -0
  254. package/src/components/ConcentrationInput.vue +185 -0
  255. package/src/components/ConfirmDialog.story.vue +248 -0
  256. package/src/components/ConfirmDialog.vue +93 -0
  257. package/src/components/DataFrame.story.vue +148 -0
  258. package/src/components/DataFrame.vue +419 -0
  259. package/src/components/DatePicker.story.vue +119 -0
  260. package/src/components/DatePicker.vue +330 -0
  261. package/src/components/DateTimePicker.story.vue +112 -0
  262. package/src/components/DateTimePicker.vue +392 -0
  263. package/src/components/Divider.story.vue +80 -0
  264. package/src/components/Divider.vue +49 -0
  265. package/src/components/DoseCalculator.story.vue +68 -0
  266. package/src/components/DoseCalculator.vue +476 -0
  267. package/src/components/DropdownButton.story.vue +102 -0
  268. package/src/components/DropdownButton.vue +181 -0
  269. package/src/components/EmptyState.story.vue +135 -0
  270. package/src/components/EmptyState.vue +69 -0
  271. package/src/components/ExperimentCodeBadge.story.vue +77 -0
  272. package/src/components/ExperimentCodeBadge.vue +64 -0
  273. package/src/components/ExperimentDataViewer.story.vue +174 -0
  274. package/src/components/ExperimentDataViewer.vue +288 -0
  275. package/src/components/ExperimentPopover.story.vue +384 -0
  276. package/src/components/ExperimentPopover.vue +241 -0
  277. package/src/components/ExperimentSelectorModal.story.vue +391 -0
  278. package/src/components/ExperimentSelectorModal.vue +387 -0
  279. package/src/components/ExperimentTimeline.story.vue +161 -0
  280. package/src/components/ExperimentTimeline.vue +382 -0
  281. package/src/components/FileUploader.story.vue +107 -0
  282. package/src/components/FileUploader.vue +386 -0
  283. package/src/components/FitPanel.story.vue +125 -0
  284. package/src/components/FitPanel.vue +120 -0
  285. package/src/components/FormActions.vue +92 -0
  286. package/src/components/FormBuilder.vue +214 -0
  287. package/src/components/FormField.story.vue +132 -0
  288. package/src/components/FormField.vue +59 -0
  289. package/src/components/FormFieldRenderer.vue +58 -0
  290. package/src/components/FormSection.vue +90 -0
  291. package/src/components/FormulaInput.story.vue +96 -0
  292. package/src/components/FormulaInput.vue +125 -0
  293. package/src/components/GroupAssigner.story.vue +83 -0
  294. package/src/components/GroupAssigner.vue +284 -0
  295. package/src/components/GroupingModal.story.vue +52 -0
  296. package/src/components/GroupingModal.vue +422 -0
  297. package/src/components/IconButton.story.vue +135 -0
  298. package/src/components/IconButton.vue +73 -0
  299. package/src/components/LoadingSpinner.story.vue +70 -0
  300. package/src/components/LoadingSpinner.vue +50 -0
  301. package/src/components/MoleculeInput.story.vue +66 -0
  302. package/src/components/MoleculeInput.vue +426 -0
  303. package/src/components/MultiSelect.story.vue +132 -0
  304. package/src/components/MultiSelect.vue +118 -0
  305. package/src/components/NumberInput.story.vue +122 -0
  306. package/src/components/NumberInput.vue +160 -0
  307. package/src/components/PlateMapEditor.story.vue +92 -0
  308. package/src/components/PlateMapEditor.vue +513 -0
  309. package/src/components/ProgressBar.story.vue +148 -0
  310. package/src/components/ProgressBar.vue +114 -0
  311. package/src/components/ProtocolStepEditor.story.vue +69 -0
  312. package/src/components/ProtocolStepEditor.vue +522 -0
  313. package/src/components/RackEditor.story.vue +100 -0
  314. package/src/components/RackEditor.vue +371 -0
  315. package/src/components/ReagentEditor.story.vue +153 -0
  316. package/src/components/ReagentEditor.vue +418 -0
  317. package/src/components/ReagentList.story.vue +137 -0
  318. package/src/components/ReagentList.vue +463 -0
  319. package/src/components/ResourceCard.story.vue +150 -0
  320. package/src/components/ResourceCard.vue +161 -0
  321. package/src/components/SampleHierarchyTree.story.vue +161 -0
  322. package/src/components/SampleHierarchyTree.vue +256 -0
  323. package/src/components/SampleLegend.story.vue +91 -0
  324. package/src/components/SampleLegend.vue +119 -0
  325. package/src/components/SampleSelector.story.vue +111 -0
  326. package/src/components/SampleSelector.vue +1033 -0
  327. package/src/components/ScheduleCalendar.story.vue +195 -0
  328. package/src/components/ScheduleCalendar.vue +569 -0
  329. package/src/components/ScientificNumber.story.vue +127 -0
  330. package/src/components/ScientificNumber.vue +197 -0
  331. package/src/components/SegmentedControl.story.vue +132 -0
  332. package/src/components/SegmentedControl.vue +79 -0
  333. package/src/components/SequenceInput.story.vue +119 -0
  334. package/src/components/SequenceInput.vue +209 -0
  335. package/src/components/SettingsButton.story.vue +58 -0
  336. package/src/components/SettingsButton.vue +76 -0
  337. package/src/components/SettingsModal.story.vue +145 -0
  338. package/src/components/SettingsModal.vue +146 -0
  339. package/src/components/Skeleton.story.vue +141 -0
  340. package/src/components/Skeleton.vue +74 -0
  341. package/src/components/StatusIndicator.story.vue +99 -0
  342. package/src/components/StatusIndicator.vue +40 -0
  343. package/src/components/StepWizard.story.vue +155 -0
  344. package/src/components/StepWizard.vue +223 -0
  345. package/src/components/TagsInput.story.vue +155 -0
  346. package/src/components/TagsInput.vue +265 -0
  347. package/src/components/ThemeToggle.story.vue +36 -0
  348. package/src/components/ThemeToggle.vue +54 -0
  349. package/src/components/TimePicker.story.vue +96 -0
  350. package/src/components/TimePicker.vue +273 -0
  351. package/src/components/TimeRangeInput.story.vue +104 -0
  352. package/src/components/TimeRangeInput.vue +122 -0
  353. package/src/components/ToastNotification.story.vue +157 -0
  354. package/src/components/ToastNotification.vue +62 -0
  355. package/src/components/Tooltip.story.vue +138 -0
  356. package/src/components/Tooltip.vue +119 -0
  357. package/src/components/UnitInput.story.vue +194 -0
  358. package/src/components/UnitInput.vue +213 -0
  359. package/src/components/WellEditPopup.vue +234 -0
  360. package/src/components/WellPlate.story.vue +282 -0
  361. package/src/components/WellPlate.vue +830 -0
  362. package/src/components/index.ts +118 -0
  363. package/src/composables/experiment-utils.ts +57 -0
  364. package/src/composables/formBuilderRegistry.ts +79 -0
  365. package/src/composables/index.ts +140 -0
  366. package/src/composables/useApi.ts +167 -0
  367. package/src/composables/useAppExperiment.ts +159 -0
  368. package/src/composables/useAsync.ts +323 -0
  369. package/src/composables/useAuth.ts +445 -0
  370. package/src/composables/useAutoGroup.ts +641 -0
  371. package/src/composables/useChemicalFormula.ts +275 -0
  372. package/src/composables/useConcentrationUnits.ts +246 -0
  373. package/src/composables/useDoseCalculator.ts +370 -0
  374. package/src/composables/useExperimentData.ts +86 -0
  375. package/src/composables/useExperimentSave.ts +192 -0
  376. package/src/composables/useExperimentSelector.ts +292 -0
  377. package/src/composables/useForm.ts +416 -0
  378. package/src/composables/useFormBuilder.ts +383 -0
  379. package/src/composables/usePasskey.ts +216 -0
  380. package/src/composables/usePlatformContext.ts +299 -0
  381. package/src/composables/usePluginApi.ts +39 -0
  382. package/src/composables/usePluginConfig.ts +93 -0
  383. package/src/composables/useProtocolTemplates.ts +518 -0
  384. package/src/composables/useRackEditor.ts +222 -0
  385. package/src/composables/useReagentSeries.ts +91 -0
  386. package/src/composables/useScheduleDrag.ts +245 -0
  387. package/src/composables/useSequenceUtils.ts +105 -0
  388. package/src/composables/useTheme.ts +58 -0
  389. package/src/composables/useTimeUtils.ts +131 -0
  390. package/src/composables/useToast.ts +40 -0
  391. package/src/composables/useWellPlateEditor.ts +421 -0
  392. package/src/histoire.setup.ts +17 -0
  393. package/src/index.ts +367 -0
  394. package/src/install.ts +32 -0
  395. package/src/stores/auth.ts +152 -0
  396. package/src/stores/index.ts +2 -0
  397. package/src/stores/settings.ts +218 -0
  398. package/src/styles/components/alert-box.css +150 -0
  399. package/src/styles/components/app-avatar-menu.css +155 -0
  400. package/src/styles/components/app-container.css +33 -0
  401. package/src/styles/components/app-layout.css +98 -0
  402. package/src/styles/components/app-page-selector.css +191 -0
  403. package/src/styles/components/app-pill-nav.css +57 -0
  404. package/src/styles/components/app-plugin-switcher.css +209 -0
  405. package/src/styles/components/app-sidebar.css +145 -0
  406. package/src/styles/components/app-top-bar.css +492 -0
  407. package/src/styles/components/audit-trail.css +143 -0
  408. package/src/styles/components/auto-group-modal.css +644 -0
  409. package/src/styles/components/avatar.css +73 -0
  410. package/src/styles/components/batch-progress-list.css +196 -0
  411. package/src/styles/components/breadcrumb.css +64 -0
  412. package/src/styles/components/button.css +188 -0
  413. package/src/styles/components/calendar.css +192 -0
  414. package/src/styles/components/chart-container.css +69 -0
  415. package/src/styles/components/checkbox.css +123 -0
  416. package/src/styles/components/chemical-formula.css +46 -0
  417. package/src/styles/components/collapsible-card.css +253 -0
  418. package/src/styles/components/color-slider.css +110 -0
  419. package/src/styles/components/concentration-input.css +156 -0
  420. package/src/styles/components/confirm-dialog.css +183 -0
  421. package/src/styles/components/dataframe.css +382 -0
  422. package/src/styles/components/date-picker.css +243 -0
  423. package/src/styles/components/datetime-picker.css +229 -0
  424. package/src/styles/components/divider.css +63 -0
  425. package/src/styles/components/dose-calculator.css +301 -0
  426. package/src/styles/components/dropdown-button.css +280 -0
  427. package/src/styles/components/empty-state.css +151 -0
  428. package/src/styles/components/experiment-code-badge.css +33 -0
  429. package/src/styles/components/experiment-data-viewer.css +138 -0
  430. package/src/styles/components/experiment-popover.css +562 -0
  431. package/src/styles/components/experiment-selector-modal.css +285 -0
  432. package/src/styles/components/experiment-timeline.css +529 -0
  433. package/src/styles/components/file-uploader.css +310 -0
  434. package/src/styles/components/fit-panel.css +67 -0
  435. package/src/styles/components/form-builder.css +69 -0
  436. package/src/styles/components/form-field.css +48 -0
  437. package/src/styles/components/formula-input.css +103 -0
  438. package/src/styles/components/group-assigner.css +200 -0
  439. package/src/styles/components/grouping-modal.css +323 -0
  440. package/src/styles/components/icon-button.css +192 -0
  441. package/src/styles/components/input.css +66 -0
  442. package/src/styles/components/loading-spinner.css +67 -0
  443. package/src/styles/components/modal.css +350 -0
  444. package/src/styles/components/molecule-input.css +186 -0
  445. package/src/styles/components/multi-select.css +131 -0
  446. package/src/styles/components/number-input.css +199 -0
  447. package/src/styles/components/pill.css +188 -0
  448. package/src/styles/components/plate-map-editor.css +464 -0
  449. package/src/styles/components/progress-bar.css +133 -0
  450. package/src/styles/components/protocol-step-editor.css +449 -0
  451. package/src/styles/components/rack-editor.css +265 -0
  452. package/src/styles/components/radio-group.css +240 -0
  453. package/src/styles/components/reagent-editor.css +510 -0
  454. package/src/styles/components/reagent-list.css +407 -0
  455. package/src/styles/components/resource-card.css +360 -0
  456. package/src/styles/components/sample-hierarchy-tree.css +314 -0
  457. package/src/styles/components/sample-legend.css +201 -0
  458. package/src/styles/components/sample-selector.css +751 -0
  459. package/src/styles/components/schedule-calendar.css +478 -0
  460. package/src/styles/components/scientific-number.css +63 -0
  461. package/src/styles/components/segmented-control.css +197 -0
  462. package/src/styles/components/select.css +77 -0
  463. package/src/styles/components/sequence-input.css +184 -0
  464. package/src/styles/components/settings-button.css +94 -0
  465. package/src/styles/components/settings-modal.css +95 -0
  466. package/src/styles/components/skeleton.css +49 -0
  467. package/src/styles/components/slider.css +74 -0
  468. package/src/styles/components/status-indicator.css +66 -0
  469. package/src/styles/components/step-wizard.css +192 -0
  470. package/src/styles/components/tabs.css +95 -0
  471. package/src/styles/components/tags-input.css +195 -0
  472. package/src/styles/components/textarea.css +82 -0
  473. package/src/styles/components/theme-toggle.css +69 -0
  474. package/src/styles/components/time-picker.css +171 -0
  475. package/src/styles/components/time-range-input.css +42 -0
  476. package/src/styles/components/toast.css +91 -0
  477. package/src/styles/components/toggle.css +146 -0
  478. package/src/styles/components/tooltip.css +91 -0
  479. package/src/styles/components/unit-input.css +123 -0
  480. package/src/styles/components/well-edit-popup.css +252 -0
  481. package/src/styles/components/well-plate.css +307 -0
  482. package/src/styles/index.css +87 -0
  483. package/src/styles/variables.css +1117 -0
  484. package/src/tailwind.preset.ts +61 -0
  485. package/src/types/auth.ts +55 -0
  486. package/src/types/auto-group.ts +40 -0
  487. package/src/types/components.ts +710 -0
  488. package/src/types/form-builder.ts +197 -0
  489. package/src/types/index.ts +207 -0
  490. package/src/types/platform.ts +116 -0
  491. package/src/utils/color.ts +96 -0
@@ -0,0 +1,116 @@
1
+ import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'
2
+
3
+ const unmountedCallbacks: Array<() => void> = []
4
+
5
+ type MessageHandler = (event: MessageEvent) => void
6
+ let messageHandler: MessageHandler | null = null
7
+
8
+ vi.mock('vue', async () => {
9
+ const actual = await vi.importActual<typeof import('vue')>('vue')
10
+ return {
11
+ ...actual,
12
+ onMounted: vi.fn((cb: () => void) => cb()),
13
+ onUnmounted: vi.fn((cb: () => void) => {
14
+ unmountedCallbacks.push(cb)
15
+ }),
16
+ }
17
+ })
18
+
19
+ import { usePlatformContext } from '../../composables/usePlatformContext'
20
+
21
+ function sendThemeMessage(origin: string, theme: 'light' | 'dark' | 'system'): void {
22
+ if (!messageHandler) {
23
+ throw new Error('Message handler was not registered')
24
+ }
25
+
26
+ messageHandler({
27
+ source: window.parent,
28
+ origin,
29
+ data: {
30
+ type: 'mld:theme-changed',
31
+ payload: theme,
32
+ },
33
+ } as MessageEvent)
34
+ }
35
+
36
+ describe('usePlatformContext', () => {
37
+ beforeEach(() => {
38
+ unmountedCallbacks.length = 0
39
+ messageHandler = null
40
+
41
+ vi.spyOn(window, 'addEventListener').mockImplementation((type, listener) => {
42
+ if (type === 'message') {
43
+ messageHandler = listener as MessageHandler
44
+ }
45
+ })
46
+ vi.spyOn(window, 'removeEventListener').mockImplementation(() => {})
47
+ })
48
+
49
+ afterEach(() => {
50
+ while (unmountedCallbacks.length > 0) {
51
+ const callback = unmountedCallbacks.pop()
52
+ callback?.()
53
+ }
54
+ vi.restoreAllMocks()
55
+ })
56
+
57
+ it('clears allowAnyOrigin policy after last consumer unmounts', () => {
58
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
59
+
60
+ const first = usePlatformContext({ allowAnyOrigin: true })
61
+ sendThemeMessage('https://evil.example', 'dark')
62
+ expect(first.theme.value).toBe('dark')
63
+
64
+ expect(unmountedCallbacks).toHaveLength(1)
65
+ unmountedCallbacks[0]!()
66
+ unmountedCallbacks.length = 0
67
+
68
+ const second = usePlatformContext()
69
+ sendThemeMessage('https://evil.example', 'light')
70
+ expect(second.theme.value).toBe('system')
71
+ expect(
72
+ warnSpy.mock.calls.some(([msg]) => String(msg).includes('Rejected postMessage')),
73
+ ).toBe(true)
74
+ })
75
+
76
+ it('does not leak explicit allowed origins to new consumers', () => {
77
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
78
+
79
+ const first = usePlatformContext({ allowedOrigins: ['https://trusted.example'] })
80
+ sendThemeMessage('https://trusted.example', 'dark')
81
+ expect(first.theme.value).toBe('dark')
82
+
83
+ expect(unmountedCallbacks).toHaveLength(1)
84
+ unmountedCallbacks[0]!()
85
+ unmountedCallbacks.length = 0
86
+
87
+ const second = usePlatformContext()
88
+ sendThemeMessage('https://trusted.example', 'light')
89
+ expect(second.theme.value).toBe('system')
90
+ expect(
91
+ warnSpy.mock.calls.some(([msg]) => String(msg).includes('Rejected postMessage')),
92
+ ).toBe(true)
93
+ })
94
+
95
+ it('removes allowAnyOrigin scope when that consumer unmounts while others remain', () => {
96
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
97
+
98
+ usePlatformContext({ allowAnyOrigin: true })
99
+ const second = usePlatformContext()
100
+
101
+ sendThemeMessage('https://evil.example', 'dark')
102
+ expect(second.theme.value).toBe('dark')
103
+
104
+ expect(unmountedCallbacks).toHaveLength(2)
105
+ unmountedCallbacks[0]!()
106
+
107
+ sendThemeMessage('https://evil.example', 'light')
108
+ expect(second.theme.value).toBe('dark')
109
+ expect(
110
+ warnSpy.mock.calls.some(([msg]) => String(msg).includes('Rejected postMessage')),
111
+ ).toBe(true)
112
+
113
+ unmountedCallbacks[1]!()
114
+ unmountedCallbacks.length = 0
115
+ })
116
+ })
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Tests for usePluginApi.
3
+ *
4
+ * Exercises the baseUrl resolution priority that every plugin relies on:
5
+ * 1. VITE_API_PREFIX (build-time env override)
6
+ * 2. fallbackPrefix option (plugin's route prefix)
7
+ * 3. '/api' default
8
+ *
9
+ * The SDK-wide axios client is a singleton, so these tests stub axios
10
+ * with a MockAdapter-style approach via vi.spyOn to observe the
11
+ * baseURL actually used at request time.
12
+ */
13
+
14
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
15
+ import { createPinia, setActivePinia } from 'pinia'
16
+ import axios, { type AxiosRequestConfig } from 'axios'
17
+
18
+ import { usePluginApi } from '../../composables/usePluginApi'
19
+
20
+ // Spy on axios create so each test starts with a fresh instance hook
21
+ // and we can observe request configs.
22
+ describe('usePluginApi', () => {
23
+ let requestConfigs: AxiosRequestConfig[] = []
24
+
25
+ beforeEach(() => {
26
+ setActivePinia(createPinia())
27
+ requestConfigs = []
28
+ // Intercept axios.get on the shared instance
29
+ vi.spyOn(axios.Axios.prototype, 'get').mockImplementation(async function (
30
+ this: unknown,
31
+ url: string,
32
+ config?: AxiosRequestConfig,
33
+ ) {
34
+ requestConfigs.push({ url, ...config })
35
+ return { data: { called: url, baseURL: config?.baseURL } }
36
+ })
37
+ })
38
+
39
+ afterEach(() => {
40
+ vi.restoreAllMocks()
41
+ vi.unstubAllEnvs()
42
+ })
43
+
44
+ it('uses fallbackPrefix when no env override', async () => {
45
+ const api = usePluginApi({ fallbackPrefix: '/api/drp' })
46
+ await api.get('/sessions')
47
+ expect(requestConfigs).toHaveLength(1)
48
+ expect(requestConfigs[0]!.baseURL).toBe('/api/drp')
49
+ })
50
+
51
+ it('falls back to /api when neither env nor fallback provided', async () => {
52
+ const api = usePluginApi()
53
+ await api.get('/health')
54
+ expect(requestConfigs[0]!.baseURL).toBe('/api')
55
+ })
56
+
57
+ it('VITE_API_PREFIX env var overrides fallbackPrefix', async () => {
58
+ vi.stubEnv('VITE_API_PREFIX', '/api/override')
59
+ const api = usePluginApi({ fallbackPrefix: '/api/drp' })
60
+ await api.get('/check')
61
+ expect(requestConfigs[0]!.baseURL).toBe('/api/override')
62
+ })
63
+
64
+ it('empty VITE_API_PREFIX env var falls through to fallback', async () => {
65
+ vi.stubEnv('VITE_API_PREFIX', '')
66
+ const api = usePluginApi({ fallbackPrefix: '/api/drp' })
67
+ await api.get('/check')
68
+ expect(requestConfigs[0]!.baseURL).toBe('/api/drp')
69
+ })
70
+
71
+ it('returns a client with all CRUD methods', () => {
72
+ const api = usePluginApi({ fallbackPrefix: '/api/drp' })
73
+ expect(typeof api.get).toBe('function')
74
+ expect(typeof api.post).toBe('function')
75
+ expect(typeof api.put).toBe('function')
76
+ expect(typeof api.patch).toBe('function')
77
+ expect(typeof api.delete).toBe('function')
78
+ expect(typeof api.upload).toBe('function')
79
+ expect(typeof api.buildUrl).toBe('function')
80
+ })
81
+ })
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Tests for usePluginConfig.
3
+ *
4
+ * Covers the plugin settings life-cycle used by every plugin's
5
+ * settings pane:
6
+ *
7
+ * - resolvedName prefers explicit pluginName, falls back to platform plugin
8
+ * - load() populates config + savedConfig from /plugins/{name}/config
9
+ * - save() PATCHes the endpoint, updates savedConfig, returns true
10
+ * - isDirty reflects user edits vs last saved state
11
+ * - reset() restores config to savedConfig and clears error
12
+ * - error messages bubble up from network failures
13
+ */
14
+
15
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
16
+ import { createPinia, setActivePinia } from 'pinia'
17
+ import axios, { type AxiosRequestConfig } from 'axios'
18
+ import { nextTick } from 'vue'
19
+
20
+ // usePluginConfig calls onMounted(load); happy-dom doesn't run Vue's
21
+ // lifecycle outside a component, so we stub onMounted to call the
22
+ // callback synchronously if and only if the test opts in.
23
+ let autoLoadOnMount = false
24
+ vi.mock('vue', async () => {
25
+ const actual = await vi.importActual<typeof import('vue')>('vue')
26
+ return {
27
+ ...actual,
28
+ onMounted: vi.fn((cb: () => void) => {
29
+ if (autoLoadOnMount) cb()
30
+ }),
31
+ }
32
+ })
33
+
34
+ import { usePluginConfig } from '../../composables/usePluginConfig'
35
+
36
+ type FakeResponse = Record<string, unknown>
37
+
38
+ let getResponse: FakeResponse = { plugin_name: 'drp', config: {} }
39
+ let patchResponse: FakeResponse = { plugin_name: 'drp', config: {} }
40
+ let getError: Error | null = null
41
+ let patchError: Error | null = null
42
+ let recordedCalls: { method: string; url: string; data?: unknown }[] = []
43
+
44
+ function mockAxios() {
45
+ vi.spyOn(axios.Axios.prototype, 'get').mockImplementation(async function (
46
+ this: unknown,
47
+ url: string,
48
+ _config?: AxiosRequestConfig,
49
+ ) {
50
+ recordedCalls.push({ method: 'GET', url })
51
+ if (getError) throw getError
52
+ return { data: getResponse }
53
+ })
54
+ vi.spyOn(axios.Axios.prototype, 'patch').mockImplementation(async function (
55
+ this: unknown,
56
+ url: string,
57
+ data?: unknown,
58
+ _config?: AxiosRequestConfig,
59
+ ) {
60
+ recordedCalls.push({ method: 'PATCH', url, data })
61
+ if (patchError) throw patchError
62
+ return { data: patchResponse }
63
+ })
64
+ }
65
+
66
+ describe('usePluginConfig', () => {
67
+ beforeEach(() => {
68
+ setActivePinia(createPinia())
69
+ autoLoadOnMount = false
70
+ getResponse = { plugin_name: 'drp', config: {} }
71
+ patchResponse = { plugin_name: 'drp', config: {} }
72
+ getError = null
73
+ patchError = null
74
+ recordedCalls = []
75
+ mockAxios()
76
+ })
77
+
78
+ afterEach(() => {
79
+ vi.restoreAllMocks()
80
+ })
81
+
82
+ it('load fetches config from correct endpoint and populates state', async () => {
83
+ getResponse = {
84
+ plugin_name: 'drp',
85
+ config: { raw_files_path: '/data/rfa', threshold: 0.5 },
86
+ }
87
+ const hook = usePluginConfig('drp')
88
+ await hook.load()
89
+
90
+ expect(recordedCalls).toEqual([
91
+ { method: 'GET', url: '/plugins/drp/config' },
92
+ ])
93
+ expect(hook.config.value).toEqual({
94
+ raw_files_path: '/data/rfa',
95
+ threshold: 0.5,
96
+ })
97
+ expect(hook.isDirty.value).toBe(false)
98
+ expect(hook.error.value).toBeNull()
99
+ })
100
+
101
+ it('encodes special characters in plugin name', async () => {
102
+ const hook = usePluginConfig('my/plugin')
103
+ await hook.load()
104
+ expect(recordedCalls[0]!.url).toBe('/plugins/my%2Fplugin/config')
105
+ })
106
+
107
+ it('save PATCHes the plugin config endpoint', async () => {
108
+ getResponse = { plugin_name: 'drp', config: { n: 1 } }
109
+ patchResponse = { plugin_name: 'drp', config: { n: 2 } }
110
+ const hook = usePluginConfig('drp')
111
+ await hook.load()
112
+ hook.config.value = { n: 2 }
113
+ await nextTick()
114
+ expect(hook.isDirty.value).toBe(true)
115
+
116
+ const ok = await hook.save()
117
+ expect(ok).toBe(true)
118
+ const patchCall = recordedCalls.find((c) => c.method === 'PATCH')!
119
+ expect(patchCall.url).toBe('/plugins/drp/config')
120
+ expect(patchCall.data).toEqual({ config: { n: 2 } })
121
+ expect(hook.config.value).toEqual({ n: 2 })
122
+ expect(hook.isDirty.value).toBe(false)
123
+ })
124
+
125
+ it('isDirty reflects edits vs last saved snapshot', async () => {
126
+ getResponse = { plugin_name: 'drp', config: { k: 'v' } }
127
+ const hook = usePluginConfig('drp')
128
+ await hook.load()
129
+ expect(hook.isDirty.value).toBe(false)
130
+
131
+ hook.config.value = { k: 'changed' }
132
+ await nextTick()
133
+ expect(hook.isDirty.value).toBe(true)
134
+
135
+ hook.config.value = { k: 'v' }
136
+ await nextTick()
137
+ expect(hook.isDirty.value).toBe(false)
138
+ })
139
+
140
+ it('reset restores saved config and clears error', async () => {
141
+ getResponse = { plugin_name: 'drp', config: { k: 'v' } }
142
+ const hook = usePluginConfig('drp')
143
+ await hook.load()
144
+ hook.config.value = { k: 'edited' }
145
+ hook.error.value = 'something'
146
+
147
+ hook.reset()
148
+ expect(hook.config.value).toEqual({ k: 'v' })
149
+ expect(hook.error.value).toBeNull()
150
+ })
151
+
152
+ it('load surfaces network error message', async () => {
153
+ getError = new Error('network down')
154
+ const hook = usePluginConfig('drp')
155
+ await hook.load()
156
+ expect(hook.error.value).toBe('network down')
157
+ expect(hook.isLoading.value).toBe(false)
158
+ })
159
+
160
+ it('save returns false and records error on failure', async () => {
161
+ patchError = new Error('403')
162
+ const hook = usePluginConfig('drp')
163
+ await hook.load()
164
+ hook.config.value = { x: 1 }
165
+ const ok = await hook.save()
166
+ expect(ok).toBe(false)
167
+ expect(hook.error.value).toBe('403')
168
+ })
169
+
170
+ it('empty plugin name produces a noop load', async () => {
171
+ const hook = usePluginConfig('')
172
+ await hook.load()
173
+ expect(recordedCalls).toEqual([])
174
+ expect(hook.error.value).toBeNull()
175
+ })
176
+ })
@@ -0,0 +1,96 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { hexToHsl, hslToHex, deriveShade } from '../../utils/color'
3
+
4
+ describe('hexToHsl', () => {
5
+ it('parses 6-digit hex', () => {
6
+ const hsl = hexToHsl('#3B82F6')
7
+ expect(hsl.h).toBeGreaterThan(200)
8
+ expect(hsl.h).toBeLessThan(230)
9
+ expect(hsl.s).toBeGreaterThan(80)
10
+ expect(hsl.l).toBeGreaterThan(45)
11
+ expect(hsl.l).toBeLessThan(65)
12
+ })
13
+
14
+ it('parses 3-digit hex', () => {
15
+ const a = hexToHsl('#39F')
16
+ const b = hexToHsl('#3399FF')
17
+ expect(Math.abs(a.h - b.h)).toBeLessThan(1)
18
+ expect(Math.abs(a.s - b.s)).toBeLessThan(1)
19
+ expect(Math.abs(a.l - b.l)).toBeLessThan(1)
20
+ })
21
+
22
+ it('handles missing # prefix', () => {
23
+ const a = hexToHsl('3B82F6')
24
+ const b = hexToHsl('#3B82F6')
25
+ expect(a).toEqual(b)
26
+ })
27
+
28
+ it('returns neutral gray for invalid input', () => {
29
+ const hsl = hexToHsl('not-a-hex')
30
+ expect(hsl.h).toBe(0)
31
+ expect(hsl.s).toBe(0)
32
+ expect(hsl.l).toBe(50)
33
+ })
34
+ })
35
+
36
+ describe('hslToHex round-trip', () => {
37
+ it('round-trips common colors within 1% lightness and 1° hue', () => {
38
+ const samples = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6']
39
+ for (const hex of samples) {
40
+ const hsl = hexToHsl(hex)
41
+ const back = hexToHsl(hslToHex(hsl.h, hsl.s, hsl.l))
42
+ expect(Math.abs(back.l - hsl.l)).toBeLessThan(1)
43
+ // Shortest angular distance between input and output hue
44
+ const dh = Math.abs(((back.h - hsl.h + 540) % 360) - 180)
45
+ const hueDelta = Math.min(dh, 360 - dh)
46
+ expect(hueDelta).toBeLessThan(1)
47
+ }
48
+ })
49
+ })
50
+
51
+ describe('deriveShade', () => {
52
+ it('returns the parent unchanged when total <= 1', () => {
53
+ expect(deriveShade('#3B82F6', 0, 1)).toBe('#3B82F6')
54
+ expect(deriveShade('#10B981', 0, 0)).toBe('#10B981')
55
+ })
56
+
57
+ it('produces children that share the parent hue', () => {
58
+ const parent = '#3B82F6'
59
+ const parentH = hexToHsl(parent).h
60
+
61
+ const shades = [0, 1, 2, 3].map(i => deriveShade(parent, i, 4))
62
+ for (const child of shades) {
63
+ const ch = hexToHsl(child).h
64
+ expect(Math.abs(ch - parentH)).toBeLessThan(2) // hue locked
65
+ }
66
+ })
67
+
68
+ it('produces a monotonically darkening progression', () => {
69
+ const shades = [0, 1, 2, 3, 4].map(i => deriveShade('#3B82F6', i, 5))
70
+ const lightnesses = shades.map(s => hexToHsl(s).l)
71
+ for (let i = 1; i < lightnesses.length; i++) {
72
+ expect(lightnesses[i]).toBeLessThan(lightnesses[i - 1])
73
+ }
74
+ })
75
+
76
+ it('clamps lightness within readable [35, 75] band', () => {
77
+ const lightParent = '#FFFFFF'
78
+ const darkParent = '#000000'
79
+ for (const parent of [lightParent, darkParent]) {
80
+ for (let i = 0; i < 4; i++) {
81
+ const child = deriveShade(parent, i, 4)
82
+ const { l } = hexToHsl(child)
83
+ expect(l).toBeGreaterThanOrEqual(34)
84
+ expect(l).toBeLessThanOrEqual(76)
85
+ }
86
+ }
87
+ })
88
+
89
+ it('keeps minimum saturation so colors do not turn grey', () => {
90
+ const shades = [0, 1, 2].map(i => deriveShade('#3B82F6', i, 3))
91
+ for (const c of shades) {
92
+ const { s } = hexToHsl(c)
93
+ expect(s).toBeGreaterThanOrEqual(35)
94
+ }
95
+ })
96
+ })
@@ -0,0 +1,204 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import AlertBox from './AlertBox.vue'
4
+ import type { AlertType } from '../types'
5
+
6
+ const alertTypes: AlertType[] = ['info', 'success', 'warning', 'error']
7
+ const dismissed = ref(false)
8
+ const actionFired = ref('')
9
+
10
+ function handleDismiss() {
11
+ dismissed.value = true
12
+ setTimeout(() => { dismissed.value = false }, 2000)
13
+ }
14
+
15
+ function handleAction(label: string) {
16
+ actionFired.value = label
17
+ setTimeout(() => { actionFired.value = '' }, 2000)
18
+ }
19
+ </script>
20
+
21
+ <template>
22
+ <Story title="Feedback/AlertBox">
23
+ <Variant title="Playground">
24
+ <template #default="{ state }">
25
+ <div style="padding: 2rem; max-width: 600px; margin: 0 auto;">
26
+ <AlertBox
27
+ :type="state.type"
28
+ :title="state.showTitle ? state.title : undefined"
29
+ :dismissible="state.dismissible"
30
+ :action-label="state.showAction ? state.actionLabel : undefined"
31
+ @dismiss="() => console.log('dismissed')"
32
+ @action="() => handleAction(state.actionLabel)"
33
+ >
34
+ {{ state.message }}
35
+ </AlertBox>
36
+ <p
37
+ v-if="actionFired"
38
+ style="margin-top: 0.75rem; font-size: 0.8rem; color: var(--mint-success, #10B981); font-family: 'Fira Code', monospace;"
39
+ >
40
+ Action fired: "{{ actionFired }}"
41
+ </p>
42
+ </div>
43
+ </template>
44
+
45
+ <template #controls="{ state }">
46
+ <HstSelect
47
+ v-model="state.type"
48
+ title="Type"
49
+ :options="alertTypes.map(t => ({ label: t, value: t }))"
50
+ />
51
+ <HstCheckbox v-model="state.showTitle" title="Show Title" />
52
+ <HstText v-model="state.title" title="Title" />
53
+ <HstText v-model="state.message" title="Message" />
54
+ <HstCheckbox v-model="state.dismissible" title="Dismissible" />
55
+ <HstCheckbox v-model="state.showAction" title="Show Action" />
56
+ <HstText v-model="state.actionLabel" title="Action Label" />
57
+ </template>
58
+ </Variant>
59
+
60
+ <Variant title="All Types">
61
+ <div style="padding: 2rem; max-width: 600px; margin: 0 auto; display: flex; flex-direction: column; gap: 1rem;">
62
+ <AlertBox
63
+ v-for="type in alertTypes"
64
+ :key="type"
65
+ :type="type"
66
+ :title="`${type.charAt(0).toUpperCase() + type.slice(1)} Alert`"
67
+ >
68
+ This is a {{ type }} alert message with supporting details.
69
+ </AlertBox>
70
+ </div>
71
+ </Variant>
72
+
73
+ <Variant title="With Action Button">
74
+ <div style="padding: 2rem; max-width: 600px; margin: 0 auto; display: flex; flex-direction: column; gap: 1rem;">
75
+ <AlertBox
76
+ type="error"
77
+ title="Server connection lost"
78
+ action-label="Restart Server"
79
+ @action="handleAction('Restart Server')"
80
+ >
81
+ The backend server is not responding. Check your network connection or restart the server.
82
+ </AlertBox>
83
+
84
+ <AlertBox
85
+ type="warning"
86
+ title="Experiment incomplete"
87
+ action-label="Resume"
88
+ dismissible
89
+ @action="handleAction('Resume')"
90
+ >
91
+ 3 wells have missing sample assignments. Resume to continue setup.
92
+ </AlertBox>
93
+
94
+ <AlertBox
95
+ type="info"
96
+ title="Update available"
97
+ action-label="Update Now"
98
+ @action="handleAction('Update Now')"
99
+ >
100
+ MINT Platform v1.0.0 is available with new analysis features.
101
+ </AlertBox>
102
+
103
+ <AlertBox
104
+ type="success"
105
+ title="Analysis complete"
106
+ action-label="View Results"
107
+ @action="handleAction('View Results')"
108
+ >
109
+ Successfully processed 96 wells across 3 plates.
110
+ </AlertBox>
111
+
112
+ <p
113
+ v-if="actionFired"
114
+ style="font-size: 0.8rem; color: var(--mint-success, #10B981); font-family: 'Fira Code', monospace;"
115
+ >
116
+ Action fired: "{{ actionFired }}"
117
+ </p>
118
+ </div>
119
+ </Variant>
120
+
121
+ <Variant title="Custom Actions Slot">
122
+ <div style="padding: 2rem; max-width: 600px; margin: 0 auto;">
123
+ <AlertBox type="error" title="Plugin conflict detected">
124
+ <template #default>
125
+ mld-plugin-drp requires numpy &gt;=2.0 but the current environment has numpy 1.26.4.
126
+ </template>
127
+ <template #actions>
128
+ <button
129
+ style="padding: 0.3125rem 0.75rem; font-size: 0.8125rem; font-weight: 500; border-radius: 0.375rem; border: 1px solid var(--mint-error-border); color: var(--mint-error); background: transparent; cursor: pointer; white-space: nowrap;"
130
+ @click="handleAction('Force Install')"
131
+ >
132
+ Force Install
133
+ </button>
134
+ <button
135
+ style="padding: 0.3125rem 0.75rem; font-size: 0.8125rem; font-weight: 500; border-radius: 0.375rem; border: 1px solid var(--border-color); color: var(--text-secondary); background: transparent; cursor: pointer; white-space: nowrap;"
136
+ @click="handleAction('View Details')"
137
+ >
138
+ Details
139
+ </button>
140
+ </template>
141
+ </AlertBox>
142
+ </div>
143
+ </Variant>
144
+
145
+ <Variant title="Dismissible">
146
+ <div style="padding: 2rem; max-width: 600px; margin: 0 auto;">
147
+ <AlertBox
148
+ v-if="!dismissed"
149
+ type="warning"
150
+ title="Experiment incomplete"
151
+ dismissible
152
+ @dismiss="handleDismiss"
153
+ >
154
+ 3 wells have missing sample assignments. Click dismiss to hide this alert (it will reappear after 2 seconds).
155
+ </AlertBox>
156
+ <p v-else style="color: var(--text-muted, #94a3b8); font-style: italic;">
157
+ Alert dismissed. Reappearing shortly...
158
+ </p>
159
+ </div>
160
+ </Variant>
161
+
162
+ <Variant title="Without Title">
163
+ <div style="padding: 2rem; max-width: 600px; margin: 0 auto; display: flex; flex-direction: column; gap: 1rem;">
164
+ <AlertBox type="info">
165
+ A simple informational message without a title.
166
+ </AlertBox>
167
+ <AlertBox type="error">
168
+ Something went wrong while processing the sample batch.
169
+ </AlertBox>
170
+ </div>
171
+ </Variant>
172
+
173
+ <Variant title="Rich Content">
174
+ <div style="padding: 2rem; max-width: 600px; margin: 0 auto;">
175
+ <AlertBox type="success" title="Analysis Complete" action-label="Download Report">
176
+ <p style="margin: 0 0 0.5rem;">
177
+ Successfully processed <strong>96 wells</strong> across 3 plates.
178
+ </p>
179
+ <ul style="margin: 0; padding-left: 1.25rem;">
180
+ <li>Peak detection: 1,247 peaks found</li>
181
+ <li>QC passed: 94/96 wells</li>
182
+ <li>Outliers flagged: 2 wells (B3, G11)</li>
183
+ </ul>
184
+ </AlertBox>
185
+ </div>
186
+ </Variant>
187
+ <Variant title="Centered alignment (default)">
188
+ <div style="padding: 2rem; max-width: 520px; display: flex; flex-direction: column; gap: 1rem;">
189
+ <AlertBox type="success" title="Analysis complete" action-label="View results">
190
+ Dose-response fit converged for all 96 wells.
191
+ </AlertBox>
192
+ <AlertBox type="error" title="Upload failed" dismissible>
193
+ The file exceeded the 50 MB limit.
194
+ </AlertBox>
195
+ <AlertBox type="warning" title="Low replicate count">
196
+ Wells C2, C3, C4 only have 2 replicates — consider additional runs.
197
+ </AlertBox>
198
+ <AlertBox type="info" title="New beta release">
199
+ Version 0.16.0-beta.3 ships a visual refresh for status components.
200
+ </AlertBox>
201
+ </div>
202
+ </Variant>
203
+ </Story>
204
+ </template>