@gemx-dev/heatmap-react 3.5.92-dev.3 → 3.5.92-dev.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/Layout/HeatmapLayout.d.ts +2 -0
- package/dist/esm/components/Layout/HeatmapLayout.d.ts.map +1 -1
- package/dist/esm/components/VizDom/VizDomHeatmap.d.ts.map +1 -1
- package/dist/esm/components/VizDom/VizDomRenderer.d.ts +2 -1
- package/dist/esm/components/VizDom/VizDomRenderer.d.ts.map +1 -1
- package/dist/esm/components/VizElement/ElementCallout.d.ts +2 -2
- package/dist/esm/components/VizElement/ElementCallout.d.ts.map +1 -1
- package/dist/esm/components/VizElement/ElementCalloutClicked.d.ts +2 -2
- package/dist/esm/components/VizElement/ElementCalloutClicked.d.ts.map +1 -1
- package/dist/esm/components/VizElement/ElementCalloutHovered.d.ts +2 -1
- package/dist/esm/components/VizElement/ElementCalloutHovered.d.ts.map +1 -1
- package/dist/esm/components/VizElement/ElementMissing.d.ts +2 -1
- package/dist/esm/components/VizElement/ElementMissing.d.ts.map +1 -1
- package/dist/esm/components/VizElement/HeatmapElements.d.ts +4 -4
- package/dist/esm/components/VizElement/HeatmapElements.d.ts.map +1 -1
- package/dist/esm/components/VizElement/VizElements.d.ts +4 -3
- package/dist/esm/components/VizElement/VizElements.d.ts.map +1 -1
- package/dist/esm/components/VizLive/VizLiveHeatmap.d.ts.map +1 -1
- package/dist/esm/components/VizScrollmap/HoverZones.d.ts +3 -3
- package/dist/esm/components/VizScrollmap/HoverZones.d.ts.map +1 -1
- package/dist/esm/components/VizScrollmap/Minimap.d.ts +3 -3
- package/dist/esm/components/VizScrollmap/Minimap.d.ts.map +1 -1
- package/dist/esm/helpers/canvas-backdrop.d.ts.map +1 -1
- package/dist/esm/helpers/viewport/element.d.ts +2 -2
- package/dist/esm/helpers/viewport/element.d.ts.map +1 -1
- package/dist/esm/helpers/viz-elm-callout/viz-elm.d.ts +2 -1
- package/dist/esm/helpers/viz-elm-callout/viz-elm.d.ts.map +1 -1
- package/dist/esm/hooks/common/index.d.ts +1 -1
- package/dist/esm/hooks/common/index.d.ts.map +1 -1
- package/dist/esm/hooks/common/useHeatmapViewportByDevice.d.ts +7 -0
- package/dist/esm/hooks/common/useHeatmapViewportByDevice.d.ts.map +1 -0
- package/dist/esm/hooks/register/useRegisterConfig.d.ts +3 -1
- package/dist/esm/hooks/register/useRegisterConfig.d.ts.map +1 -1
- package/dist/esm/hooks/view-context/index.d.ts +1 -0
- package/dist/esm/hooks/view-context/index.d.ts.map +1 -1
- package/dist/esm/hooks/view-context/useHeatmapCopyView.d.ts.map +1 -1
- package/dist/esm/hooks/view-context/useHeatmapDataContext.d.ts +4 -16
- package/dist/esm/hooks/view-context/useHeatmapDataContext.d.ts.map +1 -1
- package/dist/esm/hooks/view-context/useHeatmapLiveContext.d.ts +34 -0
- package/dist/esm/hooks/view-context/useHeatmapLiveContext.d.ts.map +1 -0
- package/dist/esm/hooks/view-context/useHeatmapSettingContext.d.ts +3 -1
- package/dist/esm/hooks/view-context/useHeatmapSettingContext.d.ts.map +1 -1
- package/dist/esm/hooks/view-context/useHeatmapVizContext.d.ts +2 -2
- package/dist/esm/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -1
- package/dist/esm/hooks/viz-canvas/useClickmap.d.ts.map +1 -1
- package/dist/esm/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
- package/dist/esm/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
- package/dist/esm/hooks/viz-elm/useClickedElement.d.ts +3 -3
- package/dist/esm/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
- package/dist/esm/hooks/viz-elm/useElementCalloutVisible.d.ts +2 -1
- package/dist/esm/hooks/viz-elm/useElementCalloutVisible.d.ts.map +1 -1
- package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -3
- package/dist/esm/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
- package/dist/esm/hooks/viz-elm/useHoveredElement.d.ts +3 -1
- package/dist/esm/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
- package/dist/esm/hooks/viz-live/useVizLiveIframeMsg.d.ts.map +1 -1
- package/dist/esm/hooks/viz-live/useVizLiveRender.d.ts.map +1 -1
- package/dist/esm/hooks/viz-render/useHeatmapIframeProcessor.d.ts +6 -0
- package/dist/esm/hooks/viz-render/useHeatmapIframeProcessor.d.ts.map +1 -0
- package/dist/esm/hooks/viz-render/useHeatmapRenderByMode.d.ts +3 -2
- package/dist/esm/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -1
- package/dist/esm/hooks/viz-render/useHeatmapRenderDom.d.ts +5 -0
- package/dist/esm/hooks/viz-render/useHeatmapRenderDom.d.ts.map +1 -0
- package/dist/esm/hooks/viz-render/useReplayRender.d.ts +2 -2
- package/dist/esm/hooks/viz-render/useReplayRender.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
- package/dist/esm/hooks/viz-scale/useScaleCalculation.d.ts +1 -2
- package/dist/esm/hooks/viz-scale/useScaleCalculation.d.ts.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2414 -1367
- package/dist/esm/index.mjs +2414 -1367
- package/dist/esm/libs/iframe-processor/index.d.ts +2 -5
- package/dist/esm/libs/iframe-processor/index.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/lifecycle.d.ts +16 -7
- package/dist/esm/libs/iframe-processor/lifecycle.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/orchestrator.d.ts +14 -45
- package/dist/esm/libs/iframe-processor/orchestrator.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/height-observer/index.d.ts +5 -14
- package/dist/esm/libs/iframe-processor/processors/height-observer/index.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/height-observer/listeners.d.ts +11 -0
- package/dist/esm/libs/iframe-processor/processors/height-observer/listeners.d.ts.map +1 -0
- package/dist/esm/libs/iframe-processor/processors/height-observer/types.d.ts +18 -1
- package/dist/esm/libs/iframe-processor/processors/height-observer/types.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/navigation/index.d.ts +3 -14
- package/dist/esm/libs/iframe-processor/processors/navigation/index.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/navigation/listeners.d.ts +3 -4
- package/dist/esm/libs/iframe-processor/processors/navigation/listeners.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/navigation/types.d.ts +13 -0
- package/dist/esm/libs/iframe-processor/processors/navigation/types.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/dimensions.d.ts +1 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/dimensions.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/global-fixes/viewport-unit-replacer/fixes.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/gp-v7-fixes/gem-slider-item/fixes.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts +0 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/global-fixes/types.d.ts +2 -2
- package/dist/esm/libs/iframe-processor/processors/viewport/index.d.ts +3 -9
- package/dist/esm/libs/iframe-processor/processors/viewport/index.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/listeners.d.ts +3 -2
- package/dist/esm/libs/iframe-processor/processors/viewport/listeners.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/pipeline.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/shop-overrides/types.d.ts +2 -0
- package/dist/esm/libs/iframe-processor/processors/viewport/shop-overrides/types.d.ts.map +1 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/constants.d.ts +5 -0
- package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/constants.d.ts.map +1 -0
- package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/functions.d.ts +11 -0
- package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/functions.d.ts.map +1 -0
- package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/index.d.ts +9 -0
- package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/index.d.ts.map +1 -0
- package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/state.d.ts +13 -0
- package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/state.d.ts.map +1 -0
- package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/types.d.ts +16 -0
- package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer/types.d.ts.map +1 -0
- package/dist/esm/libs/iframe-processor/processors/viewport/types.d.ts +8 -0
- package/dist/esm/libs/iframe-processor/processors/viewport/types.d.ts.map +1 -0
- package/dist/esm/libs/iframe-processor/shared/factory-types.d.ts +24 -0
- package/dist/esm/libs/iframe-processor/shared/factory-types.d.ts.map +1 -0
- package/dist/esm/libs/iframe-processor/shared/iframe-types.d.ts +22 -0
- package/dist/esm/libs/iframe-processor/shared/iframe-types.d.ts.map +1 -0
- package/dist/esm/libs/index.d.ts +1 -0
- package/dist/esm/libs/index.d.ts.map +1 -1
- package/dist/esm/libs/perf.d.ts +83 -0
- package/dist/esm/libs/perf.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/GXVisualizer.d.ts +23 -4
- package/dist/esm/libs/visualizer/GXVisualizer.d.ts.map +1 -1
- package/dist/esm/libs/visualizer/cache/config.d.ts +15 -0
- package/dist/esm/libs/visualizer/cache/config.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/cache/index.d.ts +16 -0
- package/dist/esm/libs/visualizer/cache/index.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/index.d.ts +2 -2
- package/dist/esm/libs/visualizer/index.d.ts.map +1 -1
- package/dist/{umd/libs/visualizer → esm/libs/visualizer/renderers}/AttentionMapRenderer.d.ts +2 -2
- package/dist/esm/libs/visualizer/renderers/AttentionMapRenderer.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/renderers/ScrollBucketRenderer.d.ts +44 -0
- package/dist/esm/libs/visualizer/renderers/ScrollBucketRenderer.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/renderers/index.d.ts +3 -0
- package/dist/esm/libs/visualizer/renderers/index.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/shadow-dom/extractor.d.ts +23 -0
- package/dist/esm/libs/visualizer/shadow-dom/extractor.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/types/data.d.ts +72 -0
- package/dist/esm/libs/visualizer/types/data.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/{types.d.ts → types/extend.d.ts} +7 -2
- package/dist/esm/libs/visualizer/types/extend.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/types/index.d.ts +6 -0
- package/dist/esm/libs/visualizer/types/index.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/types/layout.d.ts +69 -0
- package/dist/esm/libs/visualizer/types/layout.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/types/visualize.d.ts +26 -0
- package/dist/esm/libs/visualizer/types/visualize.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/utils/delay.d.ts +21 -0
- package/dist/esm/libs/visualizer/utils/delay.d.ts.map +1 -0
- package/dist/esm/libs/visualizer/utils/render.d.ts +7 -0
- package/dist/esm/libs/visualizer/utils/render.d.ts.map +1 -0
- package/dist/esm/stores/config.d.ts +4 -2
- package/dist/esm/stores/config.d.ts.map +1 -1
- package/dist/esm/stores/data.d.ts +4 -0
- package/dist/esm/stores/data.d.ts.map +1 -1
- package/dist/esm/stores/mode-live.d.ts +30 -16
- package/dist/esm/stores/mode-live.d.ts.map +1 -1
- package/dist/esm/stores/setting.d.ts +3 -1
- package/dist/esm/stores/setting.d.ts.map +1 -1
- package/dist/esm/stores/viz.d.ts +2 -2
- package/dist/esm/types/clarity.d.ts +18 -0
- package/dist/esm/types/clarity.d.ts.map +1 -1
- package/dist/esm/types/control.d.ts +2 -0
- package/dist/esm/types/control.d.ts.map +1 -1
- package/dist/esm/types/heatmap-info.d.ts +1 -0
- package/dist/esm/types/heatmap-info.d.ts.map +1 -1
- package/dist/esm/types/heatmap.d.ts +9 -0
- package/dist/esm/types/heatmap.d.ts.map +1 -1
- package/dist/esm/types/iframe-helper.d.ts +0 -19
- package/dist/esm/types/iframe-helper.d.ts.map +1 -1
- package/dist/esm/types/viz-elm-callout.d.ts +26 -1
- package/dist/esm/types/viz-elm-callout.d.ts.map +1 -1
- package/dist/esm/types/viz-scrollmap.d.ts +8 -2
- package/dist/esm/types/viz-scrollmap.d.ts.map +1 -1
- package/dist/esm/utils/dom.d.ts +1 -1
- package/dist/esm/utils/dom.d.ts.map +1 -1
- package/dist/esm/utils/retry.d.ts +1 -0
- package/dist/esm/utils/retry.d.ts.map +1 -1
- package/dist/umd/components/Layout/HeatmapLayout.d.ts +2 -0
- package/dist/umd/components/Layout/HeatmapLayout.d.ts.map +1 -1
- package/dist/umd/components/VizDom/VizDomHeatmap.d.ts.map +1 -1
- package/dist/umd/components/VizDom/VizDomRenderer.d.ts +2 -1
- package/dist/umd/components/VizDom/VizDomRenderer.d.ts.map +1 -1
- package/dist/umd/components/VizElement/ElementCallout.d.ts +2 -2
- package/dist/umd/components/VizElement/ElementCallout.d.ts.map +1 -1
- package/dist/umd/components/VizElement/ElementCalloutClicked.d.ts +2 -2
- package/dist/umd/components/VizElement/ElementCalloutClicked.d.ts.map +1 -1
- package/dist/umd/components/VizElement/ElementCalloutHovered.d.ts +2 -1
- package/dist/umd/components/VizElement/ElementCalloutHovered.d.ts.map +1 -1
- package/dist/umd/components/VizElement/ElementMissing.d.ts +2 -1
- package/dist/umd/components/VizElement/ElementMissing.d.ts.map +1 -1
- package/dist/umd/components/VizElement/HeatmapElements.d.ts +4 -4
- package/dist/umd/components/VizElement/HeatmapElements.d.ts.map +1 -1
- package/dist/umd/components/VizElement/VizElements.d.ts +4 -3
- package/dist/umd/components/VizElement/VizElements.d.ts.map +1 -1
- package/dist/umd/components/VizLive/VizLiveHeatmap.d.ts.map +1 -1
- package/dist/umd/components/VizScrollmap/HoverZones.d.ts +3 -3
- package/dist/umd/components/VizScrollmap/HoverZones.d.ts.map +1 -1
- package/dist/umd/components/VizScrollmap/Minimap.d.ts +3 -3
- package/dist/umd/components/VizScrollmap/Minimap.d.ts.map +1 -1
- package/dist/umd/helpers/canvas-backdrop.d.ts.map +1 -1
- package/dist/umd/helpers/viewport/element.d.ts +2 -2
- package/dist/umd/helpers/viewport/element.d.ts.map +1 -1
- package/dist/umd/helpers/viz-elm-callout/viz-elm.d.ts +2 -1
- package/dist/umd/helpers/viz-elm-callout/viz-elm.d.ts.map +1 -1
- package/dist/umd/hooks/common/index.d.ts +1 -1
- package/dist/umd/hooks/common/index.d.ts.map +1 -1
- package/dist/umd/hooks/common/useHeatmapViewportByDevice.d.ts +7 -0
- package/dist/umd/hooks/common/useHeatmapViewportByDevice.d.ts.map +1 -0
- package/dist/umd/hooks/register/useRegisterConfig.d.ts +3 -1
- package/dist/umd/hooks/register/useRegisterConfig.d.ts.map +1 -1
- package/dist/umd/hooks/view-context/index.d.ts +1 -0
- package/dist/umd/hooks/view-context/index.d.ts.map +1 -1
- package/dist/umd/hooks/view-context/useHeatmapCopyView.d.ts.map +1 -1
- package/dist/umd/hooks/view-context/useHeatmapDataContext.d.ts +4 -16
- package/dist/umd/hooks/view-context/useHeatmapDataContext.d.ts.map +1 -1
- package/dist/umd/hooks/view-context/useHeatmapLiveContext.d.ts +34 -0
- package/dist/umd/hooks/view-context/useHeatmapLiveContext.d.ts.map +1 -0
- package/dist/umd/hooks/view-context/useHeatmapSettingContext.d.ts +3 -1
- package/dist/umd/hooks/view-context/useHeatmapSettingContext.d.ts.map +1 -1
- package/dist/umd/hooks/view-context/useHeatmapVizContext.d.ts +2 -2
- package/dist/umd/hooks/viz-canvas/useAreaClickmap.d.ts.map +1 -1
- package/dist/umd/hooks/viz-canvas/useClickmap.d.ts.map +1 -1
- package/dist/umd/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
- package/dist/umd/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
- package/dist/umd/hooks/viz-elm/useClickedElement.d.ts +3 -3
- package/dist/umd/hooks/viz-elm/useClickedElement.d.ts.map +1 -1
- package/dist/umd/hooks/viz-elm/useElementCalloutVisible.d.ts +2 -1
- package/dist/umd/hooks/viz-elm/useElementCalloutVisible.d.ts.map +1 -1
- package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts +3 -3
- package/dist/umd/hooks/viz-elm/useHeatmapElementPosition.d.ts.map +1 -1
- package/dist/umd/hooks/viz-elm/useHoveredElement.d.ts +3 -1
- package/dist/umd/hooks/viz-elm/useHoveredElement.d.ts.map +1 -1
- package/dist/umd/hooks/viz-live/useVizLiveIframeMsg.d.ts.map +1 -1
- package/dist/umd/hooks/viz-live/useVizLiveRender.d.ts.map +1 -1
- package/dist/umd/hooks/viz-render/useHeatmapIframeProcessor.d.ts +6 -0
- package/dist/umd/hooks/viz-render/useHeatmapIframeProcessor.d.ts.map +1 -0
- package/dist/umd/hooks/viz-render/useHeatmapRenderByMode.d.ts +3 -2
- package/dist/umd/hooks/viz-render/useHeatmapRenderByMode.d.ts.map +1 -1
- package/dist/umd/hooks/viz-render/useHeatmapRenderDom.d.ts +5 -0
- package/dist/umd/hooks/viz-render/useHeatmapRenderDom.d.ts.map +1 -0
- package/dist/umd/hooks/viz-render/useReplayRender.d.ts +2 -2
- package/dist/umd/hooks/viz-render/useReplayRender.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
- package/dist/umd/hooks/viz-scale/useScaleCalculation.d.ts +1 -2
- package/dist/umd/hooks/viz-scale/useScaleCalculation.d.ts.map +1 -1
- package/dist/umd/index.d.ts +1 -1
- package/dist/umd/index.d.ts.map +1 -1
- package/dist/umd/index.js +2 -2
- package/dist/umd/libs/iframe-processor/index.d.ts +2 -5
- package/dist/umd/libs/iframe-processor/index.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/lifecycle.d.ts +16 -7
- package/dist/umd/libs/iframe-processor/lifecycle.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/orchestrator.d.ts +14 -45
- package/dist/umd/libs/iframe-processor/orchestrator.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/height-observer/index.d.ts +5 -14
- package/dist/umd/libs/iframe-processor/processors/height-observer/index.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/height-observer/listeners.d.ts +11 -0
- package/dist/umd/libs/iframe-processor/processors/height-observer/listeners.d.ts.map +1 -0
- package/dist/umd/libs/iframe-processor/processors/height-observer/types.d.ts +18 -1
- package/dist/umd/libs/iframe-processor/processors/height-observer/types.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/navigation/index.d.ts +3 -14
- package/dist/umd/libs/iframe-processor/processors/navigation/index.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/navigation/listeners.d.ts +3 -4
- package/dist/umd/libs/iframe-processor/processors/navigation/listeners.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/navigation/types.d.ts +13 -0
- package/dist/umd/libs/iframe-processor/processors/navigation/types.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/dimensions.d.ts +1 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/dimensions.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/global-fixes/viewport-unit-replacer/fixes.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/gp-v7-fixes/gem-slider-item/fixes.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts +0 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/index.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/global-fixes/types.d.ts +2 -2
- package/dist/umd/libs/iframe-processor/processors/viewport/index.d.ts +3 -9
- package/dist/umd/libs/iframe-processor/processors/viewport/index.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/listeners.d.ts +3 -2
- package/dist/umd/libs/iframe-processor/processors/viewport/listeners.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/pipeline.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/shop-overrides/types.d.ts +2 -0
- package/dist/umd/libs/iframe-processor/processors/viewport/shop-overrides/types.d.ts.map +1 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/constants.d.ts +5 -0
- package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/constants.d.ts.map +1 -0
- package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/functions.d.ts +11 -0
- package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/functions.d.ts.map +1 -0
- package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/index.d.ts +9 -0
- package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/index.d.ts.map +1 -0
- package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/state.d.ts +13 -0
- package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/state.d.ts.map +1 -0
- package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/types.d.ts +16 -0
- package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer/types.d.ts.map +1 -0
- package/dist/umd/libs/iframe-processor/processors/viewport/types.d.ts +8 -0
- package/dist/umd/libs/iframe-processor/processors/viewport/types.d.ts.map +1 -0
- package/dist/umd/libs/iframe-processor/shared/factory-types.d.ts +24 -0
- package/dist/umd/libs/iframe-processor/shared/factory-types.d.ts.map +1 -0
- package/dist/umd/libs/iframe-processor/shared/iframe-types.d.ts +22 -0
- package/dist/umd/libs/iframe-processor/shared/iframe-types.d.ts.map +1 -0
- package/dist/umd/libs/index.d.ts +1 -0
- package/dist/umd/libs/index.d.ts.map +1 -1
- package/dist/umd/libs/perf.d.ts +83 -0
- package/dist/umd/libs/perf.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/GXVisualizer.d.ts +23 -4
- package/dist/umd/libs/visualizer/GXVisualizer.d.ts.map +1 -1
- package/dist/umd/libs/visualizer/cache/config.d.ts +15 -0
- package/dist/umd/libs/visualizer/cache/config.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/cache/index.d.ts +16 -0
- package/dist/umd/libs/visualizer/cache/index.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/index.d.ts +2 -2
- package/dist/umd/libs/visualizer/index.d.ts.map +1 -1
- package/dist/{esm/libs/visualizer → umd/libs/visualizer/renderers}/AttentionMapRenderer.d.ts +2 -2
- package/dist/umd/libs/visualizer/renderers/AttentionMapRenderer.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/renderers/ScrollBucketRenderer.d.ts +44 -0
- package/dist/umd/libs/visualizer/renderers/ScrollBucketRenderer.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/renderers/index.d.ts +3 -0
- package/dist/umd/libs/visualizer/renderers/index.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/shadow-dom/extractor.d.ts +23 -0
- package/dist/umd/libs/visualizer/shadow-dom/extractor.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/types/data.d.ts +72 -0
- package/dist/umd/libs/visualizer/types/data.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/{types.d.ts → types/extend.d.ts} +7 -2
- package/dist/umd/libs/visualizer/types/extend.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/types/index.d.ts +6 -0
- package/dist/umd/libs/visualizer/types/index.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/types/layout.d.ts +69 -0
- package/dist/umd/libs/visualizer/types/layout.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/types/visualize.d.ts +26 -0
- package/dist/umd/libs/visualizer/types/visualize.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/utils/delay.d.ts +21 -0
- package/dist/umd/libs/visualizer/utils/delay.d.ts.map +1 -0
- package/dist/umd/libs/visualizer/utils/render.d.ts +7 -0
- package/dist/umd/libs/visualizer/utils/render.d.ts.map +1 -0
- package/dist/umd/stores/config.d.ts +4 -2
- package/dist/umd/stores/config.d.ts.map +1 -1
- package/dist/umd/stores/data.d.ts +4 -0
- package/dist/umd/stores/data.d.ts.map +1 -1
- package/dist/umd/stores/mode-live.d.ts +30 -16
- package/dist/umd/stores/mode-live.d.ts.map +1 -1
- package/dist/umd/stores/setting.d.ts +3 -1
- package/dist/umd/stores/setting.d.ts.map +1 -1
- package/dist/umd/stores/viz.d.ts +2 -2
- package/dist/umd/types/clarity.d.ts +18 -0
- package/dist/umd/types/clarity.d.ts.map +1 -1
- package/dist/umd/types/control.d.ts +2 -0
- package/dist/umd/types/control.d.ts.map +1 -1
- package/dist/umd/types/heatmap-info.d.ts +1 -0
- package/dist/umd/types/heatmap-info.d.ts.map +1 -1
- package/dist/umd/types/heatmap.d.ts +9 -0
- package/dist/umd/types/heatmap.d.ts.map +1 -1
- package/dist/umd/types/iframe-helper.d.ts +0 -19
- package/dist/umd/types/iframe-helper.d.ts.map +1 -1
- package/dist/umd/types/viz-elm-callout.d.ts +26 -1
- package/dist/umd/types/viz-elm-callout.d.ts.map +1 -1
- package/dist/umd/types/viz-scrollmap.d.ts +8 -2
- package/dist/umd/types/viz-scrollmap.d.ts.map +1 -1
- package/dist/umd/utils/dom.d.ts +1 -1
- package/dist/umd/utils/dom.d.ts.map +1 -1
- package/dist/umd/utils/retry.d.ts +1 -0
- package/dist/umd/utils/retry.d.ts.map +1 -1
- package/package.json +4 -4
- package/dist/esm/hooks/common/useHeatmapWidthByDevice.d.ts +0 -2
- package/dist/esm/hooks/common/useHeatmapWidthByDevice.d.ts.map +0 -1
- package/dist/esm/hooks/viz-canvas/useAreamap.d.ts +0 -14
- package/dist/esm/hooks/viz-canvas/useAreamap.d.ts.map +0 -1
- package/dist/esm/hooks/viz-canvas/useAttentionMap.d.ts +0 -9
- package/dist/esm/hooks/viz-canvas/useAttentionMap.d.ts.map +0 -1
- package/dist/esm/hooks/viz-render/useHeatmapRender.d.ts +0 -6
- package/dist/esm/hooks/viz-render/useHeatmapRender.d.ts.map +0 -1
- package/dist/esm/hooks/viz-scale/useContentDimensions.d.ts +0 -9
- package/dist/esm/hooks/viz-scale/useContentDimensions.d.ts.map +0 -1
- package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer.d.ts +0 -54
- package/dist/esm/libs/iframe-processor/processors/viewport/style-enforcer.d.ts.map +0 -1
- package/dist/esm/libs/visualizer/AttentionMapRenderer.d.ts.map +0 -1
- package/dist/esm/libs/visualizer/ClickHeatmapRenderer.d.ts +0 -30
- package/dist/esm/libs/visualizer/ClickHeatmapRenderer.d.ts.map +0 -1
- package/dist/esm/libs/visualizer/ScrollHeatmapRenderer.d.ts +0 -17
- package/dist/esm/libs/visualizer/ScrollHeatmapRenderer.d.ts.map +0 -1
- package/dist/esm/libs/visualizer/types.d.ts.map +0 -1
- package/dist/umd/hooks/common/useHeatmapWidthByDevice.d.ts +0 -2
- package/dist/umd/hooks/common/useHeatmapWidthByDevice.d.ts.map +0 -1
- package/dist/umd/hooks/viz-canvas/useAreamap.d.ts +0 -14
- package/dist/umd/hooks/viz-canvas/useAreamap.d.ts.map +0 -1
- package/dist/umd/hooks/viz-canvas/useAttentionMap.d.ts +0 -9
- package/dist/umd/hooks/viz-canvas/useAttentionMap.d.ts.map +0 -1
- package/dist/umd/hooks/viz-render/useHeatmapRender.d.ts +0 -6
- package/dist/umd/hooks/viz-render/useHeatmapRender.d.ts.map +0 -1
- package/dist/umd/hooks/viz-scale/useContentDimensions.d.ts +0 -9
- package/dist/umd/hooks/viz-scale/useContentDimensions.d.ts.map +0 -1
- package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer.d.ts +0 -54
- package/dist/umd/libs/iframe-processor/processors/viewport/style-enforcer.d.ts.map +0 -1
- package/dist/umd/libs/visualizer/AttentionMapRenderer.d.ts.map +0 -1
- package/dist/umd/libs/visualizer/ClickHeatmapRenderer.d.ts +0 -30
- package/dist/umd/libs/visualizer/ClickHeatmapRenderer.d.ts.map +0 -1
- package/dist/umd/libs/visualizer/ScrollHeatmapRenderer.d.ts +0 -17
- package/dist/umd/libs/visualizer/ScrollHeatmapRenderer.d.ts.map +0 -1
- package/dist/umd/libs/visualizer/types.d.ts.map +0 -1
package/dist/esm/index.mjs
CHANGED
|
@@ -152,6 +152,7 @@ function useDebounceCallback(callback, delay) {
|
|
|
152
152
|
|
|
153
153
|
var EDeviceType;
|
|
154
154
|
(function (EDeviceType) {
|
|
155
|
+
EDeviceType["DesktopLarge"] = "DESKTOP_LARGE";
|
|
155
156
|
EDeviceType["Desktop"] = "DESKTOP";
|
|
156
157
|
EDeviceType["Mobile"] = "MOBILE";
|
|
157
158
|
EDeviceType["Tablet"] = "TABLET";
|
|
@@ -161,6 +162,11 @@ var EHeatmapType;
|
|
|
161
162
|
EHeatmapType["Click"] = "click";
|
|
162
163
|
EHeatmapType["Scroll"] = "scroll";
|
|
163
164
|
})(EHeatmapType || (EHeatmapType = {}));
|
|
165
|
+
var EHeatmapMode;
|
|
166
|
+
(function (EHeatmapMode) {
|
|
167
|
+
EHeatmapMode["Heatmap"] = "heatmap";
|
|
168
|
+
EHeatmapMode["Replay"] = "replay";
|
|
169
|
+
})(EHeatmapMode || (EHeatmapMode = {}));
|
|
164
170
|
var EClickType;
|
|
165
171
|
(function (EClickType) {
|
|
166
172
|
/** Return all clicks (default) */
|
|
@@ -197,12 +203,16 @@ var EScrollType;
|
|
|
197
203
|
EScrollType["Attention"] = "ATTENTION";
|
|
198
204
|
EScrollType["Revenue"] = "REVENUE";
|
|
199
205
|
})(EScrollType || (EScrollType = {}));
|
|
200
|
-
var EHeatmapMode;
|
|
201
206
|
(function (EHeatmapMode) {
|
|
202
207
|
EHeatmapMode["Single"] = "single";
|
|
203
208
|
EHeatmapMode["Live"] = "live";
|
|
204
209
|
EHeatmapMode["Compare"] = "compare";
|
|
205
210
|
})(EHeatmapMode || (EHeatmapMode = {}));
|
|
211
|
+
var EHeatmapDataSource;
|
|
212
|
+
(function (EHeatmapDataSource) {
|
|
213
|
+
EHeatmapDataSource["Snapshot"] = "snapshot";
|
|
214
|
+
EHeatmapDataSource["Live"] = "live";
|
|
215
|
+
})(EHeatmapDataSource || (EHeatmapDataSource = {}));
|
|
206
216
|
|
|
207
217
|
const ViewIdContext = createContext(undefined);
|
|
208
218
|
const useViewIdContext = () => {
|
|
@@ -314,18 +324,21 @@ const useHeatmapConfigStore = create()((set) => {
|
|
|
314
324
|
return {
|
|
315
325
|
mode: EHeatmapMode.Single,
|
|
316
326
|
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
|
|
317
|
-
|
|
318
|
-
|
|
327
|
+
shopId: undefined,
|
|
328
|
+
excludeClassNames: [],
|
|
319
329
|
setMode: (mode) => set({ mode }),
|
|
320
330
|
resetMode: () => set({ mode: EHeatmapMode.Single }),
|
|
321
331
|
setSidebarWidth: (sidebarWidth) => set({ sidebarWidth }),
|
|
322
|
-
|
|
332
|
+
setShopId: (shopId) => set({ shopId }),
|
|
333
|
+
setExcludeClassNames: (excludeClassNames) => set({ excludeClassNames }),
|
|
323
334
|
};
|
|
324
335
|
});
|
|
325
336
|
|
|
326
337
|
const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
|
|
327
338
|
return {
|
|
328
339
|
data: new Map([[DEFAULT_VIEW_ID, undefined]]),
|
|
340
|
+
dataHash: new Map([[DEFAULT_VIEW_ID, undefined]]),
|
|
341
|
+
dataSnapshot: new Map([[DEFAULT_VIEW_ID, undefined]]),
|
|
329
342
|
clickmap: new Map([[DEFAULT_VIEW_ID, undefined]]),
|
|
330
343
|
clickAreas: new Map([[DEFAULT_VIEW_ID, undefined]]),
|
|
331
344
|
dataInfo: new Map([[DEFAULT_VIEW_ID, undefined]]),
|
|
@@ -358,6 +371,16 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
|
|
|
358
371
|
newData.set(viewId, data);
|
|
359
372
|
return { data: newData };
|
|
360
373
|
}),
|
|
374
|
+
setDataHash: (dataHash, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
375
|
+
const newDataHash = new Map(prev.dataHash);
|
|
376
|
+
newDataHash.set(viewId, dataHash);
|
|
377
|
+
return { dataHash: newDataHash };
|
|
378
|
+
}),
|
|
379
|
+
setDataSnapshot: (data, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
380
|
+
const newDataSnapshot = new Map(prev.dataSnapshot);
|
|
381
|
+
newDataSnapshot.set(viewId, data);
|
|
382
|
+
return { dataSnapshot: newDataSnapshot };
|
|
383
|
+
}),
|
|
361
384
|
setClickmap: (clickmap, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
362
385
|
const newClickmap = new Map(prev.clickmap);
|
|
363
386
|
newClickmap.set(viewId, clickmap);
|
|
@@ -380,12 +403,16 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
|
|
|
380
403
|
}),
|
|
381
404
|
copyView: (fromViewId, toViewId) => set((prev) => {
|
|
382
405
|
const newData = new Map(prev.data);
|
|
406
|
+
const newDataSnapshot = new Map(prev.dataSnapshot);
|
|
383
407
|
const newClickmap = new Map(prev.clickmap);
|
|
384
408
|
const newClickAreas = new Map(prev.clickAreas);
|
|
385
409
|
const newDataInfo = new Map(prev.dataInfo);
|
|
386
410
|
const newScrollmap = new Map(prev.scrollmap);
|
|
387
411
|
const newAttentionMap = new Map(prev.attentionMap);
|
|
412
|
+
const newDataHash = new Map(prev.dataHash);
|
|
388
413
|
newData.set(toViewId, prev.data.get(fromViewId));
|
|
414
|
+
newDataSnapshot.set(toViewId, prev.dataSnapshot.get(fromViewId));
|
|
415
|
+
newDataHash.set(toViewId, prev.dataHash.get(fromViewId));
|
|
389
416
|
newClickmap.set(toViewId, prev.clickmap.get(fromViewId));
|
|
390
417
|
newClickAreas.set(toViewId, prev.clickAreas.get(fromViewId));
|
|
391
418
|
newDataInfo.set(toViewId, prev.dataInfo.get(fromViewId));
|
|
@@ -393,21 +420,27 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
|
|
|
393
420
|
newAttentionMap.set(toViewId, prev.attentionMap.get(fromViewId));
|
|
394
421
|
return {
|
|
395
422
|
data: newData,
|
|
423
|
+
dataSnapshot: newDataSnapshot,
|
|
396
424
|
clickmap: newClickmap,
|
|
397
425
|
clickAreas: newClickAreas,
|
|
398
426
|
dataInfo: newDataInfo,
|
|
399
427
|
scrollmap: newScrollmap,
|
|
400
428
|
attentionMap: newAttentionMap,
|
|
429
|
+
dataHash: newDataHash,
|
|
401
430
|
};
|
|
402
431
|
}),
|
|
403
432
|
clearView: (viewId) => set((prev) => {
|
|
404
433
|
const newData = new Map(prev.data);
|
|
434
|
+
const newDataSnapshot = new Map(prev.dataSnapshot);
|
|
405
435
|
const newClickmap = new Map(prev.clickmap);
|
|
406
436
|
const newClickAreas = new Map(prev.clickAreas);
|
|
407
437
|
const newDataInfo = new Map(prev.dataInfo);
|
|
408
438
|
const newScrollmap = new Map(prev.scrollmap);
|
|
409
439
|
const newAttentionMap = new Map(prev.attentionMap);
|
|
440
|
+
const newDataHash = new Map(prev.dataHash);
|
|
410
441
|
newData.delete(viewId);
|
|
442
|
+
newDataSnapshot.delete(viewId);
|
|
443
|
+
newDataHash.delete(viewId);
|
|
411
444
|
newClickmap.delete(viewId);
|
|
412
445
|
newClickAreas.delete(viewId);
|
|
413
446
|
newDataInfo.delete(viewId);
|
|
@@ -415,6 +448,8 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
|
|
|
415
448
|
newAttentionMap.delete(viewId);
|
|
416
449
|
return {
|
|
417
450
|
data: newData,
|
|
451
|
+
dataSnapshot: newDataSnapshot,
|
|
452
|
+
dataHash: newDataHash,
|
|
418
453
|
clickmap: newClickmap,
|
|
419
454
|
clickAreas: newClickAreas,
|
|
420
455
|
dataInfo: newDataInfo,
|
|
@@ -424,6 +459,8 @@ const useHeatmapDataStore = create()(subscribeWithSelector((set) => {
|
|
|
424
459
|
}),
|
|
425
460
|
resetAll: () => set({
|
|
426
461
|
data: new Map([[DEFAULT_VIEW_ID, undefined]]),
|
|
462
|
+
dataSnapshot: new Map([[DEFAULT_VIEW_ID, undefined]]),
|
|
463
|
+
dataHash: new Map([[DEFAULT_VIEW_ID, undefined]]),
|
|
427
464
|
clickmap: new Map([[DEFAULT_VIEW_ID, undefined]]),
|
|
428
465
|
clickAreas: new Map([[DEFAULT_VIEW_ID, undefined]]),
|
|
429
466
|
dataInfo: new Map([[DEFAULT_VIEW_ID, undefined]]),
|
|
@@ -445,6 +482,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
|
|
|
445
482
|
clickMode: new Map([[DEFAULT_VIEW_ID, EClickMode.Default]]),
|
|
446
483
|
scrollType: new Map([[DEFAULT_VIEW_ID, EScrollType.Depth]]),
|
|
447
484
|
heatmapType: new Map([[DEFAULT_VIEW_ID, EHeatmapType.Click]]),
|
|
485
|
+
dataSource: new Map([[DEFAULT_VIEW_ID, EHeatmapDataSource.Snapshot]]),
|
|
448
486
|
setIsRendering: (isRendering, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
449
487
|
const newIsRendering = new Map(prev.isRendering);
|
|
450
488
|
newIsRendering.set(viewId, isRendering);
|
|
@@ -495,6 +533,11 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
|
|
|
495
533
|
newHeatmapType.set(viewId, heatmapType);
|
|
496
534
|
return { heatmapType: newHeatmapType };
|
|
497
535
|
}),
|
|
536
|
+
setDataSource: (dataSource, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
537
|
+
const newDataSource = new Map(prev.dataSource);
|
|
538
|
+
newDataSource.set(viewId, dataSource);
|
|
539
|
+
return { dataSource: newDataSource };
|
|
540
|
+
}),
|
|
498
541
|
copyView: (fromViewId, toViewId) => set((prev) => {
|
|
499
542
|
const newIsLoadingDom = new Map(prev.isLoadingDom);
|
|
500
543
|
const newIsLoadingCanvas = new Map(prev.isLoadingCanvas);
|
|
@@ -505,6 +548,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
|
|
|
505
548
|
const newClickMode = new Map(prev.clickMode);
|
|
506
549
|
const newScrollType = new Map(prev.scrollType);
|
|
507
550
|
const newHeatmapType = new Map(prev.heatmapType);
|
|
551
|
+
const newDataSource = new Map(prev.dataSource);
|
|
508
552
|
newIsShowSidebar.set(toViewId, prev.isShowSidebar.get(fromViewId) ?? false);
|
|
509
553
|
newRankedBy.set(toViewId, prev.rankedBy.get(fromViewId));
|
|
510
554
|
newDeviceType.set(toViewId, prev.deviceType.get(fromViewId));
|
|
@@ -514,6 +558,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
|
|
|
514
558
|
newHeatmapType.set(toViewId, prev.heatmapType.get(fromViewId));
|
|
515
559
|
newIsLoadingDom.set(toViewId, prev.isLoadingDom.get(fromViewId) ?? false);
|
|
516
560
|
newIsLoadingCanvas.set(toViewId, prev.isLoadingCanvas.get(fromViewId) ?? false);
|
|
561
|
+
newDataSource.set(toViewId, prev.dataSource.get(fromViewId) ?? EHeatmapDataSource.Snapshot);
|
|
517
562
|
return {
|
|
518
563
|
isShowSidebar: newIsShowSidebar,
|
|
519
564
|
rankedBy: newRankedBy,
|
|
@@ -524,6 +569,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
|
|
|
524
569
|
clickMode: newClickMode,
|
|
525
570
|
scrollType: newScrollType,
|
|
526
571
|
heatmapType: newHeatmapType,
|
|
572
|
+
dataSource: newDataSource,
|
|
527
573
|
};
|
|
528
574
|
}),
|
|
529
575
|
clearView: (viewId) => set((prev) => {
|
|
@@ -536,6 +582,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
|
|
|
536
582
|
const newClickMode = new Map(prev.clickMode);
|
|
537
583
|
const newScrollType = new Map(prev.scrollType);
|
|
538
584
|
const newHeatmapType = new Map(prev.heatmapType);
|
|
585
|
+
const newDataSource = new Map(prev.dataSource);
|
|
539
586
|
newIsShowSidebar.delete(viewId);
|
|
540
587
|
newRankedBy.delete(viewId);
|
|
541
588
|
newIsLoadingDom.delete(viewId);
|
|
@@ -545,6 +592,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
|
|
|
545
592
|
newClickMode.delete(viewId);
|
|
546
593
|
newScrollType.delete(viewId);
|
|
547
594
|
newHeatmapType.delete(viewId);
|
|
595
|
+
newDataSource.delete(viewId);
|
|
548
596
|
return {
|
|
549
597
|
isShowSidebar: newIsShowSidebar,
|
|
550
598
|
rankedBy: newRankedBy,
|
|
@@ -555,6 +603,7 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
|
|
|
555
603
|
clickMode: newClickMode,
|
|
556
604
|
scrollType: newScrollType,
|
|
557
605
|
heatmapType: newHeatmapType,
|
|
606
|
+
dataSource: newDataSource,
|
|
558
607
|
};
|
|
559
608
|
}),
|
|
560
609
|
resetAll: () => set({
|
|
@@ -568,22 +617,23 @@ const useHeatmapSettingStore = create()(subscribeWithSelector((set) => {
|
|
|
568
617
|
clickMode: new Map([[DEFAULT_VIEW_ID, EClickMode.Default]]),
|
|
569
618
|
scrollType: new Map([[DEFAULT_VIEW_ID, EScrollType.Depth]]),
|
|
570
619
|
heatmapType: new Map([[DEFAULT_VIEW_ID, EHeatmapType.Click]]),
|
|
620
|
+
dataSource: new Map([[DEFAULT_VIEW_ID, EHeatmapDataSource.Snapshot]]),
|
|
571
621
|
}),
|
|
572
622
|
};
|
|
573
623
|
}));
|
|
574
624
|
|
|
575
625
|
const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
|
|
576
626
|
return {
|
|
577
|
-
|
|
627
|
+
isDomLoaded: new Map([[DEFAULT_VIEW_ID, false]]),
|
|
578
628
|
zoomRatio: new Map([[DEFAULT_VIEW_ID, DEFAULT_ZOOM_RATIO.DEFAULT]]),
|
|
579
629
|
minZoomRatio: new Map([[DEFAULT_VIEW_ID, DEFAULT_ZOOM_RATIO.MIN]]),
|
|
580
630
|
maxZoomRatio: new Map([[DEFAULT_VIEW_ID, DEFAULT_ZOOM_RATIO.MAX]]),
|
|
581
631
|
scale: new Map([[DEFAULT_VIEW_ID, 1]]),
|
|
582
632
|
isScaledToFit: new Map([[DEFAULT_VIEW_ID, false]]),
|
|
583
|
-
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
return {
|
|
633
|
+
setIsDomLoaded: (isDomLoaded, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
634
|
+
const newIsDomLoaded = new Map(prev.isDomLoaded);
|
|
635
|
+
newIsDomLoaded.set(viewId, isDomLoaded);
|
|
636
|
+
return { isDomLoaded: newIsDomLoaded };
|
|
587
637
|
}),
|
|
588
638
|
setZoomRatio: (zoomRatio, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
589
639
|
const newZoomRatio = new Map(prev.zoomRatio);
|
|
@@ -611,18 +661,18 @@ const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
|
|
|
611
661
|
return { isScaledToFit: newIsScaledToFit };
|
|
612
662
|
}),
|
|
613
663
|
copyView: (fromViewId, toViewId) => set((prev) => {
|
|
614
|
-
const
|
|
664
|
+
const newIsDomLoaded = new Map(prev.isDomLoaded);
|
|
615
665
|
const newZoomRatio = new Map(prev.zoomRatio);
|
|
616
666
|
const newMinZoomRatio = new Map(prev.minZoomRatio);
|
|
617
667
|
const newScale = new Map(prev.scale);
|
|
618
668
|
const newIsScaledToFit = new Map(prev.isScaledToFit);
|
|
619
|
-
|
|
669
|
+
newIsDomLoaded.set(toViewId, prev.isDomLoaded.get(fromViewId) ?? false);
|
|
620
670
|
newZoomRatio.set(toViewId, prev.zoomRatio.get(fromViewId) ?? 100);
|
|
621
671
|
newMinZoomRatio.set(toViewId, prev.minZoomRatio.get(fromViewId) ?? 10);
|
|
622
672
|
newScale.set(toViewId, prev.scale.get(fromViewId) ?? 1);
|
|
623
673
|
newIsScaledToFit.set(toViewId, prev.isScaledToFit.get(fromViewId) ?? false);
|
|
624
674
|
return {
|
|
625
|
-
|
|
675
|
+
isDomLoaded: newIsDomLoaded,
|
|
626
676
|
zoomRatio: newZoomRatio,
|
|
627
677
|
minZoomRatio: newMinZoomRatio,
|
|
628
678
|
scale: newScale,
|
|
@@ -630,18 +680,18 @@ const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
|
|
|
630
680
|
};
|
|
631
681
|
}),
|
|
632
682
|
clearView: (viewId) => set((prev) => {
|
|
633
|
-
const
|
|
683
|
+
const newIsDomLoaded = new Map(prev.isDomLoaded);
|
|
634
684
|
const newZoomRatio = new Map(prev.zoomRatio);
|
|
635
685
|
const newMinZoomRatio = new Map(prev.minZoomRatio);
|
|
636
686
|
const newScale = new Map(prev.scale);
|
|
637
687
|
const newIsScaledToFit = new Map(prev.isScaledToFit);
|
|
638
|
-
|
|
688
|
+
newIsDomLoaded.delete(viewId);
|
|
639
689
|
newZoomRatio.delete(viewId);
|
|
640
690
|
newMinZoomRatio.delete(viewId);
|
|
641
691
|
newScale.delete(viewId);
|
|
642
692
|
newIsScaledToFit.delete(viewId);
|
|
643
693
|
return {
|
|
644
|
-
|
|
694
|
+
isDomLoaded: newIsDomLoaded,
|
|
645
695
|
zoomRatio: newZoomRatio,
|
|
646
696
|
minZoomRatio: newMinZoomRatio,
|
|
647
697
|
scale: newScale,
|
|
@@ -649,7 +699,7 @@ const useHeatmapVizStore = create()(subscribeWithSelector((set) => {
|
|
|
649
699
|
};
|
|
650
700
|
}),
|
|
651
701
|
resetAll: () => set({
|
|
652
|
-
|
|
702
|
+
isDomLoaded: new Map([[DEFAULT_VIEW_ID, false]]),
|
|
653
703
|
zoomRatio: new Map([[DEFAULT_VIEW_ID, 100]]),
|
|
654
704
|
minZoomRatio: new Map([[DEFAULT_VIEW_ID, 10]]),
|
|
655
705
|
scale: new Map([[DEFAULT_VIEW_ID, 1]]),
|
|
@@ -1047,24 +1097,108 @@ const useHeatmapCompareStore = create()((set, get) => {
|
|
|
1047
1097
|
});
|
|
1048
1098
|
|
|
1049
1099
|
const initialState = {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1100
|
+
decodedPayloads: new Map([[DEFAULT_VIEW_ID, []]]),
|
|
1101
|
+
encodedPayloads: new Map([[DEFAULT_VIEW_ID, []]]),
|
|
1102
|
+
htmlContent: new Map([[DEFAULT_VIEW_ID, '']]),
|
|
1103
|
+
targetUrl: new Map([[DEFAULT_VIEW_ID, '']]),
|
|
1104
|
+
renderMode: new Map([[DEFAULT_VIEW_ID, 'portal']]),
|
|
1105
|
+
storefrontPassword: new Map([[DEFAULT_VIEW_ID, '']]),
|
|
1106
|
+
};
|
|
1107
|
+
const useHeatmapLiveStore = create()(subscribeWithSelector((set) => ({
|
|
1108
|
+
...initialState,
|
|
1109
|
+
addPayload: (payload, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
1110
|
+
const newDecoded = new Map(prev.decodedPayloads);
|
|
1111
|
+
newDecoded.set(viewId, [...(newDecoded.get(viewId) ?? []), payload]);
|
|
1112
|
+
const newEncoded = new Map(prev.encodedPayloads);
|
|
1113
|
+
newEncoded.set(viewId, [...(newEncoded.get(viewId) ?? []), JSON.stringify(payload)]);
|
|
1114
|
+
return { decodedPayloads: newDecoded, encodedPayloads: newEncoded };
|
|
1115
|
+
}),
|
|
1116
|
+
setPayloads: (payloads, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
1117
|
+
const newDecoded = new Map(prev.decodedPayloads);
|
|
1118
|
+
newDecoded.set(viewId, payloads);
|
|
1119
|
+
const newEncoded = new Map(prev.encodedPayloads);
|
|
1120
|
+
newEncoded.set(viewId, payloads.map((p) => JSON.stringify(p)));
|
|
1121
|
+
return { decodedPayloads: newDecoded, encodedPayloads: newEncoded };
|
|
1122
|
+
}),
|
|
1123
|
+
setEncodedPayloads: (payloads, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
1124
|
+
const newEncoded = new Map(prev.encodedPayloads);
|
|
1125
|
+
newEncoded.set(viewId, payloads);
|
|
1126
|
+
return { encodedPayloads: newEncoded };
|
|
1127
|
+
}),
|
|
1128
|
+
setHtmlContent: (htmlContent, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
1129
|
+
const newHtmlContent = new Map(prev.htmlContent);
|
|
1130
|
+
newHtmlContent.set(viewId, htmlContent);
|
|
1131
|
+
return { htmlContent: newHtmlContent };
|
|
1132
|
+
}),
|
|
1133
|
+
setTargetUrl: (targetUrl, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
1134
|
+
const newTargetUrl = new Map(prev.targetUrl);
|
|
1135
|
+
newTargetUrl.set(viewId, targetUrl);
|
|
1136
|
+
return { targetUrl: newTargetUrl };
|
|
1137
|
+
}),
|
|
1138
|
+
setRenderMode: (renderMode, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
1139
|
+
const newRenderMode = new Map(prev.renderMode);
|
|
1140
|
+
newRenderMode.set(viewId, renderMode);
|
|
1141
|
+
return { renderMode: newRenderMode };
|
|
1142
|
+
}),
|
|
1143
|
+
setStorefrontPassword: (storefrontPassword, viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
1144
|
+
const newStorefrontPassword = new Map(prev.storefrontPassword);
|
|
1145
|
+
newStorefrontPassword.set(viewId, storefrontPassword);
|
|
1146
|
+
return { storefrontPassword: newStorefrontPassword };
|
|
1147
|
+
}),
|
|
1148
|
+
resetView: (viewId = DEFAULT_VIEW_ID) => set((prev) => {
|
|
1149
|
+
const newDecoded = new Map(prev.decodedPayloads);
|
|
1150
|
+
newDecoded.set(viewId, []);
|
|
1151
|
+
const newEncoded = new Map(prev.encodedPayloads);
|
|
1152
|
+
newEncoded.set(viewId, []);
|
|
1153
|
+
return { decodedPayloads: newDecoded, encodedPayloads: newEncoded };
|
|
1154
|
+
}),
|
|
1155
|
+
copyView: (fromViewId, toViewId) => set((prev) => {
|
|
1156
|
+
const newDecoded = new Map(prev.decodedPayloads);
|
|
1157
|
+
const newEncoded = new Map(prev.encodedPayloads);
|
|
1158
|
+
const newHtmlContent = new Map(prev.htmlContent);
|
|
1159
|
+
const newTargetUrl = new Map(prev.targetUrl);
|
|
1160
|
+
const newRenderMode = new Map(prev.renderMode);
|
|
1161
|
+
const newStorefrontPassword = new Map(prev.storefrontPassword);
|
|
1162
|
+
newDecoded.set(toViewId, prev.decodedPayloads.get(fromViewId) ?? []);
|
|
1163
|
+
newEncoded.set(toViewId, prev.encodedPayloads.get(fromViewId) ?? []);
|
|
1164
|
+
newHtmlContent.set(toViewId, prev.htmlContent.get(fromViewId) ?? '');
|
|
1165
|
+
newTargetUrl.set(toViewId, prev.targetUrl.get(fromViewId) ?? '');
|
|
1166
|
+
newRenderMode.set(toViewId, prev.renderMode.get(fromViewId) ?? 'portal');
|
|
1167
|
+
newStorefrontPassword.set(toViewId, prev.storefrontPassword.get(fromViewId) ?? '');
|
|
1168
|
+
return {
|
|
1169
|
+
decodedPayloads: newDecoded,
|
|
1170
|
+
encodedPayloads: newEncoded,
|
|
1171
|
+
htmlContent: newHtmlContent,
|
|
1172
|
+
targetUrl: newTargetUrl,
|
|
1173
|
+
renderMode: newRenderMode,
|
|
1174
|
+
storefrontPassword: newStorefrontPassword,
|
|
1175
|
+
};
|
|
1176
|
+
}),
|
|
1177
|
+
clearView: (viewId) => set((prev) => {
|
|
1178
|
+
const newDecoded = new Map(prev.decodedPayloads);
|
|
1179
|
+
const newEncoded = new Map(prev.encodedPayloads);
|
|
1180
|
+
const newHtmlContent = new Map(prev.htmlContent);
|
|
1181
|
+
const newTargetUrl = new Map(prev.targetUrl);
|
|
1182
|
+
const newRenderMode = new Map(prev.renderMode);
|
|
1183
|
+
const newStorefrontPassword = new Map(prev.storefrontPassword);
|
|
1184
|
+
newDecoded.delete(viewId);
|
|
1185
|
+
newEncoded.delete(viewId);
|
|
1186
|
+
newHtmlContent.delete(viewId);
|
|
1187
|
+
newTargetUrl.delete(viewId);
|
|
1188
|
+
newRenderMode.delete(viewId);
|
|
1189
|
+
newStorefrontPassword.delete(viewId);
|
|
1190
|
+
return {
|
|
1191
|
+
decodedPayloads: newDecoded,
|
|
1192
|
+
encodedPayloads: newEncoded,
|
|
1193
|
+
htmlContent: newHtmlContent,
|
|
1194
|
+
targetUrl: newTargetUrl,
|
|
1195
|
+
renderMode: newRenderMode,
|
|
1196
|
+
storefrontPassword: newStorefrontPassword,
|
|
1197
|
+
};
|
|
1198
|
+
}),
|
|
1199
|
+
resetAll: () => set(initialState),
|
|
1200
|
+
reset: () => set(initialState),
|
|
1201
|
+
})));
|
|
1068
1202
|
|
|
1069
1203
|
const useHeatmapVizRectStore = create()(subscribeWithSelector((set) => {
|
|
1070
1204
|
return {
|
|
@@ -1170,26 +1304,12 @@ const useHeatmapClickContext = createViewContextHook({
|
|
|
1170
1304
|
}),
|
|
1171
1305
|
});
|
|
1172
1306
|
|
|
1173
|
-
/**
|
|
1174
|
-
* Hook to access heatmap data state and actions with optional selector
|
|
1175
|
-
*
|
|
1176
|
-
* @example
|
|
1177
|
-
* ```tsx
|
|
1178
|
-
* // Get everything
|
|
1179
|
-
* const { data, clickmap, setData } = useHeatmapDataContext();
|
|
1180
|
-
*
|
|
1181
|
-
* // Get only what you need (no unnecessary re-renders)
|
|
1182
|
-
* const data = useHeatmapDataContext(s => s.data);
|
|
1183
|
-
* const { setData, setClickmap } = useHeatmapDataContext(s => ({
|
|
1184
|
-
* setData: s.setData,
|
|
1185
|
-
* setClickmap: s.setClickmap,
|
|
1186
|
-
* }));
|
|
1187
|
-
* ```
|
|
1188
|
-
*/
|
|
1189
1307
|
const useHeatmapDataContext = createViewContextHook({
|
|
1190
1308
|
useStore: useHeatmapDataStore,
|
|
1191
1309
|
getState: (store, viewId) => ({
|
|
1192
1310
|
data: store.data.get(viewId),
|
|
1311
|
+
dataHash: store.dataHash.get(viewId),
|
|
1312
|
+
dataSnapshot: store.dataSnapshot.get(viewId),
|
|
1193
1313
|
clickmap: store.clickmap.get(viewId),
|
|
1194
1314
|
clickAreas: store.clickAreas.get(viewId),
|
|
1195
1315
|
scrollmap: store.scrollmap.get(viewId),
|
|
@@ -1199,6 +1319,8 @@ const useHeatmapDataContext = createViewContextHook({
|
|
|
1199
1319
|
}),
|
|
1200
1320
|
getActions: (store, viewId) => ({
|
|
1201
1321
|
setData: (newData) => store.setData(newData, viewId),
|
|
1322
|
+
setDataSnapshot: (newData) => store.setDataSnapshot(newData, viewId),
|
|
1323
|
+
setDataHash: (newHash) => store.setDataHash(newHash, viewId),
|
|
1202
1324
|
setClickmap: (newClickmap) => store.setClickmap(newClickmap, viewId),
|
|
1203
1325
|
setClickAreas: (newClickAreas) => store.setClickAreas(newClickAreas, viewId),
|
|
1204
1326
|
setDataInfoByKey: (key, value) => store.setDataInfoByKey(key, value, viewId),
|
|
@@ -1221,6 +1343,32 @@ const useHeatmapHoverContext = createViewContextHook({
|
|
|
1221
1343
|
}),
|
|
1222
1344
|
});
|
|
1223
1345
|
|
|
1346
|
+
const useHeatmapLiveContext = createViewContextHook({
|
|
1347
|
+
useStore: useHeatmapLiveStore,
|
|
1348
|
+
getState: (store, viewId) => ({
|
|
1349
|
+
decodedPayloads: store.decodedPayloads.get(viewId) ?? [],
|
|
1350
|
+
encodedPayloads: store.encodedPayloads.get(viewId) ?? [],
|
|
1351
|
+
htmlContent: store.htmlContent.get(viewId) ?? '',
|
|
1352
|
+
targetUrl: store.targetUrl.get(viewId) ?? '',
|
|
1353
|
+
renderMode: store.renderMode.get(viewId) ?? 'portal',
|
|
1354
|
+
storefrontPassword: store.storefrontPassword.get(viewId) ?? '',
|
|
1355
|
+
}),
|
|
1356
|
+
getActions: (store, viewId) => ({
|
|
1357
|
+
addPayload: (payload) => store.addPayload(payload, viewId),
|
|
1358
|
+
setPayloads: (payloads) => store.setPayloads(payloads, viewId),
|
|
1359
|
+
setEncodedPayloads: (payloads) => store.setEncodedPayloads(payloads, viewId),
|
|
1360
|
+
setHtmlContent: (htmlContent) => store.setHtmlContent(htmlContent, viewId),
|
|
1361
|
+
setTargetUrl: (targetUrl) => store.setTargetUrl(targetUrl, viewId),
|
|
1362
|
+
setRenderMode: (mode) => store.setRenderMode(mode, viewId),
|
|
1363
|
+
setStorefrontPassword: (password) => store.setStorefrontPassword(password, viewId),
|
|
1364
|
+
resetView: () => store.resetView(viewId),
|
|
1365
|
+
copyView: (fromViewId, toViewId) => store.copyView(fromViewId, toViewId),
|
|
1366
|
+
clearView: (viewId) => store.clearView(viewId),
|
|
1367
|
+
resetAll: () => store.resetAll(),
|
|
1368
|
+
reset: () => store.reset(),
|
|
1369
|
+
}),
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1224
1372
|
const useHeatmapScrollContext = createViewContextHook({
|
|
1225
1373
|
useStore: useHeatmapVizScrollStore,
|
|
1226
1374
|
getState: (store, viewId) => ({
|
|
@@ -1250,6 +1398,7 @@ const useHeatmapSettingContext = createViewContextHook({
|
|
|
1250
1398
|
clickMode: store.clickMode.get(viewId),
|
|
1251
1399
|
scrollType: store.scrollType.get(viewId),
|
|
1252
1400
|
heatmapType: store.heatmapType.get(viewId),
|
|
1401
|
+
dataSource: store.dataSource.get(viewId) ?? EHeatmapDataSource.Snapshot,
|
|
1253
1402
|
}),
|
|
1254
1403
|
getActions: (store, viewId) => ({
|
|
1255
1404
|
setIsShowSidebar: (isShowSidebar) => store.setIsShowSidebar(isShowSidebar, viewId),
|
|
@@ -1262,6 +1411,7 @@ const useHeatmapSettingContext = createViewContextHook({
|
|
|
1262
1411
|
setIsRendering: (isRendering) => store.setIsRendering(isRendering, viewId),
|
|
1263
1412
|
setIsLoadingDom: (isLoadingDom) => store.setIsLoadingDom(isLoadingDom, viewId),
|
|
1264
1413
|
setIsLoadingCanvas: (isLoadingCanvas) => store.setIsLoadingCanvas(isLoadingCanvas, viewId),
|
|
1414
|
+
setDataSource: (dataSource) => store.setDataSource(dataSource, viewId),
|
|
1265
1415
|
clearView: (viewId) => store.clearView(viewId),
|
|
1266
1416
|
}),
|
|
1267
1417
|
});
|
|
@@ -1269,7 +1419,7 @@ const useHeatmapSettingContext = createViewContextHook({
|
|
|
1269
1419
|
const useHeatmapVizContext = createViewContextHook({
|
|
1270
1420
|
useStore: useHeatmapVizStore,
|
|
1271
1421
|
getState: (store, viewId) => ({
|
|
1272
|
-
|
|
1422
|
+
isDomLoaded: store.isDomLoaded.get(viewId) ?? false,
|
|
1273
1423
|
zoomRatio: store.zoomRatio.get(viewId) ?? DEFAULT_ZOOM_RATIO.DEFAULT,
|
|
1274
1424
|
minZoomRatio: store.minZoomRatio.get(viewId) ?? DEFAULT_ZOOM_RATIO.MIN,
|
|
1275
1425
|
maxZoomRatio: store.maxZoomRatio.get(viewId) ?? DEFAULT_ZOOM_RATIO.MAX,
|
|
@@ -1277,7 +1427,7 @@ const useHeatmapVizContext = createViewContextHook({
|
|
|
1277
1427
|
isScaledToFit: store.isScaledToFit.get(viewId) ?? false,
|
|
1278
1428
|
}),
|
|
1279
1429
|
getActions: (store, viewId) => ({
|
|
1280
|
-
|
|
1430
|
+
setIsDomLoaded: (value) => store.setIsDomLoaded(value, viewId),
|
|
1281
1431
|
setZoomRatio: (value) => store.setZoomRatio(value, viewId),
|
|
1282
1432
|
setMinZoomRatio: (value) => store.setMinZoomRatio(value, viewId),
|
|
1283
1433
|
setMaxZoomRatio: (value) => store.setMaxZoomRatio(value, viewId),
|
|
@@ -1311,9 +1461,7 @@ const useHeatmapCopyView = () => {
|
|
|
1311
1461
|
const copyVizView = useHeatmapVizStore((state) => state.copyView);
|
|
1312
1462
|
const copyVizClickView = useHeatmapVizClickStore((state) => state.copyView);
|
|
1313
1463
|
const copyVizAreaClickView = useHeatmapVizClickAreaStore((state) => state.copyView);
|
|
1314
|
-
|
|
1315
|
-
// const copyVizHoverView = useHeatmapVizHoverStore((state) => state.copyView);
|
|
1316
|
-
// const copyVizScrollView = useHeatmapVizScrollStore((state) => state.copyView);
|
|
1464
|
+
const copyLiveView = useHeatmapLiveStore((state) => state.copyView);
|
|
1317
1465
|
const clearDataView = useHeatmapDataStore((state) => state.clearView);
|
|
1318
1466
|
const clearSettingView = useHeatmapSettingStore((state) => state.clearView);
|
|
1319
1467
|
const clearVizView = useHeatmapVizStore((state) => state.clearView);
|
|
@@ -1322,6 +1470,7 @@ const useHeatmapCopyView = () => {
|
|
|
1322
1470
|
const clearVizHoverView = useHeatmapVizHoverStore((state) => state.clearView);
|
|
1323
1471
|
const clearVizScrollView = useHeatmapVizScrollStore((state) => state.clearView);
|
|
1324
1472
|
const clearVizAreaClickView = useHeatmapVizClickAreaStore((state) => state.clearView);
|
|
1473
|
+
const clearLiveView = useHeatmapLiveStore((state) => state.clearView);
|
|
1325
1474
|
const resetDataAll = useHeatmapDataStore((state) => state.resetAll);
|
|
1326
1475
|
const resetSettingAll = useHeatmapSettingStore((state) => state.resetAll);
|
|
1327
1476
|
const resetVizAll = useHeatmapVizStore((state) => state.resetAll);
|
|
@@ -1330,15 +1479,14 @@ const useHeatmapCopyView = () => {
|
|
|
1330
1479
|
const resetVizHoverAll = useHeatmapVizHoverStore((state) => state.resetAll);
|
|
1331
1480
|
const resetVizScrollViewAll = useHeatmapVizScrollStore((state) => state.resetAll);
|
|
1332
1481
|
const resetVizClickAreaAll = useHeatmapVizClickAreaStore((state) => state.resetAll);
|
|
1482
|
+
const resetLiveAll = useHeatmapLiveStore((state) => state.resetAll);
|
|
1333
1483
|
const copyView = (fromViewId, toViewId) => {
|
|
1334
1484
|
copyDataView(fromViewId, toViewId);
|
|
1335
1485
|
copySettingView(fromViewId, toViewId);
|
|
1336
1486
|
copyVizView(fromViewId, toViewId);
|
|
1337
|
-
// copyVizRectView(fromViewId, toViewId);
|
|
1338
1487
|
copyVizClickView(fromViewId, toViewId);
|
|
1339
|
-
// copyVizHoverView(fromViewId, toViewId);
|
|
1340
|
-
// copyVizScrollView(fromViewId, toViewId);
|
|
1341
1488
|
copyVizAreaClickView(fromViewId, toViewId);
|
|
1489
|
+
copyLiveView(fromViewId, toViewId);
|
|
1342
1490
|
};
|
|
1343
1491
|
const copyViewToMultiple = (fromViewId, toViewIds) => {
|
|
1344
1492
|
toViewIds.forEach((toViewId) => {
|
|
@@ -1354,6 +1502,7 @@ const useHeatmapCopyView = () => {
|
|
|
1354
1502
|
clearVizHoverView(viewId);
|
|
1355
1503
|
clearVizScrollView(viewId);
|
|
1356
1504
|
clearVizAreaClickView(viewId);
|
|
1505
|
+
clearLiveView(viewId);
|
|
1357
1506
|
};
|
|
1358
1507
|
const clearMultipleViews = (viewIds) => {
|
|
1359
1508
|
viewIds.forEach((viewId) => {
|
|
@@ -1369,6 +1518,7 @@ const useHeatmapCopyView = () => {
|
|
|
1369
1518
|
resetVizHoverAll();
|
|
1370
1519
|
resetVizScrollViewAll();
|
|
1371
1520
|
resetVizClickAreaAll();
|
|
1521
|
+
resetLiveAll();
|
|
1372
1522
|
};
|
|
1373
1523
|
return {
|
|
1374
1524
|
copyView,
|
|
@@ -1379,14 +1529,17 @@ const useHeatmapCopyView = () => {
|
|
|
1379
1529
|
};
|
|
1380
1530
|
};
|
|
1381
1531
|
|
|
1382
|
-
const
|
|
1532
|
+
const useHeatmapViewportByDevice = () => {
|
|
1383
1533
|
const deviceType = useHeatmapSettingContext((state) => state.deviceType);
|
|
1384
1534
|
const width = useMemo(() => calcWidthByDeviceType(deviceType), [deviceType]);
|
|
1385
|
-
|
|
1535
|
+
const height = useMemo(() => calcHeightByDeviceType(deviceType), [deviceType]);
|
|
1536
|
+
return { width, height };
|
|
1386
1537
|
function calcWidthByDeviceType(deviceType) {
|
|
1387
1538
|
if (!deviceType)
|
|
1388
1539
|
return 1440;
|
|
1389
1540
|
switch (deviceType) {
|
|
1541
|
+
case EDeviceType.DesktopLarge:
|
|
1542
|
+
return 1920;
|
|
1390
1543
|
case EDeviceType.Desktop:
|
|
1391
1544
|
return 1440;
|
|
1392
1545
|
case EDeviceType.Tablet:
|
|
@@ -1395,20 +1548,45 @@ const useHeatmapWidthByDevice = () => {
|
|
|
1395
1548
|
return 375;
|
|
1396
1549
|
}
|
|
1397
1550
|
}
|
|
1551
|
+
function calcHeightByDeviceType(deviceType) {
|
|
1552
|
+
if (!deviceType)
|
|
1553
|
+
return 900;
|
|
1554
|
+
switch (deviceType) {
|
|
1555
|
+
case EDeviceType.DesktopLarge:
|
|
1556
|
+
return 1080; // 1920×1080
|
|
1557
|
+
case EDeviceType.Desktop:
|
|
1558
|
+
return 900; // 1440×900
|
|
1559
|
+
case EDeviceType.Tablet:
|
|
1560
|
+
return 1024; // 768×1024 (iPad portrait)
|
|
1561
|
+
case EDeviceType.Mobile:
|
|
1562
|
+
return 812; // 375×812 (iPhone X/11/12/13)
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1398
1565
|
};
|
|
1399
1566
|
|
|
1400
|
-
const useRegisterConfig = ({ isLoading, isLoadingCanvas, }) => {
|
|
1567
|
+
const useRegisterConfig = ({ shopId, isLoading, isLoadingCanvas, excludeClassNames, }) => {
|
|
1401
1568
|
const mode = useHeatmapConfigStore((state) => state.mode);
|
|
1569
|
+
const shopIdStore = useHeatmapConfigStore((state) => state.shopId);
|
|
1402
1570
|
const deviceType = useHeatmapSettingContext((state) => state.deviceType);
|
|
1403
1571
|
const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
|
|
1404
1572
|
const setIsRendering = useHeatmapSettingContext((state) => state.setIsRendering);
|
|
1405
1573
|
const setIsLoadingDom = useHeatmapSettingContext((state) => state.setIsLoadingDom);
|
|
1406
1574
|
const setIsLoadingCanvas = useHeatmapSettingContext((state) => state.setIsLoadingCanvas);
|
|
1575
|
+
const setShopId = useHeatmapConfigStore((state) => state.setShopId);
|
|
1576
|
+
const setExcludeClassNames = useHeatmapConfigStore((state) => state.setExcludeClassNames);
|
|
1577
|
+
useEffect(() => {
|
|
1578
|
+
if (!shopId || !!shopIdStore || shopIdStore === shopId)
|
|
1579
|
+
return;
|
|
1580
|
+
setShopId(shopId);
|
|
1581
|
+
}, [shopId, setShopId, shopIdStore]);
|
|
1582
|
+
useEffect(() => {
|
|
1583
|
+
setExcludeClassNames(excludeClassNames || []);
|
|
1584
|
+
}, [excludeClassNames, setExcludeClassNames]);
|
|
1407
1585
|
useEffect(() => {
|
|
1408
1586
|
setIsRendering(true);
|
|
1409
1587
|
setTimeout(() => {
|
|
1410
1588
|
setIsRendering(false);
|
|
1411
|
-
},
|
|
1589
|
+
}, 500);
|
|
1412
1590
|
}, [mode, deviceType, sidebarWidth]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
1413
1591
|
useEffect(() => {
|
|
1414
1592
|
setIsRendering(!!isLoading);
|
|
@@ -1499,30 +1677,15 @@ const drawBackdropWithCutout = (options) => {
|
|
|
1499
1677
|
const height = Math.min(canvasHeight - top, activeRect.height + cutoutExpansion * 2);
|
|
1500
1678
|
// Clear previous drawing
|
|
1501
1679
|
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
1502
|
-
//
|
|
1680
|
+
// Draw backdrop as a single path with a cutout hole using evenodd fill rule.
|
|
1681
|
+
// This avoids seam artifacts that appear when using 4 separate rectangles
|
|
1682
|
+
// with fractional coordinates (sub-pixel gaps at rect boundaries).
|
|
1503
1683
|
ctx.fillStyle = backdropColor;
|
|
1504
1684
|
ctx.globalAlpha = backdropOpacity;
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
//
|
|
1508
|
-
|
|
1509
|
-
ctx.fillRect(0, 0, canvasWidth, top);
|
|
1510
|
-
}
|
|
1511
|
-
// Bottom rectangle (below active element)
|
|
1512
|
-
const bottomY = top + height;
|
|
1513
|
-
if (bottomY < canvasHeight) {
|
|
1514
|
-
ctx.fillRect(0, bottomY, canvasWidth, canvasHeight - bottomY);
|
|
1515
|
-
}
|
|
1516
|
-
// Left rectangle (left of active element)
|
|
1517
|
-
if (left > 0) {
|
|
1518
|
-
ctx.fillRect(0, top, left, height);
|
|
1519
|
-
}
|
|
1520
|
-
// Right rectangle (right of active element)
|
|
1521
|
-
const rightX = left + width;
|
|
1522
|
-
if (rightX < canvasWidth) {
|
|
1523
|
-
ctx.fillRect(rightX, top, canvasWidth - rightX, height);
|
|
1524
|
-
}
|
|
1525
|
-
// Reset alpha
|
|
1685
|
+
ctx.beginPath();
|
|
1686
|
+
ctx.rect(0, 0, canvasWidth, canvasHeight); // outer (full canvas)
|
|
1687
|
+
ctx.rect(left, top, width, height); // inner (cutout hole)
|
|
1688
|
+
ctx.fill('evenodd');
|
|
1526
1689
|
ctx.globalAlpha = 1.0;
|
|
1527
1690
|
};
|
|
1528
1691
|
/**
|
|
@@ -1630,6 +1793,8 @@ const getElementAtPoint = (doc, x, y) => {
|
|
|
1630
1793
|
return element;
|
|
1631
1794
|
};
|
|
1632
1795
|
function getElementHash(element) {
|
|
1796
|
+
if (!element)
|
|
1797
|
+
return null;
|
|
1633
1798
|
return element.getAttribute('data-clarity-hashbeta') || element.getAttribute('data-clarity-hashalpha');
|
|
1634
1799
|
}
|
|
1635
1800
|
|
|
@@ -1770,7 +1935,7 @@ class Logger {
|
|
|
1770
1935
|
}
|
|
1771
1936
|
}
|
|
1772
1937
|
// Export singleton instance
|
|
1773
|
-
const logger$
|
|
1938
|
+
const logger$3 = new Logger();
|
|
1774
1939
|
// Export factory function để tạo logger với config riêng
|
|
1775
1940
|
function createLogger(config = {}) {
|
|
1776
1941
|
const instance = new Logger();
|
|
@@ -2159,7 +2324,7 @@ function findElementByHash(props) {
|
|
|
2159
2324
|
}
|
|
2160
2325
|
}
|
|
2161
2326
|
catch (error) {
|
|
2162
|
-
logger$
|
|
2327
|
+
logger$3.warn(`Invalid selector "${selector}":`, error);
|
|
2163
2328
|
}
|
|
2164
2329
|
const elementByHash = iframeDocument.querySelector(`[data-clarity-hashalpha="${hash}"], [data-clarity-hash="${hash}"], [data-clarity-hashbeta="${hash}"]`);
|
|
2165
2330
|
return elementByHash;
|
|
@@ -2181,7 +2346,7 @@ function hydrateAreaNode(props) {
|
|
|
2181
2346
|
const { id, hash, selector } = persistedData;
|
|
2182
2347
|
const element = findElementByHash({ hash, selector, iframeDocument, vizRef });
|
|
2183
2348
|
if (!element) {
|
|
2184
|
-
logger$
|
|
2349
|
+
logger$3.warn(`Cannot hydrate area ${id}: element not found for hash ${hash} or selector ${selector}`);
|
|
2185
2350
|
return null;
|
|
2186
2351
|
}
|
|
2187
2352
|
const areaNode = buildAreaNode(element, hash, heatmapInfo, shadowRoot, persistedData);
|
|
@@ -2198,7 +2363,7 @@ function hydrateAreas(props) {
|
|
|
2198
2363
|
hydratedAreas.push(area);
|
|
2199
2364
|
}
|
|
2200
2365
|
}
|
|
2201
|
-
logger$
|
|
2366
|
+
logger$3.info(`Hydrated ${hydratedAreas.length} of ${clickAreas.length} persisted areas`);
|
|
2202
2367
|
return hydratedAreas;
|
|
2203
2368
|
}
|
|
2204
2369
|
/**
|
|
@@ -2902,16 +3067,16 @@ const calcCalloutPositionAbsolute = (props) => {
|
|
|
2902
3067
|
|
|
2903
3068
|
function validateAreaCreation(dataInfo, hash, areas) {
|
|
2904
3069
|
if (!dataInfo?.clickMapMetrics || !dataInfo?.totalClicks) {
|
|
2905
|
-
logger$
|
|
3070
|
+
logger$3.warn('Cannot create area: missing heatmap data');
|
|
2906
3071
|
return false;
|
|
2907
3072
|
}
|
|
2908
3073
|
if (!hash) {
|
|
2909
|
-
logger$
|
|
3074
|
+
logger$3.warn('Cannot create area: missing hash');
|
|
2910
3075
|
return false;
|
|
2911
3076
|
}
|
|
2912
3077
|
const alreadyExists = areas.some((area) => area.hash === hash);
|
|
2913
3078
|
if (alreadyExists) {
|
|
2914
|
-
logger$
|
|
3079
|
+
logger$3.warn(`Area already exists for element: ${hash}`);
|
|
2915
3080
|
return false;
|
|
2916
3081
|
}
|
|
2917
3082
|
return true;
|
|
@@ -2924,14 +3089,14 @@ function identifyConflictingAreas(area) {
|
|
|
2924
3089
|
// Case 1: New area is a child of an existing area
|
|
2925
3090
|
if (area.parentNode) {
|
|
2926
3091
|
conflicts.parentId = area.parentNode.id;
|
|
2927
|
-
logger$
|
|
3092
|
+
logger$3.info(`New area "${area.selector}" is a child of existing area "${area.parentNode.selector}". Will remove parent.`);
|
|
2928
3093
|
}
|
|
2929
3094
|
// Case 2: New area is a parent of existing area(s)
|
|
2930
3095
|
if (area.childNodes.size > 0) {
|
|
2931
3096
|
area.childNodes.forEach((childArea) => {
|
|
2932
3097
|
conflicts.childrenIds.push(childArea.id);
|
|
2933
3098
|
});
|
|
2934
|
-
logger$
|
|
3099
|
+
logger$3.info(`New area "${area.selector}" is a parent of ${area.childNodes.size} existing area(s). Will remove children.`);
|
|
2935
3100
|
}
|
|
2936
3101
|
return conflicts;
|
|
2937
3102
|
}
|
|
@@ -2982,7 +3147,7 @@ function useAreaCreation(options = {}) {
|
|
|
2982
3147
|
}
|
|
2983
3148
|
}
|
|
2984
3149
|
catch (error) {
|
|
2985
|
-
logger$
|
|
3150
|
+
logger$3.error('Failed to create area:', error);
|
|
2986
3151
|
}
|
|
2987
3152
|
}, [dataInfo, areas, addArea, removeArea, removeClickArea, customShadowRoot, onAreaCreated]);
|
|
2988
3153
|
return {
|
|
@@ -3043,7 +3208,7 @@ function useAreaEditMode({ iframeRef, onAreaCreatedElement, enabled = false, })
|
|
|
3043
3208
|
}
|
|
3044
3209
|
iframeDocument.addEventListener('mousemove', handleMouseMove);
|
|
3045
3210
|
iframeDocument.addEventListener('scroll', handleMouseLeave);
|
|
3046
|
-
iframeDocument.
|
|
3211
|
+
iframeDocument.addEventListener('mouseleave', handleMouseLeave);
|
|
3047
3212
|
iframeDocument.addEventListener('click', handleClick);
|
|
3048
3213
|
return () => {
|
|
3049
3214
|
iframeDocument.removeEventListener('mousemove', handleMouseMove);
|
|
@@ -3097,16 +3262,16 @@ function useAreaHydration(options) {
|
|
|
3097
3262
|
return;
|
|
3098
3263
|
if (!dataInfo)
|
|
3099
3264
|
return;
|
|
3100
|
-
logger$
|
|
3265
|
+
logger$3.info(`Hydrating ${clickAreas.length} persisted areas...`);
|
|
3101
3266
|
const hydratedAreas = hydrateAreas({ clickAreas, heatmapInfo: dataInfo, vizRef, shadowRoot });
|
|
3102
3267
|
if (!hydratedAreas?.length) {
|
|
3103
|
-
logger$
|
|
3268
|
+
logger$3.warn('No areas could be hydrated - all elements may have been removed from DOM');
|
|
3104
3269
|
return;
|
|
3105
3270
|
}
|
|
3106
3271
|
setIsInitializing(true);
|
|
3107
3272
|
buildAreaGraph(hydratedAreas);
|
|
3108
3273
|
setAreas(hydratedAreas);
|
|
3109
|
-
logger$
|
|
3274
|
+
logger$3.info(`Successfully hydrated ${hydratedAreas.length} areas`);
|
|
3110
3275
|
}, [dataInfo, vizRef, isInitializing, clickAreas]);
|
|
3111
3276
|
useEffect(() => {
|
|
3112
3277
|
if (!enabled)
|
|
@@ -3240,7 +3405,7 @@ function useAreaRectSync(options) {
|
|
|
3240
3405
|
area.rect.update(newRect);
|
|
3241
3406
|
}
|
|
3242
3407
|
catch (error) {
|
|
3243
|
-
logger$
|
|
3408
|
+
logger$3.error(`Failed to update rect for area ${area.id}:`, error);
|
|
3244
3409
|
}
|
|
3245
3410
|
});
|
|
3246
3411
|
buildAreaGraph(areas);
|
|
@@ -3329,34 +3494,41 @@ const useAreaTopAutoDetect = (props) => {
|
|
|
3329
3494
|
const useAreaClickmap = () => {
|
|
3330
3495
|
const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
|
|
3331
3496
|
const clickmap = useHeatmapDataContext((s) => s.clickmap);
|
|
3497
|
+
const isDomLoaded = useHeatmapVizContext((s) => s.isDomLoaded);
|
|
3332
3498
|
const start = useCallback(() => {
|
|
3333
|
-
if (!vizRef || !clickmap ||
|
|
3499
|
+
if (!vizRef || !clickmap || !isDomLoaded)
|
|
3334
3500
|
return;
|
|
3335
3501
|
try {
|
|
3336
3502
|
vizRef?.clearmap?.();
|
|
3503
|
+
if (clickmap.length === 0)
|
|
3504
|
+
return;
|
|
3505
|
+
// implement area clickmap
|
|
3506
|
+
// requestIdleCallback(() => vizRef?.areaClickmap?.(clickmap), { timeout: 300 });
|
|
3337
3507
|
}
|
|
3338
3508
|
catch (error) {
|
|
3339
|
-
console.error(
|
|
3509
|
+
console.error(`useAreaClickmap ~ error:`, error);
|
|
3340
3510
|
}
|
|
3341
|
-
}, [vizRef, clickmap]);
|
|
3511
|
+
}, [vizRef, clickmap, isDomLoaded]);
|
|
3342
3512
|
return { start };
|
|
3343
3513
|
};
|
|
3344
3514
|
|
|
3345
3515
|
const useClickmap = () => {
|
|
3346
3516
|
const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
|
|
3347
3517
|
const clickmap = useHeatmapDataContext((s) => s.clickmap);
|
|
3348
|
-
const
|
|
3518
|
+
const isDomLoaded = useHeatmapVizContext((s) => s.isDomLoaded);
|
|
3349
3519
|
const start = useCallback(() => {
|
|
3350
|
-
if (!vizRef || !clickmap ||
|
|
3520
|
+
if (!vizRef || !clickmap || !isDomLoaded)
|
|
3351
3521
|
return;
|
|
3352
3522
|
try {
|
|
3353
3523
|
vizRef?.clearmap?.();
|
|
3354
|
-
|
|
3524
|
+
if (clickmap.length === 0)
|
|
3525
|
+
return;
|
|
3526
|
+
requestIdleCallback(() => vizRef?.clickmap?.(clickmap), { timeout: 300 });
|
|
3355
3527
|
}
|
|
3356
3528
|
catch (error) {
|
|
3357
|
-
console.error(
|
|
3529
|
+
console.error(`useClickmap ~ error:`, error);
|
|
3358
3530
|
}
|
|
3359
|
-
}, [vizRef, clickmap,
|
|
3531
|
+
}, [vizRef, clickmap, isDomLoaded]);
|
|
3360
3532
|
return { start };
|
|
3361
3533
|
};
|
|
3362
3534
|
|
|
@@ -3364,29 +3536,53 @@ const useScrollmap = () => {
|
|
|
3364
3536
|
const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
|
|
3365
3537
|
const scrollType = useHeatmapSettingContext((s) => s.scrollType);
|
|
3366
3538
|
const scrollmap = useHeatmapDataContext((s) => s.scrollmap);
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3539
|
+
const isDomLoaded = useHeatmapVizContext((s) => s.isDomLoaded);
|
|
3540
|
+
const renderScrollmap = useCallback(() => {
|
|
3541
|
+
if (!vizRef || !scrollmap || scrollmap.length === 0 || !isDomLoaded)
|
|
3542
|
+
return;
|
|
3543
|
+
try {
|
|
3544
|
+
requestIdleCallback(() => vizRef?.scrollmap?.(scrollmap), { timeout: 300 });
|
|
3545
|
+
}
|
|
3546
|
+
catch (error) {
|
|
3547
|
+
console.error(`🚀 🐥 ~ useScrollmap ~ renderScrollmap:`, error);
|
|
3548
|
+
}
|
|
3549
|
+
}, [vizRef, scrollmap, isDomLoaded]);
|
|
3550
|
+
const renderScrollBucket = useCallback(() => {
|
|
3551
|
+
if (!vizRef || !scrollmap || scrollmap.length === 0 || !isDomLoaded)
|
|
3552
|
+
return;
|
|
3553
|
+
const bucketData = scrollmap.map((point) => ({
|
|
3554
|
+
position: point.scrollReachY,
|
|
3555
|
+
value: point.cumulativeSum,
|
|
3556
|
+
percent: point.percUsers,
|
|
3557
|
+
}));
|
|
3558
|
+
try {
|
|
3559
|
+
requestIdleCallback(() => vizRef?.scrollBucket?.(bucketData), { timeout: 300 });
|
|
3560
|
+
}
|
|
3561
|
+
catch (error) {
|
|
3562
|
+
console.error(`🚀 🐥 ~ useScrollmap ~ renderScrollBucket:`, error);
|
|
3377
3563
|
}
|
|
3378
|
-
}, [scrollmap,
|
|
3564
|
+
}, [vizRef, scrollmap, isDomLoaded]);
|
|
3379
3565
|
const start = useCallback(() => {
|
|
3380
|
-
if (!vizRef || !scrollmap || scrollmap.length === 0)
|
|
3566
|
+
if (!vizRef || !scrollmap || scrollmap.length === 0 || !isDomLoaded)
|
|
3381
3567
|
return;
|
|
3382
3568
|
try {
|
|
3383
3569
|
vizRef?.clearmap?.();
|
|
3384
|
-
vizRef?.scrollmap?.(scrollmap);
|
|
3385
3570
|
}
|
|
3386
3571
|
catch (error) {
|
|
3387
|
-
|
|
3572
|
+
console.error(`🚀 🐥 ~ useScrollmap ~ start:`, error);
|
|
3573
|
+
}
|
|
3574
|
+
switch (scrollType) {
|
|
3575
|
+
case EScrollType.Attention:
|
|
3576
|
+
case EScrollType.Revenue: {
|
|
3577
|
+
renderScrollBucket();
|
|
3578
|
+
break;
|
|
3579
|
+
}
|
|
3580
|
+
case EScrollType.Depth:
|
|
3581
|
+
default:
|
|
3582
|
+
renderScrollmap();
|
|
3583
|
+
break;
|
|
3388
3584
|
}
|
|
3389
|
-
}, [vizRef, scrollmap]);
|
|
3585
|
+
}, [vizRef, scrollmap, isDomLoaded, scrollType, renderScrollmap, renderScrollBucket]);
|
|
3390
3586
|
return { start };
|
|
3391
3587
|
};
|
|
3392
3588
|
|
|
@@ -3394,6 +3590,7 @@ const useHeatmapCanvas = () => {
|
|
|
3394
3590
|
const heatmapType = useHeatmapSettingContext((state) => state.heatmapType);
|
|
3395
3591
|
const clickMode = useHeatmapSettingContext((state) => state.clickMode);
|
|
3396
3592
|
const scrollType = useHeatmapSettingContext((state) => state.scrollType);
|
|
3593
|
+
const setSelectedElement = useHeatmapClickContext((s) => s.setSelectedElement);
|
|
3397
3594
|
const { start: startClickmap } = useClickmap();
|
|
3398
3595
|
const { start: startAreaClickmap } = useAreaClickmap();
|
|
3399
3596
|
const { start: startScrollmap } = useScrollmap();
|
|
@@ -3412,7 +3609,10 @@ const useHeatmapCanvas = () => {
|
|
|
3412
3609
|
startScrollmap();
|
|
3413
3610
|
break;
|
|
3414
3611
|
}
|
|
3415
|
-
|
|
3612
|
+
return () => {
|
|
3613
|
+
setSelectedElement(null);
|
|
3614
|
+
};
|
|
3615
|
+
}, [heatmapType, clickMode, startClickmap, startAreaClickmap, startScrollmap, scrollType]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
3416
3616
|
};
|
|
3417
3617
|
|
|
3418
3618
|
const scrollToElementIfNeeded = (visualRef, rect, scale, onScrollComplete) => {
|
|
@@ -3494,104 +3694,6 @@ const useClickedElement = ({ visualRef, getRect }) => {
|
|
|
3494
3694
|
return { clickedElement, showMissingElement, shouldShowCallout };
|
|
3495
3695
|
};
|
|
3496
3696
|
|
|
3497
|
-
const useElementCalloutVisible = ({ visualRef, getRect, positionMode }) => {
|
|
3498
|
-
const selectedElement = useHeatmapClickContext((s) => s.selectedElement);
|
|
3499
|
-
const setShouldShowCallout = useHeatmapClickContext((s) => s.setShouldShowCallout);
|
|
3500
|
-
const widthScale = useHeatmapVizContext((s) => s.widthScale);
|
|
3501
|
-
const dataInfo = useHeatmapDataContext((s) => s.dataInfo);
|
|
3502
|
-
useEffect(() => {
|
|
3503
|
-
const isAbsolute = positionMode === 'absolute';
|
|
3504
|
-
if (isAbsolute)
|
|
3505
|
-
return;
|
|
3506
|
-
const hash = selectedElement?.hash;
|
|
3507
|
-
if (!hash)
|
|
3508
|
-
return;
|
|
3509
|
-
const elementIsInViewportFn = () => {
|
|
3510
|
-
const elementInfo = dataInfo?.clickMapMetrics?.[hash];
|
|
3511
|
-
if (!elementInfo)
|
|
3512
|
-
return;
|
|
3513
|
-
const rect = getRect({ hash, selector: elementInfo.selector });
|
|
3514
|
-
const isInViewport = isElementInViewport(rect, visualRef, widthScale);
|
|
3515
|
-
setShouldShowCallout(isInViewport);
|
|
3516
|
-
};
|
|
3517
|
-
elementIsInViewportFn();
|
|
3518
|
-
const handleUpdate = () => {
|
|
3519
|
-
requestAnimationFrame(elementIsInViewportFn);
|
|
3520
|
-
};
|
|
3521
|
-
window.addEventListener('scroll', handleUpdate, true);
|
|
3522
|
-
window.addEventListener('resize', handleUpdate);
|
|
3523
|
-
visualRef?.current?.addEventListener('scroll', handleUpdate);
|
|
3524
|
-
return () => {
|
|
3525
|
-
window.removeEventListener('scroll', handleUpdate, true);
|
|
3526
|
-
window.removeEventListener('resize', handleUpdate);
|
|
3527
|
-
visualRef?.current?.removeEventListener('scroll', handleUpdate);
|
|
3528
|
-
};
|
|
3529
|
-
}, [selectedElement?.hash, visualRef, widthScale, dataInfo, getRect, setShouldShowCallout, positionMode]);
|
|
3530
|
-
return {};
|
|
3531
|
-
};
|
|
3532
|
-
|
|
3533
|
-
const useHeatmapEffects = ({ isVisible }) => {
|
|
3534
|
-
// const selectedElement = useHeatmapClickContext((s) => s.selectedElement);
|
|
3535
|
-
// const setShouldShowCallout = useHeatmapClickContext((s) => s.setShouldShowCallout);
|
|
3536
|
-
const resetAll = () => {
|
|
3537
|
-
// setShouldShowCallout(false);
|
|
3538
|
-
};
|
|
3539
|
-
// Reset khi ẩn
|
|
3540
|
-
useEffect(() => {
|
|
3541
|
-
}, [isVisible, resetAll]);
|
|
3542
|
-
// Ẩn callout khi sidebar mở
|
|
3543
|
-
// useEffect(() => {
|
|
3544
|
-
// if (isElementSidebarOpen && selectedElement) {
|
|
3545
|
-
// setShouldShowCallout(false);
|
|
3546
|
-
// } else if (!isElementSidebarOpen && selectedElement) {
|
|
3547
|
-
// setShouldShowCallout(true);
|
|
3548
|
-
// }
|
|
3549
|
-
// }, [isElementSidebarOpen, selectedElement]);
|
|
3550
|
-
};
|
|
3551
|
-
|
|
3552
|
-
const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
|
|
3553
|
-
const heatmapWidth = useHeatmapWidthByDevice();
|
|
3554
|
-
const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
|
|
3555
|
-
const widthScale = useHeatmapVizContext((s) => s.widthScale);
|
|
3556
|
-
const getRect = useCallback((element) => {
|
|
3557
|
-
const hash = element?.hash;
|
|
3558
|
-
if (!iframeRef.current?.contentDocument || !hash || !visualizer)
|
|
3559
|
-
return null;
|
|
3560
|
-
let domElement = null;
|
|
3561
|
-
try {
|
|
3562
|
-
domElement = visualizer.get(hash);
|
|
3563
|
-
}
|
|
3564
|
-
catch (error) {
|
|
3565
|
-
console.error('Visualizer error:', { hash, error });
|
|
3566
|
-
return null;
|
|
3567
|
-
}
|
|
3568
|
-
if (!domElement)
|
|
3569
|
-
return null;
|
|
3570
|
-
const layout = getElementLayout(domElement);
|
|
3571
|
-
if (!layout)
|
|
3572
|
-
return null;
|
|
3573
|
-
const parentEl = wrapperRef.current;
|
|
3574
|
-
if (!parentEl)
|
|
3575
|
-
return null;
|
|
3576
|
-
const scrollOffset = parentEl.scrollTop / widthScale;
|
|
3577
|
-
const adjustedTop = layout.top + scrollOffset;
|
|
3578
|
-
const outOfBounds = adjustedTop < 0 ||
|
|
3579
|
-
adjustedTop > (iframeHeight || Infinity) ||
|
|
3580
|
-
layout.left < 0 ||
|
|
3581
|
-
(typeof heatmapWidth === 'number' && layout.left > heatmapWidth);
|
|
3582
|
-
if (outOfBounds)
|
|
3583
|
-
return null;
|
|
3584
|
-
return {
|
|
3585
|
-
left: layout.left,
|
|
3586
|
-
top: adjustedTop,
|
|
3587
|
-
width: Math.min(layout.width, heatmapWidth || layout.width),
|
|
3588
|
-
height: layout.height,
|
|
3589
|
-
outOfBounds,
|
|
3590
|
-
};
|
|
3591
|
-
}, [iframeRef, wrapperRef, visualizer, heatmapWidth, iframeHeight, widthScale]);
|
|
3592
|
-
return { getRect };
|
|
3593
|
-
};
|
|
3594
|
-
|
|
3595
3697
|
function getBoundingBox(element) {
|
|
3596
3698
|
if (typeof element.getBoundingClientRect !== 'function') {
|
|
3597
3699
|
return null;
|
|
@@ -3706,15 +3808,12 @@ const useHoveredElement = ({ iframeRef, getRect }) => {
|
|
|
3706
3808
|
const iframeRect = iframe.getBoundingClientRect();
|
|
3707
3809
|
const { x, y } = convertViewportToIframeCoords(event.clientX, event.clientY, iframeRect, widthScale);
|
|
3708
3810
|
const targetElement = findTargetElement(doc, x, y, dataInfo);
|
|
3709
|
-
|
|
3811
|
+
const hash = getElementHash(targetElement);
|
|
3812
|
+
if (!targetElement || !hash || !isElmInDataInfo(hash, dataInfo)) {
|
|
3710
3813
|
reset();
|
|
3711
3814
|
return;
|
|
3712
3815
|
}
|
|
3713
|
-
|
|
3714
|
-
if (hash)
|
|
3715
|
-
return hash;
|
|
3716
|
-
reset();
|
|
3717
|
-
return;
|
|
3816
|
+
return hash;
|
|
3718
3817
|
}, [dataInfo, iframeRef, widthScale, reset]);
|
|
3719
3818
|
const onHandleMouseMove = useCallback((event) => {
|
|
3720
3819
|
if (isLoadingCanvas)
|
|
@@ -3726,7 +3825,7 @@ const useHoveredElement = ({ iframeRef, getRect }) => {
|
|
|
3726
3825
|
}
|
|
3727
3826
|
if (isHoveredElement(hash))
|
|
3728
3827
|
return;
|
|
3729
|
-
const selector = dataInfo
|
|
3828
|
+
const selector = dataInfo.clickMapMetrics?.[hash];
|
|
3730
3829
|
const rect = getRect({ hash, selector });
|
|
3731
3830
|
const elementInfo = buildElementInfo(hash, rect, dataInfo);
|
|
3732
3831
|
if (!elementInfo) {
|
|
@@ -3814,83 +3913,181 @@ const findTargetElement = (doc, x, y, heatmapInfo) => {
|
|
|
3814
3913
|
const isIframeReady = (iframeRef, heatmapInfo) => {
|
|
3815
3914
|
return !!(iframeRef.current?.contentDocument && heatmapInfo?.clickMapMetrics);
|
|
3816
3915
|
};
|
|
3817
|
-
const
|
|
3818
|
-
if (!element)
|
|
3819
|
-
return false;
|
|
3820
|
-
const hash = getElementHash(element);
|
|
3916
|
+
const isElmInDataInfo = (hash, heatmapInfo) => {
|
|
3821
3917
|
if (!hash)
|
|
3822
3918
|
return false;
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
/**
|
|
3827
|
-
* Portal service configuration
|
|
3828
|
-
*/
|
|
3829
|
-
const PORTAL_CONFIG = {
|
|
3830
|
-
// Default portal service URL
|
|
3831
|
-
// In production, this should be configured via environment or config
|
|
3832
|
-
serviceUrl: 'https://ducky.ngrok.app',
|
|
3833
|
-
// Portal tracking types
|
|
3834
|
-
trackingTypes: {
|
|
3835
|
-
HEATMAP: '1',
|
|
3836
|
-
SESSION_REPLAY: '2',
|
|
3837
|
-
},
|
|
3919
|
+
const isInSortedElements = heatmapInfo?.sortedElements?.some((item) => item.hash === hash);
|
|
3920
|
+
return !!isInSortedElements && !!heatmapInfo?.clickMapMetrics?.[hash];
|
|
3838
3921
|
};
|
|
3839
|
-
/**
|
|
3840
|
-
* Get portal service URL from config or environment
|
|
3841
|
-
* Can be overridden by window.GEMX_PORTAL_URL for runtime configuration
|
|
3842
|
-
*/
|
|
3843
|
-
function getPortalServiceUrl() {
|
|
3844
|
-
// Allow runtime override via window global
|
|
3845
|
-
if (typeof window !== 'undefined' && window.GEMX_PORTAL_URL) {
|
|
3846
|
-
return window.GEMX_PORTAL_URL;
|
|
3847
|
-
}
|
|
3848
|
-
return PORTAL_CONFIG.serviceUrl;
|
|
3849
|
-
}
|
|
3850
3922
|
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
const [isReady, setIsReady] = useState(false);
|
|
3860
|
-
const iframeRef = useRef(null);
|
|
3861
|
-
const isValidOrigin = useCallback((origin) => {
|
|
3862
|
-
if (trustedOrigins.length === 0)
|
|
3863
|
-
return true;
|
|
3864
|
-
return trustedOrigins.includes(origin);
|
|
3865
|
-
}, [trustedOrigins]);
|
|
3866
|
-
const handleMessage = useCallback((event) => {
|
|
3867
|
-
if (!isValidOrigin(event.origin)) {
|
|
3868
|
-
console.warn('Message from untrusted origin:', event.origin);
|
|
3869
|
-
return;
|
|
3870
|
-
}
|
|
3871
|
-
if (!event.data || typeof event.data !== 'object' || !event.data.type) {
|
|
3923
|
+
const useElementCalloutVisible = ({ visualRef, getRect, positionMode }) => {
|
|
3924
|
+
const selectedElement = useHeatmapClickContext((s) => s.selectedElement);
|
|
3925
|
+
const setShouldShowCallout = useHeatmapClickContext((s) => s.setShouldShowCallout);
|
|
3926
|
+
const widthScale = useHeatmapVizContext((s) => s.widthScale);
|
|
3927
|
+
const dataInfo = useHeatmapDataContext((s) => s.dataInfo);
|
|
3928
|
+
useEffect(() => {
|
|
3929
|
+
const isAbsolute = positionMode === 'absolute';
|
|
3930
|
+
if (isAbsolute)
|
|
3872
3931
|
return;
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
if (!Object.values(MessageType).includes(message.type)) {
|
|
3932
|
+
const hash = selectedElement?.hash;
|
|
3933
|
+
if (!hash)
|
|
3876
3934
|
return;
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3935
|
+
const elementIsInViewportFn = () => {
|
|
3936
|
+
if (!isElmInDataInfo(hash, dataInfo))
|
|
3937
|
+
return;
|
|
3938
|
+
const elementInfo = dataInfo?.clickMapMetrics?.[hash];
|
|
3939
|
+
if (!elementInfo)
|
|
3940
|
+
return;
|
|
3941
|
+
const rect = getRect({ hash, selector: elementInfo.selector });
|
|
3942
|
+
const isInViewport = isElementInViewport(rect, visualRef, widthScale);
|
|
3943
|
+
setShouldShowCallout(isInViewport);
|
|
3944
|
+
};
|
|
3945
|
+
elementIsInViewportFn();
|
|
3946
|
+
const handleUpdate = () => {
|
|
3947
|
+
requestAnimationFrame(elementIsInViewportFn);
|
|
3948
|
+
};
|
|
3949
|
+
window.addEventListener('scroll', handleUpdate, true);
|
|
3950
|
+
window.addEventListener('resize', handleUpdate);
|
|
3951
|
+
visualRef?.current?.addEventListener('scroll', handleUpdate);
|
|
3952
|
+
return () => {
|
|
3953
|
+
window.removeEventListener('scroll', handleUpdate, true);
|
|
3954
|
+
window.removeEventListener('resize', handleUpdate);
|
|
3955
|
+
visualRef?.current?.removeEventListener('scroll', handleUpdate);
|
|
3956
|
+
};
|
|
3957
|
+
}, [selectedElement?.hash, visualRef, widthScale, dataInfo, getRect, setShouldShowCallout, positionMode]);
|
|
3958
|
+
return {};
|
|
3959
|
+
};
|
|
3960
|
+
|
|
3961
|
+
const useHeatmapEffects = ({ isVisible }) => {
|
|
3962
|
+
// const selectedElement = useHeatmapClickContext((s) => s.selectedElement);
|
|
3963
|
+
// const setShouldShowCallout = useHeatmapClickContext((s) => s.setShouldShowCallout);
|
|
3964
|
+
const resetAll = () => {
|
|
3965
|
+
// setShouldShowCallout(false);
|
|
3966
|
+
};
|
|
3967
|
+
// Reset khi ẩn
|
|
3968
|
+
useEffect(() => {
|
|
3969
|
+
}, [isVisible, resetAll]);
|
|
3970
|
+
// Ẩn callout khi sidebar mở
|
|
3971
|
+
// useEffect(() => {
|
|
3972
|
+
// if (isElementSidebarOpen && selectedElement) {
|
|
3973
|
+
// setShouldShowCallout(false);
|
|
3974
|
+
// } else if (!isElementSidebarOpen && selectedElement) {
|
|
3975
|
+
// setShouldShowCallout(true);
|
|
3976
|
+
// }
|
|
3977
|
+
// }, [isElementSidebarOpen, selectedElement]);
|
|
3978
|
+
};
|
|
3979
|
+
|
|
3980
|
+
const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
|
|
3981
|
+
const viewport = useHeatmapViewportByDevice();
|
|
3982
|
+
const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
|
|
3983
|
+
const widthScale = useHeatmapVizContext((s) => s.widthScale);
|
|
3984
|
+
const getRect = useCallback((element) => {
|
|
3985
|
+
const hash = element?.hash;
|
|
3986
|
+
if (!iframeRef.current?.contentDocument || !hash || !visualizer)
|
|
3987
|
+
return null;
|
|
3988
|
+
let domElement = null;
|
|
3989
|
+
try {
|
|
3990
|
+
domElement = visualizer.get(hash);
|
|
3991
|
+
}
|
|
3992
|
+
catch (error) {
|
|
3993
|
+
console.error('Visualizer error:', { hash, error });
|
|
3994
|
+
return null;
|
|
3995
|
+
}
|
|
3996
|
+
if (!domElement)
|
|
3997
|
+
return null;
|
|
3998
|
+
const layout = getElementLayout(domElement);
|
|
3999
|
+
if (!layout)
|
|
4000
|
+
return null;
|
|
4001
|
+
const parentEl = wrapperRef.current;
|
|
4002
|
+
if (!parentEl)
|
|
4003
|
+
return null;
|
|
4004
|
+
const scrollOffset = parentEl.scrollTop / widthScale;
|
|
4005
|
+
const adjustedTop = layout.top + scrollOffset;
|
|
4006
|
+
const outOfBounds = adjustedTop < 0 ||
|
|
4007
|
+
adjustedTop > (iframeHeight || Infinity) ||
|
|
4008
|
+
layout.left < 0 ||
|
|
4009
|
+
(typeof viewport.width === 'number' && layout.left > viewport.width);
|
|
4010
|
+
if (outOfBounds)
|
|
4011
|
+
return null;
|
|
4012
|
+
return {
|
|
4013
|
+
left: layout.left,
|
|
4014
|
+
top: adjustedTop,
|
|
4015
|
+
width: Math.min(layout.width, viewport.width || layout.width),
|
|
4016
|
+
height: layout.height,
|
|
4017
|
+
outOfBounds,
|
|
4018
|
+
};
|
|
4019
|
+
}, [iframeRef, wrapperRef, visualizer, viewport, iframeHeight, widthScale]);
|
|
4020
|
+
return { getRect };
|
|
4021
|
+
};
|
|
4022
|
+
|
|
4023
|
+
/**
|
|
4024
|
+
* Portal service configuration
|
|
4025
|
+
*/
|
|
4026
|
+
const PORTAL_CONFIG = {
|
|
4027
|
+
// Default portal service URL
|
|
4028
|
+
// In production, this should be configured via environment or config
|
|
4029
|
+
serviceUrl: 'https://ducky.ngrok.app',
|
|
4030
|
+
// Portal tracking types
|
|
4031
|
+
trackingTypes: {
|
|
4032
|
+
HEATMAP: '1',
|
|
4033
|
+
SESSION_REPLAY: '2',
|
|
4034
|
+
},
|
|
4035
|
+
};
|
|
4036
|
+
/**
|
|
4037
|
+
* Get portal service URL from config or environment
|
|
4038
|
+
* Can be overridden by window.GEMX_PORTAL_URL for runtime configuration
|
|
4039
|
+
*/
|
|
4040
|
+
function getPortalServiceUrl() {
|
|
4041
|
+
// Allow runtime override via window global
|
|
4042
|
+
if (typeof window !== 'undefined' && window.GEMX_PORTAL_URL) {
|
|
4043
|
+
return window.GEMX_PORTAL_URL;
|
|
4044
|
+
}
|
|
4045
|
+
return PORTAL_CONFIG.serviceUrl;
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
var MessageType;
|
|
4049
|
+
(function (MessageType) {
|
|
4050
|
+
MessageType["GX_DOM_TRACKING_PAYLOAD"] = "GX_DOM_TRACKING_PAYLOAD";
|
|
4051
|
+
MessageType["CLARITY_READY"] = "CLARITY_READY";
|
|
4052
|
+
})(MessageType || (MessageType = {}));
|
|
4053
|
+
function useVizLiveIframeMsg(options = {}) {
|
|
4054
|
+
const { trustedOrigins = [], onMessage } = options;
|
|
4055
|
+
const addPayload = useHeatmapLiveContext((s) => s.addPayload);
|
|
4056
|
+
const [isReady, setIsReady] = useState(false);
|
|
4057
|
+
const iframeRef = useRef(null);
|
|
4058
|
+
const isValidOrigin = useCallback((origin) => {
|
|
4059
|
+
if (trustedOrigins.length === 0)
|
|
4060
|
+
return true;
|
|
4061
|
+
return trustedOrigins.includes(origin);
|
|
4062
|
+
}, [trustedOrigins]);
|
|
4063
|
+
const handleMessage = useCallback((event) => {
|
|
4064
|
+
if (!isValidOrigin(event.origin)) {
|
|
4065
|
+
console.warn('Message from untrusted origin:', event.origin);
|
|
4066
|
+
return;
|
|
4067
|
+
}
|
|
4068
|
+
if (!event.data || typeof event.data !== 'object' || !event.data.type) {
|
|
4069
|
+
return;
|
|
4070
|
+
}
|
|
4071
|
+
const message = event.data;
|
|
4072
|
+
if (!Object.values(MessageType).includes(message.type)) {
|
|
4073
|
+
return;
|
|
4074
|
+
}
|
|
4075
|
+
if (onMessage) {
|
|
4076
|
+
onMessage(message);
|
|
4077
|
+
}
|
|
4078
|
+
switch (message.type) {
|
|
4079
|
+
case MessageType.GX_DOM_TRACKING_PAYLOAD:
|
|
4080
|
+
if (message.payload) {
|
|
4081
|
+
const data = JSON.parse(message.payload);
|
|
4082
|
+
if (data) {
|
|
4083
|
+
addPayload(data);
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
break;
|
|
4087
|
+
case MessageType.CLARITY_READY:
|
|
4088
|
+
setIsReady(true);
|
|
4089
|
+
break;
|
|
4090
|
+
}
|
|
3894
4091
|
}, [isValidOrigin, onMessage]);
|
|
3895
4092
|
useEffect(() => {
|
|
3896
4093
|
window.addEventListener('message', handleMessage);
|
|
@@ -3904,21 +4101,474 @@ function useVizLiveIframeMsg(options = {}) {
|
|
|
3904
4101
|
};
|
|
3905
4102
|
}
|
|
3906
4103
|
|
|
4104
|
+
/**
|
|
4105
|
+
* Unified performance timing utility.
|
|
4106
|
+
*
|
|
4107
|
+
* Two complementary tools:
|
|
4108
|
+
*
|
|
4109
|
+
* 1. `perf` — global DevTools session recorder.
|
|
4110
|
+
* Stores structured timing in `window.__gemxPerf` for inspection.
|
|
4111
|
+
* Used by the iframe-processor rendering pipeline.
|
|
4112
|
+
*
|
|
4113
|
+
* perf.startSession('render-1');
|
|
4114
|
+
* const t = perf.mark('viewport.run');
|
|
4115
|
+
* perf.measure('viewport.run', t);
|
|
4116
|
+
* perf.endSession();
|
|
4117
|
+
*
|
|
4118
|
+
* 2. `createPerfTimer` — per-module console logger factory.
|
|
4119
|
+
* Logs prefixed timings to the console AND records entries into the
|
|
4120
|
+
* active global session so they appear in `window.__gemxPerf` too.
|
|
4121
|
+
*
|
|
4122
|
+
* const timer = createPerfTimer('Render');
|
|
4123
|
+
* const t0 = timer.mark('start');
|
|
4124
|
+
* await timer.wrap('visualizer.html', () => visualizer.html(...));
|
|
4125
|
+
* timer.measure('total', t0);
|
|
4126
|
+
*/
|
|
4127
|
+
const s = {
|
|
4128
|
+
enabled: true,
|
|
4129
|
+
current: null,
|
|
4130
|
+
sessions: [],
|
|
4131
|
+
maxSessions: 20,
|
|
4132
|
+
};
|
|
4133
|
+
// ── Global singleton functions ────────────────────────────────────────────────
|
|
4134
|
+
function startSession(id) {
|
|
4135
|
+
if (!s.enabled)
|
|
4136
|
+
return;
|
|
4137
|
+
s.current = { id, startedAt: performance.now(), entries: [] };
|
|
4138
|
+
}
|
|
4139
|
+
function endSession() {
|
|
4140
|
+
if (!s.enabled || !s.current)
|
|
4141
|
+
return null;
|
|
4142
|
+
const session = s.current;
|
|
4143
|
+
session.total = performance.now() - session.startedAt;
|
|
4144
|
+
s.sessions = [session, ...s.sessions].slice(0, s.maxSessions);
|
|
4145
|
+
s.current = null;
|
|
4146
|
+
flush();
|
|
4147
|
+
return session;
|
|
4148
|
+
}
|
|
4149
|
+
/** Record a point-in-time mark. Returns `performance.now()` for use with measure(). */
|
|
4150
|
+
function globalMark(label) {
|
|
4151
|
+
const now = performance.now();
|
|
4152
|
+
if (s.enabled && s.current) {
|
|
4153
|
+
s.current.entries.push({ label, t: now - s.current.startedAt });
|
|
4154
|
+
}
|
|
4155
|
+
return now;
|
|
4156
|
+
}
|
|
4157
|
+
/** Record a duration from a previous mark() timestamp. */
|
|
4158
|
+
function globalMeasure(label, t0) {
|
|
4159
|
+
const duration = performance.now() - t0;
|
|
4160
|
+
if (s.enabled && s.current) {
|
|
4161
|
+
s.current.entries.push({ label, t: t0 - s.current.startedAt, duration });
|
|
4162
|
+
}
|
|
4163
|
+
return duration;
|
|
4164
|
+
}
|
|
4165
|
+
function getReport() {
|
|
4166
|
+
return {
|
|
4167
|
+
sessions: s.sessions,
|
|
4168
|
+
latest: s.sessions[0] ?? null,
|
|
4169
|
+
};
|
|
4170
|
+
}
|
|
4171
|
+
function clearSessions() {
|
|
4172
|
+
s.current = null;
|
|
4173
|
+
s.sessions = [];
|
|
4174
|
+
if (typeof window !== 'undefined')
|
|
4175
|
+
delete window.__gemxPerf;
|
|
4176
|
+
}
|
|
4177
|
+
function enableGlobal() {
|
|
4178
|
+
s.enabled = true;
|
|
4179
|
+
}
|
|
4180
|
+
function disableGlobal() {
|
|
4181
|
+
s.enabled = false;
|
|
4182
|
+
}
|
|
4183
|
+
function flush() {
|
|
4184
|
+
if (typeof window === 'undefined')
|
|
4185
|
+
return;
|
|
4186
|
+
window.__gemxPerf = getReport();
|
|
4187
|
+
}
|
|
4188
|
+
// ── Global singleton export ───────────────────────────────────────────────────
|
|
4189
|
+
const perf$3 = {
|
|
4190
|
+
startSession,
|
|
4191
|
+
endSession,
|
|
4192
|
+
mark: globalMark,
|
|
4193
|
+
measure: globalMeasure,
|
|
4194
|
+
getReport,
|
|
4195
|
+
clear: clearSessions,
|
|
4196
|
+
enable: enableGlobal,
|
|
4197
|
+
disable: disableGlobal,
|
|
4198
|
+
};
|
|
4199
|
+
// ── createPerfTimer factory ───────────────────────────────────────────────────
|
|
4200
|
+
function createPerfTimer(config) {
|
|
4201
|
+
let cfg = typeof config === 'string' ? { prefix: config, enabled: true } : { enabled: true, ...config };
|
|
4202
|
+
function log(icon, label, extra) {
|
|
4203
|
+
if (!cfg.enabled)
|
|
4204
|
+
return;
|
|
4205
|
+
const suffix = extra ? ` — ${extra}` : '';
|
|
4206
|
+
console.log(`[${cfg.prefix}] ${icon} ${label}${suffix}`);
|
|
4207
|
+
}
|
|
4208
|
+
return {
|
|
4209
|
+
configure(next) {
|
|
4210
|
+
cfg = { ...cfg, ...next };
|
|
4211
|
+
},
|
|
4212
|
+
mark(label) {
|
|
4213
|
+
const t = globalMark(`[${cfg.prefix}] ${label}`);
|
|
4214
|
+
log('⏱', label);
|
|
4215
|
+
return t;
|
|
4216
|
+
},
|
|
4217
|
+
measure(label, from) {
|
|
4218
|
+
globalMeasure(`[${cfg.prefix}] ${label}`, from);
|
|
4219
|
+
const ms = (performance.now() - from).toFixed(1);
|
|
4220
|
+
log('✅', label, `${ms}ms`);
|
|
4221
|
+
},
|
|
4222
|
+
async wrap(label, fn) {
|
|
4223
|
+
const t = globalMark(`[${cfg.prefix}] ${label}`);
|
|
4224
|
+
log('⏱', label);
|
|
4225
|
+
const result = await fn();
|
|
4226
|
+
const duration = globalMeasure(`[${cfg.prefix}] ${label}`, t);
|
|
4227
|
+
log('✅', label, `${duration.toFixed(1)}ms`);
|
|
4228
|
+
return result;
|
|
4229
|
+
},
|
|
4230
|
+
};
|
|
4231
|
+
}
|
|
4232
|
+
|
|
4233
|
+
/**
|
|
4234
|
+
* Default iframe dimension calculation.
|
|
4235
|
+
* Used as fallback when no fix overrides getDimensions().
|
|
4236
|
+
*/
|
|
4237
|
+
function getFinalHeight(doc, onlyClientHeight = true) {
|
|
4238
|
+
void doc.body.offsetHeight; // trigger reflow
|
|
4239
|
+
// const bodyMinHeight = parseFloat(win.getComputedStyle(doc.body).minHeight) || 0;
|
|
4240
|
+
// const htmlMinHeight = parseFloat(win.getComputedStyle(doc.documentElement).minHeight) || 0;
|
|
4241
|
+
if (onlyClientHeight) {
|
|
4242
|
+
return Math.max(doc.body?.clientHeight || 0, doc.documentElement?.clientHeight || 0);
|
|
4243
|
+
}
|
|
4244
|
+
return Math.max(doc.body?.scrollHeight || 0, doc.body?.offsetHeight || 0, doc.documentElement?.scrollHeight || 0, doc.documentElement?.offsetHeight || 0, doc.documentElement?.clientHeight || 0);
|
|
4245
|
+
}
|
|
4246
|
+
function getFinalWidth(doc) {
|
|
4247
|
+
return Math.max(
|
|
4248
|
+
// doc.body?.scrollWidth || 0,
|
|
4249
|
+
// doc.body?.offsetWidth || 0,
|
|
4250
|
+
// doc.documentElement?.scrollWidth || 0,
|
|
4251
|
+
// doc.documentElement?.offsetWidth || 0,
|
|
4252
|
+
doc.documentElement?.clientWidth || 0);
|
|
4253
|
+
}
|
|
4254
|
+
|
|
3907
4255
|
/**
|
|
3908
4256
|
* DOM observation setup — ResizeObserver + MutationObserver.
|
|
3909
4257
|
* Returns a cleanup function that disconnects both observers.
|
|
3910
4258
|
*/
|
|
3911
|
-
createLogger({ enabled: false, prefix: 'IframeHeightObserver' });
|
|
4259
|
+
const logger$2 = createLogger({ enabled: false, prefix: 'IframeHeightObserver' });
|
|
4260
|
+
function setup(doc, onChange) {
|
|
4261
|
+
const resizeObserver = new ResizeObserver(onChange);
|
|
4262
|
+
resizeObserver.observe(doc.documentElement);
|
|
4263
|
+
resizeObserver.observe(doc.body);
|
|
4264
|
+
const mutationObserver = new MutationObserver(onChange);
|
|
4265
|
+
mutationObserver.observe(doc.body, {
|
|
4266
|
+
childList: true,
|
|
4267
|
+
subtree: true,
|
|
4268
|
+
attributes: true,
|
|
4269
|
+
attributeFilter: ['style', 'class', 'hidden', 'data-v'],
|
|
4270
|
+
});
|
|
4271
|
+
logger$2.log('DOM observers started (ResizeObserver + MutationObserver)');
|
|
4272
|
+
return () => {
|
|
4273
|
+
resizeObserver.disconnect();
|
|
4274
|
+
mutationObserver.disconnect();
|
|
4275
|
+
logger$2.log('DOM observers disconnected');
|
|
4276
|
+
};
|
|
4277
|
+
}
|
|
3912
4278
|
|
|
4279
|
+
// cspell:ignore cooldown
|
|
3913
4280
|
/**
|
|
3914
4281
|
* Height Observer Processor
|
|
3915
4282
|
* Background observer — watches for iframe content height changes.
|
|
3916
4283
|
*/
|
|
3917
|
-
|
|
4284
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
4285
|
+
function readCurrentHeight(s) {
|
|
4286
|
+
if (!s.iframe?.contentDocument || !s.iframe?.contentWindow)
|
|
4287
|
+
return 0;
|
|
4288
|
+
const height = getFinalHeight(s.iframe.contentDocument, false);
|
|
4289
|
+
s.logger.log('Height:', height);
|
|
4290
|
+
return height;
|
|
4291
|
+
}
|
|
4292
|
+
function isBlocked(s) {
|
|
4293
|
+
return s.isProcessing || s.isCoolingDown || s.throttleTimeout !== null;
|
|
4294
|
+
}
|
|
4295
|
+
function hasHeightChanged(s, height) {
|
|
4296
|
+
return height !== s.lastHeight;
|
|
4297
|
+
}
|
|
4298
|
+
// ── Cooldown ──────────────────────────────────────────────────────────────────
|
|
4299
|
+
function startCooldown(s) {
|
|
4300
|
+
s.isCoolingDown = true;
|
|
4301
|
+
s.cooldownTimeout = setTimeout(() => {
|
|
4302
|
+
s.cooldownTimeout = null;
|
|
4303
|
+
s.isCoolingDown = false;
|
|
4304
|
+
}, s.cooldownMs);
|
|
4305
|
+
}
|
|
4306
|
+
function stopCooldown(s) {
|
|
4307
|
+
if (s.cooldownTimeout) {
|
|
4308
|
+
clearTimeout(s.cooldownTimeout);
|
|
4309
|
+
s.cooldownTimeout = null;
|
|
4310
|
+
}
|
|
4311
|
+
s.isCoolingDown = false;
|
|
4312
|
+
}
|
|
4313
|
+
// ── Debounce ──────────────────────────────────────────────────────────────────
|
|
4314
|
+
function cancelPendingDebounce(s) {
|
|
4315
|
+
if (s.debounceTimeout) {
|
|
4316
|
+
clearTimeout(s.debounceTimeout);
|
|
4317
|
+
s.debounceTimeout = null;
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
function processCurrentHeightIfChanged(s) {
|
|
4321
|
+
const height = readCurrentHeight(s);
|
|
4322
|
+
if (hasHeightChanged(s, height)) {
|
|
4323
|
+
processHeightChange(s, height);
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
function scheduleDebounce(s) {
|
|
4327
|
+
cancelPendingDebounce(s);
|
|
4328
|
+
s.debounceTimeout = setTimeout(() => {
|
|
4329
|
+
s.debounceTimeout = null;
|
|
4330
|
+
processCurrentHeightIfChanged(s);
|
|
4331
|
+
}, s.debounceMs);
|
|
4332
|
+
}
|
|
4333
|
+
// ── Throttle ──────────────────────────────────────────────────────────────────
|
|
4334
|
+
function scheduleThrottledCheck(s) {
|
|
4335
|
+
s.throttleTimeout = setTimeout(() => {
|
|
4336
|
+
s.throttleTimeout = null;
|
|
4337
|
+
const height = readCurrentHeight(s);
|
|
4338
|
+
if (!hasHeightChanged(s, height)) {
|
|
4339
|
+
cancelPendingDebounce(s);
|
|
4340
|
+
return;
|
|
4341
|
+
}
|
|
4342
|
+
s.logger.log(`Height changed: ${s.lastHeight}px -> ${height}px`);
|
|
4343
|
+
scheduleDebounce(s);
|
|
4344
|
+
}, s.throttleMs);
|
|
4345
|
+
}
|
|
4346
|
+
// ── Core ──────────────────────────────────────────────────────────────────────
|
|
4347
|
+
async function processHeightChange(s, newHeight) {
|
|
4348
|
+
if (!s.iframe || !s.config)
|
|
4349
|
+
return;
|
|
4350
|
+
s.isProcessing = true;
|
|
4351
|
+
s.logger.log(`Processing height change: ${newHeight}px`);
|
|
4352
|
+
try {
|
|
4353
|
+
const result = {
|
|
4354
|
+
height: newHeight,
|
|
4355
|
+
width: s.iframe.contentWindow?.innerWidth ?? 0,
|
|
4356
|
+
};
|
|
4357
|
+
s.lastHeight = newHeight;
|
|
4358
|
+
s.config.onHeightChange?.(result);
|
|
4359
|
+
window.dispatchEvent(new CustomEvent('iframe-dimensions-applied', { detail: result }));
|
|
4360
|
+
}
|
|
4361
|
+
catch (error) {
|
|
4362
|
+
s.logger.error('Failed to process height change:', error);
|
|
4363
|
+
s.config.onError?.(error);
|
|
4364
|
+
}
|
|
4365
|
+
finally {
|
|
4366
|
+
s.isProcessing = false;
|
|
4367
|
+
if (s.cooldownMs > 0)
|
|
4368
|
+
startCooldown(s);
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
function handleHeightChange(s) {
|
|
4372
|
+
if (isBlocked(s))
|
|
4373
|
+
return;
|
|
4374
|
+
scheduleThrottledCheck(s);
|
|
4375
|
+
}
|
|
4376
|
+
function attachObserver(s) {
|
|
4377
|
+
if (!s.iframe?.contentDocument?.body) {
|
|
4378
|
+
s.logger.warn('Cannot observe height changes: iframe body not found');
|
|
4379
|
+
return;
|
|
4380
|
+
}
|
|
4381
|
+
s.observerCleanup?.();
|
|
4382
|
+
const height = readCurrentHeight(s);
|
|
4383
|
+
s.logger.log('Initial height:', height);
|
|
4384
|
+
if (hasHeightChanged(s, height)) {
|
|
4385
|
+
processHeightChange(s, height);
|
|
4386
|
+
}
|
|
4387
|
+
else {
|
|
4388
|
+
s.lastHeight = height;
|
|
4389
|
+
}
|
|
4390
|
+
s.observerCleanup = setup(s.iframe.contentDocument, () => handleHeightChange(s));
|
|
4391
|
+
}
|
|
4392
|
+
function detachObserver(s) {
|
|
4393
|
+
s.observerCleanup?.();
|
|
4394
|
+
s.observerCleanup = null;
|
|
4395
|
+
}
|
|
4396
|
+
function clearAllTimers(s) {
|
|
4397
|
+
if (s.throttleTimeout) {
|
|
4398
|
+
clearTimeout(s.throttleTimeout);
|
|
4399
|
+
s.throttleTimeout = null;
|
|
4400
|
+
}
|
|
4401
|
+
if (s.debounceTimeout) {
|
|
4402
|
+
clearTimeout(s.debounceTimeout);
|
|
4403
|
+
s.debounceTimeout = null;
|
|
4404
|
+
}
|
|
4405
|
+
if (s.startDelayTimeout) {
|
|
4406
|
+
clearTimeout(s.startDelayTimeout);
|
|
4407
|
+
s.startDelayTimeout = null;
|
|
4408
|
+
}
|
|
4409
|
+
stopCooldown(s);
|
|
4410
|
+
}
|
|
4411
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────────
|
|
4412
|
+
function observeImmediately(s) {
|
|
4413
|
+
attachObserver(s);
|
|
4414
|
+
s.logger.log('Height observer started');
|
|
4415
|
+
}
|
|
4416
|
+
function observeAfterDelay(s, delayMs) {
|
|
4417
|
+
s.logger.log(`Height observer will start in ${delayMs}ms`);
|
|
4418
|
+
s.startDelayTimeout = setTimeout(() => {
|
|
4419
|
+
s.startDelayTimeout = null;
|
|
4420
|
+
if (!s.running)
|
|
4421
|
+
return;
|
|
4422
|
+
attachObserver(s);
|
|
4423
|
+
s.logger.log('Height observer started (after delay)');
|
|
4424
|
+
}, delayMs);
|
|
4425
|
+
}
|
|
4426
|
+
function start$5(s, iframe, cfg) {
|
|
4427
|
+
if (s.running) {
|
|
4428
|
+
s.logger.warn('Observer is already running. Call stop() first.');
|
|
4429
|
+
return;
|
|
4430
|
+
}
|
|
4431
|
+
s.iframe = iframe;
|
|
4432
|
+
s.config = cfg;
|
|
4433
|
+
s.throttleMs = cfg.throttleMs ?? 25;
|
|
4434
|
+
s.debounceMs = cfg.debounceMs ?? 500;
|
|
4435
|
+
s.cooldownMs = cfg.cooldownMs ?? 0;
|
|
4436
|
+
s.running = true;
|
|
4437
|
+
const startDelayMs = cfg.startDelayMs ?? 0;
|
|
4438
|
+
if (startDelayMs > 0) {
|
|
4439
|
+
observeAfterDelay(s, startDelayMs);
|
|
4440
|
+
}
|
|
4441
|
+
else {
|
|
4442
|
+
observeImmediately(s);
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
function stop$5(s) {
|
|
4446
|
+
if (!s.running)
|
|
4447
|
+
return;
|
|
4448
|
+
detachObserver(s);
|
|
4449
|
+
clearAllTimers(s);
|
|
4450
|
+
s.iframe = null;
|
|
4451
|
+
s.config = null;
|
|
4452
|
+
s.lastHeight = 0;
|
|
4453
|
+
s.isProcessing = false;
|
|
4454
|
+
s.isCoolingDown = false;
|
|
4455
|
+
s.running = false;
|
|
4456
|
+
s.logger.log('Height observer stopped');
|
|
4457
|
+
}
|
|
4458
|
+
function clear(s) {
|
|
4459
|
+
detachObserver(s);
|
|
4460
|
+
clearAllTimers(s);
|
|
4461
|
+
s.lastHeight = 0;
|
|
4462
|
+
s.isProcessing = false;
|
|
4463
|
+
}
|
|
4464
|
+
function updateConfig$3(s, cfg) {
|
|
4465
|
+
if (!s.running || !s.config) {
|
|
4466
|
+
s.logger.warn('Observer is not running.');
|
|
4467
|
+
return;
|
|
4468
|
+
}
|
|
4469
|
+
s.config = { ...s.config, ...cfg };
|
|
4470
|
+
if (cfg.throttleMs !== undefined)
|
|
4471
|
+
s.throttleMs = cfg.throttleMs;
|
|
4472
|
+
if (cfg.debounceMs !== undefined)
|
|
4473
|
+
s.debounceMs = cfg.debounceMs;
|
|
4474
|
+
if (cfg.cooldownMs !== undefined)
|
|
4475
|
+
s.cooldownMs = cfg.cooldownMs;
|
|
4476
|
+
s.logger.configure({ enabled: !!s.config.debug });
|
|
4477
|
+
s.logger.log('Config updated');
|
|
4478
|
+
}
|
|
4479
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
4480
|
+
function createHeightObserver() {
|
|
4481
|
+
const s = {
|
|
4482
|
+
logger: createLogger({ enabled: true, prefix: 'IframeHeightObserver' }),
|
|
4483
|
+
iframe: null,
|
|
4484
|
+
config: null,
|
|
4485
|
+
observerCleanup: null,
|
|
4486
|
+
lastHeight: 0,
|
|
4487
|
+
throttleTimeout: null,
|
|
4488
|
+
debounceTimeout: null,
|
|
4489
|
+
startDelayTimeout: null,
|
|
4490
|
+
cooldownTimeout: null,
|
|
4491
|
+
isProcessing: false,
|
|
4492
|
+
isCoolingDown: false,
|
|
4493
|
+
throttleMs: 25,
|
|
4494
|
+
debounceMs: 500,
|
|
4495
|
+
cooldownMs: 0,
|
|
4496
|
+
running: false,
|
|
4497
|
+
};
|
|
4498
|
+
return {
|
|
4499
|
+
start: (iframe, cfg) => start$5(s, iframe, cfg),
|
|
4500
|
+
stop: () => stop$5(s),
|
|
4501
|
+
observe: () => attachObserver(s),
|
|
4502
|
+
clear: () => clear(s),
|
|
4503
|
+
updateConfig: (cfg) => updateConfig$3(s, cfg),
|
|
4504
|
+
getCurrentHeight: () => s.lastHeight,
|
|
4505
|
+
isRunning: () => s.running,
|
|
4506
|
+
getStateInfo: () => ({
|
|
4507
|
+
isRunning: s.running,
|
|
4508
|
+
lastHeight: s.lastHeight,
|
|
4509
|
+
isProcessing: s.isProcessing,
|
|
4510
|
+
hasObservers: !!s.observerCleanup,
|
|
4511
|
+
}),
|
|
4512
|
+
};
|
|
4513
|
+
}
|
|
3918
4514
|
|
|
3919
|
-
|
|
4515
|
+
/**
|
|
4516
|
+
* Window-level event management for the navigation processor.
|
|
4517
|
+
*
|
|
4518
|
+
* Responsibilities:
|
|
4519
|
+
* - Subscribe to events dispatched by this processor (for logging/hooks)
|
|
4520
|
+
* - Dispatch navigation events to the parent window
|
|
4521
|
+
*/
|
|
4522
|
+
// ── Module-level functions ────────────────────────────────────────────────────
|
|
4523
|
+
function attach$1(s, debug) {
|
|
4524
|
+
s.logger.configure({ enabled: !!debug });
|
|
4525
|
+
s.navigationBlockedListener = (e) => {
|
|
4526
|
+
const ev = e;
|
|
4527
|
+
s.logger.log('Navigation blocked:', ev.detail.url);
|
|
4528
|
+
};
|
|
4529
|
+
s.formSubmitWindowListener = (e) => {
|
|
4530
|
+
const ev = e;
|
|
4531
|
+
s.logger.log('Form submitted:', ev.detail.data);
|
|
4532
|
+
};
|
|
4533
|
+
window.addEventListener('iframe-navigation-blocked', s.navigationBlockedListener);
|
|
4534
|
+
window.addEventListener('iframe-form-submit', s.formSubmitWindowListener);
|
|
4535
|
+
}
|
|
4536
|
+
function detach$1(s) {
|
|
4537
|
+
if (s.navigationBlockedListener) {
|
|
4538
|
+
window.removeEventListener('iframe-navigation-blocked', s.navigationBlockedListener);
|
|
4539
|
+
s.navigationBlockedListener = null;
|
|
4540
|
+
}
|
|
4541
|
+
if (s.formSubmitWindowListener) {
|
|
4542
|
+
window.removeEventListener('iframe-form-submit', s.formSubmitWindowListener);
|
|
4543
|
+
s.formSubmitWindowListener = null;
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
function dispatchBlocked(url, showMessage) {
|
|
4547
|
+
if (showMessage)
|
|
4548
|
+
alert(`Navigation blocked: ${url}`);
|
|
4549
|
+
window.dispatchEvent(new CustomEvent('iframe-navigation-blocked', { detail: { url } }));
|
|
4550
|
+
}
|
|
4551
|
+
function dispatchFormSubmit(form, data) {
|
|
4552
|
+
window.dispatchEvent(new CustomEvent('iframe-form-submit', { detail: { form, data } }));
|
|
4553
|
+
}
|
|
4554
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
4555
|
+
function createNavigationListeners() {
|
|
4556
|
+
const s = {
|
|
4557
|
+
logger: createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' }),
|
|
4558
|
+
navigationBlockedListener: null,
|
|
4559
|
+
formSubmitWindowListener: null,
|
|
4560
|
+
};
|
|
4561
|
+
return {
|
|
4562
|
+
attach: (debug) => attach$1(s, debug),
|
|
4563
|
+
detach: () => detach$1(s),
|
|
4564
|
+
dispatchBlocked,
|
|
4565
|
+
dispatchFormSubmit,
|
|
4566
|
+
};
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4569
|
+
const logger$1 = createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' });
|
|
3920
4570
|
function configure$1(debug) {
|
|
3921
|
-
logger$
|
|
4571
|
+
logger$1.configure({ enabled: debug });
|
|
3922
4572
|
}
|
|
3923
4573
|
// ─── DOM Utilities ────────────────────────────────────────────────────────────
|
|
3924
4574
|
function disableAllLinks(doc) {
|
|
@@ -3942,10 +4592,10 @@ function setupLinkBlocker(doc, isEnabled, onBlocked) {
|
|
|
3942
4592
|
return;
|
|
3943
4593
|
const href = link.getAttribute('href');
|
|
3944
4594
|
if (!href || href === '' || href === '#' || href.startsWith('#')) {
|
|
3945
|
-
logger$
|
|
4595
|
+
logger$1.log('Allowed hash navigation:', href);
|
|
3946
4596
|
return;
|
|
3947
4597
|
}
|
|
3948
|
-
logger$
|
|
4598
|
+
logger$1.log('Blocked link navigation to:', href);
|
|
3949
4599
|
e.preventDefault();
|
|
3950
4600
|
e.stopPropagation();
|
|
3951
4601
|
e.stopImmediatePropagation();
|
|
@@ -3959,7 +4609,7 @@ function setupLinkBlocker(doc, isEnabled, onBlocked) {
|
|
|
3959
4609
|
return;
|
|
3960
4610
|
const href = link.getAttribute('href');
|
|
3961
4611
|
if (href && !href.startsWith('#')) {
|
|
3962
|
-
logger$
|
|
4612
|
+
logger$1.log('Blocked auxclick navigation');
|
|
3963
4613
|
e.preventDefault();
|
|
3964
4614
|
e.stopPropagation();
|
|
3965
4615
|
e.stopImmediatePropagation();
|
|
@@ -3980,7 +4630,7 @@ function setupFormBlocker(doc, isEnabled, onBlocked, onFormSubmit) {
|
|
|
3980
4630
|
const form = e.target;
|
|
3981
4631
|
const action = form.getAttribute('action');
|
|
3982
4632
|
if (!action || action === '' || action === '#') {
|
|
3983
|
-
logger$
|
|
4633
|
+
logger$1.log('Allowed same-page form');
|
|
3984
4634
|
e.preventDefault();
|
|
3985
4635
|
const data = {};
|
|
3986
4636
|
new FormData(form).forEach((value, key) => {
|
|
@@ -3989,7 +4639,7 @@ function setupFormBlocker(doc, isEnabled, onBlocked, onFormSubmit) {
|
|
|
3989
4639
|
onFormSubmit(form, data);
|
|
3990
4640
|
return;
|
|
3991
4641
|
}
|
|
3992
|
-
logger$
|
|
4642
|
+
logger$1.log('Blocked form submission to:', action);
|
|
3993
4643
|
e.preventDefault();
|
|
3994
4644
|
e.stopPropagation();
|
|
3995
4645
|
e.stopImmediatePropagation();
|
|
@@ -4003,7 +4653,7 @@ function setupWindowOpenBlocker(win, originalOpen, isEnabled, onBlocked) {
|
|
|
4003
4653
|
if (!isEnabled())
|
|
4004
4654
|
return originalOpen(...args);
|
|
4005
4655
|
const url = args[0]?.toString() || 'popup';
|
|
4006
|
-
logger$
|
|
4656
|
+
logger$1.log('Blocked window.open:', url);
|
|
4007
4657
|
onBlocked(url);
|
|
4008
4658
|
return null;
|
|
4009
4659
|
});
|
|
@@ -4015,14 +4665,14 @@ function setupUnloadBlocker(win, isEnabled) {
|
|
|
4015
4665
|
const beforeUnloadListener = (e) => {
|
|
4016
4666
|
if (!isEnabled())
|
|
4017
4667
|
return;
|
|
4018
|
-
logger$
|
|
4668
|
+
logger$1.log('Blocked beforeunload');
|
|
4019
4669
|
e.preventDefault();
|
|
4020
4670
|
e.returnValue = '';
|
|
4021
4671
|
};
|
|
4022
4672
|
const unloadListener = (e) => {
|
|
4023
4673
|
if (!isEnabled())
|
|
4024
4674
|
return;
|
|
4025
|
-
logger$
|
|
4675
|
+
logger$1.log('Blocked unload');
|
|
4026
4676
|
e.preventDefault();
|
|
4027
4677
|
e.stopPropagation();
|
|
4028
4678
|
};
|
|
@@ -4039,65 +4689,14 @@ function setupDOMMonitor(doc) {
|
|
|
4039
4689
|
return () => observer.disconnect();
|
|
4040
4690
|
}
|
|
4041
4691
|
|
|
4042
|
-
/**
|
|
4043
|
-
* Window-level event management for the navigation processor.
|
|
4044
|
-
*
|
|
4045
|
-
* Responsibilities:
|
|
4046
|
-
* - Subscribe to events dispatched by this processor (for logging/hooks)
|
|
4047
|
-
* - Dispatch navigation events to the parent window
|
|
4048
|
-
*/
|
|
4049
|
-
const logger$7 = createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' });
|
|
4050
|
-
// ─── State ────────────────────────────────────────────────────────────────────
|
|
4051
|
-
let navigationBlockedListener = null;
|
|
4052
|
-
let formSubmitWindowListener = null;
|
|
4053
|
-
// ─── Subscriptions ────────────────────────────────────────────────────────────
|
|
4054
|
-
function attach$1(debug) {
|
|
4055
|
-
logger$7.configure({ enabled: !!debug });
|
|
4056
|
-
navigationBlockedListener = (e) => {
|
|
4057
|
-
const ev = e;
|
|
4058
|
-
logger$7.log('Navigation blocked:', ev.detail.url);
|
|
4059
|
-
};
|
|
4060
|
-
formSubmitWindowListener = (e) => {
|
|
4061
|
-
const ev = e;
|
|
4062
|
-
logger$7.log('Form submitted:', ev.detail.data);
|
|
4063
|
-
};
|
|
4064
|
-
window.addEventListener('iframe-navigation-blocked', navigationBlockedListener);
|
|
4065
|
-
window.addEventListener('iframe-form-submit', formSubmitWindowListener);
|
|
4066
|
-
}
|
|
4067
|
-
function detach$1() {
|
|
4068
|
-
if (navigationBlockedListener) {
|
|
4069
|
-
window.removeEventListener('iframe-navigation-blocked', navigationBlockedListener);
|
|
4070
|
-
navigationBlockedListener = null;
|
|
4071
|
-
}
|
|
4072
|
-
if (formSubmitWindowListener) {
|
|
4073
|
-
window.removeEventListener('iframe-form-submit', formSubmitWindowListener);
|
|
4074
|
-
formSubmitWindowListener = null;
|
|
4075
|
-
}
|
|
4076
|
-
}
|
|
4077
|
-
// ─── Dispatchers ─────────────────────────────────────────────────────────────
|
|
4078
|
-
function dispatchBlocked(url, showMessage) {
|
|
4079
|
-
if (showMessage)
|
|
4080
|
-
alert(`Navigation blocked: ${url}`);
|
|
4081
|
-
window.dispatchEvent(new CustomEvent('iframe-navigation-blocked', { detail: { url } }));
|
|
4082
|
-
}
|
|
4083
|
-
function dispatchFormSubmit(form, data) {
|
|
4084
|
-
window.dispatchEvent(new CustomEvent('iframe-form-submit', { detail: { form, data } }));
|
|
4085
|
-
}
|
|
4086
|
-
|
|
4087
4692
|
/**
|
|
4088
4693
|
* Navigation Processor
|
|
4089
4694
|
* Continuous guard — blocks all navigation attempts within the iframe.
|
|
4090
4695
|
*/
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
let running$4 = false;
|
|
4096
|
-
let cleanups = [];
|
|
4097
|
-
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
4098
|
-
function start$5(iframe, cfg) {
|
|
4099
|
-
if (running$4) {
|
|
4100
|
-
logger$6.warn('Blocker is already running. Call stop() first.');
|
|
4696
|
+
// ── Module-level functions ────────────────────────────────────────────────────
|
|
4697
|
+
function start$4(s, iframe, cfg) {
|
|
4698
|
+
if (s.running) {
|
|
4699
|
+
s.logger.warn('Blocker is already running. Call stop() first.');
|
|
4101
4700
|
return;
|
|
4102
4701
|
}
|
|
4103
4702
|
if (!iframe.contentDocument || !iframe.contentWindow) {
|
|
@@ -4106,76 +4705,114 @@ function start$5(iframe, cfg) {
|
|
|
4106
4705
|
const doc = iframe.contentDocument;
|
|
4107
4706
|
const win = iframe.contentWindow;
|
|
4108
4707
|
const originalOpen = win.open.bind(win);
|
|
4109
|
-
logger
|
|
4708
|
+
s.logger.configure({ enabled: !!cfg?.debug });
|
|
4110
4709
|
configure$1(!!cfg?.debug);
|
|
4111
|
-
cleanups = [
|
|
4112
|
-
setupLinkBlocker(doc, () => isEnabled, (url) => dispatchBlocked(url, showMessage)),
|
|
4113
|
-
setupFormBlocker(doc, () => isEnabled, (url) => dispatchBlocked(url, showMessage), dispatchFormSubmit),
|
|
4114
|
-
setupWindowOpenBlocker(win, originalOpen, () => isEnabled, (url) => dispatchBlocked(url, showMessage)),
|
|
4115
|
-
setupUnloadBlocker(win, () => isEnabled),
|
|
4710
|
+
s.cleanups = [
|
|
4711
|
+
setupLinkBlocker(doc, () => s.isEnabled, (url) => s.listeners.dispatchBlocked(url, s.showMessage)),
|
|
4712
|
+
setupFormBlocker(doc, () => s.isEnabled, (url) => s.listeners.dispatchBlocked(url, s.showMessage), s.listeners.dispatchFormSubmit),
|
|
4713
|
+
setupWindowOpenBlocker(win, originalOpen, () => s.isEnabled, (url) => s.listeners.dispatchBlocked(url, s.showMessage)),
|
|
4714
|
+
setupUnloadBlocker(win, () => s.isEnabled),
|
|
4116
4715
|
setupDOMMonitor(doc),
|
|
4117
4716
|
];
|
|
4118
|
-
attach
|
|
4119
|
-
running
|
|
4120
|
-
logger
|
|
4717
|
+
s.listeners.attach(cfg?.debug);
|
|
4718
|
+
s.running = true;
|
|
4719
|
+
s.logger.log('Started');
|
|
4121
4720
|
}
|
|
4122
|
-
function stop$
|
|
4123
|
-
if (!running
|
|
4721
|
+
function stop$4(s) {
|
|
4722
|
+
if (!s.running)
|
|
4124
4723
|
return;
|
|
4125
|
-
cleanups.forEach((fn) => fn());
|
|
4126
|
-
cleanups = [];
|
|
4127
|
-
detach
|
|
4128
|
-
isEnabled = false;
|
|
4129
|
-
showMessage = false;
|
|
4130
|
-
running
|
|
4131
|
-
logger
|
|
4132
|
-
}
|
|
4133
|
-
function enable() {
|
|
4134
|
-
if (!running
|
|
4135
|
-
logger
|
|
4724
|
+
s.cleanups.forEach((fn) => fn());
|
|
4725
|
+
s.cleanups = [];
|
|
4726
|
+
s.listeners.detach();
|
|
4727
|
+
s.isEnabled = false;
|
|
4728
|
+
s.showMessage = false;
|
|
4729
|
+
s.running = false;
|
|
4730
|
+
s.logger.log('Stopped');
|
|
4731
|
+
}
|
|
4732
|
+
function enable(s) {
|
|
4733
|
+
if (!s.running) {
|
|
4734
|
+
s.logger.warn('Blocker is not running.');
|
|
4136
4735
|
return;
|
|
4137
4736
|
}
|
|
4138
|
-
isEnabled = true;
|
|
4139
|
-
logger
|
|
4737
|
+
s.isEnabled = true;
|
|
4738
|
+
s.logger.log('Navigation blocking enabled');
|
|
4739
|
+
}
|
|
4740
|
+
function disable(s) {
|
|
4741
|
+
if (!s.running) {
|
|
4742
|
+
s.logger.warn('Blocker is not running.');
|
|
4743
|
+
return;
|
|
4744
|
+
}
|
|
4745
|
+
s.isEnabled = false;
|
|
4746
|
+
s.logger.log('Navigation blocking disabled');
|
|
4747
|
+
}
|
|
4748
|
+
function enableMessage(s) {
|
|
4749
|
+
if (!s.running) {
|
|
4750
|
+
s.logger.warn('Blocker is not running.');
|
|
4751
|
+
return;
|
|
4752
|
+
}
|
|
4753
|
+
s.showMessage = true;
|
|
4754
|
+
s.logger.log('Navigation blocking message enabled');
|
|
4755
|
+
}
|
|
4756
|
+
function disableMessage(s) {
|
|
4757
|
+
if (!s.running) {
|
|
4758
|
+
s.logger.warn('Blocker is not running.');
|
|
4759
|
+
return;
|
|
4760
|
+
}
|
|
4761
|
+
s.showMessage = false;
|
|
4762
|
+
s.logger.log('Navigation blocking message disabled');
|
|
4763
|
+
}
|
|
4764
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
4765
|
+
function createNavigationBlocker() {
|
|
4766
|
+
const s = {
|
|
4767
|
+
logger: createLogger({ enabled: false, prefix: 'IframeNavigationBlocker' }),
|
|
4768
|
+
listeners: createNavigationListeners(),
|
|
4769
|
+
isEnabled: false,
|
|
4770
|
+
showMessage: false,
|
|
4771
|
+
running: false,
|
|
4772
|
+
cleanups: [],
|
|
4773
|
+
};
|
|
4774
|
+
return {
|
|
4775
|
+
start: (iframe, cfg) => start$4(s, iframe, cfg),
|
|
4776
|
+
stop: () => stop$4(s),
|
|
4777
|
+
enable: () => enable(s),
|
|
4778
|
+
disable: () => disable(s),
|
|
4779
|
+
enableMessage: () => enableMessage(s),
|
|
4780
|
+
disableMessage: () => disableMessage(s),
|
|
4781
|
+
isRunning: () => s.running,
|
|
4782
|
+
isBlockingEnabled: () => s.isEnabled,
|
|
4783
|
+
getStateInfo: () => ({ isRunning: s.running, isEnabled: s.isEnabled, showMessage: s.showMessage }),
|
|
4784
|
+
};
|
|
4140
4785
|
}
|
|
4141
4786
|
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4787
|
+
// ── Module-level functions ────────────────────────────────────────────────────
|
|
4788
|
+
function attach(s, debug) {
|
|
4789
|
+
s.logger.configure({ enabled: !!debug });
|
|
4790
|
+
s.dimensionsListener = (e) => {
|
|
4791
|
+
// const ev = e as CustomEvent<IframeDimensionsDetail>;
|
|
4792
|
+
// s.logger.log('Dimensions applied:', ev.detail);
|
|
4793
|
+
};
|
|
4794
|
+
window.addEventListener('iframe-dimensions-applied', s.dimensionsListener);
|
|
4149
4795
|
}
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4796
|
+
function detach(s) {
|
|
4797
|
+
if (s.dimensionsListener) {
|
|
4798
|
+
window.removeEventListener('iframe-dimensions-applied', s.dimensionsListener);
|
|
4799
|
+
s.dimensionsListener = null;
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
4803
|
+
function createViewportListeners() {
|
|
4804
|
+
const s = {
|
|
4805
|
+
logger: createLogger({ enabled: false, prefix: 'ViewportReplacer' }),
|
|
4806
|
+
dimensionsListener: null,
|
|
4807
|
+
};
|
|
4808
|
+
return {
|
|
4809
|
+
attach: (debug) => attach(s, debug),
|
|
4810
|
+
detach: () => detach(s),
|
|
4811
|
+
};
|
|
4163
4812
|
}
|
|
4164
4813
|
|
|
4165
|
-
/**
|
|
4166
|
-
* Computed Style Enforcer Module
|
|
4167
|
-
* Enforces computed CSS styles with viewport unit verification
|
|
4168
|
-
* @module computed-style-enforcer
|
|
4169
|
-
*/
|
|
4170
|
-
const logger$5 = createLogger({
|
|
4171
|
-
enabled: false,
|
|
4172
|
-
prefix: 'ComputedStyleEnforcer',
|
|
4173
|
-
});
|
|
4174
|
-
// ============================================================================
|
|
4175
|
-
// Constants
|
|
4176
|
-
// ============================================================================
|
|
4177
4814
|
const DEFAULT_TOLERANCE_PX = 5;
|
|
4178
|
-
const VIEWPORT_UNIT_REGEX = /([
|
|
4815
|
+
const VIEWPORT_UNIT_REGEX = /([-.\\d]+)(vh|svh|lvh|dvh|%)/gi;
|
|
4179
4816
|
const DEFAULT_CSS_VALUES = ['none', 'auto', 'normal', '0px'];
|
|
4180
4817
|
const CRITICAL_PROPERTIES = [
|
|
4181
4818
|
'display',
|
|
@@ -4212,423 +4849,215 @@ const CRITICAL_PROPERTIES = [
|
|
|
4212
4849
|
'grid-template-rows',
|
|
4213
4850
|
'gap',
|
|
4214
4851
|
];
|
|
4215
|
-
|
|
4216
|
-
//
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
let win$1 = null;
|
|
4220
|
-
let config$2 = null;
|
|
4221
|
-
const elementsWithViewportUnits$1 = new Set();
|
|
4222
|
-
let originalValues$1 = new WeakMap();
|
|
4223
|
-
let running$3 = false;
|
|
4224
|
-
// ============================================================================
|
|
4225
|
-
// Helper Functions
|
|
4226
|
-
// ============================================================================
|
|
4227
|
-
/**
|
|
4228
|
-
* Get viewport unit map for conversion from current state
|
|
4229
|
-
*/
|
|
4230
|
-
function getViewportUnitMap() {
|
|
4231
|
-
if (!config$2) {
|
|
4852
|
+
|
|
4853
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
4854
|
+
function getViewportUnitMap(s) {
|
|
4855
|
+
if (!s.config)
|
|
4232
4856
|
throw new Error('Config is not initialized');
|
|
4233
|
-
}
|
|
4234
4857
|
return {
|
|
4235
|
-
vh: config
|
|
4236
|
-
svh: config
|
|
4237
|
-
lvh: config
|
|
4238
|
-
dvh: config
|
|
4239
|
-
vw: config
|
|
4240
|
-
svw: config
|
|
4241
|
-
lvw: config
|
|
4242
|
-
dvw: config
|
|
4858
|
+
vh: s.config.targetHeight,
|
|
4859
|
+
svh: s.config.targetHeight,
|
|
4860
|
+
lvh: s.config.targetHeight,
|
|
4861
|
+
dvh: s.config.targetHeight,
|
|
4862
|
+
vw: s.config.targetWidth,
|
|
4863
|
+
svw: s.config.targetWidth,
|
|
4864
|
+
lvw: s.config.targetWidth,
|
|
4865
|
+
dvw: s.config.targetWidth,
|
|
4243
4866
|
};
|
|
4244
4867
|
}
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
*/
|
|
4248
|
-
function calculateExpectedPx(value, unit) {
|
|
4249
|
-
if (!config$2) {
|
|
4868
|
+
function calculateExpectedPx(s, value, unit) {
|
|
4869
|
+
if (!s.config)
|
|
4250
4870
|
throw new Error('Config is not initialized');
|
|
4251
|
-
}
|
|
4252
4871
|
const unitLower = unit.toLowerCase();
|
|
4253
|
-
if (unitLower === '%')
|
|
4254
|
-
return (value / 100) * config
|
|
4255
|
-
|
|
4256
|
-
const unitMap = getViewportUnitMap();
|
|
4257
|
-
return (value / 100) * (unitMap[unitLower] || 0);
|
|
4872
|
+
if (unitLower === '%')
|
|
4873
|
+
return (value / 100) * s.config.targetHeight;
|
|
4874
|
+
return (value / 100) * (getViewportUnitMap(s)[unitLower] || 0);
|
|
4258
4875
|
}
|
|
4259
|
-
/**
|
|
4260
|
-
* Check if a CSS value is a default/initial value that should be skipped
|
|
4261
|
-
*/
|
|
4262
4876
|
function isDefaultCssValue(value) {
|
|
4263
4877
|
return DEFAULT_CSS_VALUES.includes(value);
|
|
4264
4878
|
}
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
* Return true ONLY if computed value matches target config (within tolerance)
|
|
4268
|
-
* Return false if computed value is different - don't override correct values
|
|
4269
|
-
*/
|
|
4270
|
-
function shouldReplaceValue(computedValue, originalValue, tolerance = DEFAULT_TOLERANCE_PX) {
|
|
4271
|
-
if (!config$2) {
|
|
4879
|
+
function shouldReplaceValue(s, computedValue, originalValue, tolerance = DEFAULT_TOLERANCE_PX) {
|
|
4880
|
+
if (!s.config)
|
|
4272
4881
|
return false;
|
|
4273
|
-
}
|
|
4274
|
-
// Parse computed value (should be in px)
|
|
4275
4882
|
const computedPx = parseFloat(computedValue);
|
|
4276
|
-
if (isNaN(computedPx))
|
|
4277
|
-
return false;
|
|
4278
|
-
}
|
|
4279
|
-
// Parse original value to check what it should be
|
|
4883
|
+
if (isNaN(computedPx))
|
|
4884
|
+
return false;
|
|
4280
4885
|
const regex = new RegExp(VIEWPORT_UNIT_REGEX.source, VIEWPORT_UNIT_REGEX.flags);
|
|
4281
4886
|
const match = originalValue.match(regex);
|
|
4282
|
-
if (!match)
|
|
4283
|
-
return false;
|
|
4284
|
-
}
|
|
4887
|
+
if (!match)
|
|
4888
|
+
return false;
|
|
4285
4889
|
const [, value, unit] = match;
|
|
4286
4890
|
const num = parseFloat(value);
|
|
4287
|
-
if (isNaN(num))
|
|
4891
|
+
if (isNaN(num))
|
|
4288
4892
|
return false;
|
|
4289
|
-
|
|
4290
|
-
// Calculate expected value based on unit and target config
|
|
4291
|
-
const expectedPx = calculateExpectedPx(num, unit);
|
|
4292
|
-
// Check if computed value matches expected value (within tolerance)
|
|
4893
|
+
const expectedPx = calculateExpectedPx(s, num, unit);
|
|
4293
4894
|
const diff = Math.abs(computedPx - expectedPx);
|
|
4294
4895
|
if (diff <= tolerance) {
|
|
4295
|
-
|
|
4296
|
-
logger$5.log(`OK to replace: computed=${computedValue} matches expected=${expectedPx.toFixed(2)}px (diff=${diff.toFixed(2)}px)`);
|
|
4896
|
+
s.logger.log(`OK to replace: computed=${computedValue} matches expected=${expectedPx.toFixed(2)}px (diff=${diff.toFixed(2)}px)`);
|
|
4297
4897
|
return true;
|
|
4298
4898
|
}
|
|
4299
|
-
|
|
4300
|
-
logger$5.log(`Skip replace: computed=${computedValue} differs from expected=${expectedPx.toFixed(2)}px (diff=${diff.toFixed(2)}px) - keeping current value`);
|
|
4899
|
+
s.logger.log(`Skip replace: computed=${computedValue} differs from expected=${expectedPx.toFixed(2)}px (diff=${diff.toFixed(2)}px)`);
|
|
4301
4900
|
return false;
|
|
4302
4901
|
}
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
*/
|
|
4306
|
-
function applyPropertyWithVerification(element, prop, computedValue, originalValue, tolerance = DEFAULT_TOLERANCE_PX) {
|
|
4307
|
-
// Verify before replacing - only replace if computed value matches target config
|
|
4308
|
-
if (originalValue && shouldReplaceValue(computedValue, originalValue, tolerance)) {
|
|
4902
|
+
function applyPropertyWithVerification(s, element, prop, computedValue, originalValue, tolerance = DEFAULT_TOLERANCE_PX) {
|
|
4903
|
+
if (originalValue && shouldReplaceValue(s, computedValue, originalValue, tolerance)) {
|
|
4309
4904
|
element.style.setProperty(prop, computedValue, 'important');
|
|
4310
4905
|
return true;
|
|
4311
4906
|
}
|
|
4312
4907
|
else if (!originalValue) {
|
|
4313
|
-
// No original value tracked, use old behavior
|
|
4314
4908
|
element.style.setProperty(prop, computedValue, 'important');
|
|
4315
4909
|
return true;
|
|
4316
4910
|
}
|
|
4317
4911
|
return false;
|
|
4318
4912
|
}
|
|
4319
|
-
//
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
* Start the computed style enforcer
|
|
4324
|
-
* Initialize with iframe document and config
|
|
4325
|
-
*/
|
|
4326
|
-
function start$4(d, w, cfg, options = {}) {
|
|
4327
|
-
if (running$3) {
|
|
4328
|
-
logger$5.warn('Enforcer is already running. Call stop() first.');
|
|
4913
|
+
// ── Exported module-level functions ───────────────────────────────────────────
|
|
4914
|
+
function start$3(s, d, w, cfg, options = {}) {
|
|
4915
|
+
if (s.running) {
|
|
4916
|
+
s.logger.warn('Enforcer is already running. Call stop() first.');
|
|
4329
4917
|
return;
|
|
4330
4918
|
}
|
|
4331
|
-
doc
|
|
4332
|
-
win
|
|
4333
|
-
config
|
|
4334
|
-
running
|
|
4335
|
-
logger
|
|
4336
|
-
logger
|
|
4919
|
+
s.doc = d;
|
|
4920
|
+
s.win = w;
|
|
4921
|
+
s.config = cfg;
|
|
4922
|
+
s.running = true;
|
|
4923
|
+
s.logger.configure({ enabled: !!options.debug });
|
|
4924
|
+
s.logger.log('Started');
|
|
4337
4925
|
}
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4926
|
+
function stop$3(s) {
|
|
4927
|
+
if (!s.running)
|
|
4928
|
+
return;
|
|
4929
|
+
s.doc = null;
|
|
4930
|
+
s.win = null;
|
|
4931
|
+
s.config = null;
|
|
4932
|
+
s.elementsWithViewportUnits.clear();
|
|
4933
|
+
s.originalValues = new WeakMap();
|
|
4934
|
+
s.running = false;
|
|
4935
|
+
s.logger.log('Stopped');
|
|
4936
|
+
}
|
|
4937
|
+
function reset(s) {
|
|
4938
|
+
if (!s.running) {
|
|
4939
|
+
s.logger.warn('Enforcer is not running. Call start() first.');
|
|
4343
4940
|
return;
|
|
4344
4941
|
}
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
elementsWithViewportUnits$1.clear();
|
|
4349
|
-
originalValues$1 = new WeakMap();
|
|
4350
|
-
running$3 = false;
|
|
4351
|
-
logger$5.log('Computed style enforcer stopped');
|
|
4942
|
+
s.elementsWithViewportUnits.clear();
|
|
4943
|
+
s.originalValues = new WeakMap();
|
|
4944
|
+
s.logger.log('Reset');
|
|
4352
4945
|
}
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
*/
|
|
4357
|
-
function trackElement(element, propertyOriginalValues) {
|
|
4358
|
-
if (!running$3) {
|
|
4359
|
-
logger$5.warn('Enforcer is not running. Call start() first.');
|
|
4946
|
+
function trackElement(s, element, propertyOriginalValues) {
|
|
4947
|
+
if (!s.running) {
|
|
4948
|
+
s.logger.warn('Enforcer is not running. Call start() first.');
|
|
4360
4949
|
return;
|
|
4361
4950
|
}
|
|
4362
|
-
elementsWithViewportUnits
|
|
4363
|
-
|
|
4364
|
-
let elementOriginals = originalValues$1.get(element);
|
|
4951
|
+
s.elementsWithViewportUnits.add(element);
|
|
4952
|
+
let elementOriginals = s.originalValues.get(element);
|
|
4365
4953
|
if (!elementOriginals) {
|
|
4366
4954
|
elementOriginals = new Map();
|
|
4367
|
-
originalValues
|
|
4955
|
+
s.originalValues.set(element, elementOriginals);
|
|
4368
4956
|
}
|
|
4369
|
-
// Merge property original values (don't override existing)
|
|
4370
4957
|
propertyOriginalValues.forEach((value, prop) => {
|
|
4371
|
-
if (!elementOriginals.has(prop))
|
|
4958
|
+
if (!elementOriginals.has(prop))
|
|
4372
4959
|
elementOriginals.set(prop, value);
|
|
4373
|
-
}
|
|
4374
4960
|
});
|
|
4375
4961
|
}
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
function processElement(element, options = {}) {
|
|
4380
|
-
if (!running$3 || !doc$1 || !win$1 || !config$2) {
|
|
4381
|
-
logger$5.warn('Enforcer is not running. Call start() first.');
|
|
4962
|
+
function processElement(s, element, options = {}) {
|
|
4963
|
+
if (!s.running || !s.doc || !s.win || !s.config) {
|
|
4964
|
+
s.logger.warn('Enforcer is not running.');
|
|
4382
4965
|
return 0;
|
|
4383
4966
|
}
|
|
4384
|
-
if (!elementsWithViewportUnits
|
|
4967
|
+
if (!s.elementsWithViewportUnits.has(element))
|
|
4385
4968
|
return 0;
|
|
4386
|
-
}
|
|
4387
4969
|
const htmlElement = element;
|
|
4388
|
-
const computed = win
|
|
4970
|
+
const computed = s.win.getComputedStyle(htmlElement);
|
|
4389
4971
|
const inlineStyle = htmlElement.style;
|
|
4390
|
-
const elementOriginals = originalValues
|
|
4972
|
+
const elementOriginals = s.originalValues.get(element);
|
|
4391
4973
|
const tolerance = options.tolerance ?? DEFAULT_TOLERANCE_PX;
|
|
4392
4974
|
let count = 0;
|
|
4393
4975
|
CRITICAL_PROPERTIES.forEach((prop) => {
|
|
4394
4976
|
const computedValue = computed.getPropertyValue(prop);
|
|
4395
4977
|
const inlineValue = inlineStyle.getPropertyValue(prop);
|
|
4396
|
-
if (computedValue && (!inlineValue || inlineValue !== computedValue)) {
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
if (applyPropertyWithVerification(htmlElement, prop, computedValue, originalValue, tolerance)) {
|
|
4400
|
-
count++;
|
|
4401
|
-
}
|
|
4402
|
-
}
|
|
4403
|
-
}
|
|
4404
|
-
});
|
|
4405
|
-
return count;
|
|
4406
|
-
}
|
|
4407
|
-
/**
|
|
4408
|
-
* Process all tracked elements
|
|
4409
|
-
* Enforce computed styles to inline for all elements with viewport units
|
|
4410
|
-
*/
|
|
4411
|
-
function processAll(options = {}) {
|
|
4412
|
-
if (!running$3 || !doc$1 || !win$1 || !config$2) {
|
|
4413
|
-
logger$5.warn('Enforcer is not running. Call start() first.');
|
|
4414
|
-
return 0;
|
|
4415
|
-
}
|
|
4416
|
-
let totalCount = 0;
|
|
4417
|
-
elementsWithViewportUnits$1.forEach((element) => {
|
|
4418
|
-
totalCount += processElement(element, options);
|
|
4419
|
-
});
|
|
4420
|
-
logger$5.log(`Enforced ${totalCount} computed styles for ${elementsWithViewportUnits$1.size} elements`);
|
|
4421
|
-
return totalCount;
|
|
4422
|
-
}
|
|
4423
|
-
|
|
4424
|
-
/**
|
|
4425
|
-
* Core viewport unit replacement logic.
|
|
4426
|
-
* Converts vh/vw/svh/dvh/% to pixel values across all CSS in the iframe.
|
|
4427
|
-
*/
|
|
4428
|
-
const logger$4 = createLogger({ enabled: false, prefix: 'ViewportUnitReplacer' });
|
|
4429
|
-
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
4430
|
-
const HEIGHT_RELATED_PROPERTIES = ['height', 'min-height', 'max-height', 'top', 'bottom'];
|
|
4431
|
-
// ─── Per-run tracking state (reset on each process() call) ───────────────────
|
|
4432
|
-
let elementsWithViewportUnits = new Set();
|
|
4433
|
-
let originalValues = new WeakMap();
|
|
4434
|
-
// ─── Regex ────────────────────────────────────────────────────────────────────
|
|
4435
|
-
/** Fresh instance every call — avoids shared lastIndex state with the g flag. */
|
|
4436
|
-
function createRegex() {
|
|
4437
|
-
return /([-.\\d]+)(vh|svh|lvh|dvh|vw|svw|lvw|dvw)/gi;
|
|
4438
|
-
}
|
|
4439
|
-
// ─── Unit conversion ─────────────────────────────────────────────────────────
|
|
4440
|
-
function px(value) {
|
|
4441
|
-
return `${value.toFixed(2)}px`;
|
|
4442
|
-
}
|
|
4443
|
-
function getUnitMap(ctx) {
|
|
4444
|
-
return {
|
|
4445
|
-
vh: ctx.targetHeight,
|
|
4446
|
-
svh: ctx.targetHeight,
|
|
4447
|
-
lvh: ctx.targetHeight,
|
|
4448
|
-
dvh: ctx.targetHeight,
|
|
4449
|
-
vw: ctx.targetWidth,
|
|
4450
|
-
svw: ctx.targetWidth,
|
|
4451
|
-
lvw: ctx.targetWidth,
|
|
4452
|
-
dvw: ctx.targetWidth,
|
|
4453
|
-
};
|
|
4454
|
-
}
|
|
4455
|
-
function toPx(value, unit, ctx) {
|
|
4456
|
-
const u = unit.toLowerCase();
|
|
4457
|
-
if (u === '%')
|
|
4458
|
-
return (value / 100) * ctx.targetHeight;
|
|
4459
|
-
return (value / 100) * (getUnitMap(ctx)[u] ?? 0);
|
|
4460
|
-
}
|
|
4461
|
-
function convert(value, unit, ctx) {
|
|
4462
|
-
const num = parseFloat(value);
|
|
4463
|
-
return isNaN(num) ? value : px(toPx(num, unit, ctx));
|
|
4464
|
-
}
|
|
4465
|
-
function isHeightRelated(prop) {
|
|
4466
|
-
return HEIGHT_RELATED_PROPERTIES.includes(prop);
|
|
4467
|
-
}
|
|
4468
|
-
/**
|
|
4469
|
-
* Use `matchOffset` (from replace() callback) instead of indexOf to get the
|
|
4470
|
-
* exact position of the current match — avoids false matches for duplicate values.
|
|
4471
|
-
*/
|
|
4472
|
-
function extractProperty(cssText, matchOffset) {
|
|
4473
|
-
const before = cssText.substring(0, matchOffset);
|
|
4474
|
-
const m = before.match(/([a-z-]+)\s*:\s*[^;{}]*$/i);
|
|
4475
|
-
return m ? m[1].toLowerCase() : '';
|
|
4476
|
-
}
|
|
4477
|
-
function replaceInText(cssText, ctx) {
|
|
4478
|
-
return cssText.replace(createRegex(), (match, value, unit, offset) => {
|
|
4479
|
-
if (unit === '%') {
|
|
4480
|
-
return isHeightRelated(extractProperty(cssText, offset)) ? convert(value, unit, ctx) : match;
|
|
4481
|
-
}
|
|
4482
|
-
return convert(value, unit, ctx);
|
|
4483
|
-
});
|
|
4484
|
-
}
|
|
4485
|
-
// ─── Element tracking ─────────────────────────────────────────────────────────
|
|
4486
|
-
function trackSelector(selector, propOriginals, ctx) {
|
|
4487
|
-
try {
|
|
4488
|
-
ctx.doc.querySelectorAll(selector).forEach((el) => {
|
|
4489
|
-
elementsWithViewportUnits.add(el);
|
|
4490
|
-
let originals = originalValues.get(el);
|
|
4491
|
-
if (!originals) {
|
|
4492
|
-
originals = new Map();
|
|
4493
|
-
originalValues.set(el, originals);
|
|
4494
|
-
}
|
|
4495
|
-
propOriginals.forEach((v, k) => {
|
|
4496
|
-
if (!originals.has(k))
|
|
4497
|
-
originals.set(k, v);
|
|
4498
|
-
});
|
|
4499
|
-
trackElement(el, propOriginals);
|
|
4500
|
-
});
|
|
4501
|
-
}
|
|
4502
|
-
catch {
|
|
4503
|
-
logger$4.warn('Invalid selector, skipping:', selector);
|
|
4504
|
-
}
|
|
4505
|
-
}
|
|
4506
|
-
// ─── CSS processing ───────────────────────────────────────────────────────────
|
|
4507
|
-
function processInlineStyles(ctx) {
|
|
4508
|
-
let count = 0;
|
|
4509
|
-
ctx.doc.querySelectorAll('[style]').forEach((el) => {
|
|
4510
|
-
const style = el.getAttribute('style');
|
|
4511
|
-
if (style && createRegex().test(style)) {
|
|
4512
|
-
elementsWithViewportUnits.add(el);
|
|
4513
|
-
el.setAttribute('style', replaceInText(style, ctx));
|
|
4514
|
-
count++;
|
|
4515
|
-
}
|
|
4516
|
-
});
|
|
4517
|
-
logger$4.log(`Replaced ${count} inline style elements`);
|
|
4518
|
-
return count;
|
|
4519
|
-
}
|
|
4520
|
-
function processStyleTags(ctx) {
|
|
4521
|
-
let count = 0;
|
|
4522
|
-
ctx.doc.querySelectorAll('style').forEach((tag) => {
|
|
4523
|
-
const css = tag.textContent || '';
|
|
4524
|
-
if (createRegex().test(css)) {
|
|
4525
|
-
tag.textContent = replaceInText(css, ctx);
|
|
4526
|
-
count++;
|
|
4527
|
-
}
|
|
4528
|
-
});
|
|
4529
|
-
logger$4.log(`Replaced ${count} <style> tags`);
|
|
4530
|
-
return count;
|
|
4531
|
-
}
|
|
4532
|
-
function processRule(rule, ctx) {
|
|
4533
|
-
let count = 0;
|
|
4534
|
-
if ('style' in rule && rule.style) {
|
|
4535
|
-
const cssRule = rule;
|
|
4536
|
-
const style = cssRule.style;
|
|
4537
|
-
let hasVp = false;
|
|
4538
|
-
const propOriginals = new Map();
|
|
4539
|
-
for (let i = 0; i < style.length; i++) {
|
|
4540
|
-
const prop = style[i];
|
|
4541
|
-
const value = style.getPropertyValue(prop);
|
|
4542
|
-
if (value && createRegex().test(value)) {
|
|
4543
|
-
hasVp = true;
|
|
4544
|
-
propOriginals.set(prop, value);
|
|
4545
|
-
style.setProperty(prop, replaceInText(value, ctx), style.getPropertyPriority(prop));
|
|
4978
|
+
if (computedValue && (!inlineValue || inlineValue !== computedValue) && !isDefaultCssValue(computedValue)) {
|
|
4979
|
+
const originalValue = elementOriginals?.get(prop) || '';
|
|
4980
|
+
if (applyPropertyWithVerification(s, htmlElement, prop, computedValue, originalValue, tolerance))
|
|
4546
4981
|
count++;
|
|
4547
|
-
}
|
|
4548
|
-
}
|
|
4549
|
-
if (hasVp && cssRule.selectorText)
|
|
4550
|
-
trackSelector(cssRule.selectorText, propOriginals, ctx);
|
|
4551
|
-
}
|
|
4552
|
-
if ('cssRules' in rule) {
|
|
4553
|
-
for (const r of Array.from(rule.cssRules || [])) {
|
|
4554
|
-
count += processRule(r, ctx);
|
|
4555
|
-
}
|
|
4556
|
-
}
|
|
4557
|
-
return count;
|
|
4558
|
-
}
|
|
4559
|
-
/** Processes only inline <style> sheets. Linked sheets are handled by processLinkedStylesheets. */
|
|
4560
|
-
function processStylesheets(ctx) {
|
|
4561
|
-
let total = 0;
|
|
4562
|
-
Array.from(ctx.doc.styleSheets).forEach((sheet) => {
|
|
4563
|
-
if (sheet.href)
|
|
4564
|
-
return; // deferred to processLinkedStylesheets
|
|
4565
|
-
try {
|
|
4566
|
-
for (const rule of Array.from(sheet.cssRules || [])) {
|
|
4567
|
-
total += processRule(rule, ctx);
|
|
4568
|
-
}
|
|
4569
|
-
}
|
|
4570
|
-
catch (e) {
|
|
4571
|
-
logger$4.warn('Cannot read stylesheet (CORS?):', e.message);
|
|
4572
4982
|
}
|
|
4573
4983
|
});
|
|
4574
|
-
|
|
4575
|
-
return total;
|
|
4984
|
+
return count;
|
|
4576
4985
|
}
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
// Skip cross-origin — already in browser CSSOM, handled via processStylesheets
|
|
4582
|
-
if (link.href && !link.href.startsWith(ctx.win.location.origin)) {
|
|
4583
|
-
logger$4.log('Skipping cross-origin CSS:', link.href);
|
|
4584
|
-
continue;
|
|
4585
|
-
}
|
|
4586
|
-
try {
|
|
4587
|
-
const res = await fetch(link.href);
|
|
4588
|
-
let css = await res.text();
|
|
4589
|
-
if (createRegex().test(css)) {
|
|
4590
|
-
css = replaceInText(css, ctx);
|
|
4591
|
-
const style = ctx.doc.createElement('style');
|
|
4592
|
-
style.textContent = css;
|
|
4593
|
-
style.dataset.originalHref = link.href;
|
|
4594
|
-
link.parentNode?.insertBefore(style, link);
|
|
4595
|
-
link.remove();
|
|
4596
|
-
count++;
|
|
4597
|
-
}
|
|
4598
|
-
}
|
|
4599
|
-
catch (e) {
|
|
4600
|
-
logger$4.warn('Cannot load CSS:', link.href, e);
|
|
4601
|
-
}
|
|
4986
|
+
function processAll(s, options = {}) {
|
|
4987
|
+
if (!s.running || !s.doc || !s.win || !s.config) {
|
|
4988
|
+
s.logger.warn('Enforcer is not running.');
|
|
4989
|
+
return 0;
|
|
4602
4990
|
}
|
|
4603
|
-
|
|
4604
|
-
|
|
4991
|
+
let totalCount = 0;
|
|
4992
|
+
s.elementsWithViewportUnits.forEach((element) => {
|
|
4993
|
+
totalCount += processElement(s, element, options);
|
|
4994
|
+
});
|
|
4995
|
+
s.logger.log(`Enforced ${totalCount} computed styles for ${s.elementsWithViewportUnits.size} elements`);
|
|
4996
|
+
return totalCount;
|
|
4997
|
+
}
|
|
4998
|
+
function updateConfig$2(s, cfg) {
|
|
4999
|
+
if (!s.running || !s.config) {
|
|
5000
|
+
s.logger.warn('Enforcer is not running.');
|
|
5001
|
+
return;
|
|
5002
|
+
}
|
|
5003
|
+
s.config = { ...s.config, ...cfg };
|
|
5004
|
+
s.logger.log('Config updated:', cfg);
|
|
4605
5005
|
}
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
5006
|
+
|
|
5007
|
+
/**
|
|
5008
|
+
* Computed Style Enforcer
|
|
5009
|
+
* Enforces computed CSS styles with viewport unit verification.
|
|
5010
|
+
*/
|
|
5011
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
5012
|
+
function createEnforcer() {
|
|
5013
|
+
const s = {
|
|
5014
|
+
logger: createLogger({ enabled: false, prefix: 'ComputedStyleEnforcer' }),
|
|
5015
|
+
doc: null,
|
|
5016
|
+
win: null,
|
|
5017
|
+
config: null,
|
|
5018
|
+
elementsWithViewportUnits: new Set(),
|
|
5019
|
+
originalValues: new WeakMap(),
|
|
5020
|
+
running: false,
|
|
5021
|
+
};
|
|
5022
|
+
return {
|
|
5023
|
+
start: (d, w, cfg, options) => start$3(s, d, w, cfg, options),
|
|
5024
|
+
stop: () => stop$3(s),
|
|
5025
|
+
reset: () => reset(s),
|
|
5026
|
+
trackElement: (element, propertyOriginalValues) => trackElement(s, element, propertyOriginalValues),
|
|
5027
|
+
processElement: (element, options) => processElement(s, element, options),
|
|
5028
|
+
processAll: (options) => processAll(s, options),
|
|
5029
|
+
updateConfig: (cfg) => updateConfig$2(s, cfg),
|
|
5030
|
+
getStateInfo: () => ({
|
|
5031
|
+
isRunning: s.running,
|
|
5032
|
+
trackedElementsCount: s.elementsWithViewportUnits.size,
|
|
5033
|
+
hasConfig: !!s.config,
|
|
5034
|
+
}),
|
|
5035
|
+
isRunning: () => s.running,
|
|
5036
|
+
};
|
|
4621
5037
|
}
|
|
4622
5038
|
|
|
5039
|
+
const registry$1 = [];
|
|
4623
5040
|
/**
|
|
4624
|
-
*
|
|
4625
|
-
*
|
|
5041
|
+
* Register a global fix.
|
|
5042
|
+
* Fixes are run in registration order.
|
|
4626
5043
|
*/
|
|
4627
|
-
register$1({
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
5044
|
+
function register$1(fix) {
|
|
5045
|
+
registry$1.push(fix);
|
|
5046
|
+
}
|
|
5047
|
+
/**
|
|
5048
|
+
* Returns all fixes that are active for the given context.
|
|
5049
|
+
* A fix is active if it has no `shouldApply`, or `shouldApply` returns true.
|
|
5050
|
+
*/
|
|
5051
|
+
function getActiveFixes(ctx) {
|
|
5052
|
+
return registry$1.filter((fix) => {
|
|
5053
|
+
try {
|
|
5054
|
+
return !fix.shouldApply || fix.shouldApply(ctx);
|
|
5055
|
+
}
|
|
5056
|
+
catch {
|
|
5057
|
+
return false;
|
|
5058
|
+
}
|
|
5059
|
+
});
|
|
5060
|
+
}
|
|
4632
5061
|
|
|
4633
5062
|
/**
|
|
4634
5063
|
* GemPages v7 Slider Fix
|
|
@@ -4713,6 +5142,9 @@ function getMaxWByDeviceType(deviceType) {
|
|
|
4713
5142
|
// ─── Main fix ─────────────────────────────────────────────────────────────────
|
|
4714
5143
|
function afterProcess$1({ doc, targetWidth, deviceType }) {
|
|
4715
5144
|
doc.querySelectorAll(SLIDER_ITEM_SELECTOR).forEach((item) => {
|
|
5145
|
+
// When !gp-min-w-full is set, the item fills 100% width via Tailwind — skip manual width override
|
|
5146
|
+
if (item.classList.contains('!gp-min-w-full'))
|
|
5147
|
+
return;
|
|
4716
5148
|
const originalWidth = parseFloat(item.style.minWidth) || parseFloat(item.style.maxWidth) || 0;
|
|
4717
5149
|
if (!originalWidth)
|
|
4718
5150
|
return;
|
|
@@ -4813,37 +5245,6 @@ register$1({
|
|
|
4813
5245
|
afterProcess,
|
|
4814
5246
|
});
|
|
4815
5247
|
|
|
4816
|
-
const logger$3 = createLogger({ enabled: false, prefix: 'ViewportReplacer' });
|
|
4817
|
-
let dimensionsListener = null;
|
|
4818
|
-
function attach(debug) {
|
|
4819
|
-
logger$3.configure({ enabled: !!debug });
|
|
4820
|
-
dimensionsListener = (e) => {
|
|
4821
|
-
const ev = e;
|
|
4822
|
-
logger$3.log('Dimensions applied:', ev.detail);
|
|
4823
|
-
};
|
|
4824
|
-
window.addEventListener('iframe-dimensions-applied', dimensionsListener);
|
|
4825
|
-
}
|
|
4826
|
-
function detach() {
|
|
4827
|
-
if (dimensionsListener) {
|
|
4828
|
-
window.removeEventListener('iframe-dimensions-applied', dimensionsListener);
|
|
4829
|
-
dimensionsListener = null;
|
|
4830
|
-
}
|
|
4831
|
-
}
|
|
4832
|
-
|
|
4833
|
-
/**
|
|
4834
|
-
* Default iframe dimension calculation.
|
|
4835
|
-
* Used as fallback when no fix overrides getDimensions().
|
|
4836
|
-
*/
|
|
4837
|
-
function getFinalHeight(doc, win) {
|
|
4838
|
-
void doc.body.offsetHeight; // trigger reflow
|
|
4839
|
-
const bodyMinHeight = parseFloat(win.getComputedStyle(doc.body).minHeight) || 0;
|
|
4840
|
-
const htmlMinHeight = parseFloat(win.getComputedStyle(doc.documentElement).minHeight) || 0;
|
|
4841
|
-
return Math.max(doc.body?.scrollHeight || 0, doc.body?.offsetHeight || 0, doc.documentElement?.scrollHeight || 0, doc.documentElement?.offsetHeight || 0, doc.documentElement?.clientHeight || 0, bodyMinHeight, htmlMinHeight);
|
|
4842
|
-
}
|
|
4843
|
-
function getFinalWidth(doc) {
|
|
4844
|
-
return Math.max(doc.body?.scrollWidth || 0, doc.body?.offsetWidth || 0, doc.documentElement?.scrollWidth || 0, doc.documentElement?.offsetWidth || 0, doc.documentElement?.clientWidth || 0);
|
|
4845
|
-
}
|
|
4846
|
-
|
|
4847
5248
|
/**
|
|
4848
5249
|
* Viewport fix pipeline runner.
|
|
4849
5250
|
*
|
|
@@ -4853,68 +5254,79 @@ function getFinalWidth(doc) {
|
|
|
4853
5254
|
* Phase 3: afterProcess (shop → global)
|
|
4854
5255
|
* Phase 4: getDimensions (shop → global → default)
|
|
4855
5256
|
*/
|
|
4856
|
-
const logger
|
|
5257
|
+
const logger = createLogger({ enabled: false, prefix: 'ViewportReplacer' });
|
|
4857
5258
|
function configure(debug) {
|
|
4858
|
-
logger
|
|
5259
|
+
logger.configure({ enabled: debug });
|
|
4859
5260
|
}
|
|
4860
|
-
async function
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
}
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
}
|
|
4872
|
-
// ── Phase 2: process ──────────────────────────────────────────────────────
|
|
4873
|
-
for (const fix of activeGlobal) {
|
|
4874
|
-
if (fix.process) {
|
|
4875
|
-
logger$2.log(`[process] ${fix.name}`);
|
|
4876
|
-
await fix.process(ctx);
|
|
5261
|
+
async function runTracked(label, fn) {
|
|
5262
|
+
const t = perf$3.mark(label);
|
|
5263
|
+
await fn();
|
|
5264
|
+
perf$3.measure(label, t);
|
|
5265
|
+
}
|
|
5266
|
+
async function runPhase(ctx, options) {
|
|
5267
|
+
const { phaseKey, methodName, entries } = options;
|
|
5268
|
+
await runTracked(`${phaseKey}.${methodName}`, async () => {
|
|
5269
|
+
for (const { name, fn } of entries) {
|
|
5270
|
+
logger.log(`[${methodName}] ${name}`);
|
|
5271
|
+
await runTracked(`${phaseKey}.${name}.${methodName}`, () => fn(ctx));
|
|
4877
5272
|
}
|
|
4878
|
-
}
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
5273
|
+
});
|
|
5274
|
+
}
|
|
5275
|
+
function collectGlobalEntries(fixes, method) {
|
|
5276
|
+
return fixes
|
|
5277
|
+
.filter((fix) => fix[method] != null)
|
|
5278
|
+
.map((fix) => ({ name: fix.name, fn: fix[method] }));
|
|
5279
|
+
}
|
|
5280
|
+
function shopEntry(shopFix, method) {
|
|
5281
|
+
if (!shopFix?.[method])
|
|
5282
|
+
return [];
|
|
5283
|
+
return [{ name: 'shop', fn: shopFix[method] }];
|
|
5284
|
+
}
|
|
5285
|
+
function waitForAnimationFrame() {
|
|
5286
|
+
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
5287
|
+
}
|
|
5288
|
+
function resolveFirstDimensions(ctx, candidates) {
|
|
5289
|
+
for (const { name, getDimensions } of candidates) {
|
|
5290
|
+
if (!getDimensions)
|
|
5291
|
+
continue;
|
|
5292
|
+
const dims = getDimensions(ctx);
|
|
5293
|
+
if (dims) {
|
|
5294
|
+
logger.log(`Dimensions from ${name}:`, dims);
|
|
5295
|
+
return dims;
|
|
4888
5296
|
}
|
|
4889
5297
|
}
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
dimensions = { height: getFinalHeight(ctx.doc, ctx.win), width: getFinalWidth(ctx.doc) };
|
|
4913
|
-
}
|
|
4914
|
-
logger$2.log('Final dimensions:', dimensions);
|
|
4915
|
-
resolve(dimensions);
|
|
4916
|
-
});
|
|
5298
|
+
return null;
|
|
5299
|
+
}
|
|
5300
|
+
// ── Pipeline ──────────────────────────────────────────────────────────────────
|
|
5301
|
+
async function run$1(ctx, activeGlobal, shopFix) {
|
|
5302
|
+
// [Phase 1]: beforeProcess (global → shop)
|
|
5303
|
+
const beforeGlobalEntries = collectGlobalEntries(activeGlobal, 'beforeProcess');
|
|
5304
|
+
const beforeShopEntries = shopEntry(shopFix, 'beforeProcess');
|
|
5305
|
+
await runPhase(ctx, {
|
|
5306
|
+
phaseKey: 'phase1',
|
|
5307
|
+
methodName: 'beforeProcess',
|
|
5308
|
+
entries: [...beforeGlobalEntries, ...beforeShopEntries],
|
|
5309
|
+
});
|
|
5310
|
+
// [Phase 2]: process (global fixes)
|
|
5311
|
+
const processGlobalEntries = collectGlobalEntries(activeGlobal, 'process');
|
|
5312
|
+
await runPhase(ctx, { phaseKey: 'phase2', methodName: 'process', entries: [...processGlobalEntries] });
|
|
5313
|
+
// [Phase 3]: afterProcess (shop → global)
|
|
5314
|
+
const afterShopEntries = shopEntry(shopFix, 'afterProcess');
|
|
5315
|
+
const afterGlobalEntries = collectGlobalEntries(activeGlobal, 'afterProcess');
|
|
5316
|
+
await runPhase(ctx, {
|
|
5317
|
+
phaseKey: 'phase3',
|
|
5318
|
+
methodName: 'afterProcess',
|
|
5319
|
+
entries: [...afterShopEntries, ...afterGlobalEntries],
|
|
4917
5320
|
});
|
|
5321
|
+
const t4 = perf$3.mark('phase4.getDimensions');
|
|
5322
|
+
await waitForAnimationFrame();
|
|
5323
|
+
const dimensions = resolveFirstDimensions(ctx, [
|
|
5324
|
+
...(shopFix ? [{ name: 'shop fix', ...shopFix }] : []),
|
|
5325
|
+
...activeGlobal,
|
|
5326
|
+
]) ?? { height: getFinalHeight(ctx.doc, false), width: getFinalWidth(ctx.doc) };
|
|
5327
|
+
logger.log('Final dimensions:', dimensions);
|
|
5328
|
+
perf$3.measure('phase4.getDimensions', t4);
|
|
5329
|
+
return dimensions;
|
|
4918
5330
|
}
|
|
4919
5331
|
|
|
4920
5332
|
const registry = new Map();
|
|
@@ -4988,233 +5400,310 @@ register(`566240210141053597`, {
|
|
|
4988
5400
|
afterProcess: restoreAndFixLayout,
|
|
4989
5401
|
});
|
|
4990
5402
|
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
let config$1 = null;
|
|
4996
|
-
let shopFix = null;
|
|
4997
|
-
let running$2 = false;
|
|
4998
|
-
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
4999
|
-
function start$3(iframe, cfg) {
|
|
5000
|
-
if (running$2) {
|
|
5001
|
-
logger$1.warn('Already running. Call stop() first.');
|
|
5403
|
+
// ── Module-level functions ────────────────────────────────────────────────────
|
|
5404
|
+
function start$2(s, iframe, cfg) {
|
|
5405
|
+
if (s.running) {
|
|
5406
|
+
s.logger.warn('Already running. Call stop() first.');
|
|
5002
5407
|
return;
|
|
5003
5408
|
}
|
|
5004
5409
|
if (!iframe.contentDocument || !iframe.contentWindow) {
|
|
5005
5410
|
throw new Error('Iframe document or window not accessible');
|
|
5006
5411
|
}
|
|
5007
|
-
doc = iframe.contentDocument;
|
|
5008
|
-
win = iframe.contentWindow;
|
|
5009
|
-
config
|
|
5010
|
-
running
|
|
5011
|
-
shopFix = cfg.shopId != null ? get(cfg.shopId) : null;
|
|
5012
|
-
if (shopFix)
|
|
5013
|
-
logger
|
|
5014
|
-
}
|
|
5015
|
-
logger$1.configure({ enabled: !!cfg.debug });
|
|
5412
|
+
s.doc = iframe.contentDocument;
|
|
5413
|
+
s.win = iframe.contentWindow;
|
|
5414
|
+
s.config = cfg;
|
|
5415
|
+
s.running = true;
|
|
5416
|
+
s.shopFix = cfg.shopId != null ? get(cfg.shopId) : null;
|
|
5417
|
+
if (s.shopFix)
|
|
5418
|
+
s.logger.log(`Shop fix loaded for "${cfg.shopId}":`, s.shopFix.description ?? '(no description)');
|
|
5419
|
+
s.logger.configure({ enabled: !!cfg.debug });
|
|
5016
5420
|
configure(!!cfg.debug);
|
|
5017
|
-
start
|
|
5018
|
-
attach(cfg.debug);
|
|
5019
|
-
logger
|
|
5421
|
+
s.enforcer.start(s.doc, s.win, cfg, { debug: !!cfg.debug });
|
|
5422
|
+
s.listeners.attach(cfg.debug);
|
|
5423
|
+
s.logger.log('Started');
|
|
5020
5424
|
}
|
|
5021
|
-
function stop$
|
|
5022
|
-
if (!running
|
|
5425
|
+
function stop$2(s) {
|
|
5426
|
+
if (!s.running)
|
|
5023
5427
|
return;
|
|
5024
|
-
stop
|
|
5025
|
-
detach();
|
|
5026
|
-
doc = null;
|
|
5027
|
-
win = null;
|
|
5028
|
-
config
|
|
5029
|
-
shopFix = null;
|
|
5030
|
-
running
|
|
5031
|
-
logger
|
|
5032
|
-
}
|
|
5033
|
-
async function run() {
|
|
5034
|
-
if (!running
|
|
5035
|
-
logger
|
|
5428
|
+
s.enforcer.stop();
|
|
5429
|
+
s.listeners.detach();
|
|
5430
|
+
s.doc = null;
|
|
5431
|
+
s.win = null;
|
|
5432
|
+
s.config = null;
|
|
5433
|
+
s.shopFix = null;
|
|
5434
|
+
s.running = false;
|
|
5435
|
+
s.logger.log('Stopped');
|
|
5436
|
+
}
|
|
5437
|
+
async function run(s) {
|
|
5438
|
+
if (!s.running || !s.doc || !s.win || !s.config) {
|
|
5439
|
+
s.logger.warn('Not running. Call start() first.');
|
|
5036
5440
|
return { height: 1000, width: 1000 };
|
|
5037
5441
|
}
|
|
5038
5442
|
const ctx = {
|
|
5039
|
-
doc,
|
|
5040
|
-
win,
|
|
5041
|
-
deviceType: config
|
|
5042
|
-
targetWidth: config
|
|
5043
|
-
targetHeight: config
|
|
5044
|
-
debug: config
|
|
5443
|
+
doc: s.doc,
|
|
5444
|
+
win: s.win,
|
|
5445
|
+
deviceType: s.config.deviceType,
|
|
5446
|
+
targetWidth: s.config.targetWidth,
|
|
5447
|
+
targetHeight: s.config.targetHeight,
|
|
5448
|
+
debug: s.config.debug,
|
|
5449
|
+
enforcer: s.enforcer,
|
|
5045
5450
|
};
|
|
5046
5451
|
const activeGlobal = getActiveFixes(ctx);
|
|
5047
|
-
if (activeGlobal.length > 0)
|
|
5048
|
-
logger
|
|
5049
|
-
|
|
5452
|
+
if (activeGlobal.length > 0)
|
|
5453
|
+
s.logger.log(`Active global fixes: ${activeGlobal.map((f) => f.name).join(', ')}`);
|
|
5454
|
+
const tRun = perf$3.mark('viewport.run');
|
|
5050
5455
|
try {
|
|
5051
|
-
|
|
5456
|
+
const result = await run$1(ctx, activeGlobal, s.shopFix);
|
|
5457
|
+
perf$3.measure('viewport.run', tRun);
|
|
5458
|
+
return result;
|
|
5052
5459
|
}
|
|
5053
5460
|
catch (err) {
|
|
5054
|
-
|
|
5055
|
-
|
|
5461
|
+
perf$3.measure('viewport.run', tRun);
|
|
5462
|
+
s.logger.error('Critical error:', err);
|
|
5463
|
+
return { height: s.doc.body?.scrollHeight || 1000, width: s.doc.body?.scrollWidth || 1000 };
|
|
5056
5464
|
}
|
|
5057
5465
|
}
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
prefix: 'IframeFixer',
|
|
5062
|
-
});
|
|
5063
|
-
// ============================================================================
|
|
5064
|
-
// State
|
|
5065
|
-
// ============================================================================
|
|
5066
|
-
let iframe = null;
|
|
5067
|
-
let config = null;
|
|
5068
|
-
let running$1 = false;
|
|
5069
|
-
let loadListener = null;
|
|
5070
|
-
// ============================================================================
|
|
5071
|
-
// Core API Functions
|
|
5072
|
-
// ============================================================================
|
|
5073
|
-
function start$2(cfg) {
|
|
5074
|
-
if (running$1) {
|
|
5075
|
-
logger.warn('Fixer is already running. Call stop() first.');
|
|
5466
|
+
function updateConfig$1(s, cfg) {
|
|
5467
|
+
if (!s.running || !s.config) {
|
|
5468
|
+
s.logger.warn('Not running. Call start() first.');
|
|
5076
5469
|
return;
|
|
5077
5470
|
}
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
logger.log('
|
|
5083
|
-
|
|
5471
|
+
s.config = { ...s.config, ...cfg };
|
|
5472
|
+
if (cfg.shopId !== undefined)
|
|
5473
|
+
s.shopFix = cfg.shopId != null ? get(cfg.shopId) : null;
|
|
5474
|
+
s.enforcer.updateConfig(cfg);
|
|
5475
|
+
s.logger.log('Config updated');
|
|
5476
|
+
}
|
|
5477
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
5478
|
+
function createViewportProcessor() {
|
|
5479
|
+
const s = {
|
|
5480
|
+
logger: createLogger({ enabled: false, prefix: 'ViewportReplacer' }),
|
|
5481
|
+
enforcer: createEnforcer(),
|
|
5482
|
+
listeners: createViewportListeners(),
|
|
5483
|
+
doc: null,
|
|
5484
|
+
win: null,
|
|
5485
|
+
config: null,
|
|
5486
|
+
shopFix: null,
|
|
5487
|
+
running: false,
|
|
5488
|
+
};
|
|
5489
|
+
return {
|
|
5490
|
+
start: (iframe, cfg) => start$2(s, iframe, cfg),
|
|
5491
|
+
stop: () => stop$2(s),
|
|
5492
|
+
run: () => run(s),
|
|
5493
|
+
updateConfig: (cfg) => updateConfig$1(s, cfg),
|
|
5494
|
+
isRunning: () => s.running,
|
|
5495
|
+
};
|
|
5496
|
+
}
|
|
5497
|
+
|
|
5498
|
+
// ── Module-level functions ────────────────────────────────────────────────────
|
|
5499
|
+
function dispatchDimensionsEvent(dimensions) {
|
|
5500
|
+
window.dispatchEvent(new CustomEvent('iframe-dimensions-applied', { detail: dimensions }));
|
|
5084
5501
|
}
|
|
5085
|
-
function
|
|
5086
|
-
if (!
|
|
5502
|
+
async function process(s) {
|
|
5503
|
+
if (!s.iframe || !s.config)
|
|
5087
5504
|
return;
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
stop$5();
|
|
5092
|
-
// Remove load listener
|
|
5093
|
-
if (iframe && loadListener) {
|
|
5094
|
-
iframe.removeEventListener('load', loadListener);
|
|
5095
|
-
loadListener = null;
|
|
5096
|
-
}
|
|
5097
|
-
iframe = null;
|
|
5098
|
-
config = null;
|
|
5099
|
-
running$1 = false;
|
|
5100
|
-
logger.log('Iframe fixer stopped');
|
|
5101
|
-
}
|
|
5102
|
-
async function initialize() {
|
|
5103
|
-
if (!iframe || !config) {
|
|
5104
|
-
logger.error('iframe not found');
|
|
5105
|
-
config?.onError?.(new Error('iframe not found'));
|
|
5505
|
+
if (!s.iframe.contentDocument || !s.iframe.contentWindow) {
|
|
5506
|
+
s.logger.error('Cannot access iframe document');
|
|
5507
|
+
s.config.onError?.(new Error('Cannot access iframe document'));
|
|
5106
5508
|
return;
|
|
5107
5509
|
}
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5510
|
+
const sessionId = `render-${Date.now()}`;
|
|
5511
|
+
perf$3.startSession(sessionId);
|
|
5512
|
+
const t0 = perf$3.mark('orchestrator.process');
|
|
5513
|
+
try {
|
|
5514
|
+
s.logger.groupCollapsed('Processing...');
|
|
5515
|
+
s.viewportReplacer.start(s.iframe, s.config);
|
|
5516
|
+
s.navigationBlocker.start(s.iframe, { debug: s.config.debug });
|
|
5517
|
+
const result = await s.viewportReplacer.run();
|
|
5518
|
+
perf$3.measure('orchestrator.process', t0);
|
|
5519
|
+
perf$3.endSession();
|
|
5520
|
+
s.logger.groupEnd();
|
|
5521
|
+
s.logger.log('Process completed:', result);
|
|
5522
|
+
s.config.onSuccess?.(result);
|
|
5523
|
+
dispatchDimensionsEvent(result);
|
|
5111
5524
|
}
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5525
|
+
catch (error) {
|
|
5526
|
+
perf$3.measure('orchestrator.process', t0);
|
|
5527
|
+
perf$3.endSession();
|
|
5528
|
+
s.logger.error('Failed to process:', error);
|
|
5529
|
+
s.config.onError?.(error);
|
|
5115
5530
|
}
|
|
5116
5531
|
}
|
|
5117
|
-
async function
|
|
5118
|
-
if (!iframe || !config)
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
logger.error('Cannot access iframe document');
|
|
5122
|
-
config.onError?.(new Error('Cannot access iframe document'));
|
|
5532
|
+
async function initialize(s) {
|
|
5533
|
+
if (!s.iframe || !s.config) {
|
|
5534
|
+
s.logger.error('iframe not found');
|
|
5535
|
+
s.config?.onError?.(new Error('iframe not found'));
|
|
5123
5536
|
return;
|
|
5124
5537
|
}
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
start$3(iframe, config);
|
|
5128
|
-
start$5(iframe, { debug: config.debug });
|
|
5129
|
-
const result = await run();
|
|
5130
|
-
logger.log('Process completed:', result);
|
|
5131
|
-
config.onSuccess?.(result);
|
|
5132
|
-
dispatchDimensionsEvent(result);
|
|
5133
|
-
// Optionally setup height observer
|
|
5134
|
-
// setupHeightObserver();
|
|
5538
|
+
if (s.iframe.contentDocument?.readyState === 'complete') {
|
|
5539
|
+
await process(s);
|
|
5135
5540
|
}
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5541
|
+
else {
|
|
5542
|
+
s.loadListener = () => process(s);
|
|
5543
|
+
s.iframe.addEventListener('load', s.loadListener);
|
|
5139
5544
|
}
|
|
5140
5545
|
}
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5546
|
+
function start$1(s, cfg) {
|
|
5547
|
+
if (s.running) {
|
|
5548
|
+
s.logger.warn('Fixer is already running. Call stop() first.');
|
|
5549
|
+
return;
|
|
5550
|
+
}
|
|
5551
|
+
s.iframe = cfg.iframe;
|
|
5552
|
+
s.config = cfg;
|
|
5553
|
+
s.running = true;
|
|
5554
|
+
s.logger.configure({ enabled: !!cfg.debug });
|
|
5555
|
+
s.logger.log('Started');
|
|
5556
|
+
initialize(s);
|
|
5557
|
+
}
|
|
5558
|
+
function stop$1(s) {
|
|
5559
|
+
if (!s.running)
|
|
5560
|
+
return;
|
|
5561
|
+
s.viewportReplacer.stop();
|
|
5562
|
+
s.heightObserver.stop();
|
|
5563
|
+
s.navigationBlocker.stop();
|
|
5564
|
+
if (s.iframe && s.loadListener) {
|
|
5565
|
+
s.iframe.removeEventListener('load', s.loadListener);
|
|
5566
|
+
s.loadListener = null;
|
|
5567
|
+
}
|
|
5568
|
+
s.iframe = null;
|
|
5569
|
+
s.config = null;
|
|
5570
|
+
s.running = false;
|
|
5571
|
+
s.logger.log('Stopped');
|
|
5572
|
+
}
|
|
5573
|
+
function startHeightObserver(s) {
|
|
5574
|
+
if (!s.iframe || !s.config)
|
|
5575
|
+
return;
|
|
5576
|
+
s.heightObserver.stop();
|
|
5577
|
+
s.heightObserver.start(s.iframe, {
|
|
5578
|
+
iframe: s.iframe,
|
|
5579
|
+
debug: s.config.debug,
|
|
5580
|
+
throttleMs: 25,
|
|
5581
|
+
debounceMs: 500,
|
|
5582
|
+
cooldownMs: 1000,
|
|
5583
|
+
startDelayMs: s.config.heightObserverStartDelayMs,
|
|
5584
|
+
onHeightChange: (result) => {
|
|
5585
|
+
s.config?.onSuccess?.(result);
|
|
5586
|
+
dispatchDimensionsEvent(result);
|
|
5587
|
+
},
|
|
5588
|
+
onError: (error) => {
|
|
5589
|
+
s.config?.onError?.(error);
|
|
5590
|
+
},
|
|
5591
|
+
});
|
|
5148
5592
|
}
|
|
5149
|
-
function
|
|
5150
|
-
|
|
5593
|
+
async function recalculate$1(s) {
|
|
5594
|
+
if (!s.running) {
|
|
5595
|
+
s.logger.warn('Fixer is not running.');
|
|
5596
|
+
return;
|
|
5597
|
+
}
|
|
5598
|
+
s.logger.log('Recalculating...');
|
|
5599
|
+
await process(s);
|
|
5151
5600
|
}
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
function enableNavigationBlocking$2() {
|
|
5156
|
-
if (!running$1) {
|
|
5157
|
-
logger.warn('Fixer is not running. Call start() first.');
|
|
5601
|
+
function updateConfig(s, cfg) {
|
|
5602
|
+
if (!s.running || !s.config) {
|
|
5603
|
+
s.logger.warn('Fixer is not running.');
|
|
5158
5604
|
return;
|
|
5159
5605
|
}
|
|
5160
|
-
|
|
5606
|
+
s.config = { ...s.config, ...cfg };
|
|
5607
|
+
s.viewportReplacer.updateConfig(cfg);
|
|
5608
|
+
s.logger.log('Config updated');
|
|
5609
|
+
}
|
|
5610
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
5611
|
+
function createOrchestrator() {
|
|
5612
|
+
const s = {
|
|
5613
|
+
logger: createLogger({ enabled: false, prefix: 'IframeFixer' }),
|
|
5614
|
+
viewportReplacer: createViewportProcessor(),
|
|
5615
|
+
navigationBlocker: createNavigationBlocker(),
|
|
5616
|
+
heightObserver: createHeightObserver(),
|
|
5617
|
+
iframe: null,
|
|
5618
|
+
config: null,
|
|
5619
|
+
running: false,
|
|
5620
|
+
loadListener: null,
|
|
5621
|
+
};
|
|
5622
|
+
return {
|
|
5623
|
+
start: (cfg) => start$1(s, cfg),
|
|
5624
|
+
stop: () => stop$1(s),
|
|
5625
|
+
recalculate: () => recalculate$1(s),
|
|
5626
|
+
updateConfig: (cfg) => updateConfig(s, cfg),
|
|
5627
|
+
enableNavigationBlocking: () => s.navigationBlocker.enable(),
|
|
5628
|
+
enableNavigationBlockingMessage: () => s.navigationBlocker.enableMessage(),
|
|
5629
|
+
disableNavigationBlocking: () => s.navigationBlocker.disable(),
|
|
5630
|
+
disableNavigationBlockingMessage: () => s.navigationBlocker.disableMessage(),
|
|
5631
|
+
startHeightObserver: () => startHeightObserver(s),
|
|
5632
|
+
isRunning: () => s.running,
|
|
5633
|
+
getStateInfo: () => ({
|
|
5634
|
+
isRunning: s.running,
|
|
5635
|
+
hasIframe: !!s.iframe,
|
|
5636
|
+
hasConfig: !!s.config,
|
|
5637
|
+
hasNavigationBlocker: s.navigationBlocker.isRunning(),
|
|
5638
|
+
hasHeightObserver: s.heightObserver.isRunning(),
|
|
5639
|
+
viewportReplacerRunning: s.viewportReplacer.isRunning(),
|
|
5640
|
+
}),
|
|
5641
|
+
};
|
|
5161
5642
|
}
|
|
5162
5643
|
|
|
5163
5644
|
/**
|
|
5164
|
-
* Iframe Helper
|
|
5165
|
-
*
|
|
5645
|
+
* Iframe Helper — factory entry point.
|
|
5646
|
+
*
|
|
5647
|
+
* Each call to `createIframeHelper()` returns a fully isolated instance
|
|
5648
|
+
* with its own processor state. Use one instance per iframe.
|
|
5166
5649
|
*/
|
|
5167
|
-
//
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
// ============================================================================
|
|
5172
|
-
// Public API
|
|
5173
|
-
// ============================================================================
|
|
5174
|
-
function start$1(config) {
|
|
5175
|
-
if (running) {
|
|
5176
|
-
console.warn('[IframeHelperStarter] Already running. Call stop() first.');
|
|
5650
|
+
// ── Module-level functions ────────────────────────────────────────────────────
|
|
5651
|
+
function start(s, config) {
|
|
5652
|
+
if (s.running) {
|
|
5653
|
+
console.warn('[IframeHelper] Already running. Call stop() first.');
|
|
5177
5654
|
return;
|
|
5178
5655
|
}
|
|
5179
|
-
start
|
|
5180
|
-
running = true;
|
|
5656
|
+
s.fixer.start(config);
|
|
5657
|
+
s.running = true;
|
|
5181
5658
|
}
|
|
5182
|
-
function stop
|
|
5183
|
-
if (!running)
|
|
5659
|
+
function stop(s) {
|
|
5660
|
+
if (!s.running)
|
|
5184
5661
|
return;
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
running = false;
|
|
5662
|
+
s.fixer.stop();
|
|
5663
|
+
s.running = false;
|
|
5188
5664
|
}
|
|
5189
|
-
function
|
|
5190
|
-
if (!running) {
|
|
5191
|
-
console.warn('[
|
|
5665
|
+
async function recalculate(s) {
|
|
5666
|
+
if (!s.running) {
|
|
5667
|
+
console.warn('[IframeHelper] Not running. Call start() first.');
|
|
5192
5668
|
return;
|
|
5193
5669
|
}
|
|
5194
|
-
|
|
5670
|
+
await s.fixer.recalculate();
|
|
5195
5671
|
}
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5672
|
+
function enableNavigationBlocking(s) {
|
|
5673
|
+
if (!s.running) {
|
|
5674
|
+
console.warn('[IframeHelper] Not running. Call start() first.');
|
|
5675
|
+
return;
|
|
5676
|
+
}
|
|
5677
|
+
s.fixer.enableNavigationBlocking();
|
|
5202
5678
|
}
|
|
5203
|
-
|
|
5204
|
-
|
|
5679
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
5680
|
+
function createIframeHelper() {
|
|
5681
|
+
const s = {
|
|
5682
|
+
fixer: createOrchestrator(),
|
|
5683
|
+
running: false,
|
|
5684
|
+
};
|
|
5685
|
+
return {
|
|
5686
|
+
start: (config) => start(s, config),
|
|
5687
|
+
stop: () => stop(s),
|
|
5688
|
+
recalculate: () => recalculate(s),
|
|
5689
|
+
enableNavigationBlocking: () => enableNavigationBlocking(s),
|
|
5690
|
+
startHeightObserver: () => s.fixer.startHeightObserver(),
|
|
5691
|
+
isRunning: () => s.running,
|
|
5692
|
+
};
|
|
5205
5693
|
}
|
|
5206
5694
|
|
|
5695
|
+
const iframeHelper = createIframeHelper();
|
|
5207
5696
|
function useVizLiveRender() {
|
|
5208
5697
|
const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
|
|
5209
5698
|
const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
|
|
5210
5699
|
const wrapperWidth = useHeatmapVizRectContext((s) => s.wrapperWidth);
|
|
5211
|
-
const
|
|
5212
|
-
const htmlContent =
|
|
5213
|
-
const targetUrl =
|
|
5700
|
+
const setIsDomLoaded = useHeatmapVizContext((s) => s.setIsDomLoaded);
|
|
5701
|
+
const htmlContent = useHeatmapLiveContext((s) => s.htmlContent);
|
|
5702
|
+
const targetUrl = useHeatmapLiveContext((s) => s.targetUrl);
|
|
5214
5703
|
const deviceType = useHeatmapSettingContext((s) => s.deviceType);
|
|
5215
|
-
const renderMode =
|
|
5216
|
-
const storefrontPassword =
|
|
5217
|
-
|
|
5704
|
+
const renderMode = useHeatmapLiveContext((s) => s.renderMode);
|
|
5705
|
+
const storefrontPassword = useHeatmapLiveContext((s) => s.storefrontPassword);
|
|
5706
|
+
useHeatmapViewportByDevice();
|
|
5218
5707
|
const { iframeRef, isReady } = useVizLiveIframeMsg();
|
|
5219
5708
|
// Handle iframe rendering based on mode
|
|
5220
5709
|
useEffect(() => {
|
|
@@ -5254,10 +5743,10 @@ function useVizLiveRender() {
|
|
|
5254
5743
|
const hasContent = (renderMode === 'portal' && targetUrl) || (renderMode === 'inline' && htmlContent);
|
|
5255
5744
|
if (!iframe || !hasContent)
|
|
5256
5745
|
return;
|
|
5257
|
-
|
|
5258
|
-
|
|
5746
|
+
setIsDomLoaded(true);
|
|
5747
|
+
startIframe$1(iframe, deviceType, { width: wrapperWidth, height: wrapperHeight }, (height) => {
|
|
5259
5748
|
height && setIframeHeight(height);
|
|
5260
|
-
|
|
5749
|
+
setIsDomLoaded(true);
|
|
5261
5750
|
});
|
|
5262
5751
|
return () => { };
|
|
5263
5752
|
}, [
|
|
@@ -5269,7 +5758,7 @@ function useVizLiveRender() {
|
|
|
5269
5758
|
targetUrl,
|
|
5270
5759
|
htmlContent,
|
|
5271
5760
|
iframeRef,
|
|
5272
|
-
|
|
5761
|
+
setIsDomLoaded,
|
|
5273
5762
|
setIframeHeight,
|
|
5274
5763
|
]);
|
|
5275
5764
|
return {
|
|
@@ -5288,9 +5777,9 @@ function buildPortalUrl(targetUrl, storefrontPassword) {
|
|
|
5288
5777
|
const portalServiceUrl = getPortalServiceUrl();
|
|
5289
5778
|
return `${portalServiceUrl}/?${params.toString()}`;
|
|
5290
5779
|
}
|
|
5291
|
-
function
|
|
5292
|
-
stop();
|
|
5293
|
-
start({
|
|
5780
|
+
function startIframe$1(iframe, deviceType = EDeviceType.Desktop, rect, onSuccess) {
|
|
5781
|
+
iframeHelper.stop();
|
|
5782
|
+
iframeHelper.start({
|
|
5294
5783
|
deviceType: deviceType,
|
|
5295
5784
|
targetWidth: rect.width,
|
|
5296
5785
|
targetHeight: rect.height,
|
|
@@ -5301,12 +5790,115 @@ function initIframeHelper$1(iframe, deviceType = EDeviceType.Desktop, rect, onSu
|
|
|
5301
5790
|
},
|
|
5302
5791
|
});
|
|
5303
5792
|
// fixer.recalculate();
|
|
5304
|
-
enableNavigationBlocking();
|
|
5793
|
+
iframeHelper.enableNavigationBlocking();
|
|
5794
|
+
}
|
|
5795
|
+
|
|
5796
|
+
const DEFAULT_CONFIG = {
|
|
5797
|
+
dbName: 'gx-viz-html-cache',
|
|
5798
|
+
maxEntries: 20,
|
|
5799
|
+
ttlMs: 24 * 60 * 60 * 1000, // 24 hours
|
|
5800
|
+
cacheVersion: 3.0,
|
|
5801
|
+
};
|
|
5802
|
+
let _config = { ...DEFAULT_CONFIG };
|
|
5803
|
+
function getHtmlCacheConfig() {
|
|
5804
|
+
return _config;
|
|
5805
|
+
}
|
|
5806
|
+
/** Build a full cache key that includes the cache version to handle invalidation. */
|
|
5807
|
+
function buildCacheKey(baseKey, shortCircuitStrategy = 0) {
|
|
5808
|
+
return `v${_config.cacheVersion}:${baseKey}:${shortCircuitStrategy}`;
|
|
5809
|
+
}
|
|
5810
|
+
|
|
5811
|
+
const STORE_NAME = 'entries';
|
|
5812
|
+
const IDB_SCHEMA_VERSION = 1;
|
|
5813
|
+
function openDb() {
|
|
5814
|
+
const { dbName } = getHtmlCacheConfig();
|
|
5815
|
+
return new Promise((resolve, reject) => {
|
|
5816
|
+
const req = indexedDB.open(dbName, IDB_SCHEMA_VERSION);
|
|
5817
|
+
req.onupgradeneeded = () => {
|
|
5818
|
+
const db = req.result;
|
|
5819
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
5820
|
+
const store = db.createObjectStore(STORE_NAME, { keyPath: 'key' });
|
|
5821
|
+
store.createIndex('timestamp', 'timestamp');
|
|
5822
|
+
}
|
|
5823
|
+
};
|
|
5824
|
+
req.onsuccess = () => resolve(req.result);
|
|
5825
|
+
req.onerror = () => reject(req.error);
|
|
5826
|
+
});
|
|
5827
|
+
}
|
|
5828
|
+
async function evict(db) {
|
|
5829
|
+
const { maxEntries } = getHtmlCacheConfig();
|
|
5830
|
+
return new Promise((resolve) => {
|
|
5831
|
+
const tx = db.transaction(STORE_NAME, 'readwrite');
|
|
5832
|
+
const store = tx.objectStore(STORE_NAME);
|
|
5833
|
+
const countReq = store.count();
|
|
5834
|
+
countReq.onsuccess = () => {
|
|
5835
|
+
if (countReq.result <= maxEntries) {
|
|
5836
|
+
resolve();
|
|
5837
|
+
return;
|
|
5838
|
+
}
|
|
5839
|
+
const idx = store.index('timestamp');
|
|
5840
|
+
const cursorReq = idx.openCursor();
|
|
5841
|
+
let toDelete = countReq.result - maxEntries;
|
|
5842
|
+
cursorReq.onsuccess = () => {
|
|
5843
|
+
const cursor = cursorReq.result;
|
|
5844
|
+
if (cursor && toDelete > 0) {
|
|
5845
|
+
cursor.delete();
|
|
5846
|
+
toDelete--;
|
|
5847
|
+
cursor.continue();
|
|
5848
|
+
}
|
|
5849
|
+
else {
|
|
5850
|
+
resolve();
|
|
5851
|
+
}
|
|
5852
|
+
};
|
|
5853
|
+
cursorReq.onerror = () => resolve();
|
|
5854
|
+
};
|
|
5855
|
+
countReq.onerror = () => resolve();
|
|
5856
|
+
});
|
|
5305
5857
|
}
|
|
5858
|
+
const htmlCache = {
|
|
5859
|
+
async get(key) {
|
|
5860
|
+
try {
|
|
5861
|
+
const { ttlMs } = getHtmlCacheConfig();
|
|
5862
|
+
const db = await openDb();
|
|
5863
|
+
return new Promise((resolve) => {
|
|
5864
|
+
const tx = db.transaction(STORE_NAME, 'readonly');
|
|
5865
|
+
const req = tx.objectStore(STORE_NAME).get(key);
|
|
5866
|
+
req.onsuccess = () => {
|
|
5867
|
+
const entry = req.result;
|
|
5868
|
+
if (!entry || Date.now() - entry.timestamp > ttlMs) {
|
|
5869
|
+
resolve(null);
|
|
5870
|
+
}
|
|
5871
|
+
else {
|
|
5872
|
+
resolve(entry);
|
|
5873
|
+
}
|
|
5874
|
+
};
|
|
5875
|
+
req.onerror = () => resolve(null);
|
|
5876
|
+
});
|
|
5877
|
+
}
|
|
5878
|
+
catch {
|
|
5879
|
+
return null;
|
|
5880
|
+
}
|
|
5881
|
+
},
|
|
5882
|
+
async set(entry) {
|
|
5883
|
+
try {
|
|
5884
|
+
const db = await openDb();
|
|
5885
|
+
await new Promise((resolve, reject) => {
|
|
5886
|
+
const tx = db.transaction(STORE_NAME, 'readwrite');
|
|
5887
|
+
tx.objectStore(STORE_NAME).put(entry);
|
|
5888
|
+
tx.oncomplete = () => resolve();
|
|
5889
|
+
tx.onerror = () => reject(tx.error);
|
|
5890
|
+
});
|
|
5891
|
+
await evict(db);
|
|
5892
|
+
}
|
|
5893
|
+
catch {
|
|
5894
|
+
// ignore cache write errors
|
|
5895
|
+
}
|
|
5896
|
+
},
|
|
5897
|
+
};
|
|
5306
5898
|
|
|
5307
5899
|
const CANVAS_ID = 'clarity-heatmap-canvas';
|
|
5308
5900
|
const ATTENTION_HUES = [240, 210, 180, 150, 120, 90, 60, 30, 0];
|
|
5309
|
-
const CANVAS_MAX_HEIGHT = 65535;
|
|
5901
|
+
const CANVAS_MAX_HEIGHT$1 = 65535;
|
|
5310
5902
|
const Z_INDEX = 2147483647;
|
|
5311
5903
|
const DEFAULT_IFRAME_ID = 'clarity-snapshot-heatmap-iframe';
|
|
5312
5904
|
const SECONDARY_IFRAME_ID = 'clarity-snapshot-heatmap-iframe-secondary';
|
|
@@ -5371,7 +5963,7 @@ class AttentionMapRenderer {
|
|
|
5371
5963
|
const body = doc.body;
|
|
5372
5964
|
const de = doc.documentElement;
|
|
5373
5965
|
const contentHeight = Math.max(body.scrollHeight, body.offsetHeight, de.clientHeight, de.scrollHeight, de.offsetHeight);
|
|
5374
|
-
canvas.height = Math.min(contentHeight, CANVAS_MAX_HEIGHT);
|
|
5966
|
+
canvas.height = Math.min(contentHeight, CANVAS_MAX_HEIGHT$1);
|
|
5375
5967
|
canvas.style.top = '0px';
|
|
5376
5968
|
if (canvas.width <= 0 || canvas.height <= 0 || !this.attentionMapData)
|
|
5377
5969
|
return;
|
|
@@ -5393,7 +5985,7 @@ class AttentionMapRenderer {
|
|
|
5393
5985
|
const body = doc.body;
|
|
5394
5986
|
const de = doc.documentElement;
|
|
5395
5987
|
const contentHeight = Math.max(body.scrollHeight, body.offsetHeight, de.clientHeight, de.scrollHeight, de.offsetHeight);
|
|
5396
|
-
canvas.height = Math.min(contentHeight, CANVAS_MAX_HEIGHT);
|
|
5988
|
+
canvas.height = Math.min(contentHeight, CANVAS_MAX_HEIGHT$1);
|
|
5397
5989
|
canvas.style.top = '0px';
|
|
5398
5990
|
let iframeEl = document.getElementById(this.heatmapIframeId);
|
|
5399
5991
|
if (!iframeEl) {
|
|
@@ -5409,9 +6001,9 @@ class AttentionMapRenderer {
|
|
|
5409
6001
|
for (const point of this.attentionMapData) {
|
|
5410
6002
|
const timePercent = Math.floor((100 * point.cumulativeTime) / maxCumulativeTime);
|
|
5411
6003
|
const colorIndex = timePercent <= 5 ? 0 : Math.min(Math.ceil(timePercent / 10), 8);
|
|
5412
|
-
if (point.
|
|
6004
|
+
if (point.scrollReachY / 100 <= 1) {
|
|
5413
6005
|
point.colorIndex = colorIndex;
|
|
5414
|
-
colorStops[point.
|
|
6006
|
+
colorStops[point.scrollReachY] = colorIndex;
|
|
5415
6007
|
}
|
|
5416
6008
|
}
|
|
5417
6009
|
// Smooth the top portion based on the fold/viewport ratio
|
|
@@ -5442,7 +6034,7 @@ class AttentionMapRenderer {
|
|
|
5442
6034
|
buildAggregatedData = (doc, contentHeight, iframeEl) => {
|
|
5443
6035
|
const elementCache = new Map();
|
|
5444
6036
|
this.attentionMapData = Array.from({ length: 100 }, (_, i) => ({
|
|
5445
|
-
|
|
6037
|
+
scrollReachY: i,
|
|
5446
6038
|
cumulativeTime: 0,
|
|
5447
6039
|
colorIndex: 0,
|
|
5448
6040
|
}));
|
|
@@ -5481,63 +6073,526 @@ class AttentionMapRenderer {
|
|
|
5481
6073
|
}
|
|
5482
6074
|
}
|
|
5483
6075
|
}
|
|
5484
|
-
return maxTime;
|
|
5485
|
-
};
|
|
5486
|
-
overlay = () => {
|
|
5487
|
-
if (!this.state?.window)
|
|
5488
|
-
return null;
|
|
5489
|
-
const win = this.state.window;
|
|
5490
|
-
const doc = win.document;
|
|
5491
|
-
const de = doc.documentElement;
|
|
5492
|
-
let canvas = doc.getElementById(CANVAS_ID + '-single');
|
|
5493
|
-
if (!canvas) {
|
|
5494
|
-
canvas = doc.createElement('CANVAS');
|
|
5495
|
-
canvas.id = CANVAS_ID + '-single';
|
|
5496
|
-
canvas.width = 0;
|
|
5497
|
-
canvas.height = 0;
|
|
5498
|
-
canvas.style.position = 'absolute';
|
|
5499
|
-
canvas.style.zIndex = `${Z_INDEX}`;
|
|
5500
|
-
canvas.style.display = 'block';
|
|
5501
|
-
de.appendChild(canvas);
|
|
5502
|
-
win.addEventListener('resize', this.reRender, true);
|
|
5503
|
-
this.observer = typeof window.ResizeObserver !== 'undefined' ? new ResizeObserver(this.reRender) : null;
|
|
5504
|
-
this.observer?.observe(doc.body);
|
|
6076
|
+
return maxTime;
|
|
6077
|
+
};
|
|
6078
|
+
overlay = () => {
|
|
6079
|
+
if (!this.state?.window)
|
|
6080
|
+
return null;
|
|
6081
|
+
const win = this.state.window;
|
|
6082
|
+
const doc = win.document;
|
|
6083
|
+
const de = doc.documentElement;
|
|
6084
|
+
let canvas = doc.getElementById(CANVAS_ID + '-single');
|
|
6085
|
+
if (!canvas) {
|
|
6086
|
+
canvas = doc.createElement('CANVAS');
|
|
6087
|
+
canvas.id = CANVAS_ID + '-single';
|
|
6088
|
+
canvas.width = 0;
|
|
6089
|
+
canvas.height = 0;
|
|
6090
|
+
canvas.style.position = 'absolute';
|
|
6091
|
+
canvas.style.zIndex = `${Z_INDEX}`;
|
|
6092
|
+
canvas.style.display = 'block';
|
|
6093
|
+
de.appendChild(canvas);
|
|
6094
|
+
win.addEventListener('resize', this.reRender, true);
|
|
6095
|
+
this.observer = typeof window.ResizeObserver !== 'undefined' ? new ResizeObserver(this.reRender) : null;
|
|
6096
|
+
this.observer?.observe(doc.body);
|
|
6097
|
+
}
|
|
6098
|
+
canvas.width = de.clientWidth;
|
|
6099
|
+
canvas.height = de.clientHeight;
|
|
6100
|
+
canvas.style.left = `${win.pageXOffset}px`;
|
|
6101
|
+
canvas.style.top = `${win.pageYOffset}px`;
|
|
6102
|
+
canvas.getContext('2d')?.clearRect(0, 0, canvas.width, canvas.height);
|
|
6103
|
+
return canvas;
|
|
6104
|
+
};
|
|
6105
|
+
}
|
|
6106
|
+
|
|
6107
|
+
const CANVAS_MAX_HEIGHT = 65535;
|
|
6108
|
+
const REDRAW_INTERVAL = 30;
|
|
6109
|
+
class ScrollBucketRenderer {
|
|
6110
|
+
heatmap;
|
|
6111
|
+
lastData = null;
|
|
6112
|
+
dimensionsListener = null;
|
|
6113
|
+
redrawTimeout = null;
|
|
6114
|
+
constructor(heatmap) {
|
|
6115
|
+
this.heatmap = heatmap;
|
|
6116
|
+
this.attachDimensionsListener();
|
|
6117
|
+
}
|
|
6118
|
+
redraw = () => {
|
|
6119
|
+
if (!this.lastData)
|
|
6120
|
+
return;
|
|
6121
|
+
if (this.redrawTimeout)
|
|
6122
|
+
clearTimeout(this.redrawTimeout);
|
|
6123
|
+
this.redrawTimeout = setTimeout(() => this.draw(this.lastData), REDRAW_INTERVAL);
|
|
6124
|
+
};
|
|
6125
|
+
attachDimensionsListener = () => {
|
|
6126
|
+
this.dimensionsListener = () => this.redraw();
|
|
6127
|
+
window.addEventListener('iframe-dimensions-applied', this.dimensionsListener);
|
|
6128
|
+
};
|
|
6129
|
+
detachDimensionsListener = () => {
|
|
6130
|
+
if (this.dimensionsListener) {
|
|
6131
|
+
window.removeEventListener('iframe-dimensions-applied', this.dimensionsListener);
|
|
6132
|
+
this.dimensionsListener = null;
|
|
6133
|
+
}
|
|
6134
|
+
if (this.redrawTimeout) {
|
|
6135
|
+
clearTimeout(this.redrawTimeout);
|
|
6136
|
+
this.redrawTimeout = null;
|
|
6137
|
+
}
|
|
6138
|
+
};
|
|
6139
|
+
reset = () => {
|
|
6140
|
+
// no-op: canvas state is managed by the shared heatmap instance
|
|
6141
|
+
};
|
|
6142
|
+
clear = () => {
|
|
6143
|
+
this.lastData = null;
|
|
6144
|
+
this.detachDimensionsListener();
|
|
6145
|
+
};
|
|
6146
|
+
/**
|
|
6147
|
+
* Render discrete color bands for scrollRevenue / scrollAttention data.
|
|
6148
|
+
* Uses the shared heatmap canvas (same as HeatmapHelper.scroll()) to avoid
|
|
6149
|
+
* duplicate portal canvas management.
|
|
6150
|
+
* Stores data so the canvas can be redrawn when iframe height changes.
|
|
6151
|
+
*/
|
|
6152
|
+
renderBucket = async (data) => {
|
|
6153
|
+
if (!this.heatmap)
|
|
6154
|
+
return;
|
|
6155
|
+
this.attachDimensionsListener();
|
|
6156
|
+
this.lastData = data;
|
|
6157
|
+
await this.heatmap.waitForDialogs();
|
|
6158
|
+
this.draw(data);
|
|
6159
|
+
};
|
|
6160
|
+
draw = (data) => {
|
|
6161
|
+
if (!this.heatmap)
|
|
6162
|
+
return;
|
|
6163
|
+
const canvas = this.heatmap.overlay();
|
|
6164
|
+
if (!canvas)
|
|
6165
|
+
return;
|
|
6166
|
+
const context = canvas.getContext('2d');
|
|
6167
|
+
const doc = this.heatmap.state.window.document;
|
|
6168
|
+
const body = doc.body;
|
|
6169
|
+
const de = doc.documentElement;
|
|
6170
|
+
const height = Math.max(body.scrollHeight, body.offsetHeight, de.clientHeight, de.scrollHeight, de.offsetHeight);
|
|
6171
|
+
// Same canvas setup as HeatmapHelper.scroll(): full content height, top=0
|
|
6172
|
+
canvas.height = Math.min(height, CANVAS_MAX_HEIGHT);
|
|
6173
|
+
canvas.style.top = '0px';
|
|
6174
|
+
if (canvas.width <= 0 || canvas.height <= 0)
|
|
6175
|
+
return;
|
|
6176
|
+
const buckets = this.mapToBuckets(data);
|
|
6177
|
+
const maxPercent = buckets.length > 0 ? Math.max(...buckets.map((b) => b.percent)) : 0;
|
|
6178
|
+
// Coldest hue (240 = blue) used as the baseline for buckets with no data
|
|
6179
|
+
const coldColor = 'hsla(240, 100%, 50%, 0.6)';
|
|
6180
|
+
const gradient = context.createLinearGradient(0, 0, 0, canvas.height);
|
|
6181
|
+
if (buckets.length === 0 || maxPercent <= 0) {
|
|
6182
|
+
// No data — fill entire canvas with the coldest color
|
|
6183
|
+
gradient.addColorStop(0, coldColor);
|
|
6184
|
+
gradient.addColorStop(1, coldColor);
|
|
6185
|
+
}
|
|
6186
|
+
else {
|
|
6187
|
+
for (const bucket of buckets) {
|
|
6188
|
+
const stopMid = (bucket.startY + bucket.endY) / 2 / 100;
|
|
6189
|
+
// Same hue direction as HeatmapHelper.scroll() (MaxHue = 240):
|
|
6190
|
+
// high percent → hue 0 (red/hot), low percent → hue 240 (blue/cold)
|
|
6191
|
+
const hue = (1 - bucket.percent / maxPercent) * 240;
|
|
6192
|
+
const color = `hsla(${Math.round(hue)}, 100%, 50%, 0.6)`;
|
|
6193
|
+
gradient.addColorStop(stopMid, color);
|
|
6194
|
+
}
|
|
6195
|
+
}
|
|
6196
|
+
context.fillStyle = gradient;
|
|
6197
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
6198
|
+
};
|
|
6199
|
+
/**
|
|
6200
|
+
* Convert flat position markers → structured buckets with startY/endY.
|
|
6201
|
+
*
|
|
6202
|
+
* Input positions: [0, 5, 10, 15, ..., 95]
|
|
6203
|
+
* position=0 → { startY: 0, endY: 5 } (special start marker)
|
|
6204
|
+
* position=10 → { startY: 5, endY: 10 }
|
|
6205
|
+
* position=15 → { startY: 10, endY: 15 }
|
|
6206
|
+
*/
|
|
6207
|
+
mapToBuckets = (data) => {
|
|
6208
|
+
const sorted = [...data].sort((a, b) => a.position - b.position);
|
|
6209
|
+
const buckets = [];
|
|
6210
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
6211
|
+
const current = sorted[i];
|
|
6212
|
+
let startY;
|
|
6213
|
+
let endY;
|
|
6214
|
+
if (current.position === 0) {
|
|
6215
|
+
// position=0 is the start marker → range 0 to next position
|
|
6216
|
+
const next = sorted[i + 1];
|
|
6217
|
+
if (!next)
|
|
6218
|
+
continue;
|
|
6219
|
+
startY = 0;
|
|
6220
|
+
endY = next.position;
|
|
6221
|
+
}
|
|
6222
|
+
else {
|
|
6223
|
+
// Each non-zero position is the END of its bucket; start = previous position
|
|
6224
|
+
const prev = sorted[i - 1];
|
|
6225
|
+
startY = prev ? prev.position : 0;
|
|
6226
|
+
endY = current.position;
|
|
6227
|
+
}
|
|
6228
|
+
buckets.push({ startY, endY, value: current.value, percent: current.percent });
|
|
6229
|
+
}
|
|
6230
|
+
return buckets;
|
|
6231
|
+
};
|
|
6232
|
+
}
|
|
6233
|
+
|
|
6234
|
+
var Event;
|
|
6235
|
+
(function (Event) {
|
|
6236
|
+
/* Data */
|
|
6237
|
+
Event[Event["Metric"] = 0] = "Metric";
|
|
6238
|
+
Event[Event["Dimension"] = 1] = "Dimension";
|
|
6239
|
+
Event[Event["Upload"] = 2] = "Upload";
|
|
6240
|
+
Event[Event["Upgrade"] = 3] = "Upgrade";
|
|
6241
|
+
Event[Event["Baseline"] = 4] = "Baseline";
|
|
6242
|
+
Event[Event["Discover"] = 5] = "Discover";
|
|
6243
|
+
Event[Event["Mutation"] = 6] = "Mutation";
|
|
6244
|
+
Event[Event["Region"] = 7] = "Region";
|
|
6245
|
+
Event[Event["Document"] = 8] = "Document";
|
|
6246
|
+
Event[Event["Click"] = 9] = "Click";
|
|
6247
|
+
Event[Event["Scroll"] = 10] = "Scroll";
|
|
6248
|
+
Event[Event["Resize"] = 11] = "Resize";
|
|
6249
|
+
Event[Event["MouseMove"] = 12] = "MouseMove";
|
|
6250
|
+
Event[Event["MouseDown"] = 13] = "MouseDown";
|
|
6251
|
+
Event[Event["MouseUp"] = 14] = "MouseUp";
|
|
6252
|
+
Event[Event["MouseWheel"] = 15] = "MouseWheel";
|
|
6253
|
+
Event[Event["DoubleClick"] = 16] = "DoubleClick";
|
|
6254
|
+
Event[Event["TouchStart"] = 17] = "TouchStart";
|
|
6255
|
+
Event[Event["TouchEnd"] = 18] = "TouchEnd";
|
|
6256
|
+
Event[Event["TouchMove"] = 19] = "TouchMove";
|
|
6257
|
+
Event[Event["TouchCancel"] = 20] = "TouchCancel";
|
|
6258
|
+
Event[Event["Selection"] = 21] = "Selection";
|
|
6259
|
+
Event[Event["Timeline"] = 22] = "Timeline";
|
|
6260
|
+
Event[Event["Page"] = 23] = "Page";
|
|
6261
|
+
Event[Event["Custom"] = 24] = "Custom";
|
|
6262
|
+
Event[Event["Ping"] = 25] = "Ping";
|
|
6263
|
+
Event[Event["Unload"] = 26] = "Unload";
|
|
6264
|
+
Event[Event["Input"] = 27] = "Input";
|
|
6265
|
+
Event[Event["Visibility"] = 28] = "Visibility";
|
|
6266
|
+
Event[Event["Navigation"] = 29] = "Navigation";
|
|
6267
|
+
/**
|
|
6268
|
+
* @deprecated No longer support Network Connection
|
|
6269
|
+
*/
|
|
6270
|
+
Event[Event["Connection"] = 30] = "Connection";
|
|
6271
|
+
Event[Event["ScriptError"] = 31] = "ScriptError";
|
|
6272
|
+
/**
|
|
6273
|
+
* @deprecated No longer support Image Error
|
|
6274
|
+
*/
|
|
6275
|
+
Event[Event["ImageError"] = 32] = "ImageError";
|
|
6276
|
+
Event[Event["Log"] = 33] = "Log";
|
|
6277
|
+
Event[Event["Variable"] = 34] = "Variable";
|
|
6278
|
+
Event[Event["Limit"] = 35] = "Limit";
|
|
6279
|
+
Event[Event["Summary"] = 36] = "Summary";
|
|
6280
|
+
/**
|
|
6281
|
+
* @deprecated No longer support Box event
|
|
6282
|
+
*/
|
|
6283
|
+
Event[Event["Box"] = 37] = "Box";
|
|
6284
|
+
Event[Event["Clipboard"] = 38] = "Clipboard";
|
|
6285
|
+
Event[Event["Submit"] = 39] = "Submit";
|
|
6286
|
+
Event[Event["Extract"] = 40] = "Extract";
|
|
6287
|
+
Event[Event["Fraud"] = 41] = "Fraud";
|
|
6288
|
+
Event[Event["Change"] = 42] = "Change";
|
|
6289
|
+
Event[Event["Snapshot"] = 43] = "Snapshot";
|
|
6290
|
+
Event[Event["Animation"] = 44] = "Animation";
|
|
6291
|
+
Event[Event["StyleSheetAdoption"] = 45] = "StyleSheetAdoption";
|
|
6292
|
+
Event[Event["StyleSheetUpdate"] = 46] = "StyleSheetUpdate";
|
|
6293
|
+
Event[Event["Consent"] = 47] = "Consent";
|
|
6294
|
+
Event[Event["ContextMenu"] = 48] = "ContextMenu";
|
|
6295
|
+
// 49 is reserved for internal use
|
|
6296
|
+
Event[Event["Focus"] = 50] = "Focus";
|
|
6297
|
+
Event[Event["CustomElement"] = 51] = "CustomElement";
|
|
6298
|
+
Event[Event["Chat"] = 52] = "Chat";
|
|
6299
|
+
// Apps specific events
|
|
6300
|
+
Event[Event["WebViewDiscover"] = 100] = "WebViewDiscover";
|
|
6301
|
+
Event[Event["WebViewMutation"] = 101] = "WebViewMutation";
|
|
6302
|
+
Event[Event["MutationError"] = 102] = "MutationError";
|
|
6303
|
+
Event[Event["FragmentVisibility"] = 103] = "FragmentVisibility";
|
|
6304
|
+
Event[Event["Keystrokes"] = 104] = "Keystrokes";
|
|
6305
|
+
Event[Event["BackGesture"] = 105] = "BackGesture";
|
|
6306
|
+
Event[Event["WebViewStatus"] = 106] = "WebViewStatus";
|
|
6307
|
+
Event[Event["AppInstallReferrer"] = 107] = "AppInstallReferrer";
|
|
6308
|
+
// 200-300 reserved for internal use
|
|
6309
|
+
})(Event || (Event = {}));
|
|
6310
|
+
|
|
6311
|
+
var Constant;
|
|
6312
|
+
(function (Constant) {
|
|
6313
|
+
Constant["Empty"] = "";
|
|
6314
|
+
Constant["SvgPrefix"] = "svg:";
|
|
6315
|
+
Constant["DataPrefix"] = "data:";
|
|
6316
|
+
Constant["IFramePrefix"] = "iframe:";
|
|
6317
|
+
Constant["SvgNamespace"] = "http://www.w3.org/2000/svg";
|
|
6318
|
+
Constant["Id"] = "id";
|
|
6319
|
+
Constant["Class"] = "class";
|
|
6320
|
+
Constant["Style"] = "style";
|
|
6321
|
+
Constant["Href"] = "href";
|
|
6322
|
+
Constant["Src"] = "src";
|
|
6323
|
+
Constant["Srcset"] = "srcset";
|
|
6324
|
+
Constant["Hash"] = "#";
|
|
6325
|
+
Constant["Dot"] = ".";
|
|
6326
|
+
Constant["Separator"] = ">";
|
|
6327
|
+
Constant["Tilde"] = "~";
|
|
6328
|
+
Constant["Bang"] = "!";
|
|
6329
|
+
Constant["Period"] = ".";
|
|
6330
|
+
Constant["Comma"] = ",";
|
|
6331
|
+
Constant["DataAttribute"] = "data-";
|
|
6332
|
+
Constant["MaskData"] = "data-clarity-mask";
|
|
6333
|
+
Constant["UnmaskData"] = "data-clarity-unmask";
|
|
6334
|
+
Constant["RegionData"] = "data-clarity-region";
|
|
6335
|
+
Constant["GXDialogModal"] = "gx-dialog-modal";
|
|
6336
|
+
Constant["Type"] = "type";
|
|
6337
|
+
Constant["Submit"] = "submit";
|
|
6338
|
+
Constant["Name"] = "name";
|
|
6339
|
+
Constant["Base"] = "*B";
|
|
6340
|
+
Constant["SameOrigin"] = "*O";
|
|
6341
|
+
Constant["Object"] = "object";
|
|
6342
|
+
Constant["Function"] = "function";
|
|
6343
|
+
Constant["StyleTag"] = "STYLE";
|
|
6344
|
+
Constant["LinkTag"] = "LINK";
|
|
6345
|
+
Constant["InputTag"] = "INPUT";
|
|
6346
|
+
Constant["IFrameTag"] = "IFRAME";
|
|
6347
|
+
Constant["ImageTag"] = "IMG";
|
|
6348
|
+
Constant["TitleTag"] = "TITLE";
|
|
6349
|
+
Constant["BodyTag"] = "BODY";
|
|
6350
|
+
Constant["SvgTag"] = "svg:svg";
|
|
6351
|
+
Constant["BaseTag"] = "BASE";
|
|
6352
|
+
Constant["NativeCode"] = "[native code]";
|
|
6353
|
+
Constant["DocumentTag"] = "*D";
|
|
6354
|
+
Constant["ShadowDomTag"] = "*S";
|
|
6355
|
+
Constant["PolyfillShadowDomTag"] = "*P";
|
|
6356
|
+
Constant["TextTag"] = "*T";
|
|
6357
|
+
Constant["SuspendMutationTag"] = "*M";
|
|
6358
|
+
Constant["ChildList"] = "childList";
|
|
6359
|
+
Constant["Attributes"] = "attributes";
|
|
6360
|
+
Constant["CharacterData"] = "characterData";
|
|
6361
|
+
Constant["Throttle"] = "throttle";
|
|
6362
|
+
Constant["LoadEvent"] = "load";
|
|
6363
|
+
Constant["Pixel"] = "px";
|
|
6364
|
+
Constant["BorderBox"] = "border-box";
|
|
6365
|
+
Constant["Value"] = "value";
|
|
6366
|
+
Constant["MutationObserver"] = "MutationObserver";
|
|
6367
|
+
Constant["JsonLD"] = "application/ld+json";
|
|
6368
|
+
Constant["String"] = "string";
|
|
6369
|
+
Constant["Number"] = "number";
|
|
6370
|
+
Constant["Disable"] = "disable";
|
|
6371
|
+
Constant["HTML"] = "HTML";
|
|
6372
|
+
Constant["Property"] = "property";
|
|
6373
|
+
Constant["Content"] = "content";
|
|
6374
|
+
Constant["Generator"] = "generator";
|
|
6375
|
+
Constant["ogType"] = "og:type";
|
|
6376
|
+
Constant["ogTitle"] = "og:title";
|
|
6377
|
+
Constant["SvgStyle"] = "svg:style";
|
|
6378
|
+
Constant["StyleSheet"] = "stylesheet";
|
|
6379
|
+
})(Constant || (Constant = {}));
|
|
6380
|
+
|
|
6381
|
+
var ShortCircuitStrategy;
|
|
6382
|
+
(function (ShortCircuitStrategy) {
|
|
6383
|
+
ShortCircuitStrategy[ShortCircuitStrategy["None"] = 0] = "None";
|
|
6384
|
+
ShortCircuitStrategy[ShortCircuitStrategy["HashFirstTimestamp"] = 1] = "HashFirstTimestamp";
|
|
6385
|
+
ShortCircuitStrategy[ShortCircuitStrategy["HashFirstTimestampPlusBuffer"] = 2] = "HashFirstTimestampPlusBuffer";
|
|
6386
|
+
ShortCircuitStrategy[ShortCircuitStrategy["HashBeforeDeleted"] = 3] = "HashBeforeDeleted";
|
|
6387
|
+
})(ShortCircuitStrategy || (ShortCircuitStrategy = {}));
|
|
6388
|
+
|
|
6389
|
+
function isShadowDomNode(tag) {
|
|
6390
|
+
return tag === Constant.ShadowDomTag || tag === Constant.PolyfillShadowDomTag;
|
|
6391
|
+
}
|
|
6392
|
+
/** Scan the rendered DOM (including nested shadow roots) to collect tag names of shadow host elements. */
|
|
6393
|
+
function collectShadowHostTags(root, tags = new Set()) {
|
|
6394
|
+
root.querySelectorAll('*').forEach((el) => {
|
|
6395
|
+
if (el.shadowRoot) {
|
|
6396
|
+
tags.add(el.tagName);
|
|
6397
|
+
collectShadowHostTags(el.shadowRoot, tags);
|
|
6398
|
+
}
|
|
6399
|
+
});
|
|
6400
|
+
return tags;
|
|
6401
|
+
}
|
|
6402
|
+
/** Build a shadow subtree ID set from all DOM events.
|
|
6403
|
+
* Two-pass cascade to handle any event ordering:
|
|
6404
|
+
* Pass 1 — seed *S/*P node IDs.
|
|
6405
|
+
* Pass 2 — cascade: any node whose parent is in the set is also in the set (repeat until stable). */
|
|
6406
|
+
function buildShadowSubtreeIds(allDomEvents) {
|
|
6407
|
+
const subtreeIds = new Set();
|
|
6408
|
+
for (const e of allDomEvents) {
|
|
6409
|
+
const data = e.data;
|
|
6410
|
+
for (const node of data ?? []) {
|
|
6411
|
+
if (isShadowDomNode(node.tag))
|
|
6412
|
+
subtreeIds.add(node.id);
|
|
6413
|
+
}
|
|
6414
|
+
}
|
|
6415
|
+
let changed = true;
|
|
6416
|
+
while (changed) {
|
|
6417
|
+
changed = false;
|
|
6418
|
+
for (const e of allDomEvents) {
|
|
6419
|
+
const data = e.data;
|
|
6420
|
+
for (const node of data ?? []) {
|
|
6421
|
+
if (!subtreeIds.has(node.id) && subtreeIds.has(node.parent)) {
|
|
6422
|
+
subtreeIds.add(node.id);
|
|
6423
|
+
changed = true;
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
}
|
|
6427
|
+
}
|
|
6428
|
+
return subtreeIds;
|
|
6429
|
+
}
|
|
6430
|
+
function filterShadowNodes(data, subtreeIds, shadowHostTags) {
|
|
6431
|
+
return (data?.filter((node) => {
|
|
6432
|
+
if (isShadowDomNode(node.tag))
|
|
6433
|
+
return true;
|
|
6434
|
+
if (shadowHostTags.has(node.tag ?? ''))
|
|
6435
|
+
return true;
|
|
6436
|
+
return subtreeIds.has(node.id);
|
|
6437
|
+
}) ?? []);
|
|
6438
|
+
}
|
|
6439
|
+
/** Extract shadow DOM nodes from merged.dom (initial Discover event, processed by setup()).
|
|
6440
|
+
* Returns a filtered copy of the dom event, or null if no shadow DOM present. */
|
|
6441
|
+
function extractSpecialDom(dom, events, shadowHostTags) {
|
|
6442
|
+
const mutationEvents = events.filter((e) => e.event === Event.Mutation);
|
|
6443
|
+
const subtreeIds = buildShadowSubtreeIds([dom, ...mutationEvents]);
|
|
6444
|
+
const shadowNodes = filterShadowNodes(dom.data, subtreeIds, shadowHostTags);
|
|
6445
|
+
return shadowNodes.length ? { ...dom, data: shadowNodes } : null;
|
|
6446
|
+
}
|
|
6447
|
+
/** Extract shadow DOM mutations + StyleSheet + CustomElement events from merged.events. */
|
|
6448
|
+
function extractSpecialEvents(events, dom, shadowHostTags) {
|
|
6449
|
+
const mutationEvents = events.filter((e) => e.event === Event.Mutation);
|
|
6450
|
+
const subtreeIds = buildShadowSubtreeIds([dom, ...mutationEvents]);
|
|
6451
|
+
const result = [];
|
|
6452
|
+
for (const e of events) {
|
|
6453
|
+
const ev = e.event;
|
|
6454
|
+
if (ev === Event.StyleSheetAdoption ||
|
|
6455
|
+
ev === Event.StyleSheetUpdate ||
|
|
6456
|
+
ev === Event.CustomElement) {
|
|
6457
|
+
result.push(e);
|
|
6458
|
+
continue;
|
|
6459
|
+
}
|
|
6460
|
+
if (ev === Event.Mutation) {
|
|
6461
|
+
const shadowNodes = filterShadowNodes(e.data, subtreeIds, shadowHostTags);
|
|
6462
|
+
if (shadowNodes.length)
|
|
6463
|
+
result.push({ ...e, data: shadowNodes });
|
|
6464
|
+
}
|
|
6465
|
+
}
|
|
6466
|
+
return result;
|
|
6467
|
+
}
|
|
6468
|
+
|
|
6469
|
+
// utils/retry.ts
|
|
6470
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
6471
|
+
/**
|
|
6472
|
+
* Retry until the callback returns truthy, or throw if the limit is exceeded.
|
|
6473
|
+
*
|
|
6474
|
+
* @example
|
|
6475
|
+
* await retry(() => doc.readyState === 'complete', { timeout: 5000, label: 'DOM ready' });
|
|
6476
|
+
*
|
|
6477
|
+
* @example
|
|
6478
|
+
* const el = await retry(() => document.getElementById('app'), { maxRetries: 20 });
|
|
6479
|
+
*/
|
|
6480
|
+
async function retry(callback, options = {}) {
|
|
6481
|
+
const { interval = 100, label = 'Retry' } = options;
|
|
6482
|
+
const maxRetries = options.timeout != null ? Math.ceil(options.timeout / interval) : (options.maxRetries ?? 50);
|
|
6483
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
6484
|
+
console.log(`🔄 [${label}] attempt ${attempt}/${maxRetries}`);
|
|
6485
|
+
const result = await callback();
|
|
6486
|
+
if (result) {
|
|
6487
|
+
return result;
|
|
6488
|
+
}
|
|
6489
|
+
if (attempt < maxRetries) {
|
|
6490
|
+
await delay(interval);
|
|
5505
6491
|
}
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
canvas.style.top = `${win.pageYOffset}px`;
|
|
5510
|
-
canvas.getContext('2d')?.clearRect(0, 0, canvas.width, canvas.height);
|
|
5511
|
-
return canvas;
|
|
5512
|
-
};
|
|
6492
|
+
}
|
|
6493
|
+
const totalMs = maxRetries * interval;
|
|
6494
|
+
throw new Error(`[${label}] Timed out after ${maxRetries} retries (${totalMs}ms)`);
|
|
5513
6495
|
}
|
|
5514
6496
|
|
|
6497
|
+
const perf$2 = createPerfTimer('Render');
|
|
6498
|
+
const YIELD_INTERVAL_MS = 16;
|
|
6499
|
+
async function yieldToMain() {
|
|
6500
|
+
if ('scheduler' in globalThis && typeof globalThis.scheduler?.yield === 'function') {
|
|
6501
|
+
await globalThis.scheduler.yield();
|
|
6502
|
+
}
|
|
6503
|
+
else {
|
|
6504
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
6505
|
+
}
|
|
6506
|
+
}
|
|
6507
|
+
const renderLoop = async (ctx, options) => {
|
|
6508
|
+
const { events, useproxy } = options;
|
|
6509
|
+
const shortCircuitStrategy = options.shortCircuitStrategy ?? ShortCircuitStrategy.None;
|
|
6510
|
+
const hash = options.hash ?? null;
|
|
6511
|
+
const t0 = perf$2.mark('RenderLoop start');
|
|
6512
|
+
let lastYield = performance.now();
|
|
6513
|
+
const totalEvents = events.length;
|
|
6514
|
+
for (let i = 0; i < totalEvents; i++) {
|
|
6515
|
+
const entry = events[i];
|
|
6516
|
+
const entryEvent = entry.event;
|
|
6517
|
+
const now = performance.now();
|
|
6518
|
+
if (now - lastYield > YIELD_INTERVAL_MS) {
|
|
6519
|
+
console.log(`[RenderLoop] ${i}/${totalEvents} time:`, now - lastYield);
|
|
6520
|
+
await yieldToMain();
|
|
6521
|
+
lastYield = performance.now();
|
|
6522
|
+
}
|
|
6523
|
+
switch (entryEvent) {
|
|
6524
|
+
case Event.StyleSheetAdoption:
|
|
6525
|
+
case Event.StyleSheetUpdate:
|
|
6526
|
+
ctx.layout.styleChange(entry);
|
|
6527
|
+
break;
|
|
6528
|
+
case Event.CustomElement:
|
|
6529
|
+
ctx.layout.customElement(entry);
|
|
6530
|
+
break;
|
|
6531
|
+
case Event.Mutation: {
|
|
6532
|
+
const domEvent = entry;
|
|
6533
|
+
ctx.renderTime = domEvent.time;
|
|
6534
|
+
if (ctx.shortCircuitRendering(shortCircuitStrategy, domEvent, hash))
|
|
6535
|
+
return;
|
|
6536
|
+
ctx.layout.markup(domEvent, useproxy);
|
|
6537
|
+
break;
|
|
6538
|
+
}
|
|
6539
|
+
}
|
|
6540
|
+
}
|
|
6541
|
+
perf$2.measure('RenderLoop', t0);
|
|
6542
|
+
};
|
|
6543
|
+
|
|
5515
6544
|
class GXVisualizer extends Visualizer {
|
|
5516
6545
|
attentionMap;
|
|
6546
|
+
scrollBucketMap;
|
|
5517
6547
|
originalClearmap;
|
|
5518
6548
|
originalSetup;
|
|
6549
|
+
originalScrollmap;
|
|
6550
|
+
originalClickmap;
|
|
5519
6551
|
constructor() {
|
|
5520
6552
|
super();
|
|
5521
6553
|
this.attentionMap = new AttentionMapRenderer(null);
|
|
5522
|
-
|
|
6554
|
+
this.scrollBucketMap = new ScrollBucketRenderer(null);
|
|
5523
6555
|
this.originalSetup = this.setup;
|
|
5524
6556
|
this.originalClearmap = this.clearmap;
|
|
6557
|
+
this.originalScrollmap = this.scrollmap;
|
|
6558
|
+
this.originalClickmap = this.clickmap;
|
|
5525
6559
|
this.clearmap = this.clearmapOverride;
|
|
5526
6560
|
this.setup = this.setupOverride;
|
|
6561
|
+
this.scrollmap = this.scrollmapOverride;
|
|
6562
|
+
this.clickmap = this.clickmapOverride;
|
|
5527
6563
|
}
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
6564
|
+
htmlRender = async (props) => {
|
|
6565
|
+
const { decoded, target, portalCanvasId, useproxy, logerror } = props;
|
|
6566
|
+
if (!decoded || decoded.length === 0 || !target)
|
|
6567
|
+
return this;
|
|
6568
|
+
try {
|
|
6569
|
+
const merged = this.mergeForHtml(decoded);
|
|
6570
|
+
await this.setup(target, { version: decoded[0].envelope.version, dom: merged.dom, useproxy, portalCanvasId });
|
|
6571
|
+
await this.renderLoop(this, { events: merged.events, target, useproxy });
|
|
6572
|
+
}
|
|
6573
|
+
catch (e) {
|
|
6574
|
+
if (logerror)
|
|
6575
|
+
logerror(e);
|
|
6576
|
+
}
|
|
5536
6577
|
return this;
|
|
5537
6578
|
};
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
6579
|
+
htmlCached = async (cacheKey, options) => {
|
|
6580
|
+
const { decoded, target } = options;
|
|
6581
|
+
if (!decoded || decoded.length === 0 || !target)
|
|
6582
|
+
return this;
|
|
6583
|
+
const fullKey = buildCacheKey(cacheKey, options.shortCircuitStrategy);
|
|
6584
|
+
const cached = await htmlCache.get(fullKey);
|
|
6585
|
+
if (cached) {
|
|
6586
|
+
try {
|
|
6587
|
+
await this.buildHtmlByCached(cached, options);
|
|
6588
|
+
return this;
|
|
6589
|
+
}
|
|
6590
|
+
catch (e) {
|
|
6591
|
+
options?.logerror?.(e);
|
|
6592
|
+
}
|
|
6593
|
+
}
|
|
6594
|
+
await this.buildHtmlForCache(fullKey, options);
|
|
6595
|
+
return this;
|
|
5541
6596
|
};
|
|
5542
6597
|
/**
|
|
5543
6598
|
* Render attention/engagement map.
|
|
@@ -5549,75 +6604,256 @@ class GXVisualizer extends Visualizer {
|
|
|
5549
6604
|
this.clearmapOverride();
|
|
5550
6605
|
this.attentionMap.attention(attentionData, avgFold, this, isSecondary);
|
|
5551
6606
|
};
|
|
6607
|
+
/**
|
|
6608
|
+
* Render discrete color bands for scrollRevenue / scrollAttention data.
|
|
6609
|
+
* @param data - Array of bucket inputs with position markers (0, 5, 10, ..., 95) and percent values
|
|
6610
|
+
*/
|
|
6611
|
+
scrollBucket = async (data) => {
|
|
6612
|
+
this.clearmapOverride();
|
|
6613
|
+
await this.scrollBucketMap.renderBucket(data);
|
|
6614
|
+
};
|
|
6615
|
+
scrollmapOverride = (data, averageFold, addMarkers) => {
|
|
6616
|
+
this.clearmapOverride();
|
|
6617
|
+
this.originalScrollmap(data, averageFold, addMarkers);
|
|
6618
|
+
};
|
|
6619
|
+
clickmapOverride = (activity) => {
|
|
6620
|
+
this.clearmapOverride();
|
|
6621
|
+
this.originalClickmap(activity);
|
|
6622
|
+
};
|
|
6623
|
+
buildHtmlByCached = async (cached, options) => {
|
|
6624
|
+
const { target, useproxy, portalCanvasId } = options;
|
|
6625
|
+
if (!cached || !target)
|
|
6626
|
+
throw new Error('Failed to render HTML cached', { cause: { cached, target } });
|
|
6627
|
+
const doc = target.document;
|
|
6628
|
+
try {
|
|
6629
|
+
await this.setup(target, { version: cached.version, useproxy, portalCanvasId });
|
|
6630
|
+
doc.open();
|
|
6631
|
+
doc.writeln(cached.html);
|
|
6632
|
+
doc.close();
|
|
6633
|
+
const process = async () => {
|
|
6634
|
+
this.layout.hydrate(doc);
|
|
6635
|
+
// Replay shadow DOM from initial Discover event
|
|
6636
|
+
if (cached.specialDom) {
|
|
6637
|
+
this.layout.markup(cached.specialDom, useproxy);
|
|
6638
|
+
}
|
|
6639
|
+
// Replay shadow DOM mutations + StyleSheet + CustomElement
|
|
6640
|
+
await this.renderLoop(this, { ...options, events: cached.specialEvents });
|
|
6641
|
+
};
|
|
6642
|
+
await retry(() => doc.readyState === 'complete', { timeout: 30000, label: '[BuildHtml - Cached] DOM ready' });
|
|
6643
|
+
await process();
|
|
6644
|
+
return this;
|
|
6645
|
+
}
|
|
6646
|
+
catch (e) {
|
|
6647
|
+
throw new Error('Failed to render HTML cached', { cause: e });
|
|
6648
|
+
}
|
|
6649
|
+
};
|
|
6650
|
+
buildHtmlForCache = async (cacheKey, options) => {
|
|
6651
|
+
const { decoded, target, useproxy, portalCanvasId, logerror } = options;
|
|
6652
|
+
if (!decoded || decoded.length === 0 || !target)
|
|
6653
|
+
return this;
|
|
6654
|
+
try {
|
|
6655
|
+
const doc = target.document;
|
|
6656
|
+
const merged = this.mergeForHtml(decoded);
|
|
6657
|
+
await this.setup(target, { version: decoded[0].envelope.version, dom: merged.dom, useproxy, portalCanvasId });
|
|
6658
|
+
await this.renderLoop(this, { events: merged.events, target, useproxy });
|
|
6659
|
+
// if (hasVoidContainerChildren(doc)) return this;
|
|
6660
|
+
await retry(() => doc.readyState === 'complete', { timeout: 30000, label: '[BuildHtml] DOM ready' });
|
|
6661
|
+
const timestamp = Date.now();
|
|
6662
|
+
const version = decoded[0].envelope.version;
|
|
6663
|
+
const html = doc.documentElement.outerHTML;
|
|
6664
|
+
const shadowHostTags = collectShadowHostTags(doc);
|
|
6665
|
+
const specialDom = extractSpecialDom(merged.dom, merged.events, shadowHostTags);
|
|
6666
|
+
const specialEvents = extractSpecialEvents(merged.events, merged.dom, shadowHostTags);
|
|
6667
|
+
void htmlCache.set({ key: cacheKey, html, specialDom, specialEvents, version, timestamp });
|
|
6668
|
+
}
|
|
6669
|
+
catch (e) {
|
|
6670
|
+
if (logerror)
|
|
6671
|
+
logerror(e);
|
|
6672
|
+
console.log(`[BuildHtml] ~ Error:`, e);
|
|
6673
|
+
}
|
|
6674
|
+
return this;
|
|
6675
|
+
};
|
|
6676
|
+
renderLoop = async (ctx, options) => {
|
|
6677
|
+
await renderLoop(ctx, options);
|
|
6678
|
+
};
|
|
6679
|
+
setupOverride = async (target, options) => {
|
|
6680
|
+
// Mirror original Visualizer.setup(): only reset (remove listeners/state), not clear canvas.
|
|
6681
|
+
// The canvas lives in the old window's DOM which will be replaced by originalSetup's reset().
|
|
6682
|
+
this.attentionMap?.reset();
|
|
6683
|
+
this.scrollBucketMap?.reset();
|
|
6684
|
+
await this.originalSetup(target, options);
|
|
6685
|
+
this.attentionMap = new AttentionMapRenderer(this.state);
|
|
6686
|
+
this.scrollBucketMap = new ScrollBucketRenderer(this.heatmap);
|
|
6687
|
+
return this;
|
|
6688
|
+
};
|
|
6689
|
+
clearmapOverride = () => {
|
|
6690
|
+
this.originalClearmap();
|
|
6691
|
+
this.attentionMap?.clear();
|
|
6692
|
+
this.scrollBucketMap?.clear();
|
|
6693
|
+
};
|
|
5552
6694
|
}
|
|
5553
6695
|
|
|
5554
|
-
const
|
|
5555
|
-
|
|
5556
|
-
const
|
|
5557
|
-
const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
|
|
5558
|
-
const setVizRef = useHeatmapVizRectContext((s) => s.setVizRef);
|
|
5559
|
-
const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
|
|
5560
|
-
const setIsRenderViz = useHeatmapVizContext((s) => s.setIsRenderViz);
|
|
5561
|
-
const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
|
|
5562
|
-
const contentWidth = useHeatmapWidthByDevice();
|
|
6696
|
+
const perf$1 = createPerfTimer('Render');
|
|
6697
|
+
const useHeatmapIframeProcessor = () => {
|
|
6698
|
+
const shopId = useHeatmapConfigStore((s) => s.shopId);
|
|
5563
6699
|
const deviceType = useHeatmapSettingContext((s) => s.deviceType);
|
|
5564
|
-
const
|
|
5565
|
-
const
|
|
5566
|
-
|
|
6700
|
+
const viewport = useHeatmapViewportByDevice();
|
|
6701
|
+
const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
|
|
6702
|
+
const setIsDomLoaded = useHeatmapVizContext((s) => s.setIsDomLoaded);
|
|
6703
|
+
const helperRef = useRef(null);
|
|
6704
|
+
const pendingRef = useRef(null);
|
|
6705
|
+
const reset = useCallback(() => {
|
|
6706
|
+
pendingRef.current = null;
|
|
6707
|
+
}, []);
|
|
6708
|
+
const run = useCallback((iframe, t0, abort) => {
|
|
6709
|
+
if (viewport.width === 0 || viewport.height === 0) {
|
|
6710
|
+
pendingRef.current = { iframe, t0, abort };
|
|
5567
6711
|
return;
|
|
5568
|
-
|
|
6712
|
+
}
|
|
6713
|
+
startIframe({
|
|
6714
|
+
helperRef,
|
|
6715
|
+
iframe,
|
|
6716
|
+
shopId,
|
|
6717
|
+
deviceType,
|
|
6718
|
+
size: viewport,
|
|
6719
|
+
t0,
|
|
6720
|
+
onSuccess: (height) => {
|
|
6721
|
+
if (abort.signal.aborted)
|
|
6722
|
+
return;
|
|
6723
|
+
if (height)
|
|
6724
|
+
setIframeHeight(height);
|
|
6725
|
+
setIsDomLoaded(true);
|
|
6726
|
+
helperRef.current?.startHeightObserver();
|
|
6727
|
+
},
|
|
6728
|
+
});
|
|
6729
|
+
}, [deviceType]);
|
|
6730
|
+
// Retry when dims become available
|
|
6731
|
+
useEffect(() => {
|
|
6732
|
+
if (viewport.width === 0 || viewport.height === 0)
|
|
5569
6733
|
return;
|
|
5570
|
-
|
|
5571
|
-
if (!vizRef)
|
|
5572
|
-
setVizRef(visualizer);
|
|
5573
|
-
setIsRenderViz(false);
|
|
5574
|
-
const iframe = iframeRef.current;
|
|
5575
|
-
if (!iframe?.contentWindow)
|
|
6734
|
+
if (!pendingRef.current)
|
|
5576
6735
|
return;
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
6736
|
+
const { iframe, t0, abort } = pendingRef.current;
|
|
6737
|
+
pendingRef.current = null;
|
|
6738
|
+
if (abort.signal.aborted)
|
|
6739
|
+
return;
|
|
6740
|
+
startIframe({
|
|
6741
|
+
helperRef,
|
|
6742
|
+
iframe,
|
|
6743
|
+
shopId,
|
|
6744
|
+
deviceType,
|
|
6745
|
+
size: viewport,
|
|
6746
|
+
t0,
|
|
6747
|
+
onSuccess: (height) => {
|
|
6748
|
+
if (abort.signal.aborted)
|
|
6749
|
+
return;
|
|
6750
|
+
if (height)
|
|
6751
|
+
setIframeHeight(height);
|
|
6752
|
+
setIsDomLoaded(true);
|
|
6753
|
+
helperRef.current?.startHeightObserver();
|
|
6754
|
+
},
|
|
5582
6755
|
});
|
|
5583
|
-
|
|
5584
|
-
}, [wrapperHeight, contentWidth, deviceType]);
|
|
6756
|
+
}, [viewport]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
5585
6757
|
useEffect(() => {
|
|
5586
|
-
if (!data || data.length === 0)
|
|
5587
|
-
return;
|
|
5588
|
-
const decoded = decodeArrayClarity(data);
|
|
5589
|
-
renderHeatmap(decoded);
|
|
5590
6758
|
return () => {
|
|
5591
|
-
|
|
6759
|
+
helperRef.current?.stop();
|
|
6760
|
+
helperRef.current = null;
|
|
5592
6761
|
};
|
|
5593
|
-
}, [
|
|
5594
|
-
return {
|
|
5595
|
-
iframeRef,
|
|
5596
|
-
};
|
|
6762
|
+
}, []);
|
|
6763
|
+
return { run, reset };
|
|
5597
6764
|
};
|
|
5598
|
-
|
|
6765
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
6766
|
+
function startIframe({ helperRef, iframe, shopId, deviceType = EDeviceType.Desktop, size, t0, onSuccess, }) {
|
|
5599
6767
|
const docWidth = size.width ?? 0;
|
|
5600
6768
|
const docHeight = size.height ?? 0;
|
|
5601
6769
|
if (docHeight === 0)
|
|
5602
6770
|
return;
|
|
5603
|
-
stop();
|
|
5604
|
-
start
|
|
5605
|
-
|
|
6771
|
+
helperRef.current?.stop();
|
|
6772
|
+
const tHelper = perf$1.mark('IframeHelper.start');
|
|
6773
|
+
const helper = createIframeHelper();
|
|
6774
|
+
helperRef.current = helper;
|
|
6775
|
+
helper.start({
|
|
6776
|
+
deviceType,
|
|
5606
6777
|
targetWidth: docWidth,
|
|
5607
6778
|
targetHeight: docHeight,
|
|
5608
|
-
iframe
|
|
6779
|
+
iframe,
|
|
5609
6780
|
debug: true,
|
|
6781
|
+
shopId,
|
|
6782
|
+
heightObserverStartDelayMs: 1000,
|
|
5610
6783
|
onSuccess: (data) => {
|
|
6784
|
+
perf$1.measure('IframeHelper processing', tHelper);
|
|
6785
|
+
perf$1.measure('Total render', t0);
|
|
5611
6786
|
iframe.style.height = `${data.height}px`;
|
|
5612
6787
|
onSuccess(data.height);
|
|
5613
6788
|
},
|
|
5614
6789
|
});
|
|
5615
|
-
// fixer.recalculate();
|
|
5616
6790
|
}
|
|
5617
6791
|
|
|
6792
|
+
const perf = createPerfTimer('Render');
|
|
6793
|
+
const useHeatmapRenderDom = () => {
|
|
6794
|
+
const viewId = useViewIdContext();
|
|
6795
|
+
const data = useHeatmapDataContext((s) => s.data);
|
|
6796
|
+
const excludeClassNames = useHeatmapConfigStore((s) => s.excludeClassNames);
|
|
6797
|
+
const setVizRef = useHeatmapVizRectContext((s) => s.setVizRef);
|
|
6798
|
+
const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
|
|
6799
|
+
const setIsDomLoaded = useHeatmapVizContext((s) => s.setIsDomLoaded);
|
|
6800
|
+
const deviceType = useHeatmapSettingContext((s) => s.deviceType);
|
|
6801
|
+
const heatmapType = useHeatmapSettingContext((s) => s.heatmapType);
|
|
6802
|
+
const elementToShow = useHeatmapDataContext((s) => s.dataInfo?.elementToShow);
|
|
6803
|
+
const dataHash = useHeatmapDataContext((s) => s.dataHash);
|
|
6804
|
+
const iframeRef = useRef(null);
|
|
6805
|
+
const abortRef = useRef(null);
|
|
6806
|
+
const elementToShowRef = useRef(null);
|
|
6807
|
+
const dataHashRef = useRef(null);
|
|
6808
|
+
const heatmapTypeRef = useRef(heatmapType);
|
|
6809
|
+
elementToShowRef.current = elementToShow ?? null;
|
|
6810
|
+
dataHashRef.current = dataHash ?? null;
|
|
6811
|
+
heatmapTypeRef.current = heatmapType;
|
|
6812
|
+
const { run: runIframeSetup, reset: resetIframeSetup } = useHeatmapIframeProcessor();
|
|
6813
|
+
const renderHeatmap = useCallback(async (payloads) => {
|
|
6814
|
+
if (!payloads || payloads.length === 0)
|
|
6815
|
+
return;
|
|
6816
|
+
const iframe = iframeRef.current;
|
|
6817
|
+
const contentWindow = iframe?.contentWindow;
|
|
6818
|
+
if (!contentWindow)
|
|
6819
|
+
return;
|
|
6820
|
+
abortRef.current?.abort();
|
|
6821
|
+
const abort = new AbortController();
|
|
6822
|
+
abortRef.current = abort;
|
|
6823
|
+
resetIframeSetup();
|
|
6824
|
+
const t0 = perf.mark('RenderHeatmap start');
|
|
6825
|
+
const visualizer = vizRef ?? new GXVisualizer();
|
|
6826
|
+
if (!vizRef)
|
|
6827
|
+
setVizRef(visualizer);
|
|
6828
|
+
visualizer.configure({ excludeClassNames });
|
|
6829
|
+
setIsDomLoaded(false);
|
|
6830
|
+
// Phase 1: render DOM — does not depend on contentWidth/wrapperHeight
|
|
6831
|
+
const cacheKey = dataHashRef.current;
|
|
6832
|
+
const options = {
|
|
6833
|
+
decoded: payloads,
|
|
6834
|
+
target: contentWindow,
|
|
6835
|
+
portalCanvasId: viewId,
|
|
6836
|
+
logerror: (error) => {
|
|
6837
|
+
console.error('Error rendering HTML', error);
|
|
6838
|
+
},
|
|
6839
|
+
};
|
|
6840
|
+
await perf.wrap('RenderHtml', () => cacheKey ? visualizer.htmlCached(cacheKey, options) : visualizer.htmlRender(options));
|
|
6841
|
+
if (abort.signal.aborted)
|
|
6842
|
+
return;
|
|
6843
|
+
// Phase 2: iframe setup — deferred to useIframeSetup (handles dims dependency)
|
|
6844
|
+
runIframeSetup(iframe, t0, abort);
|
|
6845
|
+
}, [deviceType]);
|
|
6846
|
+
useEffect(() => {
|
|
6847
|
+
if (!data || data.length === 0)
|
|
6848
|
+
return;
|
|
6849
|
+
renderHeatmap(decodeArrayClarity(data));
|
|
6850
|
+
}, [data, renderHeatmap]);
|
|
6851
|
+
return { iframeRef };
|
|
6852
|
+
};
|
|
6853
|
+
|
|
5618
6854
|
const useReplayRender = () => {
|
|
5619
6855
|
const data = useHeatmapDataContext((s) => s.data);
|
|
5620
|
-
const
|
|
6856
|
+
const setIsDomLoaded = useHeatmapVizContext((s) => s.setIsDomLoaded);
|
|
5621
6857
|
const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
|
|
5622
6858
|
const visualizerRef = useRef(null);
|
|
5623
6859
|
const iframeRef = useRef(null);
|
|
@@ -5637,7 +6873,7 @@ const useReplayRender = () => {
|
|
|
5637
6873
|
version: envelope.version,
|
|
5638
6874
|
onresize: (height) => {
|
|
5639
6875
|
height && setIframeHeight(height);
|
|
5640
|
-
|
|
6876
|
+
setIsDomLoaded(true);
|
|
5641
6877
|
},
|
|
5642
6878
|
mobile,
|
|
5643
6879
|
vNext: true,
|
|
@@ -5740,10 +6976,12 @@ const useReplayRender = () => {
|
|
|
5740
6976
|
const useHeatmapRenderByMode = (mode) => {
|
|
5741
6977
|
const heatmapResult = useMemo(() => {
|
|
5742
6978
|
switch (mode) {
|
|
5743
|
-
case
|
|
5744
|
-
return
|
|
5745
|
-
case
|
|
6979
|
+
case EHeatmapMode.Heatmap:
|
|
6980
|
+
return useHeatmapRenderDom;
|
|
6981
|
+
case EHeatmapMode.Replay:
|
|
5746
6982
|
return useReplayRender;
|
|
6983
|
+
default:
|
|
6984
|
+
return useHeatmapRenderDom;
|
|
5747
6985
|
}
|
|
5748
6986
|
}, [mode]);
|
|
5749
6987
|
return heatmapResult();
|
|
@@ -5778,148 +7016,6 @@ const useContainerDimensions = (props) => {
|
|
|
5778
7016
|
return { containerWidth, containerHeight };
|
|
5779
7017
|
};
|
|
5780
7018
|
|
|
5781
|
-
const useContentDimensions = ({ iframeRef }) => {
|
|
5782
|
-
const contentWidth = useHeatmapWidthByDevice();
|
|
5783
|
-
useEffect(() => {
|
|
5784
|
-
if (!contentWidth)
|
|
5785
|
-
return;
|
|
5786
|
-
if (!iframeRef.current)
|
|
5787
|
-
return;
|
|
5788
|
-
// iframeRef.current.width = `${contentWidth}px`;
|
|
5789
|
-
}, [contentWidth, iframeRef]);
|
|
5790
|
-
return { contentWidth };
|
|
5791
|
-
};
|
|
5792
|
-
|
|
5793
|
-
const useObserveIframeHeight = (props) => {
|
|
5794
|
-
const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
|
|
5795
|
-
const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
|
|
5796
|
-
const isRenderViz = useHeatmapVizContext((s) => s.isRenderViz);
|
|
5797
|
-
const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
|
|
5798
|
-
const { iframeRef } = props;
|
|
5799
|
-
const resizeObserverRef = useRef(null);
|
|
5800
|
-
const mutationObserverRef = useRef(null);
|
|
5801
|
-
const debounceTimerRef = useRef(null);
|
|
5802
|
-
const lastHeightRef = useRef(0);
|
|
5803
|
-
const animationFrameRef = useRef(null);
|
|
5804
|
-
const updateIframeHeight = useCallback(() => {
|
|
5805
|
-
const iframe = iframeRef.current;
|
|
5806
|
-
if (!iframe)
|
|
5807
|
-
return;
|
|
5808
|
-
try {
|
|
5809
|
-
const iframeDocument = iframe.contentDocument;
|
|
5810
|
-
const iframeBody = iframeDocument?.body;
|
|
5811
|
-
const iframeDocumentElement = iframeDocument?.documentElement;
|
|
5812
|
-
if (!iframeBody || !iframeDocumentElement)
|
|
5813
|
-
return;
|
|
5814
|
-
// iframe.style.height = 'auto'; // TODO: check if this is needed
|
|
5815
|
-
requestAnimationFrame(() => {
|
|
5816
|
-
iframe.style.height = `${wrapperHeight}px`;
|
|
5817
|
-
const bodyHeight = Math.max(iframeBody.scrollHeight, iframeBody.offsetHeight, iframeBody.clientHeight);
|
|
5818
|
-
const documentHeight = Math.max(iframeDocumentElement.scrollHeight, iframeDocumentElement.offsetHeight, iframeDocumentElement.clientHeight);
|
|
5819
|
-
const actualHeight = Math.max(bodyHeight, documentHeight);
|
|
5820
|
-
if (actualHeight > 0) {
|
|
5821
|
-
lastHeightRef.current = actualHeight;
|
|
5822
|
-
// iframe.height = `${actualHeight}px`;
|
|
5823
|
-
iframe.style.height = `${actualHeight}px`;
|
|
5824
|
-
setIframeHeight(actualHeight);
|
|
5825
|
-
}
|
|
5826
|
-
});
|
|
5827
|
-
}
|
|
5828
|
-
catch (error) {
|
|
5829
|
-
console.warn('Cannot measure iframe content:', error);
|
|
5830
|
-
}
|
|
5831
|
-
}, [iframeRef, wrapperHeight]);
|
|
5832
|
-
useCallback(() => {
|
|
5833
|
-
// Cancel pending updates
|
|
5834
|
-
if (debounceTimerRef.current) {
|
|
5835
|
-
clearTimeout(debounceTimerRef.current);
|
|
5836
|
-
}
|
|
5837
|
-
if (animationFrameRef.current) {
|
|
5838
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
5839
|
-
}
|
|
5840
|
-
debounceTimerRef.current = setTimeout(() => {
|
|
5841
|
-
animationFrameRef.current = requestAnimationFrame(() => {
|
|
5842
|
-
updateIframeHeight();
|
|
5843
|
-
});
|
|
5844
|
-
}, 50);
|
|
5845
|
-
}, [updateIframeHeight]);
|
|
5846
|
-
// Immediate update không debounce (cho ResizeObserver)
|
|
5847
|
-
const immediateUpdate = useCallback(() => {
|
|
5848
|
-
if (animationFrameRef.current) {
|
|
5849
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
5850
|
-
}
|
|
5851
|
-
animationFrameRef.current = requestAnimationFrame(() => {
|
|
5852
|
-
updateIframeHeight();
|
|
5853
|
-
});
|
|
5854
|
-
}, [updateIframeHeight]);
|
|
5855
|
-
useEffect(() => {
|
|
5856
|
-
const iframe = iframeRef.current;
|
|
5857
|
-
if (!iframe || !iframeHeight || !isRenderViz)
|
|
5858
|
-
return;
|
|
5859
|
-
const setupObservers = () => {
|
|
5860
|
-
try {
|
|
5861
|
-
const iframeDocument = iframe.contentDocument;
|
|
5862
|
-
const iframeBody = iframeDocument?.body;
|
|
5863
|
-
if (!iframeBody)
|
|
5864
|
-
return;
|
|
5865
|
-
// Cleanup existing observers
|
|
5866
|
-
if (resizeObserverRef.current) {
|
|
5867
|
-
resizeObserverRef.current.disconnect();
|
|
5868
|
-
}
|
|
5869
|
-
if (mutationObserverRef.current) {
|
|
5870
|
-
mutationObserverRef.current.disconnect();
|
|
5871
|
-
}
|
|
5872
|
-
if (typeof window.ResizeObserver !== 'undefined') {
|
|
5873
|
-
resizeObserverRef.current = new ResizeObserver(immediateUpdate);
|
|
5874
|
-
resizeObserverRef.current.observe(iframeBody);
|
|
5875
|
-
const iframeDocumentElement = iframeDocument?.documentElement;
|
|
5876
|
-
if (iframeDocumentElement) {
|
|
5877
|
-
resizeObserverRef.current.observe(iframeDocumentElement);
|
|
5878
|
-
}
|
|
5879
|
-
}
|
|
5880
|
-
if (typeof window.MutationObserver !== 'undefined') {
|
|
5881
|
-
mutationObserverRef.current = new MutationObserver(immediateUpdate);
|
|
5882
|
-
mutationObserverRef.current.observe(iframeBody, {
|
|
5883
|
-
childList: true,
|
|
5884
|
-
subtree: true,
|
|
5885
|
-
attributes: true,
|
|
5886
|
-
attributeFilter: ['style', 'class'],
|
|
5887
|
-
characterData: false,
|
|
5888
|
-
});
|
|
5889
|
-
}
|
|
5890
|
-
updateIframeHeight();
|
|
5891
|
-
}
|
|
5892
|
-
catch (error) {
|
|
5893
|
-
console.warn('Cannot access iframe content:', error);
|
|
5894
|
-
}
|
|
5895
|
-
};
|
|
5896
|
-
if (iframe.contentDocument?.readyState === 'complete') {
|
|
5897
|
-
setupObservers();
|
|
5898
|
-
}
|
|
5899
|
-
else {
|
|
5900
|
-
iframe.addEventListener('load', setupObservers, { once: true });
|
|
5901
|
-
}
|
|
5902
|
-
return () => {
|
|
5903
|
-
// Cleanup observers
|
|
5904
|
-
if (resizeObserverRef.current) {
|
|
5905
|
-
resizeObserverRef.current.disconnect();
|
|
5906
|
-
}
|
|
5907
|
-
if (mutationObserverRef.current) {
|
|
5908
|
-
mutationObserverRef.current.disconnect();
|
|
5909
|
-
}
|
|
5910
|
-
// Cleanup timers
|
|
5911
|
-
if (debounceTimerRef.current) {
|
|
5912
|
-
clearTimeout(debounceTimerRef.current);
|
|
5913
|
-
}
|
|
5914
|
-
if (animationFrameRef.current) {
|
|
5915
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
5916
|
-
}
|
|
5917
|
-
iframe.removeEventListener('load', setupObservers);
|
|
5918
|
-
};
|
|
5919
|
-
}, [iframeRef, iframeHeight, isRenderViz]);
|
|
5920
|
-
return {};
|
|
5921
|
-
};
|
|
5922
|
-
|
|
5923
7019
|
const useScaleCalculation = (props) => {
|
|
5924
7020
|
const widthScale = useHeatmapVizContext((s) => s.widthScale);
|
|
5925
7021
|
const zoomRatio = useHeatmapVizContext((s) => s.zoomRatio);
|
|
@@ -5929,9 +7025,10 @@ const useScaleCalculation = (props) => {
|
|
|
5929
7025
|
const setScale = useHeatmapVizContext((s) => s.setScale);
|
|
5930
7026
|
const setIsScaledToFit = useHeatmapVizContext((s) => s.setIsScaledToFit);
|
|
5931
7027
|
const setMinZoomRatio = useHeatmapVizContext((s) => s.setMinZoomRatio);
|
|
5932
|
-
const
|
|
7028
|
+
const viewport = useHeatmapViewportByDevice();
|
|
7029
|
+
const { containerWidth, containerHeight, iframeHeight } = props;
|
|
5933
7030
|
const calculateScaleResult = useCallback(() => {
|
|
5934
|
-
if (containerWidth > 0 &&
|
|
7031
|
+
if (containerWidth > 0 && viewport.width > 0 && containerHeight > 0 && iframeHeight > 0) {
|
|
5935
7032
|
// 1. Calculate available dimensions
|
|
5936
7033
|
const availableWidth = containerWidth - HEATMAP_CONFIG['padding'] * 2;
|
|
5937
7034
|
const toolbarHeight = HEATMAP_CONFIG['heightToolbar'] || 0;
|
|
@@ -5939,12 +7036,12 @@ const useScaleCalculation = (props) => {
|
|
|
5939
7036
|
const availableHeight = containerHeight - toolbarHeight - paddingTotal;
|
|
5940
7037
|
// 2. Calculate widthScale (base scale to fit content width into container width)
|
|
5941
7038
|
// This represents 100% zoom (fit to width)
|
|
5942
|
-
const widthScale = Math.min(availableWidth /
|
|
7039
|
+
const widthScale = Math.min(availableWidth / viewport.width, 1);
|
|
5943
7040
|
// 3. Calculate minZoomRatio (zoom ratio to fit height)
|
|
5944
7041
|
// At minZoomRatio, the content should fit entirely within the container height
|
|
5945
|
-
// Formula:
|
|
5946
|
-
// => minZoomRatio = (availableHeight / (
|
|
5947
|
-
const calculatedMinZoomRatio = (availableHeight / (
|
|
7042
|
+
// Formula: iframeHeight * widthScale * (minZoomRatio / 100) = availableHeight
|
|
7043
|
+
// => minZoomRatio = (availableHeight / (iframeHeight * widthScale)) * 100
|
|
7044
|
+
const calculatedMinZoomRatio = (availableHeight / (iframeHeight * widthScale)) * 100;
|
|
5948
7045
|
// Limit minZoomRatio: cannot exceed MAX_ZOOM_RATIO (100%)
|
|
5949
7046
|
// and should have a reasonable minimum (e.g., 1%)
|
|
5950
7047
|
const finalMinZoomRatio = Math.max(1, Math.min(calculatedMinZoomRatio, maxZoomRatio));
|
|
@@ -5963,7 +7060,7 @@ const useScaleCalculation = (props) => {
|
|
|
5963
7060
|
setIsScaledToFit(isCurrentlyFitted);
|
|
5964
7061
|
setMinZoomRatio(finalMinZoomRatio);
|
|
5965
7062
|
}
|
|
5966
|
-
}, [containerWidth, containerHeight,
|
|
7063
|
+
}, [containerWidth, containerHeight, viewport.width, iframeHeight, zoomRatio, maxZoomRatio]);
|
|
5967
7064
|
useEffect(() => {
|
|
5968
7065
|
calculateScaleResult();
|
|
5969
7066
|
}, [calculateScaleResult]);
|
|
@@ -5996,20 +7093,15 @@ const useHeatmapScale = (props) => {
|
|
|
5996
7093
|
// 1. Observe container dimensions
|
|
5997
7094
|
const { containerWidth, containerHeight } = useContainerDimensions({ wrapperRef });
|
|
5998
7095
|
// 2. Get content dimensions from config
|
|
5999
|
-
const
|
|
7096
|
+
const viewport = useHeatmapViewportByDevice();
|
|
6000
7097
|
// 3. Observe iframe height (now reacts to width changes)
|
|
6001
|
-
useObserveIframeHeight({ iframeRef });
|
|
7098
|
+
// useObserveIframeHeight({ iframeRef });
|
|
6002
7099
|
// 4. Calculate scale
|
|
6003
|
-
const { widthScale } = useScaleCalculation({
|
|
6004
|
-
containerWidth,
|
|
6005
|
-
containerHeight,
|
|
6006
|
-
contentWidth,
|
|
6007
|
-
contentHeight: iframeHeight,
|
|
6008
|
-
});
|
|
7100
|
+
const { widthScale } = useScaleCalculation({ containerWidth, containerHeight, iframeHeight });
|
|
6009
7101
|
// 5. Setup scroll sync
|
|
6010
7102
|
const { handleScroll } = useScrollSync({ widthScale, iframeRef });
|
|
6011
7103
|
const scaledHeight = iframeHeight * widthScale;
|
|
6012
|
-
const scaledWidth =
|
|
7104
|
+
const scaledWidth = viewport.width * widthScale;
|
|
6013
7105
|
return {
|
|
6014
7106
|
scaledWidth,
|
|
6015
7107
|
scaledHeight,
|
|
@@ -6215,10 +7307,10 @@ const useScrollmapZones = (options) => {
|
|
|
6215
7307
|
const newZones = createZones(scrollmap);
|
|
6216
7308
|
setZones(newZones);
|
|
6217
7309
|
setIsReady(true);
|
|
6218
|
-
logger$
|
|
7310
|
+
logger$3.log(`[useScrollmap] Created ${newZones.length} zones in ${mode} mode`);
|
|
6219
7311
|
}
|
|
6220
7312
|
catch (error) {
|
|
6221
|
-
logger$
|
|
7313
|
+
logger$3.error('[useScrollmap] Error:', error);
|
|
6222
7314
|
setIsReady(false);
|
|
6223
7315
|
}
|
|
6224
7316
|
}, [enabled, scrollmap, mode, createZones]);
|
|
@@ -7048,11 +8140,11 @@ const AutoScrollHandler = ({ visualRef }) => {
|
|
|
7048
8140
|
};
|
|
7049
8141
|
|
|
7050
8142
|
const PortalAreaRenderer = ({ iframeRef, visualRef, shadowRoot, onAreaCreated, onAreaClick, }) => {
|
|
7051
|
-
const
|
|
8143
|
+
const isDomLoaded = useHeatmapVizContext((s) => s.isDomLoaded);
|
|
7052
8144
|
const iframeDocument = iframeRef.current?.contentDocument || undefined;
|
|
7053
8145
|
const { shadowContainer, isReady } = useAreaRendererContainer(iframeDocument, shadowRoot);
|
|
7054
|
-
useAreaRectSync({ iframeDocument, shadowRoot, enabled: isReady &&
|
|
7055
|
-
useAreaPositionsUpdater({ iframeRef, visualRef, enabled: isReady &&
|
|
8146
|
+
useAreaRectSync({ iframeDocument, shadowRoot, enabled: isReady && isDomLoaded });
|
|
8147
|
+
useAreaPositionsUpdater({ iframeRef, visualRef, enabled: isReady && isDomLoaded });
|
|
7056
8148
|
if (!shadowContainer || !isReady)
|
|
7057
8149
|
return null;
|
|
7058
8150
|
return (jsxs(Fragment$1, { children: [jsx(AutoScrollHandler, { visualRef: visualRef }), jsx(AreasPortal, { shadowContainer: shadowContainer, onAreaClick: onAreaClick }), jsx(AreaEditHighlightPortal, { shadowContainer: shadowContainer, iframeRef: iframeRef, customShadowRoot: shadowRoot, onAreaCreated: onAreaCreated })] }));
|
|
@@ -7061,7 +8153,7 @@ const PortalAreaRenderer = ({ iframeRef, visualRef, shadowRoot, onAreaCreated, o
|
|
|
7061
8153
|
const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, enableOverlapResolution = true, onAreaClick, }) => {
|
|
7062
8154
|
const clickAreas = useHeatmapDataContext((s) => s.clickAreas);
|
|
7063
8155
|
const resetView = useHeatmapAreaClickContext((s) => s.resetView);
|
|
7064
|
-
const
|
|
8156
|
+
const isDomLoaded = useHeatmapVizContext((s) => s.isDomLoaded);
|
|
7065
8157
|
useAreaTopAutoDetect({ autoCreateTopN, shadowRoot, disabled: !!clickAreas?.length });
|
|
7066
8158
|
useAreaFilterVisible({ iframeRef, enableOverlapResolution });
|
|
7067
8159
|
useAreaHydration({ shadowRoot });
|
|
@@ -7070,7 +8162,7 @@ const VizAreaClick = ({ iframeRef, visualRef, shadowRoot, autoCreateTopN = 10, e
|
|
|
7070
8162
|
resetView();
|
|
7071
8163
|
};
|
|
7072
8164
|
}, []);
|
|
7073
|
-
if (!iframeRef.current || !
|
|
8165
|
+
if (!iframeRef.current || !isDomLoaded)
|
|
7074
8166
|
return null;
|
|
7075
8167
|
return (jsx(Fragment, { children: jsx(PortalAreaRenderer, { iframeRef: iframeRef, visualRef: visualRef, shadowRoot: shadowRoot, onAreaClick: onAreaClick }) }));
|
|
7076
8168
|
};
|
|
@@ -7201,7 +8293,7 @@ const useAnchorPosition = (calloutRef, props) => {
|
|
|
7201
8293
|
const ElementMissing = ({ show = true, visualRef }) => {
|
|
7202
8294
|
const widthScale = useHeatmapVizContext((s) => s.widthScale);
|
|
7203
8295
|
const missingElementRef = useRef(null);
|
|
7204
|
-
const
|
|
8296
|
+
const viewport = useHeatmapViewportByDevice();
|
|
7205
8297
|
const [scrollPosition, setScrollPosition] = useState({ scrollTop: 0, scrollLeft: 0 });
|
|
7206
8298
|
useEffect(() => {
|
|
7207
8299
|
const container = visualRef.current;
|
|
@@ -7231,7 +8323,7 @@ const ElementMissing = ({ show = true, visualRef }) => {
|
|
|
7231
8323
|
const containerHeight = containerRect?.height ?? 0;
|
|
7232
8324
|
const topPosition = scrollTop + (containerHeight + elementHeightCenter) / 2;
|
|
7233
8325
|
const topPositionScaled = topPosition / widthScale;
|
|
7234
|
-
const leftPosition =
|
|
8326
|
+
const leftPosition = viewport.width / 2;
|
|
7235
8327
|
return (jsxs(Fragment, { children: [jsx("div", { className: "missingElement-backdrop", style: {
|
|
7236
8328
|
position: 'absolute',
|
|
7237
8329
|
top: 0,
|
|
@@ -7356,7 +8448,7 @@ const ElementOverlayComponent = (props) => {
|
|
|
7356
8448
|
const { type, element, onClick, elementId, hideOutline } = props;
|
|
7357
8449
|
const widthScale = useHeatmapVizContext((s) => s.widthScale);
|
|
7358
8450
|
const viewportHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
|
|
7359
|
-
const
|
|
8451
|
+
const viewport = useHeatmapViewportByDevice();
|
|
7360
8452
|
const overlayStyle = useMemo(() => {
|
|
7361
8453
|
const isInvalid = !element || (element.width === 0 && element.height === 0);
|
|
7362
8454
|
if (isInvalid)
|
|
@@ -7373,7 +8465,7 @@ const ElementOverlayComponent = (props) => {
|
|
|
7373
8465
|
const isHovered = type === 'hovered';
|
|
7374
8466
|
const badgeWidthScale = isHovered ? 1 : widthScale;
|
|
7375
8467
|
const showCallout = !!element?.mousePosition && !isHovered;
|
|
7376
|
-
return (jsxs(Fragment$1, { children: [jsx("div", { onClick: onClick, className: `heatmapElement heatmapElement--${type} ${hideOutline ? 'heatmapElement--hide-outline' : ''}`, id: elementId, style: overlayStyle, children: showCallout && jsx(ElementCalloutOverlay, { ...props }) }), jsx(BackdropCanvas, { activeElement: overlayStyle, viewportWidth:
|
|
8468
|
+
return (jsxs(Fragment$1, { children: [jsx("div", { onClick: onClick, className: `heatmapElement heatmapElement--${type} ${hideOutline ? 'heatmapElement--hide-outline' : ''}`, id: elementId, style: overlayStyle, children: showCallout && jsx(ElementCalloutOverlay, { ...props }) }), jsx(BackdropCanvas, { activeElement: overlayStyle, viewportWidth: viewport.width, viewportHeight: viewportHeight, show: !isHovered }), jsx(RankBadge, { hash: element.hash, show: isHovered, index: element.rank, elementRect: element, widthScale: badgeWidthScale, clickOnElement: onClick })] }));
|
|
7377
8469
|
};
|
|
7378
8470
|
ElementOverlayComponent.displayName = 'ElementOverlay';
|
|
7379
8471
|
const ElementOverlay = memo(ElementOverlayComponent);
|
|
@@ -7438,7 +8530,7 @@ const HeatmapElements = (props) => {
|
|
|
7438
8530
|
};
|
|
7439
8531
|
|
|
7440
8532
|
const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
|
|
7441
|
-
const
|
|
8533
|
+
const viewport = useHeatmapViewportByDevice();
|
|
7442
8534
|
const dataInfo = useHeatmapDataContext((s) => s.dataInfo);
|
|
7443
8535
|
const vizRef = useHeatmapVizRectContext((s) => s.vizRef);
|
|
7444
8536
|
const visualizer = {
|
|
@@ -7452,7 +8544,7 @@ const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
|
|
|
7452
8544
|
if (!iframeRef.current)
|
|
7453
8545
|
return null;
|
|
7454
8546
|
return (jsx(HeatmapElements, { visualizer: visualizer, visualRef: visualRef, iframeRef: iframeRef, wrapperRef: wrapperRef, heatmapInfo: dataInfo, isVisible: true, positionMode: DEFAULT_POSITION_MODE, isHideTopRank: true, iframeDimensions: {
|
|
7455
|
-
width:
|
|
8547
|
+
width: viewport.width,
|
|
7456
8548
|
position: 'absolute',
|
|
7457
8549
|
top: 0,
|
|
7458
8550
|
left: 0,
|
|
@@ -7476,45 +8568,6 @@ const VizClickmap = ({ iframeRef, visualRef, wrapperRef }) => {
|
|
|
7476
8568
|
}
|
|
7477
8569
|
};
|
|
7478
8570
|
|
|
7479
|
-
const IS_ENABLE_MINIMAP = false;
|
|
7480
|
-
const Minimap = ({ zones, maxUsers }) => {
|
|
7481
|
-
const scrollType = useHeatmapSettingContext((s) => s.scrollType);
|
|
7482
|
-
const showMinimap = useHeatmapScrollContext((s) => s.showMinimap);
|
|
7483
|
-
const isScrollType = [EScrollType.Attention].includes(scrollType ?? EScrollType.Depth);
|
|
7484
|
-
if (!showMinimap || !isScrollType || !IS_ENABLE_MINIMAP)
|
|
7485
|
-
return null;
|
|
7486
|
-
return (jsx("div", { style: {
|
|
7487
|
-
position: 'fixed',
|
|
7488
|
-
left: '20px',
|
|
7489
|
-
top: '50%',
|
|
7490
|
-
transform: 'translateY(-50%)',
|
|
7491
|
-
width: '60px',
|
|
7492
|
-
height: '400px',
|
|
7493
|
-
backgroundColor: 'white',
|
|
7494
|
-
borderRadius: '8px',
|
|
7495
|
-
boxShadow: '0 4px 16px rgba(0,0,0,0.15)',
|
|
7496
|
-
zIndex: 10002,
|
|
7497
|
-
padding: '8px',
|
|
7498
|
-
boxSizing: 'border-box',
|
|
7499
|
-
}, children: jsx("div", { style: {
|
|
7500
|
-
width: '100%',
|
|
7501
|
-
height: '100%',
|
|
7502
|
-
borderRadius: '4px',
|
|
7503
|
-
overflow: 'hidden',
|
|
7504
|
-
display: 'flex',
|
|
7505
|
-
flexDirection: 'column',
|
|
7506
|
-
}, children: zones.map((zone) => {
|
|
7507
|
-
const normalized = maxUsers > 0 ? zone.percUsers / maxUsers : 0;
|
|
7508
|
-
const color = getScrollGradientColor(normalized);
|
|
7509
|
-
return (jsx("div", { title: `${zone.label}: ${zone.percUsers.toFixed(2)}%`, style: {
|
|
7510
|
-
width: '100%',
|
|
7511
|
-
flex: `${zone.endY - zone.startY}`,
|
|
7512
|
-
backgroundColor: color,
|
|
7513
|
-
borderBottom: '1px solid rgba(255,255,255,0.2)',
|
|
7514
|
-
} }, zone.id));
|
|
7515
|
-
}) }) }));
|
|
7516
|
-
};
|
|
7517
|
-
|
|
7518
8571
|
const ScrollZoneTooltip = ({ zone, position, currentScrollPercent, scrollmap, }) => {
|
|
7519
8572
|
const CompScrollZoneTooltip = useHeatmapControlStore((state) => state.controls.ScrollZoneTooltip);
|
|
7520
8573
|
const tooltipRef = useRef(null);
|
|
@@ -7540,17 +8593,9 @@ const ScrollZoneTooltip = ({ zone, position, currentScrollPercent, scrollmap, })
|
|
|
7540
8593
|
|
|
7541
8594
|
const HoverZones = ({ iframeRef, wrapperRef, position, currentScrollPercent }) => {
|
|
7542
8595
|
const scrollmap = useHeatmapDataContext((s) => s.scrollmap);
|
|
7543
|
-
// const hoveredZone = useHeatmapVizScrollStore((state) => state.hoveredZone);
|
|
7544
|
-
// const setHoveredZone = useHeatmapVizScrollStore((state) => state.setHoveredZone);
|
|
7545
|
-
const { zones, isReady, maxUsers } = useScrollmapZones({
|
|
7546
|
-
iframeRef,
|
|
7547
|
-
wrapperRef,
|
|
7548
|
-
});
|
|
7549
|
-
if (!isReady || !zones.length)
|
|
7550
|
-
return null;
|
|
7551
8596
|
if (!position)
|
|
7552
8597
|
return null;
|
|
7553
|
-
return (
|
|
8598
|
+
return (jsx(Fragment, { children: jsx(ScrollZoneTooltip, { position: position, currentScrollPercent: currentScrollPercent, scrollmap: scrollmap || [] }) }));
|
|
7554
8599
|
};
|
|
7555
8600
|
|
|
7556
8601
|
const ScrollOverlay = ({ wrapperRef, iframeRef }) => {
|
|
@@ -7716,7 +8761,7 @@ const VizLoadingCanvas = () => {
|
|
|
7716
8761
|
const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHeight, onScroll, }) => {
|
|
7717
8762
|
const isLoadingCanvas = useHeatmapSettingContext((state) => state.isLoadingCanvas);
|
|
7718
8763
|
const widthScale = useHeatmapVizContext((s) => s.widthScale);
|
|
7719
|
-
const
|
|
8764
|
+
const viewport = useHeatmapViewportByDevice();
|
|
7720
8765
|
const contentHeight = calcContentHeight();
|
|
7721
8766
|
return (jsx("div", { ref: visualRef, className: "gx-hm-visual Polaris-Scrollable Polaris-Scrollable--vertical Polaris-Scrollable--scrollbarWidthThin Polaris-Scrollable--scrollbarGutterStable", onScroll: onScroll, style: {
|
|
7722
8767
|
overflowX: 'hidden',
|
|
@@ -7738,7 +8783,7 @@ const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHe
|
|
|
7738
8783
|
paddingBottom: HEATMAP_STYLE['viz']['paddingBottom'],
|
|
7739
8784
|
background: HEATMAP_STYLE['viz']['background'],
|
|
7740
8785
|
}, children: jsx("div", { className: "gx-hm-wrapper", ref: wrapperRef, style: {
|
|
7741
|
-
width:
|
|
8786
|
+
width: viewport.width,
|
|
7742
8787
|
height: iframeHeight,
|
|
7743
8788
|
transform: `scale(${widthScale})`,
|
|
7744
8789
|
transformOrigin: 'top center',
|
|
@@ -7751,14 +8796,14 @@ const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHe
|
|
|
7751
8796
|
}
|
|
7752
8797
|
};
|
|
7753
8798
|
|
|
7754
|
-
const VizDomRenderer = ({ mode
|
|
8799
|
+
const VizDomRenderer = ({ mode }) => {
|
|
7755
8800
|
const viewId = useViewIdContext();
|
|
7756
|
-
const
|
|
8801
|
+
const viewport = useHeatmapViewportByDevice();
|
|
7757
8802
|
const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
|
|
7758
|
-
const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
|
|
8803
|
+
// const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
|
|
7759
8804
|
const wrapperRef = useRef(null);
|
|
7760
8805
|
const visualRef = useRef(null);
|
|
7761
|
-
const { iframeRef } =
|
|
8806
|
+
const { iframeRef } = useHeatmapRenderDom();
|
|
7762
8807
|
const { scaledHeight, handleScroll } = useHeatmapScale({ wrapperRef, iframeRef, visualRef });
|
|
7763
8808
|
useHeatmapCanvas();
|
|
7764
8809
|
useRenderCount('VizDomRenderer');
|
|
@@ -7766,7 +8811,7 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
|
|
|
7766
8811
|
const scrollTop = e.currentTarget.scrollTop;
|
|
7767
8812
|
handleScroll(scrollTop);
|
|
7768
8813
|
};
|
|
7769
|
-
return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [jsx(VizClickmap, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef }), jsx("iframe", { ...HEATMAP_IFRAME, id: `clarity-iframe-${viewId}`, ref: iframeRef, width:
|
|
8814
|
+
return (jsxs(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, onScroll: onScroll, iframeHeight: iframeHeight, children: [jsx(VizClickmap, { iframeRef: iframeRef, visualRef: visualRef, wrapperRef: wrapperRef }), jsx("iframe", { ...HEATMAP_IFRAME, id: `clarity-iframe-${viewId}`, ref: iframeRef, width: viewport.width, height: viewport.height }), jsx(VizLoadingCanvas, {}), jsx(VizScrollMap, { visualRef: visualRef, iframeRef: iframeRef, wrapperRef: wrapperRef })] }));
|
|
7770
8815
|
};
|
|
7771
8816
|
|
|
7772
8817
|
const VizLoading = () => {
|
|
@@ -7783,8 +8828,9 @@ const VizLoading = () => {
|
|
|
7783
8828
|
const VizDomHeatmap = () => {
|
|
7784
8829
|
const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
|
|
7785
8830
|
const setIframeHeight = useHeatmapVizRectContext((s) => s.setIframeHeight);
|
|
8831
|
+
const setWrapperHeight = useHeatmapVizRectContext((s) => s.setWrapperHeight);
|
|
7786
8832
|
const setVizRef = useHeatmapVizRectContext((s) => s.setVizRef);
|
|
7787
|
-
const
|
|
8833
|
+
const setIsDomLoaded = useHeatmapVizContext((s) => s.setIsDomLoaded);
|
|
7788
8834
|
const setSelectedElement = useHeatmapClickContext((s) => s.setSelectedElement);
|
|
7789
8835
|
const setHoveredElement = useHeatmapHoverContext((s) => s.setHoveredElement);
|
|
7790
8836
|
// const setSelectedArea = useHeatmapAreaClickContext((s) => s.setSelectedArea);
|
|
@@ -7794,7 +8840,8 @@ const VizDomHeatmap = () => {
|
|
|
7794
8840
|
const cleanUp = () => {
|
|
7795
8841
|
setVizRef(null);
|
|
7796
8842
|
setIframeHeight(0);
|
|
7797
|
-
|
|
8843
|
+
setWrapperHeight(0);
|
|
8844
|
+
setIsDomLoaded(false);
|
|
7798
8845
|
setSelectedElement(null);
|
|
7799
8846
|
setHoveredElement(null);
|
|
7800
8847
|
// setSelectedArea(null);
|
|
@@ -7803,13 +8850,13 @@ const VizDomHeatmap = () => {
|
|
|
7803
8850
|
};
|
|
7804
8851
|
useEffect(() => {
|
|
7805
8852
|
return cleanUp;
|
|
7806
|
-
}, []);
|
|
7807
|
-
return (jsxs(VizContainer, { isActive: true, children: [jsx(VizDomRenderer, {}), iframeHeight === 0 && jsx(VizLoading, {})] }));
|
|
8853
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
8854
|
+
return (jsxs(VizContainer, { isActive: true, children: [jsx(VizDomRenderer, { mode: EHeatmapMode.Heatmap }), iframeHeight === 0 && jsx(VizLoading, {})] }));
|
|
7808
8855
|
};
|
|
7809
8856
|
VizDomHeatmap.displayName = 'VizDomHeatmap';
|
|
7810
8857
|
|
|
7811
8858
|
const VizLiveRenderer = () => {
|
|
7812
|
-
const
|
|
8859
|
+
const viewport = useHeatmapViewportByDevice();
|
|
7813
8860
|
const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
|
|
7814
8861
|
const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
|
|
7815
8862
|
const visualRef = useRef(null);
|
|
@@ -7820,13 +8867,13 @@ const VizLiveRenderer = () => {
|
|
|
7820
8867
|
const scrollTop = e.currentTarget.scrollTop;
|
|
7821
8868
|
handleScroll(scrollTop);
|
|
7822
8869
|
};
|
|
7823
|
-
return (jsx(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, iframeHeight: iframeHeight, onScroll: onScroll, children: jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width:
|
|
8870
|
+
return (jsx(WrapperVisual, { visualRef: visualRef, wrapperRef: wrapperRef, scaledHeight: scaledHeight, iframeHeight: iframeHeight, onScroll: onScroll, children: jsx("iframe", { ref: iframeRef, ...HEATMAP_IFRAME, width: viewport.width, height: wrapperHeight, scrolling: "no" }) }));
|
|
7824
8871
|
};
|
|
7825
8872
|
|
|
7826
8873
|
const VizLiveHeatmap = () => {
|
|
7827
8874
|
const iframeHeight = useHeatmapVizRectContext((s) => s.iframeHeight);
|
|
7828
8875
|
const wrapperHeight = useHeatmapVizRectContext((s) => s.wrapperHeight);
|
|
7829
|
-
|
|
8876
|
+
useHeatmapLiveContext((s) => s.reset);
|
|
7830
8877
|
const CompVizLoading = useHeatmapControlStore((state) => state.controls.VizLoading);
|
|
7831
8878
|
// TODO: Remove this after testing
|
|
7832
8879
|
useEffect(() => {
|
|
@@ -7877,11 +8924,11 @@ const ContentTopBar = () => {
|
|
|
7877
8924
|
}, children: CompTopBar && jsx(CompTopBar, {}) }));
|
|
7878
8925
|
};
|
|
7879
8926
|
|
|
7880
|
-
const HeatmapLayout = ({ data, clickmap, clickAreas, scrollmap, attentionMap, controls, dataInfo, isLoading, isLoadingCanvas, }) => {
|
|
8927
|
+
const HeatmapLayout = ({ shopId, data, clickmap, clickAreas, scrollmap, attentionMap, controls, dataInfo, isLoading, isLoadingCanvas, excludeClassNames, }) => {
|
|
7881
8928
|
useRegisterControl(controls);
|
|
7882
8929
|
useRegisterData(data, dataInfo);
|
|
7883
8930
|
useRegisterHeatmap({ clickmap, scrollmap, clickAreas, attentionMap });
|
|
7884
|
-
useRegisterConfig({ isLoading, isLoadingCanvas });
|
|
8931
|
+
useRegisterConfig({ isLoading, isLoadingCanvas, shopId, excludeClassNames });
|
|
7885
8932
|
// performanceLogger.configure({
|
|
7886
8933
|
// enabled: true,
|
|
7887
8934
|
// logToConsole: false,
|
|
@@ -7911,4 +8958,4 @@ const HeatmapLayout = ({ data, clickmap, clickAreas, scrollmap, attentionMap, co
|
|
|
7911
8958
|
}
|
|
7912
8959
|
};
|
|
7913
8960
|
|
|
7914
|
-
export { BACKDROP_CONFIG, DEFAULT_SIDEBAR_WIDTH, DEFAULT_VIEW_ID, DEFAULT_ZOOM_RATIO, EClickMode, EClickRankType, EClickType, EDeviceType, EHeatmapMode, EHeatmapType, ELM_CALLOUT_CONFIG, EScrollType, GraphView, HEATMAP_CONFIG, HEATMAP_IFRAME, HEATMAP_STYLE, HeatmapLayout, ViewIdContext, Z_INDEX$1 as Z_INDEX, compareViewPerformance, convertViewportToIframeCoords, createStorePerformanceTracker, createViewContextHook, decodeArrayClarity, decodeClarity, downloadPerformanceReport, getCompareViewId, getMetricsByViewId, getPerformanceReportJSON, getScrollGradientColor, performanceLogger, printPerformanceSummary, scrollToElementIfNeeded, sendPerformanceReport, serializeAreas, trackStoreAction, useAreaCreation, useAreaEditMode, useAreaFilterVisible, useAreaHydration, useAreaInteraction, useAreaPositionsUpdater, useAreaRectSync, useAreaRendererContainer, useAreaTopAutoDetect, useClickedElement, useDebounceCallback, useElementCalloutVisible, useHeatmapAreaClickContext, useHeatmapCanvas, useHeatmapClickContext, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapCopyView, useHeatmapDataContext, useHeatmapEffects, useHeatmapElementPosition, useHeatmapHoverContext,
|
|
8961
|
+
export { BACKDROP_CONFIG, DEFAULT_SIDEBAR_WIDTH, DEFAULT_VIEW_ID, DEFAULT_ZOOM_RATIO, EClickMode, EClickRankType, EClickType, EDeviceType, EHeatmapDataSource, EHeatmapMode, EHeatmapType, ELM_CALLOUT_CONFIG, EScrollType, GraphView, HEATMAP_CONFIG, HEATMAP_IFRAME, HEATMAP_STYLE, HeatmapLayout, ViewIdContext, Z_INDEX$1 as Z_INDEX, compareViewPerformance, convertViewportToIframeCoords, createStorePerformanceTracker, createViewContextHook, decodeArrayClarity, decodeClarity, downloadPerformanceReport, getCompareViewId, getMetricsByViewId, getPerformanceReportJSON, getScrollGradientColor, isElmInDataInfo, performanceLogger, printPerformanceSummary, scrollToElementIfNeeded, sendPerformanceReport, serializeAreas, trackStoreAction, useAreaCreation, useAreaEditMode, useAreaFilterVisible, useAreaHydration, useAreaInteraction, useAreaPositionsUpdater, useAreaRectSync, useAreaRendererContainer, useAreaTopAutoDetect, useClickedElement, useDebounceCallback, useElementCalloutVisible, useHeatmapAreaClickContext, useHeatmapCanvas, useHeatmapClickContext, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapCopyView, useHeatmapDataContext, useHeatmapEffects, useHeatmapElementPosition, useHeatmapHoverContext, useHeatmapLiveContext, useHeatmapRenderByMode, useHeatmapScale, useHeatmapScroll, useHeatmapScrollContext, useHeatmapSettingContext, useHeatmapViewportByDevice, useHeatmapVizContext, useHeatmapVizRectContext, useHoveredElement, useMeasureFunction, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useRenderCount, useScrollmapZones, useTrackHookCall, useViewIdContext, useVizLiveRender, useWhyDidYouUpdate, useWrapperRefHeight, useZonePositions, withPerformanceTracking };
|