@gemx-dev/heatmap-react 3.5.13 → 3.5.16
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.
- package/dist/esm/components/Layout/HeatmapLayout.d.ts +4 -0
- package/dist/esm/components/Layout/HeatmapLayout.d.ts.map +1 -1
- package/dist/esm/components/Layout/WrapperLayout.d.ts +1 -0
- package/dist/esm/components/Layout/WrapperLayout.d.ts.map +1 -1
- package/dist/esm/components/VizDom/VizDomRenderer.d.ts.map +1 -1
- package/dist/esm/components/VizElement/ClickedElementOverlay.d.ts +17 -0
- package/dist/esm/components/VizElement/ClickedElementOverlay.d.ts.map +1 -0
- package/dist/esm/components/VizElement/DefaultRankBadges.d.ts +11 -0
- package/dist/esm/components/VizElement/DefaultRankBadges.d.ts.map +1 -0
- package/dist/esm/components/VizElement/ElementCallout.d.ts +17 -0
- package/dist/esm/components/VizElement/ElementCallout.d.ts.map +1 -0
- package/dist/esm/components/VizElement/HeatmapElements.d.ts +23 -0
- package/dist/esm/components/VizElement/HeatmapElements.d.ts.map +1 -0
- package/dist/esm/components/VizElement/HoveredElementOverlay.d.ts +12 -0
- package/dist/esm/components/VizElement/HoveredElementOverlay.d.ts.map +1 -0
- package/dist/esm/components/VizElement/MissingElementMessage.d.ts +7 -0
- package/dist/esm/components/VizElement/MissingElementMessage.d.ts.map +1 -0
- package/dist/esm/components/VizElement/RankBadge.d.ts +10 -0
- package/dist/esm/components/VizElement/RankBadge.d.ts.map +1 -0
- package/dist/esm/components/VizElement/VizElements.d.ts +3 -0
- package/dist/esm/components/VizElement/VizElements.d.ts.map +1 -1
- package/dist/esm/components/VizElement/temp/ClarityVisualizer.d.ts +150 -0
- package/dist/esm/components/VizElement/temp/ClarityVisualizer.d.ts.map +1 -0
- package/dist/esm/components/VizElement/{VizElementRank.d.ts → temp/VizElementRank.d.ts} +2 -3
- package/dist/esm/components/VizElement/temp/VizElementRank.d.ts.map +1 -0
- package/dist/esm/constants/index.d.ts +5 -0
- package/dist/esm/constants/index.d.ts.map +1 -0
- package/dist/esm/helpers/viz-elements.d.ts +10 -0
- package/dist/esm/helpers/viz-elements.d.ts.map +1 -0
- package/dist/esm/hooks/index.d.ts +1 -0
- package/dist/esm/hooks/index.d.ts.map +1 -1
- package/dist/esm/hooks/vix-elements/index.d.ts +5 -0
- package/dist/esm/hooks/vix-elements/index.d.ts.map +1 -0
- package/dist/esm/hooks/vix-elements/useClickedElement.d.ts +14 -0
- package/dist/esm/hooks/vix-elements/useClickedElement.d.ts.map +1 -0
- package/dist/esm/hooks/vix-elements/useHeatmapEffects.d.ts +8 -0
- package/dist/esm/hooks/vix-elements/useHeatmapEffects.d.ts.map +1 -0
- package/dist/esm/hooks/vix-elements/useHeatmapElementPosition.d.ts +13 -0
- package/dist/esm/hooks/vix-elements/useHeatmapElementPosition.d.ts.map +1 -0
- package/dist/esm/hooks/vix-elements/useHoveredElement.d.ts +17 -0
- package/dist/esm/hooks/vix-elements/useHoveredElement.d.ts.map +1 -0
- package/dist/esm/hooks/viz-render/useHeatmapRender.d.ts +2 -0
- package/dist/esm/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
- package/dist/esm/hooks/viz-render/useHeatmapVizRender.d.ts +2 -0
- package/dist/esm/hooks/viz-render/useHeatmapVizRender.d.ts.map +1 -1
- package/dist/esm/hooks/viz-render/useReplayRender.d.ts +2 -0
- package/dist/esm/hooks/viz-render/useReplayRender.d.ts.map +1 -1
- package/dist/esm/index.js +659 -24
- package/dist/esm/index.mjs +659 -24
- package/dist/esm/stores/data.d.ts +3 -1
- package/dist/esm/stores/data.d.ts.map +1 -1
- package/dist/style.css +20 -0
- package/dist/umd/components/Layout/HeatmapLayout.d.ts +4 -0
- package/dist/umd/components/Layout/HeatmapLayout.d.ts.map +1 -1
- package/dist/umd/components/Layout/WrapperLayout.d.ts +1 -0
- package/dist/umd/components/Layout/WrapperLayout.d.ts.map +1 -1
- package/dist/umd/components/VizDom/VizDomRenderer.d.ts.map +1 -1
- package/dist/umd/components/VizElement/ClickedElementOverlay.d.ts +17 -0
- package/dist/umd/components/VizElement/ClickedElementOverlay.d.ts.map +1 -0
- package/dist/umd/components/VizElement/DefaultRankBadges.d.ts +11 -0
- package/dist/umd/components/VizElement/DefaultRankBadges.d.ts.map +1 -0
- package/dist/umd/components/VizElement/ElementCallout.d.ts +17 -0
- package/dist/umd/components/VizElement/ElementCallout.d.ts.map +1 -0
- package/dist/umd/components/VizElement/HeatmapElements.d.ts +23 -0
- package/dist/umd/components/VizElement/HeatmapElements.d.ts.map +1 -0
- package/dist/umd/components/VizElement/HoveredElementOverlay.d.ts +12 -0
- package/dist/umd/components/VizElement/HoveredElementOverlay.d.ts.map +1 -0
- package/dist/umd/components/VizElement/MissingElementMessage.d.ts +7 -0
- package/dist/umd/components/VizElement/MissingElementMessage.d.ts.map +1 -0
- package/dist/umd/components/VizElement/RankBadge.d.ts +10 -0
- package/dist/umd/components/VizElement/RankBadge.d.ts.map +1 -0
- package/dist/umd/components/VizElement/VizElements.d.ts +3 -0
- package/dist/umd/components/VizElement/VizElements.d.ts.map +1 -1
- package/dist/umd/components/VizElement/temp/ClarityVisualizer.d.ts +150 -0
- package/dist/umd/components/VizElement/temp/ClarityVisualizer.d.ts.map +1 -0
- package/dist/umd/components/VizElement/{VizElementRank.d.ts → temp/VizElementRank.d.ts} +2 -3
- package/dist/umd/components/VizElement/temp/VizElementRank.d.ts.map +1 -0
- package/dist/umd/constants/index.d.ts +5 -0
- package/dist/umd/constants/index.d.ts.map +1 -0
- package/dist/umd/helpers/viz-elements.d.ts +10 -0
- package/dist/umd/helpers/viz-elements.d.ts.map +1 -0
- package/dist/umd/hooks/index.d.ts +1 -0
- package/dist/umd/hooks/index.d.ts.map +1 -1
- package/dist/umd/hooks/vix-elements/index.d.ts +5 -0
- package/dist/umd/hooks/vix-elements/index.d.ts.map +1 -0
- package/dist/umd/hooks/vix-elements/useClickedElement.d.ts +14 -0
- package/dist/umd/hooks/vix-elements/useClickedElement.d.ts.map +1 -0
- package/dist/umd/hooks/vix-elements/useHeatmapEffects.d.ts +8 -0
- package/dist/umd/hooks/vix-elements/useHeatmapEffects.d.ts.map +1 -0
- package/dist/umd/hooks/vix-elements/useHeatmapElementPosition.d.ts +13 -0
- package/dist/umd/hooks/vix-elements/useHeatmapElementPosition.d.ts.map +1 -0
- package/dist/umd/hooks/vix-elements/useHoveredElement.d.ts +17 -0
- package/dist/umd/hooks/vix-elements/useHoveredElement.d.ts.map +1 -0
- package/dist/umd/hooks/viz-render/useHeatmapRender.d.ts +2 -0
- package/dist/umd/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
- package/dist/umd/hooks/viz-render/useHeatmapVizRender.d.ts +2 -0
- package/dist/umd/hooks/viz-render/useHeatmapVizRender.d.ts.map +1 -1
- package/dist/umd/hooks/viz-render/useReplayRender.d.ts +2 -0
- package/dist/umd/hooks/viz-render/useReplayRender.d.ts.map +1 -1
- package/dist/umd/index.js +2 -2
- package/dist/umd/stores/data.d.ts +3 -1
- package/dist/umd/stores/data.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/esm/components/VizElement/VizElementRank.d.ts.map +0 -1
- package/dist/umd/components/VizElement/VizElementRank.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,
|
|
5
|
-
import { Visualizer } from '@gemx-dev/clarity-visualize';
|
|
4
|
+
import { useEffect, useMemo, useState, useCallback, useRef, Fragment as Fragment$1 } from 'react';
|
|
6
5
|
import { create } from 'zustand';
|
|
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,6 +38,21 @@ 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 useHeatmapDataStore = create()((set, get) => ({
|
|
42
|
+
data: undefined,
|
|
43
|
+
clickmap: undefined,
|
|
44
|
+
config: undefined,
|
|
45
|
+
iframeHeight: 0,
|
|
46
|
+
state: {
|
|
47
|
+
hideSidebar: false,
|
|
48
|
+
},
|
|
49
|
+
setData: (data) => set({ data }),
|
|
50
|
+
setClickmap: (clickmap) => set({ clickmap }),
|
|
51
|
+
setState: (state) => set({ state: { ...get().state, ...state } }),
|
|
52
|
+
setConfig: (config) => set({ config: { ...get().config, ...config } }),
|
|
53
|
+
setIframeHeight: (iframeHeight) => set({ iframeHeight }),
|
|
54
|
+
}));
|
|
55
|
+
|
|
40
56
|
const BoxStack = ({ children, ...props }) => {
|
|
41
57
|
const id = props.id;
|
|
42
58
|
const flexDirection = props.flexDirection;
|
|
@@ -96,6 +112,231 @@ const HEATMAP_STYLE = {
|
|
|
96
112
|
},
|
|
97
113
|
};
|
|
98
114
|
|
|
115
|
+
const useClickedElement = ({ selectedElement, heatmapInfo, getRect }) => {
|
|
116
|
+
const [clickedElement, setClickedElement] = useState(null);
|
|
117
|
+
const [showMissingElement, setShowMissingElement] = useState(false);
|
|
118
|
+
const [shouldShowCallout, setShouldShowCallout] = useState(false);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (!selectedElement || !heatmapInfo?.elementMapInfo) {
|
|
121
|
+
setClickedElement(null);
|
|
122
|
+
setShowMissingElement(false);
|
|
123
|
+
setShouldShowCallout(false);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const info = heatmapInfo.elementMapInfo[selectedElement];
|
|
127
|
+
if (!info) {
|
|
128
|
+
setClickedElement(null);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const rect = getRect({ hash: selectedElement, selector: info.selector });
|
|
132
|
+
if (rect && heatmapInfo.sortedElements) {
|
|
133
|
+
const rank = heatmapInfo.sortedElements.findIndex((e) => e.hash === selectedElement) + 1;
|
|
134
|
+
setClickedElement({
|
|
135
|
+
...rect,
|
|
136
|
+
hash: selectedElement,
|
|
137
|
+
clicks: info.totalclicks ?? 0,
|
|
138
|
+
rank,
|
|
139
|
+
selector: info.selector ?? '',
|
|
140
|
+
});
|
|
141
|
+
setShowMissingElement(false);
|
|
142
|
+
setShouldShowCallout(true);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
const rank = (heatmapInfo.sortedElements?.findIndex((e) => e.hash === selectedElement) ?? -1) + 1;
|
|
146
|
+
setClickedElement({
|
|
147
|
+
hash: selectedElement,
|
|
148
|
+
clicks: info.totalclicks ?? 0,
|
|
149
|
+
rank,
|
|
150
|
+
selector: info.selector ?? '',
|
|
151
|
+
left: 0,
|
|
152
|
+
top: 0,
|
|
153
|
+
width: 0,
|
|
154
|
+
height: 0,
|
|
155
|
+
});
|
|
156
|
+
setShowMissingElement(true);
|
|
157
|
+
setShouldShowCallout(false);
|
|
158
|
+
}
|
|
159
|
+
}, [selectedElement, heatmapInfo, getRect]);
|
|
160
|
+
return { clickedElement, showMissingElement, shouldShowCallout, setShouldShowCallout };
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const useHeatmapEffects = ({ isVisible, isElementSidebarOpen, selectedElement, setShouldShowCallout, resetAll, }) => {
|
|
164
|
+
// Reset khi ẩn
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (!isVisible)
|
|
167
|
+
resetAll();
|
|
168
|
+
}, [isVisible, resetAll]);
|
|
169
|
+
// Ẩn callout khi sidebar mở
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
if (isElementSidebarOpen && selectedElement) {
|
|
172
|
+
setShouldShowCallout(false);
|
|
173
|
+
}
|
|
174
|
+
else if (!isElementSidebarOpen && selectedElement) {
|
|
175
|
+
setShouldShowCallout(true);
|
|
176
|
+
}
|
|
177
|
+
}, [isElementSidebarOpen, selectedElement, setShouldShowCallout]);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
function getElementLayout(element) {
|
|
181
|
+
if (!element?.getBoundingClientRect)
|
|
182
|
+
return null;
|
|
183
|
+
const rect = element.getBoundingClientRect();
|
|
184
|
+
if (rect.width === 0 && rect.height === 0)
|
|
185
|
+
return null;
|
|
186
|
+
return {
|
|
187
|
+
top: rect.top,
|
|
188
|
+
left: rect.left,
|
|
189
|
+
width: rect.width,
|
|
190
|
+
height: rect.height,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function formatPercentage(value, decimals = 2) {
|
|
194
|
+
return value.toFixed(decimals);
|
|
195
|
+
}
|
|
196
|
+
function getSimpleSelector(selector) {
|
|
197
|
+
const parts = selector.split(' > ');
|
|
198
|
+
return parts[parts.length - 1] || selector;
|
|
199
|
+
}
|
|
200
|
+
function calculateRankPosition(rect, widthScale) {
|
|
201
|
+
const top = rect.top <= 18 ? rect.top + 3 : rect.top - 18;
|
|
202
|
+
const left = rect.left <= 18 ? rect.left + 3 : rect.left - 18;
|
|
203
|
+
return {
|
|
204
|
+
transform: `scale(${1.2 * widthScale})`,
|
|
205
|
+
top: Number.isNaN(top) ? undefined : top,
|
|
206
|
+
left: Number.isNaN(left) ? undefined : left,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const useHeatmapElementPosition = ({ iframeRef, parentRef, visualizer, heatmapWidth, iframeHeight, widthScale, projectId, }) => {
|
|
211
|
+
return useCallback((element) => {
|
|
212
|
+
const hash = element?.hash;
|
|
213
|
+
if (!iframeRef.current?.contentDocument || !hash || !visualizer)
|
|
214
|
+
return null;
|
|
215
|
+
let domElement = null;
|
|
216
|
+
try {
|
|
217
|
+
domElement = visualizer.get(hash);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
console.error('Visualizer error:', { projectId, hash, error });
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
if (!domElement)
|
|
224
|
+
return null;
|
|
225
|
+
const layout = getElementLayout(domElement);
|
|
226
|
+
if (!layout)
|
|
227
|
+
return null;
|
|
228
|
+
const parentEl = parentRef.current;
|
|
229
|
+
if (!parentEl)
|
|
230
|
+
return null;
|
|
231
|
+
const scrollOffset = parentEl.scrollTop / widthScale;
|
|
232
|
+
const adjustedTop = layout.top + scrollOffset;
|
|
233
|
+
const outOfBounds = adjustedTop < 0 ||
|
|
234
|
+
adjustedTop > (iframeHeight || Infinity) ||
|
|
235
|
+
layout.left < 0 ||
|
|
236
|
+
(typeof heatmapWidth === 'number' && layout.left > heatmapWidth);
|
|
237
|
+
if (outOfBounds)
|
|
238
|
+
return null;
|
|
239
|
+
return {
|
|
240
|
+
left: layout.left,
|
|
241
|
+
top: adjustedTop,
|
|
242
|
+
width: Math.min(layout.width, heatmapWidth || layout.width),
|
|
243
|
+
height: layout.height,
|
|
244
|
+
};
|
|
245
|
+
}, [iframeRef, parentRef, visualizer, heatmapWidth, iframeHeight, widthScale, projectId]);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const debounce = (fn, delay) => {
|
|
249
|
+
let timeout;
|
|
250
|
+
return (...args) => {
|
|
251
|
+
clearTimeout(timeout);
|
|
252
|
+
timeout = setTimeout(() => fn(...args), delay);
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
const useHoveredElement = ({ iframeRef, heatmapInfo, widthScale, getRect, onSelect, }) => {
|
|
256
|
+
const [hoveredElement, setHoveredElement] = useState(null);
|
|
257
|
+
const handleMouseMove = useCallback(debounce((event) => {
|
|
258
|
+
if (!iframeRef.current?.contentDocument || !heatmapInfo?.elementMapInfo) {
|
|
259
|
+
setHoveredElement(null);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const iframe = iframeRef.current;
|
|
263
|
+
const iframeRect = iframe.getBoundingClientRect();
|
|
264
|
+
let x = event.clientX - iframeRect.left;
|
|
265
|
+
console.log(`🚀 🐥 ~ useHoveredElement ~ iframeRect.left:`, iframeRect.left);
|
|
266
|
+
console.log(`🚀 🐥 ~ useHoveredElement ~ event.clientX:`, event.clientX);
|
|
267
|
+
let y = event.clientY - iframeRect.top;
|
|
268
|
+
if (widthScale !== 1) {
|
|
269
|
+
x /= widthScale;
|
|
270
|
+
y /= widthScale;
|
|
271
|
+
}
|
|
272
|
+
const doc = iframe.contentDocument;
|
|
273
|
+
if (!doc) {
|
|
274
|
+
setHoveredElement(null);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
let targetElement = null;
|
|
278
|
+
// Best: dùng caretPositionFromPoint nếu có (Chrome, Safari)
|
|
279
|
+
targetElement = getElementAtPoint(doc, x, y);
|
|
280
|
+
if (!targetElement) {
|
|
281
|
+
targetElement = doc.elementFromPoint(x, y);
|
|
282
|
+
}
|
|
283
|
+
if (!targetElement) {
|
|
284
|
+
setHoveredElement(null);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
// Lấy hash từ nhiều attribute khả dĩ
|
|
288
|
+
const hash = targetElement.getAttribute('data-clarity-hash') ||
|
|
289
|
+
targetElement.getAttribute('data-clarity-hashalpha') ||
|
|
290
|
+
targetElement.getAttribute('data-clarity-hashbeta');
|
|
291
|
+
if (!hash || !heatmapInfo.elementMapInfo[hash]) {
|
|
292
|
+
setHoveredElement(null);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const info = heatmapInfo.elementMapInfo[hash];
|
|
296
|
+
const position = getRect({ hash, selector: info.selector });
|
|
297
|
+
if (position && heatmapInfo.sortedElements) {
|
|
298
|
+
const rank = heatmapInfo.sortedElements.findIndex((e) => e.hash === hash) + 1;
|
|
299
|
+
setHoveredElement({
|
|
300
|
+
...position,
|
|
301
|
+
hash,
|
|
302
|
+
clicks: info.totalclicks ?? 0,
|
|
303
|
+
rank,
|
|
304
|
+
selector: info.selector ?? '',
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
setHoveredElement(null);
|
|
309
|
+
}
|
|
310
|
+
}, 16), // ~60fps
|
|
311
|
+
[iframeRef, heatmapInfo, getRect]);
|
|
312
|
+
const handleMouseLeave = useCallback(() => {
|
|
313
|
+
setHoveredElement(null);
|
|
314
|
+
}, []);
|
|
315
|
+
const handleClick = useCallback(() => {
|
|
316
|
+
if (hoveredElement?.hash && onSelect) {
|
|
317
|
+
onSelect(hoveredElement.hash);
|
|
318
|
+
}
|
|
319
|
+
}, [hoveredElement, onSelect]);
|
|
320
|
+
return {
|
|
321
|
+
hoveredElement,
|
|
322
|
+
handleMouseMove,
|
|
323
|
+
handleMouseLeave,
|
|
324
|
+
handleClick,
|
|
325
|
+
};
|
|
326
|
+
};
|
|
327
|
+
const getElementAtPoint = (doc, x, y) => {
|
|
328
|
+
let el = null;
|
|
329
|
+
if ('caretPositionFromPoint' in doc) {
|
|
330
|
+
el = doc.caretPositionFromPoint(x, y)?.offsetNode ?? null;
|
|
331
|
+
}
|
|
332
|
+
el = el ?? doc.elementFromPoint(x, y);
|
|
333
|
+
let element = el;
|
|
334
|
+
while (element && element.nodeType === Node.TEXT_NODE) {
|
|
335
|
+
element = element.parentElement;
|
|
336
|
+
}
|
|
337
|
+
return element;
|
|
338
|
+
};
|
|
339
|
+
|
|
99
340
|
const recreateIframe = (iframeRef, config) => {
|
|
100
341
|
const container = iframeRef.current?.parentElement;
|
|
101
342
|
if (!container)
|
|
@@ -120,19 +361,6 @@ const recreateIframe = (iframeRef, config) => {
|
|
|
120
361
|
return newIframe;
|
|
121
362
|
};
|
|
122
363
|
|
|
123
|
-
const useHeatmapDataStore = create()((set, get) => ({
|
|
124
|
-
data: undefined,
|
|
125
|
-
config: undefined,
|
|
126
|
-
iframeHeight: 0,
|
|
127
|
-
state: {
|
|
128
|
-
hideSidebar: false,
|
|
129
|
-
},
|
|
130
|
-
setData: (data) => set({ data }),
|
|
131
|
-
setState: (state) => set({ state: { ...get().state, ...state } }),
|
|
132
|
-
setConfig: (config) => set({ config: { ...get().config, ...config } }),
|
|
133
|
-
setIframeHeight: (iframeHeight) => set({ iframeHeight }),
|
|
134
|
-
}));
|
|
135
|
-
|
|
136
364
|
function isMobileDevice(userAgent) {
|
|
137
365
|
if (!userAgent)
|
|
138
366
|
return false;
|
|
@@ -143,6 +371,7 @@ const useHeatmapRender = () => {
|
|
|
143
371
|
const data = useHeatmapDataStore((state) => state.data);
|
|
144
372
|
const config = useHeatmapDataStore((state) => state.config);
|
|
145
373
|
const setConfig = useHeatmapDataStore((state) => state.setConfig);
|
|
374
|
+
const clickmap = useHeatmapDataStore((state) => state.clickmap);
|
|
146
375
|
const visualizerRef = useRef(null);
|
|
147
376
|
const iframeRef = useRef(null);
|
|
148
377
|
const initializeVisualizer = useCallback((envelope, userAgent) => {
|
|
@@ -161,13 +390,14 @@ const useHeatmapRender = () => {
|
|
|
161
390
|
locale: 'en-us',
|
|
162
391
|
});
|
|
163
392
|
return visualizer;
|
|
164
|
-
}, [
|
|
393
|
+
}, []);
|
|
165
394
|
// Process and render heatmap HTML
|
|
166
395
|
const renderHeatmap = useCallback(async (payloads) => {
|
|
167
396
|
if (!payloads || payloads.length === 0)
|
|
168
397
|
return;
|
|
169
|
-
let visualizer =
|
|
398
|
+
let visualizer = new Visualizer();
|
|
170
399
|
const iframe = recreateIframe(iframeRef, config);
|
|
400
|
+
// const merged = visualizer.merge(payloads);
|
|
171
401
|
// setIframeHeight(Number(iframeRef.current?.height || 0));
|
|
172
402
|
// for (const decoded of payloads) {
|
|
173
403
|
// // Initialize on first sequence
|
|
@@ -185,6 +415,7 @@ const useHeatmapRender = () => {
|
|
|
185
415
|
// Render static HTML
|
|
186
416
|
if (visualizer && iframe?.contentWindow) {
|
|
187
417
|
await visualizer.html(payloads, iframe.contentWindow);
|
|
418
|
+
visualizerRef.current = visualizer;
|
|
188
419
|
}
|
|
189
420
|
}, [initializeVisualizer]);
|
|
190
421
|
useEffect(() => {
|
|
@@ -195,8 +426,15 @@ const useHeatmapRender = () => {
|
|
|
195
426
|
visualizerRef.current = null;
|
|
196
427
|
};
|
|
197
428
|
}, [config, data, renderHeatmap]);
|
|
429
|
+
useEffect(() => {
|
|
430
|
+
if (!visualizerRef.current || !clickmap || clickmap.length === 0)
|
|
431
|
+
return;
|
|
432
|
+
visualizerRef.current.clearmap();
|
|
433
|
+
visualizerRef.current?.clickmap(clickmap);
|
|
434
|
+
}, [clickmap]);
|
|
198
435
|
return {
|
|
199
436
|
iframeRef,
|
|
437
|
+
clarityVisualizer: visualizerRef.current,
|
|
200
438
|
};
|
|
201
439
|
};
|
|
202
440
|
|
|
@@ -318,6 +556,7 @@ const useReplayRender = () => {
|
|
|
318
556
|
return {
|
|
319
557
|
iframeRef,
|
|
320
558
|
isPlaying: isPlayingRef.current,
|
|
559
|
+
clarityVisualizer: visualizerRef.current,
|
|
321
560
|
play,
|
|
322
561
|
pause,
|
|
323
562
|
};
|
|
@@ -530,8 +769,386 @@ const useHeatmapScale = (props) => {
|
|
|
530
769
|
};
|
|
531
770
|
};
|
|
532
771
|
|
|
533
|
-
const
|
|
534
|
-
|
|
772
|
+
const CLICKED_ELEMENT_ID = 'clickedElement';
|
|
773
|
+
const SECONDARY_CLICKED_ELEMENT_ID = 'secondaryClickedElementID';
|
|
774
|
+
const HOVERED_ELEMENT_ID = 'hoveredElement';
|
|
775
|
+
const SECONDARY_HOVERED_ELEMENT_ID = 'secondaryhoveredElementID';
|
|
776
|
+
|
|
777
|
+
const ElementCallout = ({ element, target, totalClicks, isSecondary, isRecordingView, isCompareMode, deviceType, heatmapType, language, widthScale, parentRef, }) => {
|
|
778
|
+
const calloutRef = useRef(null);
|
|
779
|
+
const [position, setPosition] = useState({
|
|
780
|
+
top: 0,
|
|
781
|
+
left: 0,
|
|
782
|
+
placement: 'top',
|
|
783
|
+
});
|
|
784
|
+
const percentage = formatPercentage(((element.clicks ?? 0) / totalClicks) * 100, 2);
|
|
785
|
+
// Calculate callout position
|
|
786
|
+
useEffect(() => {
|
|
787
|
+
const targetElement = document.querySelector(target);
|
|
788
|
+
const calloutElement = calloutRef.current;
|
|
789
|
+
if (!targetElement || !calloutElement)
|
|
790
|
+
return;
|
|
791
|
+
const calculatePosition = () => {
|
|
792
|
+
const targetRect = targetElement.getBoundingClientRect();
|
|
793
|
+
const calloutRect = calloutElement.getBoundingClientRect();
|
|
794
|
+
const viewportWidth = window.innerWidth;
|
|
795
|
+
const viewportHeight = window.innerHeight;
|
|
796
|
+
const padding = 12; // Space between target and callout
|
|
797
|
+
const arrowSize = 8;
|
|
798
|
+
let top = 0;
|
|
799
|
+
let left = 0;
|
|
800
|
+
let placement = 'top';
|
|
801
|
+
// Try positions in order: top, bottom, right, left
|
|
802
|
+
const positions = [
|
|
803
|
+
// Top
|
|
804
|
+
{
|
|
805
|
+
placement: 'top',
|
|
806
|
+
top: targetRect.top - calloutRect.height - padding - arrowSize,
|
|
807
|
+
left: targetRect.left + targetRect.width / 2 - calloutRect.width / 2,
|
|
808
|
+
valid: targetRect.top - calloutRect.height - padding - arrowSize > 0,
|
|
809
|
+
},
|
|
810
|
+
// Bottom
|
|
811
|
+
{
|
|
812
|
+
placement: 'bottom',
|
|
813
|
+
top: targetRect.bottom + padding + arrowSize,
|
|
814
|
+
left: targetRect.left + targetRect.width / 2 - calloutRect.width / 2,
|
|
815
|
+
valid: targetRect.bottom + calloutRect.height + padding + arrowSize < viewportHeight,
|
|
816
|
+
},
|
|
817
|
+
// Right
|
|
818
|
+
{
|
|
819
|
+
placement: 'right',
|
|
820
|
+
top: targetRect.top + targetRect.height / 2 - calloutRect.height / 2,
|
|
821
|
+
left: targetRect.right + padding + arrowSize,
|
|
822
|
+
valid: targetRect.right + calloutRect.width + padding + arrowSize < viewportWidth,
|
|
823
|
+
},
|
|
824
|
+
// Left
|
|
825
|
+
{
|
|
826
|
+
placement: 'left',
|
|
827
|
+
top: targetRect.top + targetRect.height / 2 - calloutRect.height / 2,
|
|
828
|
+
left: targetRect.left - calloutRect.width - padding - arrowSize,
|
|
829
|
+
valid: targetRect.left - calloutRect.width - padding - arrowSize > 0,
|
|
830
|
+
},
|
|
831
|
+
];
|
|
832
|
+
// Find first valid position
|
|
833
|
+
const validPosition = positions.find((p) => p.valid) || positions[0];
|
|
834
|
+
top = validPosition.top;
|
|
835
|
+
left = validPosition.left;
|
|
836
|
+
placement = validPosition.placement;
|
|
837
|
+
// Keep within viewport bounds
|
|
838
|
+
left = Math.max(padding, Math.min(left, viewportWidth - calloutRect.width - padding));
|
|
839
|
+
top = Math.max(padding, Math.min(top, viewportHeight - calloutRect.height - padding));
|
|
840
|
+
setPosition({ top, left, placement });
|
|
841
|
+
};
|
|
842
|
+
// Initial calculation
|
|
843
|
+
calculatePosition();
|
|
844
|
+
// Recalculate on scroll/resize
|
|
845
|
+
const handleUpdate = () => {
|
|
846
|
+
requestAnimationFrame(calculatePosition);
|
|
847
|
+
};
|
|
848
|
+
window.addEventListener('scroll', handleUpdate, true);
|
|
849
|
+
window.addEventListener('resize', handleUpdate);
|
|
850
|
+
parentRef?.current?.addEventListener('scroll', handleUpdate);
|
|
851
|
+
return () => {
|
|
852
|
+
window.removeEventListener('scroll', handleUpdate, true);
|
|
853
|
+
window.removeEventListener('resize', handleUpdate);
|
|
854
|
+
parentRef?.current?.removeEventListener('scroll', handleUpdate);
|
|
855
|
+
};
|
|
856
|
+
}, [target, parentRef]);
|
|
857
|
+
const calloutContent = (jsxs("div", { ref: calloutRef, className: `clarity-callout clarity-callout--${position.placement} ${isSecondary ? 'clarity-callout--secondary' : ''}`, style: {
|
|
858
|
+
position: 'fixed',
|
|
859
|
+
top: position.top,
|
|
860
|
+
left: position.left,
|
|
861
|
+
zIndex: 2147483647,
|
|
862
|
+
}, "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: `
|
|
863
|
+
.clarity-callout {
|
|
864
|
+
background: white;
|
|
865
|
+
border-radius: 8px;
|
|
866
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
867
|
+
padding: 16px;
|
|
868
|
+
min-width: 200px;
|
|
869
|
+
max-width: 280px;
|
|
870
|
+
animation: clarity-callout-fade-in 0.2s ease-out;
|
|
871
|
+
pointer-events: auto;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
@keyframes clarity-callout-fade-in {
|
|
875
|
+
from {
|
|
876
|
+
opacity: 0;
|
|
877
|
+
transform: scale(0.95);
|
|
878
|
+
}
|
|
879
|
+
to {
|
|
880
|
+
opacity: 1;
|
|
881
|
+
transform: scale(1);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
.clarity-callout__arrow {
|
|
886
|
+
position: absolute;
|
|
887
|
+
width: 16px;
|
|
888
|
+
height: 16px;
|
|
889
|
+
background: white;
|
|
890
|
+
transform: rotate(45deg);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/* Arrow positions */
|
|
894
|
+
.clarity-callout--top .clarity-callout__arrow {
|
|
895
|
+
bottom: -8px;
|
|
896
|
+
left: 50%;
|
|
897
|
+
margin-left: -8px;
|
|
898
|
+
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.clarity-callout--bottom .clarity-callout__arrow {
|
|
902
|
+
top: -8px;
|
|
903
|
+
left: 50%;
|
|
904
|
+
margin-left: -8px;
|
|
905
|
+
box-shadow: -2px -2px 4px rgba(0, 0, 0, 0.1);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.clarity-callout--left .clarity-callout__arrow {
|
|
909
|
+
right: -8px;
|
|
910
|
+
top: 50%;
|
|
911
|
+
margin-top: -8px;
|
|
912
|
+
box-shadow: 2px -2px 4px rgba(0, 0, 0, 0.1);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
.clarity-callout--right .clarity-callout__arrow {
|
|
916
|
+
left: -8px;
|
|
917
|
+
top: 50%;
|
|
918
|
+
margin-top: -8px;
|
|
919
|
+
box-shadow: -2px 2px 4px rgba(0, 0, 0, 0.1);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
.clarity-callout__content {
|
|
923
|
+
position: relative;
|
|
924
|
+
z-index: 1;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.clarity-callout__rank {
|
|
928
|
+
display: inline-flex;
|
|
929
|
+
align-items: center;
|
|
930
|
+
justify-content: center;
|
|
931
|
+
width: 32px;
|
|
932
|
+
height: 32px;
|
|
933
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
934
|
+
color: white;
|
|
935
|
+
border-radius: 8px;
|
|
936
|
+
font-weight: 600;
|
|
937
|
+
font-size: 16px;
|
|
938
|
+
margin-bottom: 12px;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
.clarity-callout--secondary .clarity-callout__rank {
|
|
942
|
+
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
.clarity-callout__stats {
|
|
946
|
+
margin-bottom: 12px;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
.clarity-callout__label {
|
|
950
|
+
font-size: 12px;
|
|
951
|
+
color: #6b7280;
|
|
952
|
+
margin-bottom: 4px;
|
|
953
|
+
font-weight: 500;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
.clarity-callout__value {
|
|
957
|
+
display: flex;
|
|
958
|
+
align-items: baseline;
|
|
959
|
+
gap: 6px;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
.clarity-callout__count {
|
|
963
|
+
font-size: 20px;
|
|
964
|
+
font-weight: 700;
|
|
965
|
+
color: #111827;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
.clarity-callout__percentage {
|
|
969
|
+
font-size: 14px;
|
|
970
|
+
color: #6b7280;
|
|
971
|
+
font-weight: 500;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
.clarity-callout__button {
|
|
975
|
+
display: flex;
|
|
976
|
+
align-items: center;
|
|
977
|
+
justify-content: center;
|
|
978
|
+
gap: 6px;
|
|
979
|
+
width: 100%;
|
|
980
|
+
padding: 8px 12px;
|
|
981
|
+
background: #667eea;
|
|
982
|
+
color: white;
|
|
983
|
+
border: none;
|
|
984
|
+
border-radius: 6px;
|
|
985
|
+
font-size: 13px;
|
|
986
|
+
font-weight: 600;
|
|
987
|
+
cursor: pointer;
|
|
988
|
+
transition: all 0.2s;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
.clarity-callout__button:hover {
|
|
992
|
+
background: #5568d3;
|
|
993
|
+
transform: translateY(-1px);
|
|
994
|
+
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
.clarity-callout__button:active {
|
|
998
|
+
transform: translateY(0);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.clarity-callout__button svg {
|
|
1002
|
+
width: 14px;
|
|
1003
|
+
height: 14px;
|
|
1004
|
+
}
|
|
1005
|
+
` })] }));
|
|
1006
|
+
// Render to body using Portal
|
|
1007
|
+
return createPortal(calloutContent, document.body);
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
const RankBadge = ({ index, elementRect, widthScale, clickOnElement, }) => {
|
|
1011
|
+
const style = calculateRankPosition(elementRect, widthScale);
|
|
1012
|
+
return (jsx("div", { className: "rankBadge", style: style, onClick: clickOnElement, "data-testid": "elementRank", children: index }));
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
const ClickedElementOverlay = ({ element, shouldShowCallout, isSecondary, targetId, totalClicks = 1, isRecordingView, isCompareMode, deviceType, heatmapType, widthScale, }) => {
|
|
1016
|
+
if (!element || (element.width === 0 && element.height === 0))
|
|
1017
|
+
return null;
|
|
1018
|
+
return (jsxs(Fragment$1, { children: [jsx("div", { className: "heatmapElement", id: targetId, style: {
|
|
1019
|
+
top: element.top,
|
|
1020
|
+
left: element.left,
|
|
1021
|
+
width: element.width,
|
|
1022
|
+
height: element.height,
|
|
1023
|
+
} }), jsx(RankBadge, { index: element.rank, elementRect: element, widthScale: widthScale }), shouldShowCallout && (jsx(ElementCallout, { element: element, target: `#${targetId}`, totalClicks: totalClicks, isSecondary: isSecondary, isRecordingView: isRecordingView, isCompareMode: isCompareMode, deviceType: deviceType, heatmapType: heatmapType, widthScale: widthScale }))] }));
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
const DefaultRankBadges = ({ elements, getRect, widthScale, hidden }) => {
|
|
1027
|
+
if (hidden || elements.length === 0)
|
|
1028
|
+
return null;
|
|
1029
|
+
return (jsx(Fragment, { children: elements.map((element, index) => {
|
|
1030
|
+
const rect = getRect(element);
|
|
1031
|
+
if (!rect)
|
|
1032
|
+
return null;
|
|
1033
|
+
return (jsx(RankBadge, { index: index + 1, elementRect: rect, widthScale: widthScale }, element.hash));
|
|
1034
|
+
}) }));
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
const HoveredElementOverlay = ({ element, onClick, isSecondary, targetId, totalClicks = 1, }) => {
|
|
1038
|
+
if (!element)
|
|
1039
|
+
return null;
|
|
1040
|
+
return (jsxs(Fragment$1, { children: [jsx("div", { onClick: onClick, className: "heatmapElement hovered", id: targetId, style: {
|
|
1041
|
+
top: element.top,
|
|
1042
|
+
left: element.left,
|
|
1043
|
+
width: element.width,
|
|
1044
|
+
height: element.height,
|
|
1045
|
+
cursor: 'pointer',
|
|
1046
|
+
} }), jsx(RankBadge, { index: element.rank, elementRect: element, widthScale: 1, clickOnElement: onClick })] }));
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
const MissingElementMessage = ({ widthScale }) => {
|
|
1050
|
+
return (jsx("div", { className: "missingElement", style: {
|
|
1051
|
+
position: 'absolute',
|
|
1052
|
+
top: '50%',
|
|
1053
|
+
left: '50%',
|
|
1054
|
+
transform: `translate(-50%, -50%) scale(${1 / widthScale})`,
|
|
1055
|
+
background: 'rgba(0, 0, 0, 0.8)',
|
|
1056
|
+
color: 'white',
|
|
1057
|
+
padding: '12px 20px',
|
|
1058
|
+
borderRadius: '8px',
|
|
1059
|
+
fontSize: '14px',
|
|
1060
|
+
fontWeight: '500',
|
|
1061
|
+
zIndex: 9999,
|
|
1062
|
+
pointerEvents: 'none',
|
|
1063
|
+
whiteSpace: 'nowrap',
|
|
1064
|
+
}, "aria-live": "assertive", children: "Element not visible on current screen" }));
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
const HeatmapElements = (props) => {
|
|
1068
|
+
const { iframeRef, parentRef, visualizer, heatmapInfo, widthScale, iframeHeight, iframeDimensions, selectedElement, isElementSidebarOpen, isVisible = true, selectElement, areDefaultRanksHidden, isSecondary, ...rest } = props;
|
|
1069
|
+
const getRect = useHeatmapElementPosition({
|
|
1070
|
+
iframeRef,
|
|
1071
|
+
parentRef,
|
|
1072
|
+
visualizer,
|
|
1073
|
+
heatmapWidth: heatmapInfo?.width,
|
|
1074
|
+
iframeHeight,
|
|
1075
|
+
widthScale,
|
|
1076
|
+
projectId: props.projectId,
|
|
1077
|
+
});
|
|
1078
|
+
const { clickedElement, showMissingElement, shouldShowCallout, setShouldShowCallout } = useClickedElement({
|
|
1079
|
+
selectedElement,
|
|
1080
|
+
heatmapInfo,
|
|
1081
|
+
getRect,
|
|
1082
|
+
});
|
|
1083
|
+
const { hoveredElement, handleMouseMove, handleMouseLeave, handleClick } = useHoveredElement({
|
|
1084
|
+
iframeRef,
|
|
1085
|
+
heatmapInfo,
|
|
1086
|
+
getRect,
|
|
1087
|
+
onSelect: selectElement,
|
|
1088
|
+
widthScale,
|
|
1089
|
+
});
|
|
1090
|
+
const resetAll = () => {
|
|
1091
|
+
// nếu cần reset thêm state ở đây
|
|
1092
|
+
// setShouldShowCallout(false);
|
|
1093
|
+
};
|
|
1094
|
+
useHeatmapEffects({
|
|
1095
|
+
isVisible,
|
|
1096
|
+
isElementSidebarOpen,
|
|
1097
|
+
selectedElement,
|
|
1098
|
+
setShouldShowCallout,
|
|
1099
|
+
resetAll,
|
|
1100
|
+
});
|
|
1101
|
+
if (!isVisible)
|
|
1102
|
+
return null;
|
|
1103
|
+
const top10 = heatmapInfo?.sortedElements?.slice(0, 10) ?? [];
|
|
1104
|
+
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 }))] }));
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
const VizElements = ({ width, height, iframeRef, wrapperRef, widthScale, }) => {
|
|
1108
|
+
useHeatmapDataStore((state) => state.data);
|
|
1109
|
+
const heatmapInfo = {
|
|
1110
|
+
sortedElements: [
|
|
1111
|
+
{
|
|
1112
|
+
hash: '9ebwu6a3',
|
|
1113
|
+
selector: 'Join our email list',
|
|
1114
|
+
},
|
|
1115
|
+
{
|
|
1116
|
+
hash: '350hde5d4',
|
|
1117
|
+
selector: 'Products',
|
|
1118
|
+
},
|
|
1119
|
+
],
|
|
1120
|
+
elementMapInfo: {
|
|
1121
|
+
'9ebwu6a3': {
|
|
1122
|
+
totalclicks: 4,
|
|
1123
|
+
hash: '9ebwu6a3',
|
|
1124
|
+
},
|
|
1125
|
+
'350hde5d4': {
|
|
1126
|
+
totalclicks: 4,
|
|
1127
|
+
hash: '350hde5d4',
|
|
1128
|
+
},
|
|
1129
|
+
},
|
|
1130
|
+
totalClicks: 8,
|
|
1131
|
+
};
|
|
1132
|
+
const visualizer = {
|
|
1133
|
+
get: (hash) => {
|
|
1134
|
+
const doc = iframeRef.current?.contentDocument;
|
|
1135
|
+
if (!doc)
|
|
1136
|
+
return null;
|
|
1137
|
+
// Find element by hash attribute
|
|
1138
|
+
return doc.querySelector(`[data-clarity-hashalpha="${hash}"]`);
|
|
1139
|
+
},
|
|
1140
|
+
};
|
|
1141
|
+
const [selectedElement, setSelectedElement] = useState(null);
|
|
1142
|
+
if (!iframeRef.current)
|
|
1143
|
+
return null;
|
|
1144
|
+
return (jsx(HeatmapElements, { visualizer: visualizer, iframeRef: iframeRef, parentRef: wrapperRef, iframeHeight: window.innerHeight, widthScale: widthScale, heatmapInfo: heatmapInfo, selectedElement: selectedElement, selectElement: setSelectedElement, isVisible: true, iframeDimensions: {
|
|
1145
|
+
width,
|
|
1146
|
+
height,
|
|
1147
|
+
position: 'absolute',
|
|
1148
|
+
top: 0,
|
|
1149
|
+
left: 0,
|
|
1150
|
+
// pointerEvents: 'none',
|
|
1151
|
+
} }));
|
|
535
1152
|
};
|
|
536
1153
|
|
|
537
1154
|
const ReplayControls = () => {
|
|
@@ -597,7 +1214,7 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
|
|
|
597
1214
|
height: iframeHeight,
|
|
598
1215
|
transform: `scale(${scale})`,
|
|
599
1216
|
transformOrigin: 'top center',
|
|
600
|
-
}, children: [jsx(VizElements, { width: contentWidth, height: iframeHeight }), jsx("iframe", {
|
|
1217
|
+
}, children: [jsx(VizElements, { width: contentWidth, height: iframeHeight, widthScale: scale, iframeRef: iframeRef, wrapperRef: wrapperRef }), jsx("iframe", {
|
|
601
1218
|
// key={iframeKey}
|
|
602
1219
|
ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, height: iframeHeight, scrolling: "no" })] }) }), mode === 'replay' && jsx(ReplayControls, {})] }));
|
|
603
1220
|
};
|
|
@@ -636,15 +1253,33 @@ const WrapperPreview = ({ children }) => {
|
|
|
636
1253
|
return (jsxs("div", { className: "gx-hm-container", style: { display: 'flex', overflowY: 'hidden', flex: '1', position: 'relative' }, children: [jsx(LeftSidebar, { children: children }), jsx(VizDomContainer, {})] }));
|
|
637
1254
|
};
|
|
638
1255
|
|
|
639
|
-
const WrapperLayout = ({ header, sidebar }) => {
|
|
640
|
-
return (jsxs(BoxStack, { id: "gx-hm-layout", flexDirection: "column", flex: "1", children: [jsx(ContentHeader, { children: header }), jsx(WrapperPreview, { children: sidebar })] }));
|
|
1256
|
+
const WrapperLayout = ({ header, toolbar, sidebar }) => {
|
|
1257
|
+
return (jsxs(BoxStack, { id: "gx-hm-layout", flexDirection: "column", flex: "1", children: [jsx(ContentHeader, { children: header }), jsx(ContentHeader, { children: toolbar }), jsx(WrapperPreview, { children: sidebar })] }));
|
|
641
1258
|
};
|
|
642
1259
|
|
|
643
|
-
const HeatmapLayout = ({ header, sidebar }) => {
|
|
1260
|
+
const HeatmapLayout = ({ data, clickmap, header, toolbar, sidebar, }) => {
|
|
1261
|
+
const setData = useHeatmapDataStore((state) => state.setData);
|
|
1262
|
+
const setClickmap = useHeatmapDataStore((state) => state.setClickmap);
|
|
1263
|
+
const handleSetClickmap = useCallback((clickmap) => {
|
|
1264
|
+
if (!clickmap)
|
|
1265
|
+
return;
|
|
1266
|
+
setClickmap(clickmap);
|
|
1267
|
+
}, [clickmap]);
|
|
1268
|
+
const handleSetData = useCallback((data) => {
|
|
1269
|
+
if (!data)
|
|
1270
|
+
return;
|
|
1271
|
+
setData(data);
|
|
1272
|
+
}, [data]);
|
|
1273
|
+
useEffect(() => {
|
|
1274
|
+
handleSetData(data);
|
|
1275
|
+
}, [data]);
|
|
1276
|
+
useEffect(() => {
|
|
1277
|
+
handleSetClickmap(clickmap);
|
|
1278
|
+
}, [clickmap]);
|
|
644
1279
|
return (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: {
|
|
645
1280
|
minHeight: '100%',
|
|
646
1281
|
display: 'flex',
|
|
647
|
-
}, children: jsx(WrapperLayout, { header: header, sidebar: sidebar }) }) }) }));
|
|
1282
|
+
}, children: jsx(WrapperLayout, { header: header, toolbar: toolbar, sidebar: sidebar }) }) }) }));
|
|
648
1283
|
};
|
|
649
1284
|
|
|
650
1285
|
var PanelContent;
|