@gemx-dev/heatmap-react 3.5.92-dev.19 → 3.5.92-dev.20

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.
Files changed (36) hide show
  1. package/dist/esm/hooks/viz-render/useHeatmapRenderDom.d.ts.map +1 -1
  2. package/dist/esm/index.js +226 -417
  3. package/dist/esm/index.mjs +226 -417
  4. package/dist/esm/libs/iframe-processor/orchestrator.d.ts.map +1 -1
  5. package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts +0 -1
  6. package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts.map +1 -1
  7. package/dist/esm/libs/iframe-processor/processors/viewport/listeners.d.ts.map +1 -1
  8. package/dist/esm/libs/visualizer/GXVisualizer.d.ts +11 -9
  9. package/dist/esm/libs/visualizer/GXVisualizer.d.ts.map +1 -1
  10. package/dist/esm/libs/visualizer/cache/config.d.ts +1 -1
  11. package/dist/esm/libs/visualizer/cache/config.d.ts.map +1 -1
  12. package/dist/esm/libs/visualizer/shadow-dom/extractor.d.ts.map +1 -1
  13. package/dist/esm/libs/visualizer/types/visualize.d.ts +19 -0
  14. package/dist/esm/libs/visualizer/types/visualize.d.ts.map +1 -1
  15. package/dist/esm/libs/visualizer/utils/delay.d.ts +21 -0
  16. package/dist/esm/libs/visualizer/utils/delay.d.ts.map +1 -0
  17. package/dist/esm/libs/visualizer/utils/render.d.ts +7 -0
  18. package/dist/esm/libs/visualizer/utils/render.d.ts.map +1 -0
  19. package/dist/umd/hooks/viz-render/useHeatmapRenderDom.d.ts.map +1 -1
  20. package/dist/umd/index.js +2 -2
  21. package/dist/umd/libs/iframe-processor/orchestrator.d.ts.map +1 -1
  22. package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts +0 -1
  23. package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts.map +1 -1
  24. package/dist/umd/libs/iframe-processor/processors/viewport/listeners.d.ts.map +1 -1
  25. package/dist/umd/libs/visualizer/GXVisualizer.d.ts +11 -9
  26. package/dist/umd/libs/visualizer/GXVisualizer.d.ts.map +1 -1
  27. package/dist/umd/libs/visualizer/cache/config.d.ts +1 -1
  28. package/dist/umd/libs/visualizer/cache/config.d.ts.map +1 -1
  29. package/dist/umd/libs/visualizer/shadow-dom/extractor.d.ts.map +1 -1
  30. package/dist/umd/libs/visualizer/types/visualize.d.ts +19 -0
  31. package/dist/umd/libs/visualizer/types/visualize.d.ts.map +1 -1
  32. package/dist/umd/libs/visualizer/utils/delay.d.ts +21 -0
  33. package/dist/umd/libs/visualizer/utils/delay.d.ts.map +1 -0
  34. package/dist/umd/libs/visualizer/utils/render.d.ts +7 -0
  35. package/dist/umd/libs/visualizer/utils/render.d.ts.map +1 -0
  36. package/package.json +4 -4
@@ -1948,7 +1948,7 @@ class Logger {
1948
1948
  }
1949
1949
  }
1950
1950
  // Export singleton instance
1951
- const logger$4 = new Logger();
1951
+ const logger$3 = new Logger();
1952
1952
  // Export factory function để tạo logger với config riêng
1953
1953
  function createLogger(config = {}) {
1954
1954
  const instance = new Logger();
@@ -2337,7 +2337,7 @@ function findElementByHash(props) {
2337
2337
  }
2338
2338
  }
2339
2339
  catch (error) {
2340
- logger$4.warn(`Invalid selector "${selector}":`, error);
2340
+ logger$3.warn(`Invalid selector "${selector}":`, error);
2341
2341
  }
2342
2342
  const elementByHash = iframeDocument.querySelector(`[data-clarity-hashalpha="${hash}"], [data-clarity-hash="${hash}"], [data-clarity-hashbeta="${hash}"]`);
2343
2343
  return elementByHash;
@@ -2359,7 +2359,7 @@ function hydrateAreaNode(props) {
2359
2359
  const { id, hash, selector } = persistedData;
2360
2360
  const element = findElementByHash({ hash, selector, iframeDocument, vizRef });
2361
2361
  if (!element) {
2362
- logger$4.warn(`Cannot hydrate area ${id}: element not found for hash ${hash} or selector ${selector}`);
2362
+ logger$3.warn(`Cannot hydrate area ${id}: element not found for hash ${hash} or selector ${selector}`);
2363
2363
  return null;
2364
2364
  }
2365
2365
  const areaNode = buildAreaNode(element, hash, heatmapInfo, shadowRoot, persistedData);
@@ -2376,7 +2376,7 @@ function hydrateAreas(props) {
2376
2376
  hydratedAreas.push(area);
2377
2377
  }
2378
2378
  }
2379
- logger$4.info(`Hydrated ${hydratedAreas.length} of ${clickAreas.length} persisted areas`);
2379
+ logger$3.info(`Hydrated ${hydratedAreas.length} of ${clickAreas.length} persisted areas`);
2380
2380
  return hydratedAreas;
2381
2381
  }
