ninetwo-user-tracking 1.0.6 → 1.0.8
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 +213 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +82 -22
- package/dist/index.mjs +81 -21
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
|
|
2
|
+
# NineTwo User Tracking
|
|
3
|
+
|
|
4
|
+
Pacote de abstração de Analytics para React e Next.js.
|
|
5
|
+
Facilita a implementação do **Google Tag Manager (GTM)** utilizando **Delegação de Eventos** para cliques (via atributos HTML) e **Intersection Observer** para visualizações e confirmação de leitura.
|
|
6
|
+
|
|
7
|
+
## ✨ Funcionalidades
|
|
8
|
+
|
|
9
|
+
- 🚀 **Zero Boilerplate:** Rastreamento declarativo via atributos `data-nt-ut-*`.
|
|
10
|
+
- 🖱️ **Click Tracking Automático:** Listener global que captura cliques.
|
|
11
|
+
- 👁️ **View Tracking:** Dispara evento ao visualizar elemento.
|
|
12
|
+
- 📖 **Read Confirmation:** Dispara evento secundário automaticamente após 5s de visualização contínua.
|
|
13
|
+
- 💉 **GTM Injection:** Injeção segura do script do GTM.
|
|
14
|
+
- ⚡ **Next.js Ready:** Compatível com App Router (Providers Pattern).
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 📦 Instalação
|
|
19
|
+
|
|
20
|
+
npm install ninetwo_user_tracking
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🚀 Configuração (Next.js 13+ App Router)
|
|
24
|
+
|
|
25
|
+
### 1. Crie o componente `app/providers.tsx`
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
'use client';
|
|
29
|
+
|
|
30
|
+
import { TrackingProvider } from 'ninetwo_user_tracking';
|
|
31
|
+
|
|
32
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
33
|
+
return (
|
|
34
|
+
<TrackingProvider
|
|
35
|
+
gtmId="GTM-SEU-ID-AQUI"
|
|
36
|
+
debug={process.env.NODE_ENV === 'development'}
|
|
37
|
+
>
|
|
38
|
+
{children}
|
|
39
|
+
</TrackingProvider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. Envolva o `app/layout.tsx`
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { Providers } from "./providers";
|
|
49
|
+
import "./globals.css";
|
|
50
|
+
|
|
51
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
52
|
+
return (
|
|
53
|
+
<html lang="pt-BR">
|
|
54
|
+
<body>
|
|
55
|
+
<Providers>{children}</Providers>
|
|
56
|
+
</body>
|
|
57
|
+
</html>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 🖱️ Rastreamento de Cliques (Click)
|
|
66
|
+
|
|
67
|
+
Adicione os atributos `data-nt-ut-*` ao elemento interativo.
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
<button
|
|
71
|
+
className="btn-primary"
|
|
72
|
+
data-nt-ut-event="add_to_cart"
|
|
73
|
+
data-nt-ut-category="ecommerce"
|
|
74
|
+
data-nt-ut-label="tenis_nike_v2"
|
|
75
|
+
data-nt-ut-type="click" // Opcional (default: click)
|
|
76
|
+
>
|
|
77
|
+
Comprar Agora
|
|
78
|
+
</button>
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 👁️ Rastreamento de Visualização e Leitura (View/Read)
|
|
85
|
+
|
|
86
|
+
Use o componente `<TrackView>` para monitorar impressões.
|
|
87
|
+
**Novidade:** Se o usuário permanecer com o elemento visível por 5 segundos (padrão), um segundo evento `read_confirmation` será disparado.
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { TrackView } from 'ninetwo_user_tracking';
|
|
91
|
+
|
|
92
|
+
export default function BlogPost() {
|
|
93
|
+
return (
|
|
94
|
+
<TrackView
|
|
95
|
+
eventName="article_view"
|
|
96
|
+
category="blog"
|
|
97
|
+
label="como_aprender_react"
|
|
98
|
+
threshold={0.5} // 50% visível para disparar
|
|
99
|
+
readTime={5000} // (Opcional) Tempo em ms para confirmar leitura
|
|
100
|
+
>
|
|
101
|
+
<article>
|
|
102
|
+
<h1>Como aprender React</h1>
|
|
103
|
+
<p>Conteúdo do artigo...</p>
|
|
104
|
+
</article>
|
|
105
|
+
</TrackView>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Comportamento dos Eventos
|
|
112
|
+
|
|
113
|
+
Neste exemplo acima, dois eventos serão enviados ao DataLayer:
|
|
114
|
+
|
|
115
|
+
1. **Assim que aparecer:**
|
|
116
|
+
* event: `"article_view"`
|
|
117
|
+
* type: `"view"`
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
2. **Após 5 segundos visível:**
|
|
121
|
+
* event: `"article_view"`
|
|
122
|
+
* type: `"read_confirmation"`
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
Aqui está a documentação exclusiva para o rastreamento de **Submit de Formulários**, pronta para copiar e colar.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 📝 Rastreamento de Formulários (Submit)
|
|
132
|
+
|
|
133
|
+
O pacote detecta automaticamente o envio de formulários através de **Event Delegation**.
|
|
134
|
+
Isso significa que você deve adicionar os atributos de rastreamento **diretamente na tag `<form>**`, e não no botão de enviar.
|
|
135
|
+
|
|
136
|
+
O evento será disparado tanto ao clicar no botão `type="submit"` quanto ao pressionar `Enter` dentro de um input.
|
|
137
|
+
|
|
138
|
+
### Exemplo de Implementação
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<form
|
|
142
|
+
action="/api/newsletter"
|
|
143
|
+
method="POST"
|
|
144
|
+
// Atributos de Tracking na tag FORM (Obrigatório)
|
|
145
|
+
data-nt-ut-event="newsletter_signup"
|
|
146
|
+
data-nt-ut-category="leads"
|
|
147
|
+
data-nt-ut-label="footer_form"
|
|
148
|
+
// data-nt-ut-type="submit" -> (Opcional: o padrão já é 'submit' para formulários)
|
|
149
|
+
>
|
|
150
|
+
<div className="flex gap-2">
|
|
151
|
+
<input
|
|
152
|
+
type="email"
|
|
153
|
+
name="email"
|
|
154
|
+
placeholder="Seu melhor e-mail"
|
|
155
|
+
className="border p-2"
|
|
156
|
+
/>
|
|
157
|
+
<button type="submit" className="bg-blue-500 text-white p-2">
|
|
158
|
+
Inscrever-se
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
</form>
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### O que acontece no DataLayer?
|
|
166
|
+
|
|
167
|
+
Quando o usuário envia este formulário, o seguinte objeto é enviado para o GTM:
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
{
|
|
171
|
+
event: "newsletter_signup", // Valor de data-nt-ut-event
|
|
172
|
+
event_category: "leads", // Valor de data-nt-ut-category
|
|
173
|
+
event_label: "footer_form", // Valor de data-nt-ut-label
|
|
174
|
+
event_type: "submit", // Automático para tags <form>
|
|
175
|
+
interaction_time: "2024-01-20T14:00:00.000Z"
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## ⚙️ Configuração no GTM
|
|
183
|
+
|
|
184
|
+
O pacote envia os dados para `window.dataLayer`.
|
|
185
|
+
|
|
186
|
+
### Exemplo de Objeto Enviado (Read Confirmation)
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
{
|
|
190
|
+
event: "article_view",
|
|
191
|
+
event_category: "blog",
|
|
192
|
+
event_label: "como_aprender_react",
|
|
193
|
+
event_type: "read_confirmation",
|
|
194
|
+
interaction_time: "..."
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Configuração Recomendada
|
|
200
|
+
|
|
201
|
+
1. **Variáveis:** Crie variáveis de DataLayer para `event_category`, `event_label` e `event_type`.
|
|
202
|
+
2. **Trigger:** Use `.*` (Regex) em Evento Personalizado para capturar tudo.
|
|
203
|
+
3. **Tag GA4:** Mapeie os parâmetros. No GA4, você poderá filtrar eventos onde `type` é igual a `read_confirmation` para medir engajamento real.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
ISC © NineTwo
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ __export(src_exports, {
|
|
|
27
27
|
module.exports = __toCommonJS(src_exports);
|
|
28
28
|
|
|
29
29
|
// src/TrackingProvider.tsx
|
|
30
|
-
var
|
|
30
|
+
var import_react3 = require("react");
|
|
31
31
|
|
|
32
32
|
// src/hooks/useAutoTrackClick.ts
|
|
33
33
|
var import_react = require("react");
|
|
@@ -79,6 +79,36 @@ var useAutoTrackClick = (enabled = true) => {
|
|
|
79
79
|
}, [enabled]);
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
// src/hooks/useAutoTrackSubmit.ts
|
|
83
|
+
var import_react2 = require("react");
|
|
84
|
+
var useAutoTrackSubmit = (enabled = true) => {
|
|
85
|
+
(0, import_react2.useEffect)(() => {
|
|
86
|
+
if (!enabled || typeof document === "undefined")
|
|
87
|
+
return;
|
|
88
|
+
const handleSubmit = (e) => {
|
|
89
|
+
const target = e.target;
|
|
90
|
+
const formElement = target.closest("form[data-nt-ut-event]");
|
|
91
|
+
if (formElement) {
|
|
92
|
+
const eventName = formElement.getAttribute("data-nt-ut-event");
|
|
93
|
+
const category = formElement.getAttribute("data-nt-ut-category");
|
|
94
|
+
const label = formElement.getAttribute("data-nt-ut-label");
|
|
95
|
+
const type = formElement.getAttribute("data-nt-ut-type");
|
|
96
|
+
pushToDataLayer({
|
|
97
|
+
event: eventName || "form_submit",
|
|
98
|
+
category: category || "form",
|
|
99
|
+
label: label || "",
|
|
100
|
+
type: type || "submit"
|
|
101
|
+
// Padrão agora é 'submit'
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
document.addEventListener("submit", handleSubmit, true);
|
|
106
|
+
return () => {
|
|
107
|
+
document.removeEventListener("submit", handleSubmit, true);
|
|
108
|
+
};
|
|
109
|
+
}, [enabled]);
|
|
110
|
+
};
|
|
111
|
+
|
|
82
112
|
// src/TrackingProvider.tsx
|
|
83
113
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
84
114
|
var TrackingProvider = ({
|
|
@@ -87,7 +117,8 @@ var TrackingProvider = ({
|
|
|
87
117
|
debug = false
|
|
88
118
|
}) => {
|
|
89
119
|
useAutoTrackClick(true);
|
|
90
|
-
(
|
|
120
|
+
useAutoTrackSubmit(true);
|
|
121
|
+
(0, import_react3.useEffect)(() => {
|
|
91
122
|
if (!gtmId || typeof window === "undefined") {
|
|
92
123
|
if (debug && !gtmId)
|
|
93
124
|
console.warn("[NineTwo Tracking] GTM ID n\xE3o fornecido.");
|
|
@@ -107,14 +138,14 @@ var TrackingProvider = ({
|
|
|
107
138
|
const script = document.createElement("script");
|
|
108
139
|
script.id = scriptId;
|
|
109
140
|
script.async = true;
|
|
110
|
-
script.src = `https://www.googletagmanager.com/
|
|
141
|
+
script.src = `https://www.googletagmanager.com/gtm.js?id=${gtmId}`;
|
|
111
142
|
script.onload = () => {
|
|
112
143
|
if (debug)
|
|
113
144
|
console.log(`[NineTwo Tracking] \u2705 GTM carregado com sucesso! (${gtmId})`);
|
|
114
145
|
};
|
|
115
146
|
script.onerror = () => {
|
|
116
147
|
if (debug)
|
|
117
|
-
console.error("[NineTwo Tracking] \u274C Erro ao carregar GTM.
|
|
148
|
+
console.error("[NineTwo Tracking] \u274C Erro ao carregar GTM.");
|
|
118
149
|
};
|
|
119
150
|
document.head.appendChild(script);
|
|
120
151
|
}, [gtmId, debug]);
|
|
@@ -122,37 +153,66 @@ var TrackingProvider = ({
|
|
|
122
153
|
};
|
|
123
154
|
|
|
124
155
|
// src/components/TrackView.tsx
|
|
125
|
-
var
|
|
156
|
+
var import_react4 = require("react");
|
|
126
157
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
127
158
|
var TrackView = ({
|
|
128
159
|
children,
|
|
129
160
|
eventName,
|
|
130
161
|
category,
|
|
131
162
|
label,
|
|
132
|
-
threshold = 0.5
|
|
163
|
+
threshold = 0.5,
|
|
164
|
+
readTime = 5e3
|
|
133
165
|
}) => {
|
|
134
|
-
const ref = (0,
|
|
135
|
-
const
|
|
136
|
-
(0,
|
|
166
|
+
const ref = (0, import_react4.useRef)(null);
|
|
167
|
+
const timerRef = (0, import_react4.useRef)(null);
|
|
168
|
+
const [hasTriggeredView, setHasTriggeredView] = (0, import_react4.useState)(false);
|
|
169
|
+
const [hasTriggeredRead, setHasTriggeredRead] = (0, import_react4.useState)(false);
|
|
170
|
+
(0, import_react4.useEffect)(() => {
|
|
171
|
+
if (!ref.current)
|
|
172
|
+
return;
|
|
173
|
+
if (hasTriggeredView && hasTriggeredRead)
|
|
174
|
+
return;
|
|
137
175
|
const observer = new IntersectionObserver(
|
|
138
176
|
([entry]) => {
|
|
139
|
-
if (entry.isIntersecting
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
177
|
+
if (entry.isIntersecting) {
|
|
178
|
+
if (!hasTriggeredView) {
|
|
179
|
+
pushToDataLayer({
|
|
180
|
+
event: eventName,
|
|
181
|
+
category,
|
|
182
|
+
label,
|
|
183
|
+
type: "view"
|
|
184
|
+
});
|
|
185
|
+
setHasTriggeredView(true);
|
|
186
|
+
}
|
|
187
|
+
if (!hasTriggeredRead && !timerRef.current) {
|
|
188
|
+
timerRef.current = setTimeout(() => {
|
|
189
|
+
pushToDataLayer({
|
|
190
|
+
event: `${eventName}_read_confirmation`,
|
|
191
|
+
// Sufixo solicitado
|
|
192
|
+
category,
|
|
193
|
+
label,
|
|
194
|
+
type: "read_confirmation"
|
|
195
|
+
// Tipo diferenciado
|
|
196
|
+
});
|
|
197
|
+
setHasTriggeredRead(true);
|
|
198
|
+
}, readTime);
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
if (timerRef.current) {
|
|
202
|
+
clearTimeout(timerRef.current);
|
|
203
|
+
timerRef.current = null;
|
|
204
|
+
}
|
|
148
205
|
}
|
|
149
206
|
},
|
|
150
207
|
{ threshold }
|
|
151
208
|
);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
209
|
+
observer.observe(ref.current);
|
|
210
|
+
return () => {
|
|
211
|
+
observer.disconnect();
|
|
212
|
+
if (timerRef.current)
|
|
213
|
+
clearTimeout(timerRef.current);
|
|
214
|
+
};
|
|
215
|
+
}, [hasTriggeredView, hasTriggeredRead, eventName, category, label, threshold, readTime]);
|
|
156
216
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, style: { display: "contents" }, children });
|
|
157
217
|
};
|
|
158
218
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/TrackingProvider.tsx
|
|
2
|
-
import { useEffect as
|
|
2
|
+
import { useEffect as useEffect3 } from "react";
|
|
3
3
|
|
|
4
4
|
// src/hooks/useAutoTrackClick.ts
|
|
5
5
|
import { useEffect } from "react";
|
|
@@ -51,6 +51,36 @@ var useAutoTrackClick = (enabled = true) => {
|
|
|
51
51
|
}, [enabled]);
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
// src/hooks/useAutoTrackSubmit.ts
|
|
55
|
+
import { useEffect as useEffect2 } from "react";
|
|
56
|
+
var useAutoTrackSubmit = (enabled = true) => {
|
|
57
|
+
useEffect2(() => {
|
|
58
|
+
if (!enabled || typeof document === "undefined")
|
|
59
|
+
return;
|
|
60
|
+
const handleSubmit = (e) => {
|
|
61
|
+
const target = e.target;
|
|
62
|
+
const formElement = target.closest("form[data-nt-ut-event]");
|
|
63
|
+
if (formElement) {
|
|
64
|
+
const eventName = formElement.getAttribute("data-nt-ut-event");
|
|
65
|
+
const category = formElement.getAttribute("data-nt-ut-category");
|
|
66
|
+
const label = formElement.getAttribute("data-nt-ut-label");
|
|
67
|
+
const type = formElement.getAttribute("data-nt-ut-type");
|
|
68
|
+
pushToDataLayer({
|
|
69
|
+
event: eventName || "form_submit",
|
|
70
|
+
category: category || "form",
|
|
71
|
+
label: label || "",
|
|
72
|
+
type: type || "submit"
|
|
73
|
+
// Padrão agora é 'submit'
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
document.addEventListener("submit", handleSubmit, true);
|
|
78
|
+
return () => {
|
|
79
|
+
document.removeEventListener("submit", handleSubmit, true);
|
|
80
|
+
};
|
|
81
|
+
}, [enabled]);
|
|
82
|
+
};
|
|
83
|
+
|
|
54
84
|
// src/TrackingProvider.tsx
|
|
55
85
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
56
86
|
var TrackingProvider = ({
|
|
@@ -59,7 +89,8 @@ var TrackingProvider = ({
|
|
|
59
89
|
debug = false
|
|
60
90
|
}) => {
|
|
61
91
|
useAutoTrackClick(true);
|
|
62
|
-
|
|
92
|
+
useAutoTrackSubmit(true);
|
|
93
|
+
useEffect3(() => {
|
|
63
94
|
if (!gtmId || typeof window === "undefined") {
|
|
64
95
|
if (debug && !gtmId)
|
|
65
96
|
console.warn("[NineTwo Tracking] GTM ID n\xE3o fornecido.");
|
|
@@ -79,14 +110,14 @@ var TrackingProvider = ({
|
|
|
79
110
|
const script = document.createElement("script");
|
|
80
111
|
script.id = scriptId;
|
|
81
112
|
script.async = true;
|
|
82
|
-
script.src = `https://www.googletagmanager.com/
|
|
113
|
+
script.src = `https://www.googletagmanager.com/gtm.js?id=${gtmId}`;
|
|
83
114
|
script.onload = () => {
|
|
84
115
|
if (debug)
|
|
85
116
|
console.log(`[NineTwo Tracking] \u2705 GTM carregado com sucesso! (${gtmId})`);
|
|
86
117
|
};
|
|
87
118
|
script.onerror = () => {
|
|
88
119
|
if (debug)
|
|
89
|
-
console.error("[NineTwo Tracking] \u274C Erro ao carregar GTM.
|
|
120
|
+
console.error("[NineTwo Tracking] \u274C Erro ao carregar GTM.");
|
|
90
121
|
};
|
|
91
122
|
document.head.appendChild(script);
|
|
92
123
|
}, [gtmId, debug]);
|
|
@@ -94,37 +125,66 @@ var TrackingProvider = ({
|
|
|
94
125
|
};
|
|
95
126
|
|
|
96
127
|
// src/components/TrackView.tsx
|
|
97
|
-
import { useEffect as
|
|
128
|
+
import { useEffect as useEffect4, useRef, useState } from "react";
|
|
98
129
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
99
130
|
var TrackView = ({
|
|
100
131
|
children,
|
|
101
132
|
eventName,
|
|
102
133
|
category,
|
|
103
134
|
label,
|
|
104
|
-
threshold = 0.5
|
|
135
|
+
threshold = 0.5,
|
|
136
|
+
readTime = 5e3
|
|
105
137
|
}) => {
|
|
106
138
|
const ref = useRef(null);
|
|
107
|
-
const
|
|
108
|
-
|
|
139
|
+
const timerRef = useRef(null);
|
|
140
|
+
const [hasTriggeredView, setHasTriggeredView] = useState(false);
|
|
141
|
+
const [hasTriggeredRead, setHasTriggeredRead] = useState(false);
|
|
142
|
+
useEffect4(() => {
|
|
143
|
+
if (!ref.current)
|
|
144
|
+
return;
|
|
145
|
+
if (hasTriggeredView && hasTriggeredRead)
|
|
146
|
+
return;
|
|
109
147
|
const observer = new IntersectionObserver(
|
|
110
148
|
([entry]) => {
|
|
111
|
-
if (entry.isIntersecting
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
149
|
+
if (entry.isIntersecting) {
|
|
150
|
+
if (!hasTriggeredView) {
|
|
151
|
+
pushToDataLayer({
|
|
152
|
+
event: eventName,
|
|
153
|
+
category,
|
|
154
|
+
label,
|
|
155
|
+
type: "view"
|
|
156
|
+
});
|
|
157
|
+
setHasTriggeredView(true);
|
|
158
|
+
}
|
|
159
|
+
if (!hasTriggeredRead && !timerRef.current) {
|
|
160
|
+
timerRef.current = setTimeout(() => {
|
|
161
|
+
pushToDataLayer({
|
|
162
|
+
event: `${eventName}_read_confirmation`,
|
|
163
|
+
// Sufixo solicitado
|
|
164
|
+
category,
|
|
165
|
+
label,
|
|
166
|
+
type: "read_confirmation"
|
|
167
|
+
// Tipo diferenciado
|
|
168
|
+
});
|
|
169
|
+
setHasTriggeredRead(true);
|
|
170
|
+
}, readTime);
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
if (timerRef.current) {
|
|
174
|
+
clearTimeout(timerRef.current);
|
|
175
|
+
timerRef.current = null;
|
|
176
|
+
}
|
|
120
177
|
}
|
|
121
178
|
},
|
|
122
179
|
{ threshold }
|
|
123
180
|
);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
181
|
+
observer.observe(ref.current);
|
|
182
|
+
return () => {
|
|
183
|
+
observer.disconnect();
|
|
184
|
+
if (timerRef.current)
|
|
185
|
+
clearTimeout(timerRef.current);
|
|
186
|
+
};
|
|
187
|
+
}, [hasTriggeredView, hasTriggeredRead, eventName, category, label, threshold, readTime]);
|
|
128
188
|
return /* @__PURE__ */ jsx2("div", { ref, style: { display: "contents" }, children });
|
|
129
189
|
};
|
|
130
190
|
export {
|