clarity-visualize 0.6.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.
package/src/layout.ts ADDED
@@ -0,0 +1,351 @@
1
+ import { Data, Layout } from "clarity-decode";
2
+ import { Asset, Constant, NodeType, Setting } from "@clarity-types/visualize";
3
+ import { state } from "./clarity";
4
+
5
+ const TIMEOUT = 3000;
6
+ let stylesheets: Promise<void>[] = [];
7
+ let nodes = {};
8
+ let events = {};
9
+ let hashMap = {};
10
+
11
+ export function reset(): void {
12
+ nodes = {};
13
+ stylesheets = [];
14
+ events = {};
15
+ hashMap = {};
16
+ }
17
+
18
+ export function get(hash) {
19
+ var element = hash in hashMap ? (hashMap[hash].isConnected ? hashMap[hash] : null) : null;
20
+ return element;
21
+ }
22
+
23
+ export function box(event: Layout.BoxEvent): void {
24
+ let data = event.data;
25
+ for (let b of data) {
26
+ let el = element(b.id) as HTMLElement;
27
+ resize(el, b.width, b.height);
28
+ }
29
+ }
30
+
31
+ function addToHashMap(hash, node)
32
+ {
33
+ // In case of selector collision, prefer the first inserted node
34
+ let element = get(hash)
35
+ hashMap[hash] = element ? element : node;
36
+ }
37
+
38
+ function resize(el: HTMLElement, width: number, height: number): void {
39
+ if (el && el.nodeType === NodeType.ELEMENT_NODE && width && height) {
40
+ el.style.width = width + Layout.Constant.Pixel;
41
+ el.style.height = height + Layout.Constant.Pixel;
42
+ el.style.boxSizing = Layout.Constant.BorderBox; // Reference: https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing
43
+ }
44
+ }
45
+
46
+ export function element(nodeId: number): Node {
47
+ return nodeId !== null && nodeId > 0 && nodeId in nodes ? nodes[nodeId] : null;
48
+ }
49
+
50
+ export async function dom(event: Layout.DomEvent): Promise<void> {
51
+ if (event) {
52
+ // When setting up rendering for the first time, start off with hidden target window
53
+ // This ensures we do not show flickers to the end user
54
+ let doc = state.window.document;
55
+ if (doc && doc.documentElement) {
56
+ doc.documentElement.style.visibility = Constant.Hidden;
57
+ // Render all DOM events to reconstruct the page
58
+ markup(event);
59
+ // Wait on all stylesheets to finish loading
60
+ await Promise.all(stylesheets);
61
+ // Toggle back the visibility of target window
62
+ doc.documentElement.style.visibility = Constant.Visible;
63
+ }
64
+ }
65
+ }
66
+
67
+ export function exists(hash: string): boolean {
68
+ if (hash) {
69
+ let match = get(hash);
70
+ if (match) {
71
+ let rectangle = match.getBoundingClientRect();
72
+ return rectangle && rectangle.width > 0 && rectangle.height > 0;
73
+ }
74
+ }
75
+ return false;
76
+ }
77
+
78
+ export function markup(event: Layout.DomEvent): void {
79
+ let data = event.data;
80
+ let type = event.event;
81
+ let doc = state.window.document;
82
+ for (let node of data) {
83
+ let parent = element(node.parent);
84
+ let pivot = element(node.previous);
85
+ let insert = insertAfter;
86
+
87
+ let tag = node.tag;
88
+ if (tag && tag.indexOf(Layout.Constant.IFramePrefix) === 0) { tag = node.tag.substr(Layout.Constant.IFramePrefix.length); }
89
+ switch (tag) {
90
+ case Layout.Constant.DocumentTag:
91
+ let tagDoc = tag !== node.tag ? (parent ? (parent as HTMLIFrameElement).contentDocument : null): doc;
92
+ if (tagDoc && tagDoc === doc && type === Data.Event.Discover) { reset(); }
93
+ if (typeof XMLSerializer !== "undefined" && tagDoc) {
94
+ tagDoc.open();
95
+ tagDoc.write(new XMLSerializer().serializeToString(
96
+ tagDoc.implementation.createDocumentType(
97
+ node.attributes["name"],
98
+ node.attributes["publicId"],
99
+ node.attributes["systemId"]
100
+ )
101
+ ));
102
+ tagDoc.close();
103
+ }
104
+ break;
105
+ case Layout.Constant.PolyfillShadowDomTag:
106
+ // In case of polyfill, map shadow dom to it's parent for rendering purposes
107
+ // All its children should be inserted as regular children to the parent node.
108
+ nodes[node.id] = parent;
109
+ addToHashMap(node.hash, parent);
110
+ break;
111
+ case Layout.Constant.ShadowDomTag:
112
+ if (parent) {
113
+ let shadowRoot = element(node.id);
114
+ shadowRoot = shadowRoot ? shadowRoot : (parent as HTMLElement).attachShadow({ mode: "open" });
115
+ if ("style" in node.attributes) {
116
+ let shadowStyle = doc.createElement("style");
117
+ // Support for adoptedStyleSheet is limited and not available in all browsers.
118
+ // To ensure that we can replay session in any browser, we turn adoptedStyleSheets from recording
119
+ // into classic style tags at the playback time.
120
+ if (shadowRoot.firstChild && (shadowRoot.firstChild as HTMLElement).id === Constant.AdoptedStyleSheet) {
121
+ shadowStyle = shadowRoot.firstChild as HTMLStyleElement;
122
+ }
123
+ shadowStyle.id = Constant.AdoptedStyleSheet;
124
+ shadowStyle.textContent = node.attributes["style"];
125
+ shadowRoot.appendChild(shadowStyle);
126
+ }
127
+ nodes[node.id] = shadowRoot;
128
+ addToHashMap(node.hash, shadowRoot);
129
+ }
130
+ break;
131
+ case Layout.Constant.TextTag:
132
+ let textElement = element(node.id);
133
+ textElement = textElement ? textElement : doc.createTextNode(null);
134
+ textElement.nodeValue = node.value;
135
+ insert(node, parent, textElement, pivot);
136
+ break;
137
+ case Layout.Constant.SuspendMutationTag:
138
+ let suspendedElement = element(node.id);
139
+ if (suspendedElement && suspendedElement.nodeType === Node.ELEMENT_NODE) {
140
+ (suspendedElement as HTMLElement).setAttribute(Constant.Suspend, Layout.Constant.Empty);
141
+ }
142
+ break;
143
+ case "HTML":
144
+ let htmlDoc = tag !== node.tag ? (parent ? (parent as HTMLIFrameElement).contentDocument : null): doc;
145
+ if (htmlDoc !== null) {
146
+ let docElement = element(node.id) as HTMLElement;
147
+ if (docElement === null) {
148
+ let newDoc = htmlDoc.implementation.createHTMLDocument(Layout.Constant.Empty);
149
+ docElement = newDoc.documentElement;
150
+ let p = htmlDoc.importNode(docElement, true);
151
+ htmlDoc.replaceChild(p, htmlDoc.documentElement);
152
+ if (htmlDoc.head) { htmlDoc.head.parentNode.removeChild(htmlDoc.head); }
153
+ if (htmlDoc.body) { htmlDoc.body.parentNode.removeChild(htmlDoc.body); }
154
+ }
155
+ setAttributes(htmlDoc.documentElement, node);
156
+ // If we are still processing discover events, keep the markup hidden until we are done
157
+ if (type === Data.Event.Discover) { htmlDoc.documentElement.style.visibility = Constant.Hidden; }
158
+ nodes[node.id] = htmlDoc.documentElement;
159
+ addToHashMap(node.hash, htmlDoc.documentElement);
160
+ }
161
+ break;
162
+ case "HEAD":
163
+ let headElement = element(node.id);
164
+ if (headElement === null) {
165
+ headElement = doc.createElement(node.tag);
166
+ if (node.attributes && Layout.Constant.Base in node.attributes) {
167
+ let base = doc.createElement("base");
168
+ base.href = node.attributes[Layout.Constant.Base];
169
+ headElement.appendChild(base);
170
+ }
171
+
172
+ // Add custom styles to assist with visualization
173
+ let custom = doc.createElement("style");
174
+ custom.innerText = getCustomStyle();
175
+ headElement.appendChild(custom);
176
+ }
177
+ setAttributes(headElement as HTMLElement, node);
178
+ insert(node, parent, headElement, pivot);
179
+ break;
180
+ case "LINK":
181
+ let linkElement = element(node.id) as HTMLLinkElement;
182
+ linkElement = linkElement ? linkElement : createElement(doc, node.tag) as HTMLLinkElement;
183
+ if (!node.attributes) { node.attributes = {}; }
184
+ setAttributes(linkElement as HTMLElement, node);
185
+ if ("rel" in node.attributes && node.attributes["rel"] === "stylesheet") {
186
+ stylesheets.push(new Promise((resolve: () => void): void => {
187
+ linkElement.onload = linkElement.onerror = style.bind(this, linkElement, resolve);
188
+ setTimeout(resolve, TIMEOUT);
189
+ }));
190
+ }
191
+ insert(node, parent, linkElement, pivot);
192
+ break;
193
+ case "STYLE":
194
+ let styleElement = element(node.id) as HTMLStyleElement;
195
+ styleElement = styleElement ? styleElement : doc.createElement(node.tag) as HTMLStyleElement;
196
+ setAttributes(styleElement as HTMLElement, node);
197
+ styleElement.textContent = node.value;
198
+ insert(node, parent, styleElement, pivot);
199
+ style(styleElement);
200
+ break;
201
+ case "IFRAME":
202
+ let iframeElement = element(node.id) as HTMLElement;
203
+ iframeElement = iframeElement ? iframeElement : createElement(doc, node.tag);
204
+ if (!node.attributes) { node.attributes = {}; }
205
+ setAttributes(iframeElement as HTMLElement, node);
206
+ insert(node, parent, iframeElement, pivot);
207
+ break;
208
+ default:
209
+ let domElement = element(node.id) as HTMLElement;
210
+ domElement = domElement ? domElement : createElement(doc, node.tag);
211
+ setAttributes(domElement as HTMLElement, node);
212
+ resize(domElement, node.width, node.height);
213
+ insert(node, parent, domElement, pivot);
214
+ break;
215
+ }
216
+ // Track state for this node
217
+ if (node.id) { events[node.id] = node; }
218
+ }
219
+ }
220
+
221
+ function style(node: HTMLLinkElement | HTMLStyleElement, resolve: () => void = null): void {
222
+ // Firefox throws a SecurityError when trying to access cssRules of a stylesheet from a different domain
223
+ try {
224
+ const sheet = node.sheet as CSSStyleSheet;
225
+ let cssRules = sheet ? sheet.cssRules : [];
226
+ for (let i = 0; i < cssRules.length; i++) {
227
+ if (cssRules[i].cssText.indexOf(Constant.Hover) >= 0) {
228
+ let css = cssRules[i].cssText.replace(/:hover/g, `[${Constant.CustomHover}]`);
229
+ sheet.removeRule(i);
230
+ sheet.insertRule(css, i);
231
+ }
232
+ }
233
+ } catch { /* do nothing */ }
234
+
235
+ if (resolve) { resolve(); }
236
+ }
237
+
238
+ function createElement(doc: Document, tag: string): HTMLElement {
239
+ if (tag && tag.indexOf(Layout.Constant.SvgPrefix) === 0) {
240
+ return doc.createElementNS(Layout.Constant.SvgNamespace as string, tag.substr(Layout.Constant.SvgPrefix.length)) as HTMLElement;
241
+ }
242
+ try { return doc.createElement(tag); } catch (ex) {
243
+ // We log the warning on non-standard markup but continue with the visualization
244
+ console.warn(`Exception encountered while creating element ${tag}: ${ex}`);
245
+ return doc.createElement(Constant.UnknownTag);
246
+ };
247
+ }
248
+
249
+ function insertAfter(data: Layout.DomData, parent: Node, node: Node, previous: Node): void {
250
+ // Skip over no-op changes where parent and previous element is still the same
251
+ // In case of IFRAME, re-adding DOM at the exact same place will lead to loss of state and the markup inside will be destroyed
252
+ if (events[data.id] && events[data.id].parent === data.parent && events[data.id].previous === data.previous) { return; }
253
+ let next = previous && previous.parentElement === parent ? previous.nextSibling : null;
254
+ next = previous === null && parent ? firstChild(parent) : next;
255
+ insertBefore(data, parent, node, next);
256
+ }
257
+
258
+ function firstChild(node: Node): ChildNode {
259
+ let child = node.firstChild;
260
+ // BASE tag should always be the first child to ensure resources with relative URLs are loaded correctly
261
+ if (child && child.nodeType === NodeType.ELEMENT_NODE && (child as HTMLElement).tagName === Layout.Constant.BaseTag) {
262
+ return child.nextSibling;
263
+ }
264
+ return child;
265
+ }
266
+
267
+ function insertBefore(data: Layout.DomData, parent: Node, node: Node, next: Node): void {
268
+ if (parent !== null) {
269
+ next = next && next.parentElement !== parent ? null : next;
270
+ try {
271
+ parent.insertBefore(node, next);
272
+ } catch (ex) {
273
+ console.warn("Node: " + node + " | Parent: " + parent + " | Data: " + JSON.stringify(data));
274
+ console.warn("Exception encountered while inserting node: " + ex);
275
+ }
276
+ } else if (parent === null && node.parentElement !== null) {
277
+ node.parentElement.removeChild(node);
278
+ }
279
+ nodes[data.id] = node;
280
+ addToHashMap(data.hash, node);
281
+ }
282
+
283
+ function setAttributes(node: HTMLElement, data: Layout.DomData): void {
284
+ let attributes = data.attributes || {};
285
+ let sameorigin = false;
286
+
287
+ // Clarity attributes
288
+ attributes[Constant.Id] = `${data.id}`;
289
+ attributes[Constant.Hash] = `${data.hash}`;
290
+
291
+ let tag = node.nodeType === NodeType.ELEMENT_NODE ? node.tagName.toLowerCase() : null;
292
+ // First remove all its existing attributes
293
+ if (node.attributes) {
294
+ let length = node.attributes.length;
295
+ while (node.attributes && length > 0) {
296
+ // Do not remove "clarity-hover" attribute and let it be managed by interaction module
297
+ // This helps avoid flickers during visualization
298
+ if (node.attributes[0].name !== Constant.HoverAttribute) {
299
+ node.removeAttribute(node.attributes[0].name);
300
+ }
301
+ length--;
302
+ }
303
+ }
304
+
305
+ // Add new attributes
306
+ for (let attribute in attributes) {
307
+ if (attributes[attribute] !== undefined) {
308
+ try {
309
+ let v = attributes[attribute];
310
+ if (attribute.indexOf("xlink:") === 0) {
311
+ node.setAttributeNS("http://www.w3.org/1999/xlink", attribute, v);
312
+ } else if (attribute.indexOf(Layout.Constant.SameOrigin) === 0) {
313
+ sameorigin = true;
314
+ } else if (attribute.indexOf("*") === 0) {
315
+ // Do nothing if we encounter internal Clarity attributes
316
+ } else if (tag === Constant.IFrameTag && (attribute.indexOf("src") === 0 || attribute.indexOf("allow") === 0) || attribute === "sandbox") {
317
+ node.setAttribute(`data-clarity-${attribute}`, v);
318
+ } else if (tag === Constant.ImageTag && attribute.indexOf("src") === 0 && (v === null || v.length === 0)) {
319
+ node.setAttribute(attribute, Asset.Transparent);
320
+ let size = Constant.Large;
321
+ if (data.width) {
322
+ size = data.width <= Setting.Medium ? Constant.Medium : (data.width <= Setting.Small ? Constant.Small : size);
323
+ }
324
+ node.setAttribute(Constant.Hide, size);
325
+ } else {
326
+ node.setAttribute(attribute, v);
327
+ }
328
+ } catch (ex) {
329
+ console.warn("Node: " + node + " | " + JSON.stringify(attributes));
330
+ console.warn("Exception encountered while adding attributes: " + ex);
331
+ }
332
+ }
333
+ }
334
+
335
+ if (sameorigin === false && tag === Constant.IFrameTag && typeof node.setAttribute == Constant.Function) {
336
+ node.setAttribute(Constant.Unavailable, Layout.Constant.Empty);
337
+ }
338
+
339
+ // Add an empty ALT tag on all IMG elements
340
+ if (tag === Constant.ImageTag && !node.hasAttribute(Constant.AltAttribute)) { node.setAttribute(Constant.AltAttribute, Constant.Empty); }
341
+ }
342
+
343
+ function getCustomStyle(): string {
344
+ // tslint:disable-next-line: max-line-length
345
+ return `${Constant.ImageTag}[${Constant.Hide}] { background-color: #CCC; background-image: url(${Asset.Hide}); background-repeat:no-repeat; background-position: center; }` +
346
+ `${Constant.ImageTag}[${Constant.Hide}=${Constant.Small}] { background-size: 18px 18px; }` +
347
+ `${Constant.ImageTag}[${Constant.Hide}=${Constant.Medium}] { background-size: 24px 24px; }` +
348
+ `${Constant.ImageTag}[${Constant.Hide}=${Constant.Large}] { background-size: 36px 36px; }` +
349
+ `${Constant.IFrameTag}[${Constant.Unavailable}] { background: url(${Asset.Unavailable}) no-repeat center center, url('${Asset.Cross}'); }` +
350
+ `*[${Constant.Suspend}] { filter: grayscale(100%); }`;
351
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "esnext",
4
+ "target": "es5",
5
+ "lib": ["es6", "dom", "es2016", "es2017"],
6
+ "moduleResolution": "node",
7
+ "forceConsistentCasingInFileNames": true,
8
+ "noImplicitReturns": true,
9
+ "noUnusedLocals": true,
10
+ "noUnusedParameters": true,
11
+ "esModuleInterop": true,
12
+ "baseUrl": ".",
13
+ "paths": {
14
+ "@src/*": ["src/*"],
15
+ "@clarity-types/*": ["types/*"]
16
+ }
17
+ },
18
+ "include":["src/**/*.ts","types/**/*.d.ts"],
19
+ "exclude": ["node_modules", "build"]
20
+ }
package/tslint.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "extends": "tslint:recommended",
3
+ "rules": {
4
+ "max-line-length": [
5
+ true,
6
+ 140
7
+ ],
8
+ "no-console": [
9
+ false
10
+ ],
11
+ "no-string-literal": false,
12
+ "prefer-const": false,
13
+ "prefer-for-of": false,
14
+ "object-literal-sort-keys": false,
15
+ "one-variable-per-declaration": false,
16
+ "trailing-comma": [
17
+ false
18
+ ],
19
+ "variable-name": false,
20
+ "interface-name": false,
21
+ "interface-over-type-literal": false,
22
+ "only-arrow-functions": false,
23
+ "typedef": [
24
+ true,
25
+ "call-signature",
26
+ "parameter",
27
+ "property-declaration",
28
+ "member-variable-declaration",
29
+ "object-destructuring",
30
+ "array-destructuring"
31
+ ]
32
+ }
33
+ }
@@ -0,0 +1,19 @@
1
+ import { Activity, MergedPayload, Options, ResizeHandler, ScrollMapInfo } from "./visualize";
2
+ import { Data, Diagnostic, Interaction, Layout } from "clarity-decode"
3
+
4
+ export interface Visualize {
5
+ dom: (event: Layout.DomEvent) => Promise<void>;
6
+ html: (decoded: Data.DecodedPayload[], target: Window, hash?: string, time?: number) => Visualize;
7
+ clickmap: (activity?: Activity) => void;
8
+ clearmap: () => void;
9
+ scrollmap: (data?: ScrollMapInfo[], averageFold?: number) => void;
10
+ merge: (decoded: Data.DecodedPayload[]) => MergedPayload;
11
+ render: (events: Data.DecodedEvent[]) => void;
12
+ setup: (target: Window, options: Options) => Visualize;
13
+ time: () => number;
14
+ get: (hash: string) => HTMLElement;
15
+ }
16
+
17
+ declare const visualize: Visualize;
18
+
19
+ export { visualize, Data, Diagnostic, Interaction, Layout, MergedPayload, ResizeHandler };
@@ -0,0 +1,154 @@
1
+ import { Data, Layout } from "clarity-decode";
2
+
3
+
4
+ export type ResizeHandler = (width: number, height: number) => void;
5
+
6
+ export interface MergedPayload {
7
+ timestamp: number;
8
+ envelope: Data.Envelope;
9
+ dom: Layout.DomEvent;
10
+ events: Data.DecodedEvent[];
11
+ }
12
+
13
+ export interface Point {
14
+ time: number;
15
+ x: number;
16
+ y: number;
17
+ }
18
+
19
+ export interface Options {
20
+ version: string;
21
+ dom?: Layout.DomEvent;
22
+ onresize?: ResizeHandler;
23
+ metadata?: HTMLElement;
24
+ canvas?: boolean;
25
+ keyframes?: boolean;
26
+ }
27
+
28
+ export interface PlaybackState {
29
+ window: Window;
30
+ options: Options;
31
+ }
32
+
33
+ export type Activity = ElementData[];
34
+
35
+ export interface ScrollMapInfo {
36
+ scrollReachY: number;
37
+ cumulativeSum: number;
38
+ percUsers: number;
39
+ }
40
+
41
+
42
+ export interface ElementData {
43
+ hash: string;
44
+ selector: string;
45
+ totalclicks: number;
46
+ x: number[];
47
+ y: number[];
48
+ clicks: number[];
49
+ points: number;
50
+ }
51
+
52
+ export interface Heatmap {
53
+ x: number; /* X Coordinate */
54
+ y: number; /* Y Coordinate */
55
+ a: number; /* Alpha */
56
+ }
57
+
58
+ export const enum NodeType {
59
+ ELEMENT_NODE = 1,
60
+ ATTRIBUTE_NODE = 2,
61
+ TEXT_NODE = 3,
62
+ COMMENT_NODE = 8,
63
+ DOCUMENT_NODE = 9,
64
+ DOCUMENT_TYPE_NODE = 10
65
+ }
66
+
67
+ export const enum Asset {
68
+ Pointer = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAmCAYAAAA4LpBhAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAASDSURBVHgB7VdPTCNlFH8z0/+FFmRatnFNiDGR4O4mBk08smZvXjjIxRueNME9eHGNxoLxSNwr4WyigYToQRJLjXDzQtDNmnhR4kWWAJm20ymddtrx94bvI9NBWAptsod9ycvM92fe73vv/b73fUP0DIlCfRQ1AMTtjwcHB1+gPgOT67oK6+TkZBjNbxRF+X1gYCCDPpX6IKdGAaTu7++HuG9tbe1ONBr9GR7r+Xy+98DsIRuemJiIjI6OJgH+3e7urruzs+OOjIw8SiaTNwRwz8OtQWPpdHoYoKt///ar2/jxaw84k8k8gt5YWVnRqEfi90BrtVph0Uetx0V67d9fqFAo3G6324XZ2VldLK4noK4AVqvVaoh8YZTAxWLxdiwW20CoM70IdceWicfjSpCxfuBEIrGxsLCQZR7QNcQDwFaRRhRmcXCSL9S3kN8CtlP2Oqz2QoWt4Q4NDanHx8cy3HQBMIe6sLS0pF811B7I5uYmhUKh1nmAQWAOteM4xcXFxczMzEzXHp+u9PDwUBHvymWBmVzr6+t6t9tJhtPzEEYuFaoguebm5nTqJOXFoMxEVCO50tMFXBaYcwwbGwAfRagv5bEKthK2igdUr9epG/EDYw//xKGmzoLz/6BQd3t7m5i9dAUJsJoLSPZp5PIGp6amXHjsVSaEirqVALk8jy/axx2hwAcMTlcRH/Ad5LfA24kEZ4JzudbySSJzyqDnomq37pH14utH/iUrCA5HCeRwHYXc8dzNNs5jfXp6uoD+e/Pz8zzfDYIqq6urihg4NyTaK2/Rw8fNo0/euWvBWI3TwGAiHW2RnjY7LRVjX+7t7d3nSWL8FFSKIj46I0r2ZXr4R/PoQT5f1TTtU3Q5OAbbbAxtV4BwXx07wUI5raJdTaVS5vLysmYYhlyMDJBHJBoeHpbFwQ0CfmuP04P8V1VVVb9AVwXGy/xE6SyHw2FuW9Aa2jYAHVx1HAZh78bGxs44wYkm0zS9PPrC1QE4+8HcPwD8HONPYNzEkAU1UX+raFcYmPswzhu9ISLmShIdHBx0lFfVH2s+SyWR/IBofgYvnmCIPTQjkYiBk8mARwYWU4aW8F5uNpslXHkstBncxjcOeyqJ6vfUO9oQd2avlyeKJj3A9z/8yAOE7uHKUgGoiRQYMFZCdEq2bZfgpYFnmd9xzprlcrnCOdV13cbWaWKezGVnAUBOmVBpkOAlPH/AxuYJu/DoPQDcxfubeB/ncZCDL+IpaDKgiVwul8AzDo1BI3RC1HPLIg+mYPQmvPke+hdY+S68ehuevIHQvYpV5/i2KIxKg5pUUew1AaL6wM4cl4oPFJjxFMJ0H6BbIIgBwAbeLSzABLBVKpVszGvSCf27r5dCNE7h1tYWX1U0ECHUaDT+REhryKENrTFbwdLj+skRxIAeM+ka4rGV2QWv2vCIjVoAryC0Jk6MCk6fGvoY0OkFoF80UDsG8AG8j/BtD78YWRSMNNoJQbSe/1Zw0tmwBB6kE0ZG+wXI4v1ECYAIdbKzf/+povypEui6t/jnwvIf5FVJ1Cj/1+UAAAAASUVORK5CYII=",
69
+ Click = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAmCAYAAAA4LpBhAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAVoSURBVHgB7VdNTCRFGP2qe6aBGRhA5mfJ7kZcV0XQTQx68MYabl44yMWbetAY3IMX12gWMCZeSLwSEi/GiAHF1SjqMAhoshuTJbg/GkM0xMQIBMj8D/Nfvq+6enZmEtFdhsSDRT66q6qnXr33varqJvoPFUHHWIw6IK6/2tbWdg8dMzBJKQXHwMCAG9UPhBDXW1tbA2gz6BhKZVAAGTs7Oy5um5+fP9fU1LQExv6xsbHGAzNDHrivr88KhUJegH+0ubkp19fXZVdX1w2v13tCAzdcbhPR3N7e3gnQuY0ry3L7nRcUcCAQuIE4MTs7a1IDi9CgXuQxCNBPNq6uyF+HWuTO5IvVjEP6uSMXlk1qYCOVSnFOgYvRLUtmVmbp9HfvUSQSebS5uXkRwIFGSF2zZFpaWoTtWEHCcgvDclPm+4/p3qvvK2CPx7M4MTERZB/QEYoCxVJxBhHsYvwn0+2WKiyLslc+pfuufSjD4fAjyG+Y03AUVyupsDRkR0eHcXBwYMuNKQi3BaYIt5uYce6Hz8XZ63MMzFKHp6am/HcrtQJZWVkhl8tVIju/KqkmgBxAdQXr3LUv6exPl5XUxWIxMjk5GRgZGbljxpWZ7u3tCX3PPCuALDEYk2Ytij9+RQ9ufFEx18LCgv9Ol5PjXsUQg9hSsXubLA3IwC7JoVgjSjfD9PDm1xVzjY6O+qnWlIeDshOxGzkzVRNgeW2mdl6FfXVkxiQsKv/8LfX9sUScY4yxCPAQpP5XjA0GwFJRTLPZrG5mUFcln6Ytc+Uq0CdcLgAvyf4/lxUw1vA3LDVVpexQedfW1ojdW2m1N4cKM8PllqLKUMI0SRhCsuVKtyLUv7XsuJo3kOA/mUt1Dg4OsqRqZ4JUPJZtIttAaqMAMzQarH8NCzwqSzcjghlrcynGh63jGinwA5VP9efIaBr2vqgBnGeltH+nJonCjPts4HPIb5iXE2nP1IPyXssniZNTBhVq0RhC3p6QTd/oHxLpk4/t356yelQawijrecnek6fKOI/9w8PDYQw1ND4+zs/LelAxNzcndIdRLRwzs5kIYT7wJL17q7D/2tPn0+jIcDunRDMt6/SUmbQT6Htra2vrAj+k+yugFRT9I6qVEGxCZwCY3784dillmubr6CqWSqUyD4a61CDclsVKSGM7TaGe8vl8yenpaTMajTqTqeSUjUSdnZ3O5iCrYUXwDM1ke+ni2NspwzAuoTGBweN8xdYZd7vdXE8jMqjnAFjEq06RQZhdT08P1RdONCWTSaEFdeSyqQfvFzO5XnrupdHfAfgm+rcxeBJdaUQS+28K9QQDcxv6eaHntWLSMdHu7q6zxWorVGnNZ6nQVmVJmSEDovoGWGyjixkmLcuK4mSKglEUk4kjYriPFwqFGF550qgzeA6/KTJTx6jVTNXRBt3ZvSpP1OSlmYNeev7lVxQgYqtcLicAmkQKohgsBnViuVwuBpZRXON8j3M2GY/HE5xTv9+fw9Ip4DknlzXAnFM2VDtMcBrXz7Cw+YFNMHoWAOdx/wTue7kf5uAXcR/CWxee7u5uD64tiGaERbZR/3Zb5E4fBj0FNpcRv8GVz4DVU2DyOKR7CLPu5rdFPagzoOmE3uxNDWJUgYl6UFEFCswWH2S6ANBVGCQKwDzu05hAEsDpWCyWw3MFsu0v6S6LySlcXV3lVxUTRnDl8/lfIGkGOcwhMuxWuPQgax9BDKicSUcoyq3sLrAqgxEPmgZ4AtImcWIkcPpk0MaAxUYAVhcT1m4GeCvuu/htD58YQWwY7ah7tNEa/lnBSeeBHeA2sh3ZdFyAXNRHlAawqNadx/edqrc/wwE66lv8/4XLX3gjac6XP/Y1AAAAAElFTkSuQmCC",
70
+ Sound = "data:video/webm;base64,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",
71
+ Transparent = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
72
+ Hide = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAANvSURBVHgB7Ve9VhpREJ5dU6BNVqt0wS6dpEuX9QmiTyA+gfgE4BOgZSrJE4hlKvEJxDKVa2caoaSSfB87F4Z7dtmFhFTMOfcMe52Z+935u6PIhjb0dxTIihRFUQ2M6z0/dXuI9ay8PwTJklQaEADw0JMgCI4USFSkMx6Pe2BdrFtgS6QEFQICjirYGYDUc0AMcXCCvw8XAVVwHQD7IasAokfCMGzB0NmCA1o44N7T+wpwlwouT+80z2NbOWAaMHqDn7FuJcorapRATkej0bOvyz2s7zs7O2L0GbYXrCrscjUqlUoAuZ6vH3hAIr3diW4xHC3wW+w/KZhLgDmXEgRzbR6udvbBD/DdITB3UewfWm+FRpnIHwyYLo1A+Aq/vzkDWFdSni4krTjm1RnDOxgM9nFOS//OM++0YmeAFMydQw4gDSgeu7LVyprE3489je3u7t5waQFMifrQ6ehn7PZfX18v6BkFOwcq9MDQQKxeseRu0PXARJprBHxED2t7sPSol6p5YHs467OkXo8cqBA/rmXmmVO/atzZzk4G0Kond+DJJJLmStc3Sm+rpxLVbYcEoRu8xbWNp9U1B1rqyzzIRNQj5tAe84ZVKVmGZ6BoK5Vh2JADT1hjLny3rBL27nS/7RtUXZdDmb1H5Ug1rDgjrFMKrGGb2CzPt7e3C95gb2+vqeU/1Mor/UZpg21og50CsfYzATllsLY+E6TE60OTPoUqOV8EQNKKmuTTgifHAmO4GOokyDFah2BTTAOTNFcmIQFI3qyVoxurp+dIL3ZF72bYdzL1zKcDLb2P1n4rqUfcg/nB3Cre3t6uQeY3ZBOri72q87B7ULHY035CdmTs85H9BVlR23yWumVf+6YJo0/MK7qcI8al9RCqq9R4w4ICq9JDYZEwk44ly2TWFtGT+VKnF2PwB6cis8sUzkw+vSsrqNXQ0eUmxo+S5gEPfvQBSTpNLjU1rjzCLiKEYAAWMQRFA5m2GzdJxIUhW5H6yutFguhRToapcb8WQGwL5MwtDnt5cvQOZJuq0yHfkjUQWwHbAn5+AqgvKHGW/IsPRquR+ZdgcQIdrStkYh5tN1ocZYCpSto2Dqezl6yRMga/yQSpXToyYFzOrReQAcUhzp8E+E4eWzD/lTgxuPFGR5Wlm+Y/J3qL/7fJhja0RvoDR4Tn4Lo/zi8AAAAASUVORK5CYII=",
73
+ Unavailable = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAiCAYAAAAge+tMAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAU6SURBVHgBzVg7TyNJEG6bh5AIjpOQeCXj7LL18pDIdpztRYuzy7CjCzG/ADu8CO8vwEQXrgkvws6QeNiEFzEk4GznIFnxvO/zVntrhx6/kLxb0mh6uqurvq6urqqehHklra+ve3d3dx8SiUT6+fk5ja4ZtGf4tjzoD9AXoBmiXUsmk+cnJyc18wpKmCFodXXVx8t/enraBCDPDEchniqeg9PT06oZkAYCTsCw2A6afjc+8IQCjGR3oBtxR/bHx8crR0dHgemD+gIugPfQ9OJ4BGwF72qj0ajrsTQI7rEFcDnTndoLeHh4KDebzdAMC5z+e39/T8B+Nz6AbcJtslAWdOMDfg8LONTuJf7vRVgDPEW40H6crLG4AVi58Pj4+Deav0WUFIjBfDt8NYD+HaBbpge1Wq1wbm5uH+DXjewe5IXQ85buxAMurJS9sbi46IG/jnlforKS0Q7f92dWVlYOAXLXgqMb4CmcnZ2l8E5YpVwIlGZ7basm8nIO50qXNzY29g6y8+hP0RCKPYexBne+K3Ay3N7eNoxyDViziicFwR+la8uOwULFQUBr8LB6XgOU/gDukcGittUY3bUBY25oGR0fX1tbSwPgoVFWxquoAFufv5DxgDvgAsZdu7m52WR7cnLyIC5SAMxnqw+6PRzqSzvmOg9coPX7ZAzoAN9vNWgSQKfVpzP2yq5dQGGZD61F+S5e6KnYNkD6eozWBwZav6m6K1hs2yBJB2hGiIwrQmDsjfp8MS6L2zMqa7INebvGTedKthcdtODN90Yi+I2kCO0owsq3e4U1EqOBq98FwNWnxgKCgd66a5znAe6RjYDfGsdJzmtfgqBPWFE+Jg2HSmFcNuS8QqSv5mLkwQewiumDBKcn87JJ5UuB8BDQJ8TxYnQyFnep2ukYMCXzvXUY57ddvIOGUeIUNw47UQWneEYsrwFVJiYmSjYqkAdx9bOMMR6n4pSTF7J+0ZHCRVKwfaA7SXUZ0g3xNJn+j4+Pm655L1L+8vLyrmRHS4FR6ZfJyUichyKGy5IZgvot2EA1GC8fDanOWgXgCwBPodqPqxCwjQKIljmUPiaSTJxVuoAuCui+CXhKqOGLne84xpgEQKqoSwMpwIIy/ZajUdBytspSVV6KblaT79AsaP0afM+yFtbP4bXT48LAWqYEtymbHrIgZy/SnYurAsV4O7ocRjvD21Nf9bgIYMbK9VgAq8f2rYYHbHp6OqjVau3DK+UCXcw1P9ethI2cO9YzqYFuQLIA3/TegTYxpbPqE+UdazM7o82suWn6AC8Rr2F10upjZgBiPX19fd3E83F+fr5mviakKQiad/FDWfHq6upftlFbM0N7AvwP1kHoY5Fmzwrr7wCyzx16vywsLBDwe9s31GU5ShKz30gOmLGWQZwv2fIBYfRZ2JnCf7Vz0V8xyvIsaV1nReeQ9oXGjIDoYlB6IZ811tx6PAo+Gvoswd0urFGS5icgLCSHV8e/GS5dJYemUQHXxZnnYugFXtzRzg1HApz1jC3iqFzqkxfUDTxcTc9pDhRVXkOICrSYb78RPQ5cfOivRqKNv7S0lMAi/lTRqziSw0mSqMADauufbLdfb45ir032rjsyi0ssnlJWfw/Ltlxxm4T+fyR2+7qfi+GckQEXMHWA9c3XRDRl5KcPXOE/JKrA8vEvwezsLH8a5YwqEWDtMqz9F9sjcxVL4jJM/RuRoVB+iZjob2qSgO7cpEYO3BJrfry2etU8XAx4XtyBfxhwki3aAGyT9b39HS3/KJsoGSr4rLuuh/8DlPszm7LNbUUAAAAASUVORK5CYII=",
74
+ Cross = 'data:image/svg+xml,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%"><rect width="100%" height="100%" style="fill:rgb(204,204,204)"/><line stroke-dasharray="5, 5" x1="0" y1="100%" x2="100%" y2="0" style="stroke:rgb(119,119,119);stroke-width:1"/><line stroke-dasharray="5, 5" x1="0" y1="0" x2="100%" y2="100%" style="stroke:rgb(119,119,119);stroke-width:1"/><circle cx="50%" cy="50%" r="40" fill="rgb(204,204,204)"/></svg>',
75
+ }
76
+
77
+ export const enum Constant {
78
+ ClarityPrefix = "clarity-",
79
+ Canvas = "CANVAS",
80
+ HeatmapCanvas = "clarity-heatmap-canvas",
81
+ InteractionCanvas = "clarity-interaction-canvas",
82
+ UnknownTag = "clarity-unknown",
83
+ ImageTag = "img",
84
+ IFrameTag = "iframe",
85
+ AltAttribute = "alt",
86
+ Hover = ":hover",
87
+ CustomHover = "clarity-hover",
88
+ Region = "clarity-region",
89
+ AdoptedStyleSheet = "clarity-adopted-style",
90
+ Id = "data-clarity-id",
91
+ Hash = "data-clarity-hash",
92
+ Hide = "data-clarity-hide",
93
+ Unavailable = "data-clarity-unavailable",
94
+ Suspend = "data-clarity-suspend",
95
+ Hidden = "hidden",
96
+ Visible = "visible",
97
+ None = "none",
98
+ Small = "s",
99
+ Medium = "m",
100
+ Large = "l",
101
+ Dom = "dom",
102
+ Context = "2d",
103
+ Pixel = "px",
104
+ Separator = "X",
105
+ Absolute = "absolute",
106
+ Black = "black",
107
+ Transparent = "transparent",
108
+ HiddenOpacity = "0.4",
109
+ VisibleOpacity = "1",
110
+ ClickLayer = "clarity-click",
111
+ PointerLayer = "clarity-pointer",
112
+ TouchLayer = "clarity-touch",
113
+ HoverAttribute = "clarity-hover",
114
+ PointerClickLayer = "clarity-pointer-click",
115
+ PointerNone = "clarity-pointer-none",
116
+ PointerMove = "clarity-pointer-move",
117
+ ClickRing = "clarity-click-ring",
118
+ TouchRing = "clarity-touch-ring",
119
+ Title = "title",
120
+ Round = "round",
121
+ AverageFold = "Average Fold",
122
+ Empty = "",
123
+ Undefined = "undefined",
124
+ Function = "function"
125
+ }
126
+
127
+ export const enum Setting {
128
+ Medium = 200,
129
+ Small = 75,
130
+ Radius = 20,
131
+ AlphaBoost = 0.15,
132
+ Colors = 256,
133
+ Interval = 30,
134
+ ZIndex = 2147483647, // Max integer value
135
+ PointerWidth = 29,
136
+ PointerHeight = 38,
137
+ PointerOffset = 4,
138
+ ClickRadius = 22,
139
+ PixelLife = 3000,
140
+ TrailWidth = 6,
141
+ MaxTrailPoints = 75,
142
+ HoverDepth = 7,
143
+ MaxHue = 240,
144
+ MarkerLineHeight = 1,
145
+ MarkerHeight = 32,
146
+ MarkerMediumWidth = 84,
147
+ MarkerRange = 2,
148
+ MarkerSmallWidth = 35,
149
+ MarkerPadding = 5,
150
+ MarkerColor = "white",
151
+ CanvasTextColor = "#323130",
152
+ CanvasTextFont = "500 12px Segoe UI",
153
+ ScrollCanvasMaxHeight = 40000
154
+ }