@morscherlab/mint-sdk 1.4.0-beta.2 → 1.4.0-beta.20

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 (330) hide show
  1. package/README.md +14 -3
  2. package/dist/{ActionMenu-CScINahU.js → AlertBox-CzLg0UaL.js} +40 -209
  3. package/dist/AlertBox-CzLg0UaL.js.map +1 -0
  4. package/dist/{AppSidebar-BmiiPfz9.js → AppSidebar-6oTT-gPO.js} +1249 -239
  5. package/dist/AppSidebar-6oTT-gPO.js.map +1 -0
  6. package/dist/{ConfirmDialog-CJ7hqYUP.js → ConfirmDialog-CSls2Nuv.js} +2 -2
  7. package/dist/{ConfirmDialog-CJ7hqYUP.js.map → ConfirmDialog-CSls2Nuv.js.map} +1 -1
  8. package/dist/ExperimentPopover-9l4QZlvQ.js +337 -0
  9. package/dist/ExperimentPopover-9l4QZlvQ.js.map +1 -0
  10. package/dist/{ExperimentSelectorModal-D-9z7cNx.js → ExperimentSelectorModal-pR5fK3pA.js} +244 -211
  11. package/dist/ExperimentSelectorModal-pR5fK3pA.js.map +1 -0
  12. package/dist/{SegmentedControl-DERBN-Xd.js → SegmentedControl-CJ7EAmOq.js} +2 -2
  13. package/dist/{SegmentedControl-DERBN-Xd.js.map → SegmentedControl-CJ7EAmOq.js.map} +1 -1
  14. package/dist/{SettingsModal-CSLE31yg.js → SettingsModal-Dyitgzfp.js} +28 -27
  15. package/dist/SettingsModal-Dyitgzfp.js.map +1 -0
  16. package/dist/__stories__/experiment-helpers.d.ts +10 -0
  17. package/dist/__tests__/components/AppTopBarOnboarding.test.d.ts +1 -0
  18. package/dist/__tests__/composables/useOnboarding.test.d.ts +1 -0
  19. package/dist/__tests__/styles/paletteTokens.test.d.ts +1 -0
  20. package/dist/__tests__/styles/v2AuditRules.test.d.ts +1 -0
  21. package/dist/components/AppSidebar.vue.d.ts +1 -1
  22. package/dist/components/AppTopBar.vue.d.ts +20 -5
  23. package/dist/components/ArtifactSaveDialog.vue.d.ts +9 -1
  24. package/dist/components/AuditTrail.vue.d.ts +1 -1
  25. package/dist/components/BaseButton.vue.d.ts +4 -2
  26. package/dist/components/BaseSelect.vue.d.ts +2 -2
  27. package/dist/components/ChartContainer.vue.d.ts +2 -0
  28. package/dist/components/DatePicker.vue.d.ts +1 -1
  29. package/dist/components/ExperimentPopover.vue.d.ts +18 -0
  30. package/dist/components/ExperimentSelectorModal.vue.d.ts +15 -5
  31. package/dist/components/FilePicker.vue.d.ts +1 -1
  32. package/dist/components/FileUploader.vue.d.ts +2 -2
  33. package/dist/components/JobsStatusTray.vue.d.ts +15 -4
  34. package/dist/components/PluginIcon.vue.d.ts +2 -2
  35. package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
  36. package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
  37. package/dist/components/SampleSelector.vue.d.ts +6 -6
  38. package/dist/components/SequenceProgressBar.vue.d.ts +3 -0
  39. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -1
  40. package/dist/components/StepWizard.vue.d.ts +3 -0
  41. package/dist/components/internal/AppTopBarOnboardingInternal.vue.d.ts +7 -0
  42. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -1
  43. package/dist/components/internal/AutoGroupModalInternal.vue.d.ts +1 -1
  44. package/dist/components/internal/DatePickerTimeInternal.vue.d.ts +1 -1
  45. package/dist/components/internal/OnboardingLayerInternal.vue.d.ts +7 -0
  46. package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +2 -7
  47. package/dist/components/internal/SelectMultipleInternal.vue.d.ts +4 -0
  48. package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
  49. package/dist/composables/createClient.d.ts +2 -0
  50. package/dist/composables/index.d.ts +1 -0
  51. package/dist/composables/pluginIdentity.d.ts +24 -0
  52. package/dist/composables/pluginWorkspaceModel.d.ts +2 -2
  53. package/dist/composables/useAppExperiment.d.ts +8 -0
  54. package/dist/composables/useCalendarGrid.d.ts +1 -1
  55. package/dist/composables/useExperimentSelector.d.ts +6 -2
  56. package/dist/composables/useJobCenter.d.ts +41 -3
  57. package/dist/composables/useOnboarding.d.ts +119 -0
  58. package/dist/composables/usePlatform.d.ts +1 -0
  59. package/dist/{controlSchemaModel-CP5-_nAj.js → controlSchemaModel-DCUOx8ll.js} +2 -2
  60. package/dist/{controlSchemaModel-CP5-_nAj.js.map → controlSchemaModel-DCUOx8ll.js.map} +1 -1
  61. package/dist/{data-C2PwI_dn.js → data-DBnmeBZX.js} +179 -125
  62. package/dist/data-DBnmeBZX.js.map +1 -0
  63. package/dist/data.js +8 -8
  64. package/dist/{experiment-43OJtH7R.js → experiment-DyO54FQB.js} +25 -61
  65. package/dist/experiment-DyO54FQB.js.map +1 -0
  66. package/dist/{experimentStatus-BZmU4czV.js → experimentStatus-BFrKpHAH.js} +3 -3
  67. package/dist/{experimentStatus-BZmU4czV.js.map → experimentStatus-BFrKpHAH.js.map} +1 -1
  68. package/dist/index.js +20 -20
  69. package/dist/{install-DAID6c2-.js → install-qjELyNwc.js} +15 -14
  70. package/dist/{install-DAID6c2-.js.map → install-qjELyNwc.js.map} +1 -1
  71. package/dist/install.js +1 -1
  72. package/dist/{lab-DLwRl_Ql.js → lab-UyTbokQC.js} +136 -148
  73. package/dist/lab-UyTbokQC.js.map +1 -0
  74. package/dist/lab.js +4 -3
  75. package/dist/{records-BzTcxekG.js → records-CR_pYzIP.js} +533 -335
  76. package/dist/records-CR_pYzIP.js.map +1 -0
  77. package/dist/{runtime-D6gVat6r.js → runtime-CoHhBP10.js} +417 -54
  78. package/dist/runtime-CoHhBP10.js.map +1 -0
  79. package/dist/runtime.d.ts +1 -1
  80. package/dist/runtime.js +7 -7
  81. package/dist/{shell-5ju97nOS.js → shell-BDuZf2zb.js} +10 -5
  82. package/dist/{shell-5ju97nOS.js.map → shell-BDuZf2zb.js.map} +1 -1
  83. package/dist/shell.d.ts +1 -0
  84. package/dist/shell.js +6 -6
  85. package/dist/stores/index.d.ts +1 -1
  86. package/dist/stores/index.js +2 -2
  87. package/dist/stores/settings.d.ts +4 -12
  88. package/dist/styles.css +2732 -2083
  89. package/dist/types/componentWorkflowTypes.d.ts +2 -0
  90. package/dist/types/components.d.ts +2 -0
  91. package/dist/types/generated/platformContext.d.ts +1 -0
  92. package/dist/types/generated-ui.d.ts +4 -0
  93. package/dist/types/index.d.ts +1 -1
  94. package/dist/types/jobs.d.ts +9 -0
  95. package/dist/types/platform.d.ts +1 -1
  96. package/dist/{BaseModal-DfRIuVoj.js → useDropdownState-Bb2N4m40.js} +74 -4
  97. package/dist/useDropdownState-Bb2N4m40.js.map +1 -0
  98. package/dist/{useExperimentSamples-CxcnxZ8h.js → useExperimentSamples-Dno1fFMq.js} +4 -4
  99. package/dist/{useExperimentSamples-CxcnxZ8h.js.map → useExperimentSamples-Dno1fFMq.js.map} +1 -1
  100. package/dist/{useListReorder-CcbEYxX-.js → useListReorder-CQ9OCScc.js} +183 -351
  101. package/dist/useListReorder-CQ9OCScc.js.map +1 -0
  102. package/dist/{useRackEditor-BD5NIo5s.js → useRackEditor-wL3kPrRT.js} +73 -59
  103. package/dist/useRackEditor-wL3kPrRT.js.map +1 -0
  104. package/dist/useRealtime-C3whhVvb.js +508 -0
  105. package/dist/useRealtime-C3whhVvb.js.map +1 -0
  106. package/dist/useRequest-CDkrXNU7.js +244 -0
  107. package/dist/useRequest-CDkrXNU7.js.map +1 -0
  108. package/dist/utils/time.d.ts +7 -0
  109. package/package.json +1 -1
  110. package/src/__stories__/experiment-helpers.ts +3 -3
  111. package/src/__tests__/components/AppTopBar.test.ts +257 -3
  112. package/src/__tests__/components/AppTopBarOnboarding.test.ts +160 -0
  113. package/src/__tests__/components/AppTopBarPageSelector.test.ts +125 -9
  114. package/src/__tests__/components/AppTopBarPillNav.test.ts +35 -0
  115. package/src/__tests__/components/ArtifactSaveDialog.test.ts +78 -0
  116. package/src/__tests__/components/BaseButton.test.ts +19 -0
  117. package/src/__tests__/components/BaseSelect.multiple.test.ts +87 -32
  118. package/src/__tests__/components/BaseSelect.test.ts +2 -1
  119. package/src/__tests__/components/BatchProgressList.test.ts +16 -0
  120. package/src/__tests__/components/CalendarGridPanel.test.ts +2 -2
  121. package/src/__tests__/components/ChartContainer.test.ts +11 -0
  122. package/src/__tests__/components/ChemicalFormula.test.ts +6 -3
  123. package/src/__tests__/components/DatePicker.datetime.test.ts +6 -0
  124. package/src/__tests__/components/DatePicker.test.ts +21 -0
  125. package/src/__tests__/components/DatePicker.time.test.ts +2 -2
  126. package/src/__tests__/components/EmptyState.test.ts +1 -1
  127. package/src/__tests__/components/ExperimentPopover.test.ts +125 -16
  128. package/src/__tests__/components/ExperimentSelectorModal.test.ts +153 -14
  129. package/src/__tests__/components/GeneratedPluginView.test.ts +23 -2
  130. package/src/__tests__/components/InstrumentStateBadge.test.ts +4 -4
  131. package/src/__tests__/components/JobsStatusTray.test.ts +373 -71
  132. package/src/__tests__/components/PluginWorkspaceView.test.ts +63 -1
  133. package/src/__tests__/components/ResourcePickerListInternal.test.ts +0 -20
  134. package/src/__tests__/components/SampleSelector.test.ts +33 -8
  135. package/src/__tests__/components/SequenceProgressBar.test.ts +12 -0
  136. package/src/__tests__/components/SettingsModal.test.ts +9 -2
  137. package/src/__tests__/components/StepWizard.test.ts +11 -0
  138. package/src/__tests__/components/WellPlate.decoration.test.ts +11 -4
  139. package/src/__tests__/components/deprecationTags.test.ts +1 -1
  140. package/src/__tests__/composables/useAppExperiment.test.ts +27 -1
  141. package/src/__tests__/composables/useCalendarGrid.test.ts +4 -2
  142. package/src/__tests__/composables/useExperimentSelector.test.ts +111 -0
  143. package/src/__tests__/composables/useJobCenter.test.ts +262 -3
  144. package/src/__tests__/composables/useOnboarding.test.ts +317 -0
  145. package/src/__tests__/docs/frontendDocsCatalog.test.ts +4 -3
  146. package/src/__tests__/stores/settings.test.ts +21 -21
  147. package/src/__tests__/styles/baseControlsRules.test.ts +6 -2
  148. package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
  149. package/src/__tests__/styles/cssTokens.test.ts +6 -3
  150. package/src/__tests__/styles/menusShellRefresh.test.ts +22 -12
  151. package/src/__tests__/styles/paletteTokens.test.ts +44 -0
  152. package/src/__tests__/styles/v2AuditRules.test.ts +104 -0
  153. package/src/__tests__/utils/plotlyTemplate.test.ts +4 -4
  154. package/src/__tests__/utils/time.test.ts +18 -1
  155. package/src/components/AdductText.story.vue +7 -0
  156. package/src/components/AdductText.vue +1 -1
  157. package/src/components/AlertBox.story.vue +2 -2
  158. package/src/components/AlertBox.vue +18 -16
  159. package/src/components/AppSidebar.story.vue +0 -28
  160. package/src/components/AppTopBar.onboarding.story.vue +69 -0
  161. package/src/components/AppTopBar.story.vue +105 -15
  162. package/src/components/AppTopBar.vue +248 -122
  163. package/src/components/ArtifactSaveDialog.story.vue +23 -0
  164. package/src/components/ArtifactSaveDialog.vue +102 -12
  165. package/src/components/AuditTrail.story.vue +2 -1
  166. package/src/components/AuditTrail.vue +2 -21
  167. package/src/components/Avatar.vue +10 -14
  168. package/src/components/BaseButton.vue +16 -2
  169. package/src/components/BaseSelect.multiple.story.vue +13 -2
  170. package/src/components/BaseSelect.vue +4 -3
  171. package/src/components/BatchProgressList.vue +35 -25
  172. package/src/components/ChartContainer.story.vue +2 -1
  173. package/src/components/ChartContainer.vue +8 -6
  174. package/src/components/ConfirmDialog.story.vue +1 -1
  175. package/src/components/EmptyState.vue +1 -1
  176. package/src/components/ExperimentPopover.story.vue +165 -53
  177. package/src/components/ExperimentPopover.vue +169 -187
  178. package/src/components/ExperimentSelectorModal.story.vue +73 -31
  179. package/src/components/ExperimentSelectorModal.vue +144 -111
  180. package/src/components/FormField.story.vue +8 -48
  181. package/src/components/GeneratedPluginView.vue +11 -10
  182. package/src/components/GeneratedResultRenderer.vue +3 -4
  183. package/src/components/InstrumentStateBadge.vue +1 -1
  184. package/src/components/JobsStatusTray.story.vue +97 -43
  185. package/src/components/JobsStatusTray.vue +287 -290
  186. package/src/components/LayoutResizeHandle.story.vue +65 -4
  187. package/src/components/LoadingSpinner.vue +1 -1
  188. package/src/components/NumberInput.vue +3 -6
  189. package/src/components/PluginIcon.story.vue +2 -10
  190. package/src/components/PluginIcon.vue +2 -2
  191. package/src/components/PluginWorkspaceView.props.ts +3 -0
  192. package/src/components/PluginWorkspaceView.story.vue +1 -1
  193. package/src/components/PluginWorkspaceView.vue +17 -0
  194. package/src/components/RuntimeUpdateNotice.vue +1 -1
  195. package/src/components/SampleSelector.story.vue +22 -7
  196. package/src/components/SampleSelector.vue +34 -37
  197. package/src/components/SampleSelectorGroupNode.vue +1 -1
  198. package/src/components/SectionCard.story.vue +35 -2
  199. package/src/components/SequenceProgressBar.story.vue +9 -0
  200. package/src/components/SequenceProgressBar.vue +6 -1
  201. package/src/components/SettingsModal.schema.ts +1 -1
  202. package/src/components/SettingsModal.vue +9 -14
  203. package/src/components/SmartGroupFieldRecipe.story.vue +1 -1
  204. package/src/components/SmartGroupManual.story.vue +1 -1
  205. package/src/components/StepWizard.story.vue +33 -0
  206. package/src/components/StepWizard.vue +8 -1
  207. package/src/components/WellPlate.story.vue +3 -1
  208. package/src/components/WellPlate.vue +7 -0
  209. package/src/components/internal/AppTopBarOnboardingInternal.vue +172 -0
  210. package/src/components/internal/AppTopBarPageSelectorInternal.vue +140 -72
  211. package/src/components/internal/AppTopBarPillNavInternal.vue +33 -2
  212. package/src/components/internal/DatePickerDateInternal.vue +16 -40
  213. package/src/components/internal/DatePickerDateTimeInternal.vue +3 -13
  214. package/src/components/internal/DatePickerTimeInternal.vue +5 -8
  215. package/src/components/internal/OnboardingLayerInternal.vue +173 -0
  216. package/src/components/internal/ResourcePickerListInternal.vue +45 -85
  217. package/src/components/internal/SelectMultipleInternal.vue +182 -65
  218. package/src/components/internal/SelectSearchableInternal.vue +2 -3
  219. package/src/components/internal/WorkspaceShellInternal.vue +12 -0
  220. package/src/composables/createClient.ts +2 -0
  221. package/src/composables/index.ts +1 -0
  222. package/src/composables/pluginIdentity.ts +31 -0
  223. package/src/composables/pluginWorkspaceModel.ts +2 -2
  224. package/src/composables/useAppExperiment.ts +20 -0
  225. package/src/composables/useCalendarGrid.ts +4 -3
  226. package/src/composables/useExperimentSelector.ts +77 -31
  227. package/src/composables/useJobCenter.ts +188 -6
  228. package/src/composables/useOnboarding.ts +465 -0
  229. package/src/composables/usePluginWorkspace.ts +1 -1
  230. package/src/runtime.ts +2 -1
  231. package/src/shell.ts +17 -0
  232. package/src/stores/index.ts +0 -2
  233. package/src/stores/settings.ts +20 -50
  234. package/src/styles/components/adduct-text.css +13 -5
  235. package/src/styles/components/alert-box.css +39 -58
  236. package/src/styles/components/app-layout.css +7 -5
  237. package/src/styles/components/app-page-selector.css +121 -44
  238. package/src/styles/components/app-pill-nav.css +11 -12
  239. package/src/styles/components/app-sidebar.css +9 -18
  240. package/src/styles/components/app-top-bar.css +219 -55
  241. package/src/styles/components/artifact-save-dialog.css +153 -2
  242. package/src/styles/components/audit-trail.css +16 -19
  243. package/src/styles/components/avatar.css +6 -4
  244. package/src/styles/components/batch-progress-list.css +38 -36
  245. package/src/styles/components/button.css +36 -59
  246. package/src/styles/components/calendar.css +12 -12
  247. package/src/styles/components/chart-container.css +34 -23
  248. package/src/styles/components/checkbox.css +2 -12
  249. package/src/styles/components/collapsible-card.css +27 -29
  250. package/src/styles/components/confirm-dialog.css +13 -14
  251. package/src/styles/components/dataframe.css +13 -5
  252. package/src/styles/components/date-picker.css +80 -92
  253. package/src/styles/components/datetime-picker.css +31 -43
  254. package/src/styles/components/divider.css +20 -20
  255. package/src/styles/components/dose-calculator.css +3 -3
  256. package/src/styles/components/empty-state.css +20 -17
  257. package/src/styles/components/experiment-code-badge.css +2 -0
  258. package/src/styles/components/experiment-popover.css +174 -329
  259. package/src/styles/components/experiment-selector-modal.css +88 -56
  260. package/src/styles/components/file-picker.css +3 -3
  261. package/src/styles/components/file-uploader.css +38 -49
  262. package/src/styles/components/form-builder.css +3 -3
  263. package/src/styles/components/formula-input.css +1 -1
  264. package/src/styles/components/icon-button.css +18 -24
  265. package/src/styles/components/input.css +5 -4
  266. package/src/styles/components/instrument-monitor.css +13 -15
  267. package/src/styles/components/jobs-status-tray.css +140 -296
  268. package/src/styles/components/layout-resize-handle.css +59 -16
  269. package/src/styles/components/modal.css +4 -6
  270. package/src/styles/components/molecule-input.css +2 -2
  271. package/src/styles/components/multi-select.css +65 -117
  272. package/src/styles/components/number-input.css +26 -19
  273. package/src/styles/components/onboarding.css +368 -0
  274. package/src/styles/components/pill.css +3 -2
  275. package/src/styles/components/plate-editor.css +0 -1
  276. package/src/styles/components/plugin-icon.css +26 -23
  277. package/src/styles/components/popover.css +2 -2
  278. package/src/styles/components/presence-avatars.css +1 -1
  279. package/src/styles/components/progress-bar.css +3 -3
  280. package/src/styles/components/protocol-step-editor.css +1 -3
  281. package/src/styles/components/resource-card.css +2 -2
  282. package/src/styles/components/resource-picker-list.css +6 -57
  283. package/src/styles/components/sample-hierarchy-tree.css +4 -4
  284. package/src/styles/components/sample-selector.css +138 -79
  285. package/src/styles/components/schedule-calendar.css +8 -8
  286. package/src/styles/components/searchable-select.css +12 -12
  287. package/src/styles/components/section-card.css +54 -49
  288. package/src/styles/components/segmented-control.css +18 -15
  289. package/src/styles/components/select.css +3 -3
  290. package/src/styles/components/sequence-input.css +2 -2
  291. package/src/styles/components/sequence-progress-bar.css +17 -8
  292. package/src/styles/components/settings-modal.css +7 -2
  293. package/src/styles/components/smart-group.css +18 -18
  294. package/src/styles/components/step-wizard.css +52 -16
  295. package/src/styles/components/tabs.css +2 -2
  296. package/src/styles/components/textarea.css +3 -3
  297. package/src/styles/components/time-picker.css +36 -21
  298. package/src/styles/components/toast.css +12 -10
  299. package/src/styles/components/toggle.css +1 -11
  300. package/src/styles/components/tooltip.css +10 -9
  301. package/src/styles/components/unit-input.css +8 -7
  302. package/src/styles/components/well-plate.css +9 -2
  303. package/src/styles/index.css +1 -0
  304. package/src/styles/variables.css +241 -78
  305. package/src/types/componentWorkflowTypes.ts +2 -0
  306. package/src/types/components.ts +2 -0
  307. package/src/types/generated/platformContext.ts +1 -0
  308. package/src/types/generated-ui.ts +4 -0
  309. package/src/types/index.ts +1 -0
  310. package/src/types/jobs.ts +10 -0
  311. package/src/types/platform.ts +1 -1
  312. package/src/utils/time.ts +21 -0
  313. package/dist/ActionMenu-CScINahU.js.map +0 -1
  314. package/dist/AppSidebar-BmiiPfz9.js.map +0 -1
  315. package/dist/BaseModal-DfRIuVoj.js.map +0 -1
  316. package/dist/ExperimentPopover-C4sGw-is.js +0 -359
  317. package/dist/ExperimentPopover-C4sGw-is.js.map +0 -1
  318. package/dist/ExperimentSelectorModal-D-9z7cNx.js.map +0 -1
  319. package/dist/SettingsModal-CSLE31yg.js.map +0 -1
  320. package/dist/data-C2PwI_dn.js.map +0 -1
  321. package/dist/experiment-43OJtH7R.js.map +0 -1
  322. package/dist/lab-DLwRl_Ql.js.map +0 -1
  323. package/dist/records-BzTcxekG.js.map +0 -1
  324. package/dist/runtime-D6gVat6r.js.map +0 -1
  325. package/dist/useListReorder-CcbEYxX-.js.map +0 -1
  326. package/dist/useRackEditor-BD5NIo5s.js.map +0 -1
  327. package/dist/useRealtime-PalKue84.js +0 -696
  328. package/dist/useRealtime-PalKue84.js.map +0 -1
  329. package/dist/useRequest-D5JDfsgZ.js +0 -76
  330. package/dist/useRequest-D5JDfsgZ.js.map +0 -1
