@gemx-dev/heatmap-react 3.5.52 → 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 (204) hide show
  1. package/dist/esm/components/VizAreaClick/AreaEditHighlight.d.ts.map +1 -1
  2. package/dist/esm/components/VizAreaClick/AreaOverlay.d.ts.map +1 -1
  3. package/dist/esm/components/VizAreaClick/PortalAreaRenderer.d.ts +1 -1
  4. package/dist/esm/components/VizAreaClick/PortalAreaRenderer.d.ts.map +1 -1
  5. package/dist/esm/components/VizAreaClick/VizAreaClick.d.ts +1 -7
  6. package/dist/esm/components/VizAreaClick/VizAreaClick.d.ts.map +1 -1
  7. package/dist/esm/components/VizAreaClick/index.d.ts +0 -1
  8. package/dist/esm/components/VizAreaClick/index.d.ts.map +1 -1
  9. package/dist/esm/components/VizClickmap/VizClickmap.d.ts +8 -0
  10. package/dist/esm/components/VizClickmap/VizClickmap.d.ts.map +1 -0
  11. package/dist/esm/components/VizClickmap/index.d.ts +2 -0
  12. package/dist/esm/components/VizClickmap/index.d.ts.map +1 -0
  13. package/dist/esm/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  14. package/dist/esm/components/VizElement/ElementOverlay.d.ts +0 -1
  15. package/dist/esm/components/VizElement/ElementOverlay.d.ts.map +1 -1
  16. package/dist/esm/components/VizElement/HeatmapElements.d.ts.map +1 -1
  17. package/dist/esm/components/index.d.ts +1 -0
  18. package/dist/esm/components/index.d.ts.map +1 -1
  19. package/dist/esm/constants/index.d.ts +1 -0
  20. package/dist/esm/constants/index.d.ts.map +1 -1
  21. package/dist/esm/constants/selectors.d.ts +4 -0
  22. package/dist/esm/constants/selectors.d.ts.map +1 -0
  23. package/dist/esm/constants/viz-area-click.d.ts +10 -0
  24. package/dist/esm/constants/viz-area-click.d.ts.map +1 -1
  25. package/dist/esm/helpers/dom-utils.d.ts +29 -0
  26. package/dist/esm/helpers/dom-utils.d.ts.map +1 -0
  27. package/dist/esm/helpers/iframe-helper/fixer.d.ts.map +1 -1
  28. package/dist/esm/helpers/iframe-helper/navigation-blocker-v2.d.ts +4 -1
  29. package/dist/esm/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -1
  30. package/dist/esm/helpers/iframe-helper/navigation-blocker.d.ts.map +1 -1
  31. package/dist/esm/helpers/iframe-helper/style-replacer.d.ts.map +1 -1
  32. package/dist/esm/helpers/index.d.ts +2 -0
  33. package/dist/esm/helpers/index.d.ts.map +1 -1
  34. package/dist/esm/helpers/logger.d.ts +77 -0
  35. package/dist/esm/helpers/logger.d.ts.map +1 -0
  36. package/dist/esm/helpers/viz-area-click/area-builder.d.ts +2 -2
  37. package/dist/esm/helpers/viz-area-click/area-builder.d.ts.map +1 -1
  38. package/dist/esm/helpers/viz-area-click/area-color.d.ts.map +1 -1
  39. package/dist/esm/helpers/viz-area-click/area-renderer-setup.d.ts +20 -0
  40. package/dist/esm/helpers/viz-area-click/area-renderer-setup.d.ts.map +1 -0
  41. package/dist/esm/helpers/viz-area-click/area-utils.d.ts +0 -41
  42. package/dist/esm/helpers/viz-area-click/area-utils.d.ts.map +1 -1
  43. package/dist/esm/helpers/viz-area-click/index.d.ts +1 -0
  44. package/dist/esm/helpers/viz-area-click/index.d.ts.map +1 -1
  45. package/dist/esm/helpers/viz-elm-callout/getter.d.ts +0 -2
  46. package/dist/esm/helpers/viz-elm-callout/getter.d.ts.map +1 -1
  47. package/dist/esm/hooks/register/useRegisterConfig.d.ts.map +1 -1
  48. package/dist/esm/hooks/view-context/useHeatmapAreaClick.d.ts +1 -0
  49. package/dist/esm/hooks/view-context/useHeatmapAreaClick.d.ts.map +1 -1
  50. package/dist/esm/hooks/viz-area-click/index.d.ts +7 -0
  51. package/dist/esm/hooks/viz-area-click/index.d.ts.map +1 -1
  52. package/dist/esm/hooks/viz-area-click/useAreaCreation.d.ts +10 -0
  53. package/dist/esm/hooks/viz-area-click/useAreaCreation.d.ts.map +1 -0
  54. package/dist/esm/hooks/viz-area-click/useAreaEditMode.d.ts +2 -2
  55. package/dist/esm/hooks/viz-area-click/useAreaEditMode.d.ts.map +1 -1
  56. package/dist/esm/hooks/viz-area-click/useAreaFilterVisible.d.ts +7 -0
  57. package/dist/esm/hooks/viz-area-click/useAreaFilterVisible.d.ts.map +1 -0
  58. package/dist/esm/hooks/viz-area-click/useAreaInteraction.d.ts +17 -0
  59. package/dist/esm/hooks/viz-area-click/useAreaInteraction.d.ts.map +1 -0
  60. package/dist/esm/hooks/viz-area-click/useAreaPortals.d.ts +15 -0
  61. package/dist/esm/hooks/viz-area-click/useAreaPortals.d.ts.map +1 -0
  62. package/dist/esm/hooks/viz-area-click/useAreaRectSync.d.ts +8 -0
  63. package/dist/esm/hooks/viz-area-click/useAreaRectSync.d.ts.map +1 -0
  64. package/dist/esm/hooks/viz-area-click/useAreaRendererContainer.d.ts +13 -0
  65. package/dist/esm/hooks/viz-area-click/useAreaRendererContainer.d.ts.map +1 -0
  66. package/dist/esm/hooks/viz-area-click/useAreaScrollSync.d.ts +1 -2
  67. package/dist/esm/hooks/viz-area-click/useAreaScrollSync.d.ts.map +1 -1
  68. package/dist/esm/hooks/viz-area-click/useAreaTopAutoDetect.d.ts +8 -0
  69. package/dist/esm/hooks/viz-area-click/useAreaTopAutoDetect.d.ts.map +1 -0
  70. package/dist/esm/hooks/viz-canvas/useAreaClickmap.d.ts +4 -0
  71. package/dist/esm/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -0
  72. package/dist/esm/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
  73. package/dist/esm/hooks/viz-elm/useClickedElement.d.ts +1 -0
  74. package/dist/esm/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
  75. package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -1
  76. package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
  77. package/dist/esm/hooks/viz-elm/useHeatmapMouseHandler.d.ts +0 -25
  78. package/dist/esm/hooks/viz-elm/useHeatmapMouseHandler.d.ts.map +1 -1
  79. package/dist/esm/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
  80. package/dist/esm/hooks/viz-render/index.d.ts +1 -1
  81. package/dist/esm/hooks/viz-render/index.d.ts.map +1 -1
  82. package/dist/esm/hooks/viz-render/useHeatmapRenderByMode.d.ts +6 -0
  83. package/dist/esm/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -0
  84. package/dist/esm/index.js +968 -679
  85. package/dist/esm/index.mjs +968 -679
  86. package/dist/esm/stores/config.d.ts +3 -1
  87. package/dist/esm/stores/config.d.ts.map +1 -1
  88. package/dist/esm/stores/mode-compare.d.ts.map +1 -1
  89. package/dist/esm/stores/viz-area-click.d.ts +1 -0
  90. package/dist/esm/stores/viz-area-click.d.ts.map +1 -1
  91. package/dist/esm/types/heatmap.d.ts +4 -0
  92. package/dist/esm/types/heatmap.d.ts.map +1 -1
  93. package/dist/esm/types/iframe-helper.d.ts +1 -0
  94. package/dist/esm/types/iframe-helper.d.ts.map +1 -1
  95. package/dist/umd/components/VizAreaClick/AreaEditHighlight.d.ts.map +1 -1
  96. package/dist/umd/components/VizAreaClick/AreaOverlay.d.ts.map +1 -1
  97. package/dist/umd/components/VizAreaClick/PortalAreaRenderer.d.ts +1 -1
  98. package/dist/umd/components/VizAreaClick/PortalAreaRenderer.d.ts.map +1 -1
  99. package/dist/umd/components/VizAreaClick/VizAreaClick.d.ts +1 -7
  100. package/dist/umd/components/VizAreaClick/VizAreaClick.d.ts.map +1 -1
  101. package/dist/umd/components/VizAreaClick/index.d.ts +0 -1
  102. package/dist/umd/components/VizAreaClick/index.d.ts.map +1 -1
  103. package/dist/umd/components/VizClickmap/VizClickmap.d.ts +8 -0
  104. package/dist/umd/components/VizClickmap/VizClickmap.d.ts.map +1 -0
  105. package/dist/umd/components/VizClickmap/index.d.ts +2 -0
  106. package/dist/umd/components/VizClickmap/index.d.ts.map +1 -0
  107. package/dist/umd/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  108. package/dist/umd/components/VizElement/ElementOverlay.d.ts +0 -1
  109. package/dist/umd/components/VizElement/ElementOverlay.d.ts.map +1 -1
  110. package/dist/umd/components/VizElement/HeatmapElements.d.ts.map +1 -1
  111. package/dist/umd/components/index.d.ts +1 -0
  112. package/dist/umd/components/index.d.ts.map +1 -1
  113. package/dist/umd/constants/index.d.ts +1 -0
  114. package/dist/umd/constants/index.d.ts.map +1 -1
  115. package/dist/umd/constants/selectors.d.ts +4 -0
  116. package/dist/umd/constants/selectors.d.ts.map +1 -0
  117. package/dist/umd/constants/viz-area-click.d.ts +10 -0
  118. package/dist/umd/constants/viz-area-click.d.ts.map +1 -1
  119. package/dist/umd/helpers/dom-utils.d.ts +29 -0
  120. package/dist/umd/helpers/dom-utils.d.ts.map +1 -0
  121. package/dist/umd/helpers/iframe-helper/fixer.d.ts.map +1 -1
  122. package/dist/umd/helpers/iframe-helper/navigation-blocker-v2.d.ts +4 -1
  123. package/dist/umd/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -1
  124. package/dist/umd/helpers/iframe-helper/navigation-blocker.d.ts.map +1 -1
  125. package/dist/umd/helpers/iframe-helper/style-replacer.d.ts.map +1 -1
  126. package/dist/umd/helpers/index.d.ts +2 -0
  127. package/dist/umd/helpers/index.d.ts.map +1 -1
  128. package/dist/umd/helpers/logger.d.ts +77 -0
  129. package/dist/umd/helpers/logger.d.ts.map +1 -0
  130. package/dist/umd/helpers/viz-area-click/area-builder.d.ts +2 -2
  131. package/dist/umd/helpers/viz-area-click/area-builder.d.ts.map +1 -1
  132. package/dist/umd/helpers/viz-area-click/area-color.d.ts.map +1 -1
  133. package/dist/umd/helpers/viz-area-click/area-renderer-setup.d.ts +20 -0
  134. package/dist/umd/helpers/viz-area-click/area-renderer-setup.d.ts.map +1 -0
  135. package/dist/umd/helpers/viz-area-click/area-utils.d.ts +0 -41
  136. package/dist/umd/helpers/viz-area-click/area-utils.d.ts.map +1 -1
  137. package/dist/umd/helpers/viz-area-click/index.d.ts +1 -0
  138. package/dist/umd/helpers/viz-area-click/index.d.ts.map +1 -1
  139. package/dist/umd/helpers/viz-elm-callout/getter.d.ts +0 -2
  140. package/dist/umd/helpers/viz-elm-callout/getter.d.ts.map +1 -1
  141. package/dist/umd/hooks/register/useRegisterConfig.d.ts.map +1 -1
  142. package/dist/umd/hooks/view-context/useHeatmapAreaClick.d.ts +1 -0
  143. package/dist/umd/hooks/view-context/useHeatmapAreaClick.d.ts.map +1 -1
  144. package/dist/umd/hooks/viz-area-click/index.d.ts +7 -0
  145. package/dist/umd/hooks/viz-area-click/index.d.ts.map +1 -1
  146. package/dist/umd/hooks/viz-area-click/useAreaCreation.d.ts +10 -0
  147. package/dist/umd/hooks/viz-area-click/useAreaCreation.d.ts.map +1 -0
  148. package/dist/umd/hooks/viz-area-click/useAreaEditMode.d.ts +2 -2
  149. package/dist/umd/hooks/viz-area-click/useAreaEditMode.d.ts.map +1 -1
  150. package/dist/umd/hooks/viz-area-click/useAreaFilterVisible.d.ts +7 -0
  151. package/dist/umd/hooks/viz-area-click/useAreaFilterVisible.d.ts.map +1 -0
  152. package/dist/umd/hooks/viz-area-click/useAreaInteraction.d.ts +17 -0
  153. package/dist/umd/hooks/viz-area-click/useAreaInteraction.d.ts.map +1 -0
  154. package/dist/umd/hooks/viz-area-click/useAreaPortals.d.ts +15 -0
  155. package/dist/umd/hooks/viz-area-click/useAreaPortals.d.ts.map +1 -0
  156. package/dist/umd/hooks/viz-area-click/useAreaRectSync.d.ts +8 -0
  157. package/dist/umd/hooks/viz-area-click/useAreaRectSync.d.ts.map +1 -0
  158. package/dist/umd/hooks/viz-area-click/useAreaRendererContainer.d.ts +13 -0
  159. package/dist/umd/hooks/viz-area-click/useAreaRendererContainer.d.ts.map +1 -0
  160. package/dist/umd/hooks/viz-area-click/useAreaScrollSync.d.ts +1 -2
  161. package/dist/umd/hooks/viz-area-click/useAreaScrollSync.d.ts.map +1 -1
  162. package/dist/umd/hooks/viz-area-click/useAreaTopAutoDetect.d.ts +8 -0
  163. package/dist/umd/hooks/viz-area-click/useAreaTopAutoDetect.d.ts.map +1 -0
  164. package/dist/umd/hooks/viz-canvas/useAreaClickmap.d.ts +4 -0
  165. package/dist/umd/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -0
  166. package/dist/umd/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
  167. package/dist/umd/hooks/viz-elm/useClickedElement.d.ts +1 -0
  168. package/dist/umd/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
  169. package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -1
  170. package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
  171. package/dist/umd/hooks/viz-elm/useHeatmapMouseHandler.d.ts +0 -25
  172. package/dist/umd/hooks/viz-elm/useHeatmapMouseHandler.d.ts.map +1 -1
  173. package/dist/umd/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
  174. package/dist/umd/hooks/viz-render/index.d.ts +1 -1
  175. package/dist/umd/hooks/viz-render/index.d.ts.map +1 -1
  176. package/dist/umd/hooks/viz-render/useHeatmapRenderByMode.d.ts +6 -0
  177. package/dist/umd/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -0
  178. package/dist/umd/index.js +2 -2
  179. package/dist/umd/stores/config.d.ts +3 -1
  180. package/dist/umd/stores/config.d.ts.map +1 -1
  181. package/dist/umd/stores/mode-compare.d.ts.map +1 -1
  182. package/dist/umd/stores/viz-area-click.d.ts +1 -0
  183. package/dist/umd/stores/viz-area-click.d.ts.map +1 -1
  184. package/dist/umd/types/heatmap.d.ts +4 -0
  185. package/dist/umd/types/heatmap.d.ts.map +1 -1
  186. package/dist/umd/types/iframe-helper.d.ts +1 -0
  187. package/dist/umd/types/iframe-helper.d.ts.map +1 -1
  188. package/package.json +1 -1
  189. package/dist/esm/components/VizAreaClick/AreaControls.d.ts +0 -10
  190. package/dist/esm/components/VizAreaClick/AreaControls.d.ts.map +0 -1
  191. package/dist/esm/hooks/viz-area/useAreaHeatmap.d.ts +0 -59
  192. package/dist/esm/hooks/viz-area/useAreaHeatmap.d.ts.map +0 -1
  193. package/dist/esm/hooks/viz-area/useAreaHeatmapManager.d.ts +0 -77
  194. package/dist/esm/hooks/viz-area/useAreaHeatmapManager.d.ts.map +0 -1
  195. package/dist/esm/hooks/viz-render/useHeatmapVizRender.d.ts +0 -6
  196. package/dist/esm/hooks/viz-render/useHeatmapVizRender.d.ts.map +0 -1
  197. package/dist/umd/components/VizAreaClick/AreaControls.d.ts +0 -10
  198. package/dist/umd/components/VizAreaClick/AreaControls.d.ts.map +0 -1
  199. package/dist/umd/hooks/viz-area/useAreaHeatmap.d.ts +0 -59
  200. package/dist/umd/hooks/viz-area/useAreaHeatmap.d.ts.map +0 -1
  201. package/dist/umd/hooks/viz-area/useAreaHeatmapManager.d.ts +0 -77
  202. package/dist/umd/hooks/viz-area/useAreaHeatmapManager.d.ts.map +0 -1
  203. package/dist/umd/hooks/viz-render/useHeatmapVizRender.d.ts +0 -6
  204. package/dist/umd/hooks/viz-render/useHeatmapVizRender.d.ts.map +0 -1
