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

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 (353) hide show
  1. package/README.md +14 -3
  2. package/dist/{ActionMenu-CScINahU.js → AlertBox-DwgNTEqf.js} +40 -209
  3. package/dist/AlertBox-DwgNTEqf.js.map +1 -0
  4. package/dist/{AppSidebar-BmiiPfz9.js → AppSidebar-CZZaxni0.js} +1249 -239
  5. package/dist/AppSidebar-CZZaxni0.js.map +1 -0
  6. package/dist/{ConfirmDialog-CJ7hqYUP.js → ConfirmDialog-CjWoxAD1.js} +2 -2
  7. package/dist/{ConfirmDialog-CJ7hqYUP.js.map → ConfirmDialog-CjWoxAD1.js.map} +1 -1
  8. package/dist/ExperimentPopover-DujiuQIO.js +337 -0
  9. package/dist/ExperimentPopover-DujiuQIO.js.map +1 -0
  10. package/dist/{ExperimentSelectorModal-D-9z7cNx.js → ExperimentSelectorModal-B4j4eUOJ.js} +244 -211
  11. package/dist/ExperimentSelectorModal-B4j4eUOJ.js.map +1 -0
  12. package/dist/{SegmentedControl-DERBN-Xd.js → SegmentedControl-DyUPH3I5.js} +2 -2
  13. package/dist/{SegmentedControl-DERBN-Xd.js.map → SegmentedControl-DyUPH3I5.js.map} +1 -1
  14. package/dist/{SettingsModal-CSLE31yg.js → SettingsModal-D2MOOtuO.js} +28 -27
  15. package/dist/SettingsModal-D2MOOtuO.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__/components/FloatingPanelsInModal.test.d.ts +1 -0
  19. package/dist/__tests__/composables/useFloatingPanel.test.d.ts +1 -0
  20. package/dist/__tests__/composables/useOnboarding.test.d.ts +1 -0
  21. package/dist/__tests__/styles/paletteTokens.test.d.ts +1 -0
  22. package/dist/__tests__/styles/v2AuditRules.test.d.ts +1 -0
  23. package/dist/components/AppSidebar.vue.d.ts +1 -1
  24. package/dist/components/AppTopBar.vue.d.ts +20 -5
  25. package/dist/components/ArtifactSaveDialog.vue.d.ts +9 -1
  26. package/dist/components/AuditTrail.vue.d.ts +1 -1
  27. package/dist/components/BaseButton.vue.d.ts +4 -2
  28. package/dist/components/BaseSelect.vue.d.ts +2 -2
  29. package/dist/components/ChartContainer.vue.d.ts +2 -0
  30. package/dist/components/DatePicker.vue.d.ts +1 -1
  31. package/dist/components/ExperimentPopover.vue.d.ts +18 -0
  32. package/dist/components/ExperimentSelectorModal.vue.d.ts +15 -5
  33. package/dist/components/FilePicker.vue.d.ts +1 -1
  34. package/dist/components/FileUploader.vue.d.ts +2 -2
  35. package/dist/components/JobsStatusTray.vue.d.ts +15 -4
  36. package/dist/components/PluginIcon.vue.d.ts +2 -2
  37. package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
  38. package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
  39. package/dist/components/SampleSelector.vue.d.ts +6 -6
  40. package/dist/components/SequenceProgressBar.vue.d.ts +3 -0
  41. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +3 -3
  42. package/dist/components/StepWizard.vue.d.ts +3 -0
  43. package/dist/components/internal/AppTopBarOnboardingInternal.vue.d.ts +7 -0
  44. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -1
  45. package/dist/components/internal/AutoGroupModalInternal.vue.d.ts +3 -3
  46. package/dist/components/internal/DatePickerTimeInternal.vue.d.ts +1 -1
  47. package/dist/components/internal/OnboardingLayerInternal.vue.d.ts +7 -0
  48. package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +2 -7
  49. package/dist/components/internal/SelectMultipleInternal.vue.d.ts +4 -0
  50. package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
  51. package/dist/composables/createClient.d.ts +2 -0
  52. package/dist/composables/index.d.ts +1 -0
  53. package/dist/composables/pluginIdentity.d.ts +24 -0
  54. package/dist/composables/pluginWorkspaceModel.d.ts +2 -2
  55. package/dist/composables/useAppExperiment.d.ts +8 -0
  56. package/dist/composables/useCalendarGrid.d.ts +1 -1
  57. package/dist/composables/useExperimentSelector.d.ts +6 -2
  58. package/dist/composables/useFileImport.d.ts +7 -0
  59. package/dist/composables/useFloatingPanel.d.ts +29 -0
  60. package/dist/composables/useJobCenter.d.ts +41 -3
  61. package/dist/composables/useOnboarding.d.ts +119 -0
  62. package/dist/composables/usePlatform.d.ts +1 -0
  63. package/dist/{controlSchemaModel-CP5-_nAj.js → controlSchemaModel-Bsg0t5Ab.js} +2 -2
  64. package/dist/{controlSchemaModel-CP5-_nAj.js.map → controlSchemaModel-Bsg0t5Ab.js.map} +1 -1
  65. package/dist/{data-C2PwI_dn.js → data-Boeh4uPI.js} +180 -125
  66. package/dist/data-Boeh4uPI.js.map +1 -0
  67. package/dist/data.d.ts +1 -1
  68. package/dist/data.js +9 -9
  69. package/dist/{experiment-43OJtH7R.js → experiment-DyO54FQB.js} +25 -61
  70. package/dist/experiment-DyO54FQB.js.map +1 -0
  71. package/dist/{experimentStatus-BZmU4czV.js → experimentStatus-DCn2QKG7.js} +3 -3
  72. package/dist/{experimentStatus-BZmU4czV.js.map → experimentStatus-DCn2QKG7.js.map} +1 -1
  73. package/dist/index.js +20 -20
  74. package/dist/{install-DAID6c2-.js → install-BEHp7Mo-.js} +15 -14
  75. package/dist/{install-DAID6c2-.js.map → install-BEHp7Mo-.js.map} +1 -1
  76. package/dist/install.js +1 -1
  77. package/dist/{lab-DLwRl_Ql.js → lab-D61eaX6f.js} +136 -148
  78. package/dist/lab-D61eaX6f.js.map +1 -0
  79. package/dist/lab.js +4 -3
  80. package/dist/{records-BzTcxekG.js → records-D4aQXyZu.js} +647 -389
  81. package/dist/records-D4aQXyZu.js.map +1 -0
  82. package/dist/{runtime-D6gVat6r.js → runtime-q-R0CNLh.js} +417 -54
  83. package/dist/runtime-q-R0CNLh.js.map +1 -0
  84. package/dist/runtime.d.ts +1 -1
  85. package/dist/runtime.js +7 -7
  86. package/dist/{shell-5ju97nOS.js → shell-6CGFvUUw.js} +10 -5
  87. package/dist/{shell-5ju97nOS.js.map → shell-6CGFvUUw.js.map} +1 -1
  88. package/dist/shell.d.ts +1 -0
  89. package/dist/shell.js +6 -6
  90. package/dist/stores/index.d.ts +1 -1
  91. package/dist/stores/index.js +2 -2
  92. package/dist/stores/settings.d.ts +4 -12
  93. package/dist/styles.css +2753 -2101
  94. package/dist/types/componentWorkflowTypes.d.ts +2 -0
  95. package/dist/types/components.d.ts +2 -0
  96. package/dist/types/generated/platformContext.d.ts +1 -0
  97. package/dist/types/generated-ui.d.ts +4 -0
  98. package/dist/types/index.d.ts +1 -1
  99. package/dist/types/jobs.d.ts +9 -0
  100. package/dist/types/platform.d.ts +1 -1
  101. package/dist/{BaseModal-DfRIuVoj.js → useDropdownState-uFIz6Yv8.js} +75 -5
  102. package/dist/useDropdownState-uFIz6Yv8.js.map +1 -0
  103. package/dist/{useExperimentSamples-CxcnxZ8h.js → useExperimentSamples-QEFoq4tf.js} +48 -6
  104. package/dist/useExperimentSamples-QEFoq4tf.js.map +1 -0
  105. package/dist/{useListReorder-CcbEYxX-.js → useListReorder-DsuzwWGS.js} +219 -437
  106. package/dist/useListReorder-DsuzwWGS.js.map +1 -0
  107. package/dist/{useRackEditor-BD5NIo5s.js → useRackEditor-KpZ4iJ6o.js} +85 -81
  108. package/dist/useRackEditor-KpZ4iJ6o.js.map +1 -0
  109. package/dist/useRealtime-B58Lo-Tm.js +508 -0
  110. package/dist/useRealtime-B58Lo-Tm.js.map +1 -0
  111. package/dist/useRequest-BuDmW8Jc.js +215 -0
  112. package/dist/useRequest-BuDmW8Jc.js.map +1 -0
  113. package/dist/utils/time.d.ts +7 -0
  114. package/package.json +3 -2
  115. package/src/__stories__/experiment-helpers.ts +3 -3
  116. package/src/__tests__/components/ActionMenu.test.ts +2 -2
  117. package/src/__tests__/components/AppTopBar.test.ts +257 -3
  118. package/src/__tests__/components/AppTopBarOnboarding.test.ts +160 -0
  119. package/src/__tests__/components/AppTopBarPageSelector.test.ts +125 -9
  120. package/src/__tests__/components/AppTopBarPillNav.test.ts +35 -0
  121. package/src/__tests__/components/ArtifactSaveDialog.test.ts +78 -0
  122. package/src/__tests__/components/BaseButton.test.ts +19 -0
  123. package/src/__tests__/components/BaseModal.test.ts +13 -0
  124. package/src/__tests__/components/BaseSelect.multiple.test.ts +87 -32
  125. package/src/__tests__/components/BaseSelect.test.ts +2 -1
  126. package/src/__tests__/components/BatchProgressList.test.ts +16 -0
  127. package/src/__tests__/components/CalendarGridPanel.test.ts +2 -2
  128. package/src/__tests__/components/ChartContainer.test.ts +11 -0
  129. package/src/__tests__/components/ChemicalFormula.test.ts +6 -3
  130. package/src/__tests__/components/DatePicker.datetime.test.ts +6 -0
  131. package/src/__tests__/components/DatePicker.test.ts +21 -0
  132. package/src/__tests__/components/DatePicker.time.test.ts +2 -2
  133. package/src/__tests__/components/EmptyState.test.ts +1 -1
  134. package/src/__tests__/components/ExperimentPopover.test.ts +125 -16
  135. package/src/__tests__/components/ExperimentSelectorModal.test.ts +153 -14
  136. package/src/__tests__/components/FloatingPanelsInModal.test.ts +94 -0
  137. package/src/__tests__/components/GeneratedPluginView.test.ts +23 -2
  138. package/src/__tests__/components/InstrumentStateBadge.test.ts +4 -4
  139. package/src/__tests__/components/JobsStatusTray.test.ts +373 -71
  140. package/src/__tests__/components/PluginWorkspaceView.test.ts +63 -1
  141. package/src/__tests__/components/ResourcePickerListInternal.test.ts +0 -20
  142. package/src/__tests__/components/SampleSelector.test.ts +33 -8
  143. package/src/__tests__/components/SequenceProgressBar.test.ts +12 -0
  144. package/src/__tests__/components/SettingsModal.test.ts +9 -2
  145. package/src/__tests__/components/StepWizard.test.ts +11 -0
  146. package/src/__tests__/components/WellPlate.decoration.test.ts +11 -4
  147. package/src/__tests__/components/deprecationTags.test.ts +1 -1
  148. package/src/__tests__/composables/useAppExperiment.test.ts +27 -1
  149. package/src/__tests__/composables/useCalendarGrid.test.ts +4 -2
  150. package/src/__tests__/composables/useExperimentSelector.test.ts +111 -0
  151. package/src/__tests__/composables/useFileImport.test.ts +36 -0
  152. package/src/__tests__/composables/useFloatingPanel.test.ts +68 -0
  153. package/src/__tests__/composables/useJobCenter.test.ts +262 -3
  154. package/src/__tests__/composables/useOnboarding.test.ts +317 -0
  155. package/src/__tests__/docs/frontendDocsCatalog.test.ts +4 -3
  156. package/src/__tests__/fixtures/table-import/cells.xlsx +0 -0
  157. package/src/__tests__/fixtures/table-import/samples.xlsx +0 -0
  158. package/src/__tests__/stores/settings.test.ts +21 -21
  159. package/src/__tests__/styles/baseControlsRules.test.ts +6 -2
  160. package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
  161. package/src/__tests__/styles/cssTokens.test.ts +6 -3
  162. package/src/__tests__/styles/menusShellRefresh.test.ts +22 -12
  163. package/src/__tests__/styles/paletteTokens.test.ts +44 -0
  164. package/src/__tests__/styles/v2AuditRules.test.ts +104 -0
  165. package/src/__tests__/utils/plotlyTemplate.test.ts +4 -4
  166. package/src/__tests__/utils/time.test.ts +18 -1
  167. package/src/components/ActionMenu.vue +6 -37
  168. package/src/components/AdductText.story.vue +7 -0
  169. package/src/components/AdductText.vue +1 -1
  170. package/src/components/AlertBox.story.vue +2 -2
  171. package/src/components/AlertBox.vue +18 -16
  172. package/src/components/AppSidebar.story.vue +0 -28
  173. package/src/components/AppTopBar.onboarding.story.vue +69 -0
  174. package/src/components/AppTopBar.story.vue +105 -15
  175. package/src/components/AppTopBar.vue +248 -122
  176. package/src/components/ArtifactSaveDialog.story.vue +23 -0
  177. package/src/components/ArtifactSaveDialog.vue +102 -12
  178. package/src/components/AuditTrail.story.vue +2 -1
  179. package/src/components/AuditTrail.vue +2 -21
  180. package/src/components/Avatar.vue +10 -14
  181. package/src/components/BaseButton.vue +16 -2
  182. package/src/components/BaseModal.vue +2 -1
  183. package/src/components/BaseSelect.multiple.story.vue +13 -2
  184. package/src/components/BaseSelect.vue +4 -3
  185. package/src/components/BatchProgressList.vue +35 -25
  186. package/src/components/ChartContainer.story.vue +2 -1
  187. package/src/components/ChartContainer.vue +8 -6
  188. package/src/components/ConfirmDialog.story.vue +1 -1
  189. package/src/components/EmptyState.vue +1 -1
  190. package/src/components/ExperimentPopover.story.vue +165 -53
  191. package/src/components/ExperimentPopover.vue +169 -187
  192. package/src/components/ExperimentSelectorModal.story.vue +73 -31
  193. package/src/components/ExperimentSelectorModal.vue +144 -111
  194. package/src/components/FormField.story.vue +8 -48
  195. package/src/components/GeneratedPluginView.vue +11 -10
  196. package/src/components/GeneratedResultRenderer.vue +3 -4
  197. package/src/components/InstrumentStateBadge.vue +1 -1
  198. package/src/components/JobsStatusTray.story.vue +97 -43
  199. package/src/components/JobsStatusTray.vue +287 -290
  200. package/src/components/LayoutResizeHandle.story.vue +65 -4
  201. package/src/components/LoadingSpinner.vue +1 -1
  202. package/src/components/NumberInput.vue +3 -6
  203. package/src/components/PluginIcon.story.vue +2 -10
  204. package/src/components/PluginIcon.vue +2 -2
  205. package/src/components/PluginWorkspaceView.props.ts +3 -0
  206. package/src/components/PluginWorkspaceView.story.vue +1 -1
  207. package/src/components/PluginWorkspaceView.vue +17 -0
  208. package/src/components/RuntimeUpdateNotice.vue +1 -1
  209. package/src/components/SampleSelector.story.vue +22 -7
  210. package/src/components/SampleSelector.vue +34 -37
  211. package/src/components/SampleSelectorGroupNode.vue +1 -1
  212. package/src/components/SectionCard.story.vue +35 -2
  213. package/src/components/SequenceProgressBar.story.vue +9 -0
  214. package/src/components/SequenceProgressBar.vue +6 -1
  215. package/src/components/SettingsModal.schema.ts +1 -1
  216. package/src/components/SettingsModal.vue +9 -14
  217. package/src/components/SmartGroupFieldRecipe.story.vue +1 -1
  218. package/src/components/SmartGroupManual.story.vue +1 -1
  219. package/src/components/StepWizard.story.vue +33 -0
  220. package/src/components/StepWizard.vue +8 -1
  221. package/src/components/Tooltip.vue +9 -32
  222. package/src/components/WellPlate.story.vue +3 -1
  223. package/src/components/WellPlate.vue +7 -0
  224. package/src/components/internal/AppTopBarOnboardingInternal.vue +172 -0
  225. package/src/components/internal/AppTopBarPageSelectorInternal.vue +140 -72
  226. package/src/components/internal/AppTopBarPillNavInternal.vue +33 -2
  227. package/src/components/internal/DatePickerDateInternal.vue +23 -85
  228. package/src/components/internal/DatePickerDateTimeInternal.vue +10 -14
  229. package/src/components/internal/DatePickerTimeInternal.vue +13 -9
  230. package/src/components/internal/NumberUnitInputInternal.vue +4 -18
  231. package/src/components/internal/OnboardingLayerInternal.vue +173 -0
  232. package/src/components/internal/ResourcePickerListInternal.vue +45 -85
  233. package/src/components/internal/SelectMultipleInternal.vue +187 -65
  234. package/src/components/internal/SelectSearchableInternal.vue +8 -4
  235. package/src/components/internal/WellEditPopupInternal.vue +11 -22
  236. package/src/components/internal/WorkspaceShellInternal.vue +12 -0
  237. package/src/composables/createClient.ts +2 -0
  238. package/src/composables/index.ts +1 -0
  239. package/src/composables/pluginIdentity.ts +31 -0
  240. package/src/composables/pluginWorkspaceModel.ts +2 -2
  241. package/src/composables/useAppExperiment.ts +20 -0
  242. package/src/composables/useCalendarGrid.ts +4 -3
  243. package/src/composables/useExperimentSelector.ts +77 -31
  244. package/src/composables/useFileImport.ts +51 -1
  245. package/src/composables/useFloatingPanel.ts +107 -0
  246. package/src/composables/useJobCenter.ts +188 -6
  247. package/src/composables/useOnboarding.ts +465 -0
  248. package/src/composables/usePluginWorkspace.ts +1 -1
  249. package/src/data.ts +1 -0
  250. package/src/runtime.ts +2 -1
  251. package/src/shell.ts +17 -0
  252. package/src/stores/index.ts +0 -2
  253. package/src/stores/settings.ts +20 -50
  254. package/src/styles/components/action-menu.css +1 -1
  255. package/src/styles/components/adduct-text.css +13 -5
  256. package/src/styles/components/alert-box.css +39 -58
  257. package/src/styles/components/app-layout.css +10 -6
  258. package/src/styles/components/app-page-selector.css +121 -44
  259. package/src/styles/components/app-pill-nav.css +11 -12
  260. package/src/styles/components/app-sidebar.css +9 -18
  261. package/src/styles/components/app-top-bar.css +219 -55
  262. package/src/styles/components/artifact-save-dialog.css +153 -2
  263. package/src/styles/components/audit-trail.css +16 -19
  264. package/src/styles/components/avatar.css +6 -4
  265. package/src/styles/components/batch-progress-list.css +38 -36
  266. package/src/styles/components/button.css +36 -59
  267. package/src/styles/components/calendar.css +12 -12
  268. package/src/styles/components/chart-container.css +34 -23
  269. package/src/styles/components/checkbox.css +2 -12
  270. package/src/styles/components/collapsible-card.css +27 -29
  271. package/src/styles/components/confirm-dialog.css +13 -14
  272. package/src/styles/components/dataframe.css +13 -5
  273. package/src/styles/components/date-picker.css +84 -94
  274. package/src/styles/components/datetime-picker.css +35 -47
  275. package/src/styles/components/divider.css +20 -20
  276. package/src/styles/components/dose-calculator.css +3 -3
  277. package/src/styles/components/empty-state.css +20 -17
  278. package/src/styles/components/experiment-code-badge.css +2 -0
  279. package/src/styles/components/experiment-popover.css +174 -329
  280. package/src/styles/components/experiment-selector-modal.css +88 -56
  281. package/src/styles/components/file-picker.css +3 -3
  282. package/src/styles/components/file-uploader.css +38 -49
  283. package/src/styles/components/form-builder.css +3 -3
  284. package/src/styles/components/formula-input.css +1 -1
  285. package/src/styles/components/icon-button.css +18 -24
  286. package/src/styles/components/input.css +5 -4
  287. package/src/styles/components/instrument-monitor.css +13 -15
  288. package/src/styles/components/jobs-status-tray.css +140 -296
  289. package/src/styles/components/layout-resize-handle.css +59 -16
  290. package/src/styles/components/modal.css +5 -7
  291. package/src/styles/components/molecule-input.css +2 -2
  292. package/src/styles/components/multi-select.css +65 -117
  293. package/src/styles/components/number-input.css +26 -19
  294. package/src/styles/components/onboarding.css +368 -0
  295. package/src/styles/components/pill.css +3 -2
  296. package/src/styles/components/plate-editor.css +0 -1
  297. package/src/styles/components/plugin-icon.css +26 -23
  298. package/src/styles/components/popover.css +2 -2
  299. package/src/styles/components/presence-avatars.css +1 -1
  300. package/src/styles/components/progress-bar.css +3 -3
  301. package/src/styles/components/protocol-step-editor.css +1 -3
  302. package/src/styles/components/resource-card.css +2 -2
  303. package/src/styles/components/resource-picker-list.css +6 -57
  304. package/src/styles/components/sample-hierarchy-tree.css +4 -4
  305. package/src/styles/components/sample-selector.css +138 -79
  306. package/src/styles/components/schedule-calendar.css +8 -8
  307. package/src/styles/components/searchable-select.css +15 -19
  308. package/src/styles/components/section-card.css +54 -49
  309. package/src/styles/components/segmented-control.css +18 -15
  310. package/src/styles/components/select.css +3 -3
  311. package/src/styles/components/sequence-input.css +2 -2
  312. package/src/styles/components/sequence-progress-bar.css +17 -8
  313. package/src/styles/components/settings-modal.css +7 -2
  314. package/src/styles/components/smart-group.css +18 -18
  315. package/src/styles/components/step-wizard.css +52 -16
  316. package/src/styles/components/tabs.css +2 -2
  317. package/src/styles/components/textarea.css +3 -3
  318. package/src/styles/components/time-picker.css +37 -25
  319. package/src/styles/components/toast.css +13 -11
  320. package/src/styles/components/toggle.css +1 -11
  321. package/src/styles/components/tooltip.css +12 -11
  322. package/src/styles/components/unit-input.css +9 -8
  323. package/src/styles/components/well-edit-popup.css +2 -2
  324. package/src/styles/components/well-plate.css +9 -2
  325. package/src/styles/index.css +1 -0
  326. package/src/styles/variables.css +247 -78
  327. package/src/types/componentWorkflowTypes.ts +2 -0
  328. package/src/types/components.ts +2 -0
  329. package/src/types/generated/platformContext.ts +1 -0
  330. package/src/types/generated-ui.ts +4 -0
  331. package/src/types/index.ts +1 -0
  332. package/src/types/jobs.ts +10 -0
  333. package/src/types/platform.ts +1 -1
  334. package/src/utils/time.ts +21 -0
  335. package/dist/ActionMenu-CScINahU.js.map +0 -1
  336. package/dist/AppSidebar-BmiiPfz9.js.map +0 -1
  337. package/dist/BaseModal-DfRIuVoj.js.map +0 -1
  338. package/dist/ExperimentPopover-C4sGw-is.js +0 -359
  339. package/dist/ExperimentPopover-C4sGw-is.js.map +0 -1
  340. package/dist/ExperimentSelectorModal-D-9z7cNx.js.map +0 -1
  341. package/dist/SettingsModal-CSLE31yg.js.map +0 -1
  342. package/dist/data-C2PwI_dn.js.map +0 -1
  343. package/dist/experiment-43OJtH7R.js.map +0 -1
  344. package/dist/lab-DLwRl_Ql.js.map +0 -1
  345. package/dist/records-BzTcxekG.js.map +0 -1
  346. package/dist/runtime-D6gVat6r.js.map +0 -1
  347. package/dist/useExperimentSamples-CxcnxZ8h.js.map +0 -1
  348. package/dist/useListReorder-CcbEYxX-.js.map +0 -1
  349. package/dist/useRackEditor-BD5NIo5s.js.map +0 -1
  350. package/dist/useRealtime-PalKue84.js +0 -696
  351. package/dist/useRealtime-PalKue84.js.map +0 -1
  352. package/dist/useRequest-D5JDfsgZ.js +0 -76
  353. package/dist/useRequest-D5JDfsgZ.js.map +0 -1
