@jwiedeman/gtm-kit-next 1.1.6 → 1.2.0

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/dist/index.cjs CHANGED
@@ -5,10 +5,280 @@ 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 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)})})};
8
+ var DEFAULT_EVENT_NAME = "page_view";
9
+ var defaultBuildPayload = ({ pagePath, url, title }) => {
10
+ const payload = {
11
+ page_path: pagePath,
12
+ page_location: url
13
+ };
14
+ if (title) {
15
+ payload.page_title = title;
16
+ }
17
+ return payload;
18
+ };
19
+ var buildUrl = (pagePath, hash) => {
20
+ var _a;
21
+ const origin = typeof window !== "undefined" && ((_a = window.location) == null ? void 0 : _a.origin) ? window.location.origin : "";
22
+ if (!origin) {
23
+ return `${pagePath}${hash}`;
24
+ }
25
+ return `${origin}${pagePath}${hash}`;
26
+ };
27
+ var sanitizeHash = (hash) => hash && hash.startsWith("#") ? hash : hash ? `#${hash}` : "";
28
+ var useTrackPageViews = ({
29
+ client,
30
+ eventName = DEFAULT_EVENT_NAME,
31
+ buildPayload = defaultBuildPayload,
32
+ includeSearchParams = true,
33
+ trackHash = false,
34
+ trackOnMount = true,
35
+ skipSamePath = true,
36
+ pushEventFn = gtmKit.pushEvent,
37
+ waitForReady = false,
38
+ readyPromise
39
+ }) => {
40
+ if (!client) {
41
+ throw new Error("A GTM client is required to track page views.");
42
+ }
43
+ const pathname = navigation.usePathname();
44
+ const searchParams = navigation.useSearchParams();
45
+ const search = react.useMemo(() => {
46
+ if (!includeSearchParams || !searchParams) {
47
+ return "";
48
+ }
49
+ return searchParams.toString();
50
+ }, [includeSearchParams, searchParams]);
51
+ const previousRef = react.useRef(null);
52
+ const hasTrackedRef = react.useRef(false);
53
+ const pendingKeyRef = react.useRef(null);
54
+ const readinessRef = react.useRef(
55
+ waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null
56
+ );
57
+ const isMountedRef = react.useRef(true);
58
+ react.useEffect(() => {
59
+ isMountedRef.current = true;
60
+ return () => {
61
+ isMountedRef.current = false;
62
+ };
63
+ }, []);
64
+ react.useEffect(() => {
65
+ readinessRef.current = waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null;
66
+ }, [client, readyPromise, waitForReady]);
67
+ const logFailures = react.useCallback((states) => {
68
+ const failed = states.filter((state) => state.status === "failed");
69
+ if (!failed.length) {
70
+ return;
71
+ }
72
+ const details = failed.map((state) => state.containerId).join(", ");
73
+ console.error(`[gtm-kit/next] Failed to load GTM container script(s): ${details}`, failed);
74
+ }, []);
75
+ const handleRouteChange = react.useCallback(
76
+ (nextPathname, searchValue, rawHash) => {
77
+ if (!nextPathname) {
78
+ return;
79
+ }
80
+ const normalizedHash = trackHash ? sanitizeHash(rawHash) : "";
81
+ const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;
82
+ const key = `${pagePath}${normalizedHash}`;
83
+ const url = buildUrl(pagePath, normalizedHash);
84
+ if (!trackOnMount && !hasTrackedRef.current) {
85
+ previousRef.current = {
86
+ key,
87
+ pathname: nextPathname,
88
+ search: searchValue,
89
+ hash: normalizedHash,
90
+ pagePath,
91
+ url
92
+ };
93
+ hasTrackedRef.current = true;
94
+ return;
95
+ }
96
+ if (skipSamePath && previousRef.current && previousRef.current.key === key) {
97
+ return;
98
+ }
99
+ const title = typeof document !== "undefined" ? document.title : void 0;
100
+ const previous = previousRef.current ? {
101
+ pathname: previousRef.current.pathname,
102
+ search: previousRef.current.search,
103
+ hash: previousRef.current.hash,
104
+ pagePath: previousRef.current.pagePath,
105
+ url: previousRef.current.url
106
+ } : void 0;
107
+ const details = {
108
+ pathname: nextPathname,
109
+ search: searchValue,
110
+ hash: normalizedHash,
111
+ pagePath,
112
+ url,
113
+ title,
114
+ previous
115
+ };
116
+ const pushPayload = () => {
117
+ const payload = buildPayload(details);
118
+ pushEventFn(client, eventName, payload);
119
+ previousRef.current = {
120
+ key,
121
+ pathname: nextPathname,
122
+ search: searchValue,
123
+ hash: normalizedHash,
124
+ pagePath,
125
+ url
126
+ };
127
+ hasTrackedRef.current = true;
128
+ };
129
+ if (waitForReady && readinessRef.current) {
130
+ pendingKeyRef.current = key;
131
+ readinessRef.current.then((states) => {
132
+ if (!isMountedRef.current || pendingKeyRef.current !== key) {
133
+ return;
134
+ }
135
+ logFailures(states);
136
+ pushPayload();
137
+ }).catch((error) => {
138
+ if (!isMountedRef.current || pendingKeyRef.current !== key) {
139
+ return;
140
+ }
141
+ console.error("[gtm-kit/next] Error while waiting for GTM readiness.", error);
142
+ pushPayload();
143
+ });
144
+ return;
145
+ }
146
+ pushPayload();
147
+ },
148
+ [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]
149
+ );
150
+ react.useEffect(() => {
151
+ var _a;
152
+ if (typeof window === "undefined") {
153
+ return;
154
+ }
155
+ const hash = trackHash ? (_a = window.location.hash) != null ? _a : "" : "";
156
+ handleRouteChange(pathname, search, hash);
157
+ }, [handleRouteChange, pathname, search, trackHash]);
158
+ react.useEffect(() => {
159
+ if (!trackHash || typeof window === "undefined") {
160
+ return;
161
+ }
162
+ const listener = () => {
163
+ var _a;
164
+ handleRouteChange(pathname, search, (_a = window.location.hash) != null ? _a : "");
165
+ };
166
+ window.addEventListener("hashchange", listener);
167
+ return () => {
168
+ window.removeEventListener("hashchange", listener);
169
+ };
170
+ }, [handleRouteChange, pathname, search, trackHash]);
171
+ };
172
+ var DEFAULT_ASYNC = true;
173
+ var GtmHeadScript = ({
174
+ containers,
175
+ host = gtmKit.DEFAULT_GTM_HOST,
176
+ defaultQueryParams,
177
+ scriptAttributes,
178
+ dataLayerName = gtmKit.DEFAULT_DATA_LAYER_NAME
179
+ }) => {
180
+ const normalized = gtmKit.normalizeContainers(containers);
181
+ if (!normalized.length) {
182
+ throw new Error("At least one GTM container is required to render script tags.");
183
+ }
184
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: normalized.map((container) => {
185
+ if (!container.id) {
186
+ throw new Error("Container id is required to render GTM script tags.");
187
+ }
188
+ const params = {
189
+ ...defaultQueryParams,
190
+ ...container.queryParams
191
+ };
192
+ const src = gtmKit.buildGtmScriptUrl(host, container.id, params, dataLayerName);
193
+ const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes != null ? scriptAttributes : {};
194
+ const scriptProps = {
195
+ src,
196
+ async: asyncAttr != null ? asyncAttr : DEFAULT_ASYNC
197
+ };
198
+ if (defer !== void 0) {
199
+ scriptProps.defer = defer;
200
+ }
201
+ if (nonce) {
202
+ scriptProps.nonce = nonce;
203
+ }
204
+ for (const [attribute, value] of Object.entries(restAttributes)) {
205
+ if (attribute === "async" || attribute === "defer" || attribute === "nonce" || attribute === "src") {
206
+ continue;
207
+ }
208
+ if (value === void 0 || value === null) {
209
+ continue;
210
+ }
211
+ scriptProps[attribute] = value;
212
+ }
213
+ return /* @__PURE__ */ jsxRuntime.jsx("script", { "data-gtm-container-id": container.id, ...scriptProps }, container.id);
214
+ }) });
215
+ };
216
+ var toStringValue = (value) => String(value);
217
+ var parseStyle = (style) => {
218
+ return style.split(";").map((chunk) => chunk.trim()).filter(Boolean).reduce((acc, declaration) => {
219
+ const [property, rawValue] = declaration.split(":");
220
+ if (!property || rawValue === void 0) {
221
+ return acc;
222
+ }
223
+ const key = property.trim().replace(/-([a-z])/g, (_, char) => char.toUpperCase());
224
+ const value = rawValue.trim();
225
+ if (!key || !value) {
226
+ return acc;
227
+ }
228
+ acc[key] = value;
229
+ return acc;
230
+ }, {});
231
+ };
232
+ var GtmNoScript = ({
233
+ containers,
234
+ host = gtmKit.DEFAULT_GTM_HOST,
235
+ defaultQueryParams,
236
+ iframeAttributes,
237
+ dataLayerName = gtmKit.DEFAULT_DATA_LAYER_NAME
238
+ }) => {
239
+ const normalized = gtmKit.normalizeContainers(containers);
240
+ if (!normalized.length) {
241
+ throw new Error("At least one GTM container is required to render noscript markup.");
242
+ }
243
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: normalized.map((container) => {
244
+ if (!container.id) {
245
+ throw new Error("Container id is required to render GTM noscript markup.");
246
+ }
247
+ const params = {
248
+ ...defaultQueryParams,
249
+ ...container.queryParams
250
+ };
251
+ const src = gtmKit.buildGtmNoscriptUrl(host, container.id, params, dataLayerName);
252
+ const attributes = {
253
+ ...gtmKit.DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,
254
+ ...iframeAttributes
255
+ };
256
+ const iframeProps = {
257
+ src
258
+ };
259
+ for (const [attribute, value] of Object.entries(attributes)) {
260
+ if (attribute === "src") {
261
+ continue;
262
+ }
263
+ if (value === void 0 || value === null) {
264
+ continue;
265
+ }
266
+ if (attribute === "style") {
267
+ if (typeof value === "string") {
268
+ iframeProps.style = parseStyle(value);
269
+ } else if (typeof value === "object") {
270
+ iframeProps.style = value;
271
+ }
272
+ continue;
273
+ }
274
+ iframeProps[attribute] = toStringValue(value);
275
+ }
276
+ return /* @__PURE__ */ jsxRuntime.jsx("noscript", { children: /* @__PURE__ */ jsxRuntime.jsx("iframe", { ...iframeProps }) }, container.id);
277
+ }) });
278
+ };
9
279
 
