@gemx-dev/heatmap-react 3.5.52 → 3.5.54

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 (325) hide show
  1. package/dist/esm/components/Layout/HeatmapLayout.d.ts +2 -1
  2. package/dist/esm/components/Layout/HeatmapLayout.d.ts.map +1 -1
  3. package/dist/esm/components/Layout/HeatmapPreview.d.ts.map +1 -1
  4. package/dist/esm/components/Layout/TopBar/ContentTopBar.d.ts.map +1 -1
  5. package/dist/esm/components/Layout/VizByMode/ContentVizByMode.d.ts.map +1 -1
  6. package/dist/esm/components/VizAreaClick/AreaEditHighlight.d.ts.map +1 -1
  7. package/dist/esm/components/VizAreaClick/AreaOverlay.d.ts.map +1 -1
  8. package/dist/esm/components/VizAreaClick/PortalAreaRenderer.d.ts +1 -1
  9. package/dist/esm/components/VizAreaClick/PortalAreaRenderer.d.ts.map +1 -1
  10. package/dist/esm/components/VizAreaClick/VizAreaClick.d.ts +1 -7
  11. package/dist/esm/components/VizAreaClick/VizAreaClick.d.ts.map +1 -1
  12. package/dist/esm/components/VizAreaClick/index.d.ts +0 -1
  13. package/dist/esm/components/VizAreaClick/index.d.ts.map +1 -1
  14. package/dist/esm/components/VizClickmap/VizClickmap.d.ts +8 -0
  15. package/dist/esm/components/VizClickmap/VizClickmap.d.ts.map +1 -0
  16. package/dist/esm/components/VizClickmap/index.d.ts +2 -0
  17. package/dist/esm/components/VizClickmap/index.d.ts.map +1 -0
  18. package/dist/esm/components/VizDom/VizDomHeatmap.d.ts.map +1 -1
  19. package/dist/esm/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  20. package/dist/esm/components/VizElement/DefaultRankBadges.d.ts.map +1 -1
  21. package/dist/esm/components/VizElement/ElementOverlay.d.ts +0 -1
  22. package/dist/esm/components/VizElement/ElementOverlay.d.ts.map +1 -1
  23. package/dist/esm/components/VizElement/HeatmapElements.d.ts.map +1 -1
  24. package/dist/esm/components/VizElement/RankBadge.d.ts.map +1 -1
  25. package/dist/esm/components/VizScrollmap/HoverZones.d.ts.map +1 -1
  26. package/dist/esm/components/VizScrollmap/ScrollMapOverlay.d.ts.map +1 -1
  27. package/dist/esm/components/VizScrollmapV2/ScrollmapOverlayV2.d.ts.map +1 -1
  28. package/dist/esm/components/VizScrollmapV2/useScrollmapOverlay.d.ts.map +1 -1
  29. package/dist/esm/components/index.d.ts +1 -0
  30. package/dist/esm/components/index.d.ts.map +1 -1
  31. package/dist/esm/constants/index.d.ts +1 -0
  32. package/dist/esm/constants/index.d.ts.map +1 -1
  33. package/dist/esm/constants/selectors.d.ts +4 -0
  34. package/dist/esm/constants/selectors.d.ts.map +1 -0
  35. package/dist/esm/constants/viz-area-click.d.ts +10 -0
  36. package/dist/esm/constants/viz-area-click.d.ts.map +1 -1
  37. package/dist/esm/helpers/dom-utils.d.ts +29 -0
  38. package/dist/esm/helpers/dom-utils.d.ts.map +1 -0
  39. package/dist/esm/helpers/iframe-helper/fixer.d.ts.map +1 -1
  40. package/dist/esm/helpers/iframe-helper/navigation-blocker-v2.d.ts +4 -1
  41. package/dist/esm/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -1
  42. package/dist/esm/helpers/iframe-helper/navigation-blocker.d.ts.map +1 -1
  43. package/dist/esm/helpers/iframe-helper/style-replacer.d.ts.map +1 -1
  44. package/dist/esm/helpers/index.d.ts +2 -0
  45. package/dist/esm/helpers/index.d.ts.map +1 -1
  46. package/dist/esm/helpers/logger.d.ts +77 -0
  47. package/dist/esm/helpers/logger.d.ts.map +1 -0
  48. package/dist/esm/helpers/viz-area-click/area-builder.d.ts +2 -2
  49. package/dist/esm/helpers/viz-area-click/area-builder.d.ts.map +1 -1
  50. package/dist/esm/helpers/viz-area-click/area-graph.d.ts +63 -0
  51. package/dist/esm/helpers/viz-area-click/area-graph.d.ts.map +1 -0
  52. package/dist/esm/helpers/viz-area-click/area-hydration.d.ts +36 -0
  53. package/dist/esm/helpers/viz-area-click/area-hydration.d.ts.map +1 -0
  54. package/dist/esm/helpers/viz-area-click/area-overlap.d.ts.map +1 -1
  55. package/dist/esm/helpers/viz-area-click/area-renderer-setup.d.ts +20 -0
  56. package/dist/esm/helpers/viz-area-click/area-renderer-setup.d.ts.map +1 -0
  57. package/dist/esm/helpers/viz-area-click/area-utils.d.ts +0 -41
  58. package/dist/esm/helpers/viz-area-click/area-utils.d.ts.map +1 -1
  59. package/dist/esm/helpers/viz-area-click/index.d.ts +3 -0
  60. package/dist/esm/helpers/viz-area-click/index.d.ts.map +1 -1
  61. package/dist/esm/helpers/viz-canvas/area-clustering.d.ts.map +1 -1
  62. package/dist/esm/helpers/viz-canvas/area-overlay-manager-v2.d.ts.map +1 -1
  63. package/dist/esm/helpers/viz-canvas/hierarchical-area-clustering.d.ts.map +1 -1
  64. package/dist/esm/helpers/viz-dom/find-elm.d.ts +8 -0
  65. package/dist/esm/helpers/viz-dom/find-elm.d.ts.map +1 -0
  66. package/dist/esm/helpers/viz-dom/index.d.ts +1 -0
  67. package/dist/esm/helpers/viz-dom/index.d.ts.map +1 -1
  68. package/dist/esm/helpers/viz-elm-callout/dimensions.d.ts.map +1 -1
  69. package/dist/esm/helpers/viz-elm-callout/getter.d.ts +0 -2
  70. package/dist/esm/helpers/viz-elm-callout/getter.d.ts.map +1 -1
  71. package/dist/esm/helpers/viz-elm-callout/position-candidates.d.ts.map +1 -1
  72. package/dist/esm/helpers/viz-elm-callout/position-selector.d.ts.map +1 -1
  73. package/dist/esm/helpers/viz-elm-callout/position-validator.d.ts.map +1 -1
  74. package/dist/esm/hooks/register/useRegisterConfig.d.ts.map +1 -1
  75. package/dist/esm/hooks/register/useRegisterHeatmap.d.ts +3 -2
  76. package/dist/esm/hooks/register/useRegisterHeatmap.d.ts.map +1 -1
  77. package/dist/esm/hooks/view-context/useHeatmapAreaClick.d.ts +1 -0
  78. package/dist/esm/hooks/view-context/useHeatmapAreaClick.d.ts.map +1 -1
  79. package/dist/esm/hooks/view-context/useHeatmapClick.d.ts.map +1 -1
  80. package/dist/esm/hooks/view-context/useHeatmapData.d.ts +4 -1
  81. package/dist/esm/hooks/view-context/useHeatmapData.d.ts.map +1 -1
  82. package/dist/esm/hooks/viz-area-click/index.d.ts +8 -0
  83. package/dist/esm/hooks/viz-area-click/index.d.ts.map +1 -1
  84. package/dist/esm/hooks/viz-area-click/useAreaCreation.d.ts +10 -0
  85. package/dist/esm/hooks/viz-area-click/useAreaCreation.d.ts.map +1 -0
  86. package/dist/esm/hooks/viz-area-click/useAreaEditMode.d.ts +2 -2
  87. package/dist/esm/hooks/viz-area-click/useAreaEditMode.d.ts.map +1 -1
  88. package/dist/esm/hooks/viz-area-click/useAreaFilterVisible.d.ts +7 -0
  89. package/dist/esm/hooks/viz-area-click/useAreaFilterVisible.d.ts.map +1 -0
  90. package/dist/esm/hooks/viz-area-click/useAreaHydration.d.ts +9 -0
  91. package/dist/esm/hooks/viz-area-click/useAreaHydration.d.ts.map +1 -0
  92. package/dist/esm/hooks/viz-area-click/useAreaInteraction.d.ts +11 -0
  93. package/dist/esm/hooks/viz-area-click/useAreaInteraction.d.ts.map +1 -0
  94. package/dist/esm/hooks/viz-area-click/useAreaPortals.d.ts +15 -0
  95. package/dist/esm/hooks/viz-area-click/useAreaPortals.d.ts.map +1 -0
  96. package/dist/esm/hooks/viz-area-click/useAreaRectSync.d.ts +8 -0
  97. package/dist/esm/hooks/viz-area-click/useAreaRectSync.d.ts.map +1 -0
  98. package/dist/esm/hooks/viz-area-click/useAreaRendererContainer.d.ts +13 -0
  99. package/dist/esm/hooks/viz-area-click/useAreaRendererContainer.d.ts.map +1 -0
  100. package/dist/esm/hooks/viz-area-click/useAreaScrollSync.d.ts +1 -2
  101. package/dist/esm/hooks/viz-area-click/useAreaScrollSync.d.ts.map +1 -1
  102. package/dist/esm/hooks/viz-area-click/useAreaTopAutoDetect.d.ts +8 -0
  103. package/dist/esm/hooks/viz-area-click/useAreaTopAutoDetect.d.ts.map +1 -0
  104. package/dist/esm/hooks/viz-canvas/useAreaClickmap.d.ts +4 -0
  105. package/dist/esm/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -0
  106. package/dist/esm/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
  107. package/dist/esm/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
  108. package/dist/esm/hooks/viz-elm/useClickedElement.d.ts +1 -0
  109. package/dist/esm/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
  110. package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -1
  111. package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
  112. package/dist/esm/hooks/viz-elm/useHeatmapMouseHandler.d.ts +0 -25
  113. package/dist/esm/hooks/viz-elm/useHeatmapMouseHandler.d.ts.map +1 -1
  114. package/dist/esm/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
  115. package/dist/esm/hooks/viz-render/index.d.ts +1 -1
  116. package/dist/esm/hooks/viz-render/index.d.ts.map +1 -1
  117. package/dist/esm/hooks/viz-render/useHeatmapRenderByMode.d.ts +6 -0
  118. package/dist/esm/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -0
  119. package/dist/esm/hooks/viz-scale/useContainerDimensions.d.ts.map +1 -1
  120. package/dist/esm/hooks/viz-scale/useContentDimensions.d.ts +1 -1
  121. package/dist/esm/hooks/viz-scale/useContentDimensions.d.ts.map +1 -1
  122. package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts +0 -3
  123. package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
  124. package/dist/esm/hooks/viz-scale/useObserveIframeHeight.d.ts +0 -1
  125. package/dist/esm/hooks/viz-scale/useObserveIframeHeight.d.ts.map +1 -1
  126. package/dist/esm/hooks/viz-scale/useScaleCalculation.d.ts.map +1 -1
  127. package/dist/esm/hooks/viz-scale/useScrollSync.d.ts +1 -1
  128. package/dist/esm/hooks/viz-scale/useScrollSync.d.ts.map +1 -1
  129. package/dist/esm/hooks/viz-scale/useWrapperRefHeight.d.ts.map +1 -1
  130. package/dist/esm/hooks/viz-scroll/useScrollmapZones.d.ts.map +1 -1
  131. package/dist/esm/index.d.ts +1 -0
  132. package/dist/esm/index.d.ts.map +1 -1
  133. package/dist/esm/index.js +1324 -766
  134. package/dist/esm/index.mjs +1324 -766
  135. package/dist/esm/performance/hooks.d.ts.map +1 -1
  136. package/dist/esm/performance/performance-logger.d.ts.map +1 -1
  137. package/dist/esm/performance/types.d.ts.map +1 -1
  138. package/dist/esm/performance/utils.d.ts.map +1 -1
  139. package/dist/esm/stores/comp.d.ts.map +1 -1
  140. package/dist/esm/stores/config.d.ts +3 -1
  141. package/dist/esm/stores/config.d.ts.map +1 -1
  142. package/dist/esm/stores/data.d.ts +4 -1
  143. package/dist/esm/stores/data.d.ts.map +1 -1
  144. package/dist/esm/stores/mode-compare.d.ts.map +1 -1
  145. package/dist/esm/stores/viz-area-click.d.ts +1 -0
  146. package/dist/esm/stores/viz-area-click.d.ts.map +1 -1
  147. package/dist/esm/types/heatmap.d.ts +4 -0
  148. package/dist/esm/types/heatmap.d.ts.map +1 -1
  149. package/dist/esm/types/iframe-helper.d.ts +1 -0
  150. package/dist/esm/types/iframe-helper.d.ts.map +1 -1
  151. package/dist/esm/types/viz-area-click.d.ts +14 -0
  152. package/dist/esm/types/viz-area-click.d.ts.map +1 -1
  153. package/dist/esm/ui/BoxStack/BoxStack.d.ts.map +1 -1
  154. package/dist/esm/utils/retry.d.ts.map +1 -1
  155. package/dist/style.css +9 -11
  156. package/dist/umd/components/Layout/HeatmapLayout.d.ts +2 -1
  157. package/dist/umd/components/Layout/HeatmapLayout.d.ts.map +1 -1
  158. package/dist/umd/components/Layout/HeatmapPreview.d.ts.map +1 -1
  159. package/dist/umd/components/Layout/TopBar/ContentTopBar.d.ts.map +1 -1
  160. package/dist/umd/components/Layout/VizByMode/ContentVizByMode.d.ts.map +1 -1
  161. package/dist/umd/components/VizAreaClick/AreaEditHighlight.d.ts.map +1 -1
  162. package/dist/umd/components/VizAreaClick/AreaOverlay.d.ts.map +1 -1
  163. package/dist/umd/components/VizAreaClick/PortalAreaRenderer.d.ts +1 -1
  164. package/dist/umd/components/VizAreaClick/PortalAreaRenderer.d.ts.map +1 -1
  165. package/dist/umd/components/VizAreaClick/VizAreaClick.d.ts +1 -7
  166. package/dist/umd/components/VizAreaClick/VizAreaClick.d.ts.map +1 -1
  167. package/dist/umd/components/VizAreaClick/index.d.ts +0 -1
  168. package/dist/umd/components/VizAreaClick/index.d.ts.map +1 -1
  169. package/dist/umd/components/VizClickmap/VizClickmap.d.ts +8 -0
  170. package/dist/umd/components/VizClickmap/VizClickmap.d.ts.map +1 -0
  171. package/dist/umd/components/VizClickmap/index.d.ts +2 -0
  172. package/dist/umd/components/VizClickmap/index.d.ts.map +1 -0
  173. package/dist/umd/components/VizDom/VizDomHeatmap.d.ts.map +1 -1
  174. package/dist/umd/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  175. package/dist/umd/components/VizElement/DefaultRankBadges.d.ts.map +1 -1
  176. package/dist/umd/components/VizElement/ElementOverlay.d.ts +0 -1
  177. package/dist/umd/components/VizElement/ElementOverlay.d.ts.map +1 -1
  178. package/dist/umd/components/VizElement/HeatmapElements.d.ts.map +1 -1
  179. package/dist/umd/components/VizElement/RankBadge.d.ts.map +1 -1
  180. package/dist/umd/components/VizScrollmap/HoverZones.d.ts.map +1 -1
  181. package/dist/umd/components/VizScrollmap/ScrollMapOverlay.d.ts.map +1 -1
  182. package/dist/umd/components/VizScrollmapV2/ScrollmapOverlayV2.d.ts.map +1 -1
  183. package/dist/umd/components/VizScrollmapV2/useScrollmapOverlay.d.ts.map +1 -1
  184. package/dist/umd/components/index.d.ts +1 -0
  185. package/dist/umd/components/index.d.ts.map +1 -1
  186. package/dist/umd/constants/index.d.ts +1 -0
  187. package/dist/umd/constants/index.d.ts.map +1 -1
  188. package/dist/umd/constants/selectors.d.ts +4 -0
  189. package/dist/umd/constants/selectors.d.ts.map +1 -0
  190. package/dist/umd/constants/viz-area-click.d.ts +10 -0
  191. package/dist/umd/constants/viz-area-click.d.ts.map +1 -1
  192. package/dist/umd/helpers/dom-utils.d.ts +29 -0
  193. package/dist/umd/helpers/dom-utils.d.ts.map +1 -0
  194. package/dist/umd/helpers/iframe-helper/fixer.d.ts.map +1 -1
  195. package/dist/umd/helpers/iframe-helper/navigation-blocker-v2.d.ts +4 -1
  196. package/dist/umd/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -1
  197. package/dist/umd/helpers/iframe-helper/navigation-blocker.d.ts.map +1 -1
  198. package/dist/umd/helpers/iframe-helper/style-replacer.d.ts.map +1 -1
  199. package/dist/umd/helpers/index.d.ts +2 -0
  200. package/dist/umd/helpers/index.d.ts.map +1 -1
  201. package/dist/umd/helpers/logger.d.ts +77 -0
  202. package/dist/umd/helpers/logger.d.ts.map +1 -0
  203. package/dist/umd/helpers/viz-area-click/area-builder.d.ts +2 -2
  204. package/dist/umd/helpers/viz-area-click/area-builder.d.ts.map +1 -1
  205. package/dist/umd/helpers/viz-area-click/area-graph.d.ts +63 -0
  206. package/dist/umd/helpers/viz-area-click/area-graph.d.ts.map +1 -0
  207. package/dist/umd/helpers/viz-area-click/area-hydration.d.ts +36 -0
  208. package/dist/umd/helpers/viz-area-click/area-hydration.d.ts.map +1 -0
  209. package/dist/umd/helpers/viz-area-click/area-overlap.d.ts.map +1 -1
  210. package/dist/umd/helpers/viz-area-click/area-renderer-setup.d.ts +20 -0
  211. package/dist/umd/helpers/viz-area-click/area-renderer-setup.d.ts.map +1 -0
  212. package/dist/umd/helpers/viz-area-click/area-utils.d.ts +0 -41
  213. package/dist/umd/helpers/viz-area-click/area-utils.d.ts.map +1 -1
  214. package/dist/umd/helpers/viz-area-click/index.d.ts +3 -0
  215. package/dist/umd/helpers/viz-area-click/index.d.ts.map +1 -1
  216. package/dist/umd/helpers/viz-canvas/area-clustering.d.ts.map +1 -1
  217. package/dist/umd/helpers/viz-canvas/area-overlay-manager-v2.d.ts.map +1 -1
  218. package/dist/umd/helpers/viz-canvas/hierarchical-area-clustering.d.ts.map +1 -1
  219. package/dist/umd/helpers/viz-dom/find-elm.d.ts +8 -0
  220. package/dist/umd/helpers/viz-dom/find-elm.d.ts.map +1 -0
  221. package/dist/umd/helpers/viz-dom/index.d.ts +1 -0
  222. package/dist/umd/helpers/viz-dom/index.d.ts.map +1 -1
  223. package/dist/umd/helpers/viz-elm-callout/dimensions.d.ts.map +1 -1
  224. package/dist/umd/helpers/viz-elm-callout/getter.d.ts +0 -2
  225. package/dist/umd/helpers/viz-elm-callout/getter.d.ts.map +1 -1
  226. package/dist/umd/helpers/viz-elm-callout/position-candidates.d.ts.map +1 -1
  227. package/dist/umd/helpers/viz-elm-callout/position-selector.d.ts.map +1 -1
  228. package/dist/umd/helpers/viz-elm-callout/position-validator.d.ts.map +1 -1
  229. package/dist/umd/hooks/register/useRegisterConfig.d.ts.map +1 -1
  230. package/dist/umd/hooks/register/useRegisterHeatmap.d.ts +3 -2
  231. package/dist/umd/hooks/register/useRegisterHeatmap.d.ts.map +1 -1
  232. package/dist/umd/hooks/view-context/useHeatmapAreaClick.d.ts +1 -0
  233. package/dist/umd/hooks/view-context/useHeatmapAreaClick.d.ts.map +1 -1
  234. package/dist/umd/hooks/view-context/useHeatmapClick.d.ts.map +1 -1
  235. package/dist/umd/hooks/view-context/useHeatmapData.d.ts +4 -1
  236. package/dist/umd/hooks/view-context/useHeatmapData.d.ts.map +1 -1
  237. package/dist/umd/hooks/viz-area-click/index.d.ts +8 -0
  238. package/dist/umd/hooks/viz-area-click/index.d.ts.map +1 -1
  239. package/dist/umd/hooks/viz-area-click/useAreaCreation.d.ts +10 -0
  240. package/dist/umd/hooks/viz-area-click/useAreaCreation.d.ts.map +1 -0
  241. package/dist/umd/hooks/viz-area-click/useAreaEditMode.d.ts +2 -2
  242. package/dist/umd/hooks/viz-area-click/useAreaEditMode.d.ts.map +1 -1
  243. package/dist/umd/hooks/viz-area-click/useAreaFilterVisible.d.ts +7 -0
  244. package/dist/umd/hooks/viz-area-click/useAreaFilterVisible.d.ts.map +1 -0
  245. package/dist/umd/hooks/viz-area-click/useAreaHydration.d.ts +9 -0
  246. package/dist/umd/hooks/viz-area-click/useAreaHydration.d.ts.map +1 -0
  247. package/dist/umd/hooks/viz-area-click/useAreaInteraction.d.ts +11 -0
  248. package/dist/umd/hooks/viz-area-click/useAreaInteraction.d.ts.map +1 -0
  249. package/dist/umd/hooks/viz-area-click/useAreaPortals.d.ts +15 -0
  250. package/dist/umd/hooks/viz-area-click/useAreaPortals.d.ts.map +1 -0
  251. package/dist/umd/hooks/viz-area-click/useAreaRectSync.d.ts +8 -0
  252. package/dist/umd/hooks/viz-area-click/useAreaRectSync.d.ts.map +1 -0
  253. package/dist/umd/hooks/viz-area-click/useAreaRendererContainer.d.ts +13 -0
  254. package/dist/umd/hooks/viz-area-click/useAreaRendererContainer.d.ts.map +1 -0
  255. package/dist/umd/hooks/viz-area-click/useAreaScrollSync.d.ts +1 -2
  256. package/dist/umd/hooks/viz-area-click/useAreaScrollSync.d.ts.map +1 -1
  257. package/dist/umd/hooks/viz-area-click/useAreaTopAutoDetect.d.ts +8 -0
  258. package/dist/umd/hooks/viz-area-click/useAreaTopAutoDetect.d.ts.map +1 -0
  259. package/dist/umd/hooks/viz-canvas/useAreaClickmap.d.ts +4 -0
  260. package/dist/umd/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -0
  261. package/dist/umd/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
  262. package/dist/umd/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
  263. package/dist/umd/hooks/viz-elm/useClickedElement.d.ts +1 -0
  264. package/dist/umd/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
  265. package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -1
  266. package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
  267. package/dist/umd/hooks/viz-elm/useHeatmapMouseHandler.d.ts +0 -25
  268. package/dist/umd/hooks/viz-elm/useHeatmapMouseHandler.d.ts.map +1 -1
  269. package/dist/umd/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
  270. package/dist/umd/hooks/viz-render/index.d.ts +1 -1
  271. package/dist/umd/hooks/viz-render/index.d.ts.map +1 -1
  272. package/dist/umd/hooks/viz-render/useHeatmapRenderByMode.d.ts +6 -0
  273. package/dist/umd/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -0
  274. package/dist/umd/hooks/viz-scale/useContainerDimensions.d.ts.map +1 -1
  275. package/dist/umd/hooks/viz-scale/useContentDimensions.d.ts +1 -1
  276. package/dist/umd/hooks/viz-scale/useContentDimensions.d.ts.map +1 -1
  277. package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts +0 -3
  278. package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
  279. package/dist/umd/hooks/viz-scale/useObserveIframeHeight.d.ts +0 -1
  280. package/dist/umd/hooks/viz-scale/useObserveIframeHeight.d.ts.map +1 -1
  281. package/dist/umd/hooks/viz-scale/useScaleCalculation.d.ts.map +1 -1
  282. package/dist/umd/hooks/viz-scale/useScrollSync.d.ts +1 -1
  283. package/dist/umd/hooks/viz-scale/useScrollSync.d.ts.map +1 -1
  284. package/dist/umd/hooks/viz-scale/useWrapperRefHeight.d.ts.map +1 -1
  285. package/dist/umd/hooks/viz-scroll/useScrollmapZones.d.ts.map +1 -1
  286. package/dist/umd/index.d.ts +1 -0
  287. package/dist/umd/index.d.ts.map +1 -1
  288. package/dist/umd/index.js +2 -2
  289. package/dist/umd/performance/hooks.d.ts.map +1 -1
  290. package/dist/umd/performance/performance-logger.d.ts.map +1 -1
  291. package/dist/umd/performance/types.d.ts.map +1 -1
  292. package/dist/umd/performance/utils.d.ts.map +1 -1
  293. package/dist/umd/stores/comp.d.ts.map +1 -1
  294. package/dist/umd/stores/config.d.ts +3 -1
  295. package/dist/umd/stores/config.d.ts.map +1 -1
  296. package/dist/umd/stores/data.d.ts +4 -1
  297. package/dist/umd/stores/data.d.ts.map +1 -1
  298. package/dist/umd/stores/mode-compare.d.ts.map +1 -1
  299. package/dist/umd/stores/viz-area-click.d.ts +1 -0
  300. package/dist/umd/stores/viz-area-click.d.ts.map +1 -1
  301. package/dist/umd/types/heatmap.d.ts +4 -0
  302. package/dist/umd/types/heatmap.d.ts.map +1 -1
  303. package/dist/umd/types/iframe-helper.d.ts +1 -0
  304. package/dist/umd/types/iframe-helper.d.ts.map +1 -1
  305. package/dist/umd/types/viz-area-click.d.ts +14 -0
  306. package/dist/umd/types/viz-area-click.d.ts.map +1 -1
  307. package/dist/umd/ui/BoxStack/BoxStack.d.ts.map +1 -1
  308. package/dist/umd/utils/retry.d.ts.map +1 -1
  309. package/package.json +1 -1
  310. package/dist/esm/components/VizAreaClick/AreaControls.d.ts +0 -10
  311. package/dist/esm/components/VizAreaClick/AreaControls.d.ts.map +0 -1
  312. package/dist/esm/hooks/viz-area/useAreaHeatmap.d.ts +0 -59
  313. package/dist/esm/hooks/viz-area/useAreaHeatmap.d.ts.map +0 -1
  314. package/dist/esm/hooks/viz-area/useAreaHeatmapManager.d.ts +0 -77
  315. package/dist/esm/hooks/viz-area/useAreaHeatmapManager.d.ts.map +0 -1
  316. package/dist/esm/hooks/viz-render/useHeatmapVizRender.d.ts +0 -6
  317. package/dist/esm/hooks/viz-render/useHeatmapVizRender.d.ts.map +0 -1
  318. package/dist/umd/components/VizAreaClick/AreaControls.d.ts +0 -10
  319. package/dist/umd/components/VizAreaClick/AreaControls.d.ts.map +0 -1
  320. package/dist/umd/hooks/viz-area/useAreaHeatmap.d.ts +0 -59
  321. package/dist/umd/hooks/viz-area/useAreaHeatmap.d.ts.map +0 -1
  322. package/dist/umd/hooks/viz-area/useAreaHeatmapManager.d.ts +0 -77
  323. package/dist/umd/hooks/viz-area/useAreaHeatmapManager.d.ts.map +0 -1
  324. package/dist/umd/hooks/viz-render/useHeatmapVizRender.d.ts +0 -6
  325. package/dist/umd/hooks/viz-render/useHeatmapVizRender.d.ts.map +0 -1
