@morscherlab/mint-sdk 1.2.0-beta.1 → 1.2.0-beta.11

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 (408) hide show
  1. package/README.md +35 -5
  2. package/dist/{ExperimentPopover-tLwJ7XF4.js → ExperimentPopover-oL0fg_A0.js} +6 -4
  3. package/dist/ExperimentPopover-oL0fg_A0.js.map +1 -0
  4. package/dist/{ExperimentSelectorModal-DyPAUCLw.js → ExperimentSelectorModal-BeEOomw_.js} +5 -5
  5. package/dist/ExperimentSelectorModal-BeEOomw_.js.map +1 -0
  6. package/dist/{SettingsModal-BgKY93zh.js → SettingsModal-Dphh3RrI.js} +5 -8
  7. package/dist/SettingsModal-Dphh3RrI.js.map +1 -0
  8. package/dist/{EmptyState-p90quq8Z.js → Skeleton-DxtwJ5gt.js} +111 -93
  9. package/dist/Skeleton-DxtwJ5gt.js.map +1 -0
  10. package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
  11. package/dist/__tests__/components/PluginWorkspaceView.internal.test.d.ts +1 -0
  12. package/dist/__tests__/components/SecretInput.test.d.ts +1 -0
  13. package/dist/__tests__/components/WellEditPopupInternal.test.d.ts +1 -0
  14. package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
  15. package/dist/__tests__/composables/usePluginConfig.test.d.ts +15 -0
  16. package/dist/__tests__/styles/cssTokens.test.d.ts +1 -0
  17. package/dist/components/ActionMenu.vue.d.ts +1 -1
  18. package/dist/components/AppAvatarMenu.vue.d.ts +4 -0
  19. package/dist/components/AppPluginSwitcher.vue.d.ts +2 -0
  20. package/dist/components/AppSidebar.vue.d.ts +1 -1
  21. package/dist/components/AuditTrail.vue.d.ts +13 -0
  22. package/dist/components/AutoGroupModal.vue.d.ts +1 -1
  23. package/dist/components/BatchProgressList.vue.d.ts +17 -0
  24. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
  25. package/dist/components/DataFrame.vue.d.ts +14 -2
  26. package/dist/components/DatePicker.vue.d.ts +1 -1
  27. package/dist/components/DateTimePicker.vue.d.ts +2 -0
  28. package/dist/components/DropdownButton.vue.d.ts +2 -0
  29. package/dist/components/ExperimentDataViewer.vue.d.ts +26 -1
  30. package/dist/components/ExperimentPopover.vue.d.ts +1 -0
  31. package/dist/components/ExperimentTimeline.vue.d.ts +29 -1
  32. package/dist/components/FitPanel.vue.d.ts +1 -1
  33. package/dist/components/JobsStatusTray.vue.d.ts +9 -2
  34. package/dist/components/LcmsSequenceTable.vue.d.ts +29 -1
  35. package/dist/components/PluginWorkspaceView.props.d.ts +29 -1
  36. package/dist/components/PluginWorkspaceView.vue.d.ts +5 -5
  37. package/dist/components/ProgressBar.vue.d.ts +1 -1
  38. package/dist/components/ProtocolStepEditor.vue.d.ts +1 -0
  39. package/dist/components/ReagentList.vue.d.ts +29 -1
  40. package/dist/components/SampleHierarchyTree.vue.d.ts +29 -1
  41. package/dist/components/SampleLegend.vue.d.ts +1 -1
  42. package/dist/components/SampleSelector.drag.d.ts +13 -0
  43. package/dist/components/SampleSelector.tree.d.ts +2 -0
  44. package/dist/components/SampleSelector.vue.d.ts +35 -2
  45. package/dist/components/ScheduleCalendar.vue.d.ts +20 -0
  46. package/dist/components/SecretInput.vue.d.ts +28 -0
  47. package/dist/components/SettingsModal.vue.d.ts +1 -1
  48. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -1
  49. package/dist/components/TimePicker.vue.d.ts +1 -0
  50. package/dist/components/WellPlate.geometry.d.ts +15 -0
  51. package/dist/components/WellPlate.interaction.d.ts +13 -0
  52. package/dist/components/WellPlate.vue.d.ts +29 -0
  53. package/dist/components/index.d.ts +9 -0
  54. package/dist/components/index.js +8 -8
  55. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -0
  56. package/dist/components/internal/DataStateInternal.vue.d.ts +43 -0
  57. package/dist/components/internal/WellEditPopupInternal.vue.d.ts +3 -1
  58. package/dist/components/internal/WorkspaceControlsInternal.vue.d.ts +23 -0
  59. package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +70 -0
  60. package/dist/components/internal/WorkspaceSidebarInternal.vue.d.ts +43 -0
  61. package/dist/{components-DkHddRMl.js → components-CgAUzF8F.js} +2497 -2235
  62. package/dist/components-CgAUzF8F.js.map +1 -0
  63. package/dist/composables/controlSchemaTypes.d.ts +34 -0
  64. package/dist/composables/flattenWorkspaceBindings.d.ts +61 -0
  65. package/dist/composables/index.d.ts +9 -2
  66. package/dist/composables/index.js +10 -10
  67. package/dist/composables/pluginWorkspaceModel.d.ts +164 -0
  68. package/dist/composables/useAsync.d.ts +5 -1
  69. package/dist/composables/useBioTemplateWorkspace.d.ts +4 -20
  70. package/dist/composables/useCommandHistory.d.ts +5 -1
  71. package/dist/composables/useDropdownState.d.ts +18 -2
  72. package/dist/composables/useEventStream.d.ts +9 -0
  73. package/dist/composables/useFocusTrap.d.ts +21 -0
  74. package/dist/composables/useGeneratedAnalysis.d.ts +3 -1
  75. package/dist/composables/useJobsStatusTray.d.ts +25 -1
  76. package/dist/composables/useListReorder.d.ts +90 -0
  77. package/dist/composables/useMenuKeyboard.d.ts +29 -0
  78. package/dist/composables/useOptimisticMutation.d.ts +5 -1
  79. package/dist/composables/usePluginClient.d.ts +2 -0
  80. package/dist/composables/usePluginConfig.d.ts +27 -0
  81. package/dist/composables/usePluginWorkspace.d.ts +51 -0
  82. package/dist/composables/useRequestSyncState.d.ts +42 -2
  83. package/dist/composables/useResourceCrud.d.ts +5 -1
  84. package/dist/composables/useRovingFocus.d.ts +24 -0
  85. package/dist/composables/useScheduleDrag.d.ts +1 -0
  86. package/dist/composables/useWellPainting.d.ts +5 -1
  87. package/dist/composables/useWellPlateAdapter.d.ts +5 -1
  88. package/dist/composables/useWellPlateValidation.d.ts +5 -1
  89. package/dist/composables/workspaceRecipes.d.ts +188 -0
  90. package/dist/{composables-C_i90sGS.js → composables-B_pia3hO.js} +209 -82
  91. package/dist/composables-B_pia3hO.js.map +1 -0
  92. package/dist/{useApi-J6zFkHmn.js → errors-_W0-LbA6.js} +103 -3
  93. package/dist/errors-_W0-LbA6.js.map +1 -0
  94. package/dist/{experiment-Cq145oEW.js → experiment-Cwl9AYxw.js} +3 -8
  95. package/dist/experiment-Cwl9AYxw.js.map +1 -0
  96. package/dist/{experiment-utils-CLnzPPq5.js → experiment-utils-DV3DrvE_.js} +70 -51
  97. package/dist/experiment-utils-DV3DrvE_.js.map +1 -0
  98. package/dist/index.d.ts +1 -0
  99. package/dist/index.js +15 -15
  100. package/dist/install.js +2 -7
  101. package/dist/install.js.map +1 -1
  102. package/dist/permissions.d.ts +42 -1
  103. package/dist/permissions.js +20 -10
  104. package/dist/permissions.js.map +1 -1
  105. package/dist/stores/auth.d.ts +6 -0
  106. package/dist/stores/index.d.ts +1 -1
  107. package/dist/stores/index.js +3 -3
  108. package/dist/stores/settings.d.ts +31 -1
  109. package/dist/styles.css +11247 -29501
  110. package/dist/templates/builderReadUtils.d.ts +1 -1
  111. package/dist/templates/index.js +2 -4
  112. package/dist/{templates-DsP51i-S.js → templates-Qq113pyZ.js} +303 -106
  113. package/dist/templates-Qq113pyZ.js.map +1 -0
  114. package/dist/types/auth.d.ts +1 -0
  115. package/dist/types/form-builder.d.ts +1 -1
  116. package/dist/{useControlSchema-BDx2Olha.js → useControlSchema-aMZlb4Sc.js} +462 -1130
  117. package/dist/useControlSchema-aMZlb4Sc.js.map +1 -0
  118. package/dist/{useDropdownState-DSb3BC3N.js → useDropdownState-DitCtrTT.js} +22 -6
  119. package/dist/useDropdownState-DitCtrTT.js.map +1 -0
  120. package/dist/{useExperimentSelector-CV4iNfMS.js → useExperimentSelector-I8HC8hUu.js} +4 -4
  121. package/dist/{useExperimentSelector-CV4iNfMS.js.map → useExperimentSelector-I8HC8hUu.js.map} +1 -1
  122. package/dist/{useFormBuilder-BLxG3BOX.js → useFormBuilder-TCxNnqeb.js} +2 -2
  123. package/dist/{useFormBuilder-BLxG3BOX.js.map → useFormBuilder-TCxNnqeb.js.map} +1 -1
  124. package/dist/{useProtocolTemplates-CS_SQl6U.js → useProtocolTemplates-BmX_iVBR.js} +766 -212
  125. package/dist/useProtocolTemplates-BmX_iVBR.js.map +1 -0
  126. package/dist/utils/color.d.ts +13 -0
  127. package/dist/utils/errors.d.ts +9 -0
  128. package/dist/utils/pluginSecrets.d.ts +53 -0
  129. package/dist/utils/records.d.ts +4 -0
  130. package/package.json +1 -1
  131. package/src/__tests__/components/ActionMenu.test.ts +94 -0
  132. package/src/__tests__/components/AppAvatarMenu.test.ts +123 -0
  133. package/src/__tests__/components/BaseTabs.test.ts +67 -0
  134. package/src/__tests__/components/DataFrame.test.ts +183 -11
  135. package/src/__tests__/components/DataStates.test.ts +176 -0
  136. package/src/__tests__/components/DatePicker.test.ts +76 -0
  137. package/src/__tests__/components/DateTimePicker.test.ts +36 -0
  138. package/src/__tests__/components/DropdownButton.test.ts +134 -0
  139. package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
  140. package/src/__tests__/components/GroupAssigner.test.ts +53 -0
  141. package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
  142. package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +230 -0
  143. package/src/__tests__/components/RackEditor.test.ts +35 -0
  144. package/src/__tests__/components/ReagentList.test.ts +64 -2
  145. package/src/__tests__/components/RetiredStyles.test.ts +31 -0
  146. package/src/__tests__/components/SampleSelector.drag.test.ts +51 -1
  147. package/src/__tests__/components/ScheduleCalendar.test.ts +63 -0
  148. package/src/__tests__/components/SecretInput.test.ts +87 -0
  149. package/src/__tests__/components/SegmentedControl.test.ts +88 -0
  150. package/src/__tests__/components/TimePicker.test.ts +61 -0
  151. package/src/__tests__/components/WellEditPopupInternal.test.ts +108 -0
  152. package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
  153. package/src/__tests__/composables/useAuth.test.ts +2 -0
  154. package/src/__tests__/composables/useDropdownState.test.ts +58 -1
  155. package/src/__tests__/composables/useListReorder.test.ts +145 -0
  156. package/src/__tests__/composables/usePluginClient.test.ts +140 -0
  157. package/src/__tests__/composables/usePluginConfig.test.ts +497 -0
  158. package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
  159. package/src/__tests__/docs/frontendDocsCatalog.test.ts +39 -7
  160. package/src/__tests__/stores/settings.test.ts +71 -1
  161. package/src/__tests__/styles/cssTokens.test.ts +131 -0
  162. package/src/__tests__/utils/permissions.test.ts +68 -0
  163. package/src/components/ActionMenu.vue +45 -88
  164. package/src/components/AlertBox.vue +0 -4
  165. package/src/components/AppAvatarMenu.vue +26 -7
  166. package/src/components/AppLayout.vue +0 -4
  167. package/src/components/AppPluginSwitcher.story.vue +87 -0
  168. package/src/components/AppPluginSwitcher.vue +2 -5
  169. package/src/components/AppSidebar.vue +0 -4
  170. package/src/components/AppToastContainer.vue +6 -8
  171. package/src/components/AppTopBar.vue +0 -4
  172. package/src/components/ArtifactSaveDialog.vue +0 -4
  173. package/src/components/ArtifactSelectorModal.vue +0 -4
  174. package/src/components/AuditTrail.story.vue +18 -0
  175. package/src/components/AuditTrail.vue +27 -7
  176. package/src/components/AutoGroupModal.vue +0 -4
  177. package/src/components/Avatar.vue +0 -4
  178. package/src/components/BaseButton.vue +0 -4
  179. package/src/components/BaseCheckbox.vue +0 -4
  180. package/src/components/BaseInput.vue +0 -4
  181. package/src/components/BaseModal.vue +8 -60
  182. package/src/components/BasePill.vue +0 -4
  183. package/src/components/BaseRadioGroup.vue +0 -4
  184. package/src/components/BaseSelect.vue +0 -4
  185. package/src/components/BaseSlider.vue +0 -4
  186. package/src/components/BaseTabs.vue +26 -5
  187. package/src/components/BaseTextarea.vue +0 -4
  188. package/src/components/BaseToggle.vue +0 -4
  189. package/src/components/BatchProgressList.story.vue +18 -0
  190. package/src/components/BatchProgressList.vue +32 -4
  191. package/src/components/BioTemplateExperimentWorkspaceView.vue +10 -21
  192. package/src/components/BioTemplatePackWorkspaceView.vue +11 -15
  193. package/src/components/BioTemplatePresetWorkspaceView.vue +8 -13
  194. package/src/components/Breadcrumb.vue +0 -4
  195. package/src/components/Calendar.vue +0 -4
  196. package/src/components/ChartContainer.story.vue +1 -1
  197. package/src/components/ChartContainer.vue +0 -4
  198. package/src/components/ChemicalFormula.vue +0 -4
  199. package/src/components/CollapsibleCard.vue +0 -4
  200. package/src/components/ColorSlider.vue +0 -4
  201. package/src/components/ComponentBindingRenderer.vue +1 -3
  202. package/src/components/ConcentrationInput.vue +0 -4
  203. package/src/components/ConfirmDialog.vue +0 -4
  204. package/src/components/ControlWorkspaceView.vue +11 -22
  205. package/src/components/DataFrame.story.vue +14 -0
  206. package/src/components/DataFrame.vue +97 -23
  207. package/src/components/DatePicker.vue +73 -27
  208. package/src/components/DateTimePicker.vue +45 -17
  209. package/src/components/Divider.vue +0 -4
  210. package/src/components/DoseCalculator.vue +0 -4
  211. package/src/components/DoseDesignWorkspaceView.vue +6 -25
  212. package/src/components/DropdownButton.vue +24 -6
  213. package/src/components/EmptyState.vue +0 -4
  214. package/src/components/ExperimentCodeBadge.vue +0 -4
  215. package/src/components/ExperimentDataViewer.story.vue +3 -0
  216. package/src/components/ExperimentDataViewer.vue +33 -13
  217. package/src/components/ExperimentPopover.vue +2 -5
  218. package/src/components/ExperimentSelectorModal.vue +0 -4
  219. package/src/components/ExperimentTimeline.story.vue +18 -0
  220. package/src/components/ExperimentTimeline.vue +81 -75
  221. package/src/components/FileUploader.vue +0 -4
  222. package/src/components/FitPanel.vue +0 -4
  223. package/src/components/FormActions.story.vue +96 -0
  224. package/src/components/FormActions.vue +0 -4
  225. package/src/components/FormBuilder.vue +0 -4
  226. package/src/components/FormField.vue +0 -4
  227. package/src/components/FormulaInput.vue +0 -4
  228. package/src/components/GeneratedPathInput.story.vue +68 -0
  229. package/src/components/GeneratedPlotlyResult.story.vue +96 -0
  230. package/src/components/GeneratedPlotlyResult.vue +3 -3
  231. package/src/components/GeneratedPluginView.story.vue +145 -0
  232. package/src/components/GeneratedPluginView.vue +10 -10
  233. package/src/components/GeneratedResultRenderer.story.vue +117 -0
  234. package/src/components/GeneratedResultRenderer.vue +4 -4
  235. package/src/components/GroupAssigner.vue +67 -5
  236. package/src/components/IconButton.vue +0 -4
  237. package/src/components/InstrumentAlertLog.vue +0 -4
  238. package/src/components/InstrumentStateBadge.vue +0 -4
  239. package/src/components/InstrumentStatusCard.vue +0 -4
  240. package/src/components/JobsStatusTray.vue +16 -5
  241. package/src/components/LcmsSequenceTable.vue +81 -50
  242. package/src/components/LoadingSpinner.vue +0 -4
  243. package/src/components/MobileSupportGate.vue +0 -4
  244. package/src/components/MoleculeInput.vue +0 -4
  245. package/src/components/MultiSelect.vue +0 -4
  246. package/src/components/NumberInput.vue +0 -4
  247. package/src/components/PlateMapEditor.vue +3 -14
  248. package/src/components/PluginIcon.vue +0 -4
  249. package/src/components/PluginWorkspaceView.props.ts +38 -0
  250. package/src/components/PluginWorkspaceView.vue +53 -224
  251. package/src/components/ProgressBar.vue +0 -4
  252. package/src/components/ProtocolStepEditor.vue +2 -4
  253. package/src/components/RackEditor.vue +51 -70
  254. package/src/components/ReagentEditor.vue +0 -4
  255. package/src/components/ReagentList.story.vue +18 -0
  256. package/src/components/ReagentList.vue +127 -82
  257. package/src/components/ResourceCard.vue +0 -4
  258. package/src/components/RuntimeUpdateNotice.story.vue +49 -0
  259. package/src/components/RuntimeUpdateNotice.vue +4 -4
  260. package/src/components/SampleHierarchyTree.story.vue +18 -0
  261. package/src/components/SampleHierarchyTree.vue +32 -9
  262. package/src/components/SampleLegend.vue +2 -8
  263. package/src/components/SampleSelector.drag.ts +51 -0
  264. package/src/components/SampleSelector.story.vue +6 -0
  265. package/src/components/SampleSelector.tree.ts +2 -0
  266. package/src/components/SampleSelector.vue +49 -10
  267. package/src/components/SampleSelectorGroupNode.vue +19 -0
  268. package/src/components/ScheduleCalendar.story.vue +6 -0
  269. package/src/components/ScheduleCalendar.vue +82 -7
  270. package/src/components/ScientificNumber.vue +0 -4
  271. package/src/components/SecretInput.story.vue +79 -0
  272. package/src/components/SecretInput.vue +150 -0
  273. package/src/components/SectionCard.vue +0 -4
  274. package/src/components/SegmentedControl.vue +48 -5
  275. package/src/components/SequenceInput.vue +0 -4
  276. package/src/components/SequenceProgressBar.story.vue +110 -0
  277. package/src/components/SequenceProgressBar.vue +0 -4
  278. package/src/components/SettingsModal.vue +0 -4
  279. package/src/components/Skeleton.vue +0 -4
  280. package/src/components/SmartGroupFieldRecipe.vue +8 -6
  281. package/src/components/SmartGroupManual.vue +0 -4
  282. package/src/components/StatusIndicator.vue +0 -4
  283. package/src/components/StepWizard.vue +0 -4
  284. package/src/components/TagsInput.vue +4 -6
  285. package/src/components/ThemeToggle.vue +0 -4
  286. package/src/components/TimePicker.vue +36 -7
  287. package/src/components/TimeRangeInput.vue +0 -4
  288. package/src/components/Tooltip.story.vue +3 -3
  289. package/src/components/Tooltip.vue +0 -4
  290. package/src/components/UnitInput.vue +0 -4
  291. package/src/components/WellPlate.geometry.ts +60 -1
  292. package/src/components/WellPlate.interaction.ts +44 -0
  293. package/src/components/WellPlate.story.vue +8 -0
  294. package/src/components/WellPlate.vue +53 -7
  295. package/src/components/index.ts +9 -0
  296. package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -5
  297. package/src/components/internal/AppTopBarPillNavInternal.vue +0 -4
  298. package/src/components/internal/DataStateInternal.vue +74 -0
  299. package/src/components/internal/FormSectionRenderer.vue +0 -4
  300. package/src/components/internal/WellEditPopupInternal.vue +20 -5
  301. package/src/components/internal/WorkspaceControlsInternal.vue +20 -0
  302. package/src/components/internal/WorkspaceShellInternal.vue +125 -0
  303. package/src/components/internal/WorkspaceSidebarInternal.vue +82 -0
  304. package/src/composables/controlSchemaTypes.ts +34 -0
  305. package/src/composables/experimentDesignData.ts +1 -3
  306. package/src/composables/flattenWorkspaceBindings.ts +104 -0
  307. package/src/composables/formBuilderRegistry.ts +5 -0
  308. package/src/composables/index.ts +43 -0
  309. package/src/composables/pluginWorkspaceModel.ts +182 -0
  310. package/src/composables/useAsync.ts +5 -1
  311. package/src/composables/useAuth.ts +3 -0
  312. package/src/composables/useBioTemplatePackWorkspace.ts +15 -9
  313. package/src/composables/useBioTemplatePresetWorkspace.ts +14 -15
  314. package/src/composables/useBioTemplateWorkspace.ts +18 -43
  315. package/src/composables/useCommandHistory.ts +5 -1
  316. package/src/composables/useDropdownState.ts +35 -5
  317. package/src/composables/useEventStream.ts +14 -2
  318. package/src/composables/useFileImport.ts +2 -7
  319. package/src/composables/useFocusTrap.ts +91 -0
  320. package/src/composables/useGeneratedAnalysis.ts +22 -10
  321. package/src/composables/useJobsStatusTray.ts +184 -17
  322. package/src/composables/useListReorder.story.vue +143 -0
  323. package/src/composables/useListReorder.ts +242 -0
  324. package/src/composables/useMenuKeyboard.ts +90 -0
  325. package/src/composables/useOptimisticMutation.ts +6 -16
  326. package/src/composables/usePluginClient.ts +47 -4
  327. package/src/composables/usePluginConfig.ts +252 -0
  328. package/src/composables/usePluginWorkspace.ts +208 -0
  329. package/src/composables/useRackEditor.ts +2 -11
  330. package/src/composables/useRequestSyncState.ts +98 -28
  331. package/src/composables/useResourceCrud.ts +6 -16
  332. package/src/composables/useRovingFocus.ts +66 -0
  333. package/src/composables/useScheduleDrag.ts +37 -0
  334. package/src/composables/useWellPainting.ts +10 -30
  335. package/src/composables/useWellPlateAdapter.ts +5 -1
  336. package/src/composables/useWellPlateEditor.ts +1 -11
  337. package/src/composables/useWellPlateValidation.ts +5 -1
  338. package/src/composables/workspaceRecipes.ts +267 -0
  339. package/src/index.ts +16 -0
  340. package/src/permissions.ts +48 -9
  341. package/src/stores/auth.ts +1 -0
  342. package/src/stores/experiment.ts +2 -7
  343. package/src/stores/index.ts +9 -1
  344. package/src/stores/settings.ts +93 -1
  345. package/src/styles/components/audit-trail.css +5 -5
  346. package/src/styles/components/batch-progress-list.css +7 -0
  347. package/src/styles/components/color-slider.css +2 -2
  348. package/src/styles/components/data-state.css +30 -0
  349. package/src/styles/components/dataframe.css +29 -35
  350. package/src/styles/components/experiment-data-viewer.css +4 -4
  351. package/src/styles/components/experiment-timeline.css +3 -30
  352. package/src/styles/components/form-builder.css +5 -5
  353. package/src/styles/components/lcms-sequence-table.css +16 -1
  354. package/src/styles/components/multi-select.css +1 -1
  355. package/src/styles/components/reagent-list.css +38 -31
  356. package/src/styles/components/sample-hierarchy-tree.css +5 -19
  357. package/src/styles/components/sample-selector.css +9 -2
  358. package/src/styles/components/schedule-calendar.css +8 -0
  359. package/src/styles/components/scientific-number.css +1 -1
  360. package/src/styles/components/secret-input.css +60 -0
  361. package/src/styles/components/well-plate.css +8 -0
  362. package/src/styles/index.css +9 -0
  363. package/src/styles/variables.css +29 -0
  364. package/src/templates/builderPresetControls.ts +1 -1
  365. package/src/templates/builderReadUtils.ts +1 -3
  366. package/src/templates/componentPlateHelpers.ts +3 -1
  367. package/src/templates/componentTargetResolvers.ts +1 -3
  368. package/src/templates/controlSchemaTargets.ts +1 -3
  369. package/src/templates/doseResponseAdapters.ts +2 -13
  370. package/src/templates/elisaAssayCollectionBuilder.ts +2 -2
  371. package/src/templates/plateMapBuilder.ts +2 -2
  372. package/src/templates/qpcrPlateBuilder.ts +1 -1
  373. package/src/templates/templateValidators.ts +1 -1
  374. package/src/templates/timeCourseBuilder.ts +2 -2
  375. package/src/templates/wellPlateScreenCollectionBuilder.ts +2 -2
  376. package/src/types/auth.ts +1 -0
  377. package/src/types/form-builder.ts +1 -0
  378. package/src/utils/color.ts +51 -0
  379. package/src/utils/errors.ts +29 -0
  380. package/src/utils/formModelSync.ts +1 -3
  381. package/src/utils/pluginIcon.ts +1 -3
  382. package/src/utils/pluginSecrets.ts +102 -0
  383. package/src/utils/records.ts +9 -0
  384. package/dist/EmptyState-p90quq8Z.js.map +0 -1
  385. package/dist/ExperimentPopover-tLwJ7XF4.js.map +0 -1
  386. package/dist/ExperimentSelectorModal-DyPAUCLw.js.map +0 -1
  387. package/dist/SettingsModal-BgKY93zh.js.map +0 -1
  388. package/dist/components-DkHddRMl.js.map +0 -1
  389. package/dist/composables/autoGroupConstants.d.ts +0 -1
  390. package/dist/composables/autoGroupGrouping.d.ts +0 -3
  391. package/dist/composables/useAutoGroupInputSources.d.ts +0 -32
  392. package/dist/composables-C_i90sGS.js.map +0 -1
  393. package/dist/experiment-Cq145oEW.js.map +0 -1
  394. package/dist/experiment-utils-CLnzPPq5.js.map +0 -1
  395. package/dist/templates/builderDefaults.d.ts +0 -1
  396. package/dist/templates/plateGeometry.d.ts +0 -4
  397. package/dist/templates-DsP51i-S.js.map +0 -1
  398. package/dist/useApi-J6zFkHmn.js.map +0 -1
  399. package/dist/useControlSchema-BDx2Olha.js.map +0 -1
  400. package/dist/useDropdownState-DSb3BC3N.js.map +0 -1
  401. package/dist/useProtocolTemplates-CS_SQl6U.js.map +0 -1
  402. package/src/__tests__/composables/useAutoGroupInputSources.test.ts +0 -127
  403. package/src/composables/autoGroupConstants.ts +0 -4
  404. package/src/composables/autoGroupGrouping.ts +0 -148
  405. package/src/composables/useAutoGroupInputSources.ts +0 -147
  406. package/src/templates/builderDefaults.ts +0 -11
  407. package/src/templates/plateGeometry.ts +0 -62
  408. /package/dist/__tests__/{composables/useAutoGroupInputSources.test.d.ts → components/DataStates.test.d.ts} +0 -0