@@ -1,11 +1,11 @@
1
1
  <script setup lang="ts" generic="T">
2
2
  /**
3
- * Searchable, optionally grouped single-select list shared by the experiment and
3
+ * Searchable single-select list shared by the experiment and
4
4
  * artifact selector modals: two-line rows (title, then code + meta) with status and date
5
5
  * in a right-hand column, a filled selected row, skeleton,
6
6
  * error and empty states, and the combobox keyboard model (↑ ↓ Enter).
7
7
  */
8
- import { computed, nextTick, reactive, ref, useId, watch } from 'vue'
8
+ import { computed, nextTick, ref, useId, watch } from 'vue'
9
9
  import BaseInput from '../BaseInput.vue'
10
10
  import Skeleton from '../Skeleton.vue'
11
11
  import EmptyState from '../EmptyState.vue'
@@ -26,8 +26,6 @@ export interface ResourcePickerRow {
26
26
  const props = withDefaults(
27
27
  defineProps<{
28
28
  items: T[]
29
- /** When set, rows render under collapsible group headers in this order. */
30
- groups?: { label: string; items: T[] }[] | null
31
29
  itemKey: (item: T) => string | number
32
30
  describe: (item: T) => ResourcePickerRow
33
31
  selectedKey?: string | number | null
@@ -39,9 +37,10 @@ const props = withDefaults(
39
37
  error?: string | null
40
38
  emptyTitle?: string
41
39
  emptyDescription?: string
40
+ /** Focus the search field when the list mounts (the modal's focus trap honours `autofocus`). */
41
+ autofocusSearch?: boolean
42
42
  }>(),
43
43
  {
44
- groups: null,
45
44
  selectedKey: null,
46
45
  placeholder: 'Search…',
47
46
  loading: false,
@@ -57,50 +56,33 @@ const emit = defineEmits<{
57
56
  defineSlots<{
58
57
  /** Extra filters on the search row. */
59
58
  filters?: () => unknown
60
- /** A second filter row under the search. */
61
- advanced?: () => unknown
62
59
  /** Row controls before the check; keys pressed inside them never select the row. */
63
60
  actions?: (props: { item: T }) => unknown
64
61
  }>()
65
62
 
66
63
  const listId = `mint-resource-picker-${useId().replace(/:/g, '')}`
67
- const collapsed = reactive(new Set<string>())
68
64
  const activeIndex = ref(-1)
69
65
  const listRef = ref<HTMLElement | null>(null)
70
66
 
71
- const visibleGroups = computed(() =>
72
- props.groups?.map((group) => ({
73
- ...group,
74
- rows: collapsed.has(group.label) ? [] : group.items,
75
- })),
76
- )
77
- /** Keyboard order: the rows on screen, top to bottom. */
78
- const flat = computed(() =>
79
- visibleGroups.value ? visibleGroups.value.flatMap((group) => group.rows) : props.items,
80
- )
81
- const indexByKey = computed(() => new Map(flat.value.map((item, i) => [props.itemKey(item), i])))
67
+ const indexByKey = computed(() => new Map(props.items.map((item, i) => [props.itemKey(item), i])))
82
68
  const showList = computed(() => !props.loading && !props.error && props.items.length > 0)
83
69
 
84
70
  function optionId(item: T): string {
85
71
  return `${listId}-${props.itemKey(item)}`
86
72
  }
87
73
  const activeDescendant = computed(() => {
88
- const item = flat.value[activeIndex.value]
74
+ const item = props.items[activeIndex.value]
89
75
  return item === undefined ? undefined : optionId(item)
90
76
  })
91
77
 
92
78
  function indexOf(item: T): number {
93
79
  return indexByKey.value.get(props.itemKey(item)) ?? -1
94
80
  }
95
- function toggleGroup(label: string) {
96
- if (collapsed.has(label)) collapsed.delete(label)
97
- else collapsed.add(label)
98
- }
99
81
  function keydown(event: KeyboardEvent) {
100
- // Keys belong to the search combobox; a focused header, filter or row action keeps its own Enter.
82
+ // Keys belong to the search combobox; a focused filter or row action keeps its own Enter.
101
83
  const target = event.target as HTMLElement | null
102
84
  if (target !== event.currentTarget && !target?.closest('.mint-resource-picker__search')) return
103
- const rows = flat.value
85
+ const rows = props.items
104
86
  if (!rows.length) return
105
87
  if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
106
88
  event.preventDefault()
@@ -120,7 +102,7 @@ function keydown(event: KeyboardEvent) {
120
102
  }
121
103
 
122
104
  watch(
123
- () => [props.items, props.groups],
105
+ () => props.items,
124
106
  () => {
125
107
  activeIndex.value = -1
126
108
  },
@@ -136,6 +118,7 @@ watch(
136
118
  :placeholder="placeholder"
137
119
  size="sm"
138
120
  type="search"
121
+ :autofocus="autofocusSearch"
139
122
  role="combobox"
140
123
  :aria-label="`Search ${label.toLowerCase()}`"
141
124
  :aria-expanded="showList"
@@ -151,7 +134,6 @@ watch(
151
134
  </BaseInput>
152
135
  <slot name="filters" />
153
136
  </div>
154
- <slot name="advanced" />
155
137
 
156
138
  <div v-if="loading" class="mint-resource-picker__frame" aria-busy="true">
157
139
  <div v-for="n in 4" :key="n" class="mint-resource-picker__skeleton-row">
@@ -174,64 +156,42 @@ watch(
174
156
  role="listbox"
175
157
  :aria-label="label"
176
158
  >
177
- <template v-for="group in visibleGroups ?? [{ label: '', items, rows: items }]" :key="group.label">
178
- <button
179
- v-if="visibleGroups"
180
- type="button"
181
- class="mint-resource-picker__group"
182
- :aria-expanded="!collapsed.has(group.label)"
183
- @click="toggleGroup(group.label)"
184
- >
185
- <svg
186
- class="mint-resource-picker__chevron"
187
- viewBox="0 0 16 16"
188
- fill="none"
189
- stroke="currentColor"
190
- stroke-width="1.5"
191
- aria-hidden="true"
159
+ <div
160
+ v-for="item in items"
161
+ :id="optionId(item)"
162
+ :key="itemKey(item)"
163
+ role="option"
164
+ :aria-selected="itemKey(item) === selectedKey"
165
+ class="mint-resource-picker__row"
166
+ :class="{
167
+ 'mint-resource-picker__row--selected': itemKey(item) === selectedKey,
168
+ 'mint-resource-picker__row--active': indexOf(item) === activeIndex,
169
+ }"
170
+ @click="emit('select', item)"
171
+ @mouseenter="activeIndex = indexOf(item)"
172
+ >
173
+ <span class="mint-resource-picker__text">
174
+ <span class="mint-resource-picker__title">{{ describe(item).title }}</span>
175
+ <span v-if="describe(item).code || describe(item).meta?.length" class="mint-resource-picker__meta"
176
+ ><span v-if="describe(item).code" class="mint-resource-picker__code mint-exp-code mint-exp-code--sm">{{
177
+ describe(item).code
178
+ }}</span
179
+ ><span v-if="describe(item).meta?.length">{{ describe(item).meta!.join(' · ') }}</span></span
192
180
  >
193
- <path d="M4 6l4 4 4-4" />
194
- </svg>
195
- <span class="mint-resource-picker__group-label">{{ group.label }}</span>
196
- <span class="mint-resource-picker__group-count">{{ group.items.length }}</span>
197
- </button>
198
- <div
199
- v-for="item in group.rows"
200
- :id="optionId(item)"
201
- :key="itemKey(item)"
202
- role="option"
203
- :aria-selected="itemKey(item) === selectedKey"
204
- class="mint-resource-picker__row"
205
- :class="{
206
- 'mint-resource-picker__row--selected': itemKey(item) === selectedKey,
207
- 'mint-resource-picker__row--active': indexOf(item) === activeIndex,
208
- }"
209
- @click="emit('select', item)"
210
- @mouseenter="activeIndex = indexOf(item)"
211
- >
212
- <span class="mint-resource-picker__text">
213
- <span class="mint-resource-picker__title">{{ describe(item).title }}</span>
214
- <span v-if="describe(item).code || describe(item).meta?.length" class="mint-resource-picker__meta"
215
- ><span v-if="describe(item).code" class="mint-resource-picker__code mint-exp-code mint-exp-code--sm">{{
216
- describe(item).code
217
- }}</span
218
- ><span v-if="describe(item).meta?.length">{{ describe(item).meta!.join(' · ') }}</span></span
219
- >
220
- </span>
221
- <span v-if="describe(item).status || describe(item).date" class="mint-resource-picker__aside">
222
- <span
223
- v-if="describe(item).status"
224
- class="mint-resource-picker__status"
225
- :class="`mint-resource-picker__status--${describe(item).status!.tone}`"
226
- >{{ describe(item).status!.label }}</span
227
- >
228
- <span v-if="describe(item).date" class="mint-resource-picker__date">{{ describe(item).date }}</span>
229
- </span>
230
- <span v-if="$slots.actions" class="mint-resource-picker__actions" @click.stop>
231
- <slot name="actions" :item="item" />
232
- </span>
233
- </div>
234
- </template>
181
+ </span>
182
+ <span v-if="describe(item).status || describe(item).date" class="mint-resource-picker__aside">
183
+ <span
184
+ v-if="describe(item).status"
185
+ class="mint-resource-picker__status"
186
+ :class="`mint-resource-picker__status--${describe(item).status!.tone}`"
187
+ >{{ describe(item).status!.label }}</span
188
+ >
189
+ <span v-if="describe(item).date" class="mint-resource-picker__date">{{ describe(item).date }}</span>
190
+ </span>
191
+ <span v-if="$slots.actions" class="mint-resource-picker__actions" @click.stop>
192
+ <slot name="actions" :item="item" />
193
+ </span>
194
+ </div>
235
195
  </div>
236
196
  </div>
237
197
  </template>
@@ -1,7 +1,10 @@
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'
7
+ import { useFloatingPanel } from '../../composables/useFloatingPanel'
5
8
  import { useListSelection } from '../../composables/useListSelection'
6
9
  import { normalizeOptionInput } from '../../utils/options'
7
10
 
@@ -15,6 +18,8 @@ interface Props {
15
18
  invalid?: boolean
16
19
  max?: number
17
20
  size?: 'sm' | 'md' | 'lg'
21
+ ariaLabelledby?: string
22
+ ariaDescribedby?: string
18
23
  }
19
24
 
20
25
  const props = withDefaults(defineProps<Props>(), {
@@ -23,103 +28,220 @@ const props = withDefaults(defineProps<Props>(), {
23
28
  invalid: false,
24
29
  max: undefined,
25
30
  size: 'md',
31
+ ariaLabelledby: undefined,
32
+ ariaDescribedby: undefined,
33
+ })
34
+
35
+ defineOptions({ inheritAttrs: false })
36
+
37
+ // The wrapper takes class and style; every other attribute (aria-label, data-*) names the focusable field.
38
+ const attrs = useAttrs()
39
+ const fieldAttrs = computed(() => {
40
+ const { class: _class, style: _style, ...rest } = attrs
41
+ return rest
26
42
  })
27
43
 
28
44
  const emit = defineEmits<{
29
45
  'update:modelValue': [value: OptionPrimitive[]]
30
46
  }>()
31
47
 
48
+ const id = useId().replace(/:/g, '')
49
+ const listboxId = `mint-multi-select-${id}`
50
+ const root = ref<HTMLElement | null>(null)
51
+ const field = ref<HTMLElement | null>(null)
52
+ const search = ref<HTMLInputElement | null>(null)
53
+ const panel = ref<HTMLElement | null>(null)
54
+ const open = ref(false)
55
+ const query = ref('')
56
+ const activeIndex = ref(-1)
57
+
32
58
  const normalizedOptions = computed<MultiOption[]>(() => props.options.map(normalizeOptionInput<OptionPrimitive, MultiOption>))
33
- const optionSelection = useListSelection({
59
+ const selection = useListSelection({
34
60
  selected: () => props.modelValue,
35
61
  items: normalizedOptions,
36
62
  getValue: option => option.value,
37
63
  disabled: () => props.disabled,
38
64
  max: () => props.max,
39
65
  })
40
- const canSelectMore = optionSelection.canAddMore
41
66
 
42
- function isSelected(value: OptionPrimitive): boolean {
43
- return optionSelection.isSelected(value)
67
+ const chips = computed(() => props.modelValue.map(value => ({
68
+ value,
69
+ label: normalizedOptions.value.find(option => option.value === value)?.label ?? String(value),
70
+ })))
71
+ const visibleOptions = computed(() => {
72
+ const needle = query.value.trim().toLocaleLowerCase()
73
+ if (!needle) return normalizedOptions.value
74
+ return normalizedOptions.value.filter(option => option.label.toLocaleLowerCase().includes(needle))
75
+ })
76
+ const activeDescendant = computed(() =>
77
+ activeIndex.value >= 0 ? `${listboxId}-option-${activeIndex.value}` : undefined,
78
+ )
79
+
80
+ function isUnavailable(option: MultiOption): boolean {
81
+ return !!option.disabled || (!selection.isSelected(option.value) && !selection.canAddMore.value)
44
82
  }
45
83
 
46
- function toggleOption(option: MultiOption) {
47
- if (props.disabled || option.disabled) return
84
+ async function openPanel(): Promise<void> {
85
+ if (props.disabled || open.value) return
86
+ query.value = ''
87
+ open.value = true
88
+ activeIndex.value = visibleOptions.value.length > 0 ? 0 : -1
89
+ await nextTick()
90
+ search.value?.focus()
91
+ }
48
92
 
49
- if (isSelected(option.value) || canSelectMore.value) {
50
- emit('update:modelValue', optionSelection.toggleValue(option.value))
93
+ async function closePanel(restoreFocus = false): Promise<void> {
94
+ open.value = false
95
+ query.value = ''
96
+ activeIndex.value = -1
97
+ if (restoreFocus) {
98
+ await nextTick()
99
+ field.value?.focus()
51
100
  }
52
101
  }
53
102
 
54
- function handleKeydown(event: KeyboardEvent, option: MultiOption) {
55
- if (event.key === 'Enter' || event.key === ' ') {
56
- event.preventDefault()
57
- toggleOption(option)
58
- }
103
+ function moveActive(direction: 1 | -1): void {
104
+ const count = visibleOptions.value.length
105
+ if (count === 0) return
106
+ const start = activeIndex.value < 0 ? (direction === 1 ? -1 : count) : activeIndex.value
107
+ activeIndex.value = (start + direction + count) % count
108
+ void nextTick(() => document.getElementById(activeDescendant.value ?? '')?.scrollIntoView({ block: 'nearest' }))
109
+ }
110
+
111
+ function toggle(option: MultiOption): void {
112
+ if (props.disabled || isUnavailable(option)) return
113
+ emit('update:modelValue', selection.toggleValue(option.value))
114
+ }
115
+
116
+ function remove(value: OptionPrimitive): void {
117
+ if (props.disabled) return
118
+ emit('update:modelValue', props.modelValue.filter(selected => selected !== value))
59
119
  }
60
120
 
61
- const selectedLabels = computed(() => {
62
- return props.modelValue
63
- .map(v => normalizedOptions.value.find(o => o.value === v)?.label)
64
- .filter(Boolean)
121
+ function handleFieldKeydown(event: KeyboardEvent): void {
122
+ if (event.key !== 'Enter' && event.key !== ' ' && event.key !== 'ArrowDown') return
123
+ event.preventDefault()
124
+ void openPanel()
125
+ }
126
+
127
+ function handleSearchKeydown(event: KeyboardEvent): void {
128
+ if (event.key === 'ArrowDown') moveActive(1)
129
+ else if (event.key === 'ArrowUp') moveActive(-1)
130
+ else if (event.key === 'Enter') {
131
+ const option = visibleOptions.value[activeIndex.value]
132
+ if (option) toggle(option)
133
+ } else if (event.key === 'Escape') {
134
+ void closePanel(true)
135
+ } else if (event.key === 'Backspace' && query.value === '' && props.modelValue.length > 0) {
136
+ remove(props.modelValue[props.modelValue.length - 1]!)
137
+ } else return
138
+ event.preventDefault()
139
+ }
140
+
141
+ // Fixed next to the field, so a modal body's overflow does not clip it; it stays in place in the DOM.
142
+ const { style: panelStyle } = useFloatingPanel(field, panel, open, { matchWidth: true })
143
+
144
+ watch(visibleOptions, options => { activeIndex.value = options.length > 0 ? 0 : -1 })
145
+ watch(() => props.disabled, disabled => { if (disabled) void closePanel() })
146
+ useEventListener(() => document, 'pointerdown', event => {
147
+ if (open.value && !root.value?.contains(event.target as Node)) void closePanel()
65
148
  })
66
149
  </script>
67
150
 
68
151
  <template>
69
152
  <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
- ]"
153
+ ref="root"
154
+ :class="['mint-multi-select', `mint-multi-select--${size}`, $attrs.class]"
155
+ :style="$attrs.style"
156
+ @focusout="!root?.contains($event.relatedTarget as Node | null) && closePanel()"
76
157
  >
77
158
  <div
78
- v-if="modelValue.length === 0"
79
- class="mint-multi-select__placeholder"
159
+ v-bind="fieldAttrs"
160
+ ref="field"
161
+ role="combobox"
162
+ :tabindex="disabled ? undefined : 0"
163
+ :class="[
164
+ 'mint-multi-select__field',
165
+ {
166
+ 'mint-multi-select__field--open': open,
167
+ 'mint-multi-select__field--error': invalid,
168
+ 'mint-multi-select__field--disabled': disabled,
169
+ },
170
+ ]"
171
+ aria-haspopup="listbox"
172
+ :aria-expanded="open"
173
+ :aria-controls="listboxId"
174
+ :aria-disabled="disabled || undefined"
175
+ :aria-invalid="invalid || undefined"
176
+ :aria-labelledby="ariaLabelledby || undefined"
177
+ :aria-describedby="ariaDescribedby || undefined"
178
+ @click="open ? closePanel() : openPanel()"
179
+ @keydown.self="handleFieldKeydown"
80
180
  >
81
- {{ placeholder }}
181
+ <div class="mint-multi-select__chips">
182
+ <span v-for="chip in chips" :key="String(chip.value)" class="mint-multi-select__chip">
183
+ {{ chip.label }}
184
+ <button
185
+ v-if="!disabled"
186
+ type="button"
187
+ tabindex="-1"
188
+ class="mint-multi-select__chip-remove"
189
+ :aria-label="`Remove ${chip.label}`"
190
+ @click.stop="remove(chip.value)"
191
+ >
192
+ <X :size="11" :stroke-width="2.25" aria-hidden="true" />
193
+ </button>
194
+ </span>
195
+ <span v-if="chips.length === 0" class="mint-multi-select__placeholder">{{ placeholder }}</span>
196
+ </div>
197
+ <ChevronDown class="mint-multi-select__icon" :size="14" aria-hidden="true" />
82
198
  </div>
83
199
 
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" />
200
+ <div v-if="open" ref="panel" class="mint-popover mint-searchable-select__panel" :style="panelStyle">
201
+ <div class="mint-searchable-select__search-wrap">
202
+ <svg viewBox="0 0 20 20" fill="none" stroke="currentColor" aria-hidden="true">
203
+ <circle cx="8.5" cy="8.5" r="5.5" /><path d="m13 13 4 4" />
113
204
  </svg>
114
- <span class="mint-multi-select__chip-label">{{ option.label }}</span>
115
- </button>
116
- </div>
205
+ <input
206
+ ref="search"
207
+ v-model="query"
208
+ role="combobox"
209
+ type="search"
210
+ class="mint-searchable-select__search"
211
+ placeholder="Filter…"
212
+ aria-label="Filter options"
213
+ :aria-expanded="open"
214
+ :aria-controls="listboxId"
215
+ :aria-activedescendant="activeDescendant"
216
+ aria-autocomplete="list"
217
+ @keydown="handleSearchKeydown"
218
+ >
219
+ </div>
117
220
 
118
- <div
119
- v-if="modelValue.length > 0"
120
- class="mint-multi-select__summary"
121
- >
122
- {{ selectedLabels.join(', ') }}
221
+ <ul :id="listboxId" class="mint-searchable-select__list" role="listbox" aria-multiselectable="true">
222
+ <li
223
+ v-for="(option, index) in visibleOptions"
224
+ :id="`${listboxId}-option-${index}`"
225
+ :key="String(option.value)"
226
+ role="option"
227
+ class="mint-menu-item mint-multi-select__option"
228
+ :class="{ 'mint-menu-item--active': activeIndex === index }"
229
+ :aria-selected="selection.isSelected(option.value)"
230
+ :aria-disabled="isUnavailable(option) || undefined"
231
+ @mousedown.prevent
232
+ @mouseenter="!isUnavailable(option) && (activeIndex = index)"
233
+ @click="toggle(option)"
234
+ >
235
+ <span
236
+ :class="['mint-multi-select__check', { 'mint-multi-select__check--on': selection.isSelected(option.value) }]"
237
+ aria-hidden="true"
238
+ >
239
+ <Check v-if="selection.isSelected(option.value)" :size="10" :stroke-width="3.5" />
240
+ </span>
241
+ <span class="mint-menu-item__label">{{ option.label }}</span>
242
+ </li>
243
+ <li v-if="visibleOptions.length === 0" class="mint-searchable-select__empty">No matching options</li>
244
+ </ul>
123
245
  </div>
124
246
  </div>
125
247
  </template>
@@ -1,8 +1,10 @@
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'
7
+ import { useFloatingPanel } from '../../composables/useFloatingPanel'
6
8
  import { normalizeOptionInput } from '../../utils/options'
7
9
 
8
10
  interface Props {
@@ -52,6 +54,7 @@ const triggerId = `${listboxId}-trigger`
52
54
  const root = ref<HTMLElement | null>(null)
53
55
  const trigger = ref<HTMLButtonElement | null>(null)
54
56
  const search = ref<HTMLInputElement | null>(null)
57
+ const panel = ref<HTMLElement | null>(null)
55
58
  const open = ref(false)
56
59
  const query = ref('')
57
60
  const activeIndex = ref(-1)
@@ -143,6 +146,9 @@ function handleTriggerKeydown(event: KeyboardEvent): void {
143
146
  })
144
147
  }
145
148
 
149
+ // Fixed next to the trigger, so a modal body's overflow does not clip it; it stays in place in the DOM.
150
+ const { style: panelStyle } = useFloatingPanel(trigger, panel, open, { matchWidth: true })
151
+
146
152
  watch(visibleOptions, options => { activeIndex.value = firstEnabledIndex(options) })
147
153
  watch(() => props.disabled, disabled => { if (disabled) void closePanel() })
148
154
  useEventListener(() => document, 'pointerdown', event => {
@@ -176,12 +182,10 @@ useEventListener(() => document, 'pointerdown', event => {
176
182
  <span :class="['mint-searchable-select__value', { 'mint-searchable-select__value--placeholder': !selectedOption }]">
177
183
  {{ selectedOption?.label ?? placeholder }}
178
184
  </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>
185
+ <ChevronsUpDown :size="14" aria-hidden="true" />
182
186
  </button>
183
187
 
184
- <div v-if="open" class="mint-popover mint-searchable-select__panel">
188
+ <div v-if="open" ref="panel" class="mint-popover mint-searchable-select__panel" :style="panelStyle">
185
189
  <div class="mint-searchable-select__search-wrap">
186
190
  <svg viewBox="0 0 20 20" fill="none" stroke="currentColor" aria-hidden="true">
187
191
  <circle cx="8.5" cy="8.5" r="5.5" /><path d="m13 13 4 4" />
@@ -1,9 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  /** Single-well inspector for WellPlate / PlateEditor, composed from SDK fields (3b). */
3
3
  import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
4
- import { computePosition, flip, shift } from '@floating-ui/dom'
5
4
  import { ChevronRight, X } from 'lucide-vue-next'
6
5
  import type { Well, WellEditData, WellEditField } from '../../types'
6
+ import { useFloatingPanel } from '../../composables/useFloatingPanel'
7
7
  import { useFocusTrap } from '../../composables/useFocusTrap'
8
8
  import { sampleTypeMarker } from '../../utils/sampleTypeMarker'
9
9
  import { DEFAULT_SAMPLE_TYPE_COLORS } from '../WellPlate.colors'
@@ -157,28 +157,18 @@ function handleInlineEnter(event: KeyboardEvent) {
157
157
  emit('close')
158
158
  }
159
159
 
160
- // Placement: flip and clamp to the viewport. Transparent (not hidden) until
161
- // placed, so the focus trap can move focus in on mount.
160
+ // Placement at the clicked point: flip and clamp to the viewport. Inline, the
161
+ // inspector stays in place and is not positioned.
162
162
  const containerRef = ref<HTMLElement | null>(null)
163
- const panelStyle = ref<Record<string, string>>({ opacity: '0' })
164
-
165
- async function place() {
166
- const panel = containerRef.value
167
- if (!panel || props.inline) return
163
+ const pointAnchor = () => {
164
+ if (props.inline) return null
168
165
  const { x, y } = props.position
169
- const anchor = {
170
- getBoundingClientRect: () => ({ x, y, left: x, top: y, right: x, bottom: y, width: 0, height: 0 }),
171
- }
172
- const placed = await computePosition(anchor, panel, {
173
- placement: 'right-start',
174
- strategy: 'fixed',
175
- middleware: [flip(), shift({ padding: 8 })],
176
- })
177
- panelStyle.value = { left: `${placed.x}px`, top: `${placed.y}px` }
166
+ return { getBoundingClientRect: () => ({ x, y, left: x, top: y, right: x, bottom: y, width: 0, height: 0 }) }
178
167
  }
179
-
180
- watch(() => props.position, place)
181
- watch(showAdvanced, () => nextTick(place))
168
+ const { style: panelStyle } = useFloatingPanel(pointAnchor, containerRef, computed(() => !props.inline), {
169
+ placement: 'right-start',
170
+ offset: 0,
171
+ })
182
172
 
183
173
  // The parent mounts and unmounts the popup, so the trap follows the lifecycle,
184
174
  // the same way BaseModal drives it off `modelValue`.
@@ -200,7 +190,6 @@ watch(() => props.wellId, () => { if (props.inline) focusInline() }, { flush: 'p
200
190
  onMounted(() => {
201
191
  if (props.inline) return focusInline()
202
192
  trapActive.value = true
203
- void place()
204
193
  // Runs after the trap's own first-element focus: start in the name field.
205
194
  void nextTick(() => containerRef.value?.querySelector<HTMLInputElement>('[data-field="label"] input')?.focus())
206
195
  })
@@ -223,7 +212,7 @@ onBeforeUnmount(() => {
223
212
  :aria-label="`Edit well ${wellId}`"
224
213
  :tabindex="inline ? undefined : -1"
225
214
  @click="inline || $event.stopPropagation()"
226
- @keydown.escape="commit(); emit('close')"
215
+ @keydown.escape.prevent="commit(); emit('close')"
227
216
  @keydown="inline && handleInlineEnter($event)"
228
217
  @focusout="handleFocusout"
229
218
  >
@@ -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)"