@nuxt/scripts 1.0.0-beta.30 → 1.0.0-beta.31

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 (27) hide show
  1. package/dist/client/200.html +1 -1
  2. package/dist/client/404.html +1 -1
  3. package/dist/client/_nuxt/{CYlYSSNW.js → 6CwTUC2b.js} +1 -1
  4. package/dist/client/_nuxt/{D5FIkDae.js → B71AlSZ1.js} +1 -1
  5. package/dist/client/_nuxt/{AwAKM0sG.js → BYGJV5dd.js} +1 -1
  6. package/dist/client/_nuxt/{Bl23o3st.js → V4W-T8W6.js} +4 -4
  7. package/dist/client/_nuxt/builds/latest.json +1 -1
  8. package/dist/client/_nuxt/builds/meta/70b59a3e-a025-4a77-a25a-dfadf5b1749d.json +1 -0
  9. package/dist/client/index.html +1 -1
  10. package/dist/module.d.mts +2 -2
  11. package/dist/module.d.ts +2 -2
  12. package/dist/module.json +1 -1
  13. package/dist/module.mjs +43 -70
  14. package/dist/registry.mjs +9 -9
  15. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.d.vue.ts +5 -2
  16. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue +11 -2
  17. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue.d.ts +5 -2
  18. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.d.vue.ts +14 -0
  19. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue +50 -1
  20. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue.d.ts +14 -0
  21. package/dist/runtime/server/proxy-handler.js +25 -35
  22. package/dist/shared/scripts.D7e2ENu6.mjs +211 -0
  23. package/dist/stats.mjs +6 -14
  24. package/dist/types-source.mjs +15 -1
  25. package/package.json +2 -2
  26. package/dist/client/_nuxt/builds/meta/f0b4dd20-8496-4003-b7a3-05cbae515923.json +0 -1
  27. package/dist/shared/scripts.ViOoYQXH.mjs +0 -381
@@ -1 +1 @@
1
- {"id":"f0b4dd20-8496-4003-b7a3-05cbae515923","timestamp":1774019040423}
1
+ {"id":"70b59a3e-a025-4a77-a25a-dfadf5b1749d","timestamp":1774072124960}
@@ -0,0 +1 @@
1
+ {"id":"70b59a3e-a025-4a77-a25a-dfadf5b1749d","timestamp":1774072124960,"prerendered":[]}
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/__nuxt-scripts/_nuxt/entry.C5SUNdim.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-scripts/_nuxt/Bl23o3st.js"><script type="module" src="/__nuxt-scripts/_nuxt/Bl23o3st.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{"nuxt-scripts":{version:"",defaultScriptOptions:{trigger:"onNuxtReady"},googleStaticMapsProxy:"",endpoints:{}}},app:{baseURL:"/__nuxt-scripts",buildId:"f0b4dd20-8496-4003-b7a3-05cbae515923",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1774019045969,false]</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/__nuxt-scripts/_nuxt/entry.C5SUNdim.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-scripts/_nuxt/V4W-T8W6.js"><script type="module" src="/__nuxt-scripts/_nuxt/V4W-T8W6.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{"nuxt-scripts":{version:"",defaultScriptOptions:{trigger:"onNuxtReady"},googleStaticMapsProxy:"",endpoints:{}}},app:{baseURL:"/__nuxt-scripts",buildId:"70b59a3e-a025-4a77-a25a-dfadf5b1749d",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1774072130545,false]</script></body></html>
package/dist/module.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
  import { FetchOptions } from 'ofetch';
3
- import { ProxyPrivacyInput } from '../dist/runtime/server/utils/privacy.js';
4
3
  import { RegistryScripts, NuxtConfigScriptRegistry, NuxtUseScriptOptionsSerializable, NuxtUseScriptInput } from '../dist/runtime/types.js';
4
+ import { ProxyPrivacyInput } from '../dist/runtime/server/utils/privacy.js';
5
5
 
6
6
  /**
7
7
  * Global privacy override for all first-party proxy requests.
@@ -24,7 +24,7 @@ interface FirstPartyOptions {
24
24
  * Path prefix for proxy endpoints.
25
25
  *
26
26
  * Analytics collection requests are proxied through these paths.
27
- * For example, Google Analytics collection goes to `/_scripts/p/ga/g/collect`.
27
+ * For example, Google Analytics collection goes to `/_scripts/p/www.google-analytics.com/g/collect`.
28
28
  * @default '/_scripts/p'
29
29
  * @example '/_tracking'
30
30
  */
