forked from snowplow/snowplow-javascript-tracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutil.ts
More file actions
85 lines (73 loc) · 3.61 KB
/
util.ts
File metadata and controls
85 lines (73 loc) · 3.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import { Configuration } from './configuration';
import { Frequency } from './types';
/**
* Parses custom boundaries config.
* @param boundaryPixels Input format boundaries.
* @returns Object describing the input boundary configuration.
*/
export function defineBoundaries(boundaryPixels: number | [number, number] | [number, number, number, number]) {
let boundTop: number, boundRight: number, boundBottom: number, boundLeft: number;
if (typeof boundaryPixels === 'number') {
boundTop = boundRight = boundBottom = boundLeft = boundaryPixels;
} else if (Array.isArray(boundaryPixels)) {
if (boundaryPixels.length === 2) {
boundTop = boundBottom = boundaryPixels[0];
boundRight = boundLeft = boundaryPixels[1];
} else if (boundaryPixels.length === 4) {
[boundTop, boundRight, boundBottom, boundLeft] = boundaryPixels;
} else {
boundTop = boundRight = boundBottom = boundLeft = 0;
}
} else {
boundTop = boundRight = boundBottom = boundLeft = 0;
}
return { boundTop, boundRight, boundBottom, boundLeft };
}
/**
* Gets descendent elements of `target` (`document` by default) that match the selector info in `config`.
* This is usually a wrapper around `querySelectorAll`, but accounts for shadow roots that can be specified in `config`.
* @param config The configuration containing the selector and other details that influence how elements will be found.
* @param target The root to start finding descendent elements from; defaults to `document`
* @returns Array of elements within `target` that matched the configuration in `config`.
*/
export function getMatchingElements(config: Configuration, target: ParentNode = document) {
const { selector, shadowOnly, shadowSelector } = config;
const elements: Element[] = shadowOnly ? [] : Array.from(target.querySelectorAll(selector));
if (shadowSelector) {
Array.from(target.querySelectorAll(shadowSelector), (host) => {
if (host.shadowRoot) {
// these will have been skipped in the above check but should be included if we're recursing
if (shadowOnly) elements.push(...Array.from(host.shadowRoot.querySelectorAll(selector)));
// look for nested shadow elements
elements.push(...getMatchingElements(config, host.shadowRoot));
}
});
}
return elements;
}
/**
* Type guard to check that `Node` is an `Element`
* @param node Node to check.
* @returns If `node` is an Element.
*/
export function nodeIsElement(node: Node): node is Element {
return node.nodeType === Node.ELEMENT_NODE;
}
/**
* Evaluates whether the current intersection is eligible for firing an EXPOSE event against the given configuration.
* Mostly this handles "disabled" config (when: never), minimum size/intersection checks and custom boundaries.
*/
export function shouldTrackExpose(config: Configuration, entry: IntersectionObserverEntry): boolean {
if (config.expose.when === Frequency.NEVER) return false;
if (!entry.isIntersecting) return false;
const { boundaryPixels, minPercentage, minSize } = config.expose;
const { boundTop, boundRight, boundBottom, boundLeft } = defineBoundaries(boundaryPixels);
const { boundingClientRect } = entry;
if (boundingClientRect.height * boundingClientRect.width < minSize) return false;
const intersectionArea = entry.intersectionRect.height * entry.intersectionRect.width;
const boundingHeight = entry.boundingClientRect.height + boundTop + boundBottom;
const boundingWidth = entry.boundingClientRect.width + boundLeft + boundRight;
const boundingArea = boundingHeight * boundingWidth;
if (minPercentage > intersectionArea / boundingArea) return false;
return true;
}