@nuxt/scripts 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +73 -73
  2. package/dist/client/200.html +9 -9
  3. package/dist/client/404.html +9 -9
  4. package/dist/client/_nuxt/B7a7KwY0.js +1 -0
  5. package/dist/client/_nuxt/{Cdyv1BV6.js → BYm2jJpo.js} +11 -11
  6. package/dist/client/_nuxt/{CObySb1Z.js → CEoS15Fi.js} +1 -1
  7. package/dist/client/_nuxt/{DPxoUSuY.js → Nd9uLa0s.js} +1 -1
  8. package/dist/client/_nuxt/builds/latest.json +1 -1
  9. package/dist/client/_nuxt/builds/meta/0bada317-9aed-4ca5-ae27-fa6f72950e87.json +1 -0
  10. package/dist/client/_nuxt/{entry.FVeyw1Qn.css → entry.BAZUAl3s.css} +1 -1
  11. package/dist/client/_nuxt/error-404.ChzstOPh.css +1 -0
  12. package/dist/client/_nuxt/error-500.BaOmLlKq.css +1 -0
  13. package/dist/client/index.html +9 -9
  14. package/dist/module.d.mts +2 -2
  15. package/dist/module.d.ts +2 -2
  16. package/dist/module.json +1 -1
  17. package/dist/module.mjs +2 -0
  18. package/dist/runtime/components/ScriptCarbonAds.vue +78 -71
  19. package/dist/runtime/components/ScriptCrisp.vue +94 -87
  20. package/dist/runtime/components/ScriptGoogleAdsense.vue +75 -69
  21. package/dist/runtime/components/ScriptGoogleMaps.vue +457 -423
  22. package/dist/runtime/components/ScriptIntercom.vue +103 -96
  23. package/dist/runtime/components/ScriptLemonSqueezy.vue +52 -45
  24. package/dist/runtime/components/ScriptLoadingIndicator.vue +22 -22
  25. package/dist/runtime/components/ScriptStripePricingTable.vue +75 -68
  26. package/dist/runtime/components/ScriptVimeoPlayer.vue +281 -280
  27. package/dist/runtime/components/ScriptYouTubePlayer.vue +174 -171
  28. package/dist/runtime/composables/useScript.js +25 -7
  29. package/dist/runtime/composables/useScriptTriggerElement.d.ts +3 -1
  30. package/dist/runtime/composables/useScriptTriggerElement.js +46 -19
  31. package/dist/runtime/registry/google-adsense.js +11 -14
  32. package/dist/runtime/types.js +1 -1
  33. package/package.json +29 -28
  34. package/dist/client/_nuxt/BW5_3H_7.js +0 -1
  35. package/dist/client/_nuxt/builds/meta/b4af023e-aaec-4265-8ade-ce133bb4c4c3.json +0 -1
  36. package/dist/client/_nuxt/error-404.D8Cdd2Pl.css +0 -1
  37. package/dist/client/_nuxt/error-500.BENoupyF.css +0 -1
