@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
@@ -1,3 +1,16 @@
1
+ import { SlotPosition } from '../types/componentLabTypes';
2
+ /**
3
+ * Canonical categorical scale for sample types, plate maps, dose-response
4
+ * series and legends. Mirrors `--mint-sample-1` … `--mint-sample-9`.
5
+ */
6
+ export declare const SAMPLE_COLOR_SCALE: readonly string[];
7
+ /**
8
+ * Slot-position colors shared by `PlateMapEditor` and `RackEditor`.
9
+ * Mirrors `--mint-slot-r`, `--mint-slot-g`, `--mint-slot-b`, `--mint-slot-y`.
10
+ */
11
+ export declare const SLOT_COLORS: Record<SlotPosition, string>;
12
+ /** Slot positions in their canonical display order. */
13
+ export declare const SLOT_ORDER: readonly SlotPosition[];
1
14
  /**
2
15
  * Color utilities for deriving same-hue-family shades.
3
16
  *
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The one place a thrown value is turned into a developer-facing message.
3
+ *
4
+ * Five copies of this logic drifted apart across the composables and stores
5
+ * (some checked a bare `{ message }` object, some did not); this export is the
6
+ * union of what they all accepted, so every caller now reads the same message
7
+ * from the same value.
8
+ */
9
+ export declare function readErrorMessage(value: unknown, fallback?: string): string;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Stable references for plugin secret settings.
3
+ *
4
+ * A settings response never carries a secret's value. Every secret leaf is a
5
+ * marker instead:
6
+ *
7
+ * - `{ "$secret": "sec_…", "set": true }` — a stored secret, referenced by a
8
+ * stable opaque id. Send the marker back unchanged to keep the stored value.
9
+ * - `null` — nothing stored at that position.
10
+ * - `{ "$secret": null, "locked": true }` — the effective value comes from the
11
+ * environment and is never written back.
12
+ *
13
+ * On save each secret leaf accepts the reference, a plain string (a new value),
14
+ * or `null` (clear). Payloads carry `secret_format: "ref"` so a `null` reads as
15
+ * a deliberate clear rather than the deprecated "unchanged" placeholder.
16
+ */
17
+ /** Reserved marker key that references a stored secret. */
18
+ export declare const PLUGIN_SECRET_REF_KEY = "$secret";
19
+ /** Reserved payload key declaring which secret wire format a client speaks. */
20
+ export declare const PLUGIN_SECRET_FORMAT_KEY = "secret_format";
21
+ /** Wire format published and understood by this SDK. */
22
+ export declare const PLUGIN_SECRET_FORMAT_REF = "ref";
23
+ /** One `$secret` marker as published by a settings response. */
24
+ export interface PluginSecretRef {
25
+ $secret: string | null;
26
+ set?: boolean;
27
+ locked?: boolean;
28
+ }
29
+ /** What a settings form should tell the user about one secret field. */
30
+ export type PluginSecretState = 'set' | 'unset' | 'environment';
31
+ /** Return whether one settings value is a `$secret` reference marker. */
32
+ export declare function isPluginSecretRef(value: unknown): value is PluginSecretRef;
33
+ /** Return the stored secret id behind a marker, or `null` when nothing is stored. */
34
+ export declare function pluginSecretId(value: unknown): string | null;
35
+ /** Classify one secret field for display: stored, unset, or environment-provided. */
36
+ export declare function pluginSecretState(value: unknown): PluginSecretState;
37
+ /** Human label for one secret field's state. */
38
+ export declare function pluginSecretLabel(value: unknown): string;
39
+ /** Whether a secret field is read-only because the environment supplies it. */
40
+ export declare function isPluginSecretLocked(value: unknown): boolean;
41
+ /**
42
+ * Value to submit for an untouched secret field: the reference, unchanged.
43
+ *
44
+ * A locked marker round-trips too, which is how the platform keeps a stored
45
+ * value that an environment variable currently hides.
46
+ */
47
+ export declare function keepPluginSecret(current: unknown): PluginSecretRef | null;
48
+ /** Value to submit when the user typed a new secret. */
49
+ export declare function setPluginSecret(value: string): string;
50
+ /** Value to submit when the user cleared a secret. */
51
+ export declare function clearPluginSecret(): null;
52
+ /** Whether a settings response declares the `$secret` reference wire format. */
53
+ export declare function usesPluginSecretReferences(response: unknown): boolean;
@@ -0,0 +1,4 @@
1
+ /** Narrow an unknown value to a plain object (arrays and null excluded). */
2
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
3
+ /** Narrow an unknown value to any non-null object, arrays included. */
4
+ export declare function isObjectLike(value: unknown): value is Record<string, unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morscherlab/mint-sdk",
3
- "version": "1.2.0-beta.1",
3
+ "version": "1.2.0-beta.11",
4
4
  "description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec INtegrated Toolkit.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -102,6 +102,100 @@ describe('ActionMenu', () => {