@@ -5,8 +5,8 @@ import { useEffect, useRef, useCallback, createContext, useContext, useMemo, use
5
5
  import { create } from 'zustand';
6
6
  import { subscribeWithSelector } from 'zustand/middleware';
7
7
  import { decode } from '@gemx-dev/clarity-decode';
8
- import { Visualizer } from '@gemx-dev/clarity-visualize';
9
8
  import { createPortal } from 'react-dom';
9
+ import { Visualizer } from '@gemx-dev/clarity-visualize';
10
10
 
11
11
  const initialNodes = { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } };
12
12
  const GraphView = ({ children, width, height }) => {
@@ -148,6 +148,11 @@ var IClickType;
148
148
  IClickType["First"] = "first-clicks";
149
149
  IClickType["Last"] = "last-clicks";
150
150
  })(IClickType || (IClickType = {}));
151
+ var IClickMode;
152
+ (function (IClickMode) {
153
+ IClickMode["Default"] = "default";
154
+ IClickMode["Area"] = "click-area";
155
+ })(IClickMode || (IClickMode = {}));
151
156
  var IScrollType;
152
157
  (function (IScrollType) {
153
158
  IScrollType["Depth"] = "scroll-depth";
@@ -162,6 +167,7 @@ const useHeatmapConfigStore = create()((set) => {
162
167
  sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
163
168
  heatmapType: IHeatmapType.Click,
164
169
  clickType: IClickType.Total,
170
+ clickMode: IClickMode.Default,
165
171
  scrollType: IScrollType.Depth,
166
172
  isRendering: true,
167
173
  setMode: (mode) => set({ mode }),
@@ -170,6 +176,7 @@ const useHeatmapConfigStore = create()((set) => {
170
176
  setSidebarWidth: (sidebarWidth) => set({ sidebarWidth }),
171
177
  setHeatmapType: (heatmapType) => set({ heatmapType }),
172
178
  setClickType: (clickType) => set({ clickType }),
179
+ setClickMode: (clickMode) => set({ clickMode }),
173
180
  setScrollType: (scrollType) => set({ scrollType }),
174
181
  setIsRendering: (isRendering) => set({ isRendering }),
175
182
  };
@@ -335,7 +342,7 @@ const useHeatmapAreaClickStore = create()(subscribeWithSelector((set) => ({
335
342
  }),
336
343
  updateArea: (areaId, updates, viewId = DEFAULT_VIEW_ID) => set((state) => {
337
344
  const currentAreas = state.areas[viewId] || [];
338
- const updatedAreas = currentAreas.map((area) => area.id === areaId ? { ...area, ...updates } : area);
345
+ const updatedAreas = currentAreas.map((area) => (area.id === areaId ? { ...area, ...updates } : area));
339
346
  return {
340
347
  areas: { ...state.areas, [viewId]: updatedAreas },
341
348
  };
@@ -379,6 +386,11 @@ const useHeatmapAreaClickStore = create()(subscribeWithSelector((set) => ({
379
386
  isEditingMode: newIsEditingMode,
380
387
  };
381
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
+ })),
382
394
  resetAll: () => set({
383
395
  selectedArea: { [DEFAULT_VIEW_ID]: null },
384
396
  hoveredArea: { [DEFAULT_VIEW_ID]: null },
@@ -533,9 +545,13 @@ const useHeatmapCompareStore = create()((set, get) => {
533
545
  const state = get();
534
546
  const newViews = new Map(state.views);
535
547
  newViews.delete(viewId);
548
+ const newViewIdCounter = newViews.size;
549
+ const newLayout = newViews.size === 2 ? 'grid-2' : state.layout;
536
550
  set({
537
551
  views: newViews,
538
552
  viewOrder: state.viewOrder.filter((id) => id !== viewId),
553
+ viewIdCounter: newViewIdCounter,
554
+ layout: newLayout,
539
555
  });
540
556
  },
541
557
  updateView: (viewId, updates) => {
@@ -680,13 +696,14 @@ const useRegisterConfig = () => {
680
696
  const width = useHeatmapConfigStore((state) => state.width);
681
697
  const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
682
698
  const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
699
+ const clickMode = useHeatmapConfigStore((state) => state.clickMode);
683
700
  const setIsRendering = useHeatmapConfigStore((state) => state.setIsRendering);
684
701
  useEffect(() => {
685
702
  setIsRendering(true);
686
703
  setTimeout(() => {
687
704
  setIsRendering(false);
688
705
  }, 1000);
689
- }, [mode, width, sidebarWidth, heatmapType]);
706
+ }, [mode, width, sidebarWidth, heatmapType, clickMode]); // eslint-disable-line react-hooks/exhaustive-deps
690
707
  };
691
708
 
692
709
  const useRegisterControl = (control) => {
@@ -722,6 +739,7 @@ const useHeatmapAreaClick = (props) => {
722
739
  const removeAreaStore = useHeatmapAreaClickStore((state) => state.removeArea);
723
740
  const updateAreaStore = useHeatmapAreaClickStore((state) => state.updateArea);
724
741
  const clearAreasStore = useHeatmapAreaClickStore((state) => state.clearAreas);
742
+ const resetViewStore = useHeatmapAreaClickStore((state) => state.resetView);
725
743
  // Memoize operations to prevent unnecessary re-renders
726
744
  const memoizedOperations = useMemo(() => ({
727
745
  setSelectedArea: (area) => setSelectedAreaStore(area, viewId),
@@ -732,6 +750,7 @@ const useHeatmapAreaClick = (props) => {
732
750
  removeArea: (areaId) => removeAreaStore(areaId, viewId),
733
751
  updateArea: (areaId, updates) => updateAreaStore(areaId, updates, viewId),
734
752
  clearAreas: () => clearAreasStore(viewId),
753
+ resetView: () => resetViewStore(viewId),
735
754
  }), [
736
755
  setSelectedAreaStore,
737
756
  setHoveredAreaStore,
@@ -741,6 +760,7 @@ const useHeatmapAreaClick = (props) => {
741
760
  removeAreaStore,
742
761
  updateAreaStore,
743
762
  clearAreasStore,
763
+ resetViewStore,
744
764
  viewId,
745
765
  ]);
746
766
  return {
@@ -1046,9 +1066,37 @@ function isElementInViewport(elementRect, visualRef, scale) {
1046
1066
  return elementBottom > viewportTop && elementTop < viewportBottom;
1047
1067
  }
1048
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
+
1049
1081
  const AREA_HOVER_BOX_SHADOW = '0 0 0 1px #0078D4, 0 0 0 1px #0078D4 inset, 0 0 0 2px white inset';
1050
1082
  const AREA_HOVER_ELEMENT_ID = 'clarity-edit-hover';
1051
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
+ `;
1052
1100
  const AREA_COLOR_GRADIENT = [
1053
1101
  [0, 0, 255], // Blue
1054
1102
  [0, 255, 255], // Cyan
@@ -1056,6 +1104,12 @@ const AREA_COLOR_GRADIENT = [
1056
1104
  [255, 255, 0], // Yellow
1057
1105
  [255, 0, 0], // Red
1058
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
+ };
1059
1113
 
1060
1114
  const CALLOUT_PADDING = 0;
1061
1115
  const CALLOUT_ARROW_SIZE = 8;
@@ -1071,6 +1125,7 @@ const SECONDARY_HOVERED_ELEMENT_ID_BASE = 'gx-hm-secondary-hovered-element';
1071
1125
  function getColorFromClickDist(clickDist) {
1072
1126
  // Ensure clickDist is in range [0, 100]
1073
1127
  const normalizedDist = Math.max(0, Math.min(100, clickDist));
1128
+ console.log(`🚀 🐥 ~ getColorFromClickDist ~ normalizedDist:`, normalizedDist);
1074
1129
  // Calculate gradient index
1075
1130
  const maxIndex = AREA_COLOR_GRADIENT.length - 1;
1076
1131
  const index = Math.floor((normalizedDist / 100) * maxIndex);
@@ -1100,27 +1155,15 @@ function calculateClickDistribution(elementClicks, totalClicks) {
1100
1155
  return (elementClicks / totalClicks) * 100;
1101
1156
  }
1102
1157
 
1103
- /**
1104
- * Get element position and dimensions for rendering inside iframe
1105
- *
1106
- * This function calculates position relative to the iframe's document,
1107
- * accounting for scroll position. Suitable for elements rendered in
1108
- * shadow DOM inside the iframe with absolute positioning.
1109
- */
1110
1158
  function getElementRect(element, _shadowRoot) {
1111
1159
  const rect = element.getBoundingClientRect();
1112
1160
  const width = rect.width;
1113
1161
  const height = rect.height;
1114
- // Get the document to access scroll position
1115
1162
  const doc = element.ownerDocument || document;
1116
- // Get scroll offset from documentElement or body
1117
1163
  const scrollTop = doc.documentElement?.scrollTop || doc.body?.scrollTop || 0;
1118
1164
  const scrollLeft = doc.documentElement?.scrollLeft || doc.body?.scrollLeft || 0;
1119
- // Calculate position relative to document (not viewport)
1120
- // getBoundingClientRect() is relative to viewport, so add scroll offset
1121
1165
  const top = rect.top + scrollTop;
1122
1166
  const left = rect.left + scrollLeft;
1123
- // For absolute positioning calculations (overlap detection)
1124
1167
  const absoluteLeft = left;
1125
1168
  const absoluteTop = top;
1126
1169
  const absoluteRight = absoluteLeft + width;
@@ -1137,9 +1180,6 @@ function getElementRect(element, _shadowRoot) {
1137
1180
  outOfBounds: false,
1138
1181
  };
1139
1182
  }
1140
- /**
1141
- * Check if element has CSS position: fixed
1142
- */
1143
1183
  function isElementFixed(element) {
1144
1184
  if (getComputedStyle(element).position === 'fixed') {
1145
1185
  return true;
@@ -1150,9 +1190,6 @@ function isElementFixed(element) {
1150
1190
  const parent = element.parentElement;
1151
1191
  return parent ? isElementFixed(parent) : false;
1152
1192
  }
1153
- /**
1154
- * Check if two areas overlap
1155
- */
1156
1193
  function doAreasOverlap(area1, area2) {
1157
1194
  const r1 = area1.rect.value;
1158
1195
  const r2 = area2.rect.value;
@@ -1167,9 +1204,6 @@ function doAreasOverlap(area1, area2) {
1167
1204
  r2.absoluteRight > r1.absoluteLeft &&
1168
1205
  r2.absoluteLeft < r1.absoluteRight));
1169
1206
  }
1170
- /**
1171
- * Check if area1 is completely inside area2
1172
- */
1173
1207
  function isAreaContainedIn(area1, area2) {
1174
1208
  const r1 = area1.rect.value;
1175
1209
  const r2 = area2.rect.value;
@@ -1180,64 +1214,29 @@ function isAreaContainedIn(area1, area2) {
1180
1214
  r1.absoluteLeft >= r2.absoluteLeft &&
1181
1215
  r1.absoluteRight <= r2.absoluteRight);
1182
1216
  }
1183
- /**
1184
- * Check if element contains another element in DOM tree
1185
- */
1186
1217
  function isElementAncestorOf(ancestor, descendant, doc) {
1187
1218
  return ancestor.contains(descendant);
1188
1219
  }
1189
- /**
1190
- * Get all elements at a specific point, including shadow DOM
1191
- */
1192
- function getElementsAtPoint$1(doc, x, y, filter, visited = new Set()) {
1193
- const elements = doc.elementsFromPoint(x, y);
1194
- const filtered = elements.find(filter);
1195
- // Check shadow DOM
1196
- if (filtered?.shadowRoot && !visited.has(filtered.shadowRoot)) {
1197
- visited.add(filtered.shadowRoot);
1198
- return getElementsAtPoint$1(filtered.shadowRoot, x, y, filter, visited);
1199
- }
1200
- return elements;
1201
- }
1202
- /**
1203
- * Check if element should be selectable for area creation
1204
- */
1205
1220
  function isElementSelectable(element, index, elements) {
1206
- // Skip if it's an area map div
1221
+ if (isIgnoredCanvas(element)) {
1222
+ return false;
1223
+ }
1207
1224
  if (element.hasAttribute(AREA_MAP_DIV_ATTRIBUTE)) {
1208
1225
  return false;
1209
1226
  }
1210
- // Skip first element if it's BODY and there are other elements
1211
1227
  if (index === 0 && elements.length > 1 && element.nodeName === 'BODY') {
1212
1228
  return false;
1213
1229
  }
1214
1230
  return true;
1215
1231
  }
1216
- /**
1217
- * Sort areas by click distribution (descending)
1218
- */
1219
1232
  function sortAreasByClickDist(areas) {
1220
1233
  return [...areas].sort((a, b) => {
1221
- // Higher clickDist first
1222
1234
  if (a.clickDist !== b.clickDist) {
1223
1235
  return b.clickDist - a.clickDist;
1224
1236
  }
1225
- // Then by total clicks
1226
1237
  return b.totalclicks - a.totalclicks;
1227
1238
  });
1228
1239
  }
1229
- /**
1230
- * Create shadow root or return existing one
1231
- */
1232
- function getOrCreateShadowRoot(element) {
1233
- if (element.shadowRoot) {
1234
- return element.shadowRoot;
1235
- }
1236
- return element.attachShadow({ mode: 'open' });
1237
- }
1238
- /**
1239
- * Check if rect is too small to show label
1240
- */
1241
1240
  function isRectTooSmallForLabel(rect) {
1242
1241
  return rect.width < 67 || rect.height < 30;
1243
1242
  }
@@ -1254,8 +1253,11 @@ function getElementSelector(element) {
1254
1253
  }
1255
1254
  return element.tagName.toLowerCase();
1256
1255
  }
1257
- function buildAreaNode(element, hash, elementMapInfo, totalClicks, shadowRoot) {
1258
- const elementInfo = elementMapInfo[hash];
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];
1259
1261
  const elementClicks = elementInfo?.totalclicks || 0;
1260
1262
  const clickDist = calculateClickDistribution(elementClicks, totalClicks);
1261
1263
  const rect = getElementRect(element);
@@ -1446,6 +1448,67 @@ function getVisibleAreas(areas, iframeDocument) {
1446
1448
  return sortAreasByClickDist(visible);
1447
1449
  }
1448
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
+
1449
1512
  function findLastSizeOfDom(data) {
1450
1513
  const listDocs = data
1451
1514
  .filter((item) => item.doc?.find((doc) => doc.data.width && doc.data.height))
@@ -1510,23 +1573,6 @@ function getElementLayout(element) {
1510
1573
  height: rect.height,
1511
1574
  };
1512
1575
  }
1513
- const getElementAtPoint = (doc, x, y) => {
1514
- let el = null;
1515
- if ('caretPositionFromPoint' in doc) {
1516
- el = doc.caretPositionFromPoint(x, y)?.offsetNode ?? null;
1517
- }
1518
- el = el ?? doc.elementFromPoint(x, y);
1519
- let element = el;
1520
- while (element && element.nodeType === Node.TEXT_NODE) {
1521
- element = element.parentElement;
1522
- }
1523
- return element;
1524
- };
1525
- function getElementHash(element) {
1526
- return (element.getAttribute('data-clarity-hash') ||
1527
- element.getAttribute('data-clarity-hashalpha') ||
1528
- element.getAttribute('data-clarity-hashbeta'));
1529
- }
1530
1576
  const getElementRank = (hash, elements) => {
1531
1577
  if (!elements)
1532
1578
  return 0;
@@ -1729,6 +1775,191 @@ const calcCalloutPosition = (options) => {
1729
1775
  };
1730
1776
  };
1731
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
+
1732
1963
  class IframeNavigationBlockerV2 {
1733
1964
  doc;
1734
1965
  win;
@@ -1736,17 +1967,18 @@ class IframeNavigationBlockerV2 {
1736
1967
  showMessage = false;
1737
1968
  originalWindowOpen;
1738
1969
  observers = [];
1739
- constructor(iframe) {
1970
+ constructor(iframe, config) {
1740
1971
  if (!iframe.contentDocument || !iframe.contentWindow) {
1741
1972
  throw new Error('Iframe document or window not accessible');
1742
1973
  }
1743
1974
  this.doc = iframe.contentDocument;
1744
1975
  this.win = iframe.contentWindow;
1745
1976
  this.originalWindowOpen = this.win.open.bind(this.win);
1977
+ logger.configure({ enabled: !!config?.debug, prefix: '[IframeNavigationBlockerV2]' });
1746
1978
  this.init();
1747
1979
  }
1748
1980
  init() {
1749
- console.log('[NavigationBlocker] Initializing...');
1981
+ logger.log('Initializing...');
1750
1982
  try {
1751
1983
  // Chặn navigation qua links
1752
1984
  this.blockLinkNavigation();
@@ -1762,7 +1994,7 @@ class IframeNavigationBlockerV2 {
1762
1994
  this.injectCSP();
1763
1995
  }
1764
1996
  catch (error) {
1765
- console.error('[NavigationBlocker] Init error:', error);
1997
+ logger.error('Init error:', error);
1766
1998
  }
1767
1999
  }
1768
2000
  blockLinkNavigation() {
@@ -1776,11 +2008,11 @@ class IframeNavigationBlockerV2 {
1776
2008
  const href = link.getAttribute('href');
1777
2009
  // Cho phép hash links và empty links
1778
2010
  if (!href || href === '' || href === '#' || href.startsWith('#')) {
1779
- console.log('[NavigationBlocker] Allowed hash navigation:', href);
2011
+ logger.log('Allowed hash navigation:', href);
1780
2012
  return;
1781
2013
  }
1782
2014
  // Chặn tất cả các loại navigation
1783
- console.log('[NavigationBlocker] Blocked link navigation to:', href);
2015
+ logger.log('Blocked link navigation to:', href);
1784
2016
  e.preventDefault();
1785
2017
  e.stopPropagation();
1786
2018
  e.stopImmediatePropagation();
@@ -1796,7 +2028,7 @@ class IframeNavigationBlockerV2 {
1796
2028
  if (link) {
1797
2029
  const href = link.getAttribute('href');
1798
2030
  if (href && !href.startsWith('#')) {
1799
- console.log('[NavigationBlocker] Blocked auxclick navigation');
2031
+ logger.log('Blocked auxclick navigation');
1800
2032
  e.preventDefault();
1801
2033
  e.stopPropagation();
1802
2034
  e.stopImmediatePropagation();
@@ -1829,13 +2061,13 @@ class IframeNavigationBlockerV2 {
1829
2061
  const action = form.getAttribute('action');
1830
2062
  // Cho phép forms không có action
1831
2063
  if (!action || action === '' || action === '#') {
1832
- console.log('[NavigationBlocker] Allowed same-page form');
2064
+ logger.log('Allowed same-page form');
1833
2065
  e.preventDefault();
1834
2066
  this.handleFormSubmit(form);
1835
2067
  return;
1836
2068
  }
1837
2069
  // Chặn tất cả external submissions
1838
- console.log('[NavigationBlocker] Blocked form submission to:', action);
2070
+ logger.log('Blocked form submission to:', action);
1839
2071
  e.preventDefault();
1840
2072
  e.stopPropagation();
1841
2073
  e.stopImmediatePropagation();
@@ -1849,7 +2081,7 @@ class IframeNavigationBlockerV2 {
1849
2081
  return this.originalWindowOpen(...args);
1850
2082
  }
1851
2083
  const url = args[0]?.toString() || 'popup';
1852
- console.log('[NavigationBlocker] Blocked window.open:', url);
2084
+ logger.log('Blocked window.open:', url);
1853
2085
  this.notifyBlockedNavigation(url);
1854
2086
  return null;
1855
2087
  });
@@ -1859,7 +2091,7 @@ class IframeNavigationBlockerV2 {
1859
2091
  this.win.addEventListener('beforeunload', (e) => {
1860
2092
  if (!this.isEnabled)
1861
2093
  return;
1862
- console.log('[NavigationBlocker] Blocked beforeunload');
2094
+ logger.log('Blocked beforeunload');
1863
2095
  e.preventDefault();
1864
2096
  e.returnValue = '';
1865
2097
  return '';
@@ -1868,7 +2100,7 @@ class IframeNavigationBlockerV2 {
1868
2100
  this.win.addEventListener('unload', (e) => {
1869
2101
  if (!this.isEnabled)
1870
2102
  return;
1871
- console.log('[NavigationBlocker] Blocked unload');
2103
+ logger.log('Blocked unload');
1872
2104
  e.preventDefault();
1873
2105
  e.stopPropagation();
1874
2106
  }, true);
@@ -1876,7 +2108,7 @@ class IframeNavigationBlockerV2 {
1876
2108
  this.win.addEventListener('popstate', (e) => {
1877
2109
  if (!this.isEnabled)
1878
2110
  return;
1879
- console.log('[NavigationBlocker] Blocked popstate');
2111
+ logger.log('Blocked popstate');
1880
2112
  e.preventDefault();
1881
2113
  e.stopPropagation();
1882
2114
  }, true);
@@ -1929,11 +2161,11 @@ class IframeNavigationBlockerV2 {
1929
2161
  meta.httpEquiv = 'Content-Security-Policy';
1930
2162
  meta.content = "navigate-to 'none'"; // Chặn tất cả navigation
1931
2163
  this.doc.head.appendChild(meta);
1932
- console.log('[NavigationBlocker] Injected CSP');
2164
+ logger.log('Injected CSP');
1933
2165
  }
1934
2166
  }
1935
2167
  catch (error) {
1936
- console.warn('[NavigationBlocker] Could not inject CSP:', error);
2168
+ logger.warn('Could not inject CSP:', error);
1937
2169
  }
1938
2170
  }
1939
2171
  handleFormSubmit(form) {
@@ -1942,13 +2174,13 @@ class IframeNavigationBlockerV2 {
1942
2174
  formData.forEach((value, key) => {
1943
2175
  data[key] = value;
1944
2176
  });
1945
- console.log('[NavigationBlocker] Handling form data:', data);
2177
+ logger.log('Handling form data:', data);
1946
2178
  window.dispatchEvent(new CustomEvent('iframe-form-submit', {
1947
2179
  detail: { form, data },
1948
2180
  }));
1949
2181
  }
1950
2182
  notifyBlockedNavigation(url) {
1951
- console.warn('[NavigationBlocker] Navigation blocked to:', url);
2183
+ logger.warn('Navigation blocked to:', url);
1952
2184
  window.dispatchEvent(new CustomEvent('iframe-navigation-blocked', {
1953
2185
  detail: { url, timestamp: Date.now() },
1954
2186
  }));
@@ -1998,19 +2230,19 @@ class IframeNavigationBlockerV2 {
1998
2230
  }
1999
2231
  enable() {
2000
2232
  this.isEnabled = true;
2001
- console.log('[NavigationBlocker] Enabled');
2233
+ logger.log('Enabled');
2002
2234
  }
2003
2235
  enableMessage() {
2004
2236
  this.showMessage = true;
2005
- console.log('[NavigationBlocker] Enabled message');
2237
+ logger.log('Enabled message');
2006
2238
  }
2007
2239
  disable() {
2008
2240
  this.isEnabled = false;
2009
- console.log('[NavigationBlocker] Disabled');
2241
+ logger.log('Disabled');
2010
2242
  }
2011
2243
  disableMessage() {
2012
2244
  this.showMessage = false;
2013
- console.log('[NavigationBlocker] Disabled message');
2245
+ logger.log('Disabled message');
2014
2246
  }
2015
2247
  destroy() {
2016
2248
  this.isEnabled = false;
@@ -2018,7 +2250,7 @@ class IframeNavigationBlockerV2 {
2018
2250
  // Cleanup observers
2019
2251
  this.observers.forEach((observer) => observer.disconnect());
2020
2252
  this.observers = [];
2021
- console.log('[NavigationBlocker] Destroyed');
2253
+ logger.log('Destroyed');
2022
2254
  }
2023
2255
  }
2024
2256
 
@@ -2034,6 +2266,7 @@ class IframeStyleReplacer {
2034
2266
  this.doc = iframe.contentDocument;
2035
2267
  this.win = iframe.contentWindow;
2036
2268
  this.config = config;
2269
+ logger.configure({ enabled: !!config?.debug, prefix: '[IframeStyleReplacer]' });
2037
2270
  }
2038
2271
  px(value) {
2039
2272
  return `${value.toFixed(2)}px`;
@@ -2067,7 +2300,7 @@ class IframeStyleReplacer {
2067
2300
  count++;
2068
2301
  }
2069
2302
  });
2070
- console.log(`[IframeStyleReplacer] Replaced ${count} inline style elements`);
2303
+ logger.log(`Replaced ${count} inline style elements`);
2071
2304
  return count;
2072
2305
  }
2073
2306
  processStyleTags() {
@@ -2080,7 +2313,7 @@ class IframeStyleReplacer {
2080
2313
  count++;
2081
2314
  }
2082
2315
  });
2083
- console.log(`[IframeStyleReplacer] Replaced ${count} <style> tags`);
2316
+ logger.log(`Replaced ${count} <style> tags`);
2084
2317
  return count;
2085
2318
  }
2086
2319
  processRule(rule) {
@@ -2111,7 +2344,7 @@ class IframeStyleReplacer {
2111
2344
  try {
2112
2345
  // Bỏ qua external CSS (cross-origin)
2113
2346
  if (sheet.href && !sheet.href.startsWith(this.win.location.origin)) {
2114
- console.log('[IframeStyleReplacer] Skipping external CSS:', sheet.href);
2347
+ logger.log('Skipping external CSS:', sheet.href);
2115
2348
  return;
2116
2349
  }
2117
2350
  const rules = sheet.cssRules || sheet.rules;
@@ -2122,10 +2355,10 @@ class IframeStyleReplacer {
2122
2355
  }
2123
2356
  }
2124
2357
  catch (e) {
2125
- console.warn('[IframeStyleReplacer] Cannot read stylesheet (CORS?):', e.message);
2358
+ logger.warn('Cannot read stylesheet (CORS?):', e.message);
2126
2359
  }
2127
2360
  });
2128
- console.log(`[IframeStyleReplacer] Replaced ${total} rules in stylesheets`);
2361
+ logger.log(`Replaced ${total} rules in stylesheets`);
2129
2362
  return total;
2130
2363
  }
2131
2364
  async processLinkedStylesheets() {
@@ -2133,7 +2366,7 @@ class IframeStyleReplacer {
2133
2366
  let count = 0;
2134
2367
  for (const link of Array.from(links)) {
2135
2368
  if (!link.href.startsWith(this.win.location.origin)) {
2136
- console.log('[IframeStyleReplacer] Skipping external CSS:', link.href);
2369
+ logger.log('Skipping external CSS:', link.href);
2137
2370
  continue;
2138
2371
  }
2139
2372
  try {
@@ -2151,10 +2384,10 @@ class IframeStyleReplacer {
2151
2384
  }
2152
2385
  }
2153
2386
  catch (e) {
2154
- console.warn('[IframeStyleReplacer] Cannot load CSS:', link.href, e);
2387
+ logger.warn('Cannot load CSS:', link.href, e);
2155
2388
  }
2156
2389
  }
2157
- console.log(`[IframeStyleReplacer] Replaced ${count} linked CSS files`);
2390
+ logger.log(`Replaced ${count} linked CSS files`);
2158
2391
  return count;
2159
2392
  }
2160
2393
  getFinalHeight() {
@@ -2178,7 +2411,7 @@ class IframeStyleReplacer {
2178
2411
  }
2179
2412
  async run() {
2180
2413
  try {
2181
- console.log('[IframeStyleReplacer] Starting viewport units replacement...');
2414
+ logger.log('Starting viewport units replacement...');
2182
2415
  this.processInlineStyles();
2183
2416
  this.processStyleTags();
2184
2417
  this.processStylesheets();
@@ -2188,13 +2421,13 @@ class IframeStyleReplacer {
2188
2421
  requestAnimationFrame(() => {
2189
2422
  const height = this.getFinalHeight();
2190
2423
  const width = this.getFinalWidth();
2191
- console.log('[IframeStyleReplacer] Calculated dimensions:', { height, width });
2424
+ logger.log('Calculated dimensions:', { height, width });
2192
2425
  resolve({ height, width });
2193
2426
  });
2194
2427
  });
2195
2428
  }
2196
2429
  catch (err) {
2197
- console.error('[IframeStyleReplacer] Critical error:', err);
2430
+ logger.error('Critical error:', err);
2198
2431
  return {
2199
2432
  height: this.doc.body.scrollHeight || 1000,
2200
2433
  width: this.doc.body.scrollWidth || 1000,
@@ -2215,10 +2448,11 @@ class IframeHelperFixer {
2215
2448
  this.config = config;
2216
2449
  this.iframe = config.iframe;
2217
2450
  this.init();
2451
+ logger.configure({ enabled: !!config?.debug, prefix: '[IframeHelper]' });
2218
2452
  }
2219
2453
  async init() {
2220
2454
  if (!this.iframe) {
2221
- console.error('[IframeHelper] iframe not found');
2455
+ logger.error('iframe not found');
2222
2456
  this.config.onError?.(new Error('iframe not found'));
2223
2457
  return;
2224
2458
  }
@@ -2232,19 +2466,19 @@ class IframeHelperFixer {
2232
2466
  }
2233
2467
  async process() {
2234
2468
  if (!this.iframe.contentDocument || !this.iframe.contentWindow) {
2235
- console.error('[IframeHelper] Cannot access iframe document');
2469
+ logger.error('Cannot access iframe document');
2236
2470
  this.config.onError?.(new Error('Cannot access iframe document'));
2237
2471
  return;
2238
2472
  }
2239
2473
  try {
2240
- console.log('[IframeHelper] Processing viewport units...');
2474
+ logger.log('Processing viewport units...');
2241
2475
  // Create replacer instance
2242
2476
  this.replacer = new IframeStyleReplacer(this.iframe, this.config);
2243
2477
  // Create navigation blocker
2244
2478
  this.navigationBlocker = new IframeNavigationBlockerV2(this.iframe);
2245
2479
  // Run replacement
2246
2480
  const result = await this.replacer.run();
2247
- console.log('[IframeHelper] Process completed:', result);
2481
+ logger.log('Process completed:', result);
2248
2482
  // Trigger success callback
2249
2483
  this.config.onSuccess?.(result);
2250
2484
  // Dispatch custom event
@@ -2253,12 +2487,12 @@ class IframeHelperFixer {
2253
2487
  }));
2254
2488
  }
2255
2489
  catch (error) {
2256
- console.error('[IframeHelper] Failed to process:', error);
2490
+ logger.error('Failed to process:', error);
2257
2491
  this.config.onError?.(error);
2258
2492
  }
2259
2493
  }
2260
2494
  async recalculate() {
2261
- console.log('[IframeHelper] Recalculating...');
2495
+ logger.log('Recalculating...');
2262
2496
  await this.process();
2263
2497
  }
2264
2498
  updateConfig(config) {
@@ -2283,28 +2517,62 @@ class IframeHelperFixer {
2283
2517
  this.replacer = null;
2284
2518
  this.navigationBlocker?.destroy();
2285
2519
  this.navigationBlocker = null;
2286
- console.log('[IframeHelper] Destroyed');
2520
+ logger.log('Destroyed');
2287
2521
  }
2288
2522
  }
2289
2523
 
2290
2524
  function initIframeHelperFixer(config) {
2291
2525
  const fixer = new IframeHelperFixer(config);
2292
2526
  window.addEventListener('iframe-dimensions-applied', ((e) => {
2293
- const ev = e;
2294
- console.log('[IframeHelper] Iframe dimensions finalized:', ev.detail);
2527
+ // console.log('[IframeHelper] Iframe dimensions finalized:', ev.detail);
2295
2528
  }));
2296
2529
  window.addEventListener('iframe-navigation-blocked', ((e) => {
2297
- const ev = e;
2298
- console.warn('[IframeHelper] Iframe tried to navigate to:', ev.detail.url);
2530
+ // console.warn('[IframeHelper] Iframe tried to navigate to:', ev.detail.url);
2299
2531
  }));
2300
2532
  window.addEventListener('iframe-form-submit', ((e) => {
2301
- const ev = e;
2302
- console.log('[IframeHelper] Iframe form submitted:', ev.detail.data);
2533
+ // console.log('[IframeHelper] Iframe form submitted:', ev.detail.data);
2303
2534
  }));
2304
2535
  return fixer;
2305
2536
  }
2306
2537
 
2307
- function useAreaEditMode({ iframeRef, onCreateArea, enabled = false, }) {
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, }) {
2308
2576
  const [hoveredElement, setHoveredElement] = useState(null);
2309
2577
  const [isHovering, setIsHovering] = useState(false);
2310
2578
  const { isEditingMode } = useHeatmapAreaClick();
@@ -2313,31 +2581,30 @@ function useAreaEditMode({ iframeRef, onCreateArea, enabled = false, }) {
2313
2581
  const handleMouseMove = useCallback((e) => {
2314
2582
  if (!isActive || !iframeDocument)
2315
2583
  return;
2316
- const elements = getElementsAtPoint$1(iframeDocument, e.clientX, e.clientY, isElementSelectable);
2317
- // Find first selectable element
2584
+ const elements = getElementsAtPoint(iframeDocument, e.clientX, e.clientY);
2318
2585
  const selectableElement = elements.find((el, index, arr) => isElementSelectable(el, index, arr));
2319
- if (selectableElement && selectableElement !== hoveredElement) {
2586
+ const isSelectable = selectableElement && selectableElement !== hoveredElement;
2587
+ if (isSelectable) {
2320
2588
  setHoveredElement(selectableElement);
2321
2589
  setIsHovering(true);
2590
+ return;
2322
2591
  }
2323
- else if (!selectableElement && hoveredElement) {
2324
- setHoveredElement(null);
2325
- setIsHovering(false);
2326
- }
2592
+ setHoveredElement(null);
2593
+ setIsHovering(false);
2327
2594
  }, [isActive, iframeDocument, hoveredElement]);
2328
- const handleClick = useCallback((e) => {
2595
+ const handleMouseLeave = useCallback(() => {
2596
+ setHoveredElement(null);
2597
+ setIsHovering(false);
2598
+ }, []);
2599
+ useCallback((e) => {
2329
2600
  if (!isActive || !hoveredElement)
2330
2601
  return;
2331
2602
  e.stopPropagation();
2332
2603
  e.preventDefault();
2333
- if (onCreateArea) {
2334
- onCreateArea(hoveredElement);
2335
- }
2604
+ if (!onAreaCreatedElement)
2605
+ return;
2606
+ onAreaCreatedElement(hoveredElement);
2336
2607
  }, [isActive, hoveredElement]);
2337
- const handleMouseLeave = useCallback(() => {
2338
- setHoveredElement(null);
2339
- setIsHovering(false);
2340
- }, []);
2341
2608
  useEffect(() => {
2342
2609
  if (!isActive || !iframeDocument) {
2343
2610
  setHoveredElement(null);
@@ -2355,17 +2622,17 @@ function useAreaEditMode({ iframeRef, onCreateArea, enabled = false, }) {
2355
2622
  });
2356
2623
  };
2357
2624
  iframeDocument.addEventListener('mousemove', throttledMouseMove);
2358
- iframeDocument.addEventListener('click', handleClick);
2359
- iframeDocument.addEventListener('mouseleave', handleMouseLeave);
2360
2625
  iframeDocument.addEventListener('scroll', handleMouseLeave);
2626
+ iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
2627
+ // iframeDocument.addEventListener('click', handleClick);
2361
2628
  return () => {
2362
2629
  if (rafId) {
2363
2630
  cancelAnimationFrame(rafId);
2364
2631
  }
2365
2632
  iframeDocument.removeEventListener('mousemove', throttledMouseMove);
2366
- iframeDocument.removeEventListener('click', handleClick);
2367
2633
  iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
2368
2634
  iframeDocument.removeEventListener('scroll', handleMouseLeave);
2635
+ // iframeDocument.removeEventListener('click', handleClick);
2369
2636
  };
2370
2637
  }, [isActive, iframeDocument]);
2371
2638
  return {
@@ -2374,103 +2641,166 @@ function useAreaEditMode({ iframeRef, onCreateArea, enabled = false, }) {
2374
2641
  };
2375
2642
  }
2376
2643
 
2377
- function useAreaScrollSync(options) {
2378
- const { areas, iframeRef, enabled = true } = options;
2644
+ const useAreaFilterVisible = (props) => {
2645
+ const { iframeRef, enableOverlapResolution } = props;
2379
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]);
2380
2655
  useEffect(() => {
2381
- if (!enabled || !iframeDocument || areas.length === 0) {
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)
2382
2674
  return;
2675
+ // Toggle selection
2676
+ setSelectedArea(selectedArea?.id === area.id ? null : area);
2677
+ // Trigger callback
2678
+ if (onAreaClick) {
2679
+ onAreaClick(area);
2383
2680
  }
2384
- let rafId = null;
2385
- let isUpdating = false;
2386
- const updateAreaPositions = () => {
2387
- if (isUpdating)
2388
- return;
2389
- isUpdating = true;
2390
- rafId = requestAnimationFrame(() => {
2391
- areas.forEach((area) => {
2392
- if (!area.element || !area.rect)
2393
- return;
2394
- try {
2395
- const newRect = getElementRect(area.element);
2396
- area.rect.update(newRect);
2397
- }
2398
- catch (error) {
2399
- console.warn('[useAreaScrollSync] Failed to update area rect:', error);
2400
- }
2401
- });
2402
- isUpdating = false;
2403
- rafId = null;
2404
- });
2405
- };
2406
- iframeDocument.addEventListener('scroll', updateAreaPositions, { passive: true });
2407
- const iframeWindow = iframeDocument.defaultView;
2408
- if (iframeWindow) {
2409
- iframeWindow.addEventListener('resize', updateAreaPositions, { passive: true });
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);
2410
2693
  }
2411
- return () => {
2412
- if (rafId !== null) {
2413
- cancelAnimationFrame(rafId);
2414
- }
2415
- iframeDocument.removeEventListener('scroll', updateAreaPositions);
2416
- if (iframeWindow) {
2417
- iframeWindow.removeEventListener('resize', updateAreaPositions);
2418
- }
2419
- };
2420
- }, [areas, iframeDocument, enabled]);
2694
+ }, [isEditingMode, hoveredArea]);
2695
+ return {
2696
+ handleAreaClick,
2697
+ handleAreaMouseEnter,
2698
+ handleAreaMouseLeave,
2699
+ };
2421
2700
  }
2422
2701
 
2423
- const useClickmap = () => {
2424
- const { vizRef } = useHeatmapViz();
2425
- const { clickmap } = useHeatmapData();
2426
- const start = useCallback(() => {
2427
- if (!vizRef || !clickmap || clickmap.length === 0)
2428
- return;
2429
- try {
2430
- vizRef?.clearmap?.();
2431
- vizRef?.clickmap?.(clickmap);
2432
- }
2433
- catch (error) {
2434
- console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
2435
- }
2436
- }, [vizRef, clickmap]);
2437
- return { start };
2438
- };
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
+ }
2439
2723
 
2440
- const useScrollmap = () => {
2724
+ function useAreaRectSync(options) {
2725
+ const { iframeDocument, shadowRoot, enabled = true } = options;
2441
2726
  const { vizRef } = useHeatmapViz();
2442
- const { scrollmap } = useHeatmapData();
2443
- const start = useCallback(() => {
2444
- // if (isInitialized) return;
2445
- if (!vizRef || !scrollmap || scrollmap.length === 0)
2727
+ const { areas } = useHeatmapAreaClick();
2728
+ useEffect(() => {
2729
+ if (!enabled || !iframeDocument || areas.length === 0) {
2446
2730
  return;
2447
- try {
2448
- vizRef?.clearmap?.();
2449
- vizRef?.scrollmap?.(scrollmap);
2450
- // setIsInitialized(true);
2451
- }
2452
- catch (error) {
2453
- console.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
2454
2731
  }
2455
- }, [vizRef, scrollmap]);
2456
- return { start };
2457
- };
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
+ }
2458
2768
 
2459
- const useHeatmapCanvas = () => {
2460
- const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
2461
- const { start: startClickmap } = useClickmap();
2462
- const { start: startScrollmap } = useScrollmap();
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);
2463
2780
  useEffect(() => {
2464
- switch (heatmapType) {
2465
- case IHeatmapType.Click:
2466
- startClickmap();
2467
- break;
2468
- case IHeatmapType.Scroll:
2469
- startScrollmap();
2470
- break;
2781
+ if (!iframeDocument) {
2782
+ setIsReady(false);
2783
+ return;
2471
2784
  }
2472
- }, [heatmapType, startClickmap, startScrollmap]);
2473
- };
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
+ }
2474
2804
 
2475
2805
  const scrollToElementIfNeeded = (visualRef, rect, scale) => {
2476
2806
  if (!visualRef.current)
@@ -2528,7 +2858,7 @@ const useClickedElement = ({ visualRef, getRect }) => {
2528
2858
  requestAnimationFrame(() => {
2529
2859
  setClickedElement(elementInfo);
2530
2860
  });
2531
- }, [selectedElement, dataInfo, visualRef, widthScale]);
2861
+ }, [selectedElement, dataInfo, visualRef, widthScale]); // eslint-disable-line react-hooks/exhaustive-deps
2532
2862
  return { clickedElement, showMissingElement, shouldShowCallout };
2533
2863
  };
2534
2864
 
@@ -2584,7 +2914,7 @@ const useHeatmapEffects = ({ isVisible }) => {
2584
2914
  const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
2585
2915
  const heatmapWidth = useHeatmapConfigStore((state) => state.width);
2586
2916
  const { iframeHeight, widthScale } = useHeatmapViz();
2587
- return useCallback((element) => {
2917
+ const getRect = useCallback((element) => {
2588
2918
  const hash = element?.hash;
2589
2919
  if (!iframeRef.current?.contentDocument || !hash || !visualizer)
2590
2920
  return null;
@@ -2620,6 +2950,7 @@ const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
2620
2950
  outOfBounds,
2621
2951
  };
2622
2952
  }, [iframeRef, wrapperRef, visualizer, heatmapWidth, iframeHeight, widthScale]);
2953
+ return { getRect };
2623
2954
  };
2624
2955
 
2625
2956
  const debounce = (fn, delay) => {
@@ -2630,10 +2961,6 @@ const debounce = (fn, delay) => {
2630
2961
  };
2631
2962
  };
2632
2963
 
2633
- // ===================== UTILITY FUNCTIONS =====================
2634
- /**
2635
- * Lấy bounding box tuyệt đối của element (relative to document)
2636
- */
2637
2964
  function getBoundingBox(element) {
2638
2965
  if (typeof element.getBoundingClientRect !== 'function') {
2639
2966
  return null;
@@ -2654,84 +2981,72 @@ function getBoundingBox(element) {
2654
2981
  height: Math.floor(rect.height),
2655
2982
  };
2656
2983
  }
2657
- /**
2658
- * Lấy tất cả elements tại tọa độ (x, y), hỗ trợ Shadow DOM
2659
- */
2660
- function getElementsAtPoint(documentOrShadowRoot, x, y, filterFunction, visitedShadowRoots = new Set()) {
2661
- // Lấy tất cả elements tại vị trí
2662
- const elementsAtPoint = documentOrShadowRoot.elementsFromPoint(x, y);
2663
- if (!filterFunction) {
2664
- return elementsAtPoint;
2665
- }
2666
- // Tìm element đầu tiên match với filter
2667
- const matchedElement = elementsAtPoint.find(filterFunction);
2668
- // Nếu element có Shadow DOM và chưa visit -> đệ quy vào
2669
- if (matchedElement?.shadowRoot && !visitedShadowRoots.has(matchedElement.shadowRoot)) {
2670
- visitedShadowRoots.add(matchedElement.shadowRoot);
2671
- return getElementsAtPoint(matchedElement.shadowRoot, x, y, filterFunction, visitedShadowRoots);
2672
- }
2673
- return elementsAtPoint;
2674
- }
2675
- // ===================== EXAMPLE USAGE =====================
2676
- /*
2677
- import { useRef, useState } from 'react';
2678
-
2679
- function HeatmapComponent() {
2680
- const heatmapWrapperRef = useRef<HTMLDivElement>(null);
2681
- const iframeRef = useRef<HTMLIFrameElement>(null);
2682
- const parentRef = useRef<HTMLDivElement>(null);
2683
-
2684
- const [hoveredElement, setHoveredElement] = useState<HoveredElementInfo | null>(null);
2685
-
2686
- const heatmapInfo = {
2687
- width: 1920,
2688
- elementMapInfo: {
2689
- 'hash123': {
2690
- totalclicks: 45,
2691
- selector: 'button.submit'
2692
- }
2693
- },
2694
- sortedElements: [...]
2695
- };
2696
-
2697
- const { handleMouseMove } = useHeatmapMouseHandler({
2698
- heatmapWrapperRef,
2699
- iframeRef,
2700
- parentRef,
2701
- heatmapInfo,
2702
- scaleRatio: 0.8, // 80% zoom
2703
- onElementHover: (info) => {
2704
- setHoveredElement(info);
2705
- console.log('Hovered element:', info);
2706
- }
2707
- });
2708
-
2709
- return (
2710
- <div ref={parentRef}>
2711
- <div
2712
- ref={heatmapWrapperRef}
2713
- onMouseMove={handleMouseMove}
2714
- >
2715
- <iframe ref={iframeRef} />
2716
-
2717
- {hoveredElement && (
2718
- <div className="tooltip" style={{
2719
- position: 'absolute',
2720
- left: hoveredElement.left,
2721
- top: hoveredElement.top
2722
- }}>
2723
- Clicks: {hoveredElement.clicks}
2724
- <br />
2725
- Rank: #{hoveredElement.rank}
2726
- <br />
2727
- Selector: {hoveredElement.selector}
2728
- </div>
2729
- )}
2730
- </div>
2731
- </div>
2732
- );
2733
- }
2734
- */
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
+ // }
2735
3050
 
2736
3051
  const useHoveredElement = ({ iframeRef, getRect }) => {
2737
3052
  const { hoveredElement, setHoveredElement, setSelectedElement } = useHeatmapClick();
@@ -2814,12 +3129,14 @@ const convertViewportToIframeCoords = (clientX, clientY, iframeRect, scale) => {
2814
3129
  return { x, y };
2815
3130
  };
2816
3131
  const findTargetElement = (doc, x, y, heatmapInfo) => {
2817
- const HEATMAP_ELEMENT_ATTRIBUTE = 'data-clarity-hashalpha';
2818
- const elementsAtPoint = getElementsAtPoint(doc, Math.round(x), Math.round(y), (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE));
3132
+ const elementsAtPoint = getElementsAtPoint(doc, Math.round(x), Math.round(y), {
3133
+ filterFn: (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE),
3134
+ ignoreCanvas: true,
3135
+ });
2819
3136
  let dataElement = null;
2820
3137
  for (let i = 0; i < elementsAtPoint.length; i++) {
2821
3138
  const element = elementsAtPoint[i];
2822
- const elementHash = element.getAttribute(HEATMAP_ELEMENT_ATTRIBUTE);
3139
+ const elementHash = getElementHash(element);
2823
3140
  if (elementHash && heatmapInfo.elementMapInfo?.[elementHash]) {
2824
3141
  const boundingBox = getBoundingBox(element);
2825
3142
  if (boundingBox) {
@@ -2849,6 +3166,167 @@ const isValidElement = (element, heatmapInfo) => {
2849
3166
  return !!heatmapInfo?.elementMapInfo?.[hash];
2850
3167
  };
2851
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
+
2852
3330
  var MessageType;
2853
3331
  (function (MessageType) {
2854
3332
  MessageType["GX_DOM_TRACKING_PAYLOAD"] = "GX_DOM_TRACKING_PAYLOAD";
@@ -3137,7 +3615,7 @@ const useReplayRender = () => {
3137
3615
  };
3138
3616
  };
3139
3617
 
3140
- const useHeatmapVizRender = (mode) => {
3618
+ const useHeatmapRenderByMode = (mode) => {
3141
3619
  const heatmapResult = useMemo(() => {
3142
3620
  switch (mode) {
3143
3621
  case 'heatmap':
@@ -4228,373 +4706,69 @@ const PopoverSidebar = () => {
4228
4706
  overflow: 'auto',
4229
4707
  }, children: jsx(CompSidebar, { closeAction: { onClick: () => setIsPopoverOpen(false) } }) }) }))] }));
4230
4708
  };
4231
-
4232
- const ContentToolbar = () => {
4233
- const controls = useHeatmapControlStore((state) => state.controls);
4234
- return (jsx("div", { id: "gx-hm-content-toolbar", style: {
4235
- position: 'absolute',
4236
- bottom: 0,
4237
- left: '8px',
4238
- right: '24px',
4239
- padding: '8px',
4240
- paddingBlock: '16px',
4241
- }, children: controls.Toolbar ?? null }));
4242
- };
4243
-
4244
- const VizContainer = ({ children, isActive = false }) => {
4245
- const wrapperRef = useRef(null);
4246
- const viewId = useViewIdContext();
4247
- useWrapperRefHeight({
4248
- isActive,
4249
- wrapperRef,
4250
- });
4251
- return (jsxs(BoxStack, { ref: wrapperRef, id: `gx-hm-viz-container-${viewId}`, flexDirection: "column", flex: "1 1 auto", overflow: "auto", zIndex: 1, children: [jsx(BoxStack, { id: "gx-hm-content", flexDirection: "column", flex: "1 1 auto", overflow: "hidden", style: {
4252
- minWidth: '394px',
4253
- }, children: children }), jsx(PopoverSidebar, {})] }));
4254
- };
4255
-
4256
- /**
4257
- * Controls for area click feature - toggle edit mode, clear areas, etc.
4258
- */
4259
- const AreaControls = ({ className, style }) => {
4260
- const { isEditingMode, setIsEditingMode, areas, clearAreas } = useHeatmapAreaClick();
4261
- return (jsxs("div", { className: className, style: {
4262
- display: 'flex',
4263
- gap: '8px',
4264
- alignItems: 'center',
4265
- ...style,
4266
- }, children: [jsx("button", { onClick: () => setIsEditingMode(!isEditingMode), style: {
4267
- padding: '8px 16px',
4268
- borderRadius: '4px',
4269
- border: '1px solid #ccc',
4270
- backgroundColor: isEditingMode ? '#0078D4' : 'white',
4271
- color: isEditingMode ? 'white' : '#161514',
4272
- cursor: 'pointer',
4273
- fontWeight: 500,
4274
- transition: 'all 0.2s',
4275
- }, children: isEditingMode ? 'Exit Edit Mode' : 'Edit Areas' }), areas.length > 0 && (jsxs(Fragment, { children: [jsxs("span", { style: { color: '#605E5C', fontSize: '14px' }, children: [areas.length, " area", areas.length !== 1 ? 's' : ''] }), jsx("button", { onClick: () => {
4276
- if (confirm(`Clear all ${areas.length} areas?`)) {
4277
- clearAreas();
4278
- }
4279
- }, style: {
4280
- padding: '8px 16px',
4281
- borderRadius: '4px',
4282
- border: '1px solid #ccc',
4283
- backgroundColor: 'white',
4284
- color: '#A4262C',
4285
- cursor: 'pointer',
4286
- fontWeight: 500,
4287
- transition: 'all 0.2s',
4288
- }, children: "Clear All" })] }))] }));
4289
- };
4290
- AreaControls.displayName = 'AreaControls';
4291
-
4292
- const AreaEditHighlight = ({ element, shadowRoot, onClick, }) => {
4293
- const [rect, setRect] = useState(null);
4294
- const highlightRef = useRef(null);
4295
- useEffect(() => {
4296
- if (!element) {
4297
- setRect(null);
4298
- return;
4299
- }
4300
- // Calculate element position
4301
- const elementRect = getElementRect(element);
4302
- setRect(elementRect);
4303
- }, [element, shadowRoot]);
4304
- if (!rect) {
4305
- return null;
4306
- }
4307
- const handleClick = (e) => {
4308
- if (element && onClick) {
4309
- e.stopPropagation();
4310
- e.preventDefault();
4311
- onClick(element);
4312
- }
4313
- };
4314
- return (jsx("div", { ref: highlightRef, id: AREA_HOVER_ELEMENT_ID, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: handleClick, style: {
4315
- position: 'absolute',
4316
- top: `${rect.absoluteTop}px`,
4317
- left: `${rect.absoluteLeft}px`,
4318
- width: `${rect.width}px`,
4319
- height: `${rect.height}px`,
4320
- zIndex: Number.MAX_SAFE_INTEGER,
4321
- boxShadow: AREA_HOVER_BOX_SHADOW,
4322
- backgroundColor: 'rgba(128, 128, 128, 0.4)',
4323
- backgroundImage: 'repeating-linear-gradient(135deg, transparent, transparent 35px, rgba(255,255,255,.5) 35px, rgba(255,255,255,.5) 70px)',
4324
- pointerEvents: 'auto',
4325
- cursor: 'pointer',
4326
- boxSizing: 'border-box',
4327
- } }));
4328
- };
4329
- AreaEditHighlight.displayName = 'AreaEditHighlight';
4330
-
4331
- const AreaLabel = ({ clickDist, totalClicks, kind }) => {
4332
- if (kind === 'money') {
4333
- return null;
4334
- }
4335
- return (jsxs("div", { style: {
4336
- color: '#161514',
4337
- backgroundColor: 'rgba(255, 255, 255, 0.86)',
4338
- display: 'flex',
4339
- flexDirection: 'column',
4340
- alignItems: 'center',
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',
4341
4717
  padding: '8px',
4342
- borderRadius: '4px',
4343
- fontSize: '16px',
4344
- lineHeight: '20px',
4345
- minWidth: '56px',
4346
- fontWeight: 600,
4347
- fontFamily: '"Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif',
4348
- pointerEvents: 'none',
4349
- }, children: [jsxs("span", { children: [clickDist.toFixed(2), "%"] }), jsxs("span", { style: { fontSize: '12px', fontWeight: 400, opacity: 0.8 }, children: [totalClicks, " clicks"] })] }));
4718
+ paddingBlock: '16px',
4719
+ }, children: controls.Toolbar ?? null }));
4350
4720
  };
4351
- AreaLabel.displayName = 'AreaLabel';
4352
4721
 
4353
- const AreaOverlay = ({ area, onClick, onMouseEnter, onMouseLeave, isSelected, isHovered, }) => {
4354
- const [rect, setRect] = useState(area.rect.value);
4355
- useEffect(() => {
4356
- const handleRectChange = (newRect) => {
4357
- if (newRect) {
4358
- setRect(newRect);
4359
- }
4360
- };
4361
- area.rect.observe(handleRectChange);
4362
- return () => {
4363
- area.rect.unobserve(handleRectChange);
4364
- };
4365
- }, [area.rect]);
4366
- if (!rect)
4367
- return null;
4368
- const position = area.isFixed ? 'fixed' : 'absolute';
4369
- const showLabel = !isRectTooSmallForLabel(rect);
4370
- const backgroundColor = isHovered ? area.hoverColor : area.color;
4371
- const boxShadow = isSelected
4372
- ? '0 0 0 3px #0078d4 inset'
4373
- : isHovered
4374
- ? AREA_HOVER_BOX_SHADOW
4375
- : '0 0 0 2px white inset';
4376
- return (jsx("div", { id: `area-${area.id}`, "data-area-id": area.id, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: () => onClick?.(area), onMouseEnter: () => onMouseEnter?.(area), onMouseLeave: () => onMouseLeave?.(area), style: {
4377
- position,
4378
- top: `${rect.top}px`,
4379
- left: `${rect.left}px`,
4380
- width: `${rect.width}px`,
4381
- height: `${rect.height}px`,
4382
- backgroundColor,
4383
- boxShadow,
4384
- boxSizing: 'border-box',
4385
- display: 'flex',
4386
- alignItems: 'center',
4387
- justifyContent: 'center',
4388
- cursor: 'pointer',
4389
- transition: 'background-color 0.2s, box-shadow 0.2s',
4390
- pointerEvents: 'auto',
4391
- }, children: showLabel && (jsx(AreaLabel, { clickDist: area.clickDist, totalClicks: area.totalclicks, kind: area.kind })) }));
4722
+ const VizContainer = ({ children, isActive = false }) => {
4723
+ const wrapperRef = useRef(null);
4724
+ const viewId = useViewIdContext();
4725
+ useWrapperRefHeight({
4726
+ isActive,
4727
+ wrapperRef,
4728
+ });
4729
+ return (jsxs(BoxStack, { ref: wrapperRef, id: `gx-hm-viz-container-${viewId}`, flexDirection: "column", flex: "1 1 auto", overflow: "auto", zIndex: 1, children: [jsx(BoxStack, { id: "gx-hm-content", flexDirection: "column", flex: "1 1 auto", overflow: "hidden", style: {
4730
+ minWidth: '394px',
4731
+ }, children: children }), jsx(PopoverSidebar, {})] }));
4392
4732
  };
4393
4733
 
4394
4734
  function useAreaRenderer(options) {
4395
- const { iframeRef, shadowRoot: customShadowRoot, onAreaCreated, onAreaClick } = options;
4396
- const iframeDocument = iframeRef.current?.contentDocument;
4397
- // Get heatmap data for building areas
4398
- const { dataInfo } = useHeatmapData();
4399
- const { areas, selectedArea, hoveredArea, isEditingMode, setSelectedArea, setHoveredArea, addArea, } = useHeatmapAreaClick();
4400
- const [shadowContainer, setShadowContainer] = useState(null);
4401
- const [isReady, setIsReady] = useState(false);
4402
- const containerRef = useRef(null);
4403
- useEffect(() => {
4404
- if (!iframeDocument) {
4405
- setIsReady(false);
4406
- return;
4407
- }
4408
- let container = iframeDocument.querySelector(`[${AREA_MAP_DIV_ATTRIBUTE}]`);
4409
- if (!container) {
4410
- container = iframeDocument.createElement('div');
4411
- container.setAttribute(AREA_MAP_DIV_ATTRIBUTE, 'true');
4412
- container.style.cssText = `
4413
- position: absolute;
4414
- top: 0;
4415
- left: 0;
4416
- width: 100%;
4417
- height: 100%;
4418
- pointer-events: none;
4419
- z-index: 999999;
4420
- `;
4421
- // Append to custom shadow root or body
4422
- const targetRoot = customShadowRoot || iframeDocument.body;
4423
- if (targetRoot) {
4424
- targetRoot.appendChild(container);
4425
- }
4426
- }
4427
- // Create shadow root if needed
4428
- let shadowRoot;
4429
- if (!container.shadowRoot) {
4430
- shadowRoot = getOrCreateShadowRoot(container);
4431
- }
4432
- else {
4433
- shadowRoot = container.shadowRoot;
4434
- }
4435
- // Create inner container for React portal
4436
- let innerContainer = shadowRoot.querySelector('.heatmap-area-container');
4437
- if (!innerContainer) {
4438
- innerContainer = iframeDocument.createElement('div');
4439
- innerContainer.className = 'heatmap-area-container';
4440
- innerContainer.style.cssText = `
4441
- position: relative;
4442
- width: 100%;
4443
- height: 100%;
4444
- `;
4445
- shadowRoot.appendChild(innerContainer);
4446
- }
4447
- containerRef.current = innerContainer;
4448
- setShadowContainer(innerContainer);
4449
- setIsReady(true);
4450
- return () => {
4451
- if (container && container.parentNode) {
4452
- container.parentNode.removeChild(container);
4453
- }
4454
- containerRef.current = null;
4455
- setShadowContainer(null);
4456
- setIsReady(false);
4457
- };
4458
- }, [iframeDocument, customShadowRoot]);
4459
- const handleCreateAreaFromElement = useCallback((element) => {
4460
- if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks) {
4461
- console.warn('[useAreaRenderer] Cannot create area: missing heatmap data');
4462
- return;
4463
- }
4464
- const hash = getElementHash(element);
4465
- if (!hash) {
4466
- console.warn('[useAreaRenderer] Cannot create area: missing hash');
4467
- return;
4468
- }
4469
- const alreadyExists = areas.some((area) => area.hash === hash);
4470
- if (alreadyExists) {
4471
- console.warn(`[useAreaRenderer] Area already exists for element: ${hash}`);
4472
- return;
4473
- }
4474
- try {
4475
- const area = buildAreaNode(element, hash, dataInfo.elementMapInfo, dataInfo.totalClicks, customShadowRoot);
4476
- addArea(area);
4477
- if (onAreaCreated) {
4478
- onAreaCreated(area);
4479
- }
4480
- console.log('[useAreaRenderer] Area created:', {
4481
- hash,
4482
- selector: area.selector,
4483
- clicks: area.totalclicks,
4484
- clickDist: area.clickDist,
4485
- });
4486
- }
4487
- catch (error) {
4488
- console.error('[useAreaRenderer] Failed to create area:', error);
4489
- }
4490
- }, [dataInfo, areas, customShadowRoot, addArea]);
4491
- const { hoveredElement } = useAreaEditMode({
4492
- iframeRef,
4493
- enabled: isEditingMode,
4494
- onCreateArea: handleCreateAreaFromElement,
4495
- });
4496
- useAreaScrollSync({
4497
- areas,
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({
4498
4739
  iframeRef,
4499
- enabled: isReady,
4740
+ shadowContainer,
4741
+ isReady,
4742
+ onAreaClick,
4743
+ onAreaCreated,
4500
4744
  });
4501
- const handleAreaClick = (area) => {
4502
- if (isEditingMode)
4503
- return;
4504
- setSelectedArea(selectedArea?.id === area.id ? null : area);
4505
- if (onAreaClick) {
4506
- onAreaClick(area);
4507
- }
4508
- };
4509
- const handleAreaMouseEnter = (area) => {
4510
- if (isEditingMode)
4511
- return;
4512
- setHoveredArea(area);
4513
- };
4514
- const handleAreaMouseLeave = (area) => {
4515
- if (isEditingMode)
4516
- return;
4517
- if (hoveredArea?.id === area.id) {
4518
- setHoveredArea(null);
4519
- }
4520
- };
4521
- const areasPortal = shadowContainer && isReady
4522
- ? createPortal(jsx(Fragment, { children: areas.map((area) => (jsx(AreaOverlay, { area: area, onClick: handleAreaClick, onMouseEnter: handleAreaMouseEnter, onMouseLeave: handleAreaMouseLeave, isSelected: selectedArea?.id === area.id, isHovered: hoveredArea?.id === area.id }, area.id))) }), shadowContainer)
4523
- : null;
4524
- const editHighlightPortal = shadowContainer && isReady && isEditingMode && hoveredElement
4525
- ? createPortal(jsx(AreaEditHighlight, { element: hoveredElement, shadowRoot: customShadowRoot, onClick: handleCreateAreaFromElement }), shadowContainer)
4526
- : null;
4745
+ useAreaRectSync({ iframeDocument, shadowRoot, enabled: isReady });
4746
+ useAreaScrollSync({ iframeRef, visualRef, enabled: isReady });
4527
4747
  return {
4528
- areasPortal: areasPortal,
4529
- editHighlightPortal: editHighlightPortal,
4530
- shadowContainer,
4748
+ areasPortal,
4749
+ editHighlightPortal,
4531
4750
  isReady,
4532
4751
  };
4533
4752
  }
4534
4753
 
4535
- const VizAreaClick = ({ iframeRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true, renderControls, onAreaClick, }) => {
4536
- const iframeDocument = iframeRef.current?.contentDocument;
4537
- const { dataInfo } = useHeatmapData();
4538
- const { areas, isEditingMode, setIsEditingMode, addArea, clearAreas, setAreas } = useHeatmapAreaClick();
4754
+ const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true, onAreaClick, }) => {
4755
+ const { resetView } = useHeatmapAreaClick();
4539
4756
  const { areasPortal, editHighlightPortal, isReady } = useAreaRenderer({
4540
4757
  iframeRef,
4758
+ visualRef,
4541
4759
  shadowRoot,
4542
4760
  onAreaClick,
4543
4761
  });
4544
- // Auto-create areas from top elements
4545
- useEffect(() => {
4546
- if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks)
4547
- return;
4548
- if (autoCreateTopN <= 0)
4549
- return;
4550
- if (areas.length > 0)
4551
- return; // Already have areas
4552
- // Get top elements by clicks
4553
- const topElements = getTopElementsByClicks(dataInfo.elementMapInfo, autoCreateTopN);
4554
- // Build area nodes
4555
- const newAreas = [];
4556
- topElements.forEach(({ hash, selector }) => {
4557
- // Find element in DOM
4558
- const element = iframeDocument?.querySelector(selector);
4559
- if (!element || !(element instanceof HTMLElement))
4560
- return;
4561
- const area = buildAreaNode(element, hash, dataInfo.elementMapInfo, dataInfo.totalClicks);
4562
- newAreas.push(area);
4563
- });
4564
- // Add all areas
4565
- newAreas.forEach((area) => addArea(area));
4566
- }, [dataInfo, autoCreateTopN, areas.length, iframeDocument, shadowRoot]);
4567
- // Apply overlap resolution
4568
- const visibleAreas = useMemo(() => {
4569
- if (!enableOverlapResolution)
4570
- return areas;
4571
- if (!iframeDocument)
4572
- return areas;
4573
- return getVisibleAreas(areas, iframeDocument);
4574
- }, [areas, iframeDocument]);
4575
- // Update visible areas in store when resolution changes
4762
+ useAreaTopAutoDetect({ iframeRef, autoCreateTopN, shadowRoot });
4763
+ useAreaFilterVisible({ iframeRef, enableOverlapResolution });
4576
4764
  useEffect(() => {
4577
- if (enableOverlapResolution && visibleAreas.length !== areas.length) {
4578
- setAreas(visibleAreas);
4579
- }
4580
- }, [visibleAreas, areas.length]);
4581
- const handleToggleEdit = useCallback(() => {
4582
- setIsEditingMode(!isEditingMode);
4583
- }, [isEditingMode]);
4584
- const handleClearAll = useCallback(() => {
4585
- if (window.confirm(`Clear all ${areas.length} areas?`)) {
4586
- clearAreas();
4587
- }
4588
- }, [areas.length]);
4589
- const controlsElement = renderControls ? (renderControls({
4590
- isEditingMode,
4591
- areasCount: areas.length,
4592
- onToggleEdit: handleToggleEdit,
4593
- onClearAll: handleClearAll,
4594
- })) : (jsx(AreaControls, {}));
4765
+ return () => {
4766
+ resetView();
4767
+ };
4768
+ }, []);
4595
4769
  if (!isReady)
4596
4770
  return null;
4597
- return (jsxs(Fragment, { children: [areasPortal, editHighlightPortal, controlsElement] }));
4771
+ return (jsxs(Fragment, { children: [areasPortal, editHighlightPortal] }));
4598
4772
  };
4599
4773
  VizAreaClick.displayName = 'VizAreaClick';
4600
4774
 
@@ -4686,7 +4860,7 @@ const ElementMissing = ({ show = true }) => {
4686
4860
  }, "aria-live": "assertive", children: "Element not visible on current screen" }));
4687
4861
  };
4688
4862
 
4689
- const ElementOverlay = ({ type, element, onClick, isSecondary, elementId, }) => {
4863
+ const ElementOverlay = ({ type, element, onClick, elementId }) => {
4690
4864
  // useRenderCount('ElementOverlay');
4691
4865
  const { widthScale } = useHeatmapViz();
4692
4866
  if (!element || (element.width === 0 && element.height === 0))
@@ -4711,29 +4885,31 @@ const ELEMENT_CALLOUT = {
4711
4885
  };
4712
4886
  const HeatmapElements = (props) => {
4713
4887
  const viewId = useViewIdContext();
4714
- const { iframeHeight } = useHeatmapViz();
4715
4888
  const clickedElementId = getClickedElementId(viewId, props.isSecondary);
4716
4889
  const hoveredElementId = getHoveredElementId(viewId, props.isSecondary);
4717
- const { iframeRef, wrapperRef, visualRef, visualizer, iframeDimensions, isVisible = true, areDefaultRanksHidden, isSecondary, } = props;
4718
- const getRect = useHeatmapElementPosition({
4719
- iframeRef,
4720
- wrapperRef,
4721
- visualizer,
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,
4722
4896
  });
4723
4897
  const { clickedElement, showMissingElement, shouldShowCallout } = useClickedElement({
4724
- visualRef,
4898
+ visualRef: props.visualRef,
4725
4899
  getRect,
4726
4900
  });
4727
4901
  const { hoveredElement, handleMouseMove, handleMouseLeave, handleClick } = useHoveredElement({
4728
- iframeRef,
4902
+ iframeRef: props.iframeRef,
4729
4903
  getRect,
4730
4904
  });
4731
- useElementCalloutVisible({ visualRef, getRect });
4905
+ useElementCalloutVisible({ visualRef: props.visualRef, getRect });
4732
4906
  useHeatmapEffects({ isVisible });
4733
4907
  useRenderCount('HeatmapElements');
4734
4908
  if (!isVisible)
4735
4909
  return null;
4736
- return (jsxs("div", { onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, className: "gx-hm-elements", style: { ...iframeDimensions, height: `${iframeHeight}px` }, children: [jsx(ElementMissing, { show: showMissingElement }), jsx(DefaultRankBadges, { getRect: getRect, hidden: areDefaultRanksHidden }), jsx(ElementOverlay, { type: "clicked", element: clickedElement, isSecondary: isSecondary, elementId: clickedElementId }), jsx(ElementOverlay, { type: "hovered", element: hoveredElement, isSecondary: isSecondary, onClick: handleClick, elementId: hoveredElementId }), hoveredElement?.hash !== clickedElement?.hash && hoveredElement && (jsx(ElementCallout, { element: hoveredElement, target: `#${hoveredElementId}`, visualRef: visualRef, ...ELEMENT_CALLOUT })), shouldShowCallout && clickedElement && (jsx(ElementCallout, { element: clickedElement, target: `#${clickedElementId}`, visualRef: visualRef, ...ELEMENT_CALLOUT }))] }));
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 }))] }));
4737
4913
  };
4738
4914
 
4739
4915
  const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
@@ -4759,6 +4935,22 @@ const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
4759
4935
  } }));
4760
4936
  };
4761
4937
 
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)
4943
+ return null;
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
+ }
4952
+ };
4953
+
4762
4954
  const ScrollMapMinimap = ({ zones, maxUsers }) => {
4763
4955
  const scrollType = useHeatmapConfigStore((state) => state.scrollType);
4764
4956
  const { showMinimap } = useHeatmapScroll();
@@ -5084,12 +5276,11 @@ const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHe
5084
5276
 
5085
5277
  const VizDomRenderer = ({ mode = 'heatmap' }) => {
5086
5278
  const contentWidth = useHeatmapConfigStore((state) => state.width || 0);
5087
- const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
5088
5279
  const wrapperRef = useRef(null);
5089
5280
  const visualRef = useRef(null);
5090
5281
  const { setSelectedElement } = useHeatmapClick();
5091
5282
  const { iframeHeight, setIframeHeight, isRenderViz } = useHeatmapViz();
5092
- const { iframeRef } = useHeatmapVizRender(mode);
5283
+ const { iframeRef } = useHeatmapRenderByMode(mode);
5093
5284
  const { scaledHeight, handleScroll } = useHeatmapScale({
5094
5285
  wrapperRef,
5095
5286
  iframeRef,
@@ -5110,9 +5301,7 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
5110
5301
  useEffect(() => {
5111
5302
  return cleanUp;
5112
5303
  }, []);
5113
- return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [heatmapType === IHeatmapType.Click && (jsx(VizElements, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef })), heatmapType === IHeatmapType.ClickArea && (jsx(VizAreaClick, { iframeRef: iframeRef, onAreaClick: (area) => {
5114
- console.log('area clicked', area);
5115
- } })), jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, scrolling: "no" }), jsx(VizScrollMap, { iframeRef: iframeRef, wrapperRef: visualRef })] }));
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 })] }));
5116
5305
  };
