@nuxt/scripts 0.6.6 → 0.7.1

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 (54) hide show
  1. package/dist/client/200.html +8 -8
  2. package/dist/client/404.html +8 -8
  3. package/dist/client/_nuxt/{aQeyjCqJ.js → C1IUq70_.js} +1 -1
  4. package/dist/client/_nuxt/{BKIFFtfV.js → CnUvXFHd.js} +1 -1
  5. package/dist/client/_nuxt/DOi3Eb0n.js +31 -0
  6. package/dist/client/_nuxt/builds/latest.json +1 -1
  7. package/dist/client/_nuxt/builds/meta/e4092e18-9a58-41ab-947e-35e1f2082b75.json +1 -0
  8. package/dist/client/_nuxt/error-404.CHrXIISA.css +1 -0
  9. package/dist/client/_nuxt/error-500.CrsjMPPf.css +1 -0
  10. package/dist/client/_nuxt/{BKJqCZ0j.js → iQqcKxZm.js} +1 -1
  11. package/dist/client/index.html +8 -8
  12. package/dist/module.json +2 -2
  13. package/dist/module.mjs +10 -4
  14. package/dist/runtime/components/ScriptCrisp.vue +10 -7
  15. package/dist/runtime/components/ScriptGoogleAdsense.vue +7 -7
  16. package/dist/runtime/components/ScriptGoogleMaps.vue +236 -68
  17. package/dist/runtime/components/ScriptIntercom.vue +10 -7
  18. package/dist/runtime/components/ScriptLemonSqueezy.vue +1 -1
  19. package/dist/runtime/components/ScriptStripePricingTable.vue +6 -6
  20. package/dist/runtime/components/ScriptVimeoPlayer.vue +62 -40
  21. package/dist/runtime/components/ScriptYouTubePlayer.vue +9 -9
  22. package/dist/runtime/composables/useScript.d.ts +6 -1
  23. package/dist/runtime/composables/useScript.js +8 -8
  24. package/dist/runtime/composables/useScriptEventPage.d.ts +0 -3
  25. package/dist/runtime/registry/clarity.d.ts +1 -12
  26. package/dist/runtime/registry/cloudflare-web-analytics.d.ts +1 -3
  27. package/dist/runtime/registry/crisp.d.ts +1 -3
  28. package/dist/runtime/registry/fathom-analytics.d.ts +1 -3
  29. package/dist/runtime/registry/google-adsense.d.ts +1 -3
  30. package/dist/runtime/registry/google-analytics.d.ts +2 -10
  31. package/dist/runtime/registry/google-maps.d.ts +2 -4
  32. package/dist/runtime/registry/google-maps.js +1 -3
  33. package/dist/runtime/registry/google-tag-manager.d.ts +2 -10
  34. package/dist/runtime/registry/hotjar.d.ts +1 -3
  35. package/dist/runtime/registry/intercom.d.ts +1 -3
  36. package/dist/runtime/registry/lemon-squeezy.d.ts +1 -3
  37. package/dist/runtime/registry/matomo-analytics.d.ts +1 -3
  38. package/dist/runtime/registry/meta-pixel.d.ts +1 -3
  39. package/dist/runtime/registry/npm.d.ts +1 -3
  40. package/dist/runtime/registry/plausible-analytics.d.ts +1 -3
  41. package/dist/runtime/registry/segment.d.ts +1 -3
  42. package/dist/runtime/registry/stripe.d.ts +1 -3
  43. package/dist/runtime/registry/vimeo-player.d.ts +1 -3
  44. package/dist/runtime/registry/vimeo-player.js +1 -1
  45. package/dist/runtime/registry/x-pixel.d.ts +1 -3
  46. package/dist/runtime/registry/youtube-player.d.ts +1 -3
  47. package/dist/runtime/registry/youtube-player.js +1 -1
  48. package/dist/runtime/types.d.ts +3 -3
  49. package/dist/runtime/utils.d.ts +2 -4
  50. package/package.json +29 -23
  51. package/dist/client/_nuxt/DcF05ETD.js +0 -31
  52. package/dist/client/_nuxt/builds/meta/3dcaf2bf-82f1-460f-80a6-1412196a7a77.json +0 -1
  53. package/dist/client/_nuxt/error-404.B7Lh29wY.css +0 -1
  54. package/dist/client/_nuxt/error-500.B_VIyt60.css +0 -1
