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