102
102
  expect(document.body.querySelector('.mint-action-menu__panel')).toBeNull()
103
103
  })
104
104
 
105
+ it('should open from the trigger keyboard shortcuts and focus the first item', async () => {
106
+ const wrapper = mount(ActionMenu, {
107
+ attachTo: document.body,
108
+ props: { label: 'More user actions', items },
109
+ })
110
+
111
+ await wrapper.get('button').trigger('keydown', { key: 'ArrowDown' })
112
+ await wrapper.vm.$nextTick()
113
+
114
+ const menuItems = document.body.querySelectorAll<HTMLElement>('.mint-action-menu__item')
115
+ expect(menuItems).toHaveLength(2)
116
+ expect(document.activeElement).toBe(menuItems[0])
117
+ })
118
+
119
+ it('should open focused on the last item with ArrowUp', async () => {
120
+ const wrapper = mount(ActionMenu, {
121
+ attachTo: document.body,
122
+ props: { label: 'More user actions', items },
123
+ })
124
+
125
+ await wrapper.get('button').trigger('keydown', { key: 'ArrowUp' })
126
+ await wrapper.vm.$nextTick()
127
+
128
+ const menuItems = document.body.querySelectorAll<HTMLElement>('.mint-action-menu__item')
129
+ expect(document.activeElement).toBe(menuItems[1])
130
+ })
131
+
132
+ it('should cycle items with the arrow keys and jump with Home/End', async () => {
133
+ const wrapper = mount(ActionMenu, {
134
+ attachTo: document.body,
135
+ props: { label: 'More user actions', items },
136
+ })
137
+ await wrapper.get('button').trigger('click')
138
+ await wrapper.vm.$nextTick()
139
+
140
+ const panel = document.body.querySelector<HTMLElement>('.mint-action-menu__panel')
141
+ const menuItems = document.body.querySelectorAll<HTMLElement>('.mint-action-menu__item')
142
+
143
+ function press(key: string) {
144
+ panel?.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true }))
145
+ }
146
+
147
+ press('ArrowDown')
148
+ expect(document.activeElement).toBe(menuItems[1])
149
+ press('ArrowDown')
150
+ expect(document.activeElement).toBe(menuItems[0])
151
+ press('End')
152
+ expect(document.activeElement).toBe(menuItems[1])
153
+ press('Home')
154
+ expect(document.activeElement).toBe(menuItems[0])
155
+ })
156
+
157
+ it('should close on Tab without forcing focus back to the trigger', async () => {
158
+ const wrapper = mount(ActionMenu, {
159
+ attachTo: document.body,
160
+ props: { label: 'More user actions', items },
161
+ })
162
+ await wrapper.get('button').trigger('click')
163
+
164
+ document.body.querySelector<HTMLElement>('.mint-action-menu__panel')?.dispatchEvent(
165
+ new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }),
166
+ )
167
+ await wrapper.vm.$nextTick()
168
+
169
+ expect(document.body.querySelector('.mint-action-menu__panel')).toBeNull()
170
+ expect(document.activeElement).not.toBe(wrapper.get('button').element)
171
+ })
172
+
173
+ it('should not open from the keyboard while disabled', async () => {
174
+ const wrapper = mount(ActionMenu, {
175
+ attachTo: document.body,
176
+ props: { label: 'More user actions', items, disabled: true },
177
+ })
178
+
179
+ await wrapper.get('button').trigger('keydown', { key: 'ArrowDown' })
180
+ await wrapper.vm.$nextTick()
181
+
182
+ expect(document.body.querySelector('.mint-action-menu__panel')).toBeNull()
183
+ })
184
+
185
+ it('should close when a click lands outside the trigger and panel', async () => {
186
+ const wrapper = mount(ActionMenu, {
187
+ attachTo: document.body,
188
+ props: { label: 'More user actions', items },
189
+ })
190
+ await wrapper.get('button').trigger('click')
191
+ expect(document.body.querySelector('.mint-action-menu__panel')).not.toBeNull()
192
+
193
+ document.body.dispatchEvent(new MouseEvent('click', { bubbles: true }))
194
+ await wrapper.vm.$nextTick()
195
+
196
+ expect(document.body.querySelector('.mint-action-menu__panel')).toBeNull()
197
+ })
198
+
105
199
  it('should return focus to the trigger when Escape closes the menu', async () => {
106
200
  const wrapper = mount(ActionMenu, {
107
201
  attachTo: document.body,
@@ -35,4 +35,127 @@ describe('AppAvatarMenu', () => {
35
35
  expect(wrapper.emitted('select')).toEqual([[docs]])
36
36
  expect(wrapper.find('.mint-avatar-menu__panel').exists()).toBe(false)
37
37
  })
38
+
39
+ describe('keyboard handling (menu button pattern)', () => {
40
+ const items = [{ id: 'profile', label: 'Profile' }, { id: 'settings', label: 'Settings' }]
41
+
42
+ function mountMenu() {
43
+ return mount(AppAvatarMenu, {
44
+ props: { userName: 'Ada Lovelace', items, showSignOut: true },
45
+ attachTo: document.body,
46
+ })
47
+ }
48
+
49
+ it('sets aria-expanded on the trigger and toggles it open/closed', async () => {
50
+ const wrapper = mountMenu()
51
+ const trigger = wrapper.get('.mint-avatar-menu__trigger')
52
+ expect(trigger.attributes('aria-expanded')).toBe('false')
53
+
54
+ await trigger.trigger('click')
55
+ expect(trigger.attributes('aria-expanded')).toBe('true')
56
+
57
+ wrapper.unmount()
58
+ })
59
+
60
+ it('opens and focuses the first item on ArrowDown', async () => {
61
+ const wrapper = mountMenu()
62
+ await wrapper.get('.mint-avatar-menu__trigger').trigger('keydown', { key: 'ArrowDown' })
63
+ await wrapper.vm.$nextTick()
64
+
65
+ expect(wrapper.find('.mint-avatar-menu__panel').exists()).toBe(true)
66
+ expect(document.activeElement).toBe(wrapper.findAll('[role="menuitem"]')[0].element)
67
+
68
+ wrapper.unmount()
69
+ })
70
+
71
+ it('opens and focuses the last item on ArrowUp', async () => {
72
+ const wrapper = mountMenu()
73
+ await wrapper.get('.mint-avatar-menu__trigger').trigger('keydown', { key: 'ArrowUp' })
74
+ await wrapper.vm.$nextTick()
75
+
76
+ const menuItems = wrapper.findAll('[role="menuitem"]')
77
+ expect(document.activeElement).toBe(menuItems[menuItems.length - 1].element)
78
+
79
+ wrapper.unmount()
80
+ })
81
+
82
+ it('opens and focuses the first item on Enter and Space', async () => {
83
+ for (const key of ['Enter', ' ']) {
84
+ const wrapper = mountMenu()
85
+ await wrapper.get('.mint-avatar-menu__trigger').trigger('keydown', { key })
86
+ await wrapper.vm.$nextTick()
87
+
88
+ expect(document.activeElement).toBe(wrapper.findAll('[role="menuitem"]')[0].element)
89
+ wrapper.unmount()
90
+ }
91
+ })
92
+
93
+ it('cycles item focus with ArrowDown/ArrowUp, wrapping at the ends', async () => {
94
+ const wrapper = mountMenu()
95
+ await wrapper.get('.mint-avatar-menu__trigger').trigger('keydown', { key: 'ArrowDown' })
96
+ await wrapper.vm.$nextTick()
97
+
98
+ const menuItems = wrapper.findAll('[role="menuitem"]')
99
+ const panel = wrapper.get('.mint-avatar-menu__panel')
100
+
101
+ await panel.trigger('keydown', { key: 'ArrowDown' })
102
+ expect(document.activeElement).toBe(menuItems[1].element)
103
+
104
+ await panel.trigger('keydown', { key: 'ArrowDown' })
105
+ expect(document.activeElement).toBe(menuItems[menuItems.length - 1].element)
106
+
107
+ await panel.trigger('keydown', { key: 'ArrowDown' })
108
+ expect(document.activeElement).toBe(menuItems[0].element)
109
+
110
+ await panel.trigger('keydown', { key: 'ArrowUp' })
111
+ expect(document.activeElement).toBe(menuItems[menuItems.length - 1].element)
112
+
113
+ wrapper.unmount()
114
+ })
115
+
116
+ it('jumps to the first item on Home and the last on End', async () => {
117
+ const wrapper = mountMenu()
118
+ await wrapper.get('.mint-avatar-menu__trigger').trigger('keydown', { key: 'ArrowDown' })
119
+ await wrapper.vm.$nextTick()
120
+
121
+ const menuItems = wrapper.findAll('[role="menuitem"]')
122
+ const panel = wrapper.get('.mint-avatar-menu__panel')
123
+
124
+ await panel.trigger('keydown', { key: 'End' })
125
+ expect(document.activeElement).toBe(menuItems[menuItems.length - 1].element)
126
+
127
+ await panel.trigger('keydown', { key: 'Home' })
128
+ expect(document.activeElement).toBe(menuItems[0].element)
129
+
130
+ wrapper.unmount()
131
+ })
132
+
133
+ it('closes on Escape and returns focus to the trigger', async () => {
134
+ const wrapper = mountMenu()
135
+ const trigger = wrapper.get('.mint-avatar-menu__trigger')
136
+ await trigger.trigger('keydown', { key: 'ArrowDown' })
137
+ await wrapper.vm.$nextTick()
138
+
139
+ await wrapper.get('.mint-avatar-menu__panel').trigger('keydown', { key: 'Escape' })
140
+ await wrapper.vm.$nextTick()
141
+
142
+ expect(wrapper.find('.mint-avatar-menu__panel').exists()).toBe(false)
143
+ expect(document.activeElement).toBe(trigger.element)
144
+
145
+ wrapper.unmount()
146
+ })
147
+
148
+ it('closes on Tab without forcing focus', async () => {
149
+ const wrapper = mountMenu()
150
+ await wrapper.get('.mint-avatar-menu__trigger').trigger('keydown', { key: 'ArrowDown' })
151
+ await wrapper.vm.$nextTick()
152
+
153
+ await wrapper.get('.mint-avatar-menu__panel').trigger('keydown', { key: 'Tab' })
154
+ await wrapper.vm.$nextTick()
155
+
156
+ expect(wrapper.find('.mint-avatar-menu__panel').exists()).toBe(false)
157
+
158
+ wrapper.unmount()
159
+ })
160
+ })
38
161
  })
@@ -37,4 +37,71 @@ describe('BaseTabs', () => {
37
37
  expect(wrapper.findAll('svg.mint-tab__icon--svg')).toHaveLength(2)
38
38
  expect(wrapper.findAll('path')).toHaveLength(3)
39
39
  })
40
+
41
+ describe('keyboard navigation (WAI-ARIA tabs pattern)', () => {
42
+ const tabs = ['Run', 'Results', { id: 'settings', label: 'Settings', disabled: true }, 'Logs']
43
+
44
+ it('puts tabindex="0" only on the active tab', () => {
45
+ const wrapper = mount(BaseTabs, { props: { modelValue: 'Results', tabs } })
46
+ const buttons = wrapper.findAll('.mint-tab')
47
+
48
+ expect(buttons.map(b => b.attributes('tabindex'))).toEqual(['-1', '0', '-1', '-1'])
49
+ })
50
+
51
+ it('moves to and activates the next enabled tab on ArrowRight, skipping disabled tabs', async () => {
52
+ const wrapper = mount(BaseTabs, { props: { modelValue: 'Results', tabs } })
53
+
54
+ await wrapper.findAll('.mint-tab')[1].trigger('keydown', { key: 'ArrowRight' })
55
+
56
+ expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['Logs'])
57
+ })
58
+
59
+ it('moves to the previous enabled tab on ArrowLeft, wrapping past the start', async () => {
60
+ const wrapper = mount(BaseTabs, { props: { modelValue: 'Run', tabs } })
61
+
62
+ await wrapper.findAll('.mint-tab')[0].trigger('keydown', { key: 'ArrowLeft' })
63
+
64
+ expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['Logs'])
65
+ })
66
+
67
+ it('wraps past the end on ArrowRight', async () => {
68
+ const wrapper = mount(BaseTabs, { props: { modelValue: 'Logs', tabs } })
69
+
70
+ await wrapper.findAll('.mint-tab')[3].trigger('keydown', { key: 'ArrowRight' })
71
+
72
+ expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['Run'])
73
+ })
74
+
75
+ it('jumps to the first tab on Home and the last on End', async () => {
76
+ const wrapper = mount(BaseTabs, { props: { modelValue: 'Results', tabs } })
77
+
78
+ await wrapper.findAll('.mint-tab')[1].trigger('keydown', { key: 'End' })
79
+ expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['Logs'])
80
+
81
+ await wrapper.findAll('.mint-tab')[1].trigger('keydown', { key: 'Home' })
82
+ expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['Run'])
83
+ })
84
+
85
+ it('moves DOM focus onto the newly-activated tab', async () => {
86
+ const wrapper = mount(BaseTabs, {
87
+ props: { modelValue: 'Run', tabs },
88
+ attachTo: document.body,
89
+ })
90
+
91
+ await wrapper.findAll('.mint-tab')[0].trigger('keydown', { key: 'ArrowRight' })
92
+ await wrapper.setProps({ modelValue: 'Results' })
93
+ await wrapper.vm.$nextTick()
94
+
95
+ expect(document.activeElement).toBe(wrapper.findAll('.mint-tab')[1].element)
96
+ wrapper.unmount()
97
+ })
98
+
99
+ it('keeps aria-selected and aria-controls consistent with the active tab', () => {
100
+ const wrapper = mount(BaseTabs, { props: { modelValue: 'Results', tabs } })
101
+ const active = wrapper.findAll('.mint-tab')[1]
102
+
103
+ expect(active.attributes('aria-selected')).toBe('true')
104
+ expect(active.attributes('id')).toBe(active.attributes('aria-controls')?.replace('tabpanel', 'tab'))
105
+ })
106
+ })
40
107
  })
