1 | const utils = require('./utils');
|
2 | const EventTarget = require('./EventTarget');
|
3 |
|
4 | const nullParent = node => resetParent(null, node);
|
5 |
|
6 | const removeFromParent = (parentNode, child) => {
|
7 | const cn = parentNode.childNodes;
|
8 | cn.splice(cn.indexOf(child), 1);
|
9 | utils.disconnect(parentNode, child);
|
10 | };
|
11 |
|
12 | const resetParent = (parentNode, child) => {
|
13 | if (child.parentNode) {
|
14 | removeFromParent(child.parentNode, child);
|
15 | }
|
16 | if ((child.parentNode = parentNode)) {
|
17 | utils.connect(parentNode, child);
|
18 | }
|
19 | };
|
20 |
|
21 | const stringifiedContent = el => {
|
22 | switch(el.nodeType) {
|
23 | case Node.ELEMENT_NODE:
|
24 | case Node.DOCUMENT_FRAGMENT_NODE: return el.textContent;
|
25 | case Node.TEXT_NODE: return el.data;
|
26 | default: return '';
|
27 | }
|
28 | };
|
29 |
|
30 |
|
31 | class Node extends EventTarget {
|
32 |
|
33 | constructor(ownerDocument) {
|
34 | super();
|
35 | this.ownerDocument = ownerDocument || global.document;
|
36 | this.childNodes = [];
|
37 | }
|
38 |
|
39 | appendChild(node) {
|
40 | if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
41 | node.childNodes.splice(0).forEach(this.appendChild, this);
|
42 | } else {
|
43 | const i = this.childNodes.indexOf(node);
|
44 | if (-1 < i) this.childNodes.splice(i, 1);
|
45 | this.childNodes.push(node);
|
46 | if (i < 0) resetParent(this, node);
|
47 | }
|
48 | return node;
|
49 | }
|
50 |
|
51 | cloneNode(deep) {
|
52 | let node;
|
53 | const document = this.ownerDocument;
|
54 | switch (this.nodeType) {
|
55 | case Node.ATTRIBUTE_NODE:
|
56 | node = document.createAttribute(this.name);
|
57 | node.value = this.value;
|
58 | return node;
|
59 | case Node.TEXT_NODE:
|
60 | return document.createTextNode(this.data);
|
61 | case Node.COMMENT_NODE:
|
62 | return document.createComment(this.data);
|
63 | case Node.ELEMENT_NODE:
|
64 | node = document.createElement(this.nodeName);
|
65 |
|
66 | if (this.nodeName in document.customElements._registry) {
|
67 | node.childNodes.forEach(removeChild, node);
|
68 | node.attributes.forEach(removeAttribute, node);
|
69 | }
|
70 | this.attributes.forEach(a => node.setAttribute(a.name, a.value));
|
71 | case Node.DOCUMENT_FRAGMENT_NODE:
|
72 | if (!node) node = document.createDocumentFragment();
|
73 | if (deep)
|
74 | this.childNodes.forEach(c => node.appendChild(c.cloneNode(deep)));
|
75 | return node;
|
76 | }
|
77 | }
|
78 |
|
79 | hasChildNodes() {
|
80 | return 0 < this.childNodes.length;
|
81 | }
|
82 |
|
83 | insertBefore(node, child) {
|
84 | if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
85 | node.childNodes.splice(0).forEach(node => this.insertBefore(node, child));
|
86 | } else if (node !== child) {
|
87 | const index = this.childNodes.indexOf(node);
|
88 | const swapping = -1 < index;
|
89 | if (swapping) this.childNodes.splice(index, 1);
|
90 | if (child) {
|
91 | this.childNodes.splice(this.childNodes.indexOf(child), 0, node);
|
92 | } else {
|
93 | this.childNodes.push(node);
|
94 | }
|
95 | if (!swapping) resetParent(this, node);
|
96 | }
|
97 | return node;
|
98 | }
|
99 |
|
100 | removeChild(child) {
|
101 | nullParent(child);
|
102 | return child;
|
103 | }
|
104 |
|
105 | replaceChild(node, child) {
|
106 | if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
107 | this.insertBefore(node, child);
|
108 | this.removeChild(child);
|
109 | } else if (node !== child) {
|
110 | const i = this.childNodes.indexOf(child);
|
111 | this.childNodes.splice(i, 0, node);
|
112 | nullParent(child);
|
113 | resetParent(this, node);
|
114 | }
|
115 | return child;
|
116 | }
|
117 |
|
118 | get firstChild() {
|
119 | return this.childNodes[0];
|
120 | }
|
121 |
|
122 | get lastChild() {
|
123 | return this.childNodes[this.childNodes.length - 1];
|
124 | }
|
125 |
|
126 | get nextSibling() {
|
127 | if (this.parentNode) {
|
128 | const cn = this.parentNode.childNodes;
|
129 | return cn[cn.indexOf(this) + 1] || null;
|
130 | }
|
131 | return null;
|
132 | }
|
133 |
|
134 | get previousSibling() {
|
135 | if (this.parentNode) {
|
136 | const cn = this.parentNode.childNodes;
|
137 | return cn[cn.indexOf(this) - 1] || null;
|
138 | }
|
139 | return null;
|
140 | }
|
141 |
|
142 | get textContent() {
|
143 | switch (this.nodeType) {
|
144 | case Node.ELEMENT_NODE:
|
145 | case Node.DOCUMENT_FRAGMENT_NODE:
|
146 | return this.childNodes.map(stringifiedContent).join('');
|
147 | case Node.ATTRIBUTE_NODE:
|
148 | return this.value;
|
149 | case Node.TEXT_NODE:
|
150 | case Node.COMMENT_NODE:
|
151 | return this.data;
|
152 | default: return null;
|
153 | }
|
154 | }
|
155 |
|
156 | set textContent(text) {
|
157 | switch (this.nodeType) {
|
158 | case Node.ELEMENT_NODE:
|
159 | case Node.DOCUMENT_FRAGMENT_NODE:
|
160 | this.childNodes.splice(0).forEach(nullParent);
|
161 | if (text) {
|
162 | const node = this.ownerDocument.createTextNode(text);
|
163 | node.parentNode = this;
|
164 | this.childNodes.push(node);
|
165 | }
|
166 | break;
|
167 | case Node.ATTRIBUTE_NODE:
|
168 | this.value = text;
|
169 | break;
|
170 | case Node.TEXT_NODE:
|
171 | case Node.COMMENT_NODE:
|
172 | this.data = text;
|
173 | break;
|
174 | }
|
175 | }
|
176 |
|
177 | };
|
178 |
|
179 | Object.keys(utils.types).forEach(type => {
|
180 | Node[type] = (Node.prototype[type] = utils.types[type]);
|
181 | });
|
182 |
|
183 | module.exports = Node;
|
184 |
|
185 | function removeAttribute(attr) {
|
186 | if (attr.name === 'style')
|
187 | this.style.cssText = '';
|
188 | else
|
189 | this.removeAttributeNode(attr);
|
190 | }
|
191 |
|
192 | function removeChild(node) {
|
193 | this.removeChild(node);
|
194 | }
|