webmarker-js 0.1.0 → 0.2.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.
- package/LICENSE +21 -0
- package/README.md +15 -13
- package/dist/index.d.ts +19 -17
- package/dist/main.js +26 -26
- package/dist/module.js +26 -26
- package/package.json +1 -1
- package/src/index.ts +51 -56
- package/test-results/.last-run.json +2 -4
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 Reid Barber
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
@@ -15,7 +15,6 @@ Mark web pages for use with vision-language models.
|
|
15
15
|
|
16
16
|

|
17
17
|
|
18
|
-
|
19
18
|
## How it works
|
20
19
|
|
21
20
|
**1. Call the `mark()` function**
|
@@ -24,7 +23,7 @@ This marks the interactive elements on the page, and returns an object containin
|
|
24
23
|
|
25
24
|
- `element`: The interactive element that was marked.
|
26
25
|
- `markElement`: The label element that was added to the page.
|
27
|
-
- `
|
26
|
+
- `boundingBoxElement`: The bounding box element that was added to the page.
|
28
27
|
|
29
28
|
You can use this information to build your prompt for the vision-language model.
|
30
29
|
|
@@ -58,7 +57,7 @@ Example response: click 0
|
|
58
57
|
|
59
58
|
In a web browser (i.e. via Playwright), interact with elements as needed.
|
60
59
|
|
61
|
-
For prompting or agent ideas, see the [WebVoyager](https://
|
60
|
+
For prompting or agent ideas, see the [WebVoyager](https://github.com/MinorJerry/WebVoyager) paper.
|
62
61
|
|
63
62
|
## Playwright example
|
64
63
|
|
@@ -71,6 +70,9 @@ await page.addScriptTag({
|
|
71
70
|
// Mark the page and get the marked elements
|
72
71
|
let markedElements = await page.evaluate(async () => await WebMarker.mark());
|
73
72
|
|
73
|
+
// Click a marked element
|
74
|
+
await page.locator('[data-mark-label="0"]').click();
|
75
|
+
|
74
76
|
// (Optional) Check if page is marked
|
75
77
|
let isMarked = await page.evaluate(async () => await WebMarker.isMarked());
|
76
78
|
|
@@ -92,7 +94,7 @@ A custom CSS selector to specify which elements to mark.
|
|
92
94
|
A custom attribute to add to the marked elements. This attribute contains the label of the mark.
|
93
95
|
|
94
96
|
- Type: `string`
|
95
|
-
- Default: `"data-mark-
|
97
|
+
- Default: `"data-mark-label"`
|
96
98
|
|
97
99
|
### markStyle
|
98
100
|
|
@@ -108,21 +110,21 @@ The placement of the mark relative to the element.
|
|
108
110
|
- Type: `'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'`
|
109
111
|
- Default: `'top-start'`
|
110
112
|
|
111
|
-
###
|
113
|
+
### boundingBoxStyle
|
112
114
|
|
113
|
-
A CSS style to apply to the bounding box element. You can also specify a function that returns a CSS style object. Bounding boxes are only shown if
|
115
|
+
A CSS style to apply to the bounding box element. You can also specify a function that returns a CSS style object. Bounding boxes are only shown if showBoundingBoxes is true.
|
114
116
|
|
115
117
|
- Type: `Readonly<Partial<CSSStyleDeclaration>> or (element: Element) => Readonly<Partial<CSSStyleDeclaration>>`
|
116
118
|
- Default: `{outline: "2px dashed red", backgroundColor: "transparent"}`
|
117
119
|
|
118
|
-
###
|
120
|
+
### showBoundingBoxes
|
119
121
|
|
120
122
|
Whether or not to show bounding boxes around the elements.
|
121
123
|
|
122
124
|
- Type: `boolean`
|
123
125
|
- Default: `true`
|
124
126
|
|
125
|
-
###
|
127
|
+
### getLabel
|
126
128
|
|
127
129
|
Provide a function for generating labels. By default, labels are generated as integers starting from 0.
|
128
130
|
|
@@ -153,15 +155,15 @@ const markedElements = await mark({
|
|
153
155
|
markAttribute: "data-test-id",
|
154
156
|
// Use a blue mark with white text
|
155
157
|
markStyle: { color: "white", backgroundColor: "blue", padding: 5 },
|
156
|
-
// Use a blue dashed outline
|
157
|
-
|
158
|
+
// Use a blue dashed outline with a transparent and slighly blue background
|
159
|
+
boundingBoxStyle: { outline: "2px dashed blue", backgroundColor: "rgba(0, 0, 255, 0.1)"},
|
158
160
|
// Place the mark at the top right corner of the element
|
159
161
|
markPlacement: "top-end";
|
160
|
-
// Show
|
161
|
-
|
162
|
+
// Show bounding boxes over elements (defaults to true)
|
163
|
+
showBoundingBoxes: true,
|
162
164
|
// Generate labels as 'Element 0', 'Element 1', 'Element 2'...
|
163
165
|
// Defaults to '0', '1', '2'... if not provided.
|
164
|
-
|
166
|
+
getLabel: (element, index) => `Element ${index}`,
|
165
167
|
// A custom container element to query the elements to be marked.
|
166
168
|
// Defaults to the document.body.
|
167
169
|
containerElement: document.body.querySelector("main"),
|
package/dist/index.d.ts
CHANGED
@@ -1,43 +1,45 @@
|
|
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) => CSSStyleDeclaration;
|
3
|
+
type StyleObject = Readonly<Partial<CSSStyleDeclaration>>;
|
2
4
|
interface MarkOptions {
|
3
5
|
/**
|
4
|
-
* A CSS selector to
|
6
|
+
* A CSS selector to query the elements to be marked.
|
5
7
|
*/
|
6
8
|
selector?: string;
|
7
9
|
/**
|
8
|
-
*
|
9
|
-
*
|
10
|
-
* @default 'data-mark-id'
|
10
|
+
* Provide a function for generating labels.
|
11
|
+
* By default, labels are generated as numbers starting from 0.
|
11
12
|
*/
|
12
|
-
|
13
|
+
getLabel?: (element: Element, index: number) => string;
|
13
14
|
/**
|
14
|
-
*
|
15
|
-
*
|
15
|
+
* Name for the attribute added to the marked elements to store the mark label.
|
16
|
+
*
|
17
|
+
* @default 'data-mark-label'
|
16
18
|
*/
|
17
|
-
|
19
|
+
markAttribute?: string;
|
18
20
|
/**
|
19
21
|
* The placement of the mark relative to the element.
|
20
22
|
*
|
21
23
|
* @default 'top-start'
|
22
24
|
*/
|
23
25
|
markPlacement?: Placement;
|
26
|
+
/**
|
27
|
+
* A CSS style to apply to the label element.
|
28
|
+
* You can also specify a function that returns a CSS style object.
|
29
|
+
*/
|
30
|
+
markStyle?: StyleObject | StyleFunction;
|
24
31
|
/**
|
25
32
|
* A CSS style to apply to the bounding box element.
|
26
33
|
* You can also specify a function that returns a CSS style object.
|
27
|
-
* Bounding boxes are only shown if `
|
34
|
+
* Bounding boxes are only shown if `showBoundingBoxes` is `true`.
|
28
35
|
*/
|
29
|
-
|
36
|
+
boundingBoxStyle?: StyleObject | StyleFunction;
|
30
37
|
/**
|
31
38
|
* Whether or not to show bounding boxes around the elements.
|
32
39
|
*
|
33
40
|
* @default true
|
34
41
|
*/
|
35
|
-
|
36
|
-
/**
|
37
|
-
* Provide a function for generating labels.
|
38
|
-
* By default, labels are generated as numbers starting from 0.
|
39
|
-
*/
|
40
|
-
labelGenerator?: (element: Element, index: number) => string;
|
42
|
+
showBoundingBoxes?: boolean;
|
41
43
|
/**
|
42
44
|
* Provide a container element to query the elements to be marked.
|
43
45
|
* By default, the container element is `document.body`.
|
@@ -53,7 +55,7 @@ interface MarkOptions {
|
|
53
55
|
interface MarkedElement {
|
54
56
|
element: Element;
|
55
57
|
markElement: HTMLElement;
|
56
|
-
|
58
|
+
boundingBoxElement?: HTMLElement;
|
57
59
|
}
|
58
60
|
declare function mark(options?: MarkOptions): Promise<Record<string, MarkedElement>>;
|
59
61
|
declare function unmark(): void;
|
package/dist/main.js
CHANGED
@@ -932,7 +932,9 @@ var WebMarker = (() => {
|
|
932
932
|
return __async(this, arguments, function* (options = {}) {
|
933
933
|
const {
|
934
934
|
selector = "button, input, a, select, textarea",
|
935
|
-
|
935
|
+
getLabel = (_, index) => index.toString(),
|
936
|
+
markAttribute = "data-mark-label",
|
937
|
+
markPlacement = "top-start",
|
936
938
|
markStyle = {
|
937
939
|
backgroundColor: "red",
|
938
940
|
color: "white",
|
@@ -940,13 +942,11 @@ var WebMarker = (() => {
|
|
940
942
|
fontSize: "12px",
|
941
943
|
fontWeight: "bold"
|
942
944
|
},
|
943
|
-
|
944
|
-
maskStyle = {
|
945
|
+
boundingBoxStyle = {
|
945
946
|
outline: "2px dashed red",
|
946
947
|
backgroundColor: "transparent"
|
947
948
|
},
|
948
|
-
|
949
|
-
labelGenerator = (_, index) => index.toString(),
|
949
|
+
showBoundingBoxes = true,
|
950
950
|
containerElement = document.body,
|
951
951
|
viewPortOnly = false
|
952
952
|
} = options;
|
@@ -958,14 +958,14 @@ var WebMarker = (() => {
|
|
958
958
|
const markedElements = {};
|
959
959
|
yield Promise.all(
|
960
960
|
elements.map((element, index) => __async(this, null, function* () {
|
961
|
-
const label =
|
961
|
+
const label = getLabel(element, index);
|
962
962
|
const markElement = createMark(element, markStyle, label, markPlacement);
|
963
|
-
const
|
964
|
-
markedElements[label] = { element, markElement,
|
963
|
+
const boundingBoxElement = showBoundingBoxes ? createBoundingBox(element, boundingBoxStyle, label) : void 0;
|
964
|
+
markedElements[label] = { element, markElement, boundingBoxElement };
|
965
965
|
element.setAttribute(markAttribute, label);
|
966
966
|
}))
|
967
967
|
);
|
968
|
-
document.documentElement.
|
968
|
+
document.documentElement.setAttribute("data-marked", "true");
|
969
969
|
return markedElements;
|
970
970
|
});
|
971
971
|
}
|
@@ -987,14 +987,14 @@ var WebMarker = (() => {
|
|
987
987
|
);
|
988
988
|
return markElement;
|
989
989
|
}
|
990
|
-
function
|
991
|
-
const
|
992
|
-
|
993
|
-
|
994
|
-
document.body.appendChild(
|
995
|
-
|
990
|
+
function createBoundingBox(element, style, label) {
|
991
|
+
const boundingBoxElement = document.createElement("div");
|
992
|
+
boundingBoxElement.className = "webmarker-bounding-box";
|
993
|
+
boundingBoxElement.id = `webmarker-bounding-box-${label}`;
|
994
|
+
document.body.appendChild(boundingBoxElement);
|
995
|
+
positionBoundingBox(boundingBoxElement, element);
|
996
996
|
applyStyle(
|
997
|
-
|
997
|
+
boundingBoxElement,
|
998
998
|
{
|
999
999
|
zIndex: "999999999",
|
1000
1000
|
position: "absolute",
|
@@ -1002,7 +1002,7 @@ var WebMarker = (() => {
|
|
1002
1002
|
},
|
1003
1003
|
typeof style === "function" ? style(element) : style
|
1004
1004
|
);
|
1005
|
-
return
|
1005
|
+
return boundingBoxElement;
|
1006
1006
|
}
|
1007
1007
|
function positionMark(markElement, element, markPlacement) {
|
1008
1008
|
function updatePosition() {
|
@@ -1018,36 +1018,36 @@ var WebMarker = (() => {
|
|
1018
1018
|
}
|
1019
1019
|
cleanupFns.push(autoUpdate(element, markElement, updatePosition));
|
1020
1020
|
}
|
1021
|
-
function
|
1021
|
+
function positionBoundingBox(boundingBox, element) {
|
1022
1022
|
return __async(this, null, function* () {
|
1023
1023
|
const { width, height } = element.getBoundingClientRect();
|
1024
1024
|
function updatePosition() {
|
1025
1025
|
return __async(this, null, function* () {
|
1026
|
-
const { x
|
1026
|
+
const { x, y } = yield computePosition2(element, boundingBox, {
|
1027
1027
|
placement: "top-start"
|
1028
1028
|
});
|
1029
|
-
Object.assign(
|
1030
|
-
left: `${
|
1031
|
-
top: `${
|
1029
|
+
Object.assign(boundingBox.style, {
|
1030
|
+
left: `${x}px`,
|
1031
|
+
top: `${y + height}px`,
|
1032
1032
|
width: `${width}px`,
|
1033
1033
|
height: `${height}px`
|
1034
1034
|
});
|
1035
1035
|
});
|
1036
1036
|
}
|
1037
|
-
cleanupFns.push(autoUpdate(element,
|
1037
|
+
cleanupFns.push(autoUpdate(element, boundingBox, updatePosition));
|
1038
1038
|
});
|
1039
1039
|
}
|
1040
1040
|
function applyStyle(element, defaultStyle, customStyle) {
|
1041
1041
|
Object.assign(element.style, defaultStyle, customStyle);
|
1042
1042
|
}
|
1043
1043
|
function unmark() {
|
1044
|
-
document.querySelectorAll(".webmarker, .webmarker-
|
1045
|
-
document.documentElement.removeAttribute("data-
|
1044
|
+
document.querySelectorAll(".webmarker, .webmarker-bounding-box").forEach((el) => el.remove());
|
1045
|
+
document.documentElement.removeAttribute("data-marked");
|
1046
1046
|
cleanupFns.forEach((fn) => fn());
|
1047
1047
|
cleanupFns = [];
|
1048
1048
|
}
|
1049
1049
|
function isMarked() {
|
1050
|
-
return document.documentElement.hasAttribute("data-
|
1050
|
+
return document.documentElement.hasAttribute("data-marked");
|
1051
1051
|
}
|
1052
1052
|
if (typeof window !== "undefined") {
|
1053
1053
|
window["mark"] = mark;
|
package/dist/module.js
CHANGED
@@ -908,7 +908,9 @@ function mark() {
|
|
908
908
|
return __async(this, arguments, function* (options = {}) {
|
909
909
|
const {
|
910
910
|
selector = "button, input, a, select, textarea",
|
911
|
-
|
911
|
+
getLabel = (_, index) => index.toString(),
|
912
|
+
markAttribute = "data-mark-label",
|
913
|
+
markPlacement = "top-start",
|
912
914
|
markStyle = {
|
913
915
|
backgroundColor: "red",
|
914
916
|
color: "white",
|
@@ -916,13 +918,11 @@ function mark() {
|
|
916
918
|
fontSize: "12px",
|
917
919
|
fontWeight: "bold"
|
918
920
|
},
|
919
|
-
|
920
|
-
maskStyle = {
|
921
|
+
boundingBoxStyle = {
|
921
922
|
outline: "2px dashed red",
|
922
923
|
backgroundColor: "transparent"
|
923
924
|
},
|
924
|
-
|
925
|
-
labelGenerator = (_, index) => index.toString(),
|
925
|
+
showBoundingBoxes = true,
|
926
926
|
containerElement = document.body,
|
927
927
|
viewPortOnly = false
|
928
928
|
} = options;
|
@@ -934,14 +934,14 @@ function mark() {
|
|
934
934
|
const markedElements = {};
|
935
935
|
yield Promise.all(
|
936
936
|
elements.map((element, index) => __async(this, null, function* () {
|
937
|
-
const label =
|
937
|
+
const label = getLabel(element, index);
|
938
938
|
const markElement = createMark(element, markStyle, label, markPlacement);
|
939
|
-
const
|
940
|
-
markedElements[label] = { element, markElement,
|
939
|
+
const boundingBoxElement = showBoundingBoxes ? createBoundingBox(element, boundingBoxStyle, label) : void 0;
|
940
|
+
markedElements[label] = { element, markElement, boundingBoxElement };
|
941
941
|
element.setAttribute(markAttribute, label);
|
942
942
|
}))
|
943
943
|
);
|
944
|
-
document.documentElement.
|
944
|
+
document.documentElement.setAttribute("data-marked", "true");
|
945
945
|
return markedElements;
|
946
946
|
});
|
947
947
|
}
|
@@ -963,14 +963,14 @@ function createMark(element, style, label, markPlacement = "top-start") {
|
|
963
963
|
);
|
964
964
|
return markElement;
|
965
965
|
}
|
966
|
-
function
|
967
|
-
const
|
968
|
-
|
969
|
-
|
970
|
-
document.body.appendChild(
|
971
|
-
|
966
|
+
function createBoundingBox(element, style, label) {
|
967
|
+
const boundingBoxElement = document.createElement("div");
|
968
|
+
boundingBoxElement.className = "webmarker-bounding-box";
|
969
|
+
boundingBoxElement.id = `webmarker-bounding-box-${label}`;
|
970
|
+
document.body.appendChild(boundingBoxElement);
|
971
|
+
positionBoundingBox(boundingBoxElement, element);
|
972
972
|
applyStyle(
|
973
|
-
|
973
|
+
boundingBoxElement,
|
974
974
|
{
|
975
975
|
zIndex: "999999999",
|
976
976
|
position: "absolute",
|
@@ -978,7 +978,7 @@ function createMask(element, style, label) {
|
|
978
978
|
},
|
979
979
|
typeof style === "function" ? style(element) : style
|
980
980
|
);
|
981
|
-
return
|
981
|
+
return boundingBoxElement;
|
982
982
|
}
|
983
983
|
function positionMark(markElement, element, markPlacement) {
|
984
984
|
function updatePosition() {
|
@@ -994,36 +994,36 @@ function positionMark(markElement, element, markPlacement) {
|
|
994
994
|
}
|
995
995
|
cleanupFns.push(autoUpdate(element, markElement, updatePosition));
|
996
996
|
}
|
997
|
-
function
|
997
|
+
function positionBoundingBox(boundingBox, element) {
|
998
998
|
return __async(this, null, function* () {
|
999
999
|
const { width, height } = element.getBoundingClientRect();
|
1000
1000
|
function updatePosition() {
|
1001
1001
|
return __async(this, null, function* () {
|
1002
|
-
const { x
|
1002
|
+
const { x, y } = yield computePosition2(element, boundingBox, {
|
1003
1003
|
placement: "top-start"
|
1004
1004
|
});
|
1005
|
-
Object.assign(
|
1006
|
-
left: `${
|
1007
|
-
top: `${
|
1005
|
+
Object.assign(boundingBox.style, {
|
1006
|
+
left: `${x}px`,
|
1007
|
+
top: `${y + height}px`,
|
1008
1008
|
width: `${width}px`,
|
1009
1009
|
height: `${height}px`
|
1010
1010
|
});
|
1011
1011
|
});
|
1012
1012
|
}
|
1013
|
-
cleanupFns.push(autoUpdate(element,
|
1013
|
+
cleanupFns.push(autoUpdate(element, boundingBox, updatePosition));
|
1014
1014
|
});
|
1015
1015
|
}
|
1016
1016
|
function applyStyle(element, defaultStyle, customStyle) {
|
1017
1017
|
Object.assign(element.style, defaultStyle, customStyle);
|
1018
1018
|
}
|
1019
1019
|
function unmark() {
|
1020
|
-
document.querySelectorAll(".webmarker, .webmarker-
|
1021
|
-
document.documentElement.removeAttribute("data-
|
1020
|
+
document.querySelectorAll(".webmarker, .webmarker-bounding-box").forEach((el) => el.remove());
|
1021
|
+
document.documentElement.removeAttribute("data-marked");
|
1022
1022
|
cleanupFns.forEach((fn) => fn());
|
1023
1023
|
cleanupFns = [];
|
1024
1024
|
}
|
1025
1025
|
function isMarked() {
|
1026
|
-
return document.documentElement.hasAttribute("data-
|
1026
|
+
return document.documentElement.hasAttribute("data-marked");
|
1027
1027
|
}
|
1028
1028
|
if (typeof window !== "undefined") {
|
1029
1029
|
window["mark"] = mark;
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
@@ -14,49 +14,48 @@ type Placement =
|
|
14
14
|
| "left-start"
|
15
15
|
| "left-end";
|
16
16
|
|
17
|
+
type StyleFunction = (element: Element) => CSSStyleDeclaration;
|
18
|
+
type StyleObject = Readonly<Partial<CSSStyleDeclaration>>;
|
19
|
+
|
17
20
|
interface MarkOptions {
|
18
21
|
/**
|
19
|
-
* A CSS selector to
|
22
|
+
* A CSS selector to query the elements to be marked.
|
20
23
|
*/
|
21
24
|
selector?: string;
|
22
25
|
/**
|
23
|
-
*
|
24
|
-
*
|
25
|
-
* @default 'data-mark-id'
|
26
|
+
* Provide a function for generating labels.
|
27
|
+
* By default, labels are generated as numbers starting from 0.
|
26
28
|
*/
|
27
|
-
|
29
|
+
getLabel?: (element: Element, index: number) => string;
|
28
30
|
/**
|
29
|
-
*
|
30
|
-
*
|
31
|
+
* Name for the attribute added to the marked elements to store the mark label.
|
32
|
+
*
|
33
|
+
* @default 'data-mark-label'
|
31
34
|
*/
|
32
|
-
|
33
|
-
| Readonly<Partial<CSSStyleDeclaration>>
|
34
|
-
| ((element: Element) => Readonly<Partial<CSSStyleDeclaration>>);
|
35
|
+
markAttribute?: string;
|
35
36
|
/**
|
36
37
|
* The placement of the mark relative to the element.
|
37
38
|
*
|
38
39
|
* @default 'top-start'
|
39
40
|
*/
|
40
41
|
markPlacement?: Placement;
|
42
|
+
/**
|
43
|
+
* A CSS style to apply to the label element.
|
44
|
+
* You can also specify a function that returns a CSS style object.
|
45
|
+
*/
|
46
|
+
markStyle?: StyleObject | StyleFunction;
|
41
47
|
/**
|
42
48
|
* A CSS style to apply to the bounding box element.
|
43
49
|
* You can also specify a function that returns a CSS style object.
|
44
|
-
* Bounding boxes are only shown if `
|
50
|
+
* Bounding boxes are only shown if `showBoundingBoxes` is `true`.
|
45
51
|
*/
|
46
|
-
|
47
|
-
| Readonly<Partial<CSSStyleDeclaration>>
|
48
|
-
| ((element: Element) => Readonly<Partial<CSSStyleDeclaration>>);
|
52
|
+
boundingBoxStyle?: StyleObject | StyleFunction;
|
49
53
|
/**
|
50
54
|
* Whether or not to show bounding boxes around the elements.
|
51
55
|
*
|
52
56
|
* @default true
|
53
57
|
*/
|
54
|
-
|
55
|
-
/**
|
56
|
-
* Provide a function for generating labels.
|
57
|
-
* By default, labels are generated as numbers starting from 0.
|
58
|
-
*/
|
59
|
-
labelGenerator?: (element: Element, index: number) => string;
|
58
|
+
showBoundingBoxes?: boolean;
|
60
59
|
/**
|
61
60
|
* Provide a container element to query the elements to be marked.
|
62
61
|
* By default, the container element is `document.body`.
|
@@ -73,7 +72,7 @@ interface MarkOptions {
|
|
73
72
|
interface MarkedElement {
|
74
73
|
element: Element;
|
75
74
|
markElement: HTMLElement;
|
76
|
-
|
75
|
+
boundingBoxElement?: HTMLElement;
|
77
76
|
}
|
78
77
|
|
79
78
|
let cleanupFns: (() => void)[] = [];
|
@@ -83,7 +82,9 @@ async function mark(
|
|
83
82
|
): Promise<Record<string, MarkedElement>> {
|
84
83
|
const {
|
85
84
|
selector = "button, input, a, select, textarea",
|
86
|
-
|
85
|
+
getLabel = (_, index) => index.toString(),
|
86
|
+
markAttribute = "data-mark-label",
|
87
|
+
markPlacement = "top-start",
|
87
88
|
markStyle = {
|
88
89
|
backgroundColor: "red",
|
89
90
|
color: "white",
|
@@ -91,13 +92,11 @@ async function mark(
|
|
91
92
|
fontSize: "12px",
|
92
93
|
fontWeight: "bold",
|
93
94
|
},
|
94
|
-
|
95
|
-
maskStyle = {
|
95
|
+
boundingBoxStyle = {
|
96
96
|
outline: "2px dashed red",
|
97
97
|
backgroundColor: "transparent",
|
98
98
|
},
|
99
|
-
|
100
|
-
labelGenerator = (_, index) => index.toString(),
|
99
|
+
showBoundingBoxes = true,
|
101
100
|
containerElement = document.body,
|
102
101
|
viewPortOnly = false,
|
103
102
|
} = options;
|
@@ -112,27 +111,25 @@ async function mark(
|
|
112
111
|
|
113
112
|
await Promise.all(
|
114
113
|
elements.map(async (element, index) => {
|
115
|
-
const label =
|
114
|
+
const label = getLabel(element, index);
|
116
115
|
const markElement = createMark(element, markStyle, label, markPlacement);
|
117
116
|
|
118
|
-
const
|
119
|
-
?
|
117
|
+
const boundingBoxElement = showBoundingBoxes
|
118
|
+
? createBoundingBox(element, boundingBoxStyle, label)
|
120
119
|
: undefined;
|
121
120
|
|
122
|
-
markedElements[label] = { element, markElement,
|
121
|
+
markedElements[label] = { element, markElement, boundingBoxElement };
|
123
122
|
element.setAttribute(markAttribute, label);
|
124
123
|
})
|
125
124
|
);
|
126
125
|
|
127
|
-
document.documentElement.
|
126
|
+
document.documentElement.setAttribute("data-marked", "true");
|
128
127
|
return markedElements;
|
129
128
|
}
|
130
129
|
|
131
130
|
function createMark(
|
132
131
|
element: Element,
|
133
|
-
style:
|
134
|
-
| Readonly<Partial<CSSStyleDeclaration>>
|
135
|
-
| ((element: Element) => Readonly<Partial<CSSStyleDeclaration>>),
|
132
|
+
style: StyleObject | StyleFunction,
|
136
133
|
label: string,
|
137
134
|
markPlacement: Placement = "top-start"
|
138
135
|
): HTMLElement {
|
@@ -154,20 +151,18 @@ function createMark(
|
|
154
151
|
return markElement;
|
155
152
|
}
|
156
153
|
|
157
|
-
function
|
154
|
+
function createBoundingBox(
|
158
155
|
element: Element,
|
159
|
-
style:
|
160
|
-
| Readonly<Partial<CSSStyleDeclaration>>
|
161
|
-
| ((element: Element) => Readonly<Partial<CSSStyleDeclaration>>),
|
156
|
+
style: StyleObject | StyleFunction,
|
162
157
|
label: string
|
163
158
|
): HTMLElement {
|
164
|
-
const
|
165
|
-
|
166
|
-
|
167
|
-
document.body.appendChild(
|
168
|
-
|
159
|
+
const boundingBoxElement = document.createElement("div");
|
160
|
+
boundingBoxElement.className = "webmarker-bounding-box";
|
161
|
+
boundingBoxElement.id = `webmarker-bounding-box-${label}`;
|
162
|
+
document.body.appendChild(boundingBoxElement);
|
163
|
+
positionBoundingBox(boundingBoxElement, element);
|
169
164
|
applyStyle(
|
170
|
-
|
165
|
+
boundingBoxElement,
|
171
166
|
{
|
172
167
|
zIndex: "999999999",
|
173
168
|
position: "absolute",
|
@@ -175,7 +170,7 @@ function createMask(
|
|
175
170
|
},
|
176
171
|
typeof style === "function" ? style(element) : style
|
177
172
|
);
|
178
|
-
return
|
173
|
+
return boundingBoxElement;
|
179
174
|
}
|
180
175
|
|
181
176
|
function positionMark(
|
@@ -196,41 +191,41 @@ function positionMark(
|
|
196
191
|
cleanupFns.push(autoUpdate(element, markElement, updatePosition));
|
197
192
|
}
|
198
193
|
|
199
|
-
async function
|
194
|
+
async function positionBoundingBox(boundingBox: HTMLElement, element: Element) {
|
200
195
|
const { width, height } = element.getBoundingClientRect();
|
201
196
|
async function updatePosition() {
|
202
|
-
const { x
|
197
|
+
const { x, y } = await computePosition(element, boundingBox, {
|
203
198
|
placement: "top-start",
|
204
199
|
});
|
205
|
-
Object.assign(
|
206
|
-
left: `${
|
207
|
-
top: `${
|
200
|
+
Object.assign(boundingBox.style, {
|
201
|
+
left: `${x}px`,
|
202
|
+
top: `${y + height}px`,
|
208
203
|
width: `${width}px`,
|
209
204
|
height: `${height}px`,
|
210
205
|
});
|
211
206
|
}
|
212
|
-
cleanupFns.push(autoUpdate(element,
|
207
|
+
cleanupFns.push(autoUpdate(element, boundingBox, updatePosition));
|
213
208
|
}
|
214
209
|
|
215
210
|
function applyStyle(
|
216
211
|
element: HTMLElement,
|
217
|
-
defaultStyle:
|
218
|
-
customStyle:
|
212
|
+
defaultStyle: Partial<CSSStyleDeclaration>,
|
213
|
+
customStyle: Partial<CSSStyleDeclaration>
|
219
214
|
): void {
|
220
215
|
Object.assign(element.style, defaultStyle, customStyle);
|
221
216
|
}
|
222
217
|
|
223
218
|
function unmark(): void {
|
224
219
|
document
|
225
|
-
.querySelectorAll(".webmarker, .webmarker-
|
220
|
+
.querySelectorAll(".webmarker, .webmarker-bounding-box")
|
226
221
|
.forEach((el) => el.remove());
|
227
|
-
document.documentElement.removeAttribute("data-
|
222
|
+
document.documentElement.removeAttribute("data-marked");
|
228
223
|
cleanupFns.forEach((fn) => fn());
|
229
224
|
cleanupFns = [];
|
230
225
|
}
|
231
226
|
|
232
227
|
function isMarked(): boolean {
|
233
|
-
return document.documentElement.hasAttribute("data-
|
228
|
+
return document.documentElement.hasAttribute("data-marked");
|
234
229
|
}
|
235
230
|
|
236
231
|
if (typeof window !== "undefined") {
|