@gemx-dev/heatmap-react 3.5.92-dev.3 → 3.5.92-dev.30

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 (398) hide show
  1. package/dist/esm/components/Layout/HeatmapLayout.d.ts +2 -0
  2. package/dist/esm/components/Layout/HeatmapLayout.d.ts.map +1 -1
  3. package/dist/esm/components/VizDom/VizDomHeatmap.d.ts.map +1 -1
  4. package/dist/esm/components/VizDom/VizDomRenderer.d.ts +2 -1
  5. package/dist/esm/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  6. package/dist/esm/components/VizElement/ElementCallout.d.ts +2 -2
  7. package/dist/esm/components/VizElement/ElementCallout.d.ts.map +1 -1
  8. package/dist/esm/components/VizElement/ElementCalloutClicked.d.ts +2 -2
  9. package/dist/esm/components/VizElement/ElementCalloutClicked.d.ts.map +1 -1
  10. package/dist/esm/components/VizElement/ElementCalloutHovered.d.ts +2 -1
  11. package/dist/esm/components/VizElement/ElementCalloutHovered.d.ts.map +1 -1
  12. package/dist/esm/components/VizElement/ElementMissing.d.ts +2 -1
  13. package/dist/esm/components/VizElement/ElementMissing.d.ts.map +1 -1
  14. package/dist/esm/components/VizElement/HeatmapElements.d.ts +4 -4
  15. package/dist/esm/components/VizElement/HeatmapElements.d.ts.map +1 -1
  16. package/dist/esm/components/VizElement/VizElements.d.ts +4 -3
  17. package/dist/esm/components/VizElement/VizElements.d.ts.map +1 -1
  18. package/dist/esm/components/VizLive/VizLiveHeatmap.d.ts.map +1 -1
  19. package/dist/esm/components/VizScrollmap/HoverZones.d.ts +3 -3
  20. package/dist/esm/components/VizScrollmap/HoverZones.d.ts.map +1 -1
  21. package/dist/esm/components/VizScrollmap/Minimap.d.ts +3 -3
  22. package/dist/esm/components/VizScrollmap/Minimap.d.ts.map +1 -1
  23. package/dist/esm/helpers/canvas-backdrop.d.ts.map +1 -1
  24. package/dist/esm/helpers/viewport/element.d.ts +2 -2
  25. package/dist/esm/helpers/viewport/element.d.ts.map +1 -1
  26. package/dist/esm/helpers/viz-elm-callout/viz-elm.d.ts +2 -1
  27. package/dist/esm/helpers/viz-elm-callout/viz-elm.d.ts.map +1 -1
  28. package/dist/esm/hooks/common/index.d.ts +1 -1
  29. package/dist/esm/hooks/common/index.d.ts.map +1 -1
  30. package/dist/esm/hooks/common/useHeatmapViewportByDevice.d.ts +7 -0
  31. package/dist/esm/hooks/common/useHeatmapViewportByDevice.d.ts.map +1 -0
  32. package/dist/esm/hooks/register/useRegisterConfig.d.ts +3 -1
  33. package/dist/esm/hooks/register/useRegisterConfig.d.ts.map +1 -1
  34. package/dist/esm/hooks/view-context/index.d.ts +1 -0
  35. package/dist/esm/hooks/view-context/index.d.ts.map +1 -1
  36. package/dist/esm/hooks/view-context/useHeatmapCopyView.d.ts.map +1 -1
  37. package/dist/esm/hooks/view-context/useHeatmapDataContext.d.ts +4 -16
  38. package/dist/esm/hooks/view-context/useHeatmapDataContext.d.ts.map +1 -1
  39. package/dist/esm/hooks/view-context/useHeatmapLiveContext.d.ts +34 -0
  40. package/dist/esm/hooks/view-context/useHeatmapLiveContext.d.ts.map +1 -0
  41. package/dist/esm/hooks/view-context/useHeatmapSettingContext.d.ts +3 -1
  42. package/dist/esm/hooks/view-context/useHeatmapSettingContext.d.ts.map +1 -1
  43. package/dist/esm/hooks/view-context/useHeatmapVizContext.d.ts +2 -2
  44. package/dist/esm/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -1
  45. package/dist/esm/hooks/viz-canvas/useClickmap.d.ts.map +1 -1
  46. package/dist/esm/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
  47. package/dist/esm/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
  48. package/dist/esm/hooks/viz-elm/useClickedElement.d.ts +3 -3
  49. package/dist/esm/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
  50. package/dist/esm/hooks/viz-elm/useElementCalloutVisible.d.ts +2 -1
  51. package/dist/esm/hooks/viz-elm/useElementCalloutVisible.d.ts.map +1 -1
  52. package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -3
  53. package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
  54. package/dist/esm/hooks/viz-elm/useHoveredElement.d.ts +3 -1
  55. package/dist/esm/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
  56. package/dist/esm/hooks/viz-live/useVizLiveIframeMsg.d.ts.map +1 -1
  57. package/dist/esm/hooks/viz-live/useVizLiveRender.d.ts.map +1 -1
  58. package/dist/esm/hooks/viz-render/useHeatmapIframeProcessor.d.ts +6 -0
  59. package/dist/esm/hooks/viz-render/useHeatmapIframeProcessor.d.ts.map +1 -0
  60. package/dist/esm/hooks/viz-render/useHeatmapRenderByMode.d.ts +3 -2
  61. package/dist/esm/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -1
  62. package/dist/esm/hooks/viz-render/useHeatmapRenderDom.d.ts +5 -0
  63. package/dist/esm/hooks/viz-render/useHeatmapRenderDom.d.ts.map +1 -0
  64. package/dist/esm/hooks/viz-render/useReplayRender.d.ts +2 -2
  65. package/dist/esm/hooks/viz-render/useReplayRender.d.ts.map +1 -1
  66. package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
  67. package/dist/esm/hooks/viz-scale/useScaleCalculation.d.ts +1 -2
  68. package/dist/esm/hooks/viz-scale/useScaleCalculation.d.ts.map +1 -1
  69. package/dist/esm/index.d.ts +1 -1
  70. package/dist/esm/index.d.ts.map +1 -1
  71. package/dist/esm/index.js +2414 -1367
  72. package/dist/esm/index.mjs +2414 -1367
  73. package/dist/esm/libs/iframe-processor/index.d.ts +2 -5
  74. package/dist/esm/libs/iframe-processor/index.d.ts.map +1 -1
  75. package/dist/esm/libs/iframe-processor/lifecycle.d.ts +16 -7
  76. package/dist/esm/libs/iframe-processor/lifecycle.d.ts.map +1 -1
  77. package/dist/esm/libs/iframe-processor/orchestrator.d.ts +14 -45
  78. package/dist/esm/libs/iframe-processor/orchestrator.d.ts.map +1 -1
  79. package/dist/esm/libs/iframe-processor/processors/height-observer/index.d.ts +5 -14
  80. package/dist/esm/libs/iframe-processor/processors/height-observer/index.d.ts.map +1 -1
  81. package/dist/esm/libs/iframe-processor/processors/height-observer/listeners.d.ts +11 -0
  82. package/dist/esm/libs/iframe-processor/processors/height-observer/listeners.d.ts.map +1 -0
  83. package/dist/esm/libs/iframe-processor/processors/height-observer/types.d.ts +18 -1
  84. package/dist/esm/libs/iframe-processor/processors/height-observer/types.d.ts.map +1 -1
  85. package/dist/esm/libs/iframe-processor/processors/navigation/index.d.ts +3 -14
  86. package/dist/esm/libs/iframe-processor/processors/navigation/index.d.ts.map +1 -1
  87. package/dist/esm/libs/iframe-processor/processors/navigation/listeners.d.ts +3 -4
  88. package/dist/esm/libs/iframe-processor/processors/navigation/listeners.d.ts.map +1 -1
  89. package/dist/esm/libs/iframe-processor/processors/navigation/types.d.ts +13 -0
  90. package/dist/esm/libs/iframe-processor/processors/navigation/types.d.ts.map +1 -1
  91. package/dist/esm/libs/iframe-processor/processors/viewport/dimensions.d.ts +1 -1
  92. package/dist/esm/libs/iframe-processor/processors/viewport/dimensions.d.ts.map +1 -1
  93. package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/global-fixes/viewport-unit-replacer/fixes.d.ts.map +1 -1
  94. package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/gp-v7-fixes/gem-slider-item/fixes.d.ts.map +1 -1
  95. package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts +0 -1
  96. package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts.map +1 -1
  97. package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/types.d.ts +2 -2
  98. package/dist/esm/libs/iframe-processor/processors/viewport/index.d.ts +3 -9
  99. package/dist/esm/libs/iframe-processor/processors/viewport/index.d.ts.map +1 -1
  100. package/dist/esm/libs/iframe-processor/processors/viewport/listeners.d.ts +3 -2
  101. package/dist/esm/libs/iframe-processor/processors/viewport/listeners.d.ts.map +1 -1
  102. package/dist/esm/libs/iframe-processor/processors/viewport/pipeline.d.ts.map +1 -1
  103. package/dist/esm/libs/iframe-processor/processors/viewport/shop-overrides/types.d.ts +2 -0
  104. package/dist/esm/libs/iframe-processor/processors/viewport/shop-overrides/types.d.ts.map +1 -1
  105. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/constants.d.ts +5 -0
  106. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/constants.d.ts.map +1 -0
  107. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/functions.d.ts +11 -0
  108. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/functions.d.ts.map +1 -0
  109. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/index.d.ts +9 -0
  110. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/index.d.ts.map +1 -0
  111. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/state.d.ts +13 -0
  112. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/state.d.ts.map +1 -0
  113. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/types.d.ts +16 -0
  114. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/types.d.ts.map +1 -0
  115. package/dist/esm/libs/iframe-processor/processors/viewport/types.d.ts +8 -0
  116. package/dist/esm/libs/iframe-processor/processors/viewport/types.d.ts.map +1 -0
  117. package/dist/esm/libs/iframe-processor/shared/factory-types.d.ts +24 -0
  118. package/dist/esm/libs/iframe-processor/shared/factory-types.d.ts.map +1 -0
  119. package/dist/esm/libs/iframe-processor/shared/iframe-types.d.ts +22 -0
  120. package/dist/esm/libs/iframe-processor/shared/iframe-types.d.ts.map +1 -0
  121. package/dist/esm/libs/index.d.ts +1 -0
  122. package/dist/esm/libs/index.d.ts.map +1 -1
  123. package/dist/esm/libs/perf.d.ts +83 -0
  124. package/dist/esm/libs/perf.d.ts.map +1 -0
  125. package/dist/esm/libs/visualizer/GXVisualizer.d.ts +23 -4
  126. package/dist/esm/libs/visualizer/GXVisualizer.d.ts.map +1 -1
  127. package/dist/esm/libs/visualizer/cache/config.d.ts +15 -0
  128. package/dist/esm/libs/visualizer/cache/config.d.ts.map +1 -0
  129. package/dist/esm/libs/visualizer/cache/index.d.ts +16 -0
  130. package/dist/esm/libs/visualizer/cache/index.d.ts.map +1 -0
  131. package/dist/esm/libs/visualizer/index.d.ts +2 -2
  132. package/dist/esm/libs/visualizer/index.d.ts.map +1 -1
  133. package/dist/{umd/libs/visualizer → esm/libs/visualizer/renderers}/AttentionMapRenderer.d.ts +2 -2
  134. package/dist/esm/libs/visualizer/renderers/AttentionMapRenderer.d.ts.map +1 -0
  135. package/dist/esm/libs/visualizer/renderers/ScrollBucketRenderer.d.ts +44 -0
  136. package/dist/esm/libs/visualizer/renderers/ScrollBucketRenderer.d.ts.map +1 -0
  137. package/dist/esm/libs/visualizer/renderers/index.d.ts +3 -0
  138. package/dist/esm/libs/visualizer/renderers/index.d.ts.map +1 -0
  139. package/dist/esm/libs/visualizer/shadow-dom/extractor.d.ts +23 -0
  140. package/dist/esm/libs/visualizer/shadow-dom/extractor.d.ts.map +1 -0
  141. package/dist/esm/libs/visualizer/types/data.d.ts +72 -0
  142. package/dist/esm/libs/visualizer/types/data.d.ts.map +1 -0
  143. package/dist/esm/libs/visualizer/{types.d.ts → types/extend.d.ts} +7 -2
  144. package/dist/esm/libs/visualizer/types/extend.d.ts.map +1 -0
  145. package/dist/esm/libs/visualizer/types/index.d.ts +6 -0
  146. package/dist/esm/libs/visualizer/types/index.d.ts.map +1 -0
  147. package/dist/esm/libs/visualizer/types/layout.d.ts +69 -0
  148. package/dist/esm/libs/visualizer/types/layout.d.ts.map +1 -0
  149. package/dist/esm/libs/visualizer/types/visualize.d.ts +26 -0
  150. package/dist/esm/libs/visualizer/types/visualize.d.ts.map +1 -0
  151. package/dist/esm/libs/visualizer/utils/delay.d.ts +21 -0
  152. package/dist/esm/libs/visualizer/utils/delay.d.ts.map +1 -0
  153. package/dist/esm/libs/visualizer/utils/render.d.ts +7 -0
  154. package/dist/esm/libs/visualizer/utils/render.d.ts.map +1 -0
  155. package/dist/esm/stores/config.d.ts +4 -2
  156. package/dist/esm/stores/config.d.ts.map +1 -1
  157. package/dist/esm/stores/data.d.ts +4 -0
  158. package/dist/esm/stores/data.d.ts.map +1 -1
  159. package/dist/esm/stores/mode-live.d.ts +30 -16
  160. package/dist/esm/stores/mode-live.d.ts.map +1 -1
  161. package/dist/esm/stores/setting.d.ts +3 -1
  162. package/dist/esm/stores/setting.d.ts.map +1 -1
  163. package/dist/esm/stores/viz.d.ts +2 -2
  164. package/dist/esm/types/clarity.d.ts +18 -0
  165. package/dist/esm/types/clarity.d.ts.map +1 -1
  166. package/dist/esm/types/control.d.ts +2 -0
  167. package/dist/esm/types/control.d.ts.map +1 -1
  168. package/dist/esm/types/heatmap-info.d.ts +1 -0
  169. package/dist/esm/types/heatmap-info.d.ts.map +1 -1
  170. package/dist/esm/types/heatmap.d.ts +9 -0
  171. package/dist/esm/types/heatmap.d.ts.map +1 -1
  172. package/dist/esm/types/iframe-helper.d.ts +0 -19
  173. package/dist/esm/types/iframe-helper.d.ts.map +1 -1
  174. package/dist/esm/types/viz-elm-callout.d.ts +26 -1
  175. package/dist/esm/types/viz-elm-callout.d.ts.map +1 -1
  176. package/dist/esm/types/viz-scrollmap.d.ts +8 -2
  177. package/dist/esm/types/viz-scrollmap.d.ts.map +1 -1
  178. package/dist/esm/utils/dom.d.ts +1 -1
  179. package/dist/esm/utils/dom.d.ts.map +1 -1
  180. package/dist/esm/utils/retry.d.ts +1 -0
  181. package/dist/esm/utils/retry.d.ts.map +1 -1
  182. package/dist/umd/components/Layout/HeatmapLayout.d.ts +2 -0
  183. package/dist/umd/components/Layout/HeatmapLayout.d.ts.map +1 -1
  184. package/dist/umd/components/VizDom/VizDomHeatmap.d.ts.map +1 -1
  185. package/dist/umd/components/VizDom/VizDomRenderer.d.ts +2 -1
  186. package/dist/umd/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  187. package/dist/umd/components/VizElement/ElementCallout.d.ts +2 -2
  188. package/dist/umd/components/VizElement/ElementCallout.d.ts.map +1 -1
  189. package/dist/umd/components/VizElement/ElementCalloutClicked.d.ts +2 -2
  190. package/dist/umd/components/VizElement/ElementCalloutClicked.d.ts.map +1 -1
  191. package/dist/umd/components/VizElement/ElementCalloutHovered.d.ts +2 -1
  192. package/dist/umd/components/VizElement/ElementCalloutHovered.d.ts.map +1 -1
  193. package/dist/umd/components/VizElement/ElementMissing.d.ts +2 -1
  194. package/dist/umd/components/VizElement/ElementMissing.d.ts.map +1 -1
  195. package/dist/umd/components/VizElement/HeatmapElements.d.ts +4 -4
  196. package/dist/umd/components/VizElement/HeatmapElements.d.ts.map +1 -1
  197. package/dist/umd/components/VizElement/VizElements.d.ts +4 -3
  198. package/dist/umd/components/VizElement/VizElements.d.ts.map +1 -1
  199. package/dist/umd/components/VizLive/VizLiveHeatmap.d.ts.map +1 -1
  200. package/dist/umd/components/VizScrollmap/HoverZones.d.ts +3 -3
  201. package/dist/umd/components/VizScrollmap/HoverZones.d.ts.map +1 -1
  202. package/dist/umd/components/VizScrollmap/Minimap.d.ts +3 -3
  203. package/dist/umd/components/VizScrollmap/Minimap.d.ts.map +1 -1
  204. package/dist/umd/helpers/canvas-backdrop.d.ts.map +1 -1
  205. package/dist/umd/helpers/viewport/element.d.ts +2 -2
  206. package/dist/umd/helpers/viewport/element.d.ts.map +1 -1
  207. package/dist/umd/helpers/viz-elm-callout/viz-elm.d.ts +2 -1
  208. package/dist/umd/helpers/viz-elm-callout/viz-elm.d.ts.map +1 -1
  209. package/dist/umd/hooks/common/index.d.ts +1 -1
  210. package/dist/umd/hooks/common/index.d.ts.map +1 -1
  211. package/dist/umd/hooks/common/useHeatmapViewportByDevice.d.ts +7 -0
  212. package/dist/umd/hooks/common/useHeatmapViewportByDevice.d.ts.map +1 -0
  213. package/dist/umd/hooks/register/useRegisterConfig.d.ts +3 -1
  214. package/dist/umd/hooks/register/useRegisterConfig.d.ts.map +1 -1
  215. package/dist/umd/hooks/view-context/index.d.ts +1 -0
  216. package/dist/umd/hooks/view-context/index.d.ts.map +1 -1
  217. package/dist/umd/hooks/view-context/useHeatmapCopyView.d.ts.map +1 -1
  218. package/dist/umd/hooks/view-context/useHeatmapDataContext.d.ts +4 -16
  219. package/dist/umd/hooks/view-context/useHeatmapDataContext.d.ts.map +1 -1
  220. package/dist/umd/hooks/view-context/useHeatmapLiveContext.d.ts +34 -0
  221. package/dist/umd/hooks/view-context/useHeatmapLiveContext.d.ts.map +1 -0
  222. package/dist/umd/hooks/view-context/useHeatmapSettingContext.d.ts +3 -1
  223. package/dist/umd/hooks/view-context/useHeatmapSettingContext.d.ts.map +1 -1
  224. package/dist/umd/hooks/view-context/useHeatmapVizContext.d.ts +2 -2
  225. package/dist/umd/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -1
  226. package/dist/umd/hooks/viz-canvas/useClickmap.d.ts.map +1 -1
  227. package/dist/umd/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
  228. package/dist/umd/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
  229. package/dist/umd/hooks/viz-elm/useClickedElement.d.ts +3 -3
  230. package/dist/umd/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
  231. package/dist/umd/hooks/viz-elm/useElementCalloutVisible.d.ts +2 -1
  232. package/dist/umd/hooks/viz-elm/useElementCalloutVisible.d.ts.map +1 -1
  233. package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -3
  234. package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
  235. package/dist/umd/hooks/viz-elm/useHoveredElement.d.ts +3 -1
  236. package/dist/umd/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
  237. package/dist/umd/hooks/viz-live/useVizLiveIframeMsg.d.ts.map +1 -1
  238. package/dist/umd/hooks/viz-live/useVizLiveRender.d.ts.map +1 -1
  239. package/dist/umd/hooks/viz-render/useHeatmapIframeProcessor.d.ts +6 -0
  240. package/dist/umd/hooks/viz-render/useHeatmapIframeProcessor.d.ts.map +1 -0
  241. package/dist/umd/hooks/viz-render/useHeatmapRenderByMode.d.ts +3 -2
  242. package/dist/umd/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -1
  243. package/dist/umd/hooks/viz-render/useHeatmapRenderDom.d.ts +5 -0
  244. package/dist/umd/hooks/viz-render/useHeatmapRenderDom.d.ts.map +1 -0
  245. package/dist/umd/hooks/viz-render/useReplayRender.d.ts +2 -2
  246. package/dist/umd/hooks/viz-render/useReplayRender.d.ts.map +1 -1
  247. package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
  248. package/dist/umd/hooks/viz-scale/useScaleCalculation.d.ts +1 -2
  249. package/dist/umd/hooks/viz-scale/useScaleCalculation.d.ts.map +1 -1
  250. package/dist/umd/index.d.ts +1 -1
  251. package/dist/umd/index.d.ts.map +1 -1
  252. package/dist/umd/index.js +2 -2
  253. package/dist/umd/libs/iframe-processor/index.d.ts +2 -5
  254. package/dist/umd/libs/iframe-processor/index.d.ts.map +1 -1
  255. package/dist/umd/libs/iframe-processor/lifecycle.d.ts +16 -7
  256. package/dist/umd/libs/iframe-processor/lifecycle.d.ts.map +1 -1
  257. package/dist/umd/libs/iframe-processor/orchestrator.d.ts +14 -45
  258. package/dist/umd/libs/iframe-processor/orchestrator.d.ts.map +1 -1
  259. package/dist/umd/libs/iframe-processor/processors/height-observer/index.d.ts +5 -14
  260. package/dist/umd/libs/iframe-processor/processors/height-observer/index.d.ts.map +1 -1
  261. package/dist/umd/libs/iframe-processor/processors/height-observer/listeners.d.ts +11 -0
  262. package/dist/umd/libs/iframe-processor/processors/height-observer/listeners.d.ts.map +1 -0
  263. package/dist/umd/libs/iframe-processor/processors/height-observer/types.d.ts +18 -1
  264. package/dist/umd/libs/iframe-processor/processors/height-observer/types.d.ts.map +1 -1
  265. package/dist/umd/libs/iframe-processor/processors/navigation/index.d.ts +3 -14
  266. package/dist/umd/libs/iframe-processor/processors/navigation/index.d.ts.map +1 -1
  267. package/dist/umd/libs/iframe-processor/processors/navigation/listeners.d.ts +3 -4
  268. package/dist/umd/libs/iframe-processor/processors/navigation/listeners.d.ts.map +1 -1
  269. package/dist/umd/libs/iframe-processor/processors/navigation/types.d.ts +13 -0
  270. package/dist/umd/libs/iframe-processor/processors/navigation/types.d.ts.map +1 -1
  271. package/dist/umd/libs/iframe-processor/processors/viewport/dimensions.d.ts +1 -1
  272. package/dist/umd/libs/iframe-processor/processors/viewport/dimensions.d.ts.map +1 -1
  273. package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/global-fixes/viewport-unit-replacer/fixes.d.ts.map +1 -1
  274. package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/gp-v7-fixes/gem-slider-item/fixes.d.ts.map +1 -1
  275. package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts +0 -1
  276. package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts.map +1 -1
  277. package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/types.d.ts +2 -2
  278. package/dist/umd/libs/iframe-processor/processors/viewport/index.d.ts +3 -9
  279. package/dist/umd/libs/iframe-processor/processors/viewport/index.d.ts.map +1 -1
  280. package/dist/umd/libs/iframe-processor/processors/viewport/listeners.d.ts +3 -2
  281. package/dist/umd/libs/iframe-processor/processors/viewport/listeners.d.ts.map +1 -1
  282. package/dist/umd/libs/iframe-processor/processors/viewport/pipeline.d.ts.map +1 -1
  283. package/dist/umd/libs/iframe-processor/processors/viewport/shop-overrides/types.d.ts +2 -0
  284. package/dist/umd/libs/iframe-processor/processors/viewport/shop-overrides/types.d.ts.map +1 -1
  285. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/constants.d.ts +5 -0
  286. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/constants.d.ts.map +1 -0
  287. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/functions.d.ts +11 -0
  288. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/functions.d.ts.map +1 -0
  289. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/index.d.ts +9 -0
  290. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/index.d.ts.map +1 -0
  291. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/state.d.ts +13 -0
  292. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/state.d.ts.map +1 -0
  293. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/types.d.ts +16 -0
  294. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/types.d.ts.map +1 -0
  295. package/dist/umd/libs/iframe-processor/processors/viewport/types.d.ts +8 -0
  296. package/dist/umd/libs/iframe-processor/processors/viewport/types.d.ts.map +1 -0
  297. package/dist/umd/libs/iframe-processor/shared/factory-types.d.ts +24 -0
  298. package/dist/umd/libs/iframe-processor/shared/factory-types.d.ts.map +1 -0
  299. package/dist/umd/libs/iframe-processor/shared/iframe-types.d.ts +22 -0
  300. package/dist/umd/libs/iframe-processor/shared/iframe-types.d.ts.map +1 -0
  301. package/dist/umd/libs/index.d.ts +1 -0
  302. package/dist/umd/libs/index.d.ts.map +1 -1
  303. package/dist/umd/libs/perf.d.ts +83 -0
  304. package/dist/umd/libs/perf.d.ts.map +1 -0
  305. package/dist/umd/libs/visualizer/GXVisualizer.d.ts +23 -4
  306. package/dist/umd/libs/visualizer/GXVisualizer.d.ts.map +1 -1
  307. package/dist/umd/libs/visualizer/cache/config.d.ts +15 -0
  308. package/dist/umd/libs/visualizer/cache/config.d.ts.map +1 -0
  309. package/dist/umd/libs/visualizer/cache/index.d.ts +16 -0
  310. package/dist/umd/libs/visualizer/cache/index.d.ts.map +1 -0
  311. package/dist/umd/libs/visualizer/index.d.ts +2 -2
  312. package/dist/umd/libs/visualizer/index.d.ts.map +1 -1
  313. package/dist/{esm/libs/visualizer → umd/libs/visualizer/renderers}/AttentionMapRenderer.d.ts +2 -2
  314. package/dist/umd/libs/visualizer/renderers/AttentionMapRenderer.d.ts.map +1 -0
  315. package/dist/umd/libs/visualizer/renderers/ScrollBucketRenderer.d.ts +44 -0
  316. package/dist/umd/libs/visualizer/renderers/ScrollBucketRenderer.d.ts.map +1 -0
  317. package/dist/umd/libs/visualizer/renderers/index.d.ts +3 -0
  318. package/dist/umd/libs/visualizer/renderers/index.d.ts.map +1 -0
  319. package/dist/umd/libs/visualizer/shadow-dom/extractor.d.ts +23 -0
  320. package/dist/umd/libs/visualizer/shadow-dom/extractor.d.ts.map +1 -0
  321. package/dist/umd/libs/visualizer/types/data.d.ts +72 -0
  322. package/dist/umd/libs/visualizer/types/data.d.ts.map +1 -0
  323. package/dist/umd/libs/visualizer/{types.d.ts → types/extend.d.ts} +7 -2
  324. package/dist/umd/libs/visualizer/types/extend.d.ts.map +1 -0
  325. package/dist/umd/libs/visualizer/types/index.d.ts +6 -0
  326. package/dist/umd/libs/visualizer/types/index.d.ts.map +1 -0
  327. package/dist/umd/libs/visualizer/types/layout.d.ts +69 -0
  328. package/dist/umd/libs/visualizer/types/layout.d.ts.map +1 -0
  329. package/dist/umd/libs/visualizer/types/visualize.d.ts +26 -0
  330. package/dist/umd/libs/visualizer/types/visualize.d.ts.map +1 -0
  331. package/dist/umd/libs/visualizer/utils/delay.d.ts +21 -0
  332. package/dist/umd/libs/visualizer/utils/delay.d.ts.map +1 -0
  333. package/dist/umd/libs/visualizer/utils/render.d.ts +7 -0
  334. package/dist/umd/libs/visualizer/utils/render.d.ts.map +1 -0
  335. package/dist/umd/stores/config.d.ts +4 -2
  336. package/dist/umd/stores/config.d.ts.map +1 -1
  337. package/dist/umd/stores/data.d.ts +4 -0
  338. package/dist/umd/stores/data.d.ts.map +1 -1
  339. package/dist/umd/stores/mode-live.d.ts +30 -16
  340. package/dist/umd/stores/mode-live.d.ts.map +1 -1
  341. package/dist/umd/stores/setting.d.ts +3 -1
  342. package/dist/umd/stores/setting.d.ts.map +1 -1
  343. package/dist/umd/stores/viz.d.ts +2 -2
  344. package/dist/umd/types/clarity.d.ts +18 -0
  345. package/dist/umd/types/clarity.d.ts.map +1 -1
  346. package/dist/umd/types/control.d.ts +2 -0
  347. package/dist/umd/types/control.d.ts.map +1 -1
  348. package/dist/umd/types/heatmap-info.d.ts +1 -0
  349. package/dist/umd/types/heatmap-info.d.ts.map +1 -1
  350. package/dist/umd/types/heatmap.d.ts +9 -0
  351. package/dist/umd/types/heatmap.d.ts.map +1 -1
  352. package/dist/umd/types/iframe-helper.d.ts +0 -19
  353. package/dist/umd/types/iframe-helper.d.ts.map +1 -1
  354. package/dist/umd/types/viz-elm-callout.d.ts +26 -1
  355. package/dist/umd/types/viz-elm-callout.d.ts.map +1 -1
  356. package/dist/umd/types/viz-scrollmap.d.ts +8 -2
  357. package/dist/umd/types/viz-scrollmap.d.ts.map +1 -1
  358. package/dist/umd/utils/dom.d.ts +1 -1
  359. package/dist/umd/utils/dom.d.ts.map +1 -1
  360. package/dist/umd/utils/retry.d.ts +1 -0
  361. package/dist/umd/utils/retry.d.ts.map +1 -1
  362. package/package.json +4 -4
  363. package/dist/esm/hooks/common/useHeatmapWidthByDevice.d.ts +0 -2
  364. package/dist/esm/hooks/common/useHeatmapWidthByDevice.d.ts.map +0 -1
  365. package/dist/esm/hooks/viz-canvas/useAreamap.d.ts +0 -14
  366. package/dist/esm/hooks/viz-canvas/useAreamap.d.ts.map +0 -1
  367. package/dist/esm/hooks/viz-canvas/useAttentionMap.d.ts +0 -9
  368. package/dist/esm/hooks/viz-canvas/useAttentionMap.d.ts.map +0 -1
  369. package/dist/esm/hooks/viz-render/useHeatmapRender.d.ts +0 -6
  370. package/dist/esm/hooks/viz-render/useHeatmapRender.d.ts.map +0 -1
  371. package/dist/esm/hooks/viz-scale/useContentDimensions.d.ts +0 -9
  372. package/dist/esm/hooks/viz-scale/useContentDimensions.d.ts.map +0 -1
  373. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer.d.ts +0 -54
  374. package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer.d.ts.map +0 -1
  375. package/dist/esm/libs/visualizer/AttentionMapRenderer.d.ts.map +0 -1
  376. package/dist/esm/libs/visualizer/ClickHeatmapRenderer.d.ts +0 -30
  377. package/dist/esm/libs/visualizer/ClickHeatmapRenderer.d.ts.map +0 -1
  378. package/dist/esm/libs/visualizer/ScrollHeatmapRenderer.d.ts +0 -17
  379. package/dist/esm/libs/visualizer/ScrollHeatmapRenderer.d.ts.map +0 -1
  380. package/dist/esm/libs/visualizer/types.d.ts.map +0 -1
  381. package/dist/umd/hooks/common/useHeatmapWidthByDevice.d.ts +0 -2
  382. package/dist/umd/hooks/common/useHeatmapWidthByDevice.d.ts.map +0 -1
  383. package/dist/umd/hooks/viz-canvas/useAreamap.d.ts +0 -14
  384. package/dist/umd/hooks/viz-canvas/useAreamap.d.ts.map +0 -1
  385. package/dist/umd/hooks/viz-canvas/useAttentionMap.d.ts +0 -9
  386. package/dist/umd/hooks/viz-canvas/useAttentionMap.d.ts.map +0 -1
  387. package/dist/umd/hooks/viz-render/useHeatmapRender.d.ts +0 -6
  388. package/dist/umd/hooks/viz-render/useHeatmapRender.d.ts.map +0 -1
  389. package/dist/umd/hooks/viz-scale/useContentDimensions.d.ts +0 -9
  390. package/dist/umd/hooks/viz-scale/useContentDimensions.d.ts.map +0 -1
  391. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer.d.ts +0 -54
  392. package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer.d.ts.map +0 -1
  393. package/dist/umd/libs/visualizer/AttentionMapRenderer.d.ts.map +0 -1
  394. package/dist/umd/libs/visualizer/ClickHeatmapRenderer.d.ts +0 -30
  395. package/dist/umd/libs/visualizer/ClickHeatmapRenderer.d.ts.map +0 -1
  396. package/dist/umd/libs/visualizer/ScrollHeatmapRenderer.d.ts +0 -17
  397. package/dist/umd/libs/visualizer/ScrollHeatmapRenderer.d.ts.map +0 -1
  398. package/dist/umd/libs/visualizer/types.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";