10
- exports.GtmHeadScript = X;
11
- exports.GtmNoScript = ot;
12
- exports.useTrackPageViews = K;
280
+ exports.GtmHeadScript = GtmHeadScript;
281
+ exports.GtmNoScript = GtmNoScript;
282
+ exports.useTrackPageViews = useTrackPageViews;
13
283
  //# sourceMappingURL=out.js.map
14
284
  //# sourceMappingURL=index.cjs.map
@@ -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","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"]}
1
+ {"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx"],"names":["DEFAULT_DATA_LAYER_NAME","Fragment","jsx"],"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(`[gtm-kit/next] 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('[gtm-kit/next] 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
@@ -3,8 +3,278 @@ import { usePathname, useSearchParams } from 'next/navigation';
3
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 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)})})};
6
+ var DEFAULT_EVENT_NAME = "page_view";
7
+ var defaultBuildPayload = ({ pagePath, url, title }) => {
8
+ const payload = {
9
+ page_path: pagePath,
10
+ page_location: url
11
+ };
12
+ if (title) {
13
+ payload.page_title = title;
14
+ }
15
+ return payload;
16
+ };
17
+ var buildUrl = (pagePath, hash) => {
18
+ var _a;
19
+ const origin = typeof window !== "undefined" && ((_a = window.location) == null ? void 0 : _a.origin) ? window.location.origin : "";
20
+ if (!origin) {
21
+ return `${pagePath}${hash}`;
22
+ }
23
+ return `${origin}${pagePath}${hash}`;
24
+ };
25
+ var sanitizeHash = (hash) => hash && hash.startsWith("#") ? hash : hash ? `#${hash}` : "";
26
+ var useTrackPageViews = ({
27
+ client,
28
+ eventName = DEFAULT_EVENT_NAME,
29
+ buildPayload = defaultBuildPayload,
30
+ includeSearchParams = true,
31
+ trackHash = false,
32
+ trackOnMount = true,
33
+ skipSamePath = true,
34
+ pushEventFn = pushEvent,
35
+ waitForReady = false,
36
+ readyPromise
37
+ }) => {
38
+ if (!client) {
39
+ throw new Error("A GTM client is required to track page views.");
40
+ }
41
+ const pathname = usePathname();
42
+ const searchParams = useSearchParams();
43
+ const search = useMemo(() => {
44
+ if (!includeSearchParams || !searchParams) {
45
+ return "";
46
+ }
47
+ return searchParams.toString();
48
+ }, [includeSearchParams, searchParams]);
49
+ const previousRef = useRef(null);
50
+ const hasTrackedRef = useRef(false);
51
+ const pendingKeyRef = useRef(null);
52
+ const readinessRef = useRef(
53
+ waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null
54
+ );
55
+ const isMountedRef = useRef(true);
56
+ useEffect(() => {
57
+ isMountedRef.current = true;
58
+ return () => {
59
+ isMountedRef.current = false;
60
+ };
61
+ }, []);
62
+ useEffect(() => {
63
+ readinessRef.current = waitForReady ? readyPromise != null ? readyPromise : client.whenReady() : null;
64
+ }, [client, readyPromise, waitForReady]);
65
+ const logFailures = useCallback((states) => {
66
+ const failed = states.filter((state) => state.status === "failed");
67
+ if (!failed.length) {
68
+ return;
69
+ }
70
+ const details = failed.map((state) => state.containerId).join(", ");
71
+ console.error(`[gtm-kit/next] Failed to load GTM container script(s): ${details}`, failed);
72
+ }, []);
73
+ const handleRouteChange = useCallback(
74
+ (nextPathname, searchValue, rawHash) => {
75
+ if (!nextPathname) {
76
+ return;
77
+ }
78
+ const normalizedHash = trackHash ? sanitizeHash(rawHash) : "";
79
+ const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;
80
+ const key = `${pagePath}${normalizedHash}`;
81
+ const url = buildUrl(pagePath, normalizedHash);
82
+ if (!trackOnMount && !hasTrackedRef.current) {
83
+ previousRef.current = {
84
+ key,
85
+ pathname: nextPathname,
86
+ search: searchValue,
87
+ hash: normalizedHash,
88
+ pagePath,
89
+ url
90
+ };
91
+ hasTrackedRef.current = true;
92
+ return;
93
+ }
94
+ if (skipSamePath && previousRef.current && previousRef.current.key === key) {
95
+ return;
96
+ }
97
+ const title = typeof document !== "undefined" ? document.title : void 0;
98
+ const previous = previousRef.current ? {
99
+ pathname: previousRef.current.pathname,
100
+ search: previousRef.current.search,
101
+ hash: previousRef.current.hash,
102
+ pagePath: previousRef.current.pagePath,
103
+ url: previousRef.current.url
104
+ } : void 0;
105
+ const details = {
106
+ pathname: nextPathname,
107
+ search: searchValue,
108
+ hash: normalizedHash,
109
+ pagePath,
110
+ url,
111
+ title,
112
+ previous
113
+ };
114
+ const pushPayload = () => {
115
+ const payload = buildPayload(details);
116
+ pushEventFn(client, eventName, payload);
117
+ previousRef.current = {
118
+ key,
119
+ pathname: nextPathname,
120
+ search: searchValue,
121
+ hash: normalizedHash,
122
+ pagePath,
123
+ url
124
+ };
125
+ hasTrackedRef.current = true;
126
+ };
127
+ if (waitForReady && readinessRef.current) {
128
+ pendingKeyRef.current = key;
129
+ readinessRef.current.then((states) => {
130
+ if (!isMountedRef.current || pendingKeyRef.current !== key) {
131
+ return;
132
+ }
133
+ logFailures(states);
134
+ pushPayload();
135
+ }).catch((error) => {
136
+ if (!isMountedRef.current || pendingKeyRef.current !== key) {
137
+ return;
138
+ }
139
+ console.error("[gtm-kit/next] Error while waiting for GTM readiness.", error);
140
+ pushPayload();
141
+ });
142
+ return;
143
+ }
144
+ pushPayload();
145
+ },
146
+ [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]
147
+ );
148
+ useEffect(() => {
149
+ var _a;
150
+ if (typeof window === "undefined") {
151
+ return;
152
+ }
153
+ const hash = trackHash ? (_a = window.location.hash) != null ? _a : "" : "";
154
+ handleRouteChange(pathname, search, hash);
155
+ }, [handleRouteChange, pathname, search, trackHash]);
156
+ useEffect(() => {
157
+ if (!trackHash || typeof window === "undefined") {
158
+ return;
159
+ }
160
+ const listener = () => {
161
+ var _a;
162
+ handleRouteChange(pathname, search, (_a = window.location.hash) != null ? _a : "");
163
+ };
164
+ window.addEventListener("hashchange", listener);
165
+ return () => {
166
+ window.removeEventListener("hashchange", listener);
167
+ };
168
+ }, [handleRouteChange, pathname, search, trackHash]);
169
+ };
170
+ var DEFAULT_ASYNC = true;
171
+ var GtmHeadScript = ({
172
+ containers,
173
+ host = DEFAULT_GTM_HOST,
174
+ defaultQueryParams,
175
+ scriptAttributes,
176
+ dataLayerName = DEFAULT_DATA_LAYER_NAME
177
+ }) => {
178
+ const normalized = normalizeContainers(containers);
179
+ if (!normalized.length) {
180
+ throw new Error("At least one GTM container is required to render script tags.");
181
+ }
182
+ return /* @__PURE__ */ jsx(Fragment, { children: normalized.map((container) => {
183
+ if (!container.id) {
184
+ throw new Error("Container id is required to render GTM script tags.");
185
+ }
186
+ const params = {
187
+ ...defaultQueryParams,
188
+ ...container.queryParams
189
+ };
190
+ const src = buildGtmScriptUrl(host, container.id, params, dataLayerName);
191
+ const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes != null ? scriptAttributes : {};
192
+ const scriptProps = {
193
+ src,
194
+ async: asyncAttr != null ? asyncAttr : DEFAULT_ASYNC
195
+ };
196
+ if (defer !== void 0) {
197
+ scriptProps.defer = defer;
198
+ }
199
+ if (nonce) {
200
+ scriptProps.nonce = nonce;
201
+ }
202
+ for (const [attribute, value] of Object.entries(restAttributes)) {
203
+ if (attribute === "async" || attribute === "defer" || attribute === "nonce" || attribute === "src") {
204
+ continue;
205
+ }
206
+ if (value === void 0 || value === null) {
207
+ continue;
208
+ }
209
+ scriptProps[attribute] = value;
210
+ }
211
+ return /* @__PURE__ */ jsx("script", { "data-gtm-container-id": container.id, ...scriptProps }, container.id);
212
+ }) });
213
+ };
214
+ var toStringValue = (value) => String(value);
215
+ var parseStyle = (style) => {
216
+ return style.split(";").map((chunk) => chunk.trim()).filter(Boolean).reduce((acc, declaration) => {
217
+ const [property, rawValue] = declaration.split(":");
218
+ if (!property || rawValue === void 0) {
219
+ return acc;
220
+ }
221
+ const key = property.trim().replace(/-([a-z])/g, (_, char) => char.toUpperCase());
222
+ const value = rawValue.trim();
223
+ if (!key || !value) {
224
+ return acc;
225
+ }
226
+ acc[key] = value;
227
+ return acc;
228
+ }, {});
229
+ };
230
+ var GtmNoScript = ({
231
+ containers,
232
+ host = DEFAULT_GTM_HOST,
233
+ defaultQueryParams,
234
+ iframeAttributes,
235
+ dataLayerName = DEFAULT_DATA_LAYER_NAME
236
+ }) => {
237
+ const normalized = normalizeContainers(containers);
238
+ if (!normalized.length) {
239
+ throw new Error("At least one GTM container is required to render noscript markup.");
240
+ }
241
+ return /* @__PURE__ */ jsx(Fragment, { children: normalized.map((container) => {
242
+ if (!container.id) {
243
+ throw new Error("Container id is required to render GTM noscript markup.");
244
+ }
245
+ const params = {
246
+ ...defaultQueryParams,
247
+ ...container.queryParams
248
+ };
249
+ const src = buildGtmNoscriptUrl(host, container.id, params, dataLayerName);
250
+ const attributes = {
251
+ ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,
252
+ ...iframeAttributes
253
+ };
254
+ const iframeProps = {
255
+ src
256
+ };
257
+ for (const [attribute, value] of Object.entries(attributes)) {
258
+ if (attribute === "src") {
259
+ continue;
260
+ }
261
+ if (value === void 0 || value === null) {
262
+ continue;
263
+ }
264
+ if (attribute === "style") {
265
+ if (typeof value === "string") {
266
+ iframeProps.style = parseStyle(value);
267
+ } else if (typeof value === "object") {
268
+ iframeProps.style = value;
269
+ }
270
+ continue;
271
+ }
272
+ iframeProps[attribute] = toStringValue(value);
273
+ }
274
+ return /* @__PURE__ */ jsx("noscript", { children: /* @__PURE__ */ jsx("iframe", { ...iframeProps }) }, container.id);
275
+ }) });
276
+ };
7
277
 
8
- export { X as GtmHeadScript, ot as GtmNoScript, K as useTrackPageViews };
278
+ export { GtmHeadScript, GtmNoScript, useTrackPageViews };
9
279
  //# sourceMappingURL=out.js.map
10
280
  //# 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","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"]}
1
+ {"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx"],"names":["DEFAULT_DATA_LAYER_NAME","Fragment","jsx"],"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(`[gtm-kit/next] 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('[gtm-kit/next] 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.6",
3
+ "version": "1.2.0",
4
4
  "description": "Next.js App Router helpers for GTM Kit - Google Tag Manager integration with Server Components support.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -49,7 +49,7 @@
49
49
  "typecheck": "tsc --noEmit"
50
50
  },
51
51
  "dependencies": {
52
- "@jwiedeman/gtm-kit": "^1.0.0"
52
+ "@jwiedeman/gtm-kit": "^1.2.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "next": "^13.4.0 || ^14.0.0 || ^15.0.0",