ninetwo-user-tracking 1.0.10 → 1.0.12

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.d.mts CHANGED
@@ -14,7 +14,16 @@ interface TrackViewProps {
14
14
  label?: string;
15
15
  threshold?: number;
16
16
  readTime?: number;
17
+ debug?: boolean;
17
18
  }
19
+ /**
20
+ * TrackView
21
+ * - Dispara {eventName, type: 'view'} quando o elemento entra na viewport (threshold)
22
+ * - Dispara {eventName+'_read_confirmation', type: 'read_confirmation'} se o elemento permanecer visível por `readTime`
23
+ *
24
+ * Observação: evita usar `display: 'contents'` no wrapper porque em alguns cenários o IntersectionObserver não detecta.
25
+ * Se o wrapper realmente usa display: contents, tentamos observar o primeiro filho disponível.
26
+ */
18
27
  declare const TrackView: React.FC<TrackViewProps>;
19
28
 
20
29
  declare const useAutoTrackClick: (enabled?: boolean) => void;
package/dist/index.d.ts CHANGED
@@ -14,7 +14,16 @@ interface TrackViewProps {
14
14
  label?: string;
15
15
  threshold?: number;
16
16
  readTime?: number;
17
+ debug?: boolean;
17
18
  }
19
+ /**
20
+ * TrackView
21
+ * - Dispara {eventName, type: 'view'} quando o elemento entra na viewport (threshold)
22
+ * - Dispara {eventName+'_read_confirmation', type: 'read_confirmation'} se o elemento permanecer visível por `readTime`
23
+ *
24
+ * Observação: evita usar `display: 'contents'` no wrapper porque em alguns cenários o IntersectionObserver não detecta.
25
+ * Se o wrapper realmente usa display: contents, tentamos observar o primeiro filho disponível.
26
+ */
18
27
  declare const TrackView: React.FC<TrackViewProps>;
19
28
 
20
29
  declare const useAutoTrackClick: (enabled?: boolean) => void;