5117
5306
 
5118
5307
  const VizLoading = () => {
@@ -5231,4 +5420,104 @@ const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
5231
5420
  }
5232
5421
  };
5233
5422
 
5234
- export { DEFAULT_SIDEBAR_WIDTH, DEFAULT_VIEW_ID, GraphView, HEATMAP_CONFIG, HEATMAP_IFRAME, HEATMAP_STYLE, HeatmapLayout, IClickType, IHeatmapType, IScrollType, ViewIdContext, Z_INDEX, compareViewPerformance, convertViewportToIframeCoords, createStorePerformanceTracker, downloadPerformanceReport, getCompareViewId, getMetricsByViewId, getPerformanceReportJSON, getScrollGradientColor, performanceLogger, printPerformanceSummary, sendPerformanceReport, trackStoreAction, useAreaEditMode, useAreaScrollSync, useClickedElement, useDebounceCallback, useElementCalloutVisible, useHeatmapAreaClick, useHeatmapCanvas, useHeatmapClick, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapCopyView, useHeatmapData, useHeatmapEffects, useHeatmapElementPosition, useHeatmapLiveStore, useHeatmapScale, useHeatmapScroll, useHeatmapViz, useHeatmapVizRender, useHoveredElement, useMeasureFunction, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useRenderCount, useScrollmapZones, useTrackHookCall, useViewIdContext, useVizLiveRender, useWhyDidYouUpdate, useWrapperRefHeight, useZonePositions, withPerformanceTracking };
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 };