@gemx-dev/heatmap-react 3.5.51 → 3.5.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/Layout/HeatmapLayout.d.ts.map +1 -1
- package/dist/esm/components/Layout/HeatmapPreview.d.ts +2 -0
- package/dist/esm/components/Layout/HeatmapPreview.d.ts.map +1 -0
- package/dist/esm/components/Layout/MetricBar/ContentMetricBar.d.ts.map +1 -0
- package/dist/esm/components/Layout/MetricBar/index.d.ts +2 -0
- package/dist/esm/components/Layout/MetricBar/index.d.ts.map +1 -0
- package/dist/esm/components/Layout/Toolbar/ContentToolbar.d.ts.map +1 -0
- package/dist/esm/components/Layout/Toolbar/index.d.ts +2 -0
- package/dist/esm/components/Layout/Toolbar/index.d.ts.map +1 -0
- package/dist/esm/components/Layout/TopBar/ContentTopBar.d.ts.map +1 -0
- package/dist/esm/components/Layout/TopBar/index.d.ts +2 -0
- package/dist/esm/components/Layout/TopBar/index.d.ts.map +1 -0
- package/dist/esm/components/Layout/VizByMode/ContentVizByMode.d.ts.map +1 -0
- package/dist/esm/components/Layout/VizByMode/index.d.ts +2 -0
- package/dist/esm/components/Layout/VizByMode/index.d.ts.map +1 -0
- package/dist/esm/components/VizAreaClick/AreaEditHighlight.d.ts +8 -0
- package/dist/esm/components/VizAreaClick/AreaEditHighlight.d.ts.map +1 -0
- package/dist/esm/components/VizAreaClick/AreaLabel.d.ts +8 -0
- package/dist/esm/components/VizAreaClick/AreaLabel.d.ts.map +1 -0
- package/dist/esm/components/VizAreaClick/AreaOverlay.d.ts +12 -0
- package/dist/esm/components/VizAreaClick/AreaOverlay.d.ts.map +1 -0
- package/dist/esm/components/VizAreaClick/PortalAreaRenderer.d.ts +16 -0
- package/dist/esm/components/VizAreaClick/PortalAreaRenderer.d.ts.map +1 -0
- package/dist/esm/components/VizAreaClick/VizAreaClick.d.ts +11 -0
- package/dist/esm/components/VizAreaClick/VizAreaClick.d.ts.map +1 -0
- package/dist/esm/components/VizAreaClick/index.d.ts +4 -0
- package/dist/esm/components/VizAreaClick/index.d.ts.map +1 -0
- 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/VizContainer.d.ts.map +1 -1
- package/dist/esm/components/VizDom/VizDomRenderer.d.ts.map +1 -1
- package/dist/esm/components/VizElement/ElementOverlay.d.ts +0 -1
- package/dist/esm/components/VizElement/ElementOverlay.d.ts.map +1 -1
- package/dist/esm/components/VizElement/HeatmapElements.d.ts.map +1 -1
- package/dist/esm/components/VizScrollmap/index.d.ts +1 -0
- package/dist/esm/components/VizScrollmap/index.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/configs/style.d.ts +1 -1
- package/dist/esm/configs/style.d.ts.map +1 -1
- package/dist/esm/configs/viewId.d.ts +1 -19
- package/dist/esm/configs/viewId.d.ts.map +1 -1
- package/dist/esm/constants/index.d.ts +3 -13
- 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 +16 -0
- package/dist/esm/constants/viz-area-click.d.ts.map +1 -0
- package/dist/esm/constants/viz-elm-callout.d.ts +8 -0
- package/dist/esm/constants/viz-elm-callout.d.ts.map +1 -0
- package/dist/esm/contexts/ViewIdContext.d.ts +3 -0
- package/dist/esm/contexts/ViewIdContext.d.ts.map +1 -0
- package/dist/esm/contexts/index.d.ts +1 -1
- package/dist/esm/contexts/index.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 +6 -2
- 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/observable.d.ts +11 -0
- package/dist/esm/helpers/observable.d.ts.map +1 -0
- package/dist/esm/helpers/viewport/element.d.ts +3 -0
- package/dist/esm/helpers/viewport/element.d.ts.map +1 -0
- package/dist/esm/helpers/viewport/index.d.ts +2 -0
- package/dist/esm/helpers/viewport/index.d.ts.map +1 -0
- package/dist/esm/helpers/viz-area-click/area-builder.d.ts +8 -0
- package/dist/esm/helpers/viz-area-click/area-builder.d.ts.map +1 -0
- package/dist/esm/helpers/viz-area-click/area-color.d.ts +13 -0
- package/dist/esm/helpers/viz-area-click/area-color.d.ts.map +1 -0
- package/dist/esm/helpers/viz-area-click/area-overlap.d.ts +44 -0
- package/dist/esm/helpers/viz-area-click/area-overlap.d.ts.map +1 -0
- 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 +13 -0
- package/dist/esm/helpers/viz-area-click/area-utils.d.ts.map +1 -0
- package/dist/esm/helpers/viz-area-click/index.d.ts +6 -0
- package/dist/esm/helpers/viz-area-click/index.d.ts.map +1 -0
- package/dist/esm/helpers/{iframe.d.ts → viz-dom/decode.d.ts} +2 -2
- package/dist/esm/helpers/viz-dom/decode.d.ts.map +1 -0
- package/dist/esm/helpers/viz-dom/index.d.ts +2 -0
- package/dist/esm/helpers/viz-dom/index.d.ts.map +1 -0
- package/dist/esm/helpers/viz-elm-callout/dimensions.d.ts +3 -3
- package/dist/esm/helpers/viz-elm-callout/dimensions.d.ts.map +1 -1
- package/dist/esm/helpers/viz-elm-callout/element.d.ts +10 -0
- package/dist/esm/helpers/viz-elm-callout/element.d.ts.map +1 -0
- 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/index.d.ts +2 -0
- package/dist/esm/helpers/viz-elm-callout/index.d.ts.map +1 -1
- package/dist/esm/helpers/viz-elm-callout/position-candidates.d.ts +4 -5
- 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 +3 -3
- 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/helpers/viz-elm-callout/rank-calculator.d.ts +7 -0
- package/dist/esm/helpers/viz-elm-callout/rank-calculator.d.ts.map +1 -0
- package/dist/esm/helpers/viz-elm-callout/viz-elm.d.ts +4 -2
- package/dist/esm/helpers/viz-elm-callout/viz-elm.d.ts.map +1 -1
- package/dist/esm/hooks/index.d.ts +3 -1
- package/dist/esm/hooks/index.d.ts.map +1 -1
- package/dist/esm/hooks/register/useRegisterConfig.d.ts.map +1 -1
- package/dist/esm/hooks/view-context/index.d.ts +3 -3
- package/dist/esm/hooks/view-context/index.d.ts.map +1 -1
- package/dist/esm/hooks/view-context/useHeatmapAreaClick.d.ts +20 -0
- package/dist/esm/hooks/view-context/useHeatmapAreaClick.d.ts.map +1 -0
- package/dist/{umd/hooks/view-context/useHeatmapInteraction.d.ts → esm/hooks/view-context/useHeatmapClick.d.ts} +5 -4
- package/dist/esm/hooks/view-context/useHeatmapClick.d.ts.map +1 -0
- package/dist/esm/hooks/view-context/useHeatmapCopyView.d.ts.map +1 -1
- package/dist/{umd/hooks/view-context/useHeatmapVizScrollmap.d.ts → esm/hooks/view-context/useHeatmapScroll.d.ts} +5 -4
- package/dist/esm/hooks/view-context/useHeatmapScroll.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/index.d.ts +10 -0
- package/dist/esm/hooks/viz-area-click/index.d.ts.map +1 -0
- 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 +12 -0
- package/dist/esm/hooks/viz-area-click/useAreaEditMode.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaFilterVisible.d.ts +7 -0
- package/dist/esm/hooks/viz-area-click/useAreaFilterVisible.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaInteraction.d.ts +17 -0
- package/dist/esm/hooks/viz-area-click/useAreaInteraction.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaPortals.d.ts +15 -0
- package/dist/esm/hooks/viz-area-click/useAreaPortals.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaRectSync.d.ts +8 -0
- package/dist/esm/hooks/viz-area-click/useAreaRectSync.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaRendererContainer.d.ts +13 -0
- package/dist/esm/hooks/viz-area-click/useAreaRendererContainer.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaScrollSync.d.ts +7 -0
- package/dist/esm/hooks/viz-area-click/useAreaScrollSync.d.ts.map +1 -0
- package/dist/esm/hooks/viz-area-click/useAreaTopAutoDetect.d.ts +8 -0
- package/dist/esm/hooks/viz-area-click/useAreaTopAutoDetect.d.ts.map +1 -0
- package/dist/esm/hooks/viz-canvas/useAreaClickmap.d.ts +4 -0
- package/dist/esm/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -0
- package/dist/esm/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
- package/dist/esm/hooks/viz-elm/useClickedElement.d.ts +1 -0
- package/dist/esm/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
- package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -1
- package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
- package/dist/esm/hooks/viz-elm/useHeatmapMouseHandler.d.ts +0 -25
- package/dist/esm/hooks/viz-elm/useHeatmapMouseHandler.d.ts.map +1 -1
- package/dist/esm/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
- package/dist/esm/hooks/viz-render/index.d.ts +1 -1
- package/dist/esm/hooks/viz-render/index.d.ts.map +1 -1
- package/dist/esm/hooks/viz-render/useHeatmapRender.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-scroll/index.d.ts.map +1 -0
- package/dist/esm/hooks/viz-scroll/useScrollmapZones.d.ts.map +1 -0
- package/dist/esm/hooks/viz-scroll/useZonePositions.d.ts.map +1 -0
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1984 -717
- package/dist/esm/index.mjs +1984 -717
- package/dist/esm/stores/config.d.ts +3 -1
- package/dist/esm/stores/config.d.ts.map +1 -1
- package/dist/esm/stores/index.d.ts +3 -2
- package/dist/esm/stores/index.d.ts.map +1 -1
- package/dist/esm/stores/mode-compare.d.ts +0 -14
- package/dist/esm/stores/mode-compare.d.ts.map +1 -1
- package/dist/esm/stores/viz-area-click.d.ts +29 -0
- package/dist/esm/stores/viz-area-click.d.ts.map +1 -0
- package/dist/esm/stores/{interaction.d.ts → viz-click.d.ts} +5 -5
- package/dist/esm/stores/viz-click.d.ts.map +1 -0
- package/dist/esm/types/compare.d.ts +0 -50
- package/dist/esm/types/compare.d.ts.map +1 -1
- package/dist/esm/types/heatmap.d.ts +5 -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/index.d.ts +5 -4
- package/dist/esm/types/index.d.ts.map +1 -1
- package/dist/esm/types/viz-area-click.d.ts +84 -0
- package/dist/esm/types/viz-area-click.d.ts.map +1 -0
- package/dist/esm/types/viz-elm-callout.d.ts +15 -0
- package/dist/esm/types/viz-elm-callout.d.ts.map +1 -1
- package/dist/umd/components/Layout/HeatmapLayout.d.ts.map +1 -1
- package/dist/umd/components/Layout/HeatmapPreview.d.ts +2 -0
- package/dist/umd/components/Layout/HeatmapPreview.d.ts.map +1 -0
- package/dist/umd/components/Layout/MetricBar/ContentMetricBar.d.ts.map +1 -0
- package/dist/umd/components/Layout/MetricBar/index.d.ts +2 -0
- package/dist/umd/components/Layout/MetricBar/index.d.ts.map +1 -0
- package/dist/umd/components/Layout/Toolbar/ContentToolbar.d.ts.map +1 -0
- package/dist/umd/components/Layout/Toolbar/index.d.ts +2 -0
- package/dist/umd/components/Layout/Toolbar/index.d.ts.map +1 -0
- package/dist/umd/components/Layout/TopBar/ContentTopBar.d.ts.map +1 -0
- package/dist/umd/components/Layout/TopBar/index.d.ts +2 -0
- package/dist/umd/components/Layout/TopBar/index.d.ts.map +1 -0
- package/dist/umd/components/Layout/VizByMode/ContentVizByMode.d.ts.map +1 -0
- package/dist/umd/components/Layout/VizByMode/index.d.ts +2 -0
- package/dist/umd/components/Layout/VizByMode/index.d.ts.map +1 -0
- package/dist/umd/components/VizAreaClick/AreaEditHighlight.d.ts +8 -0
- package/dist/umd/components/VizAreaClick/AreaEditHighlight.d.ts.map +1 -0
- package/dist/umd/components/VizAreaClick/AreaLabel.d.ts +8 -0
- package/dist/umd/components/VizAreaClick/AreaLabel.d.ts.map +1 -0
- package/dist/umd/components/VizAreaClick/AreaOverlay.d.ts +12 -0
- package/dist/umd/components/VizAreaClick/AreaOverlay.d.ts.map +1 -0
- package/dist/umd/components/VizAreaClick/PortalAreaRenderer.d.ts +16 -0
- package/dist/umd/components/VizAreaClick/PortalAreaRenderer.d.ts.map +1 -0
- package/dist/umd/components/VizAreaClick/VizAreaClick.d.ts +11 -0
- package/dist/umd/components/VizAreaClick/VizAreaClick.d.ts.map +1 -0
- package/dist/umd/components/VizAreaClick/index.d.ts +4 -0
- package/dist/umd/components/VizAreaClick/index.d.ts.map +1 -0
- 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/VizContainer.d.ts.map +1 -1
- package/dist/umd/components/VizDom/VizDomRenderer.d.ts.map +1 -1
- package/dist/umd/components/VizElement/ElementOverlay.d.ts +0 -1
- package/dist/umd/components/VizElement/ElementOverlay.d.ts.map +1 -1
- package/dist/umd/components/VizElement/HeatmapElements.d.ts.map +1 -1
- package/dist/umd/components/VizScrollmap/index.d.ts +1 -0
- package/dist/umd/components/VizScrollmap/index.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/configs/style.d.ts +1 -1
- package/dist/umd/configs/style.d.ts.map +1 -1
- package/dist/umd/configs/viewId.d.ts +1 -19
- package/dist/umd/configs/viewId.d.ts.map +1 -1
- package/dist/umd/constants/index.d.ts +3 -13
- 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 +16 -0
- package/dist/umd/constants/viz-area-click.d.ts.map +1 -0
- package/dist/umd/constants/viz-elm-callout.d.ts +8 -0
- package/dist/umd/constants/viz-elm-callout.d.ts.map +1 -0
- package/dist/umd/contexts/ViewIdContext.d.ts +3 -0
- package/dist/umd/contexts/ViewIdContext.d.ts.map +1 -0
- package/dist/umd/contexts/index.d.ts +1 -1
- package/dist/umd/contexts/index.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 +6 -2
- 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/observable.d.ts +11 -0
- package/dist/umd/helpers/observable.d.ts.map +1 -0
- package/dist/umd/helpers/viewport/element.d.ts +3 -0
- package/dist/umd/helpers/viewport/element.d.ts.map +1 -0
- package/dist/umd/helpers/viewport/index.d.ts +2 -0
- package/dist/umd/helpers/viewport/index.d.ts.map +1 -0
- package/dist/umd/helpers/viz-area-click/area-builder.d.ts +8 -0
- package/dist/umd/helpers/viz-area-click/area-builder.d.ts.map +1 -0
- package/dist/umd/helpers/viz-area-click/area-color.d.ts +13 -0
- package/dist/umd/helpers/viz-area-click/area-color.d.ts.map +1 -0
- package/dist/umd/helpers/viz-area-click/area-overlap.d.ts +44 -0
- package/dist/umd/helpers/viz-area-click/area-overlap.d.ts.map +1 -0
- 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 +13 -0
- package/dist/umd/helpers/viz-area-click/area-utils.d.ts.map +1 -0
- package/dist/umd/helpers/viz-area-click/index.d.ts +6 -0
- package/dist/umd/helpers/viz-area-click/index.d.ts.map +1 -0
- package/dist/umd/helpers/{iframe.d.ts → viz-dom/decode.d.ts} +2 -2
- package/dist/umd/helpers/viz-dom/decode.d.ts.map +1 -0
- package/dist/umd/helpers/viz-dom/index.d.ts +2 -0
- package/dist/umd/helpers/viz-dom/index.d.ts.map +1 -0
- package/dist/umd/helpers/viz-elm-callout/dimensions.d.ts +3 -3
- package/dist/umd/helpers/viz-elm-callout/dimensions.d.ts.map +1 -1
- package/dist/umd/helpers/viz-elm-callout/element.d.ts +10 -0
- package/dist/umd/helpers/viz-elm-callout/element.d.ts.map +1 -0
- 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/index.d.ts +2 -0
- package/dist/umd/helpers/viz-elm-callout/index.d.ts.map +1 -1
- package/dist/umd/helpers/viz-elm-callout/position-candidates.d.ts +4 -5
- 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 +3 -3
- 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/helpers/viz-elm-callout/rank-calculator.d.ts +7 -0
- package/dist/umd/helpers/viz-elm-callout/rank-calculator.d.ts.map +1 -0
- package/dist/umd/helpers/viz-elm-callout/viz-elm.d.ts +4 -2
- package/dist/umd/helpers/viz-elm-callout/viz-elm.d.ts.map +1 -1
- package/dist/umd/hooks/index.d.ts +3 -1
- package/dist/umd/hooks/index.d.ts.map +1 -1
- package/dist/umd/hooks/register/useRegisterConfig.d.ts.map +1 -1
- package/dist/umd/hooks/view-context/index.d.ts +3 -3
- package/dist/umd/hooks/view-context/index.d.ts.map +1 -1
- package/dist/umd/hooks/view-context/useHeatmapAreaClick.d.ts +20 -0
- package/dist/umd/hooks/view-context/useHeatmapAreaClick.d.ts.map +1 -0
- package/dist/{esm/hooks/view-context/useHeatmapInteraction.d.ts → umd/hooks/view-context/useHeatmapClick.d.ts} +5 -4
- package/dist/umd/hooks/view-context/useHeatmapClick.d.ts.map +1 -0
- package/dist/umd/hooks/view-context/useHeatmapCopyView.d.ts.map +1 -1
- package/dist/{esm/hooks/view-context/useHeatmapVizScrollmap.d.ts → umd/hooks/view-context/useHeatmapScroll.d.ts} +5 -4
- package/dist/umd/hooks/view-context/useHeatmapScroll.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/index.d.ts +10 -0
- package/dist/umd/hooks/viz-area-click/index.d.ts.map +1 -0
- 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 +12 -0
- package/dist/umd/hooks/viz-area-click/useAreaEditMode.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaFilterVisible.d.ts +7 -0
- package/dist/umd/hooks/viz-area-click/useAreaFilterVisible.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaInteraction.d.ts +17 -0
- package/dist/umd/hooks/viz-area-click/useAreaInteraction.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaPortals.d.ts +15 -0
- package/dist/umd/hooks/viz-area-click/useAreaPortals.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaRectSync.d.ts +8 -0
- package/dist/umd/hooks/viz-area-click/useAreaRectSync.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaRendererContainer.d.ts +13 -0
- package/dist/umd/hooks/viz-area-click/useAreaRendererContainer.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaScrollSync.d.ts +7 -0
- package/dist/umd/hooks/viz-area-click/useAreaScrollSync.d.ts.map +1 -0
- package/dist/umd/hooks/viz-area-click/useAreaTopAutoDetect.d.ts +8 -0
- package/dist/umd/hooks/viz-area-click/useAreaTopAutoDetect.d.ts.map +1 -0
- package/dist/umd/hooks/viz-canvas/useAreaClickmap.d.ts +4 -0
- package/dist/umd/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -0
- package/dist/umd/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
- package/dist/umd/hooks/viz-elm/useClickedElement.d.ts +1 -0
- package/dist/umd/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
- package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -1
- package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
- package/dist/umd/hooks/viz-elm/useHeatmapMouseHandler.d.ts +0 -25
- package/dist/umd/hooks/viz-elm/useHeatmapMouseHandler.d.ts.map +1 -1
- package/dist/umd/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
- package/dist/umd/hooks/viz-render/index.d.ts +1 -1
- package/dist/umd/hooks/viz-render/index.d.ts.map +1 -1
- package/dist/umd/hooks/viz-render/useHeatmapRender.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-scroll/index.d.ts.map +1 -0
- package/dist/umd/hooks/viz-scroll/useScrollmapZones.d.ts.map +1 -0
- package/dist/umd/hooks/viz-scroll/useZonePositions.d.ts.map +1 -0
- package/dist/umd/index.d.ts +2 -1
- package/dist/umd/index.d.ts.map +1 -1
- package/dist/umd/index.js +2 -2
- package/dist/umd/stores/config.d.ts +3 -1
- package/dist/umd/stores/config.d.ts.map +1 -1
- package/dist/umd/stores/index.d.ts +3 -2
- package/dist/umd/stores/index.d.ts.map +1 -1
- package/dist/umd/stores/mode-compare.d.ts +0 -14
- package/dist/umd/stores/mode-compare.d.ts.map +1 -1
- package/dist/umd/stores/viz-area-click.d.ts +29 -0
- package/dist/umd/stores/viz-area-click.d.ts.map +1 -0
- package/dist/umd/stores/{interaction.d.ts → viz-click.d.ts} +5 -5
- package/dist/umd/stores/viz-click.d.ts.map +1 -0
- package/dist/umd/types/compare.d.ts +0 -50
- package/dist/umd/types/compare.d.ts.map +1 -1
- package/dist/umd/types/heatmap.d.ts +5 -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/index.d.ts +5 -4
- package/dist/umd/types/index.d.ts.map +1 -1
- package/dist/umd/types/viz-area-click.d.ts +84 -0
- package/dist/umd/types/viz-area-click.d.ts.map +1 -0
- package/dist/umd/types/viz-elm-callout.d.ts +15 -0
- package/dist/umd/types/viz-elm-callout.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/esm/components/Layout/ContentMetricBar.d.ts.map +0 -1
- package/dist/esm/components/Layout/ContentToolbar.d.ts.map +0 -1
- package/dist/esm/components/Layout/ContentTopBar.d.ts.map +0 -1
- package/dist/esm/components/Layout/ContentVizByMode.d.ts.map +0 -1
- package/dist/esm/components/Layout/WrapperLayout.d.ts +0 -2
- package/dist/esm/components/Layout/WrapperLayout.d.ts.map +0 -1
- package/dist/esm/components/Layout/WrapperPreview.d.ts +0 -2
- package/dist/esm/components/Layout/WrapperPreview.d.ts.map +0 -1
- package/dist/esm/contexts/CompareViewContext.d.ts +0 -28
- package/dist/esm/contexts/CompareViewContext.d.ts.map +0 -1
- package/dist/esm/helpers/iframe.d.ts.map +0 -1
- package/dist/esm/helpers/viz-elm-callout/constants.d.ts +0 -4
- package/dist/esm/helpers/viz-elm-callout/constants.d.ts.map +0 -1
- package/dist/esm/helpers/viz-elm-callout/types.d.ts +0 -17
- package/dist/esm/helpers/viz-elm-callout/types.d.ts.map +0 -1
- package/dist/esm/helpers/viz-elm.d.ts +0 -9
- package/dist/esm/helpers/viz-elm.d.ts.map +0 -1
- package/dist/esm/hooks/view-context/useHeatmapInteraction.d.ts.map +0 -1
- package/dist/esm/hooks/view-context/useHeatmapVizScrollmap.d.ts.map +0 -1
- package/dist/esm/hooks/view-context/useViewId.d.ts +0 -15
- package/dist/esm/hooks/view-context/useViewId.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/esm/hooks/viz-scrollmap/index.d.ts.map +0 -1
- package/dist/esm/hooks/viz-scrollmap/useScrollmapZones.d.ts.map +0 -1
- package/dist/esm/hooks/viz-scrollmap/useZonePositions.d.ts.map +0 -1
- package/dist/esm/stores/interaction.d.ts.map +0 -1
- package/dist/umd/components/Layout/ContentMetricBar.d.ts.map +0 -1
- package/dist/umd/components/Layout/ContentToolbar.d.ts.map +0 -1
- package/dist/umd/components/Layout/ContentTopBar.d.ts.map +0 -1
- package/dist/umd/components/Layout/ContentVizByMode.d.ts.map +0 -1
- package/dist/umd/components/Layout/WrapperLayout.d.ts +0 -2
- package/dist/umd/components/Layout/WrapperLayout.d.ts.map +0 -1
- package/dist/umd/components/Layout/WrapperPreview.d.ts +0 -2
- package/dist/umd/components/Layout/WrapperPreview.d.ts.map +0 -1
- package/dist/umd/contexts/CompareViewContext.d.ts +0 -28
- package/dist/umd/contexts/CompareViewContext.d.ts.map +0 -1
- package/dist/umd/helpers/iframe.d.ts.map +0 -1
- package/dist/umd/helpers/viz-elm-callout/constants.d.ts +0 -4
- package/dist/umd/helpers/viz-elm-callout/constants.d.ts.map +0 -1
- package/dist/umd/helpers/viz-elm-callout/types.d.ts +0 -17
- package/dist/umd/helpers/viz-elm-callout/types.d.ts.map +0 -1
- package/dist/umd/helpers/viz-elm.d.ts +0 -9
- package/dist/umd/helpers/viz-elm.d.ts.map +0 -1
- package/dist/umd/hooks/view-context/useHeatmapInteraction.d.ts.map +0 -1
- package/dist/umd/hooks/view-context/useHeatmapVizScrollmap.d.ts.map +0 -1
- package/dist/umd/hooks/view-context/useViewId.d.ts +0 -15
- package/dist/umd/hooks/view-context/useViewId.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/umd/hooks/viz-scrollmap/index.d.ts.map +0 -1
- package/dist/umd/hooks/viz-scrollmap/useScrollmapZones.d.ts.map +0 -1
- package/dist/umd/hooks/viz-scrollmap/useZonePositions.d.ts.map +0 -1
- package/dist/umd/stores/interaction.d.ts.map +0 -1
- /package/dist/esm/components/Layout/{ContentMetricBar.d.ts → MetricBar/ContentMetricBar.d.ts} +0 -0
- /package/dist/esm/components/Layout/{ContentToolbar.d.ts → Toolbar/ContentToolbar.d.ts} +0 -0
- /package/dist/esm/components/Layout/{ContentTopBar.d.ts → TopBar/ContentTopBar.d.ts} +0 -0
- /package/dist/esm/components/Layout/{ContentVizByMode.d.ts → VizByMode/ContentVizByMode.d.ts} +0 -0
- /package/dist/esm/hooks/{viz-scrollmap → viz-scroll}/index.d.ts +0 -0
- /package/dist/esm/hooks/{viz-scrollmap → viz-scroll}/useScrollmapZones.d.ts +0 -0
- /package/dist/esm/hooks/{viz-scrollmap → viz-scroll}/useZonePositions.d.ts +0 -0
- /package/dist/umd/components/Layout/{ContentMetricBar.d.ts → MetricBar/ContentMetricBar.d.ts} +0 -0
- /package/dist/umd/components/Layout/{ContentToolbar.d.ts → Toolbar/ContentToolbar.d.ts} +0 -0
- /package/dist/umd/components/Layout/{ContentTopBar.d.ts → TopBar/ContentTopBar.d.ts} +0 -0
- /package/dist/umd/components/Layout/{ContentVizByMode.d.ts → VizByMode/ContentVizByMode.d.ts} +0 -0
- /package/dist/umd/hooks/{viz-scrollmap → viz-scroll}/index.d.ts +0 -0
- /package/dist/umd/hooks/{viz-scrollmap → viz-scroll}/useScrollmapZones.d.ts +0 -0
- /package/dist/umd/hooks/{viz-scrollmap → viz-scroll}/useZonePositions.d.ts +0 -0
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 }) => {
|
|
@@ -48,6 +48,7 @@ const HEATMAP_IFRAME = {
|
|
|
48
48
|
height: '100%',
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
const DEFAULT_SIDEBAR_WIDTH = 260;
|
|
51
52
|
const HEATMAP_CONFIG = {
|
|
52
53
|
padding: 8,
|
|
53
54
|
borderWidth: 1,
|
|
@@ -66,32 +67,10 @@ const HEATMAP_STYLE = {
|
|
|
66
67
|
paddingInline: `${HEATMAP_CONFIG.padding}px`,
|
|
67
68
|
},
|
|
68
69
|
};
|
|
69
|
-
const DEFAULT_SIDEBAR_WIDTH = 260;
|
|
70
70
|
|
|
71
|
-
/**
|
|
72
|
-
* Default view ID for single mode
|
|
73
|
-
*/
|
|
74
71
|
const DEFAULT_VIEW_ID = 'default';
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
* @param index - Zero-based index (0, 1, 2, 3)
|
|
78
|
-
* @returns View ID string like 'view-0', 'view-1', etc.
|
|
79
|
-
*/
|
|
80
|
-
const getCompareViewId = (index) => `view-${index}`;
|
|
81
|
-
/**
|
|
82
|
-
* Check if a view ID is a compare view
|
|
83
|
-
*/
|
|
84
|
-
const isCompareViewId = (viewId) => viewId.startsWith('view-');
|
|
85
|
-
/**
|
|
86
|
-
* Extract index from compare view ID
|
|
87
|
-
* @param viewId - View ID like 'view-0'
|
|
88
|
-
* @returns Index number or -1 if invalid
|
|
89
|
-
*/
|
|
90
|
-
const getCompareViewIndex = (viewId) => {
|
|
91
|
-
if (!isCompareViewId(viewId))
|
|
92
|
-
return -1;
|
|
93
|
-
const match = viewId.match(/^view-(\d+)$/);
|
|
94
|
-
return match ? parseInt(match[1], 10) : -1;
|
|
72
|
+
const getCompareViewId = (viewId) => {
|
|
73
|
+
return `compare-${viewId}`;
|
|
95
74
|
};
|
|
96
75
|
|
|
97
76
|
const Z_INDEX = {
|
|
@@ -157,6 +136,7 @@ const useHeatmapControlStore = create()((set, get) => {
|
|
|
157
136
|
var IHeatmapType;
|
|
158
137
|
(function (IHeatmapType) {
|
|
159
138
|
IHeatmapType["Click"] = "click";
|
|
139
|
+
IHeatmapType["ClickArea"] = "click-area";
|
|
160
140
|
IHeatmapType["Scroll"] = "scroll";
|
|
161
141
|
})(IHeatmapType || (IHeatmapType = {}));
|
|
162
142
|
var IClickType;
|
|
@@ -168,6 +148,11 @@ var IClickType;
|
|
|
168
148
|
IClickType["First"] = "first-clicks";
|
|
169
149
|
IClickType["Last"] = "last-clicks";
|
|
170
150
|
})(IClickType || (IClickType = {}));
|
|
151
|
+
var IClickMode;
|
|
152
|
+
(function (IClickMode) {
|
|
153
|
+
IClickMode["Default"] = "default";
|
|
154
|
+
IClickMode["Area"] = "click-area";
|
|
155
|
+
})(IClickMode || (IClickMode = {}));
|
|
171
156
|
var IScrollType;
|
|
172
157
|
(function (IScrollType) {
|
|
173
158
|
IScrollType["Depth"] = "scroll-depth";
|
|
@@ -182,6 +167,7 @@ const useHeatmapConfigStore = create()((set) => {
|
|
|
182
167
|
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
|
|
183
168
|
heatmapType: IHeatmapType.Click,
|
|
184
169
|
clickType: IClickType.Total,
|
|
170
|
+
clickMode: IClickMode.Default,
|
|
185
171
|
scrollType: IScrollType.Depth,
|
|
186
172
|
isRendering: true,
|
|
187
173
|
setMode: (mode) => set({ mode }),
|
|
@@ -190,6 +176,7 @@ const useHeatmapConfigStore = create()((set) => {
|
|
|
190
176
|
setSidebarWidth: (sidebarWidth) => set({ sidebarWidth }),
|
|
191
177
|
setHeatmapType: (heatmapType) => set({ heatmapType }),
|
|
192
178
|
setClickType: (clickType) => set({ clickType }),
|
|
179
|
+
setClickMode: (clickMode) => set({ clickMode }),
|
|
193
180
|
setScrollType: (scrollType) => set({ scrollType }),
|
|
194
181
|
setIsRendering: (isRendering) => set({ isRendering }),
|
|
195
182
|
};
|
|
@@ -244,10 +231,178 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
|
|
|
244
231
|
};
|
|
245
232
|
}));
|
|
246
233
|
|
|
234
|
+
const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
|
|
235
|
+
return {
|
|
236
|
+
isRenderViz: { [DEFAULT_VIEW_ID]: false },
|
|
237
|
+
zoomRatio: { [DEFAULT_VIEW_ID]: 100 },
|
|
238
|
+
minZoomRatio: { [DEFAULT_VIEW_ID]: 10 },
|
|
239
|
+
scale: { [DEFAULT_VIEW_ID]: 1 },
|
|
240
|
+
isScaledToFit: { [DEFAULT_VIEW_ID]: false },
|
|
241
|
+
setIsRenderViz: (isRenderViz, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
242
|
+
isRenderViz: { ...state.isRenderViz, [viewId]: isRenderViz },
|
|
243
|
+
})),
|
|
244
|
+
setZoomRatio: (zoomRatio, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
245
|
+
zoomRatio: { ...state.zoomRatio, [viewId]: zoomRatio },
|
|
246
|
+
})),
|
|
247
|
+
setMinZoomRatio: (minZoomRatio, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
248
|
+
minZoomRatio: { ...state.minZoomRatio, [viewId]: minZoomRatio },
|
|
249
|
+
})),
|
|
250
|
+
setScale: (scale, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
251
|
+
scale: { ...state.scale, [viewId]: scale },
|
|
252
|
+
})),
|
|
253
|
+
setIsScaledToFit: (isScaledToFit, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
254
|
+
isScaledToFit: { ...state.isScaledToFit, [viewId]: isScaledToFit },
|
|
255
|
+
})),
|
|
256
|
+
copyView: (fromViewId, toViewId) => set((state) => ({
|
|
257
|
+
isRenderViz: { ...state.isRenderViz, [toViewId]: state.isRenderViz[fromViewId] ?? false },
|
|
258
|
+
zoomRatio: { ...state.zoomRatio, [toViewId]: state.zoomRatio[fromViewId] ?? 100 },
|
|
259
|
+
minZoomRatio: { ...state.minZoomRatio, [toViewId]: state.minZoomRatio[fromViewId] ?? 10 },
|
|
260
|
+
scale: { ...state.scale, [toViewId]: state.scale[fromViewId] ?? 1 },
|
|
261
|
+
isScaledToFit: {
|
|
262
|
+
...state.isScaledToFit,
|
|
263
|
+
[toViewId]: state.isScaledToFit[fromViewId] ?? false,
|
|
264
|
+
},
|
|
265
|
+
})),
|
|
266
|
+
clearView: (viewId) => set((state) => {
|
|
267
|
+
const newIsRenderViz = { ...state.isRenderViz };
|
|
268
|
+
const newZoomRatio = { ...state.zoomRatio };
|
|
269
|
+
const newMinZoomRatio = { ...state.minZoomRatio };
|
|
270
|
+
const newScale = { ...state.scale };
|
|
271
|
+
const newIsScaledToFit = { ...state.isScaledToFit };
|
|
272
|
+
delete newIsRenderViz[viewId];
|
|
273
|
+
delete newZoomRatio[viewId];
|
|
274
|
+
delete newMinZoomRatio[viewId];
|
|
275
|
+
delete newScale[viewId];
|
|
276
|
+
delete newIsScaledToFit[viewId];
|
|
277
|
+
return {
|
|
278
|
+
isRenderViz: newIsRenderViz,
|
|
279
|
+
zoomRatio: newZoomRatio,
|
|
280
|
+
minZoomRatio: newMinZoomRatio,
|
|
281
|
+
scale: newScale,
|
|
282
|
+
isScaledToFit: newIsScaledToFit,
|
|
283
|
+
};
|
|
284
|
+
}),
|
|
285
|
+
resetAll: () => set({
|
|
286
|
+
isRenderViz: { [DEFAULT_VIEW_ID]: false },
|
|
287
|
+
zoomRatio: { [DEFAULT_VIEW_ID]: 100 },
|
|
288
|
+
minZoomRatio: { [DEFAULT_VIEW_ID]: 10 },
|
|
289
|
+
scale: { [DEFAULT_VIEW_ID]: 1 },
|
|
290
|
+
isScaledToFit: { [DEFAULT_VIEW_ID]: false },
|
|
291
|
+
}),
|
|
292
|
+
};
|
|
293
|
+
}));
|
|
294
|
+
|
|
295
|
+
const useHeatmapAreaClickStore = create()(subscribeWithSelector((set) => ({
|
|
296
|
+
selectedArea: { [DEFAULT_VIEW_ID]: null },
|
|
297
|
+
hoveredArea: { [DEFAULT_VIEW_ID]: null },
|
|
298
|
+
areas: { [DEFAULT_VIEW_ID]: [] },
|
|
299
|
+
isEditingMode: { [DEFAULT_VIEW_ID]: false },
|
|
300
|
+
setSelectedArea: (area, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
301
|
+
selectedArea: { ...state.selectedArea, [viewId]: area },
|
|
302
|
+
})),
|
|
303
|
+
setHoveredArea: (area, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
304
|
+
hoveredArea: { ...state.hoveredArea, [viewId]: area },
|
|
305
|
+
})),
|
|
306
|
+
setAreas: (areas, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
307
|
+
areas: { ...state.areas, [viewId]: areas },
|
|
308
|
+
})),
|
|
309
|
+
setIsEditingMode: (isEditing, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
310
|
+
isEditingMode: { ...state.isEditingMode, [viewId]: isEditing },
|
|
311
|
+
})),
|
|
312
|
+
addArea: (area, viewId = DEFAULT_VIEW_ID) => set((state) => {
|
|
313
|
+
const currentAreas = state.areas[viewId] || [];
|
|
314
|
+
// Check if area already exists
|
|
315
|
+
const exists = currentAreas.some((a) => a.id === area.id);
|
|
316
|
+
if (exists)
|
|
317
|
+
return state;
|
|
318
|
+
return {
|
|
319
|
+
areas: {
|
|
320
|
+
...state.areas,
|
|
321
|
+
[viewId]: [...currentAreas, area],
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}),
|
|
325
|
+
removeArea: (areaId, viewId = DEFAULT_VIEW_ID) => set((state) => {
|
|
326
|
+
const currentAreas = state.areas[viewId] || [];
|
|
327
|
+
const filtered = currentAreas.filter((a) => a.id !== areaId);
|
|
328
|
+
// If removed area was selected/hovered, clear it
|
|
329
|
+
const selectedArea = state.selectedArea[viewId];
|
|
330
|
+
const hoveredArea = state.hoveredArea[viewId];
|
|
331
|
+
return {
|
|
332
|
+
areas: { ...state.areas, [viewId]: filtered },
|
|
333
|
+
selectedArea: {
|
|
334
|
+
...state.selectedArea,
|
|
335
|
+
[viewId]: selectedArea?.id === areaId ? null : selectedArea,
|
|
336
|
+
},
|
|
337
|
+
hoveredArea: {
|
|
338
|
+
...state.hoveredArea,
|
|
339
|
+
[viewId]: hoveredArea?.id === areaId ? null : hoveredArea,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}),
|
|
343
|
+
updateArea: (areaId, updates, viewId = DEFAULT_VIEW_ID) => set((state) => {
|
|
344
|
+
const currentAreas = state.areas[viewId] || [];
|
|
345
|
+
const updatedAreas = currentAreas.map((area) => (area.id === areaId ? { ...area, ...updates } : area));
|
|
346
|
+
return {
|
|
347
|
+
areas: { ...state.areas, [viewId]: updatedAreas },
|
|
348
|
+
};
|
|
349
|
+
}),
|
|
350
|
+
clearAreas: (viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
351
|
+
areas: { ...state.areas, [viewId]: [] },
|
|
352
|
+
selectedArea: { ...state.selectedArea, [viewId]: null },
|
|
353
|
+
hoveredArea: { ...state.hoveredArea, [viewId]: null },
|
|
354
|
+
})),
|
|
355
|
+
copyView: (fromViewId, toViewId) => set((state) => ({
|
|
356
|
+
selectedArea: {
|
|
357
|
+
...state.selectedArea,
|
|
358
|
+
[toViewId]: state.selectedArea[fromViewId] ?? null,
|
|
359
|
+
},
|
|
360
|
+
hoveredArea: {
|
|
361
|
+
...state.hoveredArea,
|
|
362
|
+
[toViewId]: state.hoveredArea[fromViewId] ?? null,
|
|
363
|
+
},
|
|
364
|
+
areas: {
|
|
365
|
+
...state.areas,
|
|
366
|
+
[toViewId]: state.areas[fromViewId] ?? [],
|
|
367
|
+
},
|
|
368
|
+
isEditingMode: {
|
|
369
|
+
...state.isEditingMode,
|
|
370
|
+
[toViewId]: state.isEditingMode[fromViewId] ?? false,
|
|
371
|
+
},
|
|
372
|
+
})),
|
|
373
|
+
clearView: (viewId) => set((state) => {
|
|
374
|
+
const newSelectedArea = { ...state.selectedArea };
|
|
375
|
+
const newHoveredArea = { ...state.hoveredArea };
|
|
376
|
+
const newAreas = { ...state.areas };
|
|
377
|
+
const newIsEditingMode = { ...state.isEditingMode };
|
|
378
|
+
delete newSelectedArea[viewId];
|
|
379
|
+
delete newHoveredArea[viewId];
|
|
380
|
+
delete newAreas[viewId];
|
|
381
|
+
delete newIsEditingMode[viewId];
|
|
382
|
+
return {
|
|
383
|
+
selectedArea: newSelectedArea,
|
|
384
|
+
hoveredArea: newHoveredArea,
|
|
385
|
+
areas: newAreas,
|
|
386
|
+
isEditingMode: newIsEditingMode,
|
|
387
|
+
};
|
|
388
|
+
}),
|
|
389
|
+
resetView: (viewId) => set((state) => ({
|
|
390
|
+
selectedArea: { ...state.selectedArea, [viewId]: null },
|
|
391
|
+
hoveredArea: { ...state.hoveredArea, [viewId]: null },
|
|
392
|
+
isEditingMode: { ...state.isEditingMode, [viewId]: false },
|
|
393
|
+
})),
|
|
394
|
+
resetAll: () => set({
|
|
395
|
+
selectedArea: { [DEFAULT_VIEW_ID]: null },
|
|
396
|
+
hoveredArea: { [DEFAULT_VIEW_ID]: null },
|
|
397
|
+
areas: { [DEFAULT_VIEW_ID]: [] },
|
|
398
|
+
isEditingMode: { [DEFAULT_VIEW_ID]: false },
|
|
399
|
+
}),
|
|
400
|
+
})));
|
|
401
|
+
|
|
247
402
|
const DEFAULT_STATE = {
|
|
248
403
|
hideSidebar: false,
|
|
249
404
|
};
|
|
250
|
-
const
|
|
405
|
+
const useHeatmapClickStore = create()(subscribeWithSelector((set) => {
|
|
251
406
|
return {
|
|
252
407
|
state: { [DEFAULT_VIEW_ID]: DEFAULT_STATE },
|
|
253
408
|
selectedElement: { [DEFAULT_VIEW_ID]: null },
|
|
@@ -308,67 +463,6 @@ const useHeatmapInteractionStore = create()(subscribeWithSelector((set) => {
|
|
|
308
463
|
};
|
|
309
464
|
}));
|
|
310
465
|
|
|
311
|
-
const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
|
|
312
|
-
return {
|
|
313
|
-
isRenderViz: { [DEFAULT_VIEW_ID]: false },
|
|
314
|
-
zoomRatio: { [DEFAULT_VIEW_ID]: 100 },
|
|
315
|
-
minZoomRatio: { [DEFAULT_VIEW_ID]: 10 },
|
|
316
|
-
scale: { [DEFAULT_VIEW_ID]: 1 },
|
|
317
|
-
isScaledToFit: { [DEFAULT_VIEW_ID]: false },
|
|
318
|
-
setIsRenderViz: (isRenderViz, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
319
|
-
isRenderViz: { ...state.isRenderViz, [viewId]: isRenderViz },
|
|
320
|
-
})),
|
|
321
|
-
setZoomRatio: (zoomRatio, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
322
|
-
zoomRatio: { ...state.zoomRatio, [viewId]: zoomRatio },
|
|
323
|
-
})),
|
|
324
|
-
setMinZoomRatio: (minZoomRatio, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
325
|
-
minZoomRatio: { ...state.minZoomRatio, [viewId]: minZoomRatio },
|
|
326
|
-
})),
|
|
327
|
-
setScale: (scale, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
328
|
-
scale: { ...state.scale, [viewId]: scale },
|
|
329
|
-
})),
|
|
330
|
-
setIsScaledToFit: (isScaledToFit, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
331
|
-
isScaledToFit: { ...state.isScaledToFit, [viewId]: isScaledToFit },
|
|
332
|
-
})),
|
|
333
|
-
copyView: (fromViewId, toViewId) => set((state) => ({
|
|
334
|
-
isRenderViz: { ...state.isRenderViz, [toViewId]: state.isRenderViz[fromViewId] ?? false },
|
|
335
|
-
zoomRatio: { ...state.zoomRatio, [toViewId]: state.zoomRatio[fromViewId] ?? 100 },
|
|
336
|
-
minZoomRatio: { ...state.minZoomRatio, [toViewId]: state.minZoomRatio[fromViewId] ?? 10 },
|
|
337
|
-
scale: { ...state.scale, [toViewId]: state.scale[fromViewId] ?? 1 },
|
|
338
|
-
isScaledToFit: {
|
|
339
|
-
...state.isScaledToFit,
|
|
340
|
-
[toViewId]: state.isScaledToFit[fromViewId] ?? false,
|
|
341
|
-
},
|
|
342
|
-
})),
|
|
343
|
-
clearView: (viewId) => set((state) => {
|
|
344
|
-
const newIsRenderViz = { ...state.isRenderViz };
|
|
345
|
-
const newZoomRatio = { ...state.zoomRatio };
|
|
346
|
-
const newMinZoomRatio = { ...state.minZoomRatio };
|
|
347
|
-
const newScale = { ...state.scale };
|
|
348
|
-
const newIsScaledToFit = { ...state.isScaledToFit };
|
|
349
|
-
delete newIsRenderViz[viewId];
|
|
350
|
-
delete newZoomRatio[viewId];
|
|
351
|
-
delete newMinZoomRatio[viewId];
|
|
352
|
-
delete newScale[viewId];
|
|
353
|
-
delete newIsScaledToFit[viewId];
|
|
354
|
-
return {
|
|
355
|
-
isRenderViz: newIsRenderViz,
|
|
356
|
-
zoomRatio: newZoomRatio,
|
|
357
|
-
minZoomRatio: newMinZoomRatio,
|
|
358
|
-
scale: newScale,
|
|
359
|
-
isScaledToFit: newIsScaledToFit,
|
|
360
|
-
};
|
|
361
|
-
}),
|
|
362
|
-
resetAll: () => set({
|
|
363
|
-
isRenderViz: { [DEFAULT_VIEW_ID]: false },
|
|
364
|
-
zoomRatio: { [DEFAULT_VIEW_ID]: 100 },
|
|
365
|
-
minZoomRatio: { [DEFAULT_VIEW_ID]: 10 },
|
|
366
|
-
scale: { [DEFAULT_VIEW_ID]: 1 },
|
|
367
|
-
isScaledToFit: { [DEFAULT_VIEW_ID]: false },
|
|
368
|
-
}),
|
|
369
|
-
};
|
|
370
|
-
}));
|
|
371
|
-
|
|
372
466
|
const useHeatmapVizScrollmapStore = create()(subscribeWithSelector((set) => {
|
|
373
467
|
return {
|
|
374
468
|
zones: { [DEFAULT_VIEW_ID]: [] },
|
|
@@ -418,94 +512,9 @@ const useHeatmapVizScrollmapStore = create()(subscribeWithSelector((set) => {
|
|
|
418
512
|
};
|
|
419
513
|
}));
|
|
420
514
|
|
|
421
|
-
const
|
|
422
|
-
payloads: [],
|
|
423
|
-
htmlContent: '',
|
|
424
|
-
};
|
|
425
|
-
const useHeatmapLiveStore = create()((set) => {
|
|
426
|
-
return {
|
|
427
|
-
...initialState,
|
|
428
|
-
reset: () => set(initialState),
|
|
429
|
-
setPayloads: (payloads) => set({ payloads }),
|
|
430
|
-
addPayload: (payload) => set((state) => ({ payloads: [...state.payloads, payload] })),
|
|
431
|
-
setHtmlContent: (htmlContent) => set({ htmlContent }),
|
|
432
|
-
};
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
const useHeatmapSingleStore = create()(subscribeWithSelector((set) => {
|
|
436
|
-
return {
|
|
437
|
-
vizRef: { [DEFAULT_VIEW_ID]: null },
|
|
438
|
-
iframeHeight: { [DEFAULT_VIEW_ID]: 0 },
|
|
439
|
-
wrapperHeight: { [DEFAULT_VIEW_ID]: 0 },
|
|
440
|
-
wrapperWidth: { [DEFAULT_VIEW_ID]: 0 },
|
|
441
|
-
setVizRef: (vizRef, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
442
|
-
vizRef: { ...state.vizRef, [viewId]: vizRef },
|
|
443
|
-
})),
|
|
444
|
-
setIframeHeight: (iframeHeight, viewId = DEFAULT_VIEW_ID) => {
|
|
445
|
-
set((state) => ({
|
|
446
|
-
iframeHeight: { ...state.iframeHeight, [viewId]: iframeHeight },
|
|
447
|
-
}));
|
|
448
|
-
},
|
|
449
|
-
setWrapperHeight: (wrapperHeight, viewId = DEFAULT_VIEW_ID) => {
|
|
450
|
-
set((state) => ({
|
|
451
|
-
wrapperHeight: { ...state.wrapperHeight, [viewId]: wrapperHeight },
|
|
452
|
-
}));
|
|
453
|
-
},
|
|
454
|
-
setWrapperWidth: (wrapperWidth, viewId = DEFAULT_VIEW_ID) => {
|
|
455
|
-
set((state) => ({
|
|
456
|
-
wrapperWidth: { ...state.wrapperWidth, [viewId]: wrapperWidth },
|
|
457
|
-
}));
|
|
458
|
-
},
|
|
459
|
-
copyView: (fromViewId, toViewId) => set((state) => ({
|
|
460
|
-
// Don't copy vizRef - each view needs its own visualizer instance
|
|
461
|
-
iframeHeight: { ...state.iframeHeight, [toViewId]: state.iframeHeight[fromViewId] ?? 0 },
|
|
462
|
-
wrapperHeight: {
|
|
463
|
-
...state.wrapperHeight,
|
|
464
|
-
[toViewId]: state.wrapperHeight[fromViewId] ?? 0,
|
|
465
|
-
},
|
|
466
|
-
})),
|
|
467
|
-
clearView: (viewId) => set((state) => {
|
|
468
|
-
const newVizRef = { ...state.vizRef };
|
|
469
|
-
const newIframeHeight = { ...state.iframeHeight };
|
|
470
|
-
const newWrapperHeight = { ...state.wrapperHeight };
|
|
471
|
-
const newWrapperWidth = { ...state.wrapperWidth };
|
|
472
|
-
delete newVizRef[viewId];
|
|
473
|
-
delete newIframeHeight[viewId];
|
|
474
|
-
delete newWrapperHeight[viewId];
|
|
475
|
-
delete newWrapperWidth[viewId];
|
|
476
|
-
return {
|
|
477
|
-
vizRef: newVizRef,
|
|
478
|
-
iframeHeight: newIframeHeight,
|
|
479
|
-
wrapperHeight: newWrapperHeight,
|
|
480
|
-
wrapperWidth: newWrapperWidth,
|
|
481
|
-
};
|
|
482
|
-
}),
|
|
483
|
-
resetAll: () => set({
|
|
484
|
-
vizRef: { [DEFAULT_VIEW_ID]: null },
|
|
485
|
-
iframeHeight: { [DEFAULT_VIEW_ID]: 0 },
|
|
486
|
-
wrapperHeight: { [DEFAULT_VIEW_ID]: 0 },
|
|
487
|
-
wrapperWidth: { [DEFAULT_VIEW_ID]: 0 },
|
|
488
|
-
}),
|
|
489
|
-
};
|
|
490
|
-
}));
|
|
491
|
-
|
|
492
|
-
const createDefaultView = (id, label, options) => ({
|
|
515
|
+
const createDefaultView = (id, label) => ({
|
|
493
516
|
id,
|
|
494
517
|
label,
|
|
495
|
-
heatmapType: options?.heatmapType ?? IHeatmapType.Scroll,
|
|
496
|
-
clickType: options?.clickType ?? IClickType.Total,
|
|
497
|
-
scrollType: options?.scrollType ?? IScrollType.Depth,
|
|
498
|
-
data: options?.data,
|
|
499
|
-
clickmap: undefined,
|
|
500
|
-
scrollmap: undefined,
|
|
501
|
-
dataInfo: undefined,
|
|
502
|
-
vizRef: null,
|
|
503
|
-
iframeHeight: 0,
|
|
504
|
-
zoomRatio: 100,
|
|
505
|
-
scale: 1,
|
|
506
|
-
isScaledToFit: false,
|
|
507
|
-
isRendering: true,
|
|
508
|
-
isRenderViz: false,
|
|
509
518
|
});
|
|
510
519
|
const useHeatmapCompareStore = create()((set, get) => {
|
|
511
520
|
return {
|
|
@@ -520,9 +529,9 @@ const useHeatmapCompareStore = create()((set, get) => {
|
|
|
520
529
|
viewIdCounter: 0,
|
|
521
530
|
addView: (options) => {
|
|
522
531
|
const state = get();
|
|
523
|
-
const viewId =
|
|
532
|
+
const viewId = getCompareViewId(state.viewIdCounter);
|
|
524
533
|
const label = options?.label ?? `Version ${state.viewOrder.length + 1}`;
|
|
525
|
-
const newView = createDefaultView(viewId, label
|
|
534
|
+
const newView = createDefaultView(viewId, label);
|
|
526
535
|
const newViews = new Map(state.views);
|
|
527
536
|
newViews.set(viewId, newView);
|
|
528
537
|
set({
|
|
@@ -536,9 +545,13 @@ const useHeatmapCompareStore = create()((set, get) => {
|
|
|
536
545
|
const state = get();
|
|
537
546
|
const newViews = new Map(state.views);
|
|
538
547
|
newViews.delete(viewId);
|
|
548
|
+
const newViewIdCounter = newViews.size;
|
|
549
|
+
const newLayout = newViews.size === 2 ? 'grid-2' : state.layout;
|
|
539
550
|
set({
|
|
540
551
|
views: newViews,
|
|
541
552
|
viewOrder: state.viewOrder.filter((id) => id !== viewId),
|
|
553
|
+
viewIdCounter: newViewIdCounter,
|
|
554
|
+
layout: newLayout,
|
|
542
555
|
});
|
|
543
556
|
},
|
|
544
557
|
updateView: (viewId, updates) => {
|
|
@@ -549,16 +562,6 @@ const useHeatmapCompareStore = create()((set, get) => {
|
|
|
549
562
|
const newViews = new Map(state.views);
|
|
550
563
|
newViews.set(viewId, { ...view, ...updates });
|
|
551
564
|
set({ views: newViews });
|
|
552
|
-
// Handle syncing
|
|
553
|
-
const { syncSettings } = state;
|
|
554
|
-
if (syncSettings.zoomRatio && 'zoomRatio' in updates && updates.zoomRatio !== undefined) {
|
|
555
|
-
get().syncProperty('zoomRatio', updates.zoomRatio);
|
|
556
|
-
}
|
|
557
|
-
if (syncSettings.heatmapType &&
|
|
558
|
-
'heatmapType' in updates &&
|
|
559
|
-
updates.heatmapType !== undefined) {
|
|
560
|
-
get().syncProperty('heatmapType', updates.heatmapType);
|
|
561
|
-
}
|
|
562
565
|
},
|
|
563
566
|
getView: (viewId) => {
|
|
564
567
|
return get().views.get(viewId);
|
|
@@ -573,9 +576,9 @@ const useHeatmapCompareStore = create()((set, get) => {
|
|
|
573
576
|
const viewIds = [];
|
|
574
577
|
const newViews = new Map();
|
|
575
578
|
for (let i = 0; i < count; i++) {
|
|
576
|
-
const viewId =
|
|
579
|
+
const viewId = getCompareViewId(i);
|
|
577
580
|
const label = options?.label ?? String.fromCharCode(65 + i); // A, B, C, D
|
|
578
|
-
newViews.set(viewId, createDefaultView(viewId, `Version ${label}
|
|
581
|
+
newViews.set(viewId, createDefaultView(viewId, `Version ${label}`));
|
|
579
582
|
viewIds.push(viewId);
|
|
580
583
|
}
|
|
581
584
|
const layoutMap = {
|
|
@@ -617,54 +620,192 @@ const useHeatmapCompareStore = create()((set, get) => {
|
|
|
617
620
|
};
|
|
618
621
|
});
|
|
619
622
|
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
|
|
624
|
-
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
625
|
-
const setIsRendering = useHeatmapConfigStore((state) => state.setIsRendering);
|
|
626
|
-
useEffect(() => {
|
|
627
|
-
setIsRendering(true);
|
|
628
|
-
setTimeout(() => {
|
|
629
|
-
setIsRendering(false);
|
|
630
|
-
}, 1000);
|
|
631
|
-
}, [mode, width, sidebarWidth, heatmapType]);
|
|
623
|
+
const initialState = {
|
|
624
|
+
payloads: [],
|
|
625
|
+
htmlContent: '',
|
|
632
626
|
};
|
|
627
|
+
const useHeatmapLiveStore = create()((set) => {
|
|
628
|
+
return {
|
|
629
|
+
...initialState,
|
|
630
|
+
reset: () => set(initialState),
|
|
631
|
+
setPayloads: (payloads) => set({ payloads }),
|
|
632
|
+
addPayload: (payload) => set((state) => ({ payloads: [...state.payloads, payload] })),
|
|
633
|
+
setHtmlContent: (htmlContent) => set({ htmlContent }),
|
|
634
|
+
};
|
|
635
|
+
});
|
|
633
636
|
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
637
|
+
const useHeatmapSingleStore = create()(subscribeWithSelector((set) => {
|
|
638
|
+
return {
|
|
639
|
+
vizRef: { [DEFAULT_VIEW_ID]: null },
|
|
640
|
+
iframeHeight: { [DEFAULT_VIEW_ID]: 0 },
|
|
641
|
+
wrapperHeight: { [DEFAULT_VIEW_ID]: 0 },
|
|
642
|
+
wrapperWidth: { [DEFAULT_VIEW_ID]: 0 },
|
|
643
|
+
setVizRef: (vizRef, viewId = DEFAULT_VIEW_ID) => set((state) => ({
|
|
644
|
+
vizRef: { ...state.vizRef, [viewId]: vizRef },
|
|
645
|
+
})),
|
|
646
|
+
setIframeHeight: (iframeHeight, viewId = DEFAULT_VIEW_ID) => {
|
|
647
|
+
set((state) => ({
|
|
648
|
+
iframeHeight: { ...state.iframeHeight, [viewId]: iframeHeight },
|
|
649
|
+
}));
|
|
650
|
+
},
|
|
651
|
+
setWrapperHeight: (wrapperHeight, viewId = DEFAULT_VIEW_ID) => {
|
|
652
|
+
set((state) => ({
|
|
653
|
+
wrapperHeight: { ...state.wrapperHeight, [viewId]: wrapperHeight },
|
|
654
|
+
}));
|
|
655
|
+
},
|
|
656
|
+
setWrapperWidth: (wrapperWidth, viewId = DEFAULT_VIEW_ID) => {
|
|
657
|
+
set((state) => ({
|
|
658
|
+
wrapperWidth: { ...state.wrapperWidth, [viewId]: wrapperWidth },
|
|
659
|
+
}));
|
|
660
|
+
},
|
|
661
|
+
copyView: (fromViewId, toViewId) => set((state) => ({
|
|
662
|
+
// Don't copy vizRef - each view needs its own visualizer instance
|
|
663
|
+
iframeHeight: { ...state.iframeHeight, [toViewId]: state.iframeHeight[fromViewId] ?? 0 },
|
|
664
|
+
wrapperHeight: {
|
|
665
|
+
...state.wrapperHeight,
|
|
666
|
+
[toViewId]: state.wrapperHeight[fromViewId] ?? 0,
|
|
667
|
+
},
|
|
668
|
+
})),
|
|
669
|
+
clearView: (viewId) => set((state) => {
|
|
670
|
+
const newVizRef = { ...state.vizRef };
|
|
671
|
+
const newIframeHeight = { ...state.iframeHeight };
|
|
672
|
+
const newWrapperHeight = { ...state.wrapperHeight };
|
|
673
|
+
const newWrapperWidth = { ...state.wrapperWidth };
|
|
674
|
+
delete newVizRef[viewId];
|
|
675
|
+
delete newIframeHeight[viewId];
|
|
676
|
+
delete newWrapperHeight[viewId];
|
|
677
|
+
delete newWrapperWidth[viewId];
|
|
678
|
+
return {
|
|
679
|
+
vizRef: newVizRef,
|
|
680
|
+
iframeHeight: newIframeHeight,
|
|
681
|
+
wrapperHeight: newWrapperHeight,
|
|
682
|
+
wrapperWidth: newWrapperWidth,
|
|
683
|
+
};
|
|
684
|
+
}),
|
|
685
|
+
resetAll: () => set({
|
|
686
|
+
vizRef: { [DEFAULT_VIEW_ID]: null },
|
|
687
|
+
iframeHeight: { [DEFAULT_VIEW_ID]: 0 },
|
|
688
|
+
wrapperHeight: { [DEFAULT_VIEW_ID]: 0 },
|
|
689
|
+
wrapperWidth: { [DEFAULT_VIEW_ID]: 0 },
|
|
690
|
+
}),
|
|
691
|
+
};
|
|
692
|
+
}));
|
|
693
|
+
|
|
694
|
+
const useRegisterConfig = () => {
|
|
695
|
+
const mode = useHeatmapConfigStore((state) => state.mode);
|
|
696
|
+
const width = useHeatmapConfigStore((state) => state.width);
|
|
697
|
+
const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
|
|
698
|
+
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
699
|
+
const clickMode = useHeatmapConfigStore((state) => state.clickMode);
|
|
700
|
+
const setIsRendering = useHeatmapConfigStore((state) => state.setIsRendering);
|
|
701
|
+
useEffect(() => {
|
|
702
|
+
setIsRendering(true);
|
|
703
|
+
setTimeout(() => {
|
|
704
|
+
setIsRendering(false);
|
|
705
|
+
}, 1000);
|
|
706
|
+
}, [mode, width, sidebarWidth, heatmapType, clickMode]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
const useRegisterControl = (control) => {
|
|
710
|
+
const registerControl = useHeatmapControlStore((state) => state.registerControl);
|
|
711
|
+
registerControl('Sidebar', control.Sidebar);
|
|
712
|
+
registerControl('SidebarActivator', control.SidebarActivator);
|
|
713
|
+
registerControl('TopBar', control.TopBar);
|
|
714
|
+
registerControl('Toolbar', control.Toolbar);
|
|
715
|
+
registerControl('MetricBar', control.MetricBar);
|
|
716
|
+
registerControl('VizLoading', control.VizLoading);
|
|
717
|
+
registerControl('ElementCallout', control.ElementCallout);
|
|
718
|
+
};
|
|
644
719
|
|
|
645
|
-
/**
|
|
646
|
-
* Context to provide viewId to components
|
|
647
|
-
* Used in compare mode to isolate data between views
|
|
648
|
-
*/
|
|
649
720
|
const ViewIdContext = createContext(undefined);
|
|
650
|
-
|
|
651
|
-
* Hook to get current viewId
|
|
652
|
-
* Returns DEFAULT_VIEW_ID if not in a ViewIdContext (single mode)
|
|
653
|
-
*/
|
|
654
|
-
const useViewId = () => {
|
|
721
|
+
const useViewIdContext = () => {
|
|
655
722
|
const viewId = useContext(ViewIdContext);
|
|
656
723
|
return viewId || DEFAULT_VIEW_ID;
|
|
657
724
|
};
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
const
|
|
663
|
-
|
|
725
|
+
|
|
726
|
+
const useHeatmapAreaClick = (props) => {
|
|
727
|
+
const viewId = props?.viewId || useViewIdContext();
|
|
728
|
+
// Optimized: Only subscribe to specific viewId slice
|
|
729
|
+
const selectedArea = useHeatmapAreaClickStore((state) => state.selectedArea[viewId] ?? null);
|
|
730
|
+
const hoveredArea = useHeatmapAreaClickStore((state) => state.hoveredArea[viewId] ?? null);
|
|
731
|
+
const areas = useHeatmapAreaClickStore((state) => state.areas[viewId] ?? []);
|
|
732
|
+
const isEditingMode = useHeatmapAreaClickStore((state) => state.isEditingMode[viewId] ?? false);
|
|
733
|
+
// Get setters from store
|
|
734
|
+
const setSelectedAreaStore = useHeatmapAreaClickStore((state) => state.setSelectedArea);
|
|
735
|
+
const setHoveredAreaStore = useHeatmapAreaClickStore((state) => state.setHoveredArea);
|
|
736
|
+
const setAreasStore = useHeatmapAreaClickStore((state) => state.setAreas);
|
|
737
|
+
const setIsEditingModeStore = useHeatmapAreaClickStore((state) => state.setIsEditingMode);
|
|
738
|
+
const addAreaStore = useHeatmapAreaClickStore((state) => state.addArea);
|
|
739
|
+
const removeAreaStore = useHeatmapAreaClickStore((state) => state.removeArea);
|
|
740
|
+
const updateAreaStore = useHeatmapAreaClickStore((state) => state.updateArea);
|
|
741
|
+
const clearAreasStore = useHeatmapAreaClickStore((state) => state.clearAreas);
|
|
742
|
+
const resetViewStore = useHeatmapAreaClickStore((state) => state.resetView);
|
|
743
|
+
// Memoize operations to prevent unnecessary re-renders
|
|
744
|
+
const memoizedOperations = useMemo(() => ({
|
|
745
|
+
setSelectedArea: (area) => setSelectedAreaStore(area, viewId),
|
|
746
|
+
setHoveredArea: (area) => setHoveredAreaStore(area, viewId),
|
|
747
|
+
setAreas: (areas) => setAreasStore(areas, viewId),
|
|
748
|
+
setIsEditingMode: (isEditing) => setIsEditingModeStore(isEditing, viewId),
|
|
749
|
+
addArea: (area) => addAreaStore(area, viewId),
|
|
750
|
+
removeArea: (areaId) => removeAreaStore(areaId, viewId),
|
|
751
|
+
updateArea: (areaId, updates) => updateAreaStore(areaId, updates, viewId),
|
|
752
|
+
clearAreas: () => clearAreasStore(viewId),
|
|
753
|
+
resetView: () => resetViewStore(viewId),
|
|
754
|
+
}), [
|
|
755
|
+
setSelectedAreaStore,
|
|
756
|
+
setHoveredAreaStore,
|
|
757
|
+
setAreasStore,
|
|
758
|
+
setIsEditingModeStore,
|
|
759
|
+
addAreaStore,
|
|
760
|
+
removeAreaStore,
|
|
761
|
+
updateAreaStore,
|
|
762
|
+
clearAreasStore,
|
|
763
|
+
resetViewStore,
|
|
764
|
+
viewId,
|
|
765
|
+
]);
|
|
766
|
+
return {
|
|
767
|
+
selectedArea,
|
|
768
|
+
hoveredArea,
|
|
769
|
+
areas,
|
|
770
|
+
isEditingMode,
|
|
771
|
+
...memoizedOperations,
|
|
772
|
+
};
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
const useHeatmapClick = (props) => {
|
|
776
|
+
const viewId = props?.viewId || useViewIdContext();
|
|
777
|
+
const state = useHeatmapClickStore((store) => store.state[viewId] ?? { hideSidebar: false });
|
|
778
|
+
const selectedElement = useHeatmapClickStore((store) => store.selectedElement[viewId] ?? null);
|
|
779
|
+
const hoveredElement = useHeatmapClickStore((store) => store.hoveredElement[viewId] ?? null);
|
|
780
|
+
const shouldShowCallout = useHeatmapClickStore((store) => store.shouldShowCallout[viewId] ?? false);
|
|
781
|
+
const setStateStore = useHeatmapClickStore((store) => store.setState);
|
|
782
|
+
const setSelectedElementStore = useHeatmapClickStore((store) => store.setSelectedElement);
|
|
783
|
+
const setHoveredElementStore = useHeatmapClickStore((store) => store.setHoveredElement);
|
|
784
|
+
const setShouldShowCalloutStore = useHeatmapClickStore((store) => store.setShouldShowCallout);
|
|
785
|
+
const memoizedSetters = useMemo(() => ({
|
|
786
|
+
setState: (newState) => setStateStore(newState, viewId),
|
|
787
|
+
setSelectedElement: (element) => setSelectedElementStore(element, viewId),
|
|
788
|
+
setHoveredElement: (element) => setHoveredElementStore(element, viewId),
|
|
789
|
+
setShouldShowCallout: (value) => setShouldShowCalloutStore(value, viewId),
|
|
790
|
+
}), [
|
|
791
|
+
setStateStore,
|
|
792
|
+
setSelectedElementStore,
|
|
793
|
+
setHoveredElementStore,
|
|
794
|
+
setShouldShowCalloutStore,
|
|
795
|
+
viewId,
|
|
796
|
+
]);
|
|
797
|
+
return {
|
|
798
|
+
state,
|
|
799
|
+
selectedElement,
|
|
800
|
+
hoveredElement,
|
|
801
|
+
shouldShowCallout,
|
|
802
|
+
// Setters (auto-inject viewId)
|
|
803
|
+
...memoizedSetters,
|
|
804
|
+
};
|
|
664
805
|
};
|
|
665
806
|
|
|
666
807
|
const useHeatmapData = (props) => {
|
|
667
|
-
const viewId = props?.viewId ||
|
|
808
|
+
const viewId = props?.viewId || useViewIdContext();
|
|
668
809
|
const data = useHeatmapDataStore((state) => state.data[viewId]);
|
|
669
810
|
const clickmap = useHeatmapDataStore((state) => state.clickmap[viewId]);
|
|
670
811
|
const scrollmap = useHeatmapDataStore((state) => state.scrollmap[viewId]);
|
|
@@ -689,40 +830,30 @@ const useHeatmapData = (props) => {
|
|
|
689
830
|
};
|
|
690
831
|
};
|
|
691
832
|
|
|
692
|
-
const
|
|
693
|
-
const viewId = props?.viewId ||
|
|
694
|
-
const
|
|
695
|
-
const
|
|
696
|
-
const
|
|
697
|
-
const
|
|
698
|
-
const
|
|
699
|
-
const
|
|
700
|
-
const setHoveredElementStore = useHeatmapInteractionStore((store) => store.setHoveredElement);
|
|
701
|
-
const setShouldShowCalloutStore = useHeatmapInteractionStore((store) => store.setShouldShowCallout);
|
|
833
|
+
const useHeatmapScroll = (props) => {
|
|
834
|
+
const viewId = props?.viewId || useViewIdContext();
|
|
835
|
+
const zones = useHeatmapVizScrollmapStore((store) => store.zones[viewId] ?? []);
|
|
836
|
+
const hoveredZone = useHeatmapVizScrollmapStore((store) => store.hoveredZone[viewId] ?? null);
|
|
837
|
+
const showMinimap = useHeatmapVizScrollmapStore((store) => store.showMinimap[viewId] ?? true);
|
|
838
|
+
const setZonesStore = useHeatmapVizScrollmapStore((store) => store.setZones);
|
|
839
|
+
const setHoveredZoneStore = useHeatmapVizScrollmapStore((store) => store.setHoveredZone);
|
|
840
|
+
const setShowMinimapStore = useHeatmapVizScrollmapStore((store) => store.setShowMinimap);
|
|
702
841
|
const memoizedSetters = useMemo(() => ({
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
}), [
|
|
708
|
-
setStateStore,
|
|
709
|
-
setSelectedElementStore,
|
|
710
|
-
setHoveredElementStore,
|
|
711
|
-
setShouldShowCalloutStore,
|
|
712
|
-
viewId,
|
|
713
|
-
]);
|
|
842
|
+
setZones: (newZones) => setZonesStore(newZones, viewId),
|
|
843
|
+
setHoveredZone: (zone) => setHoveredZoneStore(zone, viewId),
|
|
844
|
+
setShowMinimap: (value) => setShowMinimapStore(value, viewId),
|
|
845
|
+
}), [setZonesStore, setHoveredZoneStore, setShowMinimapStore, viewId]);
|
|
714
846
|
return {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
shouldShowCallout,
|
|
847
|
+
zones,
|
|
848
|
+
hoveredZone,
|
|
849
|
+
showMinimap,
|
|
719
850
|
// Setters (auto-inject viewId)
|
|
720
851
|
...memoizedSetters,
|
|
721
852
|
};
|
|
722
853
|
};
|
|
723
854
|
|
|
724
855
|
const useHeatmapViz = (props) => {
|
|
725
|
-
const viewId = props?.viewId ||
|
|
856
|
+
const viewId = props?.viewId || useViewIdContext();
|
|
726
857
|
// Viz store
|
|
727
858
|
const isRenderViz = useHeatmapVizStore((state) => state.isRenderViz[viewId] ?? false);
|
|
728
859
|
const zoomRatio = useHeatmapVizStore((state) => state.zoomRatio[viewId] ?? 100);
|
|
@@ -780,28 +911,6 @@ const useHeatmapViz = (props) => {
|
|
|
780
911
|
};
|
|
781
912
|
};
|
|
782
913
|
|
|
783
|
-
const useHeatmapVizScrollmap = (props) => {
|
|
784
|
-
const viewId = props?.viewId || useViewId();
|
|
785
|
-
const zones = useHeatmapVizScrollmapStore((store) => store.zones[viewId] ?? []);
|
|
786
|
-
const hoveredZone = useHeatmapVizScrollmapStore((store) => store.hoveredZone[viewId] ?? null);
|
|
787
|
-
const showMinimap = useHeatmapVizScrollmapStore((store) => store.showMinimap[viewId] ?? true);
|
|
788
|
-
const setZonesStore = useHeatmapVizScrollmapStore((store) => store.setZones);
|
|
789
|
-
const setHoveredZoneStore = useHeatmapVizScrollmapStore((store) => store.setHoveredZone);
|
|
790
|
-
const setShowMinimapStore = useHeatmapVizScrollmapStore((store) => store.setShowMinimap);
|
|
791
|
-
const memoizedSetters = useMemo(() => ({
|
|
792
|
-
setZones: (newZones) => setZonesStore(newZones, viewId),
|
|
793
|
-
setHoveredZone: (zone) => setHoveredZoneStore(zone, viewId),
|
|
794
|
-
setShowMinimap: (value) => setShowMinimapStore(value, viewId),
|
|
795
|
-
}), [setZonesStore, setHoveredZoneStore, setShowMinimapStore, viewId]);
|
|
796
|
-
return {
|
|
797
|
-
zones,
|
|
798
|
-
hoveredZone,
|
|
799
|
-
showMinimap,
|
|
800
|
-
// Setters (auto-inject viewId)
|
|
801
|
-
...memoizedSetters,
|
|
802
|
-
};
|
|
803
|
-
};
|
|
804
|
-
|
|
805
914
|
/**
|
|
806
915
|
* Hook to handle copying and clearing view data across all stores
|
|
807
916
|
*/
|
|
@@ -809,24 +918,28 @@ const useHeatmapCopyView = () => {
|
|
|
809
918
|
const copyDataView = useHeatmapDataStore((state) => state.copyView);
|
|
810
919
|
const copyVizView = useHeatmapVizStore((state) => state.copyView);
|
|
811
920
|
const copySingleView = useHeatmapSingleStore((state) => state.copyView);
|
|
812
|
-
const copyInteractionView =
|
|
921
|
+
const copyInteractionView = useHeatmapClickStore((state) => state.copyView);
|
|
813
922
|
const copyVizScrollmapView = useHeatmapVizScrollmapStore((state) => state.copyView);
|
|
923
|
+
const copyAreaClickView = useHeatmapAreaClickStore((state) => state.copyView);
|
|
814
924
|
const clearDataView = useHeatmapDataStore((state) => state.clearView);
|
|
815
925
|
const clearVizView = useHeatmapVizStore((state) => state.clearView);
|
|
816
926
|
const clearSingleView = useHeatmapSingleStore((state) => state.clearView);
|
|
817
|
-
const clearInteractionView =
|
|
927
|
+
const clearInteractionView = useHeatmapClickStore((state) => state.clearView);
|
|
818
928
|
const clearVizScrollmapView = useHeatmapVizScrollmapStore((state) => state.clearView);
|
|
929
|
+
const clearAreaClickView = useHeatmapAreaClickStore((state) => state.clearView);
|
|
819
930
|
const resetDataAll = useHeatmapDataStore((state) => state.resetAll);
|
|
820
931
|
const resetVizAll = useHeatmapVizStore((state) => state.resetAll);
|
|
821
932
|
const resetSingleAll = useHeatmapSingleStore((state) => state.resetAll);
|
|
822
|
-
const resetInteractionAll =
|
|
933
|
+
const resetInteractionAll = useHeatmapClickStore((state) => state.resetAll);
|
|
823
934
|
const resetVizScrollmapAll = useHeatmapVizScrollmapStore((state) => state.resetAll);
|
|
935
|
+
const resetAreaClickAll = useHeatmapAreaClickStore((state) => state.resetAll);
|
|
824
936
|
const copyView = (fromViewId, toViewId) => {
|
|
825
937
|
copyDataView(fromViewId, toViewId);
|
|
826
938
|
copyVizView(fromViewId, toViewId);
|
|
827
939
|
copySingleView(fromViewId, toViewId);
|
|
828
940
|
copyInteractionView(fromViewId, toViewId);
|
|
829
941
|
copyVizScrollmapView(fromViewId, toViewId);
|
|
942
|
+
copyAreaClickView(fromViewId, toViewId);
|
|
830
943
|
};
|
|
831
944
|
const copyViewToMultiple = (fromViewId, toViewIds) => {
|
|
832
945
|
toViewIds.forEach((toViewId) => {
|
|
@@ -839,6 +952,7 @@ const useHeatmapCopyView = () => {
|
|
|
839
952
|
clearSingleView(viewId);
|
|
840
953
|
clearInteractionView(viewId);
|
|
841
954
|
clearVizScrollmapView(viewId);
|
|
955
|
+
clearAreaClickView(viewId);
|
|
842
956
|
};
|
|
843
957
|
const clearMultipleViews = (viewIds) => {
|
|
844
958
|
viewIds.forEach((viewId) => {
|
|
@@ -851,6 +965,7 @@ const useHeatmapCopyView = () => {
|
|
|
851
965
|
resetSingleAll();
|
|
852
966
|
resetInteractionAll();
|
|
853
967
|
resetVizScrollmapAll();
|
|
968
|
+
resetAreaClickAll();
|
|
854
969
|
};
|
|
855
970
|
return {
|
|
856
971
|
copyView,
|
|
@@ -903,6 +1018,497 @@ const useRegisterHeatmap = ({ clickmap, scrollmap }) => {
|
|
|
903
1018
|
}, [scrollmap]);
|
|
904
1019
|
};
|
|
905
1020
|
|
|
1021
|
+
/**
|
|
1022
|
+
* Create an observable value with subscribe/unsubscribe pattern
|
|
1023
|
+
*/
|
|
1024
|
+
function createObservable(initialValue) {
|
|
1025
|
+
const subscribers = new Set();
|
|
1026
|
+
const observable = {
|
|
1027
|
+
value: initialValue,
|
|
1028
|
+
observe: (callback) => {
|
|
1029
|
+
subscribers.add(callback);
|
|
1030
|
+
// Immediately call with current value
|
|
1031
|
+
if (observable.value !== undefined) {
|
|
1032
|
+
callback(observable.value);
|
|
1033
|
+
}
|
|
1034
|
+
},
|
|
1035
|
+
unobserve: (callback) => {
|
|
1036
|
+
subscribers.delete(callback);
|
|
1037
|
+
},
|
|
1038
|
+
update: (newValue) => {
|
|
1039
|
+
observable.value = newValue;
|
|
1040
|
+
// Notify all subscribers
|
|
1041
|
+
subscribers.forEach((callback) => {
|
|
1042
|
+
callback(newValue);
|
|
1043
|
+
});
|
|
1044
|
+
},
|
|
1045
|
+
};
|
|
1046
|
+
return observable;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function isElementInViewport(elementRect, visualRef, scale) {
|
|
1050
|
+
if (!elementRect)
|
|
1051
|
+
return false;
|
|
1052
|
+
const visualRect = visualRef.current?.getBoundingClientRect();
|
|
1053
|
+
if (!visualRect)
|
|
1054
|
+
return false;
|
|
1055
|
+
// Element position relative to the document (or container's content)
|
|
1056
|
+
const elementTop = elementRect.top * scale;
|
|
1057
|
+
const elementBottom = (elementRect.top + elementRect.height) * scale;
|
|
1058
|
+
// Current scroll position
|
|
1059
|
+
const scrollTop = visualRef.current?.scrollTop || 0;
|
|
1060
|
+
const viewportHeight = visualRect.height;
|
|
1061
|
+
// Visible viewport range in the scrollable content
|
|
1062
|
+
const viewportTop = scrollTop;
|
|
1063
|
+
const viewportBottom = scrollTop + viewportHeight;
|
|
1064
|
+
// Check if element is within the visible viewport
|
|
1065
|
+
// Element is visible if it overlaps with the viewport
|
|
1066
|
+
return elementBottom > viewportTop && elementTop < viewportBottom;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
const CLARITY_HEATMAP_CANVAS_ID = 'clarity-heatmap-canvas';
|
|
1070
|
+
const HEATMAP_ELEMENT_ATTRIBUTE = 'data-clarity-hashalpha';
|
|
1071
|
+
function isIgnoredCanvas(element) {
|
|
1072
|
+
if (element.tagName === 'CANVAS') {
|
|
1073
|
+
return true;
|
|
1074
|
+
}
|
|
1075
|
+
if (element.id === CLARITY_HEATMAP_CANVAS_ID) {
|
|
1076
|
+
return true;
|
|
1077
|
+
}
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const AREA_HOVER_BOX_SHADOW = '0 0 0 1px #0078D4, 0 0 0 1px #0078D4 inset, 0 0 0 2px white inset';
|
|
1082
|
+
const AREA_HOVER_ELEMENT_ID = 'clarity-edit-hover';
|
|
1083
|
+
const AREA_MAP_DIV_ATTRIBUTE = 'data-clarity-area-map-div';
|
|
1084
|
+
const HEATMAP_AREA_CONTAINER_CLASS = 'heatmap-area-container';
|
|
1085
|
+
const HEATMAP_AREA_CONTAINER_SELECTOR = `.${HEATMAP_AREA_CONTAINER_CLASS}`;
|
|
1086
|
+
const AREA_CONTAINER_STYLES = `
|
|
1087
|
+
position: absolute;
|
|
1088
|
+
top: 0;
|
|
1089
|
+
left: 0;
|
|
1090
|
+
width: 100%;
|
|
1091
|
+
height: 100%;
|
|
1092
|
+
pointer-events: none;
|
|
1093
|
+
z-index: 999999;
|
|
1094
|
+
`;
|
|
1095
|
+
const AREA_INNER_CONTAINER_STYLES = `
|
|
1096
|
+
position: relative;
|
|
1097
|
+
width: 100%;
|
|
1098
|
+
height: 100%;
|
|
1099
|
+
`;
|
|
1100
|
+
const AREA_COLOR_GRADIENT = [
|
|
1101
|
+
[0, 0, 255], // Blue
|
|
1102
|
+
[0, 255, 255], // Cyan
|
|
1103
|
+
[0, 255, 0], // Green
|
|
1104
|
+
[255, 255, 0], // Yellow
|
|
1105
|
+
[255, 0, 0], // Red
|
|
1106
|
+
];
|
|
1107
|
+
const AREA_RENDERER_SELECTORS = {
|
|
1108
|
+
containerAttribute: AREA_MAP_DIV_ATTRIBUTE,
|
|
1109
|
+
containerSelector: `[${AREA_MAP_DIV_ATTRIBUTE}]`,
|
|
1110
|
+
innerContainerClass: HEATMAP_AREA_CONTAINER_CLASS,
|
|
1111
|
+
innerContainerSelector: HEATMAP_AREA_CONTAINER_SELECTOR,
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
const CALLOUT_PADDING = 0;
|
|
1115
|
+
const CALLOUT_ARROW_SIZE = 8;
|
|
1116
|
+
const CALLOUT_HORIZONTAL_OFFSET = 0;
|
|
1117
|
+
const CLICKED_ELEMENT_ID_BASE = 'gx-hm-clicked-element';
|
|
1118
|
+
const SECONDARY_CLICKED_ELEMENT_ID_BASE = 'gx-hm-secondary-clicked-element';
|
|
1119
|
+
const HOVERED_ELEMENT_ID_BASE = 'gx-hm-hovered-element';
|
|
1120
|
+
const SECONDARY_HOVERED_ELEMENT_ID_BASE = 'gx-hm-secondary-hovered-element';
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* Get color from click distribution percentage (0-100)
|
|
1124
|
+
*/
|
|
1125
|
+
function getColorFromClickDist(clickDist) {
|
|
1126
|
+
// Ensure clickDist is in range [0, 100]
|
|
1127
|
+
const normalizedDist = Math.max(0, Math.min(100, clickDist));
|
|
1128
|
+
console.log(`🚀 🐥 ~ getColorFromClickDist ~ normalizedDist:`, normalizedDist);
|
|
1129
|
+
// Calculate gradient index
|
|
1130
|
+
const maxIndex = AREA_COLOR_GRADIENT.length - 1;
|
|
1131
|
+
const index = Math.floor((normalizedDist / 100) * maxIndex);
|
|
1132
|
+
const clampedIndex = Math.min(index, maxIndex);
|
|
1133
|
+
const [r, g, b] = AREA_COLOR_GRADIENT[clampedIndex];
|
|
1134
|
+
// Return rgba with 60% opacity
|
|
1135
|
+
return `rgba(${r}, ${g}, ${b}, 0.6)`;
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Get hover color (slightly lighter) from click distribution
|
|
1139
|
+
*/
|
|
1140
|
+
function getHoverColorFromClickDist(clickDist) {
|
|
1141
|
+
const normalizedDist = Math.max(0, Math.min(100, clickDist));
|
|
1142
|
+
const maxIndex = AREA_COLOR_GRADIENT.length - 1;
|
|
1143
|
+
const index = Math.floor((normalizedDist / 100) * maxIndex);
|
|
1144
|
+
const clampedIndex = Math.min(index, maxIndex);
|
|
1145
|
+
const [r, g, b] = AREA_COLOR_GRADIENT[clampedIndex];
|
|
1146
|
+
// Return rgba with 80% opacity for hover
|
|
1147
|
+
return `rgba(${r}, ${g}, ${b}, 0.8)`;
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Calculate click distribution percentage from total clicks
|
|
1151
|
+
*/
|
|
1152
|
+
function calculateClickDistribution(elementClicks, totalClicks) {
|
|
1153
|
+
if (totalClicks === 0)
|
|
1154
|
+
return 0;
|
|
1155
|
+
return (elementClicks / totalClicks) * 100;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function getElementRect(element, _shadowRoot) {
|
|
1159
|
+
const rect = element.getBoundingClientRect();
|
|
1160
|
+
const width = rect.width;
|
|
1161
|
+
const height = rect.height;
|
|
1162
|
+
const doc = element.ownerDocument || document;
|
|
1163
|
+
const scrollTop = doc.documentElement?.scrollTop || doc.body?.scrollTop || 0;
|
|
1164
|
+
const scrollLeft = doc.documentElement?.scrollLeft || doc.body?.scrollLeft || 0;
|
|
1165
|
+
const top = rect.top + scrollTop;
|
|
1166
|
+
const left = rect.left + scrollLeft;
|
|
1167
|
+
const absoluteLeft = left;
|
|
1168
|
+
const absoluteTop = top;
|
|
1169
|
+
const absoluteRight = absoluteLeft + width;
|
|
1170
|
+
const absoluteBottom = absoluteTop + height;
|
|
1171
|
+
return {
|
|
1172
|
+
width,
|
|
1173
|
+
height,
|
|
1174
|
+
top,
|
|
1175
|
+
left,
|
|
1176
|
+
absoluteLeft,
|
|
1177
|
+
absoluteTop,
|
|
1178
|
+
absoluteRight,
|
|
1179
|
+
absoluteBottom,
|
|
1180
|
+
outOfBounds: false,
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
function isElementFixed(element) {
|
|
1184
|
+
if (getComputedStyle(element).position === 'fixed') {
|
|
1185
|
+
return true;
|
|
1186
|
+
}
|
|
1187
|
+
if (element.nodeName === 'HTML') {
|
|
1188
|
+
return false;
|
|
1189
|
+
}
|
|
1190
|
+
const parent = element.parentElement;
|
|
1191
|
+
return parent ? isElementFixed(parent) : false;
|
|
1192
|
+
}
|
|
1193
|
+
function doAreasOverlap(area1, area2) {
|
|
1194
|
+
const r1 = area1.rect.value;
|
|
1195
|
+
const r2 = area2.rect.value;
|
|
1196
|
+
if (!r1 || !r2)
|
|
1197
|
+
return false;
|
|
1198
|
+
return ((r1.absoluteBottom > r2.absoluteTop &&
|
|
1199
|
+
r1.absoluteTop < r2.absoluteBottom &&
|
|
1200
|
+
r1.absoluteRight > r2.absoluteLeft &&
|
|
1201
|
+
r1.absoluteLeft < r2.absoluteRight) ||
|
|
1202
|
+
(r2.absoluteBottom > r1.absoluteTop &&
|
|
1203
|
+
r2.absoluteTop < r1.absoluteBottom &&
|
|
1204
|
+
r2.absoluteRight > r1.absoluteLeft &&
|
|
1205
|
+
r2.absoluteLeft < r1.absoluteRight));
|
|
1206
|
+
}
|
|
1207
|
+
function isAreaContainedIn(area1, area2) {
|
|
1208
|
+
const r1 = area1.rect.value;
|
|
1209
|
+
const r2 = area2.rect.value;
|
|
1210
|
+
if (!r1 || !r2)
|
|
1211
|
+
return false;
|
|
1212
|
+
return (r1.absoluteTop >= r2.absoluteTop &&
|
|
1213
|
+
r1.absoluteBottom <= r2.absoluteBottom &&
|
|
1214
|
+
r1.absoluteLeft >= r2.absoluteLeft &&
|
|
1215
|
+
r1.absoluteRight <= r2.absoluteRight);
|
|
1216
|
+
}
|
|
1217
|
+
function isElementAncestorOf(ancestor, descendant, doc) {
|
|
1218
|
+
return ancestor.contains(descendant);
|
|
1219
|
+
}
|
|
1220
|
+
function isElementSelectable(element, index, elements) {
|
|
1221
|
+
if (isIgnoredCanvas(element)) {
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
if (element.hasAttribute(AREA_MAP_DIV_ATTRIBUTE)) {
|
|
1225
|
+
return false;
|
|
1226
|
+
}
|
|
1227
|
+
if (index === 0 && elements.length > 1 && element.nodeName === 'BODY') {
|
|
1228
|
+
return false;
|
|
1229
|
+
}
|
|
1230
|
+
return true;
|
|
1231
|
+
}
|
|
1232
|
+
function sortAreasByClickDist(areas) {
|
|
1233
|
+
return [...areas].sort((a, b) => {
|
|
1234
|
+
if (a.clickDist !== b.clickDist) {
|
|
1235
|
+
return b.clickDist - a.clickDist;
|
|
1236
|
+
}
|
|
1237
|
+
return b.totalclicks - a.totalclicks;
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
function isRectTooSmallForLabel(rect) {
|
|
1241
|
+
return rect.width < 67 || rect.height < 30;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function getElementSelector(element) {
|
|
1245
|
+
if (element.id) {
|
|
1246
|
+
return `#${element.id}`;
|
|
1247
|
+
}
|
|
1248
|
+
if (element.className) {
|
|
1249
|
+
const classes = Array.from(element.classList).join('.');
|
|
1250
|
+
if (classes) {
|
|
1251
|
+
return `${element.tagName.toLowerCase()}.${classes}`;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
return element.tagName.toLowerCase();
|
|
1255
|
+
}
|
|
1256
|
+
function buildAreaNode(element, hash, heatmapInfo, shadowRoot) {
|
|
1257
|
+
if (!heatmapInfo.elementMapInfo)
|
|
1258
|
+
return;
|
|
1259
|
+
const totalClicks = heatmapInfo.totalClicks || 0;
|
|
1260
|
+
const elementInfo = heatmapInfo.elementMapInfo[hash];
|
|
1261
|
+
const elementClicks = elementInfo?.totalclicks || 0;
|
|
1262
|
+
const clickDist = calculateClickDistribution(elementClicks, totalClicks);
|
|
1263
|
+
const rect = getElementRect(element);
|
|
1264
|
+
const color = getColorFromClickDist(clickDist);
|
|
1265
|
+
const hoverColor = getHoverColorFromClickDist(clickDist);
|
|
1266
|
+
const areaNode = {
|
|
1267
|
+
kind: 'area',
|
|
1268
|
+
id: `${hash}_${Date.now()}`,
|
|
1269
|
+
hash,
|
|
1270
|
+
selector: elementInfo?.selector || getElementSelector(element),
|
|
1271
|
+
// DOM references
|
|
1272
|
+
element,
|
|
1273
|
+
areaElement: null,
|
|
1274
|
+
shadowElement: null,
|
|
1275
|
+
shadowStyleElement: null,
|
|
1276
|
+
// Graph structure
|
|
1277
|
+
parentNode: null,
|
|
1278
|
+
childNodes: new Set(),
|
|
1279
|
+
// Position
|
|
1280
|
+
rect: createObservable(rect),
|
|
1281
|
+
isFixed: isElementFixed(element),
|
|
1282
|
+
priority: false,
|
|
1283
|
+
// Click tracking
|
|
1284
|
+
totalclicks: elementClicks,
|
|
1285
|
+
cumulativeClicks: elementClicks,
|
|
1286
|
+
cumulativeMaxClicks: totalClicks,
|
|
1287
|
+
clickDist,
|
|
1288
|
+
hasClickInfo: true,
|
|
1289
|
+
// Visual
|
|
1290
|
+
color,
|
|
1291
|
+
hoverColor,
|
|
1292
|
+
// Lifecycle
|
|
1293
|
+
changeObserver: null,
|
|
1294
|
+
};
|
|
1295
|
+
return areaNode;
|
|
1296
|
+
}
|
|
1297
|
+
function getTopElementsByClicks(elementMapInfo, topN = 10) {
|
|
1298
|
+
const elements = Object.entries(elementMapInfo)
|
|
1299
|
+
.map(([hash, info]) => ({
|
|
1300
|
+
hash,
|
|
1301
|
+
totalclicks: info.totalclicks,
|
|
1302
|
+
selector: info.selector || '',
|
|
1303
|
+
}))
|
|
1304
|
+
.sort((a, b) => b.totalclicks - a.totalclicks)
|
|
1305
|
+
.slice(0, topN);
|
|
1306
|
+
return elements;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
/**
|
|
1310
|
+
* Resolve overlapping areas by priority rules
|
|
1311
|
+
*
|
|
1312
|
+
* Priority Rules (in order):
|
|
1313
|
+
* 1. Priority flag (manually set areas win)
|
|
1314
|
+
* 2. Click distribution (higher % wins)
|
|
1315
|
+
* 3. Total clicks (more clicks wins)
|
|
1316
|
+
* 4. DOM containment (parent contains child, parent wins)
|
|
1317
|
+
* 5. Size (smaller areas win - more specific)
|
|
1318
|
+
*/
|
|
1319
|
+
function resolveOverlaps(areas, iframeDocument) {
|
|
1320
|
+
if (areas.length === 0)
|
|
1321
|
+
return [];
|
|
1322
|
+
// Group overlapping areas
|
|
1323
|
+
const overlapGroups = findOverlapGroups(areas);
|
|
1324
|
+
// Resolve each group
|
|
1325
|
+
const visibleAreas = new Set();
|
|
1326
|
+
overlapGroups.forEach((group) => {
|
|
1327
|
+
const winner = resolveOverlapGroup(group, iframeDocument);
|
|
1328
|
+
visibleAreas.add(winner);
|
|
1329
|
+
});
|
|
1330
|
+
// Add non-overlapping areas
|
|
1331
|
+
areas.forEach((area) => {
|
|
1332
|
+
const hasOverlap = overlapGroups.some((group) => group.areas.includes(area));
|
|
1333
|
+
if (!hasOverlap) {
|
|
1334
|
+
visibleAreas.add(area);
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
return Array.from(visibleAreas);
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Find groups of overlapping areas
|
|
1341
|
+
*/
|
|
1342
|
+
function findOverlapGroups(areas) {
|
|
1343
|
+
const groups = [];
|
|
1344
|
+
const processed = new Set();
|
|
1345
|
+
areas.forEach((area) => {
|
|
1346
|
+
if (processed.has(area.id))
|
|
1347
|
+
return;
|
|
1348
|
+
// Find all areas that overlap with this one
|
|
1349
|
+
const overlapping = areas.filter((other) => other.id !== area.id && doAreasOverlap(area, other));
|
|
1350
|
+
if (overlapping.length === 0) {
|
|
1351
|
+
// No overlap, skip grouping
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
// Create group with this area and all overlapping
|
|
1355
|
+
const groupAreas = [area, ...overlapping];
|
|
1356
|
+
groupAreas.forEach((a) => processed.add(a.id));
|
|
1357
|
+
// Placeholder - will be resolved later
|
|
1358
|
+
groups.push({
|
|
1359
|
+
areas: groupAreas,
|
|
1360
|
+
winner: area,
|
|
1361
|
+
hidden: [],
|
|
1362
|
+
});
|
|
1363
|
+
});
|
|
1364
|
+
return groups;
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Resolve a single overlap group to find the winner
|
|
1368
|
+
*/
|
|
1369
|
+
function resolveOverlapGroup(group, iframeDocument) {
|
|
1370
|
+
const { areas } = group;
|
|
1371
|
+
if (areas.length === 1)
|
|
1372
|
+
return areas[0];
|
|
1373
|
+
// Sort by priority rules
|
|
1374
|
+
const sorted = [...areas].sort((a, b) => {
|
|
1375
|
+
// Rule 1: Priority flag
|
|
1376
|
+
if (a.priority !== b.priority) {
|
|
1377
|
+
return a.priority ? -1 : 1;
|
|
1378
|
+
}
|
|
1379
|
+
// Rule 2: Click distribution
|
|
1380
|
+
if (a.clickDist !== b.clickDist) {
|
|
1381
|
+
return b.clickDist - a.clickDist;
|
|
1382
|
+
}
|
|
1383
|
+
// Rule 3: Total clicks
|
|
1384
|
+
if (a.totalclicks !== b.totalclicks) {
|
|
1385
|
+
return b.totalclicks - a.totalclicks;
|
|
1386
|
+
}
|
|
1387
|
+
// Rule 4: DOM containment - parent beats child
|
|
1388
|
+
if (iframeDocument) {
|
|
1389
|
+
const aContainsB = isElementAncestorOf(a.element, b.element);
|
|
1390
|
+
const bContainsA = isElementAncestorOf(b.element, a.element);
|
|
1391
|
+
if (aContainsB)
|
|
1392
|
+
return -1; // a is parent, a wins
|
|
1393
|
+
if (bContainsA)
|
|
1394
|
+
return 1; // b is parent, b wins
|
|
1395
|
+
}
|
|
1396
|
+
// Rule 5: Size - smaller (more specific) wins
|
|
1397
|
+
const aSize = (a.rect.value?.width || 0) * (a.rect.value?.height || 0);
|
|
1398
|
+
const bSize = (b.rect.value?.width || 0) * (b.rect.value?.height || 0);
|
|
1399
|
+
return aSize - bSize;
|
|
1400
|
+
});
|
|
1401
|
+
const winner = sorted[0];
|
|
1402
|
+
group.winner = winner;
|
|
1403
|
+
group.hidden = sorted.slice(1);
|
|
1404
|
+
return winner;
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Filter out areas that are completely contained within others
|
|
1408
|
+
* and have lower priority
|
|
1409
|
+
*/
|
|
1410
|
+
function filterContainedAreas(areas) {
|
|
1411
|
+
const visible = [];
|
|
1412
|
+
areas.forEach((area) => {
|
|
1413
|
+
// Check if this area is contained by a higher priority area
|
|
1414
|
+
const isContained = areas.some((other) => {
|
|
1415
|
+
if (other.id === area.id)
|
|
1416
|
+
return false;
|
|
1417
|
+
// Check containment
|
|
1418
|
+
if (!isAreaContainedIn(area, other))
|
|
1419
|
+
return false;
|
|
1420
|
+
// Check priority
|
|
1421
|
+
if (other.priority && !area.priority)
|
|
1422
|
+
return true;
|
|
1423
|
+
if (!other.priority && area.priority)
|
|
1424
|
+
return false;
|
|
1425
|
+
// Compare by click dist
|
|
1426
|
+
if (other.clickDist > area.clickDist)
|
|
1427
|
+
return true;
|
|
1428
|
+
if (other.clickDist < area.clickDist)
|
|
1429
|
+
return false;
|
|
1430
|
+
// Compare by total clicks
|
|
1431
|
+
return other.totalclicks > area.totalclicks;
|
|
1432
|
+
});
|
|
1433
|
+
if (!isContained) {
|
|
1434
|
+
visible.push(area);
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
return visible;
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* Get visible areas after resolving overlaps
|
|
1441
|
+
*/
|
|
1442
|
+
function getVisibleAreas(areas, iframeDocument) {
|
|
1443
|
+
// First pass: filter contained areas
|
|
1444
|
+
let visible = filterContainedAreas(areas);
|
|
1445
|
+
// Second pass: resolve overlaps
|
|
1446
|
+
visible = resolveOverlaps(visible, iframeDocument);
|
|
1447
|
+
// Sort by click dist for rendering order
|
|
1448
|
+
return sortAreasByClickDist(visible);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* Helper functions for setting up area renderer
|
|
1453
|
+
*/
|
|
1454
|
+
/**
|
|
1455
|
+
* Create the outer container for area rendering
|
|
1456
|
+
*/
|
|
1457
|
+
function createAreaContainer(iframeDocument) {
|
|
1458
|
+
const container = iframeDocument.createElement('div');
|
|
1459
|
+
container.setAttribute(AREA_MAP_DIV_ATTRIBUTE, 'true');
|
|
1460
|
+
container.style.cssText = AREA_CONTAINER_STYLES;
|
|
1461
|
+
return container;
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Create the inner container for React portal
|
|
1465
|
+
*/
|
|
1466
|
+
function createInnerContainer(iframeDocument) {
|
|
1467
|
+
const innerContainer = iframeDocument.createElement('div');
|
|
1468
|
+
innerContainer.className = AREA_RENDERER_SELECTORS.innerContainerClass;
|
|
1469
|
+
innerContainer.style.cssText = AREA_INNER_CONTAINER_STYLES;
|
|
1470
|
+
return innerContainer;
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Get or create the outer container element
|
|
1474
|
+
*/
|
|
1475
|
+
function getOrCreateAreaContainer(iframeDocument, customShadowRoot) {
|
|
1476
|
+
let container = iframeDocument.querySelector(AREA_RENDERER_SELECTORS.containerSelector);
|
|
1477
|
+
if (!container) {
|
|
1478
|
+
container = createAreaContainer(iframeDocument);
|
|
1479
|
+
const targetRoot = customShadowRoot || iframeDocument.body;
|
|
1480
|
+
if (targetRoot) {
|
|
1481
|
+
targetRoot.appendChild(container);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
return container;
|
|
1485
|
+
}
|
|
1486
|
+
function getOrCreateContainerShadowRoot(container) {
|
|
1487
|
+
if (container.shadowRoot) {
|
|
1488
|
+
return container.shadowRoot;
|
|
1489
|
+
}
|
|
1490
|
+
return container.attachShadow({ mode: 'open' });
|
|
1491
|
+
}
|
|
1492
|
+
function getOrCreateInnerContainer(shadowRoot, iframeDocument) {
|
|
1493
|
+
let innerContainer = shadowRoot.querySelector(AREA_RENDERER_SELECTORS.innerContainerSelector);
|
|
1494
|
+
if (!innerContainer) {
|
|
1495
|
+
innerContainer = createInnerContainer(iframeDocument);
|
|
1496
|
+
shadowRoot.appendChild(innerContainer);
|
|
1497
|
+
}
|
|
1498
|
+
return innerContainer;
|
|
1499
|
+
}
|
|
1500
|
+
function setupAreaRenderingContainer(iframeDocument, customShadowRoot) {
|
|
1501
|
+
const container = getOrCreateAreaContainer(iframeDocument, customShadowRoot);
|
|
1502
|
+
const shadowRoot = getOrCreateContainerShadowRoot(container);
|
|
1503
|
+
const innerContainer = getOrCreateInnerContainer(shadowRoot, iframeDocument);
|
|
1504
|
+
return innerContainer;
|
|
1505
|
+
}
|
|
1506
|
+
function cleanupAreaRenderingContainer(container) {
|
|
1507
|
+
if (container && container.parentNode) {
|
|
1508
|
+
container.parentNode.removeChild(container);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
906
1512
|
function findLastSizeOfDom(data) {
|
|
907
1513
|
const listDocs = data
|
|
908
1514
|
.filter((item) => item.doc?.find((doc) => doc.data.width && doc.data.height))
|
|
@@ -927,44 +1533,33 @@ function findLastSizeOfDom(data) {
|
|
|
927
1533
|
},
|
|
928
1534
|
};
|
|
929
1535
|
}
|
|
930
|
-
function decodePayloads(payload) {
|
|
931
|
-
try {
|
|
932
|
-
return decode(payload);
|
|
933
|
-
}
|
|
934
|
-
catch (
|
|
935
|
-
return null;
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
function calculateRankPosition(rect, widthScale) {
|
|
940
|
-
const top = rect.top <= 18 ? rect.top + 3 : rect.top - 18;
|
|
941
|
-
const left = rect.left <= 18 ? rect.left + 3 : rect.left - 18;
|
|
942
|
-
return {
|
|
943
|
-
transform: `scale(${1.2 * widthScale})`,
|
|
944
|
-
top: Number.isNaN(top) ? undefined : top,
|
|
945
|
-
left: Number.isNaN(left) ? undefined : left,
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
function isElementInViewport(elementRect, visualRef, scale) {
|
|
949
|
-
if (!elementRect)
|
|
950
|
-
return false;
|
|
951
|
-
const visualRect = visualRef.current?.getBoundingClientRect();
|
|
952
|
-
if (!visualRect)
|
|
953
|
-
return false;
|
|
954
|
-
// Element position relative to the document (or container's content)
|
|
955
|
-
const elementTop = elementRect.top * scale;
|
|
956
|
-
const elementBottom = (elementRect.top + elementRect.height) * scale;
|
|
957
|
-
// Current scroll position
|
|
958
|
-
const scrollTop = visualRef.current?.scrollTop || 0;
|
|
959
|
-
const viewportHeight = visualRect.height;
|
|
960
|
-
// Visible viewport range in the scrollable content
|
|
961
|
-
const viewportTop = scrollTop;
|
|
962
|
-
const viewportBottom = scrollTop + viewportHeight;
|
|
963
|
-
// Check if element is within the visible viewport
|
|
964
|
-
// Element is visible if it overlaps with the viewport
|
|
965
|
-
return elementBottom > viewportTop && elementTop < viewportBottom;
|
|
1536
|
+
function decodePayloads(payload) {
|
|
1537
|
+
try {
|
|
1538
|
+
return decode(payload);
|
|
1539
|
+
}
|
|
1540
|
+
catch (_error) {
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
966
1543
|
}
|
|
967
1544
|
|
|
1545
|
+
/**
|
|
1546
|
+
* Generate unique element ID for a specific view
|
|
1547
|
+
* @param baseId - Base element ID
|
|
1548
|
+
* @param viewId - View ID
|
|
1549
|
+
* @returns Unique element ID (e.g., 'gx-hm-clicked-element-view-0')
|
|
1550
|
+
*/
|
|
1551
|
+
const getElementId = (baseId, viewId) => {
|
|
1552
|
+
return `${baseId}-${viewId}`;
|
|
1553
|
+
};
|
|
1554
|
+
const getClickedElementId = (viewId, isSecondary = false) => {
|
|
1555
|
+
const baseId = isSecondary ? SECONDARY_CLICKED_ELEMENT_ID_BASE : CLICKED_ELEMENT_ID_BASE;
|
|
1556
|
+
return getElementId(baseId, viewId);
|
|
1557
|
+
};
|
|
1558
|
+
const getHoveredElementId = (viewId, isSecondary = false) => {
|
|
1559
|
+
const baseId = isSecondary ? SECONDARY_HOVERED_ELEMENT_ID_BASE : HOVERED_ELEMENT_ID_BASE;
|
|
1560
|
+
return getElementId(baseId, viewId);
|
|
1561
|
+
};
|
|
1562
|
+
|
|
968
1563
|
function getElementLayout(element) {
|
|
969
1564
|
if (!element?.getBoundingClientRect)
|
|
970
1565
|
return null;
|
|
@@ -978,23 +1573,6 @@ function getElementLayout(element) {
|
|
|
978
1573
|
height: rect.height,
|
|
979
1574
|
};
|
|
980
1575
|
}
|
|
981
|
-
const getElementAtPoint = (doc, x, y) => {
|
|
982
|
-
let el = null;
|
|
983
|
-
if ('caretPositionFromPoint' in doc) {
|
|
984
|
-
el = doc.caretPositionFromPoint(x, y)?.offsetNode ?? null;
|
|
985
|
-
}
|
|
986
|
-
el = el ?? doc.elementFromPoint(x, y);
|
|
987
|
-
let element = el;
|
|
988
|
-
while (element && element.nodeType === Node.TEXT_NODE) {
|
|
989
|
-
element = element.parentElement;
|
|
990
|
-
}
|
|
991
|
-
return element;
|
|
992
|
-
};
|
|
993
|
-
function getElementHash(element) {
|
|
994
|
-
return (element.getAttribute('data-clarity-hash') ||
|
|
995
|
-
element.getAttribute('data-clarity-hashalpha') ||
|
|
996
|
-
element.getAttribute('data-clarity-hashbeta'));
|
|
997
|
-
}
|
|
998
1576
|
const getElementRank = (hash, elements) => {
|
|
999
1577
|
if (!elements)
|
|
1000
1578
|
return 0;
|
|
@@ -1021,9 +1599,15 @@ const buildElementInfo = (hash, rect, heatmapInfo) => {
|
|
|
1021
1599
|
};
|
|
1022
1600
|
};
|
|
1023
1601
|
|
|
1024
|
-
|
|
1025
|
-
const
|
|
1026
|
-
const
|
|
1602
|
+
function calculateRankPosition(rect, widthScale) {
|
|
1603
|
+
const top = rect.top <= 18 ? rect.top + 3 : rect.top - 18;
|
|
1604
|
+
const left = rect.left <= 18 ? rect.left + 3 : rect.left - 18;
|
|
1605
|
+
return {
|
|
1606
|
+
transform: `scale(${1.2 * widthScale})`,
|
|
1607
|
+
top: Number.isNaN(top) ? undefined : top,
|
|
1608
|
+
left: Number.isNaN(left) ? undefined : left,
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1027
1611
|
|
|
1028
1612
|
const getViewportDimensions = (containerElm) => {
|
|
1029
1613
|
if (containerElm) {
|
|
@@ -1079,7 +1663,8 @@ const calculateHorizontalPlacementPosition = (targetRect, calloutRect, placement
|
|
|
1079
1663
|
|
|
1080
1664
|
const isLeftPositionValid = (leftPos, calloutWidth, viewportWidth, padding, containerRect) => {
|
|
1081
1665
|
if (containerRect) {
|
|
1082
|
-
return leftPos >= containerRect.left + padding &&
|
|
1666
|
+
return (leftPos >= containerRect.left + padding &&
|
|
1667
|
+
leftPos + calloutWidth <= containerRect.right - padding);
|
|
1083
1668
|
}
|
|
1084
1669
|
return leftPos >= padding && leftPos + calloutWidth <= viewportWidth - padding;
|
|
1085
1670
|
};
|
|
@@ -1164,14 +1749,15 @@ const constrainToViewport = (position, calloutRect, viewport, padding, container
|
|
|
1164
1749
|
return { top, left };
|
|
1165
1750
|
};
|
|
1166
1751
|
|
|
1167
|
-
const calcCalloutPosition = (
|
|
1752
|
+
const calcCalloutPosition = (options) => {
|
|
1753
|
+
const { targetElm, calloutElm, setPosition, hozOffset = CALLOUT_HORIZONTAL_OFFSET, alignment = 'center', containerElm, } = options;
|
|
1168
1754
|
return () => {
|
|
1169
1755
|
// 1. Get dimensions
|
|
1170
1756
|
const rectDimensions = getElementDimensions(targetElm, calloutElm);
|
|
1171
1757
|
const viewport = getViewportDimensions(containerElm);
|
|
1172
1758
|
const containerRect = containerElm?.getBoundingClientRect();
|
|
1173
|
-
const padding =
|
|
1174
|
-
const arrowSize =
|
|
1759
|
+
const padding = CALLOUT_PADDING;
|
|
1760
|
+
const arrowSize = CALLOUT_ARROW_SIZE;
|
|
1175
1761
|
// 2. Generate all position candidates
|
|
1176
1762
|
const candidates = generateAllPositionCandidates(rectDimensions, viewport, alignment, hozOffset, padding, arrowSize, containerRect);
|
|
1177
1763
|
// 3. Select best position
|
|
@@ -1189,6 +1775,191 @@ const calcCalloutPosition = ({ targetElm, calloutElm, setPosition, hozOffset = H
|
|
|
1189
1775
|
};
|
|
1190
1776
|
};
|
|
1191
1777
|
|
|
1778
|
+
/**
|
|
1779
|
+
* Get all elements at a specific point (x, y), with support for Shadow DOM
|
|
1780
|
+
*/
|
|
1781
|
+
function getElementsAtPoint(doc, x, y, options = {}) {
|
|
1782
|
+
const { filterFn, ignoreCanvas = true, visitedShadowRoots = new Set() } = options;
|
|
1783
|
+
// Get all elements at this point
|
|
1784
|
+
let elementsAtPoint = doc.elementsFromPoint(x, y);
|
|
1785
|
+
// Filter out canvas elements if requested
|
|
1786
|
+
if (ignoreCanvas) {
|
|
1787
|
+
elementsAtPoint = elementsAtPoint.filter((el) => !isIgnoredCanvas(el));
|
|
1788
|
+
}
|
|
1789
|
+
// Apply custom filter if provided
|
|
1790
|
+
if (filterFn) {
|
|
1791
|
+
const matchedElement = elementsAtPoint.find(filterFn);
|
|
1792
|
+
// If matched element has Shadow DOM and we haven't visited it yet, recurse
|
|
1793
|
+
if (matchedElement?.shadowRoot && !visitedShadowRoots.has(matchedElement.shadowRoot)) {
|
|
1794
|
+
visitedShadowRoots.add(matchedElement.shadowRoot);
|
|
1795
|
+
return getElementsAtPoint(matchedElement.shadowRoot, x, y, {
|
|
1796
|
+
...options,
|
|
1797
|
+
visitedShadowRoots,
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
return elementsAtPoint;
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Get the element at a specific point (x, y)
|
|
1805
|
+
*/
|
|
1806
|
+
const getElementAtPoint = (doc, x, y) => {
|
|
1807
|
+
let el = null;
|
|
1808
|
+
if ('caretPositionFromPoint' in doc) {
|
|
1809
|
+
el = doc.caretPositionFromPoint(x, y)?.offsetNode ?? null;
|
|
1810
|
+
}
|
|
1811
|
+
el = el ?? doc.elementFromPoint(x, y);
|
|
1812
|
+
let element = el;
|
|
1813
|
+
while (element && element.nodeType === Node.TEXT_NODE) {
|
|
1814
|
+
element = element.parentElement;
|
|
1815
|
+
}
|
|
1816
|
+
return element;
|
|
1817
|
+
};
|
|
1818
|
+
function getElementHash(element) {
|
|
1819
|
+
return (element.getAttribute('data-clarity-hash') ||
|
|
1820
|
+
element.getAttribute('data-clarity-hashalpha') ||
|
|
1821
|
+
element.getAttribute('data-clarity-hashbeta'));
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
class Logger {
|
|
1825
|
+
config = {
|
|
1826
|
+
enabled: false,
|
|
1827
|
+
prefix: '',
|
|
1828
|
+
timestamp: false,
|
|
1829
|
+
};
|
|
1830
|
+
/**
|
|
1831
|
+
* Cấu hình logger
|
|
1832
|
+
* @param config - Cấu hình logger
|
|
1833
|
+
*/
|
|
1834
|
+
configure(config) {
|
|
1835
|
+
this.config = { ...this.config, ...config };
|
|
1836
|
+
}
|
|
1837
|
+
/**
|
|
1838
|
+
* Lấy cấu hình hiện tại
|
|
1839
|
+
*/
|
|
1840
|
+
getConfig() {
|
|
1841
|
+
return { ...this.config };
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Bật logger
|
|
1845
|
+
*/
|
|
1846
|
+
enable() {
|
|
1847
|
+
this.config.enabled = true;
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Tắt logger
|
|
1851
|
+
*/
|
|
1852
|
+
disable() {
|
|
1853
|
+
this.config.enabled = false;
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Format message với prefix và timestamp
|
|
1857
|
+
*/
|
|
1858
|
+
formatMessage(...args) {
|
|
1859
|
+
const parts = [];
|
|
1860
|
+
if (this.config.timestamp) {
|
|
1861
|
+
parts.push(`[${new Date().toISOString()}]`);
|
|
1862
|
+
}
|
|
1863
|
+
if (this.config.prefix) {
|
|
1864
|
+
parts.push(`[${this.config.prefix}]`);
|
|
1865
|
+
}
|
|
1866
|
+
if (parts.length > 0) {
|
|
1867
|
+
return [parts.join(' '), ...args];
|
|
1868
|
+
}
|
|
1869
|
+
return args;
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Log message
|
|
1873
|
+
*/
|
|
1874
|
+
log(...args) {
|
|
1875
|
+
if (!this.config.enabled)
|
|
1876
|
+
return;
|
|
1877
|
+
console.log(...this.formatMessage(...args));
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Log info message
|
|
1881
|
+
*/
|
|
1882
|
+
info(...args) {
|
|
1883
|
+
if (!this.config.enabled)
|
|
1884
|
+
return;
|
|
1885
|
+
console.info(...this.formatMessage(...args));
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Log warning message
|
|
1889
|
+
*/
|
|
1890
|
+
warn(...args) {
|
|
1891
|
+
if (!this.config.enabled)
|
|
1892
|
+
return;
|
|
1893
|
+
console.warn(...this.formatMessage(...args));
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Log error message
|
|
1897
|
+
*/
|
|
1898
|
+
error(...args) {
|
|
1899
|
+
if (!this.config.enabled)
|
|
1900
|
+
return;
|
|
1901
|
+
console.error(...this.formatMessage(...args));
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Log debug message
|
|
1905
|
+
*/
|
|
1906
|
+
debug(...args) {
|
|
1907
|
+
if (!this.config.enabled)
|
|
1908
|
+
return;
|
|
1909
|
+
console.debug(...this.formatMessage(...args));
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Log table data
|
|
1913
|
+
*/
|
|
1914
|
+
table(data) {
|
|
1915
|
+
if (!this.config.enabled)
|
|
1916
|
+
return;
|
|
1917
|
+
console.table(data);
|
|
1918
|
+
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Start a group
|
|
1921
|
+
*/
|
|
1922
|
+
group(label) {
|
|
1923
|
+
if (!this.config.enabled)
|
|
1924
|
+
return;
|
|
1925
|
+
console.group(...this.formatMessage(label));
|
|
1926
|
+
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Start a collapsed group
|
|
1929
|
+
*/
|
|
1930
|
+
groupCollapsed(label) {
|
|
1931
|
+
if (!this.config.enabled)
|
|
1932
|
+
return;
|
|
1933
|
+
console.groupCollapsed(...this.formatMessage(label));
|
|
1934
|
+
}
|
|
1935
|
+
/**
|
|
1936
|
+
* End a group
|
|
1937
|
+
*/
|
|
1938
|
+
groupEnd() {
|
|
1939
|
+
if (!this.config.enabled)
|
|
1940
|
+
return;
|
|
1941
|
+
console.groupEnd();
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Start a timer
|
|
1945
|
+
*/
|
|
1946
|
+
time(label) {
|
|
1947
|
+
if (!this.config.enabled)
|
|
1948
|
+
return;
|
|
1949
|
+
console.time(label);
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1952
|
+
* End a timer
|
|
1953
|
+
*/
|
|
1954
|
+
timeEnd(label) {
|
|
1955
|
+
if (!this.config.enabled)
|
|
1956
|
+
return;
|
|
1957
|
+
console.timeEnd(label);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
// Export singleton instance
|
|
1961
|
+
const logger = new Logger();
|
|
1962
|
+
|
|
1192
1963
|
class IframeNavigationBlockerV2 {
|
|
1193
1964
|
doc;
|
|
1194
1965
|
win;
|
|
@@ -1196,17 +1967,18 @@ class IframeNavigationBlockerV2 {
|
|
|
1196
1967
|
showMessage = false;
|
|
1197
1968
|
originalWindowOpen;
|
|
1198
1969
|
observers = [];
|
|
1199
|
-
constructor(iframe) {
|
|
1970
|
+
constructor(iframe, config) {
|
|
1200
1971
|
if (!iframe.contentDocument || !iframe.contentWindow) {
|
|
1201
1972
|
throw new Error('Iframe document or window not accessible');
|
|
1202
1973
|
}
|
|
1203
1974
|
this.doc = iframe.contentDocument;
|
|
1204
1975
|
this.win = iframe.contentWindow;
|
|
1205
1976
|
this.originalWindowOpen = this.win.open.bind(this.win);
|
|
1977
|
+
logger.configure({ enabled: !!config?.debug, prefix: '[IframeNavigationBlockerV2]' });
|
|
1206
1978
|
this.init();
|
|
1207
1979
|
}
|
|
1208
1980
|
init() {
|
|
1209
|
-
|
|
1981
|
+
logger.log('Initializing...');
|
|
1210
1982
|
try {
|
|
1211
1983
|
// Chặn navigation qua links
|
|
1212
1984
|
this.blockLinkNavigation();
|
|
@@ -1222,7 +1994,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1222
1994
|
this.injectCSP();
|
|
1223
1995
|
}
|
|
1224
1996
|
catch (error) {
|
|
1225
|
-
|
|
1997
|
+
logger.error('Init error:', error);
|
|
1226
1998
|
}
|
|
1227
1999
|
}
|
|
1228
2000
|
blockLinkNavigation() {
|
|
@@ -1236,11 +2008,11 @@ class IframeNavigationBlockerV2 {
|
|
|
1236
2008
|
const href = link.getAttribute('href');
|
|
1237
2009
|
// Cho phép hash links và empty links
|
|
1238
2010
|
if (!href || href === '' || href === '#' || href.startsWith('#')) {
|
|
1239
|
-
|
|
2011
|
+
logger.log('Allowed hash navigation:', href);
|
|
1240
2012
|
return;
|
|
1241
2013
|
}
|
|
1242
2014
|
// Chặn tất cả các loại navigation
|
|
1243
|
-
|
|
2015
|
+
logger.log('Blocked link navigation to:', href);
|
|
1244
2016
|
e.preventDefault();
|
|
1245
2017
|
e.stopPropagation();
|
|
1246
2018
|
e.stopImmediatePropagation();
|
|
@@ -1256,7 +2028,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1256
2028
|
if (link) {
|
|
1257
2029
|
const href = link.getAttribute('href');
|
|
1258
2030
|
if (href && !href.startsWith('#')) {
|
|
1259
|
-
|
|
2031
|
+
logger.log('Blocked auxclick navigation');
|
|
1260
2032
|
e.preventDefault();
|
|
1261
2033
|
e.stopPropagation();
|
|
1262
2034
|
e.stopImmediatePropagation();
|
|
@@ -1289,13 +2061,13 @@ class IframeNavigationBlockerV2 {
|
|
|
1289
2061
|
const action = form.getAttribute('action');
|
|
1290
2062
|
// Cho phép forms không có action
|
|
1291
2063
|
if (!action || action === '' || action === '#') {
|
|
1292
|
-
|
|
2064
|
+
logger.log('Allowed same-page form');
|
|
1293
2065
|
e.preventDefault();
|
|
1294
2066
|
this.handleFormSubmit(form);
|
|
1295
2067
|
return;
|
|
1296
2068
|
}
|
|
1297
2069
|
// Chặn tất cả external submissions
|
|
1298
|
-
|
|
2070
|
+
logger.log('Blocked form submission to:', action);
|
|
1299
2071
|
e.preventDefault();
|
|
1300
2072
|
e.stopPropagation();
|
|
1301
2073
|
e.stopImmediatePropagation();
|
|
@@ -1309,7 +2081,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1309
2081
|
return this.originalWindowOpen(...args);
|
|
1310
2082
|
}
|
|
1311
2083
|
const url = args[0]?.toString() || 'popup';
|
|
1312
|
-
|
|
2084
|
+
logger.log('Blocked window.open:', url);
|
|
1313
2085
|
this.notifyBlockedNavigation(url);
|
|
1314
2086
|
return null;
|
|
1315
2087
|
});
|
|
@@ -1319,7 +2091,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1319
2091
|
this.win.addEventListener('beforeunload', (e) => {
|
|
1320
2092
|
if (!this.isEnabled)
|
|
1321
2093
|
return;
|
|
1322
|
-
|
|
2094
|
+
logger.log('Blocked beforeunload');
|
|
1323
2095
|
e.preventDefault();
|
|
1324
2096
|
e.returnValue = '';
|
|
1325
2097
|
return '';
|
|
@@ -1328,7 +2100,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1328
2100
|
this.win.addEventListener('unload', (e) => {
|
|
1329
2101
|
if (!this.isEnabled)
|
|
1330
2102
|
return;
|
|
1331
|
-
|
|
2103
|
+
logger.log('Blocked unload');
|
|
1332
2104
|
e.preventDefault();
|
|
1333
2105
|
e.stopPropagation();
|
|
1334
2106
|
}, true);
|
|
@@ -1336,7 +2108,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1336
2108
|
this.win.addEventListener('popstate', (e) => {
|
|
1337
2109
|
if (!this.isEnabled)
|
|
1338
2110
|
return;
|
|
1339
|
-
|
|
2111
|
+
logger.log('Blocked popstate');
|
|
1340
2112
|
e.preventDefault();
|
|
1341
2113
|
e.stopPropagation();
|
|
1342
2114
|
}, true);
|
|
@@ -1389,11 +2161,11 @@ class IframeNavigationBlockerV2 {
|
|
|
1389
2161
|
meta.httpEquiv = 'Content-Security-Policy';
|
|
1390
2162
|
meta.content = "navigate-to 'none'"; // Chặn tất cả navigation
|
|
1391
2163
|
this.doc.head.appendChild(meta);
|
|
1392
|
-
|
|
2164
|
+
logger.log('Injected CSP');
|
|
1393
2165
|
}
|
|
1394
2166
|
}
|
|
1395
2167
|
catch (error) {
|
|
1396
|
-
|
|
2168
|
+
logger.warn('Could not inject CSP:', error);
|
|
1397
2169
|
}
|
|
1398
2170
|
}
|
|
1399
2171
|
handleFormSubmit(form) {
|
|
@@ -1402,13 +2174,13 @@ class IframeNavigationBlockerV2 {
|
|
|
1402
2174
|
formData.forEach((value, key) => {
|
|
1403
2175
|
data[key] = value;
|
|
1404
2176
|
});
|
|
1405
|
-
|
|
2177
|
+
logger.log('Handling form data:', data);
|
|
1406
2178
|
window.dispatchEvent(new CustomEvent('iframe-form-submit', {
|
|
1407
2179
|
detail: { form, data },
|
|
1408
2180
|
}));
|
|
1409
2181
|
}
|
|
1410
2182
|
notifyBlockedNavigation(url) {
|
|
1411
|
-
|
|
2183
|
+
logger.warn('Navigation blocked to:', url);
|
|
1412
2184
|
window.dispatchEvent(new CustomEvent('iframe-navigation-blocked', {
|
|
1413
2185
|
detail: { url, timestamp: Date.now() },
|
|
1414
2186
|
}));
|
|
@@ -1458,19 +2230,19 @@ class IframeNavigationBlockerV2 {
|
|
|
1458
2230
|
}
|
|
1459
2231
|
enable() {
|
|
1460
2232
|
this.isEnabled = true;
|
|
1461
|
-
|
|
2233
|
+
logger.log('Enabled');
|
|
1462
2234
|
}
|
|
1463
2235
|
enableMessage() {
|
|
1464
2236
|
this.showMessage = true;
|
|
1465
|
-
|
|
2237
|
+
logger.log('Enabled message');
|
|
1466
2238
|
}
|
|
1467
2239
|
disable() {
|
|
1468
2240
|
this.isEnabled = false;
|
|
1469
|
-
|
|
2241
|
+
logger.log('Disabled');
|
|
1470
2242
|
}
|
|
1471
2243
|
disableMessage() {
|
|
1472
2244
|
this.showMessage = false;
|
|
1473
|
-
|
|
2245
|
+
logger.log('Disabled message');
|
|
1474
2246
|
}
|
|
1475
2247
|
destroy() {
|
|
1476
2248
|
this.isEnabled = false;
|
|
@@ -1478,7 +2250,7 @@ class IframeNavigationBlockerV2 {
|
|
|
1478
2250
|
// Cleanup observers
|
|
1479
2251
|
this.observers.forEach((observer) => observer.disconnect());
|
|
1480
2252
|
this.observers = [];
|
|
1481
|
-
|
|
2253
|
+
logger.log('Destroyed');
|
|
1482
2254
|
}
|
|
1483
2255
|
}
|
|
1484
2256
|
|
|
@@ -1494,6 +2266,7 @@ class IframeStyleReplacer {
|
|
|
1494
2266
|
this.doc = iframe.contentDocument;
|
|
1495
2267
|
this.win = iframe.contentWindow;
|
|
1496
2268
|
this.config = config;
|
|
2269
|
+
logger.configure({ enabled: !!config?.debug, prefix: '[IframeStyleReplacer]' });
|
|
1497
2270
|
}
|
|
1498
2271
|
px(value) {
|
|
1499
2272
|
return `${value.toFixed(2)}px`;
|
|
@@ -1527,7 +2300,7 @@ class IframeStyleReplacer {
|
|
|
1527
2300
|
count++;
|
|
1528
2301
|
}
|
|
1529
2302
|
});
|
|
1530
|
-
|
|
2303
|
+
logger.log(`Replaced ${count} inline style elements`);
|
|
1531
2304
|
return count;
|
|
1532
2305
|
}
|
|
1533
2306
|
processStyleTags() {
|
|
@@ -1540,7 +2313,7 @@ class IframeStyleReplacer {
|
|
|
1540
2313
|
count++;
|
|
1541
2314
|
}
|
|
1542
2315
|
});
|
|
1543
|
-
|
|
2316
|
+
logger.log(`Replaced ${count} <style> tags`);
|
|
1544
2317
|
return count;
|
|
1545
2318
|
}
|
|
1546
2319
|
processRule(rule) {
|
|
@@ -1571,7 +2344,7 @@ class IframeStyleReplacer {
|
|
|
1571
2344
|
try {
|
|
1572
2345
|
// Bỏ qua external CSS (cross-origin)
|
|
1573
2346
|
if (sheet.href && !sheet.href.startsWith(this.win.location.origin)) {
|
|
1574
|
-
|
|
2347
|
+
logger.log('Skipping external CSS:', sheet.href);
|
|
1575
2348
|
return;
|
|
1576
2349
|
}
|
|
1577
2350
|
const rules = sheet.cssRules || sheet.rules;
|
|
@@ -1582,10 +2355,10 @@ class IframeStyleReplacer {
|
|
|
1582
2355
|
}
|
|
1583
2356
|
}
|
|
1584
2357
|
catch (e) {
|
|
1585
|
-
|
|
2358
|
+
logger.warn('Cannot read stylesheet (CORS?):', e.message);
|
|
1586
2359
|
}
|
|
1587
2360
|
});
|
|
1588
|
-
|
|
2361
|
+
logger.log(`Replaced ${total} rules in stylesheets`);
|
|
1589
2362
|
return total;
|
|
1590
2363
|
}
|
|
1591
2364
|
async processLinkedStylesheets() {
|
|
@@ -1593,7 +2366,7 @@ class IframeStyleReplacer {
|
|
|
1593
2366
|
let count = 0;
|
|
1594
2367
|
for (const link of Array.from(links)) {
|
|
1595
2368
|
if (!link.href.startsWith(this.win.location.origin)) {
|
|
1596
|
-
|
|
2369
|
+
logger.log('Skipping external CSS:', link.href);
|
|
1597
2370
|
continue;
|
|
1598
2371
|
}
|
|
1599
2372
|
try {
|
|
@@ -1611,10 +2384,10 @@ class IframeStyleReplacer {
|
|
|
1611
2384
|
}
|
|
1612
2385
|
}
|
|
1613
2386
|
catch (e) {
|
|
1614
|
-
|
|
2387
|
+
logger.warn('Cannot load CSS:', link.href, e);
|
|
1615
2388
|
}
|
|
1616
2389
|
}
|
|
1617
|
-
|
|
2390
|
+
logger.log(`Replaced ${count} linked CSS files`);
|
|
1618
2391
|
return count;
|
|
1619
2392
|
}
|
|
1620
2393
|
getFinalHeight() {
|
|
@@ -1638,7 +2411,7 @@ class IframeStyleReplacer {
|
|
|
1638
2411
|
}
|
|
1639
2412
|
async run() {
|
|
1640
2413
|
try {
|
|
1641
|
-
|
|
2414
|
+
logger.log('Starting viewport units replacement...');
|
|
1642
2415
|
this.processInlineStyles();
|
|
1643
2416
|
this.processStyleTags();
|
|
1644
2417
|
this.processStylesheets();
|
|
@@ -1648,13 +2421,13 @@ class IframeStyleReplacer {
|
|
|
1648
2421
|
requestAnimationFrame(() => {
|
|
1649
2422
|
const height = this.getFinalHeight();
|
|
1650
2423
|
const width = this.getFinalWidth();
|
|
1651
|
-
|
|
2424
|
+
logger.log('Calculated dimensions:', { height, width });
|
|
1652
2425
|
resolve({ height, width });
|
|
1653
2426
|
});
|
|
1654
2427
|
});
|
|
1655
2428
|
}
|
|
1656
2429
|
catch (err) {
|
|
1657
|
-
|
|
2430
|
+
logger.error('Critical error:', err);
|
|
1658
2431
|
return {
|
|
1659
2432
|
height: this.doc.body.scrollHeight || 1000,
|
|
1660
2433
|
width: this.doc.body.scrollWidth || 1000,
|
|
@@ -1675,10 +2448,11 @@ class IframeHelperFixer {
|
|
|
1675
2448
|
this.config = config;
|
|
1676
2449
|
this.iframe = config.iframe;
|
|
1677
2450
|
this.init();
|
|
2451
|
+
logger.configure({ enabled: !!config?.debug, prefix: '[IframeHelper]' });
|
|
1678
2452
|
}
|
|
1679
2453
|
async init() {
|
|
1680
2454
|
if (!this.iframe) {
|
|
1681
|
-
|
|
2455
|
+
logger.error('iframe not found');
|
|
1682
2456
|
this.config.onError?.(new Error('iframe not found'));
|
|
1683
2457
|
return;
|
|
1684
2458
|
}
|
|
@@ -1692,19 +2466,19 @@ class IframeHelperFixer {
|
|
|
1692
2466
|
}
|
|
1693
2467
|
async process() {
|
|
1694
2468
|
if (!this.iframe.contentDocument || !this.iframe.contentWindow) {
|
|
1695
|
-
|
|
2469
|
+
logger.error('Cannot access iframe document');
|
|
1696
2470
|
this.config.onError?.(new Error('Cannot access iframe document'));
|
|
1697
2471
|
return;
|
|
1698
2472
|
}
|
|
1699
2473
|
try {
|
|
1700
|
-
|
|
2474
|
+
logger.log('Processing viewport units...');
|
|
1701
2475
|
// Create replacer instance
|
|
1702
2476
|
this.replacer = new IframeStyleReplacer(this.iframe, this.config);
|
|
1703
2477
|
// Create navigation blocker
|
|
1704
2478
|
this.navigationBlocker = new IframeNavigationBlockerV2(this.iframe);
|
|
1705
2479
|
// Run replacement
|
|
1706
2480
|
const result = await this.replacer.run();
|
|
1707
|
-
|
|
2481
|
+
logger.log('Process completed:', result);
|
|
1708
2482
|
// Trigger success callback
|
|
1709
2483
|
this.config.onSuccess?.(result);
|
|
1710
2484
|
// Dispatch custom event
|
|
@@ -1713,12 +2487,12 @@ class IframeHelperFixer {
|
|
|
1713
2487
|
}));
|
|
1714
2488
|
}
|
|
1715
2489
|
catch (error) {
|
|
1716
|
-
|
|
2490
|
+
logger.error('Failed to process:', error);
|
|
1717
2491
|
this.config.onError?.(error);
|
|
1718
2492
|
}
|
|
1719
2493
|
}
|
|
1720
2494
|
async recalculate() {
|
|
1721
|
-
|
|
2495
|
+
logger.log('Recalculating...');
|
|
1722
2496
|
await this.process();
|
|
1723
2497
|
}
|
|
1724
2498
|
updateConfig(config) {
|
|
@@ -1743,27 +2517,291 @@ class IframeHelperFixer {
|
|
|
1743
2517
|
this.replacer = null;
|
|
1744
2518
|
this.navigationBlocker?.destroy();
|
|
1745
2519
|
this.navigationBlocker = null;
|
|
1746
|
-
|
|
2520
|
+
logger.log('Destroyed');
|
|
1747
2521
|
}
|
|
1748
2522
|
}
|
|
1749
2523
|
|
|
1750
2524
|
function initIframeHelperFixer(config) {
|
|
1751
2525
|
const fixer = new IframeHelperFixer(config);
|
|
1752
2526
|
window.addEventListener('iframe-dimensions-applied', ((e) => {
|
|
1753
|
-
|
|
1754
|
-
console.log('[IframeHelper] Iframe dimensions finalized:', ev.detail);
|
|
2527
|
+
// console.log('[IframeHelper] Iframe dimensions finalized:', ev.detail);
|
|
1755
2528
|
}));
|
|
1756
2529
|
window.addEventListener('iframe-navigation-blocked', ((e) => {
|
|
1757
|
-
|
|
1758
|
-
console.warn('[IframeHelper] Iframe tried to navigate to:', ev.detail.url);
|
|
2530
|
+
// console.warn('[IframeHelper] Iframe tried to navigate to:', ev.detail.url);
|
|
1759
2531
|
}));
|
|
1760
2532
|
window.addEventListener('iframe-form-submit', ((e) => {
|
|
1761
|
-
|
|
1762
|
-
console.log('[IframeHelper] Iframe form submitted:', ev.detail.data);
|
|
2533
|
+
// console.log('[IframeHelper] Iframe form submitted:', ev.detail.data);
|
|
1763
2534
|
}));
|
|
1764
2535
|
return fixer;
|
|
1765
2536
|
}
|
|
1766
2537
|
|
|
2538
|
+
function useAreaCreation(options = {}) {
|
|
2539
|
+
const { customShadowRoot, onAreaCreated } = options;
|
|
2540
|
+
const { dataInfo } = useHeatmapData();
|
|
2541
|
+
const { areas, addArea } = useHeatmapAreaClick();
|
|
2542
|
+
const handleCreateAreaFromElement = useCallback((element) => {
|
|
2543
|
+
if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks) {
|
|
2544
|
+
logger.warn('Cannot create area: missing heatmap data');
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
const hash = getElementHash(element);
|
|
2548
|
+
if (!hash) {
|
|
2549
|
+
logger.warn('Cannot create area: missing hash');
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
const alreadyExists = areas.some((area) => area.hash === hash);
|
|
2553
|
+
if (alreadyExists) {
|
|
2554
|
+
logger.warn(`Area already exists for element: ${hash}`);
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
try {
|
|
2558
|
+
const area = buildAreaNode(element, hash, dataInfo, customShadowRoot);
|
|
2559
|
+
if (!area)
|
|
2560
|
+
return;
|
|
2561
|
+
addArea(area);
|
|
2562
|
+
if (onAreaCreated) {
|
|
2563
|
+
onAreaCreated(area);
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
catch (error) {
|
|
2567
|
+
logger.error('Failed to create area:', error);
|
|
2568
|
+
}
|
|
2569
|
+
}, [dataInfo, areas, addArea, customShadowRoot]);
|
|
2570
|
+
return {
|
|
2571
|
+
onAreaCreatedElement: handleCreateAreaFromElement,
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
function useAreaEditMode({ iframeRef, onAreaCreatedElement, enabled = false, }) {
|
|
2576
|
+
const [hoveredElement, setHoveredElement] = useState(null);
|
|
2577
|
+
const [isHovering, setIsHovering] = useState(false);
|
|
2578
|
+
const { isEditingMode } = useHeatmapAreaClick();
|
|
2579
|
+
const iframeDocument = iframeRef.current?.contentDocument;
|
|
2580
|
+
const isActive = enabled && isEditingMode;
|
|
2581
|
+
const handleMouseMove = useCallback((e) => {
|
|
2582
|
+
if (!isActive || !iframeDocument)
|
|
2583
|
+
return;
|
|
2584
|
+
const elements = getElementsAtPoint(iframeDocument, e.clientX, e.clientY);
|
|
2585
|
+
const selectableElement = elements.find((el, index, arr) => isElementSelectable(el, index, arr));
|
|
2586
|
+
const isSelectable = selectableElement && selectableElement !== hoveredElement;
|
|
2587
|
+
if (isSelectable) {
|
|
2588
|
+
setHoveredElement(selectableElement);
|
|
2589
|
+
setIsHovering(true);
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
setHoveredElement(null);
|
|
2593
|
+
setIsHovering(false);
|
|
2594
|
+
}, [isActive, iframeDocument, hoveredElement]);
|
|
2595
|
+
const handleMouseLeave = useCallback(() => {
|
|
2596
|
+
setHoveredElement(null);
|
|
2597
|
+
setIsHovering(false);
|
|
2598
|
+
}, []);
|
|
2599
|
+
useCallback((e) => {
|
|
2600
|
+
if (!isActive || !hoveredElement)
|
|
2601
|
+
return;
|
|
2602
|
+
e.stopPropagation();
|
|
2603
|
+
e.preventDefault();
|
|
2604
|
+
if (!onAreaCreatedElement)
|
|
2605
|
+
return;
|
|
2606
|
+
onAreaCreatedElement(hoveredElement);
|
|
2607
|
+
}, [isActive, hoveredElement]);
|
|
2608
|
+
useEffect(() => {
|
|
2609
|
+
if (!isActive || !iframeDocument) {
|
|
2610
|
+
setHoveredElement(null);
|
|
2611
|
+
setIsHovering(false);
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
// Throttle mouse move
|
|
2615
|
+
let rafId = null;
|
|
2616
|
+
const throttledMouseMove = (e) => {
|
|
2617
|
+
if (rafId)
|
|
2618
|
+
return;
|
|
2619
|
+
rafId = requestAnimationFrame(() => {
|
|
2620
|
+
handleMouseMove(e);
|
|
2621
|
+
rafId = null;
|
|
2622
|
+
});
|
|
2623
|
+
};
|
|
2624
|
+
iframeDocument.addEventListener('mousemove', throttledMouseMove);
|
|
2625
|
+
iframeDocument.addEventListener('scroll', handleMouseLeave);
|
|
2626
|
+
iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
|
|
2627
|
+
// iframeDocument.addEventListener('click', handleClick);
|
|
2628
|
+
return () => {
|
|
2629
|
+
if (rafId) {
|
|
2630
|
+
cancelAnimationFrame(rafId);
|
|
2631
|
+
}
|
|
2632
|
+
iframeDocument.removeEventListener('mousemove', throttledMouseMove);
|
|
2633
|
+
iframeDocument.removeEventListener('mouseleave', handleMouseLeave);
|
|
2634
|
+
iframeDocument.removeEventListener('scroll', handleMouseLeave);
|
|
2635
|
+
// iframeDocument.removeEventListener('click', handleClick);
|
|
2636
|
+
};
|
|
2637
|
+
}, [isActive, iframeDocument]);
|
|
2638
|
+
return {
|
|
2639
|
+
hoveredElement,
|
|
2640
|
+
isHovering,
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
const useAreaFilterVisible = (props) => {
|
|
2645
|
+
const { iframeRef, enableOverlapResolution } = props;
|
|
2646
|
+
const iframeDocument = iframeRef.current?.contentDocument;
|
|
2647
|
+
const { areas, setAreas } = useHeatmapAreaClick();
|
|
2648
|
+
const visibleAreas = useMemo(() => {
|
|
2649
|
+
if (!enableOverlapResolution)
|
|
2650
|
+
return areas;
|
|
2651
|
+
if (!iframeDocument)
|
|
2652
|
+
return areas;
|
|
2653
|
+
return getVisibleAreas(areas, iframeDocument);
|
|
2654
|
+
}, [areas, iframeDocument]);
|
|
2655
|
+
useEffect(() => {
|
|
2656
|
+
if (enableOverlapResolution && visibleAreas.length !== areas.length) {
|
|
2657
|
+
setAreas(visibleAreas);
|
|
2658
|
+
}
|
|
2659
|
+
}, [visibleAreas, areas.length]);
|
|
2660
|
+
return {};
|
|
2661
|
+
};
|
|
2662
|
+
|
|
2663
|
+
/**
|
|
2664
|
+
* Hook to handle area interaction (click, hover)
|
|
2665
|
+
*
|
|
2666
|
+
* @param options - Configuration options
|
|
2667
|
+
* @returns Event handlers for area interactions
|
|
2668
|
+
*/
|
|
2669
|
+
function useAreaInteraction(options = {}) {
|
|
2670
|
+
const { onAreaClick } = options;
|
|
2671
|
+
const { selectedArea, hoveredArea, isEditingMode, setSelectedArea, setHoveredArea } = useHeatmapAreaClick();
|
|
2672
|
+
const handleAreaClick = useCallback((area) => {
|
|
2673
|
+
if (isEditingMode)
|
|
2674
|
+
return;
|
|
2675
|
+
// Toggle selection
|
|
2676
|
+
setSelectedArea(selectedArea?.id === area.id ? null : area);
|
|
2677
|
+
// Trigger callback
|
|
2678
|
+
if (onAreaClick) {
|
|
2679
|
+
onAreaClick(area);
|
|
2680
|
+
}
|
|
2681
|
+
}, [isEditingMode, selectedArea]);
|
|
2682
|
+
const handleAreaMouseEnter = useCallback((area) => {
|
|
2683
|
+
if (isEditingMode)
|
|
2684
|
+
return;
|
|
2685
|
+
setHoveredArea(area);
|
|
2686
|
+
}, [isEditingMode]);
|
|
2687
|
+
const handleAreaMouseLeave = useCallback((area) => {
|
|
2688
|
+
if (isEditingMode)
|
|
2689
|
+
return;
|
|
2690
|
+
// Only clear if this is the currently hovered area
|
|
2691
|
+
if (hoveredArea?.id === area.id) {
|
|
2692
|
+
setHoveredArea(null);
|
|
2693
|
+
}
|
|
2694
|
+
}, [isEditingMode, hoveredArea]);
|
|
2695
|
+
return {
|
|
2696
|
+
handleAreaClick,
|
|
2697
|
+
handleAreaMouseEnter,
|
|
2698
|
+
handleAreaMouseLeave,
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
function useAreaPortals(options) {
|
|
2703
|
+
const { shadowContainer, isReady, iframeRef, customShadowRoot, onAreaClick, onAreaCreated } = options;
|
|
2704
|
+
const { onAreaCreatedElement } = useAreaCreation({ customShadowRoot, onAreaCreated });
|
|
2705
|
+
const { hoveredElement } = useAreaEditMode({ iframeRef, enabled: true, onAreaCreatedElement });
|
|
2706
|
+
const { areas, selectedArea, hoveredArea, isEditingMode } = useHeatmapAreaClick();
|
|
2707
|
+
const { handleAreaClick, handleAreaMouseEnter, handleAreaMouseLeave } = useAreaInteraction({
|
|
2708
|
+
onAreaClick,
|
|
2709
|
+
});
|
|
2710
|
+
const isReadyPortal = shadowContainer && isReady;
|
|
2711
|
+
const areasPortal = isReadyPortal
|
|
2712
|
+
? createPortal(jsx(Fragment, { children: areas.map((area) => (jsx(AreaOverlay, { area: area, onClick: handleAreaClick, onMouseEnter: handleAreaMouseEnter, onMouseLeave: handleAreaMouseLeave, isSelected: selectedArea?.id === area.id, isHovered: hoveredArea?.id === area.id }, area.id))) }), shadowContainer)
|
|
2713
|
+
: null;
|
|
2714
|
+
const isReadyEditHighlight = shadowContainer && isReady && isEditingMode && hoveredElement;
|
|
2715
|
+
const editHighlightPortal = isReadyEditHighlight
|
|
2716
|
+
? createPortal(jsx(AreaEditHighlight, { element: hoveredElement, shadowRoot: customShadowRoot, onClick: onAreaCreatedElement }), shadowContainer)
|
|
2717
|
+
: null;
|
|
2718
|
+
return {
|
|
2719
|
+
areasPortal: areasPortal,
|
|
2720
|
+
editHighlightPortal: editHighlightPortal,
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
function useAreaRectSync(options) {
|
|
2725
|
+
const { iframeDocument, shadowRoot, enabled = true } = options;
|
|
2726
|
+
const { vizRef } = useHeatmapViz();
|
|
2727
|
+
const { areas } = useHeatmapAreaClick();
|
|
2728
|
+
useEffect(() => {
|
|
2729
|
+
if (!enabled || !iframeDocument || areas.length === 0) {
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
areas.forEach((area) => {
|
|
2733
|
+
try {
|
|
2734
|
+
let targetElement = area.element;
|
|
2735
|
+
if (!targetElement || !iframeDocument.contains(targetElement)) {
|
|
2736
|
+
if (area.hash) {
|
|
2737
|
+
const elementByHash = vizRef?.get(area.hash);
|
|
2738
|
+
if (elementByHash) {
|
|
2739
|
+
area.element = elementByHash;
|
|
2740
|
+
targetElement = elementByHash;
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
// // If we still can't find the element, set rect to zero
|
|
2745
|
+
// if (!targetElement || !iframeDocument.contains(targetElement)) {
|
|
2746
|
+
// area.rect.update({
|
|
2747
|
+
// width: 0,
|
|
2748
|
+
// height: 0,
|
|
2749
|
+
// top: 0,
|
|
2750
|
+
// left: 0,
|
|
2751
|
+
// absoluteLeft: 0,
|
|
2752
|
+
// absoluteTop: 0,
|
|
2753
|
+
// absoluteRight: 0,
|
|
2754
|
+
// absoluteBottom: 0,
|
|
2755
|
+
// outOfBounds: true,
|
|
2756
|
+
// });
|
|
2757
|
+
// return;
|
|
2758
|
+
// }
|
|
2759
|
+
const newRect = getElementRect(targetElement, shadowRoot);
|
|
2760
|
+
area.rect.update(newRect);
|
|
2761
|
+
}
|
|
2762
|
+
catch (error) {
|
|
2763
|
+
logger.error(`Failed to update rect for area ${area.id}:`, error);
|
|
2764
|
+
}
|
|
2765
|
+
});
|
|
2766
|
+
}, [areas, iframeDocument, shadowRoot, enabled, vizRef]);
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
/**
|
|
2770
|
+
* Hook to setup and manage the shadow DOM container for area rendering
|
|
2771
|
+
*
|
|
2772
|
+
* @param iframeDocument - The iframe document
|
|
2773
|
+
* @param customShadowRoot - Optional custom shadow root element
|
|
2774
|
+
* @returns Container element and ready state
|
|
2775
|
+
*/
|
|
2776
|
+
function useAreaRendererContainer(iframeDocument, customShadowRoot) {
|
|
2777
|
+
const [shadowContainer, setShadowContainer] = useState(null);
|
|
2778
|
+
const [isReady, setIsReady] = useState(false);
|
|
2779
|
+
const containerRef = useRef(null);
|
|
2780
|
+
useEffect(() => {
|
|
2781
|
+
if (!iframeDocument) {
|
|
2782
|
+
setIsReady(false);
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2785
|
+
const innerContainer = setupAreaRenderingContainer(iframeDocument, customShadowRoot);
|
|
2786
|
+
containerRef.current = innerContainer;
|
|
2787
|
+
setShadowContainer(innerContainer);
|
|
2788
|
+
setIsReady(true);
|
|
2789
|
+
return () => {
|
|
2790
|
+
// Cleanup on unmount
|
|
2791
|
+
const container = innerContainer.parentElement?.parentElement;
|
|
2792
|
+
cleanupAreaRenderingContainer(container);
|
|
2793
|
+
containerRef.current = null;
|
|
2794
|
+
setShadowContainer(null);
|
|
2795
|
+
setIsReady(false);
|
|
2796
|
+
};
|
|
2797
|
+
}, [iframeDocument, customShadowRoot]);
|
|
2798
|
+
return {
|
|
2799
|
+
shadowContainer,
|
|
2800
|
+
isReady,
|
|
2801
|
+
containerRef,
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
|
|
1767
2805
|
const scrollToElementIfNeeded = (visualRef, rect, scale) => {
|
|
1768
2806
|
if (!visualRef.current)
|
|
1769
2807
|
return;
|
|
@@ -1782,7 +2820,7 @@ const scrollToElementIfNeeded = (visualRef, rect, scale) => {
|
|
|
1782
2820
|
});
|
|
1783
2821
|
};
|
|
1784
2822
|
const useClickedElement = ({ visualRef, getRect }) => {
|
|
1785
|
-
const { selectedElement, shouldShowCallout, setShouldShowCallout } =
|
|
2823
|
+
const { selectedElement, shouldShowCallout, setShouldShowCallout } = useHeatmapClick();
|
|
1786
2824
|
const { widthScale } = useHeatmapViz();
|
|
1787
2825
|
const { dataInfo } = useHeatmapData();
|
|
1788
2826
|
const [clickedElement, setClickedElement] = useState(null);
|
|
@@ -1820,12 +2858,12 @@ const useClickedElement = ({ visualRef, getRect }) => {
|
|
|
1820
2858
|
requestAnimationFrame(() => {
|
|
1821
2859
|
setClickedElement(elementInfo);
|
|
1822
2860
|
});
|
|
1823
|
-
}, [selectedElement, dataInfo, visualRef, widthScale]);
|
|
2861
|
+
}, [selectedElement, dataInfo, visualRef, widthScale]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
1824
2862
|
return { clickedElement, showMissingElement, shouldShowCallout };
|
|
1825
2863
|
};
|
|
1826
2864
|
|
|
1827
2865
|
const useElementCalloutVisible = ({ visualRef, getRect }) => {
|
|
1828
|
-
const { selectedElement, setShouldShowCallout } =
|
|
2866
|
+
const { selectedElement, setShouldShowCallout } = useHeatmapClick();
|
|
1829
2867
|
const { widthScale } = useHeatmapViz();
|
|
1830
2868
|
const { dataInfo } = useHeatmapData();
|
|
1831
2869
|
useEffect(() => {
|
|
@@ -1856,7 +2894,7 @@ const useElementCalloutVisible = ({ visualRef, getRect }) => {
|
|
|
1856
2894
|
};
|
|
1857
2895
|
|
|
1858
2896
|
const useHeatmapEffects = ({ isVisible }) => {
|
|
1859
|
-
|
|
2897
|
+
useHeatmapClick();
|
|
1860
2898
|
const resetAll = () => {
|
|
1861
2899
|
// setShouldShowCallout(false);
|
|
1862
2900
|
};
|
|
@@ -1876,7 +2914,7 @@ const useHeatmapEffects = ({ isVisible }) => {
|
|
|
1876
2914
|
const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
|
|
1877
2915
|
const heatmapWidth = useHeatmapConfigStore((state) => state.width);
|
|
1878
2916
|
const { iframeHeight, widthScale } = useHeatmapViz();
|
|
1879
|
-
|
|
2917
|
+
const getRect = useCallback((element) => {
|
|
1880
2918
|
const hash = element?.hash;
|
|
1881
2919
|
if (!iframeRef.current?.contentDocument || !hash || !visualizer)
|
|
1882
2920
|
return null;
|
|
@@ -1912,6 +2950,7 @@ const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
|
|
|
1912
2950
|
outOfBounds,
|
|
1913
2951
|
};
|
|
1914
2952
|
}, [iframeRef, wrapperRef, visualizer, heatmapWidth, iframeHeight, widthScale]);
|
|
2953
|
+
return { getRect };
|
|
1915
2954
|
};
|
|
1916
2955
|
|
|
1917
2956
|
const debounce = (fn, delay) => {
|
|
@@ -1921,11 +2960,7 @@ const debounce = (fn, delay) => {
|
|
|
1921
2960
|
timeout = setTimeout(() => fn(...args), delay);
|
|
1922
2961
|
};
|
|
1923
2962
|
};
|
|
1924
|
-
|
|
1925
|
-
// ===================== UTILITY FUNCTIONS =====================
|
|
1926
|
-
/**
|
|
1927
|
-
* Lấy bounding box tuyệt đối của element (relative to document)
|
|
1928
|
-
*/
|
|
2963
|
+
|
|
1929
2964
|
function getBoundingBox(element) {
|
|
1930
2965
|
if (typeof element.getBoundingClientRect !== 'function') {
|
|
1931
2966
|
return null;
|
|
@@ -1946,87 +2981,75 @@ function getBoundingBox(element) {
|
|
|
1946
2981
|
height: Math.floor(rect.height),
|
|
1947
2982
|
};
|
|
1948
2983
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
//
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
Clicks: {hoveredElement.clicks}
|
|
2016
|
-
<br />
|
|
2017
|
-
Rank: #{hoveredElement.rank}
|
|
2018
|
-
<br />
|
|
2019
|
-
Selector: {hoveredElement.selector}
|
|
2020
|
-
</div>
|
|
2021
|
-
)}
|
|
2022
|
-
</div>
|
|
2023
|
-
</div>
|
|
2024
|
-
);
|
|
2025
|
-
}
|
|
2026
|
-
*/
|
|
2984
|
+
// export function useHeatmapMouseHandler(props: UseHeatmapMouseHandlerProps) {
|
|
2985
|
+
// const { heatmapWrapperRef, iframeRef, parentRef, heatmapInfo, scaleRatio, onElementHover } = props;
|
|
2986
|
+
// const handleMouseMove = useCallback(
|
|
2987
|
+
// (event: MouseEvent) => {
|
|
2988
|
+
// if (
|
|
2989
|
+
// !heatmapWrapperRef?.current ||
|
|
2990
|
+
// !iframeRef?.current ||
|
|
2991
|
+
// !iframeRef.current.contentDocument ||
|
|
2992
|
+
// !heatmapInfo?.elementMapInfo ||
|
|
2993
|
+
// !parentRef?.current
|
|
2994
|
+
// ) {
|
|
2995
|
+
// return;
|
|
2996
|
+
// }
|
|
2997
|
+
// try {
|
|
2998
|
+
// // Calculate scroll position (scaled)
|
|
2999
|
+
// const scrollTop = parentRef.current.scrollTop / scaleRatio;
|
|
3000
|
+
// // Get position of heatmap wrapper
|
|
3001
|
+
// const wrapperRect = heatmapWrapperRef.current.getBoundingClientRect();
|
|
3002
|
+
// // Calculate mouse position in iframe (scaled)
|
|
3003
|
+
// const mouseX = (event.clientX - wrapperRect.left) / scaleRatio;
|
|
3004
|
+
// const mouseY = (event.clientY - wrapperRect.top) / scaleRatio - scrollTop;
|
|
3005
|
+
// const elementsAtPoint = getElementsAtPoint(
|
|
3006
|
+
// iframeRef.current.contentDocument,
|
|
3007
|
+
// Math.round(mouseX),
|
|
3008
|
+
// Math.round(mouseY),
|
|
3009
|
+
// {
|
|
3010
|
+
// filterFn: (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE),
|
|
3011
|
+
// ignoreCanvas: true,
|
|
3012
|
+
// },
|
|
3013
|
+
// );
|
|
3014
|
+
// if (!elementsAtPoint || elementsAtPoint.length === 0) {
|
|
3015
|
+
// return;
|
|
3016
|
+
// }
|
|
3017
|
+
// for (let i = 0; i < elementsAtPoint.length; i++) {
|
|
3018
|
+
// const element = elementsAtPoint[i] as HTMLElement;
|
|
3019
|
+
// const elementHash = element.getAttribute(HEATMAP_ELEMENT_ATTRIBUTE);
|
|
3020
|
+
// if (elementHash && heatmapInfo.elementMapInfo[elementHash]) {
|
|
3021
|
+
// const elementData = heatmapInfo.elementMapInfo[elementHash];
|
|
3022
|
+
// const boundingBox = getBoundingBox(element);
|
|
3023
|
+
// if (boundingBox) {
|
|
3024
|
+
// const rank =
|
|
3025
|
+
// Array.isArray(heatmapInfo.sortedElements) && elementData
|
|
3026
|
+
// ? heatmapInfo.sortedElements.indexOf(elementData) + 1
|
|
3027
|
+
// : NaN;
|
|
3028
|
+
// onElementHover({
|
|
3029
|
+
// ...boundingBox,
|
|
3030
|
+
// width: Math.min(boundingBox.width, heatmapInfo.width || 0),
|
|
3031
|
+
// top: boundingBox.top + scrollTop,
|
|
3032
|
+
// // Metadata
|
|
3033
|
+
// hash: elementHash,
|
|
3034
|
+
// clicks: elementData.totalclicks,
|
|
3035
|
+
// rank: rank,
|
|
3036
|
+
// selector: elementData.selector || '',
|
|
3037
|
+
// });
|
|
3038
|
+
// break;
|
|
3039
|
+
// }
|
|
3040
|
+
// }
|
|
3041
|
+
// }
|
|
3042
|
+
// } catch (error) {
|
|
3043
|
+
// console.warn('Error handling mouse move on heatmap:', error);
|
|
3044
|
+
// }
|
|
3045
|
+
// },
|
|
3046
|
+
// [heatmapWrapperRef, iframeRef, parentRef, heatmapInfo, scaleRatio, onElementHover],
|
|
3047
|
+
// );
|
|
3048
|
+
// return { handleMouseMove };
|
|
3049
|
+
// }
|
|
2027
3050
|
|
|
2028
3051
|
const useHoveredElement = ({ iframeRef, getRect }) => {
|
|
2029
|
-
const { hoveredElement, setHoveredElement, setSelectedElement } =
|
|
3052
|
+
const { hoveredElement, setHoveredElement, setSelectedElement } = useHeatmapClick();
|
|
2030
3053
|
const { widthScale } = useHeatmapViz();
|
|
2031
3054
|
const { dataInfo } = useHeatmapData();
|
|
2032
3055
|
const reset = useCallback(() => {
|
|
@@ -2106,12 +3129,14 @@ const convertViewportToIframeCoords = (clientX, clientY, iframeRect, scale) => {
|
|
|
2106
3129
|
return { x, y };
|
|
2107
3130
|
};
|
|
2108
3131
|
const findTargetElement = (doc, x, y, heatmapInfo) => {
|
|
2109
|
-
const
|
|
2110
|
-
|
|
3132
|
+
const elementsAtPoint = getElementsAtPoint(doc, Math.round(x), Math.round(y), {
|
|
3133
|
+
filterFn: (element) => element.hasAttribute(HEATMAP_ELEMENT_ATTRIBUTE),
|
|
3134
|
+
ignoreCanvas: true,
|
|
3135
|
+
});
|
|
2111
3136
|
let dataElement = null;
|
|
2112
3137
|
for (let i = 0; i < elementsAtPoint.length; i++) {
|
|
2113
3138
|
const element = elementsAtPoint[i];
|
|
2114
|
-
const elementHash = element
|
|
3139
|
+
const elementHash = getElementHash(element);
|
|
2115
3140
|
if (elementHash && heatmapInfo.elementMapInfo?.[elementHash]) {
|
|
2116
3141
|
const boundingBox = getBoundingBox(element);
|
|
2117
3142
|
if (boundingBox) {
|
|
@@ -2141,6 +3166,167 @@ const isValidElement = (element, heatmapInfo) => {
|
|
|
2141
3166
|
return !!heatmapInfo?.elementMapInfo?.[hash];
|
|
2142
3167
|
};
|
|
2143
3168
|
|
|
3169
|
+
function useAreaScrollSync(options) {
|
|
3170
|
+
const { iframeRef, visualRef, enabled = true } = options;
|
|
3171
|
+
const { widthScale } = useHeatmapViz();
|
|
3172
|
+
const { areas, selectedArea } = useHeatmapAreaClick();
|
|
3173
|
+
const iframeDocument = iframeRef.current?.contentDocument;
|
|
3174
|
+
useEffect(() => {
|
|
3175
|
+
if (!enabled || !iframeDocument || areas.length === 0) {
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
let rafId = null;
|
|
3179
|
+
let isUpdating = false;
|
|
3180
|
+
const updateAreaPositions = () => {
|
|
3181
|
+
if (isUpdating)
|
|
3182
|
+
return;
|
|
3183
|
+
isUpdating = true;
|
|
3184
|
+
rafId = requestAnimationFrame(() => {
|
|
3185
|
+
areas.forEach((area) => {
|
|
3186
|
+
if (!area.element || !area.rect)
|
|
3187
|
+
return;
|
|
3188
|
+
try {
|
|
3189
|
+
const newRect = getElementRect(area.element);
|
|
3190
|
+
area.rect.update(newRect);
|
|
3191
|
+
}
|
|
3192
|
+
catch (error) {
|
|
3193
|
+
console.warn('[useAreaScrollSync] Failed to update area rect:', error);
|
|
3194
|
+
}
|
|
3195
|
+
});
|
|
3196
|
+
isUpdating = false;
|
|
3197
|
+
rafId = null;
|
|
3198
|
+
});
|
|
3199
|
+
};
|
|
3200
|
+
iframeDocument.addEventListener('scroll', updateAreaPositions, { passive: true });
|
|
3201
|
+
const iframeWindow = iframeDocument.defaultView;
|
|
3202
|
+
if (iframeWindow) {
|
|
3203
|
+
iframeWindow.addEventListener('resize', updateAreaPositions, { passive: true });
|
|
3204
|
+
}
|
|
3205
|
+
return () => {
|
|
3206
|
+
if (rafId !== null) {
|
|
3207
|
+
cancelAnimationFrame(rafId);
|
|
3208
|
+
}
|
|
3209
|
+
iframeDocument.removeEventListener('scroll', updateAreaPositions);
|
|
3210
|
+
if (iframeWindow) {
|
|
3211
|
+
iframeWindow.removeEventListener('resize', updateAreaPositions);
|
|
3212
|
+
}
|
|
3213
|
+
};
|
|
3214
|
+
}, [areas, iframeDocument, enabled]);
|
|
3215
|
+
useEffect(() => {
|
|
3216
|
+
if (!selectedArea)
|
|
3217
|
+
return;
|
|
3218
|
+
if (!visualRef)
|
|
3219
|
+
return;
|
|
3220
|
+
if (!selectedArea.rect.value)
|
|
3221
|
+
return;
|
|
3222
|
+
scrollToElementIfNeeded(visualRef, selectedArea.rect.value, widthScale);
|
|
3223
|
+
}, [visualRef, selectedArea, widthScale]);
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
const useAreaTopAutoDetect = (props) => {
|
|
3227
|
+
const { iframeRef, autoCreateTopN, shadowRoot } = props;
|
|
3228
|
+
const iframeDocument = iframeRef.current?.contentDocument;
|
|
3229
|
+
const { dataInfo } = useHeatmapData();
|
|
3230
|
+
const { vizRef } = useHeatmapViz();
|
|
3231
|
+
const { areas, addArea } = useHeatmapAreaClick();
|
|
3232
|
+
useEffect(() => {
|
|
3233
|
+
if (!dataInfo?.elementMapInfo || !dataInfo?.totalClicks)
|
|
3234
|
+
return;
|
|
3235
|
+
if (autoCreateTopN <= 0)
|
|
3236
|
+
return;
|
|
3237
|
+
if (areas.length > 0)
|
|
3238
|
+
return;
|
|
3239
|
+
const topElements = getTopElementsByClicks(dataInfo.elementMapInfo, autoCreateTopN);
|
|
3240
|
+
const newAreas = [];
|
|
3241
|
+
topElements.forEach(({ hash }) => {
|
|
3242
|
+
const element = vizRef?.get(hash);
|
|
3243
|
+
if (!element)
|
|
3244
|
+
return;
|
|
3245
|
+
const area = buildAreaNode(element, hash, dataInfo);
|
|
3246
|
+
if (!area)
|
|
3247
|
+
return;
|
|
3248
|
+
newAreas.push(area);
|
|
3249
|
+
});
|
|
3250
|
+
newAreas.forEach((area) => addArea(area));
|
|
3251
|
+
}, [dataInfo, autoCreateTopN, areas.length, iframeDocument, shadowRoot]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
3252
|
+
return {};
|
|
3253
|
+
};
|
|
3254
|
+
|
|
3255
|
+
const useAreaClickmap = () => {
|
|
3256
|
+
const { vizRef } = useHeatmapViz();
|
|
3257
|
+
const { clickmap } = useHeatmapData();
|
|
3258
|
+
const start = useCallback(() => {
|
|
3259
|
+
if (!vizRef || !clickmap || clickmap.length === 0)
|
|
3260
|
+
return;
|
|
3261
|
+
try {
|
|
3262
|
+
vizRef?.clearmap?.();
|
|
3263
|
+
}
|
|
3264
|
+
catch (error) {
|
|
3265
|
+
console.error(`🚀 🐥 ~ useAreaClickmap ~ error:`, error);
|
|
3266
|
+
}
|
|
3267
|
+
}, [vizRef, clickmap]);
|
|
3268
|
+
return { start };
|
|
3269
|
+
};
|
|
3270
|
+
|
|
3271
|
+
const useClickmap = () => {
|
|
3272
|
+
const { vizRef } = useHeatmapViz();
|
|
3273
|
+
const { clickmap } = useHeatmapData();
|
|
3274
|
+
const start = useCallback(() => {
|
|
3275
|
+
if (!vizRef || !clickmap || clickmap.length === 0)
|
|
3276
|
+
return;
|
|
3277
|
+
try {
|
|
3278
|
+
vizRef?.clearmap?.();
|
|
3279
|
+
vizRef?.clickmap?.(clickmap);
|
|
3280
|
+
}
|
|
3281
|
+
catch (error) {
|
|
3282
|
+
console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
|
|
3283
|
+
}
|
|
3284
|
+
}, [vizRef, clickmap]);
|
|
3285
|
+
return { start };
|
|
3286
|
+
};
|
|
3287
|
+
|
|
3288
|
+
const useScrollmap = () => {
|
|
3289
|
+
const { vizRef } = useHeatmapViz();
|
|
3290
|
+
const { scrollmap } = useHeatmapData();
|
|
3291
|
+
const start = useCallback(() => {
|
|
3292
|
+
// if (isInitialized) return;
|
|
3293
|
+
if (!vizRef || !scrollmap || scrollmap.length === 0)
|
|
3294
|
+
return;
|
|
3295
|
+
try {
|
|
3296
|
+
vizRef?.clearmap?.();
|
|
3297
|
+
vizRef?.scrollmap?.(scrollmap);
|
|
3298
|
+
// setIsInitialized(true);
|
|
3299
|
+
}
|
|
3300
|
+
catch (error) {
|
|
3301
|
+
console.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
|
|
3302
|
+
}
|
|
3303
|
+
}, [vizRef, scrollmap]);
|
|
3304
|
+
return { start };
|
|
3305
|
+
};
|
|
3306
|
+
|
|
3307
|
+
const useHeatmapCanvas = () => {
|
|
3308
|
+
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
3309
|
+
const clickMode = useHeatmapConfigStore((state) => state.clickMode);
|
|
3310
|
+
const { start: startClickmap } = useClickmap();
|
|
3311
|
+
const { start: startAreaClickmap } = useAreaClickmap();
|
|
3312
|
+
const { start: startScrollmap } = useScrollmap();
|
|
3313
|
+
useEffect(() => {
|
|
3314
|
+
switch (heatmapType) {
|
|
3315
|
+
case IHeatmapType.Click:
|
|
3316
|
+
if (clickMode === IClickMode.Default) {
|
|
3317
|
+
startClickmap();
|
|
3318
|
+
}
|
|
3319
|
+
else {
|
|
3320
|
+
startAreaClickmap();
|
|
3321
|
+
}
|
|
3322
|
+
break;
|
|
3323
|
+
case IHeatmapType.Scroll:
|
|
3324
|
+
startScrollmap();
|
|
3325
|
+
break;
|
|
3326
|
+
}
|
|
3327
|
+
}, [heatmapType, clickMode, startClickmap, startAreaClickmap, startScrollmap]);
|
|
3328
|
+
};
|
|
3329
|
+
|
|
2144
3330
|
var MessageType;
|
|
2145
3331
|
(function (MessageType) {
|
|
2146
3332
|
MessageType["GX_DOM_TRACKING_PAYLOAD"] = "GX_DOM_TRACKING_PAYLOAD";
|
|
@@ -2250,8 +3436,7 @@ function reset(iframe, rect, onSuccess) {
|
|
|
2250
3436
|
|
|
2251
3437
|
const useHeatmapRender = () => {
|
|
2252
3438
|
const { data } = useHeatmapData();
|
|
2253
|
-
const { vizRef, setVizRef, setIsRenderViz, setIframeHeight
|
|
2254
|
-
console.log(`🚀 🐥 ~ useHeatmapRender ~ wrapperHeight:`, wrapperHeight);
|
|
3439
|
+
const { vizRef, setVizRef, setIsRenderViz, setIframeHeight } = useHeatmapViz();
|
|
2255
3440
|
const iframeRef = useRef(null);
|
|
2256
3441
|
const renderHeatmap = useCallback(async (payloads) => {
|
|
2257
3442
|
if (!payloads || payloads.length === 0)
|
|
@@ -2430,7 +3615,7 @@ const useReplayRender = () => {
|
|
|
2430
3615
|
};
|
|
2431
3616
|
};
|
|
2432
3617
|
|
|
2433
|
-
const
|
|
3618
|
+
const useHeatmapRenderByMode = (mode) => {
|
|
2434
3619
|
const heatmapResult = useMemo(() => {
|
|
2435
3620
|
switch (mode) {
|
|
2436
3621
|
case 'heatmap':
|
|
@@ -3452,19 +4637,6 @@ const BoxStack = forwardRef(({ children, ...props }, ref) => {
|
|
|
3452
4637
|
});
|
|
3453
4638
|
BoxStack.displayName = 'BoxStack';
|
|
3454
4639
|
|
|
3455
|
-
const ContentTopBar = () => {
|
|
3456
|
-
const controls = useHeatmapControlStore((state) => state.controls);
|
|
3457
|
-
useHeatmapConfigStore((state) => state.mode);
|
|
3458
|
-
const TopBar = controls.TopBar;
|
|
3459
|
-
// In compare mode, hide individual top bars since we have a global header
|
|
3460
|
-
// if (mode === 'compare') {
|
|
3461
|
-
// return null;
|
|
3462
|
-
// }
|
|
3463
|
-
return (jsx(BoxStack, { id: "gx-hm-content-header", flexDirection: "row", alignItems: "center", overflow: "auto", zIndex: 1, backgroundColor: "white", style: {
|
|
3464
|
-
borderBottom: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
|
|
3465
|
-
}, children: TopBar && jsx(TopBar, {}) }));
|
|
3466
|
-
};
|
|
3467
|
-
|
|
3468
4640
|
const ContentMetricBar = () => {
|
|
3469
4641
|
const controls = useHeatmapControlStore((state) => state.controls);
|
|
3470
4642
|
const borderBottom = `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`;
|
|
@@ -3472,22 +4644,11 @@ const ContentMetricBar = () => {
|
|
|
3472
4644
|
borderBottom,
|
|
3473
4645
|
}, children: controls.MetricBar ?? null }));
|
|
3474
4646
|
};
|
|
3475
|
-
|
|
3476
|
-
const ContentToolbar = () => {
|
|
3477
|
-
const controls = useHeatmapControlStore((state) => state.controls);
|
|
3478
|
-
return (jsx("div", { id: "gx-hm-content-toolbar", style: {
|
|
3479
|
-
position: 'absolute',
|
|
3480
|
-
bottom: 0,
|
|
3481
|
-
left: '8px',
|
|
3482
|
-
right: '24px',
|
|
3483
|
-
padding: '8px',
|
|
3484
|
-
paddingBlock: '16px',
|
|
3485
|
-
}, children: controls.Toolbar ?? null }));
|
|
3486
|
-
};
|
|
4647
|
+
ContentMetricBar.displayName = 'ContentMetricBar';
|
|
3487
4648
|
|
|
3488
4649
|
const ContentSidebar = () => {
|
|
3489
4650
|
const controls = useHeatmapControlStore((state) => state.controls);
|
|
3490
|
-
const { state } =
|
|
4651
|
+
const { state } = useHeatmapClick();
|
|
3491
4652
|
const isHideSidebar = state.hideSidebar;
|
|
3492
4653
|
const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
|
|
3493
4654
|
const mode = useHeatmapConfigStore((state) => state.mode);
|
|
@@ -3521,7 +4682,7 @@ const PopoverSidebar = () => {
|
|
|
3521
4682
|
const CompSidebarActivator = useHeatmapControlStore((state) => state.controls.SidebarActivator);
|
|
3522
4683
|
const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
|
|
3523
4684
|
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
|
3524
|
-
const { state } =
|
|
4685
|
+
const { state } = useHeatmapClick();
|
|
3525
4686
|
const isCompareMode = mode === 'compare';
|
|
3526
4687
|
const isHideSidebar = state.hideSidebar;
|
|
3527
4688
|
const stylePopover = {
|
|
@@ -3546,9 +4707,21 @@ const PopoverSidebar = () => {
|
|
|
3546
4707
|
}, children: jsx(CompSidebar, { closeAction: { onClick: () => setIsPopoverOpen(false) } }) }) }))] }));
|
|
3547
4708
|
};
|
|
3548
4709
|
|
|
4710
|
+
const ContentToolbar = () => {
|
|
4711
|
+
const controls = useHeatmapControlStore((state) => state.controls);
|
|
4712
|
+
return (jsx("div", { id: "gx-hm-content-toolbar", style: {
|
|
4713
|
+
position: 'absolute',
|
|
4714
|
+
bottom: 0,
|
|
4715
|
+
left: '8px',
|
|
4716
|
+
right: '24px',
|
|
4717
|
+
padding: '8px',
|
|
4718
|
+
paddingBlock: '16px',
|
|
4719
|
+
}, children: controls.Toolbar ?? null }));
|
|
4720
|
+
};
|
|
4721
|
+
|
|
3549
4722
|
const VizContainer = ({ children, isActive = false }) => {
|
|
3550
4723
|
const wrapperRef = useRef(null);
|
|
3551
|
-
const viewId =
|
|
4724
|
+
const viewId = useViewIdContext();
|
|
3552
4725
|
useWrapperRefHeight({
|
|
3553
4726
|
isActive,
|
|
3554
4727
|
wrapperRef,
|
|
@@ -3558,80 +4731,46 @@ const VizContainer = ({ children, isActive = false }) => {
|
|
|
3558
4731
|
}, children: children }), jsx(PopoverSidebar, {})] }));
|
|
3559
4732
|
};
|
|
3560
4733
|
|
|
3561
|
-
|
|
3562
|
-
const {
|
|
3563
|
-
const
|
|
3564
|
-
const
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
const { scrollmap } = useHeatmapData();
|
|
3581
|
-
const start = useCallback(() => {
|
|
3582
|
-
// if (isInitialized) return;
|
|
3583
|
-
if (!vizRef || !scrollmap || scrollmap.length === 0)
|
|
3584
|
-
return;
|
|
3585
|
-
try {
|
|
3586
|
-
vizRef?.clearmap?.();
|
|
3587
|
-
vizRef?.scrollmap?.(scrollmap);
|
|
3588
|
-
// setIsInitialized(true);
|
|
3589
|
-
}
|
|
3590
|
-
catch (error) {
|
|
3591
|
-
console.error(`🚀 🐥 ~ useScrollmap ~ error:`, error);
|
|
3592
|
-
}
|
|
3593
|
-
}, [vizRef, scrollmap]);
|
|
3594
|
-
return { start };
|
|
3595
|
-
};
|
|
4734
|
+
function useAreaRenderer(options) {
|
|
4735
|
+
const { iframeRef, visualRef, shadowRoot, onAreaCreated, onAreaClick } = options;
|
|
4736
|
+
const iframeDocument = iframeRef.current?.contentDocument || undefined;
|
|
4737
|
+
const { shadowContainer, isReady } = useAreaRendererContainer(iframeDocument, shadowRoot);
|
|
4738
|
+
const { areasPortal, editHighlightPortal } = useAreaPortals({
|
|
4739
|
+
iframeRef,
|
|
4740
|
+
shadowContainer,
|
|
4741
|
+
isReady,
|
|
4742
|
+
onAreaClick,
|
|
4743
|
+
onAreaCreated,
|
|
4744
|
+
});
|
|
4745
|
+
useAreaRectSync({ iframeDocument, shadowRoot, enabled: isReady });
|
|
4746
|
+
useAreaScrollSync({ iframeRef, visualRef, enabled: isReady });
|
|
4747
|
+
return {
|
|
4748
|
+
areasPortal,
|
|
4749
|
+
editHighlightPortal,
|
|
4750
|
+
isReady,
|
|
4751
|
+
};
|
|
4752
|
+
}
|
|
3596
4753
|
|
|
3597
|
-
const
|
|
3598
|
-
const
|
|
3599
|
-
const {
|
|
3600
|
-
|
|
4754
|
+
const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true, onAreaClick, }) => {
|
|
4755
|
+
const { resetView } = useHeatmapAreaClick();
|
|
4756
|
+
const { areasPortal, editHighlightPortal, isReady } = useAreaRenderer({
|
|
4757
|
+
iframeRef,
|
|
4758
|
+
visualRef,
|
|
4759
|
+
shadowRoot,
|
|
4760
|
+
onAreaClick,
|
|
4761
|
+
});
|
|
4762
|
+
useAreaTopAutoDetect({ iframeRef, autoCreateTopN, shadowRoot });
|
|
4763
|
+
useAreaFilterVisible({ iframeRef, enableOverlapResolution });
|
|
3601
4764
|
useEffect(() => {
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
}
|
|
3610
|
-
}, [heatmapType, startClickmap, startScrollmap]);
|
|
3611
|
-
};
|
|
3612
|
-
|
|
3613
|
-
// Base IDs for elements (without viewId suffix)
|
|
3614
|
-
const CLICKED_ELEMENT_ID_BASE = 'gx-hm-clicked-element';
|
|
3615
|
-
const SECONDARY_CLICKED_ELEMENT_ID_BASE = 'gx-hm-secondary-clicked-element';
|
|
3616
|
-
const HOVERED_ELEMENT_ID_BASE = 'gx-hm-hovered-element';
|
|
3617
|
-
const SECONDARY_HOVERED_ELEMENT_ID_BASE = 'gx-hm-secondary-hovered-element';
|
|
3618
|
-
/**
|
|
3619
|
-
* Generate unique element ID for a specific view
|
|
3620
|
-
* @param baseId - Base element ID
|
|
3621
|
-
* @param viewId - View ID
|
|
3622
|
-
* @returns Unique element ID (e.g., 'gx-hm-clicked-element-view-0')
|
|
3623
|
-
*/
|
|
3624
|
-
const getElementId = (baseId, viewId) => {
|
|
3625
|
-
return `${baseId}-${viewId}`;
|
|
3626
|
-
};
|
|
3627
|
-
const getClickedElementId = (viewId, isSecondary = false) => {
|
|
3628
|
-
const baseId = isSecondary ? SECONDARY_CLICKED_ELEMENT_ID_BASE : CLICKED_ELEMENT_ID_BASE;
|
|
3629
|
-
return getElementId(baseId, viewId);
|
|
3630
|
-
};
|
|
3631
|
-
const getHoveredElementId = (viewId, isSecondary = false) => {
|
|
3632
|
-
const baseId = isSecondary ? SECONDARY_HOVERED_ELEMENT_ID_BASE : HOVERED_ELEMENT_ID_BASE;
|
|
3633
|
-
return getElementId(baseId, viewId);
|
|
4765
|
+
return () => {
|
|
4766
|
+
resetView();
|
|
4767
|
+
};
|
|
4768
|
+
}, []);
|
|
4769
|
+
if (!isReady)
|
|
4770
|
+
return null;
|
|
4771
|
+
return (jsxs(Fragment, { children: [areasPortal, editHighlightPortal] }));
|
|
3634
4772
|
};
|
|
4773
|
+
VizAreaClick.displayName = 'VizAreaClick';
|
|
3635
4774
|
|
|
3636
4775
|
const RankBadge = ({ index, elementRect, widthScale, clickOnElement, }) => {
|
|
3637
4776
|
const style = calculateRankPosition(elementRect, widthScale);
|
|
@@ -3661,7 +4800,7 @@ const DEFAULT_POSITION = {
|
|
|
3661
4800
|
};
|
|
3662
4801
|
const ElementCallout = (props) => {
|
|
3663
4802
|
const CompElementCallout = useHeatmapControlStore((state) => state.controls.ElementCallout);
|
|
3664
|
-
const viewId =
|
|
4803
|
+
const viewId = useViewIdContext();
|
|
3665
4804
|
const { element, target, visualRef, hozOffset, alignment = 'left' } = props;
|
|
3666
4805
|
const calloutRef = useRef(null);
|
|
3667
4806
|
const [position, setPosition] = useState(DEFAULT_POSITION);
|
|
@@ -3721,7 +4860,7 @@ const ElementMissing = ({ show = true }) => {
|
|
|
3721
4860
|
}, "aria-live": "assertive", children: "Element not visible on current screen" }));
|
|
3722
4861
|
};
|
|
3723
4862
|
|
|
3724
|
-
const ElementOverlay = ({ type, element, onClick,
|
|
4863
|
+
const ElementOverlay = ({ type, element, onClick, elementId }) => {
|
|
3725
4864
|
// useRenderCount('ElementOverlay');
|
|
3726
4865
|
const { widthScale } = useHeatmapViz();
|
|
3727
4866
|
if (!element || (element.width === 0 && element.height === 0))
|
|
@@ -3745,30 +4884,32 @@ const ELEMENT_CALLOUT = {
|
|
|
3745
4884
|
alignment: 'left',
|
|
3746
4885
|
};
|
|
3747
4886
|
const HeatmapElements = (props) => {
|
|
3748
|
-
const viewId =
|
|
3749
|
-
const { iframeHeight } = useHeatmapViz();
|
|
4887
|
+
const viewId = useViewIdContext();
|
|
3750
4888
|
const clickedElementId = getClickedElementId(viewId, props.isSecondary);
|
|
3751
4889
|
const hoveredElementId = getHoveredElementId(viewId, props.isSecondary);
|
|
3752
|
-
const {
|
|
3753
|
-
const
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
4890
|
+
const { iframeDimensions, isVisible = true, areDefaultRanksHidden } = props;
|
|
4891
|
+
const { iframeHeight } = useHeatmapViz();
|
|
4892
|
+
const { getRect } = useHeatmapElementPosition({
|
|
4893
|
+
iframeRef: props.iframeRef,
|
|
4894
|
+
wrapperRef: props.wrapperRef,
|
|
4895
|
+
visualizer: props.visualizer,
|
|
3757
4896
|
});
|
|
3758
4897
|
const { clickedElement, showMissingElement, shouldShowCallout } = useClickedElement({
|
|
3759
|
-
visualRef,
|
|
4898
|
+
visualRef: props.visualRef,
|
|
3760
4899
|
getRect,
|
|
3761
4900
|
});
|
|
3762
4901
|
const { hoveredElement, handleMouseMove, handleMouseLeave, handleClick } = useHoveredElement({
|
|
3763
|
-
iframeRef,
|
|
4902
|
+
iframeRef: props.iframeRef,
|
|
3764
4903
|
getRect,
|
|
3765
4904
|
});
|
|
3766
|
-
useElementCalloutVisible({ visualRef, getRect });
|
|
4905
|
+
useElementCalloutVisible({ visualRef: props.visualRef, getRect });
|
|
3767
4906
|
useHeatmapEffects({ isVisible });
|
|
3768
4907
|
useRenderCount('HeatmapElements');
|
|
3769
4908
|
if (!isVisible)
|
|
3770
4909
|
return null;
|
|
3771
|
-
|
|
4910
|
+
const isShowHoveredElement = hoveredElement && hoveredElement.hash !== clickedElement?.hash;
|
|
4911
|
+
const isShowClickedElement = shouldShowCallout && clickedElement;
|
|
4912
|
+
return (jsxs("div", { onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, className: "gx-hm-elements", style: { ...iframeDimensions, height: `${iframeHeight}px` }, children: [jsx(ElementMissing, { show: showMissingElement }), jsx(DefaultRankBadges, { getRect: getRect, hidden: areDefaultRanksHidden }), jsx(ElementOverlay, { type: "clicked", element: clickedElement, elementId: clickedElementId }), jsx(ElementOverlay, { type: "hovered", element: hoveredElement, elementId: hoveredElementId, onClick: handleClick }), isShowHoveredElement && (jsx(ElementCallout, { element: hoveredElement, target: `#${hoveredElementId}`, visualRef: props.visualRef, ...ELEMENT_CALLOUT })), isShowClickedElement && (jsx(ElementCallout, { element: clickedElement, target: `#${clickedElementId}`, visualRef: props.visualRef, ...ELEMENT_CALLOUT }))] }));
|
|
3772
4913
|
};
|
|
3773
4914
|
|
|
3774
4915
|
const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
|
|
@@ -3785,130 +4926,34 @@ const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
|
|
|
3785
4926
|
// useRenderCount('VizElements');
|
|
3786
4927
|
if (!iframeRef.current)
|
|
3787
4928
|
return null;
|
|
3788
|
-
return (jsx(HeatmapElements, { visualizer: visualizer, visualRef: visualRef, iframeRef: iframeRef, wrapperRef: wrapperRef, heatmapInfo: dataInfo, isVisible: true, iframeDimensions: {
|
|
3789
|
-
width: contentWidth,
|
|
3790
|
-
position: 'absolute',
|
|
3791
|
-
top: 0,
|
|
3792
|
-
left: 0,
|
|
3793
|
-
// pointerEvents: 'none',
|
|
3794
|
-
} }));
|
|
3795
|
-
};
|
|
3796
|
-
|
|
3797
|
-
const AverageFoldLine = ({ iframeRef, wrapperRef }) => {
|
|
3798
|
-
const { dataInfo } = useHeatmapData();
|
|
3799
|
-
const { getZonePosition } = useZonePositions();
|
|
3800
|
-
const averageFold = dataInfo?.averageFold || 50;
|
|
3801
|
-
const position = getZonePosition({
|
|
3802
|
-
startY: averageFold,
|
|
3803
|
-
endY: averageFold,
|
|
3804
|
-
});
|
|
3805
|
-
if (!position)
|
|
3806
|
-
return null;
|
|
3807
|
-
return (jsx("div", { style: {
|
|
4929
|
+
return (jsx(HeatmapElements, { visualizer: visualizer, visualRef: visualRef, iframeRef: iframeRef, wrapperRef: wrapperRef, heatmapInfo: dataInfo, isVisible: true, iframeDimensions: {
|
|
4930
|
+
width: contentWidth,
|
|
3808
4931
|
position: 'absolute',
|
|
3809
|
-
top:
|
|
4932
|
+
top: 0,
|
|
3810
4933
|
left: 0,
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
backgroundColor: '#0078D4',
|
|
3814
|
-
pointerEvents: 'none',
|
|
3815
|
-
zIndex: 2,
|
|
3816
|
-
boxShadow: '0 0 4px rgba(0,120,212,0.5)',
|
|
3817
|
-
display: 'flex',
|
|
3818
|
-
alignItems: 'center',
|
|
3819
|
-
}, children: jsxs("div", { style: {
|
|
3820
|
-
position: 'absolute',
|
|
3821
|
-
padding: '8px',
|
|
3822
|
-
backgroundColor: 'rgba(0, 120, 212, 0.9)',
|
|
3823
|
-
color: 'white',
|
|
3824
|
-
fontSize: '16px',
|
|
3825
|
-
fontWeight: 600,
|
|
3826
|
-
borderRadius: '4px',
|
|
3827
|
-
whiteSpace: 'nowrap',
|
|
3828
|
-
left: '12px',
|
|
3829
|
-
minWidth: '120px',
|
|
3830
|
-
textAlign: 'center',
|
|
3831
|
-
}, children: ["Average fold - ", averageFold.toFixed(0), "%"] }) }));
|
|
4934
|
+
// pointerEvents: 'none',
|
|
4935
|
+
} }));
|
|
3832
4936
|
};
|
|
3833
4937
|
|
|
3834
|
-
const
|
|
3835
|
-
const
|
|
3836
|
-
const
|
|
3837
|
-
const
|
|
3838
|
-
if (!
|
|
3839
|
-
return null;
|
|
3840
|
-
const findScrollPositionForUserPercent = (targetPercent) => {
|
|
3841
|
-
for (let i = 0; i < scrollmap.length; i++) {
|
|
3842
|
-
if (scrollmap[i].percUsers <= targetPercent) {
|
|
3843
|
-
if (i > 0) {
|
|
3844
|
-
return scrollmap[i - 1].scrollReachY;
|
|
3845
|
-
}
|
|
3846
|
-
return scrollmap[i].scrollReachY;
|
|
3847
|
-
}
|
|
3848
|
-
}
|
|
3849
|
-
return scrollmap[scrollmap.length - 1]?.scrollReachY || null;
|
|
3850
|
-
};
|
|
3851
|
-
const boundaries = [
|
|
3852
|
-
{ percent: 75, label: '75%', color: '#10B981' },
|
|
3853
|
-
{ percent: 50, label: '50%', color: '#F59E0B' },
|
|
3854
|
-
{ percent: 25, label: '25%', color: '#EF4444' },
|
|
3855
|
-
{ percent: 5, label: '5%', color: '#8B5CF6' },
|
|
3856
|
-
];
|
|
3857
|
-
const isScrollDepth = scrollType === IScrollType.Depth;
|
|
3858
|
-
if (!isScrollDepth)
|
|
4938
|
+
const VizClickmap = ({ iframeRef, visualRef, wrapperRef }) => {
|
|
4939
|
+
const clickMode = useHeatmapConfigStore((state) => state.clickMode);
|
|
4940
|
+
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
4941
|
+
const isClickType = heatmapType === IHeatmapType.Click;
|
|
4942
|
+
if (!isClickType)
|
|
3859
4943
|
return null;
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
if (!position)
|
|
3869
|
-
return null;
|
|
3870
|
-
return (jsx("div", { className: `marker-boundary-line-${boundary.percent}`, style: {
|
|
3871
|
-
position: 'absolute',
|
|
3872
|
-
top: `${position.top}px`,
|
|
3873
|
-
left: 0,
|
|
3874
|
-
transformOrigin: 'left center',
|
|
3875
|
-
width: '100%',
|
|
3876
|
-
height: '0px',
|
|
3877
|
-
// borderBottom: `2px dashed #323130`,
|
|
3878
|
-
borderBottom: `2px solid ${boundary.color}`,
|
|
3879
|
-
// background: 'repeating-linear-gradient(90deg, #323130, transparent 2px 3px)',
|
|
3880
|
-
zIndex: 1,
|
|
3881
|
-
display: 'flex',
|
|
3882
|
-
alignItems: 'center',
|
|
3883
|
-
}, children: jsx("div", { style: {
|
|
3884
|
-
position: 'absolute',
|
|
3885
|
-
padding: '8px',
|
|
3886
|
-
backgroundColor: boundary.color,
|
|
3887
|
-
color: 'white',
|
|
3888
|
-
fontSize: '16px',
|
|
3889
|
-
fontWeight: 600,
|
|
3890
|
-
borderRadius: '4px',
|
|
3891
|
-
whiteSpace: 'nowrap',
|
|
3892
|
-
left: '12px',
|
|
3893
|
-
minWidth: '120px',
|
|
3894
|
-
textAlign: 'center',
|
|
3895
|
-
// textAlign: 'center',
|
|
3896
|
-
// padding: '8px',
|
|
3897
|
-
// paddingInline: '8px',
|
|
3898
|
-
// fontSize: '16px',
|
|
3899
|
-
// background: '#fff',
|
|
3900
|
-
// width: 'auto',
|
|
3901
|
-
// borderRadius: '4px',
|
|
3902
|
-
// position: 'absolute',
|
|
3903
|
-
// left: '12px',
|
|
3904
|
-
// minWidth: '120px',
|
|
3905
|
-
}, children: boundary.label }) }, boundary.label));
|
|
3906
|
-
}) }));
|
|
4944
|
+
switch (clickMode) {
|
|
4945
|
+
case IClickMode.Default:
|
|
4946
|
+
return jsx(VizElements, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef });
|
|
4947
|
+
case IClickMode.Area:
|
|
4948
|
+
return (jsx(VizAreaClick, { iframeRef: iframeRef, visualRef: visualRef, autoCreateTopN: 0, onAreaClick: (area) => {
|
|
4949
|
+
console.log('area clicked', area);
|
|
4950
|
+
} }));
|
|
4951
|
+
}
|
|
3907
4952
|
};
|
|
3908
4953
|
|
|
3909
4954
|
const ScrollMapMinimap = ({ zones, maxUsers }) => {
|
|
3910
4955
|
const scrollType = useHeatmapConfigStore((state) => state.scrollType);
|
|
3911
|
-
const { showMinimap } =
|
|
4956
|
+
const { showMinimap } = useHeatmapScroll();
|
|
3912
4957
|
const isScrollType = [IScrollType.Attention].includes(scrollType);
|
|
3913
4958
|
if (!showMinimap || !isScrollType)
|
|
3914
4959
|
return null;
|
|
@@ -4064,6 +5109,118 @@ const ScrollMapOverlay = ({ wrapperRef, iframeRef }) => {
|
|
|
4064
5109
|
}, children: jsx(HoverZones, { position: position, currentScrollPercent: currentScrollPercent, iframeRef: iframeRef, wrapperRef: wrapperRef }) }));
|
|
4065
5110
|
};
|
|
4066
5111
|
|
|
5112
|
+
const AverageFoldLine = ({ iframeRef, wrapperRef }) => {
|
|
5113
|
+
const { dataInfo } = useHeatmapData();
|
|
5114
|
+
const { getZonePosition } = useZonePositions();
|
|
5115
|
+
const averageFold = dataInfo?.averageFold || 50;
|
|
5116
|
+
const position = getZonePosition({
|
|
5117
|
+
startY: averageFold,
|
|
5118
|
+
endY: averageFold,
|
|
5119
|
+
});
|
|
5120
|
+
if (!position)
|
|
5121
|
+
return null;
|
|
5122
|
+
return (jsx("div", { style: {
|
|
5123
|
+
position: 'absolute',
|
|
5124
|
+
top: `${position.top}px`,
|
|
5125
|
+
left: 0,
|
|
5126
|
+
width: '100%',
|
|
5127
|
+
height: '2px',
|
|
5128
|
+
backgroundColor: '#0078D4',
|
|
5129
|
+
pointerEvents: 'none',
|
|
5130
|
+
zIndex: 2,
|
|
5131
|
+
boxShadow: '0 0 4px rgba(0,120,212,0.5)',
|
|
5132
|
+
display: 'flex',
|
|
5133
|
+
alignItems: 'center',
|
|
5134
|
+
}, children: jsxs("div", { style: {
|
|
5135
|
+
position: 'absolute',
|
|
5136
|
+
padding: '8px',
|
|
5137
|
+
backgroundColor: 'rgba(0, 120, 212, 0.9)',
|
|
5138
|
+
color: 'white',
|
|
5139
|
+
fontSize: '16px',
|
|
5140
|
+
fontWeight: 600,
|
|
5141
|
+
borderRadius: '4px',
|
|
5142
|
+
whiteSpace: 'nowrap',
|
|
5143
|
+
left: '12px',
|
|
5144
|
+
minWidth: '120px',
|
|
5145
|
+
textAlign: 'center',
|
|
5146
|
+
}, children: ["Average fold - ", averageFold.toFixed(0), "%"] }) }));
|
|
5147
|
+
};
|
|
5148
|
+
|
|
5149
|
+
const ScrollmapMarker = ({ iframeRef, wrapperRef }) => {
|
|
5150
|
+
const scrollType = useHeatmapConfigStore((state) => state.scrollType);
|
|
5151
|
+
const { scrollmap } = useHeatmapData();
|
|
5152
|
+
const { getZonePosition } = useZonePositions();
|
|
5153
|
+
if (!scrollmap || scrollmap.length === 0)
|
|
5154
|
+
return null;
|
|
5155
|
+
const findScrollPositionForUserPercent = (targetPercent) => {
|
|
5156
|
+
for (let i = 0; i < scrollmap.length; i++) {
|
|
5157
|
+
if (scrollmap[i].percUsers <= targetPercent) {
|
|
5158
|
+
if (i > 0) {
|
|
5159
|
+
return scrollmap[i - 1].scrollReachY;
|
|
5160
|
+
}
|
|
5161
|
+
return scrollmap[i].scrollReachY;
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
return scrollmap[scrollmap.length - 1]?.scrollReachY || null;
|
|
5165
|
+
};
|
|
5166
|
+
const boundaries = [
|
|
5167
|
+
{ percent: 75, label: '75%', color: '#10B981' },
|
|
5168
|
+
{ percent: 50, label: '50%', color: '#F59E0B' },
|
|
5169
|
+
{ percent: 25, label: '25%', color: '#EF4444' },
|
|
5170
|
+
{ percent: 5, label: '5%', color: '#8B5CF6' },
|
|
5171
|
+
];
|
|
5172
|
+
const isScrollDepth = scrollType === IScrollType.Depth;
|
|
5173
|
+
if (!isScrollDepth)
|
|
5174
|
+
return null;
|
|
5175
|
+
return (jsx(Fragment, { children: boundaries.map((boundary) => {
|
|
5176
|
+
const scrollY = findScrollPositionForUserPercent(boundary.percent);
|
|
5177
|
+
if (scrollY === null)
|
|
5178
|
+
return null;
|
|
5179
|
+
const position = getZonePosition({
|
|
5180
|
+
startY: scrollY,
|
|
5181
|
+
endY: scrollY,
|
|
5182
|
+
});
|
|
5183
|
+
if (!position)
|
|
5184
|
+
return null;
|
|
5185
|
+
return (jsx("div", { className: `marker-boundary-line-${boundary.percent}`, style: {
|
|
5186
|
+
position: 'absolute',
|
|
5187
|
+
top: `${position.top}px`,
|
|
5188
|
+
left: 0,
|
|
5189
|
+
transformOrigin: 'left center',
|
|
5190
|
+
width: '100%',
|
|
5191
|
+
height: '0px',
|
|
5192
|
+
// borderBottom: `2px dashed #323130`,
|
|
5193
|
+
borderBottom: `2px solid ${boundary.color}`,
|
|
5194
|
+
// background: 'repeating-linear-gradient(90deg, #323130, transparent 2px 3px)',
|
|
5195
|
+
zIndex: 1,
|
|
5196
|
+
display: 'flex',
|
|
5197
|
+
alignItems: 'center',
|
|
5198
|
+
}, children: jsx("div", { style: {
|
|
5199
|
+
position: 'absolute',
|
|
5200
|
+
padding: '8px',
|
|
5201
|
+
backgroundColor: boundary.color,
|
|
5202
|
+
color: 'white',
|
|
5203
|
+
fontSize: '16px',
|
|
5204
|
+
fontWeight: 600,
|
|
5205
|
+
borderRadius: '4px',
|
|
5206
|
+
whiteSpace: 'nowrap',
|
|
5207
|
+
left: '12px',
|
|
5208
|
+
minWidth: '120px',
|
|
5209
|
+
textAlign: 'center',
|
|
5210
|
+
// textAlign: 'center',
|
|
5211
|
+
// padding: '8px',
|
|
5212
|
+
// paddingInline: '8px',
|
|
5213
|
+
// fontSize: '16px',
|
|
5214
|
+
// background: '#fff',
|
|
5215
|
+
// width: 'auto',
|
|
5216
|
+
// borderRadius: '4px',
|
|
5217
|
+
// position: 'absolute',
|
|
5218
|
+
// left: '12px',
|
|
5219
|
+
// minWidth: '120px',
|
|
5220
|
+
}, children: boundary.label }) }, boundary.label));
|
|
5221
|
+
}) }));
|
|
5222
|
+
};
|
|
5223
|
+
|
|
4067
5224
|
const SCROLL_TYPES = [IHeatmapType.Scroll];
|
|
4068
5225
|
const VizScrollMap = ({ iframeRef, wrapperRef }) => {
|
|
4069
5226
|
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
@@ -4119,12 +5276,11 @@ const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHe
|
|
|
4119
5276
|
|
|
4120
5277
|
const VizDomRenderer = ({ mode = 'heatmap' }) => {
|
|
4121
5278
|
const contentWidth = useHeatmapConfigStore((state) => state.width || 0);
|
|
4122
|
-
const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
|
|
4123
5279
|
const wrapperRef = useRef(null);
|
|
4124
5280
|
const visualRef = useRef(null);
|
|
4125
|
-
const { setSelectedElement } =
|
|
5281
|
+
const { setSelectedElement } = useHeatmapClick();
|
|
4126
5282
|
const { iframeHeight, setIframeHeight, isRenderViz } = useHeatmapViz();
|
|
4127
|
-
const { iframeRef } =
|
|
5283
|
+
const { iframeRef } = useHeatmapRenderByMode(mode);
|
|
4128
5284
|
const { scaledHeight, handleScroll } = useHeatmapScale({
|
|
4129
5285
|
wrapperRef,
|
|
4130
5286
|
iframeRef,
|
|
@@ -4145,7 +5301,7 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
|
|
|
4145
5301
|
useEffect(() => {
|
|
4146
5302
|
return cleanUp;
|
|
4147
5303
|
}, []);
|
|
4148
|
-
return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [
|
|
5304
|
+
return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [jsx(VizClickmap, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef }), jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: contentWidth, scrolling: "no" }), jsx(VizScrollMap, { iframeRef: iframeRef, wrapperRef: visualRef })] }));
|
|
4149
5305
|
};
|
|
4150
5306
|
|
|
4151
5307
|
const VizLoading = () => {
|
|
@@ -4163,6 +5319,7 @@ const VizDomHeatmap = () => {
|
|
|
4163
5319
|
}, []);
|
|
4164
5320
|
return (jsxs(VizContainer, { isActive: true, children: [jsx(VizDomRenderer, {}), iframeHeight === 0 && jsx(VizLoading, {})] }));
|
|
4165
5321
|
};
|
|
5322
|
+
VizDomHeatmap.displayName = 'VizDomHeatmap';
|
|
4166
5323
|
|
|
4167
5324
|
const VizLiveRenderer = () => {
|
|
4168
5325
|
const contentWidth = useHeatmapConfigStore((state) => state.width);
|
|
@@ -4196,6 +5353,7 @@ const VizLiveHeatmap = () => {
|
|
|
4196
5353
|
}, []);
|
|
4197
5354
|
return (jsxs(VizContainer, { isActive: true, children: [jsx(VizLiveRenderer, {}), (!iframeHeight || !wrapperHeight) && jsx(VizLoading, {})] }));
|
|
4198
5355
|
};
|
|
5356
|
+
VizLiveHeatmap.displayName = 'VizLiveHeatmap';
|
|
4199
5357
|
|
|
4200
5358
|
const ContentVizByMode = () => {
|
|
4201
5359
|
const mode = useHeatmapConfigStore((state) => state.mode);
|
|
@@ -4212,14 +5370,23 @@ const ContentVizByMode = () => {
|
|
|
4212
5370
|
return jsx(VizDomHeatmap, {});
|
|
4213
5371
|
}
|
|
4214
5372
|
};
|
|
5373
|
+
ContentVizByMode.displayName = 'ContentVizByMode';
|
|
4215
5374
|
|
|
4216
|
-
const
|
|
5375
|
+
const HeatmapPreview = () => {
|
|
4217
5376
|
return (jsxs(BoxStack, { id: "gx-hm-container", flexDirection: "row", overflow: "hidden", flex: "1", position: "relative", children: [jsx(ContentSidebar, {}), jsxs(BoxStack, { flexDirection: "column", flex: "1", children: [jsx(ContentMetricBar, {}), jsx(ContentVizByMode, {}), jsx(ContentToolbar, {})] })] }));
|
|
4218
5377
|
};
|
|
4219
5378
|
|
|
4220
|
-
const
|
|
4221
|
-
|
|
4222
|
-
|
|
5379
|
+
const ContentTopBar = () => {
|
|
5380
|
+
const controls = useHeatmapControlStore((state) => state.controls);
|
|
5381
|
+
useHeatmapConfigStore((state) => state.mode);
|
|
5382
|
+
const TopBar = controls.TopBar;
|
|
5383
|
+
// In compare mode, hide individual top bars since we have a global header
|
|
5384
|
+
// if (mode === 'compare') {
|
|
5385
|
+
// return null;
|
|
5386
|
+
// }
|
|
5387
|
+
return (jsx(BoxStack, { id: "gx-hm-content-header", flexDirection: "row", alignItems: "center", overflow: "auto", zIndex: 1, backgroundColor: "white", style: {
|
|
5388
|
+
borderBottom: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
|
|
5389
|
+
}, children: TopBar && jsx(TopBar, {}) }));
|
|
4223
5390
|
};
|
|
4224
5391
|
|
|
4225
5392
|
const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
|
|
@@ -4230,11 +5397,11 @@ const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
|
|
|
4230
5397
|
performanceLogger.configure({
|
|
4231
5398
|
enabled: true,
|
|
4232
5399
|
logToConsole: false,
|
|
4233
|
-
logLevel: 'normal',
|
|
5400
|
+
logLevel: 'normal',
|
|
4234
5401
|
thresholds: {
|
|
4235
|
-
slowRenderMs: 16,
|
|
4236
|
-
slowHookMs: 5,
|
|
4237
|
-
excessiveRenderCount: 10,
|
|
5402
|
+
slowRenderMs: 16,
|
|
5403
|
+
slowHookMs: 5,
|
|
5404
|
+
excessiveRenderCount: 10,
|
|
4238
5405
|
},
|
|
4239
5406
|
externalLogger: (metric) => {
|
|
4240
5407
|
if (metric.name === 'VizDomRenderer') ;
|
|
@@ -4243,7 +5410,7 @@ const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
|
|
|
4243
5410
|
return (jsx(BoxStack, { id: "gx-hm-project", flexDirection: "column", flex: "1", height: "100%", style: getVariableStyle(), children: jsx(BoxStack, { id: "gx-hm-project-content", flexDirection: "column", flex: "1", children: jsx("div", { style: {
|
|
4244
5411
|
minHeight: '100%',
|
|
4245
5412
|
display: 'flex',
|
|
4246
|
-
}, children: jsx(
|
|
5413
|
+
}, children: jsxs(BoxStack, { id: "gx-hm-layout", flexDirection: "column", flex: "1", children: [jsx(ContentTopBar, {}), jsx(HeatmapPreview, {})] }) }) }) }));
|
|
4247
5414
|
function getVariableStyle() {
|
|
4248
5415
|
return {
|
|
4249
5416
|
'--gx-hm-border-width': `${HEATMAP_CONFIG.borderWidth}px`,
|
|
@@ -4253,4 +5420,104 @@ const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
|
|
|
4253
5420
|
}
|
|
4254
5421
|
};
|
|
4255
5422
|
|
|
4256
|
-
|
|
5423
|
+
const AreaEditHighlight = ({ element, shadowRoot, onClick }) => {
|
|
5424
|
+
const [rect, setRect] = useState(null);
|
|
5425
|
+
const highlightRef = useRef(null);
|
|
5426
|
+
useEffect(() => {
|
|
5427
|
+
if (!element) {
|
|
5428
|
+
setRect(null);
|
|
5429
|
+
return;
|
|
5430
|
+
}
|
|
5431
|
+
const elementRect = getElementRect(element);
|
|
5432
|
+
setRect(elementRect);
|
|
5433
|
+
}, [element, shadowRoot]);
|
|
5434
|
+
if (!rect) {
|
|
5435
|
+
return null;
|
|
5436
|
+
}
|
|
5437
|
+
const handleClick = (e) => {
|
|
5438
|
+
if (element && onClick) {
|
|
5439
|
+
e.stopPropagation();
|
|
5440
|
+
e.preventDefault();
|
|
5441
|
+
onClick(element);
|
|
5442
|
+
}
|
|
5443
|
+
};
|
|
5444
|
+
return (jsx("div", { ref: highlightRef, id: AREA_HOVER_ELEMENT_ID, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: handleClick, style: {
|
|
5445
|
+
position: 'absolute',
|
|
5446
|
+
top: `${rect.absoluteTop}px`,
|
|
5447
|
+
left: `${rect.absoluteLeft}px`,
|
|
5448
|
+
width: `${rect.width}px`,
|
|
5449
|
+
height: `${rect.height}px`,
|
|
5450
|
+
zIndex: Number.MAX_SAFE_INTEGER,
|
|
5451
|
+
boxShadow: AREA_HOVER_BOX_SHADOW,
|
|
5452
|
+
backgroundColor: 'rgba(128, 128, 128, 0.4)',
|
|
5453
|
+
backgroundImage: 'repeating-linear-gradient(135deg, transparent, transparent 35px, rgba(255,255,255,.5) 35px, rgba(255,255,255,.5) 70px)',
|
|
5454
|
+
pointerEvents: 'auto',
|
|
5455
|
+
cursor: 'pointer',
|
|
5456
|
+
boxSizing: 'border-box',
|
|
5457
|
+
} }));
|
|
5458
|
+
};
|
|
5459
|
+
AreaEditHighlight.displayName = 'AreaEditHighlight';
|
|
5460
|
+
|
|
5461
|
+
const AreaLabel = ({ clickDist, totalClicks, kind }) => {
|
|
5462
|
+
if (kind === 'money') {
|
|
5463
|
+
return null;
|
|
5464
|
+
}
|
|
5465
|
+
return (jsxs("div", { style: {
|
|
5466
|
+
color: '#161514',
|
|
5467
|
+
backgroundColor: 'rgba(255, 255, 255, 0.86)',
|
|
5468
|
+
display: 'flex',
|
|
5469
|
+
flexDirection: 'column',
|
|
5470
|
+
alignItems: 'center',
|
|
5471
|
+
padding: '8px',
|
|
5472
|
+
borderRadius: '4px',
|
|
5473
|
+
fontSize: '16px',
|
|
5474
|
+
lineHeight: '20px',
|
|
5475
|
+
minWidth: '56px',
|
|
5476
|
+
fontWeight: 600,
|
|
5477
|
+
fontFamily: '"Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif',
|
|
5478
|
+
pointerEvents: 'none',
|
|
5479
|
+
}, children: [jsxs("span", { children: [clickDist.toFixed(2), "%"] }), jsxs("span", { style: { fontSize: '12px', fontWeight: 400, opacity: 0.8 }, children: [totalClicks, " clicks"] })] }));
|
|
5480
|
+
};
|
|
5481
|
+
AreaLabel.displayName = 'AreaLabel';
|
|
5482
|
+
|
|
5483
|
+
const AreaOverlay = ({ area, onClick, onMouseEnter, onMouseLeave, isSelected, isHovered, }) => {
|
|
5484
|
+
const [rect, setRect] = useState(area.rect.value);
|
|
5485
|
+
useEffect(() => {
|
|
5486
|
+
const handleRectChange = (newRect) => {
|
|
5487
|
+
if (newRect) {
|
|
5488
|
+
setRect(newRect);
|
|
5489
|
+
}
|
|
5490
|
+
};
|
|
5491
|
+
area.rect.observe(handleRectChange);
|
|
5492
|
+
return () => {
|
|
5493
|
+
area.rect.unobserve(handleRectChange);
|
|
5494
|
+
};
|
|
5495
|
+
}, [area.rect]);
|
|
5496
|
+
if (!rect)
|
|
5497
|
+
return null;
|
|
5498
|
+
const position = area.isFixed ? 'fixed' : 'absolute';
|
|
5499
|
+
const showLabel = !isRectTooSmallForLabel(rect);
|
|
5500
|
+
const backgroundColor = isHovered ? area.hoverColor : area.color;
|
|
5501
|
+
const boxShadow = isSelected ? '0 0 0 3px red inset' : isHovered ? AREA_HOVER_BOX_SHADOW : '0 0 0 2px white inset';
|
|
5502
|
+
return (jsxs("div", { id: `area-${area.id}`, "data-area-id": area.id, [AREA_MAP_DIV_ATTRIBUTE]: '1', onClick: () => onClick?.(area), onMouseEnter: () => onMouseEnter?.(area), onMouseLeave: () => onMouseLeave?.(area), children: [jsx("style", { children: `
|
|
5503
|
+
#area-${area.id} {
|
|
5504
|
+
position: ${position};
|
|
5505
|
+
top: ${rect.top}px;
|
|
5506
|
+
left: ${rect.left}px;
|
|
5507
|
+
width: ${rect.width}px;
|
|
5508
|
+
height: ${rect.height}px;
|
|
5509
|
+
background-color: ${backgroundColor};
|
|
5510
|
+
box-shadow: ${boxShadow};
|
|
5511
|
+
box-sizing: border-box;
|
|
5512
|
+
display: flex;
|
|
5513
|
+
align-items: center;
|
|
5514
|
+
justify-content: center;
|
|
5515
|
+
cursor: pointer;
|
|
5516
|
+
transition: background-color 0.2s, box-shadow 0.2s;
|
|
5517
|
+
pointer-events: auto;
|
|
5518
|
+
z-index: ${Number.MAX_SAFE_INTEGER};
|
|
5519
|
+
}
|
|
5520
|
+
` }), showLabel && jsx(AreaLabel, { clickDist: area.clickDist, totalClicks: area.totalclicks, kind: area.kind })] }));
|
|
5521
|
+
};
|
|
5522
|
+
|
|
5523
|
+
export { AreaEditHighlight, AreaOverlay, DEFAULT_SIDEBAR_WIDTH, DEFAULT_VIEW_ID, GraphView, HEATMAP_CONFIG, HEATMAP_IFRAME, HEATMAP_STYLE, HeatmapLayout, IClickMode, IClickType, IHeatmapType, IScrollType, ViewIdContext, Z_INDEX, compareViewPerformance, convertViewportToIframeCoords, createStorePerformanceTracker, downloadPerformanceReport, getCompareViewId, getMetricsByViewId, getPerformanceReportJSON, getScrollGradientColor, performanceLogger, printPerformanceSummary, scrollToElementIfNeeded, sendPerformanceReport, trackStoreAction, useAreaCreation, useAreaEditMode, useAreaFilterVisible, useAreaInteraction, useAreaPortals, useAreaRectSync, useAreaRendererContainer, useAreaScrollSync, useAreaTopAutoDetect, useClickedElement, useDebounceCallback, useElementCalloutVisible, useHeatmapAreaClick, useHeatmapCanvas, useHeatmapClick, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapCopyView, useHeatmapData, useHeatmapEffects, useHeatmapElementPosition, useHeatmapLiveStore, useHeatmapRenderByMode, useHeatmapScale, useHeatmapScroll, useHeatmapViz, useHoveredElement, useMeasureFunction, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useRenderCount, useScrollmapZones, useTrackHookCall, useViewIdContext, useVizLiveRender, useWhyDidYouUpdate, useWrapperRefHeight, useZonePositions, withPerformanceTracking };
|