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