webmarker-js 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -32,7 +32,7 @@ You can use this information to build your prompt for the vision-language model.
32
32
  Example prompt:
33
33
 
34
34
  ```javascript
35
- let markedElements = await mark();
35
+ let markedElements = mark();
36
36
 
37
37
  let prompt = `The following is a screenshot of a web page.
38
38
 
@@ -148,7 +148,7 @@ Only mark elements that are visible in the current viewport.
148
148
  ### Advanced example
149
149
 
150
150
  ```typescript
151
- const markedElements = await mark({
151
+ const markedElements = mark({
152
152
  // Only mark buttons and inputs
153
153
  selector: "button, input",
154
154
  // Use test id attribute for marker labels
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  type Placement = "top" | "top-start" | "top-end" | "right" | "right-start" | "right-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end";
2
- type StyleFunction = (element: Element) => Partial<CSSStyleDeclaration>;
2
+ type StyleFunction = (element: Element, index: number) => Partial<CSSStyleDeclaration>;
3
3
  type StyleObject = Partial<CSSStyleDeclaration>;
4
4
  interface MarkOptions {
5
5
  /**
@@ -51,13 +51,21 @@ interface MarkOptions {
51
51
  * @default false
52
52
  */
53
53
  viewPortOnly?: boolean;
54
+ /**
55
+ * Additional class to apply to the mark elements.
56
+ */
57
+ markClass?: string;
58
+ /**
59
+ * Additional class to apply to the bounding box elements.
60
+ */
61
+ boundingBoxClass?: string;
54
62
  }
55
63
  interface MarkedElement {
56
64
  element: Element;
57
65
  markElement: HTMLElement;
58
66
  boundingBoxElement?: HTMLElement;
59
67
  }
60
- declare function mark(options?: MarkOptions): Promise<Record<string, MarkedElement>>;
68
+ declare function mark(options?: MarkOptions): Record<string, MarkedElement>;
61
69
  declare function unmark(): void;
62
70
  declare function isMarked(): boolean;
63
71
  export { mark, unmark, isMarked };
package/dist/main.js CHANGED
@@ -928,8 +928,8 @@ var WebMarker = (() => {
928
928
 
929
929
  // src/index.ts
930
930
  var cleanupFns = [];
931
- function mark() {
932
- return __async(this, arguments, function* (options = {}) {
931
+ function mark(options = {}) {
932
+ try {
933
933
  const {
934
934
  selector = "button, input, a, select, textarea",
935
935
  getLabel = (_, index) => index.toString(),
@@ -948,34 +948,57 @@ var WebMarker = (() => {
948
948
  },
949
949
  showBoundingBoxes = true,
950
950
  containerElement = document.body,
951
- viewPortOnly = false
951
+ viewPortOnly = false,
952
+ markClass = "",
953
+ boundingBoxClass = ""
952
954
  } = options;
955
+ const isInViewport = (el) => {
956
+ const rect = el.getBoundingClientRect();
957
+ return rect.top < window.innerHeight && rect.bottom >= 0;
958
+ };
953
959
  const elements = Array.from(
954
960
  containerElement.querySelectorAll(selector)
955
- ).filter(
956
- (el) => !viewPortOnly || el.getBoundingClientRect().top < window.innerHeight
957
- );
961
+ ).filter((el) => !viewPortOnly || isInViewport(el));
958
962
  const markedElements = {};
959
- yield Promise.all(
960
- elements.map((element, index) => __async(this, null, function* () {
961
- const label = getLabel(element, index);
962
- const markElement = createMark(element, markStyle, label, markPlacement);
963
- const boundingBoxElement = showBoundingBoxes ? createBoundingBox(element, boundingBoxStyle, label) : void 0;
964
- markedElements[label] = { element, markElement, boundingBoxElement };
965
- element.setAttribute(markAttribute, label);
966
- }))
967
- );
963
+ const fragment = document.createDocumentFragment();
964
+ elements.forEach((element, index) => {
965
+ const label = getLabel(element, index);
966
+ const markElement = createMark(
967
+ element,
968
+ index,
969
+ markStyle,
970
+ label,
971
+ markPlacement,
972
+ markClass
973
+ );
974
+ fragment.appendChild(markElement);
975
+ const boundingBoxElement = showBoundingBoxes ? createBoundingBox(element, boundingBoxStyle, label, boundingBoxClass) : void 0;
976
+ if (boundingBoxElement) {
977
+ fragment.appendChild(boundingBoxElement);
978
+ }
979
+ markedElements[label] = { element, markElement, boundingBoxElement };
980
+ element.setAttribute(markAttribute, label);
981
+ });
982
+ document.body.appendChild(fragment);
968
983
  document.documentElement.setAttribute("data-marked", "true");
969
984
  return markedElements;
970
- });
985
+ } catch (error) {
986
+ console.error("Error in mark function:", error);
987
+ throw error;
988
+ }
971
989
  }
972
- function createMark(element, style, label, markPlacement = "top-start") {
990
+ function createMark(element, index, style, label, markPlacement = "top-start", markClass) {
973
991
  const markElement = document.createElement("div");
974
- markElement.className = "webmarker";
992
+ markElement.className = `webmarker ${markClass}`.trim();
975
993
  markElement.id = `webmarker-${label}`;
976
994
  markElement.textContent = label;
977
- document.body.appendChild(markElement);
978
- positionMark(markElement, element, markPlacement);
995
+ markElement.setAttribute("aria-hidden", "true");
996
+ positionElement(markElement, element, markPlacement, (x, y) => {
997
+ Object.assign(markElement.style, {
998
+ left: `${x}px`,
999
+ top: `${y}px`
1000
+ });
1001
+ });
979
1002
  applyStyle(
980
1003
  markElement,
981
1004
  {
@@ -983,16 +1006,24 @@ var WebMarker = (() => {
983
1006
  position: "absolute",
984
1007
  pointerEvents: "none"
985
1008
  },
986
- typeof style === "function" ? style(element) : style
1009
+ typeof style === "function" ? style(element, index) : style
987
1010
  );
988
1011
  return markElement;
989
1012
  }
990
- function createBoundingBox(element, style, label) {
1013
+ function createBoundingBox(element, style, label, boundingBoxClass) {
991
1014
  const boundingBoxElement = document.createElement("div");
992
- boundingBoxElement.className = "webmarker-bounding-box";
1015
+ boundingBoxElement.className = `webmarker-bounding-box ${boundingBoxClass}`.trim();
993
1016
  boundingBoxElement.id = `webmarker-bounding-box-${label}`;
994
- document.body.appendChild(boundingBoxElement);
995
- positionBoundingBox(boundingBoxElement, element);
1017
+ boundingBoxElement.setAttribute("aria-hidden", "true");
1018
+ positionElement(boundingBoxElement, element, "top-start", (x, y) => {
1019
+ const { width, height } = element.getBoundingClientRect();
1020
+ Object.assign(boundingBoxElement.style, {
1021
+ left: `${x}px`,
1022
+ top: `${y}px`,
1023
+ width: `${width}px`,
1024
+ height: `${height}px`
1025
+ });
1026
+ });
996
1027
  applyStyle(
997
1028
  boundingBoxElement,
998
1029
  {
@@ -1000,47 +1031,27 @@ var WebMarker = (() => {
1000
1031
  position: "absolute",
1001
1032
  pointerEvents: "none"
1002
1033
  },
1003
- typeof style === "function" ? style(element) : style
1034
+ typeof style === "function" ? style(element, parseInt(label)) : style
1004
1035
  );
1005
1036
  return boundingBoxElement;
1006
1037
  }
1007
- function positionMark(markElement, element, markPlacement) {
1038
+ function positionElement(target, anchor, placement, updateCallback) {
1008
1039
  function updatePosition() {
1009
- return __async(this, null, function* () {
1010
- const { x, y } = yield computePosition2(element, markElement, {
1011
- placement: markPlacement
1012
- });
1013
- Object.assign(markElement.style, {
1014
- left: `${x}px`,
1015
- top: `${y}px`
1016
- });
1040
+ computePosition2(anchor, target, { placement }).then(({ x, y }) => {
1041
+ updateCallback(x, y);
1017
1042
  });
1018
1043
  }
1019
- cleanupFns.push(autoUpdate(element, markElement, updatePosition));
1020
- }
1021
- function positionBoundingBox(boundingBox, element) {
1022
- return __async(this, null, function* () {
1023
- const { width, height } = element.getBoundingClientRect();
1024
- function updatePosition() {
1025
- return __async(this, null, function* () {
1026
- const { x, y } = yield computePosition2(element, boundingBox, {
1027
- placement: "top-start"
1028
- });
1029
- Object.assign(boundingBox.style, {
1030
- left: `${x}px`,
1031
- top: `${y + height}px`,
1032
- width: `${width}px`,
1033
- height: `${height}px`
1034
- });
1035
- });
1036
- }
1037
- cleanupFns.push(autoUpdate(element, boundingBox, updatePosition));
1038
- });
1044
+ cleanupFns.push(autoUpdate(anchor, target, updatePosition));
1039
1045
  }
1040
1046
  function applyStyle(element, defaultStyle, customStyle) {
1041
1047
  Object.assign(element.style, defaultStyle, customStyle);
1042
1048
  }
1043
1049
  function unmark() {
1050
+ var _a;
1051
+ const markAttribute = ((_a = document.querySelector("[data-mark-label]")) == null ? void 0 : _a.getAttribute("data-mark-label")) ? "data-mark-label" : "data-webmarker-label";
1052
+ document.querySelectorAll(`[${markAttribute}]`).forEach((el) => {
1053
+ el.removeAttribute(markAttribute);
1054
+ });
1044
1055
  document.querySelectorAll(".webmarker, .webmarker-bounding-box").forEach((el) => el.remove());
1045
1056
  document.documentElement.removeAttribute("data-marked");
1046
1057
  cleanupFns.forEach((fn) => fn());
package/dist/module.js CHANGED
@@ -904,8 +904,8 @@ var computePosition2 = (reference, floating, options) => {
904
904
 
905
905
  // src/index.ts
906
906
  var cleanupFns = [];
907
- function mark() {
908
- return __async(this, arguments, function* (options = {}) {
907
+ function mark(options = {}) {
908
+ try {
909
909
  const {
910
910
  selector = "button, input, a, select, textarea",
911
911
  getLabel = (_, index) => index.toString(),
@@ -924,34 +924,57 @@ function mark() {
924
924
  },
925
925
  showBoundingBoxes = true,
926
926
  containerElement = document.body,
927
- viewPortOnly = false
927
+ viewPortOnly = false,
928
+ markClass = "",
929
+ boundingBoxClass = ""
928
930
  } = options;
931
+ const isInViewport = (el) => {
932
+ const rect = el.getBoundingClientRect();
933
+ return rect.top < window.innerHeight && rect.bottom >= 0;
934
+ };
929
935
  const elements = Array.from(
930
936
  containerElement.querySelectorAll(selector)
931
- ).filter(
932
- (el) => !viewPortOnly || el.getBoundingClientRect().top < window.innerHeight
933
- );
937
+ ).filter((el) => !viewPortOnly || isInViewport(el));
934
938
  const markedElements = {};
935
- yield Promise.all(
936
- elements.map((element, index) => __async(this, null, function* () {
937
- const label = getLabel(element, index);
938
- const markElement = createMark(element, markStyle, label, markPlacement);
939
- const boundingBoxElement = showBoundingBoxes ? createBoundingBox(element, boundingBoxStyle, label) : void 0;
940
- markedElements[label] = { element, markElement, boundingBoxElement };
941
- element.setAttribute(markAttribute, label);
942
- }))
943
- );
939
+ const fragment = document.createDocumentFragment();
940
+ elements.forEach((element, index) => {
941
+ const label = getLabel(element, index);
942
+ const markElement = createMark(
943
+ element,
944
+ index,
945
+ markStyle,
946
+ label,
947
+ markPlacement,
948
+ markClass
949
+ );
950
+ fragment.appendChild(markElement);
951
+ const boundingBoxElement = showBoundingBoxes ? createBoundingBox(element, boundingBoxStyle, label, boundingBoxClass) : void 0;
952
+ if (boundingBoxElement) {
953
+ fragment.appendChild(boundingBoxElement);
954
+ }
955
+ markedElements[label] = { element, markElement, boundingBoxElement };
956
+ element.setAttribute(markAttribute, label);
957
+ });
958
+ document.body.appendChild(fragment);
944
959
  document.documentElement.setAttribute("data-marked", "true");
945
960
  return markedElements;
946
- });
961
+ } catch (error) {
962
+ console.error("Error in mark function:", error);
963
+ throw error;
964
+ }
947
965
  }
948
- function createMark(element, style, label, markPlacement = "top-start") {
966
+ function createMark(element, index, style, label, markPlacement = "top-start", markClass) {
949
967
  const markElement = document.createElement("div");
950
- markElement.className = "webmarker";
968
+ markElement.className = `webmarker ${markClass}`.trim();
951
969
  markElement.id = `webmarker-${label}`;
952
970
  markElement.textContent = label;
953
- document.body.appendChild(markElement);
954
- positionMark(markElement, element, markPlacement);
971
+ markElement.setAttribute("aria-hidden", "true");
972
+ positionElement(markElement, element, markPlacement, (x, y) => {
973
+ Object.assign(markElement.style, {
974
+ left: `${x}px`,
975
+ top: `${y}px`
976
+ });
977
+ });
955
978
  applyStyle(
956
979
  markElement,
957
980
  {
@@ -959,16 +982,24 @@ function createMark(element, style, label, markPlacement = "top-start") {
959
982
  position: "absolute",
960
983
  pointerEvents: "none"
961
984
  },
962
- typeof style === "function" ? style(element) : style
985
+ typeof style === "function" ? style(element, index) : style
963
986
  );
964
987
  return markElement;
965
988
  }
966
- function createBoundingBox(element, style, label) {
989
+ function createBoundingBox(element, style, label, boundingBoxClass) {
967
990
  const boundingBoxElement = document.createElement("div");
968
- boundingBoxElement.className = "webmarker-bounding-box";
991
+ boundingBoxElement.className = `webmarker-bounding-box ${boundingBoxClass}`.trim();
969
992
  boundingBoxElement.id = `webmarker-bounding-box-${label}`;
970
- document.body.appendChild(boundingBoxElement);
971
- positionBoundingBox(boundingBoxElement, element);
993
+ boundingBoxElement.setAttribute("aria-hidden", "true");
994
+ positionElement(boundingBoxElement, element, "top-start", (x, y) => {
995
+ const { width, height } = element.getBoundingClientRect();
996
+ Object.assign(boundingBoxElement.style, {
997
+ left: `${x}px`,
998
+ top: `${y}px`,
999
+ width: `${width}px`,
1000
+ height: `${height}px`
1001
+ });
1002
+ });
972
1003
  applyStyle(
973
1004
  boundingBoxElement,
974
1005
  {
@@ -976,47 +1007,27 @@ function createBoundingBox(element, style, label) {
976
1007
  position: "absolute",
977
1008
  pointerEvents: "none"
978
1009
  },
979
- typeof style === "function" ? style(element) : style
1010
+ typeof style === "function" ? style(element, parseInt(label)) : style
980
1011
  );
981
1012
  return boundingBoxElement;
982
1013
  }
983
- function positionMark(markElement, element, markPlacement) {
1014
+ function positionElement(target, anchor, placement, updateCallback) {
984
1015
  function updatePosition() {
985
- return __async(this, null, function* () {
986
- const { x, y } = yield computePosition2(element, markElement, {
987
- placement: markPlacement
988
- });
989
- Object.assign(markElement.style, {
990
- left: `${x}px`,
991
- top: `${y}px`
992
- });
1016
+ computePosition2(anchor, target, { placement }).then(({ x, y }) => {
1017
+ updateCallback(x, y);
993
1018
  });
994
1019
  }
995
- cleanupFns.push(autoUpdate(element, markElement, updatePosition));
996
- }
997
- function positionBoundingBox(boundingBox, element) {
998
- return __async(this, null, function* () {
999
- const { width, height } = element.getBoundingClientRect();
1000
- function updatePosition() {
1001
- return __async(this, null, function* () {
1002
- const { x, y } = yield computePosition2(element, boundingBox, {
1003
- placement: "top-start"
1004
- });
1005
- Object.assign(boundingBox.style, {
1006
- left: `${x}px`,
1007
- top: `${y + height}px`,
1008
- width: `${width}px`,
1009
- height: `${height}px`
1010
- });
1011
- });
1012
- }
1013
- cleanupFns.push(autoUpdate(element, boundingBox, updatePosition));
1014
- });
1020
+ cleanupFns.push(autoUpdate(anchor, target, updatePosition));
1015
1021
  }
1016
1022
  function applyStyle(element, defaultStyle, customStyle) {
1017
1023
  Object.assign(element.style, defaultStyle, customStyle);
1018
1024
  }
1019
1025
  function unmark() {
1026
+ var _a;
1027
+ const markAttribute = ((_a = document.querySelector("[data-mark-label]")) == null ? void 0 : _a.getAttribute("data-mark-label")) ? "data-mark-label" : "data-webmarker-label";
1028
+ document.querySelectorAll(`[${markAttribute}]`).forEach((el) => {
1029
+ el.removeAttribute(markAttribute);
1030
+ });
1020
1031
  document.querySelectorAll(".webmarker, .webmarker-bounding-box").forEach((el) => el.remove());
1021
1032
  document.documentElement.removeAttribute("data-marked");
1022
1033
  cleanupFns.forEach((fn) => fn());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webmarker-js",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "A library for marking web pages for Set-of-Mark prompting with vision-language models.",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/main.js",
@@ -26,6 +26,7 @@
26
26
  "@mdx-js/react": "^3.0.1",
27
27
  "@next/mdx": "^14.2.3",
28
28
  "@playwright/test": "^1.46.1",
29
+ "@types/jest": "^29.5.13",
29
30
  "@types/mdx": "^2.0.13",
30
31
  "@types/node": "^22.5.1",
31
32
  "autoprefixer": "^10.4.19",
package/src/index.ts CHANGED
@@ -14,7 +14,10 @@ type Placement =
14
14
  | "left-start"
15
15
  | "left-end";
16
16
 
17
- type StyleFunction = (element: Element) => Partial<CSSStyleDeclaration>;
17
+ type StyleFunction = (
18
+ element: Element,
19
+ index: number
20
+ ) => Partial<CSSStyleDeclaration>;
18
21
  type StyleObject = Partial<CSSStyleDeclaration>;
19
22
 
20
23
  interface MarkOptions {
@@ -67,6 +70,14 @@ interface MarkOptions {
67
70
  * @default false
68
71
  */
69
72
  viewPortOnly?: boolean;
73
+ /**
74
+ * Additional class to apply to the mark elements.
75
+ */
76
+ markClass?: string;
77
+ /**
78
+ * Additional class to apply to the bounding box elements.
79
+ */
80
+ boundingBoxClass?: string;
70
81
  }
71
82
 
72
83
  interface MarkedElement {
@@ -77,68 +88,94 @@ interface MarkedElement {
77
88
 
78
89
  let cleanupFns: (() => void)[] = [];
79
90
 
80
- async function mark(
81
- options: MarkOptions = {}
82
- ): Promise<Record<string, MarkedElement>> {
83
- const {
84
- selector = "button, input, a, select, textarea",
85
- getLabel = (_, index) => index.toString(),
86
- markAttribute = "data-mark-label",
87
- markPlacement = "top-start",
88
- markStyle = {
89
- backgroundColor: "red",
90
- color: "white",
91
- padding: "2px 4px",
92
- fontSize: "12px",
93
- fontWeight: "bold",
94
- },
95
- boundingBoxStyle = {
96
- outline: "2px dashed red",
97
- backgroundColor: "transparent",
98
- },
99
- showBoundingBoxes = true,
100
- containerElement = document.body,
101
- viewPortOnly = false,
102
- } = options;
103
-
104
- const elements = Array.from(
105
- containerElement.querySelectorAll(selector)
106
- ).filter(
107
- (el) => !viewPortOnly || el.getBoundingClientRect().top < window.innerHeight
108
- );
91
+ function mark(options: MarkOptions = {}): Record<string, MarkedElement> {
92
+ try {
93
+ const {
94
+ selector = "button, input, a, select, textarea",
95
+ getLabel = (_, index) => index.toString(),
96
+ markAttribute = "data-mark-label",
97
+ markPlacement = "top-start",
98
+ markStyle = {
99
+ backgroundColor: "red",
100
+ color: "white",
101
+ padding: "2px 4px",
102
+ fontSize: "12px",
103
+ fontWeight: "bold",
104
+ },
105
+ boundingBoxStyle = {
106
+ outline: "2px dashed red",
107
+ backgroundColor: "transparent",
108
+ },
109
+ showBoundingBoxes = true,
110
+ containerElement = document.body,
111
+ viewPortOnly = false,
112
+ markClass = "",
113
+ boundingBoxClass = "",
114
+ } = options;
115
+
116
+ const isInViewport = (el: Element) => {
117
+ const rect = el.getBoundingClientRect();
118
+ return rect.top < window.innerHeight && rect.bottom >= 0;
119
+ };
120
+
121
+ const elements = Array.from(
122
+ containerElement.querySelectorAll(selector)
123
+ ).filter((el) => !viewPortOnly || isInViewport(el));
109
124
 
110
- const markedElements: Record<string, MarkedElement> = {};
125
+ const markedElements: Record<string, MarkedElement> = {};
126
+ const fragment = document.createDocumentFragment();
111
127
 
112
- await Promise.all(
113
- elements.map(async (element, index) => {
128
+ elements.forEach((element, index) => {
114
129
  const label = getLabel(element, index);
115
- const markElement = createMark(element, markStyle, label, markPlacement);
130
+ const markElement = createMark(
131
+ element,
132
+ index,
133
+ markStyle,
134
+ label,
135
+ markPlacement,
136
+ markClass
137
+ );
138
+ fragment.appendChild(markElement);
116
139
 
117
140
  const boundingBoxElement = showBoundingBoxes
118
- ? createBoundingBox(element, boundingBoxStyle, label)
141
+ ? createBoundingBox(element, boundingBoxStyle, label, boundingBoxClass)
119
142
  : undefined;
143
+ if (boundingBoxElement) {
144
+ fragment.appendChild(boundingBoxElement);
145
+ }
120
146
 
121
147
  markedElements[label] = { element, markElement, boundingBoxElement };
122
148
  element.setAttribute(markAttribute, label);
123
- })
124
- );
149
+ });
125
150
 
126
- document.documentElement.setAttribute("data-marked", "true");
127
- return markedElements;
151
+ document.body.appendChild(fragment);
152
+ document.documentElement.setAttribute("data-marked", "true");
153
+ return markedElements;
154
+ } catch (error) {
155
+ console.error("Error in mark function:", error);
156
+ throw error;
157
+ }
128
158
  }
129
159
 
130
160
  function createMark(
131
161
  element: Element,
162
+ index: number,
132
163
  style: StyleObject | StyleFunction,
133
164
  label: string,
134
- markPlacement: Placement = "top-start"
165
+ markPlacement: Placement = "top-start",
166
+ markClass: string
135
167
  ): HTMLElement {
136
168
  const markElement = document.createElement("div");
137
- markElement.className = "webmarker";
169
+ markElement.className = `webmarker ${markClass}`.trim();
138
170
  markElement.id = `webmarker-${label}`;
139
171
  markElement.textContent = label;
140
- document.body.appendChild(markElement);
141
- positionMark(markElement, element, markPlacement);
172
+ markElement.setAttribute("aria-hidden", "true");
173
+ positionElement(markElement, element, markPlacement, (x, y) => {
174
+ Object.assign(markElement.style, {
175
+ left: `${x}px`,
176
+ top: `${y}px`,
177
+ });
178
+ });
142
179
  applyStyle(
143
180
  markElement,
144
181
  {
@@ -146,7 +183,7 @@ function createMark(
146
183
  position: "absolute",
147
184
  pointerEvents: "none",
148
185
  },
149
- typeof style === "function" ? style(element) : style
186
+ typeof style === "function" ? style(element, index) : style
150
187
  );
151
188
  return markElement;
152
189
  }
@@ -154,13 +191,23 @@ function createMark(
154
191
  function createBoundingBox(
155
192
  element: Element,
156
193
  style: StyleObject | StyleFunction,
157
- label: string
194
+ label: string,
195
+ boundingBoxClass: string
158
196
  ): HTMLElement {
159
197
  const boundingBoxElement = document.createElement("div");
160
- boundingBoxElement.className = "webmarker-bounding-box";
198
+ boundingBoxElement.className =
199
+ `webmarker-bounding-box ${boundingBoxClass}`.trim();
161
200
  boundingBoxElement.id = `webmarker-bounding-box-${label}`;
162
- document.body.appendChild(boundingBoxElement);
163
- positionBoundingBox(boundingBoxElement, element);
201
+ boundingBoxElement.setAttribute("aria-hidden", "true");
202
+ positionElement(boundingBoxElement, element, "top-start", (x, y) => {
203
+ const { width, height } = element.getBoundingClientRect();
204
+ Object.assign(boundingBoxElement.style, {
205
+ left: `${x}px`,
206
+ top: `${y}px`,
207
+ width: `${width}px`,
208
+ height: `${height}px`,
209
+ });
210
+ });
164
211
  applyStyle(
165
212
  boundingBoxElement,
166
213
  {
@@ -168,43 +215,23 @@ function createBoundingBox(
168
215
  position: "absolute",
169
216
  pointerEvents: "none",
170
217
  },
171
- typeof style === "function" ? style(element) : style
218
+ typeof style === "function" ? style(element, parseInt(label)) : style
172
219
  );
173
220
  return boundingBoxElement;
174
221
  }
175
222
 
176
- function positionMark(
177
- markElement: HTMLElement,
178
- element: Element,
179
- markPlacement: Placement
223
+ function positionElement(
224
+ target: HTMLElement,
225
+ anchor: Element,
226
+ placement: Placement,
227
+ updateCallback: (x: number, y: number) => void
180
228
  ) {
181
- async function updatePosition() {
182
- const { x, y } = await computePosition(element, markElement, {
183
- placement: markPlacement,
184
- });
185
- Object.assign(markElement.style, {
186
- left: `${x}px`,
187
- top: `${y}px`,
229
+ function updatePosition() {
230
+ computePosition(anchor, target, { placement }).then(({ x, y }) => {
231
+ updateCallback(x, y);
188
232
  });
189
233
  }
190
-
191
- cleanupFns.push(autoUpdate(element, markElement, updatePosition));
192
- }
193
-
194
- async function positionBoundingBox(boundingBox: HTMLElement, element: Element) {
195
- const { width, height } = element.getBoundingClientRect();
196
- async function updatePosition() {
197
- const { x, y } = await computePosition(element, boundingBox, {
198
- placement: "top-start",
199
- });
200
- Object.assign(boundingBox.style, {
201
- left: `${x}px`,
202
- top: `${y + height}px`,
203
- width: `${width}px`,
204
- height: `${height}px`,
205
- });
206
- }
207
- cleanupFns.push(autoUpdate(element, boundingBox, updatePosition));
234
+ cleanupFns.push(autoUpdate(anchor, target, updatePosition));
208
235
  }
209
236
 
210
237
  function applyStyle(
@@ -216,6 +243,15 @@ function applyStyle(
216
243
  }
217
244
 
218
245
  function unmark(): void {
246
+ const markAttribute = document
247
+ .querySelector("[data-mark-label]")
248
+ ?.getAttribute("data-mark-label")
249
+ ? "data-mark-label"
250
+ : "data-webmarker-label";
251
+
252
+ document.querySelectorAll(`[${markAttribute}]`).forEach((el) => {
253
+ el.removeAttribute(markAttribute);
254
+ });
219
255
  document
220
256
  .querySelectorAll(".webmarker, .webmarker-bounding-box")
221
257
  .forEach((el) => el.remove());