@morscherlab/mint-sdk 1.2.0-beta.8 → 1.2.0

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 (402) hide show
  1. package/README.md +60 -0
  2. package/dist/{ExperimentPopover-CfOdh5Rm.js → ExperimentPopover-CJdNlCZb.js} +24 -15
  3. package/dist/ExperimentPopover-CJdNlCZb.js.map +1 -0
  4. package/dist/{ExperimentSelectorModal-DvIagrxY.js → ExperimentSelectorModal-DCXmwKNS.js} +5 -5
  5. package/dist/ExperimentSelectorModal-DCXmwKNS.js.map +1 -0
  6. package/dist/{SettingsModal-W3TYfnP9.js → SettingsModal-BYR20I4c.js} +5 -5
  7. package/dist/SettingsModal-BYR20I4c.js.map +1 -0
  8. package/dist/{EmptyState-NBqf2_8z.js → Skeleton-BAF3CKY7.js} +111 -93
  9. package/dist/Skeleton-BAF3CKY7.js.map +1 -0
  10. package/dist/__tests__/components/DataStates.test.d.ts +1 -0
  11. package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
  12. package/dist/__tests__/components/FileBrowserModal.test.d.ts +1 -0
  13. package/dist/__tests__/components/FilePicker.state.test.d.ts +1 -0
  14. package/dist/__tests__/components/FilePicker.test.d.ts +1 -0
  15. package/dist/__tests__/components/PlotlyChart.test.d.ts +1 -0
  16. package/dist/__tests__/components/iconButtonIconSizing.test.d.ts +1 -0
  17. package/dist/__tests__/composables/useFileBrowser.test.d.ts +1 -0
  18. package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
  19. package/dist/__tests__/composables/usePlatformFilePickerAdapter.test.d.ts +1 -0
  20. package/dist/__tests__/styles/cssTokens.test.d.ts +1 -0
  21. package/dist/apiErrors.d.ts +26 -0
  22. package/dist/components/ActionMenu.vue.d.ts +1 -1
  23. package/dist/components/AppPluginSwitcher.vue.d.ts +2 -0
  24. package/dist/components/AuditTrail.vue.d.ts +13 -0
  25. package/dist/components/BaseInput.vue.d.ts +1 -1
  26. package/dist/components/BaseSelect.vue.d.ts +1 -1
  27. package/dist/components/BaseTextarea.vue.d.ts +1 -1
  28. package/dist/components/BatchProgressList.vue.d.ts +17 -0
  29. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
  30. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +2 -2
  31. package/dist/components/ConcentrationInput.vue.d.ts +1 -1
  32. package/dist/components/DataFrame.vue.d.ts +14 -2
  33. package/dist/components/DatePicker.vue.d.ts +2 -2
  34. package/dist/components/DateTimePicker.vue.d.ts +3 -1
  35. package/dist/components/ExperimentDataViewer.vue.d.ts +26 -1
  36. package/dist/components/ExperimentPopover.vue.d.ts +1 -0
  37. package/dist/components/ExperimentTimeline.vue.d.ts +29 -1
  38. package/dist/components/FileBrowserModal.vue.d.ts +99 -0
  39. package/dist/components/FilePicker.vue.d.ts +32 -0
  40. package/dist/components/FileUploader.vue.d.ts +5 -2
  41. package/dist/components/FormulaInput.vue.d.ts +1 -1
  42. package/dist/components/GeneratedPlotlyResult.vue.d.ts +1 -3
  43. package/dist/components/LcmsSequenceTable.vue.d.ts +29 -1
  44. package/dist/components/MobileSupportGate.vue.d.ts +1 -1
  45. package/dist/components/MoleculeInput.vue.d.ts +1 -1
  46. package/dist/components/NumberInput.vue.d.ts +1 -1
  47. package/dist/components/PlotlyChart.vue.d.ts +59 -0
  48. package/dist/components/ProgressBar.vue.d.ts +1 -1
  49. package/dist/components/ProtocolStepEditor.vue.d.ts +1 -0
  50. package/dist/components/ReagentList.vue.d.ts +29 -1
  51. package/dist/components/ResourceCard.vue.d.ts +1 -1
  52. package/dist/components/SampleHierarchyTree.vue.d.ts +29 -1
  53. package/dist/components/SampleSelector.drag.d.ts +13 -0
  54. package/dist/components/SampleSelector.tree.d.ts +2 -0
  55. package/dist/components/SampleSelector.vue.d.ts +34 -1
  56. package/dist/components/ScheduleCalendar.vue.d.ts +20 -0
  57. package/dist/components/SecretInput.vue.d.ts +1 -1
  58. package/dist/components/SequenceInput.vue.d.ts +1 -1
  59. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -0
  60. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  61. package/dist/components/TagsInput.vue.d.ts +1 -1
  62. package/dist/components/TimePicker.vue.d.ts +2 -1
  63. package/dist/components/TimeRangeInput.vue.d.ts +1 -1
  64. package/dist/components/UnitInput.vue.d.ts +1 -1
  65. package/dist/components/WellPlate.geometry.d.ts +15 -0
  66. package/dist/components/WellPlate.interaction.d.ts +13 -0
  67. package/dist/components/WellPlate.vue.d.ts +30 -1
  68. package/dist/components/fileBrowserPresentation.d.ts +10 -0
  69. package/dist/components/index.d.ts +4 -0
  70. package/dist/components/index.js +8 -8
  71. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -0
  72. package/dist/components/internal/DataStateInternal.vue.d.ts +43 -0
  73. package/dist/components/internal/FileBrowserDialogInternal.vue.d.ts +34 -0
  74. package/dist/components/internal/FileBrowserListingInternal.vue.d.ts +25 -0
  75. package/dist/components/internal/FileBrowserToolbarInternal.vue.d.ts +26 -0
  76. package/dist/components/internal/FilePickerPreviewInternal.vue.d.ts +18 -0
  77. package/dist/components/internal/FilePickerTreeInternal.vue.d.ts +31 -0
  78. package/dist/{components-B80gV0P6.js → components-DIum5hkx.js} +5415 -2753
  79. package/dist/components-DIum5hkx.js.map +1 -0
  80. package/dist/composables/controlSchemaTypes.d.ts +34 -0
  81. package/dist/composables/filePicker/localFiles.d.ts +3 -0
  82. package/dist/composables/filePicker/usePickerNavigation.d.ts +96 -0
  83. package/dist/composables/filePicker/usePickerSelection.d.ts +31 -0
  84. package/dist/composables/filePicker/validation.d.ts +5 -0
  85. package/dist/composables/index.d.ts +3 -0
  86. package/dist/composables/index.js +10 -10
  87. package/dist/composables/useApi.d.ts +14 -7
  88. package/dist/composables/useDropdownState.d.ts +18 -2
  89. package/dist/composables/useEventStream.d.ts +9 -0
  90. package/dist/composables/useFileBrowser.d.ts +55 -0
  91. package/dist/composables/useFilePicker.d.ts +189 -0
  92. package/dist/composables/useListReorder.d.ts +90 -0
  93. package/dist/composables/useMenuKeyboard.d.ts +2 -2
  94. package/dist/composables/usePlatformFilePickerAdapter.d.ts +11 -0
  95. package/dist/composables/usePluginConfig.d.ts +3 -1
  96. package/dist/composables/useRequestSyncState.d.ts +42 -2
  97. package/dist/composables/useScheduleDrag.d.ts +1 -0
  98. package/dist/{composables-C8mmD5ta.js → composables-mFXsTA4g.js} +307 -74
  99. package/dist/composables-mFXsTA4g.js.map +1 -0
  100. package/dist/{useApi-CDBq6z23.js → errors-BLuqMee5.js} +178 -16
  101. package/dist/errors-BLuqMee5.js.map +1 -0
  102. package/dist/{experiment-Y9Iz7KYi.js → experiment-CVDRAnqp.js} +3 -8
  103. package/dist/experiment-CVDRAnqp.js.map +1 -0
  104. package/dist/{experiment-utils-DV3DrvE_.js → experiment-utils-CV2Wza3z.js} +1 -1
  105. package/dist/experiment-utils-CV2Wza3z.js.map +1 -0
  106. package/dist/index.d.ts +1 -0
  107. package/dist/index.js +15 -15
  108. package/dist/install.js +2 -7
  109. package/dist/install.js.map +1 -1
  110. package/dist/permissions.d.ts +42 -1
  111. package/dist/permissions.js +20 -10
  112. package/dist/permissions.js.map +1 -1
  113. package/dist/stores/index.d.ts +1 -1
  114. package/dist/stores/index.js +3 -3
  115. package/dist/stores/settings.d.ts +31 -1
  116. package/dist/styles.css +15162 -32475
  117. package/dist/templates/index.js +2 -4
  118. package/dist/{templates-CK72aKO1.js → templates-CmGfmBRA.js} +291 -85
  119. package/dist/templates-CmGfmBRA.js.map +1 -0
  120. package/dist/types/fileBrowserTypes.d.ts +77 -0
  121. package/dist/types/filePicker.d.ts +77 -0
  122. package/dist/types/index.d.ts +2 -0
  123. package/dist/{useControlSchema-_OVhavKq.js → useControlSchema-DldpxNOP.js} +234 -1121
  124. package/dist/useControlSchema-DldpxNOP.js.map +1 -0
  125. package/dist/{useDropdownState-D5G4UvWd.js → useDropdownState-C7hRiVQf.js} +22 -6
  126. package/dist/useDropdownState-C7hRiVQf.js.map +1 -0
  127. package/dist/{useExperimentSelector-DSEckf_g.js → useExperimentSelector-CEcIaueJ.js} +4 -4
  128. package/dist/{useExperimentSelector-DSEckf_g.js.map → useExperimentSelector-CEcIaueJ.js.map} +1 -1
  129. package/dist/{useFormBuilder-Bd714C7y.js → useFormBuilder-z5L_dUIy.js} +2 -2
  130. package/dist/{useFormBuilder-Bd714C7y.js.map → useFormBuilder-z5L_dUIy.js.map} +1 -1
  131. package/dist/utils/color.d.ts +13 -0
  132. package/dist/utils/errors.d.ts +9 -0
  133. package/dist/{useProtocolTemplates-Dq5Pzwyd.js → validation-uwoJGEb6.js} +231 -158
  134. package/dist/validation-uwoJGEb6.js.map +1 -0
  135. package/package.json +1 -1
  136. package/src/__tests__/apiErrors.test.ts +81 -0
  137. package/src/__tests__/components/ActionMenu.test.ts +94 -0
  138. package/src/__tests__/components/DataFrame.test.ts +183 -11
  139. package/src/__tests__/components/DataStates.test.ts +176 -0
  140. package/src/__tests__/components/DatePicker.test.ts +76 -0
  141. package/src/__tests__/components/DateTimePicker.test.ts +36 -0
  142. package/src/__tests__/components/ExperimentPopover.test.ts +30 -0
  143. package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
  144. package/src/__tests__/components/FileBrowserModal.test.ts +460 -0
  145. package/src/__tests__/components/FilePicker.state.test.ts +287 -0
  146. package/src/__tests__/components/FilePicker.test.ts +487 -0
  147. package/src/__tests__/components/GeneratedPlotlyResult.test.ts +52 -5
  148. package/src/__tests__/components/GroupAssigner.test.ts +53 -0
  149. package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
  150. package/src/__tests__/components/PlotlyChart.test.ts +305 -0
  151. package/src/__tests__/components/RackEditor.test.ts +35 -0
  152. package/src/__tests__/components/ReagentList.test.ts +64 -2
  153. package/src/__tests__/components/RetiredStyles.test.ts +31 -0
  154. package/src/__tests__/components/SampleSelector.drag.test.ts +51 -1
  155. package/src/__tests__/components/ScheduleCalendar.test.ts +63 -0
  156. package/src/__tests__/components/SegmentedControl.test.ts +107 -0
  157. package/src/__tests__/components/TimePicker.test.ts +61 -0
  158. package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
  159. package/src/__tests__/components/iconButtonIconSizing.test.ts +18 -0
  160. package/src/__tests__/composables/useApi.test.ts +92 -9
  161. package/src/__tests__/composables/useDropdownState.test.ts +58 -1
  162. package/src/__tests__/composables/useFileBrowser.test.ts +305 -0
  163. package/src/__tests__/composables/useGeneratedAnalysis.test.ts +7 -2
  164. package/src/__tests__/composables/useListReorder.test.ts +145 -0
  165. package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +67 -0
  166. package/src/__tests__/composables/usePluginClient.test.ts +3 -0
  167. package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
  168. package/src/__tests__/stores/settings.test.ts +71 -1
  169. package/src/__tests__/styles/cssTokens.test.ts +131 -0
  170. package/src/__tests__/utils/permissions.test.ts +68 -0
  171. package/src/apiErrors.ts +85 -0
  172. package/src/components/ActionMenu.vue +45 -88
  173. package/src/components/AlertBox.vue +0 -4
  174. package/src/components/AppAvatarMenu.vue +2 -14
  175. package/src/components/AppLayout.vue +0 -4
  176. package/src/components/AppPluginSwitcher.story.vue +87 -0
  177. package/src/components/AppPluginSwitcher.vue +2 -5
  178. package/src/components/AppSidebar.vue +0 -4
  179. package/src/components/AppToastContainer.vue +6 -8
  180. package/src/components/AppTopBar.vue +0 -4
  181. package/src/components/ArtifactSaveDialog.vue +0 -4
  182. package/src/components/ArtifactSelectorModal.vue +0 -4
  183. package/src/components/AuditTrail.story.vue +18 -0
  184. package/src/components/AuditTrail.vue +27 -7
  185. package/src/components/AutoGroupModal.vue +0 -4
  186. package/src/components/Avatar.vue +0 -4
  187. package/src/components/BaseButton.vue +0 -4
  188. package/src/components/BaseCheckbox.vue +0 -4
  189. package/src/components/BaseInput.vue +0 -4
  190. package/src/components/BaseModal.vue +0 -4
  191. package/src/components/BasePill.vue +0 -4
  192. package/src/components/BaseRadioGroup.vue +0 -4
  193. package/src/components/BaseSelect.vue +0 -4
  194. package/src/components/BaseSlider.vue +0 -4
  195. package/src/components/BaseTabs.vue +0 -4
  196. package/src/components/BaseTextarea.vue +0 -4
  197. package/src/components/BaseToggle.vue +0 -4
  198. package/src/components/BatchProgressList.story.vue +18 -0
  199. package/src/components/BatchProgressList.vue +32 -4
  200. package/src/components/Breadcrumb.vue +0 -4
  201. package/src/components/Calendar.vue +0 -4
  202. package/src/components/ChartContainer.story.vue +1 -1
  203. package/src/components/ChartContainer.vue +0 -4
  204. package/src/components/ChemicalFormula.vue +0 -4
  205. package/src/components/CollapsibleCard.vue +0 -4
  206. package/src/components/ColorSlider.vue +0 -4
  207. package/src/components/ConcentrationInput.vue +0 -4
  208. package/src/components/ConfirmDialog.vue +0 -4
  209. package/src/components/DataFrame.story.vue +14 -0
  210. package/src/components/DataFrame.vue +97 -23
  211. package/src/components/DatePicker.vue +73 -27
  212. package/src/components/DateTimePicker.vue +45 -17
  213. package/src/components/Divider.vue +0 -4
  214. package/src/components/DoseCalculator.vue +0 -4
  215. package/src/components/DropdownButton.vue +2 -14
  216. package/src/components/EmptyState.vue +0 -4
  217. package/src/components/ExperimentCodeBadge.vue +0 -4
  218. package/src/components/ExperimentDataViewer.story.vue +3 -0
  219. package/src/components/ExperimentDataViewer.vue +33 -13
  220. package/src/components/ExperimentPopover.vue +12 -7
  221. package/src/components/ExperimentSelectorModal.vue +0 -4
  222. package/src/components/ExperimentTimeline.story.vue +18 -0
  223. package/src/components/ExperimentTimeline.vue +81 -75
  224. package/src/components/FileBrowserModal.story.vue +203 -0
  225. package/src/components/FileBrowserModal.vue +384 -0
  226. package/src/components/FilePicker.story.vue +44 -0
  227. package/src/components/FilePicker.vue +284 -0
  228. package/src/components/FileUploader.vue +32 -18
  229. package/src/components/FitPanel.vue +0 -4
  230. package/src/components/FormActions.story.vue +96 -0
  231. package/src/components/FormActions.vue +0 -4
  232. package/src/components/FormBuilder.vue +0 -4
  233. package/src/components/FormField.vue +0 -4
  234. package/src/components/FormulaInput.vue +0 -4
  235. package/src/components/GeneratedPathInput.story.vue +68 -0
  236. package/src/components/GeneratedPlotlyResult.story.vue +96 -0
  237. package/src/components/GeneratedPlotlyResult.vue +34 -69
  238. package/src/components/GeneratedPluginView.story.vue +145 -0
  239. package/src/components/GeneratedPluginView.vue +10 -10
  240. package/src/components/GeneratedResultRenderer.story.vue +117 -0
  241. package/src/components/GeneratedResultRenderer.vue +4 -9
  242. package/src/components/GroupAssigner.vue +67 -5
  243. package/src/components/IconButton.vue +0 -4
  244. package/src/components/InstrumentAlertLog.vue +0 -4
  245. package/src/components/InstrumentStateBadge.vue +0 -4
  246. package/src/components/InstrumentStatusCard.vue +0 -4
  247. package/src/components/JobsStatusTray.vue +0 -4
  248. package/src/components/LcmsSequenceTable.vue +81 -50
  249. package/src/components/LoadingSpinner.vue +0 -4
  250. package/src/components/MobileSupportGate.vue +0 -4
  251. package/src/components/MoleculeInput.vue +0 -4
  252. package/src/components/MultiSelect.vue +0 -4
  253. package/src/components/NumberInput.vue +0 -4
  254. package/src/components/PlateMapEditor.vue +3 -14
  255. package/src/components/PlotlyChart.story.vue +61 -0
  256. package/src/components/PlotlyChart.vue +233 -0
  257. package/src/components/PluginIcon.vue +0 -4
  258. package/src/components/ProgressBar.vue +0 -4
  259. package/src/components/ProtocolStepEditor.vue +2 -4
  260. package/src/components/RackEditor.vue +51 -70
  261. package/src/components/ReagentEditor.vue +0 -4
  262. package/src/components/ReagentList.story.vue +18 -0
  263. package/src/components/ReagentList.vue +127 -82
  264. package/src/components/ResourceCard.vue +0 -4
  265. package/src/components/RuntimeUpdateNotice.story.vue +49 -0
  266. package/src/components/RuntimeUpdateNotice.vue +4 -4
  267. package/src/components/SampleHierarchyTree.story.vue +18 -0
  268. package/src/components/SampleHierarchyTree.vue +32 -9
  269. package/src/components/SampleLegend.vue +2 -8
  270. package/src/components/SampleSelector.drag.ts +51 -0
  271. package/src/components/SampleSelector.story.vue +6 -0
  272. package/src/components/SampleSelector.tree.ts +2 -0
  273. package/src/components/SampleSelector.vue +49 -10
  274. package/src/components/SampleSelectorGroupNode.vue +19 -0
  275. package/src/components/ScheduleCalendar.story.vue +6 -0
  276. package/src/components/ScheduleCalendar.vue +82 -7
  277. package/src/components/ScientificNumber.vue +0 -4
  278. package/src/components/SectionCard.vue +0 -4
  279. package/src/components/SegmentedControl.vue +50 -7
  280. package/src/components/SequenceInput.vue +0 -4
  281. package/src/components/SequenceProgressBar.story.vue +110 -0
  282. package/src/components/SequenceProgressBar.vue +0 -4
  283. package/src/components/SettingsModal.vue +0 -4
  284. package/src/components/Skeleton.vue +0 -4
  285. package/src/components/SmartGroupFieldRecipe.vue +8 -6
  286. package/src/components/SmartGroupManual.vue +0 -4
  287. package/src/components/StatusIndicator.vue +0 -4
  288. package/src/components/StepWizard.vue +0 -4
  289. package/src/components/TagsInput.vue +4 -6
  290. package/src/components/ThemeToggle.vue +0 -4
  291. package/src/components/TimePicker.vue +36 -7
  292. package/src/components/TimeRangeInput.vue +0 -4
  293. package/src/components/Tooltip.story.vue +3 -3
  294. package/src/components/Tooltip.vue +0 -4
  295. package/src/components/UnitInput.vue +0 -4
  296. package/src/components/WellPlate.geometry.ts +60 -1
  297. package/src/components/WellPlate.interaction.ts +44 -0
  298. package/src/components/WellPlate.story.vue +8 -0
  299. package/src/components/WellPlate.vue +53 -7
  300. package/src/components/fileBrowserPresentation.ts +39 -0
  301. package/src/components/index.ts +5 -0
  302. package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -5
  303. package/src/components/internal/AppTopBarPillNavInternal.vue +0 -4
  304. package/src/components/internal/DataStateInternal.vue +74 -0
  305. package/src/components/internal/FileBrowserDialogInternal.vue +75 -0
  306. package/src/components/internal/FileBrowserListingInternal.vue +197 -0
  307. package/src/components/internal/FileBrowserToolbarInternal.vue +136 -0
  308. package/src/components/internal/FilePickerPreviewInternal.vue +100 -0
  309. package/src/components/internal/FilePickerTreeInternal.vue +238 -0
  310. package/src/components/internal/FormSectionRenderer.vue +0 -4
  311. package/src/components/internal/WellEditPopupInternal.vue +0 -4
  312. package/src/composables/controlSchemaTypes.ts +34 -0
  313. package/src/composables/filePicker/localFiles.ts +58 -0
  314. package/src/composables/filePicker/usePickerNavigation.ts +220 -0
  315. package/src/composables/filePicker/usePickerSelection.ts +259 -0
  316. package/src/composables/filePicker/validation.ts +41 -0
  317. package/src/composables/index.ts +17 -0
  318. package/src/composables/useApi.ts +57 -13
  319. package/src/composables/useDropdownState.ts +35 -5
  320. package/src/composables/useEventStream.ts +14 -2
  321. package/src/composables/useFileBrowser.ts +314 -0
  322. package/src/composables/useFileImport.ts +2 -7
  323. package/src/composables/useFilePicker.ts +366 -0
  324. package/src/composables/useGeneratedAnalysis.ts +18 -26
  325. package/src/composables/useListReorder.story.vue +143 -0
  326. package/src/composables/useListReorder.ts +242 -0
  327. package/src/composables/useMenuKeyboard.ts +2 -2
  328. package/src/composables/useOptimisticMutation.ts +1 -15
  329. package/src/composables/usePlatformFilePickerAdapter.ts +104 -0
  330. package/src/composables/usePluginClient.ts +3 -0
  331. package/src/composables/usePluginConfig.ts +3 -1
  332. package/src/composables/useRackEditor.ts +2 -11
  333. package/src/composables/useRequestSyncState.ts +98 -28
  334. package/src/composables/useResourceCrud.ts +1 -15
  335. package/src/composables/useScheduleDrag.ts +37 -0
  336. package/src/composables/useWellPainting.ts +5 -29
  337. package/src/composables/useWellPlateEditor.ts +1 -11
  338. package/src/index.ts +3 -0
  339. package/src/permissions.ts +48 -9
  340. package/src/stores/experiment.ts +2 -7
  341. package/src/stores/index.ts +9 -1
  342. package/src/stores/settings.ts +93 -1
  343. package/src/styles/components/audit-trail.css +5 -5
  344. package/src/styles/components/batch-progress-list.css +7 -0
  345. package/src/styles/components/color-slider.css +2 -2
  346. package/src/styles/components/data-state.css +30 -0
  347. package/src/styles/components/dataframe.css +29 -35
  348. package/src/styles/components/experiment-data-viewer.css +4 -4
  349. package/src/styles/components/experiment-timeline.css +3 -30
  350. package/src/styles/components/file-browser-modal.css +489 -0
  351. package/src/styles/components/file-picker.css +502 -0
  352. package/src/styles/components/form-builder.css +5 -5
  353. package/src/styles/components/icon-button.css +9 -0
  354. package/src/styles/components/lcms-sequence-table.css +16 -1
  355. package/src/styles/components/multi-select.css +1 -1
  356. package/src/styles/components/reagent-list.css +38 -31
  357. package/src/styles/components/sample-hierarchy-tree.css +5 -19
  358. package/src/styles/components/sample-selector.css +9 -2
  359. package/src/styles/components/schedule-calendar.css +8 -0
  360. package/src/styles/components/scientific-number.css +1 -1
  361. package/src/styles/components/well-plate.css +8 -0
  362. package/src/styles/index.css +11 -0
  363. package/src/styles/variables.css +29 -0
  364. package/src/templates/builderPresetControls.ts +1 -1
  365. package/src/templates/componentPlateHelpers.ts +3 -1
  366. package/src/templates/doseResponseAdapters.ts +2 -13
  367. package/src/templates/elisaAssayCollectionBuilder.ts +2 -2
  368. package/src/templates/plateMapBuilder.ts +2 -2
  369. package/src/templates/qpcrPlateBuilder.ts +1 -1
  370. package/src/templates/templateValidators.ts +1 -1
  371. package/src/templates/timeCourseBuilder.ts +2 -2
  372. package/src/templates/wellPlateScreenCollectionBuilder.ts +2 -2
  373. package/src/types/fileBrowserTypes.ts +85 -0
  374. package/src/types/filePicker.ts +72 -0
  375. package/src/types/index.ts +14 -0
  376. package/src/utils/color.ts +51 -0
  377. package/src/utils/errors.ts +29 -0
  378. package/dist/EmptyState-NBqf2_8z.js.map +0 -1
  379. package/dist/ExperimentPopover-CfOdh5Rm.js.map +0 -1
  380. package/dist/ExperimentSelectorModal-DvIagrxY.js.map +0 -1
  381. package/dist/SettingsModal-W3TYfnP9.js.map +0 -1
  382. package/dist/components-B80gV0P6.js.map +0 -1
  383. package/dist/composables/autoGroupConstants.d.ts +0 -1
  384. package/dist/composables/autoGroupGrouping.d.ts +0 -3
  385. package/dist/composables/useAutoGroupInputSources.d.ts +0 -32
  386. package/dist/composables-C8mmD5ta.js.map +0 -1
  387. package/dist/experiment-Y9Iz7KYi.js.map +0 -1
  388. package/dist/experiment-utils-DV3DrvE_.js.map +0 -1
  389. package/dist/templates/builderDefaults.d.ts +0 -1
  390. package/dist/templates/plateGeometry.d.ts +0 -4
  391. package/dist/templates-CK72aKO1.js.map +0 -1
  392. package/dist/useApi-CDBq6z23.js.map +0 -1
  393. package/dist/useControlSchema-_OVhavKq.js.map +0 -1
  394. package/dist/useDropdownState-D5G4UvWd.js.map +0 -1
  395. package/dist/useProtocolTemplates-Dq5Pzwyd.js.map +0 -1
  396. package/src/__tests__/composables/useAutoGroupInputSources.test.ts +0 -127
  397. package/src/composables/autoGroupConstants.ts +0 -4
  398. package/src/composables/autoGroupGrouping.ts +0 -148
  399. package/src/composables/useAutoGroupInputSources.ts +0 -147
  400. package/src/templates/builderDefaults.ts +0 -11
  401. package/src/templates/plateGeometry.ts +0 -62
  402. /package/dist/__tests__/{composables/useAutoGroupInputSources.test.d.ts → apiErrors.test.d.ts} +0 -0
