@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
@@ -3,6 +3,11 @@
3
3
  import { h, Transition, type VNode } from 'vue'
4
4
  import type { TreeNodeType, BadgeVariant, TreeNode } from '../types'
5
5
  import { useExpansionSet } from '../composables/useExpansionSet'
6
+ import DataStateInternal from './internal/DataStateInternal.vue'
7
+
8
+ /** Folder glyph kept from the pre-1.2 hand-rolled empty state. */
9
+ const EMPTY_ICON_PATH
10
+ = 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z'
6
11
 
7
12
  interface Props {
8
13
  nodes: TreeNode[]
@@ -12,6 +17,13 @@ interface Props {
12
17
  showCounts?: boolean
13
18
  maxDepth?: number
14
19
  size?: 'sm' | 'md' | 'lg'
20
+ /** Replace the tree with a loading placeholder. */
21
+ loading?: boolean
22
+ /**
23
+ * Error message. When set, the tree is replaced by an error state.
24
+ * `null`/`undefined` renders normally.
25
+ */
26
+ error?: string | null
15
27
  }
16
28
 
17
29
  const props = withDefaults(defineProps<Props>(), {
@@ -20,6 +32,8 @@ const props = withDefaults(defineProps<Props>(), {
20
32
  showIcons: true,
21
33
  showCounts: true,
22
34
  size: 'md',
35
+ loading: false,
36
+ error: null,
23
37
  })
24
38
 
25
39
  const emit = defineEmits<{
@@ -226,12 +240,25 @@ function renderTree(): VNode[] {
226
240
 
227
241
  <template>
228
242
  <div :class="['mint-sample-tree', `mint-sample-tree--${size}`]" role="tree">
243
+ <!-- Error state -->
244
+ <div v-if="error" class="mint-sample-tree__state">
245
+ <slot name="error" :error="error">
246
+ <DataStateInternal state="error" size="sm" :title="error" />
247
+ </slot>
248
+ </div>
249
+
250
+ <!-- Loading state -->
251
+ <div v-else-if="loading" class="mint-sample-tree__state">
252
+ <slot name="loading">
253
+ <DataStateInternal state="loading" size="sm" loading-label="Loading samples" />
254
+ </slot>
255
+ </div>
256
+
229
257
  <!-- Empty state -->
230
- <div v-if="nodes.length === 0" class="mint-sample-tree__empty">
231
- <svg class="mint-sample-tree__empty-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
232
- <path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
233
- </svg>
234
- <p class="mint-sample-tree__empty-text">No items</p>
258
+ <div v-else-if="nodes.length === 0" class="mint-sample-tree__state">
259
+ <slot name="empty">
260
+ <DataStateInternal state="empty" size="sm" title="No items" :icon-path="EMPTY_ICON_PATH" />
261
+ </slot>
235
262
  </div>
236
263
 
237
264
  <!-- Tree nodes rendered via render function -->
@@ -240,7 +267,3 @@ function renderTree(): VNode[] {
240
267
  </template>
241
268
  </div>
242
269
  </template>
243
-
244
- <style>
245
- @import '../styles/components/sample-hierarchy-tree.css';
246
- </style>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  /** Clickable color-swatch legend for plate sample types, supporting selection, count display, add/remove, and custom color assignment. */
3
3
  import type { SampleType } from '../types'
4
+ import { SAMPLE_COLOR_SCALE } from '../utils/color'
4
5
 
5
6
  interface Props {
6
7
  modelValue?: string
@@ -17,10 +18,7 @@ const props = withDefaults(defineProps<Props>(), {
17
18
  samples: () => [],
18
19
  showCounts: true,
19
20
  editable: false,
20
- colorPalette: () => [
21
- '#3B82F6', '#10B981', '#EF4444', '#F59E0B', '#8B5CF6',
22
- '#F97316', '#06B6D4', '#14B8A6', '#6B7280',
23
- ],
21
+ colorPalette: () => [...SAMPLE_COLOR_SCALE],
24
22
  size: 'md',
25
23
  orientation: 'vertical',
26
24
  })
@@ -113,7 +111,3 @@ function handleAdd() {
113
111
  </button>
114
112
  </div>
115
113
  </template>
116
-
117
- <style>
118
- @import '../styles/components/sample-legend.css';
119
- </style>
@@ -1,5 +1,7 @@
1
1
  import { ref } from 'vue'
2
2
  import type { SampleGroup } from '../types'
3
+ import { useReorderAnnouncer } from '../composables/useListReorder'
4
+ import { getSampleGroupMajorPrefix } from '../utils/sampleGroupPath'
3
5
  import {
4
6
  detectSampleGroupSeparator,
5
7
  getSampleGroupParentPath,
@@ -23,6 +25,7 @@ export function useSampleSelectorDrag(groups: MutableSampleGroups) {
23
25
  const draggingGroupKind = ref<GroupDragKind | null>(null)
24
26
  const reorderTarget = ref<string | null>(null)
25
27
  const reorderPosition = ref<GroupReorderPosition | null>(null)
28
+ const { announcement, announce, announcerStyle } = useReorderAnnouncer()
26
29
 
27
30
  function handleDragStart(sample: string, sourceGroup: string | null, event: DragEvent) {
28
31
  draggingSample.value = sample
@@ -143,7 +146,45 @@ export function useSampleSelectorDrag(groups: MutableSampleGroups) {
143
146
  handleGroupDragEnd()
144
147
  }
145
148
 
149
+ /**
150
+ * Keyboard alternative to the group reorder drag. The hierarchy constrains a
151
+ * move to the dragged node's siblings (a leaf may not cross a parent), so this
152
+ * is not a flat list reorder and does not use `useListReorder`: Alt+ArrowUp /
153
+ * Alt+ArrowDown swap the node with its previous / next sibling instead.
154
+ */
155
+ function handleGroupReorderKeydown(name: string, kind: GroupDragKind, event: KeyboardEvent) {
156
+ if (!event.altKey) return
157
+ const direction = event.key === 'ArrowUp' ? -1 : event.key === 'ArrowDown' ? 1 : 0
158
+ if (direction === 0) return
159
+
160
+ event.preventDefault()
161
+ const separator = detectSampleGroupSeparator(groups.value)
162
+ const siblings = kind === 'major'
163
+ ? uniqueMajorPrefixes(groups.value, separator)
164
+ : groups.value
165
+ .filter(group =>
166
+ getSampleGroupParentPath(group.name, separator) ===
167
+ getSampleGroupParentPath(name, separator))
168
+ .map(group => group.name)
169
+
170
+ const currentIndex = siblings.indexOf(name)
171
+ const target = siblings[currentIndex + direction]
172
+ if (currentIndex === -1 || target === undefined) return
173
+
174
+ const position: GroupReorderPosition = direction === -1 ? 'before' : 'after'
175
+ const nextGroups = kind === 'major'
176
+ ? reorderSampleMajorGroup(groups.value, name, target, position)
177
+ : reorderSampleGroup(groups.value, name, target, position)
178
+
179
+ if (nextGroups === groups.value) return
180
+ groups.value = nextGroups
181
+ announce(`${name} moved ${direction === -1 ? 'up' : 'down'} to position ${currentIndex + direction + 1} of ${siblings.length}.`)
182
+ }
183
+
146
184
  return {
185
+ announcement,
186
+ announcerStyle,
187
+ handleGroupReorderKeydown,
147
188
  draggingSample,
148
189
  dragOverGroup,
149
190
  draggingGroup,
@@ -162,3 +203,13 @@ export function useSampleSelectorDrag(groups: MutableSampleGroups) {
162
203
  handleGroupDrop,
163
204
  }
164
205
  }
206
+
207
+ /** Major-group prefixes in tree order, de-duplicated. */
208
+ function uniqueMajorPrefixes(groups: SampleGroup[], separator: string): string[] {
209
+ const seen: string[] = []
210
+ for (const group of groups) {
211
+ const prefix = getSampleGroupMajorPrefix(group.name, separator)
212
+ if (!seen.includes(prefix)) seen.push(prefix)
213
+ }
214
+ return seen
215
+ }
@@ -72,6 +72,8 @@ const lcmsSamples = ['Liver', 'Kidney'].flatMap(tissue =>
72
72
 
73
73
  function initState() {
74
74
  return {
75
+ loading: false,
76
+ error: '',
75
77
  samples: mockSamples,
76
78
  selected: [] as string[],
77
79
  groups: [] as SampleGroup[],
@@ -100,6 +102,8 @@ function handleSmartGroup(result: AutoGroupResult, state: { groups: SampleGroup[
100
102
  <template #default="{ state }">
101
103
  <div style="padding: 2rem; max-width: 400px;">
102
104
  <SampleSelector
105
+ :loading="state.loading"
106
+ :error="state.error || null"
103
107
  v-model="state.selected"
104
108
  v-model:groups="state.groups"
105
109
  :samples="state.samples"
@@ -110,6 +114,8 @@ function handleSmartGroup(result: AutoGroupResult, state: { groups: SampleGroup[
110
114
  </div>
111
115
  </template>
112
116
  <template #controls="{ state }">
117
+ <HstCheckbox v-model="state.loading" title="Loading" />
118
+ <HstText v-model="state.error" title="Error Message" />
113
119
  <HstCheckbox v-model="state.enableGrouping" title="Enable Grouping" />
114
120
  <HstCheckbox v-model="state.enableSmartGroup" title="Enable Smart" />
115
121
  </template>
@@ -34,6 +34,8 @@ export interface SampleGroupTreeContext {
34
34
  onGroupDragOver: (name: string, kind: GroupDragKind, event: DragEvent) => void
35
35
  onGroupDragLeave: (event: DragEvent) => void
36
36
  onGroupDrop: (name: string, kind: GroupDragKind, event: DragEvent) => void
37
+ /** Alt+ArrowUp / Alt+ArrowDown reorder a group among its siblings. */
38
+ onGroupReorderKeydown: (name: string, kind: GroupDragKind, event: KeyboardEvent) => void
37
39
  }
38
40
 
39
41
  /** Every concrete group name beneath a node, in tree order. */
@@ -25,6 +25,7 @@ import {
25
25
  removeSampleFromGroup as removeSampleFromSampleGroup,
26
26
  removeSampleGroups,
27
27
  } from './SampleSelector.groups'
28
+ import DataStateInternal from './internal/DataStateInternal.vue'
28
29
 
29
30
  interface Props {
30
31
  samples?: string[]
@@ -35,6 +36,15 @@ interface Props {
35
36
  experimentId?: number
36
37
  designData?: Record<string, unknown>
37
38
  autoloadExperimentData?: boolean
39
+ /** Replace the selector body with a loading placeholder. */
40
+ loading?: boolean
41
+ /**
42
+ * Error message. When set, the selector body is replaced by an error state.
43
+ * `null`/`undefined` renders normally.
44
+ */
45
+ error?: string | null
46
+ /** Empty-state headline shown when there are no samples to select from. */
47
+ emptyMessage?: string
38
48
  }
39
49
 
40
50
  const props = withDefaults(defineProps<Props>(), {
@@ -45,6 +55,9 @@ const props = withDefaults(defineProps<Props>(), {
45
55
  experimentId: undefined,
46
56
  designData: undefined,
47
57
  autoloadExperimentData: true,
58
+ loading: false,
59
+ error: null,
60
+ emptyMessage: 'No samples available',
48
61
  })
49
62
 
50
63
  const emit = defineEmits<{
@@ -69,6 +82,8 @@ const internalGroups = computed({
69
82
  })
70
83
 
71
84
  const {
85
+ announcement: reorderAnnouncement,
86
+ announcerStyle,
72
87
  draggingSample,
73
88
  dragOverGroup,
74
89
  draggingGroup,
@@ -81,6 +96,7 @@ const {
81
96
  handleDragLeave,
82
97
  handleDrop,
83
98
  handleGroupDragStart,
99
+ handleGroupReorderKeydown,
84
100
  handleGroupDragEnd,
85
101
  handleGroupDragOver,
86
102
  handleGroupDragLeave,
@@ -227,6 +243,7 @@ const treeContext = computed<SampleGroupTreeContext>(() => ({
227
243
  onGroupDragOver: handleGroupDragOver,
228
244
  onGroupDragLeave: handleGroupDragLeave,
229
245
  onGroupDrop: handleGroupDrop,
246
+ onGroupReorderKeydown: handleGroupReorderKeydown,
230
247
  }))
231
248
 
232
249
  // New group
@@ -237,13 +254,36 @@ function addNewGroup() {
237
254
  newGroupName.value = ''
238
255
  }
239
256
 
257
+ /** True while a loading, error or empty placeholder stands in for the body. */
258
+ const isPlaceholderState = computed(() =>
259
+ props.loading || Boolean(props.error) || resolvedSamples.value.length === 0,
260
+ )
261
+
240
262
  defineExpose({ handleSmartGroupApply })
241
263
  </script>
242
264
 
243
265
  <template>
244
266
  <div class="mint-sample-selector">
267
+ <div v-if="error" class="mint-sample-selector__state">
268
+ <slot name="error" :error="error">
269
+ <DataStateInternal state="error" size="sm" :title="error" />
270
+ </slot>
271
+ </div>
272
+
273
+ <div v-else-if="loading" class="mint-sample-selector__state">
274
+ <slot name="loading">
275
+ <DataStateInternal state="loading" size="sm" loading-label="Loading samples" />
276
+ </slot>
277
+ </div>
278
+
279
+ <div v-else-if="resolvedSamples.length === 0" class="mint-sample-selector__state">
280
+ <slot name="empty">
281
+ <DataStateInternal state="empty" size="sm" :title="emptyMessage" />
282
+ </slot>
283
+ </div>
284
+
245
285
  <!-- Select All Row -->
246
- <label class="mint-sample-selector__select-all">
286
+ <label v-if="!isPlaceholderState" class="mint-sample-selector__select-all">
247
287
  <input
248
288
  type="checkbox"
249
289
  :checked="isAllSelected"
@@ -255,7 +295,7 @@ defineExpose({ handleSmartGroupApply })
255
295
  </label>
256
296
 
257
297
  <!-- Action Buttons Row -->
258
- <div v-if="enableGrouping" class="mint-sample-selector__actions">
298
+ <div v-if="!isPlaceholderState && enableGrouping" class="mint-sample-selector__actions">
259
299
  <div class="mint-sample-selector__actions-row">
260
300
  <!-- Grouping Button -->
261
301
  <BaseButton
@@ -291,7 +331,7 @@ defineExpose({ handleSmartGroupApply })
291
331
  </div>
292
332
 
293
333
  <!-- Grouped View -->
294
- <div v-if="groupingEnabled" class="mint-sample-selector__grouped">
334
+ <div v-if="!isPlaceholderState && groupingEnabled" class="mint-sample-selector__grouped">
295
335
  <!-- Groups Header -->
296
336
  <div class="mint-sample-selector__groups-header">
297
337
  <span class="mint-sample-selector__groups-title">Groups ({{ internalGroups.length }})</span>
@@ -320,7 +360,7 @@ defineExpose({ handleSmartGroupApply })
320
360
  />
321
361
 
322
362
  <!-- Empty state -->
323
- <div v-if="internalGroups.length === 0" class="mint-sample-selector__empty">
363
+ <div v-if="internalGroups.length === 0" class="mint-sample-selector__hint">
324
364
  Click Smart to auto-group samples
325
365
  </div>
326
366
  </div>
@@ -394,7 +434,7 @@ defineExpose({ handleSmartGroupApply })
394
434
  </div>
395
435
 
396
436
  <!-- Flat View (when no groups) -->
397
- <div v-if="!groupingEnabled" class="mint-sample-selector__flat">
437
+ <div v-if="!isPlaceholderState && !groupingEnabled" class="mint-sample-selector__flat">
398
438
  <!-- Search -->
399
439
  <div class="mint-sample-selector__search">
400
440
  <svg class="mint-sample-selector__search-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -427,7 +467,7 @@ defineExpose({ handleSmartGroupApply })
427
467
  <span class="mint-sample-selector__flat-name">{{ sample }}</span>
428
468
  </div>
429
469
 
430
- <div v-if="filteredSamples.length === 0 && searchQuery.trim()" class="mint-sample-selector__empty">
470
+ <div v-if="filteredSamples.length === 0 && searchQuery.trim()" class="mint-sample-selector__hint">
431
471
  No samples match "{{ searchQuery }}"
432
472
  </div>
433
473
  </div>
@@ -443,9 +483,8 @@ defineExpose({ handleSmartGroupApply })
443
483
  :design-data="resolvedDesignData"
444
484
  @apply="handleSmartGroupApply"
445
485
  />
486
+
487
+ <!-- Group reorder announcements for screen readers -->
488
+ <div role="status" aria-live="polite" :style="announcerStyle">{{ reorderAnnouncement }}</div>
446
489
  </div>
447
490
  </template>
448
-
449
- <style>
450
- @import '../styles/components/sample-selector.css';
451
- </style>
@@ -54,6 +54,18 @@ function onHeaderDrop(event: DragEvent) {
54
54
  if (dragKind.value === null) return
55
55
  props.ctx.onGroupDrop(dragName.value, dragKind.value, event)
56
56
  }
57
+
58
+ /** Enter/Space expand the node; Alt+ArrowUp/Down reorder it among its siblings. */
59
+ function onHeaderKeydown(event: KeyboardEvent) {
60
+ if (event.altKey) {
61
+ if (dragKind.value === null) return
62
+ props.ctx.onGroupReorderKeydown(dragName.value, dragKind.value, event)
63
+ return
64
+ }
65
+ if (event.key !== 'Enter' && event.key !== ' ') return
66
+ event.preventDefault()
67
+ props.ctx.toggleExpanded(props.node.key)
68
+ }
57
69
  </script>
58
70
 
59
71
  <template>
@@ -81,7 +93,14 @@ function onHeaderDrop(event: DragEvent) {
81
93
  reorderEdge === 'after' ? 'mint-sample-selector__header--drag-over-after' : '',
82
94
  ]"
83
95
  :draggable="dragKind !== null"
96
+ role="button"
97
+ tabindex="0"
98
+ :aria-expanded="expanded"
99
+ :aria-label="dragKind !== null
100
+ ? `${node.label}. Press Alt with the up or down arrow keys to reorder.`
101
+ : node.label"
84
102
  @click="ctx.toggleExpanded(node.key)"
103
+ @keydown="onHeaderKeydown"
85
104
  @dragstart="onHeaderDragStart"
86
105
  @dragend="ctx.onGroupDragEnd"
87
106
  @dragover="onHeaderDragOver"
@@ -86,6 +86,8 @@ const mockBlockedSlots: ScheduleBlockedSlot[] = [
86
86
 
87
87
  function initState() {
88
88
  return {
89
+ loading: false,
90
+ error: '',
89
91
  modelValue: today,
90
92
  view: 'week' as const,
91
93
  events: mockEvents,
@@ -106,6 +108,8 @@ function initState() {
106
108
  <template #default="{ state }">
107
109
  <div style="padding: 1rem; height: 700px;">
108
110
  <ScheduleCalendar
111
+ :loading="state.loading"
112
+ :error="state.error || null"
109
113
  v-model="state.modelValue"
110
114
  :view="state.view"
111
115
  :events="state.events"
@@ -123,6 +127,8 @@ function initState() {
123
127
  </div>
124
128
  </template>
125
129
  <template #controls="{ state }">
130
+ <HstCheckbox v-model="state.loading" title="Loading" />
131
+ <HstText v-model="state.error" title="Error Message" />
126
132
  <HstSelect
127
133
  v-model="state.view"
128
134
  title="View"
@@ -7,11 +7,13 @@ import type {
7
7
  ScheduleBlockedSlot,
8
8
  } from '../types/components'
9
9
  import { useScheduleDrag } from '../composables/useScheduleDrag'
10
+ import { useReorderAnnouncer } from '../composables/useListReorder'
10
11
  import {
11
12
  SCHEDULE_SLOT_HEIGHT,
12
13
  parseScheduleModelDate,
13
14
  useScheduleCalendarLayout,
14
15
  } from '../composables/useScheduleCalendarLayout'
16
+ import DataStateInternal from './internal/DataStateInternal.vue'
15
17
 
16
18
  interface Props {
17
19
  modelValue?: Date | string
@@ -28,6 +30,19 @@ interface Props {
28
30
  showNavigation?: boolean
29
31
  statusColors?: Record<string, string>
30
32
  locale?: string
33
+ /** Replace the calendar grid with a loading placeholder. */
34
+ loading?: boolean
35
+ /**
36
+ * Error message. When set, the calendar grid is replaced by an error state.
37
+ * `null`/`undefined` renders normally.
38
+ */
39
+ error?: string | null
40
+ /**
41
+ * Opt-in empty-state headline. An empty grid is meaningful on a calendar, so
42
+ * the empty state only replaces it when this message is supplied and there
43
+ * are no events.
44
+ */
45
+ emptyMessage?: string
31
46
  }
32
47
 
33
48
  const props = withDefaults(defineProps<Props>(), {
@@ -43,6 +58,8 @@ const props = withDefaults(defineProps<Props>(), {
43
58
  showViewToggle: true,
44
59
  showNavigation: true,
45
60
  locale: 'en-US',
61
+ loading: false,
62
+ error: null,
46
63
  })
47
64
 
48
65
  const emit = defineEmits<{
@@ -57,6 +74,11 @@ const emit = defineEmits<{
57
74
 
58
75
  const SLOT_HEIGHT = SCHEDULE_SLOT_HEIGHT
59
76
  const currentView = ref<ScheduleView>(props.view)
77
+
78
+ /** Empty state is opt-in: it needs an `emptyMessage` and an event-free range. */
79
+ const showEmptyState = computed(() =>
80
+ Boolean(props.emptyMessage) && props.events.length === 0,
81
+ )
60
82
  const currentDate = ref<Date>(parseScheduleModelDate(props.modelValue))
61
83
  const nowTimer = ref<ReturnType<typeof setInterval>>()
62
84
  const now = ref(new Date())
@@ -76,7 +98,9 @@ const weekStartsOnRef = computed(() => props.weekStartsOn)
76
98
  const localeRef = computed(() => props.locale)
77
99
  const showNowIndicatorRef = computed(() => props.showNowIndicator)
78
100
 
79
- const { isDragging, ghost, startCreate, startMove, startResize } = useScheduleDrag({
101
+ const { announcement, announce, announcerStyle } = useReorderAnnouncer()
102
+
103
+ const { isDragging, ghost, startCreate, startMove, startResize, nudgeEvent } = useScheduleDrag({
80
104
  slotDuration: slotDurationRef,
81
105
  dayStartHour: dayStartRef,
82
106
  dayEndHour: dayEndRef,
@@ -180,6 +204,35 @@ function onResizePointerDown(event: ScheduleEvent, edge: 'top' | 'bottom', dayIn
180
204
  startResize(event, edge, e.clientY, dayIndex)
181
205
  }
182
206
 
207
+ /**
208
+ * Keyboard alternative to dragging an event: Alt+ArrowUp/Down move it by one
209
+ * slot, Shift+Alt+ArrowUp/Down shorten or lengthen it, Enter/Space open it.
210
+ */
211
+ function onEventKeydown(event: ScheduleEvent, e: KeyboardEvent) {
212
+ if (e.key === 'Enter' || e.key === ' ') {
213
+ e.preventDefault()
214
+ emit('event-click', event)
215
+ return
216
+ }
217
+ if (!e.altKey) return
218
+ const direction = e.key === 'ArrowUp' ? -1 : e.key === 'ArrowDown' ? 1 : 0
219
+ if (direction === 0) return
220
+
221
+ e.preventDefault()
222
+ const mode = e.shiftKey ? 'resize' : 'move'
223
+ const applied = nudgeEvent(event, direction, mode)
224
+ announce(applied
225
+ ? `${event.title} ${mode === 'move' ? 'moved' : 'resized'} to ${formatEventTime(event)}.`
226
+ : `${event.title} cannot move further.`)
227
+ }
228
+
229
+ /** Instructions announced with each event. */
230
+ function eventLabel(event: ScheduleEvent): string {
231
+ const base = `${event.title}, ${formatEventTime(event)}`
232
+ if (props.readonly || event.draggable === false) return base
233
+ return `${base}. Press Alt with the up or down arrow keys to reschedule it.`
234
+ }
235
+
183
236
  function onMonthDayClick(date: Date) {
184
237
  currentDate.value = date
185
238
  emit('update:modelValue', date)
@@ -254,8 +307,27 @@ onUnmounted(() => {
254
307
  </div>
255
308
  </div>
256
309
 
310
+ <!-- Loading / error / empty placeholders replace the grid -->
311
+ <div v-if="error" class="mint-schedule__state">
312
+ <slot name="error" :error="error">
313
+ <DataStateInternal state="error" :title="error" />
314
+ </slot>
315
+ </div>
316
+
317
+ <div v-else-if="loading" class="mint-schedule__state">
318
+ <slot name="loading">
319
+ <DataStateInternal state="loading" loading-label="Loading schedule" />
320
+ </slot>
321
+ </div>
322
+
323
+ <div v-else-if="showEmptyState" class="mint-schedule__state">
324
+ <slot name="empty">
325
+ <DataStateInternal state="empty" :title="emptyMessage" />
326
+ </slot>
327
+ </div>
328
+
257
329
  <!-- Day / Week View -->
258
- <template v-if="currentView === 'day' || currentView === 'week'">
330
+ <template v-else-if="currentView === 'day' || currentView === 'week'">
259
331
  <!-- Day headers -->
260
332
  <div v-if="currentView === 'week'" class="mint-schedule__day-headers">
261
333
  <div class="mint-schedule__day-header-gutter" />
@@ -312,7 +384,11 @@ onUnmounted(() => {
312
384
  :key="event.id"
313
385
  :class="['mint-schedule__event', getStatusClass(event.status)]"
314
386
  :style="getEventStyle(event, day) || undefined"
387
+ role="button"
388
+ tabindex="0"
389
+ :aria-label="eventLabel(event)"
315
390
  @click="onEventClick(event, $event)"
391
+ @keydown="onEventKeydown(event, $event)"
316
392
  @pointerdown="onEventPointerDown(event, dayIdx, $event)"
317
393
  >
318
394
  <slot name="event" :event="event">
@@ -364,7 +440,7 @@ onUnmounted(() => {
364
440
  </template>
365
441
 
366
442
  <!-- Month View -->
367
- <template v-if="currentView === 'month'">
443
+ <template v-else-if="currentView === 'month'">
368
444
  <div class="mint-schedule__month-grid">
369
445
  <!-- Weekday headers -->
370
446
  <div
@@ -406,9 +482,8 @@ onUnmounted(() => {
406
482
  </div>
407
483
  </div>
408
484
  </template>
485
+
486
+ <!-- Reschedule announcements for screen readers -->
487
+ <div role="status" aria-live="polite" :style="announcerStyle">{{ announcement }}</div>
409
488
  </div>
410
489
  </template>
411
-
412
- <style>
413
- @import '../styles/components/schedule-calendar.css';
414
- </style>
@@ -191,7 +191,3 @@ async function copyToClipboard() {
191
191
  </button>
192
192
  </span>
193
193
  </template>
194
-
195
- <style>
196
- @import '../styles/components/scientific-number.css';
197
- </style>
@@ -0,0 +1,79 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import SecretInput from './SecretInput.vue'
4
+ import type { PluginSecretRef } from '../utils/pluginSecrets'
5
+
6
+ const sizes = ['sm', 'md', 'lg'] as const
7
+
8
+ const storedRef: PluginSecretRef = { $secret: 'sec_0f2a4c6e8a0c2e4a6c8e0a2c4e6a8c0e', set: true }
9
+ const lockedRef: PluginSecretRef = { $secret: null, locked: true }
10
+
11
+ const stored = ref<PluginSecretRef | string | null>({ ...storedRef })
12
+ const unset = ref<PluginSecretRef | string | null>(null)
13
+ const locked = ref<PluginSecretRef | string | null>({ ...lockedRef })
14
+ </script>
15
+
16
+ <template>
17
+ <Story title="Base Inputs/SecretInput">
18
+ <Variant title="Playground">
19
+ <template #default="{ state }">
20
+ <div style="padding: 2rem; max-width: 600px;">
21
+ <SecretInput
22
+ v-model="state.value"
23
+ :placeholder="state.placeholder"
24
+ :disabled="state.disabled"
25
+ :error="state.error"
26
+ :size="state.size"
27
+ />
28
+ <p style="margin-top: 1rem; font-size: 0.875rem; color: var(--text-muted, #94a3b8);">
29
+ Submits: <code>{{ JSON.stringify(state.value) }}</code>
30
+ </p>
31
+ </div>
32
+ </template>
33
+
34
+ <template #controls="{ state }">
35
+ <HstText v-model="state.placeholder" title="Placeholder" />
36
+ <HstSelect
37
+ v-model="state.size"
38
+ title="Size"
39
+ :options="sizes.map(size => ({ label: size, value: size }))"
40
+ />
41
+ <HstCheckbox v-model="state.disabled" title="Disabled" />
42
+ <HstCheckbox v-model="state.error" title="Error" />
43
+ </template>
44
+
45
+ <template #initState>
46
+ {{ {
47
+ value: { $secret: 'sec_0f2a4c6e8a0c2e4a6c8e0a2c4e6a8c0e', set: true },
48
+ placeholder: 'Enter a new value',
49
+ disabled: false,
50
+ error: false,
51
+ size: 'md',
52
+ } }}
53
+ </template>
54
+ </Variant>
55
+
56
+ <Variant title="States">
57
+ <div style="padding: 2rem; max-width: 600px; display: flex; flex-direction: column; gap: 1.5rem;">
58
+ <div>
59
+ <p style="margin: 0 0 0.5rem; font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em;">
60
+ Stored
61
+ </p>
62
+ <SecretInput v-model="stored" />
63
+ </div>
64
+ <div>
65
+ <p style="margin: 0 0 0.5rem; font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em;">
66
+ Not set
67
+ </p>
68
+ <SecretInput v-model="unset" />
69
+ </div>
70
+ <div>
71
+ <p style="margin: 0 0 0.5rem; font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em;">
72
+ Provided by environment
73
+ </p>
74
+ <SecretInput v-model="locked" />
75
+ </div>
76
+ </div>
77
+ </Variant>
78
+ </Story>
79
+ </template>