@gemx-dev/heatmap-react 3.5.52 → 3.5.53
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/VizAreaClick/AreaEditHighlight.d.ts.map +1 -1
- package/dist/esm/components/VizAreaClick/AreaOverlay.d.ts.map +1 -1
- package/dist/esm/components/VizAreaClick/PortalAreaRenderer.d.ts +1 -1
- package/dist/esm/components/VizAreaClick/PortalAreaRenderer.d.ts.map +1 -1
- package/dist/esm/components/VizAreaClick/VizAreaClick.d.ts +1 -7
- package/dist/esm/components/VizAreaClick/VizAreaClick.d.ts.map +1 -1
- package/dist/esm/components/VizAreaClick/index.d.ts +0 -1
- package/dist/esm/components/VizAreaClick/index.d.ts.map +1 -1
- package/dist/esm/components/VizClickmap/VizClickmap.d.ts +8 -0
- package/dist/esm/components/VizClickmap/VizClickmap.d.ts.map +1 -0
- package/dist/esm/components/VizClickmap/index.d.ts +2 -0
- package/dist/esm/components/VizClickmap/index.d.ts.map +1 -0
- package/dist/esm/components/VizDom/VizDomRenderer.d.ts.map +1 -1
- package/dist/esm/components/VizElement/ElementOverlay.d.ts +0 -1
- package/dist/esm/components/VizElement/ElementOverlay.d.ts.map +1 -1
- package/dist/esm/components/VizElement/HeatmapElements.d.ts.map +1 -1
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/constants/index.d.ts +1 -0
- package/dist/esm/constants/index.d.ts.map +1 -1
- package/dist/esm/constants/selectors.d.ts +4 -0
- package/dist/esm/constants/selectors.d.ts.map +1 -0
- package/dist/esm/constants/viz-area-click.d.ts +10 -0
- package/dist/esm/constants/viz-area-click.d.ts.map +1 -1
- package/dist/esm/helpers/dom-utils.d.ts +29 -0
- package/dist/esm/helpers/dom-utils.d.ts.map +1 -0
- package/dist/esm/helpers/iframe-helper/fixer.d.ts.map +1 -1
- package/dist/esm/helpers/iframe-helper/navigation-blocker-v2.d.ts +4 -1
- package/dist/esm/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -1
- package/dist/esm/helpers/iframe-helper/navigation-blocker.d.ts.map +1 -1
- package/dist/esm/helpers/iframe-helper/style-replacer.d.ts.map +1 -1
- package/dist/esm/helpers/index.d.ts +2 -0
- package/dist/esm/helpers/index.d.ts.map +1 -1
- package/dist/esm/helpers/logger.d.ts +77 -0
- package/dist/esm/helpers/logger.d.ts.map +1 -0
- 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-renderer-setup.d.ts +20 -0
- package/dist/esm/helpers/viz-area-click/area-renderer-setup.d.ts.map +1 -0
- package/dist/esm/helpers/viz-area-click/area-utils.d.ts +0 -41
- package/dist/esm/helpers/viz-area-click/area-utils.d.ts.map +1 -1
- package/dist/esm/helpers/viz-area-click/index.d.ts +1 -0
- package/dist/esm/helpers/viz-area-click/index.d.ts.map +1 -1
- package/dist/esm/helpers/viz-elm-callout/getter.d.ts +0 -2
- package/dist/esm/helpers/viz-elm-callout/getter.d.ts.map +1 -1
- package/dist/esm/hooks/register/useRegisterConfig.d.ts.map +1 -1
- package/dist/esm/hooks/view-context/useHeatmapAreaClick.d.ts +1 -0
- package/dist/esm/hooks/view-context/useHeatmapAreaClick.d.ts.map +1 -1
- package/dist/esm/hooks/viz-area-click/index.d.ts +7 -0
- package/dist/esm/hooks/viz-area-click/index.d.ts.map +1 -1
- package/dist/esm/hooks/viz-area-click/useAreaCreation.d.ts +10 -0
- package/dist/esm/hooks/viz-area-click/useAreaCreation.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaEditMode.d.ts +2 -2
- package/dist/esm/hooks/viz-area-click/useAreaEditMode.d.ts.map +1 -1
- package/dist/esm/hooks/viz-area-click/useAreaFilterVisible.d.ts +7 -0
- package/dist/esm/hooks/viz-area-click/useAreaFilterVisible.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaInteraction.d.ts +17 -0
- package/dist/esm/hooks/viz-area-click/useAreaInteraction.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaPortals.d.ts +15 -0
- package/dist/esm/hooks/viz-area-click/useAreaPortals.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaRectSync.d.ts +8 -0
- package/dist/esm/hooks/viz-area-click/useAreaRectSync.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaRendererContainer.d.ts +13 -0
- package/dist/esm/hooks/viz-area-click/useAreaRendererContainer.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaScrollSync.d.ts +1 -2
- package/dist/esm/hooks/viz-area-click/useAreaScrollSync.d.ts.map +1 -1
- package/dist/esm/hooks/viz-area-click/useAreaTopAutoDetect.d.ts +8 -0
- package/dist/esm/hooks/viz-area-click/useAreaTopAutoDetect.d.ts.map +1 -0
- package/dist/esm/hooks/viz-canvas/useAreaClickmap.d.ts +4 -0
- package/dist/esm/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -0
- package/dist/esm/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
- package/dist/esm/hooks/viz-elm/useClickedElement.d.ts +1 -0
- package/dist/esm/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
- package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -1
- package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
- package/dist/esm/hooks/viz-elm/useHeatmapMouseHandler.d.ts +0 -25
- package/dist/esm/hooks/viz-elm/useHeatmapMouseHandler.d.ts.map +1 -1
- package/dist/esm/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
- package/dist/esm/hooks/viz-render/index.d.ts +1 -1
- package/dist/esm/hooks/viz-render/index.d.ts.map +1 -1
- package/dist/esm/hooks/viz-render/useHeatmapRenderByMode.d.ts +6 -0
- package/dist/esm/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -0
- package/dist/esm/index.js +968 -679
- package/dist/esm/index.mjs +968 -679
- package/dist/esm/stores/config.d.ts +3 -1
- package/dist/esm/stores/config.d.ts.map +1 -1
- package/dist/esm/stores/mode-compare.d.ts.map +1 -1
- package/dist/esm/stores/viz-area-click.d.ts +1 -0
- package/dist/esm/stores/viz-area-click.d.ts.map +1 -1
- package/dist/esm/types/heatmap.d.ts +4 -0
- package/dist/esm/types/heatmap.d.ts.map +1 -1
- package/dist/esm/types/iframe-helper.d.ts +1 -0
- package/dist/esm/types/iframe-helper.d.ts.map +1 -1
- package/dist/umd/components/VizAreaClick/AreaEditHighlight.d.ts.map +1 -1
- package/dist/umd/components/VizAreaClick/AreaOverlay.d.ts.map +1 -1
- package/dist/umd/components/VizAreaClick/PortalAreaRenderer.d.ts +1 -1
- package/dist/umd/components/VizAreaClick/PortalAreaRenderer.d.ts.map +1 -1
- package/dist/umd/components/VizAreaClick/VizAreaClick.d.ts +1 -7
- package/dist/umd/components/VizAreaClick/VizAreaClick.d.ts.map +1 -1
- package/dist/umd/components/VizAreaClick/index.d.ts +0 -1
- package/dist/umd/components/VizAreaClick/index.d.ts.map +1 -1
- package/dist/umd/components/VizClickmap/VizClickmap.d.ts +8 -0
- package/dist/umd/components/VizClickmap/VizClickmap.d.ts.map +1 -0
- package/dist/umd/components/VizClickmap/index.d.ts +2 -0
- package/dist/umd/components/VizClickmap/index.d.ts.map +1 -0
- package/dist/umd/components/VizDom/VizDomRenderer.d.ts.map +1 -1
- package/dist/umd/components/VizElement/ElementOverlay.d.ts +0 -1
- package/dist/umd/components/VizElement/ElementOverlay.d.ts.map +1 -1
- package/dist/umd/components/VizElement/HeatmapElements.d.ts.map +1 -1
- package/dist/umd/components/index.d.ts +1 -0
- package/dist/umd/components/index.d.ts.map +1 -1
- package/dist/umd/constants/index.d.ts +1 -0
- package/dist/umd/constants/index.d.ts.map +1 -1
- package/dist/umd/constants/selectors.d.ts +4 -0
- package/dist/umd/constants/selectors.d.ts.map +1 -0
- package/dist/umd/constants/viz-area-click.d.ts +10 -0
- package/dist/umd/constants/viz-area-click.d.ts.map +1 -1
- package/dist/umd/helpers/dom-utils.d.ts +29 -0
- package/dist/umd/helpers/dom-utils.d.ts.map +1 -0
- package/dist/umd/helpers/iframe-helper/fixer.d.ts.map +1 -1
- package/dist/umd/helpers/iframe-helper/navigation-blocker-v2.d.ts +4 -1
- package/dist/umd/helpers/iframe-helper/navigation-blocker-v2.d.ts.map +1 -1
- package/dist/umd/helpers/iframe-helper/navigation-blocker.d.ts.map +1 -1
- package/dist/umd/helpers/iframe-helper/style-replacer.d.ts.map +1 -1
- package/dist/umd/helpers/index.d.ts +2 -0
- package/dist/umd/helpers/index.d.ts.map +1 -1
- package/dist/umd/helpers/logger.d.ts +77 -0
- package/dist/umd/helpers/logger.d.ts.map +1 -0
- 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-renderer-setup.d.ts +20 -0
- package/dist/umd/helpers/viz-area-click/area-renderer-setup.d.ts.map +1 -0
- package/dist/umd/helpers/viz-area-click/area-utils.d.ts +0 -41
- package/dist/umd/helpers/viz-area-click/area-utils.d.ts.map +1 -1
- package/dist/umd/helpers/viz-area-click/index.d.ts +1 -0
- package/dist/umd/helpers/viz-area-click/index.d.ts.map +1 -1
- package/dist/umd/helpers/viz-elm-callout/getter.d.ts +0 -2
- package/dist/umd/helpers/viz-elm-callout/getter.d.ts.map +1 -1
- package/dist/umd/hooks/register/useRegisterConfig.d.ts.map +1 -1
- package/dist/umd/hooks/view-context/useHeatmapAreaClick.d.ts +1 -0
- package/dist/umd/hooks/view-context/useHeatmapAreaClick.d.ts.map +1 -1
- package/dist/umd/hooks/viz-area-click/index.d.ts +7 -0
- package/dist/umd/hooks/viz-area-click/index.d.ts.map +1 -1
- package/dist/umd/hooks/viz-area-click/useAreaCreation.d.ts +10 -0
- package/dist/umd/hooks/viz-area-click/useAreaCreation.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaEditMode.d.ts +2 -2
- package/dist/umd/hooks/viz-area-click/useAreaEditMode.d.ts.map +1 -1
- package/dist/umd/hooks/viz-area-click/useAreaFilterVisible.d.ts +7 -0
- package/dist/umd/hooks/viz-area-click/useAreaFilterVisible.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaInteraction.d.ts +17 -0
- package/dist/umd/hooks/viz-area-click/useAreaInteraction.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaPortals.d.ts +15 -0
- package/dist/umd/hooks/viz-area-click/useAreaPortals.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaRectSync.d.ts +8 -0
- package/dist/umd/hooks/viz-area-click/useAreaRectSync.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaRendererContainer.d.ts +13 -0
- package/dist/umd/hooks/viz-area-click/useAreaRendererContainer.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaScrollSync.d.ts +1 -2
- package/dist/umd/hooks/viz-area-click/useAreaScrollSync.d.ts.map +1 -1
- package/dist/umd/hooks/viz-area-click/useAreaTopAutoDetect.d.ts +8 -0
- package/dist/umd/hooks/viz-area-click/useAreaTopAutoDetect.d.ts.map +1 -0
- package/dist/umd/hooks/viz-canvas/useAreaClickmap.d.ts +4 -0
- package/dist/umd/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -0
- package/dist/umd/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
- package/dist/umd/hooks/viz-elm/useClickedElement.d.ts +1 -0
- package/dist/umd/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
- package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -1
- package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
- package/dist/umd/hooks/viz-elm/useHeatmapMouseHandler.d.ts +0 -25
- package/dist/umd/hooks/viz-elm/useHeatmapMouseHandler.d.ts.map +1 -1
- package/dist/umd/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
- package/dist/umd/hooks/viz-render/index.d.ts +1 -1
- package/dist/umd/hooks/viz-render/index.d.ts.map +1 -1
- package/dist/umd/hooks/viz-render/useHeatmapRenderByMode.d.ts +6 -0
- package/dist/umd/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -0
- package/dist/umd/index.js +2 -2
- package/dist/umd/stores/config.d.ts +3 -1
- package/dist/umd/stores/config.d.ts.map +1 -1
- package/dist/umd/stores/mode-compare.d.ts.map +1 -1
- package/dist/umd/stores/viz-area-click.d.ts +1 -0
- package/dist/umd/stores/viz-area-click.d.ts.map +1 -1
- package/dist/umd/types/heatmap.d.ts +4 -0
- package/dist/umd/types/heatmap.d.ts.map +1 -1
- package/dist/umd/types/iframe-helper.d.ts +1 -0
- package/dist/umd/types/iframe-helper.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/esm/components/VizAreaClick/AreaControls.d.ts +0 -10
- package/dist/esm/components/VizAreaClick/AreaControls.d.ts.map +0 -1
- package/dist/esm/hooks/viz-area/useAreaHeatmap.d.ts +0 -59
- package/dist/esm/hooks/viz-area/useAreaHeatmap.d.ts.map +0 -1
- package/dist/esm/hooks/viz-area/useAreaHeatmapManager.d.ts +0 -77
- package/dist/esm/hooks/viz-area/useAreaHeatmapManager.d.ts.map +0 -1
- package/dist/esm/hooks/viz-render/useHeatmapVizRender.d.ts +0 -6
- package/dist/esm/hooks/viz-render/useHeatmapVizRender.d.ts.map +0 -1
- package/dist/umd/components/VizAreaClick/AreaControls.d.ts +0 -10
- package/dist/umd/components/VizAreaClick/AreaControls.d.ts.map +0 -1
- package/dist/umd/hooks/viz-area/useAreaHeatmap.d.ts +0 -59
- package/dist/umd/hooks/viz-area/useAreaHeatmap.d.ts.map +0 -1
- package/dist/umd/hooks/viz-area/useAreaHeatmapManager.d.ts +0 -77
- package/dist/umd/hooks/viz-area/useAreaHeatmapManager.d.ts.map +0 -1
- package/dist/umd/hooks/viz-render/useHeatmapVizRender.d.ts +0 -6
- package/dist/umd/hooks/viz-render/useHeatmapVizRender.d.ts.map +0 -1
package/dist/esm/index.mjs
CHANGED
|
@@ -5,8 +5,8 @@ import { useEffect, useRef, useCallback, createContext, useContext, useMemo, use
|
|
|
5
5
|
import { create } from 'zustand';
|
|
6
6
|
import { subscribeWithSelector } from 'zustand/middleware';
|
|
7
7
|
import { decode } from '@gemx-dev/clarity-decode';
|
|
8
|
-
import { Visualizer } from '@gemx-dev/clarity-visualize';
|
|
9
8
|
import { createPortal } from 'react-dom';
|
|
9
|
+
import { Visualizer } from '@gemx-dev/clarity-visualize';
|
|
10
10
|
|
|
11
11
|
const initialNodes = { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } };
|
|
12
12
|
const GraphView = ({ children, width, height }) => {
|
|
@@ -148,6 +148,11 @@ var IClickType;
|
|
|
148
148
|
IClickType["First"] = "first-clicks";
|
|
149
149
|
IClickType["Last"] = "last-clicks";
|
|
150
150
|
})(IClickType || (IClickType = {}));
|
|
151
|
+
var IClickMode;
|
|
152
|
+
(function (IClickMode) {
|
|
153
|
+
IClickMode["Default"] = "default";
|
|
154
|
+
IClickMode["Area"] = "click-area";
|
|
155
|
+
})(IClickMode || (IClickMode = {}));
|
|
151
156
|
var IScrollType;
|
|
152
157
|
(function (IScrollType) {
|
|
153
158
|
IScrollType["Depth"] = "scroll-depth";
|
|
@@ -162,6 +167,7 @@ const useHeatmapConfigStore = create()((set) => {
|
|
|
162
167
|
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
|
|
163
168
|
heatmapType: IHeatmapType.Click,
|
|
164
169
|
clickType: IClickType.Total,
|
|
170
|
+
clickMode: IClickMode.Default,
|
|
165
171
|
scrollType: IScrollType.Depth,
|
|
166
172
|
isRendering: true,
|
|
167
173
|
setMode: (mode) => set({ mode }),
|
|
@@ -170,6 +176,7 @@ const useHeatmapConfigStore = create()((set) => {
|
|
|
170
176
|
setSidebarWidth: (sidebarWidth) => set({ sidebarWidth }),
|
|
171
177
|
setHeatmapType: (heatmapType) => set({ heatmapType }),
|
|
172
178
|
setClickType: (clickType) => set({ clickType }),
|
|
179
|
+
setClickMode: (clickMode) => set({ clickMode }),
|
|
173
180
|
setScrollType: (scrollType) => set({ scrollType }),
|
|
174
181
|
setIsRendering: (isRendering) => set({ isRendering }),
|
|
175
182
|
};
|
|
@@ -335,7 +342,7 @@ const useHeatmapAreaClickStore = create()(subscribeWithSelector((set) => ({
|
|
|
335
342
|
}),
|
|
336
343
|
updateArea: (areaId, updates, viewId = DEFAULT_VIEW_ID) => set((state) => {
|
|
337
344
|
const currentAreas = state.areas[viewId] || [];
|
|
338
|
-
const updatedAreas = currentAreas.map((area) => area.id === areaId ? { ...area, ...updates } : area);
|
|
345
|
+
const updatedAreas = currentAreas.map((area) => (area.id === areaId ? { ...area, ...updates } : area));
|
|
339
346
|
return {
|
|
340
347
|
areas: { ...state.areas, [viewId]: updatedAreas },
|
|
341
348
|
};
|
|
@@ -379,6 +386,11 @@ const useHeatmapAreaClickStore = create()(subscribeWithSelector((set) => ({
|
|
|
379
386
|
isEditingMode: newIsEditingMode,
|
|
380
387
|
};
|
|
381
388
|
}),
|
|
389
|
+
resetView: (viewId) => set((state) => ({
|
|
390
|
+
selectedArea: { ...state.selectedArea, [viewId]: null },
|
|
391
|
+
hoveredArea: { ...state.hoveredArea, [viewId]: null },
|
|
392
|
+
isEditingMode: { ...state.isEditingMode, [viewId]: false },
|
|
393
|
+
})),
|
|
382
394
|
resetAll: () => set({
|
|
383
395
|
selectedArea: { [DEFAULT_VIEW_ID]: null },
|
|
384
396
|
hoveredArea: { [DEFAULT_VIEW_ID]: null },
|
|
@@ -533,9 +545,13 @@ const useHeatmapCompareStore = create()((set, get) => {
|
|
|
533
545
|
const state = get();
|
|
534
546
|
const newViews = new Map(state.views);
|
|
535
547
|
newViews.delete(viewId);
|
|
548
|
+
const newViewIdCounter = newViews.size;
|
|
549
|
+
const newLayout = newViews.size === 2 ? 'grid-2' : state.layout;
|
|
536
550
|
set({
|
|
537
551
|
views: newViews,
|
|
538
552
|
viewOrder: state.viewOrder.filter((id) => id !== viewId),
|
|
553
|
+
viewIdCounter: newViewIdCounter,
|
|
554
|
+
layout: newLayout,
|
|
539
555
|
});
|
|
540
556
|
},
|
|
541
557
|
updateView: (viewId, updates) => {
|
|
@@ -680,13 +696,14 @@ const useRegisterConfig = () => {
|
|
|
680
696
|
const width = useHeatmapConfigStore((state) => state.width);
|
|
681
697
|
const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
|
|
682
698
|
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
699
|
+
const clickMode = useHeatmapConfigStore((state) => state.clickMode);
|
|
683
700
|
const setIsRendering = useHeatmapConfigStore((state) => state.setIsRendering);
|
|
684
701
|
useEffect(() => {
|
|
685
702
|
setIsRendering(true);
|
|
686
703
|
setTimeout(() => {
|
|
687
704
|
setIsRendering(false);
|
|
688
705
|
}, 1000);
|
|
689
|
-
}, [mode, width, sidebarWidth, heatmapType]);
|
|
706
|
+
}, [mode, width, sidebarWidth, heatmapType, clickMode]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
690
707
|
};
|
|
691
708
|
|
|
692
709
|
const useRegisterControl = (control) => {
|
|
@@ -722,6 +739,7 @@ const useHeatmapAreaClick = (props) => {
|
|
|
722
739
|
const removeAreaStore = useHeatmapAreaClickStore((state) => state.removeArea);
|
|
723
740
|
const updateAreaStore = useHeatmapAreaClickStore((state) => state.updateArea);
|
|
724
741
|
const clearAreasStore = useHeatmapAreaClickStore((state) => state.clearAreas);
|
|
742
|
+
const resetViewStore = useHeatmapAreaClickStore((state) => state.resetView);
|
|
725
743
|
// Memoize operations to prevent unnecessary re-renders
|
|
726
744
|
const memoizedOperations = useMemo(() => ({
|
|
727
745
|
setSelectedArea: (area) => setSelectedAreaStore(area, viewId),
|
|
@@ -732,6 +750,7 @@ const useHeatmapAreaClick = (props) => {
|
|
|
732
750
|
removeArea: (areaId) => removeAreaStore(areaId, viewId),
|
|
733
751
|
updateArea: (areaId, updates) => updateAreaStore(areaId, updates, viewId),
|
|
734
752
|
clearAreas: () => clearAreasStore(viewId),
|
|
753
|
+
resetView: () => resetViewStore(viewId),
|
|
735
754
|
}), [
|
|
736
755
|
setSelectedAreaStore,
|
|
737
756
|
setHoveredAreaStore,
|
|
@@ -741,6 +760,7 @@ const useHeatmapAreaClick = (props) => {
|
|
|
741
760
|
removeAreaStore,
|
|
742
761
|
updateAreaStore,
|
|
743
762
|
clearAreasStore,
|
|
763
|
+
resetViewStore,
|
|
744
764
|
viewId,
|
|
745
765
|
]);
|
|
746
766
|
return {
|
|
@@ -1046,9 +1066,37 @@ function isElementInViewport(elementRect, visualRef, scale) {
|
|
|
1046
1066
|
return elementBottom > viewportTop && elementTop < viewportBottom;
|
|
1047
1067
|
}
|
|
1048
1068
|
|
|
1069
|
+
const CLARITY_HEATMAP_CANVAS_ID = 'clarity-heatmap-canvas';
|
|
1070
|
+
const HEATMAP_ELEMENT_ATTRIBUTE = 'data-clarity-hashalpha';
|
|
1071
|
+
function isIgnoredCanvas(element) {
|
|
1072
|
+
if (element.tagName === 'CANVAS') {
|
|
1073
|
+
return true;
|
|
1074
|
+
}
|
|
1075
|
+
if (element.id === CLARITY_HEATMAP_CANVAS_ID) {
|
|
1076
|
+
return true;
|
|
1077
|
+
}
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1049
1081
|
const AREA_HOVER_BOX_SHADOW = '0 0 0 1px #0078D4, 0 0 0 1px #0078D4 inset, 0 0 0 2px white inset';
|
|
1050
1082
|
const AREA_HOVER_ELEMENT_ID = 'clarity-edit-hover';
|
|
1051
1083
|
const AREA_MAP_DIV_ATTRIBUTE = 'data-clarity-area-map-div';
|
|
1084
|
+
const HEATMAP_AREA_CONTAINER_CLASS = 'heatmap-area-container';
|
|
1085
|
+
const HEATMAP_AREA_CONTAINER_SELECTOR = `.${HEATMAP_AREA_CONTAINER_CLASS}`;
|
|
1086
|
+
const AREA_CONTAINER_STYLES = `
|
|
1087
|
+
position: absolute;
|
|
1088
|
+
top: 0;
|
|
1089
|
+
left: 0;
|
|
1090
|
+
width: 100%;
|
|
1091
|
+
height: 100%;
|
|
1092
|
+
pointer-events: none;
|
|
1093
|
+
z-index: 999999;
|
|
1094
|
+
`;
|
|
1095
|
+
const AREA_INNER_CONTAINER_STYLES = `
|
|
1096
|
+
position: relative;
|
|
1097
|
+
width: 100%;
|
|
1098
|
+
height: 100%;
|
|
1099
|
+
`;
|
|
1052
1100
|
const AREA_COLOR_GRADIENT = [
|
|
1053
1101
|
[0, 0, 255], // Blue
|
|
1054
1102
|
[0, 255, 255], // Cyan
|
|
@@ -1056,6 +1104,12 @@ const AREA_COLOR_GRADIENT = [
|
|
|
1056
1104
|
[255, 255, 0], // Yellow
|
|
1057
1105
|
[255, 0, 0], // Red
|
|
1058
1106
|
];
|
|
1107
|
+
const AREA_RENDERER_SELECTORS = {
|
|
1108
|
+
containerAttribute: AREA_MAP_DIV_ATTRIBUTE,
|
|
1109
|
+
containerSelector: `[${AREA_MAP_DIV_ATTRIBUTE}]`,
|
|
1110
|
+
innerContainerClass: HEATMAP_AREA_CONTAINER_CLASS,
|
|
1111
|
+
innerContainerSelector: HEATMAP_AREA_CONTAINER_SELECTOR,
|
|
1112
|
+
};
|
|
1059
1113
|
|
|
1060
1114
|
const CALLOUT_PADDING = 0;
|
|
1061
1115
|
const CALLOUT_ARROW_SIZE = 8;
|
|
@@ -1071,6 +1125,7 @@ const SECONDARY_HOVERED_ELEMENT_ID_BASE = 'gx-hm-secondary-hovered-element';
|
|
|
1071
1125
|
function getColorFromClickDist(clickDist) {
|
|
1072
1126
|
// Ensure clickDist is in range [0, 100]
|
|
1073
1127
|
const normalizedDist = Math.max(0, Math.min(100, clickDist));
|
|
1128
|
+
console.log(`🚀 🐥 ~ getColorFromClickDist ~ normalizedDist:`, normalizedDist);
|
|
1074
1129
|
// Calculate gradient index
|
|
1075
1130
|
const maxIndex = AREA_COLOR_GRADIENT.length - 1;
|
|
1076
1131
|
const index = Math.floor((normalizedDist / 100) * maxIndex);
|
|
@@ -1100,27 +1155,15 @@ function calculateClickDistribution(elementClicks, totalClicks) {
|
|
|
1100
1155
|
return (elementClicks / totalClicks) * 100;
|
|
1101
1156
|
}
|
|
1102
1157
|
|
|
1103
|
-
/**
|
|
1104
|
-
* Get element position and dimensions for rendering inside iframe
|
|
1105
|
-
*
|
|
1106
|
-
* This function calculates position relative to the iframe's document,
|
|
1107
|
-
* accounting for scroll position. Suitable for elements rendered in
|
|
1108
|
-
* shadow DOM inside the iframe with absolute positioning.
|
|
1109
|
-
*/
|
|
1110
1158
|
function getElementRect(element, _shadowRoot) {
|
|
1111
1159
|
const rect = element.getBoundingClientRect();
|
|
1112
1160
|
const width = rect.width;
|
|
1113
1161
|
const height = rect.height;
|
|
1114
|
-
// Get the document to access scroll position
|
|
1115
1162
|
const doc = element.ownerDocument || document;
|
|
1116
|
-
// Get scroll offset from documentElement or body
|
|
1117
1163
|
const scrollTop = doc.documentElement?.scrollTop || doc.body?.scrollTop || 0;
|
|
1118
1164
|
const scrollLeft = doc.documentElement?.scrollLeft || doc.body?.scrollLeft || 0;
|
|
1119
|
-
// Calculate position relative to document (not viewport)
|
|
1120
|
-
// getBoundingClientRect() is relative to viewport, so add scroll offset
|
|
1121
1165
|
const top = rect.top + scrollTop;
|
|
1122
1166
|
const left = rect.left + scrollLeft;
|
|
1123
|
-
// For absolute positioning calculations (overlap detection)
|
|
1124
1167
|
const absoluteLeft = left;
|
|
1125
1168
|
const absoluteTop = top;
|
|
1126
1169
|
const absoluteRight = absoluteLeft + width;
|
|
@@ -1137,9 +1180,6 @@ function getElementRect(element, _shadowRoot) {
|
|
|
1137
1180
|
outOfBounds: false,
|
|
1138
1181
|
};
|
|
1139
1182
|
}
|
|
1140
|
-
/**
|
|
1141
|
-
* Check if element has CSS position: fixed
|
|
1142
|
-
*/
|
|
1143
1183
|
function isElementFixed(element) {
|
|
1144
1184
|
if (getComputedStyle(element).position === 'fixed') {
|
|
1145
1185
|
return true;
|
|
@@ -1150,9 +1190,6 @@ function isElementFixed(element) {
|
|
|
1150
1190
|
const parent = element.parentElement;
|
|
1151
1191
|
return parent ? isElementFixed(parent) : false;
|
|
1152
1192
|
}
|
|
1153
|
-
/**
|
|
1154
|
-
* Check if two areas overlap
|
|
1155
|
-
*/
|
|
1156
1193
|
function doAreasOverlap(area1, area2) {
|
|
1157
1194
|
const r1 = area1.rect.value;
|
|
1158
1195
|
const r2 = area2.rect.value;
|
|
@@ -1167,9 +1204,6 @@ function doAreasOverlap(area1, area2) {
|
|
|
1167
1204
|
r2.absoluteRight > r1.absoluteLeft &&
|
|
1168
1205
|
r2.absoluteLeft < r1.absoluteRight));
|
|
1169
1206
|
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Check if area1 is completely inside area2
|
|
1172
|
-
*/
|
|
1173
1207
|
function isAreaContainedIn(area1, area2) {
|
|
1174
1208
|
const r1 = area1.rect.value;
|
|
1175
1209
|
const r2 = area2.rect.value;
|
|
@@ -1180,64 +1214,29 @@ function isAreaContainedIn(area1, area2) {
|
|
|
1180
1214
|
r1.absoluteLeft >= r2.absoluteLeft &&
|
|
1181
1215
|
r1.absoluteRight <= r2.absoluteRight);
|
|
1182
1216
|
}
|
|
1183
|
-
/**
|
|
1184
|
-
* Check if element contains another element in DOM tree
|
|
1185
|
-
*/
|
|
1186
1217
|
function isElementAncestorOf(ancestor, descendant, doc) {
|
|
1187
1218
|
return ancestor.contains(descendant);
|
|
1188
1219
|
}
|
|
1189
|
-
/**
|
|
1190
|
-
* Get all elements at a specific point, including shadow DOM
|
|
1191
|
-
*/
|
|
1192
|
-
function getElementsAtPoint$1(doc, x, y, filter, visited = new Set()) {
|
|
1193
|
-
const elements = doc.elementsFromPoint(x, y);
|
|
1194
|
-
const filtered = elements.find(filter);
|
|
1195
|
-
// Check shadow DOM
|
|
1196
|
-
if (filtered?.shadowRoot && !visited.has(filtered.shadowRoot)) {
|
|
1197
|
-
visited.add(filtered.shadowRoot);
|
|
1198
|
-
return getElementsAtPoint$1(filtered.shadowRoot, x, y, filter, visited);
|
|
1199
|
-
}
|
|
1200
|
-
return elements;
|
|
1201
|
-
}
|
|
1202
|
-
/**
|
|
1203
|
-
* Check if element should be selectable for area creation
|
|
1204
|
-
*/
|
|
1205
1220
|
function isElementSelectable(element, index, elements) {
|
|
1206
|
-
|
|
1221
|
+
if (isIgnoredCanvas(element)) {
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1207
1224
|
if (element.hasAttribute(AREA_MAP_DIV_ATTRIBUTE)) {
|
|
1208
1225
|
return false;
|
|
1209
1226
|
}
|
|
1210
|
-
// Skip first element if it's BODY and there are other elements
|
|
1211
1227
|
if (index === 0 && elements.length > 1 && element.nodeName === 'BODY') {
|
|
1212
1228
|
return false;
|
|
1213
1229
|
}
|
|
1214
1230
|
return true;
|
|
1215
1231
|
}
|
|
1216
|
-
/**
|
|
1217
|
-
* Sort areas by click distribution (descending)
|
|
1218
|
-
*/
|
|
1219
1232
|
function sortAreasByClickDist(areas) {
|
|
1220
1233
|
return [...areas].sort((a, b) => {
|
|
1221
|
-
// Higher clickDist first
|
|
1222
1234
|
if (a.clickDist !== b.clickDist) {
|
|
1223
1235
|
return b.clickDist - a.clickDist;
|
|
1224
1236
|
}
|
|
1225
|
-
// Then by total clicks
|
|
1226
1237
|
return b.totalclicks - a.totalclicks;
|
|
1227
1238
|
});
|
|
1228
1239
|
}
|
|
1229
|
-
/**
|
|
1230
|
-
* Create shadow root or return existing one
|
|
1231
|
-
*/
|
|
1232
|
-
function getOrCreateShadowRoot(element) {
|
|
1233
|
-
if (element.shadowRoot) {
|
|
1234
|
-
return element.shadowRoot;
|
|
1235
|
-
}
|
|
1236
|
-
return element.attachShadow({ mode: 'open' });
|
|
1237
|
-
}
|
|
1238
|
-
/**
|
|
1239
|
-
* Check if rect is too small to show label
|
|
1240
|
-
*/
|
|
1241
1240
|
function isRectTooSmallForLabel(rect) {
|
|
1242
1241
|
return rect.width < 67 || rect.height < 30;
|
|
1243
1242
|
}
|
|
@@ -1254,8 +1253,11 @@ function getElementSelector(element) {
|
|
|
1254
1253
|
}
|
|
1255
1254
|
return element.tagName.toLowerCase();
|
|
1256
1255
|
}
|
|
1257
|
-
function buildAreaNode(element, hash,
|
|
1258
|
-
|
|
1256
|
+
function buildAreaNode(element, hash, heatmapInfo, shadowRoot) {
|
|
1257
|
+
if (!heatmapInfo.elementMapInfo)
|
|
1258
|
+
return;
|
|
1259
|
+
const totalClicks = heatmapInfo.totalClicks || 0;
|
|
1260
|
+
const elementInfo = heatmapInfo.elementMapInfo[hash];
|
|
1259
1261
|
const elementClicks = elementInfo?.totalclicks || 0;
|
|
1260
1262
|
const clickDist = calculateClickDistribution(elementClicks, totalClicks);
|
|
1261
1263
|
const rect = getElementRect(element);
|
|
@@ -1446,6 +1448,67 @@ function getVisibleAreas(areas, iframeDocument) {
|
|
|
1446
1448
|
return sortAreasByClickDist(visible);
|
|
1447
1449
|
}
|
|
1448
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);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
return container;
|
|
1485
|
+
}
|
|
1486
|
+
function getOrCreateContainerShadowRoot(container) {
|
|
1487
|
+
if (container.shadowRoot) {
|
|
1488
|
+
return container.shadowRoot;
|
|
1489
|
+
}
|
|
1490
|
+
return container.attachShadow({ mode: 'open' });
|
|
1491
|
+
}
|
|
1492
|
+
function getOrCreateInnerContainer(shadowRoot, iframeDocument) {
|
|
1493
|
+
let innerContainer = shadowRoot.querySelector(AREA_RENDERER_SELECTORS.innerContainerSelector);
|
|
1494
|
+
if (!innerContainer) {
|
|
1495
|
+
innerContainer = createInnerContainer(iframeDocument);
|
|
1496
|
+
shadowRoot.appendChild(innerContainer);
|
|
1497
|
+
}
|
|
1498
|
+
return innerContainer;
|
|
1499
|
+
}
|
|
1500
|
+
function setupAreaRenderingContainer(iframeDocument, customShadowRoot) {
|
|
1501
|
+
const container = getOrCreateAreaContainer(iframeDocument, customShadowRoot);
|
|
1502
|
+
const shadowRoot = getOrCreateContainerShadowRoot(container);
|
|
1503
|
+
const innerContainer = getOrCreateInnerContainer(shadowRoot, iframeDocument);
|
|
1504
|
+
return innerContainer;
|
|
1505
|
+
}
|
|
1506
|
+
function cleanupAreaRenderingContainer(container) {
|
|
1507
|
+
if (container && container.parentNode) {
|
|
1508
|
+
container.parentNode.removeChild(container);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1449
1512
|
function findLastSizeOfDom(data) {
|
|
1450
1513
|
const listDocs = data
|
|
1451
1514
|
.filter((item) => item.doc?.find((doc) => doc.data.width && doc.data.height))
|
|
@@ -1510,23 +1573,6 @@ function getElementLayout(element) {
|
|
|
1510
1573
|
height: rect.height,
|
|
1511
1574
|
};
|
|
1512
1575
|
}
|
|
1513
|
-
const getElementAtPoint = (doc, x, y) => {
|
|
1514
|
-
let el = null;
|
|
1515
|
-
if ('caretPositionFromPoint' in doc) {
|
|
1516
|
-
el = doc.caretPositionFromPoint(x, y)?.offsetNode ?? null;
|
|
1517
|
-
}
|
|
1518
|
-
el = el ?? doc.elementFromPoint(x, y);
|
|
1519
|
-
let element = el;
|
|
1520
|
-
while (element && element.nodeType === Node.TEXT_NODE) {
|
|
1521
|
-
element = element.parentElement;
|
|
1522
|
-
}
|
|
1523
|
-
return element;
|
|
1524
|
-
};
|
|
1525
|
-
function getElementHash(element) {
|
|
1526
|
-
return (element.getAttribute('data-clarity-hash') ||
|
|
1527
|
-
element.getAttribute('data-clarity-hashalpha') ||
|
|
1528
|
-
element.getAttribute('data-clarity-hashbeta'));
|
|
1529
|
-
}
|
|
1530
1576
|
const getElementRank = (hash, elements) => {
|
|
1531
1577
|
if (!elements)
|
|
1532
1578
|
return 0;
|
|
@@ -1729,6 +1775,191 @@ const calcCalloutPosition = (options) => {
|
|
|
1729
1775
|
};
|
|
1730
1776
|
};
|
|
1731
1777
|
|
|
1778
|
+
/**
|
|
1779
|
+
* Get all elements at a specific point (x, y), with support for Shadow DOM
|
|
1780
|
+
*/
|
|
1781
|
+
function getElementsAtPoint(doc, x, y, options = {}) {
|
|
1782
|
+
const { filterFn, ignoreCanvas = true, visitedShadowRoots = new Set() } = options;
|
|
1783
|
+
// Get all elements at this point
|
|
1784
|
+
let elementsAtPoint = doc.elementsFromPoint(x, y);
|
|
1785
|
+
// Filter out canvas elements if requested
|
|
1786
|
+
if (ignoreCanvas) {
|
|
1787
|
+
elementsAtPoint = elementsAtPoint.filter((el) => !isIgnoredCanvas(el));
|
|
1788
|
+
}
|
|
1789
|
+
// Apply custom filter if provided
|
|
1790
|
+
if (filterFn) {
|
|
1791
|
+
const matchedElement = elementsAtPoint.find(filterFn);
|
|
1792
|
+
// If matched element has Shadow DOM and we haven't visited it yet, recurse
|
|
1793
|
+
if (matchedElement?.shadowRoot && !visitedShadowRoots.has(matchedElement.shadowRoot)) {
|
|
1794
|
+
visitedShadowRoots.add(matchedElement.shadowRoot);
|
|
1795
|
+
return getElementsAtPoint(matchedElement.shadowRoot, x, y, {
|
|
1796
|
+
...options,
|
|
1797
|
+
visitedShadowRoots,
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
return elementsAtPoint;
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Get the element at a specific point (x, y)
|
|
1805
|
+
*/
|
|
1806
|
+
const getElementAtPoint = (doc, x, y) => {
|
|
1807
|
+
let el = null;
|
|
1808
|
+
if ('caretPositionFromPoint' in doc) {
|
|
1809
|
+
el = doc.caretPositionFromPoint(x, y)?.offsetNode ?? null;
|
|
1810
|
+
}
|
|
1811
|
+
el = el ?? doc.elementFromPoint(x, y);
|
|
1812
|
+
let element = el;
|
|
1813
|
+
while (element && element.nodeType === Node.TEXT_NODE) {
|
|
1814
|
+
element = element.parentElement;
|
|
1815
|
+
}
|
|
1816
|
+
return element;
|
|
1817
|
+
};
|
|
1818
|
+
function getElementHash(element) {
|
|
1819
|
+
return (element.getAttribute('data-clarity-hash') ||
|
|
1820
|
+
element.getAttribute('data-clarity-hashalpha') ||
|
|
1821
|
+
element.getAttribute('data-clarity-hashbeta'));
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
class Logger {
|
|
1825
|
+
config = {
|
|
1826
|
+
enabled: false,
|
|
1827
|
+
prefix: '',
|
|
1828
|
+
timestamp: false,
|
|
1829
|
+
};
|
|
1830
|
+
/**
|
|
1831
|
+
* Cấu hình logger
|
|
1832
|
+
* @param config - Cấu hình logger
|
|
1833
|
+
*/
|
|
1834
|
+
configure(config) {
|
|
1835
|
+
this.config = { ...this.config, ...config };
|
|
1836
|
+
}
|
|
1837
|
+
/**
|
|
1838
|
+
* Lấy cấu hình hiện tại
|
|
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);
|
|
1918
|
+
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Start a group
|
|
1921
|
+
*/
|
|
1922
|
+
group(label) {
|
|
1923
|
+
if (!this.config.enabled)
|
|
1924
|
+
return;
|
|
1925
|
+
console.group(...this.formatMessage(label));
|
|
1926
|
+
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Start a collapsed group
|
|
1929
|
+
*/
|
|
1930
|
+
groupCollapsed(label) {
|
|
1931
|
+
if (!this.config.enabled)
|
|
1932
|
+
return;
|
|
1933
|
+
console.groupCollapsed(...this.formatMessage(label));
|
|
1934
|
+
}
|
|
1935
|
+
/**
|
|
1936
|
+
* End a group
|
|
1937
|
+
*/
|
|
1938
|
+
groupEnd() {
|
|
1939
|
+
if (!this.config.enabled)
|
|
1940
|
+
return;
|
|
1941
|
+
console.groupEnd();
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Start a timer
|
|
1945
|
+
*/
|
|
1946
|
+
time(label) {
|
|
1947
|
+
if (!this.config.enabled)
|
|
1948
|
+
return;
|
|
1949
|
+
console.time(label);
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1952
|
+
* End a timer
|
|
1953
|
+
*/
|
|
1954
|
+
timeEnd(label) {
|
|
1955
|
+
if (!this.config.enabled)
|
|
1956
|
+
return;
|
|
1957
|
+
console.timeEnd(label);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
// Export singleton instance
|
|
1961
|
+
const logger = new Logger();
|
|
1962
|
+
|
|
1732
1963
|
class IframeNavigationBlockerV2 {
|
|
1733
1964
|
doc;
|
|
1734
1965
|
win;
|
|
@@ -1736,17 +1967,18 @@ class IframeNavigationBlockerV2 {
|
|
|
1736
1967
|
showMessage = false;
|
|
1737
1968
|
originalWindowOpen;
|
|
1738
1969
|
observers = [];
|
|
1739
|
-
constructor(iframe) {
|
|
1970
|
+
constructor(iframe, config) {
|
|
1740
1971
|
if (!iframe.contentDocument || !iframe.contentWindow) {
|
|
1741
1972
|
throw new Error('Iframe document or window not accessible');
|
|
1742
1973
|
}
|
|
1743
1974
|
this.doc = iframe.contentDocument;
|
|
1744
1975
|
this.win = iframe.contentWindow;
|
|
1745
1976
|
this.originalWindowOpen = this.win.open.bind(this.win);
|
|
1977
|
+
logger.configure({ enabled: !!config?.debug, prefix: '[IframeNavigationBlockerV2]' });
|
|
1746
1978
|
this.init();
|
|
1747
1979
|
}
|
|
1748
1980
|
init() {
|
|
1749
|
-
|
|
1981
|
+
logger.log('Initializing...');
|
|
1750
1982
|
try {
|
|
1751
1983
|
// Chặn navigation qua links
|
|
1752
1984
|
this.blockLinkNavigation();
|
|
@@ -1762,7 +1994,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1762
1994
|
this.injectCSP();
|
|
1763
1995
|
}
|
|
1764
1996
|
catch (error) {
|
|
1765
|
-
|
|
1997
|
+
logger.error('Init error:', error);
|
|
1766
1998
|
}
|
|
1767
1999
|
}
|
|
1768
2000
|
blockLinkNavigation() {
|
|
@@ -1776,11 +2008,11 @@ class IframeNavigationBlockerV2 {
|
|
|
1776
2008
|
const href = link.getAttribute('href');
|
|
1777
2009
|
// Cho phép hash links và empty links
|
|
1778
2010
|
if (!href || href === '' || href === '#' || href.startsWith('#')) {
|
|
1779
|
-
|
|
2011
|
+
logger.log('Allowed hash navigation:', href);
|
|
1780
2012
|
return;
|
|
1781
2013
|
}
|
|
1782
2014
|
// Chặn tất cả các loại navigation
|
|
1783
|
-
|
|
2015
|
+
logger.log('Blocked link navigation to:', href);
|
|
1784
2016
|
e.preventDefault();
|
|
1785
2017
|
e.stopPropagation();
|
|
1786
2018
|
e.stopImmediatePropagation();
|
|
@@ -1796,7 +2028,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1796
2028
|
if (link) {
|
|
1797
2029
|
const href = link.getAttribute('href');
|
|
1798
2030
|
if (href && !href.startsWith('#')) {
|
|
1799
|
-
|
|
2031
|
+
logger.log('Blocked auxclick navigation');
|
|
1800
2032
|
e.preventDefault();
|
|
1801
2033
|
e.stopPropagation();
|
|
1802
2034
|
e.stopImmediatePropagation();
|
|
@@ -1829,13 +2061,13 @@ class IframeNavigationBlockerV2 {
|
|
|
1829
2061
|
const action = form.getAttribute('action');
|
|
1830
2062
|
// Cho phép forms không có action
|
|
1831
2063
|
if (!action || action === '' || action === '#') {
|
|
1832
|
-
|
|
2064
|
+
logger.log('Allowed same-page form');
|
|
1833
2065
|
e.preventDefault();
|
|
1834
2066
|
this.handleFormSubmit(form);
|
|
1835
2067
|
return;
|
|
1836
2068
|
}
|
|
1837
2069
|
// Chặn tất cả external submissions
|
|
1838
|
-
|
|
2070
|
+
logger.log('Blocked form submission to:', action);
|
|
1839
2071
|
e.preventDefault();
|
|
1840
2072
|
e.stopPropagation();
|
|
1841
2073
|
e.stopImmediatePropagation();
|
|
@@ -1849,7 +2081,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1849
2081
|
return this.originalWindowOpen(...args);
|
|
1850
2082
|
}
|
|
1851
2083
|
const url = args[0]?.toString() || 'popup';
|
|
1852
|
-
|
|
2084
|
+
logger.log('Blocked window.open:', url);
|
|
1853
2085
|
this.notifyBlockedNavigation(url);
|
|
1854
2086
|
return null;
|
|
1855
2087
|
});
|
|
@@ -1859,7 +2091,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1859
2091
|
this.win.addEventListener('beforeunload', (e) => {
|
|
1860
2092
|
if (!this.isEnabled)
|
|
1861
2093
|
return;
|
|
1862
|
-
|
|
2094
|
+
logger.log('Blocked beforeunload');
|
|
1863
2095
|
e.preventDefault();
|
|
1864
2096
|
e.returnValue = '';
|
|
1865
2097
|
return '';
|
|
@@ -1868,7 +2100,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1868
2100
|
this.win.addEventListener('unload', (e) => {
|
|
1869
2101
|
if (!this.isEnabled)
|
|
1870
2102
|
return;
|
|
1871
|
-
|
|
2103
|
+
logger.log('Blocked unload');
|
|
1872
2104
|
e.preventDefault();
|
|
1873
2105
|
e.stopPropagation();
|
|
1874
2106
|
}, true);
|
|
@@ -1876,7 +2108,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1876
2108
|
this.win.addEventListener('popstate', (e) => {
|
|
1877
2109
|
if (!this.isEnabled)
|
|
1878
2110
|
return;
|
|
1879
|
-
|
|
2111
|
+
logger.log('Blocked popstate');
|
|
1880
2112
|
e.preventDefault();
|
|
1881
2113
|
e.stopPropagation();
|
|
1882
2114
|
}, true);
|
|
@@ -1929,11 +2161,11 @@ class IframeNavigationBlockerV2 {
|
|
|
1929
2161
|
meta.httpEquiv = 'Content-Security-Policy';
|
|
1930
2162
|
meta.content = "navigate-to 'none'"; // Chặn tất cả navigation
|
|
1931
2163
|
this.doc.head.appendChild(meta);
|
|
1932
|
-
|
|
2164
|
+
logger.log('Injected CSP');
|
|
1933
2165
|
}
|
|
1934
2166
|
}
|
|
1935
2167
|
catch (error) {
|
|
1936
|
-
|
|
2168
|
+
logger.warn('Could not inject CSP:', error);
|
|
1937
2169
|
}
|
|
1938
2170
|
}
|
|
1939
2171
|
handleFormSubmit(form) {
|
|
@@ -1942,13 +2174,13 @@ class IframeNavigationBlockerV2 {
|
|
|
1942
2174
|
formData.forEach((value, key) => {
|
|
1943
2175
|
data[key] = value;
|
|
1944
2176
|
});
|
|
1945
|
-
|
|
2177
|
+
logger.log('Handling form data:', data);
|
|
1946
2178
|
window.dispatchEvent(new CustomEvent('iframe-form-submit', {
|
|
1947
2179
|
detail: { form, data },
|
|
1948
2180
|
}));
|
|
1949
2181
|
}
|
|
1950
2182
|
notifyBlockedNavigation(url) {
|
|
1951
|
-
|
|
2183
|
+
logger.warn('Navigation blocked to:', url);
|
|
1952
2184
|
window.dispatchEvent(new CustomEvent('iframe-navigation-blocked', {
|
|
1953
2185
|
detail: { url, timestamp: Date.now() },
|
|
1954
2186
|
}));
|
|
@@ -1998,19 +2230,19 @@ class IframeNavigationBlockerV2 {
|
|
|
1998
2230
|
}
|
|
1999
2231
|
enable() {
|
|
2000
2232
|
this.isEnabled = true;
|
|
2001
|
-
|
|
2233
|
+
logger.log('Enabled');
|
|
2002
2234
|
}
|
|
2003
2235
|
enableMessage() {
|
|
2004
2236
|
this.showMessage = true;
|
|
2005
|
-
|
|
2237
|
+
logger.log('Enabled message');
|
|
2006
2238
|
}
|
|
2007
2239
|
disable() {
|
|
2008
2240
|
this.isEnabled = false;
|
|
2009
|
-
|
|
2241
|
+
logger.log('Disabled');
|
|
2010
2242
|
}
|
|
2011
2243
|
disableMessage() {
|
|
2012
2244
|
this.showMessage = false;
|
|
2013
|
-
|
|
2245
|
+
logger.log('Disabled message');
|
|
2014
2246
|
}
|
|
2015
2247
|
destroy() {
|
|
2016
2248
|
this.isEnabled = false;
|
|
@@ -2018,7 +2250,7 @@ class IframeNavigationBlockerV2 {
|
|
|
2018
2250
|
// Cleanup observers
|
|
2019
2251
|
this.observers.forEach((observer) => observer.disconnect());
|
|
2020
2252
|
this.observers = [];
|
|
2021
|
-
|
|
2253
|
+
logger.log('Destroyed');
|
|
2022
2254
|
}
|
|
2023
2255
|
}
|
|
2024
2256
|
|
|
@@ -2034,6 +2266,7 @@ class IframeStyleReplacer {
|
|
|
2034
2266
|
this.doc = iframe.contentDocument;
|
|
2035
2267
|
this.win = iframe.contentWindow;
|
|
2036
2268
|
this.config = config;
|
|
2269
|
+
logger.configure({ enabled: !!config?.debug, prefix: '[IframeStyleReplacer]' });
|
|
2037
2270
|
}
|
|
2038
2271
|
px(value) {
|
|
2039
2272
|
return `${value.toFixed(2)}px`;
|
|
@@ -2067,7 +2300,7 @@ class IframeStyleReplacer {
|
|
|
2067
2300
|
count++;
|
|
2068
2301
|
}
|
|
2069
2302
|
});
|
|
2070
|
-
|
|
2303
|
+
logger.log(`Replaced ${count} inline style elements`);
|
|
2071
2304
|
return count;
|
|
2072
2305
|
}
|
|
2073
2306
|
processStyleTags() {
|
|
@@ -2080,7 +2313,7 @@ class IframeStyleReplacer {
|
|
|
2080
2313
|
count++;
|
|
2081
2314
|
}
|
|
2082
2315
|
});
|
|
2083
|
-
|
|
2316
|
+
logger.log(`Replaced ${count} <style> tags`);
|
|
2084
2317
|
return count;
|
|
2085
2318
|
}
|
|
2086
2319
|
processRule(rule) {
|
|
@@ -2111,7 +2344,7 @@ class IframeStyleReplacer {
|
|
|
2111
2344
|
try {
|
|
2112
2345
|
// Bỏ qua external CSS (cross-origin)
|
|
2113
2346
|
if (sheet.href && !sheet.href.startsWith(this.win.location.origin)) {
|
|
2114
|
-
|
|
2347
|
+
logger.log('Skipping external CSS:', sheet.href);
|
|
2115
2348
|
return;
|
|
2116
2349
|
}
|
|
2117
2350
|
const rules = sheet.cssRules || sheet.rules;
|
|
@@ -2122,10 +2355,10 @@ class IframeStyleReplacer {
|
|
|
2122
2355
|
}
|
|
2123
2356
|
}
|
|
2124
2357
|
catch (e) {
|
|
2125
|
-
|
|
2358
|
+
logger.warn('Cannot read stylesheet (CORS?):', e.message);
|
|
2126
2359
|
}
|
|
2127
2360
|
});
|
|
2128
|
-
|
|
2361
|
+
logger.log(`Replaced ${total} rules in stylesheets`);
|
|
2129
2362
|
return total;
|
|
2130
2363
|
}
|
|
2131
2364
|
async processLinkedStylesheets() {
|
|
@@ -2133,7 +2366,7 @@ class IframeStyleReplacer {
|
|
|
2133
2366
|
let count = 0;
|
|
2134
2367
|
for (const link of Array.from(links)) {
|
|
2135
2368
|
if (!link.href.startsWith(this.win.location.origin)) {
|
|
2136
|
-
|
|
2369
|
+
logger.log('Skipping external CSS:', link.href);
|
|
2137
2370
|
continue;
|
|
2138
2371
|
}
|
|
2139
2372
|
try {
|
|
@@ -2151,10 +2384,10 @@ class IframeStyleReplacer {
|
|
|
2151
2384
|
}
|
|
2152
2385
|
}
|
|
2153
2386
|
catch (e) {
|
|
2154
|
-
|
|
2387
|
+
logger.warn('Cannot load CSS:', link.href, e);
|
|
2155
2388
|
}
|
|
2156
2389
|
}
|
|
2157
|
-
|
|
2390
|
+
logger.log(`Replaced ${count} linked CSS files`);
|
|
2158
2391
|
return count;
|
|
2159
2392
|
}
|
|
2160
2393
|
getFinalHeight() {
|
|
@@ -2178,7 +2411,7 @@ class IframeStyleReplacer {
|
|
|
2178
2411
|
}
|
|
2179
2412
|
async run() {
|
|
2180
2413
|
try {
|
|
2181
|
-
|
|
2414
|
+
logger.log('Starting viewport units replacement...');
|
|
2182
2415
|
this.processInlineStyles();
|
|
2183
2416
|
this.processStyleTags();
|
|
2184
2417
|
this.processStylesheets();
|
|
@@ -2188,13 +2421,13 @@ class IframeStyleReplacer {
|
|
|
2188
2421
|
requestAnimationFrame(() => {
|
|
2189
2422
|
const height = this.getFinalHeight();
|
|
2190
2423
|
const width = this.getFinalWidth();
|
|
2191
|
-
|
|
2424
|
+
logger.log('Calculated dimensions:', { height, width });
|
|
2192
2425
|
resolve({ height, width });
|
|
2193
2426
|
});
|
|
2194
2427
|
});
|
|
2195
2428
|
}
|
|
2196
2429
|
catch (err) {
|
|
2197
|
-
|
|
2430
|
+
logger.error('Critical error:', err);
|
|
2198
2431
|
return {
|
|
2199
2432
|
height: this.doc.body.scrollHeight || 1000,
|
|
2200
2433
|
width: this.doc.body.scrollWidth || 1000,
|
|
@@ -2215,10 +2448,11 @@ class IframeHelperFixer {
|
|
|
2215
2448
|
this.config = config;
|
|
2216
2449
|
this.iframe = config.iframe;
|
|
2217
2450
|
this.init();
|
|
2451
|
+
logger.configure({ enabled: !!config?.debug, prefix: '[IframeHelper]' });
|
|
2218
2452
|
}
|
|
2219
2453
|
async init() {
|
|
2220
2454
|
if (!this.iframe) {
|
|
2221
|
-
|
|
2455
|
+
logger.error('iframe not found');
|
|
2222
2456
|
this.config.onError?.(new Error('iframe not found'));
|
|
2223
2457
|
return;
|
|
2224
2458
|
}
|
|
@@ -2232,19 +2466,19 @@ class IframeHelperFixer {
|
|
|
2232
2466
|
}
|
|
2233
2467
|
async process() {
|
|
2234
2468
|
if (!this.iframe.contentDocument || !this.iframe.contentWindow) {
|
|
2235
|
-
|
|
2469
|
+
logger.error('Cannot access iframe document');
|
|
2236
2470
|
this.config.onError?.(new Error('Cannot access iframe document'));
|
|
2237
2471
|
return;
|
|
2238
2472
|
}
|
|
2239
2473
|
try {
|
|
2240
|
-
|
|
2474
|
+
logger.log('Processing viewport units...');
|
|
2241
2475
|
// Create replacer instance
|
|
2242
2476
|
this.replacer = new IframeStyleReplacer(this.iframe, this.config);
|
|
2243
2477
|
// Create navigation blocker
|
|
2244
2478
|
this.navigationBlocker = new IframeNavigationBlockerV2(this.iframe);
|
|
2245
2479
|
// Run replacement
|
|
2246
2480
|
const result = await this.replacer.run();
|
|
2247
|
-
|
|
2481
|
+
logger.log('Process completed:', result);
|
|
2248
2482
|
// Trigger success callback
|
|
2249
2483
|
this.config.onSuccess?.(result);
|
|
2250
2484
|
// Dispatch custom event
|
|
@@ -2253,12 +2487,12 @@ class IframeHelperFixer {
|
|
|
2253
2487
|
}));
|
|
2254
2488
|
}
|
|
2255
2489
|
catch (error) {
|
|
2256
|
-
|
|
2490
|
+
logger.error('Failed to process:', error);
|
|
2257
2491
|
this.config.onError?.(error);
|
|
2258
2492
|
}
|
|
2259
2493
|
}
|
|
2260
2494
|
async recalculate() {
|
|
2261
|
-
|
|
2495
|
+
logger.log('Recalculating...');
|
|
2262
2496
|
await this.process();
|
|
2263
2497
|
}
|
|
2264
2498
|
updateConfig(config) {
|
|
@@ -2283,28 +2517,62 @@ class IframeHelperFixer {
|
|
|
2283
2517
|
this.replacer = null;
|
|
2284
2518
|
this.navigationBlocker?.destroy();
|
|
2285
2519
|
this.navigationBlocker = null;
|
|
2286
|
-
|
|
2520
|
+
logger.log('Destroyed');
|
|
2287
2521
|
}
|
|
2288
2522
|
}
|
|
2289
2523
|
|
|
2290
2524
|
function initIframeHelperFixer(config) {
|
|
2291
2525
|
const fixer = new IframeHelperFixer(config);
|
|
2292
2526
|
window.addEventListener('iframe-dimensions-applied', ((e) => {
|
|
2293
|
-
|
|
2294
|
-
console.log('[IframeHelper] Iframe dimensions finalized:', ev.detail);
|
|
2527
|
+
// console.log('[IframeHelper] Iframe dimensions finalized:', ev.detail);
|
|
2295
2528
|
}));
|
|
2296
2529
|
window.addEventListener('iframe-navigation-blocked', ((e) => {
|
|
2297
|
-
|
|
2298
|
-
console.warn('[IframeHelper] Iframe tried to navigate to:', ev.detail.url);
|
|
2530
|
+
// console.warn('[IframeHelper] Iframe tried to navigate to:', ev.detail.url);
|
|
2299
2531
|
}));
|
|
2300
2532
|
window.addEventListener('iframe-form-submit', ((e) => {
|
|
2301
|
-
|
|
2302
|
-
console.log('[IframeHelper] Iframe form submitted:', ev.detail.data);
|
|
2533
|
+
// console.log('[IframeHelper] Iframe form submitted:', ev.detail.data);
|
|
2303
2534
|
}));
|
|
2304
2535
|
return fixer;
|
|
2305
2536
|
}
|
|
2306
2537
|
|
|
2307
|
-
function
|
|
2538
|
+
function useAreaCreation(options = {}) {
|
|
2539
|
+
const { customShadowRoot, onAreaCreated } = options;
|
|
2540
|
+
const { dataInfo } = useHeatmapData();
|
|
2541
|
+
const { areas, addArea } = useHeatmapAreaClick();
|
|
2542
|
+
const handleCreateAreaFromElement = useCallback((element) => {
|
|
2543
|
+
if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks) {
|
|
2544
|
+
logger.warn('Cannot create area: missing heatmap data');
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
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}`);
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
try {
|
|
2558
|
+
const area = buildAreaNode(element, hash, dataInfo, customShadowRoot);
|
|
2559
|
+
if (!area)
|
|
2560
|
+
return;
|
|
2561
|
+
addArea(area);
|
|
2562
|
+
if (onAreaCreated) {
|
|
2563
|
+
onAreaCreated(area);
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
catch (error) {
|
|
2567
|
+
logger.error('Failed to create area:', error);
|
|
2568
|
+
}
|
|
2569
|
+
}, [dataInfo, areas, addArea, customShadowRoot]);
|
|
2570
|
+
return {
|
|
2571
|
+
onAreaCreatedElement: handleCreateAreaFromElement,
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
function useAreaEditMode({ iframeRef, onAreaCreatedElement, enabled = false, }) {
|
|
2308
2576
|
const [hoveredElement, setHoveredElement] = useState(null);
|
|
2309
2577
|
const [isHovering, setIsHovering] = useState(false);
|
|
2310
2578
|
const { isEditingMode } = useHeatmapAreaClick();
|
|
@@ -2313,31 +2581,30 @@ function useAreaEditMode({ iframeRef, onCreateArea, enabled = false, }) {
|
|
|
2313
2581
|
const handleMouseMove = useCallback((e) => {
|
|
2314
2582
|
if (!isActive || !iframeDocument)
|
|
2315
2583
|
return;
|
|
2316
|
-
const elements = getElementsAtPoint
|
|
2317
|
-
// Find first selectable element
|
|
2584
|
+
const elements = getElementsAtPoint(iframeDocument, e.clientX, e.clientY);
|
|
2318
2585
|
const selectableElement = elements.find((el, index, arr) => isElementSelectable(el, index, arr));
|
|
2319
|
-
|
|
2586
|
+
const isSelectable = selectableElement && selectableElement !== hoveredElement;
|
|
2587
|
+
if (isSelectable) {
|
|
2320
2588
|
setHoveredElement(selectableElement);
|
|
2321
2589
|
setIsHovering(true);
|
|
2590
|
+
return;
|
|
2322
2591
|
}
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
setIsHovering(false);
|
|
2326
|
-
}
|
|
2592
|
+
setHoveredElement(null);
|
|
2593
|
+
setIsHovering(false);
|
|
2327
2594
|
}, [isActive, iframeDocument, hoveredElement]);
|
|
2328
|
-
const
|
|
2595
|
+
const handleMouseLeave = useCallback(() => {
|
|
2596
|
+
setHoveredElement(null);
|
|
2597
|
+
setIsHovering(false);
|
|
2598
|
+
}, []);
|
|
2599
|
+
useCallback((e) => {
|
|
2329
2600
|
if (!isActive || !hoveredElement)
|
|
2330
2601
|
return;
|
|
2331
2602
|
e.stopPropagation();
|
|
2332
2603
|
e.preventDefault();
|
|
2333
|
-
if (
|
|
2334
|
-
|
|
2335
|
-
|
|
2604
|
+
if (!onAreaCreatedElement)
|
|
2605
|
+
return;
|
|
2606
|
+
onAreaCreatedElement(hoveredElement);
|
|
2336
2607
|
}, [isActive, hoveredElement]);
|
|
2337
|
-
const handleMouseLeave = useCallback(() => {
|
|
2338
|
-
setHoveredElement(null);
|
|
2339
|
-
setIsHovering(false);
|
|
2340
|
-
}, []);
|
|
2341
2608
|
useEffect(() => {
|
|
2342
2609
|
if (!isActive || !iframeDocument) {
|
|
2343
2610
|
setHoveredElement(null);
|
|
@@ -2355,17 +2622,17 @@ function useAreaEditMode({ iframeRef, onCreateArea, enabled = false, }) {
|
|
|
2355
2622
|
});
|
|
2356
2623
|
};
|
|
2357
2624
|
iframeDocument.addEventListener('mousemove', throttledMouseMove);
|
|
2358
|
-
iframeDocument.addEventListener('click', handleClick);
|
|
2359
|
-
iframeDocument.addEventListener('mouseleave', handleMouseLeave);
|
|
2360
2625
|
iframeDocument.addEventListener('scroll', handleMouseLeave);
|
|
2626
|
+
iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
|
|
2627
|
+
// iframeDocument.addEventListener('click', handleClick);
|
|
2361
2628
|
return () => {
|
|
2362
2629
|
if (rafId) {
|
|
2363
2630
|
cancelAnimationFrame(rafId);
|
|
2364
2631
|
}
|
|
2365
2632
|
iframeDocument.removeEventListener('mousemove', throttledMouseMove);
|
|
2366
|
-
iframeDocument.removeEventListener('click', handleClick);
|
|
2367
2633
|
iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
|
|
2368
2634
|
iframeDocument.removeEventListener('scroll', handleMouseLeave);
|
|
2635
|
+
// iframeDocument.removeEventListener('click', handleClick);
|
|
2369
2636
|
};
|
|
2370
2637
|
}, [isActive, iframeDocument]);
|
|
2371
2638
|
return {
|
|
@@ -2374,103 +2641,166 @@ function useAreaEditMode({ iframeRef, onCreateArea, enabled = false, }) {
|
|
|
2374
2641
|
};
|
|
2375
2642
|
}
|
|
2376
2643
|
|
|
2377
|
-
|
|
2378
|
-
const {
|
|
2644
|
+
const useAreaFilterVisible = (props) => {
|
|
2645
|
+
const { iframeRef, enableOverlapResolution } = props;
|
|
2379
2646
|
const iframeDocument = iframeRef.current?.contentDocument;
|
|
2647
|
+
const { areas, setAreas } = useHeatmapAreaClick();
|
|
2648
|
+
const visibleAreas = useMemo(() => {
|
|
2649
|
+
if (!enableOverlapResolution)
|
|
2650
|
+
return areas;
|
|
2651
|
+
if (!iframeDocument)
|
|
2652
|
+
return areas;
|
|
2653
|
+
return getVisibleAreas(areas, iframeDocument);
|
|
2654
|
+
}, [areas, iframeDocument]);
|
|
2380
2655
|
useEffect(() => {
|
|
2381
|
-
if (
|
|
2656
|
+
if (enableOverlapResolution && visibleAreas.length !== areas.length) {
|
|
2657
|
+
setAreas(visibleAreas);
|
|
2658
|
+
}
|
|
2659
|
+
}, [visibleAreas, areas.length]);
|
|
2660
|
+
return {};
|
|
2661
|
+
};
|
|
2662
|
+
|
|
2663
|
+
/**
|
|
2664
|
+
* Hook to handle area interaction (click, hover)
|
|
2665
|
+
*
|
|
2666
|
+
* @param options - Configuration options
|
|
2667
|
+
* @returns Event handlers for area interactions
|
|
2668
|
+
*/
|
|
2669
|
+
function useAreaInteraction(options = {}) {
|
|
2670
|
+
const { onAreaClick } = options;
|
|
2671
|
+
const { selectedArea, hoveredArea, isEditingMode, setSelectedArea, setHoveredArea } = useHeatmapAreaClick();
|
|
2672
|
+
const handleAreaClick = useCallback((area) => {
|
|
2673
|
+
if (isEditingMode)
|
|
2382
2674
|
return;
|
|
2675
|
+
// Toggle selection
|
|
2676
|
+
setSelectedArea(selectedArea?.id === area.id ? null : area);
|
|
2677
|
+
// Trigger callback
|
|
2678
|
+
if (onAreaClick) {
|
|
2679
|
+
onAreaClick(area);
|
|
2383
2680
|
}
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
area.rect.update(newRect);
|
|
2397
|
-
}
|
|
2398
|
-
catch (error) {
|
|
2399
|
-
console.warn('[useAreaScrollSync] Failed to update area rect:', error);
|
|
2400
|
-
}
|
|
2401
|
-
});
|
|
2402
|
-
isUpdating = false;
|
|
2403
|
-
rafId = null;
|
|
2404
|
-
});
|
|
2405
|
-
};
|
|
2406
|
-
iframeDocument.addEventListener('scroll', updateAreaPositions, { passive: true });
|
|
2407
|
-
const iframeWindow = iframeDocument.defaultView;
|
|
2408
|
-
if (iframeWindow) {
|
|
2409
|
-
iframeWindow.addEventListener('resize', updateAreaPositions, { passive: true });
|
|
2681
|
+
}, [isEditingMode, selectedArea]);
|
|
2682
|
+
const handleAreaMouseEnter = useCallback((area) => {
|
|
2683
|
+
if (isEditingMode)
|
|
2684
|
+
return;
|
|
2685
|
+
setHoveredArea(area);
|
|
2686
|
+
}, [isEditingMode]);
|
|
2687
|
+
const handleAreaMouseLeave = useCallback((area) => {
|
|
2688
|
+
if (isEditingMode)
|
|
2689
|
+
return;
|
|
2690
|
+
// Only clear if this is the currently hovered area
|
|
2691
|
+
if (hoveredArea?.id === area.id) {
|
|
2692
|
+
setHoveredArea(null);
|
|
2410
2693
|
}
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
iframeWindow.removeEventListener('resize', updateAreaPositions);
|
|
2418
|
-
}
|
|
2419
|
-
};
|
|
2420
|
-
}, [areas, iframeDocument, enabled]);
|
|
2694
|
+
}, [isEditingMode, hoveredArea]);
|
|
2695
|
+
return {
|
|
2696
|
+
handleAreaClick,
|
|
2697
|
+
handleAreaMouseEnter,
|
|
2698
|
+
handleAreaMouseLeave,
|
|
2699
|
+
};
|
|
2421
2700
|
}
|
|
2422
2701
|
|
|
2423
|
-
|
|
2424
|
-
const {
|
|
2425
|
-
const {
|
|
2426
|
-
const
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2702
|
+
function useAreaPortals(options) {
|
|
2703
|
+
const { shadowContainer, isReady, iframeRef, customShadowRoot, onAreaClick, onAreaCreated } = options;
|
|
2704
|
+
const { onAreaCreatedElement } = useAreaCreation({ customShadowRoot, onAreaCreated });
|
|
2705
|
+
const { hoveredElement } = useAreaEditMode({ iframeRef, enabled: true, onAreaCreatedElement });
|
|
2706
|
+
const { areas, selectedArea, hoveredArea, isEditingMode } = useHeatmapAreaClick();
|
|
2707
|
+
const { handleAreaClick, handleAreaMouseEnter, handleAreaMouseLeave } = useAreaInteraction({
|
|
2708
|
+
onAreaClick,
|
|
2709
|
+
});
|
|
2710
|
+
const isReadyPortal = shadowContainer && isReady;
|
|
2711
|
+
const areasPortal = isReadyPortal
|
|
2712
|
+
? createPortal(jsx(Fragment, { children: areas.map((area) => (jsx(AreaOverlay, { area: area, onClick: handleAreaClick, onMouseEnter: handleAreaMouseEnter, onMouseLeave: handleAreaMouseLeave, isSelected: selectedArea?.id === area.id, isHovered: hoveredArea?.id === area.id }, area.id))) }), shadowContainer)
|
|
2713
|
+
: null;
|
|
2714
|
+
const isReadyEditHighlight = shadowContainer && isReady && isEditingMode && hoveredElement;
|
|
2715
|
+
const editHighlightPortal = isReadyEditHighlight
|
|
2716
|
+
? createPortal(jsx(AreaEditHighlight, { element: hoveredElement, shadowRoot: customShadowRoot, onClick: onAreaCreatedElement }), shadowContainer)
|
|
2717
|
+
: null;
|
|
2718
|
+
return {
|
|
2719
|
+
areasPortal: areasPortal,
|
|
2720
|
+
editHighlightPortal: editHighlightPortal,
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2439
2723
|
|
|
2440
|
-
|
|
2724
|
+
function useAreaRectSync(options) {
|
|
2725
|
+
const { iframeDocument, shadowRoot, enabled = true } = options;
|
|
2441
2726
|
const { vizRef } = useHeatmapViz();
|
|
2442
|
-
const {
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
if (!vizRef || !scrollmap || scrollmap.length === 0)
|
|
2727
|
+
const { areas } = useHeatmapAreaClick();
|
|
2728
|
+
useEffect(() => {
|
|
2729
|
+
if (!enabled || !iframeDocument || areas.length === 0) {
|
|
2446
2730
|
return;
|
|
2447
|
-
try {
|
|
2448
|
-
vizRef?.clearmap?.();
|
|
2449
|
-
vizRef?.scrollmap?.(scrollmap);
|
|
2450
|
-
// setIsInitialized(true);
|
|
2451
|
-
}
|
|
2452
|
-
catch (error) {
|
|
2453
|
-
console.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
|
|
2454
2731
|
}
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2732
|
+
areas.forEach((area) => {
|
|
2733
|
+
try {
|
|
2734
|
+
let targetElement = area.element;
|
|
2735
|
+
if (!targetElement || !iframeDocument.contains(targetElement)) {
|
|
2736
|
+
if (area.hash) {
|
|
2737
|
+
const elementByHash = vizRef?.get(area.hash);
|
|
2738
|
+
if (elementByHash) {
|
|
2739
|
+
area.element = elementByHash;
|
|
2740
|
+
targetElement = elementByHash;
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
// // If we still can't find the element, set rect to zero
|
|
2745
|
+
// if (!targetElement || !iframeDocument.contains(targetElement)) {
|
|
2746
|
+
// area.rect.update({
|
|
2747
|
+
// width: 0,
|
|
2748
|
+
// height: 0,
|
|
2749
|
+
// top: 0,
|
|
2750
|
+
// left: 0,
|
|
2751
|
+
// absoluteLeft: 0,
|
|
2752
|
+
// absoluteTop: 0,
|
|
2753
|
+
// absoluteRight: 0,
|
|
2754
|
+
// absoluteBottom: 0,
|
|
2755
|
+
// outOfBounds: true,
|
|
2756
|
+
// });
|
|
2757
|
+
// return;
|
|
2758
|
+
// }
|
|
2759
|
+
const newRect = getElementRect(targetElement, shadowRoot);
|
|
2760
|
+
area.rect.update(newRect);
|
|
2761
|
+
}
|
|
2762
|
+
catch (error) {
|
|
2763
|
+
logger.error(`Failed to update rect for area ${area.id}:`, error);
|
|
2764
|
+
}
|
|
2765
|
+
});
|
|
2766
|
+
}, [areas, iframeDocument, shadowRoot, enabled, vizRef]);
|
|
2767
|
+
}
|
|
2458
2768
|
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2769
|
+
/**
|
|
2770
|
+
* Hook to setup and manage the shadow DOM container for area rendering
|
|
2771
|
+
*
|
|
2772
|
+
* @param iframeDocument - The iframe document
|
|
2773
|
+
* @param customShadowRoot - Optional custom shadow root element
|
|
2774
|
+
* @returns Container element and ready state
|
|
2775
|
+
*/
|
|
2776
|
+
function useAreaRendererContainer(iframeDocument, customShadowRoot) {
|
|
2777
|
+
const [shadowContainer, setShadowContainer] = useState(null);
|
|
2778
|
+
const [isReady, setIsReady] = useState(false);
|
|
2779
|
+
const containerRef = useRef(null);
|
|
2463
2780
|
useEffect(() => {
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
break;
|
|
2468
|
-
case IHeatmapType.Scroll:
|
|
2469
|
-
startScrollmap();
|
|
2470
|
-
break;
|
|
2781
|
+
if (!iframeDocument) {
|
|
2782
|
+
setIsReady(false);
|
|
2783
|
+
return;
|
|
2471
2784
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2785
|
+
const innerContainer = setupAreaRenderingContainer(iframeDocument, customShadowRoot);
|
|
2786
|
+
containerRef.current = innerContainer;
|
|
2787
|
+
setShadowContainer(innerContainer);
|
|
2788
|
+
setIsReady(true);
|
|
2789
|
+
return () => {
|
|
2790
|
+
// Cleanup on unmount
|
|
2791
|
+
const container = innerContainer.parentElement?.parentElement;
|
|
2792
|
+
cleanupAreaRenderingContainer(container);
|
|
2793
|
+
containerRef.current = null;
|
|
2794
|
+
setShadowContainer(null);
|
|
2795
|
+
setIsReady(false);
|
|
2796
|
+
};
|
|
2797
|
+
}, [iframeDocument, customShadowRoot]);
|
|
2798
|
+
return {
|
|
2799
|
+
shadowContainer,
|
|
2800
|
+
isReady,
|
|
2801
|
+
containerRef,
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2474
2804
|
|
|
2475
2805
|
const scrollToElementIfNeeded = (visualRef, rect, scale) => {
|
|
2476
2806
|
if (!visualRef.current)
|
|
@@ -2528,7 +2858,7 @@ const useClickedElement = ({ visualRef, getRect }) => {
|
|
|
2528
2858
|
requestAnimationFrame(() => {
|
|
2529
2859
|
setClickedElement(elementInfo);
|
|
2530
2860
|
});
|
|
2531
|
-
}, [selectedElement, dataInfo, visualRef, widthScale]);
|
|
2861
|
+
}, [selectedElement, dataInfo, visualRef, widthScale]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
2532
2862
|
return { clickedElement, showMissingElement, shouldShowCallout };
|
|
2533
2863
|
};
|
|
2534
2864
|
|
|
@@ -2584,7 +2914,7 @@ const useHeatmapEffects = ({ isVisible }) => {
|
|
|
2584
2914
|
const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
|
|
2585
2915
|
const heatmapWidth = useHeatmapConfigStore((state) => state.width);
|
|
2586
2916
|
const { iframeHeight, widthScale } = useHeatmapViz();
|
|
2587
|
-
|
|
2917
|
+
const getRect = useCallback((element) => {
|
|
2588
2918
|
const hash = element?.hash;
|
|
2589
2919
|
if (!iframeRef.current?.contentDocument || !hash || !visualizer)
|
|
2590
2920
|
return null;
|
|
@@ -2620,6 +2950,7 @@ const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
|
|
|
2620
2950
|
outOfBounds,
|
|
2621
2951
|
};
|
|
2622
2952
|
}, [iframeRef, wrapperRef, visualizer, heatmapWidth, iframeHeight, widthScale]);
|
|
2953
|
+
return { getRect };
|
|
2623
2954
|
};
|
|
2624
2955
|
|
|
2625
2956
|
const debounce = (fn, delay) => {
|
|
@@ -2630,10 +2961,6 @@ const debounce = (fn, delay) => {
|
|
|
2630
2961
|
};
|
|
2631
2962
|
};
|
|
2632
2963
|
|
|
2633
|
-
// ===================== UTILITY FUNCTIONS =====================
|
|
2634
|
-
/**
|
|
2635
|
-
* Lấy bounding box tuyệt đối của element (relative to document)
|
|
2636
|
-
*/
|
|
2637
2964
|
function getBoundingBox(element) {
|
|
2638
2965
|
if (typeof element.getBoundingClientRect !== 'function') {
|
|
2639
2966
|
return null;
|
|
@@ -2654,84 +2981,72 @@ function getBoundingBox(element) {
|
|
|
2654
2981
|
height: Math.floor(rect.height),
|
|
2655
2982
|
};
|
|
2656
2983
|
}
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
//
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
Clicks: {hoveredElement.clicks}
|
|
2724
|
-
<br />
|
|
2725
|
-
Rank: #{hoveredElement.rank}
|
|
2726
|
-
<br />
|
|
2727
|
-
Selector: {hoveredElement.selector}
|
|
2728
|
-
</div>
|
|
2729
|
-
)}
|
|
2730
|
-
</div>
|
|
2731
|
-
</div>
|
|
2732
|
-
);
|
|
2733
|
-
}
|
|
2734
|
-
*/
|
|
2984
|
+
// export function useHeatmapMouseHandler(props: UseHeatmapMouseHandlerProps) {
|
|
2985
|
+
// const { heatmapWrapperRef, iframeRef, parentRef, heatmapInfo, scaleRatio, onElementHover } = props;
|
|
2986
|
+
// const handleMouseMove = useCallback(
|
|
2987
|
+
// (event: MouseEvent) => {
|
|
2988
|
+
// if (
|
|
2989
|
+
// !heatmapWrapperRef?.current ||
|
|
2990
|
+
// !iframeRef?.current ||
|
|
2991
|
+
// !iframeRef.current.contentDocument ||
|
|
2992
|
+
// !heatmapInfo?.elementMapInfo ||
|
|
2993
|
+
// !parentRef?.current
|
|
2994
|
+
// ) {
|
|
2995
|
+
// return;
|
|
2996
|
+
// }
|
|
2997
|
+
// try {
|
|
2998
|
+
// // Calculate scroll position (scaled)
|
|
2999
|
+
// const scrollTop = parentRef.current.scrollTop / scaleRatio;
|
|
3000
|
+
// // Get position of heatmap wrapper
|
|
3001
|
+
// const wrapperRect = heatmapWrapperRef.current.getBoundingClientRect();
|
|
3002
|
+
// // Calculate mouse position in iframe (scaled)
|
|
3003
|
+
// const mouseX = (event.clientX - wrapperRect.left) / scaleRatio;
|
|
3004
|
+
// const mouseY = (event.clientY - wrapperRect.top) / scaleRatio - scrollTop;
|
|
3005
|
+
// const elementsAtPoint = getElementsAtPoint(
|
|
3006
|
+
// iframeRef.current.contentDocument,
|
|
3007
|
+
// Math.round(mouseX),
|
|
3008
|
+
// Math.round(mouseY),
|
|
3009
|
+
// {
|
|
3010
|
+
// filterFn: (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE),
|
|
3011
|
+
// ignoreCanvas: true,
|
|
3012
|
+
// },
|
|
3013
|
+
// );
|
|
3014
|
+
// if (!elementsAtPoint || elementsAtPoint.length === 0) {
|
|
3015
|
+
// return;
|
|
3016
|
+
// }
|
|
3017
|
+
// for (let i = 0; i < elementsAtPoint.length; i++) {
|
|
3018
|
+
// const element = elementsAtPoint[i] as HTMLElement;
|
|
3019
|
+
// const elementHash = element.getAttribute(HEATMAP_ELEMENT_ATTRIBUTE);
|
|
3020
|
+
// if (elementHash && heatmapInfo.elementMapInfo[elementHash]) {
|
|
3021
|
+
// const elementData = heatmapInfo.elementMapInfo[elementHash];
|
|
3022
|
+
// const boundingBox = getBoundingBox(element);
|
|
3023
|
+
// if (boundingBox) {
|
|
3024
|
+
// const rank =
|
|
3025
|
+
// Array.isArray(heatmapInfo.sortedElements) && elementData
|
|
3026
|
+
// ? heatmapInfo.sortedElements.indexOf(elementData) + 1
|
|
3027
|
+
// : NaN;
|
|
3028
|
+
// onElementHover({
|
|
3029
|
+
// ...boundingBox,
|
|
3030
|
+
// width: Math.min(boundingBox.width, heatmapInfo.width || 0),
|
|
3031
|
+
// top: boundingBox.top + scrollTop,
|
|
3032
|
+
// // Metadata
|
|
3033
|
+
// hash: elementHash,
|
|
3034
|
+
// clicks: elementData.totalclicks,
|
|
3035
|
+
// rank: rank,
|
|
3036
|
+
// selector: elementData.selector || '',
|
|
3037
|
+
// });
|
|
3038
|
+
// break;
|
|
3039
|
+
// }
|
|
3040
|
+
// }
|
|
3041
|
+
// }
|
|
3042
|
+
// } catch (error) {
|
|
3043
|
+
// console.warn('Error handling mouse move on heatmap:', error);
|
|
3044
|
+
// }
|
|
3045
|
+
// },
|
|
3046
|
+
// [heatmapWrapperRef, iframeRef, parentRef, heatmapInfo, scaleRatio, onElementHover],
|
|
3047
|
+
// );
|
|
3048
|
+
// return { handleMouseMove };
|
|
3049
|
+
// }
|
|
2735
3050
|
|
|
2736
3051
|
const useHoveredElement = ({ iframeRef, getRect }) => {
|
|
2737
3052
|
const { hoveredElement, setHoveredElement, setSelectedElement } = useHeatmapClick();
|
|
@@ -2814,12 +3129,14 @@ const convertViewportToIframeCoords = (clientX, clientY, iframeRect, scale) => {
|
|
|
2814
3129
|
return { x, y };
|
|
2815
3130
|
};
|
|
2816
3131
|
const findTargetElement = (doc, x, y, heatmapInfo) => {
|
|
2817
|
-
const
|
|
2818
|
-
|
|
3132
|
+
const elementsAtPoint = getElementsAtPoint(doc, Math.round(x), Math.round(y), {
|
|
3133
|
+
filterFn: (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE),
|
|
3134
|
+
ignoreCanvas: true,
|
|
3135
|
+
});
|
|
2819
3136
|
let dataElement = null;
|
|
2820
3137
|
for (let i = 0; i < elementsAtPoint.length; i++) {
|
|
2821
3138
|
const element = elementsAtPoint[i];
|
|
2822
|
-
const elementHash = element
|
|
3139
|
+
const elementHash = getElementHash(element);
|
|
2823
3140
|
if (elementHash && heatmapInfo.elementMapInfo?.[elementHash]) {
|
|
2824
3141
|
const boundingBox = getBoundingBox(element);
|
|
2825
3142
|
if (boundingBox) {
|
|
@@ -2849,6 +3166,167 @@ const isValidElement = (element, heatmapInfo) => {
|
|
|
2849
3166
|
return !!heatmapInfo?.elementMapInfo?.[hash];
|
|
2850
3167
|
};
|
|
2851
3168
|
|
|
3169
|
+
function useAreaScrollSync(options) {
|
|
3170
|
+
const { iframeRef, visualRef, enabled = true } = options;
|
|
3171
|
+
const { widthScale } = useHeatmapViz();
|
|
3172
|
+
const { areas, selectedArea } = useHeatmapAreaClick();
|
|
3173
|
+
const iframeDocument = iframeRef.current?.contentDocument;
|
|
3174
|
+
useEffect(() => {
|
|
3175
|
+
if (!enabled || !iframeDocument || areas.length === 0) {
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
let rafId = null;
|
|
3179
|
+
let isUpdating = false;
|
|
3180
|
+
const updateAreaPositions = () => {
|
|
3181
|
+
if (isUpdating)
|
|
3182
|
+
return;
|
|
3183
|
+
isUpdating = true;
|
|
3184
|
+
rafId = requestAnimationFrame(() => {
|
|
3185
|
+
areas.forEach((area) => {
|
|
3186
|
+
if (!area.element || !area.rect)
|
|
3187
|
+
return;
|
|
3188
|
+
try {
|
|
3189
|
+
const newRect = getElementRect(area.element);
|
|
3190
|
+
area.rect.update(newRect);
|
|
3191
|
+
}
|
|
3192
|
+
catch (error) {
|
|
3193
|
+
console.warn('[useAreaScrollSync] Failed to update area rect:', error);
|
|
3194
|
+
}
|
|
3195
|
+
});
|
|
3196
|
+
isUpdating = false;
|
|
3197
|
+
rafId = null;
|
|
3198
|
+
});
|
|
3199
|
+
};
|
|
3200
|
+
iframeDocument.addEventListener('scroll', updateAreaPositions, { passive: true });
|
|
3201
|
+
const iframeWindow = iframeDocument.defaultView;
|
|
3202
|
+
if (iframeWindow) {
|
|
3203
|
+
iframeWindow.addEventListener('resize', updateAreaPositions, { passive: true });
|
|
3204
|
+
}
|
|
3205
|
+
return () => {
|
|
3206
|
+
if (rafId !== null) {
|
|
3207
|
+
cancelAnimationFrame(rafId);
|
|
3208
|
+
}
|
|
3209
|
+
iframeDocument.removeEventListener('scroll', updateAreaPositions);
|
|
3210
|
+
if (iframeWindow) {
|
|
3211
|
+
iframeWindow.removeEventListener('resize', updateAreaPositions);
|
|
3212
|
+
}
|
|
3213
|
+
};
|
|
3214
|
+
}, [areas, iframeDocument, enabled]);
|
|
3215
|
+
useEffect(() => {
|
|
3216
|
+
if (!selectedArea)
|
|
3217
|
+
return;
|
|
3218
|
+
if (!visualRef)
|
|
3219
|
+
return;
|
|
3220
|
+
if (!selectedArea.rect.value)
|
|
3221
|
+
return;
|
|
3222
|
+
scrollToElementIfNeeded(visualRef, selectedArea.rect.value, widthScale);
|
|
3223
|
+
}, [visualRef, selectedArea, widthScale]);
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
const useAreaTopAutoDetect = (props) => {
|
|
3227
|
+
const { iframeRef, autoCreateTopN, shadowRoot } = props;
|
|
3228
|
+
const iframeDocument = iframeRef.current?.contentDocument;
|
|
3229
|
+
const { dataInfo } = useHeatmapData();
|
|
3230
|
+
const { vizRef } = useHeatmapViz();
|
|
3231
|
+
const { areas, addArea } = useHeatmapAreaClick();
|
|
3232
|
+
useEffect(() => {
|
|
3233
|
+
if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks)
|
|
3234
|
+
return;
|
|
3235
|
+
if (autoCreateTopN <= 0)
|
|
3236
|
+
return;
|
|
3237
|
+
if (areas.length > 0)
|
|
3238
|
+
return;
|
|
3239
|
+
const topElements = getTopElementsByClicks(dataInfo.elementMapInfo, autoCreateTopN);
|
|
3240
|
+
const newAreas = [];
|
|
3241
|
+
topElements.forEach(({ hash }) => {
|
|
3242
|
+
const element = vizRef?.get(hash);
|
|
3243
|
+
if (!element)
|
|
3244
|
+
return;
|
|
3245
|
+
const area = buildAreaNode(element, hash, dataInfo);
|
|
3246
|
+
if (!area)
|
|
3247
|
+
return;
|
|
3248
|
+
newAreas.push(area);
|
|
3249
|
+
});
|
|
3250
|
+
newAreas.forEach((area) => addArea(area));
|
|
3251
|
+
}, [dataInfo, autoCreateTopN, areas.length, iframeDocument, shadowRoot]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
3252
|
+
return {};
|
|
3253
|
+
};
|
|
3254
|
+
|
|
3255
|
+
const useAreaClickmap = () => {
|
|
3256
|
+
const { vizRef } = useHeatmapViz();
|
|
3257
|
+
const { clickmap } = useHeatmapData();
|
|
3258
|
+
const start = useCallback(() => {
|
|
3259
|
+
if (!vizRef || !clickmap || clickmap.length === 0)
|
|
3260
|
+
return;
|
|
3261
|
+
try {
|
|
3262
|
+
vizRef?.clearmap?.();
|
|
3263
|
+
}
|
|
3264
|
+
catch (error) {
|
|
3265
|
+
console.error(`🚀 🐥 ~ useAreaClickmap ~ error:`, error);
|
|
3266
|
+
}
|
|
3267
|
+
}, [vizRef, clickmap]);
|
|
3268
|
+
return { start };
|
|
3269
|
+
};
|
|
3270
|
+
|
|
3271
|
+
const useClickmap = () => {
|
|
3272
|
+
const { vizRef } = useHeatmapViz();
|
|
3273
|
+
const { clickmap } = useHeatmapData();
|
|
3274
|
+
const start = useCallback(() => {
|
|
3275
|
+
if (!vizRef || !clickmap || clickmap.length === 0)
|
|
3276
|
+
return;
|
|
3277
|
+
try {
|
|
3278
|
+
vizRef?.clearmap?.();
|
|
3279
|
+
vizRef?.clickmap?.(clickmap);
|
|
3280
|
+
}
|
|
3281
|
+
catch (error) {
|
|
3282
|
+
console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
|
|
3283
|
+
}
|
|
3284
|
+
}, [vizRef, clickmap]);
|
|
3285
|
+
return { start };
|
|
3286
|
+
};
|
|
3287
|
+
|
|
3288
|
+
const useScrollmap = () => {
|
|
3289
|
+
const { vizRef } = useHeatmapViz();
|
|
3290
|
+
const { scrollmap } = useHeatmapData();
|
|
3291
|
+
const start = useCallback(() => {
|
|
3292
|
+
// if (isInitialized) return;
|
|
3293
|
+
if (!vizRef || !scrollmap || scrollmap.length === 0)
|
|
3294
|
+
return;
|
|
3295
|
+
try {
|
|
3296
|
+
vizRef?.clearmap?.();
|
|
3297
|
+
vizRef?.scrollmap?.(scrollmap);
|
|
3298
|
+
// setIsInitialized(true);
|
|
3299
|
+
}
|
|
3300
|
+
catch (error) {
|
|
3301
|
+
console.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
|
|
3302
|
+
}
|
|
3303
|
+
}, [vizRef, scrollmap]);
|
|
3304
|
+
return { start };
|
|
3305
|
+
};
|
|
3306
|
+
|
|
3307
|
+
const useHeatmapCanvas = () => {
|
|
3308
|
+
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
3309
|
+
const clickMode = useHeatmapConfigStore((state) => state.clickMode);
|
|
3310
|
+
const { start: startClickmap } = useClickmap();
|
|
3311
|
+
const { start: startAreaClickmap } = useAreaClickmap();
|
|
3312
|
+
const { start: startScrollmap } = useScrollmap();
|
|
3313
|
+
useEffect(() => {
|
|
3314
|
+
switch (heatmapType) {
|
|
3315
|
+
case IHeatmapType.Click:
|
|
3316
|
+
if (clickMode === IClickMode.Default) {
|
|
3317
|
+
startClickmap();
|
|
3318
|
+
}
|
|
3319
|
+
else {
|
|
3320
|
+
startAreaClickmap();
|
|
3321
|
+
}
|
|
3322
|
+
break;
|
|
3323
|
+
case IHeatmapType.Scroll:
|
|
3324
|
+
startScrollmap();
|
|
3325
|
+
break;
|
|
3326
|
+
}
|
|
3327
|
+
}, [heatmapType, clickMode, startClickmap, startAreaClickmap, startScrollmap]);
|
|
3328
|
+
};
|
|
3329
|
+
|
|
2852
3330
|
var MessageType;
|
|
2853
3331
|
(function (MessageType) {
|
|
2854
3332
|
MessageType["GX_DOM_TRACKING_PAYLOAD"] = "GX_DOM_TRACKING_PAYLOAD";
|
|
@@ -3137,7 +3615,7 @@ const useReplayRender = () => {
|
|
|
3137
3615
|
};
|
|
3138
3616
|
};
|
|
3139
3617
|
|
|
3140
|
-
const
|
|
3618
|
+
const useHeatmapRenderByMode = (mode) => {
|
|
3141
3619
|
const heatmapResult = useMemo(() => {
|
|
3142
3620
|
switch (mode) {
|
|
3143
3621
|
case 'heatmap':
|
|
@@ -4228,373 +4706,69 @@ const PopoverSidebar = () => {
|
|
|
4228
4706
|
overflow: 'auto',
|
|
4229
4707
|
}, children: jsx(CompSidebar, { closeAction: { onClick: () => setIsPopoverOpen(false) } }) }) }))] }));
|
|
4230
4708
|
};
|
|
4231
|
-
|
|
4232
|
-
const ContentToolbar = () => {
|
|
4233
|
-
const controls = useHeatmapControlStore((state) => state.controls);
|
|
4234
|
-
return (jsx("div", { id: "gx-hm-content-toolbar", style: {
|
|
4235
|
-
position: 'absolute',
|
|
4236
|
-
bottom: 0,
|
|
4237
|
-
left: '8px',
|
|
4238
|
-
right: '24px',
|
|
4239
|
-
padding: '8px',
|
|
4240
|
-
paddingBlock: '16px',
|
|
4241
|
-
}, children: controls.Toolbar ?? null }));
|
|
4242
|
-
};
|
|
4243
|
-
|
|
4244
|
-
const VizContainer = ({ children, isActive = false }) => {
|
|
4245
|
-
const wrapperRef = useRef(null);
|
|
4246
|
-
const viewId = useViewIdContext();
|
|
4247
|
-
useWrapperRefHeight({
|
|
4248
|
-
isActive,
|
|
4249
|
-
wrapperRef,
|
|
4250
|
-
});
|
|
4251
|
-
return (jsxs(BoxStack, { ref: wrapperRef, id: `gx-hm-viz-container-${viewId}`, flexDirection: "column", flex: "1 1 auto", overflow: "auto", zIndex: 1, children: [jsx(BoxStack, { id: "gx-hm-content", flexDirection: "column", flex: "1 1 auto", overflow: "hidden", style: {
|
|
4252
|
-
minWidth: '394px',
|
|
4253
|
-
}, children: children }), jsx(PopoverSidebar, {})] }));
|
|
4254
|
-
};
|
|
4255
|
-
|
|
4256
|
-
/**
|
|
4257
|
-
* Controls for area click feature - toggle edit mode, clear areas, etc.
|
|
4258
|
-
*/
|
|
4259
|
-
const AreaControls = ({ className, style }) => {
|
|
4260
|
-
const { isEditingMode, setIsEditingMode, areas, clearAreas } = useHeatmapAreaClick();
|
|
4261
|
-
return (jsxs("div", { className: className, style: {
|
|
4262
|
-
display: 'flex',
|
|
4263
|
-
gap: '8px',
|
|
4264
|
-
alignItems: 'center',
|
|
4265
|
-
...style,
|
|
4266
|
-
}, children: [jsx("button", { onClick: () => setIsEditingMode(!isEditingMode), style: {
|
|
4267
|
-
padding: '8px 16px',
|
|
4268
|
-
borderRadius: '4px',
|
|
4269
|
-
border: '1px solid #ccc',
|
|
4270
|
-
backgroundColor: isEditingMode ? '#0078D4' : 'white',
|
|
4271
|
-
color: isEditingMode ? 'white' : '#161514',
|
|
4272
|
-
cursor: 'pointer',
|
|
4273
|
-
fontWeight: 500,
|
|
4274
|
-
transition: 'all 0.2s',
|
|
4275
|
-
}, children: isEditingMode ? 'Exit Edit Mode' : 'Edit Areas' }), areas.length > 0 && (jsxs(Fragment, { children: [jsxs("span", { style: { color: '#605E5C', fontSize: '14px' }, children: [areas.length, " area", areas.length !== 1 ? 's' : ''] }), jsx("button", { onClick: () => {
|
|
4276
|
-
if (confirm(`Clear all ${areas.length} areas?`)) {
|
|
4277
|
-
clearAreas();
|
|
4278
|
-
}
|
|
4279
|
-
}, style: {
|
|
4280
|
-
padding: '8px 16px',
|
|
4281
|
-
borderRadius: '4px',
|
|
4282
|
-
border: '1px solid #ccc',
|
|
4283
|
-
backgroundColor: 'white',
|
|
4284
|
-
color: '#A4262C',
|
|
4285
|
-
cursor: 'pointer',
|
|
4286
|
-
fontWeight: 500,
|
|
4287
|
-
transition: 'all 0.2s',
|
|
4288
|
-
}, children: "Clear All" })] }))] }));
|
|
4289
|
-
};
|
|
4290
|
-
AreaControls.displayName = 'AreaControls';
|
|
4291
|
-
|
|
4292
|
-
const AreaEditHighlight = ({ element, shadowRoot, onClick, }) => {
|
|
4293
|
-
const [rect, setRect] = useState(null);
|
|
4294
|
-
const highlightRef = useRef(null);
|
|
4295
|
-
useEffect(() => {
|
|
4296
|
-
if (!element) {
|
|
4297
|
-
setRect(null);
|
|
4298
|
-
return;
|
|
4299
|
-
}
|
|
4300
|
-
// Calculate element position
|
|
4301
|
-
const elementRect = getElementRect(element);
|
|
4302
|
-
setRect(elementRect);
|
|
4303
|
-
}, [element, shadowRoot]);
|
|
4304
|
-
if (!rect) {
|
|
4305
|
-
return null;
|
|
4306
|
-
}
|
|
4307
|
-
const handleClick = (e) => {
|
|
4308
|
-
if (element && onClick) {
|
|
4309
|
-
e.stopPropagation();
|
|
4310
|
-
e.preventDefault();
|
|
4311
|
-
onClick(element);
|
|
4312
|
-
}
|
|
4313
|
-
};
|
|
4314
|
-
return (jsx("div", { ref: highlightRef, id: AREA_HOVER_ELEMENT_ID, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: handleClick, style: {
|
|
4315
|
-
position: 'absolute',
|
|
4316
|
-
top: `${rect.absoluteTop}px`,
|
|
4317
|
-
left: `${rect.absoluteLeft}px`,
|
|
4318
|
-
width: `${rect.width}px`,
|
|
4319
|
-
height: `${rect.height}px`,
|
|
4320
|
-
zIndex: Number.MAX_SAFE_INTEGER,
|
|
4321
|
-
boxShadow: AREA_HOVER_BOX_SHADOW,
|
|
4322
|
-
backgroundColor: 'rgba(128, 128, 128, 0.4)',
|
|
4323
|
-
backgroundImage: 'repeating-linear-gradient(135deg, transparent, transparent 35px, rgba(255,255,255,.5) 35px, rgba(255,255,255,.5) 70px)',
|
|
4324
|
-
pointerEvents: 'auto',
|
|
4325
|
-
cursor: 'pointer',
|
|
4326
|
-
boxSizing: 'border-box',
|
|
4327
|
-
} }));
|
|
4328
|
-
};
|
|
4329
|
-
AreaEditHighlight.displayName = 'AreaEditHighlight';
|
|
4330
|
-
|
|
4331
|
-
const AreaLabel = ({ clickDist, totalClicks, kind }) => {
|
|
4332
|
-
if (kind === 'money') {
|
|
4333
|
-
return null;
|
|
4334
|
-
}
|
|
4335
|
-
return (jsxs("div", { style: {
|
|
4336
|
-
color: '#161514',
|
|
4337
|
-
backgroundColor: 'rgba(255, 255, 255, 0.86)',
|
|
4338
|
-
display: 'flex',
|
|
4339
|
-
flexDirection: 'column',
|
|
4340
|
-
alignItems: 'center',
|
|
4709
|
+
|
|
4710
|
+
const ContentToolbar = () => {
|
|
4711
|
+
const controls = useHeatmapControlStore((state) => state.controls);
|
|
4712
|
+
return (jsx("div", { id: "gx-hm-content-toolbar", style: {
|
|
4713
|
+
position: 'absolute',
|
|
4714
|
+
bottom: 0,
|
|
4715
|
+
left: '8px',
|
|
4716
|
+
right: '24px',
|
|
4341
4717
|
padding: '8px',
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
lineHeight: '20px',
|
|
4345
|
-
minWidth: '56px',
|
|
4346
|
-
fontWeight: 600,
|
|
4347
|
-
fontFamily: '"Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif',
|
|
4348
|
-
pointerEvents: 'none',
|
|
4349
|
-
}, children: [jsxs("span", { children: [clickDist.toFixed(2), "%"] }), jsxs("span", { style: { fontSize: '12px', fontWeight: 400, opacity: 0.8 }, children: [totalClicks, " clicks"] })] }));
|
|
4718
|
+
paddingBlock: '16px',
|
|
4719
|
+
}, children: controls.Toolbar ?? null }));
|
|
4350
4720
|
};
|
|
4351
|
-
AreaLabel.displayName = 'AreaLabel';
|
|
4352
4721
|
|
|
4353
|
-
const
|
|
4354
|
-
const
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
area.rect.unobserve(handleRectChange);
|
|
4364
|
-
};
|
|
4365
|
-
}, [area.rect]);
|
|
4366
|
-
if (!rect)
|
|
4367
|
-
return null;
|
|
4368
|
-
const position = area.isFixed ? 'fixed' : 'absolute';
|
|
4369
|
-
const showLabel = !isRectTooSmallForLabel(rect);
|
|
4370
|
-
const backgroundColor = isHovered ? area.hoverColor : area.color;
|
|
4371
|
-
const boxShadow = isSelected
|
|
4372
|
-
? '0 0 0 3px #0078d4 inset'
|
|
4373
|
-
: isHovered
|
|
4374
|
-
? AREA_HOVER_BOX_SHADOW
|
|
4375
|
-
: '0 0 0 2px white inset';
|
|
4376
|
-
return (jsx("div", { id: `area-${area.id}`, "data-area-id": area.id, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: () => onClick?.(area), onMouseEnter: () => onMouseEnter?.(area), onMouseLeave: () => onMouseLeave?.(area), style: {
|
|
4377
|
-
position,
|
|
4378
|
-
top: `${rect.top}px`,
|
|
4379
|
-
left: `${rect.left}px`,
|
|
4380
|
-
width: `${rect.width}px`,
|
|
4381
|
-
height: `${rect.height}px`,
|
|
4382
|
-
backgroundColor,
|
|
4383
|
-
boxShadow,
|
|
4384
|
-
boxSizing: 'border-box',
|
|
4385
|
-
display: 'flex',
|
|
4386
|
-
alignItems: 'center',
|
|
4387
|
-
justifyContent: 'center',
|
|
4388
|
-
cursor: 'pointer',
|
|
4389
|
-
transition: 'background-color 0.2s, box-shadow 0.2s',
|
|
4390
|
-
pointerEvents: 'auto',
|
|
4391
|
-
}, children: showLabel && (jsx(AreaLabel, { clickDist: area.clickDist, totalClicks: area.totalclicks, kind: area.kind })) }));
|
|
4722
|
+
const VizContainer = ({ children, isActive = false }) => {
|
|
4723
|
+
const wrapperRef = useRef(null);
|
|
4724
|
+
const viewId = useViewIdContext();
|
|
4725
|
+
useWrapperRefHeight({
|
|
4726
|
+
isActive,
|
|
4727
|
+
wrapperRef,
|
|
4728
|
+
});
|
|
4729
|
+
return (jsxs(BoxStack, { ref: wrapperRef, id: `gx-hm-viz-container-${viewId}`, flexDirection: "column", flex: "1 1 auto", overflow: "auto", zIndex: 1, children: [jsx(BoxStack, { id: "gx-hm-content", flexDirection: "column", flex: "1 1 auto", overflow: "hidden", style: {
|
|
4730
|
+
minWidth: '394px',
|
|
4731
|
+
}, children: children }), jsx(PopoverSidebar, {})] }));
|
|
4392
4732
|
};
|
|
4393
4733
|
|
|
4394
4734
|
function useAreaRenderer(options) {
|
|
4395
|
-
const { iframeRef, shadowRoot
|
|
4396
|
-
const iframeDocument = iframeRef.current?.contentDocument;
|
|
4397
|
-
|
|
4398
|
-
const {
|
|
4399
|
-
const { areas, selectedArea, hoveredArea, isEditingMode, setSelectedArea, setHoveredArea, addArea, } = useHeatmapAreaClick();
|
|
4400
|
-
const [shadowContainer, setShadowContainer] = useState(null);
|
|
4401
|
-
const [isReady, setIsReady] = useState(false);
|
|
4402
|
-
const containerRef = useRef(null);
|
|
4403
|
-
useEffect(() => {
|
|
4404
|
-
if (!iframeDocument) {
|
|
4405
|
-
setIsReady(false);
|
|
4406
|
-
return;
|
|
4407
|
-
}
|
|
4408
|
-
let container = iframeDocument.querySelector(`[${AREA_MAP_DIV_ATTRIBUTE}]`);
|
|
4409
|
-
if (!container) {
|
|
4410
|
-
container = iframeDocument.createElement('div');
|
|
4411
|
-
container.setAttribute(AREA_MAP_DIV_ATTRIBUTE, 'true');
|
|
4412
|
-
container.style.cssText = `
|
|
4413
|
-
position: absolute;
|
|
4414
|
-
top: 0;
|
|
4415
|
-
left: 0;
|
|
4416
|
-
width: 100%;
|
|
4417
|
-
height: 100%;
|
|
4418
|
-
pointer-events: none;
|
|
4419
|
-
z-index: 999999;
|
|
4420
|
-
`;
|
|
4421
|
-
// Append to custom shadow root or body
|
|
4422
|
-
const targetRoot = customShadowRoot || iframeDocument.body;
|
|
4423
|
-
if (targetRoot) {
|
|
4424
|
-
targetRoot.appendChild(container);
|
|
4425
|
-
}
|
|
4426
|
-
}
|
|
4427
|
-
// Create shadow root if needed
|
|
4428
|
-
let shadowRoot;
|
|
4429
|
-
if (!container.shadowRoot) {
|
|
4430
|
-
shadowRoot = getOrCreateShadowRoot(container);
|
|
4431
|
-
}
|
|
4432
|
-
else {
|
|
4433
|
-
shadowRoot = container.shadowRoot;
|
|
4434
|
-
}
|
|
4435
|
-
// Create inner container for React portal
|
|
4436
|
-
let innerContainer = shadowRoot.querySelector('.heatmap-area-container');
|
|
4437
|
-
if (!innerContainer) {
|
|
4438
|
-
innerContainer = iframeDocument.createElement('div');
|
|
4439
|
-
innerContainer.className = 'heatmap-area-container';
|
|
4440
|
-
innerContainer.style.cssText = `
|
|
4441
|
-
position: relative;
|
|
4442
|
-
width: 100%;
|
|
4443
|
-
height: 100%;
|
|
4444
|
-
`;
|
|
4445
|
-
shadowRoot.appendChild(innerContainer);
|
|
4446
|
-
}
|
|
4447
|
-
containerRef.current = innerContainer;
|
|
4448
|
-
setShadowContainer(innerContainer);
|
|
4449
|
-
setIsReady(true);
|
|
4450
|
-
return () => {
|
|
4451
|
-
if (container && container.parentNode) {
|
|
4452
|
-
container.parentNode.removeChild(container);
|
|
4453
|
-
}
|
|
4454
|
-
containerRef.current = null;
|
|
4455
|
-
setShadowContainer(null);
|
|
4456
|
-
setIsReady(false);
|
|
4457
|
-
};
|
|
4458
|
-
}, [iframeDocument, customShadowRoot]);
|
|
4459
|
-
const handleCreateAreaFromElement = useCallback((element) => {
|
|
4460
|
-
if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks) {
|
|
4461
|
-
console.warn('[useAreaRenderer] Cannot create area: missing heatmap data');
|
|
4462
|
-
return;
|
|
4463
|
-
}
|
|
4464
|
-
const hash = getElementHash(element);
|
|
4465
|
-
if (!hash) {
|
|
4466
|
-
console.warn('[useAreaRenderer] Cannot create area: missing hash');
|
|
4467
|
-
return;
|
|
4468
|
-
}
|
|
4469
|
-
const alreadyExists = areas.some((area) => area.hash === hash);
|
|
4470
|
-
if (alreadyExists) {
|
|
4471
|
-
console.warn(`[useAreaRenderer] Area already exists for element: ${hash}`);
|
|
4472
|
-
return;
|
|
4473
|
-
}
|
|
4474
|
-
try {
|
|
4475
|
-
const area = buildAreaNode(element, hash, dataInfo.elementMapInfo, dataInfo.totalClicks, customShadowRoot);
|
|
4476
|
-
addArea(area);
|
|
4477
|
-
if (onAreaCreated) {
|
|
4478
|
-
onAreaCreated(area);
|
|
4479
|
-
}
|
|
4480
|
-
console.log('[useAreaRenderer] Area created:', {
|
|
4481
|
-
hash,
|
|
4482
|
-
selector: area.selector,
|
|
4483
|
-
clicks: area.totalclicks,
|
|
4484
|
-
clickDist: area.clickDist,
|
|
4485
|
-
});
|
|
4486
|
-
}
|
|
4487
|
-
catch (error) {
|
|
4488
|
-
console.error('[useAreaRenderer] Failed to create area:', error);
|
|
4489
|
-
}
|
|
4490
|
-
}, [dataInfo, areas, customShadowRoot, addArea]);
|
|
4491
|
-
const { hoveredElement } = useAreaEditMode({
|
|
4492
|
-
iframeRef,
|
|
4493
|
-
enabled: isEditingMode,
|
|
4494
|
-
onCreateArea: handleCreateAreaFromElement,
|
|
4495
|
-
});
|
|
4496
|
-
useAreaScrollSync({
|
|
4497
|
-
areas,
|
|
4735
|
+
const { iframeRef, visualRef, shadowRoot, onAreaCreated, onAreaClick } = options;
|
|
4736
|
+
const iframeDocument = iframeRef.current?.contentDocument || undefined;
|
|
4737
|
+
const { shadowContainer, isReady } = useAreaRendererContainer(iframeDocument, shadowRoot);
|
|
4738
|
+
const { areasPortal, editHighlightPortal } = useAreaPortals({
|
|
4498
4739
|
iframeRef,
|
|
4499
|
-
|
|
4740
|
+
shadowContainer,
|
|
4741
|
+
isReady,
|
|
4742
|
+
onAreaClick,
|
|
4743
|
+
onAreaCreated,
|
|
4500
4744
|
});
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
return;
|
|
4504
|
-
setSelectedArea(selectedArea?.id === area.id ? null : area);
|
|
4505
|
-
if (onAreaClick) {
|
|
4506
|
-
onAreaClick(area);
|
|
4507
|
-
}
|
|
4508
|
-
};
|
|
4509
|
-
const handleAreaMouseEnter = (area) => {
|
|
4510
|
-
if (isEditingMode)
|
|
4511
|
-
return;
|
|
4512
|
-
setHoveredArea(area);
|
|
4513
|
-
};
|
|
4514
|
-
const handleAreaMouseLeave = (area) => {
|
|
4515
|
-
if (isEditingMode)
|
|
4516
|
-
return;
|
|
4517
|
-
if (hoveredArea?.id === area.id) {
|
|
4518
|
-
setHoveredArea(null);
|
|
4519
|
-
}
|
|
4520
|
-
};
|
|
4521
|
-
const areasPortal = shadowContainer && isReady
|
|
4522
|
-
? createPortal(jsx(Fragment, { children: areas.map((area) => (jsx(AreaOverlay, { area: area, onClick: handleAreaClick, onMouseEnter: handleAreaMouseEnter, onMouseLeave: handleAreaMouseLeave, isSelected: selectedArea?.id === area.id, isHovered: hoveredArea?.id === area.id }, area.id))) }), shadowContainer)
|
|
4523
|
-
: null;
|
|
4524
|
-
const editHighlightPortal = shadowContainer && isReady && isEditingMode && hoveredElement
|
|
4525
|
-
? createPortal(jsx(AreaEditHighlight, { element: hoveredElement, shadowRoot: customShadowRoot, onClick: handleCreateAreaFromElement }), shadowContainer)
|
|
4526
|
-
: null;
|
|
4745
|
+
useAreaRectSync({ iframeDocument, shadowRoot, enabled: isReady });
|
|
4746
|
+
useAreaScrollSync({ iframeRef, visualRef, enabled: isReady });
|
|
4527
4747
|
return {
|
|
4528
|
-
areasPortal
|
|
4529
|
-
editHighlightPortal
|
|
4530
|
-
shadowContainer,
|
|
4748
|
+
areasPortal,
|
|
4749
|
+
editHighlightPortal,
|
|
4531
4750
|
isReady,
|
|
4532
4751
|
};
|
|
4533
4752
|
}
|
|
4534
4753
|
|
|
4535
|
-
const VizAreaClick = ({ iframeRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true,
|
|
4536
|
-
const
|
|
4537
|
-
const { dataInfo } = useHeatmapData();
|
|
4538
|
-
const { areas, isEditingMode, setIsEditingMode, addArea, clearAreas, setAreas } = useHeatmapAreaClick();
|
|
4754
|
+
const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true, onAreaClick, }) => {
|
|
4755
|
+
const { resetView } = useHeatmapAreaClick();
|
|
4539
4756
|
const { areasPortal, editHighlightPortal, isReady } = useAreaRenderer({
|
|
4540
4757
|
iframeRef,
|
|
4758
|
+
visualRef,
|
|
4541
4759
|
shadowRoot,
|
|
4542
4760
|
onAreaClick,
|
|
4543
4761
|
});
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks)
|
|
4547
|
-
return;
|
|
4548
|
-
if (autoCreateTopN <= 0)
|
|
4549
|
-
return;
|
|
4550
|
-
if (areas.length > 0)
|
|
4551
|
-
return; // Already have areas
|
|
4552
|
-
// Get top elements by clicks
|
|
4553
|
-
const topElements = getTopElementsByClicks(dataInfo.elementMapInfo, autoCreateTopN);
|
|
4554
|
-
// Build area nodes
|
|
4555
|
-
const newAreas = [];
|
|
4556
|
-
topElements.forEach(({ hash, selector }) => {
|
|
4557
|
-
// Find element in DOM
|
|
4558
|
-
const element = iframeDocument?.querySelector(selector);
|
|
4559
|
-
if (!element || !(element instanceof HTMLElement))
|
|
4560
|
-
return;
|
|
4561
|
-
const area = buildAreaNode(element, hash, dataInfo.elementMapInfo, dataInfo.totalClicks);
|
|
4562
|
-
newAreas.push(area);
|
|
4563
|
-
});
|
|
4564
|
-
// Add all areas
|
|
4565
|
-
newAreas.forEach((area) => addArea(area));
|
|
4566
|
-
}, [dataInfo, autoCreateTopN, areas.length, iframeDocument, shadowRoot]);
|
|
4567
|
-
// Apply overlap resolution
|
|
4568
|
-
const visibleAreas = useMemo(() => {
|
|
4569
|
-
if (!enableOverlapResolution)
|
|
4570
|
-
return areas;
|
|
4571
|
-
if (!iframeDocument)
|
|
4572
|
-
return areas;
|
|
4573
|
-
return getVisibleAreas(areas, iframeDocument);
|
|
4574
|
-
}, [areas, iframeDocument]);
|
|
4575
|
-
// Update visible areas in store when resolution changes
|
|
4762
|
+
useAreaTopAutoDetect({ iframeRef, autoCreateTopN, shadowRoot });
|
|
4763
|
+
useAreaFilterVisible({ iframeRef, enableOverlapResolution });
|
|
4576
4764
|
useEffect(() => {
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
}
|
|
4580
|
-
}, [
|
|
4581
|
-
const handleToggleEdit = useCallback(() => {
|
|
4582
|
-
setIsEditingMode(!isEditingMode);
|
|
4583
|
-
}, [isEditingMode]);
|
|
4584
|
-
const handleClearAll = useCallback(() => {
|
|
4585
|
-
if (window.confirm(`Clear all ${areas.length} areas?`)) {
|
|
4586
|
-
clearAreas();
|
|
4587
|
-
}
|
|
4588
|
-
}, [areas.length]);
|
|
4589
|
-
const controlsElement = renderControls ? (renderControls({
|
|
4590
|
-
isEditingMode,
|
|
4591
|
-
areasCount: areas.length,
|
|
4592
|
-
onToggleEdit: handleToggleEdit,
|
|
4593
|
-
onClearAll: handleClearAll,
|
|
4594
|
-
})) : (jsx(AreaControls, {}));
|
|
4765
|
+
return () => {
|
|
4766
|
+
resetView();
|
|
4767
|
+
};
|
|
4768
|
+
}, []);
|
|
4595
4769
|
if (!isReady)
|
|
4596
4770
|
return null;
|
|
4597
|
-
return (jsxs(Fragment, { children: [areasPortal, editHighlightPortal
|
|
4771
|
+
return (jsxs(Fragment, { children: [areasPortal, editHighlightPortal] }));
|
|
4598
4772
|
};
|
|
4599
4773
|
VizAreaClick.displayName = 'VizAreaClick';
|
|
4600
4774
|
|
|
@@ -4686,7 +4860,7 @@ const ElementMissing = ({ show = true }) => {
|
|
|
4686
4860
|
}, "aria-live": "assertive", children: "Element not visible on current screen" }));
|
|
4687
4861
|
};
|
|
4688
4862
|
|
|
4689
|
-
const ElementOverlay = ({ type, element, onClick,
|
|
4863
|
+
const ElementOverlay = ({ type, element, onClick, elementId }) => {
|
|
4690
4864
|
// useRenderCount('ElementOverlay');
|
|
4691
4865
|
const { widthScale } = useHeatmapViz();
|
|
4692
4866
|
if (!element || (element.width === 0 && element.height === 0))
|
|
@@ -4711,29 +4885,31 @@ const ELEMENT_CALLOUT = {
|
|
|
4711
4885
|
};
|
|
4712
4886
|
const HeatmapElements = (props) => {
|
|
4713
4887
|
const viewId = useViewIdContext();
|
|
4714
|
-
const { iframeHeight } = useHeatmapViz();
|
|
4715
4888
|
const clickedElementId = getClickedElementId(viewId, props.isSecondary);
|
|
4716
4889
|
const hoveredElementId = getHoveredElementId(viewId, props.isSecondary);
|
|
4717
|
-
const {
|
|
4718
|
-
const
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4890
|
+
const { iframeDimensions, isVisible = true, areDefaultRanksHidden } = props;
|
|
4891
|
+
const { iframeHeight } = useHeatmapViz();
|
|
4892
|
+
const { getRect } = useHeatmapElementPosition({
|
|
4893
|
+
iframeRef: props.iframeRef,
|
|
4894
|
+
wrapperRef: props.wrapperRef,
|
|
4895
|
+
visualizer: props.visualizer,
|
|
4722
4896
|
});
|
|
4723
4897
|
const { clickedElement, showMissingElement, shouldShowCallout } = useClickedElement({
|
|
4724
|
-
visualRef,
|
|
4898
|
+
visualRef: props.visualRef,
|
|
4725
4899
|
getRect,
|
|
4726
4900
|
});
|
|
4727
4901
|
const { hoveredElement, handleMouseMove, handleMouseLeave, handleClick } = useHoveredElement({
|
|
4728
|
-
iframeRef,
|
|
4902
|
+
iframeRef: props.iframeRef,
|
|
4729
4903
|
getRect,
|
|
4730
4904
|
});
|
|
4731
|
-
useElementCalloutVisible({ visualRef, getRect });
|
|
4905
|
+
useElementCalloutVisible({ visualRef: props.visualRef, getRect });
|
|
4732
4906
|
useHeatmapEffects({ isVisible });
|
|
4733
4907
|
useRenderCount('HeatmapElements');
|
|
4734
4908
|
if (!isVisible)
|
|
4735
4909
|
return null;
|
|
4736
|
-
|
|
4910
|
+
const isShowHoveredElement = hoveredElement && hoveredElement.hash !== clickedElement?.hash;
|
|
4911
|
+
const isShowClickedElement = shouldShowCallout && clickedElement;
|
|
4912
|
+
return (jsxs("div", { onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, className: "gx-hm-elements", style: { ...iframeDimensions, height: `${iframeHeight}px` }, children: [jsx(ElementMissing, { show: showMissingElement }), jsx(DefaultRankBadges, { getRect: getRect, hidden: areDefaultRanksHidden }), jsx(ElementOverlay, { type: "clicked", element: clickedElement, elementId: clickedElementId }), jsx(ElementOverlay, { type: "hovered", element: hoveredElement, elementId: hoveredElementId, onClick: handleClick }), isShowHoveredElement && (jsx(ElementCallout, { element: hoveredElement, target: `#${hoveredElementId}`, visualRef: props.visualRef, ...ELEMENT_CALLOUT })), isShowClickedElement && (jsx(ElementCallout, { element: clickedElement, target: `#${clickedElementId}`, visualRef: props.visualRef, ...ELEMENT_CALLOUT }))] }));
|
|
4737
4913
|
};
|
|
4738
4914
|
|
|
4739
4915
|
const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
|
|
@@ -4759,6 +4935,22 @@ const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
|
|
|
4759
4935
|
} }));
|
|
4760
4936
|
};
|
|
4761
4937
|
|
|
4938
|
+
const VizClickmap = ({ iframeRef, visualRef, wrapperRef }) => {
|
|
4939
|
+
const clickMode = useHeatmapConfigStore((state) => state.clickMode);
|
|
4940
|
+
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
4941
|
+
const isClickType = heatmapType === IHeatmapType.Click;
|
|
4942
|
+
if (!isClickType)
|
|
4943
|
+
return null;
|
|
4944
|
+
switch (clickMode) {
|
|
4945
|
+
case IClickMode.Default:
|
|
4946
|
+
return jsx(VizElements, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef });
|
|
4947
|
+
case IClickMode.Area:
|
|
4948
|
+
return (jsx(VizAreaClick, { iframeRef: iframeRef, visualRef: visualRef, autoCreateTopN: 0, onAreaClick: (area) => {
|
|
4949
|
+
console.log('area clicked', area);
|
|
4950
|
+
} }));
|
|
4951
|
+
}
|
|
4952
|
+
};
|
|
4953
|
+
|
|
4762
4954
|
const ScrollMapMinimap = ({ zones, maxUsers }) => {
|
|
4763
4955
|
const scrollType = useHeatmapConfigStore((state) => state.scrollType);
|
|
4764
4956
|
const { showMinimap } = useHeatmapScroll();
|
|
@@ -5084,12 +5276,11 @@ const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHe
|
|
|
5084
5276
|
|
|
5085
5277
|
const VizDomRenderer = ({ mode = 'heatmap' }) => {
|
|
5086
5278
|
const contentWidth = useHeatmapConfigStore((state) => state.width || 0);
|
|
5087
|
-
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
5088
5279
|
const wrapperRef = useRef(null);
|
|
5089
5280
|
const visualRef = useRef(null);
|
|
5090
5281
|
const { setSelectedElement } = useHeatmapClick();
|
|
5091
5282
|
const { iframeHeight, setIframeHeight, isRenderViz } = useHeatmapViz();
|
|
5092
|
-
const { iframeRef } =
|
|
5283
|
+
const { iframeRef } = useHeatmapRenderByMode(mode);
|
|
5093
5284
|
const { scaledHeight, handleScroll } = useHeatmapScale({
|
|
5094
5285
|
wrapperRef,
|
|
5095
5286
|
iframeRef,
|
|
@@ -5110,9 +5301,7 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
|
|
|
5110
5301
|
useEffect(() => {
|
|
5111
5302
|
return cleanUp;
|
|
5112
5303
|
}, []);
|
|
5113
|
-
return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [
|
|
5114
|
-
console.log('area clicked', area);
|
|
5115
|
-
} })), jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, scrolling: "no" }), jsx(VizScrollMap, { iframeRef: iframeRef, wrapperRef: visualRef })] }));
|
|
5304
|
+
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 })] }));
|
|
5116
5305
|
};
|
|
5117
5306
|
|
|
5118
5307
|
const VizLoading = () => {
|
|
@@ -5231,4 +5420,104 @@ const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
|
|
|
5231
5420
|
}
|
|
5232
5421
|
};
|
|
5233
5422
|
|
|
5234
|
-
|
|
5423
|
+
const AreaEditHighlight = ({ element, shadowRoot, onClick }) => {
|
|
5424
|
+
const [rect, setRect] = useState(null);
|
|
5425
|
+
const highlightRef = useRef(null);
|
|
5426
|
+
useEffect(() => {
|
|
5427
|
+
if (!element) {
|
|
5428
|
+
setRect(null);
|
|
5429
|
+
return;
|
|
5430
|
+
}
|
|
5431
|
+
const elementRect = getElementRect(element);
|
|
5432
|
+
setRect(elementRect);
|
|
5433
|
+
}, [element, shadowRoot]);
|
|
5434
|
+
if (!rect) {
|
|
5435
|
+
return null;
|
|
5436
|
+
}
|
|
5437
|
+
const handleClick = (e) => {
|
|
5438
|
+
if (element && onClick) {
|
|
5439
|
+
e.stopPropagation();
|
|
5440
|
+
e.preventDefault();
|
|
5441
|
+
onClick(element);
|
|
5442
|
+
}
|
|
5443
|
+
};
|
|
5444
|
+
return (jsx("div", { ref: highlightRef, id: AREA_HOVER_ELEMENT_ID, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: handleClick, style: {
|
|
5445
|
+
position: 'absolute',
|
|
5446
|
+
top: `${rect.absoluteTop}px`,
|
|
5447
|
+
left: `${rect.absoluteLeft}px`,
|
|
5448
|
+
width: `${rect.width}px`,
|
|
5449
|
+
height: `${rect.height}px`,
|
|
5450
|
+
zIndex: Number.MAX_SAFE_INTEGER,
|
|
5451
|
+
boxShadow: AREA_HOVER_BOX_SHADOW,
|
|
5452
|
+
backgroundColor: 'rgba(128, 128, 128, 0.4)',
|
|
5453
|
+
backgroundImage: 'repeating-linear-gradient(135deg, transparent, transparent 35px, rgba(255,255,255,.5) 35px, rgba(255,255,255,.5) 70px)',
|
|
5454
|
+
pointerEvents: 'auto',
|
|
5455
|
+
cursor: 'pointer',
|
|
5456
|
+
boxSizing: 'border-box',
|
|
5457
|
+
} }));
|
|
5458
|
+
};
|
|
5459
|
+
AreaEditHighlight.displayName = 'AreaEditHighlight';
|
|
5460
|
+
|
|
5461
|
+
const AreaLabel = ({ clickDist, totalClicks, kind }) => {
|
|
5462
|
+
if (kind === 'money') {
|
|
5463
|
+
return null;
|
|
5464
|
+
}
|
|
5465
|
+
return (jsxs("div", { style: {
|
|
5466
|
+
color: '#161514',
|
|
5467
|
+
backgroundColor: 'rgba(255, 255, 255, 0.86)',
|
|
5468
|
+
display: 'flex',
|
|
5469
|
+
flexDirection: 'column',
|
|
5470
|
+
alignItems: 'center',
|
|
5471
|
+
padding: '8px',
|
|
5472
|
+
borderRadius: '4px',
|
|
5473
|
+
fontSize: '16px',
|
|
5474
|
+
lineHeight: '20px',
|
|
5475
|
+
minWidth: '56px',
|
|
5476
|
+
fontWeight: 600,
|
|
5477
|
+
fontFamily: '"Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif',
|
|
5478
|
+
pointerEvents: 'none',
|
|
5479
|
+
}, children: [jsxs("span", { children: [clickDist.toFixed(2), "%"] }), jsxs("span", { style: { fontSize: '12px', fontWeight: 400, opacity: 0.8 }, children: [totalClicks, " clicks"] })] }));
|
|
5480
|
+
};
|
|
5481
|
+
AreaLabel.displayName = 'AreaLabel';
|
|
5482
|
+
|
|
5483
|
+
const AreaOverlay = ({ area, onClick, onMouseEnter, onMouseLeave, isSelected, isHovered, }) => {
|
|
5484
|
+
const [rect, setRect] = useState(area.rect.value);
|
|
5485
|
+
useEffect(() => {
|
|
5486
|
+
const handleRectChange = (newRect) => {
|
|
5487
|
+
if (newRect) {
|
|
5488
|
+
setRect(newRect);
|
|
5489
|
+
}
|
|
5490
|
+
};
|
|
5491
|
+
area.rect.observe(handleRectChange);
|
|
5492
|
+
return () => {
|
|
5493
|
+
area.rect.unobserve(handleRectChange);
|
|
5494
|
+
};
|
|
5495
|
+
}, [area.rect]);
|
|
5496
|
+
if (!rect)
|
|
5497
|
+
return null;
|
|
5498
|
+
const position = area.isFixed ? 'fixed' : 'absolute';
|
|
5499
|
+
const showLabel = !isRectTooSmallForLabel(rect);
|
|
5500
|
+
const backgroundColor = isHovered ? area.hoverColor : area.color;
|
|
5501
|
+
const boxShadow = isSelected ? '0 0 0 3px red inset' : isHovered ? AREA_HOVER_BOX_SHADOW : '0 0 0 2px white inset';
|
|
5502
|
+
return (jsxs("div", { id: `area-${area.id}`, "data-area-id": area.id, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: () => onClick?.(area), onMouseEnter: () => onMouseEnter?.(area), onMouseLeave: () => onMouseLeave?.(area), children: [jsx("style", { children: `
|
|
5503
|
+
#area-${area.id} {
|
|
5504
|
+
position: ${position};
|
|
5505
|
+
top: ${rect.top}px;
|
|
5506
|
+
left: ${rect.left}px;
|
|
5507
|
+
width: ${rect.width}px;
|
|
5508
|
+
height: ${rect.height}px;
|
|
5509
|
+
background-color: ${backgroundColor};
|
|
5510
|
+
box-shadow: ${boxShadow};
|
|
5511
|
+
box-sizing: border-box;
|
|
5512
|
+
display: flex;
|
|
5513
|
+
align-items: center;
|
|
5514
|
+
justify-content: center;
|
|
5515
|
+
cursor: pointer;
|
|
5516
|
+
transition: background-color 0.2s, box-shadow 0.2s;
|
|
5517
|
+
pointer-events: auto;
|
|
5518
|
+
z-index: ${Number.MAX_SAFE_INTEGER};
|
|
5519
|
+
}
|
|
5520
|
+
` }), showLabel && jsx(AreaLabel, { clickDist: area.clickDist, totalClicks: area.totalclicks, kind: area.kind })] }));
|
|
5521
|
+
};
|
|
5522
|
+
|
|
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 };
|