UNPKG

25 kBJavaScriptView Raw
1"use strict";
2
3const DocumentFragment = require("./DocumentFragment");
4const DOMException = require("domexception");
5const makeAbsolute = require("./makeAbsolute");
6const vm = require("vm");
7const {EventEmitter} = require("events");
8const {Event} = require("./Events");
9const {getElementsByClassName, getElementsByTagName} = require("./HTMLCollection");
10const {RadioNodeList} = require("./NodeList");
11
12const rwProperties = ["id", "name", "type"];
13const inputElements = ["input", "button", "textarea"];
14
15module.exports = Element;
16
17function 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
845function 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
874function 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
926function 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
958function 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}