@gemx-dev/heatmap-react 3.5.30 → 3.5.32

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 (161) hide show
  1. package/dist/esm/components/Layout/ContentMetricBar.d.ts +2 -0
  2. package/dist/esm/components/Layout/ContentMetricBar.d.ts.map +1 -0
  3. package/dist/esm/components/Layout/ContentToolbar.d.ts +2 -0
  4. package/dist/esm/components/Layout/ContentToolbar.d.ts.map +1 -0
  5. package/dist/esm/components/Layout/ContentTopBar.d.ts +2 -0
  6. package/dist/esm/components/Layout/ContentTopBar.d.ts.map +1 -0
  7. package/dist/esm/components/Layout/HeatmapLayout.d.ts +4 -4
  8. package/dist/esm/components/Layout/HeatmapLayout.d.ts.map +1 -1
  9. package/dist/esm/components/Layout/LeftSidebar.d.ts +1 -3
  10. package/dist/esm/components/Layout/LeftSidebar.d.ts.map +1 -1
  11. package/dist/esm/components/Layout/WrapperLayout.d.ts +1 -7
  12. package/dist/esm/components/Layout/WrapperLayout.d.ts.map +1 -1
  13. package/dist/esm/components/Layout/WrapperPreview.d.ts +1 -3
  14. package/dist/esm/components/Layout/WrapperPreview.d.ts.map +1 -1
  15. package/dist/esm/components/VizDom/VizDomContainer.d.ts.map +1 -1
  16. package/dist/esm/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  17. package/dist/esm/components/VizElement/ElementCallout.d.ts.map +1 -1
  18. package/dist/esm/components/VizElement/HeatmapElements.d.ts +0 -3
  19. package/dist/esm/components/VizElement/HeatmapElements.d.ts.map +1 -1
  20. package/dist/esm/components/VizElement/VizElements.d.ts +0 -1
  21. package/dist/esm/components/VizElement/VizElements.d.ts.map +1 -1
  22. package/dist/esm/configs/style.d.ts +8 -1
  23. package/dist/esm/configs/style.d.ts.map +1 -1
  24. package/dist/esm/hooks/index.d.ts +1 -0
  25. package/dist/esm/hooks/index.d.ts.map +1 -1
  26. package/dist/esm/hooks/register/index.d.ts +5 -0
  27. package/dist/esm/hooks/register/index.d.ts.map +1 -0
  28. package/dist/esm/hooks/register/useRegisterConfig.d.ts +2 -0
  29. package/dist/esm/hooks/register/useRegisterConfig.d.ts.map +1 -0
  30. package/dist/esm/hooks/register/useRegisterControl.d.ts +3 -0
  31. package/dist/esm/hooks/register/useRegisterControl.d.ts.map +1 -0
  32. package/dist/esm/hooks/register/useRegisterData.d.ts +3 -0
  33. package/dist/esm/hooks/register/useRegisterData.d.ts.map +1 -0
  34. package/dist/esm/hooks/register/useRegisterHeatmap.d.ts +3 -0
  35. package/dist/esm/hooks/register/useRegisterHeatmap.d.ts.map +1 -0
  36. package/dist/esm/hooks/vix-elements/useClickedElement.d.ts +1 -2
  37. package/dist/esm/hooks/vix-elements/useClickedElement.d.ts.map +1 -1
  38. package/dist/esm/hooks/vix-elements/useHeatmapEffects.d.ts +1 -2
  39. package/dist/esm/hooks/vix-elements/useHeatmapEffects.d.ts.map +1 -1
  40. package/dist/esm/hooks/vix-elements/useHeatmapElementPosition.d.ts +1 -2
  41. package/dist/esm/hooks/vix-elements/useHeatmapElementPosition.d.ts.map +1 -1
  42. package/dist/esm/hooks/vix-elements/useHoveredElement.d.ts +3 -4
  43. package/dist/esm/hooks/vix-elements/useHoveredElement.d.ts.map +1 -1
  44. package/dist/esm/hooks/viz-canvas/index.d.ts +2 -0
  45. package/dist/esm/hooks/viz-canvas/index.d.ts.map +1 -0
  46. package/dist/esm/hooks/viz-canvas/useClickmap.d.ts +2 -0
  47. package/dist/esm/hooks/viz-canvas/useClickmap.d.ts.map +1 -0
  48. package/dist/esm/hooks/viz-canvas/useHeatmapVizCanvas.d.ts +5 -0
  49. package/dist/esm/hooks/viz-canvas/useHeatmapVizCanvas.d.ts.map +1 -0
  50. package/dist/esm/hooks/viz-canvas/useScrollmap.d.ts +2 -0
  51. package/dist/esm/hooks/viz-canvas/useScrollmap.d.ts.map +1 -0
  52. package/dist/esm/hooks/viz-render/useHeatmapRender.d.ts +0 -2
  53. package/dist/esm/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
  54. package/dist/esm/hooks/viz-render/useHeatmapVizRender.d.ts +0 -2
  55. package/dist/esm/hooks/viz-render/useHeatmapVizRender.d.ts.map +1 -1
  56. package/dist/esm/hooks/viz-render/useReplayRender.d.ts +0 -2
  57. package/dist/esm/hooks/viz-render/useReplayRender.d.ts.map +1 -1
  58. package/dist/esm/hooks/viz-scale/useContentDimensions.d.ts.map +1 -1
  59. package/dist/esm/hooks/viz-scale/useIframeHeight.d.ts.map +1 -1
  60. package/dist/esm/hooks/viz-scale/useScaleCalculation.d.ts.map +1 -1
  61. package/dist/esm/index.js +329 -275
  62. package/dist/esm/index.mjs +329 -275
  63. package/dist/esm/stores/comp.d.ts +7 -0
  64. package/dist/esm/stores/comp.d.ts.map +1 -0
  65. package/dist/esm/stores/data.d.ts +5 -8
  66. package/dist/esm/stores/data.d.ts.map +1 -1
  67. package/dist/esm/stores/index.d.ts +3 -0
  68. package/dist/esm/stores/index.d.ts.map +1 -1
  69. package/dist/esm/stores/interaction.d.ts +14 -0
  70. package/dist/esm/stores/interaction.d.ts.map +1 -0
  71. package/dist/esm/stores/viz.d.ts +10 -0
  72. package/dist/esm/stores/viz.d.ts.map +1 -0
  73. package/dist/esm/types/control.d.ts +8 -0
  74. package/dist/esm/types/control.d.ts.map +1 -0
  75. package/dist/esm/types/heatmap.d.ts +6 -1
  76. package/dist/esm/types/heatmap.d.ts.map +1 -1
  77. package/dist/esm/ui/BoxStack/BoxStack.d.ts +1 -1
  78. package/dist/esm/ui/BoxStack/BoxStack.d.ts.map +1 -1
  79. package/dist/style.css +31 -2
  80. package/dist/umd/components/Layout/ContentMetricBar.d.ts +2 -0
  81. package/dist/umd/components/Layout/ContentMetricBar.d.ts.map +1 -0
  82. package/dist/umd/components/Layout/ContentToolbar.d.ts +2 -0
  83. package/dist/umd/components/Layout/ContentToolbar.d.ts.map +1 -0
  84. package/dist/umd/components/Layout/ContentTopBar.d.ts +2 -0
  85. package/dist/umd/components/Layout/ContentTopBar.d.ts.map +1 -0
  86. package/dist/umd/components/Layout/HeatmapLayout.d.ts +4 -4
  87. package/dist/umd/components/Layout/HeatmapLayout.d.ts.map +1 -1
  88. package/dist/umd/components/Layout/LeftSidebar.d.ts +1 -3
  89. package/dist/umd/components/Layout/LeftSidebar.d.ts.map +1 -1
  90. package/dist/umd/components/Layout/WrapperLayout.d.ts +1 -7
  91. package/dist/umd/components/Layout/WrapperLayout.d.ts.map +1 -1
  92. package/dist/umd/components/Layout/WrapperPreview.d.ts +1 -3
  93. package/dist/umd/components/Layout/WrapperPreview.d.ts.map +1 -1
  94. package/dist/umd/components/VizDom/VizDomContainer.d.ts.map +1 -1
  95. package/dist/umd/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  96. package/dist/umd/components/VizElement/ElementCallout.d.ts.map +1 -1
  97. package/dist/umd/components/VizElement/HeatmapElements.d.ts +0 -3
  98. package/dist/umd/components/VizElement/HeatmapElements.d.ts.map +1 -1
  99. package/dist/umd/components/VizElement/VizElements.d.ts +0 -1
  100. package/dist/umd/components/VizElement/VizElements.d.ts.map +1 -1
  101. package/dist/umd/configs/style.d.ts +8 -1
  102. package/dist/umd/configs/style.d.ts.map +1 -1
  103. package/dist/umd/hooks/index.d.ts +1 -0
  104. package/dist/umd/hooks/index.d.ts.map +1 -1
  105. package/dist/umd/hooks/register/index.d.ts +5 -0
  106. package/dist/umd/hooks/register/index.d.ts.map +1 -0
  107. package/dist/umd/hooks/register/useRegisterConfig.d.ts +2 -0
  108. package/dist/umd/hooks/register/useRegisterConfig.d.ts.map +1 -0
  109. package/dist/umd/hooks/register/useRegisterControl.d.ts +3 -0
  110. package/dist/umd/hooks/register/useRegisterControl.d.ts.map +1 -0
  111. package/dist/umd/hooks/register/useRegisterData.d.ts +3 -0
  112. package/dist/umd/hooks/register/useRegisterData.d.ts.map +1 -0
  113. package/dist/umd/hooks/register/useRegisterHeatmap.d.ts +3 -0
  114. package/dist/umd/hooks/register/useRegisterHeatmap.d.ts.map +1 -0
  115. package/dist/umd/hooks/vix-elements/useClickedElement.d.ts +1 -2
  116. package/dist/umd/hooks/vix-elements/useClickedElement.d.ts.map +1 -1
  117. package/dist/umd/hooks/vix-elements/useHeatmapEffects.d.ts +1 -2
  118. package/dist/umd/hooks/vix-elements/useHeatmapEffects.d.ts.map +1 -1
  119. package/dist/umd/hooks/vix-elements/useHeatmapElementPosition.d.ts +1 -2
  120. package/dist/umd/hooks/vix-elements/useHeatmapElementPosition.d.ts.map +1 -1
  121. package/dist/umd/hooks/vix-elements/useHoveredElement.d.ts +3 -4
  122. package/dist/umd/hooks/vix-elements/useHoveredElement.d.ts.map +1 -1
  123. package/dist/umd/hooks/viz-canvas/index.d.ts +2 -0
  124. package/dist/umd/hooks/viz-canvas/index.d.ts.map +1 -0
  125. package/dist/umd/hooks/viz-canvas/useClickmap.d.ts +2 -0
  126. package/dist/umd/hooks/viz-canvas/useClickmap.d.ts.map +1 -0
  127. package/dist/umd/hooks/viz-canvas/useHeatmapVizCanvas.d.ts +5 -0
  128. package/dist/umd/hooks/viz-canvas/useHeatmapVizCanvas.d.ts.map +1 -0
  129. package/dist/umd/hooks/viz-canvas/useScrollmap.d.ts +2 -0
  130. package/dist/umd/hooks/viz-canvas/useScrollmap.d.ts.map +1 -0
  131. package/dist/umd/hooks/viz-render/useHeatmapRender.d.ts +0 -2
  132. package/dist/umd/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
  133. package/dist/umd/hooks/viz-render/useHeatmapVizRender.d.ts +0 -2
  134. package/dist/umd/hooks/viz-render/useHeatmapVizRender.d.ts.map +1 -1
  135. package/dist/umd/hooks/viz-render/useReplayRender.d.ts +0 -2
  136. package/dist/umd/hooks/viz-render/useReplayRender.d.ts.map +1 -1
  137. package/dist/umd/hooks/viz-scale/useContentDimensions.d.ts.map +1 -1
  138. package/dist/umd/hooks/viz-scale/useIframeHeight.d.ts.map +1 -1
  139. package/dist/umd/hooks/viz-scale/useScaleCalculation.d.ts.map +1 -1
  140. package/dist/umd/index.js +2 -2
  141. package/dist/umd/stores/comp.d.ts +7 -0
  142. package/dist/umd/stores/comp.d.ts.map +1 -0
  143. package/dist/umd/stores/data.d.ts +5 -8
  144. package/dist/umd/stores/data.d.ts.map +1 -1
  145. package/dist/umd/stores/index.d.ts +3 -0
  146. package/dist/umd/stores/index.d.ts.map +1 -1
  147. package/dist/umd/stores/interaction.d.ts +14 -0
  148. package/dist/umd/stores/interaction.d.ts.map +1 -0
  149. package/dist/umd/stores/viz.d.ts +10 -0
  150. package/dist/umd/stores/viz.d.ts.map +1 -0
  151. package/dist/umd/types/control.d.ts +8 -0
  152. package/dist/umd/types/control.d.ts.map +1 -0
  153. package/dist/umd/types/heatmap.d.ts +6 -1
  154. package/dist/umd/types/heatmap.d.ts.map +1 -1
  155. package/dist/umd/ui/BoxStack/BoxStack.d.ts +1 -1
  156. package/dist/umd/ui/BoxStack/BoxStack.d.ts.map +1 -1
  157. package/package.json +1 -1
  158. package/dist/esm/components/Layout/ContentHeader.d.ts +0 -4
  159. package/dist/esm/components/Layout/ContentHeader.d.ts.map +0 -1
  160. package/dist/umd/components/Layout/ContentHeader.d.ts +0 -4
  161. package/dist/umd/components/Layout/ContentHeader.d.ts.map +0 -1