package/dist/module.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
  import { FetchOptions } from 'ofetch';
3
- import { ProxyPrivacyInput } from '../dist/runtime/server/utils/privacy.js';
4
3
  import { RegistryScripts, NuxtConfigScriptRegistry, NuxtUseScriptOptionsSerializable, NuxtUseScriptInput } from '../dist/runtime/types.js';
4
+ import { ProxyPrivacyInput } from '../dist/runtime/server/utils/privacy.js';
5
5
 
6
6
  /**
7
7
  * Global privacy override for all first-party proxy requests.
@@ -24,7 +24,7 @@ interface FirstPartyOptions {
24
24
  * Path prefix for proxy endpoints.
25
25
  *
26
26
  * Analytics collection requests are proxied through these paths.
27
- * For example, Google Analytics collection goes to `/_scripts/p/ga/g/collect`.
27
+ * For example, Google Analytics collection goes to `/_scripts/p/www.google-analytics.com/g/collect`.
28
28
  * @default '/_scripts/p'
29
29
  * @example '/_tracking'
30
30
  */
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16"
6
6
  },
7
- "version": "1.0.0-beta.30",
7
+ "version": "1.0.0-beta.31",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -19,48 +19,34 @@ import { colors } from 'consola/utils';
19
19
  import MagicString from 'magic-string';
20
20
  import { hash } from 'ohash';
21
21
  import { registry } from './registry.mjs';
22
- import { g as getAllProxyConfigs, r as routesToInterceptRules } from './shared/scripts.ViOoYQXH.mjs';
22
+ import { g as getAllProxyConfigs } from './shared/scripts.D7e2ENu6.mjs';
23
23
 