2382
2382
  /**
@@ -3080,16 +3080,16 @@ const calcCalloutPositionAbsolute = (props) => {
3080
3080
 
3081
3081
  function validateAreaCreation(dataInfo, hash, areas) {
3082
3082
  if (!dataInfo?.clickMapMetrics || !dataInfo?.totalClicks) {
3083
- logger$4.warn('Cannot create area: missing heatmap data');
3083
+ logger$3.warn('Cannot create area: missing heatmap data');
3084
3084
  return false;
3085
3085
  }
3086
3086
  if (!hash) {
3087
- logger$4.warn('Cannot create area: missing hash');
3087
+ logger$3.warn('Cannot create area: missing hash');
3088
3088
  return false;
3089
3089
  }
3090
3090
  const alreadyExists = areas.some((area) => area.hash === hash);
3091
3091
  if (alreadyExists) {
3092
- logger$4.warn(`Area already exists for element: ${hash}`);
3092
+ logger$3.warn(`Area already exists for element: ${hash}`);
3093
3093
  return false;
3094
3094
  }
3095
3095
  return true;
@@ -3102,14 +3102,14 @@ function identifyConflictingAreas(area) {
3102
3102
  // Case 1: New area is a child of an existing area
3103
3103
  if (area.parentNode) {
3104
3104
  conflicts.parentId = area.parentNode.id;
3105
- logger$4.info(`New area "${area.selector}" is a child of existing area "${area.parentNode.selector}". Will remove parent.`);
3105
+ logger$3.info(`New area "${area.selector}" is a child of existing area "${area.parentNode.selector}". Will remove parent.`);
3106
3106
  }
3107
3107
  // Case 2: New area is a parent of existing area(s)
3108
3108
  if (area.childNodes.size > 0) {
3109
3109
  area.childNodes.forEach((childArea) => {
3110
3110
  conflicts.childrenIds.push(childArea.id);
3111
3111
  });
3112
- logger$4.info(`New area "${area.selector}" is a parent of ${area.childNodes.size} existing area(s). Will remove children.`);
3112
+ logger$3.info(`New area "${area.selector}" is a parent of ${area.childNodes.size} existing area(s). Will remove children.`);
3113
3113
  }
3114
3114
  return conflicts;
3115
3115
  }
@@ -3160,7 +3160,7 @@ function useAreaCreation(options = {}) {
3160
3160
  }
3161
3161
  }
3162
3162
  catch (error) {
3163
- logger$4.error('Failed to create area:', error);
3163
+ logger$3.error('Failed to create area:', error);
3164
3164
  }
3165
3165
  }, [dataInfo, areas, addArea, removeArea, removeClickArea, customShadowRoot, onAreaCreated]);
3166
3166
  return {
@@ -3275,16 +3275,16 @@ function useAreaHydration(options) {
3275
3275
  return;
3276
3276
  if (!dataInfo)
3277
3277
  return;
3278
- logger$4.info(`Hydrating ${clickAreas.length} persisted areas...`);
3278
+ logger$3.info(`Hydrating ${clickAreas.length} persisted areas...`);
3279
3279
  const hydratedAreas = hydrateAreas({ clickAreas, heatmapInfo: dataInfo, vizRef, shadowRoot });
3280
3280
  if (!hydratedAreas?.length) {
3281
- logger$4.warn('No areas could be hydrated - all elements may have been removed from DOM');
3281
+ logger$3.warn('No areas could be hydrated - all elements may have been removed from DOM');
3282
3282
  return;
3283
3283
  }
3284
3284
  setIsInitializing(true);
3285
3285
  buildAreaGraph(hydratedAreas);
3286
3286
  setAreas(hydratedAreas);
3287
- logger$4.info(`Successfully hydrated ${hydratedAreas.length} areas`);
3287
+ logger$3.info(`Successfully hydrated ${hydratedAreas.length} areas`);
3288
3288
  }, [dataInfo, vizRef, isInitializing, clickAreas]);
3289
3289
  useEffect(() => {
3290
3290
  if (!enabled)
@@ -3418,7 +3418,7 @@ function useAreaRectSync(options) {
3418
3418
  area.rect.update(newRect);
3419
3419
  }
3420
3420
  catch (error) {
3421
- logger$4.error(`Failed to update rect for area ${area.id}:`, error);
3421
+ logger$3.error(`Failed to update rect for area ${area.id}:`, error);
3422
3422
  }
3423
3423
  });
3424
3424
  buildAreaGraph(areas);
@@ -3567,7 +3567,7 @@ const useScrollmap = () => {
3567
3567
  }, { timeout: 300 });
3568
3568
  }
3569
3569
  catch (error) {
3570
- logger$4.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
3570
+ logger$3.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
3571
3571
  }
3572
3572
  }, [vizRef, scrollmap, isDomLoaded]);
3573
3573
  return { start };
@@ -4172,7 +4172,7 @@ function flush() {
4172
4172
  window.__gemxPerf = getReport();
4173
4173
  }
4174
4174
  // ── Global singleton export ───────────────────────────────────────────────────
4175
- const perf$2 = {
4175
+ const perf$3 = {
4176
4176
  startSession,
4177
4177
  endSession,
4178
4178
  mark: globalMark,
@@ -4220,7 +4220,7 @@ function createPerfTimer(config) {
4220
4220
  * DOM observation setup — ResizeObserver + MutationObserver.
4221
4221
  * Returns a cleanup function that disconnects both observers.
4222
4222
  */
4223
- const logger$3 = createLogger({ enabled: false, prefix: 'IframeHeightObserver' });
4223
+ const logger$2 = createLogger({ enabled: false, prefix: 'IframeHeightObserver' });
4224
4224
  function setup(doc, onChange) {
4225
4225
  const resizeObserver = new ResizeObserver(onChange);
4226
4226
  resizeObserver.observe(doc.documentElement);
@@ -4232,11 +4232,11 @@ function setup(doc, onChange) {
4232
4232
  attributes: true,
4233
4233
  attributeFilter: ['style', 'class', 'hidden', 'data-v'],
4234
4234
  });
4235
- logger$3.log('DOM observers started (ResizeObserver + MutationObserver)');
4235
+ logger$2.log('DOM observers started (ResizeObserver + MutationObserver)');
4236
4236
  return () => {
4237
4237
  resizeObserver.disconnect();
4238
4238
  mutationObserver.disconnect();
4239
- logger$3.log('DOM observers disconnected');
4239
+ logger$2.log('DOM observers disconnected');
4240
4240
  };
4241
4241
  }
4242
4242
 
@@ -4452,9 +4452,9 @@ function createNavigationListeners() {
4452
4452
  };
4453
4453
  }
4454
4454
 
4455
- const logger$2 = createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' });
4455
+ const logger$1 = createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' });
4456
4456
  function configure$1(debug) {
4457
- logger$2.configure({ enabled: debug });
4457
+ logger$1.configure({ enabled: debug });
4458
4458
  }
4459
4459
  // ─── DOM Utilities ────────────────────────────────────────────────────────────