@@ -5,6 +5,14 @@ import type { DataFrameColumn, SortState, PaginationState } from '../types'
5
5
  import { useSortedItems } from '../composables/useSortedItems'
6
6
  import { useTextSearch } from '../composables/useTextSearch'
7
7
  import { useListSelection } from '../composables/useListSelection'
8
+ import DataStateInternal from './internal/DataStateInternal.vue'
9
+ import LoadingSpinner from './LoadingSpinner.vue'
10
+ import { tableDensityToSize, tryUseSettingsStore } from '../stores/settings'
11
+
12
+ /** Inbox glyph kept from the pre-1.2 hand-rolled empty state. */
13
+ const EMPTY_ICON_PATH
14
+ = 'M22 12L16 12L14 15L10 15L8 12L2 12'
15
+ + 'M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z'
8
16
 
9
17
  /**
10
18
  * DataFrame - Data table with sorting, searching, pagination, and row selection.
@@ -34,7 +42,11 @@ interface Props {
34
42
  striped?: boolean
35
43
  /** Show table borders */
36
44
  bordered?: boolean
37
- /** Table size */
45
+ /**
46
+ * Table size. Left unset, it follows the platform's `tableDensity` setting
47
+ * (compact → `sm`, normal → `md`, comfortable → `lg`) and falls back to
48
+ * `md` when no settings store is installed.
49
+ */
38
50
  size?: 'sm' | 'md' | 'lg'