24
- function generatePartytownResolveUrl(interceptRules) {
25
- const rulesJson = JSON.stringify(interceptRules);
24
+ function generatePartytownResolveUrl(proxyPrefix) {
26
25
  return `function(url, location, type) {
27
- var rules = ${rulesJson};
28
- for (var i = 0; i < rules.length; i++) {
29
- var rule = rules[i];
30
- if (url.hostname === rule.pattern || url.hostname.endsWith('.' + rule.pattern)) {
31
- if (rule.pathPrefix && !url.pathname.startsWith(rule.pathPrefix)) continue;
32
- var path = rule.pathPrefix ? url.pathname.slice(rule.pathPrefix.length) : url.pathname;
33
- var newPath = rule.target + (path.startsWith('/') ? '' : '/') + path + url.search;
34
- return new URL(newPath, location.origin);
35
- }
26
+ if (url.origin !== location.origin) {
27
+ return new URL(${JSON.stringify(proxyPrefix)} + '/' + url.host + url.pathname + url.search, location.origin);
36
28
  }
37
29
  }`;
38
30
  }
39
31
 
40
32
  const logger = useLogger("@nuxt/scripts");
41
33
 
42
- function generateInterceptPluginContents(interceptRules) {
43
- const rulesJson = JSON.stringify(interceptRules);
34
+ function generateInterceptPluginContents(proxyPrefix) {
44
35
  return `export default defineNuxtPlugin({
45
36
  name: 'nuxt-scripts:intercept',
46
37
  enforce: 'pre',
47
38
  setup() {
48
- const rules = ${rulesJson};
39
+ const proxyPrefix = ${JSON.stringify(proxyPrefix)};
49
40
  const origBeacon = typeof navigator !== 'undefined' && navigator.sendBeacon
50
41
  ? navigator.sendBeacon.bind(navigator)
51
42
  : () => false;
52
43
  const origFetch = globalThis.fetch.bind(globalThis);
53
44
 
54
- function rewriteUrl(url) {
45
+ function proxyUrl(url) {
55
46
  try {
56
47
  const parsed = new URL(url, location.origin);
57
- for (const rule of rules) {
58
- if (parsed.hostname === rule.pattern || parsed.hostname.endsWith('.' + rule.pattern)) {
59
- if (rule.pathPrefix && !parsed.pathname.startsWith(rule.pathPrefix)) continue;
60
- const path = rule.pathPrefix ? parsed.pathname.slice(rule.pathPrefix.length) : parsed.pathname;
61
- return location.origin + rule.target + (path.startsWith('/') ? '' : '/') + path + parsed.search;
62
- }
63
- }
48
+ if (parsed.origin !== location.origin)
49
+ return location.origin + proxyPrefix + '/' + parsed.host + parsed.pathname + parsed.search;
64
50
  } catch {}
65
51
  return url;
66
52
  }
@@ -70,7 +56,7 @@ function generateInterceptPluginContents(interceptRules) {
70
56
  class ProxiedXHR extends OrigXHR {
71
57
  open() {
72
58
  const args = Array.from(arguments);
73
- if (typeof args[1] === 'string') args[1] = rewriteUrl(args[1]);
59
+ if (typeof args[1] === 'string') args[1] = proxyUrl(args[1]);
74
60
  return super.open.apply(this, args);
75
61
  }
76
62
  }
@@ -83,7 +69,7 @@ function generateInterceptPluginContents(interceptRules) {
83
69
  if (origSrcDesc && origSrcDesc.set) {
84
70
  Object.defineProperty(img, 'src', {
85
71
  get() { return origSrcDesc.get.call(this); },
86
- set(v) { origSrcDesc.set.call(this, typeof v === 'string' ? rewriteUrl(v) : v); },
72
+ set(v) { origSrcDesc.set.call(this, typeof v === 'string' ? proxyUrl(v) : v); },
87
73
  configurable: true,
88
74
  });
89
75
  }
@@ -91,8 +77,8 @@ function generateInterceptPluginContents(interceptRules) {
91
77
  }
92
78
 
93
79
  globalThis.__nuxtScripts = {
94
- sendBeacon: (url, data) => origBeacon(rewriteUrl(url), data),
95
- fetch: (url, opts) => origFetch(typeof url === 'string' ? rewriteUrl(url) : url, opts),
80
+ sendBeacon: (url, data) => origBeacon(proxyUrl(url), data),
81
+ fetch: (url, opts) => origFetch(typeof url === 'string' ? proxyUrl(url) : url, opts),
96
82
  XMLHttpRequest: ProxiedXHR,
97
83
  Image: ProxiedImage,
98
84
  };
@@ -106,7 +92,7 @@ async function setupFirstParty(config, resolvePath) {
106
92
  const proxyPrefix = typeof config.firstParty === "object" ? config.firstParty.proxyPrefix || "/_scripts/p" : "/_scripts/p";
107
93
  const privacy = typeof config.firstParty === "object" ? config.firstParty.privacy : void 0;
108
94
  const assetsPrefix = config.assets?.prefix || "/_scripts/assets";
109
- const proxyConfigs = enabled ? getAllProxyConfigs(proxyPrefix) : {};
95
+ const proxyConfigs = enabled ? getAllProxyConfigs() : {};
110
96
  const firstParty = { enabled, proxyPrefix, privacy, assetsPrefix, proxyConfigs };
111
97
  if (enabled) {
112
98
  const proxyHandlerPath = await resolvePath("./runtime/server/proxy-handler");
@@ -123,6 +109,9 @@ function applyAutoInject(registry, runtimeConfig, proxyPrefix, registryKey, auto
123
109
  if (!entry)
124
110
  return;
125
111
  const input = entry[0];
112
+ const scriptOptions = entry[1];
113
+ if (input?.firstParty === false || scriptOptions?.firstParty === false)
114
+ return;
126
115
  const rtScripts = runtimeConfig.public?.scripts;
127
116
  const rtEntry = rtScripts?.[registryKey];
128
117
  const config = rtEntry && typeof rtEntry === "object" ? rtEntry : input;
@@ -133,16 +122,6 @@ function applyAutoInject(registry, runtimeConfig, proxyPrefix, registryKey, auto
133
122
  if (rtEntry && typeof rtEntry === "object" && rtEntry !== input)
134
123
  rtEntry[autoInject.configField] = value;
135
124
  }
136
- const DOMAIN_RE = /^https?:\/\/([^/]+)/;
137
- function extractDomains(routes) {
138
- const domains = /* @__PURE__ */ new Set();
139
- for (const { proxy } of Object.values(routes)) {
140
- const match = proxy.match(DOMAIN_RE);
141
- if (match?.[1])
142
- domains.add(match[1]);
143
- }
144
- return [...domains].sort();
145
- }
146
125
  function computePrivacyLevel(privacy) {
147
126
  const flags = Object.values(privacy);
148
127
  if (flags.every(Boolean))
@@ -160,10 +139,10 @@ function finalizeFirstParty(opts) {
160
139
  if (script.registryKey)
161
140
  scriptByKey.set(script.registryKey, script);
162
141
  }
163
- const neededRoutes = {};
164
- const routePrivacyOverrides = {};
142
+ const domainPrivacy = {};
165
143
  const unsupportedScripts = [];
166
144
  const unmatchedScripts = [];
145
+ let totalDomains = 0;
167
146
  const devtoolsScripts = [];
168
147
  for (const key of registryKeys) {
169
148
  const script = scriptByKey.get(key);
@@ -173,6 +152,11 @@ function finalizeFirstParty(opts) {
173
152
  }
174
153
  if (script.proxy === false)
175
154
  continue;
155
+ const registryEntry = opts.registry?.[key];
156
+ const entryScriptOptions = registryEntry?.[1];
157
+ const entryInput = registryEntry?.[0];
158
+ if (entryScriptOptions?.firstParty === false || entryInput?.firstParty === false)
159
+ continue;
176
160
  const configKey = script.proxy || key;
177
161
  const proxyConfig = proxyConfigs[configKey];
178
162
  if (!proxyConfig) {
@@ -180,11 +164,9 @@ function finalizeFirstParty(opts) {
180
164
  unsupportedScripts.push(key);
181
165
  continue;
182
166
  }
183
- const scriptRoutes = proxyConfig.routes || {};
184
- if (proxyConfig.routes) {
185
- Object.assign(neededRoutes, proxyConfig.routes);
186
- for (const routePath of Object.keys(proxyConfig.routes))
187
- routePrivacyOverrides[routePath] = proxyConfig.privacy;
167
+ for (const domain of proxyConfig.domains) {
168
+ domainPrivacy[domain] = proxyConfig.privacy;
169
+ totalDomains++;
188
170
  }
189
171
  if (proxyConfig.autoInject && opts.registry)
190
172
  applyAutoInject(opts.registry, nuxtOptions.runtimeConfig, proxyPrefix, key, proxyConfig.autoInject);
@@ -200,7 +182,6 @@ function finalizeFirstParty(opts) {
200
182
  };
201
183
  const logo = script.logo;
202
184
  const logoStr = typeof logo === "object" ? logo.dark || logo.light : logo || "";
203
- const interceptRules2 = routesToInterceptRules(scriptRoutes);
204
185
  devtoolsScripts.push({
205
186
  registryKey: key,
206
187
  label: script.label || key,
@@ -213,9 +194,7 @@ function finalizeFirstParty(opts) {
213
194
  hasPostProcess: !!proxyConfig.postProcess,
214
195
  privacy: normalizedPrivacy,
215
196
  privacyLevel: computePrivacyLevel(normalizedPrivacy),
216
- domains: extractDomains(scriptRoutes),
217
- routes: Object.entries(scriptRoutes).map(([local, { proxy }]) => ({ local, target: proxy })),
218
- interceptRules: interceptRules2
197
+ domains: [...proxyConfig.domains]
219
198
  });
220
199
  }
221
200
  }
@@ -231,30 +210,21 @@ These scripts will not have proxy routes registered. Check that the registry key
231
210
  They will load directly from third-party servers. Request support at https://github.com/nuxt/scripts/issues`
232
211
  );
233
212
  }
234
- const interceptRules = routesToInterceptRules(neededRoutes);
235
213
  addPluginTemplate({
236
214
  filename: "nuxt-scripts-intercept.client.mjs",
237
215
  getContents() {
238
- return generateInterceptPluginContents(interceptRules);
216
+ return generateInterceptPluginContents(proxyPrefix);
239
217
  }
240
218
  });
241
- const flatRoutes = {};
242
- for (const [path, config] of Object.entries(neededRoutes))
243
- flatRoutes[path] = config.proxy;
244
219
  nuxtOptions.runtimeConfig["nuxt-scripts-proxy"] = {
245
- routes: flatRoutes,
246
- privacy: firstParty.privacy,
247
- routePrivacy: routePrivacyOverrides
220
+ proxyPrefix,
221
+ domainPrivacy,
222
+ privacy: firstParty.privacy
248
223
  };
249
224
  const privacyLabel = firstParty.privacy === void 0 ? "per-script" : typeof firstParty.privacy === "boolean" ? firstParty.privacy ? "anonymize" : "passthrough" : "custom";
250
- if (Object.keys(neededRoutes).length && nuxtOptions.dev) {
251
- const routeCount = Object.keys(neededRoutes).length;
225
+ if (totalDomains > 0 && nuxtOptions.dev) {
252
226
  const scriptsCount = registryKeys.length;
253
- logger.success(`First-party mode enabled for ${scriptsCount} script(s), ${routeCount} proxy route(s) configured (privacy: ${privacyLabel})`);
254
- if (logger.level >= 4) {
255
- for (const [path, config] of Object.entries(neededRoutes))
256
- logger.debug(` ${path} \u2192 ${config.proxy}`);
257
- }
227
+ logger.success(`First-party mode enabled for ${scriptsCount} script(s), ${totalDomains} domain(s) proxied (privacy: ${privacyLabel})`);
258
228
  }
259
229
  const staticPresets = ["static", "github-pages", "cloudflare-pages-static", "netlify-static", "azure-static", "firebase-static"];
260
230
  const preset = process.env.NITRO_PRESET || "";
@@ -283,11 +253,10 @@ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
283
253
  proxyPrefix,
284
254
  privacyMode: privacyLabel,
285
255
  scripts: devtoolsScripts,
286
- totalRoutes: Object.keys(neededRoutes).length,
287
256
  totalDomains: allDomains.size
288
257
  };
289
258
  }
