Line data Source code
1 : // @ts-check
2 :
3 : import { forEach, indexOf } from "lodash";
4 :
5 1 : const DEFAULT_MAX_RESULTS = 250;
6 :
7 1 : const renderableElements = new Set([
8 : "a", "circle", "ellipse", "foreignObject",
9 : "g", "image", "line", "path", "polygon", "polyline", "rect", "svg", "switch",
10 : "symbol", "text", "textPath", "tspan", "use"
11 : ]);
12 :
13 : /**
14 : * @param {Element} element
15 : * @param {{ maxNodes?: number, maxResults?: number }} [options]
16 : * @return {string[]}
17 : */
18 : export function getDomSelectors(element, options) {
19 5 : const ret = getDomIdClass(element, options);
20 5 : if (ret.length < (options?.maxResults ?? DEFAULT_MAX_RESULTS)) {
21 5 : const maxResults = (options?.maxResults ?? DEFAULT_MAX_RESULTS) - ret.length;
22 5 : return ret.concat(getDirectPaths(element, { maxResults }));
23 : }
24 0 : return ret;
25 : }
26 :
27 : /**
28 : * @param {Element} element
29 : * @param {{ maxNodes?: number, maxResults?: number }} [options]
30 : * @return {string[]}
31 : */
32 : export function getDomIdClass(element, options) {
33 5 : if (!element) { return []; }
34 :
35 5 : const stack = [ element ];
36 5 : const ret = new Set();
37 5 : let maxNodes = options?.maxNodes ?? 250;
38 5 : let maxResults = options?.maxResults ?? DEFAULT_MAX_RESULTS;
39 :
40 5 : while (maxNodes > 0 && maxResults > 0 && stack.length > 0) {
41 65 : const element = /** @type {Element} */ (stack.shift());
42 65 : forEach(element.children, (elt) => { /* eslint-disable-line no-loop-func */ /* jshint ignore:line */
43 60 : stack.push(elt);
44 60 : if (elt.id) {
45 6 : ret.add("#" + elt.id);
46 6 : if (--maxResults <= 0) { return false; }
47 : }
48 60 : forEach(elt.classList, (c) => {
49 4 : ret.add("." + c);
50 4 : if (--maxResults <= 0) { return false; }
51 : });
52 60 : if (--maxNodes <= 0) { return false; }
53 : });
54 : }
55 5 : return Array.from(ret);
56 : }
57 :
58 : /**
59 : * @param {Element} element
60 : * @param {{ maxResults?: number, maxDepth?: number, allowedNodes?: Set<string> }} [options]
61 : * @return {string[]}
62 : */
63 : export function getDirectPaths(element, options) {
64 5 : if (!element) { return []; }
65 :
66 5 : const stack = [ { elt: element, path: ":scope", depth: 0 } ];
67 : /** @type {string[]} */
68 5 : const ret = [];
69 5 : let maxResults = options?.maxResults ?? DEFAULT_MAX_RESULTS;
70 5 : const maxDepth = options?.maxDepth ?? 3;
71 5 : const allowedNodes = options?.allowedNodes ?? renderableElements;
72 :
73 5 : while (maxResults > 0 && stack.length > 0) {
74 11 : const element = stack.shift();
75 : /** @type {{ [tag: string]: number }} */
76 11 : const childCountMap = {};
77 11 : forEach(element?.elt.children, (elt) => { /* eslint-disable-line no-loop-func */ /* jshint ignore:line */
78 11 : const type = elt.tagName;
79 11 : if (!allowedNodes.has(type) || !element) { return; }
80 :
81 6 : childCountMap[type] = (childCountMap[type] ?? 0) + 1;
82 :
83 6 : const path = `${element?.path}>${type}:nth-of-type(${childCountMap[type]})`;
84 6 : if (element.depth < maxDepth) {
85 6 : stack.push({ elt, path, depth: element.depth + 1 });
86 : }
87 6 : ret.push(path);
88 6 : if (--maxResults <= 0) { return false; }
89 : });
90 : }
91 5 : return ret;
92 : }
93 :
94 : /**
95 : * @param {Element|null} root
96 : * @param {Element|null} element
97 : * @return {string|null}
98 : */
99 : export function getElementPath(root, element) {
100 0 : if (element && element === root) {
101 0 : return ":scope";
102 : }
103 : else {
104 0 : if (!element || !element.parentElement) { return null; }
105 0 : const tagName = element.tagName;
106 :
107 0 : const nth = indexOf(element.parentElement.getElementsByTagName(tagName), element);
108 :
109 0 : return `${getElementPath(root, element.parentElement)}>${tagName}:nth-of-type(${nth})`;
110 : }
111 : }
112 :
113 1 : let id = 0;
114 :
115 : /** @returns {string} */
116 : export function genId() {
117 36 : return "x-" + (++id);
118 : }
119 :
120 : /**
121 : * @brief function used to automatically and correctly infer types in mixins
122 : * @template V, Data, Methods, Computed, PropNames, SetupBindings, Mixin, Extends
123 : * @type {V.mixinMaker<V, Data, Methods, Computed, PropNames, SetupBindings, Mixin, Extends>}}
124 : */
125 4 : export function mixinMaker(m) { return /** @type {any} */ (m); }
|