@gemx-dev/heatmap-react 3.5.52 → 3.5.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/Layout/HeatmapLayout.d.ts +2 -1
- package/dist/esm/components/Layout/HeatmapLayout.d.ts.map +1 -1
- package/dist/esm/components/Layout/HeatmapPreview.d.ts.map +1 -1
- package/dist/esm/components/Layout/TopBar/ContentTopBar.d.ts.map +1 -1
- package/dist/esm/components/Layout/VizByMode/ContentVizByMode.d.ts.map +1 -1
- package/dist/esm/components/VizAreaClick/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/VizDomHeatmap.d.ts.map +1 -1
- package/dist/esm/components/VizDom/VizDomRenderer.d.ts.map +1 -1
- package/dist/esm/components/VizElement/DefaultRankBadges.d.ts.map +1 -1
- package/dist/esm/components/VizElement/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/VizElement/RankBadge.d.ts.map +1 -1
- package/dist/esm/components/VizScrollmap/HoverZones.d.ts.map +1 -1
- package/dist/esm/components/VizScrollmap/ScrollMapOverlay.d.ts.map +1 -1
- package/dist/esm/components/VizScrollmapV2/ScrollmapOverlayV2.d.ts.map +1 -1
- package/dist/esm/components/VizScrollmapV2/useScrollmapOverlay.d.ts.map +1 -1
- package/dist/esm/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-graph.d.ts +63 -0
- package/dist/esm/helpers/viz-area-click/area-graph.d.ts.map +1 -0
- package/dist/esm/helpers/viz-area-click/area-hydration.d.ts +36 -0
- package/dist/esm/helpers/viz-area-click/area-hydration.d.ts.map +1 -0
- package/dist/esm/helpers/viz-area-click/area-overlap.d.ts.map +1 -1
- package/dist/esm/helpers/viz-area-click/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 +3 -0
- package/dist/esm/helpers/viz-area-click/index.d.ts.map +1 -1
- package/dist/esm/helpers/viz-canvas/area-clustering.d.ts.map +1 -1
- package/dist/esm/helpers/viz-canvas/area-overlay-manager-v2.d.ts.map +1 -1
- package/dist/esm/helpers/viz-canvas/hierarchical-area-clustering.d.ts.map +1 -1
- package/dist/esm/helpers/viz-dom/find-elm.d.ts +8 -0
- package/dist/esm/helpers/viz-dom/find-elm.d.ts.map +1 -0
- package/dist/esm/helpers/viz-dom/index.d.ts +1 -0
- package/dist/esm/helpers/viz-dom/index.d.ts.map +1 -1
- package/dist/esm/helpers/viz-elm-callout/dimensions.d.ts.map +1 -1
- package/dist/esm/helpers/viz-elm-callout/getter.d.ts +0 -2
- package/dist/esm/helpers/viz-elm-callout/getter.d.ts.map +1 -1
- package/dist/esm/helpers/viz-elm-callout/position-candidates.d.ts.map +1 -1
- package/dist/esm/helpers/viz-elm-callout/position-selector.d.ts.map +1 -1
- package/dist/esm/helpers/viz-elm-callout/position-validator.d.ts.map +1 -1
- package/dist/esm/hooks/register/useRegisterConfig.d.ts.map +1 -1
- package/dist/esm/hooks/register/useRegisterHeatmap.d.ts +3 -2
- package/dist/esm/hooks/register/useRegisterHeatmap.d.ts.map +1 -1
- package/dist/esm/hooks/view-context/useHeatmapAreaClick.d.ts +1 -0
- package/dist/esm/hooks/view-context/useHeatmapAreaClick.d.ts.map +1 -1
- package/dist/esm/hooks/view-context/useHeatmapClick.d.ts.map +1 -1
- package/dist/esm/hooks/view-context/useHeatmapData.d.ts +4 -1
- package/dist/esm/hooks/view-context/useHeatmapData.d.ts.map +1 -1
- package/dist/esm/hooks/viz-area-click/index.d.ts +8 -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/useAreaHydration.d.ts +9 -0
- package/dist/esm/hooks/viz-area-click/useAreaHydration.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaInteraction.d.ts +11 -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-canvas/useScrollmap.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/hooks/viz-scale/useContainerDimensions.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useContentDimensions.d.ts +1 -1
- package/dist/esm/hooks/viz-scale/useContentDimensions.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts +0 -3
- package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useObserveIframeHeight.d.ts +0 -1
- package/dist/esm/hooks/viz-scale/useObserveIframeHeight.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useScaleCalculation.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useScrollSync.d.ts +1 -1
- package/dist/esm/hooks/viz-scale/useScrollSync.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useWrapperRefHeight.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scroll/useScrollmapZones.d.ts.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1324 -766
- package/dist/esm/index.mjs +1324 -766
- package/dist/esm/performance/hooks.d.ts.map +1 -1
- package/dist/esm/performance/performance-logger.d.ts.map +1 -1
- package/dist/esm/performance/types.d.ts.map +1 -1
- package/dist/esm/performance/utils.d.ts.map +1 -1
- package/dist/esm/stores/comp.d.ts.map +1 -1
- package/dist/esm/stores/config.d.ts +3 -1
- package/dist/esm/stores/config.d.ts.map +1 -1
- package/dist/esm/stores/data.d.ts +4 -1
- package/dist/esm/stores/data.d.ts.map +1 -1
- package/dist/esm/stores/mode-compare.d.ts.map +1 -1
- package/dist/esm/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/esm/types/viz-area-click.d.ts +14 -0
- package/dist/esm/types/viz-area-click.d.ts.map +1 -1
- package/dist/esm/ui/BoxStack/BoxStack.d.ts.map +1 -1
- package/dist/esm/utils/retry.d.ts.map +1 -1
- package/dist/style.css +9 -11
- package/dist/umd/components/Layout/HeatmapLayout.d.ts +2 -1
- package/dist/umd/components/Layout/HeatmapLayout.d.ts.map +1 -1
- package/dist/umd/components/Layout/HeatmapPreview.d.ts.map +1 -1
- package/dist/umd/components/Layout/TopBar/ContentTopBar.d.ts.map +1 -1
- package/dist/umd/components/Layout/VizByMode/ContentVizByMode.d.ts.map +1 -1
- package/dist/umd/components/VizAreaClick/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/VizDomHeatmap.d.ts.map +1 -1
- package/dist/umd/components/VizDom/VizDomRenderer.d.ts.map +1 -1
- package/dist/umd/components/VizElement/DefaultRankBadges.d.ts.map +1 -1
- package/dist/umd/components/VizElement/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/VizElement/RankBadge.d.ts.map +1 -1
- package/dist/umd/components/VizScrollmap/HoverZones.d.ts.map +1 -1
- package/dist/umd/components/VizScrollmap/ScrollMapOverlay.d.ts.map +1 -1
- package/dist/umd/components/VizScrollmapV2/ScrollmapOverlayV2.d.ts.map +1 -1
- package/dist/umd/components/VizScrollmapV2/useScrollmapOverlay.d.ts.map +1 -1
- package/dist/umd/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-graph.d.ts +63 -0
- package/dist/umd/helpers/viz-area-click/area-graph.d.ts.map +1 -0
- package/dist/umd/helpers/viz-area-click/area-hydration.d.ts +36 -0
- package/dist/umd/helpers/viz-area-click/area-hydration.d.ts.map +1 -0
- package/dist/umd/helpers/viz-area-click/area-overlap.d.ts.map +1 -1
- package/dist/umd/helpers/viz-area-click/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 +3 -0
- package/dist/umd/helpers/viz-area-click/index.d.ts.map +1 -1
- package/dist/umd/helpers/viz-canvas/area-clustering.d.ts.map +1 -1
- package/dist/umd/helpers/viz-canvas/area-overlay-manager-v2.d.ts.map +1 -1
- package/dist/umd/helpers/viz-canvas/hierarchical-area-clustering.d.ts.map +1 -1
- package/dist/umd/helpers/viz-dom/find-elm.d.ts +8 -0
- package/dist/umd/helpers/viz-dom/find-elm.d.ts.map +1 -0
- package/dist/umd/helpers/viz-dom/index.d.ts +1 -0
- package/dist/umd/helpers/viz-dom/index.d.ts.map +1 -1
- package/dist/umd/helpers/viz-elm-callout/dimensions.d.ts.map +1 -1
- package/dist/umd/helpers/viz-elm-callout/getter.d.ts +0 -2
- package/dist/umd/helpers/viz-elm-callout/getter.d.ts.map +1 -1
- package/dist/umd/helpers/viz-elm-callout/position-candidates.d.ts.map +1 -1
- package/dist/umd/helpers/viz-elm-callout/position-selector.d.ts.map +1 -1
- package/dist/umd/helpers/viz-elm-callout/position-validator.d.ts.map +1 -1
- package/dist/umd/hooks/register/useRegisterConfig.d.ts.map +1 -1
- package/dist/umd/hooks/register/useRegisterHeatmap.d.ts +3 -2
- package/dist/umd/hooks/register/useRegisterHeatmap.d.ts.map +1 -1
- package/dist/umd/hooks/view-context/useHeatmapAreaClick.d.ts +1 -0
- package/dist/umd/hooks/view-context/useHeatmapAreaClick.d.ts.map +1 -1
- package/dist/umd/hooks/view-context/useHeatmapClick.d.ts.map +1 -1
- package/dist/umd/hooks/view-context/useHeatmapData.d.ts +4 -1
- package/dist/umd/hooks/view-context/useHeatmapData.d.ts.map +1 -1
- package/dist/umd/hooks/viz-area-click/index.d.ts +8 -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/useAreaHydration.d.ts +9 -0
- package/dist/umd/hooks/viz-area-click/useAreaHydration.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaInteraction.d.ts +11 -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-canvas/useScrollmap.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/hooks/viz-scale/useContainerDimensions.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useContentDimensions.d.ts +1 -1
- package/dist/umd/hooks/viz-scale/useContentDimensions.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts +0 -3
- package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useObserveIframeHeight.d.ts +0 -1
- package/dist/umd/hooks/viz-scale/useObserveIframeHeight.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useScaleCalculation.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useScrollSync.d.ts +1 -1
- package/dist/umd/hooks/viz-scale/useScrollSync.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useWrapperRefHeight.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scroll/useScrollmapZones.d.ts.map +1 -1
- package/dist/umd/index.d.ts +1 -0
- package/dist/umd/index.d.ts.map +1 -1
- package/dist/umd/index.js +2 -2
- package/dist/umd/performance/hooks.d.ts.map +1 -1
- package/dist/umd/performance/performance-logger.d.ts.map +1 -1
- package/dist/umd/performance/types.d.ts.map +1 -1
- package/dist/umd/performance/utils.d.ts.map +1 -1
- package/dist/umd/stores/comp.d.ts.map +1 -1
- package/dist/umd/stores/config.d.ts +3 -1
- package/dist/umd/stores/config.d.ts.map +1 -1
- package/dist/umd/stores/data.d.ts +4 -1
- package/dist/umd/stores/data.d.ts.map +1 -1
- package/dist/umd/stores/mode-compare.d.ts.map +1 -1
- package/dist/umd/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/dist/umd/types/viz-area-click.d.ts +14 -0
- package/dist/umd/types/viz-area-click.d.ts.map +1 -1
- package/dist/umd/ui/BoxStack/BoxStack.d.ts.map +1 -1
- package/dist/umd/utils/retry.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/esm/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
|
};
|
|
@@ -179,8 +186,20 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
|
|
|
179
186
|
return {
|
|
180
187
|
data: { [DEFAULT_VIEW_ID]: undefined },
|
|
181
188
|
clickmap: { [DEFAULT_VIEW_ID]: undefined },
|
|
189
|
+
clickAreas: { [DEFAULT_VIEW_ID]: undefined },
|
|
182
190
|
dataInfo: { [DEFAULT_VIEW_ID]: undefined },
|
|
183
191
|
scrollmap: { [DEFAULT_VIEW_ID]: undefined },
|
|
192
|
+
setClickAreas: (clickAreas, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
193
|
+
clickAreas: { ...state.clickAreas, [viewId]: clickAreas },
|
|
194
|
+
})),
|
|
195
|
+
removeClickArea: (areaId, viewId = DEFAULT_VIEW_ID) => set((state) => {
|
|
196
|
+
const currentAreas = state.clickAreas[viewId] || [];
|
|
197
|
+
const filtered = currentAreas.filter((a) => a.id !== areaId);
|
|
198
|
+
console.log(`🚀 🐥 ~ filtered:`, filtered);
|
|
199
|
+
return {
|
|
200
|
+
clickAreas: { ...state.clickAreas, [viewId]: filtered },
|
|
201
|
+
};
|
|
202
|
+
}),
|
|
184
203
|
setDataInfo: (dataInfo, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
185
204
|
dataInfo: { ...state.dataInfo, [viewId]: dataInfo },
|
|
186
205
|
})),
|
|
@@ -196,21 +215,25 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
|
|
|
196
215
|
copyView: (fromViewId, toViewId) => set((state) => ({
|
|
197
216
|
data: { ...state.data, [toViewId]: state.data[fromViewId] },
|
|
198
217
|
clickmap: { ...state.clickmap, [toViewId]: state.clickmap[fromViewId] },
|
|
218
|
+
clickAreas: { ...state.clickAreas, [toViewId]: state.clickAreas[fromViewId] },
|
|
199
219
|
dataInfo: { ...state.dataInfo, [toViewId]: state.dataInfo[fromViewId] },
|
|
200
220
|
scrollmap: { ...state.scrollmap, [toViewId]: state.scrollmap[fromViewId] },
|
|
201
221
|
})),
|
|
202
222
|
clearView: (viewId) => set((state) => {
|
|
203
223
|
const newData = { ...state.data };
|
|
204
224
|
const newClickmap = { ...state.clickmap };
|
|
225
|
+
const newClickAreas = { ...state.clickAreas };
|
|
205
226
|
const newDataInfo = { ...state.dataInfo };
|
|
206
227
|
const newScrollmap = { ...state.scrollmap };
|
|
207
228
|
delete newData[viewId];
|
|
208
229
|
delete newClickmap[viewId];
|
|
230
|
+
delete newClickAreas[viewId];
|
|
209
231
|
delete newDataInfo[viewId];
|
|
210
232
|
delete newScrollmap[viewId];
|
|
211
233
|
return {
|
|
212
234
|
data: newData,
|
|
213
235
|
clickmap: newClickmap,
|
|
236
|
+
clickAreas: newClickAreas,
|
|
214
237
|
dataInfo: newDataInfo,
|
|
215
238
|
scrollmap: newScrollmap,
|
|
216
239
|
};
|
|
@@ -218,6 +241,7 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
|
|
|
218
241
|
resetAll: () => set({
|
|
219
242
|
data: { [DEFAULT_VIEW_ID]: undefined },
|
|
220
243
|
clickmap: { [DEFAULT_VIEW_ID]: undefined },
|
|
244
|
+
clickAreas: { [DEFAULT_VIEW_ID]: undefined },
|
|
221
245
|
dataInfo: { [DEFAULT_VIEW_ID]: undefined },
|
|
222
246
|
scrollmap: { [DEFAULT_VIEW_ID]: undefined },
|
|
223
247
|
}),
|
|
@@ -335,7 +359,7 @@ const useHeatmapAreaClickStore = create()(subscribeWithSelector((set) => ({
|
|
|
335
359
|
}),
|
|
336
360
|
updateArea: (areaId, updates, viewId = DEFAULT_VIEW_ID) => set((state) => {
|
|
337
361
|
const currentAreas = state.areas[viewId] || [];
|
|
338
|
-
const updatedAreas = currentAreas.map((area) => area.id === areaId ? { ...area, ...updates } : area);
|
|
362
|
+
const updatedAreas = currentAreas.map((area) => (area.id === areaId ? { ...area, ...updates } : area));
|
|
339
363
|
return {
|
|
340
364
|
areas: { ...state.areas, [viewId]: updatedAreas },
|
|
341
365
|
};
|
|
@@ -379,6 +403,11 @@ const useHeatmapAreaClickStore = create()(subscribeWithSelector((set) => ({
|
|
|
379
403
|
isEditingMode: newIsEditingMode,
|
|
380
404
|
};
|
|
381
405
|
}),
|
|
406
|
+
resetView: (viewId) => set((state) => ({
|
|
407
|
+
selectedArea: { ...state.selectedArea, [viewId]: null },
|
|
408
|
+
hoveredArea: { ...state.hoveredArea, [viewId]: null },
|
|
409
|
+
isEditingMode: { ...state.isEditingMode, [viewId]: false },
|
|
410
|
+
})),
|
|
382
411
|
resetAll: () => set({
|
|
383
412
|
selectedArea: { [DEFAULT_VIEW_ID]: null },
|
|
384
413
|
hoveredArea: { [DEFAULT_VIEW_ID]: null },
|
|
@@ -533,9 +562,13 @@ const useHeatmapCompareStore = create()((set, get) => {
|
|
|
533
562
|
const state = get();
|
|
534
563
|
const newViews = new Map(state.views);
|
|
535
564
|
newViews.delete(viewId);
|
|
565
|
+
const newViewIdCounter = newViews.size;
|
|
566
|
+
const newLayout = newViews.size === 2 ? 'grid-2' : state.layout;
|
|
536
567
|
set({
|
|
537
568
|
views: newViews,
|
|
538
569
|
viewOrder: state.viewOrder.filter((id) => id !== viewId),
|
|
570
|
+
viewIdCounter: newViewIdCounter,
|
|
571
|
+
layout: newLayout,
|
|
539
572
|
});
|
|
540
573
|
},
|
|
541
574
|
updateView: (viewId, updates) => {
|
|
@@ -680,13 +713,14 @@ const useRegisterConfig = () => {
|
|
|
680
713
|
const width = useHeatmapConfigStore((state) => state.width);
|
|
681
714
|
const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
|
|
682
715
|
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
716
|
+
const clickMode = useHeatmapConfigStore((state) => state.clickMode);
|
|
683
717
|
const setIsRendering = useHeatmapConfigStore((state) => state.setIsRendering);
|
|
684
718
|
useEffect(() => {
|
|
685
719
|
setIsRendering(true);
|
|
686
720
|
setTimeout(() => {
|
|
687
721
|
setIsRendering(false);
|
|
688
722
|
}, 1000);
|
|
689
|
-
}, [mode, width, sidebarWidth, heatmapType]);
|
|
723
|
+
}, [mode, width, sidebarWidth, heatmapType, clickMode]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
690
724
|
};
|
|
691
725
|
|
|
692
726
|
const useRegisterControl = (control) => {
|
|
@@ -722,6 +756,7 @@ const useHeatmapAreaClick = (props) => {
|
|
|
722
756
|
const removeAreaStore = useHeatmapAreaClickStore((state) => state.removeArea);
|
|
723
757
|
const updateAreaStore = useHeatmapAreaClickStore((state) => state.updateArea);
|
|
724
758
|
const clearAreasStore = useHeatmapAreaClickStore((state) => state.clearAreas);
|
|
759
|
+
const resetViewStore = useHeatmapAreaClickStore((state) => state.resetView);
|
|
725
760
|
// Memoize operations to prevent unnecessary re-renders
|
|
726
761
|
const memoizedOperations = useMemo(() => ({
|
|
727
762
|
setSelectedArea: (area) => setSelectedAreaStore(area, viewId),
|
|
@@ -732,6 +767,7 @@ const useHeatmapAreaClick = (props) => {
|
|
|
732
767
|
removeArea: (areaId) => removeAreaStore(areaId, viewId),
|
|
733
768
|
updateArea: (areaId, updates) => updateAreaStore(areaId, updates, viewId),
|
|
734
769
|
clearAreas: () => clearAreasStore(viewId),
|
|
770
|
+
resetView: () => resetViewStore(viewId),
|
|
735
771
|
}), [
|
|
736
772
|
setSelectedAreaStore,
|
|
737
773
|
setHoveredAreaStore,
|
|
@@ -741,6 +777,7 @@ const useHeatmapAreaClick = (props) => {
|
|
|
741
777
|
removeAreaStore,
|
|
742
778
|
updateAreaStore,
|
|
743
779
|
clearAreasStore,
|
|
780
|
+
resetViewStore,
|
|
744
781
|
viewId,
|
|
745
782
|
]);
|
|
746
783
|
return {
|
|
@@ -767,13 +804,7 @@ const useHeatmapClick = (props) => {
|
|
|
767
804
|
setSelectedElement: (element) => setSelectedElementStore(element, viewId),
|
|
768
805
|
setHoveredElement: (element) => setHoveredElementStore(element, viewId),
|
|
769
806
|
setShouldShowCallout: (value) => setShouldShowCalloutStore(value, viewId),
|
|
770
|
-
}), [
|
|
771
|
-
setStateStore,
|
|
772
|
-
setSelectedElementStore,
|
|
773
|
-
setHoveredElementStore,
|
|
774
|
-
setShouldShowCalloutStore,
|
|
775
|
-
viewId,
|
|
776
|
-
]);
|
|
807
|
+
}), [setStateStore, setSelectedElementStore, setHoveredElementStore, setShouldShowCalloutStore, viewId]);
|
|
777
808
|
return {
|
|
778
809
|
state,
|
|
779
810
|
selectedElement,
|
|
@@ -788,24 +819,29 @@ const useHeatmapData = (props) => {
|
|
|
788
819
|
const viewId = props?.viewId || useViewIdContext();
|
|
789
820
|
const data = useHeatmapDataStore((state) => state.data[viewId]);
|
|
790
821
|
const clickmap = useHeatmapDataStore((state) => state.clickmap[viewId]);
|
|
822
|
+
const clickAreas = useHeatmapDataStore((state) => state.clickAreas[viewId]);
|
|
791
823
|
const scrollmap = useHeatmapDataStore((state) => state.scrollmap[viewId]);
|
|
792
824
|
const dataInfo = useHeatmapDataStore((state) => state.dataInfo[viewId]);
|
|
793
825
|
const setData = useHeatmapDataStore((state) => state.setData);
|
|
794
826
|
const setClickmap = useHeatmapDataStore((state) => state.setClickmap);
|
|
827
|
+
const setClickAreas = useHeatmapDataStore((state) => state.setClickAreas);
|
|
795
828
|
const setScrollmap = useHeatmapDataStore((state) => state.setScrollmap);
|
|
796
829
|
const setDataInfo = useHeatmapDataStore((state) => state.setDataInfo);
|
|
830
|
+
const removeClickArea = useHeatmapDataStore((state) => state.removeClickArea);
|
|
797
831
|
const memoizedSetters = useMemo(() => ({
|
|
798
832
|
setData: (newData) => setData(newData, viewId),
|
|
799
833
|
setClickmap: (newClickmap) => setClickmap(newClickmap, viewId),
|
|
834
|
+
setClickAreas: (newClickAreas) => setClickAreas(newClickAreas, viewId),
|
|
800
835
|
setScrollmap: (newScrollmap) => setScrollmap(newScrollmap, viewId),
|
|
801
836
|
setDataInfo: (newDataInfo) => setDataInfo(newDataInfo, viewId),
|
|
802
|
-
|
|
837
|
+
removeClickArea: (areaId) => removeClickArea(areaId, viewId),
|
|
838
|
+
}), [setData, setClickmap, setClickAreas, setScrollmap, setDataInfo, removeClickArea, viewId]);
|
|
803
839
|
return {
|
|
804
840
|
data,
|
|
805
841
|
clickmap,
|
|
842
|
+
clickAreas,
|
|
806
843
|
scrollmap,
|
|
807
844
|
dataInfo,
|
|
808
|
-
// Setters (auto-inject viewId)
|
|
809
845
|
...memoizedSetters,
|
|
810
846
|
};
|
|
811
847
|
};
|
|
@@ -978,24 +1014,32 @@ const useRegisterData = (data, dataInfo) => {
|
|
|
978
1014
|
}, [dataInfo]);
|
|
979
1015
|
};
|
|
980
1016
|
|
|
981
|
-
const useRegisterHeatmap = ({ clickmap, scrollmap }) => {
|
|
982
|
-
const { setClickmap, setScrollmap } = useHeatmapData();
|
|
983
|
-
const handleSetClickmap = useCallback((
|
|
1017
|
+
const useRegisterHeatmap = ({ clickmap, scrollmap, clickAreas }) => {
|
|
1018
|
+
const { setClickmap, setScrollmap, setClickAreas } = useHeatmapData();
|
|
1019
|
+
const handleSetClickmap = useCallback(() => {
|
|
984
1020
|
if (!clickmap)
|
|
985
1021
|
return;
|
|
986
1022
|
setClickmap(clickmap);
|
|
987
|
-
}, [clickmap]);
|
|
988
|
-
const
|
|
1023
|
+
}, [clickmap]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
1024
|
+
const handleSetClickAreas = useCallback(() => {
|
|
1025
|
+
if (!clickAreas)
|
|
1026
|
+
return;
|
|
1027
|
+
setClickAreas(clickAreas);
|
|
1028
|
+
}, [clickAreas]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
1029
|
+
const handleSetScrollmap = useCallback(() => {
|
|
989
1030
|
if (!scrollmap)
|
|
990
1031
|
return;
|
|
991
1032
|
setScrollmap(scrollmap);
|
|
992
|
-
}, [scrollmap]);
|
|
1033
|
+
}, [scrollmap]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
993
1034
|
useEffect(() => {
|
|
994
|
-
handleSetClickmap(
|
|
995
|
-
}, [
|
|
1035
|
+
handleSetClickmap();
|
|
1036
|
+
}, [handleSetClickmap]);
|
|
996
1037
|
useEffect(() => {
|
|
997
|
-
|
|
998
|
-
}, [
|
|
1038
|
+
handleSetClickAreas();
|
|
1039
|
+
}, [handleSetClickAreas]);
|
|
1040
|
+
useEffect(() => {
|
|
1041
|
+
handleSetScrollmap();
|
|
1042
|
+
}, [handleSetScrollmap]);
|
|
999
1043
|
};
|
|
1000
1044
|
|
|
1001
1045
|
/**
|
|
@@ -1046,9 +1090,83 @@ function isElementInViewport(elementRect, visualRef, scale) {
|
|
|
1046
1090
|
return elementBottom > viewportTop && elementTop < viewportBottom;
|
|
1047
1091
|
}
|
|
1048
1092
|
|
|
1093
|
+
const CLARITY_HEATMAP_CANVAS_ID = 'clarity-heatmap-canvas';
|
|
1094
|
+
const HEATMAP_ELEMENT_ATTRIBUTE = 'data-clarity-hashalpha';
|
|
1095
|
+
function isIgnoredCanvas(element) {
|
|
1096
|
+
if (element.tagName === 'CANVAS') {
|
|
1097
|
+
return true;
|
|
1098
|
+
}
|
|
1099
|
+
if (element.id === CLARITY_HEATMAP_CANVAS_ID) {
|
|
1100
|
+
return true;
|
|
1101
|
+
}
|
|
1102
|
+
return false;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Get all elements at a specific point (x, y), with support for Shadow DOM
|
|
1107
|
+
*/
|
|
1108
|
+
function getElementsAtPoint(doc, x, y, options = {}) {
|
|
1109
|
+
const { filterFn, ignoreCanvas = true, visitedShadowRoots = new Set() } = options;
|
|
1110
|
+
// Get all elements at this point
|
|
1111
|
+
let elementsAtPoint = doc.elementsFromPoint(x, y);
|
|
1112
|
+
// Filter out canvas elements if requested
|
|
1113
|
+
if (ignoreCanvas) {
|
|
1114
|
+
elementsAtPoint = elementsAtPoint.filter((el) => !isIgnoredCanvas(el));
|
|
1115
|
+
}
|
|
1116
|
+
// Apply custom filter if provided
|
|
1117
|
+
if (filterFn) {
|
|
1118
|
+
const matchedElement = elementsAtPoint.find(filterFn);
|
|
1119
|
+
// If matched element has Shadow DOM and we haven't visited it yet, recurse
|
|
1120
|
+
if (matchedElement?.shadowRoot && !visitedShadowRoots.has(matchedElement.shadowRoot)) {
|
|
1121
|
+
visitedShadowRoots.add(matchedElement.shadowRoot);
|
|
1122
|
+
return getElementsAtPoint(matchedElement.shadowRoot, x, y, {
|
|
1123
|
+
...options,
|
|
1124
|
+
visitedShadowRoots,
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return elementsAtPoint;
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Get the element at a specific point (x, y)
|
|
1132
|
+
*/
|
|
1133
|
+
const getElementAtPoint = (doc, x, y) => {
|
|
1134
|
+
let el = null;
|
|
1135
|
+
if ('caretPositionFromPoint' in doc) {
|
|
1136
|
+
el = doc.caretPositionFromPoint(x, y)?.offsetNode ?? null;
|
|
1137
|
+
}
|
|
1138
|
+
el = el ?? doc.elementFromPoint(x, y);
|
|
1139
|
+
let element = el;
|
|
1140
|
+
while (element && element.nodeType === Node.TEXT_NODE) {
|
|
1141
|
+
element = element.parentElement;
|
|
1142
|
+
}
|
|
1143
|
+
return element;
|
|
1144
|
+
};
|
|
1145
|
+
function getElementHash(element) {
|
|
1146
|
+
return (element.getAttribute('data-clarity-hash') ||
|
|
1147
|
+
element.getAttribute('data-clarity-hashalpha') ||
|
|
1148
|
+
element.getAttribute('data-clarity-hashbeta'));
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1049
1151
|
const AREA_HOVER_BOX_SHADOW = '0 0 0 1px #0078D4, 0 0 0 1px #0078D4 inset, 0 0 0 2px white inset';
|
|
1050
1152
|
const AREA_HOVER_ELEMENT_ID = 'clarity-edit-hover';
|
|
1051
1153
|
const AREA_MAP_DIV_ATTRIBUTE = 'data-clarity-area-map-div';
|
|
1154
|
+
const HEATMAP_AREA_CONTAINER_CLASS = 'heatmap-area-container';
|
|
1155
|
+
const HEATMAP_AREA_CONTAINER_SELECTOR = `.${HEATMAP_AREA_CONTAINER_CLASS}`;
|
|
1156
|
+
const AREA_CONTAINER_STYLES = `
|
|
1157
|
+
position: absolute;
|
|
1158
|
+
top: 0;
|
|
1159
|
+
left: 0;
|
|
1160
|
+
width: 100%;
|
|
1161
|
+
height: 100%;
|
|
1162
|
+
pointer-events: none;
|
|
1163
|
+
z-index: 999999;
|
|
1164
|
+
`;
|
|
1165
|
+
const AREA_INNER_CONTAINER_STYLES = `
|
|
1166
|
+
position: relative;
|
|
1167
|
+
width: 100%;
|
|
1168
|
+
height: 100%;
|
|
1169
|
+
`;
|
|
1052
1170
|
const AREA_COLOR_GRADIENT = [
|
|
1053
1171
|
[0, 0, 255], // Blue
|
|
1054
1172
|
[0, 255, 255], // Cyan
|
|
@@ -1056,6 +1174,12 @@ const AREA_COLOR_GRADIENT = [
|
|
|
1056
1174
|
[255, 255, 0], // Yellow
|
|
1057
1175
|
[255, 0, 0], // Red
|
|
1058
1176
|
];
|
|
1177
|
+
const AREA_RENDERER_SELECTORS = {
|
|
1178
|
+
containerAttribute: AREA_MAP_DIV_ATTRIBUTE,
|
|
1179
|
+
containerSelector: `[${AREA_MAP_DIV_ATTRIBUTE}]`,
|
|
1180
|
+
innerContainerClass: HEATMAP_AREA_CONTAINER_CLASS,
|
|
1181
|
+
innerContainerSelector: HEATMAP_AREA_CONTAINER_SELECTOR,
|
|
1182
|
+
};
|
|
1059
1183
|
|
|
1060
1184
|
const CALLOUT_PADDING = 0;
|
|
1061
1185
|
const CALLOUT_ARROW_SIZE = 8;
|
|
@@ -1100,27 +1224,15 @@ function calculateClickDistribution(elementClicks, totalClicks) {
|
|
|
1100
1224
|
return (elementClicks / totalClicks) * 100;
|
|
1101
1225
|
}
|
|
1102
1226
|
|
|
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
1227
|
function getElementRect(element, _shadowRoot) {
|
|
1111
1228
|
const rect = element.getBoundingClientRect();
|
|
1112
1229
|
const width = rect.width;
|
|
1113
1230
|
const height = rect.height;
|
|
1114
|
-
// Get the document to access scroll position
|
|
1115
1231
|
const doc = element.ownerDocument || document;
|
|
1116
|
-
// Get scroll offset from documentElement or body
|
|
1117
1232
|
const scrollTop = doc.documentElement?.scrollTop || doc.body?.scrollTop || 0;
|
|
1118
1233
|
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
1234
|
const top = rect.top + scrollTop;
|
|
1122
1235
|
const left = rect.left + scrollLeft;
|
|
1123
|
-
// For absolute positioning calculations (overlap detection)
|
|
1124
1236
|
const absoluteLeft = left;
|
|
1125
1237
|
const absoluteTop = top;
|
|
1126
1238
|
const absoluteRight = absoluteLeft + width;
|
|
@@ -1137,9 +1249,6 @@ function getElementRect(element, _shadowRoot) {
|
|
|
1137
1249
|
outOfBounds: false,
|
|
1138
1250
|
};
|
|
1139
1251
|
}
|
|
1140
|
-
/**
|
|
1141
|
-
* Check if element has CSS position: fixed
|
|
1142
|
-
*/
|
|
1143
1252
|
function isElementFixed(element) {
|
|
1144
1253
|
if (getComputedStyle(element).position === 'fixed') {
|
|
1145
1254
|
return true;
|
|
@@ -1150,9 +1259,6 @@ function isElementFixed(element) {
|
|
|
1150
1259
|
const parent = element.parentElement;
|
|
1151
1260
|
return parent ? isElementFixed(parent) : false;
|
|
1152
1261
|
}
|
|
1153
|
-
/**
|
|
1154
|
-
* Check if two areas overlap
|
|
1155
|
-
*/
|
|
1156
1262
|
function doAreasOverlap(area1, area2) {
|
|
1157
1263
|
const r1 = area1.rect.value;
|
|
1158
1264
|
const r2 = area2.rect.value;
|
|
@@ -1167,9 +1273,6 @@ function doAreasOverlap(area1, area2) {
|
|
|
1167
1273
|
r2.absoluteRight > r1.absoluteLeft &&
|
|
1168
1274
|
r2.absoluteLeft < r1.absoluteRight));
|
|
1169
1275
|
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Check if area1 is completely inside area2
|
|
1172
|
-
*/
|
|
1173
1276
|
function isAreaContainedIn(area1, area2) {
|
|
1174
1277
|
const r1 = area1.rect.value;
|
|
1175
1278
|
const r2 = area2.rect.value;
|
|
@@ -1180,64 +1283,29 @@ function isAreaContainedIn(area1, area2) {
|
|
|
1180
1283
|
r1.absoluteLeft >= r2.absoluteLeft &&
|
|
1181
1284
|
r1.absoluteRight <= r2.absoluteRight);
|
|
1182
1285
|
}
|
|
1183
|
-
/**
|
|
1184
|
-
* Check if element contains another element in DOM tree
|
|
1185
|
-
*/
|
|
1186
1286
|
function isElementAncestorOf(ancestor, descendant, doc) {
|
|
1187
1287
|
return ancestor.contains(descendant);
|
|
1188
1288
|
}
|
|
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
1289
|
function isElementSelectable(element, index, elements) {
|
|
1206
|
-
|
|
1290
|
+
if (isIgnoredCanvas(element)) {
|
|
1291
|
+
return false;
|
|
1292
|
+
}
|
|
1207
1293
|
if (element.hasAttribute(AREA_MAP_DIV_ATTRIBUTE)) {
|
|
1208
1294
|
return false;
|
|
1209
1295
|
}
|
|
1210
|
-
// Skip first element if it's BODY and there are other elements
|
|
1211
1296
|
if (index === 0 && elements.length > 1 && element.nodeName === 'BODY') {
|
|
1212
1297
|
return false;
|
|
1213
1298
|
}
|
|
1214
1299
|
return true;
|
|
1215
1300
|
}
|
|
1216
|
-
/**
|
|
1217
|
-
* Sort areas by click distribution (descending)
|
|
1218
|
-
*/
|
|
1219
1301
|
function sortAreasByClickDist(areas) {
|
|
1220
1302
|
return [...areas].sort((a, b) => {
|
|
1221
|
-
// Higher clickDist first
|
|
1222
1303
|
if (a.clickDist !== b.clickDist) {
|
|
1223
1304
|
return b.clickDist - a.clickDist;
|
|
1224
1305
|
}
|
|
1225
|
-
// Then by total clicks
|
|
1226
1306
|
return b.totalclicks - a.totalclicks;
|
|
1227
1307
|
});
|
|
1228
1308
|
}
|
|
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
1309
|
function isRectTooSmallForLabel(rect) {
|
|
1242
1310
|
return rect.width < 67 || rect.height < 30;
|
|
1243
1311
|
}
|
|
@@ -1254,18 +1322,46 @@ function getElementSelector(element) {
|
|
|
1254
1322
|
}
|
|
1255
1323
|
return element.tagName.toLowerCase();
|
|
1256
1324
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1325
|
+
/**
|
|
1326
|
+
* Calculate total clicks for an element including all its child elements
|
|
1327
|
+
* @param element - The parent element
|
|
1328
|
+
* @param elementMapInfo - Map of hash to element click info
|
|
1329
|
+
* @returns Total clicks for element + all descendants
|
|
1330
|
+
*/
|
|
1331
|
+
function calculateTotalClicksWithChildren(element, elementMapInfo) {
|
|
1332
|
+
let totalClicks = 0;
|
|
1333
|
+
// Get clicks for the element itself
|
|
1334
|
+
const elementHash = getElementHash(element);
|
|
1335
|
+
if (elementHash) {
|
|
1336
|
+
const elementInfo = elementMapInfo[elementHash];
|
|
1337
|
+
totalClicks += elementInfo?.totalclicks || 0;
|
|
1338
|
+
}
|
|
1339
|
+
const children = element.querySelectorAll('*');
|
|
1340
|
+
children.forEach((child) => {
|
|
1341
|
+
const childHash = getElementHash(child);
|
|
1342
|
+
if (childHash) {
|
|
1343
|
+
const childInfo = elementMapInfo[childHash];
|
|
1344
|
+
totalClicks += childInfo?.totalclicks || 0;
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
return totalClicks;
|
|
1348
|
+
}
|
|
1349
|
+
function buildAreaNode(element, hash, heatmapInfo, shadowRoot, persistedData) {
|
|
1350
|
+
if (!heatmapInfo.elementMapInfo)
|
|
1351
|
+
return;
|
|
1352
|
+
const totalClicks = heatmapInfo.totalClicks || 0;
|
|
1353
|
+
const elementInfo = heatmapInfo.elementMapInfo[hash];
|
|
1354
|
+
// Calculate total clicks including all child elements
|
|
1355
|
+
const elementClicks = calculateTotalClicksWithChildren(element, heatmapInfo.elementMapInfo);
|
|
1260
1356
|
const clickDist = calculateClickDistribution(elementClicks, totalClicks);
|
|
1261
1357
|
const rect = getElementRect(element);
|
|
1262
1358
|
const color = getColorFromClickDist(clickDist);
|
|
1263
1359
|
const hoverColor = getHoverColorFromClickDist(clickDist);
|
|
1264
1360
|
const areaNode = {
|
|
1265
|
-
kind: 'area',
|
|
1266
|
-
id: `${hash}_${Date.now()}`,
|
|
1361
|
+
kind: persistedData?.kind || 'area',
|
|
1362
|
+
id: persistedData?.id || `${hash}_${Date.now()}`,
|
|
1267
1363
|
hash,
|
|
1268
|
-
selector: elementInfo?.selector || getElementSelector(element),
|
|
1364
|
+
selector: persistedData?.selector || elementInfo?.selector || getElementSelector(element),
|
|
1269
1365
|
// DOM references
|
|
1270
1366
|
element,
|
|
1271
1367
|
areaElement: null,
|
|
@@ -1304,6 +1400,294 @@ function getTopElementsByClicks(elementMapInfo, topN = 10) {
|
|
|
1304
1400
|
return elements;
|
|
1305
1401
|
}
|
|
1306
1402
|
|
|
1403
|
+
/**
|
|
1404
|
+
* Build parent-child relationships between areas based on DOM hierarchy
|
|
1405
|
+
* @param areas - Array of area nodes to build relationships for
|
|
1406
|
+
*/
|
|
1407
|
+
function buildAreaGraph(areas) {
|
|
1408
|
+
// Clear existing relationships
|
|
1409
|
+
areas.forEach((area) => {
|
|
1410
|
+
area.parentNode = null;
|
|
1411
|
+
area.childNodes.clear();
|
|
1412
|
+
});
|
|
1413
|
+
// Build relationships based on DOM containment
|
|
1414
|
+
for (let i = 0; i < areas.length; i++) {
|
|
1415
|
+
const area = areas[i];
|
|
1416
|
+
for (let j = 0; j < areas.length; j++) {
|
|
1417
|
+
if (i === j)
|
|
1418
|
+
continue;
|
|
1419
|
+
const otherArea = areas[j];
|
|
1420
|
+
// Check if area's element is contained within otherArea's element
|
|
1421
|
+
if (otherArea.element.contains(area.element)) {
|
|
1422
|
+
// Find the closest parent (not just any ancestor)
|
|
1423
|
+
if (!area.parentNode || area.parentNode.element.contains(otherArea.element)) {
|
|
1424
|
+
// Remove from old parent if exists
|
|
1425
|
+
if (area.parentNode) {
|
|
1426
|
+
area.parentNode.childNodes.delete(area);
|
|
1427
|
+
}
|
|
1428
|
+
// Set new parent
|
|
1429
|
+
area.parentNode = otherArea;
|
|
1430
|
+
otherArea.childNodes.add(area);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
class Logger {
|
|
1438
|
+
config = {
|
|
1439
|
+
enabled: false,
|
|
1440
|
+
prefix: '',
|
|
1441
|
+
timestamp: false,
|
|
1442
|
+
};
|
|
1443
|
+
/**
|
|
1444
|
+
* Cấu hình logger
|
|
1445
|
+
* @param config - Cấu hình logger
|
|
1446
|
+
*/
|
|
1447
|
+
configure(config) {
|
|
1448
|
+
this.config = { ...this.config, ...config };
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Lấy cấu hình hiện tại
|
|
1452
|
+
*/
|
|
1453
|
+
getConfig() {
|
|
1454
|
+
return { ...this.config };
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Bật logger
|
|
1458
|
+
*/
|
|
1459
|
+
enable() {
|
|
1460
|
+
this.config.enabled = true;
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Tắt logger
|
|
1464
|
+
*/
|
|
1465
|
+
disable() {
|
|
1466
|
+
this.config.enabled = false;
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Format message với prefix và timestamp
|
|
1470
|
+
*/
|
|
1471
|
+
formatMessage(...args) {
|
|
1472
|
+
const parts = [];
|
|
1473
|
+
if (this.config.timestamp) {
|
|
1474
|
+
parts.push(`[${new Date().toISOString()}]`);
|
|
1475
|
+
}
|
|
1476
|
+
if (this.config.prefix) {
|
|
1477
|
+
parts.push(`[${this.config.prefix}]`);
|
|
1478
|
+
}
|
|
1479
|
+
if (parts.length > 0) {
|
|
1480
|
+
return [parts.join(' '), ...args];
|
|
1481
|
+
}
|
|
1482
|
+
return args;
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Log message
|
|
1486
|
+
*/
|
|
1487
|
+
log(...args) {
|
|
1488
|
+
if (!this.config.enabled)
|
|
1489
|
+
return;
|
|
1490
|
+
console.log(...this.formatMessage(...args));
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Log info message
|
|
1494
|
+
*/
|
|
1495
|
+
info(...args) {
|
|
1496
|
+
if (!this.config.enabled)
|
|
1497
|
+
return;
|
|
1498
|
+
console.info(...this.formatMessage(...args));
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Log warning message
|
|
1502
|
+
*/
|
|
1503
|
+
warn(...args) {
|
|
1504
|
+
if (!this.config.enabled)
|
|
1505
|
+
return;
|
|
1506
|
+
console.warn(...this.formatMessage(...args));
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Log error message
|
|
1510
|
+
*/
|
|
1511
|
+
error(...args) {
|
|
1512
|
+
if (!this.config.enabled)
|
|
1513
|
+
return;
|
|
1514
|
+
console.error(...this.formatMessage(...args));
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Log debug message
|
|
1518
|
+
*/
|
|
1519
|
+
debug(...args) {
|
|
1520
|
+
if (!this.config.enabled)
|
|
1521
|
+
return;
|
|
1522
|
+
console.debug(...this.formatMessage(...args));
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Log table data
|
|
1526
|
+
*/
|
|
1527
|
+
table(data) {
|
|
1528
|
+
if (!this.config.enabled)
|
|
1529
|
+
return;
|
|
1530
|
+
console.table(data);
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Start a group
|
|
1534
|
+
*/
|
|
1535
|
+
group(label) {
|
|
1536
|
+
if (!this.config.enabled)
|
|
1537
|
+
return;
|
|
1538
|
+
console.group(...this.formatMessage(label));
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Start a collapsed group
|
|
1542
|
+
*/
|
|
1543
|
+
groupCollapsed(label) {
|
|
1544
|
+
if (!this.config.enabled)
|
|
1545
|
+
return;
|
|
1546
|
+
console.groupCollapsed(...this.formatMessage(label));
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* End a group
|
|
1550
|
+
*/
|
|
1551
|
+
groupEnd() {
|
|
1552
|
+
if (!this.config.enabled)
|
|
1553
|
+
return;
|
|
1554
|
+
console.groupEnd();
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Start a timer
|
|
1558
|
+
*/
|
|
1559
|
+
time(label) {
|
|
1560
|
+
if (!this.config.enabled)
|
|
1561
|
+
return;
|
|
1562
|
+
console.time(label);
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* End a timer
|
|
1566
|
+
*/
|
|
1567
|
+
timeEnd(label) {
|
|
1568
|
+
if (!this.config.enabled)
|
|
1569
|
+
return;
|
|
1570
|
+
console.timeEnd(label);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
// Export singleton instance
|
|
1574
|
+
const logger$3 = new Logger();
|
|
1575
|
+
// Export factory function để tạo logger với config riêng
|
|
1576
|
+
function createLogger(config = {}) {
|
|
1577
|
+
const instance = new Logger();
|
|
1578
|
+
instance.configure(config);
|
|
1579
|
+
return instance;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
function findLastSizeOfDom(data) {
|
|
1583
|
+
const listDocs = data
|
|
1584
|
+
.filter((item) => item.doc?.find((doc) => doc.data.width && doc.data.height))
|
|
1585
|
+
.flatMap((item) => item.doc?.flatMap((doc) => doc.data));
|
|
1586
|
+
const lastDoc = listDocs?.[listDocs.length - 1];
|
|
1587
|
+
const docSize = {
|
|
1588
|
+
width: lastDoc?.width,
|
|
1589
|
+
height: lastDoc?.height,
|
|
1590
|
+
};
|
|
1591
|
+
const listResizes = data.filter((item) => !!item.resize).flatMap((item) => item.resize);
|
|
1592
|
+
const lastResizeEvent = listResizes?.[listResizes.length - 1];
|
|
1593
|
+
const resize = {
|
|
1594
|
+
width: lastResizeEvent?.data.width,
|
|
1595
|
+
height: lastResizeEvent?.data.height,
|
|
1596
|
+
};
|
|
1597
|
+
return {
|
|
1598
|
+
doc: docSize,
|
|
1599
|
+
resize: resize,
|
|
1600
|
+
size: {
|
|
1601
|
+
width: resize.width || docSize.width,
|
|
1602
|
+
height: resize.height || docSize.height,
|
|
1603
|
+
},
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
function decodePayloads(payload) {
|
|
1607
|
+
try {
|
|
1608
|
+
return decode(payload);
|
|
1609
|
+
}
|
|
1610
|
+
catch (_error) {
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
function findElementByHash(props) {
|
|
1616
|
+
const { hash, selector, iframeDocument, vizRef } = props;
|
|
1617
|
+
if (vizRef) {
|
|
1618
|
+
const element = vizRef.get(hash);
|
|
1619
|
+
return element;
|
|
1620
|
+
}
|
|
1621
|
+
// Fallback
|
|
1622
|
+
if (!iframeDocument)
|
|
1623
|
+
return null;
|
|
1624
|
+
try {
|
|
1625
|
+
const element = selector ? iframeDocument.querySelector(selector) : null;
|
|
1626
|
+
if (element) {
|
|
1627
|
+
return element;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
catch (error) {
|
|
1631
|
+
logger$3.warn(`Invalid selector "${selector}":`, error);
|
|
1632
|
+
}
|
|
1633
|
+
const elementByHash = iframeDocument.querySelector(`[data-clarity-hashalpha="${hash}"], [data-clarity-hash="${hash}"], [data-clarity-hashbeta="${hash}"]`);
|
|
1634
|
+
return elementByHash;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
/**
|
|
1638
|
+
* Hydrates persisted area data into full area node
|
|
1639
|
+
* Finds element in DOM and calculates all runtime values
|
|
1640
|
+
*
|
|
1641
|
+
* @param persistedData - Minimal data from database
|
|
1642
|
+
* @param iframeDocument - Document to find element in
|
|
1643
|
+
* @param heatmapInfo - Heatmap data for click calculations
|
|
1644
|
+
* @param vizRef - Map of hash to elements
|
|
1645
|
+
* @param shadowRoot - Optional shadow root for rect calculation
|
|
1646
|
+
* @returns Full area node or null if element not found
|
|
1647
|
+
*/
|
|
1648
|
+
function hydrateAreaNode(props) {
|
|
1649
|
+
const { persistedData, iframeDocument, heatmapInfo, vizRef, shadowRoot } = props;
|
|
1650
|
+
const { id, hash, selector } = persistedData;
|
|
1651
|
+
const element = findElementByHash({ hash, selector, iframeDocument, vizRef });
|
|
1652
|
+
if (!element) {
|
|
1653
|
+
logger$3.warn(`Cannot hydrate area ${id}: element not found for hash ${hash} or selector ${selector}`);
|
|
1654
|
+
return null;
|
|
1655
|
+
}
|
|
1656
|
+
const areaNode = buildAreaNode(element, hash, heatmapInfo, shadowRoot, persistedData);
|
|
1657
|
+
if (!areaNode)
|
|
1658
|
+
return null;
|
|
1659
|
+
return areaNode;
|
|
1660
|
+
}
|
|
1661
|
+
function hydrateAreas(props) {
|
|
1662
|
+
const { clickAreas, iframeDocument, heatmapInfo, vizRef, shadowRoot } = props;
|
|
1663
|
+
const hydratedAreas = [];
|
|
1664
|
+
for (const persistedData of clickAreas) {
|
|
1665
|
+
const area = hydrateAreaNode({ persistedData, iframeDocument, heatmapInfo, vizRef, shadowRoot });
|
|
1666
|
+
if (area) {
|
|
1667
|
+
hydratedAreas.push(area);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
logger$3.info(`Hydrated ${hydratedAreas.length} of ${clickAreas.length} persisted areas`);
|
|
1671
|
+
return hydratedAreas;
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Serializes area node to persisted data for database storage
|
|
1675
|
+
*/
|
|
1676
|
+
function serializeAreaNode(area) {
|
|
1677
|
+
return {
|
|
1678
|
+
kind: area.kind,
|
|
1679
|
+
id: area.id,
|
|
1680
|
+
hash: area.hash,
|
|
1681
|
+
selector: area.selector,
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* Serializes multiple areas for database storage
|
|
1686
|
+
*/
|
|
1687
|
+
function serializeAreas(areas) {
|
|
1688
|
+
return areas.map(serializeAreaNode);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1307
1691
|
/**
|
|
1308
1692
|
* Resolve overlapping areas by priority rules
|
|
1309
1693
|
*
|
|
@@ -1446,36 +1830,64 @@ function getVisibleAreas(areas, iframeDocument) {
|
|
|
1446
1830
|
return sortAreasByClickDist(visible);
|
|
1447
1831
|
}
|
|
1448
1832
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
const resize = {
|
|
1461
|
-
width: lastResizeEvent?.data.width,
|
|
1462
|
-
height: lastResizeEvent?.data.height,
|
|
1463
|
-
};
|
|
1464
|
-
return {
|
|
1465
|
-
doc: docSize,
|
|
1466
|
-
resize: resize,
|
|
1467
|
-
size: {
|
|
1468
|
-
width: resize.width || docSize.width,
|
|
1469
|
-
height: resize.height || docSize.height,
|
|
1470
|
-
},
|
|
1471
|
-
};
|
|
1833
|
+
/**
|
|
1834
|
+
* Helper functions for setting up area renderer
|
|
1835
|
+
*/
|
|
1836
|
+
/**
|
|
1837
|
+
* Create the outer container for area rendering
|
|
1838
|
+
*/
|
|
1839
|
+
function createAreaContainer(iframeDocument) {
|
|
1840
|
+
const container = iframeDocument.createElement('div');
|
|
1841
|
+
container.setAttribute(AREA_MAP_DIV_ATTRIBUTE, 'true');
|
|
1842
|
+
container.style.cssText = AREA_CONTAINER_STYLES;
|
|
1843
|
+
return container;
|
|
1472
1844
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1845
|
+
/**
|
|
1846
|
+
* Create the inner container for React portal
|
|
1847
|
+
*/
|
|
1848
|
+
function createInnerContainer(iframeDocument) {
|
|
1849
|
+
const innerContainer = iframeDocument.createElement('div');
|
|
1850
|
+
innerContainer.className = AREA_RENDERER_SELECTORS.innerContainerClass;
|
|
1851
|
+
innerContainer.style.cssText = AREA_INNER_CONTAINER_STYLES;
|
|
1852
|
+
return innerContainer;
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Get or create the outer container element
|
|
1856
|
+
*/
|
|
1857
|
+
function getOrCreateAreaContainer(iframeDocument, customShadowRoot) {
|
|
1858
|
+
let container = iframeDocument.querySelector(AREA_RENDERER_SELECTORS.containerSelector);
|
|
1859
|
+
if (!container) {
|
|
1860
|
+
container = createAreaContainer(iframeDocument);
|
|
1861
|
+
const targetRoot = customShadowRoot || iframeDocument.body;
|
|
1862
|
+
if (targetRoot) {
|
|
1863
|
+
targetRoot.appendChild(container);
|
|
1864
|
+
}
|
|
1476
1865
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1866
|
+
return container;
|
|
1867
|
+
}
|
|
1868
|
+
function getOrCreateContainerShadowRoot(container) {
|
|
1869
|
+
if (container.shadowRoot) {
|
|
1870
|
+
return container.shadowRoot;
|
|
1871
|
+
}
|
|
1872
|
+
return container.attachShadow({ mode: 'open' });
|
|
1873
|
+
}
|
|
1874
|
+
function getOrCreateInnerContainer(shadowRoot, iframeDocument) {
|
|
1875
|
+
let innerContainer = shadowRoot.querySelector(AREA_RENDERER_SELECTORS.innerContainerSelector);
|
|
1876
|
+
if (!innerContainer) {
|
|
1877
|
+
innerContainer = createInnerContainer(iframeDocument);
|
|
1878
|
+
shadowRoot.appendChild(innerContainer);
|
|
1879
|
+
}
|
|
1880
|
+
return innerContainer;
|
|
1881
|
+
}
|
|
1882
|
+
function setupAreaRenderingContainer(iframeDocument, customShadowRoot) {
|
|
1883
|
+
const container = getOrCreateAreaContainer(iframeDocument, customShadowRoot);
|
|
1884
|
+
const shadowRoot = getOrCreateContainerShadowRoot(container);
|
|
1885
|
+
const innerContainer = getOrCreateInnerContainer(shadowRoot, iframeDocument);
|
|
1886
|
+
return innerContainer;
|
|
1887
|
+
}
|
|
1888
|
+
function cleanupAreaRenderingContainer(container) {
|
|
1889
|
+
if (container && container.parentNode) {
|
|
1890
|
+
container.parentNode.removeChild(container);
|
|
1479
1891
|
}
|
|
1480
1892
|
}
|
|
1481
1893
|
|
|
@@ -1510,23 +1922,6 @@ function getElementLayout(element) {
|
|
|
1510
1922
|
height: rect.height,
|
|
1511
1923
|
};
|
|
1512
1924
|
}
|
|
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
1925
|
const getElementRank = (hash, elements) => {
|
|
1531
1926
|
if (!elements)
|
|
1532
1927
|
return 0;
|
|
@@ -1617,8 +2012,7 @@ const calculateHorizontalPlacementPosition = (targetRect, calloutRect, placement
|
|
|
1617
2012
|
|
|
1618
2013
|
const isLeftPositionValid = (leftPos, calloutWidth, viewportWidth, padding, containerRect) => {
|
|
1619
2014
|
if (containerRect) {
|
|
1620
|
-
return
|
|
1621
|
-
leftPos + calloutWidth <= containerRect.right - padding);
|
|
2015
|
+
return leftPos >= containerRect.left + padding && leftPos + calloutWidth <= containerRect.right - padding;
|
|
1622
2016
|
}
|
|
1623
2017
|
return leftPos >= padding && leftPos + calloutWidth <= viewportWidth - padding;
|
|
1624
2018
|
};
|
|
@@ -1662,8 +2056,7 @@ const generateVerticalPositionCandidates = (targetRect, calloutRect, viewportHei
|
|
|
1662
2056
|
top: verticalPos,
|
|
1663
2057
|
left: horizontalPos,
|
|
1664
2058
|
horizontalAlign: align,
|
|
1665
|
-
valid: verticalValid &&
|
|
1666
|
-
isLeftPositionValid(horizontalPos, calloutRect.width, viewportWidth, padding, containerRect),
|
|
2059
|
+
valid: verticalValid && isLeftPositionValid(horizontalPos, calloutRect.width, viewportWidth, padding, containerRect),
|
|
1667
2060
|
});
|
|
1668
2061
|
});
|
|
1669
2062
|
});
|
|
@@ -1729,6 +2122,10 @@ const calcCalloutPosition = (options) => {
|
|
|
1729
2122
|
};
|
|
1730
2123
|
};
|
|
1731
2124
|
|
|
2125
|
+
const logger$2 = createLogger({
|
|
2126
|
+
enabled: false,
|
|
2127
|
+
prefix: 'IframeNavigationBlockerV2',
|
|
2128
|
+
});
|
|
1732
2129
|
class IframeNavigationBlockerV2 {
|
|
1733
2130
|
doc;
|
|
1734
2131
|
win;
|
|
@@ -1736,17 +2133,18 @@ class IframeNavigationBlockerV2 {
|
|
|
1736
2133
|
showMessage = false;
|
|
1737
2134
|
originalWindowOpen;
|
|
1738
2135
|
observers = [];
|
|
1739
|
-
constructor(iframe) {
|
|
2136
|
+
constructor(iframe, config) {
|
|
1740
2137
|
if (!iframe.contentDocument || !iframe.contentWindow) {
|
|
1741
2138
|
throw new Error('Iframe document or window not accessible');
|
|
1742
2139
|
}
|
|
1743
2140
|
this.doc = iframe.contentDocument;
|
|
1744
2141
|
this.win = iframe.contentWindow;
|
|
1745
2142
|
this.originalWindowOpen = this.win.open.bind(this.win);
|
|
2143
|
+
logger$2.configure({ enabled: !!config?.debug });
|
|
1746
2144
|
this.init();
|
|
1747
2145
|
}
|
|
1748
2146
|
init() {
|
|
1749
|
-
|
|
2147
|
+
logger$2.log('Initializing...');
|
|
1750
2148
|
try {
|
|
1751
2149
|
// Chặn navigation qua links
|
|
1752
2150
|
this.blockLinkNavigation();
|
|
@@ -1762,7 +2160,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1762
2160
|
this.injectCSP();
|
|
1763
2161
|
}
|
|
1764
2162
|
catch (error) {
|
|
1765
|
-
|
|
2163
|
+
logger$2.error('Init error:', error);
|
|
1766
2164
|
}
|
|
1767
2165
|
}
|
|
1768
2166
|
blockLinkNavigation() {
|
|
@@ -1776,11 +2174,11 @@ class IframeNavigationBlockerV2 {
|
|
|
1776
2174
|
const href = link.getAttribute('href');
|
|
1777
2175
|
// Cho phép hash links và empty links
|
|
1778
2176
|
if (!href || href === '' || href === '#' || href.startsWith('#')) {
|
|
1779
|
-
|
|
2177
|
+
logger$2.log('Allowed hash navigation:', href);
|
|
1780
2178
|
return;
|
|
1781
2179
|
}
|
|
1782
2180
|
// Chặn tất cả các loại navigation
|
|
1783
|
-
|
|
2181
|
+
logger$2.log('Blocked link navigation to:', href);
|
|
1784
2182
|
e.preventDefault();
|
|
1785
2183
|
e.stopPropagation();
|
|
1786
2184
|
e.stopImmediatePropagation();
|
|
@@ -1796,7 +2194,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1796
2194
|
if (link) {
|
|
1797
2195
|
const href = link.getAttribute('href');
|
|
1798
2196
|
if (href && !href.startsWith('#')) {
|
|
1799
|
-
|
|
2197
|
+
logger$2.log('Blocked auxclick navigation');
|
|
1800
2198
|
e.preventDefault();
|
|
1801
2199
|
e.stopPropagation();
|
|
1802
2200
|
e.stopImmediatePropagation();
|
|
@@ -1829,13 +2227,13 @@ class IframeNavigationBlockerV2 {
|
|
|
1829
2227
|
const action = form.getAttribute('action');
|
|
1830
2228
|
// Cho phép forms không có action
|
|
1831
2229
|
if (!action || action === '' || action === '#') {
|
|
1832
|
-
|
|
2230
|
+
logger$2.log('Allowed same-page form');
|
|
1833
2231
|
e.preventDefault();
|
|
1834
2232
|
this.handleFormSubmit(form);
|
|
1835
2233
|
return;
|
|
1836
2234
|
}
|
|
1837
2235
|
// Chặn tất cả external submissions
|
|
1838
|
-
|
|
2236
|
+
logger$2.log('Blocked form submission to:', action);
|
|
1839
2237
|
e.preventDefault();
|
|
1840
2238
|
e.stopPropagation();
|
|
1841
2239
|
e.stopImmediatePropagation();
|
|
@@ -1849,7 +2247,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1849
2247
|
return this.originalWindowOpen(...args);
|
|
1850
2248
|
}
|
|
1851
2249
|
const url = args[0]?.toString() || 'popup';
|
|
1852
|
-
|
|
2250
|
+
logger$2.log('Blocked window.open:', url);
|
|
1853
2251
|
this.notifyBlockedNavigation(url);
|
|
1854
2252
|
return null;
|
|
1855
2253
|
});
|
|
@@ -1859,7 +2257,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1859
2257
|
this.win.addEventListener('beforeunload', (e) => {
|
|
1860
2258
|
if (!this.isEnabled)
|
|
1861
2259
|
return;
|
|
1862
|
-
|
|
2260
|
+
logger$2.log('Blocked beforeunload');
|
|
1863
2261
|
e.preventDefault();
|
|
1864
2262
|
e.returnValue = '';
|
|
1865
2263
|
return '';
|
|
@@ -1868,7 +2266,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1868
2266
|
this.win.addEventListener('unload', (e) => {
|
|
1869
2267
|
if (!this.isEnabled)
|
|
1870
2268
|
return;
|
|
1871
|
-
|
|
2269
|
+
logger$2.log('Blocked unload');
|
|
1872
2270
|
e.preventDefault();
|
|
1873
2271
|
e.stopPropagation();
|
|
1874
2272
|
}, true);
|
|
@@ -1876,7 +2274,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1876
2274
|
this.win.addEventListener('popstate', (e) => {
|
|
1877
2275
|
if (!this.isEnabled)
|
|
1878
2276
|
return;
|
|
1879
|
-
|
|
2277
|
+
logger$2.log('Blocked popstate');
|
|
1880
2278
|
e.preventDefault();
|
|
1881
2279
|
e.stopPropagation();
|
|
1882
2280
|
}, true);
|
|
@@ -1929,11 +2327,11 @@ class IframeNavigationBlockerV2 {
|
|
|
1929
2327
|
meta.httpEquiv = 'Content-Security-Policy';
|
|
1930
2328
|
meta.content = "navigate-to 'none'"; // Chặn tất cả navigation
|
|
1931
2329
|
this.doc.head.appendChild(meta);
|
|
1932
|
-
|
|
2330
|
+
logger$2.log('Injected CSP');
|
|
1933
2331
|
}
|
|
1934
2332
|
}
|
|
1935
2333
|
catch (error) {
|
|
1936
|
-
|
|
2334
|
+
logger$2.warn('Could not inject CSP:', error);
|
|
1937
2335
|
}
|
|
1938
2336
|
}
|
|
1939
2337
|
handleFormSubmit(form) {
|
|
@@ -1942,13 +2340,13 @@ class IframeNavigationBlockerV2 {
|
|
|
1942
2340
|
formData.forEach((value, key) => {
|
|
1943
2341
|
data[key] = value;
|
|
1944
2342
|
});
|
|
1945
|
-
|
|
2343
|
+
logger$2.log('Handling form data:', data);
|
|
1946
2344
|
window.dispatchEvent(new CustomEvent('iframe-form-submit', {
|
|
1947
2345
|
detail: { form, data },
|
|
1948
2346
|
}));
|
|
1949
2347
|
}
|
|
1950
2348
|
notifyBlockedNavigation(url) {
|
|
1951
|
-
|
|
2349
|
+
logger$2.warn('Navigation blocked to:', url);
|
|
1952
2350
|
window.dispatchEvent(new CustomEvent('iframe-navigation-blocked', {
|
|
1953
2351
|
detail: { url, timestamp: Date.now() },
|
|
1954
2352
|
}));
|
|
@@ -1998,19 +2396,19 @@ class IframeNavigationBlockerV2 {
|
|
|
1998
2396
|
}
|
|
1999
2397
|
enable() {
|
|
2000
2398
|
this.isEnabled = true;
|
|
2001
|
-
|
|
2399
|
+
logger$2.log('Enabled');
|
|
2002
2400
|
}
|
|
2003
2401
|
enableMessage() {
|
|
2004
2402
|
this.showMessage = true;
|
|
2005
|
-
|
|
2403
|
+
logger$2.log('Enabled message');
|
|
2006
2404
|
}
|
|
2007
2405
|
disable() {
|
|
2008
2406
|
this.isEnabled = false;
|
|
2009
|
-
|
|
2407
|
+
logger$2.log('Disabled');
|
|
2010
2408
|
}
|
|
2011
2409
|
disableMessage() {
|
|
2012
2410
|
this.showMessage = false;
|
|
2013
|
-
|
|
2411
|
+
logger$2.log('Disabled message');
|
|
2014
2412
|
}
|
|
2015
2413
|
destroy() {
|
|
2016
2414
|
this.isEnabled = false;
|
|
@@ -2018,10 +2416,14 @@ class IframeNavigationBlockerV2 {
|
|
|
2018
2416
|
// Cleanup observers
|
|
2019
2417
|
this.observers.forEach((observer) => observer.disconnect());
|
|
2020
2418
|
this.observers = [];
|
|
2021
|
-
|
|
2419
|
+
logger$2.log('Destroyed');
|
|
2022
2420
|
}
|
|
2023
2421
|
}
|
|
2024
2422
|
|
|
2423
|
+
const logger$1 = createLogger({
|
|
2424
|
+
enabled: false,
|
|
2425
|
+
prefix: 'IframeStyleReplacer',
|
|
2426
|
+
});
|
|
2025
2427
|
class IframeStyleReplacer {
|
|
2026
2428
|
doc;
|
|
2027
2429
|
win;
|
|
@@ -2034,6 +2436,7 @@ class IframeStyleReplacer {
|
|
|
2034
2436
|
this.doc = iframe.contentDocument;
|
|
2035
2437
|
this.win = iframe.contentWindow;
|
|
2036
2438
|
this.config = config;
|
|
2439
|
+
logger$1.configure({ enabled: !!config?.debug });
|
|
2037
2440
|
}
|
|
2038
2441
|
px(value) {
|
|
2039
2442
|
return `${value.toFixed(2)}px`;
|
|
@@ -2067,7 +2470,7 @@ class IframeStyleReplacer {
|
|
|
2067
2470
|
count++;
|
|
2068
2471
|
}
|
|
2069
2472
|
});
|
|
2070
|
-
|
|
2473
|
+
logger$1.log(`Replaced ${count} inline style elements`);
|
|
2071
2474
|
return count;
|
|
2072
2475
|
}
|
|
2073
2476
|
processStyleTags() {
|
|
@@ -2080,7 +2483,7 @@ class IframeStyleReplacer {
|
|
|
2080
2483
|
count++;
|
|
2081
2484
|
}
|
|
2082
2485
|
});
|
|
2083
|
-
|
|
2486
|
+
logger$1.log(`Replaced ${count} <style> tags`);
|
|
2084
2487
|
return count;
|
|
2085
2488
|
}
|
|
2086
2489
|
processRule(rule) {
|
|
@@ -2111,7 +2514,7 @@ class IframeStyleReplacer {
|
|
|
2111
2514
|
try {
|
|
2112
2515
|
// Bỏ qua external CSS (cross-origin)
|
|
2113
2516
|
if (sheet.href && !sheet.href.startsWith(this.win.location.origin)) {
|
|
2114
|
-
|
|
2517
|
+
logger$1.log('Skipping external CSS:', sheet.href);
|
|
2115
2518
|
return;
|
|
2116
2519
|
}
|
|
2117
2520
|
const rules = sheet.cssRules || sheet.rules;
|
|
@@ -2122,10 +2525,10 @@ class IframeStyleReplacer {
|
|
|
2122
2525
|
}
|
|
2123
2526
|
}
|
|
2124
2527
|
catch (e) {
|
|
2125
|
-
|
|
2528
|
+
logger$1.warn('Cannot read stylesheet (CORS?):', e.message);
|
|
2126
2529
|
}
|
|
2127
2530
|
});
|
|
2128
|
-
|
|
2531
|
+
logger$1.log(`Replaced ${total} rules in stylesheets`);
|
|
2129
2532
|
return total;
|
|
2130
2533
|
}
|
|
2131
2534
|
async processLinkedStylesheets() {
|
|
@@ -2133,7 +2536,7 @@ class IframeStyleReplacer {
|
|
|
2133
2536
|
let count = 0;
|
|
2134
2537
|
for (const link of Array.from(links)) {
|
|
2135
2538
|
if (!link.href.startsWith(this.win.location.origin)) {
|
|
2136
|
-
|
|
2539
|
+
logger$1.log('Skipping external CSS:', link.href);
|
|
2137
2540
|
continue;
|
|
2138
2541
|
}
|
|
2139
2542
|
try {
|
|
@@ -2151,10 +2554,10 @@ class IframeStyleReplacer {
|
|
|
2151
2554
|
}
|
|
2152
2555
|
}
|
|
2153
2556
|
catch (e) {
|
|
2154
|
-
|
|
2557
|
+
logger$1.warn('Cannot load CSS:', link.href, e);
|
|
2155
2558
|
}
|
|
2156
2559
|
}
|
|
2157
|
-
|
|
2560
|
+
logger$1.log(`Replaced ${count} linked CSS files`);
|
|
2158
2561
|
return count;
|
|
2159
2562
|
}
|
|
2160
2563
|
getFinalHeight() {
|
|
@@ -2178,7 +2581,7 @@ class IframeStyleReplacer {
|
|
|
2178
2581
|
}
|
|
2179
2582
|
async run() {
|
|
2180
2583
|
try {
|
|
2181
|
-
|
|
2584
|
+
logger$1.log('Starting viewport units replacement...');
|
|
2182
2585
|
this.processInlineStyles();
|
|
2183
2586
|
this.processStyleTags();
|
|
2184
2587
|
this.processStylesheets();
|
|
@@ -2188,13 +2591,13 @@ class IframeStyleReplacer {
|
|
|
2188
2591
|
requestAnimationFrame(() => {
|
|
2189
2592
|
const height = this.getFinalHeight();
|
|
2190
2593
|
const width = this.getFinalWidth();
|
|
2191
|
-
|
|
2594
|
+
logger$1.log('Calculated dimensions:', { height, width });
|
|
2192
2595
|
resolve({ height, width });
|
|
2193
2596
|
});
|
|
2194
2597
|
});
|
|
2195
2598
|
}
|
|
2196
2599
|
catch (err) {
|
|
2197
|
-
|
|
2600
|
+
logger$1.error('Critical error:', err);
|
|
2198
2601
|
return {
|
|
2199
2602
|
height: this.doc.body.scrollHeight || 1000,
|
|
2200
2603
|
width: this.doc.body.scrollWidth || 1000,
|
|
@@ -2206,6 +2609,10 @@ class IframeStyleReplacer {
|
|
|
2206
2609
|
}
|
|
2207
2610
|
}
|
|
2208
2611
|
|
|
2612
|
+
const logger = createLogger({
|
|
2613
|
+
enabled: false,
|
|
2614
|
+
prefix: 'IframeHelper',
|
|
2615
|
+
});
|
|
2209
2616
|
class IframeHelperFixer {
|
|
2210
2617
|
iframe;
|
|
2211
2618
|
config;
|
|
@@ -2215,10 +2622,11 @@ class IframeHelperFixer {
|
|
|
2215
2622
|
this.config = config;
|
|
2216
2623
|
this.iframe = config.iframe;
|
|
2217
2624
|
this.init();
|
|
2625
|
+
logger.configure({ enabled: !!config?.debug });
|
|
2218
2626
|
}
|
|
2219
2627
|
async init() {
|
|
2220
2628
|
if (!this.iframe) {
|
|
2221
|
-
|
|
2629
|
+
logger.error('iframe not found');
|
|
2222
2630
|
this.config.onError?.(new Error('iframe not found'));
|
|
2223
2631
|
return;
|
|
2224
2632
|
}
|
|
@@ -2232,19 +2640,19 @@ class IframeHelperFixer {
|
|
|
2232
2640
|
}
|
|
2233
2641
|
async process() {
|
|
2234
2642
|
if (!this.iframe.contentDocument || !this.iframe.contentWindow) {
|
|
2235
|
-
|
|
2643
|
+
logger.error('Cannot access iframe document');
|
|
2236
2644
|
this.config.onError?.(new Error('Cannot access iframe document'));
|
|
2237
2645
|
return;
|
|
2238
2646
|
}
|
|
2239
2647
|
try {
|
|
2240
|
-
|
|
2648
|
+
logger.log('Processing viewport units...');
|
|
2241
2649
|
// Create replacer instance
|
|
2242
2650
|
this.replacer = new IframeStyleReplacer(this.iframe, this.config);
|
|
2243
2651
|
// Create navigation blocker
|
|
2244
|
-
this.navigationBlocker = new IframeNavigationBlockerV2(this.iframe);
|
|
2652
|
+
this.navigationBlocker = new IframeNavigationBlockerV2(this.iframe, { debug: this.config.debug });
|
|
2245
2653
|
// Run replacement
|
|
2246
2654
|
const result = await this.replacer.run();
|
|
2247
|
-
|
|
2655
|
+
logger.log('Process completed:', result);
|
|
2248
2656
|
// Trigger success callback
|
|
2249
2657
|
this.config.onSuccess?.(result);
|
|
2250
2658
|
// Dispatch custom event
|
|
@@ -2253,12 +2661,12 @@ class IframeHelperFixer {
|
|
|
2253
2661
|
}));
|
|
2254
2662
|
}
|
|
2255
2663
|
catch (error) {
|
|
2256
|
-
|
|
2664
|
+
logger.error('Failed to process:', error);
|
|
2257
2665
|
this.config.onError?.(error);
|
|
2258
2666
|
}
|
|
2259
2667
|
}
|
|
2260
2668
|
async recalculate() {
|
|
2261
|
-
|
|
2669
|
+
logger.log('Recalculating...');
|
|
2262
2670
|
await this.process();
|
|
2263
2671
|
}
|
|
2264
2672
|
updateConfig(config) {
|
|
@@ -2283,61 +2691,150 @@ class IframeHelperFixer {
|
|
|
2283
2691
|
this.replacer = null;
|
|
2284
2692
|
this.navigationBlocker?.destroy();
|
|
2285
2693
|
this.navigationBlocker = null;
|
|
2286
|
-
|
|
2694
|
+
logger.log('Destroyed');
|
|
2287
2695
|
}
|
|
2288
2696
|
}
|
|
2289
2697
|
|
|
2290
2698
|
function initIframeHelperFixer(config) {
|
|
2291
2699
|
const fixer = new IframeHelperFixer(config);
|
|
2292
2700
|
window.addEventListener('iframe-dimensions-applied', ((e) => {
|
|
2293
|
-
|
|
2294
|
-
console.log('[IframeHelper] Iframe dimensions finalized:', ev.detail);
|
|
2701
|
+
// console.log('[IframeHelper] Iframe dimensions finalized:', ev.detail);
|
|
2295
2702
|
}));
|
|
2296
2703
|
window.addEventListener('iframe-navigation-blocked', ((e) => {
|
|
2297
|
-
|
|
2298
|
-
console.warn('[IframeHelper] Iframe tried to navigate to:', ev.detail.url);
|
|
2704
|
+
// console.warn('[IframeHelper] Iframe tried to navigate to:', ev.detail.url);
|
|
2299
2705
|
}));
|
|
2300
2706
|
window.addEventListener('iframe-form-submit', ((e) => {
|
|
2301
|
-
|
|
2302
|
-
console.log('[IframeHelper] Iframe form submitted:', ev.detail.data);
|
|
2707
|
+
// console.log('[IframeHelper] Iframe form submitted:', ev.detail.data);
|
|
2303
2708
|
}));
|
|
2304
2709
|
return fixer;
|
|
2305
2710
|
}
|
|
2306
2711
|
|
|
2307
|
-
function
|
|
2712
|
+
function validateAreaCreation(dataInfo, hash, areas) {
|
|
2713
|
+
if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks) {
|
|
2714
|
+
logger$3.warn('Cannot create area: missing heatmap data');
|
|
2715
|
+
return false;
|
|
2716
|
+
}
|
|
2717
|
+
if (!hash) {
|
|
2718
|
+
logger$3.warn('Cannot create area: missing hash');
|
|
2719
|
+
return false;
|
|
2720
|
+
}
|
|
2721
|
+
const alreadyExists = areas.some((area) => area.hash === hash);
|
|
2722
|
+
if (alreadyExists) {
|
|
2723
|
+
logger$3.warn(`Area already exists for element: ${hash}`);
|
|
2724
|
+
return false;
|
|
2725
|
+
}
|
|
2726
|
+
return true;
|
|
2727
|
+
}
|
|
2728
|
+
function identifyConflictingAreas(area) {
|
|
2729
|
+
const conflicts = {
|
|
2730
|
+
parentId: null,
|
|
2731
|
+
childrenIds: [],
|
|
2732
|
+
};
|
|
2733
|
+
// Case 1: New area is a child of an existing area
|
|
2734
|
+
if (area.parentNode) {
|
|
2735
|
+
conflicts.parentId = area.parentNode.id;
|
|
2736
|
+
logger$3.info(`New area "${area.selector}" is a child of existing area "${area.parentNode.selector}". Will remove parent.`);
|
|
2737
|
+
}
|
|
2738
|
+
// Case 2: New area is a parent of existing area(s)
|
|
2739
|
+
if (area.childNodes.size > 0) {
|
|
2740
|
+
area.childNodes.forEach((childArea) => {
|
|
2741
|
+
conflicts.childrenIds.push(childArea.id);
|
|
2742
|
+
});
|
|
2743
|
+
logger$3.info(`New area "${area.selector}" is a parent of ${area.childNodes.size} existing area(s). Will remove children.`);
|
|
2744
|
+
}
|
|
2745
|
+
return conflicts;
|
|
2746
|
+
}
|
|
2747
|
+
function clearGraphRelationships(area) {
|
|
2748
|
+
area.parentNode = null;
|
|
2749
|
+
area.childNodes.clear();
|
|
2750
|
+
}
|
|
2751
|
+
function removeConflictingAreas(conflicts, removeArea) {
|
|
2752
|
+
if (conflicts.parentId) {
|
|
2753
|
+
removeArea(conflicts.parentId);
|
|
2754
|
+
}
|
|
2755
|
+
conflicts.childrenIds.forEach((childId) => {
|
|
2756
|
+
removeArea(childId);
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
function useAreaCreation(options = {}) {
|
|
2760
|
+
const { customShadowRoot, onAreaCreated } = options;
|
|
2761
|
+
const { dataInfo } = useHeatmapData();
|
|
2762
|
+
const { areas, addArea, removeArea } = useHeatmapAreaClick();
|
|
2763
|
+
const onAreaCreatedElement = useCallback((element) => {
|
|
2764
|
+
if (!dataInfo)
|
|
2765
|
+
return;
|
|
2766
|
+
const hash = getElementHash(element);
|
|
2767
|
+
if (!validateAreaCreation(dataInfo, hash, areas)) {
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2770
|
+
try {
|
|
2771
|
+
const area = buildAreaNode(element, hash, dataInfo, customShadowRoot);
|
|
2772
|
+
if (!area)
|
|
2773
|
+
return;
|
|
2774
|
+
const tempAreas = [...areas, area];
|
|
2775
|
+
buildAreaGraph(tempAreas);
|
|
2776
|
+
const conflicts = identifyConflictingAreas(area);
|
|
2777
|
+
clearGraphRelationships(area);
|
|
2778
|
+
addArea(area);
|
|
2779
|
+
removeConflictingAreas(conflicts, removeArea);
|
|
2780
|
+
if (onAreaCreated) {
|
|
2781
|
+
onAreaCreated(area);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
catch (error) {
|
|
2785
|
+
logger$3.error('Failed to create area:', error);
|
|
2786
|
+
}
|
|
2787
|
+
}, [dataInfo, areas, addArea, removeArea, customShadowRoot, onAreaCreated]);
|
|
2788
|
+
return {
|
|
2789
|
+
onAreaCreatedElement,
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
function useAreaEditMode({ iframeRef, onAreaCreatedElement, enabled = false, }) {
|
|
2308
2794
|
const [hoveredElement, setHoveredElement] = useState(null);
|
|
2309
2795
|
const [isHovering, setIsHovering] = useState(false);
|
|
2796
|
+
// Use ref to always get latest hoveredElement without causing re-renders
|
|
2797
|
+
const hoveredElementRef = useRef(null);
|
|
2798
|
+
const onAreaCreatedElementRef = useRef(onAreaCreatedElement);
|
|
2310
2799
|
const { isEditingMode } = useHeatmapAreaClick();
|
|
2311
2800
|
const iframeDocument = iframeRef.current?.contentDocument;
|
|
2312
2801
|
const isActive = enabled && isEditingMode;
|
|
2802
|
+
// Keep refs in sync
|
|
2803
|
+
useEffect(() => {
|
|
2804
|
+
hoveredElementRef.current = hoveredElement;
|
|
2805
|
+
}, [hoveredElement]);
|
|
2806
|
+
useEffect(() => {
|
|
2807
|
+
onAreaCreatedElementRef.current = onAreaCreatedElement;
|
|
2808
|
+
}, [onAreaCreatedElement]);
|
|
2313
2809
|
const handleMouseMove = useCallback((e) => {
|
|
2314
2810
|
if (!isActive || !iframeDocument)
|
|
2315
2811
|
return;
|
|
2316
|
-
const elements = getElementsAtPoint
|
|
2317
|
-
// Find first selectable element
|
|
2812
|
+
const elements = getElementsAtPoint(iframeDocument, e.clientX, e.clientY);
|
|
2318
2813
|
const selectableElement = elements.find((el, index, arr) => isElementSelectable(el, index, arr));
|
|
2319
|
-
|
|
2814
|
+
const isSelectable = selectableElement && selectableElement !== hoveredElement;
|
|
2815
|
+
if (isSelectable) {
|
|
2320
2816
|
setHoveredElement(selectableElement);
|
|
2321
2817
|
setIsHovering(true);
|
|
2322
|
-
}
|
|
2323
|
-
else if (!selectableElement && hoveredElement) {
|
|
2324
|
-
setHoveredElement(null);
|
|
2325
|
-
setIsHovering(false);
|
|
2326
|
-
}
|
|
2327
|
-
}, [isActive, iframeDocument, hoveredElement]);
|
|
2328
|
-
const handleClick = useCallback((e) => {
|
|
2329
|
-
if (!isActive || !hoveredElement)
|
|
2330
2818
|
return;
|
|
2331
|
-
e.stopPropagation();
|
|
2332
|
-
e.preventDefault();
|
|
2333
|
-
if (onCreateArea) {
|
|
2334
|
-
onCreateArea(hoveredElement);
|
|
2335
2819
|
}
|
|
2336
|
-
|
|
2820
|
+
setHoveredElement(null);
|
|
2821
|
+
setIsHovering(false);
|
|
2822
|
+
}, [isActive, iframeDocument, hoveredElement]);
|
|
2337
2823
|
const handleMouseLeave = useCallback(() => {
|
|
2338
2824
|
setHoveredElement(null);
|
|
2339
2825
|
setIsHovering(false);
|
|
2340
2826
|
}, []);
|
|
2827
|
+
const handleClick = useCallback((e) => {
|
|
2828
|
+
const currentHoveredElement = hoveredElementRef.current;
|
|
2829
|
+
const currentCallback = onAreaCreatedElementRef.current;
|
|
2830
|
+
if (!isActive || !currentHoveredElement)
|
|
2831
|
+
return;
|
|
2832
|
+
e.stopPropagation();
|
|
2833
|
+
e.preventDefault();
|
|
2834
|
+
if (!currentCallback)
|
|
2835
|
+
return;
|
|
2836
|
+
currentCallback(currentHoveredElement);
|
|
2837
|
+
}, [isActive]);
|
|
2341
2838
|
useEffect(() => {
|
|
2342
2839
|
if (!isActive || !iframeDocument) {
|
|
2343
2840
|
setHoveredElement(null);
|
|
@@ -2355,17 +2852,17 @@ function useAreaEditMode({ iframeRef, onCreateArea, enabled = false, }) {
|
|
|
2355
2852
|
});
|
|
2356
2853
|
};
|
|
2357
2854
|
iframeDocument.addEventListener('mousemove', throttledMouseMove);
|
|
2358
|
-
iframeDocument.addEventListener('click', handleClick);
|
|
2359
|
-
iframeDocument.addEventListener('mouseleave', handleMouseLeave);
|
|
2360
2855
|
iframeDocument.addEventListener('scroll', handleMouseLeave);
|
|
2856
|
+
iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
|
|
2857
|
+
iframeDocument.addEventListener('click', handleClick);
|
|
2361
2858
|
return () => {
|
|
2362
2859
|
if (rafId) {
|
|
2363
2860
|
cancelAnimationFrame(rafId);
|
|
2364
2861
|
}
|
|
2365
2862
|
iframeDocument.removeEventListener('mousemove', throttledMouseMove);
|
|
2366
|
-
iframeDocument.removeEventListener('click', handleClick);
|
|
2367
2863
|
iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
|
|
2368
2864
|
iframeDocument.removeEventListener('scroll', handleMouseLeave);
|
|
2865
|
+
iframeDocument.removeEventListener('click', handleClick);
|
|
2369
2866
|
};
|
|
2370
2867
|
}, [isActive, iframeDocument]);
|
|
2371
2868
|
return {
|
|
@@ -2374,109 +2871,209 @@ function useAreaEditMode({ iframeRef, onCreateArea, enabled = false, }) {
|
|
|
2374
2871
|
};
|
|
2375
2872
|
}
|
|
2376
2873
|
|
|
2377
|
-
|
|
2378
|
-
const {
|
|
2874
|
+
const useAreaFilterVisible = (props) => {
|
|
2875
|
+
const { iframeRef, enableOverlapResolution } = props;
|
|
2379
2876
|
const iframeDocument = iframeRef.current?.contentDocument;
|
|
2877
|
+
const { areas, setAreas } = useHeatmapAreaClick();
|
|
2878
|
+
const visibleAreas = useMemo(() => {
|
|
2879
|
+
if (!enableOverlapResolution)
|
|
2880
|
+
return areas;
|
|
2881
|
+
if (!iframeDocument)
|
|
2882
|
+
return areas;
|
|
2883
|
+
return getVisibleAreas(areas, iframeDocument);
|
|
2884
|
+
}, [areas, iframeDocument]);
|
|
2380
2885
|
useEffect(() => {
|
|
2381
|
-
if (
|
|
2382
|
-
|
|
2383
|
-
}
|
|
2384
|
-
let rafId = null;
|
|
2385
|
-
let isUpdating = false;
|
|
2386
|
-
const updateAreaPositions = () => {
|
|
2387
|
-
if (isUpdating)
|
|
2388
|
-
return;
|
|
2389
|
-
isUpdating = true;
|
|
2390
|
-
rafId = requestAnimationFrame(() => {
|
|
2391
|
-
areas.forEach((area) => {
|
|
2392
|
-
if (!area.element || !area.rect)
|
|
2393
|
-
return;
|
|
2394
|
-
try {
|
|
2395
|
-
const newRect = getElementRect(area.element);
|
|
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 });
|
|
2886
|
+
if (enableOverlapResolution && visibleAreas.length !== areas.length) {
|
|
2887
|
+
setAreas(visibleAreas);
|
|
2410
2888
|
}
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
}
|
|
2415
|
-
iframeDocument.removeEventListener('scroll', updateAreaPositions);
|
|
2416
|
-
if (iframeWindow) {
|
|
2417
|
-
iframeWindow.removeEventListener('resize', updateAreaPositions);
|
|
2418
|
-
}
|
|
2419
|
-
};
|
|
2420
|
-
}, [areas, iframeDocument, enabled]);
|
|
2421
|
-
}
|
|
2889
|
+
}, [visibleAreas, areas.length]);
|
|
2890
|
+
return {};
|
|
2891
|
+
};
|
|
2422
2892
|
|
|
2423
|
-
|
|
2893
|
+
function useAreaHydration(options) {
|
|
2894
|
+
const { shadowRoot, enabled = true } = options;
|
|
2895
|
+
const [isInitializing, setIsInitializing] = useState(false);
|
|
2896
|
+
const { dataInfo, clickAreas } = useHeatmapData();
|
|
2424
2897
|
const { vizRef } = useHeatmapViz();
|
|
2425
|
-
const {
|
|
2426
|
-
const
|
|
2427
|
-
if (
|
|
2898
|
+
const { areas, setAreas } = useHeatmapAreaClick();
|
|
2899
|
+
const hydratePersistedAreas = useCallback(() => {
|
|
2900
|
+
if (isInitializing)
|
|
2901
|
+
return;
|
|
2902
|
+
if (!vizRef)
|
|
2903
|
+
return;
|
|
2904
|
+
if (!clickAreas)
|
|
2905
|
+
return;
|
|
2906
|
+
if (!dataInfo)
|
|
2907
|
+
return;
|
|
2908
|
+
logger$3.info(`Hydrating ${clickAreas.length} persisted areas...`);
|
|
2909
|
+
const hydratedAreas = hydrateAreas({ clickAreas, heatmapInfo: dataInfo, vizRef, shadowRoot });
|
|
2910
|
+
if (!hydratedAreas?.length) {
|
|
2911
|
+
logger$3.warn('No areas could be hydrated - all elements may have been removed from DOM');
|
|
2428
2912
|
return;
|
|
2429
|
-
try {
|
|
2430
|
-
vizRef?.clearmap?.();
|
|
2431
|
-
vizRef?.clickmap?.(clickmap);
|
|
2432
|
-
}
|
|
2433
|
-
catch (error) {
|
|
2434
|
-
console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
|
|
2435
2913
|
}
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2914
|
+
setIsInitializing(true);
|
|
2915
|
+
buildAreaGraph(hydratedAreas);
|
|
2916
|
+
setAreas(hydratedAreas);
|
|
2917
|
+
logger$3.info(`Successfully hydrated ${hydratedAreas.length} areas`);
|
|
2918
|
+
}, [dataInfo, vizRef, isInitializing, clickAreas]);
|
|
2919
|
+
useEffect(() => {
|
|
2920
|
+
if (!enabled)
|
|
2921
|
+
return;
|
|
2922
|
+
if (!dataInfo)
|
|
2923
|
+
return;
|
|
2924
|
+
if (!clickAreas)
|
|
2925
|
+
return;
|
|
2926
|
+
if (areas.length)
|
|
2927
|
+
return;
|
|
2928
|
+
hydratePersistedAreas();
|
|
2929
|
+
}, [enabled, dataInfo, clickAreas, areas.length, hydratePersistedAreas]);
|
|
2930
|
+
return {
|
|
2931
|
+
hydratePersistedAreas,
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2439
2934
|
|
|
2440
|
-
|
|
2441
|
-
const {
|
|
2442
|
-
const {
|
|
2443
|
-
const
|
|
2444
|
-
|
|
2445
|
-
if (!vizRef || !scrollmap || scrollmap.length === 0)
|
|
2935
|
+
function useAreaInteraction(options = {}) {
|
|
2936
|
+
const { onAreaClick } = options;
|
|
2937
|
+
const { selectedArea, hoveredArea, isEditingMode, setSelectedArea, setHoveredArea } = useHeatmapAreaClick();
|
|
2938
|
+
const handleAreaClick = useCallback((area) => {
|
|
2939
|
+
if (isEditingMode)
|
|
2446
2940
|
return;
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2941
|
+
// Toggle selection
|
|
2942
|
+
setSelectedArea(selectedArea?.id === area.id ? null : area);
|
|
2943
|
+
// Trigger callback
|
|
2944
|
+
if (onAreaClick) {
|
|
2945
|
+
onAreaClick(area);
|
|
2451
2946
|
}
|
|
2452
|
-
|
|
2453
|
-
|
|
2947
|
+
}, [isEditingMode, selectedArea]);
|
|
2948
|
+
const handleAreaMouseEnter = useCallback((area) => {
|
|
2949
|
+
if (isEditingMode)
|
|
2950
|
+
return;
|
|
2951
|
+
setHoveredArea(area);
|
|
2952
|
+
}, [isEditingMode]);
|
|
2953
|
+
const handleAreaMouseLeave = useCallback((area) => {
|
|
2954
|
+
if (isEditingMode)
|
|
2955
|
+
return;
|
|
2956
|
+
// Only clear if this is the currently hovered area
|
|
2957
|
+
if (hoveredArea?.id === area.id) {
|
|
2958
|
+
setHoveredArea(null);
|
|
2454
2959
|
}
|
|
2455
|
-
}, [
|
|
2456
|
-
return {
|
|
2457
|
-
|
|
2960
|
+
}, [isEditingMode, hoveredArea]);
|
|
2961
|
+
return {
|
|
2962
|
+
handleAreaClick,
|
|
2963
|
+
handleAreaMouseEnter,
|
|
2964
|
+
handleAreaMouseLeave,
|
|
2965
|
+
};
|
|
2966
|
+
}
|
|
2458
2967
|
|
|
2459
|
-
|
|
2460
|
-
const
|
|
2461
|
-
const {
|
|
2462
|
-
const {
|
|
2968
|
+
function useAreaPortals(options) {
|
|
2969
|
+
const { shadowContainer, isReady, iframeRef, customShadowRoot, onAreaClick, onAreaCreated } = options;
|
|
2970
|
+
const { onAreaCreatedElement } = useAreaCreation({ customShadowRoot, onAreaCreated });
|
|
2971
|
+
const { hoveredElement } = useAreaEditMode({ iframeRef, enabled: true, onAreaCreatedElement });
|
|
2972
|
+
const { areas, selectedArea, hoveredArea, isEditingMode } = useHeatmapAreaClick();
|
|
2973
|
+
const { handleAreaClick, handleAreaMouseEnter, handleAreaMouseLeave } = useAreaInteraction({
|
|
2974
|
+
onAreaClick,
|
|
2975
|
+
});
|
|
2976
|
+
const isReadyPortal = shadowContainer && isReady;
|
|
2977
|
+
const areasPortal = isReadyPortal
|
|
2978
|
+
? 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)
|
|
2979
|
+
: null;
|
|
2980
|
+
const isReadyEditHighlight = shadowContainer && isReady && isEditingMode && hoveredElement;
|
|
2981
|
+
const editHighlightPortal = isReadyEditHighlight
|
|
2982
|
+
? createPortal(jsx(AreaEditHighlight, { element: hoveredElement, shadowRoot: customShadowRoot, onClick: onAreaCreatedElement }), shadowContainer)
|
|
2983
|
+
: null;
|
|
2984
|
+
return {
|
|
2985
|
+
areasPortal: areasPortal,
|
|
2986
|
+
editHighlightPortal: editHighlightPortal,
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
function useAreaRectSync(options) {
|
|
2991
|
+
const { iframeDocument, shadowRoot, enabled = true } = options;
|
|
2992
|
+
const { vizRef } = useHeatmapViz();
|
|
2993
|
+
const { areas } = useHeatmapAreaClick();
|
|
2463
2994
|
useEffect(() => {
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
startClickmap();
|
|
2467
|
-
break;
|
|
2468
|
-
case IHeatmapType.Scroll:
|
|
2469
|
-
startScrollmap();
|
|
2470
|
-
break;
|
|
2995
|
+
if (!enabled || !iframeDocument || areas.length === 0) {
|
|
2996
|
+
return;
|
|
2471
2997
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2998
|
+
areas.forEach((area) => {
|
|
2999
|
+
try {
|
|
3000
|
+
let targetElement = area.element;
|
|
3001
|
+
if (!targetElement || !iframeDocument.contains(targetElement)) {
|
|
3002
|
+
if (area.hash) {
|
|
3003
|
+
const elementByHash = vizRef?.get(area.hash);
|
|
3004
|
+
if (elementByHash) {
|
|
3005
|
+
area.element = elementByHash;
|
|
3006
|
+
targetElement = elementByHash;
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
// // If we still can't find the element, set rect to zero
|
|
3011
|
+
// if (!targetElement || !iframeDocument.contains(targetElement)) {
|
|
3012
|
+
// area.rect.update({
|
|
3013
|
+
// width: 0,
|
|
3014
|
+
// height: 0,
|
|
3015
|
+
// top: 0,
|
|
3016
|
+
// left: 0,
|
|
3017
|
+
// absoluteLeft: 0,
|
|
3018
|
+
// absoluteTop: 0,
|
|
3019
|
+
// absoluteRight: 0,
|
|
3020
|
+
// absoluteBottom: 0,
|
|
3021
|
+
// outOfBounds: true,
|
|
3022
|
+
// });
|
|
3023
|
+
// return;
|
|
3024
|
+
// }
|
|
3025
|
+
const newRect = getElementRect(targetElement, shadowRoot);
|
|
3026
|
+
area.rect.update(newRect);
|
|
3027
|
+
}
|
|
3028
|
+
catch (error) {
|
|
3029
|
+
logger$3.error(`Failed to update rect for area ${area.id}:`, error);
|
|
3030
|
+
}
|
|
3031
|
+
});
|
|
3032
|
+
buildAreaGraph(areas);
|
|
3033
|
+
}, [areas, iframeDocument, shadowRoot, enabled, vizRef]);
|
|
3034
|
+
}
|
|
2474
3035
|
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
3036
|
+
/**
|
|
3037
|
+
* Hook to setup and manage the shadow DOM container for area rendering
|
|
3038
|
+
*
|
|
3039
|
+
* @param iframeDocument - The iframe document
|
|
3040
|
+
* @param customShadowRoot - Optional custom shadow root element
|
|
3041
|
+
* @returns Container element and ready state
|
|
3042
|
+
*/
|
|
3043
|
+
function useAreaRendererContainer(iframeDocument, customShadowRoot) {
|
|
3044
|
+
const [shadowContainer, setShadowContainer] = useState(null);
|
|
3045
|
+
const [isReady, setIsReady] = useState(false);
|
|
3046
|
+
const containerRef = useRef(null);
|
|
3047
|
+
useEffect(() => {
|
|
3048
|
+
if (!iframeDocument) {
|
|
3049
|
+
setIsReady(false);
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
const innerContainer = setupAreaRenderingContainer(iframeDocument, customShadowRoot);
|
|
3053
|
+
containerRef.current = innerContainer;
|
|
3054
|
+
setShadowContainer(innerContainer);
|
|
3055
|
+
setIsReady(true);
|
|
3056
|
+
return () => {
|
|
3057
|
+
// Cleanup on unmount
|
|
3058
|
+
const container = innerContainer.parentElement?.parentElement;
|
|
3059
|
+
cleanupAreaRenderingContainer(container);
|
|
3060
|
+
containerRef.current = null;
|
|
3061
|
+
setShadowContainer(null);
|
|
3062
|
+
setIsReady(false);
|
|
3063
|
+
};
|
|
3064
|
+
}, [iframeDocument, customShadowRoot]);
|
|
3065
|
+
return {
|
|
3066
|
+
shadowContainer,
|
|
3067
|
+
isReady,
|
|
3068
|
+
containerRef,
|
|
3069
|
+
};
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
const scrollToElementIfNeeded = (visualRef, rect, scale) => {
|
|
3073
|
+
if (!visualRef.current)
|
|
3074
|
+
return;
|
|
3075
|
+
const visualRect = visualRef.current.getBoundingClientRect();
|
|
3076
|
+
if (isElementInViewport(rect, visualRef, scale)) {
|
|
2480
3077
|
return;
|
|
2481
3078
|
}
|
|
2482
3079
|
const topRaw = rect.top; // - visualRect.top
|
|
@@ -2528,7 +3125,7 @@ const useClickedElement = ({ visualRef, getRect }) => {
|
|
|
2528
3125
|
requestAnimationFrame(() => {
|
|
2529
3126
|
setClickedElement(elementInfo);
|
|
2530
3127
|
});
|
|
2531
|
-
}, [selectedElement, dataInfo, visualRef, widthScale]);
|
|
3128
|
+
}, [selectedElement, dataInfo, visualRef, widthScale]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
2532
3129
|
return { clickedElement, showMissingElement, shouldShowCallout };
|
|
2533
3130
|
};
|
|
2534
3131
|
|
|
@@ -2584,7 +3181,7 @@ const useHeatmapEffects = ({ isVisible }) => {
|
|
|
2584
3181
|
const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
|
|
2585
3182
|
const heatmapWidth = useHeatmapConfigStore((state) => state.width);
|
|
2586
3183
|
const { iframeHeight, widthScale } = useHeatmapViz();
|
|
2587
|
-
|
|
3184
|
+
const getRect = useCallback((element) => {
|
|
2588
3185
|
const hash = element?.hash;
|
|
2589
3186
|
if (!iframeRef.current?.contentDocument || !hash || !visualizer)
|
|
2590
3187
|
return null;
|
|
@@ -2620,6 +3217,7 @@ const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
|
|
|
2620
3217
|
outOfBounds,
|
|
2621
3218
|
};
|
|
2622
3219
|
}, [iframeRef, wrapperRef, visualizer, heatmapWidth, iframeHeight, widthScale]);
|
|
3220
|
+
return { getRect };
|
|
2623
3221
|
};
|
|
2624
3222
|
|
|
2625
3223
|
const debounce = (fn, delay) => {
|
|
@@ -2630,10 +3228,6 @@ const debounce = (fn, delay) => {
|
|
|
2630
3228
|
};
|
|
2631
3229
|
};
|
|
2632
3230
|
|
|
2633
|
-
// ===================== UTILITY FUNCTIONS =====================
|
|
2634
|
-
/**
|
|
2635
|
-
* Lấy bounding box tuyệt đối của element (relative to document)
|
|
2636
|
-
*/
|
|
2637
3231
|
function getBoundingBox(element) {
|
|
2638
3232
|
if (typeof element.getBoundingClientRect !== 'function') {
|
|
2639
3233
|
return null;
|
|
@@ -2654,84 +3248,72 @@ function getBoundingBox(element) {
|
|
|
2654
3248
|
height: Math.floor(rect.height),
|
|
2655
3249
|
};
|
|
2656
3250
|
}
|
|
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
|
-
*/
|
|
3251
|
+
// export function useHeatmapMouseHandler(props: UseHeatmapMouseHandlerProps) {
|
|
3252
|
+
// const { heatmapWrapperRef, iframeRef, parentRef, heatmapInfo, scaleRatio, onElementHover } = props;
|
|
3253
|
+
// const handleMouseMove = useCallback(
|
|
3254
|
+
// (event: MouseEvent) => {
|
|
3255
|
+
// if (
|
|
3256
|
+
// !heatmapWrapperRef?.current ||
|
|
3257
|
+
// !iframeRef?.current ||
|
|
3258
|
+
// !iframeRef.current.contentDocument ||
|
|
3259
|
+
// !heatmapInfo?.elementMapInfo ||
|
|
3260
|
+
// !parentRef?.current
|
|
3261
|
+
// ) {
|
|
3262
|
+
// return;
|
|
3263
|
+
// }
|
|
3264
|
+
// try {
|
|
3265
|
+
// // Calculate scroll position (scaled)
|
|
3266
|
+
// const scrollTop = parentRef.current.scrollTop / scaleRatio;
|
|
3267
|
+
// // Get position of heatmap wrapper
|
|
3268
|
+
// const wrapperRect = heatmapWrapperRef.current.getBoundingClientRect();
|
|
3269
|
+
// // Calculate mouse position in iframe (scaled)
|
|
3270
|
+
// const mouseX = (event.clientX - wrapperRect.left) / scaleRatio;
|
|
3271
|
+
// const mouseY = (event.clientY - wrapperRect.top) / scaleRatio - scrollTop;
|
|
3272
|
+
// const elementsAtPoint = getElementsAtPoint(
|
|
3273
|
+
// iframeRef.current.contentDocument,
|
|
3274
|
+
// Math.round(mouseX),
|
|
3275
|
+
// Math.round(mouseY),
|
|
3276
|
+
// {
|
|
3277
|
+
// filterFn: (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE),
|
|
3278
|
+
// ignoreCanvas: true,
|
|
3279
|
+
// },
|
|
3280
|
+
// );
|
|
3281
|
+
// if (!elementsAtPoint || elementsAtPoint.length === 0) {
|
|
3282
|
+
// return;
|
|
3283
|
+
// }
|
|
3284
|
+
// for (let i = 0; i < elementsAtPoint.length; i++) {
|
|
3285
|
+
// const element = elementsAtPoint[i] as HTMLElement;
|
|
3286
|
+
// const elementHash = element.getAttribute(HEATMAP_ELEMENT_ATTRIBUTE);
|
|
3287
|
+
// if (elementHash && heatmapInfo.elementMapInfo[elementHash]) {
|
|
3288
|
+
// const elementData = heatmapInfo.elementMapInfo[elementHash];
|
|
3289
|
+
// const boundingBox = getBoundingBox(element);
|
|
3290
|
+
// if (boundingBox) {
|
|
3291
|
+
// const rank =
|
|
3292
|
+
// Array.isArray(heatmapInfo.sortedElements) && elementData
|
|
3293
|
+
// ? heatmapInfo.sortedElements.indexOf(elementData) + 1
|
|
3294
|
+
// : NaN;
|
|
3295
|
+
// onElementHover({
|
|
3296
|
+
// ...boundingBox,
|
|
3297
|
+
// width: Math.min(boundingBox.width, heatmapInfo.width || 0),
|
|
3298
|
+
// top: boundingBox.top + scrollTop,
|
|
3299
|
+
// // Metadata
|
|
3300
|
+
// hash: elementHash,
|
|
3301
|
+
// clicks: elementData.totalclicks,
|
|
3302
|
+
// rank: rank,
|
|
3303
|
+
// selector: elementData.selector || '',
|
|
3304
|
+
// });
|
|
3305
|
+
// break;
|
|
3306
|
+
// }
|
|
3307
|
+
// }
|
|
3308
|
+
// }
|
|
3309
|
+
// } catch (error) {
|
|
3310
|
+
// console.warn('Error handling mouse move on heatmap:', error);
|
|
3311
|
+
// }
|
|
3312
|
+
// },
|
|
3313
|
+
// [heatmapWrapperRef, iframeRef, parentRef, heatmapInfo, scaleRatio, onElementHover],
|
|
3314
|
+
// );
|
|
3315
|
+
// return { handleMouseMove };
|
|
3316
|
+
// }
|
|
2735
3317
|
|
|
2736
3318
|
const useHoveredElement = ({ iframeRef, getRect }) => {
|
|
2737
3319
|
const { hoveredElement, setHoveredElement, setSelectedElement } = useHeatmapClick();
|
|
@@ -2814,12 +3396,14 @@ const convertViewportToIframeCoords = (clientX, clientY, iframeRect, scale) => {
|
|
|
2814
3396
|
return { x, y };
|
|
2815
3397
|
};
|
|
2816
3398
|
const findTargetElement = (doc, x, y, heatmapInfo) => {
|
|
2817
|
-
const
|
|
2818
|
-
|
|
3399
|
+
const elementsAtPoint = getElementsAtPoint(doc, Math.round(x), Math.round(y), {
|
|
3400
|
+
filterFn: (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE),
|
|
3401
|
+
ignoreCanvas: true,
|
|
3402
|
+
});
|
|
2819
3403
|
let dataElement = null;
|
|
2820
3404
|
for (let i = 0; i < elementsAtPoint.length; i++) {
|
|
2821
3405
|
const element = elementsAtPoint[i];
|
|
2822
|
-
const elementHash = element
|
|
3406
|
+
const elementHash = getElementHash(element);
|
|
2823
3407
|
if (elementHash && heatmapInfo.elementMapInfo?.[elementHash]) {
|
|
2824
3408
|
const boundingBox = getBoundingBox(element);
|
|
2825
3409
|
if (boundingBox) {
|
|
@@ -2849,6 +3433,177 @@ const isValidElement = (element, heatmapInfo) => {
|
|
|
2849
3433
|
return !!heatmapInfo?.elementMapInfo?.[hash];
|
|
2850
3434
|
};
|
|
2851
3435
|
|
|
3436
|
+
function useAreaScrollSync(options) {
|
|
3437
|
+
const { iframeRef, visualRef, enabled = true } = options;
|
|
3438
|
+
const { widthScale } = useHeatmapViz();
|
|
3439
|
+
const { areas, selectedArea } = useHeatmapAreaClick();
|
|
3440
|
+
const iframeDocument = iframeRef.current?.contentDocument;
|
|
3441
|
+
useEffect(() => {
|
|
3442
|
+
if (!enabled || !iframeDocument || areas.length === 0) {
|
|
3443
|
+
return;
|
|
3444
|
+
}
|
|
3445
|
+
let rafId = null;
|
|
3446
|
+
let isUpdating = false;
|
|
3447
|
+
const updateAreaPositions = () => {
|
|
3448
|
+
if (isUpdating)
|
|
3449
|
+
return;
|
|
3450
|
+
isUpdating = true;
|
|
3451
|
+
rafId = requestAnimationFrame(() => {
|
|
3452
|
+
areas.forEach((area) => {
|
|
3453
|
+
if (!area.element || !area.rect)
|
|
3454
|
+
return;
|
|
3455
|
+
try {
|
|
3456
|
+
const newRect = getElementRect(area.element);
|
|
3457
|
+
area.rect.update(newRect);
|
|
3458
|
+
}
|
|
3459
|
+
catch (error) {
|
|
3460
|
+
console.warn('[useAreaScrollSync] Failed to update area rect:', error);
|
|
3461
|
+
}
|
|
3462
|
+
});
|
|
3463
|
+
isUpdating = false;
|
|
3464
|
+
rafId = null;
|
|
3465
|
+
});
|
|
3466
|
+
};
|
|
3467
|
+
iframeDocument.addEventListener('scroll', updateAreaPositions, { passive: true });
|
|
3468
|
+
const iframeWindow = iframeDocument.defaultView;
|
|
3469
|
+
if (iframeWindow) {
|
|
3470
|
+
iframeWindow.addEventListener('resize', updateAreaPositions, { passive: true });
|
|
3471
|
+
}
|
|
3472
|
+
return () => {
|
|
3473
|
+
if (rafId !== null) {
|
|
3474
|
+
cancelAnimationFrame(rafId);
|
|
3475
|
+
}
|
|
3476
|
+
iframeDocument.removeEventListener('scroll', updateAreaPositions);
|
|
3477
|
+
if (iframeWindow) {
|
|
3478
|
+
iframeWindow.removeEventListener('resize', updateAreaPositions);
|
|
3479
|
+
}
|
|
3480
|
+
};
|
|
3481
|
+
}, [areas, iframeDocument, enabled]);
|
|
3482
|
+
useEffect(() => {
|
|
3483
|
+
if (!selectedArea)
|
|
3484
|
+
return;
|
|
3485
|
+
if (!visualRef)
|
|
3486
|
+
return;
|
|
3487
|
+
if (!selectedArea.rect.value)
|
|
3488
|
+
return;
|
|
3489
|
+
scrollToElementIfNeeded(visualRef, selectedArea.rect.value, widthScale);
|
|
3490
|
+
}, [visualRef, selectedArea, widthScale]);
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
const useAreaTopAutoDetect = (props) => {
|
|
3494
|
+
const { autoCreateTopN, shadowRoot, disabled = false } = props;
|
|
3495
|
+
const [isInitializing, setIsInitializing] = useState(disabled);
|
|
3496
|
+
const { dataInfo, clickAreas } = useHeatmapData();
|
|
3497
|
+
const { vizRef } = useHeatmapViz();
|
|
3498
|
+
const { areas, addArea } = useHeatmapAreaClick();
|
|
3499
|
+
useEffect(() => {
|
|
3500
|
+
if (isInitializing)
|
|
3501
|
+
return;
|
|
3502
|
+
if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks)
|
|
3503
|
+
return;
|
|
3504
|
+
if (!autoCreateTopN)
|
|
3505
|
+
return;
|
|
3506
|
+
if (clickAreas?.length)
|
|
3507
|
+
return;
|
|
3508
|
+
if (areas?.length)
|
|
3509
|
+
return;
|
|
3510
|
+
const topElements = getTopElementsByClicks(dataInfo.elementMapInfo, autoCreateTopN);
|
|
3511
|
+
const newAreas = [];
|
|
3512
|
+
topElements.forEach(({ hash }) => {
|
|
3513
|
+
const element = findElementByHash({ hash, vizRef });
|
|
3514
|
+
if (!element)
|
|
3515
|
+
return;
|
|
3516
|
+
const area = buildAreaNode(element, hash, dataInfo);
|
|
3517
|
+
if (!area)
|
|
3518
|
+
return;
|
|
3519
|
+
newAreas.push(area);
|
|
3520
|
+
});
|
|
3521
|
+
buildAreaGraph(newAreas);
|
|
3522
|
+
const areasToAdd = newAreas.filter((area) => {
|
|
3523
|
+
if (area.parentNode) {
|
|
3524
|
+
return false;
|
|
3525
|
+
}
|
|
3526
|
+
return true;
|
|
3527
|
+
});
|
|
3528
|
+
setIsInitializing(true);
|
|
3529
|
+
areasToAdd.forEach((area) => addArea(area));
|
|
3530
|
+
}, [dataInfo, autoCreateTopN, areas.length, shadowRoot, vizRef, isInitializing, clickAreas]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
3531
|
+
return {};
|
|
3532
|
+
};
|
|
3533
|
+
|
|
3534
|
+
const useAreaClickmap = () => {
|
|
3535
|
+
const { vizRef } = useHeatmapViz();
|
|
3536
|
+
const { clickmap } = useHeatmapData();
|
|
3537
|
+
const start = useCallback(() => {
|
|
3538
|
+
if (!vizRef || !clickmap || clickmap.length === 0)
|
|
3539
|
+
return;
|
|
3540
|
+
try {
|
|
3541
|
+
vizRef?.clearmap?.();
|
|
3542
|
+
}
|
|
3543
|
+
catch (error) {
|
|
3544
|
+
console.error(`🚀 🐥 ~ useAreaClickmap ~ error:`, error);
|
|
3545
|
+
}
|
|
3546
|
+
}, [vizRef, clickmap]);
|
|
3547
|
+
return { start };
|
|
3548
|
+
};
|
|
3549
|
+
|
|
3550
|
+
const useClickmap = () => {
|
|
3551
|
+
const { vizRef } = useHeatmapViz();
|
|
3552
|
+
const { clickmap } = useHeatmapData();
|
|
3553
|
+
const start = useCallback(() => {
|
|
3554
|
+
if (!vizRef || !clickmap || clickmap.length === 0)
|
|
3555
|
+
return;
|
|
3556
|
+
try {
|
|
3557
|
+
vizRef?.clearmap?.();
|
|
3558
|
+
vizRef?.clickmap?.(clickmap);
|
|
3559
|
+
}
|
|
3560
|
+
catch (error) {
|
|
3561
|
+
console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
|
|
3562
|
+
}
|
|
3563
|
+
}, [vizRef, clickmap]);
|
|
3564
|
+
return { start };
|
|
3565
|
+
};
|
|
3566
|
+
|
|
3567
|
+
const useScrollmap = () => {
|
|
3568
|
+
const { vizRef } = useHeatmapViz();
|
|
3569
|
+
const { scrollmap } = useHeatmapData();
|
|
3570
|
+
const start = useCallback(() => {
|
|
3571
|
+
if (!vizRef || !scrollmap || scrollmap.length === 0)
|
|
3572
|
+
return;
|
|
3573
|
+
try {
|
|
3574
|
+
vizRef?.clearmap?.();
|
|
3575
|
+
vizRef?.scrollmap?.(scrollmap);
|
|
3576
|
+
}
|
|
3577
|
+
catch (error) {
|
|
3578
|
+
logger$3.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
|
|
3579
|
+
}
|
|
3580
|
+
}, [vizRef, scrollmap]);
|
|
3581
|
+
return { start };
|
|
3582
|
+
};
|
|
3583
|
+
|
|
3584
|
+
const useHeatmapCanvas = () => {
|
|
3585
|
+
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
3586
|
+
const clickMode = useHeatmapConfigStore((state) => state.clickMode);
|
|
3587
|
+
const { start: startClickmap } = useClickmap();
|
|
3588
|
+
const { start: startAreaClickmap } = useAreaClickmap();
|
|
3589
|
+
const { start: startScrollmap } = useScrollmap();
|
|
3590
|
+
useEffect(() => {
|
|
3591
|
+
switch (heatmapType) {
|
|
3592
|
+
case IHeatmapType.Click:
|
|
3593
|
+
if (clickMode === IClickMode.Default) {
|
|
3594
|
+
startClickmap();
|
|
3595
|
+
}
|
|
3596
|
+
else {
|
|
3597
|
+
startAreaClickmap();
|
|
3598
|
+
}
|
|
3599
|
+
break;
|
|
3600
|
+
case IHeatmapType.Scroll:
|
|
3601
|
+
startScrollmap();
|
|
3602
|
+
break;
|
|
3603
|
+
}
|
|
3604
|
+
}, [heatmapType, clickMode, startClickmap, startAreaClickmap, startScrollmap]);
|
|
3605
|
+
};
|
|
3606
|
+
|
|
2852
3607
|
var MessageType;
|
|
2853
3608
|
(function (MessageType) {
|
|
2854
3609
|
MessageType["GX_DOM_TRACKING_PAYLOAD"] = "GX_DOM_TRACKING_PAYLOAD";
|
|
@@ -3000,6 +3755,7 @@ payloads, onSuccess) {
|
|
|
3000
3755
|
targetWidth: docWidth,
|
|
3001
3756
|
targetHeight: docHeight,
|
|
3002
3757
|
iframe: iframe,
|
|
3758
|
+
debug: false,
|
|
3003
3759
|
onSuccess: (data) => {
|
|
3004
3760
|
iframe.height = `${data.height}px`;
|
|
3005
3761
|
onSuccess(data.height);
|
|
@@ -3137,7 +3893,7 @@ const useReplayRender = () => {
|
|
|
3137
3893
|
};
|
|
3138
3894
|
};
|
|
3139
3895
|
|
|
3140
|
-
const
|
|
3896
|
+
const useHeatmapRenderByMode = (mode) => {
|
|
3141
3897
|
const heatmapResult = useMemo(() => {
|
|
3142
3898
|
switch (mode) {
|
|
3143
3899
|
case 'heatmap':
|
|
@@ -3178,7 +3934,7 @@ const useContainerDimensions = (props) => {
|
|
|
3178
3934
|
return { containerWidth, containerHeight };
|
|
3179
3935
|
};
|
|
3180
3936
|
|
|
3181
|
-
const useContentDimensions = ({ iframeRef
|
|
3937
|
+
const useContentDimensions = ({ iframeRef }) => {
|
|
3182
3938
|
const contentWidth = useHeatmapConfigStore((state) => state.width);
|
|
3183
3939
|
useEffect(() => {
|
|
3184
3940
|
if (!contentWidth)
|
|
@@ -3191,8 +3947,8 @@ const useContentDimensions = ({ iframeRef, }) => {
|
|
|
3191
3947
|
};
|
|
3192
3948
|
|
|
3193
3949
|
const useObserveIframeHeight = (props) => {
|
|
3194
|
-
const { iframeRef
|
|
3195
|
-
const { setIframeHeight } = useHeatmapViz();
|
|
3950
|
+
const { iframeRef } = props;
|
|
3951
|
+
const { iframeHeight, setIframeHeight, isRenderViz } = useHeatmapViz();
|
|
3196
3952
|
const resizeObserverRef = useRef(null);
|
|
3197
3953
|
const mutationObserverRef = useRef(null);
|
|
3198
3954
|
const debounceTimerRef = useRef(null);
|
|
@@ -3250,7 +4006,7 @@ const useObserveIframeHeight = (props) => {
|
|
|
3250
4006
|
}, [updateIframeHeight]);
|
|
3251
4007
|
useEffect(() => {
|
|
3252
4008
|
const iframe = iframeRef.current;
|
|
3253
|
-
if (!iframe || !isRenderViz)
|
|
4009
|
+
if (!iframe || !iframeHeight || !isRenderViz)
|
|
3254
4010
|
return;
|
|
3255
4011
|
const setupObservers = () => {
|
|
3256
4012
|
try {
|
|
@@ -3312,7 +4068,7 @@ const useObserveIframeHeight = (props) => {
|
|
|
3312
4068
|
}
|
|
3313
4069
|
iframe.removeEventListener('load', setupObservers);
|
|
3314
4070
|
};
|
|
3315
|
-
}, [iframeRef, isRenderViz, updateIframeHeight, debouncedUpdate, immediateUpdate]);
|
|
4071
|
+
}, [iframeRef, iframeHeight, isRenderViz, updateIframeHeight, debouncedUpdate, immediateUpdate]);
|
|
3316
4072
|
return {};
|
|
3317
4073
|
};
|
|
3318
4074
|
|
|
@@ -3361,7 +4117,7 @@ const useScaleCalculation = (props) => {
|
|
|
3361
4117
|
return { widthScale, isScaledToFit, minZoomRatio };
|
|
3362
4118
|
};
|
|
3363
4119
|
|
|
3364
|
-
const useScrollSync = ({ widthScale, iframeRef
|
|
4120
|
+
const useScrollSync = ({ widthScale, iframeRef }) => {
|
|
3365
4121
|
const handleScroll = useCallback((scrollTop) => {
|
|
3366
4122
|
const iframe = iframeRef.current;
|
|
3367
4123
|
if (!iframe || widthScale <= 0)
|
|
@@ -3382,13 +4138,13 @@ const useScrollSync = ({ widthScale, iframeRef, }) => {
|
|
|
3382
4138
|
};
|
|
3383
4139
|
|
|
3384
4140
|
const useHeatmapScale = (props) => {
|
|
3385
|
-
const { wrapperRef, iframeRef, iframeHeight
|
|
4141
|
+
const { wrapperRef, iframeRef, iframeHeight } = props;
|
|
3386
4142
|
// 1. Observe container dimensions
|
|
3387
4143
|
const { containerWidth, containerHeight } = useContainerDimensions({ wrapperRef });
|
|
3388
4144
|
// 2. Get content dimensions from config
|
|
3389
4145
|
const { contentWidth } = useContentDimensions({ iframeRef });
|
|
3390
4146
|
// 3. Observe iframe height (now reacts to width changes)
|
|
3391
|
-
useObserveIframeHeight({ iframeRef
|
|
4147
|
+
useObserveIframeHeight({ iframeRef });
|
|
3392
4148
|
// 4. Calculate scale
|
|
3393
4149
|
const { widthScale } = useScaleCalculation({
|
|
3394
4150
|
containerWidth,
|
|
@@ -3401,8 +4157,6 @@ const useHeatmapScale = (props) => {
|
|
|
3401
4157
|
const scaledHeight = iframeHeight * widthScale;
|
|
3402
4158
|
const scaledWidth = contentWidth * widthScale;
|
|
3403
4159
|
return {
|
|
3404
|
-
containerWidth,
|
|
3405
|
-
containerHeight,
|
|
3406
4160
|
scaledWidth,
|
|
3407
4161
|
scaledHeight,
|
|
3408
4162
|
handleScroll,
|
|
@@ -3583,10 +4337,10 @@ const useScrollmapZones = (options) => {
|
|
|
3583
4337
|
const newZones = createZones(scrollmap);
|
|
3584
4338
|
setZones(newZones);
|
|
3585
4339
|
setIsReady(true);
|
|
3586
|
-
|
|
4340
|
+
logger$3.log(`[useScrollmap] Created ${newZones.length} zones in ${mode} mode`);
|
|
3587
4341
|
}
|
|
3588
4342
|
catch (error) {
|
|
3589
|
-
|
|
4343
|
+
logger$3.error('[useScrollmap] Error:', error);
|
|
3590
4344
|
setIsReady(false);
|
|
3591
4345
|
}
|
|
3592
4346
|
}, [enabled, scrollmap, mode, createZones]);
|
|
@@ -3721,12 +4475,8 @@ class PerformanceLogger {
|
|
|
3721
4475
|
const totalRenders = renderMetrics.length;
|
|
3722
4476
|
const totalHookCalls = hookMetrics.length;
|
|
3723
4477
|
const totalStoreUpdates = storeMetrics.length;
|
|
3724
|
-
const renderDurations = renderMetrics
|
|
3725
|
-
|
|
3726
|
-
.map((m) => m.duration);
|
|
3727
|
-
const averageRenderTime = renderDurations.length > 0
|
|
3728
|
-
? renderDurations.reduce((a, b) => a + b, 0) / renderDurations.length
|
|
3729
|
-
: 0;
|
|
4478
|
+
const renderDurations = renderMetrics.filter((m) => m.duration).map((m) => m.duration);
|
|
4479
|
+
const averageRenderTime = renderDurations.length > 0 ? renderDurations.reduce((a, b) => a + b, 0) / renderDurations.length : 0;
|
|
3730
4480
|
// View-specific metrics
|
|
3731
4481
|
const viewMetrics = {};
|
|
3732
4482
|
this.metrics.forEach((metric) => {
|
|
@@ -4253,352 +5003,50 @@ const VizContainer = ({ children, isActive = false }) => {
|
|
|
4253
5003
|
}, children: children }), jsx(PopoverSidebar, {})] }));
|
|
4254
5004
|
};
|
|
4255
5005
|
|
|
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',
|
|
4341
|
-
padding: '8px',
|
|
4342
|
-
borderRadius: '4px',
|
|
4343
|
-
fontSize: '16px',
|
|
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"] })] }));
|
|
4350
|
-
};
|
|
4351
|
-
AreaLabel.displayName = 'AreaLabel';
|
|
4352
|
-
|
|
4353
|
-
const AreaOverlay = ({ area, onClick, onMouseEnter, onMouseLeave, isSelected, isHovered, }) => {
|
|
4354
|
-
const [rect, setRect] = useState(area.rect.value);
|
|
4355
|
-
useEffect(() => {
|
|
4356
|
-
const handleRectChange = (newRect) => {
|
|
4357
|
-
if (newRect) {
|
|
4358
|
-
setRect(newRect);
|
|
4359
|
-
}
|
|
4360
|
-
};
|
|
4361
|
-
area.rect.observe(handleRectChange);
|
|
4362
|
-
return () => {
|
|
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 })) }));
|
|
4392
|
-
};
|
|
4393
|
-
|
|
4394
5006
|
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,
|
|
5007
|
+
const { iframeRef, visualRef, shadowRoot, onAreaCreated, onAreaClick } = options;
|
|
5008
|
+
const iframeDocument = iframeRef.current?.contentDocument || undefined;
|
|
5009
|
+
const { shadowContainer, isReady } = useAreaRendererContainer(iframeDocument, shadowRoot);
|
|
5010
|
+
const { areasPortal, editHighlightPortal } = useAreaPortals({
|
|
4498
5011
|
iframeRef,
|
|
4499
|
-
|
|
5012
|
+
shadowContainer,
|
|
5013
|
+
isReady,
|
|
5014
|
+
onAreaClick,
|
|
5015
|
+
onAreaCreated,
|
|
4500
5016
|
});
|
|
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;
|
|
5017
|
+
useAreaRectSync({ iframeDocument, shadowRoot, enabled: isReady });
|
|
5018
|
+
useAreaScrollSync({ iframeRef, visualRef, enabled: isReady });
|
|
4527
5019
|
return {
|
|
4528
|
-
areasPortal
|
|
4529
|
-
editHighlightPortal
|
|
4530
|
-
shadowContainer,
|
|
5020
|
+
areasPortal,
|
|
5021
|
+
editHighlightPortal,
|
|
4531
5022
|
isReady,
|
|
4532
5023
|
};
|
|
4533
5024
|
}
|
|
4534
5025
|
|
|
4535
|
-
const VizAreaClick = ({ iframeRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true,
|
|
4536
|
-
const
|
|
4537
|
-
const {
|
|
4538
|
-
const { areas, isEditingMode, setIsEditingMode, addArea, clearAreas, setAreas } = useHeatmapAreaClick();
|
|
5026
|
+
const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true, onAreaClick, }) => {
|
|
5027
|
+
const { clickAreas } = useHeatmapData();
|
|
5028
|
+
const { resetView } = useHeatmapAreaClick();
|
|
4539
5029
|
const { areasPortal, editHighlightPortal, isReady } = useAreaRenderer({
|
|
4540
5030
|
iframeRef,
|
|
5031
|
+
visualRef,
|
|
4541
5032
|
shadowRoot,
|
|
4542
5033
|
onAreaClick,
|
|
4543
5034
|
});
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
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
|
|
5035
|
+
useAreaTopAutoDetect({ autoCreateTopN, shadowRoot, disabled: !!clickAreas?.length });
|
|
5036
|
+
useAreaFilterVisible({ iframeRef, enableOverlapResolution });
|
|
5037
|
+
useAreaHydration({ shadowRoot });
|
|
4576
5038
|
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, {}));
|
|
5039
|
+
return () => {
|
|
5040
|
+
resetView();
|
|
5041
|
+
};
|
|
5042
|
+
}, []);
|
|
4595
5043
|
if (!isReady)
|
|
4596
5044
|
return null;
|
|
4597
|
-
return (jsxs(Fragment, { children: [areasPortal, editHighlightPortal
|
|
5045
|
+
return (jsxs(Fragment, { children: [areasPortal, editHighlightPortal] }));
|
|
4598
5046
|
};
|
|
4599
5047
|
VizAreaClick.displayName = 'VizAreaClick';
|
|
4600
5048
|
|
|
4601
|
-
const RankBadge = ({ index, elementRect, widthScale, clickOnElement
|
|
5049
|
+
const RankBadge = ({ index, elementRect, widthScale, clickOnElement }) => {
|
|
4602
5050
|
const style = calculateRankPosition(elementRect, widthScale);
|
|
4603
5051
|
return (jsx("div", { className: "gx-hm-rank-badge", style: style, onClick: clickOnElement, children: index }));
|
|
4604
5052
|
};
|
|
@@ -4614,7 +5062,7 @@ const DefaultRankBadges = ({ getRect, hidden }) => {
|
|
|
4614
5062
|
const rect = getRect(element);
|
|
4615
5063
|
if (!rect)
|
|
4616
5064
|
return null;
|
|
4617
|
-
return
|
|
5065
|
+
return jsx(RankBadge, { index: index + 1, elementRect: rect, widthScale: widthScale }, element.hash);
|
|
4618
5066
|
}) }));
|
|
4619
5067
|
};
|
|
4620
5068
|
|
|
@@ -4686,7 +5134,7 @@ const ElementMissing = ({ show = true }) => {
|
|
|
4686
5134
|
}, "aria-live": "assertive", children: "Element not visible on current screen" }));
|
|
4687
5135
|
};
|
|
4688
5136
|
|
|
4689
|
-
const ElementOverlay = ({ type, element, onClick,
|
|
5137
|
+
const ElementOverlay = ({ type, element, onClick, elementId }) => {
|
|
4690
5138
|
// useRenderCount('ElementOverlay');
|
|
4691
5139
|
const { widthScale } = useHeatmapViz();
|
|
4692
5140
|
if (!element || (element.width === 0 && element.height === 0))
|
|
@@ -4711,29 +5159,31 @@ const ELEMENT_CALLOUT = {
|
|
|
4711
5159
|
};
|
|
4712
5160
|
const HeatmapElements = (props) => {
|
|
4713
5161
|
const viewId = useViewIdContext();
|
|
4714
|
-
const { iframeHeight } = useHeatmapViz();
|
|
4715
5162
|
const clickedElementId = getClickedElementId(viewId, props.isSecondary);
|
|
4716
5163
|
const hoveredElementId = getHoveredElementId(viewId, props.isSecondary);
|
|
4717
|
-
const {
|
|
4718
|
-
const
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
5164
|
+
const { iframeDimensions, isVisible = true, areDefaultRanksHidden } = props;
|
|
5165
|
+
const { iframeHeight } = useHeatmapViz();
|
|
5166
|
+
const { getRect } = useHeatmapElementPosition({
|
|
5167
|
+
iframeRef: props.iframeRef,
|
|
5168
|
+
wrapperRef: props.wrapperRef,
|
|
5169
|
+
visualizer: props.visualizer,
|
|
4722
5170
|
});
|
|
4723
5171
|
const { clickedElement, showMissingElement, shouldShowCallout } = useClickedElement({
|
|
4724
|
-
visualRef,
|
|
5172
|
+
visualRef: props.visualRef,
|
|
4725
5173
|
getRect,
|
|
4726
5174
|
});
|
|
4727
5175
|
const { hoveredElement, handleMouseMove, handleMouseLeave, handleClick } = useHoveredElement({
|
|
4728
|
-
iframeRef,
|
|
5176
|
+
iframeRef: props.iframeRef,
|
|
4729
5177
|
getRect,
|
|
4730
5178
|
});
|
|
4731
|
-
useElementCalloutVisible({ visualRef, getRect });
|
|
5179
|
+
useElementCalloutVisible({ visualRef: props.visualRef, getRect });
|
|
4732
5180
|
useHeatmapEffects({ isVisible });
|
|
4733
5181
|
useRenderCount('HeatmapElements');
|
|
4734
5182
|
if (!isVisible)
|
|
4735
5183
|
return null;
|
|
4736
|
-
|
|
5184
|
+
const isShowHoveredElement = hoveredElement && hoveredElement.hash !== clickedElement?.hash;
|
|
5185
|
+
const isShowClickedElement = shouldShowCallout && clickedElement;
|
|
5186
|
+
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
5187
|
};
|
|
4738
5188
|
|
|
4739
5189
|
const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
|
|
@@ -4759,6 +5209,22 @@ const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
|
|
|
4759
5209
|
} }));
|
|
4760
5210
|
};
|
|
4761
5211
|
|
|
5212
|
+
const VizClickmap = ({ iframeRef, visualRef, wrapperRef }) => {
|
|
5213
|
+
const clickMode = useHeatmapConfigStore((state) => state.clickMode);
|
|
5214
|
+
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
5215
|
+
const isClickType = heatmapType === IHeatmapType.Click;
|
|
5216
|
+
if (!isClickType)
|
|
5217
|
+
return null;
|
|
5218
|
+
switch (clickMode) {
|
|
5219
|
+
case IClickMode.Default:
|
|
5220
|
+
return jsx(VizElements, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef });
|
|
5221
|
+
case IClickMode.Area:
|
|
5222
|
+
return (jsx(VizAreaClick, { iframeRef: iframeRef, visualRef: visualRef, autoCreateTopN: 10, onAreaClick: (area) => {
|
|
5223
|
+
console.log('area clicked', area);
|
|
5224
|
+
} }));
|
|
5225
|
+
}
|
|
5226
|
+
};
|
|
5227
|
+
|
|
4762
5228
|
const ScrollMapMinimap = ({ zones, maxUsers }) => {
|
|
4763
5229
|
const scrollType = useHeatmapConfigStore((state) => state.scrollType);
|
|
4764
5230
|
const { showMinimap } = useHeatmapScroll();
|
|
@@ -4851,7 +5317,7 @@ const TooltipByZone = ({ zone }) => {
|
|
|
4851
5317
|
return jsx(BasicTooltipContent, { zone: zone });
|
|
4852
5318
|
}
|
|
4853
5319
|
};
|
|
4854
|
-
return
|
|
5320
|
+
return jsx("div", { style: { paddingTop: '12px', borderTop: '1px solid #E5E7EB' }, children: contentMarkup() });
|
|
4855
5321
|
};
|
|
4856
5322
|
const BasicTooltipContent = ({ zone }) => {
|
|
4857
5323
|
if (!zone)
|
|
@@ -4861,11 +5327,11 @@ const BasicTooltipContent = ({ zone }) => {
|
|
|
4861
5327
|
const MetricsTooltipContent = ({ zone }) => {
|
|
4862
5328
|
if (!zone)
|
|
4863
5329
|
return null;
|
|
4864
|
-
return (jsxs(Fragment, { children: [jsx("div", { style: { fontWeight: 600, marginBottom: '8px' }, children: zone.label }), jsxs("div", { style: { fontSize: '20px', fontWeight: 700, marginBottom: '8px' }, children: [zone.percUsers.toFixed(2), "% users"] }), zone.metrics && (jsxs("div", { style: { display: 'grid', gap: '6px', fontSize: '13px' }, children: [zone.metrics.revenue !== undefined && (jsx(MetricRow, { label: "Revenue", value: `$${zone.metrics.revenue.toFixed(2)}` })), zone.metrics.conversionRate !== undefined && (jsx(MetricRow, { label: "Conversion", value: `${zone.metrics.conversionRate.toFixed(2)}%` })), zone.metrics.orders !== undefined &&
|
|
5330
|
+
return (jsxs(Fragment, { children: [jsx("div", { style: { fontWeight: 600, marginBottom: '8px' }, children: zone.label }), jsxs("div", { style: { fontSize: '20px', fontWeight: 700, marginBottom: '8px' }, children: [zone.percUsers.toFixed(2), "% users"] }), zone.metrics && (jsxs("div", { style: { display: 'grid', gap: '6px', fontSize: '13px' }, children: [zone.metrics.revenue !== undefined && (jsx(MetricRow, { label: "Revenue", value: `$${zone.metrics.revenue.toFixed(2)}` })), zone.metrics.conversionRate !== undefined && (jsx(MetricRow, { label: "Conversion", value: `${zone.metrics.conversionRate.toFixed(2)}%` })), zone.metrics.orders !== undefined && jsx(MetricRow, { label: "Orders", value: zone.metrics.orders.toString() })] }))] }));
|
|
4865
5331
|
};
|
|
4866
5332
|
const MetricRow = ({ label, value }) => (jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [jsxs("span", { style: { color: '#605E5C' }, children: [label, ":"] }), jsx("span", { style: { fontWeight: 600 }, children: value })] }));
|
|
4867
5333
|
|
|
4868
|
-
const HoverZones = ({ iframeRef, wrapperRef, position, currentScrollPercent
|
|
5334
|
+
const HoverZones = ({ iframeRef, wrapperRef, position, currentScrollPercent }) => {
|
|
4869
5335
|
const { scrollmap } = useHeatmapData();
|
|
4870
5336
|
// const hoveredZone = useHeatmapVizScrollmapStore((state) => state.hoveredZone);
|
|
4871
5337
|
// const setHoveredZone = useHeatmapVizScrollmapStore((state) => state.setHoveredZone);
|
|
@@ -5084,18 +5550,15 @@ const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHe
|
|
|
5084
5550
|
|
|
5085
5551
|
const VizDomRenderer = ({ mode = 'heatmap' }) => {
|
|
5086
5552
|
const contentWidth = useHeatmapConfigStore((state) => state.width || 0);
|
|
5087
|
-
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
5088
5553
|
const wrapperRef = useRef(null);
|
|
5089
5554
|
const visualRef = useRef(null);
|
|
5090
|
-
const {
|
|
5091
|
-
const { iframeHeight
|
|
5092
|
-
const { iframeRef } = useHeatmapVizRender(mode);
|
|
5555
|
+
const { iframeRef } = useHeatmapRenderByMode(mode);
|
|
5556
|
+
const { iframeHeight } = useHeatmapViz();
|
|
5093
5557
|
const { scaledHeight, handleScroll } = useHeatmapScale({
|
|
5094
5558
|
wrapperRef,
|
|
5095
5559
|
iframeRef,
|
|
5096
5560
|
visualRef,
|
|
5097
5561
|
iframeHeight,
|
|
5098
|
-
isRenderViz,
|
|
5099
5562
|
});
|
|
5100
5563
|
useHeatmapCanvas();
|
|
5101
5564
|
useRenderCount('VizDomRenderer');
|
|
@@ -5103,16 +5566,7 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
|
|
|
5103
5566
|
const scrollTop = e.currentTarget.scrollTop;
|
|
5104
5567
|
handleScroll(scrollTop);
|
|
5105
5568
|
};
|
|
5106
|
-
|
|
5107
|
-
setIframeHeight(0);
|
|
5108
|
-
setSelectedElement(null);
|
|
5109
|
-
};
|
|
5110
|
-
useEffect(() => {
|
|
5111
|
-
return cleanUp;
|
|
5112
|
-
}, []);
|
|
5113
|
-
return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [heatmapType === IHeatmapType.Click && (jsx(VizElements, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef })), heatmapType === IHeatmapType.ClickArea && (jsx(VizAreaClick, { iframeRef: iframeRef, onAreaClick: (area) => {
|
|
5114
|
-
console.log('area clicked', area);
|
|
5115
|
-
} })), jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, scrolling: "no" }), jsx(VizScrollMap, { iframeRef: iframeRef, wrapperRef: visualRef })] }));
|
|
5569
|
+
return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [jsx(VizClickmap, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef }), jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, scrolling: "no" }), jsx(VizScrollMap, { iframeRef: iframeRef, wrapperRef: visualRef })] }));
|
|
5116
5570
|
};
|
|
5117
5571
|
|
|
5118
5572
|
const VizLoading = () => {
|
|
@@ -5120,13 +5574,22 @@ const VizLoading = () => {
|
|
|
5120
5574
|
};
|
|
5121
5575
|
|
|
5122
5576
|
const VizDomHeatmap = () => {
|
|
5123
|
-
const { iframeHeight, setIframeHeight } = useHeatmapViz();
|
|
5577
|
+
const { iframeHeight, setIframeHeight, setVizRef, setIsRenderViz } = useHeatmapViz();
|
|
5578
|
+
const { setSelectedElement, setHoveredElement } = useHeatmapClick();
|
|
5579
|
+
const { setSelectedArea, setHoveredArea, setAreas } = useHeatmapAreaClick();
|
|
5124
5580
|
useRenderCount('VizDomHeatmap');
|
|
5581
|
+
const cleanUp = () => {
|
|
5582
|
+
setVizRef(null);
|
|
5583
|
+
setIframeHeight(0);
|
|
5584
|
+
setIsRenderViz(false);
|
|
5585
|
+
setSelectedElement(null);
|
|
5586
|
+
setHoveredElement(null);
|
|
5587
|
+
setSelectedArea(null);
|
|
5588
|
+
setHoveredArea(null);
|
|
5589
|
+
setAreas([]);
|
|
5590
|
+
};
|
|
5125
5591
|
useEffect(() => {
|
|
5126
|
-
return
|
|
5127
|
-
console.log('🚀 🐥 ~ useEffect ~ return:');
|
|
5128
|
-
setIframeHeight(0);
|
|
5129
|
-
};
|
|
5592
|
+
return cleanUp;
|
|
5130
5593
|
}, []);
|
|
5131
5594
|
return (jsxs(VizContainer, { isActive: true, children: [jsx(VizDomRenderer, {}), iframeHeight === 0 && jsx(VizLoading, {})] }));
|
|
5132
5595
|
};
|
|
@@ -5189,21 +5652,16 @@ const HeatmapPreview = () => {
|
|
|
5189
5652
|
|
|
5190
5653
|
const ContentTopBar = () => {
|
|
5191
5654
|
const controls = useHeatmapControlStore((state) => state.controls);
|
|
5192
|
-
useHeatmapConfigStore((state) => state.mode);
|
|
5193
5655
|
const TopBar = controls.TopBar;
|
|
5194
|
-
// In compare mode, hide individual top bars since we have a global header
|
|
5195
|
-
// if (mode === 'compare') {
|
|
5196
|
-
// return null;
|
|
5197
|
-
// }
|
|
5198
5656
|
return (jsx(BoxStack, { id: "gx-hm-content-header", flexDirection: "row", alignItems: "center", overflow: "auto", zIndex: 1, backgroundColor: "white", style: {
|
|
5199
5657
|
borderBottom: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
|
|
5200
5658
|
}, children: TopBar && jsx(TopBar, {}) }));
|
|
5201
5659
|
};
|
|
5202
5660
|
|
|
5203
|
-
const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
|
|
5661
|
+
const HeatmapLayout = ({ data, clickmap, clickAreas, scrollmap, controls, dataInfo, }) => {
|
|
5204
5662
|
useRegisterControl(controls);
|
|
5205
5663
|
useRegisterData(data, dataInfo);
|
|
5206
|
-
useRegisterHeatmap({ clickmap, scrollmap });
|
|
5664
|
+
useRegisterHeatmap({ clickmap, scrollmap, clickAreas });
|
|
5207
5665
|
useRegisterConfig();
|
|
5208
5666
|
performanceLogger.configure({
|
|
5209
5667
|
enabled: true,
|
|
@@ -5231,4 +5689,104 @@ const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
|
|
|
5231
5689
|
}
|
|
5232
5690
|
};
|
|
5233
5691
|
|
|
5234
|
-
|
|
5692
|
+
const AreaEditHighlight = ({ element, shadowRoot, onClick }) => {
|
|
5693
|
+
const [rect, setRect] = useState(null);
|
|
5694
|
+
const highlightRef = useRef(null);
|
|
5695
|
+
useEffect(() => {
|
|
5696
|
+
if (!element) {
|
|
5697
|
+
setRect(null);
|
|
5698
|
+
return;
|
|
5699
|
+
}
|
|
5700
|
+
const elementRect = getElementRect(element);
|
|
5701
|
+
setRect(elementRect);
|
|
5702
|
+
}, [element, shadowRoot]);
|
|
5703
|
+
if (!rect) {
|
|
5704
|
+
return null;
|
|
5705
|
+
}
|
|
5706
|
+
const handleClick = (e) => {
|
|
5707
|
+
if (element && onClick) {
|
|
5708
|
+
e.stopPropagation();
|
|
5709
|
+
e.preventDefault();
|
|
5710
|
+
onClick(element);
|
|
5711
|
+
}
|
|
5712
|
+
};
|
|
5713
|
+
return (jsx("div", { ref: highlightRef, id: AREA_HOVER_ELEMENT_ID, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: handleClick, style: {
|
|
5714
|
+
position: 'absolute',
|
|
5715
|
+
top: `${rect.absoluteTop}px`,
|
|
5716
|
+
left: `${rect.absoluteLeft}px`,
|
|
5717
|
+
width: `${rect.width}px`,
|
|
5718
|
+
height: `${rect.height}px`,
|
|
5719
|
+
zIndex: Number.MAX_SAFE_INTEGER,
|
|
5720
|
+
boxShadow: AREA_HOVER_BOX_SHADOW,
|
|
5721
|
+
backgroundColor: 'rgba(128, 128, 128, 0.4)',
|
|
5722
|
+
backgroundImage: 'repeating-linear-gradient(135deg, transparent, transparent 35px, rgba(255,255,255,.5) 35px, rgba(255,255,255,.5) 70px)',
|
|
5723
|
+
pointerEvents: 'auto',
|
|
5724
|
+
cursor: 'pointer',
|
|
5725
|
+
boxSizing: 'border-box',
|
|
5726
|
+
} }));
|
|
5727
|
+
};
|
|
5728
|
+
AreaEditHighlight.displayName = 'AreaEditHighlight';
|
|
5729
|
+
|
|
5730
|
+
const AreaLabel = ({ clickDist, totalClicks, kind }) => {
|
|
5731
|
+
if (kind === 'money') {
|
|
5732
|
+
return null;
|
|
5733
|
+
}
|
|
5734
|
+
return (jsxs("div", { style: {
|
|
5735
|
+
color: '#161514',
|
|
5736
|
+
backgroundColor: 'rgba(255, 255, 255, 0.86)',
|
|
5737
|
+
display: 'flex',
|
|
5738
|
+
flexDirection: 'column',
|
|
5739
|
+
alignItems: 'center',
|
|
5740
|
+
padding: '8px',
|
|
5741
|
+
borderRadius: '4px',
|
|
5742
|
+
fontSize: '16px',
|
|
5743
|
+
lineHeight: '20px',
|
|
5744
|
+
minWidth: '56px',
|
|
5745
|
+
fontWeight: 600,
|
|
5746
|
+
fontFamily: '"Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif',
|
|
5747
|
+
pointerEvents: 'none',
|
|
5748
|
+
}, children: [jsxs("span", { children: [clickDist.toFixed(2), "%"] }), jsxs("span", { style: { fontSize: '12px', fontWeight: 400, opacity: 0.8 }, children: [totalClicks, " clicks"] })] }));
|
|
5749
|
+
};
|
|
5750
|
+
AreaLabel.displayName = 'AreaLabel';
|
|
5751
|
+
|
|
5752
|
+
const AreaOverlay = ({ area, onClick, onMouseEnter, onMouseLeave, isSelected, isHovered, }) => {
|
|
5753
|
+
const [rect, setRect] = useState(area.rect.value);
|
|
5754
|
+
useEffect(() => {
|
|
5755
|
+
const handleRectChange = (newRect) => {
|
|
5756
|
+
if (newRect) {
|
|
5757
|
+
setRect(newRect);
|
|
5758
|
+
}
|
|
5759
|
+
};
|
|
5760
|
+
area.rect.observe(handleRectChange);
|
|
5761
|
+
return () => {
|
|
5762
|
+
area.rect.unobserve(handleRectChange);
|
|
5763
|
+
};
|
|
5764
|
+
}, [area.rect]);
|
|
5765
|
+
if (!rect)
|
|
5766
|
+
return null;
|
|
5767
|
+
const position = area.isFixed ? 'fixed' : 'absolute';
|
|
5768
|
+
const showLabel = !isRectTooSmallForLabel(rect);
|
|
5769
|
+
const backgroundColor = isHovered ? area.hoverColor : area.color;
|
|
5770
|
+
const boxShadow = isSelected ? '0 0 0 3px red inset' : isHovered ? AREA_HOVER_BOX_SHADOW : '0 0 0 2px white inset';
|
|
5771
|
+
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: `
|
|
5772
|
+
#area-${area.id} {
|
|
5773
|
+
position: ${position};
|
|
5774
|
+
top: ${rect.top}px;
|
|
5775
|
+
left: ${rect.left}px;
|
|
5776
|
+
width: ${rect.width}px;
|
|
5777
|
+
height: ${rect.height}px;
|
|
5778
|
+
background-color: ${backgroundColor};
|
|
5779
|
+
box-shadow: ${boxShadow};
|
|
5780
|
+
box-sizing: border-box;
|
|
5781
|
+
display: flex;
|
|
5782
|
+
align-items: center;
|
|
5783
|
+
justify-content: center;
|
|
5784
|
+
cursor: pointer;
|
|
5785
|
+
transition: background-color 0.2s, box-shadow 0.2s;
|
|
5786
|
+
pointer-events: auto;
|
|
5787
|
+
z-index: ${Number.MAX_SAFE_INTEGER};
|
|
5788
|
+
}
|
|
5789
|
+
` }), showLabel && jsx(AreaLabel, { clickDist: area.clickDist, totalClicks: area.totalclicks, kind: area.kind })] }));
|
|
5790
|
+
};
|
|
5791
|
+
|
|
5792
|
+
export { AreaEditHighlight, AreaOverlay, DEFAULT_SIDEBAR_WIDTH, DEFAULT_VIEW_ID, GraphView, HEATMAP_CONFIG, HEATMAP_IFRAME, HEATMAP_STYLE, HeatmapLayout, IClickMode, IClickType, IHeatmapType, IScrollType, ViewIdContext, Z_INDEX, compareViewPerformance, convertViewportToIframeCoords, createStorePerformanceTracker, downloadPerformanceReport, getCompareViewId, getMetricsByViewId, getPerformanceReportJSON, getScrollGradientColor, performanceLogger, printPerformanceSummary, scrollToElementIfNeeded, sendPerformanceReport, serializeAreas, trackStoreAction, useAreaCreation, useAreaEditMode, useAreaFilterVisible, useAreaHydration, useAreaInteraction, useAreaPortals, useAreaRectSync, useAreaRendererContainer, useAreaScrollSync, useAreaTopAutoDetect, useClickedElement, useDebounceCallback, useElementCalloutVisible, useHeatmapAreaClick, useHeatmapCanvas, useHeatmapClick, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapCopyView, useHeatmapData, useHeatmapEffects, useHeatmapElementPosition, useHeatmapLiveStore, useHeatmapRenderByMode, useHeatmapScale, useHeatmapScroll, useHeatmapViz, useHoveredElement, useMeasureFunction, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useRenderCount, useScrollmapZones, useTrackHookCall, useViewIdContext, useVizLiveRender, useWhyDidYouUpdate, useWrapperRefHeight, useZonePositions, withPerformanceTracking };
|