UNPKG

5.14 kBJavaScriptView Raw
1const utils = require('./utils');
2const EventTarget = require('./EventTarget');
3
4const nullParent = node => resetParent(null, node);
5
6const removeFromParent = (parentNode, child) => {
7 const cn = parentNode.childNodes;
8 cn.splice(cn.indexOf(child), 1);
9 utils.disconnect(parentNode, child);
10};
11
12const 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
21const 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// interface Node : EventTarget // https://dom.spec.whatwg.org/#node
31class 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 // if populated during constructor discard all content
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
179Object.keys(utils.types).forEach(type => {
180 Node[type] = (Node.prototype[type] = utils.types[type]);
181});
182
183module.exports = Node;
184
185function removeAttribute(attr) {
186 if (attr.name === 'style')
187 this.style.cssText = '';
188 else
189 this.removeAttributeNode(attr);
190}
191
192function removeChild(node) {
193 this.removeChild(node);
194}