@@ -21,6 +21,8 @@ const allCompleted = [
21
21
 
22
22
  function initState() {
23
23
  return {
24
+ loading: false,
25
+ error: '',
24
26
  items: mixedItems,
25
27
  showSummary: true,
26
28
  title: 'Processing Raw Data Files',
@@ -35,6 +37,8 @@ function initState() {
35
37
  <template #default="{ state }">
36
38
  <div style="padding: 2rem; max-width: 500px;">
37
39
  <BatchProgressList
40
+ :loading="state.loading"
41
+ :error="state.error || null"
38
42
  :items="state.items"
39
43
  :show-summary="state.showSummary"
40
44
  :title="state.title"
@@ -45,6 +49,8 @@ function initState() {
45
49
  </div>
46
50
  </template>
47
51
  <template #controls="{ state }">
52
+ <HstCheckbox v-model="state.loading" title="Loading" />
53
+ <HstText v-model="state.error" title="Error Message" />
48
54
  <HstCheckbox v-model="state.showSummary" title="Show Summary" />
49
55
  <HstText v-model="state.title" title="Title" />
50
56
  <HstCheckbox v-model="state.autoScroll" title="Auto Scroll" />
@@ -88,5 +94,17 @@ function initState() {
88
94
  />
89
95
  </div>
90
96
  </Variant>
97
+
98
+ <Variant title="Loading">
99
+ <div style="padding: 2rem; max-width: 600px;">
100
+ <BatchProgressList :items="[]" loading />
101
+ </div>
102
+ </Variant>
103
+
104
+ <Variant title="Error">
105
+ <div style="padding: 2rem; max-width: 600px;">
106
+ <BatchProgressList :items="[]" error="Could not load the batch." />
107
+ </div>
108
+ </Variant>
91
109
  </Story>
92
110
  </template>
@@ -3,6 +3,7 @@
3
3
  import { computed, ref, watch, nextTick } from 'vue'
4
4
  import type { BatchItem, BatchSummary } from '../types'
5
5
  import { useExpansionSet } from '../composables/useExpansionSet'
6
+ import DataStateInternal from './internal/DataStateInternal.vue'
6
7
 
7
8
  interface Props {
8
9
  items: BatchItem[]
@@ -10,11 +11,23 @@ interface Props {
10
11
  title?: string
11
12
  maxHeight?: string
12
13
  autoScroll?: boolean
14
+ /** Replace the item list with a loading placeholder. */
15
+ loading?: boolean
16
+ /**
17
+ * Error message. When set, the item list is replaced by an error state.
18
+ * `null`/`undefined` renders normally.
19
+ */
20
+ error?: string | null
21
+ /** Empty-state headline shown when there are no batch items. */
22
+ emptyMessage?: string
13
23
  }
14
24
 
15
25
  const props = withDefaults(defineProps<Props>(), {
16
26
  showSummary: true,
17
27
  autoScroll: true,
28
+ loading: false,
29
+ error: null,
30
+ emptyMessage: 'No items in this batch',
18
31
  })
19
32
 
20
33
  const emit = defineEmits<{
@@ -91,7 +104,26 @@ watch(
91
104
  </div>
92
105
  </slot>
93
106
 
107
+ <div v-if="error" class="mint-batch-progress__state">
108
+ <slot name="error" :error="error">
109
+ <DataStateInternal state="error" size="sm" :title="error" />
110
+ </slot>
111
+ </div>
112
+
113
+ <div v-else-if="loading" class="mint-batch-progress__state">
114
+ <slot name="loading">
115
+ <DataStateInternal state="loading" size="sm" loading-label="Loading batch" />
116
+ </slot>
117
+ </div>
118
+
119
+ <div v-else-if="items.length === 0" class="mint-batch-progress__state">
120
+ <slot name="empty">
121
+ <DataStateInternal state="empty" size="sm" :title="emptyMessage" />
122
+ </slot>
123
+ </div>
124
+
94
125
  <div
126
+ v-else
95
127
  ref="listRef"
96
128
  class="mint-batch-progress__list"
97
129
  :style="maxHeight ? { maxHeight, overflowY: 'auto' } : {}"
@@ -175,7 +207,3 @@ watch(
175
207
  </div>
176
208
  </div>
177
209
  </template>
178
-
179
- <style>
180
- @import '../styles/components/batch-progress-list.css';
181
- </style>
@@ -75,7 +75,3 @@ function handleClick(item: BreadcrumbItem) {
75
75
  </ol>
76
76
  </nav>
77
77
  </template>
78
-
79
- <style>
80
- @import '../styles/components/breadcrumb.css';
81
- </style>
@@ -361,7 +361,3 @@ function nextMonth() {
361
361
  </div>
362
362
  </div>
363
363
  </template>
364
-
365
- <style>
366
- @import '../styles/components/calendar.css';
367
- </style>
@@ -75,7 +75,7 @@ import ChartContainer from './ChartContainer.vue'
75
75
  <button type="button" style="padding: 0.25rem 0.75rem; font-size: 0.8125rem; border: 1px solid var(--border-color, #e2e8f0); border-radius: 0.375rem; background: var(--bg-card, #fff); color: var(--text-primary, #1e293b); cursor: pointer;">
76
76
  Linear
77
77
  </button>
78
- <button type="button" style="padding: 0.25rem 0.75rem; font-size: 0.8125rem; border: 1px solid var(--mint-primary, #3B82F6); border-radius: 0.375rem; background: var(--mint-primary, #3B82F6); color: #fff; cursor: pointer;">
78
+ <button type="button" style="padding: 0.25rem 0.75rem; font-size: 0.8125rem; border: 1px solid var(--color-primary); border-radius: 0.375rem; background: var(--color-primary); color: #fff; cursor: pointer;">
79
79
  Log
80
80
  </button>
81
81
  </div>
@@ -58,7 +58,3 @@ withDefaults(defineProps<Props>(), {
58
58
  </div>
59
59
  </div>
60
60
  </template>
61
-
62
- <style>
63
- @import '../styles/components/chart-container.css';
64
- </style>
@@ -33,7 +33,3 @@ function getParts(f: string): FormulaPart[] {
33
33
  </template>
34
34
  </component>
35
35
  </template>
36
-
37
- <style>
38
- @import '../styles/components/chemical-formula.css';
39
- </style>
@@ -239,7 +239,3 @@ function handleActionClick(action: SidebarToolSectionAction) {
239
239
  </Transition>
240
240
  </div>
241
241
  </template>
242
-
243
- <style>
244
- @import '../styles/components/collapsible-card.css';
245
- </style>
@@ -158,7 +158,3 @@ function handleInput(event: Event) {
158
158
  </div>
159
159
  </div>
160
160
  </template>
161
-
162
- <style>
163
- @import '../styles/components/color-slider.css';
164
- </style>
@@ -143,7 +143,3 @@ watch(availableUnits, (units) => {
143
143
  </div>
144
144
  </div>
145
145
  </template>
146
-
147
- <style>
148
- @import '../styles/components/concentration-input.css';
149
- </style>
@@ -94,7 +94,3 @@ const confirmVariant = computed<ButtonVariant>(() => {
94
94
  </template>
95
95
  </BaseModal>
96
96
  </template>
97
-
98
- <style>
99
- @import '../styles/components/confirm-dialog.css';
100
- </style>
@@ -37,6 +37,7 @@ const pagination = ref<PaginationState>({ page: 1, pageSize: 5, total: data.leng
37
37
  <template #default="{ state }">
38
38
  <div style="padding: 2rem; max-width: 900px; margin: 0 auto;">
39
39
  <DataFrame
40
+ :error="state.error || null"
40
41
  :data="data"
41
42
  :columns="columns"
42
43
  row-key="id"
@@ -55,6 +56,8 @@ const pagination = ref<PaginationState>({ page: 1, pageSize: 5, total: data.leng
55
56
  </template>
56
57
 
57
58
  <template #controls="{ state }">
59
+
60
+ <HstText v-model="state.error" title="Error Message" />
58
61
  <HstSelect
59
62
  v-model="state.size"
60
63
  title="Size"
@@ -144,5 +147,16 @@ const pagination = ref<PaginationState>({ page: 1, pageSize: 5, total: data.leng
144
147
  />
145
148
  </div>
146
149
  </Variant>
150
+
151
+ <Variant title="Error">
152
+ <div style="padding: 2rem; max-width: 900px; margin: 0 auto;">
153
+ <DataFrame
154
+ :data="data"
155
+ :columns="columns"
156
+ row-key="id"
157
+ error="Could not load the result table."
158
+ />
159
+ </div>
160
+ </Variant>
147
161
  </Story>
148
162
  </template>
@@ -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>