@gemx-dev/heatmap-react 3.5.21 → 3.5.23

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 (223) hide show
  1. package/dist/esm/components/HeatmapLayout/ContentHeader.d.ts +4 -0
  2. package/dist/esm/components/HeatmapLayout/ContentHeader.d.ts.map +1 -0
  3. package/dist/esm/components/HeatmapLayout/HeatmapLayout.d.ts +7 -0
  4. package/dist/esm/components/HeatmapLayout/HeatmapLayout.d.ts.map +1 -0
  5. package/dist/esm/components/HeatmapLayout/LeftSidebar.d.ts +4 -0
  6. package/dist/esm/components/HeatmapLayout/LeftSidebar.d.ts.map +1 -0
  7. package/dist/esm/components/HeatmapLayout/ReplayControls.d.ts +2 -0
  8. package/dist/esm/components/HeatmapLayout/ReplayControls.d.ts.map +1 -0
  9. package/dist/esm/components/HeatmapLayout/VizDomContainer.d.ts +2 -0
  10. package/dist/esm/components/HeatmapLayout/VizDomContainer.d.ts.map +1 -0
  11. package/dist/esm/components/HeatmapLayout/VizDomRenderer.d.ts +6 -0
  12. package/dist/esm/components/HeatmapLayout/VizDomRenderer.d.ts.map +1 -0
  13. package/dist/esm/components/HeatmapLayout/WrapperLayout.d.ts +7 -0
  14. package/dist/esm/components/HeatmapLayout/WrapperLayout.d.ts.map +1 -0
  15. package/dist/esm/components/HeatmapLayout/WrapperPreview.d.ts +4 -0
  16. package/dist/esm/components/HeatmapLayout/WrapperPreview.d.ts.map +1 -0
  17. package/dist/esm/components/HeatmapLayout/index.d.ts +2 -0
  18. package/dist/esm/components/HeatmapLayout/index.d.ts.map +1 -0
  19. package/dist/esm/components/VizElement/ClarityVisualizer.d.ts +150 -0
  20. package/dist/esm/components/VizElement/ClarityVisualizer.d.ts.map +1 -0
  21. package/dist/esm/components/VizElement/HeatmapElementV2.d.ts +45 -0
  22. package/dist/esm/components/VizElement/HeatmapElementV2.d.ts.map +1 -0
  23. package/dist/esm/components/VizElement/PayloadProcessor.d.ts +65 -0
  24. package/dist/esm/components/VizElement/PayloadProcessor.d.ts.map +1 -0
  25. package/dist/esm/components/VizElement/VizElementRank.d.ts +74 -0
  26. package/dist/esm/components/VizElement/VizElementRank.d.ts.map +1 -0
  27. package/dist/esm/components/VizElement/constants.d.ts +5 -0
  28. package/dist/esm/components/VizElement/constants.d.ts.map +1 -0
  29. package/dist/esm/components/VizElement/helpers.d.ts +20 -0
  30. package/dist/esm/components/VizElement/helpers.d.ts.map +1 -0
  31. package/dist/esm/components/VizElement/types.d.ts +13 -0
  32. package/dist/esm/components/VizElement/types.d.ts.map +1 -0
  33. package/dist/esm/hooks/useHeatmapByMode.d.ts +6 -0
  34. package/dist/esm/hooks/useHeatmapByMode.d.ts.map +1 -0
  35. package/dist/esm/hooks/useHeatmapRender.d.ts +6 -0
  36. package/dist/esm/hooks/useHeatmapRender.d.ts.map +1 -0
  37. package/dist/esm/hooks/useHeatmapScale.d.ts +28 -0
  38. package/dist/esm/hooks/useHeatmapScale.d.ts.map +1 -0
  39. package/dist/esm/hooks/useReplayRender.d.ts +9 -0
  40. package/dist/esm/hooks/useReplayRender.d.ts.map +1 -0
  41. package/dist/esm/types/index.d.ts +3 -0
  42. package/dist/esm/types/index.d.ts.map +1 -0
  43. package/dist/umd/components/VizElement/VizElementRank.d.ts +75 -0
  44. package/dist/umd/components/VizElement/VizElementRank.d.ts.map +1 -0
  45. package/package.json +11 -14
  46. package/src/components/GraphView.tsx +58 -0
  47. package/src/components/Layout/ContentHeader.tsx +15 -0
  48. package/src/components/Layout/HeatmapLayout.tsx +63 -0
  49. package/src/components/Layout/LeftSidebar.tsx +32 -0
  50. package/src/components/Layout/WrapperLayout.tsx +18 -0
  51. package/src/components/Layout/WrapperPreview.tsx +14 -0
  52. package/src/components/Layout/index.ts +1 -0
  53. package/src/components/Test.tsx +1106 -0
  54. package/src/components/VizDom/ReplayControls.tsx +48 -0
  55. package/src/components/VizDom/VizDomContainer.tsx +25 -0
  56. package/src/components/VizDom/VizDomRenderer.tsx +87 -0
  57. package/src/components/VizDom/index.ts +1 -0
  58. package/src/components/VizElement/ClickedElementOverlay.tsx +65 -0
  59. package/src/components/VizElement/DefaultRankBadges.tsx +33 -0
  60. package/src/components/VizElement/ElementCallout.tsx +337 -0
  61. package/src/components/VizElement/HeatmapElements.tsx +147 -0
  62. package/src/components/VizElement/HoveredElementOverlay.tsx +46 -0
  63. package/src/components/VizElement/MissingElementMessage.tsx +32 -0
  64. package/src/components/VizElement/RankBadge.tsx +25 -0
  65. package/src/components/VizElement/VizElements.tsx +80 -0
  66. package/src/components/VizElement/index.ts +1 -0
  67. package/src/components/VizElement/temp/ClarityVisualizer.ts +764 -0
  68. package/src/components/VizElement/temp/VizElementRank.tsx +579 -0
  69. package/src/components/index.tsx +4 -0
  70. package/src/configs/iframe.ts +15 -0
  71. package/src/configs/index.ts +2 -0
  72. package/src/configs/style.ts +9 -0
  73. package/src/constants/index.ts +4 -0
  74. package/src/dist/components/GraphView.d.ts +9 -0
  75. package/src/dist/components/GraphView.d.ts.map +1 -0
  76. package/src/dist/components/Layout/ContentHeader.d.ts +4 -0
  77. package/src/dist/components/Layout/ContentHeader.d.ts.map +1 -0
  78. package/src/dist/components/Layout/HeatmapLayout.d.ts +11 -0
  79. package/src/dist/components/Layout/HeatmapLayout.d.ts.map +1 -0
  80. package/src/dist/components/Layout/LeftSidebar.d.ts +4 -0
  81. package/src/dist/components/Layout/LeftSidebar.d.ts.map +1 -0
  82. package/src/dist/components/Layout/WrapperLayout.d.ts +8 -0
  83. package/src/dist/components/Layout/WrapperLayout.d.ts.map +1 -0
  84. package/src/dist/components/Layout/WrapperPreview.d.ts +4 -0
  85. package/src/dist/components/Layout/WrapperPreview.d.ts.map +1 -0
  86. package/src/dist/components/Layout/index.d.ts +2 -0
  87. package/src/dist/components/Layout/index.d.ts.map +1 -0
  88. package/src/dist/components/Test.d.ts +121 -0
  89. package/src/dist/components/Test.d.ts.map +1 -0
  90. package/src/dist/components/VizDom/ReplayControls.d.ts +2 -0
  91. package/src/dist/components/VizDom/ReplayControls.d.ts.map +1 -0
  92. package/src/dist/components/VizDom/VizDomContainer.d.ts +2 -0
  93. package/src/dist/components/VizDom/VizDomContainer.d.ts.map +1 -0
  94. package/src/dist/components/VizDom/VizDomRenderer.d.ts +6 -0
  95. package/src/dist/components/VizDom/VizDomRenderer.d.ts.map +1 -0
  96. package/src/dist/components/VizDom/index.d.ts +2 -0
  97. package/src/dist/components/VizDom/index.d.ts.map +1 -0
  98. package/src/dist/components/VizElement/ClickedElementOverlay.d.ts +17 -0
  99. package/src/dist/components/VizElement/ClickedElementOverlay.d.ts.map +1 -0
  100. package/src/dist/components/VizElement/DefaultRankBadges.d.ts +11 -0
  101. package/src/dist/components/VizElement/DefaultRankBadges.d.ts.map +1 -0
  102. package/src/dist/components/VizElement/ElementCallout.d.ts +17 -0
  103. package/src/dist/components/VizElement/ElementCallout.d.ts.map +1 -0
  104. package/src/dist/components/VizElement/HeatmapElements.d.ts +23 -0
  105. package/src/dist/components/VizElement/HeatmapElements.d.ts.map +1 -0
  106. package/src/dist/components/VizElement/HoveredElementOverlay.d.ts +12 -0
  107. package/src/dist/components/VizElement/HoveredElementOverlay.d.ts.map +1 -0
  108. package/src/dist/components/VizElement/MissingElementMessage.d.ts +7 -0
  109. package/src/dist/components/VizElement/MissingElementMessage.d.ts.map +1 -0
  110. package/src/dist/components/VizElement/RankBadge.d.ts +10 -0
  111. package/src/dist/components/VizElement/RankBadge.d.ts.map +1 -0
  112. package/src/dist/components/VizElement/VizElements.d.ts +10 -0
  113. package/src/dist/components/VizElement/VizElements.d.ts.map +1 -0
  114. package/src/dist/components/VizElement/index.d.ts +2 -0
  115. package/src/dist/components/VizElement/index.d.ts.map +1 -0
  116. package/src/dist/components/VizElement/temp/ClarityVisualizer.d.ts +150 -0
  117. package/src/dist/components/VizElement/temp/ClarityVisualizer.d.ts.map +1 -0
  118. package/src/dist/components/VizElement/temp/VizElementRank.d.ts +74 -0
  119. package/src/dist/components/VizElement/temp/VizElementRank.d.ts.map +1 -0
  120. package/src/dist/components/index.d.ts +4 -0
  121. package/src/dist/components/index.d.ts.map +1 -0
  122. package/src/dist/configs/iframe.d.ts +10 -0
  123. package/src/dist/configs/iframe.d.ts.map +1 -0
  124. package/src/dist/configs/index.d.ts +3 -0
  125. package/src/dist/configs/index.d.ts.map +1 -0
  126. package/src/dist/configs/style.d.ts +9 -0
  127. package/src/dist/configs/style.d.ts.map +1 -0
  128. package/src/dist/constants/index.d.ts +5 -0
  129. package/src/dist/constants/index.d.ts.map +1 -0
  130. package/src/dist/helpers/iframe.d.ts +3 -0
  131. package/src/dist/helpers/iframe.d.ts.map +1 -0
  132. package/src/dist/helpers/index.d.ts +2 -0
  133. package/src/dist/helpers/index.d.ts.map +1 -0
  134. package/src/dist/helpers/viz-elements.d.ts +10 -0
  135. package/src/dist/helpers/viz-elements.d.ts.map +1 -0
  136. package/src/dist/hooks/index.d.ts +4 -0
  137. package/src/dist/hooks/index.d.ts.map +1 -0
  138. package/src/dist/hooks/vix-elements/index.d.ts +5 -0
  139. package/src/dist/hooks/vix-elements/index.d.ts.map +1 -0
  140. package/src/dist/hooks/vix-elements/useClickedElement.d.ts +14 -0
  141. package/src/dist/hooks/vix-elements/useClickedElement.d.ts.map +1 -0
  142. package/src/dist/hooks/vix-elements/useHeatmapEffects.d.ts +8 -0
  143. package/src/dist/hooks/vix-elements/useHeatmapEffects.d.ts.map +1 -0
  144. package/src/dist/hooks/vix-elements/useHeatmapElementPosition.d.ts +13 -0
  145. package/src/dist/hooks/vix-elements/useHeatmapElementPosition.d.ts.map +1 -0
  146. package/src/dist/hooks/vix-elements/useHoveredElement.d.ts +17 -0
  147. package/src/dist/hooks/vix-elements/useHoveredElement.d.ts.map +1 -0
  148. package/src/dist/hooks/viz-render/index.d.ts +2 -0
  149. package/src/dist/hooks/viz-render/index.d.ts.map +1 -0
  150. package/src/dist/hooks/viz-render/useHeatmapRender.d.ts +8 -0
  151. package/src/dist/hooks/viz-render/useHeatmapRender.d.ts.map +1 -0
  152. package/src/dist/hooks/viz-render/useHeatmapVizRender.d.ts +8 -0
  153. package/src/dist/hooks/viz-render/useHeatmapVizRender.d.ts.map +1 -0
  154. package/src/dist/hooks/viz-render/useReplayRender.d.ts +11 -0
  155. package/src/dist/hooks/viz-render/useReplayRender.d.ts.map +1 -0
  156. package/src/dist/hooks/viz-scale/index.d.ts +2 -0
  157. package/src/dist/hooks/viz-scale/index.d.ts.map +1 -0
  158. package/src/dist/hooks/viz-scale/useContainerDimensions.d.ts +10 -0
  159. package/src/dist/hooks/viz-scale/useContainerDimensions.d.ts.map +1 -0
  160. package/src/dist/hooks/viz-scale/useContentDimensions.d.ts +11 -0
  161. package/src/dist/hooks/viz-scale/useContentDimensions.d.ts.map +1 -0
  162. package/src/dist/hooks/viz-scale/useHeatmapScale.d.ts +19 -0
  163. package/src/dist/hooks/viz-scale/useHeatmapScale.d.ts.map +1 -0
  164. package/src/dist/hooks/viz-scale/useIframeHeight.d.ts +10 -0
  165. package/src/dist/hooks/viz-scale/useIframeHeight.d.ts.map +1 -0
  166. package/src/dist/hooks/viz-scale/useScaleCalculation.d.ts +10 -0
  167. package/src/dist/hooks/viz-scale/useScaleCalculation.d.ts.map +1 -0
  168. package/src/dist/hooks/viz-scale/useScrollSync.d.ts +10 -0
  169. package/src/dist/hooks/viz-scale/useScrollSync.d.ts.map +1 -0
  170. package/src/dist/index.d.ts +4 -0
  171. package/src/dist/index.d.ts.map +1 -0
  172. package/src/dist/stores/data.d.ts +18 -0
  173. package/src/dist/stores/data.d.ts.map +1 -0
  174. package/src/dist/stores/index.d.ts +2 -0
  175. package/src/dist/stores/index.d.ts.map +1 -0
  176. package/src/dist/ui/BoxStack/BoxStack.d.ts +56 -0
  177. package/src/dist/ui/BoxStack/BoxStack.d.ts.map +1 -0
  178. package/src/dist/ui/BoxStack/index.d.ts +2 -0
  179. package/src/dist/ui/BoxStack/index.d.ts.map +1 -0
  180. package/src/dist/ui/index.d.ts +2 -0
  181. package/src/dist/ui/index.d.ts.map +1 -0
  182. package/src/dist/utils/device.d.ts +2 -0
  183. package/src/dist/utils/device.d.ts.map +1 -0
  184. package/src/dist/utils/retry.d.ts +2 -0
  185. package/src/dist/utils/retry.d.ts.map +1 -0
  186. package/src/dist/utils/sort.d.ts +3 -0
  187. package/src/dist/utils/sort.d.ts.map +1 -0
  188. package/src/global.d.ts +5 -0
  189. package/src/helpers/iframe.ts +33 -0
  190. package/src/helpers/index.ts +1 -0
  191. package/src/helpers/viz-elements.ts +34 -0
  192. package/src/hooks/index.ts +3 -0
  193. package/src/hooks/vix-elements/index.ts +4 -0
  194. package/src/hooks/vix-elements/useClickedElement.ts +62 -0
  195. package/src/hooks/vix-elements/useHeatmapEffects.ts +29 -0
  196. package/src/hooks/vix-elements/useHeatmapElementPosition.ts +66 -0
  197. package/src/hooks/vix-elements/useHoveredElement.ts +135 -0
  198. package/src/hooks/viz-render/index.ts +1 -0
  199. package/src/hooks/viz-render/useHeatmapRender.ts +98 -0
  200. package/src/hooks/viz-render/useHeatmapVizRender.ts +22 -0
  201. package/src/hooks/viz-render/useReplayRender.ts +162 -0
  202. package/src/hooks/viz-scale/index.ts +1 -0
  203. package/src/hooks/viz-scale/useContainerDimensions.ts +46 -0
  204. package/src/hooks/viz-scale/useContentDimensions.ts +29 -0
  205. package/src/hooks/viz-scale/useHeatmapScale.ts +53 -0
  206. package/src/hooks/viz-scale/useIframeHeight.ts +119 -0
  207. package/src/hooks/viz-scale/useScaleCalculation.ts +28 -0
  208. package/src/hooks/viz-scale/useScrollSync.ts +36 -0
  209. package/src/index.ts +5 -0
  210. package/src/stores/data.ts +34 -0
  211. package/src/stores/index.ts +1 -0
  212. package/src/styles/base.css +1 -0
  213. package/src/styles/style.css +30 -0
  214. package/src/types/clarity.d.ts +38 -0
  215. package/src/types/heatmap.d.ts +3 -0
  216. package/src/types/index.d.ts +3 -0
  217. package/src/types/viz-element.d.ts +39 -0
  218. package/src/ui/BoxStack/BoxStack.tsx +120 -0
  219. package/src/ui/BoxStack/index.ts +1 -0
  220. package/src/ui/index.ts +1 -0
  221. package/src/utils/device.ts +7 -0
  222. package/src/utils/retry.ts +20 -0
  223. package/src/utils/sort.ts +5 -0
