From 8dd60d09f5eabe79b5687d85bcc5f2488cd8bdf7 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Fri, 20 Mar 2026 18:39:35 +1100 Subject: [PATCH 1/2] fix(google-maps): fix OverlayView, InfoWindow, and AdvancedMarkerElement DX issues - Fix OverlayView not rendering when v-model:open is not used. Vue's boolean casting converted the missing prop to false, causing draw() to hide content. Fixed with { default: undefined } on the model. - Fix InfoWindow not toggling on re-click. Added isOpen state tracking so clicking the same marker closes the InfoWindow. - Fix opening a new InfoWindow not closing the previous one. Added shared activateInfoWindow on MAP_INJECTION_KEY so only one InfoWindow is open at a time within a map. - Switch AdvancedMarkerElement click from deprecated addListener('click') to addEventListener('gmp-click') with gmpClickable: true. - Fix AdvancedMarkerElement emits not being registered at runtime. Changed defineEmits from complex TS type to runtime array syntax. - Remove broken changeQuery button from styled playground page. - Update overlay-popup playground to use Tailwind styled card content instead of inline styles, showcasing OverlayView's slot flexibility. - Add regression tests for all fixes. --- .../google-maps/overlay-popup.vue | 69 ++++++++ .../third-parties/google-maps/styled.vue | 25 --- .../GoogleMaps/ScriptGoogleMaps.vue | 13 +- .../ScriptGoogleMapsAdvancedMarkerElement.vue | 41 +++-- .../GoogleMaps/ScriptGoogleMapsInfoWindow.vue | 42 +++-- .../ScriptGoogleMapsOverlayView.vue | 2 +- .../components/GoogleMaps/injectionKeys.ts | 2 + test/unit/__mocks__/google-maps-api.ts | 3 + test/unit/google-maps-regressions.test.ts | 157 ++++++++++++++++++ 9 files changed, 295 insertions(+), 59 deletions(-) create mode 100644 playground/pages/third-parties/google-maps/overlay-popup.vue create mode 100644 test/unit/google-maps-regressions.test.ts diff --git a/playground/pages/third-parties/google-maps/overlay-popup.vue b/playground/pages/third-parties/google-maps/overlay-popup.vue new file mode 100644 index 00000000..b6865ea7 --- /dev/null +++ b/playground/pages/third-parties/google-maps/overlay-popup.vue @@ -0,0 +1,69 @@ + + + diff --git a/playground/pages/third-parties/google-maps/styled.vue b/playground/pages/third-parties/google-maps/styled.vue index 062a009f..0e6c33d5 100644 --- a/playground/pages/third-parties/google-maps/styled.vue +++ b/playground/pages/third-parties/google-maps/styled.vue @@ -16,30 +16,5 @@ const mapOptions = { :map-options="mapOptions" /> -
- -
- - diff --git a/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue b/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue index 03f4d3b6..03285307 100644 --- a/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +++ b/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue @@ -334,7 +334,18 @@ const googleMaps = { defineExpose(googleMaps) -provide(MAP_INJECTION_KEY, { map, mapsApi }) +// Shared InfoWindow group: only one InfoWindow open at a time within this map +let activeInfoWindow: google.maps.InfoWindow | undefined +provide(MAP_INJECTION_KEY, { + map, + mapsApi, + activateInfoWindow(iw: google.maps.InfoWindow) { + if (activeInfoWindow && activeInfoWindow !== iw) { + activeInfoWindow.close() + } + activeInfoWindow = iw + }, +}) onMounted(() => { watch(ready, (v) => { diff --git a/src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue b/src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue index 166b730e..1012f592 100644 --- a/src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue +++ b/src/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue @@ -14,32 +14,22 @@ const props = defineProps<{ options?: Omit }>() -const emit = defineEmits<{ - (event: typeof eventsWithoutPayload[number]): void - (event: typeof eventsWithMapMouseEventPayload[number], payload: google.maps.MapMouseEvent): void -}>() - -// AdvancedMarkerElement supported events only -// See https://developers.google.com/maps/documentation/javascript/reference/advanced-markers -const eventsWithoutPayload = [] as const - -const eventsWithMapMouseEventPayload = [ - 'click', - 'drag', - 'dragend', - 'dragstart', -] as const - +const emit = defineEmits(['click', 'drag', 'dragend', 'dragstart']) +const dragEvents = ['drag', 'dragend', 'dragstart'] as const const slots = useSlots() const markerContent = useTemplateRef('marker-content') const markerClustererContext = inject(MARKER_CLUSTERER_INJECTION_KEY, undefined) +// gmp-click handler for cleanup (AdvancedMarkerElement uses DOM gmp-click instead of Maps addListener click) +let gmpClickHandler: ((e: any) => void) | undefined + const advancedMarkerElement = useGoogleMapsResource({ ready: () => !slots.content || !!markerContent.value, async create({ mapsApi, map }) { await mapsApi.importLibrary('marker') const marker = new mapsApi.marker.AdvancedMarkerElement({ ...props.options, + gmpClickable: true, ...(props.position ? { position: props.position } : {}), }) @@ -48,11 +38,6 @@ const advancedMarkerElement = useGoogleMapsResource emit('click', e) + marker.addEventListener('gmp-click', gmpClickHandler) + + // Drag events still use Maps API addListener + bindGoogleMapsEvents(marker, emit, { + withPayload: dragEvents, + }) + return marker }, cleanup(marker, { mapsApi }) { + if (gmpClickHandler) { + marker.removeEventListener('gmp-click', gmpClickHandler) + gmpClickHandler = undefined + } mapsApi.event.clearInstanceListeners(marker) if (markerClustererContext?.markerClusterer.value) { markerClustererContext.markerClusterer.value.removeMarker(marker, true) diff --git a/src/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue b/src/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue index 8ac01a23..cb6b8707 100644 --- a/src/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue +++ b/src/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue @@ -1,7 +1,7 @@