@@ -161,6 +162,11 @@ var EHeatmapType;
161
162
  EHeatmapType["Click"] = "click";
162
163
  EHeatmapType["Scroll"] = "scroll";
163
164
  })(EHeatmapType || (EHeatmapType = {}));
165
+ var EHeatmapMode;
166
+ (function (EHeatmapMode) {
167
+ EHeatmapMode["Heatmap"] = "heatmap";
168
+ EHeatmapMode["Replay"] = "replay";
169
+ })(EHeatmapMode || (EHeatmapMode = {}));
164
170
  var EClickType;
165
171
  (function (EClickType) {
166
172
  /** Return all clicks (default) */
@@ -197,12 +203,16 @@ var EScrollType;
197
203
  EScrollType["Attention"] = "ATTENTION";
198
204
  EScrollType["Revenue"] = "REVENUE";
199
205
  })(EScrollType || (EScrollType = {}));
200
- var EHeatmapMode;
201
206
  (function (EHeatmapMode) {
202
207
  EHeatmapMode["Single"] = "single";
203
208
  EHeatmapMode["Live"] = "live";
204
209
  EHeatmapMode["Compare"] = "compare";
205
210
  })(EHeatmapMode || (EHeatmapMode = {}));
211
+ var EHeatmapDataSource;
212
+ (function (EHeatmapDataSource) {
213
+ EHeatmapDataSource["Snapshot"] = "snapshot";
214
+ EHeatmapDataSource["Live"] = "live";
215
+ })(EHeatmapDataSource || (EHeatmapDataSource = {}));
206
216
 
207
217
  const ViewIdContext = createContext(undefined);
208
218
  const useViewIdContext = () => {
@@ -314,18 +324,21 @@ const useHeatmapConfigStore = create()((set) => {
314
324
  return {
315
325
  mode: EHeatmapMode.Single,
316
326
  sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
317
- clickMode: EClickMode.Default,
318
- isRendering: true,
327
+ shopId: undefined,
328
+ excludeClassNames: [],
319
329
  setMode: (mode) => set({ mode }),
320
330
  resetMode: () => set({ mode: EHeatmapMode.Single }),
321
331
  setSidebarWidth: (sidebarWidth) => set({ sidebarWidth }),
322
- setIsRendering: (isRendering) => set({ isRendering }),
332
+ setShopId: (shopId) => set({ shopId }),
333
+ setExcludeClassNames: (excludeClassNames) => set({ excludeClassNames }),
323
334
  };
324
335
  });
325
336
 
326
337
  const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
327
338
  return {
328
339
  data: new Map([[DEFAULT_VIEW_ID, undefined]]),
340
+ dataHash: new Map([[DEFAULT_VIEW_ID, undefined]]),
341
+ dataSnapshot: new Map([[DEFAULT_VIEW_ID, undefined]]),
329
342
  clickmap: new Map([[DEFAULT_VIEW_ID, undefined]]),
330
343
  clickAreas: new Map([[DEFAULT_VIEW_ID, undefined]]),
331
344
  dataInfo: new Map([[DEFAULT_VIEW_ID, undefined]]),
@@ -358,6 +371,16 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
358
371
  newData.set(viewId, data);
359
372
  return { data: newData };
360
373
  }),
374
+ setDataHash: (dataHash, viewId = DEFAULT_VIEW_ID) => set((prev) => {
375
+ const newDataHash = new Map(prev.dataHash);
376
+ newDataHash.set(viewId, dataHash);
377
+ return { dataHash: newDataHash };
378
+ }),
379
+ setDataSnapshot: (data, viewId = DEFAULT_VIEW_ID) => set((prev) => {
380
+ const newDataSnapshot = new Map(prev.dataSnapshot);
381
+ newDataSnapshot.set(viewId, data);
382
+ return { dataSnapshot: newDataSnapshot };
383
+ }),
361
384
  setClickmap: (clickmap, viewId = DEFAULT_VIEW_ID) => set((prev) => {
362
385
  const newClickmap = new Map(prev.clickmap);
363
386
  newClickmap.set(viewId, clickmap);
@@ -380,12 +403,16 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
380
403
  }),
381
404
  copyView: (fromViewId, toViewId) => set((prev) => {
382
405
  const newData = new Map(prev.data);
406
+ const newDataSnapshot = new Map(prev.dataSnapshot);
383
407
  const newClickmap = new Map(prev.clickmap);
384
408
  const newClickAreas = new Map(prev.clickAreas);
385
409
  const newDataInfo = new Map(prev.dataInfo);
386
410
  const newScrollmap = new Map(prev.scrollmap);
387
411
  const newAttentionMap = new Map(prev.attentionMap);
412
+ const newDataHash = new Map(prev.dataHash);
388
413
  newData.set(toViewId, prev.data.get(fromViewId));
414
+ newDataSnapshot.set(toViewId, prev.dataSnapshot.get(fromViewId));
415
+ newDataHash.set(toViewId, prev.dataHash.get(fromViewId));
389
416
  newClickmap.set(toViewId, prev.clickmap.get(fromViewId));
390
417
  newClickAreas.set(toViewId, prev.clickAreas.get(fromViewId));
391
418
  newDataInfo.set(toViewId, prev.dataInfo.get(fromViewId));
@@ -393,21 +420,27 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
393
420
  newAttentionMap.set(toViewId, prev.attentionMap.get(fromViewId));
394
421
  return {
395
422
  data: newData,
423
+ dataSnapshot: newDataSnapshot,
396
424
  clickmap: newClickmap,
397
425
  clickAreas: newClickAreas,
398
426
  dataInfo: newDataInfo,
399
427
  scrollmap: newScrollmap,
400
428
  attentionMap: newAttentionMap,
429
+ dataHash: newDataHash,
401
430
  };
402
431
  }),
403
432
  clearView: (viewId) => set((prev) => {
404
433
  const newData = new Map(prev.data);
434
+ const newDataSnapshot = new Map(prev.dataSnapshot);
405
435
  const newClickmap = new Map(prev.clickmap);
406
436
  const newClickAreas = new Map(prev.clickAreas);
407
437
  const newDataInfo = new Map(prev.dataInfo);
408
438
  const newScrollmap = new Map(prev.scrollmap);
409
439
  const newAttentionMap = new Map(prev.attentionMap);
440
+ const newDataHash = new Map(prev.dataHash);
410
441
  newData.delete(viewId);
442
+ newDataSnapshot.delete(viewId);
443
+ newDataHash.delete(viewId);
411
444
  newClickmap.delete(viewId);
412
445
  newClickAreas.delete(viewId);
413
446
  newDataInfo.delete(viewId);
@@ -415,6 +448,8 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
415
448
  newAttentionMap.delete(viewId);
416
449
  return {
417
450
  data: newData,
451
+ dataSnapshot: newDataSnapshot,
452
+ dataHash: newDataHash,
418
453
  clickmap: newClickmap,
419
454
  clickAreas: newClickAreas,
420
455
  dataInfo: newDataInfo,
@@ -424,6 +459,8 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
424
459
  }),
425
460
  resetAll: () => set({
426
461
  data: new Map([[DEFAULT_VIEW_ID, undefined]]),
462
+ dataSnapshot: new Map([[DEFAULT_VIEW_ID, undefined]]),
463
+ dataHash: new Map([[DEFAULT_VIEW_ID, undefined]]),
427
464
  clickmap: new Map([[DEFAULT_VIEW_ID, undefined]]),
428
465
  clickAreas: new Map([[DEFAULT_VIEW_ID, undefined]]),
429
466
  dataInfo: new Map([[DEFAULT_VIEW_ID, undefined]]),
@@ -445,6 +482,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
445
482
  clickMode: new Map([[DEFAULT_VIEW_ID, EClickMode.Default]]),
446
483
  scrollType: new Map([[DEFAULT_VIEW_ID, EScrollType.Depth]]),
447
484
  heatmapType: new Map([[DEFAULT_VIEW_ID, EHeatmapType.Click]]),
485
+ dataSource: new Map([[DEFAULT_VIEW_ID, EHeatmapDataSource.Snapshot]]),
448
486
  setIsRendering: (isRendering, viewId = DEFAULT_VIEW_ID) => set((prev) => {
449
487
  const newIsRendering = new Map(prev.isRendering);
450
488
  newIsRendering.set(viewId, isRendering);
@@ -495,6 +533,11 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
495
533
  newHeatmapType.set(viewId, heatmapType);
496
534
  return { heatmapType: newHeatmapType };
497
535
  }),
536
+ setDataSource: (dataSource, viewId = DEFAULT_VIEW_ID) => set((prev) => {
537
+ const newDataSource = new Map(prev.dataSource);
538
+ newDataSource.set(viewId, dataSource);
539
+ return { dataSource: newDataSource };
540
+ }),
498
541
  copyView: (fromViewId, toViewId) => set((prev) => {
499
542
  const newIsLoadingDom = new Map(prev.isLoadingDom);
500
543
  const newIsLoadingCanvas = new Map(prev.isLoadingCanvas);
@@ -505,6 +548,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
505
548
  const newClickMode = new Map(prev.clickMode);
506
549
  const newScrollType = new Map(prev.scrollType);
507
550
  const newHeatmapType = new Map(prev.heatmapType);
551
+ const newDataSource = new Map(prev.dataSource);
508
552
  newIsShowSidebar.set(toViewId, prev.isShowSidebar.get(fromViewId) ?? false);
509
553
  newRankedBy.set(toViewId, prev.rankedBy.get(fromViewId));
510
554
  newDeviceType.set(toViewId, prev.deviceType.get(fromViewId));
@@ -514,6 +558,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
514
558
  newHeatmapType.set(toViewId, prev.heatmapType.get(fromViewId));
515
559
  newIsLoadingDom.set(toViewId, prev.isLoadingDom.get(fromViewId) ?? false);
516
560
  newIsLoadingCanvas.set(toViewId, prev.isLoadingCanvas.get(fromViewId) ?? false);
561
+ newDataSource.set(toViewId, prev.dataSource.get(fromViewId) ?? EHeatmapDataSource.Snapshot);
517
562
  return {
518
563
  isShowSidebar: newIsShowSidebar,
519
564
  rankedBy: newRankedBy,
@@ -524,6 +569,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
524
569
  clickMode: newClickMode,
525
570
  scrollType: newScrollType,
526
571
  heatmapType: newHeatmapType,
572
+ dataSource: newDataSource,
527
573
  };
528
574
  }),
529
575
  clearView: (viewId) => set((prev) => {
@@ -536,6 +582,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
536
582
  const newClickMode = new Map(prev.clickMode);
537
583
  const newScrollType = new Map(prev.scrollType);
538
584
  const newHeatmapType = new Map(prev.heatmapType);
585
+ const newDataSource = new Map(prev.dataSource);
539
586
  newIsShowSidebar.delete(viewId);
540
587
  newRankedBy.delete(viewId);
541
588
  newIsLoadingDom.delete(viewId);
@@ -545,6 +592,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
545
592
  newClickMode.delete(viewId);
546
593
  newScrollType.delete(viewId);
547
594
  newHeatmapType.delete(viewId);
595
+ newDataSource.delete(viewId);
548
596
  return {
549
597
  isShowSidebar: newIsShowSidebar,
550
598
  rankedBy: newRankedBy,
@@ -555,6 +603,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
555
603
  clickMode: newClickMode,
556
604
  scrollType: newScrollType,
557
605
  heatmapType: newHeatmapType,
606
+ dataSource: newDataSource,
558
607
  };
559
608
  }),
560
609
  resetAll: () => set({
@@ -568,22 +617,23 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
568
617
  clickMode: new Map([[DEFAULT_VIEW_ID, EClickMode.Default]]),
569
618
  scrollType: new Map([[DEFAULT_VIEW_ID, EScrollType.Depth]]),
570
619
  heatmapType: new Map([[DEFAULT_VIEW_ID, EHeatmapType.Click]]),
620
+ dataSource: new Map([[DEFAULT_VIEW_ID, EHeatmapDataSource.Snapshot]]),
571
621
  }),
572
622
  };
573
623
  }));
574
624
 
575
625
  const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
576
626
  return {
577
- isRenderViz: new Map([[DEFAULT_VIEW_ID, false]]),
627
+ isDomLoaded: new Map([[DEFAULT_VIEW_ID, false]]),
578
628
  zoomRatio: new Map([[DEFAULT_VIEW_ID, DEFAULT_ZOOM_RATIO.DEFAULT]]),
579
629
  minZoomRatio: new Map([[DEFAULT_VIEW_ID, DEFAULT_ZOOM_RATIO.MIN]]),
580
630
  maxZoomRatio: new Map([[DEFAULT_VIEW_ID, DEFAULT_ZOOM_RATIO.MAX]]),
581
631
  scale: new Map([[DEFAULT_VIEW_ID, 1]]),
582
632
  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 };
633
+ setIsDomLoaded: (isDomLoaded, viewId = DEFAULT_VIEW_ID) => set((prev) => {
634
+ const newIsDomLoaded = new Map(prev.isDomLoaded);
635
+ newIsDomLoaded.set(viewId, isDomLoaded);
636
+ return { isDomLoaded: newIsDomLoaded };
587
637
  }),
588
638
  setZoomRatio: (zoomRatio, viewId = DEFAULT_VIEW_ID) => set((prev) => {
589
639
  const newZoomRatio = new Map(prev.zoomRatio);
@@ -611,18 +661,18 @@ const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
611
661
  return { isScaledToFit: newIsScaledToFit };
612
662
  }),
613
663
  copyView: (fromViewId, toViewId) => set((prev) => {
614
- const newIsRenderViz = new Map(prev.isRenderViz);
664
+ const newIsDomLoaded = new Map(prev.isDomLoaded);
615
665
  const newZoomRatio = new Map(prev.zoomRatio);
616
666
  const newMinZoomRatio = new Map(prev.minZoomRatio);
617
667
  const newScale = new Map(prev.scale);
618
668
  const newIsScaledToFit = new Map(prev.isScaledToFit);
619
- newIsRenderViz.set(toViewId, prev.isRenderViz.get(fromViewId) ?? false);
669
+ newIsDomLoaded.set(toViewId, prev.isDomLoaded.get(fromViewId) ?? false);
620
670
  newZoomRatio.set(toViewId, prev.zoomRatio.get(fromViewId) ?? 100);
621
671
  newMinZoomRatio.set(toViewId, prev.minZoomRatio.get(fromViewId) ?? 10);
622
672
  newScale.set(toViewId, prev.scale.get(fromViewId) ?? 1);
623
673
  newIsScaledToFit.set(toViewId, prev.isScaledToFit.get(fromViewId) ?? false);
624
674
  return {
625
- isRenderViz: newIsRenderViz,
675
+ isDomLoaded: newIsDomLoaded,
626
676
  zoomRatio: newZoomRatio,
627
677
  minZoomRatio: newMinZoomRatio,
628
678
  scale: newScale,
@@ -630,18 +680,18 @@ const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
630
680
  };
631
681
  }),
632
682
  clearView: (viewId) => set((prev) => {
633
- const newIsRenderViz = new Map(prev.isRenderViz);
683
+ const newIsDomLoaded = new Map(prev.isDomLoaded);
634
684
  const newZoomRatio = new Map(prev.zoomRatio);
635
685
  const newMinZoomRatio = new Map(prev.minZoomRatio);
636
686
  const newScale = new Map(prev.scale);
637
687
  const newIsScaledToFit = new Map(prev.isScaledToFit);
638
- newIsRenderViz.delete(viewId);
688
+ newIsDomLoaded.delete(viewId);
639
689
  newZoomRatio.delete(viewId);
640
690
  newMinZoomRatio.delete(viewId);
641
691
  newScale.delete(viewId);
642
692
  newIsScaledToFit.delete(viewId);
643
693
  return {
644
- isRenderViz: newIsRenderViz,
694
+ isDomLoaded: newIsDomLoaded,
645
695
  zoomRatio: newZoomRatio,
646
696
  minZoomRatio: newMinZoomRatio,
647
697
  scale: newScale,
@@ -649,7 +699,7 @@ const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
649
699
  };
650
700
  }),
651
701
  resetAll: () => set({
652
- isRenderViz: new Map([[DEFAULT_VIEW_ID, false]]),
702
+ isDomLoaded: new Map([[DEFAULT_VIEW_ID, false]]),
653
703
  zoomRatio: new Map([[DEFAULT_VIEW_ID, 100]]),
654
704
  minZoomRatio: new Map([[DEFAULT_VIEW_ID, 10]]),
655
705
  scale: new Map([[DEFAULT_VIEW_ID, 1]]),
@@ -1047,24 +1097,108 @@ const useHeatmapCompareStore = create()((set, get) => {
1047
1097
  });
1048
1098
 
1049
1099
  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
- });
1100
+ decodedPayloads: new Map([[DEFAULT_VIEW_ID, []]]),
1101
+ encodedPayloads: new Map([[DEFAULT_VIEW_ID, []]]),
1102
+ htmlContent: new Map([[DEFAULT_VIEW_ID, '']]),
1103
+ targetUrl: new Map([[DEFAULT_VIEW_ID, '']]),
1104
+ renderMode: new Map([[DEFAULT_VIEW_ID, 'portal']]),
1105
+ storefrontPassword: new Map([[DEFAULT_VIEW_ID, '']]),
1106
+ };
1107
+ const useHeatmapLiveStore = create()(subscribeWithSelector((set) => ({
1108
+ ...initialState,
1109
+ addPayload: (payload, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1110
+ const newDecoded = new Map(prev.decodedPayloads);
1111
+ newDecoded.set(viewId, [...(newDecoded.get(viewId) ?? []), payload]);
1112
+ const newEncoded = new Map(prev.encodedPayloads);
1113
+ newEncoded.set(viewId, [...(newEncoded.get(viewId) ?? []), JSON.stringify(payload)]);
1114
+ return { decodedPayloads: newDecoded, encodedPayloads: newEncoded };
1115
+ }),
1116
+ setPayloads: (payloads, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1117
+ const newDecoded = new Map(prev.decodedPayloads);
1118
+ newDecoded.set(viewId, payloads);
1119
+ const newEncoded = new Map(prev.encodedPayloads);
1120
+ newEncoded.set(viewId, payloads.map((p) => JSON.stringify(p)));
1121
+ return { decodedPayloads: newDecoded, encodedPayloads: newEncoded };
1122
+ }),
1123
+ setEncodedPayloads: (payloads, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1124
+ const newEncoded = new Map(prev.encodedPayloads);
1125
+ newEncoded.set(viewId, payloads);
1126
+ return { encodedPayloads: newEncoded };
1127
+ }),
1128
+ setHtmlContent: (htmlContent, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1129
+ const newHtmlContent = new Map(prev.htmlContent);
1130
+ newHtmlContent.set(viewId, htmlContent);
1131
+ return { htmlContent: newHtmlContent };
1132
+ }),
1133
+ setTargetUrl: (targetUrl, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1134
+ const newTargetUrl = new Map(prev.targetUrl);
1135
+ newTargetUrl.set(viewId, targetUrl);
1136
+ return { targetUrl: newTargetUrl };
1137
+ }),
1138
+ setRenderMode: (renderMode, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1139
+ const newRenderMode = new Map(prev.renderMode);
1140
+ newRenderMode.set(viewId, renderMode);
1141
+ return { renderMode: newRenderMode };
1142
+ }),
1143
+ setStorefrontPassword: (storefrontPassword, viewId = DEFAULT_VIEW_ID) => set((prev) => {
1144
+ const newStorefrontPassword = new Map(prev.storefrontPassword);
1145
+ newStorefrontPassword.set(viewId, storefrontPassword);
1146
+ return { storefrontPassword: newStorefrontPassword };
1147
+ }),
1148
+ resetView: (viewId = DEFAULT_VIEW_ID) => set((prev) => {
1149
+ const newDecoded = new Map(prev.decodedPayloads);
1150
+ newDecoded.set(viewId, []);
1151
+ const newEncoded = new Map(prev.encodedPayloads);
1152
+ newEncoded.set(viewId, []);
1153
+ return { decodedPayloads: newDecoded, encodedPayloads: newEncoded };
1154
+ }),
1155
+ copyView: (fromViewId, toViewId) => set((prev) => {
1156
+ const newDecoded = new Map(prev.decodedPayloads);
1157
+ const newEncoded = new Map(prev.encodedPayloads);
1158
+ const newHtmlContent = new Map(prev.htmlContent);
1159
+ const newTargetUrl = new Map(prev.targetUrl);
1160
+ const newRenderMode = new Map(prev.renderMode);
1161
+ const newStorefrontPassword = new Map(prev.storefrontPassword);
1162
+ newDecoded.set(toViewId, prev.decodedPayloads.get(fromViewId) ?? []);
1163
+ newEncoded.set(toViewId, prev.encodedPayloads.get(fromViewId) ?? []);
1164
+ newHtmlContent.set(toViewId, prev.htmlContent.get(fromViewId) ?? '');
1165
+ newTargetUrl.set(toViewId, prev.targetUrl.get(fromViewId) ?? '');
1166
+ newRenderMode.set(toViewId, prev.renderMode.get(fromViewId) ?? 'portal');
1167
+ newStorefrontPassword.set(toViewId, prev.storefrontPassword.get(fromViewId) ?? '');
1168
+ return {
1169
+ decodedPayloads: newDecoded,
1170
+ encodedPayloads: newEncoded,
1171
+ htmlContent: newHtmlContent,
1172
+ targetUrl: newTargetUrl,
1173
+ renderMode: newRenderMode,
1174
+ storefrontPassword: newStorefrontPassword,
1175
+ };
1176
+ }),
1177
+ clearView: (viewId) => set((prev) => {
1178
+ const newDecoded = new Map(prev.decodedPayloads);
1179
+ const newEncoded = new Map(prev.encodedPayloads);
1180
+ const newHtmlContent = new Map(prev.htmlContent);
1181
+ const newTargetUrl = new Map(prev.targetUrl);
1182
+ const newRenderMode = new Map(prev.renderMode);
1183
+ const newStorefrontPassword = new Map(prev.storefrontPassword);
1184
+ newDecoded.delete(viewId);
1185
+ newEncoded.delete(viewId);
1186
+ newHtmlContent.delete(viewId);
1187
+ newTargetUrl.delete(viewId);
1188
+ newRenderMode.delete(viewId);
1189
+ newStorefrontPassword.delete(viewId);
1190
+ return {
1191
+ decodedPayloads: newDecoded,
1192
+ encodedPayloads: newEncoded,
1193
+ htmlContent: newHtmlContent,
1194
+ targetUrl: newTargetUrl,
1195
+ renderMode: newRenderMode,
1196
+ storefrontPassword: newStorefrontPassword,
1197
+ };
1198
+ }),
1199
+ resetAll: () => set(initialState),
1200
+ reset: () => set(initialState),
1201
+ })));
1068
1202
 