290
- return { interceptRules, devtools };
259
+ return { proxyPrefix, devtools };
291
260
  }
292
261
 
293
262
  const renderedScript = /* @__PURE__ */ new Map();
@@ -1056,7 +1025,10 @@ function NuxtScriptBundleTransformer(options = {
1056
1025
  const script = options.scripts?.find((s2) => s2.import.name === fnName);
1057
1026
  const proxyConfigKey = script?.proxy !== false ? script?.proxy || registryKey : void 0;
1058
1027
  const proxyConfig = !firstPartyOptOut && proxyConfigKey ? options.proxyConfigs?.[proxyConfigKey] : void 0;
1059
- const proxyRewrites = proxyConfig?.rewrite;
1028
+ const proxyRewrites = proxyConfig?.domains?.map((domain) => ({
1029
+ from: domain,
1030
+ to: `${options.proxyPrefix}/${domain}`
1031
+ }));
1060
1032
  const postProcess = proxyConfig?.postProcess;
1061
1033
  const skipApiRewrites = !!(registryKey && options.partytownScripts?.has(registryKey));
1062
1034
  deferredOps.push(async () => {
@@ -1601,7 +1573,7 @@ const module$1 = defineNuxtModule({
1601
1573
  }
1602
1574
  const { renderedScript } = setupPublicAssetStrategy(config.assets);
1603
1575
  if (firstParty.enabled) {
1604
- const { interceptRules, devtools: devtoolsData } = finalizeFirstParty({
1576
+ const { proxyPrefix, devtools: devtoolsData } = finalizeFirstParty({
1605
1577
  firstParty,
1606
1578
  registry: config.registry,
1607
1579
  registryScripts,
@@ -1610,10 +1582,10 @@ const module$1 = defineNuxtModule({
1610
1582
  if (devtoolsData) {
1611
1583
  nuxt.options.runtimeConfig.public["nuxt-scripts-devtools"] = devtoolsData;
1612
1584
  }
1613
- if (config.partytown?.length && hasNuxtModule("@nuxtjs/partytown") && interceptRules.length) {
1585
+ if (config.partytown?.length && hasNuxtModule("@nuxtjs/partytown")) {
1614
1586
  const partytownConfig = nuxt.options.partytown || {};
1615
1587
  if (!partytownConfig.resolveUrl) {
1616
- partytownConfig.resolveUrl = generatePartytownResolveUrl(interceptRules);
1588
+ partytownConfig.resolveUrl = generatePartytownResolveUrl(proxyPrefix);
1617
1589
  nuxt.options.partytown = partytownConfig;
1618
1590
  logger.info("[partytown] Auto-configured resolveUrl for first-party proxy");
1619
1591
  } else {
@@ -1630,6 +1602,7 @@ const module$1 = defineNuxtModule({
1630
1602
  registryConfig: nuxt.options.runtimeConfig.public.scripts,
1631
1603
  defaultBundle: firstParty.enabled || config.defaultScriptOptions?.bundle,
1632
1604
  proxyConfigs: firstParty.proxyConfigs,
1605
+ proxyPrefix: firstParty.proxyPrefix,
1633
1606
  partytownScripts: new Set(config.partytown || []),
1634
1607
  moduleDetected(module) {
1635
1608
  if (nuxt.options.dev && module !== "@nuxt/scripts" && !moduleInstallPromises.has(module) && !hasNuxtModule(module))
package/dist/registry.mjs CHANGED
@@ -340,6 +340,8 @@ async function registry(resolve) {
340
340
  registryKey: "stripe",
341
341
  label: "Stripe",
342
342
  scriptBundling: false,
343
+ // needs fingerprinting for fraud detection
344
+ proxy: false,
343
345
  category: "payments",
344
346
  logo: LOGOS.stripe,
345
347
  import: {
@@ -363,7 +365,8 @@ async function registry(resolve) {
363
365
  registryKey: "paypal",
364
366
  label: "PayPal",
365
367
  src: false,
366
- // should not be bundled
368
+ // needs fingerprinting for fraud detection
369
+ proxy: false,
367
370
  category: "payments",
368
371
  logo: LOGOS.paypal,
369
372
  import: {
@@ -466,18 +469,14 @@ async function registry(resolve) {
466
469
  {
467
470
  registryKey: "googleRecaptcha",
468
471
  label: "Google reCAPTCHA",
472
+ scriptBundling: false,
473
+ // needs fingerprinting for bot detection
474
+ proxy: false,
469
475
  category: "utility",
470
476
  logo: LOGOS.googleRecaptcha,
471
477
  import: {
472
478
  name: "useScriptGoogleRecaptcha",
473
479
  from: await resolve("./runtime/registry/google-recaptcha")
474
- },
475
- scriptBundling(options) {
476
- if (!options?.siteKey) {
477
- return false;
478
- }
479
- const baseUrl = options?.recaptchaNet ? "https://www.recaptcha.net/recaptcha" : "https://www.google.com/recaptcha";
480
- return `${baseUrl}/${options?.enterprise ? "enterprise.js" : "api.js"}`;
481
480
  }
482
481
  },
483
482
  {
@@ -485,7 +484,8 @@ async function registry(resolve) {
485
484
  label: "Google Sign-In",
486
485
  src: "https://accounts.google.com/gsi/client",
487
486
  scriptBundling: false,
488
- // CORS prevents bundling
487
+ // CORS prevents bundling, needs fingerprinting for auth
488
+ proxy: false,
489
489
  category: "utility",
490
490
  logo: LOGOS.googleSignIn,
491
491
  import: {
@@ -12,10 +12,13 @@ export interface MarkerClustererOptions {
12
12
  renderer?: unknown;
13
13
  onClusterClick?: unknown;
14
14
  }
15
- export declare const MARKER_CLUSTERER_INJECTION_KEY: InjectionKey<{
15
+ export interface MarkerClustererContext {
16
16
  markerClusterer: ShallowRef<MarkerClustererInstance | undefined>;
17
17
  requestRerender: () => void;
18
- }>;
18
+ /** Increments after each clustering cycle; watch to detect cluster membership changes */
19
+ clusteringVersion: ShallowRef<number>;
20
+ }
21
+ export declare const MARKER_CLUSTERER_INJECTION_KEY: InjectionKey<MarkerClustererContext>;
19
22
  declare const _default: typeof __VLS_export;
20
23
  export default _default;
21
24
  declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
@@ -1,6 +1,7 @@
1
1
  <script>
2
- import { provide, shallowRef, watch } from "vue";
2
+ import { inject, provide, shallowRef, watch } from "vue";
3
3
  import { bindGoogleMapsEvents } from "./bindGoogleMapsEvents";
4
+ import { MAP_INJECTION_KEY } from "./injectionKeys";
4
5
  import { useGoogleMapsResource } from "./useGoogleMapsResource";
5
6
  export const MARKER_CLUSTERER_INJECTION_KEY = /* @__PURE__ */ Symbol("marker-clusterer");
6
7
  </script>
@@ -15,6 +16,8 @@ const markerClustererEvents = [
15
16
  "clusteringbegin",
16
17
  "clusteringend"
17
18
  ];
19
+ const mapContext = inject(MAP_INJECTION_KEY, void 0);
20
+ const clusteringVersion = shallowRef(0);
18
21
  const markerClusterer = useGoogleMapsResource({
19
22
  async create({ map }) {
20
23
  const { MarkerClusterer } = await import("@googlemaps/markerclusterer");
@@ -23,6 +26,9 @@ const markerClusterer = useGoogleMapsResource({
23
26
  ...props.options
24
27
  });
25
28
  bindGoogleMapsEvents(clusterer, emit, { withPayload: markerClustererEvents });
29
+ clusterer.addListener("clusteringend", () => {
30
+ clusteringVersion.value++;
31
+ });
26
32
  return clusterer;
27
33
  },
28
34
  cleanup(clusterer, { mapsApi }) {
@@ -39,6 +45,8 @@ watch(
39
45
  (ready) => {
40
46
  if (!ready)
41
47
  return;
48
+ if (!mapContext?.map.value?.getProjection())
49
+ return;
42
50
  rerenderPending.value = false;
43
51
  try {
44
52
  markerClusterer.value.render();
@@ -53,7 +61,8 @@ provide(
53
61
  MARKER_CLUSTERER_INJECTION_KEY,
54
62
  {
55
63
  markerClusterer,
56
- requestRerender
64
+ requestRerender,
65
+ clusteringVersion
57
66
  }
58
67
  );
59
68
  </script>
@@ -12,10 +12,13 @@ export interface MarkerClustererOptions {
12
12
  renderer?: unknown;
13
13
  onClusterClick?: unknown;
14
14
  }
15
- export declare const MARKER_CLUSTERER_INJECTION_KEY: InjectionKey<{
15
+ export interface MarkerClustererContext {
16
16
  markerClusterer: ShallowRef<MarkerClustererInstance | undefined>;
17
17
  requestRerender: () => void;
18
- }>;
18
+ /** Increments after each clustering cycle; watch to detect cluster membership changes */
19
+ clusteringVersion: ShallowRef<number>;
20
+ }
21
+ export declare const MARKER_CLUSTERER_INJECTION_KEY: InjectionKey<MarkerClustererContext>;
19
22
  declare const _default: typeof __VLS_export;
20
23
  export default _default;
21
24
  declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
@@ -33,6 +33,18 @@ type __VLS_Props = {
33
33
  * @default true
34
34
  */
35
35
  blockMapInteraction?: boolean;
36
+ /**
37
+ * Pan the map so the overlay is fully visible when opened, similar to InfoWindow behavior.
38
+ * Set to `true` for default 40px padding, or a number for custom padding.
39
+ * @default true
40
+ */
41
+ panOnOpen?: boolean | number;
42
+ /**
43
+ * Automatically hide the overlay when its parent marker joins a cluster (on zoom out).
44
+ * Only applies when nested inside a ScriptGoogleMapsMarkerClusterer.
45
+ * @default true
46
+ */
47
+ hideWhenClustered?: boolean;
36
48
  };
37
49
  type __VLS_ModelProps = {
38
50
  'open'?: boolean;
@@ -52,6 +64,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
52
64
  anchor: OverlayAnchor;
53
65
  pane: OverlayPane;
54
66
  blockMapInteraction: boolean;
67
+ panOnOpen: boolean | number;
68
+ hideWhenClustered: boolean;
55
69
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
56
70
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
57
71
  declare const _default: typeof __VLS_export;
@@ -1,6 +1,7 @@
1
1
  <script setup>
2
2
  import { inject, useTemplateRef, watch } from "vue";
3
3
  import { ADVANCED_MARKER_ELEMENT_INJECTION_KEY, MARKER_INJECTION_KEY } from "./injectionKeys";
4
+ import { MARKER_CLUSTERER_INJECTION_KEY } from "./ScriptGoogleMapsMarkerClusterer.vue";
4
5
  import { useGoogleMapsResource } from "./useGoogleMapsResource";
5
6
  const props = defineProps({
6
7
  position: { type: null, required: false },
@@ -8,11 +9,14 @@ const props = defineProps({
8
9
  offset: { type: Object, required: false },
9
10
  pane: { type: String, required: false, default: "floatPane" },
10
11
  zIndex: { type: Number, required: false },
11
- blockMapInteraction: { type: Boolean, required: false, default: true }
12
+ blockMapInteraction: { type: Boolean, required: false, default: true },
13
+ panOnOpen: { type: [Boolean, Number], required: false, default: true },
14
+ hideWhenClustered: { type: Boolean, required: false, default: true }
12
15
  });
13
16
  const open = defineModel("open", { type: Boolean, ...{ default: void 0 } });
14
17
  const markerContext = inject(MARKER_INJECTION_KEY, void 0);
15
18
  const advancedMarkerElementContext = inject(ADVANCED_MARKER_ELEMENT_INJECTION_KEY, void 0);
19
+ const markerClustererContext = inject(MARKER_CLUSTERER_INJECTION_KEY, void 0);
16
20
  function getResolvedPosition() {
17
21
  if (props.position)
18
22
  return props.position;
@@ -44,6 +48,25 @@ const ANCHOR_TRANSFORMS = {
44
48
  };
45
49
  const overlayContent = useTemplateRef("overlay-content");
46
50
  const listeners = [];
51
+ function panMapToFitOverlay(el, map, padding) {
52
+ const child = el.firstElementChild;
53
+ if (!child)
54
+ return;
55
+ const overlayRect = child.getBoundingClientRect();
56
+ const mapRect = map.getDiv().getBoundingClientRect();
57
+ let panX = 0;
58
+ let panY = 0;
59
+ if (overlayRect.top - padding < mapRect.top)
60
+ panY = overlayRect.top - mapRect.top - padding;
61
+ if (overlayRect.bottom + padding > mapRect.bottom)
62
+ panY = overlayRect.bottom - mapRect.bottom + padding;
63
+ if (overlayRect.left - padding < mapRect.left)
64
+ panX = overlayRect.left - mapRect.left - padding;
65
+ else if (overlayRect.right + padding > mapRect.right)
66
+ panX = overlayRect.right - mapRect.right + padding;
67
+ if (panX !== 0 || panY !== 0)
68
+ map.panBy(panX, panY);
69
+ }
47
70
  const overlay = useGoogleMapsResource({
48
71
  // ready condition accesses .value on ShallowRefs — tracked by whenever() in useGoogleMapsResource
49
72
  ready: () => !!overlayContent.value && !!(props.position || markerContext?.marker.value || advancedMarkerElementContext?.advancedMarkerElement.value),
@@ -57,6 +80,12 @@ const overlay = useGoogleMapsResource({
57
80
  if (props.blockMapInteraction)
58
81
  mapsApi.OverlayView.preventMapHitsAndGesturesFrom(el);
59
82
  }
83
+ if (props.panOnOpen) {
84
+ const padding = typeof props.panOnOpen === "number" ? props.panOnOpen : 40;
85
+ requestAnimationFrame(() => {
86
+ panMapToFitOverlay(el, map, padding);
87
+ });
88
+ }
60
89
  }
61
90
  draw() {
62
91
  if (open.value === false) {
@@ -148,6 +177,26 @@ watch([() => props.pane, () => props.blockMapInteraction], () => {
148
177
  overlay.value.setMap(map);
149
178
  }
150
179
  });
180
+ if (markerClustererContext && (markerContext || advancedMarkerElementContext)) {
181
+ watch(
182
+ () => markerClustererContext.clusteringVersion.value,
183
+ () => {
184
+ if (!props.hideWhenClustered || open.value === false)
185
+ return;
186
+ const clusterer = markerClustererContext.markerClusterer.value;
187
+ if (!clusterer?.clusters)
188
+ return;
189
+ const parentMarker = advancedMarkerElementContext?.advancedMarkerElement.value ?? markerContext?.marker.value;
190
+ if (!parentMarker)
191
+ return;
192
+ const isClustered = clusterer.clusters.some(
193
+ (cluster) => cluster.count > 1 && cluster.markers?.includes(parentMarker)
194
+ );
195
+ if (isClustered)
196
+ open.value = false;
197
+ }
198
+ );
199
+ }
151
200
  defineExpose({ overlay });
152
201
  </script>
153
202
 
@@ -33,6 +33,18 @@ type __VLS_Props = {
33
33
  * @default true
34
34
  */
35
35
  blockMapInteraction?: boolean;
36
+ /**
37
+ * Pan the map so the overlay is fully visible when opened, similar to InfoWindow behavior.
38
+ * Set to `true` for default 40px padding, or a number for custom padding.
39
+ * @default true
40
+ */
41
+ panOnOpen?: boolean | number;
42
+ /**
43
+ * Automatically hide the overlay when its parent marker joins a cluster (on zoom out).
44
+ * Only applies when nested inside a ScriptGoogleMapsMarkerClusterer.
45
+ * @default true
46
+ */
47
+ hideWhenClustered?: boolean;
36
48
  };
37
49
  type __VLS_ModelProps = {
38
50
  'open'?: boolean;
@@ -52,6 +64,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
52
64
  anchor: OverlayAnchor;
53
65
  pane: OverlayPane;
54
66
  blockMapInteraction: boolean;
67
+ panOnOpen: boolean | number;
68
+ hideWhenClustered: boolean;
55
69
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
56
70
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
57
71
  declare const _default: typeof __VLS_export;