@@ -1,6 +1,8 @@
1
- import { describe, it, expect } from 'vitest'
1
+ import { afterEach, describe, it, expect } from 'vitest'
2
2
  import { mount } from '@vue/test-utils'
3
+ import { createPinia, setActivePinia } from 'pinia'
3
4
  import DataFrame from '../../components/DataFrame.vue'
5
+ import { useSettingsStore } from '../../stores/settings'
4
6
  import type { DataFrameColumn, SortState, PaginationState } from '../../types'
5
7
 
6
8
  describe('DataFrame', () => {
@@ -167,7 +169,7 @@ describe('DataFrame', () => {
167
169
  props: { data: mockData, columns: sortableColumns },
168
170
  })
169
171
  const nameHeader = wrapper.findAll('.mint-dataframe__th')[0]
170
- await nameHeader.trigger('click')
172
+ await nameHeader.get('.mint-dataframe__th-button').trigger('click')
171
173
 
172
174
  expect(wrapper.emitted('update:sort')).toHaveLength(1)
173
175
  const sortState = wrapper.emitted('update:sort')?.[0]?.[0] as SortState
@@ -184,7 +186,7 @@ describe('DataFrame', () => {
184
186
  },
185
187
  })
186
188
  const nameHeader = wrapper.findAll('.mint-dataframe__th')[0]
187
- await nameHeader.trigger('click')
189
+ await nameHeader.get('.mint-dataframe__th-button').trigger('click')
188
190
 
189
191
  const sortState = wrapper.emitted('update:sort')?.[0]?.[0] as SortState
190
192
  expect(sortState.direction).toBe('desc')
@@ -199,7 +201,7 @@ describe('DataFrame', () => {
199
201
  },
200
202
  })
