From e7e062a2222bf90539d492e4c4932a451d3e0b03 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Wed, 9 Jul 2025 07:32:20 -0700 Subject: [PATCH] Pause map autofit when user initiates pan/zoom (#26114) * Pause map autofit when user initiates pan/zoom * not a state * a different approach --- src/components/map/ha-map.ts | 61 +++++++++++++++++++++-- src/panels/lovelace/cards/hui-map-card.ts | 6 +-- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/components/map/ha-map.ts b/src/components/map/ha-map.ts index d8a2f1c102..d334ad9297 100644 --- a/src/components/map/ha-map.ts +++ b/src/components/map/ha-map.ts @@ -36,6 +36,8 @@ declare global { } } +const PROGRAMMITIC_FIT_DELAY = 250; + const getEntityId = (entity: string | HaMapEntity): string => typeof entity === "string" ? entity : entity.entity_id; @@ -113,14 +115,33 @@ export class HaMap extends ReactiveElement { private _clickCount = 0; + private _isProgrammaticFit = false; + + private _pauseAutoFit = false; + public connectedCallback(): void { + this._pauseAutoFit = false; + document.addEventListener("visibilitychange", this._handleVisibilityChange); + this._handleVisibilityChange(); super.connectedCallback(); this._loadMap(); this._attachObserver(); } + private _handleVisibilityChange = async () => { + if (!document.hidden) { + setTimeout(() => { + this._pauseAutoFit = false; + }, 500); + } + }; + public disconnectedCallback(): void { super.disconnectedCallback(); + document.removeEventListener( + "visibilitychange", + this._handleVisibilityChange + ); if (this.leafletMap) { this.leafletMap.remove(); this.leafletMap = undefined; @@ -145,7 +166,7 @@ export class HaMap extends ReactiveElement { if (changedProps.has("_loaded") || changedProps.has("entities")) { this._drawEntities(); - autoFitRequired = true; + autoFitRequired = !this._pauseAutoFit; } else if (this._loaded && oldHass && this.entities) { // Check if any state has changed for (const entity of this.entities) { @@ -154,7 +175,7 @@ export class HaMap extends ReactiveElement { this.hass!.states[getEntityId(entity)] ) { this._drawEntities(); - autoFitRequired = true; + autoFitRequired = !this._pauseAutoFit; break; } } @@ -178,7 +199,11 @@ export class HaMap extends ReactiveElement { } if (changedProps.has("zoom")) { + this._isProgrammaticFit = true; this.leafletMap!.setZoom(this.zoom); + setTimeout(() => { + this._isProgrammaticFit = false; + }, PROGRAMMITIC_FIT_DELAY); } if ( @@ -234,13 +259,30 @@ export class HaMap extends ReactiveElement { } this._clickCount++; }); + this.leafletMap.on("zoomstart", () => { + if (!this._isProgrammaticFit) { + this._pauseAutoFit = true; + } + }); + this.leafletMap.on("movestart", () => { + if (!this._isProgrammaticFit) { + this._pauseAutoFit = true; + } + }); this._loaded = true; } finally { this._loading = false; } } - public fitMap(options?: { zoom?: number; pad?: number }): void { + public fitMap(options?: { + zoom?: number; + pad?: number; + unpause_autofit?: boolean; + }): void { + if (options?.unpause_autofit) { + this._pauseAutoFit = false; + } if (!this.leafletMap || !this.Leaflet || !this.hass) { return; } @@ -250,6 +292,7 @@ export class HaMap extends ReactiveElement { !this._mapFocusZones.length && !this.layers?.length ) { + this._isProgrammaticFit = true; this.leafletMap.setView( new this.Leaflet.LatLng( this.hass.config.latitude, @@ -257,6 +300,9 @@ export class HaMap extends ReactiveElement { ), options?.zoom || this.zoom ); + setTimeout(() => { + this._isProgrammaticFit = false; + }, PROGRAMMITIC_FIT_DELAY); return; } @@ -277,8 +323,11 @@ export class HaMap extends ReactiveElement { }); bounds = bounds.pad(options?.pad ?? 0.5); - + this._isProgrammaticFit = true; this.leafletMap.fitBounds(bounds, { maxZoom: options?.zoom || this.zoom }); + setTimeout(() => { + this._isProgrammaticFit = false; + }, PROGRAMMITIC_FIT_DELAY); } public fitBounds( @@ -291,7 +340,11 @@ export class HaMap extends ReactiveElement { const bounds = this.Leaflet.latLngBounds(boundingbox).pad( options?.pad ?? 0.5 ); + this._isProgrammaticFit = true; this.leafletMap.fitBounds(bounds, { maxZoom: options?.zoom || this.zoom }); + setTimeout(() => { + this._isProgrammaticFit = false; + }, PROGRAMMITIC_FIT_DELAY); } private _drawLayers(prevLayers: Layer[] | undefined): void { diff --git a/src/panels/lovelace/cards/hui-map-card.ts b/src/panels/lovelace/cards/hui-map-card.ts index 7c0de67ea7..74160b8be6 100644 --- a/src/panels/lovelace/cards/hui-map-card.ts +++ b/src/panels/lovelace/cards/hui-map-card.ts @@ -239,7 +239,7 @@ class HuiMapCard extends LitElement implements LovelaceCard { )} .path=${mdiImageFilterCenterFocus} style=${isDarkMode ? "color:#ffffff" : "color:#000000"} - @click=${this._fitMap} + @click=${this._resetFocus} tabindex="0" > @@ -389,8 +389,8 @@ class HuiMapCard extends LitElement implements LovelaceCard { : (root.style.paddingBottom = "100%"); } - private _fitMap() { - this._map?.fitMap(); + private _resetFocus() { + this._map?.fitMap({ unpause_autofit: true }); } private _toggleClusterMarkers() {