@gemx-dev/heatmap-react 3.5.41 → 3.5.42

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 (74) hide show
  1. package/dist/esm/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  2. package/dist/esm/components/VizElement/HeatmapElements.d.ts.map +1 -1
  3. package/dist/esm/components/VizLive/VizLiveRenderer.d.ts.map +1 -1
  4. package/dist/esm/configs/style.d.ts +2 -0
  5. package/dist/esm/configs/style.d.ts.map +1 -1
  6. package/dist/esm/hooks/vix-elements/useHeatmapMouseHandler.d.ts +34 -0
  7. package/dist/esm/hooks/vix-elements/useHeatmapMouseHandler.d.ts.map +1 -0
  8. package/dist/esm/hooks/vix-elements/useHoveredElement.d.ts.map +1 -1
  9. package/dist/esm/hooks/viz-canvas/useClickmap.d.ts +3 -1
  10. package/dist/esm/hooks/viz-canvas/useClickmap.d.ts.map +1 -1
  11. package/dist/esm/hooks/viz-canvas/useHeatmapVizCanvas.d.ts +1 -1
  12. package/dist/esm/hooks/viz-canvas/useHeatmapVizCanvas.d.ts.map +1 -1
  13. package/dist/esm/hooks/viz-canvas/useScrollmap.d.ts +3 -1
  14. package/dist/esm/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
  15. package/dist/esm/hooks/viz-live/index.d.ts +1 -1
  16. package/dist/esm/hooks/viz-live/{useIframeMessage.d.ts → useVizLiveIframeMsg.d.ts} +2 -5
  17. package/dist/esm/hooks/viz-live/useVizLiveIframeMsg.d.ts.map +1 -0
  18. package/dist/esm/hooks/viz-live/useVizLiveRender.d.ts +4 -0
  19. package/dist/esm/hooks/viz-live/useVizLiveRender.d.ts.map +1 -0
  20. package/dist/esm/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
  21. package/dist/esm/hooks/viz-scale/useContainerDimensions.d.ts.map +1 -1
  22. package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts +1 -1
  23. package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
  24. package/dist/esm/hooks/viz-scale/useObserveIframeHeight.d.ts +10 -0
  25. package/dist/esm/hooks/viz-scale/useObserveIframeHeight.d.ts.map +1 -0
  26. package/dist/esm/index.js +451 -62
  27. package/dist/esm/index.mjs +451 -62
  28. package/dist/esm/stores/index.d.ts +1 -0
  29. package/dist/esm/stores/index.d.ts.map +1 -1
  30. package/dist/esm/stores/interaction.d.ts.map +1 -1
  31. package/dist/esm/stores/mode-single.d.ts +9 -0
  32. package/dist/esm/stores/mode-single.d.ts.map +1 -0
  33. package/dist/esm/stores/viz.d.ts +0 -5
  34. package/dist/esm/stores/viz.d.ts.map +1 -1
  35. package/dist/umd/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  36. package/dist/umd/components/VizElement/HeatmapElements.d.ts.map +1 -1
  37. package/dist/umd/components/VizLive/VizLiveRenderer.d.ts.map +1 -1
  38. package/dist/umd/configs/style.d.ts +2 -0
  39. package/dist/umd/configs/style.d.ts.map +1 -1
  40. package/dist/umd/hooks/vix-elements/useHeatmapMouseHandler.d.ts +34 -0
  41. package/dist/umd/hooks/vix-elements/useHeatmapMouseHandler.d.ts.map +1 -0
  42. package/dist/umd/hooks/vix-elements/useHoveredElement.d.ts.map +1 -1
  43. package/dist/umd/hooks/viz-canvas/useClickmap.d.ts +3 -1
  44. package/dist/umd/hooks/viz-canvas/useClickmap.d.ts.map +1 -1
  45. package/dist/umd/hooks/viz-canvas/useHeatmapVizCanvas.d.ts +1 -1
  46. package/dist/umd/hooks/viz-canvas/useHeatmapVizCanvas.d.ts.map +1 -1
  47. package/dist/umd/hooks/viz-canvas/useScrollmap.d.ts +3 -1
  48. package/dist/umd/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
  49. package/dist/umd/hooks/viz-live/index.d.ts +1 -1
  50. package/dist/umd/hooks/viz-live/{useIframeMessage.d.ts → useVizLiveIframeMsg.d.ts} +2 -5
  51. package/dist/umd/hooks/viz-live/useVizLiveIframeMsg.d.ts.map +1 -0
  52. package/dist/umd/hooks/viz-live/useVizLiveRender.d.ts +4 -0
  53. package/dist/umd/hooks/viz-live/useVizLiveRender.d.ts.map +1 -0
  54. package/dist/umd/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
  55. package/dist/umd/hooks/viz-scale/useContainerDimensions.d.ts.map +1 -1
  56. package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts +1 -1
  57. package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
  58. package/dist/umd/hooks/viz-scale/useObserveIframeHeight.d.ts +10 -0
  59. package/dist/umd/hooks/viz-scale/useObserveIframeHeight.d.ts.map +1 -0
  60. package/dist/umd/index.js +2 -2
  61. package/dist/umd/stores/index.d.ts +1 -0
  62. package/dist/umd/stores/index.d.ts.map +1 -1
  63. package/dist/umd/stores/interaction.d.ts.map +1 -1
  64. package/dist/umd/stores/mode-single.d.ts +9 -0
  65. package/dist/umd/stores/mode-single.d.ts.map +1 -0
  66. package/dist/umd/stores/viz.d.ts +0 -5
  67. package/dist/umd/stores/viz.d.ts.map +1 -1
  68. package/package.json +1 -1
  69. package/dist/esm/hooks/viz-live/useIframeMessage.d.ts.map +0 -1
  70. package/dist/esm/hooks/viz-scale/useIframeHeight.d.ts +0 -10
  71. package/dist/esm/hooks/viz-scale/useIframeHeight.d.ts.map +0 -1
  72. package/dist/umd/hooks/viz-live/useIframeMessage.d.ts.map +0 -1
  73. package/dist/umd/hooks/viz-scale/useIframeHeight.d.ts +0 -10
  74. package/dist/umd/hooks/viz-scale/useIframeHeight.d.ts.map +0 -1
