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 +2 -2
- package/dist/index.d.ts +10 -2
- package/dist/main.js +66 -55
- package/dist/module.js +66 -55
- package/package.json +2 -1
- package/src/index.ts +114 -78
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 =
|
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 =
|
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):
|
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
|
-
|
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
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
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 =
|
992
|
+
markElement.className = `webmarker ${markClass}`.trim();
|
975
993
|
markElement.id = `webmarker-${label}`;
|
976
994
|
markElement.textContent = label;
|
977
|
-
|
978
|
-
|
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 =
|
1015
|
+
boundingBoxElement.className = `webmarker-bounding-box ${boundingBoxClass}`.trim();
|
993
1016
|
boundingBoxElement.id = `webmarker-bounding-box-${label}`;
|
994
|
-
|
995
|
-
|
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
|
1038
|
+
function positionElement(target, anchor, placement, updateCallback) {
|
1008
1039
|
function updatePosition() {
|
1009
|
-
|
1010
|
-
|
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(
|
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
|
-
|
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
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
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 =
|
968
|
+
markElement.className = `webmarker ${markClass}`.trim();
|
951
969
|
markElement.id = `webmarker-${label}`;
|
952
970
|
markElement.textContent = label;
|
953
|
-
|
954
|
-
|
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 =
|
991
|
+
boundingBoxElement.className = `webmarker-bounding-box ${boundingBoxClass}`.trim();
|
969
992
|
boundingBoxElement.id = `webmarker-bounding-box-${label}`;
|
970
|
-
|
971
|
-
|
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
|
1014
|
+
function positionElement(target, anchor, placement, updateCallback) {
|
984
1015
|
function updatePosition() {
|
985
|
-
|
986
|
-
|
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(
|
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
|
+
"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 = (
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
125
|
+
const markedElements: Record<string, MarkedElement> = {};
|
126
|
+
const fragment = document.createDocumentFragment();
|
111
127
|
|
112
|
-
|
113
|
-
elements.map(async (element, index) => {
|
128
|
+
elements.forEach((element, index) => {
|
114
129
|
const label = getLabel(element, index);
|
115
|
-
const markElement = createMark(
|
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
|
-
|
127
|
-
|
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 =
|
169
|
+
markElement.className = `webmarker ${markClass}`.trim();
|
138
170
|
markElement.id = `webmarker-${label}`;
|
139
171
|
markElement.textContent = label;
|
140
|
-
|
141
|
-
|
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 =
|
198
|
+
boundingBoxElement.className =
|
199
|
+
`webmarker-bounding-box ${boundingBoxClass}`.trim();
|
161
200
|
boundingBoxElement.id = `webmarker-bounding-box-${label}`;
|
162
|
-
|
163
|
-
|
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
|
177
|
-
|
178
|
-
|
179
|
-
|
223
|
+
function positionElement(
|
224
|
+
target: HTMLElement,
|
225
|
+
anchor: Element,
|
226
|
+
placement: Placement,
|
227
|
+
updateCallback: (x: number, y: number) => void
|
180
228
|
) {
|
181
|
-
|
182
|
-
|
183
|
-
|
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());
|