Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,14 @@ export default antfu(
'no-new': 'off',
},
},
{
// SFCs with two <script> blocks (one for exported types, one for setup)
// confuse `import/first`: it concatenates the blocks and reports the
// setup-block imports as "below the body of the module" because the
// first block has exports.
files: ['**/*.vue'],
rules: {
'import/first': 'off',
},
},
)
207 changes: 129 additions & 78 deletions packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,13 @@
/// <reference types="google.maps" />
import type { ElementScriptTrigger } from '#nuxt-scripts/types'
import type { HTMLAttributes, ReservedProps, ShallowRef } from 'vue'
import { useScriptTriggerElement } from '#nuxt-scripts/composables/useScriptTriggerElement'
import { useScriptGoogleMaps } from '#nuxt-scripts/registry/google-maps'
import { scriptRuntimeConfig, scriptsPrefix } from '#nuxt-scripts/utils'
import { defu } from 'defu'
import { tryUseNuxtApp, useHead, useRuntimeConfig } from 'nuxt/app'
import { computed, onBeforeUnmount, onMounted, provide, ref, shallowRef, toRaw, useAttrs, watch } from 'vue'
import ScriptAriaLoadingIndicator from '../ScriptAriaLoadingIndicator.vue'

import { MAP_INJECTION_KEY } from './useGoogleMapsResource'

const DIGITS_ONLY_RE = /^\d+$/
const DIGITS_PX_RE = /^\d+px$/i

export { MAP_INJECTION_KEY } from './useGoogleMapsResource'
</script>

<script lang="ts" setup>
const props = withDefaults(defineProps<{
export interface ScriptGoogleMapsProps {
/**
* Defines the trigger event to load the script.
* @default ['mouseenter', 'mouseover', 'mousedown']
*/
trigger?: ElementScriptTrigger
/**
Expand All @@ -46,19 +33,21 @@ const props = withDefaults(defineProps<{
*/
region?: string
/**
* Defines the language of the map
* Defines the language of the map.
*/
language?: string
/**
* Defines the version of google maps js API
* Defines the version of google maps js API.
*/
version?: string
/**
* Defines the width of the map.
* @default 640
*/
width?: number | string
/**
* Defines the height of the map
* Defines the height of the map.
* @default 400
*/
height?: number | string
/**
Expand All @@ -73,57 +62,112 @@ const props = withDefaults(defineProps<{
mapIds?: { light?: string, dark?: string }
/**
* Manual color mode control. When provided, overrides auto-detection from @nuxtjs/color-mode.
* Accepts 'light', 'dark', or a reactive ref.
* Accepts 'light' or 'dark'.
*/
colorMode?: 'light' | 'dark'
}>(), {
// @ts-expect-error untyped
trigger: ['mouseenter', 'mouseover', 'mousedown'],
width: 640,
height: 400,
})
}

export interface ScriptGoogleMapsExpose {
/**
* A reference to the loaded Google Maps API, or `undefined` if not yet loaded.
*/
googleMaps: ShallowRef<typeof google.maps | undefined>
/**
* A reference to the Google Map instance, or `undefined` if not yet initialized.
*/
map: ShallowRef<google.maps.Map | undefined>
/**
* Utility function to resolve a location query (e.g. "New York, NY") to latitude/longitude coordinates.
* Uses a caching mechanism and a server-side proxy to avoid unnecessary client-side API calls.
*/
resolveQueryToLatLng: (query: string) => Promise<google.maps.LatLng | google.maps.LatLngLiteral | undefined>
/**
* Utility function to dynamically import additional Google Maps libraries (e.g. "marker", "places").
* Caches imported libraries for efficient reuse.
*/
importLibrary: {
(key: 'marker'): Promise<google.maps.MarkerLibrary>
(key: 'places'): Promise<google.maps.PlacesLibrary>
(key: 'geometry'): Promise<google.maps.GeometryLibrary>
(key: 'drawing'): Promise<google.maps.DrawingLibrary>
(key: 'visualization'): Promise<google.maps.VisualizationLibrary>
(key: string): Promise<any>
}
}

const emits = defineEmits<{
export interface ScriptGoogleMapsEmits {
/**
* Fired when the Google Maps instance is fully loaded and ready to use. Provides access to the maps API.
*/
ready: [e: typeof googleMaps]
ready: [payload: ScriptGoogleMapsExpose]
/**
* Fired when the Google Maps script fails to load.
*/
error: []
}>()
}