201
203
  const nameHeader = wrapper.findAll('.mint-dataframe__th')[0]
202
- await nameHeader.trigger('click')
204
+ await nameHeader.get('.mint-dataframe__th-button').trigger('click')
203
205
 
204
206
  const sortState = wrapper.emitted('update:sort')?.[0]?.[0]
205
207
  expect(sortState).toBeNull()
@@ -210,7 +212,7 @@ describe('DataFrame', () => {
210
212
  props: { data: mockData, columns: sortableColumns },
211
213
  })
212
214
  const nameHeader = wrapper.findAll('.mint-dataframe__th')[0]
213
- await nameHeader.trigger('click')
215
+ await nameHeader.get('.mint-dataframe__th-button').trigger('click')
214
216
 
215
217
  // After sorting, first row should have name starting with 'A'
216
218
  const firstRow = wrapper.findAll('.mint-dataframe__row')[0]
@@ -223,7 +225,7 @@ describe('DataFrame', () => {
223
225
  props: { data: mockData, columns: sortableColumns },
224
226
  })
225
227
  const ageHeader = wrapper.findAll('.mint-dataframe__th')[1]
226
- await ageHeader.trigger('click')
228
+ await ageHeader.get('.mint-dataframe__th-button').trigger('click')
227
229
 
228
230
  // After sorting ascending, first row should have age 25
