clarity-js 0.8.10-beta → 0.8.10
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/.lintstagedrc.yml +3 -0
- package/biome.json +43 -0
- package/build/clarity.extended.js +1 -1
- package/build/clarity.insight.js +1 -1
- package/build/clarity.js +3581 -3019
- package/build/clarity.min.js +1 -1
- package/build/clarity.module.js +3581 -3019
- package/build/clarity.performance.js +1 -1
- package/package.json +17 -10
- package/rollup.config.ts +84 -88
- package/src/clarity.ts +34 -28
- package/src/core/config.ts +2 -2
- package/src/core/event.ts +36 -32
- package/src/core/hash.ts +5 -6
- package/src/core/history.ts +10 -11
- package/src/core/index.ts +21 -11
- package/src/core/measure.ts +9 -5
- package/src/core/report.ts +9 -5
- package/src/core/scrub.ts +29 -20
- package/src/core/task.ts +73 -45
- package/src/core/time.ts +3 -3
- package/src/core/timeout.ts +2 -2
- package/src/core/version.ts +1 -1
- package/src/data/baseline.ts +60 -55
- package/src/data/consent.ts +2 -2
- package/src/data/custom.ts +8 -13
- package/src/data/dimension.ts +11 -7
- package/src/data/encode.ts +36 -30
- package/src/data/envelope.ts +38 -38
- package/src/data/extract.ts +86 -77
- package/src/data/index.ts +10 -6
- package/src/data/limit.ts +1 -1
- package/src/data/metadata.ts +305 -266
- package/src/data/metric.ts +18 -8
- package/src/data/ping.ts +8 -4
- package/src/data/signal.ts +18 -18
- package/src/data/summary.ts +6 -4
- package/src/data/token.ts +10 -8
- package/src/data/upgrade.ts +7 -3
- package/src/data/upload.ts +100 -49
- package/src/data/variable.ts +27 -20
- package/src/diagnostic/encode.ts +2 -2
- package/src/diagnostic/fraud.ts +3 -4
- package/src/diagnostic/internal.ts +11 -5
- package/src/diagnostic/script.ts +12 -8
- package/src/global.ts +1 -1
- package/src/insight/blank.ts +4 -4
- package/src/insight/encode.ts +23 -17
- package/src/insight/snapshot.ts +57 -37
- package/src/interaction/change.ts +9 -6
- package/src/interaction/click.ts +34 -28
- package/src/interaction/clipboard.ts +2 -2
- package/src/interaction/encode.ts +35 -31
- package/src/interaction/input.ts +11 -9
- package/src/interaction/pointer.ts +41 -30
- package/src/interaction/resize.ts +5 -5
- package/src/interaction/scroll.ts +20 -17
- package/src/interaction/selection.ts +12 -8
- package/src/interaction/submit.ts +2 -2
- package/src/interaction/timeline.ts +13 -9
- package/src/interaction/unload.ts +1 -1
- package/src/interaction/visibility.ts +2 -2
- package/src/layout/animation.ts +47 -41
- package/src/layout/discover.ts +5 -5
- package/src/layout/document.ts +31 -19
- package/src/layout/dom.ts +141 -91
- package/src/layout/encode.ts +52 -37
- package/src/layout/mutation.ts +321 -318
- package/src/layout/node.ts +104 -81
- package/src/layout/offset.ts +7 -6
- package/src/layout/region.ts +66 -43
- package/src/layout/schema.ts +15 -8
- package/src/layout/selector.ts +47 -25
- package/src/layout/style.ts +44 -36
- package/src/layout/target.ts +14 -10
- package/src/layout/traverse.ts +17 -11
- package/src/performance/blank.ts +1 -1
- package/src/performance/encode.ts +4 -4
- package/src/performance/interaction.ts +58 -70
- package/src/performance/navigation.ts +2 -2
- package/src/performance/observer.ts +59 -26
- package/src/queue.ts +16 -9
- package/tsconfig.json +2 -2
- package/tslint.json +25 -32
- package/types/core.d.ts +13 -13
- package/types/data.d.ts +32 -29
- package/types/diagnostic.d.ts +1 -1
- package/types/interaction.d.ts +5 -4
- package/types/layout.d.ts +36 -21
- package/types/performance.d.ts +6 -5
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Code, Event, Severity } from "@clarity-types/data";
|
|
2
|
-
import { LogData } from "@clarity-types/diagnostic";
|
|
1
|
+
import { type Code, Event, type Severity } from "@clarity-types/data";
|
|
2
|
+
import type { LogData } from "@clarity-types/diagnostic";
|
|
3
3
|
import encode from "./encode";
|
|
4
4
|
|
|
5
5
|
let history: { [key: number]: string[] } = {};
|
|
@@ -10,15 +10,21 @@ export function start(): void {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function log(code: Code, severity: Severity, name: string = null, message: string = null, stack: string = null): void {
|
|
13
|
-
|
|
13
|
+
const key = name ? `${name}|${message}` : "";
|
|
14
14
|
// While rare, it's possible for code to fail repeatedly during the lifetime of the same page
|
|
15
15
|
// In those cases, we only want to log the failure once and not spam logs with redundant information.
|
|
16
|
-
if (code in history && history[code].indexOf(key) >= 0) {
|
|
16
|
+
if (code in history && history[code].indexOf(key) >= 0) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
17
19
|
|
|
18
20
|
data = { code, name, message, stack, severity };
|
|
19
21
|
|
|
20
22
|
// Maintain history of errors in memory to avoid sending redundant information
|
|
21
|
-
if (code in history) {
|
|
23
|
+
if (code in history) {
|
|
24
|
+
history[code].push(key);
|
|
25
|
+
} else {
|
|
26
|
+
history[code] = [key];
|
|
27
|
+
}
|
|
22
28
|
|
|
23
29
|
encode(Event.Log);
|
|
24
30
|
}
|
package/src/diagnostic/script.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Event, Setting } from "@clarity-types/data";
|
|
2
|
-
import { ScriptErrorData } from "@clarity-types/diagnostic";
|
|
2
|
+
import type { ScriptErrorData } from "@clarity-types/diagnostic";
|
|
3
3
|
import { FunctionNames } from "@clarity-types/performance";
|
|
4
4
|
import { bind } from "@src/core/event";
|
|
5
5
|
import encode from "./encode";
|
|
@@ -14,20 +14,24 @@ export function start(): void {
|
|
|
14
14
|
|
|
15
15
|
function handler(error: ErrorEvent): boolean {
|
|
16
16
|
handler.dn = FunctionNames.ScriptHandler;
|
|
17
|
-
|
|
17
|
+
const e = error.error || error;
|
|
18
18
|
// While rare, it's possible for code to fail repeatedly during the lifetime of the same page
|
|
19
19
|
// In those cases, we only want to log the failure first few times and not spam logs with redundant information.
|
|
20
|
-
if (!(e.message in history)) {
|
|
21
|
-
|
|
20
|
+
if (!(e.message in history)) {
|
|
21
|
+
history[e.message] = 0;
|
|
22
|
+
}
|
|
23
|
+
if (history[e.message]++ >= Setting.ScriptErrorLimit) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
22
26
|
|
|
23
27
|
// Send back information only if the handled error has valid information
|
|
24
|
-
if (e
|
|
28
|
+
if (e?.message) {
|
|
25
29
|
data = {
|
|
26
30
|
message: e.message,
|
|
27
|
-
line: error
|
|
28
|
-
column: error
|
|
31
|
+
line: error.lineno,
|
|
32
|
+
column: error.colno,
|
|
29
33
|
stack: e.stack,
|
|
30
|
-
source: error
|
|
34
|
+
source: error.filename,
|
|
31
35
|
};
|
|
32
36
|
|
|
33
37
|
encode(Event.ScriptError);
|
package/src/global.ts
CHANGED
package/src/insight/blank.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
1
|
+
export const state = [];
|
|
2
|
+
export const sheetAdoptionState = [];
|
|
3
|
+
export const sheetUpdateState = [];
|
|
4
|
+
export const data = null;
|
|
5
5
|
|
|
6
6
|
/* Intentionally blank module with empty code */
|
|
7
7
|
|
package/src/insight/encode.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Privacy } from "@clarity-types/core";
|
|
2
|
-
import { Event, Token } from "@clarity-types/data";
|
|
3
|
-
import { Constant, NodeInfo } from "@clarity-types/layout";
|
|
1
|
+
import type { Privacy } from "@clarity-types/core";
|
|
2
|
+
import { Event, type Token } from "@clarity-types/data";
|
|
3
|
+
import { Constant, type NodeInfo } from "@clarity-types/layout";
|
|
4
4
|
import * as scrub from "@src/core/scrub";
|
|
5
5
|
import { time } from "@src/core/time";
|
|
6
6
|
import * as baseline from "@src/data/baseline";
|
|
@@ -10,34 +10,39 @@ import * as snapshot from "@src/insight/snapshot";
|
|
|
10
10
|
import * as doc from "@src/layout/document";
|
|
11
11
|
|
|
12
12
|
export default async function (type: Event): Promise<void> {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const eventTime = time();
|
|
14
|
+
const tokens: Token[] = [eventTime, type];
|
|
15
15
|
switch (type) {
|
|
16
|
-
case Event.Document:
|
|
17
|
-
|
|
16
|
+
case Event.Document: {
|
|
17
|
+
const d = doc.data;
|
|
18
18
|
tokens.push(d.width);
|
|
19
19
|
tokens.push(d.height);
|
|
20
20
|
baseline.track(type, d.width, d.height);
|
|
21
21
|
queue(tokens);
|
|
22
22
|
break;
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
}
|
|
24
|
+
case Event.Snapshot: {
|
|
25
|
+
const values = snapshot.values;
|
|
25
26
|
// Only encode and queue DOM updates if we have valid updates to report back
|
|
26
27
|
if (values.length > 0) {
|
|
27
|
-
for (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
for (
|
|
28
|
+
for (const value of values) {
|
|
29
|
+
const privacy = value.metadata.privacy;
|
|
30
|
+
const data: NodeInfo = value.data;
|
|
31
|
+
for (const key of ["tag", "attributes", "value"]) {
|
|
31
32
|
if (data[key]) {
|
|
32
33
|
switch (key) {
|
|
33
34
|
case "tag":
|
|
34
35
|
tokens.push(value.id);
|
|
35
|
-
if (value.parent) {
|
|
36
|
-
|
|
36
|
+
if (value.parent) {
|
|
37
|
+
tokens.push(value.parent);
|
|
38
|
+
}
|
|
39
|
+
if (value.previous) {
|
|
40
|
+
tokens.push(value.previous);
|
|
41
|
+
}
|
|
37
42
|
tokens.push(data[key]);
|
|
38
43
|
break;
|
|
39
44
|
case "attributes":
|
|
40
|
-
for (
|
|
45
|
+
for (const attr in data[key]) {
|
|
41
46
|
if (data[key][attr] !== undefined) {
|
|
42
47
|
tokens.push(attribute(attr, data[key][attr], privacy));
|
|
43
48
|
}
|
|
@@ -53,9 +58,10 @@ export default async function (type: Event): Promise<void> {
|
|
|
53
58
|
queue(tokenize(tokens), true);
|
|
54
59
|
}
|
|
55
60
|
break;
|
|
61
|
+
}
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
function attribute(key: string, value: string, privacy: Privacy): string {
|
|
60
66
|
return `${key}=${scrub.text(value, key.indexOf(Constant.DataAttribute) === 0 ? Constant.DataAttribute : key, privacy)}`;
|
|
61
|
-
}
|
|
67
|
+
}
|
package/src/insight/snapshot.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { OffsetDistance, Privacy } from "@clarity-types/core";
|
|
1
|
+
import { type OffsetDistance, Privacy } from "@clarity-types/core";
|
|
2
2
|
import { Event } from "@clarity-types/data";
|
|
3
|
-
import { Constant, NodeInfo, NodeValue, TargetMetadata } from "@clarity-types/layout";
|
|
4
|
-
import
|
|
3
|
+
import { Constant, type NodeInfo, type NodeValue, type TargetMetadata } from "@clarity-types/layout";
|
|
4
|
+
import config from "@src/core/config";
|
|
5
5
|
import encode from "@src/insight/encode";
|
|
6
6
|
import * as interaction from "@src/interaction";
|
|
7
|
-
import
|
|
7
|
+
import * as doc from "@src/layout/document";
|
|
8
8
|
export let values: NodeValue[] = [];
|
|
9
|
-
let index
|
|
9
|
+
let index = 1;
|
|
10
10
|
let idMap: WeakMap<Node, number> = null; // Maps node => id.
|
|
11
11
|
|
|
12
12
|
export function start(): void {
|
|
@@ -21,24 +21,34 @@ export function stop(): void {
|
|
|
21
21
|
doc.stop();
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export function compute(): void {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export function
|
|
24
|
+
export function compute(): void {
|
|
25
|
+
/* Intentionally Blank */
|
|
26
|
+
}
|
|
27
|
+
export function iframe(): boolean {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
export function offset(): OffsetDistance {
|
|
31
|
+
return { x: 0, y: 0 };
|
|
32
|
+
}
|
|
33
|
+
export function hashText(): void {
|
|
34
|
+
/* Intentionally Blank */
|
|
35
|
+
}
|
|
28
36
|
|
|
29
37
|
export function target(evt: UIEvent): Node {
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
const path = evt.composed && evt.composedPath ? evt.composedPath() : null;
|
|
39
|
+
const node = (path && path.length > 0 ? path[0] : evt.target) as Node;
|
|
32
40
|
return node.nodeType === Node.DOCUMENT_NODE ? (node as Document).documentElement : node;
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
export function metadata(node: Node): TargetMetadata {
|
|
36
|
-
|
|
37
|
-
if (node) {
|
|
44
|
+
const output: TargetMetadata = { id: 0, hash: null, privacy: config.conversions ? Privacy.Sensitive : Privacy.Snapshot };
|
|
45
|
+
if (node) {
|
|
46
|
+
output.id = idMap.has(node) ? idMap.get(node) : getId(node);
|
|
47
|
+
}
|
|
38
48
|
return output;
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
export function snapshot(): void {
|
|
51
|
+
export function snapshot(): void {
|
|
42
52
|
values = [];
|
|
43
53
|
traverse(document);
|
|
44
54
|
encode(Event.Snapshot);
|
|
@@ -49,46 +59,50 @@ function reset(): void {
|
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
function traverse(root: Node): void {
|
|
52
|
-
|
|
62
|
+
const queue = [root];
|
|
53
63
|
while (queue.length > 0) {
|
|
54
|
-
let attributes = null
|
|
55
|
-
let
|
|
64
|
+
let attributes = null;
|
|
65
|
+
let tag = null;
|
|
66
|
+
let value = null;
|
|
67
|
+
const node = queue.shift();
|
|
56
68
|
let next = node.firstChild;
|
|
57
|
-
|
|
69
|
+
const parent = node.parentElement ? node.parentElement : node.parentNode ? node.parentNode : null;
|
|
58
70
|
|
|
59
71
|
while (next) {
|
|
60
72
|
queue.push(next);
|
|
61
73
|
next = next.nextSibling;
|
|
62
74
|
}
|
|
63
|
-
|
|
75
|
+
|
|
64
76
|
// Process the node
|
|
65
|
-
|
|
77
|
+
const type = node.nodeType;
|
|
66
78
|
switch (type) {
|
|
67
|
-
case Node.DOCUMENT_TYPE_NODE:
|
|
68
|
-
|
|
79
|
+
case Node.DOCUMENT_TYPE_NODE: {
|
|
80
|
+
const doctype = node as DocumentType;
|
|
69
81
|
tag = Constant.DocumentTag;
|
|
70
|
-
attributes = { name: doctype.name, publicId: doctype.publicId, systemId: doctype.systemId }
|
|
82
|
+
attributes = { name: doctype.name, publicId: doctype.publicId, systemId: doctype.systemId };
|
|
71
83
|
break;
|
|
72
|
-
|
|
84
|
+
}
|
|
85
|
+
case Node.TEXT_NODE:
|
|
73
86
|
value = node.nodeValue;
|
|
74
87
|
tag = idMap.get(parent) ? Constant.TextTag : tag;
|
|
75
88
|
break;
|
|
76
|
-
case Node.ELEMENT_NODE:
|
|
77
|
-
|
|
89
|
+
case Node.ELEMENT_NODE: {
|
|
90
|
+
const element = node as HTMLElement;
|
|
78
91
|
attributes = getAttributes(element);
|
|
79
92
|
tag = ["NOSCRIPT", "SCRIPT", "STYLE"].indexOf(element.tagName) < 0 ? element.tagName : tag;
|
|
80
93
|
break;
|
|
94
|
+
}
|
|
81
95
|
}
|
|
82
|
-
add(node, parent, { tag, attributes, value });
|
|
96
|
+
add(node, parent, { tag, attributes, value });
|
|
83
97
|
}
|
|
84
98
|
}
|
|
85
99
|
|
|
86
100
|
/* Helper Functions - Snapshot Traversal */
|
|
87
101
|
function getAttributes(element: HTMLElement): { [key: string]: string } {
|
|
88
|
-
|
|
89
|
-
|
|
102
|
+
const output = {};
|
|
103
|
+
const attributes = element.attributes;
|
|
90
104
|
if (attributes && attributes.length > 0) {
|
|
91
|
-
for (let i = 0; i < attributes.length; i++) {
|
|
105
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
92
106
|
output[attributes[i].name] = attributes[i].value;
|
|
93
107
|
}
|
|
94
108
|
}
|
|
@@ -96,20 +110,26 @@ function getAttributes(element: HTMLElement): { [key: string]: string } {
|
|
|
96
110
|
}
|
|
97
111
|
|
|
98
112
|
function getId(node: Node): number {
|
|
99
|
-
if (node === null) {
|
|
100
|
-
|
|
113
|
+
if (node === null) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (idMap.has(node)) {
|
|
117
|
+
return idMap.get(node);
|
|
118
|
+
}
|
|
101
119
|
idMap.set(node, index);
|
|
102
120
|
return index++;
|
|
103
121
|
}
|
|
104
122
|
|
|
105
123
|
function add(node: Node, parent: Node, data: NodeInfo): void {
|
|
106
124
|
if (node && data && data.tag) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
125
|
+
const id = getId(node);
|
|
126
|
+
const parentId = parent ? idMap.get(parent) : null;
|
|
127
|
+
const previous = node.previousSibling ? idMap.get(node.previousSibling) : null;
|
|
128
|
+
const metadata = { active: true, suspend: false, privacy: Privacy.Snapshot, position: null, fraud: null, size: null };
|
|
111
129
|
values.push({ id, parent: parentId, previous, children: [], data, selector: null, hash: null, region: null, metadata });
|
|
112
130
|
}
|
|
113
131
|
}
|
|
114
132
|
|
|
115
|
-
export function get(_node: Node): NodeValue {
|
|
133
|
+
export function get(_node: Node): NodeValue {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Constant, Event, Setting } from "@clarity-types/data";
|
|
2
|
-
import { ChangeState } from "@clarity-types/interaction";
|
|
2
|
+
import type { ChangeState } from "@clarity-types/interaction";
|
|
3
|
+
import { Mask } from "@clarity-types/layout";
|
|
3
4
|
import { FunctionNames } from "@clarity-types/performance";
|
|
4
5
|
import config from "@src/core/config";
|
|
5
6
|
import { bind } from "@src/core/event";
|
|
@@ -8,7 +9,6 @@ import { schedule } from "@src/core/task";
|
|
|
8
9
|
import { time } from "@src/core/time";
|
|
9
10
|
import { target } from "@src/layout/target";
|
|
10
11
|
import encode from "./encode";
|
|
11
|
-
import { Mask } from "@clarity-types/layout";
|
|
12
12
|
|
|
13
13
|
export let state: ChangeState[] = [];
|
|
14
14
|
|
|
@@ -22,13 +22,16 @@ export function observe(root: Node): void {
|
|
|
22
22
|
|
|
23
23
|
function recompute(evt: UIEvent): void {
|
|
24
24
|
recompute.dn = FunctionNames.ChangeRecompute;
|
|
25
|
-
|
|
25
|
+
const element = target(evt) as HTMLInputElement;
|
|
26
26
|
if (element) {
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const value = element.value;
|
|
28
|
+
const checksum =
|
|
29
|
+
value && value.length >= Setting.WordLength && config.fraud && Mask.Exclude.indexOf(element.type) === -1
|
|
30
|
+
? hash(value, Setting.ChecksumPrecision)
|
|
31
|
+
: Constant.Empty;
|
|
29
32
|
state.push({ time: time(evt), event: Event.Change, data: { target: target(evt), type: element.type, value, checksum } });
|
|
30
33
|
schedule(encode.bind(this, Event.Change));
|
|
31
|
-
}
|
|
34
|
+
}
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
export function reset(): void {
|
package/src/interaction/click.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BooleanFlag, Constant, Event, Setting } from "@clarity-types/data";
|
|
2
|
-
import { BrowsingContext, ClickState, TextInfo } from "@clarity-types/interaction";
|
|
3
|
-
import { Box } from "@clarity-types/layout";
|
|
2
|
+
import { BrowsingContext, type ClickState, type TextInfo } from "@clarity-types/interaction";
|
|
3
|
+
import type { Box } from "@clarity-types/layout";
|
|
4
4
|
import { FunctionNames } from "@clarity-types/performance";
|
|
5
5
|
import { bind } from "@src/core/event";
|
|
6
6
|
import { schedule } from "@src/core/task";
|
|
@@ -23,41 +23,43 @@ export function observe(root: Node): void {
|
|
|
23
23
|
|
|
24
24
|
function handler(event: Event, root: Node, evt: MouseEvent): void {
|
|
25
25
|
handler.dn = FunctionNames.ClickHandler;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
let x = "pageX" in evt ? Math.round(evt.pageX) :
|
|
29
|
-
let y = "pageY" in evt ? Math.round(evt.pageY) :
|
|
26
|
+
const frame = iframe(root);
|
|
27
|
+
const d = frame ? frame.contentDocument.documentElement : document.documentElement;
|
|
28
|
+
let x = "pageX" in evt ? Math.round(evt.pageX) : "clientX" in evt ? Math.round((evt as MouseEvent).clientX + d.scrollLeft) : null;
|
|
29
|
+
let y = "pageY" in evt ? Math.round(evt.pageY) : "clientY" in evt ? Math.round((evt as MouseEvent).clientY + d.scrollTop) : null;
|
|
30
30
|
// In case of iframe, we adjust (x,y) to be relative to top parent's origin
|
|
31
31
|
if (frame) {
|
|
32
|
-
|
|
32
|
+
const distance = offset(frame);
|
|
33
33
|
x = x ? x + Math.round(distance.x) : x;
|
|
34
34
|
y = y ? y + Math.round(distance.y) : y;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
const t = target(evt);
|
|
38
38
|
// Find nearest anchor tag (<a/>) parent if current target node is part of one
|
|
39
39
|
// If present, we use the returned link element to populate text and link properties below
|
|
40
|
-
|
|
40
|
+
const a = link(t);
|
|
41
41
|
|
|
42
42
|
// Get layout rectangle for the target element
|
|
43
|
-
|
|
43
|
+
const l = layout(t as Element);
|
|
44
44
|
|
|
45
45
|
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail
|
|
46
46
|
// This property helps differentiate between a keyboard navigation vs. pointer click
|
|
47
47
|
// In case of a keyboard navigation, we use center of target element as (x,y)
|
|
48
48
|
if (evt.detail === 0 && l) {
|
|
49
|
-
x = Math.round(l.x +
|
|
50
|
-
y = Math.round(l.y +
|
|
49
|
+
x = Math.round(l.x + l.w / 2);
|
|
50
|
+
y = Math.round(l.y + l.h / 2);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const eX = l ? Math.max(Math.floor(((x - l.x) / l.w) * Setting.ClickPrecision), 0) : 0;
|
|
54
|
+
const eY = l ? Math.max(Math.floor(((y - l.y) / l.h) * Setting.ClickPrecision), 0) : 0;
|
|
55
55
|
|
|
56
56
|
// Check for null values before processing this event
|
|
57
57
|
if (x !== null && y !== null) {
|
|
58
58
|
const textInfo = text(t);
|
|
59
59
|
state.push({
|
|
60
|
-
time: time(evt),
|
|
60
|
+
time: time(evt),
|
|
61
|
+
event,
|
|
62
|
+
data: {
|
|
61
63
|
target: t,
|
|
62
64
|
x,
|
|
63
65
|
y,
|
|
@@ -71,16 +73,17 @@ function handler(event: Event, root: Node, evt: MouseEvent): void {
|
|
|
71
73
|
hash: null,
|
|
72
74
|
trust: evt.isTrusted ? BooleanFlag.True : BooleanFlag.False,
|
|
73
75
|
isFullText: textInfo.isFullText,
|
|
74
|
-
}
|
|
76
|
+
},
|
|
75
77
|
});
|
|
76
78
|
schedule(encode.bind(this, event));
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
function link(
|
|
82
|
+
function link(inputNode: Node): HTMLAnchorElement {
|
|
83
|
+
let node = inputNode;
|
|
81
84
|
while (node && node !== document) {
|
|
82
85
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
83
|
-
|
|
86
|
+
const element = node as HTMLElement;
|
|
84
87
|
if (element.tagName === "A") {
|
|
85
88
|
return element as HTMLAnchorElement;
|
|
86
89
|
}
|
|
@@ -95,11 +98,11 @@ function text(element: Node): TextInfo {
|
|
|
95
98
|
let isFullText = false;
|
|
96
99
|
if (element) {
|
|
97
100
|
// Grab text using "textContent" for most HTMLElements, however, use "value" for HTMLInputElements and "alt" for HTMLImageElement.
|
|
98
|
-
|
|
101
|
+
const t = element.textContent || String((element as HTMLInputElement).value || "") || (element as HTMLImageElement).alt;
|
|
99
102
|
if (t) {
|
|
100
103
|
// Replace multiple occurrence of space characters with a single white space
|
|
101
104
|
// Also, trim any spaces at the beginning or at the end of string
|
|
102
|
-
const trimmedText =
|
|
105
|
+
const trimmedText = t.replace(/\s+/g, Constant.Space).trim();
|
|
103
106
|
// Finally, send only first few characters as specified by the Setting
|
|
104
107
|
output = trimmedText.substring(0, Setting.ClickText);
|
|
105
108
|
isFullText = output.length === trimmedText.length;
|
|
@@ -111,7 +114,7 @@ function text(element: Node): TextInfo {
|
|
|
111
114
|
|
|
112
115
|
function reaction(element: Node): BooleanFlag {
|
|
113
116
|
if (element.nodeType === Node.ELEMENT_NODE) {
|
|
114
|
-
|
|
117
|
+
const tag = (element as HTMLElement).tagName.toLowerCase();
|
|
115
118
|
if (UserInputTags.indexOf(tag) >= 0) {
|
|
116
119
|
return BooleanFlag.False;
|
|
117
120
|
}
|
|
@@ -121,10 +124,10 @@ function reaction(element: Node): BooleanFlag {
|
|
|
121
124
|
|
|
122
125
|
function layout(element: Element): Box {
|
|
123
126
|
let box: Box = null;
|
|
124
|
-
|
|
127
|
+
const de = document.documentElement;
|
|
125
128
|
if (typeof element.getBoundingClientRect === "function") {
|
|
126
129
|
// getBoundingClientRect returns rectangle relative positioning to viewport
|
|
127
|
-
|
|
130
|
+
const rect = element.getBoundingClientRect();
|
|
128
131
|
|
|
129
132
|
if (rect && rect.width > 0 && rect.height > 0) {
|
|
130
133
|
// Add viewport's scroll position to rectangle to get position relative to document origin
|
|
@@ -135,7 +138,7 @@ function layout(element: Element): Box {
|
|
|
135
138
|
x: Math.floor(rect.left + ("pageXOffset" in window ? window.pageXOffset : de.scrollLeft)),
|
|
136
139
|
y: Math.floor(rect.top + ("pageYOffset" in window ? window.pageYOffset : de.scrollTop)),
|
|
137
140
|
w: Math.floor(rect.width),
|
|
138
|
-
h: Math.floor(rect.height)
|
|
141
|
+
h: Math.floor(rect.height),
|
|
139
142
|
};
|
|
140
143
|
}
|
|
141
144
|
}
|
|
@@ -143,11 +146,14 @@ function layout(element: Element): Box {
|
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
function context(a: HTMLAnchorElement): BrowsingContext {
|
|
146
|
-
if (a
|
|
149
|
+
if (a?.hasAttribute(Constant.Target)) {
|
|
147
150
|
switch (a.getAttribute(Constant.Target)) {
|
|
148
|
-
case Constant.Blank:
|
|
149
|
-
|
|
150
|
-
case Constant.
|
|
151
|
+
case Constant.Blank:
|
|
152
|
+
return BrowsingContext.Blank;
|
|
153
|
+
case Constant.Parent:
|
|
154
|
+
return BrowsingContext.Parent;
|
|
155
|
+
case Constant.Top:
|
|
156
|
+
return BrowsingContext.Top;
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
return BrowsingContext.Self;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Event } from "@clarity-types/data";
|
|
2
|
-
import { Clipboard, ClipboardState } from "@clarity-types/interaction";
|
|
2
|
+
import { Clipboard, type ClipboardState } from "@clarity-types/interaction";
|
|
3
3
|
import { FunctionNames } from "@clarity-types/performance";
|
|
4
4
|
import { bind } from "@src/core/event";
|
|
5
5
|
import { schedule } from "@src/core/task";
|
|
6
6
|
import { time } from "@src/core/time";
|
|
7
|
-
import encode from "./encode";
|
|
8
7
|
import { target } from "@src/layout/target";
|
|
8
|
+
import encode from "./encode";
|
|
9
9
|
|
|
10
10
|
export let state: ClipboardState[] = [];
|
|
11
11
|
|