@gemx-dev/heatmap-react 3.5.92-dev.12 → 3.5.92-dev.14

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.
@@ -1,6 +1,5 @@
1
- interface IUseHeatmapRenderResult {
1
+ export interface IUseHeatmapRenderResult {
2
2
  iframeRef: React.RefObject<HTMLIFrameElement | null>;
3
3
  }
4
4
  export declare const useHeatmapRender: () => IUseHeatmapRenderResult;
5
- export {};
6
5
  //# sourceMappingURL=useHeatmapRender.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useHeatmapRender.d.ts","sourceRoot":"","sources":["../../../src/hooks/viz-render/useHeatmapRender.ts"],"names":[],"mappings":"AAiCA,UAAU,uBAAuB;IAC/B,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;CACtD;AAcD,eAAO,MAAM,gBAAgB,QAAO,uBAoFnC,CAAC"}
1
+ {"version":3,"file":"useHeatmapRender.d.ts","sourceRoot":"","sources":["../../../src/hooks/viz-render/useHeatmapRender.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;CACtD;AAYD,eAAO,MAAM,gBAAgB,QAAO,uBAsGnC,CAAC"}
package/dist/esm/index.js CHANGED
@@ -4148,7 +4148,7 @@ function flush() {
4148
4148
  window.__gemxPerf = getReport();
4149
4149
  }
4150
4150
  // ── Global singleton export ───────────────────────────────────────────────────
4151
- const perf = {
4151
+ const perf$1 = {
4152
4152
  startSession,
4153
4153
  endSession,
4154
4154
  mark: globalMark,
@@ -4158,6 +4158,39 @@ const perf = {
4158
4158
  enable: enableGlobal,
4159
4159
  disable: disableGlobal,
4160
4160
  };
4161
+ // ── createPerfTimer factory ───────────────────────────────────────────────────
4162
+ function createPerfTimer(config) {
4163
+ let cfg = typeof config === 'string' ? { prefix: config, enabled: true } : { enabled: true, ...config };
4164
+ function log(icon, label, extra) {
4165
+ if (!cfg.enabled)
4166
+ return;
4167
+ const suffix = extra ? ` — ${extra}` : '';
4168
+ console.log(`[${cfg.prefix}] ${icon} ${label}${suffix}`);
4169
+ }
4170
+ return {
4171
+ configure(next) {
4172
+ cfg = { ...cfg, ...next };
4173
+ },
4174
+ mark(label) {
4175
+ const t = globalMark(`[${cfg.prefix}] ${label}`);
4176
+ log('⏱', label);
4177
+ return t;
4178
+ },
4179
+ measure(label, from) {
4180
+ globalMeasure(`[${cfg.prefix}] ${label}`, from);
4181
+ const ms = (performance.now() - from).toFixed(1);
4182
+ log('✅', label, `${ms}ms`);
4183
+ },
4184
+ async wrap(label, fn) {
4185
+ const t = globalMark(`[${cfg.prefix}] ${label}`);
4186
+ log('⏱', label);
4187
+ const result = await fn();
4188
+ const duration = globalMeasure(`[${cfg.prefix}] ${label}`, t);
4189
+ log('✅', label, `${duration.toFixed(1)}ms`);
4190
+ return result;
4191
+ },
4192
+ };
4193
+ }
4161
4194
 
4162
4195
  /**
4163
4196
  * DOM observation setup — ResizeObserver + MutationObserver.
@@ -5351,52 +5384,52 @@ function configure(debug) {
5351
5384
  }
5352
5385
  async function run$1(ctx, activeGlobal, shopFix) {
5353
5386
  // ── Phase 1: beforeProcess ────────────────────────────────────────────────
5354
- const t1 = perf.mark('phase1.beforeProcess');
5387
+ const t1 = perf$1.mark('phase1.beforeProcess');
5355
5388
  for (const fix of activeGlobal) {
5356
5389
  if (fix.beforeProcess) {
5357
5390
  logger.log(`[beforeProcess] ${fix.name}`);
5358
- const t = perf.mark(`phase1.${fix.name}.beforeProcess`);
5391
+ const t = perf$1.mark(`phase1.${fix.name}.beforeProcess`);
5359
5392
  await fix.beforeProcess(ctx);
5360
- perf.measure(`phase1.${fix.name}.beforeProcess`, t);
5393
+ perf$1.measure(`phase1.${fix.name}.beforeProcess`, t);
5361
5394
  }
5362
5395
  }
5363
5396
  if (shopFix?.beforeProcess) {
5364
5397
  logger.log('[beforeProcess] shop');
5365
- const t = perf.mark('phase1.shop.beforeProcess');
5398
+ const t = perf$1.mark('phase1.shop.beforeProcess');
5366
5399
  await shopFix.beforeProcess(ctx);
5367
- perf.measure('phase1.shop.beforeProcess', t);
5400
+ perf$1.measure('phase1.shop.beforeProcess', t);
5368
5401
  }
5369
- perf.measure('phase1.beforeProcess', t1);
5402
+ perf$1.measure('phase1.beforeProcess', t1);
5370
5403
  // ── Phase 2: process ──────────────────────────────────────────────────────
5371
- const t2 = perf.mark('phase2.process');
5404
+ const t2 = perf$1.mark('phase2.process');
5372
5405
  for (const fix of activeGlobal) {
5373
5406
  if (fix.process) {
5374
5407
  logger.log(`[process] ${fix.name}`);
5375
- const t = perf.mark(`phase2.${fix.name}.process`);
5408
+ const t = perf$1.mark(`phase2.${fix.name}.process`);
5376
5409
  await fix.process(ctx);
5377
- perf.measure(`phase2.${fix.name}.process`, t);
5410
+ perf$1.measure(`phase2.${fix.name}.process`, t);
5378
5411
  }
5379
5412
  }
5380
- perf.measure('phase2.process', t2);
5413
+ perf$1.measure('phase2.process', t2);
5381
5414
  // ── Phase 3: afterProcess ─────────────────────────────────────────────────
5382
- const t3 = perf.mark('phase3.afterProcess');
5415
+ const t3 = perf$1.mark('phase3.afterProcess');
5383
5416
  if (shopFix?.afterProcess) {
5384
5417
  logger.log('[afterProcess] shop');
5385
- const t = perf.mark('phase3.shop.afterProcess');
5418
+ const t = perf$1.mark('phase3.shop.afterProcess');
5386
5419
  await shopFix.afterProcess(ctx);
5387
- perf.measure('phase3.shop.afterProcess', t);
5420
+ perf$1.measure('phase3.shop.afterProcess', t);
5388
5421
  }
5389
5422
  for (const fix of activeGlobal) {
5390
5423
  if (fix.afterProcess) {
5391
5424
  logger.log(`[afterProcess] ${fix.name}`);
5392
- const t = perf.mark(`phase3.${fix.name}.afterProcess`);
5425
+ const t = perf$1.mark(`phase3.${fix.name}.afterProcess`);
5393
5426
  await fix.afterProcess(ctx);
5394
- perf.measure(`phase3.${fix.name}.afterProcess`, t);
5427
+ perf$1.measure(`phase3.${fix.name}.afterProcess`, t);
5395
5428
  }
5396
5429
  }
5397
- perf.measure('phase3.afterProcess', t3);
5430
+ perf$1.measure('phase3.afterProcess', t3);
5398
5431
  // ── Phase 4: getDimensions ────────────────────────────────────────────────
5399
- const t4 = perf.mark('phase4.getDimensions');
5432
+ const t4 = perf$1.mark('phase4.getDimensions');
5400
5433
  return new Promise((resolve) => {
5401
5434
  requestAnimationFrame(() => {
5402
5435
  let dimensions = null;
@@ -5421,7 +5454,7 @@ async function run$1(ctx, activeGlobal, shopFix) {
5421
5454
  dimensions = { height: getFinalHeight(ctx.doc, ctx.win), width: getFinalWidth(ctx.doc) };
5422
5455
  }
5423
5456
  logger.log('Final dimensions:', dimensions);
5424
- perf.measure('phase4.getDimensions', t4);
5457
+ perf$1.measure('phase4.getDimensions', t4);
5425
5458
  resolve(dimensions);
5426
5459
  });
5427
5460
  });
@@ -5549,14 +5582,14 @@ async function run(s) {
5549
5582
  const activeGlobal = getActiveFixes(ctx);
5550
5583
  if (activeGlobal.length > 0)
5551
5584
  s.logger.log(`Active global fixes: ${activeGlobal.map((f) => f.name).join(', ')}`);
5552
- const tRun = perf.mark('viewport.run');
5585
+ const tRun = perf$1.mark('viewport.run');
5553
5586
  try {
5554
5587
  const result = await run$1(ctx, activeGlobal, s.shopFix);
5555
- perf.measure('viewport.run', tRun);
5588
+ perf$1.measure('viewport.run', tRun);
5556
5589
  return result;
5557
5590
  }
5558
5591
  catch (err) {
5559
- perf.measure('viewport.run', tRun);
5592
+ perf$1.measure('viewport.run', tRun);
5560
5593
  s.logger.error('Critical error:', err);
5561
5594
  return { height: s.doc.body?.scrollHeight || 1000, width: s.doc.body?.scrollWidth || 1000 };
5562
5595
  }
@@ -5606,22 +5639,22 @@ async function process(s) {
5606
5639
  return;
5607
5640
  }
5608
5641
  const sessionId = `render-${Date.now()}`;
5609
- perf.startSession(sessionId);
5610
- const t0 = perf.mark('orchestrator.process');
5642
+ perf$1.startSession(sessionId);
5643
+ const t0 = perf$1.mark('orchestrator.process');
5611
5644
  try {
5612
5645
  s.logger.log('Processing viewport units...');
5613
5646
  s.viewportReplacer.start(s.iframe, s.config);
5614
5647
  s.navigationBlocker.start(s.iframe, { debug: s.config.debug });
5615
5648
  const result = await s.viewportReplacer.run();
5616
- perf.measure('orchestrator.process', t0);
5617
- perf.endSession();
5649
+ perf$1.measure('orchestrator.process', t0);
5650
+ perf$1.endSession();
5618
5651
  s.logger.log('Process completed:', result);
5619
5652
  s.config.onSuccess?.(result);
5620
5653
  dispatchDimensionsEvent(result);
5621
5654
  }
5622
5655
  catch (error) {
5623
- perf.measure('orchestrator.process', t0);
5624
- perf.endSession();
5656
+ perf$1.measure('orchestrator.process', t0);
5657
+ perf$1.endSession();
5625
5658
  s.logger.error('Failed to process:', error);
5626
5659
  s.config.onError?.(error);
5627
5660
  }
@@ -6083,20 +6116,15 @@ class GXVisualizer extends Visualizer {
6083
6116
  constructor() {
6084
6117
  super();
6085
6118
  this.attentionMap = new AttentionMapRenderer(null);
6086
- // Save references to base implementations before overriding
6087
6119
  this.originalSetup = this.setup;
6088
6120
  this.originalClearmap = this.clearmap;
6089
6121
  this.clearmap = this.clearmapOverride;
6090
6122
  this.setup = this.setupOverride;
6091
6123
  }
6092
6124
  setupOverride = async (target, options) => {
6093
- // Clear existing custom renderers before re-initializing (null-safe)
6094
6125
  this.attentionMap?.clear();
6095
- // Initialize base Visualizer (sets this.state, this.heatmap, this.layout, etc.)
6096
6126
  await this.originalSetup(target, options);
6097
- // Re-create custom renderers with the newly initialized PlaybackState
6098
- const state = this.state;
6099
- this.attentionMap = new AttentionMapRenderer(state);
6127
+ this.attentionMap = new AttentionMapRenderer(this.state);
6100
6128
  return this;
6101
6129
  };
6102
6130
  clearmapOverride = () => {
@@ -6115,17 +6143,7 @@ class GXVisualizer extends Visualizer {
6115
6143
  };
6116
6144
  }
6117
6145
 
6118
- // ── Performance timing ────────────────────────────────────────────────────────
6119
- function mark(label) {
6120
- const t = performance.now();
6121
- console.log(`[Render] ⏱ ${label}`);
6122
- return t;
6123
- }
6124
- function measure(label, startMs) {
6125
- const ms = (performance.now() - startMs).toFixed(1);
6126
- console.log(`[Render] ✅ ${label} — ${ms}ms`);
6127
- }
6128
- // ── Hook ──────────────────────────────────────────────────────────────────────
6146
+ const perf = createPerfTimer('Render');
6129
6147
  const useHeatmapRender = () => {
6130
6148
  const viewId = useViewIdContext();
6131
6149
  const data = useHeatmapDataContext((s) => s.data);
@@ -6141,36 +6159,41 @@ const useHeatmapRender = () => {
6141
6159
  const iframeRef = useRef(null);
6142
6160
  const helperRef = useRef(null);
6143
6161
  const abortRef = useRef(null);
6162
+ const pendingDataRef = useRef(null);
6163
+ const wrapperHeightRef = useRef(wrapperHeight);
6164
+ wrapperHeightRef.current = wrapperHeight;
6165
+ const contentWidthRef = useRef(contentWidth);
6166
+ contentWidthRef.current = contentWidth;
6144
6167
  const renderHeatmap = useCallback(async (payloads) => {
6145
- if (contentWidth === 0 || wrapperHeight === 0)
6168
+ if (contentWidthRef.current === 0 || wrapperHeightRef.current === 0) {
6169
+ pendingDataRef.current = payloads;
6146
6170
  return;
6171
+ }
6172
+ pendingDataRef.current = null;
6147
6173
  if (!payloads || payloads.length === 0)
6148
6174
  return;
6149
- // Cancel any in-flight render before starting a new one
6175
+ const iframe = iframeRef.current;
6176
+ if (!iframe?.contentWindow)
6177
+ return;
6150
6178
  abortRef.current?.abort();
6151
6179
  const abort = new AbortController();
6152
6180
  abortRef.current = abort;
6153
- const t0 = mark('renderHeatmap start');
6181
+ const t0 = perf.mark('renderHeatmap start');
6154
6182
  const visualizer = vizRef ?? new GXVisualizer();
6155
6183
  if (!vizRef)
6156
6184
  setVizRef(visualizer);
6157
6185
  visualizer.configure({ excludeClassNames });
6158
6186
  setIsRenderedViz(false);
6159
- const iframe = iframeRef.current;
6160
- if (!iframe?.contentWindow)
6161
- return;
6162
- const tHtml = mark('visualizer.html start');
6163
- await visualizer.html(payloads, iframe.contentWindow, viewId);
6164
- measure('visualizer.html', tHtml);
6165
- // Bail out if data changed or component unmounted while we were awaiting
6187
+ await perf.wrap('visualizer.html', () => visualizer.html(payloads, iframe.contentWindow, viewId));
6166
6188
  if (abort.signal.aborted)
6167
6189
  return;
6190
+ const size = { width: contentWidthRef.current, height: wrapperHeightRef.current };
6168
6191
  startIframe({
6169
6192
  helperRef,
6170
6193
  iframe,
6171
6194
  shopId,
6172
6195
  deviceType,
6173
- size: { width: contentWidth, height: wrapperHeight },
6196
+ size,
6174
6197
  t0,
6175
6198
  onSuccess: (height) => {
6176
6199
  if (abort.signal.aborted)
@@ -6180,27 +6203,33 @@ const useHeatmapRender = () => {
6180
6203
  setIsRenderedViz(true);
6181
6204
  },
6182
6205
  });
6183
- }, [wrapperHeight, contentWidth, deviceType]);
6206
+ }, [deviceType]);
6184
6207
  useEffect(() => {
6185
6208
  if (!data || data.length === 0)
6186
6209
  return;
6187
6210
  const decoded = decodeArrayClarity(data);
6188
6211
  renderHeatmap(decoded);
6189
- return () => {
6190
- setVizRef(null);
6191
- };
6192
- }, [data, renderHeatmap, setVizRef]);
6193
- // Abort render + stop helper when the component unmounts
6212
+ return () => { };
6213
+ }, [data, renderHeatmap]);
6214
+ // Retry pending render when dimensions become available
6215
+ useEffect(() => {
6216
+ if (contentWidth === 0 || wrapperHeight === 0)
6217
+ return;
6218
+ if (!pendingDataRef.current)
6219
+ return;
6220
+ const pending = pendingDataRef.current;
6221
+ pendingDataRef.current = null;
6222
+ renderHeatmap(pending);
6223
+ }, [contentWidth, wrapperHeight, renderHeatmap]);
6194
6224
  useEffect(() => {
6195
6225
  return () => {
6226
+ setVizRef(null);
6196
6227
  abortRef.current?.abort();
6197
6228
  helperRef.current?.stop();
6198
6229
  helperRef.current = null;
6199
6230
  };
6200
- }, []);
6201
- return {
6202
- iframeRef,
6203
- };
6231
+ }, [setVizRef]);
6232
+ return { iframeRef };
6204
6233
  };
6205
6234
  // ── Helpers ───────────────────────────────────────────────────────────────────
6206
6235
  function startIframe({ helperRef, iframe, shopId, deviceType = EDeviceType.Desktop, size, t0, onSuccess, }) {
@@ -6209,7 +6238,7 @@ function startIframe({ helperRef, iframe, shopId, deviceType = EDeviceType.Deskt
6209
6238
  if (docHeight === 0)
6210
6239
  return;
6211
6240
  helperRef.current?.stop();
6212
- const tHelper = mark('IframeHelper.start');
6241
+ const tHelper = perf.mark('IframeHelper.start');
6213
6242
  const helper = createIframeHelper();
6214
6243
  helperRef.current = helper;
6215
6244
  helper.start({
@@ -6220,8 +6249,8 @@ function startIframe({ helperRef, iframe, shopId, deviceType = EDeviceType.Deskt
6220
6249
  debug: true,
6221
6250
  shopId,
6222
6251
  onSuccess: (data) => {
6223
- measure('IframeHelper processing', tHelper);
6224
- measure('Total render', t0);
6252
+ perf.measure('IframeHelper processing', tHelper);
6253
+ perf.measure('Total render', t0);
6225
6254
  iframe.style.height = `${data.height}px`;
6226
6255
  onSuccess(data.height);
6227
6256
  },