@gemx-dev/heatmap-react 3.5.53 → 3.5.54
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 +2 -1
- package/dist/esm/components/Layout/HeatmapLayout.d.ts.map +1 -1
- package/dist/esm/components/Layout/HeatmapPreview.d.ts.map +1 -1
- package/dist/esm/components/Layout/TopBar/ContentTopBar.d.ts.map +1 -1
- package/dist/esm/components/Layout/VizByMode/ContentVizByMode.d.ts.map +1 -1
- package/dist/esm/components/VizAreaClick/VizAreaClick.d.ts.map +1 -1
- package/dist/esm/components/VizDom/VizDomHeatmap.d.ts.map +1 -1
- package/dist/esm/components/VizDom/VizDomRenderer.d.ts.map +1 -1
- package/dist/esm/components/VizElement/DefaultRankBadges.d.ts.map +1 -1
- package/dist/esm/components/VizElement/RankBadge.d.ts.map +1 -1
- package/dist/esm/components/VizScrollmap/HoverZones.d.ts.map +1 -1
- package/dist/esm/components/VizScrollmap/ScrollMapOverlay.d.ts.map +1 -1
- package/dist/esm/components/VizScrollmapV2/ScrollmapOverlayV2.d.ts.map +1 -1
- package/dist/esm/components/VizScrollmapV2/useScrollmapOverlay.d.ts.map +1 -1
- package/dist/esm/helpers/iframe-helper/fixer.d.ts.map +1 -1
- package/dist/esm/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -1
- package/dist/esm/helpers/iframe-helper/style-replacer.d.ts.map +1 -1
- package/dist/esm/helpers/viz-area-click/area-builder.d.ts +2 -2
- package/dist/esm/helpers/viz-area-click/area-builder.d.ts.map +1 -1
- package/dist/esm/helpers/viz-area-click/area-color.d.ts.map +1 -1
- package/dist/esm/helpers/viz-area-click/area-graph.d.ts +63 -0
- package/dist/esm/helpers/viz-area-click/area-graph.d.ts.map +1 -0
- package/dist/esm/helpers/viz-area-click/area-hydration.d.ts +36 -0
- package/dist/esm/helpers/viz-area-click/area-hydration.d.ts.map +1 -0
- package/dist/esm/helpers/viz-area-click/area-overlap.d.ts.map +1 -1
- package/dist/esm/helpers/viz-area-click/index.d.ts +2 -0
- package/dist/esm/helpers/viz-area-click/index.d.ts.map +1 -1
- package/dist/esm/helpers/viz-canvas/area-clustering.d.ts.map +1 -1
- package/dist/esm/helpers/viz-canvas/area-overlay-manager-v2.d.ts.map +1 -1
- package/dist/esm/helpers/viz-canvas/hierarchical-area-clustering.d.ts.map +1 -1
- package/dist/esm/helpers/viz-dom/find-elm.d.ts +8 -0
- package/dist/esm/helpers/viz-dom/find-elm.d.ts.map +1 -0
- package/dist/esm/helpers/viz-dom/index.d.ts +1 -0
- package/dist/esm/helpers/viz-dom/index.d.ts.map +1 -1
- package/dist/esm/helpers/viz-elm-callout/dimensions.d.ts.map +1 -1
- package/dist/esm/helpers/viz-elm-callout/position-candidates.d.ts.map +1 -1
- package/dist/esm/helpers/viz-elm-callout/position-selector.d.ts.map +1 -1
- package/dist/esm/helpers/viz-elm-callout/position-validator.d.ts.map +1 -1
- package/dist/esm/hooks/register/useRegisterHeatmap.d.ts +3 -2
- package/dist/esm/hooks/register/useRegisterHeatmap.d.ts.map +1 -1
- package/dist/esm/hooks/view-context/useHeatmapClick.d.ts.map +1 -1
- package/dist/esm/hooks/view-context/useHeatmapData.d.ts +4 -1
- package/dist/esm/hooks/view-context/useHeatmapData.d.ts.map +1 -1
- package/dist/esm/hooks/viz-area-click/index.d.ts +1 -0
- package/dist/esm/hooks/viz-area-click/index.d.ts.map +1 -1
- package/dist/esm/hooks/viz-area-click/useAreaCreation.d.ts.map +1 -1
- package/dist/esm/hooks/viz-area-click/useAreaEditMode.d.ts.map +1 -1
- package/dist/esm/hooks/viz-area-click/useAreaFilterVisible.d.ts.map +1 -1
- package/dist/esm/hooks/viz-area-click/useAreaHydration.d.ts +9 -0
- package/dist/esm/hooks/viz-area-click/useAreaHydration.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaInteraction.d.ts +0 -6
- package/dist/esm/hooks/viz-area-click/useAreaInteraction.d.ts.map +1 -1
- package/dist/esm/hooks/viz-area-click/useAreaRectSync.d.ts.map +1 -1
- package/dist/esm/hooks/viz-area-click/useAreaTopAutoDetect.d.ts +1 -1
- package/dist/esm/hooks/viz-area-click/useAreaTopAutoDetect.d.ts.map +1 -1
- package/dist/esm/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
- package/dist/esm/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useContainerDimensions.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useContentDimensions.d.ts +1 -1
- package/dist/esm/hooks/viz-scale/useContentDimensions.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts +0 -3
- package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useObserveIframeHeight.d.ts +0 -1
- package/dist/esm/hooks/viz-scale/useObserveIframeHeight.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useScaleCalculation.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useScrollSync.d.ts +1 -1
- package/dist/esm/hooks/viz-scale/useScrollSync.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useWrapperRefHeight.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scroll/useScrollmapZones.d.ts.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +990 -721
- package/dist/esm/index.mjs +990 -721
- package/dist/esm/performance/hooks.d.ts.map +1 -1
- package/dist/esm/performance/performance-logger.d.ts.map +1 -1
- package/dist/esm/performance/types.d.ts.map +1 -1
- package/dist/esm/performance/utils.d.ts.map +1 -1
- package/dist/esm/stores/comp.d.ts.map +1 -1
- package/dist/esm/stores/data.d.ts +4 -1
- package/dist/esm/stores/data.d.ts.map +1 -1
- package/dist/esm/stores/mode-compare.d.ts.map +1 -1
- package/dist/esm/types/viz-area-click.d.ts +14 -0
- package/dist/esm/types/viz-area-click.d.ts.map +1 -1
- package/dist/esm/ui/BoxStack/BoxStack.d.ts.map +1 -1
- package/dist/esm/utils/retry.d.ts.map +1 -1
- package/dist/style.css +9 -11
- package/dist/umd/components/Layout/HeatmapLayout.d.ts +2 -1
- package/dist/umd/components/Layout/HeatmapLayout.d.ts.map +1 -1
- package/dist/umd/components/Layout/HeatmapPreview.d.ts.map +1 -1
- package/dist/umd/components/Layout/TopBar/ContentTopBar.d.ts.map +1 -1
- package/dist/umd/components/Layout/VizByMode/ContentVizByMode.d.ts.map +1 -1
- package/dist/umd/components/VizAreaClick/VizAreaClick.d.ts.map +1 -1
- package/dist/umd/components/VizDom/VizDomHeatmap.d.ts.map +1 -1
- package/dist/umd/components/VizDom/VizDomRenderer.d.ts.map +1 -1
- package/dist/umd/components/VizElement/DefaultRankBadges.d.ts.map +1 -1
- package/dist/umd/components/VizElement/RankBadge.d.ts.map +1 -1
- package/dist/umd/components/VizScrollmap/HoverZones.d.ts.map +1 -1
- package/dist/umd/components/VizScrollmap/ScrollMapOverlay.d.ts.map +1 -1
- package/dist/umd/components/VizScrollmapV2/ScrollmapOverlayV2.d.ts.map +1 -1
- package/dist/umd/components/VizScrollmapV2/useScrollmapOverlay.d.ts.map +1 -1
- package/dist/umd/helpers/iframe-helper/fixer.d.ts.map +1 -1
- package/dist/umd/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -1
- package/dist/umd/helpers/iframe-helper/style-replacer.d.ts.map +1 -1
- package/dist/umd/helpers/viz-area-click/area-builder.d.ts +2 -2
- package/dist/umd/helpers/viz-area-click/area-builder.d.ts.map +1 -1
- package/dist/umd/helpers/viz-area-click/area-color.d.ts.map +1 -1
- package/dist/umd/helpers/viz-area-click/area-graph.d.ts +63 -0
- package/dist/umd/helpers/viz-area-click/area-graph.d.ts.map +1 -0
- package/dist/umd/helpers/viz-area-click/area-hydration.d.ts +36 -0
- package/dist/umd/helpers/viz-area-click/area-hydration.d.ts.map +1 -0
- package/dist/umd/helpers/viz-area-click/area-overlap.d.ts.map +1 -1
- package/dist/umd/helpers/viz-area-click/index.d.ts +2 -0
- package/dist/umd/helpers/viz-area-click/index.d.ts.map +1 -1
- package/dist/umd/helpers/viz-canvas/area-clustering.d.ts.map +1 -1
- package/dist/umd/helpers/viz-canvas/area-overlay-manager-v2.d.ts.map +1 -1
- package/dist/umd/helpers/viz-canvas/hierarchical-area-clustering.d.ts.map +1 -1
- package/dist/umd/helpers/viz-dom/find-elm.d.ts +8 -0
- package/dist/umd/helpers/viz-dom/find-elm.d.ts.map +1 -0
- package/dist/umd/helpers/viz-dom/index.d.ts +1 -0
- package/dist/umd/helpers/viz-dom/index.d.ts.map +1 -1
- package/dist/umd/helpers/viz-elm-callout/dimensions.d.ts.map +1 -1
- package/dist/umd/helpers/viz-elm-callout/position-candidates.d.ts.map +1 -1
- package/dist/umd/helpers/viz-elm-callout/position-selector.d.ts.map +1 -1
- package/dist/umd/helpers/viz-elm-callout/position-validator.d.ts.map +1 -1
- package/dist/umd/hooks/register/useRegisterHeatmap.d.ts +3 -2
- package/dist/umd/hooks/register/useRegisterHeatmap.d.ts.map +1 -1
- package/dist/umd/hooks/view-context/useHeatmapClick.d.ts.map +1 -1
- package/dist/umd/hooks/view-context/useHeatmapData.d.ts +4 -1
- package/dist/umd/hooks/view-context/useHeatmapData.d.ts.map +1 -1
- package/dist/umd/hooks/viz-area-click/index.d.ts +1 -0
- package/dist/umd/hooks/viz-area-click/index.d.ts.map +1 -1
- package/dist/umd/hooks/viz-area-click/useAreaCreation.d.ts.map +1 -1
- package/dist/umd/hooks/viz-area-click/useAreaEditMode.d.ts.map +1 -1
- package/dist/umd/hooks/viz-area-click/useAreaFilterVisible.d.ts.map +1 -1
- package/dist/umd/hooks/viz-area-click/useAreaHydration.d.ts +9 -0
- package/dist/umd/hooks/viz-area-click/useAreaHydration.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaInteraction.d.ts +0 -6
- package/dist/umd/hooks/viz-area-click/useAreaInteraction.d.ts.map +1 -1
- package/dist/umd/hooks/viz-area-click/useAreaRectSync.d.ts.map +1 -1
- package/dist/umd/hooks/viz-area-click/useAreaTopAutoDetect.d.ts +1 -1
- package/dist/umd/hooks/viz-area-click/useAreaTopAutoDetect.d.ts.map +1 -1
- package/dist/umd/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
- package/dist/umd/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useContainerDimensions.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useContentDimensions.d.ts +1 -1
- package/dist/umd/hooks/viz-scale/useContentDimensions.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts +0 -3
- package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useObserveIframeHeight.d.ts +0 -1
- package/dist/umd/hooks/viz-scale/useObserveIframeHeight.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useScaleCalculation.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useScrollSync.d.ts +1 -1
- package/dist/umd/hooks/viz-scale/useScrollSync.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useWrapperRefHeight.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scroll/useScrollmapZones.d.ts.map +1 -1
- package/dist/umd/index.d.ts +1 -0
- package/dist/umd/index.d.ts.map +1 -1
- package/dist/umd/index.js +2 -2
- package/dist/umd/performance/hooks.d.ts.map +1 -1
- package/dist/umd/performance/performance-logger.d.ts.map +1 -1
- package/dist/umd/performance/types.d.ts.map +1 -1
- package/dist/umd/performance/utils.d.ts.map +1 -1
- package/dist/umd/stores/comp.d.ts.map +1 -1
- package/dist/umd/stores/data.d.ts +4 -1
- package/dist/umd/stores/data.d.ts.map +1 -1
- package/dist/umd/stores/mode-compare.d.ts.map +1 -1
- package/dist/umd/types/viz-area-click.d.ts +14 -0
- package/dist/umd/types/viz-area-click.d.ts.map +1 -1
- package/dist/umd/ui/BoxStack/BoxStack.d.ts.map +1 -1
- package/dist/umd/utils/retry.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/esm/index.mjs
CHANGED
|
@@ -186,8 +186,20 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
|
|
|
186
186
|
return {
|
|
187
187
|
data: { [DEFAULT_VIEW_ID]: undefined },
|
|
188
188
|
clickmap: { [DEFAULT_VIEW_ID]: undefined },
|
|
189
|
+
clickAreas: { [DEFAULT_VIEW_ID]: undefined },
|
|
189
190
|
dataInfo: { [DEFAULT_VIEW_ID]: undefined },
|
|
190
191
|
scrollmap: { [DEFAULT_VIEW_ID]: undefined },
|
|
192
|
+
setClickAreas: (clickAreas, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
193
|
+
clickAreas: { ...state.clickAreas, [viewId]: clickAreas },
|
|
194
|
+
})),
|
|
195
|
+
removeClickArea: (areaId, viewId = DEFAULT_VIEW_ID) => set((state) => {
|
|
196
|
+
const currentAreas = state.clickAreas[viewId] || [];
|
|
197
|
+
const filtered = currentAreas.filter((a) => a.id !== areaId);
|
|
198
|
+
console.log(`🚀 🐥 ~ filtered:`, filtered);
|
|
199
|
+
return {
|
|
200
|
+
clickAreas: { ...state.clickAreas, [viewId]: filtered },
|
|
201
|
+
};
|
|
202
|
+
}),
|
|
191
203
|
setDataInfo: (dataInfo, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
192
204
|
dataInfo: { ...state.dataInfo, [viewId]: dataInfo },
|
|
193
205
|
})),
|
|
@@ -203,21 +215,25 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
|
|
|
203
215
|
copyView: (fromViewId, toViewId) => set((state) => ({
|
|
204
216
|
data: { ...state.data, [toViewId]: state.data[fromViewId] },
|
|
205
217
|
clickmap: { ...state.clickmap, [toViewId]: state.clickmap[fromViewId] },
|
|
218
|
+
clickAreas: { ...state.clickAreas, [toViewId]: state.clickAreas[fromViewId] },
|
|
206
219
|
dataInfo: { ...state.dataInfo, [toViewId]: state.dataInfo[fromViewId] },
|
|
207
220
|
scrollmap: { ...state.scrollmap, [toViewId]: state.scrollmap[fromViewId] },
|
|
208
221
|
})),
|
|
209
222
|
clearView: (viewId) => set((state) => {
|
|
210
223
|
const newData = { ...state.data };
|
|
211
224
|
const newClickmap = { ...state.clickmap };
|
|
225
|
+
const newClickAreas = { ...state.clickAreas };
|
|
212
226
|
const newDataInfo = { ...state.dataInfo };
|
|
213
227
|
const newScrollmap = { ...state.scrollmap };
|
|
214
228
|
delete newData[viewId];
|
|
215
229
|
delete newClickmap[viewId];
|
|
230
|
+
delete newClickAreas[viewId];
|
|
216
231
|
delete newDataInfo[viewId];
|
|
217
232
|
delete newScrollmap[viewId];
|
|
218
233
|
return {
|
|
219
234
|
data: newData,
|
|
220
235
|
clickmap: newClickmap,
|
|
236
|
+
clickAreas: newClickAreas,
|
|
221
237
|
dataInfo: newDataInfo,
|
|
222
238
|
scrollmap: newScrollmap,
|
|
223
239
|
};
|
|
@@ -225,6 +241,7 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
|
|
|
225
241
|
resetAll: () => set({
|
|
226
242
|
data: { [DEFAULT_VIEW_ID]: undefined },
|
|
227
243
|
clickmap: { [DEFAULT_VIEW_ID]: undefined },
|
|
244
|
+
clickAreas: { [DEFAULT_VIEW_ID]: undefined },
|
|
228
245
|
dataInfo: { [DEFAULT_VIEW_ID]: undefined },
|
|
229
246
|
scrollmap: { [DEFAULT_VIEW_ID]: undefined },
|
|
230
247
|
}),
|
|
@@ -787,13 +804,7 @@ const useHeatmapClick = (props) => {
|
|
|
787
804
|
setSelectedElement: (element) => setSelectedElementStore(element, viewId),
|
|
788
805
|
setHoveredElement: (element) => setHoveredElementStore(element, viewId),
|
|
789
806
|
setShouldShowCallout: (value) => setShouldShowCalloutStore(value, viewId),
|
|
790
|
-
}), [
|
|
791
|
-
setStateStore,
|
|
792
|
-
setSelectedElementStore,
|
|
793
|
-
setHoveredElementStore,
|
|
794
|
-
setShouldShowCalloutStore,
|
|
795
|
-
viewId,
|
|
796
|
-
]);
|
|
807
|
+
}), [setStateStore, setSelectedElementStore, setHoveredElementStore, setShouldShowCalloutStore, viewId]);
|
|
797
808
|
return {
|
|
798
809
|
state,
|
|
799
810
|
selectedElement,
|
|
@@ -808,24 +819,29 @@ const useHeatmapData = (props) => {
|
|
|
808
819
|
const viewId = props?.viewId || useViewIdContext();
|
|
809
820
|
const data = useHeatmapDataStore((state) => state.data[viewId]);
|
|
810
821
|
const clickmap = useHeatmapDataStore((state) => state.clickmap[viewId]);
|
|
822
|
+
const clickAreas = useHeatmapDataStore((state) => state.clickAreas[viewId]);
|
|
811
823
|
const scrollmap = useHeatmapDataStore((state) => state.scrollmap[viewId]);
|
|
812
824
|
const dataInfo = useHeatmapDataStore((state) => state.dataInfo[viewId]);
|
|
813
825
|
const setData = useHeatmapDataStore((state) => state.setData);
|
|
814
826
|
const setClickmap = useHeatmapDataStore((state) => state.setClickmap);
|
|
827
|
+
const setClickAreas = useHeatmapDataStore((state) => state.setClickAreas);
|
|
815
828
|
const setScrollmap = useHeatmapDataStore((state) => state.setScrollmap);
|
|
816
829
|
const setDataInfo = useHeatmapDataStore((state) => state.setDataInfo);
|
|
830
|
+
const removeClickArea = useHeatmapDataStore((state) => state.removeClickArea);
|
|
817
831
|
const memoizedSetters = useMemo(() => ({
|
|
818
832
|
setData: (newData) => setData(newData, viewId),
|
|
819
833
|
setClickmap: (newClickmap) => setClickmap(newClickmap, viewId),
|
|
834
|
+
setClickAreas: (newClickAreas) => setClickAreas(newClickAreas, viewId),
|
|
820
835
|
setScrollmap: (newScrollmap) => setScrollmap(newScrollmap, viewId),
|
|
821
836
|
setDataInfo: (newDataInfo) => setDataInfo(newDataInfo, viewId),
|
|
822
|
-
|
|
837
|
+
removeClickArea: (areaId) => removeClickArea(areaId, viewId),
|
|
838
|
+
}), [setData, setClickmap, setClickAreas, setScrollmap, setDataInfo, removeClickArea, viewId]);
|
|
823
839
|
return {
|
|
824
840
|
data,
|
|
825
841
|
clickmap,
|
|
842
|
+
clickAreas,
|
|
826
843
|
scrollmap,
|
|
827
844
|
dataInfo,
|
|
828
|
-
// Setters (auto-inject viewId)
|
|
829
845
|
...memoizedSetters,
|
|
830
846
|
};
|
|
831
847
|
};
|
|
@@ -998,24 +1014,32 @@ const useRegisterData = (data, dataInfo) => {
|
|
|
998
1014
|
}, [dataInfo]);
|
|
999
1015
|
};
|
|
1000
1016
|
|
|
1001
|
-
const useRegisterHeatmap = ({ clickmap, scrollmap }) => {
|
|
1002
|
-
const { setClickmap, setScrollmap } = useHeatmapData();
|
|
1003
|
-
const handleSetClickmap = useCallback((
|
|
1017
|
+
const useRegisterHeatmap = ({ clickmap, scrollmap, clickAreas }) => {
|
|
1018
|
+
const { setClickmap, setScrollmap, setClickAreas } = useHeatmapData();
|
|
1019
|
+
const handleSetClickmap = useCallback(() => {
|
|
1004
1020
|
if (!clickmap)
|
|
1005
1021
|
return;
|
|
1006
1022
|
setClickmap(clickmap);
|
|
1007
|
-
}, [clickmap]);
|
|
1008
|
-
const
|
|
1023
|
+
}, [clickmap]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
1024
|
+
const handleSetClickAreas = useCallback(() => {
|
|
1025
|
+
if (!clickAreas)
|
|
1026
|
+
return;
|
|
1027
|
+
setClickAreas(clickAreas);
|
|
1028
|
+
}, [clickAreas]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
1029
|
+
const handleSetScrollmap = useCallback(() => {
|
|
1009
1030
|
if (!scrollmap)
|
|
1010
1031
|
return;
|
|
1011
1032
|
setScrollmap(scrollmap);
|
|
1012
|
-
}, [scrollmap]);
|
|
1033
|
+
}, [scrollmap]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
1013
1034
|
useEffect(() => {
|
|
1014
|
-
handleSetClickmap(
|
|
1015
|
-
}, [
|
|
1035
|
+
handleSetClickmap();
|
|
1036
|
+
}, [handleSetClickmap]);
|
|
1016
1037
|
useEffect(() => {
|
|
1017
|
-
|
|
1018
|
-
}, [
|
|
1038
|
+
handleSetClickAreas();
|
|
1039
|
+
}, [handleSetClickAreas]);
|
|
1040
|
+
useEffect(() => {
|
|
1041
|
+
handleSetScrollmap();
|
|
1042
|
+
}, [handleSetScrollmap]);
|
|
1019
1043
|
};
|
|
1020
1044
|
|
|
1021
1045
|
/**
|
|
@@ -1078,6 +1102,52 @@ function isIgnoredCanvas(element) {
|
|
|
1078
1102
|
return false;
|
|
1079
1103
|
}
|
|
1080
1104
|
|
|
1105
|
+
/**
|
|
1106
|
+
* Get all elements at a specific point (x, y), with support for Shadow DOM
|
|
1107
|
+
*/
|
|
1108
|
+
function getElementsAtPoint(doc, x, y, options = {}) {
|
|
1109
|
+
const { filterFn, ignoreCanvas = true, visitedShadowRoots = new Set() } = options;
|
|
1110
|
+
// Get all elements at this point
|
|
1111
|
+
let elementsAtPoint = doc.elementsFromPoint(x, y);
|
|
1112
|
+
// Filter out canvas elements if requested
|
|
1113
|
+
if (ignoreCanvas) {
|
|
1114
|
+
elementsAtPoint = elementsAtPoint.filter((el) => !isIgnoredCanvas(el));
|
|
1115
|
+
}
|
|
1116
|
+
// Apply custom filter if provided
|
|
1117
|
+
if (filterFn) {
|
|
1118
|
+
const matchedElement = elementsAtPoint.find(filterFn);
|
|
1119
|
+
// If matched element has Shadow DOM and we haven't visited it yet, recurse
|
|
1120
|
+
if (matchedElement?.shadowRoot && !visitedShadowRoots.has(matchedElement.shadowRoot)) {
|
|
1121
|
+
visitedShadowRoots.add(matchedElement.shadowRoot);
|
|
1122
|
+
return getElementsAtPoint(matchedElement.shadowRoot, x, y, {
|
|
1123
|
+
...options,
|
|
1124
|
+
visitedShadowRoots,
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return elementsAtPoint;
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Get the element at a specific point (x, y)
|
|
1132
|
+
*/
|
|
1133
|
+
const getElementAtPoint = (doc, x, y) => {
|
|
1134
|
+
let el = null;
|
|
1135
|
+
if ('caretPositionFromPoint' in doc) {
|
|
1136
|
+
el = doc.caretPositionFromPoint(x, y)?.offsetNode ?? null;
|
|
1137
|
+
}
|
|
1138
|
+
el = el ?? doc.elementFromPoint(x, y);
|
|
1139
|
+
let element = el;
|
|
1140
|
+
while (element && element.nodeType === Node.TEXT_NODE) {
|
|
1141
|
+
element = element.parentElement;
|
|
1142
|
+
}
|
|
1143
|
+
return element;
|
|
1144
|
+
};
|
|
1145
|
+
function getElementHash(element) {
|
|
1146
|
+
return (element.getAttribute('data-clarity-hash') ||
|
|
1147
|
+
element.getAttribute('data-clarity-hashalpha') ||
|
|
1148
|
+
element.getAttribute('data-clarity-hashbeta'));
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1081
1151
|
const AREA_HOVER_BOX_SHADOW = '0 0 0 1px #0078D4, 0 0 0 1px #0078D4 inset, 0 0 0 2px white inset';
|
|
1082
1152
|
const AREA_HOVER_ELEMENT_ID = 'clarity-edit-hover';
|
|
1083
1153
|
const AREA_MAP_DIV_ATTRIBUTE = 'data-clarity-area-map-div';
|
|
@@ -1125,7 +1195,6 @@ const SECONDARY_HOVERED_ELEMENT_ID_BASE = 'gx-hm-secondary-hovered-element';
|
|
|
1125
1195
|
function getColorFromClickDist(clickDist) {
|
|
1126
1196
|
// Ensure clickDist is in range [0, 100]
|
|
1127
1197
|
const normalizedDist = Math.max(0, Math.min(100, clickDist));
|
|
1128
|
-
console.log(`🚀 🐥 ~ getColorFromClickDist ~ normalizedDist:`, normalizedDist);
|
|
1129
1198
|
// Calculate gradient index
|
|
1130
1199
|
const maxIndex = AREA_COLOR_GRADIENT.length - 1;
|
|
1131
1200
|
const index = Math.floor((normalizedDist / 100) * maxIndex);
|
|
@@ -1253,21 +1322,46 @@ function getElementSelector(element) {
|
|
|
1253
1322
|
}
|
|
1254
1323
|
return element.tagName.toLowerCase();
|
|
1255
1324
|
}
|
|
1256
|
-
|
|
1325
|
+
/**
|
|
1326
|
+
* Calculate total clicks for an element including all its child elements
|
|
1327
|
+
* @param element - The parent element
|
|
1328
|
+
* @param elementMapInfo - Map of hash to element click info
|
|
1329
|
+
* @returns Total clicks for element + all descendants
|
|
1330
|
+
*/
|
|
1331
|
+
function calculateTotalClicksWithChildren(element, elementMapInfo) {
|
|
1332
|
+
let totalClicks = 0;
|
|
1333
|
+
// Get clicks for the element itself
|
|
1334
|
+
const elementHash = getElementHash(element);
|
|
1335
|
+
if (elementHash) {
|
|
1336
|
+
const elementInfo = elementMapInfo[elementHash];
|
|
1337
|
+
totalClicks += elementInfo?.totalclicks || 0;
|
|
1338
|
+
}
|
|
1339
|
+
const children = element.querySelectorAll('*');
|
|
1340
|
+
children.forEach((child) => {
|
|
1341
|
+
const childHash = getElementHash(child);
|
|
1342
|
+
if (childHash) {
|
|
1343
|
+
const childInfo = elementMapInfo[childHash];
|
|
1344
|
+
totalClicks += childInfo?.totalclicks || 0;
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
return totalClicks;
|
|
1348
|
+
}
|
|
1349
|
+
function buildAreaNode(element, hash, heatmapInfo, shadowRoot, persistedData) {
|
|
1257
1350
|
if (!heatmapInfo.elementMapInfo)
|
|
1258
1351
|
return;
|
|
1259
1352
|
const totalClicks = heatmapInfo.totalClicks || 0;
|
|
1260
1353
|
const elementInfo = heatmapInfo.elementMapInfo[hash];
|
|
1261
|
-
|
|
1354
|
+
// Calculate total clicks including all child elements
|
|
1355
|
+
const elementClicks = calculateTotalClicksWithChildren(element, heatmapInfo.elementMapInfo);
|
|
1262
1356
|
const clickDist = calculateClickDistribution(elementClicks, totalClicks);
|
|
1263
1357
|
const rect = getElementRect(element);
|
|
1264
1358
|
const color = getColorFromClickDist(clickDist);
|
|
1265
1359
|
const hoverColor = getHoverColorFromClickDist(clickDist);
|
|
1266
1360
|
const areaNode = {
|
|
1267
|
-
kind: 'area',
|
|
1268
|
-
id: `${hash}_${Date.now()}`,
|
|
1361
|
+
kind: persistedData?.kind || 'area',
|
|
1362
|
+
id: persistedData?.id || `${hash}_${Date.now()}`,
|
|
1269
1363
|
hash,
|
|
1270
|
-
selector: elementInfo?.selector || getElementSelector(element),
|
|
1364
|
+
selector: persistedData?.selector || elementInfo?.selector || getElementSelector(element),
|
|
1271
1365
|
// DOM references
|
|
1272
1366
|
element,
|
|
1273
1367
|
areaElement: null,
|
|
@@ -1307,207 +1401,183 @@ function getTopElementsByClicks(elementMapInfo, topN = 10) {
|
|
|
1307
1401
|
}
|
|
1308
1402
|
|
|
1309
1403
|
/**
|
|
1310
|
-
*
|
|
1311
|
-
*
|
|
1312
|
-
* Priority Rules (in order):
|
|
1313
|
-
* 1. Priority flag (manually set areas win)
|
|
1314
|
-
* 2. Click distribution (higher % wins)
|
|
1315
|
-
* 3. Total clicks (more clicks wins)
|
|
1316
|
-
* 4. DOM containment (parent contains child, parent wins)
|
|
1317
|
-
* 5. Size (smaller areas win - more specific)
|
|
1404
|
+
* Build parent-child relationships between areas based on DOM hierarchy
|
|
1405
|
+
* @param areas - Array of area nodes to build relationships for
|
|
1318
1406
|
*/
|
|
1319
|
-
function
|
|
1320
|
-
|
|
1321
|
-
return [];
|
|
1322
|
-
// Group overlapping areas
|
|
1323
|
-
const overlapGroups = findOverlapGroups(areas);
|
|
1324
|
-
// Resolve each group
|
|
1325
|
-
const visibleAreas = new Set();
|
|
1326
|
-
overlapGroups.forEach((group) => {
|
|
1327
|
-
const winner = resolveOverlapGroup(group, iframeDocument);
|
|
1328
|
-
visibleAreas.add(winner);
|
|
1329
|
-
});
|
|
1330
|
-
// Add non-overlapping areas
|
|
1407
|
+
function buildAreaGraph(areas) {
|
|
1408
|
+
// Clear existing relationships
|
|
1331
1409
|
areas.forEach((area) => {
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
visibleAreas.add(area);
|
|
1335
|
-
}
|
|
1410
|
+
area.parentNode = null;
|
|
1411
|
+
area.childNodes.clear();
|
|
1336
1412
|
});
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1413
|
+
// Build relationships based on DOM containment
|
|
1414
|
+
for (let i = 0; i < areas.length; i++) {
|
|
1415
|
+
const area = areas[i];
|
|
1416
|
+
for (let j = 0; j < areas.length; j++) {
|
|
1417
|
+
if (i === j)
|
|
1418
|
+
continue;
|
|
1419
|
+
const otherArea = areas[j];
|
|
1420
|
+
// Check if area's element is contained within otherArea's element
|
|
1421
|
+
if (otherArea.element.contains(area.element)) {
|
|
1422
|
+
// Find the closest parent (not just any ancestor)
|
|
1423
|
+
if (!area.parentNode || area.parentNode.element.contains(otherArea.element)) {
|
|
1424
|
+
// Remove from old parent if exists
|
|
1425
|
+
if (area.parentNode) {
|
|
1426
|
+
area.parentNode.childNodes.delete(area);
|
|
1427
|
+
}
|
|
1428
|
+
// Set new parent
|
|
1429
|
+
area.parentNode = otherArea;
|
|
1430
|
+
otherArea.childNodes.add(area);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1353
1433
|
}
|
|
1354
|
-
|
|
1355
|
-
const groupAreas = [area, ...overlapping];
|
|
1356
|
-
groupAreas.forEach((a) => processed.add(a.id));
|
|
1357
|
-
// Placeholder - will be resolved later
|
|
1358
|
-
groups.push({
|
|
1359
|
-
areas: groupAreas,
|
|
1360
|
-
winner: area,
|
|
1361
|
-
hidden: [],
|
|
1362
|
-
});
|
|
1363
|
-
});
|
|
1364
|
-
return groups;
|
|
1434
|
+
}
|
|
1365
1435
|
}
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1436
|
+
|
|
1437
|
+
class Logger {
|
|
1438
|
+
config = {
|
|
1439
|
+
enabled: false,
|
|
1440
|
+
prefix: '',
|
|
1441
|
+
timestamp: false,
|
|
1442
|
+
};
|
|
1443
|
+
/**
|
|
1444
|
+
* Cấu hình logger
|
|
1445
|
+
* @param config - Cấu hình logger
|
|
1446
|
+
*/
|
|
1447
|
+
configure(config) {
|
|
1448
|
+
this.config = { ...this.config, ...config };
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Lấy cấu hình hiện tại
|
|
1452
|
+
*/
|
|
1453
|
+
getConfig() {
|
|
1454
|
+
return { ...this.config };
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Bật logger
|
|
1458
|
+
*/
|
|
1459
|
+
enable() {
|
|
1460
|
+
this.config.enabled = true;
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Tắt logger
|
|
1464
|
+
*/
|
|
1465
|
+
disable() {
|
|
1466
|
+
this.config.enabled = false;
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Format message với prefix và timestamp
|
|
1470
|
+
*/
|
|
1471
|
+
formatMessage(...args) {
|
|
1472
|
+
const parts = [];
|
|
1473
|
+
if (this.config.timestamp) {
|
|
1474
|
+
parts.push(`[${new Date().toISOString()}]`);
|
|
1386
1475
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
const aContainsB = isElementAncestorOf(a.element, b.element);
|
|
1390
|
-
const bContainsA = isElementAncestorOf(b.element, a.element);
|
|
1391
|
-
if (aContainsB)
|
|
1392
|
-
return -1; // a is parent, a wins
|
|
1393
|
-
if (bContainsA)
|
|
1394
|
-
return 1; // b is parent, b wins
|
|
1476
|
+
if (this.config.prefix) {
|
|
1477
|
+
parts.push(`[${this.config.prefix}]`);
|
|
1395
1478
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
const bSize = (b.rect.value?.width || 0) * (b.rect.value?.height || 0);
|
|
1399
|
-
return aSize - bSize;
|
|
1400
|
-
});
|
|
1401
|
-
const winner = sorted[0];
|
|
1402
|
-
group.winner = winner;
|
|
1403
|
-
group.hidden = sorted.slice(1);
|
|
1404
|
-
return winner;
|
|
1405
|
-
}
|
|
1406
|
-
/**
|
|
1407
|
-
* Filter out areas that are completely contained within others
|
|
1408
|
-
* and have lower priority
|
|
1409
|
-
*/
|
|
1410
|
-
function filterContainedAreas(areas) {
|
|
1411
|
-
const visible = [];
|
|
1412
|
-
areas.forEach((area) => {
|
|
1413
|
-
// Check if this area is contained by a higher priority area
|
|
1414
|
-
const isContained = areas.some((other) => {
|
|
1415
|
-
if (other.id === area.id)
|
|
1416
|
-
return false;
|
|
1417
|
-
// Check containment
|
|
1418
|
-
if (!isAreaContainedIn(area, other))
|
|
1419
|
-
return false;
|
|
1420
|
-
// Check priority
|
|
1421
|
-
if (other.priority && !area.priority)
|
|
1422
|
-
return true;
|
|
1423
|
-
if (!other.priority && area.priority)
|
|
1424
|
-
return false;
|
|
1425
|
-
// Compare by click dist
|
|
1426
|
-
if (other.clickDist > area.clickDist)
|
|
1427
|
-
return true;
|
|
1428
|
-
if (other.clickDist < area.clickDist)
|
|
1429
|
-
return false;
|
|
1430
|
-
// Compare by total clicks
|
|
1431
|
-
return other.totalclicks > area.totalclicks;
|
|
1432
|
-
});
|
|
1433
|
-
if (!isContained) {
|
|
1434
|
-
visible.push(area);
|
|
1435
|
-
}
|
|
1436
|
-
});
|
|
1437
|
-
return visible;
|
|
1438
|
-
}
|
|
1439
|
-
/**
|
|
1440
|
-
* Get visible areas after resolving overlaps
|
|
1441
|
-
*/
|
|
1442
|
-
function getVisibleAreas(areas, iframeDocument) {
|
|
1443
|
-
// First pass: filter contained areas
|
|
1444
|
-
let visible = filterContainedAreas(areas);
|
|
1445
|
-
// Second pass: resolve overlaps
|
|
1446
|
-
visible = resolveOverlaps(visible, iframeDocument);
|
|
1447
|
-
// Sort by click dist for rendering order
|
|
1448
|
-
return sortAreasByClickDist(visible);
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
/**
|
|
1452
|
-
* Helper functions for setting up area renderer
|
|
1453
|
-
*/
|
|
1454
|
-
/**
|
|
1455
|
-
* Create the outer container for area rendering
|
|
1456
|
-
*/
|
|
1457
|
-
function createAreaContainer(iframeDocument) {
|
|
1458
|
-
const container = iframeDocument.createElement('div');
|
|
1459
|
-
container.setAttribute(AREA_MAP_DIV_ATTRIBUTE, 'true');
|
|
1460
|
-
container.style.cssText = AREA_CONTAINER_STYLES;
|
|
1461
|
-
return container;
|
|
1462
|
-
}
|
|
1463
|
-
/**
|
|
1464
|
-
* Create the inner container for React portal
|
|
1465
|
-
*/
|
|
1466
|
-
function createInnerContainer(iframeDocument) {
|
|
1467
|
-
const innerContainer = iframeDocument.createElement('div');
|
|
1468
|
-
innerContainer.className = AREA_RENDERER_SELECTORS.innerContainerClass;
|
|
1469
|
-
innerContainer.style.cssText = AREA_INNER_CONTAINER_STYLES;
|
|
1470
|
-
return innerContainer;
|
|
1471
|
-
}
|
|
1472
|
-
/**
|
|
1473
|
-
* Get or create the outer container element
|
|
1474
|
-
*/
|
|
1475
|
-
function getOrCreateAreaContainer(iframeDocument, customShadowRoot) {
|
|
1476
|
-
let container = iframeDocument.querySelector(AREA_RENDERER_SELECTORS.containerSelector);
|
|
1477
|
-
if (!container) {
|
|
1478
|
-
container = createAreaContainer(iframeDocument);
|
|
1479
|
-
const targetRoot = customShadowRoot || iframeDocument.body;
|
|
1480
|
-
if (targetRoot) {
|
|
1481
|
-
targetRoot.appendChild(container);
|
|
1479
|
+
if (parts.length > 0) {
|
|
1480
|
+
return [parts.join(' '), ...args];
|
|
1482
1481
|
}
|
|
1482
|
+
return args;
|
|
1483
1483
|
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1484
|
+
/**
|
|
1485
|
+
* Log message
|
|
1486
|
+
*/
|
|
1487
|
+
log(...args) {
|
|
1488
|
+
if (!this.config.enabled)
|
|
1489
|
+
return;
|
|
1490
|
+
console.log(...this.formatMessage(...args));
|
|
1489
1491
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1492
|
+
/**
|
|
1493
|
+
* Log info message
|
|
1494
|
+
*/
|
|
1495
|
+
info(...args) {
|
|
1496
|
+
if (!this.config.enabled)
|
|
1497
|
+
return;
|
|
1498
|
+
console.info(...this.formatMessage(...args));
|
|
1497
1499
|
}
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1500
|
+
/**
|
|
1501
|
+
* Log warning message
|
|
1502
|
+
*/
|
|
1503
|
+
warn(...args) {
|
|
1504
|
+
if (!this.config.enabled)
|
|
1505
|
+
return;
|
|
1506
|
+
console.warn(...this.formatMessage(...args));
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Log error message
|
|
1510
|
+
*/
|
|
1511
|
+
error(...args) {
|
|
1512
|
+
if (!this.config.enabled)
|
|
1513
|
+
return;
|
|
1514
|
+
console.error(...this.formatMessage(...args));
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Log debug message
|
|
1518
|
+
*/
|
|
1519
|
+
debug(...args) {
|
|
1520
|
+
if (!this.config.enabled)
|
|
1521
|
+
return;
|
|
1522
|
+
console.debug(...this.formatMessage(...args));
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Log table data
|
|
1526
|
+
*/
|
|
1527
|
+
table(data) {
|
|
1528
|
+
if (!this.config.enabled)
|
|
1529
|
+
return;
|
|
1530
|
+
console.table(data);
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Start a group
|
|
1534
|
+
*/
|
|
1535
|
+
group(label) {
|
|
1536
|
+
if (!this.config.enabled)
|
|
1537
|
+
return;
|
|
1538
|
+
console.group(...this.formatMessage(label));
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Start a collapsed group
|
|
1542
|
+
*/
|
|
1543
|
+
groupCollapsed(label) {
|
|
1544
|
+
if (!this.config.enabled)
|
|
1545
|
+
return;
|
|
1546
|
+
console.groupCollapsed(...this.formatMessage(label));
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* End a group
|
|
1550
|
+
*/
|
|
1551
|
+
groupEnd() {
|
|
1552
|
+
if (!this.config.enabled)
|
|
1553
|
+
return;
|
|
1554
|
+
console.groupEnd();
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Start a timer
|
|
1558
|
+
*/
|
|
1559
|
+
time(label) {
|
|
1560
|
+
if (!this.config.enabled)
|
|
1561
|
+
return;
|
|
1562
|
+
console.time(label);
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* End a timer
|
|
1566
|
+
*/
|
|
1567
|
+
timeEnd(label) {
|
|
1568
|
+
if (!this.config.enabled)
|
|
1569
|
+
return;
|
|
1570
|
+
console.timeEnd(label);
|
|
1509
1571
|
}
|
|
1510
1572
|
}
|
|
1573
|
+
// Export singleton instance
|
|
1574
|
+
const logger$3 = new Logger();
|
|
1575
|
+
// Export factory function để tạo logger với config riêng
|
|
1576
|
+
function createLogger(config = {}) {
|
|
1577
|
+
const instance = new Logger();
|
|
1578
|
+
instance.configure(config);
|
|
1579
|
+
return instance;
|
|
1580
|
+
}
|
|
1511
1581
|
|
|
1512
1582
|
function findLastSizeOfDom(data) {
|
|
1513
1583
|
const listDocs = data
|
|
@@ -1542,424 +1612,520 @@ function decodePayloads(payload) {
|
|
|
1542
1612
|
}
|
|
1543
1613
|
}
|
|
1544
1614
|
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
};
|
|
1554
|
-
const getClickedElementId = (viewId, isSecondary = false) => {
|
|
1555
|
-
const baseId = isSecondary ? SECONDARY_CLICKED_ELEMENT_ID_BASE : CLICKED_ELEMENT_ID_BASE;
|
|
1556
|
-
return getElementId(baseId, viewId);
|
|
1557
|
-
};
|
|
1558
|
-
const getHoveredElementId = (viewId, isSecondary = false) => {
|
|
1559
|
-
const baseId = isSecondary ? SECONDARY_HOVERED_ELEMENT_ID_BASE : HOVERED_ELEMENT_ID_BASE;
|
|
1560
|
-
return getElementId(baseId, viewId);
|
|
1561
|
-
};
|
|
1562
|
-
|
|
1563
|
-
function getElementLayout(element) {
|
|
1564
|
-
if (!element?.getBoundingClientRect)
|
|
1565
|
-
return null;
|
|
1566
|
-
const rect = element.getBoundingClientRect();
|
|
1567
|
-
if (rect.width === 0 && rect.height === 0)
|
|
1615
|
+
function findElementByHash(props) {
|
|
1616
|
+
const { hash, selector, iframeDocument, vizRef } = props;
|
|
1617
|
+
if (vizRef) {
|
|
1618
|
+
const element = vizRef.get(hash);
|
|
1619
|
+
return element;
|
|
1620
|
+
}
|
|
1621
|
+
// Fallback
|
|
1622
|
+
if (!iframeDocument)
|
|
1568
1623
|
return null;
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
}
|
|
1624
|
+
try {
|
|
1625
|
+
const element = selector ? iframeDocument.querySelector(selector) : null;
|
|
1626
|
+
if (element) {
|
|
1627
|
+
return element;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
catch (error) {
|
|
1631
|
+
logger$3.warn(`Invalid selector "${selector}":`, error);
|
|
1632
|
+
}
|
|
1633
|
+
const elementByHash = iframeDocument.querySelector(`[data-clarity-hashalpha="${hash}"], [data-clarity-hash="${hash}"], [data-clarity-hashbeta="${hash}"]`);
|
|
1634
|
+
return elementByHash;
|
|
1575
1635
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1636
|
+
|
|
1637
|
+
/**
|
|
1638
|
+
* Hydrates persisted area data into full area node
|
|
1639
|
+
* Finds element in DOM and calculates all runtime values
|
|
1640
|
+
*
|
|
1641
|
+
* @param persistedData - Minimal data from database
|
|
1642
|
+
* @param iframeDocument - Document to find element in
|
|
1643
|
+
* @param heatmapInfo - Heatmap data for click calculations
|
|
1644
|
+
* @param vizRef - Map of hash to elements
|
|
1645
|
+
* @param shadowRoot - Optional shadow root for rect calculation
|
|
1646
|
+
* @returns Full area node or null if element not found
|
|
1647
|
+
*/
|
|
1648
|
+
function hydrateAreaNode(props) {
|
|
1649
|
+
const { persistedData, iframeDocument, heatmapInfo, vizRef, shadowRoot } = props;
|
|
1650
|
+
const { id, hash, selector } = persistedData;
|
|
1651
|
+
const element = findElementByHash({ hash, selector, iframeDocument, vizRef });
|
|
1652
|
+
if (!element) {
|
|
1653
|
+
logger$3.warn(`Cannot hydrate area ${id}: element not found for hash ${hash} or selector ${selector}`);
|
|
1583
1654
|
return null;
|
|
1584
|
-
|
|
1585
|
-
|
|
1655
|
+
}
|
|
1656
|
+
const areaNode = buildAreaNode(element, hash, heatmapInfo, shadowRoot, persistedData);
|
|
1657
|
+
if (!areaNode)
|
|
1586
1658
|
return null;
|
|
1587
|
-
|
|
1588
|
-
const clicks = info.totalclicks ?? 0;
|
|
1589
|
-
const selector = info.selector ?? '';
|
|
1590
|
-
const baseInfo = {
|
|
1591
|
-
hash,
|
|
1592
|
-
clicks,
|
|
1593
|
-
rank,
|
|
1594
|
-
selector,
|
|
1595
|
-
};
|
|
1596
|
-
return {
|
|
1597
|
-
...baseInfo,
|
|
1598
|
-
...rect,
|
|
1599
|
-
};
|
|
1600
|
-
};
|
|
1601
|
-
|
|
1602
|
-
function calculateRankPosition(rect, widthScale) {
|
|
1603
|
-
const top = rect.top <= 18 ? rect.top + 3 : rect.top - 18;
|
|
1604
|
-
const left = rect.left <= 18 ? rect.left + 3 : rect.left - 18;
|
|
1605
|
-
return {
|
|
1606
|
-
transform: `scale(${1.2 * widthScale})`,
|
|
1607
|
-
top: Number.isNaN(top) ? undefined : top,
|
|
1608
|
-
left: Number.isNaN(left) ? undefined : left,
|
|
1609
|
-
};
|
|
1659
|
+
return areaNode;
|
|
1610
1660
|
}
|
|
1611
|
-
|
|
1612
|
-
const
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
}
|
|
1661
|
+
function hydrateAreas(props) {
|
|
1662
|
+
const { clickAreas, iframeDocument, heatmapInfo, vizRef, shadowRoot } = props;
|
|
1663
|
+
const hydratedAreas = [];
|
|
1664
|
+
for (const persistedData of clickAreas) {
|
|
1665
|
+
const area = hydrateAreaNode({ persistedData, iframeDocument, heatmapInfo, vizRef, shadowRoot });
|
|
1666
|
+
if (area) {
|
|
1667
|
+
hydratedAreas.push(area);
|
|
1668
|
+
}
|
|
1619
1669
|
}
|
|
1670
|
+
logger$3.info(`Hydrated ${hydratedAreas.length} of ${clickAreas.length} persisted areas`);
|
|
1671
|
+
return hydratedAreas;
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Serializes area node to persisted data for database storage
|
|
1675
|
+
*/
|
|
1676
|
+
function serializeAreaNode(area) {
|
|
1620
1677
|
return {
|
|
1621
|
-
|
|
1622
|
-
|
|
1678
|
+
kind: area.kind,
|
|
1679
|
+
id: area.id,
|
|
1680
|
+
hash: area.hash,
|
|
1681
|
+
selector: area.selector,
|
|
1623
1682
|
};
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* Serializes multiple areas for database storage
|
|
1686
|
+
*/
|
|
1687
|
+
function serializeAreas(areas) {
|
|
1688
|
+
return areas.map(serializeAreaNode);
|
|
1689
|
+
}
|
|
1629
1690
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
};
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
const
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
return placement === 'right'
|
|
1684
|
-
? targetRect.right + calloutRect.width + padding + arrowSize <= containerRect.right
|
|
1685
|
-
: targetRect.left - calloutRect.width - padding - arrowSize >= containerRect.left;
|
|
1686
|
-
}
|
|
1687
|
-
return placement === 'right'
|
|
1688
|
-
? targetRect.right + calloutRect.width + padding + arrowSize < viewportWidth
|
|
1689
|
-
: targetRect.left - calloutRect.width - padding - arrowSize > 0;
|
|
1690
|
-
};
|
|
1691
|
-
|
|
1692
|
-
const generateVerticalPositionCandidates = (targetRect, calloutRect, viewportHeight, viewportWidth, alignment, hozOffset, padding, arrowSize, containerRect) => {
|
|
1693
|
-
const candidates = [];
|
|
1694
|
-
const placements = ['top', 'bottom'];
|
|
1695
|
-
placements.forEach((placement) => {
|
|
1696
|
-
const verticalPos = calculateVerticalPosition(targetRect, calloutRect, placement, padding, arrowSize);
|
|
1697
|
-
const verticalValid = isVerticalPositionValid(targetRect, calloutRect, placement, viewportHeight, padding, arrowSize, containerRect);
|
|
1698
|
-
const alignmentOrder = getAlignmentOrder(alignment);
|
|
1699
|
-
alignmentOrder.forEach((align) => {
|
|
1700
|
-
const horizontalPos = calculateLeftPosition({
|
|
1701
|
-
targetRect,
|
|
1702
|
-
calloutRect,
|
|
1703
|
-
hozOffset,
|
|
1704
|
-
align,
|
|
1705
|
-
});
|
|
1706
|
-
candidates.push({
|
|
1707
|
-
placement,
|
|
1708
|
-
top: verticalPos,
|
|
1709
|
-
left: horizontalPos,
|
|
1710
|
-
horizontalAlign: align,
|
|
1711
|
-
valid: verticalValid &&
|
|
1712
|
-
isLeftPositionValid(horizontalPos, calloutRect.width, viewportWidth, padding, containerRect),
|
|
1713
|
-
});
|
|
1691
|
+
/**
|
|
1692
|
+
* Resolve overlapping areas by priority rules
|
|
1693
|
+
*
|
|
1694
|
+
* Priority Rules (in order):
|
|
1695
|
+
* 1. Priority flag (manually set areas win)
|
|
1696
|
+
* 2. Click distribution (higher % wins)
|
|
1697
|
+
* 3. Total clicks (more clicks wins)
|
|
1698
|
+
* 4. DOM containment (parent contains child, parent wins)
|
|
1699
|
+
* 5. Size (smaller areas win - more specific)
|
|
1700
|
+
*/
|
|
1701
|
+
function resolveOverlaps(areas, iframeDocument) {
|
|
1702
|
+
if (areas.length === 0)
|
|
1703
|
+
return [];
|
|
1704
|
+
// Group overlapping areas
|
|
1705
|
+
const overlapGroups = findOverlapGroups(areas);
|
|
1706
|
+
// Resolve each group
|
|
1707
|
+
const visibleAreas = new Set();
|
|
1708
|
+
overlapGroups.forEach((group) => {
|
|
1709
|
+
const winner = resolveOverlapGroup(group, iframeDocument);
|
|
1710
|
+
visibleAreas.add(winner);
|
|
1711
|
+
});
|
|
1712
|
+
// Add non-overlapping areas
|
|
1713
|
+
areas.forEach((area) => {
|
|
1714
|
+
const hasOverlap = overlapGroups.some((group) => group.areas.includes(area));
|
|
1715
|
+
if (!hasOverlap) {
|
|
1716
|
+
visibleAreas.add(area);
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1719
|
+
return Array.from(visibleAreas);
|
|
1720
|
+
}
|
|
1721
|
+
/**
|
|
1722
|
+
* Find groups of overlapping areas
|
|
1723
|
+
*/
|
|
1724
|
+
function findOverlapGroups(areas) {
|
|
1725
|
+
const groups = [];
|
|
1726
|
+
const processed = new Set();
|
|
1727
|
+
areas.forEach((area) => {
|
|
1728
|
+
if (processed.has(area.id))
|
|
1729
|
+
return;
|
|
1730
|
+
// Find all areas that overlap with this one
|
|
1731
|
+
const overlapping = areas.filter((other) => other.id !== area.id && doAreasOverlap(area, other));
|
|
1732
|
+
if (overlapping.length === 0) {
|
|
1733
|
+
// No overlap, skip grouping
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
// Create group with this area and all overlapping
|
|
1737
|
+
const groupAreas = [area, ...overlapping];
|
|
1738
|
+
groupAreas.forEach((a) => processed.add(a.id));
|
|
1739
|
+
// Placeholder - will be resolved later
|
|
1740
|
+
groups.push({
|
|
1741
|
+
areas: groupAreas,
|
|
1742
|
+
winner: area,
|
|
1743
|
+
hidden: [],
|
|
1714
1744
|
});
|
|
1715
1745
|
});
|
|
1716
|
-
return
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1746
|
+
return groups;
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Resolve a single overlap group to find the winner
|
|
1750
|
+
*/
|
|
1751
|
+
function resolveOverlapGroup(group, iframeDocument) {
|
|
1752
|
+
const { areas } = group;
|
|
1753
|
+
if (areas.length === 1)
|
|
1754
|
+
return areas[0];
|
|
1755
|
+
// Sort by priority rules
|
|
1756
|
+
const sorted = [...areas].sort((a, b) => {
|
|
1757
|
+
// Rule 1: Priority flag
|
|
1758
|
+
if (a.priority !== b.priority) {
|
|
1759
|
+
return a.priority ? -1 : 1;
|
|
1760
|
+
}
|
|
1761
|
+
// Rule 2: Click distribution
|
|
1762
|
+
if (a.clickDist !== b.clickDist) {
|
|
1763
|
+
return b.clickDist - a.clickDist;
|
|
1764
|
+
}
|
|
1765
|
+
// Rule 3: Total clicks
|
|
1766
|
+
if (a.totalclicks !== b.totalclicks) {
|
|
1767
|
+
return b.totalclicks - a.totalclicks;
|
|
1768
|
+
}
|
|
1769
|
+
// Rule 4: DOM containment - parent beats child
|
|
1770
|
+
if (iframeDocument) {
|
|
1771
|
+
const aContainsB = isElementAncestorOf(a.element, b.element);
|
|
1772
|
+
const bContainsA = isElementAncestorOf(b.element, a.element);
|
|
1773
|
+
if (aContainsB)
|
|
1774
|
+
return -1; // a is parent, a wins
|
|
1775
|
+
if (bContainsA)
|
|
1776
|
+
return 1; // b is parent, b wins
|
|
1777
|
+
}
|
|
1778
|
+
// Rule 5: Size - smaller (more specific) wins
|
|
1779
|
+
const aSize = (a.rect.value?.width || 0) * (a.rect.value?.height || 0);
|
|
1780
|
+
const bSize = (b.rect.value?.width || 0) * (b.rect.value?.height || 0);
|
|
1781
|
+
return aSize - bSize;
|
|
1729
1782
|
});
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
return [...verticalCandidates, ...horizontalCandidates];
|
|
1736
|
-
};
|
|
1737
|
-
|
|
1738
|
-
const selectBestPosition = (candidates) => {
|
|
1739
|
-
return candidates.find((p) => p.valid) || candidates[0];
|
|
1740
|
-
};
|
|
1741
|
-
const constrainToViewport = (position, calloutRect, viewport, padding, containerRect) => {
|
|
1742
|
-
if (containerRect) {
|
|
1743
|
-
const left = Math.max(containerRect.left + padding, Math.min(position.left, containerRect.right - calloutRect.width - padding));
|
|
1744
|
-
const top = Math.max(containerRect.top + padding, Math.min(position.top, containerRect.bottom - calloutRect.height - padding));
|
|
1745
|
-
return { top, left };
|
|
1746
|
-
}
|
|
1747
|
-
const left = Math.max(padding, Math.min(position.left, viewport.width - calloutRect.width - padding));
|
|
1748
|
-
const top = Math.max(padding, Math.min(position.top, viewport.height - calloutRect.height - padding));
|
|
1749
|
-
return { top, left };
|
|
1750
|
-
};
|
|
1751
|
-
|
|
1752
|
-
const calcCalloutPosition = (options) => {
|
|
1753
|
-
const { targetElm, calloutElm, setPosition, hozOffset = CALLOUT_HORIZONTAL_OFFSET, alignment = 'center', containerElm, } = options;
|
|
1754
|
-
return () => {
|
|
1755
|
-
// 1. Get dimensions
|
|
1756
|
-
const rectDimensions = getElementDimensions(targetElm, calloutElm);
|
|
1757
|
-
const viewport = getViewportDimensions(containerElm);
|
|
1758
|
-
const containerRect = containerElm?.getBoundingClientRect();
|
|
1759
|
-
const padding = CALLOUT_PADDING;
|
|
1760
|
-
const arrowSize = CALLOUT_ARROW_SIZE;
|
|
1761
|
-
// 2. Generate all position candidates
|
|
1762
|
-
const candidates = generateAllPositionCandidates(rectDimensions, viewport, alignment, hozOffset, padding, arrowSize, containerRect);
|
|
1763
|
-
// 3. Select best position
|
|
1764
|
-
const bestPosition = selectBestPosition(candidates);
|
|
1765
|
-
// 4. Constrain to viewport
|
|
1766
|
-
const constrainedPosition = constrainToViewport({ top: bestPosition.top, left: bestPosition.left }, rectDimensions.calloutRect, viewport, padding, containerRect);
|
|
1767
|
-
// 5. Create final position object
|
|
1768
|
-
const finalPosition = {
|
|
1769
|
-
top: constrainedPosition.top,
|
|
1770
|
-
left: constrainedPosition.left,
|
|
1771
|
-
placement: bestPosition.placement,
|
|
1772
|
-
horizontalAlign: bestPosition.horizontalAlign,
|
|
1773
|
-
};
|
|
1774
|
-
setPosition(finalPosition);
|
|
1775
|
-
};
|
|
1776
|
-
};
|
|
1777
|
-
|
|
1783
|
+
const winner = sorted[0];
|
|
1784
|
+
group.winner = winner;
|
|
1785
|
+
group.hidden = sorted.slice(1);
|
|
1786
|
+
return winner;
|
|
1787
|
+
}
|
|
1778
1788
|
/**
|
|
1779
|
-
*
|
|
1789
|
+
* Filter out areas that are completely contained within others
|
|
1790
|
+
* and have lower priority
|
|
1780
1791
|
*/
|
|
1781
|
-
function
|
|
1782
|
-
const
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1792
|
+
function filterContainedAreas(areas) {
|
|
1793
|
+
const visible = [];
|
|
1794
|
+
areas.forEach((area) => {
|
|
1795
|
+
// Check if this area is contained by a higher priority area
|
|
1796
|
+
const isContained = areas.some((other) => {
|
|
1797
|
+
if (other.id === area.id)
|
|
1798
|
+
return false;
|
|
1799
|
+
// Check containment
|
|
1800
|
+
if (!isAreaContainedIn(area, other))
|
|
1801
|
+
return false;
|
|
1802
|
+
// Check priority
|
|
1803
|
+
if (other.priority && !area.priority)
|
|
1804
|
+
return true;
|
|
1805
|
+
if (!other.priority && area.priority)
|
|
1806
|
+
return false;
|
|
1807
|
+
// Compare by click dist
|
|
1808
|
+
if (other.clickDist > area.clickDist)
|
|
1809
|
+
return true;
|
|
1810
|
+
if (other.clickDist < area.clickDist)
|
|
1811
|
+
return false;
|
|
1812
|
+
// Compare by total clicks
|
|
1813
|
+
return other.totalclicks > area.totalclicks;
|
|
1814
|
+
});
|
|
1815
|
+
if (!isContained) {
|
|
1816
|
+
visible.push(area);
|
|
1799
1817
|
}
|
|
1800
|
-
}
|
|
1801
|
-
return
|
|
1818
|
+
});
|
|
1819
|
+
return visible;
|
|
1802
1820
|
}
|
|
1803
1821
|
/**
|
|
1804
|
-
* Get
|
|
1822
|
+
* Get visible areas after resolving overlaps
|
|
1805
1823
|
*/
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
while (element && element.nodeType === Node.TEXT_NODE) {
|
|
1814
|
-
element = element.parentElement;
|
|
1815
|
-
}
|
|
1816
|
-
return element;
|
|
1817
|
-
};
|
|
1818
|
-
function getElementHash(element) {
|
|
1819
|
-
return (element.getAttribute('data-clarity-hash') ||
|
|
1820
|
-
element.getAttribute('data-clarity-hashalpha') ||
|
|
1821
|
-
element.getAttribute('data-clarity-hashbeta'));
|
|
1824
|
+
function getVisibleAreas(areas, iframeDocument) {
|
|
1825
|
+
// First pass: filter contained areas
|
|
1826
|
+
let visible = filterContainedAreas(areas);
|
|
1827
|
+
// Second pass: resolve overlaps
|
|
1828
|
+
visible = resolveOverlaps(visible, iframeDocument);
|
|
1829
|
+
// Sort by click dist for rendering order
|
|
1830
|
+
return sortAreasByClickDist(visible);
|
|
1822
1831
|
}
|
|
1823
1832
|
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
/**
|
|
1856
|
-
* Format message với prefix và timestamp
|
|
1857
|
-
*/
|
|
1858
|
-
formatMessage(...args) {
|
|
1859
|
-
const parts = [];
|
|
1860
|
-
if (this.config.timestamp) {
|
|
1861
|
-
parts.push(`[${new Date().toISOString()}]`);
|
|
1862
|
-
}
|
|
1863
|
-
if (this.config.prefix) {
|
|
1864
|
-
parts.push(`[${this.config.prefix}]`);
|
|
1865
|
-
}
|
|
1866
|
-
if (parts.length > 0) {
|
|
1867
|
-
return [parts.join(' '), ...args];
|
|
1833
|
+
/**
|
|
1834
|
+
* Helper functions for setting up area renderer
|
|
1835
|
+
*/
|
|
1836
|
+
/**
|
|
1837
|
+
* Create the outer container for area rendering
|
|
1838
|
+
*/
|
|
1839
|
+
function createAreaContainer(iframeDocument) {
|
|
1840
|
+
const container = iframeDocument.createElement('div');
|
|
1841
|
+
container.setAttribute(AREA_MAP_DIV_ATTRIBUTE, 'true');
|
|
1842
|
+
container.style.cssText = AREA_CONTAINER_STYLES;
|
|
1843
|
+
return container;
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Create the inner container for React portal
|
|
1847
|
+
*/
|
|
1848
|
+
function createInnerContainer(iframeDocument) {
|
|
1849
|
+
const innerContainer = iframeDocument.createElement('div');
|
|
1850
|
+
innerContainer.className = AREA_RENDERER_SELECTORS.innerContainerClass;
|
|
1851
|
+
innerContainer.style.cssText = AREA_INNER_CONTAINER_STYLES;
|
|
1852
|
+
return innerContainer;
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Get or create the outer container element
|
|
1856
|
+
*/
|
|
1857
|
+
function getOrCreateAreaContainer(iframeDocument, customShadowRoot) {
|
|
1858
|
+
let container = iframeDocument.querySelector(AREA_RENDERER_SELECTORS.containerSelector);
|
|
1859
|
+
if (!container) {
|
|
1860
|
+
container = createAreaContainer(iframeDocument);
|
|
1861
|
+
const targetRoot = customShadowRoot || iframeDocument.body;
|
|
1862
|
+
if (targetRoot) {
|
|
1863
|
+
targetRoot.appendChild(container);
|
|
1868
1864
|
}
|
|
1869
|
-
return args;
|
|
1870
|
-
}
|
|
1871
|
-
/**
|
|
1872
|
-
* Log message
|
|
1873
|
-
*/
|
|
1874
|
-
log(...args) {
|
|
1875
|
-
if (!this.config.enabled)
|
|
1876
|
-
return;
|
|
1877
|
-
console.log(...this.formatMessage(...args));
|
|
1878
1865
|
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
return;
|
|
1885
|
-
console.info(...this.formatMessage(...args));
|
|
1866
|
+
return container;
|
|
1867
|
+
}
|
|
1868
|
+
function getOrCreateContainerShadowRoot(container) {
|
|
1869
|
+
if (container.shadowRoot) {
|
|
1870
|
+
return container.shadowRoot;
|
|
1886
1871
|
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1872
|
+
return container.attachShadow({ mode: 'open' });
|
|
1873
|
+
}
|
|
1874
|
+
function getOrCreateInnerContainer(shadowRoot, iframeDocument) {
|
|
1875
|
+
let innerContainer = shadowRoot.querySelector(AREA_RENDERER_SELECTORS.innerContainerSelector);
|
|
1876
|
+
if (!innerContainer) {
|
|
1877
|
+
innerContainer = createInnerContainer(iframeDocument);
|
|
1878
|
+
shadowRoot.appendChild(innerContainer);
|
|
1894
1879
|
}
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1880
|
+
return innerContainer;
|
|
1881
|
+
}
|
|
1882
|
+
function setupAreaRenderingContainer(iframeDocument, customShadowRoot) {
|
|
1883
|
+
const container = getOrCreateAreaContainer(iframeDocument, customShadowRoot);
|
|
1884
|
+
const shadowRoot = getOrCreateContainerShadowRoot(container);
|
|
1885
|
+
const innerContainer = getOrCreateInnerContainer(shadowRoot, iframeDocument);
|
|
1886
|
+
return innerContainer;
|
|
1887
|
+
}
|
|
1888
|
+
function cleanupAreaRenderingContainer(container) {
|
|
1889
|
+
if (container && container.parentNode) {
|
|
1890
|
+
container.parentNode.removeChild(container);
|
|
1902
1891
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
/**
|
|
1895
|
+
* Generate unique element ID for a specific view
|
|
1896
|
+
* @param baseId - Base element ID
|
|
1897
|
+
* @param viewId - View ID
|
|
1898
|
+
* @returns Unique element ID (e.g., 'gx-hm-clicked-element-view-0')
|
|
1899
|
+
*/
|
|
1900
|
+
const getElementId = (baseId, viewId) => {
|
|
1901
|
+
return `${baseId}-${viewId}`;
|
|
1902
|
+
};
|
|
1903
|
+
const getClickedElementId = (viewId, isSecondary = false) => {
|
|
1904
|
+
const baseId = isSecondary ? SECONDARY_CLICKED_ELEMENT_ID_BASE : CLICKED_ELEMENT_ID_BASE;
|
|
1905
|
+
return getElementId(baseId, viewId);
|
|
1906
|
+
};
|
|
1907
|
+
const getHoveredElementId = (viewId, isSecondary = false) => {
|
|
1908
|
+
const baseId = isSecondary ? SECONDARY_HOVERED_ELEMENT_ID_BASE : HOVERED_ELEMENT_ID_BASE;
|
|
1909
|
+
return getElementId(baseId, viewId);
|
|
1910
|
+
};
|
|
1911
|
+
|
|
1912
|
+
function getElementLayout(element) {
|
|
1913
|
+
if (!element?.getBoundingClientRect)
|
|
1914
|
+
return null;
|
|
1915
|
+
const rect = element.getBoundingClientRect();
|
|
1916
|
+
if (rect.width === 0 && rect.height === 0)
|
|
1917
|
+
return null;
|
|
1918
|
+
return {
|
|
1919
|
+
top: rect.top,
|
|
1920
|
+
left: rect.left,
|
|
1921
|
+
width: rect.width,
|
|
1922
|
+
height: rect.height,
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
const getElementRank = (hash, elements) => {
|
|
1926
|
+
if (!elements)
|
|
1927
|
+
return 0;
|
|
1928
|
+
return elements.findIndex((e) => e.hash === hash) + 1;
|
|
1929
|
+
};
|
|
1930
|
+
const buildElementInfo = (hash, rect, heatmapInfo) => {
|
|
1931
|
+
if (!rect || !heatmapInfo)
|
|
1932
|
+
return null;
|
|
1933
|
+
const info = heatmapInfo.elementMapInfo?.[hash];
|
|
1934
|
+
if (!info)
|
|
1935
|
+
return null;
|
|
1936
|
+
const rank = getElementRank(hash, heatmapInfo.sortedElements);
|
|
1937
|
+
const clicks = info.totalclicks ?? 0;
|
|
1938
|
+
const selector = info.selector ?? '';
|
|
1939
|
+
const baseInfo = {
|
|
1940
|
+
hash,
|
|
1941
|
+
clicks,
|
|
1942
|
+
rank,
|
|
1943
|
+
selector,
|
|
1944
|
+
};
|
|
1945
|
+
return {
|
|
1946
|
+
...baseInfo,
|
|
1947
|
+
...rect,
|
|
1948
|
+
};
|
|
1949
|
+
};
|
|
1950
|
+
|
|
1951
|
+
function calculateRankPosition(rect, widthScale) {
|
|
1952
|
+
const top = rect.top <= 18 ? rect.top + 3 : rect.top - 18;
|
|
1953
|
+
const left = rect.left <= 18 ? rect.left + 3 : rect.left - 18;
|
|
1954
|
+
return {
|
|
1955
|
+
transform: `scale(${1.2 * widthScale})`,
|
|
1956
|
+
top: Number.isNaN(top) ? undefined : top,
|
|
1957
|
+
left: Number.isNaN(left) ? undefined : left,
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
const getViewportDimensions = (containerElm) => {
|
|
1962
|
+
if (containerElm) {
|
|
1963
|
+
const containerRect = containerElm.getBoundingClientRect();
|
|
1964
|
+
return {
|
|
1965
|
+
width: containerRect.width,
|
|
1966
|
+
height: containerRect.height,
|
|
1967
|
+
};
|
|
1910
1968
|
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1969
|
+
return {
|
|
1970
|
+
width: window.innerWidth,
|
|
1971
|
+
height: window.innerHeight,
|
|
1972
|
+
};
|
|
1973
|
+
};
|
|
1974
|
+
const getElementDimensions = (targetElm, calloutElm) => ({
|
|
1975
|
+
targetRect: targetElm.getBoundingClientRect(),
|
|
1976
|
+
calloutRect: calloutElm.getBoundingClientRect(),
|
|
1977
|
+
});
|
|
1978
|
+
|
|
1979
|
+
const getAlignmentOrder = (alignment) => {
|
|
1980
|
+
switch (alignment) {
|
|
1981
|
+
case 'center':
|
|
1982
|
+
return ['center', 'left', 'right'];
|
|
1983
|
+
case 'left':
|
|
1984
|
+
return ['left', 'center', 'right'];
|
|
1985
|
+
case 'right':
|
|
1986
|
+
return ['right', 'center', 'left'];
|
|
1918
1987
|
}
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1988
|
+
};
|
|
1989
|
+
const calculateLeftPosition = ({ targetRect, calloutRect, hozOffset, align, }) => {
|
|
1990
|
+
switch (align) {
|
|
1991
|
+
case 'left':
|
|
1992
|
+
return targetRect.left + hozOffset;
|
|
1993
|
+
case 'right':
|
|
1994
|
+
return targetRect.right - calloutRect.width - hozOffset;
|
|
1995
|
+
case 'center':
|
|
1996
|
+
default:
|
|
1997
|
+
return targetRect.left + targetRect.width / 2 - calloutRect.width / 2;
|
|
1926
1998
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1999
|
+
};
|
|
2000
|
+
const calculateVerticalPosition = (targetRect, calloutRect, placement, padding, arrowSize) => {
|
|
2001
|
+
return placement === 'top'
|
|
2002
|
+
? targetRect.top - calloutRect.height - padding - arrowSize
|
|
2003
|
+
: targetRect.bottom + padding + arrowSize;
|
|
2004
|
+
};
|
|
2005
|
+
const calculateHorizontalPlacementPosition = (targetRect, calloutRect, placement, padding, arrowSize) => {
|
|
2006
|
+
const top = targetRect.top + targetRect.height / 2 - calloutRect.height / 2;
|
|
2007
|
+
const left = placement === 'right'
|
|
2008
|
+
? targetRect.right + padding + arrowSize
|
|
2009
|
+
: targetRect.left - calloutRect.width - padding - arrowSize;
|
|
2010
|
+
return { top, left };
|
|
2011
|
+
};
|
|
2012
|
+
|
|
2013
|
+
const isLeftPositionValid = (leftPos, calloutWidth, viewportWidth, padding, containerRect) => {
|
|
2014
|
+
if (containerRect) {
|
|
2015
|
+
return leftPos >= containerRect.left + padding && leftPos + calloutWidth <= containerRect.right - padding;
|
|
1934
2016
|
}
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
2017
|
+
return leftPos >= padding && leftPos + calloutWidth <= viewportWidth - padding;
|
|
2018
|
+
};
|
|
2019
|
+
const isVerticalPositionValid = (targetRect, calloutRect, placement, viewportHeight, padding, arrowSize, containerRect) => {
|
|
2020
|
+
if (containerRect) {
|
|
2021
|
+
return placement === 'top'
|
|
2022
|
+
? targetRect.top - calloutRect.height - padding - arrowSize >= containerRect.top
|
|
2023
|
+
: targetRect.bottom + calloutRect.height + padding + arrowSize <= containerRect.bottom;
|
|
1942
2024
|
}
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
2025
|
+
return placement === 'top'
|
|
2026
|
+
? targetRect.top - calloutRect.height - padding - arrowSize > 0
|
|
2027
|
+
: targetRect.bottom + calloutRect.height + padding + arrowSize < viewportHeight;
|
|
2028
|
+
};
|
|
2029
|
+
const isHorizontalPlacementValid = (targetRect, calloutRect, placement, viewportWidth, padding, arrowSize, containerRect) => {
|
|
2030
|
+
if (containerRect) {
|
|
2031
|
+
return placement === 'right'
|
|
2032
|
+
? targetRect.right + calloutRect.width + padding + arrowSize <= containerRect.right
|
|
2033
|
+
: targetRect.left - calloutRect.width - padding - arrowSize >= containerRect.left;
|
|
1950
2034
|
}
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
2035
|
+
return placement === 'right'
|
|
2036
|
+
? targetRect.right + calloutRect.width + padding + arrowSize < viewportWidth
|
|
2037
|
+
: targetRect.left - calloutRect.width - padding - arrowSize > 0;
|
|
2038
|
+
};
|
|
2039
|
+
|
|
2040
|
+
const generateVerticalPositionCandidates = (targetRect, calloutRect, viewportHeight, viewportWidth, alignment, hozOffset, padding, arrowSize, containerRect) => {
|
|
2041
|
+
const candidates = [];
|
|
2042
|
+
const placements = ['top', 'bottom'];
|
|
2043
|
+
placements.forEach((placement) => {
|
|
2044
|
+
const verticalPos = calculateVerticalPosition(targetRect, calloutRect, placement, padding, arrowSize);
|
|
2045
|
+
const verticalValid = isVerticalPositionValid(targetRect, calloutRect, placement, viewportHeight, padding, arrowSize, containerRect);
|
|
2046
|
+
const alignmentOrder = getAlignmentOrder(alignment);
|
|
2047
|
+
alignmentOrder.forEach((align) => {
|
|
2048
|
+
const horizontalPos = calculateLeftPosition({
|
|
2049
|
+
targetRect,
|
|
2050
|
+
calloutRect,
|
|
2051
|
+
hozOffset,
|
|
2052
|
+
align,
|
|
2053
|
+
});
|
|
2054
|
+
candidates.push({
|
|
2055
|
+
placement,
|
|
2056
|
+
top: verticalPos,
|
|
2057
|
+
left: horizontalPos,
|
|
2058
|
+
horizontalAlign: align,
|
|
2059
|
+
valid: verticalValid && isLeftPositionValid(horizontalPos, calloutRect.width, viewportWidth, padding, containerRect),
|
|
2060
|
+
});
|
|
2061
|
+
});
|
|
2062
|
+
});
|
|
2063
|
+
return candidates;
|
|
2064
|
+
};
|
|
2065
|
+
const generateHorizontalPositionCandidates = (targetRect, calloutRect, viewportWidth, padding, arrowSize, containerRect) => {
|
|
2066
|
+
const placements = ['left', 'right'];
|
|
2067
|
+
return placements.map((placement) => {
|
|
2068
|
+
const { top, left } = calculateHorizontalPlacementPosition(targetRect, calloutRect, placement, padding, arrowSize);
|
|
2069
|
+
return {
|
|
2070
|
+
placement,
|
|
2071
|
+
top,
|
|
2072
|
+
left,
|
|
2073
|
+
horizontalAlign: 'center',
|
|
2074
|
+
valid: isHorizontalPlacementValid(targetRect, calloutRect, placement, viewportWidth, padding, arrowSize, containerRect),
|
|
2075
|
+
};
|
|
2076
|
+
});
|
|
2077
|
+
};
|
|
2078
|
+
const generateAllPositionCandidates = (rectDimensions, viewport, alignment, hozOffset, padding, arrowSize, containerRect) => {
|
|
2079
|
+
const { targetRect, calloutRect } = rectDimensions;
|
|
2080
|
+
const verticalCandidates = generateVerticalPositionCandidates(targetRect, calloutRect, viewport.height, viewport.width, alignment, hozOffset, padding, arrowSize, containerRect);
|
|
2081
|
+
const horizontalCandidates = generateHorizontalPositionCandidates(targetRect, calloutRect, viewport.width, padding, arrowSize, containerRect);
|
|
2082
|
+
return [...verticalCandidates, ...horizontalCandidates];
|
|
2083
|
+
};
|
|
2084
|
+
|
|
2085
|
+
const selectBestPosition = (candidates) => {
|
|
2086
|
+
return candidates.find((p) => p.valid) || candidates[0];
|
|
2087
|
+
};
|
|
2088
|
+
const constrainToViewport = (position, calloutRect, viewport, padding, containerRect) => {
|
|
2089
|
+
if (containerRect) {
|
|
2090
|
+
const left = Math.max(containerRect.left + padding, Math.min(position.left, containerRect.right - calloutRect.width - padding));
|
|
2091
|
+
const top = Math.max(containerRect.top + padding, Math.min(position.top, containerRect.bottom - calloutRect.height - padding));
|
|
2092
|
+
return { top, left };
|
|
1958
2093
|
}
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
2094
|
+
const left = Math.max(padding, Math.min(position.left, viewport.width - calloutRect.width - padding));
|
|
2095
|
+
const top = Math.max(padding, Math.min(position.top, viewport.height - calloutRect.height - padding));
|
|
2096
|
+
return { top, left };
|
|
2097
|
+
};
|
|
2098
|
+
|
|
2099
|
+
const calcCalloutPosition = (options) => {
|
|
2100
|
+
const { targetElm, calloutElm, setPosition, hozOffset = CALLOUT_HORIZONTAL_OFFSET, alignment = 'center', containerElm, } = options;
|
|
2101
|
+
return () => {
|
|
2102
|
+
// 1. Get dimensions
|
|
2103
|
+
const rectDimensions = getElementDimensions(targetElm, calloutElm);
|
|
2104
|
+
const viewport = getViewportDimensions(containerElm);
|
|
2105
|
+
const containerRect = containerElm?.getBoundingClientRect();
|
|
2106
|
+
const padding = CALLOUT_PADDING;
|
|
2107
|
+
const arrowSize = CALLOUT_ARROW_SIZE;
|
|
2108
|
+
// 2. Generate all position candidates
|
|
2109
|
+
const candidates = generateAllPositionCandidates(rectDimensions, viewport, alignment, hozOffset, padding, arrowSize, containerRect);
|
|
2110
|
+
// 3. Select best position
|
|
2111
|
+
const bestPosition = selectBestPosition(candidates);
|
|
2112
|
+
// 4. Constrain to viewport
|
|
2113
|
+
const constrainedPosition = constrainToViewport({ top: bestPosition.top, left: bestPosition.left }, rectDimensions.calloutRect, viewport, padding, containerRect);
|
|
2114
|
+
// 5. Create final position object
|
|
2115
|
+
const finalPosition = {
|
|
2116
|
+
top: constrainedPosition.top,
|
|
2117
|
+
left: constrainedPosition.left,
|
|
2118
|
+
placement: bestPosition.placement,
|
|
2119
|
+
horizontalAlign: bestPosition.horizontalAlign,
|
|
2120
|
+
};
|
|
2121
|
+
setPosition(finalPosition);
|
|
2122
|
+
};
|
|
2123
|
+
};
|
|
1962
2124
|
|
|
2125
|
+
const logger$2 = createLogger({
|
|
2126
|
+
enabled: false,
|
|
2127
|
+
prefix: 'IframeNavigationBlockerV2',
|
|
2128
|
+
});
|
|
1963
2129
|
class IframeNavigationBlockerV2 {
|
|
1964
2130
|
doc;
|
|
1965
2131
|
win;
|
|
@@ -1974,11 +2140,11 @@ class IframeNavigationBlockerV2 {
|
|
|
1974
2140
|
this.doc = iframe.contentDocument;
|
|
1975
2141
|
this.win = iframe.contentWindow;
|
|
1976
2142
|
this.originalWindowOpen = this.win.open.bind(this.win);
|
|
1977
|
-
logger.configure({ enabled: !!config?.debug
|
|
2143
|
+
logger$2.configure({ enabled: !!config?.debug });
|
|
1978
2144
|
this.init();
|
|
1979
2145
|
}
|
|
1980
2146
|
init() {
|
|
1981
|
-
logger.log('Initializing...');
|
|
2147
|
+
logger$2.log('Initializing...');
|
|
1982
2148
|
try {
|
|
1983
2149
|
// Chặn navigation qua links
|
|
1984
2150
|
this.blockLinkNavigation();
|
|
@@ -1994,7 +2160,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1994
2160
|
this.injectCSP();
|
|
1995
2161
|
}
|
|
1996
2162
|
catch (error) {
|
|
1997
|
-
logger.error('Init error:', error);
|
|
2163
|
+
logger$2.error('Init error:', error);
|
|
1998
2164
|
}
|
|
1999
2165
|
}
|
|
2000
2166
|
blockLinkNavigation() {
|
|
@@ -2008,11 +2174,11 @@ class IframeNavigationBlockerV2 {
|
|
|
2008
2174
|
const href = link.getAttribute('href');
|
|
2009
2175
|
// Cho phép hash links và empty links
|
|
2010
2176
|
if (!href || href === '' || href === '#' || href.startsWith('#')) {
|
|
2011
|
-
logger.log('Allowed hash navigation:', href);
|
|
2177
|
+
logger$2.log('Allowed hash navigation:', href);
|
|
2012
2178
|
return;
|
|
2013
2179
|
}
|
|
2014
2180
|
// Chặn tất cả các loại navigation
|
|
2015
|
-
logger.log('Blocked link navigation to:', href);
|
|
2181
|
+
logger$2.log('Blocked link navigation to:', href);
|
|
2016
2182
|
e.preventDefault();
|
|
2017
2183
|
e.stopPropagation();
|
|
2018
2184
|
e.stopImmediatePropagation();
|
|
@@ -2028,7 +2194,7 @@ class IframeNavigationBlockerV2 {
|
|
|
2028
2194
|
if (link) {
|
|
2029
2195
|
const href = link.getAttribute('href');
|
|
2030
2196
|
if (href && !href.startsWith('#')) {
|
|
2031
|
-
logger.log('Blocked auxclick navigation');
|
|
2197
|
+
logger$2.log('Blocked auxclick navigation');
|
|
2032
2198
|
e.preventDefault();
|
|
2033
2199
|
e.stopPropagation();
|
|
2034
2200
|
e.stopImmediatePropagation();
|
|
@@ -2061,13 +2227,13 @@ class IframeNavigationBlockerV2 {
|
|
|
2061
2227
|
const action = form.getAttribute('action');
|
|
2062
2228
|
// Cho phép forms không có action
|
|
2063
2229
|
if (!action || action === '' || action === '#') {
|
|
2064
|
-
logger.log('Allowed same-page form');
|
|
2230
|
+
logger$2.log('Allowed same-page form');
|
|
2065
2231
|
e.preventDefault();
|
|
2066
2232
|
this.handleFormSubmit(form);
|
|
2067
2233
|
return;
|
|
2068
2234
|
}
|
|
2069
2235
|
// Chặn tất cả external submissions
|
|
2070
|
-
logger.log('Blocked form submission to:', action);
|
|
2236
|
+
logger$2.log('Blocked form submission to:', action);
|
|
2071
2237
|
e.preventDefault();
|
|
2072
2238
|
e.stopPropagation();
|
|
2073
2239
|
e.stopImmediatePropagation();
|
|
@@ -2081,7 +2247,7 @@ class IframeNavigationBlockerV2 {
|
|
|
2081
2247
|
return this.originalWindowOpen(...args);
|
|
2082
2248
|
}
|
|
2083
2249
|
const url = args[0]?.toString() || 'popup';
|
|
2084
|
-
logger.log('Blocked window.open:', url);
|
|
2250
|
+
logger$2.log('Blocked window.open:', url);
|
|
2085
2251
|
this.notifyBlockedNavigation(url);
|
|
2086
2252
|
return null;
|
|
2087
2253
|
});
|
|
@@ -2091,7 +2257,7 @@ class IframeNavigationBlockerV2 {
|
|
|
2091
2257
|
this.win.addEventListener('beforeunload', (e) => {
|
|
2092
2258
|
if (!this.isEnabled)
|
|
2093
2259
|
return;
|
|
2094
|
-
logger.log('Blocked beforeunload');
|
|
2260
|
+
logger$2.log('Blocked beforeunload');
|
|
2095
2261
|
e.preventDefault();
|
|
2096
2262
|
e.returnValue = '';
|
|
2097
2263
|
return '';
|
|
@@ -2100,7 +2266,7 @@ class IframeNavigationBlockerV2 {
|
|
|
2100
2266
|
this.win.addEventListener('unload', (e) => {
|
|
2101
2267
|
if (!this.isEnabled)
|
|
2102
2268
|
return;
|
|
2103
|
-
logger.log('Blocked unload');
|
|
2269
|
+
logger$2.log('Blocked unload');
|
|
2104
2270
|
e.preventDefault();
|
|
2105
2271
|
e.stopPropagation();
|
|
2106
2272
|
}, true);
|
|
@@ -2108,7 +2274,7 @@ class IframeNavigationBlockerV2 {
|
|
|
2108
2274
|
this.win.addEventListener('popstate', (e) => {
|
|
2109
2275
|
if (!this.isEnabled)
|
|
2110
2276
|
return;
|
|
2111
|
-
logger.log('Blocked popstate');
|
|
2277
|
+
logger$2.log('Blocked popstate');
|
|
2112
2278
|
e.preventDefault();
|
|
2113
2279
|
e.stopPropagation();
|
|
2114
2280
|
}, true);
|
|
@@ -2161,11 +2327,11 @@ class IframeNavigationBlockerV2 {
|
|
|
2161
2327
|
meta.httpEquiv = 'Content-Security-Policy';
|
|
2162
2328
|
meta.content = "navigate-to 'none'"; // Chặn tất cả navigation
|
|
2163
2329
|
this.doc.head.appendChild(meta);
|
|
2164
|
-
logger.log('Injected CSP');
|
|
2330
|
+
logger$2.log('Injected CSP');
|
|
2165
2331
|
}
|
|
2166
2332
|
}
|
|
2167
2333
|
catch (error) {
|
|
2168
|
-
logger.warn('Could not inject CSP:', error);
|
|
2334
|
+
logger$2.warn('Could not inject CSP:', error);
|
|
2169
2335
|
}
|
|
2170
2336
|
}
|
|
2171
2337
|
handleFormSubmit(form) {
|
|
@@ -2174,13 +2340,13 @@ class IframeNavigationBlockerV2 {
|
|
|
2174
2340
|
formData.forEach((value, key) => {
|
|
2175
2341
|
data[key] = value;
|
|
2176
2342
|
});
|
|
2177
|
-
logger.log('Handling form data:', data);
|
|
2343
|
+
logger$2.log('Handling form data:', data);
|
|
2178
2344
|
window.dispatchEvent(new CustomEvent('iframe-form-submit', {
|
|
2179
2345
|
detail: { form, data },
|
|
2180
2346
|
}));
|
|
2181
2347
|
}
|
|
2182
2348
|
notifyBlockedNavigation(url) {
|
|
2183
|
-
logger.warn('Navigation blocked to:', url);
|
|
2349
|
+
logger$2.warn('Navigation blocked to:', url);
|
|
2184
2350
|
window.dispatchEvent(new CustomEvent('iframe-navigation-blocked', {
|
|
2185
2351
|
detail: { url, timestamp: Date.now() },
|
|
2186
2352
|
}));
|
|
@@ -2230,19 +2396,19 @@ class IframeNavigationBlockerV2 {
|
|
|
2230
2396
|
}
|
|
2231
2397
|
enable() {
|
|
2232
2398
|
this.isEnabled = true;
|
|
2233
|
-
logger.log('Enabled');
|
|
2399
|
+
logger$2.log('Enabled');
|
|
2234
2400
|
}
|
|
2235
2401
|
enableMessage() {
|
|
2236
2402
|
this.showMessage = true;
|
|
2237
|
-
logger.log('Enabled message');
|
|
2403
|
+
logger$2.log('Enabled message');
|
|
2238
2404
|
}
|
|
2239
2405
|
disable() {
|
|
2240
2406
|
this.isEnabled = false;
|
|
2241
|
-
logger.log('Disabled');
|
|
2407
|
+
logger$2.log('Disabled');
|
|
2242
2408
|
}
|
|
2243
2409
|
disableMessage() {
|
|
2244
2410
|
this.showMessage = false;
|
|
2245
|
-
logger.log('Disabled message');
|
|
2411
|
+
logger$2.log('Disabled message');
|
|
2246
2412
|
}
|
|
2247
2413
|
destroy() {
|
|
2248
2414
|
this.isEnabled = false;
|
|
@@ -2250,10 +2416,14 @@ class IframeNavigationBlockerV2 {
|
|
|
2250
2416
|
// Cleanup observers
|
|
2251
2417
|
this.observers.forEach((observer) => observer.disconnect());
|
|
2252
2418
|
this.observers = [];
|
|
2253
|
-
logger.log('Destroyed');
|
|
2419
|
+
logger$2.log('Destroyed');
|
|
2254
2420
|
}
|
|
2255
2421
|
}
|
|
2256
2422
|
|
|
2423
|
+
const logger$1 = createLogger({
|
|
2424
|
+
enabled: false,
|
|
2425
|
+
prefix: 'IframeStyleReplacer',
|
|
2426
|
+
});
|
|
2257
2427
|
class IframeStyleReplacer {
|
|
2258
2428
|
doc;
|
|
2259
2429
|
win;
|
|
@@ -2266,7 +2436,7 @@ class IframeStyleReplacer {
|
|
|
2266
2436
|
this.doc = iframe.contentDocument;
|
|
2267
2437
|
this.win = iframe.contentWindow;
|
|
2268
2438
|
this.config = config;
|
|
2269
|
-
logger.configure({ enabled: !!config?.debug
|
|
2439
|
+
logger$1.configure({ enabled: !!config?.debug });
|
|
2270
2440
|
}
|
|
2271
2441
|
px(value) {
|
|
2272
2442
|
return `${value.toFixed(2)}px`;
|
|
@@ -2300,7 +2470,7 @@ class IframeStyleReplacer {
|
|
|
2300
2470
|
count++;
|
|
2301
2471
|
}
|
|
2302
2472
|
});
|
|
2303
|
-
logger.log(`Replaced ${count} inline style elements`);
|
|
2473
|
+
logger$1.log(`Replaced ${count} inline style elements`);
|
|
2304
2474
|
return count;
|
|
2305
2475
|
}
|
|
2306
2476
|
processStyleTags() {
|
|
@@ -2313,7 +2483,7 @@ class IframeStyleReplacer {
|
|
|
2313
2483
|
count++;
|
|
2314
2484
|
}
|
|
2315
2485
|
});
|
|
2316
|
-
logger.log(`Replaced ${count} <style> tags`);
|
|
2486
|
+
logger$1.log(`Replaced ${count} <style> tags`);
|
|
2317
2487
|
return count;
|
|
2318
2488
|
}
|
|
2319
2489
|
processRule(rule) {
|
|
@@ -2344,7 +2514,7 @@ class IframeStyleReplacer {
|
|
|
2344
2514
|
try {
|
|
2345
2515
|
// Bỏ qua external CSS (cross-origin)
|
|
2346
2516
|
if (sheet.href && !sheet.href.startsWith(this.win.location.origin)) {
|
|
2347
|
-
logger.log('Skipping external CSS:', sheet.href);
|
|
2517
|
+
logger$1.log('Skipping external CSS:', sheet.href);
|
|
2348
2518
|
return;
|
|
2349
2519
|
}
|
|
2350
2520
|
const rules = sheet.cssRules || sheet.rules;
|
|
@@ -2355,10 +2525,10 @@ class IframeStyleReplacer {
|
|
|
2355
2525
|
}
|
|
2356
2526
|
}
|
|
2357
2527
|
catch (e) {
|
|
2358
|
-
logger.warn('Cannot read stylesheet (CORS?):', e.message);
|
|
2528
|
+
logger$1.warn('Cannot read stylesheet (CORS?):', e.message);
|
|
2359
2529
|
}
|
|
2360
2530
|
});
|
|
2361
|
-
logger.log(`Replaced ${total} rules in stylesheets`);
|
|
2531
|
+
logger$1.log(`Replaced ${total} rules in stylesheets`);
|
|
2362
2532
|
return total;
|
|
2363
2533
|
}
|
|
2364
2534
|
async processLinkedStylesheets() {
|
|
@@ -2366,7 +2536,7 @@ class IframeStyleReplacer {
|
|
|
2366
2536
|
let count = 0;
|
|
2367
2537
|
for (const link of Array.from(links)) {
|
|
2368
2538
|
if (!link.href.startsWith(this.win.location.origin)) {
|
|
2369
|
-
logger.log('Skipping external CSS:', link.href);
|
|
2539
|
+
logger$1.log('Skipping external CSS:', link.href);
|
|
2370
2540
|
continue;
|
|
2371
2541
|
}
|
|
2372
2542
|
try {
|
|
@@ -2384,10 +2554,10 @@ class IframeStyleReplacer {
|
|
|
2384
2554
|
}
|
|
2385
2555
|
}
|
|
2386
2556
|
catch (e) {
|
|
2387
|
-
logger.warn('Cannot load CSS:', link.href, e);
|
|
2557
|
+
logger$1.warn('Cannot load CSS:', link.href, e);
|
|
2388
2558
|
}
|
|
2389
2559
|
}
|
|
2390
|
-
logger.log(`Replaced ${count} linked CSS files`);
|
|
2560
|
+
logger$1.log(`Replaced ${count} linked CSS files`);
|
|
2391
2561
|
return count;
|
|
2392
2562
|
}
|
|
2393
2563
|
getFinalHeight() {
|
|
@@ -2411,7 +2581,7 @@ class IframeStyleReplacer {
|
|
|
2411
2581
|
}
|
|
2412
2582
|
async run() {
|
|
2413
2583
|
try {
|
|
2414
|
-
logger.log('Starting viewport units replacement...');
|
|
2584
|
+
logger$1.log('Starting viewport units replacement...');
|
|
2415
2585
|
this.processInlineStyles();
|
|
2416
2586
|
this.processStyleTags();
|
|
2417
2587
|
this.processStylesheets();
|
|
@@ -2421,13 +2591,13 @@ class IframeStyleReplacer {
|
|
|
2421
2591
|
requestAnimationFrame(() => {
|
|
2422
2592
|
const height = this.getFinalHeight();
|
|
2423
2593
|
const width = this.getFinalWidth();
|
|
2424
|
-
logger.log('Calculated dimensions:', { height, width });
|
|
2594
|
+
logger$1.log('Calculated dimensions:', { height, width });
|
|
2425
2595
|
resolve({ height, width });
|
|
2426
2596
|
});
|
|
2427
2597
|
});
|
|
2428
2598
|
}
|
|
2429
2599
|
catch (err) {
|
|
2430
|
-
logger.error('Critical error:', err);
|
|
2600
|
+
logger$1.error('Critical error:', err);
|
|
2431
2601
|
return {
|
|
2432
2602
|
height: this.doc.body.scrollHeight || 1000,
|
|
2433
2603
|
width: this.doc.body.scrollWidth || 1000,
|
|
@@ -2439,6 +2609,10 @@ class IframeStyleReplacer {
|
|
|
2439
2609
|
}
|
|
2440
2610
|
}
|
|
2441
2611
|
|
|
2612
|
+
const logger = createLogger({
|
|
2613
|
+
enabled: false,
|
|
2614
|
+
prefix: 'IframeHelper',
|
|
2615
|
+
});
|
|
2442
2616
|
class IframeHelperFixer {
|
|
2443
2617
|
iframe;
|
|
2444
2618
|
config;
|
|
@@ -2448,7 +2622,7 @@ class IframeHelperFixer {
|
|
|
2448
2622
|
this.config = config;
|
|
2449
2623
|
this.iframe = config.iframe;
|
|
2450
2624
|
this.init();
|
|
2451
|
-
logger.configure({ enabled: !!config?.debug
|
|
2625
|
+
logger.configure({ enabled: !!config?.debug });
|
|
2452
2626
|
}
|
|
2453
2627
|
async init() {
|
|
2454
2628
|
if (!this.iframe) {
|
|
@@ -2475,7 +2649,7 @@ class IframeHelperFixer {
|
|
|
2475
2649
|
// Create replacer instance
|
|
2476
2650
|
this.replacer = new IframeStyleReplacer(this.iframe, this.config);
|
|
2477
2651
|
// Create navigation blocker
|
|
2478
|
-
this.navigationBlocker = new IframeNavigationBlockerV2(this.iframe);
|
|
2652
|
+
this.navigationBlocker = new IframeNavigationBlockerV2(this.iframe, { debug: this.config.debug });
|
|
2479
2653
|
// Run replacement
|
|
2480
2654
|
const result = await this.replacer.run();
|
|
2481
2655
|
logger.log('Process completed:', result);
|
|
@@ -2535,49 +2709,103 @@ function initIframeHelperFixer(config) {
|
|
|
2535
2709
|
return fixer;
|
|
2536
2710
|
}
|
|
2537
2711
|
|
|
2712
|
+
function validateAreaCreation(dataInfo, hash, areas) {
|
|
2713
|
+
if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks) {
|
|
2714
|
+
logger$3.warn('Cannot create area: missing heatmap data');
|
|
2715
|
+
return false;
|
|
2716
|
+
}
|
|
2717
|
+
if (!hash) {
|
|
2718
|
+
logger$3.warn('Cannot create area: missing hash');
|
|
2719
|
+
return false;
|
|
2720
|
+
}
|
|
2721
|
+
const alreadyExists = areas.some((area) => area.hash === hash);
|
|
2722
|
+
if (alreadyExists) {
|
|
2723
|
+
logger$3.warn(`Area already exists for element: ${hash}`);
|
|
2724
|
+
return false;
|
|
2725
|
+
}
|
|
2726
|
+
return true;
|
|
2727
|
+
}
|
|
2728
|
+
function identifyConflictingAreas(area) {
|
|
2729
|
+
const conflicts = {
|
|
2730
|
+
parentId: null,
|
|
2731
|
+
childrenIds: [],
|
|
2732
|
+
};
|
|
2733
|
+
// Case 1: New area is a child of an existing area
|
|
2734
|
+
if (area.parentNode) {
|
|
2735
|
+
conflicts.parentId = area.parentNode.id;
|
|
2736
|
+
logger$3.info(`New area "${area.selector}" is a child of existing area "${area.parentNode.selector}". Will remove parent.`);
|
|
2737
|
+
}
|
|
2738
|
+
// Case 2: New area is a parent of existing area(s)
|
|
2739
|
+
if (area.childNodes.size > 0) {
|
|
2740
|
+
area.childNodes.forEach((childArea) => {
|
|
2741
|
+
conflicts.childrenIds.push(childArea.id);
|
|
2742
|
+
});
|
|
2743
|
+
logger$3.info(`New area "${area.selector}" is a parent of ${area.childNodes.size} existing area(s). Will remove children.`);
|
|
2744
|
+
}
|
|
2745
|
+
return conflicts;
|
|
2746
|
+
}
|
|
2747
|
+
function clearGraphRelationships(area) {
|
|
2748
|
+
area.parentNode = null;
|
|
2749
|
+
area.childNodes.clear();
|
|
2750
|
+
}
|
|
2751
|
+
function removeConflictingAreas(conflicts, removeArea) {
|
|
2752
|
+
if (conflicts.parentId) {
|
|
2753
|
+
removeArea(conflicts.parentId);
|
|
2754
|
+
}
|
|
2755
|
+
conflicts.childrenIds.forEach((childId) => {
|
|
2756
|
+
removeArea(childId);
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2538
2759
|
function useAreaCreation(options = {}) {
|
|
2539
2760
|
const { customShadowRoot, onAreaCreated } = options;
|
|
2540
2761
|
const { dataInfo } = useHeatmapData();
|
|
2541
|
-
const { areas, addArea } = useHeatmapAreaClick();
|
|
2542
|
-
const
|
|
2543
|
-
if (!dataInfo
|
|
2544
|
-
logger.warn('Cannot create area: missing heatmap data');
|
|
2762
|
+
const { areas, addArea, removeArea } = useHeatmapAreaClick();
|
|
2763
|
+
const onAreaCreatedElement = useCallback((element) => {
|
|
2764
|
+
if (!dataInfo)
|
|
2545
2765
|
return;
|
|
2546
|
-
}
|
|
2547
2766
|
const hash = getElementHash(element);
|
|
2548
|
-
if (!hash) {
|
|
2549
|
-
logger.warn('Cannot create area: missing hash');
|
|
2550
|
-
return;
|
|
2551
|
-
}
|
|
2552
|
-
const alreadyExists = areas.some((area) => area.hash === hash);
|
|
2553
|
-
if (alreadyExists) {
|
|
2554
|
-
logger.warn(`Area already exists for element: ${hash}`);
|
|
2767
|
+
if (!validateAreaCreation(dataInfo, hash, areas)) {
|
|
2555
2768
|
return;
|
|
2556
2769
|
}
|
|
2557
2770
|
try {
|
|
2558
2771
|
const area = buildAreaNode(element, hash, dataInfo, customShadowRoot);
|
|
2559
2772
|
if (!area)
|
|
2560
2773
|
return;
|
|
2774
|
+
const tempAreas = [...areas, area];
|
|
2775
|
+
buildAreaGraph(tempAreas);
|
|
2776
|
+
const conflicts = identifyConflictingAreas(area);
|
|
2777
|
+
clearGraphRelationships(area);
|
|
2561
2778
|
addArea(area);
|
|
2779
|
+
removeConflictingAreas(conflicts, removeArea);
|
|
2562
2780
|
if (onAreaCreated) {
|
|
2563
2781
|
onAreaCreated(area);
|
|
2564
2782
|
}
|
|
2565
2783
|
}
|
|
2566
2784
|
catch (error) {
|
|
2567
|
-
logger.error('Failed to create area:', error);
|
|
2785
|
+
logger$3.error('Failed to create area:', error);
|
|
2568
2786
|
}
|
|
2569
|
-
}, [dataInfo, areas, addArea, customShadowRoot]);
|
|
2787
|
+
}, [dataInfo, areas, addArea, removeArea, customShadowRoot, onAreaCreated]);
|
|
2570
2788
|
return {
|
|
2571
|
-
onAreaCreatedElement
|
|
2789
|
+
onAreaCreatedElement,
|
|
2572
2790
|
};
|
|
2573
2791
|
}
|
|
2574
2792
|
|
|
2575
2793
|
function useAreaEditMode({ iframeRef, onAreaCreatedElement, enabled = false, }) {
|
|
2576
2794
|
const [hoveredElement, setHoveredElement] = useState(null);
|
|
2577
2795
|
const [isHovering, setIsHovering] = useState(false);
|
|
2796
|
+
// Use ref to always get latest hoveredElement without causing re-renders
|
|
2797
|
+
const hoveredElementRef = useRef(null);
|
|
2798
|
+
const onAreaCreatedElementRef = useRef(onAreaCreatedElement);
|
|
2578
2799
|
const { isEditingMode } = useHeatmapAreaClick();
|
|
2579
2800
|
const iframeDocument = iframeRef.current?.contentDocument;
|
|
2580
2801
|
const isActive = enabled && isEditingMode;
|
|
2802
|
+
// Keep refs in sync
|
|
2803
|
+
useEffect(() => {
|
|
2804
|
+
hoveredElementRef.current = hoveredElement;
|
|
2805
|
+
}, [hoveredElement]);
|
|
2806
|
+
useEffect(() => {
|
|
2807
|
+
onAreaCreatedElementRef.current = onAreaCreatedElement;
|
|
2808
|
+
}, [onAreaCreatedElement]);
|
|
2581
2809
|
const handleMouseMove = useCallback((e) => {
|
|
2582
2810
|
if (!isActive || !iframeDocument)
|
|
2583
2811
|
return;
|
|
@@ -2596,15 +2824,17 @@ function useAreaEditMode({ iframeRef, onAreaCreatedElement, enabled = false, })
|
|
|
2596
2824
|
setHoveredElement(null);
|
|
2597
2825
|
setIsHovering(false);
|
|
2598
2826
|
}, []);
|
|
2599
|
-
useCallback((e) => {
|
|
2600
|
-
|
|
2827
|
+
const handleClick = useCallback((e) => {
|
|
2828
|
+
const currentHoveredElement = hoveredElementRef.current;
|
|
2829
|
+
const currentCallback = onAreaCreatedElementRef.current;
|
|
2830
|
+
if (!isActive || !currentHoveredElement)
|
|
2601
2831
|
return;
|
|
2602
2832
|
e.stopPropagation();
|
|
2603
2833
|
e.preventDefault();
|
|
2604
|
-
if (!
|
|
2834
|
+
if (!currentCallback)
|
|
2605
2835
|
return;
|
|
2606
|
-
|
|
2607
|
-
}, [isActive
|
|
2836
|
+
currentCallback(currentHoveredElement);
|
|
2837
|
+
}, [isActive]);
|
|
2608
2838
|
useEffect(() => {
|
|
2609
2839
|
if (!isActive || !iframeDocument) {
|
|
2610
2840
|
setHoveredElement(null);
|
|
@@ -2624,7 +2854,7 @@ function useAreaEditMode({ iframeRef, onAreaCreatedElement, enabled = false, })
|
|
|
2624
2854
|
iframeDocument.addEventListener('mousemove', throttledMouseMove);
|
|
2625
2855
|
iframeDocument.addEventListener('scroll', handleMouseLeave);
|
|
2626
2856
|
iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
|
|
2627
|
-
|
|
2857
|
+
iframeDocument.addEventListener('click', handleClick);
|
|
2628
2858
|
return () => {
|
|
2629
2859
|
if (rafId) {
|
|
2630
2860
|
cancelAnimationFrame(rafId);
|
|
@@ -2632,7 +2862,7 @@ function useAreaEditMode({ iframeRef, onAreaCreatedElement, enabled = false, })
|
|
|
2632
2862
|
iframeDocument.removeEventListener('mousemove', throttledMouseMove);
|
|
2633
2863
|
iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
|
|
2634
2864
|
iframeDocument.removeEventListener('scroll', handleMouseLeave);
|
|
2635
|
-
|
|
2865
|
+
iframeDocument.removeEventListener('click', handleClick);
|
|
2636
2866
|
};
|
|
2637
2867
|
}, [isActive, iframeDocument]);
|
|
2638
2868
|
return {
|
|
@@ -2660,12 +2890,48 @@ const useAreaFilterVisible = (props) => {
|
|
|
2660
2890
|
return {};
|
|
2661
2891
|
};
|
|
2662
2892
|
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2893
|
+
function useAreaHydration(options) {
|
|
2894
|
+
const { shadowRoot, enabled = true } = options;
|
|
2895
|
+
const [isInitializing, setIsInitializing] = useState(false);
|
|
2896
|
+
const { dataInfo, clickAreas } = useHeatmapData();
|
|
2897
|
+
const { vizRef } = useHeatmapViz();
|
|
2898
|
+
const { areas, setAreas } = useHeatmapAreaClick();
|
|
2899
|
+
const hydratePersistedAreas = useCallback(() => {
|
|
2900
|
+
if (isInitializing)
|
|
2901
|
+
return;
|
|
2902
|
+
if (!vizRef)
|
|
2903
|
+
return;
|
|
2904
|
+
if (!clickAreas)
|
|
2905
|
+
return;
|
|
2906
|
+
if (!dataInfo)
|
|
2907
|
+
return;
|
|
2908
|
+
logger$3.info(`Hydrating ${clickAreas.length} persisted areas...`);
|
|
2909
|
+
const hydratedAreas = hydrateAreas({ clickAreas, heatmapInfo: dataInfo, vizRef, shadowRoot });
|
|
2910
|
+
if (!hydratedAreas?.length) {
|
|
2911
|
+
logger$3.warn('No areas could be hydrated - all elements may have been removed from DOM');
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
setIsInitializing(true);
|
|
2915
|
+
buildAreaGraph(hydratedAreas);
|
|
2916
|
+
setAreas(hydratedAreas);
|
|
2917
|
+
logger$3.info(`Successfully hydrated ${hydratedAreas.length} areas`);
|
|
2918
|
+
}, [dataInfo, vizRef, isInitializing, clickAreas]);
|
|
2919
|
+
useEffect(() => {
|
|
2920
|
+
if (!enabled)
|
|
2921
|
+
return;
|
|
2922
|
+
if (!dataInfo)
|
|
2923
|
+
return;
|
|
2924
|
+
if (!clickAreas)
|
|
2925
|
+
return;
|
|
2926
|
+
if (areas.length)
|
|
2927
|
+
return;
|
|
2928
|
+
hydratePersistedAreas();
|
|
2929
|
+
}, [enabled, dataInfo, clickAreas, areas.length, hydratePersistedAreas]);
|
|
2930
|
+
return {
|
|
2931
|
+
hydratePersistedAreas,
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2669
2935
|
function useAreaInteraction(options = {}) {
|
|
2670
2936
|
const { onAreaClick } = options;
|
|
2671
2937
|
const { selectedArea, hoveredArea, isEditingMode, setSelectedArea, setHoveredArea } = useHeatmapAreaClick();
|
|
@@ -2760,9 +3026,10 @@ function useAreaRectSync(options) {
|
|
|
2760
3026
|
area.rect.update(newRect);
|
|
2761
3027
|
}
|
|
2762
3028
|
catch (error) {
|
|
2763
|
-
logger.error(`Failed to update rect for area ${area.id}:`, error);
|
|
3029
|
+
logger$3.error(`Failed to update rect for area ${area.id}:`, error);
|
|
2764
3030
|
}
|
|
2765
3031
|
});
|
|
3032
|
+
buildAreaGraph(areas);
|
|
2766
3033
|
}, [areas, iframeDocument, shadowRoot, enabled, vizRef]);
|
|
2767
3034
|
}
|
|
2768
3035
|
|
|
@@ -3224,22 +3491,26 @@ function useAreaScrollSync(options) {
|
|
|
3224
3491
|
}
|
|
3225
3492
|
|
|
3226
3493
|
const useAreaTopAutoDetect = (props) => {
|
|
3227
|
-
const {
|
|
3228
|
-
const
|
|
3229
|
-
const { dataInfo } = useHeatmapData();
|
|
3494
|
+
const { autoCreateTopN, shadowRoot, disabled = false } = props;
|
|
3495
|
+
const [isInitializing, setIsInitializing] = useState(disabled);
|
|
3496
|
+
const { dataInfo, clickAreas } = useHeatmapData();
|
|
3230
3497
|
const { vizRef } = useHeatmapViz();
|
|
3231
3498
|
const { areas, addArea } = useHeatmapAreaClick();
|
|
3232
3499
|
useEffect(() => {
|
|
3500
|
+
if (isInitializing)
|
|
3501
|
+
return;
|
|
3233
3502
|
if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks)
|
|
3234
3503
|
return;
|
|
3235
|
-
if (autoCreateTopN
|
|
3504
|
+
if (!autoCreateTopN)
|
|
3505
|
+
return;
|
|
3506
|
+
if (clickAreas?.length)
|
|
3236
3507
|
return;
|
|
3237
|
-
if (areas
|
|
3508
|
+
if (areas?.length)
|
|
3238
3509
|
return;
|
|
3239
3510
|
const topElements = getTopElementsByClicks(dataInfo.elementMapInfo, autoCreateTopN);
|
|
3240
3511
|
const newAreas = [];
|
|
3241
3512
|
topElements.forEach(({ hash }) => {
|
|
3242
|
-
const element =
|
|
3513
|
+
const element = findElementByHash({ hash, vizRef });
|
|
3243
3514
|
if (!element)
|
|
3244
3515
|
return;
|
|
3245
3516
|
const area = buildAreaNode(element, hash, dataInfo);
|
|
@@ -3247,8 +3518,16 @@ const useAreaTopAutoDetect = (props) => {
|
|
|
3247
3518
|
return;
|
|
3248
3519
|
newAreas.push(area);
|
|
3249
3520
|
});
|
|
3250
|
-
newAreas
|
|
3251
|
-
|
|
3521
|
+
buildAreaGraph(newAreas);
|
|
3522
|
+
const areasToAdd = newAreas.filter((area) => {
|
|
3523
|
+
if (area.parentNode) {
|
|
3524
|
+
return false;
|
|
3525
|
+
}
|
|
3526
|
+
return true;
|
|
3527
|
+
});
|
|
3528
|
+
setIsInitializing(true);
|
|
3529
|
+
areasToAdd.forEach((area) => addArea(area));
|
|
3530
|
+
}, [dataInfo, autoCreateTopN, areas.length, shadowRoot, vizRef, isInitializing, clickAreas]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
3252
3531
|
return {};
|
|
3253
3532
|
};
|
|
3254
3533
|
|
|
@@ -3289,16 +3568,14 @@ const useScrollmap = () => {
|
|
|
3289
3568
|
const { vizRef } = useHeatmapViz();
|
|
3290
3569
|
const { scrollmap } = useHeatmapData();
|
|
3291
3570
|
const start = useCallback(() => {
|
|
3292
|
-
// if (isInitialized) return;
|
|
3293
3571
|
if (!vizRef || !scrollmap || scrollmap.length === 0)
|
|
3294
3572
|
return;
|
|
3295
3573
|
try {
|
|
3296
3574
|
vizRef?.clearmap?.();
|
|
3297
3575
|
vizRef?.scrollmap?.(scrollmap);
|
|
3298
|
-
// setIsInitialized(true);
|
|
3299
3576
|
}
|
|
3300
3577
|
catch (error) {
|
|
3301
|
-
|
|
3578
|
+
logger$3.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
|
|
3302
3579
|
}
|
|
3303
3580
|
}, [vizRef, scrollmap]);
|
|
3304
3581
|
return { start };
|
|
@@ -3478,6 +3755,7 @@ payloads, onSuccess) {
|
|
|
3478
3755
|
targetWidth: docWidth,
|
|
3479
3756
|
targetHeight: docHeight,
|
|
3480
3757
|
iframe: iframe,
|
|
3758
|
+
debug: false,
|
|
3481
3759
|
onSuccess: (data) => {
|
|
3482
3760
|
iframe.height = `${data.height}px`;
|
|
3483
3761
|
onSuccess(data.height);
|
|
@@ -3656,7 +3934,7 @@ const useContainerDimensions = (props) => {
|
|
|
3656
3934
|
return { containerWidth, containerHeight };
|
|
3657
3935
|
};
|
|
3658
3936
|
|
|
3659
|
-
const useContentDimensions = ({ iframeRef
|
|
3937
|
+
const useContentDimensions = ({ iframeRef }) => {
|
|
3660
3938
|
const contentWidth = useHeatmapConfigStore((state) => state.width);
|
|
3661
3939
|
useEffect(() => {
|
|
3662
3940
|
if (!contentWidth)
|
|
@@ -3669,8 +3947,8 @@ const useContentDimensions = ({ iframeRef, }) => {
|
|
|
3669
3947
|
};
|
|
3670
3948
|
|
|
3671
3949
|
const useObserveIframeHeight = (props) => {
|
|
3672
|
-
const { iframeRef
|
|
3673
|
-
const { setIframeHeight } = useHeatmapViz();
|
|
3950
|
+
const { iframeRef } = props;
|
|
3951
|
+
const { iframeHeight, setIframeHeight, isRenderViz } = useHeatmapViz();
|
|
3674
3952
|
const resizeObserverRef = useRef(null);
|
|
3675
3953
|
const mutationObserverRef = useRef(null);
|
|
3676
3954
|
const debounceTimerRef = useRef(null);
|
|
@@ -3728,7 +4006,7 @@ const useObserveIframeHeight = (props) => {
|
|
|
3728
4006
|
}, [updateIframeHeight]);
|
|
3729
4007
|
useEffect(() => {
|
|
3730
4008
|
const iframe = iframeRef.current;
|
|
3731
|
-
if (!iframe || !isRenderViz)
|
|
4009
|
+
if (!iframe || !iframeHeight || !isRenderViz)
|
|
3732
4010
|
return;
|
|
3733
4011
|
const setupObservers = () => {
|
|
3734
4012
|
try {
|
|
@@ -3790,7 +4068,7 @@ const useObserveIframeHeight = (props) => {
|
|
|
3790
4068
|
}
|
|
3791
4069
|
iframe.removeEventListener('load', setupObservers);
|
|
3792
4070
|
};
|
|
3793
|
-
}, [iframeRef, isRenderViz, updateIframeHeight, debouncedUpdate, immediateUpdate]);
|
|
4071
|
+
}, [iframeRef, iframeHeight, isRenderViz, updateIframeHeight, debouncedUpdate, immediateUpdate]);
|
|
3794
4072
|
return {};
|
|
3795
4073
|
};
|
|
3796
4074
|
|
|
@@ -3839,7 +4117,7 @@ const useScaleCalculation = (props) => {
|
|
|
3839
4117
|
return { widthScale, isScaledToFit, minZoomRatio };
|
|
3840
4118
|
};
|
|
3841
4119
|
|
|
3842
|
-
const useScrollSync = ({ widthScale, iframeRef
|
|
4120
|
+
const useScrollSync = ({ widthScale, iframeRef }) => {
|
|
3843
4121
|
const handleScroll = useCallback((scrollTop) => {
|
|
3844
4122
|
const iframe = iframeRef.current;
|
|
3845
4123
|
if (!iframe || widthScale <= 0)
|
|
@@ -3860,13 +4138,13 @@ const useScrollSync = ({ widthScale, iframeRef, }) => {
|
|
|
3860
4138
|
};
|
|
3861
4139
|
|
|
3862
4140
|
const useHeatmapScale = (props) => {
|
|
3863
|
-
const { wrapperRef, iframeRef, iframeHeight
|
|
4141
|
+
const { wrapperRef, iframeRef, iframeHeight } = props;
|
|
3864
4142
|
// 1. Observe container dimensions
|
|
3865
4143
|
const { containerWidth, containerHeight } = useContainerDimensions({ wrapperRef });
|
|
3866
4144
|
// 2. Get content dimensions from config
|
|
3867
4145
|
const { contentWidth } = useContentDimensions({ iframeRef });
|
|
3868
4146
|
// 3. Observe iframe height (now reacts to width changes)
|
|
3869
|
-
useObserveIframeHeight({ iframeRef
|
|
4147
|
+
useObserveIframeHeight({ iframeRef });
|
|
3870
4148
|
// 4. Calculate scale
|
|
3871
4149
|
const { widthScale } = useScaleCalculation({
|
|
3872
4150
|
containerWidth,
|
|
@@ -3879,8 +4157,6 @@ const useHeatmapScale = (props) => {
|
|
|
3879
4157
|
const scaledHeight = iframeHeight * widthScale;
|
|
3880
4158
|
const scaledWidth = contentWidth * widthScale;
|
|
3881
4159
|
return {
|
|
3882
|
-
containerWidth,
|
|
3883
|
-
containerHeight,
|
|
3884
4160
|
scaledWidth,
|
|
3885
4161
|
scaledHeight,
|
|
3886
4162
|
handleScroll,
|
|
@@ -4061,10 +4337,10 @@ const useScrollmapZones = (options) => {
|
|
|
4061
4337
|
const newZones = createZones(scrollmap);
|
|
4062
4338
|
setZones(newZones);
|
|
4063
4339
|
setIsReady(true);
|
|
4064
|
-
|
|
4340
|
+
logger$3.log(`[useScrollmap] Created ${newZones.length} zones in ${mode} mode`);
|
|
4065
4341
|
}
|
|
4066
4342
|
catch (error) {
|
|
4067
|
-
|
|
4343
|
+
logger$3.error('[useScrollmap] Error:', error);
|
|
4068
4344
|
setIsReady(false);
|
|
4069
4345
|
}
|
|
4070
4346
|
}, [enabled, scrollmap, mode, createZones]);
|
|
@@ -4199,12 +4475,8 @@ class PerformanceLogger {
|
|
|
4199
4475
|
const totalRenders = renderMetrics.length;
|
|
4200
4476
|
const totalHookCalls = hookMetrics.length;
|
|
4201
4477
|
const totalStoreUpdates = storeMetrics.length;
|
|
4202
|
-
const renderDurations = renderMetrics
|
|
4203
|
-
|
|
4204
|
-
.map((m) => m.duration);
|
|
4205
|
-
const averageRenderTime = renderDurations.length > 0
|
|
4206
|
-
? renderDurations.reduce((a, b) => a + b, 0) / renderDurations.length
|
|
4207
|
-
: 0;
|
|
4478
|
+
const renderDurations = renderMetrics.filter((m) => m.duration).map((m) => m.duration);
|
|
4479
|
+
const averageRenderTime = renderDurations.length > 0 ? renderDurations.reduce((a, b) => a + b, 0) / renderDurations.length : 0;
|
|
4208
4480
|
// View-specific metrics
|
|
4209
4481
|
const viewMetrics = {};
|
|
4210
4482
|
this.metrics.forEach((metric) => {
|
|
@@ -4752,6 +5024,7 @@ function useAreaRenderer(options) {
|
|
|
4752
5024
|
}
|
|
4753
5025
|
|
|
4754
5026
|
const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true, onAreaClick, }) => {
|
|
5027
|
+
const { clickAreas } = useHeatmapData();
|
|
4755
5028
|
const { resetView } = useHeatmapAreaClick();
|
|
4756
5029
|
const { areasPortal, editHighlightPortal, isReady } = useAreaRenderer({
|
|
4757
5030
|
iframeRef,
|
|
@@ -4759,8 +5032,9 @@ const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, e
|
|
|
4759
5032
|
shadowRoot,
|
|
4760
5033
|
onAreaClick,
|
|
4761
5034
|
});
|
|
4762
|
-
useAreaTopAutoDetect({
|
|
5035
|
+
useAreaTopAutoDetect({ autoCreateTopN, shadowRoot, disabled: !!clickAreas?.length });
|
|
4763
5036
|
useAreaFilterVisible({ iframeRef, enableOverlapResolution });
|
|
5037
|
+
useAreaHydration({ shadowRoot });
|
|
4764
5038
|
useEffect(() => {
|
|
4765
5039
|
return () => {
|
|
4766
5040
|
resetView();
|
|
@@ -4772,7 +5046,7 @@ const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, e
|
|
|
4772
5046
|
};
|
|
4773
5047
|
VizAreaClick.displayName = 'VizAreaClick';
|
|
4774
5048
|
|
|
4775
|
-
const RankBadge = ({ index, elementRect, widthScale, clickOnElement
|
|
5049
|
+
const RankBadge = ({ index, elementRect, widthScale, clickOnElement }) => {
|
|
4776
5050
|
const style = calculateRankPosition(elementRect, widthScale);
|
|
4777
5051
|
return (jsx("div", { className: "gx-hm-rank-badge", style: style, onClick: clickOnElement, children: index }));
|
|
4778
5052
|
};
|
|
@@ -4788,7 +5062,7 @@ const DefaultRankBadges = ({ getRect, hidden }) => {
|
|
|
4788
5062
|
const rect = getRect(element);
|
|
4789
5063
|
if (!rect)
|
|
4790
5064
|
return null;
|
|
4791
|
-
return
|
|
5065
|
+
return jsx(RankBadge, { index: index + 1, elementRect: rect, widthScale: widthScale }, element.hash);
|
|
4792
5066
|
}) }));
|
|
4793
5067
|
};
|
|
4794
5068
|
|
|
@@ -4945,7 +5219,7 @@ const VizClickmap = ({ iframeRef, visualRef, wrapperRef }) => {
|
|
|
4945
5219
|
case IClickMode.Default:
|
|
4946
5220
|
return jsx(VizElements, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef });
|
|
4947
5221
|
case IClickMode.Area:
|
|
4948
|
-
return (jsx(VizAreaClick, { iframeRef: iframeRef, visualRef: visualRef, autoCreateTopN:
|
|
5222
|
+
return (jsx(VizAreaClick, { iframeRef: iframeRef, visualRef: visualRef, autoCreateTopN: 10, onAreaClick: (area) => {
|
|
4949
5223
|
console.log('area clicked', area);
|
|
4950
5224
|
} }));
|
|
4951
5225
|
}
|
|
@@ -5043,7 +5317,7 @@ const TooltipByZone = ({ zone }) => {
|
|
|
5043
5317
|
return jsx(BasicTooltipContent, { zone: zone });
|
|
5044
5318
|
}
|
|
5045
5319
|
};
|
|
5046
|
-
return
|
|
5320
|
+
return jsx("div", { style: { paddingTop: '12px', borderTop: '1px solid #E5E7EB' }, children: contentMarkup() });
|
|
5047
5321
|
};
|
|
5048
5322
|
const BasicTooltipContent = ({ zone }) => {
|
|
5049
5323
|
if (!zone)
|
|
@@ -5053,11 +5327,11 @@ const BasicTooltipContent = ({ zone }) => {
|
|
|
5053
5327
|
const MetricsTooltipContent = ({ zone }) => {
|
|
5054
5328
|
if (!zone)
|
|
5055
5329
|
return null;
|
|
5056
|
-
return (jsxs(Fragment, { children: [jsx("div", { style: { fontWeight: 600, marginBottom: '8px' }, children: zone.label }), jsxs("div", { style: { fontSize: '20px', fontWeight: 700, marginBottom: '8px' }, children: [zone.percUsers.toFixed(2), "% users"] }), zone.metrics && (jsxs("div", { style: { display: 'grid', gap: '6px', fontSize: '13px' }, children: [zone.metrics.revenue !== undefined && (jsx(MetricRow, { label: "Revenue", value: `$${zone.metrics.revenue.toFixed(2)}` })), zone.metrics.conversionRate !== undefined && (jsx(MetricRow, { label: "Conversion", value: `${zone.metrics.conversionRate.toFixed(2)}%` })), zone.metrics.orders !== undefined &&
|
|
5330
|
+
return (jsxs(Fragment, { children: [jsx("div", { style: { fontWeight: 600, marginBottom: '8px' }, children: zone.label }), jsxs("div", { style: { fontSize: '20px', fontWeight: 700, marginBottom: '8px' }, children: [zone.percUsers.toFixed(2), "% users"] }), zone.metrics && (jsxs("div", { style: { display: 'grid', gap: '6px', fontSize: '13px' }, children: [zone.metrics.revenue !== undefined && (jsx(MetricRow, { label: "Revenue", value: `$${zone.metrics.revenue.toFixed(2)}` })), zone.metrics.conversionRate !== undefined && (jsx(MetricRow, { label: "Conversion", value: `${zone.metrics.conversionRate.toFixed(2)}%` })), zone.metrics.orders !== undefined && jsx(MetricRow, { label: "Orders", value: zone.metrics.orders.toString() })] }))] }));
|
|
5057
5331
|
};
|
|
5058
5332
|
const MetricRow = ({ label, value }) => (jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [jsxs("span", { style: { color: '#605E5C' }, children: [label, ":"] }), jsx("span", { style: { fontWeight: 600 }, children: value })] }));
|
|
5059
5333
|
|
|
5060
|
-
const HoverZones = ({ iframeRef, wrapperRef, position, currentScrollPercent
|
|
5334
|
+
const HoverZones = ({ iframeRef, wrapperRef, position, currentScrollPercent }) => {
|
|
5061
5335
|
const { scrollmap } = useHeatmapData();
|
|
5062
5336
|
// const hoveredZone = useHeatmapVizScrollmapStore((state) => state.hoveredZone);
|
|
5063
5337
|
// const setHoveredZone = useHeatmapVizScrollmapStore((state) => state.setHoveredZone);
|
|
@@ -5278,15 +5552,13 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
|
|
|
5278
5552
|
const contentWidth = useHeatmapConfigStore((state) => state.width || 0);
|
|
5279
5553
|
const wrapperRef = useRef(null);
|
|
5280
5554
|
const visualRef = useRef(null);
|
|
5281
|
-
const { setSelectedElement } = useHeatmapClick();
|
|
5282
|
-
const { iframeHeight, setIframeHeight, isRenderViz } = useHeatmapViz();
|
|
5283
5555
|
const { iframeRef } = useHeatmapRenderByMode(mode);
|
|
5556
|
+
const { iframeHeight } = useHeatmapViz();
|
|
5284
5557
|
const { scaledHeight, handleScroll } = useHeatmapScale({
|
|
5285
5558
|
wrapperRef,
|
|
5286
5559
|
iframeRef,
|
|
5287
5560
|
visualRef,
|
|
5288
5561
|
iframeHeight,
|
|
5289
|
-
isRenderViz,
|
|
5290
5562
|
});
|
|
5291
5563
|
useHeatmapCanvas();
|
|
5292
5564
|
useRenderCount('VizDomRenderer');
|
|
@@ -5294,13 +5566,6 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
|
|
|
5294
5566
|
const scrollTop = e.currentTarget.scrollTop;
|
|
5295
5567
|
handleScroll(scrollTop);
|
|
5296
5568
|
};
|
|
5297
|
-
const cleanUp = () => {
|
|
5298
|
-
setIframeHeight(0);
|
|
5299
|
-
setSelectedElement(null);
|
|
5300
|
-
};
|
|
5301
|
-
useEffect(() => {
|
|
5302
|
-
return cleanUp;
|
|
5303
|
-
}, []);
|
|
5304
5569
|
return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [jsx(VizClickmap, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef }), jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, scrolling: "no" }), jsx(VizScrollMap, { iframeRef: iframeRef, wrapperRef: visualRef })] }));
|
|
5305
5570
|
};
|
|
5306
5571
|
|
|
@@ -5309,13 +5574,22 @@ const VizLoading = () => {
|
|
|
5309
5574
|
};
|
|
5310
5575
|
|
|
5311
5576
|
const VizDomHeatmap = () => {
|
|
5312
|
-
const { iframeHeight, setIframeHeight } = useHeatmapViz();
|
|
5577
|
+
const { iframeHeight, setIframeHeight, setVizRef, setIsRenderViz } = useHeatmapViz();
|
|
5578
|
+
const { setSelectedElement, setHoveredElement } = useHeatmapClick();
|
|
5579
|
+
const { setSelectedArea, setHoveredArea, setAreas } = useHeatmapAreaClick();
|
|
5313
5580
|
useRenderCount('VizDomHeatmap');
|
|
5581
|
+
const cleanUp = () => {
|
|
5582
|
+
setVizRef(null);
|
|
5583
|
+
setIframeHeight(0);
|
|
5584
|
+
setIsRenderViz(false);
|
|
5585
|
+
setSelectedElement(null);
|
|
5586
|
+
setHoveredElement(null);
|
|
5587
|
+
setSelectedArea(null);
|
|
5588
|
+
setHoveredArea(null);
|
|
5589
|
+
setAreas([]);
|
|
5590
|
+
};
|
|
5314
5591
|
useEffect(() => {
|
|
5315
|
-
return
|
|
5316
|
-
console.log('🚀 🐥 ~ useEffect ~ return:');
|
|
5317
|
-
setIframeHeight(0);
|
|
5318
|
-
};
|
|
5592
|
+
return cleanUp;
|
|
5319
5593
|
}, []);
|
|
5320
5594
|
return (jsxs(VizContainer, { isActive: true, children: [jsx(VizDomRenderer, {}), iframeHeight === 0 && jsx(VizLoading, {})] }));
|
|
5321
5595
|
};
|
|
@@ -5378,21 +5652,16 @@ const HeatmapPreview = () => {
|
|
|
5378
5652
|
|
|
5379
5653
|
const ContentTopBar = () => {
|
|
5380
5654
|
const controls = useHeatmapControlStore((state) => state.controls);
|
|
5381
|
-
useHeatmapConfigStore((state) => state.mode);
|
|
5382
5655
|
const TopBar = controls.TopBar;
|
|
5383
|
-
// In compare mode, hide individual top bars since we have a global header
|
|
5384
|
-
// if (mode === 'compare') {
|
|
5385
|
-
// return null;
|
|
5386
|
-
// }
|
|
5387
5656
|
return (jsx(BoxStack, { id: "gx-hm-content-header", flexDirection: "row", alignItems: "center", overflow: "auto", zIndex: 1, backgroundColor: "white", style: {
|
|
5388
5657
|
borderBottom: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
|
|
5389
5658
|
}, children: TopBar && jsx(TopBar, {}) }));
|
|
5390
5659
|
};
|
|
5391
5660
|
|
|
5392
|
-
const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
|
|
5661
|
+
const HeatmapLayout = ({ data, clickmap, clickAreas, scrollmap, controls, dataInfo, }) => {
|
|
5393
5662
|
useRegisterControl(controls);
|
|
5394
5663
|
useRegisterData(data, dataInfo);
|
|
5395
|
-
useRegisterHeatmap({ clickmap, scrollmap });
|
|
5664
|
+
useRegisterHeatmap({ clickmap, scrollmap, clickAreas });
|
|
5396
5665
|
useRegisterConfig();
|
|
5397
5666
|
performanceLogger.configure({
|
|
5398
5667
|
enabled: true,
|
|
@@ -5520,4 +5789,4 @@ const AreaOverlay = ({ area, onClick, onMouseEnter, onMouseLeave, isSelected, is
|
|
|
5520
5789
|
` }), showLabel && jsx(AreaLabel, { clickDist: area.clickDist, totalClicks: area.totalclicks, kind: area.kind })] }));
|
|
5521
5790
|
};
|
|
5522
5791
|
|
|
5523
|
-
export { AreaEditHighlight, AreaOverlay, DEFAULT_SIDEBAR_WIDTH, DEFAULT_VIEW_ID, GraphView, HEATMAP_CONFIG, HEATMAP_IFRAME, HEATMAP_STYLE, HeatmapLayout, IClickMode, IClickType, IHeatmapType, IScrollType, ViewIdContext, Z_INDEX, compareViewPerformance, convertViewportToIframeCoords, createStorePerformanceTracker, downloadPerformanceReport, getCompareViewId, getMetricsByViewId, getPerformanceReportJSON, getScrollGradientColor, performanceLogger, printPerformanceSummary, scrollToElementIfNeeded, sendPerformanceReport, trackStoreAction, useAreaCreation, useAreaEditMode, useAreaFilterVisible, useAreaInteraction, useAreaPortals, useAreaRectSync, useAreaRendererContainer, useAreaScrollSync, useAreaTopAutoDetect, useClickedElement, useDebounceCallback, useElementCalloutVisible, useHeatmapAreaClick, useHeatmapCanvas, useHeatmapClick, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapCopyView, useHeatmapData, useHeatmapEffects, useHeatmapElementPosition, useHeatmapLiveStore, useHeatmapRenderByMode, useHeatmapScale, useHeatmapScroll, useHeatmapViz, useHoveredElement, useMeasureFunction, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useRenderCount, useScrollmapZones, useTrackHookCall, useViewIdContext, useVizLiveRender, useWhyDidYouUpdate, useWrapperRefHeight, useZonePositions, withPerformanceTracking };
|
|
5792
|
+
export { AreaEditHighlight, AreaOverlay, DEFAULT_SIDEBAR_WIDTH, DEFAULT_VIEW_ID, GraphView, HEATMAP_CONFIG, HEATMAP_IFRAME, HEATMAP_STYLE, HeatmapLayout, IClickMode, IClickType, IHeatmapType, IScrollType, ViewIdContext, Z_INDEX, compareViewPerformance, convertViewportToIframeCoords, createStorePerformanceTracker, downloadPerformanceReport, getCompareViewId, getMetricsByViewId, getPerformanceReportJSON, getScrollGradientColor, performanceLogger, printPerformanceSummary, scrollToElementIfNeeded, sendPerformanceReport, serializeAreas, trackStoreAction, useAreaCreation, useAreaEditMode, useAreaFilterVisible, useAreaHydration, useAreaInteraction, useAreaPortals, useAreaRectSync, useAreaRendererContainer, useAreaScrollSync, useAreaTopAutoDetect, useClickedElement, useDebounceCallback, useElementCalloutVisible, useHeatmapAreaClick, useHeatmapCanvas, useHeatmapClick, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapCopyView, useHeatmapData, useHeatmapEffects, useHeatmapElementPosition, useHeatmapLiveStore, useHeatmapRenderByMode, useHeatmapScale, useHeatmapScroll, useHeatmapViz, useHoveredElement, useMeasureFunction, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useRenderCount, useScrollmapZones, useTrackHookCall, useViewIdContext, useVizLiveRender, useWhyDidYouUpdate, useWrapperRefHeight, useZonePositions, withPerformanceTracking };
|