@jwiedeman/gtm-kit-next 1.1.1 → 1.1.3
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.
- package/README.md +26 -26
- package/dist/index.cjs +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @jwiedeman/gtm-kit-next
|
|
2
2
|
|
|
3
|
-
[](https://github.com/jwiedeman/GTM-Kit/actions/workflows/ci.yml)
|
|
4
|
+
[](https://codecov.io/gh/jwiedeman/GTM-Kit)
|
|
5
|
+
[](https://www.npmjs.com/package/@jwiedeman/gtm-kit-next)
|
|
6
|
+
[](https://bundlephobia.com/package/@jwiedeman/gtm-kit-next)
|
|
7
7
|
[](https://www.typescriptlang.org/)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
[](https://nextjs.org/)
|
|
@@ -17,15 +17,15 @@ The Next.js adapter for GTM Kit - provides server components and route tracking
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm install @
|
|
20
|
+
npm install @jwiedeman/gtm-kit @jwiedeman/gtm-kit-next
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
yarn add @
|
|
24
|
+
yarn add @jwiedeman/gtm-kit @jwiedeman/gtm-kit-next
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
pnpm add @
|
|
28
|
+
pnpm add @jwiedeman/gtm-kit @jwiedeman/gtm-kit-next
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
---
|
|
@@ -36,7 +36,7 @@ pnpm add @react-gtm-kit/core @react-gtm-kit/next
|
|
|
36
36
|
|
|
37
37
|
```tsx
|
|
38
38
|
// app/layout.tsx
|
|
39
|
-
import { GtmHeadScript, GtmNoScript } from '@
|
|
39
|
+
import { GtmHeadScript, GtmNoScript } from '@jwiedeman/gtm-kit-next';
|
|
40
40
|
|
|
41
41
|
export default function RootLayout({ children }) {
|
|
42
42
|
return (
|
|
@@ -58,8 +58,8 @@ export default function RootLayout({ children }) {
|
|
|
58
58
|
```tsx
|
|
59
59
|
// app/providers/gtm.tsx
|
|
60
60
|
'use client';
|
|
61
|
-
import { createGtmClient } from '@
|
|
62
|
-
import { useTrackPageViews } from '@
|
|
61
|
+
import { createGtmClient } from '@jwiedeman/gtm-kit';
|
|
62
|
+
import { useTrackPageViews } from '@jwiedeman/gtm-kit-next';
|
|
63
63
|
|
|
64
64
|
const client = createGtmClient({ containers: 'GTM-XXXXXX' });
|
|
65
65
|
client.init();
|
|
@@ -74,7 +74,7 @@ export function GtmProvider({ children }) {
|
|
|
74
74
|
|
|
75
75
|
```tsx
|
|
76
76
|
'use client';
|
|
77
|
-
import { pushEvent } from '@
|
|
77
|
+
import { pushEvent } from '@jwiedeman/gtm-kit';
|
|
78
78
|
|
|
79
79
|
// In any client component
|
|
80
80
|
function BuyButton({ client }) {
|
|
@@ -104,7 +104,7 @@ function BuyButton({ client }) {
|
|
|
104
104
|
Renders the GTM script tag. Place in your `<head>`.
|
|
105
105
|
|
|
106
106
|
```tsx
|
|
107
|
-
import { GtmHeadScript } from '@
|
|
107
|
+
import { GtmHeadScript } from '@jwiedeman/gtm-kit-next';
|
|
108
108
|
|
|
109
109
|
<GtmHeadScript
|
|
110
110
|
containers="GTM-XXXXXX"
|
|
@@ -117,7 +117,7 @@ import { GtmHeadScript } from '@react-gtm-kit/next';
|
|
|
117
117
|
Renders the noscript fallback iframe. Place at the start of `<body>`.
|
|
118
118
|
|
|
119
119
|
```tsx
|
|
120
|
-
import { GtmNoScript } from '@
|
|
120
|
+
import { GtmNoScript } from '@jwiedeman/gtm-kit-next';
|
|
121
121
|
|
|
122
122
|
<GtmNoScript containers="GTM-XXXXXX" />;
|
|
123
123
|
```
|
|
@@ -132,7 +132,7 @@ Automatically tracks page views on route changes.
|
|
|
132
132
|
|
|
133
133
|
```tsx
|
|
134
134
|
'use client';
|
|
135
|
-
import { useTrackPageViews } from '@
|
|
135
|
+
import { useTrackPageViews } from '@jwiedeman/gtm-kit-next';
|
|
136
136
|
|
|
137
137
|
export function GtmProvider({ children, client }) {
|
|
138
138
|
useTrackPageViews({ client });
|
|
@@ -148,7 +148,7 @@ export function GtmProvider({ children, client }) {
|
|
|
148
148
|
|
|
149
149
|
```tsx
|
|
150
150
|
// app/layout.tsx
|
|
151
|
-
import { GtmHeadScript, GtmNoScript } from '@
|
|
151
|
+
import { GtmHeadScript, GtmNoScript } from '@jwiedeman/gtm-kit-next';
|
|
152
152
|
import { GtmProvider } from './providers/gtm';
|
|
153
153
|
|
|
154
154
|
export default function RootLayout({ children }) {
|
|
@@ -171,8 +171,8 @@ export default function RootLayout({ children }) {
|
|
|
171
171
|
```tsx
|
|
172
172
|
// app/providers/gtm.tsx
|
|
173
173
|
'use client';
|
|
174
|
-
import { createGtmClient } from '@
|
|
175
|
-
import { useTrackPageViews } from '@
|
|
174
|
+
import { createGtmClient } from '@jwiedeman/gtm-kit';
|
|
175
|
+
import { useTrackPageViews } from '@jwiedeman/gtm-kit-next';
|
|
176
176
|
import { createContext, useContext } from 'react';
|
|
177
177
|
|
|
178
178
|
const client = createGtmClient({ containers: 'GTM-XXXXXX' });
|
|
@@ -195,7 +195,7 @@ export function useGtmClient() {
|
|
|
195
195
|
```tsx
|
|
196
196
|
// app/components/BuyButton.tsx
|
|
197
197
|
'use client';
|
|
198
|
-
import { pushEvent } from '@
|
|
198
|
+
import { pushEvent } from '@jwiedeman/gtm-kit';
|
|
199
199
|
import { useGtmClient } from '../providers/gtm';
|
|
200
200
|
|
|
201
201
|
export function BuyButton() {
|
|
@@ -212,8 +212,8 @@ export function BuyButton() {
|
|
|
212
212
|
```tsx
|
|
213
213
|
// app/providers/gtm.tsx
|
|
214
214
|
'use client';
|
|
215
|
-
import { createGtmClient, consentPresets } from '@
|
|
216
|
-
import { useTrackPageViews } from '@
|
|
215
|
+
import { createGtmClient, consentPresets } from '@jwiedeman/gtm-kit';
|
|
216
|
+
import { useTrackPageViews } from '@jwiedeman/gtm-kit-next';
|
|
217
217
|
|
|
218
218
|
const client = createGtmClient({ containers: 'GTM-XXXXXX' });
|
|
219
219
|
|
|
@@ -234,7 +234,7 @@ export { client };
|
|
|
234
234
|
// app/components/CookieBanner.tsx
|
|
235
235
|
'use client';
|
|
236
236
|
import { client } from '../providers/gtm';
|
|
237
|
-
import { consentPresets } from '@
|
|
237
|
+
import { consentPresets } from '@jwiedeman/gtm-kit';
|
|
238
238
|
|
|
239
239
|
export function CookieBanner() {
|
|
240
240
|
// Accept all tracking
|
|
@@ -280,7 +280,7 @@ For strict CSP configurations, pass a nonce:
|
|
|
280
280
|
```tsx
|
|
281
281
|
// app/layout.tsx
|
|
282
282
|
import { headers } from 'next/headers';
|
|
283
|
-
import { GtmHeadScript, GtmNoScript } from '@
|
|
283
|
+
import { GtmHeadScript, GtmNoScript } from '@jwiedeman/gtm-kit-next';
|
|
284
284
|
|
|
285
285
|
export default function RootLayout({ children }) {
|
|
286
286
|
const nonce = headers().get('x-nonce') || '';
|
|
@@ -313,11 +313,11 @@ export default function RootLayout({ children }) {
|
|
|
313
313
|
|
|
314
314
|
## Pages Router (Legacy)
|
|
315
315
|
|
|
316
|
-
For Next.js Pages Router, use `@
|
|
316
|
+
For Next.js Pages Router, use `@jwiedeman/gtm-kit-react` instead:
|
|
317
317
|
|
|
318
318
|
```tsx
|
|
319
319
|
// pages/_app.tsx
|
|
320
|
-
import { GtmProvider } from '@
|
|
320
|
+
import { GtmProvider } from '@jwiedeman/gtm-kit-react';
|
|
321
321
|
|
|
322
322
|
export default function App({ Component, pageProps }) {
|
|
323
323
|
return (
|
|
@@ -334,7 +334,7 @@ export default function App({ Component, pageProps }) {
|
|
|
334
334
|
|
|
335
335
|
- Next.js 13.4+ (App Router)
|
|
336
336
|
- React 18+
|
|
337
|
-
- `@
|
|
337
|
+
- `@jwiedeman/gtm-kit` (peer dependency)
|
|
338
338
|
|
|
339
339
|
---
|
|
340
340
|
|
package/dist/index.cjs
CHANGED
|
@@ -5,10 +5,10 @@ var navigation = require('next/navigation');
|
|
|
5
5
|
var gtmKit = require('@jwiedeman/gtm-kit');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var B="page_view",j=({pagePath:t,url:e,title:a})=>{let r={page_path:t,page_location:e};return a&&(r.page_title=a),r},Y=(t,e)=>{var r;let a=typeof window!="undefined"&&((r=window.location)!=null&&r.origin)?window.location.origin:"";return a?`${a}${t}${e}`:`${t}${e}`},Q=t=>t&&t.startsWith("#")?t:t?`#${t}`:"",K=({client:t,eventName:e=B,buildPayload:a=j,includeSearchParams:r=!0,trackHash:c=!1,trackOnMount:d=!0,skipSamePath:n=!0,pushEventFn:w=gtmKit.pushEvent,waitForReady:f=!1,readyPromise:u})=>{if(!t)throw new Error("A GTM client is required to track page views.");let l=navigation.usePathname(),m=navigation.useSearchParams(),s=react.useMemo(()=>!r||!m?"":m.toString(),[r,m]),o=react.useRef(null),g=react.useRef(!1),S=react.useRef(null),G=react.useRef(f?u!=null?u:t.whenReady():null),b=react.useRef(!0);react.useEffect(()=>(b.current=!0,()=>{b.current=!1;}),[]),react.useEffect(()=>{G.current=f?u!=null?u:t.whenReady():null;},[t,u,f]);let U=react.useCallback(p=>{let i=p.filter(h=>h.status==="failed");if(!i.length)return;let _=i.map(h=>h.containerId).join(", ");console.error(`[react-gtm-kit] Failed to load GTM container script(s): ${_}`,i);},[]),C=react.useCallback((p,i,_)=>{if(!p)return;let h=c?Q(_):"",y=i?`${p}?${i}`:p,R=`${y}${h}`,k=Y(y,h);if(!d&&!g.current){o.current={key:R,pathname:p,search:i,hash:h,pagePath:y,url:k},g.current=!0;return}if(n&&o.current&&o.current.key===R)return;let V=typeof document!="undefined"?document.title:void 0,$=o.current?{pathname:o.current.pathname,search:o.current.search,hash:o.current.hash,pagePath:o.current.pagePath,url:o.current.url}:void 0,H={pathname:p,search:i,hash:h,pagePath:y,url:k,title:V,previous:$},v=()=>{let T=a(H);w(t,e,T),o.current={key:R,pathname:p,search:i,hash:h,pagePath:y,url:k},g.current=!0;};if(f&&G.current){S.current=R,G.current.then(T=>{!b.current||S.current!==R||(U(T),v());}).catch(T=>{!b.current||S.current!==R||(console.error("[react-gtm-kit] Error while waiting for GTM readiness.",T),v());});return}v();},[a,t,e,U,w,n,c,d,f]);react.useEffect(()=>{var i;if(typeof window=="undefined")return;let p=c&&(i=window.location.hash)!=null?i:"";C(l,s,p);},[C,l,s,c]),react.useEffect(()=>{if(!c||typeof window=="undefined")return;let p=()=>{var i;C(l,s,(i=window.location.hash)!=null?i:"");};return window.addEventListener("hashchange",p),()=>{window.removeEventListener("hashchange",p);}},[C,l,s,c]);};var J=!0,X=({containers:t,host:e=gtmKit.DEFAULT_GTM_HOST,defaultQueryParams:a,scriptAttributes:r,dataLayerName:c=gtmKit.DEFAULT_DATA_LAYER_NAME})=>{let d=gtmKit.normalizeContainers(t);if(!d.length)throw new Error("At least one GTM container is required to render script tags.");return jsxRuntime.jsx(jsxRuntime.Fragment,{children:d.map(n=>{if(!n.id)throw new Error("Container id is required to render GTM script tags.");let w={...a,...n.queryParams},f=gtmKit.buildGtmScriptUrl(e,n.id,w,c),{async:u,defer:l,nonce:m,...s}=r!=null?r:{},o={src:f,async:u!=null?u:J};l!==void 0&&(o.defer=l),m&&(o.nonce=m);for(let[g,S]of Object.entries(s))g==="async"||g==="defer"||g==="nonce"||g==="src"||S!=null&&(o[g]=S);return jsxRuntime.jsx("script",{"data-gtm-container-id":n.id,...o},n.id)})})};var rt=t=>String(t),nt=t=>t.split(";").map(e=>e.trim()).filter(Boolean).reduce((e,a)=>{let[r,c]=a.split(":");if(!r||c===void 0)return e;let d=r.trim().replace(/-([a-z])/g,(w,f)=>f.toUpperCase()),n=c.trim();return !d||!n||(e[d]=n),e},{}),ot=({containers:t,host:e=gtmKit.DEFAULT_GTM_HOST,defaultQueryParams:a,iframeAttributes:r,dataLayerName:c=gtmKit.DEFAULT_DATA_LAYER_NAME})=>{let d=gtmKit.normalizeContainers(t);if(!d.length)throw new Error("At least one GTM container is required to render noscript markup.");return jsxRuntime.jsx(jsxRuntime.Fragment,{children:d.map(n=>{if(!n.id)throw new Error("Container id is required to render GTM noscript markup.");let w={...a,...n.queryParams},f=gtmKit.buildGtmNoscriptUrl(e,n.id,w,c),u={...gtmKit.DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,...r},l={src:f};for(let[m,s]of Object.entries(u))if(m!=="src"&&s!=null){if(m==="style"){typeof s=="string"?l.style=nt(s):typeof s=="object"&&(l.style=s);continue}l[m]=rt(s);}return jsxRuntime.jsx("noscript",{children:jsxRuntime.jsx("iframe",{...l})},n.id)})})};
|
|
9
9
|
|
|
10
|
-
exports.GtmHeadScript =
|
|
11
|
-
exports.GtmNoScript =
|
|
12
|
-
exports.useTrackPageViews =
|
|
10
|
+
exports.GtmHeadScript = X;
|
|
11
|
+
exports.GtmNoScript = ot;
|
|
12
|
+
exports.useTrackPageViews = K;
|
|
13
13
|
//# sourceMappingURL=out.js.map
|
|
14
14
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx"],"names":["useCallback","useEffect","useMemo","useRef","usePathname","useSearchParams","pushEvent","DEFAULT_EVENT_NAME","defaultBuildPayload","pagePath","url","title","payload","buildUrl","hash","_a","origin","sanitizeHash","useTrackPageViews","client","eventName","buildPayload","includeSearchParams","trackHash","trackOnMount","skipSamePath","pushEventFn","waitForReady","readyPromise","pathname","searchParams","search","previousRef","hasTrackedRef","pendingKeyRef","readinessRef","isMountedRef","logFailures","states","failed","state","details","handleRouteChange","nextPathname","searchValue","rawHash","normalizedHash","key","previous","pushPayload","error","listener","DEFAULT_DATA_LAYER_NAME","DEFAULT_GTM_HOST","isString","value","normalizeContainer","input","normalizeContainers","containers","toRecord","params","acc","normalizeHost","host","kind","containerId","queryParams","dataLayerName","normalizedHost","buildScriptUrl","buildNoscriptUrl","Fragment","jsx","DEFAULT_ASYNC","GtmHeadScript","defaultQueryParams","scriptAttributes","normalized","container","src","asyncAttr","defer","nonce","restAttributes","scriptProps","attribute","DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES","toStringValue","parseStyle","style","chunk","declaration","property","rawValue","_","char","GtmNoScript","iframeAttributes","attributes","iframeProps"],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport type { GtmClient, PageViewPayload, ScriptLoadState } from '@jwiedeman/gtm-kit';\nimport { pushEvent } from '@jwiedeman/gtm-kit';\n\nconst DEFAULT_EVENT_NAME = 'page_view';\n\nexport interface RouteLocationSnapshot {\n pathname: string;\n search: string;\n hash: string;\n pagePath: string;\n url: string;\n}\n\nexport interface RouteChangeEventDetails extends RouteLocationSnapshot {\n title?: string;\n previous?: RouteLocationSnapshot;\n}\n\nexport type PageViewPayloadBuilder = (details: RouteChangeEventDetails) => PageViewPayload;\n\nexport interface UseTrackPageViewsOptions {\n client: Pick<GtmClient, 'push' | 'whenReady'>;\n eventName?: string;\n buildPayload?: PageViewPayloadBuilder;\n includeSearchParams?: boolean;\n trackHash?: boolean;\n trackOnMount?: boolean;\n skipSamePath?: boolean;\n pushEventFn?: typeof pushEvent;\n waitForReady?: boolean;\n readyPromise?: Promise<ScriptLoadState[]>;\n}\n\ninterface RouteSnapshot extends RouteLocationSnapshot {\n key: string;\n}\n\nconst defaultBuildPayload: PageViewPayloadBuilder = ({ pagePath, url, title }) => {\n const payload: PageViewPayload = {\n page_path: pagePath,\n page_location: url\n };\n\n if (title) {\n payload.page_title = title;\n }\n\n return payload;\n};\n\nconst buildUrl = (pagePath: string, hash: string): string => {\n const origin = typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '';\n if (!origin) {\n return `${pagePath}${hash}`;\n }\n\n return `${origin}${pagePath}${hash}`;\n};\n\nconst sanitizeHash = (hash: string): string => (hash && hash.startsWith('#') ? hash : hash ? `#${hash}` : '');\n\nexport const useTrackPageViews = ({\n client,\n eventName = DEFAULT_EVENT_NAME,\n buildPayload = defaultBuildPayload,\n includeSearchParams = true,\n trackHash = false,\n trackOnMount = true,\n skipSamePath = true,\n pushEventFn = pushEvent,\n waitForReady = false,\n readyPromise\n}: UseTrackPageViewsOptions): void => {\n if (!client) {\n throw new Error('A GTM client is required to track page views.');\n }\n\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n const search = useMemo(() => {\n if (!includeSearchParams || !searchParams) {\n return '';\n }\n\n return searchParams.toString();\n }, [includeSearchParams, searchParams]);\n\n const previousRef = useRef<RouteSnapshot | null>(null);\n const hasTrackedRef = useRef(false);\n const pendingKeyRef = useRef<string | null>(null);\n const readinessRef = useRef<Promise<ScriptLoadState[]> | null>(\n waitForReady ? (readyPromise ?? client.whenReady()) : null\n );\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n readinessRef.current = waitForReady ? (readyPromise ?? client.whenReady()) : null;\n }, [client, readyPromise, waitForReady]);\n\n const logFailures = useCallback((states: ScriptLoadState[]) => {\n const failed = states.filter((state) => state.status === 'failed');\n if (!failed.length) {\n return;\n }\n\n const details = failed.map((state) => state.containerId).join(', ');\n // eslint-disable-next-line no-console\n console.error(`[react-gtm-kit] Failed to load GTM container script(s): ${details}`, failed);\n }, []);\n\n const handleRouteChange = useCallback(\n (nextPathname: string | null, searchValue: string, rawHash: string) => {\n if (!nextPathname) {\n return;\n }\n\n const normalizedHash = trackHash ? sanitizeHash(rawHash) : '';\n const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;\n const key = `${pagePath}${normalizedHash}`;\n const url = buildUrl(pagePath, normalizedHash);\n\n if (!trackOnMount && !hasTrackedRef.current) {\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n return;\n }\n\n if (skipSamePath && previousRef.current && previousRef.current.key === key) {\n return;\n }\n\n const title = typeof document !== 'undefined' ? document.title : undefined;\n const previous = previousRef.current\n ? {\n pathname: previousRef.current.pathname,\n search: previousRef.current.search,\n hash: previousRef.current.hash,\n pagePath: previousRef.current.pagePath,\n url: previousRef.current.url\n }\n : undefined;\n\n const details: RouteChangeEventDetails = {\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url,\n title,\n previous\n };\n\n const pushPayload = (): void => {\n const payload = buildPayload(details);\n pushEventFn(client, eventName, payload);\n\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n };\n\n if (waitForReady && readinessRef.current) {\n pendingKeyRef.current = key;\n readinessRef.current\n .then((states) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n\n logFailures(states);\n pushPayload();\n })\n .catch((error) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n // eslint-disable-next-line no-console\n console.error('[react-gtm-kit] Error while waiting for GTM readiness.', error);\n pushPayload();\n });\n\n return;\n }\n\n pushPayload();\n },\n [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]\n );\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const hash = trackHash ? (window.location.hash ?? '') : '';\n handleRouteChange(pathname, search, hash);\n }, [handleRouteChange, pathname, search, trackHash]);\n\n useEffect(() => {\n if (!trackHash || typeof window === 'undefined') {\n return;\n }\n\n const listener = (): void => {\n handleRouteChange(pathname, search, window.location.hash ?? '');\n };\n\n window.addEventListener('hashchange', listener);\n return () => {\n window.removeEventListener('hashchange', listener);\n };\n }, [handleRouteChange, pathname, search, trackHash]);\n};\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\nimport { buildScriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmHeadScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nconst DEFAULT_ASYNC = true;\n\nexport const GtmHeadScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmHeadScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render script tags.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM script tags.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n const scriptProps: React.ScriptHTMLAttributes<HTMLScriptElement> = {\n src,\n async: asyncAttr ?? DEFAULT_ASYNC\n };\n\n if (defer !== undefined) {\n scriptProps.defer = defer;\n }\n\n if (nonce) {\n scriptProps.nonce = nonce;\n }\n\n for (const [attribute, value] of Object.entries(restAttributes)) {\n if (attribute === 'async' || attribute === 'defer' || attribute === 'nonce' || attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n (scriptProps as Record<string, unknown>)[attribute] = value;\n }\n\n return <script key={container.id} data-gtm-container-id={container.id} {...scriptProps} />;\n })}\n </>\n );\n};\n","import { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ContainerDescriptor } from '@jwiedeman/gtm-kit';\n\nexport const DEFAULT_GTM_HOST = 'https://www.googletagmanager.com';\n\nconst isString = (value: unknown): value is string => typeof value === 'string';\n\nexport const normalizeContainer = (input: ContainerConfigInput): ContainerDescriptor => {\n if (isString(input)) {\n return { id: input };\n }\n\n return input;\n};\n\nexport const normalizeContainers = (\n containers: ContainerConfigInput | ContainerConfigInput[]\n): ContainerDescriptor[] => {\n if (Array.isArray(containers)) {\n return containers.map(normalizeContainer);\n }\n\n return [normalizeContainer(containers)];\n};\n\nconst toRecord = (params?: Record<string, string | number | boolean>): Record<string, string> => {\n if (!params) {\n return {};\n }\n\n return Object.entries(params).reduce<Record<string, string>>((acc, [key, value]) => {\n acc[key] = String(value);\n return acc;\n }, {});\n};\n\nconst normalizeHost = (host: string): string => (host.endsWith('/') ? host.slice(0, -1) : host);\n\nconst buildUrl = (\n kind: 'gtm' | 'ns',\n host: string,\n containerId: string,\n queryParams?: Record<string, string | number | boolean>,\n dataLayerName: string = DEFAULT_DATA_LAYER_NAME\n): string => {\n const normalizedHost = normalizeHost(host);\n const searchParams = new URLSearchParams({ id: containerId });\n\n const params = toRecord(queryParams);\n if (dataLayerName !== DEFAULT_DATA_LAYER_NAME && params.l === undefined) {\n params.l = dataLayerName;\n }\n\n for (const [key, value] of Object.entries(params)) {\n if (key === 'id') {\n continue;\n }\n searchParams.set(key, value);\n }\n\n const suffix = kind === 'gtm' ? 'gtm.js' : 'ns.html';\n return `${normalizedHost}/${suffix}?${searchParams.toString()}`;\n};\n\nexport const buildScriptUrl = (\n host: string,\n containerId: string,\n queryParams?: Record<string, string | number | boolean>,\n dataLayerName: string = DEFAULT_DATA_LAYER_NAME\n): string => buildUrl('gtm', host, containerId, queryParams, dataLayerName);\n\nexport const buildNoscriptUrl = (\n host: string,\n containerId: string,\n queryParams?: Record<string, string | number | boolean>,\n dataLayerName: string = DEFAULT_DATA_LAYER_NAME\n): string => buildUrl('ns', host, containerId, queryParams, dataLayerName);\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput } from '@jwiedeman/gtm-kit';\nimport { DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';\nimport { buildNoscriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmNoScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n iframeAttributes?: Record<string, string | number | boolean>;\n dataLayerName?: string;\n}\n\nconst toStringValue = (value: string | number | boolean): string => String(value);\n\nconst parseStyle = (style: string): React.CSSProperties => {\n return style\n .split(';')\n .map((chunk) => chunk.trim())\n .filter(Boolean)\n .reduce<React.CSSProperties>((acc, declaration) => {\n const [property, rawValue] = declaration.split(':');\n if (!property || rawValue === undefined) {\n return acc;\n }\n\n const key = property.trim().replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());\n const value = rawValue.trim();\n if (!key || !value) {\n return acc;\n }\n\n (acc as Record<string, string>)[key] = value;\n return acc;\n }, {});\n};\n\nexport const GtmNoScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmNoScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render noscript markup.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM noscript markup.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildNoscriptUrl(host, container.id, params, dataLayerName);\n const attributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n const iframeProps: React.IframeHTMLAttributes<HTMLIFrameElement> = {\n src\n };\n\n for (const [attribute, value] of Object.entries(attributes)) {\n if (attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n if (attribute === 'style') {\n if (typeof value === 'string') {\n iframeProps.style = parseStyle(value);\n } else if (typeof value === 'object') {\n iframeProps.style = value as React.CSSProperties;\n }\n continue;\n }\n\n (iframeProps as Record<string, unknown>)[attribute] = toStringValue(value as string | number | boolean);\n }\n\n return (\n <noscript key={container.id}>\n <iframe {...iframeProps} />\n </noscript>\n );\n })}\n </>\n );\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx"],"names":["useCallback","useEffect","useMemo","useRef","usePathname","useSearchParams","pushEvent","DEFAULT_EVENT_NAME","defaultBuildPayload","pagePath","url","title","payload","buildUrl","hash","_a","origin","sanitizeHash","useTrackPageViews","client","eventName","buildPayload","includeSearchParams","trackHash","trackOnMount","skipSamePath","pushEventFn","waitForReady","readyPromise","pathname","searchParams","search","previousRef","hasTrackedRef","pendingKeyRef","readinessRef","isMountedRef","logFailures","states","failed","state","details","handleRouteChange","nextPathname","searchValue","rawHash","normalizedHash","key","previous","pushPayload","error","listener","DEFAULT_DATA_LAYER_NAME","DEFAULT_GTM_HOST","normalizeContainer","normalizeContainers","buildGtmScriptUrl","buildGtmNoscriptUrl","Fragment","jsx","DEFAULT_ASYNC","GtmHeadScript","containers","host","defaultQueryParams","scriptAttributes","dataLayerName","normalized","container","params","src","asyncAttr","defer","nonce","restAttributes","scriptProps","attribute","value","DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES","toStringValue","parseStyle","style","chunk","acc","declaration","property","rawValue","_","char","GtmNoScript","iframeAttributes","attributes","iframeProps"],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport type { GtmClient, PageViewPayload, ScriptLoadState } from '@jwiedeman/gtm-kit';\nimport { pushEvent } from '@jwiedeman/gtm-kit';\n\nconst DEFAULT_EVENT_NAME = 'page_view';\n\nexport interface RouteLocationSnapshot {\n pathname: string;\n search: string;\n hash: string;\n pagePath: string;\n url: string;\n}\n\nexport interface RouteChangeEventDetails extends RouteLocationSnapshot {\n title?: string;\n previous?: RouteLocationSnapshot;\n}\n\nexport type PageViewPayloadBuilder = (details: RouteChangeEventDetails) => PageViewPayload;\n\nexport interface UseTrackPageViewsOptions {\n client: Pick<GtmClient, 'push' | 'whenReady'>;\n eventName?: string;\n buildPayload?: PageViewPayloadBuilder;\n includeSearchParams?: boolean;\n trackHash?: boolean;\n trackOnMount?: boolean;\n skipSamePath?: boolean;\n pushEventFn?: typeof pushEvent;\n waitForReady?: boolean;\n readyPromise?: Promise<ScriptLoadState[]>;\n}\n\ninterface RouteSnapshot extends RouteLocationSnapshot {\n key: string;\n}\n\nconst defaultBuildPayload: PageViewPayloadBuilder = ({ pagePath, url, title }) => {\n const payload: PageViewPayload = {\n page_path: pagePath,\n page_location: url\n };\n\n if (title) {\n payload.page_title = title;\n }\n\n return payload;\n};\n\nconst buildUrl = (pagePath: string, hash: string): string => {\n const origin = typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '';\n if (!origin) {\n return `${pagePath}${hash}`;\n }\n\n return `${origin}${pagePath}${hash}`;\n};\n\nconst sanitizeHash = (hash: string): string => (hash && hash.startsWith('#') ? hash : hash ? `#${hash}` : '');\n\nexport const useTrackPageViews = ({\n client,\n eventName = DEFAULT_EVENT_NAME,\n buildPayload = defaultBuildPayload,\n includeSearchParams = true,\n trackHash = false,\n trackOnMount = true,\n skipSamePath = true,\n pushEventFn = pushEvent,\n waitForReady = false,\n readyPromise\n}: UseTrackPageViewsOptions): void => {\n if (!client) {\n throw new Error('A GTM client is required to track page views.');\n }\n\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n const search = useMemo(() => {\n if (!includeSearchParams || !searchParams) {\n return '';\n }\n\n return searchParams.toString();\n }, [includeSearchParams, searchParams]);\n\n const previousRef = useRef<RouteSnapshot | null>(null);\n const hasTrackedRef = useRef(false);\n const pendingKeyRef = useRef<string | null>(null);\n const readinessRef = useRef<Promise<ScriptLoadState[]> | null>(\n waitForReady ? (readyPromise ?? client.whenReady()) : null\n );\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n readinessRef.current = waitForReady ? (readyPromise ?? client.whenReady()) : null;\n }, [client, readyPromise, waitForReady]);\n\n const logFailures = useCallback((states: ScriptLoadState[]) => {\n const failed = states.filter((state) => state.status === 'failed');\n if (!failed.length) {\n return;\n }\n\n const details = failed.map((state) => state.containerId).join(', ');\n // eslint-disable-next-line no-console\n console.error(`[react-gtm-kit] Failed to load GTM container script(s): ${details}`, failed);\n }, []);\n\n const handleRouteChange = useCallback(\n (nextPathname: string | null, searchValue: string, rawHash: string) => {\n if (!nextPathname) {\n return;\n }\n\n const normalizedHash = trackHash ? sanitizeHash(rawHash) : '';\n const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;\n const key = `${pagePath}${normalizedHash}`;\n const url = buildUrl(pagePath, normalizedHash);\n\n if (!trackOnMount && !hasTrackedRef.current) {\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n return;\n }\n\n if (skipSamePath && previousRef.current && previousRef.current.key === key) {\n return;\n }\n\n const title = typeof document !== 'undefined' ? document.title : undefined;\n const previous = previousRef.current\n ? {\n pathname: previousRef.current.pathname,\n search: previousRef.current.search,\n hash: previousRef.current.hash,\n pagePath: previousRef.current.pagePath,\n url: previousRef.current.url\n }\n : undefined;\n\n const details: RouteChangeEventDetails = {\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url,\n title,\n previous\n };\n\n const pushPayload = (): void => {\n const payload = buildPayload(details);\n pushEventFn(client, eventName, payload);\n\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n };\n\n if (waitForReady && readinessRef.current) {\n pendingKeyRef.current = key;\n readinessRef.current\n .then((states) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n\n logFailures(states);\n pushPayload();\n })\n .catch((error) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n // eslint-disable-next-line no-console\n console.error('[react-gtm-kit] Error while waiting for GTM readiness.', error);\n pushPayload();\n });\n\n return;\n }\n\n pushPayload();\n },\n [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]\n );\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const hash = trackHash ? (window.location.hash ?? '') : '';\n handleRouteChange(pathname, search, hash);\n }, [handleRouteChange, pathname, search, trackHash]);\n\n useEffect(() => {\n if (!trackHash || typeof window === 'undefined') {\n return;\n }\n\n const listener = (): void => {\n handleRouteChange(pathname, search, window.location.hash ?? '');\n };\n\n window.addEventListener('hashchange', listener);\n return () => {\n window.removeEventListener('hashchange', listener);\n };\n }, [handleRouteChange, pathname, search, trackHash]);\n};\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\nimport { buildScriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmHeadScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nconst DEFAULT_ASYNC = true;\n\nexport const GtmHeadScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmHeadScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render script tags.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM script tags.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n const scriptProps: React.ScriptHTMLAttributes<HTMLScriptElement> = {\n src,\n async: asyncAttr ?? DEFAULT_ASYNC\n };\n\n if (defer !== undefined) {\n scriptProps.defer = defer;\n }\n\n if (nonce) {\n scriptProps.nonce = nonce;\n }\n\n for (const [attribute, value] of Object.entries(restAttributes)) {\n if (attribute === 'async' || attribute === 'defer' || attribute === 'nonce' || attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n (scriptProps as Record<string, unknown>)[attribute] = value;\n }\n\n return <script key={container.id} data-gtm-container-id={container.id} {...scriptProps} />;\n })}\n </>\n );\n};\n","// Re-export URL utilities from core for internal use\nexport {\n DEFAULT_GTM_HOST,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n} from '@jwiedeman/gtm-kit';\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput } from '@jwiedeman/gtm-kit';\nimport { DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';\nimport { buildNoscriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmNoScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n iframeAttributes?: Record<string, string | number | boolean>;\n dataLayerName?: string;\n}\n\nconst toStringValue = (value: string | number | boolean): string => String(value);\n\nconst parseStyle = (style: string): React.CSSProperties => {\n return style\n .split(';')\n .map((chunk) => chunk.trim())\n .filter(Boolean)\n .reduce<React.CSSProperties>((acc, declaration) => {\n const [property, rawValue] = declaration.split(':');\n if (!property || rawValue === undefined) {\n return acc;\n }\n\n const key = property.trim().replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());\n const value = rawValue.trim();\n if (!key || !value) {\n return acc;\n }\n\n (acc as Record<string, string>)[key] = value;\n return acc;\n }, {});\n};\n\nexport const GtmNoScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmNoScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render noscript markup.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM noscript markup.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildNoscriptUrl(host, container.id, params, dataLayerName);\n const attributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n const iframeProps: React.IframeHTMLAttributes<HTMLIFrameElement> = {\n src\n };\n\n for (const [attribute, value] of Object.entries(attributes)) {\n if (attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n if (attribute === 'style') {\n if (typeof value === 'string') {\n iframeProps.style = parseStyle(value);\n } else if (typeof value === 'object') {\n iframeProps.style = value as React.CSSProperties;\n }\n continue;\n }\n\n (iframeProps as Record<string, unknown>)[attribute] = toStringValue(value as string | number | boolean);\n }\n\n return (\n <noscript key={container.id}>\n <iframe {...iframeProps} />\n </noscript>\n );\n })}\n </>\n );\n};\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useMemo, useRef, useEffect, useCallback } from 'react';
|
|
2
2
|
import { usePathname, useSearchParams } from 'next/navigation';
|
|
3
|
-
import { pushEvent, DEFAULT_DATA_LAYER_NAME, DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';
|
|
3
|
+
import { pushEvent, normalizeContainers, buildGtmScriptUrl, DEFAULT_GTM_HOST, DEFAULT_DATA_LAYER_NAME, buildGtmNoscriptUrl, DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';
|
|
4
4
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var B="page_view",j=({pagePath:t,url:e,title:a})=>{let r={page_path:t,page_location:e};return a&&(r.page_title=a),r},Y=(t,e)=>{var r;let a=typeof window!="undefined"&&((r=window.location)!=null&&r.origin)?window.location.origin:"";return a?`${a}${t}${e}`:`${t}${e}`},Q=t=>t&&t.startsWith("#")?t:t?`#${t}`:"",K=({client:t,eventName:e=B,buildPayload:a=j,includeSearchParams:r=!0,trackHash:c=!1,trackOnMount:d=!0,skipSamePath:n=!0,pushEventFn:w=pushEvent,waitForReady:f=!1,readyPromise:u})=>{if(!t)throw new Error("A GTM client is required to track page views.");let l=usePathname(),m=useSearchParams(),s=useMemo(()=>!r||!m?"":m.toString(),[r,m]),o=useRef(null),g=useRef(!1),S=useRef(null),G=useRef(f?u!=null?u:t.whenReady():null),b=useRef(!0);useEffect(()=>(b.current=!0,()=>{b.current=!1;}),[]),useEffect(()=>{G.current=f?u!=null?u:t.whenReady():null;},[t,u,f]);let U=useCallback(p=>{let i=p.filter(h=>h.status==="failed");if(!i.length)return;let _=i.map(h=>h.containerId).join(", ");console.error(`[react-gtm-kit] Failed to load GTM container script(s): ${_}`,i);},[]),C=useCallback((p,i,_)=>{if(!p)return;let h=c?Q(_):"",y=i?`${p}?${i}`:p,R=`${y}${h}`,k=Y(y,h);if(!d&&!g.current){o.current={key:R,pathname:p,search:i,hash:h,pagePath:y,url:k},g.current=!0;return}if(n&&o.current&&o.current.key===R)return;let V=typeof document!="undefined"?document.title:void 0,$=o.current?{pathname:o.current.pathname,search:o.current.search,hash:o.current.hash,pagePath:o.current.pagePath,url:o.current.url}:void 0,H={pathname:p,search:i,hash:h,pagePath:y,url:k,title:V,previous:$},v=()=>{let T=a(H);w(t,e,T),o.current={key:R,pathname:p,search:i,hash:h,pagePath:y,url:k},g.current=!0;};if(f&&G.current){S.current=R,G.current.then(T=>{!b.current||S.current!==R||(U(T),v());}).catch(T=>{!b.current||S.current!==R||(console.error("[react-gtm-kit] Error while waiting for GTM readiness.",T),v());});return}v();},[a,t,e,U,w,n,c,d,f]);useEffect(()=>{var i;if(typeof window=="undefined")return;let p=c&&(i=window.location.hash)!=null?i:"";C(l,s,p);},[C,l,s,c]),useEffect(()=>{if(!c||typeof window=="undefined")return;let p=()=>{var i;C(l,s,(i=window.location.hash)!=null?i:"");};return window.addEventListener("hashchange",p),()=>{window.removeEventListener("hashchange",p);}},[C,l,s,c]);};var J=!0,X=({containers:t,host:e=DEFAULT_GTM_HOST,defaultQueryParams:a,scriptAttributes:r,dataLayerName:c=DEFAULT_DATA_LAYER_NAME})=>{let d=normalizeContainers(t);if(!d.length)throw new Error("At least one GTM container is required to render script tags.");return jsx(Fragment,{children:d.map(n=>{if(!n.id)throw new Error("Container id is required to render GTM script tags.");let w={...a,...n.queryParams},f=buildGtmScriptUrl(e,n.id,w,c),{async:u,defer:l,nonce:m,...s}=r!=null?r:{},o={src:f,async:u!=null?u:J};l!==void 0&&(o.defer=l),m&&(o.nonce=m);for(let[g,S]of Object.entries(s))g==="async"||g==="defer"||g==="nonce"||g==="src"||S!=null&&(o[g]=S);return jsx("script",{"data-gtm-container-id":n.id,...o},n.id)})})};var rt=t=>String(t),nt=t=>t.split(";").map(e=>e.trim()).filter(Boolean).reduce((e,a)=>{let[r,c]=a.split(":");if(!r||c===void 0)return e;let d=r.trim().replace(/-([a-z])/g,(w,f)=>f.toUpperCase()),n=c.trim();return !d||!n||(e[d]=n),e},{}),ot=({containers:t,host:e=DEFAULT_GTM_HOST,defaultQueryParams:a,iframeAttributes:r,dataLayerName:c=DEFAULT_DATA_LAYER_NAME})=>{let d=normalizeContainers(t);if(!d.length)throw new Error("At least one GTM container is required to render noscript markup.");return jsx(Fragment,{children:d.map(n=>{if(!n.id)throw new Error("Container id is required to render GTM noscript markup.");let w={...a,...n.queryParams},f=buildGtmNoscriptUrl(e,n.id,w,c),u={...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,...r},l={src:f};for(let[m,s]of Object.entries(u))if(m!=="src"&&s!=null){if(m==="style"){typeof s=="string"?l.style=nt(s):typeof s=="object"&&(l.style=s);continue}l[m]=rt(s);}return jsx("noscript",{children:jsx("iframe",{...l})},n.id)})})};
|
|
7
7
|
|
|
8
|
-
export {
|
|
8
|
+
export { X as GtmHeadScript, ot as GtmNoScript, K as useTrackPageViews };
|
|
9
9
|
//# sourceMappingURL=out.js.map
|
|
10
10
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx"],"names":["useCallback","useEffect","useMemo","useRef","usePathname","useSearchParams","pushEvent","DEFAULT_EVENT_NAME","defaultBuildPayload","pagePath","url","title","payload","buildUrl","hash","_a","origin","sanitizeHash","useTrackPageViews","client","eventName","buildPayload","includeSearchParams","trackHash","trackOnMount","skipSamePath","pushEventFn","waitForReady","readyPromise","pathname","searchParams","search","previousRef","hasTrackedRef","pendingKeyRef","readinessRef","isMountedRef","logFailures","states","failed","state","details","handleRouteChange","nextPathname","searchValue","rawHash","normalizedHash","key","previous","pushPayload","error","listener","DEFAULT_DATA_LAYER_NAME","DEFAULT_GTM_HOST","isString","value","normalizeContainer","input","normalizeContainers","containers","toRecord","params","acc","normalizeHost","host","kind","containerId","queryParams","dataLayerName","normalizedHost","buildScriptUrl","buildNoscriptUrl","Fragment","jsx","DEFAULT_ASYNC","GtmHeadScript","defaultQueryParams","scriptAttributes","normalized","container","src","asyncAttr","defer","nonce","restAttributes","scriptProps","attribute","DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES","toStringValue","parseStyle","style","chunk","declaration","property","rawValue","_","char","GtmNoScript","iframeAttributes","attributes","iframeProps"],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport type { GtmClient, PageViewPayload, ScriptLoadState } from '@jwiedeman/gtm-kit';\nimport { pushEvent } from '@jwiedeman/gtm-kit';\n\nconst DEFAULT_EVENT_NAME = 'page_view';\n\nexport interface RouteLocationSnapshot {\n pathname: string;\n search: string;\n hash: string;\n pagePath: string;\n url: string;\n}\n\nexport interface RouteChangeEventDetails extends RouteLocationSnapshot {\n title?: string;\n previous?: RouteLocationSnapshot;\n}\n\nexport type PageViewPayloadBuilder = (details: RouteChangeEventDetails) => PageViewPayload;\n\nexport interface UseTrackPageViewsOptions {\n client: Pick<GtmClient, 'push' | 'whenReady'>;\n eventName?: string;\n buildPayload?: PageViewPayloadBuilder;\n includeSearchParams?: boolean;\n trackHash?: boolean;\n trackOnMount?: boolean;\n skipSamePath?: boolean;\n pushEventFn?: typeof pushEvent;\n waitForReady?: boolean;\n readyPromise?: Promise<ScriptLoadState[]>;\n}\n\ninterface RouteSnapshot extends RouteLocationSnapshot {\n key: string;\n}\n\nconst defaultBuildPayload: PageViewPayloadBuilder = ({ pagePath, url, title }) => {\n const payload: PageViewPayload = {\n page_path: pagePath,\n page_location: url\n };\n\n if (title) {\n payload.page_title = title;\n }\n\n return payload;\n};\n\nconst buildUrl = (pagePath: string, hash: string): string => {\n const origin = typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '';\n if (!origin) {\n return `${pagePath}${hash}`;\n }\n\n return `${origin}${pagePath}${hash}`;\n};\n\nconst sanitizeHash = (hash: string): string => (hash && hash.startsWith('#') ? hash : hash ? `#${hash}` : '');\n\nexport const useTrackPageViews = ({\n client,\n eventName = DEFAULT_EVENT_NAME,\n buildPayload = defaultBuildPayload,\n includeSearchParams = true,\n trackHash = false,\n trackOnMount = true,\n skipSamePath = true,\n pushEventFn = pushEvent,\n waitForReady = false,\n readyPromise\n}: UseTrackPageViewsOptions): void => {\n if (!client) {\n throw new Error('A GTM client is required to track page views.');\n }\n\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n const search = useMemo(() => {\n if (!includeSearchParams || !searchParams) {\n return '';\n }\n\n return searchParams.toString();\n }, [includeSearchParams, searchParams]);\n\n const previousRef = useRef<RouteSnapshot | null>(null);\n const hasTrackedRef = useRef(false);\n const pendingKeyRef = useRef<string | null>(null);\n const readinessRef = useRef<Promise<ScriptLoadState[]> | null>(\n waitForReady ? (readyPromise ?? client.whenReady()) : null\n );\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n readinessRef.current = waitForReady ? (readyPromise ?? client.whenReady()) : null;\n }, [client, readyPromise, waitForReady]);\n\n const logFailures = useCallback((states: ScriptLoadState[]) => {\n const failed = states.filter((state) => state.status === 'failed');\n if (!failed.length) {\n return;\n }\n\n const details = failed.map((state) => state.containerId).join(', ');\n // eslint-disable-next-line no-console\n console.error(`[react-gtm-kit] Failed to load GTM container script(s): ${details}`, failed);\n }, []);\n\n const handleRouteChange = useCallback(\n (nextPathname: string | null, searchValue: string, rawHash: string) => {\n if (!nextPathname) {\n return;\n }\n\n const normalizedHash = trackHash ? sanitizeHash(rawHash) : '';\n const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;\n const key = `${pagePath}${normalizedHash}`;\n const url = buildUrl(pagePath, normalizedHash);\n\n if (!trackOnMount && !hasTrackedRef.current) {\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n return;\n }\n\n if (skipSamePath && previousRef.current && previousRef.current.key === key) {\n return;\n }\n\n const title = typeof document !== 'undefined' ? document.title : undefined;\n const previous = previousRef.current\n ? {\n pathname: previousRef.current.pathname,\n search: previousRef.current.search,\n hash: previousRef.current.hash,\n pagePath: previousRef.current.pagePath,\n url: previousRef.current.url\n }\n : undefined;\n\n const details: RouteChangeEventDetails = {\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url,\n title,\n previous\n };\n\n const pushPayload = (): void => {\n const payload = buildPayload(details);\n pushEventFn(client, eventName, payload);\n\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n };\n\n if (waitForReady && readinessRef.current) {\n pendingKeyRef.current = key;\n readinessRef.current\n .then((states) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n\n logFailures(states);\n pushPayload();\n })\n .catch((error) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n // eslint-disable-next-line no-console\n console.error('[react-gtm-kit] Error while waiting for GTM readiness.', error);\n pushPayload();\n });\n\n return;\n }\n\n pushPayload();\n },\n [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]\n );\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const hash = trackHash ? (window.location.hash ?? '') : '';\n handleRouteChange(pathname, search, hash);\n }, [handleRouteChange, pathname, search, trackHash]);\n\n useEffect(() => {\n if (!trackHash || typeof window === 'undefined') {\n return;\n }\n\n const listener = (): void => {\n handleRouteChange(pathname, search, window.location.hash ?? '');\n };\n\n window.addEventListener('hashchange', listener);\n return () => {\n window.removeEventListener('hashchange', listener);\n };\n }, [handleRouteChange, pathname, search, trackHash]);\n};\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\nimport { buildScriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmHeadScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nconst DEFAULT_ASYNC = true;\n\nexport const GtmHeadScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmHeadScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render script tags.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM script tags.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n const scriptProps: React.ScriptHTMLAttributes<HTMLScriptElement> = {\n src,\n async: asyncAttr ?? DEFAULT_ASYNC\n };\n\n if (defer !== undefined) {\n scriptProps.defer = defer;\n }\n\n if (nonce) {\n scriptProps.nonce = nonce;\n }\n\n for (const [attribute, value] of Object.entries(restAttributes)) {\n if (attribute === 'async' || attribute === 'defer' || attribute === 'nonce' || attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n (scriptProps as Record<string, unknown>)[attribute] = value;\n }\n\n return <script key={container.id} data-gtm-container-id={container.id} {...scriptProps} />;\n })}\n </>\n );\n};\n","import { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ContainerDescriptor } from '@jwiedeman/gtm-kit';\n\nexport const DEFAULT_GTM_HOST = 'https://www.googletagmanager.com';\n\nconst isString = (value: unknown): value is string => typeof value === 'string';\n\nexport const normalizeContainer = (input: ContainerConfigInput): ContainerDescriptor => {\n if (isString(input)) {\n return { id: input };\n }\n\n return input;\n};\n\nexport const normalizeContainers = (\n containers: ContainerConfigInput | ContainerConfigInput[]\n): ContainerDescriptor[] => {\n if (Array.isArray(containers)) {\n return containers.map(normalizeContainer);\n }\n\n return [normalizeContainer(containers)];\n};\n\nconst toRecord = (params?: Record<string, string | number | boolean>): Record<string, string> => {\n if (!params) {\n return {};\n }\n\n return Object.entries(params).reduce<Record<string, string>>((acc, [key, value]) => {\n acc[key] = String(value);\n return acc;\n }, {});\n};\n\nconst normalizeHost = (host: string): string => (host.endsWith('/') ? host.slice(0, -1) : host);\n\nconst buildUrl = (\n kind: 'gtm' | 'ns',\n host: string,\n containerId: string,\n queryParams?: Record<string, string | number | boolean>,\n dataLayerName: string = DEFAULT_DATA_LAYER_NAME\n): string => {\n const normalizedHost = normalizeHost(host);\n const searchParams = new URLSearchParams({ id: containerId });\n\n const params = toRecord(queryParams);\n if (dataLayerName !== DEFAULT_DATA_LAYER_NAME && params.l === undefined) {\n params.l = dataLayerName;\n }\n\n for (const [key, value] of Object.entries(params)) {\n if (key === 'id') {\n continue;\n }\n searchParams.set(key, value);\n }\n\n const suffix = kind === 'gtm' ? 'gtm.js' : 'ns.html';\n return `${normalizedHost}/${suffix}?${searchParams.toString()}`;\n};\n\nexport const buildScriptUrl = (\n host: string,\n containerId: string,\n queryParams?: Record<string, string | number | boolean>,\n dataLayerName: string = DEFAULT_DATA_LAYER_NAME\n): string => buildUrl('gtm', host, containerId, queryParams, dataLayerName);\n\nexport const buildNoscriptUrl = (\n host: string,\n containerId: string,\n queryParams?: Record<string, string | number | boolean>,\n dataLayerName: string = DEFAULT_DATA_LAYER_NAME\n): string => buildUrl('ns', host, containerId, queryParams, dataLayerName);\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput } from '@jwiedeman/gtm-kit';\nimport { DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';\nimport { buildNoscriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmNoScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n iframeAttributes?: Record<string, string | number | boolean>;\n dataLayerName?: string;\n}\n\nconst toStringValue = (value: string | number | boolean): string => String(value);\n\nconst parseStyle = (style: string): React.CSSProperties => {\n return style\n .split(';')\n .map((chunk) => chunk.trim())\n .filter(Boolean)\n .reduce<React.CSSProperties>((acc, declaration) => {\n const [property, rawValue] = declaration.split(':');\n if (!property || rawValue === undefined) {\n return acc;\n }\n\n const key = property.trim().replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());\n const value = rawValue.trim();\n if (!key || !value) {\n return acc;\n }\n\n (acc as Record<string, string>)[key] = value;\n return acc;\n }, {});\n};\n\nexport const GtmNoScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmNoScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render noscript markup.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM noscript markup.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildNoscriptUrl(host, container.id, params, dataLayerName);\n const attributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n const iframeProps: React.IframeHTMLAttributes<HTMLIFrameElement> = {\n src\n };\n\n for (const [attribute, value] of Object.entries(attributes)) {\n if (attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n if (attribute === 'style') {\n if (typeof value === 'string') {\n iframeProps.style = parseStyle(value);\n } else if (typeof value === 'object') {\n iframeProps.style = value as React.CSSProperties;\n }\n continue;\n }\n\n (iframeProps as Record<string, unknown>)[attribute] = toStringValue(value as string | number | boolean);\n }\n\n return (\n <noscript key={container.id}>\n <iframe {...iframeProps} />\n </noscript>\n );\n })}\n </>\n );\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx"],"names":["useCallback","useEffect","useMemo","useRef","usePathname","useSearchParams","pushEvent","DEFAULT_EVENT_NAME","defaultBuildPayload","pagePath","url","title","payload","buildUrl","hash","_a","origin","sanitizeHash","useTrackPageViews","client","eventName","buildPayload","includeSearchParams","trackHash","trackOnMount","skipSamePath","pushEventFn","waitForReady","readyPromise","pathname","searchParams","search","previousRef","hasTrackedRef","pendingKeyRef","readinessRef","isMountedRef","logFailures","states","failed","state","details","handleRouteChange","nextPathname","searchValue","rawHash","normalizedHash","key","previous","pushPayload","error","listener","DEFAULT_DATA_LAYER_NAME","DEFAULT_GTM_HOST","normalizeContainer","normalizeContainers","buildGtmScriptUrl","buildGtmNoscriptUrl","Fragment","jsx","DEFAULT_ASYNC","GtmHeadScript","containers","host","defaultQueryParams","scriptAttributes","dataLayerName","normalized","container","params","src","asyncAttr","defer","nonce","restAttributes","scriptProps","attribute","value","DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES","toStringValue","parseStyle","style","chunk","acc","declaration","property","rawValue","_","char","GtmNoScript","iframeAttributes","attributes","iframeProps"],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport type { GtmClient, PageViewPayload, ScriptLoadState } from '@jwiedeman/gtm-kit';\nimport { pushEvent } from '@jwiedeman/gtm-kit';\n\nconst DEFAULT_EVENT_NAME = 'page_view';\n\nexport interface RouteLocationSnapshot {\n pathname: string;\n search: string;\n hash: string;\n pagePath: string;\n url: string;\n}\n\nexport interface RouteChangeEventDetails extends RouteLocationSnapshot {\n title?: string;\n previous?: RouteLocationSnapshot;\n}\n\nexport type PageViewPayloadBuilder = (details: RouteChangeEventDetails) => PageViewPayload;\n\nexport interface UseTrackPageViewsOptions {\n client: Pick<GtmClient, 'push' | 'whenReady'>;\n eventName?: string;\n buildPayload?: PageViewPayloadBuilder;\n includeSearchParams?: boolean;\n trackHash?: boolean;\n trackOnMount?: boolean;\n skipSamePath?: boolean;\n pushEventFn?: typeof pushEvent;\n waitForReady?: boolean;\n readyPromise?: Promise<ScriptLoadState[]>;\n}\n\ninterface RouteSnapshot extends RouteLocationSnapshot {\n key: string;\n}\n\nconst defaultBuildPayload: PageViewPayloadBuilder = ({ pagePath, url, title }) => {\n const payload: PageViewPayload = {\n page_path: pagePath,\n page_location: url\n };\n\n if (title) {\n payload.page_title = title;\n }\n\n return payload;\n};\n\nconst buildUrl = (pagePath: string, hash: string): string => {\n const origin = typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '';\n if (!origin) {\n return `${pagePath}${hash}`;\n }\n\n return `${origin}${pagePath}${hash}`;\n};\n\nconst sanitizeHash = (hash: string): string => (hash && hash.startsWith('#') ? hash : hash ? `#${hash}` : '');\n\nexport const useTrackPageViews = ({\n client,\n eventName = DEFAULT_EVENT_NAME,\n buildPayload = defaultBuildPayload,\n includeSearchParams = true,\n trackHash = false,\n trackOnMount = true,\n skipSamePath = true,\n pushEventFn = pushEvent,\n waitForReady = false,\n readyPromise\n}: UseTrackPageViewsOptions): void => {\n if (!client) {\n throw new Error('A GTM client is required to track page views.');\n }\n\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n const search = useMemo(() => {\n if (!includeSearchParams || !searchParams) {\n return '';\n }\n\n return searchParams.toString();\n }, [includeSearchParams, searchParams]);\n\n const previousRef = useRef<RouteSnapshot | null>(null);\n const hasTrackedRef = useRef(false);\n const pendingKeyRef = useRef<string | null>(null);\n const readinessRef = useRef<Promise<ScriptLoadState[]> | null>(\n waitForReady ? (readyPromise ?? client.whenReady()) : null\n );\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n readinessRef.current = waitForReady ? (readyPromise ?? client.whenReady()) : null;\n }, [client, readyPromise, waitForReady]);\n\n const logFailures = useCallback((states: ScriptLoadState[]) => {\n const failed = states.filter((state) => state.status === 'failed');\n if (!failed.length) {\n return;\n }\n\n const details = failed.map((state) => state.containerId).join(', ');\n // eslint-disable-next-line no-console\n console.error(`[react-gtm-kit] Failed to load GTM container script(s): ${details}`, failed);\n }, []);\n\n const handleRouteChange = useCallback(\n (nextPathname: string | null, searchValue: string, rawHash: string) => {\n if (!nextPathname) {\n return;\n }\n\n const normalizedHash = trackHash ? sanitizeHash(rawHash) : '';\n const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;\n const key = `${pagePath}${normalizedHash}`;\n const url = buildUrl(pagePath, normalizedHash);\n\n if (!trackOnMount && !hasTrackedRef.current) {\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n return;\n }\n\n if (skipSamePath && previousRef.current && previousRef.current.key === key) {\n return;\n }\n\n const title = typeof document !== 'undefined' ? document.title : undefined;\n const previous = previousRef.current\n ? {\n pathname: previousRef.current.pathname,\n search: previousRef.current.search,\n hash: previousRef.current.hash,\n pagePath: previousRef.current.pagePath,\n url: previousRef.current.url\n }\n : undefined;\n\n const details: RouteChangeEventDetails = {\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url,\n title,\n previous\n };\n\n const pushPayload = (): void => {\n const payload = buildPayload(details);\n pushEventFn(client, eventName, payload);\n\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n };\n\n if (waitForReady && readinessRef.current) {\n pendingKeyRef.current = key;\n readinessRef.current\n .then((states) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n\n logFailures(states);\n pushPayload();\n })\n .catch((error) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n // eslint-disable-next-line no-console\n console.error('[react-gtm-kit] Error while waiting for GTM readiness.', error);\n pushPayload();\n });\n\n return;\n }\n\n pushPayload();\n },\n [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]\n );\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const hash = trackHash ? (window.location.hash ?? '') : '';\n handleRouteChange(pathname, search, hash);\n }, [handleRouteChange, pathname, search, trackHash]);\n\n useEffect(() => {\n if (!trackHash || typeof window === 'undefined') {\n return;\n }\n\n const listener = (): void => {\n handleRouteChange(pathname, search, window.location.hash ?? '');\n };\n\n window.addEventListener('hashchange', listener);\n return () => {\n window.removeEventListener('hashchange', listener);\n };\n }, [handleRouteChange, pathname, search, trackHash]);\n};\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\nimport { buildScriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmHeadScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nconst DEFAULT_ASYNC = true;\n\nexport const GtmHeadScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmHeadScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render script tags.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM script tags.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n const scriptProps: React.ScriptHTMLAttributes<HTMLScriptElement> = {\n src,\n async: asyncAttr ?? DEFAULT_ASYNC\n };\n\n if (defer !== undefined) {\n scriptProps.defer = defer;\n }\n\n if (nonce) {\n scriptProps.nonce = nonce;\n }\n\n for (const [attribute, value] of Object.entries(restAttributes)) {\n if (attribute === 'async' || attribute === 'defer' || attribute === 'nonce' || attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n (scriptProps as Record<string, unknown>)[attribute] = value;\n }\n\n return <script key={container.id} data-gtm-container-id={container.id} {...scriptProps} />;\n })}\n </>\n );\n};\n","// Re-export URL utilities from core for internal use\nexport {\n DEFAULT_GTM_HOST,\n normalizeContainer,\n normalizeContainers,\n buildGtmScriptUrl as buildScriptUrl,\n buildGtmNoscriptUrl as buildNoscriptUrl\n} from '@jwiedeman/gtm-kit';\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput } from '@jwiedeman/gtm-kit';\nimport { DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';\nimport { buildNoscriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmNoScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n iframeAttributes?: Record<string, string | number | boolean>;\n dataLayerName?: string;\n}\n\nconst toStringValue = (value: string | number | boolean): string => String(value);\n\nconst parseStyle = (style: string): React.CSSProperties => {\n return style\n .split(';')\n .map((chunk) => chunk.trim())\n .filter(Boolean)\n .reduce<React.CSSProperties>((acc, declaration) => {\n const [property, rawValue] = declaration.split(':');\n if (!property || rawValue === undefined) {\n return acc;\n }\n\n const key = property.trim().replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());\n const value = rawValue.trim();\n if (!key || !value) {\n return acc;\n }\n\n (acc as Record<string, string>)[key] = value;\n return acc;\n }, {});\n};\n\nexport const GtmNoScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmNoScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render noscript markup.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM noscript markup.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildNoscriptUrl(host, container.id, params, dataLayerName);\n const attributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n const iframeProps: React.IframeHTMLAttributes<HTMLIFrameElement> = {\n src\n };\n\n for (const [attribute, value] of Object.entries(attributes)) {\n if (attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n if (attribute === 'style') {\n if (typeof value === 'string') {\n iframeProps.style = parseStyle(value);\n } else if (typeof value === 'object') {\n iframeProps.style = value as React.CSSProperties;\n }\n continue;\n }\n\n (iframeProps as Record<string, unknown>)[attribute] = toStringValue(value as string | number | boolean);\n }\n\n return (\n <noscript key={container.id}>\n <iframe {...iframeProps} />\n </noscript>\n );\n })}\n </>\n );\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jwiedeman/gtm-kit-next",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "Next.js App Router helpers for GTM Kit - Google Tag Manager integration.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"typecheck": "tsc --noEmit"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@jwiedeman/gtm-kit": "^1.1.
|
|
44
|
+
"@jwiedeman/gtm-kit": "^1.1.3"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"next": "^13.4.0 || ^14.0.0 || ^15.0.0",
|