@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.
- package/README.md +14 -3
- package/dist/{ActionMenu-CScINahU.js → AlertBox-DwgNTEqf.js} +40 -209
- package/dist/AlertBox-DwgNTEqf.js.map +1 -0
- package/dist/{AppSidebar-BmiiPfz9.js → AppSidebar-CZZaxni0.js} +1249 -239
- package/dist/AppSidebar-CZZaxni0.js.map +1 -0
- package/dist/{ConfirmDialog-CJ7hqYUP.js → ConfirmDialog-CjWoxAD1.js} +2 -2
- package/dist/{ConfirmDialog-CJ7hqYUP.js.map → ConfirmDialog-CjWoxAD1.js.map} +1 -1
- package/dist/ExperimentPopover-DujiuQIO.js +337 -0
- package/dist/ExperimentPopover-DujiuQIO.js.map +1 -0
- package/dist/{ExperimentSelectorModal-D-9z7cNx.js → ExperimentSelectorModal-B4j4eUOJ.js} +244 -211
- package/dist/ExperimentSelectorModal-B4j4eUOJ.js.map +1 -0
- package/dist/{SegmentedControl-DERBN-Xd.js → SegmentedControl-DyUPH3I5.js} +2 -2
- package/dist/{SegmentedControl-DERBN-Xd.js.map → SegmentedControl-DyUPH3I5.js.map} +1 -1
- package/dist/{SettingsModal-CSLE31yg.js → SettingsModal-D2MOOtuO.js} +28 -27
- package/dist/SettingsModal-D2MOOtuO.js.map +1 -0
- package/dist/__stories__/experiment-helpers.d.ts +10 -0
- package/dist/__tests__/components/AppTopBarOnboarding.test.d.ts +1 -0
- package/dist/__tests__/components/FloatingPanelsInModal.test.d.ts +1 -0
- package/dist/__tests__/composables/useFloatingPanel.test.d.ts +1 -0
- package/dist/__tests__/composables/useOnboarding.test.d.ts +1 -0
- package/dist/__tests__/styles/paletteTokens.test.d.ts +1 -0
- package/dist/__tests__/styles/v2AuditRules.test.d.ts +1 -0
- package/dist/components/AppSidebar.vue.d.ts +1 -1
- package/dist/components/AppTopBar.vue.d.ts +20 -5
- package/dist/components/ArtifactSaveDialog.vue.d.ts +9 -1
- package/dist/components/AuditTrail.vue.d.ts +1 -1
- package/dist/components/BaseButton.vue.d.ts +4 -2
- package/dist/components/BaseSelect.vue.d.ts +2 -2
- package/dist/components/ChartContainer.vue.d.ts +2 -0
- package/dist/components/DatePicker.vue.d.ts +1 -1
- package/dist/components/ExperimentPopover.vue.d.ts +18 -0
- package/dist/components/ExperimentSelectorModal.vue.d.ts +15 -5
- package/dist/components/FilePicker.vue.d.ts +1 -1
- package/dist/components/FileUploader.vue.d.ts +2 -2
- package/dist/components/JobsStatusTray.vue.d.ts +15 -4
- package/dist/components/PluginIcon.vue.d.ts +2 -2
- package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
- package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
- package/dist/components/SampleSelector.vue.d.ts +6 -6
- package/dist/components/SequenceProgressBar.vue.d.ts +3 -0
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +3 -3
- package/dist/components/StepWizard.vue.d.ts +3 -0
- package/dist/components/internal/AppTopBarOnboardingInternal.vue.d.ts +7 -0
- package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -1
- package/dist/components/internal/AutoGroupModalInternal.vue.d.ts +3 -3
- package/dist/components/internal/DatePickerTimeInternal.vue.d.ts +1 -1
- package/dist/components/internal/OnboardingLayerInternal.vue.d.ts +7 -0
- package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +2 -7
- package/dist/components/internal/SelectMultipleInternal.vue.d.ts +4 -0
- package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
- package/dist/composables/createClient.d.ts +2 -0
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/pluginIdentity.d.ts +24 -0
- package/dist/composables/pluginWorkspaceModel.d.ts +2 -2
- package/dist/composables/useAppExperiment.d.ts +8 -0
- package/dist/composables/useCalendarGrid.d.ts +1 -1
- package/dist/composables/useExperimentSelector.d.ts +6 -2
- package/dist/composables/useFileImport.d.ts +7 -0
- package/dist/composables/useFloatingPanel.d.ts +29 -0
- package/dist/composables/useJobCenter.d.ts +41 -3
- package/dist/composables/useOnboarding.d.ts +119 -0
- package/dist/composables/usePlatform.d.ts +1 -0
- package/dist/{controlSchemaModel-CP5-_nAj.js → controlSchemaModel-Bsg0t5Ab.js} +2 -2
- package/dist/{controlSchemaModel-CP5-_nAj.js.map → controlSchemaModel-Bsg0t5Ab.js.map} +1 -1
- package/dist/{data-C2PwI_dn.js → data-Boeh4uPI.js} +180 -125
- package/dist/data-Boeh4uPI.js.map +1 -0
- package/dist/data.d.ts +1 -1
- package/dist/data.js +9 -9
- package/dist/{experiment-43OJtH7R.js → experiment-DyO54FQB.js} +25 -61
- package/dist/experiment-DyO54FQB.js.map +1 -0
- package/dist/{experimentStatus-BZmU4czV.js → experimentStatus-DCn2QKG7.js} +3 -3
- package/dist/{experimentStatus-BZmU4czV.js.map → experimentStatus-DCn2QKG7.js.map} +1 -1
- package/dist/index.js +20 -20
- package/dist/{install-DAID6c2-.js → install-BEHp7Mo-.js} +15 -14
- package/dist/{install-DAID6c2-.js.map → install-BEHp7Mo-.js.map} +1 -1
- package/dist/install.js +1 -1
- package/dist/{lab-DLwRl_Ql.js → lab-D61eaX6f.js} +136 -148
- package/dist/lab-D61eaX6f.js.map +1 -0
- package/dist/lab.js +4 -3
- package/dist/{records-BzTcxekG.js → records-D4aQXyZu.js} +647 -389
- package/dist/records-D4aQXyZu.js.map +1 -0
- package/dist/{runtime-D6gVat6r.js → runtime-q-R0CNLh.js} +417 -54
- package/dist/runtime-q-R0CNLh.js.map +1 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +7 -7
- package/dist/{shell-5ju97nOS.js → shell-6CGFvUUw.js} +10 -5
- package/dist/{shell-5ju97nOS.js.map → shell-6CGFvUUw.js.map} +1 -1
- package/dist/shell.d.ts +1 -0
- package/dist/shell.js +6 -6
- package/dist/stores/index.d.ts +1 -1
- package/dist/stores/index.js +2 -2
- package/dist/stores/settings.d.ts +4 -12
- package/dist/styles.css +2753 -2101
- package/dist/types/componentWorkflowTypes.d.ts +2 -0
- package/dist/types/components.d.ts +2 -0
- package/dist/types/generated/platformContext.d.ts +1 -0
- package/dist/types/generated-ui.d.ts +4 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/jobs.d.ts +9 -0
- package/dist/types/platform.d.ts +1 -1
- package/dist/{BaseModal-DfRIuVoj.js → useDropdownState-uFIz6Yv8.js} +75 -5
- package/dist/useDropdownState-uFIz6Yv8.js.map +1 -0
- package/dist/{useExperimentSamples-CxcnxZ8h.js → useExperimentSamples-QEFoq4tf.js} +48 -6
- package/dist/useExperimentSamples-QEFoq4tf.js.map +1 -0
- package/dist/{useListReorder-CcbEYxX-.js → useListReorder-DsuzwWGS.js} +219 -437
- package/dist/useListReorder-DsuzwWGS.js.map +1 -0
- package/dist/{useRackEditor-BD5NIo5s.js → useRackEditor-KpZ4iJ6o.js} +85 -81
- package/dist/useRackEditor-KpZ4iJ6o.js.map +1 -0
- package/dist/useRealtime-B58Lo-Tm.js +508 -0
- package/dist/useRealtime-B58Lo-Tm.js.map +1 -0
- package/dist/useRequest-BuDmW8Jc.js +215 -0
- package/dist/useRequest-BuDmW8Jc.js.map +1 -0
- package/dist/utils/time.d.ts +7 -0
- package/package.json +3 -2
- package/src/__stories__/experiment-helpers.ts +3 -3
- package/src/__tests__/components/ActionMenu.test.ts +2 -2
- package/src/__tests__/components/AppTopBar.test.ts +257 -3
- package/src/__tests__/components/AppTopBarOnboarding.test.ts +160 -0
- package/src/__tests__/components/AppTopBarPageSelector.test.ts +125 -9
- package/src/__tests__/components/AppTopBarPillNav.test.ts +35 -0
- package/src/__tests__/components/ArtifactSaveDialog.test.ts +78 -0
- package/src/__tests__/components/BaseButton.test.ts +19 -0
- package/src/__tests__/components/BaseModal.test.ts +13 -0
- package/src/__tests__/components/BaseSelect.multiple.test.ts +87 -32
- package/src/__tests__/components/BaseSelect.test.ts +2 -1
- package/src/__tests__/components/BatchProgressList.test.ts +16 -0
- package/src/__tests__/components/CalendarGridPanel.test.ts +2 -2
- package/src/__tests__/components/ChartContainer.test.ts +11 -0
- package/src/__tests__/components/ChemicalFormula.test.ts +6 -3
- package/src/__tests__/components/DatePicker.datetime.test.ts +6 -0
- package/src/__tests__/components/DatePicker.test.ts +21 -0
- package/src/__tests__/components/DatePicker.time.test.ts +2 -2
- package/src/__tests__/components/EmptyState.test.ts +1 -1
- package/src/__tests__/components/ExperimentPopover.test.ts +125 -16
- package/src/__tests__/components/ExperimentSelectorModal.test.ts +153 -14
- package/src/__tests__/components/FloatingPanelsInModal.test.ts +94 -0
- package/src/__tests__/components/GeneratedPluginView.test.ts +23 -2
- package/src/__tests__/components/InstrumentStateBadge.test.ts +4 -4
- package/src/__tests__/components/JobsStatusTray.test.ts +373 -71
- package/src/__tests__/components/PluginWorkspaceView.test.ts +63 -1
- package/src/__tests__/components/ResourcePickerListInternal.test.ts +0 -20
- package/src/__tests__/components/SampleSelector.test.ts +33 -8
- package/src/__tests__/components/SequenceProgressBar.test.ts +12 -0
- package/src/__tests__/components/SettingsModal.test.ts +9 -2
- package/src/__tests__/components/StepWizard.test.ts +11 -0
- package/src/__tests__/components/WellPlate.decoration.test.ts +11 -4
- package/src/__tests__/components/deprecationTags.test.ts +1 -1
- package/src/__tests__/composables/useAppExperiment.test.ts +27 -1
- package/src/__tests__/composables/useCalendarGrid.test.ts +4 -2
- package/src/__tests__/composables/useExperimentSelector.test.ts +111 -0
- package/src/__tests__/composables/useFileImport.test.ts +36 -0
- package/src/__tests__/composables/useFloatingPanel.test.ts +68 -0
- package/src/__tests__/composables/useJobCenter.test.ts +262 -3
- package/src/__tests__/composables/useOnboarding.test.ts +317 -0
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +4 -3
- package/src/__tests__/fixtures/table-import/cells.xlsx +0 -0
- package/src/__tests__/fixtures/table-import/samples.xlsx +0 -0
- package/src/__tests__/stores/settings.test.ts +21 -21
- package/src/__tests__/styles/baseControlsRules.test.ts +6 -2
- package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
- package/src/__tests__/styles/cssTokens.test.ts +6 -3
- package/src/__tests__/styles/menusShellRefresh.test.ts +22 -12
- package/src/__tests__/styles/paletteTokens.test.ts +44 -0
- package/src/__tests__/styles/v2AuditRules.test.ts +104 -0
- package/src/__tests__/utils/plotlyTemplate.test.ts +4 -4
- package/src/__tests__/utils/time.test.ts +18 -1
- package/src/components/ActionMenu.vue +6 -37
- package/src/components/AdductText.story.vue +7 -0
- package/src/components/AdductText.vue +1 -1
- package/src/components/AlertBox.story.vue +2 -2
- package/src/components/AlertBox.vue +18 -16
- package/src/components/AppSidebar.story.vue +0 -28
- package/src/components/AppTopBar.onboarding.story.vue +69 -0
- package/src/components/AppTopBar.story.vue +105 -15
- package/src/components/AppTopBar.vue +248 -122
- package/src/components/ArtifactSaveDialog.story.vue +23 -0
- package/src/components/ArtifactSaveDialog.vue +102 -12
- package/src/components/AuditTrail.story.vue +2 -1
- package/src/components/AuditTrail.vue +2 -21
- package/src/components/Avatar.vue +10 -14
- package/src/components/BaseButton.vue +16 -2
- package/src/components/BaseModal.vue +2 -1
- package/src/components/BaseSelect.multiple.story.vue +13 -2
- package/src/components/BaseSelect.vue +4 -3
- package/src/components/BatchProgressList.vue +35 -25
- package/src/components/ChartContainer.story.vue +2 -1
- package/src/components/ChartContainer.vue +8 -6
- package/src/components/ConfirmDialog.story.vue +1 -1
- package/src/components/EmptyState.vue +1 -1
- package/src/components/ExperimentPopover.story.vue +165 -53
- package/src/components/ExperimentPopover.vue +169 -187
- package/src/components/ExperimentSelectorModal.story.vue +73 -31
- package/src/components/ExperimentSelectorModal.vue +144 -111
- package/src/components/FormField.story.vue +8 -48
- package/src/components/GeneratedPluginView.vue +11 -10
- package/src/components/GeneratedResultRenderer.vue +3 -4
- package/src/components/InstrumentStateBadge.vue +1 -1
- package/src/components/JobsStatusTray.story.vue +97 -43
- package/src/components/JobsStatusTray.vue +287 -290
- package/src/components/LayoutResizeHandle.story.vue +65 -4
- package/src/components/LoadingSpinner.vue +1 -1
- package/src/components/NumberInput.vue +3 -6
- package/src/components/PluginIcon.story.vue +2 -10
- package/src/components/PluginIcon.vue +2 -2
- package/src/components/PluginWorkspaceView.props.ts +3 -0
- package/src/components/PluginWorkspaceView.story.vue +1 -1
- package/src/components/PluginWorkspaceView.vue +17 -0
- package/src/components/RuntimeUpdateNotice.vue +1 -1
- package/src/components/SampleSelector.story.vue +22 -7
- package/src/components/SampleSelector.vue +34 -37
- package/src/components/SampleSelectorGroupNode.vue +1 -1
- package/src/components/SectionCard.story.vue +35 -2
- package/src/components/SequenceProgressBar.story.vue +9 -0
- package/src/components/SequenceProgressBar.vue +6 -1
- package/src/components/SettingsModal.schema.ts +1 -1
- package/src/components/SettingsModal.vue +9 -14
- package/src/components/SmartGroupFieldRecipe.story.vue +1 -1
- package/src/components/SmartGroupManual.story.vue +1 -1
- package/src/components/StepWizard.story.vue +33 -0
- package/src/components/StepWizard.vue +8 -1
- package/src/components/Tooltip.vue +9 -32
- package/src/components/WellPlate.story.vue +3 -1
- package/src/components/WellPlate.vue +7 -0
- package/src/components/internal/AppTopBarOnboardingInternal.vue +172 -0
- package/src/components/internal/AppTopBarPageSelectorInternal.vue +140 -72
- package/src/components/internal/AppTopBarPillNavInternal.vue +33 -2
- package/src/components/internal/DatePickerDateInternal.vue +23 -85
- package/src/components/internal/DatePickerDateTimeInternal.vue +10 -14
- package/src/components/internal/DatePickerTimeInternal.vue +13 -9
- package/src/components/internal/NumberUnitInputInternal.vue +4 -18
- package/src/components/internal/OnboardingLayerInternal.vue +173 -0
- package/src/components/internal/ResourcePickerListInternal.vue +45 -85
- package/src/components/internal/SelectMultipleInternal.vue +187 -65
- package/src/components/internal/SelectSearchableInternal.vue +8 -4
- package/src/components/internal/WellEditPopupInternal.vue +11 -22
- package/src/components/internal/WorkspaceShellInternal.vue +12 -0
- package/src/composables/createClient.ts +2 -0
- package/src/composables/index.ts +1 -0
- package/src/composables/pluginIdentity.ts +31 -0
- package/src/composables/pluginWorkspaceModel.ts +2 -2
- package/src/composables/useAppExperiment.ts +20 -0
- package/src/composables/useCalendarGrid.ts +4 -3
- package/src/composables/useExperimentSelector.ts +77 -31
- package/src/composables/useFileImport.ts +51 -1
- package/src/composables/useFloatingPanel.ts +107 -0
- package/src/composables/useJobCenter.ts +188 -6
- package/src/composables/useOnboarding.ts +465 -0
- package/src/composables/usePluginWorkspace.ts +1 -1
- package/src/data.ts +1 -0
- package/src/runtime.ts +2 -1
- package/src/shell.ts +17 -0
- package/src/stores/index.ts +0 -2
- package/src/stores/settings.ts +20 -50
- package/src/styles/components/action-menu.css +1 -1
- package/src/styles/components/adduct-text.css +13 -5
- package/src/styles/components/alert-box.css +39 -58
- package/src/styles/components/app-layout.css +10 -6
- package/src/styles/components/app-page-selector.css +121 -44
- package/src/styles/components/app-pill-nav.css +11 -12
- package/src/styles/components/app-sidebar.css +9 -18
- package/src/styles/components/app-top-bar.css +219 -55
- package/src/styles/components/artifact-save-dialog.css +153 -2
- package/src/styles/components/audit-trail.css +16 -19
- package/src/styles/components/avatar.css +6 -4
- package/src/styles/components/batch-progress-list.css +38 -36
- package/src/styles/components/button.css +36 -59
- package/src/styles/components/calendar.css +12 -12
- package/src/styles/components/chart-container.css +34 -23
- package/src/styles/components/checkbox.css +2 -12
- package/src/styles/components/collapsible-card.css +27 -29
- package/src/styles/components/confirm-dialog.css +13 -14
- package/src/styles/components/dataframe.css +13 -5
- package/src/styles/components/date-picker.css +84 -94
- package/src/styles/components/datetime-picker.css +35 -47
- package/src/styles/components/divider.css +20 -20
- package/src/styles/components/dose-calculator.css +3 -3
- package/src/styles/components/empty-state.css +20 -17
- package/src/styles/components/experiment-code-badge.css +2 -0
- package/src/styles/components/experiment-popover.css +174 -329
- package/src/styles/components/experiment-selector-modal.css +88 -56
- package/src/styles/components/file-picker.css +3 -3
- package/src/styles/components/file-uploader.css +38 -49
- package/src/styles/components/form-builder.css +3 -3
- package/src/styles/components/formula-input.css +1 -1
- package/src/styles/components/icon-button.css +18 -24
- package/src/styles/components/input.css +5 -4
- package/src/styles/components/instrument-monitor.css +13 -15
- package/src/styles/components/jobs-status-tray.css +140 -296
- package/src/styles/components/layout-resize-handle.css +59 -16
- package/src/styles/components/modal.css +5 -7
- package/src/styles/components/molecule-input.css +2 -2
- package/src/styles/components/multi-select.css +65 -117
- package/src/styles/components/number-input.css +26 -19
- package/src/styles/components/onboarding.css +368 -0
- package/src/styles/components/pill.css +3 -2
- package/src/styles/components/plate-editor.css +0 -1
- package/src/styles/components/plugin-icon.css +26 -23
- package/src/styles/components/popover.css +2 -2
- package/src/styles/components/presence-avatars.css +1 -1
- package/src/styles/components/progress-bar.css +3 -3
- package/src/styles/components/protocol-step-editor.css +1 -3
- package/src/styles/components/resource-card.css +2 -2
- package/src/styles/components/resource-picker-list.css +6 -57
- package/src/styles/components/sample-hierarchy-tree.css +4 -4
- package/src/styles/components/sample-selector.css +138 -79
- package/src/styles/components/schedule-calendar.css +8 -8
- package/src/styles/components/searchable-select.css +15 -19
- package/src/styles/components/section-card.css +54 -49
- package/src/styles/components/segmented-control.css +18 -15
- package/src/styles/components/select.css +3 -3
- package/src/styles/components/sequence-input.css +2 -2
- package/src/styles/components/sequence-progress-bar.css +17 -8
- package/src/styles/components/settings-modal.css +7 -2
- package/src/styles/components/smart-group.css +18 -18
- package/src/styles/components/step-wizard.css +52 -16
- package/src/styles/components/tabs.css +2 -2
- package/src/styles/components/textarea.css +3 -3
- package/src/styles/components/time-picker.css +37 -25
- package/src/styles/components/toast.css +13 -11
- package/src/styles/components/toggle.css +1 -11
- package/src/styles/components/tooltip.css +12 -11
- package/src/styles/components/unit-input.css +9 -8
- package/src/styles/components/well-edit-popup.css +2 -2
- package/src/styles/components/well-plate.css +9 -2
- package/src/styles/index.css +1 -0
- package/src/styles/variables.css +247 -78
- package/src/types/componentWorkflowTypes.ts +2 -0
- package/src/types/components.ts +2 -0
- package/src/types/generated/platformContext.ts +1 -0
- package/src/types/generated-ui.ts +4 -0
- package/src/types/index.ts +1 -0
- package/src/types/jobs.ts +10 -0
- package/src/types/platform.ts +1 -1
- package/src/utils/time.ts +21 -0
- package/dist/ActionMenu-CScINahU.js.map +0 -1
- package/dist/AppSidebar-BmiiPfz9.js.map +0 -1
- package/dist/BaseModal-DfRIuVoj.js.map +0 -1
- package/dist/ExperimentPopover-C4sGw-is.js +0 -359
- package/dist/ExperimentPopover-C4sGw-is.js.map +0 -1
- package/dist/ExperimentSelectorModal-D-9z7cNx.js.map +0 -1
- package/dist/SettingsModal-CSLE31yg.js.map +0 -1
- package/dist/data-C2PwI_dn.js.map +0 -1
- package/dist/experiment-43OJtH7R.js.map +0 -1
- package/dist/lab-DLwRl_Ql.js.map +0 -1
- package/dist/records-BzTcxekG.js.map +0 -1
- package/dist/runtime-D6gVat6r.js.map +0 -1
- package/dist/useExperimentSamples-CxcnxZ8h.js.map +0 -1
- package/dist/useListReorder-CcbEYxX-.js.map +0 -1
- package/dist/useRackEditor-BD5NIo5s.js.map +0 -1
- package/dist/useRealtime-PalKue84.js +0 -696
- package/dist/useRealtime-PalKue84.js.map +0 -1
- package/dist/useRequest-D5JDfsgZ.js +0 -76
- package/dist/useRequest-D5JDfsgZ.js.map +0 -1
package/src/composables/index.ts
CHANGED
|
@@ -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
|
-
/**
|
|
54
|
-
title
|
|
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 = ['
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|