@@ -1,423 +1,457 @@
1
- <script lang="ts" setup>
2
- /// <reference types="google.maps" />
3
- import { computed, onBeforeUnmount, onMounted, ref, watch, toRaw } from 'vue'
4
- import type { HTMLAttributes, ImgHTMLAttributes, Ref, ReservedProps } from 'vue'
5
- import { withQuery } from 'ufo'
6
- import type { QueryObject } from 'ufo'
7
- import { defu } from 'defu'
8
- import { hash } from 'ohash'
9
- import type { ElementScriptTrigger } from '../types'
10
- import { scriptRuntimeConfig } from '../utils'
11
- import { useScriptTriggerElement } from '../composables/useScriptTriggerElement'
12
- import { useScriptGoogleMaps } from '../registry/google-maps'
13
- import { resolveComponent, useHead } from '#imports'
14
-
15
- interface PlaceholderOptions {
16
- width?: string | number
17
- height?: string | number
18
- center?: string
19
- zoom?: number
20
- size?: string
21
- scale?: number
22
- format?: 'png' | 'jpg' | 'gif' | 'png8' | 'png32' | 'jpg-baseline'
23
- maptype?: 'roadmap' | 'satellite' | 'terrain' | 'hybrid'
24
- language?: string
25
- region?: string
26
- markers?: string
27
- path?: string
28
- visible?: string
29
- style?: string
30
- map_id?: string
31
- key?: string
32
- signature?: string
33
- }
34
-
35
- const props = withDefaults(defineProps<{
36
- /**
37
- * Defines the trigger event to load the script.
38
- */
39
- trigger?: ElementScriptTrigger
40
- /**
41
- * Is Google Maps being rendered above the fold?
42
- * This will load the placeholder image with higher priority.
43
- */
44
- aboveTheFold?: boolean
45
- /**
46
- * Defines the Google Maps API key. Must have access to the Static Maps API as well.
47
- */
48
- apiKey?: string
49
- /**
50
- * A latitude / longitude of where to focus the map.
51
- */
52
- center?: google.maps.LatLng | google.maps.LatLngLiteral | `${string},${string}`
53
- /**
54
- * Should a marker be displayed on the map where the centre is.
55
- */
56
- centerMarker?: boolean
57
- /**
58
- * Options for the map.
59
- */
60
- mapOptions?: google.maps.MapOptions
61
- /**
62
- * Defines the width of the map.
63
- */
64
- width?: number | string
65
- /**
66
- * Defines the height of the map
67
- */
68
- height?: number | string
69
- /**
70
- * Customize the placeholder image attributes.
71
- *
72
- * @see https://developers.google.com/maps/documentation/maps-static/start.
73
- */
74
- placeholderOptions?: PlaceholderOptions
75
- /**
76
- * Customize the placeholder image attributes.
77
- */
78
- placeholderAttrs?: ImgHTMLAttributes & ReservedProps & Record<string, unknown>
79
- /**
80
- * Customize the root element attributes.
81
- */
82
- rootAttrs?: HTMLAttributes & ReservedProps & Record<string, unknown>
83
- /**
84
- * Extra Markers to add to the map.
85
- */
86
- markers?: (`${string},${string}` | google.maps.marker.AdvancedMarkerElementOptions)[]
87
- }>(), {
88
- // @ts-expect-error untyped
89
- trigger: ['mouseenter', 'mouseover', 'mousedown'],
90
- width: 640,
91
- height: 400,
92
- centerMarker: true,
93
- })
94
-
95
- const emits = defineEmits<{
96
- // our emit
97
- ready: [e: typeof googleMaps]
98
- error: []
99
- }>()
100
-
101
- const apiKey = props.apiKey || scriptRuntimeConfig('googleMaps')?.apiKey
102
-
103
- const mapsApi = ref<typeof google.maps | undefined>()
104
-
105
- if (import.meta.dev && !apiKey)
106
- throw new Error('GoogleMaps requires an API key. Please provide `apiKey` on the <ScriptGoogleMaps> or globally via `runtimeConfig.public.scripts.googleMaps.apiKey`.')
107
-
108
- // TODO allow a null center may need to be resolved via an API function
109
-
110
- const rootEl = ref<HTMLElement>()
111
- const mapEl = ref<HTMLElement>()
112
-
113
- const centerOverride = ref()
114
-
115
- const { load, status, onLoaded } = useScriptGoogleMaps({
116
- apiKey: props.apiKey,
117
- scriptOptions: {
118
- trigger: useScriptTriggerElement({ trigger: props.trigger, el: rootEl }),
119
- },
120
- })
121
-
122
- const options = computed(() => {
123
- return defu({ center: centerOverride.value }, props.mapOptions, {
124
- center: props.center,
125
- zoom: 15,
126
- mapId: 'map',
127
- })
128
- })
129
- const ready = ref(false)
130
-
131
- const map: Ref<google.maps.Map | undefined> = ref()
132
- const mapMarkers: Ref<Map<string, google.maps.marker.AdvancedMarkerElement>> = ref(new Map())
133
-
134
- function isLocationQuery(s: string | any) {
135
- return typeof s === 'string' && (s.split(',').length > 2 || s.includes('+'))
136
- }
137
-
138
- async function createAdvancedMapMarker(_options: google.maps.marker.AdvancedMarkerElementOptions | `${string},${string}`) {
139
- const lib = await importLibrary('marker')
140
- const options = typeof _options === 'string'
141
- ? {
142
- position: {
143
- lat: Number.parseFloat(_options.split(',')[0]),
144
- lng: Number.parseFloat(_options.split(',')[1]),
145
- },
146
- }
147
- : _options
148
- const mapMarkerOptions = defu(toRaw(options), {
149
- map: toRaw(map.value!),
150
- // @ts-expect-error unified API for maps and markers
151
- position: options.location,
152
- })
153
- const marker = new lib.AdvancedMarkerElement(mapMarkerOptions)
154
- // create new marker
155
- mapMarkers.value.set(hash(_options), marker)
156
- return marker
157
- }
158
-
159
- const queryToLatLngCache = new Map<string, google.maps.LatLng>()
160
-
161
- async function resolveQueryToLatLang(query: string) {
162
- if (query && typeof query === 'object')
163
- return Promise.resolve(query)
164
- if (queryToLatLngCache.has(query)) {
165
- return Promise.resolve(queryToLatLngCache.get(query))
166
- }
167
- // only if the query is a string we need to do a lookup
168
- // eslint-disable-next-line no-async-promise-executor
169
- return new Promise<google.maps.LatLng>(async (resolve, reject) => {
170
- if (!mapsApi.value) {
171
- await load()
172
- // await new promise, watch until mapsApi is set
173
- await new Promise<void>((resolve) => {
174
- const _ = watch(mapsApi, () => {
175
- _()
176
- resolve()
177
- })
178
- })
179
- }
180
- const placesService = new mapsApi.value!.places.PlacesService(map.value!)
181
- placesService.findPlaceFromQuery({
182
- query,
183
- fields: ['name', 'geometry'],
184
- }, (results, status) => {
185
- if (status === 'OK' && results?.[0]?.geometry?.location)
186
- return resolve(results[0].geometry.location)
187
- return reject(new Error(`No location found for ${query}`))
188
- })
189
- }).then((res) => {
190
- queryToLatLngCache.set(query, res)
191
- return res
192
- })
193
- }
194
-
195
- const libraries = new Map<string, any>()
196
-
197
- function importLibrary(key: 'marker'): Promise<google.maps.MarkerLibrary>
198
- function importLibrary(key: 'places'): Promise<google.maps.PlacesLibrary>
199
- function importLibrary(key: 'geometry'): Promise<google.maps.GeometryLibrary>
200
- function importLibrary(key: 'drawing'): Promise<google.maps.DrawingLibrary>
201
- function importLibrary(key: 'visualization'): Promise<google.maps.VisualizationLibrary>
202
- function importLibrary(key: string): Promise<any>
203
- function importLibrary<T>(key: string): Promise<T> {
204
- if (libraries.has(key))
205
- return libraries.get(key)
206
- const p = mapsApi.value?.importLibrary(key) || new Promise((resolve) => {
207
- const stop = watch(mapsApi, (api) => {
208
- if (api) {
209
- const p = api.importLibrary(key)
210
- resolve(p)
211
- stop()
212
- }
213
- }, { immediate: true })
214
- })
215
- libraries.set(key, p)
216
- return p as any as Promise<T>
217
- }
218
-
219
- const googleMaps = {
220
- googleMaps: mapsApi,
221
- map,
222
- createAdvancedMapMarker,
223
- resolveQueryToLatLang,
224
- importLibrary,
225
- } as const
226
-
227
- defineExpose(googleMaps)
228
-
229
- onMounted(() => {
230
- watch(ready, (v) => {
231
- if (v) {
232
- emits('ready', googleMaps)
233
- }
234
- })
235
- watch(status, (v) => {
236
- if (v === 'error') {
237
- emits('error')
238
- }
239
- })
240
- watch(options, () => {
241
- map.value?.setOptions(options.value)
242
- })
243
- watch([() => props.markers, map], () => {
244
- if (!map.value) {
245
- return
246
- }
247
- // mapMarkers is a map where we hash the next array entry as the map key
248
- // we need to do a diff to see what we remove or add
249
- const nextMap = new Map((props.markers || []).map(m => [hash(m), m]))
250
- // compare idsToMatch in nextMap, if we're missing an id, we need to remove it
251
- const toRemove = new Set([
252
- ...mapMarkers.value.keys(),
253
- ].filter(k => !nextMap.has(k)))
254
- // compare to existing
255
- const toAdd = new Set([...nextMap.keys()].filter(k => !mapMarkers.value.has(k)))
256
- // do a diff of next and prev
257
- const centerHash = hash({ position: options.value.center })
258
- toRemove.forEach((key) => {
259
- if (key === centerHash) {
260
- return
261
- }
262
- // @ts-expect-error broken type
263
- mapMarkers.value.get(key)?.setMap(null)
264
- mapMarkers.value.delete(key)
265
- })
266
- for (const k of toAdd) {
267
- // @ts-expect-error broken
268
- createAdvancedMapMarker(nextMap.get(k))
269
- }
270
- }, {
271
- immediate: true,
272
- deep: true,
273
- })
274
- watch([() => props.center, ready], async (next, prev) => {
275
- if (!map.value) {
276
- return
277
- }
278
- let center = toRaw(next[0])
279
- if (center) {
280
- if (isLocationQuery(center) && ready.value) {
281
- // need to resolve center from query
282
- // @ts-expect-error broken
283
- center = await resolveQueryToLatLang(center as string)
284
- }
285
- map.value!.setCenter(center as google.maps.LatLng)
286
- if (props.centerMarker) {
287
- if (prev[0]) {
288
- const prevCenterHash = hash({ position: prev[0] })
289
- // @ts-expect-error broken upstream type
290
- mapMarkers.value.get(prevCenterHash)?.setMap(null)
291
- mapMarkers.value.delete(prevCenterHash)
292
- }
293
- // @ts-expect-error untyped
294
- createAdvancedMapMarker({ position: center })
295
- }
296
- }
297
- }, {
298
- immediate: true,
299
- deep: true,
300
- })
301
- onLoaded(async (instance) => {
302
- mapsApi.value = await instance.maps as any as typeof google.maps // some weird type issue here
303
- // may need to transform the center before we can init the map
304
- const center = options.value.center as string
305
- const _options: google.maps.MapOptions = {
306
- ...options.value,
307
- // @ts-expect-error broken
308
- center: !center || isLocationQuery(center) ? undefined : center,
309
- }
310
- map.value = new mapsApi.value!.Map(mapEl.value!, _options)
311
- if (center && isLocationQuery(center)) {
312
- // need to resolve center
313
- centerOverride.value = await resolveQueryToLatLang(center)
314
- map.value?.setCenter(centerOverride.value)
315
- }
316
- ready.value = true
317
- })
318
- })
319
-
320
- if (import.meta.server) {
321
- useHead({
322
- link: [
323
- {
324
- rel: props.aboveTheFold ? 'preconnect' : 'dns-prefetch',
325
- href: 'https://maps.googleapis.com',
326
- },
327
- ],
328
- })
329
- }
330
-
331
- const placeholder = computed(() => {
332
- let center = options.value.center
333
- if (center && typeof center === 'object') {
334
- center = `${center.lat},${center.lng}`
335
- }
336
- // @ts-expect-error lazy type
337
- const placeholderOptions: PlaceholderOptions = defu(props.placeholderOptions, {
338
- // only map option values
339
- zoom: options.value.zoom,
340
- center,
341
- }, {
342
- size: `${props.width}x${props.height}`,
343
- key: apiKey,
344
- scale: 2, // we assume a high DPI to avoid hydration issues
345
- markers: [
346
- ...(props.markers || []),
347
- center,
348
- ]
349
- .filter(Boolean)
350
- .map((m) => {
351
- if (typeof m === 'object' && m.location) {
352
- m = m.location
353
- }
354
- if (typeof m === 'object' && m.lat) {
355
- return `${m.lat},${m.lng}`
356
- }
357
- return m
358
- })
359
- .join('|'),
360
- })
361
- return withQuery('https://maps.googleapis.com/maps/api/staticmap', placeholderOptions as QueryObject)
362
- })
363
-
364
- const placeholderAttrs = computed(() => {
365
- return defu(props.placeholderAttrs, {
366
- src: placeholder.value,
367
- alt: 'Google Maps Static Map',
368
- loading: props.aboveTheFold ? 'eager' : 'lazy',
369
- style: {
370
- cursor: 'pointer',
371
- width: '100%',
372
- objectFit: 'cover',
373
- height: '100%',
374
- },
375
- } satisfies ImgHTMLAttributes)
376
- })
377
-
378
- const rootAttrs = computed(() => {
379
- return defu(props.rootAttrs, {
380
- 'aria-busy': status.value === 'loading',
381
- 'aria-label': status.value === 'awaitingLoad'
382
- ? 'Google Maps Static Map'
383
- : status.value === 'loading'
384
- ? 'Google Maps Map Embed Loading'
385
- : 'Google Maps Embed',
386
- 'aria-live': 'polite',
387
- 'role': 'application',
388
- 'style': {
389
- cursor: 'pointer',
390
- position: 'relative',
391
- maxWidth: '100%',
392
- width: `${props.width}px`,
393
- height: `'auto'`,
394
- aspectRatio: `${props.width}/${props.height}`,
395
- },
396
- }) as HTMLAttributes
397
- })
398
-
399
- const ScriptLoadingIndicator = resolveComponent('ScriptLoadingIndicator')
400
-
401
- onBeforeUnmount(() => {
402
- mapMarkers.value.forEach(marker => marker.remove())
403
- mapMarkers.value.clear()
404
- map.value?.unbindAll()
405
- map.value = undefined
406
- mapEl.value?.firstChild?.remove()
407
- })
408
- </script>
409
-
410
- <template>
411
- <div ref="rootEl" v-bind="rootAttrs">
412
- <div v-show="ready" ref="mapEl" :style="{ width: '100%', height: '100%', maxWidth: '100%' }" />
413
- <slot v-if="!ready" :placeholder="placeholder" name="placeholder">
414
- <img v-bind="placeholderAttrs">
415
- </slot>
416
- <slot v-if="status !== 'awaitingLoad' && !ready" name="loading">
417
- <ScriptLoadingIndicator color="black" />
418
- </slot>
419
- <slot v-if="status === 'awaitingLoad'" name="awaitingLoad" />
420
- <slot v-else-if="status === 'error'" name="error" />
421
- <slot />
422
- </div>
423
- </template>
1
+ <script lang="ts" setup>
2
+ /// <reference types="google.maps" />
3
+ import { computed, onBeforeUnmount, onMounted, ref, watch, toRaw } from 'vue'
4
+ import type { HTMLAttributes, ImgHTMLAttributes, Ref, ReservedProps } from 'vue'
5
+ import { withQuery } from 'ufo'
6
+ import type { QueryObject } from 'ufo'
7
+ import { defu } from 'defu'
8
+ import { hash } from 'ohash'
9
+ import type { ElementScriptTrigger } from '../types'
10
+ import { scriptRuntimeConfig } from '../utils'
11
+ import { useScriptTriggerElement } from '../composables/useScriptTriggerElement'
12
+ import { useScriptGoogleMaps } from '../registry/google-maps'
13
+ import { resolveComponent, useHead } from '#imports'
14
+
15
+ interface PlaceholderOptions {
16
+ width?: string | number
17
+ height?: string | number
18
+ center?: string
19
+ zoom?: number
20
+ size?: string
21
+ scale?: number
22
+ format?: 'png' | 'jpg' | 'gif' | 'png8' | 'png32' | 'jpg-baseline'
23
+ maptype?: 'roadmap' | 'satellite' | 'terrain' | 'hybrid'
24
+ language?: string
25
+ region?: string
26
+ markers?: string
27
+ path?: string
28
+ visible?: string
29
+ style?: string
30
+ map_id?: string
31
+ key?: string
32
+ signature?: string
33
+ }
34
+
35
+ const props = withDefaults(defineProps<{
36
+ /**
37
+ * Defines the trigger event to load the script.
38
+ */
39
+ trigger?: ElementScriptTrigger
40
+ /**
41
+ * Is Google Maps being rendered above the fold?
42
+ * This will load the placeholder image with higher priority.
43
+ */
44
+ aboveTheFold?: boolean
45
+ /**
46
+ * Defines the Google Maps API key. Must have access to the Static Maps API as well.
47
+ */
48
+ apiKey?: string
49
+ /**
50
+ * A latitude / longitude of where to focus the map.
51
+ */
52
+ center?: google.maps.LatLng | google.maps.LatLngLiteral | `${string},${string}`
53
+ /**
54
+ * Should a marker be displayed on the map where the centre is.
55
+ */
56
+ centerMarker?: boolean
57
+ /**
58
+ * Options for the map.
59
+ */
60
+ mapOptions?: google.maps.MapOptions
61
+ /**
62
+ * Defines the width of the map.
63
+ */
64
+ width?: number | string
65
+ /**
66
+ * Defines the height of the map
67
+ */
68
+ height?: number | string
69
+ /**
70
+ * Customize the placeholder image attributes.
71
+ *
72
+ * @see https://developers.google.com/maps/documentation/maps-static/start.
73
+ */
74
+ placeholderOptions?: PlaceholderOptions
75
+ /**
76
+ * Customize the placeholder image attributes.
77
+ */
78
+ placeholderAttrs?: ImgHTMLAttributes & ReservedProps & Record<string, unknown>
79
+ /**
80
+ * Customize the root element attributes.
81
+ */
82
+ rootAttrs?: HTMLAttributes & ReservedProps & Record<string, unknown>
83
+ /**
84
+ * Extra Markers to add to the map.
85
+ */
86
+ markers?: (`${string},${string}` | google.maps.marker.AdvancedMarkerElementOptions)[]
87
+ }>(), {
88
+ // @ts-expect-error untyped
89
+ trigger: ['mouseenter', 'mouseover', 'mousedown'],
90
+ width: 640,
91
+ height: 400,
92
+ })
93
+
94
+ const emits = defineEmits<{
95
+ // our emit
96
+ ready: [e: typeof googleMaps]
97
+ error: []
98
+ }>()
99
+
100
+ const apiKey = props.apiKey || scriptRuntimeConfig('googleMaps')?.apiKey
101
+
102
+ const mapsApi = ref<typeof google.maps | undefined>()
103
+
104
+ if (import.meta.dev && !apiKey)
105
+ throw new Error('GoogleMaps requires an API key. Please provide `apiKey` on the <ScriptGoogleMaps> or globally via `runtimeConfig.public.scripts.googleMaps.apiKey`.')
106
+
107
+ // TODO allow a null center may need to be resolved via an API function
108
+
109
+ const rootEl = ref<HTMLElement>()
110
+ const mapEl = ref<HTMLElement>()
111
+
112
+ const centerOverride = ref()
113
+
114
+ const trigger = useScriptTriggerElement({ trigger: props.trigger, el: rootEl })
115
+ const { load, status, onLoaded } = useScriptGoogleMaps({
116
+ apiKey: props.apiKey,
117
+ scriptOptions: {
118
+ trigger,
119
+ },
120
+ })
121
+
122
+ const options = computed(() => {
123
+ return defu({ center: centerOverride.value }, props.mapOptions, {
124
+ center: props.center,
125
+ zoom: 15,
126
+ mapId: props.mapOptions?.styles ? undefined : 'map',
127
+ })
128
+ })
129
+ const ready = ref(false)
130
+
131
+ const map: Ref<google.maps.Map | undefined> = ref()
132
+ const mapMarkers: Ref<Map<string, Promise<google.maps.marker.AdvancedMarkerElement>>> = ref(new Map())
133
+
134
+ function isLocationQuery(s: string | any) {
135
+ return typeof s === 'string' && (s.split(',').length > 2 || s.includes('+'))
136
+ }
137
+
138
+ async function createAdvancedMapMarker(_options: google.maps.marker.AdvancedMarkerElementOptions | `${string},${string}`) {
139
+ const key = hash(_options)
140
+ if (mapMarkers.value.has(key))
141
+ return mapMarkers.value.get(key)
142
+ // eslint-disable-next-line no-async-promise-executor
143
+ const p = new Promise<google.maps.marker.AdvancedMarkerElement>(async (resolve) => {
144
+ const lib = await importLibrary('marker')
145
+ const options = typeof _options === 'string'
146
+ ? {
147
+ position: {
148
+ lat: Number.parseFloat(_options.split(',')[0] || '0'),
149
+ lng: Number.parseFloat(_options.split(',')[1] || '0'),
150
+ },
151
+ }
152
+ : _options
153
+ const mapMarkerOptions = defu(toRaw(options), {
154
+ map: toRaw(map.value!),
155
+ // @ts-expect-error unified API for maps and markers
156
+ position: options.location,
157
+ })
158
+ resolve(new lib.AdvancedMarkerElement(mapMarkerOptions))
159
+ })
160
+ mapMarkers.value.set(key, p)
161
+ return p
162
+ }
163
+
164
+ const queryToLatLngCache = new Map<string, google.maps.LatLng>()
165
+
166
+ async function resolveQueryToLatLang(query: string) {
167
+ if (query && typeof query === 'object')
168
+ return Promise.resolve(query)
169
+ if (queryToLatLngCache.has(query)) {
170
+ return Promise.resolve(queryToLatLngCache.get(query))
171
+ }
172
+ // only if the query is a string we need to do a lookup
173
+ // eslint-disable-next-line no-async-promise-executor
174
+ return new Promise<google.maps.LatLng>(async (resolve, reject) => {
175
+ if (!mapsApi.value) {
176
+ await load()
177
+ // await new promise, watch until mapsApi is set
178
+ await new Promise<void>((resolve) => {
179
+ const _ = watch(mapsApi, () => {
180
+ _()
181
+ resolve()
182
+ })
183
+ })
184
+ }
185
+ const placesService = new mapsApi.value!.places.PlacesService(map.value!)
186
+ placesService.findPlaceFromQuery({
187
+ query,
188
+ fields: ['name', 'geometry'],
189
+ }, (results, status) => {
190
+ if (status === 'OK' && results?.[0]?.geometry?.location)
191
+ return resolve(results[0].geometry.location)
192
+ return reject(new Error(`No location found for ${query}`))
193
+ })
194
+ }).then((res) => {
195
+ queryToLatLngCache.set(query, res)
196
+ return res
197
+ })
198
+ }
199
+
200
+ const libraries = new Map<string, any>()
201
+
202
+ function importLibrary(key: 'marker'): Promise<google.maps.MarkerLibrary>
203
+ function importLibrary(key: 'places'): Promise<google.maps.PlacesLibrary>
204
+ function importLibrary(key: 'geometry'): Promise<google.maps.GeometryLibrary>
205
+ function importLibrary(key: 'drawing'): Promise<google.maps.DrawingLibrary>
206
+ function importLibrary(key: 'visualization'): Promise<google.maps.VisualizationLibrary>
207
+ function importLibrary(key: string): Promise<any>
208
+ function importLibrary<T>(key: string): Promise<T> {
209
+ if (libraries.has(key))
210
+ return libraries.get(key)
211
+ const p = mapsApi.value?.importLibrary(key) || new Promise((resolve) => {
212
+ const stop = watch(mapsApi, (api) => {
213
+ if (api) {
214
+ const p = api.importLibrary(key)
215
+ resolve(p)
216
+ stop()
217
+ }
218
+ }, { immediate: true })
219
+ })
220
+ libraries.set(key, p)
221
+ return p as any as Promise<T>
222
+ }
223
+
224
+ const googleMaps = {
225
+ googleMaps: mapsApi,
226
+ map,
227
+ createAdvancedMapMarker,
228
+ resolveQueryToLatLang,
229
+ importLibrary,
230
+ } as const
231
+
232
+ defineExpose(googleMaps)
233
+
234
+ onMounted(() => {
235
+ watch(ready, (v) => {
236
+ if (v) {
237
+ emits('ready', googleMaps)
238
+ }
239
+ })
240
+ watch(status, (v) => {
241
+ if (v === 'error') {
242
+ emits('error')
243
+ }
244
+ })
245
+ watch(options, () => {
246
+ map.value?.setOptions(options.value)
247
+ })
248
+ watch([() => props.markers, map], async () => {
249
+ if (!map.value) {
250
+ return
251
+ }
252
+ // mapMarkers is a map where we hash the next array entry as the map key
253
+ // we need to do a diff to see what we remove or add
254
+ const nextMap = new Map((props.markers || []).map(m => [hash(m), m]))
255
+ // compare idsToMatch in nextMap, if we're missing an id, we need to remove it
256
+ const toRemove = new Set([
257
+ ...mapMarkers.value.keys(),
258
+ ].filter(k => !nextMap.has(k)))
259
+ // compare to existing
260
+ const toAdd = new Set([...nextMap.keys()].filter(k => !mapMarkers.value.has(k)))
261
+ // do a diff of next and prev
262
+ const centerHash = hash({ position: options.value.center })
263
+ for (const key of toRemove) {
264
+ if (key === centerHash) {
265
+ continue
266
+ }
267
+ const marker = await mapMarkers.value.get(key)
268
+ if (marker) {
269
+ // @ts-expect-error broken type
270
+ marker.setMap(null)
271
+ // make sure it gets removed from map
272
+ mapMarkers.value.delete(key)
273
+ }
274
+ }
275
+ for (const k of toAdd) {
276
+ // @ts-expect-error broken
277
+ createAdvancedMapMarker(nextMap.get(k))
278
+ }
279
+ }, {
280
+ immediate: true,
281
+ deep: true,
282
+ })
283
+ watch([() => options.value.center, ready, map], async (next, prev) => {
284
+ if (!map.value) {
285
+ return
286
+ }
287
+ let center = toRaw(next[0])
288
+ if (center) {
289
+ if (isLocationQuery(center) && ready.value) {
290
+ // need to resolve center from query
291
+ center = await resolveQueryToLatLang(center as string)
292
+ }
293
+ map.value!.setCenter(center as google.maps.LatLng)
294
+ if (typeof props.centerMarker === 'undefined' || props.centerMarker) {
295
+ if (options.value.mapId) {
296
+ // not allowed to use advanced markers with styles
297
+ return
298
+ }
299
+ if (prev[0]) {
300
+ const prevCenterHash = hash({ position: prev[0] })
301
+ // @ts-expect-error broken upstream type
302
+ mapMarkers.value.get(prevCenterHash)?.setMap(null)
303
+ mapMarkers.value.delete(prevCenterHash)
304
+ }
305
+ createAdvancedMapMarker({ position: center })
306
+ }
307
+ }
308
+ }, {
309
+ immediate: true,
310
+ })
311
+ onLoaded(async (instance) => {
312
+ mapsApi.value = await instance.maps as any as typeof google.maps // some weird type issue here
313
+ // may need to transform the center before we can init the map
314
+ const center = options.value.center as string
315
+ const _options: google.maps.MapOptions = {
316
+ ...options.value,
317
+ // @ts-expect-error broken
318
+ center: !center || isLocationQuery(center) ? undefined : center,
319
+ }
320
+ map.value = new mapsApi.value!.Map(mapEl.value!, _options)
321
+ if (center && isLocationQuery(center)) {
322
+ // need to resolve center
323
+ centerOverride.value = await resolveQueryToLatLang(center)
324
+ map.value?.setCenter(centerOverride.value)
325
+ }
326
+ ready.value = true
327
+ })
328
+ })
329
+
330
+ if (import.meta.server) {
331
+ useHead({
332
+ link: [
333
+ {
334
+ rel: props.aboveTheFold ? 'preconnect' : 'dns-prefetch',
335
+ href: 'https://maps.googleapis.com',
336
+ },
337
+ ],
338
+ })
339
+ }
340
+
341
+ function transformMapStyles(styles: google.maps.MapTypeStyle[]) {
342
+ return styles.map((style) => {
343
+ const feature = style.featureType ? `feature:${style.featureType}` : ''
344
+ const element = style.elementType ? `element:${style.elementType}` : ''
345
+ const rules = (style.stylers || []).map((styler) => {
346
+ return Object.entries(styler).map(([key, value]) => {
347
+ if (key === 'color' && typeof value === 'string') {
348
+ value = value.replace('#', '0x')
349
+ }
350
+ return `${key}:${value}`
351
+ }).join('|')
352
+ }).filter(Boolean).join('|')
353
+ return [feature, element, rules].filter(Boolean).join('|')
354
+ }).filter(Boolean)
355
+ }
356
+
357
+ const placeholder = computed(() => {
358
+ let center = options.value.center
359
+ if (center && typeof center === 'object') {
360
+ center = `${center.lat},${center.lng}`
361
+ }
362
+ // @ts-expect-error lazy type
363
+ const placeholderOptions: PlaceholderOptions = defu(props.placeholderOptions, {
364
+ // only map option values
365
+ zoom: options.value.zoom,
366
+ center,
367
+ }, {
368
+ size: `${props.width}x${props.height}`,
369
+ key: apiKey,
370
+ scale: 2, // we assume a high DPI to avoid hydration issues
371
+ style: props.mapOptions?.styles ? transformMapStyles(props.mapOptions.styles) : undefined,
372
+ markers: [
373
+ ...(props.markers || []),
374
+ center,
375
+ ]
376
+ .filter(Boolean)
377
+ .map((m) => {
378
+ if (typeof m === 'object' && m.location) {
379
+ m = m.location
380
+ }
381
+ if (typeof m === 'object' && m.lat) {
382
+ return `${m.lat},${m.lng}`
383
+ }
384
+ return m
385
+ })
386
+ .join('|'),
387
+ })
388
+ return withQuery('https://maps.googleapis.com/maps/api/staticmap', placeholderOptions as QueryObject)
389
+ })
390
+
391
+ const placeholderAttrs = computed(() => {
392
+ return defu(props.placeholderAttrs, {
393
+ src: placeholder.value,
394
+ alt: 'Google Maps Static Map',
395
+ loading: props.aboveTheFold ? 'eager' : 'lazy',
396
+ style: {
397
+ cursor: 'pointer',
398
+ width: '100%',
399
+ objectFit: 'cover',
400
+ height: '100%',
401
+ },
402
+ } satisfies ImgHTMLAttributes)
403
+ })
404
+
405
+ const rootAttrs = computed(() => {
406
+ return defu(props.rootAttrs, {
407
+ 'aria-busy': status.value === 'loading',
408
+ 'aria-label': status.value === 'awaitingLoad'
409
+ ? 'Google Maps Static Map'
410
+ : status.value === 'loading'
411
+ ? 'Google Maps Map Embed Loading'
412
+ : 'Google Maps Embed',
413
+ 'aria-live': 'polite',
414
+ 'role': 'application',
415
+ 'style': {
416
+ cursor: 'pointer',
417
+ position: 'relative',
418
+ maxWidth: '100%',
419
+ width: `${props.width}px`,
420
+ height: `'auto'`,
421
+ aspectRatio: `${props.width}/${props.height}`,
422
+ },
423
+ ...(trigger instanceof Promise ? trigger.ssrAttrs || {} : {}),
424
+ }) as HTMLAttributes
425
+ })
426
+
427
+ const ScriptLoadingIndicator = resolveComponent('ScriptLoadingIndicator')
428
+
429
+ onBeforeUnmount(async () => {
430
+ await Promise.all([...mapMarkers.value.entries()].map(async (_marker) => {
431
+ const marker = await _marker
432
+ if (marker) {
433
+ // @ts-expect-error broken type
434
+ marker.setMap(null)
435
+ }
436
+ }))
437
+ mapMarkers.value.clear()
438
+ map.value?.unbindAll()
439
+ map.value = undefined
440
+ mapEl.value?.firstChild?.remove()
441
+ })
442
+ </script>
443
+
444
+ <template>
445
+ <div ref="rootEl" v-bind="rootAttrs">
446
+ <div v-show="ready" ref="mapEl" :style="{ width: '100%', height: '100%', maxWidth: '100%' }" />
447
+ <slot v-if="!ready" :placeholder="placeholder" name="placeholder">
448
+ <img v-bind="placeholderAttrs">
449
+ </slot>
450
+ <slot v-if="status !== 'awaitingLoad' && !ready" name="loading">
451
+ <ScriptLoadingIndicator color="black" />
452
+ </slot>
453
+ <slot v-if="status === 'awaitingLoad'" name="awaitingLoad" />
454
+ <slot v-else-if="status === 'error'" name="error" />
455
+ <slot />
456
+ </div>
457
+ </template>