@@ -1 +1 @@
1
- {"id":"3dcaf2bf-82f1-460f-80a6-1412196a7a77","timestamp":1722985798275}
1
+ {"id":"e4092e18-9a58-41ab-947e-35e1f2082b75","timestamp":1724318010522}
@@ -0,0 +1 @@
1
+ {"id":"e4092e18-9a58-41ab-947e-35e1f2082b75","timestamp":1724318010522,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
@@ -0,0 +1 @@
1
+ .spotlight[data-v-379fced0]{background:linear-gradient(45deg,#00dc82,#36e4da 50%,#0047e1);bottom:-30vh;filter:blur(20vh);height:40vh}.gradient-border[data-v-379fced0]{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-radius:.5rem;position:relative}@media (prefers-color-scheme:light){.gradient-border[data-v-379fced0]{background-color:#ffffff4d}.gradient-border[data-v-379fced0]:before{background:linear-gradient(90deg,#e2e2e2,#e2e2e2 25%,#00dc82,#36e4da 75%,#0047e1)}}@media (prefers-color-scheme:dark){.gradient-border[data-v-379fced0]{background-color:#1414144d}.gradient-border[data-v-379fced0]:before{background:linear-gradient(90deg,#303030,#303030 25%,#00dc82,#36e4da 75%,#0047e1)}}.gradient-border[data-v-379fced0]:before{background-size:400% auto;border-radius:.5rem;bottom:0;content:"";left:0;-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:.5;padding:2px;position:absolute;right:0;top:0;transition:background-position .3s ease-in-out,opacity .2s ease-in-out;width:100%}.gradient-border[data-v-379fced0]:hover:before{background-position:-50% 0;opacity:1}.fixed[data-v-379fced0]{position:fixed}.left-0[data-v-379fced0]{left:0}.right-0[data-v-379fced0]{right:0}.z-10[data-v-379fced0]{z-index:10}.z-20[data-v-379fced0]{z-index:20}.grid[data-v-379fced0]{display:grid}.mb-16[data-v-379fced0]{margin-bottom:4rem}.mb-8[data-v-379fced0]{margin-bottom:2rem}.max-w-520px[data-v-379fced0]{max-width:520px}.min-h-screen[data-v-379fced0]{min-height:100vh}.w-full[data-v-379fced0]{width:100%}.flex[data-v-379fced0]{display:flex}.cursor-pointer[data-v-379fced0]{cursor:pointer}.place-content-center[data-v-379fced0]{place-content:center}.items-center[data-v-379fced0]{align-items:center}.justify-center[data-v-379fced0]{justify-content:center}.overflow-hidden[data-v-379fced0]{overflow:hidden}.bg-white[data-v-379fced0]{--un-bg-opacity:1;background-color:rgb(255 255 255/var(--un-bg-opacity))}.px-4[data-v-379fced0]{padding-left:1rem;padding-right:1rem}.px-8[data-v-379fced0]{padding-left:2rem;padding-right:2rem}.py-2[data-v-379fced0]{padding-bottom:.5rem;padding-top:.5rem}.text-center[data-v-379fced0]{text-align:center}.text-8xl[data-v-379fced0]{font-size:6rem;line-height:1}.text-xl[data-v-379fced0]{font-size:1.25rem;line-height:1.75rem}.text-black[data-v-379fced0]{--un-text-opacity:1;color:rgb(0 0 0/var(--un-text-opacity))}.font-light[data-v-379fced0]{font-weight:300}.font-medium[data-v-379fced0]{font-weight:500}.leading-tight[data-v-379fced0]{line-height:1.25}.font-sans[data-v-379fced0]{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.antialiased[data-v-379fced0]{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (prefers-color-scheme:dark){.dark\:bg-black[data-v-379fced0]{--un-bg-opacity:1;background-color:rgb(0 0 0/var(--un-bg-opacity))}.dark\:text-white[data-v-379fced0]{--un-text-opacity:1;color:rgb(255 255 255/var(--un-text-opacity))}}@media (min-width:640px){.sm\:px-0[data-v-379fced0]{padding-left:0;padding-right:0}.sm\:px-6[data-v-379fced0]{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-3[data-v-379fced0]{padding-bottom:.75rem;padding-top:.75rem}.sm\:text-4xl[data-v-379fced0]{font-size:2.25rem;line-height:2.5rem}.sm\:text-xl[data-v-379fced0]{font-size:1.25rem;line-height:1.75rem}}
@@ -0,0 +1 @@
1
+ .spotlight[data-v-8b7b3844]{background:linear-gradient(45deg,#00dc82,#36e4da 50%,#0047e1);filter:blur(20vh)}.fixed[data-v-8b7b3844]{position:fixed}.-bottom-1\/2[data-v-8b7b3844]{bottom:-50%}.left-0[data-v-8b7b3844]{left:0}.right-0[data-v-8b7b3844]{right:0}.grid[data-v-8b7b3844]{display:grid}.mb-16[data-v-8b7b3844]{margin-bottom:4rem}.mb-8[data-v-8b7b3844]{margin-bottom:2rem}.h-1\/2[data-v-8b7b3844]{height:50%}.max-w-520px[data-v-8b7b3844]{max-width:520px}.min-h-screen[data-v-8b7b3844]{min-height:100vh}.place-content-center[data-v-8b7b3844]{place-content:center}.overflow-hidden[data-v-8b7b3844]{overflow:hidden}.bg-white[data-v-8b7b3844]{--un-bg-opacity:1;background-color:rgb(255 255 255/var(--un-bg-opacity))}.px-8[data-v-8b7b3844]{padding-left:2rem;padding-right:2rem}.text-center[data-v-8b7b3844]{text-align:center}.text-8xl[data-v-8b7b3844]{font-size:6rem;line-height:1}.text-xl[data-v-8b7b3844]{font-size:1.25rem;line-height:1.75rem}.text-black[data-v-8b7b3844]{--un-text-opacity:1;color:rgb(0 0 0/var(--un-text-opacity))}.font-light[data-v-8b7b3844]{font-weight:300}.font-medium[data-v-8b7b3844]{font-weight:500}.leading-tight[data-v-8b7b3844]{line-height:1.25}.font-sans[data-v-8b7b3844]{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.antialiased[data-v-8b7b3844]{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (prefers-color-scheme:dark){.dark\:bg-black[data-v-8b7b3844]{--un-bg-opacity:1;background-color:rgb(0 0 0/var(--un-bg-opacity))}.dark\:text-white[data-v-8b7b3844]{--un-text-opacity:1;color:rgb(255 255 255/var(--un-text-opacity))}}@media (min-width:640px){.sm\:px-0[data-v-8b7b3844]{padding-left:0;padding-right:0}.sm\:text-4xl[data-v-8b7b3844]{font-size:2.25rem;line-height:2.5rem}}
@@ -1 +1 @@
1
- import{i as o,r as u,g as f,h as d,j as v,k as l,l as i,m as h,n as m}from"./DcF05ETD.js";function U(t,a={}){const e=a.head||o();if(e)return e.ssr?e.push(t,a):p(e,t,a)}function p(t,a,e={}){const s=u(!1),n=u({});f(()=>{n.value=s.value?{}:h(a)});const r=t.push(n.value,e);return d(n,c=>{r.patch(c)}),m()&&(v(()=>{r.dispose()}),l(()=>{s.value=!0}),i(()=>{s.value=!1})),r}export{U as u};
1
+ import{i as o,r as u,g as f,h as d,j as v,k as l,l as i,m as h,n as m}from"./DOi3Eb0n.js";function U(t,a={}){const e=a.head||o();if(e)return e.ssr?e.push(t,a):p(e,t,a)}function p(t,a,e={}){const s=u(!1),n=u({});f(()=>{n.value=s.value?{}:h(a)});const r=t.push(n.value,e);return d(n,c=>{r.patch(c)}),m()&&(v(()=>{r.dispose()}),l(()=>{s.value=!0}),i(()=>{s.value=!1})),r}export{U as u};
@@ -1,7 +1,7 @@
1
1
  <!DOCTYPE html><html data-capo=""><head><meta charset="utf-8">
2
2
  <meta name="viewport" content="width=device-width, initial-scale=1">
3
3
  <link rel="stylesheet" href="/__nuxt-scripts/_nuxt/entry.FVeyw1Qn.css">
4
- <link rel="modulepreload" as="script" crossorigin href="/__nuxt-scripts/_nuxt/DcF05ETD.js">
4
+ <link rel="modulepreload" as="script" crossorigin href="/__nuxt-scripts/_nuxt/DOi3Eb0n.js">
5
5
  <link rel="prefetch" as="script" crossorigin href="/__nuxt-scripts/_nuxt/DXFkqnOI.js">
6
6
  <link rel="prefetch" as="script" crossorigin href="/__nuxt-scripts/_nuxt/BigF1UXR.js">
7
7
  <link rel="prefetch" as="script" crossorigin href="/__nuxt-scripts/_nuxt/CowR2XfX.js">
@@ -257,10 +257,10 @@
257
257
  <link rel="prefetch" as="script" crossorigin href="/__nuxt-scripts/_nuxt/B3g-KkBK.js">
258
258
  <link rel="prefetch" as="script" crossorigin href="/__nuxt-scripts/_nuxt/Bxkoe-BC.js">
259
259
  <link rel="prefetch" as="script" crossorigin href="/__nuxt-scripts/_nuxt/Br6ll-O0.js">
260
- <link rel="prefetch" as="style" href="/__nuxt-scripts/_nuxt/error-404.B7Lh29wY.css">
261
- <link rel="prefetch" as="script" crossorigin href="/__nuxt-scripts/_nuxt/aQeyjCqJ.js">
262
- <link rel="prefetch" as="script" crossorigin href="/__nuxt-scripts/_nuxt/BKJqCZ0j.js">
263
- <link rel="prefetch" as="style" href="/__nuxt-scripts/_nuxt/error-500.B_VIyt60.css">
264
- <link rel="prefetch" as="script" crossorigin href="/__nuxt-scripts/_nuxt/BKIFFtfV.js">
265
- <script type="module" src="/__nuxt-scripts/_nuxt/DcF05ETD.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script type="application/json" id="__NUXT_DATA__" data-ssr="false">[{"serverRendered":1},false]</script>
266
- <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-scripts",buildId:"3dcaf2bf-82f1-460f-80a6-1412196a7a77",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
260
+ <link rel="prefetch" as="style" href="/__nuxt-scripts/_nuxt/error-404.CHrXIISA.css">
261
+ <link rel="prefetch" as="script" crossorigin href="/__nuxt-scripts/_nuxt/C1IUq70_.js">
262
+ <link rel="prefetch" as="script" crossorigin href="/__nuxt-scripts/_nuxt/iQqcKxZm.js">
263
+ <link rel="prefetch" as="style" href="/__nuxt-scripts/_nuxt/error-500.CrsjMPPf.css">
264
+ <link rel="prefetch" as="script" crossorigin href="/__nuxt-scripts/_nuxt/CnUvXFHd.js">
265
+ <script type="module" src="/__nuxt-scripts/_nuxt/DOi3Eb0n.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script type="application/json" id="__NUXT_DATA__" data-ssr="false">[{"serverRendered":1},false]</script>
266
+ <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-scripts",buildId:"e4092e18-9a58-41ab-947e-35e1f2082b75",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
package/dist/module.json CHANGED
@@ -5,9 +5,9 @@
5
5
  "nuxt": ">=3",
6
6
  "bridge": false
7
7
  },
8
- "version": "0.6.6",
8
+ "version": "0.7.1",
9
9
  "builder": {
10
- "@nuxt/module-builder": "0.8.1",
10
+ "@nuxt/module-builder": "0.8.3",
11
11
  "unbuild": "2.0.0"
12
12
  }
13
13
  }
package/dist/module.mjs CHANGED
@@ -152,7 +152,7 @@ function NuxtScriptBundleTransformer(options) {
152
152
  if (scriptSrcNode || src) {
153
153
  src = src || (typeof scriptSrcNode?.value === "string" ? scriptSrcNode?.value : false);
154
154
  if (src) {
155
- let canBundle = options.defaultBundle;
155
+ let canBundle = !!options.defaultBundle;
156
156
  if (node.arguments[1]?.type === "ObjectExpression") {
157
157
  const scriptOptionsArg = node.arguments[1];
158
158
  const bundleProperty = scriptOptionsArg.properties.find(
@@ -175,6 +175,13 @@ function NuxtScriptBundleTransformer(options) {
175
175
  canBundle = true;
176
176
  }
177
177
  }
178
+ const scriptOptions = node.arguments[0].properties?.find(
179
+ (p) => p.key?.name === "scriptOptions"
180
+ );
181
+ const bundleOption = scriptOptions?.value.properties?.find((prop) => {
182
+ return prop.type === "Property" && prop.key?.name === "bundle" && prop.value.type === "Literal";
183
+ });
184
+ canBundle = bundleOption ? bundleOption.value.value : canBundle;
178
185
  if (canBundle) {
179
186
  const newSrc = options.resolveScript(src);
180
187
  if (src === newSrc) {
@@ -502,9 +509,8 @@ const module = defineNuxtModule({
502
509
  const unheadPath = await resolvePath("@unhead/vue").catch(() => void 0);
503
510
  if (unheadPath) {
504
511
  const { version: unheadVersion } = await readPackageJSON(join(unheadPath, "package.json"));
505
- if (!unheadVersion || lt(unheadVersion, "1.9.0")) {
506
- logger.warn("@nuxt/scripts requires @unhead/vue >= 1.9.0, please upgrade to use the module.");
507
- return;
512
+ if (!unheadVersion || lt(unheadVersion, "1.10.0")) {
513
+ logger.error(`Nuxt Scripts requires Unhead >= 1.10.0, you are using v${unheadVersion}. Please run \`nuxi upgrade --clean\` to upgrade...`);
508
514
  }
509
515
  }
510
516
  nuxt.options.alias["#nuxt-scripts-validator"] = resolve(`./runtime/validation/${nuxt.options.dev || nuxt.options._prepare ? "valibot" : "mock"}`);
@@ -40,9 +40,12 @@ const crisp = useScriptCrisp({
40
40
  trigger,
41
41
  },
42
42
  })
43
- if (props.trigger === 'click')
44
- crisp.do('chat:open')
45
- const { $script } = crisp
43
+ const { onLoaded, status } = crisp
44
+ if (props.trigger === 'click') {
45
+ onLoaded((instance) => {
46
+ instance.do('chat:open')
47
+ })
48
+ }
46
49
 
47
50
  defineExpose({
48
51
  crisp,
@@ -50,7 +53,7 @@ defineExpose({
50
53
 
51
54
  let observer: MutationObserver
52
55
  onMounted(() => {
53
- watch($script.status, (status) => {
56
+ watch(status, (status) => {
54
57
  if (status === 'loaded') {
55
58
  observer = new MutationObserver(() => {
56
59
  if (document.getElementById('crisp-chatbox')) {
@@ -77,8 +80,8 @@ onBeforeUnmount(() => {
77
80
  :style="{ display: isReady ? 'none' : 'block' }"
78
81
  >
79
82
  <slot :ready="isReady" />
80
- <slot v-if="$script.status.value === 'awaitingLoad'" name="awaitingLoad" />
81
- <slot v-else-if="$script.status.value === 'loading' || !isReady" name="loading" />
82
- <slot v-else-if="$script.status.value === 'error'" name="error" />
83
+ <slot v-if="status === 'awaitingLoad'" name="awaitingLoad" />
84
+ <slot v-else-if="status === 'loading' || !isReady" name="loading" />
85
+ <slot v-else-if="status === 'error'" name="error" />
83
86
  </div>
84
87
  </template>
@@ -34,17 +34,17 @@ const instance = useScriptGoogleAdsense({
34
34
  },
35
35
  })
36
36
 
37
- const { $script } = instance
37
+ const { status } = instance
38
38
 
39
39
  onMounted(() => {
40
40
  callOnce(() => {
41
41
  (window.adsbygoogle = window.adsbygoogle || []).push({})
42
42
  })
43
- watch(instance.$script.status, () => {
44
- if (instance.$script.status.value === 'loaded') {
43
+ watch(status, (val) => {
44
+ if (val === 'loaded') {
45
45
  emits('ready', instance)
46
46
  }
47
- else if (instance.$script.status.value === 'error') {
47
+ else if (val === 'error') {
48
48
  emits('error')
49
49
  }
50
50
  })
@@ -62,8 +62,8 @@ onMounted(() => {
62
62
  :data-full-width-responsive="dataFullWidthResponsive"
63
63
  v-bind="{ ...$attrs }"
64
64
  >
65
- <slot v-if="$script.status.value === 'awaitingLoad'" name="awaitingLoad" />
66
- <slot v-else-if="$script.status.value === 'loading'" name="loading" />
67
- <slot v-else-if="$script.status.value === 'error'" name="error" />
65
+ <slot v-if="status === 'awaitingLoad'" name="awaitingLoad" />
66
+ <slot v-else-if="status === 'loading'" name="loading" />
67
+ <slot v-else-if="status === 'error'" name="error" />
68
68
  </ins>
69
69
  </template>
@@ -1,10 +1,11 @@
1
1
  <script lang="ts" setup>
2
2
  /// <reference types="google.maps" />
3
- import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
3
+ import { computed, onBeforeUnmount, onMounted, ref, watch, toRaw } from 'vue'
4
4
  import type { HTMLAttributes, ImgHTMLAttributes, Ref, ReservedProps } from 'vue'
5
5
  import { withQuery } from 'ufo'
6
6
  import type { QueryObject } from 'ufo'
7
7
  import { defu } from 'defu'
8
+ import { hash } from 'ohash'
8
9
  import type { ElementScriptTrigger } from '../types'
9
10
  import { scriptRuntimeConfig } from '../utils'
10
11
  import { useScriptTriggerElement } from '../composables/useScriptTriggerElement'
@@ -44,15 +45,19 @@ const props = withDefaults(defineProps<{
44
45
  /**
45
46
  * Defines the Google Maps API key. Must have access to the Static Maps API as well.
46
47
  */
47
- apiKey: string
48
+ apiKey?: string
48
49
  /**
49
- * Defines map marker location.
50
+ * A latitude / longitude of where to focus the map.
50
51
  */
51
- query?: string
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
52
57
  /**
53
58
  * Options for the map.
54
59
  */
55
- options?: google.maps.MapOptions
60
+ mapOptions?: google.maps.MapOptions
56
61
  /**
57
62
  * Defines the width of the map.
58
63
  */
@@ -75,30 +80,39 @@ const props = withDefaults(defineProps<{
75
80
  * Customize the root element attributes.
76
81
  */
77
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)[]
78
87
  }>(), {
79
88
  // @ts-expect-error untyped
80
89
  trigger: ['mouseenter', 'mouseover', 'mousedown'],
81
- width: 600,
90
+ width: 640,
82
91
  height: 400,
92
+ centerMarker: true,
83
93
  })
84
94
 
85
95
  const emits = defineEmits<{
86
96
  // our emit
87
- ready: [e: Ref<google.maps.Map | undefined>]
97
+ ready: [e: typeof googleMaps]
88
98
  error: []
89
99
  }>()
90
100
 
91
101
  const apiKey = props.apiKey || scriptRuntimeConfig('googleMaps')?.apiKey
92
102
 
93
- if (!apiKey)
103
+ const mapsApi = ref<typeof google.maps | undefined>()
104
+
105
+ if (import.meta.dev && !apiKey)
94
106
  throw new Error('GoogleMaps requires an API key. Please provide `apiKey` on the <ScriptGoogleMaps> or globally via `runtimeConfig.public.scripts.googleMaps.apiKey`.')
95
- if (!props.query && !props.options?.center)
96
- throw new Error('GoogleMaps requires either a query or center prop to be set.')
107
+
108
+ // TODO allow a null center may need to be resolved via an API function
97
109
 
98
110
  const rootEl = ref<HTMLElement>()
99
111
  const mapEl = ref<HTMLElement>()
100
112
 
101
- const { $script } = useScriptGoogleMaps({
113
+ const centerOverride = ref()
114
+
115
+ const { load, status, onLoaded } = useScriptGoogleMaps({
102
116
  apiKey: props.apiKey,
103
117
  scriptOptions: {
104
118
  trigger: useScriptTriggerElement({ trigger: props.trigger, el: rootEl }),
@@ -106,16 +120,70 @@ const { $script } = useScriptGoogleMaps({
106
120
  })
107
121
 
108
122
  const options = computed(() => {
109
- return defu(props.options, {
123
+ return defu({ center: centerOverride.value }, props.mapOptions, {
124
+ center: props.center,
110
125
  zoom: 15,
111
126
  mapId: 'map',
112
127
  })
113
128
  })
114
129
  const ready = ref(false)
115
130
 
116
- function queryToLocation(service: google.maps.places.PlacesService, query: string) {
117
- return new Promise<google.maps.LatLng>((resolve, reject) => {
118
- service.findPlaceFromQuery({
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({
119
187
  query,
120
188
  fields: ['name', 'geometry'],
121
189
  }, (results, status) => {
@@ -123,60 +191,135 @@ function queryToLocation(service: google.maps.places.PlacesService, query: strin
123
191
  return resolve(results[0].geometry.location)
124
192
  return reject(new Error(`No location found for ${query}`))
125
193
  })
194
+ }).then((res) => {
195
+ queryToLatLngCache.set(query, res)
196
+ return res
126
197
  })
127
198
  }
128
199
 
129
- const map: Ref<google.maps.Map | undefined> = ref()
130
- const markers: Ref<google.maps.marker.AdvancedMarkerElement[]> = ref([])
131
- defineExpose({
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,
132
226
  map,
133
- markers,
134
- })
227
+ createAdvancedMapMarker,
228
+ resolveQueryToLatLang,
229
+ importLibrary,
230
+ } as const
231
+
232
+ defineExpose(googleMaps)
135
233
 
136
234
  onMounted(() => {
137
235
  watch(ready, (v) => {
138
236
  if (v) {
139
- emits('ready', map)
237
+ emits('ready', googleMaps)
140
238
  }
141
239
  })
142
- watch($script.status, () => {
143
- if ($script.status.value === 'error') {
240
+ watch(status, (v) => {
241
+ if (v === 'error') {
144
242
  emits('error')
145
243
  }
146
244
  })
147
- // create the map
148
- $script.then(async (instance) => {
149
- const maps = await instance.maps as any as typeof google.maps // some weird type issue here
150
- const _map = new maps.Map(mapEl.value!, options.value)
151
- const placesService = new maps.places.PlacesService(_map)
152
-
153
- watch(options, () => _map.setOptions(options.value))
154
- watch(() => props.query, async (query) => {
155
- // always clear old markers
156
- markers.value.forEach(marker => marker.remove())
157
- markers.value = []
158
- if (query) {
159
- const marker = await maps.importLibrary('marker') as google.maps.MarkerLibrary
160
- const location = await queryToLocation(placesService, query).catch((err) => {
161
- console.warn(err)
162
- return {
163
- lat: 0,
164
- lng: 0,
165
- }
166
- })
167
- _map.setCenter(location)
168
- markers.value.push(new marker.AdvancedMarkerElement({
169
- map: _map,
170
- position: location,
171
- }))
172
- ready.value = true
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
173
266
  }
174
- }, {
175
- immediate: !!props.query,
176
- })
177
- if (!props.query)
178
- ready.value = true
179
- map.value = _map
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 (props.centerMarker) {
295
+ if (prev[0]) {
296
+ const prevCenterHash = hash({ position: prev[0] })
297
+ // @ts-expect-error broken upstream type
298
+ mapMarkers.value.get(prevCenterHash)?.setMap(null)
299
+ mapMarkers.value.delete(prevCenterHash)
300
+ }
301
+ createAdvancedMapMarker({ position: center })
302
+ }
303
+ }
304
+ }, {
305
+ immediate: true,
306
+ })
307
+ onLoaded(async (instance) => {
308
+ mapsApi.value = await instance.maps as any as typeof google.maps // some weird type issue here
309
+ // may need to transform the center before we can init the map
310
+ const center = options.value.center as string
311
+ const _options: google.maps.MapOptions = {
312
+ ...options.value,
313
+ // @ts-expect-error broken
314
+ center: !center || isLocationQuery(center) ? undefined : center,
315
+ }
316
+ map.value = new mapsApi.value!.Map(mapEl.value!, _options)
317
+ if (center && isLocationQuery(center)) {
318
+ // need to resolve center
319
+ centerOverride.value = await resolveQueryToLatLang(center)
320
+ map.value?.setCenter(centerOverride.value)
321
+ }
322
+ ready.value = true
180
323
  })
181
324
  })
182
325
 
@@ -192,15 +335,34 @@ if (import.meta.server) {
192
335
  }
193
336
 
194
337
  const placeholder = computed(() => {
338
+ let center = options.value.center
339
+ if (center && typeof center === 'object') {
340
+ center = `${center.lat},${center.lng}`
341
+ }
342
+ // @ts-expect-error lazy type
195
343
  const placeholderOptions: PlaceholderOptions = defu(props.placeholderOptions, {
196
344
  // only map option values
197
345
  zoom: options.value.zoom,
198
- center: options.value.center?.toString() || '',
346
+ center,
199
347
  }, {
200
348
  size: `${props.width}x${props.height}`,
201
349
  key: apiKey,
202
350
  scale: 2, // we assume a high DPI to avoid hydration issues
203
- markers: `color:red|${props.query}`,
351
+ markers: [
352
+ ...(props.markers || []),
353
+ center,
354
+ ]
355
+ .filter(Boolean)
356
+ .map((m) => {
357
+ if (typeof m === 'object' && m.location) {
358
+ m = m.location
359
+ }
360
+ if (typeof m === 'object' && m.lat) {
361
+ return `${m.lat},${m.lng}`
362
+ }
363
+ return m
364
+ })
365
+ .join('|'),
204
366
  })
205
367
  return withQuery('https://maps.googleapis.com/maps/api/staticmap', placeholderOptions as QueryObject)
206
368
  })
@@ -208,7 +370,7 @@ const placeholder = computed(() => {
208
370
  const placeholderAttrs = computed(() => {
209
371
  return defu(props.placeholderAttrs, {
210
372
  src: placeholder.value,
211
- alt: props.query || '',
373
+ alt: 'Google Maps Static Map',
212
374
  loading: props.aboveTheFold ? 'eager' : 'lazy',
213
375
  style: {
214
376
  cursor: 'pointer',
@@ -221,10 +383,10 @@ const placeholderAttrs = computed(() => {
221
383
 
222
384
  const rootAttrs = computed(() => {
223
385
  return defu(props.rootAttrs, {
224
- 'aria-busy': $script.status.value === 'loading',
225
- 'aria-label': $script.status.value === 'awaitingLoad'
386
+ 'aria-busy': status.value === 'loading',
387
+ 'aria-label': status.value === 'awaitingLoad'
226
388
  ? 'Google Maps Static Map'
227
- : $script.status.value === 'loading'
389
+ : status.value === 'loading'
228
390
  ? 'Google Maps Map Embed Loading'
229
391
  : 'Google Maps Embed',
230
392
  'aria-live': 'polite',
@@ -242,9 +404,15 @@ const rootAttrs = computed(() => {
242
404
 
243
405
  const ScriptLoadingIndicator = resolveComponent('ScriptLoadingIndicator')
244
406
 
245
- onBeforeUnmount(() => {
246
- markers.value.forEach(marker => marker.remove())
247
- markers.value = []
407
+ onBeforeUnmount(async () => {
408
+ await Promise.all([...mapMarkers.value.entries()].map(async (_marker) => {
409
+ const marker = await _marker
410
+ if (marker) {
411
+ // @ts-expect-error broken type
412
+ marker.setMap(null)
413
+ }
414
+ }))
415
+ mapMarkers.value.clear()
248
416
  map.value?.unbindAll()
249
417
  map.value = undefined
250
418
  mapEl.value?.firstChild?.remove()
@@ -253,15 +421,15 @@ onBeforeUnmount(() => {
253
421
 
254
422
  <template>
255
423
  <div ref="rootEl" v-bind="rootAttrs">
256
- <div v-show="ready" ref="mapEl" class="script-google-maps__map" :style="{ width: '100%', height: '100%', maxWidth: '100%' }" />
424
+ <div v-show="ready" ref="mapEl" :style="{ width: '100%', height: '100%', maxWidth: '100%' }" />
257
425
  <slot v-if="!ready" :placeholder="placeholder" name="placeholder">
258
426
  <img v-bind="placeholderAttrs">
259
427
  </slot>
260
- <slot v-if="$script.status.value === 'loading'" name="loading">
428
+ <slot v-if="status !== 'awaitingLoad' && !ready" name="loading">
261
429
  <ScriptLoadingIndicator color="black" />
262
430
  </slot>
263
- <slot v-if="$script.status.value === 'awaitingLoad'" name="awaitingLoad" />
264
- <slot v-else-if="$script.status.value === 'error'" name="error" />
431
+ <slot v-if="status === 'awaitingLoad'" name="awaitingLoad" />
432
+ <slot v-else-if="status === 'error'" name="error" />
265
433
  <slot />
266
434
  </div>
267
435
  </template>
@@ -46,9 +46,12 @@ const intercom = useScriptIntercom({
46
46
  trigger,
47
47
  },
48
48
  })
49
- if (props.trigger === 'click')
50
- intercom.Intercom('show')
51
- const { $script } = intercom
49
+ const { status, onLoaded } = intercom
50
+ if (props.trigger === 'click') {
51
+ onLoaded((instance) => {
52
+ instance.Intercom('show')
53
+ })
54
+ }
52
55
 
53
56
  defineExpose({
54
57
  intercom,
@@ -56,7 +59,7 @@ defineExpose({
56
59
 
57
60
  let observer: MutationObserver
58
61
  onMounted(() => {
59
- watch($script.status, (status) => {
62
+ watch(status, (status) => {
60
63
  if (status === 'loading') {
61
64
  observer = new MutationObserver(() => {
62
65
  if (document.getElementById('intercom-frame')) {
@@ -86,8 +89,8 @@ onBeforeUnmount(() => {
86
89
  }"
87
90
  >
88
91
  <slot :ready="isReady" />
89
- <slot v-if="$script.status.value === 'awaitingLoad'" name="awaitingLoad" />
90
- <slot v-else-if="$script.status.value === 'loading' || !isReady" name="loading" />
91
- <slot v-else-if="$script.status.value === 'error'" name="error" />
92
+ <slot v-if="status === 'awaitingLoad'" name="awaitingLoad" />
93
+ <slot v-else-if="status === 'loading' || !isReady" name="loading" />
94
+ <slot v-else-if="status === 'error'" name="error" />
92
95
  </div>
93
96
  </template>
@@ -26,7 +26,7 @@ onMounted(() => {
26
26
  rootEl.value?.querySelectorAll('a[href]').forEach((a) => {
27
27
  a.classList.add('lemonsqueezy-button')
28
28
  })
29
- instance.$script.then(({ Setup, Refresh }) => {
29
+ instance.onLoaded(({ Setup, Refresh }) => {
30
30
  Setup({
31
31
  eventHandler(event) {
32
32
  emits('lemonSqueezyEvent', event)