@ubio/webvision 2.6.1 → 2.6.3

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.
@@ -0,0 +1,1001 @@
1
+ "use strict";
2
+ var Webvision = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/page/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ Counter: () => Counter,
25
+ INTERACTIVE_ROLES: () => INTERACTIVE_ROLES,
26
+ INTERACTIVE_TAGS: () => INTERACTIVE_TAGS,
27
+ PointSet: () => PointSet,
28
+ VX_DOM_SYMBOL: () => VX_DOM_SYMBOL,
29
+ VX_IGNORE_SYMBOL: () => VX_IGNORE_SYMBOL,
30
+ VX_IGNORE_TAGS: () => VX_IGNORE_TAGS,
31
+ VX_LABEL_ATTRS: () => VX_LABEL_ATTRS,
32
+ VX_LABEL_ATTR_PATTERNS: () => VX_LABEL_ATTR_PATTERNS,
33
+ VX_LAST_REFS_SYMBOL: () => VX_LAST_REFS_SYMBOL,
34
+ VX_NODE_SYMBOL: () => VX_NODE_SYMBOL,
35
+ VX_SRC_ATTRS: () => VX_SRC_ATTRS,
36
+ VX_TAG_PREFERENCE: () => VX_TAG_PREFERENCE,
37
+ VX_TREE_SYMBOL: () => VX_TREE_SYMBOL,
38
+ VX_VALUE_ATTRS: () => VX_VALUE_ATTRS,
39
+ VxPageView: () => VxPageView,
40
+ VxTreeParser: () => VxTreeParser,
41
+ VxTreeView: () => VxTreeView,
42
+ captureSnapshot: () => captureSnapshot,
43
+ clearOverlay: () => clearOverlay,
44
+ containsSelector: () => containsSelector,
45
+ escapeAttribute: () => escapeAttribute,
46
+ fixZIndex: () => fixZIndex,
47
+ getNormalizedText: () => getNormalizedText,
48
+ getOffsetTop: () => getOffsetTop,
49
+ getOverlayContainer: () => getOverlayContainer,
50
+ getSnapshot: () => getSnapshot,
51
+ getViewportSize: () => getViewportSize,
52
+ hasVisibleArea: () => hasVisibleArea,
53
+ highlightEl: () => highlightEl,
54
+ isContainerNode: () => isContainerNode,
55
+ isDeepHidden: () => isDeepHidden,
56
+ isHidden: () => isHidden,
57
+ isInteractive: () => isInteractive,
58
+ isObjectEmpty: () => isObjectEmpty,
59
+ isRandomIdentifier: () => isRandomIdentifier,
60
+ isRectInViewport: () => isRectInViewport,
61
+ makeOverlaysOpaque: () => makeOverlaysOpaque,
62
+ normalizeText: () => normalizeText,
63
+ probeViewport: () => probeViewport,
64
+ renderVxNode: () => renderVxNode,
65
+ resolveDomNode: () => resolveDomNode,
66
+ showPoint: () => showPoint,
67
+ traverseElements: () => traverseElements,
68
+ traverseVxNode: () => traverseVxNode,
69
+ truncateAttrValue: () => truncateAttrValue
70
+ });
71
+
72
+ // src/page/counter.ts
73
+ var Counter = class {
74
+ constructor(value = 0) {
75
+ this.value = value;
76
+ }
77
+ next() {
78
+ this.value += 1;
79
+ return this.value;
80
+ }
81
+ current() {
82
+ return this.value;
83
+ }
84
+ };
85
+
86
+ // src/page/dom.ts
87
+ var ORIGINAL_STYLE_SYMBOL = Symbol("vx:originalStyle");
88
+ var INTERACTIVE_TAGS = ["a", "button", "input", "textarea", "select", "label", "option", "optgroup"];
89
+ var INTERACTIVE_ROLES = [
90
+ "clickable",
91
+ "button",
92
+ "link",
93
+ "checkbox",
94
+ "radio",
95
+ "textbox",
96
+ "combobox",
97
+ "listbox",
98
+ "menu",
99
+ "menuitem",
100
+ "menuitemcheckbox",
101
+ "menuitemradio",
102
+ "option",
103
+ "optgroup",
104
+ "progressbar",
105
+ "scrollbar",
106
+ "slider",
107
+ "spinbutton",
108
+ "switch",
109
+ "tab",
110
+ "tablist",
111
+ "timer",
112
+ "toolbar"
113
+ ];
114
+ function isHidden(el) {
115
+ const style = getComputedStyle(el);
116
+ if (style.display === "none") {
117
+ return true;
118
+ }
119
+ if (style.visibility === "hidden") {
120
+ return true;
121
+ }
122
+ if (style.opacity === "0") {
123
+ return true;
124
+ }
125
+ return false;
126
+ }
127
+ function getNormalizedText(el) {
128
+ return normalizeText(el.textContent ?? "");
129
+ }
130
+ function normalizeText(str) {
131
+ return str.replace(/\p{Cf}/gu, " ").replace(/\s+/g, " ").trim();
132
+ }
133
+ function truncateAttrValue(value, limit = 100) {
134
+ if (value.match(/^(javascript:)?void/)) {
135
+ return "";
136
+ }
137
+ if (value.startsWith("data:")) {
138
+ return "";
139
+ }
140
+ if (value.length > limit) {
141
+ return value.slice(0, limit) + "\u2026";
142
+ }
143
+ return value;
144
+ }
145
+ function escapeAttribute(value) {
146
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\r?\n/g, " ");
147
+ }
148
+ function containsSelector(el, selector) {
149
+ return el.matches(selector) || !!el.querySelector(selector);
150
+ }
151
+ function getOffsetTop(node) {
152
+ let y = 0;
153
+ let current = node;
154
+ while (current) {
155
+ y += current.offsetTop ?? 0;
156
+ current = current.offsetParent;
157
+ }
158
+ return y;
159
+ }
160
+ function hasVisibleArea(element) {
161
+ const rect = element.getBoundingClientRect();
162
+ const area = rect.width * rect.height;
163
+ return area > 64;
164
+ }
165
+ function isDeepHidden(element) {
166
+ if (isHidden(element)) {
167
+ return true;
168
+ }
169
+ if (!hasVisibleArea(element)) {
170
+ return [...element.children].every((el) => isDeepHidden(el));
171
+ }
172
+ return false;
173
+ }
174
+ function isInteractive(el) {
175
+ const htmlEl = el;
176
+ if (INTERACTIVE_TAGS.includes(el.tagName.toLowerCase())) {
177
+ return true;
178
+ }
179
+ const tabindex = htmlEl.getAttribute("tabindex");
180
+ if (tabindex && parseInt(tabindex) >= 0) {
181
+ return true;
182
+ }
183
+ const role = htmlEl.getAttribute("role") ?? htmlEl.getAttribute("aria-role") ?? "";
184
+ if (INTERACTIVE_ROLES.includes(role)) {
185
+ return true;
186
+ }
187
+ if (htmlEl.onclick != null) {
188
+ return true;
189
+ }
190
+ const styles = getComputedStyle(htmlEl);
191
+ if (styles.cursor === "pointer") {
192
+ return true;
193
+ }
194
+ return false;
195
+ }
196
+ function getViewportSize() {
197
+ return {
198
+ width: window.innerWidth,
199
+ height: window.innerHeight
200
+ };
201
+ }
202
+ function isRectInViewport(rect, viewport) {
203
+ return rect.left < viewport.width && rect.right > 0 && rect.top < viewport.height && rect.bottom > 0;
204
+ }
205
+ function makeOverlaysOpaque(el) {
206
+ if (!el[ORIGINAL_STYLE_SYMBOL]) {
207
+ el[ORIGINAL_STYLE_SYMBOL] = el.getAttribute("style");
208
+ }
209
+ const rect = el.getBoundingClientRect();
210
+ if (rect.width < 500 || rect.height < 500) {
211
+ return;
212
+ }
213
+ const style = getComputedStyle(el);
214
+ if (style.pointerEvents === "none") {
215
+ return;
216
+ }
217
+ el.style.setProperty("animation", "none", "important");
218
+ el.style.setProperty("transition", "none", "important");
219
+ el.style.setProperty("backdrop-filter", "none", "important");
220
+ el.style.setProperty("mix-blend-mode", "normal", "important");
221
+ el.style.setProperty("opacity", "1", "important");
222
+ el.style.setProperty("filter", "none", "important");
223
+ const bg = style.backgroundColor;
224
+ el.style.setProperty("background-color", removeAlpha(bg));
225
+ }
226
+ function removeAlpha(color) {
227
+ const rgba = color.match(/^rgba\((.*)\)$/);
228
+ if (!rgba) {
229
+ return color;
230
+ }
231
+ const [r, g, b, a] = rgba[1].split(",").map(parseFloat);
232
+ if (a > 0 && a < 1) {
233
+ return `rgba(${r},${g},${b},1)`;
234
+ }
235
+ return color;
236
+ }
237
+ function isRandomIdentifier(str) {
238
+ const words = str.split(/[_-]/g);
239
+ return !words.some((w) => isWordLike(w)) || words.some((w) => isRandomString(w));
240
+ }
241
+ function isWordLike(str) {
242
+ if (str.length <= 2) {
243
+ return false;
244
+ }
245
+ if (/[^eyuioa]{4,}/.test(str)) {
246
+ return false;
247
+ }
248
+ if (/[eyuioa]{4,}/.test(str)) {
249
+ return false;
250
+ }
251
+ return true;
252
+ }
253
+ function isRandomString(str) {
254
+ if (/^[0-9a-f]+$/i.test(str) && /[0-9]/.test(str)) {
255
+ return true;
256
+ }
257
+ if (/[0-9][^0-9]+[0-9]/.test(str)) {
258
+ return true;
259
+ }
260
+ return false;
261
+ }
262
+ function fixZIndex(el) {
263
+ const _style = getComputedStyle(el);
264
+ const zIndex = parseInt(_style.zIndex);
265
+ if (!isNaN(zIndex)) {
266
+ const maxZIndex = 2147483647;
267
+ if (zIndex > maxZIndex - 200) {
268
+ el.style.zIndex = String(zIndex - 1);
269
+ }
270
+ }
271
+ }
272
+
273
+ // src/page/traverse.ts
274
+ function* traverseVxNode(vxNode, depth = 0) {
275
+ yield { vxNode, depth };
276
+ for (const child of vxNode.children ?? []) {
277
+ yield* traverseVxNode(child, depth + 1);
278
+ }
279
+ }
280
+ function* traverseElements(element) {
281
+ yield element;
282
+ for (const child of element.children) {
283
+ yield* traverseElements(child);
284
+ }
285
+ }
286
+
287
+ // src/page/probe.ts
288
+ function probeViewport() {
289
+ const { width, height } = getViewportSize();
290
+ const points = new PointSet(width, height);
291
+ const result = /* @__PURE__ */ new Set();
292
+ for (const el of traverseElements(document.body)) {
293
+ const rect = el.getBoundingClientRect();
294
+ points.add(rect.left, rect.top);
295
+ points.add(rect.right, rect.top);
296
+ points.add(rect.left, rect.bottom);
297
+ points.add(rect.right, rect.bottom);
298
+ points.add(rect.left + rect.width / 2, rect.top + rect.height / 2);
299
+ }
300
+ for (const { x, y } of points.getAll()) {
301
+ const element = document.elementFromPoint(x, y);
302
+ if (element && !result.has(element)) {
303
+ result.add(element);
304
+ }
305
+ }
306
+ return Array.from(result);
307
+ }
308
+ var PointSet = class {
309
+ constructor(maxX, maxY) {
310
+ this.maxX = maxX;
311
+ this.maxY = maxY;
312
+ this.map = /* @__PURE__ */ new Map();
313
+ }
314
+ *getAll() {
315
+ for (const [y, set] of this.map.entries()) {
316
+ for (const x of set) {
317
+ yield { x, y };
318
+ }
319
+ }
320
+ }
321
+ has(x, y) {
322
+ if (x > this.maxX || y > this.maxY) {
323
+ return false;
324
+ }
325
+ const set = this.map.get(y);
326
+ return set ? set.has(x) : false;
327
+ }
328
+ add(x, y) {
329
+ if (x > this.maxX || y > this.maxY) {
330
+ return;
331
+ }
332
+ const set = this.map.get(y);
333
+ if (set) {
334
+ set.add(x);
335
+ } else {
336
+ this.map.set(y, /* @__PURE__ */ new Set([x]));
337
+ }
338
+ }
339
+ };
340
+
341
+ // src/page/util.ts
342
+ function isContainerNode(vxNode) {
343
+ if (vxNode.tagName === "select") {
344
+ return false;
345
+ }
346
+ const children = vxNode.children ?? [];
347
+ const hasTextChildren = children.some((child) => !child.tagName);
348
+ return children.length > 0 && !hasTextChildren;
349
+ }
350
+ function isObjectEmpty(obj) {
351
+ return !Object.values(obj).some((value) => !!value);
352
+ }
353
+
354
+ // src/page/parser.ts
355
+ var VX_NODE_SYMBOL = Symbol("vx:node");
356
+ var VX_IGNORE_SYMBOL = Symbol("vx:ignore");
357
+ var VX_IGNORE_TAGS = ["svg", "script", "noscript", "style", "link", "meta"];
358
+ var VX_LABEL_ATTRS = ["title", "alt", "placeholder", "aria-label"];
359
+ var VX_LABEL_ATTR_PATTERNS = [
360
+ /^aria-.+/,
361
+ // aria-* attributes
362
+ /^data-label/,
363
+ // data-label attributes
364
+ /title/i
365
+ // title-* attributes
366
+ ];
367
+ var VX_VALUE_ATTRS = ["value", "checked", "selected", "disabled", "readonly"];
368
+ var VX_SRC_ATTRS = ["src", "href"];
369
+ var VX_TAG_PREFERENCE = [
370
+ "a",
371
+ "iframe",
372
+ "input",
373
+ "button",
374
+ "select",
375
+ "textarea",
376
+ "img",
377
+ "label",
378
+ "h1",
379
+ "h2",
380
+ "h3",
381
+ "h4",
382
+ "h5",
383
+ "h6",
384
+ "ul",
385
+ "ol",
386
+ "li",
387
+ "dl",
388
+ "dt",
389
+ "dd",
390
+ "section",
391
+ "article",
392
+ "main",
393
+ "header",
394
+ "footer",
395
+ "nav",
396
+ "aside",
397
+ "form",
398
+ "input",
399
+ "textarea",
400
+ "select",
401
+ "option",
402
+ "fieldset",
403
+ "legend",
404
+ "p",
405
+ "pre",
406
+ "code",
407
+ "blockquote",
408
+ "figure",
409
+ "figcaption",
410
+ "table",
411
+ "thead",
412
+ "tbody",
413
+ "tr",
414
+ "td",
415
+ "th",
416
+ "strong",
417
+ "em",
418
+ "sub",
419
+ "sup"
420
+ ];
421
+ var VX_KEEP_SELECTOR = "a, button, input, textarea, select, label, iframe, option, optgroup";
422
+ var VxTreeParser = class {
423
+ constructor(options = {}) {
424
+ this.options = options;
425
+ this.probeElements = [];
426
+ this.domRefMap = /* @__PURE__ */ new Map();
427
+ const { startRef = 0 } = options;
428
+ this.viewport = getViewportSize();
429
+ this.counter = new Counter(startRef);
430
+ if (options.probeViewport) {
431
+ this.probeElements = probeViewport();
432
+ }
433
+ const vxRoot = this.parseDocument();
434
+ this.refRange = [startRef, this.counter.current()];
435
+ this.vxNodes = this.pruneRecursive(vxRoot);
436
+ }
437
+ parseDocument() {
438
+ return this.parseNode(document.documentElement, null);
439
+ }
440
+ getTree() {
441
+ return new VxTreeView(this.vxNodes, this.refRange);
442
+ }
443
+ getNodes() {
444
+ return this.vxNodes;
445
+ }
446
+ getRefRange() {
447
+ return this.refRange;
448
+ }
449
+ getDomMap() {
450
+ return this.domRefMap;
451
+ }
452
+ parseNode(node, parent) {
453
+ if (!node || node[VX_IGNORE_SYMBOL]) {
454
+ return null;
455
+ }
456
+ if (node instanceof Text) {
457
+ const textContent = normalizeText(node.textContent ?? "");
458
+ if (textContent) {
459
+ return this.makeNode(node, {
460
+ textContent,
461
+ hasVisibleArea: parent?.hasVisibleArea,
462
+ isOutsideViewport: parent?.isOutsideViewport,
463
+ isProbeHit: parent?.isProbeHit
464
+ });
465
+ }
466
+ return null;
467
+ }
468
+ if (node instanceof Element) {
469
+ if (this.options.opaqueOverlays) {
470
+ makeOverlaysOpaque(node);
471
+ }
472
+ return this.parseElement(node);
473
+ }
474
+ return null;
475
+ }
476
+ parseElement(el) {
477
+ const skip = VX_IGNORE_TAGS.includes(el.tagName.toLowerCase()) || isHidden(el);
478
+ if (skip) {
479
+ this.clearRecursive(el);
480
+ return null;
481
+ }
482
+ const parentEl = el.matches("option, optgroup") ? el.closest("select") ?? el : el;
483
+ const rect = parentEl.getBoundingClientRect();
484
+ const id = el.getAttribute("id") ?? "";
485
+ const vxNode = this.makeNode(el, {
486
+ tagName: el.tagName.toLowerCase(),
487
+ id: isRandomIdentifier(id) ? void 0 : id,
488
+ classList: Array.from(el.classList).filter((cls) => !isRandomIdentifier(cls)).slice(0, 4),
489
+ labelAttrs: this.collectLabelAttrs(el),
490
+ valueAttrs: this.collectValueAttrs(el),
491
+ srcAttrs: this.collectSrcAttrs(el),
492
+ hasVisibleArea: hasVisibleArea(parentEl),
493
+ isInteractive: isInteractive(parentEl),
494
+ isOutsideViewport: !isRectInViewport(rect, this.viewport),
495
+ isProbeHit: this.isProbeHit(parentEl),
496
+ isKept: el.matches(VX_KEEP_SELECTOR)
497
+ });
498
+ const children = [...el.childNodes].map((child) => this.parseNode(child, vxNode)).filter((_) => _ != null);
499
+ if (children.length === 1 && !children[0]?.tagName) {
500
+ vxNode.textContent = normalizeText(children[0].textContent ?? "");
501
+ } else {
502
+ vxNode.children = children;
503
+ }
504
+ return vxNode;
505
+ }
506
+ makeNode(node, spec) {
507
+ const ref = this.counter.next();
508
+ const isNew = !node[VX_NODE_SYMBOL];
509
+ const vxNode = {
510
+ ...spec,
511
+ ref,
512
+ isNew
513
+ };
514
+ this.domRefMap.set(ref, node);
515
+ node[VX_NODE_SYMBOL] = vxNode;
516
+ return vxNode;
517
+ }
518
+ pruneRecursive(vxNode) {
519
+ if (!vxNode) {
520
+ return [];
521
+ }
522
+ const children = vxNode.children ?? [];
523
+ const newChildren = children.flatMap((child) => this.pruneRecursive(child));
524
+ if (this.shouldOmit(vxNode)) {
525
+ return newChildren;
526
+ }
527
+ vxNode.children = newChildren;
528
+ if (newChildren.length === 1) {
529
+ const child = newChildren[0];
530
+ return this.collapseSingleChild(vxNode, child);
531
+ }
532
+ return [vxNode];
533
+ }
534
+ collapseSingleChild(parent, child) {
535
+ const preferParent = this.collapsePreferParent(parent, child);
536
+ const merged = preferParent ? this.collapseMerge(parent, child) : this.collapseMerge(child, parent);
537
+ return this.pruneRecursive({
538
+ ...merged,
539
+ children: child.children,
540
+ textContent: child.textContent
541
+ });
542
+ }
543
+ collapsePreferParent(parent, child) {
544
+ const parentRank = VX_TAG_PREFERENCE.indexOf(parent.tagName ?? "");
545
+ const childRank = VX_TAG_PREFERENCE.indexOf(child.tagName ?? "");
546
+ if (parentRank === -1 && childRank === -1) {
547
+ const parentAttrCount = Object.keys({
548
+ ...parent.labelAttrs,
549
+ ...parent.valueAttrs,
550
+ ...parent.srcAttrs
551
+ }).length;
552
+ const childAttrCount = Object.keys({
553
+ ...child.labelAttrs,
554
+ ...child.valueAttrs,
555
+ ...child.srcAttrs
556
+ }).length;
557
+ return parentAttrCount > childAttrCount;
558
+ }
559
+ return parentRank !== -1 && (childRank === -1 || parentRank < childRank);
560
+ }
561
+ collapseMerge(a, b) {
562
+ return {
563
+ ...b,
564
+ ...a,
565
+ labelAttrs: { ...b.labelAttrs, ...a.labelAttrs },
566
+ valueAttrs: { ...b.valueAttrs, ...a.valueAttrs },
567
+ srcAttrs: { ...b.srcAttrs, ...a.srcAttrs }
568
+ };
569
+ }
570
+ shouldOmit(vxNode) {
571
+ const tagName = vxNode.tagName ?? "";
572
+ if (!vxNode.hasVisibleArea) {
573
+ return true;
574
+ }
575
+ if (this.options.viewportOnly && vxNode.isOutsideViewport) {
576
+ return true;
577
+ }
578
+ if (this.options.probeViewport && !vxNode.isProbeHit) {
579
+ return true;
580
+ }
581
+ if (this.options.skipImages && vxNode.tagName === "img") {
582
+ return true;
583
+ }
584
+ const hasText = !!vxNode.textContent;
585
+ const hasChildren = (vxNode.children ?? []).length > 0;
586
+ const hasAttrs = [vxNode.labelAttrs, vxNode.valueAttrs, vxNode.srcAttrs].some((attrs) => !isObjectEmpty(attrs ?? {}));
587
+ if (this.options.unnestDivs) {
588
+ const isDivOrSpan = ["div", "span"].includes(tagName);
589
+ if (isDivOrSpan && hasChildren && !hasAttrs) {
590
+ return true;
591
+ }
592
+ }
593
+ if (vxNode.isKept) {
594
+ return false;
595
+ }
596
+ return !hasText && !hasAttrs && !hasChildren;
597
+ }
598
+ isProbeHit(el) {
599
+ for (const probeEl of this.probeElements) {
600
+ if (el.contains(probeEl)) {
601
+ return true;
602
+ }
603
+ }
604
+ return false;
605
+ }
606
+ collectLabelAttrs(el) {
607
+ const attrs = {};
608
+ for (const attr of el.attributes) {
609
+ const attrName = attr.name;
610
+ const value = truncateAttrValue(attr.value);
611
+ if (!value) {
612
+ continue;
613
+ }
614
+ if (VX_LABEL_ATTRS.includes(attrName)) {
615
+ attrs[attrName] = value;
616
+ continue;
617
+ }
618
+ if (VX_LABEL_ATTR_PATTERNS.some((pattern) => pattern.test(attrName))) {
619
+ attrs[attrName] = value;
620
+ }
621
+ }
622
+ return attrs;
623
+ }
624
+ collectValueAttrs(el) {
625
+ const attrs = {};
626
+ for (const attr of VX_VALUE_ATTRS) {
627
+ const value = el[attr] ?? "";
628
+ if (value) {
629
+ attrs[attr] = String(value);
630
+ }
631
+ }
632
+ return attrs;
633
+ }
634
+ collectSrcAttrs(el) {
635
+ const attrs = {};
636
+ for (const attr of VX_SRC_ATTRS) {
637
+ const value = truncateAttrValue(el[attr] ?? "");
638
+ if (value) {
639
+ attrs[attr] = value;
640
+ }
641
+ }
642
+ return attrs;
643
+ }
644
+ clearRecursive(node) {
645
+ node[VX_NODE_SYMBOL] = null;
646
+ if (node instanceof Element) {
647
+ for (const child of node.children) {
648
+ this.clearRecursive(child);
649
+ }
650
+ }
651
+ }
652
+ };
653
+
654
+ // src/page/overlay.ts
655
+ function showPoint(x, y, clear = true) {
656
+ if (clear) {
657
+ clearOverlay();
658
+ }
659
+ const point = document.createElement("div");
660
+ point.style.position = "absolute";
661
+ point.style.left = `${x}px`;
662
+ point.style.top = `${y}px`;
663
+ point.style.width = "32px";
664
+ point.style.height = "32px";
665
+ point.style.transform = "translate(-50%, -50%)";
666
+ point.style.backgroundColor = "red";
667
+ point.style.borderRadius = "100%";
668
+ point.style.opacity = "0.5";
669
+ const container = getOverlayContainer();
670
+ container.appendChild(point);
671
+ }
672
+ function clearOverlay() {
673
+ const container = getOverlayContainer();
674
+ container.remove();
675
+ }
676
+ function getOverlayContainer() {
677
+ let container = document.querySelector("#webvision-overlay");
678
+ if (!container) {
679
+ container = document.createElement("div");
680
+ container[VX_IGNORE_SYMBOL] = true;
681
+ container.id = "webvision-overlay";
682
+ container.style.position = "fixed";
683
+ container.style.top = "0";
684
+ container.style.left = "0";
685
+ container.style.bottom = "0";
686
+ container.style.right = "0";
687
+ container.style.zIndex = "2147483647";
688
+ container.style.pointerEvents = "none";
689
+ document.body.appendChild(container);
690
+ }
691
+ return container;
692
+ }
693
+
694
+ // src/page/highlight.ts
695
+ function highlightEl(el, ref = 0, color) {
696
+ if (!(el instanceof Element)) {
697
+ return;
698
+ }
699
+ if (el instanceof HTMLOptGroupElement) {
700
+ return;
701
+ }
702
+ if (el instanceof HTMLOptionElement) {
703
+ return;
704
+ }
705
+ const container = getOverlayContainer();
706
+ const rect = el.getBoundingClientRect();
707
+ const overlay = document.createElement("div");
708
+ container.appendChild(overlay);
709
+ overlay.style.position = "absolute";
710
+ overlay.style.top = `${rect.top}px`;
711
+ overlay.style.left = `${rect.left}px`;
712
+ overlay.style.width = `${rect.width}px`;
713
+ overlay.style.height = `${rect.height}px`;
714
+ overlay.style.border = `2px solid ${color}`;
715
+ const label = document.createElement("div");
716
+ overlay.appendChild(label);
717
+ label.style.position = "absolute";
718
+ label.style.bottom = `100%`;
719
+ label.style.left = `0`;
720
+ label.style.backgroundColor = color ?? getRandomColor(ref);
721
+ label.style.color = "white";
722
+ label.style.fontSize = "10px";
723
+ label.style.fontFamily = "monospace";
724
+ label.style.fontWeight = "normal";
725
+ label.style.fontStyle = "normal";
726
+ label.style.opacity = "0.8";
727
+ label.style.padding = "0 2px";
728
+ label.style.transform = "translateY(50%)";
729
+ label.textContent = String(ref);
730
+ }
731
+ function getRandomColor(index) {
732
+ const hue = index * 120 * 0.382 % 360;
733
+ return `hsl(${hue}, 85%, 50%)`;
734
+ }
735
+
736
+ // src/page/render.ts
737
+ function renderVxNode(scope, options = {}) {
738
+ const buffer = [];
739
+ const whitelistRefs = options.whitelistRefs ?? [];
740
+ for (const { vxNode, depth } of traverseVxNode(scope)) {
741
+ if (whitelistRefs.length > 0) {
742
+ if (!whitelistRefs.includes(vxNode.ref)) {
743
+ continue;
744
+ }
745
+ }
746
+ if (options.skipNonInteractive && !vxNode.isInteractive) {
747
+ continue;
748
+ }
749
+ const indent = options.skipNonInteractive ? "" : " ".repeat(depth);
750
+ buffer.push(renderIndentedLine(indent, vxNode, options));
751
+ }
752
+ return buffer.join("\n");
753
+ }
754
+ function renderIndentedLine(indent, vxNode, options = {}) {
755
+ const diffPrefix = options.renderDiff ? vxNode.isNew ? "+ " : " " : "";
756
+ if (!vxNode.tagName) {
757
+ return [diffPrefix, indent, vxNode.textContent].filter(Boolean).join("");
758
+ }
759
+ const tagLine = renderTagLine(vxNode, options);
760
+ const htmlStyle = options.renderStyle === "html";
761
+ return [
762
+ diffPrefix,
763
+ indent,
764
+ tagLine,
765
+ htmlStyle ? "" : " ",
766
+ vxNode.textContent ?? ""
767
+ ].join("");
768
+ }
769
+ function renderTagLine(vxNode, options) {
770
+ const htmlStyle = options.renderStyle === "html";
771
+ const components = [];
772
+ if (options.renderTagNames && vxNode.tagName) {
773
+ components.push(vxNode.tagName);
774
+ }
775
+ if (options.renderIds && vxNode.id) {
776
+ if (htmlStyle) {
777
+ components.push(`id="${vxNode.id}"`);
778
+ } else {
779
+ components.push(`#${vxNode.id}`);
780
+ }
781
+ }
782
+ if (options.renderClassNames && vxNode.classList?.length) {
783
+ if (htmlStyle) {
784
+ components.push(`class="${vxNode.classList.join(" ")}"`);
785
+ } else {
786
+ components.push("." + vxNode.classList.join("."));
787
+ }
788
+ }
789
+ if (options.renderRefs) {
790
+ const isRenderRef = [
791
+ options.renderRefs === "all",
792
+ options.renderRefs === true && !isContainerNode(vxNode)
793
+ ].some(Boolean);
794
+ if (isRenderRef) {
795
+ components.push(`[@${vxNode.ref}]`);
796
+ }
797
+ }
798
+ const attrs = [];
799
+ if (options.renderLabelAttrs) {
800
+ for (const [attr, value] of Object.entries(vxNode.labelAttrs ?? {})) {
801
+ attrs.push(`${attr}="${truncateAttrValue(value)}"`);
802
+ }
803
+ }
804
+ if (options.renderValueAttrs) {
805
+ for (const [attr, value] of Object.entries(vxNode.valueAttrs ?? {})) {
806
+ attrs.push(`${attr}="${truncateAttrValue(value)}"`);
807
+ }
808
+ }
809
+ if (options.renderSrcAttrs) {
810
+ for (const [attr, value] of Object.entries(vxNode.srcAttrs ?? {})) {
811
+ attrs.push(`${attr}="${truncateAttrValue(value)}"`);
812
+ }
813
+ }
814
+ if (htmlStyle) {
815
+ components.push(...attrs);
816
+ } else {
817
+ components.push(...attrs.map((attr) => `[${attr}]`));
818
+ }
819
+ return htmlStyle ? `<${components.join(" ")}>` : components.join("");
820
+ }
821
+
822
+ // src/page/snapshot.ts
823
+ var VX_DOM_SYMBOL = Symbol("vx:dom");
824
+ var VX_TREE_SYMBOL = Symbol("vx:tree");
825
+ var VX_LAST_REFS_SYMBOL = Symbol("vx:lastRefs");
826
+ function captureSnapshot(options = {}) {
827
+ const parser = new VxTreeParser(options);
828
+ const domMap = parser.getDomMap();
829
+ const vxTree = parser.getTree();
830
+ globalThis[VX_DOM_SYMBOL] = domMap;
831
+ globalThis[VX_TREE_SYMBOL] = vxTree;
832
+ return vxTree;
833
+ }
834
+ function getSnapshot() {
835
+ const vxTree = globalThis[VX_TREE_SYMBOL];
836
+ if (!vxTree) {
837
+ throw new Error("[VX] Snapshot not found");
838
+ }
839
+ return vxTree;
840
+ }
841
+ function resolveDomNode(ref) {
842
+ const domMap = globalThis[VX_DOM_SYMBOL];
843
+ if (!domMap) {
844
+ return null;
845
+ }
846
+ return domMap.get(ref) ?? null;
847
+ }
848
+
849
+ // src/page/tree.ts
850
+ var VxTreeView = class {
851
+ constructor(nodes, refRange) {
852
+ this.nodes = nodes;
853
+ this.refRange = refRange;
854
+ this.refMap = /* @__PURE__ */ new Map();
855
+ this.buildRefMap(nodes);
856
+ }
857
+ get nodeCount() {
858
+ return this.refMap.size;
859
+ }
860
+ *traverse() {
861
+ for (const vxNode of this.nodes) {
862
+ yield* traverseVxNode(vxNode, 0);
863
+ }
864
+ }
865
+ buildRefMap(nodes) {
866
+ for (const node of nodes) {
867
+ this.refMap.set(node.ref, node);
868
+ this.buildRefMap(node.children ?? []);
869
+ }
870
+ }
871
+ findNode(ref) {
872
+ return this.refMap.get(ref) ?? null;
873
+ }
874
+ render(options = {}) {
875
+ return this.nodes.map((node) => {
876
+ return renderVxNode(node, options);
877
+ }).join("\n");
878
+ }
879
+ highlight(options = {}) {
880
+ if (options.clearOverlay) {
881
+ clearOverlay();
882
+ }
883
+ const leafOnly = !options.includeAll;
884
+ const refs = this.collectRefs(leafOnly, options.filterRefs);
885
+ this.highlightRefs(refs, options);
886
+ }
887
+ highlightRefs(refs, options = {}) {
888
+ for (const ref of refs) {
889
+ const el = resolveDomNode(ref);
890
+ if (el instanceof Element) {
891
+ const vxNode = this.findNode(ref);
892
+ const isNew = vxNode?.isNew ?? false;
893
+ const color = options.useColors ? void 0 : isNew ? "#0a0" : "#060";
894
+ highlightEl(el, ref, color);
895
+ }
896
+ }
897
+ }
898
+ collectRefs(leafOnly = true, filterRefs) {
899
+ const refs = [];
900
+ for (const { vxNode } of this.traverse()) {
901
+ if (leafOnly && isContainerNode(vxNode)) {
902
+ continue;
903
+ }
904
+ if (filterRefs && !filterRefs.includes(vxNode.ref)) {
905
+ continue;
906
+ }
907
+ refs.push(vxNode.ref);
908
+ }
909
+ return refs;
910
+ }
911
+ };
912
+
913
+ // src/page/frame.ts
914
+ var VxPageView = class {
915
+ constructor(vxFrames) {
916
+ this.vxFrames = vxFrames;
917
+ this.vxFrameMap = /* @__PURE__ */ new Map();
918
+ this.vxTreeMap = /* @__PURE__ */ new Map();
919
+ for (const frame of vxFrames) {
920
+ this.vxFrameMap.set(frame.frameId, frame);
921
+ this.vxTreeMap.set(frame.frameId, new VxTreeView(frame.nodes, frame.refRange));
922
+ }
923
+ }
924
+ get vxTrees() {
925
+ return [...this.vxTreeMap.values()];
926
+ }
927
+ get nodeCount() {
928
+ return this.vxTrees.reduce((acc, vxTree) => acc + vxTree.nodeCount, 0);
929
+ }
930
+ findNodeByRef(ref) {
931
+ const vxTree = this.findTreeByRef(ref);
932
+ if (vxTree == null) {
933
+ return null;
934
+ }
935
+ return vxTree.findNode(ref);
936
+ }
937
+ findTreeByFrameId(frameId) {
938
+ return this.vxTreeMap.get(frameId) ?? null;
939
+ }
940
+ findTreeByRef(ref) {
941
+ const vxFrame = this.findFrameByRef(ref);
942
+ if (vxFrame == null) {
943
+ return null;
944
+ }
945
+ return this.vxTreeMap.get(vxFrame.frameId) ?? null;
946
+ }
947
+ findFrameByFrameId(frameId) {
948
+ return this.vxFrameMap.get(frameId) ?? null;
949
+ }
950
+ findFrameByRef(ref) {
951
+ const vxFrame = this.vxFrames.find(
952
+ (frame) => ref >= frame.refRange[0] && ref <= frame.refRange[1]
953
+ );
954
+ if (!vxFrame) {
955
+ return null;
956
+ }
957
+ return this.vxFrameMap.get(vxFrame.frameId) ?? null;
958
+ }
959
+ getFrameByRef(ref) {
960
+ const vxFrame = this.findFrameByRef(ref);
961
+ if (vxFrame == null) {
962
+ throw new Error(`[VX] Frame not found for [ref=${ref}]`);
963
+ }
964
+ return vxFrame;
965
+ }
966
+ findParentFrame(frameId) {
967
+ const frame = this.findFrameByFrameId(frameId);
968
+ const iframeRef = frame?.iframeRef;
969
+ if (iframeRef == null) {
970
+ return null;
971
+ }
972
+ return this.findFrameByRef(iframeRef);
973
+ }
974
+ isFrameShown(frameId) {
975
+ const frame = this.findFrameByFrameId(frameId);
976
+ if (!frame) {
977
+ return false;
978
+ }
979
+ const parentFrame = this.findParentFrame(frameId);
980
+ const { iframeRef } = frame;
981
+ if (parentFrame == null || iframeRef == null) {
982
+ return true;
983
+ }
984
+ const vxTree = this.vxTreeMap.get(frameId);
985
+ const existsInParent = !!vxTree?.findNode(iframeRef);
986
+ return existsInParent ? this.isFrameShown(parentFrame.frameId) : false;
987
+ }
988
+ renderAll(options) {
989
+ return this.vxFrames.map((frame) => {
990
+ const vxTree = this.vxTreeMap.get(frame.frameId);
991
+ const rendered = vxTree?.render(options);
992
+ return [
993
+ `FRAME ${frame.frameId}${frame.iframeRef ? ` [@${frame.iframeRef}]` : ""}`,
994
+ rendered
995
+ ].join("\n\n");
996
+ }).join("\n\n");
997
+ }
998
+ };
999
+ return __toCommonJS(index_exports);
1000
+ })();
1001
+ //# sourceMappingURL=webvision.global.js.map