webmarker-js 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
![Screenshot of marked Google homepage](https://github.com/user-attachments/assets/722e1034-06d4-4ccd-a7d6-f03749435681)
|
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") {
|