@gemx-dev/heatmap-react 3.5.92-dev.1 → 3.5.92-dev.11

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 (172) hide show
  1. package/dist/esm/components/Layout/HeatmapLayout.d.ts +1 -0
  2. package/dist/esm/components/Layout/HeatmapLayout.d.ts.map +1 -1
  3. package/dist/esm/components/VizLive/VizLiveHeatmap.d.ts.map +1 -1
  4. package/dist/esm/hooks/common/useHeatmapWidthByDevice.d.ts.map +1 -1
  5. package/dist/esm/hooks/register/useRegisterConfig.d.ts +2 -1
  6. package/dist/esm/hooks/register/useRegisterConfig.d.ts.map +1 -1
  7. package/dist/esm/hooks/view-context/index.d.ts +1 -0
  8. package/dist/esm/hooks/view-context/index.d.ts.map +1 -1
  9. package/dist/esm/hooks/view-context/useHeatmapCopyView.d.ts.map +1 -1
  10. package/dist/esm/hooks/view-context/useHeatmapDataContext.d.ts +4 -16
  11. package/dist/esm/hooks/view-context/useHeatmapDataContext.d.ts.map +1 -1
  12. package/dist/esm/hooks/view-context/useHeatmapLiveContext.d.ts +34 -0
  13. package/dist/esm/hooks/view-context/useHeatmapLiveContext.d.ts.map +1 -0
  14. package/dist/esm/hooks/view-context/useHeatmapSettingContext.d.ts +3 -1
  15. package/dist/esm/hooks/view-context/useHeatmapSettingContext.d.ts.map +1 -1
  16. package/dist/esm/hooks/view-context/useHeatmapVizContext.d.ts +2 -2
  17. package/dist/esm/hooks/view-context/useHeatmapVizContext.d.ts.map +1 -1
  18. package/dist/esm/hooks/viz-live/useVizLiveIframeMsg.d.ts.map +1 -1
  19. package/dist/esm/hooks/viz-live/useVizLiveRender.d.ts.map +1 -1
  20. package/dist/esm/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
  21. package/dist/esm/index.d.ts +1 -1
  22. package/dist/esm/index.d.ts.map +1 -1
  23. package/dist/esm/index.js +1221 -616
  24. package/dist/esm/index.mjs +1221 -616
  25. package/dist/esm/libs/iframe-processor/index.d.ts +2 -5
  26. package/dist/esm/libs/iframe-processor/index.d.ts.map +1 -1
  27. package/dist/esm/libs/iframe-processor/lifecycle.d.ts +15 -7
  28. package/dist/esm/libs/iframe-processor/lifecycle.d.ts.map +1 -1
  29. package/dist/esm/libs/iframe-processor/orchestrator.d.ts +13 -45
  30. package/dist/esm/libs/iframe-processor/orchestrator.d.ts.map +1 -1
  31. package/dist/esm/libs/iframe-processor/processors/height-observer/index.d.ts +3 -14
  32. package/dist/esm/libs/iframe-processor/processors/height-observer/index.d.ts.map +1 -1
  33. package/dist/esm/libs/iframe-processor/processors/height-observer/types.d.ts +9 -1
  34. package/dist/esm/libs/iframe-processor/processors/height-observer/types.d.ts.map +1 -1
  35. package/dist/esm/libs/iframe-processor/processors/navigation/index.d.ts +3 -14
  36. package/dist/esm/libs/iframe-processor/processors/navigation/index.d.ts.map +1 -1
  37. package/dist/esm/libs/iframe-processor/processors/navigation/listeners.d.ts +3 -4
  38. package/dist/esm/libs/iframe-processor/processors/navigation/listeners.d.ts.map +1 -1
  39. package/dist/esm/libs/iframe-processor/processors/navigation/types.d.ts +13 -0
  40. package/dist/esm/libs/iframe-processor/processors/navigation/types.d.ts.map +1 -1
  41. package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/global-fixes/viewport-unit-replacer/fixes.d.ts.map +1 -1
  42. package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/gp-v7-fixes/gem-slider-item/fixes.d.ts.map +1 -1
  43. package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/types.d.ts +2 -2
  44. package/dist/esm/libs/iframe-processor/processors/viewport/index.d.ts +3 -9
  45. package/dist/esm/libs/iframe-processor/processors/viewport/index.d.ts.map +1 -1
  46. package/dist/esm/libs/iframe-processor/processors/viewport/listeners.d.ts +3 -2
  47. package/dist/esm/libs/iframe-processor/processors/viewport/listeners.d.ts.map +1 -1
  48. package/dist/esm/libs/iframe-processor/processors/viewport/pipeline.d.ts.map +1 -1
  49. package/dist/esm/libs/iframe-processor/processors/viewport/shop-overrides/types.d.ts +2 -0
  50. package/dist/esm/libs/iframe-processor/processors/viewport/shop-overrides/types.d.ts.map +1 -1
  51. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/constants.d.ts +5 -0
  52. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/constants.d.ts.map +1 -0
  53. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/functions.d.ts +11 -0
  54. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/functions.d.ts.map +1 -0
  55. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/index.d.ts +9 -0
  56. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/index.d.ts.map +1 -0
  57. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/state.d.ts +13 -0
  58. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/state.d.ts.map +1 -0
  59. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/types.d.ts +16 -0
  60. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/types.d.ts.map +1 -0
  61. package/dist/esm/libs/iframe-processor/processors/viewport/types.d.ts +8 -0
  62. package/dist/esm/libs/iframe-processor/processors/viewport/types.d.ts.map +1 -0
  63. package/dist/esm/libs/iframe-processor/shared/factory-types.d.ts +24 -0
  64. package/dist/esm/libs/iframe-processor/shared/factory-types.d.ts.map +1 -0
  65. package/dist/esm/libs/iframe-processor/shared/iframe-types.d.ts +20 -0
  66. package/dist/esm/libs/iframe-processor/shared/iframe-types.d.ts.map +1 -0
  67. package/dist/esm/libs/index.d.ts +1 -0
  68. package/dist/esm/libs/index.d.ts.map +1 -1
  69. package/dist/esm/libs/perf.d.ts +83 -0
  70. package/dist/esm/libs/perf.d.ts.map +1 -0
  71. package/dist/esm/stores/config.d.ts +2 -2
  72. package/dist/esm/stores/config.d.ts.map +1 -1
  73. package/dist/esm/stores/data.d.ts +4 -0
  74. package/dist/esm/stores/data.d.ts.map +1 -1
  75. package/dist/esm/stores/mode-live.d.ts +30 -16
  76. package/dist/esm/stores/mode-live.d.ts.map +1 -1
  77. package/dist/esm/stores/setting.d.ts +3 -1
  78. package/dist/esm/stores/setting.d.ts.map +1 -1
  79. package/dist/esm/stores/viz.d.ts +2 -2
  80. package/dist/esm/stores/viz.d.ts.map +1 -1
  81. package/dist/esm/types/heatmap.d.ts +5 -0
  82. package/dist/esm/types/heatmap.d.ts.map +1 -1
  83. package/dist/esm/types/iframe-helper.d.ts +0 -19
  84. package/dist/esm/types/iframe-helper.d.ts.map +1 -1
  85. package/dist/umd/components/Layout/HeatmapLayout.d.ts +1 -0
  86. package/dist/umd/components/Layout/HeatmapLayout.d.ts.map +1 -1
  87. package/dist/umd/components/VizLive/VizLiveHeatmap.d.ts.map +1 -1
  88. package/dist/umd/hooks/common/useHeatmapWidthByDevice.d.ts.map +1 -1
  89. package/dist/umd/hooks/register/useRegisterConfig.d.ts +2 -1
  90. package/dist/umd/hooks/register/useRegisterConfig.d.ts.map +1 -1
  91. package/dist/umd/hooks/view-context/index.d.ts +1 -0
  92. package/dist/umd/hooks/view-context/index.d.ts.map +1 -1
  93. package/dist/umd/hooks/view-context/useHeatmapCopyView.d.ts.map +1 -1
  94. package/dist/umd/hooks/view-context/useHeatmapDataContext.d.ts +4 -16
  95. package/dist/umd/hooks/view-context/useHeatmapDataContext.d.ts.map +1 -1
  96. package/dist/umd/hooks/view-context/useHeatmapLiveContext.d.ts +34 -0
  97. package/dist/umd/hooks/view-context/useHeatmapLiveContext.d.ts.map +1 -0
  98. package/dist/umd/hooks/view-context/useHeatmapSettingContext.d.ts +3 -1
  99. package/dist/umd/hooks/view-context/useHeatmapSettingContext.d.ts.map +1 -1
  100. package/dist/umd/hooks/view-context/useHeatmapVizContext.d.ts +2 -2
  101. package/dist/umd/hooks/view-context/useHeatmapVizContext.d.ts.map +1 -1
  102. package/dist/umd/hooks/viz-live/useVizLiveIframeMsg.d.ts.map +1 -1
  103. package/dist/umd/hooks/viz-live/useVizLiveRender.d.ts.map +1 -1
  104. package/dist/umd/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
  105. package/dist/umd/index.d.ts +1 -1
  106. package/dist/umd/index.d.ts.map +1 -1
  107. package/dist/umd/index.js +2 -2
  108. package/dist/umd/libs/iframe-processor/index.d.ts +2 -5
  109. package/dist/umd/libs/iframe-processor/index.d.ts.map +1 -1
  110. package/dist/umd/libs/iframe-processor/lifecycle.d.ts +15 -7
  111. package/dist/umd/libs/iframe-processor/lifecycle.d.ts.map +1 -1
  112. package/dist/umd/libs/iframe-processor/orchestrator.d.ts +13 -45
  113. package/dist/umd/libs/iframe-processor/orchestrator.d.ts.map +1 -1
  114. package/dist/umd/libs/iframe-processor/processors/height-observer/index.d.ts +3 -14
  115. package/dist/umd/libs/iframe-processor/processors/height-observer/index.d.ts.map +1 -1
  116. package/dist/umd/libs/iframe-processor/processors/height-observer/types.d.ts +9 -1
  117. package/dist/umd/libs/iframe-processor/processors/height-observer/types.d.ts.map +1 -1
  118. package/dist/umd/libs/iframe-processor/processors/navigation/index.d.ts +3 -14
  119. package/dist/umd/libs/iframe-processor/processors/navigation/index.d.ts.map +1 -1
  120. package/dist/umd/libs/iframe-processor/processors/navigation/listeners.d.ts +3 -4
  121. package/dist/umd/libs/iframe-processor/processors/navigation/listeners.d.ts.map +1 -1
  122. package/dist/umd/libs/iframe-processor/processors/navigation/types.d.ts +13 -0
  123. package/dist/umd/libs/iframe-processor/processors/navigation/types.d.ts.map +1 -1
  124. package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/global-fixes/viewport-unit-replacer/fixes.d.ts.map +1 -1
  125. package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/gp-v7-fixes/gem-slider-item/fixes.d.ts.map +1 -1
  126. package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/types.d.ts +2 -2
  127. package/dist/umd/libs/iframe-processor/processors/viewport/index.d.ts +3 -9
  128. package/dist/umd/libs/iframe-processor/processors/viewport/index.d.ts.map +1 -1
  129. package/dist/umd/libs/iframe-processor/processors/viewport/listeners.d.ts +3 -2
  130. package/dist/umd/libs/iframe-processor/processors/viewport/listeners.d.ts.map +1 -1
  131. package/dist/umd/libs/iframe-processor/processors/viewport/pipeline.d.ts.map +1 -1
  132. package/dist/umd/libs/iframe-processor/processors/viewport/shop-overrides/types.d.ts +2 -0
  133. package/dist/umd/libs/iframe-processor/processors/viewport/shop-overrides/types.d.ts.map +1 -1
  134. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/constants.d.ts +5 -0
  135. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/constants.d.ts.map +1 -0
  136. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/functions.d.ts +11 -0
  137. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/functions.d.ts.map +1 -0
  138. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/index.d.ts +9 -0
  139. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/index.d.ts.map +1 -0
  140. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/state.d.ts +13 -0
  141. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/state.d.ts.map +1 -0
  142. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/types.d.ts +16 -0
  143. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/types.d.ts.map +1 -0
  144. package/dist/umd/libs/iframe-processor/processors/viewport/types.d.ts +8 -0
  145. package/dist/umd/libs/iframe-processor/processors/viewport/types.d.ts.map +1 -0
  146. package/dist/umd/libs/iframe-processor/shared/factory-types.d.ts +24 -0
  147. package/dist/umd/libs/iframe-processor/shared/factory-types.d.ts.map +1 -0
  148. package/dist/umd/libs/iframe-processor/shared/iframe-types.d.ts +20 -0
  149. package/dist/umd/libs/iframe-processor/shared/iframe-types.d.ts.map +1 -0
  150. package/dist/umd/libs/index.d.ts +1 -0
  151. package/dist/umd/libs/index.d.ts.map +1 -1
  152. package/dist/umd/libs/perf.d.ts +83 -0
  153. package/dist/umd/libs/perf.d.ts.map +1 -0
  154. package/dist/umd/stores/config.d.ts +2 -2
  155. package/dist/umd/stores/config.d.ts.map +1 -1
  156. package/dist/umd/stores/data.d.ts +4 -0
  157. package/dist/umd/stores/data.d.ts.map +1 -1
  158. package/dist/umd/stores/mode-live.d.ts +30 -16
  159. package/dist/umd/stores/mode-live.d.ts.map +1 -1
  160. package/dist/umd/stores/setting.d.ts +3 -1
  161. package/dist/umd/stores/setting.d.ts.map +1 -1
  162. package/dist/umd/stores/viz.d.ts +2 -2
  163. package/dist/umd/stores/viz.d.ts.map +1 -1
  164. package/dist/umd/types/heatmap.d.ts +5 -0
  165. package/dist/umd/types/heatmap.d.ts.map +1 -1
  166. package/dist/umd/types/iframe-helper.d.ts +0 -19
  167. package/dist/umd/types/iframe-helper.d.ts.map +1 -1
  168. package/package.json +1 -1
  169. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer.d.ts +0 -54
  170. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer.d.ts.map +0 -1
  171. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer.d.ts +0 -54
  172. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer.d.ts.map +0 -1
package/dist/esm/index.js CHANGED
@@ -152,6 +152,7 @@ function useDebounceCallback(callback, delay) {
152
152
 
153
153
  var EDeviceType;
154
154
  (function (EDeviceType) {
155
+ EDeviceType["DesktopLarge"] = "DESKTOP_LARGE";
155
156
  EDeviceType["Desktop"] = "DESKTOP";
156
157
  EDeviceType["Mobile"] = "MOBILE";
157
158
  EDeviceType["Tablet"] = "TABLET";
@@ -203,6 +204,11 @@ var EHeatmapMode;
203
204
  EHeatmapMode["Live"] = "live";
204
205
  EHeatmapMode["Compare"] = "compare";
205
206
  })(EHeatmapMode || (EHeatmapMode = {}));
207
+ var EHeatmapDataSource;
208
+ (function (EHeatmapDataSource) {
209
+ EHeatmapDataSource["Snapshot"] = "snapshot";
210
+ EHeatmapDataSource["Live"] = "live";
211
+ })(EHeatmapDataSource || (EHeatmapDataSource = {}));
206
212
 
207
213
  const ViewIdContext = createContext(undefined);
208
214
  const useViewIdContext = () => {
@@ -314,18 +320,19 @@ const useHeatmapConfigStore = create()((set) => {
314
320
  return {
315
321
  mode: EHeatmapMode.Single,
316
322
  sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
317
- clickMode: EClickMode.Default,
318
- isRendering: true,
323
+ shopId: undefined,
319
324
  setMode: (mode) => set({ mode }),
320
325
  resetMode: () => set({ mode: EHeatmapMode.Single }),
321
326
  setSidebarWidth: (sidebarWidth) => set({ sidebarWidth }),
322
- setIsRendering: (isRendering) => set({ isRendering }),
327
+ setShopId: (shopId) => set({ shopId }),
323
328
  };
324
329
  });
325
330
 
326
331
  const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
327
332
  return {
328
333
  data: new Map([[DEFAULT_VIEW_ID, undefined]]),
334
+ dataHash: new Map([[DEFAULT_VIEW_ID, undefined]]),
335
+ dataSnapshot: new Map([[DEFAULT_VIEW_ID, undefined]]),
329
336
  clickmap: new Map([[DEFAULT_VIEW_ID, undefined]]),
330
337
  clickAreas: new Map([[DEFAULT_VIEW_ID, undefined]]),
331
338
  dataInfo: new Map([[DEFAULT_VIEW_ID, undefined]]),
@@ -358,6 +365,16 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
358
365
  newData.set(viewId, data);
359
366
  return { data: newData };
360
367
  }),
368
+ setDataHash: (dataHash, viewId = DEFAULT_VIEW_ID) => set((prev) => {
369
+ const newDataHash = new Map(prev.dataHash);
370
+ newDataHash.set(viewId, dataHash);
371
+ return { dataHash: newDataHash };
372
+ }),
373
+ setDataSnapshot: (data, viewId = DEFAULT_VIEW_ID) => set((prev) => {
374
+ const newDataSnapshot = new Map(prev.dataSnapshot);
375
+ newDataSnapshot.set(viewId, data);
376
+ return { dataSnapshot: newDataSnapshot };
377
+ }),
361
378
  setClickmap: (clickmap, viewId = DEFAULT_VIEW_ID) => set((prev) => {
362
379
  const newClickmap = new Map(prev.clickmap);
363
380
  newClickmap.set(viewId, clickmap);
@@ -380,12 +397,16 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
380
397
  }),
381
398
  copyView: (fromViewId, toViewId) => set((prev) => {
382
399
  const newData = new Map(prev.data);
400
+ const newDataSnapshot = new Map(prev.dataSnapshot);
383
401
  const newClickmap = new Map(prev.clickmap);
384
402
  const newClickAreas = new Map(prev.clickAreas);
385
403
  const newDataInfo = new Map(prev.dataInfo);
386
404
  const newScrollmap = new Map(prev.scrollmap);
387
405
  const newAttentionMap = new Map(prev.attentionMap);
406
+ const newDataHash = new Map(prev.dataHash);
388
407
  newData.set(toViewId, prev.data.get(fromViewId));
408
+ newDataSnapshot.set(toViewId, prev.dataSnapshot.get(fromViewId));
409
+ newDataHash.set(toViewId, prev.dataHash.get(fromViewId));
389
410
  newClickmap.set(toViewId, prev.clickmap.get(fromViewId));
390
411
  newClickAreas.set(toViewId, prev.clickAreas.get(fromViewId));
391
412
  newDataInfo.set(toViewId, prev.dataInfo.get(fromViewId));
@@ -393,21 +414,27 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
393
414
  newAttentionMap.set(toViewId, prev.attentionMap.get(fromViewId));
394
415
  return {
395
416
  data: newData,
417
+ dataSnapshot: newDataSnapshot,
396
418
  clickmap: newClickmap,
397
419
  clickAreas: newClickAreas,
398
420
  dataInfo: newDataInfo,
399
421
  scrollmap: newScrollmap,
400
422
  attentionMap: newAttentionMap,
423
+ dataHash: newDataHash,
401
424
  };
402
425
  }),
403
426
  clearView: (viewId) => set((prev) => {
404
427
  const newData = new Map(prev.data);
428
+ const newDataSnapshot = new Map(prev.dataSnapshot);
405
429
  const newClickmap = new Map(prev.clickmap);
406
430
  const newClickAreas = new Map(prev.clickAreas);
407
431
  const newDataInfo = new Map(prev.dataInfo);
408
432
  const newScrollmap = new Map(prev.scrollmap);
409
433
  const newAttentionMap = new Map(prev.attentionMap);
434
+ const newDataHash = new Map(prev.dataHash);
410
435
  newData.delete(viewId);
436
+ newDataSnapshot.delete(viewId);
437
+ newDataHash.delete(viewId);
411
438
  newClickmap.delete(viewId);
412
439
  newClickAreas.delete(viewId);
413
440
  newDataInfo.delete(viewId);
@@ -415,6 +442,8 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
415
442
  newAttentionMap.delete(viewId);
416
443
  return {
417
444
  data: newData,
445
+ dataSnapshot: newDataSnapshot,
446
+ dataHash: newDataHash,
418
447
  clickmap: newClickmap,
419
448
  clickAreas: newClickAreas,
420
449
  dataInfo: newDataInfo,
@@ -424,6 +453,8 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
424
453
  }),
425
454
  resetAll: () => set({
426
455
  data: new Map([[DEFAULT_VIEW_ID, undefined]]),
456
+ dataSnapshot: new Map([[DEFAULT_VIEW_ID, undefined]]),
457
+ dataHash: new Map([[DEFAULT_VIEW_ID, undefined]]),
427
458
  clickmap: new Map([[DEFAULT_VIEW_ID, undefined]]),
428
459
  clickAreas: new Map([[DEFAULT_VIEW_ID, undefined]]),
429
460
  dataInfo: new Map([[DEFAULT_VIEW_ID, undefined]]),
@@ -445,6 +476,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
445
476
  clickMode: new Map([[DEFAULT_VIEW_ID, EClickMode.Default]]),
446
477
  scrollType: new Map([[DEFAULT_VIEW_ID, EScrollType.Depth]]),
447
478
  heatmapType: new Map([[DEFAULT_VIEW_ID, EHeatmapType.Click]]),
479
+ dataSource: new Map([[DEFAULT_VIEW_ID, EHeatmapDataSource.Snapshot]]),
448
480
  setIsRendering: (isRendering, viewId = DEFAULT_VIEW_ID) => set((prev) => {
449
481
  const newIsRendering = new Map(prev.isRendering);
450
482
  newIsRendering.set(viewId, isRendering);
@@ -495,6 +527,11 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
495
527
  newHeatmapType.set(viewId, heatmapType);
496
528
  return { heatmapType: newHeatmapType };
497
529
  }),
530
+ setDataSource: (dataSource, viewId = DEFAULT_VIEW_ID) => set((prev) => {
531
+ const newDataSource = new Map(prev.dataSource);
532
+ newDataSource.set(viewId, dataSource);
533
+ return { dataSource: newDataSource };
534
+ }),
498
535
  copyView: (fromViewId, toViewId) => set((prev) => {
499
536
  const newIsLoadingDom = new Map(prev.isLoadingDom);
500
537
  const newIsLoadingCanvas = new Map(prev.isLoadingCanvas);
@@ -505,6 +542,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
505
542
  const newClickMode = new Map(prev.clickMode);
506
543
  const newScrollType = new Map(prev.scrollType);
507
544
  const newHeatmapType = new Map(prev.heatmapType);
545
+ const newDataSource = new Map(prev.dataSource);
508
546
  newIsShowSidebar.set(toViewId, prev.isShowSidebar.get(fromViewId) ?? false);
509
547
  newRankedBy.set(toViewId, prev.rankedBy.get(fromViewId));
510
548
  newDeviceType.set(toViewId, prev.deviceType.get(fromViewId));
@@ -514,6 +552,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
514
552
  newHeatmapType.set(toViewId, prev.heatmapType.get(fromViewId));
515
553
  newIsLoadingDom.set(toViewId, prev.isLoadingDom.get(fromViewId) ?? false);
516
554
  newIsLoadingCanvas.set(toViewId, prev.isLoadingCanvas.get(fromViewId) ?? false);
555
+ newDataSource.set(toViewId, prev.dataSource.get(fromViewId) ?? EHeatmapDataSource.Snapshot);
517
556
  return {
518
557
  isShowSidebar: newIsShowSidebar,
519
558
  rankedBy: newRankedBy,
@@ -524,6 +563,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
524
563
  clickMode: newClickMode,
525
564
  scrollType: newScrollType,
526
565
  heatmapType: newHeatmapType,
566
+ dataSource: newDataSource,
527
567
  };
528
568
  }),
529
569
  clearView: (viewId) => set((prev) => {
@@ -536,6 +576,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
536
576
  const newClickMode = new Map(prev.clickMode);
537
577
  const newScrollType = new Map(prev.scrollType);
538
578
  const newHeatmapType = new Map(prev.heatmapType);
579
+ const newDataSource = new Map(prev.dataSource);
539
580
  newIsShowSidebar.delete(viewId);
540
581
  newRankedBy.delete(viewId);
541
582
  newIsLoadingDom.delete(viewId);
@@ -545,6 +586,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
545
586
  newClickMode.delete(viewId);
546
587
  newScrollType.delete(viewId);
547
588
  newHeatmapType.delete(viewId);
589
+ newDataSource.delete(viewId);
548
590
  return {
549
591
  isShowSidebar: newIsShowSidebar,
550
592
  rankedBy: newRankedBy,
@@ -555,6 +597,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
555
597
  clickMode: newClickMode,
556
598
  scrollType: newScrollType,
557
599
  heatmapType: newHeatmapType,
600
+ dataSource: newDataSource,
558
601
  };
559
602
  }),
560
603
  resetAll: () => set({
@@ -568,22 +611,23 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
568
611
  clickMode: new Map([[DEFAULT_VIEW_ID, EClickMode.Default]]),
569
612
  scrollType: new Map([[DEFAULT_VIEW_ID, EScrollType.Depth]]),
570
613
  heatmapType: new Map([[DEFAULT_VIEW_ID, EHeatmapType.Click]]),
614
+ dataSource: new Map([[DEFAULT_VIEW_ID, EHeatmapDataSource.Snapshot]]),
571
615
  }),
572
616
  };
573
617
  }));