package/dist/esm/index.js CHANGED
@@ -5,8 +5,8 @@ import { useEffect, useRef, useCallback, createContext, useContext, useMemo, use
5
5
  import { create } from 'zustand';
6
6
  import { subscribeWithSelector } from 'zustand/middleware';
7
7
  import { decode } from '@gemx-dev/clarity-decode';
8
- import { Visualizer } from '@gemx-dev/clarity-visualize';
9
8
  import { createPortal } from 'react-dom';
9
+ import { Visualizer } from '@gemx-dev/clarity-visualize';
10
10
 
11
11
  const initialNodes = { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } };
12
12
  const GraphView = ({ children, width, height }) => {
@@ -148,6 +148,11 @@ var IClickType;
148
148
  IClickType["First"] = "first-clicks";
149
149
  IClickType["Last"] = "last-clicks";
150
150
  })(IClickType || (IClickType = {}));
151
+ var IClickMode;
152
+ (function (IClickMode) {
153
+ IClickMode["Default"] = "default";
154
+ IClickMode["Area"] = "click-area";
155
+ })(IClickMode || (IClickMode = {}));
151
156
  var IScrollType;
152
157
  (function (IScrollType) {
153
158
  IScrollType["Depth"] = "scroll-depth";
@@ -162,6 +167,7 @@ const useHeatmapConfigStore = create()((set) => {
162
167
  sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
163
168
  heatmapType: IHeatmapType.Click,
164
169
  clickType: IClickType.Total,
170
+ clickMode: IClickMode.Default,
165
171
  scrollType: IScrollType.Depth,
166
172
  isRendering: true,
167
173
  setMode: (mode) => set({ mode }),
@@ -170,6 +176,7 @@ const useHeatmapConfigStore = create()((set) => {
170
176
  setSidebarWidth: (sidebarWidth) => set({ sidebarWidth }),
171
177
  setHeatmapType: (heatmapType) => set({ heatmapType }),
172
178
  setClickType: (clickType) => set({ clickType }),
179
+ setClickMode: (clickMode) => set({ clickMode }),
173
180
  setScrollType: (scrollType) => set({ scrollType }),
174
181
  setIsRendering: (isRendering) => set({ isRendering }),
175
182
  };
@@ -179,8 +186,20 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
179
186
  return {
180
187
  data: { [DEFAULT_VIEW_ID]: undefined },
181
188
  clickmap: { [DEFAULT_VIEW_ID]: undefined },
189
+ clickAreas: { [DEFAULT_VIEW_ID]: undefined },
182
190
  dataInfo: { [DEFAULT_VIEW_ID]: undefined },
183
191
  scrollmap: { [DEFAULT_VIEW_ID]: undefined },
192
+ setClickAreas: (clickAreas, viewId = DEFAULT_VIEW_ID) => set((state) => ({
193
+ clickAreas: { ...state.clickAreas, [viewId]: clickAreas },
194
+ })),
195
+ removeClickArea: (areaId, viewId = DEFAULT_VIEW_ID) => set((state) => {
196
+ const currentAreas = state.clickAreas[viewId] || [];
197
+ const filtered = currentAreas.filter((a) => a.id !== areaId);
198
+ console.log(`🚀 🐥 ~ filtered:`, filtered);
199
+ return {
200
+ clickAreas: { ...state.clickAreas, [viewId]: filtered },
201
+ };
202
+ }),
184
203
  setDataInfo: (dataInfo, viewId = DEFAULT_VIEW_ID) => set((state) => ({
185
204
  dataInfo: { ...state.dataInfo, [viewId]: dataInfo },
186
205
  })),
@@ -196,21 +215,25 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
196
215
  copyView: (fromViewId, toViewId) => set((state) => ({
197
216
  data: { ...state.data, [toViewId]: state.data[fromViewId] },
198
217
  clickmap: { ...state.clickmap, [toViewId]: state.clickmap[fromViewId] },
218
+ clickAreas: { ...state.clickAreas, [toViewId]: state.clickAreas[fromViewId] },
199
219
  dataInfo: { ...state.dataInfo, [toViewId]: state.dataInfo[fromViewId] },
200
220
  scrollmap: { ...state.scrollmap, [toViewId]: state.scrollmap[fromViewId] },
201
221
  })),
202
222
  clearView: (viewId) => set((state) => {
203
223
  const newData = { ...state.data };
204
224
  const newClickmap = { ...state.clickmap };
225
+ const newClickAreas = { ...state.clickAreas };
205
226
  const newDataInfo = { ...state.dataInfo };
206
227
  const newScrollmap = { ...state.scrollmap };
207
228
  delete newData[viewId];
208
229
  delete newClickmap[viewId];
230
+ delete newClickAreas[viewId];
209
231
  delete newDataInfo[viewId];
210
232
  delete newScrollmap[viewId];
211
233
  return {
212
234
  data: newData,
213
235
  clickmap: newClickmap,
236
+ clickAreas: newClickAreas,
214
237
  dataInfo: newDataInfo,
215
238
  scrollmap: newScrollmap,
216
239
  };
@@ -218,6 +241,7 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
218
241
  resetAll: () => set({
219
242
  data: { [DEFAULT_VIEW_ID]: undefined },
220
243
  clickmap: { [DEFAULT_VIEW_ID]: undefined },
244
+ clickAreas: { [DEFAULT_VIEW_ID]: undefined },
221
245
  dataInfo: { [DEFAULT_VIEW_ID]: undefined },
222
246
  scrollmap: { [DEFAULT_VIEW_ID]: undefined },
223
247
  }),
@@ -335,7 +359,7 @@ const useHeatmapAreaClickStore = create()(subscribeWithSelector((set) => ({
335
359
  }),
336
360
  updateArea: (areaId, updates, viewId = DEFAULT_VIEW_ID) => set((state) => {
337
361
  const currentAreas = state.areas[viewId] || [];
338
- const updatedAreas = currentAreas.map((area) => area.id === areaId ? { ...area, ...updates } : area);
362
+ const updatedAreas = currentAreas.map((area) => (area.id === areaId ? { ...area, ...updates } : area));
339
363
  return {
340
364
  areas: { ...state.areas, [viewId]: updatedAreas },
341
365
  };
@@ -379,6 +403,11 @@ const useHeatmapAreaClickStore = create()(subscribeWithSelector((set) => ({
379
403
  isEditingMode: newIsEditingMode,
380
404
  };
381
405
  }),
406
+ resetView: (viewId) => set((state) => ({
407
+ selectedArea: { ...state.selectedArea, [viewId]: null },
408
+ hoveredArea: { ...state.hoveredArea, [viewId]: null },
409
+ isEditingMode: { ...state.isEditingMode, [viewId]: false },
410
+ })),
382
411
  resetAll: () => set({
383
412
  selectedArea: { [DEFAULT_VIEW_ID]: null },
384
413
  hoveredArea: { [DEFAULT_VIEW_ID]: null },
@@ -533,9 +562,13 @@ const useHeatmapCompareStore = create()((set, get) => {
533
562
  const state = get();
534
563
  const newViews = new Map(state.views);
535
564
  newViews.delete(viewId);
565
+ const newViewIdCounter = newViews.size;
566
+ const newLayout = newViews.size === 2 ? 'grid-2' : state.layout;
536
567
  set({
537
568
  views: newViews,
538
569
  viewOrder: state.viewOrder.filter((id) => id !== viewId),
570
+ viewIdCounter: newViewIdCounter,
571
+ layout: newLayout,
539
572
  });
540
573
  },
541
574
  updateView: (viewId, updates) => {
@@ -680,13 +713,14 @@ const useRegisterConfig = () => {
680
713
  const width = useHeatmapConfigStore((state) => state.width);
681
714
  const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
682
715
  const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
716
+ const clickMode = useHeatmapConfigStore((state) => state.clickMode);
683
717
  const setIsRendering = useHeatmapConfigStore((state) => state.setIsRendering);
684
718
  useEffect(() => {
685
719
  setIsRendering(true);
686
720
  setTimeout(() => {
687
721
  setIsRendering(false);
688
722
  }, 1000);
689
- }, [mode, width, sidebarWidth, heatmapType]);
723
+ }, [mode, width, sidebarWidth, heatmapType, clickMode]); // eslint-disable-line react-hooks/exhaustive-deps
690
724
  };
691
725
 
692
726
  const useRegisterControl = (control) => {
@@ -722,6 +756,7 @@ const useHeatmapAreaClick = (props) => {
722
756
  const removeAreaStore = useHeatmapAreaClickStore((state) => state.removeArea);
723
757
  const updateAreaStore = useHeatmapAreaClickStore((state) => state.updateArea);
724
758
  const clearAreasStore = useHeatmapAreaClickStore((state) => state.clearAreas);
759
+ const resetViewStore = useHeatmapAreaClickStore((state) => state.resetView);
725
760
  // Memoize operations to prevent unnecessary re-renders
726
761
  const memoizedOperations = useMemo(() => ({
727
762
  setSelectedArea: (area) => setSelectedAreaStore(area, viewId),
@@ -732,6 +767,7 @@ const useHeatmapAreaClick = (props) => {
732
767
  removeArea: (areaId) => removeAreaStore(areaId, viewId),
733
768
  updateArea: (areaId, updates) => updateAreaStore(areaId, updates, viewId),
734
769
  clearAreas: () => clearAreasStore(viewId),
770
+ resetView: () => resetViewStore(viewId),
735
771
  }), [
736
772
  setSelectedAreaStore,
737
773
  setHoveredAreaStore,
@@ -741,6 +777,7 @@ const useHeatmapAreaClick = (props) => {
741
777
  removeAreaStore,
742
778
  updateAreaStore,
743
779
  clearAreasStore,
780
+ resetViewStore,
744
781
  viewId,
745
782
  ]);
746
783
  return {
@@ -767,13 +804,7 @@ const useHeatmapClick = (props) => {
767
804
  setSelectedElement: (element) => setSelectedElementStore(element, viewId),
768
805
  setHoveredElement: (element) => setHoveredElementStore(element, viewId),
769
806
  setShouldShowCallout: (value) => setShouldShowCalloutStore(value, viewId),
770
- }), [
771
- setStateStore,
772
- setSelectedElementStore,
773
- setHoveredElementStore,
774
- setShouldShowCalloutStore,
775
- viewId,
776
- ]);
807
+ }), [setStateStore, setSelectedElementStore, setHoveredElementStore, setShouldShowCalloutStore, viewId]);
777
808
  return {
778
809
  state,
779
810
  selectedElement,
@@ -788,24 +819,29 @@ const useHeatmapData = (props) => {
788
819
  const viewId = props?.viewId || useViewIdContext();
789
820
  const data = useHeatmapDataStore((state) => state.data[viewId]);
790
821
  const clickmap = useHeatmapDataStore((state) => state.clickmap[viewId]);
822
+ const clickAreas = useHeatmapDataStore((state) => state.clickAreas[viewId]);
791
823
  const scrollmap = useHeatmapDataStore((state) => state.scrollmap[viewId]);
792
824
  const dataInfo = useHeatmapDataStore((state) => state.dataInfo[viewId]);
793
825
  const setData = useHeatmapDataStore((state) => state.setData);
794
826
  const setClickmap = useHeatmapDataStore((state) => state.setClickmap);
827
+ const setClickAreas = useHeatmapDataStore((state) => state.setClickAreas);
795
828
  const setScrollmap = useHeatmapDataStore((state) => state.setScrollmap);
796
829
  const setDataInfo = useHeatmapDataStore((state) => state.setDataInfo);
830
+ const removeClickArea = useHeatmapDataStore((state) => state.removeClickArea);
797
831
  const memoizedSetters = useMemo(() => ({
798
832
  setData: (newData) => setData(newData, viewId),
799
833
  setClickmap: (newClickmap) => setClickmap(newClickmap, viewId),
834
+ setClickAreas: (newClickAreas) => setClickAreas(newClickAreas, viewId),
800
835
  setScrollmap: (newScrollmap) => setScrollmap(newScrollmap, viewId),
801
836
  setDataInfo: (newDataInfo) => setDataInfo(newDataInfo, viewId),
802
- }), [setData, setClickmap, setScrollmap, setDataInfo, viewId]);
837
+ removeClickArea: (areaId) => removeClickArea(areaId, viewId),
838
+ }), [setData, setClickmap, setClickAreas, setScrollmap, setDataInfo, removeClickArea, viewId]);
803
839
  return {
804
840
  data,
805
841
  clickmap,
842
+ clickAreas,
806
843
  scrollmap,
807
844
  dataInfo,
808
- // Setters (auto-inject viewId)
809
845
  ...memoizedSetters,
810
846
  };
811
847
  };
@@ -978,24 +1014,32 @@ const useRegisterData = (data, dataInfo) => {
978
1014
  }, [dataInfo]);
979
1015
  };
980
1016
 
981
- const useRegisterHeatmap = ({ clickmap, scrollmap }) => {
982
- const { setClickmap, setScrollmap } = useHeatmapData();
983
- const handleSetClickmap = useCallback((clickmap) => {
1017
+ const useRegisterHeatmap = ({ clickmap, scrollmap, clickAreas }) => {
1018
+ const { setClickmap, setScrollmap, setClickAreas } = useHeatmapData();
1019
+ const handleSetClickmap = useCallback(() => {
984
1020
  if (!clickmap)
985
1021
  return;
986
1022
  setClickmap(clickmap);
987
- }, [clickmap]);
988
- const handleSetScrollmap = useCallback((scrollmap) => {
1023
+ }, [clickmap]); // eslint-disable-line react-hooks/exhaustive-deps
1024
+ const handleSetClickAreas = useCallback(() => {
1025
+ if (!clickAreas)
1026
+ return;
1027
+ setClickAreas(clickAreas);
1028
+ }, [clickAreas]); // eslint-disable-line react-hooks/exhaustive-deps
1029
+ const handleSetScrollmap = useCallback(() => {
989
1030
  if (!scrollmap)
990
1031
  return;
991
1032
  setScrollmap(scrollmap);
992
- }, [scrollmap]);
1033
+ }, [scrollmap]); // eslint-disable-line react-hooks/exhaustive-deps
993
1034
  useEffect(() => {
994
- handleSetClickmap(clickmap);
995
- }, [clickmap]);
1035
+ handleSetClickmap();
1036
+ }, [handleSetClickmap]);
996
1037
  useEffect(() => {
997
- handleSetScrollmap(scrollmap);
998
- }, [scrollmap]);
1038
+ handleSetClickAreas();
1039
+ }, [handleSetClickAreas]);
1040
+ useEffect(() => {
1041
+ handleSetScrollmap();
1042
+ }, [handleSetScrollmap]);
999
1043
  };
1000
1044
 
1001
1045
  /**
@@ -1046,9 +1090,83 @@ function isElementInViewport(elementRect, visualRef, scale) {
1046
1090
  return elementBottom > viewportTop && elementTop < viewportBottom;
1047
1091
  }
1048
1092
 
1093
+ const CLARITY_HEATMAP_CANVAS_ID = 'clarity-heatmap-canvas';
1094
+ const HEATMAP_ELEMENT_ATTRIBUTE = 'data-clarity-hashalpha';
1095
+ function isIgnoredCanvas(element) {
1096
+ if (element.tagName === 'CANVAS') {
1097
+ return true;
1098
+ }
1099
+ if (element.id === CLARITY_HEATMAP_CANVAS_ID) {
1100
+ return true;
1101
+ }
1102
+ return false;
1103
+ }
1104
+
1105
+ /**
1106
+ * Get all elements at a specific point (x, y), with support for Shadow DOM
1107
+ */
1108
+ function getElementsAtPoint(doc, x, y, options = {}) {
1109
+ const { filterFn, ignoreCanvas = true, visitedShadowRoots = new Set() } = options;
1110
+ // Get all elements at this point
1111
+ let elementsAtPoint = doc.elementsFromPoint(x, y);
1112
+ // Filter out canvas elements if requested
1113
+ if (ignoreCanvas) {
1114
+ elementsAtPoint = elementsAtPoint.filter((el) => !isIgnoredCanvas(el));
1115
+ }
1116
+ // Apply custom filter if provided
1117
+ if (filterFn) {
1118
+ const matchedElement = elementsAtPoint.find(filterFn);
1119
+ // If matched element has Shadow DOM and we haven't visited it yet, recurse
1120
+ if (matchedElement?.shadowRoot && !visitedShadowRoots.has(matchedElement.shadowRoot)) {
1121
+ visitedShadowRoots.add(matchedElement.shadowRoot);
1122
+ return getElementsAtPoint(matchedElement.shadowRoot, x, y, {
1123
+ ...options,
1124
+ visitedShadowRoots,
1125
+ });
1126
+ }
1127
+ }
1128
+ return elementsAtPoint;
1129
+ }
1130
+ /**
1131
+ * Get the element at a specific point (x, y)
1132
+ */
1133
+ const getElementAtPoint = (doc, x, y) => {
1134
+ let el = null;
1135
+ if ('caretPositionFromPoint' in doc) {
1136
+ el = doc.caretPositionFromPoint(x, y)?.offsetNode ?? null;
1137
+ }
1138
+ el = el ?? doc.elementFromPoint(x, y);
1139
+ let element = el;
1140
+ while (element && element.nodeType === Node.TEXT_NODE) {
1141
+ element = element.parentElement;
1142
+ }
1143
+ return element;
1144
+ };
1145
+ function getElementHash(element) {
1146
+ return (element.getAttribute('data-clarity-hash') ||
1147
+ element.getAttribute('data-clarity-hashalpha') ||
1148
+ element.getAttribute('data-clarity-hashbeta'));
1149
+ }
1150
+
1049
1151
  const AREA_HOVER_BOX_SHADOW = '0 0 0 1px #0078D4, 0 0 0 1px #0078D4 inset, 0 0 0 2px white inset';
1050
1152
  const AREA_HOVER_ELEMENT_ID = 'clarity-edit-hover';
1051
1153
  const AREA_MAP_DIV_ATTRIBUTE = 'data-clarity-area-map-div';
1154
+ const HEATMAP_AREA_CONTAINER_CLASS = 'heatmap-area-container';
1155
+ const HEATMAP_AREA_CONTAINER_SELECTOR = `.${HEATMAP_AREA_CONTAINER_CLASS}`;
1156
+ const AREA_CONTAINER_STYLES = `
1157
+ position: absolute;
1158
+ top: 0;
1159
+ left: 0;
1160
+ width: 100%;
1161
+ height: 100%;
1162
+ pointer-events: none;
1163
+ z-index: 999999;
1164
+ `;
1165
+ const AREA_INNER_CONTAINER_STYLES = `
1166
+ position: relative;
1167
+ width: 100%;
1168
+ height: 100%;
1169
+ `;
1052
1170
  const AREA_COLOR_GRADIENT = [
1053
1171
  [0, 0, 255], // Blue
1054
1172
  [0, 255, 255], // Cyan
@@ -1056,6 +1174,12 @@ const AREA_COLOR_GRADIENT = [
1056
1174
  [255, 255, 0], // Yellow
1057
1175
  [255, 0, 0], // Red
1058
1176
  ];
1177
+ const AREA_RENDERER_SELECTORS = {
1178
+ containerAttribute: AREA_MAP_DIV_ATTRIBUTE,
1179
+ containerSelector: `[${AREA_MAP_DIV_ATTRIBUTE}]`,
1180
+ innerContainerClass: HEATMAP_AREA_CONTAINER_CLASS,
1181
+ innerContainerSelector: HEATMAP_AREA_CONTAINER_SELECTOR,
1182
+ };
1059
1183
 
1060
1184
  const CALLOUT_PADDING = 0;
1061
1185
  const CALLOUT_ARROW_SIZE = 8;
@@ -1100,27 +1224,15 @@ function calculateClickDistribution(elementClicks, totalClicks) {
1100
1224
  return (elementClicks / totalClicks) * 100;
1101
1225
  }
1102
1226
 
1103
- /**
1104
- * Get element position and dimensions for rendering inside iframe
1105
- *
1106
- * This function calculates position relative to the iframe's document,
1107
- * accounting for scroll position. Suitable for elements rendered in
1108
- * shadow DOM inside the iframe with absolute positioning.
1109
- */
1110
1227
  function getElementRect(element, _shadowRoot) {
1111
1228
  const rect = element.getBoundingClientRect();
1112
1229
  const width = rect.width;
1113
1230
  const height = rect.height;
1114
- // Get the document to access scroll position
1115
1231
  const doc = element.ownerDocument || document;
1116
- // Get scroll offset from documentElement or body
1117
1232
  const scrollTop = doc.documentElement?.scrollTop || doc.body?.scrollTop || 0;
1118
1233
  const scrollLeft = doc.documentElement?.scrollLeft || doc.body?.scrollLeft || 0;
1119
- // Calculate position relative to document (not viewport)
1120
- // getBoundingClientRect() is relative to viewport, so add scroll offset
1121
1234
  const top = rect.top + scrollTop;
1122
1235
  const left = rect.left + scrollLeft;
1123
- // For absolute positioning calculations (overlap detection)
1124
1236
  const absoluteLeft = left;
1125
1237
  const absoluteTop = top;
1126
1238
  const absoluteRight = absoluteLeft + width;
@@ -1137,9 +1249,6 @@ function getElementRect(element, _shadowRoot) {
1137
1249
  outOfBounds: false,
1138
1250
  };
1139
1251
  }
1140
- /**
1141
- * Check if element has CSS position: fixed
1142
- */
1143
1252
  function isElementFixed(element) {
1144
1253
  if (getComputedStyle(element).position === 'fixed') {
1145
1254
  return true;
@@ -1150,9 +1259,6 @@ function isElementFixed(element) {
1150
1259
  const parent = element.parentElement;
1151
1260
  return parent ? isElementFixed(parent) : false;
1152
1261
  }
1153
- /**
1154
- * Check if two areas overlap
1155
- */
1156
1262
  function doAreasOverlap(area1, area2) {
1157
1263
  const r1 = area1.rect.value;
1158
1264
  const r2 = area2.rect.value;
@@ -1167,9 +1273,6 @@ function doAreasOverlap(area1, area2) {
1167
1273
  r2.absoluteRight > r1.absoluteLeft &&
1168
1274
  r2.absoluteLeft < r1.absoluteRight));
1169
1275
  }
1170
- /**
1171
- * Check if area1 is completely inside area2
1172
- */
1173
1276
  function isAreaContainedIn(area1, area2) {
1174
1277
  const r1 = area1.rect.value;
1175
1278
  const r2 = area2.rect.value;
@@ -1180,64 +1283,29 @@ function isAreaContainedIn(area1, area2) {
1180
1283
  r1.absoluteLeft >= r2.absoluteLeft &&
1181
1284
  r1.absoluteRight <= r2.absoluteRight);
1182
1285
  }
1183
- /**
1184
- * Check if element contains another element in DOM tree
1185
- */
1186
1286
  function isElementAncestorOf(ancestor, descendant, doc) {
1187
1287
  return ancestor.contains(descendant);
1188
1288
  }
1189
- /**
1190
- * Get all elements at a specific point, including shadow DOM
1191
- */
1192
- function getElementsAtPoint$1(doc, x, y, filter, visited = new Set()) {
1193
- const elements = doc.elementsFromPoint(x, y);
1194
- const filtered = elements.find(filter);
1195
- // Check shadow DOM
1196
- if (filtered?.shadowRoot && !visited.has(filtered.shadowRoot)) {
1197
- visited.add(filtered.shadowRoot);
1198
- return getElementsAtPoint$1(filtered.shadowRoot, x, y, filter, visited);
1199
- }
1200
- return elements;
1201
- }
1202
- /**
1203
- * Check if element should be selectable for area creation
1204
- */
1205
1289
  function isElementSelectable(element, index, elements) {
1206
- // Skip if it's an area map div
1290
+ if (isIgnoredCanvas(element)) {
1291
+ return false;
1292
+ }
1207
1293
  if (element.hasAttribute(AREA_MAP_DIV_ATTRIBUTE)) {
1208
1294
  return false;
1209
1295
  }
1210
- // Skip first element if it's BODY and there are other elements
1211
1296
  if (index === 0 && elements.length > 1 && element.nodeName === 'BODY') {
1212
1297
  return false;
1213
1298
  }
1214
1299
  return true;
1215
1300
  }
1216
- /**
1217
- * Sort areas by click distribution (descending)
1218
- */
1219
1301
  function sortAreasByClickDist(areas) {
1220
1302
  return [...areas].sort((a, b) => {
1221
- // Higher clickDist first
1222
1303
  if (a.clickDist !== b.clickDist) {
1223
1304
  return b.clickDist - a.clickDist;
1224
1305
  }
1225
- // Then by total clicks
1226
1306
  return b.totalclicks - a.totalclicks;
1227
1307
  });
1228
1308
  }
1229
- /**
1230
- * Create shadow root or return existing one
1231
- */
1232
- function getOrCreateShadowRoot(element) {
1233
- if (element.shadowRoot) {
1234
- return element.shadowRoot;
1235
- }
1236
- return element.attachShadow({ mode: 'open' });
1237
- }
1238
- /**
1239
- * Check if rect is too small to show label
1240
- */
1241
1309
  function isRectTooSmallForLabel(rect) {
1242
1310
  return rect.width < 67 || rect.height < 30;
1243
1311
  }
@@ -1254,18 +1322,46 @@ function getElementSelector(element) {
1254
1322
  }
1255
1323
  return element.tagName.toLowerCase();
1256
1324
  }
1257
- function buildAreaNode(element, hash, elementMapInfo, totalClicks, shadowRoot) {
1258
- const elementInfo = elementMapInfo[hash];
1259
- const elementClicks = elementInfo?.totalclicks || 0;
1325
+ /**
1326
+ * Calculate total clicks for an element including all its child elements
1327
+ * @param element - The parent element
1328
+ * @param elementMapInfo - Map of hash to element click info
1329
+ * @returns Total clicks for element + all descendants
1330
+ */
1331
+ function calculateTotalClicksWithChildren(element, elementMapInfo) {
1332
+ let totalClicks = 0;
1333
+ // Get clicks for the element itself
1334
+ const elementHash = getElementHash(element);
1335
+ if (elementHash) {
1336
+ const elementInfo = elementMapInfo[elementHash];
1337
+ totalClicks += elementInfo?.totalclicks || 0;
1338
+ }
1339
+ const children = element.querySelectorAll('*');
1340
+ children.forEach((child) => {
1341
+ const childHash = getElementHash(child);
1342
+ if (childHash) {
1343
+ const childInfo = elementMapInfo[childHash];
1344
+ totalClicks += childInfo?.totalclicks || 0;
1345
+ }
1346
+ });
1347
+ return totalClicks;
1348
+ }
1349
+ function buildAreaNode(element, hash, heatmapInfo, shadowRoot, persistedData) {
1350
+ if (!heatmapInfo.elementMapInfo)
1351
+ return;
1352
+ const totalClicks = heatmapInfo.totalClicks || 0;
1353
+ const elementInfo = heatmapInfo.elementMapInfo[hash];
1354
+ // Calculate total clicks including all child elements
1355
+ const elementClicks = calculateTotalClicksWithChildren(element, heatmapInfo.elementMapInfo);
1260
1356
  const clickDist = calculateClickDistribution(elementClicks, totalClicks);
1261
1357
  const rect = getElementRect(element);
1262
1358
  const color = getColorFromClickDist(clickDist);
1263
1359
  const hoverColor = getHoverColorFromClickDist(clickDist);
1264
1360
  const areaNode = {
1265
- kind: 'area',
1266
- id: `${hash}_${Date.now()}`,
1361
+ kind: persistedData?.kind || 'area',
1362
+ id: persistedData?.id || `${hash}_${Date.now()}`,
1267
1363
  hash,
1268
- selector: elementInfo?.selector || getElementSelector(element),
1364
+ selector: persistedData?.selector || elementInfo?.selector || getElementSelector(element),
1269
1365
  // DOM references
1270
1366
  element,
1271
1367
  areaElement: null,
@@ -1304,6 +1400,294 @@ function getTopElementsByClicks(elementMapInfo, topN = 10) {
1304
1400
  return elements;
1305
1401
  }
1306
1402
 
1403
+ /**
1404
+ * Build parent-child relationships between areas based on DOM hierarchy
1405
+ * @param areas - Array of area nodes to build relationships for
1406
+ */
1407
+ function buildAreaGraph(areas) {
1408
+ // Clear existing relationships
1409
+ areas.forEach((area) => {
1410
+ area.parentNode = null;
1411
+ area.childNodes.clear();
1412
+ });
1413
+ // Build relationships based on DOM containment
1414
+ for (let i = 0; i < areas.length; i++) {
1415
+ const area = areas[i];
1416
+ for (let j = 0; j < areas.length; j++) {
1417
+ if (i === j)
1418
+ continue;
1419
+ const otherArea = areas[j];
1420
+ // Check if area's element is contained within otherArea's element
1421
+ if (otherArea.element.contains(area.element)) {
1422
+ // Find the closest parent (not just any ancestor)
1423
+ if (!area.parentNode || area.parentNode.element.contains(otherArea.element)) {
1424
+ // Remove from old parent if exists
1425
+ if (area.parentNode) {
1426
+ area.parentNode.childNodes.delete(area);
1427
+ }
1428
+ // Set new parent
1429
+ area.parentNode = otherArea;
1430
+ otherArea.childNodes.add(area);
1431
+ }
1432
+ }
1433
+ }
1434
+ }
1435
+ }
1436
+
1437
+ class Logger {
1438
+ config = {
1439
+ enabled: false,
1440
+ prefix: '',
1441
+ timestamp: false,
1442
+ };
1443
+ /**
1444
+ * Cấu hình logger
1445
+ * @param config - Cấu hình logger
1446
+ */
1447
+ configure(config) {
1448
+ this.config = { ...this.config, ...config };
1449
+ }
1450
+ /**
1451
+ * Lấy cấu hình hiện tại
1452
+ */
1453
+ getConfig() {
1454
+ return { ...this.config };
1455
+ }
1456
+ /**
1457
+ * Bật logger
1458
+ */
1459
+ enable() {
1460
+ this.config.enabled = true;
1461
+ }
1462
+ /**
1463
+ * Tắt logger
1464
+ */
1465
+ disable() {
1466
+ this.config.enabled = false;
1467
+ }
1468
+ /**
1469
+ * Format message với prefix và timestamp
1470
+ */
1471
+ formatMessage(...args) {
1472
+ const parts = [];
1473
+ if (this.config.timestamp) {
1474
+ parts.push(`[${new Date().toISOString()}]`);
1475
+ }
1476
+ if (this.config.prefix) {
1477
+ parts.push(`[${this.config.prefix}]`);
1478
+ }
1479
+ if (parts.length > 0) {
1480
+ return [parts.join(' '), ...args];
1481
+ }
1482
+ return args;
1483
+ }
1484
+ /**
1485
+ * Log message
1486
+ */
1487
+ log(...args) {
1488
+ if (!this.config.enabled)
1489
+ return;
1490
+ console.log(...this.formatMessage(...args));
1491
+ }
1492
+ /**
1493
+ * Log info message
1494
+ */
1495
+ info(...args) {
1496
+ if (!this.config.enabled)
1497
+ return;
1498
+ console.info(...this.formatMessage(...args));
1499
+ }
1500
+ /**
1501
+ * Log warning message
1502
+ */
1503
+ warn(...args) {
1504
+ if (!this.config.enabled)
1505
+ return;
1506
+ console.warn(...this.formatMessage(...args));
1507
+ }
1508
+ /**
1509
+ * Log error message
1510
+ */
1511
+ error(...args) {
1512
+ if (!this.config.enabled)
1513
+ return;
1514
+ console.error(...this.formatMessage(...args));
1515
+ }
1516
+ /**
1517
+ * Log debug message
1518
+ */
1519
+ debug(...args) {
1520
+ if (!this.config.enabled)
1521
+ return;
1522
+ console.debug(...this.formatMessage(...args));
1523
+ }
1524
+ /**
1525
+ * Log table data
1526
+ */
1527
+ table(data) {
1528
+ if (!this.config.enabled)
1529
+ return;
1530
+ console.table(data);
1531
+ }
1532
+ /**
1533
+ * Start a group
1534
+ */
1535
+ group(label) {
1536
+ if (!this.config.enabled)
1537
+ return;
1538
+ console.group(...this.formatMessage(label));
1539
+ }
1540
+ /**
1541
+ * Start a collapsed group
1542
+ */
1543
+ groupCollapsed(label) {
1544
+ if (!this.config.enabled)
1545
+ return;
1546
+ console.groupCollapsed(...this.formatMessage(label));
1547
+ }
1548
+ /**
1549
+ * End a group
1550
+ */
1551
+ groupEnd() {
1552
+ if (!this.config.enabled)
1553
+ return;
1554
+ console.groupEnd();
1555
+ }
1556
+ /**
1557
+ * Start a timer
1558
+ */
1559
+ time(label) {
1560
+ if (!this.config.enabled)
1561
+ return;
1562
+ console.time(label);
1563
+ }
1564
+ /**
1565
+ * End a timer
1566
+ */
1567
+ timeEnd(label) {
1568
+ if (!this.config.enabled)
1569
+ return;
1570
+ console.timeEnd(label);
1571
+ }
1572
+ }
1573
+ // Export singleton instance
1574
+ const logger$3 = new Logger();
1575
+ // Export factory function để tạo logger với config riêng
1576
+ function createLogger(config = {}) {
1577
+ const instance = new Logger();
1578
+ instance.configure(config);
1579
+ return instance;
1580
+ }
1581
+
1582
+ function findLastSizeOfDom(data) {
1583
+ const listDocs = data
1584
+ .filter((item) => item.doc?.find((doc) => doc.data.width && doc.data.height))
1585
+ .flatMap((item) => item.doc?.flatMap((doc) => doc.data));
1586
+ const lastDoc = listDocs?.[listDocs.length - 1];
1587
+ const docSize = {
1588
+ width: lastDoc?.width,
1589
+ height: lastDoc?.height,
1590
+ };
1591
+ const listResizes = data.filter((item) => !!item.resize).flatMap((item) => item.resize);
1592
+ const lastResizeEvent = listResizes?.[listResizes.length - 1];
1593
+ const resize = {
1594
+ width: lastResizeEvent?.data.width,
1595
+ height: lastResizeEvent?.data.height,
1596
+ };
1597
+ return {
1598
+ doc: docSize,
1599
+ resize: resize,
1600
+ size: {
1601
+ width: resize.width || docSize.width,
1602
+ height: resize.height || docSize.height,
1603
+ },
1604
+ };
1605
+ }
1606
+ function decodePayloads(payload) {
1607
+ try {
1608
+ return decode(payload);
1609
+ }
1610
+ catch (_error) {
1611
+ return null;
1612
+ }
1613
+ }
1614
+
1615
+ function findElementByHash(props) {
1616
+ const { hash, selector, iframeDocument, vizRef } = props;
1617
+ if (vizRef) {
1618
+ const element = vizRef.get(hash);
1619
+ return element;
1620
+ }
1621
+ // Fallback
1622
+ if (!iframeDocument)
1623
+ return null;
1624
+ try {
1625
+ const element = selector ? iframeDocument.querySelector(selector) : null;
1626
+ if (element) {
1627
+ return element;
1628
+ }
1629
+ }
1630
+ catch (error) {
1631
+ logger$3.warn(`Invalid selector "${selector}":`, error);
1632
+ }
1633
+ const elementByHash = iframeDocument.querySelector(`[data-clarity-hashalpha="${hash}"], [data-clarity-hash="${hash}"], [data-clarity-hashbeta="${hash}"]`);
1634
+ return elementByHash;
1635
+ }
1636
+
1637
+ /**
1638
+ * Hydrates persisted area data into full area node
1639
+ * Finds element in DOM and calculates all runtime values
1640
+ *
1641
+ * @param persistedData - Minimal data from database
1642
+ * @param iframeDocument - Document to find element in
1643
+ * @param heatmapInfo - Heatmap data for click calculations
1644
+ * @param vizRef - Map of hash to elements
1645
+ * @param shadowRoot - Optional shadow root for rect calculation
1646
+ * @returns Full area node or null if element not found
1647
+ */
1648
+ function hydrateAreaNode(props) {
1649
+ const { persistedData, iframeDocument, heatmapInfo, vizRef, shadowRoot } = props;
1650
+ const { id, hash, selector } = persistedData;
1651
+ const element = findElementByHash({ hash, selector, iframeDocument, vizRef });
1652
+ if (!element) {
1653
+ logger$3.warn(`Cannot hydrate area ${id}: element not found for hash ${hash} or selector ${selector}`);
1654
+ return null;
1655
+ }
1656
+ const areaNode = buildAreaNode(element, hash, heatmapInfo, shadowRoot, persistedData);
1657
+ if (!areaNode)
1658
+ return null;
1659
+ return areaNode;
1660
+ }
1661
+ function hydrateAreas(props) {
1662
+ const { clickAreas, iframeDocument, heatmapInfo, vizRef, shadowRoot } = props;
1663
+ const hydratedAreas = [];
1664
+ for (const persistedData of clickAreas) {
1665
+ const area = hydrateAreaNode({ persistedData, iframeDocument, heatmapInfo, vizRef, shadowRoot });
1666
+ if (area) {
1667
+ hydratedAreas.push(area);
1668
+ }
1669
+ }
1670
+ logger$3.info(`Hydrated ${hydratedAreas.length} of ${clickAreas.length} persisted areas`);
1671
+ return hydratedAreas;
1672
+ }
1673
+ /**
1674
+ * Serializes area node to persisted data for database storage
1675
+ */
1676
+ function serializeAreaNode(area) {
1677
+ return {
1678
+ kind: area.kind,
1679
+ id: area.id,
1680
+ hash: area.hash,
1681
+ selector: area.selector,
1682
+ };
1683
+ }
1684
+ /**
1685
+ * Serializes multiple areas for database storage
1686
+ */
1687
+ function serializeAreas(areas) {
1688
+ return areas.map(serializeAreaNode);
1689
+ }
1690
+
1307
1691
  /**
1308
1692
  * Resolve overlapping areas by priority rules
1309
1693
  *
@@ -1446,36 +1830,64 @@ function getVisibleAreas(areas, iframeDocument) {
1446
1830
  return sortAreasByClickDist(visible);
1447
1831
  }
1448
1832
 
1449
- function findLastSizeOfDom(data) {
1450
- const listDocs = data
1451
- .filter((item) => item.doc?.find((doc) => doc.data.width && doc.data.height))
1452
- .flatMap((item) => item.doc?.flatMap((doc) => doc.data));
1453
- const lastDoc = listDocs?.[listDocs.length - 1];
1454
- const docSize = {
1455
- width: lastDoc?.width,
1456
- height: lastDoc?.height,
1457
- };
1458
- const listResizes = data.filter((item) => !!item.resize).flatMap((item) => item.resize);
1459
- const lastResizeEvent = listResizes?.[listResizes.length - 1];
1460
- const resize = {
1461
- width: lastResizeEvent?.data.width,
1462
- height: lastResizeEvent?.data.height,
1463
- };
1464
- return {
1465
- doc: docSize,
1466
- resize: resize,
1467
- size: {
1468
- width: resize.width || docSize.width,
1469
- height: resize.height || docSize.height,
1470
- },
1471
- };
1833
+ /**
1834
+ * Helper functions for setting up area renderer
1835
+ */
1836
+ /**
1837
+ * Create the outer container for area rendering
1838
+ */
1839
+ function createAreaContainer(iframeDocument) {
1840
+ const container = iframeDocument.createElement('div');
1841
+ container.setAttribute(AREA_MAP_DIV_ATTRIBUTE, 'true');
1842
+ container.style.cssText = AREA_CONTAINER_STYLES;
1843
+ return container;
1472
1844
  }
1473
- function decodePayloads(payload) {
1474
- try {
1475
- return decode(payload);
1845
+ /**
1846
+ * Create the inner container for React portal
1847
+ */
1848
+ function createInnerContainer(iframeDocument) {
1849
+ const innerContainer = iframeDocument.createElement('div');
1850
+ innerContainer.className = AREA_RENDERER_SELECTORS.innerContainerClass;
1851
+ innerContainer.style.cssText = AREA_INNER_CONTAINER_STYLES;
1852
+ return innerContainer;
1853
+ }
1854
+ /**
1855
+ * Get or create the outer container element
1856
+ */
1857
+ function getOrCreateAreaContainer(iframeDocument, customShadowRoot) {
1858
+ let container = iframeDocument.querySelector(AREA_RENDERER_SELECTORS.containerSelector);
1859
+ if (!container) {
1860
+ container = createAreaContainer(iframeDocument);
1861
+ const targetRoot = customShadowRoot || iframeDocument.body;
1862
+ if (targetRoot) {
1863
+ targetRoot.appendChild(container);
1864
+ }
1476
1865
  }
1477
- catch (_error) {
1478
- return null;
1866
+ return container;
1867
+ }
1868
+ function getOrCreateContainerShadowRoot(container) {
1869
+ if (container.shadowRoot) {
1870
+ return container.shadowRoot;
1871
+ }
1872
+ return container.attachShadow({ mode: 'open' });
1873
+ }
1874
+ function getOrCreateInnerContainer(shadowRoot, iframeDocument) {
1875
+ let innerContainer = shadowRoot.querySelector(AREA_RENDERER_SELECTORS.innerContainerSelector);
1876
+ if (!innerContainer) {
1877
+ innerContainer = createInnerContainer(iframeDocument);
1878
+ shadowRoot.appendChild(innerContainer);
1879
+ }
1880
+ return innerContainer;
1881
+ }
1882
+ function setupAreaRenderingContainer(iframeDocument, customShadowRoot) {
1883
+ const container = getOrCreateAreaContainer(iframeDocument, customShadowRoot);
1884
+ const shadowRoot = getOrCreateContainerShadowRoot(container);
1885
+ const innerContainer = getOrCreateInnerContainer(shadowRoot, iframeDocument);
1886
+ return innerContainer;
1887
+ }
1888
+ function cleanupAreaRenderingContainer(container) {
1889
+ if (container && container.parentNode) {
1890
+ container.parentNode.removeChild(container);
1479
1891
  }
1480
1892
  }
1481
1893
 
@@ -1510,23 +1922,6 @@ function getElementLayout(element) {
1510
1922
  height: rect.height,
1511
1923
  };
1512
1924
  }
1513
- const getElementAtPoint = (doc, x, y) => {
1514
- let el = null;
1515
- if ('caretPositionFromPoint' in doc) {
1516
- el = doc.caretPositionFromPoint(x, y)?.offsetNode ?? null;
1517
- }
1518
- el = el ?? doc.elementFromPoint(x, y);
1519
- let element = el;
1520
- while (element && element.nodeType === Node.TEXT_NODE) {
1521
- element = element.parentElement;
1522
- }
1523
- return element;
1524
- };
1525
- function getElementHash(element) {
1526
- return (element.getAttribute('data-clarity-hash') ||
1527
- element.getAttribute('data-clarity-hashalpha') ||
1528
- element.getAttribute('data-clarity-hashbeta'));
1529
- }
1530
1925
  const getElementRank = (hash, elements) => {
1531
1926
  if (!elements)
1532
1927
  return 0;
@@ -1617,8 +2012,7 @@ const calculateHorizontalPlacementPosition = (targetRect, calloutRect, placement
1617
2012
 
1618
2013
  const isLeftPositionValid = (leftPos, calloutWidth, viewportWidth, padding, containerRect) => {
1619
2014
  if (containerRect) {
1620
- return (leftPos >= containerRect.left + padding &&
1621
- leftPos + calloutWidth <= containerRect.right - padding);
2015
+ return leftPos >= containerRect.left + padding && leftPos + calloutWidth <= containerRect.right - padding;
1622
2016
  }
1623
2017
  return leftPos >= padding && leftPos + calloutWidth <= viewportWidth - padding;
1624
2018
  };
@@ -1662,8 +2056,7 @@ const generateVerticalPositionCandidates = (targetRect, calloutRect, viewportHei
1662
2056
  top: verticalPos,
1663
2057
  left: horizontalPos,
1664
2058
  horizontalAlign: align,
1665
- valid: verticalValid &&
1666
- isLeftPositionValid(horizontalPos, calloutRect.width, viewportWidth, padding, containerRect),
2059
+ valid: verticalValid && isLeftPositionValid(horizontalPos, calloutRect.width, viewportWidth, padding, containerRect),
1667
2060
  });
1668
2061
  });
1669
2062
  });
@@ -1729,6 +2122,10 @@ const calcCalloutPosition = (options) => {
1729
2122
  };
1730
2123
  };
1731
2124
 
2125
+ const logger$2 = createLogger({
2126
+ enabled: false,
2127
+ prefix: 'IframeNavigationBlockerV2',
2128
+ });
1732
2129
  class IframeNavigationBlockerV2 {
1733
2130
  doc;
1734
2131
  win;
@@ -1736,17 +2133,18 @@ class IframeNavigationBlockerV2 {
1736
2133
  showMessage = false;
1737
2134
  originalWindowOpen;
1738
2135
  observers = [];
1739
- constructor(iframe) {
2136
+ constructor(iframe, config) {
1740
2137
  if (!iframe.contentDocument || !iframe.contentWindow) {
1741
2138
  throw new Error('Iframe document or window not accessible');
1742
2139
  }
1743
2140
  this.doc = iframe.contentDocument;
1744
2141
  this.win = iframe.contentWindow;
1745
2142
  this.originalWindowOpen = this.win.open.bind(this.win);
2143
+ logger$2.configure({ enabled: !!config?.debug });
1746
2144
  this.init();
1747
2145
  }
1748
2146
  init() {
1749
- console.log('[NavigationBlocker] Initializing...');
2147
+ logger$2.log('Initializing...');
1750
2148
  try {
1751
2149
  // Chặn navigation qua links
1752
2150
  this.blockLinkNavigation();
@@ -1762,7 +2160,7 @@ class IframeNavigationBlockerV2 {
1762
2160
  this.injectCSP();
1763
2161
  }
1764
2162
  catch (error) {
1765
- console.error('[NavigationBlocker] Init error:', error);
2163
+ logger$2.error('Init error:', error);
1766
2164
  }
1767
2165
  }
1768
2166
  blockLinkNavigation() {
@@ -1776,11 +2174,11 @@ class IframeNavigationBlockerV2 {
1776
2174
  const href = link.getAttribute('href');
1777
2175
  // Cho phép hash links và empty links
1778
2176
  if (!href || href === '' || href === '#' || href.startsWith('#')) {
1779
- console.log('[NavigationBlocker] Allowed hash navigation:', href);
2177
+ logger$2.log('Allowed hash navigation:', href);
1780
2178
  return;
1781
2179
  }
1782
2180
  // Chặn tất cả các loại navigation
1783
- console.log('[NavigationBlocker] Blocked link navigation to:', href);
2181
+ logger$2.log('Blocked link navigation to:', href);
1784
2182
  e.preventDefault();
1785
2183
  e.stopPropagation();
1786
2184
  e.stopImmediatePropagation();
@@ -1796,7 +2194,7 @@ class IframeNavigationBlockerV2 {
1796
2194
  if (link) {
1797
2195
  const href = link.getAttribute('href');
1798
2196
  if (href && !href.startsWith('#')) {
1799
- console.log('[NavigationBlocker] Blocked auxclick navigation');
2197
+ logger$2.log('Blocked auxclick navigation');
1800
2198
  e.preventDefault();
1801
2199
  e.stopPropagation();
1802
2200
  e.stopImmediatePropagation();
@@ -1829,13 +2227,13 @@ class IframeNavigationBlockerV2 {
1829
2227
  const action = form.getAttribute('action');
1830
2228
  // Cho phép forms không có action
1831
2229
  if (!action || action === '' || action === '#') {
1832
- console.log('[NavigationBlocker] Allowed same-page form');
2230
+ logger$2.log('Allowed same-page form');
1833
2231
  e.preventDefault();
1834
2232
  this.handleFormSubmit(form);
1835
2233
  return;
1836
2234
  }
1837
2235
  // Chặn tất cả external submissions
1838
- console.log('[NavigationBlocker] Blocked form submission to:', action);
2236
+ logger$2.log('Blocked form submission to:', action);
1839
2237
  e.preventDefault();
1840
2238
  e.stopPropagation();
1841
2239
  e.stopImmediatePropagation();
@@ -1849,7 +2247,7 @@ class IframeNavigationBlockerV2 {
1849
2247
  return this.originalWindowOpen(...args);
1850
2248
  }
1851
2249
  const url = args[0]?.toString() || 'popup';
1852
- console.log('[NavigationBlocker] Blocked window.open:', url);
2250
+ logger$2.log('Blocked window.open:', url);
1853
2251
  this.notifyBlockedNavigation(url);
1854
2252
  return null;
1855
2253
  });
@@ -1859,7 +2257,7 @@ class IframeNavigationBlockerV2 {
1859
2257
  this.win.addEventListener('beforeunload', (e) => {
1860
2258
  if (!this.isEnabled)
1861
2259
  return;
1862
- console.log('[NavigationBlocker] Blocked beforeunload');
2260
+ logger$2.log('Blocked beforeunload');
1863
2261
  e.preventDefault();
1864
2262
  e.returnValue = '';
1865
2263
  return '';
@@ -1868,7 +2266,7 @@ class IframeNavigationBlockerV2 {
1868
2266
  this.win.addEventListener('unload', (e) => {
1869
2267
  if (!this.isEnabled)
1870
2268
  return;
1871
- console.log('[NavigationBlocker] Blocked unload');
2269
+ logger$2.log('Blocked unload');
1872
2270
  e.preventDefault();
1873
2271
  e.stopPropagation();
1874
2272
  }, true);
@@ -1876,7 +2274,7 @@ class IframeNavigationBlockerV2 {
1876
2274
  this.win.addEventListener('popstate', (e) => {
1877
2275
  if (!this.isEnabled)
1878
2276
  return;
1879
- console.log('[NavigationBlocker] Blocked popstate');
2277
+ logger$2.log('Blocked popstate');
1880
2278
  e.preventDefault();
1881
2279
  e.stopPropagation();
1882
2280
  }, true);
@@ -1929,11 +2327,11 @@ class IframeNavigationBlockerV2 {
1929
2327
  meta.httpEquiv = 'Content-Security-Policy';
1930
2328
  meta.content = "navigate-to 'none'"; // Chặn tất cả navigation
1931
2329
  this.doc.head.appendChild(meta);
1932
- console.log('[NavigationBlocker] Injected CSP');
2330
+ logger$2.log('Injected CSP');
1933
2331
  }
1934
2332
  }
1935
2333
  catch (error) {
1936
- console.warn('[NavigationBlocker] Could not inject CSP:', error);
2334
+ logger$2.warn('Could not inject CSP:', error);
1937
2335
  }
1938
2336
  }
1939
2337
  handleFormSubmit(form) {
@@ -1942,13 +2340,13 @@ class IframeNavigationBlockerV2 {
1942
2340
  formData.forEach((value, key) => {
1943
2341
  data[key] = value;
1944
2342
  });
1945
- console.log('[NavigationBlocker] Handling form data:', data);
2343
+ logger$2.log('Handling form data:', data);
1946
2344
  window.dispatchEvent(new CustomEvent('iframe-form-submit', {
1947
2345
  detail: { form, data },
1948
2346
  }));
1949
2347
  }
1950
2348
  notifyBlockedNavigation(url) {
1951
- console.warn('[NavigationBlocker] Navigation blocked to:', url);
2349
+ logger$2.warn('Navigation blocked to:', url);
1952
2350
  window.dispatchEvent(new CustomEvent('iframe-navigation-blocked', {
1953
2351
  detail: { url, timestamp: Date.now() },
1954
2352
  }));
@@ -1998,19 +2396,19 @@ class IframeNavigationBlockerV2 {
1998
2396
  }
1999
2397
  enable() {
2000
2398
  this.isEnabled = true;
2001
- console.log('[NavigationBlocker] Enabled');
2399
+ logger$2.log('Enabled');
2002
2400
  }
2003
2401
  enableMessage() {
2004
2402
  this.showMessage = true;
2005
- console.log('[NavigationBlocker] Enabled message');
2403
+ logger$2.log('Enabled message');
2006
2404
  }
2007
2405
  disable() {
2008
2406
  this.isEnabled = false;
2009
- console.log('[NavigationBlocker] Disabled');
2407
+ logger$2.log('Disabled');
2010
2408
  }
2011
2409
  disableMessage() {
2012
2410
  this.showMessage = false;
2013
- console.log('[NavigationBlocker] Disabled message');
2411
+ logger$2.log('Disabled message');
2014
2412
  }
2015
2413
  destroy() {
2016
2414
  this.isEnabled = false;
@@ -2018,10 +2416,14 @@ class IframeNavigationBlockerV2 {
2018
2416
  // Cleanup observers
2019
2417
  this.observers.forEach((observer) => observer.disconnect());
2020
2418
  this.observers = [];
2021
- console.log('[NavigationBlocker] Destroyed');
2419
+ logger$2.log('Destroyed');
2022
2420
  }
2023
2421
  }
2024
2422
 
2423
+ const logger$1 = createLogger({
2424
+ enabled: false,
2425
+ prefix: 'IframeStyleReplacer',
2426
+ });
2025
2427
  class IframeStyleReplacer {
2026
2428
  doc;
2027
2429
  win;
@@ -2034,6 +2436,7 @@ class IframeStyleReplacer {
2034
2436
  this.doc = iframe.contentDocument;
2035
2437
  this.win = iframe.contentWindow;
2036
2438
  this.config = config;
2439
+ logger$1.configure({ enabled: !!config?.debug });
2037
2440
  }
2038
2441
  px(value) {
2039
2442
  return `${value.toFixed(2)}px`;
@@ -2067,7 +2470,7 @@ class IframeStyleReplacer {
2067
2470
  count++;
2068
2471
  }
2069
2472
  });
2070
- console.log(`[IframeStyleReplacer] Replaced ${count} inline style elements`);
2473
+ logger$1.log(`Replaced ${count} inline style elements`);
2071
2474
  return count;
2072
2475
  }
2073
2476
  processStyleTags() {
@@ -2080,7 +2483,7 @@ class IframeStyleReplacer {
2080
2483
  count++;
2081
2484
  }
2082
2485
  });
2083
- console.log(`[IframeStyleReplacer] Replaced ${count} <style> tags`);
2486
+ logger$1.log(`Replaced ${count} <style> tags`);
2084
2487
  return count;
2085
2488
  }
2086
2489
  processRule(rule) {
@@ -2111,7 +2514,7 @@ class IframeStyleReplacer {
2111
2514
  try {
2112
2515
  // Bỏ qua external CSS (cross-origin)
2113
2516
  if (sheet.href && !sheet.href.startsWith(this.win.location.origin)) {
2114
- console.log('[IframeStyleReplacer] Skipping external CSS:', sheet.href);
2517
+ logger$1.log('Skipping external CSS:', sheet.href);
2115
2518
  return;
2116
2519
  }
2117
2520
  const rules = sheet.cssRules || sheet.rules;
@@ -2122,10 +2525,10 @@ class IframeStyleReplacer {
2122
2525
  }
2123
2526
  }
2124
2527
  catch (e) {
2125
- console.warn('[IframeStyleReplacer] Cannot read stylesheet (CORS?):', e.message);
2528
+ logger$1.warn('Cannot read stylesheet (CORS?):', e.message);
2126
2529
  }
2127
2530
  });
2128
- console.log(`[IframeStyleReplacer] Replaced ${total} rules in stylesheets`);
2531
+ logger$1.log(`Replaced ${total} rules in stylesheets`);
2129
2532
  return total;
2130
2533
  }
2131
2534
  async processLinkedStylesheets() {
@@ -2133,7 +2536,7 @@ class IframeStyleReplacer {
2133
2536
  let count = 0;
2134
2537
  for (const link of Array.from(links)) {
2135
2538
  if (!link.href.startsWith(this.win.location.origin)) {
2136
- console.log('[IframeStyleReplacer] Skipping external CSS:', link.href);
2539
+ logger$1.log('Skipping external CSS:', link.href);
2137
2540
  continue;
2138
2541
  }
2139
2542
  try {
@@ -2151,10 +2554,10 @@ class IframeStyleReplacer {
2151
2554
  }
2152
2555
  }
2153
2556
  catch (e) {
2154
- console.warn('[IframeStyleReplacer] Cannot load CSS:', link.href, e);
2557
+ logger$1.warn('Cannot load CSS:', link.href, e);
2155
2558
  }
2156
2559
  }
2157
- console.log(`[IframeStyleReplacer] Replaced ${count} linked CSS files`);
2560
+ logger$1.log(`Replaced ${count} linked CSS files`);
2158
2561
  return count;
2159
2562
  }
2160
2563
  getFinalHeight() {
@@ -2178,7 +2581,7 @@ class IframeStyleReplacer {
2178
2581
  }
2179
2582
  async run() {
2180
2583
  try {
2181
- console.log('[IframeStyleReplacer] Starting viewport units replacement...');
2584
+ logger$1.log('Starting viewport units replacement...');
2182
2585
  this.processInlineStyles();
2183
2586
  this.processStyleTags();
2184
2587
  this.processStylesheets();
@@ -2188,13 +2591,13 @@ class IframeStyleReplacer {
2188
2591
  requestAnimationFrame(() => {
2189
2592
  const height = this.getFinalHeight();
2190
2593
  const width = this.getFinalWidth();
2191
- console.log('[IframeStyleReplacer] Calculated dimensions:', { height, width });
2594
+ logger$1.log('Calculated dimensions:', { height, width });
2192
2595
  resolve({ height, width });
2193
2596
  });
2194
2597
  });
2195
2598
  }
2196
2599
  catch (err) {
2197
- console.error('[IframeStyleReplacer] Critical error:', err);
2600
+ logger$1.error('Critical error:', err);
2198
2601
  return {
2199
2602
  height: this.doc.body.scrollHeight || 1000,
2200
2603
  width: this.doc.body.scrollWidth || 1000,
@@ -2206,6 +2609,10 @@ class IframeStyleReplacer {
2206
2609
  }
2207
2610
  }
2208
2611
 
2612
+ const logger = createLogger({
2613
+ enabled: false,
2614
+ prefix: 'IframeHelper',
2615
+ });
2209
2616
  class IframeHelperFixer {
2210
2617
  iframe;
2211
2618
  config;
@@ -2215,10 +2622,11 @@ class IframeHelperFixer {
2215
2622
  this.config = config;
2216
2623
  this.iframe = config.iframe;
2217
2624
  this.init();
2625
+ logger.configure({ enabled: !!config?.debug });
2218
2626
  }
2219
2627
  async init() {
2220
2628
  if (!this.iframe) {
2221
- console.error('[IframeHelper] iframe not found');
2629
+ logger.error('iframe not found');
2222
2630
  this.config.onError?.(new Error('iframe not found'));
2223
2631
  return;
2224
2632
  }
@@ -2232,19 +2640,19 @@ class IframeHelperFixer {
2232
2640
  }
2233
2641
  async process() {
2234
2642
  if (!this.iframe.contentDocument || !this.iframe.contentWindow) {
2235
- console.error('[IframeHelper] Cannot access iframe document');
2643
+ logger.error('Cannot access iframe document');
2236
2644
  this.config.onError?.(new Error('Cannot access iframe document'));
2237
2645
  return;
2238
2646
  }
2239
2647
  try {
2240
- console.log('[IframeHelper] Processing viewport units...');
2648
+ logger.log('Processing viewport units...');
2241
2649
  // Create replacer instance
2242
2650
  this.replacer = new IframeStyleReplacer(this.iframe, this.config);
2243
2651
  // Create navigation blocker
2244
- this.navigationBlocker = new IframeNavigationBlockerV2(this.iframe);
2652
+ this.navigationBlocker = new IframeNavigationBlockerV2(this.iframe, { debug: this.config.debug });
2245
2653
  // Run replacement
2246
2654
  const result = await this.replacer.run();
2247
- console.log('[IframeHelper] Process completed:', result);
2655
+ logger.log('Process completed:', result);
2248
2656
  // Trigger success callback
2249
2657
  this.config.onSuccess?.(result);
2250
2658
  // Dispatch custom event
@@ -2253,12 +2661,12 @@ class IframeHelperFixer {
2253
2661
  }));
2254
2662
  }
2255
2663
  catch (error) {
2256
- console.error('[IframeHelper] Failed to process:', error);
2664
+ logger.error('Failed to process:', error);
2257
2665
  this.config.onError?.(error);
2258
2666
  }
2259
2667
  }
2260
2668
  async recalculate() {
2261
- console.log('[IframeHelper] Recalculating...');
2669
+ logger.log('Recalculating...');
2262
2670
  await this.process();
2263
2671
  }
2264
2672
  updateConfig(config) {
@@ -2283,61 +2691,150 @@ class IframeHelperFixer {
2283
2691
  this.replacer = null;
2284
2692
  this.navigationBlocker?.destroy();
2285
2693
  this.navigationBlocker = null;
2286
- console.log('[IframeHelper] Destroyed');
2694
+ logger.log('Destroyed');
2287
2695
  }
2288
2696
  }
2289
2697
 
2290
2698
  function initIframeHelperFixer(config) {
2291
2699
  const fixer = new IframeHelperFixer(config);
2292
2700
  window.addEventListener('iframe-dimensions-applied', ((e) => {
2293
- const ev = e;
2294
- console.log('[IframeHelper] Iframe dimensions finalized:', ev.detail);
2701
+ // console.log('[IframeHelper] Iframe dimensions finalized:', ev.detail);
2295
2702
  }));
2296
2703
  window.addEventListener('iframe-navigation-blocked', ((e) => {
2297
- const ev = e;
2298
- console.warn('[IframeHelper] Iframe tried to navigate to:', ev.detail.url);
2704
+ // console.warn('[IframeHelper] Iframe tried to navigate to:', ev.detail.url);
2299
2705
  }));
2300
2706
  window.addEventListener('iframe-form-submit', ((e) => {
2301
- const ev = e;
2302
- console.log('[IframeHelper] Iframe form submitted:', ev.detail.data);
2707
+ // console.log('[IframeHelper] Iframe form submitted:', ev.detail.data);
2303
2708
  }));
2304
2709
  return fixer;
2305
2710
  }
2306
2711
 
2307
- function useAreaEditMode({ iframeRef, onCreateArea, enabled = false, }) {
2712
+ function validateAreaCreation(dataInfo, hash, areas) {
2713
+ if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks) {
2714
+ logger$3.warn('Cannot create area: missing heatmap data');
2715
+ return false;
2716
+ }
2717
+ if (!hash) {
2718
+ logger$3.warn('Cannot create area: missing hash');
2719
+ return false;
2720
+ }
2721
+ const alreadyExists = areas.some((area) => area.hash === hash);
2722
+ if (alreadyExists) {
2723
+ logger$3.warn(`Area already exists for element: ${hash}`);
2724
+ return false;
2725
+ }
2726
+ return true;
2727
+ }
2728
+ function identifyConflictingAreas(area) {
2729
+ const conflicts = {
2730
+ parentId: null,
2731
+ childrenIds: [],
2732
+ };
2733
+ // Case 1: New area is a child of an existing area
2734
+ if (area.parentNode) {
2735
+ conflicts.parentId = area.parentNode.id;
2736
+ logger$3.info(`New area "${area.selector}" is a child of existing area "${area.parentNode.selector}". Will remove parent.`);
2737
+ }
2738
+ // Case 2: New area is a parent of existing area(s)
2739
+ if (area.childNodes.size > 0) {
2740
+ area.childNodes.forEach((childArea) => {
2741
+ conflicts.childrenIds.push(childArea.id);
2742
+ });
2743
+ logger$3.info(`New area "${area.selector}" is a parent of ${area.childNodes.size} existing area(s). Will remove children.`);
2744
+ }
2745
+ return conflicts;
2746
+ }
2747
+ function clearGraphRelationships(area) {
2748
+ area.parentNode = null;
2749
+ area.childNodes.clear();
2750
+ }
2751
+ function removeConflictingAreas(conflicts, removeArea) {
2752
+ if (conflicts.parentId) {
2753
+ removeArea(conflicts.parentId);
2754
+ }
2755
+ conflicts.childrenIds.forEach((childId) => {
2756
+ removeArea(childId);
2757
+ });
2758
+ }
2759
+ function useAreaCreation(options = {}) {
2760
+ const { customShadowRoot, onAreaCreated } = options;
2761
+ const { dataInfo } = useHeatmapData();
2762
+ const { areas, addArea, removeArea } = useHeatmapAreaClick();
2763
+ const onAreaCreatedElement = useCallback((element) => {
2764
+ if (!dataInfo)
2765
+ return;
2766
+ const hash = getElementHash(element);
2767
+ if (!validateAreaCreation(dataInfo, hash, areas)) {
2768
+ return;
2769
+ }
2770
+ try {
2771
+ const area = buildAreaNode(element, hash, dataInfo, customShadowRoot);
2772
+ if (!area)
2773
+ return;
2774
+ const tempAreas = [...areas, area];
2775
+ buildAreaGraph(tempAreas);
2776
+ const conflicts = identifyConflictingAreas(area);
2777
+ clearGraphRelationships(area);
2778
+ addArea(area);
2779
+ removeConflictingAreas(conflicts, removeArea);
2780
+ if (onAreaCreated) {
2781
+ onAreaCreated(area);
2782
+ }
2783
+ }
2784
+ catch (error) {
2785
+ logger$3.error('Failed to create area:', error);
2786
+ }
2787
+ }, [dataInfo, areas, addArea, removeArea, customShadowRoot, onAreaCreated]);
2788
+ return {
2789
+ onAreaCreatedElement,
2790
+ };
2791
+ }
2792
+
2793
+ function useAreaEditMode({ iframeRef, onAreaCreatedElement, enabled = false, }) {
2308
2794
  const [hoveredElement, setHoveredElement] = useState(null);
2309
2795
  const [isHovering, setIsHovering] = useState(false);
2796
+ // Use ref to always get latest hoveredElement without causing re-renders
2797
+ const hoveredElementRef = useRef(null);
2798
+ const onAreaCreatedElementRef = useRef(onAreaCreatedElement);
2310
2799
  const { isEditingMode } = useHeatmapAreaClick();
2311
2800
  const iframeDocument = iframeRef.current?.contentDocument;
2312
2801
  const isActive = enabled && isEditingMode;
2802
+ // Keep refs in sync
2803
+ useEffect(() => {
2804
+ hoveredElementRef.current = hoveredElement;
2805
+ }, [hoveredElement]);
2806
+ useEffect(() => {
2807
+ onAreaCreatedElementRef.current = onAreaCreatedElement;
2808
+ }, [onAreaCreatedElement]);
2313
2809
  const handleMouseMove = useCallback((e) => {
2314
2810
  if (!isActive || !iframeDocument)
2315
2811
  return;
2316
- const elements = getElementsAtPoint$1(iframeDocument, e.clientX, e.clientY, isElementSelectable);
2317
- // Find first selectable element
2812
+ const elements = getElementsAtPoint(iframeDocument, e.clientX, e.clientY);
2318
2813
  const selectableElement = elements.find((el, index, arr) => isElementSelectable(el, index, arr));
2319
- if (selectableElement && selectableElement !== hoveredElement) {
2814
+ const isSelectable = selectableElement && selectableElement !== hoveredElement;
2815
+ if (isSelectable) {
2320
2816
  setHoveredElement(selectableElement);
2321
2817
  setIsHovering(true);
2322
- }
2323
- else if (!selectableElement && hoveredElement) {
2324
- setHoveredElement(null);
2325
- setIsHovering(false);
2326
- }
2327
- }, [isActive, iframeDocument, hoveredElement]);
2328
- const handleClick = useCallback((e) => {
2329
- if (!isActive || !hoveredElement)
2330
2818
  return;
2331
- e.stopPropagation();
2332
- e.preventDefault();
2333
- if (onCreateArea) {
2334
- onCreateArea(hoveredElement);
2335
2819
  }
2336
- }, [isActive, hoveredElement]);
2820
+ setHoveredElement(null);
2821
+ setIsHovering(false);
2822
+ }, [isActive, iframeDocument, hoveredElement]);
2337
2823
  const handleMouseLeave = useCallback(() => {
2338
2824
  setHoveredElement(null);
2339
2825
  setIsHovering(false);
2340
2826
  }, []);
2827
+ const handleClick = useCallback((e) => {
2828
+ const currentHoveredElement = hoveredElementRef.current;
2829
+ const currentCallback = onAreaCreatedElementRef.current;
2830
+ if (!isActive || !currentHoveredElement)
2831
+ return;
2832
+ e.stopPropagation();
2833
+ e.preventDefault();
2834
+ if (!currentCallback)
2835
+ return;
2836
+ currentCallback(currentHoveredElement);
2837
+ }, [isActive]);
2341
2838
  useEffect(() => {
2342
2839
  if (!isActive || !iframeDocument) {
2343
2840
  setHoveredElement(null);
@@ -2355,17 +2852,17 @@ function useAreaEditMode({ iframeRef, onCreateArea, enabled = false, }) {
2355
2852
  });
2356
2853
  };
2357
2854
  iframeDocument.addEventListener('mousemove', throttledMouseMove);
2358
- iframeDocument.addEventListener('click', handleClick);
2359
- iframeDocument.addEventListener('mouseleave', handleMouseLeave);
2360
2855
  iframeDocument.addEventListener('scroll', handleMouseLeave);
2856
+ iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
2857
+ iframeDocument.addEventListener('click', handleClick);
2361
2858
  return () => {
2362
2859
  if (rafId) {
2363
2860
  cancelAnimationFrame(rafId);
2364
2861
  }
2365
2862
  iframeDocument.removeEventListener('mousemove', throttledMouseMove);
2366
- iframeDocument.removeEventListener('click', handleClick);
2367
2863
  iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
2368
2864
  iframeDocument.removeEventListener('scroll', handleMouseLeave);
2865
+ iframeDocument.removeEventListener('click', handleClick);
2369
2866
  };
2370
2867
  }, [isActive, iframeDocument]);
2371
2868
  return {
@@ -2374,109 +2871,209 @@ function useAreaEditMode({ iframeRef, onCreateArea, enabled = false, }) {
2374
2871
  };
2375
2872
  }
2376
2873
 
2377
- function useAreaScrollSync(options) {
2378
- const { areas, iframeRef, enabled = true } = options;
2874
+ const useAreaFilterVisible = (props) => {
2875
+ const { iframeRef, enableOverlapResolution } = props;
2379
2876
  const iframeDocument = iframeRef.current?.contentDocument;
2877
+ const { areas, setAreas } = useHeatmapAreaClick();
2878
+ const visibleAreas = useMemo(() => {
2879
+ if (!enableOverlapResolution)
2880
+ return areas;
2881
+ if (!iframeDocument)
2882
+ return areas;
2883
+ return getVisibleAreas(areas, iframeDocument);
2884
+ }, [areas, iframeDocument]);
2380
2885
  useEffect(() => {
2381
- if (!enabled || !iframeDocument || areas.length === 0) {
2382
- return;
2383
- }
2384
- let rafId = null;
2385
- let isUpdating = false;
2386
- const updateAreaPositions = () => {
2387
- if (isUpdating)
2388
- return;
2389
- isUpdating = true;
2390
- rafId = requestAnimationFrame(() => {
2391
- areas.forEach((area) => {
2392
- if (!area.element || !area.rect)
2393
- return;
2394
- try {
2395
- const newRect = getElementRect(area.element);
2396
- area.rect.update(newRect);
2397
- }
2398
- catch (error) {
2399
- console.warn('[useAreaScrollSync] Failed to update area rect:', error);
2400
- }
2401
- });
2402
- isUpdating = false;
2403
- rafId = null;
2404
- });
2405
- };
2406
- iframeDocument.addEventListener('scroll', updateAreaPositions, { passive: true });
2407
- const iframeWindow = iframeDocument.defaultView;
2408
- if (iframeWindow) {
2409
- iframeWindow.addEventListener('resize', updateAreaPositions, { passive: true });
2886
+ if (enableOverlapResolution && visibleAreas.length !== areas.length) {
2887
+ setAreas(visibleAreas);
2410
2888
  }
2411
- return () => {
2412
- if (rafId !== null) {
2413
- cancelAnimationFrame(rafId);
2414
- }
2415
- iframeDocument.removeEventListener('scroll', updateAreaPositions);
2416
- if (iframeWindow) {
2417
- iframeWindow.removeEventListener('resize', updateAreaPositions);
2418
- }
2419
- };
2420
- }, [areas, iframeDocument, enabled]);
2421
- }
2889
+ }, [visibleAreas, areas.length]);
2890
+ return {};
2891
+ };
2422
2892
 
2423
- const useClickmap = () => {
2893
+ function useAreaHydration(options) {
2894
+ const { shadowRoot, enabled = true } = options;
2895
+ const [isInitializing, setIsInitializing] = useState(false);
2896
+ const { dataInfo, clickAreas } = useHeatmapData();
2424
2897
  const { vizRef } = useHeatmapViz();
2425
- const { clickmap } = useHeatmapData();
2426
- const start = useCallback(() => {
2427
- if (!vizRef || !clickmap || clickmap.length === 0)
2898
+ const { areas, setAreas } = useHeatmapAreaClick();
2899
+ const hydratePersistedAreas = useCallback(() => {
2900
+ if (isInitializing)
2901
+ return;
2902
+ if (!vizRef)
2903
+ return;
2904
+ if (!clickAreas)
2905
+ return;
2906
+ if (!dataInfo)
2907
+ return;
2908
+ logger$3.info(`Hydrating ${clickAreas.length} persisted areas...`);
2909
+ const hydratedAreas = hydrateAreas({ clickAreas, heatmapInfo: dataInfo, vizRef, shadowRoot });
2910
+ if (!hydratedAreas?.length) {
2911
+ logger$3.warn('No areas could be hydrated - all elements may have been removed from DOM');
2428
2912
  return;
2429
- try {
2430
- vizRef?.clearmap?.();
2431
- vizRef?.clickmap?.(clickmap);
2432
- }
2433
- catch (error) {
2434
- console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
2435
2913
  }
2436
- }, [vizRef, clickmap]);
2437
- return { start };
2438
- };
2914
+ setIsInitializing(true);
2915
+ buildAreaGraph(hydratedAreas);
2916
+ setAreas(hydratedAreas);
2917
+ logger$3.info(`Successfully hydrated ${hydratedAreas.length} areas`);
2918
+ }, [dataInfo, vizRef, isInitializing, clickAreas]);
2919
+ useEffect(() => {
2920
+ if (!enabled)
2921
+ return;
2922
+ if (!dataInfo)
2923
+ return;
2924
+ if (!clickAreas)
2925
+ return;
2926
+ if (areas.length)
2927
+ return;
2928
+ hydratePersistedAreas();
2929
+ }, [enabled, dataInfo, clickAreas, areas.length, hydratePersistedAreas]);
2930
+ return {
2931
+ hydratePersistedAreas,
2932
+ };
2933
+ }
2439
2934
 
2440
- const useScrollmap = () => {
2441
- const { vizRef } = useHeatmapViz();
2442
- const { scrollmap } = useHeatmapData();
2443
- const start = useCallback(() => {
2444
- // if (isInitialized) return;
2445
- if (!vizRef || !scrollmap || scrollmap.length === 0)
2935
+ function useAreaInteraction(options = {}) {
2936
+ const { onAreaClick } = options;
2937
+ const { selectedArea, hoveredArea, isEditingMode, setSelectedArea, setHoveredArea } = useHeatmapAreaClick();
2938
+ const handleAreaClick = useCallback((area) => {
2939
+ if (isEditingMode)
2446
2940
  return;
2447
- try {
2448
- vizRef?.clearmap?.();
2449
- vizRef?.scrollmap?.(scrollmap);
2450
- // setIsInitialized(true);
2941
+ // Toggle selection
2942
+ setSelectedArea(selectedArea?.id === area.id ? null : area);
2943
+ // Trigger callback
2944
+ if (onAreaClick) {
2945
+ onAreaClick(area);
2451
2946
  }
2452
- catch (error) {
2453
- console.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
2947
+ }, [isEditingMode, selectedArea]);
2948
+ const handleAreaMouseEnter = useCallback((area) => {
2949
+ if (isEditingMode)
2950
+ return;
2951
+ setHoveredArea(area);
2952
+ }, [isEditingMode]);
2953
+ const handleAreaMouseLeave = useCallback((area) => {
2954
+ if (isEditingMode)
2955
+ return;
2956
+ // Only clear if this is the currently hovered area
2957
+ if (hoveredArea?.id === area.id) {
2958
+ setHoveredArea(null);
2454
2959
  }
2455
- }, [vizRef, scrollmap]);
2456
- return { start };
2457
- };
2960
+ }, [isEditingMode, hoveredArea]);
2961
+ return {
2962
+ handleAreaClick,
2963
+ handleAreaMouseEnter,
2964
+ handleAreaMouseLeave,
2965
+ };
2966
+ }
2458
2967
 
2459
- const useHeatmapCanvas = () => {
2460
- const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
2461
- const { start: startClickmap } = useClickmap();
2462
- const { start: startScrollmap } = useScrollmap();
2968
+ function useAreaPortals(options) {
2969
+ const { shadowContainer, isReady, iframeRef, customShadowRoot, onAreaClick, onAreaCreated } = options;
2970
+ const { onAreaCreatedElement } = useAreaCreation({ customShadowRoot, onAreaCreated });
2971
+ const { hoveredElement } = useAreaEditMode({ iframeRef, enabled: true, onAreaCreatedElement });
2972
+ const { areas, selectedArea, hoveredArea, isEditingMode } = useHeatmapAreaClick();
2973
+ const { handleAreaClick, handleAreaMouseEnter, handleAreaMouseLeave } = useAreaInteraction({
2974
+ onAreaClick,
2975
+ });
2976
+ const isReadyPortal = shadowContainer && isReady;
2977
+ const areasPortal = isReadyPortal
2978
+ ? createPortal(jsx(Fragment, { children: areas.map((area) => (jsx(AreaOverlay, { area: area, onClick: handleAreaClick, onMouseEnter: handleAreaMouseEnter, onMouseLeave: handleAreaMouseLeave, isSelected: selectedArea?.id === area.id, isHovered: hoveredArea?.id === area.id }, area.id))) }), shadowContainer)
2979
+ : null;
2980
+ const isReadyEditHighlight = shadowContainer && isReady && isEditingMode && hoveredElement;
2981
+ const editHighlightPortal = isReadyEditHighlight
2982
+ ? createPortal(jsx(AreaEditHighlight, { element: hoveredElement, shadowRoot: customShadowRoot, onClick: onAreaCreatedElement }), shadowContainer)
2983
+ : null;
2984
+ return {
2985
+ areasPortal: areasPortal,
2986
+ editHighlightPortal: editHighlightPortal,
2987
+ };
2988
+ }
2989
+
2990
+ function useAreaRectSync(options) {
2991
+ const { iframeDocument, shadowRoot, enabled = true } = options;
2992
+ const { vizRef } = useHeatmapViz();
2993
+ const { areas } = useHeatmapAreaClick();
2463
2994
  useEffect(() => {
2464
- switch (heatmapType) {
2465
- case IHeatmapType.Click:
2466
- startClickmap();
2467
- break;
2468
- case IHeatmapType.Scroll:
2469
- startScrollmap();
2470
- break;
2995
+ if (!enabled || !iframeDocument || areas.length === 0) {
2996
+ return;
2471
2997
  }
2472
- }, [heatmapType, startClickmap, startScrollmap]);
2473
- };
2998
+ areas.forEach((area) => {
2999
+ try {
3000
+ let targetElement = area.element;
3001
+ if (!targetElement || !iframeDocument.contains(targetElement)) {
3002
+ if (area.hash) {
3003
+ const elementByHash = vizRef?.get(area.hash);
3004
+ if (elementByHash) {
3005
+ area.element = elementByHash;
3006
+ targetElement = elementByHash;
3007
+ }
3008
+ }
3009
+ }
3010
+ // // If we still can't find the element, set rect to zero
3011
+ // if (!targetElement || !iframeDocument.contains(targetElement)) {
3012
+ // area.rect.update({
3013
+ // width: 0,
3014
+ // height: 0,
3015
+ // top: 0,
3016
+ // left: 0,
3017
+ // absoluteLeft: 0,
3018
+ // absoluteTop: 0,
3019
+ // absoluteRight: 0,
3020
+ // absoluteBottom: 0,
3021
+ // outOfBounds: true,
3022
+ // });
3023
+ // return;
3024
+ // }
3025
+ const newRect = getElementRect(targetElement, shadowRoot);
3026
+ area.rect.update(newRect);
3027
+ }
3028
+ catch (error) {
3029
+ logger$3.error(`Failed to update rect for area ${area.id}:`, error);
3030
+ }
3031
+ });
3032
+ buildAreaGraph(areas);
3033
+ }, [areas, iframeDocument, shadowRoot, enabled, vizRef]);
3034
+ }
2474
3035
 
2475
- const scrollToElementIfNeeded = (visualRef, rect, scale) => {
2476
- if (!visualRef.current)
2477
- return;
2478
- const visualRect = visualRef.current.getBoundingClientRect();
2479
- if (isElementInViewport(rect, visualRef, scale)) {
3036
+ /**
3037
+ * Hook to setup and manage the shadow DOM container for area rendering
3038
+ *
3039
+ * @param iframeDocument - The iframe document
3040
+ * @param customShadowRoot - Optional custom shadow root element
3041
+ * @returns Container element and ready state
3042
+ */
3043
+ function useAreaRendererContainer(iframeDocument, customShadowRoot) {
3044
+ const [shadowContainer, setShadowContainer] = useState(null);
3045
+ const [isReady, setIsReady] = useState(false);
3046
+ const containerRef = useRef(null);
3047
+ useEffect(() => {
3048
+ if (!iframeDocument) {
3049
+ setIsReady(false);
3050
+ return;
3051
+ }
3052
+ const innerContainer = setupAreaRenderingContainer(iframeDocument, customShadowRoot);
3053
+ containerRef.current = innerContainer;
3054
+ setShadowContainer(innerContainer);
3055
+ setIsReady(true);
3056
+ return () => {
3057
+ // Cleanup on unmount
3058
+ const container = innerContainer.parentElement?.parentElement;
3059
+ cleanupAreaRenderingContainer(container);
3060
+ containerRef.current = null;
3061
+ setShadowContainer(null);
3062
+ setIsReady(false);
3063
+ };
3064
+ }, [iframeDocument, customShadowRoot]);
3065
+ return {
3066
+ shadowContainer,
3067
+ isReady,
3068
+ containerRef,
3069
+ };
3070
+ }
3071
+
3072
+ const scrollToElementIfNeeded = (visualRef, rect, scale) => {
3073
+ if (!visualRef.current)
3074
+ return;
3075
+ const visualRect = visualRef.current.getBoundingClientRect();
3076
+ if (isElementInViewport(rect, visualRef, scale)) {
2480
3077
  return;
2481
3078
  }
2482
3079
  const topRaw = rect.top; // - visualRect.top
@@ -2528,7 +3125,7 @@ const useClickedElement = ({ visualRef, getRect }) => {
2528
3125
  requestAnimationFrame(() => {
2529
3126
  setClickedElement(elementInfo);
2530
3127
  });
2531
- }, [selectedElement, dataInfo, visualRef, widthScale]);
3128
+ }, [selectedElement, dataInfo, visualRef, widthScale]); // eslint-disable-line react-hooks/exhaustive-deps
2532
3129
  return { clickedElement, showMissingElement, shouldShowCallout };
2533
3130
  };
2534
3131
 
@@ -2584,7 +3181,7 @@ const useHeatmapEffects = ({ isVisible }) => {
2584
3181
  const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
2585
3182
  const heatmapWidth = useHeatmapConfigStore((state) => state.width);
2586
3183
  const { iframeHeight, widthScale } = useHeatmapViz();
2587
- return useCallback((element) => {
3184
+ const getRect = useCallback((element) => {
2588
3185
  const hash = element?.hash;
2589
3186
  if (!iframeRef.current?.contentDocument || !hash || !visualizer)
2590
3187
  return null;
@@ -2620,6 +3217,7 @@ const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
2620
3217
  outOfBounds,
2621
3218
  };
2622
3219
  }, [iframeRef, wrapperRef, visualizer, heatmapWidth, iframeHeight, widthScale]);
3220
+ return { getRect };
2623
3221
  };
2624
3222
 
2625
3223
  const debounce = (fn, delay) => {
@@ -2630,10 +3228,6 @@ const debounce = (fn, delay) => {
2630
3228
  };
2631
3229
  };
2632
3230
 
2633
- // ===================== UTILITY FUNCTIONS =====================
2634
- /**
2635
- * Lấy bounding box tuyệt đối của element (relative to document)
2636
- */
2637
3231
  function getBoundingBox(element) {
2638
3232
  if (typeof element.getBoundingClientRect !== 'function') {
2639
3233
  return null;
@@ -2654,84 +3248,72 @@ function getBoundingBox(element) {
2654
3248
  height: Math.floor(rect.height),
2655
3249
  };
2656
3250
  }
2657
- /**
2658
- * Lấy tất cả elements tại tọa độ (x, y), hỗ trợ Shadow DOM
2659
- */
2660
- function getElementsAtPoint(documentOrShadowRoot, x, y, filterFunction, visitedShadowRoots = new Set()) {
2661
- // Lấy tất cả elements tại vị trí
2662
- const elementsAtPoint = documentOrShadowRoot.elementsFromPoint(x, y);
2663
- if (!filterFunction) {
2664
- return elementsAtPoint;
2665
- }
2666
- // Tìm element đầu tiên match với filter
2667
- const matchedElement = elementsAtPoint.find(filterFunction);
2668
- // Nếu element có Shadow DOM và chưa visit -> đệ quy vào
2669
- if (matchedElement?.shadowRoot && !visitedShadowRoots.has(matchedElement.shadowRoot)) {
2670
- visitedShadowRoots.add(matchedElement.shadowRoot);
2671
- return getElementsAtPoint(matchedElement.shadowRoot, x, y, filterFunction, visitedShadowRoots);
2672
- }
2673
- return elementsAtPoint;
2674
- }
2675
- // ===================== EXAMPLE USAGE =====================
2676
- /*
2677
- import { useRef, useState } from 'react';
2678
-
2679
- function HeatmapComponent() {
2680
- const heatmapWrapperRef = useRef<HTMLDivElement>(null);
2681
- const iframeRef = useRef<HTMLIFrameElement>(null);
2682
- const parentRef = useRef<HTMLDivElement>(null);
2683
-
2684
- const [hoveredElement, setHoveredElement] = useState<HoveredElementInfo | null>(null);
2685
-
2686
- const heatmapInfo = {
2687
- width: 1920,
2688
- elementMapInfo: {
2689
- 'hash123': {
2690
- totalclicks: 45,
2691
- selector: 'button.submit'
2692
- }
2693
- },
2694
- sortedElements: [...]
2695
- };
2696
-
2697
- const { handleMouseMove } = useHeatmapMouseHandler({
2698
- heatmapWrapperRef,
2699
- iframeRef,
2700
- parentRef,
2701
- heatmapInfo,
2702
- scaleRatio: 0.8, // 80% zoom
2703
- onElementHover: (info) => {
2704
- setHoveredElement(info);
2705
- console.log('Hovered element:', info);
2706
- }
2707
- });
2708
-
2709
- return (
2710
- <div ref={parentRef}>
2711
- <div
2712
- ref={heatmapWrapperRef}
2713
- onMouseMove={handleMouseMove}
2714
- >
2715
- <iframe ref={iframeRef} />
2716
-
2717
- {hoveredElement && (
2718
- <div className="tooltip" style={{
2719
- position: 'absolute',
2720
- left: hoveredElement.left,
2721
- top: hoveredElement.top
2722
- }}>
2723
- Clicks: {hoveredElement.clicks}
2724
- <br />
2725
- Rank: #{hoveredElement.rank}
2726
- <br />
2727
- Selector: {hoveredElement.selector}
2728
- </div>
2729
- )}
2730
- </div>
2731
- </div>
2732
- );
2733
- }
2734
- */
3251
+ // export function useHeatmapMouseHandler(props: UseHeatmapMouseHandlerProps) {
3252
+ // const { heatmapWrapperRef, iframeRef, parentRef, heatmapInfo, scaleRatio, onElementHover } = props;
3253
+ // const handleMouseMove = useCallback(
3254
+ // (event: MouseEvent) => {
3255
+ // if (
3256
+ // !heatmapWrapperRef?.current ||
3257
+ // !iframeRef?.current ||
3258
+ // !iframeRef.current.contentDocument ||
3259
+ // !heatmapInfo?.elementMapInfo ||
3260
+ // !parentRef?.current
3261
+ // ) {
3262
+ // return;
3263
+ // }
3264
+ // try {
3265
+ // // Calculate scroll position (scaled)
3266
+ // const scrollTop = parentRef.current.scrollTop / scaleRatio;
3267
+ // // Get position of heatmap wrapper
3268
+ // const wrapperRect = heatmapWrapperRef.current.getBoundingClientRect();
3269
+ // // Calculate mouse position in iframe (scaled)
3270
+ // const mouseX = (event.clientX - wrapperRect.left) / scaleRatio;
3271
+ // const mouseY = (event.clientY - wrapperRect.top) / scaleRatio - scrollTop;
3272
+ // const elementsAtPoint = getElementsAtPoint(
3273
+ // iframeRef.current.contentDocument,
3274
+ // Math.round(mouseX),
3275
+ // Math.round(mouseY),
3276
+ // {
3277
+ // filterFn: (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE),
3278
+ // ignoreCanvas: true,
3279
+ // },
3280
+ // );
3281
+ // if (!elementsAtPoint || elementsAtPoint.length === 0) {
3282
+ // return;
3283
+ // }
3284
+ // for (let i = 0; i < elementsAtPoint.length; i++) {
3285
+ // const element = elementsAtPoint[i] as HTMLElement;
3286
+ // const elementHash = element.getAttribute(HEATMAP_ELEMENT_ATTRIBUTE);
3287
+ // if (elementHash && heatmapInfo.elementMapInfo[elementHash]) {
3288
+ // const elementData = heatmapInfo.elementMapInfo[elementHash];
3289
+ // const boundingBox = getBoundingBox(element);
3290
+ // if (boundingBox) {
3291
+ // const rank =
3292
+ // Array.isArray(heatmapInfo.sortedElements) && elementData
3293
+ // ? heatmapInfo.sortedElements.indexOf(elementData) + 1
3294
+ // : NaN;
3295
+ // onElementHover({
3296
+ // ...boundingBox,
3297
+ // width: Math.min(boundingBox.width, heatmapInfo.width || 0),
3298
+ // top: boundingBox.top + scrollTop,
3299
+ // // Metadata
3300
+ // hash: elementHash,
3301
+ // clicks: elementData.totalclicks,
3302
+ // rank: rank,
3303
+ // selector: elementData.selector || '',
3304
+ // });
3305
+ // break;
3306
+ // }
3307
+ // }
3308
+ // }
3309
+ // } catch (error) {
3310
+ // console.warn('Error handling mouse move on heatmap:', error);
3311
+ // }
3312
+ // },
3313
+ // [heatmapWrapperRef, iframeRef, parentRef, heatmapInfo, scaleRatio, onElementHover],
3314
+ // );
3315
+ // return { handleMouseMove };
3316
+ // }
2735
3317
 
2736
3318
  const useHoveredElement = ({ iframeRef, getRect }) => {
2737
3319
  const { hoveredElement, setHoveredElement, setSelectedElement } = useHeatmapClick();
@@ -2814,12 +3396,14 @@ const convertViewportToIframeCoords = (clientX, clientY, iframeRect, scale) => {
2814
3396
  return { x, y };
2815
3397
  };
2816
3398
  const findTargetElement = (doc, x, y, heatmapInfo) => {
2817
- const HEATMAP_ELEMENT_ATTRIBUTE = 'data-clarity-hashalpha';
2818
- const elementsAtPoint = getElementsAtPoint(doc, Math.round(x), Math.round(y), (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE));
3399
+ const elementsAtPoint = getElementsAtPoint(doc, Math.round(x), Math.round(y), {
3400
+ filterFn: (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE),
3401
+ ignoreCanvas: true,
3402
+ });
2819
3403
  let dataElement = null;
2820
3404
  for (let i = 0; i < elementsAtPoint.length; i++) {
2821
3405
  const element = elementsAtPoint[i];
2822
- const elementHash = element.getAttribute(HEATMAP_ELEMENT_ATTRIBUTE);
3406
+ const elementHash = getElementHash(element);
2823
3407
  if (elementHash && heatmapInfo.elementMapInfo?.[elementHash]) {
2824
3408
  const boundingBox = getBoundingBox(element);
2825
3409
  if (boundingBox) {
@@ -2849,6 +3433,177 @@ const isValidElement = (element, heatmapInfo) => {
2849
3433
  return !!heatmapInfo?.elementMapInfo?.[hash];
2850
3434
  };
2851
3435
 
3436
+ function useAreaScrollSync(options) {
3437
+ const { iframeRef, visualRef, enabled = true } = options;
3438
+ const { widthScale } = useHeatmapViz();
3439
+ const { areas, selectedArea } = useHeatmapAreaClick();
3440
+ const iframeDocument = iframeRef.current?.contentDocument;
3441
+ useEffect(() => {
3442
+ if (!enabled || !iframeDocument || areas.length === 0) {
3443
+ return;
3444
+ }
3445
+ let rafId = null;
3446
+ let isUpdating = false;
3447
+ const updateAreaPositions = () => {
3448
+ if (isUpdating)
3449
+ return;
3450
+ isUpdating = true;
3451
+ rafId = requestAnimationFrame(() => {
3452
+ areas.forEach((area) => {
3453
+ if (!area.element || !area.rect)
3454
+ return;
3455
+ try {
3456
+ const newRect = getElementRect(area.element);
3457
+ area.rect.update(newRect);
3458
+ }
3459
+ catch (error) {
3460
+ console.warn('[useAreaScrollSync] Failed to update area rect:', error);
3461
+ }
3462
+ });
3463
+ isUpdating = false;
3464
+ rafId = null;
3465
+ });
3466
+ };
3467
+ iframeDocument.addEventListener('scroll', updateAreaPositions, { passive: true });
3468
+ const iframeWindow = iframeDocument.defaultView;
3469
+ if (iframeWindow) {
3470
+ iframeWindow.addEventListener('resize', updateAreaPositions, { passive: true });
3471
+ }
3472
+ return () => {
3473
+ if (rafId !== null) {
3474
+ cancelAnimationFrame(rafId);
3475
+ }
3476
+ iframeDocument.removeEventListener('scroll', updateAreaPositions);
3477
+ if (iframeWindow) {
3478
+ iframeWindow.removeEventListener('resize', updateAreaPositions);
3479
+ }
3480
+ };
3481
+ }, [areas, iframeDocument, enabled]);
3482
+ useEffect(() => {
3483
+ if (!selectedArea)
3484
+ return;
3485
+ if (!visualRef)
3486
+ return;
3487
+ if (!selectedArea.rect.value)
3488
+ return;
3489
+ scrollToElementIfNeeded(visualRef, selectedArea.rect.value, widthScale);
3490
+ }, [visualRef, selectedArea, widthScale]);
3491
+ }
3492
+
3493
+ const useAreaTopAutoDetect = (props) => {
3494
+ const { autoCreateTopN, shadowRoot, disabled = false } = props;
3495
+ const [isInitializing, setIsInitializing] = useState(disabled);
3496
+ const { dataInfo, clickAreas } = useHeatmapData();
3497
+ const { vizRef } = useHeatmapViz();
3498
+ const { areas, addArea } = useHeatmapAreaClick();
3499
+ useEffect(() => {
3500
+ if (isInitializing)
3501
+ return;
3502
+ if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks)
3503
+ return;
3504
+ if (!autoCreateTopN)
3505
+ return;
3506
+ if (clickAreas?.length)
3507
+ return;
3508
+ if (areas?.length)
3509
+ return;
3510
+ const topElements = getTopElementsByClicks(dataInfo.elementMapInfo, autoCreateTopN);
3511
+ const newAreas = [];
3512
+ topElements.forEach(({ hash }) => {
3513
+ const element = findElementByHash({ hash, vizRef });
3514
+ if (!element)
3515
+ return;
3516
+ const area = buildAreaNode(element, hash, dataInfo);
3517
+ if (!area)
3518
+ return;
3519
+ newAreas.push(area);
3520
+ });
3521
+ buildAreaGraph(newAreas);
3522
+ const areasToAdd = newAreas.filter((area) => {
3523
+ if (area.parentNode) {
3524
+ return false;
3525
+ }
3526
+ return true;
3527
+ });
3528
+ setIsInitializing(true);
3529
+ areasToAdd.forEach((area) => addArea(area));
3530
+ }, [dataInfo, autoCreateTopN, areas.length, shadowRoot, vizRef, isInitializing, clickAreas]); // eslint-disable-line react-hooks/exhaustive-deps
3531
+ return {};
3532
+ };
3533
+
3534
+ const useAreaClickmap = () => {
3535
+ const { vizRef } = useHeatmapViz();
3536
+ const { clickmap } = useHeatmapData();
3537
+ const start = useCallback(() => {
3538
+ if (!vizRef || !clickmap || clickmap.length === 0)
3539
+ return;
3540
+ try {
3541
+ vizRef?.clearmap?.();
3542
+ }
3543
+ catch (error) {
3544
+ console.error(`🚀 🐥 ~ useAreaClickmap ~ error:`, error);
3545
+ }
3546
+ }, [vizRef, clickmap]);
3547
+ return { start };
3548
+ };
3549
+
3550
+ const useClickmap = () => {
3551
+ const { vizRef } = useHeatmapViz();
3552
+ const { clickmap } = useHeatmapData();
3553
+ const start = useCallback(() => {
3554
+ if (!vizRef || !clickmap || clickmap.length === 0)
3555
+ return;
3556
+ try {
3557
+ vizRef?.clearmap?.();
3558
+ vizRef?.clickmap?.(clickmap);
3559
+ }
3560
+ catch (error) {
3561
+ console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
3562
+ }
3563
+ }, [vizRef, clickmap]);
3564
+ return { start };
3565
+ };
3566
+
3567
+ const useScrollmap = () => {
3568
+ const { vizRef } = useHeatmapViz();
3569
+ const { scrollmap } = useHeatmapData();
3570
+ const start = useCallback(() => {
3571
+ if (!vizRef || !scrollmap || scrollmap.length === 0)
3572
+ return;
3573
+ try {
3574
+ vizRef?.clearmap?.();
3575
+ vizRef?.scrollmap?.(scrollmap);
3576
+ }
3577
+ catch (error) {
3578
+ logger$3.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
3579
+ }
3580
+ }, [vizRef, scrollmap]);
3581
+ return { start };
3582
+ };
3583
+
3584
+ const useHeatmapCanvas = () => {
3585
+ const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
3586
+ const clickMode = useHeatmapConfigStore((state) => state.clickMode);
3587
+ const { start: startClickmap } = useClickmap();
3588
+ const { start: startAreaClickmap } = useAreaClickmap();
3589
+ const { start: startScrollmap } = useScrollmap();
3590
+ useEffect(() => {
3591
+ switch (heatmapType) {
3592
+ case IHeatmapType.Click:
3593
+ if (clickMode === IClickMode.Default) {
3594
+ startClickmap();
3595
+ }
3596
+ else {
3597
+ startAreaClickmap();
3598
+ }
3599
+ break;
3600
+ case IHeatmapType.Scroll:
3601
+ startScrollmap();
3602
+ break;
3603
+ }
3604
+ }, [heatmapType, clickMode, startClickmap, startAreaClickmap, startScrollmap]);
3605
+ };
3606
+
2852
3607
  var MessageType;
2853
3608
  (function (MessageType) {
2854
3609
  MessageType["GX_DOM_TRACKING_PAYLOAD"] = "GX_DOM_TRACKING_PAYLOAD";
@@ -3000,6 +3755,7 @@ payloads, onSuccess) {
3000
3755
  targetWidth: docWidth,
3001
3756
  targetHeight: docHeight,
3002
3757
  iframe: iframe,
3758
+ debug: false,
3003
3759
  onSuccess: (data) => {
3004
3760
  iframe.height = `${data.height}px`;
3005
3761
  onSuccess(data.height);
@@ -3137,7 +3893,7 @@ const useReplayRender = () => {
3137
3893
  };
3138
3894
  };
3139
3895
 
3140
- const useHeatmapVizRender = (mode) => {
3896
+ const useHeatmapRenderByMode = (mode) => {
3141
3897
  const heatmapResult = useMemo(() => {
3142
3898
  switch (mode) {
3143
3899
  case 'heatmap':
@@ -3178,7 +3934,7 @@ const useContainerDimensions = (props) => {
3178
3934
  return { containerWidth, containerHeight };
3179
3935
  };
3180
3936
 
3181
- const useContentDimensions = ({ iframeRef, }) => {
3937
+ const useContentDimensions = ({ iframeRef }) => {
3182
3938
  const contentWidth = useHeatmapConfigStore((state) => state.width);
3183
3939
  useEffect(() => {
3184
3940
  if (!contentWidth)
@@ -3191,8 +3947,8 @@ const useContentDimensions = ({ iframeRef, }) => {
3191
3947
  };
3192
3948
 
3193
3949
  const useObserveIframeHeight = (props) => {
3194
- const { iframeRef, isRenderViz } = props;
3195
- const { setIframeHeight } = useHeatmapViz();
3950
+ const { iframeRef } = props;
3951
+ const { iframeHeight, setIframeHeight, isRenderViz } = useHeatmapViz();
3196
3952
  const resizeObserverRef = useRef(null);
3197
3953
  const mutationObserverRef = useRef(null);
3198
3954
  const debounceTimerRef = useRef(null);
@@ -3250,7 +4006,7 @@ const useObserveIframeHeight = (props) => {
3250
4006
  }, [updateIframeHeight]);
3251
4007
  useEffect(() => {
3252
4008
  const iframe = iframeRef.current;
3253
- if (!iframe || !isRenderViz)
4009
+ if (!iframe || !iframeHeight || !isRenderViz)
3254
4010
  return;
3255
4011
  const setupObservers = () => {
3256
4012
  try {
@@ -3312,7 +4068,7 @@ const useObserveIframeHeight = (props) => {
3312
4068
  }
3313
4069
  iframe.removeEventListener('load', setupObservers);
3314
4070
  };
3315
- }, [iframeRef, isRenderViz, updateIframeHeight, debouncedUpdate, immediateUpdate]);
4071
+ }, [iframeRef, iframeHeight, isRenderViz, updateIframeHeight, debouncedUpdate, immediateUpdate]);
3316
4072
  return {};
3317
4073
  };
3318
4074
 
@@ -3361,7 +4117,7 @@ const useScaleCalculation = (props) => {
3361
4117
  return { widthScale, isScaledToFit, minZoomRatio };
3362
4118
  };
3363
4119
 
3364
- const useScrollSync = ({ widthScale, iframeRef, }) => {
4120
+ const useScrollSync = ({ widthScale, iframeRef }) => {
3365
4121
  const handleScroll = useCallback((scrollTop) => {
3366
4122
  const iframe = iframeRef.current;
3367
4123
  if (!iframe || widthScale <= 0)
@@ -3382,13 +4138,13 @@ const useScrollSync = ({ widthScale, iframeRef, }) => {
3382
4138
  };
3383
4139
 
3384
4140
  const useHeatmapScale = (props) => {
3385
- const { wrapperRef, iframeRef, iframeHeight, isRenderViz } = props;
4141
+ const { wrapperRef, iframeRef, iframeHeight } = props;
3386
4142
  // 1. Observe container dimensions
3387
4143
  const { containerWidth, containerHeight } = useContainerDimensions({ wrapperRef });
3388
4144
  // 2. Get content dimensions from config
3389
4145
  const { contentWidth } = useContentDimensions({ iframeRef });
3390
4146
  // 3. Observe iframe height (now reacts to width changes)
3391
- useObserveIframeHeight({ iframeRef, isRenderViz });
4147
+ useObserveIframeHeight({ iframeRef });
3392
4148
  // 4. Calculate scale
3393
4149
  const { widthScale } = useScaleCalculation({
3394
4150
  containerWidth,
@@ -3401,8 +4157,6 @@ const useHeatmapScale = (props) => {
3401
4157
  const scaledHeight = iframeHeight * widthScale;
3402
4158
  const scaledWidth = contentWidth * widthScale;
3403
4159
  return {
3404
- containerWidth,
3405
- containerHeight,
3406
4160
  scaledWidth,
3407
4161
  scaledHeight,
3408
4162
  handleScroll,
@@ -3583,10 +4337,10 @@ const useScrollmapZones = (options) => {
3583
4337
  const newZones = createZones(scrollmap);
3584
4338
  setZones(newZones);
3585
4339
  setIsReady(true);
3586
- console.log(`[useScrollmap] Created ${newZones.length} zones in ${mode} mode`);
4340
+ logger$3.log(`[useScrollmap] Created ${newZones.length} zones in ${mode} mode`);
3587
4341
  }
3588
4342
  catch (error) {
3589
- console.error('[useScrollmap] Error:', error);
4343
+ logger$3.error('[useScrollmap] Error:', error);
3590
4344
  setIsReady(false);
3591
4345
  }
3592
4346
  }, [enabled, scrollmap, mode, createZones]);
@@ -3721,12 +4475,8 @@ class PerformanceLogger {
3721
4475
  const totalRenders = renderMetrics.length;
3722
4476
  const totalHookCalls = hookMetrics.length;
3723
4477
  const totalStoreUpdates = storeMetrics.length;
3724
- const renderDurations = renderMetrics
3725
- .filter((m) => m.duration)
3726
- .map((m) => m.duration);
3727
- const averageRenderTime = renderDurations.length > 0
3728
- ? renderDurations.reduce((a, b) => a + b, 0) / renderDurations.length
3729
- : 0;
4478
+ const renderDurations = renderMetrics.filter((m) => m.duration).map((m) => m.duration);
4479
+ const averageRenderTime = renderDurations.length > 0 ? renderDurations.reduce((a, b) => a + b, 0) / renderDurations.length : 0;
3730
4480
  // View-specific metrics
3731
4481
  const viewMetrics = {};
3732
4482
  this.metrics.forEach((metric) => {
@@ -4253,352 +5003,50 @@ const VizContainer = ({ children, isActive = false }) => {
4253
5003
  }, children: children }), jsx(PopoverSidebar, {})] }));
4254
5004
  };
4255
5005
 
4256
- /**
4257
- * Controls for area click feature - toggle edit mode, clear areas, etc.
4258
- */
4259
- const AreaControls = ({ className, style }) => {
4260
- const { isEditingMode, setIsEditingMode, areas, clearAreas } = useHeatmapAreaClick();
4261
- return (jsxs("div", { className: className, style: {
4262
- display: 'flex',
4263
- gap: '8px',
4264
- alignItems: 'center',
4265
- ...style,
4266
- }, children: [jsx("button", { onClick: () => setIsEditingMode(!isEditingMode), style: {
4267
- padding: '8px 16px',
4268
- borderRadius: '4px',
4269
- border: '1px solid #ccc',
4270
- backgroundColor: isEditingMode ? '#0078D4' : 'white',
4271
- color: isEditingMode ? 'white' : '#161514',
4272
- cursor: 'pointer',
4273
- fontWeight: 500,
4274
- transition: 'all 0.2s',
4275
- }, children: isEditingMode ? 'Exit Edit Mode' : 'Edit Areas' }), areas.length > 0 && (jsxs(Fragment, { children: [jsxs("span", { style: { color: '#605E5C', fontSize: '14px' }, children: [areas.length, " area", areas.length !== 1 ? 's' : ''] }), jsx("button", { onClick: () => {
4276
- if (confirm(`Clear all ${areas.length} areas?`)) {
4277
- clearAreas();
4278
- }
4279
- }, style: {
4280
- padding: '8px 16px',
4281
- borderRadius: '4px',
4282
- border: '1px solid #ccc',
4283
- backgroundColor: 'white',
4284
- color: '#A4262C',
4285
- cursor: 'pointer',
4286
- fontWeight: 500,
4287
- transition: 'all 0.2s',
4288
- }, children: "Clear All" })] }))] }));
4289
- };
4290
- AreaControls.displayName = 'AreaControls';
4291
-
4292
- const AreaEditHighlight = ({ element, shadowRoot, onClick, }) => {
4293
- const [rect, setRect] = useState(null);
4294
- const highlightRef = useRef(null);
4295
- useEffect(() => {
4296
- if (!element) {
4297
- setRect(null);
4298
- return;
4299
- }
4300
- // Calculate element position
4301
- const elementRect = getElementRect(element);
4302
- setRect(elementRect);
4303
- }, [element, shadowRoot]);
4304
- if (!rect) {
4305
- return null;
4306
- }
4307
- const handleClick = (e) => {
4308
- if (element && onClick) {
4309
- e.stopPropagation();
4310
- e.preventDefault();
4311
- onClick(element);
4312
- }
4313
- };
4314
- return (jsx("div", { ref: highlightRef, id: AREA_HOVER_ELEMENT_ID, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: handleClick, style: {
4315
- position: 'absolute',
4316
- top: `${rect.absoluteTop}px`,
4317
- left: `${rect.absoluteLeft}px`,
4318
- width: `${rect.width}px`,
4319
- height: `${rect.height}px`,
4320
- zIndex: Number.MAX_SAFE_INTEGER,
4321
- boxShadow: AREA_HOVER_BOX_SHADOW,
4322
- backgroundColor: 'rgba(128, 128, 128, 0.4)',
4323
- backgroundImage: 'repeating-linear-gradient(135deg, transparent, transparent 35px, rgba(255,255,255,.5) 35px, rgba(255,255,255,.5) 70px)',
4324
- pointerEvents: 'auto',
4325
- cursor: 'pointer',
4326
- boxSizing: 'border-box',
4327
- } }));
4328
- };
4329
- AreaEditHighlight.displayName = 'AreaEditHighlight';
4330
-
4331
- const AreaLabel = ({ clickDist, totalClicks, kind }) => {
4332
- if (kind === 'money') {
4333
- return null;
4334
- }
4335
- return (jsxs("div", { style: {
4336
- color: '#161514',
4337
- backgroundColor: 'rgba(255, 255, 255, 0.86)',
4338
- display: 'flex',
4339
- flexDirection: 'column',
4340
- alignItems: 'center',
4341
- padding: '8px',
4342
- borderRadius: '4px',
4343
- fontSize: '16px',
4344
- lineHeight: '20px',
4345
- minWidth: '56px',
4346
- fontWeight: 600,
4347
- fontFamily: '"Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif',
4348
- pointerEvents: 'none',
4349
- }, children: [jsxs("span", { children: [clickDist.toFixed(2), "%"] }), jsxs("span", { style: { fontSize: '12px', fontWeight: 400, opacity: 0.8 }, children: [totalClicks, " clicks"] })] }));
4350
- };
4351
- AreaLabel.displayName = 'AreaLabel';
4352
-
4353
- const AreaOverlay = ({ area, onClick, onMouseEnter, onMouseLeave, isSelected, isHovered, }) => {
4354
- const [rect, setRect] = useState(area.rect.value);
4355
- useEffect(() => {
4356
- const handleRectChange = (newRect) => {
4357
- if (newRect) {
4358
- setRect(newRect);
4359
- }
4360
- };
4361
- area.rect.observe(handleRectChange);
4362
- return () => {
4363
- area.rect.unobserve(handleRectChange);
4364
- };
4365
- }, [area.rect]);
4366
- if (!rect)
4367
- return null;
4368
- const position = area.isFixed ? 'fixed' : 'absolute';
4369
- const showLabel = !isRectTooSmallForLabel(rect);
4370
- const backgroundColor = isHovered ? area.hoverColor : area.color;
4371
- const boxShadow = isSelected
4372
- ? '0 0 0 3px #0078d4 inset'
4373
- : isHovered
4374
- ? AREA_HOVER_BOX_SHADOW
4375
- : '0 0 0 2px white inset';
4376
- return (jsx("div", { id: `area-${area.id}`, "data-area-id": area.id, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: () => onClick?.(area), onMouseEnter: () => onMouseEnter?.(area), onMouseLeave: () => onMouseLeave?.(area), style: {
4377
- position,
4378
- top: `${rect.top}px`,
4379
- left: `${rect.left}px`,
4380
- width: `${rect.width}px`,
4381
- height: `${rect.height}px`,
4382
- backgroundColor,
4383
- boxShadow,
4384
- boxSizing: 'border-box',
4385
- display: 'flex',
4386
- alignItems: 'center',
4387
- justifyContent: 'center',
4388
- cursor: 'pointer',
4389
- transition: 'background-color 0.2s, box-shadow 0.2s',
4390
- pointerEvents: 'auto',
4391
- }, children: showLabel && (jsx(AreaLabel, { clickDist: area.clickDist, totalClicks: area.totalclicks, kind: area.kind })) }));
4392
- };
4393
-
4394
5006
  function useAreaRenderer(options) {
4395
- const { iframeRef, shadowRoot: customShadowRoot, onAreaCreated, onAreaClick } = options;
4396
- const iframeDocument = iframeRef.current?.contentDocument;
4397
- // Get heatmap data for building areas
4398
- const { dataInfo } = useHeatmapData();
4399
- const { areas, selectedArea, hoveredArea, isEditingMode, setSelectedArea, setHoveredArea, addArea, } = useHeatmapAreaClick();
4400
- const [shadowContainer, setShadowContainer] = useState(null);
4401
- const [isReady, setIsReady] = useState(false);
4402
- const containerRef = useRef(null);
4403
- useEffect(() => {
4404
- if (!iframeDocument) {
4405
- setIsReady(false);
4406
- return;
4407
- }
4408
- let container = iframeDocument.querySelector(`[${AREA_MAP_DIV_ATTRIBUTE}]`);
4409
- if (!container) {
4410
- container = iframeDocument.createElement('div');
4411
- container.setAttribute(AREA_MAP_DIV_ATTRIBUTE, 'true');
4412
- container.style.cssText = `
4413
- position: absolute;
4414
- top: 0;
4415
- left: 0;
4416
- width: 100%;
4417
- height: 100%;
4418
- pointer-events: none;
4419
- z-index: 999999;
4420
- `;
4421
- // Append to custom shadow root or body
4422
- const targetRoot = customShadowRoot || iframeDocument.body;
4423
- if (targetRoot) {
4424
- targetRoot.appendChild(container);
4425
- }
4426
- }
4427
- // Create shadow root if needed
4428
- let shadowRoot;
4429
- if (!container.shadowRoot) {
4430
- shadowRoot = getOrCreateShadowRoot(container);
4431
- }
4432
- else {
4433
- shadowRoot = container.shadowRoot;
4434
- }
4435
- // Create inner container for React portal
4436
- let innerContainer = shadowRoot.querySelector('.heatmap-area-container');
4437
- if (!innerContainer) {
4438
- innerContainer = iframeDocument.createElement('div');
4439
- innerContainer.className = 'heatmap-area-container';
4440
- innerContainer.style.cssText = `
4441
- position: relative;
4442
- width: 100%;
4443
- height: 100%;
4444
- `;
4445
- shadowRoot.appendChild(innerContainer);
4446
- }
4447
- containerRef.current = innerContainer;
4448
- setShadowContainer(innerContainer);
4449
- setIsReady(true);
4450
- return () => {
4451
- if (container && container.parentNode) {
4452
- container.parentNode.removeChild(container);
4453
- }
4454
- containerRef.current = null;
4455
- setShadowContainer(null);
4456
- setIsReady(false);
4457
- };
4458
- }, [iframeDocument, customShadowRoot]);
4459
- const handleCreateAreaFromElement = useCallback((element) => {
4460
- if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks) {
4461
- console.warn('[useAreaRenderer] Cannot create area: missing heatmap data');
4462
- return;
4463
- }
4464
- const hash = getElementHash(element);
4465
- if (!hash) {
4466
- console.warn('[useAreaRenderer] Cannot create area: missing hash');
4467
- return;
4468
- }
4469
- const alreadyExists = areas.some((area) => area.hash === hash);
4470
- if (alreadyExists) {
4471
- console.warn(`[useAreaRenderer] Area already exists for element: ${hash}`);
4472
- return;
4473
- }
4474
- try {
4475
- const area = buildAreaNode(element, hash, dataInfo.elementMapInfo, dataInfo.totalClicks, customShadowRoot);
4476
- addArea(area);
4477
- if (onAreaCreated) {
4478
- onAreaCreated(area);
4479
- }
4480
- console.log('[useAreaRenderer] Area created:', {
4481
- hash,
4482
- selector: area.selector,
4483
- clicks: area.totalclicks,
4484
- clickDist: area.clickDist,
4485
- });
4486
- }
4487
- catch (error) {
4488
- console.error('[useAreaRenderer] Failed to create area:', error);
4489
- }
4490
- }, [dataInfo, areas, customShadowRoot, addArea]);
4491
- const { hoveredElement } = useAreaEditMode({
4492
- iframeRef,
4493
- enabled: isEditingMode,
4494
- onCreateArea: handleCreateAreaFromElement,
4495
- });
4496
- useAreaScrollSync({
4497
- areas,
5007
+ const { iframeRef, visualRef, shadowRoot, onAreaCreated, onAreaClick } = options;
5008
+ const iframeDocument = iframeRef.current?.contentDocument || undefined;
5009
+ const { shadowContainer, isReady } = useAreaRendererContainer(iframeDocument, shadowRoot);
5010
+ const { areasPortal, editHighlightPortal } = useAreaPortals({
4498
5011
  iframeRef,
4499
- enabled: isReady,
5012
+ shadowContainer,
5013
+ isReady,
5014
+ onAreaClick,
5015
+ onAreaCreated,
4500
5016
  });
4501
- const handleAreaClick = (area) => {
4502
- if (isEditingMode)
4503
- return;
4504
- setSelectedArea(selectedArea?.id === area.id ? null : area);
4505
- if (onAreaClick) {
4506
- onAreaClick(area);
4507
- }
4508
- };
4509
- const handleAreaMouseEnter = (area) => {
4510
- if (isEditingMode)
4511
- return;
4512
- setHoveredArea(area);
4513
- };
4514
- const handleAreaMouseLeave = (area) => {
4515
- if (isEditingMode)
4516
- return;
4517
- if (hoveredArea?.id === area.id) {
4518
- setHoveredArea(null);
4519
- }
4520
- };
4521
- const areasPortal = shadowContainer && isReady
4522
- ? createPortal(jsx(Fragment, { children: areas.map((area) => (jsx(AreaOverlay, { area: area, onClick: handleAreaClick, onMouseEnter: handleAreaMouseEnter, onMouseLeave: handleAreaMouseLeave, isSelected: selectedArea?.id === area.id, isHovered: hoveredArea?.id === area.id }, area.id))) }), shadowContainer)
4523
- : null;
4524
- const editHighlightPortal = shadowContainer && isReady && isEditingMode && hoveredElement
4525
- ? createPortal(jsx(AreaEditHighlight, { element: hoveredElement, shadowRoot: customShadowRoot, onClick: handleCreateAreaFromElement }), shadowContainer)
4526
- : null;
5017
+ useAreaRectSync({ iframeDocument, shadowRoot, enabled: isReady });
5018
+ useAreaScrollSync({ iframeRef, visualRef, enabled: isReady });
4527
5019
  return {
4528
- areasPortal: areasPortal,
4529
- editHighlightPortal: editHighlightPortal,
4530
- shadowContainer,
5020
+ areasPortal,
5021
+ editHighlightPortal,
4531
5022
  isReady,
4532
5023
  };
4533
5024
  }
4534
5025
 
4535
- const VizAreaClick = ({ iframeRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true, renderControls, onAreaClick, }) => {
4536
- const iframeDocument = iframeRef.current?.contentDocument;
4537
- const { dataInfo } = useHeatmapData();
4538
- const { areas, isEditingMode, setIsEditingMode, addArea, clearAreas, setAreas } = useHeatmapAreaClick();
5026
+ const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true, onAreaClick, }) => {
5027
+ const { clickAreas } = useHeatmapData();
5028
+ const { resetView } = useHeatmapAreaClick();
4539
5029
  const { areasPortal, editHighlightPortal, isReady } = useAreaRenderer({
4540
5030
  iframeRef,
5031
+ visualRef,
4541
5032
  shadowRoot,
4542
5033
  onAreaClick,
4543
5034
  });
4544
- // Auto-create areas from top elements
4545
- useEffect(() => {
4546
- if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks)
4547
- return;
4548
- if (autoCreateTopN <= 0)
4549
- return;
4550
- if (areas.length > 0)
4551
- return; // Already have areas
4552
- // Get top elements by clicks
4553
- const topElements = getTopElementsByClicks(dataInfo.elementMapInfo, autoCreateTopN);
4554
- // Build area nodes
4555
- const newAreas = [];
4556
- topElements.forEach(({ hash, selector }) => {
4557
- // Find element in DOM
4558
- const element = iframeDocument?.querySelector(selector);
4559
- if (!element || !(element instanceof HTMLElement))
4560
- return;
4561
- const area = buildAreaNode(element, hash, dataInfo.elementMapInfo, dataInfo.totalClicks);
4562
- newAreas.push(area);
4563
- });
4564
- // Add all areas
4565
- newAreas.forEach((area) => addArea(area));
4566
- }, [dataInfo, autoCreateTopN, areas.length, iframeDocument, shadowRoot]);
4567
- // Apply overlap resolution
4568
- const visibleAreas = useMemo(() => {
4569
- if (!enableOverlapResolution)
4570
- return areas;
4571
- if (!iframeDocument)
4572
- return areas;
4573
- return getVisibleAreas(areas, iframeDocument);
4574
- }, [areas, iframeDocument]);
4575
- // Update visible areas in store when resolution changes
5035
+ useAreaTopAutoDetect({ autoCreateTopN, shadowRoot, disabled: !!clickAreas?.length });
5036
+ useAreaFilterVisible({ iframeRef, enableOverlapResolution });
5037
+ useAreaHydration({ shadowRoot });
4576
5038
  useEffect(() => {
4577
- if (enableOverlapResolution && visibleAreas.length !== areas.length) {
4578
- setAreas(visibleAreas);
4579
- }
4580
- }, [visibleAreas, areas.length]);
4581
- const handleToggleEdit = useCallback(() => {
4582
- setIsEditingMode(!isEditingMode);
4583
- }, [isEditingMode]);
4584
- const handleClearAll = useCallback(() => {
4585
- if (window.confirm(`Clear all ${areas.length} areas?`)) {
4586
- clearAreas();
4587
- }
4588
- }, [areas.length]);
4589
- const controlsElement = renderControls ? (renderControls({
4590
- isEditingMode,
4591
- areasCount: areas.length,
4592
- onToggleEdit: handleToggleEdit,
4593
- onClearAll: handleClearAll,
4594
- })) : (jsx(AreaControls, {}));
5039
+ return () => {
5040
+ resetView();
5041
+ };
5042
+ }, []);
4595
5043
  if (!isReady)
4596
5044
  return null;
4597
- return (jsxs(Fragment, { children: [areasPortal, editHighlightPortal, controlsElement] }));
5045
+ return (jsxs(Fragment, { children: [areasPortal, editHighlightPortal] }));
4598
5046
  };
4599
5047
  VizAreaClick.displayName = 'VizAreaClick';
4600
5048
 
4601
- const RankBadge = ({ index, elementRect, widthScale, clickOnElement, }) => {
5049
+ const RankBadge = ({ index, elementRect, widthScale, clickOnElement }) => {
4602
5050
  const style = calculateRankPosition(elementRect, widthScale);
4603
5051
  return (jsx("div", { className: "gx-hm-rank-badge", style: style, onClick: clickOnElement, children: index }));
4604
5052
  };
@@ -4614,7 +5062,7 @@ const DefaultRankBadges = ({ getRect, hidden }) => {
4614
5062
  const rect = getRect(element);
4615
5063
  if (!rect)
4616
5064
  return null;
4617
- return (jsx(RankBadge, { index: index + 1, elementRect: rect, widthScale: widthScale }, element.hash));
5065
+ return jsx(RankBadge, { index: index + 1, elementRect: rect, widthScale: widthScale }, element.hash);
4618
5066
  }) }));
4619
5067
  };
4620
5068
 
@@ -4686,7 +5134,7 @@ const ElementMissing = ({ show = true }) => {
4686
5134
  }, "aria-live": "assertive", children: "Element not visible on current screen" }));
4687
5135
  };
4688
5136
 
4689
- const ElementOverlay = ({ type, element, onClick, isSecondary, elementId, }) => {
5137
+ const ElementOverlay = ({ type, element, onClick, elementId }) => {
4690
5138
  // useRenderCount('ElementOverlay');
4691
5139
  const { widthScale } = useHeatmapViz();
4692
5140
  if (!element || (element.width === 0 && element.height === 0))
@@ -4711,29 +5159,31 @@ const ELEMENT_CALLOUT = {
4711
5159
  };
4712
5160
  const HeatmapElements = (props) => {
4713
5161
  const viewId = useViewIdContext();
4714
- const { iframeHeight } = useHeatmapViz();
4715
5162
  const clickedElementId = getClickedElementId(viewId, props.isSecondary);
4716
5163
  const hoveredElementId = getHoveredElementId(viewId, props.isSecondary);
4717
- const { iframeRef, wrapperRef, visualRef, visualizer, iframeDimensions, isVisible = true, areDefaultRanksHidden, isSecondary, } = props;
4718
- const getRect = useHeatmapElementPosition({
4719
- iframeRef,
4720
- wrapperRef,
4721
- visualizer,
5164
+ const { iframeDimensions, isVisible = true, areDefaultRanksHidden } = props;
5165
+ const { iframeHeight } = useHeatmapViz();
5166
+ const { getRect } = useHeatmapElementPosition({
5167
+ iframeRef: props.iframeRef,
5168
+ wrapperRef: props.wrapperRef,
5169
+ visualizer: props.visualizer,
4722
5170
  });
4723
5171
  const { clickedElement, showMissingElement, shouldShowCallout } = useClickedElement({
4724
- visualRef,
5172
+ visualRef: props.visualRef,
4725
5173
  getRect,
4726
5174
  });
4727
5175
  const { hoveredElement, handleMouseMove, handleMouseLeave, handleClick } = useHoveredElement({
4728
- iframeRef,
5176
+ iframeRef: props.iframeRef,
4729
5177
  getRect,
4730
5178
  });
4731
- useElementCalloutVisible({ visualRef, getRect });
5179
+ useElementCalloutVisible({ visualRef: props.visualRef, getRect });
4732
5180
  useHeatmapEffects({ isVisible });
4733
5181
  useRenderCount('HeatmapElements');
4734
5182
  if (!isVisible)
4735
5183
  return null;
4736
- return (jsxs("div", { onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, className: "gx-hm-elements", style: { ...iframeDimensions, height: `${iframeHeight}px` }, children: [jsx(ElementMissing, { show: showMissingElement }), jsx(DefaultRankBadges, { getRect: getRect, hidden: areDefaultRanksHidden }), jsx(ElementOverlay, { type: "clicked", element: clickedElement, isSecondary: isSecondary, elementId: clickedElementId }), jsx(ElementOverlay, { type: "hovered", element: hoveredElement, isSecondary: isSecondary, onClick: handleClick, elementId: hoveredElementId }), hoveredElement?.hash !== clickedElement?.hash && hoveredElement && (jsx(ElementCallout, { element: hoveredElement, target: `#${hoveredElementId}`, visualRef: visualRef, ...ELEMENT_CALLOUT })), shouldShowCallout && clickedElement && (jsx(ElementCallout, { element: clickedElement, target: `#${clickedElementId}`, visualRef: visualRef, ...ELEMENT_CALLOUT }))] }));
5184
+ const isShowHoveredElement = hoveredElement && hoveredElement.hash !== clickedElement?.hash;
5185
+ const isShowClickedElement = shouldShowCallout && clickedElement;
5186
+ return (jsxs("div", { onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, className: "gx-hm-elements", style: { ...iframeDimensions, height: `${iframeHeight}px` }, children: [jsx(ElementMissing, { show: showMissingElement }), jsx(DefaultRankBadges, { getRect: getRect, hidden: areDefaultRanksHidden }), jsx(ElementOverlay, { type: "clicked", element: clickedElement, elementId: clickedElementId }), jsx(ElementOverlay, { type: "hovered", element: hoveredElement, elementId: hoveredElementId, onClick: handleClick }), isShowHoveredElement && (jsx(ElementCallout, { element: hoveredElement, target: `#${hoveredElementId}`, visualRef: props.visualRef, ...ELEMENT_CALLOUT })), isShowClickedElement && (jsx(ElementCallout, { element: clickedElement, target: `#${clickedElementId}`, visualRef: props.visualRef, ...ELEMENT_CALLOUT }))] }));
4737
5187
  };
4738
5188
 
4739
5189
  const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
@@ -4759,6 +5209,22 @@ const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
4759
5209
  } }));
4760
5210
  };
4761
5211
 
5212
+ const VizClickmap = ({ iframeRef, visualRef, wrapperRef }) => {
5213
+ const clickMode = useHeatmapConfigStore((state) => state.clickMode);
5214
+ const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
5215
+ const isClickType = heatmapType === IHeatmapType.Click;
5216
+ if (!isClickType)
5217
+ return null;
5218
+ switch (clickMode) {
5219
+ case IClickMode.Default:
5220
+ return jsx(VizElements, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef });
5221
+ case IClickMode.Area:
5222
+ return (jsx(VizAreaClick, { iframeRef: iframeRef, visualRef: visualRef, autoCreateTopN: 10, onAreaClick: (area) => {
5223
+ console.log('area clicked', area);
5224
+ } }));
5225
+ }
5226
+ };
5227
+
4762
5228
  const ScrollMapMinimap = ({ zones, maxUsers }) => {
4763
5229
  const scrollType = useHeatmapConfigStore((state) => state.scrollType);
4764
5230
  const { showMinimap } = useHeatmapScroll();
@@ -4851,7 +5317,7 @@ const TooltipByZone = ({ zone }) => {
4851
5317
  return jsx(BasicTooltipContent, { zone: zone });
4852
5318
  }
4853
5319
  };
4854
- return (jsx("div", { style: { paddingTop: '12px', borderTop: '1px solid #E5E7EB' }, children: contentMarkup() }));
5320
+ return jsx("div", { style: { paddingTop: '12px', borderTop: '1px solid #E5E7EB' }, children: contentMarkup() });
4855
5321
  };
4856
5322
  const BasicTooltipContent = ({ zone }) => {
4857
5323
  if (!zone)
@@ -4861,11 +5327,11 @@ const BasicTooltipContent = ({ zone }) => {
4861
5327
  const MetricsTooltipContent = ({ zone }) => {
4862
5328
  if (!zone)
4863
5329
  return null;
4864
- return (jsxs(Fragment, { children: [jsx("div", { style: { fontWeight: 600, marginBottom: '8px' }, children: zone.label }), jsxs("div", { style: { fontSize: '20px', fontWeight: 700, marginBottom: '8px' }, children: [zone.percUsers.toFixed(2), "% users"] }), zone.metrics && (jsxs("div", { style: { display: 'grid', gap: '6px', fontSize: '13px' }, children: [zone.metrics.revenue !== undefined && (jsx(MetricRow, { label: "Revenue", value: `$${zone.metrics.revenue.toFixed(2)}` })), zone.metrics.conversionRate !== undefined && (jsx(MetricRow, { label: "Conversion", value: `${zone.metrics.conversionRate.toFixed(2)}%` })), zone.metrics.orders !== undefined && (jsx(MetricRow, { label: "Orders", value: zone.metrics.orders.toString() }))] }))] }));
5330
+ return (jsxs(Fragment, { children: [jsx("div", { style: { fontWeight: 600, marginBottom: '8px' }, children: zone.label }), jsxs("div", { style: { fontSize: '20px', fontWeight: 700, marginBottom: '8px' }, children: [zone.percUsers.toFixed(2), "% users"] }), zone.metrics && (jsxs("div", { style: { display: 'grid', gap: '6px', fontSize: '13px' }, children: [zone.metrics.revenue !== undefined && (jsx(MetricRow, { label: "Revenue", value: `$${zone.metrics.revenue.toFixed(2)}` })), zone.metrics.conversionRate !== undefined && (jsx(MetricRow, { label: "Conversion", value: `${zone.metrics.conversionRate.toFixed(2)}%` })), zone.metrics.orders !== undefined && jsx(MetricRow, { label: "Orders", value: zone.metrics.orders.toString() })] }))] }));
4865
5331
  };
4866
5332
  const MetricRow = ({ label, value }) => (jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [jsxs("span", { style: { color: '#605E5C' }, children: [label, ":"] }), jsx("span", { style: { fontWeight: 600 }, children: value })] }));
4867
5333
 
4868
- const HoverZones = ({ iframeRef, wrapperRef, position, currentScrollPercent, }) => {
5334
+ const HoverZones = ({ iframeRef, wrapperRef, position, currentScrollPercent }) => {
4869
5335
  const { scrollmap } = useHeatmapData();
4870
5336
  // const hoveredZone = useHeatmapVizScrollmapStore((state) => state.hoveredZone);
4871
5337
  // const setHoveredZone = useHeatmapVizScrollmapStore((state) => state.setHoveredZone);
@@ -5084,18 +5550,15 @@ const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHe
5084
5550
 
5085
5551
  const VizDomRenderer = ({ mode = 'heatmap' }) => {
5086
5552
  const contentWidth = useHeatmapConfigStore((state) => state.width || 0);
5087
- const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
5088
5553
  const wrapperRef = useRef(null);
5089
5554
  const visualRef = useRef(null);
5090
- const { setSelectedElement } = useHeatmapClick();
5091
- const { iframeHeight, setIframeHeight, isRenderViz } = useHeatmapViz();
5092
- const { iframeRef } = useHeatmapVizRender(mode);
5555
+ const { iframeRef } = useHeatmapRenderByMode(mode);
5556
+ const { iframeHeight } = useHeatmapViz();
5093
5557
  const { scaledHeight, handleScroll } = useHeatmapScale({
5094
5558
  wrapperRef,
5095
5559
  iframeRef,
5096
5560
  visualRef,
5097
5561
  iframeHeight,
5098
- isRenderViz,
5099
5562
  });
5100
5563
  useHeatmapCanvas();
5101
5564
  useRenderCount('VizDomRenderer');
@@ -5103,16 +5566,7 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
5103
5566
  const scrollTop = e.currentTarget.scrollTop;
5104
5567
  handleScroll(scrollTop);
5105
5568
  };
5106
- const cleanUp = () => {
5107
- setIframeHeight(0);
5108
- setSelectedElement(null);
5109
- };
5110
- useEffect(() => {
5111
- return cleanUp;
5112
- }, []);
5113
- return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [heatmapType === IHeatmapType.Click && (jsx(VizElements, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef })), heatmapType === IHeatmapType.ClickArea && (jsx(VizAreaClick, { iframeRef: iframeRef, onAreaClick: (area) => {
5114
- console.log('area clicked', area);
5115
- } })), jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, scrolling: "no" }), jsx(VizScrollMap, { iframeRef: iframeRef, wrapperRef: visualRef })] }));
5569
+ return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [jsx(VizClickmap, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef }), jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, scrolling: "no" }), jsx(VizScrollMap, { iframeRef: iframeRef, wrapperRef: visualRef })] }));
5116
5570
  };
5117
5571
 
5118
5572
  const VizLoading = () => {
@@ -5120,13 +5574,22 @@ const VizLoading = () => {
5120
5574
  };
5121
5575
 
5122
5576
  const VizDomHeatmap = () => {
5123
- const { iframeHeight, setIframeHeight } = useHeatmapViz();
5577
+ const { iframeHeight, setIframeHeight, setVizRef, setIsRenderViz } = useHeatmapViz();
5578
+ const { setSelectedElement, setHoveredElement } = useHeatmapClick();
5579
+ const { setSelectedArea, setHoveredArea, setAreas } = useHeatmapAreaClick();
5124
5580
  useRenderCount('VizDomHeatmap');
5581
+ const cleanUp = () => {
5582
+ setVizRef(null);
5583
+ setIframeHeight(0);
5584
+ setIsRenderViz(false);
5585
+ setSelectedElement(null);
5586
+ setHoveredElement(null);
5587
+ setSelectedArea(null);
5588
+ setHoveredArea(null);
5589
+ setAreas([]);
5590
+ };
5125
5591
  useEffect(() => {
5126
- return () => {
5127
- console.log('🚀 🐥 ~ useEffect ~ return:');
5128
- setIframeHeight(0);
5129
- };
5592
+ return cleanUp;
5130
5593
  }, []);
5131
5594
  return (jsxs(VizContainer, { isActive: true, children: [jsx(VizDomRenderer, {}), iframeHeight === 0 && jsx(VizLoading, {})] }));
5132
5595
  };
@@ -5189,21 +5652,16 @@ const HeatmapPreview = () => {
5189
5652
 
5190
5653
  const ContentTopBar = () => {
5191
5654
  const controls = useHeatmapControlStore((state) => state.controls);
5192
- useHeatmapConfigStore((state) => state.mode);
5193
5655
  const TopBar = controls.TopBar;
5194
- // In compare mode, hide individual top bars since we have a global header
5195
- // if (mode === 'compare') {
5196
- // return null;
5197
- // }
5198
5656
  return (jsx(BoxStack, { id: "gx-hm-content-header", flexDirection: "row", alignItems: "center", overflow: "auto", zIndex: 1, backgroundColor: "white", style: {
5199
5657
  borderBottom: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
5200
5658
  }, children: TopBar && jsx(TopBar, {}) }));
5201
5659
  };
5202
5660
 
5203
- const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
5661
+ const HeatmapLayout = ({ data, clickmap, clickAreas, scrollmap, controls, dataInfo, }) => {
5204
5662
  useRegisterControl(controls);
5205
5663
  useRegisterData(data, dataInfo);
5206
- useRegisterHeatmap({ clickmap, scrollmap });
5664
+ useRegisterHeatmap({ clickmap, scrollmap, clickAreas });
5207
5665
  useRegisterConfig();
5208
5666
  performanceLogger.configure({
5209
5667
  enabled: true,
@@ -5231,4 +5689,104 @@ const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
5231
5689
  }
5232
5690
  };
5233
5691
 
5234
- export { DEFAULT_SIDEBAR_WIDTH, DEFAULT_VIEW_ID, GraphView, HEATMAP_CONFIG, HEATMAP_IFRAME, HEATMAP_STYLE, HeatmapLayout, IClickType, IHeatmapType, IScrollType, ViewIdContext, Z_INDEX, compareViewPerformance, convertViewportToIframeCoords, createStorePerformanceTracker, downloadPerformanceReport, getCompareViewId, getMetricsByViewId, getPerformanceReportJSON, getScrollGradientColor, performanceLogger, printPerformanceSummary, sendPerformanceReport, trackStoreAction, useAreaEditMode, useAreaScrollSync, useClickedElement, useDebounceCallback, useElementCalloutVisible, useHeatmapAreaClick, useHeatmapCanvas, useHeatmapClick, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapCopyView, useHeatmapData, useHeatmapEffects, useHeatmapElementPosition, useHeatmapLiveStore, useHeatmapScale, useHeatmapScroll, useHeatmapViz, useHeatmapVizRender, useHoveredElement, useMeasureFunction, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useRenderCount, useScrollmapZones, useTrackHookCall, useViewIdContext, useVizLiveRender, useWhyDidYouUpdate, useWrapperRefHeight, useZonePositions, withPerformanceTracking };
5692
+ const AreaEditHighlight = ({ element, shadowRoot, onClick }) => {
5693
+ const [rect, setRect] = useState(null);
5694
+ const highlightRef = useRef(null);
5695
+ useEffect(() => {
5696
+ if (!element) {
5697
+ setRect(null);
5698
+ return;
5699
+ }
5700
+ const elementRect = getElementRect(element);
5701
+ setRect(elementRect);
5702
+ }, [element, shadowRoot]);
5703
+ if (!rect) {
5704
+ return null;
5705
+ }
5706
+ const handleClick = (e) => {
5707
+ if (element && onClick) {
5708
+ e.stopPropagation();
5709
+ e.preventDefault();
5710
+ onClick(element);
5711
+ }
5712
+ };
5713
+ return (jsx("div", { ref: highlightRef, id: AREA_HOVER_ELEMENT_ID, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: handleClick, style: {
5714
+ position: 'absolute',
5715
+ top: `${rect.absoluteTop}px`,
5716
+ left: `${rect.absoluteLeft}px`,
5717
+ width: `${rect.width}px`,
5718
+ height: `${rect.height}px`,
5719
+ zIndex: Number.MAX_SAFE_INTEGER,
5720
+ boxShadow: AREA_HOVER_BOX_SHADOW,
5721
+ backgroundColor: 'rgba(128, 128, 128, 0.4)',
5722
+ backgroundImage: 'repeating-linear-gradient(135deg, transparent, transparent 35px, rgba(255,255,255,.5) 35px, rgba(255,255,255,.5) 70px)',
5723
+ pointerEvents: 'auto',
5724
+ cursor: 'pointer',
5725
+ boxSizing: 'border-box',
5726
+ } }));
5727
+ };
5728
+ AreaEditHighlight.displayName = 'AreaEditHighlight';
5729
+
5730
+ const AreaLabel = ({ clickDist, totalClicks, kind }) => {
5731
+ if (kind === 'money') {
5732
+ return null;
5733
+ }
5734
+ return (jsxs("div", { style: {
5735
+ color: '#161514',
5736
+ backgroundColor: 'rgba(255, 255, 255, 0.86)',
5737
+ display: 'flex',
5738
+ flexDirection: 'column',
5739
+ alignItems: 'center',
5740
+ padding: '8px',
5741
+ borderRadius: '4px',
5742
+ fontSize: '16px',
5743
+ lineHeight: '20px',
5744
+ minWidth: '56px',
5745
+ fontWeight: 600,
5746
+ fontFamily: '"Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif',
5747
+ pointerEvents: 'none',
5748
+ }, children: [jsxs("span", { children: [clickDist.toFixed(2), "%"] }), jsxs("span", { style: { fontSize: '12px', fontWeight: 400, opacity: 0.8 }, children: [totalClicks, " clicks"] })] }));
5749
+ };
5750
+ AreaLabel.displayName = 'AreaLabel';
5751
+
5752
+ const AreaOverlay = ({ area, onClick, onMouseEnter, onMouseLeave, isSelected, isHovered, }) => {
5753
+ const [rect, setRect] = useState(area.rect.value);
5754
+ useEffect(() => {
5755
+ const handleRectChange = (newRect) => {
5756
+ if (newRect) {
5757
+ setRect(newRect);
5758
+ }
5759
+ };
5760
+ area.rect.observe(handleRectChange);
5761
+ return () => {
5762
+ area.rect.unobserve(handleRectChange);
5763
+ };
5764
+ }, [area.rect]);
5765
+ if (!rect)
5766
+ return null;
5767
+ const position = area.isFixed ? 'fixed' : 'absolute';
5768
+ const showLabel = !isRectTooSmallForLabel(rect);
5769
+ const backgroundColor = isHovered ? area.hoverColor : area.color;
5770
+ const boxShadow = isSelected ? '0 0 0 3px red inset' : isHovered ? AREA_HOVER_BOX_SHADOW : '0 0 0 2px white inset';
5771
+ return (jsxs("div", { id: `area-${area.id}`, "data-area-id": area.id, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: () => onClick?.(area), onMouseEnter: () => onMouseEnter?.(area), onMouseLeave: () => onMouseLeave?.(area), children: [jsx("style", { children: `
5772
+ #area-${area.id} {
5773
+ position: ${position};
5774
+ top: ${rect.top}px;
5775
+ left: ${rect.left}px;
5776
+ width: ${rect.width}px;
5777
+ height: ${rect.height}px;
5778
+ background-color: ${backgroundColor};
5779
+ box-shadow: ${boxShadow};
5780
+ box-sizing: border-box;
5781
+ display: flex;
5782
+ align-items: center;
5783
+ justify-content: center;
5784
+ cursor: pointer;
5785
+ transition: background-color 0.2s, box-shadow 0.2s;
5786
+ pointer-events: auto;
5787
+ z-index: ${Number.MAX_SAFE_INTEGER};
5788
+ }
5789
+ ` }), showLabel && jsx(AreaLabel, { clickDist: area.clickDist, totalClicks: area.totalclicks, kind: area.kind })] }));
5790
+ };
5791
+
5792
+ export { AreaEditHighlight, AreaOverlay, DEFAULT_SIDEBAR_WIDTH, DEFAULT_VIEW_ID, GraphView, HEATMAP_CONFIG, HEATMAP_IFRAME, HEATMAP_STYLE, HeatmapLayout, IClickMode, IClickType, IHeatmapType, IScrollType, ViewIdContext, Z_INDEX, compareViewPerformance, convertViewportToIframeCoords, createStorePerformanceTracker, downloadPerformanceReport, getCompareViewId, getMetricsByViewId, getPerformanceReportJSON, getScrollGradientColor, performanceLogger, printPerformanceSummary, scrollToElementIfNeeded, sendPerformanceReport, serializeAreas, trackStoreAction, useAreaCreation, useAreaEditMode, useAreaFilterVisible, useAreaHydration, useAreaInteraction, useAreaPortals, useAreaRectSync, useAreaRendererContainer, useAreaScrollSync, useAreaTopAutoDetect, useClickedElement, useDebounceCallback, useElementCalloutVisible, useHeatmapAreaClick, useHeatmapCanvas, useHeatmapClick, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapCopyView, useHeatmapData, useHeatmapEffects, useHeatmapElementPosition, useHeatmapLiveStore, useHeatmapRenderByMode, useHeatmapScale, useHeatmapScroll, useHeatmapViz, useHoveredElement, useMeasureFunction, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useRenderCount, useScrollmapZones, useTrackHookCall, useViewIdContext, useVizLiveRender, useWhyDidYouUpdate, useWrapperRefHeight, useZonePositions, withPerformanceTracking };