1 | "use strict";
|
2 |
|
3 | const DocumentFragment = require("./DocumentFragment");
|
4 | const DOMException = require("domexception");
|
5 | const makeAbsolute = require("./makeAbsolute");
|
6 | const vm = require("vm");
|
7 | const {EventEmitter} = require("events");
|
8 | const {Event} = require("./Events");
|
9 | const {getElementsByClassName, getElementsByTagName} = require("./HTMLCollection");
|
10 | const {RadioNodeList} = require("./NodeList");
|
11 |
|
12 | const rwProperties = ["id", "name", "type"];
|
13 | const inputElements = ["input", "button", "textarea"];
|
14 |
|
15 | module.exports = Element;
|
16 |
|
17 | function Element(document, $elm) {
|
18 | const {$, location, _getElement} = document;
|
19 | const tagName = (($elm[0] && $elm[0].name) || "").toLowerCase();
|
20 | const nodeType = $elm[0] && $elm[0].nodeType;
|
21 |
|
22 | let eventTarget;
|
23 |
|
24 | if (nodeType === 3) return Text(document, $elm);
|
25 |
|
26 | const rects = {
|
27 | top: 99999,
|
28 | bottom: 99999,
|
29 | right: 0,
|
30 | left: 0,
|
31 | height: 0,
|
32 | width: 0
|
33 | };
|
34 |
|
35 | rects.bottom = rects.top + rects.height;
|
36 |
|
37 | const emitter = new EventEmitter();
|
38 | emitter.setMaxListeners(0);
|
39 |
|
40 | const classList = getClassList();
|
41 |
|
42 | const element = eventTarget = {
|
43 | $elm,
|
44 | _emitter: emitter,
|
45 | _setBoundingClientRect: setBoundingClientRect,
|
46 | appendChild,
|
47 | classList,
|
48 | click,
|
49 | closest,
|
50 | contains,
|
51 | dispatchEvent,
|
52 | getAttribute,
|
53 | getBoundingClientRect,
|
54 | getElementsByClassName(classNames) {
|
55 | return getElementsByClassName(this, classNames);
|
56 | },
|
57 | getElementsByTagName(name) {
|
58 | return getElementsByTagName(this, name);
|
59 | },
|
60 | hasAttribute,
|
61 | insertAdjacentHTML,
|
62 | insertBefore,
|
63 | matches,
|
64 | remove,
|
65 | removeAttribute,
|
66 | removeChild,
|
67 | requestFullscreen,
|
68 | setAttribute,
|
69 | setElementsToScroll,
|
70 | cloneNode,
|
71 | style: getStyle(),
|
72 | };
|
73 |
|
74 | Object.setPrototypeOf(element, Element.prototype);
|
75 |
|
76 | Object.defineProperty(element, "tagName", {
|
77 | get: () => tagName ? tagName.toUpperCase() : undefined
|
78 | });
|
79 |
|
80 | rwProperties.forEach((p) => {
|
81 | Object.defineProperty(element, p, {
|
82 | get: () => getAttribute(p),
|
83 | set: (value) => setAttribute(p, value)
|
84 | });
|
85 | });
|
86 |
|
87 | Object.defineProperty(element, "nodeType", {
|
88 | enumerable: true,
|
89 | set() {},
|
90 | get() {
|
91 | return nodeType;
|
92 | },
|
93 | });
|
94 |
|
95 | Object.defineProperty(element, "ownerDocument", {
|
96 | get() {
|
97 | return document;
|
98 | }
|
99 | });
|
100 |
|
101 | Object.defineProperty(element, "firstChild", {
|
102 | get: getFirstChild
|
103 | });
|
104 |
|
105 | Object.defineProperty(element, "firstElementChild", {
|
106 | get: getFirstChildElement
|
107 | });
|
108 |
|
109 | Object.defineProperty(element, "lastChild", {
|
110 | get: getLastChild
|
111 | });
|
112 |
|
113 | Object.defineProperty(element, "lastElementChild", {
|
114 | get: getLastChildElement
|
115 | });
|
116 |
|
117 | Object.defineProperty(element, "previousElementSibling", {
|
118 | get: () => _getElement($elm.prev())
|
119 | });
|
120 |
|
121 | Object.defineProperty(element, "nextElementSibling", {
|
122 | get: () => _getElement($elm.next())
|
123 | });
|
124 |
|
125 | Object.defineProperty(element, "children", {
|
126 | get: getChildren
|
127 | });
|
128 |
|
129 | Object.defineProperty(element, "childNodes", {
|
130 | get: getChildNodes
|
131 | });
|
132 |
|
133 | Object.defineProperty(element, "parentElement", {
|
134 | get: () => _getElement($elm.parent())
|
135 | });
|
136 |
|
137 | Object.defineProperty(element, "parentNode", {
|
138 | get: () => _getElement($elm.parent())
|
139 | });
|
140 |
|
141 | Object.defineProperty(element, "innerHTML", {
|
142 | get: () => $elm.html(),
|
143 | set: (value) => {
|
144 | $elm.html(value);
|
145 | if (tagName === "textarea") {
|
146 | element.value = $elm.html();
|
147 | }
|
148 |
|
149 | emitter.emit("_insert");
|
150 | }
|
151 | });
|
152 |
|
153 | Object.defineProperty(element, "innerText", {
|
154 | get: () => element.textContent,
|
155 | set: (value) => {
|
156 | element.textContent = value;
|
157 |
|
158 | if (tagName === "textarea") {
|
159 | element.value = element.textContent;
|
160 | }
|
161 | }
|
162 | });
|
163 |
|
164 | Object.defineProperty(element, "textContent", {
|
165 | get: () => {
|
166 | return tagName === "script" ? $elm.html() : $elm.text();
|
167 | },
|
168 | set: (value) => {
|
169 | const response = tagName === "script" ? $elm.html(value) : $elm.text(value);
|
170 | emitter.emit("_insert");
|
171 | return response;
|
172 | }
|
173 | });
|
174 |
|
175 | Object.defineProperty(element, "outerHTML", {
|
176 | get: () => {
|
177 | return $.html($elm);
|
178 | },
|
179 | set: (value) => {
|
180 | $elm.replaceWith($(value));
|
181 | emitter.emit("_insert");
|
182 | }
|
183 | });
|
184 |
|
185 | Object.defineProperty(element, "href", {
|
186 | get: () => {
|
187 | const rel = getAttribute("href");
|
188 | return makeAbsolute(location, rel);
|
189 | },
|
190 | set: (value) => {
|
191 | setAttribute("href", value);
|
192 | }
|
193 | });
|
194 |
|
195 | Object.defineProperty(element, "src", {
|
196 | get: () => {
|
197 | const rel = getAttribute("src");
|
198 | return makeAbsolute(location, rel);
|
199 | },
|
200 | set: (value) => {
|
201 | setAttribute("src", value);
|
202 | dispatchEvent(new Event("load", {bubbles: true}));
|
203 | }
|
204 | });
|
205 |
|
206 | Object.defineProperty(element, "content", {
|
207 | get: () => {
|
208 | if (tagName !== "template") return;
|
209 | return DocumentFragment(Element, element);
|
210 | }
|
211 | });
|
212 |
|
213 | Object.defineProperty(element, "checked", {
|
214 | get: () => getProperty("checked"),
|
215 | set: (value) => {
|
216 | if ($elm.attr("type") === "radio") radioButtonChecked(value);
|
217 | else if ($elm.attr("type") === "checkbox") checkboxChecked(value);
|
218 | }
|
219 | });
|
220 |
|
221 | Object.defineProperty(element, "options", {
|
222 | get: () => getChildren("option")
|
223 | });
|
224 |
|
225 | Object.defineProperty(element, "selected", {
|
226 | get: () => getProperty("selected"),
|
227 | set: (value) => {
|
228 | const oldValue = getProperty("selected");
|
229 | const $select = $elm.parent("select");
|
230 | if (!$select.attr("multiple")) {
|
231 | if (value) $elm.siblings("option").prop("selected", false);
|
232 | }
|
233 |
|
234 | setProperty("selected", value);
|
235 |
|
236 | if (value !== oldValue) {
|
237 | _getElement($select).dispatchEvent(new Event("change", { bubbles: true }));
|
238 | }
|
239 |
|
240 | return value;
|
241 | }
|
242 | });
|
243 |
|
244 | Object.defineProperty(element, "selectedIndex", {
|
245 | get: () => getChildren("option").findIndex((option) => option.selected)
|
246 | });
|
247 |
|
248 | Object.defineProperty(element, "selectedOptions", {
|
249 | get() {
|
250 | return element.options ? element.options.filter((option) => option.selected) : undefined;
|
251 | }
|
252 | });
|
253 |
|
254 | Object.defineProperty(element, "disabled", {
|
255 | get: () => {
|
256 | const value = getAttribute("disabled");
|
257 | if (value === undefined) {
|
258 | if (!inputElements.includes(tagName)) return;
|
259 | }
|
260 | return value === "disabled";
|
261 | },
|
262 | set: (value) => {
|
263 | if (value === true) return setAttribute("disabled", "disabled");
|
264 | $elm.removeAttr("disabled");
|
265 | }
|
266 | });
|
267 |
|
268 | Object.defineProperty(element, "className", {
|
269 | get: () => $elm.attr("class"),
|
270 | set: (value) => setAttribute("class", value)
|
271 | });
|
272 |
|
273 | Object.defineProperty(element, "form", {
|
274 | get: () => _getElement($elm.closest("form"))
|
275 | });
|
276 |
|
277 | Object.defineProperty(element, "offsetWidth", {
|
278 | get: () => getBoundingClientRect().width
|
279 | });
|
280 |
|
281 | Object.defineProperty(element, "offsetHeight", {
|
282 | get: () => getBoundingClientRect().height
|
283 | });
|
284 |
|
285 | Object.defineProperty(element, "dataset", {
|
286 | get: () => Dataset($elm)
|
287 | });
|
288 |
|
289 | Object.defineProperty(element, "scrollWidth", {
|
290 | get: () => {
|
291 | return element.children.reduce((acc, el) => {
|
292 | acc += el.getBoundingClientRect().width;
|
293 | return acc;
|
294 | }, 0);
|
295 | }
|
296 | });
|
297 |
|
298 | Object.defineProperty(element, "scrollHeight", {
|
299 | get: () => {
|
300 | return element.children.reduce((acc, el) => {
|
301 | acc += el.getBoundingClientRect().height;
|
302 | return acc;
|
303 | }, 0);
|
304 | }
|
305 | });
|
306 |
|
307 | Object.defineProperty(element, "value", {
|
308 | get: () => {
|
309 | if (element.tagName === "SELECT") {
|
310 | const selectedIndex = element.selectedIndex;
|
311 | if (selectedIndex < 0) return "";
|
312 | const option = element.options[selectedIndex];
|
313 | if (option.hasAttribute("value")) {
|
314 | return option.getAttribute("value");
|
315 | }
|
316 | return option.innerText;
|
317 | } else if (element.tagName === "OPTION") {
|
318 | if (element.hasAttribute("value")) {
|
319 | return element.getAttribute("value");
|
320 | }
|
321 | return "";
|
322 | }
|
323 |
|
324 | if (!inputElements.includes(tagName)) return;
|
325 | const value = getAttribute("value");
|
326 | if (value === undefined) return "";
|
327 | return value;
|
328 | },
|
329 | set: (value) => {
|
330 | if (!inputElements.includes(tagName)) return;
|
331 | setAttribute("value", value);
|
332 | }
|
333 | });
|
334 |
|
335 | Object.defineProperty(element, "elements", {
|
336 | get() {
|
337 | if (tagName !== "form") return;
|
338 | return $elm.find("input,button,select,textarea").map(toElement).toArray();
|
339 | }
|
340 | });
|
341 |
|
342 | let currentScrollLeft = 0;
|
343 | Object.defineProperty(element, "scrollLeft", {
|
344 | get: () => currentScrollLeft,
|
345 | set: (value) => {
|
346 | const maxScroll = element.scrollWidth - element.offsetWidth;
|
347 | if (value > maxScroll) value = maxScroll;
|
348 | else if (value < 0) value = 0;
|
349 |
|
350 | onElementScroll(value);
|
351 | currentScrollLeft = value;
|
352 | dispatchEvent(new Event("scroll", { bubbles: true }));
|
353 | }
|
354 | });
|
355 |
|
356 | let currentScrollTop = 0;
|
357 | Object.defineProperty(element, "scrollTop", {
|
358 | get: () => currentScrollTop,
|
359 | set: (value) => {
|
360 | const maxScroll = element.scrollHeight - element.offsetHeight;
|
361 | if (value > maxScroll) value = maxScroll;
|
362 | else if (value < 0) value = 0;
|
363 |
|
364 | onElementScroll(undefined, value);
|
365 | currentScrollTop = value;
|
366 | dispatchEvent(new Event("scroll", { bubbles: true }));
|
367 | }
|
368 | });
|
369 |
|
370 | let elementsToScroll = () => {};
|
371 | function setElementsToScroll(elmsToScrollFn) {
|
372 | elementsToScroll = elmsToScrollFn;
|
373 | }
|
374 |
|
375 | function onElementScroll(scrollLeft, scrollTop) {
|
376 | if (!elementsToScroll) return;
|
377 | const elms = elementsToScroll(document);
|
378 | if (!elms || !elms.length) return;
|
379 |
|
380 | if (scrollLeft !== undefined) onHorizontalScroll(elms, scrollLeft);
|
381 | if (scrollTop !== undefined) onVerticalScroll(elms, scrollTop);
|
382 | }
|
383 |
|
384 | function onHorizontalScroll(elms, scrollLeft) {
|
385 | const delta = currentScrollLeft - scrollLeft;
|
386 |
|
387 | elms.slice().forEach((elm) => {
|
388 | const {left, right} = elm.getBoundingClientRect();
|
389 | elm._setBoundingClientRect({
|
390 | left: (left || 0) + delta,
|
391 | right: (right || 0) + delta
|
392 | });
|
393 | });
|
394 | }
|
395 |
|
396 | function onVerticalScroll(elms, scrollTop) {
|
397 | const delta = currentScrollTop - scrollTop;
|
398 |
|
399 | elms.slice().forEach((elm) => {
|
400 | const {top, bottom} = elm.getBoundingClientRect();
|
401 | elm._setBoundingClientRect({
|
402 | top: (top || 0) + delta,
|
403 | bottom: (bottom || 0) + delta
|
404 | });
|
405 | });
|
406 | }
|
407 |
|
408 | if (tagName === "video") {
|
409 | element.play = () => {
|
410 | return Promise.resolve(undefined);
|
411 | };
|
412 |
|
413 | element.pause = () => {
|
414 | return undefined;
|
415 | };
|
416 |
|
417 | element.load = () => {
|
418 | };
|
419 |
|
420 | element.canPlayType = function canPlayType() {
|
421 | return "maybe";
|
422 | };
|
423 | }
|
424 |
|
425 | Object.assign(element, EventListeners(element));
|
426 |
|
427 | emitter.on("_insert", (...args) => {
|
428 | if (element.parentElement) {
|
429 | element.parentElement._emitter.emit("_insert", ...args);
|
430 | }
|
431 | }).on("_attributeChange", (...args) => {
|
432 | if (element.parentElement) {
|
433 | element.parentElement._emitter.emit("_attributeChange", ...args);
|
434 | }
|
435 | });
|
436 |
|
437 | if (tagName === "form") {
|
438 | element.submit = submit;
|
439 | element.reset = reset;
|
440 |
|
441 | return (eventTarget = addFormFieldProxy(element));
|
442 | }
|
443 |
|
444 | return element;
|
445 |
|
446 | function getFirstChildElement() {
|
447 | const firstChild = find("> :first-child");
|
448 | if (!firstChild.length) return null;
|
449 | return _getElement(firstChild);
|
450 | }
|
451 |
|
452 | function getLastChildElement() {
|
453 | const lastChild = find("> :last-child");
|
454 | if (!lastChild.length) return null;
|
455 | return _getElement(find("> :last-child"));
|
456 | }
|
457 |
|
458 | function getFirstChild() {
|
459 | const child = $elm.contents().first();
|
460 | if (!child || !child[0]) return null;
|
461 | return _getElement(child);
|
462 | }
|
463 |
|
464 | function getLastChild() {
|
465 | const child = $elm.contents().last();
|
466 | if (!child || !child[0]) return null;
|
467 | return _getElement(child);
|
468 | }
|
469 |
|
470 | function appendChild(childElement) {
|
471 | if (childElement instanceof DocumentFragment) {
|
472 | insertAdjacentHTML("beforeend", childElement._getContent());
|
473 | } else if (childElement.$elm) {
|
474 | $elm.append(childElement.$elm);
|
475 |
|
476 | if (childElement.$elm[0].tagName === "script") {
|
477 | vm.runInNewContext(childElement.innerText, document.window);
|
478 | }
|
479 |
|
480 | emitter.emit("_insert");
|
481 | } else if (childElement.textContent) {
|
482 | insertAdjacentHTML("beforeend", childElement.textContent);
|
483 | }
|
484 | }
|
485 |
|
486 | function click() {
|
487 | if (element.disabled) return;
|
488 | const clickEvent = new Event("click", { bubbles: true });
|
489 |
|
490 | let changed = false;
|
491 | if (element.type === "radio") {
|
492 | changed = !element.checked;
|
493 | element.checked = true;
|
494 | }
|
495 |
|
496 | if (element.type === "checkbox") {
|
497 | changed = true;
|
498 | element.checked = !element.checked;
|
499 | }
|
500 |
|
501 | dispatchEvent(clickEvent);
|
502 |
|
503 | if (!clickEvent.defaultPrevented && element.form) {
|
504 | if (changed) {
|
505 | dispatchEvent(new Event("change", { bubbles: true }));
|
506 | } else if (!element.type || element.type === "submit") {
|
507 | const submitEvent = new Event("submit", { bubbles: true });
|
508 | submitEvent._submitElement = element;
|
509 | element.form.dispatchEvent(submitEvent);
|
510 | } else if (element.type === "reset") {
|
511 | element.form.reset();
|
512 | }
|
513 | }
|
514 | }
|
515 |
|
516 | function contains(el) {
|
517 | return $elm === el.$elm || $elm.find(el.$elm).length > 0;
|
518 | }
|
519 |
|
520 | function matches(selector) {
|
521 | try {
|
522 | return $elm.is(selector);
|
523 | } catch (error) {
|
524 | throw new DOMException(`Failed to execute 'matches' on 'Element': '${selector}' is not a valid selector.`, "SyntaxError");
|
525 | }
|
526 | }
|
527 |
|
528 | function dispatchEvent(event) {
|
529 | if (event.cancelBubble) return;
|
530 | event.path.push(eventTarget);
|
531 | if (!event.target) {
|
532 | event.target = eventTarget;
|
533 | }
|
534 | emitter.emit(event.type, event);
|
535 | if (event.bubbles) {
|
536 | if (element.parentElement) return element.parentElement.dispatchEvent(event);
|
537 |
|
538 | if (document && document.firstElementChild === element) {
|
539 | document.dispatchEvent(event);
|
540 | }
|
541 | }
|
542 | }
|
543 |
|
544 | function getAttribute(name) {
|
545 | return $elm.attr(name);
|
546 | }
|
547 |
|
548 | function hasAttribute(name) {
|
549 | return $elm.is(`[${name}]`);
|
550 | }
|
551 |
|
552 | function getProperty(name) {
|
553 | return $elm.prop(name);
|
554 | }
|
555 |
|
556 | function setProperty(name, val) {
|
557 | return $elm.prop(name, val);
|
558 | }
|
559 |
|
560 | function removeChild(child) {
|
561 | if ($elm[0].children.indexOf(child.$elm[0]) === -1) {
|
562 | throw new DOMException("Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.");
|
563 | }
|
564 |
|
565 | child.$elm.remove();
|
566 | emitter.emit("_insert");
|
567 | return child;
|
568 | }
|
569 |
|
570 | function remove() {
|
571 | $elm.remove();
|
572 | }
|
573 |
|
574 | function find(selector) {
|
575 | return $elm.find(selector);
|
576 | }
|
577 |
|
578 | function closest(selector) {
|
579 | return _getElement($elm.closest(selector));
|
580 | }
|
581 |
|
582 | function getChildren(selector) {
|
583 | if (!$elm) return [];
|
584 | return $elm.children(selector).map(toElement).toArray();
|
585 | }
|
586 |
|
587 | function getChildNodes() {
|
588 | return $elm.contents().map(toElement).toArray();
|
589 | }
|
590 |
|
591 | function insertAdjacentHTML(position, markup) {
|
592 | switch (position) {
|
593 | case "beforebegin":
|
594 | $elm.before(markup);
|
595 | if (element.parentElement) element.parentElement._emitter.emit("_insert");
|
596 | break;
|
597 | case "afterbegin":
|
598 | $elm.prepend(markup);
|
599 | emitter.emit("_insert");
|
600 | break;
|
601 | case "beforeend":
|
602 | $elm.append(markup);
|
603 | emitter.emit("_insert");
|
604 | break;
|
605 | case "afterend":
|
606 | $elm.after(markup);
|
607 | if (element.parentElement) element.parentElement._emitter.emit("_insert");
|
608 | break;
|
609 | default:
|
610 | throw new DOMException(`Failed to execute 'insertAdjacentHTML' on 'Element': The value provided (${position}) is not one of 'beforeBegin', 'afterBegin', 'beforeEnd', or 'afterEnd'.`);
|
611 | }
|
612 | }
|
613 |
|
614 | function insertBefore(newNode, referenceNode) {
|
615 | if (referenceNode === null) {
|
616 | element.appendChild(newNode);
|
617 | return newNode;
|
618 | }
|
619 |
|
620 | if (newNode.$elm) {
|
621 | if (referenceNode.parentElement !== element) {
|
622 | throw new DOMException("Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.");
|
623 | }
|
624 | newNode.$elm.insertBefore(referenceNode.$elm);
|
625 | emitter.emit("_insert");
|
626 | return newNode;
|
627 | }
|
628 |
|
629 | if (newNode.textContent) {
|
630 | referenceNode.$elm.before(newNode.textContent);
|
631 | emitter.emit("_insert");
|
632 | return newNode;
|
633 | }
|
634 | }
|
635 |
|
636 | function setBoundingClientRect(axes) {
|
637 | if (!("bottom" in axes)) {
|
638 | axes.bottom = axes.top;
|
639 | }
|
640 |
|
641 | for (const axis in axes) {
|
642 | if (axes.hasOwnProperty(axis)) {
|
643 | rects[axis] = axes[axis];
|
644 | }
|
645 | }
|
646 |
|
647 | rects.height = rects.bottom - rects.top;
|
648 | rects.width = rects.right - rects.left;
|
649 |
|
650 | return rects;
|
651 | }
|
652 |
|
653 | function getBoundingClientRect() {
|
654 | return rects;
|
655 | }
|
656 |
|
657 | function setAttribute(name, val) {
|
658 | $elm.attr(name, val);
|
659 | emitter.emit("_attributeChange", name, element);
|
660 | }
|
661 |
|
662 | function removeAttribute(name) {
|
663 | $elm.removeAttr(name);
|
664 | }
|
665 |
|
666 | function requestFullscreen() {
|
667 | const fullscreenchangeEvent = new Event("fullscreenchange", { bubbles: true });
|
668 | fullscreenchangeEvent.target = element;
|
669 |
|
670 | document.dispatchEvent(fullscreenchangeEvent);
|
671 | }
|
672 |
|
673 | function cloneNode(deep) {
|
674 | const $clone = $elm.clone();
|
675 | if (!deep) {
|
676 | $clone.empty();
|
677 | }
|
678 | return _getElement($clone);
|
679 | }
|
680 |
|
681 | function radioButtonChecked(value) {
|
682 | uncheckRadioButtons();
|
683 | setAttribute("checked", value);
|
684 | }
|
685 |
|
686 | function checkboxChecked(value) {
|
687 | setProperty("checked", value);
|
688 | }
|
689 |
|
690 | function uncheckRadioButtons() {
|
691 | if ($elm.attr("type") !== "radio") return;
|
692 |
|
693 | const name = $elm.attr("name");
|
694 |
|
695 | const $form = $elm.closest("form");
|
696 | if ($form && $form.length) {
|
697 | return $form.find(`input[type="radio"][name="${name}"]`).removeAttr("checked");
|
698 | }
|
699 |
|
700 | $(`input[type="radio"][name="${name}"]`).removeAttr("checked");
|
701 | }
|
702 |
|
703 | function toElement(idx, elm) {
|
704 | return _getElement($(elm));
|
705 | }
|
706 |
|
707 | function submit() {
|
708 | dispatchEvent(new Event("submit", { bubbles: true }));
|
709 | }
|
710 |
|
711 | function reset() {
|
712 | const $inputs = find("input[type='checkbox']");
|
713 | if ($inputs.length) {
|
714 | $inputs.each((idx, elm) => {
|
715 | $(elm).prop("checked", !!$(elm).attr("checked"));
|
716 | });
|
717 | }
|
718 |
|
719 | const $options = find("option");
|
720 | if ($options.length) {
|
721 | $options.each((idx, elm) => {
|
722 | $(elm).prop("selected", !!$(elm).attr("selected"));
|
723 | });
|
724 | }
|
725 |
|
726 | dispatchEvent(new Event("reset", { bubbles: true }));
|
727 | }
|
728 |
|
729 | function getClassList() {
|
730 | if (!$elm.attr) return;
|
731 |
|
732 | const classListApi = {
|
733 | contains(className) {
|
734 | return $elm.hasClass(className);
|
735 | },
|
736 | add(...classNames) {
|
737 | $elm.addClass(classNames.join(" "));
|
738 | emitter.emit("_classadded", ...classNames);
|
739 | emitter.emit("_attributeChange", "class", element);
|
740 | },
|
741 | remove(...classNames) {
|
742 | $elm.removeClass(classNames.join(" "));
|
743 | emitter.emit("_classremoved", ...classNames);
|
744 | emitter.emit("_attributeChange", "class", element);
|
745 | },
|
746 | toggle(className, force) {
|
747 | const hasClass = $elm.hasClass(className);
|
748 |
|
749 | if (force === undefined) {
|
750 | const methodName = this.contains(className) ? "remove" : "add";
|
751 | this[methodName](className);
|
752 | return !hasClass;
|
753 | }
|
754 |
|
755 | if (force) {
|
756 | this.add(className);
|
757 | } else {
|
758 | this.remove(className);
|
759 | }
|
760 | return !hasClass;
|
761 | }
|
762 | };
|
763 |
|
764 | Object.defineProperty(classListApi, "_classes", {
|
765 | get: getClassArray
|
766 | });
|
767 |
|
768 | return classListApi;
|
769 |
|
770 | function getClassArray() {
|
771 | return ($elm.attr("class") || "").split(" ");
|
772 | }
|
773 | }
|
774 |
|
775 | function getStyle() {
|
776 | const elementStyle = getAttribute("style") || "";
|
777 | const prefixNamePattern = /^(-?)(moz|ms|webkit)([A-Z]|\1)/;
|
778 |
|
779 | const Style = {};
|
780 | if (elementStyle) {
|
781 | elementStyle.replace(/\s*(.+?):\s*(.*?)(;|$)/g, (_, name, value) => {
|
782 | let ccName = name.replace(prefixNamePattern, (__, isPrefix, prefix, suffix) => {
|
783 | return prefix + suffix;
|
784 | });
|
785 | ccName = ccName.replace(/-(\w)(\w+)/g, (__, firstLetter, rest) => `${firstLetter.toUpperCase()}${rest}`);
|
786 | Style[ccName] = value;
|
787 | });
|
788 | }
|
789 |
|
790 | Object.defineProperty(Style, "removeProperty", {
|
791 | enumerable: false,
|
792 | value: removeProperty
|
793 | });
|
794 |
|
795 | const StyleHandler = {
|
796 | set: (target, name, value) => {
|
797 | if (!name) return false;
|
798 | target[name] = handleResetValue(value);
|
799 | setStyle();
|
800 | return true;
|
801 | },
|
802 | deleteProperty: (target, name) => {
|
803 | if (!name) return false;
|
804 | delete target[name];
|
805 | setStyle();
|
806 | return true;
|
807 | }
|
808 | };
|
809 |
|
810 | return new Proxy(Style, StyleHandler);
|
811 |
|
812 | function removeProperty(name) {
|
813 | delete Style[name];
|
814 | setStyle();
|
815 | }
|
816 |
|
817 | function setStyle() {
|
818 | const keys = Object.keys(Style);
|
819 | if (!keys.length) return removeAttribute("style");
|
820 | const styleValue = keys.reduce((result, name) => {
|
821 | const value = Style[name];
|
822 | if (value === undefined || value === "") return result;
|
823 |
|
824 | let kcName = name.replace(prefixNamePattern, (__, isPrefix, prefix, suffix) => {
|
825 | return `-${prefix}${suffix}`;
|
826 | });
|
827 |
|
828 | kcName = kcName.replace(/([A-Z])([a-z]+)/g, (__, firstLetter, rest) => `-${firstLetter.toLowerCase()}${rest}`);
|
829 |
|
830 | result += `${kcName}: ${value};`;
|
831 | return result;
|
832 | }, "");
|
833 |
|
834 | if (!styleValue) return removeAttribute("style");
|
835 | setAttribute("style", styleValue);
|
836 | }
|
837 |
|
838 | function handleResetValue(value) {
|
839 | if (value === "" || value === null) return "";
|
840 | return value;
|
841 | }
|
842 | }
|
843 | }
|
844 |
|
845 | function Dataset($elm) {
|
846 | if (!$elm || !$elm[0]) return;
|
847 | return makeProxy(get());
|
848 |
|
849 | function get() {
|
850 | if (!$elm[0].attribs) return {};
|
851 | const {attribs} = $elm[0];
|
852 | return Object.keys(attribs).reduce((acc, key) => {
|
853 | if (key.startsWith("data-")) {
|
854 | acc[key.replace(/^data-/, "").replace(/-(\w)/g, (a, b) => b.toUpperCase())] = attribs[key];
|
855 | }
|
856 | return acc;
|
857 | }, {});
|
858 | }
|
859 |
|
860 | function makeProxy(attributes = {}) {
|
861 | return new Proxy(attributes, {
|
862 | set: setDataset
|
863 | });
|
864 | }
|
865 |
|
866 | function setDataset(_, prop, value) {
|
867 | if (!$elm || !$elm[0] || !$elm[0].attribs) return false;
|
868 | const key = prop.replace(/[A-Z]/g, (g) => `-${g[0].toLowerCase()}`);
|
869 | $elm.attr(`data-${key}`, value);
|
870 | return true;
|
871 | }
|
872 | }
|
873 |
|
874 | function EventListeners(element) {
|
875 | const listeners = [];
|
876 | return {
|
877 | addEventListener,
|
878 | removeEventListener
|
879 | };
|
880 |
|
881 | function addEventListener(name, fn, options) {
|
882 | const config = [name, fn, usesCapture(options), boundFn];
|
883 | const existingListenerIndex = getExistingIndex(...config);
|
884 | if (existingListenerIndex !== -1) return;
|
885 |
|
886 | listeners.push(config);
|
887 | element._emitter.on(name, boundFn);
|
888 |
|
889 | function boundFn(...args) {
|
890 | fn.apply(element, args);
|
891 |
|
892 | if (options && options.once) {
|
893 | removeEventListener(name, fn);
|
894 | }
|
895 | }
|
896 | }
|
897 |
|
898 | function removeEventListener(name, fn, options) {
|
899 | const existingListenerIndex = getExistingIndex(name, fn, usesCapture(options));
|
900 | if (existingListenerIndex === -1) return;
|
901 |
|
902 | const existingListener = listeners[existingListenerIndex];
|
903 | const boundFn = existingListener[3];
|
904 |
|
905 | element._emitter.removeListener(name, boundFn);
|
906 | listeners.splice(existingListenerIndex, 1);
|
907 | }
|
908 |
|
909 | function usesCapture(options) {
|
910 | if (typeof options === "object") {
|
911 | return !!options.capture;
|
912 | }
|
913 |
|
914 | return !!options;
|
915 | }
|
916 |
|
917 | function getExistingIndex(...config) {
|
918 | return listeners.findIndex((listener) => {
|
919 | return listener[0] === config[0]
|
920 | && listener[1] === config[1]
|
921 | && listener[2] === config[2];
|
922 | });
|
923 | }
|
924 | }
|
925 |
|
926 | function Text(document, $elm) {
|
927 | const nodeType = $elm[0].nodeType;
|
928 | const node = {
|
929 | $elm,
|
930 | };
|
931 |
|
932 | Object.setPrototypeOf(node, Text.prototype);
|
933 |
|
934 | Object.defineProperty(node, "ownerDocument", {
|
935 | get() {
|
936 | return document;
|
937 | },
|
938 | });
|
939 |
|
940 | Object.defineProperty(node, "textContent", {
|
941 | enumerable: true,
|
942 | get() {
|
943 | return $elm[0].data;
|
944 | },
|
945 | });
|
946 |
|
947 | Object.defineProperty(node, "nodeType", {
|
948 | enumerable: true,
|
949 | set() {},
|
950 | get() {
|
951 | return nodeType;
|
952 | },
|
953 | });
|
954 |
|
955 | return node;
|
956 | }
|
957 |
|
958 | function addFormFieldProxy(element) {
|
959 | const nameHandler = {
|
960 | has(target, prop) {
|
961 | if (prop in target) return true;
|
962 | const result = Object.getOwnPropertyDescriptor(target, prop);
|
963 | if (result) return true;
|
964 | return !!element.$elm.find(`[name="${prop}"]`).length;
|
965 | },
|
966 | get(target, prop) {
|
967 | if (typeof prop === "symbol") return target[prop];
|
968 | const value = target[prop];
|
969 | if (value !== undefined) return value;
|
970 |
|
971 | const elements = element.$elm.find(`[name="${prop}"]`);
|
972 |
|
973 | if (elements.eq(0).attr("type") === "radio") return new RadioNodeList(element, `[name="${prop}"]`);
|
974 | return element.$elm.find(`[name="${prop}"]`).eq(0);
|
975 | }
|
976 | };
|
977 |
|
978 | return new Proxy(element, nameHandler);
|
979 | }
|