package/dist/index.js CHANGED
@@ -36,14 +36,28 @@ var import_react = require("react");
36
36
  var pushToDataLayer = (props) => {
37
37
  if (typeof window === "undefined")
38
38
  return;
39
- const dataLayer = window.dataLayer || [];
40
- dataLayer.push({
41
- event: props.event,
42
- // Nome do evento (ex: 'click_cta')
43
- event_category: props.category,
44
- event_label: props.label,
45
- event_type: props.type,
46
- interaction_time: (/* @__PURE__ */ new Date()).toISOString()
39
+ const { event, category, label, type, ...rest } = props;
40
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
41
+ const params = {
42
+ event_category: category,
43
+ event_label: label,
44
+ event_type: type,
45
+ interaction_time: timestamp,
46
+ ...rest
47
+ // permite enviar dados extras
48
+ };
49
+ if (typeof window.gtag === "function") {
50
+ try {
51
+ window.gtag("event", event, params);
52
+ return;
53
+ } catch (err) {
54
+ console.warn("[NineTwo Tracking] gtag falhou, fallback para dataLayer", err);
55
+ }
56
+ }
57
+ window.dataLayer = window.dataLayer || [];
58
+ window.dataLayer.push({
59
+ event,
60
+ ...params
47
61
  });
48
62
  };
49
63
 
@@ -205,21 +219,53 @@ var TrackView = ({
205
219
  category,
206
220
  label,
207
221
  threshold = 0.5,
208
- readTime = 5e3
222
+ readTime = 5e3,
223
+ debug = false
209
224
  }) => {
210
225
  const ref = (0, import_react4.useRef)(null);
211
226
  const timerRef = (0, import_react4.useRef)(null);
212
227
  const [hasTriggeredView, setHasTriggeredView] = (0, import_react4.useState)(false);
213
228
  const [hasTriggeredRead, setHasTriggeredRead] = (0, import_react4.useState)(false);
214
229
  (0, import_react4.useEffect)(() => {
215
- if (!ref.current)
230
+ const rootEl = ref.current;
231
+ if (!rootEl)
216
232
  return;
217
233
  if (hasTriggeredView && hasTriggeredRead)
218
234
  return;
235
+ if (typeof window !== "undefined" && !("IntersectionObserver" in window)) {
236
+ if (!hasTriggeredView) {
237
+ pushToDataLayer({
238
+ event: eventName,
239
+ category,
240
+ label,
241
+ type: "view"
242
+ });
243
+ setHasTriggeredView(true);
244
+ }
245
+ if (!hasTriggeredRead) {
246
+ timerRef.current = window.setTimeout(() => {
247
+ pushToDataLayer({
248
+ event: `${eventName}_read_confirmation`,
249
+ category,
250
+ label,
251
+ type: "read_confirmation"
252
+ });
253
+ setHasTriggeredRead(true);
254
+ }, readTime);
255
+ }
256
+ return () => {
257
+ if (timerRef.current) {
258
+ window.clearTimeout(timerRef.current);
259
+ timerRef.current = null;
260
+ }
261
+ };
262
+ }
219
263
  const observer = new IntersectionObserver(
220
264
  ([entry]) => {
221
265
  if (entry.isIntersecting) {
222
266
  if (!hasTriggeredView) {
267
+ if (debug)
268
+ console.log("[TrackView] view ->", eventName, { category, label });
223
269
  pushToDataLayer({
224
270
  event: eventName,
225
271
  category,
@@ -228,36 +274,70 @@ var TrackView = ({
228
274
  });
229
275
  setHasTriggeredView(true);
230
276
  }
231
- if (!hasTriggeredRead && !timerRef.current) {
232
- timerRef.current = setTimeout(() => {
277
+ if (!hasTriggeredRead && timerRef.current === null) {
278
+ timerRef.current = window.setTimeout(() => {
279
+ if (debug)
280
+ console.log("[TrackView] read_confirmation ->", eventName, { category, label });
233
281
  pushToDataLayer({
234
282
  event: `${eventName}_read_confirmation`,
235
- // Sufixo solicitado
236
283
  category,
237
284
  label,
238
285
  type: "read_confirmation"
239
- // Tipo diferenciado
240
286
  });
241
287
  setHasTriggeredRead(true);
288
+ timerRef.current = null;
242
289
  }, readTime);
243
290
  }
244
291
  } else {
245
292
  if (timerRef.current) {
246
- clearTimeout(timerRef.current);
293
+ if (debug)
294
+ console.log("[TrackView] saiu antes do readTime, cancelando timer ->", eventName);
295
+ window.clearTimeout(timerRef.current);
247
296
  timerRef.current = null;
248
297
  }
249
298
  }
250
299
  },
251
300
  { threshold }
252
301
  );
253
- observer.observe(ref.current);
302
+ let elementToObserve = rootEl;
303
+ try {
304
+ const cs = window.getComputedStyle(rootEl);
305
+ if (cs && cs.display === "contents") {
306
+ const firstChild = rootEl.firstElementChild;
307
+ if (firstChild) {
308
+ elementToObserve = firstChild;
309
+ if (debug)
310
+ console.log("[TrackView] wrapper display:contents \u2014 observando primeiro filho", firstChild);
311
+ } else {
312
+ if (debug)
313
+ console.log("[TrackView] wrapper display:contents mas sem filhos \u2014 observando wrapper", rootEl);
314
+ }
315
+ }
316
+ } catch (e) {
317
+ if (debug)
318
+ console.warn("[TrackView] erro ao checar computedStyle", e);
319
+ }
320
+ if (elementToObserve) {
321
+ observer.observe(elementToObserve);
322
+ }
254
323
  return () => {
255
324
  observer.disconnect();
256
- if (timerRef.current)
257
- clearTimeout(timerRef.current);
325
+ if (timerRef.current) {
326
+ window.clearTimeout(timerRef.current);
327
+ timerRef.current = null;
328
+ }
258
329
  };
259
- }, [hasTriggeredView, hasTriggeredRead, eventName, category, label, threshold, readTime]);
260
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, style: { display: "contents" }, children });
330
+ }, [
331
+ hasTriggeredView,
332
+ hasTriggeredRead,
333
+ eventName,
334
+ category,
335
+ label,
336
+ threshold,
337
+ readTime,
338
+ debug
339
+ ]);
340
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, style: { display: "block" }, children });
261
341
  };
262
342
  // Annotate the CommonJS export names for ESM import in node:
263
343
  0 && (module.exports = {
package/dist/index.mjs CHANGED
@@ -8,14 +8,28 @@ import { useEffect } from "react";
8
8
  var pushToDataLayer = (props) => {
9
9
  if (typeof window === "undefined")
10
10
  return;
11
- const dataLayer = window.dataLayer || [];
12
- dataLayer.push({
13
- event: props.event,
14
- // Nome do evento (ex: 'click_cta')
15
- event_category: props.category,
16
- event_label: props.label,
17
- event_type: props.type,
18
- interaction_time: (/* @__PURE__ */ new Date()).toISOString()
11
+ const { event, category, label, type, ...rest } = props;
12
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
13
+ const params = {
14
+ event_category: category,
15
+ event_label: label,
16
+ event_type: type,
17
+ interaction_time: timestamp,
18
+ ...rest
19
+ // permite enviar dados extras
20
+ };
21
+ if (typeof window.gtag === "function") {
22
+ try {
23
+ window.gtag("event", event, params);
24
+ return;
25
+ } catch (err) {
26
+ console.warn("[NineTwo Tracking] gtag falhou, fallback para dataLayer", err);
27
+ }
28
+ }
29
+ window.dataLayer = window.dataLayer || [];
30
+ window.dataLayer.push({
31
+ event,
32
+ ...params
19
33
  });
20
34
  };
21
35
 
@@ -177,21 +191,53 @@ var TrackView = ({
177
191
  category,
178
192
  label,
179
193
  threshold = 0.5,
180
- readTime = 5e3
194
+ readTime = 5e3,
195
+ debug = false
181
196
  }) => {
182
197
  const ref = useRef(null);
183
198
  const timerRef = useRef(null);
184
199
  const [hasTriggeredView, setHasTriggeredView] = useState(false);
185
200
  const [hasTriggeredRead, setHasTriggeredRead] = useState(false);
186
201
  useEffect4(() => {
187
- if (!ref.current)
202
+ const rootEl = ref.current;
203
+ if (!rootEl)
188
204
  return;
189
205
  if (hasTriggeredView && hasTriggeredRead)
190
206
  return;
207
+ if (typeof window !== "undefined" && !("IntersectionObserver" in window)) {
208
+ if (!hasTriggeredView) {
209
+ pushToDataLayer({
210
+ event: eventName,
211
+ category,
212
+ label,
213
+ type: "view"
214
+ });
215
+ setHasTriggeredView(true);
216
+ }
217
+ if (!hasTriggeredRead) {
218
+ timerRef.current = window.setTimeout(() => {
219
+ pushToDataLayer({
220
+ event: `${eventName}_read_confirmation`,
221
+ category,
222
+ label,
223
+ type: "read_confirmation"
224
+ });
225
+ setHasTriggeredRead(true);
226
+ }, readTime);
227
+ }
228
+ return () => {
229
+ if (timerRef.current) {
230
+ window.clearTimeout(timerRef.current);
231
+ timerRef.current = null;
232
+ }
233
+ };
234
+ }
191
235
  const observer = new IntersectionObserver(
192
236
  ([entry]) => {
193
237
  if (entry.isIntersecting) {
194
238
  if (!hasTriggeredView) {
239
+ if (debug)
240
+ console.log("[TrackView] view ->", eventName, { category, label });
195
241
  pushToDataLayer({
196
242
  event: eventName,
197
243
  category,
@@ -200,36 +246,70 @@ var TrackView = ({
200
246
  });
201
247
  setHasTriggeredView(true);
202
248
  }
203
- if (!hasTriggeredRead && !timerRef.current) {
204
- timerRef.current = setTimeout(() => {
249
+ if (!hasTriggeredRead && timerRef.current === null) {
250
+ timerRef.current = window.setTimeout(() => {
251
+ if (debug)
252
+ console.log("[TrackView] read_confirmation ->", eventName, { category, label });
205
253
  pushToDataLayer({
206
254
  event: `${eventName}_read_confirmation`,
207
- // Sufixo solicitado
208
255
  category,
209
256
  label,
210
257
  type: "read_confirmation"
211
- // Tipo diferenciado
212
258
  });
213
259
  setHasTriggeredRead(true);
260
+ timerRef.current = null;
214
261
  }, readTime);
215
262
  }
216
263
  } else {
217
264
  if (timerRef.current) {
218
- clearTimeout(timerRef.current);
265
+ if (debug)
266
+ console.log("[TrackView] saiu antes do readTime, cancelando timer ->", eventName);
267
+ window.clearTimeout(timerRef.current);
219
268
  timerRef.current = null;
220
269
  }
221
270
  }
222
271
  },
223
272
  { threshold }
224
273
  );
225
- observer.observe(ref.current);
274
+ let elementToObserve = rootEl;
275
+ try {
276
+ const cs = window.getComputedStyle(rootEl);
277
+ if (cs && cs.display === "contents") {
278
+ const firstChild = rootEl.firstElementChild;
279
+ if (firstChild) {
280
+ elementToObserve = firstChild;
281
+ if (debug)
282
+ console.log("[TrackView] wrapper display:contents \u2014 observando primeiro filho", firstChild);
283
+ } else {
284
+ if (debug)
285
+ console.log("[TrackView] wrapper display:contents mas sem filhos \u2014 observando wrapper", rootEl);
286
+ }
287
+ }
288
+ } catch (e) {
289
+ if (debug)
290
+ console.warn("[TrackView] erro ao checar computedStyle", e);
291
+ }
292
+ if (elementToObserve) {
293
+ observer.observe(elementToObserve);
294
+ }
226
295
  return () => {
227
296
  observer.disconnect();
228
- if (timerRef.current)
229
- clearTimeout(timerRef.current);
297
+ if (timerRef.current) {
298
+ window.clearTimeout(timerRef.current);
299
+ timerRef.current = null;
300
+ }
230
301
  };
231
- }, [hasTriggeredView, hasTriggeredRead, eventName, category, label, threshold, readTime]);
232
- return /* @__PURE__ */ jsx2("div", { ref, style: { display: "contents" }, children });
302
+ }, [
303
+ hasTriggeredView,
304
+ hasTriggeredRead,
305
+ eventName,
306
+ category,
307
+ label,
308
+ threshold,
309
+ readTime,
310
+ debug
311
+ ]);
312
+ return /* @__PURE__ */ jsx2("div", { ref, style: { display: "block" }, children });
233
313
  };
234
314
  export {
235
315
  TrackView,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ninetwo-user-tracking",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "User tracking abstraction for React/Nextjs with GTM",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",