@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,383 @@
1
+ import { ref, computed, watch } from 'vue'
2
+ import { useForm, type FieldRules, type UseFormReturn } from './useForm'
3
+ import { getFieldRegistryEntry, getTypeDefault } from './formBuilderRegistry'
4
+ import type {
5
+ FormSchema,
6
+ FormFieldSchema,
7
+ FormSectionSchema,
8
+ FieldCondition,
9
+ FieldValidation,
10
+ FormEnhancements,
11
+ UseFormBuilderReturn,
12
+ } from '../types/form-builder'
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Condition evaluator
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /**
19
+ * Evaluate a JSON-serializable field condition against the current form data.
20
+ *
21
+ * Supports logical operators (`and`, `or`, `not`) and comparison operators
22
+ * (`eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `in`, `notIn`, `truthy`, `falsy`,
23
+ * `contains`). Returns `true` if the condition passes.
24
+ */
25
+ export function evaluateCondition(
26
+ condition: FieldCondition,
27
+ data: Record<string, unknown>,
28
+ ): boolean {
29
+ if ('and' in condition) {
30
+ return condition.and.every((c) => evaluateCondition(c, data))
31
+ }
32
+ if ('or' in condition) {
33
+ return condition.or.some((c) => evaluateCondition(c, data))
34
+ }
35
+ if ('not' in condition) {
36
+ return !evaluateCondition(condition.not, data)
37
+ }
38
+
39
+ const value = data[condition.field]
40
+
41
+ if ('eq' in condition) return value === condition.eq
42
+ if ('neq' in condition) return value !== condition.neq
43
+ if ('gt' in condition) return typeof value === 'number' && value > condition.gt
44
+ if ('lt' in condition) return typeof value === 'number' && value < condition.lt
45
+ if ('gte' in condition) return typeof value === 'number' && value >= condition.gte
46
+ if ('lte' in condition) return typeof value === 'number' && value <= condition.lte
47
+ if ('in' in condition) return condition.in.includes(value)
48
+ if ('notIn' in condition) return !condition.notIn.includes(value)
49
+ if ('truthy' in condition) return !!value
50
+ if ('falsy' in condition) return !value
51
+ if ('contains' in condition) {
52
+ if (typeof value === 'string') return value.includes(condition.contains)
53
+ if (Array.isArray(value)) return value.includes(condition.contains)
54
+ return false
55
+ }
56
+
57
+ return true
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Schema helpers
62
+ // ---------------------------------------------------------------------------
63
+
64
+ /** Return all sections across steps (wizard) or directly from a flat schema. */
65
+ function collectSections(schema: FormSchema): FormSectionSchema[] {
66
+ return schema.steps ? schema.steps.flatMap((step) => step.sections) : schema.sections
67
+ }
68
+
69
+ /** Return all field schemas in schema order, flattening sections and steps. */
70
+ function flattenFields(schema: FormSchema): FormFieldSchema[] {
71
+ return collectSections(schema).flatMap((section) => section.fields)
72
+ }
73
+
74
+ /** Convert a JSON-safe `FieldValidation` descriptor to a runtime `FieldRules` object. */
75
+ function convertValidation(v: FieldValidation): FieldRules {
76
+ const rules: FieldRules = {}
77
+
78
+ if (v.required !== undefined) rules.required = v.required
79
+ if (v.minLength !== undefined) rules.minLength = v.minLength
80
+ if (v.maxLength !== undefined) rules.maxLength = v.maxLength
81
+ if (v.min !== undefined) rules.min = v.min
82
+ if (v.max !== undefined) rules.max = v.max
83
+ if (v.email !== undefined) rules.email = v.email
84
+
85
+ if (v.pattern !== undefined) {
86
+ rules.pattern =
87
+ typeof v.pattern === 'string'
88
+ ? new RegExp(v.pattern)
89
+ : { value: new RegExp(v.pattern.value), message: v.pattern.message }
90
+ }
91
+
92
+ return rules
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // useFormBuilder
97
+ // ---------------------------------------------------------------------------
98
+
99
+ /**
100
+ * Drive a `FormSchema` as reactive form state.
101
+ *
102
+ * Builds initial values from schema defaults and `initialData`, derives
103
+ * validation rules from `FieldValidation` descriptors and enhancement
104
+ * validators, evaluates `FieldCondition` expressions for field/section
105
+ * visibility, and wires wizard step navigation when `schema.steps` is set.
106
+ *
107
+ * @param schema - Declarative form or wizard schema.
108
+ * @param initialData - Values that override schema defaults.
109
+ * @param enhancements - TypeScript-only callbacks (dynamic options, validators,
110
+ * submit handler, transform, field-change watcher).
111
+ */
112
+ /** Drives a declarative FormSchema as reactive state with conditional fields, wizard steps, and validation. */
113
+ export function useFormBuilder<T extends Record<string, unknown> = Record<string, unknown>>(
114
+ schema: FormSchema,
115
+ initialData?: Partial<T>,
116
+ enhancements?: FormEnhancements<T>,
117
+ ): UseFormBuilderReturn<T> {
118
+ const fields = flattenFields(schema)
119
+ const sections = collectSections(schema)
120
+
121
+ // -- Build initial values --------------------------------------------------
122
+ const initialValues = {} as Record<string, unknown>
123
+ for (const field of fields) {
124
+ const key = field.name
125
+ if (initialData && key in initialData) {
126
+ initialValues[key] = (initialData as Record<string, unknown>)[key]
127
+ } else if (field.defaultValue !== undefined) {
128
+ initialValues[key] = field.defaultValue
129
+ } else {
130
+ initialValues[key] = getTypeDefault(field.type)
131
+ }
132
+ }
133
+
134
+ // -- Build validation rules ------------------------------------------------
135
+ const rules: Partial<Record<string, FieldRules>> = {}
136
+ for (const field of fields) {
137
+ const base: FieldRules = field.validation ? convertValidation(field.validation) : {}
138
+ const enhancement = enhancements?.fields?.[field.name as keyof T]
139
+
140
+ // Wrap validators to skip hidden fields
141
+ const customValidators: Array<(value: unknown, formData: Record<string, unknown>) => string | undefined | null> = []
142
+
143
+ if (enhancement?.validate) {
144
+ const fn = enhancement.validate
145
+ customValidators.push((value, formData) => fn(value, formData as T))
146
+ }
147
+
148
+ if (customValidators.length > 0) {
149
+ base.custom = customValidators
150
+ }
151
+
152
+ // Wrap all rules to skip hidden fields
153
+ if (Object.keys(base).length > 0) {
154
+ rules[field.name] = base
155
+ }
156
+ }
157
+
158
+ // -- Create form -----------------------------------------------------------
159
+ const form = useForm(initialValues as T, rules as Partial<Record<keyof T, FieldRules>>)
160
+
161
+ // -- Visibility ------------------------------------------------------------
162
+ function isFieldVisible(name: string): boolean {
163
+ const enhancement = enhancements?.fields?.[name as keyof T]
164
+ if (enhancement?.visible) {
165
+ return enhancement.visible(form.data as T)
166
+ }
167
+
168
+ const field = fields.find((f) => f.name === name)
169
+ if (field?.condition) {
170
+ return evaluateCondition(field.condition, form.data as Record<string, unknown>)
171
+ }
172
+
173
+ return true
174
+ }
175
+
176
+ function isSectionVisible(id: string): boolean {
177
+ const section = sections.find((s) => s.id === id)
178
+ if (section?.condition) {
179
+ return evaluateCondition(section.condition, form.data as Record<string, unknown>)
180
+ }
181
+ return true
182
+ }
183
+
184
+ // -- Resolved field props --------------------------------------------------
185
+ function getResolvedFieldProps(field: FormFieldSchema): Record<string, unknown> {
186
+ const entry = getFieldRegistryEntry(field.type)
187
+ const formProps = form.getFieldProps(field.name as keyof T)
188
+
189
+ const merged: Record<string, unknown> = {
190
+ ...entry.defaults,
191
+ ...(field.props ?? {}),
192
+ }
193
+
194
+ // Dynamic enhancement props
195
+ const enhancement = enhancements?.fields?.[field.name as keyof T]
196
+ if (enhancement?.props) {
197
+ Object.assign(merged, enhancement.props(form.data as T))
198
+ }
199
+
200
+ // Dynamic options
201
+ const options = getFieldOptions(field.name)
202
+ if (options) {
203
+ merged.options = options
204
+ }
205
+
206
+ // Form field bindings
207
+ if (entry.vModel) {
208
+ merged.modelValue = formProps.modelValue
209
+ merged['onUpdate:modelValue'] = formProps['onUpdate:modelValue']
210
+ }
211
+ merged.onBlur = formProps.onBlur
212
+
213
+ // Error as boolean for components that use boolean error prop
214
+ const errorMsg = formProps.error
215
+ if (errorMsg) {
216
+ merged.error = true
217
+ }
218
+
219
+ // Schema-level props
220
+ if (field.placeholder) merged.placeholder = field.placeholder
221
+ if (field.size) merged.size = field.size
222
+ if (field.disabled) merged.disabled = true
223
+ if (field.readonly) merged.readonly = true
224
+
225
+ // Radio group needs a name prop
226
+ if (field.type === 'radio' && !merged.name) {
227
+ merged.name = field.name
228
+ }
229
+
230
+ return merged
231
+ }
232
+
233
+ function getFieldOptions(name: string): { label: string; value: unknown }[] | undefined {
234
+ const enhancement = enhancements?.fields?.[name as keyof T]
235
+ if (enhancement?.options) {
236
+ return enhancement.options(form.data as T)
237
+ }
238
+
239
+ const field = fields.find((f) => f.name === name)
240
+ if (field?.props?.options) {
241
+ return field.props.options as { label: string; value: unknown }[]
242
+ }
243
+
244
+ return undefined
245
+ }
246
+
247
+ // -- Wizard state ----------------------------------------------------------
248
+ const currentStep = ref(0)
249
+
250
+ const isCurrentStepValid = computed(() => {
251
+ if (!schema.steps) return form.isValid.value
252
+
253
+ const step = schema.steps[currentStep.value]
254
+ if (!step) return true
255
+
256
+ for (const section of step.sections) {
257
+ if (!isSectionVisible(section.id)) continue
258
+ for (const field of section.fields) {
259
+ if (!isFieldVisible(field.name)) continue
260
+ if (!form.validateField(field.name)) return false
261
+ }
262
+ }
263
+ return true
264
+ })
265
+
266
+ function goNext(): boolean {
267
+ if (!schema.steps) return false
268
+
269
+ // Touch and validate all visible fields in current step
270
+ const step = schema.steps[currentStep.value]
271
+ if (!step) return false
272
+
273
+ let valid = true
274
+ for (const section of step.sections) {
275
+ if (!isSectionVisible(section.id)) continue
276
+ for (const field of section.fields) {
277
+ if (!isFieldVisible(field.name)) continue
278
+ form.setFieldTouched(field.name, true)
279
+ if (!form.validateField(field.name)) valid = false
280
+ }
281
+ }
282
+
283
+ if (!valid) return false
284
+
285
+ if (currentStep.value < schema.steps.length - 1) {
286
+ currentStep.value++
287
+ }
288
+ return true
289
+ }
290
+
291
+ function goBack(): void {
292
+ if (currentStep.value > 0) {
293
+ currentStep.value--
294
+ }
295
+ }
296
+
297
+ function goToStep(index: number): void {
298
+ if (!schema.steps) return
299
+ if (index >= 0 && index < schema.steps.length) {
300
+ currentStep.value = index
301
+ }
302
+ }
303
+
304
+ // -- Validate (skip hidden fields) -----------------------------------------
305
+ function validate(): boolean {
306
+ let allValid = true
307
+ for (const field of fields) {
308
+ if (!isFieldVisible(field.name)) continue
309
+ form.setFieldTouched(field.name, true)
310
+ if (!form.validateField(field.name)) {
311
+ allValid = false
312
+ }
313
+ }
314
+ return allValid
315
+ }
316
+
317
+ // -- Submit ----------------------------------------------------------------
318
+ async function submit(): Promise<void> {
319
+ if (!validate()) return
320
+
321
+ // Build submission data excluding hidden fields
322
+ let submitData = {} as Record<string, unknown>
323
+ for (const field of fields) {
324
+ if (isFieldVisible(field.name)) {
325
+ submitData[field.name] = (form.data as Record<string, unknown>)[field.name]
326
+ }
327
+ }
328
+
329
+ if (enhancements?.transform) {
330
+ submitData = enhancements.transform(submitData as T) as Record<string, unknown>
331
+ }
332
+
333
+ if (enhancements?.onSubmit) {
334
+ form.isSubmitting.value = true
335
+ try {
336
+ await enhancements.onSubmit(submitData as T)
337
+ } finally {
338
+ form.isSubmitting.value = false
339
+ }
340
+ }
341
+ }
342
+
343
+ // -- Reset -----------------------------------------------------------------
344
+ function reset(values?: Partial<T>): void {
345
+ form.reset(values)
346
+ currentStep.value = 0
347
+ }
348
+
349
+ // -- onFieldChange wiring --------------------------------------------------
350
+ if (enhancements?.onFieldChange) {
351
+ const callback = enhancements.onFieldChange
352
+ watch(
353
+ () => ({ ...(form.data as Record<string, unknown>) }),
354
+ (newData, oldData) => {
355
+ if (!oldData) return
356
+ for (const key of Object.keys(newData)) {
357
+ if (newData[key] !== oldData[key]) {
358
+ callback(key as keyof T, newData[key], newData as T)
359
+ }
360
+ }
361
+ },
362
+ { deep: true },
363
+ )
364
+ }
365
+
366
+ return {
367
+ form: form as UseFormReturn<T>,
368
+ rules: rules as Partial<Record<keyof T, FieldRules>>,
369
+ isFieldVisible,
370
+ isSectionVisible,
371
+ fields,
372
+ getResolvedFieldProps,
373
+ getFieldOptions,
374
+ currentStep,
375
+ isCurrentStepValid,
376
+ goNext,
377
+ goBack,
378
+ goToStep,
379
+ validate,
380
+ reset,
381
+ submit,
382
+ }
383
+ }
@@ -0,0 +1,216 @@
1
+ import axios from 'axios'
2
+ import { useAuthStore } from '../stores/auth'
3
+ import { useSettingsStore } from '../stores/settings'
4
+ import type { CredentialInfo } from '../types'
5
+
6
+ // Lazy-load @simplewebauthn/browser so plugins that never call usePasskey
7
+ // don't need the optional peer dep installed (avoids build failures).
8
+ async function loadWebAuthn() {
9
+ try {
10
+ return await import('@simplewebauthn/browser')
11
+ } catch {
12
+ throw new Error(
13
+ '@simplewebauthn/browser is required for passkey support. Install it: bun add @simplewebauthn/browser',
14
+ )
15
+ }
16
+ }
17
+
18
+ interface PasskeyLoginResponse {
19
+ access_token: string
20
+ token_type: string
21
+ expires_in: number
22
+ }
23
+
24
+ /** Registers and authenticates passkeys using WebAuthn, lazily loading the browser dependency. */
25
+ export function usePasskey() {
26
+ const authStore = useAuthStore()
27
+ const settingsStore = useSettingsStore()
28
+
29
+ function getApiBaseUrl(): string {
30
+ return settingsStore.getApiBaseUrl()
31
+ }
32
+
33
+ async function isSupported(): Promise<boolean> {
34
+ try {
35
+ const { browserSupportsWebAuthn } = await loadWebAuthn()
36
+ return browserSupportsWebAuthn()
37
+ } catch {
38
+ return false
39
+ }
40
+ }
41
+
42
+ async function registerPasskey(deviceName?: string): Promise<boolean> {
43
+ const webauthn = await loadWebAuthn()
44
+ if (!webauthn.browserSupportsWebAuthn()) {
45
+ authStore.setError('WebAuthn is not supported in this browser')
46
+ return false
47
+ }
48
+
49
+ authStore.setLoading(true)
50
+ authStore.setError(null)
51
+
52
+ try {
53
+ const optionsResponse = await axios.get<{ options: string }>(
54
+ `${getApiBaseUrl()}/auth/passkey/register/options`,
55
+ {
56
+ headers: { Authorization: `Bearer ${authStore.token}` },
57
+ withCredentials: true,
58
+ }
59
+ )
60
+
61
+ const options = JSON.parse(optionsResponse.data.options)
62
+
63
+ const credential = await webauthn.startRegistration(options)
64
+
65
+ await axios.post(
66
+ `${getApiBaseUrl()}/auth/passkey/register/verify`,
67
+ {
68
+ credential: JSON.stringify(credential),
69
+ device_name: deviceName,
70
+ },
71
+ {
72
+ headers: { Authorization: `Bearer ${authStore.token}` },
73
+ withCredentials: true,
74
+ }
75
+ )
76
+
77
+ authStore.setAuthConfig({
78
+ ...authStore.authConfig,
79
+ passkeyRegistered: true,
80
+ })
81
+
82
+ return true
83
+ } catch (error) {
84
+ if (axios.isAxiosError(error) && error.response) {
85
+ authStore.setError(error.response.data.detail || 'Passkey registration failed')
86
+ } else if (error instanceof Error) {
87
+ if (error.name === 'NotAllowedError') {
88
+ authStore.setError('Registration was cancelled or timed out')
89
+ } else if (error.name === 'InvalidStateError') {
90
+ authStore.setError('This authenticator is already registered')
91
+ } else {
92
+ authStore.setError(error.message)
93
+ }
94
+ } else {
95
+ authStore.setError('Passkey registration failed')
96
+ }
97
+ return false
98
+ } finally {
99
+ authStore.setLoading(false)
100
+ }
101
+ }
102
+
103
+ async function loginWithPasskey(): Promise<boolean> {
104
+ const webauthn = await loadWebAuthn()
105
+ if (!webauthn.browserSupportsWebAuthn()) {
106
+ authStore.setError('WebAuthn is not supported in this browser')
107
+ return false
108
+ }
109
+
110
+ authStore.setLoading(true)
111
+ authStore.setError(null)
112
+
113
+ try {
114
+ const optionsResponse = await axios.get<{ options: string }>(
115
+ `${getApiBaseUrl()}/auth/passkey/login/options`,
116
+ { withCredentials: true }
117
+ )
118
+
119
+ const options = JSON.parse(optionsResponse.data.options)
120
+
121
+ const credential = await webauthn.startAuthentication(options)
122
+
123
+ const response = await axios.post<PasskeyLoginResponse>(
124
+ `${getApiBaseUrl()}/auth/passkey/login/verify`,
125
+ { credential: JSON.stringify(credential) },
126
+ { withCredentials: true }
127
+ )
128
+
129
+ authStore.setToken(response.data.access_token, response.data.expires_in)
130
+
131
+ return true
132
+ } catch (error) {
133
+ if (axios.isAxiosError(error) && error.response) {
134
+ if (error.response.status === 404) {
135
+ authStore.setError('No passkeys registered. Please login with password first.')
136
+ } else {
137
+ authStore.setError(error.response.data.detail || 'Passkey login failed')
138
+ }
139
+ } else if (error instanceof Error) {
140
+ if (error.name === 'NotAllowedError') {
141
+ authStore.setError('Authentication was cancelled or timed out')
142
+ } else {
143
+ authStore.setError(error.message)
144
+ }
145
+ } else {
146
+ authStore.setError('Passkey login failed')
147
+ }
148
+ return false
149
+ } finally {
150
+ authStore.setLoading(false)
151
+ }
152
+ }
153
+
154
+ async function listCredentials(): Promise<CredentialInfo[]> {
155
+ try {
156
+ const response = await axios.get<{ credentials: CredentialInfo[] }>(
157
+ `${getApiBaseUrl()}/auth/passkey/credentials`,
158
+ {
159
+ headers: { Authorization: `Bearer ${authStore.token}` },
160
+ }
161
+ )
162
+ return response.data.credentials
163
+ } catch {
164
+ return []
165
+ }
166
+ }
167
+
168
+ async function deleteCredential(credentialId: string): Promise<boolean> {
169
+ try {
170
+ await axios.delete(
171
+ `${getApiBaseUrl()}/auth/passkey/credentials/${encodeURIComponent(credentialId)}`,
172
+ {
173
+ headers: { Authorization: `Bearer ${authStore.token}` },
174
+ }
175
+ )
176
+
177
+ const remaining = await listCredentials()
178
+ if (remaining.length === 0) {
179
+ authStore.setAuthConfig({
180
+ ...authStore.authConfig,
181
+ passkeyRegistered: false,
182
+ })
183
+ }
184
+
185
+ return true
186
+ } catch {
187
+ return false
188
+ }
189
+ }
190
+
191
+ async function deleteAllCredentials(): Promise<boolean> {
192
+ try {
193
+ await axios.delete(`${getApiBaseUrl()}/auth/passkey/credentials`, {
194
+ headers: { Authorization: `Bearer ${authStore.token}` },
195
+ })
196
+
197
+ authStore.setAuthConfig({
198
+ ...authStore.authConfig,
199
+ passkeyRegistered: false,
200
+ })
201
+
202
+ return true
203
+ } catch {
204
+ return false
205
+ }
206
+ }
207
+
208
+ return {
209
+ isSupported,
210
+ registerPasskey,
211
+ loginWithPasskey,
212
+ listCredentials,
213
+ deleteCredential,
214
+ deleteAllCredentials,
215
+ }
216
+ }