package/dist/esm/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  "use client"
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import { useNodesState, ReactFlow, Controls, Background } from '@xyflow/react';
4
- import { useEffect, useMemo, useState, useCallback, useRef, Fragment as Fragment$1 } from 'react';
4
+ import { useEffect, useCallback, useState, useRef, useMemo, Fragment as Fragment$1 } from 'react';
5
5
  import { create } from 'zustand';
6
6
  import { Visualizer } from '@gemx-dev/clarity-visualize';
7
+ import { createPortal } from 'react-dom';
7
8
 
8
9
  const initialNodes = { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } };
9
10
  const GraphView = ({ children, width, height }) => {
@@ -37,88 +38,162 @@ const GraphView = ({ children, width, height }) => {
37
38
  return (jsxs(ReactFlow, { nodes: nodes, nodeTypes: nodeTypes, onNodesChange: onNodesChange, debug: true, minZoom: 0.5, maxZoom: 2, fitView: true, children: [jsx(Controls, {}), jsx(Background, {})] }));
38
39
  };
39
40
 
41
+ const HEATMAP_IFRAME = {
42
+ id: 'clarity-iframe',
43
+ title: 'Clarity Session Replay',
44
+ sandbox: 'allow-same-origin allow-scripts',
45
+ scrolling: 'no',
46
+ height: '100%',
47
+ };
48
+
49
+ const HEATMAP_CONFIG = {
50
+ padding: 8,
51
+ borderWidth: 1,
52
+ borderColor: '#E3E3E3',
53
+ };
54
+ const HEATMAP_STYLE = {
55
+ viz: {
56
+ background: '#f3f3f3',
57
+ paddingBottom: `${HEATMAP_CONFIG.padding}px`,
58
+ },
59
+ wrapper: {
60
+ padding: `${HEATMAP_CONFIG.padding}px 0`,
61
+ },
62
+ };
63
+ const DEFAULT_SIDEBAR_WIDTH = 260;
64
+
65
+ const useHeatmapControlStore = create()((set, get) => {
66
+ return {
67
+ controls: {
68
+ Sidebar: null,
69
+ TopBar: null,
70
+ Toolbar: null,
71
+ MetricBar: null,
72
+ VizLoading: null,
73
+ },
74
+ registerControl: (key, control) => {
75
+ set({
76
+ controls: {
77
+ ...get().controls,
78
+ [key]: control,
79
+ },
80
+ });
81
+ },
82
+ };
83
+ });
84
+
85
+ var IHeatmapType;
86
+ (function (IHeatmapType) {
87
+ IHeatmapType["Click"] = "click";
88
+ IHeatmapType["Scroll"] = "scroll";
89
+ })(IHeatmapType || (IHeatmapType = {}));
90
+
40
91
  const useHeatmapDataStore = create()((set, get) => {
41
92
  return {
42
93
  data: undefined,
43
94
  clickmap: undefined,
44
- config: undefined,
45
- iframeHeight: 0,
46
- state: {
47
- hideSidebar: false,
95
+ config: {
96
+ sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
97
+ width: 1440,
98
+ heatmapType: IHeatmapType.Click,
48
99
  },
100
+ dataInfo: undefined,
101
+ isRendering: true,
102
+ setIsRendering: (isRendering) => set({ isRendering }),
103
+ setDataInfo: (dataInfo) => set({ dataInfo }),
49
104
  setData: (data) => set({ data }),
50
105
  setClickmap: (clickmap) => set({ clickmap }),
51
- setState: (state) => set({ state: { ...get().state, ...state } }),
52
106
  setConfig: (value) => set({ config: { ...get().config, ...value } }),
53
107
  setConfigBy: (key, value) => set({ config: { ...get().config, [key]: value } }),
54
- setIframeHeight: (iframeHeight) => set({ iframeHeight }),
55
108
  };
56
109
  });
57
110
 
58
- const BoxStack = ({ children, ...props }) => {
59
- const id = props.id;
60
- const flexDirection = props.flexDirection;
61
- const overflow = props.overflow || 'hidden';
62
- const position = props.position || 'relative';
63
- const flex = props.flex || 'none';
64
- const justifyContent = props.justifyContent;
65
- const alignItems = props.alignItems;
66
- const style = props.style || {};
67
- const gap = props.gap || 0;
68
- const height = props.height || 'auto';
69
- const styleGap = useMemo(() => {
70
- switch (flexDirection) {
71
- case 'row':
72
- return {
73
- columnGap: gap,
74
- };
75
- case 'column':
76
- return {
77
- rowGap: gap,
78
- };
79
- }
80
- }, [gap, flexDirection]);
81
- const styleProps = {
82
- display: 'flex',
83
- flexDirection,
84
- overflow,
85
- position,
86
- flex,
87
- justifyContent,
88
- alignItems,
89
- height,
90
- ...styleGap,
91
- ...style,
111
+ const useHeatmapInteractionStore = create()((set, get) => {
112
+ return {
113
+ state: {
114
+ hideSidebar: false,
115
+ },
116
+ setState: (state) => set({ state }),
117
+ selectedElement: null,
118
+ setSelectedElement: (selectedElement) => set({ selectedElement }),
119
+ hoveredElement: null,
120
+ setHoveredElement: (hoveredElement) => set({ hoveredElement }),
92
121
  };
93
- return (jsx("div", { id: id, style: styleProps, children: children }));
94
- };
122
+ });
123
+
124
+ const useHeatmapVizStore = create()((set, get) => {
125
+ return {
126
+ scale: 1,
127
+ vizRef: undefined,
128
+ iframeHeight: 0,
129
+ setScale: (scale) => set({ scale }),
130
+ setVizRef: (vizRef) => set({ vizRef }),
131
+ setIframeHeight: (iframeHeight) => set({ iframeHeight }),
132
+ };
133
+ });
95
134
 
96
- const ContentHeader = ({ children }) => {
97
- return (jsx(BoxStack, { id: "gx-hm-content-header", flexDirection: "row", alignItems: "center", overflow: "auto", children: children }));
135
+ const useRegisterConfig = () => {
136
+ const config = useHeatmapDataStore((state) => state.config);
137
+ const setIsRendering = useHeatmapDataStore((state) => state.setIsRendering);
138
+ useEffect(() => {
139
+ setIsRendering(true);
140
+ setTimeout(() => {
141
+ setIsRendering(false);
142
+ }, 1000);
143
+ }, [config]);
98
144
  };
99
145
 
100
- const HEATMAP_IFRAME = {
101
- id: 'clarity-iframe',
102
- title: 'Clarity Session Replay',
103
- sandbox: 'allow-same-origin allow-scripts',
104
- scrolling: 'no',
105
- height: '100%',
146
+ const useRegisterControl = (control) => {
147
+ const registerControl = useHeatmapControlStore((state) => state.registerControl);
148
+ registerControl('Sidebar', control.Sidebar);
149
+ registerControl('TopBar', control.TopBar);
150
+ registerControl('Toolbar', control.Toolbar);
151
+ registerControl('MetricBar', control.MetricBar);
152
+ registerControl('VizLoading', control.VizLoading);
106
153
  };
107
154
 
108
- const HEATMAP_CONFIG = {
109
- paddingBlock: 0,
155
+ const useRegisterData = (data, dataInfo) => {
156
+ const setData = useHeatmapDataStore((state) => state.setData);
157
+ const setIsRendering = useHeatmapDataStore((state) => state.setIsRendering);
158
+ const setDataInfo = useHeatmapDataStore((state) => state.setDataInfo);
159
+ const handleSetData = useCallback((data) => {
160
+ if (!data)
161
+ return;
162
+ setData(data);
163
+ setIsRendering(false);
164
+ }, [data]);
165
+ const handleSetDataInfo = useCallback((dataInfo) => {
166
+ if (!dataInfo)
167
+ return;
168
+ setDataInfo(dataInfo);
169
+ }, [setDataInfo]);
170
+ useEffect(() => {
171
+ handleSetData(data);
172
+ }, [data]);
173
+ useEffect(() => {
174
+ handleSetDataInfo(dataInfo);
175
+ }, [dataInfo]);
110
176
  };
111
- const HEATMAP_STYLE = {
112
- wrapper: {
113
- padding: `${HEATMAP_CONFIG.paddingBlock}px 0`,
114
- },
177
+
178
+ const useRegisterHeatmap = (clickmap) => {
179
+ const setClickmap = useHeatmapDataStore((state) => state.setClickmap);
180
+ const handleSetClickmap = useCallback((clickmap) => {
181
+ if (!clickmap)
182
+ return;
183
+ setClickmap(clickmap);
184
+ }, [clickmap]);
185
+ useEffect(() => {
186
+ handleSetClickmap(clickmap);
187
+ }, [clickmap]);
115
188
  };
116
189
 
117
- const useClickedElement = ({ selectedElement, heatmapInfo, getRect }) => {
190
+ const useClickedElement = ({ heatmapInfo, getRect }) => {
191
+ const selectedElement = useHeatmapInteractionStore((state) => state.selectedElement);
118
192
  const [clickedElement, setClickedElement] = useState(null);
119
193
  const [showMissingElement, setShowMissingElement] = useState(false);
120
194
  const [shouldShowCallout, setShouldShowCallout] = useState(false);
121
195
  useEffect(() => {
196
+ console.log(`🚀 🐥 ~ useClickedElement ~ heatmapInfo?.elementMapInfo:`, heatmapInfo?.elementMapInfo);
122
197
  if (!selectedElement || !heatmapInfo?.elementMapInfo) {
123
198
  setClickedElement(null);
124
199
  setShowMissingElement(false);
@@ -162,7 +237,8 @@ const useClickedElement = ({ selectedElement, heatmapInfo, getRect }) => {
162
237
  return { clickedElement, showMissingElement, shouldShowCallout, setShouldShowCallout };
163
238
  };
164
239
 
165
- const useHeatmapEffects = ({ isVisible, isElementSidebarOpen, selectedElement, setShouldShowCallout, resetAll, }) => {
240
+ const useHeatmapEffects = ({ isVisible, isElementSidebarOpen, setShouldShowCallout, resetAll, }) => {
241
+ const selectedElement = useHeatmapInteractionStore((state) => state.selectedElement);
166
242
  // Reset khi ẩn
167
243
  useEffect(() => {
168
244
  if (!isVisible)
@@ -195,10 +271,6 @@ function getElementLayout(element) {
195
271
  function formatPercentage(value, decimals = 2) {
196
272
  return value.toFixed(decimals);
197
273
  }
198
- function getSimpleSelector(selector) {
199
- const parts = selector.split(' > ');
200
- return parts[parts.length - 1] || selector;
201
- }
202
274
  function calculateRankPosition(rect, widthScale) {
203
275
  const top = rect.top <= 18 ? rect.top + 3 : rect.top - 18;
204
276
  const left = rect.left <= 18 ? rect.left + 3 : rect.left - 18;
@@ -209,7 +281,8 @@ function calculateRankPosition(rect, widthScale) {
209
281
  };
210
282
  }
211
283
 
212
- const useHeatmapElementPosition = ({ iframeRef, parentRef, visualizer, heatmapWidth, iframeHeight, widthScale, projectId, }) => {
284
+ const useHeatmapElementPosition = ({ iframeRef, parentRef, visualizer, heatmapWidth, widthScale, projectId, }) => {
285
+ const iframeHeight = useHeatmapVizStore((state) => state.iframeHeight);
213
286
  return useCallback((element) => {
214
287
  const hash = element?.hash;
215
288
  if (!iframeRef.current?.contentDocument || !hash || !visualizer)
@@ -254,8 +327,10 @@ const debounce = (fn, delay) => {
254
327
  timeout = setTimeout(() => fn(...args), delay);
255
328
  };
256
329
  };
257
- const useHoveredElement = ({ iframeRef, heatmapInfo, widthScale, getRect, onSelect, }) => {
258
- const [hoveredElement, setHoveredElement] = useState(null);
330
+ const useHoveredElement = ({ iframeRef, heatmapInfo, widthScale, getRect }) => {
331
+ const hoveredElement = useHeatmapInteractionStore((state) => state.hoveredElement);
332
+ const setHoveredElement = useHeatmapInteractionStore((state) => state.setHoveredElement);
333
+ const onSelect = useHeatmapInteractionStore((state) => state.setSelectedElement);
259
334
  const handleMouseMove = useCallback(debounce((event) => {
260
335
  if (!iframeRef.current?.contentDocument || !heatmapInfo?.elementMapInfo) {
261
336
  setHoveredElement(null);
@@ -264,8 +339,6 @@ const useHoveredElement = ({ iframeRef, heatmapInfo, widthScale, getRect, onSele
264
339
  const iframe = iframeRef.current;
265
340
  const iframeRect = iframe.getBoundingClientRect();
266
341
  let x = event.clientX - iframeRect.left;
267
- console.log(`🚀 🐥 ~ useHoveredElement ~ iframeRect.left:`, iframeRect.left);
268
- console.log(`🚀 🐥 ~ useHoveredElement ~ event.clientX:`, event.clientX);
269
342
  let y = event.clientY - iframeRect.top;
270
343
  if (widthScale !== 1) {
271
344
  x /= widthScale;
@@ -276,9 +349,7 @@ const useHoveredElement = ({ iframeRef, heatmapInfo, widthScale, getRect, onSele
276
349
  setHoveredElement(null);
277
350
  return;
278
351
  }
279
- let targetElement = null;
280
- // Best: dùng caretPositionFromPoint nếu có (Chrome, Safari)
281
- targetElement = getElementAtPoint(doc, x, y);
352
+ let targetElement = getElementAtPoint(doc, x, y) || null;
282
353
  if (!targetElement) {
283
354
  targetElement = doc.elementFromPoint(x, y);
284
355
  }
@@ -286,7 +357,6 @@ const useHoveredElement = ({ iframeRef, heatmapInfo, widthScale, getRect, onSele
286
357
  setHoveredElement(null);
287
358
  return;
288
359
  }
289
- // Lấy hash từ nhiều attribute khả dĩ
290
360
  const hash = targetElement.getAttribute('data-clarity-hash') ||
291
361
  targetElement.getAttribute('data-clarity-hashalpha') ||
292
362
  targetElement.getAttribute('data-clarity-hashbeta');
@@ -339,107 +409,40 @@ const getElementAtPoint = (doc, x, y) => {
339
409
  return element;
340
410
  };
341
411
 
342
- const recreateIframe = (iframeRef, config) => {
343
- const container = iframeRef.current?.parentElement;
344
- if (!container)
345
- return;
346
- const oldIframe = iframeRef.current;
347
- if (!oldIframe?.contentDocument?.body.innerHTML) {
348
- return oldIframe;
349
- }
350
- if (oldIframe && oldIframe.parentElement) {
351
- oldIframe.parentElement.removeChild(oldIframe);
352
- }
353
- const newIframe = document.createElement('iframe');
354
- newIframe.id = HEATMAP_IFRAME.id;
355
- newIframe.title = HEATMAP_IFRAME.title;
356
- newIframe.sandbox = HEATMAP_IFRAME.sandbox;
357
- newIframe.scrolling = HEATMAP_IFRAME.scrolling;
358
- newIframe.width = config?.width ? `${config.width}px` : '100%';
359
- // Append to container
360
- container.appendChild(newIframe);
361
- // Update ref
362
- iframeRef.current = newIframe;
363
- return newIframe;
364
- };
365
-
366
- function isMobileDevice(userAgent) {
367
- if (!userAgent)
368
- return false;
369
- return /android|webos|iphone|ipad|ipod|blackberry|windows phone|opera mini|iemobile|mobile|silk|fennec|bada|tizen|symbian|nokia|palmsource|meego|sailfish|kindle|playbook|bb10|rim/i.test(userAgent);
370
- }
371
-
372
412
  const useHeatmapRender = () => {
373
413
  const data = useHeatmapDataStore((state) => state.data);
374
414
  const config = useHeatmapDataStore((state) => state.config);
375
- const setConfig = useHeatmapDataStore((state) => state.setConfig);
376
- const clickmap = useHeatmapDataStore((state) => state.clickmap);
377
- const visualizerRef = useRef(null);
415
+ const setVizRef = useHeatmapVizStore((state) => state.setVizRef);
378
416
  const iframeRef = useRef(null);
379
- const initializeVisualizer = useCallback((envelope, userAgent) => {
380
- const iframe = iframeRef.current;
381
- if (!iframe?.contentWindow)
382
- return null;
383
- const visualizer = new Visualizer();
384
- const mobile = isMobileDevice(userAgent);
385
- visualizer.setup(iframe.contentWindow, {
386
- version: envelope.version,
387
- onresize: (width) => {
388
- setConfig({ width });
389
- },
390
- mobile,
391
- vNext: true,
392
- locale: 'en-us',
393
- });
394
- return visualizer;
395
- }, []);
396
- // Process and render heatmap HTML
397
417
  const renderHeatmap = useCallback(async (payloads) => {
398
418
  if (!payloads || payloads.length === 0)
399
419
  return;
400
- let visualizer = new Visualizer();
401
- const iframe = recreateIframe(iframeRef, config);
402
- // const merged = visualizer.merge(payloads);
403
- // setIframeHeight(Number(iframeRef.current?.height || 0));
404
- // for (const decoded of payloads) {
405
- // // Initialize on first sequence
406
- // if (decoded.envelope.sequence === 1) {
407
- // const userAgent = (decoded.dimension?.[0]?.data[0]?.[0] as string) || '';
408
- // visualizer = initializeVisualizer(decoded.envelope as any, userAgent);
409
- // if (!visualizer) return;
410
- // visualizerRef.current = visualizer;
411
- // }
412
- // if (!visualizer) continue;
413
- // // Merge and process DOM
414
- // const merged = visualizer.merge([decoded]);
415
- // visualizer.dom(merged.dom);
416
- // }
417
- // Render static HTML
418
- if (visualizer && iframe?.contentWindow) {
419
- await visualizer.html(payloads, iframe.contentWindow);
420
- visualizerRef.current = visualizer;
421
- }
422
- }, [initializeVisualizer]);
420
+ const visualizer = new Visualizer();
421
+ const iframe = iframeRef.current;
422
+ if (!iframe?.contentWindow)
423
+ return;
424
+ await visualizer.html(payloads, iframe.contentWindow);
425
+ setVizRef(visualizer);
426
+ }, []);
423
427
  useEffect(() => {
424
428
  if (!data || data.length === 0)
425
429
  return;
426
430
  renderHeatmap(data);
427
431
  return () => {
428
- visualizerRef.current = null;
432
+ setVizRef(null);
429
433
  };
430
- }, [config, data, renderHeatmap]);
431
- useEffect(() => {
432
- if (!visualizerRef.current || !clickmap || clickmap.length === 0)
433
- return;
434
- visualizerRef.current.clearmap();
435
- visualizerRef.current?.clickmap(clickmap);
436
- }, [clickmap]);
434
+ }, [config, data]);
437
435
  return {
438
436
  iframeRef,
439
- clarityVisualizer: visualizerRef.current,
440
437
  };
441
438
  };
442
439
 
440
+ function isMobileDevice(userAgent) {
441
+ if (!userAgent)
442
+ return false;
443
+ return /android|webos|iphone|ipad|ipod|blackberry|windows phone|opera mini|iemobile|mobile|silk|fennec|bada|tizen|symbian|nokia|palmsource|meego|sailfish|kindle|playbook|bb10|rim/i.test(userAgent);
444
+ }
445
+
443
446
  function sortEvents(a, b) {
444
447
  return a.time - b.time;
445
448
  }
@@ -558,7 +561,6 @@ const useReplayRender = () => {
558
561
  return {
559
562
  iframeRef,
560
563
  isPlaying: isPlayingRef.current,
561
- clarityVisualizer: visualizerRef.current,
562
564
  play,
563
565
  pause,
564
566
  };
@@ -607,23 +609,21 @@ const useContainerDimensions = (props) => {
607
609
 
608
610
  const useContentDimensions = (props) => {
609
611
  const { iframeRef, config } = props;
610
- const [contentWidth, setContentWidth] = useState(0);
611
612
  useEffect(() => {
612
- if (config?.width) {
613
- if (iframeRef.current) {
614
- iframeRef.current.width = `${config.width}px`;
615
- }
616
- setContentWidth(config.width);
617
- }
613
+ if (!config?.width)
614
+ return;
615
+ if (!iframeRef.current)
616
+ return;
617
+ iframeRef.current.width = `${config.width}px`;
618
618
  }, [config?.width, iframeRef]);
619
- return { contentWidth };
619
+ return { contentWidth: config?.width ?? 0 };
620
620
  };
621
621
 
622
622
  // Hook 3: Iframe Height Observer
623
623
  const useIframeHeight = (props) => {
624
624
  const { iframeRef, contentWidth } = props;
625
- const iframeHeight = useHeatmapDataStore((state) => state.iframeHeight);
626
- const setIframeHeight = useHeatmapDataStore((state) => state.setIframeHeight);
625
+ const iframeHeight = useHeatmapVizStore((state) => state.iframeHeight);
626
+ const setIframeHeight = useHeatmapVizStore((state) => state.setIframeHeight);
627
627
  const resizeObserverRef = useRef(null);
628
628
  const mutationObserverRef = useRef(null);
629
629
  const updateIframeHeight = useCallback(() => {
@@ -645,16 +645,14 @@ const useIframeHeight = (props) => {
645
645
  console.warn('Cannot measure iframe content:', error);
646
646
  }
647
647
  }, [iframeRef, setIframeHeight]);
648
- // Trigger height update when content width changes
649
648
  useEffect(() => {
650
649
  if (contentWidth > 0) {
651
- // Delay to allow iframe content to reflow after width change
652
650
  const timeoutId = setTimeout(() => {
653
651
  updateIframeHeight();
654
652
  }, 100);
655
653
  return () => clearTimeout(timeoutId);
656
654
  }
657
- }, [contentWidth, updateIframeHeight]);
655
+ }, [contentWidth]);
658
656
  useEffect(() => {
659
657
  const iframe = iframeRef.current;
660
658
  if (!iframe)
@@ -714,11 +712,12 @@ const useIframeHeight = (props) => {
714
712
  };
715
713
 
716
714
  const useScaleCalculation = (props) => {
715
+ const scale = useHeatmapVizStore((state) => state.scale);
716
+ const setScale = useHeatmapVizStore((state) => state.setScale);
717
717
  const { containerWidth, contentWidth } = props;
718
- const [scale, setScale] = useState(1);
719
718
  useEffect(() => {
720
719
  if (containerWidth > 0 && contentWidth > 0) {
721
- const availableWidth = containerWidth - HEATMAP_CONFIG['paddingBlock'] * 2;
720
+ const availableWidth = containerWidth - HEATMAP_CONFIG['padding'] * 2;
722
721
  const calculatedScale = Math.min(availableWidth / contentWidth, 1);
723
722
  setScale(calculatedScale);
724
723
  }
@@ -771,6 +770,84 @@ const useHeatmapScale = (props) => {
771
770
  };
772
771
  };
773
772
 
773
+ const BoxStack = ({ children, ...props }) => {
774
+ const id = props.id;
775
+ const flexDirection = props.flexDirection;
776
+ const overflow = props.overflow || 'hidden';
777
+ const position = props.position || 'relative';
778
+ const flex = props.flex || 'none';
779
+ const justifyContent = props.justifyContent;
780
+ const alignItems = props.alignItems;
781
+ const style = props.style || {};
782
+ const gap = props.gap || 0;
783
+ const height = props.height || 'auto';
784
+ const styleGap = useMemo(() => {
785
+ switch (flexDirection) {
786
+ case 'row':
787
+ return {
788
+ columnGap: gap,
789
+ };
790
+ case 'column':
791
+ return {
792
+ rowGap: gap,
793
+ };
794
+ }
795
+ }, [gap, flexDirection]);
796
+ const styleProps = {
797
+ display: 'flex',
798
+ flexDirection,
799
+ overflow,
800
+ position,
801
+ flex,
802
+ justifyContent,
803
+ alignItems,
804
+ height,
805
+ ...styleGap,
806
+ ...style,
807
+ };
808
+ return (jsx("div", { id: id, style: styleProps, children: children }));
809
+ };
810
+
811
+ const ContentTopBar = () => {
812
+ const controls = useHeatmapControlStore((state) => state.controls);
813
+ return (jsx(BoxStack, { id: "gx-hm-content-header", flexDirection: "row", alignItems: "center", overflow: "auto", style: {
814
+ borderBottom: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
815
+ }, children: controls.TopBar ?? null }));
816
+ };
817
+
818
+ const useClickmap = () => {
819
+ const [isInitialized, setIsInitialized] = useState(false);
820
+ const clickmap = useHeatmapDataStore((state) => state.clickmap);
821
+ const vizRef = useHeatmapVizStore((state) => state.vizRef);
822
+ useEffect(() => {
823
+ if (isInitialized)
824
+ return;
825
+ if (!vizRef || !clickmap || clickmap.length === 0)
826
+ return;
827
+ vizRef.clearmap();
828
+ vizRef?.clickmap(clickmap);
829
+ setIsInitialized(true);
830
+ }, [vizRef, clickmap]);
831
+ return {};
832
+ };
833
+
834
+ const useScrollmap = () => {
835
+ useHeatmapDataStore((state) => state.clickmap);
836
+ return {};
837
+ };
838
+
839
+ const useHeatmapVizCanvas = ({ type }) => {
840
+ const heatmapRender = useMemo(() => {
841
+ switch (type) {
842
+ case IHeatmapType.Click:
843
+ return useClickmap;
844
+ case IHeatmapType.Scroll:
845
+ return useScrollmap;
846
+ }
847
+ }, [type]);
848
+ return heatmapRender?.();
849
+ };
850
+
774
851
  const CLICKED_ELEMENT_ID = 'clickedElement';
775
852
  const SECONDARY_CLICKED_ELEMENT_ID = 'secondaryClickedElementID';
776
853
  const HOVERED_ELEMENT_ID = 'hoveredElement';
@@ -784,7 +861,6 @@ const ElementCallout = ({ element, target, totalClicks, isSecondary, isRecording
784
861
  placement: 'top',
785
862
  });
786
863
  const percentage = formatPercentage(((element.clicks ?? 0) / totalClicks) * 100, 2);
787
- // Calculate callout position
788
864
  useEffect(() => {
789
865
  const targetElement = document.querySelector(target);
790
866
  const calloutElement = calloutRef.current;
@@ -800,30 +876,25 @@ const ElementCallout = ({ element, target, totalClicks, isSecondary, isRecording
800
876
  let top = 0;
801
877
  let left = 0;
802
878
  let placement = 'top';
803
- // Try positions in order: top, bottom, right, left
804
879
  const positions = [
805
- // Top
806
880
  {
807
881
  placement: 'top',
808
882
  top: targetRect.top - calloutRect.height - padding - arrowSize,
809
883
  left: targetRect.left + targetRect.width / 2 - calloutRect.width / 2,
810
884
  valid: targetRect.top - calloutRect.height - padding - arrowSize > 0,
811
885
  },
812
- // Bottom
813
886
  {
814
887
  placement: 'bottom',
815
888
  top: targetRect.bottom + padding + arrowSize,
816
889
  left: targetRect.left + targetRect.width / 2 - calloutRect.width / 2,
817
890
  valid: targetRect.bottom + calloutRect.height + padding + arrowSize < viewportHeight,
818
891
  },
819
- // Right
820
892
  {
821
893
  placement: 'right',
822
894
  top: targetRect.top + targetRect.height / 2 - calloutRect.height / 2,
823
895
  left: targetRect.right + padding + arrowSize,
824
896
  valid: targetRect.right + calloutRect.width + padding + arrowSize < viewportWidth,
825
897
  },
826
- // Left
827
898
  {
828
899
  placement: 'left',
829
900
  top: targetRect.top + targetRect.height / 2 - calloutRect.height / 2,
@@ -841,9 +912,7 @@ const ElementCallout = ({ element, target, totalClicks, isSecondary, isRecording
841
912
  top = Math.max(padding, Math.min(top, viewportHeight - calloutRect.height - padding));
842
913
  setPosition({ top, left, placement });
843
914
  };
844
- // Initial calculation
845
915
  calculatePosition();
846
- // Recalculate on scroll/resize
847
916
  const handleUpdate = () => {
848
917
  requestAnimationFrame(calculatePosition);
849
918
  };
@@ -856,12 +925,12 @@ const ElementCallout = ({ element, target, totalClicks, isSecondary, isRecording
856
925
  parentRef?.current?.removeEventListener('scroll', handleUpdate);
857
926
  };
858
927
  }, [target, parentRef]);
859
- (jsxs("div", { ref: calloutRef, className: `clarity-callout clarity-callout--${position.placement} ${isSecondary ? 'clarity-callout--secondary' : ''}`, style: {
928
+ const calloutContent = (jsxs("div", { ref: calloutRef, className: `clarity-callout clarity-callout--${position.placement} ${isSecondary ? 'clarity-callout--secondary' : ''}`, style: {
860
929
  position: 'fixed',
861
930
  top: position.top,
862
931
  left: position.left,
863
932
  zIndex: 2147483647,
864
- }, "aria-live": "assertive", role: "tooltip", children: [jsx("div", { className: "clarity-callout__arrow" }), jsxs("div", { className: "clarity-callout__content", children: [jsx("div", { className: "clarity-callout__rank", children: element.rank }), jsxs("div", { className: "clarity-callout__stats", children: [jsx("div", { className: "clarity-callout__label", children: "Clicks" }), jsxs("div", { className: "clarity-callout__value", children: [jsx("span", { className: "clarity-callout__count", children: element.clicks?.toLocaleString(language) }), jsxs("span", { className: "clarity-callout__percentage", children: ["(", percentage, "%)"] })] })] }), !isRecordingView && !isCompareMode && (jsxs("button", { className: "clarity-callout__button", "data-clarity-id": "viewRecordingClickedFromHeatmapIframe", "data-element-hash": element.hash, "data-selector": getSimpleSelector(element.selector), "data-device-type": deviceType, "data-heatmap-type": heatmapType, children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: jsx("path", { d: "M5 3L11 8L5 13V3Z", fill: "currentColor" }) }), "View Recording"] }))] }), jsx("style", { children: `
933
+ }, "aria-live": "assertive", role: "tooltip", children: [jsx("div", { className: "clarity-callout__arrow" }), jsx("div", { className: "clarity-callout__content", children: jsxs("div", { className: "clarity-callout__stats", children: [jsx("div", { className: "clarity-callout__label", children: "Clicks" }), jsxs("div", { className: "clarity-callout__value", children: [jsx("span", { className: "clarity-callout__count", children: element.clicks?.toLocaleString(language) }), jsxs("span", { className: "clarity-callout__percentage", children: ["(", percentage, "%)"] })] })] }) }), jsx("style", { children: `
865
934
  .clarity-callout {
866
935
  background: white;
867
936
  border-radius: 8px;
@@ -1005,20 +1074,7 @@ const ElementCallout = ({ element, target, totalClicks, isSecondary, isRecording
1005
1074
  height: 14px;
1006
1075
  }
1007
1076
  ` })] }));
1008
- const [portalContainer, setPortalContainer] = useState(null);
1009
- useEffect(() => {
1010
- const container = document.querySelector('#gx-hm-project-portal');
1011
- if (container instanceof HTMLElement) {
1012
- setPortalContainer(container);
1013
- }
1014
- else {
1015
- console.warn('Portal container #gx-hm-project-portal not found!');
1016
- }
1017
- }, []);
1018
- if (!portalContainer) {
1019
- return jsx(Fragment, {});
1020
- }
1021
- // return createPortal(calloutContent, portalContainer);
1077
+ return createPortal(calloutContent, document.body);
1022
1078
  };
1023
1079
 
1024
1080
  const RankBadge = ({ index, elementRect, widthScale, clickOnElement, }) => {
@@ -1079,84 +1135,64 @@ const MissingElementMessage = ({ widthScale }) => {
1079
1135
  };
1080
1136
 
1081
1137
  const HeatmapElements = (props) => {
1082
- const { iframeRef, parentRef, visualizer, heatmapInfo, widthScale, iframeHeight, iframeDimensions, selectedElement, isElementSidebarOpen, isVisible = true, selectElement, areDefaultRanksHidden, isSecondary, ...rest } = props;
1138
+ const height = useHeatmapVizStore((state) => state.iframeHeight);
1139
+ const { heatmapInfo, iframeRef, parentRef, visualizer, widthScale, iframeDimensions, isElementSidebarOpen, isVisible = true, areDefaultRanksHidden, isSecondary, ...rest } = props;
1083
1140
  const getRect = useHeatmapElementPosition({
1084
1141
  iframeRef,
1085
1142
  parentRef,
1086
1143
  visualizer,
1087
1144
  heatmapWidth: heatmapInfo?.width,
1088
- iframeHeight,
1089
1145
  widthScale,
1090
1146
  projectId: props.projectId,
1091
1147
  });
1092
1148
  const { clickedElement, showMissingElement, shouldShowCallout, setShouldShowCallout } = useClickedElement({
1093
- selectedElement,
1094
1149
  heatmapInfo,
1095
1150
  getRect,
1096
1151
  });
1152
+ console.log(`🚀 🐥 ~ HeatmapElements ~ clickedElement:`, clickedElement);
1097
1153
  const { hoveredElement, handleMouseMove, handleMouseLeave, handleClick } = useHoveredElement({
1098
1154
  iframeRef,
1099
1155
  heatmapInfo,
1100
1156
  getRect,
1101
- onSelect: selectElement,
1102
1157
  widthScale,
1103
1158
  });
1104
1159
  const resetAll = () => {
1105
- // nếu cần reset thêm state ở đây
1106
1160
  // setShouldShowCallout(false);
1107
1161
  };
1108
1162
  useHeatmapEffects({
1109
1163
  isVisible,
1110
1164
  isElementSidebarOpen,
1111
- selectedElement,
1112
1165
  setShouldShowCallout,
1113
1166
  resetAll,
1114
1167
  });
1115
1168
  if (!isVisible)
1116
1169
  return null;
1117
1170
  const top10 = heatmapInfo?.sortedElements?.slice(0, 10) ?? [];
1118
- return (jsxs("div", { onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, className: "heatmapElements gx-hm-elements", style: iframeDimensions, children: [jsx(DefaultRankBadges, { elements: top10, getRect: getRect, widthScale: widthScale, hidden: areDefaultRanksHidden }), jsx(ClickedElementOverlay, { widthScale: widthScale, element: clickedElement, shouldShowCallout: shouldShowCallout, isSecondary: isSecondary, targetId: isSecondary ? SECONDARY_CLICKED_ELEMENT_ID : CLICKED_ELEMENT_ID, ...rest }), showMissingElement && jsx(MissingElementMessage, { widthScale: widthScale }), jsx(HoveredElementOverlay, { element: hoveredElement, onClick: handleClick, isSecondary: isSecondary, targetId: isSecondary ? SECONDARY_HOVERED_ELEMENT_ID : HOVERED_ELEMENT_ID, totalClicks: heatmapInfo?.totalClicks ?? 1 }), hoveredElement !== clickedElement && hoveredElement && (jsx(ElementCallout, { element: hoveredElement, target: `#${props.isSecondary ? SECONDARY_HOVERED_ELEMENT_ID : HOVERED_ELEMENT_ID}`, totalClicks: props.heatmapInfo?.totalClicks ?? 1, isSecondary: props.isSecondary, parentRef: props.parentRef }))] }));
1171
+ return (jsxs("div", { onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, className: "heatmapElements gx-hm-elements", style: { ...iframeDimensions, height }, children: [jsx(DefaultRankBadges, { elements: top10, getRect: getRect, widthScale: widthScale, hidden: areDefaultRanksHidden }), jsx(ClickedElementOverlay, { widthScale: widthScale, element: clickedElement, shouldShowCallout: shouldShowCallout, isSecondary: isSecondary, targetId: isSecondary ? SECONDARY_CLICKED_ELEMENT_ID : CLICKED_ELEMENT_ID, ...rest }), showMissingElement && jsx(MissingElementMessage, { widthScale: widthScale }), jsx(HoveredElementOverlay, { element: hoveredElement, onClick: handleClick, isSecondary: isSecondary, targetId: isSecondary ? SECONDARY_HOVERED_ELEMENT_ID : HOVERED_ELEMENT_ID, totalClicks: heatmapInfo?.totalClicks ?? 1 }), hoveredElement?.hash !== clickedElement?.hash && hoveredElement && (jsx(ElementCallout, { element: hoveredElement, target: `#${props.isSecondary ? SECONDARY_HOVERED_ELEMENT_ID : HOVERED_ELEMENT_ID}`, totalClicks: props.heatmapInfo?.totalClicks ?? 1, isSecondary: props.isSecondary, parentRef: props.parentRef }))] }));
1119
1172
  };
1120
1173
 
1121
- const VizElements = ({ width, height, iframeRef, wrapperRef, widthScale, }) => {
1122
- const heatmapInfo = {
1123
- sortedElements: [
1124
- {
1125
- hash: '9ebwu6a3',
1126
- selector: 'Join our email list',
1127
- },
1128
- {
1129
- hash: '350hde5d4',
1130
- selector: 'Products',
1131
- },
1132
- ],
1133
- elementMapInfo: {
1134
- '9ebwu6a3': {
1135
- totalclicks: 4,
1136
- hash: '9ebwu6a3',
1137
- },
1138
- '350hde5d4': {
1139
- totalclicks: 4,
1140
- hash: '350hde5d4',
1141
- },
1142
- },
1143
- totalClicks: 8,
1144
- };
1174
+ const VizElements = ({ width, iframeRef, wrapperRef, widthScale, }) => {
1175
+ const heatmapInfo = useHeatmapDataStore((state) => state.dataInfo);
1145
1176
  const visualizer = {
1146
1177
  get: (hash) => {
1147
1178
  const doc = iframeRef.current?.contentDocument;
1148
1179
  if (!doc)
1149
1180
  return null;
1150
- // Find element by hash attribute
1151
- return doc.querySelector(`[data-clarity-hashalpha="${hash}"]`);
1181
+ const elmHashAlpha = doc.querySelector(`[data-clarity-hashalpha="${hash}"]`);
1182
+ if (elmHashAlpha) {
1183
+ return elmHashAlpha;
1184
+ }
1185
+ const elmHash = doc.querySelector(`[data-clarity-hash="${hash}"]`);
1186
+ if (elmHash) {
1187
+ return elmHash;
1188
+ }
1189
+ return null;
1152
1190
  },
1153
1191
  };
1154
- const [selectedElement, setSelectedElement] = useState(null);
1155
1192
  if (!iframeRef.current)
1156
1193
  return null;
1157
- return (jsx(HeatmapElements, { visualizer: visualizer, iframeRef: iframeRef, parentRef: wrapperRef, iframeHeight: window.innerHeight, widthScale: widthScale, heatmapInfo: heatmapInfo, selectedElement: selectedElement, selectElement: setSelectedElement, isVisible: true, iframeDimensions: {
1194
+ return (jsx(HeatmapElements, { visualizer: visualizer, iframeRef: iframeRef, parentRef: wrapperRef, widthScale: widthScale, heatmapInfo: heatmapInfo, isVisible: true, iframeDimensions: {
1158
1195
  width,
1159
- height,
1160
1196
  position: 'absolute',
1161
1197
  top: 0,
1162
1198
  left: 0,
@@ -1195,6 +1231,7 @@ const ReplayControls = () => {
1195
1231
 
1196
1232
  const VizDomRenderer = ({ mode = 'heatmap' }) => {
1197
1233
  const config = useHeatmapDataStore((state) => state.config);
1234
+ const setIframeHeight = useHeatmapVizStore((state) => state.setIframeHeight);
1198
1235
  const wrapperRef = useRef(null);
1199
1236
  const visualRef = useRef(null);
1200
1237
  const { iframeRef } = useHeatmapVizRender(mode);
@@ -1207,45 +1244,66 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
1207
1244
  const scrollTop = e.currentTarget.scrollTop;
1208
1245
  handleScroll(scrollTop);
1209
1246
  };
1247
+ useHeatmapVizCanvas({ type: config?.heatmapType });
1248
+ const cleanUp = () => {
1249
+ setIframeHeight(0);
1250
+ };
1251
+ useEffect(() => {
1252
+ return cleanUp;
1253
+ }, []);
1210
1254
  return (jsxs("div", { ref: visualRef, className: "gx-hm-visual", onScroll: onScroll, style: {
1211
1255
  overflow: 'hidden auto',
1212
1256
  display: 'flex',
1213
1257
  position: 'relative',
1214
1258
  justifyContent: 'center',
1215
1259
  flex: 1,
1216
- backgroundColor: '#fff',
1217
- // borderRadius: '8px',
1260
+ // paddingBottom: HEATMAP_STYLE['viz']['paddingBottom'],
1261
+ background: '#fff',
1218
1262
  }, children: [jsx("div", { className: "gx-hm-visual-unscaled", style: {
1219
1263
  width: '100%',
1220
1264
  display: 'flex',
1221
1265
  justifyContent: 'center',
1222
1266
  alignItems: 'flex-start',
1223
- height: scaledHeight > 0 ? `${scaledHeight + HEATMAP_CONFIG['paddingBlock']}px` : 'auto',
1267
+ height: scaledHeight > 0 ? `${scaledHeight + HEATMAP_CONFIG['padding'] * 2}px` : 'auto',
1224
1268
  padding: HEATMAP_STYLE['wrapper']['padding'],
1269
+ paddingBottom: HEATMAP_STYLE['viz']['paddingBottom'],
1270
+ background: HEATMAP_STYLE['viz']['background'],
1225
1271
  }, children: jsxs("div", { className: "gx-hm-wrapper", ref: wrapperRef, style: {
1226
1272
  width: contentWidth,
1227
1273
  height: iframeHeight,
1228
1274
  transform: `scale(${scale})`,
1229
1275
  transformOrigin: 'top center',
1230
- }, children: [jsx(VizElements, { width: contentWidth, height: iframeHeight, widthScale: scale, iframeRef: iframeRef, wrapperRef: wrapperRef }), jsx("iframe", {
1231
- // key={iframeKey}
1232
- ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, height: iframeHeight, scrolling: "no" })] }) }), mode === 'replay' && jsx(ReplayControls, {})] }));
1276
+ paddingBlockEnd: '0',
1277
+ }, children: [jsx(VizElements, { width: contentWidth, widthScale: scale, iframeRef: iframeRef, wrapperRef: wrapperRef }), jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, height: iframeHeight, scrolling: "no" })] }) }), mode === 'replay' && jsx(ReplayControls, {})] }));
1233
1278
  };
1234
1279
 
1235
1280
  const VizDomContainer = () => {
1281
+ const controls = useHeatmapControlStore((state) => state.controls);
1282
+ const isRendering = useHeatmapDataStore((state) => state.isRendering);
1283
+ if (isRendering)
1284
+ return controls.VizLoading ?? null;
1236
1285
  return (jsx(BoxStack, { id: "gx-hm-viz-container", flexDirection: "column", flex: "1 1 auto", overflow: "auto", children: jsx(BoxStack, { id: "gx-hm-content", flexDirection: "column", flex: "1 1 auto", overflow: "hidden", style: {
1237
- // margin: '0px 16px 0px 12px',
1238
- // borderRadius: '8px 8px 0 0',
1239
- // borderRadius: '8px',
1240
1286
  minWidth: '394px',
1241
- padding: '12px',
1242
- background: '#f1f1f1',
1243
1287
  }, children: jsx(VizDomRenderer, {}) }) }));
1244
1288
  };
1245
1289
 
1246
- const SIDEBAR_WIDTH = 280;
1247
- const LeftSidebar = ({ children }) => {
1248
- const isHideSidebar = useHeatmapDataStore((state) => state.state.hideSidebar);
1290
+ const ContentMetricBar = () => {
1291
+ const controls = useHeatmapControlStore((state) => state.controls);
1292
+ return (jsx(BoxStack, { id: "gx-hm-content-header", flexDirection: "row", alignItems: "center", overflow: "auto", style: {
1293
+ borderBottom: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
1294
+ }, children: controls.MetricBar ?? null }));
1295
+ };
1296
+
1297
+ const ContentToolbar = () => {
1298
+ const controls = useHeatmapControlStore((state) => state.controls);
1299
+ return (jsx("div", { id: "gx-hm-content-toolbar", style: { position: 'absolute', bottom: 0, left: '8px', right: '24px', padding: '8px' }, children: controls.Toolbar ?? null }));
1300
+ };
1301
+
1302
+ const LeftSidebar = () => {
1303
+ const controls = useHeatmapControlStore((state) => state.controls);
1304
+ const isHideSidebar = useHeatmapInteractionStore((state) => state.state.hideSidebar);
1305
+ const config = useHeatmapDataStore((state) => state.config);
1306
+ const sidebarWidth = config?.sidebarWidth || DEFAULT_SIDEBAR_WIDTH;
1249
1307
  if (isHideSidebar) {
1250
1308
  return null;
1251
1309
  }
@@ -1258,41 +1316,37 @@ const LeftSidebar = ({ children }) => {
1258
1316
  transform: 'translateX(-100%)',
1259
1317
  visibility: 'hidden',
1260
1318
  }
1261
- : { width: `${SIDEBAR_WIDTH}px` }),
1262
- }, children: jsx("div", { className: "gx-hm-sidebar-wrapper", style: { height: '100%', width: `${SIDEBAR_WIDTH}px` }, children: children }) }));
1319
+ : { width: `${sidebarWidth}px + ${HEATMAP_CONFIG.borderWidth}px` }),
1320
+ }, children: jsx("div", { className: "gx-hm-sidebar-wrapper", style: {
1321
+ height: '100%',
1322
+ width: `calc(${sidebarWidth}px + ${HEATMAP_CONFIG.borderWidth}px)`,
1323
+ borderRight: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
1324
+ }, children: controls.Sidebar ?? null }) }));
1263
1325
  };
1264
1326
 
1265
- const WrapperPreview = ({ children }) => {
1266
- return (jsxs("div", { className: "gx-hm-container", style: { display: 'flex', overflowY: 'hidden', flex: '1', position: 'relative' }, children: [jsx(LeftSidebar, { children: children }), jsx(VizDomContainer, {})] }));
1327
+ const WrapperPreview = () => {
1328
+ return (jsxs(BoxStack, { id: "gx-hm-container", flexDirection: "row", overflow: "hidden", flex: "1", position: "relative", children: [jsx(LeftSidebar, {}), jsxs(BoxStack, { flexDirection: "column", flex: "1", children: [jsx(ContentMetricBar, {}), jsx(VizDomContainer, {}), jsx(ContentToolbar, {})] })] }));
1267
1329
  };
1268
1330
 
1269
- const WrapperLayout = ({ header, toolbar, sidebar }) => {
1270
- return (jsxs(BoxStack, { id: "gx-hm-layout", flexDirection: "column", flex: "1", children: [jsx(ContentHeader, { children: header }), jsx(ContentHeader, { children: toolbar }), jsx(WrapperPreview, { children: sidebar })] }));
1331
+ const WrapperLayout = () => {
1332
+ return (jsxs(BoxStack, { id: "gx-hm-layout", flexDirection: "column", flex: "1", children: [jsx(ContentTopBar, {}), jsx(WrapperPreview, {})] }));
1271
1333
  };
1272
1334
 
1273
- const HeatmapLayout = ({ data, clickmap, header, toolbar, sidebar, }) => {
1274
- const setData = useHeatmapDataStore((state) => state.setData);
1275
- const setClickmap = useHeatmapDataStore((state) => state.setClickmap);
1276
- const handleSetClickmap = useCallback((clickmap) => {
1277
- if (!clickmap)
1278
- return;
1279
- setClickmap(clickmap);
1280
- }, [clickmap]);
1281
- const handleSetData = useCallback((data) => {
1282
- if (!data)
1283
- return;
1284
- setData(data);
1285
- }, [data]);
1286
- useEffect(() => {
1287
- handleSetData(data);
1288
- }, [data]);
1289
- useEffect(() => {
1290
- handleSetClickmap(clickmap);
1291
- }, [clickmap]);
1292
- return (jsxs(Fragment, { children: [jsx(BoxStack, { id: "gx-hm-project", flexDirection: "column", flex: "1", height: "100%", children: jsx(BoxStack, { id: "gx-hm-project-content", flexDirection: "column", flex: "1", children: jsx("div", { style: {
1293
- minHeight: '100%',
1294
- display: 'flex',
1295
- }, children: jsx(WrapperLayout, { header: header, toolbar: toolbar, sidebar: sidebar }) }) }) }), jsx("div", { id: "gx-hm-project-portal" })] }));
1335
+ const HeatmapLayout = ({ data, clickmap, controls, dataInfo, }) => {
1336
+ useRegisterControl(controls);
1337
+ useRegisterData(data, dataInfo);
1338
+ useRegisterHeatmap(clickmap);
1339
+ useRegisterConfig();
1340
+ return (jsx(BoxStack, { id: "gx-hm-project", flexDirection: "column", flex: "1", height: "100%", style: getVariableStyle(), children: jsx(BoxStack, { id: "gx-hm-project-content", flexDirection: "column", flex: "1", children: jsx("div", { style: {
1341
+ minHeight: '100%',
1342
+ display: 'flex',
1343
+ }, children: jsx(WrapperLayout, {}) }) }) }));
1344
+ function getVariableStyle() {
1345
+ return {
1346
+ '--gx-hm-border-width': `${HEATMAP_CONFIG.borderWidth}px`,
1347
+ '--gx-hm-border-color': `${HEATMAP_CONFIG.borderColor}`,
1348
+ };
1349
+ }
1296
1350
  };
1297
1351
 
1298
1352
  var PanelContent;
@@ -1313,4 +1367,4 @@ var ErrorType;
1313
1367
  ErrorType["DataError"] = "DataError";
1314
1368
  })(ErrorType || (ErrorType = {}));
1315
1369
 
1316
- export { GraphView, HeatmapLayout, useHeatmapDataStore };
1370
+ export { GraphView, HeatmapLayout, IHeatmapType, useHeatmapDataStore };