Skip to content

Commit bf560fd

Browse files
authored
refactor(google-maps)!: deprecate top-level center/zoom props (#694)
1 parent 33ebf61 commit bf560fd

File tree

5 files changed

+215
-7
lines changed

5 files changed

+215
-7
lines changed

docs/content/docs/4.migration-guide/1.v0-to-v1.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,15 @@ Use child `ScriptGoogleMapsMarker` components instead:
383383
+</ScriptGoogleMaps>
384384
```
385385

386+
#### Top-Level `center` and `zoom` Props Deprecated
387+
388+
The top-level `center` and `zoom` props on `<ScriptGoogleMaps>`{lang="html"} are deprecated in favour of passing them via `mapOptions`. Both APIs still work; using the legacy form emits a dev-mode warning. When both are set, `mapOptions` wins.
389+
390+
```diff
391+
-<ScriptGoogleMaps :center="{ lat, lng }" :zoom="12" />
392+
+<ScriptGoogleMaps :map-options="{ center: { lat, lng }, zoom: 12 }" />
393+
```
394+
386395
### Google Maps Static Placeholder ([#673](https://github.com/nuxt/scripts/pull/673))
387396

388397
v1 extracts the built-in static map placeholder into a standalone [`<ScriptGoogleMapsStaticMap>`{lang="html"}](/scripts/google-maps/api/static-map) component. This removes the following props from `<ScriptGoogleMaps>`{lang="html"}:

docs/content/scripts/google-maps/2.api/1.script-google-maps.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@ By default, it will load on the `mouseenter`, `mouseover`, and `mousedown` event
1515

1616
See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for all props, events, and slots.
1717

18+
::tip
19+
**Deprecated:** the top-level `center` and `zoom` props are now deprecated. Pass them via `mapOptions` instead. The legacy props still work and emit a dev-mode warning when used. `mapOptions.center` and `mapOptions.zoom` take precedence when both are set.
20+
21+
```vue
22+
<template>
23+
<!-- Before (deprecated) -->
24+
<ScriptGoogleMaps :center="{ lat, lng }" :zoom="12" />
25+
26+
<!-- After -->
27+
<ScriptGoogleMaps :map-options="{ center: { lat, lng }, zoom: 12 }" />
28+
</template>
29+
```
30+
::
31+
1832
## Template Ref API
1933

2034
Access the basic Google Maps instances via a template ref. The exposed object contains:

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

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,20 @@ export interface ScriptGoogleMapsProps {
1717
apiKey?: string
1818
/**
1919
* A latitude / longitude of where to focus the map.
20+
*
21+
* @deprecated Pass `center` via `mapOptions` instead. The top-level `center`
22+
* prop will be removed in a future major version. When both are set,
23+
* `mapOptions.center` wins.
24+
* @see https://scripts.nuxt.com/docs/migration-guide/v0-to-v1
2025
*/
2126
center?: google.maps.LatLng | google.maps.LatLngLiteral | `${string},${string}`
2227
/**
2328
* Zoom level for the map (0-21). Reactive: changing this will update the map.
24-
* Takes precedence over mapOptions.zoom when provided.
29+
*
30+
* @deprecated Pass `zoom` via `mapOptions` instead. The top-level `zoom`
31+
* prop will be removed in a future major version. When both are set,
32+
* `mapOptions.zoom` wins.
33+
* @see https://scripts.nuxt.com/docs/migration-guide/v0-to-v1
2534
*/
2635
zoom?: number
2736
/**
@@ -138,7 +147,7 @@ import { defu } from 'defu'
138147
import { tryUseNuxtApp, useHead, useRuntimeConfig } from 'nuxt/app'
139148
import { computed, onBeforeUnmount, onMounted, provide, ref, shallowRef, toRaw, useAttrs, useTemplateRef, watch } from 'vue'
140149
import ScriptAriaLoadingIndicator from '../ScriptAriaLoadingIndicator.vue'
141-
import { MAP_INJECTION_KEY, waitForMapsReady } from './useGoogleMapsResource'
150+
import { MAP_INJECTION_KEY, waitForMapsReady, warnDeprecatedTopLevelMapProps } from './useGoogleMapsResource'
142151
143152
const props = withDefaults(defineProps<ScriptGoogleMapsProps>(), {
144153
// @ts-expect-error untyped
@@ -184,6 +193,7 @@ if (import.meta.dev) {
184193
if (prop in attrs)
185194
console.warn(`[nuxt-scripts] <ScriptGoogleMaps> prop "${prop}" was removed in v1. ${message} See https://scripts.nuxt.com/docs/migration-guide/v0-to-v1`)
186195
}
196+
warnDeprecatedTopLevelMapProps({ center: props.center, zoom: props.zoom })
187197
}
188198
189199
const rootEl = useTemplateRef<HTMLElement>('rootEl')
@@ -204,10 +214,17 @@ const { load, status, onLoaded } = useScriptGoogleMaps({
204214
205215
const options = computed(() => {
206216
const mapId = props.mapOptions?.styles ? undefined : (currentMapId.value || 'map')
207-
return defu({ center: centerOverride.value, mapId, zoom: props.zoom }, props.mapOptions, {
208-
center: props.center,
209-
zoom: 15,
210-
})
217+
// Precedence (defu merges left-to-right, leftmost wins):
218+
// 1. centerOverride: resolved query result, always wins for center
219+
// 2. mapOptions: preferred public API
220+
// 3. deprecated top-level: legacy fallback for center/zoom
221+
// 4. defaults: { zoom: 15 } when nothing else is set
222+
return defu(
223+
{ center: centerOverride.value, mapId },
224+
props.mapOptions,
225+
{ center: props.center, zoom: props.zoom },
226+
{ zoom: 15 },
227+
)
211228
})
212229
const isMapReady = ref(false)
213230

packages/script/src/runtime/components/GoogleMaps/useGoogleMapsResource.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,33 @@ export interface GoogleMapsResourceContext {
4141
mapsApi: typeof google.maps
4242
}
4343

44+
/**
45+
* Emits dev-mode deprecation warnings for the legacy top-level `center` and
46+
* `zoom` props on `<ScriptGoogleMaps>`. Both props still work, but new code
47+
* should pass them via `mapOptions` instead.
48+
*
49+
* Returns the number of warnings emitted (useful for tests).
50+
*/
51+
export function warnDeprecatedTopLevelMapProps(props: {
52+
center?: unknown
53+
zoom?: unknown
54+
}): number {
55+
let warned = 0
56+
if (props.center !== undefined) {
57+
warned++
58+
console.warn(
59+
'[nuxt-scripts] <ScriptGoogleMaps> prop "center" is deprecated; use `:map-options="{ center: ... }"` instead. See https://scripts.nuxt.com/docs/migration-guide/v0-to-v1',
60+
)
61+
}
62+
if (props.zoom !== undefined) {
63+
warned++
64+
console.warn(
65+
'[nuxt-scripts] <ScriptGoogleMaps> prop "zoom" is deprecated; use `:map-options="{ zoom: ... }"` instead. See https://scripts.nuxt.com/docs/migration-guide/v0-to-v1',
66+
)
67+
}
68+
return warned
69+
}
70+
4471
/**
4572
* Wait until the Google Maps API and a Map instance are both available.
4673
*

test/unit/google-maps-components.test.ts

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import type { MocksType } from './__helpers__/google-maps-test-utils'
22
/**
33
* @vitest-environment happy-dom
44
*/
5-
import { beforeEach, describe, expect, it, vi } from 'vitest'
5+
import { defu } from 'defu'
6+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
67
import { ref } from 'vue'
8+
import { warnDeprecatedTopLevelMapProps } from '../../packages/script/src/runtime/components/GoogleMaps/useGoogleMapsResource'
79
import {
810

911
simulateAdvancedMarkerLifecycle,
@@ -366,3 +368,142 @@ describe('google Maps SFC Components Logic', () => {
366368
})
367369
})
368370
})
371+
372+
describe('scriptGoogleMaps top-level center/zoom deprecation', () => {
373+
// Guards the deprecation path introduced when migrating users from
374+
// top-level `center`/`zoom` props to `mapOptions.{center,zoom}`. Both
375+
// APIs must keep working; the new API takes precedence.
376+
377+
describe('warnDeprecatedTopLevelMapProps', () => {
378+
let warnSpy: ReturnType<typeof vi.spyOn>
379+
380+
beforeEach(() => {
381+
warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
382+
})
383+
384+
afterEach(() => {
385+
warnSpy.mockRestore()
386+
})
387+
388+
it('warns when top-level center is set', () => {
389+
const warned = warnDeprecatedTopLevelMapProps({ center: { lat: 0, lng: 0 } })
390+
391+
expect(warned).toBe(1)
392+
expect(warnSpy).toHaveBeenCalledTimes(1)
393+
expect(warnSpy.mock.calls[0]![0]).toContain('"center" is deprecated')
394+
expect(warnSpy.mock.calls[0]![0]).toContain('map-options')
395+
expect(warnSpy.mock.calls[0]![0]).toContain('migration-guide/v0-to-v1')
396+
})
397+
398+
it('warns when top-level zoom is set', () => {
399+
const warned = warnDeprecatedTopLevelMapProps({ zoom: 12 })
400+
401+
expect(warned).toBe(1)
402+
expect(warnSpy).toHaveBeenCalledTimes(1)
403+
expect(warnSpy.mock.calls[0]![0]).toContain('"zoom" is deprecated')
404+
})
405+
406+
it('warns once for each prop when both are set', () => {
407+
const warned = warnDeprecatedTopLevelMapProps({ center: { lat: 0, lng: 0 }, zoom: 8 })
408+
409+
expect(warned).toBe(2)
410+
expect(warnSpy).toHaveBeenCalledTimes(2)
411+
})
412+
413+
it('does not warn when neither prop is set', () => {
414+
const warned = warnDeprecatedTopLevelMapProps({})
415+
416+
expect(warned).toBe(0)
417+
expect(warnSpy).not.toHaveBeenCalled()
418+
})
419+
420+
it('does not warn for explicit undefined values', () => {
421+
// withDefaults leaves undeclared optional props as undefined; that
422+
// must not trip the warning.
423+
const warned = warnDeprecatedTopLevelMapProps({ center: undefined, zoom: undefined })
424+
425+
expect(warned).toBe(0)
426+
expect(warnSpy).not.toHaveBeenCalled()
427+
})
428+
})
429+
430+
describe('mapOptions precedence over deprecated top-level props', () => {
431+
// Mirrors the precedence order used in ScriptGoogleMaps.vue's `options`
432+
// computed: { centerOverride, mapId } > mapOptions > { props.center,
433+
// props.zoom } > { zoom: 15 }. defu merges left-to-right, leftmost wins.
434+
function mergeOptions(props: {
435+
center?: any
436+
zoom?: number
437+
mapOptions?: Record<string, any>
438+
centerOverride?: any
439+
mapId?: string
440+
}) {
441+
return defu(
442+
{ center: props.centerOverride, mapId: props.mapId },
443+
props.mapOptions,
444+
{ center: props.center, zoom: props.zoom },
445+
{ zoom: 15 },
446+
)
447+
}
448+
449+
it('mapOptions.center wins over deprecated top-level center', () => {
450+
const merged = mergeOptions({
451+
center: { lat: 1, lng: 1 },
452+
mapOptions: { center: { lat: 2, lng: 2 } },
453+
})
454+
455+
expect(merged.center).toEqual({ lat: 2, lng: 2 })
456+
})
457+
458+
it('mapOptions.zoom wins over deprecated top-level zoom', () => {
459+
const merged = mergeOptions({
460+
zoom: 5,
461+
mapOptions: { zoom: 10 },
462+
})
463+
464+
expect(merged.zoom).toBe(10)
465+
})
466+
467+
it('top-level center is used when mapOptions.center is absent', () => {
468+
const merged = mergeOptions({
469+
center: { lat: 3, lng: 4 },
470+
mapOptions: { mapTypeId: 'roadmap' },
471+
})
472+
473+
expect(merged.center).toEqual({ lat: 3, lng: 4 })
474+
expect(merged.mapTypeId).toBe('roadmap')
475+
})
476+
477+
it('top-level zoom is used when mapOptions.zoom is absent', () => {
478+
const merged = mergeOptions({
479+
zoom: 7,
480+
mapOptions: { mapTypeId: 'satellite' },
481+
})
482+
483+
expect(merged.zoom).toBe(7)
484+
})
485+
486+
it('produces identical merged options for old and new APIs', () => {
487+
const oldApi = mergeOptions({ center: { lat: 5, lng: 6 }, zoom: 14 })
488+
const newApi = mergeOptions({ mapOptions: { center: { lat: 5, lng: 6 }, zoom: 14 } })
489+
490+
expect(oldApi).toEqual(newApi)
491+
})
492+
493+
it('falls back to default zoom when nothing is set', () => {
494+
const merged = mergeOptions({})
495+
496+
expect(merged.zoom).toBe(15)
497+
})
498+
499+
it('centerOverride (resolved query result) wins over both APIs', () => {
500+
const merged = mergeOptions({
501+
center: { lat: 1, lng: 1 },
502+
mapOptions: { center: { lat: 2, lng: 2 } },
503+
centerOverride: { lat: 99, lng: 99 },
504+
})
505+
506+
expect(merged.center).toEqual({ lat: 99, lng: 99 })
507+
})
508+
})
509+
})

0 commit comments

Comments
 (0)