39
51
  /** Make header sticky on scroll */
40
52
  stickyHeader?: boolean
@@ -42,6 +54,11 @@ interface Props {
42
54
  maxHeight?: string | number
43
55
  /** Show loading overlay */
44
56
  loading?: boolean
57
+ /**
58
+ * Error message. When set, the table body is replaced by an error state
59
+ * instead of the empty state. `null`/`undefined` renders normally.
60
+ */
61
+ error?: string | null
45
62
  /** Empty state message */
46
63
  emptyText?: string
47
64
  /** Enable column sorting */
@@ -75,9 +92,9 @@ interface Props {
75
92
  const props = withDefaults(defineProps<Props>(), {
76
93
  striped: true,
77
94
  bordered: true,
78
- size: 'md',
79
95
  stickyHeader: false,
80
96
  loading: false,
97
+ error: null,
81
98
  emptyText: 'No data',
82
99
  sortable: false,
83
100
  pagination: false,
@@ -108,6 +125,13 @@ const emit = defineEmits<{
108
125
  'delete-row': [row: Record<string, unknown>, index: number]
109
126
  }>()
110
127
 
128
+ const settings = tryUseSettingsStore()
129
+
130
+ /** Explicit `size` wins; otherwise the user's table density, else `md`. */
131
+ const size = computed<'sm' | 'md' | 'lg'>(() =>
132
+ props.size ?? (settings ? tableDensityToSize(settings.tableDensity) : 'md'),
133
+ )
134
+
111
135
  const searchQuery = ref('')
112
136
  const internalSort = ref<SortState | null>(null)
113
137
 
@@ -219,6 +243,21 @@ function isColumnSortable(col: DataFrameColumn): boolean {
219
243
  return col.sortable === true || (props.sortable && col.sortable !== false)
220
244
  }
221
245
 
246
+ /** `aria-sort` for the header cell, omitted on columns that cannot be sorted. */
247
+ function columnAriaSort(col: DataFrameColumn): 'ascending' | 'descending' | 'none' | undefined {
248
+ if (!isColumnSortable(col)) return undefined
249
+ if (activeSort.value?.key !== col.key || !activeSort.value.direction) return 'none'
250
+ return activeSort.value.direction === 'asc' ? 'ascending' : 'descending'
251
+ }
252
+
253
+ // Native buttons activate on Enter/Space, but the default is prevented here so
254
+ // the header sorts exactly once per key press in every engine.
255
+ function handleSortKeydown(event: KeyboardEvent, col: DataFrameColumn) {
256
+ if (event.key !== 'Enter' && event.key !== ' ') return
257
+ event.preventDefault()
258
+ handleSort(col)
259
+ }
260
+
222
261
  // Selection
223
262
  const pageRowKeys = computed(() =>
224
263
  paginatedData.value.map((row, i) => getRowKey(row, i))
@@ -264,6 +303,35 @@ function columnStyle(col: DataFrameColumn) {
264
303
  }
265
304
  }
266
305
 
306
+ const RESIZE_KEY_STEP = 16
307
+
308
+ function columnMinWidth(col: DataFrameColumn): number {
309
+ return Math.max(MIN_COL_WIDTH, typeof col.minWidth === 'number' ? col.minWidth : 0)
310
+ }
311
+
312
+ /** Current px width of a column when one is known (resized, or a numeric `width`). */
313
+ function columnWidthValue(col: DataFrameColumn): number | undefined {
314
+ const width = activeWidths.value[col.key] ?? col.width
315
+ return typeof width === 'number' ? width : undefined
316
+ }
317
+
318
+ function applyColumnWidth(key: string, width: number) {
319
+ const widths = { ...activeWidths.value, [key]: width }
320
+ if (props.columnWidths === undefined) internalWidths.value = widths
321
+ emit('update:columnWidths', widths)
322
+ }
323
+
324
+ /** Left/Right resize the column by a fixed step; the handle is a `role="slider"`. */
325
+ function handleResizeKeydown(event: KeyboardEvent, col: DataFrameColumn) {
326
+ if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
327
+ event.preventDefault()
328
+ const th = (event.target as HTMLElement).closest('th')
329
+ const base =
330
+ columnWidthValue(col) ?? (th instanceof HTMLElement ? th.offsetWidth : MIN_COL_WIDTH)
331
+ const delta = event.key === 'ArrowRight' ? RESIZE_KEY_STEP : -RESIZE_KEY_STEP
332
+ applyColumnWidth(col.key, Math.max(columnMinWidth(col), base + delta))
333
+ }
334
+
267
335
  let resizeState: { key: string; startX: number; startWidth: number; min: number } | null = null
268
336
 
269
337
  function startResize(col: DataFrameColumn, event: MouseEvent) {
@@ -272,8 +340,7 @@ function startResize(col: DataFrameColumn, event: MouseEvent) {
272
340
  const th = (event.target as HTMLElement).closest('th')
273
341
  const startWidth =
274
342
  th instanceof HTMLElement ? th.offsetWidth : (typeof col.width === 'number' ? col.width : MIN_COL_WIDTH)
275
- const min = Math.max(MIN_COL_WIDTH, typeof col.minWidth === 'number' ? col.minWidth : 0)
276
- resizeState = { key: col.key, startX: event.clientX, startWidth, min }
343
+ resizeState = { key: col.key, startX: event.clientX, startWidth, min: columnMinWidth(col) }
277
344
  window.addEventListener('mousemove', onResizeMove)
278
345
  window.addEventListener('mouseup', stopResize)
279
346
  }
@@ -281,9 +348,7 @@ function startResize(col: DataFrameColumn, event: MouseEvent) {
281
348
  function onResizeMove(event: MouseEvent) {
282
349
  if (!resizeState) return
283
350
  const next = Math.max(resizeState.min, resizeState.startWidth + (event.clientX - resizeState.startX))
284
- const widths = { ...activeWidths.value, [resizeState.key]: next }
285
- if (props.columnWidths === undefined) internalWidths.value = widths
286
- emit('update:columnWidths', widths)
351
+ applyColumnWidth(resizeState.key, next)
287
352
  }
288
353
 
289
354
  function stopResize() {
@@ -355,8 +420,15 @@ const wrapperStyle = computed(() => {
355
420
  },
356
421
  ]"
357
422
  :style="columnStyle(col)"
358
- @click="isColumnSortable(col) ? handleSort(col) : undefined"
423
+ :aria-sort="columnAriaSort(col)"
359
424
  >
425
+ <component
426
+ :is="isColumnSortable(col) ? 'button' : 'div'"
427
+ :type="isColumnSortable(col) ? 'button' : undefined"
428
+ class="mint-dataframe__th-button"
429
+ @click="isColumnSortable(col) ? handleSort(col) : undefined"
430
+ @keydown="isColumnSortable(col) ? handleSortKeydown($event, col) : undefined"
431
+ >
360
432
  <slot :name="`header-${col.key}`" :column="col">
361
433
  <div class="mint-dataframe__th-content">
362
434
  <span>{{ col.label }}</span>
@@ -380,13 +452,18 @@ const wrapperStyle = computed(() => {
380
452
  </svg>
381
453
  </div>
382
454
  </slot>
455
+ </component>
383
456
  <span
384
457
  v-if="resizable"
385
458
  class="mint-dataframe__resize-handle"
386
- role="separator"
459
+ role="slider"
460
+ tabindex="0"
387
461
  aria-orientation="vertical"
388
- aria-label="Resize column"
462
+ :aria-label="`Resize ${col.label} column`"
463
+ :aria-valuemin="columnMinWidth(col)"
464
+ :aria-valuenow="columnWidthValue(col)"
389
465
  @mousedown="startResize(col, $event)"
466
+ @keydown="handleResizeKeydown($event, col)"
390
467
  @click.stop
391
468
  />
392
469
  </th>
@@ -459,23 +536,24 @@ const wrapperStyle = computed(() => {
459
536
  </tbody>
460
537
  </table>
461
538
 
539
+ <!-- Error state -->
540
+ <div v-if="error" class="mint-dataframe__state">
541
+ <slot name="error" :error="error">
542
+ <DataStateInternal state="error" :title="error" />
543
+ </slot>
544
+ </div>
545
+
462
546
  <!-- Empty state -->
463
- <div v-if="paginatedData.length === 0 && !loading" class="mint-dataframe__empty">
547
+ <div v-else-if="paginatedData.length === 0 && !loading" class="mint-dataframe__state">
464
548
  <slot name="empty">
465
- <svg class="mint-dataframe__empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
466
- <polyline points="22 12 16 12 14 15 10 15 8 12 2 12" /><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
467
- </svg>
468
- <p class="mint-dataframe__empty-text">{{ emptyText }}</p>
549
+ <DataStateInternal state="empty" :title="emptyText" :icon-path="EMPTY_ICON_PATH" />
469
550
  </slot>
470
551
  </div>
471
552
 
472
553
  <!-- Loading overlay -->
473
554
  <div v-if="loading" class="mint-dataframe__loading">
474
555
  <slot name="loading">
475
- <svg class="mint-dataframe__loading-spinner" viewBox="0 0 24 24" fill="none" aria-hidden="true">
476
- <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" opacity="0.25" />
477
- <path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" stroke-width="3" stroke-linecap="round" />
478
- </svg>
556
+ <LoadingSpinner size="lg" />
479
557
  </slot>
480
558
  </div>
481
559
  </div>
@@ -514,7 +592,3 @@ const wrapperStyle = computed(() => {
514
592
  </div>
515
593
  </div>
516
594
  </template>
517
-
518
- <style>
519
- @import '../styles/components/dataframe.css';
520
- </style>
@@ -1,9 +1,10 @@
1
1
  <script setup lang="ts">
2
2
  /** Input that opens a calendar dropdown for selecting a single date, with clearable support. */
3
- import { ref, computed, watch } from 'vue'
3
+ import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom'
4
+ import { ref, computed, nextTick, onBeforeUnmount, watch } from 'vue'
4
5
  import { useCalendarGrid, type CalendarGridDay } from '../composables/useCalendarGrid'
5
6
  import { useDropdownState } from '../composables/useDropdownState'
6
- import { useEventListener } from '../composables/useEventListener'
7
+ import { useFocusTrap } from '../composables/useFocusTrap'
7
8
  import CalendarGridPanelInternal from './internal/CalendarGridPanelInternal.vue'
8
9
 
9
10
  interface Props {
@@ -28,12 +29,14 @@ const emit = defineEmits<{
28
29
  'update:modelValue': [value: string | undefined]
29
30
  }>()
30
31
 
31
- const inputRef = ref<HTMLDivElement | null>(null)
32
- const dropdownRef = ref<HTMLDivElement | null>(null)
32
+ const dropdownRef = ref<HTMLElement | null>(null)
33
33
  const dropdownStyle = ref<Record<string, string>>({})
34
- const { isOpen, rootRef: containerRef, close, toggle } = useDropdownState({
34
+ const { isOpen, rootRef: containerRef, triggerRef, open, close, toggle } = useDropdownState({
35
35
  insideRefs: [dropdownRef],
36
36
  })
37
+ // The panel is a non-modal dialog: Tab cycles inside it while it is open, and
38
+ // focus returns to the input when it closes.
39
+ useFocusTrap({ containerRef: dropdownRef, active: isOpen })
37
40
  const {
38
41
  weekDays,
39
42
  monthYear,
@@ -88,41 +91,88 @@ function toggleCalendar() {
88
91
  toggle()
89
92
  }
90
93
 
91
- function updateDropdownPosition() {
92
- if (!inputRef.value) return
93
- const rect = inputRef.value.getBoundingClientRect()
94
+ function handleTriggerKeydown(event: KeyboardEvent) {
95
+ if (props.disabled || isOpen.value) return
96
+ if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {
97
+ event.preventDefault()
98
+ open()
99
+ }
100
+ }
101
+
102
+ // floating-ui keeps the panel anchored (flip/shift) while the page scrolls,
103
+ // instead of the panel closing on every scroll or resize.
104
+ let stopAutoUpdate: (() => void) | null = null
105
+
106
+ async function updateDropdownPosition() {
107
+ const trigger = triggerRef.value
108
+ const panel = dropdownRef.value
109
+ if (!trigger || !panel) return
110
+
111
+ const { x, y } = await computePosition(trigger, panel, {
112
+ placement: 'bottom-start',
113
+ strategy: 'fixed',
114
+ middleware: [offset(4), flip(), shift({ padding: 8 })],
115
+ })
94
116
  dropdownStyle.value = {
95
117
  position: 'fixed',
96
- top: `${rect.bottom + 4}px`,
97
- left: `${rect.left}px`,
98
- width: `${Math.max(rect.width, 288)}px`,
118
+ top: `${y}px`,
119
+ left: `${x}px`,
120
+ width: `${Math.max(trigger.getBoundingClientRect().width, 288)}px`,
99
121
  zIndex: '9999',
100
122
  }
101
123
  }
102
124
 
103
- function handleScrollOrResize() {
104
- close()
125
+ function stopPositioning() {
126
+ stopAutoUpdate?.()
127
+ stopAutoUpdate = null
105
128
  }
106
129
 
107
- watch(isOpen, (open) => {
108
- if (open) {
109
- updateDropdownPosition()
110
- if (selectedDate.value) {
111
- setCurrentMonthFromDate(selectedDate.value)
112
- }
130
+ function startPositioning() {
131
+ const trigger = triggerRef.value
132
+ const panel = dropdownRef.value
133
+ if (!trigger || !panel) return
134
+ stopPositioning()
135
+ stopAutoUpdate = autoUpdate(trigger, panel, () => void updateDropdownPosition())
136
+ }
137
+
138
+ /** Moves focus into the calendar grid: selected day, else today, else the first day. */
139
+ function focusCalendar() {
140
+ const panel = dropdownRef.value
141
+ if (!panel) return
142
+ const target =
143
+ panel.querySelector<HTMLElement>('.mint-date-picker__day--selected') ??
144
+ panel.querySelector<HTMLElement>('.mint-date-picker__day--today') ??
145
+ panel.querySelector<HTMLElement>('.mint-date-picker__day:not([disabled])')
146
+ target?.focus()
147
+ }
148
+
149
+ watch(isOpen, async (opened) => {
150
+ if (!opened) {
151
+ stopPositioning()
152
+ return
153
+ }
154
+ if (selectedDate.value) {
155
+ setCurrentMonthFromDate(selectedDate.value)
113
156
  }
157
+ await nextTick()
158
+ startPositioning()
159
+ // A second tick so this focus lands after the focus trap's initial focus.
160
+ await nextTick()
161
+ focusCalendar()
114
162
  })
115
163
 
116
- useEventListener(() => window, 'scroll', handleScrollOrResize, true)
117
- useEventListener(() => window, 'resize', handleScrollOrResize)
164
+ onBeforeUnmount(stopPositioning)
118
165
  </script>
119
166
 
120
167
  <template>
121
168
  <div ref="containerRef" class="mint-date-picker">
122
- <div ref="inputRef" class="mint-date-picker__input-wrapper">
169
+ <div class="mint-date-picker__input-wrapper">
123
170
  <input
171
+ ref="triggerRef"
124
172
  type="text"
125
173
  readonly
174
+ aria-haspopup="dialog"
175
+ :aria-expanded="isOpen"
126
176
  :value="displayValue"
127
177
  :placeholder="placeholder || 'Select date'"
128
178
  :disabled="disabled"
@@ -133,6 +183,7 @@ useEventListener(() => window, 'resize', handleScrollOrResize)
133
183
  disabled ? 'mint-date-picker__input--disabled' : '',
134
184
  ]"
135
185
  @click="toggleCalendar"
186
+ @keydown="handleTriggerKeydown"
136
187
  />
137
188
  <div class="mint-date-picker__icon">
138
189
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -156,7 +207,6 @@ useEventListener(() => window, 'resize', handleScrollOrResize)
156
207
  class="mint-date-picker__dropdown"
157
208
  :style="dropdownStyle"
158
209
  role="dialog"
159
- aria-modal="true"
160
210
  aria-label="Date picker"
161
211
  >
162
212
  <CalendarGridPanelInternal
@@ -191,7 +241,3 @@ useEventListener(() => window, 'resize', handleScrollOrResize)
191
241
  </Teleport>
192
242
  </div>
193
243
  </template>
194
-
195
- <style>
196
- @import '../styles/components/date-picker.css';
197
- </style>
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  /** Combined date and time picker with a dropdown calendar, time slots, and 12/24 h format support. */
3
- import { computed, watch, nextTick } from 'vue'
3
+ import { computed, ref, watch, nextTick } from 'vue'
4
4
  import { useCalendarGrid, type CalendarGridDay } from '../composables/useCalendarGrid'
5
5
  import { useDropdownState } from '../composables/useDropdownState'
6
+ import { useFocusTrap } from '../composables/useFocusTrap'
6
7
  import { formatTime, formatTimeSlot, generateTimeSlots, toMinutes } from '../composables/useTimeUtils'
7
8
  import CalendarGridPanelInternal from './internal/CalendarGridPanelInternal.vue'
8
9
 
@@ -35,7 +36,10 @@ const emit = defineEmits<{
35
36
  'update:modelValue': [value: string | undefined]
36
37
  }>()
37
38
 
38
- const { isOpen, rootRef, close, toggle } = useDropdownState()
39
+ const dropdownRef = ref<HTMLElement | null>(null)
40
+ const { isOpen, rootRef, triggerRef, open, close, toggle } = useDropdownState()
41
+ // Non-modal dialog: Tab stays inside the panel, focus returns to the input on close.
42
+ useFocusTrap({ containerRef: dropdownRef, active: isOpen })
39
43
  const {
40
44
  weekDays,
41
45
  monthYear,
@@ -148,18 +152,42 @@ function toggleDropdown() {
148
152
  toggle()
149
153
  }
150
154
 
151
- watch(isOpen, (open) => {
152
- if (open && selectedDate.value) {
155
+ function handleTriggerKeydown(event: KeyboardEvent) {
156
+ if (props.disabled || isOpen.value) return
157
+ if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {
158
+ event.preventDefault()
159
+ open()
160
+ }
161
+ }
162
+
163
+ /** Moves focus into the calendar grid: selected day, else today, else the first day. */
164
+ function focusCalendar() {
165
+ const panel = dropdownRef.value
166
+ if (!panel) return
167
+ const target =
168
+ panel.querySelector<HTMLElement>('.mint-date-picker__day--selected') ??
169
+ panel.querySelector<HTMLElement>('.mint-date-picker__day--today') ??
170
+ panel.querySelector<HTMLElement>('.mint-date-picker__day:not([disabled])')
171
+ target?.focus()
172
+ }
173
+
174
+ watch(isOpen, async (opened) => {
175
+ if (!opened) return
176
+ if (selectedDate.value) {
153
177
  setCurrentMonthFromDate(selectedDate.value)
154
- nextTick(() => {
155
- // Scroll time grid to selected time
156
- const grid = rootRef.value?.querySelector('.mint-datetime-picker__time-grid')
157
- const active = rootRef.value?.querySelector('.mint-datetime-picker__time-chip--active')
158
- if (grid && active) {
159
- active.scrollIntoView({ block: 'center' })
160
- }
161
- })
162
178
  }
179
+ await nextTick()
180
+ if (selectedDate.value) {
181
+ // Scroll time grid to selected time
182
+ const grid = dropdownRef.value?.querySelector('.mint-datetime-picker__time-grid')
183
+ const active = dropdownRef.value?.querySelector('.mint-datetime-picker__time-chip--active')
184
+ if (grid && active) {
185
+ active.scrollIntoView({ block: 'center' })
186
+ }
187
+ }
188
+ // A second tick so this focus lands after the focus trap's initial focus.
189
+ await nextTick()
190
+ focusCalendar()
163
191
  })
164
192
  </script>
165
193
 
@@ -172,8 +200,11 @@ watch(isOpen, (open) => {
172
200
  </svg>
173
201
  </div>
174
202
  <input
203
+ ref="triggerRef"
175
204
  type="text"
176
205
  readonly
206
+ aria-haspopup="dialog"
207
+ :aria-expanded="isOpen"
177
208
  :value="displayValue"
178
209
  :placeholder="placeholder"
179
210
  :disabled="disabled"
@@ -185,6 +216,7 @@ watch(isOpen, (open) => {
185
216
  ]"
186
217
  aria-label="Select date and time"
187
218
  @click="toggleDropdown"
219
+ @keydown="handleTriggerKeydown"
188
220
  />
189
221
  <div class="mint-datetime-picker__icon-clock">
190
222
  <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -203,9 +235,9 @@ watch(isOpen, (open) => {
203
235
  >
204
236
  <div
205
237
  v-if="isOpen"
238
+ ref="dropdownRef"
206
239
  class="mint-datetime-picker__dropdown"
207
240
  role="dialog"
208
- aria-modal="true"
209
241
  aria-label="Date and time picker"
210
242
  >
211
243
  <!-- Calendar section -->
@@ -265,7 +297,3 @@ watch(isOpen, (open) => {
265
297
  </Transition>
266
298
  </div>
267
299
  </template>
268
-
269
- <style>
270
- @import '../styles/components/datetime-picker.css';
271
- </style>
@@ -43,7 +43,3 @@ withDefaults(defineProps<Props>(), {
43
43
  </template>
44
44
  </div>
45
45
  </template>
46
-
47
- <style>
48
- @import '../styles/components/divider.css';
49
- </style>
@@ -410,7 +410,3 @@ watch(activeMode, (mode) => {
410
410
  </div>
411
411
  </div>
412
412
  </template>
413
-
414
- <style>
415
- @import '../styles/components/dose-calculator.css';
416
- </style>
@@ -19,6 +19,7 @@ import type {
19
19
  UseControlWorkspaceReturn,
20
20
  } from '../composables/useControlSchema'
21
21
  import { defineDoseDesignControlModel } from '../composables/useControlSchema'
22
+ import { doseDesignWorkspaceProps } from '../composables/workspaceRecipes'
22
23
  import ControlWorkspaceView from './ControlWorkspaceView.vue'
23
24
  import DoseCalculator from './DoseCalculator.vue'
24
25
  import WellPlate from './WellPlate.vue'
@@ -158,6 +159,10 @@ const resolvedModel = computed<ControlModel | ControlModelBinding>(() =>
158
159
  props.model ?? defineDoseDesignControlModel(props.doseDesignOptions)
159
160
  )
160
161
 
162
+ // Thin recipe: forwards the ~25 props this view shares verbatim with ControlWorkspaceView
163
+ // (see workspaceRecipes.ts) instead of spelling each one out in the template.
164
+ const controlWorkspaceRecipe = computed(() => doseDesignWorkspaceProps(props, resolvedModel.value))
165
+
161
166
  function resolvedPlateId(): string {
162
167
  return props.plateId ?? props.doseDesignOptions.componentProps?.plateId ?? 'plate'
163
168
  }
@@ -200,31 +205,7 @@ function slotProps(slotProps: {
200
205
 
201
206
  <template>
202
207
  <ControlWorkspaceView
203
- :model="resolvedModel"
204
- :workspace="workspace"
205
- :control-options="controlOptions"
206
- :initial-values="initialValues"
207
- :model-value="modelValue"
208
- :values="values"
209
- :title="title"
210
- :subtitle="subtitle"
211
- :top-bar-variant="topBarVariant"
212
- :sidebar-width="sidebarWidth"
213
- :sidebar-variant="sidebarVariant"
214
- :responsive-sidebar="responsiveSidebar"
215
- :sidebar-position="sidebarPosition"
216
- :sidebar-title="sidebarTitle"
217
- :sidebar-subtitle="sidebarSubtitle"
218
- :sidebar-badge="sidebarBadge"
219
- :floating="floating"
220
- :dense="dense"
221
- :show-settings="showSettings"
222
- :show-form-actions="showFormActions"
223
- :form-enhancements="formEnhancements"
224
- :form-loading="formLoading"
225
- :form-disabled="formDisabled"
226
- :form-readonly="formReadonly"
227
- :form-size="formSize"
208
+ v-bind="controlWorkspaceRecipe"
228
209
  @update:model-value="emit('update:modelValue', $event)"
229
210
  @update:values="emit('update:values', $event)"
230
211
  @submit="emit('submit', $event)"
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  /** Button that opens a dropdown menu for selecting one option from a list. */
3
- import { computed } from 'vue'
3
+ import { computed, ref } from 'vue'
4
4
  import { useDropdownState } from '../composables/useDropdownState'
5
+ import { useMenuKeyboard } from '../composables/useMenuKeyboard'
5
6
  import type { SelectOption, SelectOptionInput, ButtonVariant, ButtonSize } from '../types'
6
7
  import { normalizeOptionInput } from '../utils/options'
7
8
 
@@ -53,11 +54,25 @@ const emit = defineEmits<{
53
54
  select: [option: SelectOption<string | number>]
54
55
  }>()
55
56
 
56
- const { isOpen, rootRef, close, toggle: toggleDropdown } = useDropdownState()
57
+ const { isOpen, rootRef, triggerRef, open, close, toggle: toggleDropdown } = useDropdownState()
57
58
  const normalizedOptions = computed<SelectOption<string | number>[]>(() =>
58
59
  props.options.map(normalizeOptionInput)
59
60
  )
60
61
 
62
+ const panelRef = ref<HTMLElement | null>(null)
63
+
64
+ function optionItems(): HTMLElement[] {
65
+ if (!panelRef.value) return []
66
+ return Array.from(panelRef.value.querySelectorAll<HTMLElement>('[role="option"]:not([aria-disabled="true"])'))
67
+ }
68
+
69
+ const { handleTriggerKeydown, handlePanelKeydown } = useMenuKeyboard({
70
+ items: optionItems,
71
+ isOpen: () => isOpen.value,
72
+ open,
73
+ close,
74
+ })
75
+
61
76
  const selectedLabel = computed(() => {
62
77
  if (props.modelValue === undefined) return props.placeholder ?? 'Select...'
63
78
  const found = normalizedOptions.value.find(o => o.value === props.modelValue)
@@ -80,6 +95,7 @@ function selectOption(option: SelectOption<string | number>) {
80
95
  <template>
81
96
  <div ref="rootRef" class="mint-dropdown-button">
82
97
  <button
98
+ ref="triggerRef"
83
99
  type="button"
84
100
  :disabled="disabled || loading"
85
101
  :class="[
@@ -94,6 +110,7 @@ function selectOption(option: SelectOption<string | number>) {
94
110
  :aria-expanded="isOpen"
95
111
  aria-haspopup="listbox"
96
112
  @click="toggle"
113
+ @keydown="handleTriggerKeydown"
97
114
  >
98
115
  <svg v-if="loading" class="mint-dropdown-button__spinner" viewBox="0 0 24 24" fill="none" aria-hidden="true">
99
116
  <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" opacity="0.25" />
@@ -116,15 +133,18 @@ function selectOption(option: SelectOption<string | number>) {
116
133
  <Transition name="mint-dropdown-button-menu">
117
134
  <ul
118
135
  v-if="isOpen"
136
+ ref="panelRef"
119
137
  class="mint-dropdown-button__menu"
120
138
  role="listbox"
121
139
  :aria-activedescendant="modelValue !== undefined ? `mint-dropdown-option-${modelValue}` : undefined"
140
+ @keydown="handlePanelKeydown"
122
141
  >
123
142
  <li
124
143
  v-for="option in normalizedOptions"
125
144
  :id="`mint-dropdown-option-${option.value}`"
126
145
  :key="String(option.value)"
127
146
  role="option"
147
+ :tabindex="-1"
128
148
  :aria-selected="option.value === modelValue"
129
149
  :aria-disabled="option.disabled"
130
150
  :class="[
@@ -135,6 +155,8 @@ function selectOption(option: SelectOption<string | number>) {
135
155
  },
136
156
  ]"
137
157
  @click="selectOption(option)"
158
+ @keydown.enter.prevent="selectOption(option)"
159
+ @keydown.space.prevent="selectOption(option)"
138
160
  >
139
161
  <span class="mint-dropdown-button__option-label">{{ option.label }}</span>
140
162
  <span v-if="option.description" class="mint-dropdown-button__option-description">
@@ -157,7 +179,3 @@ function selectOption(option: SelectOption<string | number>) {
157
179
  </Transition>
158
180
  </div>
159
181
  </template>
160
-
161
- <style>
162
- @import '../styles/components/dropdown-button.css';
163
- </style>
@@ -65,7 +65,3 @@ const defaultIconPaths = [
65
65
  </div>
66
66
  </div>
67
67
  </template>
68
-
69
- <style>
70
- @import '../styles/components/empty-state.css';
71
- </style>
@@ -58,7 +58,3 @@ function handleKeydown(event: KeyboardEvent) {
58
58
  {{ copied ? 'Copied!' : code }}
59
59
  </span>
60
60
  </template>
61
-
62
- <style>
63
- @import '../styles/components/experiment-code-badge.css';
64
- </style>