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, "selectedOptions", {
|
215 | get() {
|
216 | return element.options ? element.options.filter((option) => option.selected) : undefined;
|
217 | }
|
218 | });
|
219 |
|
220 | Object.defineProperty(element, "disabled", {
|
221 | get: () => {
|
222 | const value = getAttribute("disabled");
|
223 | if (value === undefined) {
|
224 | if (!inputElements.includes(tagName)) return;
|
225 | }
|
226 | return value === "disabled";
|
227 | },
|
228 | set: (value) => {
|
229 | if (value === true) return setAttribute("disabled", "disabled");
|
230 | $elm.removeAttr("disabled");
|
231 | }
|
232 | });
|
233 |
|
234 | Object.defineProperty(element, "className", {
|
235 | get: () => $elm.attr("class"),
|
236 | set: (value) => $elm.attr("class", value)
|
237 | });
|
238 |
|
239 | Object.defineProperty(element, "form", {
|
240 | get: () => _getElement($elm.closest("form"))
|
241 | });
|
242 |
|
243 | Object.defineProperty(element, "offsetWidth", {
|
244 | get: () => getBoundingClientRect().width
|
245 | });
|
246 |
|
247 | Object.defineProperty(element, "offsetHeight", {
|
248 | get: () => getBoundingClientRect().height
|
249 | });
|
250 |
|
251 | Object.defineProperty(element, "dataset", {
|
252 | get: () => Dataset($elm)
|
253 | });
|
254 |
|
255 | Object.defineProperty(element, "nodeType", {
|
256 | get: () => 1
|
257 | });
|
258 |
|
259 | Object.defineProperty(element, "scrollWidth", {
|
260 | get: () => {
|
261 | return element.children.reduce((acc, el) => {
|
262 | acc += el.getBoundingClientRect().width;
|
263 | return acc;
|
264 | }, 0);
|
265 | }
|
266 | });
|
267 |
|
268 | Object.defineProperty(element, "value", {
|
269 | get: () => {
|
270 | if (element.tagName === "SELECT") {
|
271 | const selectedIndex = element.selectedIndex;
|
272 | if (selectedIndex < 0) return "";
|
273 | const option = element.options[selectedIndex];
|
274 | if (option.hasAttribute("value")) {
|
275 | return option.getAttribute("value");
|
276 | }
|
277 | return option.innerText;
|
278 | } else if (element.tagName === "OPTION") {
|
279 | if (element.hasAttribute("value")) {
|
280 | return element.getAttribute("value");
|
281 | }
|
282 | return "";
|
283 | }
|
284 |
|
285 | if (!inputElements.includes(tagName)) return;
|
286 | const value = getAttribute("value");
|
287 | if (value === undefined) return "";
|
288 | return value;
|
289 | },
|
290 | set: (value) => {
|
291 | if (!inputElements.includes(tagName)) return;
|
292 | setAttribute("value", value);
|
293 | }
|
294 | });
|
295 |
|
296 | Object.defineProperty(element, "elements", {
|
297 | get() {
|
298 | if (tagName !== "form") return;
|
299 | return $elm.find("input,button,select,textarea").map(toElement).toArray();
|
300 | }
|
301 | });
|
302 |
|
303 | let currentScrollLeft = 0;
|
304 | Object.defineProperty(element, "scrollLeft", {
|
305 | get: () => currentScrollLeft,
|
306 | set: (value) => {
|
307 | const scrollWidth = element.scrollWidth;
|
308 | if (value > scrollWidth) value = scrollWidth;
|
309 | else if (value < 0) value = 0;
|
310 |
|
311 | onElementScroll(value);
|
312 | currentScrollLeft = value;
|
313 | dispatchEvent(new Event("scroll", { bubbles: true }));
|
314 | }
|
315 | });
|
316 |
|
317 | let elementsToScroll = () => {};
|
318 | function setElementsToScroll(elmsToScrollFn) {
|
319 | elementsToScroll = elmsToScrollFn;
|
320 | }
|
321 |
|
322 | function onElementScroll(scrollLeft) {
|
323 | if (!elementsToScroll) return;
|
324 | const elms = elementsToScroll(document);
|
325 | if (!elms || !elms.length) return;
|
326 |
|
327 | const delta = currentScrollLeft - scrollLeft;
|
328 |
|
329 | elms.slice().forEach((elm) => {
|
330 | const {left, right} = elm.getBoundingClientRect();
|
331 | elm._setBoundingClientRect({
|
332 | left: (left || 0) + delta,
|
333 | right: (right || 0) + delta
|
334 | });
|
335 | });
|
336 | }
|
337 |
|
338 | if (tagName === "form") {
|
339 | element.submit = submit;
|
340 | element.reset = reset;
|
341 | }
|
342 |
|
343 | if (tagName === "video") {
|
344 | element.play = () => {
|
345 | return Promise.resolve(undefined);
|
346 | };
|
347 |
|
348 | element.pause = () => {
|
349 | return undefined;
|
350 | };
|
351 |
|
352 | element.load = () => {
|
353 | };
|
354 |
|
355 | element.canPlayType = function canPlayType() {
|
356 | return "maybe";
|
357 | };
|
358 | }
|
359 |
|
360 | Object.assign(element, EventListeners(element));
|
361 |
|
362 | emitter.on("_insert", () => {
|
363 | if (element.parentElement) {
|
364 | element.parentElement._emitter.emit("_insert");
|
365 | }
|
366 | }).on("_attributeChange", (...args) => {
|
367 | if (element.parentElement) {
|
368 | element.parentElement._emitter.emit("_attributeChange", ...args);
|
369 | }
|
370 | });
|
371 |
|
372 | return element;
|
373 |
|
374 | function getFirstChildElement() {
|
375 | const firstChild = find("> :first-child");
|
376 | if (!firstChild.length) return null;
|
377 | return _getElement(firstChild);
|
378 | }
|
379 |
|
380 | function getLastChildElement() {
|
381 | const lastChild = find("> :last-child");
|
382 | if (!lastChild.length) return null;
|
383 | return _getElement(find("> :last-child"));
|
384 | }
|
385 |
|
386 | function getFirstChild() {
|
387 | const firstChild = $elm[0].children[0];
|
388 | if (!firstChild) return null;
|
389 | if (firstChild.type === "text") return firstChild.data;
|
390 | return getFirstChildElement();
|
391 | }
|
392 |
|
393 | function getLastChild() {
|
394 | const elmChildren = $elm[0].children;
|
395 | if (!elmChildren.length) return null;
|
396 | const lastChild = elmChildren[elmChildren.length - 1];
|
397 | if (lastChild.type === "text") return lastChild.data;
|
398 | return getLastChildElement();
|
399 | }
|
400 |
|
401 | function getElementsByClassName(query) {
|
402 | return find(`.${query}`).map(toElement).toArray();
|
403 | }
|
404 |
|
405 | function getElementsByTagName(query) {
|
406 | return find(`${query}`).map((idx, elm) => _getElement($(elm))).toArray();
|
407 | }
|
408 |
|
409 | function appendChild(childElement) {
|
410 | if (childElement instanceof DocumentFragment) {
|
411 | insertAdjacentHTML("beforeend", childElement._getContent());
|
412 | } else if (childElement.$elm) {
|
413 | $elm.append(childElement.$elm);
|
414 |
|
415 | if (childElement.$elm[0].tagName === "script") {
|
416 | vm.runInNewContext(childElement.innerText, document.window);
|
417 | }
|
418 |
|
419 | emitter.emit("_insert");
|
420 | } else if (childElement.textContent) {
|
421 | insertAdjacentHTML("beforeend", childElement.textContent);
|
422 | }
|
423 | }
|
424 |
|
425 | function click() {
|
426 | if (element.disabled) return;
|
427 | const clickEvent = new Event("click", { bubbles: true });
|
428 |
|
429 | let changed = false;
|
430 | if (element.type === "radio" || element.type === "checkbox") {
|
431 | changed = !element.checked;
|
432 | element.checked = true;
|
433 | }
|
434 |
|
435 | dispatchEvent(clickEvent);
|
436 |
|
437 | if (!clickEvent.defaultPrevented && element.form) {
|
438 | if (changed) {
|
439 | dispatchEvent(new Event("change", { bubbles: true }));
|
440 | } else if (!element.type || element.type === "submit") {
|
441 | const submitEvent = new Event("submit", { bubbles: true });
|
442 | submitEvent._submitElement = element;
|
443 | element.form.dispatchEvent(submitEvent);
|
444 | } else if (element.type === "reset") {
|
445 | element.form.reset();
|
446 | }
|
447 | }
|
448 | }
|
449 |
|
450 | function contains(el) {
|
451 | return $elm === el.$elm || $elm.find(el.$elm).length > 0;
|
452 | }
|
453 |
|
454 | function matches(selector) {
|
455 | try {
|
456 | return $elm.is(selector);
|
457 | } catch (error) {
|
458 | throw new DOMException(`Failed to execute 'matches' on 'Element': '${selector}' is not a valid selector.`, "SyntaxError");
|
459 | }
|
460 | }
|
461 |
|
462 | function dispatchEvent(event) {
|
463 | if (event.cancelBubble) return;
|
464 | event.path.push(element);
|
465 | if (!event.target) {
|
466 | event.target = element;
|
467 | }
|
468 | emitter.emit(event.type, event);
|
469 | if (event.bubbles) {
|
470 | if (element.parentElement) return element.parentElement.dispatchEvent(event);
|
471 |
|
472 | if (document && document.firstElementChild === element) {
|
473 | document.dispatchEvent(event);
|
474 | }
|
475 | }
|
476 | }
|
477 |
|
478 | function getAttribute(name) {
|
479 | return $elm.attr(name);
|
480 | }
|
481 |
|
482 | function hasAttribute(name) {
|
483 | return $elm.is(`[${name}]`);
|
484 | }
|
485 |
|
486 | function getProperty(name) {
|
487 | return $elm.prop(name);
|
488 | }
|
489 |
|
490 | function setProperty(name, val) {
|
491 | return $elm.prop(name, val);
|
492 | }
|
493 |
|
494 | function removeChild(childElement) {
|
495 | emitter.emit("_insert");
|
496 | childElement.$elm.remove();
|
497 | }
|
498 |
|
499 | function remove() {
|
500 | $elm.remove();
|
501 | }
|
502 |
|
503 | function find(selector) {
|
504 | return $elm.find(selector);
|
505 | }
|
506 |
|
507 | function closest(selector) {
|
508 | return _getElement($elm.closest(selector));
|
509 | }
|
510 |
|
511 | function getChildren(selector) {
|
512 | if (!$elm) return [];
|
513 | return $elm.children(selector).map(toElement).toArray();
|
514 | }
|
515 |
|
516 | function insertAdjacentHTML(position, markup) {
|
517 | switch (position) {
|
518 | case "beforebegin":
|
519 | $elm.before(markup);
|
520 | if (element.parentElement) element.parentElement._emitter.emit("_insert");
|
521 | break;
|
522 | case "afterbegin":
|
523 | $elm.prepend(markup);
|
524 | emitter.emit("_insert");
|
525 | break;
|
526 | case "beforeend":
|
527 | $elm.append(markup);
|
528 | emitter.emit("_insert");
|
529 | break;
|
530 | case "afterend":
|
531 | $elm.after(markup);
|
532 | if (element.parentElement) element.parentElement._emitter.emit("_insert");
|
533 | break;
|
534 | default:
|
535 | throw new DOMException(`Failed to execute 'insertAdjacentHTML' on 'Element': The value provided (${position}) is not one of 'beforeBegin', 'afterBegin', 'beforeEnd', or 'afterEnd'.`);
|
536 | }
|
537 | }
|
538 |
|
539 | function insertBefore(newNode, referenceNode) {
|
540 | if (referenceNode === null) {
|
541 | element.appendChild(newNode);
|
542 | return newNode;
|
543 | }
|
544 |
|
545 | if (newNode.$elm) {
|
546 | if (referenceNode.parentElement !== element) {
|
547 | 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.");
|
548 | }
|
549 | newNode.$elm.insertBefore(referenceNode.$elm);
|
550 | emitter.emit("_insert");
|
551 | return newNode;
|
552 | }
|
553 |
|
554 | if (newNode.textContent) {
|
555 | referenceNode.$elm.before(newNode.textContent);
|
556 | emitter.emit("_insert");
|
557 | return newNode;
|
558 | }
|
559 | }
|
560 |
|
561 | function setBoundingClientRect(axes) {
|
562 | if (!("bottom" in axes)) {
|
563 | axes.bottom = axes.top;
|
564 | }
|
565 |
|
566 | for (const axis in axes) {
|
567 | if (axes.hasOwnProperty(axis)) {
|
568 | rects[axis] = axes[axis];
|
569 | }
|
570 | }
|
571 |
|
572 | rects.height = rects.bottom - rects.top;
|
573 | rects.width = rects.right - rects.left;
|
574 |
|
575 | return rects;
|
576 | }
|
577 |
|
578 | function getBoundingClientRect() {
|
579 | return rects;
|
580 | }
|
581 |
|
582 | function setAttribute(name, val) {
|
583 | $elm.attr(name, val);
|
584 | emitter.emit("_attributeChange", name, element);
|
585 | }
|
586 |
|
587 | function removeAttribute(name) {
|
588 | $elm.removeAttr(name);
|
589 | }
|
590 |
|
591 | function requestFullscreen() {
|
592 | const fullscreenchangeEvent = new Event("fullscreenchange", { bubbles: true });
|
593 | fullscreenchangeEvent.target = element;
|
594 |
|
595 | document.dispatchEvent(fullscreenchangeEvent);
|
596 | }
|
597 |
|
598 | function cloneNode(deep) {
|
599 | const $clone = $elm.clone();
|
600 | if (!deep) {
|
601 | $clone.empty();
|
602 | }
|
603 | return _getElement($clone);
|
604 | }
|
605 |
|
606 | function radioButtonChecked(value) {
|
607 | uncheckRadioButtons();
|
608 | setAttribute("checked", value);
|
609 | }
|
610 |
|
611 | function checkboxChecked(value) {
|
612 | setProperty("checked", value);
|
613 | }
|
614 |
|
615 | function uncheckRadioButtons() {
|
616 | if ($elm.attr("type") !== "radio") return;
|
617 |
|
618 | const name = $elm.attr("name");
|
619 |
|
620 | const $form = $elm.closest("form");
|
621 | if ($form && $form.length) {
|
622 | return $form.find(`input[type="radio"][name="${name}"]`).removeAttr("checked");
|
623 | }
|
624 |
|
625 | $(`input[type="radio"][name="${name}"]`).removeAttr("checked");
|
626 | }
|
627 |
|
628 | function toElement(idx, elm) {
|
629 | return _getElement($(elm));
|
630 | }
|
631 |
|
632 | function submit() {
|
633 | dispatchEvent(new Event("submit", { bubbles: true }));
|
634 | }
|
635 |
|
636 | function reset() {
|
637 | const $inputs = find("input[type='checkbox']");
|
638 | if ($inputs.length) {
|
639 | $inputs.each((idx, elm) => {
|
640 | $(elm).prop("checked", !!$(elm).attr("checked"));
|
641 | });
|
642 | }
|
643 |
|
644 | const $options = find("option");
|
645 | if ($options.length) {
|
646 | $options.each((idx, elm) => {
|
647 | $(elm).prop("selected", !!$(elm).attr("selected"));
|
648 | });
|
649 | }
|
650 |
|
651 | dispatchEvent(new Event("reset", { bubbles: true }));
|
652 | }
|
653 |
|
654 | function getClassList() {
|
655 | if (!$elm.attr) return;
|
656 |
|
657 | const classListApi = {
|
658 | contains(className) {
|
659 | return $elm.hasClass(className);
|
660 | },
|
661 | add(...classNames) {
|
662 | $elm.addClass(classNames.join(" "));
|
663 | emitter.emit("_classadded", ...classNames);
|
664 | emitter.emit("_attributeChange", "class", element);
|
665 | },
|
666 | remove(...classNames) {
|
667 | $elm.removeClass(classNames.join(" "));
|
668 | emitter.emit("_classremoved", ...classNames);
|
669 | emitter.emit("_attributeChange", "class", element);
|
670 | },
|
671 | toggle(className, force) {
|
672 | const hasClass = $elm.hasClass(className);
|
673 |
|
674 | if (force === undefined) {
|
675 | const methodName = this.contains(className) ? "remove" : "add";
|
676 | this[methodName](className);
|
677 | return !hasClass;
|
678 | }
|
679 |
|
680 | if (force) {
|
681 | this.add(className);
|
682 | } else {
|
683 | this.remove(className);
|
684 | }
|
685 | return !hasClass;
|
686 | }
|
687 | };
|
688 |
|
689 | Object.defineProperty(classListApi, "_classes", {
|
690 | get: getClassArray
|
691 | });
|
692 |
|
693 | return classListApi;
|
694 |
|
695 | function getClassArray() {
|
696 | return ($elm.attr("class") || "").split(" ");
|
697 | }
|
698 | }
|
699 |
|
700 | function getStyle() {
|
701 | const elementStyle = getAttribute("style") || "";
|
702 | const prefixNamePattern = /^(-?)(moz|ms|webkit)([A-Z]|\1)/;
|
703 |
|
704 | const Style = {};
|
705 | if (elementStyle) {
|
706 | elementStyle.replace(/\s*(.+?):\s*(.*?)(;|$)/g, (_, name, value) => {
|
707 | let ccName = name.replace(prefixNamePattern, (__, isPrefix, prefix, suffix) => {
|
708 | return prefix + suffix;
|
709 | });
|
710 | ccName = ccName.replace(/-(\w)(\w+)/g, (__, firstLetter, rest) => `${firstLetter.toUpperCase()}${rest}`);
|
711 | Style[ccName] = value;
|
712 | });
|
713 | }
|
714 |
|
715 | Object.defineProperty(Style, "removeProperty", {
|
716 | enumerable: false,
|
717 | value: removeProperty
|
718 | });
|
719 |
|
720 | const StyleHandler = {
|
721 | set: (target, name, value) => {
|
722 | if (!name) return false;
|
723 | target[name] = value;
|
724 | setStyle();
|
725 | return true;
|
726 | },
|
727 | deleteProperty: (target, name) => {
|
728 | if (!name) return false;
|
729 | delete target[name];
|
730 | setStyle();
|
731 | return true;
|
732 | }
|
733 | };
|
734 |
|
735 | return new Proxy(Style, StyleHandler);
|
736 |
|
737 | function removeProperty(name) {
|
738 | delete Style[name];
|
739 | setStyle();
|
740 | }
|
741 |
|
742 | function setStyle() {
|
743 | const keys = Object.keys(Style);
|
744 | if (!keys.length) return removeAttribute("style");
|
745 | const styleValue = keys.reduce((result, name) => {
|
746 | const value = Style[name];
|
747 | if (value === undefined || value === "") return result;
|
748 |
|
749 | let kcName = name.replace(prefixNamePattern, (__, isPrefix, prefix, suffix) => {
|
750 | return `-${prefix}${suffix}`;
|
751 | });
|
752 |
|
753 | kcName = kcName.replace(/([A-Z])([a-z]+)/g, (__, firstLetter, rest) => `-${firstLetter.toLowerCase()}${rest}`);
|
754 |
|
755 | result += `${kcName}: ${value};`;
|
756 | return result;
|
757 | }, "");
|
758 |
|
759 | if (!styleValue) return removeAttribute("style");
|
760 | setAttribute("style", styleValue);
|
761 | }
|
762 | }
|
763 | }
|
764 |
|
765 | function Dataset($elm) {
|
766 | if (!$elm || !$elm[0]) return;
|
767 | return makeProxy(get());
|
768 |
|
769 | function get() {
|
770 | if (!$elm[0].attribs) return {};
|
771 | const {attribs} = $elm[0];
|
772 | return Object.keys(attribs).reduce((acc, key) => {
|
773 | if (key.startsWith("data-")) {
|
774 | acc[key.replace(/^data-/, "").replace(/-(\w)/g, (a, b) => b.toUpperCase())] = attribs[key];
|
775 | }
|
776 | return acc;
|
777 | }, {});
|
778 | }
|
779 |
|
780 | function makeProxy(attributes = {}) {
|
781 | return new Proxy(attributes, {
|
782 | set: setDataset
|
783 | });
|
784 | }
|
785 |
|
786 | function setDataset(_, prop, value) {
|
787 | if (!$elm || !$elm[0] || !$elm[0].attribs) return false;
|
788 | const key = prop.replace(/[A-Z]/g, (g) => `-${g[0].toLowerCase()}`);
|
789 | $elm.attr(`data-${key}`, value);
|
790 | return true;
|
791 | }
|
792 | }
|
793 |
|
794 | function EventListeners(element) {
|
795 | const listeners = [];
|
796 | return {
|
797 | addEventListener,
|
798 | removeEventListener
|
799 | };
|
800 |
|
801 | function addEventListener(name, fn, options) {
|
802 | const config = [name, fn, usesCapture(options), boundFn];
|
803 | const existingListenerIndex = getExistingIndex(...config);
|
804 | if (existingListenerIndex !== -1) return;
|
805 |
|
806 | listeners.push(config);
|
807 | element._emitter.on(name, boundFn);
|
808 |
|
809 | function boundFn(...args) {
|
810 | fn.apply(element, args);
|
811 |
|
812 | if (options && options.once) {
|
813 | removeEventListener(name, fn);
|
814 | }
|
815 | }
|
816 | }
|
817 |
|
818 | function removeEventListener(name, fn, options) {
|
819 | const existingListenerIndex = getExistingIndex(name, fn, usesCapture(options));
|
820 | if (existingListenerIndex === -1) return;
|
821 |
|
822 | const existingListener = listeners[existingListenerIndex];
|
823 | const boundFn = existingListener[3];
|
824 |
|
825 | element._emitter.removeListener(name, boundFn);
|
826 | listeners.splice(existingListenerIndex, 1);
|
827 | }
|
828 |
|
829 | function usesCapture(options) {
|
830 | if (typeof options === "object") {
|
831 | return !!options.capture;
|
832 | }
|
833 |
|
834 | return !!options;
|
835 | }
|
836 |
|
837 | function getExistingIndex(...config) {
|
838 | return listeners.findIndex((listener) => {
|
839 | return listener[0] === config[0]
|
840 | && listener[1] === config[1]
|
841 | && listener[2] === config[2];
|
842 | });
|
843 | }
|
844 | }
|