diff --git a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue
index 113f8420..29c53585 100644
--- a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue
+++ b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue
@@ -234,11 +234,15 @@ function makeOverlayClass(mapsApi: typeof google.maps, map: google.maps.Map) {
if (blockMapInteraction)
mapsApi.OverlayView.preventMapHitsAndGesturesFrom(el)
}
- if (panOnOpen) {
- // Wait for draw() to position the element, then pan
+ if (panOnOpen && open.value !== false) {
+ // Wait for draw() to position the element, then pan. Re-check inside
+ // the rAF callback so we don't pan when:
+ // - the controlled `open` prop flipped to false during the frame
+ // - draw() never resolved a position (closed/missing position)
+ // - the anchor element is gone (component unmounted mid-frame)
const padding = typeof panOnOpen === 'number' ? panOnOpen : 40
requestAnimationFrame(() => {
- if (overlayAnchor.value)
+ if (open.value !== false && overlayAnchor.value && overlayPosition.value)
panMapToFitOverlay(overlayAnchor.value, map, padding)
})
}
diff --git a/test/nuxt-runtime/google-maps-overlay-view.nuxt.test.ts b/test/nuxt-runtime/google-maps-overlay-view.nuxt.test.ts
index e8212436..b94e960b 100644
--- a/test/nuxt-runtime/google-maps-overlay-view.nuxt.test.ts
+++ b/test/nuxt-runtime/google-maps-overlay-view.nuxt.test.ts
@@ -1,6 +1,6 @@
///
import { mountSuspended } from '@nuxt/test-utils/runtime'
-import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { defineComponent, h, nextTick, provide, shallowRef } from 'vue'
import ScriptGoogleMapsOverlayView from '../../packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue'
import { MAP_INJECTION_KEY, normalizeLatLng } from '../../packages/script/src/runtime/components/GoogleMaps/useGoogleMapsResource'
@@ -347,4 +347,63 @@ describe('scriptGoogleMapsOverlayView', () => {
expect(content.dataset.state).toBe('open')
})
})
+
+ describe('panOnOpen guard for closed/unpositioned overlays', () => {
+ // Regression: `onAdd()` previously scheduled `panMapToFitOverlay` whenever
+ // `panOnOpen` was enabled, even if the overlay started closed or never
+ // resolved a position. That caused unexpected map panning on initial mount
+ // and remount. The fix gates the rAF scheduling on `open !== false` and
+ // re-checks `open` + `overlayPosition` inside the callback before panning.
+
+ let rafSpy: ReturnType
+
+ beforeEach(() => {
+ rafSpy = vi.spyOn(globalThis, 'requestAnimationFrame')
+ })
+
+ afterEach(() => {
+ rafSpy.mockRestore()
+ })
+
+ it('does not schedule pan-on-open when overlay starts closed (defaultOpen=false)', async () => {
+ const mocks = createOverlayMocks()
+ await mountOverlay(
+ { position: { lat: 10, lng: 20 }, defaultOpen: false },
+ mocks,
+ )
+
+ expect(rafSpy).not.toHaveBeenCalled()
+ expect(mocks.mockMap.panBy).not.toHaveBeenCalled()
+ })
+
+ it('does not schedule pan-on-open when controlled :open is false on mount', async () => {
+ const mocks = createOverlayMocks()
+ await mountOverlay(
+ { position: { lat: 10, lng: 20 }, open: false },
+ mocks,
+ )
+
+ expect(rafSpy).not.toHaveBeenCalled()
+ expect(mocks.mockMap.panBy).not.toHaveBeenCalled()
+ })
+
+ it('schedules pan-on-open when overlay starts open with a position', async () => {
+ const mocks = createOverlayMocks()
+ await mountOverlay({ position: { lat: 10, lng: 20 } }, mocks)
+
+ // The guard allows the rAF to be scheduled when the happy path applies
+ expect(rafSpy).toHaveBeenCalled()
+ })
+
+ it('respects panOnOpen=false even when overlay is open and positioned', async () => {
+ const mocks = createOverlayMocks()
+ await mountOverlay(
+ { position: { lat: 10, lng: 20 }, panOnOpen: false },
+ mocks,
+ )
+
+ expect(rafSpy).not.toHaveBeenCalled()
+ expect(mocks.mockMap.panBy).not.toHaveBeenCalled()
+ })
+ })
})