defineSlots<{
export interface ScriptGoogleMapsSlots {
/**
* Default slot for rendering child components (e.g. markers, info windows) that depend on the map being ready.
*/
default?: () => any
placeholder?: () => any
/**
* Slot displayed while the map is loading. Can be used to show a custom loading indicator.
*/
loading?: () => any
/**
* Slot displayed when the script is awaiting user interaction to load (based on the `trigger` prop).
*/
awaitingLoad?: () => any
/**
* Slot displayed if the script fails to load.
*/
error?: () => any
}>()
/**
* Slot displayed as a placeholder before the map is ready. Useful for showing a static map or skeleton.
*/
placeholder?: () => any
}
</script>

<script lang="ts" setup>
import { useScriptTriggerElement } from '#nuxt-scripts/composables/useScriptTriggerElement'
import { useScriptGoogleMaps } from '#nuxt-scripts/registry/google-maps'
import { scriptRuntimeConfig, scriptsPrefix } from '#nuxt-scripts/utils'
import { defu } from 'defu'
import { tryUseNuxtApp, useHead, useRuntimeConfig } from 'nuxt/app'
import { computed, onBeforeUnmount, onMounted, provide, ref, shallowRef, toRaw, useAttrs, useTemplateRef, watch } from 'vue'
import ScriptAriaLoadingIndicator from '../ScriptAriaLoadingIndicator.vue'
import { MAP_INJECTION_KEY } from './useGoogleMapsResource'

const props = withDefaults(defineProps<ScriptGoogleMapsProps>(), {
// @ts-expect-error untyped
trigger: ['mouseenter', 'mouseover', 'mousedown'],
width: 640,
height: 400,
})
const emits = defineEmits<ScriptGoogleMapsEmits>()
defineSlots<ScriptGoogleMapsSlots>()
const DIGITS_ONLY_RE = /^\d+$/
const DIGITS_PX_RE = /^\d+px$/i

const apiKey = props.apiKey || scriptRuntimeConfig('googleMaps')?.apiKey
const runtimeConfig = useRuntimeConfig()

// Color mode support - try to auto-detect from @nuxtjs/color-mode
const nuxtApp = tryUseNuxtApp()
const nuxtColorMode = nuxtApp?.$colorMode as { value: string } | undefined

const currentColorMode = computed(() => {
if (props.colorMode)
return props.colorMode
if (nuxtColorMode?.value)
return nuxtColorMode.value === 'dark' ? 'dark' : 'light'
return 'light'
const nuxtColorMode = computed(() => {
const value = (tryUseNuxtApp()?.$colorMode as { value: string } | undefined)?.value
return value === 'dark' || value === 'light' ? value : undefined
})

const currentColorMode = computed(() => props.colorMode || nuxtColorMode.value || 'light')

const currentMapId = computed(() => {
if (!props.mapIds)
return props.mapOptions?.mapId
return props.mapIds[currentColorMode.value] || props.mapIds.light || props.mapOptions?.mapId
})

const mapsApi = ref<typeof google.maps | undefined>()
const mapsApi = shallowRef<typeof google.maps | undefined>()

if (import.meta.dev) {
if (!apiKey)
Expand All @@ -142,8 +186,8 @@ if (import.meta.dev) {
}
}

const rootEl = ref<HTMLElement>()
const mapEl = ref<HTMLElement>()
const rootEl = useTemplateRef<HTMLElement>('rootEl')
const mapEl = useTemplateRef<HTMLElement>('mapEl')

const centerOverride = ref()

Expand All @@ -165,7 +209,7 @@ const options = computed(() => {
zoom: 15,
})
})
const ready = ref(false)
const isMapReady = ref(false)

const map: ShallowRef<google.maps.Map | undefined> = shallowRef()

Expand Down Expand Up @@ -198,31 +242,37 @@ async function resolveQueryToLatLng(query: string) {
}

