cuprite 0.18 → 0.20

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.
@@ -1,606 +1,628 @@
1
- class InvalidSelector extends Error {}
2
- class TimedOutPromise extends Error {}
3
- class MouseEventFailed extends Error {}
4
-
5
- const EVENTS = {
6
- FOCUS: ["blur", "focus", "focusin", "focusout"],
7
- MOUSE: ["click", "dblclick", "mousedown", "mouseenter", "mouseleave",
8
- "mousemove", "mouseover", "mouseout", "mouseup", "contextmenu"],
9
- FORM: ["submit"]
10
- }
11
-
12
- class Cuprite {
13
- constructor() {
14
- this._json = JSON; // In case someone overrides it like mootools
1
+ (() => {
2
+ if (window._cuprite) return;
3
+
4
+ class InvalidSelector extends Error {}
5
+ class TimedOutPromise extends Error {}
6
+ class MouseEventFailed extends Error {}
7
+
8
+ const EVENTS = {
9
+ FOCUS: ["blur", "focus", "focusin", "focusout"],
10
+ MOUSE: ["click", "dblclick", "mousedown", "mouseenter", "mouseleave",
11
+ "mousemove", "mouseover", "mouseout", "mouseup", "contextmenu"],
12
+ FORM: ["submit"]
15
13
  }
16
14
 
17
- find(method, selector, within = document) {
18
- try {
19
- let results = [];
15
+ class Cuprite {
16
+ constructor() {
17
+ this._json = JSON; // In case someone overrides it like mootools
18
+ }
20
19
 
21
- if (method == "xpath") {
22
- let xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
23
- for (let i = 0; i < xpath.snapshotLength; i++) {
24
- results.push(xpath.snapshotItem(i));
20
+ find(method, selector, within = document) {
21
+ try {
22
+ let results = [];
23
+
24
+ if (method == "xpath") {
25
+ let xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
26
+ for (let i = 0; i < xpath.snapshotLength; i++) {
27
+ results.push(xpath.snapshotItem(i));
28
+ }
29
+ } else {
30
+ results = Array.from(within.querySelectorAll(selector));
25
31
  }
26
- } else {
27
- results = Array.from(within.querySelectorAll(selector));
28
- }
29
32
 
30
- return results;
31
- } catch (error) {
32
- // DOMException.INVALID_EXPRESSION_ERR is undefined, using pure code
33
- if (error.code == DOMException.SYNTAX_ERR || error.code == 51) {
34
- throw new InvalidSelector;
35
- } else {
36
- throw error;
33
+ return results;
34
+ } catch (error) {
35
+ // DOMException.INVALID_EXPRESSION_ERR is undefined, using pure code
36
+ if (error.code == DOMException.SYNTAX_ERR || error.code == 51) {
37
+ throw new InvalidSelector;
38
+ } else {
39
+ throw error;
40
+ }
37
41
  }
38
42
  }
39
- }
40
43
 
41
- parents(node) {
42
- let nodes = [];
43
- let parent = node.parentNode;
44
- while (parent != document && parent !== null) {
45
- nodes.push(parent);
46
- parent = parent.parentNode;
47
- }
48
- return nodes;
49
- }
50
-
51
- visibleText(node) {
52
- if (this.isVisible(node)) {
53
- if (node.nodeName == "TEXTAREA") {
54
- return node.textContent;
55
- }
56
- if (node instanceof SVGElement) {
57
- return node.textContent;
44
+ parents(node) {
45
+ let nodes = [];
46
+ let parent = node.parentNode;
47
+ while (parent != document && parent !== null) {
48
+ nodes.push(parent);
49
+ parent = parent.parentNode;
58
50
  }
59
- if (node instanceof ShadowRoot) {
60
- return Array.from(node.children)
61
- .map(child => this.visibleText(child))
62
- .filter(text => text)
63
- .join(" ");
64
- }
65
- return node.innerText;
51
+ return nodes;
66
52
  }
67
- }
68
53
 
69
- isVisible(node) {
70
- let mapName, style;
71
- // if node is area, check visibility of relevant image
72
- if (node.tagName === "AREA") {
73
- mapName = document.evaluate("./ancestor::map/@name", node, null, XPathResult.STRING_TYPE, null).stringValue;
74
- node = document.querySelector(`img[usemap="#${mapName}"]`);
75
- if (node == null) {
76
- return false;
54
+ visibleText(node) {
55
+ if (this.isVisible(node)) {
56
+ if (node.nodeName == "TEXTAREA") {
57
+ return node.textContent;
58
+ }
59
+ if (node instanceof SVGElement) {
60
+ return node.textContent;
61
+ }
62
+ if (node instanceof ShadowRoot) {
63
+ return Array.from(node.children)
64
+ .map(child => this.visibleText(child))
65
+ .filter(text => text)
66
+ .join(" ");
67
+ }
68
+ return node.innerText;
77
69
  }
78
70
  }
79
71
 
80
- while (node) {
81
- if (node instanceof ShadowRoot) {
82
- node = node.host;
83
- } else {
84
- style = window.getComputedStyle(node);
85
- if (style.display === "none" || style.visibility === "hidden" || parseFloat(style.opacity) === 0) {
72
+ isVisible(node) {
73
+ let mapName, style;
74
+ // if node is area, check visibility of relevant image
75
+ if (node.tagName === "AREA") {
76
+ mapName = document.evaluate("./ancestor::map/@name", node, null, XPathResult.STRING_TYPE, null).stringValue;
77
+ node = document.querySelector(`img[usemap="#${mapName}"]`);
78
+ if (node == null) {
86
79
  return false;
87
80
  }
81
+ }
82
+
83
+ let ancestorOpacityApplies = true;
88
84
 
89
- let parent = node.parentElement;
90
- if (parent && parent.tagName === "DETAILS" && !parent.open) {
91
- // In a closed <details> only the first <summary> (and its subtree) is rendered.
92
- if (node !== parent.querySelector(":scope > summary")) {
85
+ while (node) {
86
+ if (node instanceof ShadowRoot) {
87
+ node = node.host;
88
+ } else {
89
+ style = window.getComputedStyle(node);
90
+ if (style.display === "none" || style.visibility === "hidden" || (ancestorOpacityApplies && parseFloat(style.opacity) === 0)) {
93
91
  return false;
94
92
  }
95
- }
96
93
 
97
- node = parent ?? (node.getRootNode() instanceof ShadowRoot && node.getRootNode());
98
- }
99
- }
94
+ // A node matching :modal or :popover-open is a top-layer element, so
95
+ // an ancestor's opacity no longer applies to it once shown.
96
+ if (ancestorOpacityApplies) {
97
+ try {
98
+ if (node.matches(":modal, :popover-open")) {
99
+ ancestorOpacityApplies = false;
100
+ }
101
+ } catch (e) {}
102
+ }
100
103
 
101
- return true;
102
- }
104
+ let parent = node.parentElement;
105
+ if (parent && parent.tagName === "DETAILS" && !parent.open) {
106
+ // In a closed <details> only the first <summary> (and its subtree) is rendered.
107
+ if (node !== parent.querySelector(":scope > summary")) {
108
+ return false;
109
+ }
110
+ }
103
111
 
104
- isDisabled(node) {
105
- let xpath = "parent::optgroup[@disabled] | \
106
- ancestor::select[@disabled] | \
107
- parent::fieldset[@disabled] | \
108
- ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]";
112
+ node = parent ?? (node.getRootNode() instanceof ShadowRoot && node.getRootNode());
113
+ }
114
+ }
109
115
 
110
- return node.disabled || document.evaluate(xpath, node, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
111
- }
116
+ return true;
117
+ }
112
118
 
113
- path(node) {
114
- if (node.getRootNode && node.getRootNode() instanceof ShadowRoot) {
115
- return "(: Shadow DOM element - no XPath :)";
116
- };
119
+ isDisabled(node) {
120
+ let xpath = "parent::optgroup[@disabled] | \
121
+ ancestor::select[@disabled] | \
122
+ parent::fieldset[@disabled] | \
123
+ ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]";
117
124
 
118
- let nodes = [node];
119
- let parent = node.parentNode;
120
- while (parent !== document && parent !== null) {
121
- nodes.unshift(parent);
122
- parent = parent.parentNode;
125
+ return node.disabled || document.evaluate(xpath, node, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
123
126
  }
124
127
 
125
- let selectors = nodes.map(node => {
126
- let prevSiblings = [];
127
- let xpath = document.evaluate(`./preceding-sibling::${node.tagName}`, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
128
+ path(node) {
129
+ if (node.getRootNode && node.getRootNode() instanceof ShadowRoot) {
130
+ return "(: Shadow DOM element - no XPath :)";
131
+ };
128
132
 
129
- for (let i = 0; i < xpath.snapshotLength; i++) {
130
- prevSiblings.push(xpath.snapshotItem(i));
133
+ let nodes = [node];
134
+ let parent = node.parentNode;
135
+ while (parent !== document && parent !== null) {
136
+ nodes.unshift(parent);
137
+ parent = parent.parentNode;
131
138
  }
132
139
 
133
- return `${node.tagName}[${(prevSiblings.length + 1)}]`;
134
- });
135
-
136
- return `//${selectors.join("/")}`;
137
- }
140
+ let selectors = nodes.map(node => {
141
+ let prevSiblings = [];
142
+ let xpath = document.evaluate(`./preceding-sibling::${node.tagName}`, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
138
143
 
139
- /**
140
- * Returns true if the node is obscured in the viewport.
141
- *
142
- * @param {Element} node
143
- * @return {boolean} true if the node is obscured, false otherwise
144
- */
145
- isObscured(node) {
146
- let win = window;
147
- let rect = node.getBoundingClientRect();
148
- let px = rect.left + rect.width / 2;
149
- let py = rect.top + rect.height / 2;
144
+ for (let i = 0; i < xpath.snapshotLength; i++) {
145
+ prevSiblings.push(xpath.snapshotItem(i));
146
+ }
150
147
 
151
- while (win) {
152
- let topNode = win.document.elementFromPoint(px, py);
148
+ return `${node.tagName}[${(prevSiblings.length + 1)}]`;
149
+ });
153
150
 
154
- if (node !== topNode && !node.contains(topNode)) return true;
151
+ return `//${selectors.join("/")}`;
152
+ }
155
153
 
156
- node = win.frameElement;
157
- if (!node) return false;
154
+ /**
155
+ * Returns true if the node is obscured in the viewport.
156
+ *
157
+ * @param {Element} node
158
+ * @return {boolean} true if the node is obscured, false otherwise
159
+ */
160
+ isObscured(node) {
161
+ let win = window;
162
+ let rect = node.getBoundingClientRect();
163
+ let px = rect.left + rect.width / 2;
164
+ let py = rect.top + rect.height / 2;
165
+
166
+ while (win) {
167
+ let topNode = win.document.elementFromPoint(px, py);
168
+
169
+ if (node !== topNode && !node.contains(topNode)) return true;
170
+
171
+ node = win.frameElement;
172
+ if (!node) return false;
173
+
174
+ rect = node.getBoundingClientRect();
175
+ px = rect.left + px;
176
+ py = rect.top + py;
177
+ win = win.parent;
178
+ }
158
179
 
159
- rect = node.getBoundingClientRect();
160
- px = rect.left + px;
161
- py = rect.top + py;
162
- win = win.parent;
180
+ return false;
163
181
  }
164
182
 
165
- return false;
166
- }
183
+ set(node, value) {
184
+ if (node.readOnly) return;
167
185
 
168
- set(node, value) {
169
- if (node.readOnly) return;
170
-
171
- if (node.maxLength >= 0) {
172
- value = value.substr(0, node.maxLength);
173
- }
174
-
175
- let valueBefore = node.value;
176
-
177
- node.focus();
178
- this.trigger(node, "focus");
179
- this.setValue(node, "");
180
-
181
- if (node.type == "number" || node.type == "date" || node.type == "range" || node.type == "time" || node.type == "month" || node.type == "week") {
182
- this.setValue(node, value);
183
- this.input(node);
184
- } else if (node.type == "datetime-local") {
185
- value = new Date(value);
186
- let year = value.getFullYear();
187
- let month = ("0" + (value.getMonth() + 1)).slice(-2);
188
- let date = ("0" + value.getDate()).slice(-2);
189
- let hour = ("0" + value.getHours()).slice(-2);
190
- let min = ("0" + value.getMinutes()).slice(-2);
191
- let sec = ("0" + value.getSeconds()).slice(-2);
192
- this.setValue(node, `${year}-${month}-${date}T${hour}:${min}:${sec}`);
193
- this.input(node);
194
- } else {
195
- for (let i = 0; i < value.length; i++) {
196
- let char = value[i];
197
- let keyCode = this.characterToKeyCode(char);
198
- // call the following functions in order, if one returns false (preventDefault),
199
- // stop the call chain
200
- [
201
- () => this.keyupdowned(node, "keydown", char, keyCode),
202
- () => this.keypressed(node, false, false, false, false, char.charCodeAt(0), char.charCodeAt(0)),
203
- () => {
204
- this.setValue(node, node.value + char)
205
- this.input(node)
206
- }
207
- ].some(fn => fn())
186
+ if (node.maxLength >= 0) {
187
+ value = value.substr(0, node.maxLength);
188
+ }
189
+
190
+ let valueBefore = node.value;
191
+
192
+ node.focus();
193
+ this.trigger(node, "focus");
194
+ this.setValue(node, "");
195
+
196
+ if (node.type == "number" || node.type == "date" || node.type == "range" || node.type == "time" || node.type == "month" || node.type == "week") {
197
+ this.setValue(node, value);
198
+ this.input(node);
199
+ } else if (node.type == "datetime-local") {
200
+ value = new Date(value);
201
+ let year = value.getFullYear();
202
+ let month = ("0" + (value.getMonth() + 1)).slice(-2);
203
+ let date = ("0" + value.getDate()).slice(-2);
204
+ let hour = ("0" + value.getHours()).slice(-2);
205
+ let min = ("0" + value.getMinutes()).slice(-2);
206
+ let sec = ("0" + value.getSeconds()).slice(-2);
207
+ this.setValue(node, `${year}-${month}-${date}T${hour}:${min}:${sec}`);
208
+ this.input(node);
209
+ } else {
210
+ for (let i = 0; i < value.length; i++) {
211
+ let char = value[i];
212
+ let keyCode = this.characterToKeyCode(char);
213
+ // call the following functions in order, if one returns false (preventDefault),
214
+ // stop the call chain
215
+ [
216
+ () => this.keyupdowned(node, "keydown", char, keyCode),
217
+ () => this.keypressed(node, false, false, false, false, char.charCodeAt(0), char.charCodeAt(0)),
218
+ () => {
219
+ this.setValue(node, node.value + char)
220
+ this.input(node)
221
+ }
222
+ ].some(fn => fn())
223
+
224
+ this.keyupdowned(node, "keyup", char, keyCode);
225
+ }
208
226
 
209
- this.keyupdowned(node, "keyup", char, keyCode);
227
+ if (valueBefore !== "" && node.value === "") {
228
+ this.input(node, "deleteContentBackward");
229
+ }
230
+ }
231
+
232
+ if (valueBefore !== node.value) {
233
+ this.changed(node);
210
234
  }
235
+ this.trigger(node, "blur");
211
236
  }
212
237
 
213
- if (valueBefore !== node.value) {
214
- this.changed(node);
238
+ setValue(node, value) {
239
+ let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
240
+ let nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
241
+
242
+ if (node.tagName.toLowerCase() === 'input') {
243
+ return nativeInputValueSetter.call(node, value);
244
+ }
245
+ return nativeTextareaValueSetter.call(node, value);
215
246
  }
216
- this.trigger(node, "blur");
217
- }
218
247
 
219
- setValue(node, value) {
220
- let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
221
- let nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
248
+ input(node, inputType = "insertText") {
249
+ let event = new InputEvent("input", { inputType: inputType, bubbles: true, cancelable: false });
250
+ node.dispatchEvent(event);
251
+ }
222
252
 
223
- if (node.tagName.toLowerCase() === 'input') {
224
- return nativeInputValueSetter.call(node, value);
253
+ /**
254
+ * @return {boolean} false when an event handler called preventDefault()
255
+ */
256
+ keyupdowned(node, eventName, char, keyCode) {
257
+ let event = new KeyboardEvent(
258
+ eventName, {
259
+ bubbles: true,
260
+ cancelable: true,
261
+ key: char,
262
+ keyCode: keyCode
263
+ }
264
+ );
265
+ return !node.dispatchEvent(event);
225
266
  }
226
- return nativeTextareaValueSetter.call(node, value);
227
- }
228
267
 
229
- input(node) {
230
- let event = new InputEvent("input", { inputType: "insertText", bubbles: true, cancelable: false });
231
- node.dispatchEvent(event);
232
- }
268
+ /**
269
+ * @return {boolean} false when an event handler called preventDefault()
270
+ */
271
+ keypressed(node, altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
272
+ let event = new KeyboardEvent(
273
+ "keypress", {
274
+ bubbles: true,
275
+ cancelable: true,
276
+ altKey: altKey,
277
+ ctrlKey: ctrlKey,
278
+ shiftKey: shiftKey,
279
+ metaKey: metaKey,
280
+ keyCode: keyCode,
281
+ charCode: charCode
282
+ }
283
+ );
284
+ return !node.dispatchEvent(event);
285
+ }
233
286
 
234
- /**
235
- * @return {boolean} false when an event handler called preventDefault()
236
- */
237
- keyupdowned(node, eventName, char, keyCode) {
238
- let event = new KeyboardEvent(
239
- eventName, {
240
- bubbles: true,
241
- cancelable: true,
242
- key: char,
243
- keyCode: keyCode
287
+ characterToKeyCode(char) {
288
+ const specialKeys = {
289
+ 96: 192, // `
290
+ 45: 189, // -
291
+ 61: 187, // =
292
+ 91: 219, // [
293
+ 93: 221, // ]
294
+ 92: 220, // \
295
+ 59: 186, // ;
296
+ 39: 222, // '
297
+ 44: 188, // ,
298
+ 46: 190, // .
299
+ 47: 191, // /
300
+ 127: 46, // delete
301
+ 126: 192, // ~
302
+ 33: 49, // !
303
+ 64: 50, // @
304
+ 35: 51, // #
305
+ 36: 52, // $
306
+ 37: 53, // %
307
+ 94: 54, // ^
308
+ 38: 55, // &
309
+ 42: 56, // *
310
+ 40: 57, // (
311
+ 41: 48, // )
312
+ 95: 189, // _
313
+ 43: 187, // +
314
+ 123: 219, // {
315
+ 125: 221, // }
316
+ 124: 220, // |
317
+ 58: 186, // :
318
+ 34: 222, // "
319
+ 60: 188, // <
320
+ 62: 190, // >
321
+ 63: 191, // ?
244
322
  }
245
- );
246
- return !node.dispatchEvent(event);
247
- }
248
323
 
249
- /**
250
- * @return {boolean} false when an event handler called preventDefault()
251
- */
252
- keypressed(node, altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
253
- let event = new KeyboardEvent(
254
- "keypress", {
255
- bubbles: true,
256
- cancelable: true,
257
- altKey: altKey,
258
- ctrlKey: ctrlKey,
259
- shiftKey: shiftKey,
260
- metaKey: metaKey,
261
- keyCode: keyCode,
262
- charCode: charCode
263
- }
264
- );
265
- return !node.dispatchEvent(event);
266
- }
324
+ let code = char.toUpperCase().charCodeAt(0);
325
+ return specialKeys[code] || code;
326
+ }
267
327
 
268
- characterToKeyCode(char) {
269
- const specialKeys = {
270
- 96: 192, // `
271
- 45: 189, // -
272
- 61: 187, // =
273
- 91: 219, // [
274
- 93: 221, // ]
275
- 92: 220, // \
276
- 59: 186, // ;
277
- 39: 222, // '
278
- 44: 188, // ,
279
- 46: 190, // .
280
- 47: 191, // /
281
- 127: 46, // delete
282
- 126: 192, // ~
283
- 33: 49, // !
284
- 64: 50, // @
285
- 35: 51, // #
286
- 36: 52, // $
287
- 37: 53, // %
288
- 94: 54, // ^
289
- 38: 55, // &
290
- 42: 56, // *
291
- 40: 57, // (
292
- 41: 48, // )
293
- 95: 189, // _
294
- 43: 187, // +
295
- 123: 219, // {
296
- 125: 221, // }
297
- 124: 220, // |
298
- 58: 186, // :
299
- 34: 222, // "
300
- 60: 188, // <
301
- 62: 190, // >
302
- 63: 191, // ?
303
- }
304
-
305
- let code = char.toUpperCase().charCodeAt(0);
306
- return specialKeys[code] || code;
307
- }
328
+ scrollIntoViewport(node) {
329
+ let areaImage = this._getAreaImage(node);
308
330
 
309
- scrollIntoViewport(node) {
310
- let areaImage = this._getAreaImage(node);
331
+ if (areaImage) {
332
+ return this.scrollIntoViewport(areaImage);
333
+ } else {
334
+ node.scrollIntoViewIfNeeded();
311
335
 
312
- if (areaImage) {
313
- return this.scrollIntoViewport(areaImage);
314
- } else {
315
- node.scrollIntoViewIfNeeded();
336
+ if (!this._isInViewport(node)) {
337
+ node.scrollIntoView({block: "center", inline: "center", behavior: "instant"});
338
+ return this._isInViewport(node);
339
+ }
316
340
 
317
- if (!this._isInViewport(node)) {
318
- node.scrollIntoView({block: "center", inline: "center", behavior: "instant"});
319
- return this._isInViewport(node);
341
+ return true;
320
342
  }
321
-
322
- return true;
323
343
  }
324
- }
325
344
 
326
- mouseEventTest(node, name, x, y) {
327
- let frameOffset = this._frameOffset();
328
- x -= frameOffset.left;
329
- y -= frameOffset.top;
345
+ mouseEventTest(node, name, x, y) {
346
+ let frameOffset = this._frameOffset();
347
+ x -= frameOffset.left;
348
+ y -= frameOffset.top;
330
349
 
331
- let element = node.getRootNode().elementFromPoint(x, y);
350
+ let element = node.getRootNode().elementFromPoint(x, y);
332
351
 
333
- let el = element;
334
- while (el) {
335
- if (el == node) {
336
- return true;
337
- } else {
338
- el = el.parentNode;
352
+ let el = element;
353
+ while (el) {
354
+ if (el == node) {
355
+ return true;
356
+ } else {
357
+ el = el.parentNode;
358
+ }
339
359
  }
360
+
361
+ let selector = element && this._getSelector(element) || "none";
362
+ throw new MouseEventFailed([name, selector, x, y].join(", "));
340
363
  }
341
364
 
342
- let selector = element && this._getSelector(element) || "none";
343
- throw new MouseEventFailed([name, selector, x, y].join(", "));
344
- }
365
+ _getAreaImage(node) {
366
+ if ("area" == node.tagName.toLowerCase()) {
367
+ let map = node.parentNode;
368
+ if (map.tagName.toLowerCase() != "map") {
369
+ throw new Error("the area is not within a map");
370
+ }
345
371
 
346
- _getAreaImage(node) {
347
- if ("area" == node.tagName.toLowerCase()) {
348
- let map = node.parentNode;
349
- if (map.tagName.toLowerCase() != "map") {
350
- throw new Error("the area is not within a map");
351
- }
372
+ let mapName = map.getAttribute("name");
373
+ if (typeof mapName === "undefined" || mapName === null) {
374
+ throw new Error("area's parent map must have a name");
375
+ }
352
376
 
353
- let mapName = map.getAttribute("name");
354
- if (typeof mapName === "undefined" || mapName === null) {
355
- throw new Error("area's parent map must have a name");
356
- }
377
+ mapName = `#${mapName.toLowerCase()}`;
378
+ let imageNode = this.find("css", `img[usemap='${mapName}']`)[0];
379
+ if (typeof imageNode === "undefined" || imageNode === null) {
380
+ throw new Error("no image matches the map");
381
+ }
357
382
 
358
- mapName = `#${mapName.toLowerCase()}`;
359
- let imageNode = this.find("css", `img[usemap='${mapName}']`)[0];
360
- if (typeof imageNode === "undefined" || imageNode === null) {
361
- throw new Error("no image matches the map");
383
+ return imageNode;
362
384
  }
363
-
364
- return imageNode;
365
385
  }
366
- }
367
386
 
368
- _frameOffset() {
369
- let win = window;
370
- let offset = { top: 0, left: 0 };
387
+ _frameOffset() {
388
+ let win = window;
389
+ let offset = { top: 0, left: 0 };
371
390
 
372
- while (win.frameElement) {
373
- let rect = win.frameElement.getClientRects()[0];
374
- let style = win.getComputedStyle(win.frameElement);
375
- win = win.parent;
391
+ while (win.frameElement) {
392
+ let rect = win.frameElement.getClientRects()[0];
393
+ let style = win.getComputedStyle(win.frameElement);
394
+ win = win.parent;
395
+
396
+ offset.top += rect.top + parseInt(style.getPropertyValue("padding-top"), 10)
397
+ offset.left += rect.left + parseInt(style.getPropertyValue("padding-left"), 10)
398
+ }
376
399
 
377
- offset.top += rect.top + parseInt(style.getPropertyValue("padding-top"), 10)
378
- offset.left += rect.left + parseInt(style.getPropertyValue("padding-left"), 10)
400
+ return offset;
379
401
  }
380
402
 
381
- return offset;
382
- }
403
+ _getSelector(el) {
404
+ let selector = (el.tagName != 'HTML') ? this._getSelector(el.parentNode) + " " : "";
405
+ selector += el.tagName.toLowerCase();
406
+ if (el.id) { selector += `#${el.id}` };
407
+ el.classList.forEach(c => selector += `.${c}`);
408
+ return selector;
409
+ }
383
410
 
384
- _getSelector(el) {
385
- let selector = (el.tagName != 'HTML') ? this._getSelector(el.parentNode) + " " : "";
386
- selector += el.tagName.toLowerCase();
387
- if (el.id) { selector += `#${el.id}` };
388
- el.classList.forEach(c => selector += `.${c}`);
389
- return selector;
390
- }
411
+ _isInViewport(node) {
412
+ let rect = node.getBoundingClientRect();
391
413
 
392
- _isInViewport(node) {
393
- let rect = node.getBoundingClientRect();
414
+ let inViewport = rect.top >= 0 &&
415
+ rect.left >= 0 &&
416
+ rect.bottom <= window.innerHeight &&
417
+ rect.right <= window.innerWidth;
394
418
 
395
- let inViewport = rect.top >= 0 &&
396
- rect.left >= 0 &&
397
- rect.bottom <= window.innerHeight &&
398
- rect.right <= window.innerWidth;
419
+ if (inViewport) {
420
+ // check if obscured by another element
421
+ let x = rect.width/2;
422
+ let y = rect.height/2 ;
399
423
 
400
- if (inViewport) {
401
- // check if obscured by another element
402
- let x = rect.width/2;
403
- let y = rect.height/2 ;
424
+ let px = rect.left + x,
425
+ py = rect.top + y,
426
+ e = document.elementFromPoint(px, py);
427
+ return node == e;
428
+ }
404
429
 
405
- let px = rect.left + x,
406
- py = rect.top + y,
407
- e = document.elementFromPoint(px, py);
408
- return node == e;
430
+ return false;
409
431
  }
410
432
 
411
- return false;
412
- }
413
-
414
- select(node, value) {
415
- if (this.isDisabled(node)) {
416
- return false;
417
- } else if (value == false && !node.parentNode.multiple) {
418
- return false;
419
- } else {
420
- let parentNode = node.parentNode;
421
- this.trigger(parentNode, "focus");
433
+ select(node, value) {
434
+ if (this.isDisabled(node)) {
435
+ return false;
436
+ } else if (value == false && !node.parentNode.multiple) {
437
+ return false;
438
+ } else {
439
+ let selectNode = node.closest("select");
440
+ this.trigger(selectNode, "focus");
422
441
 
423
- node.selected = value;
424
- this.changed(node);
442
+ node.selected = value;
443
+ selectNode.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
444
+ this.changed(node);
425
445
 
426
- this.trigger(parentNode, "blur");
427
- return true;
446
+ this.trigger(selectNode, "blur");
447
+ return true;
448
+ }
428
449
  }
429
- }
430
450
 
431
- changed(node) {
432
- let element;
433
- let event = document.createEvent("HTMLEvents");
434
- event.initEvent("change", true, false);
435
-
436
- // In the case of an OPTION tag, the change event should come
437
- // from the parent SELECT
438
- if (node.nodeName == "OPTION") {
439
- element = node.parentNode
440
- if (element.nodeName == "OPTGROUP") {
441
- element = element.parentNode
451
+ changed(node) {
452
+ let element;
453
+ let event = document.createEvent("HTMLEvents");
454
+ event.initEvent("change", true, false);
455
+
456
+ // In the case of an OPTION tag, the change event should come
457
+ // from the parent SELECT
458
+ if (node.nodeName == "OPTION") {
459
+ element = node.parentNode
460
+ if (element.nodeName == "OPTGROUP") {
461
+ element = element.parentNode
462
+ }
463
+ element
464
+ } else {
465
+ element = node
442
466
  }
443
- element
444
- } else {
445
- element = node
446
- }
447
467
 
448
- element.dispatchEvent(event)
449
- }
468
+ element.dispatchEvent(event)
469
+ }
450
470
 
451
- trigger(node, name, options = {}) {
452
- let event;
453
-
454
- if (EVENTS.MOUSE.indexOf(name) != -1) {
455
- event = document.createEvent("MouseEvent");
456
- event.initMouseEvent(
457
- name, true, true, window, 0,
458
- options["screenX"] || 0, options["screenY"] || 0,
459
- options["clientX"] || 0, options["clientY"] || 0,
460
- options["ctrlKey"] || false,
461
- options["altKey"] || false,
462
- options["shiftKey"] || false,
463
- options["metaKey"] || false,
464
- options["button"] || 0, null
465
- )
466
- } else if (EVENTS.FOCUS.indexOf(name) != -1) {
467
- event = new FocusEvent(name, { bubbles: true, cancelable: true });
468
- } else if (EVENTS.FORM.indexOf(name) != -1) {
469
- event = this.obtainEvent(name);
470
- } else {
471
- throw "Unknown event";
472
- }
473
-
474
- node.dispatchEvent(event);
475
- }
471
+ trigger(node, name, options = {}) {
472
+ let event;
473
+
474
+ if (EVENTS.MOUSE.indexOf(name) != -1) {
475
+ event = document.createEvent("MouseEvent");
476
+ event.initMouseEvent(
477
+ name, true, true, window, 0,
478
+ options["screenX"] || 0, options["screenY"] || 0,
479
+ options["clientX"] || 0, options["clientY"] || 0,
480
+ options["ctrlKey"] || false,
481
+ options["altKey"] || false,
482
+ options["shiftKey"] || false,
483
+ options["metaKey"] || false,
484
+ options["button"] || 0, null
485
+ )
486
+ } else if (EVENTS.FOCUS.indexOf(name) != -1) {
487
+ event = new FocusEvent(name, { bubbles: true, cancelable: true });
488
+ } else if (EVENTS.FORM.indexOf(name) != -1) {
489
+ event = this.obtainEvent(name);
490
+ } else if (name == "input") {
491
+ event = new InputEvent(name, { inputType: "insertText", bubbles: true, composed: true });
492
+ } else {
493
+ event = new Event(name, { bubbles: true, cancelable: true });
494
+ }
476
495
 
477
- obtainEvent(name) {
478
- let event = document.createEvent("HTMLEvents");
479
- event.initEvent(name, true, true);
480
- return event;
481
- }
496
+ node.dispatchEvent(event);
497
+ }
482
498
 
483
- getAttributes(node) {
484
- let attrs = {};
485
- for (let i = 0, len = node.attributes.length; i < len; i++) {
486
- let attr = node.attributes[i];
487
- attrs[attr.name] = attr.value.replace("\n", "\\n");
499
+ obtainEvent(name) {
500
+ let event = document.createEvent("HTMLEvents");
501
+ event.initEvent(name, true, true);
502
+ return event;
488
503
  }
489
504
 
490
- return this._json.stringify(attrs);
491
- }
505
+ getAttributes(node) {
506
+ let attrs = {};
507
+ for (let i = 0, len = node.attributes.length; i < len; i++) {
508
+ let attr = node.attributes[i];
509
+ attrs[attr.name] = attr.value.replace("\n", "\\n");
510
+ }
492
511
 
493
- getAttribute(node, name) {
494
- if (name == "checked" || name == "selected") {
495
- return node[name];
496
- } else {
497
- return node.getAttribute(name);
512
+ return this._json.stringify(attrs);
513
+ }
514
+
515
+ getAttribute(node, name) {
516
+ if (name == "checked" || name == "selected") {
517
+ return node[name];
518
+ } else {
519
+ return node.getAttribute(name);
520
+ }
498
521
  }
499
- }
500
522
 
501
- value(node) {
502
- if (node.tagName == "SELECT" && node.multiple) {
503
- let result = []
523
+ value(node) {
524
+ if (node.tagName == "SELECT" && node.multiple) {
525
+ let result = []
504
526
 
505
- for (let i = 0, len = node.children.length; i < len; i++) {
506
- let option = node.children[i];
507
- if (option.selected) {
508
- result.push(option.value);
527
+ for (let i = 0, len = node.children.length; i < len; i++) {
528
+ let option = node.children[i];
529
+ if (option.selected) {
530
+ result.push(option.value);
531
+ }
509
532
  }
533
+
534
+ return result;
535
+ } else {
536
+ return node.value;
510
537
  }
538
+ }
511
539
 
512
- return result;
513
- } else {
514
- return node.value;
540
+ deleteText(node) {
541
+ let range = document.createRange();
542
+ range.selectNodeContents(node);
543
+ window.getSelection().removeAllRanges();
544
+ window.getSelection().addRange(range);
545
+ window.getSelection().deleteFromDocument();
515
546
  }
516
- }
517
547
 
518
- deleteText(node) {
519
- let range = document.createRange();
520
- range.selectNodeContents(node);
521
- window.getSelection().removeAllRanges();
522
- window.getSelection().addRange(range);
523
- window.getSelection().deleteFromDocument();
524
- }
548
+ hasFocus(node) {
549
+ return document.activeElement === node;
550
+ }
525
551
 
526
- containsSelection(node) {
527
- let selectedNode = document.getSelection().focusNode;
552
+ containsSelection(node) {
553
+ let selectedNode = document.getSelection().focusNode;
528
554
 
529
- if (!selectedNode) {
530
- return false;
531
- }
555
+ if (!selectedNode) {
556
+ return false;
557
+ }
558
+
559
+ if (selectedNode.nodeType == 3) {
560
+ selectedNode = selectedNode.parentNode;
561
+ }
532
562
 
533
- if (selectedNode.nodeType == 3) {
534
- selectedNode = selectedNode.parentNode;
563
+ return node.contains(selectedNode);
535
564
  }
536
565
 
537
- return node.contains(selectedNode);
538
- }
566
+ dragMousedownTracker() {
567
+ window._cupriteMousedownPrevented = null;
568
+ document.addEventListener('mousedown', ev => {
569
+ window._cupriteMousedownPrevented = ev.defaultPrevented;
570
+ }, { once: true, passive: true });
571
+ }
539
572
 
540
- dragMousedownTracker() {
541
- window._cupriteMousedownPrevented = null;
542
- document.addEventListener('mousedown', ev => {
543
- window._cupriteMousedownPrevented = ev.defaultPrevented;
544
- }, { once: true, passive: true });
545
- }
573
+ legacyDragCheck(node) {
574
+ if ([true, null].indexOf(window._cupriteMousedownPrevented) >= 0) {
575
+ return true;
576
+ }
546
577
 
547
- legacyDragCheck(node) {
548
- if ([true, null].indexOf(window._cupriteMousedownPrevented) >= 0) {
578
+ let el = node;
579
+ do {
580
+ if (el.draggable) return false;
581
+ } while (el = el.parentElement);
549
582
  return true;
550
583
  }
551
584
 
552
- let el = node;
553
- do {
554
- if (el.draggable) return false;
555
- } while (el = el.parentElement);
556
- return true;
557
- }
558
-
559
- // Ported from Capybara's Selenium html5_drag.rb (DROP_STRING).
560
- dropString(strings, target) {
561
- let dt = new DataTransfer();
562
- let opts = { cancelable: true, bubbles: true, dataTransfer: dt };
563
- for (let i = 0; i < strings.length; i++) {
564
- if (dt.items) {
565
- dt.items.add(strings[i]["data"], strings[i]["type"]);
566
- } else {
567
- dt.setData(strings[i]["type"], strings[i]["data"]);
585
+ // Ported from Capybara's Selenium html5_drag.rb (DROP_STRING).
586
+ dropString(strings, target) {
587
+ let dt = new DataTransfer();
588
+ let opts = { cancelable: true, bubbles: true, dataTransfer: dt };
589
+ for (let i = 0; i < strings.length; i++) {
590
+ if (dt.items) {
591
+ dt.items.add(strings[i]["data"], strings[i]["type"]);
592
+ } else {
593
+ dt.setData(strings[i]["type"], strings[i]["data"]);
594
+ }
568
595
  }
596
+ target.dispatchEvent(new DragEvent("drop", opts));
569
597
  }
570
- target.dispatchEvent(new DragEvent("drop", opts));
571
- }
572
598
 
573
- // Ported from Capybara's Selenium html5_drag.rb (ATTACH_FILE).
574
- attachDropInput() {
575
- document.getElementById("_cuprite_drop_file")?.remove();
576
- let input = document.createElement("INPUT");
577
- input.type = "file";
578
- input.id = "_cuprite_drop_file";
579
- input.multiple = true;
580
- document.body.appendChild(input);
581
- }
599
+ // Ported from Capybara's Selenium html5_drag.rb (ATTACH_FILE).
600
+ attachDropInput() {
601
+ document.getElementById("_cuprite_drop_file")?.remove();
602
+ let input = document.createElement("INPUT");
603
+ input.type = "file";
604
+ input.id = "_cuprite_drop_file";
605
+ input.multiple = true;
606
+ document.body.appendChild(input);
607
+ }
582
608
 
583
- // Ported from Capybara's Selenium html5_drag.rb (DROP_FILE).
584
- dropFile(target) {
585
- let input = document.getElementById("_cuprite_drop_file");
586
- let files = input.files;
587
- let dt = new DataTransfer();
588
- let opts = { cancelable: true, bubbles: true, dataTransfer: dt };
589
- input.parentElement.removeChild(input);
590
- if (dt.items) {
591
- for (let i = 0; i < files.length; i++) {
592
- dt.items.add(files[i]);
609
+ // Ported from Capybara's Selenium html5_drag.rb (DROP_FILE).
610
+ dropFile(target) {
611
+ let input = document.getElementById("_cuprite_drop_file");
612
+ let files = input.files;
613
+ let dt = new DataTransfer();
614
+ let opts = { cancelable: true, bubbles: true, dataTransfer: dt };
615
+ input.parentElement.removeChild(input);
616
+ if (dt.items) {
617
+ for (let i = 0; i < files.length; i++) {
618
+ dt.items.add(files[i]);
619
+ }
620
+ } else {
621
+ Object.defineProperty(dt, "files", { value: files, writable: false });
593
622
  }
594
- } else {
595
- Object.defineProperty(dt, "files", { value: files, writable: false });
623
+ target.dispatchEvent(new DragEvent("drop", opts));
596
624
  }
597
- target.dispatchEvent(new DragEvent("drop", opts));
598
- }
599
-
600
- // This command is purely for testing error handling
601
- browserError() {
602
- throw new Error("zomg");
603
625
  }
604
- }
605
626
 
606
- window._cuprite = new Cuprite;
627
+ window._cuprite = new Cuprite;
628
+ })();