@gemx-dev/heatmap-react 3.5.49 → 3.5.50

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.
Files changed (231) hide show
  1. package/dist/esm/components/Layout/ContentMetricBar.d.ts.map +1 -1
  2. package/dist/esm/components/Layout/ContentVizByMode.d.ts.map +1 -1
  3. package/dist/esm/components/Layout/HeatmapLayout.d.ts.map +1 -1
  4. package/dist/esm/components/Layout/Sidebar/ContentSidebar.d.ts +2 -0
  5. package/dist/esm/components/Layout/Sidebar/ContentSidebar.d.ts.map +1 -0
  6. package/dist/esm/components/Layout/Sidebar/PopoverSidebar.d.ts +2 -0
  7. package/dist/esm/components/Layout/Sidebar/PopoverSidebar.d.ts.map +1 -0
  8. package/dist/esm/components/Layout/Sidebar/index.d.ts +3 -0
  9. package/dist/esm/components/Layout/Sidebar/index.d.ts.map +1 -0
  10. package/dist/esm/components/Layout/WrapperLayout.d.ts.map +1 -1
  11. package/dist/esm/components/Layout/WrapperPreview.d.ts.map +1 -1
  12. package/dist/esm/components/VizDom/VizContainer.d.ts.map +1 -1
  13. package/dist/esm/components/VizDom/VizDomHeatmap.d.ts.map +1 -1
  14. package/dist/esm/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  15. package/dist/esm/components/VizDom/WrapperVisual.d.ts.map +1 -1
  16. package/dist/esm/components/VizElement/DefaultRankBadges.d.ts.map +1 -1
  17. package/dist/esm/components/VizElement/ElementCallout.d.ts.map +1 -1
  18. package/dist/esm/components/VizElement/ElementOverlay.d.ts +1 -0
  19. package/dist/esm/components/VizElement/ElementOverlay.d.ts.map +1 -1
  20. package/dist/esm/components/VizElement/HeatmapElements.d.ts +0 -1
  21. package/dist/esm/components/VizElement/HeatmapElements.d.ts.map +1 -1
  22. package/dist/esm/components/VizElement/VizElements.d.ts.map +1 -1
  23. package/dist/esm/components/VizLive/VizLiveHeatmap.d.ts.map +1 -1
  24. package/dist/esm/components/VizLive/VizLiveRenderer.d.ts.map +1 -1
  25. package/dist/esm/components/VizScrollmap/ScrollMapMinimap.d.ts.map +1 -1
  26. package/dist/esm/components/VizScrollmap/VizScrollMap.d.ts.map +1 -1
  27. package/dist/esm/configs/index.d.ts +2 -0
  28. package/dist/esm/configs/index.d.ts.map +1 -1
  29. package/dist/esm/configs/viewId.d.ts +21 -0
  30. package/dist/esm/configs/viewId.d.ts.map +1 -0
  31. package/dist/esm/configs/z-index.d.ts +8 -0
  32. package/dist/esm/configs/z-index.d.ts.map +1 -0
  33. package/dist/esm/constants/index.d.ts +13 -4
  34. package/dist/esm/constants/index.d.ts.map +1 -1
  35. package/dist/esm/helpers/iframe-helper/style-replacer.d.ts.map +1 -1
  36. package/dist/esm/hooks/index.d.ts +1 -4
  37. package/dist/esm/hooks/index.d.ts.map +1 -1
  38. package/dist/esm/hooks/register/useRegisterControl.d.ts.map +1 -1
  39. package/dist/esm/hooks/view-context/index.d.ts +7 -0
  40. package/dist/esm/hooks/view-context/index.d.ts.map +1 -0
  41. package/dist/esm/hooks/view-context/useHeatmapCopyView.d.ts +33 -0
  42. package/dist/esm/hooks/view-context/useHeatmapCopyView.d.ts.map +1 -0
  43. package/dist/esm/hooks/{useHeatmapData.d.ts → view-context/useHeatmapData.d.ts} +1 -1
  44. package/dist/esm/hooks/view-context/useHeatmapData.d.ts.map +1 -0
  45. package/dist/esm/hooks/view-context/useHeatmapInteraction.d.ts +16 -0
  46. package/dist/esm/hooks/view-context/useHeatmapInteraction.d.ts.map +1 -0
  47. package/dist/{umd/hooks → esm/hooks/view-context}/useHeatmapViz.d.ts +2 -0
  48. package/dist/esm/hooks/view-context/useHeatmapViz.d.ts.map +1 -0
  49. package/dist/esm/hooks/view-context/useHeatmapVizScrollmap.d.ts +13 -0
  50. package/dist/esm/hooks/view-context/useHeatmapVizScrollmap.d.ts.map +1 -0
  51. package/dist/esm/hooks/{useViewId.d.ts → view-context/useViewId.d.ts} +1 -1
  52. package/dist/esm/hooks/view-context/useViewId.d.ts.map +1 -0
  53. package/dist/esm/hooks/viz-canvas/useClickmap.d.ts.map +1 -1
  54. package/dist/esm/hooks/viz-canvas/useHeatmapCanvas.d.ts +1 -3
  55. package/dist/esm/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
  56. package/dist/esm/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
  57. package/dist/esm/hooks/viz-elements/useClickedElement.d.ts +0 -1
  58. package/dist/esm/hooks/viz-elements/useClickedElement.d.ts.map +1 -1
  59. package/dist/esm/hooks/viz-elements/useElementCalloutVisible.d.ts.map +1 -1
  60. package/dist/esm/hooks/viz-elements/useHeatmapEffects.d.ts +1 -4
  61. package/dist/esm/hooks/viz-elements/useHeatmapEffects.d.ts.map +1 -1
  62. package/dist/esm/hooks/viz-elements/useHoveredElement.d.ts.map +1 -1
  63. package/dist/esm/hooks/viz-live/useVizLiveRender.d.ts.map +1 -1
  64. package/dist/esm/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
  65. package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts +0 -1
  66. package/dist/esm/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
  67. package/dist/esm/hooks/viz-scale/useObserveIframeHeight.d.ts +0 -1
  68. package/dist/esm/hooks/viz-scale/useObserveIframeHeight.d.ts.map +1 -1
  69. package/dist/esm/hooks/viz-scale/useWrapperRefHeight.d.ts.map +1 -1
  70. package/dist/esm/index.d.ts +3 -2
  71. package/dist/esm/index.d.ts.map +1 -1
  72. package/dist/esm/index.js +1080 -474
  73. package/dist/esm/index.mjs +1080 -474
  74. package/dist/esm/performance/PerformanceLogger.d.ts +22 -0
  75. package/dist/esm/performance/PerformanceLogger.d.ts.map +1 -0
  76. package/dist/esm/performance/hooks.d.ts +28 -0
  77. package/dist/esm/performance/hooks.d.ts.map +1 -0
  78. package/dist/esm/performance/index.d.ts +7 -0
  79. package/dist/esm/performance/index.d.ts.map +1 -0
  80. package/dist/esm/performance/storeTracker.d.ts +10 -0
  81. package/dist/esm/performance/storeTracker.d.ts.map +1 -0
  82. package/dist/esm/performance/types.d.ts +79 -0
  83. package/dist/esm/performance/types.d.ts.map +1 -0
  84. package/dist/esm/performance/utils.d.ts +42 -0
  85. package/dist/esm/performance/utils.d.ts.map +1 -0
  86. package/dist/esm/performance/withPerformanceTracking.d.ts +14 -0
  87. package/dist/esm/performance/withPerformanceTracking.d.ts.map +1 -0
  88. package/dist/esm/stores/comp.d.ts.map +1 -1
  89. package/dist/esm/stores/data.d.ts +9 -1
  90. package/dist/esm/stores/data.d.ts.map +1 -1
  91. package/dist/esm/stores/interaction.d.ts +20 -9
  92. package/dist/esm/stores/interaction.d.ts.map +1 -1
  93. package/dist/esm/stores/mode-single.d.ts +11 -1
  94. package/dist/esm/stores/mode-single.d.ts.map +1 -1
  95. package/dist/esm/stores/viz-scrollmap.d.ts +18 -7
  96. package/dist/esm/stores/viz-scrollmap.d.ts.map +1 -1
  97. package/dist/esm/stores/viz.d.ts +9 -1
  98. package/dist/esm/stores/viz.d.ts.map +1 -1
  99. package/dist/esm/types/control.d.ts +9 -1
  100. package/dist/esm/types/control.d.ts.map +1 -1
  101. package/dist/esm/types/index.d.ts +1 -0
  102. package/dist/esm/types/index.d.ts.map +1 -1
  103. package/dist/style.css +7 -1
  104. package/dist/umd/components/Layout/ContentMetricBar.d.ts.map +1 -1
  105. package/dist/umd/components/Layout/ContentVizByMode.d.ts.map +1 -1
  106. package/dist/umd/components/Layout/HeatmapLayout.d.ts.map +1 -1
  107. package/dist/umd/components/Layout/Sidebar/ContentSidebar.d.ts +2 -0
  108. package/dist/umd/components/Layout/Sidebar/ContentSidebar.d.ts.map +1 -0
  109. package/dist/umd/components/Layout/Sidebar/PopoverSidebar.d.ts +2 -0
  110. package/dist/umd/components/Layout/Sidebar/PopoverSidebar.d.ts.map +1 -0
  111. package/dist/umd/components/Layout/Sidebar/index.d.ts +3 -0
  112. package/dist/umd/components/Layout/Sidebar/index.d.ts.map +1 -0
  113. package/dist/umd/components/Layout/WrapperLayout.d.ts.map +1 -1
  114. package/dist/umd/components/Layout/WrapperPreview.d.ts.map +1 -1
  115. package/dist/umd/components/VizDom/VizContainer.d.ts.map +1 -1
  116. package/dist/umd/components/VizDom/VizDomHeatmap.d.ts.map +1 -1
  117. package/dist/umd/components/VizDom/VizDomRenderer.d.ts.map +1 -1
  118. package/dist/umd/components/VizDom/WrapperVisual.d.ts.map +1 -1
  119. package/dist/umd/components/VizElement/DefaultRankBadges.d.ts.map +1 -1
  120. package/dist/umd/components/VizElement/ElementCallout.d.ts.map +1 -1
  121. package/dist/umd/components/VizElement/ElementOverlay.d.ts +1 -0
  122. package/dist/umd/components/VizElement/ElementOverlay.d.ts.map +1 -1
  123. package/dist/umd/components/VizElement/HeatmapElements.d.ts +0 -1
  124. package/dist/umd/components/VizElement/HeatmapElements.d.ts.map +1 -1
  125. package/dist/umd/components/VizElement/VizElements.d.ts.map +1 -1
  126. package/dist/umd/components/VizLive/VizLiveHeatmap.d.ts.map +1 -1
  127. package/dist/umd/components/VizLive/VizLiveRenderer.d.ts.map +1 -1
  128. package/dist/umd/components/VizScrollmap/ScrollMapMinimap.d.ts.map +1 -1
  129. package/dist/umd/components/VizScrollmap/VizScrollMap.d.ts.map +1 -1
  130. package/dist/umd/configs/index.d.ts +2 -0
  131. package/dist/umd/configs/index.d.ts.map +1 -1
  132. package/dist/umd/configs/viewId.d.ts +21 -0
  133. package/dist/umd/configs/viewId.d.ts.map +1 -0
  134. package/dist/umd/configs/z-index.d.ts +8 -0
  135. package/dist/umd/configs/z-index.d.ts.map +1 -0
  136. package/dist/umd/constants/index.d.ts +13 -4
  137. package/dist/umd/constants/index.d.ts.map +1 -1
  138. package/dist/umd/helpers/iframe-helper/style-replacer.d.ts.map +1 -1
  139. package/dist/umd/hooks/index.d.ts +1 -4
  140. package/dist/umd/hooks/index.d.ts.map +1 -1
  141. package/dist/umd/hooks/register/useRegisterControl.d.ts.map +1 -1
  142. package/dist/umd/hooks/view-context/index.d.ts +7 -0
  143. package/dist/umd/hooks/view-context/index.d.ts.map +1 -0
  144. package/dist/umd/hooks/view-context/useHeatmapCopyView.d.ts +33 -0
  145. package/dist/umd/hooks/view-context/useHeatmapCopyView.d.ts.map +1 -0
  146. package/dist/umd/hooks/{useHeatmapData.d.ts → view-context/useHeatmapData.d.ts} +1 -1
  147. package/dist/umd/hooks/view-context/useHeatmapData.d.ts.map +1 -0
  148. package/dist/umd/hooks/view-context/useHeatmapInteraction.d.ts +16 -0
  149. package/dist/umd/hooks/view-context/useHeatmapInteraction.d.ts.map +1 -0
  150. package/dist/{esm/hooks → umd/hooks/view-context}/useHeatmapViz.d.ts +2 -0
  151. package/dist/umd/hooks/view-context/useHeatmapViz.d.ts.map +1 -0
  152. package/dist/umd/hooks/view-context/useHeatmapVizScrollmap.d.ts +13 -0
  153. package/dist/umd/hooks/view-context/useHeatmapVizScrollmap.d.ts.map +1 -0
  154. package/dist/umd/hooks/{useViewId.d.ts → view-context/useViewId.d.ts} +1 -1
  155. package/dist/umd/hooks/view-context/useViewId.d.ts.map +1 -0
  156. package/dist/umd/hooks/viz-canvas/useClickmap.d.ts.map +1 -1
  157. package/dist/umd/hooks/viz-canvas/useHeatmapCanvas.d.ts +1 -3
  158. package/dist/umd/hooks/viz-canvas/useHeatmapCanvas.d.ts.map +1 -1
  159. package/dist/umd/hooks/viz-canvas/useScrollmap.d.ts.map +1 -1
  160. package/dist/umd/hooks/viz-elements/useClickedElement.d.ts +0 -1
  161. package/dist/umd/hooks/viz-elements/useClickedElement.d.ts.map +1 -1
  162. package/dist/umd/hooks/viz-elements/useElementCalloutVisible.d.ts.map +1 -1
  163. package/dist/umd/hooks/viz-elements/useHeatmapEffects.d.ts +1 -4
  164. package/dist/umd/hooks/viz-elements/useHeatmapEffects.d.ts.map +1 -1
  165. package/dist/umd/hooks/viz-elements/useHoveredElement.d.ts.map +1 -1
  166. package/dist/umd/hooks/viz-live/useVizLiveRender.d.ts.map +1 -1
  167. package/dist/umd/hooks/viz-render/useHeatmapRender.d.ts.map +1 -1
  168. package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts +0 -1
  169. package/dist/umd/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -1
  170. package/dist/umd/hooks/viz-scale/useObserveIframeHeight.d.ts +0 -1
  171. package/dist/umd/hooks/viz-scale/useObserveIframeHeight.d.ts.map +1 -1
  172. package/dist/umd/hooks/viz-scale/useWrapperRefHeight.d.ts.map +1 -1
  173. package/dist/umd/index.d.ts +3 -2
  174. package/dist/umd/index.d.ts.map +1 -1
  175. package/dist/umd/index.js +2 -2
  176. package/dist/umd/performance/PerformanceLogger.d.ts +22 -0
  177. package/dist/umd/performance/PerformanceLogger.d.ts.map +1 -0
  178. package/dist/umd/performance/hooks.d.ts +28 -0
  179. package/dist/umd/performance/hooks.d.ts.map +1 -0
  180. package/dist/umd/performance/index.d.ts +7 -0
  181. package/dist/umd/performance/index.d.ts.map +1 -0
  182. package/dist/umd/performance/storeTracker.d.ts +10 -0
  183. package/dist/umd/performance/storeTracker.d.ts.map +1 -0
  184. package/dist/umd/performance/types.d.ts +79 -0
  185. package/dist/umd/performance/types.d.ts.map +1 -0
  186. package/dist/umd/performance/utils.d.ts +42 -0
  187. package/dist/umd/performance/utils.d.ts.map +1 -0
  188. package/dist/umd/performance/withPerformanceTracking.d.ts +14 -0
  189. package/dist/umd/performance/withPerformanceTracking.d.ts.map +1 -0
  190. package/dist/umd/stores/comp.d.ts.map +1 -1
  191. package/dist/umd/stores/data.d.ts +9 -1
  192. package/dist/umd/stores/data.d.ts.map +1 -1
  193. package/dist/umd/stores/interaction.d.ts +20 -9
  194. package/dist/umd/stores/interaction.d.ts.map +1 -1
  195. package/dist/umd/stores/mode-single.d.ts +11 -1
  196. package/dist/umd/stores/mode-single.d.ts.map +1 -1
  197. package/dist/umd/stores/viz-scrollmap.d.ts +18 -7
  198. package/dist/umd/stores/viz-scrollmap.d.ts.map +1 -1
  199. package/dist/umd/stores/viz.d.ts +9 -1
  200. package/dist/umd/stores/viz.d.ts.map +1 -1
  201. package/dist/umd/types/control.d.ts +9 -1
  202. package/dist/umd/types/control.d.ts.map +1 -1
  203. package/dist/umd/types/index.d.ts +1 -0
  204. package/dist/umd/types/index.d.ts.map +1 -1
  205. package/package.json +1 -1
  206. package/dist/esm/components/Layout/LeftSidebar.d.ts +0 -2
  207. package/dist/esm/components/Layout/LeftSidebar.d.ts.map +0 -1
  208. package/dist/esm/hooks/compare/index.d.ts +0 -4
  209. package/dist/esm/hooks/compare/index.d.ts.map +0 -1
  210. package/dist/esm/hooks/compare/useCompareAwareConfig.d.ts +0 -21
  211. package/dist/esm/hooks/compare/useCompareAwareConfig.d.ts.map +0 -1
  212. package/dist/esm/hooks/compare/useCompareAwareData.d.ts +0 -28
  213. package/dist/esm/hooks/compare/useCompareAwareData.d.ts.map +0 -1
  214. package/dist/esm/hooks/compare/useCompareAwareViz.d.ts +0 -34
  215. package/dist/esm/hooks/compare/useCompareAwareViz.d.ts.map +0 -1
  216. package/dist/esm/hooks/useHeatmapData.d.ts.map +0 -1
  217. package/dist/esm/hooks/useHeatmapViz.d.ts.map +0 -1
  218. package/dist/esm/hooks/useViewId.d.ts.map +0 -1
  219. package/dist/umd/components/Layout/LeftSidebar.d.ts +0 -2
  220. package/dist/umd/components/Layout/LeftSidebar.d.ts.map +0 -1
  221. package/dist/umd/hooks/compare/index.d.ts +0 -4
  222. package/dist/umd/hooks/compare/index.d.ts.map +0 -1
  223. package/dist/umd/hooks/compare/useCompareAwareConfig.d.ts +0 -21
  224. package/dist/umd/hooks/compare/useCompareAwareConfig.d.ts.map +0 -1
  225. package/dist/umd/hooks/compare/useCompareAwareData.d.ts +0 -28
  226. package/dist/umd/hooks/compare/useCompareAwareData.d.ts.map +0 -1
  227. package/dist/umd/hooks/compare/useCompareAwareViz.d.ts +0 -34
  228. package/dist/umd/hooks/compare/useCompareAwareViz.d.ts.map +0 -1
  229. package/dist/umd/hooks/useHeatmapData.d.ts.map +0 -1
  230. package/dist/umd/hooks/useHeatmapViz.d.ts.map +0 -1
  231. package/dist/umd/hooks/useViewId.d.ts.map +0 -1