// Fallback: use Places API client-side
// eslint-disable-next-line no-async-promise-executor
return new Promise<google.maps.LatLng>(async (resolve, reject) => {
if (!mapsApi.value) {
await load()
// await new promise, watch until mapsApi is set
await new Promise<void>((resolve) => {
const _ = watch(mapsApi, () => {
_()
resolve()
})
if (!mapsApi.value) {
await load()
// await new promise, watch until mapsApi is set
await new Promise<void>((resolve) => {
const _ = watch(mapsApi, () => {
_()
resolve()
})
}
const placesService = new mapsApi.value!.places.PlacesService(map.value!)
placesService.findPlaceFromQuery({
query,
fields: ['name', 'geometry'],
}, (results, status) => {
if (status === 'OK' && results?.[0]?.geometry?.location)
return resolve(results[0].geometry.location)
return reject(new Error(`No location found for ${query}`))
})
}).then((res) => {
queryToLatLngCache.set(query, res)
return res
}
Comment on lines +245 to +254
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file structure and locate the file
fd ScriptGoogleMaps.vue

Repository: nuxt/scripts

Length of output: 126


🏁 Script executed:

# Read the file to understand the context around lines 246-255
wc -l packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue

Repository: nuxt/scripts

Length of output: 130


🏁 Script executed:

# Get the code context around the problematic lines
cat -n packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue | sed -n '230,270p'

Repository: nuxt/scripts

Length of output: 1823


🏁 Script executed:

# Find the load() function definition
rg -A 20 "^\s*(?:async\s+)?load\s*\(" packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue

Repository: nuxt/scripts

Length of output: 38


🏁 Script executed:

# Search for resolveQueryToLatLng to understand its usage
rg -B 5 -A 10 "resolveQueryToLatLng" packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue

Repository: nuxt/scripts

Length of output: 3155


🏁 Script executed:

# Search for mapsApi ref definition and where it gets assigned
rg -B 2 -A 2 "mapsApi\s*=" packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue | head -40

Repository: nuxt/scripts

Length of output: 144


🏁 Script executed:

# Search for load() function definition
rg -B 5 -A 20 "const load\s*=|function load|async load" packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue

Repository: nuxt/scripts

Length of output: 38


🏁 Script executed:

# Search for where mapsApi.value is assigned
rg "mapsApi\.value\s*=" packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue

Repository: nuxt/scripts

Length of output: 95


🏁 Script executed:

# Get more context around the mapsApi initialization
cat -n packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue | sed -n '1,100p'

Repository: nuxt/scripts

Length of output: 3997


🏁 Script executed:

# Search for status variable that's used in proposed fix
rg "status\s*=" packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue | head -20

Repository: nuxt/scripts

Length of output: 318


🏁 Script executed:

# Search for useScript usage which likely defines load()
rg "useScript|from.*script" packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue

Repository: nuxt/scripts

Length of output: 494


🏁 Script executed:

# Get more context around the mapsApi.value assignment
rg -B 30 "mapsApi\.value\s*=" packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue

Repository: nuxt/scripts

Length of output: 1193


🏁 Script executed:

# Get more lines around the beginning to find setup
cat -n packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue | sed -n '100,200p'

Repository: nuxt/scripts

Length of output: 4822


🏁 Script executed:

# Search for status ref definition
rg "const status|status\s*=\s*ref|status\s*=\s*shallowRef" packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue

Repository: nuxt/scripts

Length of output: 38


🏁 Script executed:

# Get more context around the onLoaded callback
cat -n packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue | sed -n '375,395p'

Repository: nuxt/scripts

Length of output: 907


🏁 Script executed:

# Look for useScriptGoogleMaps implementation
fd useScriptGoogleMaps

Repository: nuxt/scripts

Length of output: 38


🏁 Script executed:

# Get the full context of the problematic code and surrounding logic
cat -n packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue | sed -n '240,290p'

Repository: nuxt/scripts

Length of output: 2016


🏁 Script executed:

# Let's verify the exact timing by checking if there's synchronous behavior in useScriptGoogleMaps
fd useScriptGoogleMaps -type f

Repository: nuxt/scripts

Length of output: 226


🏁 Script executed:

# Check if there's documentation or if load() might complete before watcher registration
cat packages/script/src/runtime/composables/useScriptGoogleMaps.ts | head -100

Repository: nuxt/scripts

Length of output: 150


🏁 Script executed:

# Verify the watch behavior - is it really non-immediate and could miss value changes?
rg "const mapsApi" packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue -A 5

Repository: nuxt/scripts

Length of output: 528


🏁 Script executed:

# Check if there are any other similar patterns with immediate watchers in the codebase
rg "watch\(" packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue | grep -c immediate

Repository: nuxt/scripts

Length of output: 57


🏁 Script executed:

# Find the useScriptGoogleMaps file
fd useScriptGoogleMaps

Repository: nuxt/scripts

Length of output: 38


🏁 Script executed:

# Search for the google-maps registry file
fd "google-maps" --type f | head -20

Repository: nuxt/scripts

Length of output: 656


🏁 Script executed:

# Look for where useScriptGoogleMaps is defined
rg "export.*useScriptGoogleMaps|function useScriptGoogleMaps" packages/

Repository: nuxt/scripts

Length of output: 198


🏁 Script executed:

# Get the full implementation of useScriptGoogleMaps
cat packages/script/src/runtime/registry/google-maps.ts

Repository: nuxt/scripts

Length of output: 2136


🏁 Script executed:

# Let's verify the timing issue by checking if the watcher could miss the value change
# Check the full context where resolveQueryToLatLng is defined and see if status is in scope
cat -n packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue | sed -n '190,230p'

Repository: nuxt/scripts

Length of output: 1687


🏁 Script executed:

# Verify if status variable is accessible within the resolveQueryToLatLng function scope
rg -B 50 "async function resolveQueryToLatLng" packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue | grep -E "status|const \{" | head -20

Repository: nuxt/scripts

Length of output: 112


Close the load() / watcher race condition in resolveQueryToLatLng().

If load() completes and triggers onLoaded before the watcher at line 250 is created, mapsApi.value will already be set. Since the watcher is non-immediate, it only fires on value changes, not on existing values—causing it to never fire and the Promise to hang indefinitely.

Recheck mapsApi.value after await load(), or use an immediate watcher with error handling via the status variable:

Suggested fix
   if (!mapsApi.value) {
     await load()
-    // await new promise, watch until mapsApi is set
-    await new Promise<void>((resolve) => {
-      const _ = watch(mapsApi, () => {
-        _()
-        resolve()
-      })
-    })
+    if (!mapsApi.value) {
+      await new Promise<void>((resolve, reject) => {
+        const stop = watch([mapsApi, status], ([api, nextStatus]) => {
+          if (api) {
+            stop()
+            resolve()
+          }
+          else if (nextStatus === 'error') {
+            stop()
+            reject(new Error('Failed to load Google Maps'))
+          }
+        }, { immediate: true })
+      })
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!mapsApi.value) {
await load()
// await new promise, watch until mapsApi is set
await new Promise<void>((resolve) => {
const _ = watch(mapsApi, () => {
_()
resolve()
})
}
const placesService = new mapsApi.value!.places.PlacesService(map.value!)
placesService.findPlaceFromQuery({
query,
fields: ['name', 'geometry'],
}, (results, status) => {
if (status === 'OK' && results?.[0]?.geometry?.location)
return resolve(results[0].geometry.location)
return reject(new Error(`No location found for ${query}`))
})
}).then((res) => {
queryToLatLngCache.set(query, res)
return res
}
if (!mapsApi.value) {
await load()
if (!mapsApi.value) {
await new Promise<void>((resolve, reject) => {
const stop = watch([mapsApi, status], ([api, nextStatus]) => {
if (api) {
stop()
resolve()
}
else if (nextStatus === 'error') {
stop()
reject(new Error('Failed to load Google Maps'))
}
}, { immediate: true })
})
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue`
around lines 246 - 255, In resolveQueryToLatLng(), the Promise waits on a
non-immediate watcher of mapsApi which can hang if load() sets mapsApi before
the watcher is created; fix by re-checking mapsApi.value immediately after await
load() and resolving if set, or replace the watcher with watch(mapsApi,
callback, { immediate: true }) (and ensure the watcher is properly torn down) so
the promise always resolves when mapsApi is already populated or changes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing on main — the watcher race in resolveQueryToLatLng is identical to the version this PR replaces (same non-immediate watch(mapsApi, ...) pattern). Out of scope for this internal-cleanup PR; will get its own fix(google-maps): close races in resolveQueryToLatLng follow-up alongside the map.value guard you flagged below.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@harlan-zw, understood — the non-immediate watch(mapsApi, ...) pattern is identical to what was on main before this PR, so it's not a regression introduced here. I'll treat this as pre-existing and out of scope for this cleanup PR.

Would you like me to open a GitHub issue to track the fix(google-maps): close races in resolveQueryToLatLng follow-up (covering both the mapsApi watcher race and the map.value guard)?


const placesService = new mapsApi.value!.places.PlacesService(map.value!)
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential null reference: map.value may be undefined when PlacesService is instantiated.

resolveQueryToLatLng is exposed publicly and can be called before the map is initialized. After mapsApi.value becomes available, there's a window before map.value is assigned in onLoaded (line 384). Calling this function during that window will throw because PlacesService requires a valid map/element.

Suggested fix: wait for map initialization
-  const placesService = new mapsApi.value!.places.PlacesService(map.value!)
+  // Wait for map to be initialized if not already
+  if (!map.value) {
+    await new Promise<void>((resolve) => {
+      const stop = watch(map, (m) => {
+        if (m) {
+          stop()
+          resolve()
+        }
+      }, { immediate: true })
+    })
+  }
+  const placesService = new mapsApi.value!.places.PlacesService(map.value!)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const placesService = new mapsApi.value!.places.PlacesService(map.value!)
// Wait for map to be initialized if not already
if (!map.value) {
await new Promise<void>((resolve) => {
const stop = watch(map, (m) => {
if (m) {
stop()
resolve()
}
}, { immediate: true })
})
}
const placesService = new mapsApi.value!.places.PlacesService(map.value!)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue` at
line 256, The PlacesService is created with new
mapsApi.value!.places.PlacesService(map.value!) which can run before map.value
is set, causing a null reference when resolveQueryToLatLng is called; change to
lazily create or defer creating placesService until the map is initialized
(e.g., move instantiation into resolveQueryToLatLng or check map.value and await
the onLoaded/map-ready promise before calling new
mapsApi.value!.places.PlacesService), and add a defensive guard in
resolveQueryToLatLng to wait for or throw a clear error if map.value is not yet
available; reference symbols: placesService creation line, resolveQueryToLatLng,
mapsApi.value, map.value, onLoaded.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing on main — same new mapsApi.value!.places.PlacesService(map.value!) line existed before this refactor. Real bug, just out of scope for this PR. Tracking it together with the watcher race above for a dedicated fix PR.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@harlan-zw, understood — thanks for the context. The new mapsApi.value!.places.PlacesService(map.value!) null-ref issue predates this PR and is tracked for a dedicated fix PR alongside the watcher race.

Would you like me to open a GitHub issue to track both of these (the map.value null-ref in PlacesService instantiation and the watcher race in resolveQueryToLatLng) so they don't get lost?

const result = await new Promise<google.maps.LatLng>((resolve, reject) => {
placesService.findPlaceFromQuery(
{
query,
fields: ['name', 'geometry'],
},
(results, status) => {
if (status === 'OK' && results?.[0]?.geometry?.location) {
resolve(results[0].geometry.location)
}
else {
reject(new Error(`No location found for ${query}`))
}
},
)
})

queryToLatLngCache.set(query, result)
return result
}

const libraries = new Map<string, any>()
Expand Down Expand Up @@ -254,14 +304,14 @@ function importLibrary<T>(key: string): Promise<T> {
return cached as Promise<T>
}

const googleMaps = {
const googleMaps: ScriptGoogleMapsExpose = {
googleMaps: mapsApi,
map,
resolveQueryToLatLng,
importLibrary,
} as const
}

defineExpose(googleMaps)
defineExpose<ScriptGoogleMapsExpose>(googleMaps)

// Shared InfoWindow group: only one InfoWindow open at a time within this map
let activeInfoWindow: google.maps.InfoWindow | undefined
Expand All @@ -277,7 +327,7 @@ provide(MAP_INJECTION_KEY, {
})

onMounted(() => {
watch(ready, (v) => {
watch(isMapReady, (v) => {
if (v) {
emits('ready', googleMaps)
}
Expand All @@ -299,13 +349,13 @@ onMounted(() => {
if (map.value && zoom != null)
map.value.setZoom(zoom)
})
watch([() => options.value.center, ready, map], async (next) => {
watch([() => options.value.center, isMapReady, map], async (next) => {
if (!map.value) {
return
}
let center = toRaw(next[0])
if (center) {
if (isLocationQuery(center) && ready.value) {
if (isLocationQuery(center) && isMapReady.value) {
center = await resolveQueryToLatLng(center as string)
}
// Skip setCenter if the map is already at the same position to avoid
Expand Down Expand Up @@ -334,9 +384,10 @@ onMounted(() => {
map.value = new mapsApi.value!.Map(mapEl.value!, _options)
if (center && isLocationQuery(center)) {
centerOverride.value = await resolveQueryToLatLng(center)
map.value?.setCenter(centerOverride.value)
if (centerOverride.value)
map.value?.setCenter(centerOverride.value)
}
ready.value = true
isMapReady.value = true
})
})

Expand Down Expand Up @@ -404,9 +455,9 @@ onBeforeUnmount(() => {

<template>
<div ref="rootEl" v-bind="rootAttrs">
<div v-show="ready" ref="mapEl" :style="{ width: '100%', height: '100%', maxWidth: '100%' }" />
<slot v-if="!ready" name="placeholder" />
<slot v-if="status !== 'awaitingLoad' && !ready" name="loading">
<div v-show="isMapReady" ref="mapEl" :style="{ width: '100%', height: '100%', maxWidth: '100%' }" />
<slot v-if="!isMapReady" name="placeholder" />
<slot v-if="status !== 'awaitingLoad' && !isMapReady" name="loading">
<ScriptAriaLoadingIndicator />
</slot>
<slot v-if="status === 'awaitingLoad'" name="awaitingLoad" />
Expand Down
Loading
Loading