229
231
  const firstRow = wrapper.findAll('.mint-dataframe__row')[0]
@@ -553,28 +555,38 @@ describe('DataFrame', () => {
553
555
  const wrapper = mount(DataFrame, {
554
556
  props: { data: [], columns: mockColumns },
555
557
  })
556
- expect(wrapper.find('.mint-dataframe__empty').exists()).toBe(true)
558
+ expect(wrapper.find('.mint-dataframe__state').exists()).toBe(true)
559
+ expect(wrapper.find('[data-state="empty"]').exists()).toBe(true)
557
560
  })
558
561
 
559
562
  it('should display empty text', () => {
560
563
  const wrapper = mount(DataFrame, {
561
564
  props: { data: [], columns: mockColumns },
562
565
  })
563
- expect(wrapper.find('.mint-dataframe__empty-text').text()).toBe('No data')
566
+ expect(wrapper.find('.mint-empty-state__title').text()).toBe('No data')
564
567
  })
565
568
 
566
569
  it('should use custom empty text', () => {
567
570
  const wrapper = mount(DataFrame, {
568
571
  props: { data: [], columns: mockColumns, emptyText: 'No records found' },
569
572
  })
570
- expect(wrapper.find('.mint-dataframe__empty-text').text()).toBe('No records found')
573
+ expect(wrapper.find('.mint-empty-state__title').text()).toBe('No records found')
571
574
  })
