@grainql/analytics-web 2.6.0 → 2.7.1
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/attention-quality.d.ts +128 -0
- package/dist/attention-quality.d.ts.map +1 -0
- package/dist/attention-quality.js +246 -0
- package/dist/cjs/attention-quality.d.ts +128 -0
- package/dist/cjs/attention-quality.d.ts.map +1 -0
- package/dist/cjs/attention-quality.js +246 -0
- package/dist/cjs/attention-quality.js.map +1 -0
- package/dist/cjs/heatmap-tracking.d.ts +3 -0
- package/dist/cjs/heatmap-tracking.d.ts.map +1 -1
- package/dist/cjs/heatmap-tracking.js +36 -1
- package/dist/cjs/heatmap-tracking.js.map +1 -1
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/interaction-tracking.d.ts.map +1 -1
- package/dist/cjs/interaction-tracking.js +2 -1
- package/dist/cjs/interaction-tracking.js.map +1 -1
- package/dist/cjs/section-tracking.d.ts +3 -0
- package/dist/cjs/section-tracking.d.ts.map +1 -1
- package/dist/cjs/section-tracking.js +29 -0
- package/dist/cjs/section-tracking.js.map +1 -1
- package/dist/cjs/text-utils.d.ts +14 -0
- package/dist/cjs/text-utils.d.ts.map +1 -0
- package/dist/cjs/text-utils.js +49 -0
- package/dist/cjs/text-utils.js.map +1 -0
- package/dist/cjs/types/auto-tracking.d.ts +3 -0
- package/dist/cjs/types/auto-tracking.d.ts.map +1 -1
- package/dist/cjs/types/heatmap-tracking.d.ts +3 -0
- package/dist/cjs/types/heatmap-tracking.d.ts.map +1 -1
- package/dist/esm/attention-quality.d.ts +128 -0
- package/dist/esm/attention-quality.d.ts.map +1 -0
- package/dist/esm/attention-quality.js +242 -0
- package/dist/esm/attention-quality.js.map +1 -0
- package/dist/esm/heatmap-tracking.d.ts +3 -0
- package/dist/esm/heatmap-tracking.d.ts.map +1 -1
- package/dist/esm/heatmap-tracking.js +36 -1
- package/dist/esm/heatmap-tracking.js.map +1 -1
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/interaction-tracking.d.ts.map +1 -1
- package/dist/esm/interaction-tracking.js +2 -1
- package/dist/esm/interaction-tracking.js.map +1 -1
- package/dist/esm/section-tracking.d.ts +3 -0
- package/dist/esm/section-tracking.d.ts.map +1 -1
- package/dist/esm/section-tracking.js +29 -0
- package/dist/esm/section-tracking.js.map +1 -1
- package/dist/esm/text-utils.d.ts +14 -0
- package/dist/esm/text-utils.d.ts.map +1 -0
- package/dist/esm/text-utils.js +45 -0
- package/dist/esm/text-utils.js.map +1 -0
- package/dist/esm/types/auto-tracking.d.ts +3 -0
- package/dist/esm/types/auto-tracking.d.ts.map +1 -1
- package/dist/esm/types/heatmap-tracking.d.ts +3 -0
- package/dist/esm/types/heatmap-tracking.d.ts.map +1 -1
- package/dist/heatmap-tracking.d.ts +3 -0
- package/dist/heatmap-tracking.d.ts.map +1 -1
- package/dist/heatmap-tracking.js +36 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.global.dev.js +330 -11
- package/dist/index.global.dev.js.map +4 -4
- package/dist/index.global.js +2 -2
- package/dist/index.global.js.map +4 -4
- package/dist/index.js +9 -0
- package/dist/index.mjs +9 -0
- package/dist/interaction-tracking.d.ts.map +1 -1
- package/dist/interaction-tracking.js +2 -1
- package/dist/section-tracking.d.ts +3 -0
- package/dist/section-tracking.d.ts.map +1 -1
- package/dist/section-tracking.js +29 -0
- package/dist/text-utils.d.ts +14 -0
- package/dist/text-utils.d.ts.map +1 -0
- package/dist/text-utils.js +49 -0
- package/dist/types/auto-tracking.d.ts +3 -0
- package/dist/types/auto-tracking.d.ts.map +1 -1
- package/dist/types/heatmap-tracking.d.ts +3 -0
- package/dist/types/heatmap-tracking.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.global.dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Grain Analytics Web SDK v2.
|
|
1
|
+
/* Grain Analytics Web SDK v2.7.1 | MIT License | Development Build */
|
|
2
2
|
"use strict";
|
|
3
3
|
var Grain = (() => {
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -22,23 +22,271 @@ var Grain = (() => {
|
|
|
22
22
|
};
|
|
23
23
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
24
24
|
|
|
25
|
+
// src/attention-quality.ts
|
|
26
|
+
var DEFAULT_OPTIONS, AttentionQualityManager;
|
|
27
|
+
var init_attention_quality = __esm({
|
|
28
|
+
"src/attention-quality.ts"() {
|
|
29
|
+
"use strict";
|
|
30
|
+
DEFAULT_OPTIONS = {
|
|
31
|
+
maxSectionDuration: 9e3,
|
|
32
|
+
// 9 seconds
|
|
33
|
+
minScrollDistance: 100,
|
|
34
|
+
// 100 pixels
|
|
35
|
+
idleThreshold: 3e4
|
|
36
|
+
// 30 seconds
|
|
37
|
+
};
|
|
38
|
+
AttentionQualityManager = class {
|
|
39
|
+
constructor(activityDetector, options = {}) {
|
|
40
|
+
this.isDestroyed = false;
|
|
41
|
+
// Page visibility tracking
|
|
42
|
+
this.isPageVisible = true;
|
|
43
|
+
this.visibilityChangeHandler = null;
|
|
44
|
+
// Section attention state
|
|
45
|
+
this.sectionStates = /* @__PURE__ */ new Map();
|
|
46
|
+
// Policies applied reasons (for debugging/traceability)
|
|
47
|
+
this.lastFilterReason = null;
|
|
48
|
+
this.activityDetector = activityDetector;
|
|
49
|
+
this.options = {
|
|
50
|
+
...DEFAULT_OPTIONS,
|
|
51
|
+
...options,
|
|
52
|
+
debug: options.debug ?? false
|
|
53
|
+
};
|
|
54
|
+
this.setupPageVisibilityTracking();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Setup page visibility tracking
|
|
58
|
+
*/
|
|
59
|
+
setupPageVisibilityTracking() {
|
|
60
|
+
if (typeof document === "undefined")
|
|
61
|
+
return;
|
|
62
|
+
this.isPageVisible = document.visibilityState === "visible";
|
|
63
|
+
this.visibilityChangeHandler = () => {
|
|
64
|
+
const wasVisible = this.isPageVisible;
|
|
65
|
+
this.isPageVisible = document.visibilityState === "visible";
|
|
66
|
+
if (!this.isPageVisible && wasVisible) {
|
|
67
|
+
this.log("Page hidden - tracking paused");
|
|
68
|
+
} else if (this.isPageVisible && !wasVisible) {
|
|
69
|
+
this.log("Page visible - tracking resumed");
|
|
70
|
+
this.resetAllSections();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
document.addEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if tracking should be allowed (global check)
|
|
77
|
+
* Returns true if tracking is allowed, false if it should be paused
|
|
78
|
+
*/
|
|
79
|
+
shouldTrack() {
|
|
80
|
+
if (!this.isPageVisible) {
|
|
81
|
+
this.lastFilterReason = "page_hidden";
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if (!this.activityDetector.isActive(this.options.idleThreshold)) {
|
|
85
|
+
this.lastFilterReason = "user_idle";
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
this.lastFilterReason = null;
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if section view tracking should be allowed for a specific section
|
|
93
|
+
* @param sectionName - Section identifier
|
|
94
|
+
* @param currentScrollY - Current scroll position
|
|
95
|
+
* @returns Object with shouldTrack boolean and optional reason
|
|
96
|
+
*/
|
|
97
|
+
shouldTrackSection(sectionName, currentScrollY) {
|
|
98
|
+
if (!this.shouldTrack()) {
|
|
99
|
+
return {
|
|
100
|
+
shouldTrack: false,
|
|
101
|
+
reason: this.lastFilterReason || "global_policy"
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
let state = this.sectionStates.get(sectionName);
|
|
105
|
+
if (!state) {
|
|
106
|
+
state = {
|
|
107
|
+
sectionName,
|
|
108
|
+
currentDuration: 0,
|
|
109
|
+
lastScrollPosition: currentScrollY,
|
|
110
|
+
lastResetTime: Date.now()
|
|
111
|
+
};
|
|
112
|
+
this.sectionStates.set(sectionName, state);
|
|
113
|
+
}
|
|
114
|
+
const scrollDistance = Math.abs(currentScrollY - state.lastScrollPosition);
|
|
115
|
+
const hasScrolledEnough = scrollDistance >= this.options.minScrollDistance;
|
|
116
|
+
if (hasScrolledEnough) {
|
|
117
|
+
this.log(`Section "${sectionName}": Attention reset due to ${Math.round(scrollDistance)}px scroll`);
|
|
118
|
+
state.currentDuration = 0;
|
|
119
|
+
state.lastScrollPosition = currentScrollY;
|
|
120
|
+
state.lastResetTime = Date.now();
|
|
121
|
+
return {
|
|
122
|
+
shouldTrack: true,
|
|
123
|
+
resetAttention: true
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (state.currentDuration >= this.options.maxSectionDuration) {
|
|
127
|
+
return {
|
|
128
|
+
shouldTrack: false,
|
|
129
|
+
reason: "max_duration_reached"
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
shouldTrack: true
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Update section duration (call this when tracking a section view event)
|
|
138
|
+
* @param sectionName - Section identifier
|
|
139
|
+
* @param durationMs - Duration to add to current attention block
|
|
140
|
+
*/
|
|
141
|
+
updateSectionDuration(sectionName, durationMs) {
|
|
142
|
+
const state = this.sectionStates.get(sectionName);
|
|
143
|
+
if (state) {
|
|
144
|
+
state.currentDuration += durationMs;
|
|
145
|
+
if (state.currentDuration >= this.options.maxSectionDuration) {
|
|
146
|
+
this.log(`Section "${sectionName}": Max duration cap reached (${state.currentDuration}ms)`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Reset attention for a specific section (call when user navigates to different section)
|
|
152
|
+
* @param sectionName - Section identifier
|
|
153
|
+
*/
|
|
154
|
+
resetSection(sectionName) {
|
|
155
|
+
const state = this.sectionStates.get(sectionName);
|
|
156
|
+
if (state) {
|
|
157
|
+
this.log(`Section "${sectionName}": Attention reset (section exit)`);
|
|
158
|
+
state.currentDuration = 0;
|
|
159
|
+
state.lastResetTime = Date.now();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Reset all section attention states
|
|
164
|
+
*/
|
|
165
|
+
resetAllSections() {
|
|
166
|
+
this.log("Resetting all section attention states");
|
|
167
|
+
for (const state of this.sectionStates.values()) {
|
|
168
|
+
state.currentDuration = 0;
|
|
169
|
+
state.lastResetTime = Date.now();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get current attention state for a section (for debugging/monitoring)
|
|
174
|
+
*/
|
|
175
|
+
getSectionState(sectionName) {
|
|
176
|
+
return this.sectionStates.get(sectionName);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get reason why last tracking attempt was filtered
|
|
180
|
+
*/
|
|
181
|
+
getLastFilterReason() {
|
|
182
|
+
return this.lastFilterReason;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Check if scroll tracking should be allowed
|
|
186
|
+
* Similar to shouldTrack() but also checks scroll-specific conditions
|
|
187
|
+
*/
|
|
188
|
+
shouldTrackScroll(previousScrollY, currentScrollY) {
|
|
189
|
+
if (!this.shouldTrack()) {
|
|
190
|
+
return {
|
|
191
|
+
shouldTrack: false,
|
|
192
|
+
reason: this.lastFilterReason || "global_policy"
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const scrollDistance = Math.abs(currentScrollY - previousScrollY);
|
|
196
|
+
if (scrollDistance < 10) {
|
|
197
|
+
return {
|
|
198
|
+
shouldTrack: false,
|
|
199
|
+
reason: "scroll_too_small"
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
shouldTrack: true
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get all active policies as object (for monitoring/debugging)
|
|
208
|
+
*/
|
|
209
|
+
getPolicies() {
|
|
210
|
+
return {
|
|
211
|
+
maxSectionDuration: this.options.maxSectionDuration,
|
|
212
|
+
minScrollDistance: this.options.minScrollDistance,
|
|
213
|
+
idleThreshold: this.options.idleThreshold
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get current tracking state (for monitoring/debugging)
|
|
218
|
+
*/
|
|
219
|
+
getTrackingState() {
|
|
220
|
+
return {
|
|
221
|
+
isPageVisible: this.isPageVisible,
|
|
222
|
+
isUserActive: this.activityDetector.isActive(this.options.idleThreshold),
|
|
223
|
+
timeSinceLastActivity: this.activityDetector.getTimeSinceLastActivity(),
|
|
224
|
+
activeSections: this.sectionStates.size
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Log debug messages
|
|
229
|
+
*/
|
|
230
|
+
log(...args) {
|
|
231
|
+
if (this.options.debug) {
|
|
232
|
+
console.log("[AttentionQuality]", ...args);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Destroy and cleanup
|
|
237
|
+
*/
|
|
238
|
+
destroy() {
|
|
239
|
+
if (this.isDestroyed)
|
|
240
|
+
return;
|
|
241
|
+
this.isDestroyed = true;
|
|
242
|
+
if (this.visibilityChangeHandler && typeof document !== "undefined") {
|
|
243
|
+
document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
244
|
+
this.visibilityChangeHandler = null;
|
|
245
|
+
}
|
|
246
|
+
this.sectionStates.clear();
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// src/text-utils.ts
|
|
253
|
+
function removeEmojis(text) {
|
|
254
|
+
if (!text)
|
|
255
|
+
return void 0;
|
|
256
|
+
return text.replace(/[\u{1F300}-\u{1F9FF}]/gu, "").replace(/[\u{1F600}-\u{1F64F}]/gu, "").replace(/[\u{1F680}-\u{1F6FF}]/gu, "").replace(/[\u{2600}-\u{26FF}]/gu, "").replace(/[\u{2700}-\u{27BF}]/gu, "").replace(/[\u{1F900}-\u{1F9FF}]/gu, "").replace(/[\u{1F1E0}-\u{1F1FF}]/gu, "").replace(/[\u{200D}]/gu, "").replace(/[\u{FE0F}]/gu, "").replace(/[\u{20E3}]/gu, "").trim();
|
|
257
|
+
}
|
|
258
|
+
function cleanElementText(text, maxLength = 100) {
|
|
259
|
+
if (!text)
|
|
260
|
+
return void 0;
|
|
261
|
+
const cleaned = removeEmojis(text);
|
|
262
|
+
if (!cleaned)
|
|
263
|
+
return void 0;
|
|
264
|
+
return cleaned.substring(0, maxLength) || void 0;
|
|
265
|
+
}
|
|
266
|
+
var init_text_utils = __esm({
|
|
267
|
+
"src/text-utils.ts"() {
|
|
268
|
+
"use strict";
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
25
272
|
// src/heatmap-tracking.ts
|
|
26
273
|
var heatmap_tracking_exports = {};
|
|
27
274
|
__export(heatmap_tracking_exports, {
|
|
28
275
|
HeatmapTrackingManager: () => HeatmapTrackingManager
|
|
29
276
|
});
|
|
30
|
-
var
|
|
277
|
+
var DEFAULT_OPTIONS2, HeatmapTrackingManager;
|
|
31
278
|
var init_heatmap_tracking = __esm({
|
|
32
279
|
"src/heatmap-tracking.ts"() {
|
|
33
280
|
"use strict";
|
|
34
|
-
|
|
281
|
+
init_attention_quality();
|
|
282
|
+
init_text_utils();
|
|
283
|
+
DEFAULT_OPTIONS2 = {
|
|
35
284
|
scrollDebounceDelay: 100,
|
|
36
285
|
batchDelay: 2e3,
|
|
37
286
|
maxBatchSize: 20,
|
|
38
287
|
debug: false
|
|
39
288
|
};
|
|
40
289
|
HeatmapTrackingManager = class {
|
|
41
|
-
// 3 seconds - same as section tracking
|
|
42
290
|
constructor(tracker, options = {}) {
|
|
43
291
|
this.isDestroyed = false;
|
|
44
292
|
// Tracking state
|
|
@@ -55,7 +303,19 @@ var Grain = (() => {
|
|
|
55
303
|
this.lastScrollTime = Date.now();
|
|
56
304
|
this.SPLIT_DURATION = 3e3;
|
|
57
305
|
this.tracker = tracker;
|
|
58
|
-
this.options = { ...
|
|
306
|
+
this.options = { ...DEFAULT_OPTIONS2, ...options };
|
|
307
|
+
this.attentionQuality = new AttentionQualityManager(
|
|
308
|
+
tracker.getActivityDetector(),
|
|
309
|
+
{
|
|
310
|
+
maxSectionDuration: 9e3,
|
|
311
|
+
// 9 seconds per viewport section
|
|
312
|
+
minScrollDistance: 100,
|
|
313
|
+
// 100 pixels
|
|
314
|
+
idleThreshold: 3e4,
|
|
315
|
+
// 30 seconds
|
|
316
|
+
debug: this.options.debug
|
|
317
|
+
}
|
|
318
|
+
);
|
|
59
319
|
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
60
320
|
if (document.readyState === "loading") {
|
|
61
321
|
document.addEventListener("DOMContentLoaded", () => this.initialize());
|
|
@@ -133,12 +393,27 @@ var Grain = (() => {
|
|
|
133
393
|
return;
|
|
134
394
|
if (!this.tracker.hasConsent("analytics"))
|
|
135
395
|
return;
|
|
396
|
+
if (!this.attentionQuality.shouldTrack()) {
|
|
397
|
+
this.log("Scroll tracking paused:", this.attentionQuality.getLastFilterReason());
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
136
400
|
const currentTime = Date.now();
|
|
137
401
|
const duration = currentTime - this.currentScrollState.entryTime;
|
|
138
402
|
if (duration > 1e3) {
|
|
139
403
|
const scrollY = window.scrollY || window.pageYOffset;
|
|
140
404
|
const viewportHeight = window.innerHeight;
|
|
141
405
|
const pageHeight = document.documentElement.scrollHeight;
|
|
406
|
+
const sectionKey = `viewport_section_${this.currentScrollState.viewportSection}`;
|
|
407
|
+
const attentionCheck = this.attentionQuality.shouldTrackSection(sectionKey, scrollY);
|
|
408
|
+
if (attentionCheck.resetAttention) {
|
|
409
|
+
this.log(`Viewport section ${this.currentScrollState.viewportSection}: Attention reset`);
|
|
410
|
+
this.currentScrollState.entryTime = currentTime;
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (!attentionCheck.shouldTrack) {
|
|
414
|
+
this.log(`Viewport section ${this.currentScrollState.viewportSection}: ${attentionCheck.reason}`);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
142
417
|
const scrollData = {
|
|
143
418
|
pageUrl: window.location.href,
|
|
144
419
|
viewportSection: this.currentScrollState.viewportSection,
|
|
@@ -161,6 +436,7 @@ var Grain = (() => {
|
|
|
161
436
|
is_split: true
|
|
162
437
|
// Flag to indicate periodic tracking, not final exit
|
|
163
438
|
}, { flush: true });
|
|
439
|
+
this.attentionQuality.updateSectionDuration(sectionKey, duration);
|
|
164
440
|
this.currentScrollState.entryTime = currentTime;
|
|
165
441
|
}
|
|
166
442
|
}, this.SPLIT_DURATION);
|
|
@@ -210,7 +486,7 @@ var Grain = (() => {
|
|
|
210
486
|
const pageX = Math.round(event.pageX);
|
|
211
487
|
const pageY = Math.round(event.pageY);
|
|
212
488
|
const elementTag = element.tagName?.toLowerCase() || "unknown";
|
|
213
|
-
const elementText = element.textContent
|
|
489
|
+
const elementText = cleanElementText(element.textContent);
|
|
214
490
|
const clickData = {
|
|
215
491
|
pageUrl,
|
|
216
492
|
xpath,
|
|
@@ -276,6 +552,8 @@ var Grain = (() => {
|
|
|
276
552
|
};
|
|
277
553
|
this.pendingScrolls.push(scrollData);
|
|
278
554
|
}
|
|
555
|
+
const prevSectionKey = `viewport_section_${this.currentScrollState.viewportSection}`;
|
|
556
|
+
this.attentionQuality.resetSection(prevSectionKey);
|
|
279
557
|
}
|
|
280
558
|
if (!this.currentScrollState || this.currentScrollState.viewportSection !== viewportSection) {
|
|
281
559
|
this.currentScrollState = {
|
|
@@ -448,6 +726,7 @@ var Grain = (() => {
|
|
|
448
726
|
clearInterval(this.periodicScrollTimer);
|
|
449
727
|
this.periodicScrollTimer = null;
|
|
450
728
|
}
|
|
729
|
+
this.attentionQuality.destroy();
|
|
451
730
|
this.flushPendingEvents();
|
|
452
731
|
}
|
|
453
732
|
};
|
|
@@ -463,6 +742,7 @@ var Grain = (() => {
|
|
|
463
742
|
var init_interaction_tracking = __esm({
|
|
464
743
|
"src/interaction-tracking.ts"() {
|
|
465
744
|
"use strict";
|
|
745
|
+
init_text_utils();
|
|
466
746
|
InteractionTrackingManager = class {
|
|
467
747
|
constructor(tracker, interactions, config = {}) {
|
|
468
748
|
this.isDestroyed = false;
|
|
@@ -541,7 +821,7 @@ var Grain = (() => {
|
|
|
541
821
|
interaction_description: interaction.description,
|
|
542
822
|
interaction_priority: interaction.priority,
|
|
543
823
|
element_tag: element.tagName?.toLowerCase(),
|
|
544
|
-
element_text: element.textContent
|
|
824
|
+
element_text: cleanElementText(element.textContent),
|
|
545
825
|
element_id: element.id || void 0,
|
|
546
826
|
element_class: element.className || void 0,
|
|
547
827
|
...isNavigationLink && { href: element.href },
|
|
@@ -720,11 +1000,12 @@ var Grain = (() => {
|
|
|
720
1000
|
__export(section_tracking_exports, {
|
|
721
1001
|
SectionTrackingManager: () => SectionTrackingManager
|
|
722
1002
|
});
|
|
723
|
-
var
|
|
1003
|
+
var DEFAULT_OPTIONS3, SectionTrackingManager;
|
|
724
1004
|
var init_section_tracking = __esm({
|
|
725
1005
|
"src/section-tracking.ts"() {
|
|
726
1006
|
"use strict";
|
|
727
|
-
|
|
1007
|
+
init_attention_quality();
|
|
1008
|
+
DEFAULT_OPTIONS3 = {
|
|
728
1009
|
minDwellTime: 1e3,
|
|
729
1010
|
// 1 second minimum
|
|
730
1011
|
scrollVelocityThreshold: 500,
|
|
@@ -737,7 +1018,6 @@ var Grain = (() => {
|
|
|
737
1018
|
debug: false
|
|
738
1019
|
};
|
|
739
1020
|
SectionTrackingManager = class {
|
|
740
|
-
// 3 seconds
|
|
741
1021
|
constructor(tracker, sections, options = {}) {
|
|
742
1022
|
this.isDestroyed = false;
|
|
743
1023
|
// Tracking state
|
|
@@ -758,7 +1038,19 @@ var Grain = (() => {
|
|
|
758
1038
|
this.SPLIT_DURATION = 3e3;
|
|
759
1039
|
this.tracker = tracker;
|
|
760
1040
|
this.sections = sections;
|
|
761
|
-
this.options = { ...
|
|
1041
|
+
this.options = { ...DEFAULT_OPTIONS3, ...options };
|
|
1042
|
+
this.attentionQuality = new AttentionQualityManager(
|
|
1043
|
+
tracker.getActivityDetector(),
|
|
1044
|
+
{
|
|
1045
|
+
maxSectionDuration: 9e3,
|
|
1046
|
+
// 9 seconds
|
|
1047
|
+
minScrollDistance: 100,
|
|
1048
|
+
// 100 pixels
|
|
1049
|
+
idleThreshold: 3e4,
|
|
1050
|
+
// 30 seconds
|
|
1051
|
+
debug: this.options.debug
|
|
1052
|
+
}
|
|
1053
|
+
);
|
|
762
1054
|
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
763
1055
|
if (document.readyState === "loading") {
|
|
764
1056
|
document.addEventListener("DOMContentLoaded", () => this.initialize());
|
|
@@ -892,6 +1184,21 @@ var Grain = (() => {
|
|
|
892
1184
|
const now = Date.now();
|
|
893
1185
|
const duration = now - state.entryTime;
|
|
894
1186
|
if (duration >= this.options.minDwellTime) {
|
|
1187
|
+
const currentScrollY = window.scrollY;
|
|
1188
|
+
const attentionCheck = this.attentionQuality.shouldTrackSection(
|
|
1189
|
+
state.config.sectionName,
|
|
1190
|
+
currentScrollY
|
|
1191
|
+
);
|
|
1192
|
+
if (attentionCheck.resetAttention) {
|
|
1193
|
+
this.log(`Section "${state.config.sectionName}": Attention reset, restarting timer`);
|
|
1194
|
+
state.entryTime = now;
|
|
1195
|
+
state.entryScrollSpeed = this.scrollVelocity;
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
if (!attentionCheck.shouldTrack) {
|
|
1199
|
+
this.log(`Section "${state.config.sectionName}": Tracking paused - ${attentionCheck.reason}`);
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
895
1202
|
const viewData = {
|
|
896
1203
|
sectionName: state.config.sectionName,
|
|
897
1204
|
sectionType: state.config.sectionType,
|
|
@@ -922,6 +1229,7 @@ var Grain = (() => {
|
|
|
922
1229
|
is_split: true
|
|
923
1230
|
// Flag to indicate this is a periodic split, not final exit
|
|
924
1231
|
});
|
|
1232
|
+
this.attentionQuality.updateSectionDuration(state.config.sectionName, duration);
|
|
925
1233
|
state.entryTime = now;
|
|
926
1234
|
state.entryScrollSpeed = this.scrollVelocity;
|
|
927
1235
|
}
|
|
@@ -944,6 +1252,7 @@ var Grain = (() => {
|
|
|
944
1252
|
*/
|
|
945
1253
|
handleSectionExit(state) {
|
|
946
1254
|
this.stopPeriodicTracking(state.config.sectionName);
|
|
1255
|
+
this.attentionQuality.resetSection(state.config.sectionName);
|
|
947
1256
|
if (state.entryTime === null)
|
|
948
1257
|
return;
|
|
949
1258
|
state.exitTime = Date.now();
|
|
@@ -1140,6 +1449,7 @@ var Grain = (() => {
|
|
|
1140
1449
|
this.intersectionObserver.disconnect();
|
|
1141
1450
|
this.intersectionObserver = null;
|
|
1142
1451
|
}
|
|
1452
|
+
this.attentionQuality.destroy();
|
|
1143
1453
|
this.sectionStates.clear();
|
|
1144
1454
|
this.xpathCache.clear();
|
|
1145
1455
|
this.pendingEvents = [];
|
|
@@ -5869,6 +6179,15 @@ var Grain = (() => {
|
|
|
5869
6179
|
resetEventCountSinceLastHeartbeat() {
|
|
5870
6180
|
this.eventCountSinceLastHeartbeat = 0;
|
|
5871
6181
|
}
|
|
6182
|
+
/**
|
|
6183
|
+
* Get the activity detector (for internal use by tracking managers)
|
|
6184
|
+
*/
|
|
6185
|
+
getActivityDetector() {
|
|
6186
|
+
if (!this.activityDetector) {
|
|
6187
|
+
throw new Error("Activity detector not initialized");
|
|
6188
|
+
}
|
|
6189
|
+
return this.activityDetector;
|
|
6190
|
+
}
|
|
5872
6191
|
/**
|
|
5873
6192
|
* Get the effective user ID (public method)
|
|
5874
6193
|
*/
|