package/dist/esm/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use client"
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import { useNodesState, ReactFlow, Controls, Background } from '@xyflow/react';
4
- import { useEffect, createContext, useContext, useCallback, useState, useRef, useMemo, forwardRef, Fragment as Fragment$1 } from 'react';
4
+ import { useEffect, createContext, useContext, useMemo, useCallback, useState, useRef, forwardRef, Fragment as Fragment$1 } from 'react';
5
5
  import { create } from 'zustand';
6
+ import { subscribeWithSelector } from 'zustand/middleware';
6
7
  import { decode } from '@gemx-dev/clarity-decode';
7
8
  import { Visualizer } from '@gemx-dev/clarity-visualize';
8
9
  import { createPortal } from 'react-dom';
@@ -67,10 +68,45 @@ const HEATMAP_STYLE = {
67
68
  };
68
69
  const DEFAULT_SIDEBAR_WIDTH = 260;
69
70
 
71
+ /**
72
+ * Default view ID for single mode
73
+ */
74
+ const DEFAULT_VIEW_ID = 'default';
75
+ /**
76
+ * Generate compare view ID
77
+ * @param index - Zero-based index (0, 1, 2, 3)
78
+ * @returns View ID string like 'view-0', 'view-1', etc.
79
+ */
80
+ const getCompareViewId = (index) => `view-${index}`;
81
+ /**
82
+ * Check if a view ID is a compare view
83
+ */
84
+ const isCompareViewId = (viewId) => viewId.startsWith('view-');
85
+ /**
86
+ * Extract index from compare view ID
87
+ * @param viewId - View ID like 'view-0'
88
+ * @returns Index number or -1 if invalid
89
+ */
90
+ const getCompareViewIndex = (viewId) => {
91
+ if (!isCompareViewId(viewId))
92
+ return -1;
93
+ const match = viewId.match(/^view-(\d+)$/);
94
+ return match ? parseInt(match[1], 10) : -1;
95
+ };
96
+
97
+ const Z_INDEX = {
98
+ ELEMENTS: 1000,
99
+ CALLOUT: 2000,
100
+ MINIMAP: 3000,
101
+ SIDEBAR: 4000,
102
+ SIDEBAR_POPOVER: 4001,
103
+ };
104
+
70
105
  const useHeatmapControlStore = create()((set, get) => {
71
106
  return {
72
107
  controls: {
73
- Sidebar: null,
108
+ Sidebar: undefined,
109
+ SidebarActivator: undefined,
74
110
  Toolbar: null,
75
111
  MetricBar: null,
76
112
  VizLoading: null,
@@ -114,7 +150,7 @@ const useHeatmapConfigStore = create()((set, get) => {
114
150
  mode: 'single',
115
151
  width: 1440,
116
152
  sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
117
- heatmapType: IHeatmapType.Scroll,
153
+ heatmapType: IHeatmapType.Click,
118
154
  clickType: IClickType.Total,
119
155
  scrollType: IScrollType.Depth,
120
156
  isRendering: true,
@@ -129,23 +165,22 @@ const useHeatmapConfigStore = create()((set, get) => {
129
165
  };
130
166
  });
131
167
 
132
- const DEFAULT_VIEW_ID$2 = 'default';
133
- const useHeatmapDataStore = create()((set, get) => {
168
+ const useHeatmapDataStore = create()(subscribeWithSelector((set, get) => {
134
169
  return {
135
- data: { [DEFAULT_VIEW_ID$2]: undefined },
136
- clickmap: { [DEFAULT_VIEW_ID$2]: undefined },
137
- dataInfo: { [DEFAULT_VIEW_ID$2]: undefined },
138
- scrollmap: { [DEFAULT_VIEW_ID$2]: undefined },
139
- setDataInfo: (dataInfo, viewId = DEFAULT_VIEW_ID$2) => set((state) => ({
170
+ data: { [DEFAULT_VIEW_ID]: undefined },
171
+ clickmap: { [DEFAULT_VIEW_ID]: undefined },
172
+ dataInfo: { [DEFAULT_VIEW_ID]: undefined },
173
+ scrollmap: { [DEFAULT_VIEW_ID]: undefined },
174
+ setDataInfo: (dataInfo, viewId = DEFAULT_VIEW_ID) => set((state) => ({
140
175
  dataInfo: { ...state.dataInfo, [viewId]: dataInfo },
141
176
  })),
142
- setData: (data, viewId = DEFAULT_VIEW_ID$2) => set((state) => ({
177
+ setData: (data, viewId = DEFAULT_VIEW_ID) => set((state) => ({
143
178
  data: { ...state.data, [viewId]: data },
144
179
  })),
145
- setClickmap: (clickmap, viewId = DEFAULT_VIEW_ID$2) => set((state) => ({
180
+ setClickmap: (clickmap, viewId = DEFAULT_VIEW_ID) => set((state) => ({
146
181
  clickmap: { ...state.clickmap, [viewId]: clickmap },
147
182
  })),
148
- setScrollmap: (scrollmap, viewId = DEFAULT_VIEW_ID$2) => set((state) => ({
183
+ setScrollmap: (scrollmap, viewId = DEFAULT_VIEW_ID) => set((state) => ({
149
184
  scrollmap: { ...state.scrollmap, [viewId]: scrollmap },
150
185
  })),
151
186
  copyView: (fromViewId, toViewId) => set((state) => ({
@@ -171,50 +206,98 @@ const useHeatmapDataStore = create()((set, get) => {
171
206
  };
172
207
  }),
173
208
  resetAll: () => set({
174
- data: { [DEFAULT_VIEW_ID$2]: undefined },
175
- clickmap: { [DEFAULT_VIEW_ID$2]: undefined },
176
- dataInfo: { [DEFAULT_VIEW_ID$2]: undefined },
177
- scrollmap: { [DEFAULT_VIEW_ID$2]: undefined },
209
+ data: { [DEFAULT_VIEW_ID]: undefined },
210
+ clickmap: { [DEFAULT_VIEW_ID]: undefined },
211
+ dataInfo: { [DEFAULT_VIEW_ID]: undefined },
212
+ scrollmap: { [DEFAULT_VIEW_ID]: undefined },
178
213
  }),
179
214
  };
180
- });
215
+ }));
181
216
 
182
- const useHeatmapInteractionStore = create()((set, get) => {
217
+ const DEFAULT_STATE = {
218
+ hideSidebar: false,
219
+ };
220
+ const useHeatmapInteractionStore = create()(subscribeWithSelector((set, get) => {
183
221
  return {
184
- state: {
185
- hideSidebar: false,
186
- },
187
- setState: (state) => set({ state }),
188
- selectedElement: null,
189
- setSelectedElement: (selectedElement) => set({ selectedElement }),
190
- hoveredElement: null,
191
- setHoveredElement: (hoveredElement) => set({ hoveredElement }),
192
- shouldShowCallout: false,
193
- setShouldShowCallout: (shouldShowCallout) => set({ shouldShowCallout }),
222
+ state: { [DEFAULT_VIEW_ID]: DEFAULT_STATE },
223
+ selectedElement: { [DEFAULT_VIEW_ID]: null },
224
+ hoveredElement: { [DEFAULT_VIEW_ID]: null },
225
+ shouldShowCallout: { [DEFAULT_VIEW_ID]: false },
226
+ setState: (state, viewId = DEFAULT_VIEW_ID) => set((prev) => ({
227
+ state: { ...prev.state, [viewId]: state },
228
+ })),
229
+ setSelectedElement: (selectedElement, viewId = DEFAULT_VIEW_ID) => set((prev) => ({
230
+ selectedElement: { ...prev.selectedElement, [viewId]: selectedElement },
231
+ })),
232
+ setHoveredElement: (hoveredElement, viewId = DEFAULT_VIEW_ID) => set((prev) => ({
233
+ hoveredElement: { ...prev.hoveredElement, [viewId]: hoveredElement },
234
+ })),
235
+ setShouldShowCallout: (shouldShowCallout, viewId = DEFAULT_VIEW_ID) => set((prev) => ({
236
+ shouldShowCallout: { ...prev.shouldShowCallout, [viewId]: shouldShowCallout },
237
+ })),
238
+ copyView: (fromViewId, toViewId) => set((state) => ({
239
+ state: {
240
+ ...state.state,
241
+ [toViewId]: state.state[fromViewId] ?? DEFAULT_STATE,
242
+ },
243
+ selectedElement: {
244
+ ...state.selectedElement,
245
+ [toViewId]: state.selectedElement[fromViewId] ?? null,
246
+ },
247
+ hoveredElement: {
248
+ ...state.hoveredElement,
249
+ [toViewId]: state.hoveredElement[fromViewId] ?? null,
250
+ },
251
+ shouldShowCallout: {
252
+ ...state.shouldShowCallout,
253
+ [toViewId]: state.shouldShowCallout[fromViewId] ?? false,
254
+ },
255
+ })),
256
+ clearView: (viewId) => set((state) => {
257
+ const newState = { ...state.state };
258
+ const newSelectedElement = { ...state.selectedElement };
259
+ const newHoveredElement = { ...state.hoveredElement };
260
+ const newShouldShowCallout = { ...state.shouldShowCallout };
261
+ delete newState[viewId];
262
+ delete newSelectedElement[viewId];
263
+ delete newHoveredElement[viewId];
264
+ delete newShouldShowCallout[viewId];
265
+ return {
266
+ state: newState,
267
+ selectedElement: newSelectedElement,
268
+ hoveredElement: newHoveredElement,
269
+ shouldShowCallout: newShouldShowCallout,
270
+ };
271
+ }),
272
+ resetAll: () => set({
273
+ state: { default: DEFAULT_STATE },
274
+ selectedElement: { default: null },
275
+ hoveredElement: { default: null },
276
+ shouldShowCallout: { default: false },
277
+ }),
194
278
  };
195
- });
279
+ }));
196
280
 
197
- const DEFAULT_VIEW_ID$1 = 'default';
198
- const useHeatmapVizStore = create()((set, get) => {
281
+ const useHeatmapVizStore = create()(subscribeWithSelector((set, get) => {
199
282
  return {
200
- isRenderViz: { [DEFAULT_VIEW_ID$1]: false },
201
- zoomRatio: { [DEFAULT_VIEW_ID$1]: 100 },
202
- minZoomRatio: { [DEFAULT_VIEW_ID$1]: 10 },
203
- scale: { [DEFAULT_VIEW_ID$1]: 1 },
204
- isScaledToFit: { [DEFAULT_VIEW_ID$1]: false },
205
- setIsRenderViz: (isRenderViz, viewId = DEFAULT_VIEW_ID$1) => set((state) => ({
283
+ isRenderViz: { [DEFAULT_VIEW_ID]: false },
284
+ zoomRatio: { [DEFAULT_VIEW_ID]: 100 },
285
+ minZoomRatio: { [DEFAULT_VIEW_ID]: 10 },
286
+ scale: { [DEFAULT_VIEW_ID]: 1 },
287
+ isScaledToFit: { [DEFAULT_VIEW_ID]: false },
288
+ setIsRenderViz: (isRenderViz, viewId = DEFAULT_VIEW_ID) => set((state) => ({
206
289
  isRenderViz: { ...state.isRenderViz, [viewId]: isRenderViz },
207
290
  })),
208
- setZoomRatio: (zoomRatio, viewId = DEFAULT_VIEW_ID$1) => set((state) => ({
291
+ setZoomRatio: (zoomRatio, viewId = DEFAULT_VIEW_ID) => set((state) => ({
209
292
  zoomRatio: { ...state.zoomRatio, [viewId]: zoomRatio },
210
293
  })),
211
- setMinZoomRatio: (minZoomRatio, viewId = DEFAULT_VIEW_ID$1) => set((state) => ({
294
+ setMinZoomRatio: (minZoomRatio, viewId = DEFAULT_VIEW_ID) => set((state) => ({
212
295
  minZoomRatio: { ...state.minZoomRatio, [viewId]: minZoomRatio },
213
296
  })),
214
- setScale: (scale, viewId = DEFAULT_VIEW_ID$1) => set((state) => ({
297
+ setScale: (scale, viewId = DEFAULT_VIEW_ID) => set((state) => ({
215
298
  scale: { ...state.scale, [viewId]: scale },
216
299
  })),
217
- setIsScaledToFit: (isScaledToFit, viewId = DEFAULT_VIEW_ID$1) => set((state) => ({
300
+ setIsScaledToFit: (isScaledToFit, viewId = DEFAULT_VIEW_ID) => set((state) => ({
218
301
  isScaledToFit: { ...state.isScaledToFit, [viewId]: isScaledToFit },
219
302
  })),
220
303
  copyView: (fromViewId, toViewId) => set((state) => ({
@@ -222,7 +305,10 @@ const useHeatmapVizStore = create()((set, get) => {
222
305
  zoomRatio: { ...state.zoomRatio, [toViewId]: state.zoomRatio[fromViewId] ?? 100 },
223
306
  minZoomRatio: { ...state.minZoomRatio, [toViewId]: state.minZoomRatio[fromViewId] ?? 10 },
224
307
  scale: { ...state.scale, [toViewId]: state.scale[fromViewId] ?? 1 },
225
- isScaledToFit: { ...state.isScaledToFit, [toViewId]: state.isScaledToFit[fromViewId] ?? false },
308
+ isScaledToFit: {
309
+ ...state.isScaledToFit,
310
+ [toViewId]: state.isScaledToFit[fromViewId] ?? false,
311
+ },
226
312
  })),
227
313
  clearView: (viewId) => set((state) => {
228
314
  const newIsRenderViz = { ...state.isRenderViz };
@@ -244,25 +330,63 @@ const useHeatmapVizStore = create()((set, get) => {
244
330
  };
245
331
  }),
246
332
  resetAll: () => set({
247
- isRenderViz: { [DEFAULT_VIEW_ID$1]: false },
248
- zoomRatio: { [DEFAULT_VIEW_ID$1]: 100 },
249
- minZoomRatio: { [DEFAULT_VIEW_ID$1]: 10 },
250
- scale: { [DEFAULT_VIEW_ID$1]: 1 },
251
- isScaledToFit: { [DEFAULT_VIEW_ID$1]: false },
333
+ isRenderViz: { [DEFAULT_VIEW_ID]: false },
334
+ zoomRatio: { [DEFAULT_VIEW_ID]: 100 },
335
+ minZoomRatio: { [DEFAULT_VIEW_ID]: 10 },
336
+ scale: { [DEFAULT_VIEW_ID]: 1 },
337
+ isScaledToFit: { [DEFAULT_VIEW_ID]: false },
252
338
  }),
253
339
  };
254
- });
340
+ }));
255
341
 
256
- const useHeatmapVizScrollmapStore = create()((set, get) => {
342
+ const useHeatmapVizScrollmapStore = create()(subscribeWithSelector((set, get) => {
257
343
  return {
258
- zones: [],
259
- hoveredZone: null,
260
- showMinimap: true,
261
- setZones: (zones) => set({ zones }),
262
- setHoveredZone: (hoveredZone) => set({ hoveredZone }),
263
- setShowMinimap: (showMinimap) => set({ showMinimap }),
344
+ zones: { [DEFAULT_VIEW_ID]: [] },
345
+ hoveredZone: { [DEFAULT_VIEW_ID]: null },
346
+ showMinimap: { [DEFAULT_VIEW_ID]: true },
347
+ setZones: (zones, viewId = DEFAULT_VIEW_ID) => set((prev) => ({
348
+ zones: { ...prev.zones, [viewId]: zones },
349
+ })),
350
+ setHoveredZone: (hoveredZone, viewId = DEFAULT_VIEW_ID) => set((prev) => ({
351
+ hoveredZone: { ...prev.hoveredZone, [viewId]: hoveredZone },
352
+ })),
353
+ setShowMinimap: (showMinimap, viewId = DEFAULT_VIEW_ID) => set((prev) => ({
354
+ showMinimap: { ...prev.showMinimap, [viewId]: showMinimap },
355
+ })),
356
+ copyView: (fromViewId, toViewId) => set((state) => ({
357
+ zones: {
358
+ ...state.zones,
359
+ [toViewId]: state.zones[fromViewId] ?? [],
360
+ },
361
+ hoveredZone: {
362
+ ...state.hoveredZone,
363
+ [toViewId]: state.hoveredZone[fromViewId] ?? null,
364
+ },
365
+ showMinimap: {
366
+ ...state.showMinimap,
367
+ [toViewId]: state.showMinimap[fromViewId] ?? true,
368
+ },
369
+ })),
370
+ clearView: (viewId) => set((state) => {
371
+ const newZones = { ...state.zones };
372
+ const newHoveredZone = { ...state.hoveredZone };
373
+ const newShowMinimap = { ...state.showMinimap };
374
+ delete newZones[viewId];
375
+ delete newHoveredZone[viewId];
376
+ delete newShowMinimap[viewId];
377
+ return {
378
+ zones: newZones,
379
+ hoveredZone: newHoveredZone,
380
+ showMinimap: newShowMinimap,
381
+ };
382
+ }),
383
+ resetAll: () => set({
384
+ zones: { default: [] },
385
+ hoveredZone: { default: null },
386
+ showMinimap: { default: true },
387
+ }),
264
388
  };
265
- });
389
+ }));
266
390
 
267
391
  const initialState = {
268
392
  payloads: [],
@@ -278,12 +402,12 @@ const useHeatmapLiveStore = create()((set, get) => {
278
402
  };
279
403
  });
280
404
 
281
- const DEFAULT_VIEW_ID = 'default';
282
- const useHeatmapSingleStore = create()((set, get) => {
405
+ const useHeatmapSingleStore = create()(subscribeWithSelector((set, get) => {
283
406
  return {
284
407
  vizRef: { [DEFAULT_VIEW_ID]: null },
285
408
  iframeHeight: { [DEFAULT_VIEW_ID]: 0 },
286
409
  wrapperHeight: { [DEFAULT_VIEW_ID]: 0 },
410
+ wrapperWidth: { [DEFAULT_VIEW_ID]: 0 },
287
411
  setVizRef: (vizRef, viewId = DEFAULT_VIEW_ID) => set((state) => ({
288
412
  vizRef: { ...state.vizRef, [viewId]: vizRef },
289
413
  })),
@@ -297,31 +421,43 @@ const useHeatmapSingleStore = create()((set, get) => {
297
421
  wrapperHeight: { ...state.wrapperHeight, [viewId]: wrapperHeight },
298
422
  }));
299
423
  },
424
+ setWrapperWidth: (wrapperWidth, viewId = DEFAULT_VIEW_ID) => {
425
+ set((state) => ({
426
+ wrapperWidth: { ...state.wrapperWidth, [viewId]: wrapperWidth },
427
+ }));
428
+ },
300
429
  copyView: (fromViewId, toViewId) => set((state) => ({
301
430
  // Don't copy vizRef - each view needs its own visualizer instance
302
431
  iframeHeight: { ...state.iframeHeight, [toViewId]: state.iframeHeight[fromViewId] ?? 0 },
303
- wrapperHeight: { ...state.wrapperHeight, [toViewId]: state.wrapperHeight[fromViewId] ?? 0 },
432
+ wrapperHeight: {
433
+ ...state.wrapperHeight,
434
+ [toViewId]: state.wrapperHeight[fromViewId] ?? 0,
435
+ },
304
436
  })),
305
437
  clearView: (viewId) => set((state) => {
306
438
  const newVizRef = { ...state.vizRef };
307
439
  const newIframeHeight = { ...state.iframeHeight };
308
440
  const newWrapperHeight = { ...state.wrapperHeight };
441
+ const newWrapperWidth = { ...state.wrapperWidth };
309
442
  delete newVizRef[viewId];
310
443
  delete newIframeHeight[viewId];
311
444
  delete newWrapperHeight[viewId];
445
+ delete newWrapperWidth[viewId];
312
446
  return {
313
447
  vizRef: newVizRef,
314
448
  iframeHeight: newIframeHeight,
315
449
  wrapperHeight: newWrapperHeight,
450
+ wrapperWidth: newWrapperWidth,
316
451
  };
317
452
  }),
318
453
  resetAll: () => set({
319
454
  vizRef: { [DEFAULT_VIEW_ID]: null },
320
455
  iframeHeight: { [DEFAULT_VIEW_ID]: 0 },
321
456
  wrapperHeight: { [DEFAULT_VIEW_ID]: 0 },
457
+ wrapperWidth: { [DEFAULT_VIEW_ID]: 0 },
322
458
  }),
323
459
  };
324
- });
460
+ }));
325
461
 
326
462
  const createDefaultView = (id, label, options) => ({
327
463
  id,
@@ -468,6 +604,7 @@ const useRegisterConfig = () => {
468
604
  const useRegisterControl = (control) => {
469
605
  const registerControl = useHeatmapControlStore((state) => state.registerControl);
470
606
  registerControl('Sidebar', control.Sidebar);
607
+ registerControl('SidebarActivator', control.SidebarActivator);
471
608
  registerControl('TopBar', control.TopBar);
472
609
  registerControl('Toolbar', control.Toolbar);
473
610
  registerControl('MetricBar', control.MetricBar);
@@ -482,18 +619,18 @@ const useRegisterControl = (control) => {
482
619
  const ViewIdContext = createContext(undefined);
483
620
  /**
484
621
  * Hook to get current viewId
485
- * Returns 'default' if not in a ViewIdContext (single mode)
622
+ * Returns DEFAULT_VIEW_ID if not in a ViewIdContext (single mode)
486
623
  */
487
624
  const useViewId = () => {
488
625
  const viewId = useContext(ViewIdContext);
489
- return viewId || 'default';
626
+ return viewId || DEFAULT_VIEW_ID;
490
627
  };
491
628
  /**
492
629
  * Hook to check if currently in compare mode
493
630
  */
494
631
  const useIsCompareMode = () => {
495
632
  const viewId = useContext(ViewIdContext);
496
- return viewId !== undefined && viewId !== 'default';
633
+ return viewId !== undefined && viewId !== DEFAULT_VIEW_ID;
497
634
  };
498
635
 
499
636
  const useHeatmapData = (props) => {
@@ -506,17 +643,191 @@ const useHeatmapData = (props) => {
506
643
  const setClickmap = useHeatmapDataStore((state) => state.setClickmap);
507
644
  const setScrollmap = useHeatmapDataStore((state) => state.setScrollmap);
508
645
  const setDataInfo = useHeatmapDataStore((state) => state.setDataInfo);
646
+ const memoizedSetters = useMemo(() => ({
647
+ setData: (newData) => setData(newData, viewId),
648
+ setClickmap: (newClickmap) => setClickmap(newClickmap, viewId),
649
+ setScrollmap: (newScrollmap) => setScrollmap(newScrollmap, viewId),
650
+ setDataInfo: (newDataInfo) => setDataInfo(newDataInfo, viewId),
651
+ }), [setData, setClickmap, setScrollmap, setDataInfo, viewId]);
509
652
  return {
510
- // Data
511
653
  data,
512
654
  clickmap,
513
655
  scrollmap,
514
656
  dataInfo,
515
657
  // Setters (auto-inject viewId)
516
- setData: (newData) => setData(newData, viewId),
517
- setClickmap: (newClickmap) => setClickmap(newClickmap, viewId),
518
- setScrollmap: (newScrollmap) => setScrollmap(newScrollmap, viewId),
519
- setDataInfo: (newDataInfo) => setDataInfo(newDataInfo, viewId),
658
+ ...memoizedSetters,
659
+ };
660
+ };
661
+
662
+ const useHeatmapInteraction = (props) => {
663
+ const viewId = props?.viewId || useViewId();
664
+ const state = useHeatmapInteractionStore((store) => store.state[viewId] ?? { hideSidebar: false });
665
+ const selectedElement = useHeatmapInteractionStore((store) => store.selectedElement[viewId] ?? null);
666
+ const hoveredElement = useHeatmapInteractionStore((store) => store.hoveredElement[viewId] ?? null);
667
+ const shouldShowCallout = useHeatmapInteractionStore((store) => store.shouldShowCallout[viewId] ?? false);
668
+ const setStateStore = useHeatmapInteractionStore((store) => store.setState);
669
+ const setSelectedElementStore = useHeatmapInteractionStore((store) => store.setSelectedElement);
670
+ const setHoveredElementStore = useHeatmapInteractionStore((store) => store.setHoveredElement);
671
+ const setShouldShowCalloutStore = useHeatmapInteractionStore((store) => store.setShouldShowCallout);
672
+ const memoizedSetters = useMemo(() => ({
673
+ setState: (newState) => setStateStore(newState, viewId),
674
+ setSelectedElement: (element) => setSelectedElementStore(element, viewId),
675
+ setHoveredElement: (element) => setHoveredElementStore(element, viewId),
676
+ setShouldShowCallout: (value) => setShouldShowCalloutStore(value, viewId),
677
+ }), [
678
+ setStateStore,
679
+ setSelectedElementStore,
680
+ setHoveredElementStore,
681
+ setShouldShowCalloutStore,
682
+ viewId,
683
+ ]);
684
+ return {
685
+ state,
686
+ selectedElement,
687
+ hoveredElement,
688
+ shouldShowCallout,
689
+ // Setters (auto-inject viewId)
690
+ ...memoizedSetters,
691
+ };
692
+ };
693
+
694
+ const useHeatmapViz = (props) => {
695
+ const viewId = props?.viewId || useViewId();
696
+ // Viz store
697
+ const isRenderViz = useHeatmapVizStore((state) => state.isRenderViz[viewId] ?? false);
698
+ const zoomRatio = useHeatmapVizStore((state) => state.zoomRatio[viewId] ?? 100);
699
+ const minZoomRatio = useHeatmapVizStore((state) => state.minZoomRatio[viewId] ?? 10);
700
+ const widthScale = useHeatmapVizStore((state) => state.scale[viewId] ?? 1);
701
+ const isScaledToFit = useHeatmapVizStore((state) => state.isScaledToFit[viewId] ?? false);
702
+ const setIsRenderViz = useHeatmapVizStore((state) => state.setIsRenderViz);
703
+ const setZoomRatio = useHeatmapVizStore((state) => state.setZoomRatio);
704
+ const setMinZoomRatio = useHeatmapVizStore((state) => state.setMinZoomRatio);
705
+ const setScale = useHeatmapVizStore((state) => state.setScale);
706
+ const setIsScaledToFit = useHeatmapVizStore((state) => state.setIsScaledToFit);
707
+ // Single store
708
+ const vizRef = useHeatmapSingleStore((state) => state.vizRef[viewId] ?? null);
709
+ const iframeHeight = useHeatmapSingleStore((state) => state.iframeHeight[viewId] ?? 0);
710
+ const wrapperHeight = useHeatmapSingleStore((state) => state.wrapperHeight[viewId] ?? 0);
711
+ const wrapperWidth = useHeatmapSingleStore((state) => state.wrapperWidth[viewId] ?? 0);
712
+ const setVizRef = useHeatmapSingleStore((state) => state.setVizRef);
713
+ const setIframeHeight = useHeatmapSingleStore((state) => state.setIframeHeight);
714
+ const setWrapperHeight = useHeatmapSingleStore((state) => state.setWrapperHeight);
715
+ const setWrapperWidth = useHeatmapSingleStore((state) => state.setWrapperWidth);
716
+ const memoizedSetters = useMemo(() => ({
717
+ setIsRenderViz: (value) => setIsRenderViz(value, viewId),
718
+ setZoomRatio: (value) => setZoomRatio(value, viewId),
719
+ setMinZoomRatio: (value) => setMinZoomRatio(value, viewId),
720
+ setScale: (value) => setScale(value, viewId),
721
+ setIsScaledToFit: (value) => setIsScaledToFit(value, viewId),
722
+ setVizRef: (value) => setVizRef(value, viewId),
723
+ setIframeHeight: (value) => setIframeHeight(value, viewId),
724
+ setWrapperHeight: (value) => setWrapperHeight(value, viewId),
725
+ setWrapperWidth: (value) => setWrapperWidth(value, viewId),
726
+ }), [
727
+ setIsRenderViz,
728
+ setZoomRatio,
729
+ setMinZoomRatio,
730
+ setScale,
731
+ setIsScaledToFit,
732
+ setVizRef,
733
+ setIframeHeight,
734
+ setWrapperHeight,
735
+ setWrapperWidth,
736
+ viewId,
737
+ ]);
738
+ return {
739
+ isRenderViz,
740
+ zoomRatio,
741
+ minZoomRatio,
742
+ widthScale,
743
+ isScaledToFit,
744
+ vizRef,
745
+ iframeHeight,
746
+ wrapperHeight,
747
+ wrapperWidth,
748
+ // Setters (auto-inject viewId)
749
+ ...memoizedSetters,
750
+ };
751
+ };
752
+
753
+ const useHeatmapVizScrollmap = (props) => {
754
+ const viewId = props?.viewId || useViewId();
755
+ const zones = useHeatmapVizScrollmapStore((store) => store.zones[viewId] ?? []);
756
+ const hoveredZone = useHeatmapVizScrollmapStore((store) => store.hoveredZone[viewId] ?? null);
757
+ const showMinimap = useHeatmapVizScrollmapStore((store) => store.showMinimap[viewId] ?? true);
758
+ const setZonesStore = useHeatmapVizScrollmapStore((store) => store.setZones);
759
+ const setHoveredZoneStore = useHeatmapVizScrollmapStore((store) => store.setHoveredZone);
760
+ const setShowMinimapStore = useHeatmapVizScrollmapStore((store) => store.setShowMinimap);
761
+ const memoizedSetters = useMemo(() => ({
762
+ setZones: (newZones) => setZonesStore(newZones, viewId),
763
+ setHoveredZone: (zone) => setHoveredZoneStore(zone, viewId),
764
+ setShowMinimap: (value) => setShowMinimapStore(value, viewId),
765
+ }), [setZonesStore, setHoveredZoneStore, setShowMinimapStore, viewId]);
766
+ return {
767
+ zones,
768
+ hoveredZone,
769
+ showMinimap,
770
+ // Setters (auto-inject viewId)
771
+ ...memoizedSetters,
772
+ };
773
+ };
774
+
775
+ /**
776
+ * Hook to handle copying and clearing view data across all stores
777
+ */
778
+ const useHeatmapCopyView = () => {
779
+ const copyDataView = useHeatmapDataStore((state) => state.copyView);
780
+ const copyVizView = useHeatmapVizStore((state) => state.copyView);
781
+ const copySingleView = useHeatmapSingleStore((state) => state.copyView);
782
+ const copyInteractionView = useHeatmapInteractionStore((state) => state.copyView);
783
+ const copyVizScrollmapView = useHeatmapVizScrollmapStore((state) => state.copyView);
784
+ const clearDataView = useHeatmapDataStore((state) => state.clearView);
785
+ const clearVizView = useHeatmapVizStore((state) => state.clearView);
786
+ const clearSingleView = useHeatmapSingleStore((state) => state.clearView);
787
+ const clearInteractionView = useHeatmapInteractionStore((state) => state.clearView);
788
+ const clearVizScrollmapView = useHeatmapVizScrollmapStore((state) => state.clearView);
789
+ const resetDataAll = useHeatmapDataStore((state) => state.resetAll);
790
+ const resetVizAll = useHeatmapVizStore((state) => state.resetAll);
791
+ const resetSingleAll = useHeatmapSingleStore((state) => state.resetAll);
792
+ const resetInteractionAll = useHeatmapInteractionStore((state) => state.resetAll);
793
+ const resetVizScrollmapAll = useHeatmapVizScrollmapStore((state) => state.resetAll);
794
+ const copyView = (fromViewId, toViewId) => {
795
+ copyDataView(fromViewId, toViewId);
796
+ copyVizView(fromViewId, toViewId);
797
+ copySingleView(fromViewId, toViewId);
798
+ copyInteractionView(fromViewId, toViewId);
799
+ copyVizScrollmapView(fromViewId, toViewId);
800
+ };
801
+ const copyViewToMultiple = (fromViewId, toViewIds) => {
802
+ toViewIds.forEach((toViewId) => {
803
+ copyView(fromViewId, toViewId);
804
+ });
805
+ };
806
+ const clearView = (viewId) => {
807
+ clearDataView(viewId);
808
+ clearVizView(viewId);
809
+ clearSingleView(viewId);
810
+ clearInteractionView(viewId);
811
+ clearVizScrollmapView(viewId);
812
+ };
813
+ const clearMultipleViews = (viewIds) => {
814
+ viewIds.forEach((viewId) => {
815
+ clearView(viewId);
816
+ });
817
+ };
818
+ const resetAll = () => {
819
+ resetDataAll();
820
+ resetVizAll();
821
+ resetSingleAll();
822
+ resetInteractionAll();
823
+ resetVizScrollmapAll();
824
+ };
825
+ return {
826
+ copyView,
827
+ copyViewToMultiple,
828
+ clearView,
829
+ clearMultipleViews,
830
+ resetAll,
520
831
  };
521
832
  };
522
833
 
@@ -777,33 +1088,29 @@ const buildElementInfo = (hash, rect, heatmapInfo) => {
777
1088
  };
778
1089
 
779
1090
  function findLastSizeOfDom(data) {
780
- const firstDoc = data.find((item) => item.envelope.sequence === 1)?.doc;
781
- const docSorted = firstDoc?.sort(sort);
782
- const firstEvent = docSorted?.[0];
1091
+ const listDocs = data
1092
+ .filter((item) => item.doc?.find((doc) => doc.data.width && doc.data.height))
1093
+ .flatMap((item) => item.doc?.flatMap((doc) => doc.data));
1094
+ const lastDoc = listDocs?.[listDocs.length - 1];
783
1095
  const docSize = {
784
- width: firstEvent?.data.width,
785
- height: firstEvent?.data.height,
1096
+ width: lastDoc?.width,
1097
+ height: lastDoc?.height,
786
1098
  };
787
- const newData = JSON.parse(JSON.stringify(data));
788
- const reversedData = newData.reverse();
789
- const lastResizeEvent = reversedData.find((item) => !!item.resize);
790
- const firstEventResize = lastResizeEvent?.resize?.[0];
1099
+ const listResizes = data.filter((item) => !!item.resize).flatMap((item) => item.resize);
1100
+ const lastResizeEvent = listResizes?.[listResizes.length - 1];
791
1101
  const resize = {
792
- width: firstEventResize?.data.width,
793
- height: firstEventResize?.data.height,
1102
+ width: lastResizeEvent?.data.width,
1103
+ height: lastResizeEvent?.data.height,
794
1104
  };
795
1105
  return {
796
1106
  doc: docSize,
797
1107
  resize: resize,
798
1108
  size: {
799
- width: resize.width ?? docSize.width,
800
- height: resize.height ?? docSize.height,
1109
+ width: resize.width || docSize.width,
1110
+ height: resize.height || docSize.height,
801
1111
  },
802
1112
  };
803
1113
  }
804
- function sort(a, b) {
805
- return a.time - b.time;
806
- }
807
1114
  function decodePayloads(payload) {
808
1115
  try {
809
1116
  return decode(payload);
@@ -1139,7 +1446,7 @@ class IframeStyleReplacer {
1139
1446
  doc;
1140
1447
  win;
1141
1448
  config;
1142
- regex = /([-.\d]+)(vh|svh|lvh|dvh|vw|svw|lvw|dvw)/gi;
1449
+ regex = /([-.\d]+)(vh|svh|lvh|dvh)/gi; //vw|svw|lvw|dvw
1143
1450
  constructor(iframe, config) {
1144
1451
  if (!iframe.contentDocument || !iframe.contentWindow) {
1145
1452
  throw new Error('Iframe document or window not accessible');
@@ -1417,48 +1724,6 @@ function initIframeHelperFixer(config) {
1417
1724
  return fixer;
1418
1725
  }
1419
1726
 
1420
- const useHeatmapViz = (props) => {
1421
- const viewId = props?.viewId || useViewId();
1422
- // Viz store
1423
- const isRenderViz = useHeatmapVizStore((state) => state.isRenderViz[viewId] ?? false);
1424
- const zoomRatio = useHeatmapVizStore((state) => state.zoomRatio[viewId] ?? 100);
1425
- const minZoomRatio = useHeatmapVizStore((state) => state.minZoomRatio[viewId] ?? 10);
1426
- const widthScale = useHeatmapVizStore((state) => state.scale[viewId] ?? 1);
1427
- const isScaledToFit = useHeatmapVizStore((state) => state.isScaledToFit[viewId] ?? false);
1428
- const setIsRenderViz = useHeatmapVizStore((state) => state.setIsRenderViz);
1429
- const setZoomRatio = useHeatmapVizStore((state) => state.setZoomRatio);
1430
- const setMinZoomRatio = useHeatmapVizStore((state) => state.setMinZoomRatio);
1431
- const setScale = useHeatmapVizStore((state) => state.setScale);
1432
- const setIsScaledToFit = useHeatmapVizStore((state) => state.setIsScaledToFit);
1433
- // Single store
1434
- const vizRef = useHeatmapSingleStore((state) => state.vizRef[viewId] ?? null);
1435
- const iframeHeight = useHeatmapSingleStore((state) => state.iframeHeight[viewId] ?? 0);
1436
- const wrapperHeight = useHeatmapSingleStore((state) => state.wrapperHeight[viewId] ?? 0);
1437
- const setVizRef = useHeatmapSingleStore((state) => state.setVizRef);
1438
- const setIframeHeight = useHeatmapSingleStore((state) => state.setIframeHeight);
1439
- const setWrapperHeight = useHeatmapSingleStore((state) => state.setWrapperHeight);
1440
- return {
1441
- // State
1442
- isRenderViz,
1443
- zoomRatio,
1444
- minZoomRatio,
1445
- widthScale,
1446
- isScaledToFit,
1447
- vizRef,
1448
- iframeHeight,
1449
- wrapperHeight,
1450
- // Setters (auto-inject viewId)
1451
- setIsRenderViz: (value) => setIsRenderViz(value, viewId),
1452
- setZoomRatio: (value) => setZoomRatio(value, viewId),
1453
- setMinZoomRatio: (value) => setMinZoomRatio(value, viewId),
1454
- setScale: (value) => setScale(value, viewId),
1455
- setIsScaledToFit: (value) => setIsScaledToFit(value, viewId),
1456
- setVizRef: (value) => setVizRef(value, viewId),
1457
- setIframeHeight: (value) => setIframeHeight(value, viewId),
1458
- setWrapperHeight: (value) => setWrapperHeight(value, viewId),
1459
- };
1460
- };
1461
-
1462
1727
  const scrollToElementIfNeeded = (visualRef, rect, scale) => {
1463
1728
  if (!visualRef.current)
1464
1729
  return;
@@ -1477,9 +1742,7 @@ const scrollToElementIfNeeded = (visualRef, rect, scale) => {
1477
1742
  });
1478
1743
  };
1479
1744
  const useClickedElement = ({ visualRef, getRect }) => {
1480
- const selectedElement = useHeatmapInteractionStore((state) => state.selectedElement);
1481
- const shouldShowCallout = useHeatmapInteractionStore((state) => state.shouldShowCallout);
1482
- const setShouldShowCallout = useHeatmapInteractionStore((state) => state.setShouldShowCallout);
1745
+ const { selectedElement, shouldShowCallout, setShouldShowCallout } = useHeatmapInteraction();
1483
1746
  const { widthScale } = useHeatmapViz();
1484
1747
  const { dataInfo } = useHeatmapData();
1485
1748
  const [clickedElement, setClickedElement] = useState(null);
@@ -1490,6 +1753,8 @@ const useClickedElement = ({ visualRef, getRect }) => {
1490
1753
  setShouldShowCallout(false);
1491
1754
  };
1492
1755
  useEffect(() => {
1756
+ if (selectedElement === clickedElement?.hash)
1757
+ return;
1493
1758
  if (!selectedElement || !dataInfo?.elementMapInfo) {
1494
1759
  reset();
1495
1760
  return;
@@ -1515,13 +1780,12 @@ const useClickedElement = ({ visualRef, getRect }) => {
1515
1780
  requestAnimationFrame(() => {
1516
1781
  setClickedElement(elementInfo);
1517
1782
  });
1518
- }, [selectedElement, dataInfo, getRect, visualRef, widthScale]);
1519
- return { clickedElement, showMissingElement, shouldShowCallout, setShouldShowCallout };
1783
+ }, [selectedElement, dataInfo, visualRef, widthScale]);
1784
+ return { clickedElement, showMissingElement, shouldShowCallout };
1520
1785
  };
1521
1786
 
1522
1787
  const useElementCalloutVisible = ({ visualRef, getRect }) => {
1523
- const selectedElement = useHeatmapInteractionStore((state) => state.selectedElement);
1524
- const setShouldShowCallout = useHeatmapInteractionStore((state) => state.setShouldShowCallout);
1788
+ const { selectedElement, setShouldShowCallout } = useHeatmapInteraction();
1525
1789
  const { widthScale } = useHeatmapViz();
1526
1790
  const { dataInfo } = useHeatmapData();
1527
1791
  useEffect(() => {
@@ -1547,26 +1811,26 @@ const useElementCalloutVisible = ({ visualRef, getRect }) => {
1547
1811
  window.removeEventListener('resize', handleUpdate);
1548
1812
  visualRef?.current?.removeEventListener('scroll', handleUpdate);
1549
1813
  };
1550
- }, [selectedElement, visualRef, getRect, widthScale, dataInfo, setShouldShowCallout]);
1814
+ }, [selectedElement, visualRef, widthScale, dataInfo]);
1551
1815
  return {};
1552
1816
  };
1553
1817
 
1554
- const useHeatmapEffects = ({ isVisible, isElementSidebarOpen, setShouldShowCallout, resetAll, }) => {
1555
- const selectedElement = useHeatmapInteractionStore((state) => state.selectedElement);
1818
+ const useHeatmapEffects = ({ isVisible }) => {
1819
+ useHeatmapInteraction();
1820
+ const resetAll = () => {
1821
+ // setShouldShowCallout(false);
1822
+ };
1556
1823
  // Reset khi ẩn
1557
1824
  useEffect(() => {
1558
- if (!isVisible)
1559
- resetAll();
1560
1825
  }, [isVisible, resetAll]);
1561
1826
  // Ẩn callout khi sidebar mở
1562
- useEffect(() => {
1563
- if (isElementSidebarOpen && selectedElement) {
1564
- setShouldShowCallout(false);
1565
- }
1566
- else if (!isElementSidebarOpen && selectedElement) {
1567
- setShouldShowCallout(true);
1568
- }
1569
- }, [isElementSidebarOpen, selectedElement, setShouldShowCallout]);
1827
+ // useEffect(() => {
1828
+ // if (isElementSidebarOpen && selectedElement) {
1829
+ // setShouldShowCallout(false);
1830
+ // } else if (!isElementSidebarOpen && selectedElement) {
1831
+ // setShouldShowCallout(true);
1832
+ // }
1833
+ // }, [isElementSidebarOpen, selectedElement]);
1570
1834
  };
1571
1835
 
1572
1836
  const useHeatmapElementPosition = ({ iframeRef, wrapperRef, visualizer }) => {
@@ -1722,9 +1986,7 @@ function HeatmapComponent() {
1722
1986
  */
1723
1987
 
1724
1988
  const useHoveredElement = ({ iframeRef, getRect }) => {
1725
- const hoveredElement = useHeatmapInteractionStore((state) => state.hoveredElement);
1726
- const setHoveredElement = useHeatmapInteractionStore((state) => state.setHoveredElement);
1727
- const onSelect = useHeatmapInteractionStore((state) => state.setSelectedElement);
1989
+ const { hoveredElement, setHoveredElement, setSelectedElement } = useHeatmapInteraction();
1728
1990
  const { widthScale } = useHeatmapViz();
1729
1991
  const { dataInfo } = useHeatmapData();
1730
1992
  const reset = useCallback(() => {
@@ -1739,7 +2001,15 @@ const useHoveredElement = ({ iframeRef, getRect }) => {
1739
2001
  return;
1740
2002
  }
1741
2003
  const iframe = iframeRef.current;
2004
+ if (!iframe) {
2005
+ reset();
2006
+ return;
2007
+ }
1742
2008
  const doc = iframe.contentDocument;
2009
+ if (!doc) {
2010
+ reset();
2011
+ return;
2012
+ }
1743
2013
  const iframeRect = iframe.getBoundingClientRect();
1744
2014
  const { x, y } = convertViewportToIframeCoords(event.clientX, event.clientY, iframeRect, widthScale);
1745
2015
  const targetElement = findTargetElement(doc, x, y, dataInfo);
@@ -1775,7 +2045,7 @@ const useHoveredElement = ({ iframeRef, getRect }) => {
1775
2045
  const handleClick = useCallback(() => {
1776
2046
  if (!hoveredElement?.hash)
1777
2047
  return;
1778
- onSelect(hoveredElement.hash);
2048
+ setSelectedElement(hoveredElement.hash);
1779
2049
  }, [hoveredElement?.hash]);
1780
2050
  return {
1781
2051
  hoveredElement,
@@ -1887,7 +2157,7 @@ function useVizLiveIframeMsg(options = {}) {
1887
2157
  }
1888
2158
 
1889
2159
  function useVizLiveRender() {
1890
- const { setIframeHeight, wrapperHeight, setIsRenderViz } = useHeatmapViz();
2160
+ const { setIframeHeight, wrapperHeight, setIsRenderViz, wrapperWidth } = useHeatmapViz();
1891
2161
  const contentWidth = useHeatmapConfigStore((state) => state.width);
1892
2162
  const htmlContent = useHeatmapLiveStore((state) => state.htmlContent);
1893
2163
  const { iframeRef, isReady } = useVizLiveIframeMsg();
@@ -1905,17 +2175,20 @@ function useVizLiveRender() {
1905
2175
  useEffect(() => {
1906
2176
  if (!isReady)
1907
2177
  return;
2178
+ if (!wrapperHeight)
2179
+ return;
1908
2180
  if (!iframeRef.current)
1909
2181
  return;
2182
+ console.log(`🚀 🐥 ~ useVizLiveRender ~ wrapperHeight:`, wrapperHeight);
1910
2183
  const iframe = iframeRef.current;
1911
2184
  if (!iframe || !htmlContent)
1912
2185
  return;
1913
2186
  setIsRenderViz(false);
1914
- reset(iframe, { width: contentWidth, height: wrapperHeight }, (height) => {
2187
+ reset(iframe, { width: wrapperWidth, height: wrapperHeight }, (height) => {
1915
2188
  height && setIframeHeight(height);
1916
2189
  setIsRenderViz(true);
1917
2190
  });
1918
- }, [isReady, contentWidth, wrapperHeight]);
2191
+ }, [isReady, contentWidth, wrapperHeight, wrapperWidth]);
1919
2192
  return {
1920
2193
  iframeRef,
1921
2194
  };
@@ -1936,12 +2209,17 @@ function reset(iframe, rect, onSuccess) {
1936
2209
 
1937
2210
  const useHeatmapRender = () => {
1938
2211
  const { data } = useHeatmapData();
1939
- const { vizRef, setVizRef, setIsRenderViz, setIframeHeight } = useHeatmapViz();
2212
+ const { vizRef, setVizRef, setIsRenderViz, setIframeHeight, wrapperHeight, wrapperWidth } = useHeatmapViz();
2213
+ console.log(`🚀 🐥 ~ useHeatmapRender ~ wrapperHeight:`, wrapperHeight);
1940
2214
  const iframeRef = useRef(null);
1941
2215
  const renderHeatmap = useCallback(async (payloads) => {
1942
2216
  if (!payloads || payloads.length === 0)
1943
2217
  return;
1944
- const visualizer = vizRef || new Visualizer();
2218
+ let visualizer = vizRef;
2219
+ if (!visualizer) {
2220
+ visualizer = new Visualizer();
2221
+ setVizRef(visualizer);
2222
+ }
1945
2223
  setIsRenderViz(false);
1946
2224
  const iframe = iframeRef.current;
1947
2225
  if (!iframe?.contentWindow)
@@ -1950,7 +2228,6 @@ const useHeatmapRender = () => {
1950
2228
  initIframe(iframe, payloads, (height) => {
1951
2229
  height && setIframeHeight(height);
1952
2230
  setIsRenderViz(true);
1953
- setVizRef(visualizer);
1954
2231
  });
1955
2232
  }, []);
1956
2233
  useEffect(() => {
@@ -1965,7 +2242,9 @@ const useHeatmapRender = () => {
1965
2242
  iframeRef,
1966
2243
  };
1967
2244
  };
1968
- function initIframe(iframe, payloads, onSuccess) {
2245
+ function initIframe(iframe,
2246
+ // size: { width: number; height: number },
2247
+ payloads, onSuccess) {
1969
2248
  const { size } = findLastSizeOfDom(payloads);
1970
2249
  const docWidth = size.width ?? 0;
1971
2250
  const docHeight = size.height ?? 0;
@@ -2164,7 +2443,8 @@ const useContentDimensions = ({ iframeRef, }) => {
2164
2443
  };
2165
2444
 
2166
2445
  const useObserveIframeHeight = (props) => {
2167
- const { iframeRef, setIframeHeight, isRenderViz } = props;
2446
+ const { iframeRef, isRenderViz } = props;
2447
+ const { setIframeHeight } = useHeatmapViz();
2168
2448
  const resizeObserverRef = useRef(null);
2169
2449
  const mutationObserverRef = useRef(null);
2170
2450
  const debounceTimerRef = useRef(null);
@@ -2172,7 +2452,7 @@ const useObserveIframeHeight = (props) => {
2172
2452
  const animationFrameRef = useRef(null);
2173
2453
  const updateIframeHeight = useCallback(() => {
2174
2454
  const iframe = iframeRef.current;
2175
- if (!iframe || !setIframeHeight)
2455
+ if (!iframe)
2176
2456
  return;
2177
2457
  try {
2178
2458
  const iframeDocument = iframe.contentDocument;
@@ -2180,7 +2460,7 @@ const useObserveIframeHeight = (props) => {
2180
2460
  const iframeDocumentElement = iframeDocument?.documentElement;
2181
2461
  if (!iframeBody || !iframeDocumentElement)
2182
2462
  return;
2183
- iframe.style.height = 'auto';
2463
+ // iframe.style.height = 'auto'; // TODO: check if this is needed
2184
2464
  requestAnimationFrame(() => {
2185
2465
  const bodyHeight = Math.max(iframeBody.scrollHeight, iframeBody.offsetHeight, iframeBody.clientHeight);
2186
2466
  const documentHeight = Math.max(iframeDocumentElement.scrollHeight, iframeDocumentElement.offsetHeight, iframeDocumentElement.clientHeight);
@@ -2196,7 +2476,7 @@ const useObserveIframeHeight = (props) => {
2196
2476
  catch (error) {
2197
2477
  console.warn('Cannot measure iframe content:', error);
2198
2478
  }
2199
- }, [iframeRef, setIframeHeight]);
2479
+ }, [iframeRef]);
2200
2480
  const debouncedUpdate = useCallback(() => {
2201
2481
  // Cancel pending updates
2202
2482
  if (debounceTimerRef.current) {
@@ -2298,7 +2578,7 @@ const useScaleCalculation = (props) => {
2298
2578
  const availableWidth = containerWidth - HEATMAP_CONFIG['padding'] * 2;
2299
2579
  const widthScale = Math.min(availableWidth / contentWidth, 1);
2300
2580
  // 2. Calculate available height
2301
- const toolbarHeight = HEATMAP_CONFIG['heightToolbar'] ;
2581
+ const toolbarHeight = HEATMAP_CONFIG['heightToolbar'] || 0;
2302
2582
  const paddingTotal = HEATMAP_CONFIG['padding'] * 2;
2303
2583
  const availableHeight = containerHeight - toolbarHeight - paddingTotal; // 10px buffer to avoid scroll bar
2304
2584
  // 3. Calculate minZoomRatio (zoom ratio minimum to fit iframe in container)
@@ -2354,13 +2634,13 @@ const useScrollSync = ({ widthScale, iframeRef, }) => {
2354
2634
  };
2355
2635
 
2356
2636
  const useHeatmapScale = (props) => {
2357
- const { wrapperRef, iframeRef, iframeHeight, setIframeHeight, isRenderViz } = props;
2637
+ const { wrapperRef, iframeRef, iframeHeight, isRenderViz } = props;
2358
2638
  // 1. Observe container dimensions
2359
2639
  const { containerWidth, containerHeight } = useContainerDimensions({ wrapperRef });
2360
2640
  // 2. Get content dimensions from config
2361
2641
  const { contentWidth } = useContentDimensions({ iframeRef });
2362
2642
  // 3. Observe iframe height (now reacts to width changes)
2363
- useObserveIframeHeight({ iframeRef, setIframeHeight, isRenderViz });
2643
+ useObserveIframeHeight({ iframeRef, isRenderViz });
2364
2644
  // 4. Calculate scale
2365
2645
  const { widthScale } = useScaleCalculation({
2366
2646
  containerWidth,
@@ -2385,15 +2665,17 @@ const useWrapperRefHeight = (props) => {
2385
2665
  const { isActive, wrapperRef } = props;
2386
2666
  const resizeObserverRef = useRef(null);
2387
2667
  const mutationObserverRef = useRef(null);
2388
- const { isRenderViz, setWrapperHeight } = useHeatmapViz();
2668
+ const { setWrapperHeight, setWrapperWidth } = useHeatmapViz();
2389
2669
  const updateWrapperHeight = useCallback(() => {
2390
2670
  const wrapper = wrapperRef.current;
2391
2671
  if (!wrapper)
2392
2672
  return;
2393
2673
  try {
2394
2674
  const wrapperHeight = wrapper.offsetHeight;
2675
+ const wrapperWidth = wrapper.offsetWidth;
2395
2676
  if (wrapperHeight > 0) {
2396
2677
  setWrapperHeight(wrapperHeight);
2678
+ setWrapperWidth(wrapperWidth);
2397
2679
  }
2398
2680
  }
2399
2681
  catch (error) {
@@ -2402,7 +2684,7 @@ const useWrapperRefHeight = (props) => {
2402
2684
  }, [wrapperRef]);
2403
2685
  useEffect(() => {
2404
2686
  const wrapper = wrapperRef.current;
2405
- if (!wrapper || !isRenderViz)
2687
+ if (!wrapper)
2406
2688
  return;
2407
2689
  const setupObservers = () => {
2408
2690
  try {
@@ -2432,6 +2714,7 @@ const useWrapperRefHeight = (props) => {
2432
2714
  updateWrapperHeight();
2433
2715
  }
2434
2716
  catch (error) {
2717
+ console.log(`🚀 🐥 ~ setupObservers ~ error:`, error);
2435
2718
  console.warn('Cannot access wrapper content:', error);
2436
2719
  }
2437
2720
  };
@@ -2446,7 +2729,7 @@ const useWrapperRefHeight = (props) => {
2446
2729
  mutationObserverRef.current.disconnect();
2447
2730
  }
2448
2731
  };
2449
- }, [wrapperRef, isRenderViz, updateWrapperHeight, isActive]);
2732
+ }, [wrapperRef, updateWrapperHeight, isActive]);
2450
2733
  return {};
2451
2734
  };
2452
2735
 
@@ -2580,184 +2863,513 @@ const getScrollGradientColor = (normalized) => {
2580
2863
  return `rgb(${r}, ${g}, ${b})`;
2581
2864
  };
2582
2865
 
2583
- const CompareViewContext = createContext(null);
2584
- /**
2585
- * Hook to safely access compare view context (returns null if not in compare mode)
2586
- */
2587
- const useCompareViewContextSafe = () => {
2588
- return useContext(CompareViewContext);
2589
- };
2590
-
2591
- /**
2592
- * Hook that returns data from compare view context if in compare mode,
2593
- * otherwise returns data from global store
2594
- */
2595
- const useCompareAwareData = () => {
2596
- const compareContext = useCompareViewContextSafe();
2597
- // Global store values
2598
- const globalData = useHeatmapDataStore((state) => state.data);
2599
- const globalClickmap = useHeatmapDataStore((state) => state.clickmap);
2600
- const globalScrollmap = useHeatmapDataStore((state) => state.scrollmap);
2601
- const globalDataInfo = useHeatmapDataStore((state) => state.dataInfo);
2602
- // If in compare mode, use view context
2603
- if (compareContext) {
2866
+ class PerformanceLogger {
2867
+ static instance;
2868
+ metrics = [];
2869
+ sessionId;
2870
+ sessionStartTime;
2871
+ config;
2872
+ constructor() {
2873
+ this.sessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
2874
+ this.sessionStartTime = Date.now();
2875
+ this.config = {
2876
+ enabled: false,
2877
+ logToConsole: false,
2878
+ logLevel: 'normal',
2879
+ thresholds: {
2880
+ slowRenderMs: 16, // > 16ms = slower than 60fps
2881
+ slowHookMs: 5,
2882
+ excessiveRenderCount: 10,
2883
+ },
2884
+ };
2885
+ }
2886
+ static getInstance() {
2887
+ if (!PerformanceLogger.instance) {
2888
+ PerformanceLogger.instance = new PerformanceLogger();
2889
+ }
2890
+ return PerformanceLogger.instance;
2891
+ }
2892
+ configure(config) {
2893
+ this.config = { ...this.config, ...config };
2894
+ if (this.config.enabled && this.config.logToConsole) {
2895
+ console.log('[Performance Monitor] Enabled', {
2896
+ sessionId: this.sessionId,
2897
+ config: this.config,
2898
+ });
2899
+ }
2900
+ }
2901
+ log(metric) {
2902
+ if (!this.config.enabled)
2903
+ return;
2904
+ this.metrics.push(metric);
2905
+ // Log to console based on level
2906
+ if (this.config.logToConsole) {
2907
+ this.logToConsole(metric);
2908
+ }
2909
+ // Send to external logger if configured
2910
+ if (this.config.externalLogger) {
2911
+ this.config.externalLogger(metric);
2912
+ }
2913
+ // Check thresholds and warn
2914
+ this.checkThresholds(metric);
2915
+ }
2916
+ logToConsole(metric) {
2917
+ const { logLevel } = this.config;
2918
+ if (logLevel === 'minimal') {
2919
+ // Only log warnings
2920
+ return;
2921
+ }
2922
+ const style = this.getConsoleStyle(metric.type);
2923
+ const label = `[${metric.type.toUpperCase()}] ${metric.name}`;
2924
+ if (logLevel === 'verbose') {
2925
+ console.log(`%c${label}`, style, metric);
2926
+ }
2927
+ else {
2928
+ // Normal: Log compact info
2929
+ const info = { name: metric.name };
2930
+ if (metric.duration)
2931
+ info.duration = `${metric.duration.toFixed(2)}ms`;
2932
+ if ('viewId' in metric && metric.viewId)
2933
+ info.viewId = metric.viewId;
2934
+ if ('renderCount' in metric)
2935
+ info.renderCount = metric.renderCount;
2936
+ console.log(`%c${label}`, style, info);
2937
+ }
2938
+ }
2939
+ getConsoleStyle(type) {
2940
+ const styles = {
2941
+ render: 'color: #61dafb; font-weight: bold',
2942
+ hook: 'color: #ffa500; font-weight: bold',
2943
+ store: 'color: #9c27b0; font-weight: bold',
2944
+ function: 'color: #4caf50; font-weight: bold',
2945
+ };
2946
+ return styles[type] || '';
2947
+ }
2948
+ checkThresholds(metric) {
2949
+ const { thresholds } = this.config;
2950
+ // Check slow render
2951
+ if (metric.type === 'render' && metric.duration && metric.duration > thresholds.slowRenderMs) {
2952
+ console.warn(`[Performance] Slow render detected: ${metric.name} took ${metric.duration.toFixed(2)}ms`, metric);
2953
+ }
2954
+ // Check slow hook
2955
+ if (metric.type === 'hook' && metric.duration && metric.duration > thresholds.slowHookMs) {
2956
+ console.warn(`[Performance] Slow hook detected: ${metric.name} took ${metric.duration.toFixed(2)}ms`, metric);
2957
+ }
2958
+ // Check excessive renders
2959
+ if (metric.type === 'render' && 'renderCount' in metric) {
2960
+ const renderMetric = metric;
2961
+ if (renderMetric.renderCount > thresholds.excessiveRenderCount) {
2962
+ console.warn(`[Performance] Excessive renders: ${metric.name} has rendered ${renderMetric.renderCount} times`, metric);
2963
+ }
2964
+ }
2965
+ }
2966
+ generateReport() {
2967
+ const now = Date.now();
2968
+ const duration = now - this.sessionStartTime;
2969
+ // Calculate summary
2970
+ const renderMetrics = this.metrics.filter((m) => m.type === 'render');
2971
+ const hookMetrics = this.metrics.filter((m) => m.type === 'hook');
2972
+ const storeMetrics = this.metrics.filter((m) => m.type === 'store');
2973
+ const totalRenders = renderMetrics.length;
2974
+ const totalHookCalls = hookMetrics.length;
2975
+ const totalStoreUpdates = storeMetrics.length;
2976
+ const renderDurations = renderMetrics
2977
+ .filter((m) => m.duration)
2978
+ .map((m) => m.duration);
2979
+ const averageRenderTime = renderDurations.length > 0
2980
+ ? renderDurations.reduce((a, b) => a + b, 0) / renderDurations.length
2981
+ : 0;
2982
+ // View-specific metrics
2983
+ const viewMetrics = {};
2984
+ this.metrics.forEach((metric) => {
2985
+ const viewId = 'viewId' in metric && metric.viewId ? metric.viewId : DEFAULT_VIEW_ID;
2986
+ if (!viewMetrics[viewId]) {
2987
+ viewMetrics[viewId] = { renders: 0, hookCalls: 0, storeUpdates: 0 };
2988
+ }
2989
+ if (metric.type === 'render')
2990
+ viewMetrics[viewId].renders++;
2991
+ if (metric.type === 'hook')
2992
+ viewMetrics[viewId].hookCalls++;
2993
+ if (metric.type === 'store')
2994
+ viewMetrics[viewId].storeUpdates++;
2995
+ });
2996
+ // Find warnings
2997
+ const renderCounts = new Map();
2998
+ renderMetrics.forEach((m) => {
2999
+ const key = `${m.name}-${'viewId' in m ? m.viewId : DEFAULT_VIEW_ID}`;
3000
+ renderCounts.set(key, (renderCounts.get(key) || 0) + 1);
3001
+ });
3002
+ const excessiveRenders = Array.from(renderCounts.entries())
3003
+ .filter(([, count]) => count > this.config.thresholds.excessiveRenderCount)
3004
+ .map(([key, count]) => {
3005
+ const [component, viewId] = key.split('-');
3006
+ return { component, count, viewId: viewId !== DEFAULT_VIEW_ID ? viewId : undefined };
3007
+ });
3008
+ const slowOperations = this.metrics
3009
+ .filter((m) => m.duration &&
3010
+ ((m.type === 'render' && m.duration > this.config.thresholds.slowRenderMs) ||
3011
+ (m.type === 'hook' && m.duration > this.config.thresholds.slowHookMs)))
3012
+ .map((m) => ({
3013
+ name: m.name,
3014
+ duration: m.duration,
3015
+ type: m.type,
3016
+ }));
2604
3017
  return {
2605
- data: compareContext.view.data,
2606
- clickmap: compareContext.view.clickmap,
2607
- scrollmap: compareContext.view.scrollmap,
2608
- dataInfo: compareContext.view.dataInfo,
3018
+ session: {
3019
+ id: this.sessionId,
3020
+ startTime: this.sessionStartTime,
3021
+ endTime: now,
3022
+ duration,
3023
+ },
3024
+ summary: {
3025
+ totalRenders,
3026
+ totalHookCalls,
3027
+ totalStoreUpdates,
3028
+ averageRenderTime,
3029
+ viewMetrics,
3030
+ },
3031
+ metrics: this.metrics,
3032
+ warnings: {
3033
+ excessiveRenders,
3034
+ slowOperations,
3035
+ },
2609
3036
  };
2610
3037
  }
2611
- // Otherwise use global store
2612
- return {
2613
- data: globalData,
2614
- clickmap: globalClickmap,
2615
- scrollmap: globalScrollmap,
2616
- dataInfo: globalDataInfo,
2617
- };
2618
- };
3038
+ clearMetrics() {
3039
+ this.metrics = [];
3040
+ this.sessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
3041
+ this.sessionStartTime = Date.now();
3042
+ }
3043
+ getMetrics() {
3044
+ return [...this.metrics];
3045
+ }
3046
+ isEnabled() {
3047
+ return this.config.enabled;
3048
+ }
3049
+ }
3050
+ const performanceLogger = PerformanceLogger.getInstance();
3051
+
2619
3052
  /**
2620
- * Hook that returns setters for data
2621
- * In compare mode, updates the view context
2622
- * In single/live mode, updates the global store
3053
+ * Hook to track render count of a component
3054
+ * @param componentName - Name of the component
3055
+ * @param viewId - Optional viewId for compare mode
2623
3056
  */
2624
- const useCompareAwareDataSetters = () => {
2625
- const compareContext = useCompareViewContextSafe();
2626
- const setDataGlobal = useHeatmapDataStore((state) => state.setData);
2627
- const setClickmapGlobal = useHeatmapDataStore((state) => state.setClickmap);
2628
- const setScrollmapGlobal = useHeatmapDataStore((state) => state.setScrollmap);
2629
- const setDataInfoGlobal = useHeatmapDataStore((state) => state.setDataInfo);
2630
- if (compareContext) {
2631
- return {
2632
- setData: (data) => compareContext.updateView({ data }),
2633
- setClickmap: (clickmap) => compareContext.updateView({ clickmap }),
2634
- setScrollmap: (scrollmap) => compareContext.updateView({ scrollmap }),
2635
- setDataInfo: (dataInfo) => compareContext.updateView({ dataInfo }),
3057
+ function useRenderCount(componentName, viewId) {
3058
+ const renderCount = useRef(0);
3059
+ const startTime = useRef(0);
3060
+ // Increment before render
3061
+ renderCount.current += 1;
3062
+ startTime.current = performance.now();
3063
+ useEffect(() => {
3064
+ const duration = performance.now() - startTime.current;
3065
+ const metric = {
3066
+ id: `render-${componentName}-${Date.now()}`,
3067
+ type: 'render',
3068
+ name: componentName,
3069
+ componentName,
3070
+ renderCount: renderCount.current,
3071
+ timestamp: Date.now(),
3072
+ duration,
3073
+ viewId,
2636
3074
  };
2637
- }
2638
- return {
2639
- setData: setDataGlobal,
2640
- setClickmap: setClickmapGlobal,
2641
- setScrollmap: setScrollmapGlobal,
2642
- setDataInfo: setDataInfoGlobal,
3075
+ performanceLogger.log(metric);
3076
+ });
3077
+ return renderCount.current;
3078
+ }
3079
+ /**
3080
+ * Hook to detect why a component re-rendered (which props changed)
3081
+ * @param componentName - Name of the component
3082
+ * @param props - Props object to track
3083
+ * @param viewId - Optional viewId
3084
+ */
3085
+ function useWhyDidYouUpdate(componentName, props, viewId) {
3086
+ const previousProps = useRef();
3087
+ const renderCount = useRef(0);
3088
+ const startTime = useRef(0);
3089
+ renderCount.current += 1;
3090
+ startTime.current = performance.now();
3091
+ useEffect(() => {
3092
+ if (previousProps.current) {
3093
+ const duration = performance.now() - startTime.current;
3094
+ const allKeys = Object.keys({ ...previousProps.current, ...props });
3095
+ const changedProps = [];
3096
+ allKeys.forEach((key) => {
3097
+ if (previousProps.current[key] !== props[key]) {
3098
+ changedProps.push(key);
3099
+ }
3100
+ });
3101
+ if (changedProps.length > 0) {
3102
+ const metric = {
3103
+ id: `render-${componentName}-${Date.now()}`,
3104
+ type: 'render',
3105
+ name: componentName,
3106
+ componentName,
3107
+ renderCount: renderCount.current,
3108
+ timestamp: Date.now(),
3109
+ duration,
3110
+ viewId,
3111
+ reason: 'Props changed',
3112
+ propsChanged: changedProps,
3113
+ metadata: {
3114
+ changes: changedProps.reduce((acc, key) => {
3115
+ acc[key] = {
3116
+ from: previousProps.current[key],
3117
+ to: props[key],
3118
+ };
3119
+ return acc;
3120
+ }, {}),
3121
+ },
3122
+ };
3123
+ performanceLogger.log(metric);
3124
+ }
3125
+ }
3126
+ previousProps.current = props;
3127
+ });
3128
+ }
3129
+ /**
3130
+ * Hook to measure execution time of a function
3131
+ * @param functionName - Name of the function
3132
+ * @param fn - Function to measure
3133
+ * @param viewId - Optional viewId
3134
+ */
3135
+ function useMeasureFunction(functionName, fn, viewId) {
3136
+ const measuredFn = ((...args) => {
3137
+ const startTime = performance.now();
3138
+ const result = fn(...args);
3139
+ const duration = performance.now() - startTime;
3140
+ performanceLogger.log({
3141
+ id: `function-${functionName}-${Date.now()}`,
3142
+ type: 'function',
3143
+ name: functionName,
3144
+ functionName,
3145
+ timestamp: Date.now(),
3146
+ duration,
3147
+ viewId,
3148
+ metadata: {
3149
+ args: args.length,
3150
+ },
3151
+ });
3152
+ return result;
3153
+ });
3154
+ return measuredFn;
3155
+ }
3156
+ /**
3157
+ * Hook to track when a hook is called
3158
+ * @param hookName - Name of the hook
3159
+ * @param viewId - Optional viewId
3160
+ * @param storeSlice - Optional store slice being accessed
3161
+ */
3162
+ function useTrackHookCall(hookName, viewId, storeSlice) {
3163
+ const startTime = useRef(0);
3164
+ startTime.current = performance.now();
3165
+ useEffect(() => {
3166
+ const duration = performance.now() - startTime.current;
3167
+ performanceLogger.log({
3168
+ id: `hook-${hookName}-${Date.now()}`,
3169
+ type: 'hook',
3170
+ name: hookName,
3171
+ hookName,
3172
+ timestamp: Date.now(),
3173
+ duration,
3174
+ viewId,
3175
+ storeSlice,
3176
+ });
3177
+ });
3178
+ }
3179
+
3180
+ /**
3181
+ * HOC to track component performance
3182
+ * @param Component - Component to wrap
3183
+ * @param options - Tracking options
3184
+ */
3185
+ function withPerformanceTracking(Component, options = {}) {
3186
+ const { trackProps = true, componentName, viewIdProp = 'viewId' } = options;
3187
+ const WrappedComponent = (props) => {
3188
+ const name = componentName || Component.displayName || Component.name || 'Unknown';
3189
+ const viewId = viewIdProp in props ? props[viewIdProp] : undefined;
3190
+ if (trackProps) {
3191
+ useWhyDidYouUpdate(name, props, viewId);
3192
+ }
3193
+ return jsx(Component, { ...props });
2643
3194
  };
2644
- };
3195
+ WrappedComponent.displayName = `withPerformanceTracking(${componentName || Component.displayName || Component.name})`;
3196
+ return WrappedComponent;
3197
+ }
2645
3198
 
2646
3199
  /**
2647
- * Hook that returns config from compare view context if in compare mode,
2648
- * otherwise returns config from global store
3200
+ * Middleware để track store updates
2649
3201
  */
2650
- const useCompareAwareConfig = () => {
2651
- const compareContext = useCompareViewContextSafe();
2652
- // Global store values
2653
- const globalHeatmapType = useHeatmapConfigStore((state) => state.heatmapType);
2654
- const globalClickType = useHeatmapConfigStore((state) => state.clickType);
2655
- const globalScrollType = useHeatmapConfigStore((state) => state.scrollType);
2656
- // If in compare mode, use view context
2657
- if (compareContext) {
2658
- return {
2659
- heatmapType: compareContext.view.heatmapType,
2660
- clickType: compareContext.view.clickType,
2661
- scrollType: compareContext.view.scrollType,
3202
+ function createStorePerformanceTracker(storeName) {
3203
+ return (config) => (set, get, api) => {
3204
+ const wrappedSet = (partial, replace) => {
3205
+ const startTime = performance.now();
3206
+ const prevState = get();
3207
+ // Call original set
3208
+ set(partial, replace);
3209
+ const duration = performance.now() - startTime;
3210
+ const nextState = get();
3211
+ // Detect which viewIds were affected
3212
+ const affectedViews = new Set();
3213
+ // Check Record<string, any> properties for viewId keys
3214
+ Object.keys(nextState).forEach((key) => {
3215
+ if (typeof prevState[key] === 'object' &&
3216
+ typeof nextState[key] === 'object' &&
3217
+ prevState[key] !== nextState[key]) {
3218
+ // Check if this is a Record<viewId, value> structure
3219
+ const prevKeys = Object.keys(prevState[key] || {});
3220
+ const nextKeys = Object.keys(nextState[key] || {});
3221
+ const allKeys = new Set([...prevKeys, ...nextKeys]);
3222
+ allKeys.forEach((viewId) => {
3223
+ if (prevState[key]?.[viewId] !== nextState[key]?.[viewId]) {
3224
+ affectedViews.add(viewId);
3225
+ }
3226
+ });
3227
+ }
3228
+ });
3229
+ const metric = {
3230
+ id: `store-${storeName}-${Date.now()}`,
3231
+ type: 'store',
3232
+ name: `${storeName} update`,
3233
+ storeName,
3234
+ action: typeof partial === 'function' ? 'function update' : 'direct update',
3235
+ timestamp: Date.now(),
3236
+ duration,
3237
+ affectedViews: Array.from(affectedViews),
3238
+ metadata: {
3239
+ stateKeys: Object.keys(nextState),
3240
+ },
3241
+ };
3242
+ performanceLogger.log(metric);
2662
3243
  };
2663
- }
2664
- // Otherwise use global store
2665
- return {
2666
- heatmapType: globalHeatmapType,
2667
- clickType: globalClickType,
2668
- scrollType: globalScrollType,
3244
+ return config(wrappedSet, get, api);
2669
3245
  };
2670
- };
3246
+ }
2671
3247
  /**
2672
- * Hook that returns config setters
2673
- * In compare mode, updates the view context
2674
- * In single/live mode, updates the global store
3248
+ * Track specific store action
2675
3249
  */
2676
- const useCompareAwareConfigSetters = () => {
2677
- const compareContext = useCompareViewContextSafe();
2678
- const setHeatmapTypeGlobal = useHeatmapConfigStore((state) => state.setHeatmapType);
2679
- const setClickTypeGlobal = useHeatmapConfigStore((state) => state.setClickType);
2680
- const setScrollTypeGlobal = useHeatmapConfigStore((state) => state.setScrollType);
2681
- if (compareContext) {
2682
- return {
2683
- setHeatmapType: (heatmapType) => compareContext.updateView({ heatmapType }),
2684
- setClickType: (clickType) => compareContext.updateView({ clickType }),
2685
- setScrollType: (scrollType) => compareContext.updateView({ scrollType }),
2686
- };
2687
- }
2688
- return {
2689
- setHeatmapType: setHeatmapTypeGlobal,
2690
- setClickType: setClickTypeGlobal,
2691
- setScrollType: setScrollTypeGlobal,
3250
+ function trackStoreAction(storeName, action, viewId, metadata) {
3251
+ const metric = {
3252
+ id: `store-${storeName}-${action}-${Date.now()}`,
3253
+ type: 'store',
3254
+ name: `${storeName}.${action}`,
3255
+ storeName,
3256
+ action,
3257
+ timestamp: Date.now(),
3258
+ viewId,
3259
+ metadata,
2692
3260
  };
2693
- };
3261
+ performanceLogger.log(metric);
3262
+ }
2694
3263
 
2695
3264
  /**
2696
- * Hook that returns viz state from compare view context if in compare mode,
2697
- * otherwise returns viz state from global store
3265
+ * Get performance report as JSON string
2698
3266
  */
2699
- const useCompareAwareViz = () => {
2700
- const compareContext = useCompareViewContextSafe();
2701
- // Global store values
2702
- const globalZoomRatio = useHeatmapVizStore((state) => state.zoomRatio);
2703
- const globalScale = useHeatmapVizStore((state) => state.scale);
2704
- const globalIsScaledToFit = useHeatmapVizStore((state) => state.isScaledToFit);
2705
- const globalIsRenderViz = useHeatmapVizStore((state) => state.isRenderViz);
2706
- const globalIframeHeight = useHeatmapSingleStore((state) => state.iframeHeight);
2707
- const globalVizRef = useHeatmapSingleStore((state) => state.vizRef);
2708
- // If in compare mode, use view context
2709
- if (compareContext) {
2710
- return {
2711
- zoomRatio: compareContext.view.zoomRatio,
2712
- scale: compareContext.view.scale,
2713
- isScaledToFit: compareContext.view.isScaledToFit,
2714
- isRenderViz: compareContext.view.isRenderViz,
2715
- iframeHeight: compareContext.view.iframeHeight,
2716
- vizRef: compareContext.view.vizRef,
2717
- };
3267
+ function getPerformanceReportJSON() {
3268
+ const report = performanceLogger.generateReport();
3269
+ return JSON.stringify(report, null, 2);
3270
+ }
3271
+ /**
3272
+ * Download performance report as JSON file
3273
+ */
3274
+ function downloadPerformanceReport(filename = 'heatmap-performance-report.json') {
3275
+ const report = performanceLogger.generateReport();
3276
+ const blob = new Blob([JSON.stringify(report, null, 2)], { type: 'application/json' });
3277
+ const url = URL.createObjectURL(blob);
3278
+ const link = document.createElement('a');
3279
+ link.href = url;
3280
+ link.download = filename;
3281
+ link.click();
3282
+ URL.revokeObjectURL(url);
3283
+ }
3284
+ /**
3285
+ * Send performance report to external endpoint
3286
+ */
3287
+ async function sendPerformanceReport(endpoint) {
3288
+ const report = performanceLogger.generateReport();
3289
+ try {
3290
+ const response = await fetch(endpoint, {
3291
+ method: 'POST',
3292
+ headers: {
3293
+ 'Content-Type': 'application/json',
3294
+ },
3295
+ body: JSON.stringify(report),
3296
+ });
3297
+ if (!response.ok) {
3298
+ throw new Error(`Failed to send report: ${response.statusText}`);
3299
+ }
3300
+ }
3301
+ catch (error) {
3302
+ console.error('[Performance] Failed to send report:', error);
3303
+ throw error;
2718
3304
  }
2719
- // Otherwise use global store
3305
+ }
3306
+ /**
3307
+ * Print performance summary to console
3308
+ */
3309
+ function printPerformanceSummary() {
3310
+ const report = performanceLogger.generateReport();
3311
+ console.group('📊 Performance Summary');
3312
+ console.log('Session:', report.session);
3313
+ console.log('Total Renders:', report.summary.totalRenders);
3314
+ console.log('Total Hook Calls:', report.summary.totalHookCalls);
3315
+ console.log('Total Store Updates:', report.summary.totalStoreUpdates);
3316
+ console.log('Average Render Time:', `${report.summary.averageRenderTime.toFixed(2)}ms`);
3317
+ console.log('View Metrics:', report.summary.viewMetrics);
3318
+ if (report.warnings.excessiveRenders.length > 0) {
3319
+ console.group('⚠️ Excessive Renders');
3320
+ report.warnings.excessiveRenders.forEach((warning) => {
3321
+ console.warn(`${warning.component}${warning.viewId ? ` (${warning.viewId})` : ''}: ${warning.count} renders`);
3322
+ });
3323
+ console.groupEnd();
3324
+ }
3325
+ if (report.warnings.slowOperations.length > 0) {
3326
+ console.group('🐌 Slow Operations');
3327
+ report.warnings.slowOperations.forEach((warning) => {
3328
+ console.warn(`${warning.name} (${warning.type}): ${warning.duration.toFixed(2)}ms`);
3329
+ });
3330
+ console.groupEnd();
3331
+ }
3332
+ console.groupEnd();
3333
+ }
3334
+ /**
3335
+ * Get performance metrics filtered by viewId
3336
+ */
3337
+ function getMetricsByViewId(viewId) {
3338
+ const allMetrics = performanceLogger.getMetrics();
3339
+ const filteredMetrics = allMetrics.filter((m) => {
3340
+ return 'viewId' in m && m.viewId === viewId;
3341
+ });
3342
+ const report = performanceLogger.generateReport();
2720
3343
  return {
2721
- zoomRatio: globalZoomRatio,
2722
- scale: globalScale,
2723
- isScaledToFit: globalIsScaledToFit,
2724
- isRenderViz: globalIsRenderViz,
2725
- iframeHeight: globalIframeHeight,
2726
- vizRef: globalVizRef,
3344
+ ...report,
3345
+ metrics: filteredMetrics,
3346
+ summary: {
3347
+ ...report.summary,
3348
+ totalRenders: filteredMetrics.filter((m) => m.type === 'render').length,
3349
+ totalHookCalls: filteredMetrics.filter((m) => m.type === 'hook').length,
3350
+ totalStoreUpdates: filteredMetrics.filter((m) => m.type === 'store').length,
3351
+ averageRenderTime: 0, // Recalculate if needed
3352
+ viewMetrics: { [viewId]: report.summary.viewMetrics[viewId] || { renders: 0, hookCalls: 0, storeUpdates: 0 } },
3353
+ },
2727
3354
  };
2728
- };
3355
+ }
2729
3356
  /**
2730
- * Hook that returns viz setters
2731
- * In compare mode, updates the view context
2732
- * In single/live mode, updates the global store
3357
+ * Compare performance between two viewIds
2733
3358
  */
2734
- const useCompareAwareVizSetters = () => {
2735
- const compareContext = useCompareViewContextSafe();
2736
- const setZoomRatioGlobal = useHeatmapVizStore((state) => state.setZoomRatio);
2737
- const setScaleGlobal = useHeatmapVizStore((state) => state.setScale);
2738
- const setIsScaledToFitGlobal = useHeatmapVizStore((state) => state.setIsScaledToFit);
2739
- const setIsRenderVizGlobal = useHeatmapVizStore((state) => state.setIsRenderViz);
2740
- const setIframeHeightGlobal = useHeatmapSingleStore((state) => state.setIframeHeight);
2741
- const setVizRefGlobal = useHeatmapSingleStore((state) => state.setVizRef);
2742
- if (compareContext) {
2743
- return {
2744
- setZoomRatio: (zoomRatio) => compareContext.updateView({ zoomRatio }),
2745
- setScale: (scale) => compareContext.updateView({ scale }),
2746
- setIsScaledToFit: (isScaledToFit) => compareContext.updateView({ isScaledToFit }),
2747
- setIsRenderViz: (isRenderViz) => compareContext.updateView({ isRenderViz }),
2748
- setIframeHeight: (iframeHeight) => compareContext.updateView({ iframeHeight }),
2749
- setVizRef: (vizRef) => compareContext.updateView({ vizRef }),
2750
- };
2751
- }
3359
+ function compareViewPerformance(viewId1, viewId2) {
3360
+ const report = performanceLogger.generateReport();
3361
+ const view1Metrics = report.summary.viewMetrics[viewId1] || { renders: 0, hookCalls: 0, storeUpdates: 0 };
3362
+ const view2Metrics = report.summary.viewMetrics[viewId2] || { renders: 0, hookCalls: 0, storeUpdates: 0 };
2752
3363
  return {
2753
- setZoomRatio: setZoomRatioGlobal,
2754
- setScale: setScaleGlobal,
2755
- setIsScaledToFit: setIsScaledToFitGlobal,
2756
- setIsRenderViz: setIsRenderVizGlobal,
2757
- setIframeHeight: setIframeHeightGlobal,
2758
- setVizRef: setVizRefGlobal,
3364
+ view1: view1Metrics,
3365
+ view2: view2Metrics,
3366
+ difference: {
3367
+ renders: view1Metrics.renders - view2Metrics.renders,
3368
+ hookCalls: view1Metrics.hookCalls - view2Metrics.hookCalls,
3369
+ storeUpdates: view1Metrics.storeUpdates - view2Metrics.storeUpdates,
3370
+ },
2759
3371
  };
2760
- };
3372
+ }
2761
3373
 
2762
3374
  const BoxStack = forwardRef(({ children, ...props }, ref) => {
2763
3375
  const id = props.id;
@@ -2817,8 +3429,9 @@ const ContentTopBar = () => {
2817
3429
 
2818
3430
  const ContentMetricBar = () => {
2819
3431
  const controls = useHeatmapControlStore((state) => state.controls);
3432
+ const borderBottom = `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`;
2820
3433
  return (jsx(BoxStack, { id: "gx-hm-content-metric-bar", flexDirection: "row", alignItems: "center", overflow: "auto", zIndex: 1, backgroundColor: "white", style: {
2821
- borderBottom: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
3434
+ borderBottom,
2822
3435
  }, children: controls.MetricBar ?? null }));
2823
3436
  };
2824
3437
 
@@ -2834,30 +3447,88 @@ const ContentToolbar = () => {
2834
3447
  }, children: controls.Toolbar ?? null }));
2835
3448
  };
2836
3449
 
3450
+ const ContentSidebar = () => {
3451
+ const controls = useHeatmapControlStore((state) => state.controls);
3452
+ const { state } = useHeatmapInteraction();
3453
+ const isHideSidebar = state.hideSidebar;
3454
+ const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
3455
+ const mode = useHeatmapConfigStore((state) => state.mode);
3456
+ const SidebarComponent = controls.Sidebar ?? null;
3457
+ const isCompareMode = mode === 'compare';
3458
+ if (isCompareMode)
3459
+ return null;
3460
+ if (!SidebarComponent)
3461
+ return null;
3462
+ return (jsx("div", { className: "gx-hm-sidebar", style: {
3463
+ height: '100%',
3464
+ display: 'flex',
3465
+ zIndex: 1,
3466
+ ...(isHideSidebar
3467
+ ? {
3468
+ width: '0',
3469
+ transform: 'translateX(-100%)',
3470
+ visibility: 'hidden',
3471
+ }
3472
+ : { width: `${sidebarWidth}px + ${HEATMAP_CONFIG.borderWidth}px` }),
3473
+ }, children: jsx("div", { className: "gx-hm-sidebar-wrapper", style: {
3474
+ height: '100%',
3475
+ width: `calc(${sidebarWidth}px + ${HEATMAP_CONFIG.borderWidth}px)`,
3476
+ borderRight: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
3477
+ }, children: jsx(SidebarComponent, {}) }) }));
3478
+ };
3479
+
3480
+ const PopoverSidebar = () => {
3481
+ const mode = useHeatmapConfigStore((state) => state.mode);
3482
+ const CompSidebar = useHeatmapControlStore((state) => state.controls.Sidebar);
3483
+ const CompSidebarActivator = useHeatmapControlStore((state) => state.controls.SidebarActivator);
3484
+ const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
3485
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
3486
+ const { state } = useHeatmapInteraction();
3487
+ const isCompareMode = mode === 'compare';
3488
+ const isHideSidebar = state.hideSidebar;
3489
+ const stylePopover = {
3490
+ position: 'absolute',
3491
+ top: '24px',
3492
+ left: '24px',
3493
+ zIndex: Z_INDEX.SIDEBAR_POPOVER,
3494
+ height: `calc(100% - ${HEATMAP_CONFIG.heightToolbar}px - ${HEATMAP_CONFIG.padding}px - 24px)`,
3495
+ width: `calc(${sidebarWidth}px + ${HEATMAP_CONFIG.borderWidth}px)`,
3496
+ };
3497
+ if (isHideSidebar || !isCompareMode)
3498
+ return null;
3499
+ if (!CompSidebar || !CompSidebarActivator)
3500
+ return null;
3501
+ return (jsxs("div", { children: [!isPopoverOpen && (jsx("div", { style: { ...stylePopover, width: 'auto', height: 'auto' }, children: jsx(CompSidebarActivator, { onClick: () => setIsPopoverOpen(true) }) })), isPopoverOpen && (jsx(Fragment, { children: jsx("div", { className: "gx-hm-sidebar-popover", style: {
3502
+ ...stylePopover,
3503
+ backgroundColor: '#fff',
3504
+ borderRight: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
3505
+ borderRadius: '8px',
3506
+ boxShadow: '4px 0 12px rgba(0, 0, 0, 0.15)',
3507
+ overflow: 'auto',
3508
+ }, children: jsx(CompSidebar, { closeAction: { onClick: () => setIsPopoverOpen(false) } }) }) }))] }));
3509
+ };
3510
+
2837
3511
  const VizContainer = ({ children, isActive = false }) => {
2838
3512
  const wrapperRef = useRef(null);
3513
+ const viewId = useViewId();
2839
3514
  useWrapperRefHeight({
2840
3515
  isActive,
2841
3516
  wrapperRef,
2842
3517
  });
2843
- return (jsx(BoxStack, { ref: wrapperRef, id: "gx-hm-viz-container", flexDirection: "column", flex: "1 1 auto", overflow: "auto", zIndex: 1, children: jsx(BoxStack, { id: "gx-hm-content", flexDirection: "column", flex: "1 1 auto", overflow: "hidden", style: {
2844
- minWidth: '394px',
2845
- }, children: children }) }));
3518
+ return (jsxs(BoxStack, { ref: wrapperRef, id: `gx-hm-viz-container-${viewId}`, flexDirection: "column", flex: "1 1 auto", overflow: "auto", zIndex: 1, children: [jsx(BoxStack, { id: "gx-hm-content", flexDirection: "column", flex: "1 1 auto", overflow: "hidden", style: {
3519
+ minWidth: '394px',
3520
+ }, children: children }), jsx(PopoverSidebar, {})] }));
2846
3521
  };
2847
3522
 
2848
3523
  const useClickmap = () => {
2849
- const [isInitialized, setIsInitialized] = useState(false);
2850
- const { clickmap } = useHeatmapData();
2851
3524
  const { vizRef } = useHeatmapViz();
3525
+ const { clickmap } = useHeatmapData();
2852
3526
  const start = useCallback(() => {
2853
- if (isInitialized)
2854
- return;
2855
3527
  if (!vizRef || !clickmap || clickmap.length === 0)
2856
3528
  return;
2857
3529
  try {
2858
3530
  vizRef?.clearmap?.();
2859
3531
  vizRef?.clickmap?.(clickmap);
2860
- setIsInitialized(true);
2861
3532
  }
2862
3533
  catch (error) {
2863
3534
  console.error(`🚀 🐥 ~ useClickmap ~ error:`, error);
@@ -2885,7 +3556,7 @@ const useScrollmap = () => {
2885
3556
  return { start };
2886
3557
  };
2887
3558
 
2888
- const useHeatmapCanvas = ({ iframeRef, }) => {
3559
+ const useHeatmapCanvas = () => {
2889
3560
  const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
2890
3561
  const { start: startClickmap } = useClickmap();
2891
3562
  const { start: startScrollmap } = useScrollmap();
@@ -2901,10 +3572,28 @@ const useHeatmapCanvas = ({ iframeRef, }) => {
2901
3572
  }, [heatmapType, startClickmap, startScrollmap]);
2902
3573
  };
2903
3574
 
2904
- const CLICKED_ELEMENT_ID = 'gx-hm-clicked-element';
2905
- const SECONDARY_CLICKED_ELEMENT_ID = 'gx-hm-secondary-clicked-element';
2906
- const HOVERED_ELEMENT_ID = 'gx-hm-hovered-element';
2907
- const SECONDARY_HOVERED_ELEMENT_ID = 'gx-hm-secondary-hovered-element';
3575
+ // Base IDs for elements (without viewId suffix)
3576
+ const CLICKED_ELEMENT_ID_BASE = 'gx-hm-clicked-element';
3577
+ const SECONDARY_CLICKED_ELEMENT_ID_BASE = 'gx-hm-secondary-clicked-element';
3578
+ const HOVERED_ELEMENT_ID_BASE = 'gx-hm-hovered-element';
3579
+ const SECONDARY_HOVERED_ELEMENT_ID_BASE = 'gx-hm-secondary-hovered-element';
3580
+ /**
3581
+ * Generate unique element ID for a specific view
3582
+ * @param baseId - Base element ID
3583
+ * @param viewId - View ID
3584
+ * @returns Unique element ID (e.g., 'gx-hm-clicked-element-view-0')
3585
+ */
3586
+ const getElementId = (baseId, viewId) => {
3587
+ return `${baseId}-${viewId}`;
3588
+ };
3589
+ const getClickedElementId = (viewId, isSecondary = false) => {
3590
+ const baseId = isSecondary ? SECONDARY_CLICKED_ELEMENT_ID_BASE : CLICKED_ELEMENT_ID_BASE;
3591
+ return getElementId(baseId, viewId);
3592
+ };
3593
+ const getHoveredElementId = (viewId, isSecondary = false) => {
3594
+ const baseId = isSecondary ? SECONDARY_HOVERED_ELEMENT_ID_BASE : HOVERED_ELEMENT_ID_BASE;
3595
+ return getElementId(baseId, viewId);
3596
+ };
2908
3597
 
2909
3598
  const RankBadge = ({ index, elementRect, widthScale, clickOnElement, }) => {
2910
3599
  const style = calculateRankPosition(elementRect, widthScale);
@@ -2934,6 +3623,7 @@ const DEFAULT_POSITION = {
2934
3623
  };
2935
3624
  const ElementCallout = (props) => {
2936
3625
  const CompElementCallout = useHeatmapControlStore((state) => state.controls.ElementCallout);
3626
+ const viewId = useViewId();
2937
3627
  const { element, target, visualRef, hozOffset, alignment = 'left' } = props;
2938
3628
  const calloutRef = useRef(null);
2939
3629
  const [position, setPosition] = useState(DEFAULT_POSITION);
@@ -2966,9 +3656,9 @@ const ElementCallout = (props) => {
2966
3656
  position: 'fixed',
2967
3657
  top: position.top,
2968
3658
  left: position.left,
2969
- zIndex: 2147483647,
3659
+ zIndex: Z_INDEX.CALLOUT,
2970
3660
  }, "aria-live": "assertive", role: "tooltip", children: CompElementCallout && jsx(CompElementCallout, { elementHash: element.hash }) }));
2971
- return createPortal(calloutContent, document.getElementById('gx-hm-viz-container'));
3661
+ return createPortal(calloutContent, document.getElementById(`gx-hm-viz-container-${viewId}`));
2972
3662
  };
2973
3663
 
2974
3664
  const ElementMissing = ({ show = true }) => {
@@ -2992,17 +3682,8 @@ const ElementMissing = ({ show = true }) => {
2992
3682
  }, "aria-live": "assertive", children: "Element not visible on current screen" }));
2993
3683
  };
2994
3684
 
2995
- const TARGET_ID_BY_TYPE = {
2996
- hovered: {
2997
- default: HOVERED_ELEMENT_ID,
2998
- secondary: SECONDARY_HOVERED_ELEMENT_ID,
2999
- },
3000
- clicked: {
3001
- default: CLICKED_ELEMENT_ID,
3002
- secondary: SECONDARY_CLICKED_ELEMENT_ID,
3003
- },
3004
- };
3005
- const ElementOverlay = ({ type, element, onClick, isSecondary }) => {
3685
+ const ElementOverlay = ({ type, element, onClick, isSecondary, elementId, }) => {
3686
+ // useRenderCount('ElementOverlay');
3006
3687
  const { widthScale } = useHeatmapViz();
3007
3688
  if (!element || (element.width === 0 && element.height === 0))
3008
3689
  return null;
@@ -3011,8 +3692,7 @@ const ElementOverlay = ({ type, element, onClick, isSecondary }) => {
3011
3692
  const left = element.left + HEATMAP_CONFIG['borderWidthIframe'];
3012
3693
  const width = element.width;
3013
3694
  const height = element.height;
3014
- const targetId = TARGET_ID_BY_TYPE[type][isSecondary ? 'secondary' : 'default'];
3015
- return (jsxs(Fragment$1, { children: [jsx("div", { onClick: onClick, className: "heatmapElement", id: targetId, style: {
3695
+ return (jsxs(Fragment$1, { children: [jsx("div", { onClick: onClick, className: "heatmapElement", id: elementId, style: {
3016
3696
  top,
3017
3697
  left,
3018
3698
  width,
@@ -3026,14 +3706,17 @@ const ELEMENT_CALLOUT = {
3026
3706
  alignment: 'left',
3027
3707
  };
3028
3708
  const HeatmapElements = (props) => {
3709
+ const viewId = useViewId();
3029
3710
  const { iframeHeight } = useHeatmapViz();
3030
- const { iframeRef, wrapperRef, visualRef, visualizer, iframeDimensions, isElementSidebarOpen, isVisible = true, areDefaultRanksHidden, isSecondary, } = props;
3711
+ const clickedElementId = getClickedElementId(viewId, props.isSecondary);
3712
+ const hoveredElementId = getHoveredElementId(viewId, props.isSecondary);
3713
+ const { iframeRef, wrapperRef, visualRef, visualizer, iframeDimensions, isVisible = true, areDefaultRanksHidden, isSecondary, } = props;
3031
3714
  const getRect = useHeatmapElementPosition({
3032
3715
  iframeRef,
3033
3716
  wrapperRef,
3034
3717
  visualizer,
3035
3718
  });
3036
- const { clickedElement, showMissingElement, shouldShowCallout, setShouldShowCallout } = useClickedElement({
3719
+ const { clickedElement, showMissingElement, shouldShowCallout } = useClickedElement({
3037
3720
  visualRef,
3038
3721
  getRect,
3039
3722
  });
@@ -3041,25 +3724,12 @@ const HeatmapElements = (props) => {
3041
3724
  iframeRef,
3042
3725
  getRect,
3043
3726
  });
3044
- useElementCalloutVisible({
3045
- visualRef,
3046
- getRect,
3047
- });
3048
- const resetAll = () => {
3049
- // setShouldShowCallout(false);
3050
- };
3051
- useHeatmapEffects({
3052
- isVisible,
3053
- isElementSidebarOpen,
3054
- setShouldShowCallout,
3055
- resetAll,
3056
- });
3727
+ useElementCalloutVisible({ visualRef, getRect });
3728
+ useHeatmapEffects({ isVisible });
3729
+ useRenderCount('HeatmapElements');
3057
3730
  if (!isVisible)
3058
3731
  return null;
3059
- return (jsxs("div", { onMouseMove: (event) => {
3060
- handleMouseMove(event);
3061
- // handleMouseMove2(event as any);
3062
- }, onMouseLeave: handleMouseLeave, className: "gx-hm-elements", style: { ...iframeDimensions, height: `${iframeHeight}px` }, children: [jsx(ElementMissing, { show: showMissingElement }), jsx(DefaultRankBadges, { getRect: getRect, hidden: areDefaultRanksHidden }), jsx(ElementOverlay, { type: "clicked", element: clickedElement, isSecondary: isSecondary }), jsx(ElementOverlay, { type: "hovered", element: hoveredElement, isSecondary: isSecondary, onClick: handleClick }), hoveredElement?.hash !== clickedElement?.hash && hoveredElement && (jsx(ElementCallout, { element: hoveredElement, target: `#${HOVERED_ELEMENT_ID}`, visualRef: visualRef, ...ELEMENT_CALLOUT })), shouldShowCallout && clickedElement && (jsx(ElementCallout, { element: clickedElement, target: `#${CLICKED_ELEMENT_ID}`, visualRef: visualRef, ...ELEMENT_CALLOUT }))] }));
3732
+ return (jsxs("div", { onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, className: "gx-hm-elements", style: { ...iframeDimensions, height: `${iframeHeight}px` }, children: [jsx(ElementMissing, { show: showMissingElement }), jsx(DefaultRankBadges, { getRect: getRect, hidden: areDefaultRanksHidden }), jsx(ElementOverlay, { type: "clicked", element: clickedElement, isSecondary: isSecondary, elementId: clickedElementId }), jsx(ElementOverlay, { type: "hovered", element: hoveredElement, isSecondary: isSecondary, onClick: handleClick, elementId: hoveredElementId }), hoveredElement?.hash !== clickedElement?.hash && hoveredElement && (jsx(ElementCallout, { element: hoveredElement, target: `#${hoveredElementId}`, visualRef: visualRef, ...ELEMENT_CALLOUT })), shouldShowCallout && clickedElement && (jsx(ElementCallout, { element: clickedElement, target: `#${clickedElementId}`, visualRef: visualRef, ...ELEMENT_CALLOUT }))] }));
3063
3733
  };
3064
3734
 
3065
3735
  const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
@@ -3068,23 +3738,12 @@ const VizElements = ({ iframeRef, visualRef, wrapperRef }) => {
3068
3738
  const { vizRef } = useHeatmapViz();
3069
3739
  const visualizer = {
3070
3740
  get: (hash) => {
3071
- if (vizRef) {
3072
- return vizRef.get(hash);
3073
- }
3074
- const doc = iframeRef.current?.contentDocument;
3075
- if (!doc)
3741
+ if (!vizRef)
3076
3742
  return null;
3077
- const elmHashAlpha = doc.querySelector(`[data-clarity-hashalpha="${hash}"]`);
3078
- if (elmHashAlpha) {
3079
- return elmHashAlpha;
3080
- }
3081
- const elmHash = doc.querySelector(`[data-clarity-hash="${hash}"]`);
3082
- if (elmHash) {
3083
- return elmHash;
3084
- }
3085
- return null;
3743
+ return vizRef.get(hash);
3086
3744
  },
3087
3745
  };
3746
+ // useRenderCount('VizElements');
3088
3747
  if (!iframeRef.current)
3089
3748
  return null;
3090
3749
  return (jsx(HeatmapElements, { visualizer: visualizer, visualRef: visualRef, iframeRef: iframeRef, wrapperRef: wrapperRef, heatmapInfo: dataInfo, isVisible: true, iframeDimensions: {
@@ -3209,8 +3868,8 @@ const ScrollmapMarker = ({ iframeRef, wrapperRef }) => {
3209
3868
  };
3210
3869
 
3211
3870
  const ScrollMapMinimap = ({ zones, maxUsers }) => {
3212
- const showMinimap = useHeatmapVizScrollmapStore((state) => state.showMinimap);
3213
3871
  const scrollType = useHeatmapConfigStore((state) => state.scrollType);
3872
+ const { showMinimap } = useHeatmapVizScrollmap();
3214
3873
  const isScrollType = [IScrollType.Attention].includes(scrollType);
3215
3874
  if (!showMinimap || !isScrollType)
3216
3875
  return null;
@@ -3370,6 +4029,7 @@ const SCROLL_TYPES = [IHeatmapType.Scroll];
3370
4029
  const VizScrollMap = ({ iframeRef, wrapperRef }) => {
3371
4030
  const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
3372
4031
  const { iframeHeight } = useHeatmapViz();
4032
+ // useRenderCount('VizScrollMap');
3373
4033
  const isHeatmapScroll = SCROLL_TYPES.includes(heatmapType);
3374
4034
  if (!iframeHeight || !isHeatmapScroll)
3375
4035
  return null;
@@ -3386,9 +4046,7 @@ const VizScrollMap = ({ iframeRef, wrapperRef }) => {
3386
4046
  const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHeight, onScroll, }) => {
3387
4047
  const contentWidth = useHeatmapConfigStore((state) => state.width);
3388
4048
  const { widthScale } = useHeatmapViz();
3389
- const contentHeight = scaledHeight > 0
3390
- ? `${scaledHeight + HEATMAP_CONFIG['heightToolbar'] + HEATMAP_CONFIG['padding'] * 2}px`
3391
- : 'auto';
4049
+ const contentHeight = calcContentHeight();
3392
4050
  return (jsx("div", { ref: visualRef, className: "gx-hm-visual", onScroll: onScroll, style: {
3393
4051
  overflow: 'hidden scroll',
3394
4052
  display: 'flex',
@@ -3413,15 +4071,20 @@ const WrapperVisual = ({ children, visualRef, wrapperRef, scaledHeight, iframeHe
3413
4071
  transformOrigin: 'top center',
3414
4072
  paddingBlockEnd: '0',
3415
4073
  }, children: children }) }) }));
4074
+ function calcContentHeight() {
4075
+ return scaledHeight > 0
4076
+ ? `${scaledHeight + HEATMAP_CONFIG['heightToolbar'] + HEATMAP_CONFIG['padding'] * 2}px`
4077
+ : 'auto';
4078
+ }
3416
4079
  };
3417
4080
 
3418
4081
  const VizDomRenderer = ({ mode = 'heatmap' }) => {
3419
- const width = useHeatmapConfigStore((state) => state.width);
4082
+ const contentWidth = useHeatmapConfigStore((state) => state.width || 0);
3420
4083
  const heatmapType = useHeatmapConfigStore((state) => state.heatmapType);
3421
- const setSelectedElement = useHeatmapInteractionStore((state) => state.setSelectedElement);
3422
- const { iframeHeight, setIframeHeight, isRenderViz } = useHeatmapViz();
3423
4084
  const wrapperRef = useRef(null);
3424
4085
  const visualRef = useRef(null);
4086
+ const { setSelectedElement } = useHeatmapInteraction();
4087
+ const { iframeHeight, setIframeHeight, isRenderViz } = useHeatmapViz();
3425
4088
  const { iframeRef } = useHeatmapVizRender(mode);
3426
4089
  const { scaledHeight, handleScroll } = useHeatmapScale({
3427
4090
  wrapperRef,
@@ -3430,12 +4093,12 @@ const VizDomRenderer = ({ mode = 'heatmap' }) => {
3430
4093
  iframeHeight,
3431
4094
  isRenderViz,
3432
4095
  });
3433
- const contentWidth = width ?? 0;
4096
+ useHeatmapCanvas();
4097
+ useRenderCount('VizDomRenderer');
3434
4098
  const onScroll = (e) => {
3435
4099
  const scrollTop = e.currentTarget.scrollTop;
3436
4100
  handleScroll(scrollTop);
3437
4101
  };
3438
- useHeatmapCanvas({ iframeRef: iframeRef });
3439
4102
  const cleanUp = () => {
3440
4103
  setIframeHeight(0);
3441
4104
  setSelectedElement(null);
@@ -3451,21 +4114,15 @@ const VizLoading = () => {
3451
4114
  };
3452
4115
 
3453
4116
  const VizDomHeatmap = () => {
3454
- const controls = useHeatmapControlStore((state) => state.controls);
3455
- const { isRendering } = useHeatmapData();
3456
- console.log(`🚀 🐥 ~ VizDomHeatmap ~ isRendering:`, isRendering);
3457
- const { iframeHeight, setIframeHeight, setVizRef } = useHeatmapViz();
3458
- const viewId = useViewId();
4117
+ const { iframeHeight, setIframeHeight } = useHeatmapViz();
4118
+ useRenderCount('VizDomHeatmap');
3459
4119
  useEffect(() => {
3460
4120
  return () => {
3461
- console.log(`🚀 🐥 ~ useEffect ~ return:`, viewId, iframeHeight);
3462
- setVizRef(null);
4121
+ console.log('🚀 🐥 ~ useEffect ~ return:');
3463
4122
  setIframeHeight(0);
3464
4123
  };
3465
4124
  }, []);
3466
- if (isRendering)
3467
- return controls.VizLoading ?? null;
3468
- return (jsxs(VizContainer, { children: [jsx(VizDomRenderer, {}), iframeHeight === 0 && jsx(VizLoading, {})] }));
4125
+ return (jsxs(VizContainer, { isActive: true, children: [jsx(VizDomRenderer, {}), iframeHeight === 0 && jsx(VizLoading, {})] }));
3469
4126
  };
3470
4127
 
3471
4128
  const VizLiveRenderer = () => {
@@ -3480,7 +4137,6 @@ const VizLiveRenderer = () => {
3480
4137
  visualRef,
3481
4138
  iframeHeight,
3482
4139
  isRenderViz,
3483
- setIframeHeight,
3484
4140
  });
3485
4141
  const onScroll = (e) => {
3486
4142
  const scrollTop = e.currentTarget.scrollTop;
@@ -3492,8 +4148,6 @@ const VizLiveRenderer = () => {
3492
4148
  };
3493
4149
 
3494
4150
  const VizLiveHeatmap = () => {
3495
- const controls = useHeatmapControlStore((state) => state.controls);
3496
- const isRendering = useHeatmapConfigStore((state) => state.isRendering);
3497
4151
  const { iframeHeight, wrapperHeight } = useHeatmapViz();
3498
4152
  const reset = useHeatmapLiveStore((state) => state.reset);
3499
4153
  useEffect(() => {
@@ -3501,13 +4155,15 @@ const VizLiveHeatmap = () => {
3501
4155
  reset();
3502
4156
  };
3503
4157
  }, []);
3504
- if (isRendering)
3505
- return controls.VizLoading ?? null;
3506
4158
  return (jsxs(VizContainer, { isActive: true, children: [jsx(VizLiveRenderer, {}), (!iframeHeight || !wrapperHeight) && jsx(VizLoading, {})] }));
3507
4159
  };
3508
4160
 
3509
4161
  const ContentVizByMode = () => {
3510
4162
  const mode = useHeatmapConfigStore((state) => state.mode);
4163
+ const isRendering = useHeatmapConfigStore((state) => state.isRendering);
4164
+ const controls = useHeatmapControlStore((state) => state.controls);
4165
+ if (isRendering)
4166
+ return controls.VizLoading ?? null;
3511
4167
  switch (mode) {
3512
4168
  case 'live':
3513
4169
  return jsx(VizLiveHeatmap, {});
@@ -3518,75 +4174,12 @@ const ContentVizByMode = () => {
3518
4174
  }
3519
4175
  };
3520
4176
 
3521
- const LeftSidebar = () => {
3522
- const controls = useHeatmapControlStore((state) => state.controls);
3523
- const isHideSidebar = useHeatmapInteractionStore((state) => state.state.hideSidebar);
3524
- const sidebarWidth = useHeatmapConfigStore((state) => state.sidebarWidth);
3525
- const mode = useHeatmapConfigStore((state) => state.mode);
3526
- const [isPopoverOpen, setIsPopoverOpen] = useState(false);
3527
- // In compare mode, render as popover
3528
- if (mode === 'compare') {
3529
- return (jsxs("div", { style: { position: 'relative', zIndex: 10 }, children: [jsx("button", { onClick: () => setIsPopoverOpen(!isPopoverOpen), style: {
3530
- position: 'absolute',
3531
- top: '8px',
3532
- left: '8px',
3533
- padding: '8px 16px',
3534
- backgroundColor: '#fff',
3535
- border: '1px solid #c9cccf',
3536
- borderRadius: '8px',
3537
- cursor: 'pointer',
3538
- fontSize: '14px',
3539
- fontWeight: 500,
3540
- zIndex: 11,
3541
- boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)',
3542
- }, children: "Click data" }), isPopoverOpen && (jsxs(Fragment, { children: [jsx("div", { onClick: () => setIsPopoverOpen(false), style: {
3543
- position: 'fixed',
3544
- top: 0,
3545
- left: 0,
3546
- right: 0,
3547
- bottom: 0,
3548
- backgroundColor: 'rgba(0, 0, 0, 0.3)',
3549
- zIndex: 12,
3550
- } }), jsx("div", { className: "gx-hm-sidebar-popover", style: {
3551
- position: 'fixed',
3552
- top: 0,
3553
- left: 0,
3554
- height: '100vh',
3555
- width: `calc(${sidebarWidth}px + ${HEATMAP_CONFIG.borderWidth}px)`,
3556
- backgroundColor: '#fff',
3557
- borderRight: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
3558
- zIndex: 13,
3559
- boxShadow: '4px 0 12px rgba(0, 0, 0, 0.15)',
3560
- overflow: 'auto',
3561
- }, children: controls.Sidebar ?? null })] }))] }));
3562
- }
3563
- // Normal sidebar rendering for single mode
3564
- if (isHideSidebar) {
3565
- return null;
3566
- }
3567
- return (jsx("div", { className: "gx-hm-sidebar", style: {
3568
- height: '100%',
3569
- display: 'flex',
3570
- zIndex: 1,
3571
- ...(isHideSidebar
3572
- ? {
3573
- width: '0',
3574
- transform: 'translateX(-100%)',
3575
- visibility: 'hidden',
3576
- }
3577
- : { width: `${sidebarWidth}px + ${HEATMAP_CONFIG.borderWidth}px` }),
3578
- }, children: jsx("div", { className: "gx-hm-sidebar-wrapper", style: {
3579
- height: '100%',
3580
- width: `calc(${sidebarWidth}px + ${HEATMAP_CONFIG.borderWidth}px)`,
3581
- borderRight: `${HEATMAP_CONFIG.borderWidth}px solid ${HEATMAP_CONFIG.borderColor}`,
3582
- }, children: controls.Sidebar ?? null }) }));
3583
- };
3584
-
3585
4177
  const WrapperPreview = () => {
3586
- return (jsxs(BoxStack, { id: "gx-hm-container", flexDirection: "row", overflow: "hidden", flex: "1", position: "relative", children: [jsx(LeftSidebar, {}), jsxs(BoxStack, { flexDirection: "column", flex: "1", children: [jsx(ContentMetricBar, {}), jsx(ContentVizByMode, {}), jsx(ContentToolbar, {})] })] }));
4178
+ return (jsxs(BoxStack, { id: "gx-hm-container", flexDirection: "row", overflow: "hidden", flex: "1", position: "relative", children: [jsx(ContentSidebar, {}), jsxs(BoxStack, { flexDirection: "column", flex: "1", children: [jsx(ContentMetricBar, {}), jsx(ContentVizByMode, {}), jsx(ContentToolbar, {})] })] }));
3587
4179
  };
3588
4180
 
3589
4181
  const WrapperLayout = () => {
4182
+ useRenderCount('WrapperLayout');
3590
4183
  return (jsxs(BoxStack, { id: "gx-hm-layout", flexDirection: "column", flex: "1", children: [jsx(ContentTopBar, {}), jsx(WrapperPreview, {})] }));
3591
4184
  };
3592
4185
 
@@ -3595,6 +4188,19 @@ const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
3595
4188
  useRegisterData(data, dataInfo);
3596
4189
  useRegisterHeatmap({ clickmap, scrollmap });
3597
4190
  useRegisterConfig();
4191
+ performanceLogger.configure({
4192
+ enabled: true,
4193
+ logToConsole: false,
4194
+ logLevel: 'normal', // 'verbose' | 'normal' | 'minimal'
4195
+ thresholds: {
4196
+ slowRenderMs: 16, // Warn if render > 16ms (60fps)
4197
+ slowHookMs: 5, // Warn if hook > 5ms
4198
+ excessiveRenderCount: 10, // Warn if component renders > 10 times
4199
+ },
4200
+ externalLogger: (metric) => {
4201
+ if (metric.name === 'VizDomRenderer') ;
4202
+ },
4203
+ });
3598
4204
  return (jsx(BoxStack, { id: "gx-hm-project", flexDirection: "column", flex: "1", height: "100%", style: getVariableStyle(), children: jsx(BoxStack, { id: "gx-hm-project-content", flexDirection: "column", flex: "1", children: jsx("div", { style: {
3599
4205
  minHeight: '100%',
3600
4206
  display: 'flex',
@@ -3608,4 +4214,4 @@ const HeatmapLayout = ({ data, clickmap, scrollmap, controls, dataInfo, }) => {
3608
4214
  }
3609
4215
  };
3610
4216
 
3611
- export { GraphView, HeatmapLayout, IClickType, IHeatmapType, IScrollType, ViewIdContext, convertViewportToIframeCoords, getScrollGradientColor, useClickedElement, useCompareAwareConfig, useCompareAwareConfigSetters, useCompareAwareData, useCompareAwareDataSetters, useCompareAwareViz, useCompareAwareVizSetters, useElementCalloutVisible, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapData, useHeatmapDataStore, useHeatmapEffects, useHeatmapElementPosition, useHeatmapInteractionStore, useHeatmapLiveStore, useHeatmapScale, useHeatmapSingleStore, useHeatmapViz, useHeatmapVizRender, useHeatmapVizStore, useHoveredElement, useIsCompareMode, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useScrollmapZones, useViewId, useVizLiveRender, useWrapperRefHeight, useZonePositions };
4217
+ export { DEFAULT_SIDEBAR_WIDTH, DEFAULT_VIEW_ID, GraphView, HEATMAP_CONFIG, HEATMAP_IFRAME, HEATMAP_STYLE, HeatmapLayout, IClickType, IHeatmapType, IScrollType, ViewIdContext, Z_INDEX, compareViewPerformance, convertViewportToIframeCoords, createStorePerformanceTracker, downloadPerformanceReport, getCompareViewId, getCompareViewIndex, getMetricsByViewId, getPerformanceReportJSON, getScrollGradientColor, isCompareViewId, performanceLogger, printPerformanceSummary, sendPerformanceReport, trackStoreAction, useClickedElement, useElementCalloutVisible, useHeatmapCompareStore, useHeatmapConfigStore, useHeatmapCopyView, useHeatmapData, useHeatmapDataStore, useHeatmapEffects, useHeatmapElementPosition, useHeatmapInteraction, useHeatmapInteractionStore, useHeatmapLiveStore, useHeatmapScale, useHeatmapSingleStore, useHeatmapViz, useHeatmapVizRender, useHeatmapVizScrollmap, useHeatmapVizScrollmapStore, useHeatmapVizStore, useHoveredElement, useIsCompareMode, useMeasureFunction, useRegisterConfig, useRegisterControl, useRegisterData, useRegisterHeatmap, useRenderCount, useScrollmapZones, useTrackHookCall, useViewId, useVizLiveRender, useWhyDidYouUpdate, useWrapperRefHeight, useZonePositions, withPerformanceTracking };