1069
1203
  const useHeatmapVizRectStore = create()(subscribeWithSelector((set) => {
1070
1204
  return {
@@ -1170,26 +1304,12 @@ const useHeatmapClickContext = createViewContextHook({
1170
1304
  }),
1171
1305
  });
1172
1306
 
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
1307
  const useHeatmapDataContext = createViewContextHook({
1190
1308
  useStore: useHeatmapDataStore,
1191
1309
  getState: (store, viewId) => ({
1192
1310
  data: store.data.get(viewId),
1311
+ dataHash: store.dataHash.get(viewId),
1312
+ dataSnapshot: store.dataSnapshot.get(viewId),
1193
1313
  clickmap: store.clickmap.get(viewId),
1194
1314
  clickAreas: store.clickAreas.get(viewId),
1195
1315
  scrollmap: store.scrollmap.get(viewId),
@@ -1199,6 +1319,8 @@ const useHeatmapDataContext = createViewContextHook({
1199
1319
  }),
1200
1320
  getActions: (store, viewId) => ({
1201
1321
  setData: (newData) => store.setData(newData, viewId),
1322
+ setDataSnapshot: (newData) => store.setDataSnapshot(newData, viewId),
1323
+ setDataHash: (newHash) => store.setDataHash(newHash, viewId),
1202
1324
  setClickmap: (newClickmap) => store.setClickmap(newClickmap, viewId),
1203
1325
  setClickAreas: (newClickAreas) => store.setClickAreas(newClickAreas, viewId),
1204
1326
  setDataInfoByKey: (key, value) => store.setDataInfoByKey(key, value, viewId),
@@ -1221,6 +1343,32 @@ const useHeatmapHoverContext = createViewContextHook({
1221
1343
  }),
1222
1344
  });
1223
1345
 
1346
+ const useHeatmapLiveContext = createViewContextHook({
1347
+ useStore: useHeatmapLiveStore,
1348
+ getState: (store, viewId) => ({
1349
+ decodedPayloads: store.decodedPayloads.get(viewId) ?? [],
1350
+ encodedPayloads: store.encodedPayloads.get(viewId) ?? [],
1351
+ htmlContent: store.htmlContent.get(viewId) ?? '',
1352
+ targetUrl: store.targetUrl.get(viewId) ?? '',
1353
+ renderMode: store.renderMode.get(viewId) ?? 'portal',
1354
+ storefrontPassword: store.storefrontPassword.get(viewId) ?? '',
1355
+ }),
1356
+ getActions: (store, viewId) => ({
1357
+ addPayload: (payload) => store.addPayload(payload, viewId),
1358
+ setPayloads: (payloads) => store.setPayloads(payloads, viewId),
1359
+ setEncodedPayloads: (payloads) => store.setEncodedPayloads(payloads, viewId),
1360
+ setHtmlContent: (htmlContent) => store.setHtmlContent(htmlContent, viewId),
1361
+ setTargetUrl: (targetUrl) => store.setTargetUrl(targetUrl, viewId),
1362
+ setRenderMode: (mode) => store.setRenderMode(mode, viewId),
1363
+ setStorefrontPassword: (password) => store.setStorefrontPassword(password, viewId),
1364
+ resetView: () => store.resetView(viewId),
1365
+ copyView: (fromViewId, toViewId) => store.copyView(fromViewId, toViewId),
1366
+ clearView: (viewId) => store.clearView(viewId),
1367
+ resetAll: () => store.resetAll(),
1368
+ reset: () => store.reset(),
1369
+ }),
1370
+ });
1371
+
1224
1372
  const useHeatmapScrollContext = createViewContextHook({
1225
1373
  useStore: useHeatmapVizScrollStore,
1226
1374
  getState: (store, viewId) => ({
@@ -1250,6 +1398,7 @@ const useHeatmapSettingContext = createViewContextHook({
1250
1398
  clickMode: store.clickMode.get(viewId),
1251
1399
  scrollType: store.scrollType.get(viewId),
1252
1400
  heatmapType: store.heatmapType.get(viewId),
1401
+ dataSource: store.dataSource.get(viewId) ?? EHeatmapDataSource.Snapshot,
1253
1402
  }),
1254
1403
  getActions: (store, viewId) => ({
1255
1404
  setIsShowSidebar: (isShowSidebar) => store.setIsShowSidebar(isShowSidebar, viewId),
@@ -1262,6 +1411,7 @@ const useHeatmapSettingContext = createViewContextHook({
1262
1411
  setIsRendering: (isRendering) => store.setIsRendering(isRendering, viewId),
1263
1412
  setIsLoadingDom: (isLoadingDom) => store.setIsLoadingDom(isLoadingDom, viewId),
1264
1413
  setIsLoadingCanvas: (isLoadingCanvas) => store.setIsLoadingCanvas(isLoadingCanvas, viewId),
1414
+ setDataSource: (dataSource) => store.setDataSource(dataSource, viewId),
1265
1415
  clearView: (viewId) => store.clearView(viewId),
1266
1416
  }),
1267
1417
  });
@@ -1269,7 +1419,7 @@ const useHeatmapSettingContext = createViewContextHook({
1269
1419
  const useHeatmapVizContext = createViewContextHook({
1270
1420
  useStore: useHeatmapVizStore,
1271
1421
  getState: (store, viewId) => ({
1272
- isRenderViz: store.isRenderViz.get(viewId) ?? false,
1422
+ isDomLoaded: store.isDomLoaded.get(viewId) ?? false,
1273
1423
  zoomRatio: store.zoomRatio.get(viewId) ?? DEFAULT_ZOOM_RATIO.DEFAULT,
1274
1424
  minZoomRatio: store.minZoomRatio.get(viewId) ?? DEFAULT_ZOOM_RATIO.MIN,
1275
1425
  maxZoomRatio: store.maxZoomRatio.get(viewId) ?? DEFAULT_ZOOM_RATIO.MAX,
@@ -1277,7 +1427,7 @@ const useHeatmapVizContext = createViewContextHook({
1277
1427
  isScaledToFit: store.isScaledToFit.get(viewId) ?? false,
1278
1428
  }),
1279
1429
  getActions: (store, viewId) => ({
1280
- setIsRenderViz: (value) => store.setIsRenderViz(value, viewId),
1430
+ setIsDomLoaded: (value) => store.setIsDomLoaded(value, viewId),
1281
1431
  setZoomRatio: (value) => store.setZoomRatio(value, viewId),
1282
1432
  setMinZoomRatio: (value) => store.setMinZoomRatio(value, viewId),
1283
1433
  setMaxZoomRatio: (value) => store.setMaxZoomRatio(value, viewId),
@@ -1311,9 +1461,7 @@ const useHeatmapCopyView = () => {
1311
1461
  const copyVizView = useHeatmapVizStore((state) => state.copyView);
1312
1462
  const copyVizClickView = useHeatmapVizClickStore((state) => state.copyView);
1313
1463
  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);
1464
+ const copyLiveView = useHeatmapLiveStore((state) => state.copyView);
1317
1465
  const clearDataView = useHeatmapDataStore((state) => state.clearView);
1318
1466
  const clearSettingView = useHeatmapSettingStore((state) => state.clearView);
1319
1467
  const clearVizView = useHeatmapVizStore((state) => state.clearView);
@@ -1322,6 +1470,7 @@ const useHeatmapCopyView = () => {
1322
1470
  const clearVizHoverView = useHeatmapVizHoverStore((state) => state.clearView);
1323
1471
  const clearVizScrollView = useHeatmapVizScrollStore((state) => state.clearView);
1324
1472
  const clearVizAreaClickView = useHeatmapVizClickAreaStore((state) => state.clearView);
1473
+ const clearLiveView = useHeatmapLiveStore((state) => state.clearView);
1325
1474
  const resetDataAll = useHeatmapDataStore((state) => state.resetAll);
1326
1475
  const resetSettingAll = useHeatmapSettingStore((state) => state.resetAll);
1327
1476
  const resetVizAll = useHeatmapVizStore((state) => state.resetAll);
@@ -1330,15 +1479,14 @@ const useHeatmapCopyView = () => {
1330
1479
  const resetVizHoverAll = useHeatmapVizHoverStore((state) => state.resetAll);
1331
1480
  const resetVizScrollViewAll = useHeatmapVizScrollStore((state) => state.resetAll);
1332
1481
  const resetVizClickAreaAll = useHeatmapVizClickAreaStore((state) => state.resetAll);
1482
+ const resetLiveAll = useHeatmapLiveStore((state) => state.resetAll);
1333
1483
  const copyView = (fromViewId, toViewId) => {
1334
1484
  copyDataView(fromViewId, toViewId);
1335
1485
  copySettingView(fromViewId, toViewId);
1336
1486
  copyVizView(fromViewId, toViewId);
1337
- // copyVizRectView(fromViewId, toViewId);
1338
1487
  copyVizClickView(fromViewId, toViewId);
1339
- // copyVizHoverView(fromViewId, toViewId);
1340
- // copyVizScrollView(fromViewId, toViewId);
1341
1488
  copyVizAreaClickView(fromViewId, toViewId);
1489
+ copyLiveView(fromViewId, toViewId);
1342
1490
  };
1343
1491
  const copyViewToMultiple = (fromViewId, toViewIds) => {
1344
1492
  toViewIds.forEach((toViewId) => {
@@ -1354,6 +1502,7 @@ const useHeatmapCopyView = () => {
1354
1502
  clearVizHoverView(viewId);
1355
1503
  clearVizScrollView(viewId);
1356
1504
  clearVizAreaClickView(viewId);
1505
+ clearLiveView(viewId);
1357
1506
  };
1358
1507
  const clearMultipleViews = (viewIds) => {
1359
1508
  viewIds.forEach((viewId) => {
@@ -1369,6 +1518,7 @@ const useHeatmapCopyView = () => {
1369
1518
  resetVizHoverAll();
1370
1519
  resetVizScrollViewAll();
1371
1520
  resetVizClickAreaAll();
1521
+ resetLiveAll();
1372
1522
  };
1373
1523
  return {
1374
1524
  copyView,
@@ -1379,14 +1529,17 @@ const useHeatmapCopyView = () => {
1379
1529
  };
1380
1530
  };
1381
1531
 
1382
- const useHeatmapWidthByDevice = () => {
1532
+ const useHeatmapViewportByDevice = () => {
1383
1533
  const deviceType = useHeatmapSettingContext((state) => state.deviceType);
1384
1534
  const width = useMemo(() => calcWidthByDeviceType(deviceType), [deviceType]);
1385
- return width;
1535
+ const height = useMemo(() => calcHeightByDeviceType(deviceType), [deviceType]);
1536
+ return { width, height };
1386
1537
  function calcWidthByDeviceType(deviceType) {
1387
1538
  if (!deviceType)
1388
1539
  return 1440;
1389
1540
  switch (deviceType) {
1541
+ case EDeviceType.DesktopLarge:
1542
+ return 1920;
1390
1543
  case EDeviceType.Desktop:
1391
1544
  return 1440;
1392
1545
  case EDeviceType.Tablet:
@@ -1395,20 +1548,45 @@ const useHeatmapWidthByDevice = () => {
1395
1548
  return 375;
1396
1549
  }
1397
1550
  }
1551
+ function calcHeightByDeviceType(deviceType) {
1552
+ if (!deviceType)
1553
+ return 900;
1554
+ switch (deviceType) {
1555
+ case EDeviceType.DesktopLarge:
1556
+ return 1080; // 1920×1080
1557
+ case EDeviceType.Desktop:
1558
+ return 900; // 1440×900
1559
+ case EDeviceType.Tablet:
1560
+ return 1024; // 768×1024 (iPad portrait)
1561
+ case EDeviceType.Mobile:
1562
+ return 812; // 375×812 (iPhone X/11/12/13)
1563
+ }
1564
+ }
1398
1565
  };
1399
1566
 
1400
- const useRegisterConfig = ({ isLoading, isLoadingCanvas, }) => {
1567
+ const useRegisterConfig = ({ shopId, isLoading, isLoadingCanvas, excludeClassNames, }) => {
1401
1568
  const mode = useHeatmapConfigStore((state) => state.mode);
1569
+ const shopIdStore = useHeatmapConfigStore((state) => state.shopId);
1402
1570
  const deviceType = useHeatmapSettingContext((state) => state.deviceType);
1403
1571
  const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
1404
1572
  const setIsRendering = useHeatmapSettingContext((state) => state.setIsRendering);
1405
1573
  const setIsLoadingDom = useHeatmapSettingContext((state) => state.setIsLoadingDom);
1406
1574
  const setIsLoadingCanvas = useHeatmapSettingContext((state) => state.setIsLoadingCanvas);
1575
+ const setShopId = useHeatmapConfigStore((state) => state.setShopId);
1576
+ const setExcludeClassNames = useHeatmapConfigStore((state) => state.setExcludeClassNames);
1577
+ useEffect(() => {
1578
+ if (!shopId || !!shopIdStore || shopIdStore === shopId)
1579
+ return;
1580
+ setShopId(shopId);
1581
+ }, [shopId, setShopId, shopIdStore]);
1582
+ useEffect(() => {
1583
+ setExcludeClassNames(excludeClassNames || []);
1584
+ }, [excludeClassNames, setExcludeClassNames]);
1407
1585
  useEffect(() => {
1408
1586
  setIsRendering(true);
1409
1587
  setTimeout(() => {
1410
1588
  setIsRendering(false);
1411
- }, 100);
1589
+ }, 500);
1412
1590
  }, [mode, deviceType, sidebarWidth]); // eslint-disable-line react-hooks/exhaustive-deps
1413
1591
  useEffect(() => {
1414
1592
  setIsRendering(!!isLoading);
@@ -1499,30 +1677,15 @@ const drawBackdropWithCutout = (options) => {
1499
1677
  const height = Math.min(canvasHeight - top, activeRect.height + cutoutExpansion * 2);
1500
1678
  // Clear previous drawing
1501
1679
  ctx.clearRect(0, 0, canvasWidth, canvasHeight);
1502
- // Set backdrop style
1680
+ // Draw backdrop as a single path with a cutout hole using evenodd fill rule.
1681
+ // This avoids seam artifacts that appear when using 4 separate rectangles
1682
+ // with fractional coordinates (sub-pixel gaps at rect boundaries).
1503
1683
  ctx.fillStyle = backdropColor;
1504
1684
  ctx.globalAlpha = backdropOpacity;
1505
- // Draw backdrop in 4 rectangles around the active element
1506
- // This creates a cutout effect
1507
- // Top rectangle (above active element)
1508
- if (top > 0) {
1509
- ctx.fillRect(0, 0, canvasWidth, top);
1510
- }
1511
- // Bottom rectangle (below active element)
1512
- const bottomY = top + height;
1513
- if (bottomY < canvasHeight) {
1514
- ctx.fillRect(0, bottomY, canvasWidth, canvasHeight - bottomY);
1515
- }
1516
- // Left rectangle (left of active element)
1517
- if (left > 0) {
1518
- ctx.fillRect(0, top, left, height);
1519
- }
1520
- // Right rectangle (right of active element)
1521
- const rightX = left + width;
1522
- if (rightX < canvasWidth) {
1523
- ctx.fillRect(rightX, top, canvasWidth - rightX, height);
1524
- }
1525
- // Reset alpha
1685
+ ctx.beginPath();
1686
+ ctx.rect(0, 0, canvasWidth, canvasHeight); // outer (full canvas)
1687
+ ctx.rect(left, top, width, height); // inner (cutout hole)
1688
+ ctx.fill('evenodd');
1526
1689
  ctx.globalAlpha = 1.0;
1527
1690
  };
1528
1691
  /**
@@ -1630,6 +1793,8 @@ const getElementAtPoint = (doc, x, y) => {
1630
1793
  return element;
1631
1794
  };
1632
1795
  function getElementHash(element) {
1796
+ if (!element)
1797
+ return null;
1633
1798
  return element.getAttribute('data-clarity-hashbeta') || element.getAttribute('data-clarity-hashalpha');
1634
1799
  }
1635
1800
 
@@ -1770,7 +1935,7 @@ class Logger {
1770
1935
  }
1771
1936
  }
1772
1937
  // Export singleton instance
1773
- const logger$9 = new Logger();
1938
+ const logger$3 = new Logger();
1774
1939
  // Export factory function để tạo logger với config riêng
1775
1940
  function createLogger(config = {}) {
1776
1941
  const instance = new Logger();
@@ -2159,7 +2324,7 @@ function findElementByHash(props) {
2159
2324
  }
2160
2325
  }
2161
2326
  catch (error) {
2162
- logger$9.warn(`Invalid selector "${selector}":`, error);
2327
+ logger$3.warn(`Invalid selector "${selector}":`, error);
2163
2328
  }
2164
2329
  const elementByHash = iframeDocument.querySelector(`[data-clarity-hashalpha="${hash}"], [data-clarity-hash="${hash}"], [data-clarity-hashbeta="${hash}"]`);
2165
2330
  return elementByHash;
@@ -2181,7 +2346,7 @@ function hydrateAreaNode(props) {
2181
2346
  const { id, hash, selector } = persistedData;
2182
2347
  const element = findElementByHash({ hash, selector, iframeDocument, vizRef });
2183
2348
  if (!element) {
2184
- logger$9.warn(`Cannot hydrate area ${id}: element not found for hash ${hash} or selector ${selector}`);
2349
+ logger$3.warn(`Cannot hydrate area ${id}: element not found for hash ${hash} or selector ${selector}`);
2185
2350
  return null;
2186
2351
  }
2187
2352
  const areaNode = buildAreaNode(element, hash, heatmapInfo, shadowRoot, persistedData);
@@ -2198,7 +2363,7 @@ function hydrateAreas(props) {
2198
2363
  hydratedAreas.push(area);
2199
2364
  }
2200
2365
  }
2201
- logger$9.info(`Hydrated ${hydratedAreas.length} of ${clickAreas.length} persisted areas`);
2366
+ logger$3.info(`Hydrated ${hydratedAreas.length} of ${clickAreas.length} persisted areas`);
2202
2367
  return hydratedAreas;
2203
2368
  }
2204
2369
  /**
@@ -2902,16 +3067,16 @@ const calcCalloutPositionAbsolute = (props) => {
2902
3067
 
2903
3068
  function validateAreaCreation(dataInfo, hash, areas) {
2904
3069
  if (!dataInfo?.clickMapMetrics || !dataInfo?.totalClicks) {
2905
- logger$9.warn('Cannot create area: missing heatmap data');
3070
+ logger$3.warn('Cannot create area: missing heatmap data');
2906
3071
  return false;
2907
3072
  }
2908
3073
  if (!hash) {
2909
- logger$9.warn('Cannot create area: missing hash');
3074
+ logger$3.warn('Cannot create area: missing hash');
2910
3075
  return false;
2911
3076
  }
2912
3077
  const alreadyExists = areas.some((area) => area.hash === hash);
2913
3078
  if (alreadyExists) {
2914
- logger$9.warn(`Area already exists for element: ${hash}`);
3079
+ logger$3.warn(`Area already exists for element: ${hash}`);
2915
3080
  return false;
2916
3081
  }
2917
3082
  return true;
@@ -2924,14 +3089,14 @@ function identifyConflictingAreas(area) {
2924
3089
  // Case 1: New area is a child of an existing area
2925
3090
  if (area.parentNode) {
2926
3091
  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.`);
3092
+ logger$3.info(`New area "${area.selector}" is a child of existing area "${area.parentNode.selector}". Will remove parent.`);
2928
3093
  }
2929
3094
  // Case 2: New area is a parent of existing area(s)
2930
3095
  if (area.childNodes.size > 0) {
2931
3096
  area.childNodes.forEach((childArea) => {
2932
3097
  conflicts.childrenIds.push(childArea.id);
2933
3098
  });
2934
- logger$9.info(`New area "${area.selector}" is a parent of ${area.childNodes.size} existing area(s). Will remove children.`);
3099
+ logger$3.info(`New area "${area.selector}" is a parent of ${area.childNodes.size} existing area(s). Will remove children.`);
2935
3100
  }
2936
3101
  return conflicts;
2937
3102
  }
@@ -2982,7 +3147,7 @@ function useAreaCreation(options = {}) {
2982
3147
  }
2983
3148
  }
2984
3149
  catch (error) {
2985
- logger$9.error('Failed to create area:', error);
3150
+ logger$3.error('Failed to create area:', error);
2986
3151
  }
2987
3152
  }, [dataInfo, areas, addArea, removeArea, removeClickArea, customShadowRoot, onAreaCreated]);
2988
3153
  return {
@@ -3043,7 +3208,7 @@ function useAreaEditMode({ iframeRef, onAreaCreatedElement, enabled = false, })
3043
3208
  }
3044
3209
  iframeDocument.addEventListener('mousemove', handleMouseMove);
3045
3210
  iframeDocument.addEventListener('scroll', handleMouseLeave);
3046
- iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
3211
+ iframeDocument.addEventListener('mouseleave', handleMouseLeave);
3047
3212
  iframeDocument.addEventListener('click', handleClick);
3048
3213
  return () => {
3049
3214
  iframeDocument.removeEventListener('mousemove', handleMouseMove);
@@ -3097,16 +3262,16 @@ function useAreaHydration(options) {
3097
3262
  return;
3098
3263
  if (!dataInfo)
3099
3264
  return;
3100
- logger$9.info(`Hydrating ${clickAreas.length} persisted areas...`);
3265
+ logger$3.info(`Hydrating ${clickAreas.length} persisted areas...`);
3101
3266
  const hydratedAreas = hydrateAreas({ clickAreas, heatmapInfo: dataInfo, vizRef, shadowRoot });
3102
3267
  if (!hydratedAreas?.length) {
3103
- logger$9.warn('No areas could be hydrated - all elements may have been removed from DOM');
3268
+ logger$3.warn('No areas could be hydrated - all elements may have been removed from DOM');
3104
3269
  return;
3105
3270
  }
3106
3271
  setIsInitializing(true);
3107
3272
  buildAreaGraph(hydratedAreas);
3108
3273
  setAreas(hydratedAreas);
3109
- logger$9.info(`Successfully hydrated ${hydratedAreas.length} areas`);
3274
+ logger$3.info(`Successfully hydrated ${hydratedAreas.length} areas`);
3110
3275
  }, [dataInfo, vizRef, isInitializing, clickAreas]);
3111
3276
  useEffect(() => {
3112
3277
  if (!enabled)
@@ -3240,7 +3405,7 @@ function useAreaRectSync(options) {
3240
3405
  area.rect.update(newRect);
3241
3406
  }
3242
3407
  catch (error) {
3243
- logger$9.error(`Failed to update rect for area ${area.id}:`, error);
3408
+ logger$3.error(`Failed to update rect for area ${area.id}:`, error);
3244
3409
  }
3245
3410
  });
3246
3411
  buildAreaGraph(areas);
@@ -3329,34 +3494,41 @@ const useAreaTopAutoDetect = (props) => {
3329
3494
  const useAreaClickmap = () => {
3330
3495
  const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
3331
3496
  const clickmap = useHeatmapDataContext((s) => s.clickmap);
3497
+ const isDomLoaded = useHeatmapVizContext((s) => s.isDomLoaded);
3332
3498
  const start = useCallback(() => {
3333
- if (!vizRef || !clickmap || clickmap.length === 0)
3499
+ if (!vizRef || !clickmap || !isDomLoaded)
3334
3500
  return;
3335
3501
  try {
3336
3502
  vizRef?.clearmap?.();
3503
+ if (clickmap.length === 0)
3504
+ return;
3505
+ // implement area clickmap
3506
+ // requestIdleCallback(() => vizRef?.areaClickmap?.(clickmap), { timeout: 300 });
3337
3507
  }
3338
3508
  catch (error) {
3339
- console.error(`🚀 🐥 ~ useAreaClickmap ~ error:`, error);
3509
+ console.error(`useAreaClickmap ~ error:`, error);
3340
3510
  }
3341
- }, [vizRef, clickmap]);
3511
+ }, [vizRef, clickmap, isDomLoaded]);
3342
3512
  return { start };
3343
3513
  };
3344
3514
 
3345
3515
  const useClickmap = () => {
3346
3516
  const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
3347
3517
  const clickmap = useHeatmapDataContext((s) => s.clickmap);
3348
- const isRenderViz = useHeatmapVizContext((s) => s.isRenderViz);
3518
+ const isDomLoaded = useHeatmapVizContext((s) => s.isDomLoaded);
3349
3519
  const start = useCallback(() => {
3350
- if (!vizRef || !clickmap || clickmap.length === 0 || !isRenderViz)
3520
+ if (!vizRef || !clickmap || !isDomLoaded)
3351
3521
  return;
3352
3522
  try {
3353
3523
  vizRef?.clearmap?.();
3354
- vizRef?.clickmap?.(clickmap);
3524
+ if (clickmap.length === 0)
3525
+ return;
3526
+ requestIdleCallback(() => vizRef?.clickmap?.(clickmap), { timeout: 300 });
3355
3527
  }
3356
3528
  catch (error) {
3357
- console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
3529
+ console.error(`useClickmap ~ error:`, error);
3358
3530
  }
3359
- }, [vizRef, clickmap, isRenderViz]);
3531
+ }, [vizRef, clickmap, isDomLoaded]);
3360
3532
  return { start };
3361
3533
  };
3362
3534
 
@@ -3364,29 +3536,53 @@ const useScrollmap = () => {
3364
3536
  const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
3365
3537
  const scrollType = useHeatmapSettingContext((s) => s.scrollType);
3366
3538
  const scrollmap = useHeatmapDataContext((s) => s.scrollmap);
3367
- useMemo(() => {
3368
- switch (scrollType) {
3369
- case EScrollType.Depth:
3370
- return scrollmap;
3371
- case EScrollType.Attention:
3372
- return scrollmap;
3373
- case EScrollType.Revenue:
3374
- return scrollmap;
3375
- default:
3376
- return scrollmap;
3539
+ const isDomLoaded = useHeatmapVizContext((s) => s.isDomLoaded);
3540
+ const renderScrollmap = useCallback(() => {
3541
+ if (!vizRef || !scrollmap || scrollmap.length === 0 || !isDomLoaded)
3542
+ return;
3543
+ try {
3544
+ requestIdleCallback(() => vizRef?.scrollmap?.(scrollmap), { timeout: 300 });
3545
+ }
3546
+ catch (error) {
3547
+ console.error(`🚀 🐥 ~ useScrollmap ~ renderScrollmap:`, error);
3548
+ }
3549
+ }, [vizRef, scrollmap, isDomLoaded]);
3550
+ const renderScrollBucket = useCallback(() => {
3551
+ if (!vizRef || !scrollmap || scrollmap.length === 0 || !isDomLoaded)
3552
+ return;
3553
+ const bucketData = scrollmap.map((point) => ({
3554
+ position: point.scrollReachY,
3555
+ value: point.cumulativeSum,
3556
+ percent: point.percUsers,
3557
+ }));
3558
+ try {
3559
+ requestIdleCallback(() => vizRef?.scrollBucket?.(bucketData), { timeout: 300 });
3560
+ }
3561
+ catch (error) {
3562
+ console.error(`🚀 🐥 ~ useScrollmap ~ renderScrollBucket:`, error);
3377
3563
  }
3378
- }, [scrollmap, scrollType]);
3564
+ }, [vizRef, scrollmap, isDomLoaded]);
3379
3565
  const start = useCallback(() => {
3380
- if (!vizRef || !scrollmap || scrollmap.length === 0)
3566
+ if (!vizRef || !scrollmap || scrollmap.length === 0 || !isDomLoaded)
3381
3567
  return;
3382
3568
  try {
3383
3569
  vizRef?.clearmap?.();
3384
- vizRef?.scrollmap?.(scrollmap);
3385
3570
  }
3386
3571
  catch (error) {
3387
- logger$9.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
3572
+ console.error(`🚀 🐥 ~ useScrollmap ~ start:`, error);
3573
+ }
3574
+ switch (scrollType) {
3575
+ case EScrollType.Attention:
3576
+ case EScrollType.Revenue: {
3577
+ renderScrollBucket();
3578
+ break;
3579
+ }
3580
+ case EScrollType.Depth:
3581
+ default:
3582
+ renderScrollmap();
3583
+ break;
3388
3584
  }
3389
- }, [vizRef, scrollmap]);
3585
+ }, [vizRef, scrollmap, isDomLoaded, scrollType, renderScrollmap, renderScrollBucket]);
3390
3586
  return { start };
3391
3587
  };
3392
3588
 
@@ -3394,6 +3590,7 @@ const useHeatmapCanvas = () => {
3394
3590
  const heatmapType = useHeatmapSettingContext((state) => state.heatmapType);
3395
3591
  const clickMode = useHeatmapSettingContext((state) => state.clickMode);
3396
3592
  const scrollType = useHeatmapSettingContext((state) => state.scrollType);
3593
+ const setSelectedElement = useHeatmapClickContext((s) => s.setSelectedElement);
3397
3594
  const { start: startClickmap } = useClickmap();
3398
3595
  const { start: startAreaClickmap } = useAreaClickmap();
3399
3596
  const { start: startScrollmap } = useScrollmap();
@@ -3412,7 +3609,10 @@ const useHeatmapCanvas = () => {
3412
3609
  startScrollmap();
3413
3610
  break;
3414
3611
  }
3415
- }, [heatmapType, clickMode, startClickmap, startAreaClickmap, startScrollmap, scrollType]);
3612
+ return () => {
3613
+ setSelectedElement(null);
3614
+ };
3615
+ }, [heatmapType, clickMode, startClickmap, startAreaClickmap, startScrollmap, scrollType]); // eslint-disable-line react-hooks/exhaustive-deps
3416
3616
  };
3417
3617
 
3418
3618
  const scrollToElementIfNeeded = (visualRef, rect, scale, onScrollComplete) => {
@@ -3494,104 +3694,6 @@ const useClickedElement = ({ visualRef, getRect }) => {
3494
3694
  return { clickedElement, showMissingElement, shouldShowCallout };
3495
3695
  };
3496
3696
 
3497
- const useElementCalloutVisible = ({ visualRef, getRect, positionMode }) => {
3498
- const selectedElement = useHeatmapClickContext((s) => s.selectedElement);
3499
- const setShouldShowCallout = useHeatmapClickContext((s) => s.setShouldShowCallout);
3500
- const widthScale = useHeatmapVizContext((s) => s.widthScale);
3501
- const dataInfo = useHeatmapDataContext((s) => s.dataInfo);
3502
- useEffect(() => {
3503
- const isAbsolute = positionMode === 'absolute';
3504
- if (isAbsolute)
3505
- return;
3506
- const hash = selectedElement?.hash;
3507
- if (!hash)
3508
- return;
3509
- const elementIsInViewportFn = () => {
3510
- const elementInfo = dataInfo?.clickMapMetrics?.[hash];
3511
- if (!elementInfo)
3512
- return;
3513
- const rect = getRect({ hash, selector: elementInfo.selector });
3514
- const isInViewport = isElementInViewport(rect, visualRef, widthScale);
3515
- setShouldShowCallout(isInViewport);
3516
- };
3517
- elementIsInViewportFn();
3518
- const handleUpdate = () => {
3519
- requestAnimationFrame(elementIsInViewportFn);
3520
- };
3521
- window.addEventListener('scroll', handleUpdate, true);
3522
- window.addEventListener('resize', handleUpdate);
3523
- visualRef?.current?.addEventListener('scroll', handleUpdate);
3524
- return () => {
3525
- window.removeEventListener('scroll', handleUpdate, true);
3526
- window.removeEventListener('resize', handleUpdate);
3527
- visualRef?.current?.removeEventListener('scroll', handleUpdate);
3528
- };
3529
- }, [selectedElement?.hash, visualRef, widthScale, dataInfo, getRect, setShouldShowCallout, positionMode]);
3530
- return {};
3531
- };
3532
-
3533
- const useHeatmapEffects = ({ isVisible }) => {
3534
- // const selectedElement = useHeatmapClickContext((s) => s.selectedElement);
3535
- // const setShouldShowCallout = useHeatmapClickContext((s) => s.setShouldShowCallout);
3536
- const resetAll = () => {
3537
- // setShouldShowCallout(false);
3538
- };
3539
- // Reset khi ẩn
3540
- useEffect(() => {
3541
- }, [isVisible, resetAll]);
3542
- // Ẩn callout khi sidebar mở
3543
- // useEffect(() => {
3544
- // if (isElementSidebarOpen && selectedElement) {
3545
- // setShouldShowCallout(false);
3546
- // } else if (!isElementSidebarOpen && selectedElement) {
3547
- // setShouldShowCallout(true);
3548
- // }
3549
- // }, [isElementSidebarOpen, selectedElement]);
3550
- };
3551
-
3552
- const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
3553
- const heatmapWidth = useHeatmapWidthByDevice();
3554
- const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
3555
- const widthScale = useHeatmapVizContext((s) => s.widthScale);
3556
- const getRect = useCallback((element) => {
3557
- const hash = element?.hash;
3558
- if (!iframeRef.current?.contentDocument || !hash || !visualizer)
3559
- return null;
3560
- let domElement = null;
3561
- try {
3562
- domElement = visualizer.get(hash);
3563
- }
3564
- catch (error) {
3565
- console.error('Visualizer error:', { hash, error });
3566
- return null;
3567
- }
3568
- if (!domElement)
3569
- return null;
3570
- const layout = getElementLayout(domElement);
3571
- if (!layout)
3572
- return null;
3573
- const parentEl = wrapperRef.current;
3574
- if (!parentEl)
3575
- return null;
3576
- const scrollOffset = parentEl.scrollTop / widthScale;
3577
- const adjustedTop = layout.top + scrollOffset;
3578
- const outOfBounds = adjustedTop < 0 ||
3579
- adjustedTop > (iframeHeight || Infinity) ||
3580
- layout.left < 0 ||
3581
- (typeof heatmapWidth === 'number' && layout.left > heatmapWidth);
3582
- if (outOfBounds)
3583
- return null;
3584
- return {
3585
- left: layout.left,
3586
- top: adjustedTop,
3587
- width: Math.min(layout.width, heatmapWidth || layout.width),
3588
- height: layout.height,
3589
- outOfBounds,
3590
- };
3591
- }, [iframeRef, wrapperRef, visualizer, heatmapWidth, iframeHeight, widthScale]);
3592
- return { getRect };
3593
- };
3594
-
3595
3697
  function getBoundingBox(element) {
3596
3698
  if (typeof element.getBoundingClientRect !== 'function') {
3597
3699
  return null;
@@ -3706,15 +3808,12 @@ const useHoveredElement = ({ iframeRef, getRect }) => {
3706
3808
  const iframeRect = iframe.getBoundingClientRect();
3707
3809
  const { x, y } = convertViewportToIframeCoords(event.clientX, event.clientY, iframeRect, widthScale);
3708
3810
  const targetElement = findTargetElement(doc, x, y, dataInfo);
3709
- if (!targetElement || !isValidElement(targetElement, dataInfo)) {
3811
+ const hash = getElementHash(targetElement);
3812
+ if (!targetElement || !hash || !isElmInDataInfo(hash, dataInfo)) {
3710
3813
  reset();
3711
3814
  return;
3712
3815
  }
3713
- const hash = getElementHash(targetElement);
3714
- if (hash)
3715
- return hash;
3716
- reset();
3717
- return;
3816
+ return hash;
3718
3817
  }, [dataInfo, iframeRef, widthScale, reset]);
3719
3818
  const onHandleMouseMove = useCallback((event) => {
3720
3819
  if (isLoadingCanvas)
@@ -3726,7 +3825,7 @@ const useHoveredElement = ({ iframeRef, getRect }) => {
3726
3825
  }
3727
3826
  if (isHoveredElement(hash))
3728
3827
  return;
3729
- const selector = dataInfo?.clickMapMetrics?.[hash];
3828
+ const selector = dataInfo.clickMapMetrics?.[hash];
3730
3829
  const rect = getRect({ hash, selector });
3731
3830
  const elementInfo = buildElementInfo(hash, rect, dataInfo);
3732
3831
  if (!elementInfo) {
@@ -3814,83 +3913,181 @@ const findTargetElement = (doc, x, y, heatmapInfo) => {
3814
3913
  const isIframeReady = (iframeRef, heatmapInfo) => {
3815
3914
  return !!(iframeRef.current?.contentDocument && heatmapInfo?.clickMapMetrics);
3816
3915
  };
3817
- const isValidElement = (element, heatmapInfo) => {
3818
- if (!element)
3819
- return false;
3820
- const hash = getElementHash(element);
3916
+ const isElmInDataInfo = (hash, heatmapInfo) => {
3821
3917
  if (!hash)
3822
3918
  return false;
3823
- return !!heatmapInfo?.clickMapMetrics?.[hash];
3824
- };
3825
-
3826
- /**
3827
- * Portal service configuration
3828
- */
3829
- const PORTAL_CONFIG = {
3830
- // Default portal service URL
3831
- // In production, this should be configured via environment or config
3832
- serviceUrl: 'https://ducky.ngrok.app',
3833
- // Portal tracking types
3834
- trackingTypes: {
3835
- HEATMAP: '1',
3836
- SESSION_REPLAY: '2',
3837
- },
3919
+ const isInSortedElements = heatmapInfo?.sortedElements?.some((item) => item.hash === hash);
3920
+ return !!isInSortedElements && !!heatmapInfo?.clickMapMetrics?.[hash];
3838
3921
  };
3839
- /**
3840
- * Get portal service URL from config or environment
3841
- * Can be overridden by window.GEMX_PORTAL_URL for runtime configuration
3842
- */
3843
- function getPortalServiceUrl() {
3844
- // Allow runtime override via window global
3845
- if (typeof window !== 'undefined' && window.GEMX_PORTAL_URL) {
3846
- return window.GEMX_PORTAL_URL;
3847
- }
3848
- return PORTAL_CONFIG.serviceUrl;
3849
- }
3850
3922
 
3851
- var MessageType;
3852
- (function (MessageType) {
3853
- MessageType["GX_DOM_TRACKING_PAYLOAD"] = "GX_DOM_TRACKING_PAYLOAD";
3854
- MessageType["CLARITY_READY"] = "CLARITY_READY";
3855
- })(MessageType || (MessageType = {}));
3856
- function useVizLiveIframeMsg(options = {}) {
3857
- const { trustedOrigins = [], onMessage } = options;
3858
- const addPayload = useHeatmapLiveStore((state) => state.addPayload);
3859
- const [isReady, setIsReady] = useState(false);
3860
- const iframeRef = useRef(null);
3861
- const isValidOrigin = useCallback((origin) => {
3862
- if (trustedOrigins.length === 0)
3863
- return true;
3864
- return trustedOrigins.includes(origin);
3865
- }, [trustedOrigins]);
3866
- const handleMessage = useCallback((event) => {
3867
- if (!isValidOrigin(event.origin)) {
3868
- console.warn('Message from untrusted origin:', event.origin);
3869
- return;
3870
- }
3871
- if (!event.data || typeof event.data !== 'object' || !event.data.type) {
3923
+ const useElementCalloutVisible = ({ visualRef, getRect, positionMode }) => {
3924
+ const selectedElement = useHeatmapClickContext((s) => s.selectedElement);
3925
+ const setShouldShowCallout = useHeatmapClickContext((s) => s.setShouldShowCallout);
3926
+ const widthScale = useHeatmapVizContext((s) => s.widthScale);
3927
+ const dataInfo = useHeatmapDataContext((s) => s.dataInfo);
3928
+ useEffect(() => {
3929
+ const isAbsolute = positionMode === 'absolute';
3930
+ if (isAbsolute)
3872
3931
  return;
3873
- }
3874
- const message = event.data;
3875
- if (!Object.values(MessageType).includes(message.type)) {
3932
+ const hash = selectedElement?.hash;
3933
+ if (!hash)
3876
3934
  return;
3877
- }
3878
- if (onMessage) {
3879
- onMessage(message);
3880
- }
3881
- switch (message.type) {
3882
- case MessageType.GX_DOM_TRACKING_PAYLOAD:
3883
- if (message.payload) {
3884
- const data = decodeClarity(message.payload);
3885
- if (data) {
3886
- addPayload(data);
3887
- }
3888
- }
3889
- break;
3890
- case MessageType.CLARITY_READY:
3891
- setIsReady(true);
3892
- break;
3893
- }
3935
+ const elementIsInViewportFn = () => {
3936
+ if (!isElmInDataInfo(hash, dataInfo))
3937
+ return;
3938
+ const elementInfo = dataInfo?.clickMapMetrics?.[hash];
3939
+ if (!elementInfo)
3940
+ return;
3941
+ const rect = getRect({ hash, selector: elementInfo.selector });
3942
+ const isInViewport = isElementInViewport(rect, visualRef, widthScale);
3943
+ setShouldShowCallout(isInViewport);
3944
+ };
3945
+ elementIsInViewportFn();
3946
+ const handleUpdate = () => {
3947
+ requestAnimationFrame(elementIsInViewportFn);
3948
+ };
3949
+ window.addEventListener('scroll', handleUpdate, true);
3950
+ window.addEventListener('resize', handleUpdate);
3951
+ visualRef?.current?.addEventListener('scroll', handleUpdate);
3952
+ return () => {
3953
+ window.removeEventListener('scroll', handleUpdate, true);
3954
+ window.removeEventListener('resize', handleUpdate);
3955
+ visualRef?.current?.removeEventListener('scroll', handleUpdate);
3956
+ };
3957
+ }, [selectedElement?.hash, visualRef, widthScale, dataInfo, getRect, setShouldShowCallout, positionMode]);
3958
+ return {};
3959
+ };
3960
+
3961
+ const useHeatmapEffects = ({ isVisible }) => {
3962
+ // const selectedElement = useHeatmapClickContext((s) => s.selectedElement);
3963
+ // const setShouldShowCallout = useHeatmapClickContext((s) => s.setShouldShowCallout);
3964
+ const resetAll = () => {
3965
+ // setShouldShowCallout(false);
3966
+ };
3967
+ // Reset khi ẩn
3968
+ useEffect(() => {
3969
+ }, [isVisible, resetAll]);
3970
+ // Ẩn callout khi sidebar mở
3971
+ // useEffect(() => {
3972
+ // if (isElementSidebarOpen && selectedElement) {
3973
+ // setShouldShowCallout(false);
3974
+ // } else if (!isElementSidebarOpen && selectedElement) {
3975
+ // setShouldShowCallout(true);
3976
+ // }
3977
+ // }, [isElementSidebarOpen, selectedElement]);
3978
+ };
3979
+
3980
+ const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
3981
+ const viewport = useHeatmapViewportByDevice();
3982
+ const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
3983
+ const widthScale = useHeatmapVizContext((s) => s.widthScale);
3984
+ const getRect = useCallback((element) => {
3985
+ const hash = element?.hash;
3986
+ if (!iframeRef.current?.contentDocument || !hash || !visualizer)
3987
+ return null;
3988
+ let domElement = null;
3989
+ try {
3990
+ domElement = visualizer.get(hash);
3991
+ }
3992
+ catch (error) {
3993
+ console.error('Visualizer error:', { hash, error });
3994
+ return null;
3995
+ }
3996
+ if (!domElement)
3997
+ return null;
3998
+ const layout = getElementLayout(domElement);
3999
+ if (!layout)
4000
+ return null;
4001
+ const parentEl = wrapperRef.current;
4002
+ if (!parentEl)
4003
+ return null;
4004
+ const scrollOffset = parentEl.scrollTop / widthScale;
4005
+ const adjustedTop = layout.top + scrollOffset;
4006
+ const outOfBounds = adjustedTop < 0 ||
4007
+ adjustedTop > (iframeHeight || Infinity) ||
4008
+ layout.left < 0 ||
4009
+ (typeof viewport.width === 'number' && layout.left > viewport.width);
4010
+ if (outOfBounds)
4011
+ return null;
4012
+ return {
4013
+ left: layout.left,
4014
+ top: adjustedTop,
4015
+ width: Math.min(layout.width, viewport.width || layout.width),
4016
+ height: layout.height,
4017
+ outOfBounds,
4018
+ };
4019
+ }, [iframeRef, wrapperRef, visualizer, viewport, iframeHeight, widthScale]);
4020
+ return { getRect };
4021
+ };
4022
+
4023
+ /**
4024
+ * Portal service configuration
4025
+ */
4026
+ const PORTAL_CONFIG = {
4027
+ // Default portal service URL
4028
+ // In production, this should be configured via environment or config
4029
+ serviceUrl: 'https://ducky.ngrok.app',
4030
+ // Portal tracking types
4031
+ trackingTypes: {
4032
+ HEATMAP: '1',
4033
+ SESSION_REPLAY: '2',
4034
+ },
4035
+ };
4036
+ /**
4037
+ * Get portal service URL from config or environment
4038
+ * Can be overridden by window.GEMX_PORTAL_URL for runtime configuration
4039
+ */
4040
+ function getPortalServiceUrl() {
4041
+ // Allow runtime override via window global
4042
+ if (typeof window !== 'undefined' && window.GEMX_PORTAL_URL) {
4043
+ return window.GEMX_PORTAL_URL;
4044
+ }
4045
+ return PORTAL_CONFIG.serviceUrl;
4046
+ }
4047
+
4048
+ var MessageType;
4049
+ (function (MessageType) {
4050
+ MessageType["GX_DOM_TRACKING_PAYLOAD"] = "GX_DOM_TRACKING_PAYLOAD";
4051
+ MessageType["CLARITY_READY"] = "CLARITY_READY";
4052
+ })(MessageType || (MessageType = {}));
4053
+ function useVizLiveIframeMsg(options = {}) {
4054
+ const { trustedOrigins = [], onMessage } = options;
4055
+ const addPayload = useHeatmapLiveContext((s) => s.addPayload);
4056
+ const [isReady, setIsReady] = useState(false);
4057
+ const iframeRef = useRef(null);
4058
+ const isValidOrigin = useCallback((origin) => {
4059
+ if (trustedOrigins.length === 0)
4060
+ return true;
4061
+ return trustedOrigins.includes(origin);
4062
+ }, [trustedOrigins]);
4063
+ const handleMessage = useCallback((event) => {
4064
+ if (!isValidOrigin(event.origin)) {
4065
+ console.warn('Message from untrusted origin:', event.origin);
4066
+ return;
4067
+ }
4068
+ if (!event.data || typeof event.data !== 'object' || !event.data.type) {
4069
+ return;
4070
+ }
4071
+ const message = event.data;
4072
+ if (!Object.values(MessageType).includes(message.type)) {
4073
+ return;
4074
+ }
4075
+ if (onMessage) {
4076
+ onMessage(message);
4077
+ }
4078
+ switch (message.type) {
4079
+ case MessageType.GX_DOM_TRACKING_PAYLOAD:
4080
+ if (message.payload) {
4081
+ const data = JSON.parse(message.payload);
4082
+ if (data) {
4083
+ addPayload(data);
4084
+ }
4085
+ }
4086
+ break;
4087
+ case MessageType.CLARITY_READY:
4088
+ setIsReady(true);
4089
+ break;
4090
+ }
3894
4091
  }, [isValidOrigin, onMessage]);
3895
4092
  useEffect(() => {
3896
4093
  window.addEventListener('message', handleMessage);
@@ -3904,21 +4101,474 @@ function useVizLiveIframeMsg(options = {}) {
3904
4101
  };
3905
4102
  }
3906
4103
 
4104
+ /**
4105
+ * Unified performance timing utility.
4106
+ *
4107
+ * Two complementary tools:
4108
+ *
4109
+ * 1. `perf` — global DevTools session recorder.
4110
+ * Stores structured timing in `window.__gemxPerf` for inspection.
4111
+ * Used by the iframe-processor rendering pipeline.
4112
+ *
4113
+ * perf.startSession('render-1');
4114
+ * const t = perf.mark('viewport.run');
4115
+ * perf.measure('viewport.run', t);
4116
+ * perf.endSession();
4117
+ *
4118
+ * 2. `createPerfTimer` — per-module console logger factory.
4119
+ * Logs prefixed timings to the console AND records entries into the
4120
+ * active global session so they appear in `window.__gemxPerf` too.
4121
+ *
4122
+ * const timer = createPerfTimer('Render');
4123
+ * const t0 = timer.mark('start');
4124
+ * await timer.wrap('visualizer.html', () => visualizer.html(...));
4125
+ * timer.measure('total', t0);
4126
+ */
4127
+ const s = {
4128
+ enabled: true,
4129
+ current: null,
4130
+ sessions: [],
4131
+ maxSessions: 20,
4132
+ };
4133
+ // ── Global singleton functions ────────────────────────────────────────────────
4134
+ function startSession(id) {
4135
+ if (!s.enabled)
4136
+ return;
4137
+ s.current = { id, startedAt: performance.now(), entries: [] };
4138
+ }
4139
+ function endSession() {
4140
+ if (!s.enabled || !s.current)
4141
+ return null;
4142
+ const session = s.current;
4143
+ session.total = performance.now() - session.startedAt;
4144
+ s.sessions = [session, ...s.sessions].slice(0, s.maxSessions);
4145
+ s.current = null;
4146
+ flush();
4147
+ return session;
4148
+ }
4149
+ /** Record a point-in-time mark. Returns `performance.now()` for use with measure(). */
4150
+ function globalMark(label) {
4151
+ const now = performance.now();
4152
+ if (s.enabled && s.current) {
4153
+ s.current.entries.push({ label, t: now - s.current.startedAt });
4154
+ }
4155
+ return now;
4156
+ }
4157
+ /** Record a duration from a previous mark() timestamp. */
4158
+ function globalMeasure(label, t0) {
4159
+ const duration = performance.now() - t0;
4160
+ if (s.enabled && s.current) {
4161
+ s.current.entries.push({ label, t: t0 - s.current.startedAt, duration });
4162
+ }
4163
+ return duration;
4164
+ }
4165
+ function getReport() {
4166
+ return {
4167
+ sessions: s.sessions,
4168
+ latest: s.sessions[0] ?? null,
4169
+ };
4170
+ }
4171
+ function clearSessions() {
4172
+ s.current = null;
4173
+ s.sessions = [];
4174
+ if (typeof window !== 'undefined')
4175
+ delete window.__gemxPerf;
4176
+ }
4177
+ function enableGlobal() {
4178
+ s.enabled = true;
4179
+ }
4180
+ function disableGlobal() {
4181
+ s.enabled = false;
4182
+ }
4183
+ function flush() {
4184
+ if (typeof window === 'undefined')
4185
+ return;
4186
+ window.__gemxPerf = getReport();
4187
+ }
4188
+ // ── Global singleton export ───────────────────────────────────────────────────
4189
+ const perf$3 = {
4190
+ startSession,
4191
+ endSession,
4192
+ mark: globalMark,
4193
+ measure: globalMeasure,
4194
+ getReport,
4195
+ clear: clearSessions,
4196
+ enable: enableGlobal,
4197
+ disable: disableGlobal,
4198
+ };
4199
+ // ── createPerfTimer factory ───────────────────────────────────────────────────
4200
+ function createPerfTimer(config) {
4201
+ let cfg = typeof config === 'string' ? { prefix: config, enabled: true } : { enabled: true, ...config };
4202
+ function log(icon, label, extra) {
4203
+ if (!cfg.enabled)
4204
+ return;
4205
+ const suffix = extra ? ` — ${extra}` : '';
4206
+ console.log(`[${cfg.prefix}] ${icon} ${label}${suffix}`);
4207
+ }
4208
+ return {
4209
+ configure(next) {
4210
+ cfg = { ...cfg, ...next };
4211
+ },
4212
+ mark(label) {
4213
+ const t = globalMark(`[${cfg.prefix}] ${label}`);
4214
+ log('⏱', label);
4215
+ return t;
4216
+ },
4217
+ measure(label, from) {
4218
+ globalMeasure(`[${cfg.prefix}] ${label}`, from);
4219
+ const ms = (performance.now() - from).toFixed(1);
4220
+ log('✅', label, `${ms}ms`);
4221
+ },
4222
+ async wrap(label, fn) {
4223
+ const t = globalMark(`[${cfg.prefix}] ${label}`);
4224
+ log('⏱', label);
4225
+ const result = await fn();
4226
+ const duration = globalMeasure(`[${cfg.prefix}] ${label}`, t);
4227
+ log('✅', label, `${duration.toFixed(1)}ms`);
4228
+ return result;
4229
+ },
4230
+ };
4231
+ }
4232
+
4233
+ /**
4234
+ * Default iframe dimension calculation.
4235
+ * Used as fallback when no fix overrides getDimensions().
4236
+ */
4237
+ function getFinalHeight(doc, onlyClientHeight = true) {
4238
+ void doc.body.offsetHeight; // trigger reflow
4239
+ // const bodyMinHeight = parseFloat(win.getComputedStyle(doc.body).minHeight) || 0;
4240
+ // const htmlMinHeight = parseFloat(win.getComputedStyle(doc.documentElement).minHeight) || 0;
4241
+ if (onlyClientHeight) {
4242
+ return Math.max(doc.body?.clientHeight || 0, doc.documentElement?.clientHeight || 0);
4243
+ }
4244
+ return Math.max(doc.body?.scrollHeight || 0, doc.body?.offsetHeight || 0, doc.documentElement?.scrollHeight || 0, doc.documentElement?.offsetHeight || 0, doc.documentElement?.clientHeight || 0);
4245
+ }
4246
+ function getFinalWidth(doc) {
4247
+ return Math.max(
4248
+ // doc.body?.scrollWidth || 0,
4249
+ // doc.body?.offsetWidth || 0,
4250
+ // doc.documentElement?.scrollWidth || 0,
4251
+ // doc.documentElement?.offsetWidth || 0,
4252
+ doc.documentElement?.clientWidth || 0);
4253
+ }
4254
+
3907
4255
  /**
3908
4256
  * DOM observation setup — ResizeObserver + MutationObserver.
3909
4257
  * Returns a cleanup function that disconnects both observers.
3910
4258
  */
3911
- createLogger({ enabled: false, prefix: 'IframeHeightObserver' });
4259
+ const logger$2 = createLogger({ enabled: false, prefix: 'IframeHeightObserver' });
4260
+ function setup(doc, onChange) {
4261
+ const resizeObserver = new ResizeObserver(onChange);
4262
+ resizeObserver.observe(doc.documentElement);
4263
+ resizeObserver.observe(doc.body);
4264
+ const mutationObserver = new MutationObserver(onChange);
4265
+ mutationObserver.observe(doc.body, {
4266
+ childList: true,
4267
+ subtree: true,
4268
+ attributes: true,
4269
+ attributeFilter: ['style', 'class', 'hidden', 'data-v'],
4270
+ });
4271
+ logger$2.log('DOM observers started (ResizeObserver + MutationObserver)');
4272
+ return () => {
4273
+ resizeObserver.disconnect();
4274
+ mutationObserver.disconnect();
4275
+ logger$2.log('DOM observers disconnected');
4276
+ };
4277
+ }
3912
4278
 
4279
+ // cspell:ignore cooldown
3913
4280
  /**
3914
4281
  * Height Observer Processor
3915
4282
  * Background observer — watches for iframe content height changes.
3916
4283
  */
3917
- createLogger({ enabled: true, prefix: 'IframeHeightObserver' });
4284
+ // ── Helpers ───────────────────────────────────────────────────────────────────
4285
+ function readCurrentHeight(s) {
4286
+ if (!s.iframe?.contentDocument || !s.iframe?.contentWindow)
4287
+ return 0;
4288
+ const height = getFinalHeight(s.iframe.contentDocument, false);
4289
+ s.logger.log('Height:', height);
4290
+ return height;
4291
+ }
4292
+ function isBlocked(s) {
4293
+ return s.isProcessing || s.isCoolingDown || s.throttleTimeout !== null;
4294
+ }
4295
+ function hasHeightChanged(s, height) {
4296
+ return height !== s.lastHeight;
4297
+ }
4298
+ // ── Cooldown ──────────────────────────────────────────────────────────────────
4299
+ function startCooldown(s) {
4300
+ s.isCoolingDown = true;
4301
+ s.cooldownTimeout = setTimeout(() => {
4302
+ s.cooldownTimeout = null;
4303
+ s.isCoolingDown = false;
4304
+ }, s.cooldownMs);
4305
+ }
4306
+ function stopCooldown(s) {
4307
+ if (s.cooldownTimeout) {
4308
+ clearTimeout(s.cooldownTimeout);
4309
+ s.cooldownTimeout = null;
4310
+ }
4311
+ s.isCoolingDown = false;
4312
+ }
4313
+ // ── Debounce ──────────────────────────────────────────────────────────────────
4314
+ function cancelPendingDebounce(s) {
4315
+ if (s.debounceTimeout) {
4316
+ clearTimeout(s.debounceTimeout);
4317
+ s.debounceTimeout = null;
4318
+ }
4319
+ }
4320
+ function processCurrentHeightIfChanged(s) {
4321
+ const height = readCurrentHeight(s);
4322
+ if (hasHeightChanged(s, height)) {
4323
+ processHeightChange(s, height);
4324
+ }
4325
+ }
4326
+ function scheduleDebounce(s) {
4327
+ cancelPendingDebounce(s);
4328
+ s.debounceTimeout = setTimeout(() => {
4329
+ s.debounceTimeout = null;
4330
+ processCurrentHeightIfChanged(s);
4331
+ }, s.debounceMs);
4332
+ }
4333
+ // ── Throttle ──────────────────────────────────────────────────────────────────
4334
+ function scheduleThrottledCheck(s) {
4335
+ s.throttleTimeout = setTimeout(() => {
4336
+ s.throttleTimeout = null;
4337
+ const height = readCurrentHeight(s);
4338
+ if (!hasHeightChanged(s, height)) {
4339
+ cancelPendingDebounce(s);
4340
+ return;
4341
+ }
4342
+ s.logger.log(`Height changed: ${s.lastHeight}px -> ${height}px`);
4343
+ scheduleDebounce(s);
4344
+ }, s.throttleMs);
4345
+ }
4346
+ // ── Core ──────────────────────────────────────────────────────────────────────
4347
+ async function processHeightChange(s, newHeight) {
4348
+ if (!s.iframe || !s.config)
4349
+ return;
4350
+ s.isProcessing = true;
4351
+ s.logger.log(`Processing height change: ${newHeight}px`);
4352
+ try {
4353
+ const result = {
4354
+ height: newHeight,
4355
+ width: s.iframe.contentWindow?.innerWidth ?? 0,
4356
+ };
4357
+ s.lastHeight = newHeight;
4358
+ s.config.onHeightChange?.(result);
4359
+ window.dispatchEvent(new CustomEvent('iframe-dimensions-applied', { detail: result }));
4360
+ }
4361
+ catch (error) {
4362
+ s.logger.error('Failed to process height change:', error);
4363
+ s.config.onError?.(error);
4364
+ }
4365
+ finally {
4366
+ s.isProcessing = false;
4367
+ if (s.cooldownMs > 0)
4368
+ startCooldown(s);
4369
+ }
4370
+ }
4371
+ function handleHeightChange(s) {
4372
+ if (isBlocked(s))
4373
+ return;
4374
+ scheduleThrottledCheck(s);
4375
+ }
4376
+ function attachObserver(s) {
4377
+ if (!s.iframe?.contentDocument?.body) {
4378
+ s.logger.warn('Cannot observe height changes: iframe body not found');
4379
+ return;
4380
+ }
4381
+ s.observerCleanup?.();
4382
+ const height = readCurrentHeight(s);
4383
+ s.logger.log('Initial height:', height);
4384
+ if (hasHeightChanged(s, height)) {
4385
+ processHeightChange(s, height);
4386
+ }
4387
+ else {
4388
+ s.lastHeight = height;
4389
+ }
4390
+ s.observerCleanup = setup(s.iframe.contentDocument, () => handleHeightChange(s));
4391
+ }
4392
+ function detachObserver(s) {
4393
+ s.observerCleanup?.();
4394
+ s.observerCleanup = null;
4395
+ }
4396
+ function clearAllTimers(s) {
4397
+ if (s.throttleTimeout) {
4398
+ clearTimeout(s.throttleTimeout);
4399
+ s.throttleTimeout = null;
4400
+ }
4401
+ if (s.debounceTimeout) {
4402
+ clearTimeout(s.debounceTimeout);
4403
+ s.debounceTimeout = null;
4404
+ }
4405
+ if (s.startDelayTimeout) {
4406
+ clearTimeout(s.startDelayTimeout);
4407
+ s.startDelayTimeout = null;
4408
+ }
4409
+ stopCooldown(s);
4410
+ }
4411
+ // ── Lifecycle ─────────────────────────────────────────────────────────────────
4412
+ function observeImmediately(s) {
4413
+ attachObserver(s);
4414
+ s.logger.log('Height observer started');
4415
+ }
4416
+ function observeAfterDelay(s, delayMs) {
4417
+ s.logger.log(`Height observer will start in ${delayMs}ms`);
4418
+ s.startDelayTimeout = setTimeout(() => {
4419
+ s.startDelayTimeout = null;
4420
+ if (!s.running)
4421
+ return;
4422
+ attachObserver(s);
4423
+ s.logger.log('Height observer started (after delay)');
4424
+ }, delayMs);
4425
+ }
4426
+ function start$5(s, iframe, cfg) {
4427
+ if (s.running) {
4428
+ s.logger.warn('Observer is already running. Call stop() first.');
4429
+ return;
4430
+ }
4431
+ s.iframe = iframe;
4432
+ s.config = cfg;
4433
+ s.throttleMs = cfg.throttleMs ?? 25;
4434
+ s.debounceMs = cfg.debounceMs ?? 500;
4435
+ s.cooldownMs = cfg.cooldownMs ?? 0;
4436
+ s.running = true;
4437
+ const startDelayMs = cfg.startDelayMs ?? 0;
4438
+ if (startDelayMs > 0) {
4439
+ observeAfterDelay(s, startDelayMs);
4440
+ }
4441
+ else {
4442
+ observeImmediately(s);
4443
+ }
4444
+ }
4445
+ function stop$5(s) {
4446
+ if (!s.running)
4447
+ return;
4448
+ detachObserver(s);
4449
+ clearAllTimers(s);
4450
+ s.iframe = null;
4451
+ s.config = null;
4452
+ s.lastHeight = 0;
4453
+ s.isProcessing = false;
4454
+ s.isCoolingDown = false;
4455
+ s.running = false;
4456
+ s.logger.log('Height observer stopped');
4457
+ }
4458
+ function clear(s) {
4459
+ detachObserver(s);
4460
+ clearAllTimers(s);
4461
+ s.lastHeight = 0;
4462
+ s.isProcessing = false;
4463
+ }
4464
+ function updateConfig$3(s, cfg) {
4465
+ if (!s.running || !s.config) {
4466
+ s.logger.warn('Observer is not running.');
4467
+ return;
4468
+ }
4469
+ s.config = { ...s.config, ...cfg };
4470
+ if (cfg.throttleMs !== undefined)
4471
+ s.throttleMs = cfg.throttleMs;
4472
+ if (cfg.debounceMs !== undefined)
4473
+ s.debounceMs = cfg.debounceMs;
4474
+ if (cfg.cooldownMs !== undefined)
4475
+ s.cooldownMs = cfg.cooldownMs;
4476
+ s.logger.configure({ enabled: !!s.config.debug });
4477
+ s.logger.log('Config updated');
4478
+ }
4479
+ // ── Factory ───────────────────────────────────────────────────────────────────
4480
+ function createHeightObserver() {
4481
+ const s = {
4482
+ logger: createLogger({ enabled: true, prefix: 'IframeHeightObserver' }),
4483
+ iframe: null,
4484
+ config: null,
4485
+ observerCleanup: null,
4486
+ lastHeight: 0,
4487
+ throttleTimeout: null,
4488
+ debounceTimeout: null,
4489
+ startDelayTimeout: null,
4490
+ cooldownTimeout: null,
4491
+ isProcessing: false,
4492
+ isCoolingDown: false,
4493
+ throttleMs: 25,
4494
+ debounceMs: 500,
4495
+ cooldownMs: 0,
4496
+ running: false,
4497
+ };
4498
+ return {
4499
+ start: (iframe, cfg) => start$5(s, iframe, cfg),
4500
+ stop: () => stop$5(s),
4501
+ observe: () => attachObserver(s),
4502
+ clear: () => clear(s),
4503
+ updateConfig: (cfg) => updateConfig$3(s, cfg),
4504
+ getCurrentHeight: () => s.lastHeight,
4505
+ isRunning: () => s.running,
4506
+ getStateInfo: () => ({
4507
+ isRunning: s.running,
4508
+ lastHeight: s.lastHeight,
4509
+ isProcessing: s.isProcessing,
4510
+ hasObservers: !!s.observerCleanup,
4511
+ }),
4512
+ };
4513
+ }
3918
4514
 
3919
- const logger$8 = createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' });
4515
+ /**
4516
+ * Window-level event management for the navigation processor.
4517
+ *
4518
+ * Responsibilities:
4519
+ * - Subscribe to events dispatched by this processor (for logging/hooks)
4520
+ * - Dispatch navigation events to the parent window
4521
+ */
4522
+ // ── Module-level functions ────────────────────────────────────────────────────
4523
+ function attach$1(s, debug) {
4524
+ s.logger.configure({ enabled: !!debug });
4525
+ s.navigationBlockedListener = (e) => {
4526
+ const ev = e;
4527
+ s.logger.log('Navigation blocked:', ev.detail.url);
4528
+ };
4529
+ s.formSubmitWindowListener = (e) => {
4530
+ const ev = e;
4531
+ s.logger.log('Form submitted:', ev.detail.data);
4532
+ };
4533
+ window.addEventListener('iframe-navigation-blocked', s.navigationBlockedListener);
4534
+ window.addEventListener('iframe-form-submit', s.formSubmitWindowListener);
4535
+ }
4536
+ function detach$1(s) {
4537
+ if (s.navigationBlockedListener) {
4538
+ window.removeEventListener('iframe-navigation-blocked', s.navigationBlockedListener);
4539
+ s.navigationBlockedListener = null;
4540
+ }
4541
+ if (s.formSubmitWindowListener) {
4542
+ window.removeEventListener('iframe-form-submit', s.formSubmitWindowListener);
4543
+ s.formSubmitWindowListener = null;
4544
+ }
4545
+ }
4546
+ function dispatchBlocked(url, showMessage) {
4547
+ if (showMessage)
4548
+ alert(`Navigation blocked: ${url}`);
4549
+ window.dispatchEvent(new CustomEvent('iframe-navigation-blocked', { detail: { url } }));
4550
+ }
4551
+ function dispatchFormSubmit(form, data) {
4552
+ window.dispatchEvent(new CustomEvent('iframe-form-submit', { detail: { form, data } }));
4553
+ }
4554
+ // ── Factory ───────────────────────────────────────────────────────────────────
4555
+ function createNavigationListeners() {
4556
+ const s = {
4557
+ logger: createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' }),
4558
+ navigationBlockedListener: null,
4559
+ formSubmitWindowListener: null,
4560
+ };
4561
+ return {
4562
+ attach: (debug) => attach$1(s, debug),
4563
+ detach: () => detach$1(s),
4564
+ dispatchBlocked,
4565
+ dispatchFormSubmit,
4566
+ };
4567
+ }
4568
+
4569
+ const logger$1 = createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' });
3920
4570
  function configure$1(debug) {
3921
- logger$8.configure({ enabled: debug });
4571
+ logger$1.configure({ enabled: debug });
3922
4572
  }
3923
4573
  // ─── DOM Utilities ────────────────────────────────────────────────────────────
3924
4574
  function disableAllLinks(doc) {
@@ -3942,10 +4592,10 @@ function setupLinkBlocker(doc, isEnabled, onBlocked) {
3942
4592
  return;
3943
4593
  const href = link.getAttribute('href');
3944
4594
  if (!href || href === '' || href === '#' || href.startsWith('#')) {
3945
- logger$8.log('Allowed hash navigation:', href);
4595
+ logger$1.log('Allowed hash navigation:', href);
3946
4596
  return;
3947
4597
  }
3948
- logger$8.log('Blocked link navigation to:', href);
4598
+ logger$1.log('Blocked link navigation to:', href);
3949
4599
  e.preventDefault();
3950
4600
  e.stopPropagation();
3951
4601
  e.stopImmediatePropagation();
@@ -3959,7 +4609,7 @@ function setupLinkBlocker(doc, isEnabled, onBlocked) {
3959
4609
  return;
3960
4610
  const href = link.getAttribute('href');
3961
4611
  if (href && !href.startsWith('#')) {
3962
- logger$8.log('Blocked auxclick navigation');
4612
+ logger$1.log('Blocked auxclick navigation');
3963
4613
  e.preventDefault();
3964
4614
  e.stopPropagation();
3965
4615
  e.stopImmediatePropagation();
@@ -3980,7 +4630,7 @@ function setupFormBlocker(doc, isEnabled, onBlocked, onFormSubmit) {
3980
4630
  const form = e.target;
3981
4631
  const action = form.getAttribute('action');
3982
4632
  if (!action || action === '' || action === '#') {
3983
- logger$8.log('Allowed same-page form');
4633
+ logger$1.log('Allowed same-page form');
3984
4634
  e.preventDefault();
3985
4635
  const data = {};
3986
4636
  new FormData(form).forEach((value, key) => {
@@ -3989,7 +4639,7 @@ function setupFormBlocker(doc, isEnabled, onBlocked, onFormSubmit) {
3989
4639
  onFormSubmit(form, data);
3990
4640
  return;
3991
4641
  }
3992
- logger$8.log('Blocked form submission to:', action);
4642
+ logger$1.log('Blocked form submission to:', action);
3993
4643
  e.preventDefault();
3994
4644
  e.stopPropagation();
3995
4645
  e.stopImmediatePropagation();
@@ -4003,7 +4653,7 @@ function setupWindowOpenBlocker(win, originalOpen, isEnabled, onBlocked) {
4003
4653
  if (!isEnabled())
4004
4654
  return originalOpen(...args);
4005
4655
  const url = args[0]?.toString() || 'popup';
4006
- logger$8.log('Blocked window.open:', url);
4656
+ logger$1.log('Blocked window.open:', url);
4007
4657
  onBlocked(url);
4008
4658
  return null;
4009
4659
  });
@@ -4015,14 +4665,14 @@ function setupUnloadBlocker(win, isEnabled) {
4015
4665
  const beforeUnloadListener = (e) => {
4016
4666
  if (!isEnabled())
4017
4667
  return;
4018
- logger$8.log('Blocked beforeunload');
4668
+ logger$1.log('Blocked beforeunload');
4019
4669
  e.preventDefault();
4020
4670
  e.returnValue = '';
4021
4671
  };
4022
4672
  const unloadListener = (e) => {
4023
4673
  if (!isEnabled())
4024
4674
  return;
4025
- logger$8.log('Blocked unload');
4675
+ logger$1.log('Blocked unload');
4026
4676
  e.preventDefault();
4027
4677
  e.stopPropagation();
4028
4678
  };
@@ -4039,65 +4689,14 @@ function setupDOMMonitor(doc) {
4039
4689
  return () => observer.disconnect();
4040
4690
  }
4041
4691
 
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
4692
  /**
4088
4693
  * Navigation Processor
4089
4694
  * Continuous guard — blocks all navigation attempts within the iframe.
4090
4695
  */
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.');
4696
+ // ── Module-level functions ────────────────────────────────────────────────────
4697
+ function start$4(s, iframe, cfg) {
4698
+ if (s.running) {
4699
+ s.logger.warn('Blocker is already running. Call stop() first.');
4101
4700
  return;
4102
4701
  }
4103
4702
  if (!iframe.contentDocument || !iframe.contentWindow) {
@@ -4106,76 +4705,114 @@ function start$5(iframe, cfg) {
4106
4705
  const doc = iframe.contentDocument;
4107
4706
  const win = iframe.contentWindow;
4108
4707
  const originalOpen = win.open.bind(win);
4109
- logger$6.configure({ enabled: !!cfg?.debug });
4708
+ s.logger.configure({ enabled: !!cfg?.debug });
4110
4709
  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),
4710
+ s.cleanups = [
4711
+ setupLinkBlocker(doc, () => s.isEnabled, (url) => s.listeners.dispatchBlocked(url, s.showMessage)),
4712
+ setupFormBlocker(doc, () => s.isEnabled, (url) => s.listeners.dispatchBlocked(url, s.showMessage), s.listeners.dispatchFormSubmit),
4713
+ setupWindowOpenBlocker(win, originalOpen, () => s.isEnabled, (url) => s.listeners.dispatchBlocked(url, s.showMessage)),
4714
+ setupUnloadBlocker(win, () => s.isEnabled),
4116
4715
  setupDOMMonitor(doc),
4117
4716
  ];
4118
- attach$1(cfg?.debug);
4119
- running$4 = true;
4120
- logger$6.log('Navigation blocker started');
4717
+ s.listeners.attach(cfg?.debug);
4718
+ s.running = true;
4719
+ s.logger.log('Started');
4121
4720
  }
4122
- function stop$5() {
4123
- if (!running$4)
4721
+ function stop$4(s) {
4722
+ if (!s.running)
4124
4723
  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.');
4724
+ s.cleanups.forEach((fn) => fn());
4725
+ s.cleanups = [];
4726
+ s.listeners.detach();
4727
+ s.isEnabled = false;
4728
+ s.showMessage = false;
4729
+ s.running = false;
4730
+ s.logger.log('Stopped');
4731
+ }
4732
+ function enable(s) {
4733
+ if (!s.running) {
4734
+ s.logger.warn('Blocker is not running.');
4136
4735
  return;
4137
4736
  }
4138
- isEnabled = true;
4139
- logger$6.log('Navigation blocking enabled');
4737
+ s.isEnabled = true;
4738
+ s.logger.log('Navigation blocking enabled');
4739
+ }
4740
+ function disable(s) {
4741
+ if (!s.running) {
4742
+ s.logger.warn('Blocker is not running.');
4743
+ return;
4744
+ }
4745
+ s.isEnabled = false;
4746
+ s.logger.log('Navigation blocking disabled');
4747
+ }
4748
+ function enableMessage(s) {
4749
+ if (!s.running) {
4750
+ s.logger.warn('Blocker is not running.');
4751
+ return;
4752
+ }
4753
+ s.showMessage = true;
4754
+ s.logger.log('Navigation blocking message enabled');
4755
+ }
4756
+ function disableMessage(s) {
4757
+ if (!s.running) {
4758
+ s.logger.warn('Blocker is not running.');
4759
+ return;
4760
+ }
4761
+ s.showMessage = false;
4762
+ s.logger.log('Navigation blocking message disabled');
4763
+ }
4764
+ // ── Factory ───────────────────────────────────────────────────────────────────
4765
+ function createNavigationBlocker() {
4766
+ const s = {
4767
+ logger: createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' }),
4768
+ listeners: createNavigationListeners(),
4769
+ isEnabled: false,
4770
+ showMessage: false,
4771
+ running: false,
4772
+ cleanups: [],
4773
+ };
4774
+ return {
4775
+ start: (iframe, cfg) => start$4(s, iframe, cfg),
4776
+ stop: () => stop$4(s),
4777
+ enable: () => enable(s),
4778
+ disable: () => disable(s),
4779
+ enableMessage: () => enableMessage(s),
4780
+ disableMessage: () => disableMessage(s),
4781
+ isRunning: () => s.running,
4782
+ isBlockingEnabled: () => s.isEnabled,
4783
+ getStateInfo: () => ({ isRunning: s.running, isEnabled: s.isEnabled, showMessage: s.showMessage }),
4784
+ };
4140
4785
  }
4141
4786
 
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);
4787
+ // ── Module-level functions ────────────────────────────────────────────────────
4788
+ function attach(s, debug) {
4789
+ s.logger.configure({ enabled: !!debug });
4790
+ s.dimensionsListener = (e) => {
4791
+ // const ev = e as CustomEvent<IframeDimensionsDetail>;
4792
+ // s.logger.log('Dimensions applied:', ev.detail);
4793
+ };
4794
+ window.addEventListener('iframe-dimensions-applied', s.dimensionsListener);
4149
4795
  }
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
- });
4796
+ function detach(s) {
4797
+ if (s.dimensionsListener) {
4798
+ window.removeEventListener('iframe-dimensions-applied', s.dimensionsListener);
4799
+ s.dimensionsListener = null;
4800
+ }
4801
+ }
4802
+ // ── Factory ───────────────────────────────────────────────────────────────────
4803
+ function createViewportListeners() {
4804
+ const s = {
4805
+ logger: createLogger({ enabled: false, prefix: 'ViewportReplacer' }),
4806
+ dimensionsListener: null,
4807
+ };
4808
+ return {
4809
+ attach: (debug) => attach(s, debug),
4810
+ detach: () => detach(s),
4811
+ };
4163
4812
  }
4164
4813
 
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
4814
  const DEFAULT_TOLERANCE_PX = 5;
4178
- const VIEWPORT_UNIT_REGEX = /([-.\d]+)(vh|svh|lvh|dvh|%)/gi;
4815
+ const VIEWPORT_UNIT_REGEX = /([-.\\d]+)(vh|svh|lvh|dvh|%)/gi;
4179
4816
  const DEFAULT_CSS_VALUES = ['none', 'auto', 'normal', '0px'];
4180
4817
  const CRITICAL_PROPERTIES = [
4181
4818
  'display',
@@ -4212,423 +4849,215 @@ const CRITICAL_PROPERTIES = [
4212
4849
  'grid-template-rows',
4213
4850
  'gap',
4214
4851
  ];
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) {
4852
+
4853
+ // ── Helpers ───────────────────────────────────────────────────────────────────
4854
+ function getViewportUnitMap(s) {
4855
+ if (!s.config)
4232
4856
  throw new Error('Config is not initialized');
4233
- }
4234
4857
  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,
4858
+ vh: s.config.targetHeight,
4859
+ svh: s.config.targetHeight,
4860
+ lvh: s.config.targetHeight,
4861
+ dvh: s.config.targetHeight,
4862
+ vw: s.config.targetWidth,
4863
+ svw: s.config.targetWidth,
4864
+ lvw: s.config.targetWidth,
4865
+ dvw: s.config.targetWidth,
4243
4866
  };
4244
4867
  }
4245
- /**
4246
- * Calculate expected pixel value from viewport unit using current state
4247
- */
4248
- function calculateExpectedPx(value, unit) {
4249
- if (!config$2) {
4868
+ function calculateExpectedPx(s, value, unit) {
4869
+ if (!s.config)
4250
4870
  throw new Error('Config is not initialized');
4251
- }
4252
4871
  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);
4872
+ if (unitLower === '%')
4873
+ return (value / 100) * s.config.targetHeight;
4874
+ return (value / 100) * (getViewportUnitMap(s)[unitLower] || 0);
4258
4875
  }
4259
- /**
4260
- * Check if a CSS value is a default/initial value that should be skipped
4261
- */
4262
4876
  function isDefaultCssValue(value) {
4263
4877
  return DEFAULT_CSS_VALUES.includes(value);
4264
4878
  }
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) {
4879
+ function shouldReplaceValue(s, computedValue, originalValue, tolerance = DEFAULT_TOLERANCE_PX) {
4880
+ if (!s.config)
4272
4881
  return false;
4273
- }
4274
- // Parse computed value (should be in px)
4275
4882
  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
4883
+ if (isNaN(computedPx))
4884
+ return false;
4280
4885
  const regex = new RegExp(VIEWPORT_UNIT_REGEX.source, VIEWPORT_UNIT_REGEX.flags);
4281
4886
  const match = originalValue.match(regex);
4282
- if (!match) {
4283
- return false; // No viewport units found, don't replace
4284
- }
4887
+ if (!match)
4888
+ return false;
4285
4889
  const [, value, unit] = match;
4286
4890
  const num = parseFloat(value);
4287
- if (isNaN(num)) {
4891
+ if (isNaN(num))
4288
4892
  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)
4893
+ const expectedPx = calculateExpectedPx(s, num, unit);
4293
4894
  const diff = Math.abs(computedPx - expectedPx);
4294
4895
  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)`);
4896
+ s.logger.log(`OK to replace: computed=${computedValue} matches expected=${expectedPx.toFixed(2)}px (diff=${diff.toFixed(2)}px)`);
4297
4897
  return true;
4298
4898
  }
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`);
4899
+ s.logger.log(`Skip replace: computed=${computedValue} differs from expected=${expectedPx.toFixed(2)}px (diff=${diff.toFixed(2)}px)`);
4301
4900
  return false;
4302
4901
  }
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)) {
4902
+ function applyPropertyWithVerification(s, element, prop, computedValue, originalValue, tolerance = DEFAULT_TOLERANCE_PX) {
4903
+ if (originalValue && shouldReplaceValue(s, computedValue, originalValue, tolerance)) {
4309
4904
  element.style.setProperty(prop, computedValue, 'important');
4310
4905
  return true;
4311
4906
  }
4312
4907
  else if (!originalValue) {
4313
- // No original value tracked, use old behavior
4314
4908
  element.style.setProperty(prop, computedValue, 'important');
4315
4909
  return true;
4316
4910
  }
4317
4911
  return false;
4318
4912
  }
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.');
4913
+ // ── Exported module-level functions ───────────────────────────────────────────
4914
+ function start$3(s, d, w, cfg, options = {}) {
4915
+ if (s.running) {
4916
+ s.logger.warn('Enforcer is already running. Call stop() first.');
4329
4917
  return;
4330
4918
  }
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');
4919
+ s.doc = d;
4920
+ s.win = w;
4921
+ s.config = cfg;
4922
+ s.running = true;
4923
+ s.logger.configure({ enabled: !!options.debug });
4924
+ s.logger.log('Started');
4337
4925
  }
4338
- /**
4339
- * Stop the enforcer and clear state
4340
- */
4341
- function stop$4() {
4342
- if (!running$3) {
4926
+ function stop$3(s) {
4927
+ if (!s.running)
4928
+ return;
4929
+ s.doc = null;
4930
+ s.win = null;
4931
+ s.config = null;
4932
+ s.elementsWithViewportUnits.clear();
4933
+ s.originalValues = new WeakMap();
4934
+ s.running = false;
4935
+ s.logger.log('Stopped');
4936
+ }
4937
+ function reset(s) {
4938
+ if (!s.running) {
4939
+ s.logger.warn('Enforcer is not running. Call start() first.');
4343
4940
  return;
4344
4941
  }
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');
4942
+ s.elementsWithViewportUnits.clear();
4943
+ s.originalValues = new WeakMap();
4944
+ s.logger.log('Reset');
4352
4945
  }
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.');
4946
+ function trackElement(s, element, propertyOriginalValues) {
4947
+ if (!s.running) {
4948
+ s.logger.warn('Enforcer is not running. Call start() first.');
4360
4949
  return;
4361
4950
  }
4362
- elementsWithViewportUnits$1.add(element);
4363
- // Store original values for this element
4364
- let elementOriginals = originalValues$1.get(element);
4951
+ s.elementsWithViewportUnits.add(element);
4952
+ let elementOriginals = s.originalValues.get(element);
4365
4953
  if (!elementOriginals) {
4366
4954
  elementOriginals = new Map();
4367
- originalValues$1.set(element, elementOriginals);
4955
+ s.originalValues.set(element, elementOriginals);
4368
4956
  }
4369
- // Merge property original values (don't override existing)
4370
4957
  propertyOriginalValues.forEach((value, prop) => {
4371
- if (!elementOriginals.has(prop)) {
4958
+ if (!elementOriginals.has(prop))
4372
4959
  elementOriginals.set(prop, value);
4373
- }
4374
4960
  });
4375
4961
  }
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.');
4962
+ function processElement(s, element, options = {}) {
4963
+ if (!s.running || !s.doc || !s.win || !s.config) {
4964
+ s.logger.warn('Enforcer is not running.');
4382
4965
  return 0;
4383
4966
  }
4384
- if (!elementsWithViewportUnits$1.has(element)) {
4967
+ if (!s.elementsWithViewportUnits.has(element))
4385
4968
  return 0;
4386
- }
4387
4969
  const htmlElement = element;
4388
- const computed = win$1.getComputedStyle(htmlElement);
4970
+ const computed = s.win.getComputedStyle(htmlElement);
4389
4971
  const inlineStyle = htmlElement.style;
4390
- const elementOriginals = originalValues$1.get(element);
4972
+ const elementOriginals = s.originalValues.get(element);
4391
4973
  const tolerance = options.tolerance ?? DEFAULT_TOLERANCE_PX;
4392
4974
  let count = 0;
4393
4975
  CRITICAL_PROPERTIES.forEach((prop) => {
4394
4976
  const computedValue = computed.getPropertyValue(prop);
4395
4977
  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
- }
4403
- }
4404
- });
4405
- return count;
4406
- }
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.');
4414
- return 0;
4415
- }
4416
- let totalCount = 0;
4417
- elementsWithViewportUnits$1.forEach((element) => {
4418
- totalCount += processElement(element, options);
4419
- });
4420
- logger$5.log(`Enforced ${totalCount} computed styles for ${elementsWithViewportUnits$1.size} elements`);
4421
- return totalCount;
4422
- }
4423
-
4424
- /**
4425
- * Core viewport unit replacement logic.
4426
- * Converts vh/vw/svh/dvh/% to pixel values across all CSS in the iframe.
4427
- */
4428
- const logger$4 = createLogger({ enabled: false, prefix: 'ViewportUnitReplacer' });
4429
- // ─── Constants ────────────────────────────────────────────────────────────────
4430
- const HEIGHT_RELATED_PROPERTIES = ['height', 'min-height', 'max-height', 'top', 'bottom'];
4431
- // ─── Per-run tracking state (reset on each process() call) ───────────────────
4432
- let elementsWithViewportUnits = new Set();
4433
- let originalValues = new WeakMap();
4434
- // ─── Regex ────────────────────────────────────────────────────────────────────
4435
- /** Fresh instance every call — avoids shared lastIndex state with the g flag. */
4436
- function createRegex() {
4437
- return /([-.\\d]+)(vh|svh|lvh|dvh|vw|svw|lvw|dvw)/gi;
4438
- }
4439
- // ─── Unit conversion ─────────────────────────────────────────────────────────
4440
- function px(value) {
4441
- return `${value.toFixed(2)}px`;
4442
- }
4443
- function getUnitMap(ctx) {
4444
- return {
4445
- vh: ctx.targetHeight,
4446
- svh: ctx.targetHeight,
4447
- lvh: ctx.targetHeight,
4448
- dvh: ctx.targetHeight,
4449
- vw: ctx.targetWidth,
4450
- svw: ctx.targetWidth,
4451
- lvw: ctx.targetWidth,
4452
- dvw: ctx.targetWidth,
4453
- };
4454
- }
4455
- function toPx(value, unit, ctx) {
4456
- const u = unit.toLowerCase();
4457
- if (u === '%')
4458
- return (value / 100) * ctx.targetHeight;
4459
- return (value / 100) * (getUnitMap(ctx)[u] ?? 0);
4460
- }
4461
- function convert(value, unit, ctx) {
4462
- const num = parseFloat(value);
4463
- return isNaN(num) ? value : px(toPx(num, unit, ctx));
4464
- }
4465
- function isHeightRelated(prop) {
4466
- return HEIGHT_RELATED_PROPERTIES.includes(prop);
4467
- }
4468
- /**
4469
- * Use `matchOffset` (from replace() callback) instead of indexOf to get the
4470
- * exact position of the current match — avoids false matches for duplicate values.
4471
- */
4472
- function extractProperty(cssText, matchOffset) {
4473
- const before = cssText.substring(0, matchOffset);
4474
- const m = before.match(/([a-z-]+)\s*:\s*[^;{}]*$/i);
4475
- return m ? m[1].toLowerCase() : '';
4476
- }
4477
- function replaceInText(cssText, ctx) {
4478
- return cssText.replace(createRegex(), (match, value, unit, offset) => {
4479
- if (unit === '%') {
4480
- return isHeightRelated(extractProperty(cssText, offset)) ? convert(value, unit, ctx) : match;
4481
- }
4482
- return convert(value, unit, ctx);
4483
- });
4484
- }
4485
- // ─── Element tracking ─────────────────────────────────────────────────────────
4486
- function trackSelector(selector, propOriginals, ctx) {
4487
- try {
4488
- ctx.doc.querySelectorAll(selector).forEach((el) => {
4489
- elementsWithViewportUnits.add(el);
4490
- let originals = originalValues.get(el);
4491
- if (!originals) {
4492
- originals = new Map();
4493
- originalValues.set(el, originals);
4494
- }
4495
- propOriginals.forEach((v, k) => {
4496
- if (!originals.has(k))
4497
- originals.set(k, v);
4498
- });
4499
- trackElement(el, propOriginals);
4500
- });
4501
- }
4502
- catch {
4503
- logger$4.warn('Invalid selector, skipping:', selector);
4504
- }
4505
- }
4506
- // ─── CSS processing ───────────────────────────────────────────────────────────
4507
- function processInlineStyles(ctx) {
4508
- let count = 0;
4509
- ctx.doc.querySelectorAll('[style]').forEach((el) => {
4510
- const style = el.getAttribute('style');
4511
- if (style && createRegex().test(style)) {
4512
- elementsWithViewportUnits.add(el);
4513
- el.setAttribute('style', replaceInText(style, ctx));
4514
- count++;
4515
- }
4516
- });
4517
- logger$4.log(`Replaced ${count} inline style elements`);
4518
- return count;
4519
- }
4520
- function processStyleTags(ctx) {
4521
- let count = 0;
4522
- ctx.doc.querySelectorAll('style').forEach((tag) => {
4523
- const css = tag.textContent || '';
4524
- if (createRegex().test(css)) {
4525
- tag.textContent = replaceInText(css, ctx);
4526
- count++;
4527
- }
4528
- });
4529
- logger$4.log(`Replaced ${count} <style> tags`);
4530
- return count;
4531
- }
4532
- function processRule(rule, ctx) {
4533
- let count = 0;
4534
- if ('style' in rule && rule.style) {
4535
- const cssRule = rule;
4536
- const style = cssRule.style;
4537
- let hasVp = false;
4538
- const propOriginals = new Map();
4539
- for (let i = 0; i < style.length; i++) {
4540
- const prop = style[i];
4541
- const value = style.getPropertyValue(prop);
4542
- if (value && createRegex().test(value)) {
4543
- hasVp = true;
4544
- propOriginals.set(prop, value);
4545
- style.setProperty(prop, replaceInText(value, ctx), style.getPropertyPriority(prop));
4978
+ if (computedValue && (!inlineValue || inlineValue !== computedValue) && !isDefaultCssValue(computedValue)) {
4979
+ const originalValue = elementOriginals?.get(prop) || '';
4980
+ if (applyPropertyWithVerification(s, htmlElement, prop, computedValue, originalValue, tolerance))
4546
4981
  count++;
4547
- }
4548
- }
4549
- if (hasVp && cssRule.selectorText)
4550
- trackSelector(cssRule.selectorText, propOriginals, ctx);
4551
- }
4552
- if ('cssRules' in rule) {
4553
- for (const r of Array.from(rule.cssRules || [])) {
4554
- count += processRule(r, ctx);
4555
- }
4556
- }
4557
- return count;
4558
- }
4559
- /** Processes only inline <style> sheets. Linked sheets are handled by processLinkedStylesheets. */
4560
- function processStylesheets(ctx) {
4561
- let total = 0;
4562
- Array.from(ctx.doc.styleSheets).forEach((sheet) => {
4563
- if (sheet.href)
4564
- return; // deferred to processLinkedStylesheets
4565
- try {
4566
- for (const rule of Array.from(sheet.cssRules || [])) {
4567
- total += processRule(rule, ctx);
4568
- }
4569
- }
4570
- catch (e) {
4571
- logger$4.warn('Cannot read stylesheet (CORS?):', e.message);
4572
4982
  }
4573
4983
  });
4574
- logger$4.log(`Replaced ${total} rules in inline stylesheets`);
4575
- return total;
4984
+ return count;
4576
4985
  }
4577
- async function processLinkedStylesheets(ctx) {
4578
- const links = ctx.doc.querySelectorAll('link[rel="stylesheet"]');
4579
- let count = 0;
4580
- for (const link of Array.from(links)) {
4581
- // Skip cross-origin — already in browser CSSOM, handled via processStylesheets
4582
- if (link.href && !link.href.startsWith(ctx.win.location.origin)) {
4583
- logger$4.log('Skipping cross-origin CSS:', link.href);
4584
- continue;
4585
- }
4586
- try {
4587
- const res = await fetch(link.href);
4588
- let css = await res.text();
4589
- if (createRegex().test(css)) {
4590
- css = replaceInText(css, ctx);
4591
- const style = ctx.doc.createElement('style');
4592
- style.textContent = css;
4593
- style.dataset.originalHref = link.href;
4594
- link.parentNode?.insertBefore(style, link);
4595
- link.remove();
4596
- count++;
4597
- }
4598
- }
4599
- catch (e) {
4600
- logger$4.warn('Cannot load CSS:', link.href, e);
4601
- }
4986
+ function processAll(s, options = {}) {
4987
+ if (!s.running || !s.doc || !s.win || !s.config) {
4988
+ s.logger.warn('Enforcer is not running.');
4989
+ return 0;
4602
4990
  }
4603
- logger$4.log(`Replaced ${count} linked CSS files`);
4604
- return count;
4991
+ let totalCount = 0;
4992
+ s.elementsWithViewportUnits.forEach((element) => {
4993
+ totalCount += processElement(s, element, options);
4994
+ });
4995
+ s.logger.log(`Enforced ${totalCount} computed styles for ${s.elementsWithViewportUnits.size} elements`);
4996
+ return totalCount;
4997
+ }
4998
+ function updateConfig$2(s, cfg) {
4999
+ if (!s.running || !s.config) {
5000
+ s.logger.warn('Enforcer is not running.');
5001
+ return;
5002
+ }
5003
+ s.config = { ...s.config, ...cfg };
5004
+ s.logger.log('Config updated:', cfg);
4605
5005
  }
4606
- // ─── Public entry point ───────────────────────────────────────────────────────
4607
- async function process$1(ctx) {
4608
- logger$4.configure({ enabled: !!ctx.debug });
4609
- // Reset tracking state from any previous run
4610
- elementsWithViewportUnits = new Set();
4611
- originalValues = new WeakMap();
4612
- processInlineStyles(ctx);
4613
- processStyleTags(ctx);
4614
- processStylesheets(ctx);
4615
- await processLinkedStylesheets(ctx);
4616
- // Wait for browser to apply the replaced styles
4617
- await new Promise((resolve) => requestAnimationFrame(resolve));
4618
- // 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`);
5006
+
5007
+ /**
5008
+ * Computed Style Enforcer
5009
+ * Enforces computed CSS styles with viewport unit verification.
5010
+ */
5011
+ // ── Factory ───────────────────────────────────────────────────────────────────
5012
+ function createEnforcer() {
5013
+ const s = {
5014
+ logger: createLogger({ enabled: false, prefix: 'ComputedStyleEnforcer' }),
5015
+ doc: null,
5016
+ win: null,
5017
+ config: null,
5018
+ elementsWithViewportUnits: new Set(),
5019
+ originalValues: new WeakMap(),
5020
+ running: false,
5021
+ };
5022
+ return {
5023
+ start: (d, w, cfg, options) => start$3(s, d, w, cfg, options),
5024
+ stop: () => stop$3(s),
5025
+ reset: () => reset(s),
5026
+ trackElement: (element, propertyOriginalValues) => trackElement(s, element, propertyOriginalValues),
5027
+ processElement: (element, options) => processElement(s, element, options),
5028
+ processAll: (options) => processAll(s, options),
5029
+ updateConfig: (cfg) => updateConfig$2(s, cfg),
5030
+ getStateInfo: () => ({
5031
+ isRunning: s.running,
5032
+ trackedElementsCount: s.elementsWithViewportUnits.size,
5033
+ hasConfig: !!s.config,
5034
+ }),
5035
+ isRunning: () => s.running,
5036
+ };
4621
5037
  }
4622
5038
 
5039
+ const registry$1 = [];
4623
5040
  /**
4624
- * Built-in global fix — always runs, no shouldApply condition.
4625
- * Registered first so it runs before any other global process hooks.
5041
+ * Register a global fix.
5042
+ * Fixes are run in registration order.
4626
5043
  */
4627
- register$1({
4628
- name: 'viewport-unit-replacer',
4629
- description: 'Core: convert vh/vw/svh/dvh/% to px values across all iframe CSS',
4630
- process: process$1,
4631
- });
5044
+ function register$1(fix) {
5045
+ registry$1.push(fix);
5046
+ }
5047
+ /**
5048
+ * Returns all fixes that are active for the given context.
5049
+ * A fix is active if it has no `shouldApply`, or `shouldApply` returns true.
5050
+ */
5051
+ function getActiveFixes(ctx) {
5052
+ return registry$1.filter((fix) => {
5053
+ try {
5054
+ return !fix.shouldApply || fix.shouldApply(ctx);
5055
+ }
5056
+ catch {
5057
+ return false;
5058
+ }
5059
+ });
5060
+ }
4632
5061
 
4633
5062
  /**
4634
5063
  * GemPages v7 Slider Fix
@@ -4713,6 +5142,9 @@ function getMaxWByDeviceType(deviceType) {
4713
5142
  // ─── Main fix ─────────────────────────────────────────────────────────────────
4714
5143
  function afterProcess$1({ doc, targetWidth, deviceType }) {
4715
5144
  doc.querySelectorAll(SLIDER_ITEM_SELECTOR).forEach((item) => {
5145
+ // When !gp-min-w-full is set, the item fills 100% width via Tailwind — skip manual width override
5146
+ if (item.classList.contains('!gp-min-w-full'))
5147
+ return;
4716
5148
  const originalWidth = parseFloat(item.style.minWidth) || parseFloat(item.style.maxWidth) || 0;
4717
5149
  if (!originalWidth)
4718
5150
  return;
@@ -4813,37 +5245,6 @@ register$1({
4813
5245
  afterProcess,
4814
5246
  });
4815
5247
 
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
- /**
4834
- * Default iframe dimension calculation.
4835
- * Used as fallback when no fix overrides getDimensions().
4836
- */
4837
- function getFinalHeight(doc, win) {
4838
- void doc.body.offsetHeight; // trigger reflow
4839
- const bodyMinHeight = parseFloat(win.getComputedStyle(doc.body).minHeight) || 0;
4840
- const htmlMinHeight = parseFloat(win.getComputedStyle(doc.documentElement).minHeight) || 0;
4841
- return Math.max(doc.body?.scrollHeight || 0, doc.body?.offsetHeight || 0, doc.documentElement?.scrollHeight || 0, doc.documentElement?.offsetHeight || 0, doc.documentElement?.clientHeight || 0, bodyMinHeight, htmlMinHeight);
4842
- }
4843
- function getFinalWidth(doc) {
4844
- return Math.max(doc.body?.scrollWidth || 0, doc.body?.offsetWidth || 0, doc.documentElement?.scrollWidth || 0, doc.documentElement?.offsetWidth || 0, doc.documentElement?.clientWidth || 0);
4845
- }
4846
-
4847
5248
  /**
4848
5249
  * Viewport fix pipeline runner.
4849
5250
  *
@@ -4853,68 +5254,79 @@ function getFinalWidth(doc) {
4853
5254
  * Phase 3: afterProcess (shop → global)
4854
5255
  * Phase 4: getDimensions (shop → global → default)
4855
5256
  */
4856
- const logger$2 = createLogger({ enabled: false, prefix: 'ViewportReplacer' });
5257
+ const logger = createLogger({ enabled: false, prefix: 'ViewportReplacer' });
4857
5258
  function configure(debug) {
4858
- logger$2.configure({ enabled: debug });
5259
+ logger.configure({ enabled: debug });
4859
5260
  }
4860
- async function run$1(ctx, activeGlobal, shopFix) {
4861
- // ── Phase 1: beforeProcess ────────────────────────────────────────────────
4862
- for (const fix of activeGlobal) {
4863
- if (fix.beforeProcess) {
4864
- logger$2.log(`[beforeProcess] ${fix.name}`);
4865
- await fix.beforeProcess(ctx);
4866
- }
4867
- }
4868
- if (shopFix?.beforeProcess) {
4869
- logger$2.log('[beforeProcess] shop');
4870
- await shopFix.beforeProcess(ctx);
4871
- }
4872
- // ── Phase 2: process ──────────────────────────────────────────────────────
4873
- for (const fix of activeGlobal) {
4874
- if (fix.process) {
4875
- logger$2.log(`[process] ${fix.name}`);
4876
- await fix.process(ctx);
5261
+ async function runTracked(label, fn) {
5262
+ const t = perf$3.mark(label);
5263
+ await fn();
5264
+ perf$3.measure(label, t);
5265
+ }
5266
+ async function runPhase(ctx, options) {
5267
+ const { phaseKey, methodName, entries } = options;
5268
+ await runTracked(`${phaseKey}.${methodName}`, async () => {
5269
+ for (const { name, fn } of entries) {
5270
+ logger.log(`[${methodName}] ${name}`);
5271
+ await runTracked(`${phaseKey}.${name}.${methodName}`, () => fn(ctx));
4877
5272
  }
4878
- }
4879
- // ── Phase 3: afterProcess ─────────────────────────────────────────────────
4880
- if (shopFix?.afterProcess) {
4881
- logger$2.log('[afterProcess] shop');
4882
- await shopFix.afterProcess(ctx);
4883
- }
4884
- for (const fix of activeGlobal) {
4885
- if (fix.afterProcess) {
4886
- logger$2.log(`[afterProcess] ${fix.name}`);
4887
- await fix.afterProcess(ctx);
5273
+ });
5274
+ }
5275
+ function collectGlobalEntries(fixes, method) {
5276
+ return fixes
5277
+ .filter((fix) => fix[method] != null)
5278
+ .map((fix) => ({ name: fix.name, fn: fix[method] }));
5279
+ }
5280
+ function shopEntry(shopFix, method) {
5281
+ if (!shopFix?.[method])
5282
+ return [];
5283
+ return [{ name: 'shop', fn: shopFix[method] }];
5284
+ }
5285
+ function waitForAnimationFrame() {
5286
+ return new Promise((resolve) => requestAnimationFrame(() => resolve()));
5287
+ }
5288
+ function resolveFirstDimensions(ctx, candidates) {
5289
+ for (const { name, getDimensions } of candidates) {
5290
+ if (!getDimensions)
5291
+ continue;
5292
+ const dims = getDimensions(ctx);
5293
+ if (dims) {
5294
+ logger.log(`Dimensions from ${name}:`, dims);
5295
+ return dims;
4888
5296
  }
4889
5297
  }
4890
- // ── Phase 4: getDimensions ────────────────────────────────────────────────
4891
- return new Promise((resolve) => {
4892
- requestAnimationFrame(() => {
4893
- let dimensions = null;
4894
- // Priority: shop global → default
4895
- if (shopFix?.getDimensions) {
4896
- dimensions = shopFix.getDimensions(ctx);
4897
- if (dimensions)
4898
- logger$2.log('Dimensions from shop fix:', dimensions);
4899
- }
4900
- if (!dimensions) {
4901
- for (const fix of activeGlobal) {
4902
- if (fix.getDimensions) {
4903
- dimensions = fix.getDimensions(ctx);
4904
- if (dimensions) {
4905
- logger$2.log(`Dimensions from global fix [${fix.name}]:`, dimensions);
4906
- break;
4907
- }
4908
- }
4909
- }
4910
- }
4911
- if (!dimensions) {
4912
- dimensions = { height: getFinalHeight(ctx.doc, ctx.win), width: getFinalWidth(ctx.doc) };
4913
- }
4914
- logger$2.log('Final dimensions:', dimensions);
4915
- resolve(dimensions);
4916
- });
5298
+ return null;
5299
+ }
5300
+ // ── Pipeline ──────────────────────────────────────────────────────────────────
5301
+ async function run$1(ctx, activeGlobal, shopFix) {
5302
+ // [Phase 1]: beforeProcess (global → shop)
5303
+ const beforeGlobalEntries = collectGlobalEntries(activeGlobal, 'beforeProcess');
5304
+ const beforeShopEntries = shopEntry(shopFix, 'beforeProcess');
5305
+ await runPhase(ctx, {
5306
+ phaseKey: 'phase1',
5307
+ methodName: 'beforeProcess',
5308
+ entries: [...beforeGlobalEntries, ...beforeShopEntries],
5309
+ });
5310
+ // [Phase 2]: process (global fixes)
5311
+ const processGlobalEntries = collectGlobalEntries(activeGlobal, 'process');
5312
+ await runPhase(ctx, { phaseKey: 'phase2', methodName: 'process', entries: [...processGlobalEntries] });
5313
+ // [Phase 3]: afterProcess (shop → global)
5314
+ const afterShopEntries = shopEntry(shopFix, 'afterProcess');
5315
+ const afterGlobalEntries = collectGlobalEntries(activeGlobal, 'afterProcess');
5316
+ await runPhase(ctx, {
5317
+ phaseKey: 'phase3',
5318
+ methodName: 'afterProcess',
5319
+ entries: [...afterShopEntries, ...afterGlobalEntries],
4917
5320
  });
5321
+ const t4 = perf$3.mark('phase4.getDimensions');
5322
+ await waitForAnimationFrame();
5323
+ const dimensions = resolveFirstDimensions(ctx, [
5324
+ ...(shopFix ? [{ name: 'shop fix', ...shopFix }] : []),
5325
+ ...activeGlobal,
5326
+ ]) ?? { height: getFinalHeight(ctx.doc, false), width: getFinalWidth(ctx.doc) };
5327
+ logger.log('Final dimensions:', dimensions);
5328
+ perf$3.measure('phase4.getDimensions', t4);
5329
+ return dimensions;
4918
5330
  }
4919
5331
 
4920
5332
  const registry = new Map();
@@ -4988,233 +5400,310 @@ register(`566240210141053597`, {
4988
5400
  afterProcess: restoreAndFixLayout,
4989
5401
  });
4990
5402
 
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.');
5403
+ // ── Module-level functions ────────────────────────────────────────────────────
5404
+ function start$2(s, iframe, cfg) {
5405
+ if (s.running) {
5406
+ s.logger.warn('Already running. Call stop() first.');
5002
5407
  return;
5003
5408
  }
5004
5409
  if (!iframe.contentDocument || !iframe.contentWindow) {
5005
5410
  throw new Error('Iframe document or window not accessible');
5006
5411
  }
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 });
5412
+ s.doc = iframe.contentDocument;
5413
+ s.win = iframe.contentWindow;
5414
+ s.config = cfg;
5415
+ s.running = true;
5416
+ s.shopFix = cfg.shopId != null ? get(cfg.shopId) : null;
5417
+ if (s.shopFix)
5418
+ s.logger.log(`Shop fix loaded for "${cfg.shopId}":`, s.shopFix.description ?? '(no description)');
5419
+ s.logger.configure({ enabled: !!cfg.debug });
5016
5420
  configure(!!cfg.debug);
5017
- start$4(doc, win, cfg, { debug: !!cfg.debug });
5018
- attach(cfg.debug);
5019
- logger$1.log('Started');
5421
+ s.enforcer.start(s.doc, s.win, cfg, { debug: !!cfg.debug });
5422
+ s.listeners.attach(cfg.debug);
5423
+ s.logger.log('Started');
5020
5424
  }
5021
- function stop$3() {
5022
- if (!running$2)
5425
+ function stop$2(s) {
5426
+ if (!s.running)
5023
5427
  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.');
5428
+ s.enforcer.stop();
5429
+ s.listeners.detach();
5430
+ s.doc = null;
5431
+ s.win = null;
5432
+ s.config = null;
5433
+ s.shopFix = null;
5434
+ s.running = false;
5435
+ s.logger.log('Stopped');
5436
+ }
5437
+ async function run(s) {
5438
+ if (!s.running || !s.doc || !s.win || !s.config) {
5439
+ s.logger.warn('Not running. Call start() first.');
5036
5440
  return { height: 1000, width: 1000 };
5037
5441
  }
5038
5442
  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,
5443
+ doc: s.doc,
5444
+ win: s.win,
5445
+ deviceType: s.config.deviceType,
5446
+ targetWidth: s.config.targetWidth,
5447
+ targetHeight: s.config.targetHeight,
5448
+ debug: s.config.debug,
5449
+ enforcer: s.enforcer,
5045
5450
  };
5046
5451
  const activeGlobal = getActiveFixes(ctx);
5047
- if (activeGlobal.length > 0) {
5048
- logger$1.log(`Active global fixes: ${activeGlobal.map((f) => f.name).join(', ')}`);
5049
- }
5452
+ if (activeGlobal.length > 0)
5453
+ s.logger.log(`Active global fixes: ${activeGlobal.map((f) => f.name).join(', ')}`);
5454
+ const tRun = perf$3.mark('viewport.run');
5050
5455
  try {
5051
- return await run$1(ctx, activeGlobal, shopFix);
5456
+ const result = await run$1(ctx, activeGlobal, s.shopFix);
5457
+ perf$3.measure('viewport.run', tRun);
5458
+ return result;
5052
5459
  }
5053
5460
  catch (err) {
5054
- logger$1.error('Critical error:', err);
5055
- return { height: doc.body?.scrollHeight || 1000, width: doc.body?.scrollWidth || 1000 };
5461
+ perf$3.measure('viewport.run', tRun);
5462
+ s.logger.error('Critical error:', err);
5463
+ return { height: s.doc.body?.scrollHeight || 1000, width: s.doc.body?.scrollWidth || 1000 };
5056
5464
  }
5057
5465
  }
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.');
5466
+ function updateConfig$1(s, cfg) {
5467
+ if (!s.running || !s.config) {
5468
+ s.logger.warn('Not running. Call start() first.');
5076
5469
  return;
5077
5470
  }
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();
5471
+ s.config = { ...s.config, ...cfg };
5472
+ if (cfg.shopId !== undefined)
5473
+ s.shopFix = cfg.shopId != null ? get(cfg.shopId) : null;
5474
+ s.enforcer.updateConfig(cfg);
5475
+ s.logger.log('Config updated');
5476
+ }
5477
+ // ── Factory ───────────────────────────────────────────────────────────────────
5478
+ function createViewportProcessor() {
5479
+ const s = {
5480
+ logger: createLogger({ enabled: false, prefix: 'ViewportReplacer' }),
5481
+ enforcer: createEnforcer(),
5482
+ listeners: createViewportListeners(),
5483
+ doc: null,
5484
+ win: null,
5485
+ config: null,
5486
+ shopFix: null,
5487
+ running: false,
5488
+ };
5489
+ return {
5490
+ start: (iframe, cfg) => start$2(s, iframe, cfg),
5491
+ stop: () => stop$2(s),
5492
+ run: () => run(s),
5493
+ updateConfig: (cfg) => updateConfig$1(s, cfg),
5494
+ isRunning: () => s.running,
5495
+ };
5496
+ }
5497
+
5498
+ // ── Module-level functions ────────────────────────────────────────────────────
5499
+ function dispatchDimensionsEvent(dimensions) {
5500
+ window.dispatchEvent(new CustomEvent('iframe-dimensions-applied', { detail: dimensions }));
5084
5501
  }
5085
- function stop$2() {
5086
- if (!running$1) {
5502
+ async function process(s) {
5503
+ if (!s.iframe || !s.config)
5087
5504
  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'));
5505
+ if (!s.iframe.contentDocument || !s.iframe.contentWindow) {
5506
+ s.logger.error('Cannot access iframe document');
5507
+ s.config.onError?.(new Error('Cannot access iframe document'));
5106
5508
  return;
5107
5509
  }
5108
- // Wait for iframe to load completely
5109
- if (iframe.contentDocument?.readyState === 'complete') {
5110
- await process();
5510
+ const sessionId = `render-${Date.now()}`;
5511
+ perf$3.startSession(sessionId);
5512
+ const t0 = perf$3.mark('orchestrator.process');
5513
+ try {
5514
+ s.logger.groupCollapsed('Processing...');
5515
+ s.viewportReplacer.start(s.iframe, s.config);
5516
+ s.navigationBlocker.start(s.iframe, { debug: s.config.debug });
5517
+ const result = await s.viewportReplacer.run();
5518
+ perf$3.measure('orchestrator.process', t0);
5519
+ perf$3.endSession();
5520
+ s.logger.groupEnd();
5521
+ s.logger.log('Process completed:', result);
5522
+ s.config.onSuccess?.(result);
5523
+ dispatchDimensionsEvent(result);
5111
5524
  }
5112
- else {
5113
- loadListener = handleIframeLoad;
5114
- iframe.addEventListener('load', loadListener);
5525
+ catch (error) {
5526
+ perf$3.measure('orchestrator.process', t0);
5527
+ perf$3.endSession();
5528
+ s.logger.error('Failed to process:', error);
5529
+ s.config.onError?.(error);
5115
5530
  }
5116
5531
  }
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'));
5532
+ async function initialize(s) {
5533
+ if (!s.iframe || !s.config) {
5534
+ s.logger.error('iframe not found');
5535
+ s.config?.onError?.(new Error('iframe not found'));
5123
5536
  return;
5124
5537
  }
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();
5538
+ if (s.iframe.contentDocument?.readyState === 'complete') {
5539
+ await process(s);
5135
5540
  }
5136
- catch (error) {
5137
- logger.error('Failed to process:', error);
5138
- config.onError?.(error);
5541
+ else {
5542
+ s.loadListener = () => process(s);
5543
+ s.iframe.addEventListener('load', s.loadListener);
5139
5544
  }
5140
5545
  }
5141
- // ============================================================================
5142
- // Helper Functions
5143
- // ============================================================================
5144
- function dispatchDimensionsEvent(dimensions) {
5145
- window.dispatchEvent(new CustomEvent('iframe-dimensions-applied', {
5146
- detail: dimensions,
5147
- }));
5546
+ function start$1(s, cfg) {
5547
+ if (s.running) {
5548
+ s.logger.warn('Fixer is already running. Call stop() first.');
5549
+ return;
5550
+ }
5551
+ s.iframe = cfg.iframe;
5552
+ s.config = cfg;
5553
+ s.running = true;
5554
+ s.logger.configure({ enabled: !!cfg.debug });
5555
+ s.logger.log('Started');
5556
+ initialize(s);
5557
+ }
5558
+ function stop$1(s) {
5559
+ if (!s.running)
5560
+ return;
5561
+ s.viewportReplacer.stop();
5562
+ s.heightObserver.stop();
5563
+ s.navigationBlocker.stop();
5564
+ if (s.iframe && s.loadListener) {
5565
+ s.iframe.removeEventListener('load', s.loadListener);
5566
+ s.loadListener = null;
5567
+ }
5568
+ s.iframe = null;
5569
+ s.config = null;
5570
+ s.running = false;
5571
+ s.logger.log('Stopped');
5572
+ }
5573
+ function startHeightObserver(s) {
5574
+ if (!s.iframe || !s.config)
5575
+ return;
5576
+ s.heightObserver.stop();
5577
+ s.heightObserver.start(s.iframe, {
5578
+ iframe: s.iframe,
5579
+ debug: s.config.debug,
5580
+ throttleMs: 25,
5581
+ debounceMs: 500,
5582
+ cooldownMs: 1000,
5583
+ startDelayMs: s.config.heightObserverStartDelayMs,
5584
+ onHeightChange: (result) => {
5585
+ s.config?.onSuccess?.(result);
5586
+ dispatchDimensionsEvent(result);
5587
+ },
5588
+ onError: (error) => {
5589
+ s.config?.onError?.(error);
5590
+ },
5591
+ });
5148
5592
  }
5149
- function handleIframeLoad() {
5150
- process();
5593
+ async function recalculate$1(s) {
5594
+ if (!s.running) {
5595
+ s.logger.warn('Fixer is not running.');
5596
+ return;
5597
+ }
5598
+ s.logger.log('Recalculating...');
5599
+ await process(s);
5151
5600
  }
5152
- /**
5153
- * Enable navigation blocking
5154
- */
5155
- function enableNavigationBlocking$2() {
5156
- if (!running$1) {
5157
- logger.warn('Fixer is not running. Call start() first.');
5601
+ function updateConfig(s, cfg) {
5602
+ if (!s.running || !s.config) {
5603
+ s.logger.warn('Fixer is not running.');
5158
5604
  return;
5159
5605
  }
5160
- enable();
5606
+ s.config = { ...s.config, ...cfg };
5607
+ s.viewportReplacer.updateConfig(cfg);
5608
+ s.logger.log('Config updated');
5609
+ }
5610
+ // ── Factory ───────────────────────────────────────────────────────────────────
5611
+ function createOrchestrator() {
5612
+ const s = {
5613
+ logger: createLogger({ enabled: false, prefix: 'IframeFixer' }),
5614
+ viewportReplacer: createViewportProcessor(),
5615
+ navigationBlocker: createNavigationBlocker(),
5616
+ heightObserver: createHeightObserver(),
5617
+ iframe: null,
5618
+ config: null,
5619
+ running: false,
5620
+ loadListener: null,
5621
+ };
5622
+ return {
5623
+ start: (cfg) => start$1(s, cfg),
5624
+ stop: () => stop$1(s),
5625
+ recalculate: () => recalculate$1(s),
5626
+ updateConfig: (cfg) => updateConfig(s, cfg),
5627
+ enableNavigationBlocking: () => s.navigationBlocker.enable(),
5628
+ enableNavigationBlockingMessage: () => s.navigationBlocker.enableMessage(),
5629
+ disableNavigationBlocking: () => s.navigationBlocker.disable(),
5630
+ disableNavigationBlockingMessage: () => s.navigationBlocker.disableMessage(),
5631
+ startHeightObserver: () => startHeightObserver(s),
5632
+ isRunning: () => s.running,
5633
+ getStateInfo: () => ({
5634
+ isRunning: s.running,
5635
+ hasIframe: !!s.iframe,
5636
+ hasConfig: !!s.config,
5637
+ hasNavigationBlocker: s.navigationBlocker.isRunning(),
5638
+ hasHeightObserver: s.heightObserver.isRunning(),
5639
+ viewportReplacerRunning: s.viewportReplacer.isRunning(),
5640
+ }),
5641
+ };
5161
5642
  }
5162
5643
 
5163
5644
  /**
5164
- * Iframe Helper Starter Module
5165
- * @module start
5645
+ * Iframe Helper factory entry point.
5646
+ *
5647
+ * Each call to `createIframeHelper()` returns a fully isolated instance
5648
+ * with its own processor state. Use one instance per iframe.
5166
5649
  */
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.');
5650
+ // ── Module-level functions ────────────────────────────────────────────────────
5651
+ function start(s, config) {
5652
+ if (s.running) {
5653
+ console.warn('[IframeHelper] Already running. Call stop() first.');
5177
5654
  return;
5178
5655
  }
5179
- start$2(config);
5180
- running = true;
5656
+ s.fixer.start(config);
5657
+ s.running = true;
5181
5658
  }
5182
- function stop$1() {
5183
- if (!running) {
5659
+ function stop(s) {
5660
+ if (!s.running)
5184
5661
  return;
5185
- }
5186
- stop$2();
5187
- running = false;
5662
+ s.fixer.stop();
5663
+ s.running = false;
5188
5664
  }
5189
- function enableNavigationBlocking$1() {
5190
- if (!running) {
5191
- console.warn('[IframeHelperStarter] Not running. Call start() first.');
5665
+ async function recalculate(s) {
5666
+ if (!s.running) {
5667
+ console.warn('[IframeHelper] Not running. Call start() first.');
5192
5668
  return;
5193
5669
  }
5194
- enableNavigationBlocking$2();
5670
+ await s.fixer.recalculate();
5195
5671
  }
5196
-
5197
- function start(config) {
5198
- start$1(config);
5199
- }
5200
- function stop() {
5201
- stop$1();
5672
+ function enableNavigationBlocking(s) {
5673
+ if (!s.running) {
5674
+ console.warn('[IframeHelper] Not running. Call start() first.');
5675
+ return;
5676
+ }
5677
+ s.fixer.enableNavigationBlocking();
5202
5678
  }
5203
- function enableNavigationBlocking() {
5204
- enableNavigationBlocking$1();
5679
+ // ── Factory ───────────────────────────────────────────────────────────────────
5680
+ function createIframeHelper() {
5681
+ const s = {
5682
+ fixer: createOrchestrator(),
5683
+ running: false,
5684
+ };
5685
+ return {
5686
+ start: (config) => start(s, config),
5687
+ stop: () => stop(s),
5688
+ recalculate: () => recalculate(s),
5689
+ enableNavigationBlocking: () => enableNavigationBlocking(s),
5690
+ startHeightObserver: () => s.fixer.startHeightObserver(),
5691
+ isRunning: () => s.running,
5692
+ };
5205
5693
  }
5206
5694
 
5695
+ const iframeHelper = createIframeHelper();
5207
5696
  function useVizLiveRender() {
5208
5697
  const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
5209
5698
  const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
5210
5699
  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);
5700
+ const setIsDomLoaded = useHeatmapVizContext((s) => s.setIsDomLoaded);
5701
+ const htmlContent = useHeatmapLiveContext((s) => s.htmlContent);
5702
+ const targetUrl = useHeatmapLiveContext((s) => s.targetUrl);
5214
5703
  const deviceType = useHeatmapSettingContext((s) => s.deviceType);
5215
- const renderMode = useHeatmapLiveStore((s) => s.renderMode);
5216
- const storefrontPassword = useHeatmapLiveStore((s) => s.storefrontPassword);
5217
- useHeatmapWidthByDevice();
5704
+ const renderMode = useHeatmapLiveContext((s) => s.renderMode);
5705
+ const storefrontPassword = useHeatmapLiveContext((s) => s.storefrontPassword);
5706
+ useHeatmapViewportByDevice();
5218
5707
  const { iframeRef, isReady } = useVizLiveIframeMsg();
5219
5708
  // Handle iframe rendering based on mode
5220
5709
  useEffect(() => {
@@ -5254,10 +5743,10 @@ function useVizLiveRender() {
5254
5743
  const hasContent = (renderMode === 'portal' && targetUrl) || (renderMode === 'inline' && htmlContent);
5255
5744
  if (!iframe || !hasContent)
5256
5745
  return;
5257
- setIsRenderViz(true);
5258
- initIframeHelper$1(iframe, deviceType, { width: wrapperWidth, height: wrapperHeight }, (height) => {
5746
+ setIsDomLoaded(true);
5747
+ startIframe$1(iframe, deviceType, { width: wrapperWidth, height: wrapperHeight }, (height) => {
5259
5748
  height && setIframeHeight(height);
5260
- setIsRenderViz(true);
5749
+ setIsDomLoaded(true);
5261
5750
  });
5262
5751
  return () => { };
5263
5752
  }, [
@@ -5269,7 +5758,7 @@ function useVizLiveRender() {
5269
5758
  targetUrl,
5270
5759
  htmlContent,
5271
5760
  iframeRef,
5272
- setIsRenderViz,
5761
+ setIsDomLoaded,
5273
5762
  setIframeHeight,
5274
5763
  ]);
5275
5764
  return {
@@ -5288,9 +5777,9 @@ function buildPortalUrl(targetUrl, storefrontPassword) {
5288
5777
  const portalServiceUrl = getPortalServiceUrl();
5289
5778
  return `${portalServiceUrl}/?${params.toString()}`;
5290
5779
  }
5291
- function initIframeHelper$1(iframe, deviceType = EDeviceType.Desktop, rect, onSuccess) {
5292
- stop();
5293
- start({
5780
+ function startIframe$1(iframe, deviceType = EDeviceType.Desktop, rect, onSuccess) {
5781
+ iframeHelper.stop();
5782
+ iframeHelper.start({
5294
5783
  deviceType: deviceType,
5295
5784
  targetWidth: rect.width,
5296
5785
  targetHeight: rect.height,
@@ -5301,12 +5790,115 @@ function initIframeHelper$1(iframe, deviceType = EDeviceType.Desktop, rect, onSu
5301
5790
  },
5302
5791
  });
5303
5792
  // fixer.recalculate();
5304
- enableNavigationBlocking();
5793
+ iframeHelper.enableNavigationBlocking();
5794
+ }
5795
+
5796
+ const DEFAULT_CONFIG = {
5797
+ dbName: 'gx-viz-html-cache',
5798
+ maxEntries: 20,
5799
+ ttlMs: 24 * 60 * 60 * 1000, // 24 hours
5800
+ cacheVersion: 3.0,
5801
+ };
5802
+ let _config = { ...DEFAULT_CONFIG };
5803
+ function getHtmlCacheConfig() {
5804
+ return _config;
5805
+ }
5806
+ /** Build a full cache key that includes the cache version to handle invalidation. */
5807
+ function buildCacheKey(baseKey, shortCircuitStrategy = 0) {
5808
+ return `v${_config.cacheVersion}:${baseKey}:${shortCircuitStrategy}`;
5809
+ }
5810
+
5811
+ const STORE_NAME = 'entries';
5812
+ const IDB_SCHEMA_VERSION = 1;
5813
+ function openDb() {
5814
+ const { dbName } = getHtmlCacheConfig();
5815
+ return new Promise((resolve, reject) => {
5816
+ const req = indexedDB.open(dbName, IDB_SCHEMA_VERSION);
5817
+ req.onupgradeneeded = () => {
5818
+ const db = req.result;
5819
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
5820
+ const store = db.createObjectStore(STORE_NAME, { keyPath: 'key' });
5821
+ store.createIndex('timestamp', 'timestamp');
5822
+ }
5823
+ };
5824
+ req.onsuccess = () => resolve(req.result);
5825
+ req.onerror = () => reject(req.error);
5826
+ });
5827
+ }
5828
+ async function evict(db) {
5829
+ const { maxEntries } = getHtmlCacheConfig();
5830
+ return new Promise((resolve) => {
5831
+ const tx = db.transaction(STORE_NAME, 'readwrite');
5832
+ const store = tx.objectStore(STORE_NAME);
5833
+ const countReq = store.count();
5834
+ countReq.onsuccess = () => {
5835
+ if (countReq.result <= maxEntries) {
5836
+ resolve();
5837
+ return;
5838
+ }
5839
+ const idx = store.index('timestamp');
5840
+ const cursorReq = idx.openCursor();
5841
+ let toDelete = countReq.result - maxEntries;
5842
+ cursorReq.onsuccess = () => {
5843
+ const cursor = cursorReq.result;
5844
+ if (cursor && toDelete > 0) {
5845
+ cursor.delete();
5846
+ toDelete--;
5847
+ cursor.continue();
5848
+ }
5849
+ else {
5850
+ resolve();
5851
+ }
5852
+ };
5853
+ cursorReq.onerror = () => resolve();
5854
+ };
5855
+ countReq.onerror = () => resolve();
5856
+ });
5305
5857
  }
5858
+ const htmlCache = {
5859
+ async get(key) {
5860
+ try {
5861
+ const { ttlMs } = getHtmlCacheConfig();
5862
+ const db = await openDb();
5863
+ return new Promise((resolve) => {
5864
+ const tx = db.transaction(STORE_NAME, 'readonly');
5865
+ const req = tx.objectStore(STORE_NAME).get(key);
5866
+ req.onsuccess = () => {
5867
+ const entry = req.result;
5868
+ if (!entry || Date.now() - entry.timestamp > ttlMs) {
5869
+ resolve(null);
5870
+ }
5871
+ else {
5872
+ resolve(entry);
5873
+ }
5874
+ };
5875
+ req.onerror = () => resolve(null);
5876
+ });
5877
+ }
5878
+ catch {
5879
+ return null;
5880
+ }
5881
+ },
5882
+ async set(entry) {
5883
+ try {
5884
+ const db = await openDb();
5885
+ await new Promise((resolve, reject) => {
5886
+ const tx = db.transaction(STORE_NAME, 'readwrite');
5887
+ tx.objectStore(STORE_NAME).put(entry);
5888
+ tx.oncomplete = () => resolve();
5889
+ tx.onerror = () => reject(tx.error);
5890
+ });
5891
+ await evict(db);
5892
+ }
5893
+ catch {
5894
+ // ignore cache write errors
5895
+ }
5896
+ },
5897
+ };
5306
5898
 
5307
5899
  const CANVAS_ID = 'clarity-heatmap-canvas';
5308
5900
  const ATTENTION_HUES = [240, 210, 180, 150, 120, 90, 60, 30, 0];
5309
- const CANVAS_MAX_HEIGHT = 65535;
5901
+ const CANVAS_MAX_HEIGHT$1 = 65535;
5310
5902
  const Z_INDEX = 2147483647;
5311
5903
  const DEFAULT_IFRAME_ID = 'clarity-snapshot-heatmap-iframe';
5312
5904
  const SECONDARY_IFRAME_ID = 'clarity-snapshot-heatmap-iframe-secondary';
@@ -5371,7 +5963,7 @@ class AttentionMapRenderer {
5371
5963
  const body = doc.body;
5372
5964
  const de = doc.documentElement;
5373
5965
  const contentHeight = Math.max(body.scrollHeight, body.offsetHeight, de.clientHeight, de.scrollHeight, de.offsetHeight);
5374
- canvas.height = Math.min(contentHeight, CANVAS_MAX_HEIGHT);
5966
+ canvas.height = Math.min(contentHeight, CANVAS_MAX_HEIGHT$1);
5375
5967
  canvas.style.top = '0px';
5376
5968
  if (canvas.width <= 0 || canvas.height <= 0 || !this.attentionMapData)
5377
5969
  return;
@@ -5393,7 +5985,7 @@ class AttentionMapRenderer {
5393
5985
  const body = doc.body;
5394
5986
  const de = doc.documentElement;
5395
5987
  const contentHeight = Math.max(body.scrollHeight, body.offsetHeight, de.clientHeight, de.scrollHeight, de.offsetHeight);
5396
- canvas.height = Math.min(contentHeight, CANVAS_MAX_HEIGHT);
5988
+ canvas.height = Math.min(contentHeight, CANVAS_MAX_HEIGHT$1);
5397
5989
  canvas.style.top = '0px';
5398
5990
  let iframeEl = document.getElementById(this.heatmapIframeId);
5399
5991
  if (!iframeEl) {
@@ -5409,9 +6001,9 @@ class AttentionMapRenderer {
5409
6001
  for (const point of this.attentionMapData) {
5410
6002
  const timePercent = Math.floor((100 * point.cumulativeTime) / maxCumulativeTime);
5411
6003
  const colorIndex = timePercent <= 5 ? 0 : Math.min(Math.ceil(timePercent / 10), 8);
5412
- if (point.scrollPos / 100 <= 1) {
6004
+ if (point.scrollReachY / 100 <= 1) {
5413
6005
  point.colorIndex = colorIndex;
5414
- colorStops[point.scrollPos] = colorIndex;
6006
+ colorStops[point.scrollReachY] = colorIndex;
5415
6007
  }
5416
6008
  }
5417
6009
  // Smooth the top portion based on the fold/viewport ratio
@@ -5442,7 +6034,7 @@ class AttentionMapRenderer {
5442
6034
  buildAggregatedData = (doc, contentHeight, iframeEl) => {
5443
6035
  const elementCache = new Map();
5444
6036
  this.attentionMapData = Array.from({ length: 100 }, (_, i) => ({
5445
- scrollPos: i,
6037
+ scrollReachY: i,
5446
6038
  cumulativeTime: 0,
5447
6039
  colorIndex: 0,
5448
6040
  }));
@@ -5481,63 +6073,526 @@ class AttentionMapRenderer {
5481
6073
  }
5482
6074
  }
5483
6075
  }
5484
- return maxTime;
5485
- };
5486
- overlay = () => {
5487
- if (!this.state?.window)
5488
- return null;
5489
- const win = this.state.window;
5490
- const doc = win.document;
5491
- const de = doc.documentElement;
5492
- let canvas = doc.getElementById(CANVAS_ID + '-single');
5493
- if (!canvas) {
5494
- canvas = doc.createElement('CANVAS');
5495
- canvas.id = CANVAS_ID + '-single';
5496
- canvas.width = 0;
5497
- canvas.height = 0;
5498
- canvas.style.position = 'absolute';
5499
- canvas.style.zIndex = `${Z_INDEX}`;
5500
- canvas.style.display = 'block';
5501
- de.appendChild(canvas);
5502
- win.addEventListener('resize', this.reRender, true);
5503
- this.observer = typeof window.ResizeObserver !== 'undefined' ? new ResizeObserver(this.reRender) : null;
5504
- this.observer?.observe(doc.body);
6076
+ return maxTime;
6077
+ };
6078
+ overlay = () => {
6079
+ if (!this.state?.window)
6080
+ return null;
6081
+ const win = this.state.window;
6082
+ const doc = win.document;
6083
+ const de = doc.documentElement;
6084
+ let canvas = doc.getElementById(CANVAS_ID + '-single');
6085
+ if (!canvas) {
6086
+ canvas = doc.createElement('CANVAS');
6087
+ canvas.id = CANVAS_ID + '-single';
6088
+ canvas.width = 0;
6089
+ canvas.height = 0;
6090
+ canvas.style.position = 'absolute';
6091
+ canvas.style.zIndex = `${Z_INDEX}`;
6092
+ canvas.style.display = 'block';
6093
+ de.appendChild(canvas);
6094
+ win.addEventListener('resize', this.reRender, true);
6095
+ this.observer = typeof window.ResizeObserver !== 'undefined' ? new ResizeObserver(this.reRender) : null;
6096
+ this.observer?.observe(doc.body);
6097
+ }
6098
+ canvas.width = de.clientWidth;
6099
+ canvas.height = de.clientHeight;
6100
+ canvas.style.left = `${win.pageXOffset}px`;
6101
+ canvas.style.top = `${win.pageYOffset}px`;
6102
+ canvas.getContext('2d')?.clearRect(0, 0, canvas.width, canvas.height);
6103
+ return canvas;
6104
+ };
6105
+ }
6106
+
6107
+ const CANVAS_MAX_HEIGHT = 65535;
6108
+ const REDRAW_INTERVAL = 30;
6109
+ class ScrollBucketRenderer {
6110
+ heatmap;
6111
+ lastData = null;
6112
+ dimensionsListener = null;
6113
+ redrawTimeout = null;
6114
+ constructor(heatmap) {
6115
+ this.heatmap = heatmap;
6116
+ this.attachDimensionsListener();
6117
+ }
6118
+ redraw = () => {
6119
+ if (!this.lastData)
6120
+ return;
6121
+ if (this.redrawTimeout)
6122
+ clearTimeout(this.redrawTimeout);
6123
+ this.redrawTimeout = setTimeout(() => this.draw(this.lastData), REDRAW_INTERVAL);
6124
+ };
6125
+ attachDimensionsListener = () => {
6126
+ this.dimensionsListener = () => this.redraw();
6127
+ window.addEventListener('iframe-dimensions-applied', this.dimensionsListener);
6128
+ };
6129
+ detachDimensionsListener = () => {
6130
+ if (this.dimensionsListener) {
6131
+ window.removeEventListener('iframe-dimensions-applied', this.dimensionsListener);
6132
+ this.dimensionsListener = null;
6133
+ }
6134
+ if (this.redrawTimeout) {
6135
+ clearTimeout(this.redrawTimeout);
6136
+ this.redrawTimeout = null;
6137
+ }
6138
+ };
6139
+ reset = () => {
6140
+ // no-op: canvas state is managed by the shared heatmap instance
6141
+ };
6142
+ clear = () => {
6143
+ this.lastData = null;
6144
+ this.detachDimensionsListener();
6145
+ };
6146
+ /**
6147
+ * Render discrete color bands for scrollRevenue / scrollAttention data.
6148
+ * Uses the shared heatmap canvas (same as HeatmapHelper.scroll()) to avoid
6149
+ * duplicate portal canvas management.
6150
+ * Stores data so the canvas can be redrawn when iframe height changes.
6151
+ */
6152
+ renderBucket = async (data) => {
6153
+ if (!this.heatmap)
6154
+ return;
6155
+ this.attachDimensionsListener();
6156
+ this.lastData = data;
6157
+ await this.heatmap.waitForDialogs();
6158
+ this.draw(data);
6159
+ };
6160
+ draw = (data) => {
6161
+ if (!this.heatmap)
6162
+ return;
6163
+ const canvas = this.heatmap.overlay();
6164
+ if (!canvas)
6165
+ return;
6166
+ const context = canvas.getContext('2d');
6167
+ const doc = this.heatmap.state.window.document;
6168
+ const body = doc.body;
6169
+ const de = doc.documentElement;
6170
+ const height = Math.max(body.scrollHeight, body.offsetHeight, de.clientHeight, de.scrollHeight, de.offsetHeight);
6171
+ // Same canvas setup as HeatmapHelper.scroll(): full content height, top=0
6172
+ canvas.height = Math.min(height, CANVAS_MAX_HEIGHT);
6173
+ canvas.style.top = '0px';
6174
+ if (canvas.width <= 0 || canvas.height <= 0)
6175
+ return;
6176
+ const buckets = this.mapToBuckets(data);
6177
+ const maxPercent = buckets.length > 0 ? Math.max(...buckets.map((b) => b.percent)) : 0;
6178
+ // Coldest hue (240 = blue) used as the baseline for buckets with no data
6179
+ const coldColor = 'hsla(240, 100%, 50%, 0.6)';
6180
+ const gradient = context.createLinearGradient(0, 0, 0, canvas.height);
6181
+ if (buckets.length === 0 || maxPercent <= 0) {
6182
+ // No data — fill entire canvas with the coldest color
6183
+ gradient.addColorStop(0, coldColor);
6184
+ gradient.addColorStop(1, coldColor);
6185
+ }
6186
+ else {
6187
+ for (const bucket of buckets) {
6188
+ const stopMid = (bucket.startY + bucket.endY) / 2 / 100;
6189
+ // Same hue direction as HeatmapHelper.scroll() (MaxHue = 240):
6190
+ // high percent → hue 0 (red/hot), low percent → hue 240 (blue/cold)
6191
+ const hue = (1 - bucket.percent / maxPercent) * 240;
6192
+ const color = `hsla(${Math.round(hue)}, 100%, 50%, 0.6)`;
6193
+ gradient.addColorStop(stopMid, color);
6194
+ }
6195
+ }
6196
+ context.fillStyle = gradient;
6197
+ context.fillRect(0, 0, canvas.width, canvas.height);
6198
+ };
6199
+ /**
6200
+ * Convert flat position markers → structured buckets with startY/endY.
6201
+ *
6202
+ * Input positions: [0, 5, 10, 15, ..., 95]
6203
+ * position=0 → { startY: 0, endY: 5 } (special start marker)
6204
+ * position=10 → { startY: 5, endY: 10 }
6205
+ * position=15 → { startY: 10, endY: 15 }
6206
+ */
6207
+ mapToBuckets = (data) => {
6208
+ const sorted = [...data].sort((a, b) => a.position - b.position);
6209
+ const buckets = [];
6210
+ for (let i = 0; i < sorted.length; i++) {
6211
+ const current = sorted[i];
6212
+ let startY;
6213
+ let endY;
6214
+ if (current.position === 0) {
6215
+ // position=0 is the start marker → range 0 to next position
6216
+ const next = sorted[i + 1];
6217
+ if (!next)
6218
+ continue;
6219
+ startY = 0;
6220
+ endY = next.position;
6221
+ }
6222
+ else {
6223
+ // Each non-zero position is the END of its bucket; start = previous position
6224
+ const prev = sorted[i - 1];
6225
+ startY = prev ? prev.position : 0;
6226
+ endY = current.position;
6227
+ }
6228
+ buckets.push({ startY, endY, value: current.value, percent: current.percent });
6229
+ }
6230
+ return buckets;
6231
+ };
6232
+ }
6233
+
6234
+ var Event;
6235
+ (function (Event) {
6236
+ /* Data */
6237
+ Event[Event["Metric"] = 0] = "Metric";
6238
+ Event[Event["Dimension"] = 1] = "Dimension";
6239
+ Event[Event["Upload"] = 2] = "Upload";
6240
+ Event[Event["Upgrade"] = 3] = "Upgrade";
6241
+ Event[Event["Baseline"] = 4] = "Baseline";
6242
+ Event[Event["Discover"] = 5] = "Discover";
6243
+ Event[Event["Mutation"] = 6] = "Mutation";
6244
+ Event[Event["Region"] = 7] = "Region";
6245
+ Event[Event["Document"] = 8] = "Document";
6246
+ Event[Event["Click"] = 9] = "Click";
6247
+ Event[Event["Scroll"] = 10] = "Scroll";
6248
+ Event[Event["Resize"] = 11] = "Resize";
6249
+ Event[Event["MouseMove"] = 12] = "MouseMove";
6250
+ Event[Event["MouseDown"] = 13] = "MouseDown";
6251
+ Event[Event["MouseUp"] = 14] = "MouseUp";
6252
+ Event[Event["MouseWheel"] = 15] = "MouseWheel";
6253
+ Event[Event["DoubleClick"] = 16] = "DoubleClick";
6254
+ Event[Event["TouchStart"] = 17] = "TouchStart";
6255
+ Event[Event["TouchEnd"] = 18] = "TouchEnd";
6256
+ Event[Event["TouchMove"] = 19] = "TouchMove";
6257
+ Event[Event["TouchCancel"] = 20] = "TouchCancel";
6258
+ Event[Event["Selection"] = 21] = "Selection";
6259
+ Event[Event["Timeline"] = 22] = "Timeline";
6260
+ Event[Event["Page"] = 23] = "Page";
6261
+ Event[Event["Custom"] = 24] = "Custom";
6262
+ Event[Event["Ping"] = 25] = "Ping";
6263
+ Event[Event["Unload"] = 26] = "Unload";
6264
+ Event[Event["Input"] = 27] = "Input";
6265
+ Event[Event["Visibility"] = 28] = "Visibility";
6266
+ Event[Event["Navigation"] = 29] = "Navigation";
6267
+ /**
6268
+ * @deprecated No longer support Network Connection
6269
+ */
6270
+ Event[Event["Connection"] = 30] = "Connection";
6271
+ Event[Event["ScriptError"] = 31] = "ScriptError";
6272
+ /**
6273
+ * @deprecated No longer support Image Error
6274
+ */
6275
+ Event[Event["ImageError"] = 32] = "ImageError";
6276
+ Event[Event["Log"] = 33] = "Log";
6277
+ Event[Event["Variable"] = 34] = "Variable";
6278
+ Event[Event["Limit"] = 35] = "Limit";
6279
+ Event[Event["Summary"] = 36] = "Summary";
6280
+ /**
6281
+ * @deprecated No longer support Box event
6282
+ */
6283
+ Event[Event["Box"] = 37] = "Box";
6284
+ Event[Event["Clipboard"] = 38] = "Clipboard";
6285
+ Event[Event["Submit"] = 39] = "Submit";
6286
+ Event[Event["Extract"] = 40] = "Extract";
6287
+ Event[Event["Fraud"] = 41] = "Fraud";
6288
+ Event[Event["Change"] = 42] = "Change";
6289
+ Event[Event["Snapshot"] = 43] = "Snapshot";
6290
+ Event[Event["Animation"] = 44] = "Animation";
6291
+ Event[Event["StyleSheetAdoption"] = 45] = "StyleSheetAdoption";
6292
+ Event[Event["StyleSheetUpdate"] = 46] = "StyleSheetUpdate";
6293
+ Event[Event["Consent"] = 47] = "Consent";
6294
+ Event[Event["ContextMenu"] = 48] = "ContextMenu";
6295
+ // 49 is reserved for internal use
6296
+ Event[Event["Focus"] = 50] = "Focus";
6297
+ Event[Event["CustomElement"] = 51] = "CustomElement";
6298
+ Event[Event["Chat"] = 52] = "Chat";
6299
+ // Apps specific events
6300
+ Event[Event["WebViewDiscover"] = 100] = "WebViewDiscover";
6301
+ Event[Event["WebViewMutation"] = 101] = "WebViewMutation";
6302
+ Event[Event["MutationError"] = 102] = "MutationError";
6303
+ Event[Event["FragmentVisibility"] = 103] = "FragmentVisibility";
6304
+ Event[Event["Keystrokes"] = 104] = "Keystrokes";
6305
+ Event[Event["BackGesture"] = 105] = "BackGesture";
6306
+ Event[Event["WebViewStatus"] = 106] = "WebViewStatus";
6307
+ Event[Event["AppInstallReferrer"] = 107] = "AppInstallReferrer";
6308
+ // 200-300 reserved for internal use
6309
+ })(Event || (Event = {}));
6310
+
6311
+ var Constant;
6312
+ (function (Constant) {
6313
+ Constant["Empty"] = "";
6314
+ Constant["SvgPrefix"] = "svg:";
6315
+ Constant["DataPrefix"] = "data:";
6316
+ Constant["IFramePrefix"] = "iframe:";
6317
+ Constant["SvgNamespace"] = "http://www.w3.org/2000/svg";
6318
+ Constant["Id"] = "id";
6319
+ Constant["Class"] = "class";
6320
+ Constant["Style"] = "style";
6321
+ Constant["Href"] = "href";
6322
+ Constant["Src"] = "src";
6323
+ Constant["Srcset"] = "srcset";
6324
+ Constant["Hash"] = "#";
6325
+ Constant["Dot"] = ".";
6326
+ Constant["Separator"] = ">";
6327
+ Constant["Tilde"] = "~";
6328
+ Constant["Bang"] = "!";
6329
+ Constant["Period"] = ".";
6330
+ Constant["Comma"] = ",";
6331
+ Constant["DataAttribute"] = "data-";
6332
+ Constant["MaskData"] = "data-clarity-mask";
6333
+ Constant["UnmaskData"] = "data-clarity-unmask";
6334
+ Constant["RegionData"] = "data-clarity-region";
6335
+ Constant["GXDialogModal"] = "gx-dialog-modal";
6336
+ Constant["Type"] = "type";
6337
+ Constant["Submit"] = "submit";
6338
+ Constant["Name"] = "name";
6339
+ Constant["Base"] = "*B";
6340
+ Constant["SameOrigin"] = "*O";
6341
+ Constant["Object"] = "object";
6342
+ Constant["Function"] = "function";
6343
+ Constant["StyleTag"] = "STYLE";
6344
+ Constant["LinkTag"] = "LINK";
6345
+ Constant["InputTag"] = "INPUT";
6346
+ Constant["IFrameTag"] = "IFRAME";
6347
+ Constant["ImageTag"] = "IMG";
6348
+ Constant["TitleTag"] = "TITLE";
6349
+ Constant["BodyTag"] = "BODY";
6350
+ Constant["SvgTag"] = "svg:svg";
6351
+ Constant["BaseTag"] = "BASE";
6352
+ Constant["NativeCode"] = "[native code]";
6353
+ Constant["DocumentTag"] = "*D";
6354
+ Constant["ShadowDomTag"] = "*S";
6355
+ Constant["PolyfillShadowDomTag"] = "*P";
6356
+ Constant["TextTag"] = "*T";
6357
+ Constant["SuspendMutationTag"] = "*M";
6358
+ Constant["ChildList"] = "childList";
6359
+ Constant["Attributes"] = "attributes";
6360
+ Constant["CharacterData"] = "characterData";
6361
+ Constant["Throttle"] = "throttle";
6362
+ Constant["LoadEvent"] = "load";
6363
+ Constant["Pixel"] = "px";
6364
+ Constant["BorderBox"] = "border-box";
6365
+ Constant["Value"] = "value";
6366
+ Constant["MutationObserver"] = "MutationObserver";
6367
+ Constant["JsonLD"] = "application/ld+json";
6368
+ Constant["String"] = "string";
6369
+ Constant["Number"] = "number";
6370
+ Constant["Disable"] = "disable";
6371
+ Constant["HTML"] = "HTML";
6372
+ Constant["Property"] = "property";
6373
+ Constant["Content"] = "content";
6374
+ Constant["Generator"] = "generator";
6375
+ Constant["ogType"] = "og:type";
6376
+ Constant["ogTitle"] = "og:title";
6377
+ Constant["SvgStyle"] = "svg:style";
6378
+ Constant["StyleSheet"] = "stylesheet";
6379
+ })(Constant || (Constant = {}));
6380
+
6381
+ var ShortCircuitStrategy;
6382
+ (function (ShortCircuitStrategy) {
6383
+ ShortCircuitStrategy[ShortCircuitStrategy["None"] = 0] = "None";
6384
+ ShortCircuitStrategy[ShortCircuitStrategy["HashFirstTimestamp"] = 1] = "HashFirstTimestamp";
6385
+ ShortCircuitStrategy[ShortCircuitStrategy["HashFirstTimestampPlusBuffer"] = 2] = "HashFirstTimestampPlusBuffer";
6386
+ ShortCircuitStrategy[ShortCircuitStrategy["HashBeforeDeleted"] = 3] = "HashBeforeDeleted";
6387
+ })(ShortCircuitStrategy || (ShortCircuitStrategy = {}));
6388
+
6389
+ function isShadowDomNode(tag) {
6390
+ return tag === Constant.ShadowDomTag || tag === Constant.PolyfillShadowDomTag;
6391
+ }
6392
+ /** Scan the rendered DOM (including nested shadow roots) to collect tag names of shadow host elements. */
6393
+ function collectShadowHostTags(root, tags = new Set()) {
6394
+ root.querySelectorAll('*').forEach((el) => {
6395
+ if (el.shadowRoot) {
6396
+ tags.add(el.tagName);
6397
+ collectShadowHostTags(el.shadowRoot, tags);
6398
+ }
6399
+ });
6400
+ return tags;
6401
+ }
6402
+ /** Build a shadow subtree ID set from all DOM events.
6403
+ * Two-pass cascade to handle any event ordering:
6404
+ * Pass 1 — seed *S/*P node IDs.
6405
+ * Pass 2 — cascade: any node whose parent is in the set is also in the set (repeat until stable). */
6406
+ function buildShadowSubtreeIds(allDomEvents) {
6407
+ const subtreeIds = new Set();
6408
+ for (const e of allDomEvents) {
6409
+ const data = e.data;
6410
+ for (const node of data ?? []) {
6411
+ if (isShadowDomNode(node.tag))
6412
+ subtreeIds.add(node.id);
6413
+ }
6414
+ }
6415
+ let changed = true;
6416
+ while (changed) {
6417
+ changed = false;
6418
+ for (const e of allDomEvents) {
6419
+ const data = e.data;
6420
+ for (const node of data ?? []) {
6421
+ if (!subtreeIds.has(node.id) && subtreeIds.has(node.parent)) {
6422
+ subtreeIds.add(node.id);
6423
+ changed = true;
6424
+ }
6425
+ }
6426
+ }
6427
+ }
6428
+ return subtreeIds;
6429
+ }
6430
+ function filterShadowNodes(data, subtreeIds, shadowHostTags) {
6431
+ return (data?.filter((node) => {
6432
+ if (isShadowDomNode(node.tag))
6433
+ return true;
6434
+ if (shadowHostTags.has(node.tag ?? ''))
6435
+ return true;
6436
+ return subtreeIds.has(node.id);
6437
+ }) ?? []);
6438
+ }
6439
+ /** Extract shadow DOM nodes from merged.dom (initial Discover event, processed by setup()).
6440
+ * Returns a filtered copy of the dom event, or null if no shadow DOM present. */
6441
+ function extractSpecialDom(dom, events, shadowHostTags) {
6442
+ const mutationEvents = events.filter((e) => e.event === Event.Mutation);
6443
+ const subtreeIds = buildShadowSubtreeIds([dom, ...mutationEvents]);
6444
+ const shadowNodes = filterShadowNodes(dom.data, subtreeIds, shadowHostTags);
6445
+ return shadowNodes.length ? { ...dom, data: shadowNodes } : null;
6446
+ }
6447
+ /** Extract shadow DOM mutations + StyleSheet + CustomElement events from merged.events. */
6448
+ function extractSpecialEvents(events, dom, shadowHostTags) {
6449
+ const mutationEvents = events.filter((e) => e.event === Event.Mutation);
6450
+ const subtreeIds = buildShadowSubtreeIds([dom, ...mutationEvents]);
6451
+ const result = [];
6452
+ for (const e of events) {
6453
+ const ev = e.event;
6454
+ if (ev === Event.StyleSheetAdoption ||
6455
+ ev === Event.StyleSheetUpdate ||
6456
+ ev === Event.CustomElement) {
6457
+ result.push(e);
6458
+ continue;
6459
+ }
6460
+ if (ev === Event.Mutation) {
6461
+ const shadowNodes = filterShadowNodes(e.data, subtreeIds, shadowHostTags);
6462
+ if (shadowNodes.length)
6463
+ result.push({ ...e, data: shadowNodes });
6464
+ }
6465
+ }
6466
+ return result;
6467
+ }
6468
+
6469
+ // utils/retry.ts
6470
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6471
+ /**
6472
+ * Retry until the callback returns truthy, or throw if the limit is exceeded.
6473
+ *
6474
+ * @example
6475
+ * await retry(() => doc.readyState === 'complete', { timeout: 5000, label: 'DOM ready' });
6476
+ *
6477
+ * @example
6478
+ * const el = await retry(() => document.getElementById('app'), { maxRetries: 20 });
6479
+ */
6480
+ async function retry(callback, options = {}) {
6481
+ const { interval = 100, label = 'Retry' } = options;
6482
+ const maxRetries = options.timeout != null ? Math.ceil(options.timeout / interval) : (options.maxRetries ?? 50);
6483
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
6484
+ console.log(`🔄 [${label}] attempt ${attempt}/${maxRetries}`);
6485
+ const result = await callback();
6486
+ if (result) {
6487
+ return result;
6488
+ }
6489
+ if (attempt < maxRetries) {
6490
+ await delay(interval);
5505
6491
  }
5506
- canvas.width = de.clientWidth;
5507
- canvas.height = de.clientHeight;
5508
- canvas.style.left = `${win.pageXOffset}px`;
5509
- canvas.style.top = `${win.pageYOffset}px`;
5510
- canvas.getContext('2d')?.clearRect(0, 0, canvas.width, canvas.height);
5511
- return canvas;
5512
- };
6492
+ }
6493
+ const totalMs = maxRetries * interval;
6494
+ throw new Error(`[${label}] Timed out after ${maxRetries} retries (${totalMs}ms)`);
5513
6495
  }
5514
6496
 
6497
+ const perf$2 = createPerfTimer('Render');
6498
+ const YIELD_INTERVAL_MS = 16;
6499
+ async function yieldToMain() {
6500
+ if ('scheduler' in globalThis && typeof globalThis.scheduler?.yield === 'function') {
6501
+ await globalThis.scheduler.yield();
6502
+ }
6503
+ else {
6504
+ await new Promise((resolve) => setTimeout(resolve, 0));
6505
+ }
6506
+ }
6507
+ const renderLoop = async (ctx, options) => {
6508
+ const { events, useproxy } = options;
6509
+ const shortCircuitStrategy = options.shortCircuitStrategy ?? ShortCircuitStrategy.None;
6510
+ const hash = options.hash ?? null;
6511
+ const t0 = perf$2.mark('RenderLoop start');
6512
+ let lastYield = performance.now();
6513
+ const totalEvents = events.length;
6514
+ for (let i = 0; i < totalEvents; i++) {
6515
+ const entry = events[i];
6516
+ const entryEvent = entry.event;
6517
+ const now = performance.now();
6518
+ if (now - lastYield > YIELD_INTERVAL_MS) {
6519
+ console.log(`[RenderLoop] ${i}/${totalEvents} time:`, now - lastYield);
6520
+ await yieldToMain();
6521
+ lastYield = performance.now();
6522
+ }
6523
+ switch (entryEvent) {
6524
+ case Event.StyleSheetAdoption:
6525
+ case Event.StyleSheetUpdate:
6526
+ ctx.layout.styleChange(entry);
6527
+ break;
6528
+ case Event.CustomElement:
6529
+ ctx.layout.customElement(entry);
6530
+ break;
6531
+ case Event.Mutation: {
6532
+ const domEvent = entry;
6533
+ ctx.renderTime = domEvent.time;
6534
+ if (ctx.shortCircuitRendering(shortCircuitStrategy, domEvent, hash))
6535
+ return;
6536
+ ctx.layout.markup(domEvent, useproxy);
6537
+ break;
6538
+ }
6539
+ }
6540
+ }
6541
+ perf$2.measure('RenderLoop', t0);
6542
+ };
6543
+
5515
6544
  class GXVisualizer extends Visualizer {
5516
6545
  attentionMap;
6546
+ scrollBucketMap;
5517
6547
  originalClearmap;
5518
6548
  originalSetup;
6549
+ originalScrollmap;
6550
+ originalClickmap;
5519
6551
  constructor() {
5520
6552
  super();
5521
6553
  this.attentionMap = new AttentionMapRenderer(null);
5522
- // Save references to base implementations before overriding
6554
+ this.scrollBucketMap = new ScrollBucketRenderer(null);
5523
6555
  this.originalSetup = this.setup;
5524
6556
  this.originalClearmap = this.clearmap;
6557
+ this.originalScrollmap = this.scrollmap;
6558
+ this.originalClickmap = this.clickmap;
5525
6559
  this.clearmap = this.clearmapOverride;
5526
6560
  this.setup = this.setupOverride;
6561
+ this.scrollmap = this.scrollmapOverride;
6562
+ this.clickmap = this.clickmapOverride;
5527
6563
  }
5528
- setupOverride = async (target, options) => {
5529
- // Clear existing custom renderers before re-initializing (null-safe)
5530
- this.attentionMap?.clear();
5531
- // Initialize base Visualizer (sets this.state, this.heatmap, this.layout, etc.)
5532
- await this.originalSetup(target, options);
5533
- // Re-create custom renderers with the newly initialized PlaybackState
5534
- const state = this.state;
5535
- this.attentionMap = new AttentionMapRenderer(state);
6564
+ htmlRender = async (props) => {
6565
+ const { decoded, target, portalCanvasId, useproxy, logerror } = props;
6566
+ if (!decoded || decoded.length === 0 || !target)
6567
+ return this;
6568
+ try {
6569
+ const merged = this.mergeForHtml(decoded);
6570
+ await this.setup(target, { version: decoded[0].envelope.version, dom: merged.dom, useproxy, portalCanvasId });
6571
+ await this.renderLoop(this, { events: merged.events, target, useproxy });
6572
+ }
6573
+ catch (e) {
6574
+ if (logerror)
6575
+ logerror(e);
6576
+ }
5536
6577
  return this;
5537
6578
  };
5538
- clearmapOverride = () => {
5539
- this.originalClearmap();
5540
- this.attentionMap?.clear();
6579
+ htmlCached = async (cacheKey, options) => {
6580
+ const { decoded, target } = options;
6581
+ if (!decoded || decoded.length === 0 || !target)
6582
+ return this;
6583
+ const fullKey = buildCacheKey(cacheKey, options.shortCircuitStrategy);
6584
+ const cached = await htmlCache.get(fullKey);
6585
+ if (cached) {
6586
+ try {
6587
+ await this.buildHtmlByCached(cached, options);
6588
+ return this;
6589
+ }
6590
+ catch (e) {
6591
+ options?.logerror?.(e);
6592
+ }
6593
+ }
6594
+ await this.buildHtmlForCache(fullKey, options);
6595
+ return this;
5541
6596
  };
5542
6597
  /**
5543
6598
  * Render attention/engagement map.
@@ -5549,75 +6604,256 @@ class GXVisualizer extends Visualizer {
5549
6604
  this.clearmapOverride();
5550
6605
  this.attentionMap.attention(attentionData, avgFold, this, isSecondary);
5551
6606
  };
6607
+ /**
6608
+ * Render discrete color bands for scrollRevenue / scrollAttention data.
6609
+ * @param data - Array of bucket inputs with position markers (0, 5, 10, ..., 95) and percent values
6610
+ */
6611
+ scrollBucket = async (data) => {
6612
+ this.clearmapOverride();
6613
+ await this.scrollBucketMap.renderBucket(data);
6614
+ };
6615
+ scrollmapOverride = (data, averageFold, addMarkers) => {
6616
+ this.clearmapOverride();
6617
+ this.originalScrollmap(data, averageFold, addMarkers);
6618
+ };
6619
+ clickmapOverride = (activity) => {
6620
+ this.clearmapOverride();
6621
+ this.originalClickmap(activity);
6622
+ };
6623
+ buildHtmlByCached = async (cached, options) => {
6624
+ const { target, useproxy, portalCanvasId } = options;
6625
+ if (!cached || !target)
6626
+ throw new Error('Failed to render HTML cached', { cause: { cached, target } });
6627
+ const doc = target.document;
6628
+ try {
6629
+ await this.setup(target, { version: cached.version, useproxy, portalCanvasId });
6630
+ doc.open();
6631
+ doc.writeln(cached.html);
6632
+ doc.close();
6633
+ const process = async () => {
6634
+ this.layout.hydrate(doc);
6635
+ // Replay shadow DOM from initial Discover event
6636
+ if (cached.specialDom) {
6637
+ this.layout.markup(cached.specialDom, useproxy);
6638
+ }
6639
+ // Replay shadow DOM mutations + StyleSheet + CustomElement
6640
+ await this.renderLoop(this, { ...options, events: cached.specialEvents });
6641
+ };
6642
+ await retry(() => doc.readyState === 'complete', { timeout: 30000, label: '[BuildHtml - Cached] DOM ready' });
6643
+ await process();
6644
+ return this;
6645
+ }
6646
+ catch (e) {
6647
+ throw new Error('Failed to render HTML cached', { cause: e });
6648
+ }
6649
+ };
6650
+ buildHtmlForCache = async (cacheKey, options) => {
6651
+ const { decoded, target, useproxy, portalCanvasId, logerror } = options;
6652
+ if (!decoded || decoded.length === 0 || !target)
6653
+ return this;
6654
+ try {
6655
+ const doc = target.document;
6656
+ const merged = this.mergeForHtml(decoded);
6657
+ await this.setup(target, { version: decoded[0].envelope.version, dom: merged.dom, useproxy, portalCanvasId });
6658
+ await this.renderLoop(this, { events: merged.events, target, useproxy });
6659
+ // if (hasVoidContainerChildren(doc)) return this;
6660
+ await retry(() => doc.readyState === 'complete', { timeout: 30000, label: '[BuildHtml] DOM ready' });
6661
+ const timestamp = Date.now();
6662
+ const version = decoded[0].envelope.version;
6663
+ const html = doc.documentElement.outerHTML;
6664
+ const shadowHostTags = collectShadowHostTags(doc);
6665
+ const specialDom = extractSpecialDom(merged.dom, merged.events, shadowHostTags);
6666
+ const specialEvents = extractSpecialEvents(merged.events, merged.dom, shadowHostTags);
6667
+ void htmlCache.set({ key: cacheKey, html, specialDom, specialEvents, version, timestamp });
6668
+ }
6669
+ catch (e) {
6670
+ if (logerror)
6671
+ logerror(e);
6672
+ console.log(`[BuildHtml] ~ Error:`, e);
6673
+ }
6674
+ return this;
6675
+ };
6676
+ renderLoop = async (ctx, options) => {
6677
+ await renderLoop(ctx, options);
6678
+ };
6679
+ setupOverride = async (target, options) => {
6680
+ // Mirror original Visualizer.setup(): only reset (remove listeners/state), not clear canvas.
6681
+ // The canvas lives in the old window's DOM which will be replaced by originalSetup's reset().
6682
+ this.attentionMap?.reset();
6683
+ this.scrollBucketMap?.reset();
6684
+ await this.originalSetup(target, options);
6685
+ this.attentionMap = new AttentionMapRenderer(this.state);
6686
+ this.scrollBucketMap = new ScrollBucketRenderer(this.heatmap);
6687
+ return this;
6688
+ };
6689
+ clearmapOverride = () => {
6690
+ this.originalClearmap();
6691
+ this.attentionMap?.clear();
6692
+ this.scrollBucketMap?.clear();
6693
+ };
5552
6694
  }
5553
6695
 
5554
- const useHeatmapRender = () => {
5555
- const viewId = useViewIdContext();
5556
- const data = useHeatmapDataContext((s) => s.data);
5557
- const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
5558
- const setVizRef = useHeatmapVizRectContext((s) => s.setVizRef);
5559
- const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
5560
- const setIsRenderViz = useHeatmapVizContext((s) => s.setIsRenderViz);
5561
- const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
5562
- const contentWidth = useHeatmapWidthByDevice();
6696
+ const perf$1 = createPerfTimer('Render');
6697
+ const useHeatmapIframeProcessor = () => {
6698
+ const shopId = useHeatmapConfigStore((s) => s.shopId);
5563
6699
  const deviceType = useHeatmapSettingContext((s) => s.deviceType);
5564
- const iframeRef = useRef(null);
5565
- const renderHeatmap = useCallback(async (payloads) => {
5566
- if (contentWidth === 0 || wrapperHeight === 0)
6700
+ const viewport = useHeatmapViewportByDevice();
6701
+ const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
6702
+ const setIsDomLoaded = useHeatmapVizContext((s) => s.setIsDomLoaded);
6703
+ const helperRef = useRef(null);
6704
+ const pendingRef = useRef(null);
6705
+ const reset = useCallback(() => {
6706
+ pendingRef.current = null;
6707
+ }, []);
6708
+ const run = useCallback((iframe, t0, abort) => {
6709
+ if (viewport.width === 0 || viewport.height === 0) {
6710
+ pendingRef.current = { iframe, t0, abort };
5567
6711
  return;
5568
- if (!payloads || payloads.length === 0)
6712
+ }
6713
+ startIframe({
6714
+ helperRef,
6715
+ iframe,
6716
+ shopId,
6717
+ deviceType,
6718
+ size: viewport,
6719
+ t0,
6720
+ onSuccess: (height) => {
6721
+ if (abort.signal.aborted)
6722
+ return;
6723
+ if (height)
6724
+ setIframeHeight(height);
6725
+ setIsDomLoaded(true);
6726
+ helperRef.current?.startHeightObserver();
6727
+ },
6728
+ });
6729
+ }, [deviceType]);
6730
+ // Retry when dims become available
6731
+ useEffect(() => {
6732
+ if (viewport.width === 0 || viewport.height === 0)
5569
6733
  return;
5570
- const visualizer = vizRef ?? new GXVisualizer();
5571
- if (!vizRef)
5572
- setVizRef(visualizer);
5573
- setIsRenderViz(false);
5574
- const iframe = iframeRef.current;
5575
- if (!iframe?.contentWindow)
6734
+ if (!pendingRef.current)
5576
6735
  return;
5577
- 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);
6736
+ const { iframe, t0, abort } = pendingRef.current;
6737
+ pendingRef.current = null;
6738
+ if (abort.signal.aborted)
6739
+ return;
6740
+ startIframe({
6741
+ helperRef,
6742
+ iframe,
6743
+ shopId,
6744
+ deviceType,
6745
+ size: viewport,
6746
+ t0,
6747
+ onSuccess: (height) => {
6748
+ if (abort.signal.aborted)
6749
+ return;
6750
+ if (height)
6751
+ setIframeHeight(height);
6752
+ setIsDomLoaded(true);
6753
+ helperRef.current?.startHeightObserver();
6754
+ },
5582
6755
  });
5583
- // setIsRenderViz(true);
5584
- }, [wrapperHeight, contentWidth, deviceType]);
6756
+ }, [viewport]); // eslint-disable-line react-hooks/exhaustive-deps
5585
6757
  useEffect(() => {
5586
- if (!data || data.length === 0)
5587
- return;
5588
- const decoded = decodeArrayClarity(data);
5589
- renderHeatmap(decoded);
5590
6758
  return () => {
5591
- setVizRef(null);
6759
+ helperRef.current?.stop();
6760
+ helperRef.current = null;
5592
6761
  };
5593
- }, [data, renderHeatmap, setVizRef]);
5594
- return {
5595
- iframeRef,
5596
- };
6762
+ }, []);
6763
+ return { run, reset };
5597
6764
  };
5598
- function initIframeHelper(iframe, deviceType = EDeviceType.Desktop, size, onSuccess) {
6765
+ // ── Helpers ───────────────────────────────────────────────────────────────────
6766
+ function startIframe({ helperRef, iframe, shopId, deviceType = EDeviceType.Desktop, size, t0, onSuccess, }) {
5599
6767
  const docWidth = size.width ?? 0;
5600
6768
  const docHeight = size.height ?? 0;
5601
6769
  if (docHeight === 0)
5602
6770
  return;
5603
- stop();
5604
- start({
5605
- deviceType: deviceType,
6771
+ helperRef.current?.stop();
6772
+ const tHelper = perf$1.mark('IframeHelper.start');
6773
+ const helper = createIframeHelper();
6774
+ helperRef.current = helper;
6775
+ helper.start({
6776
+ deviceType,
5606
6777
  targetWidth: docWidth,
5607
6778
  targetHeight: docHeight,
5608
- iframe: iframe,
6779
+ iframe,
5609
6780
  debug: true,
6781
+ shopId,
6782
+ heightObserverStartDelayMs: 1000,
5610
6783
  onSuccess: (data) => {
6784
+ perf$1.measure('IframeHelper processing', tHelper);
6785
+ perf$1.measure('Total render', t0);
5611
6786
  iframe.style.height = `${data.height}px`;
5612
6787
  onSuccess(data.height);
5613
6788
  },
5614
6789
  });
5615
- // fixer.recalculate();
5616
6790
  }
5617
6791
 
6792
+ const perf = createPerfTimer('Render');
6793
+ const useHeatmapRenderDom = () => {
6794
+ const viewId = useViewIdContext();
6795
+ const data = useHeatmapDataContext((s) => s.data);
6796
+ const excludeClassNames = useHeatmapConfigStore((s) => s.excludeClassNames);
6797
+ const setVizRef = useHeatmapVizRectContext((s) => s.setVizRef);
6798
+ const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
6799
+ const setIsDomLoaded = useHeatmapVizContext((s) => s.setIsDomLoaded);
6800
+ const deviceType = useHeatmapSettingContext((s) => s.deviceType);
6801
+ const heatmapType = useHeatmapSettingContext((s) => s.heatmapType);
6802
+ const elementToShow = useHeatmapDataContext((s) => s.dataInfo?.elementToShow);
6803
+ const dataHash = useHeatmapDataContext((s) => s.dataHash);
6804
+ const iframeRef = useRef(null);
6805
+ const abortRef = useRef(null);
6806
+ const elementToShowRef = useRef(null);
6807
+ const dataHashRef = useRef(null);
6808
+ const heatmapTypeRef = useRef(heatmapType);
6809
+ elementToShowRef.current = elementToShow ?? null;
6810
+ dataHashRef.current = dataHash ?? null;
6811
+ heatmapTypeRef.current = heatmapType;
6812
+ const { run: runIframeSetup, reset: resetIframeSetup } = useHeatmapIframeProcessor();
6813
+ const renderHeatmap = useCallback(async (payloads) => {
6814
+ if (!payloads || payloads.length === 0)
6815
+ return;
6816
+ const iframe = iframeRef.current;
6817
+ const contentWindow = iframe?.contentWindow;
6818
+ if (!contentWindow)
6819
+ return;
6820
+ abortRef.current?.abort();
6821
+ const abort = new AbortController();
6822
+ abortRef.current = abort;
6823
+ resetIframeSetup();
6824
+ const t0 = perf.mark('RenderHeatmap start');
6825
+ const visualizer = vizRef ?? new GXVisualizer();
6826
+ if (!vizRef)
6827
+ setVizRef(visualizer);
6828
+ visualizer.configure({ excludeClassNames });
6829
+ setIsDomLoaded(false);
6830
+ // Phase 1: render DOM — does not depend on contentWidth/wrapperHeight
6831
+ const cacheKey = dataHashRef.current;
6832
+ const options = {
6833
+ decoded: payloads,
6834
+ target: contentWindow,
6835
+ portalCanvasId: viewId,
6836
+ logerror: (error) => {
6837
+ console.error('Error rendering HTML', error);
6838
+ },
6839
+ };
6840
+ await perf.wrap('RenderHtml', () => cacheKey ? visualizer.htmlCached(cacheKey, options) : visualizer.htmlRender(options));
6841
+ if (abort.signal.aborted)
6842
+ return;
6843
+ // Phase 2: iframe setup — deferred to useIframeSetup (handles dims dependency)
6844
+ runIframeSetup(iframe, t0, abort);
6845
+ }, [deviceType]);
6846
+ useEffect(() => {
6847
+ if (!data || data.length === 0)
6848
+ return;
6849
+ renderHeatmap(decodeArrayClarity(data));
6850
+ }, [data, renderHeatmap]);
6851
+ return { iframeRef };
6852
+ };
6853
+
5618
6854
  const useReplayRender = () => {
5619
6855
  const data = useHeatmapDataContext((s) => s.data);
5620
- const setIsRenderViz = useHeatmapVizContext((s) => s.setIsRenderViz);
6856
+ const setIsDomLoaded = useHeatmapVizContext((s) => s.setIsDomLoaded);
5621
6857
  const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
5622
6858
  const visualizerRef = useRef(null);
5623
6859
  const iframeRef = useRef(null);
@@ -5637,7 +6873,7 @@ const useReplayRender = () => {
5637
6873
  version: envelope.version,
5638
6874
  onresize: (height) => {
5639
6875
  height && setIframeHeight(height);
5640
- setIsRenderViz(true);
6876
+ setIsDomLoaded(true);
5641
6877
  },
5642
6878
  mobile,
5643
6879
  vNext: true,
@@ -5740,10 +6976,12 @@ const useReplayRender = () => {
5740
6976
  const useHeatmapRenderByMode = (mode) => {
5741
6977
  const heatmapResult = useMemo(() => {
5742
6978
  switch (mode) {
5743
- case 'heatmap':
5744
- return useHeatmapRender;
5745
- case 'replay':
6979
+ case EHeatmapMode.Heatmap:
6980
+ return useHeatmapRenderDom;
6981
+ case EHeatmapMode.Replay:
5746
6982
  return useReplayRender;
6983
+ default:
6984
+ return useHeatmapRenderDom;
5747
6985
  }
5748
6986
  }, [mode]);
5749
6987
  return heatmapResult();
@@ -5778,148 +7016,6 @@ const useContainerDimensions = (props) => {
5778
7016
  return { containerWidth, containerHeight };
5779
7017
  };
5780
7018
 
5781
- const useContentDimensions = ({ iframeRef }) => {
5782
- const contentWidth = useHeatmapWidthByDevice();
5783
- useEffect(() => {
5784
- if (!contentWidth)
5785
- return;
5786
- if (!iframeRef.current)
5787
- return;
5788
- // iframeRef.current.width = `${contentWidth}px`;
5789
- }, [contentWidth, iframeRef]);
5790
- return { contentWidth };
5791
- };
5792
-
5793
- const useObserveIframeHeight = (props) => {
5794
- const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
5795
- const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
5796
- const isRenderViz = useHeatmapVizContext((s) => s.isRenderViz);
5797
- const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
5798
- const { iframeRef } = props;
5799
- const resizeObserverRef = useRef(null);
5800
- const mutationObserverRef = useRef(null);
5801
- const debounceTimerRef = useRef(null);
5802
- const lastHeightRef = useRef(0);
5803
- const animationFrameRef = useRef(null);
5804
- const updateIframeHeight = useCallback(() => {
5805
- const iframe = iframeRef.current;
5806
- if (!iframe)
5807
- return;
5808
- try {
5809
- const iframeDocument = iframe.contentDocument;
5810
- const iframeBody = iframeDocument?.body;
5811
- const iframeDocumentElement = iframeDocument?.documentElement;
5812
- if (!iframeBody || !iframeDocumentElement)
5813
- return;
5814
- // iframe.style.height = 'auto'; // TODO: check if this is needed
5815
- requestAnimationFrame(() => {
5816
- iframe.style.height = `${wrapperHeight}px`;
5817
- const bodyHeight = Math.max(iframeBody.scrollHeight, iframeBody.offsetHeight, iframeBody.clientHeight);
5818
- const documentHeight = Math.max(iframeDocumentElement.scrollHeight, iframeDocumentElement.offsetHeight, iframeDocumentElement.clientHeight);
5819
- const actualHeight = Math.max(bodyHeight, documentHeight);
5820
- if (actualHeight > 0) {
5821
- lastHeightRef.current = actualHeight;
5822
- // iframe.height = `${actualHeight}px`;
5823
- iframe.style.height = `${actualHeight}px`;
5824
- setIframeHeight(actualHeight);
5825
- }
5826
- });
5827
- }
5828
- catch (error) {
5829
- console.warn('Cannot measure iframe content:', error);
5830
- }
5831
- }, [iframeRef, wrapperHeight]);
5832
- useCallback(() => {
5833
- // Cancel pending updates
5834
- if (debounceTimerRef.current) {
5835
- clearTimeout(debounceTimerRef.current);
5836
- }
5837
- if (animationFrameRef.current) {
5838
- cancelAnimationFrame(animationFrameRef.current);
5839
- }
5840
- debounceTimerRef.current = setTimeout(() => {
5841
- animationFrameRef.current = requestAnimationFrame(() => {
5842
- updateIframeHeight();
5843
- });
5844
- }, 50);
5845
- }, [updateIframeHeight]);
5846
- // Immediate update không debounce (cho ResizeObserver)
5847
- const immediateUpdate = useCallback(() => {
5848
- if (animationFrameRef.current) {
5849
- cancelAnimationFrame(animationFrameRef.current);
5850
- }
5851
- animationFrameRef.current = requestAnimationFrame(() => {
5852
- updateIframeHeight();
5853
- });
5854
- }, [updateIframeHeight]);
5855
- useEffect(() => {
5856
- const iframe = iframeRef.current;
5857
- if (!iframe || !iframeHeight || !isRenderViz)
5858
- return;
5859
- const setupObservers = () => {
5860
- try {
5861
- const iframeDocument = iframe.contentDocument;
5862
- const iframeBody = iframeDocument?.body;
5863
- if (!iframeBody)
5864
- return;
5865
- // Cleanup existing observers
5866
- if (resizeObserverRef.current) {
5867
- resizeObserverRef.current.disconnect();
5868
- }
5869
- if (mutationObserverRef.current) {
5870
- mutationObserverRef.current.disconnect();
5871
- }
5872
- if (typeof window.ResizeObserver !== 'undefined') {
5873
- resizeObserverRef.current = new ResizeObserver(immediateUpdate);
5874
- resizeObserverRef.current.observe(iframeBody);
5875
- const iframeDocumentElement = iframeDocument?.documentElement;
5876
- if (iframeDocumentElement) {
5877
- resizeObserverRef.current.observe(iframeDocumentElement);
5878
- }
5879
- }
5880
- if (typeof window.MutationObserver !== 'undefined') {
5881
- mutationObserverRef.current = new MutationObserver(immediateUpdate);
5882
- mutationObserverRef.current.observe(iframeBody, {
5883
- childList: true,
5884
- subtree: true,
5885
- attributes: true,
5886
- attributeFilter: ['style', 'class'],
5887
- characterData: false,
5888
- });
5889
- }
5890
- updateIframeHeight();
5891
- }
5892
- catch (error) {
5893
- console.warn('Cannot access iframe content:', error);
5894
- }
5895
- };
5896
- if (iframe.contentDocument?.readyState === 'complete') {
5897
- setupObservers();
5898
- }
5899
- else {
5900
- iframe.addEventListener('load', setupObservers, { once: true });
5901
- }
5902
- return () => {
5903
- // Cleanup observers
5904
- if (resizeObserverRef.current) {
5905
- resizeObserverRef.current.disconnect();
5906
- }
5907
- if (mutationObserverRef.current) {
5908
- mutationObserverRef.current.disconnect();
5909
- }
5910
- // Cleanup timers
5911
- if (debounceTimerRef.current) {
5912
- clearTimeout(debounceTimerRef.current);
5913
- }
5914
- if (animationFrameRef.current) {
5915
- cancelAnimationFrame(animationFrameRef.current);
5916
- }
5917
- iframe.removeEventListener('load', setupObservers);
5918
- };
5919
- }, [iframeRef, iframeHeight, isRenderViz]);
5920
- return {};
5921
- };
5922
-
5923
7019
  const useScaleCalculation = (props) => {
5924
7020
  const widthScale = useHeatmapVizContext((s) => s.widthScale);
5925
7021
  const zoomRatio = useHeatmapVizContext((s) => s.zoomRatio);
@@ -5929,9 +7025,10 @@ const useScaleCalculation = (props) => {
5929
7025
  const setScale = useHeatmapVizContext((s) => s.setScale);
5930
7026
  const setIsScaledToFit = useHeatmapVizContext((s) => s.setIsScaledToFit);
5931
7027
  const setMinZoomRatio = useHeatmapVizContext((s) => s.setMinZoomRatio);
5932
- const { containerWidth, containerHeight, contentWidth, contentHeight } = props;
7028
+ const viewport = useHeatmapViewportByDevice();
7029
+ const { containerWidth, containerHeight, iframeHeight } = props;
5933
7030
  const calculateScaleResult = useCallback(() => {
5934
- if (containerWidth > 0 && contentWidth > 0 && containerHeight > 0 && contentHeight > 0) {
7031
+ if (containerWidth > 0 && viewport.width > 0 && containerHeight > 0 && iframeHeight > 0) {
5935
7032
  // 1. Calculate available dimensions
5936
7033
  const availableWidth = containerWidth - HEATMAP_CONFIG['padding'] * 2;
5937
7034
  const toolbarHeight = HEATMAP_CONFIG['heightToolbar'] || 0;
@@ -5939,12 +7036,12 @@ const useScaleCalculation = (props) => {
5939
7036
  const availableHeight = containerHeight - toolbarHeight - paddingTotal;
5940
7037
  // 2. Calculate widthScale (base scale to fit content width into container width)
5941
7038
  // This represents 100% zoom (fit to width)
5942
- const widthScale = Math.min(availableWidth / contentWidth, 1);
7039
+ const widthScale = Math.min(availableWidth / viewport.width, 1);
5943
7040
  // 3. Calculate minZoomRatio (zoom ratio to fit height)
5944
7041
  // At minZoomRatio, the content should fit entirely within the container height
5945
- // Formula: contentHeight * widthScale * (minZoomRatio / 100) = availableHeight
5946
- // => minZoomRatio = (availableHeight / (contentHeight * widthScale)) * 100
5947
- const calculatedMinZoomRatio = (availableHeight / (contentHeight * widthScale)) * 100;
7042
+ // Formula: iframeHeight * widthScale * (minZoomRatio / 100) = availableHeight
7043
+ // => minZoomRatio = (availableHeight / (iframeHeight * widthScale)) * 100
7044
+ const calculatedMinZoomRatio = (availableHeight / (iframeHeight * widthScale)) * 100;
5948
7045
  // Limit minZoomRatio: cannot exceed MAX_ZOOM_RATIO (100%)
5949
7046
  // and should have a reasonable minimum (e.g., 1%)
5950
7047
  const finalMinZoomRatio = Math.max(1, Math.min(calculatedMinZoomRatio, maxZoomRatio));
@@ -5963,7 +7060,7 @@ const useScaleCalculation = (props) => {
5963
7060
  setIsScaledToFit(isCurrentlyFitted);
5964
7061
  setMinZoomRatio(finalMinZoomRatio);
5965
7062
  }
5966
- }, [containerWidth, containerHeight, contentWidth, contentHeight, zoomRatio, maxZoomRatio]);
7063
+ }, [containerWidth, containerHeight, viewport.width, iframeHeight, zoomRatio, maxZoomRatio]);
5967
7064
  useEffect(() => {
5968
7065
  calculateScaleResult();
5969
7066
  }, [calculateScaleResult]);
@@ -5996,20 +7093,15 @@ const useHeatmapScale = (props) => {
5996
7093
  // 1. Observe container dimensions
5997
7094
  const { containerWidth, containerHeight } = useContainerDimensions({ wrapperRef });
5998
7095
  // 2. Get content dimensions from config
5999
- const { contentWidth } = useContentDimensions({ iframeRef });
7096
+ const viewport = useHeatmapViewportByDevice();
6000
7097
  // 3. Observe iframe height (now reacts to width changes)
6001
- useObserveIframeHeight({ iframeRef });
7098
+ // useObserveIframeHeight({ iframeRef });
6002
7099
  // 4. Calculate scale
6003
- const { widthScale } = useScaleCalculation({
6004
- containerWidth,
6005
- containerHeight,
6006
- contentWidth,
6007
- contentHeight: iframeHeight,
6008
- });
7100
+ const { widthScale } = useScaleCalculation({ containerWidth, containerHeight, iframeHeight });
6009
7101
  // 5. Setup scroll sync
6010
7102
  const { handleScroll } = useScrollSync({ widthScale, iframeRef });
6011
7103
  const scaledHeight = iframeHeight * widthScale;
6012
- const scaledWidth = contentWidth * widthScale;
7104
+ const scaledWidth = viewport.width * widthScale;
6013
7105
  return {
6014
7106
  scaledWidth,
6015
7107
  scaledHeight,
@@ -6215,10 +7307,10 @@ const useScrollmapZones = (options) => {
6215
7307
  const newZones = createZones(scrollmap);
6216
7308
  setZones(newZones);
6217
7309
  setIsReady(true);
6218
- logger$9.log(`[useScrollmap] Created ${newZones.length} zones in ${mode} mode`);
7310
+ logger$3.log(`[useScrollmap] Created ${newZones.length} zones in ${mode} mode`);
6219
7311
  }
6220
7312
  catch (error) {
6221
- logger$9.error('[useScrollmap] Error:', error);
7313
+ logger$3.error('[useScrollmap] Error:', error);
6222
7314
  setIsReady(false);
6223
7315
  }
6224
7316
  }, [enabled, scrollmap, mode, createZones]);
@@ -7048,11 +8140,11 @@ const AutoScrollHandler = ({ visualRef }) => {
7048
8140
  };
7049
8141
 
7050
8142
  const PortalAreaRenderer = ({ iframeRef, visualRef, shadowRoot, onAreaCreated, onAreaClick, }) => {
7051
- const isRenderViz = useHeatmapVizContext((s) => s.isRenderViz);
8143
+ const isDomLoaded = useHeatmapVizContext((s) => s.isDomLoaded);
7052
8144
  const iframeDocument = iframeRef.current?.contentDocument || undefined;
7053
8145
  const { shadowContainer, isReady } = useAreaRendererContainer(iframeDocument, shadowRoot);
7054
- useAreaRectSync({ iframeDocument, shadowRoot, enabled: isReady && isRenderViz });
7055
- useAreaPositionsUpdater({ iframeRef, visualRef, enabled: isReady && isRenderViz });
8146
+ useAreaRectSync({ iframeDocument, shadowRoot, enabled: isReady && isDomLoaded });
8147
+ useAreaPositionsUpdater({ iframeRef, visualRef, enabled: isReady && isDomLoaded });
7056
8148
  if (!shadowContainer || !isReady)
7057
8149
  return null;
7058
8150
  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 +8153,7 @@ const PortalAreaRenderer = ({ iframeRef, visualRef, shadowRoot, onAreaCreated, o
7061
8153
  const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true, onAreaClick, }) => {
7062
8154
  const clickAreas = useHeatmapDataContext((s) => s.clickAreas);
7063
8155
  const resetView = useHeatmapAreaClickContext((s) => s.resetView);
7064
- const isRenderViz = useHeatmapVizContext((s) => s.isRenderViz);
8156
+ const isDomLoaded = useHeatmapVizContext((s) => s.isDomLoaded);
7065
8157
  useAreaTopAutoDetect({ autoCreateTopN, shadowRoot, disabled: !!clickAreas?.length });
7066
8158
  useAreaFilterVisible({ iframeRef, enableOverlapResolution });
7067
8159
  useAreaHydration({ shadowRoot });
@@ -7070,7 +8162,7 @@ const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, e
7070
8162
  resetView();
7071
8163
  };
7072
8164
  }, []);
7073
- if (!iframeRef.current || !isRenderViz)
8165
+ if (!iframeRef.current || !isDomLoaded)
7074
8166
  return null;
7075
8167
  return (jsx(Fragment, { children: jsx(PortalAreaRenderer, { iframeRef: iframeRef, visualRef: visualRef, shadowRoot: shadowRoot, onAreaClick: onAreaClick }) }));
7076
8168
  };
@@ -7201,7 +8293,7 @@ const useAnchorPosition = (calloutRef, props) => {
7201
8293
  const ElementMissing = ({ show = true, visualRef }) => {
7202
8294
  const widthScale = useHeatmapVizContext((s) => s.widthScale);
7203
8295
  const missingElementRef = useRef(null);
7204
- const wrapperWidth = useHeatmapWidthByDevice();
8296
+ const viewport = useHeatmapViewportByDevice();
7205
8297
  const [scrollPosition, setScrollPosition] = useState({ scrollTop: 0, scrollLeft: 0 });
7206
8298
  useEffect(() => {
7207
8299
  const container = visualRef.current;
@@ -7231,7 +8323,7 @@ const ElementMissing = ({ show = true, visualRef }) => {
7231
8323
  const containerHeight = containerRect?.height ?? 0;
7232
8324
  const topPosition = scrollTop + (containerHeight + elementHeightCenter) / 2;
7233
8325
  const topPositionScaled = topPosition / widthScale;
7234
- const leftPosition = wrapperWidth / 2;
8326
+ const leftPosition = viewport.width / 2;
7235
8327
  return (jsxs(Fragment, { children: [jsx("div", { className: "missingElement-backdrop", style: {
7236
8328
  position: 'absolute',
7237
8329
  top: 0,
@@ -7356,7 +8448,7 @@ const ElementOverlayComponent = (props) => {
7356
8448
  const { type, element, onClick, elementId, hideOutline } = props;
7357
8449
  const widthScale = useHeatmapVizContext((s) => s.widthScale);
7358
8450
  const viewportHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
7359
- const viewportWidth = useHeatmapWidthByDevice();
8451
+ const viewport = useHeatmapViewportByDevice();
7360
8452
  const overlayStyle = useMemo(() => {
7361
8453
  const isInvalid = !element || (element.width === 0 && element.height === 0);
7362
8454
  if (isInvalid)
@@ -7373,7 +8465,7 @@ const ElementOverlayComponent = (props) => {
7373
8465
  const isHovered = type === 'hovered';
7374
8466
  const badgeWidthScale = isHovered ? 1 : widthScale;
7375
8467
  const showCallout = !!element?.mousePosition && !isHovered;
7376
- return (jsxs(Fragment$1, { children: [jsx("div", { onClick: onClick, className: `heatmapElement heatmapElement--${type} ${hideOutline ? 'heatmapElement--hide-outline' : ''}`, id: elementId, style: overlayStyle, children: showCallout && jsx(ElementCalloutOverlay, { ...props }) }), jsx(BackdropCanvas, { activeElement: overlayStyle, viewportWidth: viewportWidth, viewportHeight: viewportHeight, show: !isHovered }), jsx(RankBadge, { hash: element.hash, show: isHovered, index: element.rank, elementRect: element, widthScale: badgeWidthScale, clickOnElement: onClick })] }));
8468
+ return (jsxs(Fragment$1, { children: [jsx("div", { onClick: onClick, className: `heatmapElement heatmapElement--${type} ${hideOutline ? 'heatmapElement--hide-outline' : ''}`, id: elementId, style: overlayStyle, children: showCallout && jsx(ElementCalloutOverlay, { ...props }) }), jsx(BackdropCanvas, { activeElement: overlayStyle, viewportWidth: viewport.width, viewportHeight: viewportHeight, show: !isHovered }), jsx(RankBadge, { hash: element.hash, show: isHovered, index: element.rank, elementRect: element, widthScale: badgeWidthScale, clickOnElement: onClick })] }));
7377
8469
  };
7378
8470
  ElementOverlayComponent.displayName = 'ElementOverlay';
7379
8471
  const ElementOverlay = memo(ElementOverlayComponent);
@@ -7438,7 +8530,7 @@ const HeatmapElements = (props) => {
7438
8530
  };
7439
8531
 
7440
8532
  const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
7441
- const contentWidth = useHeatmapWidthByDevice();
8533
+ const viewport = useHeatmapViewportByDevice();
7442
8534
  const dataInfo = useHeatmapDataContext((s) => s.dataInfo);
7443
8535
  const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
7444
8536
  const visualizer = {
@@ -7452,7 +8544,7 @@ const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
7452
8544
  if (!iframeRef.current)
7453
8545
  return null;
7454
8546
  return (jsx(HeatmapElements, { visualizer: visualizer, visualRef: visualRef, iframeRef: iframeRef, wrapperRef: wrapperRef, heatmapInfo: dataInfo, isVisible: true, positionMode: DEFAULT_POSITION_MODE, isHideTopRank: true, iframeDimensions: {
7455
- width: contentWidth,
8547
+ width: viewport.width,
7456
8548
  position: 'absolute',
7457
8549
  top: 0,
7458
8550
  left: 0,
@@ -7476,45 +8568,6 @@ const VizClickmap = ({ iframeRef, visualRef, wrapperRef }) => {
7476
8568
  }
7477
8569
  };
7478
8570
 
7479
- const IS_ENABLE_MINIMAP = false;
7480
- const Minimap = ({ zones, maxUsers }) => {
7481
- const scrollType = useHeatmapSettingContext((s) => s.scrollType);
7482
- const showMinimap = useHeatmapScrollContext((s) => s.showMinimap);
7483
- const isScrollType = [EScrollType.Attention].includes(scrollType ?? EScrollType.Depth);
7484
- if (!showMinimap || !isScrollType || !IS_ENABLE_MINIMAP)
7485
- return null;
7486
- return (jsx("div", { style: {
7487
- position: 'fixed',
7488
- left: '20px',
7489
- top: '50%',
7490
- transform: 'translateY(-50%)',
7491
- width: '60px',
7492
- height: '400px',
7493
- backgroundColor: 'white',
7494
- borderRadius: '8px',
7495
- boxShadow: '0 4px 16px rgba(0,0,0,0.15)',
7496
- zIndex: 10002,
7497
- padding: '8px',
7498
- boxSizing: 'border-box',
7499
- }, children: jsx("div", { style: {
7500
- width: '100%',
7501
- height: '100%',
7502
- borderRadius: '4px',
7503
- overflow: 'hidden',
7504
- display: 'flex',
7505
- flexDirection: 'column',
7506
- }, children: zones.map((zone) => {
7507
- const normalized = maxUsers > 0 ? zone.percUsers / maxUsers : 0;
7508
- const color = getScrollGradientColor(normalized);
7509
- return (jsx("div", { title: `${zone.label}: ${zone.percUsers.toFixed(2)}%`, style: {
7510
- width: '100%',
7511
- flex: `${zone.endY - zone.startY}`,
7512
- backgroundColor: color,
7513
- borderBottom: '1px solid rgba(255,255,255,0.2)',
7514
- } }, zone.id));
7515
- }) }) }));
7516
- };
7517
-
7518
8571
  const ScrollZoneTooltip = ({ zone, position, currentScrollPercent, scrollmap, }) => {
7519
8572
  const CompScrollZoneTooltip = useHeatmapControlStore((state) => state.controls.ScrollZoneTooltip);
7520
8573
  const tooltipRef = useRef(null);
@@ -7540,17 +8593,9 @@ const ScrollZoneTooltip = ({ zone, position, currentScrollPercent, scrollmap, })
7540
8593
 
7541
8594
  const HoverZones = ({ iframeRef, wrapperRef, position, currentScrollPercent }) => {
7542
8595
  const scrollmap = useHeatmapDataContext((s) => s.scrollmap);
7543
- // const hoveredZone = useHeatmapVizScrollStore((state) => state.hoveredZone);
7544
- // const setHoveredZone = useHeatmapVizScrollStore((state) => state.setHoveredZone);
7545
- const { zones, isReady, maxUsers } = useScrollmapZones({
7546
- iframeRef,
7547
- wrapperRef,
7548
- });
7549
- if (!isReady || !zones.length)
7550
- return null;
7551
8596
  if (!position)
7552
8597
  return null;
7553
- return (jsxs(Fragment, { children: [jsx(ScrollZoneTooltip, { position: position, currentScrollPercent: currentScrollPercent, scrollmap: scrollmap || [] }), jsx(Minimap, { zones: zones, maxUsers: maxUsers })] }));
8598
+ return (jsx(Fragment, { children: jsx(ScrollZoneTooltip, { position: position, currentScrollPercent: currentScrollPercent, scrollmap: scrollmap || [] }) }));
7554
8599
  };
7555
8600
 
7556
8601
  const ScrollOverlay = ({ wrapperRef, iframeRef }) => {
@@ -7716,7 +8761,7 @@ const VizLoadingCanvas = () => {
7716
8761
  const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHeight, onScroll, }) => {
7717
8762
  const isLoadingCanvas = useHeatmapSettingContext((state) => state.isLoadingCanvas);
7718
8763
  const widthScale = useHeatmapVizContext((s) => s.widthScale);
7719
- const contentWidth = useHeatmapWidthByDevice();
8764
+ const viewport = useHeatmapViewportByDevice();
7720
8765
  const contentHeight = calcContentHeight();
7721
8766
  return (jsx("div", { ref: visualRef, className: "gx-hm-visual Polaris-Scrollable Polaris-Scrollable--vertical Polaris-Scrollable--scrollbarWidthThin Polaris-Scrollable--scrollbarGutterStable", onScroll: onScroll, style: {
7722
8767
  overflowX: 'hidden',
@@ -7738,7 +8783,7 @@ const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHe
7738
8783
  paddingBottom: HEATMAP_STYLE['viz']['paddingBottom'],
7739
8784
  background: HEATMAP_STYLE['viz']['background'],
7740
8785
  }, children: jsx("div", { className: "gx-hm-wrapper", ref: wrapperRef, style: {
7741
- width: contentWidth,
8786
+ width: viewport.width,
7742
8787
  height: iframeHeight,
7743
8788
  transform: `scale(${widthScale})`,
7744
8789
  transformOrigin: 'top center',
@@ -7751,14 +8796,14 @@ const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHe
7751
8796
  }
7752
8797
  };
7753
8798
 
7754
- const VizDomRenderer = ({ mode = 'heatmap' }) => {
8799
+ const VizDomRenderer = ({ mode }) => {
7755
8800
  const viewId = useViewIdContext();
7756
- const contentWidth = useHeatmapWidthByDevice();
8801
+ const viewport = useHeatmapViewportByDevice();
7757
8802
  const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
7758
- const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
8803
+ // const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
7759
8804
  const wrapperRef = useRef(null);
7760
8805
  const visualRef = useRef(null);
7761
- const { iframeRef } = useHeatmapRenderByMode(mode);
8806
+ const { iframeRef } = useHeatmapRenderDom();
7762
8807
  const { scaledHeight, handleScroll } = useHeatmapScale({ wrapperRef, iframeRef, visualRef });
7763
8808
  useHeatmapCanvas();
7764
8809
  useRenderCount('VizDomRenderer');
@@ -7766,7 +8811,7 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
7766
8811
  const scrollTop = e.currentTarget.scrollTop;
7767
8812
  handleScroll(scrollTop);
7768
8813
  };
7769
- return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [jsx(VizClickmap, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef }), jsx("iframe", { ...HEATMAP_IFRAME, id: `clarity-iframe-${viewId}`, ref: iframeRef, width: contentWidth, height: wrapperHeight }), jsx(VizLoadingCanvas, {}), jsx(VizScrollMap, { visualRef: visualRef, iframeRef: iframeRef, wrapperRef: wrapperRef })] }));
8814
+ return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [jsx(VizClickmap, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef }), jsx("iframe", { ...HEATMAP_IFRAME, id: `clarity-iframe-${viewId}`, ref: iframeRef, width: viewport.width, height: viewport.height }), jsx(VizLoadingCanvas, {}), jsx(VizScrollMap, { visualRef: visualRef, iframeRef: iframeRef, wrapperRef: wrapperRef })] }));
7770
8815
  };
7771
8816
 
7772
8817
  const VizLoading = () => {
@@ -7783,8 +8828,9 @@ const VizLoading = () => {
7783
8828
  const VizDomHeatmap = () => {
7784
8829
  const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
7785
8830
  const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
8831
+ const setWrapperHeight = useHeatmapVizRectContext((s) => s.setWrapperHeight);
7786
8832
  const setVizRef = useHeatmapVizRectContext((s) => s.setVizRef);
7787
- const setIsRenderViz = useHeatmapVizContext((s) => s.setIsRenderViz);
8833
+ const setIsDomLoaded = useHeatmapVizContext((s) => s.setIsDomLoaded);
7788
8834
  const setSelectedElement = useHeatmapClickContext((s) => s.setSelectedElement);
7789
8835
  const setHoveredElement = useHeatmapHoverContext((s) => s.setHoveredElement);
7790
8836
  // const setSelectedArea = useHeatmapAreaClickContext((s) => s.setSelectedArea);
@@ -7794,7 +8840,8 @@ const VizDomHeatmap = () => {
7794
8840
  const cleanUp = () => {
7795
8841
  setVizRef(null);
7796
8842
  setIframeHeight(0);
7797
- setIsRenderViz(false);
8843
+ setWrapperHeight(0);
8844
+ setIsDomLoaded(false);
7798
8845
  setSelectedElement(null);
7799
8846
  setHoveredElement(null);
7800
8847
  // setSelectedArea(null);
@@ -7803,13 +8850,13 @@ const VizDomHeatmap = () => {
7803
8850
  };
7804
8851
  useEffect(() => {
7805
8852
  return cleanUp;
7806
- }, []);
7807
- return (jsxs(VizContainer, { isActive: true, children: [jsx(VizDomRenderer, {}), iframeHeight === 0 && jsx(VizLoading, {})] }));
8853
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
8854
+ return (jsxs(VizContainer, { isActive: true, children: [jsx(VizDomRenderer, { mode: EHeatmapMode.Heatmap }), iframeHeight === 0 && jsx(VizLoading, {})] }));
7808
8855
  };
7809
8856
  VizDomHeatmap.displayName = 'VizDomHeatmap';
7810
8857
 
7811
8858
  const VizLiveRenderer = () => {
7812
- const contentWidth = useHeatmapWidthByDevice();
8859
+ const viewport = useHeatmapViewportByDevice();
7813
8860
  const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
7814
8861
  const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
7815
8862
  const visualRef = useRef(null);
@@ -7820,13 +8867,13 @@ const VizLiveRenderer = () => {
7820
8867
  const scrollTop = e.currentTarget.scrollTop;
7821
8868
  handleScroll(scrollTop);
7822
8869
  };
7823
- return (jsx(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, iframeHeight: iframeHeight, onScroll: onScroll, children: jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, height: wrapperHeight, scrolling: "no" }) }));
8870
+ return (jsx(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, iframeHeight: iframeHeight, onScroll: onScroll, children: jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: viewport.width, height: wrapperHeight, scrolling: "no" }) }));
7824
8871
  };
7825
8872
 
7826
8873
  const VizLiveHeatmap = () => {
7827
8874
  const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
7828
8875
  const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
7829
- useHeatmapLiveStore((state) => state.reset);
8876
+ useHeatmapLiveContext((s) => s.reset);
7830
8877
  const CompVizLoading = useHeatmapControlStore((state) => state.controls.VizLoading);
7831
8878
  // TODO: Remove this after testing
7832
8879
  useEffect(() => {
@@ -7877,11 +8924,11 @@ const ContentTopBar = () => {
7877
8924
  }, children: CompTopBar && jsx(CompTopBar, {}) }));
7878
8925
  };
7879
8926
 
7880
- const HeatmapLayout = ({ data, clickmap, clickAreas, scrollmap, attentionMap, controls, dataInfo, isLoading, isLoadingCanvas, }) => {
8927
+ const HeatmapLayout = ({ shopId, data, clickmap, clickAreas, scrollmap, attentionMap, controls, dataInfo, isLoading, isLoadingCanvas, excludeClassNames, }) => {
7881
8928
  useRegisterControl(controls);
7882
8929
  useRegisterData(data, dataInfo);
7883
8930
  useRegisterHeatmap({ clickmap, scrollmap, clickAreas, attentionMap });
7884
- useRegisterConfig({ isLoading, isLoadingCanvas });
8931
+ useRegisterConfig({ isLoading, isLoadingCanvas, shopId, excludeClassNames });
7885
8932
  // performanceLogger.configure({
7886
8933
  // enabled: true,
7887
8934
  // logToConsole: false,
@@ -7911,4 +8958,4 @@ const HeatmapLayout = ({ data, clickmap, clickAreas, scrollmap, attentionMap, co
7911
8958
  }
7912
8959
  };
7913
8960
 
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 };
8961
+ 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, isElmInDataInfo, 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, useHeatmapViewportByDevice, useHeatmapVizContext, useHeatmapVizRectContext, useHoveredElement, useMeasureFunction, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useRenderCount, useScrollmapZones, useTrackHookCall, useViewIdContext, useVizLiveRender, useWhyDidYouUpdate, useWrapperRefHeight, useZonePositions, withPerformanceTracking };