Skip to content

Commit 506dfd8

Browse files
authored
fix(google-maps): guard pan-on-open for closed/unpositioned overlay (#698)
1 parent c33473c commit 506dfd8

File tree

2 files changed

+67
-4
lines changed

2 files changed

+67
-4
lines changed

packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,15 @@ function makeOverlayClass(mapsApi: typeof google.maps, map: google.maps.Map) {
234234
if (blockMapInteraction)
235235
mapsApi.OverlayView.preventMapHitsAndGesturesFrom(el)
236236
}
237-
if (panOnOpen) {
238-
// Wait for draw() to position the element, then pan
237+
if (panOnOpen && open.value !== false) {
238+
// Wait for draw() to position the element, then pan. Re-check inside
239+
// the rAF callback so we don't pan when:
240+
// - the controlled `open` prop flipped to false during the frame
241+
// - draw() never resolved a position (closed/missing position)
242+
// - the anchor element is gone (component unmounted mid-frame)
239243
const padding = typeof panOnOpen === 'number' ? panOnOpen : 40
240244
requestAnimationFrame(() => {
241-
if (overlayAnchor.value)
245+
if (open.value !== false && overlayAnchor.value && overlayPosition.value)
242246
panMapToFitOverlay(overlayAnchor.value, map, padding)
243247
})
244248
}

test/nuxt-runtime/google-maps-overlay-view.nuxt.test.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="google.maps" />
22
import { mountSuspended } from '@nuxt/test-utils/runtime'
3-
import { beforeEach, describe, expect, it, vi } from 'vitest'
3+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
44
import { defineComponent, h, nextTick, provide, shallowRef } from 'vue'
55
import ScriptGoogleMapsOverlayView from '../../packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue'
66
import { MAP_INJECTION_KEY, normalizeLatLng } from '../../packages/script/src/runtime/components/GoogleMaps/useGoogleMapsResource'
@@ -347,4 +347,63 @@ describe('scriptGoogleMapsOverlayView', () => {
347347
expect(content.dataset.state).toBe('open')
348348
})
349349
})
350+
351+
describe('panOnOpen guard for closed/unpositioned overlays', () => {
352+
// Regression: `onAdd()` previously scheduled `panMapToFitOverlay` whenever
353+
// `panOnOpen` was enabled, even if the overlay started closed or never
354+
// resolved a position. That caused unexpected map panning on initial mount
355+
// and remount. The fix gates the rAF scheduling on `open !== false` and
356+
// re-checks `open` + `overlayPosition` inside the callback before panning.
357+
358+
let rafSpy: ReturnType<typeof vi.spyOn>
359+
360+
beforeEach(() => {
361+
rafSpy = vi.spyOn(globalThis, 'requestAnimationFrame')
362+
})
363+
364+
afterEach(() => {
365+
rafSpy.mockRestore()
366+
})
367+
368+
it('does not schedule pan-on-open when overlay starts closed (defaultOpen=false)', async () => {
369+
const mocks = createOverlayMocks()
370+
await mountOverlay(
371+
{ position: { lat: 10, lng: 20 }, defaultOpen: false },
372+
mocks,
373+
)
374+
375+
expect(rafSpy).not.toHaveBeenCalled()
376+
expect(mocks.mockMap.panBy).not.toHaveBeenCalled()
377+
})
378+
379+
it('does not schedule pan-on-open when controlled :open is false on mount', async () => {
380+
const mocks = createOverlayMocks()
381+
await mountOverlay(
382+
{ position: { lat: 10, lng: 20 }, open: false },
383+
mocks,
384+
)
385+
386+
expect(rafSpy).not.toHaveBeenCalled()
387+
expect(mocks.mockMap.panBy).not.toHaveBeenCalled()
388+
})
389+
390+
it('schedules pan-on-open when overlay starts open with a position', async () => {
391+
const mocks = createOverlayMocks()
392+
await mountOverlay({ position: { lat: 10, lng: 20 } }, mocks)
393+
394+
// The guard allows the rAF to be scheduled when the happy path applies
395+
expect(rafSpy).toHaveBeenCalled()
396+
})
397+
398+
it('respects panOnOpen=false even when overlay is open and positioned', async () => {
399+
const mocks = createOverlayMocks()
400+
await mountOverlay(
401+
{ position: { lat: 10, lng: 20 }, panOnOpen: false },
402+
mocks,
403+
)
404+
405+
expect(rafSpy).not.toHaveBeenCalled()
406+
expect(mocks.mockMap.panBy).not.toHaveBeenCalled()
407+
})
408+
})
350409
})

0 commit comments

Comments
 (0)