cryptique-sdk 1.2.17 → 1.2.18
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/lib/cjs/index.js +212 -71
- package/lib/esm/index.js +212 -71
- package/lib/umd/index.js +212 -71
- package/package.json +2 -1
package/lib/cjs/index.js
CHANGED
|
@@ -5519,104 +5519,157 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5519
5519
|
|
|
5520
5520
|
/**
|
|
5521
5521
|
* ElementVisibilityTracker - Emits element_view auto events when elements
|
|
5522
|
-
*
|
|
5523
|
-
*
|
|
5524
|
-
*
|
|
5522
|
+
* enter the viewport for ≥ 1 second, capturing the actual dwell duration.
|
|
5523
|
+
*
|
|
5524
|
+
* Covers interactive elements (buttons, links, inputs, headings, images) and
|
|
5525
|
+
* any element with an id or data-cq-track attribute. Each viewport entry is
|
|
5526
|
+
* tracked independently — if a user scrolls away and back, both dwells count.
|
|
5527
|
+
* This gives accurate attention data for the heatmap attention overlay.
|
|
5525
5528
|
*/
|
|
5526
5529
|
startElementVisibilityTracking() {
|
|
5527
5530
|
try {
|
|
5528
5531
|
if (typeof IntersectionObserver === 'undefined') return;
|
|
5529
5532
|
|
|
5530
|
-
//
|
|
5533
|
+
// Observe interactive/structural elements in addition to id/data-cq-track
|
|
5534
|
+
const TRACKABLE_SELECTOR = [
|
|
5535
|
+
'[id]:not([id=""])', '[data-cq-track]',
|
|
5536
|
+
'button', 'a[href]', 'input:not([type=hidden])', 'select', 'textarea',
|
|
5537
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img[alt]',
|
|
5538
|
+
'[role="button"]', '[role="tab"]', '[role="menuitem"]', '[role="link"]'
|
|
5539
|
+
].join(', ');
|
|
5540
|
+
|
|
5531
5541
|
const getTrackableElements = () =>
|
|
5532
|
-
document.querySelectorAll(
|
|
5542
|
+
Array.from(document.querySelectorAll(TRACKABLE_SELECTOR)).filter(el => {
|
|
5543
|
+
try { return el.getBoundingClientRect().height >= 20; } catch (_) { return true; }
|
|
5544
|
+
});
|
|
5533
5545
|
|
|
5534
|
-
|
|
5535
|
-
const
|
|
5546
|
+
// enterTimes: key → { enterTime, el } — tracks elements currently in viewport
|
|
5547
|
+
const enterTimes = new Map();
|
|
5548
|
+
const observedEls = new WeakSet(); // prevents double-observing same DOM node
|
|
5549
|
+
|
|
5550
|
+
function elementKey(el) {
|
|
5551
|
+
return `${el.tagName}|${el.id || ''}|${el.getAttribute('data-cq-track') || ''}|${(el.textContent || '').trim().slice(0, 30)}`;
|
|
5552
|
+
}
|
|
5553
|
+
|
|
5554
|
+
function getElementData(el) {
|
|
5555
|
+
return {
|
|
5556
|
+
element_id: el.id || '',
|
|
5557
|
+
element_name: el.getAttribute('name') || el.getAttribute('data-cq-track') || '',
|
|
5558
|
+
element_tag_name: el.tagName || '',
|
|
5559
|
+
element_type: el.type || el.getAttribute('type') || '',
|
|
5560
|
+
element_class: el.className || '',
|
|
5561
|
+
element_text: (el.textContent || '').trim().slice(0, 100)
|
|
5562
|
+
};
|
|
5563
|
+
}
|
|
5536
5564
|
|
|
5537
5565
|
const observer = new IntersectionObserver((entries) => {
|
|
5538
5566
|
entries.forEach((entry) => {
|
|
5539
|
-
const el
|
|
5540
|
-
const key = el
|
|
5541
|
-
if (!key || reported.has(key)) return;
|
|
5567
|
+
const el = entry.target;
|
|
5568
|
+
const key = elementKey(el);
|
|
5542
5569
|
|
|
5543
5570
|
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
|
5544
|
-
// Element entered viewport —
|
|
5545
|
-
if (!
|
|
5546
|
-
|
|
5547
|
-
const timer = setTimeout(() => {
|
|
5548
|
-
if (reported.has(key)) return;
|
|
5549
|
-
reported.add(key);
|
|
5550
|
-
EventsManager.trackAutoEvent('element_view', {
|
|
5551
|
-
time_in_viewport_ms: Date.now() - enterTime,
|
|
5552
|
-
viewport_percent_visible: Math.round(entry.intersectionRatio * 100)
|
|
5553
|
-
}, {
|
|
5554
|
-
element_id: el.id || '',
|
|
5555
|
-
element_name: el.getAttribute('name') || el.getAttribute('data-cq-track') || '',
|
|
5556
|
-
element_tag_name: el.tagName || '',
|
|
5557
|
-
element_type: el.type || el.getAttribute('type') || '',
|
|
5558
|
-
element_class: el.className || '',
|
|
5559
|
-
element_text: (el.textContent || '').trim().slice(0, 100)
|
|
5560
|
-
});
|
|
5561
|
-
viewTimers.delete(key);
|
|
5562
|
-
}, 1000);
|
|
5563
|
-
viewTimers.set(key, timer);
|
|
5571
|
+
// Element entered viewport — record entry time
|
|
5572
|
+
if (!enterTimes.has(key)) {
|
|
5573
|
+
enterTimes.set(key, { enterTime: Date.now(), el });
|
|
5564
5574
|
}
|
|
5565
5575
|
} else {
|
|
5566
|
-
// Element left viewport
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5576
|
+
// Element left viewport — emit actual dwell if ≥ 1s
|
|
5577
|
+
const record = enterTimes.get(key);
|
|
5578
|
+
if (record) {
|
|
5579
|
+
const dwell = Date.now() - record.enterTime;
|
|
5580
|
+
enterTimes.delete(key);
|
|
5581
|
+
if (dwell >= 1000) {
|
|
5582
|
+
EventsManager.trackAutoEvent('element_view', {
|
|
5583
|
+
time_in_viewport_ms: dwell,
|
|
5584
|
+
viewport_percent_visible: Math.round(entry.intersectionRatio * 100),
|
|
5585
|
+
scroll_y_at_view: window.scrollY
|
|
5586
|
+
}, getElementData(el));
|
|
5587
|
+
}
|
|
5570
5588
|
}
|
|
5571
5589
|
}
|
|
5572
5590
|
});
|
|
5573
|
-
}, { threshold: 0.5 });
|
|
5591
|
+
}, { threshold: [0.5] });
|
|
5592
|
+
|
|
5593
|
+
const observe = (el) => {
|
|
5594
|
+
if (!observedEls.has(el)) { observedEls.add(el); observer.observe(el); }
|
|
5595
|
+
};
|
|
5574
5596
|
|
|
5575
|
-
|
|
5576
|
-
getTrackableElements().forEach(el => observer.observe(el));
|
|
5597
|
+
getTrackableElements().forEach(observe);
|
|
5577
5598
|
|
|
5578
5599
|
// Observe elements added to DOM later (SPAs)
|
|
5579
5600
|
if (typeof MutationObserver !== 'undefined') {
|
|
5580
5601
|
new MutationObserver(() => {
|
|
5581
|
-
getTrackableElements().forEach(
|
|
5582
|
-
if (!viewTimers.has(el.id || el.getAttribute('data-cq-track') || el.className)) {
|
|
5583
|
-
observer.observe(el);
|
|
5584
|
-
}
|
|
5585
|
-
});
|
|
5602
|
+
getTrackableElements().forEach(observe);
|
|
5586
5603
|
}).observe(document.body, { childList: true, subtree: true });
|
|
5587
5604
|
}
|
|
5588
|
-
|
|
5589
|
-
|
|
5605
|
+
|
|
5606
|
+
// On page unload, flush elements still in viewport
|
|
5607
|
+
window.addEventListener('pagehide', () => {
|
|
5608
|
+
enterTimes.forEach(({ enterTime, el }) => {
|
|
5609
|
+
const dwell = Date.now() - enterTime;
|
|
5610
|
+
if (dwell >= 1000) {
|
|
5611
|
+
EventsManager.trackAutoEvent('element_view', {
|
|
5612
|
+
time_in_viewport_ms: dwell,
|
|
5613
|
+
viewport_percent_visible: 100,
|
|
5614
|
+
scroll_y_at_view: window.scrollY
|
|
5615
|
+
}, getElementData(el));
|
|
5616
|
+
}
|
|
5617
|
+
});
|
|
5618
|
+
});
|
|
5619
|
+
} catch (error) {}
|
|
5590
5620
|
},
|
|
5591
5621
|
|
|
5592
5622
|
/**
|
|
5593
5623
|
* PageSummaryTracker — collects high-frequency signals in-memory and flushes
|
|
5594
5624
|
* them as a single 'page_summary' auto-event on page unload.
|
|
5595
5625
|
*
|
|
5596
|
-
*
|
|
5597
|
-
*
|
|
5598
|
-
*
|
|
5626
|
+
* High-volume/continuous data lives here (grids, counts) to keep event volume low.
|
|
5627
|
+
* Low-frequency, high-signal moments (exit_intent) are emitted as separate auto-events
|
|
5628
|
+
* but their counters are also included here for correlation.
|
|
5629
|
+
*
|
|
5630
|
+
* Captures:
|
|
5631
|
+
* - Mouse movement grid (move_grid) + hover dwell grid (hover_grid)
|
|
5632
|
+
* - Element-level hover dwells for named/interactive elements (element_dwells)
|
|
5599
5633
|
* - Tab-switch count and hidden time
|
|
5600
|
-
* - Scroll reversal count
|
|
5634
|
+
* - Scroll reversal count + depths at each reversal (scroll_reversal_depths)
|
|
5601
5635
|
* - Per-field focus dwell times
|
|
5636
|
+
* - Exit intent count + scroll depth at last exit intent
|
|
5637
|
+
* - First interaction time (how long before user engaged)
|
|
5638
|
+
* - Performance: CLS score, long task count/max, INP
|
|
5602
5639
|
*/
|
|
5603
5640
|
startPageSummaryTracking() {
|
|
5604
5641
|
try {
|
|
5605
5642
|
const GRID_SIZE = 40;
|
|
5606
|
-
// In-memory accumulators — never sent to the server individually
|
|
5607
|
-
const moveGrid = new Map(); // cellKey → move count
|
|
5608
|
-
const hoverGrid = new Map(); // cellKey → total dwell_ms
|
|
5609
|
-
const fieldDwells = []; // [{field_id, field_label, field_type, dwell_ms, was_filled}]
|
|
5610
|
-
|
|
5611
|
-
let tabSwitches = 0;
|
|
5612
|
-
let totalTabHiddenMs = 0;
|
|
5613
|
-
let tabHiddenTime = null;
|
|
5614
|
-
let scrollReversals = 0;
|
|
5615
5643
|
|
|
5616
|
-
//
|
|
5644
|
+
// In-memory accumulators — never sent to the server individually
|
|
5645
|
+
const moveGrid = new Map(); // cellKey → move count
|
|
5646
|
+
const hoverGrid = new Map(); // cellKey → total dwell_ms
|
|
5647
|
+
const fieldDwells = []; // [{field_id, field_label, field_type, dwell_ms, was_filled}]
|
|
5648
|
+
const elementDwellsMap = new Map(); // elemKey → {tag, id, text, dwell_ms}
|
|
5649
|
+
const scrollReversalDepths = []; // scroll depth % at each reversal
|
|
5650
|
+
|
|
5651
|
+
let tabSwitches = 0;
|
|
5652
|
+
let totalTabHiddenMs = 0;
|
|
5653
|
+
let tabHiddenTime = null;
|
|
5654
|
+
let scrollReversals = 0;
|
|
5655
|
+
let exitIntentCount = 0;
|
|
5656
|
+
let exitIntentLastScrollDepth = 0;
|
|
5657
|
+
let firstInteractionMs = null;
|
|
5658
|
+
let clsScore = 0;
|
|
5659
|
+
let longTasksCount = 0;
|
|
5660
|
+
let maxLongTaskMs = 0;
|
|
5661
|
+
let inpMs = 0;
|
|
5662
|
+
|
|
5663
|
+
const pageLoadTime = Date.now();
|
|
5617
5664
|
let lastSigScrollY = window.scrollY;
|
|
5618
5665
|
let lastSigDir = 0; // 1=down, -1=up
|
|
5619
5666
|
|
|
5667
|
+
// ── Helpers
|
|
5668
|
+
function getScrollDepth() {
|
|
5669
|
+
const maxScroll = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - window.innerHeight;
|
|
5670
|
+
return maxScroll <= 0 ? 100 : Math.round((window.scrollY / maxScroll) * 100);
|
|
5671
|
+
}
|
|
5672
|
+
|
|
5620
5673
|
function getGridCell(pageX, pageY) {
|
|
5621
5674
|
const docW = Math.max(1, document.documentElement.scrollWidth || document.body.scrollWidth || window.innerWidth);
|
|
5622
5675
|
const docH = Math.max(1, document.documentElement.scrollHeight || document.body.scrollHeight || window.innerHeight);
|
|
@@ -5625,6 +5678,39 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5625
5678
|
return col * GRID_SIZE + row;
|
|
5626
5679
|
}
|
|
5627
5680
|
|
|
5681
|
+
// ── PerformanceObserver: CLS, Long Tasks, INP (best-effort, not all browsers)
|
|
5682
|
+
try {
|
|
5683
|
+
new PerformanceObserver(list => {
|
|
5684
|
+
for (const e of list.getEntries()) {
|
|
5685
|
+
if (!e.hadRecentInput) clsScore = Math.round((clsScore + e.value) * 1000) / 1000;
|
|
5686
|
+
}
|
|
5687
|
+
}).observe({ type: 'layout-shift', buffered: true });
|
|
5688
|
+
} catch (_) {}
|
|
5689
|
+
try {
|
|
5690
|
+
new PerformanceObserver(list => {
|
|
5691
|
+
for (const e of list.getEntries()) {
|
|
5692
|
+
longTasksCount++;
|
|
5693
|
+
if (e.duration > maxLongTaskMs) maxLongTaskMs = Math.round(e.duration);
|
|
5694
|
+
}
|
|
5695
|
+
}).observe({ type: 'longtask', buffered: true });
|
|
5696
|
+
} catch (_) {}
|
|
5697
|
+
try {
|
|
5698
|
+
new PerformanceObserver(list => {
|
|
5699
|
+
for (const e of list.getEntries()) {
|
|
5700
|
+
if (e.duration > inpMs) inpMs = Math.round(e.duration);
|
|
5701
|
+
}
|
|
5702
|
+
}).observe({ type: 'event', durationThreshold: 40, buffered: true });
|
|
5703
|
+
} catch (_) {}
|
|
5704
|
+
|
|
5705
|
+
// ── First interaction: time from page load to first meaningful engagement
|
|
5706
|
+
const markFirstInteraction = () => {
|
|
5707
|
+
if (firstInteractionMs === null) firstInteractionMs = Date.now() - pageLoadTime;
|
|
5708
|
+
};
|
|
5709
|
+
document.addEventListener('click', markFirstInteraction, { once: true, passive: true });
|
|
5710
|
+
document.addEventListener('keydown', markFirstInteraction, { once: true, passive: true });
|
|
5711
|
+
document.addEventListener('touchstart', markFirstInteraction, { once: true, passive: true });
|
|
5712
|
+
window.addEventListener('scroll', markFirstInteraction, { once: true, passive: true });
|
|
5713
|
+
|
|
5628
5714
|
// ── Mouse movement → move_grid (throttled to max 1 sample / 100 ms)
|
|
5629
5715
|
let moveThrottle = false;
|
|
5630
5716
|
document.addEventListener('mousemove', (e) => {
|
|
@@ -5635,7 +5721,8 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5635
5721
|
moveGrid.set(key, (moveGrid.get(key) || 0) + 1);
|
|
5636
5722
|
}, { passive: true });
|
|
5637
5723
|
|
|
5638
|
-
// ── Hover dwell → hover_grid
|
|
5724
|
+
// ── Hover dwell → hover_grid + element_dwells for named/interactive elements
|
|
5725
|
+
const ELEMENT_DWELL_TAGS = new Set(['BUTTON', 'A', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'INPUT', 'SELECT', 'TEXTAREA', 'IMG', 'LABEL']);
|
|
5639
5726
|
const hoverStarts = new Map(); // element → enterTime
|
|
5640
5727
|
document.addEventListener('mouseover', (e) => {
|
|
5641
5728
|
if (e.target && e.target !== document.documentElement) {
|
|
@@ -5654,6 +5741,22 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5654
5741
|
const cy = rect.top + rect.height / 2 + window.scrollY;
|
|
5655
5742
|
const key = getGridCell(cx, cy);
|
|
5656
5743
|
hoverGrid.set(key, (hoverGrid.get(key) || 0) + dwell);
|
|
5744
|
+
|
|
5745
|
+
// Element-level dwell: track named/interactive elements hovered ≥ 300ms
|
|
5746
|
+
if (dwell >= 300 && (target.id || ELEMENT_DWELL_TAGS.has(target.tagName))) {
|
|
5747
|
+
const elemKey = `${target.tagName}|${target.id || ''}|${(target.textContent || '').trim().slice(0, 30)}`;
|
|
5748
|
+
const existing = elementDwellsMap.get(elemKey);
|
|
5749
|
+
if (existing) {
|
|
5750
|
+
existing.dwell_ms += dwell;
|
|
5751
|
+
} else {
|
|
5752
|
+
elementDwellsMap.set(elemKey, {
|
|
5753
|
+
tag: target.tagName.toLowerCase(),
|
|
5754
|
+
id: target.id || '',
|
|
5755
|
+
text: (target.textContent || '').trim().slice(0, 50),
|
|
5756
|
+
dwell_ms: dwell
|
|
5757
|
+
});
|
|
5758
|
+
}
|
|
5759
|
+
}
|
|
5657
5760
|
} catch (_) {}
|
|
5658
5761
|
}, { passive: true });
|
|
5659
5762
|
|
|
@@ -5668,14 +5771,15 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5668
5771
|
}
|
|
5669
5772
|
});
|
|
5670
5773
|
|
|
5671
|
-
// ── Scroll reversal detection (direction change
|
|
5774
|
+
// ── Scroll reversal detection (direction change ≥ 200 px) + depth recording
|
|
5672
5775
|
window.addEventListener('scroll', () => {
|
|
5673
5776
|
const curr = window.scrollY;
|
|
5674
5777
|
const diff = curr - lastSigScrollY;
|
|
5675
|
-
if (Math.abs(diff) < 50) return;
|
|
5676
|
-
const dir
|
|
5778
|
+
if (Math.abs(diff) < 50) return;
|
|
5779
|
+
const dir = diff > 0 ? 1 : -1;
|
|
5677
5780
|
if (lastSigDir !== 0 && dir !== lastSigDir && Math.abs(curr - lastSigScrollY) >= 200) {
|
|
5678
5781
|
scrollReversals++;
|
|
5782
|
+
scrollReversalDepths.push(getScrollDepth());
|
|
5679
5783
|
}
|
|
5680
5784
|
if (Math.abs(diff) >= 50) { lastSigDir = dir; lastSigScrollY = curr; }
|
|
5681
5785
|
}, { passive: true });
|
|
@@ -5693,7 +5797,7 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5693
5797
|
if (!t || !fieldFocusTimes.has(t)) return;
|
|
5694
5798
|
const dwell = Date.now() - fieldFocusTimes.get(t);
|
|
5695
5799
|
fieldFocusTimes.delete(t);
|
|
5696
|
-
if (dwell < 100) return;
|
|
5800
|
+
if (dwell < 100) return;
|
|
5697
5801
|
const label = t.id
|
|
5698
5802
|
? (document.querySelector(`label[for="${CSS.escape(t.id)}"]`)?.textContent?.trim() || t.placeholder || t.name || t.id)
|
|
5699
5803
|
: (t.placeholder || t.name || t.type || '');
|
|
@@ -5706,6 +5810,26 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5706
5810
|
});
|
|
5707
5811
|
}, true);
|
|
5708
5812
|
|
|
5813
|
+
// ── Exit intent: mouse leaving viewport toward the top (user about to navigate away)
|
|
5814
|
+
// Also fires as a standalone auto-event for direct querying.
|
|
5815
|
+
let lastExitIntentTime = 0;
|
|
5816
|
+
document.addEventListener('mouseleave', (e) => {
|
|
5817
|
+
if (e.clientY > 0 || e.relatedTarget !== null) return; // only top-edge exits
|
|
5818
|
+
const now = Date.now();
|
|
5819
|
+
if (now - lastExitIntentTime < 2000) return; // debounce: max once per 2s
|
|
5820
|
+
lastExitIntentTime = now;
|
|
5821
|
+
exitIntentCount++;
|
|
5822
|
+
exitIntentLastScrollDepth = getScrollDepth();
|
|
5823
|
+
EventsManager.trackAutoEvent('exit_intent', {
|
|
5824
|
+
scroll_depth: exitIntentLastScrollDepth,
|
|
5825
|
+
time_on_page_ms: now - pageLoadTime,
|
|
5826
|
+
scroll_y: window.scrollY,
|
|
5827
|
+
document_height: Math.min(
|
|
5828
|
+
document.documentElement.scrollHeight || document.body.scrollHeight, 25000
|
|
5829
|
+
)
|
|
5830
|
+
}).catch(() => {});
|
|
5831
|
+
});
|
|
5832
|
+
|
|
5709
5833
|
// ── Serialise grid Map to [[col, row, value], ...] sparse array
|
|
5710
5834
|
function serializeGrid(grid) {
|
|
5711
5835
|
const out = [];
|
|
@@ -5722,17 +5846,34 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5722
5846
|
let summarySent = false;
|
|
5723
5847
|
function firePageSummary() {
|
|
5724
5848
|
if (summarySent) return; // only fire once per page lifecycle
|
|
5725
|
-
|
|
5726
|
-
|
|
5849
|
+
const hasData = moveGrid.size > 0 || hoverGrid.size > 0 || fieldDwells.length > 0
|
|
5850
|
+
|| tabSwitches > 0 || scrollReversals > 0 || exitIntentCount > 0
|
|
5851
|
+
|| firstInteractionMs !== null || clsScore > 0 || longTasksCount > 0;
|
|
5852
|
+
if (!hasData) return;
|
|
5727
5853
|
summarySent = true;
|
|
5728
5854
|
try {
|
|
5855
|
+
// Serialize element_dwells: filter ≥ 300ms, sort desc, cap at 20
|
|
5856
|
+
const elementDwells = Array.from(elementDwellsMap.values())
|
|
5857
|
+
.filter(e => e.dwell_ms >= 300)
|
|
5858
|
+
.sort((a, b) => b.dwell_ms - a.dwell_ms)
|
|
5859
|
+
.slice(0, 20);
|
|
5860
|
+
|
|
5729
5861
|
EventsManager.trackAutoEvent('page_summary', {
|
|
5730
|
-
scroll_reversals:
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5862
|
+
scroll_reversals: scrollReversals,
|
|
5863
|
+
scroll_reversal_depths: scrollReversalDepths,
|
|
5864
|
+
tab_switches: tabSwitches,
|
|
5865
|
+
total_tab_hidden_ms: totalTabHiddenMs,
|
|
5866
|
+
move_grid: serializeGrid(moveGrid),
|
|
5867
|
+
hover_grid: serializeGrid(hoverGrid),
|
|
5868
|
+
field_dwells: fieldDwells,
|
|
5869
|
+
element_dwells: elementDwells,
|
|
5870
|
+
first_interaction_ms: firstInteractionMs,
|
|
5871
|
+
exit_intent_count: exitIntentCount,
|
|
5872
|
+
exit_intent_last_scroll_depth: exitIntentLastScrollDepth,
|
|
5873
|
+
cls_score: clsScore,
|
|
5874
|
+
long_tasks_count: longTasksCount,
|
|
5875
|
+
max_long_task_ms: maxLongTaskMs,
|
|
5876
|
+
inp_ms: inpMs,
|
|
5736
5877
|
document_height: document.documentElement.scrollHeight || document.body.scrollHeight || 0,
|
|
5737
5878
|
document_width: document.documentElement.scrollWidth || document.body.scrollWidth || 0,
|
|
5738
5879
|
viewport_width: window.innerWidth,
|
package/lib/esm/index.js
CHANGED
|
@@ -5517,104 +5517,157 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5517
5517
|
|
|
5518
5518
|
/**
|
|
5519
5519
|
* ElementVisibilityTracker - Emits element_view auto events when elements
|
|
5520
|
-
*
|
|
5521
|
-
*
|
|
5522
|
-
*
|
|
5520
|
+
* enter the viewport for ≥ 1 second, capturing the actual dwell duration.
|
|
5521
|
+
*
|
|
5522
|
+
* Covers interactive elements (buttons, links, inputs, headings, images) and
|
|
5523
|
+
* any element with an id or data-cq-track attribute. Each viewport entry is
|
|
5524
|
+
* tracked independently — if a user scrolls away and back, both dwells count.
|
|
5525
|
+
* This gives accurate attention data for the heatmap attention overlay.
|
|
5523
5526
|
*/
|
|
5524
5527
|
startElementVisibilityTracking() {
|
|
5525
5528
|
try {
|
|
5526
5529
|
if (typeof IntersectionObserver === 'undefined') return;
|
|
5527
5530
|
|
|
5528
|
-
//
|
|
5531
|
+
// Observe interactive/structural elements in addition to id/data-cq-track
|
|
5532
|
+
const TRACKABLE_SELECTOR = [
|
|
5533
|
+
'[id]:not([id=""])', '[data-cq-track]',
|
|
5534
|
+
'button', 'a[href]', 'input:not([type=hidden])', 'select', 'textarea',
|
|
5535
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img[alt]',
|
|
5536
|
+
'[role="button"]', '[role="tab"]', '[role="menuitem"]', '[role="link"]'
|
|
5537
|
+
].join(', ');
|
|
5538
|
+
|
|
5529
5539
|
const getTrackableElements = () =>
|
|
5530
|
-
document.querySelectorAll(
|
|
5540
|
+
Array.from(document.querySelectorAll(TRACKABLE_SELECTOR)).filter(el => {
|
|
5541
|
+
try { return el.getBoundingClientRect().height >= 20; } catch (_) { return true; }
|
|
5542
|
+
});
|
|
5531
5543
|
|
|
5532
|
-
|
|
5533
|
-
const
|
|
5544
|
+
// enterTimes: key → { enterTime, el } — tracks elements currently in viewport
|
|
5545
|
+
const enterTimes = new Map();
|
|
5546
|
+
const observedEls = new WeakSet(); // prevents double-observing same DOM node
|
|
5547
|
+
|
|
5548
|
+
function elementKey(el) {
|
|
5549
|
+
return `${el.tagName}|${el.id || ''}|${el.getAttribute('data-cq-track') || ''}|${(el.textContent || '').trim().slice(0, 30)}`;
|
|
5550
|
+
}
|
|
5551
|
+
|
|
5552
|
+
function getElementData(el) {
|
|
5553
|
+
return {
|
|
5554
|
+
element_id: el.id || '',
|
|
5555
|
+
element_name: el.getAttribute('name') || el.getAttribute('data-cq-track') || '',
|
|
5556
|
+
element_tag_name: el.tagName || '',
|
|
5557
|
+
element_type: el.type || el.getAttribute('type') || '',
|
|
5558
|
+
element_class: el.className || '',
|
|
5559
|
+
element_text: (el.textContent || '').trim().slice(0, 100)
|
|
5560
|
+
};
|
|
5561
|
+
}
|
|
5534
5562
|
|
|
5535
5563
|
const observer = new IntersectionObserver((entries) => {
|
|
5536
5564
|
entries.forEach((entry) => {
|
|
5537
|
-
const el
|
|
5538
|
-
const key = el
|
|
5539
|
-
if (!key || reported.has(key)) return;
|
|
5565
|
+
const el = entry.target;
|
|
5566
|
+
const key = elementKey(el);
|
|
5540
5567
|
|
|
5541
5568
|
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
|
5542
|
-
// Element entered viewport —
|
|
5543
|
-
if (!
|
|
5544
|
-
|
|
5545
|
-
const timer = setTimeout(() => {
|
|
5546
|
-
if (reported.has(key)) return;
|
|
5547
|
-
reported.add(key);
|
|
5548
|
-
EventsManager.trackAutoEvent('element_view', {
|
|
5549
|
-
time_in_viewport_ms: Date.now() - enterTime,
|
|
5550
|
-
viewport_percent_visible: Math.round(entry.intersectionRatio * 100)
|
|
5551
|
-
}, {
|
|
5552
|
-
element_id: el.id || '',
|
|
5553
|
-
element_name: el.getAttribute('name') || el.getAttribute('data-cq-track') || '',
|
|
5554
|
-
element_tag_name: el.tagName || '',
|
|
5555
|
-
element_type: el.type || el.getAttribute('type') || '',
|
|
5556
|
-
element_class: el.className || '',
|
|
5557
|
-
element_text: (el.textContent || '').trim().slice(0, 100)
|
|
5558
|
-
});
|
|
5559
|
-
viewTimers.delete(key);
|
|
5560
|
-
}, 1000);
|
|
5561
|
-
viewTimers.set(key, timer);
|
|
5569
|
+
// Element entered viewport — record entry time
|
|
5570
|
+
if (!enterTimes.has(key)) {
|
|
5571
|
+
enterTimes.set(key, { enterTime: Date.now(), el });
|
|
5562
5572
|
}
|
|
5563
5573
|
} else {
|
|
5564
|
-
// Element left viewport
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5574
|
+
// Element left viewport — emit actual dwell if ≥ 1s
|
|
5575
|
+
const record = enterTimes.get(key);
|
|
5576
|
+
if (record) {
|
|
5577
|
+
const dwell = Date.now() - record.enterTime;
|
|
5578
|
+
enterTimes.delete(key);
|
|
5579
|
+
if (dwell >= 1000) {
|
|
5580
|
+
EventsManager.trackAutoEvent('element_view', {
|
|
5581
|
+
time_in_viewport_ms: dwell,
|
|
5582
|
+
viewport_percent_visible: Math.round(entry.intersectionRatio * 100),
|
|
5583
|
+
scroll_y_at_view: window.scrollY
|
|
5584
|
+
}, getElementData(el));
|
|
5585
|
+
}
|
|
5568
5586
|
}
|
|
5569
5587
|
}
|
|
5570
5588
|
});
|
|
5571
|
-
}, { threshold: 0.5 });
|
|
5589
|
+
}, { threshold: [0.5] });
|
|
5590
|
+
|
|
5591
|
+
const observe = (el) => {
|
|
5592
|
+
if (!observedEls.has(el)) { observedEls.add(el); observer.observe(el); }
|
|
5593
|
+
};
|
|
5572
5594
|
|
|
5573
|
-
|
|
5574
|
-
getTrackableElements().forEach(el => observer.observe(el));
|
|
5595
|
+
getTrackableElements().forEach(observe);
|
|
5575
5596
|
|
|
5576
5597
|
// Observe elements added to DOM later (SPAs)
|
|
5577
5598
|
if (typeof MutationObserver !== 'undefined') {
|
|
5578
5599
|
new MutationObserver(() => {
|
|
5579
|
-
getTrackableElements().forEach(
|
|
5580
|
-
if (!viewTimers.has(el.id || el.getAttribute('data-cq-track') || el.className)) {
|
|
5581
|
-
observer.observe(el);
|
|
5582
|
-
}
|
|
5583
|
-
});
|
|
5600
|
+
getTrackableElements().forEach(observe);
|
|
5584
5601
|
}).observe(document.body, { childList: true, subtree: true });
|
|
5585
5602
|
}
|
|
5586
|
-
|
|
5587
|
-
|
|
5603
|
+
|
|
5604
|
+
// On page unload, flush elements still in viewport
|
|
5605
|
+
window.addEventListener('pagehide', () => {
|
|
5606
|
+
enterTimes.forEach(({ enterTime, el }) => {
|
|
5607
|
+
const dwell = Date.now() - enterTime;
|
|
5608
|
+
if (dwell >= 1000) {
|
|
5609
|
+
EventsManager.trackAutoEvent('element_view', {
|
|
5610
|
+
time_in_viewport_ms: dwell,
|
|
5611
|
+
viewport_percent_visible: 100,
|
|
5612
|
+
scroll_y_at_view: window.scrollY
|
|
5613
|
+
}, getElementData(el));
|
|
5614
|
+
}
|
|
5615
|
+
});
|
|
5616
|
+
});
|
|
5617
|
+
} catch (error) {}
|
|
5588
5618
|
},
|
|
5589
5619
|
|
|
5590
5620
|
/**
|
|
5591
5621
|
* PageSummaryTracker — collects high-frequency signals in-memory and flushes
|
|
5592
5622
|
* them as a single 'page_summary' auto-event on page unload.
|
|
5593
5623
|
*
|
|
5594
|
-
*
|
|
5595
|
-
*
|
|
5596
|
-
*
|
|
5624
|
+
* High-volume/continuous data lives here (grids, counts) to keep event volume low.
|
|
5625
|
+
* Low-frequency, high-signal moments (exit_intent) are emitted as separate auto-events
|
|
5626
|
+
* but their counters are also included here for correlation.
|
|
5627
|
+
*
|
|
5628
|
+
* Captures:
|
|
5629
|
+
* - Mouse movement grid (move_grid) + hover dwell grid (hover_grid)
|
|
5630
|
+
* - Element-level hover dwells for named/interactive elements (element_dwells)
|
|
5597
5631
|
* - Tab-switch count and hidden time
|
|
5598
|
-
* - Scroll reversal count
|
|
5632
|
+
* - Scroll reversal count + depths at each reversal (scroll_reversal_depths)
|
|
5599
5633
|
* - Per-field focus dwell times
|
|
5634
|
+
* - Exit intent count + scroll depth at last exit intent
|
|
5635
|
+
* - First interaction time (how long before user engaged)
|
|
5636
|
+
* - Performance: CLS score, long task count/max, INP
|
|
5600
5637
|
*/
|
|
5601
5638
|
startPageSummaryTracking() {
|
|
5602
5639
|
try {
|
|
5603
5640
|
const GRID_SIZE = 40;
|
|
5604
|
-
// In-memory accumulators — never sent to the server individually
|
|
5605
|
-
const moveGrid = new Map(); // cellKey → move count
|
|
5606
|
-
const hoverGrid = new Map(); // cellKey → total dwell_ms
|
|
5607
|
-
const fieldDwells = []; // [{field_id, field_label, field_type, dwell_ms, was_filled}]
|
|
5608
|
-
|
|
5609
|
-
let tabSwitches = 0;
|
|
5610
|
-
let totalTabHiddenMs = 0;
|
|
5611
|
-
let tabHiddenTime = null;
|
|
5612
|
-
let scrollReversals = 0;
|
|
5613
5641
|
|
|
5614
|
-
//
|
|
5642
|
+
// In-memory accumulators — never sent to the server individually
|
|
5643
|
+
const moveGrid = new Map(); // cellKey → move count
|
|
5644
|
+
const hoverGrid = new Map(); // cellKey → total dwell_ms
|
|
5645
|
+
const fieldDwells = []; // [{field_id, field_label, field_type, dwell_ms, was_filled}]
|
|
5646
|
+
const elementDwellsMap = new Map(); // elemKey → {tag, id, text, dwell_ms}
|
|
5647
|
+
const scrollReversalDepths = []; // scroll depth % at each reversal
|
|
5648
|
+
|
|
5649
|
+
let tabSwitches = 0;
|
|
5650
|
+
let totalTabHiddenMs = 0;
|
|
5651
|
+
let tabHiddenTime = null;
|
|
5652
|
+
let scrollReversals = 0;
|
|
5653
|
+
let exitIntentCount = 0;
|
|
5654
|
+
let exitIntentLastScrollDepth = 0;
|
|
5655
|
+
let firstInteractionMs = null;
|
|
5656
|
+
let clsScore = 0;
|
|
5657
|
+
let longTasksCount = 0;
|
|
5658
|
+
let maxLongTaskMs = 0;
|
|
5659
|
+
let inpMs = 0;
|
|
5660
|
+
|
|
5661
|
+
const pageLoadTime = Date.now();
|
|
5615
5662
|
let lastSigScrollY = window.scrollY;
|
|
5616
5663
|
let lastSigDir = 0; // 1=down, -1=up
|
|
5617
5664
|
|
|
5665
|
+
// ── Helpers
|
|
5666
|
+
function getScrollDepth() {
|
|
5667
|
+
const maxScroll = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - window.innerHeight;
|
|
5668
|
+
return maxScroll <= 0 ? 100 : Math.round((window.scrollY / maxScroll) * 100);
|
|
5669
|
+
}
|
|
5670
|
+
|
|
5618
5671
|
function getGridCell(pageX, pageY) {
|
|
5619
5672
|
const docW = Math.max(1, document.documentElement.scrollWidth || document.body.scrollWidth || window.innerWidth);
|
|
5620
5673
|
const docH = Math.max(1, document.documentElement.scrollHeight || document.body.scrollHeight || window.innerHeight);
|
|
@@ -5623,6 +5676,39 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5623
5676
|
return col * GRID_SIZE + row;
|
|
5624
5677
|
}
|
|
5625
5678
|
|
|
5679
|
+
// ── PerformanceObserver: CLS, Long Tasks, INP (best-effort, not all browsers)
|
|
5680
|
+
try {
|
|
5681
|
+
new PerformanceObserver(list => {
|
|
5682
|
+
for (const e of list.getEntries()) {
|
|
5683
|
+
if (!e.hadRecentInput) clsScore = Math.round((clsScore + e.value) * 1000) / 1000;
|
|
5684
|
+
}
|
|
5685
|
+
}).observe({ type: 'layout-shift', buffered: true });
|
|
5686
|
+
} catch (_) {}
|
|
5687
|
+
try {
|
|
5688
|
+
new PerformanceObserver(list => {
|
|
5689
|
+
for (const e of list.getEntries()) {
|
|
5690
|
+
longTasksCount++;
|
|
5691
|
+
if (e.duration > maxLongTaskMs) maxLongTaskMs = Math.round(e.duration);
|
|
5692
|
+
}
|
|
5693
|
+
}).observe({ type: 'longtask', buffered: true });
|
|
5694
|
+
} catch (_) {}
|
|
5695
|
+
try {
|
|
5696
|
+
new PerformanceObserver(list => {
|
|
5697
|
+
for (const e of list.getEntries()) {
|
|
5698
|
+
if (e.duration > inpMs) inpMs = Math.round(e.duration);
|
|
5699
|
+
}
|
|
5700
|
+
}).observe({ type: 'event', durationThreshold: 40, buffered: true });
|
|
5701
|
+
} catch (_) {}
|
|
5702
|
+
|
|
5703
|
+
// ── First interaction: time from page load to first meaningful engagement
|
|
5704
|
+
const markFirstInteraction = () => {
|
|
5705
|
+
if (firstInteractionMs === null) firstInteractionMs = Date.now() - pageLoadTime;
|
|
5706
|
+
};
|
|
5707
|
+
document.addEventListener('click', markFirstInteraction, { once: true, passive: true });
|
|
5708
|
+
document.addEventListener('keydown', markFirstInteraction, { once: true, passive: true });
|
|
5709
|
+
document.addEventListener('touchstart', markFirstInteraction, { once: true, passive: true });
|
|
5710
|
+
window.addEventListener('scroll', markFirstInteraction, { once: true, passive: true });
|
|
5711
|
+
|
|
5626
5712
|
// ── Mouse movement → move_grid (throttled to max 1 sample / 100 ms)
|
|
5627
5713
|
let moveThrottle = false;
|
|
5628
5714
|
document.addEventListener('mousemove', (e) => {
|
|
@@ -5633,7 +5719,8 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5633
5719
|
moveGrid.set(key, (moveGrid.get(key) || 0) + 1);
|
|
5634
5720
|
}, { passive: true });
|
|
5635
5721
|
|
|
5636
|
-
// ── Hover dwell → hover_grid
|
|
5722
|
+
// ── Hover dwell → hover_grid + element_dwells for named/interactive elements
|
|
5723
|
+
const ELEMENT_DWELL_TAGS = new Set(['BUTTON', 'A', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'INPUT', 'SELECT', 'TEXTAREA', 'IMG', 'LABEL']);
|
|
5637
5724
|
const hoverStarts = new Map(); // element → enterTime
|
|
5638
5725
|
document.addEventListener('mouseover', (e) => {
|
|
5639
5726
|
if (e.target && e.target !== document.documentElement) {
|
|
@@ -5652,6 +5739,22 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5652
5739
|
const cy = rect.top + rect.height / 2 + window.scrollY;
|
|
5653
5740
|
const key = getGridCell(cx, cy);
|
|
5654
5741
|
hoverGrid.set(key, (hoverGrid.get(key) || 0) + dwell);
|
|
5742
|
+
|
|
5743
|
+
// Element-level dwell: track named/interactive elements hovered ≥ 300ms
|
|
5744
|
+
if (dwell >= 300 && (target.id || ELEMENT_DWELL_TAGS.has(target.tagName))) {
|
|
5745
|
+
const elemKey = `${target.tagName}|${target.id || ''}|${(target.textContent || '').trim().slice(0, 30)}`;
|
|
5746
|
+
const existing = elementDwellsMap.get(elemKey);
|
|
5747
|
+
if (existing) {
|
|
5748
|
+
existing.dwell_ms += dwell;
|
|
5749
|
+
} else {
|
|
5750
|
+
elementDwellsMap.set(elemKey, {
|
|
5751
|
+
tag: target.tagName.toLowerCase(),
|
|
5752
|
+
id: target.id || '',
|
|
5753
|
+
text: (target.textContent || '').trim().slice(0, 50),
|
|
5754
|
+
dwell_ms: dwell
|
|
5755
|
+
});
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5655
5758
|
} catch (_) {}
|
|
5656
5759
|
}, { passive: true });
|
|
5657
5760
|
|
|
@@ -5666,14 +5769,15 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5666
5769
|
}
|
|
5667
5770
|
});
|
|
5668
5771
|
|
|
5669
|
-
// ── Scroll reversal detection (direction change
|
|
5772
|
+
// ── Scroll reversal detection (direction change ≥ 200 px) + depth recording
|
|
5670
5773
|
window.addEventListener('scroll', () => {
|
|
5671
5774
|
const curr = window.scrollY;
|
|
5672
5775
|
const diff = curr - lastSigScrollY;
|
|
5673
|
-
if (Math.abs(diff) < 50) return;
|
|
5674
|
-
const dir
|
|
5776
|
+
if (Math.abs(diff) < 50) return;
|
|
5777
|
+
const dir = diff > 0 ? 1 : -1;
|
|
5675
5778
|
if (lastSigDir !== 0 && dir !== lastSigDir && Math.abs(curr - lastSigScrollY) >= 200) {
|
|
5676
5779
|
scrollReversals++;
|
|
5780
|
+
scrollReversalDepths.push(getScrollDepth());
|
|
5677
5781
|
}
|
|
5678
5782
|
if (Math.abs(diff) >= 50) { lastSigDir = dir; lastSigScrollY = curr; }
|
|
5679
5783
|
}, { passive: true });
|
|
@@ -5691,7 +5795,7 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5691
5795
|
if (!t || !fieldFocusTimes.has(t)) return;
|
|
5692
5796
|
const dwell = Date.now() - fieldFocusTimes.get(t);
|
|
5693
5797
|
fieldFocusTimes.delete(t);
|
|
5694
|
-
if (dwell < 100) return;
|
|
5798
|
+
if (dwell < 100) return;
|
|
5695
5799
|
const label = t.id
|
|
5696
5800
|
? (document.querySelector(`label[for="${CSS.escape(t.id)}"]`)?.textContent?.trim() || t.placeholder || t.name || t.id)
|
|
5697
5801
|
: (t.placeholder || t.name || t.type || '');
|
|
@@ -5704,6 +5808,26 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5704
5808
|
});
|
|
5705
5809
|
}, true);
|
|
5706
5810
|
|
|
5811
|
+
// ── Exit intent: mouse leaving viewport toward the top (user about to navigate away)
|
|
5812
|
+
// Also fires as a standalone auto-event for direct querying.
|
|
5813
|
+
let lastExitIntentTime = 0;
|
|
5814
|
+
document.addEventListener('mouseleave', (e) => {
|
|
5815
|
+
if (e.clientY > 0 || e.relatedTarget !== null) return; // only top-edge exits
|
|
5816
|
+
const now = Date.now();
|
|
5817
|
+
if (now - lastExitIntentTime < 2000) return; // debounce: max once per 2s
|
|
5818
|
+
lastExitIntentTime = now;
|
|
5819
|
+
exitIntentCount++;
|
|
5820
|
+
exitIntentLastScrollDepth = getScrollDepth();
|
|
5821
|
+
EventsManager.trackAutoEvent('exit_intent', {
|
|
5822
|
+
scroll_depth: exitIntentLastScrollDepth,
|
|
5823
|
+
time_on_page_ms: now - pageLoadTime,
|
|
5824
|
+
scroll_y: window.scrollY,
|
|
5825
|
+
document_height: Math.min(
|
|
5826
|
+
document.documentElement.scrollHeight || document.body.scrollHeight, 25000
|
|
5827
|
+
)
|
|
5828
|
+
}).catch(() => {});
|
|
5829
|
+
});
|
|
5830
|
+
|
|
5707
5831
|
// ── Serialise grid Map to [[col, row, value], ...] sparse array
|
|
5708
5832
|
function serializeGrid(grid) {
|
|
5709
5833
|
const out = [];
|
|
@@ -5720,17 +5844,34 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5720
5844
|
let summarySent = false;
|
|
5721
5845
|
function firePageSummary() {
|
|
5722
5846
|
if (summarySent) return; // only fire once per page lifecycle
|
|
5723
|
-
|
|
5724
|
-
|
|
5847
|
+
const hasData = moveGrid.size > 0 || hoverGrid.size > 0 || fieldDwells.length > 0
|
|
5848
|
+
|| tabSwitches > 0 || scrollReversals > 0 || exitIntentCount > 0
|
|
5849
|
+
|| firstInteractionMs !== null || clsScore > 0 || longTasksCount > 0;
|
|
5850
|
+
if (!hasData) return;
|
|
5725
5851
|
summarySent = true;
|
|
5726
5852
|
try {
|
|
5853
|
+
// Serialize element_dwells: filter ≥ 300ms, sort desc, cap at 20
|
|
5854
|
+
const elementDwells = Array.from(elementDwellsMap.values())
|
|
5855
|
+
.filter(e => e.dwell_ms >= 300)
|
|
5856
|
+
.sort((a, b) => b.dwell_ms - a.dwell_ms)
|
|
5857
|
+
.slice(0, 20);
|
|
5858
|
+
|
|
5727
5859
|
EventsManager.trackAutoEvent('page_summary', {
|
|
5728
|
-
scroll_reversals:
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5860
|
+
scroll_reversals: scrollReversals,
|
|
5861
|
+
scroll_reversal_depths: scrollReversalDepths,
|
|
5862
|
+
tab_switches: tabSwitches,
|
|
5863
|
+
total_tab_hidden_ms: totalTabHiddenMs,
|
|
5864
|
+
move_grid: serializeGrid(moveGrid),
|
|
5865
|
+
hover_grid: serializeGrid(hoverGrid),
|
|
5866
|
+
field_dwells: fieldDwells,
|
|
5867
|
+
element_dwells: elementDwells,
|
|
5868
|
+
first_interaction_ms: firstInteractionMs,
|
|
5869
|
+
exit_intent_count: exitIntentCount,
|
|
5870
|
+
exit_intent_last_scroll_depth: exitIntentLastScrollDepth,
|
|
5871
|
+
cls_score: clsScore,
|
|
5872
|
+
long_tasks_count: longTasksCount,
|
|
5873
|
+
max_long_task_ms: maxLongTaskMs,
|
|
5874
|
+
inp_ms: inpMs,
|
|
5734
5875
|
document_height: document.documentElement.scrollHeight || document.body.scrollHeight || 0,
|
|
5735
5876
|
document_width: document.documentElement.scrollWidth || document.body.scrollWidth || 0,
|
|
5736
5877
|
viewport_width: window.innerWidth,
|
package/lib/umd/index.js
CHANGED
|
@@ -5523,104 +5523,157 @@
|
|
|
5523
5523
|
|
|
5524
5524
|
/**
|
|
5525
5525
|
* ElementVisibilityTracker - Emits element_view auto events when elements
|
|
5526
|
-
*
|
|
5527
|
-
*
|
|
5528
|
-
*
|
|
5526
|
+
* enter the viewport for ≥ 1 second, capturing the actual dwell duration.
|
|
5527
|
+
*
|
|
5528
|
+
* Covers interactive elements (buttons, links, inputs, headings, images) and
|
|
5529
|
+
* any element with an id or data-cq-track attribute. Each viewport entry is
|
|
5530
|
+
* tracked independently — if a user scrolls away and back, both dwells count.
|
|
5531
|
+
* This gives accurate attention data for the heatmap attention overlay.
|
|
5529
5532
|
*/
|
|
5530
5533
|
startElementVisibilityTracking() {
|
|
5531
5534
|
try {
|
|
5532
5535
|
if (typeof IntersectionObserver === 'undefined') return;
|
|
5533
5536
|
|
|
5534
|
-
//
|
|
5537
|
+
// Observe interactive/structural elements in addition to id/data-cq-track
|
|
5538
|
+
const TRACKABLE_SELECTOR = [
|
|
5539
|
+
'[id]:not([id=""])', '[data-cq-track]',
|
|
5540
|
+
'button', 'a[href]', 'input:not([type=hidden])', 'select', 'textarea',
|
|
5541
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img[alt]',
|
|
5542
|
+
'[role="button"]', '[role="tab"]', '[role="menuitem"]', '[role="link"]'
|
|
5543
|
+
].join(', ');
|
|
5544
|
+
|
|
5535
5545
|
const getTrackableElements = () =>
|
|
5536
|
-
document.querySelectorAll(
|
|
5546
|
+
Array.from(document.querySelectorAll(TRACKABLE_SELECTOR)).filter(el => {
|
|
5547
|
+
try { return el.getBoundingClientRect().height >= 20; } catch (_) { return true; }
|
|
5548
|
+
});
|
|
5537
5549
|
|
|
5538
|
-
|
|
5539
|
-
const
|
|
5550
|
+
// enterTimes: key → { enterTime, el } — tracks elements currently in viewport
|
|
5551
|
+
const enterTimes = new Map();
|
|
5552
|
+
const observedEls = new WeakSet(); // prevents double-observing same DOM node
|
|
5553
|
+
|
|
5554
|
+
function elementKey(el) {
|
|
5555
|
+
return `${el.tagName}|${el.id || ''}|${el.getAttribute('data-cq-track') || ''}|${(el.textContent || '').trim().slice(0, 30)}`;
|
|
5556
|
+
}
|
|
5557
|
+
|
|
5558
|
+
function getElementData(el) {
|
|
5559
|
+
return {
|
|
5560
|
+
element_id: el.id || '',
|
|
5561
|
+
element_name: el.getAttribute('name') || el.getAttribute('data-cq-track') || '',
|
|
5562
|
+
element_tag_name: el.tagName || '',
|
|
5563
|
+
element_type: el.type || el.getAttribute('type') || '',
|
|
5564
|
+
element_class: el.className || '',
|
|
5565
|
+
element_text: (el.textContent || '').trim().slice(0, 100)
|
|
5566
|
+
};
|
|
5567
|
+
}
|
|
5540
5568
|
|
|
5541
5569
|
const observer = new IntersectionObserver((entries) => {
|
|
5542
5570
|
entries.forEach((entry) => {
|
|
5543
|
-
const el
|
|
5544
|
-
const key = el
|
|
5545
|
-
if (!key || reported.has(key)) return;
|
|
5571
|
+
const el = entry.target;
|
|
5572
|
+
const key = elementKey(el);
|
|
5546
5573
|
|
|
5547
5574
|
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
|
5548
|
-
// Element entered viewport —
|
|
5549
|
-
if (!
|
|
5550
|
-
|
|
5551
|
-
const timer = setTimeout(() => {
|
|
5552
|
-
if (reported.has(key)) return;
|
|
5553
|
-
reported.add(key);
|
|
5554
|
-
EventsManager.trackAutoEvent('element_view', {
|
|
5555
|
-
time_in_viewport_ms: Date.now() - enterTime,
|
|
5556
|
-
viewport_percent_visible: Math.round(entry.intersectionRatio * 100)
|
|
5557
|
-
}, {
|
|
5558
|
-
element_id: el.id || '',
|
|
5559
|
-
element_name: el.getAttribute('name') || el.getAttribute('data-cq-track') || '',
|
|
5560
|
-
element_tag_name: el.tagName || '',
|
|
5561
|
-
element_type: el.type || el.getAttribute('type') || '',
|
|
5562
|
-
element_class: el.className || '',
|
|
5563
|
-
element_text: (el.textContent || '').trim().slice(0, 100)
|
|
5564
|
-
});
|
|
5565
|
-
viewTimers.delete(key);
|
|
5566
|
-
}, 1000);
|
|
5567
|
-
viewTimers.set(key, timer);
|
|
5575
|
+
// Element entered viewport — record entry time
|
|
5576
|
+
if (!enterTimes.has(key)) {
|
|
5577
|
+
enterTimes.set(key, { enterTime: Date.now(), el });
|
|
5568
5578
|
}
|
|
5569
5579
|
} else {
|
|
5570
|
-
// Element left viewport
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5580
|
+
// Element left viewport — emit actual dwell if ≥ 1s
|
|
5581
|
+
const record = enterTimes.get(key);
|
|
5582
|
+
if (record) {
|
|
5583
|
+
const dwell = Date.now() - record.enterTime;
|
|
5584
|
+
enterTimes.delete(key);
|
|
5585
|
+
if (dwell >= 1000) {
|
|
5586
|
+
EventsManager.trackAutoEvent('element_view', {
|
|
5587
|
+
time_in_viewport_ms: dwell,
|
|
5588
|
+
viewport_percent_visible: Math.round(entry.intersectionRatio * 100),
|
|
5589
|
+
scroll_y_at_view: window.scrollY
|
|
5590
|
+
}, getElementData(el));
|
|
5591
|
+
}
|
|
5574
5592
|
}
|
|
5575
5593
|
}
|
|
5576
5594
|
});
|
|
5577
|
-
}, { threshold: 0.5 });
|
|
5595
|
+
}, { threshold: [0.5] });
|
|
5596
|
+
|
|
5597
|
+
const observe = (el) => {
|
|
5598
|
+
if (!observedEls.has(el)) { observedEls.add(el); observer.observe(el); }
|
|
5599
|
+
};
|
|
5578
5600
|
|
|
5579
|
-
|
|
5580
|
-
getTrackableElements().forEach(el => observer.observe(el));
|
|
5601
|
+
getTrackableElements().forEach(observe);
|
|
5581
5602
|
|
|
5582
5603
|
// Observe elements added to DOM later (SPAs)
|
|
5583
5604
|
if (typeof MutationObserver !== 'undefined') {
|
|
5584
5605
|
new MutationObserver(() => {
|
|
5585
|
-
getTrackableElements().forEach(
|
|
5586
|
-
if (!viewTimers.has(el.id || el.getAttribute('data-cq-track') || el.className)) {
|
|
5587
|
-
observer.observe(el);
|
|
5588
|
-
}
|
|
5589
|
-
});
|
|
5606
|
+
getTrackableElements().forEach(observe);
|
|
5590
5607
|
}).observe(document.body, { childList: true, subtree: true });
|
|
5591
5608
|
}
|
|
5592
|
-
|
|
5593
|
-
|
|
5609
|
+
|
|
5610
|
+
// On page unload, flush elements still in viewport
|
|
5611
|
+
window.addEventListener('pagehide', () => {
|
|
5612
|
+
enterTimes.forEach(({ enterTime, el }) => {
|
|
5613
|
+
const dwell = Date.now() - enterTime;
|
|
5614
|
+
if (dwell >= 1000) {
|
|
5615
|
+
EventsManager.trackAutoEvent('element_view', {
|
|
5616
|
+
time_in_viewport_ms: dwell,
|
|
5617
|
+
viewport_percent_visible: 100,
|
|
5618
|
+
scroll_y_at_view: window.scrollY
|
|
5619
|
+
}, getElementData(el));
|
|
5620
|
+
}
|
|
5621
|
+
});
|
|
5622
|
+
});
|
|
5623
|
+
} catch (error) {}
|
|
5594
5624
|
},
|
|
5595
5625
|
|
|
5596
5626
|
/**
|
|
5597
5627
|
* PageSummaryTracker — collects high-frequency signals in-memory and flushes
|
|
5598
5628
|
* them as a single 'page_summary' auto-event on page unload.
|
|
5599
5629
|
*
|
|
5600
|
-
*
|
|
5601
|
-
*
|
|
5602
|
-
*
|
|
5630
|
+
* High-volume/continuous data lives here (grids, counts) to keep event volume low.
|
|
5631
|
+
* Low-frequency, high-signal moments (exit_intent) are emitted as separate auto-events
|
|
5632
|
+
* but their counters are also included here for correlation.
|
|
5633
|
+
*
|
|
5634
|
+
* Captures:
|
|
5635
|
+
* - Mouse movement grid (move_grid) + hover dwell grid (hover_grid)
|
|
5636
|
+
* - Element-level hover dwells for named/interactive elements (element_dwells)
|
|
5603
5637
|
* - Tab-switch count and hidden time
|
|
5604
|
-
* - Scroll reversal count
|
|
5638
|
+
* - Scroll reversal count + depths at each reversal (scroll_reversal_depths)
|
|
5605
5639
|
* - Per-field focus dwell times
|
|
5640
|
+
* - Exit intent count + scroll depth at last exit intent
|
|
5641
|
+
* - First interaction time (how long before user engaged)
|
|
5642
|
+
* - Performance: CLS score, long task count/max, INP
|
|
5606
5643
|
*/
|
|
5607
5644
|
startPageSummaryTracking() {
|
|
5608
5645
|
try {
|
|
5609
5646
|
const GRID_SIZE = 40;
|
|
5610
|
-
// In-memory accumulators — never sent to the server individually
|
|
5611
|
-
const moveGrid = new Map(); // cellKey → move count
|
|
5612
|
-
const hoverGrid = new Map(); // cellKey → total dwell_ms
|
|
5613
|
-
const fieldDwells = []; // [{field_id, field_label, field_type, dwell_ms, was_filled}]
|
|
5614
|
-
|
|
5615
|
-
let tabSwitches = 0;
|
|
5616
|
-
let totalTabHiddenMs = 0;
|
|
5617
|
-
let tabHiddenTime = null;
|
|
5618
|
-
let scrollReversals = 0;
|
|
5619
5647
|
|
|
5620
|
-
//
|
|
5648
|
+
// In-memory accumulators — never sent to the server individually
|
|
5649
|
+
const moveGrid = new Map(); // cellKey → move count
|
|
5650
|
+
const hoverGrid = new Map(); // cellKey → total dwell_ms
|
|
5651
|
+
const fieldDwells = []; // [{field_id, field_label, field_type, dwell_ms, was_filled}]
|
|
5652
|
+
const elementDwellsMap = new Map(); // elemKey → {tag, id, text, dwell_ms}
|
|
5653
|
+
const scrollReversalDepths = []; // scroll depth % at each reversal
|
|
5654
|
+
|
|
5655
|
+
let tabSwitches = 0;
|
|
5656
|
+
let totalTabHiddenMs = 0;
|
|
5657
|
+
let tabHiddenTime = null;
|
|
5658
|
+
let scrollReversals = 0;
|
|
5659
|
+
let exitIntentCount = 0;
|
|
5660
|
+
let exitIntentLastScrollDepth = 0;
|
|
5661
|
+
let firstInteractionMs = null;
|
|
5662
|
+
let clsScore = 0;
|
|
5663
|
+
let longTasksCount = 0;
|
|
5664
|
+
let maxLongTaskMs = 0;
|
|
5665
|
+
let inpMs = 0;
|
|
5666
|
+
|
|
5667
|
+
const pageLoadTime = Date.now();
|
|
5621
5668
|
let lastSigScrollY = window.scrollY;
|
|
5622
5669
|
let lastSigDir = 0; // 1=down, -1=up
|
|
5623
5670
|
|
|
5671
|
+
// ── Helpers
|
|
5672
|
+
function getScrollDepth() {
|
|
5673
|
+
const maxScroll = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - window.innerHeight;
|
|
5674
|
+
return maxScroll <= 0 ? 100 : Math.round((window.scrollY / maxScroll) * 100);
|
|
5675
|
+
}
|
|
5676
|
+
|
|
5624
5677
|
function getGridCell(pageX, pageY) {
|
|
5625
5678
|
const docW = Math.max(1, document.documentElement.scrollWidth || document.body.scrollWidth || window.innerWidth);
|
|
5626
5679
|
const docH = Math.max(1, document.documentElement.scrollHeight || document.body.scrollHeight || window.innerHeight);
|
|
@@ -5629,6 +5682,39 @@
|
|
|
5629
5682
|
return col * GRID_SIZE + row;
|
|
5630
5683
|
}
|
|
5631
5684
|
|
|
5685
|
+
// ── PerformanceObserver: CLS, Long Tasks, INP (best-effort, not all browsers)
|
|
5686
|
+
try {
|
|
5687
|
+
new PerformanceObserver(list => {
|
|
5688
|
+
for (const e of list.getEntries()) {
|
|
5689
|
+
if (!e.hadRecentInput) clsScore = Math.round((clsScore + e.value) * 1000) / 1000;
|
|
5690
|
+
}
|
|
5691
|
+
}).observe({ type: 'layout-shift', buffered: true });
|
|
5692
|
+
} catch (_) {}
|
|
5693
|
+
try {
|
|
5694
|
+
new PerformanceObserver(list => {
|
|
5695
|
+
for (const e of list.getEntries()) {
|
|
5696
|
+
longTasksCount++;
|
|
5697
|
+
if (e.duration > maxLongTaskMs) maxLongTaskMs = Math.round(e.duration);
|
|
5698
|
+
}
|
|
5699
|
+
}).observe({ type: 'longtask', buffered: true });
|
|
5700
|
+
} catch (_) {}
|
|
5701
|
+
try {
|
|
5702
|
+
new PerformanceObserver(list => {
|
|
5703
|
+
for (const e of list.getEntries()) {
|
|
5704
|
+
if (e.duration > inpMs) inpMs = Math.round(e.duration);
|
|
5705
|
+
}
|
|
5706
|
+
}).observe({ type: 'event', durationThreshold: 40, buffered: true });
|
|
5707
|
+
} catch (_) {}
|
|
5708
|
+
|
|
5709
|
+
// ── First interaction: time from page load to first meaningful engagement
|
|
5710
|
+
const markFirstInteraction = () => {
|
|
5711
|
+
if (firstInteractionMs === null) firstInteractionMs = Date.now() - pageLoadTime;
|
|
5712
|
+
};
|
|
5713
|
+
document.addEventListener('click', markFirstInteraction, { once: true, passive: true });
|
|
5714
|
+
document.addEventListener('keydown', markFirstInteraction, { once: true, passive: true });
|
|
5715
|
+
document.addEventListener('touchstart', markFirstInteraction, { once: true, passive: true });
|
|
5716
|
+
window.addEventListener('scroll', markFirstInteraction, { once: true, passive: true });
|
|
5717
|
+
|
|
5632
5718
|
// ── Mouse movement → move_grid (throttled to max 1 sample / 100 ms)
|
|
5633
5719
|
let moveThrottle = false;
|
|
5634
5720
|
document.addEventListener('mousemove', (e) => {
|
|
@@ -5639,7 +5725,8 @@
|
|
|
5639
5725
|
moveGrid.set(key, (moveGrid.get(key) || 0) + 1);
|
|
5640
5726
|
}, { passive: true });
|
|
5641
5727
|
|
|
5642
|
-
// ── Hover dwell → hover_grid
|
|
5728
|
+
// ── Hover dwell → hover_grid + element_dwells for named/interactive elements
|
|
5729
|
+
const ELEMENT_DWELL_TAGS = new Set(['BUTTON', 'A', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'INPUT', 'SELECT', 'TEXTAREA', 'IMG', 'LABEL']);
|
|
5643
5730
|
const hoverStarts = new Map(); // element → enterTime
|
|
5644
5731
|
document.addEventListener('mouseover', (e) => {
|
|
5645
5732
|
if (e.target && e.target !== document.documentElement) {
|
|
@@ -5658,6 +5745,22 @@
|
|
|
5658
5745
|
const cy = rect.top + rect.height / 2 + window.scrollY;
|
|
5659
5746
|
const key = getGridCell(cx, cy);
|
|
5660
5747
|
hoverGrid.set(key, (hoverGrid.get(key) || 0) + dwell);
|
|
5748
|
+
|
|
5749
|
+
// Element-level dwell: track named/interactive elements hovered ≥ 300ms
|
|
5750
|
+
if (dwell >= 300 && (target.id || ELEMENT_DWELL_TAGS.has(target.tagName))) {
|
|
5751
|
+
const elemKey = `${target.tagName}|${target.id || ''}|${(target.textContent || '').trim().slice(0, 30)}`;
|
|
5752
|
+
const existing = elementDwellsMap.get(elemKey);
|
|
5753
|
+
if (existing) {
|
|
5754
|
+
existing.dwell_ms += dwell;
|
|
5755
|
+
} else {
|
|
5756
|
+
elementDwellsMap.set(elemKey, {
|
|
5757
|
+
tag: target.tagName.toLowerCase(),
|
|
5758
|
+
id: target.id || '',
|
|
5759
|
+
text: (target.textContent || '').trim().slice(0, 50),
|
|
5760
|
+
dwell_ms: dwell
|
|
5761
|
+
});
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
5661
5764
|
} catch (_) {}
|
|
5662
5765
|
}, { passive: true });
|
|
5663
5766
|
|
|
@@ -5672,14 +5775,15 @@
|
|
|
5672
5775
|
}
|
|
5673
5776
|
});
|
|
5674
5777
|
|
|
5675
|
-
// ── Scroll reversal detection (direction change
|
|
5778
|
+
// ── Scroll reversal detection (direction change ≥ 200 px) + depth recording
|
|
5676
5779
|
window.addEventListener('scroll', () => {
|
|
5677
5780
|
const curr = window.scrollY;
|
|
5678
5781
|
const diff = curr - lastSigScrollY;
|
|
5679
|
-
if (Math.abs(diff) < 50) return;
|
|
5680
|
-
const dir
|
|
5782
|
+
if (Math.abs(diff) < 50) return;
|
|
5783
|
+
const dir = diff > 0 ? 1 : -1;
|
|
5681
5784
|
if (lastSigDir !== 0 && dir !== lastSigDir && Math.abs(curr - lastSigScrollY) >= 200) {
|
|
5682
5785
|
scrollReversals++;
|
|
5786
|
+
scrollReversalDepths.push(getScrollDepth());
|
|
5683
5787
|
}
|
|
5684
5788
|
if (Math.abs(diff) >= 50) { lastSigDir = dir; lastSigScrollY = curr; }
|
|
5685
5789
|
}, { passive: true });
|
|
@@ -5697,7 +5801,7 @@
|
|
|
5697
5801
|
if (!t || !fieldFocusTimes.has(t)) return;
|
|
5698
5802
|
const dwell = Date.now() - fieldFocusTimes.get(t);
|
|
5699
5803
|
fieldFocusTimes.delete(t);
|
|
5700
|
-
if (dwell < 100) return;
|
|
5804
|
+
if (dwell < 100) return;
|
|
5701
5805
|
const label = t.id
|
|
5702
5806
|
? (document.querySelector(`label[for="${CSS.escape(t.id)}"]`)?.textContent?.trim() || t.placeholder || t.name || t.id)
|
|
5703
5807
|
: (t.placeholder || t.name || t.type || '');
|
|
@@ -5710,6 +5814,26 @@
|
|
|
5710
5814
|
});
|
|
5711
5815
|
}, true);
|
|
5712
5816
|
|
|
5817
|
+
// ── Exit intent: mouse leaving viewport toward the top (user about to navigate away)
|
|
5818
|
+
// Also fires as a standalone auto-event for direct querying.
|
|
5819
|
+
let lastExitIntentTime = 0;
|
|
5820
|
+
document.addEventListener('mouseleave', (e) => {
|
|
5821
|
+
if (e.clientY > 0 || e.relatedTarget !== null) return; // only top-edge exits
|
|
5822
|
+
const now = Date.now();
|
|
5823
|
+
if (now - lastExitIntentTime < 2000) return; // debounce: max once per 2s
|
|
5824
|
+
lastExitIntentTime = now;
|
|
5825
|
+
exitIntentCount++;
|
|
5826
|
+
exitIntentLastScrollDepth = getScrollDepth();
|
|
5827
|
+
EventsManager.trackAutoEvent('exit_intent', {
|
|
5828
|
+
scroll_depth: exitIntentLastScrollDepth,
|
|
5829
|
+
time_on_page_ms: now - pageLoadTime,
|
|
5830
|
+
scroll_y: window.scrollY,
|
|
5831
|
+
document_height: Math.min(
|
|
5832
|
+
document.documentElement.scrollHeight || document.body.scrollHeight, 25000
|
|
5833
|
+
)
|
|
5834
|
+
}).catch(() => {});
|
|
5835
|
+
});
|
|
5836
|
+
|
|
5713
5837
|
// ── Serialise grid Map to [[col, row, value], ...] sparse array
|
|
5714
5838
|
function serializeGrid(grid) {
|
|
5715
5839
|
const out = [];
|
|
@@ -5726,17 +5850,34 @@
|
|
|
5726
5850
|
let summarySent = false;
|
|
5727
5851
|
function firePageSummary() {
|
|
5728
5852
|
if (summarySent) return; // only fire once per page lifecycle
|
|
5729
|
-
|
|
5730
|
-
|
|
5853
|
+
const hasData = moveGrid.size > 0 || hoverGrid.size > 0 || fieldDwells.length > 0
|
|
5854
|
+
|| tabSwitches > 0 || scrollReversals > 0 || exitIntentCount > 0
|
|
5855
|
+
|| firstInteractionMs !== null || clsScore > 0 || longTasksCount > 0;
|
|
5856
|
+
if (!hasData) return;
|
|
5731
5857
|
summarySent = true;
|
|
5732
5858
|
try {
|
|
5859
|
+
// Serialize element_dwells: filter ≥ 300ms, sort desc, cap at 20
|
|
5860
|
+
const elementDwells = Array.from(elementDwellsMap.values())
|
|
5861
|
+
.filter(e => e.dwell_ms >= 300)
|
|
5862
|
+
.sort((a, b) => b.dwell_ms - a.dwell_ms)
|
|
5863
|
+
.slice(0, 20);
|
|
5864
|
+
|
|
5733
5865
|
EventsManager.trackAutoEvent('page_summary', {
|
|
5734
|
-
scroll_reversals:
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5866
|
+
scroll_reversals: scrollReversals,
|
|
5867
|
+
scroll_reversal_depths: scrollReversalDepths,
|
|
5868
|
+
tab_switches: tabSwitches,
|
|
5869
|
+
total_tab_hidden_ms: totalTabHiddenMs,
|
|
5870
|
+
move_grid: serializeGrid(moveGrid),
|
|
5871
|
+
hover_grid: serializeGrid(hoverGrid),
|
|
5872
|
+
field_dwells: fieldDwells,
|
|
5873
|
+
element_dwells: elementDwells,
|
|
5874
|
+
first_interaction_ms: firstInteractionMs,
|
|
5875
|
+
exit_intent_count: exitIntentCount,
|
|
5876
|
+
exit_intent_last_scroll_depth: exitIntentLastScrollDepth,
|
|
5877
|
+
cls_score: clsScore,
|
|
5878
|
+
long_tasks_count: longTasksCount,
|
|
5879
|
+
max_long_task_ms: maxLongTaskMs,
|
|
5880
|
+
inp_ms: inpMs,
|
|
5740
5881
|
document_height: document.documentElement.scrollHeight || document.body.scrollHeight || 0,
|
|
5741
5882
|
document_width: document.documentElement.scrollWidth || document.body.scrollWidth || 0,
|
|
5742
5883
|
viewport_width: window.innerWidth,
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cryptique-sdk",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Cryptique Analytics SDK - Comprehensive web analytics and user tracking for modern web applications",
|
|
6
6
|
"main": "lib/cjs/index.js",
|
|
7
|
+
|
|
7
8
|
"module": "lib/esm/index.js",
|
|
8
9
|
"browser": "lib/umd/index.js",
|
|
9
10
|
"types": "lib/types/index.d.ts",
|