572
575
 
573
576
  it('should not show empty state when loading', () => {
574
577
  const wrapper = mount(DataFrame, {
575
578
  props: { data: [], columns: mockColumns, loading: true },
576
579
  })
577
- expect(wrapper.find('.mint-dataframe__empty').exists()).toBe(false)
580
+ expect(wrapper.find('[data-state="empty"]').exists()).toBe(false)
581
+ })
582
+
583
+ it('should render the empty slot instead of the default placeholder', () => {
584
+ const wrapper = mount(DataFrame, {
585
+ props: { data: [], columns: mockColumns },
586
+ slots: { empty: '<p class="custom-empty">Nothing here</p>' },
587
+ })
588
+ expect(wrapper.find('.custom-empty').exists()).toBe(true)
589
+ expect(wrapper.find('[data-state="empty"]').exists()).toBe(false)
578
590
  })
579
591
  })
580
592
 
@@ -590,7 +602,40 @@ describe('DataFrame', () => {
590
602
  const wrapper = mount(DataFrame, {
591
603
  props: { data: mockData, columns: mockColumns, loading: true },
592
604
  })
593
- expect(wrapper.find('.mint-dataframe__loading-spinner').exists()).toBe(true)
605
+ expect(wrapper.find('.mint-spinner').exists()).toBe(true)
606
+ })
607
+ })
608
+
609
+ describe('error state', () => {
610
+ it('should render nothing extra when error is not passed', () => {
611
+ const wrapper = mount(DataFrame, {
612
+ props: { data: mockData, columns: mockColumns },
613
+ })
614
+ expect(wrapper.find('[data-state="error"]').exists()).toBe(false)
615
+ })
616
+
617
+ it('should replace the empty state with an error state', () => {
618
+ const wrapper = mount(DataFrame, {
619
+ props: { data: [], columns: mockColumns, error: 'Request failed' },
620
+ })
621
+ expect(wrapper.find('[data-state="error"]').exists()).toBe(true)
622
+ expect(wrapper.find('[data-state="empty"]').exists()).toBe(false)
623
+ expect(wrapper.find('.mint-empty-state__title').text()).toBe('Request failed')
624
+ })
625
+
626
+ it('should take precedence over the empty state even with rows present', () => {
627
+ const wrapper = mount(DataFrame, {
628
+ props: { data: mockData, columns: mockColumns, error: 'Boom' },
629
+ })
630
+ expect(wrapper.find('[data-state="error"]').exists()).toBe(true)
631
+ })
632
+
633
+ it('should render the error slot with the message', () => {
634
+ const wrapper = mount(DataFrame, {
635
+ props: { data: [], columns: mockColumns, error: 'Boom' },
636
+ slots: { error: '<p class="custom-error">custom error</p>' },
637
+ })
638
+ expect(wrapper.find('.custom-error').exists()).toBe(true)
594
639
  })
595
640
  })
