@gemx-dev/heatmap-react 3.5.92-dev.20 → 3.5.92-dev.21
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/esm/hooks/viz-canvas/useClickmap.d.ts.map +1 -1
- package/dist/esm/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
- package/dist/esm/index.js +225 -166
- package/dist/esm/index.mjs +225 -166
- package/dist/esm/libs/iframe-processor/orchestrator.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/height-observer/index.d.ts +2 -0
- package/dist/esm/libs/iframe-processor/processors/height-observer/index.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/height-observer/listeners.d.ts +11 -0
- package/dist/esm/libs/iframe-processor/processors/height-observer/listeners.d.ts.map +1 -0
- package/dist/esm/libs/iframe-processor/processors/height-observer/types.d.ts +6 -1
- package/dist/esm/libs/iframe-processor/processors/height-observer/types.d.ts.map +1 -1
- package/dist/esm/libs/visualizer/GXVisualizer.d.ts +12 -1
- package/dist/esm/libs/visualizer/GXVisualizer.d.ts.map +1 -1
- package/dist/esm/libs/visualizer/index.d.ts +1 -0
- package/dist/esm/libs/visualizer/index.d.ts.map +1 -1
- package/dist/esm/libs/visualizer/renderers/AttentionMapRenderer.d.ts +1 -1
- package/dist/esm/libs/visualizer/renderers/AttentionMapRenderer.d.ts.map +1 -1
- package/dist/esm/libs/visualizer/renderers/ScrollBucketRenderer.d.ts +44 -0
- package/dist/esm/libs/visualizer/renderers/ScrollBucketRenderer.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/renderers/index.d.ts +1 -1
- package/dist/esm/libs/visualizer/renderers/index.d.ts.map +1 -1
- package/dist/esm/types/clarity.d.ts +18 -0
- package/dist/esm/types/clarity.d.ts.map +1 -1
- package/dist/esm/types/control.d.ts +2 -0
- package/dist/esm/types/control.d.ts.map +1 -1
- package/dist/esm/types/viz-scrollmap.d.ts +4 -0
- package/dist/esm/types/viz-scrollmap.d.ts.map +1 -1
- package/dist/esm/utils/retry.d.ts +1 -0
- package/dist/esm/utils/retry.d.ts.map +1 -1
- package/dist/umd/hooks/viz-canvas/useClickmap.d.ts.map +1 -1
- package/dist/umd/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
- package/dist/umd/index.js +2 -2
- package/dist/umd/libs/iframe-processor/orchestrator.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/height-observer/index.d.ts +2 -0
- package/dist/umd/libs/iframe-processor/processors/height-observer/index.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/height-observer/listeners.d.ts +11 -0
- package/dist/umd/libs/iframe-processor/processors/height-observer/listeners.d.ts.map +1 -0
- package/dist/umd/libs/iframe-processor/processors/height-observer/types.d.ts +6 -1
- package/dist/umd/libs/iframe-processor/processors/height-observer/types.d.ts.map +1 -1
- package/dist/umd/libs/visualizer/GXVisualizer.d.ts +12 -1
- package/dist/umd/libs/visualizer/GXVisualizer.d.ts.map +1 -1
- package/dist/umd/libs/visualizer/index.d.ts +1 -0
- package/dist/umd/libs/visualizer/index.d.ts.map +1 -1
- package/dist/umd/libs/visualizer/renderers/AttentionMapRenderer.d.ts +1 -1
- package/dist/umd/libs/visualizer/renderers/AttentionMapRenderer.d.ts.map +1 -1
- package/dist/umd/libs/visualizer/renderers/ScrollBucketRenderer.d.ts +44 -0
- package/dist/umd/libs/visualizer/renderers/ScrollBucketRenderer.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/renderers/index.d.ts +1 -1
- package/dist/umd/libs/visualizer/renderers/index.d.ts.map +1 -1
- package/dist/umd/types/clarity.d.ts +18 -0
- package/dist/umd/types/clarity.d.ts.map +1 -1
- package/dist/umd/types/control.d.ts +2 -0
- package/dist/umd/types/control.d.ts.map +1 -1
- package/dist/umd/types/viz-scrollmap.d.ts +4 -0
- package/dist/umd/types/viz-scrollmap.d.ts.map +1 -1
- package/dist/umd/utils/retry.d.ts +1 -0
- package/dist/umd/utils/retry.d.ts.map +1 -1
- package/package.json +4 -4
- package/dist/esm/libs/visualizer/renderers/ClickHeatmapRenderer.d.ts +0 -30
- package/dist/esm/libs/visualizer/renderers/ClickHeatmapRenderer.d.ts.map +0 -1
- package/dist/umd/libs/visualizer/renderers/ClickHeatmapRenderer.d.ts +0 -30
- package/dist/umd/libs/visualizer/renderers/ClickHeatmapRenderer.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useClickmap.d.ts","sourceRoot":"","sources":["../../../src/hooks/viz-canvas/useClickmap.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,WAAW;;
|
|
1
|
+
{"version":3,"file":"useClickmap.d.ts","sourceRoot":"","sources":["../../../src/hooks/viz-canvas/useClickmap.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,WAAW;;CAiBvB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useScrollmap.d.ts","sourceRoot":"","sources":["../../../src/hooks/viz-canvas/useScrollmap.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useScrollmap.d.ts","sourceRoot":"","sources":["../../../src/hooks/viz-canvas/useScrollmap.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,YAAY;;CAmDxB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useHeatmapScale.d.ts","sourceRoot":"","sources":["../../../src/hooks/viz-scale/useHeatmapScale.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useHeatmapScale.d.ts","sourceRoot":"","sources":["../../../src/hooks/viz-scale/useHeatmapScale.ts"],"names":[],"mappings":"AAMA,UAAU,kBAAkB;IAC1B,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACnD,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACrD,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CACnD;AAED,UAAU,mBAAmB;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED,eAAO,MAAM,eAAe,GAAI,OAAO,kBAAkB,KAAG,mBA4B3D,CAAC"}
|
package/dist/esm/index.js
CHANGED
|
@@ -3528,10 +3528,8 @@ const useClickmap = () => {
|
|
|
3528
3528
|
if (!vizRef || !clickmap || clickmap.length === 0 || !isDomLoaded)
|
|
3529
3529
|
return;
|
|
3530
3530
|
try {
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
vizRef?.clickmap?.(clickmap);
|
|
3534
|
-
}, { timeout: 300 });
|
|
3531
|
+
vizRef?.clearmap?.();
|
|
3532
|
+
requestIdleCallback(() => vizRef?.clickmap?.(clickmap), { timeout: 300 });
|
|
3535
3533
|
}
|
|
3536
3534
|
catch (error) {
|
|
3537
3535
|
console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
|
|
@@ -3545,31 +3543,47 @@ const useScrollmap = () => {
|
|
|
3545
3543
|
const scrollType = useHeatmapSettingContext((s) => s.scrollType);
|
|
3546
3544
|
const scrollmap = useHeatmapDataContext((s) => s.scrollmap);
|
|
3547
3545
|
const isDomLoaded = useHeatmapVizContext((s) => s.isDomLoaded);
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
return scrollmap;
|
|
3554
|
-
case EScrollType.Revenue:
|
|
3555
|
-
return scrollmap;
|
|
3556
|
-
default:
|
|
3557
|
-
return scrollmap;
|
|
3546
|
+
const renderScrollmap = useCallback(() => {
|
|
3547
|
+
if (!vizRef || !scrollmap || scrollmap.length === 0 || !isDomLoaded)
|
|
3548
|
+
return;
|
|
3549
|
+
try {
|
|
3550
|
+
requestIdleCallback(() => vizRef?.scrollmap?.(scrollmap), { timeout: 300 });
|
|
3558
3551
|
}
|
|
3559
|
-
|
|
3560
|
-
|
|
3552
|
+
catch (error) {
|
|
3553
|
+
console.error(`🚀 🐥 ~ useScrollmap ~ renderScrollmap:`, error);
|
|
3554
|
+
}
|
|
3555
|
+
}, [vizRef, scrollmap, isDomLoaded]);
|
|
3556
|
+
const renderScrollBucket = useCallback(() => {
|
|
3561
3557
|
if (!vizRef || !scrollmap || scrollmap.length === 0 || !isDomLoaded)
|
|
3562
3558
|
return;
|
|
3559
|
+
const bucketData = scrollmap.map((point) => ({
|
|
3560
|
+
position: point.scrollReachY,
|
|
3561
|
+
value: point.cumulativeSum,
|
|
3562
|
+
percent: point.percUsers,
|
|
3563
|
+
}));
|
|
3563
3564
|
try {
|
|
3564
|
-
requestIdleCallback(() => {
|
|
3565
|
-
vizRef?.clearmap?.();
|
|
3566
|
-
vizRef?.scrollmap?.(scrollmap);
|
|
3567
|
-
}, { timeout: 300 });
|
|
3565
|
+
requestIdleCallback(() => vizRef?.scrollBucket?.(bucketData), { timeout: 300 });
|
|
3568
3566
|
}
|
|
3569
3567
|
catch (error) {
|
|
3570
|
-
|
|
3568
|
+
console.error(`🚀 🐥 ~ useScrollmap ~ renderScrollBucket:`, error);
|
|
3571
3569
|
}
|
|
3572
3570
|
}, [vizRef, scrollmap, isDomLoaded]);
|
|
3571
|
+
const start = useCallback(() => {
|
|
3572
|
+
vizRef?.clearmap?.();
|
|
3573
|
+
if (!vizRef || !scrollmap || scrollmap.length === 0 || !isDomLoaded)
|
|
3574
|
+
return;
|
|
3575
|
+
switch (scrollType) {
|
|
3576
|
+
case EScrollType.Attention:
|
|
3577
|
+
case EScrollType.Revenue: {
|
|
3578
|
+
renderScrollBucket();
|
|
3579
|
+
break;
|
|
3580
|
+
}
|
|
3581
|
+
case EScrollType.Depth:
|
|
3582
|
+
default:
|
|
3583
|
+
renderScrollmap();
|
|
3584
|
+
break;
|
|
3585
|
+
}
|
|
3586
|
+
}, [vizRef, scrollmap, isDomLoaded, scrollType, renderScrollmap, renderScrollBucket]);
|
|
3573
3587
|
return { start };
|
|
3574
3588
|
};
|
|
3575
3589
|
|
|
@@ -4299,14 +4313,25 @@ function handleHeightChange(s) {
|
|
|
4299
4313
|
s.throttleTimeout = setTimeout(() => {
|
|
4300
4314
|
s.throttleTimeout = null;
|
|
4301
4315
|
const currentHeight = getActualHeight(s);
|
|
4302
|
-
if (currentHeight === s.lastHeight)
|
|
4316
|
+
if (currentHeight === s.lastHeight) {
|
|
4317
|
+
// Height returned to known state — cancel any stale pending debounce
|
|
4318
|
+
// to prevent it from firing with an outdated height and corrupting lastHeight.
|
|
4319
|
+
if (s.debounceTimeout) {
|
|
4320
|
+
clearTimeout(s.debounceTimeout);
|
|
4321
|
+
s.debounceTimeout = null;
|
|
4322
|
+
}
|
|
4303
4323
|
return;
|
|
4324
|
+
}
|
|
4304
4325
|
s.logger.log(`Height changed: ${s.lastHeight}px -> ${currentHeight}px`);
|
|
4305
4326
|
if (s.debounceTimeout)
|
|
4306
4327
|
clearTimeout(s.debounceTimeout);
|
|
4307
4328
|
s.debounceTimeout = setTimeout(() => {
|
|
4308
4329
|
s.debounceTimeout = null;
|
|
4309
|
-
|
|
4330
|
+
// Re-read height at dispatch time to avoid using a stale closure value.
|
|
4331
|
+
const finalHeight = getActualHeight(s);
|
|
4332
|
+
if (finalHeight !== s.lastHeight) {
|
|
4333
|
+
processHeightChange(s, finalHeight);
|
|
4334
|
+
}
|
|
4310
4335
|
}, s.debounceMs);
|
|
4311
4336
|
}, s.throttleMs);
|
|
4312
4337
|
}
|
|
@@ -4316,16 +4341,16 @@ function observe(s) {
|
|
|
4316
4341
|
return;
|
|
4317
4342
|
}
|
|
4318
4343
|
s.observerCleanup?.();
|
|
4319
|
-
s.lastHeight = s
|
|
4344
|
+
s.lastHeight = getActualHeight(s);
|
|
4320
4345
|
s.logger.log('Initial height:', s.lastHeight);
|
|
4321
4346
|
s.observerCleanup = setup(s.iframe.contentDocument, () => handleHeightChange(s));
|
|
4322
4347
|
}
|
|
4323
|
-
function start$5(s, cfg) {
|
|
4348
|
+
function start$5(s, iframe, cfg) {
|
|
4324
4349
|
if (s.running) {
|
|
4325
4350
|
s.logger.warn('Observer is already running. Call stop() first.');
|
|
4326
4351
|
return;
|
|
4327
4352
|
}
|
|
4328
|
-
s.iframe =
|
|
4353
|
+
s.iframe = iframe;
|
|
4329
4354
|
s.config = cfg;
|
|
4330
4355
|
s.throttleMs = cfg.throttleMs ?? 25;
|
|
4331
4356
|
s.debounceMs = cfg.debounceMs ?? 500;
|
|
@@ -4382,7 +4407,7 @@ function createHeightObserver() {
|
|
|
4382
4407
|
running: false,
|
|
4383
4408
|
};
|
|
4384
4409
|
return {
|
|
4385
|
-
start: (cfg) => start$5(s, cfg),
|
|
4410
|
+
start: (iframe, cfg) => start$5(s, iframe, cfg),
|
|
4386
4411
|
stop: () => stop$5(s),
|
|
4387
4412
|
observe: () => observe(s),
|
|
4388
4413
|
clear: () => clear(s),
|
|
@@ -5426,6 +5451,20 @@ async function process(s) {
|
|
|
5426
5451
|
perf$3.endSession();
|
|
5427
5452
|
s.logger.groupEnd();
|
|
5428
5453
|
s.logger.log('Process completed:', result);
|
|
5454
|
+
s.heightObserver.stop();
|
|
5455
|
+
s.heightObserver.start(s.iframe, {
|
|
5456
|
+
iframe: s.iframe,
|
|
5457
|
+
debug: s.config.debug,
|
|
5458
|
+
throttleMs: 25,
|
|
5459
|
+
debounceMs: 500,
|
|
5460
|
+
onHeightChange: (result) => {
|
|
5461
|
+
s.config?.onSuccess?.(result);
|
|
5462
|
+
dispatchDimensionsEvent(result);
|
|
5463
|
+
},
|
|
5464
|
+
onError: (error) => {
|
|
5465
|
+
s.config?.onError?.(error);
|
|
5466
|
+
},
|
|
5467
|
+
});
|
|
5429
5468
|
s.config.onSuccess?.(result);
|
|
5430
5469
|
dispatchDimensionsEvent(result);
|
|
5431
5470
|
}
|
|
@@ -5783,7 +5822,7 @@ const htmlCache = {
|
|
|
5783
5822
|
|
|
5784
5823
|
const CANVAS_ID = 'clarity-heatmap-canvas';
|
|
5785
5824
|
const ATTENTION_HUES = [240, 210, 180, 150, 120, 90, 60, 30, 0];
|
|
5786
|
-
const CANVAS_MAX_HEIGHT = 65535;
|
|
5825
|
+
const CANVAS_MAX_HEIGHT$1 = 65535;
|
|
5787
5826
|
const Z_INDEX = 2147483647;
|
|
5788
5827
|
const DEFAULT_IFRAME_ID = 'clarity-snapshot-heatmap-iframe';
|
|
5789
5828
|
const SECONDARY_IFRAME_ID = 'clarity-snapshot-heatmap-iframe-secondary';
|
|
@@ -5848,7 +5887,7 @@ class AttentionMapRenderer {
|
|
|
5848
5887
|
const body = doc.body;
|
|
5849
5888
|
const de = doc.documentElement;
|
|
5850
5889
|
const contentHeight = Math.max(body.scrollHeight, body.offsetHeight, de.clientHeight, de.scrollHeight, de.offsetHeight);
|
|
5851
|
-
canvas.height = Math.min(contentHeight, CANVAS_MAX_HEIGHT);
|
|
5890
|
+
canvas.height = Math.min(contentHeight, CANVAS_MAX_HEIGHT$1);
|
|
5852
5891
|
canvas.style.top = '0px';
|
|
5853
5892
|
if (canvas.width <= 0 || canvas.height <= 0 || !this.attentionMapData)
|
|
5854
5893
|
return;
|
|
@@ -5870,7 +5909,7 @@ class AttentionMapRenderer {
|
|
|
5870
5909
|
const body = doc.body;
|
|
5871
5910
|
const de = doc.documentElement;
|
|
5872
5911
|
const contentHeight = Math.max(body.scrollHeight, body.offsetHeight, de.clientHeight, de.scrollHeight, de.offsetHeight);
|
|
5873
|
-
canvas.height = Math.min(contentHeight, CANVAS_MAX_HEIGHT);
|
|
5912
|
+
canvas.height = Math.min(contentHeight, CANVAS_MAX_HEIGHT$1);
|
|
5874
5913
|
canvas.style.top = '0px';
|
|
5875
5914
|
let iframeEl = document.getElementById(this.heatmapIframeId);
|
|
5876
5915
|
if (!iframeEl) {
|
|
@@ -5886,9 +5925,9 @@ class AttentionMapRenderer {
|
|
|
5886
5925
|
for (const point of this.attentionMapData) {
|
|
5887
5926
|
const timePercent = Math.floor((100 * point.cumulativeTime) / maxCumulativeTime);
|
|
5888
5927
|
const colorIndex = timePercent <= 5 ? 0 : Math.min(Math.ceil(timePercent / 10), 8);
|
|
5889
|
-
if (point.
|
|
5928
|
+
if (point.scrollReachY / 100 <= 1) {
|
|
5890
5929
|
point.colorIndex = colorIndex;
|
|
5891
|
-
colorStops[point.
|
|
5930
|
+
colorStops[point.scrollReachY] = colorIndex;
|
|
5892
5931
|
}
|
|
5893
5932
|
}
|
|
5894
5933
|
// Smooth the top portion based on the fold/viewport ratio
|
|
@@ -5919,7 +5958,7 @@ class AttentionMapRenderer {
|
|
|
5919
5958
|
buildAggregatedData = (doc, contentHeight, iframeEl) => {
|
|
5920
5959
|
const elementCache = new Map();
|
|
5921
5960
|
this.attentionMapData = Array.from({ length: 100 }, (_, i) => ({
|
|
5922
|
-
|
|
5961
|
+
scrollReachY: i,
|
|
5923
5962
|
cumulativeTime: 0,
|
|
5924
5963
|
colorIndex: 0,
|
|
5925
5964
|
}));
|
|
@@ -5989,6 +6028,128 @@ class AttentionMapRenderer {
|
|
|
5989
6028
|
};
|
|
5990
6029
|
}
|
|
5991
6030
|
|
|
6031
|
+
const CANVAS_MAX_HEIGHT = 65535;
|
|
6032
|
+
const REDRAW_INTERVAL = 30;
|
|
6033
|
+
class ScrollBucketRenderer {
|
|
6034
|
+
heatmap;
|
|
6035
|
+
lastData = null;
|
|
6036
|
+
dimensionsListener = null;
|
|
6037
|
+
redrawTimeout = null;
|
|
6038
|
+
constructor(heatmap) {
|
|
6039
|
+
this.heatmap = heatmap;
|
|
6040
|
+
this.attachDimensionsListener();
|
|
6041
|
+
}
|
|
6042
|
+
redraw = () => {
|
|
6043
|
+
if (!this.lastData)
|
|
6044
|
+
return;
|
|
6045
|
+
if (this.redrawTimeout)
|
|
6046
|
+
clearTimeout(this.redrawTimeout);
|
|
6047
|
+
this.redrawTimeout = setTimeout(() => this.draw(this.lastData), REDRAW_INTERVAL);
|
|
6048
|
+
};
|
|
6049
|
+
attachDimensionsListener = () => {
|
|
6050
|
+
this.dimensionsListener = () => this.redraw();
|
|
6051
|
+
window.addEventListener('iframe-dimensions-applied', this.dimensionsListener);
|
|
6052
|
+
};
|
|
6053
|
+
detachDimensionsListener = () => {
|
|
6054
|
+
if (this.dimensionsListener) {
|
|
6055
|
+
window.removeEventListener('iframe-dimensions-applied', this.dimensionsListener);
|
|
6056
|
+
this.dimensionsListener = null;
|
|
6057
|
+
}
|
|
6058
|
+
if (this.redrawTimeout) {
|
|
6059
|
+
clearTimeout(this.redrawTimeout);
|
|
6060
|
+
this.redrawTimeout = null;
|
|
6061
|
+
}
|
|
6062
|
+
};
|
|
6063
|
+
reset = () => {
|
|
6064
|
+
// no-op: canvas state is managed by the shared heatmap instance
|
|
6065
|
+
};
|
|
6066
|
+
clear = () => {
|
|
6067
|
+
this.lastData = null;
|
|
6068
|
+
this.detachDimensionsListener();
|
|
6069
|
+
};
|
|
6070
|
+
/**
|
|
6071
|
+
* Render discrete color bands for scrollRevenue / scrollAttention data.
|
|
6072
|
+
* Uses the shared heatmap canvas (same as HeatmapHelper.scroll()) to avoid
|
|
6073
|
+
* duplicate portal canvas management.
|
|
6074
|
+
* Stores data so the canvas can be redrawn when iframe height changes.
|
|
6075
|
+
*/
|
|
6076
|
+
renderBucket = async (data) => {
|
|
6077
|
+
if (!this.heatmap)
|
|
6078
|
+
return;
|
|
6079
|
+
this.attachDimensionsListener();
|
|
6080
|
+
this.lastData = data;
|
|
6081
|
+
await this.heatmap.waitForDialogs();
|
|
6082
|
+
this.draw(data);
|
|
6083
|
+
};
|
|
6084
|
+
draw = (data) => {
|
|
6085
|
+
if (!this.heatmap)
|
|
6086
|
+
return;
|
|
6087
|
+
const canvas = this.heatmap.overlay();
|
|
6088
|
+
if (!canvas)
|
|
6089
|
+
return;
|
|
6090
|
+
const context = canvas.getContext('2d');
|
|
6091
|
+
const doc = this.heatmap.state.window.document;
|
|
6092
|
+
const body = doc.body;
|
|
6093
|
+
const de = doc.documentElement;
|
|
6094
|
+
const height = Math.max(body.scrollHeight, body.offsetHeight, de.clientHeight, de.scrollHeight, de.offsetHeight);
|
|
6095
|
+
// Same canvas setup as HeatmapHelper.scroll(): full content height, top=0
|
|
6096
|
+
canvas.height = Math.min(height, CANVAS_MAX_HEIGHT);
|
|
6097
|
+
canvas.style.top = '0px';
|
|
6098
|
+
if (canvas.width <= 0 || canvas.height <= 0)
|
|
6099
|
+
return;
|
|
6100
|
+
const buckets = this.mapToBuckets(data);
|
|
6101
|
+
if (buckets.length === 0)
|
|
6102
|
+
return;
|
|
6103
|
+
const maxPercent = Math.max(...buckets.map((b) => b.percent));
|
|
6104
|
+
if (maxPercent <= 0)
|
|
6105
|
+
return;
|
|
6106
|
+
const gradient = context.createLinearGradient(0, 0, 0, canvas.height);
|
|
6107
|
+
for (const bucket of buckets) {
|
|
6108
|
+
const stopMid = (bucket.startY + bucket.endY) / 2 / 100;
|
|
6109
|
+
// Same hue direction as HeatmapHelper.scroll() (MaxHue = 240):
|
|
6110
|
+
// high percent → hue 0 (red/hot), low percent → hue 240 (blue/cold)
|
|
6111
|
+
const hue = (1 - bucket.percent / maxPercent) * 240;
|
|
6112
|
+
const color = `hsla(${Math.round(hue)}, 100%, 50%, 0.6)`;
|
|
6113
|
+
gradient.addColorStop(stopMid, color);
|
|
6114
|
+
}
|
|
6115
|
+
context.fillStyle = gradient;
|
|
6116
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
6117
|
+
};
|
|
6118
|
+
/**
|
|
6119
|
+
* Convert flat position markers → structured buckets with startY/endY.
|
|
6120
|
+
*
|
|
6121
|
+
* Input positions: [0, 5, 10, 15, ..., 95]
|
|
6122
|
+
* position=0 → { startY: 0, endY: 5 } (special start marker)
|
|
6123
|
+
* position=10 → { startY: 5, endY: 10 }
|
|
6124
|
+
* position=15 → { startY: 10, endY: 15 }
|
|
6125
|
+
*/
|
|
6126
|
+
mapToBuckets = (data) => {
|
|
6127
|
+
const sorted = [...data].sort((a, b) => a.position - b.position);
|
|
6128
|
+
const buckets = [];
|
|
6129
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
6130
|
+
const current = sorted[i];
|
|
6131
|
+
let startY;
|
|
6132
|
+
let endY;
|
|
6133
|
+
if (current.position === 0) {
|
|
6134
|
+
// position=0 is the start marker → range 0 to next position
|
|
6135
|
+
const next = sorted[i + 1];
|
|
6136
|
+
if (!next)
|
|
6137
|
+
continue;
|
|
6138
|
+
startY = 0;
|
|
6139
|
+
endY = next.position;
|
|
6140
|
+
}
|
|
6141
|
+
else {
|
|
6142
|
+
// Each non-zero position is the END of its bucket; start = previous position
|
|
6143
|
+
const prev = sorted[i - 1];
|
|
6144
|
+
startY = prev ? prev.position : 0;
|
|
6145
|
+
endY = current.position;
|
|
6146
|
+
}
|
|
6147
|
+
buckets.push({ startY, endY, value: current.value, percent: current.percent });
|
|
6148
|
+
}
|
|
6149
|
+
return buckets;
|
|
6150
|
+
};
|
|
6151
|
+
}
|
|
6152
|
+
|
|
5992
6153
|
var Event;
|
|
5993
6154
|
(function (Event) {
|
|
5994
6155
|
/* Data */
|
|
@@ -6301,15 +6462,23 @@ const renderLoop = async (ctx, options) => {
|
|
|
6301
6462
|
|
|
6302
6463
|
class GXVisualizer extends Visualizer {
|
|
6303
6464
|
attentionMap;
|
|
6465
|
+
scrollBucketMap;
|
|
6304
6466
|
originalClearmap;
|
|
6305
6467
|
originalSetup;
|
|
6468
|
+
originalScrollmap;
|
|
6469
|
+
originalClickmap;
|
|
6306
6470
|
constructor() {
|
|
6307
6471
|
super();
|
|
6308
6472
|
this.attentionMap = new AttentionMapRenderer(null);
|
|
6473
|
+
this.scrollBucketMap = new ScrollBucketRenderer(null);
|
|
6309
6474
|
this.originalSetup = this.setup;
|
|
6310
6475
|
this.originalClearmap = this.clearmap;
|
|
6476
|
+
this.originalScrollmap = this.scrollmap;
|
|
6477
|
+
this.originalClickmap = this.clickmap;
|
|
6311
6478
|
this.clearmap = this.clearmapOverride;
|
|
6312
6479
|
this.setup = this.setupOverride;
|
|
6480
|
+
this.scrollmap = this.scrollmapOverride;
|
|
6481
|
+
this.clickmap = this.clickmapOverride;
|
|
6313
6482
|
}
|
|
6314
6483
|
htmlRender = async (props) => {
|
|
6315
6484
|
const { decoded, target, portalCanvasId, useproxy, logerror } = props;
|
|
@@ -6354,12 +6523,27 @@ class GXVisualizer extends Visualizer {
|
|
|
6354
6523
|
this.clearmapOverride();
|
|
6355
6524
|
this.attentionMap.attention(attentionData, avgFold, this, isSecondary);
|
|
6356
6525
|
};
|
|
6526
|
+
/**
|
|
6527
|
+
* Render discrete color bands for scrollRevenue / scrollAttention data.
|
|
6528
|
+
* @param data - Array of bucket inputs with position markers (0, 5, 10, ..., 95) and percent values
|
|
6529
|
+
*/
|
|
6530
|
+
scrollBucket = async (data) => {
|
|
6531
|
+
this.clearmapOverride();
|
|
6532
|
+
await this.scrollBucketMap.renderBucket(data);
|
|
6533
|
+
};
|
|
6534
|
+
scrollmapOverride = (data, averageFold, addMarkers) => {
|
|
6535
|
+
this.clearmapOverride();
|
|
6536
|
+
this.originalScrollmap(data, averageFold, addMarkers);
|
|
6537
|
+
};
|
|
6538
|
+
clickmapOverride = (activity) => {
|
|
6539
|
+
this.clearmapOverride();
|
|
6540
|
+
this.originalClickmap(activity);
|
|
6541
|
+
};
|
|
6357
6542
|
buildHtmlByCached = async (cached, options) => {
|
|
6358
6543
|
const { target, useproxy, portalCanvasId } = options;
|
|
6359
6544
|
if (!cached || !target)
|
|
6360
6545
|
throw new Error('Failed to render HTML cached');
|
|
6361
6546
|
const doc = target.document;
|
|
6362
|
-
target.window;
|
|
6363
6547
|
try {
|
|
6364
6548
|
await this.setup(target, { version: cached.version, useproxy, portalCanvasId });
|
|
6365
6549
|
doc.open();
|
|
@@ -6408,14 +6592,19 @@ class GXVisualizer extends Visualizer {
|
|
|
6408
6592
|
await renderLoop(ctx, options);
|
|
6409
6593
|
};
|
|
6410
6594
|
setupOverride = async (target, options) => {
|
|
6411
|
-
|
|
6595
|
+
// Mirror original Visualizer.setup(): only reset (remove listeners/state), not clear canvas.
|
|
6596
|
+
// The canvas lives in the old window's DOM which will be replaced by originalSetup's reset().
|
|
6597
|
+
this.attentionMap?.reset();
|
|
6598
|
+
this.scrollBucketMap?.reset();
|
|
6412
6599
|
await this.originalSetup(target, options);
|
|
6413
6600
|
this.attentionMap = new AttentionMapRenderer(this.state);
|
|
6601
|
+
this.scrollBucketMap = new ScrollBucketRenderer(this.heatmap);
|
|
6414
6602
|
return this;
|
|
6415
6603
|
};
|
|
6416
6604
|
clearmapOverride = () => {
|
|
6417
6605
|
this.originalClearmap();
|
|
6418
6606
|
this.attentionMap?.clear();
|
|
6607
|
+
this.scrollBucketMap?.clear();
|
|
6419
6608
|
};
|
|
6420
6609
|
}
|
|
6421
6610
|
|
|
@@ -6739,136 +6928,6 @@ const useContainerDimensions = (props) => {
|
|
|
6739
6928
|
return { containerWidth, containerHeight };
|
|
6740
6929
|
};
|
|
6741
6930
|
|
|
6742
|
-
const useObserveIframeHeight = (props) => {
|
|
6743
|
-
const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
|
|
6744
|
-
const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
|
|
6745
|
-
const isDomLoaded = useHeatmapVizContext((s) => s.isDomLoaded);
|
|
6746
|
-
const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
|
|
6747
|
-
const { iframeRef } = props;
|
|
6748
|
-
const resizeObserverRef = useRef(null);
|
|
6749
|
-
const mutationObserverRef = useRef(null);
|
|
6750
|
-
const debounceTimerRef = useRef(null);
|
|
6751
|
-
const lastHeightRef = useRef(0);
|
|
6752
|
-
const animationFrameRef = useRef(null);
|
|
6753
|
-
const updateIframeHeight = useCallback(() => {
|
|
6754
|
-
const iframe = iframeRef.current;
|
|
6755
|
-
if (!iframe)
|
|
6756
|
-
return;
|
|
6757
|
-
try {
|
|
6758
|
-
const iframeDocument = iframe.contentDocument;
|
|
6759
|
-
const iframeBody = iframeDocument?.body;
|
|
6760
|
-
const iframeDocumentElement = iframeDocument?.documentElement;
|
|
6761
|
-
if (!iframeBody || !iframeDocumentElement)
|
|
6762
|
-
return;
|
|
6763
|
-
// iframe.style.height = 'auto'; // TODO: check if this is needed
|
|
6764
|
-
requestAnimationFrame(() => {
|
|
6765
|
-
iframe.style.height = `${wrapperHeight}px`;
|
|
6766
|
-
const bodyHeight = Math.max(iframeBody.scrollHeight, iframeBody.offsetHeight, iframeBody.clientHeight);
|
|
6767
|
-
const documentHeight = Math.max(iframeDocumentElement.scrollHeight, iframeDocumentElement.offsetHeight, iframeDocumentElement.clientHeight);
|
|
6768
|
-
const actualHeight = Math.max(bodyHeight, documentHeight);
|
|
6769
|
-
if (actualHeight > 0) {
|
|
6770
|
-
lastHeightRef.current = actualHeight;
|
|
6771
|
-
// iframe.height = `${actualHeight}px`;
|
|
6772
|
-
iframe.style.height = `${actualHeight}px`;
|
|
6773
|
-
setIframeHeight(actualHeight);
|
|
6774
|
-
}
|
|
6775
|
-
});
|
|
6776
|
-
}
|
|
6777
|
-
catch (error) {
|
|
6778
|
-
console.warn('Cannot measure iframe content:', error);
|
|
6779
|
-
}
|
|
6780
|
-
}, [iframeRef, wrapperHeight]);
|
|
6781
|
-
useCallback(() => {
|
|
6782
|
-
// Cancel pending updates
|
|
6783
|
-
if (debounceTimerRef.current) {
|
|
6784
|
-
clearTimeout(debounceTimerRef.current);
|
|
6785
|
-
}
|
|
6786
|
-
if (animationFrameRef.current) {
|
|
6787
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
6788
|
-
}
|
|
6789
|
-
debounceTimerRef.current = setTimeout(() => {
|
|
6790
|
-
animationFrameRef.current = requestAnimationFrame(() => {
|
|
6791
|
-
updateIframeHeight();
|
|
6792
|
-
});
|
|
6793
|
-
}, 50);
|
|
6794
|
-
}, [updateIframeHeight]);
|
|
6795
|
-
// Immediate update không debounce (cho ResizeObserver)
|
|
6796
|
-
const immediateUpdate = useCallback(() => {
|
|
6797
|
-
if (animationFrameRef.current) {
|
|
6798
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
6799
|
-
}
|
|
6800
|
-
animationFrameRef.current = requestAnimationFrame(() => {
|
|
6801
|
-
updateIframeHeight();
|
|
6802
|
-
});
|
|
6803
|
-
}, [updateIframeHeight]);
|
|
6804
|
-
useEffect(() => {
|
|
6805
|
-
const iframe = iframeRef.current;
|
|
6806
|
-
if (!iframe || !iframeHeight || !isDomLoaded)
|
|
6807
|
-
return;
|
|
6808
|
-
const setupObservers = () => {
|
|
6809
|
-
try {
|
|
6810
|
-
const iframeDocument = iframe.contentDocument;
|
|
6811
|
-
const iframeBody = iframeDocument?.body;
|
|
6812
|
-
if (!iframeBody)
|
|
6813
|
-
return;
|
|
6814
|
-
// Cleanup existing observers
|
|
6815
|
-
if (resizeObserverRef.current) {
|
|
6816
|
-
resizeObserverRef.current.disconnect();
|
|
6817
|
-
}
|
|
6818
|
-
if (mutationObserverRef.current) {
|
|
6819
|
-
mutationObserverRef.current.disconnect();
|
|
6820
|
-
}
|
|
6821
|
-
if (typeof window.ResizeObserver !== 'undefined') {
|
|
6822
|
-
resizeObserverRef.current = new ResizeObserver(immediateUpdate);
|
|
6823
|
-
resizeObserverRef.current.observe(iframeBody);
|
|
6824
|
-
const iframeDocumentElement = iframeDocument?.documentElement;
|
|
6825
|
-
if (iframeDocumentElement) {
|
|
6826
|
-
resizeObserverRef.current.observe(iframeDocumentElement);
|
|
6827
|
-
}
|
|
6828
|
-
}
|
|
6829
|
-
if (typeof window.MutationObserver !== 'undefined') {
|
|
6830
|
-
mutationObserverRef.current = new MutationObserver(immediateUpdate);
|
|
6831
|
-
mutationObserverRef.current.observe(iframeBody, {
|
|
6832
|
-
childList: true,
|
|
6833
|
-
subtree: true,
|
|
6834
|
-
attributes: true,
|
|
6835
|
-
attributeFilter: ['style', 'class'],
|
|
6836
|
-
characterData: false,
|
|
6837
|
-
});
|
|
6838
|
-
}
|
|
6839
|
-
updateIframeHeight();
|
|
6840
|
-
}
|
|
6841
|
-
catch (error) {
|
|
6842
|
-
console.warn('Cannot access iframe content:', error);
|
|
6843
|
-
}
|
|
6844
|
-
};
|
|
6845
|
-
if (iframe.contentDocument?.readyState === 'complete') {
|
|
6846
|
-
setupObservers();
|
|
6847
|
-
}
|
|
6848
|
-
else {
|
|
6849
|
-
iframe.addEventListener('load', setupObservers, { once: true });
|
|
6850
|
-
}
|
|
6851
|
-
return () => {
|
|
6852
|
-
// Cleanup observers
|
|
6853
|
-
if (resizeObserverRef.current) {
|
|
6854
|
-
resizeObserverRef.current.disconnect();
|
|
6855
|
-
}
|
|
6856
|
-
if (mutationObserverRef.current) {
|
|
6857
|
-
mutationObserverRef.current.disconnect();
|
|
6858
|
-
}
|
|
6859
|
-
// Cleanup timers
|
|
6860
|
-
if (debounceTimerRef.current) {
|
|
6861
|
-
clearTimeout(debounceTimerRef.current);
|
|
6862
|
-
}
|
|
6863
|
-
if (animationFrameRef.current) {
|
|
6864
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
6865
|
-
}
|
|
6866
|
-
iframe.removeEventListener('load', setupObservers);
|
|
6867
|
-
};
|
|
6868
|
-
}, [iframeRef, iframeHeight, isDomLoaded]);
|
|
6869
|
-
return {};
|
|
6870
|
-
};
|
|
6871
|
-
|
|
6872
6931
|
const useScaleCalculation = (props) => {
|
|
6873
6932
|
const widthScale = useHeatmapVizContext((s) => s.widthScale);
|
|
6874
6933
|
const zoomRatio = useHeatmapVizContext((s) => s.zoomRatio);
|
|
@@ -6948,7 +7007,7 @@ const useHeatmapScale = (props) => {
|
|
|
6948
7007
|
// 2. Get content dimensions from config
|
|
6949
7008
|
const viewport = useHeatmapViewportByDevice();
|
|
6950
7009
|
// 3. Observe iframe height (now reacts to width changes)
|
|
6951
|
-
useObserveIframeHeight({ iframeRef });
|
|
7010
|
+
// useObserveIframeHeight({ iframeRef });
|
|
6952
7011
|
// 4. Calculate scale
|
|
6953
7012
|
const { widthScale } = useScaleCalculation({ containerWidth, containerHeight, iframeHeight });
|
|
6954
7013
|
// 5. Setup scroll sync
|