From b6171840ecd3ac13c65a10f6699ad244870342a4 Mon Sep 17 00:00:00 2001 From: Pascal Roehling Date: Tue, 3 Mar 2026 18:58:10 +0100 Subject: [PATCH 1/2] fix(pins): properly remove map event listener --- src/plugins/pins/store.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/plugins/pins/store.ts b/src/plugins/pins/store.ts index 981c207710..504891c8e4 100644 --- a/src/plugins/pins/store.ts +++ b/src/plugins/pins/store.ts @@ -5,6 +5,7 @@ /* eslint-enable tsdoc/syntax */ import type { GeoJsonGeometryTypes, Point as GeoJsonPoint } from 'geojson' +import type { MapBrowserEvent } from 'ol' import type { Coordinate } from 'ol/coordinate' import { toMerged } from 'es-toolkit' @@ -82,9 +83,7 @@ export const usePinsStore = defineStore('plugins/pins', () => { function setupPlugin() { coreStore.map.addLayer(pinLayer) pinLayer.setZIndex(100) - coreStore.map.on('singleclick', async ({ coordinate }) => { - await click(coordinate) - }) + coreStore.map.on('singleclick', onSingleClick) setupCoordinateSource() setupInitial() setupInteractions() @@ -92,9 +91,7 @@ export const usePinsStore = defineStore('plugins/pins', () => { function teardownPlugin() { const { map } = coreStore - map.un('singleclick', async ({ coordinate }) => { - await click(coordinate) - }) + map.un('singleclick', onSingleClick) removePin() map.removeLayer(pinLayer) map.removeInteraction(move) @@ -185,6 +182,10 @@ export const usePinsStore = defineStore('plugins/pins', () => { coreStore.map.addInteraction(translate) } + async function onSingleClick({ coordinate }: MapBrowserEvent) { + await click(coordinate) + } + async function click(coordinate: Coordinate) { const isDrawing = coreStore.map .getInteractions() From 133c2d5d577db508a26b31a7f83a4b28d9f8a45f Mon Sep 17 00:00:00 2001 From: Pascal Roehling Date: Tue, 3 Mar 2026 19:00:03 +0100 Subject: [PATCH 2/2] fix(core): cleanup before unmounting and make sure ol objects are added to the store raw Also, add proper cleanup example to snowbox. --- examples/snowbox/index.js | 8 ++- src/core/components/PolarContainer.ce.vue | 17 +++++- src/core/components/PolarMap.ce.vue | 58 ++++++++++++------- src/core/stores/main.ts | 7 +++ src/core/stores/marker.ts | 6 +- src/core/utils/map/setupMarkers.ts | 19 +++++- .../map/updateDragAndZoomInteractions.ts | 4 ++ 7 files changed, 91 insertions(+), 28 deletions(-) diff --git a/examples/snowbox/index.js b/examples/snowbox/index.js index ac13fea773..ca694b2d29 100644 --- a/examples/snowbox/index.js +++ b/examples/snowbox/index.js @@ -204,6 +204,7 @@ const map = await createMap( services ) +const additionalMaps = [] document.getElementById('secondMap').addEventListener('click', async () => { const secondMap = createMapElement( { @@ -227,9 +228,14 @@ document.getElementById('secondMap').addEventListener('click', async () => { layoutTag: 'TOP_RIGHT', }) ) + additionalMaps.push(secondMap) }) document.getElementById('secondMapClean').addEventListener('click', () => { - document.getElementById('secondMapContainer').innerText = '' + additionalMaps.forEach((map, i) => { + map.remove() + delete additionalMaps[i] + }) + additionalMaps.length = 0 }) addPlugin( diff --git a/src/core/components/PolarContainer.ce.vue b/src/core/components/PolarContainer.ce.vue index ffc3493fe0..1ecf23a094 100644 --- a/src/core/components/PolarContainer.ce.vue +++ b/src/core/components/PolarContainer.ce.vue @@ -22,8 +22,9 @@ diff --git a/src/core/components/PolarMap.ce.vue b/src/core/components/PolarMap.ce.vue index 035057dc6a..f5085e4b9b 100644 --- a/src/core/components/PolarMap.ce.vue +++ b/src/core/components/PolarMap.ce.vue @@ -19,7 +19,14 @@ import { t } from 'i18next' import { easeOut } from 'ol/easing' import { defaults } from 'ol/interaction' import { storeToRefs } from 'pinia' -import { computed, onMounted, useTemplateRef, watch } from 'vue' +import { + computed, + markRaw, + onBeforeUnmount, + onMounted, + useTemplateRef, + watch, +} from 'vue' import { useT } from '../composables/useT' import { useMainStore } from '../stores/main' @@ -42,26 +49,28 @@ function onMove() { } function createMap() { - mainStore.map = api.map.createMap( - { - target: polarMapContainer.value, - extent: undefined, - ...mainStore.configuration, - layerConf: mainStore.serviceRegister, - }, - '2D', - { - mapParams: { - interactions: defaults({ - altShiftDragRotate: false, - pinchRotate: false, - dragPan: false, - mouseWheelZoom: false, - keyboard: false, - }), + mainStore.map = markRaw( + api.map.createMap( + { + target: polarMapContainer.value, + extent: undefined, + ...mainStore.configuration, + layerConf: mainStore.serviceRegister, }, - } - ) as Map + '2D', + { + mapParams: { + interactions: defaults({ + altShiftDragRotate: false, + pinchRotate: false, + dragPan: false, + mouseWheelZoom: false, + keyboard: false, + }), + }, + } + ) as Map + ) mainStore.map.on('moveend', onMove) updateDragAndZoomInteractions( @@ -126,17 +135,24 @@ onMounted(async () => { mainStore.serviceRegister ) }) +onBeforeUnmount(() => { + mainStore.map.un('moveend', onMove) + hammer?.destroy() + hammer = null +}) const oneFingerPan = useT(() => t(($) => $.overlay.oneFingerPan, { ns: 'core' }) ) +let hammer: { destroy: () => void } | null = null function updateListeners() { if ( !hasWindowSize.value && polarMapContainer.value && mainStore.hasSmallDisplay ) { - new Hammer(polarMapContainer.value).on('pan', (e) => { + hammer?.destroy() + hammer = new Hammer(polarMapContainer.value).on('pan', (e) => { if ( overlay.value && e.maxPointers === 1 && diff --git a/src/core/stores/main.ts b/src/core/stores/main.ts index 4ca1203531..1a3d628d6a 100644 --- a/src/core/stores/main.ts +++ b/src/core/stores/main.ts @@ -6,6 +6,9 @@ import { toMerged } from 'es-toolkit' import { acceptHMRUpdate, defineStore } from 'pinia' import { computed, ref, shallowRef, watch } from 'vue' +import { teardownMarkers } from '@/core/utils/map/setupMarkers.ts' +import { teardownInteractions } from '@/core/utils/map/updateDragAndZoomInteractions.ts' + import type { ColorScheme, MapConfigurationIncludingDefaults, @@ -85,6 +88,10 @@ export const useMainStore = defineStore('main', () => { function teardown() { removeEventListener('resize', updateHasSmallDisplay) + teardownInteractions() + if (configuration.value.markers) { + teardownMarkers(map.value) + } } return { diff --git a/src/core/stores/marker.ts b/src/core/stores/marker.ts index 46c238f869..aefdfde9fc 100644 --- a/src/core/stores/marker.ts +++ b/src/core/stores/marker.ts @@ -1,7 +1,7 @@ import { Feature } from 'ol' import { Point } from 'ol/geom' import { acceptHMRUpdate, defineStore } from 'pinia' -import { computed, ref } from 'vue' +import { computed, shallowRef } from 'vue' import type { CallOnMapSelect } from '../types' @@ -20,8 +20,8 @@ export const useMarkerStore = defineStore('marker', () => { () => (configuration.value?.clusterClickZoom as boolean) || false ) - const hovered = ref(null) - const selected = ref(null) + const hovered = shallowRef(null) + const selected = shallowRef(null) const selectedCoordinates = computed(() => selected.value === null ? null diff --git a/src/core/utils/map/setupMarkers.ts b/src/core/utils/map/setupMarkers.ts index 9c64d54098..ab237ed8dd 100644 --- a/src/core/utils/map/setupMarkers.ts +++ b/src/core/utils/map/setupMarkers.ts @@ -19,6 +19,8 @@ import type { MarkerLayer, MarkerStyle, PluginId } from '../../types' import { useMainStore } from '../../stores/main' import { getMarkerStyle } from '../../utils/markers' +let stopWatcher: (() => void) | null = null + // these have been measured to fit once and influence marker size const imgSize: [number, number] = [26, 36] const imgSizeMulti: [number, number] = [40, 36] @@ -121,7 +123,7 @@ function updateSelection( store.selected = markRaw(selectedCluster) if (centerOnFeature) { const mainStore = useMainStore() - mainStore.centerOnFeature(store.selected as Feature) + mainStore.centerOnFeature(store.selected) } } @@ -205,7 +207,7 @@ export function setupMarkers(map: Map) { // // // STORE EVENT HANDLING - watch( + stopWatcher = watch( () => store.hovered, (feature) => { if (feature !== null && feature !== toRaw(store.selected)) { @@ -236,6 +238,19 @@ export function setupMarkers(map: Map) { map.on('singleclick', mapSingleClick) } +export function teardownMarkers(map: Map) { + stopWatcher?.() + stopWatcher = null + layers = [] + lastClickEvent = null + + map.un('moveend', mapMoveEnd) + map.un('pointermove', mapPointerMove) + map.un('click', mapClick) + + map.un('singleclick', mapSingleClick) +} + // // // MAP EVENT HANDLING let lastClickEvent: MapBrowserEvent | null = null diff --git a/src/core/utils/map/updateDragAndZoomInteractions.ts b/src/core/utils/map/updateDragAndZoomInteractions.ts index 1ce5bfdc9a..4cfbac1de9 100644 --- a/src/core/utils/map/updateDragAndZoomInteractions.ts +++ b/src/core/utils/map/updateDragAndZoomInteractions.ts @@ -18,3 +18,7 @@ export function updateDragAndZoomInteractions( map.addInteraction(interaction) } } + +export function teardownInteractions() { + interactions = [] +}