@morscherlab/mint-sdk 1.2.6 → 1.2.8
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/dist/components/AppTopBar.toastOffset.d.ts +2 -0
- package/dist/components/AppTopBar.vue.d.ts +6 -2
- package/dist/components/index.js +1 -1
- package/dist/{components-CZzBCakZ.js → components-Bbg_gtzF.js} +56 -2
- package/dist/components-Bbg_gtzF.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/install.js +1 -1
- package/dist/styles.css +2 -1
- package/package.json +2 -2
- package/src/__tests__/components/AppToastContainer.test.ts +8 -0
- package/src/__tests__/components/AppTopBar.test.ts +98 -1
- package/src/components/AppTopBar.toastOffset.ts +45 -0
- package/src/components/AppTopBar.vue +9 -1
- package/src/styles/components/toast.css +2 -1
- package/dist/components-CZzBCakZ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { a as useAuthStore, c as paletteCssVariables, d as useSettingsStore, f a
|
|
|
2
2
|
import { a as SORT_OPTIONS, c as formatExperimentDate, d as resolveExperimentCode, f as BaseModal_default, h as BaseButton_default, i as EXPERIMENT_STATUS_VARIANT_MAP, l as formatExperimentStatus, m as useEventListener, n as EXPERIMENT_STATUS_LABELS, o as datePresetToISO, p as useFocusTrap, r as EXPERIMENT_STATUS_OPTIONS, s as experimentIdFromRoute, t as DATE_PRESET_OPTIONS, u as getExperimentStatusVariant } from "./experiment-utils-CV2Wza3z.js";
|
|
3
3
|
import { a as BaseSelect_default, i as BasePill_default, n as useRequestSyncState, r as EmptyState_default, s as BaseInput_default, t as Skeleton_default } from "./Skeleton-jhF9wUkU.js";
|
|
4
4
|
import { $ as resolveJobCapabilities, A as useConcentrationUnits, At as formatTimeSlot, Bt as MultiSelect_default, C as controlsToViewIds, Ct as addMinutes, D as getTypeDefault, Dt as findNearestTimeSlotIndex, E as getFieldRegistryEntry, Et as findAvailableSlots, F as ATOMIC_WEIGHTS, Ft as rangesOverlap, G as createGeneratedAnalysisTransport, Gt as BaseToggle_default, Ht as useSelectionLimit, I as useChemicalFormula, It as snapToSlot, J as isActiveJobStatus, Jt as BaseTextarea_default, K as useGeneratedAnalysis, Kt as BaseCheckbox_default, L as GeneratedPathInput_default, Lt as toMinutes, M as SequenceInput_default, Mt as generateTimeSlots, N as useSequenceUtils, Nt as isTimeInRange, O as ConcentrationInput_default, Ot as formatDuration, P as FormulaInput_default, Pt as parseTime, Q as normalizeJobState, R as ArtifactSelectorModal_default, Rt as useTimeUtils, S as controlsToTopBarSettingsConfig, St as TimePicker_default, T as getDefaultControlView, Tt as durationMinutes, Ut as BaseSlider_default, Vt as useListSelection, W as resolveCurrentPluginId, Wt as BaseRadioGroup_default, X as jobStatusLabel, Y as isTerminalJobStatus, Z as normalizeJobPercent, _ as controlsToFormSchema, _t as pluginSecretLabel, a as defineDoseCalculatorControlProps, at as AlertBox_default, b as controlsToSettingsSchema, bt as usesPluginSecretReferences, c as defineWellPlateDoseComponentBindings, ct as SecretInput_default, d as defineControlModel, dt as PLUGIN_SECRET_REF_KEY, ft as clearPluginSecret, gt as pluginSecretId, h as controlValuesToComponentProps, ht as keepPluginSecret, i as useControlSchema, j as MoleculeInput_default, jt as fromMinutes, k as UnitInput_default, kt as formatTime, l as defineWellPlateDoseControlProps, lt as PLUGIN_SECRET_FORMAT_KEY, m as controlValuesToComponentBindingsById, mt as isPluginSecretRef, n as defineControls, o as defineDoseDesignControlModel, ot as FileUploader_default, p as controlValuesToComponentBindings, pt as isPluginSecretLocked, q as usePluginJobs, qt as Tooltip_default, r as getControlDefaults, s as defineWellPlateControlProps, st as NumberInput_default, t as defineControlComponentBindings, tt as DateTimePicker_default, u as useControlWorkspace, ut as PLUGIN_SECRET_FORMAT_REF, v as controlsToSectionFormSchema, vt as pluginSecretState, w as controlsToViewItems, wt as compareTime, x as controlsToSidebarPanels, xt as TagsInput_default, y as controlsToSectionFormSchemas, yt as setPluginSecret, z as useAnalysisArtifacts, zt as DatePicker_default } from "./useControlSchema-vLiRPUmj.js";
|
|
5
|
-
import { $ as ScheduleCalendar_default, A as ChemicalFormula_default, At as CollapsibleCard_default, B as BioTemplateExperimentWorkspaceView_default, Bt as SegmentedControl_default, C as SequenceProgressBar_default, Ct as FormBuilder_default, D as formatSequenceRemaining, Dt as AppPluginSwitcher_default, E as formatSequenceEta, Et as AppTopBar_default, F as SmartGroupModal_default, Ft as AppToastContainer_default, G as ProgressBar_default, H as AdductText_default, Ht as ColorSlider_default, I as GroupAssigner_default, It as DataFrame_default, J as LayoutResizeHandle_default, K as StatusIndicator_default, L as RackEditor_default, Lt as LoadingSpinner_default, M as ProtocolStepEditor_default, Mt as ActionMenu_default, N as SampleHierarchyTree_default, Nt as IconButton_default, O as sequenceProgressPercent, Ot as AppAvatarMenu_default, P as ReagentEditor_default, Pt as RuntimeUpdateNotice_default, Q as ComponentBindingRenderer_default, R as BioTemplatePresetWorkspaceView_default, Rt as Calendar_default, S as InstrumentAlertLog_default, St as AppSidebar_default, T as estimateSequenceRemainingSeconds, Tt as StepWizard_default, U as Breadcrumb_default, Ut as SearchableSelect_default, V as BioTemplateRenderer_default, Vt as BaseTabs_default, W as Avatar_default, X as DoseDesignWorkspaceView_default, Y as AppContainer_default, Z as ControlWorkspaceView_default, _ as inferLcmsPlateTypeFromWellIds, _t as generatedJobFormSchema, a as FileBrowserModal_default, at as PlateMapEditor_default, b as InstrumentStatusCard_default, bt as MobileSupportGate_default, c as TimeRangeInput_default, ct as ExperimentTimeline_default, d as AuditTrail_default, dt as JobsStatusTray_default, et as SampleSelector_default, f as LcmsSequenceTable_default, ft as GeneratedResultRenderer_default, g as extractLcmsSampleName, gt as generatedJobDefaults, h as extractLcmsCommonPrefix, ht as ChartContainer_default, i as ArtifactSaveDialog_default, it as ReagentList_default, j as ScientificNumber_default, jt as ThemeToggle_default, k as sequenceSamplesRemaining, kt as PluginIcon_default, l as ExperimentDataViewer_default, lt as DoseCalculator_default, m as basenameFromWindowsPath, mt as PlotlyChart_default, n as FilePicker_default, nt as SmartGroupManual_default, o as SectionCard_default, ot as SampleLegend_default, p as DEFAULT_LCMS_SEQUENCE_COLUMNS, pt as GeneratedPlotlyResult_default, q as Divider_default, r as FitPanel_default, rt as SmartGroupFieldRecipe_default, s as ResourceCard_default, st as WellPlate_default, tt as AutoGroupModal_default, u as BatchProgressList_default, ut as GeneratedPluginView_default, v as lcmsWellIdFromPosition, vt as normalizeGeneratedJobInput, w as estimateSequenceFinishDate, wt as FormActions_default, x as InstrumentStateBadge_default, xt as AppLayout_default, y as reconstructLcmsPlateCellsFromSequenceItems, yt as PluginWorkspaceView_default, z as BioTemplatePackWorkspaceView_default, zt as DropdownButton_default } from "./components-
|
|
5
|
+
import { $ as ScheduleCalendar_default, A as ChemicalFormula_default, At as CollapsibleCard_default, B as BioTemplateExperimentWorkspaceView_default, Bt as SegmentedControl_default, C as SequenceProgressBar_default, Ct as FormBuilder_default, D as formatSequenceRemaining, Dt as AppPluginSwitcher_default, E as formatSequenceEta, Et as AppTopBar_default, F as SmartGroupModal_default, Ft as AppToastContainer_default, G as ProgressBar_default, H as AdductText_default, Ht as ColorSlider_default, I as GroupAssigner_default, It as DataFrame_default, J as LayoutResizeHandle_default, K as StatusIndicator_default, L as RackEditor_default, Lt as LoadingSpinner_default, M as ProtocolStepEditor_default, Mt as ActionMenu_default, N as SampleHierarchyTree_default, Nt as IconButton_default, O as sequenceProgressPercent, Ot as AppAvatarMenu_default, P as ReagentEditor_default, Pt as RuntimeUpdateNotice_default, Q as ComponentBindingRenderer_default, R as BioTemplatePresetWorkspaceView_default, Rt as Calendar_default, S as InstrumentAlertLog_default, St as AppSidebar_default, T as estimateSequenceRemainingSeconds, Tt as StepWizard_default, U as Breadcrumb_default, Ut as SearchableSelect_default, V as BioTemplateRenderer_default, Vt as BaseTabs_default, W as Avatar_default, X as DoseDesignWorkspaceView_default, Y as AppContainer_default, Z as ControlWorkspaceView_default, _ as inferLcmsPlateTypeFromWellIds, _t as generatedJobFormSchema, a as FileBrowserModal_default, at as PlateMapEditor_default, b as InstrumentStatusCard_default, bt as MobileSupportGate_default, c as TimeRangeInput_default, ct as ExperimentTimeline_default, d as AuditTrail_default, dt as JobsStatusTray_default, et as SampleSelector_default, f as LcmsSequenceTable_default, ft as GeneratedResultRenderer_default, g as extractLcmsSampleName, gt as generatedJobDefaults, h as extractLcmsCommonPrefix, ht as ChartContainer_default, i as ArtifactSaveDialog_default, it as ReagentList_default, j as ScientificNumber_default, jt as ThemeToggle_default, k as sequenceSamplesRemaining, kt as PluginIcon_default, l as ExperimentDataViewer_default, lt as DoseCalculator_default, m as basenameFromWindowsPath, mt as PlotlyChart_default, n as FilePicker_default, nt as SmartGroupManual_default, o as SectionCard_default, ot as SampleLegend_default, p as DEFAULT_LCMS_SEQUENCE_COLUMNS, pt as GeneratedPlotlyResult_default, q as Divider_default, r as FitPanel_default, rt as SmartGroupFieldRecipe_default, s as ResourceCard_default, st as WellPlate_default, tt as AutoGroupModal_default, u as BatchProgressList_default, ut as GeneratedPluginView_default, v as lcmsWellIdFromPosition, vt as normalizeGeneratedJobInput, w as estimateSequenceFinishDate, wt as FormActions_default, x as InstrumentStateBadge_default, xt as AppLayout_default, y as reconstructLcmsPlateCellsFromSequenceItems, yt as PluginWorkspaceView_default, z as BioTemplatePackWorkspaceView_default, zt as DropdownButton_default } from "./components-Bbg_gtzF.js";
|
|
6
6
|
import { s as FormField_default, t as SettingsModal_default } from "./SettingsModal-DELMkUFR.js";
|
|
7
7
|
import { $ as getInjectedFrontendRevision, A as useFileImport, B as REORDER_ANNOUNCER_STYLE, C as useExpansionSet, D as detectDelimiter, F as extractSampleNamesFromDesignData, G as combinePluginJobSources, H as useListReorder, I as extractSampleOptionsFromDesignData, J as usePluginWorkspace, K as useJobsStatusTray, L as unwrapExperimentDesignData, M as extractSamplesFromDesignData, N as useAutoGroup, O as parseDelimitedText, Q as useAppExperiment, R as DEFAULT_COLORS, S as useExperimentData, U as useReorderAnnouncer, V as reorderList, W as useDoseCalculator, X as useMobileSupportGate, Y as DEFAULT_MOBILE_VIEWPORT_QUERY, Z as APP_EXPERIMENT_KEY, _ as useBioTemplateControls, a as DEFAULT_PRESETS, at as candidateMatchesSearch, b as useScheduleDrag, c as useReagentSeries, ct as compareSortValues, d as useBioTemplatePresetWorkspace, dt as useRovingFocus, et as useRuntimeAlignment, f as useBioTemplatePackWorkspace, g as useBioTemplateComponents, h as toBioTemplateComponentPropsByComponent, i as useProtocolTemplates, it as useToast, j as validateImportFile, k as readFileAsText, l as useGroupAssignment, lt as useSortedItems, m as getBioTemplateComponentProps, nt as usePresenceHeartbeat, o as DEFAULT_UNITS, ot as normalizeSearchQuery, p as useBioTemplateWorkspace, q as usePluginJobCenter, rt as useTheme, s as generateDilutionSeries, st as useTextSearch, tt as getPresenceClientId, u as useRackEditor, ut as useMenuKeyboard, v as useTemplateCollection, w as useSampleGroups, x as useExperimentSamples, y as useExperimentSave, z as useWellPlateEditor } from "./validation-ChMyixAf.js";
|
|
8
8
|
import { i as usePlatformContext, n as evaluateCondition, r as useForm, t as useFormBuilder } from "./useFormBuilder-D37ajgS8.js";
|
package/dist/install.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./useControlSchema-vLiRPUmj.js";
|
|
2
|
-
import { t as components_exports } from "./components-
|
|
2
|
+
import { t as components_exports } from "./components-Bbg_gtzF.js";
|
|
3
3
|
//#region src/install.ts
|
|
4
4
|
/**
|
|
5
5
|
* Vue plugin that registers all MINT SDK components globally.
|
package/dist/styles.css
CHANGED
|
@@ -7510,7 +7510,8 @@ html.dark .mint-tab--active .mint-tab__badge {
|
|
|
7510
7510
|
/* ToastNotification Component Styles — neutral surface, accent only on icon + TTL bar */
|
|
7511
7511
|
.mint-toast__container {
|
|
7512
7512
|
position: fixed;
|
|
7513
|
-
|
|
7513
|
+
/* AppTopBar publishes its bottom edge + 1rem so toasts clear its controls. */
|
|
7514
|
+
top: var(--mint-toast-offset-top, 1rem);
|
|
7514
7515
|
right: 1rem;
|
|
7515
7516
|
z-index: 9999;
|
|
7516
7517
|
display: flex;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morscherlab/mint-sdk",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.8",
|
|
4
4
|
"description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec INtegrated Toolkit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
85
|
"@floating-ui/dom": "^1.7.6",
|
|
86
|
-
"axios": "1.
|
|
86
|
+
"axios": "1.20.0",
|
|
87
87
|
"lucide-vue-next": "1.0.0",
|
|
88
88
|
"plotly.js-dist-min": "^3.7.0"
|
|
89
89
|
},
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
1
3
|
import { mount } from '@vue/test-utils'
|
|
2
4
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
3
5
|
import { nextTick } from 'vue'
|
|
@@ -88,4 +90,10 @@ describe('AppToastContainer', () => {
|
|
|
88
90
|
expect(wrapper.find('.mint-toast__count').text()).toBe('×4')
|
|
89
91
|
expect(wrapper.find('.mint-toast__item--stacked').exists()).toBe(true)
|
|
90
92
|
})
|
|
93
|
+
|
|
94
|
+
it('should place toasts 1rem from the top when no top bar offset is set', () => {
|
|
95
|
+
const styles = readFileSync(resolve(process.cwd(), 'src/styles/components/toast.css'), 'utf8')
|
|
96
|
+
|
|
97
|
+
expect(styles).toMatch(/\.mint-toast__container\s*\{[^}]*top:\s*var\(--mint-toast-offset-top,\s*1rem\);/s)
|
|
98
|
+
})
|
|
91
99
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'
|
|
2
|
+
import { enableAutoUnmount, flushPromises, mount, type VueWrapper } from '@vue/test-utils'
|
|
3
3
|
import { computed, ref } from 'vue'
|
|
4
4
|
import { createPinia } from 'pinia'
|
|
5
5
|
import AppTopBar from '../../components/AppTopBar.vue'
|
|
@@ -75,6 +75,8 @@ async function openSettingsModal(wrapper: VueWrapper) {
|
|
|
75
75
|
return wrapper.findComponent(SettingsModal)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
enableAutoUnmount(afterEach)
|
|
79
|
+
|
|
78
80
|
describe('AppTopBar', () => {
|
|
79
81
|
beforeEach(() => {
|
|
80
82
|
vi.clearAllMocks()
|
|
@@ -1253,4 +1255,99 @@ describe('AppTopBar', () => {
|
|
|
1253
1255
|
})
|
|
1254
1256
|
|
|
1255
1257
|
})
|
|
1258
|
+
|
|
1259
|
+
describe('toast offset', () => {
|
|
1260
|
+
const property = '--mint-toast-offset-top'
|
|
1261
|
+
let rects: Record<string, { top: number; bottom: number }> = {}
|
|
1262
|
+
|
|
1263
|
+
function toastOffset() {
|
|
1264
|
+
return document.documentElement.style.getPropertyValue(property)
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
beforeEach(() => {
|
|
1268
|
+
rects = {}
|
|
1269
|
+
vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(function (this: HTMLElement) {
|
|
1270
|
+
const title = this.querySelector('.mint-topbar__title-only')?.textContent ?? ''
|
|
1271
|
+
return (rects[title] ?? { top: 16, bottom: 72 }) as DOMRect
|
|
1272
|
+
})
|
|
1273
|
+
})
|
|
1274
|
+
|
|
1275
|
+
afterEach(() => {
|
|
1276
|
+
vi.restoreAllMocks()
|
|
1277
|
+
vi.unstubAllGlobals()
|
|
1278
|
+
})
|
|
1279
|
+
|
|
1280
|
+
it('should set the toast offset below the top bar when mounted', () => {
|
|
1281
|
+
createWrapper({ title: 'Test App' })
|
|
1282
|
+
|
|
1283
|
+
expect(toastOffset()).toBe('calc(72px + 1rem)')
|
|
1284
|
+
})
|
|
1285
|
+
|
|
1286
|
+
it('should remove the toast offset when the top bar unmounts', () => {
|
|
1287
|
+
createWrapper({ title: 'Test App' }).unmount()
|
|
1288
|
+
|
|
1289
|
+
expect(toastOffset()).toBe('')
|
|
1290
|
+
})
|
|
1291
|
+
|
|
1292
|
+
it('should keep the toast offset when another top bar is still mounted', () => {
|
|
1293
|
+
createWrapper({ title: 'Remaining' })
|
|
1294
|
+
createWrapper({ title: 'Removed' }).unmount()
|
|
1295
|
+
|
|
1296
|
+
expect(toastOffset()).toBe('calc(72px + 1rem)')
|
|
1297
|
+
})
|
|
1298
|
+
|
|
1299
|
+
it('should clamp the toast offset at 0 when the top bar is scrolled above the viewport', () => {
|
|
1300
|
+
rects['Test App'] = { top: -96, bottom: -40 }
|
|
1301
|
+
createWrapper({ title: 'Test App' })
|
|
1302
|
+
|
|
1303
|
+
expect(toastOffset()).toBe('calc(0px + 1rem)')
|
|
1304
|
+
})
|
|
1305
|
+
|
|
1306
|
+
it('should follow the top-most top bar when several are mounted', () => {
|
|
1307
|
+
rects.Header = { top: 16, bottom: 72 }
|
|
1308
|
+
rects.Lower = { top: 200, bottom: 260 }
|
|
1309
|
+
createWrapper({ title: 'Lower' })
|
|
1310
|
+
createWrapper({ title: 'Header' })
|
|
1311
|
+
|
|
1312
|
+
expect(toastOffset()).toBe('calc(72px + 1rem)')
|
|
1313
|
+
})
|
|
1314
|
+
|
|
1315
|
+
it('should update the toast offset when the page scrolls', () => {
|
|
1316
|
+
createWrapper({ title: 'Test App' })
|
|
1317
|
+
rects['Test App'] = { top: -26, bottom: 30 }
|
|
1318
|
+
window.dispatchEvent(new Event('scroll'))
|
|
1319
|
+
|
|
1320
|
+
expect(toastOffset()).toBe('calc(30px + 1rem)')
|
|
1321
|
+
})
|
|
1322
|
+
|
|
1323
|
+
it('should update the toast offset when the window resizes', () => {
|
|
1324
|
+
createWrapper({ title: 'Test App' })
|
|
1325
|
+
rects['Test App'] = { top: 16, bottom: 96 }
|
|
1326
|
+
window.dispatchEvent(new Event('resize'))
|
|
1327
|
+
|
|
1328
|
+
expect(toastOffset()).toBe('calc(96px + 1rem)')
|
|
1329
|
+
})
|
|
1330
|
+
|
|
1331
|
+
it('should update the toast offset when the top bar resizes', () => {
|
|
1332
|
+
let onResize: (() => void) | undefined
|
|
1333
|
+
vi.stubGlobal('ResizeObserver', class {
|
|
1334
|
+
constructor(callback: () => void) { onResize = callback }
|
|
1335
|
+
observe() {}
|
|
1336
|
+
unobserve() {}
|
|
1337
|
+
disconnect() {}
|
|
1338
|
+
})
|
|
1339
|
+
createWrapper({ title: 'Test App' })
|
|
1340
|
+
rects['Test App'] = { top: 16, bottom: 120 }
|
|
1341
|
+
onResize?.()
|
|
1342
|
+
|
|
1343
|
+
expect(toastOffset()).toBe('calc(120px + 1rem)')
|
|
1344
|
+
})
|
|
1345
|
+
|
|
1346
|
+
it('should stop listening for scroll when the last top bar unmounts', () => {
|
|
1347
|
+
const removeListener = vi.spyOn(window, 'removeEventListener')
|
|
1348
|
+
createWrapper({ title: 'Test App' }).unmount()
|
|
1349
|
+
|
|
1350
|
+
expect(removeListener).toHaveBeenCalledWith('scroll', expect.any(Function), expect.objectContaining({ capture: true }))
|
|
1351
|
+
})
|
|
1352
|
+
})
|
|
1256
1353
|
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publishes the top-most mounted AppTopBar's bottom edge as `--mint-toast-offset-top`
|
|
3
|
+
* on `<html>`, so the teleported toast container starts below the top bar.
|
|
4
|
+
*/
|
|
5
|
+
const TOAST_OFFSET_PROPERTY = '--mint-toast-offset-top'
|
|
6
|
+
const LISTENER_OPTIONS = { capture: true, passive: true } as const
|
|
7
|
+
const topBars = new Set<HTMLElement>()
|
|
8
|
+
let observer: ResizeObserver | undefined
|
|
9
|
+
|
|
10
|
+
function syncToastOffset(): void {
|
|
11
|
+
const style = document.documentElement.style
|
|
12
|
+
if (topBars.size === 0) {
|
|
13
|
+
style.removeProperty(TOAST_OFFSET_PROPERTY)
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
const header = [...topBars]
|
|
17
|
+
.map((el) => el.getBoundingClientRect())
|
|
18
|
+
.reduce((top, rect) => (rect.top < top.top ? rect : top))
|
|
19
|
+
// Clamp so a top bar scrolled out of view leaves toasts at the plain 1rem gap.
|
|
20
|
+
style.setProperty(TOAST_OFFSET_PROPERTY, `calc(${Math.max(0, Math.round(header.bottom))}px + 1rem)`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Track a top bar element; returns the cleanup to call on unmount. */
|
|
24
|
+
export function trackTopBarForToasts(el: HTMLElement): () => void {
|
|
25
|
+
if (topBars.size === 0) {
|
|
26
|
+
// Capture so scrolling any container that holds the top bar also moves the toasts.
|
|
27
|
+
window.addEventListener('scroll', syncToastOffset, LISTENER_OPTIONS)
|
|
28
|
+
window.addEventListener('resize', syncToastOffset, LISTENER_OPTIONS)
|
|
29
|
+
if (typeof ResizeObserver !== 'undefined') observer = new ResizeObserver(syncToastOffset)
|
|
30
|
+
}
|
|
31
|
+
topBars.add(el)
|
|
32
|
+
observer?.observe(el)
|
|
33
|
+
syncToastOffset()
|
|
34
|
+
return () => {
|
|
35
|
+
topBars.delete(el)
|
|
36
|
+
observer?.unobserve(el)
|
|
37
|
+
if (topBars.size === 0) {
|
|
38
|
+
window.removeEventListener('scroll', syncToastOffset, LISTENER_OPTIONS)
|
|
39
|
+
window.removeEventListener('resize', syncToastOffset, LISTENER_OPTIONS)
|
|
40
|
+
observer?.disconnect()
|
|
41
|
+
observer = undefined
|
|
42
|
+
}
|
|
43
|
+
syncToastOffset()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Full application top bar with brand logo, page selector or plugin switcher, centered pill nav, experiment popover, and avatar menu. */
|
|
3
|
-
import { ref, computed, inject, defineAsyncComponent } from 'vue'
|
|
3
|
+
import { ref, computed, inject, defineAsyncComponent, onMounted, onBeforeUnmount } from 'vue'
|
|
4
4
|
import type {
|
|
5
5
|
PillNavOption,
|
|
6
6
|
TopBarSettingsConfig,
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
currentItemIdFromLocation,
|
|
34
34
|
pluginNavItemToPageSelectorItem,
|
|
35
35
|
} from './AppTopBar.navigation'
|
|
36
|
+
import { trackTopBarForToasts } from './AppTopBar.toastOffset'
|
|
36
37
|
|
|
37
38
|
interface Props {
|
|
38
39
|
/** App or plugin title shown in the left title group when no page selector is present. */
|
|
@@ -119,6 +120,12 @@ const emit = defineEmits<{
|
|
|
119
120
|
}>()
|
|
120
121
|
|
|
121
122
|
const settingsOpen = ref(false)
|
|
123
|
+
const headerEl = ref<HTMLElement | null>(null)
|
|
124
|
+
let untrackToastOffset: (() => void) | undefined
|
|
125
|
+
onMounted(() => {
|
|
126
|
+
if (headerEl.value) untrackToastOffset = trackTopBarForToasts(headerEl.value)
|
|
127
|
+
})
|
|
128
|
+
onBeforeUnmount(() => untrackToastOffset?.())
|
|
122
129
|
const { isIntegrated, plugin } = usePlatformContext()
|
|
123
130
|
const isStandalone = computed(() => !isIntegrated.value)
|
|
124
131
|
const presenceScope = computed(() => plugin.value?.id || plugin.value?.name || '')
|
|
@@ -194,6 +201,7 @@ const effectiveCurrentPageSelectorId = computed(() =>
|
|
|
194
201
|
|
|
195
202
|
<template>
|
|
196
203
|
<header
|
|
204
|
+
ref="headerEl"
|
|
197
205
|
:class="[
|
|
198
206
|
'mint-topbar',
|
|
199
207
|
`mint-topbar--${props.variant}`,
|