@@ -1,7 +1,9 @@
1
1
  <script setup lang="ts">
2
- /** BaseSelect with `multiple`: a checkbox chip list with an optional selection cap (`max`). */
3
- import { computed } from 'vue'
2
+ /** BaseSelect with `multiple`: a field of removable chips that opens a filterable checkbox listbox, capped by `max`. */
3
+ import { computed, nextTick, ref, useAttrs, useId, watch } from 'vue'
4
+ import { Check, ChevronDown, X } from 'lucide-vue-next'
4
5
  import type { OptionPrimitive, SelectOption, SelectOptionInput } from '../../types'
6
+ import { useEventListener } from '../../composables/useEventListener'
5
7
  import { useListSelection } from '../../composables/useListSelection'
6
8
  import { normalizeOptionInput } from '../../utils/options'
7
9
 
@@ -15,6 +17,8 @@ interface Props {
15
17
  invalid?: boolean
16
18
  max?: number
17
19
  size?: 'sm' | 'md' | 'lg'
20
+ ariaLabelledby?: string
21
+ ariaDescribedby?: string
18
22
  }
19
23
 
20
24
  const props = withDefaults(defineProps<Props>(), {
@@ -23,103 +27,216 @@ const props = withDefaults(defineProps<Props>(), {
23
27
  invalid: false,
24
28
  max: undefined,
25
29
  size: 'md',
30
+ ariaLabelledby: undefined,
31
+ ariaDescribedby: undefined,
32
+ })
33
+
34
+ defineOptions({ inheritAttrs: false })
35
+
36
+ // The wrapper takes class and style; every other attribute (aria-label, data-*) names the focusable field.
37
+ const attrs = useAttrs()
38
+ const fieldAttrs = computed(() => {
39
+ const { class: _class, style: _style, ...rest } = attrs
40
+ return rest
26
41
  })
27
42
 
28
43
  const emit = defineEmits<{
29
44
  'update:modelValue': [value: OptionPrimitive[]]
30
45
  }>()
31
46
 
47
+ const id = useId().replace(/:/g, '')
48
+ const listboxId = `mint-multi-select-${id}`
49
+ const root = ref<HTMLElement | null>(null)
50
+ const field = ref<HTMLElement | null>(null)
51
+ const search = ref<HTMLInputElement | null>(null)
52
+ const open = ref(false)
53
+ const query = ref('')
54
+ const activeIndex = ref(-1)
55
+
32
56
  const normalizedOptions = computed<MultiOption[]>(() => props.options.map(normalizeOptionInput<OptionPrimitive, MultiOption>))
33
- const optionSelection = useListSelection({
57
+ const selection = useListSelection({
34
58
  selected: () => props.modelValue,
35
59
  items: normalizedOptions,
36
60
  getValue: option => option.value,
37
61
  disabled: () => props.disabled,
38
62
  max: () => props.max,
39
63
  })
40
- const canSelectMore = optionSelection.canAddMore
41
64
 
42
- function isSelected(value: OptionPrimitive): boolean {
43
- return optionSelection.isSelected(value)
65
+ const chips = computed(() => props.modelValue.map(value => ({
66
+ value,
67
+ label: normalizedOptions.value.find(option => option.value === value)?.label ?? String(value),
68
+ })))
69
+ const visibleOptions = computed(() => {
70
+ const needle = query.value.trim().toLocaleLowerCase()
71
+ if (!needle) return normalizedOptions.value
72
+ return normalizedOptions.value.filter(option => option.label.toLocaleLowerCase().includes(needle))
73
+ })
74
+ const activeDescendant = computed(() =>
75
+ activeIndex.value >= 0 ? `${listboxId}-option-${activeIndex.value}` : undefined,
76
+ )
77
+
78
+ function isUnavailable(option: MultiOption): boolean {
79
+ return !!option.disabled || (!selection.isSelected(option.value) && !selection.canAddMore.value)
44
80
  }
45
81
 
46
- function toggleOption(option: MultiOption) {
47
- if (props.disabled || option.disabled) return
82
+ async function openPanel(): Promise<void> {
83
+ if (props.disabled || open.value) return
84
+ query.value = ''
85
+ open.value = true
86
+ activeIndex.value = visibleOptions.value.length > 0 ? 0 : -1
87
+ await nextTick()
88
+ search.value?.focus()
89
+ }
48
90
 
49
- if (isSelected(option.value) || canSelectMore.value) {
50
- emit('update:modelValue', optionSelection.toggleValue(option.value))
91
+ async function closePanel(restoreFocus = false): Promise<void> {
92
+ open.value = false
93
+ query.value = ''
94
+ activeIndex.value = -1
95
+ if (restoreFocus) {
96
+ await nextTick()
97
+ field.value?.focus()
51
98
  }
52
99
  }
53
100
 
54
- function handleKeydown(event: KeyboardEvent, option: MultiOption) {
55
- if (event.key === 'Enter' || event.key === ' ') {
56
- event.preventDefault()
57
- toggleOption(option)
58
- }
101
+ function moveActive(direction: 1 | -1): void {
102
+ const count = visibleOptions.value.length
103
+ if (count === 0) return
104
+ const start = activeIndex.value < 0 ? (direction === 1 ? -1 : count) : activeIndex.value
105
+ activeIndex.value = (start + direction + count) % count
106
+ void nextTick(() => document.getElementById(activeDescendant.value ?? '')?.scrollIntoView({ block: 'nearest' }))
107
+ }
108
+
109
+ function toggle(option: MultiOption): void {
110
+ if (props.disabled || isUnavailable(option)) return
111
+ emit('update:modelValue', selection.toggleValue(option.value))
112
+ }
113
+
114
+ function remove(value: OptionPrimitive): void {
115
+ if (props.disabled) return
116
+ emit('update:modelValue', props.modelValue.filter(selected => selected !== value))
117
+ }
118
+
119
+ function handleFieldKeydown(event: KeyboardEvent): void {
120
+ if (event.key !== 'Enter' && event.key !== ' ' && event.key !== 'ArrowDown') return
121
+ event.preventDefault()
122
+ void openPanel()
59
123
  }
60
124
 
61
- const selectedLabels = computed(() => {
62
- return props.modelValue
63
- .map(v => normalizedOptions.value.find(o => o.value === v)?.label)
64
- .filter(Boolean)
125
+ function handleSearchKeydown(event: KeyboardEvent): void {
126
+ if (event.key === 'ArrowDown') moveActive(1)
127
+ else if (event.key === 'ArrowUp') moveActive(-1)
128
+ else if (event.key === 'Enter') {
129
+ const option = visibleOptions.value[activeIndex.value]
130
+ if (option) toggle(option)
131
+ } else if (event.key === 'Escape') {
132
+ void closePanel(true)
133
+ } else if (event.key === 'Backspace' && query.value === '' && props.modelValue.length > 0) {
134
+ remove(props.modelValue[props.modelValue.length - 1]!)
135
+ } else return
136
+ event.preventDefault()
137
+ }
138
+
139
+ watch(visibleOptions, options => { activeIndex.value = options.length > 0 ? 0 : -1 })
140
+ watch(() => props.disabled, disabled => { if (disabled) void closePanel() })
141
+ useEventListener(() => document, 'pointerdown', event => {
142
+ if (open.value && !root.value?.contains(event.target as Node)) void closePanel()
65
143
  })
66
144
  </script>
67
145
 
68
146
  <template>
69
147
  <div
70
- :class="[
71
- 'mint-multi-select',
72
- `mint-multi-select--${size}`,
73
- disabled ? 'mint-multi-select--disabled' : '',
74
- invalid ? 'mint-multi-select--error' : '',
75
- ]"
148
+ ref="root"
149
+ :class="['mint-multi-select', `mint-multi-select--${size}`, $attrs.class]"
150
+ :style="$attrs.style"
151
+ @focusout="!root?.contains($event.relatedTarget as Node | null) && closePanel()"
76
152
  >
77
153
  <div
78
- v-if="modelValue.length === 0"
79
- class="mint-multi-select__placeholder"
154
+ v-bind="fieldAttrs"
155
+ ref="field"
156
+ role="combobox"
157
+ :tabindex="disabled ? undefined : 0"
158
+ :class="[
159
+ 'mint-multi-select__field',
160
+ {
161
+ 'mint-multi-select__field--open': open,
162
+ 'mint-multi-select__field--error': invalid,
163
+ 'mint-multi-select__field--disabled': disabled,
164
+ },
165
+ ]"
166
+ aria-haspopup="listbox"
167
+ :aria-expanded="open"
168
+ :aria-controls="listboxId"
169
+ :aria-disabled="disabled || undefined"
170
+ :aria-invalid="invalid || undefined"
171
+ :aria-labelledby="ariaLabelledby || undefined"
172
+ :aria-describedby="ariaDescribedby || undefined"
173
+ @click="open ? closePanel() : openPanel()"
174
+ @keydown.self="handleFieldKeydown"
80
175
  >
81
- {{ placeholder }}
176
+ <div class="mint-multi-select__chips">
177
+ <span v-for="chip in chips" :key="String(chip.value)" class="mint-multi-select__chip">
178
+ {{ chip.label }}
179
+ <button
180
+ v-if="!disabled"
181
+ type="button"
182
+ tabindex="-1"
183
+ class="mint-multi-select__chip-remove"
184
+ :aria-label="`Remove ${chip.label}`"
185
+ @click.stop="remove(chip.value)"
186
+ >
187
+ <X :size="11" :stroke-width="2.25" aria-hidden="true" />
188
+ </button>
189
+ </span>
190
+ <span v-if="chips.length === 0" class="mint-multi-select__placeholder">{{ placeholder }}</span>
191
+ </div>
192
+ <ChevronDown class="mint-multi-select__icon" :size="14" aria-hidden="true" />
82
193
  </div>
83
194
 
84
- <div class="mint-multi-select__options" role="group" :aria-invalid="invalid || undefined">
85
- <button
86
- v-for="option in normalizedOptions"
87
- :key="String(option.value)"
88
- type="button"
89
- role="checkbox"
90
- :aria-checked="isSelected(option.value)"
91
- :disabled="disabled || option.disabled || (!isSelected(option.value) && !canSelectMore)"
92
- :class="[
93
- 'mint-multi-select__chip',
94
- `mint-multi-select__chip--${size}`,
95
- isSelected(option.value) ? 'mint-multi-select__chip--active' : '',
96
- option.disabled ? 'mint-multi-select__chip--disabled' : '',
97
- !isSelected(option.value) && !canSelectMore ? 'mint-multi-select__chip--disabled' : '',
98
- ]"
99
- @click="toggleOption(option)"
100
- @keydown="handleKeydown($event, option)"
101
- >
102
- <svg
103
- class="mint-multi-select__chip-check"
104
- viewBox="0 0 24 24"
105
- fill="none"
106
- stroke="currentColor"
107
- stroke-width="2.5"
108
- stroke-linecap="round"
109
- stroke-linejoin="round"
110
- aria-hidden="true"
111
- >
112
- <path d="M20 6 9 17l-5-5" />
195
+ <div v-if="open" class="mint-popover mint-searchable-select__panel">
196
+ <div class="mint-searchable-select__search-wrap">
197
+ <svg viewBox="0 0 20 20" fill="none" stroke="currentColor" aria-hidden="true">
198
+ <circle cx="8.5" cy="8.5" r="5.5" /><path d="m13 13 4 4" />
113
199
  </svg>
114
- <span class="mint-multi-select__chip-label">{{ option.label }}</span>
115
- </button>
116
- </div>
200
+ <input
201
+ ref="search"
202
+ v-model="query"
203
+ role="combobox"
204
+ type="search"
205
+ class="mint-searchable-select__search"
206
+ placeholder="Filter…"
207
+ aria-label="Filter options"
208
+ :aria-expanded="open"
209
+ :aria-controls="listboxId"
210
+ :aria-activedescendant="activeDescendant"
211
+ aria-autocomplete="list"
212
+ @keydown="handleSearchKeydown"
213
+ >
214
+ </div>
117
215
 
118
- <div
119
- v-if="modelValue.length > 0"
120
- class="mint-multi-select__summary"
121
- >
122
- {{ selectedLabels.join(', ') }}
216
+ <ul :id="listboxId" class="mint-searchable-select__list" role="listbox" aria-multiselectable="true">
217
+ <li
218
+ v-for="(option, index) in visibleOptions"
219
+ :id="`${listboxId}-option-${index}`"
220
+ :key="String(option.value)"
221
+ role="option"
222
+ class="mint-menu-item mint-multi-select__option"
223
+ :class="{ 'mint-menu-item--active': activeIndex === index }"
224
+ :aria-selected="selection.isSelected(option.value)"
225
+ :aria-disabled="isUnavailable(option) || undefined"
226
+ @mousedown.prevent
227
+ @mouseenter="!isUnavailable(option) && (activeIndex = index)"
228
+ @click="toggle(option)"
229
+ >
230
+ <span
231
+ :class="['mint-multi-select__check', { 'mint-multi-select__check--on': selection.isSelected(option.value) }]"
232
+ aria-hidden="true"
233
+ >
234
+ <Check v-if="selection.isSelected(option.value)" :size="10" :stroke-width="3.5" />
235
+ </span>
236
+ <span class="mint-menu-item__label">{{ option.label }}</span>
237
+ </li>
238
+ <li v-if="visibleOptions.length === 0" class="mint-searchable-select__empty">No matching options</li>
239
+ </ul>
123
240
  </div>
124
241
  </div>
125
242
  </template>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  /** BaseSelect with `searchable`: a single-select listbox with a search field, descriptions, disabled reasons, and custom slots. */
3
3
  import { computed, nextTick, ref, useAttrs, useId, watch } from 'vue'
4
+ import { ChevronsUpDown } from 'lucide-vue-next'
4
5
  import type { SelectOption, SelectOptionInput } from '../../types'
5
6
  import { useEventListener } from '../../composables/useEventListener'
6
7
  import { normalizeOptionInput } from '../../utils/options'
@@ -176,9 +177,7 @@ useEventListener(() => document, 'pointerdown', event => {
176
177
  <span :class="['mint-searchable-select__value', { 'mint-searchable-select__value--placeholder': !selectedOption }]">
177
178
  {{ selectedOption?.label ?? placeholder }}
178
179
  </span>
179
- <svg viewBox="0 0 20 20" fill="none" stroke="currentColor" aria-hidden="true">
180
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.75" d="m6 8 4 4 4-4" />
181
- </svg>
180
+ <ChevronsUpDown :size="14" aria-hidden="true" />
182
181
  </button>
183
182
 
184
183
  <div v-if="open" class="mint-popover mint-searchable-select__panel">
@@ -12,15 +12,22 @@ import type {
12
12
  import AppLayout from '../AppLayout.vue'
13
13
  import AppTopBar from '../AppTopBar.vue'
14
14
  import type { PluginWorkspaceShellModel } from '../../composables/pluginWorkspaceModel'
15
+ import type { TopBarJob } from '../../types/jobs'
15
16
 
16
17
  defineProps<{
17
18
  shell: PluginWorkspaceShellModel
19
+ /** Passed to AppTopBar; undefined means no jobs control. */
20
+ job?: TopBarJob | null
21
+ jobsDone?: number
22
+ jobsOpen?: boolean
23
+ jobsPanelId?: string
18
24
  }>()
19
25
 
20
26
  const emit = defineEmits<{
21
27
  'pill-select': [item: PillNavItem]
22
28
  'pill-option-select': [option: PillNavOption, item: PillNavItem]
23
29
  'settings-values-change': [data: Record<string, unknown>]
30
+ 'job-click': []
24
31
  }>()
25
32
 
26
33
  function handlePillOptionSelect(option: PillNavOption, item: PillNavItem) {
@@ -58,6 +65,11 @@ defineSlots<{
58
65
  :subtitle="shell.subtitle"
59
66
  :spec="shell.topBar"
60
67
  :pill="shell.pill"
68
+ :job="job"
69
+ :jobs-done="jobsDone"
70
+ :jobs-open="jobsOpen"
71
+ :jobs-panel-id="jobsPanelId"
72
+ @job-click="emit('job-click')"
61
73
  @pill-select="emit('pill-select', $event)"
62
74
  @pill-option-select="handlePillOptionSelect"
63
75
  @settings-values-change="emit('settings-values-change', $event)"
@@ -86,6 +86,8 @@ export interface PluginContract {
86
86
  schemaVersion: 2
87
87
  plugin: {
88
88
  name?: string
89
+ displayName?: string
90
+ subtitle?: string
89
91
  packageName?: string
90
92
  version?: string
91
93
  description?: string
@@ -6,6 +6,7 @@ export { useToast } from './useToast'
6
6
  export { usePlatform } from './usePlatform'
7
7
  export { useExperiment, type ExperimentLoadStatus, type UseExperimentReturn } from './useExperiment'
8
8
  export { resolvePluginFrontendBase } from './pluginFrontendBase'
9
+ export { providePluginIdentity, type PluginIdentityContract } from './pluginIdentity'
9
10
  export {
10
11
  useRequest,
11
12
  type RequestKind,
@@ -0,0 +1,31 @@
1
+ import { reactive, readonly } from 'vue'
2
+ import type { PluginContract } from './createClient'
3
+
4
+ /** The `plugin` block of the generated contract (`./generated/mint-plugin`) that `providePluginIdentity` reads. */
5
+ export interface PluginIdentityContract {
6
+ plugin: Pick<PluginContract['plugin'], 'name' | 'displayName' | 'subtitle' | 'description' | 'icon'>
7
+ }
8
+
9
+ const identity = reactive<{ name?: string; subtitle?: string; icon?: string }>({})
10
+
11
+ /** The seeded identity AppTopBar shows in standalone mode. Internal: not exported from the package. */
12
+ export const pluginIdentity = readonly(identity)
13
+
14
+ /**
15
+ * Seed the standalone AppTopBar with the plugin name, subtitle and icon. In integrated
16
+ * mode the platform supplies them. Explicit `title`/`subtitle` props and the `#icon` slot still win.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { providePluginIdentity } from '@morscherlab/mint-sdk'
21
+ * import { contract } from './generated/mint-plugin'
22
+ *
23
+ * providePluginIdentity(contract)
24
+ * ```
25
+ */
26
+ export function providePluginIdentity(contract: PluginIdentityContract): void {
27
+ const { plugin } = contract
28
+ identity.name = plugin.displayName || plugin.name || undefined
29
+ identity.subtitle = plugin.subtitle || plugin.description || undefined
30
+ identity.icon = plugin.icon || undefined
31
+ }
@@ -50,8 +50,8 @@ export interface PluginWorkspaceShellModel {
50
50
  sidebarCloseLabel: string
51
51
  /** Whether AppLayout should render a `#sidebar` template at all. */
52
52
  hasSidebarSurface: boolean
53
- /** App or plugin title shown in the top bar. */
54
- title: string
53
+ /** Top bar title. Unset, the top bar shows the plugin identity (`providePluginIdentity` or the platform). */
54
+ title?: string
55
55
  /** Secondary copy shown under the title. */
56
56
  subtitle?: string
57
57
  /** AppTopBar spec with the workspace defaults applied and the generated pills filled in; `false` removes the top bar. */
@@ -23,12 +23,19 @@ export interface UseAppExperimentOptions {
23
23
  onDetach?: () => void
24
24
  saveDisabled?: Ref<boolean> | ComputedRef<boolean>
25
25
  saveDisabledMessage?: string | Ref<string | undefined> | ComputedRef<string | undefined>
26
+ /** Unsaved changes; marks the Save button with a dot. */
27
+ dirty?: Ref<boolean> | ComputedRef<boolean>
26
28
  }
27
29
 
28
30
  export interface AppExperimentPopoverBinding {
29
31
  experimentName?: string
30
32
  experimentCode?: string
31
33
  experimentStatus?: ExperimentStatus
34
+ experimentId?: number | null
35
+ experimentProject?: string
36
+ experimentOwner?: string
37
+ /** ISO timestamp of the experiment's last update. */
38
+ experimentUpdatedAt?: string
32
39
  /** An experiment is bound but its record has not arrived yet. */
33
40
  isResolving: boolean
34
41
  /** Resolution failed; the binding is kept so the user can retry or detach. */
@@ -39,6 +46,7 @@ export interface AppExperimentPopoverBinding {
39
46
  saveDisabledMessage?: string
40
47
  saveLoading: boolean
41
48
  saveSuccessMessage?: string
49
+ dirty?: boolean
42
50
  }
43
51
 
44
52
  export interface AppExperimentSelectorModalBinding {
@@ -125,10 +133,19 @@ export function useAppExperiment(options: UseAppExperimentOptions = {}): UseAppE
125
133
  const showDetach = computed(() => experimentId.value !== null)
126
134
  const saveDisabled = computed(() => toValue(options.saveDisabled) ?? false)
127
135
  const saveDisabledMessage = computed(() => toValue(options.saveDisabledMessage))
136
+ const dirty = computed(() => toValue(options.dirty) ?? false)
137
+ const experimentOwner = computed(() => {
138
+ const owner = store.current?.created_by_user
139
+ return owner ? [owner.first_name, owner.last_name].filter(Boolean).join(' ') || owner.username : undefined
140
+ })
128
141
  const popover = computed<AppExperimentPopoverBinding>(() => ({
129
142
  experimentName: experimentName.value,
130
143
  experimentCode: experimentCode.value,
131
144
  experimentStatus: experimentStatus.value,
145
+ experimentId: experimentId.value,
146
+ experimentProject: store.current?.project_name || store.current?.project || undefined,
147
+ experimentOwner: experimentOwner.value,
148
+ experimentUpdatedAt: store.current?.updated_at,
132
149
  isResolving: isResolving.value,
133
150
  error: error.value ?? undefined,
134
151
  showSave: showSave.value,
@@ -137,6 +154,7 @@ export function useAppExperiment(options: UseAppExperimentOptions = {}): UseAppE
137
154
  saveDisabledMessage: saveDisabledMessage.value,
138
155
  saveLoading: saveLoading.value,
139
156
  saveSuccessMessage: saveSuccessMessage.value,
157
+ dirty: dirty.value,
140
158
  }))
141
159
  const selectorModal = computed<AppExperimentSelectorModalBinding>(() => ({
142
160
  open: showModal.value,
@@ -166,6 +184,8 @@ export function useAppExperiment(options: UseAppExperimentOptions = {}): UseAppE
166
184
  saveLoading.value = true
167
185
  try {
168
186
  const message = await options.onSave()
187
+ // The save changed the record on the platform; read it back so "Updated" is current.
188
+ await store.refresh()
169
189
  if (message) {
170
190
  saveSuccessMessage.value = message
171
191
  if (successTimer) clearTimeout(successTimer)
@@ -26,9 +26,9 @@ export interface UseCalendarGridReturn {
26
26
  nextMonth: () => void
27
27
  }
28
28
 
29
- const WEEK_DAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'] as const
29
+ const WEEK_DAYS = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'] as const
30
30
 
31
- /** Shared calendar grid state for date and datetime pickers. */
31
+ /** Shared Monday-first calendar grid state for date and datetime pickers. */
32
32
  export function useCalendarGrid(options: UseCalendarGridOptions = {}): UseCalendarGridReturn {
33
33
  const currentMonth = ref(new Date())
34
34
 
@@ -46,7 +46,8 @@ export function useCalendarGrid(options: UseCalendarGridOptions = {}): UseCalend
46
46
  const lastDay = new Date(year, month + 1, 0)
47
47
  const days: CalendarGridDay[] = []
48
48
 
49
- const startPadding = firstDay.getDay()
49
+ // Weeks start on Monday.
50
+ const startPadding = (firstDay.getDay() + 6) % 7
50
51
  for (let i = startPadding - 1; i >= 0; i--) {
51
52
  const date = new Date(year, month, -i)
52
53
  days.push({ date, isCurrentMonth: false, isDisabled: isDateDisabled(date) })