596
641
 
@@ -911,5 +956,132 @@ describe('DataFrame', () => {
911
956
  await wrapper.find('.mint-dataframe__resize-handle').trigger('click')
912
957
  expect(wrapper.emitted('update:sort')).toBeUndefined()
913
958
  })
959
+
960
+ it('should expose the resize handle as a slider with its current width', () => {
961
+ const wrapper = mount(DataFrame, {
962
+ props: {
963
+ data: mockData,
964
+ columns: [{ key: 'name', label: 'Name', minWidth: 80 }],
965
+ resizable: true,
966
+ columnWidths: { name: 240 },
967
+ },
968
+ })
969
+
970
+ const handle = wrapper.get('.mint-dataframe__resize-handle')
971
+ expect(handle.attributes('role')).toBe('slider')
972
+ expect(handle.attributes('tabindex')).toBe('0')
973
+ expect(handle.attributes('aria-label')).toBe('Resize Name column')
974
+ expect(handle.attributes('aria-valuenow')).toBe('240')
975
+ expect(handle.attributes('aria-valuemin')).toBe('80')
976
+ })
977
+
978
+ it('should resize a column with the arrow keys, clamped to the minimum width', async () => {
979
+ const wrapper = mount(DataFrame, {
980
+ props: {
981
+ data: mockData,
982
+ columns: [{ key: 'name', label: 'Name' }],
983
+ resizable: true,
984
+ columnWidths: { name: 120 },
985
+ },
986
+ })
987
+
988
+ const handle = wrapper.get('.mint-dataframe__resize-handle')
989
+ await handle.trigger('keydown', { key: 'ArrowRight' })
990
+ expect(wrapper.emitted('update:columnWidths')?.[0]).toEqual([{ name: 136 }])
991
+
992
+ await handle.trigger('keydown', { key: 'ArrowLeft' })
993
+ expect(wrapper.emitted('update:columnWidths')?.[1]).toEqual([{ name: 104 }])
994
+
995
+ await wrapper.setProps({ columnWidths: { name: 50 } })
996
+ await handle.trigger('keydown', { key: 'ArrowLeft' })
997
+ expect(wrapper.emitted('update:columnWidths')?.[2]).toEqual([{ name: 48 }])
998
+ })
999
+ })
1000
+
1001
+ describe('sortable header accessibility', () => {
1002
+ const sortableColumn = [{ key: 'name', label: 'Name', sortable: true }]
1003
+
1004
+ it('should render sortable headers as buttons and plain headers without one', () => {
1005
+ const wrapper = mount(DataFrame, {
1006
+ props: {
1007
+ data: mockData,
1008
+ columns: [...sortableColumn, { key: 'age', label: 'Age' }],
1009
+ },
1010
+ })
1011
+
1012
+ const headers = wrapper.findAll('.mint-dataframe__th')
1013
+ expect(headers[0].get('.mint-dataframe__th-button').element.tagName).toBe('BUTTON')
1014
+ expect(headers[1].get('.mint-dataframe__th-button').element.tagName).toBe('DIV')
1015
+ })
1016
+
1017
+ it('should reflect the sort direction with aria-sort', async () => {
1018
+ const wrapper = mount(DataFrame, {
1019
+ props: {
1020
+ data: mockData,
1021
+ columns: [...sortableColumn, { key: 'age', label: 'Age' }],
1022
+ },
1023
+ })
1024
+
1025
+ const nameHeader = () => wrapper.findAll('.mint-dataframe__th')[0]
1026
+ expect(nameHeader().attributes('aria-sort')).toBe('none')
1027
+ expect(wrapper.findAll('.mint-dataframe__th')[1].attributes('aria-sort')).toBeUndefined()
1028
+
1029
+ await nameHeader().get('.mint-dataframe__th-button').trigger('click')
1030
+ expect(nameHeader().attributes('aria-sort')).toBe('ascending')
1031
+
1032
+ await nameHeader().get('.mint-dataframe__th-button').trigger('click')
1033
+ expect(nameHeader().attributes('aria-sort')).toBe('descending')
1034
+
1035
+ await nameHeader().get('.mint-dataframe__th-button').trigger('click')
1036
+ expect(nameHeader().attributes('aria-sort')).toBe('none')
1037
+ })
1038
+
1039
+ it('should sort from the keyboard with Enter and Space', async () => {
1040
+ const wrapper = mount(DataFrame, {
1041
+ props: { data: mockData, columns: sortableColumn },
1042
+ })
1043
+
1044
+ const button = wrapper.get('.mint-dataframe__th-button')
1045
+ await button.trigger('keydown', { key: 'Enter' })
1046
+ expect(wrapper.emitted('update:sort')?.[0]?.[0]).toEqual({ key: 'name', direction: 'asc' })
1047
+
1048
+ await button.trigger('keydown', { key: ' ' })
1049
+ expect(wrapper.emitted('update:sort')?.[1]?.[0]).toEqual({ key: 'name', direction: 'desc' })
1050
+ })
1051
+ })
1052
+
1053
+ describe('table density default', () => {
1054
+ afterEach(() => {
1055
+ setActivePinia(undefined)
1056
+ })
1057
+
1058
+ it('falls back to md when no settings store is installed', () => {
1059
+ setActivePinia(undefined)
1060
+ const wrapper = mount(DataFrame, {
1061
+ props: { data: mockData, columns: mockColumns },
1062
+ })
1063
+ expect(wrapper.find('.mint-dataframe__td--md').exists()).toBe(true)
1064
+ })
1065
+
1066
+ it('follows the tableDensity setting when size is not passed', () => {
1067
+ setActivePinia(createPinia())
1068
+ useSettingsStore().tableDensity = 'compact'
1069
+
1070
+ const wrapper = mount(DataFrame, {
1071
+ props: { data: mockData, columns: mockColumns },
1072
+ })
1073
+ expect(wrapper.find('.mint-dataframe__td--sm').exists()).toBe(true)
1074
+ expect(wrapper.find('.mint-dataframe__td--md').exists()).toBe(false)
1075
+ })
1076
+
1077
+ it('lets an explicit size prop win over the density setting', () => {
1078
+ setActivePinia(createPinia())
1079
+ useSettingsStore().tableDensity = 'compact'
1080
+
1081
+ const wrapper = mount(DataFrame, {
1082
+ props: { data: mockData, columns: mockColumns, size: 'lg' },
1083
+ })
1084
+ expect(wrapper.find('.mint-dataframe__td--lg').exists()).toBe(true)
1085
+ })
914
1086
  })
915
1087
  })