@gemx-dev/heatmap-react 3.5.51 → 3.5.53

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 (440) hide show
  1. package/dist/esm/components/Layout/HeatmapLayout.d.ts.map +1 -1
  2. package/dist/esm/components/Layout/HeatmapPreview.d.ts +2 -0
  3. package/dist/esm/components/Layout/HeatmapPreview.d.ts.map +1 -0
  4. package/dist/esm/components/Layout/MetricBar/ContentMetricBar.d.ts.map +1 -0
  5. package/dist/esm/components/Layout/MetricBar/index.d.ts +2 -0
  6. package/dist/esm/components/Layout/MetricBar/index.d.ts.map +1 -0
  7. package/dist/esm/components/Layout/Toolbar/ContentToolbar.d.ts.map +1 -0
  8. package/dist/esm/components/Layout/Toolbar/index.d.ts +2 -0
  9. package/dist/esm/components/Layout/Toolbar/index.d.ts.map +1 -0
  10. package/dist/esm/components/Layout/TopBar/ContentTopBar.d.ts.map +1 -0
  11. package/dist/esm/components/Layout/TopBar/index.d.ts +2 -0
  12. package/dist/esm/components/Layout/TopBar/index.d.ts.map +1 -0
  13. package/dist/esm/components/Layout/VizByMode/ContentVizByMode.d.ts.map +1 -0
  14. package/dist/esm/components/Layout/VizByMode/index.d.ts +2 -0
  15. package/dist/esm/components/Layout/VizByMode/index.d.ts.map +1 -0
  16. package/dist/esm/components/VizAreaClick/AreaEditHighlight.d.ts +8 -0
  17. package/dist/esm/components/VizAreaClick/AreaEditHighlight.d.ts.map +1 -0
  18. package/dist/esm/components/VizAreaClick/AreaLabel.d.ts +8 -0
  19. package/dist/esm/components/VizAreaClick/AreaLabel.d.ts.map +1 -0
  20. package/dist/esm/components/VizAreaClick/AreaOverlay.d.ts +12 -0
  21. package/dist/esm/components/VizAreaClick/AreaOverlay.d.ts.map +1 -0
  22. package/dist/esm/components/VizAreaClick/PortalAreaRenderer.d.ts +16 -0
  23. package/dist/esm/components/VizAreaClick/PortalAreaRenderer.d.ts.map +1 -0
  24. package/dist/esm/components/VizAreaClick/VizAreaClick.d.ts +11 -0
  25. package/dist/esm/components/VizAreaClick/VizAreaClick.d.ts.map +1 -0
  26. package/dist/esm/components/VizAreaClick/index.d.ts +4 -0
  27. package/dist/esm/components/VizAreaClick/index.d.ts.map +1 -0
  28. package/dist/esm/components/VizClickmap/VizClickmap.d.ts +8 -0
  29. package/dist/esm/components/VizClickmap/VizClickmap.d.ts.map +1 -0
  30. package/dist/esm/components/VizClickmap/index.d.ts +2 -0
  31. package/dist/esm/components/VizClickmap/index.d.ts.map +1 -0
  32. package/dist/esm/components/VizDom/VizContainer.d.ts.map +1 -1
  33. package/dist/esm/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  34. package/dist/esm/components/VizElement/ElementOverlay.d.ts +0 -1
  35. package/dist/esm/components/VizElement/ElementOverlay.d.ts.map +1 -1
  36. package/dist/esm/components/VizElement/HeatmapElements.d.ts.map +1 -1
  37. package/dist/esm/components/VizScrollmap/index.d.ts +1 -0
  38. package/dist/esm/components/VizScrollmap/index.d.ts.map +1 -1
  39. package/dist/esm/components/index.d.ts +1 -0
  40. package/dist/esm/components/index.d.ts.map +1 -1
  41. package/dist/esm/configs/style.d.ts +1 -1
  42. package/dist/esm/configs/style.d.ts.map +1 -1
  43. package/dist/esm/configs/viewId.d.ts +1 -19
  44. package/dist/esm/configs/viewId.d.ts.map +1 -1
  45. package/dist/esm/constants/index.d.ts +3 -13
  46. package/dist/esm/constants/index.d.ts.map +1 -1
  47. package/dist/esm/constants/selectors.d.ts +4 -0
  48. package/dist/esm/constants/selectors.d.ts.map +1 -0
  49. package/dist/esm/constants/viz-area-click.d.ts +16 -0
  50. package/dist/esm/constants/viz-area-click.d.ts.map +1 -0
  51. package/dist/esm/constants/viz-elm-callout.d.ts +8 -0
  52. package/dist/esm/constants/viz-elm-callout.d.ts.map +1 -0
  53. package/dist/esm/contexts/ViewIdContext.d.ts +3 -0
  54. package/dist/esm/contexts/ViewIdContext.d.ts.map +1 -0
  55. package/dist/esm/contexts/index.d.ts +1 -1
  56. package/dist/esm/contexts/index.d.ts.map +1 -1
  57. package/dist/esm/helpers/dom-utils.d.ts +29 -0
  58. package/dist/esm/helpers/dom-utils.d.ts.map +1 -0
  59. package/dist/esm/helpers/iframe-helper/fixer.d.ts.map +1 -1
  60. package/dist/esm/helpers/iframe-helper/navigation-blocker-v2.d.ts +4 -1
  61. package/dist/esm/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -1
  62. package/dist/esm/helpers/iframe-helper/navigation-blocker.d.ts.map +1 -1
  63. package/dist/esm/helpers/iframe-helper/style-replacer.d.ts.map +1 -1
  64. package/dist/esm/helpers/index.d.ts +6 -2
  65. package/dist/esm/helpers/index.d.ts.map +1 -1
  66. package/dist/esm/helpers/logger.d.ts +77 -0
  67. package/dist/esm/helpers/logger.d.ts.map +1 -0
  68. package/dist/esm/helpers/observable.d.ts +11 -0
  69. package/dist/esm/helpers/observable.d.ts.map +1 -0
  70. package/dist/esm/helpers/viewport/element.d.ts +3 -0
  71. package/dist/esm/helpers/viewport/element.d.ts.map +1 -0
  72. package/dist/esm/helpers/viewport/index.d.ts +2 -0
  73. package/dist/esm/helpers/viewport/index.d.ts.map +1 -0
  74. package/dist/esm/helpers/viz-area-click/area-builder.d.ts +8 -0
  75. package/dist/esm/helpers/viz-area-click/area-builder.d.ts.map +1 -0
  76. package/dist/esm/helpers/viz-area-click/area-color.d.ts +13 -0
  77. package/dist/esm/helpers/viz-area-click/area-color.d.ts.map +1 -0
  78. package/dist/esm/helpers/viz-area-click/area-overlap.d.ts +44 -0
  79. package/dist/esm/helpers/viz-area-click/area-overlap.d.ts.map +1 -0
  80. package/dist/esm/helpers/viz-area-click/area-renderer-setup.d.ts +20 -0
  81. package/dist/esm/helpers/viz-area-click/area-renderer-setup.d.ts.map +1 -0
  82. package/dist/esm/helpers/viz-area-click/area-utils.d.ts +13 -0
  83. package/dist/esm/helpers/viz-area-click/area-utils.d.ts.map +1 -0
  84. package/dist/esm/helpers/viz-area-click/index.d.ts +6 -0
  85. package/dist/esm/helpers/viz-area-click/index.d.ts.map +1 -0
  86. package/dist/esm/helpers/{iframe.d.ts → viz-dom/decode.d.ts} +2 -2
  87. package/dist/esm/helpers/viz-dom/decode.d.ts.map +1 -0
  88. package/dist/esm/helpers/viz-dom/index.d.ts +2 -0
  89. package/dist/esm/helpers/viz-dom/index.d.ts.map +1 -0
  90. package/dist/esm/helpers/viz-elm-callout/dimensions.d.ts +3 -3
  91. package/dist/esm/helpers/viz-elm-callout/dimensions.d.ts.map +1 -1
  92. package/dist/esm/helpers/viz-elm-callout/element.d.ts +10 -0
  93. package/dist/esm/helpers/viz-elm-callout/element.d.ts.map +1 -0
  94. package/dist/esm/helpers/viz-elm-callout/getter.d.ts +0 -2
  95. package/dist/esm/helpers/viz-elm-callout/getter.d.ts.map +1 -1
  96. package/dist/esm/helpers/viz-elm-callout/index.d.ts +2 -0
  97. package/dist/esm/helpers/viz-elm-callout/index.d.ts.map +1 -1
  98. package/dist/esm/helpers/viz-elm-callout/position-candidates.d.ts +4 -5
  99. package/dist/esm/helpers/viz-elm-callout/position-candidates.d.ts.map +1 -1
  100. package/dist/esm/helpers/viz-elm-callout/position-selector.d.ts +3 -3
  101. package/dist/esm/helpers/viz-elm-callout/position-selector.d.ts.map +1 -1
  102. package/dist/esm/helpers/viz-elm-callout/position-validator.d.ts.map +1 -1
  103. package/dist/esm/helpers/viz-elm-callout/rank-calculator.d.ts +7 -0
  104. package/dist/esm/helpers/viz-elm-callout/rank-calculator.d.ts.map +1 -0
  105. package/dist/esm/helpers/viz-elm-callout/viz-elm.d.ts +4 -2
  106. package/dist/esm/helpers/viz-elm-callout/viz-elm.d.ts.map +1 -1
  107. package/dist/esm/hooks/index.d.ts +3 -1
  108. package/dist/esm/hooks/index.d.ts.map +1 -1
  109. package/dist/esm/hooks/register/useRegisterConfig.d.ts.map +1 -1
  110. package/dist/esm/hooks/view-context/index.d.ts +3 -3
  111. package/dist/esm/hooks/view-context/index.d.ts.map +1 -1
  112. package/dist/esm/hooks/view-context/useHeatmapAreaClick.d.ts +20 -0
  113. package/dist/esm/hooks/view-context/useHeatmapAreaClick.d.ts.map +1 -0
  114. package/dist/{umd/hooks/view-context/useHeatmapInteraction.d.ts → esm/hooks/view-context/useHeatmapClick.d.ts} +5 -4
  115. package/dist/esm/hooks/view-context/useHeatmapClick.d.ts.map +1 -0
  116. package/dist/esm/hooks/view-context/useHeatmapCopyView.d.ts.map +1 -1
  117. package/dist/{umd/hooks/view-context/useHeatmapVizScrollmap.d.ts → esm/hooks/view-context/useHeatmapScroll.d.ts} +5 -4
  118. package/dist/esm/hooks/view-context/useHeatmapScroll.d.ts.map +1 -0
  119. package/dist/esm/hooks/viz-area-click/index.d.ts +10 -0
  120. package/dist/esm/hooks/viz-area-click/index.d.ts.map +1 -0
  121. package/dist/esm/hooks/viz-area-click/useAreaCreation.d.ts +10 -0
  122. package/dist/esm/hooks/viz-area-click/useAreaCreation.d.ts.map +1 -0
  123. package/dist/esm/hooks/viz-area-click/useAreaEditMode.d.ts +12 -0
  124. package/dist/esm/hooks/viz-area-click/useAreaEditMode.d.ts.map +1 -0
  125. package/dist/esm/hooks/viz-area-click/useAreaFilterVisible.d.ts +7 -0
  126. package/dist/esm/hooks/viz-area-click/useAreaFilterVisible.d.ts.map +1 -0
  127. package/dist/esm/hooks/viz-area-click/useAreaInteraction.d.ts +17 -0
  128. package/dist/esm/hooks/viz-area-click/useAreaInteraction.d.ts.map +1 -0
  129. package/dist/esm/hooks/viz-area-click/useAreaPortals.d.ts +15 -0
  130. package/dist/esm/hooks/viz-area-click/useAreaPortals.d.ts.map +1 -0
  131. package/dist/esm/hooks/viz-area-click/useAreaRectSync.d.ts +8 -0
  132. package/dist/esm/hooks/viz-area-click/useAreaRectSync.d.ts.map +1 -0
  133. package/dist/esm/hooks/viz-area-click/useAreaRendererContainer.d.ts +13 -0
  134. package/dist/esm/hooks/viz-area-click/useAreaRendererContainer.d.ts.map +1 -0
  135. package/dist/esm/hooks/viz-area-click/useAreaScrollSync.d.ts +7 -0
  136. package/dist/esm/hooks/viz-area-click/useAreaScrollSync.d.ts.map +1 -0
  137. package/dist/esm/hooks/viz-area-click/useAreaTopAutoDetect.d.ts +8 -0
  138. package/dist/esm/hooks/viz-area-click/useAreaTopAutoDetect.d.ts.map +1 -0
  139. package/dist/esm/hooks/viz-canvas/useAreaClickmap.d.ts +4 -0
  140. package/dist/esm/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -0
  141. package/dist/esm/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
  142. package/dist/esm/hooks/viz-elm/useClickedElement.d.ts +1 -0
  143. package/dist/esm/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
  144. package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -1
  145. package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
  146. package/dist/esm/hooks/viz-elm/useHeatmapMouseHandler.d.ts +0 -25
  147. package/dist/esm/hooks/viz-elm/useHeatmapMouseHandler.d.ts.map +1 -1
  148. package/dist/esm/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
  149. package/dist/esm/hooks/viz-render/index.d.ts +1 -1
  150. package/dist/esm/hooks/viz-render/index.d.ts.map +1 -1
  151. package/dist/esm/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
  152. package/dist/esm/hooks/viz-render/useHeatmapRenderByMode.d.ts +6 -0
  153. package/dist/esm/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -0
  154. package/dist/esm/hooks/viz-scroll/index.d.ts.map +1 -0
  155. package/dist/esm/hooks/viz-scroll/useScrollmapZones.d.ts.map +1 -0
  156. package/dist/esm/hooks/viz-scroll/useZonePositions.d.ts.map +1 -0
  157. package/dist/esm/index.d.ts +2 -1
  158. package/dist/esm/index.d.ts.map +1 -1
  159. package/dist/esm/index.js +1984 -717
  160. package/dist/esm/index.mjs +1984 -717
  161. package/dist/esm/stores/config.d.ts +3 -1
  162. package/dist/esm/stores/config.d.ts.map +1 -1
  163. package/dist/esm/stores/index.d.ts +3 -2
  164. package/dist/esm/stores/index.d.ts.map +1 -1
  165. package/dist/esm/stores/mode-compare.d.ts +0 -14
  166. package/dist/esm/stores/mode-compare.d.ts.map +1 -1
  167. package/dist/esm/stores/viz-area-click.d.ts +29 -0
  168. package/dist/esm/stores/viz-area-click.d.ts.map +1 -0
  169. package/dist/esm/stores/{interaction.d.ts → viz-click.d.ts} +5 -5
  170. package/dist/esm/stores/viz-click.d.ts.map +1 -0
  171. package/dist/esm/types/compare.d.ts +0 -50
  172. package/dist/esm/types/compare.d.ts.map +1 -1
  173. package/dist/esm/types/heatmap.d.ts +5 -0
  174. package/dist/esm/types/heatmap.d.ts.map +1 -1
  175. package/dist/esm/types/iframe-helper.d.ts +1 -0
  176. package/dist/esm/types/iframe-helper.d.ts.map +1 -1
  177. package/dist/esm/types/index.d.ts +5 -4
  178. package/dist/esm/types/index.d.ts.map +1 -1
  179. package/dist/esm/types/viz-area-click.d.ts +84 -0
  180. package/dist/esm/types/viz-area-click.d.ts.map +1 -0
  181. package/dist/esm/types/viz-elm-callout.d.ts +15 -0
  182. package/dist/esm/types/viz-elm-callout.d.ts.map +1 -1
  183. package/dist/umd/components/Layout/HeatmapLayout.d.ts.map +1 -1
  184. package/dist/umd/components/Layout/HeatmapPreview.d.ts +2 -0
  185. package/dist/umd/components/Layout/HeatmapPreview.d.ts.map +1 -0
  186. package/dist/umd/components/Layout/MetricBar/ContentMetricBar.d.ts.map +1 -0
  187. package/dist/umd/components/Layout/MetricBar/index.d.ts +2 -0
  188. package/dist/umd/components/Layout/MetricBar/index.d.ts.map +1 -0
  189. package/dist/umd/components/Layout/Toolbar/ContentToolbar.d.ts.map +1 -0
  190. package/dist/umd/components/Layout/Toolbar/index.d.ts +2 -0
  191. package/dist/umd/components/Layout/Toolbar/index.d.ts.map +1 -0
  192. package/dist/umd/components/Layout/TopBar/ContentTopBar.d.ts.map +1 -0
  193. package/dist/umd/components/Layout/TopBar/index.d.ts +2 -0
  194. package/dist/umd/components/Layout/TopBar/index.d.ts.map +1 -0
  195. package/dist/umd/components/Layout/VizByMode/ContentVizByMode.d.ts.map +1 -0
  196. package/dist/umd/components/Layout/VizByMode/index.d.ts +2 -0
  197. package/dist/umd/components/Layout/VizByMode/index.d.ts.map +1 -0
  198. package/dist/umd/components/VizAreaClick/AreaEditHighlight.d.ts +8 -0
  199. package/dist/umd/components/VizAreaClick/AreaEditHighlight.d.ts.map +1 -0
  200. package/dist/umd/components/VizAreaClick/AreaLabel.d.ts +8 -0
  201. package/dist/umd/components/VizAreaClick/AreaLabel.d.ts.map +1 -0
  202. package/dist/umd/components/VizAreaClick/AreaOverlay.d.ts +12 -0
  203. package/dist/umd/components/VizAreaClick/AreaOverlay.d.ts.map +1 -0
  204. package/dist/umd/components/VizAreaClick/PortalAreaRenderer.d.ts +16 -0
  205. package/dist/umd/components/VizAreaClick/PortalAreaRenderer.d.ts.map +1 -0
  206. package/dist/umd/components/VizAreaClick/VizAreaClick.d.ts +11 -0
  207. package/dist/umd/components/VizAreaClick/VizAreaClick.d.ts.map +1 -0
  208. package/dist/umd/components/VizAreaClick/index.d.ts +4 -0
  209. package/dist/umd/components/VizAreaClick/index.d.ts.map +1 -0
  210. package/dist/umd/components/VizClickmap/VizClickmap.d.ts +8 -0
  211. package/dist/umd/components/VizClickmap/VizClickmap.d.ts.map +1 -0
  212. package/dist/umd/components/VizClickmap/index.d.ts +2 -0
  213. package/dist/umd/components/VizClickmap/index.d.ts.map +1 -0
  214. package/dist/umd/components/VizDom/VizContainer.d.ts.map +1 -1
  215. package/dist/umd/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  216. package/dist/umd/components/VizElement/ElementOverlay.d.ts +0 -1
  217. package/dist/umd/components/VizElement/ElementOverlay.d.ts.map +1 -1
  218. package/dist/umd/components/VizElement/HeatmapElements.d.ts.map +1 -1
  219. package/dist/umd/components/VizScrollmap/index.d.ts +1 -0
  220. package/dist/umd/components/VizScrollmap/index.d.ts.map +1 -1
  221. package/dist/umd/components/index.d.ts +1 -0
  222. package/dist/umd/components/index.d.ts.map +1 -1
  223. package/dist/umd/configs/style.d.ts +1 -1
  224. package/dist/umd/configs/style.d.ts.map +1 -1
  225. package/dist/umd/configs/viewId.d.ts +1 -19
  226. package/dist/umd/configs/viewId.d.ts.map +1 -1
  227. package/dist/umd/constants/index.d.ts +3 -13
  228. package/dist/umd/constants/index.d.ts.map +1 -1
  229. package/dist/umd/constants/selectors.d.ts +4 -0
  230. package/dist/umd/constants/selectors.d.ts.map +1 -0
  231. package/dist/umd/constants/viz-area-click.d.ts +16 -0
  232. package/dist/umd/constants/viz-area-click.d.ts.map +1 -0
  233. package/dist/umd/constants/viz-elm-callout.d.ts +8 -0
  234. package/dist/umd/constants/viz-elm-callout.d.ts.map +1 -0
  235. package/dist/umd/contexts/ViewIdContext.d.ts +3 -0
  236. package/dist/umd/contexts/ViewIdContext.d.ts.map +1 -0
  237. package/dist/umd/contexts/index.d.ts +1 -1
  238. package/dist/umd/contexts/index.d.ts.map +1 -1
  239. package/dist/umd/helpers/dom-utils.d.ts +29 -0
  240. package/dist/umd/helpers/dom-utils.d.ts.map +1 -0
  241. package/dist/umd/helpers/iframe-helper/fixer.d.ts.map +1 -1
  242. package/dist/umd/helpers/iframe-helper/navigation-blocker-v2.d.ts +4 -1
  243. package/dist/umd/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -1
  244. package/dist/umd/helpers/iframe-helper/navigation-blocker.d.ts.map +1 -1
  245. package/dist/umd/helpers/iframe-helper/style-replacer.d.ts.map +1 -1
  246. package/dist/umd/helpers/index.d.ts +6 -2
  247. package/dist/umd/helpers/index.d.ts.map +1 -1
  248. package/dist/umd/helpers/logger.d.ts +77 -0
  249. package/dist/umd/helpers/logger.d.ts.map +1 -0
  250. package/dist/umd/helpers/observable.d.ts +11 -0
  251. package/dist/umd/helpers/observable.d.ts.map +1 -0
  252. package/dist/umd/helpers/viewport/element.d.ts +3 -0
  253. package/dist/umd/helpers/viewport/element.d.ts.map +1 -0
  254. package/dist/umd/helpers/viewport/index.d.ts +2 -0
  255. package/dist/umd/helpers/viewport/index.d.ts.map +1 -0
  256. package/dist/umd/helpers/viz-area-click/area-builder.d.ts +8 -0
  257. package/dist/umd/helpers/viz-area-click/area-builder.d.ts.map +1 -0
  258. package/dist/umd/helpers/viz-area-click/area-color.d.ts +13 -0
  259. package/dist/umd/helpers/viz-area-click/area-color.d.ts.map +1 -0
  260. package/dist/umd/helpers/viz-area-click/area-overlap.d.ts +44 -0
  261. package/dist/umd/helpers/viz-area-click/area-overlap.d.ts.map +1 -0
  262. package/dist/umd/helpers/viz-area-click/area-renderer-setup.d.ts +20 -0
  263. package/dist/umd/helpers/viz-area-click/area-renderer-setup.d.ts.map +1 -0
  264. package/dist/umd/helpers/viz-area-click/area-utils.d.ts +13 -0
  265. package/dist/umd/helpers/viz-area-click/area-utils.d.ts.map +1 -0
  266. package/dist/umd/helpers/viz-area-click/index.d.ts +6 -0
  267. package/dist/umd/helpers/viz-area-click/index.d.ts.map +1 -0
  268. package/dist/umd/helpers/{iframe.d.ts → viz-dom/decode.d.ts} +2 -2
  269. package/dist/umd/helpers/viz-dom/decode.d.ts.map +1 -0
  270. package/dist/umd/helpers/viz-dom/index.d.ts +2 -0
  271. package/dist/umd/helpers/viz-dom/index.d.ts.map +1 -0
  272. package/dist/umd/helpers/viz-elm-callout/dimensions.d.ts +3 -3
  273. package/dist/umd/helpers/viz-elm-callout/dimensions.d.ts.map +1 -1
  274. package/dist/umd/helpers/viz-elm-callout/element.d.ts +10 -0
  275. package/dist/umd/helpers/viz-elm-callout/element.d.ts.map +1 -0
  276. package/dist/umd/helpers/viz-elm-callout/getter.d.ts +0 -2
  277. package/dist/umd/helpers/viz-elm-callout/getter.d.ts.map +1 -1
  278. package/dist/umd/helpers/viz-elm-callout/index.d.ts +2 -0
  279. package/dist/umd/helpers/viz-elm-callout/index.d.ts.map +1 -1
  280. package/dist/umd/helpers/viz-elm-callout/position-candidates.d.ts +4 -5
  281. package/dist/umd/helpers/viz-elm-callout/position-candidates.d.ts.map +1 -1
  282. package/dist/umd/helpers/viz-elm-callout/position-selector.d.ts +3 -3
  283. package/dist/umd/helpers/viz-elm-callout/position-selector.d.ts.map +1 -1
  284. package/dist/umd/helpers/viz-elm-callout/position-validator.d.ts.map +1 -1
  285. package/dist/umd/helpers/viz-elm-callout/rank-calculator.d.ts +7 -0
  286. package/dist/umd/helpers/viz-elm-callout/rank-calculator.d.ts.map +1 -0
  287. package/dist/umd/helpers/viz-elm-callout/viz-elm.d.ts +4 -2
  288. package/dist/umd/helpers/viz-elm-callout/viz-elm.d.ts.map +1 -1
  289. package/dist/umd/hooks/index.d.ts +3 -1
  290. package/dist/umd/hooks/index.d.ts.map +1 -1
  291. package/dist/umd/hooks/register/useRegisterConfig.d.ts.map +1 -1
  292. package/dist/umd/hooks/view-context/index.d.ts +3 -3
  293. package/dist/umd/hooks/view-context/index.d.ts.map +1 -1
  294. package/dist/umd/hooks/view-context/useHeatmapAreaClick.d.ts +20 -0
  295. package/dist/umd/hooks/view-context/useHeatmapAreaClick.d.ts.map +1 -0
  296. package/dist/{esm/hooks/view-context/useHeatmapInteraction.d.ts → umd/hooks/view-context/useHeatmapClick.d.ts} +5 -4
  297. package/dist/umd/hooks/view-context/useHeatmapClick.d.ts.map +1 -0
  298. package/dist/umd/hooks/view-context/useHeatmapCopyView.d.ts.map +1 -1
  299. package/dist/{esm/hooks/view-context/useHeatmapVizScrollmap.d.ts → umd/hooks/view-context/useHeatmapScroll.d.ts} +5 -4
  300. package/dist/umd/hooks/view-context/useHeatmapScroll.d.ts.map +1 -0
  301. package/dist/umd/hooks/viz-area-click/index.d.ts +10 -0
  302. package/dist/umd/hooks/viz-area-click/index.d.ts.map +1 -0
  303. package/dist/umd/hooks/viz-area-click/useAreaCreation.d.ts +10 -0
  304. package/dist/umd/hooks/viz-area-click/useAreaCreation.d.ts.map +1 -0
  305. package/dist/umd/hooks/viz-area-click/useAreaEditMode.d.ts +12 -0
  306. package/dist/umd/hooks/viz-area-click/useAreaEditMode.d.ts.map +1 -0
  307. package/dist/umd/hooks/viz-area-click/useAreaFilterVisible.d.ts +7 -0
  308. package/dist/umd/hooks/viz-area-click/useAreaFilterVisible.d.ts.map +1 -0
  309. package/dist/umd/hooks/viz-area-click/useAreaInteraction.d.ts +17 -0
  310. package/dist/umd/hooks/viz-area-click/useAreaInteraction.d.ts.map +1 -0
  311. package/dist/umd/hooks/viz-area-click/useAreaPortals.d.ts +15 -0
  312. package/dist/umd/hooks/viz-area-click/useAreaPortals.d.ts.map +1 -0
  313. package/dist/umd/hooks/viz-area-click/useAreaRectSync.d.ts +8 -0
  314. package/dist/umd/hooks/viz-area-click/useAreaRectSync.d.ts.map +1 -0
  315. package/dist/umd/hooks/viz-area-click/useAreaRendererContainer.d.ts +13 -0
  316. package/dist/umd/hooks/viz-area-click/useAreaRendererContainer.d.ts.map +1 -0
  317. package/dist/umd/hooks/viz-area-click/useAreaScrollSync.d.ts +7 -0
  318. package/dist/umd/hooks/viz-area-click/useAreaScrollSync.d.ts.map +1 -0
  319. package/dist/umd/hooks/viz-area-click/useAreaTopAutoDetect.d.ts +8 -0
  320. package/dist/umd/hooks/viz-area-click/useAreaTopAutoDetect.d.ts.map +1 -0
  321. package/dist/umd/hooks/viz-canvas/useAreaClickmap.d.ts +4 -0
  322. package/dist/umd/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -0
  323. package/dist/umd/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
  324. package/dist/umd/hooks/viz-elm/useClickedElement.d.ts +1 -0
  325. package/dist/umd/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
  326. package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -1
  327. package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
  328. package/dist/umd/hooks/viz-elm/useHeatmapMouseHandler.d.ts +0 -25
  329. package/dist/umd/hooks/viz-elm/useHeatmapMouseHandler.d.ts.map +1 -1
  330. package/dist/umd/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
  331. package/dist/umd/hooks/viz-render/index.d.ts +1 -1
  332. package/dist/umd/hooks/viz-render/index.d.ts.map +1 -1
  333. package/dist/umd/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
  334. package/dist/umd/hooks/viz-render/useHeatmapRenderByMode.d.ts +6 -0
  335. package/dist/umd/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -0
  336. package/dist/umd/hooks/viz-scroll/index.d.ts.map +1 -0
  337. package/dist/umd/hooks/viz-scroll/useScrollmapZones.d.ts.map +1 -0
  338. package/dist/umd/hooks/viz-scroll/useZonePositions.d.ts.map +1 -0
  339. package/dist/umd/index.d.ts +2 -1
  340. package/dist/umd/index.d.ts.map +1 -1
  341. package/dist/umd/index.js +2 -2
  342. package/dist/umd/stores/config.d.ts +3 -1
  343. package/dist/umd/stores/config.d.ts.map +1 -1
  344. package/dist/umd/stores/index.d.ts +3 -2
  345. package/dist/umd/stores/index.d.ts.map +1 -1
  346. package/dist/umd/stores/mode-compare.d.ts +0 -14
  347. package/dist/umd/stores/mode-compare.d.ts.map +1 -1
  348. package/dist/umd/stores/viz-area-click.d.ts +29 -0
  349. package/dist/umd/stores/viz-area-click.d.ts.map +1 -0
  350. package/dist/umd/stores/{interaction.d.ts → viz-click.d.ts} +5 -5
  351. package/dist/umd/stores/viz-click.d.ts.map +1 -0
  352. package/dist/umd/types/compare.d.ts +0 -50
  353. package/dist/umd/types/compare.d.ts.map +1 -1
  354. package/dist/umd/types/heatmap.d.ts +5 -0
  355. package/dist/umd/types/heatmap.d.ts.map +1 -1
  356. package/dist/umd/types/iframe-helper.d.ts +1 -0
  357. package/dist/umd/types/iframe-helper.d.ts.map +1 -1
  358. package/dist/umd/types/index.d.ts +5 -4
  359. package/dist/umd/types/index.d.ts.map +1 -1
  360. package/dist/umd/types/viz-area-click.d.ts +84 -0
  361. package/dist/umd/types/viz-area-click.d.ts.map +1 -0
  362. package/dist/umd/types/viz-elm-callout.d.ts +15 -0
  363. package/dist/umd/types/viz-elm-callout.d.ts.map +1 -1
  364. package/package.json +1 -1
  365. package/dist/esm/components/Layout/ContentMetricBar.d.ts.map +0 -1
  366. package/dist/esm/components/Layout/ContentToolbar.d.ts.map +0 -1
  367. package/dist/esm/components/Layout/ContentTopBar.d.ts.map +0 -1
  368. package/dist/esm/components/Layout/ContentVizByMode.d.ts.map +0 -1
  369. package/dist/esm/components/Layout/WrapperLayout.d.ts +0 -2
  370. package/dist/esm/components/Layout/WrapperLayout.d.ts.map +0 -1
  371. package/dist/esm/components/Layout/WrapperPreview.d.ts +0 -2
  372. package/dist/esm/components/Layout/WrapperPreview.d.ts.map +0 -1
  373. package/dist/esm/contexts/CompareViewContext.d.ts +0 -28
  374. package/dist/esm/contexts/CompareViewContext.d.ts.map +0 -1
  375. package/dist/esm/helpers/iframe.d.ts.map +0 -1
  376. package/dist/esm/helpers/viz-elm-callout/constants.d.ts +0 -4
  377. package/dist/esm/helpers/viz-elm-callout/constants.d.ts.map +0 -1
  378. package/dist/esm/helpers/viz-elm-callout/types.d.ts +0 -17
  379. package/dist/esm/helpers/viz-elm-callout/types.d.ts.map +0 -1
  380. package/dist/esm/helpers/viz-elm.d.ts +0 -9
  381. package/dist/esm/helpers/viz-elm.d.ts.map +0 -1
  382. package/dist/esm/hooks/view-context/useHeatmapInteraction.d.ts.map +0 -1
  383. package/dist/esm/hooks/view-context/useHeatmapVizScrollmap.d.ts.map +0 -1
  384. package/dist/esm/hooks/view-context/useViewId.d.ts +0 -15
  385. package/dist/esm/hooks/view-context/useViewId.d.ts.map +0 -1
  386. package/dist/esm/hooks/viz-area/useAreaHeatmap.d.ts +0 -59
  387. package/dist/esm/hooks/viz-area/useAreaHeatmap.d.ts.map +0 -1
  388. package/dist/esm/hooks/viz-area/useAreaHeatmapManager.d.ts +0 -77
  389. package/dist/esm/hooks/viz-area/useAreaHeatmapManager.d.ts.map +0 -1
  390. package/dist/esm/hooks/viz-render/useHeatmapVizRender.d.ts +0 -6
  391. package/dist/esm/hooks/viz-render/useHeatmapVizRender.d.ts.map +0 -1
  392. package/dist/esm/hooks/viz-scrollmap/index.d.ts.map +0 -1
  393. package/dist/esm/hooks/viz-scrollmap/useScrollmapZones.d.ts.map +0 -1
  394. package/dist/esm/hooks/viz-scrollmap/useZonePositions.d.ts.map +0 -1
  395. package/dist/esm/stores/interaction.d.ts.map +0 -1
  396. package/dist/umd/components/Layout/ContentMetricBar.d.ts.map +0 -1
  397. package/dist/umd/components/Layout/ContentToolbar.d.ts.map +0 -1
  398. package/dist/umd/components/Layout/ContentTopBar.d.ts.map +0 -1
  399. package/dist/umd/components/Layout/ContentVizByMode.d.ts.map +0 -1
  400. package/dist/umd/components/Layout/WrapperLayout.d.ts +0 -2
  401. package/dist/umd/components/Layout/WrapperLayout.d.ts.map +0 -1
  402. package/dist/umd/components/Layout/WrapperPreview.d.ts +0 -2
  403. package/dist/umd/components/Layout/WrapperPreview.d.ts.map +0 -1
  404. package/dist/umd/contexts/CompareViewContext.d.ts +0 -28
  405. package/dist/umd/contexts/CompareViewContext.d.ts.map +0 -1
  406. package/dist/umd/helpers/iframe.d.ts.map +0 -1
  407. package/dist/umd/helpers/viz-elm-callout/constants.d.ts +0 -4
  408. package/dist/umd/helpers/viz-elm-callout/constants.d.ts.map +0 -1
  409. package/dist/umd/helpers/viz-elm-callout/types.d.ts +0 -17
  410. package/dist/umd/helpers/viz-elm-callout/types.d.ts.map +0 -1
  411. package/dist/umd/helpers/viz-elm.d.ts +0 -9
  412. package/dist/umd/helpers/viz-elm.d.ts.map +0 -1
  413. package/dist/umd/hooks/view-context/useHeatmapInteraction.d.ts.map +0 -1
  414. package/dist/umd/hooks/view-context/useHeatmapVizScrollmap.d.ts.map +0 -1
  415. package/dist/umd/hooks/view-context/useViewId.d.ts +0 -15
  416. package/dist/umd/hooks/view-context/useViewId.d.ts.map +0 -1
  417. package/dist/umd/hooks/viz-area/useAreaHeatmap.d.ts +0 -59
  418. package/dist/umd/hooks/viz-area/useAreaHeatmap.d.ts.map +0 -1
  419. package/dist/umd/hooks/viz-area/useAreaHeatmapManager.d.ts +0 -77
  420. package/dist/umd/hooks/viz-area/useAreaHeatmapManager.d.ts.map +0 -1
  421. package/dist/umd/hooks/viz-render/useHeatmapVizRender.d.ts +0 -6
  422. package/dist/umd/hooks/viz-render/useHeatmapVizRender.d.ts.map +0 -1
  423. package/dist/umd/hooks/viz-scrollmap/index.d.ts.map +0 -1
  424. package/dist/umd/hooks/viz-scrollmap/useScrollmapZones.d.ts.map +0 -1
  425. package/dist/umd/hooks/viz-scrollmap/useZonePositions.d.ts.map +0 -1
  426. package/dist/umd/stores/interaction.d.ts.map +0 -1
  427. /package/dist/esm/components/Layout/{ContentMetricBar.d.ts → MetricBar/ContentMetricBar.d.ts} +0 -0
  428. /package/dist/esm/components/Layout/{ContentToolbar.d.ts → Toolbar/ContentToolbar.d.ts} +0 -0
  429. /package/dist/esm/components/Layout/{ContentTopBar.d.ts → TopBar/ContentTopBar.d.ts} +0 -0
  430. /package/dist/esm/components/Layout/{ContentVizByMode.d.ts → VizByMode/ContentVizByMode.d.ts} +0 -0
  431. /package/dist/esm/hooks/{viz-scrollmap → viz-scroll}/index.d.ts +0 -0
  432. /package/dist/esm/hooks/{viz-scrollmap → viz-scroll}/useScrollmapZones.d.ts +0 -0
  433. /package/dist/esm/hooks/{viz-scrollmap → viz-scroll}/useZonePositions.d.ts +0 -0
  434. /package/dist/umd/components/Layout/{ContentMetricBar.d.ts → MetricBar/ContentMetricBar.d.ts} +0 -0
  435. /package/dist/umd/components/Layout/{ContentToolbar.d.ts → Toolbar/ContentToolbar.d.ts} +0 -0
  436. /package/dist/umd/components/Layout/{ContentTopBar.d.ts → TopBar/ContentTopBar.d.ts} +0 -0
  437. /package/dist/umd/components/Layout/{ContentVizByMode.d.ts → VizByMode/ContentVizByMode.d.ts} +0 -0
  438. /package/dist/umd/hooks/{viz-scrollmap → viz-scroll}/index.d.ts +0 -0
  439. /package/dist/umd/hooks/{viz-scrollmap → viz-scroll}/useScrollmapZones.d.ts +0 -0
  440. /package/dist/umd/hooks/{viz-scrollmap → viz-scroll}/useZonePositions.d.ts +0 -0
