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