4460
4460
  function disableAllLinks(doc) {
@@ -4478,10 +4478,10 @@ function setupLinkBlocker(doc, isEnabled, onBlocked) {
4478
4478
  return;
4479
4479
  const href = link.getAttribute('href');
4480
4480
  if (!href || href === '' || href === '#' || href.startsWith('#')) {
4481
- logger$2.log('Allowed hash navigation:', href);
4481
+ logger$1.log('Allowed hash navigation:', href);
4482
4482
  return;
4483
4483
  }
4484
- logger$2.log('Blocked link navigation to:', href);
4484
+ logger$1.log('Blocked link navigation to:', href);
4485
4485
  e.preventDefault();
4486
4486
  e.stopPropagation();
4487
4487
  e.stopImmediatePropagation();
@@ -4495,7 +4495,7 @@ function setupLinkBlocker(doc, isEnabled, onBlocked) {
4495
4495
  return;
4496
4496
  const href = link.getAttribute('href');
4497
4497
  if (href && !href.startsWith('#')) {
4498
- logger$2.log('Blocked auxclick navigation');
4498
+ logger$1.log('Blocked auxclick navigation');
4499
4499
  e.preventDefault();
4500
4500
  e.stopPropagation();
4501
4501
  e.stopImmediatePropagation();
@@ -4516,7 +4516,7 @@ function setupFormBlocker(doc, isEnabled, onBlocked, onFormSubmit) {
4516
4516
  const form = e.target;
4517
4517
  const action = form.getAttribute('action');
4518
4518
  if (!action || action === '' || action === '#') {
4519
- logger$2.log('Allowed same-page form');
4519
+ logger$1.log('Allowed same-page form');
4520
4520
  e.preventDefault();
4521
4521
  const data = {};
4522
4522
  new FormData(form).forEach((value, key) => {
@@ -4525,7 +4525,7 @@ function setupFormBlocker(doc, isEnabled, onBlocked, onFormSubmit) {
4525
4525
  onFormSubmit(form, data);
4526
4526
  return;
4527
4527
  }
4528
- logger$2.log('Blocked form submission to:', action);
4528
+ logger$1.log('Blocked form submission to:', action);
4529
4529
  e.preventDefault();
4530
4530
  e.stopPropagation();
4531
4531
  e.stopImmediatePropagation();
@@ -4539,7 +4539,7 @@ function setupWindowOpenBlocker(win, originalOpen, isEnabled, onBlocked) {
4539
4539
  if (!isEnabled())
4540
4540
  return originalOpen(...args);
4541
4541
  const url = args[0]?.toString() || 'popup';
4542
- logger$2.log('Blocked window.open:', url);
4542
+ logger$1.log('Blocked window.open:', url);
4543
4543
  onBlocked(url);
4544
4544
  return null;
4545
4545
  });
@@ -4551,14 +4551,14 @@ function setupUnloadBlocker(win, isEnabled) {
4551
4551
  const beforeUnloadListener = (e) => {
4552
4552
  if (!isEnabled())
4553
4553
  return;
4554
- logger$2.log('Blocked beforeunload');
4554
+ logger$1.log('Blocked beforeunload');
4555
4555
  e.preventDefault();
4556
4556
  e.returnValue = '';
4557
4557
  };
4558
4558
  const unloadListener = (e) => {
4559
4559
  if (!isEnabled())
4560
4560
  return;
4561
- logger$2.log('Blocked unload');
4561
+ logger$1.log('Blocked unload');
4562
4562
  e.preventDefault();
4563
4563
  e.stopPropagation();
4564
4564
  };
@@ -4602,7 +4602,7 @@ function start$4(s, iframe, cfg) {
4602
4602
  ];
4603
4603
  s.listeners.attach(cfg?.debug);
4604
4604
  s.running = true;
4605
- s.logger.log('Navigation blocker started');
4605
+ s.logger.log('Started');
4606
4606
  }
4607
4607
  function stop$4(s) {
4608
4608
  if (!s.running)
@@ -4613,7 +4613,7 @@ function stop$4(s) {
4613
4613
  s.isEnabled = false;
4614
4614
  s.showMessage = false;
4615
4615
  s.running = false;
4616
- s.logger.log('Navigation blocker stopped');
4616
+ s.logger.log('Stopped');
4617
4617
  }
4618
4618
  function enable(s) {
4619
4619
  if (!s.running) {
@@ -4674,8 +4674,8 @@ function createNavigationBlocker() {
4674
4674
  function attach(s, debug) {
4675
4675
  s.logger.configure({ enabled: !!debug });
4676
4676
  s.dimensionsListener = (e) => {
4677
- const ev = e;
4678
- s.logger.log('Dimensions applied:', ev.detail);
4677
+ // const ev = e as CustomEvent<IframeDimensionsDetail>;
4678
+ // s.logger.log('Dimensions applied:', ev.detail);
4679
4679
  };
4680
4680
  window.addEventListener('iframe-dimensions-applied', s.dimensionsListener);
4681
4681
  }
@@ -4807,7 +4807,7 @@ function start$3(s, d, w, cfg, options = {}) {
4807
4807
  s.config = cfg;
4808
4808
  s.running = true;
4809
4809
  s.logger.configure({ enabled: !!options.debug });
4810
- s.logger.log('Computed style enforcer started');
4810
+ s.logger.log('Started');
4811
4811
  }
4812
4812
  function stop$3(s) {
4813
4813
  if (!s.running)
@@ -4818,7 +4818,7 @@ function stop$3(s) {
4818
4818
  s.elementsWithViewportUnits.clear();
4819
4819
  s.originalValues = new WeakMap();
4820
4820
  s.running = false;
4821
- s.logger.log('Computed style enforcer stopped');
4821
+ s.logger.log('Stopped');
4822
4822
  }
4823
4823
  function reset(s) {
4824
4824
  if (!s.running) {
@@ -4827,7 +4827,7 @@ function reset(s) {
4827
4827
  }
4828
4828
  s.elementsWithViewportUnits.clear();
4829
4829
  s.originalValues = new WeakMap();
4830
- s.logger.log('Computed style enforcer reset');
4830
+ s.logger.log('Reset');
4831
4831
  }
4832
4832
  function trackElement(s, element, propertyOriginalValues) {
4833
4833
  if (!s.running) {
@@ -4945,254 +4945,6 @@ function getActiveFixes(ctx) {
4945
4945
  });
4946
4946
  }
4947
4947
 
4948
- /**
4949
- * Core viewport unit replacement logic.
4950
- * Converts vh/vw/svh/dvh/% to pixel values across all CSS in the iframe.
4951
- */
4952
- const logger$1 = createLogger({ enabled: false, prefix: 'ViewportUnitReplacer' });
4953
- // ─── Constants ────────────────────────────────────────────────────────────────
4954
- const HEIGHT_RELATED_PROPERTIES = ['height', 'min-height', 'max-height', 'top', 'bottom'];
4955
- /**
4956
- * Number of top-level CSS rules to process before yielding to the browser.
4957
- * Keeps the main thread responsive during large stylesheets (prevents tab kill on mobile).
4958
- */
4959
- const YIELD_EVERY_RULES = 100;
4960
- // ─── Scheduler ────────────────────────────────────────────────────────────────
4961
- /**
4962
- * Yield control back to the browser so it can handle input, paint frames, and
4963
- * avoid "page unresponsive" / tab-kill on mobile during heavy CSS processing.
4964
- *
4965
- * Uses `scheduler.yield()` (Chrome 115+) when available; falls back to a
4966
- * zero-timeout macrotask which is universally supported.
4967
- */
4968
- function yieldToMain$1() {
4969
- if (typeof globalThis !== 'undefined' && 'scheduler' in globalThis) {
4970
- return globalThis.scheduler.yield();
4971
- }
4972
- return new Promise((resolve) => setTimeout(resolve, 0));
4973
- }
4974
- // ─── Per-run tracking state (reset on each process() call) ───────────────────
4975
- let elementsWithViewportUnits = new Set();
4976
- let originalValues = new WeakMap();
4977
- // ─── Regex ────────────────────────────────────────────────────────────────────
4978
- /**
4979
- * Stateless test-only regex (no `g` flag) — safe to share across calls.
4980
- * Used exclusively for `.test()` checks before doing a full replacement.
4981
- */
4982
- const VIEWPORT_RE_TEST = /([-.?\d]+)(vh|svh|lvh|dvh|vw|svw|lvw|dvw)/i;
4983
- /** Fresh `g`-flagged instance for String.replace() callbacks. */
4984
- function createRegex() {
4985
- return /([-.?\d]+)(vh|svh|lvh|dvh|vw|svw|lvw|dvw)/gi;
4986
- }
4987
- // ─── Unit conversion ─────────────────────────────────────────────────────────
4988
- function px(value) {
4989
- return `${value.toFixed(2)}px`;
4990
- }
4991
- function buildUnitMap(ctx) {
4992
- return {
4993
- vh: ctx.targetHeight,
4994
- svh: ctx.targetHeight,
4995
- lvh: ctx.targetHeight,
4996
- dvh: ctx.targetHeight,
4997
- vw: ctx.targetWidth,
4998
- svw: ctx.targetWidth,
4999
- lvw: ctx.targetWidth,
5000
- dvw: ctx.targetWidth,
5001
- };
5002
- }
5003
- function toPx(value, unit, unitMap, targetHeight) {
5004
- const u = unit.toLowerCase();
5005
- if (u === '%')
5006
- return (value / 100) * targetHeight;
5007
- return (value / 100) * (unitMap[u] ?? 0);
5008
- }
5009
- function convert(value, unit, unitMap, targetHeight) {
5010
- const num = parseFloat(value);
5011
- return isNaN(num) ? value : px(toPx(num, unit, unitMap, targetHeight));
5012
- }
5013
- function isHeightRelated(prop) {
5014
- return HEIGHT_RELATED_PROPERTIES.includes(prop);
5015
- }
5016
- /**
5017
- * Use `matchOffset` (from replace() callback) instead of indexOf to get the
5018
- * exact position of the current match — avoids false matches for duplicate values.
5019
- */
5020
- function extractProperty(cssText, matchOffset) {
5021
- const before = cssText.substring(0, matchOffset);
5022
- const m = before.match(/([a-z-]+)\s*:\s*[^;{}]*$/i);
5023
- return m ? m[1].toLowerCase() : '';
5024
- }
5025
- function replaceInText(cssText, ctx) {
5026
- const unitMap = buildUnitMap(ctx);
5027
- const { targetHeight } = ctx;
5028
- return cssText.replace(createRegex(), (match, value, unit, offset) => {
5029
- if (unit === '%') {
5030
- return isHeightRelated(extractProperty(cssText, offset)) ? convert(value, unit, unitMap, targetHeight) : match;
5031
- }
5032
- return convert(value, unit, unitMap, targetHeight);
5033
- });
5034
- }
5035
- // ─── Element tracking ─────────────────────────────────────────────────────────
5036
- function trackSelector(selector, propOriginals, ctx) {
5037
- try {
5038
- ctx.doc.querySelectorAll(selector).forEach((el) => {
5039
- elementsWithViewportUnits.add(el);
5040
- let originals = originalValues.get(el);
5041
- if (!originals) {
5042
- originals = new Map();
5043
- originalValues.set(el, originals);
5044
- }
5045
- propOriginals.forEach((v, k) => {
5046
- if (!originals.has(k))
5047
- originals.set(k, v);
5048
- });
5049
- ctx.enforcer?.trackElement(el, propOriginals);
5050
- });
5051
- }
5052
- catch {
5053
- logger$1.warn('Invalid selector, skipping:', selector);
5054
- }
5055
- }
5056
- // ─── CSS processing ───────────────────────────────────────────────────────────
5057
- function processInlineStyles(ctx) {
5058
- let count = 0;
5059
- ctx.doc.querySelectorAll('[style]').forEach((el) => {
5060
- const style = el.getAttribute('style');
5061
- if (style && VIEWPORT_RE_TEST.test(style)) {
5062
- elementsWithViewportUnits.add(el);
5063
- el.setAttribute('style', replaceInText(style, ctx));
5064
- count++;
5065
- }
5066
- });
5067
- logger$1.log(`Replaced ${count} inline style elements`);
5068
- return count;
5069
- }
5070
- function processStyleTags(ctx) {
5071
- let count = 0;
5072
- ctx.doc.querySelectorAll('style').forEach((tag) => {
5073
- const css = tag.textContent || '';
5074
- if (VIEWPORT_RE_TEST.test(css)) {
5075
- tag.textContent = replaceInText(css, ctx);
5076
- count++;
5077
- }
5078
- });
5079
- logger$1.log(`Replaced ${count} <style> tags`);
5080
- return count;
5081
- }
5082
- function processRule(rule, ctx) {
5083
- let count = 0;
5084
- if ('style' in rule && rule.style) {
5085
- const cssRule = rule;
5086
- const style = cssRule.style;
5087
- let hasVp = false;
5088
- const propOriginals = new Map();
5089
- for (let i = 0; i < style.length; i++) {
5090
- const prop = style[i];
5091
- const value = style.getPropertyValue(prop);
5092
- if (value && VIEWPORT_RE_TEST.test(value)) {
5093
- hasVp = true;
5094
- propOriginals.set(prop, value);
5095
- style.setProperty(prop, replaceInText(value, ctx), style.getPropertyPriority(prop));
5096
- count++;
5097
- }
5098
- }
5099
- if (hasVp && cssRule.selectorText)
5100
- trackSelector(cssRule.selectorText, propOriginals, ctx);
5101
- }
5102
- if ('cssRules' in rule) {
5103
- const nested = rule.cssRules;
5104
- if (nested) {
5105
- for (let i = 0; i < nested.length; i++) {
5106
- count += processRule(nested[i], ctx);
5107
- }
5108
- }
5109
- }
5110
- return count;
5111
- }
5112
- /** Processes only inline <style> sheets. Linked sheets are handled by processLinkedStylesheets. */
5113
- async function processStylesheets(ctx) {
5114
- let total = 0;
5115
- let rulesSinceYield = 0;
5116
- const sheets = ctx.doc.styleSheets;
5117
- for (let i = 0; i < sheets.length; i++) {
5118
- const sheet = sheets[i];
5119
- if (sheet.href)
5120
- continue; // deferred to processLinkedStylesheets
5121
- try {
5122
- const rules = sheet.cssRules;
5123
- for (let j = 0; j < rules.length; j++) {
5124
- total += processRule(rules[j], ctx);
5125
- rulesSinceYield++;
5126
- if (rulesSinceYield >= YIELD_EVERY_RULES) {
5127
- rulesSinceYield = 0;
5128
- await yieldToMain$1();
5129
- }
5130
- }
5131
- }
5132
- catch (e) {
5133
- logger$1.warn('Cannot read stylesheet (CORS?):', e.message);
5134
- }
5135
- }
5136
- logger$1.log(`Replaced ${total} rules in inline stylesheets`);
5137
- return total;
5138
- }
5139
- async function processLinkedStylesheets(ctx) {
5140
- const links = ctx.doc.querySelectorAll('link[rel="stylesheet"]');
5141
- let count = 0;
5142
- for (let i = 0; i < links.length; i++) {
5143
- const link = links[i];
5144
- // Skip cross-origin — already in browser CSSOM, handled via processStylesheets
5145
- if (link.href && !link.href.startsWith(ctx.win.location.origin)) {
5146
- logger$1.log('Skipping cross-origin CSS:', link.href);
5147
- continue;
5148
- }
5149
- try {
5150
- const res = await fetch(link.href);
5151
- let css = await res.text();
5152
- if (VIEWPORT_RE_TEST.test(css)) {
5153
- css = replaceInText(css, ctx);
5154
- const style = ctx.doc.createElement('style');
5155
- style.textContent = css;
5156
- style.dataset.originalHref = link.href;
5157
- link.parentNode?.insertBefore(style, link);
5158
- link.remove();
5159
- count++;
5160
- }
5161
- }
5162
- catch (e) {
5163
- logger$1.warn('Cannot load CSS:', link.href, e);
5164
- }
5165
- }
5166
- logger$1.log(`Replaced ${count} linked CSS files`);
5167
- return count;
5168
- }
5169
- // ─── Public entry point ───────────────────────────────────────────────────────
5170
- async function process$1(ctx) {
5171
- logger$1.configure({ enabled: !!ctx.debug });
5172
- // Reset tracking state from any previous run
5173
- elementsWithViewportUnits = new Set();
5174
- originalValues = new WeakMap();
5175
- processInlineStyles(ctx);
5176
- processStyleTags(ctx);
5177
- await processStylesheets(ctx);
5178
- await processLinkedStylesheets(ctx);
5179
- // Wait for browser to apply the replaced styles
5180
- await new Promise((resolve) => requestAnimationFrame(resolve));
5181
- // Enforce final computed styles to inline with !important
5182
- const count = ctx.enforcer?.processAll({ debug: !!ctx.debug }) ?? 0;
5183
- logger$1.log(`Enforced ${count} computed styles for ${elementsWithViewportUnits.size} tracked elements`);
5184
- }
5185
-
5186
- /**
5187
- * Built-in global fix — always runs, no shouldApply condition.
5188
- * Registered first so it runs before any other global process hooks.
5189
- */
5190
- register$1({
5191
- name: 'viewport-unit-replacer',
5192
- description: 'Core: convert vh/vw/svh/dvh/% to px values across all iframe CSS',
5193
- process: process$1,
5194
- });
5195
-
5196
4948
  /**
5197
4949
  * GemPages v7 Slider Fix
5198
4950
  *
@@ -5408,52 +5160,52 @@ function configure(debug) {
5408
5160
  }
5409
5161
  async function run$1(ctx, activeGlobal, shopFix) {
5410
5162
  // ── Phase 1: beforeProcess ────────────────────────────────────────────────
5411
- const t1 = perf$2.mark('phase1.beforeProcess');
5163
+ const t1 = perf$3.mark('phase1.beforeProcess');
5412
5164
  for (const fix of activeGlobal) {
5413
5165
  if (fix.beforeProcess) {
5414
5166
  logger.log(`[beforeProcess] ${fix.name}`);
5415
- const t = perf$2.mark(`phase1.${fix.name}.beforeProcess`);
5167
+ const t = perf$3.mark(`phase1.${fix.name}.beforeProcess`);
5416
5168
  await fix.beforeProcess(ctx);
5417
- perf$2.measure(`phase1.${fix.name}.beforeProcess`, t);
5169
+ perf$3.measure(`phase1.${fix.name}.beforeProcess`, t);
5418
5170
  }
5419
5171
  }
5420
5172
  if (shopFix?.beforeProcess) {
5421
5173
  logger.log('[beforeProcess] shop');
5422
- const t = perf$2.mark('phase1.shop.beforeProcess');
5174
+ const t = perf$3.mark('phase1.shop.beforeProcess');
5423
5175
  await shopFix.beforeProcess(ctx);
5424
- perf$2.measure('phase1.shop.beforeProcess', t);
5176
+ perf$3.measure('phase1.shop.beforeProcess', t);
5425
5177
  }
5426
- perf$2.measure('phase1.beforeProcess', t1);
5178
+ perf$3.measure('phase1.beforeProcess', t1);
5427
5179
  // ── Phase 2: process ──────────────────────────────────────────────────────
5428
- const t2 = perf$2.mark('phase2.process');
5180
+ const t2 = perf$3.mark('phase2.process');
5429
5181
  for (const fix of activeGlobal) {
5430
5182
  if (fix.process) {
5431
5183
  logger.log(`[process] ${fix.name}`);
5432
- const t = perf$2.mark(`phase2.${fix.name}.process`);
5184
+ const t = perf$3.mark(`phase2.${fix.name}.process`);
5433
5185
  await fix.process(ctx);
5434
- perf$2.measure(`phase2.${fix.name}.process`, t);
5186
+ perf$3.measure(`phase2.${fix.name}.process`, t);
5435
5187
  }
5436
5188
  }
5437
- perf$2.measure('phase2.process', t2);
5189
+ perf$3.measure('phase2.process', t2);
5438
5190
  // ── Phase 3: afterProcess ─────────────────────────────────────────────────
5439
- const t3 = perf$2.mark('phase3.afterProcess');
5191
+ const t3 = perf$3.mark('phase3.afterProcess');
5440
5192
  if (shopFix?.afterProcess) {
5441
5193
  logger.log('[afterProcess] shop');
5442
- const t = perf$2.mark('phase3.shop.afterProcess');
5194
+ const t = perf$3.mark('phase3.shop.afterProcess');
5443
5195
  await shopFix.afterProcess(ctx);
5444
- perf$2.measure('phase3.shop.afterProcess', t);
5196
+ perf$3.measure('phase3.shop.afterProcess', t);
5445
5197
  }
5446
5198
  for (const fix of activeGlobal) {
5447
5199
  if (fix.afterProcess) {
5448
5200
  logger.log(`[afterProcess] ${fix.name}`);
5449
- const t = perf$2.mark(`phase3.${fix.name}.afterProcess`);
5201
+ const t = perf$3.mark(`phase3.${fix.name}.afterProcess`);
5450
5202
  await fix.afterProcess(ctx);
5451
- perf$2.measure(`phase3.${fix.name}.afterProcess`, t);
5203
+ perf$3.measure(`phase3.${fix.name}.afterProcess`, t);
5452
5204
  }
5453
5205
  }
5454
- perf$2.measure('phase3.afterProcess', t3);
5206
+ perf$3.measure('phase3.afterProcess', t3);
5455
5207
  // ── Phase 4: getDimensions ────────────────────────────────────────────────
5456
- const t4 = perf$2.mark('phase4.getDimensions');
5208
+ const t4 = perf$3.mark('phase4.getDimensions');
5457
5209
  return new Promise((resolve) => {
5458
5210
  requestAnimationFrame(() => {
5459
5211
  let dimensions = null;
@@ -5478,7 +5230,7 @@ async function run$1(ctx, activeGlobal, shopFix) {
5478
5230
  dimensions = { height: getFinalHeight(ctx.doc, ctx.win), width: getFinalWidth(ctx.doc) };
5479
5231
  }
5480
5232
  logger.log('Final dimensions:', dimensions);
5481
- perf$2.measure('phase4.getDimensions', t4);
5233
+ perf$3.measure('phase4.getDimensions', t4);
5482
5234
  resolve(dimensions);
5483
5235
  });
5484
5236
  });
@@ -5606,14 +5358,14 @@ async function run(s) {
5606
5358
  const activeGlobal = getActiveFixes(ctx);
5607
5359
  if (activeGlobal.length > 0)
5608
5360
  s.logger.log(`Active global fixes: ${activeGlobal.map((f) => f.name).join(', ')}`);
5609
- const tRun = perf$2.mark('viewport.run');
5361
+ const tRun = perf$3.mark('viewport.run');
5610
5362
  try {
5611
5363
  const result = await run$1(ctx, activeGlobal, s.shopFix);
5612
- perf$2.measure('viewport.run', tRun);
5364
+ perf$3.measure('viewport.run', tRun);
5613
5365
  return result;
5614
5366
  }
5615
5367
  catch (err) {
5616
- perf$2.measure('viewport.run', tRun);
5368
+ perf$3.measure('viewport.run', tRun);
5617
5369
  s.logger.error('Critical error:', err);
5618
5370
  return { height: s.doc.body?.scrollHeight || 1000, width: s.doc.body?.scrollWidth || 1000 };
5619
5371
  }
@@ -5663,22 +5415,23 @@ async function process(s) {
5663
5415
  return;
5664
5416
  }
5665
5417
  const sessionId = `render-${Date.now()}`;
5666
- perf$2.startSession(sessionId);
5667
- const t0 = perf$2.mark('orchestrator.process');
5418
+ perf$3.startSession(sessionId);
5419
+ const t0 = perf$3.mark('orchestrator.process');
5668
5420
  try {
5669
- s.logger.log('Processing viewport units...');
5421
+ s.logger.groupCollapsed('Processing...');
5670
5422
  s.viewportReplacer.start(s.iframe, s.config);
5671
5423
  s.navigationBlocker.start(s.iframe, { debug: s.config.debug });
5672
5424
  const result = await s.viewportReplacer.run();
5673
- perf$2.measure('orchestrator.process', t0);
5674
- perf$2.endSession();
5425
+ perf$3.measure('orchestrator.process', t0);
5426
+ perf$3.endSession();
5427
+ s.logger.groupEnd();
5675
5428
  s.logger.log('Process completed:', result);
5676
5429
  s.config.onSuccess?.(result);
5677
5430
  dispatchDimensionsEvent(result);
5678
5431
  }
5679
5432
  catch (error) {
5680
- perf$2.measure('orchestrator.process', t0);
5681
- perf$2.endSession();
5433
+ perf$3.measure('orchestrator.process', t0);
5434
+ perf$3.endSession();
5682
5435
  s.logger.error('Failed to process:', error);
5683
5436
  s.config.onError?.(error);
5684
5437
  }
@@ -5706,7 +5459,7 @@ function start$1(s, cfg) {
5706
5459
  s.config = cfg;
5707
5460
  s.running = true;
5708
5461
  s.logger.configure({ enabled: !!cfg.debug });
5709
- s.logger.log('Iframe fixer started');
5462
+ s.logger.log('Started');
5710
5463
  initialize(s);
5711
5464
  }
5712
5465
  function stop$1(s) {
@@ -5722,7 +5475,7 @@ function stop$1(s) {
5722
5475
  s.iframe = null;
5723
5476
  s.config = null;
5724
5477
  s.running = false;
5725
- s.logger.log('Iframe fixer stopped');
5478
+ s.logger.log('Stopped');
5726
5479
  }
5727
5480
  async function recalculate$1(s) {
5728
5481
  if (!s.running) {
@@ -5936,7 +5689,7 @@ function getHtmlCacheConfig() {
5936
5689
  return _config;
5937
5690
  }
5938
5691
  /** Build a full cache key that includes the cache version to handle invalidation. */
5939
- function buildCacheKey(baseKey, shortCircuitStrategy) {
5692
+ function buildCacheKey(baseKey, shortCircuitStrategy = 0) {
5940
5693
  return `v${_config.cacheVersion}:${baseKey}:${shortCircuitStrategy}`;
5941
5694
  }
5942
5695
 
@@ -6436,7 +6189,7 @@ function filterShadowNodes(data, subtreeIds, shadowHostTags) {
6436
6189
  return (data?.filter((node) => {
6437
6190
  if (isShadowDomNode(node.tag))
6438
6191
  return true;
6439
- if (shadowHostTags.has(node.tag ?? '') && subtreeIds.has(node.parent))
6192
+ if (shadowHostTags.has(node.tag ?? ''))
6440
6193
  return true;
6441
6194
  return subtreeIds.has(node.id);
6442
6195
  }) ?? []);
@@ -6471,6 +6224,35 @@ function extractSpecialEvents(events, dom, shadowHostTags) {
6471
6224
  return result;
6472
6225
  }
6473
6226
 
6227
+ // utils/retry.ts
6228
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6229
+ /**
6230
+ * Retry until the callback returns truthy, or throw if the limit is exceeded.
6231
+ *
6232
+ * @example
6233
+ * await retry(() => doc.readyState === 'complete', { timeout: 5000, label: 'DOM ready' });
6234
+ *
6235
+ * @example
6236
+ * const el = await retry(() => document.getElementById('app'), { maxRetries: 20 });
6237
+ */
6238
+ async function retry(callback, options = {}) {
6239
+ const { interval = 100, label = 'Retry' } = options;
6240
+ const maxRetries = options.timeout != null ? Math.ceil(options.timeout / interval) : (options.maxRetries ?? 50);
6241
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
6242
+ console.log(`🔄 [${label}] attempt ${attempt}/${maxRetries}`);
6243
+ const result = await callback();
6244
+ if (result) {
6245
+ return result;
6246
+ }
6247
+ if (attempt < maxRetries) {
6248
+ await delay(interval);
6249
+ }
6250
+ }
6251
+ const totalMs = maxRetries * interval;
6252
+ throw new Error(`[${label}] Timed out after ${maxRetries} retries (${totalMs}ms)`);
6253
+ }
6254
+
6255
+ const perf$2 = createPerfTimer('Render');
6474
6256
  const YIELD_INTERVAL_MS = 16;
6475
6257
  async function yieldToMain() {
6476
6258
  if ('scheduler' in globalThis && typeof globalThis.scheduler?.yield === 'function') {
@@ -6480,6 +6262,43 @@ async function yieldToMain() {
6480
6262
  await new Promise((resolve) => setTimeout(resolve, 0));
6481
6263
  }
6482
6264
  }
6265
+ const renderLoop = async (ctx, options) => {
6266
+ const { events, useproxy } = options;
6267
+ const shortCircuitStrategy = options.shortCircuitStrategy ?? ShortCircuitStrategy.None;
6268
+ const hash = options.hash ?? null;
6269
+ const t0 = perf$2.mark('RenderLoop start');
6270
+ let lastYield = performance.now();
6271
+ const totalEvents = events.length;
6272
+ for (let i = 0; i < totalEvents; i++) {
6273
+ const entry = events[i];
6274
+ const entryEvent = entry.event;
6275
+ const now = performance.now();
6276
+ if (now - lastYield > YIELD_INTERVAL_MS) {
6277
+ console.log(`[RenderLoop] ${i}/${totalEvents} time:`, now - lastYield);
6278
+ await yieldToMain();
6279
+ lastYield = performance.now();
6280
+ }
6281
+ switch (entryEvent) {
6282
+ case Event.StyleSheetAdoption:
6283
+ case Event.StyleSheetUpdate:
6284
+ ctx.layout.styleChange(entry);
6285
+ break;
6286
+ case Event.CustomElement:
6287
+ ctx.layout.customElement(entry);
6288
+ break;
6289
+ case Event.Mutation: {
6290
+ const domEvent = entry;
6291
+ ctx.renderTime = domEvent.time;
6292
+ if (ctx.shortCircuitRendering(shortCircuitStrategy, domEvent, hash))
6293
+ return;
6294
+ ctx.layout.markup(domEvent, useproxy);
6295
+ break;
6296
+ }
6297
+ }
6298
+ }
6299
+ perf$2.measure('RenderLoop', t0);
6300
+ };
6301
+
6483
6302
  class GXVisualizer extends Visualizer {
6484
6303
  attentionMap;
6485
6304
  originalClearmap;
@@ -6491,104 +6310,93 @@ class GXVisualizer extends Visualizer {
6491
6310
  this.originalClearmap = this.clearmap;
6492
6311
  this.clearmap = this.clearmapOverride;
6493
6312
  this.setup = this.setupOverride;
6494
- this.html = this.htmlOverride;
6495
6313
  }
6496
- setupOverride = async (target, options) => {
6497
- this.attentionMap?.clear();
6498
- await this.originalSetup(target, options);
6499
- this.attentionMap = new AttentionMapRenderer(this.state);
6500
- return this;
6501
- };
6502
- clearmapOverride = () => {
6503
- this.originalClearmap();
6504
- this.attentionMap?.clear();
6505
- };
6506
- renderLoop = async (v, events, hash, useproxy, shortCircuitStrategy) => {
6507
- let lastYield = performance.now();
6508
- for (let i = 0; i < events.length; i++) {
6509
- const now = performance.now();
6510
- if (now - lastYield > YIELD_INTERVAL_MS) {
6511
- await yieldToMain();
6512
- lastYield = performance.now();
6513
- }
6514
- const entry = events[i];
6515
- const entryEvent = entry.event;
6516
- switch (entryEvent) {
6517
- case Event.StyleSheetAdoption:
6518
- case Event.StyleSheetUpdate:
6519
- v.layout.styleChange(entry);
6520
- break;
6521
- case Event.CustomElement:
6522
- v.layout.customElement(entry);
6523
- break;
6524
- case Event.Mutation: {
6525
- const domEvent = entry;
6526
- this.renderTime = domEvent.time;
6527
- if (v.shortCircuitRendering(shortCircuitStrategy, domEvent, hash))
6528
- return;
6529
- v.layout.markup(domEvent, useproxy);
6530
- break;
6531
- }
6532
- }
6314
+ htmlRender = async (props) => {
6315
+ const { decoded, target, portalCanvasId, useproxy, logerror } = props;
6316
+ if (!decoded || decoded.length === 0 || !target)
6317
+ return this;
6318
+ try {
6319
+ const merged = this.mergeForHtml(decoded);
6320
+ await this.setup(target, { version: decoded[0].envelope.version, dom: merged.dom, useproxy, portalCanvasId });
6321
+ await this.renderLoop(this, { events: merged.events, target, useproxy });
6533
6322
  }
6534
- };
6535
- htmlOverride = async (decoded, target, portalCanvasId, hash = '', useproxy, logerror, shortCircuitStrategy = ShortCircuitStrategy.None) => {
6536
- if (decoded && decoded.length > 0 && target) {
6537
- try {
6538
- const v = this;
6539
- const merged = v.mergeForHtml(decoded);
6540
- await this.setup(target, { version: decoded[0].envelope.version, dom: merged.dom, useproxy, portalCanvasId });
6541
- await this.renderLoop(v, merged.events, hash, useproxy, shortCircuitStrategy);
6542
- }
6543
- catch (e) {
6544
- if (logerror)
6545
- logerror(e);
6546
- }
6323
+ catch (e) {
6324
+ if (logerror)
6325
+ logerror(e);
6547
6326
  }
6548
6327
  return this;
6549
6328
  };
6550
- htmlCached = async (cacheKey, decoded, target, portalCanvasId, hash = '', useproxy, logerror, shortCircuitStrategy = 0) => {
6329
+ htmlCached = async (cacheKey, options) => {
6330
+ const { decoded, target } = options;
6551
6331
  if (!decoded || decoded.length === 0 || !target)
6552
6332
  return this;
6553
- const fullKey = buildCacheKey(cacheKey, 0);
6333
+ const fullKey = buildCacheKey(cacheKey, options.shortCircuitStrategy);
6554
6334
  const cached = await htmlCache.get(fullKey);
6555
6335
  if (cached) {
6556
6336
  try {
6557
- const v = this;
6558
- await this.setup(target, { version: cached.version, useproxy, portalCanvasId });
6559
- target.document.open();
6560
- target.document.write(cached.html);
6561
- target.document.close();
6562
- v.layout.hydrate(target.document);
6563
- // Replay shadow DOM from initial Discover event (was processed by setup() in full render)
6564
- if (cached.specialDom) {
6565
- v.layout.markup(cached.specialDom, useproxy);
6566
- }
6567
- // Replay shadow DOM mutations + StyleSheet + CustomElement
6568
- await this.renderLoop(v, cached.specialEvents, '', useproxy, 0);
6337
+ await this.buildHtmlByCached(cached, options);
6569
6338
  return this;
6570
6339
  }
6571
6340
  catch (e) {
6572
- if (logerror)
6573
- logerror(e);
6341
+ options?.logerror?.(e);
6574
6342
  }
6575
6343
  }
6344
+ await this.buildHtmlForCache(fullKey, options);
6345
+ return this;
6346
+ };
6347
+ /**
6348
+ * Render attention/engagement map.
6349
+ * @param attentionData - Array of attention data points with start/end element hashes and time spent
6350
+ * @param avgFold - Average fold pixel position (used to scale top portion)
6351
+ * @param isSecondary - Whether to use secondary comparison iframe IDs
6352
+ */
6353
+ attention = (attentionData, avgFold, isSecondary = false) => {
6354
+ this.clearmapOverride();
6355
+ this.attentionMap.attention(attentionData, avgFold, this, isSecondary);
6356
+ };
6357
+ buildHtmlByCached = async (cached, options) => {
6358
+ const { target, useproxy, portalCanvasId } = options;
6359
+ if (!cached || !target)
6360
+ throw new Error('Failed to render HTML cached');
6361
+ const doc = target.document;
6362
+ target.window;
6576
6363
  try {
6577
- const v = this;
6578
- const merged = v.mergeForHtml(decoded);
6364
+ await this.setup(target, { version: cached.version, useproxy, portalCanvasId });
6365
+ doc.open();
6366
+ doc.write(cached.html);
6367
+ doc.close();
6368
+ const process = async () => {
6369
+ this.layout.hydrate(doc);
6370
+ // Replay shadow DOM from initial Discover event
6371
+ if (cached.specialDom) {
6372
+ this.layout.markup(cached.specialDom, useproxy);
6373
+ }
6374
+ // Replay shadow DOM mutations + StyleSheet + CustomElement
6375
+ await this.renderLoop(this, { ...options, events: cached.specialEvents });
6376
+ };
6377
+ await retry(() => doc.readyState === 'complete', { timeout: 30000, label: 'DOM ready' });
6378
+ await process();
6379
+ return this;
6380
+ }
6381
+ catch (e) {
6382
+ throw new Error('Failed to render HTML cached', { cause: e });
6383
+ }
6384
+ };
6385
+ buildHtmlForCache = async (cacheKey, options) => {
6386
+ const { decoded, target, useproxy, portalCanvasId, logerror } = options;
6387
+ if (!decoded || decoded.length === 0 || !target)
6388
+ return this;
6389
+ try {
6390
+ const merged = this.mergeForHtml(decoded);
6579
6391
  await this.setup(target, { version: decoded[0].envelope.version, dom: merged.dom, useproxy, portalCanvasId });
6580
- await this.renderLoop(v, merged.events, hash, useproxy, shortCircuitStrategy);
6392
+ await this.renderLoop(this, { events: merged.events, target, useproxy });
6393
+ const timestamp = Date.now();
6394
+ const version = decoded[0].envelope.version;
6395
+ const html = target.document.documentElement.outerHTML;
6581
6396
  const shadowHostTags = collectShadowHostTags(target.document);
6582
6397
  const specialDom = extractSpecialDom(merged.dom, merged.events, shadowHostTags);
6583
6398
  const specialEvents = extractSpecialEvents(merged.events, merged.dom, shadowHostTags);
6584
- void htmlCache.set({
6585
- key: fullKey,
6586
- html: target.document.documentElement.outerHTML,
6587
- specialDom: specialDom,
6588
- specialEvents,
6589
- version: decoded[0].envelope.version,
6590
- timestamp: Date.now(),
6591
- });
6399
+ void htmlCache.set({ key: cacheKey, html, specialDom, specialEvents, version, timestamp });
6592
6400
  }
6593
6401
  catch (e) {
6594
6402
  if (logerror)
@@ -6596,15 +6404,18 @@ class GXVisualizer extends Visualizer {
6596
6404
  }
6597
6405
  return this;
6598
6406
  };
6599
- /**
6600
- * Render attention/engagement map.
6601
- * @param attentionData - Array of attention data points with start/end element hashes and time spent
6602
- * @param avgFold - Average fold pixel position (used to scale top portion)
6603
- * @param isSecondary - Whether to use secondary comparison iframe IDs
6604
- */
6605
- attention = (attentionData, avgFold, isSecondary = false) => {
6606
- this.clearmapOverride();
6607
- this.attentionMap.attention(attentionData, avgFold, this, isSecondary);
6407
+ renderLoop = async (ctx, options) => {
6408
+ await renderLoop(ctx, options);
6409
+ };
6410
+ setupOverride = async (target, options) => {
6411
+ this.attentionMap?.clear();
6412
+ await this.originalSetup(target, options);
6413
+ this.attentionMap = new AttentionMapRenderer(this.state);
6414
+ return this;
6415
+ };
6416
+ clearmapOverride = () => {
6417
+ this.originalClearmap();
6418
+ this.attentionMap?.clear();
6608
6419
  };
6609
6420
  }
6610
6421
 
@@ -6701,12 +6512,6 @@ function startIframe({ helperRef, iframe, shopId, deviceType = EDeviceType.Deskt
6701
6512
  });
6702
6513
  }
6703
6514
 
6704
- const EShortCircuitStrategy = {
6705
- None: 0,
6706
- HashFirstTimestamp: 1,
6707
- HashFirstTimestampPlusBuffer: 2,
6708
- HashBeforeDeleted: 3,
6709
- };
6710
6515
  const perf = createPerfTimer('Render');
6711
6516
  const useHeatmapRenderDom = () => {
6712
6517
  const viewId = useViewIdContext();
@@ -6739,19 +6544,23 @@ const useHeatmapRenderDom = () => {
6739
6544
  const abort = new AbortController();
6740
6545
  abortRef.current = abort;
6741
6546
  resetIframeSetup();
6742
- const t0 = perf.mark('renderHeatmap start');
6547
+ const t0 = perf.mark('RenderHeatmap start');
6743
6548
  const visualizer = vizRef ?? new GXVisualizer();
6744
6549
  if (!vizRef)
6745
6550
  setVizRef(visualizer);
6746
6551
  visualizer.configure({ excludeClassNames });
6747
6552
  setIsDomLoaded(false);
6748
6553
  // Phase 1: render DOM — does not depend on contentWidth/wrapperHeight
6749
- const hash = elementToShowRef.current ?? undefined;
6750
6554
  const cacheKey = dataHashRef.current;
6751
- const strategy = hash ? EShortCircuitStrategy.HashFirstTimestampPlusBuffer : EShortCircuitStrategy.None;
6752
- await perf.wrap('visualizer.html', () => cacheKey
6753
- ? visualizer.htmlCached(cacheKey, payloads, contentWindow, viewId, hash, undefined, undefined, strategy)
6754
- : visualizer.html(payloads, contentWindow, viewId, hash, undefined, undefined, strategy));
6555
+ const options = {
6556
+ decoded: payloads,
6557
+ target: contentWindow,
6558
+ portalCanvasId: viewId,
6559
+ logerror: (error) => {
6560
+ console.error('Error rendering HTML', error);
6561
+ },
6562
+ };
6563
+ await perf.wrap('RenderHtml', () => cacheKey ? visualizer.htmlCached(cacheKey, options) : visualizer.htmlRender(options));
6755
6564
  if (abort.signal.aborted)
6756
6565
  return;
6757
6566
  // Phase 2: iframe setup — deferred to useIframeSetup (handles dims dependency)
@@ -7351,10 +7160,10 @@ const useScrollmapZones = (options) => {
7351
7160
  const newZones = createZones(scrollmap);
7352
7161
  setZones(newZones);
7353
7162
  setIsReady(true);
7354
- logger$4.log(`[useScrollmap] Created ${newZones.length} zones in ${mode} mode`);
7163
+ logger$3.log(`[useScrollmap] Created ${newZones.length} zones in ${mode} mode`);
7355
7164
  }
7356
7165
  catch (error) {
7357
- logger$4.error('[useScrollmap] Error:', error);
7166
+ logger$3.error('[useScrollmap] Error:', error);
7358
7167
  setIsReady(false);
7359
7168
  }
7360
7169
  }, [enabled, scrollmap, mode, createZones]);