@@ -0,0 +1,764 @@
1
+ // ============================================================================
2
+ // Type Definitions
3
+ // ============================================================================
4
+
5
+ interface ViewNode {
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ visible: boolean;
11
+ hash?: string;
12
+ hashAlpha?: string;
13
+ hashBeta?: string;
14
+ renderNodeId?: number;
15
+ isWebView?: boolean;
16
+ selectorAlpha?: string;
17
+ selectorBeta?: string;
18
+ }
19
+
20
+ interface WebViewInfo {
21
+ renderNodeId: number;
22
+ visualizer: any;
23
+ div: HTMLDivElement;
24
+ scale: number;
25
+ top: number;
26
+ left: number;
27
+ visible: boolean;
28
+ orderInFrame?: number;
29
+ }
30
+
31
+ interface ViewHierarchy {
32
+ root: ViewNode;
33
+ timestamp?: number;
34
+ visibleFragments?: string[];
35
+ }
36
+
37
+ interface Frame {
38
+ viewHierarchy: ViewHierarchy;
39
+ screenWidth: number;
40
+ screenHeight: number;
41
+ density: number;
42
+ keyboardHeight?: number;
43
+ systemBackgroundColor?: number | null;
44
+ }
45
+
46
+ interface ClarityVisualizerState {
47
+ div?: HTMLElement;
48
+ window?: Window;
49
+ options?: {
50
+ version?: string;
51
+ platform?: number;
52
+ mute?: boolean;
53
+ canvas?: boolean;
54
+ keyframes?: boolean;
55
+ canvasId?: string;
56
+ heatmap?: boolean;
57
+ logError?: (error: any) => void;
58
+ fetchAssets?: (assets: any) => Promise<any>;
59
+ clickmapScrolledCallback?: () => void;
60
+ };
61
+ }
62
+
63
+ interface NodeLayout {
64
+ x: number;
65
+ y: number;
66
+ width: number;
67
+ height: number;
68
+ isNative?: boolean;
69
+ htmlElement?: HTMLElement;
70
+ selector?: string;
71
+ }
72
+
73
+ interface ElementWithHash {
74
+ top: number;
75
+ left: number;
76
+ width: number;
77
+ height: number;
78
+ hash: string;
79
+ }
80
+
81
+ interface ResizeEvent {
82
+ event: number;
83
+ time: number;
84
+ webViewRenderNodeId: number;
85
+ data: {
86
+ width: number;
87
+ height: number;
88
+ };
89
+ }
90
+
91
+ interface MutationEvent {
92
+ isKeyFrame: boolean;
93
+ serializedFrame: string;
94
+ }
95
+
96
+ // ============================================================================
97
+ // ClarityVisualizer Class Implementation
98
+ // ============================================================================
99
+
100
+ export class ClarityVisualizer {
101
+ // State properties
102
+ public state: ClarityVisualizerState | null = null;
103
+ public webViews: { [key: number]: WebViewInfo } = {};
104
+ public lastFrame: Frame | null = null;
105
+ public hashMapAlpha: { [key: string]: ViewNode } = {};
106
+ public hashMapBeta: { [key: string]: ViewNode } = {};
107
+
108
+ private lastWidth: number | null = null;
109
+ private lastHeight: number | null = null;
110
+
111
+ // Canvas/rendering wrapper (simplified - you'll need actual implementation)
112
+ private ckWrapper: any = null;
113
+ public heatmap: any = null;
114
+
115
+ // ============================================================================
116
+ // Constructor
117
+ // ============================================================================
118
+
119
+ constructor(state?: ClarityVisualizerState) {
120
+ if (state) {
121
+ this.setState(state);
122
+ }
123
+ }
124
+
125
+ // ============================================================================
126
+ // State Management
127
+ // ============================================================================
128
+
129
+ public getState(): ClarityVisualizerState | null {
130
+ return this.state;
131
+ }
132
+
133
+ public setState(state: ClarityVisualizerState): void {
134
+ this.state = state;
135
+ }
136
+
137
+ // ============================================================================
138
+ // Initialization
139
+ // ============================================================================
140
+
141
+ public async init(div: HTMLElement, canvasId?: string, heatmap?: boolean): Promise<void> {
142
+ this.webViews = {};
143
+ this.lastFrame = null;
144
+
145
+ // Initialize state
146
+ this.state = {
147
+ div,
148
+ options: {
149
+ ...this.state?.options,
150
+ canvasId,
151
+ heatmap,
152
+ },
153
+ };
154
+
155
+ // Create overlay canvas if not in heatmap mode
156
+ if (!heatmap) {
157
+ const canvas = this.overlay(div, canvasId);
158
+ // TODO: Initialize canvas wrapper
159
+ // await this.ckWrapper?.setBaseCanvas(canvas);
160
+ }
161
+ }
162
+
163
+ public reset(): void {
164
+ this.hashMapAlpha = {};
165
+ this.hashMapBeta = {};
166
+ this.lastFrame = null;
167
+ this.webViews = {};
168
+
169
+ if (this.state?.div) {
170
+ this.state.div.innerHTML = '';
171
+ }
172
+
173
+ // Reset canvas wrapper
174
+ this.ckWrapper?.reset();
175
+ }
176
+
177
+ // ============================================================================
178
+ // Node Retrieval - Core Methods
179
+ // ============================================================================
180
+
181
+ /**
182
+ * Get node layout by hash - matches fr.get() implementation
183
+ */
184
+ public get(
185
+ hash: string,
186
+ checkViewport: boolean = false,
187
+ scrollIntoView: boolean = false,
188
+ ): NodeLayout | null {
189
+ // Check hashMapBeta first (primary)
190
+ if (hash in this.hashMapBeta && this.hashMapBeta[hash].visible) {
191
+ const node = this.hashMapBeta[hash];
192
+ return {
193
+ x: node.x,
194
+ y: node.y,
195
+ width: node.width,
196
+ height: node.height,
197
+ isNative: true,
198
+ selector: node.selectorBeta,
199
+ };
200
+ }
201
+
202
+ // Check hashMapAlpha (fallback)
203
+ if (hash in this.hashMapAlpha && this.hashMapAlpha[hash].visible) {
204
+ const node = this.hashMapAlpha[hash];
205
+ return {
206
+ x: node.x,
207
+ y: node.y,
208
+ width: node.width,
209
+ height: node.height,
210
+ isNative: true,
211
+ selector: node.selectorAlpha,
212
+ };
213
+ }
214
+
215
+ // Check WebViews
216
+ const visibleWebViews = Object.values(this.webViews).filter((wv) => wv.visible);
217
+
218
+ for (const webView of visibleWebViews) {
219
+ const element = webView.visualizer?.get?.(hash);
220
+
221
+ if (element) {
222
+ // Check if element is in viewport if required
223
+ if (checkViewport && !this.isElementInViewport(element, webView.div)) {
224
+ continue;
225
+ }
226
+
227
+ // Scroll element into view if requested
228
+ if (scrollIntoView && element) {
229
+ this.scrollElementIntoView(element, webView);
230
+ }
231
+
232
+ const layout = this.getNodeLayout(webView, element);
233
+ if (layout) {
234
+ return {
235
+ ...layout,
236
+ isNative: false,
237
+ htmlElement: element,
238
+ };
239
+ }
240
+ }
241
+ }
242
+
243
+ return null;
244
+ }
245
+
246
+ /**
247
+ * Get all elements at given coordinates - matches fr.getViews() implementation
248
+ */
249
+ public getViews(x: number, y: number, useAlpha: boolean): ElementWithHash[] {
250
+ if (!this.lastFrame) return [];
251
+
252
+ const root = this.lastFrame.viewHierarchy.root;
253
+ const elements: ElementWithHash[] = [];
254
+
255
+ // Traverse view hierarchy for native elements
256
+ this.traverseViewHierarchy(this.lastFrame.viewHierarchy, (node: ViewNode) => {
257
+ if (
258
+ x > node.x &&
259
+ x < node.x + node.width &&
260
+ y > node.y &&
261
+ y < node.y + node.height &&
262
+ node.visible &&
263
+ this.intersectRect(node, root)
264
+ ) {
265
+ const hash = useAlpha ? node.hashAlpha : node.hashBeta;
266
+ if (hash) {
267
+ elements.push({
268
+ left: node.x,
269
+ top: node.y,
270
+ width: node.width,
271
+ height: node.height,
272
+ hash,
273
+ });
274
+ }
275
+ }
276
+ });
277
+
278
+ // Check WebView elements
279
+ const hashAttribute = useAlpha ? 'data-clarity-hashalpha' : 'data-clarity-hashbeta';
280
+ const processedHashes: { [key: string]: boolean } = {};
281
+
282
+ const checkWebViewElements = (
283
+ doc: Document | ShadowRoot | null,
284
+ clientX: number,
285
+ clientY: number,
286
+ webView: WebViewInfo,
287
+ ) => {
288
+ if (!doc) return;
289
+
290
+ const elementsAtPoint = doc.elementsFromPoint?.(clientX, clientY);
291
+ if (!elementsAtPoint) return;
292
+
293
+ // Reverse to get top-most elements first
294
+ elementsAtPoint.reverse();
295
+
296
+ for (const element of elementsAtPoint) {
297
+ const hash = element.getAttribute(hashAttribute);
298
+
299
+ if (hash && !processedHashes[hash]) {
300
+ processedHashes[hash] = true;
301
+
302
+ const layout = this.getNodeLayout(webView, element as HTMLElement);
303
+ if (layout) {
304
+ elements.push({
305
+ top: layout.y,
306
+ left: layout.x,
307
+ width: layout.width,
308
+ height: layout.height,
309
+ hash,
310
+ });
311
+ }
312
+
313
+ // Check shadow root
314
+ const shadowRoot = (element as HTMLElement).shadowRoot;
315
+ if (shadowRoot) {
316
+ checkWebViewElements(shadowRoot, clientX, clientY, webView);
317
+ }
318
+ }
319
+ }
320
+ };
321
+
322
+ // Process all visible webviews
323
+ for (const webView of Object.values(this.webViews)) {
324
+ if (webView.visible && webView.visualizer?.state?.window?.document) {
325
+ const doc = webView.visualizer.state.window.document;
326
+ const localX = x / webView.scale - webView.left;
327
+ const localY = y / webView.scale - webView.top;
328
+
329
+ checkWebViewElements(doc, Math.round(localX), Math.round(localY), webView);
330
+ }
331
+ }
332
+
333
+ return elements;
334
+ }
335
+
336
+ /**
337
+ * Get node layout from HTML element
338
+ */
339
+ public getNodeLayout(webView: WebViewInfo, element: HTMLElement): NodeLayout | null {
340
+ if (typeof element.getBoundingClientRect !== 'function') {
341
+ return null;
342
+ }
343
+
344
+ const rect = element.getBoundingClientRect();
345
+ const scrollLeft =
346
+ 'pageXOffset' in window ? window.pageXOffset : document.documentElement.scrollLeft;
347
+ const scrollTop =
348
+ 'pageYOffset' in window ? window.pageYOffset : document.documentElement.scrollTop;
349
+
350
+ if (rect && (rect.height !== 0 || rect.width !== 0)) {
351
+ return {
352
+ x:
353
+ (Math.floor(rect.left + scrollLeft) + webView.left - webView.div.scrollLeft) *
354
+ webView.scale,
355
+ y: (Math.floor(rect.top + scrollTop) + webView.top - webView.div.scrollTop) * webView.scale,
356
+ width: Math.floor(rect.width) * webView.scale,
357
+ height: Math.floor(rect.height) * webView.scale,
358
+ };
359
+ }
360
+
361
+ return null;
362
+ }
363
+
364
+ // ============================================================================
365
+ // Frame Processing
366
+ // ============================================================================
367
+
368
+ /**
369
+ * Process mutation event and update frame
370
+ */
371
+ public async mutation(event: MutationEvent): Promise<void> {
372
+ try {
373
+ // Parse frame
374
+ if (event.isKeyFrame) {
375
+ this.lastFrame = JSON.parse(event.serializedFrame);
376
+ } else {
377
+ // Apply incremental update (you'll need to implement merge logic)
378
+ if (this.lastFrame) {
379
+ this.applyFrameUpdate(this.lastFrame, JSON.parse(event.serializedFrame));
380
+ }
381
+ }
382
+
383
+ // Update hash maps
384
+ this.updateHashMaps();
385
+
386
+ // Handle resize if dimensions changed
387
+ if (this.lastFrame) {
388
+ if (
389
+ this.lastFrame.screenWidth !== this.lastWidth ||
390
+ this.lastFrame.screenHeight !== this.lastHeight
391
+ ) {
392
+ await this.resize({
393
+ event: 11,
394
+ time: 0,
395
+ webViewRenderNodeId: -2,
396
+ data: {
397
+ width: this.lastFrame.screenWidth,
398
+ height: this.lastFrame.screenHeight,
399
+ },
400
+ });
401
+ }
402
+
403
+ // Update WebViews
404
+ await this.updateWebViews();
405
+
406
+ // Draw frame
407
+ // await this.ckWrapper?.drawFrame(this.lastFrame);
408
+
409
+ // Draw keyboard if present
410
+ const keyboardHeight = this.lastFrame.keyboardHeight ?? 0;
411
+ this.drawKeyboard(keyboardHeight / this.lastFrame.screenHeight);
412
+
413
+ // Set background color
414
+ this.setSystemBackgroundColor(this.lastFrame.systemBackgroundColor ?? null);
415
+ }
416
+ } catch (error) {
417
+ this.state?.options?.logError?.(error);
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Update hash maps from current frame
423
+ */
424
+ public updateHashMaps(): void {
425
+ this.hashMapAlpha = {};
426
+ this.hashMapBeta = {};
427
+
428
+ if (!this.lastFrame) return;
429
+
430
+ this.traverseViewHierarchy(this.lastFrame.viewHierarchy, (node: ViewNode) => {
431
+ if (node.visible) {
432
+ if (node.hashAlpha) {
433
+ this.hashMapAlpha[node.hashAlpha] = node;
434
+ }
435
+ if (node.hashBeta) {
436
+ this.hashMapBeta[node.hashBeta] = node;
437
+ }
438
+ }
439
+ });
440
+ }
441
+
442
+ /**
443
+ * Handle resize event
444
+ */
445
+ public async resize(event: ResizeEvent): Promise<void> {
446
+ const canvas = document.getElementById('clarity-android-canvas') as HTMLCanvasElement;
447
+
448
+ if (canvas) {
449
+ canvas.width = event.data.width;
450
+ canvas.height = event.data.height;
451
+ this.lastWidth = event.data.width;
452
+ this.lastHeight = event.data.height;
453
+
454
+ // await this.ckWrapper?.setBaseCanvas(canvas);
455
+ }
456
+ }
457
+
458
+ // ============================================================================
459
+ // WebView Management
460
+ // ============================================================================
461
+
462
+ public getWebViewWindows(): Window[] {
463
+ return Object.keys(this.webViews)
464
+ .map(Number)
465
+ .map((id) => this.webViews[id].visualizer?.state?.window)
466
+ .filter(Boolean);
467
+ }
468
+
469
+ public webViewDivs(): HTMLDivElement[] {
470
+ return Object.keys(this.webViews)
471
+ .map(Number)
472
+ .map((id) => this.webViews[id].div);
473
+ }
474
+
475
+ private async updateWebViews(): Promise<void> {
476
+ if (!this.lastFrame) return;
477
+
478
+ const webViewNodes = this.getWebViewNodes(this.lastFrame.viewHierarchy);
479
+ this.createWebViewDivs(webViewNodes);
480
+ this.hideWebViewDivs(webViewNodes);
481
+
482
+ // Update WebView positions and visibility
483
+ for (const node of webViewNodes) {
484
+ if (this.webViews[node.renderNodeId!]) {
485
+ const webView = this.webViews[node.renderNodeId!];
486
+ webView.visible = node.visible;
487
+
488
+ if (webView.div) {
489
+ webView.div.style.visibility = node.visible ? 'visible' : 'hidden';
490
+ webView.div.style.position = 'absolute';
491
+ webView.div.style.top = `${node.y}px`;
492
+ webView.div.style.left = `${node.x}px`;
493
+ webView.div.style.width = `${node.width}px`;
494
+ webView.div.style.height = `${node.height}px`;
495
+ }
496
+ }
497
+ }
498
+ }
499
+
500
+ private createWebViewDivs(nodes: ViewNode[]): void {
501
+ for (const node of nodes) {
502
+ if (node.renderNodeId && !this.webViews[node.renderNodeId]) {
503
+ const div = document.createElement('div');
504
+ div.id = `clarity-webview-div-${node.renderNodeId}`;
505
+ div.style.visibility = 'hidden';
506
+
507
+ this.state?.div?.appendChild(div);
508
+
509
+ this.webViews[node.renderNodeId] = {
510
+ renderNodeId: node.renderNodeId,
511
+ visualizer: null,
512
+ div,
513
+ scale: 1,
514
+ top: 0,
515
+ left: 0,
516
+ visible: false,
517
+ };
518
+ }
519
+ }
520
+ }
521
+
522
+ private hideWebViewDivs(visibleNodes: ViewNode[]): void {
523
+ const visibleIds = new Set(visibleNodes.map((n) => n.renderNodeId).filter(Boolean));
524
+
525
+ for (const id of Object.keys(this.webViews).map(Number)) {
526
+ if (!visibleIds.has(id)) {
527
+ this.webViews[id].div.style.visibility = 'hidden';
528
+ this.webViews[id].visible = false;
529
+ }
530
+ }
531
+ }
532
+
533
+ // ============================================================================
534
+ // Callback Setup
535
+ // ============================================================================
536
+
537
+ public setAndroidClickmapScrolledCallback(callback: () => void): void {
538
+ if (this.state?.options) {
539
+ this.state.options.clickmapScrolledCallback = callback;
540
+ }
541
+ }
542
+
543
+ // ============================================================================
544
+ // Asset Management
545
+ // ============================================================================
546
+
547
+ public async preloadImpressionAssets(frames: Frame[]): Promise<void> {
548
+ // TODO: Implement asset preloading logic
549
+ console.log('Preloading assets for', frames.length, 'frames');
550
+ }
551
+
552
+ // ============================================================================
553
+ // Helper Methods
554
+ // ============================================================================
555
+
556
+ private traverseViewHierarchy(
557
+ hierarchy: ViewHierarchy,
558
+ callback: (node: ViewNode) => void,
559
+ ): void {
560
+ const traverse = (node: ViewNode) => {
561
+ callback(node);
562
+ // If node has children, traverse them (you'll need to add children property)
563
+ // For now, just call on root
564
+ };
565
+
566
+ traverse(hierarchy.root);
567
+ }
568
+
569
+ private getWebViewNodes(hierarchy: ViewHierarchy): ViewNode[] {
570
+ const webViewNodes: ViewNode[] = [];
571
+
572
+ this.traverseViewHierarchy(hierarchy, (node) => {
573
+ if (node.isWebView) {
574
+ webViewNodes.push(node);
575
+ }
576
+ });
577
+
578
+ return webViewNodes;
579
+ }
580
+
581
+ private intersectRect(rect1: ViewNode, rect2: ViewNode): boolean {
582
+ return !(
583
+ rect2.x > rect1.x + rect1.width ||
584
+ rect2.x + rect2.width < rect1.x ||
585
+ rect2.y > rect1.y + rect1.height ||
586
+ rect2.y + rect2.height < rect1.y
587
+ );
588
+ }
589
+
590
+ private isElementInViewport(element: HTMLElement, container: HTMLDivElement): boolean {
591
+ const rect = element.getBoundingClientRect();
592
+
593
+ return this.intersectRect(
594
+ {
595
+ x: rect.left,
596
+ y: rect.top,
597
+ width: rect.width,
598
+ height: rect.height,
599
+ visible: true,
600
+ },
601
+ {
602
+ x: container.scrollLeft,
603
+ y: container.scrollTop,
604
+ width: container.clientWidth,
605
+ height: container.clientHeight,
606
+ visible: true,
607
+ },
608
+ );
609
+ }
610
+
611
+ private scrollElementIntoView(element: HTMLElement, webView: WebViewInfo): void {
612
+ element.style.scrollMarginBlock = '100px';
613
+ element.style.scrollMarginInline = '50px';
614
+
615
+ // Add scroll listener to redraw heatmap
616
+ const onScroll = () => {
617
+ setTimeout(() => {
618
+ this.heatmap?.redraw?.(null);
619
+ }, 200);
620
+ };
621
+
622
+ // Add scroll listeners to all parent elements
623
+ let parent: Node | null = element;
624
+ while (parent) {
625
+ parent =
626
+ parent.parentNode ||
627
+ (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE ? (parent as any).host : null);
628
+
629
+ if (parent) {
630
+ (parent as Element).addEventListener?.('scroll', onScroll, {
631
+ capture: true,
632
+ once: false,
633
+ passive: true,
634
+ });
635
+ }
636
+ }
637
+
638
+ element.scrollIntoView({
639
+ behavior: 'smooth',
640
+ block: 'start',
641
+ inline: 'nearest',
642
+ });
643
+ }
644
+
645
+ private overlay(div: HTMLElement, canvasId?: string): HTMLCanvasElement {
646
+ const id = `clarity-android-canvas${canvasId ? `-${canvasId}` : ''}`;
647
+ let canvas = document.getElementById(id) as HTMLCanvasElement;
648
+
649
+ if (canvas?.parentNode) {
650
+ canvas.parentNode.removeChild(canvas);
651
+ }
652
+
653
+ canvas = document.createElement('canvas');
654
+ canvas.id = id;
655
+ canvas.width = div.offsetWidth;
656
+ canvas.height = div.offsetHeight;
657
+ canvas.style.position = 'absolute';
658
+ canvas.style.top = '0';
659
+ canvas.style.left = '0';
660
+
661
+ div.prepend(canvas);
662
+
663
+ return canvas;
664
+ }
665
+
666
+ private drawKeyboard(heightRatio: number): void {
667
+ if (!this.state?.div) return;
668
+
669
+ const doc = document;
670
+ let keyboardLayer = doc.getElementById('clarity-keyboard-layer');
671
+
672
+ if (heightRatio > 0 && !keyboardLayer) {
673
+ keyboardLayer = this.createKeyboardLayer(doc, heightRatio);
674
+ this.state.div.appendChild(keyboardLayer);
675
+ } else if (heightRatio <= 0 && keyboardLayer) {
676
+ keyboardLayer.remove();
677
+ }
678
+ }
679
+
680
+ private createKeyboardLayer(doc: Document, heightRatio: number): HTMLDivElement {
681
+ const layer = doc.createElement('div');
682
+ layer.id = 'clarity-keyboard-layer';
683
+
684
+ const style = doc.createElement('style');
685
+ style.textContent = this.getKeyboardLayerStyle(heightRatio);
686
+ layer.appendChild(style);
687
+ layer.setAttribute('title', 'Keyboard Event');
688
+
689
+ return layer;
690
+ }
691
+
692
+ private getKeyboardLayerStyle(heightRatio: number): string {
693
+ return `
694
+ @keyframes fade-in {
695
+ 0% { bottom: -100% }
696
+ 100% { bottom: 0 }
697
+ }
698
+
699
+ #clarity-keyboard-layer {
700
+ position: absolute;
701
+ bottom: 0;
702
+ left: 0;
703
+ z-index: 2147483647;
704
+ width: 100%;
705
+ height: ${100 * heightRatio}%;
706
+ background-repeat: no-repeat;
707
+ background-color: #EDEBE9;
708
+ background-size: 100% 100%;
709
+ background-position: center center;
710
+ animation: fade-in 0.5s ease-in-out;
711
+ }
712
+ `;
713
+ }
714
+
715
+ private setSystemBackgroundColor(color: number | null): void {
716
+ if (!this.state?.div) return;
717
+
718
+ if (color !== null) {
719
+ const rgba = this.colorIntToRgba(color);
720
+ this.state.div.style.backgroundColor = `rgba(${rgba[0]}, ${rgba[1]}, ${rgba[2]}, ${rgba[3]})`;
721
+ } else {
722
+ this.state.div.style.backgroundColor = '';
723
+ }
724
+ }
725
+
726
+ private colorIntToRgba(color: number): [number, number, number, number] {
727
+ return [
728
+ (color >> 16) & 0xff, // R
729
+ (color >> 8) & 0xff, // G
730
+ color & 0xff, // B
731
+ ((color >> 24) & 0xff) / 255, // A
732
+ ];
733
+ }
734
+
735
+ private applyFrameUpdate(baseFrame: Frame, update: Partial<Frame>): void {
736
+ // TODO: Implement incremental frame update logic
737
+ // This should merge the update into the base frame
738
+ Object.assign(baseFrame, update);
739
+ }
740
+
741
+ public getWebViewRenderNodeIdsFromKeyFrame(event: MutationEvent): Set<number> {
742
+ const ids = new Set<number>();
743
+
744
+ if (!event.isKeyFrame) return ids;
745
+
746
+ try {
747
+ const frame = JSON.parse(event.serializedFrame);
748
+
749
+ if (frame?.viewHierarchy?.root) {
750
+ this.traverseViewHierarchy(frame.viewHierarchy, (node) => {
751
+ if (node.isWebView && node.renderNodeId) {
752
+ ids.add(node.renderNodeId);
753
+ }
754
+ });
755
+ }
756
+ } catch (error) {
757
+ console.error('Failed to parse keyframe:', error);
758
+ }
759
+
760
+ return ids;
761
+ }
762
+ }
763
+
764
+ export default ClarityVisualizer;