@@ -61,6 +61,8 @@ const HEATMAP_STYLE = {
61
61
  },
62
62
  wrapper: {
63
63
  padding: `${HEATMAP_CONFIG.padding}px 0`,
64
+ paddingBlock: `${HEATMAP_CONFIG.padding}px`,
65
+ paddingInline: `${HEATMAP_CONFIG.padding}px`,
64
66
  },
65
67
  };
66
68
  const DEFAULT_SIDEBAR_WIDTH = 260;
@@ -139,11 +141,7 @@ const useHeatmapVizStore = create()((set, get) => {
139
141
  isRenderViz: false,
140
142
  setIsRenderViz: (isRenderViz) => set({ isRenderViz }),
141
143
  scale: 1,
142
- vizRef: null,
143
- iframeHeight: 0,
144
144
  setScale: (scale) => set({ scale }),
145
- setVizRef: (vizRef) => set({ vizRef }),
146
- setIframeHeight: (iframeHeight) => set({ iframeHeight }),
147
145
  };
148
146
  });
149
147
 
@@ -165,6 +163,15 @@ const useHeatmapLiveStore = create()((set, get) => {
165
163
  };
166
164
  });
167
165
 
166
+ const useHeatmapSingleStore = create()((set, get) => {
167
+ return {
168
+ vizRef: null,
169
+ iframeHeight: 0,
170
+ setVizRef: (vizRef) => set({ vizRef }),
171
+ setIframeHeight: (iframeHeight) => set({ iframeHeight }),
172
+ };
173
+ });
174
+
168
175
  const useRegisterConfig = () => {
169
176
  const mode = useHeatmapConfigStore((state) => state.mode);
170
177
  const width = useHeatmapConfigStore((state) => state.width);
@@ -872,7 +879,7 @@ const useHeatmapEffects = ({ isVisible, isElementSidebarOpen, setShouldShowCallo
872
879
 
873
880
  const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
874
881
  const widthScale = useHeatmapVizStore((state) => state.scale);
875
- const iframeHeight = useHeatmapVizStore((state) => state.iframeHeight);
882
+ const iframeHeight = useHeatmapSingleStore((state) => state.iframeHeight);
876
883
  const heatmapWidth = useHeatmapConfigStore((state) => state.width);
877
884
  return useCallback((element) => {
878
885
  const hash = element?.hash;
@@ -920,6 +927,179 @@ const debounce = (fn, delay) => {
920
927
  };
921
928
  };
922
929
 
930
+ // ===================== CONSTANTS =====================
931
+ const HEATMAP_ELEMENT_ATTRIBUTE = 'data-clarity-hashalpha'; // Hoặc attribute bạn đang dùng
932
+ // ===================== UTILITY FUNCTIONS =====================
933
+ /**
934
+ * Lấy bounding box tuyệt đối của element (relative to document)
935
+ */
936
+ function getBoundingBox(element) {
937
+ if (typeof element.getBoundingClientRect !== 'function') {
938
+ return null;
939
+ }
940
+ const rect = element.getBoundingClientRect();
941
+ // Lấy scroll offset (hỗ trợ cả cách cũ và mới)
942
+ const scrollLeft = 'pageXOffset' in window ? window.pageXOffset : document.documentElement.scrollLeft;
943
+ const scrollTop = 'pageYOffset' in window ? window.pageYOffset : document.documentElement.scrollTop;
944
+ // Kiểm tra element có kích thước hợp lệ
945
+ if (!rect || (rect.height === 0 && rect.width === 0)) {
946
+ return null;
947
+ }
948
+ // Trả về vị trí tuyệt đối
949
+ return {
950
+ left: Math.floor(rect.left + scrollLeft),
951
+ top: Math.floor(rect.top + scrollTop),
952
+ width: Math.floor(rect.width),
953
+ height: Math.floor(rect.height),
954
+ };
955
+ }
956
+ /**
957
+ * Lấy tất cả elements tại tọa độ (x, y), hỗ trợ Shadow DOM
958
+ */
959
+ function getElementsAtPoint(documentOrShadowRoot, x, y, filterFunction, visitedShadowRoots = new Set()) {
960
+ // Lấy tất cả elements tại vị trí
961
+ const elementsAtPoint = documentOrShadowRoot.elementsFromPoint(x, y);
962
+ if (!filterFunction) {
963
+ return elementsAtPoint;
964
+ }
965
+ // Tìm element đầu tiên match với filter
966
+ const matchedElement = elementsAtPoint.find(filterFunction);
967
+ // Nếu element có Shadow DOM và chưa visit -> đệ quy vào
968
+ if (matchedElement?.shadowRoot && !visitedShadowRoots.has(matchedElement.shadowRoot)) {
969
+ visitedShadowRoots.add(matchedElement.shadowRoot);
970
+ return getElementsAtPoint(matchedElement.shadowRoot, x, y, filterFunction, visitedShadowRoots);
971
+ }
972
+ return elementsAtPoint;
973
+ }
974
+ // ===================== MAIN HOOK =====================
975
+ function useHeatmapMouseHandler(props) {
976
+ const { heatmapWrapperRef, iframeRef, parentRef, heatmapInfo, scaleRatio, onElementHover } = props;
977
+ const handleMouseMove = useCallback((event) => {
978
+ // Kiểm tra tất cả refs và data cần thiết
979
+ if (!heatmapWrapperRef?.current ||
980
+ !iframeRef?.current ||
981
+ !iframeRef.current.contentDocument ||
982
+ !heatmapInfo?.elementMapInfo ||
983
+ !parentRef?.current) {
984
+ return;
985
+ }
986
+ try {
987
+ // Tính toán scroll position (đã scale)
988
+ const scrollTop = parentRef.current.scrollTop / scaleRatio;
989
+ console.log(`🚀 🐥 ~ useHeatmapMouseHandler ~ scrollTop:`, scrollTop);
990
+ // Lấy vị trí của heatmap wrapper
991
+ const wrapperRect = heatmapWrapperRef.current.getBoundingClientRect();
992
+ // Tính toán tọa độ chuột trong iframe (đã scale)
993
+ const mouseX = (event.clientX - wrapperRect.left) / scaleRatio;
994
+ const mouseY = (event.clientY - wrapperRect.top) / scaleRatio - scrollTop;
995
+ // Tìm elements tại vị trí chuột
996
+ const elementsAtPoint = getElementsAtPoint(iframeRef.current.contentDocument, Math.round(mouseX), Math.round(mouseY),
997
+ // Filter: chỉ lấy elements có heatmap attribute
998
+ (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE));
999
+ if (!elementsAtPoint || elementsAtPoint.length === 0) {
1000
+ return;
1001
+ }
1002
+ // Duyệt qua các elements tìm được
1003
+ for (let i = 0; i < elementsAtPoint.length; i++) {
1004
+ const element = elementsAtPoint[i];
1005
+ // Lấy hash/id của element
1006
+ const elementHash = element.getAttribute(HEATMAP_ELEMENT_ATTRIBUTE);
1007
+ // Kiểm tra element có data trong heatmapInfo không
1008
+ if (elementHash && heatmapInfo.elementMapInfo[elementHash]) {
1009
+ const elementData = heatmapInfo.elementMapInfo[elementHash];
1010
+ // Lấy bounding box của element
1011
+ const boundingBox = getBoundingBox(element);
1012
+ if (boundingBox) {
1013
+ // Tính rank của element
1014
+ const rank = Array.isArray(heatmapInfo.sortedElements) && elementData
1015
+ ? heatmapInfo.sortedElements.indexOf(elementData) + 1
1016
+ : NaN;
1017
+ // Callback với thông tin element
1018
+ onElementHover({
1019
+ ...boundingBox,
1020
+ // Giới hạn width không vượt quá width của heatmap
1021
+ width: Math.min(boundingBox.width, heatmapInfo.width || 0),
1022
+ // Adjust top position với scroll
1023
+ top: boundingBox.top + scrollTop,
1024
+ // Metadata
1025
+ hash: elementHash,
1026
+ clicks: elementData.totalclicks,
1027
+ rank: rank,
1028
+ selector: elementData.selector || '',
1029
+ });
1030
+ // Dừng loop khi tìm thấy element hợp lệ đầu tiên
1031
+ break;
1032
+ }
1033
+ }
1034
+ }
1035
+ }
1036
+ catch (error) {
1037
+ console.warn('Error handling mouse move on heatmap:', error);
1038
+ }
1039
+ }, [heatmapWrapperRef, iframeRef, parentRef, heatmapInfo, scaleRatio, onElementHover]);
1040
+ return { handleMouseMove };
1041
+ }
1042
+ // ===================== EXAMPLE USAGE =====================
1043
+ /*
1044
+ import { useRef, useState } from 'react';
1045
+
1046
+ function HeatmapComponent() {
1047
+ const heatmapWrapperRef = useRef<HTMLDivElement>(null);
1048
+ const iframeRef = useRef<HTMLIFrameElement>(null);
1049
+ const parentRef = useRef<HTMLDivElement>(null);
1050
+
1051
+ const [hoveredElement, setHoveredElement] = useState<HoveredElementInfo | null>(null);
1052
+
1053
+ const heatmapInfo = {
1054
+ width: 1920,
1055
+ elementMapInfo: {
1056
+ 'hash123': {
1057
+ totalclicks: 45,
1058
+ selector: 'button.submit'
1059
+ }
1060
+ },
1061
+ sortedElements: [...]
1062
+ };
1063
+
1064
+ const { handleMouseMove } = useHeatmapMouseHandler({
1065
+ heatmapWrapperRef,
1066
+ iframeRef,
1067
+ parentRef,
1068
+ heatmapInfo,
1069
+ scaleRatio: 0.8, // 80% zoom
1070
+ onElementHover: (info) => {
1071
+ setHoveredElement(info);
1072
+ console.log('Hovered element:', info);
1073
+ }
1074
+ });
1075
+
1076
+ return (
1077
+ <div ref={parentRef}>
1078
+ <div
1079
+ ref={heatmapWrapperRef}
1080
+ onMouseMove={handleMouseMove}
1081
+ >
1082
+ <iframe ref={iframeRef} />
1083
+
1084
+ {hoveredElement && (
1085
+ <div className="tooltip" style={{
1086
+ position: 'absolute',
1087
+ left: hoveredElement.left,
1088
+ top: hoveredElement.top
1089
+ }}>
1090
+ Clicks: {hoveredElement.clicks}
1091
+ <br />
1092
+ Rank: #{hoveredElement.rank}
1093
+ <br />
1094
+ Selector: {hoveredElement.selector}
1095
+ </div>
1096
+ )}
1097
+ </div>
1098
+ </div>
1099
+ );
1100
+ }
1101
+ */
1102
+
923
1103
  const useHoveredElement = ({ iframeRef, getRect }) => {
924
1104
  const hoveredElement = useHeatmapInteractionStore((state) => state.hoveredElement);
925
1105
  const setHoveredElement = useHeatmapInteractionStore((state) => state.setHoveredElement);
@@ -941,7 +1121,7 @@ const useHoveredElement = ({ iframeRef, getRect }) => {
941
1121
  const doc = iframe.contentDocument;
942
1122
  const iframeRect = iframe.getBoundingClientRect();
943
1123
  const { x, y } = convertViewportToIframeCoords(event.clientX, event.clientY, iframeRect, widthScale);
944
- const targetElement = findTargetElement(doc, x, y);
1124
+ const targetElement = findTargetElement(doc, x, y, heatmapInfo);
945
1125
  if (!targetElement || !isValidElement(targetElement, heatmapInfo)) {
946
1126
  reset();
947
1127
  return;
@@ -992,7 +1172,25 @@ const convertViewportToIframeCoords = (clientX, clientY, iframeRect, scale) => {
992
1172
  }
993
1173
  return { x, y };
994
1174
  };
995
- const findTargetElement = (doc, x, y) => {
1175
+ const findTargetElement = (doc, x, y, heatmapInfo) => {
1176
+ const HEATMAP_ELEMENT_ATTRIBUTE = 'data-clarity-hashalpha';
1177
+ const elementsAtPoint = getElementsAtPoint(doc, Math.round(x), Math.round(y), (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE));
1178
+ let dataElement = null;
1179
+ for (let i = 0; i < elementsAtPoint.length; i++) {
1180
+ const element = elementsAtPoint[i];
1181
+ const elementHash = element.getAttribute(HEATMAP_ELEMENT_ATTRIBUTE);
1182
+ if (elementHash && heatmapInfo.elementMapInfo?.[elementHash]) {
1183
+ heatmapInfo.elementMapInfo[elementHash];
1184
+ const boundingBox = getBoundingBox(element);
1185
+ if (boundingBox) {
1186
+ dataElement = element;
1187
+ break;
1188
+ }
1189
+ }
1190
+ }
1191
+ if (!!dataElement) {
1192
+ return dataElement;
1193
+ }
996
1194
  let targetElement = getElementAtPoint(doc, x, y);
997
1195
  if (!targetElement) {
998
1196
  targetElement = doc.elementFromPoint(x, y);
@@ -1016,7 +1214,7 @@ var MessageType;
1016
1214
  MessageType["GX_DOM_TRACKING_PAYLOAD"] = "GX_DOM_TRACKING_PAYLOAD";
1017
1215
  MessageType["CLARITY_READY"] = "CLARITY_READY";
1018
1216
  })(MessageType || (MessageType = {}));
1019
- function useIframeMessage(options = {}) {
1217
+ function useVizLiveIframeMsg(options = {}) {
1020
1218
  const { trustedOrigins = [], onMessage } = options;
1021
1219
  const addPayload = useHeatmapLiveStore((state) => state.addPayload);
1022
1220
  const [isReady, setIsReady] = useState(false);
@@ -1066,13 +1264,14 @@ function useIframeMessage(options = {}) {
1066
1264
  isReady,
1067
1265
  };
1068
1266
  }
1069
- function useIframeRender() {
1267
+
1268
+ function useVizLiveRender() {
1070
1269
  const wrapperHeight = useHeatmapLiveStore((state) => state.wrapperHeight);
1270
+ const setIframeHeight = useHeatmapLiveStore((state) => state.setIframeHeight);
1071
1271
  const contentWidth = useHeatmapConfigStore((state) => state.width);
1072
- const setIframeHeight = useHeatmapVizStore((state) => state.setIframeHeight);
1073
1272
  const htmlContent = useHeatmapLiveStore((state) => state.htmlContent);
1074
1273
  const setIsRenderViz = useHeatmapVizStore((state) => state.setIsRenderViz);
1075
- const { iframeRef, isReady } = useIframeMessage();
1274
+ const { iframeRef, isReady } = useVizLiveIframeMsg();
1076
1275
  useEffect(() => {
1077
1276
  if (!htmlContent || !iframeRef.current)
1078
1277
  return;
@@ -1119,9 +1318,9 @@ function reset$1(iframe, rect, onSuccess) {
1119
1318
  let visualizer = new Visualizer();
1120
1319
  const useHeatmapRender = () => {
1121
1320
  const data = useHeatmapDataStore((state) => state.data);
1122
- const setVizRef = useHeatmapVizStore((state) => state.setVizRef);
1321
+ const setVizRef = useHeatmapSingleStore((state) => state.setVizRef);
1123
1322
  const setIsRenderViz = useHeatmapVizStore((state) => state.setIsRenderViz);
1124
- const setIframeHeight = useHeatmapVizStore((state) => state.setIframeHeight);
1323
+ const setIframeHeight = useHeatmapSingleStore((state) => state.setIframeHeight);
1125
1324
  const iframeRef = useRef(null);
1126
1325
  const renderHeatmap = useCallback(async (payloads) => {
1127
1326
  if (!payloads || payloads.length === 0)
@@ -1131,8 +1330,6 @@ const useHeatmapRender = () => {
1131
1330
  if (!iframe?.contentWindow)
1132
1331
  return;
1133
1332
  await visualizer.html(payloads, iframe.contentWindow);
1134
- const element = visualizer.get('3w0uppvjw');
1135
- console.log(`🚀 🐥 ~ useHeatmapRender ~ element:`, element);
1136
1333
  reset(iframe, payloads, (height) => {
1137
1334
  height && setIframeHeight(height);
1138
1335
  setIsRenderViz(true);
@@ -1160,8 +1357,8 @@ function reset(iframe, payloads, onSuccess) {
1160
1357
  targetHeight: docHeight,
1161
1358
  iframe: iframe,
1162
1359
  onSuccess: (data) => {
1163
- onSuccess(data.height);
1164
1360
  iframe.height = `${data.height}px`;
1361
+ onSuccess(data.height);
1165
1362
  },
1166
1363
  });
1167
1364
  viewportFixer.recalculate();
@@ -1350,30 +1547,64 @@ const useContentDimensions = ({ iframeRef, }) => {
1350
1547
  return { contentWidth };
1351
1548
  };
1352
1549
 
1353
- const useIframeHeight = (props) => {
1550
+ const useObserveIframeHeight = (props) => {
1354
1551
  const { iframeRef, setIframeHeight } = props;
1355
1552
  const isRenderViz = useHeatmapVizStore((state) => state.isRenderViz);
1356
1553
  const resizeObserverRef = useRef(null);
1357
1554
  const mutationObserverRef = useRef(null);
1555
+ const debounceTimerRef = useRef(null);
1556
+ const lastHeightRef = useRef(0);
1557
+ const animationFrameRef = useRef(null);
1358
1558
  const updateIframeHeight = useCallback(() => {
1359
1559
  const iframe = iframeRef.current;
1360
- if (!iframe)
1560
+ if (!iframe || !setIframeHeight)
1361
1561
  return;
1362
1562
  try {
1363
1563
  const iframeDocument = iframe.contentDocument;
1364
1564
  const iframeBody = iframeDocument?.body;
1365
- if (!iframeBody)
1565
+ const iframeDocumentElement = iframeDocument?.documentElement;
1566
+ if (!iframeBody || !iframeDocumentElement)
1366
1567
  return;
1367
- const bodyHeight = Math.max(iframeBody.scrollHeight, iframeBody.offsetHeight, iframeBody.clientHeight);
1368
- if (bodyHeight > 0) {
1369
- iframe.height = `${bodyHeight}px`;
1370
- setIframeHeight(bodyHeight);
1371
- }
1568
+ iframe.style.height = 'auto';
1569
+ requestAnimationFrame(() => {
1570
+ const bodyHeight = Math.max(iframeBody.scrollHeight, iframeBody.offsetHeight, iframeBody.clientHeight);
1571
+ const documentHeight = Math.max(iframeDocumentElement.scrollHeight, iframeDocumentElement.offsetHeight, iframeDocumentElement.clientHeight);
1572
+ const actualHeight = Math.max(bodyHeight, documentHeight);
1573
+ if (actualHeight > 0) {
1574
+ lastHeightRef.current = actualHeight;
1575
+ iframe.height = `${actualHeight}px`;
1576
+ iframe.style.height = `${actualHeight}px`;
1577
+ setIframeHeight(actualHeight);
1578
+ }
1579
+ });
1372
1580
  }
1373
1581
  catch (error) {
1374
1582
  console.warn('Cannot measure iframe content:', error);
1375
1583
  }
1376
1584
  }, [iframeRef, setIframeHeight]);
1585
+ const debouncedUpdate = useCallback(() => {
1586
+ // Cancel pending updates
1587
+ if (debounceTimerRef.current) {
1588
+ clearTimeout(debounceTimerRef.current);
1589
+ }
1590
+ if (animationFrameRef.current) {
1591
+ cancelAnimationFrame(animationFrameRef.current);
1592
+ }
1593
+ debounceTimerRef.current = setTimeout(() => {
1594
+ animationFrameRef.current = requestAnimationFrame(() => {
1595
+ updateIframeHeight();
1596
+ });
1597
+ }, 50);
1598
+ }, [updateIframeHeight]);
1599
+ // Immediate update không debounce (cho ResizeObserver)
1600
+ const immediateUpdate = useCallback(() => {
1601
+ if (animationFrameRef.current) {
1602
+ cancelAnimationFrame(animationFrameRef.current);
1603
+ }
1604
+ animationFrameRef.current = requestAnimationFrame(() => {
1605
+ updateIframeHeight();
1606
+ });
1607
+ }, [updateIframeHeight]);
1377
1608
  useEffect(() => {
1378
1609
  const iframe = iframeRef.current;
1379
1610
  if (!iframe || !isRenderViz)
@@ -1391,22 +1622,24 @@ const useIframeHeight = (props) => {
1391
1622
  if (mutationObserverRef.current) {
1392
1623
  mutationObserverRef.current.disconnect();
1393
1624
  }
1394
- // ResizeObserver for size changes
1395
1625
  if (typeof window.ResizeObserver !== 'undefined') {
1396
- resizeObserverRef.current = new ResizeObserver(updateIframeHeight);
1626
+ resizeObserverRef.current = new ResizeObserver(immediateUpdate);
1397
1627
  resizeObserverRef.current.observe(iframeBody);
1628
+ const iframeDocumentElement = iframeDocument?.documentElement;
1629
+ if (iframeDocumentElement) {
1630
+ resizeObserverRef.current.observe(iframeDocumentElement);
1631
+ }
1398
1632
  }
1399
- // MutationObserver for DOM changes
1400
1633
  if (typeof window.MutationObserver !== 'undefined') {
1401
- mutationObserverRef.current = new MutationObserver(updateIframeHeight);
1634
+ mutationObserverRef.current = new MutationObserver(immediateUpdate);
1402
1635
  mutationObserverRef.current.observe(iframeBody, {
1403
1636
  childList: true,
1404
1637
  subtree: true,
1405
1638
  attributes: true,
1406
- characterData: true,
1639
+ attributeFilter: ['style', 'class'],
1640
+ characterData: false,
1407
1641
  });
1408
1642
  }
1409
- // Initial measurement
1410
1643
  updateIframeHeight();
1411
1644
  }
1412
1645
  catch (error) {
@@ -1420,15 +1653,23 @@ const useIframeHeight = (props) => {
1420
1653
  iframe.addEventListener('load', setupObservers, { once: true });
1421
1654
  }
1422
1655
  return () => {
1656
+ // Cleanup observers
1423
1657
  if (resizeObserverRef.current) {
1424
1658
  resizeObserverRef.current.disconnect();
1425
1659
  }
1426
1660
  if (mutationObserverRef.current) {
1427
1661
  mutationObserverRef.current.disconnect();
1428
1662
  }
1663
+ // Cleanup timers
1664
+ if (debounceTimerRef.current) {
1665
+ clearTimeout(debounceTimerRef.current);
1666
+ }
1667
+ if (animationFrameRef.current) {
1668
+ cancelAnimationFrame(animationFrameRef.current);
1669
+ }
1429
1670
  iframe.removeEventListener('load', setupObservers);
1430
1671
  };
1431
- }, [iframeRef, isRenderViz, updateIframeHeight]);
1672
+ }, [iframeRef, isRenderViz, updateIframeHeight, debouncedUpdate, immediateUpdate]);
1432
1673
  return {};
1433
1674
  };
1434
1675
 
@@ -1474,7 +1715,7 @@ const useHeatmapScale = (props) => {
1474
1715
  // 2. Get content dimensions from config
1475
1716
  const { contentWidth } = useContentDimensions({ iframeRef });
1476
1717
  // 3. Observe iframe height (now reacts to width changes)
1477
- useIframeHeight({ iframeRef, setIframeHeight });
1718
+ useObserveIframeHeight({ iframeRef, setIframeHeight });
1478
1719
  // 4. Calculate scale
1479
1720
  const { scale } = useScaleCalculation({ containerWidth, contentWidth });
1480
1721
  // 5. Setup scroll sync
@@ -1645,37 +1886,159 @@ const VizContainer = ({ children, setWrapperHeight }) => {
1645
1886
  const useClickmap = () => {
1646
1887
  const [isInitialized, setIsInitialized] = useState(false);
1647
1888
  const clickmap = useHeatmapDataStore((state) => state.clickmap);
1648
- const vizRef = useHeatmapVizStore((state) => state.vizRef);
1649
- useEffect(() => {
1889
+ const vizRef = useHeatmapSingleStore((state) => state.vizRef);
1890
+ const start = useCallback(() => {
1650
1891
  if (isInitialized)
1651
1892
  return;
1652
1893
  if (!vizRef || !clickmap || clickmap.length === 0)
1653
1894
  return;
1654
- vizRef.clearmap();
1655
- const element = vizRef.get('3w0uppvjw');
1656
- console.log(`🚀 🐥 ~ useClickmap ~ element:`, element);
1657
- vizRef?.clickmap(clickmap);
1658
- setIsInitialized(true);
1895
+ try {
1896
+ vizRef?.clearmap?.();
1897
+ vizRef?.clickmap?.(clickmap);
1898
+ setIsInitialized(true);
1899
+ }
1900
+ catch (error) {
1901
+ console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
1902
+ }
1659
1903
  }, [vizRef, clickmap]);
1660
- return {};
1904
+ return { start };
1661
1905
  };
1662
1906
 
1907
+ const DATA_SCROLLMAP = [
1908
+ {
1909
+ scrollReachY: 5,
1910
+ cumulativeSum: 0,
1911
+ percUsers: 0,
1912
+ },
1913
+ {
1914
+ scrollReachY: 10,
1915
+ cumulativeSum: 0,
1916
+ percUsers: 0,
1917
+ },
1918
+ {
1919
+ scrollReachY: 15,
1920
+ cumulativeSum: 0,
1921
+ percUsers: 0,
1922
+ },
1923
+ {
1924
+ scrollReachY: 20,
1925
+ cumulativeSum: 0,
1926
+ percUsers: 0,
1927
+ },
1928
+ {
1929
+ scrollReachY: 25,
1930
+ cumulativeSum: 0,
1931
+ percUsers: 0,
1932
+ },
1933
+ {
1934
+ scrollReachY: 30,
1935
+ cumulativeSum: 0,
1936
+ percUsers: 0,
1937
+ },
1938
+ {
1939
+ scrollReachY: 35,
1940
+ cumulativeSum: 0,
1941
+ percUsers: 0,
1942
+ },
1943
+ {
1944
+ scrollReachY: 40,
1945
+ cumulativeSum: 0,
1946
+ percUsers: 0,
1947
+ },
1948
+ {
1949
+ scrollReachY: 45,
1950
+ cumulativeSum: 0,
1951
+ percUsers: 0,
1952
+ },
1953
+ {
1954
+ scrollReachY: 50,
1955
+ cumulativeSum: 0,
1956
+ percUsers: 0,
1957
+ },
1958
+ {
1959
+ scrollReachY: 55,
1960
+ cumulativeSum: 0,
1961
+ percUsers: 0,
1962
+ },
1963
+ {
1964
+ scrollReachY: 60,
1965
+ cumulativeSum: 0,
1966
+ percUsers: 0,
1967
+ },
1968
+ {
1969
+ scrollReachY: 65,
1970
+ cumulativeSum: 0,
1971
+ percUsers: 0,
1972
+ },
1973
+ {
1974
+ scrollReachY: 70,
1975
+ cumulativeSum: 0,
1976
+ percUsers: 0,
1977
+ },
1978
+ {
1979
+ scrollReachY: 75,
1980
+ cumulativeSum: 0,
1981
+ percUsers: 0,
1982
+ },
1983
+ {
1984
+ scrollReachY: 80,
1985
+ cumulativeSum: 0,
1986
+ percUsers: 0,
1987
+ },
1988
+ {
1989
+ scrollReachY: 85,
1990
+ cumulativeSum: 0,
1991
+ percUsers: 0,
1992
+ },
1993
+ {
1994
+ scrollReachY: 90,
1995
+ cumulativeSum: 0,
1996
+ percUsers: 0,
1997
+ },
1998
+ {
1999
+ scrollReachY: 95,
2000
+ cumulativeSum: 0,
2001
+ percUsers: 0,
2002
+ },
2003
+ {
2004
+ scrollReachY: 100,
2005
+ cumulativeSum: 0,
2006
+ percUsers: 0,
2007
+ },
2008
+ ];
1663
2009
  const useScrollmap = () => {
1664
- useHeatmapDataStore((state) => state.clickmap);
1665
- return {};
2010
+ const vizRef = useHeatmapSingleStore((state) => state.vizRef);
2011
+ const start = useCallback(() => {
2012
+ // if (isInitialized) return;
2013
+ const scrollmap = DATA_SCROLLMAP;
2014
+ if (!vizRef || !scrollmap || scrollmap.length === 0)
2015
+ return;
2016
+ try {
2017
+ vizRef?.clearmap?.();
2018
+ vizRef?.scrollmap?.(scrollmap);
2019
+ // setIsInitialized(true);
2020
+ }
2021
+ catch (error) {
2022
+ console.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
2023
+ }
2024
+ }, [vizRef]);
2025
+ return { start };
1666
2026
  };
1667
2027
 
1668
2028
  const useHeatmapVizCanvas = () => {
1669
2029
  const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
1670
- const heatmapRender = useMemo(() => {
2030
+ const { start: startClickmap } = useClickmap();
2031
+ const { start: startScrollmap } = useScrollmap();
2032
+ useEffect(() => {
1671
2033
  switch (heatmapType) {
1672
2034
  case IHeatmapType.Click:
1673
- return useClickmap;
2035
+ startClickmap();
2036
+ break;
1674
2037
  case IHeatmapType.Scroll:
1675
- return useScrollmap;
2038
+ startClickmap();
2039
+ break;
1676
2040
  }
1677
- }, [heatmapType]);
1678
- return heatmapRender?.();
2041
+ }, [heatmapType, startClickmap, startScrollmap]);
1679
2042
  };
1680
2043
 
1681
2044
  const CLICKED_ELEMENT_ID = 'gx-hm-clicked-element';
@@ -1738,7 +2101,7 @@ const ElementCallout = (props) => {
1738
2101
  window.removeEventListener('resize', handleUpdate);
1739
2102
  visualRef?.current?.removeEventListener('scroll', handleUpdate);
1740
2103
  };
1741
- }, [target, visualRef, hozOffset, alignment]);
2104
+ }, [element, target, visualRef, hozOffset, alignment]);
1742
2105
  const calloutContent = (jsx("div", { ref: calloutRef, className: `clarity-callout clarity-callout--${position.placement} clarity-callout--align-${position.horizontalAlign}`, style: {
1743
2106
  position: 'fixed',
1744
2107
  top: position.top,
@@ -1803,7 +2166,8 @@ const ELEMENT_CALLOUT = {
1803
2166
  alignment: 'left',
1804
2167
  };
1805
2168
  const HeatmapElements = (props) => {
1806
- const height = useHeatmapVizStore((state) => state.iframeHeight);
2169
+ const iframeHeight = useHeatmapSingleStore((state) => state.iframeHeight);
2170
+ const setHoveredElement = useHeatmapInteractionStore((state) => state.setHoveredElement);
1807
2171
  const { iframeRef, wrapperRef, visualRef, visualizer, iframeDimensions, isElementSidebarOpen, isVisible = true, areDefaultRanksHidden, isSecondary, ...rest } = props;
1808
2172
  const getRect = useHeatmapElementPosition({
1809
2173
  iframeRef,
@@ -1818,6 +2182,27 @@ const HeatmapElements = (props) => {
1818
2182
  iframeRef,
1819
2183
  getRect,
1820
2184
  });
2185
+ const heatmapInfo = useHeatmapDataStore((state) => state.dataInfo);
2186
+ useHeatmapMouseHandler({
2187
+ heatmapWrapperRef: wrapperRef,
2188
+ iframeRef,
2189
+ parentRef: visualRef,
2190
+ heatmapInfo: heatmapInfo || {},
2191
+ scaleRatio: 0.8, // 80% zoom
2192
+ onElementHover: (info) => {
2193
+ setHoveredElement({
2194
+ hash: info.hash,
2195
+ clicks: info.clicks,
2196
+ rank: info.rank,
2197
+ selector: info.selector,
2198
+ top: info.top,
2199
+ left: info.left,
2200
+ width: info.width,
2201
+ height: info.height,
2202
+ });
2203
+ console.log(`🚀 🐥 ~ HeatmapElements ~ info:`, info);
2204
+ },
2205
+ });
1821
2206
  useElementCalloutVisible({
1822
2207
  visualRef,
1823
2208
  getRect,
@@ -1833,13 +2218,16 @@ const HeatmapElements = (props) => {
1833
2218
  });
1834
2219
  if (!isVisible)
1835
2220
  return null;
1836
- return (jsxs("div", { onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, className: "gx-hm-elements", style: { ...iframeDimensions, height }, children: [jsx(ElementMissing, { show: showMissingElement }), jsx(DefaultRankBadges, { getRect: getRect, hidden: areDefaultRanksHidden }), jsx(ElementOverlay, { type: "clicked", element: clickedElement, isSecondary: isSecondary }), jsx(ElementOverlay, { type: "hovered", element: hoveredElement, isSecondary: isSecondary, onClick: handleClick }), hoveredElement?.hash !== clickedElement?.hash && hoveredElement && (jsx(ElementCallout, { element: hoveredElement, target: `#${HOVERED_ELEMENT_ID}`, visualRef: visualRef, ...ELEMENT_CALLOUT })), shouldShowCallout && clickedElement && (jsx(ElementCallout, { element: clickedElement, target: `#${CLICKED_ELEMENT_ID}`, visualRef: visualRef, ...ELEMENT_CALLOUT }))] }));
2221
+ return (jsxs("div", { onMouseMove: (event) => {
2222
+ handleMouseMove(event);
2223
+ // handleMouseMove2(event as any);
2224
+ }, 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 }), jsx(ElementOverlay, { type: "hovered", element: hoveredElement, isSecondary: isSecondary, onClick: handleClick }), hoveredElement?.hash !== clickedElement?.hash && hoveredElement && (jsx(ElementCallout, { element: hoveredElement, target: `#${HOVERED_ELEMENT_ID}`, visualRef: visualRef, ...ELEMENT_CALLOUT })), shouldShowCallout && clickedElement && (jsx(ElementCallout, { element: clickedElement, target: `#${CLICKED_ELEMENT_ID}`, visualRef: visualRef, ...ELEMENT_CALLOUT }))] }));
1837
2225
  };
1838
2226
 
1839
2227
  const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
1840
2228
  const heatmapInfo = useHeatmapDataStore((state) => state.dataInfo);
1841
2229
  const contentWidth = useHeatmapConfigStore((state) => state.width);
1842
- const vizRef = useHeatmapVizStore((state) => state.vizRef);
2230
+ const vizRef = useHeatmapSingleStore((state) => state.vizRef);
1843
2231
  const visualizer = {
1844
2232
  get: (hash) => {
1845
2233
  if (vizRef) {
@@ -1904,8 +2292,8 @@ const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHe
1904
2292
 
1905
2293
  const VizDomRenderer = ({ mode = 'heatmap' }) => {
1906
2294
  const width = useHeatmapConfigStore((state) => state.width);
1907
- const iframeHeight = useHeatmapVizStore((state) => state.iframeHeight);
1908
- const setIframeHeight = useHeatmapVizStore((state) => state.setIframeHeight);
2295
+ const iframeHeight = useHeatmapSingleStore((state) => state.iframeHeight);
2296
+ const setIframeHeight = useHeatmapSingleStore((state) => state.setIframeHeight);
1909
2297
  const setSelectedElement = useHeatmapInteractionStore((state) => state.setSelectedElement);
1910
2298
  const wrapperRef = useRef(null);
1911
2299
  const visualRef = useRef(null);
@@ -1915,7 +2303,6 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
1915
2303
  iframeRef,
1916
2304
  visualRef,
1917
2305
  iframeHeight,
1918
- setIframeHeight,
1919
2306
  });
1920
2307
  const contentWidth = width ?? 0;
1921
2308
  const onScroll = (e) => {
@@ -1930,7 +2317,7 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
1930
2317
  useEffect(() => {
1931
2318
  return cleanUp;
1932
2319
  }, []);
1933
- return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [jsx(VizElements, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef }), jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, height: iframeHeight, scrolling: "no" })] }));
2320
+ return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [jsx(VizElements, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef }), jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, scrolling: "no" })] }));
1934
2321
  };
1935
2322
 
1936
2323
  const VizLoading = () => {
@@ -1940,12 +2327,12 @@ const VizLoading = () => {
1940
2327
  const VizDomHeatmap = () => {
1941
2328
  const controls = useHeatmapControlStore((state) => state.controls);
1942
2329
  const isRendering = useHeatmapDataStore((state) => state.isRendering);
1943
- const iframeHeight = useHeatmapVizStore((state) => state.iframeHeight);
1944
- const setIframeHeight = useHeatmapVizStore((state) => state.setIframeHeight);
1945
- const setVizRef = useHeatmapVizStore((state) => state.setVizRef);
2330
+ const iframeHeight = useHeatmapSingleStore((state) => state.iframeHeight);
2331
+ const setIframeHeight = useHeatmapSingleStore((state) => state.setIframeHeight);
2332
+ const setVizRef = useHeatmapSingleStore((state) => state.setVizRef);
1946
2333
  useEffect(() => {
1947
2334
  return () => {
1948
- setVizRef(undefined);
2335
+ setVizRef(null);
1949
2336
  setIframeHeight(0);
1950
2337
  };
1951
2338
  }, []);
@@ -1960,7 +2347,7 @@ const VizLiveRenderer = () => {
1960
2347
  const setIframeHeight = useHeatmapLiveStore((state) => state.setIframeHeight);
1961
2348
  const visualRef = useRef(null);
1962
2349
  const wrapperRef = useRef(null);
1963
- const { iframeRef } = useIframeRender();
2350
+ const { iframeRef } = useVizLiveRender();
1964
2351
  const { scaledHeight, handleScroll } = useHeatmapScale({
1965
2352
  wrapperRef,
1966
2353
  iframeRef,
@@ -1972,13 +2359,15 @@ const VizLiveRenderer = () => {
1972
2359
  const scrollTop = e.currentTarget.scrollTop;
1973
2360
  handleScroll(scrollTop);
1974
2361
  };
1975
- return (jsx(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, iframeHeight: iframeHeight, onScroll: onScroll, children: jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, height: iframeHeight, scrolling: "no", sandbox: "allow-scripts allow-same-origin" }) }));
2362
+ return (jsx(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, iframeHeight: iframeHeight, onScroll: onScroll, children: jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth,
2363
+ // height={iframeHeight}
2364
+ scrolling: "no", sandbox: "allow-scripts allow-same-origin" }) }));
1976
2365
  };
1977
2366
 
1978
2367
  const VizLiveHeatmap = () => {
1979
2368
  const controls = useHeatmapControlStore((state) => state.controls);
1980
2369
  const isRendering = useHeatmapDataStore((state) => state.isRendering);
1981
- const iframeHeight = useHeatmapVizStore((state) => state.iframeHeight);
2370
+ const iframeHeight = useHeatmapSingleStore((state) => state.iframeHeight);
1982
2371
  const wrapperHeight = useHeatmapLiveStore((state) => state.wrapperHeight);
1983
2372
  const setWrapperHeight = useHeatmapLiveStore((state) => state.setWrapperHeight);
1984
2373
  const reset = useHeatmapLiveStore((state) => state.reset);