574
618
 
575
619
  const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
576
620
  return {
577
- isRenderViz: new Map([[DEFAULT_VIEW_ID, false]]),
621
+ isRenderedViz: new Map([[DEFAULT_VIEW_ID, false]]),
578
622
  zoomRatio: new Map([[DEFAULT_VIEW_ID, DEFAULT_ZOOM_RATIO.DEFAULT]]),
579
623
  minZoomRatio: new Map([[DEFAULT_VIEW_ID, DEFAULT_ZOOM_RATIO.MIN]]),
580
624
  maxZoomRatio: new Map([[DEFAULT_VIEW_ID, DEFAULT_ZOOM_RATIO.MAX]]),
581
625
  scale: new Map([[DEFAULT_VIEW_ID, 1]]),
582
626
  isScaledToFit: new Map([[DEFAULT_VIEW_ID, false]]),
583
- setIsRenderViz: (isRenderViz, viewId = DEFAULT_VIEW_ID) => set((prev) => {
584
- const newIsRenderViz = new Map(prev.isRenderViz);
585
- newIsRenderViz.set(viewId, isRenderViz);
586
- return { isRenderViz: newIsRenderViz };
627
+ setIsRenderedViz: (isRenderedViz, viewId = DEFAULT_VIEW_ID) => set((prev) => {
628
+ const newIsRenderedViz = new Map(prev.isRenderedViz);
629
+ newIsRenderedViz.set(viewId, isRenderedViz);
630
+ return { isRenderedViz: newIsRenderedViz };
587
631
  }),
588
632
  setZoomRatio: (zoomRatio, viewId = DEFAULT_VIEW_ID) => set((prev) => {
589
633
  const newZoomRatio = new Map(prev.zoomRatio);
@@ -611,18 +655,18 @@ const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
611
655
  return { isScaledToFit: newIsScaledToFit };
612
656
  }),
613
657
  copyView: (fromViewId, toViewId) => set((prev) => {
614
- const newIsRenderViz = new Map(prev.isRenderViz);
658
+ const newIsRenderedViz = new Map(prev.isRenderedViz);
615
659
  const newZoomRatio = new Map(prev.zoomRatio);
616
660
  const newMinZoomRatio = new Map(prev.minZoomRatio);
617
661
  const newScale = new Map(prev.scale);
618
662
  const newIsScaledToFit = new Map(prev.isScaledToFit);
619
- newIsRenderViz.set(toViewId, prev.isRenderViz.get(fromViewId) ?? false);
663
+ newIsRenderedViz.set(toViewId, prev.isRenderedViz.get(fromViewId) ?? false);
620
664
  newZoomRatio.set(toViewId, prev.zoomRatio.get(fromViewId) ?? 100);
621
665
  newMinZoomRatio.set(toViewId, prev.minZoomRatio.get(fromViewId) ?? 10);
622
666
  newScale.set(toViewId, prev.scale.get(fromViewId) ?? 1);
623
667
  newIsScaledToFit.set(toViewId, prev.isScaledToFit.get(fromViewId) ?? false);
624
668
  return {
625
- isRenderViz: newIsRenderViz,
669
+ isRenderedViz: newIsRenderedViz,
626
670
  zoomRatio: newZoomRatio,
627
671
  minZoomRatio: newMinZoomRatio,
628
672
  scale: newScale,
@@ -630,18 +674,18 @@ const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
630
674
  };
631
675
  }),
632
676
  clearView: (viewId) => set((prev) => {
633
- const newIsRenderViz = new Map(prev.isRenderViz);
677
+ const newIsRenderedViz = new Map(prev.isRenderedViz);
634
678
  const newZoomRatio = new Map(prev.zoomRatio);
635
679
  const newMinZoomRatio = new Map(prev.minZoomRatio);
636
680
  const newScale = new Map(prev.scale);
637
681
  const newIsScaledToFit = new Map(prev.isScaledToFit);
638
- newIsRenderViz.delete(viewId);
682
+ newIsRenderedViz.delete(viewId);
639
683
  newZoomRatio.delete(viewId);
640
684
  newMinZoomRatio.delete(viewId);
641
685
  newScale.delete(viewId);
642
686
  newIsScaledToFit.delete(viewId);
643
687
  return {
644
- isRenderViz: newIsRenderViz,
688
+ isRenderedViz: newIsRenderedViz,
645
689
  zoomRatio: newZoomRatio,
646
690
  minZoomRatio: newMinZoomRatio,
647
691
  scale: newScale,
@@ -649,7 +693,7 @@ const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
649
693
  };
650
694
  }),
651
695
  resetAll: () => set({
652
- isRenderViz: new Map([[DEFAULT_VIEW_ID, false]]),
696
+ isRenderedViz: new Map([[DEFAULT_VIEW_ID, false]]),
653
697
  zoomRatio: new Map([[DEFAULT_VIEW_ID, 100]]),
654
698
  minZoomRatio: new Map([[DEFAULT_VIEW_ID, 10]]),
655
699
  scale: new Map([[DEFAULT_VIEW_ID, 1]]),
@@ -1047,24 +1091,108 @@ const useHeatmapCompareStore = create()((set, get) => {
1047
1091
  });
1048
1092
 
1049
1093
  const initialState = {
1050
- payloads: [],
1051
- htmlContent: '',
1052
- targetUrl: '',
1053
- renderMode: 'portal',
1054
- storefrontPassword: '',
1055
- };
1056
- const useHeatmapLiveStore = create()((set) => {
1057
- return {
1058
- ...initialState,
1059
- reset: () => set(initialState),
1060
- setPayloads: (payloads) => set({ payloads }),
1061
- addPayload: (payload) => set((state) => ({ payloads: [...state.payloads, payload] })),
1062
- setHtmlContent: (htmlContent) => set({ htmlContent }),
1063
- setTargetUrl: (targetUrl) => set({ targetUrl }),
1064
- setRenderMode: (renderMode) => set({ renderMode }),
1065
- setStorefrontPassword: (storefrontPassword) => set({ storefrontPassword }),
1066
- };
1067
- });
1094
+ decodedPayloads: new Map([[DEFAULT_VIEW_ID, []]]),
1095
+ encodedPayloads: new Map([[DEFAULT_VIEW_ID, []]]),
1096
+ htmlContent: new Map([[DEFAULT_VIEW_ID, '']]),
1097
+ targetUrl: new Map([[DEFAULT_VIEW_ID, '']]),
1098
+ renderMode: new Map([[DEFAULT_VIEW_ID, 'portal']]),
1099
+ storefrontPassword: new Map([[DEFAULT_VIEW_ID, '']]),
1100
+ };
1101
+ const useHeatmapLiveStore = create()(subscribeWithSelector((set) => ({
1102
+ ...initialState,
1103
+ addPayload: (payload, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1104
+ const newDecoded = new Map(prev.decodedPayloads);
1105
+ newDecoded.set(viewId, [...(newDecoded.get(viewId) ?? []), payload]);
1106
+ const newEncoded = new Map(prev.encodedPayloads);
1107
+ newEncoded.set(viewId, [...(newEncoded.get(viewId) ?? []), JSON.stringify(payload)]);
1108
+ return { decodedPayloads: newDecoded, encodedPayloads: newEncoded };
1109
+ }),
1110
+ setPayloads: (payloads, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1111
+ const newDecoded = new Map(prev.decodedPayloads);
1112
+ newDecoded.set(viewId, payloads);
1113
+ const newEncoded = new Map(prev.encodedPayloads);
1114
+ newEncoded.set(viewId, payloads.map((p) => JSON.stringify(p)));
1115
+ return { decodedPayloads: newDecoded, encodedPayloads: newEncoded };
1116
+ }),
1117
+ setEncodedPayloads: (payloads, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1118
+ const newEncoded = new Map(prev.encodedPayloads);
1119
+ newEncoded.set(viewId, payloads);
1120
+ return { encodedPayloads: newEncoded };
1121
+ }),
1122
+ setHtmlContent: (htmlContent, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1123
+ const newHtmlContent = new Map(prev.htmlContent);
1124
+ newHtmlContent.set(viewId, htmlContent);
1125
+ return { htmlContent: newHtmlContent };
1126
+ }),
1127
+ setTargetUrl: (targetUrl, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1128
+ const newTargetUrl = new Map(prev.targetUrl);
1129
+ newTargetUrl.set(viewId, targetUrl);
1130
+ return { targetUrl: newTargetUrl };
1131
+ }),
1132
+ setRenderMode: (renderMode, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1133
+ const newRenderMode = new Map(prev.renderMode);
1134
+ newRenderMode.set(viewId, renderMode);
1135
+ return { renderMode: newRenderMode };
1136
+ }),
1137
+ setStorefrontPassword: (storefrontPassword, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1138
+ const newStorefrontPassword = new Map(prev.storefrontPassword);
1139
+ newStorefrontPassword.set(viewId, storefrontPassword);
1140
+ return { storefrontPassword: newStorefrontPassword };
1141
+ }),
1142
+ resetView: (viewId = DEFAULT_VIEW_ID) => set((prev) => {
1143
+ const newDecoded = new Map(prev.decodedPayloads);
1144
+ newDecoded.set(viewId, []);
1145
+ const newEncoded = new Map(prev.encodedPayloads);
1146
+ newEncoded.set(viewId, []);
1147
+ return { decodedPayloads: newDecoded, encodedPayloads: newEncoded };
1148
+ }),
1149
+ copyView: (fromViewId, toViewId) => set((prev) => {
1150
+ const newDecoded = new Map(prev.decodedPayloads);
1151
+ const newEncoded = new Map(prev.encodedPayloads);
1152
+ const newHtmlContent = new Map(prev.htmlContent);
1153
+ const newTargetUrl = new Map(prev.targetUrl);
1154
+ const newRenderMode = new Map(prev.renderMode);
1155
+ const newStorefrontPassword = new Map(prev.storefrontPassword);
1156
+ newDecoded.set(toViewId, prev.decodedPayloads.get(fromViewId) ?? []);
1157
+ newEncoded.set(toViewId, prev.encodedPayloads.get(fromViewId) ?? []);
1158
+ newHtmlContent.set(toViewId, prev.htmlContent.get(fromViewId) ?? '');
1159
+ newTargetUrl.set(toViewId, prev.targetUrl.get(fromViewId) ?? '');
1160
+ newRenderMode.set(toViewId, prev.renderMode.get(fromViewId) ?? 'portal');
1161
+ newStorefrontPassword.set(toViewId, prev.storefrontPassword.get(fromViewId) ?? '');
1162
+ return {
1163
+ decodedPayloads: newDecoded,
1164
+ encodedPayloads: newEncoded,
1165
+ htmlContent: newHtmlContent,
1166
+ targetUrl: newTargetUrl,
1167
+ renderMode: newRenderMode,
1168
+ storefrontPassword: newStorefrontPassword,
1169
+ };
1170
+ }),
1171
+ clearView: (viewId) => set((prev) => {
1172
+ const newDecoded = new Map(prev.decodedPayloads);
1173
+ const newEncoded = new Map(prev.encodedPayloads);
1174
+ const newHtmlContent = new Map(prev.htmlContent);
1175
+ const newTargetUrl = new Map(prev.targetUrl);
1176
+ const newRenderMode = new Map(prev.renderMode);
1177
+ const newStorefrontPassword = new Map(prev.storefrontPassword);
1178
+ newDecoded.delete(viewId);
1179
+ newEncoded.delete(viewId);
1180
+ newHtmlContent.delete(viewId);
1181
+ newTargetUrl.delete(viewId);
1182
+ newRenderMode.delete(viewId);
1183
+ newStorefrontPassword.delete(viewId);
1184
+ return {
1185
+ decodedPayloads: newDecoded,
1186
+ encodedPayloads: newEncoded,
1187
+ htmlContent: newHtmlContent,
1188
+ targetUrl: newTargetUrl,
1189
+ renderMode: newRenderMode,
1190
+ storefrontPassword: newStorefrontPassword,
1191
+ };
1192
+ }),
1193
+ resetAll: () => set(initialState),
1194
+ reset: () => set(initialState),
1195
+ })));
1068
1196
 
