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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -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) })
@@ -28,6 +28,8 @@ export interface UseExperimentSelectorOptions {
28
28
  apiBaseUrl?: string
29
29
  limit?: number
30
30
  immediate?: boolean
31
+ /** Keep `statusCounts` in step with the non-status filters (one stats request per change). */
32
+ withStatusCounts?: boolean
31
33
  }
32
34
 
33
35
  export interface UseExperimentSelectorReturn {
@@ -57,9 +59,8 @@ export interface UseExperimentSelectorReturn {
57
59
  experimentTypes: Ref<ExperimentTypeOption[]>
58
60
  /** Available project options for filter UI. */
59
61
  projects: Ref<SelectOption<string>[]>
60
- // Grouped view
61
- /** Fetched experiments grouped by project name. */
62
- groupedByProject: ComputedRef<[string, ExperimentSummary[]][]>
62
+ /** Experiment count per status under every filter except status itself. */
63
+ statusCounts: Ref<Record<string, number>>
63
64
  // Methods
64
65
  /** Fetch the current page with the current filters. */
65
66
  fetch: () => Promise<void>
@@ -73,6 +74,8 @@ export interface UseExperimentSelectorReturn {
73
74
  clear: () => void
74
75
  /** Fetch experiment type and project filter options. */
75
76
  fetchFilterOptions: () => Promise<void>
77
+ /** Fetch `statusCounts` from `GET /experiments/stats`. */
78
+ fetchStatusCounts: () => Promise<void>
76
79
  }
77
80
 
78
81
  /** Fetches a paginated, filtered experiment list from the platform API for picker and selector UIs. */
@@ -85,6 +88,7 @@ export function useExperimentSelector(
85
88
  experimentType,
86
89
  allowedExperimentTypes: optionAllowedExperimentTypes,
87
90
  apiBaseUrl,
91
+ withStatusCounts = false,
88
92
  } = options
89
93
  const platformBase = apiBaseUrl ?? getPlatformApiUrl()
90
94
  const api = useApi()
@@ -107,6 +111,10 @@ export function useExperimentSelector(
107
111
  const projects = ref<SelectOption<string>[]>([])
108
112
  let filterOptionsFetched = false
109
113
 
114
+ const statusCounts = ref<Record<string, number>>({})
115
+ let statsGeneration = 0
116
+ let countedScope: string | null = null
117
+
110
118
  const hasMore = computed(() => experiments.value.length < total.value)
111
119
 
112
120
  const filters: ExperimentFilters = reactive({
@@ -115,6 +123,7 @@ export function useExperimentSelector(
115
123
  project: undefined,
116
124
  experimentType: undefined,
117
125
  datePreset: undefined,
126
+ mine: undefined,
118
127
  })
119
128
 
120
129
  function normalizeExperimentTypes(values: string[] | null | undefined): string[] | null {
@@ -163,21 +172,27 @@ export function useExperimentSelector(
163
172
  return { kind: 'all' }
164
173
  }
165
174
 
166
- function buildExperimentListUrl(typeFilter: string | undefined, skip: number, pageLimit: number): string {
175
+ /** Query params shared by the list and the stats route; status, sort and paging are list-only. */
176
+ function scopeParams(typeFilter: string | undefined): URLSearchParams {
167
177
  const params = new URLSearchParams()
168
178
  if (typeFilter) params.set('experiment_type', typeFilter)
169
- if (filters.status) params.set('status', filters.status)
170
179
  if (filters.search) params.set('search', filters.search)
171
180
  if (filters.project) params.set('project', filters.project)
181
+ if (filters.mine) params.set('mine', 'true')
182
+ if (filters.datePreset) {
183
+ params.set('created_after', datePresetToISO(filters.datePreset))
184
+ }
185
+ return params
186
+ }
187
+
188
+ function buildExperimentListUrl(typeFilter: string | undefined, skip: number, pageLimit: number): string {
189
+ const params = scopeParams(typeFilter)
190
+ if (filters.status) params.set('status', filters.status)
172
191
 
173
192
  const { sortBy, sortOrder } = parseSortKey()
174
193
  params.set('sort_by', sortBy)
175
194
  params.set('sort_order', sortOrder)
176
195
 
177
- if (filters.datePreset) {
178
- params.set('created_after', datePresetToISO(filters.datePreset))
179
- }
180
-
181
196
  params.set('limit', String(pageLimit))
182
197
  params.set('skip', String(skip))
183
198
 
@@ -298,6 +313,51 @@ export function useExperimentSelector(
298
313
  }
299
314
  }
300
315
 
316
+ /** The filters the stats route reads; status, sort and paging do not change the counts. */
317
+ function countScope(): string {
318
+ return JSON.stringify([filters.search, filters.project, filters.experimentType, filters.datePreset, filters.mine])
319
+ }
320
+
321
+ function refreshStatusCountsOnScopeChange(): void {
322
+ if (withStatusCounts && countScope() !== countedScope) fetchStatusCounts()
323
+ }
324
+
325
+ async function fetchStatusCounts(): Promise<void> {
326
+ const generation = ++statsGeneration
327
+ countedScope = countScope()
328
+ const scope = resolveExperimentTypeScope()
329
+ if (scope.kind === 'blocked') {
330
+ statusCounts.value = {}
331
+ return
332
+ }
333
+ const types = scope.kind === 'multi'
334
+ ? scope.experimentTypes
335
+ : [scope.kind === 'single' ? scope.experimentType : undefined]
336
+ const base = platformBase ?? ''
337
+ try {
338
+ const responses = await Promise.all(
339
+ types.map(type => api.get<{ total: number; by_status: Record<string, number> }>(
340
+ `${base}/experiments/stats?${scopeParams(type)}`,
341
+ )),
342
+ )
343
+ if (generation !== statsGeneration) return
344
+ const counts: Record<string, number> = {}
345
+ for (const response of responses) {
346
+ for (const [status, count] of Object.entries(response.by_status)) {
347
+ counts[status] = (counts[status] ?? 0) + count
348
+ }
349
+ }
350
+ statusCounts.value = counts
351
+ } catch {
352
+ // Counts only decorate the status menu; without them it shows bare labels.
353
+ // Forget the scope so the next status or sort change retries the request.
354
+ if (generation === statsGeneration) {
355
+ statusCounts.value = {}
356
+ countedScope = null
357
+ }
358
+ }
359
+ }
360
+
301
361
  async function loadMore(): Promise<void> {
302
362
  if (!hasMore.value || isLoading.value) return
303
363
  page.value++
@@ -323,31 +383,12 @@ export function useExperimentSelector(
323
383
  filters.project = undefined
324
384
  filters.experimentType = undefined
325
385
  filters.datePreset = undefined
386
+ filters.mine = undefined
326
387
  sortKey.value = 'created_at:desc'
327
388
  page.value = 0
328
389
  request.clearError()
329
390
  }
330
391
 
331
- // Group experiments by project (client-side)
332
- const groupedByProject = computed<[string, ExperimentSummary[]][]>(() => {
333
- const groups = new Map<string, ExperimentSummary[]>()
334
- for (const exp of experiments.value) {
335
- const key = exp.project_name ?? exp.project ?? 'No project'
336
- const list = groups.get(key)
337
- if (list) {
338
- list.push(exp)
339
- } else {
340
- groups.set(key, [exp])
341
- }
342
- }
343
- // Sort alphabetically, "No project" last
344
- return [...groups.entries()].sort(([a], [b]) => {
345
- if (a === 'No project') return 1
346
- if (b === 'No project') return -1
347
- return a.localeCompare(b)
348
- })
349
- })
350
-
351
392
  watch(
352
393
  () => [
353
394
  filters.search,
@@ -355,6 +396,7 @@ export function useExperimentSelector(
355
396
  filters.project,
356
397
  filters.experimentType,
357
398
  filters.datePreset,
399
+ filters.mine,
358
400
  sortKey.value,
359
401
  ],
360
402
  () => invalidateFetch(),
@@ -366,17 +408,20 @@ export function useExperimentSelector(
366
408
  () => {
367
409
  page.value = 0
368
410
  fetchExperiments()
411
+ refreshStatusCountsOnScopeChange()
369
412
  },
370
413
  { delay: 300 },
371
414
  )
372
415
 
373
416
  // Immediate watch on discrete filters and sort (cancel any pending search debounce)
374
417
  watch(
375
- () => [filters.status, filters.project, filters.experimentType, filters.datePreset, sortKey.value],
418
+ () => [filters.status, filters.project, filters.experimentType, filters.datePreset, filters.mine, sortKey.value],
376
419
  () => {
377
420
  searchWatch.cancel()
378
421
  page.value = 0
379
422
  fetchExperiments()
423
+ // Also checked on a status or sort change: the cancelled debounce may hold a new search.
424
+ refreshStatusCountsOnScopeChange()
380
425
  },
381
426
  )
382
427
 
@@ -397,12 +442,13 @@ export function useExperimentSelector(
397
442
  sortKey,
398
443
  experimentTypes,
399
444
  projects,
400
- groupedByProject,
445
+ statusCounts,
401
446
  fetch: fetchExperiments,
402
447
  loadMore,
403
448
  reset,
404
449
  select,
405
450
  clear,
406
451
  fetchFilterOptions,
452
+ fetchStatusCounts,
407
453
  }
408
454
  }
@@ -1,4 +1,5 @@
1
1
  import { ref, shallowRef, type Ref, type ShallowRef } from 'vue'
2
+ import type { SheetData } from 'read-excel-file/universal'
2
3
  import { readErrorMessage } from '../utils/errors'
3
4
 
4
5
  export interface DelimitedTable {
@@ -116,6 +117,53 @@ export function readFileAsText(file: File, encoding?: string): Promise<string> {
116
117
  })
117
118
  }
118
119
 
120
+ /**
121
+ * Read a `.csv`, `.tsv` or `.xlsx` upload as comma-delimited CSV text.
122
+ * A `.csv` file (or any other extension) comes back unchanged. An `.xlsx`
123
+ * file gives its first sheet; each cell is its stored value, so number
124
+ * formats such as leading zeros are not applied. Dates become ISO text.
125
+ */
126
+ export async function readTableFileAsCsv(file: File): Promise<string> {
127
+ const name = file.name.toLowerCase()
128
+ if (name.endsWith('.xlsx')) return rowsToCsv(await readFirstSheet(file))
129
+ const text = await readFileAsText(file)
130
+ return name.endsWith('.tsv') ? rowsToCsv(parseDelimitedRows(text, '\t')) : text
131
+ }
132
+
133
+ async function readFirstSheet(file: File): Promise<string[][]> {
134
+ // Lazy import: plugins that never read .xlsx do not load the library.
135
+ const { readSheet } = await import('read-excel-file/universal')
136
+ let rows: SheetData<string>
137
+ try {
138
+ // Keep the stored text: no trim, and no float parse that rounds big numbers.
139
+ rows = await readSheet(file, { trim: false, parseNumber: text => text })
140
+ } catch {
141
+ throw new Error(`${file.name} is not a valid .xlsx workbook.`)
142
+ }
143
+ return rows.map(row => row.map(formatCell))
144
+ }
145
+
146
+ function formatCell(value: unknown): string {
147
+ if (value === null || value === undefined) return ''
148
+ if (typeof value === 'boolean') return value ? 'TRUE' : 'FALSE'
149
+ if (value instanceof Date) {
150
+ // Excel stores dates as float day counts; round off the float error.
151
+ const [date, time] = new Date(Math.round(value.getTime() / 1000) * 1000)
152
+ .toISOString().slice(0, 19).split('T')
153
+ if (date === '1899-12-30') return time // a time-only cell
154
+ return time === '00:00:00' ? date : `${date}T${time}`
155
+ }
156
+ return String(value)
157
+ }
158
+
159
+ function rowsToCsv(rows: readonly (readonly string[])[]): string {
160
+ return rows.map(row => row.map(quoteCsvField).join(',')).join('\n')
161
+ }
162
+
163
+ function quoteCsvField(value: string): string {
164
+ return /[",\r\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value
165
+ }
166
+
119
167
  export function parseDelimitedText(
120
168
  text: string,
121
169
  options: ParseDelimitedTextOptions = {},
@@ -170,8 +218,10 @@ function parseDelimitedRows(text: string, delimiter: string): string[][] {
170
218
  if (quoted && next === '"') {
171
219
  field += '"'
172
220
  index++
173
- } else {
221
+ } else if (quoted || field === '') {
174
222
  quoted = !quoted
223
+ } else {
224
+ field += '"' // a quote inside an unquoted field is text
175
225
  }
176
226
  continue
177
227
  }
@@ -0,0 +1,107 @@
1
+ import {
2
+ autoUpdate,
3
+ computePosition,
4
+ flip,
5
+ hide,
6
+ offset,
7
+ shift,
8
+ size,
9
+ type Placement,
10
+ type ReferenceElement,
11
+ } from '@floating-ui/dom'
12
+ import { onScopeDispose, ref, toValue, watch, type MaybeRefOrGetter, type Ref } from 'vue'
13
+
14
+ // Internal: not exported from the package entry points.
15
+
16
+ export interface UseFloatingPanelOptions {
17
+ /** Default `'bottom-start'`. */
18
+ placement?: MaybeRefOrGetter<Placement>
19
+ /** Gap to the reference in px. Default 4. */
20
+ offset?: number
21
+ /** At least the reference width, at most 28rem (selects). */
22
+ matchWidth?: boolean
23
+ /** Re-place on scroll, resize and layout shift while open. Default `true`. */
24
+ follow?: boolean
25
+ }
26
+
27
+ export interface UseFloatingPanelReturn {
28
+ /** Bind to the panel: `position: fixed`, `left`, `top`, and `visibility: hidden` while the reference is scrolled out of view. */
29
+ style: Ref<Record<string, string>>
30
+ /** The placement after flip, for arrows and transitions. */
31
+ placement: Ref<Placement>
32
+ update: () => Promise<void>
33
+ }
34
+
35
+ /**
36
+ * Places `floating` next to `reference` with fixed positioning (offset, flip,
37
+ * shift, size, hide). A fixed panel escapes the `overflow` of its ancestors (a
38
+ * modal body) unless one of them has a transform, so it can stay in the DOM right
39
+ * after its trigger: Tab order, focus traps and outside-click checks are those of
40
+ * an in-place panel. The size middleware publishes the free height as
41
+ * `--mint-floating-available-height`; a panel whose list scrolls caps its
42
+ * `max-height` with it.
43
+ */
44
+ export function useFloatingPanel(
45
+ reference: MaybeRefOrGetter<ReferenceElement | null>,
46
+ floating: Ref<HTMLElement | null>,
47
+ open: Ref<boolean>,
48
+ options: UseFloatingPanelOptions = {},
49
+ ): UseFloatingPanelReturn {
50
+ const style = ref<Record<string, string>>({ position: 'fixed', left: '0px', top: '0px' })
51
+ const placement = ref<Placement>(toValue(options.placement) ?? 'bottom-start')
52
+
53
+ async function update(): Promise<void> {
54
+ const referenceEl = toValue(reference)
55
+ const floatingEl = floating.value
56
+ if (!referenceEl || !floatingEl) return
57
+ const result = await computePosition(referenceEl, floatingEl, {
58
+ placement: toValue(options.placement) ?? 'bottom-start',
59
+ strategy: 'fixed',
60
+ middleware: [
61
+ offset(options.offset ?? 4),
62
+ flip(),
63
+ shift({ padding: 8 }),
64
+ size({
65
+ padding: 8,
66
+ // Written to the element, not the reactive style: floating-ui measures again right after.
67
+ apply({ availableHeight, rects, elements }) {
68
+ const target = elements.floating.style
69
+ target.setProperty('--mint-floating-available-height', `${Math.max(0, availableHeight)}px`)
70
+ if (options.matchWidth) {
71
+ target.minWidth = `${rects.reference.width}px`
72
+ target.maxWidth = '28rem'
73
+ }
74
+ },
75
+ }),
76
+ hide(),
77
+ ],
78
+ })
79
+ const next: Record<string, string> = { position: 'fixed', left: `${result.x}px`, top: `${result.y}px` }
80
+ // The trigger scrolled out of its scroll container: keep the panel open, but not floating detached.
81
+ if (result.middlewareData.hide?.referenceHidden) next.visibility = 'hidden'
82
+ // autoUpdate fires on every scroll; re-render only when the panel changes.
83
+ const current = style.value
84
+ if (current.left !== next.left || current.top !== next.top || current.visibility !== next.visibility) style.value = next
85
+ placement.value = result.placement
86
+ }
87
+
88
+ let stop: (() => void) | undefined
89
+ function release(): void {
90
+ stop?.()
91
+ stop = undefined
92
+ }
93
+
94
+ watch(
95
+ [open, () => toValue(reference), floating],
96
+ ([isOpen, referenceEl, floatingEl]) => {
97
+ release()
98
+ if (!isOpen || !referenceEl || !floatingEl) return
99
+ if (options.follow ?? true) stop = autoUpdate(referenceEl, floatingEl, () => void update())
100
+ else void update()
101
+ },
102
+ { flush: 'post', immediate: true },
103
+ )
104
+ onScopeDispose(release)
105
+
106
+ return { style, placement, update }
107
+ }