@midscene/shared 0.17.2 → 0.17.3-beta-20250526031130.0

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.
@@ -6,6 +6,7 @@ declare enum NodeType {
6
6
  CONTAINER = "CONTAINER Node",
7
7
  FORM_ITEM = "FORM_ITEM Node",
8
8
  BUTTON = "BUTTON Node",
9
+ A = "Anchor Node",
9
10
  IMG = "IMG Node",
10
11
  TEXT = "TEXT Node",
11
12
  POSITION = "POSITION Node"
@@ -7,6 +7,7 @@ var NodeType = /* @__PURE__ */ ((NodeType2) => {
7
7
  NodeType2["CONTAINER"] = "CONTAINER Node";
8
8
  NodeType2["FORM_ITEM"] = "FORM_ITEM Node";
9
9
  NodeType2["BUTTON"] = "BUTTON Node";
10
+ NodeType2["A"] = "Anchor Node";
10
11
  NodeType2["IMG"] = "IMG Node";
11
12
  NodeType2["TEXT"] = "TEXT Node";
12
13
  NodeType2["POSITION"] = "POSITION Node";
@@ -39,6 +39,9 @@ function isFormElement(node) {
39
39
  function isButtonElement(node) {
40
40
  return node instanceof HTMLElement && node.tagName.toLowerCase() === "button";
41
41
  }
42
+ function isAElement(node) {
43
+ return node instanceof HTMLElement && node.tagName.toLowerCase() === "a";
44
+ }
42
45
  function isImgElement(node) {
43
46
  if (!includeBaseElement(node) && node instanceof Element) {
44
47
  const computedStyle = window.getComputedStyle(node);
@@ -89,7 +92,8 @@ function includeBaseElement(node) {
89
92
  "textarea",
90
93
  "select",
91
94
  "option",
92
- "img"
95
+ "img",
96
+ "a"
93
97
  ];
94
98
  for (const tagName of includeList) {
95
99
  const element = node.querySelectorAll(tagName);
@@ -471,10 +475,20 @@ function collectElementInfo(node, currentWindow, currentDocument, baseZoom = 1,
471
475
  return elementInfo;
472
476
  }
473
477
  if (isButtonElement(node)) {
478
+ const rect2 = mergeElementAndChildrenRects(
479
+ node,
480
+ currentWindow,
481
+ currentDocument,
482
+ baseZoom,
483
+ visibleOnly
484
+ );
485
+ if (!rect2) {
486
+ return null;
487
+ }
474
488
  const attributes = getNodeAttributes(node, currentWindow);
475
489
  const pseudo = getPseudoElementContent(node, currentWindow);
476
490
  const content = node.innerText || pseudo.before || pseudo.after || "";
477
- const nodeHashId = midsceneGenerateHash(node, content, rect);
491
+ const nodeHashId = midsceneGenerateHash(node, content, rect2);
478
492
  const selector = setDataForNode(node, nodeHashId, false, currentWindow);
479
493
  const elementInfo = {
480
494
  id: nodeHashId,
@@ -488,12 +502,12 @@ function collectElementInfo(node, currentWindow, currentDocument, baseZoom = 1,
488
502
  nodeType: "BUTTON Node" /* BUTTON */
489
503
  },
490
504
  content,
491
- rect,
505
+ rect: rect2,
492
506
  center: [
493
- Math.round(rect.left + rect.width / 2),
494
- Math.round(rect.top + rect.height / 2)
507
+ Math.round(rect2.left + rect2.width / 2),
508
+ Math.round(rect2.top + rect2.height / 2)
495
509
  ],
496
- zoom: rect.zoom
510
+ zoom: rect2.zoom
497
511
  };
498
512
  return elementInfo;
499
513
  }
@@ -558,6 +572,33 @@ function collectElementInfo(node, currentWindow, currentDocument, baseZoom = 1,
558
572
  };
559
573
  return elementInfo;
560
574
  }
575
+ if (isAElement(node)) {
576
+ const attributes = getNodeAttributes(node, currentWindow);
577
+ const pseudo = getPseudoElementContent(node, currentWindow);
578
+ const content = node.innerText || pseudo.before || pseudo.after || "";
579
+ const nodeHashId = midsceneGenerateHash(node, content, rect);
580
+ const selector = setDataForNode(node, nodeHashId, false, currentWindow);
581
+ const elementInfo = {
582
+ id: nodeHashId,
583
+ indexId: indexId++,
584
+ nodeHashId,
585
+ nodeType: "Anchor Node" /* A */,
586
+ locator: selector,
587
+ attributes: {
588
+ ...attributes,
589
+ htmlTagName: tagNameOfNode(node),
590
+ nodeType: "Anchor Node" /* A */
591
+ },
592
+ content,
593
+ rect,
594
+ center: [
595
+ Math.round(rect.left + rect.width / 2),
596
+ Math.round(rect.top + rect.height / 2)
597
+ ],
598
+ zoom: rect.zoom
599
+ };
600
+ return elementInfo;
601
+ }
561
602
  if (isContainerElement(node)) {
562
603
  const attributes = getNodeAttributes(node, currentWindow);
563
604
  const nodeHashId = midsceneGenerateHash(node, "", rect);
@@ -628,7 +669,7 @@ function extractTreeNode(initNode, debugMode2 = false) {
628
669
  node: elementInfo,
629
670
  children: []
630
671
  };
631
- if (elementInfo?.nodeType === "BUTTON Node" /* BUTTON */ || elementInfo?.nodeType === "IMG Node" /* IMG */ || elementInfo?.nodeType === "TEXT Node" /* TEXT */ || elementInfo?.nodeType === "FORM_ITEM Node" /* FORM_ITEM */ || elementInfo?.nodeType === "CONTAINER Node" /* CONTAINER */) {
672
+ if (elementInfo?.nodeType === "BUTTON Node" /* BUTTON */ || elementInfo?.nodeType === "IMG Node" /* IMG */ || elementInfo?.nodeType === "TEXT Node" /* TEXT */ || elementInfo?.nodeType === "Anchor Node" /* A */ || elementInfo?.nodeType === "FORM_ITEM Node" /* FORM_ITEM */ || elementInfo?.nodeType === "CONTAINER Node" /* CONTAINER */) {
632
673
  return nodeInfo;
633
674
  }
634
675
  const rect = getRect(node, baseZoom, currentWindow);
@@ -683,6 +724,50 @@ function extractTreeNode(initNode, debugMode2 = false) {
683
724
  children: topChildren
684
725
  };
685
726
  }
727
+ function mergeElementAndChildrenRects(node, currentWindow, currentDocument, baseZoom = 1, visibleOnly = true) {
728
+ const selfRect = elementRect(
729
+ node,
730
+ currentWindow,
731
+ currentDocument,
732
+ baseZoom,
733
+ visibleOnly
734
+ );
735
+ if (!selfRect)
736
+ return null;
737
+ let minLeft = selfRect.left;
738
+ let minTop = selfRect.top;
739
+ let maxRight = selfRect.left + selfRect.width;
740
+ let maxBottom = selfRect.top + selfRect.height;
741
+ function traverse(child) {
742
+ for (let i = 0; i < child.childNodes.length; i++) {
743
+ const sub = child.childNodes[i];
744
+ if (sub.nodeType === 1) {
745
+ const rect = elementRect(
746
+ sub,
747
+ currentWindow,
748
+ currentDocument,
749
+ baseZoom,
750
+ visibleOnly
751
+ );
752
+ if (rect) {
753
+ minLeft = Math.min(minLeft, rect.left);
754
+ minTop = Math.min(minTop, rect.top);
755
+ maxRight = Math.max(maxRight, rect.left + rect.width);
756
+ maxBottom = Math.max(maxBottom, rect.top + rect.height);
757
+ }
758
+ traverse(sub);
759
+ }
760
+ }
761
+ }
762
+ traverse(node);
763
+ return {
764
+ ...selfRect,
765
+ left: minLeft,
766
+ top: minTop,
767
+ width: maxRight - minLeft,
768
+ height: maxBottom - minTop
769
+ };
770
+ }
686
771
 
687
772
  // src/extractor/debug.ts
688
773
  console.log(extractTextWithPosition(document.body, true));
@@ -160,6 +160,9 @@ function isFormElement(node) {
160
160
  function isButtonElement(node) {
161
161
  return node instanceof HTMLElement && node.tagName.toLowerCase() === "button";
162
162
  }
163
+ function isAElement(node) {
164
+ return node instanceof HTMLElement && node.tagName.toLowerCase() === "a";
165
+ }
163
166
  function isImgElement(node) {
164
167
  if (!includeBaseElement(node) && node instanceof Element) {
165
168
  const computedStyle = window.getComputedStyle(node);
@@ -210,7 +213,8 @@ function includeBaseElement(node) {
210
213
  "textarea",
211
214
  "select",
212
215
  "option",
213
- "img"
216
+ "img",
217
+ "a"
214
218
  ];
215
219
  for (const tagName of includeList) {
216
220
  const element = node.querySelectorAll(tagName);
@@ -599,10 +603,20 @@ function collectElementInfo(node, currentWindow, currentDocument, baseZoom = 1,
599
603
  return elementInfo;
600
604
  }
601
605
  if (isButtonElement(node)) {
606
+ const rect2 = mergeElementAndChildrenRects(
607
+ node,
608
+ currentWindow,
609
+ currentDocument,
610
+ baseZoom,
611
+ visibleOnly
612
+ );
613
+ if (!rect2) {
614
+ return null;
615
+ }
602
616
  const attributes = getNodeAttributes(node, currentWindow);
603
617
  const pseudo = getPseudoElementContent(node, currentWindow);
604
618
  const content = node.innerText || pseudo.before || pseudo.after || "";
605
- const nodeHashId = midsceneGenerateHash(node, content, rect);
619
+ const nodeHashId = midsceneGenerateHash(node, content, rect2);
606
620
  const selector = setDataForNode(node, nodeHashId, false, currentWindow);
607
621
  const elementInfo = {
608
622
  id: nodeHashId,
@@ -616,12 +630,12 @@ function collectElementInfo(node, currentWindow, currentDocument, baseZoom = 1,
616
630
  nodeType: "BUTTON Node" /* BUTTON */
617
631
  },
618
632
  content,
619
- rect,
633
+ rect: rect2,
620
634
  center: [
621
- Math.round(rect.left + rect.width / 2),
622
- Math.round(rect.top + rect.height / 2)
635
+ Math.round(rect2.left + rect2.width / 2),
636
+ Math.round(rect2.top + rect2.height / 2)
623
637
  ],
624
- zoom: rect.zoom
638
+ zoom: rect2.zoom
625
639
  };
626
640
  return elementInfo;
627
641
  }
@@ -686,6 +700,33 @@ function collectElementInfo(node, currentWindow, currentDocument, baseZoom = 1,
686
700
  };
687
701
  return elementInfo;
688
702
  }
703
+ if (isAElement(node)) {
704
+ const attributes = getNodeAttributes(node, currentWindow);
705
+ const pseudo = getPseudoElementContent(node, currentWindow);
706
+ const content = node.innerText || pseudo.before || pseudo.after || "";
707
+ const nodeHashId = midsceneGenerateHash(node, content, rect);
708
+ const selector = setDataForNode(node, nodeHashId, false, currentWindow);
709
+ const elementInfo = {
710
+ id: nodeHashId,
711
+ indexId: indexId++,
712
+ nodeHashId,
713
+ nodeType: "Anchor Node" /* A */,
714
+ locator: selector,
715
+ attributes: {
716
+ ...attributes,
717
+ htmlTagName: tagNameOfNode(node),
718
+ nodeType: "Anchor Node" /* A */
719
+ },
720
+ content,
721
+ rect,
722
+ center: [
723
+ Math.round(rect.left + rect.width / 2),
724
+ Math.round(rect.top + rect.height / 2)
725
+ ],
726
+ zoom: rect.zoom
727
+ };
728
+ return elementInfo;
729
+ }
689
730
  if (isContainerElement(node)) {
690
731
  const attributes = getNodeAttributes(node, currentWindow);
691
732
  const nodeHashId = midsceneGenerateHash(node, "", rect);
@@ -760,7 +801,7 @@ function extractTreeNode(initNode, debugMode2 = false) {
760
801
  node: elementInfo,
761
802
  children: []
762
803
  };
763
- if (elementInfo?.nodeType === "BUTTON Node" /* BUTTON */ || elementInfo?.nodeType === "IMG Node" /* IMG */ || elementInfo?.nodeType === "TEXT Node" /* TEXT */ || elementInfo?.nodeType === "FORM_ITEM Node" /* FORM_ITEM */ || elementInfo?.nodeType === "CONTAINER Node" /* CONTAINER */) {
804
+ if (elementInfo?.nodeType === "BUTTON Node" /* BUTTON */ || elementInfo?.nodeType === "IMG Node" /* IMG */ || elementInfo?.nodeType === "TEXT Node" /* TEXT */ || elementInfo?.nodeType === "Anchor Node" /* A */ || elementInfo?.nodeType === "FORM_ITEM Node" /* FORM_ITEM */ || elementInfo?.nodeType === "CONTAINER Node" /* CONTAINER */) {
764
805
  return nodeInfo;
765
806
  }
766
807
  const rect = getRect(node, baseZoom, currentWindow);
@@ -815,6 +856,50 @@ function extractTreeNode(initNode, debugMode2 = false) {
815
856
  children: topChildren
816
857
  };
817
858
  }
859
+ function mergeElementAndChildrenRects(node, currentWindow, currentDocument, baseZoom = 1, visibleOnly = true) {
860
+ const selfRect = elementRect(
861
+ node,
862
+ currentWindow,
863
+ currentDocument,
864
+ baseZoom,
865
+ visibleOnly
866
+ );
867
+ if (!selfRect)
868
+ return null;
869
+ let minLeft = selfRect.left;
870
+ let minTop = selfRect.top;
871
+ let maxRight = selfRect.left + selfRect.width;
872
+ let maxBottom = selfRect.top + selfRect.height;
873
+ function traverse(child) {
874
+ for (let i = 0; i < child.childNodes.length; i++) {
875
+ const sub = child.childNodes[i];
876
+ if (sub.nodeType === 1) {
877
+ const rect = elementRect(
878
+ sub,
879
+ currentWindow,
880
+ currentDocument,
881
+ baseZoom,
882
+ visibleOnly
883
+ );
884
+ if (rect) {
885
+ minLeft = Math.min(minLeft, rect.left);
886
+ minTop = Math.min(minTop, rect.top);
887
+ maxRight = Math.max(maxRight, rect.left + rect.width);
888
+ maxBottom = Math.max(maxBottom, rect.top + rect.height);
889
+ }
890
+ traverse(sub);
891
+ }
892
+ }
893
+ }
894
+ traverse(node);
895
+ return {
896
+ ...selfRect,
897
+ left: minLeft,
898
+ top: minTop,
899
+ width: maxRight - minLeft,
900
+ height: maxBottom - minTop
901
+ };
902
+ }
818
903
 
819
904
  // src/extractor/locator.ts
820
905
  var getElementIndex = (element) => {
@@ -828,16 +913,6 @@ var getElementIndex = (element) => {
828
913
  }
829
914
  return index;
830
915
  };
831
- var findFirstAncestorWithId = (element) => {
832
- let current = element;
833
- while (current?.parentElement) {
834
- if (current.id) {
835
- return current;
836
- }
837
- current = current.parentElement;
838
- }
839
- return null;
840
- };
841
916
  var getTextNodeIndex = (textNode) => {
842
917
  let index = 1;
843
918
  let current = textNode.previousSibling;
@@ -855,6 +930,10 @@ var getElementXPath = (element) => {
855
930
  if (parentNode && parentNode.nodeType === Node.ELEMENT_NODE) {
856
931
  const parentXPath = getElementXPath(parentNode);
857
932
  const textIndex = getTextNodeIndex(element);
933
+ const textContent = element.textContent?.trim();
934
+ if (textContent) {
935
+ return `${parentXPath}/text()[${textIndex}][normalize-space()="${textContent}"]`;
936
+ }
858
937
  return `${parentXPath}/text()[${textIndex}]`;
859
938
  }
860
939
  return "";
@@ -868,22 +947,6 @@ var getElementXPath = (element) => {
868
947
  if (el === document.body) {
869
948
  return "/html/body";
870
949
  }
871
- if (el.id) {
872
- return `//*[@id="${el.id}"]`;
873
- }
874
- const ancestorWithId = findFirstAncestorWithId(el);
875
- if (ancestorWithId) {
876
- const ancestorPath = `//*[@id="${ancestorWithId.id}"]`;
877
- let current = el;
878
- const pathParts = [];
879
- while (current && current !== ancestorWithId) {
880
- const index2 = getElementIndex(current);
881
- const tagName2 = current.nodeName.toLowerCase();
882
- pathParts.unshift(`${tagName2}[${index2}]`);
883
- current = current.parentElement;
884
- }
885
- return pathParts.length > 0 ? `${ancestorPath}/${pathParts.join("/")}` : ancestorPath;
886
- }
887
950
  if (!el.parentNode) {
888
951
  return `/${el.nodeName.toLowerCase()}`;
889
952
  }
@@ -898,8 +961,8 @@ var getElementXPath = (element) => {
898
961
  function generateXPaths(node) {
899
962
  if (!node)
900
963
  return [];
901
- const xPath = getElementXPath(node);
902
- return [xPath];
964
+ const fullXPath = getElementXPath(node);
965
+ return [fullXPath];
903
966
  }
904
967
  function getXpathsById(id) {
905
968
  const node = getNodeFromCacheList(id);
@@ -6,6 +6,7 @@ declare enum NodeType {
6
6
  CONTAINER = "CONTAINER Node",
7
7
  FORM_ITEM = "FORM_ITEM Node",
8
8
  BUTTON = "BUTTON Node",
9
+ A = "Anchor Node",
9
10
  IMG = "IMG Node",
10
11
  TEXT = "TEXT Node",
11
12
  POSITION = "POSITION Node"
@@ -41,6 +41,7 @@ var NodeType = /* @__PURE__ */ ((NodeType2) => {
41
41
  NodeType2["CONTAINER"] = "CONTAINER Node";
42
42
  NodeType2["FORM_ITEM"] = "FORM_ITEM Node";
43
43
  NodeType2["BUTTON"] = "BUTTON Node";
44
+ NodeType2["A"] = "Anchor Node";
44
45
  NodeType2["IMG"] = "IMG Node";
45
46
  NodeType2["TEXT"] = "TEXT Node";
46
47
  NodeType2["POSITION"] = "POSITION Node";
@@ -41,6 +41,9 @@ function isFormElement(node) {
41
41
  function isButtonElement(node) {
42
42
  return node instanceof HTMLElement && node.tagName.toLowerCase() === "button";
43
43
  }
44
+ function isAElement(node) {
45
+ return node instanceof HTMLElement && node.tagName.toLowerCase() === "a";
46
+ }
44
47
  function isImgElement(node) {
45
48
  if (!includeBaseElement(node) && node instanceof Element) {
46
49
  const computedStyle = window.getComputedStyle(node);
@@ -91,7 +94,8 @@ function includeBaseElement(node) {
91
94
  "textarea",
92
95
  "select",
93
96
  "option",
94
- "img"
97
+ "img",
98
+ "a"
95
99
  ];
96
100
  for (const tagName of includeList) {
97
101
  const element = node.querySelectorAll(tagName);
@@ -473,10 +477,20 @@ function collectElementInfo(node, currentWindow, currentDocument, baseZoom = 1,
473
477
  return elementInfo;
474
478
  }
475
479
  if (isButtonElement(node)) {
480
+ const rect2 = mergeElementAndChildrenRects(
481
+ node,
482
+ currentWindow,
483
+ currentDocument,
484
+ baseZoom,
485
+ visibleOnly
486
+ );
487
+ if (!rect2) {
488
+ return null;
489
+ }
476
490
  const attributes = getNodeAttributes(node, currentWindow);
477
491
  const pseudo = getPseudoElementContent(node, currentWindow);
478
492
  const content = node.innerText || pseudo.before || pseudo.after || "";
479
- const nodeHashId = midsceneGenerateHash(node, content, rect);
493
+ const nodeHashId = midsceneGenerateHash(node, content, rect2);
480
494
  const selector = setDataForNode(node, nodeHashId, false, currentWindow);
481
495
  const elementInfo = {
482
496
  id: nodeHashId,
@@ -490,12 +504,12 @@ function collectElementInfo(node, currentWindow, currentDocument, baseZoom = 1,
490
504
  nodeType: "BUTTON Node" /* BUTTON */
491
505
  },
492
506
  content,
493
- rect,
507
+ rect: rect2,
494
508
  center: [
495
- Math.round(rect.left + rect.width / 2),
496
- Math.round(rect.top + rect.height / 2)
509
+ Math.round(rect2.left + rect2.width / 2),
510
+ Math.round(rect2.top + rect2.height / 2)
497
511
  ],
498
- zoom: rect.zoom
512
+ zoom: rect2.zoom
499
513
  };
500
514
  return elementInfo;
501
515
  }
@@ -560,6 +574,33 @@ function collectElementInfo(node, currentWindow, currentDocument, baseZoom = 1,
560
574
  };
561
575
  return elementInfo;
562
576
  }
577
+ if (isAElement(node)) {
578
+ const attributes = getNodeAttributes(node, currentWindow);
579
+ const pseudo = getPseudoElementContent(node, currentWindow);
580
+ const content = node.innerText || pseudo.before || pseudo.after || "";
581
+ const nodeHashId = midsceneGenerateHash(node, content, rect);
582
+ const selector = setDataForNode(node, nodeHashId, false, currentWindow);
583
+ const elementInfo = {
584
+ id: nodeHashId,
585
+ indexId: indexId++,
586
+ nodeHashId,
587
+ nodeType: "Anchor Node" /* A */,
588
+ locator: selector,
589
+ attributes: {
590
+ ...attributes,
591
+ htmlTagName: tagNameOfNode(node),
592
+ nodeType: "Anchor Node" /* A */
593
+ },
594
+ content,
595
+ rect,
596
+ center: [
597
+ Math.round(rect.left + rect.width / 2),
598
+ Math.round(rect.top + rect.height / 2)
599
+ ],
600
+ zoom: rect.zoom
601
+ };
602
+ return elementInfo;
603
+ }
563
604
  if (isContainerElement(node)) {
564
605
  const attributes = getNodeAttributes(node, currentWindow);
565
606
  const nodeHashId = midsceneGenerateHash(node, "", rect);
@@ -630,7 +671,7 @@ function extractTreeNode(initNode, debugMode2 = false) {
630
671
  node: elementInfo,
631
672
  children: []
632
673
  };
633
- if (elementInfo?.nodeType === "BUTTON Node" /* BUTTON */ || elementInfo?.nodeType === "IMG Node" /* IMG */ || elementInfo?.nodeType === "TEXT Node" /* TEXT */ || elementInfo?.nodeType === "FORM_ITEM Node" /* FORM_ITEM */ || elementInfo?.nodeType === "CONTAINER Node" /* CONTAINER */) {
674
+ if (elementInfo?.nodeType === "BUTTON Node" /* BUTTON */ || elementInfo?.nodeType === "IMG Node" /* IMG */ || elementInfo?.nodeType === "TEXT Node" /* TEXT */ || elementInfo?.nodeType === "Anchor Node" /* A */ || elementInfo?.nodeType === "FORM_ITEM Node" /* FORM_ITEM */ || elementInfo?.nodeType === "CONTAINER Node" /* CONTAINER */) {
634
675
  return nodeInfo;
635
676
  }
636
677
  const rect = getRect(node, baseZoom, currentWindow);
@@ -685,6 +726,50 @@ function extractTreeNode(initNode, debugMode2 = false) {
685
726
  children: topChildren
686
727
  };
687
728
  }
729
+ function mergeElementAndChildrenRects(node, currentWindow, currentDocument, baseZoom = 1, visibleOnly = true) {
730
+ const selfRect = elementRect(
731
+ node,
732
+ currentWindow,
733
+ currentDocument,
734
+ baseZoom,
735
+ visibleOnly
736
+ );
737
+ if (!selfRect)
738
+ return null;
739
+ let minLeft = selfRect.left;
740
+ let minTop = selfRect.top;
741
+ let maxRight = selfRect.left + selfRect.width;
742
+ let maxBottom = selfRect.top + selfRect.height;
743
+ function traverse(child) {
744
+ for (let i = 0; i < child.childNodes.length; i++) {
745
+ const sub = child.childNodes[i];
746
+ if (sub.nodeType === 1) {
747
+ const rect = elementRect(
748
+ sub,
749
+ currentWindow,
750
+ currentDocument,
751
+ baseZoom,
752
+ visibleOnly
753
+ );
754
+ if (rect) {
755
+ minLeft = Math.min(minLeft, rect.left);
756
+ minTop = Math.min(minTop, rect.top);
757
+ maxRight = Math.max(maxRight, rect.left + rect.width);
758
+ maxBottom = Math.max(maxBottom, rect.top + rect.height);
759
+ }
760
+ traverse(sub);
761
+ }
762
+ }
763
+ }
764
+ traverse(node);
765
+ return {
766
+ ...selfRect,
767
+ left: minLeft,
768
+ top: minTop,
769
+ width: maxRight - minLeft,
770
+ height: maxBottom - minTop
771
+ };
772
+ }
688
773
 
689
774
  // src/extractor/debug.ts
690
775
  console.log(extractTextWithPosition(document.body, true));