1069
1197
  const useHeatmapVizRectStore = create()(subscribeWithSelector((set) => {
1070
1198
  return {
@@ -1170,26 +1298,12 @@ const useHeatmapClickContext = createViewContextHook({
1170
1298
  }),
1171
1299
  });
1172
1300
 
1173
- /**
1174
- * Hook to access heatmap data state and actions with optional selector
1175
- *
1176
- * @example
1177
- * ```tsx
1178
- * // Get everything
1179
- * const { data, clickmap, setData } = useHeatmapDataContext();
1180
- *
1181
- * // Get only what you need (no unnecessary re-renders)
1182
- * const data = useHeatmapDataContext(s => s.data);
1183
- * const { setData, setClickmap } = useHeatmapDataContext(s => ({
1184
- * setData: s.setData,
1185
- * setClickmap: s.setClickmap,
1186
- * }));
1187
- * ```
1188
- */
1189
1301
  const useHeatmapDataContext = createViewContextHook({
1190
1302
  useStore: useHeatmapDataStore,
1191
1303
  getState: (store, viewId) => ({
1192
1304
  data: store.data.get(viewId),
1305
+ dataHash: store.dataHash.get(viewId),
1306
+ dataSnapshot: store.dataSnapshot.get(viewId),
1193
1307
  clickmap: store.clickmap.get(viewId),
1194
1308
  clickAreas: store.clickAreas.get(viewId),
1195
1309
  scrollmap: store.scrollmap.get(viewId),
@@ -1199,6 +1313,8 @@ const useHeatmapDataContext = createViewContextHook({
1199
1313
  }),
1200
1314
  getActions: (store, viewId) => ({
1201
1315
  setData: (newData) => store.setData(newData, viewId),
1316
+ setDataSnapshot: (newData) => store.setDataSnapshot(newData, viewId),
1317
+ setDataHash: (newHash) => store.setDataHash(newHash, viewId),
1202
1318
  setClickmap: (newClickmap) => store.setClickmap(newClickmap, viewId),
1203
1319
  setClickAreas: (newClickAreas) => store.setClickAreas(newClickAreas, viewId),
1204
1320
  setDataInfoByKey: (key, value) => store.setDataInfoByKey(key, value, viewId),
@@ -1221,6 +1337,32 @@ const useHeatmapHoverContext = createViewContextHook({
1221
1337
  }),
1222
1338
  });
1223
1339
 
1340
+ const useHeatmapLiveContext = createViewContextHook({
1341
+ useStore: useHeatmapLiveStore,
1342
+ getState: (store, viewId) => ({
1343
+ decodedPayloads: store.decodedPayloads.get(viewId) ?? [],
1344
+ encodedPayloads: store.encodedPayloads.get(viewId) ?? [],
1345
+ htmlContent: store.htmlContent.get(viewId) ?? '',
1346
+ targetUrl: store.targetUrl.get(viewId) ?? '',
1347
+ renderMode: store.renderMode.get(viewId) ?? 'portal',
1348
+ storefrontPassword: store.storefrontPassword.get(viewId) ?? '',
1349
+ }),
1350
+ getActions: (store, viewId) => ({
1351
+ addPayload: (payload) => store.addPayload(payload, viewId),
1352
+ setPayloads: (payloads) => store.setPayloads(payloads, viewId),
1353
+ setEncodedPayloads: (payloads) => store.setEncodedPayloads(payloads, viewId),
1354
+ setHtmlContent: (htmlContent) => store.setHtmlContent(htmlContent, viewId),
1355
+ setTargetUrl: (targetUrl) => store.setTargetUrl(targetUrl, viewId),
1356
+ setRenderMode: (mode) => store.setRenderMode(mode, viewId),
1357
+ setStorefrontPassword: (password) => store.setStorefrontPassword(password, viewId),
1358
+ resetView: () => store.resetView(viewId),
1359
+ copyView: (fromViewId, toViewId) => store.copyView(fromViewId, toViewId),
1360
+ clearView: (viewId) => store.clearView(viewId),
1361
+ resetAll: () => store.resetAll(),
1362
+ reset: () => store.reset(),
1363
+ }),
1364
+ });
1365
+
1224
1366
  const useHeatmapScrollContext = createViewContextHook({
1225
1367
  useStore: useHeatmapVizScrollStore,
1226
1368
  getState: (store, viewId) => ({
@@ -1250,6 +1392,7 @@ const useHeatmapSettingContext = createViewContextHook({
1250
1392
  clickMode: store.clickMode.get(viewId),
1251
1393
  scrollType: store.scrollType.get(viewId),
1252
1394
  heatmapType: store.heatmapType.get(viewId),
1395
+ dataSource: store.dataSource.get(viewId) ?? EHeatmapDataSource.Snapshot,
1253
1396
  }),
1254
1397
  getActions: (store, viewId) => ({
1255
1398
  setIsShowSidebar: (isShowSidebar) => store.setIsShowSidebar(isShowSidebar, viewId),
@@ -1262,6 +1405,7 @@ const useHeatmapSettingContext = createViewContextHook({
1262
1405
  setIsRendering: (isRendering) => store.setIsRendering(isRendering, viewId),
1263
1406
  setIsLoadingDom: (isLoadingDom) => store.setIsLoadingDom(isLoadingDom, viewId),
1264
1407
  setIsLoadingCanvas: (isLoadingCanvas) => store.setIsLoadingCanvas(isLoadingCanvas, viewId),
1408
+ setDataSource: (dataSource) => store.setDataSource(dataSource, viewId),
1265
1409
  clearView: (viewId) => store.clearView(viewId),
1266
1410
  }),
1267
1411
  });
@@ -1269,7 +1413,7 @@ const useHeatmapSettingContext = createViewContextHook({
1269
1413
  const useHeatmapVizContext = createViewContextHook({
1270
1414
  useStore: useHeatmapVizStore,
1271
1415
  getState: (store, viewId) => ({
1272
- isRenderViz: store.isRenderViz.get(viewId) ?? false,
1416
+ isRenderedViz: store.isRenderedViz.get(viewId) ?? false,
1273
1417
  zoomRatio: store.zoomRatio.get(viewId) ?? DEFAULT_ZOOM_RATIO.DEFAULT,
1274
1418
  minZoomRatio: store.minZoomRatio.get(viewId) ?? DEFAULT_ZOOM_RATIO.MIN,
1275
1419
  maxZoomRatio: store.maxZoomRatio.get(viewId) ?? DEFAULT_ZOOM_RATIO.MAX,
@@ -1277,7 +1421,7 @@ const useHeatmapVizContext = createViewContextHook({
1277
1421
  isScaledToFit: store.isScaledToFit.get(viewId) ?? false,
1278
1422
  }),
1279
1423
  getActions: (store, viewId) => ({
1280
- setIsRenderViz: (value) => store.setIsRenderViz(value, viewId),
1424
+ setIsRenderedViz: (value) => store.setIsRenderedViz(value, viewId),
1281
1425
  setZoomRatio: (value) => store.setZoomRatio(value, viewId),
1282
1426
  setMinZoomRatio: (value) => store.setMinZoomRatio(value, viewId),
1283
1427
  setMaxZoomRatio: (value) => store.setMaxZoomRatio(value, viewId),
@@ -1311,9 +1455,7 @@ const useHeatmapCopyView = () => {
1311
1455
  const copyVizView = useHeatmapVizStore((state) => state.copyView);
1312
1456
  const copyVizClickView = useHeatmapVizClickStore((state) => state.copyView);
1313
1457
  const copyVizAreaClickView = useHeatmapVizClickAreaStore((state) => state.copyView);
1314
- // const copyVizRectView = useHeatmapVizRectStore((state) => state.copyView);
1315
- // const copyVizHoverView = useHeatmapVizHoverStore((state) => state.copyView);
1316
- // const copyVizScrollView = useHeatmapVizScrollStore((state) => state.copyView);
1458
+ const copyLiveView = useHeatmapLiveStore((state) => state.copyView);
1317
1459
  const clearDataView = useHeatmapDataStore((state) => state.clearView);
1318
1460
  const clearSettingView = useHeatmapSettingStore((state) => state.clearView);
1319
1461
  const clearVizView = useHeatmapVizStore((state) => state.clearView);
@@ -1322,6 +1464,7 @@ const useHeatmapCopyView = () => {
1322
1464
  const clearVizHoverView = useHeatmapVizHoverStore((state) => state.clearView);
1323
1465
  const clearVizScrollView = useHeatmapVizScrollStore((state) => state.clearView);
1324
1466
  const clearVizAreaClickView = useHeatmapVizClickAreaStore((state) => state.clearView);
1467
+ const clearLiveView = useHeatmapLiveStore((state) => state.clearView);
1325
1468
  const resetDataAll = useHeatmapDataStore((state) => state.resetAll);
1326
1469
  const resetSettingAll = useHeatmapSettingStore((state) => state.resetAll);
1327
1470
  const resetVizAll = useHeatmapVizStore((state) => state.resetAll);
@@ -1330,15 +1473,14 @@ const useHeatmapCopyView = () => {
1330
1473
  const resetVizHoverAll = useHeatmapVizHoverStore((state) => state.resetAll);
1331
1474
  const resetVizScrollViewAll = useHeatmapVizScrollStore((state) => state.resetAll);
1332
1475
  const resetVizClickAreaAll = useHeatmapVizClickAreaStore((state) => state.resetAll);
1476
+ const resetLiveAll = useHeatmapLiveStore((state) => state.resetAll);
1333
1477
  const copyView = (fromViewId, toViewId) => {
1334
1478
  copyDataView(fromViewId, toViewId);
1335
1479
  copySettingView(fromViewId, toViewId);
1336
1480
  copyVizView(fromViewId, toViewId);
1337
- // copyVizRectView(fromViewId, toViewId);
1338
1481
  copyVizClickView(fromViewId, toViewId);
1339
- // copyVizHoverView(fromViewId, toViewId);
1340
- // copyVizScrollView(fromViewId, toViewId);
1341
1482
  copyVizAreaClickView(fromViewId, toViewId);
1483
+ copyLiveView(fromViewId, toViewId);
1342
1484
  };
1343
1485
  const copyViewToMultiple = (fromViewId, toViewIds) => {
1344
1486
  toViewIds.forEach((toViewId) => {
@@ -1354,6 +1496,7 @@ const useHeatmapCopyView = () => {
1354
1496
  clearVizHoverView(viewId);
1355
1497
  clearVizScrollView(viewId);
1356
1498
  clearVizAreaClickView(viewId);
1499
+ clearLiveView(viewId);
1357
1500
  };
1358
1501
  const clearMultipleViews = (viewIds) => {
1359
1502
  viewIds.forEach((viewId) => {
@@ -1369,6 +1512,7 @@ const useHeatmapCopyView = () => {
1369
1512
  resetVizHoverAll();
1370
1513
  resetVizScrollViewAll();
1371
1514
  resetVizClickAreaAll();
1515
+ resetLiveAll();
1372
1516
  };
1373
1517
  return {
1374
1518
  copyView,
@@ -1387,6 +1531,8 @@ const useHeatmapWidthByDevice = () => {
1387
1531
  if (!deviceType)
1388
1532
  return 1440;
1389
1533
  switch (deviceType) {
1534
+ case EDeviceType.DesktopLarge:
1535
+ return 1920;
1390
1536
  case EDeviceType.Desktop:
1391
1537
  return 1440;
1392
1538
  case EDeviceType.Tablet:
@@ -1397,13 +1543,20 @@ const useHeatmapWidthByDevice = () => {
1397
1543
  }
1398
1544
  };
1399
1545
 
1400
- const useRegisterConfig = ({ isLoading, isLoadingCanvas, }) => {
1546
+ const useRegisterConfig = ({ shopId, isLoading, isLoadingCanvas, }) => {
1401
1547
  const mode = useHeatmapConfigStore((state) => state.mode);
1548
+ const shopIdStore = useHeatmapConfigStore((state) => state.shopId);
1402
1549
  const deviceType = useHeatmapSettingContext((state) => state.deviceType);
1403
1550
  const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
1404
1551
  const setIsRendering = useHeatmapSettingContext((state) => state.setIsRendering);
1405
1552
  const setIsLoadingDom = useHeatmapSettingContext((state) => state.setIsLoadingDom);
1406
1553
  const setIsLoadingCanvas = useHeatmapSettingContext((state) => state.setIsLoadingCanvas);
1554
+ const setShopId = useHeatmapConfigStore((state) => state.setShopId);
1555
+ useEffect(() => {
1556
+ if (!shopId || !!shopIdStore || shopIdStore === shopId)
1557
+ return;
1558
+ setShopId(shopId);
1559
+ }, [shopId, setShopId, shopIdStore]);
1407
1560
  useEffect(() => {
1408
1561
  setIsRendering(true);
1409
1562
  setTimeout(() => {
@@ -1770,7 +1923,7 @@ class Logger {
1770
1923
  }
1771
1924
  }
1772
1925
  // Export singleton instance
1773
- const logger$9 = new Logger();
1926
+ const logger$4 = new Logger();
1774
1927
  // Export factory function để tạo logger với config riêng
1775
1928
  function createLogger(config = {}) {
1776
1929
  const instance = new Logger();
@@ -2159,7 +2312,7 @@ function findElementByHash(props) {
2159
2312
  }
2160
2313
  }
2161
2314
  catch (error) {
2162
- logger$9.warn(`Invalid selector "${selector}":`, error);
2315
+ logger$4.warn(`Invalid selector "${selector}":`, error);
2163
2316
  }
2164
2317
  const elementByHash = iframeDocument.querySelector(`[data-clarity-hashalpha="${hash}"], [data-clarity-hash="${hash}"], [data-clarity-hashbeta="${hash}"]`);
2165
2318
  return elementByHash;
@@ -2181,7 +2334,7 @@ function hydrateAreaNode(props) {
2181
2334
  const { id, hash, selector } = persistedData;
2182
2335
  const element = findElementByHash({ hash, selector, iframeDocument, vizRef });
2183
2336
  if (!element) {
2184
- logger$9.warn(`Cannot hydrate area ${id}: element not found for hash ${hash} or selector ${selector}`);
2337
+ logger$4.warn(`Cannot hydrate area ${id}: element not found for hash ${hash} or selector ${selector}`);
2185
2338
  return null;
2186
2339
  }
2187
2340
  const areaNode = buildAreaNode(element, hash, heatmapInfo, shadowRoot, persistedData);
@@ -2198,7 +2351,7 @@ function hydrateAreas(props) {
2198
2351
  hydratedAreas.push(area);
2199
2352
  }
2200
2353
  }
2201
- logger$9.info(`Hydrated ${hydratedAreas.length} of ${clickAreas.length} persisted areas`);
2354
+ logger$4.info(`Hydrated ${hydratedAreas.length} of ${clickAreas.length} persisted areas`);
2202
2355
  return hydratedAreas;
2203
2356
  }
2204
2357
  /**
@@ -2902,16 +3055,16 @@ const calcCalloutPositionAbsolute = (props) => {
2902
3055
 
2903
3056
  function validateAreaCreation(dataInfo, hash, areas) {
2904
3057
  if (!dataInfo?.clickMapMetrics || !dataInfo?.totalClicks) {
2905
- logger$9.warn('Cannot create area: missing heatmap data');
3058
+ logger$4.warn('Cannot create area: missing heatmap data');
2906
3059
  return false;
2907
3060
  }
2908
3061
  if (!hash) {
2909
- logger$9.warn('Cannot create area: missing hash');
3062
+ logger$4.warn('Cannot create area: missing hash');
2910
3063
  return false;
2911
3064
  }
2912
3065
  const alreadyExists = areas.some((area) => area.hash === hash);
2913
3066
  if (alreadyExists) {
2914
- logger$9.warn(`Area already exists for element: ${hash}`);
3067
+ logger$4.warn(`Area already exists for element: ${hash}`);
2915
3068
  return false;
2916
3069
  }
2917
3070
  return true;
@@ -2924,14 +3077,14 @@ function identifyConflictingAreas(area) {
2924
3077
  // Case 1: New area is a child of an existing area
2925
3078
  if (area.parentNode) {
2926
3079
  conflicts.parentId = area.parentNode.id;
2927
- logger$9.info(`New area "${area.selector}" is a child of existing area "${area.parentNode.selector}". Will remove parent.`);
3080
+ logger$4.info(`New area "${area.selector}" is a child of existing area "${area.parentNode.selector}". Will remove parent.`);
2928
3081
  }
2929
3082
  // Case 2: New area is a parent of existing area(s)
2930
3083
  if (area.childNodes.size > 0) {
2931
3084
  area.childNodes.forEach((childArea) => {
2932
3085
  conflicts.childrenIds.push(childArea.id);
2933
3086
  });
2934
- logger$9.info(`New area "${area.selector}" is a parent of ${area.childNodes.size} existing area(s). Will remove children.`);
3087
+ logger$4.info(`New area "${area.selector}" is a parent of ${area.childNodes.size} existing area(s). Will remove children.`);
2935
3088
  }
2936
3089
  return conflicts;
2937
3090
  }
@@ -2982,7 +3135,7 @@ function useAreaCreation(options = {}) {
2982
3135
  }
2983
3136
  }
2984
3137
  catch (error) {
2985
- logger$9.error('Failed to create area:', error);
3138
+ logger$4.error('Failed to create area:', error);
2986
3139
  }
2987
3140
  }, [dataInfo, areas, addArea, removeArea, removeClickArea, customShadowRoot, onAreaCreated]);
2988
3141
  return {
@@ -3097,16 +3250,16 @@ function useAreaHydration(options) {
3097
3250
  return;
3098
3251
  if (!dataInfo)
3099
3252
  return;
3100
- logger$9.info(`Hydrating ${clickAreas.length} persisted areas...`);
3253
+ logger$4.info(`Hydrating ${clickAreas.length} persisted areas...`);
3101
3254
  const hydratedAreas = hydrateAreas({ clickAreas, heatmapInfo: dataInfo, vizRef, shadowRoot });
3102
3255
  if (!hydratedAreas?.length) {
3103
- logger$9.warn('No areas could be hydrated - all elements may have been removed from DOM');
3256
+ logger$4.warn('No areas could be hydrated - all elements may have been removed from DOM');
3104
3257
  return;
3105
3258
  }
3106
3259
  setIsInitializing(true);
3107
3260
  buildAreaGraph(hydratedAreas);
3108
3261
  setAreas(hydratedAreas);
3109
- logger$9.info(`Successfully hydrated ${hydratedAreas.length} areas`);
3262
+ logger$4.info(`Successfully hydrated ${hydratedAreas.length} areas`);
3110
3263
  }, [dataInfo, vizRef, isInitializing, clickAreas]);
3111
3264
  useEffect(() => {
3112
3265
  if (!enabled)
@@ -3240,7 +3393,7 @@ function useAreaRectSync(options) {
3240
3393
  area.rect.update(newRect);
3241
3394
  }
3242
3395
  catch (error) {
3243
- logger$9.error(`Failed to update rect for area ${area.id}:`, error);
3396
+ logger$4.error(`Failed to update rect for area ${area.id}:`, error);
3244
3397
  }
3245
3398
  });
3246
3399
  buildAreaGraph(areas);
@@ -3345,9 +3498,9 @@ const useAreaClickmap = () => {
3345
3498
  const useClickmap = () => {
3346
3499
  const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
3347
3500
  const clickmap = useHeatmapDataContext((s) => s.clickmap);
3348
- const isRenderViz = useHeatmapVizContext((s) => s.isRenderViz);
3501
+ const isRenderedViz = useHeatmapVizContext((s) => s.isRenderedViz);
3349
3502
  const start = useCallback(() => {
3350
- if (!vizRef || !clickmap || clickmap.length === 0 || !isRenderViz)
3503
+ if (!vizRef || !clickmap || clickmap.length === 0 || !isRenderedViz)
3351
3504
  return;
3352
3505
  try {
3353
3506
  vizRef?.clearmap?.();
@@ -3356,7 +3509,7 @@ const useClickmap = () => {
3356
3509
  catch (error) {
3357
3510
  console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
3358
3511
  }
3359
- }, [vizRef, clickmap, isRenderViz]);
3512
+ }, [vizRef, clickmap, isRenderedViz]);
3360
3513
  return { start };
3361
3514
  };
3362
3515
 
@@ -3384,7 +3537,7 @@ const useScrollmap = () => {
3384
3537
  vizRef?.scrollmap?.(scrollmap);
3385
3538
  }
3386
3539
  catch (error) {
3387
- logger$9.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
3540
+ logger$4.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
3388
3541
  }
3389
3542
  }, [vizRef, scrollmap]);
3390
3543
  return { start };
@@ -3855,7 +4008,7 @@ var MessageType;
3855
4008
  })(MessageType || (MessageType = {}));
3856
4009
  function useVizLiveIframeMsg(options = {}) {
3857
4010
  const { trustedOrigins = [], onMessage } = options;
3858
- const addPayload = useHeatmapLiveStore((state) => state.addPayload);
4011
+ const addPayload = useHeatmapLiveContext((s) => s.addPayload);
3859
4012
  const [isReady, setIsReady] = useState(false);
3860
4013
  const iframeRef = useRef(null);
3861
4014
  const isValidOrigin = useCallback((origin) => {
@@ -3881,7 +4034,7 @@ function useVizLiveIframeMsg(options = {}) {
3881
4034
  switch (message.type) {
3882
4035
  case MessageType.GX_DOM_TRACKING_PAYLOAD:
3883
4036
  if (message.payload) {
3884
- const data = decodeClarity(message.payload);
4037
+ const data = JSON.parse(message.payload);
3885
4038
  if (data) {
3886
4039
  addPayload(data);
3887
4040
  }
@@ -3904,21 +4057,341 @@ function useVizLiveIframeMsg(options = {}) {
3904
4057
  };
3905
4058
  }
3906
4059
 
4060
+ /**
4061
+ * Unified performance timing utility.
4062
+ *
4063
+ * Two complementary tools:
4064
+ *
4065
+ * 1. `perf` — global DevTools session recorder.
4066
+ * Stores structured timing in `window.__gemxPerf` for inspection.
4067
+ * Used by the iframe-processor rendering pipeline.
4068
+ *
4069
+ * perf.startSession('render-1');
4070
+ * const t = perf.mark('viewport.run');
4071
+ * perf.measure('viewport.run', t);
4072
+ * perf.endSession();
4073
+ *
4074
+ * 2. `createPerfTimer` — per-module console logger factory.
4075
+ * Logs prefixed timings to the console AND records entries into the
4076
+ * active global session so they appear in `window.__gemxPerf` too.
4077
+ *
4078
+ * const timer = createPerfTimer('Render');
4079
+ * const t0 = timer.mark('start');
4080
+ * await timer.wrap('visualizer.html', () => visualizer.html(...));
4081
+ * timer.measure('total', t0);
4082
+ */
4083
+ const s = {
4084
+ enabled: true,
4085
+ current: null,
4086
+ sessions: [],
4087
+ maxSessions: 20,
4088
+ };
4089
+ // ── Global singleton functions ────────────────────────────────────────────────
4090
+ function startSession(id) {
4091
+ if (!s.enabled)
4092
+ return;
4093
+ s.current = { id, startedAt: performance.now(), entries: [] };
4094
+ }
4095
+ function endSession() {
4096
+ if (!s.enabled || !s.current)
4097
+ return null;
4098
+ const session = s.current;
4099
+ session.total = performance.now() - session.startedAt;
4100
+ s.sessions = [session, ...s.sessions].slice(0, s.maxSessions);
4101
+ s.current = null;
4102
+ flush();
4103
+ return session;
4104
+ }
4105
+ /** Record a point-in-time mark. Returns `performance.now()` for use with measure(). */
4106
+ function globalMark(label) {
4107
+ const now = performance.now();
4108
+ if (s.enabled && s.current) {
4109
+ s.current.entries.push({ label, t: now - s.current.startedAt });
4110
+ }
4111
+ return now;
4112
+ }
4113
+ /** Record a duration from a previous mark() timestamp. */
4114
+ function globalMeasure(label, t0) {
4115
+ const duration = performance.now() - t0;
4116
+ if (s.enabled && s.current) {
4117
+ s.current.entries.push({ label, t: t0 - s.current.startedAt, duration });
4118
+ }
4119
+ return duration;
4120
+ }
4121
+ function getReport() {
4122
+ return {
4123
+ sessions: s.sessions,
4124
+ latest: s.sessions[0] ?? null,
4125
+ };
4126
+ }
4127
+ function clearSessions() {
4128
+ s.current = null;
4129
+ s.sessions = [];
4130
+ if (typeof window !== 'undefined')
4131
+ delete window.__gemxPerf;
4132
+ }
4133
+ function enableGlobal() {
4134
+ s.enabled = true;
4135
+ }
4136
+ function disableGlobal() {
4137
+ s.enabled = false;
4138
+ }
4139
+ function flush() {
4140
+ if (typeof window === 'undefined')
4141
+ return;
4142
+ window.__gemxPerf = getReport();
4143
+ }
4144
+ // ── Global singleton export ───────────────────────────────────────────────────
4145
+ const perf = {
4146
+ startSession,
4147
+ endSession,
4148
+ mark: globalMark,
4149
+ measure: globalMeasure,
4150
+ getReport,
4151
+ clear: clearSessions,
4152
+ enable: enableGlobal,
4153
+ disable: disableGlobal,
4154
+ };
4155
+
3907
4156
  /**
3908
4157
  * DOM observation setup — ResizeObserver + MutationObserver.
3909
4158
  * Returns a cleanup function that disconnects both observers.
3910
4159
  */
3911
- createLogger({ enabled: false, prefix: 'IframeHeightObserver' });
4160
+ const logger$3 = createLogger({ enabled: false, prefix: 'IframeHeightObserver' });
4161
+ function setup(doc, onChange) {
4162
+ const resizeObserver = new ResizeObserver(onChange);
4163
+ resizeObserver.observe(doc.documentElement);
4164
+ resizeObserver.observe(doc.body);
4165
+ const mutationObserver = new MutationObserver(onChange);
4166
+ mutationObserver.observe(doc.body, {
4167
+ childList: true,
4168
+ subtree: true,
4169
+ attributes: true,
4170
+ attributeFilter: ['style', 'class', 'hidden', 'data-v'],
4171
+ });
4172
+ logger$3.log('DOM observers started (ResizeObserver + MutationObserver)');
4173
+ return () => {
4174
+ resizeObserver.disconnect();
4175
+ mutationObserver.disconnect();
4176
+ logger$3.log('DOM observers disconnected');
4177
+ };
4178
+ }
3912
4179
 
3913
4180
  /**
3914
4181
  * Height Observer Processor
3915
4182
  * Background observer — watches for iframe content height changes.
3916
4183
  */
3917
- createLogger({ enabled: true, prefix: 'IframeHeightObserver' });
4184
+ // ── Module-level functions ────────────────────────────────────────────────────
4185
+ function clearTimers(s) {
4186
+ if (s.throttleTimeout) {
4187
+ clearTimeout(s.throttleTimeout);
4188
+ s.throttleTimeout = null;
4189
+ }
4190
+ if (s.debounceTimeout) {
4191
+ clearTimeout(s.debounceTimeout);
4192
+ s.debounceTimeout = null;
4193
+ }
4194
+ }
4195
+ function getActualHeight(s) {
4196
+ if (!s.iframe?.contentDocument)
4197
+ return 0;
4198
+ const { documentElement: docEl, body } = s.iframe.contentDocument;
4199
+ const heights = [docEl.scrollHeight, docEl.offsetHeight, body.scrollHeight, body.offsetHeight];
4200
+ const maxHeight = Math.max(...heights.filter((h) => h > 0));
4201
+ s.logger.log('Height sources:', {
4202
+ 'documentElement.scrollHeight': docEl.scrollHeight,
4203
+ 'documentElement.offsetHeight': docEl.offsetHeight,
4204
+ 'body.scrollHeight': body.scrollHeight,
4205
+ 'body.offsetHeight': body.offsetHeight,
4206
+ maxHeight,
4207
+ });
4208
+ return maxHeight;
4209
+ }
4210
+ async function processHeightChange(s, newHeight) {
4211
+ if (!s.iframe || !s.config)
4212
+ return;
4213
+ s.isProcessing = true;
4214
+ s.logger.log(`Processing height change: ${newHeight}px`);
4215
+ try {
4216
+ const result = {
4217
+ height: newHeight,
4218
+ width: s.iframe.contentWindow?.innerWidth ?? 0,
4219
+ };
4220
+ s.lastHeight = newHeight;
4221
+ s.logger.log('Height change processed:', result);
4222
+ s.config.onHeightChange?.(result);
4223
+ window.dispatchEvent(new CustomEvent('iframe-dimensions-applied', { detail: result }));
4224
+ }
4225
+ catch (error) {
4226
+ s.logger.error('Failed to process height change:', error);
4227
+ s.config.onError?.(error);
4228
+ }
4229
+ finally {
4230
+ s.isProcessing = false;
4231
+ }
4232
+ }
4233
+ function handleHeightChange(s) {
4234
+ if (s.isProcessing || s.throttleTimeout)
4235
+ return;
4236
+ s.throttleTimeout = setTimeout(() => {
4237
+ s.throttleTimeout = null;
4238
+ const currentHeight = getActualHeight(s);
4239
+ if (currentHeight === s.lastHeight)
4240
+ return;
4241
+ s.logger.log(`Height changed: ${s.lastHeight}px -> ${currentHeight}px`);
4242
+ if (s.debounceTimeout)
4243
+ clearTimeout(s.debounceTimeout);
4244
+ s.debounceTimeout = setTimeout(() => {
4245
+ s.debounceTimeout = null;
4246
+ processHeightChange(s, currentHeight);
4247
+ }, s.debounceMs);
4248
+ }, s.throttleMs);
4249
+ }
4250
+ function observe(s) {
4251
+ if (!s.iframe?.contentDocument?.body) {
4252
+ s.logger.warn('Cannot observe height changes: iframe body not found');
4253
+ return;
4254
+ }
4255
+ s.observerCleanup?.();
4256
+ s.lastHeight = s.iframe.contentDocument.documentElement.scrollHeight;
4257
+ s.logger.log('Initial height:', s.lastHeight);
4258
+ s.observerCleanup = setup(s.iframe.contentDocument, () => handleHeightChange(s));
4259
+ }
4260
+ function start$5(s, cfg) {
4261
+ if (s.running) {
4262
+ s.logger.warn('Observer is already running. Call stop() first.');
4263
+ return;
4264
+ }
4265
+ s.iframe = cfg.iframe;
4266
+ s.config = cfg;
4267
+ s.throttleMs = cfg.throttleMs ?? 25;
4268
+ s.debounceMs = cfg.debounceMs ?? 500;
4269
+ s.running = true;
4270
+ observe(s);
4271
+ s.logger.log('Height observer started');
4272
+ }
4273
+ function stop$5(s) {
4274
+ if (!s.running)
4275
+ return;
4276
+ s.observerCleanup?.();
4277
+ s.observerCleanup = null;
4278
+ clearTimers(s);
4279
+ s.iframe = null;
4280
+ s.config = null;
4281
+ s.lastHeight = 0;
4282
+ s.isProcessing = false;
4283
+ s.running = false;
4284
+ s.logger.log('Height observer stopped');
4285
+ }
4286
+ function clear(s) {
4287
+ s.observerCleanup?.();
4288
+ s.observerCleanup = null;
4289
+ clearTimers(s);
4290
+ s.lastHeight = 0;
4291
+ s.isProcessing = false;
4292
+ }
4293
+ function updateConfig$3(s, cfg) {
4294
+ if (!s.running || !s.config) {
4295
+ s.logger.warn('Observer is not running.');
4296
+ return;
4297
+ }
4298
+ s.config = { ...s.config, ...cfg };
4299
+ if (cfg.throttleMs !== undefined)
4300
+ s.throttleMs = cfg.throttleMs;
4301
+ if (cfg.debounceMs !== undefined)
4302
+ s.debounceMs = cfg.debounceMs;
4303
+ s.logger.configure({ enabled: !!s.config.debug });
4304
+ s.logger.log('Config updated');
4305
+ }
4306
+ // ── Factory ───────────────────────────────────────────────────────────────────
4307
+ function createHeightObserver() {
4308
+ const s = {
4309
+ logger: createLogger({ enabled: true, prefix: 'IframeHeightObserver' }),
4310
+ iframe: null,
4311
+ config: null,
4312
+ observerCleanup: null,
4313
+ lastHeight: 0,
4314
+ throttleTimeout: null,
4315
+ debounceTimeout: null,
4316
+ isProcessing: false,
4317
+ throttleMs: 25,
4318
+ debounceMs: 500,
4319
+ running: false,
4320
+ };
4321
+ return {
4322
+ start: (cfg) => start$5(s, cfg),
4323
+ stop: () => stop$5(s),
4324
+ observe: () => observe(s),
4325
+ clear: () => clear(s),
4326
+ updateConfig: (cfg) => updateConfig$3(s, cfg),
4327
+ getCurrentHeight: () => s.lastHeight,
4328
+ isRunning: () => s.running,
4329
+ getStateInfo: () => ({
4330
+ isRunning: s.running,
4331
+ lastHeight: s.lastHeight,
4332
+ isProcessing: s.isProcessing,
4333
+ hasObservers: !!s.observerCleanup,
4334
+ }),
4335
+ };
4336
+ }
4337
+
4338
+ /**
4339
+ * Window-level event management for the navigation processor.
4340
+ *
4341
+ * Responsibilities:
4342
+ * - Subscribe to events dispatched by this processor (for logging/hooks)
4343
+ * - Dispatch navigation events to the parent window
4344
+ */
4345
+ // ── Module-level functions ────────────────────────────────────────────────────
4346
+ function attach$1(s, debug) {
4347
+ s.logger.configure({ enabled: !!debug });
4348
+ s.navigationBlockedListener = (e) => {
4349
+ const ev = e;
4350
+ s.logger.log('Navigation blocked:', ev.detail.url);
4351
+ };
4352
+ s.formSubmitWindowListener = (e) => {
4353
+ const ev = e;
4354
+ s.logger.log('Form submitted:', ev.detail.data);
4355
+ };
4356
+ window.addEventListener('iframe-navigation-blocked', s.navigationBlockedListener);
4357
+ window.addEventListener('iframe-form-submit', s.formSubmitWindowListener);
4358
+ }
4359
+ function detach$1(s) {
4360
+ if (s.navigationBlockedListener) {
4361
+ window.removeEventListener('iframe-navigation-blocked', s.navigationBlockedListener);
4362
+ s.navigationBlockedListener = null;
4363
+ }
4364
+ if (s.formSubmitWindowListener) {
4365
+ window.removeEventListener('iframe-form-submit', s.formSubmitWindowListener);
4366
+ s.formSubmitWindowListener = null;
4367
+ }
4368
+ }
4369
+ function dispatchBlocked(url, showMessage) {
4370
+ if (showMessage)
4371
+ alert(`Navigation blocked: ${url}`);
4372
+ window.dispatchEvent(new CustomEvent('iframe-navigation-blocked', { detail: { url } }));
4373
+ }
4374
+ function dispatchFormSubmit(form, data) {
4375
+ window.dispatchEvent(new CustomEvent('iframe-form-submit', { detail: { form, data } }));
4376
+ }
4377
+ // ── Factory ───────────────────────────────────────────────────────────────────
4378
+ function createNavigationListeners() {
4379
+ const s = {
4380
+ logger: createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' }),
4381
+ navigationBlockedListener: null,
4382
+ formSubmitWindowListener: null,
4383
+ };
4384
+ return {
4385
+ attach: (debug) => attach$1(s, debug),
4386
+ detach: () => detach$1(s),
4387
+ dispatchBlocked,
4388
+ dispatchFormSubmit,
4389
+ };
4390
+ }
3918
4391
 
3919
- const logger$8 = createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' });
4392
+ const logger$2 = createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' });
3920
4393
  function configure$1(debug) {
3921
- logger$8.configure({ enabled: debug });
4394
+ logger$2.configure({ enabled: debug });
3922
4395
  }
3923
4396
  // ─── DOM Utilities ────────────────────────────────────────────────────────────
3924
4397
  function disableAllLinks(doc) {
@@ -3942,10 +4415,10 @@ function setupLinkBlocker(doc, isEnabled, onBlocked) {
3942
4415
  return;
3943
4416
  const href = link.getAttribute('href');
3944
4417
  if (!href || href === '' || href === '#' || href.startsWith('#')) {
3945
- logger$8.log('Allowed hash navigation:', href);
4418
+ logger$2.log('Allowed hash navigation:', href);
3946
4419
  return;
3947
4420
  }
3948
- logger$8.log('Blocked link navigation to:', href);
4421
+ logger$2.log('Blocked link navigation to:', href);
3949
4422
  e.preventDefault();
3950
4423
  e.stopPropagation();
3951
4424
  e.stopImmediatePropagation();
@@ -3959,7 +4432,7 @@ function setupLinkBlocker(doc, isEnabled, onBlocked) {
3959
4432
  return;
3960
4433
  const href = link.getAttribute('href');
3961
4434
  if (href && !href.startsWith('#')) {
3962
- logger$8.log('Blocked auxclick navigation');
4435
+ logger$2.log('Blocked auxclick navigation');
3963
4436
  e.preventDefault();
3964
4437
  e.stopPropagation();
3965
4438
  e.stopImmediatePropagation();
@@ -3980,7 +4453,7 @@ function setupFormBlocker(doc, isEnabled, onBlocked, onFormSubmit) {
3980
4453
  const form = e.target;
3981
4454
  const action = form.getAttribute('action');
3982
4455
  if (!action || action === '' || action === '#') {
3983
- logger$8.log('Allowed same-page form');
4456
+ logger$2.log('Allowed same-page form');
3984
4457
  e.preventDefault();
3985
4458
  const data = {};
3986
4459
  new FormData(form).forEach((value, key) => {
@@ -3989,7 +4462,7 @@ function setupFormBlocker(doc, isEnabled, onBlocked, onFormSubmit) {
3989
4462
  onFormSubmit(form, data);
3990
4463
  return;
3991
4464
  }
3992
- logger$8.log('Blocked form submission to:', action);
4465
+ logger$2.log('Blocked form submission to:', action);
3993
4466
  e.preventDefault();
3994
4467
  e.stopPropagation();
3995
4468
  e.stopImmediatePropagation();
@@ -4003,7 +4476,7 @@ function setupWindowOpenBlocker(win, originalOpen, isEnabled, onBlocked) {
4003
4476
  if (!isEnabled())
4004
4477
  return originalOpen(...args);
4005
4478
  const url = args[0]?.toString() || 'popup';
4006
- logger$8.log('Blocked window.open:', url);
4479
+ logger$2.log('Blocked window.open:', url);
4007
4480
  onBlocked(url);
4008
4481
  return null;
4009
4482
  });
@@ -4015,14 +4488,14 @@ function setupUnloadBlocker(win, isEnabled) {
4015
4488
  const beforeUnloadListener = (e) => {
4016
4489
  if (!isEnabled())
4017
4490
  return;
4018
- logger$8.log('Blocked beforeunload');
4491
+ logger$2.log('Blocked beforeunload');
4019
4492
  e.preventDefault();
4020
4493
  e.returnValue = '';
4021
4494
  };
4022
4495
  const unloadListener = (e) => {
4023
4496
  if (!isEnabled())
4024
4497
  return;
4025
- logger$8.log('Blocked unload');
4498
+ logger$2.log('Blocked unload');
4026
4499
  e.preventDefault();
4027
4500
  e.stopPropagation();
4028
4501
  };
@@ -4039,65 +4512,14 @@ function setupDOMMonitor(doc) {
4039
4512
  return () => observer.disconnect();
4040
4513
  }
4041
4514
 
4042
- /**
4043
- * Window-level event management for the navigation processor.
4044
- *
4045
- * Responsibilities:
4046
- * - Subscribe to events dispatched by this processor (for logging/hooks)
4047
- * - Dispatch navigation events to the parent window
4048
- */
4049
- const logger$7 = createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' });
4050
- // ─── State ────────────────────────────────────────────────────────────────────
4051
- let navigationBlockedListener = null;
4052
- let formSubmitWindowListener = null;
4053
- // ─── Subscriptions ────────────────────────────────────────────────────────────
4054
- function attach$1(debug) {
4055
- logger$7.configure({ enabled: !!debug });
4056
- navigationBlockedListener = (e) => {
4057
- const ev = e;
4058
- logger$7.log('Navigation blocked:', ev.detail.url);
4059
- };
4060
- formSubmitWindowListener = (e) => {
4061
- const ev = e;
4062
- logger$7.log('Form submitted:', ev.detail.data);
4063
- };
4064
- window.addEventListener('iframe-navigation-blocked', navigationBlockedListener);
4065
- window.addEventListener('iframe-form-submit', formSubmitWindowListener);
4066
- }
4067
- function detach$1() {
4068
- if (navigationBlockedListener) {
4069
- window.removeEventListener('iframe-navigation-blocked', navigationBlockedListener);
4070
- navigationBlockedListener = null;
4071
- }
4072
- if (formSubmitWindowListener) {
4073
- window.removeEventListener('iframe-form-submit', formSubmitWindowListener);
4074
- formSubmitWindowListener = null;
4075
- }
4076
- }
4077
- // ─── Dispatchers ─────────────────────────────────────────────────────────────
4078
- function dispatchBlocked(url, showMessage) {
4079
- if (showMessage)
4080
- alert(`Navigation blocked: ${url}`);
4081
- window.dispatchEvent(new CustomEvent('iframe-navigation-blocked', { detail: { url } }));
4082
- }
4083
- function dispatchFormSubmit(form, data) {
4084
- window.dispatchEvent(new CustomEvent('iframe-form-submit', { detail: { form, data } }));
4085
- }
4086
-
4087
4515
  /**
4088
4516
  * Navigation Processor
4089
4517
  * Continuous guard — blocks all navigation attempts within the iframe.
4090
4518
  */
4091
- const logger$6 = createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' });
4092
- // ─── State ────────────────────────────────────────────────────────────────────
4093
- let isEnabled = false;
4094
- let showMessage = false;
4095
- let running$4 = false;
4096
- let cleanups = [];
4097
- // ─── Public API ───────────────────────────────────────────────────────────────
4098
- function start$5(iframe, cfg) {
4099
- if (running$4) {
4100
- logger$6.warn('Blocker is already running. Call stop() first.');
4519
+ // ── Module-level functions ────────────────────────────────────────────────────
4520
+ function start$4(s, iframe, cfg) {
4521
+ if (s.running) {
4522
+ s.logger.warn('Blocker is already running. Call stop() first.');
4101
4523
  return;
4102
4524
  }
4103
4525
  if (!iframe.contentDocument || !iframe.contentWindow) {
@@ -4106,76 +4528,114 @@ function start$5(iframe, cfg) {
4106
4528
  const doc = iframe.contentDocument;
4107
4529
  const win = iframe.contentWindow;
4108
4530
  const originalOpen = win.open.bind(win);
4109
- logger$6.configure({ enabled: !!cfg?.debug });
4531
+ s.logger.configure({ enabled: !!cfg?.debug });
4110
4532
  configure$1(!!cfg?.debug);
4111
- cleanups = [
4112
- setupLinkBlocker(doc, () => isEnabled, (url) => dispatchBlocked(url, showMessage)),
4113
- setupFormBlocker(doc, () => isEnabled, (url) => dispatchBlocked(url, showMessage), dispatchFormSubmit),
4114
- setupWindowOpenBlocker(win, originalOpen, () => isEnabled, (url) => dispatchBlocked(url, showMessage)),
4115
- setupUnloadBlocker(win, () => isEnabled),
4533
+ s.cleanups = [
4534
+ setupLinkBlocker(doc, () => s.isEnabled, (url) => s.listeners.dispatchBlocked(url, s.showMessage)),
4535
+ setupFormBlocker(doc, () => s.isEnabled, (url) => s.listeners.dispatchBlocked(url, s.showMessage), s.listeners.dispatchFormSubmit),
4536
+ setupWindowOpenBlocker(win, originalOpen, () => s.isEnabled, (url) => s.listeners.dispatchBlocked(url, s.showMessage)),
4537
+ setupUnloadBlocker(win, () => s.isEnabled),
4116
4538
  setupDOMMonitor(doc),
4117
4539
  ];
4118
- attach$1(cfg?.debug);
4119
- running$4 = true;
4120
- logger$6.log('Navigation blocker started');
4540
+ s.listeners.attach(cfg?.debug);
4541
+ s.running = true;
4542
+ s.logger.log('Navigation blocker started');
4543
+ }
4544
+ function stop$4(s) {
4545
+ if (!s.running)
4546
+ return;
4547
+ s.cleanups.forEach((fn) => fn());
4548
+ s.cleanups = [];
4549
+ s.listeners.detach();
4550
+ s.isEnabled = false;
4551
+ s.showMessage = false;
4552
+ s.running = false;
4553
+ s.logger.log('Navigation blocker stopped');
4554
+ }
4555
+ function enable(s) {
4556
+ if (!s.running) {
4557
+ s.logger.warn('Blocker is not running.');
4558
+ return;
4559
+ }
4560
+ s.isEnabled = true;
4561
+ s.logger.log('Navigation blocking enabled');
4121
4562
  }
4122
- function stop$5() {
4123
- if (!running$4)
4563
+ function disable(s) {
4564
+ if (!s.running) {
4565
+ s.logger.warn('Blocker is not running.');
4124
4566
  return;
4125
- cleanups.forEach((fn) => fn());
4126
- cleanups = [];
4127
- detach$1();
4128
- isEnabled = false;
4129
- showMessage = false;
4130
- running$4 = false;
4131
- logger$6.log('Navigation blocker stopped');
4132
- }
4133
- function enable() {
4134
- if (!running$4) {
4135
- logger$6.warn('Blocker is not running. Call start() first.');
4567
+ }
4568
+ s.isEnabled = false;
4569
+ s.logger.log('Navigation blocking disabled');
4570
+ }
4571
+ function enableMessage(s) {
4572
+ if (!s.running) {
4573
+ s.logger.warn('Blocker is not running.');
4136
4574
  return;
4137
4575
  }
4138
- isEnabled = true;
4139
- logger$6.log('Navigation blocking enabled');
4576
+ s.showMessage = true;
4577
+ s.logger.log('Navigation blocking message enabled');
4578
+ }
4579
+ function disableMessage(s) {
4580
+ if (!s.running) {
4581
+ s.logger.warn('Blocker is not running.');
4582
+ return;
4583
+ }
4584
+ s.showMessage = false;
4585
+ s.logger.log('Navigation blocking message disabled');
4586
+ }
4587
+ // ── Factory ───────────────────────────────────────────────────────────────────
4588
+ function createNavigationBlocker() {
4589
+ const s = {
4590
+ logger: createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' }),
4591
+ listeners: createNavigationListeners(),
4592
+ isEnabled: false,
4593
+ showMessage: false,
4594
+ running: false,
4595
+ cleanups: [],
4596
+ };
4597
+ return {
4598
+ start: (iframe, cfg) => start$4(s, iframe, cfg),
4599
+ stop: () => stop$4(s),
4600
+ enable: () => enable(s),
4601
+ disable: () => disable(s),
4602
+ enableMessage: () => enableMessage(s),
4603
+ disableMessage: () => disableMessage(s),
4604
+ isRunning: () => s.running,
4605
+ isBlockingEnabled: () => s.isEnabled,
4606
+ getStateInfo: () => ({ isRunning: s.running, isEnabled: s.isEnabled, showMessage: s.showMessage }),
4607
+ };
4140
4608
  }
4141
4609
 
4142
- const registry$1 = [];
4143
- /**
4144
- * Register a global fix.
4145
- * Fixes are run in registration order.
4146
- */
4147
- function register$1(fix) {
4148
- registry$1.push(fix);
4610
+ // ── Module-level functions ────────────────────────────────────────────────────
4611
+ function attach(s, debug) {
4612
+ s.logger.configure({ enabled: !!debug });
4613
+ s.dimensionsListener = (e) => {
4614
+ const ev = e;
4615
+ s.logger.log('Dimensions applied:', ev.detail);
4616
+ };
4617
+ window.addEventListener('iframe-dimensions-applied', s.dimensionsListener);
4149
4618
  }
4150
- /**
4151
- * Returns all fixes that are active for the given context.
4152
- * A fix is active if it has no `shouldApply`, or `shouldApply` returns true.
4153
- */
4154
- function getActiveFixes(ctx) {
4155
- return registry$1.filter((fix) => {
4156
- try {
4157
- return !fix.shouldApply || fix.shouldApply(ctx);
4158
- }
4159
- catch {
4160
- return false;
4161
- }
4162
- });
4619
+ function detach(s) {
4620
+ if (s.dimensionsListener) {
4621
+ window.removeEventListener('iframe-dimensions-applied', s.dimensionsListener);
4622
+ s.dimensionsListener = null;
4623
+ }
4624
+ }
4625
+ // ── Factory ───────────────────────────────────────────────────────────────────
4626
+ function createViewportListeners() {
4627
+ const s = {
4628
+ logger: createLogger({ enabled: false, prefix: 'ViewportReplacer' }),
4629
+ dimensionsListener: null,
4630
+ };
4631
+ return {
4632
+ attach: (debug) => attach(s, debug),
4633
+ detach: () => detach(s),
4634
+ };
4163
4635
  }
4164
4636
 
4165
- /**
4166
- * Computed Style Enforcer Module
4167
- * Enforces computed CSS styles with viewport unit verification
4168
- * @module computed-style-enforcer
4169
- */
4170
- const logger$5 = createLogger({
4171
- enabled: false,
4172
- prefix: 'ComputedStyleEnforcer',
4173
- });
4174
- // ============================================================================
4175
- // Constants
4176
- // ============================================================================
4177
4637
  const DEFAULT_TOLERANCE_PX = 5;
4178
- const VIEWPORT_UNIT_REGEX = /([-.\d]+)(vh|svh|lvh|dvh|%)/gi;
4638
+ const VIEWPORT_UNIT_REGEX = /([-.\\d]+)(vh|svh|lvh|dvh|%)/gi;
4179
4639
  const DEFAULT_CSS_VALUES = ['none', 'auto', 'normal', '0px'];
4180
4640
  const CRITICAL_PROPERTIES = [
4181
4641
  'display',
@@ -4212,235 +4672,260 @@ const CRITICAL_PROPERTIES = [
4212
4672
  'grid-template-rows',
4213
4673
  'gap',
4214
4674
  ];
4215
- // ============================================================================
4216
- // State
4217
- // ============================================================================
4218
- let doc$1 = null;
4219
- let win$1 = null;
4220
- let config$2 = null;
4221
- const elementsWithViewportUnits$1 = new Set();
4222
- let originalValues$1 = new WeakMap();
4223
- let running$3 = false;
4224
- // ============================================================================
4225
- // Helper Functions
4226
- // ============================================================================
4227
- /**
4228
- * Get viewport unit map for conversion from current state
4229
- */
4230
- function getViewportUnitMap() {
4231
- if (!config$2) {
4675
+
4676
+ // ── Helpers ───────────────────────────────────────────────────────────────────
4677
+ function getViewportUnitMap(s) {
4678
+ if (!s.config)
4232
4679
  throw new Error('Config is not initialized');
4233
- }
4234
4680
  return {
4235
- vh: config$2.targetHeight,
4236
- svh: config$2.targetHeight,
4237
- lvh: config$2.targetHeight,
4238
- dvh: config$2.targetHeight,
4239
- vw: config$2.targetWidth,
4240
- svw: config$2.targetWidth,
4241
- lvw: config$2.targetWidth,
4242
- dvw: config$2.targetWidth,
4681
+ vh: s.config.targetHeight,
4682
+ svh: s.config.targetHeight,
4683
+ lvh: s.config.targetHeight,
4684
+ dvh: s.config.targetHeight,
4685
+ vw: s.config.targetWidth,
4686
+ svw: s.config.targetWidth,
4687
+ lvw: s.config.targetWidth,
4688
+ dvw: s.config.targetWidth,
4243
4689
  };
4244
4690
  }
4245
- /**
4246
- * Calculate expected pixel value from viewport unit using current state
4247
- */
4248
- function calculateExpectedPx(value, unit) {
4249
- if (!config$2) {
4691
+ function calculateExpectedPx(s, value, unit) {
4692
+ if (!s.config)
4250
4693
  throw new Error('Config is not initialized');
4251
- }
4252
4694
  const unitLower = unit.toLowerCase();
4253
- if (unitLower === '%') {
4254
- return (value / 100) * config$2.targetHeight;
4255
- }
4256
- const unitMap = getViewportUnitMap();
4257
- return (value / 100) * (unitMap[unitLower] || 0);
4695
+ if (unitLower === '%')
4696
+ return (value / 100) * s.config.targetHeight;
4697
+ return (value / 100) * (getViewportUnitMap(s)[unitLower] || 0);
4258
4698
  }
4259
- /**
4260
- * Check if a CSS value is a default/initial value that should be skipped
4261
- */
4262
4699
  function isDefaultCssValue(value) {
4263
4700
  return DEFAULT_CSS_VALUES.includes(value);
4264
4701
  }
4265
- /**
4266
- * Verify if we should replace the computed value using current state
4267
- * Return true ONLY if computed value matches target config (within tolerance)
4268
- * Return false if computed value is different - don't override correct values
4269
- */
4270
- function shouldReplaceValue(computedValue, originalValue, tolerance = DEFAULT_TOLERANCE_PX) {
4271
- if (!config$2) {
4702
+ function shouldReplaceValue(s, computedValue, originalValue, tolerance = DEFAULT_TOLERANCE_PX) {
4703
+ if (!s.config)
4272
4704
  return false;
4273
- }
4274
- // Parse computed value (should be in px)
4275
4705
  const computedPx = parseFloat(computedValue);
4276
- if (isNaN(computedPx)) {
4277
- return false; // Cannot verify, don't replace
4278
- }
4279
- // Parse original value to check what it should be
4706
+ if (isNaN(computedPx))
4707
+ return false;
4280
4708
  const regex = new RegExp(VIEWPORT_UNIT_REGEX.source, VIEWPORT_UNIT_REGEX.flags);
4281
4709
  const match = originalValue.match(regex);
4282
- if (!match) {
4283
- return false; // No viewport units found, don't replace
4284
- }
4710
+ if (!match)
4711
+ return false;
4285
4712
  const [, value, unit] = match;
4286
4713
  const num = parseFloat(value);
4287
- if (isNaN(num)) {
4714
+ if (isNaN(num))
4288
4715
  return false;
4289
- }
4290
- // Calculate expected value based on unit and target config
4291
- const expectedPx = calculateExpectedPx(num, unit);
4292
- // Check if computed value matches expected value (within tolerance)
4716
+ const expectedPx = calculateExpectedPx(s, num, unit);
4293
4717
  const diff = Math.abs(computedPx - expectedPx);
4294
4718
  if (diff <= tolerance) {
4295
- // Matches target config, OK to replace
4296
- logger$5.log(`OK to replace: computed=${computedValue} matches expected=${expectedPx.toFixed(2)}px (diff=${diff.toFixed(2)}px)`);
4719
+ s.logger.log(`OK to replace: computed=${computedValue} matches expected=${expectedPx.toFixed(2)}px (diff=${diff.toFixed(2)}px)`);
4297
4720
  return true;
4298
4721
  }
4299
- // Different from target config, DON'T replace - value already has correct computation
4300
- logger$5.log(`Skip replace: computed=${computedValue} differs from expected=${expectedPx.toFixed(2)}px (diff=${diff.toFixed(2)}px) - keeping current value`);
4722
+ s.logger.log(`Skip replace: computed=${computedValue} differs from expected=${expectedPx.toFixed(2)}px (diff=${diff.toFixed(2)}px)`);
4301
4723
  return false;
4302
4724
  }
4303
- /**
4304
- * Apply a single property to an element with verification using current state
4305
- */
4306
- function applyPropertyWithVerification(element, prop, computedValue, originalValue, tolerance = DEFAULT_TOLERANCE_PX) {
4307
- // Verify before replacing - only replace if computed value matches target config
4308
- if (originalValue && shouldReplaceValue(computedValue, originalValue, tolerance)) {
4725
+ function applyPropertyWithVerification(s, element, prop, computedValue, originalValue, tolerance = DEFAULT_TOLERANCE_PX) {
4726
+ if (originalValue && shouldReplaceValue(s, computedValue, originalValue, tolerance)) {
4309
4727
  element.style.setProperty(prop, computedValue, 'important');
4310
4728
  return true;
4311
4729
  }
4312
4730
  else if (!originalValue) {
4313
- // No original value tracked, use old behavior
4314
4731
  element.style.setProperty(prop, computedValue, 'important');
4315
4732
  return true;
4316
4733
  }
4317
4734
  return false;
4318
4735
  }
4319
- // ============================================================================
4320
- // Public API Functions
4321
- // ============================================================================
4322
- /**
4323
- * Start the computed style enforcer
4324
- * Initialize with iframe document and config
4325
- */
4326
- function start$4(d, w, cfg, options = {}) {
4327
- if (running$3) {
4328
- logger$5.warn('Enforcer is already running. Call stop() first.');
4736
+ // ── Exported module-level functions ───────────────────────────────────────────
4737
+ function start$3(s, d, w, cfg, options = {}) {
4738
+ if (s.running) {
4739
+ s.logger.warn('Enforcer is already running. Call stop() first.');
4329
4740
  return;
4330
4741
  }
4331
- doc$1 = d;
4332
- win$1 = w;
4333
- config$2 = cfg;
4334
- running$3 = true;
4335
- logger$5.configure({ enabled: !!options.debug });
4336
- logger$5.log('Computed style enforcer started');
4742
+ s.doc = d;
4743
+ s.win = w;
4744
+ s.config = cfg;
4745
+ s.running = true;
4746
+ s.logger.configure({ enabled: !!options.debug });
4747
+ s.logger.log('Computed style enforcer started');
4337
4748
  }
4338
- /**
4339
- * Stop the enforcer and clear state
4340
- */
4341
- function stop$4() {
4342
- if (!running$3) {
4749
+ function stop$3(s) {
4750
+ if (!s.running)
4751
+ return;
4752
+ s.doc = null;
4753
+ s.win = null;
4754
+ s.config = null;
4755
+ s.elementsWithViewportUnits.clear();
4756
+ s.originalValues = new WeakMap();
4757
+ s.running = false;
4758
+ s.logger.log('Computed style enforcer stopped');
4759
+ }
4760
+ function reset(s) {
4761
+ if (!s.running) {
4762
+ s.logger.warn('Enforcer is not running. Call start() first.');
4343
4763
  return;
4344
4764
  }
4345
- doc$1 = null;
4346
- win$1 = null;
4347
- config$2 = null;
4348
- elementsWithViewportUnits$1.clear();
4349
- originalValues$1 = new WeakMap();
4350
- running$3 = false;
4351
- logger$5.log('Computed style enforcer stopped');
4765
+ s.elementsWithViewportUnits.clear();
4766
+ s.originalValues = new WeakMap();
4767
+ s.logger.log('Computed style enforcer reset');
4352
4768
  }
4353
- /**
4354
- * Track an element with viewport units
4355
- * Store original CSS values for later verification
4356
- */
4357
- function trackElement(element, propertyOriginalValues) {
4358
- if (!running$3) {
4359
- logger$5.warn('Enforcer is not running. Call start() first.');
4769
+ function trackElement(s, element, propertyOriginalValues) {
4770
+ if (!s.running) {
4771
+ s.logger.warn('Enforcer is not running. Call start() first.');
4360
4772
  return;
4361
4773
  }
4362
- elementsWithViewportUnits$1.add(element);
4363
- // Store original values for this element
4364
- let elementOriginals = originalValues$1.get(element);
4774
+ s.elementsWithViewportUnits.add(element);
4775
+ let elementOriginals = s.originalValues.get(element);
4365
4776
  if (!elementOriginals) {
4366
4777
  elementOriginals = new Map();
4367
- originalValues$1.set(element, elementOriginals);
4778
+ s.originalValues.set(element, elementOriginals);
4368
4779
  }
4369
- // Merge property original values (don't override existing)
4370
4780
  propertyOriginalValues.forEach((value, prop) => {
4371
- if (!elementOriginals.has(prop)) {
4781
+ if (!elementOriginals.has(prop))
4372
4782
  elementOriginals.set(prop, value);
4373
- }
4374
4783
  });
4375
4784
  }
4376
- /**
4377
- * Process a single element - enforce computed styles to inline
4378
- */
4379
- function processElement(element, options = {}) {
4380
- if (!running$3 || !doc$1 || !win$1 || !config$2) {
4381
- logger$5.warn('Enforcer is not running. Call start() first.');
4785
+ function processElement(s, element, options = {}) {
4786
+ if (!s.running || !s.doc || !s.win || !s.config) {
4787
+ s.logger.warn('Enforcer is not running.');
4382
4788
  return 0;
4383
4789
  }
4384
- if (!elementsWithViewportUnits$1.has(element)) {
4790
+ if (!s.elementsWithViewportUnits.has(element))
4385
4791
  return 0;
4386
- }
4387
4792
  const htmlElement = element;
4388
- const computed = win$1.getComputedStyle(htmlElement);
4793
+ const computed = s.win.getComputedStyle(htmlElement);
4389
4794
  const inlineStyle = htmlElement.style;
4390
- const elementOriginals = originalValues$1.get(element);
4795
+ const elementOriginals = s.originalValues.get(element);
4391
4796
  const tolerance = options.tolerance ?? DEFAULT_TOLERANCE_PX;
4392
4797
  let count = 0;
4393
4798
  CRITICAL_PROPERTIES.forEach((prop) => {
4394
4799
  const computedValue = computed.getPropertyValue(prop);
4395
4800
  const inlineValue = inlineStyle.getPropertyValue(prop);
4396
- if (computedValue && (!inlineValue || inlineValue !== computedValue)) {
4397
- if (!isDefaultCssValue(computedValue)) {
4398
- const originalValue = elementOriginals?.get(prop) || '';
4399
- if (applyPropertyWithVerification(htmlElement, prop, computedValue, originalValue, tolerance)) {
4400
- count++;
4401
- }
4402
- }
4801
+ if (computedValue && (!inlineValue || inlineValue !== computedValue) && !isDefaultCssValue(computedValue)) {
4802
+ const originalValue = elementOriginals?.get(prop) || '';
4803
+ if (applyPropertyWithVerification(s, htmlElement, prop, computedValue, originalValue, tolerance))
4804
+ count++;
4403
4805
  }
4404
4806
  });
4405
4807
  return count;
4406
4808
  }
4407
- /**
4408
- * Process all tracked elements
4409
- * Enforce computed styles to inline for all elements with viewport units
4410
- */
4411
- function processAll(options = {}) {
4412
- if (!running$3 || !doc$1 || !win$1 || !config$2) {
4413
- logger$5.warn('Enforcer is not running. Call start() first.');
4809
+ function processAll(s, options = {}) {
4810
+ if (!s.running || !s.doc || !s.win || !s.config) {
4811
+ s.logger.warn('Enforcer is not running.');
4414
4812
  return 0;
4415
4813
  }
4416
4814
  let totalCount = 0;
4417
- elementsWithViewportUnits$1.forEach((element) => {
4418
- totalCount += processElement(element, options);
4815
+ s.elementsWithViewportUnits.forEach((element) => {
4816
+ totalCount += processElement(s, element, options);
4419
4817
  });
4420
- logger$5.log(`Enforced ${totalCount} computed styles for ${elementsWithViewportUnits$1.size} elements`);
4818
+ s.logger.log(`Enforced ${totalCount} computed styles for ${s.elementsWithViewportUnits.size} elements`);
4421
4819
  return totalCount;
4422
4820
  }
4821
+ function updateConfig$2(s, cfg) {
4822
+ if (!s.running || !s.config) {
4823
+ s.logger.warn('Enforcer is not running.');
4824
+ return;
4825
+ }
4826
+ s.config = { ...s.config, ...cfg };
4827
+ s.logger.log('Config updated:', cfg);
4828
+ }
4829
+
4830
+ /**
4831
+ * Computed Style Enforcer
4832
+ * Enforces computed CSS styles with viewport unit verification.
4833
+ */
4834
+ // ── Factory ───────────────────────────────────────────────────────────────────
4835
+ function createEnforcer() {
4836
+ const s = {
4837
+ logger: createLogger({ enabled: false, prefix: 'ComputedStyleEnforcer' }),
4838
+ doc: null,
4839
+ win: null,
4840
+ config: null,
4841
+ elementsWithViewportUnits: new Set(),
4842
+ originalValues: new WeakMap(),
4843
+ running: false,
4844
+ };
4845
+ return {
4846
+ start: (d, w, cfg, options) => start$3(s, d, w, cfg, options),
4847
+ stop: () => stop$3(s),
4848
+ reset: () => reset(s),
4849
+ trackElement: (element, propertyOriginalValues) => trackElement(s, element, propertyOriginalValues),
4850
+ processElement: (element, options) => processElement(s, element, options),
4851
+ processAll: (options) => processAll(s, options),
4852
+ updateConfig: (cfg) => updateConfig$2(s, cfg),
4853
+ getStateInfo: () => ({
4854
+ isRunning: s.running,
4855
+ trackedElementsCount: s.elementsWithViewportUnits.size,
4856
+ hasConfig: !!s.config,
4857
+ }),
4858
+ isRunning: () => s.running,
4859
+ };
4860
+ }
4861
+
4862
+ const registry$1 = [];
4863
+ /**
4864
+ * Register a global fix.
4865
+ * Fixes are run in registration order.
4866
+ */
4867
+ function register$1(fix) {
4868
+ registry$1.push(fix);
4869
+ }
4870
+ /**
4871
+ * Returns all fixes that are active for the given context.
4872
+ * A fix is active if it has no `shouldApply`, or `shouldApply` returns true.
4873
+ */
4874
+ function getActiveFixes(ctx) {
4875
+ return registry$1.filter((fix) => {
4876
+ try {
4877
+ return !fix.shouldApply || fix.shouldApply(ctx);
4878
+ }
4879
+ catch {
4880
+ return false;
4881
+ }
4882
+ });
4883
+ }
4423
4884
 
4424
4885
  /**
4425
4886
  * Core viewport unit replacement logic.
4426
4887
  * Converts vh/vw/svh/dvh/% to pixel values across all CSS in the iframe.
4427
4888
  */
4428
- const logger$4 = createLogger({ enabled: false, prefix: 'ViewportUnitReplacer' });
4889
+ const logger$1 = createLogger({ enabled: false, prefix: 'ViewportUnitReplacer' });
4429
4890
  // ─── Constants ────────────────────────────────────────────────────────────────
4430
4891
  const HEIGHT_RELATED_PROPERTIES = ['height', 'min-height', 'max-height', 'top', 'bottom'];
4892
+ /**
4893
+ * Number of top-level CSS rules to process before yielding to the browser.
4894
+ * Keeps the main thread responsive during large stylesheets (prevents tab kill on mobile).
4895
+ */
4896
+ const YIELD_EVERY_RULES = 100;
4897
+ // ─── Scheduler ────────────────────────────────────────────────────────────────
4898
+ /**
4899
+ * Yield control back to the browser so it can handle input, paint frames, and
4900
+ * avoid "page unresponsive" / tab-kill on mobile during heavy CSS processing.
4901
+ *
4902
+ * Uses `scheduler.yield()` (Chrome 115+) when available; falls back to a
4903
+ * zero-timeout macrotask which is universally supported.
4904
+ */
4905
+ function yieldToMain() {
4906
+ if (typeof globalThis !== 'undefined' && 'scheduler' in globalThis) {
4907
+ return globalThis.scheduler.yield();
4908
+ }
4909
+ return new Promise((resolve) => setTimeout(resolve, 0));
4910
+ }
4431
4911
  // ─── Per-run tracking state (reset on each process() call) ───────────────────
4432
4912
  let elementsWithViewportUnits = new Set();
4433
4913
  let originalValues = new WeakMap();
4434
4914
  // ─── Regex ────────────────────────────────────────────────────────────────────
4435
- /** Fresh instance every call — avoids shared lastIndex state with the g flag. */
4915
+ /**
4916
+ * Stateless test-only regex (no `g` flag) — safe to share across calls.
4917
+ * Used exclusively for `.test()` checks before doing a full replacement.
4918
+ */
4919
+ const VIEWPORT_RE_TEST = /([-.?\d]+)(vh|svh|lvh|dvh|vw|svw|lvw|dvw)/i;
4920
+ /** Fresh `g`-flagged instance for String.replace() callbacks. */
4436
4921
  function createRegex() {
4437
- return /([-.\\d]+)(vh|svh|lvh|dvh|vw|svw|lvw|dvw)/gi;
4922
+ return /([-.?\d]+)(vh|svh|lvh|dvh|vw|svw|lvw|dvw)/gi;
4438
4923
  }
4439
4924
  // ─── Unit conversion ─────────────────────────────────────────────────────────
4440
4925
  function px(value) {
4441
4926
  return `${value.toFixed(2)}px`;
4442
4927
  }
4443
- function getUnitMap(ctx) {
4928
+ function buildUnitMap(ctx) {
4444
4929
  return {
4445
4930
  vh: ctx.targetHeight,
4446
4931
  svh: ctx.targetHeight,
@@ -4452,15 +4937,15 @@ function getUnitMap(ctx) {
4452
4937
  dvw: ctx.targetWidth,
4453
4938
  };
4454
4939
  }
4455
- function toPx(value, unit, ctx) {
4940
+ function toPx(value, unit, unitMap, targetHeight) {
4456
4941
  const u = unit.toLowerCase();
4457
4942
  if (u === '%')
4458
- return (value / 100) * ctx.targetHeight;
4459
- return (value / 100) * (getUnitMap(ctx)[u] ?? 0);
4943
+ return (value / 100) * targetHeight;
4944
+ return (value / 100) * (unitMap[u] ?? 0);
4460
4945
  }
4461
- function convert(value, unit, ctx) {
4946
+ function convert(value, unit, unitMap, targetHeight) {
4462
4947
  const num = parseFloat(value);
4463
- return isNaN(num) ? value : px(toPx(num, unit, ctx));
4948
+ return isNaN(num) ? value : px(toPx(num, unit, unitMap, targetHeight));
4464
4949
  }
4465
4950
  function isHeightRelated(prop) {
4466
4951
  return HEIGHT_RELATED_PROPERTIES.includes(prop);
@@ -4475,11 +4960,13 @@ function extractProperty(cssText, matchOffset) {
4475
4960
  return m ? m[1].toLowerCase() : '';
4476
4961
  }
4477
4962
  function replaceInText(cssText, ctx) {
4963
+ const unitMap = buildUnitMap(ctx);
4964
+ const { targetHeight } = ctx;
4478
4965
  return cssText.replace(createRegex(), (match, value, unit, offset) => {
4479
4966
  if (unit === '%') {
4480
- return isHeightRelated(extractProperty(cssText, offset)) ? convert(value, unit, ctx) : match;
4967
+ return isHeightRelated(extractProperty(cssText, offset)) ? convert(value, unit, unitMap, targetHeight) : match;
4481
4968
  }
4482
- return convert(value, unit, ctx);
4969
+ return convert(value, unit, unitMap, targetHeight);
4483
4970
  });
4484
4971
  }
4485
4972
  // ─── Element tracking ─────────────────────────────────────────────────────────
@@ -4496,11 +4983,11 @@ function trackSelector(selector, propOriginals, ctx) {
4496
4983
  if (!originals.has(k))
4497
4984
  originals.set(k, v);
4498
4985
  });
4499
- trackElement(el, propOriginals);
4986
+ ctx.enforcer?.trackElement(el, propOriginals);
4500
4987
  });
4501
4988
  }
4502
4989
  catch {
4503
- logger$4.warn('Invalid selector, skipping:', selector);
4990
+ logger$1.warn('Invalid selector, skipping:', selector);
4504
4991
  }
4505
4992
  }
4506
4993
  // ─── CSS processing ───────────────────────────────────────────────────────────
@@ -4508,25 +4995,25 @@ function processInlineStyles(ctx) {
4508
4995
  let count = 0;
4509
4996
  ctx.doc.querySelectorAll('[style]').forEach((el) => {
4510
4997
  const style = el.getAttribute('style');
4511
- if (style && createRegex().test(style)) {
4998
+ if (style && VIEWPORT_RE_TEST.test(style)) {
4512
4999
  elementsWithViewportUnits.add(el);
4513
5000
  el.setAttribute('style', replaceInText(style, ctx));
4514
5001
  count++;
4515
5002
  }
4516
5003
  });
4517
- logger$4.log(`Replaced ${count} inline style elements`);
5004
+ logger$1.log(`Replaced ${count} inline style elements`);
4518
5005
  return count;
4519
5006
  }
4520
5007
  function processStyleTags(ctx) {
4521
5008
  let count = 0;
4522
5009
  ctx.doc.querySelectorAll('style').forEach((tag) => {
4523
5010
  const css = tag.textContent || '';
4524
- if (createRegex().test(css)) {
5011
+ if (VIEWPORT_RE_TEST.test(css)) {
4525
5012
  tag.textContent = replaceInText(css, ctx);
4526
5013
  count++;
4527
5014
  }
4528
5015
  });
4529
- logger$4.log(`Replaced ${count} <style> tags`);
5016
+ logger$1.log(`Replaced ${count} <style> tags`);
4530
5017
  return count;
4531
5018
  }
4532
5019
  function processRule(rule, ctx) {
@@ -4539,7 +5026,7 @@ function processRule(rule, ctx) {
4539
5026
  for (let i = 0; i < style.length; i++) {
4540
5027
  const prop = style[i];
4541
5028
  const value = style.getPropertyValue(prop);
4542
- if (value && createRegex().test(value)) {
5029
+ if (value && VIEWPORT_RE_TEST.test(value)) {
4543
5030
  hasVp = true;
4544
5031
  propOriginals.set(prop, value);
4545
5032
  style.setProperty(prop, replaceInText(value, ctx), style.getPropertyPriority(prop));
@@ -4550,43 +5037,56 @@ function processRule(rule, ctx) {
4550
5037
  trackSelector(cssRule.selectorText, propOriginals, ctx);
4551
5038
  }
4552
5039
  if ('cssRules' in rule) {
4553
- for (const r of Array.from(rule.cssRules || [])) {
4554
- count += processRule(r, ctx);
5040
+ const nested = rule.cssRules;
5041
+ if (nested) {
5042
+ for (let i = 0; i < nested.length; i++) {
5043
+ count += processRule(nested[i], ctx);
5044
+ }
4555
5045
  }
4556
5046
  }
4557
5047
  return count;
4558
5048
  }
4559
5049
  /** Processes only inline <style> sheets. Linked sheets are handled by processLinkedStylesheets. */
4560
- function processStylesheets(ctx) {
5050
+ async function processStylesheets(ctx) {
4561
5051
  let total = 0;
4562
- Array.from(ctx.doc.styleSheets).forEach((sheet) => {
5052
+ let rulesSinceYield = 0;
5053
+ const sheets = ctx.doc.styleSheets;
5054
+ for (let i = 0; i < sheets.length; i++) {
5055
+ const sheet = sheets[i];
4563
5056
  if (sheet.href)
4564
- return; // deferred to processLinkedStylesheets
5057
+ continue; // deferred to processLinkedStylesheets
4565
5058
  try {
4566
- for (const rule of Array.from(sheet.cssRules || [])) {
4567
- total += processRule(rule, ctx);
5059
+ const rules = sheet.cssRules;
5060
+ for (let j = 0; j < rules.length; j++) {
5061
+ total += processRule(rules[j], ctx);
5062
+ rulesSinceYield++;
5063
+ if (rulesSinceYield >= YIELD_EVERY_RULES) {
5064
+ rulesSinceYield = 0;
5065
+ await yieldToMain();
5066
+ }
4568
5067
  }
4569
5068
  }
4570
5069
  catch (e) {
4571
- logger$4.warn('Cannot read stylesheet (CORS?):', e.message);
5070
+ logger$1.warn('Cannot read stylesheet (CORS?):', e.message);
4572
5071
  }
4573
- });
4574
- logger$4.log(`Replaced ${total} rules in inline stylesheets`);
5072
+ }
5073
+ logger$1.log(`Replaced ${total} rules in inline stylesheets`);
4575
5074
  return total;
4576
5075
  }
4577
5076
  async function processLinkedStylesheets(ctx) {
4578
5077
  const links = ctx.doc.querySelectorAll('link[rel="stylesheet"]');
4579
5078
  let count = 0;
4580
- for (const link of Array.from(links)) {
5079
+ for (let i = 0; i < links.length; i++) {
5080
+ const link = links[i];
4581
5081
  // Skip cross-origin — already in browser CSSOM, handled via processStylesheets
4582
5082
  if (link.href && !link.href.startsWith(ctx.win.location.origin)) {
4583
- logger$4.log('Skipping cross-origin CSS:', link.href);
5083
+ logger$1.log('Skipping cross-origin CSS:', link.href);
4584
5084
  continue;
4585
5085
  }
4586
5086
  try {
4587
5087
  const res = await fetch(link.href);
4588
5088
  let css = await res.text();
4589
- if (createRegex().test(css)) {
5089
+ if (VIEWPORT_RE_TEST.test(css)) {
4590
5090
  css = replaceInText(css, ctx);
4591
5091
  const style = ctx.doc.createElement('style');
4592
5092
  style.textContent = css;
@@ -4597,27 +5097,27 @@ async function processLinkedStylesheets(ctx) {
4597
5097
  }
4598
5098
  }
4599
5099
  catch (e) {
4600
- logger$4.warn('Cannot load CSS:', link.href, e);
5100
+ logger$1.warn('Cannot load CSS:', link.href, e);
4601
5101
  }
4602
5102
  }
4603
- logger$4.log(`Replaced ${count} linked CSS files`);
5103
+ logger$1.log(`Replaced ${count} linked CSS files`);
4604
5104
  return count;
4605
5105
  }
4606
5106
  // ─── Public entry point ───────────────────────────────────────────────────────
4607
5107
  async function process$1(ctx) {
4608
- logger$4.configure({ enabled: !!ctx.debug });
5108
+ logger$1.configure({ enabled: !!ctx.debug });
4609
5109
  // Reset tracking state from any previous run
4610
5110
  elementsWithViewportUnits = new Set();
4611
5111
  originalValues = new WeakMap();
4612
5112
  processInlineStyles(ctx);
4613
5113
  processStyleTags(ctx);
4614
- processStylesheets(ctx);
5114
+ await processStylesheets(ctx);
4615
5115
  await processLinkedStylesheets(ctx);
4616
5116
  // Wait for browser to apply the replaced styles
4617
5117
  await new Promise((resolve) => requestAnimationFrame(resolve));
4618
5118
  // Enforce final computed styles to inline with !important
4619
- const count = processAll({ debug: !!ctx.debug });
4620
- logger$4.log(`Enforced ${count} computed styles for ${elementsWithViewportUnits.size} tracked elements`);
5119
+ const count = ctx.enforcer?.processAll({ debug: !!ctx.debug }) ?? 0;
5120
+ logger$1.log(`Enforced ${count} computed styles for ${elementsWithViewportUnits.size} tracked elements`);
4621
5121
  }
4622
5122
 
4623
5123
  /**
@@ -4713,6 +5213,9 @@ function getMaxWByDeviceType(deviceType) {
4713
5213
  // ─── Main fix ─────────────────────────────────────────────────────────────────
4714
5214
  function afterProcess$1({ doc, targetWidth, deviceType }) {
4715
5215
  doc.querySelectorAll(SLIDER_ITEM_SELECTOR).forEach((item) => {
5216
+ // When !gp-min-w-full is set, the item fills 100% width via Tailwind — skip manual width override
5217
+ if (item.classList.contains('!gp-min-w-full'))
5218
+ return;
4716
5219
  const originalWidth = parseFloat(item.style.minWidth) || parseFloat(item.style.maxWidth) || 0;
4717
5220
  if (!originalWidth)
4718
5221
  return;
@@ -4813,23 +5316,6 @@ register$1({
4813
5316
  afterProcess,
4814
5317
  });
4815
5318
 
4816
- const logger$3 = createLogger({ enabled: false, prefix: 'ViewportReplacer' });
4817
- let dimensionsListener = null;
4818
- function attach(debug) {
4819
- logger$3.configure({ enabled: !!debug });
4820
- dimensionsListener = (e) => {
4821
- const ev = e;
4822
- logger$3.log('Dimensions applied:', ev.detail);
4823
- };
4824
- window.addEventListener('iframe-dimensions-applied', dimensionsListener);
4825
- }
4826
- function detach() {
4827
- if (dimensionsListener) {
4828
- window.removeEventListener('iframe-dimensions-applied', dimensionsListener);
4829
- dimensionsListener = null;
4830
- }
4831
- }
4832
-
4833
5319
  /**
4834
5320
  * Default iframe dimension calculation.
4835
5321
  * Used as fallback when no fix overrides getDimensions().
@@ -4853,41 +5339,58 @@ function getFinalWidth(doc) {
4853
5339
  * Phase 3: afterProcess (shop → global)
4854
5340
  * Phase 4: getDimensions (shop → global → default)
4855
5341
  */
4856
- const logger$2 = createLogger({ enabled: false, prefix: 'ViewportReplacer' });
5342
+ const logger = createLogger({ enabled: false, prefix: 'ViewportReplacer' });
4857
5343
  function configure(debug) {
4858
- logger$2.configure({ enabled: debug });
5344
+ logger.configure({ enabled: debug });
4859
5345
  }
4860
5346
  async function run$1(ctx, activeGlobal, shopFix) {
4861
5347
  // ── Phase 1: beforeProcess ────────────────────────────────────────────────
5348
+ const t1 = perf.mark('phase1.beforeProcess');
4862
5349
  for (const fix of activeGlobal) {
4863
5350
  if (fix.beforeProcess) {
4864
- logger$2.log(`[beforeProcess] ${fix.name}`);
5351
+ logger.log(`[beforeProcess] ${fix.name}`);
5352
+ const t = perf.mark(`phase1.${fix.name}.beforeProcess`);
4865
5353
  await fix.beforeProcess(ctx);
5354
+ perf.measure(`phase1.${fix.name}.beforeProcess`, t);
4866
5355
  }
4867
5356
  }
4868
5357
  if (shopFix?.beforeProcess) {
4869
- logger$2.log('[beforeProcess] shop');
5358
+ logger.log('[beforeProcess] shop');
5359
+ const t = perf.mark('phase1.shop.beforeProcess');
4870
5360
  await shopFix.beforeProcess(ctx);
5361
+ perf.measure('phase1.shop.beforeProcess', t);
4871
5362
  }
5363
+ perf.measure('phase1.beforeProcess', t1);
4872
5364
  // ── Phase 2: process ──────────────────────────────────────────────────────
5365
+ const t2 = perf.mark('phase2.process');
4873
5366
  for (const fix of activeGlobal) {
4874
5367
  if (fix.process) {
4875
- logger$2.log(`[process] ${fix.name}`);
5368
+ logger.log(`[process] ${fix.name}`);
5369
+ const t = perf.mark(`phase2.${fix.name}.process`);
4876
5370
  await fix.process(ctx);
5371
+ perf.measure(`phase2.${fix.name}.process`, t);
4877
5372
  }
4878
5373
  }
5374
+ perf.measure('phase2.process', t2);
4879
5375
  // ── Phase 3: afterProcess ─────────────────────────────────────────────────
5376
+ const t3 = perf.mark('phase3.afterProcess');
4880
5377
  if (shopFix?.afterProcess) {
4881
- logger$2.log('[afterProcess] shop');
5378
+ logger.log('[afterProcess] shop');
5379
+ const t = perf.mark('phase3.shop.afterProcess');
4882
5380
  await shopFix.afterProcess(ctx);
5381
+ perf.measure('phase3.shop.afterProcess', t);
4883
5382
  }
4884
5383
  for (const fix of activeGlobal) {
4885
5384
  if (fix.afterProcess) {
4886
- logger$2.log(`[afterProcess] ${fix.name}`);
5385
+ logger.log(`[afterProcess] ${fix.name}`);
5386
+ const t = perf.mark(`phase3.${fix.name}.afterProcess`);
4887
5387
  await fix.afterProcess(ctx);
5388
+ perf.measure(`phase3.${fix.name}.afterProcess`, t);
4888
5389
  }
4889
5390
  }
5391
+ perf.measure('phase3.afterProcess', t3);
4890
5392
  // ── Phase 4: getDimensions ────────────────────────────────────────────────
5393
+ const t4 = perf.mark('phase4.getDimensions');
4891
5394
  return new Promise((resolve) => {
4892
5395
  requestAnimationFrame(() => {
4893
5396
  let dimensions = null;
@@ -4895,14 +5398,14 @@ async function run$1(ctx, activeGlobal, shopFix) {
4895
5398
  if (shopFix?.getDimensions) {
4896
5399
  dimensions = shopFix.getDimensions(ctx);
4897
5400
  if (dimensions)
4898
- logger$2.log('Dimensions from shop fix:', dimensions);
5401
+ logger.log('Dimensions from shop fix:', dimensions);
4899
5402
  }
4900
5403
  if (!dimensions) {
4901
5404
  for (const fix of activeGlobal) {
4902
5405
  if (fix.getDimensions) {
4903
5406
  dimensions = fix.getDimensions(ctx);
4904
5407
  if (dimensions) {
4905
- logger$2.log(`Dimensions from global fix [${fix.name}]:`, dimensions);
5408
+ logger.log(`Dimensions from global fix [${fix.name}]:`, dimensions);
4906
5409
  break;
4907
5410
  }
4908
5411
  }
@@ -4911,7 +5414,8 @@ async function run$1(ctx, activeGlobal, shopFix) {
4911
5414
  if (!dimensions) {
4912
5415
  dimensions = { height: getFinalHeight(ctx.doc, ctx.win), width: getFinalWidth(ctx.doc) };
4913
5416
  }
4914
- logger$2.log('Final dimensions:', dimensions);
5417
+ logger.log('Final dimensions:', dimensions);
5418
+ perf.measure('phase4.getDimensions', t4);
4915
5419
  resolve(dimensions);
4916
5420
  });
4917
5421
  });
@@ -4988,232 +5492,286 @@ register(`566240210141053597`, {
4988
5492
  afterProcess: restoreAndFixLayout,
4989
5493
  });
4990
5494
 
4991
- const logger$1 = createLogger({ enabled: false, prefix: 'ViewportReplacer' });
4992
- // ─── State ────────────────────────────────────────────────────────────────────
4993
- let doc = null;
4994
- let win = null;
4995
- let config$1 = null;
4996
- let shopFix = null;
4997
- let running$2 = false;
4998
- // ─── Public API ───────────────────────────────────────────────────────────────
4999
- function start$3(iframe, cfg) {
5000
- if (running$2) {
5001
- logger$1.warn('Already running. Call stop() first.');
5495
+ // ── Module-level functions ────────────────────────────────────────────────────
5496
+ function start$2(s, iframe, cfg) {
5497
+ if (s.running) {
5498
+ s.logger.warn('Already running. Call stop() first.');
5002
5499
  return;
5003
5500
  }
5004
5501
  if (!iframe.contentDocument || !iframe.contentWindow) {
5005
5502
  throw new Error('Iframe document or window not accessible');
5006
5503
  }
5007
- doc = iframe.contentDocument;
5008
- win = iframe.contentWindow;
5009
- config$1 = cfg;
5010
- running$2 = true;
5011
- shopFix = cfg.shopId != null ? get(cfg.shopId) : null;
5012
- if (shopFix) {
5013
- logger$1.log(`Shop fix loaded for "${cfg.shopId}":`, shopFix.description ?? '(no description)');
5014
- }
5015
- logger$1.configure({ enabled: !!cfg.debug });
5504
+ s.doc = iframe.contentDocument;
5505
+ s.win = iframe.contentWindow;
5506
+ s.config = cfg;
5507
+ s.running = true;
5508
+ s.shopFix = cfg.shopId != null ? get(cfg.shopId) : null;
5509
+ if (s.shopFix)
5510
+ s.logger.log(`Shop fix loaded for "${cfg.shopId}":`, s.shopFix.description ?? '(no description)');
5511
+ s.logger.configure({ enabled: !!cfg.debug });
5016
5512
  configure(!!cfg.debug);
5017
- start$4(doc, win, cfg, { debug: !!cfg.debug });
5018
- attach(cfg.debug);
5019
- logger$1.log('Started');
5513
+ s.enforcer.start(s.doc, s.win, cfg, { debug: !!cfg.debug });
5514
+ s.listeners.attach(cfg.debug);
5515
+ s.logger.log('Started');
5020
5516
  }
5021
- function stop$3() {
5022
- if (!running$2)
5517
+ function stop$2(s) {
5518
+ if (!s.running)
5023
5519
  return;
5024
- stop$4();
5025
- detach();
5026
- doc = null;
5027
- win = null;
5028
- config$1 = null;
5029
- shopFix = null;
5030
- running$2 = false;
5031
- logger$1.log('Stopped');
5032
- }
5033
- async function run() {
5034
- if (!running$2 || !doc || !win || !config$1) {
5035
- logger$1.warn('Not running. Call start() first.');
5520
+ s.enforcer.stop();
5521
+ s.listeners.detach();
5522
+ s.doc = null;
5523
+ s.win = null;
5524
+ s.config = null;
5525
+ s.shopFix = null;
5526
+ s.running = false;
5527
+ s.logger.log('Stopped');
5528
+ }
5529
+ async function run(s) {
5530
+ if (!s.running || !s.doc || !s.win || !s.config) {
5531
+ s.logger.warn('Not running. Call start() first.');
5036
5532
  return { height: 1000, width: 1000 };
5037
5533
  }
5038
5534
  const ctx = {
5039
- doc,
5040
- win,
5041
- deviceType: config$1.deviceType,
5042
- targetWidth: config$1.targetWidth,
5043
- targetHeight: config$1.targetHeight,
5044
- debug: config$1.debug,
5535
+ doc: s.doc,
5536
+ win: s.win,
5537
+ deviceType: s.config.deviceType,
5538
+ targetWidth: s.config.targetWidth,
5539
+ targetHeight: s.config.targetHeight,
5540
+ debug: s.config.debug,
5541
+ enforcer: s.enforcer,
5045
5542
  };
5046
5543
  const activeGlobal = getActiveFixes(ctx);
5047
- if (activeGlobal.length > 0) {
5048
- logger$1.log(`Active global fixes: ${activeGlobal.map((f) => f.name).join(', ')}`);
5049
- }
5544
+ if (activeGlobal.length > 0)
5545
+ s.logger.log(`Active global fixes: ${activeGlobal.map((f) => f.name).join(', ')}`);
5546
+ const tRun = perf.mark('viewport.run');
5050
5547
  try {
5051
- return await run$1(ctx, activeGlobal, shopFix);
5548
+ const result = await run$1(ctx, activeGlobal, s.shopFix);
5549
+ perf.measure('viewport.run', tRun);
5550
+ return result;
5052
5551
  }
5053
5552
  catch (err) {
5054
- logger$1.error('Critical error:', err);
5055
- return { height: doc.body?.scrollHeight || 1000, width: doc.body?.scrollWidth || 1000 };
5553
+ perf.measure('viewport.run', tRun);
5554
+ s.logger.error('Critical error:', err);
5555
+ return { height: s.doc.body?.scrollHeight || 1000, width: s.doc.body?.scrollWidth || 1000 };
5056
5556
  }
5057
5557
  }
5058
-
5059
- const logger = createLogger({
5060
- enabled: false,
5061
- prefix: 'IframeFixer',
5062
- });
5063
- // ============================================================================
5064
- // State
5065
- // ============================================================================
5066
- let iframe = null;
5067
- let config = null;
5068
- let running$1 = false;
5069
- let loadListener = null;
5070
- // ============================================================================
5071
- // Core API Functions
5072
- // ============================================================================
5073
- function start$2(cfg) {
5074
- if (running$1) {
5075
- logger.warn('Fixer is already running. Call stop() first.');
5558
+ function updateConfig$1(s, cfg) {
5559
+ if (!s.running || !s.config) {
5560
+ s.logger.warn('Not running. Call start() first.');
5076
5561
  return;
5077
5562
  }
5078
- iframe = cfg.iframe;
5079
- config = cfg;
5080
- running$1 = true;
5081
- logger.configure({ enabled: !!cfg.debug });
5082
- logger.log('Iframe fixer started');
5083
- initialize();
5563
+ s.config = { ...s.config, ...cfg };
5564
+ if (cfg.shopId !== undefined)
5565
+ s.shopFix = cfg.shopId != null ? get(cfg.shopId) : null;
5566
+ s.enforcer.updateConfig(cfg);
5567
+ s.logger.log('Config updated');
5568
+ }
5569
+ // ── Factory ───────────────────────────────────────────────────────────────────
5570
+ function createViewportProcessor() {
5571
+ const s = {
5572
+ logger: createLogger({ enabled: false, prefix: 'ViewportReplacer' }),
5573
+ enforcer: createEnforcer(),
5574
+ listeners: createViewportListeners(),
5575
+ doc: null,
5576
+ win: null,
5577
+ config: null,
5578
+ shopFix: null,
5579
+ running: false,
5580
+ };
5581
+ return {
5582
+ start: (iframe, cfg) => start$2(s, iframe, cfg),
5583
+ stop: () => stop$2(s),
5584
+ run: () => run(s),
5585
+ updateConfig: (cfg) => updateConfig$1(s, cfg),
5586
+ isRunning: () => s.running,
5587
+ };
5588
+ }
5589
+
5590
+ // ── Module-level functions ────────────────────────────────────────────────────
5591
+ function dispatchDimensionsEvent(dimensions) {
5592
+ window.dispatchEvent(new CustomEvent('iframe-dimensions-applied', { detail: dimensions }));
5084
5593
  }
5085
- function stop$2() {
5086
- if (!running$1) {
5594
+ async function process(s) {
5595
+ if (!s.iframe || !s.config)
5087
5596
  return;
5088
- }
5089
- // Stop viewport replacer
5090
- stop$3();
5091
- stop$5();
5092
- // Remove load listener
5093
- if (iframe && loadListener) {
5094
- iframe.removeEventListener('load', loadListener);
5095
- loadListener = null;
5096
- }
5097
- iframe = null;
5098
- config = null;
5099
- running$1 = false;
5100
- logger.log('Iframe fixer stopped');
5101
- }
5102
- async function initialize() {
5103
- if (!iframe || !config) {
5104
- logger.error('iframe not found');
5105
- config?.onError?.(new Error('iframe not found'));
5597
+ if (!s.iframe.contentDocument || !s.iframe.contentWindow) {
5598
+ s.logger.error('Cannot access iframe document');
5599
+ s.config.onError?.(new Error('Cannot access iframe document'));
5106
5600
  return;
5107
5601
  }
5108
- // Wait for iframe to load completely
5109
- if (iframe.contentDocument?.readyState === 'complete') {
5110
- await process();
5602
+ const sessionId = `render-${Date.now()}`;
5603
+ perf.startSession(sessionId);
5604
+ const t0 = perf.mark('orchestrator.process');
5605
+ try {
5606
+ s.logger.log('Processing viewport units...');
5607
+ s.viewportReplacer.start(s.iframe, s.config);
5608
+ s.navigationBlocker.start(s.iframe, { debug: s.config.debug });
5609
+ const result = await s.viewportReplacer.run();
5610
+ perf.measure('orchestrator.process', t0);
5611
+ perf.endSession();
5612
+ s.logger.log('Process completed:', result);
5613
+ s.config.onSuccess?.(result);
5614
+ dispatchDimensionsEvent(result);
5111
5615
  }
5112
- else {
5113
- loadListener = handleIframeLoad;
5114
- iframe.addEventListener('load', loadListener);
5616
+ catch (error) {
5617
+ perf.measure('orchestrator.process', t0);
5618
+ perf.endSession();
5619
+ s.logger.error('Failed to process:', error);
5620
+ s.config.onError?.(error);
5115
5621
  }
5116
5622
  }
5117
- async function process() {
5118
- if (!iframe || !config)
5119
- return;
5120
- if (!iframe.contentDocument || !iframe.contentWindow) {
5121
- logger.error('Cannot access iframe document');
5122
- config.onError?.(new Error('Cannot access iframe document'));
5623
+ async function initialize(s) {
5624
+ if (!s.iframe || !s.config) {
5625
+ s.logger.error('iframe not found');
5626
+ s.config?.onError?.(new Error('iframe not found'));
5123
5627
  return;
5124
5628
  }
5125
- try {
5126
- logger.log('Processing viewport units...');
5127
- start$3(iframe, config);
5128
- start$5(iframe, { debug: config.debug });
5129
- const result = await run();
5130
- logger.log('Process completed:', result);
5131
- config.onSuccess?.(result);
5132
- dispatchDimensionsEvent(result);
5133
- // Optionally setup height observer
5134
- // setupHeightObserver();
5629
+ if (s.iframe.contentDocument?.readyState === 'complete') {
5630
+ await process(s);
5135
5631
  }
5136
- catch (error) {
5137
- logger.error('Failed to process:', error);
5138
- config.onError?.(error);
5632
+ else {
5633
+ s.loadListener = () => process(s);
5634
+ s.iframe.addEventListener('load', s.loadListener);
5139
5635
  }
5140
5636
  }
5141
- // ============================================================================
5142
- // Helper Functions
5143
- // ============================================================================
5144
- function dispatchDimensionsEvent(dimensions) {
5145
- window.dispatchEvent(new CustomEvent('iframe-dimensions-applied', {
5146
- detail: dimensions,
5147
- }));
5637
+ function start$1(s, cfg) {
5638
+ if (s.running) {
5639
+ s.logger.warn('Fixer is already running. Call stop() first.');
5640
+ return;
5641
+ }
5642
+ s.iframe = cfg.iframe;
5643
+ s.config = cfg;
5644
+ s.running = true;
5645
+ s.logger.configure({ enabled: !!cfg.debug });
5646
+ s.logger.log('Iframe fixer started');
5647
+ initialize(s);
5148
5648
  }
5149
- function handleIframeLoad() {
5150
- process();
5649
+ function stop$1(s) {
5650
+ if (!s.running)
5651
+ return;
5652
+ s.viewportReplacer.stop();
5653
+ s.heightObserver.stop();
5654
+ s.navigationBlocker.stop();
5655
+ if (s.iframe && s.loadListener) {
5656
+ s.iframe.removeEventListener('load', s.loadListener);
5657
+ s.loadListener = null;
5658
+ }
5659
+ s.iframe = null;
5660
+ s.config = null;
5661
+ s.running = false;
5662
+ s.logger.log('Iframe fixer stopped');
5663
+ }
5664
+ async function recalculate$1(s) {
5665
+ if (!s.running) {
5666
+ s.logger.warn('Fixer is not running.');
5667
+ return;
5668
+ }
5669
+ s.logger.log('Recalculating...');
5670
+ await process(s);
5151
5671
  }
5152
- /**
5153
- * Enable navigation blocking
5154
- */
5155
- function enableNavigationBlocking$2() {
5156
- if (!running$1) {
5157
- logger.warn('Fixer is not running. Call start() first.');
5672
+ function updateConfig(s, cfg) {
5673
+ if (!s.running || !s.config) {
5674
+ s.logger.warn('Fixer is not running.');
5158
5675
  return;
5159
5676
  }
5160
- enable();
5677
+ s.config = { ...s.config, ...cfg };
5678
+ s.viewportReplacer.updateConfig(cfg);
5679
+ s.logger.log('Config updated');
5680
+ }
5681
+ // ── Factory ───────────────────────────────────────────────────────────────────
5682
+ function createOrchestrator() {
5683
+ const s = {
5684
+ logger: createLogger({ enabled: false, prefix: 'IframeFixer' }),
5685
+ viewportReplacer: createViewportProcessor(),
5686
+ navigationBlocker: createNavigationBlocker(),
5687
+ heightObserver: createHeightObserver(),
5688
+ iframe: null,
5689
+ config: null,
5690
+ running: false,
5691
+ loadListener: null,
5692
+ };
5693
+ return {
5694
+ start: (cfg) => start$1(s, cfg),
5695
+ stop: () => stop$1(s),
5696
+ recalculate: () => recalculate$1(s),
5697
+ updateConfig: (cfg) => updateConfig(s, cfg),
5698
+ enableNavigationBlocking: () => s.navigationBlocker.enable(),
5699
+ enableNavigationBlockingMessage: () => s.navigationBlocker.enableMessage(),
5700
+ disableNavigationBlocking: () => s.navigationBlocker.disable(),
5701
+ disableNavigationBlockingMessage: () => s.navigationBlocker.disableMessage(),
5702
+ isRunning: () => s.running,
5703
+ getStateInfo: () => ({
5704
+ isRunning: s.running,
5705
+ hasIframe: !!s.iframe,
5706
+ hasConfig: !!s.config,
5707
+ hasNavigationBlocker: s.navigationBlocker.isRunning(),
5708
+ hasHeightObserver: s.heightObserver.isRunning(),
5709
+ viewportReplacerRunning: s.viewportReplacer.isRunning(),
5710
+ }),
5711
+ };
5161
5712
  }
5162
5713
 
5163
5714
  /**
5164
- * Iframe Helper Starter Module
5165
- * @module start
5715
+ * Iframe Helper factory entry point.
5716
+ *
5717
+ * Each call to `createIframeHelper()` returns a fully isolated instance
5718
+ * with its own processor state. Use one instance per iframe.
5166
5719
  */
5167
- // ============================================================================
5168
- // State
5169
- // ============================================================================
5170
- let running = false;
5171
- // ============================================================================
5172
- // Public API
5173
- // ============================================================================
5174
- function start$1(config) {
5175
- if (running) {
5176
- console.warn('[IframeHelperStarter] Already running. Call stop() first.');
5720
+ // ── Module-level functions ────────────────────────────────────────────────────
5721
+ function start(s, config) {
5722
+ if (s.running) {
5723
+ console.warn('[IframeHelper] Already running. Call stop() first.');
5177
5724
  return;
5178
5725
  }
5179
- start$2(config);
5180
- running = true;
5726
+ s.fixer.start(config);
5727
+ s.running = true;
5181
5728
  }
5182
- function stop$1() {
5183
- if (!running) {
5729
+ function stop(s) {
5730
+ if (!s.running)
5184
5731
  return;
5185
- }
5186
- stop$2();
5187
- running = false;
5732
+ s.fixer.stop();
5733
+ s.running = false;
5188
5734
  }
5189
- function enableNavigationBlocking$1() {
5190
- if (!running) {
5191
- console.warn('[IframeHelperStarter] Not running. Call start() first.');
5735
+ async function recalculate(s) {
5736
+ if (!s.running) {
5737
+ console.warn('[IframeHelper] Not running. Call start() first.');
5192
5738
  return;
5193
5739
  }
5194
- enableNavigationBlocking$2();
5740
+ await s.fixer.recalculate();
5195
5741
  }
5196
-
5197
- function start(config) {
5198
- start$1(config);
5199
- }
5200
- function stop() {
5201
- stop$1();
5742
+ function enableNavigationBlocking(s) {
5743
+ if (!s.running) {
5744
+ console.warn('[IframeHelper] Not running. Call start() first.');
5745
+ return;
5746
+ }
5747
+ s.fixer.enableNavigationBlocking();
5202
5748
  }
5203
- function enableNavigationBlocking() {
5204
- enableNavigationBlocking$1();
5749
+ // ── Factory ───────────────────────────────────────────────────────────────────
5750
+ function createIframeHelper() {
5751
+ const s = {
5752
+ fixer: createOrchestrator(),
5753
+ running: false,
5754
+ };
5755
+ return {
5756
+ start: (config) => start(s, config),
5757
+ stop: () => stop(s),
5758
+ recalculate: () => recalculate(s),
5759
+ enableNavigationBlocking: () => enableNavigationBlocking(s),
5760
+ isRunning: () => s.running,
5761
+ };
5205
5762
  }
5206
5763
 
5764
+ const iframeHelper = createIframeHelper();
5207
5765
  function useVizLiveRender() {
5208
5766
  const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
5209
5767
  const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
5210
5768
  const wrapperWidth = useHeatmapVizRectContext((s) => s.wrapperWidth);
5211
- const setIsRenderViz = useHeatmapVizContext((s) => s.setIsRenderViz);
5212
- const htmlContent = useHeatmapLiveStore((s) => s.htmlContent);
5213
- const targetUrl = useHeatmapLiveStore((s) => s.targetUrl);
5769
+ const setIsRenderedViz = useHeatmapVizContext((s) => s.setIsRenderedViz);
5770
+ const htmlContent = useHeatmapLiveContext((s) => s.htmlContent);
5771
+ const targetUrl = useHeatmapLiveContext((s) => s.targetUrl);
5214
5772
  const deviceType = useHeatmapSettingContext((s) => s.deviceType);
5215
- const renderMode = useHeatmapLiveStore((s) => s.renderMode);
5216
- const storefrontPassword = useHeatmapLiveStore((s) => s.storefrontPassword);
5773
+ const renderMode = useHeatmapLiveContext((s) => s.renderMode);
5774
+ const storefrontPassword = useHeatmapLiveContext((s) => s.storefrontPassword);
5217
5775
  useHeatmapWidthByDevice();
5218
5776
  const { iframeRef, isReady } = useVizLiveIframeMsg();
5219
5777
  // Handle iframe rendering based on mode
@@ -5254,10 +5812,10 @@ function useVizLiveRender() {
5254
5812
  const hasContent = (renderMode === 'portal' && targetUrl) || (renderMode === 'inline' && htmlContent);
5255
5813
  if (!iframe || !hasContent)
5256
5814
  return;
5257
- setIsRenderViz(true);
5258
- initIframeHelper$1(iframe, deviceType, { width: wrapperWidth, height: wrapperHeight }, (height) => {
5815
+ setIsRenderedViz(true);
5816
+ startIframe$1(iframe, deviceType, { width: wrapperWidth, height: wrapperHeight }, (height) => {
5259
5817
  height && setIframeHeight(height);
5260
- setIsRenderViz(true);
5818
+ setIsRenderedViz(true);
5261
5819
  });
5262
5820
  return () => { };
5263
5821
  }, [
@@ -5269,7 +5827,7 @@ function useVizLiveRender() {
5269
5827
  targetUrl,
5270
5828
  htmlContent,
5271
5829
  iframeRef,
5272
- setIsRenderViz,
5830
+ setIsRenderedViz,
5273
5831
  setIframeHeight,
5274
5832
  ]);
5275
5833
  return {
@@ -5288,9 +5846,9 @@ function buildPortalUrl(targetUrl, storefrontPassword) {
5288
5846
  const portalServiceUrl = getPortalServiceUrl();
5289
5847
  return `${portalServiceUrl}/?${params.toString()}`;
5290
5848
  }
5291
- function initIframeHelper$1(iframe, deviceType = EDeviceType.Desktop, rect, onSuccess) {
5292
- stop();
5293
- start({
5849
+ function startIframe$1(iframe, deviceType = EDeviceType.Desktop, rect, onSuccess) {
5850
+ iframeHelper.stop();
5851
+ iframeHelper.start({
5294
5852
  deviceType: deviceType,
5295
5853
  targetWidth: rect.width,
5296
5854
  targetHeight: rect.height,
@@ -5301,7 +5859,7 @@ function initIframeHelper$1(iframe, deviceType = EDeviceType.Desktop, rect, onSu
5301
5859
  },
5302
5860
  });
5303
5861
  // fixer.recalculate();
5304
- enableNavigationBlocking();
5862
+ iframeHelper.enableNavigationBlocking();
5305
5863
  }
5306
5864
 
5307
5865
  const CANVAS_ID = 'clarity-heatmap-canvas';
@@ -5551,36 +6109,69 @@ class GXVisualizer extends Visualizer {
5551
6109
  };
5552
6110
  }
5553
6111
 
6112
+ // ── Performance timing ────────────────────────────────────────────────────────
6113
+ function mark(label) {
6114
+ const t = performance.now();
6115
+ console.log(`[Render] ⏱ ${label}`);
6116
+ return t;
6117
+ }
6118
+ function measure(label, startMs) {
6119
+ const ms = (performance.now() - startMs).toFixed(1);
6120
+ console.log(`[Render] ✅ ${label} — ${ms}ms`);
6121
+ }
6122
+ // ── Hook ──────────────────────────────────────────────────────────────────────
5554
6123
  const useHeatmapRender = () => {
5555
6124
  const viewId = useViewIdContext();
5556
6125
  const data = useHeatmapDataContext((s) => s.data);
6126
+ const shopId = useHeatmapConfigStore((s) => s.shopId);
5557
6127
  const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
5558
6128
  const setVizRef = useHeatmapVizRectContext((s) => s.setVizRef);
5559
6129
  const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
5560
- const setIsRenderViz = useHeatmapVizContext((s) => s.setIsRenderViz);
6130
+ const setIsRenderedViz = useHeatmapVizContext((s) => s.setIsRenderedViz);
5561
6131
  const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
5562
6132
  const contentWidth = useHeatmapWidthByDevice();
5563
6133
  const deviceType = useHeatmapSettingContext((s) => s.deviceType);
5564
6134
  const iframeRef = useRef(null);
6135
+ const helperRef = useRef(null);
6136
+ const abortRef = useRef(null);
5565
6137
  const renderHeatmap = useCallback(async (payloads) => {
5566
6138
  if (contentWidth === 0 || wrapperHeight === 0)
5567
6139
  return;
5568
6140
  if (!payloads || payloads.length === 0)
5569
6141
  return;
6142
+ // Cancel any in-flight render before starting a new one
6143
+ abortRef.current?.abort();
6144
+ const abort = new AbortController();
6145
+ abortRef.current = abort;
6146
+ const t0 = mark('renderHeatmap start');
5570
6147
  const visualizer = vizRef ?? new GXVisualizer();
5571
6148
  if (!vizRef)
5572
6149
  setVizRef(visualizer);
5573
- setIsRenderViz(false);
6150
+ setIsRenderedViz(false);
5574
6151
  const iframe = iframeRef.current;
5575
6152
  if (!iframe?.contentWindow)
5576
6153
  return;
6154
+ const tHtml = mark('visualizer.html start');
5577
6155
  await visualizer.html(payloads, iframe.contentWindow, viewId);
5578
- const size = { width: contentWidth, height: wrapperHeight };
5579
- initIframeHelper(iframe, deviceType, size, (height) => {
5580
- height && setIframeHeight(height);
5581
- setIsRenderViz(true);
6156
+ measure('visualizer.html', tHtml);
6157
+ // Bail out if data changed or component unmounted while we were awaiting
6158
+ if (abort.signal.aborted)
6159
+ return;
6160
+ startIframe({
6161
+ helperRef,
6162
+ iframe,
6163
+ shopId,
6164
+ deviceType,
6165
+ size: { width: contentWidth, height: wrapperHeight },
6166
+ t0,
6167
+ onSuccess: (height) => {
6168
+ if (abort.signal.aborted)
6169
+ return;
6170
+ if (height)
6171
+ setIframeHeight(height);
6172
+ setIsRenderedViz(true);
6173
+ },
5582
6174
  });
5583
- // setIsRenderViz(true);
5584
6175
  }, [wrapperHeight, contentWidth, deviceType]);
5585
6176
  useEffect(() => {
5586
6177
  if (!data || data.length === 0)
@@ -5591,33 +6182,47 @@ const useHeatmapRender = () => {
5591
6182
  setVizRef(null);
5592
6183
  };
5593
6184
  }, [data, renderHeatmap, setVizRef]);
6185
+ // Abort render + stop helper when the component unmounts
6186
+ useEffect(() => {
6187
+ return () => {
6188
+ abortRef.current?.abort();
6189
+ helperRef.current?.stop();
6190
+ helperRef.current = null;
6191
+ };
6192
+ }, []);
5594
6193
  return {
5595
6194
  iframeRef,
5596
6195
  };
5597
6196
  };
5598
- function initIframeHelper(iframe, deviceType = EDeviceType.Desktop, size, onSuccess) {
6197
+ // ── Helpers ───────────────────────────────────────────────────────────────────
6198
+ function startIframe({ helperRef, iframe, shopId, deviceType = EDeviceType.Desktop, size, t0, onSuccess, }) {
5599
6199
  const docWidth = size.width ?? 0;
5600
6200
  const docHeight = size.height ?? 0;
5601
6201
  if (docHeight === 0)
5602
6202
  return;
5603
- stop();
5604
- start({
5605
- deviceType: deviceType,
6203
+ helperRef.current?.stop();
6204
+ const tHelper = mark('IframeHelper.start');
6205
+ const helper = createIframeHelper();
6206
+ helperRef.current = helper;
6207
+ helper.start({
6208
+ deviceType,
5606
6209
  targetWidth: docWidth,
5607
6210
  targetHeight: docHeight,
5608
- iframe: iframe,
6211
+ iframe,
5609
6212
  debug: true,
6213
+ shopId,
5610
6214
  onSuccess: (data) => {
6215
+ measure('IframeHelper processing', tHelper);
6216
+ measure('Total render', t0);
5611
6217
  iframe.style.height = `${data.height}px`;
5612
6218
  onSuccess(data.height);
5613
6219
  },
5614
6220
  });
5615
- // fixer.recalculate();
5616
6221
  }
5617
6222
 
5618
6223
  const useReplayRender = () => {
5619
6224
  const data = useHeatmapDataContext((s) => s.data);
5620
- const setIsRenderViz = useHeatmapVizContext((s) => s.setIsRenderViz);
6225
+ const setIsRenderedViz = useHeatmapVizContext((s) => s.setIsRenderedViz);
5621
6226
  const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
5622
6227
  const visualizerRef = useRef(null);
5623
6228
  const iframeRef = useRef(null);
@@ -5637,7 +6242,7 @@ const useReplayRender = () => {
5637
6242
  version: envelope.version,
5638
6243
  onresize: (height) => {
5639
6244
  height && setIframeHeight(height);
5640
- setIsRenderViz(true);
6245
+ setIsRenderedViz(true);
5641
6246
  },
5642
6247
  mobile,
5643
6248
  vNext: true,
@@ -5793,7 +6398,7 @@ const useContentDimensions = ({ iframeRef }) => {
5793
6398
  const useObserveIframeHeight = (props) => {
5794
6399
  const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
5795
6400
  const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
5796
- const isRenderViz = useHeatmapVizContext((s) => s.isRenderViz);
6401
+ const isRenderedViz = useHeatmapVizContext((s) => s.isRenderedViz);
5797
6402
  const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
5798
6403
  const { iframeRef } = props;
5799
6404
  const resizeObserverRef = useRef(null);
@@ -5854,7 +6459,7 @@ const useObserveIframeHeight = (props) => {
5854
6459
  }, [updateIframeHeight]);
5855
6460
  useEffect(() => {
5856
6461
  const iframe = iframeRef.current;
5857
- if (!iframe || !iframeHeight || !isRenderViz)
6462
+ if (!iframe || !iframeHeight || !isRenderedViz)
5858
6463
  return;
5859
6464
  const setupObservers = () => {
5860
6465
  try {
@@ -5916,7 +6521,7 @@ const useObserveIframeHeight = (props) => {
5916
6521
  }
5917
6522
  iframe.removeEventListener('load', setupObservers);
5918
6523
  };
5919
- }, [iframeRef, iframeHeight, isRenderViz]);
6524
+ }, [iframeRef, iframeHeight, isRenderedViz]);
5920
6525
  return {};
5921
6526
  };
5922
6527
 
@@ -6215,10 +6820,10 @@ const useScrollmapZones = (options) => {
6215
6820
  const newZones = createZones(scrollmap);
6216
6821
  setZones(newZones);
6217
6822
  setIsReady(true);
6218
- logger$9.log(`[useScrollmap] Created ${newZones.length} zones in ${mode} mode`);
6823
+ logger$4.log(`[useScrollmap] Created ${newZones.length} zones in ${mode} mode`);
6219
6824
  }
6220
6825
  catch (error) {
6221
- logger$9.error('[useScrollmap] Error:', error);
6826
+ logger$4.error('[useScrollmap] Error:', error);
6222
6827
  setIsReady(false);
6223
6828
  }
6224
6829
  }, [enabled, scrollmap, mode, createZones]);
@@ -7048,11 +7653,11 @@ const AutoScrollHandler = ({ visualRef }) => {
7048
7653
  };
7049
7654
 
7050
7655
  const PortalAreaRenderer = ({ iframeRef, visualRef, shadowRoot, onAreaCreated, onAreaClick, }) => {
7051
- const isRenderViz = useHeatmapVizContext((s) => s.isRenderViz);
7656
+ const isRenderedViz = useHeatmapVizContext((s) => s.isRenderedViz);
7052
7657
  const iframeDocument = iframeRef.current?.contentDocument || undefined;
7053
7658
  const { shadowContainer, isReady } = useAreaRendererContainer(iframeDocument, shadowRoot);
7054
- useAreaRectSync({ iframeDocument, shadowRoot, enabled: isReady && isRenderViz });
7055
- useAreaPositionsUpdater({ iframeRef, visualRef, enabled: isReady && isRenderViz });
7659
+ useAreaRectSync({ iframeDocument, shadowRoot, enabled: isReady && isRenderedViz });
7660
+ useAreaPositionsUpdater({ iframeRef, visualRef, enabled: isReady && isRenderedViz });
7056
7661
  if (!shadowContainer || !isReady)
7057
7662
  return null;
7058
7663
  return (jsxs(Fragment$1, { children: [jsx(AutoScrollHandler, { visualRef: visualRef }), jsx(AreasPortal, { shadowContainer: shadowContainer, onAreaClick: onAreaClick }), jsx(AreaEditHighlightPortal, { shadowContainer: shadowContainer, iframeRef: iframeRef, customShadowRoot: shadowRoot, onAreaCreated: onAreaCreated })] }));
@@ -7061,7 +7666,7 @@ const PortalAreaRenderer = ({ iframeRef, visualRef, shadowRoot, onAreaCreated, o
7061
7666
  const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true, onAreaClick, }) => {
7062
7667
  const clickAreas = useHeatmapDataContext((s) => s.clickAreas);
7063
7668
  const resetView = useHeatmapAreaClickContext((s) => s.resetView);
7064
- const isRenderViz = useHeatmapVizContext((s) => s.isRenderViz);
7669
+ const isRenderedViz = useHeatmapVizContext((s) => s.isRenderedViz);
7065
7670
  useAreaTopAutoDetect({ autoCreateTopN, shadowRoot, disabled: !!clickAreas?.length });
7066
7671
  useAreaFilterVisible({ iframeRef, enableOverlapResolution });
7067
7672
  useAreaHydration({ shadowRoot });
@@ -7070,7 +7675,7 @@ const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, e
7070
7675
  resetView();
7071
7676
  };
7072
7677
  }, []);
7073
- if (!iframeRef.current || !isRenderViz)
7678
+ if (!iframeRef.current || !isRenderedViz)
7074
7679
  return null;
7075
7680
  return (jsx(Fragment, { children: jsx(PortalAreaRenderer, { iframeRef: iframeRef, visualRef: visualRef, shadowRoot: shadowRoot, onAreaClick: onAreaClick }) }));
7076
7681
  };
@@ -7784,7 +8389,7 @@ const VizDomHeatmap = () => {
7784
8389
  const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
7785
8390
  const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
7786
8391
  const setVizRef = useHeatmapVizRectContext((s) => s.setVizRef);
7787
- const setIsRenderViz = useHeatmapVizContext((s) => s.setIsRenderViz);
8392
+ const setIsRenderedViz = useHeatmapVizContext((s) => s.setIsRenderedViz);
7788
8393
  const setSelectedElement = useHeatmapClickContext((s) => s.setSelectedElement);
7789
8394
  const setHoveredElement = useHeatmapHoverContext((s) => s.setHoveredElement);
7790
8395
  // const setSelectedArea = useHeatmapAreaClickContext((s) => s.setSelectedArea);
@@ -7794,7 +8399,7 @@ const VizDomHeatmap = () => {
7794
8399
  const cleanUp = () => {
7795
8400
  setVizRef(null);
7796
8401
  setIframeHeight(0);
7797
- setIsRenderViz(false);
8402
+ setIsRenderedViz(false);
7798
8403
  setSelectedElement(null);
7799
8404
  setHoveredElement(null);
7800
8405
  // setSelectedArea(null);
@@ -7826,7 +8431,7 @@ const VizLiveRenderer = () => {
7826
8431
  const VizLiveHeatmap = () => {
7827
8432
  const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
7828
8433
  const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
7829
- useHeatmapLiveStore((state) => state.reset);
8434
+ useHeatmapLiveContext((s) => s.reset);
7830
8435
  const CompVizLoading = useHeatmapControlStore((state) => state.controls.VizLoading);
7831
8436
  // TODO: Remove this after testing
7832
8437
  useEffect(() => {
@@ -7877,11 +8482,11 @@ const ContentTopBar = () => {
7877
8482
  }, children: CompTopBar && jsx(CompTopBar, {}) }));
7878
8483
  };
7879
8484
 
7880
- const HeatmapLayout = ({ data, clickmap, clickAreas, scrollmap, attentionMap, controls, dataInfo, isLoading, isLoadingCanvas, }) => {
8485
+ const HeatmapLayout = ({ shopId, data, clickmap, clickAreas, scrollmap, attentionMap, controls, dataInfo, isLoading, isLoadingCanvas, }) => {
7881
8486
  useRegisterControl(controls);
7882
8487
  useRegisterData(data, dataInfo);
7883
8488
  useRegisterHeatmap({ clickmap, scrollmap, clickAreas, attentionMap });
7884
- useRegisterConfig({ isLoading, isLoadingCanvas });
8489
+ useRegisterConfig({ isLoading, isLoadingCanvas, shopId });
7885
8490
  // performanceLogger.configure({
7886
8491
  // enabled: true,
7887
8492
  // logToConsole: false,
@@ -7911,4 +8516,4 @@ const HeatmapLayout = ({ data, clickmap, clickAreas, scrollmap, attentionMap, co
7911
8516
  }
7912
8517
  };
7913
8518
 
7914
- export { BACKDROP_CONFIG, DEFAULT_SIDEBAR_WIDTH, DEFAULT_VIEW_ID, DEFAULT_ZOOM_RATIO, EClickMode, EClickRankType, EClickType, EDeviceType, EHeatmapMode, EHeatmapType, ELM_CALLOUT_CONFIG, EScrollType, GraphView, HEATMAP_CONFIG, HEATMAP_IFRAME, HEATMAP_STYLE, HeatmapLayout, ViewIdContext, Z_INDEX$1 as Z_INDEX, compareViewPerformance, convertViewportToIframeCoords, createStorePerformanceTracker, createViewContextHook, decodeArrayClarity, decodeClarity, downloadPerformanceReport, getCompareViewId, getMetricsByViewId, getPerformanceReportJSON, getScrollGradientColor, performanceLogger, printPerformanceSummary, scrollToElementIfNeeded, sendPerformanceReport, serializeAreas, trackStoreAction, useAreaCreation, useAreaEditMode, useAreaFilterVisible, useAreaHydration, useAreaInteraction, useAreaPositionsUpdater, useAreaRectSync, useAreaRendererContainer, useAreaTopAutoDetect, useClickedElement, useDebounceCallback, useElementCalloutVisible, useHeatmapAreaClickContext, useHeatmapCanvas, useHeatmapClickContext, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapCopyView, useHeatmapDataContext, useHeatmapEffects, useHeatmapElementPosition, useHeatmapHoverContext, useHeatmapLiveStore, useHeatmapRenderByMode, useHeatmapScale, useHeatmapScroll, useHeatmapScrollContext, useHeatmapSettingContext, useHeatmapVizContext, useHeatmapVizRectContext, useHeatmapWidthByDevice, useHoveredElement, useMeasureFunction, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useRenderCount, useScrollmapZones, useTrackHookCall, useViewIdContext, useVizLiveRender, useWhyDidYouUpdate, useWrapperRefHeight, useZonePositions, withPerformanceTracking };
8519
+ export { BACKDROP_CONFIG, DEFAULT_SIDEBAR_WIDTH, DEFAULT_VIEW_ID, DEFAULT_ZOOM_RATIO, EClickMode, EClickRankType, EClickType, EDeviceType, EHeatmapDataSource, EHeatmapMode, EHeatmapType, ELM_CALLOUT_CONFIG, EScrollType, GraphView, HEATMAP_CONFIG, HEATMAP_IFRAME, HEATMAP_STYLE, HeatmapLayout, ViewIdContext, Z_INDEX$1 as Z_INDEX, compareViewPerformance, convertViewportToIframeCoords, createStorePerformanceTracker, createViewContextHook, decodeArrayClarity, decodeClarity, downloadPerformanceReport, getCompareViewId, getMetricsByViewId, getPerformanceReportJSON, getScrollGradientColor, performanceLogger, printPerformanceSummary, scrollToElementIfNeeded, sendPerformanceReport, serializeAreas, trackStoreAction, useAreaCreation, useAreaEditMode, useAreaFilterVisible, useAreaHydration, useAreaInteraction, useAreaPositionsUpdater, useAreaRectSync, useAreaRendererContainer, useAreaTopAutoDetect, useClickedElement, useDebounceCallback, useElementCalloutVisible, useHeatmapAreaClickContext, useHeatmapCanvas, useHeatmapClickContext, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapCopyView, useHeatmapDataContext, useHeatmapEffects, useHeatmapElementPosition, useHeatmapHoverContext, useHeatmapLiveContext, useHeatmapRenderByMode, useHeatmapScale, useHeatmapScroll, useHeatmapScrollContext, useHeatmapSettingContext, useHeatmapVizContext, useHeatmapVizRectContext, useHeatmapWidthByDevice, useHoveredElement, useMeasureFunction, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useRenderCount, useScrollmapZones, useTrackHookCall, useViewIdContext, useVizLiveRender, useWhyDidYouUpdate, useWrapperRefHeight, useZonePositions, withPerformanceTracking };