@@ -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 }) => {
@@ -48,6 +48,7 @@ const HEATMAP_IFRAME = {
48
48
  height: '100%',
49
49
  };
50
50
 
51
+ const DEFAULT_SIDEBAR_WIDTH = 260;
51
52
  const HEATMAP_CONFIG = {
52
53
  padding: 8,
53
54
  borderWidth: 1,
@@ -66,32 +67,10 @@ const HEATMAP_STYLE = {
66
67
  paddingInline: `${HEATMAP_CONFIG.padding}px`,
67
68
  },
68
69
  };
69
- const DEFAULT_SIDEBAR_WIDTH = 260;
70
70
 
71
- /**
72
- * Default view ID for single mode
73
- */
74
71
  const DEFAULT_VIEW_ID = 'default';
75
- /**
76
- * Generate compare view ID
77
- * @param index - Zero-based index (0, 1, 2, 3)
78
- * @returns View ID string like 'view-0', 'view-1', etc.
79
- */
80
- const getCompareViewId = (index) => `view-${index}`;
81
- /**
82
- * Check if a view ID is a compare view
83
- */
84
- const isCompareViewId = (viewId) => viewId.startsWith('view-');
85
- /**
86
- * Extract index from compare view ID
87
- * @param viewId - View ID like 'view-0'
88
- * @returns Index number or -1 if invalid
89
- */
90
- const getCompareViewIndex = (viewId) => {
91
- if (!isCompareViewId(viewId))
92
- return -1;
93
- const match = viewId.match(/^view-(\d+)$/);
94
- return match ? parseInt(match[1], 10) : -1;
72
+ const getCompareViewId = (viewId) => {
73
+ return `compare-${viewId}`;
95
74
  };
96
75
 
97
76
  const Z_INDEX = {
@@ -157,6 +136,7 @@ const useHeatmapControlStore = create()((set, get) => {
157
136
  var IHeatmapType;
158
137
  (function (IHeatmapType) {
159
138
  IHeatmapType["Click"] = "click";
139
+ IHeatmapType["ClickArea"] = "click-area";
160
140
  IHeatmapType["Scroll"] = "scroll";
161
141
  })(IHeatmapType || (IHeatmapType = {}));
162
142
  var IClickType;
@@ -168,6 +148,11 @@ var IClickType;
168
148
  IClickType["First"] = "first-clicks";
169
149
  IClickType["Last"] = "last-clicks";
170
150
  })(IClickType || (IClickType = {}));
151
+ var IClickMode;
152
+ (function (IClickMode) {
153
+ IClickMode["Default"] = "default";
154
+ IClickMode["Area"] = "click-area";
155
+ })(IClickMode || (IClickMode = {}));
171
156
  var IScrollType;
172
157
  (function (IScrollType) {
173
158
  IScrollType["Depth"] = "scroll-depth";
@@ -182,6 +167,7 @@ const useHeatmapConfigStore = create()((set) => {
182
167
  sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
183
168
  heatmapType: IHeatmapType.Click,
184
169
  clickType: IClickType.Total,
170
+ clickMode: IClickMode.Default,
185
171
  scrollType: IScrollType.Depth,
186
172
  isRendering: true,
187
173
  setMode: (mode) => set({ mode }),
@@ -190,6 +176,7 @@ const useHeatmapConfigStore = create()((set) => {
190
176
  setSidebarWidth: (sidebarWidth) => set({ sidebarWidth }),
191
177
  setHeatmapType: (heatmapType) => set({ heatmapType }),
192
178
  setClickType: (clickType) => set({ clickType }),
179
+ setClickMode: (clickMode) => set({ clickMode }),
193
180
  setScrollType: (scrollType) => set({ scrollType }),
194
181
  setIsRendering: (isRendering) => set({ isRendering }),
195
182
  };
@@ -244,10 +231,178 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
244
231
  };
245
232
  }));
246
233
 
234
+ const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
235
+ return {
236
+ isRenderViz: { [DEFAULT_VIEW_ID]: false },
237
+ zoomRatio: { [DEFAULT_VIEW_ID]: 100 },
238
+ minZoomRatio: { [DEFAULT_VIEW_ID]: 10 },
239
+ scale: { [DEFAULT_VIEW_ID]: 1 },
240
+ isScaledToFit: { [DEFAULT_VIEW_ID]: false },
241
+ setIsRenderViz: (isRenderViz, viewId = DEFAULT_VIEW_ID) => set((state) => ({
242
+ isRenderViz: { ...state.isRenderViz, [viewId]: isRenderViz },
243
+ })),
244
+ setZoomRatio: (zoomRatio, viewId = DEFAULT_VIEW_ID) => set((state) => ({
245
+ zoomRatio: { ...state.zoomRatio, [viewId]: zoomRatio },
246
+ })),
247
+ setMinZoomRatio: (minZoomRatio, viewId = DEFAULT_VIEW_ID) => set((state) => ({
248
+ minZoomRatio: { ...state.minZoomRatio, [viewId]: minZoomRatio },
249
+ })),
250
+ setScale: (scale, viewId = DEFAULT_VIEW_ID) => set((state) => ({
251
+ scale: { ...state.scale, [viewId]: scale },
252
+ })),
253
+ setIsScaledToFit: (isScaledToFit, viewId = DEFAULT_VIEW_ID) => set((state) => ({
254
+ isScaledToFit: { ...state.isScaledToFit, [viewId]: isScaledToFit },
255
+ })),
256
+ copyView: (fromViewId, toViewId) => set((state) => ({
257
+ isRenderViz: { ...state.isRenderViz, [toViewId]: state.isRenderViz[fromViewId] ?? false },
258
+ zoomRatio: { ...state.zoomRatio, [toViewId]: state.zoomRatio[fromViewId] ?? 100 },
259
+ minZoomRatio: { ...state.minZoomRatio, [toViewId]: state.minZoomRatio[fromViewId] ?? 10 },
260
+ scale: { ...state.scale, [toViewId]: state.scale[fromViewId] ?? 1 },
261
+ isScaledToFit: {
262
+ ...state.isScaledToFit,
263
+ [toViewId]: state.isScaledToFit[fromViewId] ?? false,
264
+ },
265
+ })),
266
+ clearView: (viewId) => set((state) => {
267
+ const newIsRenderViz = { ...state.isRenderViz };
268
+ const newZoomRatio = { ...state.zoomRatio };
269
+ const newMinZoomRatio = { ...state.minZoomRatio };
270
+ const newScale = { ...state.scale };
271
+ const newIsScaledToFit = { ...state.isScaledToFit };
272
+ delete newIsRenderViz[viewId];
273
+ delete newZoomRatio[viewId];
274
+ delete newMinZoomRatio[viewId];
275
+ delete newScale[viewId];
276
+ delete newIsScaledToFit[viewId];
277
+ return {
278
+ isRenderViz: newIsRenderViz,
279
+ zoomRatio: newZoomRatio,
280
+ minZoomRatio: newMinZoomRatio,
281
+ scale: newScale,
282
+ isScaledToFit: newIsScaledToFit,
283
+ };
284
+ }),
285
+ resetAll: () => set({
286
+ isRenderViz: { [DEFAULT_VIEW_ID]: false },
287
+ zoomRatio: { [DEFAULT_VIEW_ID]: 100 },
288
+ minZoomRatio: { [DEFAULT_VIEW_ID]: 10 },
289
+ scale: { [DEFAULT_VIEW_ID]: 1 },
290
+ isScaledToFit: { [DEFAULT_VIEW_ID]: false },
291
+ }),
292
+ };
293
+ }));
294
+
295
+ const useHeatmapAreaClickStore = create()(subscribeWithSelector((set) => ({
296
+ selectedArea: { [DEFAULT_VIEW_ID]: null },
297
+ hoveredArea: { [DEFAULT_VIEW_ID]: null },
298
+ areas: { [DEFAULT_VIEW_ID]: [] },
299
+ isEditingMode: { [DEFAULT_VIEW_ID]: false },
300
+ setSelectedArea: (area, viewId = DEFAULT_VIEW_ID) => set((state) => ({
301
+ selectedArea: { ...state.selectedArea, [viewId]: area },
302
+ })),
303
+ setHoveredArea: (area, viewId = DEFAULT_VIEW_ID) => set((state) => ({
304
+ hoveredArea: { ...state.hoveredArea, [viewId]: area },
305
+ })),
306
+ setAreas: (areas, viewId = DEFAULT_VIEW_ID) => set((state) => ({
307
+ areas: { ...state.areas, [viewId]: areas },
308
+ })),
309
+ setIsEditingMode: (isEditing, viewId = DEFAULT_VIEW_ID) => set((state) => ({
310
+ isEditingMode: { ...state.isEditingMode, [viewId]: isEditing },
311
+ })),
312
+ addArea: (area, viewId = DEFAULT_VIEW_ID) => set((state) => {
313
+ const currentAreas = state.areas[viewId] || [];
314
+ // Check if area already exists
315
+ const exists = currentAreas.some((a) => a.id === area.id);
316
+ if (exists)
317
+ return state;
318
+ return {
319
+ areas: {
320
+ ...state.areas,
321
+ [viewId]: [...currentAreas, area],
322
+ },
323
+ };
324
+ }),
325
+ removeArea: (areaId, viewId = DEFAULT_VIEW_ID) => set((state) => {
326
+ const currentAreas = state.areas[viewId] || [];
327
+ const filtered = currentAreas.filter((a) => a.id !== areaId);
328
+ // If removed area was selected/hovered, clear it
329
+ const selectedArea = state.selectedArea[viewId];
330
+ const hoveredArea = state.hoveredArea[viewId];
331
+ return {
332
+ areas: { ...state.areas, [viewId]: filtered },
333
+ selectedArea: {
334
+ ...state.selectedArea,
335
+ [viewId]: selectedArea?.id === areaId ? null : selectedArea,
336
+ },
337
+ hoveredArea: {
338
+ ...state.hoveredArea,
339
+ [viewId]: hoveredArea?.id === areaId ? null : hoveredArea,
340
+ },
341
+ };
342
+ }),
343
+ updateArea: (areaId, updates, viewId = DEFAULT_VIEW_ID) => set((state) => {
344
+ const currentAreas = state.areas[viewId] || [];
345
+ const updatedAreas = currentAreas.map((area) => (area.id === areaId ? { ...area, ...updates } : area));
346
+ return {
347
+ areas: { ...state.areas, [viewId]: updatedAreas },
348
+ };
349
+ }),
350
+ clearAreas: (viewId = DEFAULT_VIEW_ID) => set((state) => ({
351
+ areas: { ...state.areas, [viewId]: [] },
352
+ selectedArea: { ...state.selectedArea, [viewId]: null },
353
+ hoveredArea: { ...state.hoveredArea, [viewId]: null },
354
+ })),
355
+ copyView: (fromViewId, toViewId) => set((state) => ({
356
+ selectedArea: {
357
+ ...state.selectedArea,
358
+ [toViewId]: state.selectedArea[fromViewId] ?? null,
359
+ },
360
+ hoveredArea: {
361
+ ...state.hoveredArea,
362
+ [toViewId]: state.hoveredArea[fromViewId] ?? null,
363
+ },
364
+ areas: {
365
+ ...state.areas,
366
+ [toViewId]: state.areas[fromViewId] ?? [],
367
+ },
368
+ isEditingMode: {
369
+ ...state.isEditingMode,
370
+ [toViewId]: state.isEditingMode[fromViewId] ?? false,
371
+ },
372
+ })),
373
+ clearView: (viewId) => set((state) => {
374
+ const newSelectedArea = { ...state.selectedArea };
375
+ const newHoveredArea = { ...state.hoveredArea };
376
+ const newAreas = { ...state.areas };
377
+ const newIsEditingMode = { ...state.isEditingMode };
378
+ delete newSelectedArea[viewId];
379
+ delete newHoveredArea[viewId];
380
+ delete newAreas[viewId];
381
+ delete newIsEditingMode[viewId];
382
+ return {
383
+ selectedArea: newSelectedArea,
384
+ hoveredArea: newHoveredArea,
385
+ areas: newAreas,
386
+ isEditingMode: newIsEditingMode,
387
+ };
388
+ }),
389
+ resetView: (viewId) => set((state) => ({
390
+ selectedArea: { ...state.selectedArea, [viewId]: null },
391
+ hoveredArea: { ...state.hoveredArea, [viewId]: null },
392
+ isEditingMode: { ...state.isEditingMode, [viewId]: false },
393
+ })),
394
+ resetAll: () => set({
395
+ selectedArea: { [DEFAULT_VIEW_ID]: null },
396
+ hoveredArea: { [DEFAULT_VIEW_ID]: null },
397
+ areas: { [DEFAULT_VIEW_ID]: [] },
398
+ isEditingMode: { [DEFAULT_VIEW_ID]: false },
399
+ }),
400
+ })));
401
+
247
402
  const DEFAULT_STATE = {
248
403
  hideSidebar: false,
249
404
  };
250
- const useHeatmapInteractionStore = create()(subscribeWithSelector((set) => {
405
+ const useHeatmapClickStore = create()(subscribeWithSelector((set) => {
251
406
  return {
252
407
  state: { [DEFAULT_VIEW_ID]: DEFAULT_STATE },
253
408
  selectedElement: { [DEFAULT_VIEW_ID]: null },
@@ -308,67 +463,6 @@ const useHeatmapInteractionStore = create()(subscribeWithSelector((set) => {
308
463
  };
309
464
  }));
310
465
 
311
- const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
312
- return {
313
- isRenderViz: { [DEFAULT_VIEW_ID]: false },
314
- zoomRatio: { [DEFAULT_VIEW_ID]: 100 },
315
- minZoomRatio: { [DEFAULT_VIEW_ID]: 10 },
316
- scale: { [DEFAULT_VIEW_ID]: 1 },
317
- isScaledToFit: { [DEFAULT_VIEW_ID]: false },
318
- setIsRenderViz: (isRenderViz, viewId = DEFAULT_VIEW_ID) => set((state) => ({
319
- isRenderViz: { ...state.isRenderViz, [viewId]: isRenderViz },
320
- })),
321
- setZoomRatio: (zoomRatio, viewId = DEFAULT_VIEW_ID) => set((state) => ({
322
- zoomRatio: { ...state.zoomRatio, [viewId]: zoomRatio },
323
- })),
324
- setMinZoomRatio: (minZoomRatio, viewId = DEFAULT_VIEW_ID) => set((state) => ({
325
- minZoomRatio: { ...state.minZoomRatio, [viewId]: minZoomRatio },
326
- })),
327
- setScale: (scale, viewId = DEFAULT_VIEW_ID) => set((state) => ({
328
- scale: { ...state.scale, [viewId]: scale },
329
- })),
330
- setIsScaledToFit: (isScaledToFit, viewId = DEFAULT_VIEW_ID) => set((state) => ({
331
- isScaledToFit: { ...state.isScaledToFit, [viewId]: isScaledToFit },
332
- })),
333
- copyView: (fromViewId, toViewId) => set((state) => ({
334
- isRenderViz: { ...state.isRenderViz, [toViewId]: state.isRenderViz[fromViewId] ?? false },
335
- zoomRatio: { ...state.zoomRatio, [toViewId]: state.zoomRatio[fromViewId] ?? 100 },
336
- minZoomRatio: { ...state.minZoomRatio, [toViewId]: state.minZoomRatio[fromViewId] ?? 10 },
337
- scale: { ...state.scale, [toViewId]: state.scale[fromViewId] ?? 1 },
338
- isScaledToFit: {
339
- ...state.isScaledToFit,
340
- [toViewId]: state.isScaledToFit[fromViewId] ?? false,
341
- },
342
- })),
343
- clearView: (viewId) => set((state) => {
344
- const newIsRenderViz = { ...state.isRenderViz };
345
- const newZoomRatio = { ...state.zoomRatio };
346
- const newMinZoomRatio = { ...state.minZoomRatio };
347
- const newScale = { ...state.scale };
348
- const newIsScaledToFit = { ...state.isScaledToFit };
349
- delete newIsRenderViz[viewId];
350
- delete newZoomRatio[viewId];
351
- delete newMinZoomRatio[viewId];
352
- delete newScale[viewId];
353
- delete newIsScaledToFit[viewId];
354
- return {
355
- isRenderViz: newIsRenderViz,
356
- zoomRatio: newZoomRatio,
357
- minZoomRatio: newMinZoomRatio,
358
- scale: newScale,
359
- isScaledToFit: newIsScaledToFit,
360
- };
361
- }),
362
- resetAll: () => set({
363
- isRenderViz: { [DEFAULT_VIEW_ID]: false },
364
- zoomRatio: { [DEFAULT_VIEW_ID]: 100 },
365
- minZoomRatio: { [DEFAULT_VIEW_ID]: 10 },
366
- scale: { [DEFAULT_VIEW_ID]: 1 },
367
- isScaledToFit: { [DEFAULT_VIEW_ID]: false },
368
- }),
369
- };
370
- }));
371
-
372
466
  const useHeatmapVizScrollmapStore = create()(subscribeWithSelector((set) => {
373
467
  return {
374
468
  zones: { [DEFAULT_VIEW_ID]: [] },
@@ -418,94 +512,9 @@ const useHeatmapVizScrollmapStore = create()(subscribeWithSelector((set) => {
418
512
  };
419
513
  }));
420
514
 
421
- const initialState = {
422
- payloads: [],
423
- htmlContent: '',
424
- };
425
- const useHeatmapLiveStore = create()((set) => {
426
- return {
427
- ...initialState,
428
- reset: () => set(initialState),
429
- setPayloads: (payloads) => set({ payloads }),
430
- addPayload: (payload) => set((state) => ({ payloads: [...state.payloads, payload] })),
431
- setHtmlContent: (htmlContent) => set({ htmlContent }),
432
- };
433
- });
434
-
435
- const useHeatmapSingleStore = create()(subscribeWithSelector((set) => {
436
- return {
437
- vizRef: { [DEFAULT_VIEW_ID]: null },
438
- iframeHeight: { [DEFAULT_VIEW_ID]: 0 },
439
- wrapperHeight: { [DEFAULT_VIEW_ID]: 0 },
440
- wrapperWidth: { [DEFAULT_VIEW_ID]: 0 },
441
- setVizRef: (vizRef, viewId = DEFAULT_VIEW_ID) => set((state) => ({
442
- vizRef: { ...state.vizRef, [viewId]: vizRef },
443
- })),
444
- setIframeHeight: (iframeHeight, viewId = DEFAULT_VIEW_ID) => {
445
- set((state) => ({
446
- iframeHeight: { ...state.iframeHeight, [viewId]: iframeHeight },
447
- }));
448
- },
449
- setWrapperHeight: (wrapperHeight, viewId = DEFAULT_VIEW_ID) => {
450
- set((state) => ({
451
- wrapperHeight: { ...state.wrapperHeight, [viewId]: wrapperHeight },
452
- }));
453
- },
454
- setWrapperWidth: (wrapperWidth, viewId = DEFAULT_VIEW_ID) => {
455
- set((state) => ({
456
- wrapperWidth: { ...state.wrapperWidth, [viewId]: wrapperWidth },
457
- }));
458
- },
459
- copyView: (fromViewId, toViewId) => set((state) => ({
460
- // Don't copy vizRef - each view needs its own visualizer instance
461
- iframeHeight: { ...state.iframeHeight, [toViewId]: state.iframeHeight[fromViewId] ?? 0 },
462
- wrapperHeight: {
463
- ...state.wrapperHeight,
464
- [toViewId]: state.wrapperHeight[fromViewId] ?? 0,
465
- },
466
- })),
467
- clearView: (viewId) => set((state) => {
468
- const newVizRef = { ...state.vizRef };
469
- const newIframeHeight = { ...state.iframeHeight };
470
- const newWrapperHeight = { ...state.wrapperHeight };
471
- const newWrapperWidth = { ...state.wrapperWidth };
472
- delete newVizRef[viewId];
473
- delete newIframeHeight[viewId];
474
- delete newWrapperHeight[viewId];
475
- delete newWrapperWidth[viewId];
476
- return {
477
- vizRef: newVizRef,
478
- iframeHeight: newIframeHeight,
479
- wrapperHeight: newWrapperHeight,
480
- wrapperWidth: newWrapperWidth,
481
- };
482
- }),
483
- resetAll: () => set({
484
- vizRef: { [DEFAULT_VIEW_ID]: null },
485
- iframeHeight: { [DEFAULT_VIEW_ID]: 0 },
486
- wrapperHeight: { [DEFAULT_VIEW_ID]: 0 },
487
- wrapperWidth: { [DEFAULT_VIEW_ID]: 0 },
488
- }),
489
- };
490
- }));
491
-
492
- const createDefaultView = (id, label, options) => ({
515
+ const createDefaultView = (id, label) => ({
493
516
  id,
494
517
  label,
495
- heatmapType: options?.heatmapType ?? IHeatmapType.Scroll,
496
- clickType: options?.clickType ?? IClickType.Total,
497
- scrollType: options?.scrollType ?? IScrollType.Depth,
498
- data: options?.data,
499
- clickmap: undefined,
500
- scrollmap: undefined,
501
- dataInfo: undefined,
502
- vizRef: null,
503
- iframeHeight: 0,
504
- zoomRatio: 100,
505
- scale: 1,
506
- isScaledToFit: false,
507
- isRendering: true,
508
- isRenderViz: false,
509
518
  });
510
519
  const useHeatmapCompareStore = create()((set, get) => {
511
520
  return {
@@ -520,9 +529,9 @@ const useHeatmapCompareStore = create()((set, get) => {
520
529
  viewIdCounter: 0,
521
530
  addView: (options) => {
522
531
  const state = get();
523
- const viewId = `view-${state.viewIdCounter}`;
532
+ const viewId = getCompareViewId(state.viewIdCounter);
524
533
  const label = options?.label ?? `Version ${state.viewOrder.length + 1}`;
525
- const newView = createDefaultView(viewId, label, options);
534
+ const newView = createDefaultView(viewId, label);
526
535
  const newViews = new Map(state.views);
527
536
  newViews.set(viewId, newView);
528
537
  set({
@@ -536,9 +545,13 @@ const useHeatmapCompareStore = create()((set, get) => {
536
545
  const state = get();
537
546
  const newViews = new Map(state.views);
538
547
  newViews.delete(viewId);
548
+ const newViewIdCounter = newViews.size;
549
+ const newLayout = newViews.size === 2 ? 'grid-2' : state.layout;
539
550
  set({
540
551
  views: newViews,
541
552
  viewOrder: state.viewOrder.filter((id) => id !== viewId),
553
+ viewIdCounter: newViewIdCounter,
554
+ layout: newLayout,
542
555
  });
543
556
  },
544
557
  updateView: (viewId, updates) => {
@@ -549,16 +562,6 @@ const useHeatmapCompareStore = create()((set, get) => {
549
562
  const newViews = new Map(state.views);
550
563
  newViews.set(viewId, { ...view, ...updates });
551
564
  set({ views: newViews });
552
- // Handle syncing
553
- const { syncSettings } = state;
554
- if (syncSettings.zoomRatio && 'zoomRatio' in updates && updates.zoomRatio !== undefined) {
555
- get().syncProperty('zoomRatio', updates.zoomRatio);
556
- }
557
- if (syncSettings.heatmapType &&
558
- 'heatmapType' in updates &&
559
- updates.heatmapType !== undefined) {
560
- get().syncProperty('heatmapType', updates.heatmapType);
561
- }
562
565
  },
563
566
  getView: (viewId) => {
564
567
  return get().views.get(viewId);
@@ -573,9 +576,9 @@ const useHeatmapCompareStore = create()((set, get) => {
573
576
  const viewIds = [];
574
577
  const newViews = new Map();
575
578
  for (let i = 0; i < count; i++) {
576
- const viewId = `view-${i}`;
579
+ const viewId = getCompareViewId(i);
577
580
  const label = options?.label ?? String.fromCharCode(65 + i); // A, B, C, D
578
- newViews.set(viewId, createDefaultView(viewId, `Version ${label}`, options));
581
+ newViews.set(viewId, createDefaultView(viewId, `Version ${label}`));
579
582
  viewIds.push(viewId);
580
583
  }
581
584
  const layoutMap = {
@@ -617,54 +620,192 @@ const useHeatmapCompareStore = create()((set, get) => {
617
620
  };
618
621
  });
619
622
 
620
- const useRegisterConfig = () => {
621
- const mode = useHeatmapConfigStore((state) => state.mode);
622
- const width = useHeatmapConfigStore((state) => state.width);
623
- const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
624
- const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
625
- const setIsRendering = useHeatmapConfigStore((state) => state.setIsRendering);
626
- useEffect(() => {
627
- setIsRendering(true);
628
- setTimeout(() => {
629
- setIsRendering(false);
630
- }, 1000);
631
- }, [mode, width, sidebarWidth, heatmapType]);
623
+ const initialState = {
624
+ payloads: [],
625
+ htmlContent: '',
632
626
  };
627
+ const useHeatmapLiveStore = create()((set) => {
628
+ return {
629
+ ...initialState,
630
+ reset: () => set(initialState),
631
+ setPayloads: (payloads) => set({ payloads }),
632
+ addPayload: (payload) => set((state) => ({ payloads: [...state.payloads, payload] })),
633
+ setHtmlContent: (htmlContent) => set({ htmlContent }),
634
+ };
635
+ });
633
636
 
634
- const useRegisterControl = (control) => {
635
- const registerControl = useHeatmapControlStore((state) => state.registerControl);
636
- registerControl('Sidebar', control.Sidebar);
637
- registerControl('SidebarActivator', control.SidebarActivator);
638
- registerControl('TopBar', control.TopBar);
639
- registerControl('Toolbar', control.Toolbar);
640
- registerControl('MetricBar', control.MetricBar);
641
- registerControl('VizLoading', control.VizLoading);
642
- registerControl('ElementCallout', control.ElementCallout);
643
- };
637
+ const useHeatmapSingleStore = create()(subscribeWithSelector((set) => {
638
+ return {
639
+ vizRef: { [DEFAULT_VIEW_ID]: null },
640
+ iframeHeight: { [DEFAULT_VIEW_ID]: 0 },
641
+ wrapperHeight: { [DEFAULT_VIEW_ID]: 0 },
642
+ wrapperWidth: { [DEFAULT_VIEW_ID]: 0 },
643
+ setVizRef: (vizRef, viewId = DEFAULT_VIEW_ID) => set((state) => ({
644
+ vizRef: { ...state.vizRef, [viewId]: vizRef },
645
+ })),
646
+ setIframeHeight: (iframeHeight, viewId = DEFAULT_VIEW_ID) => {
647
+ set((state) => ({
648
+ iframeHeight: { ...state.iframeHeight, [viewId]: iframeHeight },
649
+ }));
650
+ },
651
+ setWrapperHeight: (wrapperHeight, viewId = DEFAULT_VIEW_ID) => {
652
+ set((state) => ({
653
+ wrapperHeight: { ...state.wrapperHeight, [viewId]: wrapperHeight },
654
+ }));
655
+ },
656
+ setWrapperWidth: (wrapperWidth, viewId = DEFAULT_VIEW_ID) => {
657
+ set((state) => ({
658
+ wrapperWidth: { ...state.wrapperWidth, [viewId]: wrapperWidth },
659
+ }));
660
+ },
661
+ copyView: (fromViewId, toViewId) => set((state) => ({
662
+ // Don't copy vizRef - each view needs its own visualizer instance
663
+ iframeHeight: { ...state.iframeHeight, [toViewId]: state.iframeHeight[fromViewId] ?? 0 },
664
+ wrapperHeight: {
665
+ ...state.wrapperHeight,
666
+ [toViewId]: state.wrapperHeight[fromViewId] ?? 0,
667
+ },
668
+ })),
669
+ clearView: (viewId) => set((state) => {
670
+ const newVizRef = { ...state.vizRef };
671
+ const newIframeHeight = { ...state.iframeHeight };
672
+ const newWrapperHeight = { ...state.wrapperHeight };
673
+ const newWrapperWidth = { ...state.wrapperWidth };
674
+ delete newVizRef[viewId];
675
+ delete newIframeHeight[viewId];
676
+ delete newWrapperHeight[viewId];
677
+ delete newWrapperWidth[viewId];
678
+ return {
679
+ vizRef: newVizRef,
680
+ iframeHeight: newIframeHeight,
681
+ wrapperHeight: newWrapperHeight,
682
+ wrapperWidth: newWrapperWidth,
683
+ };
684
+ }),
685
+ resetAll: () => set({
686
+ vizRef: { [DEFAULT_VIEW_ID]: null },
687
+ iframeHeight: { [DEFAULT_VIEW_ID]: 0 },
688
+ wrapperHeight: { [DEFAULT_VIEW_ID]: 0 },
689
+ wrapperWidth: { [DEFAULT_VIEW_ID]: 0 },
690
+ }),
691
+ };
692
+ }));
693
+
694
+ const useRegisterConfig = () => {
695
+ const mode = useHeatmapConfigStore((state) => state.mode);
696
+ const width = useHeatmapConfigStore((state) => state.width);
697
+ const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
698
+ const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
699
+ const clickMode = useHeatmapConfigStore((state) => state.clickMode);
700
+ const setIsRendering = useHeatmapConfigStore((state) => state.setIsRendering);
701
+ useEffect(() => {
702
+ setIsRendering(true);
703
+ setTimeout(() => {
704
+ setIsRendering(false);
705
+ }, 1000);
706
+ }, [mode, width, sidebarWidth, heatmapType, clickMode]); // eslint-disable-line react-hooks/exhaustive-deps
707
+ };
708
+
709
+ const useRegisterControl = (control) => {
710
+ const registerControl = useHeatmapControlStore((state) => state.registerControl);
711
+ registerControl('Sidebar', control.Sidebar);
712
+ registerControl('SidebarActivator', control.SidebarActivator);
713
+ registerControl('TopBar', control.TopBar);
714
+ registerControl('Toolbar', control.Toolbar);
715
+ registerControl('MetricBar', control.MetricBar);
716
+ registerControl('VizLoading', control.VizLoading);
717
+ registerControl('ElementCallout', control.ElementCallout);
718
+ };
644
719
 
645
- /**
646
- * Context to provide viewId to components
647
- * Used in compare mode to isolate data between views
648
- */
649
720
  const ViewIdContext = createContext(undefined);
650
- /**
651
- * Hook to get current viewId
652
- * Returns DEFAULT_VIEW_ID if not in a ViewIdContext (single mode)
653
- */
654
- const useViewId = () => {
721
+ const useViewIdContext = () => {
655
722
  const viewId = useContext(ViewIdContext);
656
723
  return viewId || DEFAULT_VIEW_ID;
657
724
  };
658
- /**
659
- * Hook to check if currently in compare mode
660
- */
661
- const useIsCompareMode = () => {
662
- const viewId = useContext(ViewIdContext);
663
- return viewId !== undefined && viewId !== DEFAULT_VIEW_ID;
725
+
726
+ const useHeatmapAreaClick = (props) => {
727
+ const viewId = props?.viewId || useViewIdContext();
728
+ // Optimized: Only subscribe to specific viewId slice
729
+ const selectedArea = useHeatmapAreaClickStore((state) => state.selectedArea[viewId] ?? null);
730
+ const hoveredArea = useHeatmapAreaClickStore((state) => state.hoveredArea[viewId] ?? null);
731
+ const areas = useHeatmapAreaClickStore((state) => state.areas[viewId] ?? []);
732
+ const isEditingMode = useHeatmapAreaClickStore((state) => state.isEditingMode[viewId] ?? false);
733
+ // Get setters from store
734
+ const setSelectedAreaStore = useHeatmapAreaClickStore((state) => state.setSelectedArea);
735
+ const setHoveredAreaStore = useHeatmapAreaClickStore((state) => state.setHoveredArea);
736
+ const setAreasStore = useHeatmapAreaClickStore((state) => state.setAreas);
737
+ const setIsEditingModeStore = useHeatmapAreaClickStore((state) => state.setIsEditingMode);
738
+ const addAreaStore = useHeatmapAreaClickStore((state) => state.addArea);
739
+ const removeAreaStore = useHeatmapAreaClickStore((state) => state.removeArea);
740
+ const updateAreaStore = useHeatmapAreaClickStore((state) => state.updateArea);
741
+ const clearAreasStore = useHeatmapAreaClickStore((state) => state.clearAreas);
742
+ const resetViewStore = useHeatmapAreaClickStore((state) => state.resetView);
743
+ // Memoize operations to prevent unnecessary re-renders
744
+ const memoizedOperations = useMemo(() => ({
745
+ setSelectedArea: (area) => setSelectedAreaStore(area, viewId),
746
+ setHoveredArea: (area) => setHoveredAreaStore(area, viewId),
747
+ setAreas: (areas) => setAreasStore(areas, viewId),
748
+ setIsEditingMode: (isEditing) => setIsEditingModeStore(isEditing, viewId),
749
+ addArea: (area) => addAreaStore(area, viewId),
750
+ removeArea: (areaId) => removeAreaStore(areaId, viewId),
751
+ updateArea: (areaId, updates) => updateAreaStore(areaId, updates, viewId),
752
+ clearAreas: () => clearAreasStore(viewId),
753
+ resetView: () => resetViewStore(viewId),
754
+ }), [
755
+ setSelectedAreaStore,
756
+ setHoveredAreaStore,
757
+ setAreasStore,
758
+ setIsEditingModeStore,
759
+ addAreaStore,
760
+ removeAreaStore,
761
+ updateAreaStore,
762
+ clearAreasStore,
763
+ resetViewStore,
764
+ viewId,
765
+ ]);
766
+ return {
767
+ selectedArea,
768
+ hoveredArea,
769
+ areas,
770
+ isEditingMode,
771
+ ...memoizedOperations,
772
+ };
773
+ };
774
+
775
+ const useHeatmapClick = (props) => {
776
+ const viewId = props?.viewId || useViewIdContext();
777
+ const state = useHeatmapClickStore((store) => store.state[viewId] ?? { hideSidebar: false });
778
+ const selectedElement = useHeatmapClickStore((store) => store.selectedElement[viewId] ?? null);
779
+ const hoveredElement = useHeatmapClickStore((store) => store.hoveredElement[viewId] ?? null);
780
+ const shouldShowCallout = useHeatmapClickStore((store) => store.shouldShowCallout[viewId] ?? false);
781
+ const setStateStore = useHeatmapClickStore((store) => store.setState);
782
+ const setSelectedElementStore = useHeatmapClickStore((store) => store.setSelectedElement);
783
+ const setHoveredElementStore = useHeatmapClickStore((store) => store.setHoveredElement);
784
+ const setShouldShowCalloutStore = useHeatmapClickStore((store) => store.setShouldShowCallout);
785
+ const memoizedSetters = useMemo(() => ({
786
+ setState: (newState) => setStateStore(newState, viewId),
787
+ setSelectedElement: (element) => setSelectedElementStore(element, viewId),
788
+ setHoveredElement: (element) => setHoveredElementStore(element, viewId),
789
+ setShouldShowCallout: (value) => setShouldShowCalloutStore(value, viewId),
790
+ }), [
791
+ setStateStore,
792
+ setSelectedElementStore,
793
+ setHoveredElementStore,
794
+ setShouldShowCalloutStore,
795
+ viewId,
796
+ ]);
797
+ return {
798
+ state,
799
+ selectedElement,
800
+ hoveredElement,
801
+ shouldShowCallout,
802
+ // Setters (auto-inject viewId)
803
+ ...memoizedSetters,
804
+ };
664
805
  };
665
806
 
666
807
  const useHeatmapData = (props) => {
667
- const viewId = props?.viewId || useViewId();
808
+ const viewId = props?.viewId || useViewIdContext();
668
809
  const data = useHeatmapDataStore((state) => state.data[viewId]);
669
810
  const clickmap = useHeatmapDataStore((state) => state.clickmap[viewId]);
670
811
  const scrollmap = useHeatmapDataStore((state) => state.scrollmap[viewId]);
@@ -689,40 +830,30 @@ const useHeatmapData = (props) => {
689
830
  };
690
831
  };
691
832
 
692
- const useHeatmapInteraction = (props) => {
693
- const viewId = props?.viewId || useViewId();
694
- const state = useHeatmapInteractionStore((store) => store.state[viewId] ?? { hideSidebar: false });
695
- const selectedElement = useHeatmapInteractionStore((store) => store.selectedElement[viewId] ?? null);
696
- const hoveredElement = useHeatmapInteractionStore((store) => store.hoveredElement[viewId] ?? null);
697
- const shouldShowCallout = useHeatmapInteractionStore((store) => store.shouldShowCallout[viewId] ?? false);
698
- const setStateStore = useHeatmapInteractionStore((store) => store.setState);
699
- const setSelectedElementStore = useHeatmapInteractionStore((store) => store.setSelectedElement);
700
- const setHoveredElementStore = useHeatmapInteractionStore((store) => store.setHoveredElement);
701
- const setShouldShowCalloutStore = useHeatmapInteractionStore((store) => store.setShouldShowCallout);
833
+ const useHeatmapScroll = (props) => {
834
+ const viewId = props?.viewId || useViewIdContext();
835
+ const zones = useHeatmapVizScrollmapStore((store) => store.zones[viewId] ?? []);
836
+ const hoveredZone = useHeatmapVizScrollmapStore((store) => store.hoveredZone[viewId] ?? null);
837
+ const showMinimap = useHeatmapVizScrollmapStore((store) => store.showMinimap[viewId] ?? true);
838
+ const setZonesStore = useHeatmapVizScrollmapStore((store) => store.setZones);
839
+ const setHoveredZoneStore = useHeatmapVizScrollmapStore((store) => store.setHoveredZone);
840
+ const setShowMinimapStore = useHeatmapVizScrollmapStore((store) => store.setShowMinimap);
702
841
  const memoizedSetters = useMemo(() => ({
703
- setState: (newState) => setStateStore(newState, viewId),
704
- setSelectedElement: (element) => setSelectedElementStore(element, viewId),
705
- setHoveredElement: (element) => setHoveredElementStore(element, viewId),
706
- setShouldShowCallout: (value) => setShouldShowCalloutStore(value, viewId),
707
- }), [
708
- setStateStore,
709
- setSelectedElementStore,
710
- setHoveredElementStore,
711
- setShouldShowCalloutStore,
712
- viewId,
713
- ]);
842
+ setZones: (newZones) => setZonesStore(newZones, viewId),
843
+ setHoveredZone: (zone) => setHoveredZoneStore(zone, viewId),
844
+ setShowMinimap: (value) => setShowMinimapStore(value, viewId),
845
+ }), [setZonesStore, setHoveredZoneStore, setShowMinimapStore, viewId]);
714
846
  return {
715
- state,
716
- selectedElement,
717
- hoveredElement,
718
- shouldShowCallout,
847
+ zones,
848
+ hoveredZone,
849
+ showMinimap,
719
850
  // Setters (auto-inject viewId)
720
851
  ...memoizedSetters,
721
852
  };
722
853
  };
723
854
 
724
855
  const useHeatmapViz = (props) => {
725
- const viewId = props?.viewId || useViewId();
856
+ const viewId = props?.viewId || useViewIdContext();
726
857
  // Viz store
727
858
  const isRenderViz = useHeatmapVizStore((state) => state.isRenderViz[viewId] ?? false);
728
859
  const zoomRatio = useHeatmapVizStore((state) => state.zoomRatio[viewId] ?? 100);
@@ -780,28 +911,6 @@ const useHeatmapViz = (props) => {
780
911
  };
781
912
  };
782
913
 
783
- const useHeatmapVizScrollmap = (props) => {
784
- const viewId = props?.viewId || useViewId();
785
- const zones = useHeatmapVizScrollmapStore((store) => store.zones[viewId] ?? []);
786
- const hoveredZone = useHeatmapVizScrollmapStore((store) => store.hoveredZone[viewId] ?? null);
787
- const showMinimap = useHeatmapVizScrollmapStore((store) => store.showMinimap[viewId] ?? true);
788
- const setZonesStore = useHeatmapVizScrollmapStore((store) => store.setZones);
789
- const setHoveredZoneStore = useHeatmapVizScrollmapStore((store) => store.setHoveredZone);
790
- const setShowMinimapStore = useHeatmapVizScrollmapStore((store) => store.setShowMinimap);
791
- const memoizedSetters = useMemo(() => ({
792
- setZones: (newZones) => setZonesStore(newZones, viewId),
793
- setHoveredZone: (zone) => setHoveredZoneStore(zone, viewId),
794
- setShowMinimap: (value) => setShowMinimapStore(value, viewId),
795
- }), [setZonesStore, setHoveredZoneStore, setShowMinimapStore, viewId]);
796
- return {
797
- zones,
798
- hoveredZone,
799
- showMinimap,
800
- // Setters (auto-inject viewId)
801
- ...memoizedSetters,
802
- };
803
- };
804
-
805
914
  /**
806
915
  * Hook to handle copying and clearing view data across all stores
807
916
  */
@@ -809,24 +918,28 @@ const useHeatmapCopyView = () => {
809
918
  const copyDataView = useHeatmapDataStore((state) => state.copyView);
810
919
  const copyVizView = useHeatmapVizStore((state) => state.copyView);
811
920
  const copySingleView = useHeatmapSingleStore((state) => state.copyView);
812
- const copyInteractionView = useHeatmapInteractionStore((state) => state.copyView);
921
+ const copyInteractionView = useHeatmapClickStore((state) => state.copyView);
813
922
  const copyVizScrollmapView = useHeatmapVizScrollmapStore((state) => state.copyView);
923
+ const copyAreaClickView = useHeatmapAreaClickStore((state) => state.copyView);
814
924
  const clearDataView = useHeatmapDataStore((state) => state.clearView);
815
925
  const clearVizView = useHeatmapVizStore((state) => state.clearView);
816
926
  const clearSingleView = useHeatmapSingleStore((state) => state.clearView);
817
- const clearInteractionView = useHeatmapInteractionStore((state) => state.clearView);
927
+ const clearInteractionView = useHeatmapClickStore((state) => state.clearView);
818
928
  const clearVizScrollmapView = useHeatmapVizScrollmapStore((state) => state.clearView);
929
+ const clearAreaClickView = useHeatmapAreaClickStore((state) => state.clearView);
819
930
  const resetDataAll = useHeatmapDataStore((state) => state.resetAll);
820
931
  const resetVizAll = useHeatmapVizStore((state) => state.resetAll);
821
932
  const resetSingleAll = useHeatmapSingleStore((state) => state.resetAll);
822
- const resetInteractionAll = useHeatmapInteractionStore((state) => state.resetAll);
933
+ const resetInteractionAll = useHeatmapClickStore((state) => state.resetAll);
823
934
  const resetVizScrollmapAll = useHeatmapVizScrollmapStore((state) => state.resetAll);
935
+ const resetAreaClickAll = useHeatmapAreaClickStore((state) => state.resetAll);
824
936
  const copyView = (fromViewId, toViewId) => {
825
937
  copyDataView(fromViewId, toViewId);
826
938
  copyVizView(fromViewId, toViewId);
827
939
  copySingleView(fromViewId, toViewId);
828
940
  copyInteractionView(fromViewId, toViewId);
829
941
  copyVizScrollmapView(fromViewId, toViewId);
942
+ copyAreaClickView(fromViewId, toViewId);
830
943
  };
831
944
  const copyViewToMultiple = (fromViewId, toViewIds) => {
832
945
  toViewIds.forEach((toViewId) => {
@@ -839,6 +952,7 @@ const useHeatmapCopyView = () => {
839
952
  clearSingleView(viewId);
840
953
  clearInteractionView(viewId);
841
954
  clearVizScrollmapView(viewId);
955
+ clearAreaClickView(viewId);
842
956
  };
843
957
  const clearMultipleViews = (viewIds) => {
844
958
  viewIds.forEach((viewId) => {
@@ -851,6 +965,7 @@ const useHeatmapCopyView = () => {
851
965
  resetSingleAll();
852
966
  resetInteractionAll();
853
967
  resetVizScrollmapAll();
968
+ resetAreaClickAll();
854
969
  };
855
970
  return {
856
971
  copyView,
@@ -903,6 +1018,497 @@ const useRegisterHeatmap = ({ clickmap, scrollmap }) => {
903
1018
  }, [scrollmap]);
904
1019
  };
905
1020
 
1021
+ /**
1022
+ * Create an observable value with subscribe/unsubscribe pattern
1023
+ */
1024
+ function createObservable(initialValue) {
1025
+ const subscribers = new Set();
1026
+ const observable = {
1027
+ value: initialValue,
1028
+ observe: (callback) => {
1029
+ subscribers.add(callback);
1030
+ // Immediately call with current value
1031
+ if (observable.value !== undefined) {
1032
+ callback(observable.value);
1033
+ }
1034
+ },
1035
+ unobserve: (callback) => {
1036
+ subscribers.delete(callback);
1037
+ },
1038
+ update: (newValue) => {
1039
+ observable.value = newValue;
1040
+ // Notify all subscribers
1041
+ subscribers.forEach((callback) => {
1042
+ callback(newValue);
1043
+ });
1044
+ },
1045
+ };
1046
+ return observable;
1047
+ }
1048
+
1049
+ function isElementInViewport(elementRect, visualRef, scale) {
1050
+ if (!elementRect)
1051
+ return false;
1052
+ const visualRect = visualRef.current?.getBoundingClientRect();
1053
+ if (!visualRect)
1054
+ return false;
1055
+ // Element position relative to the document (or container's content)
1056
+ const elementTop = elementRect.top * scale;
1057
+ const elementBottom = (elementRect.top + elementRect.height) * scale;
1058
+ // Current scroll position
1059
+ const scrollTop = visualRef.current?.scrollTop || 0;
1060
+ const viewportHeight = visualRect.height;
1061
+ // Visible viewport range in the scrollable content
1062
+ const viewportTop = scrollTop;
1063
+ const viewportBottom = scrollTop + viewportHeight;
1064
+ // Check if element is within the visible viewport
1065
+ // Element is visible if it overlaps with the viewport
1066
+ return elementBottom > viewportTop && elementTop < viewportBottom;
1067
+ }
1068
+
1069
+ const CLARITY_HEATMAP_CANVAS_ID = 'clarity-heatmap-canvas';
1070
+ const HEATMAP_ELEMENT_ATTRIBUTE = 'data-clarity-hashalpha';
1071
+ function isIgnoredCanvas(element) {
1072
+ if (element.tagName === 'CANVAS') {
1073
+ return true;
1074
+ }
1075
+ if (element.id === CLARITY_HEATMAP_CANVAS_ID) {
1076
+ return true;
1077
+ }
1078
+ return false;
1079
+ }
1080
+
1081
+ const AREA_HOVER_BOX_SHADOW = '0 0 0 1px #0078D4, 0 0 0 1px #0078D4 inset, 0 0 0 2px white inset';
1082
+ const AREA_HOVER_ELEMENT_ID = 'clarity-edit-hover';
1083
+ const AREA_MAP_DIV_ATTRIBUTE = 'data-clarity-area-map-div';
1084
+ const HEATMAP_AREA_CONTAINER_CLASS = 'heatmap-area-container';
1085
+ const HEATMAP_AREA_CONTAINER_SELECTOR = `.${HEATMAP_AREA_CONTAINER_CLASS}`;
1086
+ const AREA_CONTAINER_STYLES = `
1087
+ position: absolute;
1088
+ top: 0;
1089
+ left: 0;
1090
+ width: 100%;
1091
+ height: 100%;
1092
+ pointer-events: none;
1093
+ z-index: 999999;
1094
+ `;
1095
+ const AREA_INNER_CONTAINER_STYLES = `
1096
+ position: relative;
1097
+ width: 100%;
1098
+ height: 100%;
1099
+ `;
1100
+ const AREA_COLOR_GRADIENT = [
1101
+ [0, 0, 255], // Blue
1102
+ [0, 255, 255], // Cyan
1103
+ [0, 255, 0], // Green
1104
+ [255, 255, 0], // Yellow
1105
+ [255, 0, 0], // Red
1106
+ ];
1107
+ const AREA_RENDERER_SELECTORS = {
1108
+ containerAttribute: AREA_MAP_DIV_ATTRIBUTE,
1109
+ containerSelector: `[${AREA_MAP_DIV_ATTRIBUTE}]`,
1110
+ innerContainerClass: HEATMAP_AREA_CONTAINER_CLASS,
1111
+ innerContainerSelector: HEATMAP_AREA_CONTAINER_SELECTOR,
1112
+ };
1113
+
1114
+ const CALLOUT_PADDING = 0;
1115
+ const CALLOUT_ARROW_SIZE = 8;
1116
+ const CALLOUT_HORIZONTAL_OFFSET = 0;
1117
+ const CLICKED_ELEMENT_ID_BASE = 'gx-hm-clicked-element';
1118
+ const SECONDARY_CLICKED_ELEMENT_ID_BASE = 'gx-hm-secondary-clicked-element';
1119
+ const HOVERED_ELEMENT_ID_BASE = 'gx-hm-hovered-element';
1120
+ const SECONDARY_HOVERED_ELEMENT_ID_BASE = 'gx-hm-secondary-hovered-element';
1121
+
1122
+ /**
1123
+ * Get color from click distribution percentage (0-100)
1124
+ */
1125
+ function getColorFromClickDist(clickDist) {
1126
+ // Ensure clickDist is in range [0, 100]
1127
+ const normalizedDist = Math.max(0, Math.min(100, clickDist));
1128
+ console.log(`🚀 🐥 ~ getColorFromClickDist ~ normalizedDist:`, normalizedDist);
1129
+ // Calculate gradient index
1130
+ const maxIndex = AREA_COLOR_GRADIENT.length - 1;
1131
+ const index = Math.floor((normalizedDist / 100) * maxIndex);
1132
+ const clampedIndex = Math.min(index, maxIndex);
1133
+ const [r, g, b] = AREA_COLOR_GRADIENT[clampedIndex];
1134
+ // Return rgba with 60% opacity
1135
+ return `rgba(${r}, ${g}, ${b}, 0.6)`;
1136
+ }
1137
+ /**
1138
+ * Get hover color (slightly lighter) from click distribution
1139
+ */
1140
+ function getHoverColorFromClickDist(clickDist) {
1141
+ const normalizedDist = Math.max(0, Math.min(100, clickDist));
1142
+ const maxIndex = AREA_COLOR_GRADIENT.length - 1;
1143
+ const index = Math.floor((normalizedDist / 100) * maxIndex);
1144
+ const clampedIndex = Math.min(index, maxIndex);
1145
+ const [r, g, b] = AREA_COLOR_GRADIENT[clampedIndex];
1146
+ // Return rgba with 80% opacity for hover
1147
+ return `rgba(${r}, ${g}, ${b}, 0.8)`;
1148
+ }
1149
+ /**
1150
+ * Calculate click distribution percentage from total clicks
1151
+ */
1152
+ function calculateClickDistribution(elementClicks, totalClicks) {
1153
+ if (totalClicks === 0)
1154
+ return 0;
1155
+ return (elementClicks / totalClicks) * 100;
1156
+ }
1157
+
1158
+ function getElementRect(element, _shadowRoot) {
1159
+ const rect = element.getBoundingClientRect();
1160
+ const width = rect.width;
1161
+ const height = rect.height;
1162
+ const doc = element.ownerDocument || document;
1163
+ const scrollTop = doc.documentElement?.scrollTop || doc.body?.scrollTop || 0;
1164
+ const scrollLeft = doc.documentElement?.scrollLeft || doc.body?.scrollLeft || 0;
1165
+ const top = rect.top + scrollTop;
1166
+ const left = rect.left + scrollLeft;
1167
+ const absoluteLeft = left;
1168
+ const absoluteTop = top;
1169
+ const absoluteRight = absoluteLeft + width;
1170
+ const absoluteBottom = absoluteTop + height;
1171
+ return {
1172
+ width,
1173
+ height,
1174
+ top,
1175
+ left,
1176
+ absoluteLeft,
1177
+ absoluteTop,
1178
+ absoluteRight,
1179
+ absoluteBottom,
1180
+ outOfBounds: false,
1181
+ };
1182
+ }
1183
+ function isElementFixed(element) {
1184
+ if (getComputedStyle(element).position === 'fixed') {
1185
+ return true;
1186
+ }
1187
+ if (element.nodeName === 'HTML') {
1188
+ return false;
1189
+ }
1190
+ const parent = element.parentElement;
1191
+ return parent ? isElementFixed(parent) : false;
1192
+ }
1193
+ function doAreasOverlap(area1, area2) {
1194
+ const r1 = area1.rect.value;
1195
+ const r2 = area2.rect.value;
1196
+ if (!r1 || !r2)
1197
+ return false;
1198
+ return ((r1.absoluteBottom > r2.absoluteTop &&
1199
+ r1.absoluteTop < r2.absoluteBottom &&
1200
+ r1.absoluteRight > r2.absoluteLeft &&
1201
+ r1.absoluteLeft < r2.absoluteRight) ||
1202
+ (r2.absoluteBottom > r1.absoluteTop &&
1203
+ r2.absoluteTop < r1.absoluteBottom &&
1204
+ r2.absoluteRight > r1.absoluteLeft &&
1205
+ r2.absoluteLeft < r1.absoluteRight));
1206
+ }
1207
+ function isAreaContainedIn(area1, area2) {
1208
+ const r1 = area1.rect.value;
1209
+ const r2 = area2.rect.value;
1210
+ if (!r1 || !r2)
1211
+ return false;
1212
+ return (r1.absoluteTop >= r2.absoluteTop &&
1213
+ r1.absoluteBottom <= r2.absoluteBottom &&
1214
+ r1.absoluteLeft >= r2.absoluteLeft &&
1215
+ r1.absoluteRight <= r2.absoluteRight);
1216
+ }
1217
+ function isElementAncestorOf(ancestor, descendant, doc) {
1218
+ return ancestor.contains(descendant);
1219
+ }
1220
+ function isElementSelectable(element, index, elements) {
1221
+ if (isIgnoredCanvas(element)) {
1222
+ return false;
1223
+ }
1224
+ if (element.hasAttribute(AREA_MAP_DIV_ATTRIBUTE)) {
1225
+ return false;
1226
+ }
1227
+ if (index === 0 && elements.length > 1 && element.nodeName === 'BODY') {
1228
+ return false;
1229
+ }
1230
+ return true;
1231
+ }
1232
+ function sortAreasByClickDist(areas) {
1233
+ return [...areas].sort((a, b) => {
1234
+ if (a.clickDist !== b.clickDist) {
1235
+ return b.clickDist - a.clickDist;
1236
+ }
1237
+ return b.totalclicks - a.totalclicks;
1238
+ });
1239
+ }
1240
+ function isRectTooSmallForLabel(rect) {
1241
+ return rect.width < 67 || rect.height < 30;
1242
+ }
1243
+
1244
+ function getElementSelector(element) {
1245
+ if (element.id) {
1246
+ return `#${element.id}`;
1247
+ }
1248
+ if (element.className) {
1249
+ const classes = Array.from(element.classList).join('.');
1250
+ if (classes) {
1251
+ return `${element.tagName.toLowerCase()}.${classes}`;
1252
+ }
1253
+ }
1254
+ return element.tagName.toLowerCase();
1255
+ }
1256
+ function buildAreaNode(element, hash, heatmapInfo, shadowRoot) {
1257
+ if (!heatmapInfo.elementMapInfo)
1258
+ return;
1259
+ const totalClicks = heatmapInfo.totalClicks || 0;
1260
+ const elementInfo = heatmapInfo.elementMapInfo[hash];
1261
+ const elementClicks = elementInfo?.totalclicks || 0;
1262
+ const clickDist = calculateClickDistribution(elementClicks, totalClicks);
1263
+ const rect = getElementRect(element);
1264
+ const color = getColorFromClickDist(clickDist);
1265
+ const hoverColor = getHoverColorFromClickDist(clickDist);
1266
+ const areaNode = {
1267
+ kind: 'area',
1268
+ id: `${hash}_${Date.now()}`,
1269
+ hash,
1270
+ selector: elementInfo?.selector || getElementSelector(element),
1271
+ // DOM references
1272
+ element,
1273
+ areaElement: null,
1274
+ shadowElement: null,
1275
+ shadowStyleElement: null,
1276
+ // Graph structure
1277
+ parentNode: null,
1278
+ childNodes: new Set(),
1279
+ // Position
1280
+ rect: createObservable(rect),
1281
+ isFixed: isElementFixed(element),
1282
+ priority: false,
1283
+ // Click tracking
1284
+ totalclicks: elementClicks,
1285
+ cumulativeClicks: elementClicks,
1286
+ cumulativeMaxClicks: totalClicks,
1287
+ clickDist,
1288
+ hasClickInfo: true,
1289
+ // Visual
1290
+ color,
1291
+ hoverColor,
1292
+ // Lifecycle
1293
+ changeObserver: null,
1294
+ };
1295
+ return areaNode;
1296
+ }
1297
+ function getTopElementsByClicks(elementMapInfo, topN = 10) {
1298
+ const elements = Object.entries(elementMapInfo)
1299
+ .map(([hash, info]) => ({
1300
+ hash,
1301
+ totalclicks: info.totalclicks,
1302
+ selector: info.selector || '',
1303
+ }))
1304
+ .sort((a, b) => b.totalclicks - a.totalclicks)
1305
+ .slice(0, topN);
1306
+ return elements;
1307
+ }
1308
+
1309
+ /**
1310
+ * Resolve overlapping areas by priority rules
1311
+ *
1312
+ * Priority Rules (in order):
1313
+ * 1. Priority flag (manually set areas win)
1314
+ * 2. Click distribution (higher % wins)
1315
+ * 3. Total clicks (more clicks wins)
1316
+ * 4. DOM containment (parent contains child, parent wins)
1317
+ * 5. Size (smaller areas win - more specific)
1318
+ */
1319
+ function resolveOverlaps(areas, iframeDocument) {
1320
+ if (areas.length === 0)
1321
+ return [];
1322
+ // Group overlapping areas
1323
+ const overlapGroups = findOverlapGroups(areas);
1324
+ // Resolve each group
1325
+ const visibleAreas = new Set();
1326
+ overlapGroups.forEach((group) => {
1327
+ const winner = resolveOverlapGroup(group, iframeDocument);
1328
+ visibleAreas.add(winner);
1329
+ });
1330
+ // Add non-overlapping areas
1331
+ areas.forEach((area) => {
1332
+ const hasOverlap = overlapGroups.some((group) => group.areas.includes(area));
1333
+ if (!hasOverlap) {
1334
+ visibleAreas.add(area);
1335
+ }
1336
+ });
1337
+ return Array.from(visibleAreas);
1338
+ }
1339
+ /**
1340
+ * Find groups of overlapping areas
1341
+ */
1342
+ function findOverlapGroups(areas) {
1343
+ const groups = [];
1344
+ const processed = new Set();
1345
+ areas.forEach((area) => {
1346
+ if (processed.has(area.id))
1347
+ return;
1348
+ // Find all areas that overlap with this one
1349
+ const overlapping = areas.filter((other) => other.id !== area.id && doAreasOverlap(area, other));
1350
+ if (overlapping.length === 0) {
1351
+ // No overlap, skip grouping
1352
+ return;
1353
+ }
1354
+ // Create group with this area and all overlapping
1355
+ const groupAreas = [area, ...overlapping];
1356
+ groupAreas.forEach((a) => processed.add(a.id));
1357
+ // Placeholder - will be resolved later
1358
+ groups.push({
1359
+ areas: groupAreas,
1360
+ winner: area,
1361
+ hidden: [],
1362
+ });
1363
+ });
1364
+ return groups;
1365
+ }
1366
+ /**
1367
+ * Resolve a single overlap group to find the winner
1368
+ */
1369
+ function resolveOverlapGroup(group, iframeDocument) {
1370
+ const { areas } = group;
1371
+ if (areas.length === 1)
1372
+ return areas[0];
1373
+ // Sort by priority rules
1374
+ const sorted = [...areas].sort((a, b) => {
1375
+ // Rule 1: Priority flag
1376
+ if (a.priority !== b.priority) {
1377
+ return a.priority ? -1 : 1;
1378
+ }
1379
+ // Rule 2: Click distribution
1380
+ if (a.clickDist !== b.clickDist) {
1381
+ return b.clickDist - a.clickDist;
1382
+ }
1383
+ // Rule 3: Total clicks
1384
+ if (a.totalclicks !== b.totalclicks) {
1385
+ return b.totalclicks - a.totalclicks;
1386
+ }
1387
+ // Rule 4: DOM containment - parent beats child
1388
+ if (iframeDocument) {
1389
+ const aContainsB = isElementAncestorOf(a.element, b.element);
1390
+ const bContainsA = isElementAncestorOf(b.element, a.element);
1391
+ if (aContainsB)
1392
+ return -1; // a is parent, a wins
1393
+ if (bContainsA)
1394
+ return 1; // b is parent, b wins
1395
+ }
1396
+ // Rule 5: Size - smaller (more specific) wins
1397
+ const aSize = (a.rect.value?.width || 0) * (a.rect.value?.height || 0);
1398
+ const bSize = (b.rect.value?.width || 0) * (b.rect.value?.height || 0);
1399
+ return aSize - bSize;
1400
+ });
1401
+ const winner = sorted[0];
1402
+ group.winner = winner;
1403
+ group.hidden = sorted.slice(1);
1404
+ return winner;
1405
+ }
1406
+ /**
1407
+ * Filter out areas that are completely contained within others
1408
+ * and have lower priority
1409
+ */
1410
+ function filterContainedAreas(areas) {
1411
+ const visible = [];
1412
+ areas.forEach((area) => {
1413
+ // Check if this area is contained by a higher priority area
1414
+ const isContained = areas.some((other) => {
1415
+ if (other.id === area.id)
1416
+ return false;
1417
+ // Check containment
1418
+ if (!isAreaContainedIn(area, other))
1419
+ return false;
1420
+ // Check priority
1421
+ if (other.priority && !area.priority)
1422
+ return true;
1423
+ if (!other.priority && area.priority)
1424
+ return false;
1425
+ // Compare by click dist
1426
+ if (other.clickDist > area.clickDist)
1427
+ return true;
1428
+ if (other.clickDist < area.clickDist)
1429
+ return false;
1430
+ // Compare by total clicks
1431
+ return other.totalclicks > area.totalclicks;
1432
+ });
1433
+ if (!isContained) {
1434
+ visible.push(area);
1435
+ }
1436
+ });
1437
+ return visible;
1438
+ }
1439
+ /**
1440
+ * Get visible areas after resolving overlaps
1441
+ */
1442
+ function getVisibleAreas(areas, iframeDocument) {
1443
+ // First pass: filter contained areas
1444
+ let visible = filterContainedAreas(areas);
1445
+ // Second pass: resolve overlaps
1446
+ visible = resolveOverlaps(visible, iframeDocument);
1447
+ // Sort by click dist for rendering order
1448
+ return sortAreasByClickDist(visible);
1449
+ }
1450
+
1451
+ /**
1452
+ * Helper functions for setting up area renderer
1453
+ */
1454
+ /**
1455
+ * Create the outer container for area rendering
1456
+ */
1457
+ function createAreaContainer(iframeDocument) {
1458
+ const container = iframeDocument.createElement('div');
1459
+ container.setAttribute(AREA_MAP_DIV_ATTRIBUTE, 'true');
1460
+ container.style.cssText = AREA_CONTAINER_STYLES;
1461
+ return container;
1462
+ }
1463
+ /**
1464
+ * Create the inner container for React portal
1465
+ */
1466
+ function createInnerContainer(iframeDocument) {
1467
+ const innerContainer = iframeDocument.createElement('div');
1468
+ innerContainer.className = AREA_RENDERER_SELECTORS.innerContainerClass;
1469
+ innerContainer.style.cssText = AREA_INNER_CONTAINER_STYLES;
1470
+ return innerContainer;
1471
+ }
1472
+ /**
1473
+ * Get or create the outer container element
1474
+ */
1475
+ function getOrCreateAreaContainer(iframeDocument, customShadowRoot) {
1476
+ let container = iframeDocument.querySelector(AREA_RENDERER_SELECTORS.containerSelector);
1477
+ if (!container) {
1478
+ container = createAreaContainer(iframeDocument);
1479
+ const targetRoot = customShadowRoot || iframeDocument.body;
1480
+ if (targetRoot) {
1481
+ targetRoot.appendChild(container);
1482
+ }
1483
+ }
1484
+ return container;
1485
+ }
1486
+ function getOrCreateContainerShadowRoot(container) {
1487
+ if (container.shadowRoot) {
1488
+ return container.shadowRoot;
1489
+ }
1490
+ return container.attachShadow({ mode: 'open' });
1491
+ }
1492
+ function getOrCreateInnerContainer(shadowRoot, iframeDocument) {
1493
+ let innerContainer = shadowRoot.querySelector(AREA_RENDERER_SELECTORS.innerContainerSelector);
1494
+ if (!innerContainer) {
1495
+ innerContainer = createInnerContainer(iframeDocument);
1496
+ shadowRoot.appendChild(innerContainer);
1497
+ }
1498
+ return innerContainer;
1499
+ }
1500
+ function setupAreaRenderingContainer(iframeDocument, customShadowRoot) {
1501
+ const container = getOrCreateAreaContainer(iframeDocument, customShadowRoot);
1502
+ const shadowRoot = getOrCreateContainerShadowRoot(container);
1503
+ const innerContainer = getOrCreateInnerContainer(shadowRoot, iframeDocument);
1504
+ return innerContainer;
1505
+ }
1506
+ function cleanupAreaRenderingContainer(container) {
1507
+ if (container && container.parentNode) {
1508
+ container.parentNode.removeChild(container);
1509
+ }
1510
+ }
1511
+
906
1512
  function findLastSizeOfDom(data) {
907
1513
  const listDocs = data
908
1514
  .filter((item) => item.doc?.find((doc) => doc.data.width && doc.data.height))
@@ -927,44 +1533,33 @@ function findLastSizeOfDom(data) {
927
1533
  },
928
1534
  };
929
1535
  }
930
- function decodePayloads(payload) {
931
- try {
932
- return decode(payload);
933
- }
934
- catch (error) {
935
- return null;
936
- }
937
- }
938
-
939
- function calculateRankPosition(rect, widthScale) {
940
- const top = rect.top <= 18 ? rect.top + 3 : rect.top - 18;
941
- const left = rect.left <= 18 ? rect.left + 3 : rect.left - 18;
942
- return {
943
- transform: `scale(${1.2 * widthScale})`,
944
- top: Number.isNaN(top) ? undefined : top,
945
- left: Number.isNaN(left) ? undefined : left,
946
- };
947
- }
948
- function isElementInViewport(elementRect, visualRef, scale) {
949
- if (!elementRect)
950
- return false;
951
- const visualRect = visualRef.current?.getBoundingClientRect();
952
- if (!visualRect)
953
- return false;
954
- // Element position relative to the document (or container's content)
955
- const elementTop = elementRect.top * scale;
956
- const elementBottom = (elementRect.top + elementRect.height) * scale;
957
- // Current scroll position
958
- const scrollTop = visualRef.current?.scrollTop || 0;
959
- const viewportHeight = visualRect.height;
960
- // Visible viewport range in the scrollable content
961
- const viewportTop = scrollTop;
962
- const viewportBottom = scrollTop + viewportHeight;
963
- // Check if element is within the visible viewport
964
- // Element is visible if it overlaps with the viewport
965
- return elementBottom > viewportTop && elementTop < viewportBottom;
1536
+ function decodePayloads(payload) {
1537
+ try {
1538
+ return decode(payload);
1539
+ }
1540
+ catch (_error) {
1541
+ return null;
1542
+ }
966
1543
  }
967
1544
 
1545
+ /**
1546
+ * Generate unique element ID for a specific view
1547
+ * @param baseId - Base element ID
1548
+ * @param viewId - View ID
1549
+ * @returns Unique element ID (e.g., 'gx-hm-clicked-element-view-0')
1550
+ */
1551
+ const getElementId = (baseId, viewId) => {
1552
+ return `${baseId}-${viewId}`;
1553
+ };
1554
+ const getClickedElementId = (viewId, isSecondary = false) => {
1555
+ const baseId = isSecondary ? SECONDARY_CLICKED_ELEMENT_ID_BASE : CLICKED_ELEMENT_ID_BASE;
1556
+ return getElementId(baseId, viewId);
1557
+ };
1558
+ const getHoveredElementId = (viewId, isSecondary = false) => {
1559
+ const baseId = isSecondary ? SECONDARY_HOVERED_ELEMENT_ID_BASE : HOVERED_ELEMENT_ID_BASE;
1560
+ return getElementId(baseId, viewId);
1561
+ };
1562
+
968
1563
  function getElementLayout(element) {
969
1564
  if (!element?.getBoundingClientRect)
970
1565
  return null;
@@ -978,23 +1573,6 @@ function getElementLayout(element) {
978
1573
  height: rect.height,
979
1574
  };
980
1575
  }
981
- const getElementAtPoint = (doc, x, y) => {
982
- let el = null;
983
- if ('caretPositionFromPoint' in doc) {
984
- el = doc.caretPositionFromPoint(x, y)?.offsetNode ?? null;
985
- }
986
- el = el ?? doc.elementFromPoint(x, y);
987
- let element = el;
988
- while (element && element.nodeType === Node.TEXT_NODE) {
989
- element = element.parentElement;
990
- }
991
- return element;
992
- };
993
- function getElementHash(element) {
994
- return (element.getAttribute('data-clarity-hash') ||
995
- element.getAttribute('data-clarity-hashalpha') ||
996
- element.getAttribute('data-clarity-hashbeta'));
997
- }
998
1576
  const getElementRank = (hash, elements) => {
999
1577
  if (!elements)
1000
1578
  return 0;
@@ -1021,9 +1599,15 @@ const buildElementInfo = (hash, rect, heatmapInfo) => {
1021
1599
  };
1022
1600
  };
1023
1601
 
1024
- const PADDING = 0;
1025
- const ARROW_SIZE = 8;
1026
- const HORIZONTAL_OFFSET = 0;
1602
+ function calculateRankPosition(rect, widthScale) {
1603
+ const top = rect.top <= 18 ? rect.top + 3 : rect.top - 18;
1604
+ const left = rect.left <= 18 ? rect.left + 3 : rect.left - 18;
1605
+ return {
1606
+ transform: `scale(${1.2 * widthScale})`,
1607
+ top: Number.isNaN(top) ? undefined : top,
1608
+ left: Number.isNaN(left) ? undefined : left,
1609
+ };
1610
+ }
1027
1611
 
1028
1612
  const getViewportDimensions = (containerElm) => {
1029
1613
  if (containerElm) {
@@ -1079,7 +1663,8 @@ const calculateHorizontalPlacementPosition = (targetRect, calloutRect, placement
1079
1663
 
1080
1664
  const isLeftPositionValid = (leftPos, calloutWidth, viewportWidth, padding, containerRect) => {
1081
1665
  if (containerRect) {
1082
- return leftPos >= containerRect.left + padding && leftPos + calloutWidth <= containerRect.right - padding;
1666
+ return (leftPos >= containerRect.left + padding &&
1667
+ leftPos + calloutWidth <= containerRect.right - padding);
1083
1668
  }
1084
1669
  return leftPos >= padding && leftPos + calloutWidth <= viewportWidth - padding;
1085
1670
  };
@@ -1164,14 +1749,15 @@ const constrainToViewport = (position, calloutRect, viewport, padding, container
1164
1749
  return { top, left };
1165
1750
  };
1166
1751
 
1167
- const calcCalloutPosition = ({ targetElm, calloutElm, setPosition, hozOffset = HORIZONTAL_OFFSET, alignment = 'center', containerElm, }) => {
1752
+ const calcCalloutPosition = (options) => {
1753
+ const { targetElm, calloutElm, setPosition, hozOffset = CALLOUT_HORIZONTAL_OFFSET, alignment = 'center', containerElm, } = options;
1168
1754
  return () => {
1169
1755
  // 1. Get dimensions
1170
1756
  const rectDimensions = getElementDimensions(targetElm, calloutElm);
1171
1757
  const viewport = getViewportDimensions(containerElm);
1172
1758
  const containerRect = containerElm?.getBoundingClientRect();
1173
- const padding = PADDING;
1174
- const arrowSize = ARROW_SIZE;
1759
+ const padding = CALLOUT_PADDING;
1760
+ const arrowSize = CALLOUT_ARROW_SIZE;
1175
1761
  // 2. Generate all position candidates
1176
1762
  const candidates = generateAllPositionCandidates(rectDimensions, viewport, alignment, hozOffset, padding, arrowSize, containerRect);
1177
1763
  // 3. Select best position
@@ -1189,6 +1775,191 @@ const calcCalloutPosition = ({ targetElm, calloutElm, setPosition, hozOffset = H
1189
1775
  };
1190
1776
  };
1191
1777
 
1778
+ /**
1779
+ * Get all elements at a specific point (x, y), with support for Shadow DOM
1780
+ */
1781
+ function getElementsAtPoint(doc, x, y, options = {}) {
1782
+ const { filterFn, ignoreCanvas = true, visitedShadowRoots = new Set() } = options;
1783
+ // Get all elements at this point
1784
+ let elementsAtPoint = doc.elementsFromPoint(x, y);
1785
+ // Filter out canvas elements if requested
1786
+ if (ignoreCanvas) {
1787
+ elementsAtPoint = elementsAtPoint.filter((el) => !isIgnoredCanvas(el));
1788
+ }
1789
+ // Apply custom filter if provided
1790
+ if (filterFn) {
1791
+ const matchedElement = elementsAtPoint.find(filterFn);
1792
+ // If matched element has Shadow DOM and we haven't visited it yet, recurse
1793
+ if (matchedElement?.shadowRoot && !visitedShadowRoots.has(matchedElement.shadowRoot)) {
1794
+ visitedShadowRoots.add(matchedElement.shadowRoot);
1795
+ return getElementsAtPoint(matchedElement.shadowRoot, x, y, {
1796
+ ...options,
1797
+ visitedShadowRoots,
1798
+ });
1799
+ }
1800
+ }
1801
+ return elementsAtPoint;
1802
+ }
1803
+ /**
1804
+ * Get the element at a specific point (x, y)
1805
+ */
1806
+ const getElementAtPoint = (doc, x, y) => {
1807
+ let el = null;
1808
+ if ('caretPositionFromPoint' in doc) {
1809
+ el = doc.caretPositionFromPoint(x, y)?.offsetNode ?? null;
1810
+ }
1811
+ el = el ?? doc.elementFromPoint(x, y);
1812
+ let element = el;
1813
+ while (element && element.nodeType === Node.TEXT_NODE) {
1814
+ element = element.parentElement;
1815
+ }
1816
+ return element;
1817
+ };
1818
+ function getElementHash(element) {
1819
+ return (element.getAttribute('data-clarity-hash') ||
1820
+ element.getAttribute('data-clarity-hashalpha') ||
1821
+ element.getAttribute('data-clarity-hashbeta'));
1822
+ }
1823
+
1824
+ class Logger {
1825
+ config = {
1826
+ enabled: false,
1827
+ prefix: '',
1828
+ timestamp: false,
1829
+ };
1830
+ /**
1831
+ * Cấu hình logger
1832
+ * @param config - Cấu hình logger
1833
+ */
1834
+ configure(config) {
1835
+ this.config = { ...this.config, ...config };
1836
+ }
1837
+ /**
1838
+ * Lấy cấu hình hiện tại
1839
+ */
1840
+ getConfig() {
1841
+ return { ...this.config };
1842
+ }
1843
+ /**
1844
+ * Bật logger
1845
+ */
1846
+ enable() {
1847
+ this.config.enabled = true;
1848
+ }
1849
+ /**
1850
+ * Tắt logger
1851
+ */
1852
+ disable() {
1853
+ this.config.enabled = false;
1854
+ }
1855
+ /**
1856
+ * Format message với prefix và timestamp
1857
+ */
1858
+ formatMessage(...args) {
1859
+ const parts = [];
1860
+ if (this.config.timestamp) {
1861
+ parts.push(`[${new Date().toISOString()}]`);
1862
+ }
1863
+ if (this.config.prefix) {
1864
+ parts.push(`[${this.config.prefix}]`);
1865
+ }
1866
+ if (parts.length > 0) {
1867
+ return [parts.join(' '), ...args];
1868
+ }
1869
+ return args;
1870
+ }
1871
+ /**
1872
+ * Log message
1873
+ */
1874
+ log(...args) {
1875
+ if (!this.config.enabled)
1876
+ return;
1877
+ console.log(...this.formatMessage(...args));
1878
+ }
1879
+ /**
1880
+ * Log info message
1881
+ */
1882
+ info(...args) {
1883
+ if (!this.config.enabled)
1884
+ return;
1885
+ console.info(...this.formatMessage(...args));
1886
+ }
1887
+ /**
1888
+ * Log warning message
1889
+ */
1890
+ warn(...args) {
1891
+ if (!this.config.enabled)
1892
+ return;
1893
+ console.warn(...this.formatMessage(...args));
1894
+ }
1895
+ /**
1896
+ * Log error message
1897
+ */
1898
+ error(...args) {
1899
+ if (!this.config.enabled)
1900
+ return;
1901
+ console.error(...this.formatMessage(...args));
1902
+ }
1903
+ /**
1904
+ * Log debug message
1905
+ */
1906
+ debug(...args) {
1907
+ if (!this.config.enabled)
1908
+ return;
1909
+ console.debug(...this.formatMessage(...args));
1910
+ }
1911
+ /**
1912
+ * Log table data
1913
+ */
1914
+ table(data) {
1915
+ if (!this.config.enabled)
1916
+ return;
1917
+ console.table(data);
1918
+ }
1919
+ /**
1920
+ * Start a group
1921
+ */
1922
+ group(label) {
1923
+ if (!this.config.enabled)
1924
+ return;
1925
+ console.group(...this.formatMessage(label));
1926
+ }
1927
+ /**
1928
+ * Start a collapsed group
1929
+ */
1930
+ groupCollapsed(label) {
1931
+ if (!this.config.enabled)
1932
+ return;
1933
+ console.groupCollapsed(...this.formatMessage(label));
1934
+ }
1935
+ /**
1936
+ * End a group
1937
+ */
1938
+ groupEnd() {
1939
+ if (!this.config.enabled)
1940
+ return;
1941
+ console.groupEnd();
1942
+ }
1943
+ /**
1944
+ * Start a timer
1945
+ */
1946
+ time(label) {
1947
+ if (!this.config.enabled)
1948
+ return;
1949
+ console.time(label);
1950
+ }
1951
+ /**
1952
+ * End a timer
1953
+ */
1954
+ timeEnd(label) {
1955
+ if (!this.config.enabled)
1956
+ return;
1957
+ console.timeEnd(label);
1958
+ }
1959
+ }
1960
+ // Export singleton instance
1961
+ const logger = new Logger();
1962
+
1192
1963
  class IframeNavigationBlockerV2 {
1193
1964
  doc;
1194
1965
  win;
@@ -1196,17 +1967,18 @@ class IframeNavigationBlockerV2 {
1196
1967
  showMessage = false;
1197
1968
  originalWindowOpen;
1198
1969
  observers = [];
1199
- constructor(iframe) {
1970
+ constructor(iframe, config) {
1200
1971
  if (!iframe.contentDocument || !iframe.contentWindow) {
1201
1972
  throw new Error('Iframe document or window not accessible');
1202
1973
  }
1203
1974
  this.doc = iframe.contentDocument;
1204
1975
  this.win = iframe.contentWindow;
1205
1976
  this.originalWindowOpen = this.win.open.bind(this.win);
1977
+ logger.configure({ enabled: !!config?.debug, prefix: '[IframeNavigationBlockerV2]' });
1206
1978
  this.init();
1207
1979
  }
1208
1980
  init() {
1209
- console.log('[NavigationBlocker] Initializing...');
1981
+ logger.log('Initializing...');
1210
1982
  try {
1211
1983
  // Chặn navigation qua links
1212
1984
  this.blockLinkNavigation();
@@ -1222,7 +1994,7 @@ class IframeNavigationBlockerV2 {
1222
1994
  this.injectCSP();
1223
1995
  }
1224
1996
  catch (error) {
1225
- console.error('[NavigationBlocker] Init error:', error);
1997
+ logger.error('Init error:', error);
1226
1998
  }
1227
1999
  }
1228
2000
  blockLinkNavigation() {
@@ -1236,11 +2008,11 @@ class IframeNavigationBlockerV2 {
1236
2008
  const href = link.getAttribute('href');
1237
2009
  // Cho phép hash links và empty links
1238
2010
  if (!href || href === '' || href === '#' || href.startsWith('#')) {
1239
- console.log('[NavigationBlocker] Allowed hash navigation:', href);
2011
+ logger.log('Allowed hash navigation:', href);
1240
2012
  return;
1241
2013
  }
1242
2014
  // Chặn tất cả các loại navigation
1243
- console.log('[NavigationBlocker] Blocked link navigation to:', href);
2015
+ logger.log('Blocked link navigation to:', href);
1244
2016
  e.preventDefault();
1245
2017
  e.stopPropagation();
1246
2018
  e.stopImmediatePropagation();
@@ -1256,7 +2028,7 @@ class IframeNavigationBlockerV2 {
1256
2028
  if (link) {
1257
2029
  const href = link.getAttribute('href');
1258
2030
  if (href && !href.startsWith('#')) {
1259
- console.log('[NavigationBlocker] Blocked auxclick navigation');
2031
+ logger.log('Blocked auxclick navigation');
1260
2032
  e.preventDefault();
1261
2033
  e.stopPropagation();
1262
2034
  e.stopImmediatePropagation();
@@ -1289,13 +2061,13 @@ class IframeNavigationBlockerV2 {
1289
2061
  const action = form.getAttribute('action');
1290
2062
  // Cho phép forms không có action
1291
2063
  if (!action || action === '' || action === '#') {
1292
- console.log('[NavigationBlocker] Allowed same-page form');
2064
+ logger.log('Allowed same-page form');
1293
2065
  e.preventDefault();
1294
2066
  this.handleFormSubmit(form);
1295
2067
  return;
1296
2068
  }
1297
2069
  // Chặn tất cả external submissions
1298
- console.log('[NavigationBlocker] Blocked form submission to:', action);
2070
+ logger.log('Blocked form submission to:', action);
1299
2071
  e.preventDefault();
1300
2072
  e.stopPropagation();
1301
2073
  e.stopImmediatePropagation();
@@ -1309,7 +2081,7 @@ class IframeNavigationBlockerV2 {
1309
2081
  return this.originalWindowOpen(...args);
1310
2082
  }
1311
2083
  const url = args[0]?.toString() || 'popup';
1312
- console.log('[NavigationBlocker] Blocked window.open:', url);
2084
+ logger.log('Blocked window.open:', url);
1313
2085
  this.notifyBlockedNavigation(url);
1314
2086
  return null;
1315
2087
  });
@@ -1319,7 +2091,7 @@ class IframeNavigationBlockerV2 {
1319
2091
  this.win.addEventListener('beforeunload', (e) => {
1320
2092
  if (!this.isEnabled)
1321
2093
  return;
1322
- console.log('[NavigationBlocker] Blocked beforeunload');
2094
+ logger.log('Blocked beforeunload');
1323
2095
  e.preventDefault();
1324
2096
  e.returnValue = '';
1325
2097
  return '';
@@ -1328,7 +2100,7 @@ class IframeNavigationBlockerV2 {
1328
2100
  this.win.addEventListener('unload', (e) => {
1329
2101
  if (!this.isEnabled)
1330
2102
  return;
1331
- console.log('[NavigationBlocker] Blocked unload');
2103
+ logger.log('Blocked unload');
1332
2104
  e.preventDefault();
1333
2105
  e.stopPropagation();
1334
2106
  }, true);
@@ -1336,7 +2108,7 @@ class IframeNavigationBlockerV2 {
1336
2108
  this.win.addEventListener('popstate', (e) => {
1337
2109
  if (!this.isEnabled)
1338
2110
  return;
1339
- console.log('[NavigationBlocker] Blocked popstate');
2111
+ logger.log('Blocked popstate');
1340
2112
  e.preventDefault();
1341
2113
  e.stopPropagation();
1342
2114
  }, true);
@@ -1389,11 +2161,11 @@ class IframeNavigationBlockerV2 {
1389
2161
  meta.httpEquiv = 'Content-Security-Policy';
1390
2162
  meta.content = "navigate-to 'none'"; // Chặn tất cả navigation
1391
2163
  this.doc.head.appendChild(meta);
1392
- console.log('[NavigationBlocker] Injected CSP');
2164
+ logger.log('Injected CSP');
1393
2165
  }
1394
2166
  }
1395
2167
  catch (error) {
1396
- console.warn('[NavigationBlocker] Could not inject CSP:', error);
2168
+ logger.warn('Could not inject CSP:', error);
1397
2169
  }
1398
2170
  }
1399
2171
  handleFormSubmit(form) {
@@ -1402,13 +2174,13 @@ class IframeNavigationBlockerV2 {
1402
2174
  formData.forEach((value, key) => {
1403
2175
  data[key] = value;
1404
2176
  });
1405
- console.log('[NavigationBlocker] Handling form data:', data);
2177
+ logger.log('Handling form data:', data);
1406
2178
  window.dispatchEvent(new CustomEvent('iframe-form-submit', {
1407
2179
  detail: { form, data },
1408
2180
  }));
1409
2181
  }
1410
2182
  notifyBlockedNavigation(url) {
1411
- console.warn('[NavigationBlocker] Navigation blocked to:', url);
2183
+ logger.warn('Navigation blocked to:', url);
1412
2184
  window.dispatchEvent(new CustomEvent('iframe-navigation-blocked', {
1413
2185
  detail: { url, timestamp: Date.now() },
1414
2186
  }));
@@ -1458,19 +2230,19 @@ class IframeNavigationBlockerV2 {
1458
2230
  }
1459
2231
  enable() {
1460
2232
  this.isEnabled = true;
1461
- console.log('[NavigationBlocker] Enabled');
2233
+ logger.log('Enabled');
1462
2234
  }
1463
2235
  enableMessage() {
1464
2236
  this.showMessage = true;
1465
- console.log('[NavigationBlocker] Enabled message');
2237
+ logger.log('Enabled message');
1466
2238
  }
1467
2239
  disable() {
1468
2240
  this.isEnabled = false;
1469
- console.log('[NavigationBlocker] Disabled');
2241
+ logger.log('Disabled');
1470
2242
  }
1471
2243
  disableMessage() {
1472
2244
  this.showMessage = false;
1473
- console.log('[NavigationBlocker] Disabled message');
2245
+ logger.log('Disabled message');
1474
2246
  }
1475
2247
  destroy() {
1476
2248
  this.isEnabled = false;
@@ -1478,7 +2250,7 @@ class IframeNavigationBlockerV2 {
1478
2250
  // Cleanup observers
1479
2251
  this.observers.forEach((observer) => observer.disconnect());
1480
2252
  this.observers = [];
1481
- console.log('[NavigationBlocker] Destroyed');
2253
+ logger.log('Destroyed');
1482
2254
  }
1483
2255
  }
1484
2256
 
@@ -1494,6 +2266,7 @@ class IframeStyleReplacer {
1494
2266
  this.doc = iframe.contentDocument;
1495
2267
  this.win = iframe.contentWindow;
1496
2268
  this.config = config;
2269
+ logger.configure({ enabled: !!config?.debug, prefix: '[IframeStyleReplacer]' });
1497
2270
  }
1498
2271
  px(value) {
1499
2272
  return `${value.toFixed(2)}px`;
@@ -1527,7 +2300,7 @@ class IframeStyleReplacer {
1527
2300
  count++;
1528
2301
  }
1529
2302
  });
1530
- console.log(`[IframeStyleReplacer] Replaced ${count} inline style elements`);
2303
+ logger.log(`Replaced ${count} inline style elements`);
1531
2304
  return count;
1532
2305
  }
1533
2306
  processStyleTags() {
@@ -1540,7 +2313,7 @@ class IframeStyleReplacer {
1540
2313
  count++;
1541
2314
  }
1542
2315
  });
1543
- console.log(`[IframeStyleReplacer] Replaced ${count} <style> tags`);
2316
+ logger.log(`Replaced ${count} <style> tags`);
1544
2317
  return count;
1545
2318
  }
1546
2319
  processRule(rule) {
@@ -1571,7 +2344,7 @@ class IframeStyleReplacer {
1571
2344
  try {
1572
2345
  // Bỏ qua external CSS (cross-origin)
1573
2346
  if (sheet.href && !sheet.href.startsWith(this.win.location.origin)) {
1574
- console.log('[IframeStyleReplacer] Skipping external CSS:', sheet.href);
2347
+ logger.log('Skipping external CSS:', sheet.href);
1575
2348
  return;
1576
2349
  }
1577
2350
  const rules = sheet.cssRules || sheet.rules;
@@ -1582,10 +2355,10 @@ class IframeStyleReplacer {
1582
2355
  }
1583
2356
  }
1584
2357
  catch (e) {
1585
- console.warn('[IframeStyleReplacer] Cannot read stylesheet (CORS?):', e.message);
2358
+ logger.warn('Cannot read stylesheet (CORS?):', e.message);
1586
2359
  }
1587
2360
  });
1588
- console.log(`[IframeStyleReplacer] Replaced ${total} rules in stylesheets`);
2361
+ logger.log(`Replaced ${total} rules in stylesheets`);
1589
2362
  return total;
1590
2363
  }
1591
2364
  async processLinkedStylesheets() {
@@ -1593,7 +2366,7 @@ class IframeStyleReplacer {
1593
2366
  let count = 0;
1594
2367
  for (const link of Array.from(links)) {
1595
2368
  if (!link.href.startsWith(this.win.location.origin)) {
1596
- console.log('[IframeStyleReplacer] Skipping external CSS:', link.href);
2369
+ logger.log('Skipping external CSS:', link.href);
1597
2370
  continue;
1598
2371
  }
1599
2372
  try {
@@ -1611,10 +2384,10 @@ class IframeStyleReplacer {
1611
2384
  }
1612
2385
  }
1613
2386
  catch (e) {
1614
- console.warn('[IframeStyleReplacer] Cannot load CSS:', link.href, e);
2387
+ logger.warn('Cannot load CSS:', link.href, e);
1615
2388
  }
1616
2389
  }
1617
- console.log(`[IframeStyleReplacer] Replaced ${count} linked CSS files`);
2390
+ logger.log(`Replaced ${count} linked CSS files`);
1618
2391
  return count;
1619
2392
  }
1620
2393
  getFinalHeight() {
@@ -1638,7 +2411,7 @@ class IframeStyleReplacer {
1638
2411
  }
1639
2412
  async run() {
1640
2413
  try {
1641
- console.log('[IframeStyleReplacer] Starting viewport units replacement...');
2414
+ logger.log('Starting viewport units replacement...');
1642
2415
  this.processInlineStyles();
1643
2416
  this.processStyleTags();
1644
2417
  this.processStylesheets();
@@ -1648,13 +2421,13 @@ class IframeStyleReplacer {
1648
2421
  requestAnimationFrame(() => {
1649
2422
  const height = this.getFinalHeight();
1650
2423
  const width = this.getFinalWidth();
1651
- console.log('[IframeStyleReplacer] Calculated dimensions:', { height, width });
2424
+ logger.log('Calculated dimensions:', { height, width });
1652
2425
  resolve({ height, width });
1653
2426
  });
1654
2427
  });
1655
2428
  }
1656
2429
  catch (err) {
1657
- console.error('[IframeStyleReplacer] Critical error:', err);
2430
+ logger.error('Critical error:', err);
1658
2431
  return {
1659
2432
  height: this.doc.body.scrollHeight || 1000,
1660
2433
  width: this.doc.body.scrollWidth || 1000,
@@ -1675,10 +2448,11 @@ class IframeHelperFixer {
1675
2448
  this.config = config;
1676
2449
  this.iframe = config.iframe;
1677
2450
  this.init();
2451
+ logger.configure({ enabled: !!config?.debug, prefix: '[IframeHelper]' });
1678
2452
  }
1679
2453
  async init() {
1680
2454
  if (!this.iframe) {
1681
- console.error('[IframeHelper] iframe not found');
2455
+ logger.error('iframe not found');
1682
2456
  this.config.onError?.(new Error('iframe not found'));
1683
2457
  return;
1684
2458
  }
@@ -1692,19 +2466,19 @@ class IframeHelperFixer {
1692
2466
  }
1693
2467
  async process() {
1694
2468
  if (!this.iframe.contentDocument || !this.iframe.contentWindow) {
1695
- console.error('[IframeHelper] Cannot access iframe document');
2469
+ logger.error('Cannot access iframe document');
1696
2470
  this.config.onError?.(new Error('Cannot access iframe document'));
1697
2471
  return;
1698
2472
  }
1699
2473
  try {
1700
- console.log('[IframeHelper] Processing viewport units...');
2474
+ logger.log('Processing viewport units...');
1701
2475
  // Create replacer instance
1702
2476
  this.replacer = new IframeStyleReplacer(this.iframe, this.config);
1703
2477
  // Create navigation blocker
1704
2478
  this.navigationBlocker = new IframeNavigationBlockerV2(this.iframe);
1705
2479
  // Run replacement
1706
2480
  const result = await this.replacer.run();
1707
- console.log('[IframeHelper] Process completed:', result);
2481
+ logger.log('Process completed:', result);
1708
2482
  // Trigger success callback
1709
2483
  this.config.onSuccess?.(result);
1710
2484
  // Dispatch custom event
@@ -1713,12 +2487,12 @@ class IframeHelperFixer {
1713
2487
  }));
1714
2488
  }
1715
2489
  catch (error) {
1716
- console.error('[IframeHelper] Failed to process:', error);
2490
+ logger.error('Failed to process:', error);
1717
2491
  this.config.onError?.(error);
1718
2492
  }
1719
2493
  }
1720
2494
  async recalculate() {
1721
- console.log('[IframeHelper] Recalculating...');
2495
+ logger.log('Recalculating...');
1722
2496
  await this.process();
1723
2497
  }
1724
2498
  updateConfig(config) {
@@ -1743,27 +2517,291 @@ class IframeHelperFixer {
1743
2517
  this.replacer = null;
1744
2518
  this.navigationBlocker?.destroy();
1745
2519
  this.navigationBlocker = null;
1746
- console.log('[IframeHelper] Destroyed');
2520
+ logger.log('Destroyed');
1747
2521
  }
1748
2522
  }
1749
2523
 
1750
2524
  function initIframeHelperFixer(config) {
1751
2525
  const fixer = new IframeHelperFixer(config);
1752
2526
  window.addEventListener('iframe-dimensions-applied', ((e) => {
1753
- const ev = e;
1754
- console.log('[IframeHelper] Iframe dimensions finalized:', ev.detail);
2527
+ // console.log('[IframeHelper] Iframe dimensions finalized:', ev.detail);
1755
2528
  }));
1756
2529
  window.addEventListener('iframe-navigation-blocked', ((e) => {
1757
- const ev = e;
1758
- console.warn('[IframeHelper] Iframe tried to navigate to:', ev.detail.url);
2530
+ // console.warn('[IframeHelper] Iframe tried to navigate to:', ev.detail.url);
1759
2531
  }));
1760
2532
  window.addEventListener('iframe-form-submit', ((e) => {
1761
- const ev = e;
1762
- console.log('[IframeHelper] Iframe form submitted:', ev.detail.data);
2533
+ // console.log('[IframeHelper] Iframe form submitted:', ev.detail.data);
1763
2534
  }));
1764
2535
  return fixer;
1765
2536
  }
1766
2537
 
2538
+ function useAreaCreation(options = {}) {
2539
+ const { customShadowRoot, onAreaCreated } = options;
2540
+ const { dataInfo } = useHeatmapData();
2541
+ const { areas, addArea } = useHeatmapAreaClick();
2542
+ const handleCreateAreaFromElement = useCallback((element) => {
2543
+ if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks) {
2544
+ logger.warn('Cannot create area: missing heatmap data');
2545
+ return;
2546
+ }
2547
+ const hash = getElementHash(element);
2548
+ if (!hash) {
2549
+ logger.warn('Cannot create area: missing hash');
2550
+ return;
2551
+ }
2552
+ const alreadyExists = areas.some((area) => area.hash === hash);
2553
+ if (alreadyExists) {
2554
+ logger.warn(`Area already exists for element: ${hash}`);
2555
+ return;
2556
+ }
2557
+ try {
2558
+ const area = buildAreaNode(element, hash, dataInfo, customShadowRoot);
2559
+ if (!area)
2560
+ return;
2561
+ addArea(area);
2562
+ if (onAreaCreated) {
2563
+ onAreaCreated(area);
2564
+ }
2565
+ }
2566
+ catch (error) {
2567
+ logger.error('Failed to create area:', error);
2568
+ }
2569
+ }, [dataInfo, areas, addArea, customShadowRoot]);
2570
+ return {
2571
+ onAreaCreatedElement: handleCreateAreaFromElement,
2572
+ };
2573
+ }
2574
+
2575
+ function useAreaEditMode({ iframeRef, onAreaCreatedElement, enabled = false, }) {
2576
+ const [hoveredElement, setHoveredElement] = useState(null);
2577
+ const [isHovering, setIsHovering] = useState(false);
2578
+ const { isEditingMode } = useHeatmapAreaClick();
2579
+ const iframeDocument = iframeRef.current?.contentDocument;
2580
+ const isActive = enabled && isEditingMode;
2581
+ const handleMouseMove = useCallback((e) => {
2582
+ if (!isActive || !iframeDocument)
2583
+ return;
2584
+ const elements = getElementsAtPoint(iframeDocument, e.clientX, e.clientY);
2585
+ const selectableElement = elements.find((el, index, arr) => isElementSelectable(el, index, arr));
2586
+ const isSelectable = selectableElement && selectableElement !== hoveredElement;
2587
+ if (isSelectable) {
2588
+ setHoveredElement(selectableElement);
2589
+ setIsHovering(true);
2590
+ return;
2591
+ }
2592
+ setHoveredElement(null);
2593
+ setIsHovering(false);
2594
+ }, [isActive, iframeDocument, hoveredElement]);
2595
+ const handleMouseLeave = useCallback(() => {
2596
+ setHoveredElement(null);
2597
+ setIsHovering(false);
2598
+ }, []);
2599
+ useCallback((e) => {
2600
+ if (!isActive || !hoveredElement)
2601
+ return;
2602
+ e.stopPropagation();
2603
+ e.preventDefault();
2604
+ if (!onAreaCreatedElement)
2605
+ return;
2606
+ onAreaCreatedElement(hoveredElement);
2607
+ }, [isActive, hoveredElement]);
2608
+ useEffect(() => {
2609
+ if (!isActive || !iframeDocument) {
2610
+ setHoveredElement(null);
2611
+ setIsHovering(false);
2612
+ return;
2613
+ }
2614
+ // Throttle mouse move
2615
+ let rafId = null;
2616
+ const throttledMouseMove = (e) => {
2617
+ if (rafId)
2618
+ return;
2619
+ rafId = requestAnimationFrame(() => {
2620
+ handleMouseMove(e);
2621
+ rafId = null;
2622
+ });
2623
+ };
2624
+ iframeDocument.addEventListener('mousemove', throttledMouseMove);
2625
+ iframeDocument.addEventListener('scroll', handleMouseLeave);
2626
+ iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
2627
+ // iframeDocument.addEventListener('click', handleClick);
2628
+ return () => {
2629
+ if (rafId) {
2630
+ cancelAnimationFrame(rafId);
2631
+ }
2632
+ iframeDocument.removeEventListener('mousemove', throttledMouseMove);
2633
+ iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
2634
+ iframeDocument.removeEventListener('scroll', handleMouseLeave);
2635
+ // iframeDocument.removeEventListener('click', handleClick);
2636
+ };
2637
+ }, [isActive, iframeDocument]);
2638
+ return {
2639
+ hoveredElement,
2640
+ isHovering,
2641
+ };
2642
+ }
2643
+
2644
+ const useAreaFilterVisible = (props) => {
2645
+ const { iframeRef, enableOverlapResolution } = props;
2646
+ const iframeDocument = iframeRef.current?.contentDocument;
2647
+ const { areas, setAreas } = useHeatmapAreaClick();
2648
+ const visibleAreas = useMemo(() => {
2649
+ if (!enableOverlapResolution)
2650
+ return areas;
2651
+ if (!iframeDocument)
2652
+ return areas;
2653
+ return getVisibleAreas(areas, iframeDocument);
2654
+ }, [areas, iframeDocument]);
2655
+ useEffect(() => {
2656
+ if (enableOverlapResolution && visibleAreas.length !== areas.length) {
2657
+ setAreas(visibleAreas);
2658
+ }
2659
+ }, [visibleAreas, areas.length]);
2660
+ return {};
2661
+ };
2662
+
2663
+ /**
2664
+ * Hook to handle area interaction (click, hover)
2665
+ *
2666
+ * @param options - Configuration options
2667
+ * @returns Event handlers for area interactions
2668
+ */
2669
+ function useAreaInteraction(options = {}) {
2670
+ const { onAreaClick } = options;
2671
+ const { selectedArea, hoveredArea, isEditingMode, setSelectedArea, setHoveredArea } = useHeatmapAreaClick();
2672
+ const handleAreaClick = useCallback((area) => {
2673
+ if (isEditingMode)
2674
+ return;
2675
+ // Toggle selection
2676
+ setSelectedArea(selectedArea?.id === area.id ? null : area);
2677
+ // Trigger callback
2678
+ if (onAreaClick) {
2679
+ onAreaClick(area);
2680
+ }
2681
+ }, [isEditingMode, selectedArea]);
2682
+ const handleAreaMouseEnter = useCallback((area) => {
2683
+ if (isEditingMode)
2684
+ return;
2685
+ setHoveredArea(area);
2686
+ }, [isEditingMode]);
2687
+ const handleAreaMouseLeave = useCallback((area) => {
2688
+ if (isEditingMode)
2689
+ return;
2690
+ // Only clear if this is the currently hovered area
2691
+ if (hoveredArea?.id === area.id) {
2692
+ setHoveredArea(null);
2693
+ }
2694
+ }, [isEditingMode, hoveredArea]);
2695
+ return {
2696
+ handleAreaClick,
2697
+ handleAreaMouseEnter,
2698
+ handleAreaMouseLeave,
2699
+ };
2700
+ }
2701
+
2702
+ function useAreaPortals(options) {
2703
+ const { shadowContainer, isReady, iframeRef, customShadowRoot, onAreaClick, onAreaCreated } = options;
2704
+ const { onAreaCreatedElement } = useAreaCreation({ customShadowRoot, onAreaCreated });
2705
+ const { hoveredElement } = useAreaEditMode({ iframeRef, enabled: true, onAreaCreatedElement });
2706
+ const { areas, selectedArea, hoveredArea, isEditingMode } = useHeatmapAreaClick();
2707
+ const { handleAreaClick, handleAreaMouseEnter, handleAreaMouseLeave } = useAreaInteraction({
2708
+ onAreaClick,
2709
+ });
2710
+ const isReadyPortal = shadowContainer && isReady;
2711
+ const areasPortal = isReadyPortal
2712
+ ? 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)
2713
+ : null;
2714
+ const isReadyEditHighlight = shadowContainer && isReady && isEditingMode && hoveredElement;
2715
+ const editHighlightPortal = isReadyEditHighlight
2716
+ ? createPortal(jsx(AreaEditHighlight, { element: hoveredElement, shadowRoot: customShadowRoot, onClick: onAreaCreatedElement }), shadowContainer)
2717
+ : null;
2718
+ return {
2719
+ areasPortal: areasPortal,
2720
+ editHighlightPortal: editHighlightPortal,
2721
+ };
2722
+ }
2723
+
2724
+ function useAreaRectSync(options) {
2725
+ const { iframeDocument, shadowRoot, enabled = true } = options;
2726
+ const { vizRef } = useHeatmapViz();
2727
+ const { areas } = useHeatmapAreaClick();
2728
+ useEffect(() => {
2729
+ if (!enabled || !iframeDocument || areas.length === 0) {
2730
+ return;
2731
+ }
2732
+ areas.forEach((area) => {
2733
+ try {
2734
+ let targetElement = area.element;
2735
+ if (!targetElement || !iframeDocument.contains(targetElement)) {
2736
+ if (area.hash) {
2737
+ const elementByHash = vizRef?.get(area.hash);
2738
+ if (elementByHash) {
2739
+ area.element = elementByHash;
2740
+ targetElement = elementByHash;
2741
+ }
2742
+ }
2743
+ }
2744
+ // // If we still can't find the element, set rect to zero
2745
+ // if (!targetElement || !iframeDocument.contains(targetElement)) {
2746
+ // area.rect.update({
2747
+ // width: 0,
2748
+ // height: 0,
2749
+ // top: 0,
2750
+ // left: 0,
2751
+ // absoluteLeft: 0,
2752
+ // absoluteTop: 0,
2753
+ // absoluteRight: 0,
2754
+ // absoluteBottom: 0,
2755
+ // outOfBounds: true,
2756
+ // });
2757
+ // return;
2758
+ // }
2759
+ const newRect = getElementRect(targetElement, shadowRoot);
2760
+ area.rect.update(newRect);
2761
+ }
2762
+ catch (error) {
2763
+ logger.error(`Failed to update rect for area ${area.id}:`, error);
2764
+ }
2765
+ });
2766
+ }, [areas, iframeDocument, shadowRoot, enabled, vizRef]);
2767
+ }
2768
+
2769
+ /**
2770
+ * Hook to setup and manage the shadow DOM container for area rendering
2771
+ *
2772
+ * @param iframeDocument - The iframe document
2773
+ * @param customShadowRoot - Optional custom shadow root element
2774
+ * @returns Container element and ready state
2775
+ */
2776
+ function useAreaRendererContainer(iframeDocument, customShadowRoot) {
2777
+ const [shadowContainer, setShadowContainer] = useState(null);
2778
+ const [isReady, setIsReady] = useState(false);
2779
+ const containerRef = useRef(null);
2780
+ useEffect(() => {
2781
+ if (!iframeDocument) {
2782
+ setIsReady(false);
2783
+ return;
2784
+ }
2785
+ const innerContainer = setupAreaRenderingContainer(iframeDocument, customShadowRoot);
2786
+ containerRef.current = innerContainer;
2787
+ setShadowContainer(innerContainer);
2788
+ setIsReady(true);
2789
+ return () => {
2790
+ // Cleanup on unmount
2791
+ const container = innerContainer.parentElement?.parentElement;
2792
+ cleanupAreaRenderingContainer(container);
2793
+ containerRef.current = null;
2794
+ setShadowContainer(null);
2795
+ setIsReady(false);
2796
+ };
2797
+ }, [iframeDocument, customShadowRoot]);
2798
+ return {
2799
+ shadowContainer,
2800
+ isReady,
2801
+ containerRef,
2802
+ };
2803
+ }
2804
+
1767
2805
  const scrollToElementIfNeeded = (visualRef, rect, scale) => {
1768
2806
  if (!visualRef.current)
1769
2807
  return;
@@ -1782,7 +2820,7 @@ const scrollToElementIfNeeded = (visualRef, rect, scale) => {
1782
2820
  });
1783
2821
  };
1784
2822
  const useClickedElement = ({ visualRef, getRect }) => {
1785
- const { selectedElement, shouldShowCallout, setShouldShowCallout } = useHeatmapInteraction();
2823
+ const { selectedElement, shouldShowCallout, setShouldShowCallout } = useHeatmapClick();
1786
2824
  const { widthScale } = useHeatmapViz();
1787
2825
  const { dataInfo } = useHeatmapData();
1788
2826
  const [clickedElement, setClickedElement] = useState(null);
@@ -1820,12 +2858,12 @@ const useClickedElement = ({ visualRef, getRect }) => {
1820
2858
  requestAnimationFrame(() => {
1821
2859
  setClickedElement(elementInfo);
1822
2860
  });
1823
- }, [selectedElement, dataInfo, visualRef, widthScale]);
2861
+ }, [selectedElement, dataInfo, visualRef, widthScale]); // eslint-disable-line react-hooks/exhaustive-deps
1824
2862
  return { clickedElement, showMissingElement, shouldShowCallout };
1825
2863
  };
1826
2864
 
1827
2865
  const useElementCalloutVisible = ({ visualRef, getRect }) => {
1828
- const { selectedElement, setShouldShowCallout } = useHeatmapInteraction();
2866
+ const { selectedElement, setShouldShowCallout } = useHeatmapClick();
1829
2867
  const { widthScale } = useHeatmapViz();
1830
2868
  const { dataInfo } = useHeatmapData();
1831
2869
  useEffect(() => {
@@ -1856,7 +2894,7 @@ const useElementCalloutVisible = ({ visualRef, getRect }) => {
1856
2894
  };
1857
2895
 
1858
2896
  const useHeatmapEffects = ({ isVisible }) => {
1859
- useHeatmapInteraction();
2897
+ useHeatmapClick();
1860
2898
  const resetAll = () => {
1861
2899
  // setShouldShowCallout(false);
1862
2900
  };
@@ -1876,7 +2914,7 @@ const useHeatmapEffects = ({ isVisible }) => {
1876
2914
  const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
1877
2915
  const heatmapWidth = useHeatmapConfigStore((state) => state.width);
1878
2916
  const { iframeHeight, widthScale } = useHeatmapViz();
1879
- return useCallback((element) => {
2917
+ const getRect = useCallback((element) => {
1880
2918
  const hash = element?.hash;
1881
2919
  if (!iframeRef.current?.contentDocument || !hash || !visualizer)
1882
2920
  return null;
@@ -1912,6 +2950,7 @@ const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
1912
2950
  outOfBounds,
1913
2951
  };
1914
2952
  }, [iframeRef, wrapperRef, visualizer, heatmapWidth, iframeHeight, widthScale]);
2953
+ return { getRect };
1915
2954
  };
1916
2955
 
1917
2956
  const debounce = (fn, delay) => {
@@ -1921,11 +2960,7 @@ const debounce = (fn, delay) => {
1921
2960
  timeout = setTimeout(() => fn(...args), delay);
1922
2961
  };
1923
2962
  };
1924
-
1925
- // ===================== UTILITY FUNCTIONS =====================
1926
- /**
1927
- * Lấy bounding box tuyệt đối của element (relative to document)
1928
- */
2963
+
1929
2964
  function getBoundingBox(element) {
1930
2965
  if (typeof element.getBoundingClientRect !== 'function') {
1931
2966
  return null;
@@ -1946,87 +2981,75 @@ function getBoundingBox(element) {
1946
2981
  height: Math.floor(rect.height),
1947
2982
  };
1948
2983
  }
1949
- /**
1950
- * Lấy tất cả elements tại tọa độ (x, y), hỗ trợ Shadow DOM
1951
- */
1952
- function getElementsAtPoint(documentOrShadowRoot, x, y, filterFunction, visitedShadowRoots = new Set()) {
1953
- // Lấy tất cả elements tại vị trí
1954
- const elementsAtPoint = documentOrShadowRoot.elementsFromPoint(x, y);
1955
- if (!filterFunction) {
1956
- return elementsAtPoint;
1957
- }
1958
- // Tìm element đầu tiên match với filter
1959
- const matchedElement = elementsAtPoint.find(filterFunction);
1960
- // Nếu element có Shadow DOM và chưa visit -> đệ quy vào
1961
- if (matchedElement?.shadowRoot && !visitedShadowRoots.has(matchedElement.shadowRoot)) {
1962
- visitedShadowRoots.add(matchedElement.shadowRoot);
1963
- return getElementsAtPoint(matchedElement.shadowRoot, x, y, filterFunction, visitedShadowRoots);
1964
- }
1965
- return elementsAtPoint;
1966
- }
1967
- // ===================== EXAMPLE USAGE =====================
1968
- /*
1969
- import { useRef, useState } from 'react';
1970
-
1971
- function HeatmapComponent() {
1972
- const heatmapWrapperRef = useRef<HTMLDivElement>(null);
1973
- const iframeRef = useRef<HTMLIFrameElement>(null);
1974
- const parentRef = useRef<HTMLDivElement>(null);
1975
-
1976
- const [hoveredElement, setHoveredElement] = useState<HoveredElementInfo | null>(null);
1977
-
1978
- const heatmapInfo = {
1979
- width: 1920,
1980
- elementMapInfo: {
1981
- 'hash123': {
1982
- totalclicks: 45,
1983
- selector: 'button.submit'
1984
- }
1985
- },
1986
- sortedElements: [...]
1987
- };
1988
-
1989
- const { handleMouseMove } = useHeatmapMouseHandler({
1990
- heatmapWrapperRef,
1991
- iframeRef,
1992
- parentRef,
1993
- heatmapInfo,
1994
- scaleRatio: 0.8, // 80% zoom
1995
- onElementHover: (info) => {
1996
- setHoveredElement(info);
1997
- console.log('Hovered element:', info);
1998
- }
1999
- });
2000
-
2001
- return (
2002
- <div ref={parentRef}>
2003
- <div
2004
- ref={heatmapWrapperRef}
2005
- onMouseMove={handleMouseMove}
2006
- >
2007
- <iframe ref={iframeRef} />
2008
-
2009
- {hoveredElement && (
2010
- <div className="tooltip" style={{
2011
- position: 'absolute',
2012
- left: hoveredElement.left,
2013
- top: hoveredElement.top
2014
- }}>
2015
- Clicks: {hoveredElement.clicks}
2016
- <br />
2017
- Rank: #{hoveredElement.rank}
2018
- <br />
2019
- Selector: {hoveredElement.selector}
2020
- </div>
2021
- )}
2022
- </div>
2023
- </div>
2024
- );
2025
- }
2026
- */
2984
+ // export function useHeatmapMouseHandler(props: UseHeatmapMouseHandlerProps) {
2985
+ // const { heatmapWrapperRef, iframeRef, parentRef, heatmapInfo, scaleRatio, onElementHover } = props;
2986
+ // const handleMouseMove = useCallback(
2987
+ // (event: MouseEvent) => {
2988
+ // if (
2989
+ // !heatmapWrapperRef?.current ||
2990
+ // !iframeRef?.current ||
2991
+ // !iframeRef.current.contentDocument ||
2992
+ // !heatmapInfo?.elementMapInfo ||
2993
+ // !parentRef?.current
2994
+ // ) {
2995
+ // return;
2996
+ // }
2997
+ // try {
2998
+ // // Calculate scroll position (scaled)
2999
+ // const scrollTop = parentRef.current.scrollTop / scaleRatio;
3000
+ // // Get position of heatmap wrapper
3001
+ // const wrapperRect = heatmapWrapperRef.current.getBoundingClientRect();
3002
+ // // Calculate mouse position in iframe (scaled)
3003
+ // const mouseX = (event.clientX - wrapperRect.left) / scaleRatio;
3004
+ // const mouseY = (event.clientY - wrapperRect.top) / scaleRatio - scrollTop;
3005
+ // const elementsAtPoint = getElementsAtPoint(
3006
+ // iframeRef.current.contentDocument,
3007
+ // Math.round(mouseX),
3008
+ // Math.round(mouseY),
3009
+ // {
3010
+ // filterFn: (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE),
3011
+ // ignoreCanvas: true,
3012
+ // },
3013
+ // );
3014
+ // if (!elementsAtPoint || elementsAtPoint.length === 0) {
3015
+ // return;
3016
+ // }
3017
+ // for (let i = 0; i < elementsAtPoint.length; i++) {
3018
+ // const element = elementsAtPoint[i] as HTMLElement;
3019
+ // const elementHash = element.getAttribute(HEATMAP_ELEMENT_ATTRIBUTE);
3020
+ // if (elementHash && heatmapInfo.elementMapInfo[elementHash]) {
3021
+ // const elementData = heatmapInfo.elementMapInfo[elementHash];
3022
+ // const boundingBox = getBoundingBox(element);
3023
+ // if (boundingBox) {
3024
+ // const rank =
3025
+ // Array.isArray(heatmapInfo.sortedElements) && elementData
3026
+ // ? heatmapInfo.sortedElements.indexOf(elementData) + 1
3027
+ // : NaN;
3028
+ // onElementHover({
3029
+ // ...boundingBox,
3030
+ // width: Math.min(boundingBox.width, heatmapInfo.width || 0),
3031
+ // top: boundingBox.top + scrollTop,
3032
+ // // Metadata
3033
+ // hash: elementHash,
3034
+ // clicks: elementData.totalclicks,
3035
+ // rank: rank,
3036
+ // selector: elementData.selector || '',
3037
+ // });
3038
+ // break;
3039
+ // }
3040
+ // }
3041
+ // }
3042
+ // } catch (error) {
3043
+ // console.warn('Error handling mouse move on heatmap:', error);
3044
+ // }
3045
+ // },
3046
+ // [heatmapWrapperRef, iframeRef, parentRef, heatmapInfo, scaleRatio, onElementHover],
3047
+ // );
3048
+ // return { handleMouseMove };
3049
+ // }
2027
3050
 
2028
3051
  const useHoveredElement = ({ iframeRef, getRect }) => {
2029
- const { hoveredElement, setHoveredElement, setSelectedElement } = useHeatmapInteraction();
3052
+ const { hoveredElement, setHoveredElement, setSelectedElement } = useHeatmapClick();
2030
3053
  const { widthScale } = useHeatmapViz();
2031
3054
  const { dataInfo } = useHeatmapData();
2032
3055
  const reset = useCallback(() => {
@@ -2106,12 +3129,14 @@ const convertViewportToIframeCoords = (clientX, clientY, iframeRect, scale) => {
2106
3129
  return { x, y };
2107
3130
  };
2108
3131
  const findTargetElement = (doc, x, y, heatmapInfo) => {
2109
- const HEATMAP_ELEMENT_ATTRIBUTE = 'data-clarity-hashalpha';
2110
- const elementsAtPoint = getElementsAtPoint(doc, Math.round(x), Math.round(y), (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE));
3132
+ const elementsAtPoint = getElementsAtPoint(doc, Math.round(x), Math.round(y), {
3133
+ filterFn: (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE),
3134
+ ignoreCanvas: true,
3135
+ });
2111
3136
  let dataElement = null;
2112
3137
  for (let i = 0; i < elementsAtPoint.length; i++) {
2113
3138
  const element = elementsAtPoint[i];
2114
- const elementHash = element.getAttribute(HEATMAP_ELEMENT_ATTRIBUTE);
3139
+ const elementHash = getElementHash(element);
2115
3140
  if (elementHash && heatmapInfo.elementMapInfo?.[elementHash]) {
2116
3141
  const boundingBox = getBoundingBox(element);
2117
3142
  if (boundingBox) {
@@ -2141,6 +3166,167 @@ const isValidElement = (element, heatmapInfo) => {
2141
3166
  return !!heatmapInfo?.elementMapInfo?.[hash];
2142
3167
  };
2143
3168
 
3169
+ function useAreaScrollSync(options) {
3170
+ const { iframeRef, visualRef, enabled = true } = options;
3171
+ const { widthScale } = useHeatmapViz();
3172
+ const { areas, selectedArea } = useHeatmapAreaClick();
3173
+ const iframeDocument = iframeRef.current?.contentDocument;
3174
+ useEffect(() => {
3175
+ if (!enabled || !iframeDocument || areas.length === 0) {
3176
+ return;
3177
+ }
3178
+ let rafId = null;
3179
+ let isUpdating = false;
3180
+ const updateAreaPositions = () => {
3181
+ if (isUpdating)
3182
+ return;
3183
+ isUpdating = true;
3184
+ rafId = requestAnimationFrame(() => {
3185
+ areas.forEach((area) => {
3186
+ if (!area.element || !area.rect)
3187
+ return;
3188
+ try {
3189
+ const newRect = getElementRect(area.element);
3190
+ area.rect.update(newRect);
3191
+ }
3192
+ catch (error) {
3193
+ console.warn('[useAreaScrollSync] Failed to update area rect:', error);
3194
+ }
3195
+ });
3196
+ isUpdating = false;
3197
+ rafId = null;
3198
+ });
3199
+ };
3200
+ iframeDocument.addEventListener('scroll', updateAreaPositions, { passive: true });
3201
+ const iframeWindow = iframeDocument.defaultView;
3202
+ if (iframeWindow) {
3203
+ iframeWindow.addEventListener('resize', updateAreaPositions, { passive: true });
3204
+ }
3205
+ return () => {
3206
+ if (rafId !== null) {
3207
+ cancelAnimationFrame(rafId);
3208
+ }
3209
+ iframeDocument.removeEventListener('scroll', updateAreaPositions);
3210
+ if (iframeWindow) {
3211
+ iframeWindow.removeEventListener('resize', updateAreaPositions);
3212
+ }
3213
+ };
3214
+ }, [areas, iframeDocument, enabled]);
3215
+ useEffect(() => {
3216
+ if (!selectedArea)
3217
+ return;
3218
+ if (!visualRef)
3219
+ return;
3220
+ if (!selectedArea.rect.value)
3221
+ return;
3222
+ scrollToElementIfNeeded(visualRef, selectedArea.rect.value, widthScale);
3223
+ }, [visualRef, selectedArea, widthScale]);
3224
+ }
3225
+
3226
+ const useAreaTopAutoDetect = (props) => {
3227
+ const { iframeRef, autoCreateTopN, shadowRoot } = props;
3228
+ const iframeDocument = iframeRef.current?.contentDocument;
3229
+ const { dataInfo } = useHeatmapData();
3230
+ const { vizRef } = useHeatmapViz();
3231
+ const { areas, addArea } = useHeatmapAreaClick();
3232
+ useEffect(() => {
3233
+ if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks)
3234
+ return;
3235
+ if (autoCreateTopN <= 0)
3236
+ return;
3237
+ if (areas.length > 0)
3238
+ return;
3239
+ const topElements = getTopElementsByClicks(dataInfo.elementMapInfo, autoCreateTopN);
3240
+ const newAreas = [];
3241
+ topElements.forEach(({ hash }) => {
3242
+ const element = vizRef?.get(hash);
3243
+ if (!element)
3244
+ return;
3245
+ const area = buildAreaNode(element, hash, dataInfo);
3246
+ if (!area)
3247
+ return;
3248
+ newAreas.push(area);
3249
+ });
3250
+ newAreas.forEach((area) => addArea(area));
3251
+ }, [dataInfo, autoCreateTopN, areas.length, iframeDocument, shadowRoot]); // eslint-disable-line react-hooks/exhaustive-deps
3252
+ return {};
3253
+ };
3254
+
3255
+ const useAreaClickmap = () => {
3256
+ const { vizRef } = useHeatmapViz();
3257
+ const { clickmap } = useHeatmapData();
3258
+ const start = useCallback(() => {
3259
+ if (!vizRef || !clickmap || clickmap.length === 0)
3260
+ return;
3261
+ try {
3262
+ vizRef?.clearmap?.();
3263
+ }
3264
+ catch (error) {
3265
+ console.error(`🚀 🐥 ~ useAreaClickmap ~ error:`, error);
3266
+ }
3267
+ }, [vizRef, clickmap]);
3268
+ return { start };
3269
+ };
3270
+
3271
+ const useClickmap = () => {
3272
+ const { vizRef } = useHeatmapViz();
3273
+ const { clickmap } = useHeatmapData();
3274
+ const start = useCallback(() => {
3275
+ if (!vizRef || !clickmap || clickmap.length === 0)
3276
+ return;
3277
+ try {
3278
+ vizRef?.clearmap?.();
3279
+ vizRef?.clickmap?.(clickmap);
3280
+ }
3281
+ catch (error) {
3282
+ console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
3283
+ }
3284
+ }, [vizRef, clickmap]);
3285
+ return { start };
3286
+ };
3287
+
3288
+ const useScrollmap = () => {
3289
+ const { vizRef } = useHeatmapViz();
3290
+ const { scrollmap } = useHeatmapData();
3291
+ const start = useCallback(() => {
3292
+ // if (isInitialized) return;
3293
+ if (!vizRef || !scrollmap || scrollmap.length === 0)
3294
+ return;
3295
+ try {
3296
+ vizRef?.clearmap?.();
3297
+ vizRef?.scrollmap?.(scrollmap);
3298
+ // setIsInitialized(true);
3299
+ }
3300
+ catch (error) {
3301
+ console.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
3302
+ }
3303
+ }, [vizRef, scrollmap]);
3304
+ return { start };
3305
+ };
3306
+
3307
+ const useHeatmapCanvas = () => {
3308
+ const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
3309
+ const clickMode = useHeatmapConfigStore((state) => state.clickMode);
3310
+ const { start: startClickmap } = useClickmap();
3311
+ const { start: startAreaClickmap } = useAreaClickmap();
3312
+ const { start: startScrollmap } = useScrollmap();
3313
+ useEffect(() => {
3314
+ switch (heatmapType) {
3315
+ case IHeatmapType.Click:
3316
+ if (clickMode === IClickMode.Default) {
3317
+ startClickmap();
3318
+ }
3319
+ else {
3320
+ startAreaClickmap();
3321
+ }
3322
+ break;
3323
+ case IHeatmapType.Scroll:
3324
+ startScrollmap();
3325
+ break;
3326
+ }
3327
+ }, [heatmapType, clickMode, startClickmap, startAreaClickmap, startScrollmap]);
3328
+ };
3329
+
2144
3330
  var MessageType;
2145
3331
  (function (MessageType) {
2146
3332
  MessageType["GX_DOM_TRACKING_PAYLOAD"] = "GX_DOM_TRACKING_PAYLOAD";
@@ -2250,8 +3436,7 @@ function reset(iframe, rect, onSuccess) {
2250
3436
 
2251
3437
  const useHeatmapRender = () => {
2252
3438
  const { data } = useHeatmapData();
2253
- const { vizRef, setVizRef, setIsRenderViz, setIframeHeight, wrapperHeight, wrapperWidth } = useHeatmapViz();
2254
- console.log(`🚀 🐥 ~ useHeatmapRender ~ wrapperHeight:`, wrapperHeight);
3439
+ const { vizRef, setVizRef, setIsRenderViz, setIframeHeight } = useHeatmapViz();
2255
3440
  const iframeRef = useRef(null);
2256
3441
  const renderHeatmap = useCallback(async (payloads) => {
2257
3442
  if (!payloads || payloads.length === 0)
@@ -2430,7 +3615,7 @@ const useReplayRender = () => {
2430
3615
  };
2431
3616
  };
2432
3617
 
2433
- const useHeatmapVizRender = (mode) => {
3618
+ const useHeatmapRenderByMode = (mode) => {
2434
3619
  const heatmapResult = useMemo(() => {
2435
3620
  switch (mode) {
2436
3621
  case 'heatmap':
@@ -3452,19 +4637,6 @@ const BoxStack = forwardRef(({ children, ...props }, ref) => {
3452
4637
  });
3453
4638
  BoxStack.displayName = 'BoxStack';
3454
4639
 
3455
- const ContentTopBar = () => {
3456
- const controls = useHeatmapControlStore((state) => state.controls);
3457
- useHeatmapConfigStore((state) => state.mode);
3458
- const TopBar = controls.TopBar;
3459
- // In compare mode, hide individual top bars since we have a global header
3460
- // if (mode === 'compare') {
3461
- // return null;
3462
- // }
3463
- return (jsx(BoxStack, { id: "gx-hm-content-header", flexDirection: "row", alignItems: "center", overflow: "auto", zIndex: 1, backgroundColor: "white", style: {
3464
- borderBottom: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
3465
- }, children: TopBar && jsx(TopBar, {}) }));
3466
- };
3467
-
3468
4640
  const ContentMetricBar = () => {
3469
4641
  const controls = useHeatmapControlStore((state) => state.controls);
3470
4642
  const borderBottom = `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`;
@@ -3472,22 +4644,11 @@ const ContentMetricBar = () => {
3472
4644
  borderBottom,
3473
4645
  }, children: controls.MetricBar ?? null }));
3474
4646
  };
3475
-
3476
- const ContentToolbar = () => {
3477
- const controls = useHeatmapControlStore((state) => state.controls);
3478
- return (jsx("div", { id: "gx-hm-content-toolbar", style: {
3479
- position: 'absolute',
3480
- bottom: 0,
3481
- left: '8px',
3482
- right: '24px',
3483
- padding: '8px',
3484
- paddingBlock: '16px',
3485
- }, children: controls.Toolbar ?? null }));
3486
- };
4647
+ ContentMetricBar.displayName = 'ContentMetricBar';
3487
4648
 
3488
4649
  const ContentSidebar = () => {
3489
4650
  const controls = useHeatmapControlStore((state) => state.controls);
3490
- const { state } = useHeatmapInteraction();
4651
+ const { state } = useHeatmapClick();
3491
4652
  const isHideSidebar = state.hideSidebar;
3492
4653
  const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
3493
4654
  const mode = useHeatmapConfigStore((state) => state.mode);
@@ -3521,7 +4682,7 @@ const PopoverSidebar = () => {
3521
4682
  const CompSidebarActivator = useHeatmapControlStore((state) => state.controls.SidebarActivator);
3522
4683
  const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
3523
4684
  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
3524
- const { state } = useHeatmapInteraction();
4685
+ const { state } = useHeatmapClick();
3525
4686
  const isCompareMode = mode === 'compare';
3526
4687
  const isHideSidebar = state.hideSidebar;
3527
4688
  const stylePopover = {
@@ -3546,9 +4707,21 @@ const PopoverSidebar = () => {
3546
4707
  }, children: jsx(CompSidebar, { closeAction: { onClick: () => setIsPopoverOpen(false) } }) }) }))] }));
3547
4708
  };
3548
4709
 
4710
+ const ContentToolbar = () => {
4711
+ const controls = useHeatmapControlStore((state) => state.controls);
4712
+ return (jsx("div", { id: "gx-hm-content-toolbar", style: {
4713
+ position: 'absolute',
4714
+ bottom: 0,
4715
+ left: '8px',
4716
+ right: '24px',
4717
+ padding: '8px',
4718
+ paddingBlock: '16px',
4719
+ }, children: controls.Toolbar ?? null }));
4720
+ };
4721
+
3549
4722
  const VizContainer = ({ children, isActive = false }) => {
3550
4723
  const wrapperRef = useRef(null);
3551
- const viewId = useViewId();
4724
+ const viewId = useViewIdContext();
3552
4725
  useWrapperRefHeight({
3553
4726
  isActive,
3554
4727
  wrapperRef,
@@ -3558,80 +4731,46 @@ const VizContainer = ({ children, isActive = false }) => {
3558
4731
  }, children: children }), jsx(PopoverSidebar, {})] }));
3559
4732
  };
3560
4733
 
3561
- const useClickmap = () => {
3562
- const { vizRef } = useHeatmapViz();
3563
- const { clickmap } = useHeatmapData();
3564
- const start = useCallback(() => {
3565
- if (!vizRef || !clickmap || clickmap.length === 0)
3566
- return;
3567
- try {
3568
- vizRef?.clearmap?.();
3569
- vizRef?.clickmap?.(clickmap);
3570
- }
3571
- catch (error) {
3572
- console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
3573
- }
3574
- }, [vizRef, clickmap]);
3575
- return { start };
3576
- };
3577
-
3578
- const useScrollmap = () => {
3579
- const { vizRef } = useHeatmapViz();
3580
- const { scrollmap } = useHeatmapData();
3581
- const start = useCallback(() => {
3582
- // if (isInitialized) return;
3583
- if (!vizRef || !scrollmap || scrollmap.length === 0)
3584
- return;
3585
- try {
3586
- vizRef?.clearmap?.();
3587
- vizRef?.scrollmap?.(scrollmap);
3588
- // setIsInitialized(true);
3589
- }
3590
- catch (error) {
3591
- console.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
3592
- }
3593
- }, [vizRef, scrollmap]);
3594
- return { start };
3595
- };
4734
+ function useAreaRenderer(options) {
4735
+ const { iframeRef, visualRef, shadowRoot, onAreaCreated, onAreaClick } = options;
4736
+ const iframeDocument = iframeRef.current?.contentDocument || undefined;
4737
+ const { shadowContainer, isReady } = useAreaRendererContainer(iframeDocument, shadowRoot);
4738
+ const { areasPortal, editHighlightPortal } = useAreaPortals({
4739
+ iframeRef,
4740
+ shadowContainer,
4741
+ isReady,
4742
+ onAreaClick,
4743
+ onAreaCreated,
4744
+ });
4745
+ useAreaRectSync({ iframeDocument, shadowRoot, enabled: isReady });
4746
+ useAreaScrollSync({ iframeRef, visualRef, enabled: isReady });
4747
+ return {
4748
+ areasPortal,
4749
+ editHighlightPortal,
4750
+ isReady,
4751
+ };
4752
+ }
3596
4753
 
3597
- const useHeatmapCanvas = () => {
3598
- const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
3599
- const { start: startClickmap } = useClickmap();
3600
- const { start: startScrollmap } = useScrollmap();
4754
+ const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true, onAreaClick, }) => {
4755
+ const { resetView } = useHeatmapAreaClick();
4756
+ const { areasPortal, editHighlightPortal, isReady } = useAreaRenderer({
4757
+ iframeRef,
4758
+ visualRef,
4759
+ shadowRoot,
4760
+ onAreaClick,
4761
+ });
4762
+ useAreaTopAutoDetect({ iframeRef, autoCreateTopN, shadowRoot });
4763
+ useAreaFilterVisible({ iframeRef, enableOverlapResolution });
3601
4764
  useEffect(() => {
3602
- switch (heatmapType) {
3603
- case IHeatmapType.Click:
3604
- startClickmap();
3605
- break;
3606
- case IHeatmapType.Scroll:
3607
- startScrollmap();
3608
- break;
3609
- }
3610
- }, [heatmapType, startClickmap, startScrollmap]);
3611
- };
3612
-
3613
- // Base IDs for elements (without viewId suffix)
3614
- const CLICKED_ELEMENT_ID_BASE = 'gx-hm-clicked-element';
3615
- const SECONDARY_CLICKED_ELEMENT_ID_BASE = 'gx-hm-secondary-clicked-element';
3616
- const HOVERED_ELEMENT_ID_BASE = 'gx-hm-hovered-element';
3617
- const SECONDARY_HOVERED_ELEMENT_ID_BASE = 'gx-hm-secondary-hovered-element';
3618
- /**
3619
- * Generate unique element ID for a specific view
3620
- * @param baseId - Base element ID
3621
- * @param viewId - View ID
3622
- * @returns Unique element ID (e.g., 'gx-hm-clicked-element-view-0')
3623
- */
3624
- const getElementId = (baseId, viewId) => {
3625
- return `${baseId}-${viewId}`;
3626
- };
3627
- const getClickedElementId = (viewId, isSecondary = false) => {
3628
- const baseId = isSecondary ? SECONDARY_CLICKED_ELEMENT_ID_BASE : CLICKED_ELEMENT_ID_BASE;
3629
- return getElementId(baseId, viewId);
3630
- };
3631
- const getHoveredElementId = (viewId, isSecondary = false) => {
3632
- const baseId = isSecondary ? SECONDARY_HOVERED_ELEMENT_ID_BASE : HOVERED_ELEMENT_ID_BASE;
3633
- return getElementId(baseId, viewId);
4765
+ return () => {
4766
+ resetView();
4767
+ };
4768
+ }, []);
4769
+ if (!isReady)
4770
+ return null;
4771
+ return (jsxs(Fragment, { children: [areasPortal, editHighlightPortal] }));
3634
4772
  };
4773
+ VizAreaClick.displayName = 'VizAreaClick';
3635
4774
 
3636
4775
  const RankBadge = ({ index, elementRect, widthScale, clickOnElement, }) => {
3637
4776
  const style = calculateRankPosition(elementRect, widthScale);
@@ -3661,7 +4800,7 @@ const DEFAULT_POSITION = {
3661
4800
  };
3662
4801
  const ElementCallout = (props) => {
3663
4802
  const CompElementCallout = useHeatmapControlStore((state) => state.controls.ElementCallout);
3664
- const viewId = useViewId();
4803
+ const viewId = useViewIdContext();
3665
4804
  const { element, target, visualRef, hozOffset, alignment = 'left' } = props;
3666
4805
  const calloutRef = useRef(null);
3667
4806
  const [position, setPosition] = useState(DEFAULT_POSITION);
@@ -3721,7 +4860,7 @@ const ElementMissing = ({ show = true }) => {
3721
4860
  }, "aria-live": "assertive", children: "Element not visible on current screen" }));
3722
4861
  };
3723
4862
 
3724
- const ElementOverlay = ({ type, element, onClick, isSecondary, elementId, }) => {
4863
+ const ElementOverlay = ({ type, element, onClick, elementId }) => {
3725
4864
  // useRenderCount('ElementOverlay');
3726
4865
  const { widthScale } = useHeatmapViz();
3727
4866
  if (!element || (element.width === 0 && element.height === 0))
@@ -3745,30 +4884,32 @@ const ELEMENT_CALLOUT = {
3745
4884
  alignment: 'left',
3746
4885
  };
3747
4886
  const HeatmapElements = (props) => {
3748
- const viewId = useViewId();
3749
- const { iframeHeight } = useHeatmapViz();
4887
+ const viewId = useViewIdContext();
3750
4888
  const clickedElementId = getClickedElementId(viewId, props.isSecondary);
3751
4889
  const hoveredElementId = getHoveredElementId(viewId, props.isSecondary);
3752
- const { iframeRef, wrapperRef, visualRef, visualizer, iframeDimensions, isVisible = true, areDefaultRanksHidden, isSecondary, } = props;
3753
- const getRect = useHeatmapElementPosition({
3754
- iframeRef,
3755
- wrapperRef,
3756
- visualizer,
4890
+ const { iframeDimensions, isVisible = true, areDefaultRanksHidden } = props;
4891
+ const { iframeHeight } = useHeatmapViz();
4892
+ const { getRect } = useHeatmapElementPosition({
4893
+ iframeRef: props.iframeRef,
4894
+ wrapperRef: props.wrapperRef,
4895
+ visualizer: props.visualizer,
3757
4896
  });
3758
4897
  const { clickedElement, showMissingElement, shouldShowCallout } = useClickedElement({
3759
- visualRef,
4898
+ visualRef: props.visualRef,
3760
4899
  getRect,
3761
4900
  });
3762
4901
  const { hoveredElement, handleMouseMove, handleMouseLeave, handleClick } = useHoveredElement({
3763
- iframeRef,
4902
+ iframeRef: props.iframeRef,
3764
4903
  getRect,
3765
4904
  });
3766
- useElementCalloutVisible({ visualRef, getRect });
4905
+ useElementCalloutVisible({ visualRef: props.visualRef, getRect });
3767
4906
  useHeatmapEffects({ isVisible });
3768
4907
  useRenderCount('HeatmapElements');
3769
4908
  if (!isVisible)
3770
4909
  return null;
3771
- 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 }))] }));
4910
+ const isShowHoveredElement = hoveredElement && hoveredElement.hash !== clickedElement?.hash;
4911
+ const isShowClickedElement = shouldShowCallout && clickedElement;
4912
+ 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 }))] }));
3772
4913
  };
3773
4914
 
3774
4915
  const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
@@ -3785,130 +4926,34 @@ const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
3785
4926
  // useRenderCount('VizElements');
3786
4927
  if (!iframeRef.current)
3787
4928
  return null;
3788
- return (jsx(HeatmapElements, { visualizer: visualizer, visualRef: visualRef, iframeRef: iframeRef, wrapperRef: wrapperRef, heatmapInfo: dataInfo, isVisible: true, iframeDimensions: {
3789
- width: contentWidth,
3790
- position: 'absolute',
3791
- top: 0,
3792
- left: 0,
3793
- // pointerEvents: 'none',
3794
- } }));
3795
- };
3796
-
3797
- const AverageFoldLine = ({ iframeRef, wrapperRef }) => {
3798
- const { dataInfo } = useHeatmapData();
3799
- const { getZonePosition } = useZonePositions();
3800
- const averageFold = dataInfo?.averageFold || 50;
3801
- const position = getZonePosition({
3802
- startY: averageFold,
3803
- endY: averageFold,
3804
- });
3805
- if (!position)
3806
- return null;
3807
- return (jsx("div", { style: {
4929
+ return (jsx(HeatmapElements, { visualizer: visualizer, visualRef: visualRef, iframeRef: iframeRef, wrapperRef: wrapperRef, heatmapInfo: dataInfo, isVisible: true, iframeDimensions: {
4930
+ width: contentWidth,
3808
4931
  position: 'absolute',
3809
- top: `${position.top}px`,
4932
+ top: 0,
3810
4933
  left: 0,
3811
- width: '100%',
3812
- height: '2px',
3813
- backgroundColor: '#0078D4',
3814
- pointerEvents: 'none',
3815
- zIndex: 2,
3816
- boxShadow: '0 0 4px rgba(0,120,212,0.5)',
3817
- display: 'flex',
3818
- alignItems: 'center',
3819
- }, children: jsxs("div", { style: {
3820
- position: 'absolute',
3821
- padding: '8px',
3822
- backgroundColor: 'rgba(0, 120, 212, 0.9)',
3823
- color: 'white',
3824
- fontSize: '16px',
3825
- fontWeight: 600,
3826
- borderRadius: '4px',
3827
- whiteSpace: 'nowrap',
3828
- left: '12px',
3829
- minWidth: '120px',
3830
- textAlign: 'center',
3831
- }, children: ["Average fold - ", averageFold.toFixed(0), "%"] }) }));
4934
+ // pointerEvents: 'none',
4935
+ } }));
3832
4936
  };
3833
4937
 
3834
- const ScrollmapMarker = ({ iframeRef, wrapperRef }) => {
3835
- const scrollType = useHeatmapConfigStore((state) => state.scrollType);
3836
- const { scrollmap } = useHeatmapData();
3837
- const { getZonePosition } = useZonePositions();
3838
- if (!scrollmap || scrollmap.length === 0)
3839
- return null;
3840
- const findScrollPositionForUserPercent = (targetPercent) => {
3841
- for (let i = 0; i < scrollmap.length; i++) {
3842
- if (scrollmap[i].percUsers <= targetPercent) {
3843
- if (i > 0) {
3844
- return scrollmap[i - 1].scrollReachY;
3845
- }
3846
- return scrollmap[i].scrollReachY;
3847
- }
3848
- }
3849
- return scrollmap[scrollmap.length - 1]?.scrollReachY || null;
3850
- };
3851
- const boundaries = [
3852
- { percent: 75, label: '75%', color: '#10B981' },
3853
- { percent: 50, label: '50%', color: '#F59E0B' },
3854
- { percent: 25, label: '25%', color: '#EF4444' },
3855
- { percent: 5, label: '5%', color: '#8B5CF6' },
3856
- ];
3857
- const isScrollDepth = scrollType === IScrollType.Depth;
3858
- if (!isScrollDepth)
4938
+ const VizClickmap = ({ iframeRef, visualRef, wrapperRef }) => {
4939
+ const clickMode = useHeatmapConfigStore((state) => state.clickMode);
4940
+ const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
4941
+ const isClickType = heatmapType === IHeatmapType.Click;
4942
+ if (!isClickType)
3859
4943
  return null;
3860
- return (jsx(Fragment, { children: boundaries.map((boundary) => {
3861
- const scrollY = findScrollPositionForUserPercent(boundary.percent);
3862
- if (scrollY === null)
3863
- return null;
3864
- const position = getZonePosition({
3865
- startY: scrollY,
3866
- endY: scrollY,
3867
- });
3868
- if (!position)
3869
- return null;
3870
- return (jsx("div", { className: `marker-boundary-line-${boundary.percent}`, style: {
3871
- position: 'absolute',
3872
- top: `${position.top}px`,
3873
- left: 0,
3874
- transformOrigin: 'left center',
3875
- width: '100%',
3876
- height: '0px',
3877
- // borderBottom: `2px dashed #323130`,
3878
- borderBottom: `2px solid ${boundary.color}`,
3879
- // background: 'repeating-linear-gradient(90deg, #323130, transparent 2px 3px)',
3880
- zIndex: 1,
3881
- display: 'flex',
3882
- alignItems: 'center',
3883
- }, children: jsx("div", { style: {
3884
- position: 'absolute',
3885
- padding: '8px',
3886
- backgroundColor: boundary.color,
3887
- color: 'white',
3888
- fontSize: '16px',
3889
- fontWeight: 600,
3890
- borderRadius: '4px',
3891
- whiteSpace: 'nowrap',
3892
- left: '12px',
3893
- minWidth: '120px',
3894
- textAlign: 'center',
3895
- // textAlign: 'center',
3896
- // padding: '8px',
3897
- // paddingInline: '8px',
3898
- // fontSize: '16px',
3899
- // background: '#fff',
3900
- // width: 'auto',
3901
- // borderRadius: '4px',
3902
- // position: 'absolute',
3903
- // left: '12px',
3904
- // minWidth: '120px',
3905
- }, children: boundary.label }) }, boundary.label));
3906
- }) }));
4944
+ switch (clickMode) {
4945
+ case IClickMode.Default:
4946
+ return jsx(VizElements, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef });
4947
+ case IClickMode.Area:
4948
+ return (jsx(VizAreaClick, { iframeRef: iframeRef, visualRef: visualRef, autoCreateTopN: 0, onAreaClick: (area) => {
4949
+ console.log('area clicked', area);
4950
+ } }));
4951
+ }
3907
4952
  };
3908
4953
 
3909
4954
  const ScrollMapMinimap = ({ zones, maxUsers }) => {
3910
4955
  const scrollType = useHeatmapConfigStore((state) => state.scrollType);
3911
- const { showMinimap } = useHeatmapVizScrollmap();
4956
+ const { showMinimap } = useHeatmapScroll();
3912
4957
  const isScrollType = [IScrollType.Attention].includes(scrollType);
3913
4958
  if (!showMinimap || !isScrollType)
3914
4959
  return null;
@@ -4064,6 +5109,118 @@ const ScrollMapOverlay = ({ wrapperRef, iframeRef }) => {
4064
5109
  }, children: jsx(HoverZones, { position: position, currentScrollPercent: currentScrollPercent, iframeRef: iframeRef, wrapperRef: wrapperRef }) }));
4065
5110
  };
4066
5111
 
5112
+ const AverageFoldLine = ({ iframeRef, wrapperRef }) => {
5113
+ const { dataInfo } = useHeatmapData();
5114
+ const { getZonePosition } = useZonePositions();
5115
+ const averageFold = dataInfo?.averageFold || 50;
5116
+ const position = getZonePosition({
5117
+ startY: averageFold,
5118
+ endY: averageFold,
5119
+ });
5120
+ if (!position)
5121
+ return null;
5122
+ return (jsx("div", { style: {
5123
+ position: 'absolute',
5124
+ top: `${position.top}px`,
5125
+ left: 0,
5126
+ width: '100%',
5127
+ height: '2px',
5128
+ backgroundColor: '#0078D4',
5129
+ pointerEvents: 'none',
5130
+ zIndex: 2,
5131
+ boxShadow: '0 0 4px rgba(0,120,212,0.5)',
5132
+ display: 'flex',
5133
+ alignItems: 'center',
5134
+ }, children: jsxs("div", { style: {
5135
+ position: 'absolute',
5136
+ padding: '8px',
5137
+ backgroundColor: 'rgba(0, 120, 212, 0.9)',
5138
+ color: 'white',
5139
+ fontSize: '16px',
5140
+ fontWeight: 600,
5141
+ borderRadius: '4px',
5142
+ whiteSpace: 'nowrap',
5143
+ left: '12px',
5144
+ minWidth: '120px',
5145
+ textAlign: 'center',
5146
+ }, children: ["Average fold - ", averageFold.toFixed(0), "%"] }) }));
5147
+ };
5148
+
5149
+ const ScrollmapMarker = ({ iframeRef, wrapperRef }) => {
5150
+ const scrollType = useHeatmapConfigStore((state) => state.scrollType);
5151
+ const { scrollmap } = useHeatmapData();
5152
+ const { getZonePosition } = useZonePositions();
5153
+ if (!scrollmap || scrollmap.length === 0)
5154
+ return null;
5155
+ const findScrollPositionForUserPercent = (targetPercent) => {
5156
+ for (let i = 0; i < scrollmap.length; i++) {
5157
+ if (scrollmap[i].percUsers <= targetPercent) {
5158
+ if (i > 0) {
5159
+ return scrollmap[i - 1].scrollReachY;
5160
+ }
5161
+ return scrollmap[i].scrollReachY;
5162
+ }
5163
+ }
5164
+ return scrollmap[scrollmap.length - 1]?.scrollReachY || null;
5165
+ };
5166
+ const boundaries = [
5167
+ { percent: 75, label: '75%', color: '#10B981' },
5168
+ { percent: 50, label: '50%', color: '#F59E0B' },
5169
+ { percent: 25, label: '25%', color: '#EF4444' },
5170
+ { percent: 5, label: '5%', color: '#8B5CF6' },
5171
+ ];
5172
+ const isScrollDepth = scrollType === IScrollType.Depth;
5173
+ if (!isScrollDepth)
5174
+ return null;
5175
+ return (jsx(Fragment, { children: boundaries.map((boundary) => {
5176
+ const scrollY = findScrollPositionForUserPercent(boundary.percent);
5177
+ if (scrollY === null)
5178
+ return null;
5179
+ const position = getZonePosition({
5180
+ startY: scrollY,
5181
+ endY: scrollY,
5182
+ });
5183
+ if (!position)
5184
+ return null;
5185
+ return (jsx("div", { className: `marker-boundary-line-${boundary.percent}`, style: {
5186
+ position: 'absolute',
5187
+ top: `${position.top}px`,
5188
+ left: 0,
5189
+ transformOrigin: 'left center',
5190
+ width: '100%',
5191
+ height: '0px',
5192
+ // borderBottom: `2px dashed #323130`,
5193
+ borderBottom: `2px solid ${boundary.color}`,
5194
+ // background: 'repeating-linear-gradient(90deg, #323130, transparent 2px 3px)',
5195
+ zIndex: 1,
5196
+ display: 'flex',
5197
+ alignItems: 'center',
5198
+ }, children: jsx("div", { style: {
5199
+ position: 'absolute',
5200
+ padding: '8px',
5201
+ backgroundColor: boundary.color,
5202
+ color: 'white',
5203
+ fontSize: '16px',
5204
+ fontWeight: 600,
5205
+ borderRadius: '4px',
5206
+ whiteSpace: 'nowrap',
5207
+ left: '12px',
5208
+ minWidth: '120px',
5209
+ textAlign: 'center',
5210
+ // textAlign: 'center',
5211
+ // padding: '8px',
5212
+ // paddingInline: '8px',
5213
+ // fontSize: '16px',
5214
+ // background: '#fff',
5215
+ // width: 'auto',
5216
+ // borderRadius: '4px',
5217
+ // position: 'absolute',
5218
+ // left: '12px',
5219
+ // minWidth: '120px',
5220
+ }, children: boundary.label }) }, boundary.label));
5221
+ }) }));
5222
+ };
5223
+
4067
5224
  const SCROLL_TYPES = [IHeatmapType.Scroll];
4068
5225
  const VizScrollMap = ({ iframeRef, wrapperRef }) => {
4069
5226
  const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
@@ -4119,12 +5276,11 @@ const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHe
4119
5276
 
4120
5277
  const VizDomRenderer = ({ mode = 'heatmap' }) => {
4121
5278
  const contentWidth = useHeatmapConfigStore((state) => state.width || 0);
4122
- const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
4123
5279
  const wrapperRef = useRef(null);
4124
5280
  const visualRef = useRef(null);
4125
- const { setSelectedElement } = useHeatmapInteraction();
5281
+ const { setSelectedElement } = useHeatmapClick();
4126
5282
  const { iframeHeight, setIframeHeight, isRenderViz } = useHeatmapViz();
4127
- const { iframeRef } = useHeatmapVizRender(mode);
5283
+ const { iframeRef } = useHeatmapRenderByMode(mode);
4128
5284
  const { scaledHeight, handleScroll } = useHeatmapScale({
4129
5285
  wrapperRef,
4130
5286
  iframeRef,
@@ -4145,7 +5301,7 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
4145
5301
  useEffect(() => {
4146
5302
  return cleanUp;
4147
5303
  }, []);
4148
- 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 })), jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, scrolling: "no" }), jsx(VizScrollMap, { iframeRef: iframeRef, wrapperRef: visualRef })] }));
5304
+ 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 })] }));
4149
5305
  };
4150
5306
 
4151
5307
  const VizLoading = () => {
@@ -4163,6 +5319,7 @@ const VizDomHeatmap = () => {
4163
5319
  }, []);
4164
5320
  return (jsxs(VizContainer, { isActive: true, children: [jsx(VizDomRenderer, {}), iframeHeight === 0 && jsx(VizLoading, {})] }));
4165
5321
  };
5322
+ VizDomHeatmap.displayName = 'VizDomHeatmap';
4166
5323
 
4167
5324
  const VizLiveRenderer = () => {
4168
5325
  const contentWidth = useHeatmapConfigStore((state) => state.width);
@@ -4196,6 +5353,7 @@ const VizLiveHeatmap = () => {
4196
5353
  }, []);
4197
5354
  return (jsxs(VizContainer, { isActive: true, children: [jsx(VizLiveRenderer, {}), (!iframeHeight || !wrapperHeight) && jsx(VizLoading, {})] }));
4198
5355
  };
5356
+ VizLiveHeatmap.displayName = 'VizLiveHeatmap';
4199
5357
 
4200
5358
  const ContentVizByMode = () => {
4201
5359
  const mode = useHeatmapConfigStore((state) => state.mode);
@@ -4212,14 +5370,23 @@ const ContentVizByMode = () => {
4212
5370
  return jsx(VizDomHeatmap, {});
4213
5371
  }
4214
5372
  };
5373
+ ContentVizByMode.displayName = 'ContentVizByMode';
4215
5374
 
4216
- const WrapperPreview = () => {
5375
+ const HeatmapPreview = () => {
4217
5376
  return (jsxs(BoxStack, { id: "gx-hm-container", flexDirection: "row", overflow: "hidden", flex: "1", position: "relative", children: [jsx(ContentSidebar, {}), jsxs(BoxStack, { flexDirection: "column", flex: "1", children: [jsx(ContentMetricBar, {}), jsx(ContentVizByMode, {}), jsx(ContentToolbar, {})] })] }));
4218
5377
  };
4219
5378
 
4220
- const WrapperLayout = () => {
4221
- useRenderCount('WrapperLayout');
4222
- return (jsxs(BoxStack, { id: "gx-hm-layout", flexDirection: "column", flex: "1", children: [jsx(ContentTopBar, {}), jsx(WrapperPreview, {})] }));
5379
+ const ContentTopBar = () => {
5380
+ const controls = useHeatmapControlStore((state) => state.controls);
5381
+ useHeatmapConfigStore((state) => state.mode);
5382
+ const TopBar = controls.TopBar;
5383
+ // In compare mode, hide individual top bars since we have a global header
5384
+ // if (mode === 'compare') {
5385
+ // return null;
5386
+ // }
5387
+ return (jsx(BoxStack, { id: "gx-hm-content-header", flexDirection: "row", alignItems: "center", overflow: "auto", zIndex: 1, backgroundColor: "white", style: {
5388
+ borderBottom: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
5389
+ }, children: TopBar && jsx(TopBar, {}) }));
4223
5390
  };
4224
5391
 
4225
5392
  const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
@@ -4230,11 +5397,11 @@ const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
4230
5397
  performanceLogger.configure({
4231
5398
  enabled: true,
4232
5399
  logToConsole: false,
4233
- logLevel: 'normal', // 'verbose' | 'normal' | 'minimal'
5400
+ logLevel: 'normal',
4234
5401
  thresholds: {
4235
- slowRenderMs: 16, // Warn if render > 16ms (60fps)
4236
- slowHookMs: 5, // Warn if hook > 5ms
4237
- excessiveRenderCount: 10, // Warn if component renders > 10 times
5402
+ slowRenderMs: 16,
5403
+ slowHookMs: 5,
5404
+ excessiveRenderCount: 10,
4238
5405
  },
4239
5406
  externalLogger: (metric) => {
4240
5407
  if (metric.name === 'VizDomRenderer') ;
@@ -4243,7 +5410,7 @@ const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
4243
5410
  return (jsx(BoxStack, { id: "gx-hm-project", flexDirection: "column", flex: "1", height: "100%", style: getVariableStyle(), children: jsx(BoxStack, { id: "gx-hm-project-content", flexDirection: "column", flex: "1", children: jsx("div", { style: {
4244
5411
  minHeight: '100%',
4245
5412
  display: 'flex',
4246
- }, children: jsx(WrapperLayout, {}) }) }) }));
5413
+ }, children: jsxs(BoxStack, { id: "gx-hm-layout", flexDirection: "column", flex: "1", children: [jsx(ContentTopBar, {}), jsx(HeatmapPreview, {})] }) }) }) }));
4247
5414
  function getVariableStyle() {
4248
5415
  return {
4249
5416
  '--gx-hm-border-width': `${HEATMAP_CONFIG.borderWidth}px`,
@@ -4253,4 +5420,104 @@ const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
4253
5420
  }
4254
5421
  };
4255
5422
 
4256
- 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, getCompareViewIndex, getMetricsByViewId, getPerformanceReportJSON, getScrollGradientColor, isCompareViewId, performanceLogger, printPerformanceSummary, sendPerformanceReport, trackStoreAction, useClickedElement, useDebounceCallback, useElementCalloutVisible, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapCopyView, useHeatmapData, useHeatmapDataStore, useHeatmapEffects, useHeatmapElementPosition, useHeatmapInteraction, useHeatmapInteractionStore, useHeatmapLiveStore, useHeatmapScale, useHeatmapSingleStore, useHeatmapViz, useHeatmapVizRender, useHeatmapVizScrollmap, useHeatmapVizScrollmapStore, useHeatmapVizStore, useHoveredElement, useIsCompareMode, useMeasureFunction, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useRenderCount, useScrollmapZones, useTrackHookCall, useViewId, useVizLiveRender, useWhyDidYouUpdate, useWrapperRefHeight, useZonePositions, withPerformanceTracking };
5423
+ const AreaEditHighlight = ({ element, shadowRoot, onClick }) => {
5424
+ const [rect, setRect] = useState(null);
5425
+ const highlightRef = useRef(null);
5426
+ useEffect(() => {
5427
+ if (!element) {
5428
+ setRect(null);
5429
+ return;
5430
+ }
5431
+ const elementRect = getElementRect(element);
5432
+ setRect(elementRect);
5433
+ }, [element, shadowRoot]);
5434
+ if (!rect) {
5435
+ return null;
5436
+ }
5437
+ const handleClick = (e) => {
5438
+ if (element && onClick) {
5439
+ e.stopPropagation();
5440
+ e.preventDefault();
5441
+ onClick(element);
5442
+ }
5443
+ };
5444
+ return (jsx("div", { ref: highlightRef, id: AREA_HOVER_ELEMENT_ID, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: handleClick, style: {
5445
+ position: 'absolute',
5446
+ top: `${rect.absoluteTop}px`,
5447
+ left: `${rect.absoluteLeft}px`,
5448
+ width: `${rect.width}px`,
5449
+ height: `${rect.height}px`,
5450
+ zIndex: Number.MAX_SAFE_INTEGER,
5451
+ boxShadow: AREA_HOVER_BOX_SHADOW,
5452
+ backgroundColor: 'rgba(128, 128, 128, 0.4)',
5453
+ backgroundImage: 'repeating-linear-gradient(135deg, transparent, transparent 35px, rgba(255,255,255,.5) 35px, rgba(255,255,255,.5) 70px)',
5454
+ pointerEvents: 'auto',
5455
+ cursor: 'pointer',
5456
+ boxSizing: 'border-box',
5457
+ } }));
5458
+ };
5459
+ AreaEditHighlight.displayName = 'AreaEditHighlight';
5460
+
5461
+ const AreaLabel = ({ clickDist, totalClicks, kind }) => {
5462
+ if (kind === 'money') {
5463
+ return null;
5464
+ }
5465
+ return (jsxs("div", { style: {
5466
+ color: '#161514',
5467
+ backgroundColor: 'rgba(255, 255, 255, 0.86)',
5468
+ display: 'flex',
5469
+ flexDirection: 'column',
5470
+ alignItems: 'center',
5471
+ padding: '8px',
5472
+ borderRadius: '4px',
5473
+ fontSize: '16px',
5474
+ lineHeight: '20px',
5475
+ minWidth: '56px',
5476
+ fontWeight: 600,
5477
+ fontFamily: '"Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif',
5478
+ pointerEvents: 'none',
5479
+ }, children: [jsxs("span", { children: [clickDist.toFixed(2), "%"] }), jsxs("span", { style: { fontSize: '12px', fontWeight: 400, opacity: 0.8 }, children: [totalClicks, " clicks"] })] }));
5480
+ };
5481
+ AreaLabel.displayName = 'AreaLabel';
5482
+
5483
+ const AreaOverlay = ({ area, onClick, onMouseEnter, onMouseLeave, isSelected, isHovered, }) => {
5484
+ const [rect, setRect] = useState(area.rect.value);
5485
+ useEffect(() => {
5486
+ const handleRectChange = (newRect) => {
5487
+ if (newRect) {
5488
+ setRect(newRect);
5489
+ }
5490
+ };
5491
+ area.rect.observe(handleRectChange);
5492
+ return () => {
5493
+ area.rect.unobserve(handleRectChange);
5494
+ };
5495
+ }, [area.rect]);
5496
+ if (!rect)
5497
+ return null;
5498
+ const position = area.isFixed ? 'fixed' : 'absolute';
5499
+ const showLabel = !isRectTooSmallForLabel(rect);
5500
+ const backgroundColor = isHovered ? area.hoverColor : area.color;
5501
+ const boxShadow = isSelected ? '0 0 0 3px red inset' : isHovered ? AREA_HOVER_BOX_SHADOW : '0 0 0 2px white inset';
5502
+ 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: `
5503
+ #area-${area.id} {
5504
+ position: ${position};
5505
+ top: ${rect.top}px;
5506
+ left: ${rect.left}px;
5507
+ width: ${rect.width}px;
5508
+ height: ${rect.height}px;
5509
+ background-color: ${backgroundColor};
5510
+ box-shadow: ${boxShadow};
5511
+ box-sizing: border-box;
5512
+ display: flex;
5513
+ align-items: center;
5514
+ justify-content: center;
5515
+ cursor: pointer;
5516
+ transition: background-color 0.2s, box-shadow 0.2s;
5517
+ pointer-events: auto;
5518
+ z-index: ${Number.MAX_SAFE_INTEGER};
5519
+ }
5520
+ ` }), showLabel && jsx(AreaLabel, { clickDist: area.clickDist, totalClicks: area.totalclicks, kind: area.kind })] }));
5521
+ };
5522
+
5523
+ 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, trackStoreAction, useAreaCreation, useAreaEditMode, useAreaFilterVisible, 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 };