1 |
|
2 | import { XLINK_NS, SVG_NS, mixin } from './utils'
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | function eventProxy(event) {
|
10 | return event.currentTarget['events'][event.type](event)
|
11 | }
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | const getKey = node => (node ? node.key : null)
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | function setProp(element, prop, newValue, oldValue, isSVG) {
|
31 |
|
32 | if (
|
33 | prop === 'key' ||
|
34 | prop === 'onmount' ||
|
35 | prop === 'onupdate' ||
|
36 | prop === 'onunmount' ||
|
37 | prop === 'onComponentDidMount' ||
|
38 | prop === 'onComponentDidUpdate' ||
|
39 | prop === 'onComponentWillUnmount'
|
40 | ) {
|
41 | return
|
42 | } else if (
|
43 | prop === 'style' &&
|
44 | typeof newValue === 'object' &&
|
45 | !Array.isArray(newValue)
|
46 | ) {
|
47 | for (let i in mixin(oldValue, newValue)) {
|
48 | const style = newValue == null || newValue[i] == null ? '' : newValue[i]
|
49 | if (i[0] === '-') {
|
50 | element[prop].setProperty(i, style)
|
51 | } else {
|
52 | element[prop][i] = style
|
53 | }
|
54 | }
|
55 | } else {
|
56 |
|
57 | prop = prop.toLowerCase()
|
58 | if (prop[0] === 'o' && prop[1] === 'n') {
|
59 | if (!element['events']) element['events'] = {}
|
60 |
|
61 | element['events'][(prop = prop.slice(2))] = newValue
|
62 |
|
63 | if (newValue == null) {
|
64 | element.removeEventListener(prop, eventProxy)
|
65 | } else if (oldValue == null) {
|
66 | element.addEventListener(prop, eventProxy)
|
67 | }
|
68 | }
|
69 |
|
70 |
|
71 | if (prop === 'classname') {
|
72 | prop = 'class'
|
73 | }
|
74 |
|
75 |
|
76 | if (prop === 'dangerouslysetinnerhtml') {
|
77 | element.innerHTML = newValue
|
78 | }
|
79 |
|
80 | if (prop in element && prop !== 'list' && !isSVG) {
|
81 | element[prop] = newValue == (null || 'no') ? '' : newValue
|
82 | } else if (
|
83 | newValue != null &&
|
84 | newValue !== 'null' &&
|
85 | newValue !== 'false' &&
|
86 | newValue !== 'no' &&
|
87 | newValue !== 'off'
|
88 | ) {
|
89 |
|
90 | if (prop === 'xlink-href') {
|
91 | element.setAttributeNS(XLINK_NS, 'href', newValue)
|
92 | element.setAttribute('href', newValue)
|
93 | } else {
|
94 | if (newValue === 'true') newValue = ''
|
95 |
|
96 | if (prop !== 'dangerouslysetinnerhtml') {
|
97 | element.setAttribute(prop, newValue)
|
98 | }
|
99 | }
|
100 | }
|
101 |
|
102 | if (
|
103 | newValue == null ||
|
104 | newValue === 'null' ||
|
105 | newValue === 'undefined' ||
|
106 | newValue === 'false' ||
|
107 | newValue === 'no' ||
|
108 | newValue === 'off'
|
109 | ) {
|
110 | element.removeAttribute(prop)
|
111 | }
|
112 | }
|
113 | }
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 | function createElement(node, isSVG) {
|
122 | let element
|
123 | if (typeof node === 'number') node = node.toString()
|
124 | if (typeof node === 'string') {
|
125 | element = document.createTextNode(node)
|
126 | } else if ((isSVG = isSVG || node.type === 'svg')) {
|
127 | element = document.createElementNS(SVG_NS, node.type)
|
128 | } else {
|
129 | element = document.createElement(node.type)
|
130 | }
|
131 | |
132 |
|
133 |
|
134 | const props = node.props
|
135 | if (props) {
|
136 | for (let i = 0; i < node.children.length; i++) {
|
137 | element.appendChild(createElement(node.children[i], isSVG))
|
138 | }
|
139 |
|
140 | for (let prop in props) {
|
141 | setProp(element, prop, props[prop], null, isSVG)
|
142 | }
|
143 | }
|
144 |
|
145 | return element
|
146 | }
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 | const removeElement = (parent, element, node) => {
|
156 | const remove = function() {
|
157 | parent.removeChild(element)
|
158 | }
|
159 | const callback =
|
160 | (node['props'] && node['props']['onunmount']) ||
|
161 | (node['props'] && node['props']['onComponentWillUnmount'])
|
162 | if (callback != null) {
|
163 | callback(element, remove)
|
164 | } else {
|
165 | remove()
|
166 | }
|
167 | }
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 | function updateElement(element, oldProps, newProps, isSVG) {
|
179 | for (let prop in mixin(oldProps, newProps)) {
|
180 | if (
|
181 | newProps[prop] !==
|
182 | (prop === 'value' || prop === 'checked' ? element[prop] : oldProps[prop])
|
183 | ) {
|
184 | setProp(element, prop, newProps[prop], oldProps[prop], isSVG)
|
185 | }
|
186 | }
|
187 |
|
188 |
|
189 | if (element['mounted'] && newProps && newProps.onComponentDidUpdate) {
|
190 | newProps.onComponentDidUpdate.call(
|
191 | newProps.onComponentDidUpdate,
|
192 | oldProps,
|
193 | newProps,
|
194 | element
|
195 | )
|
196 | }
|
197 | if (element['mounted'] && newProps && newProps.onupdate) {
|
198 | newProps.onupdate.call(newProps.onupdate, oldProps, newProps, element)
|
199 | }
|
200 | }
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | export function patchElement(parent, element, oldVNode, newVNode, isSVG) {
|
212 |
|
213 | if (newVNode === oldVNode) {
|
214 | return
|
215 | } else if (oldVNode == null || oldVNode.type !== newVNode.type) {
|
216 | const newElement = createElement(newVNode, isSVG)
|
217 | if (parent) {
|
218 | parent.insertBefore(newElement, element)
|
219 | if (oldVNode != null) {
|
220 | removeElement(parent, element, oldVNode)
|
221 | }
|
222 | }
|
223 | element = (newElement)
|
224 | } else if (oldVNode.type == null) {
|
225 | element.nodeValue = newVNode
|
226 | } else {
|
227 | updateElement(
|
228 | element,
|
229 | oldVNode.props,
|
230 | newVNode.props,
|
231 | (isSVG = isSVG || newVNode.type === 'svg')
|
232 | )
|
233 |
|
234 | const oldKeyed = {}
|
235 | const newKeyed = {}
|
236 | const oldElements = []
|
237 | const oldChildren = oldVNode.children
|
238 | const children = newVNode.children
|
239 |
|
240 | for (let i = 0; i < oldChildren.length; i++) {
|
241 | oldElements[i] = element.childNodes[i]
|
242 |
|
243 | const oldKey = getKey(oldChildren[i])
|
244 | if (oldKey != null) {
|
245 | oldKeyed[oldKey] = [oldElements[i], oldChildren[i]]
|
246 | }
|
247 | }
|
248 |
|
249 | let i = 0
|
250 | let j = 0
|
251 |
|
252 | while (j < children.length) {
|
253 | let oldKey = getKey(oldChildren[i])
|
254 | let newKey = getKey(children[j])
|
255 |
|
256 | if (newKeyed[oldKey]) {
|
257 | i++
|
258 | continue
|
259 | }
|
260 |
|
261 | if (newKey != null && newKey === getKey(oldChildren[i + 1])) {
|
262 | if (oldKey == null) {
|
263 | removeElement(element, oldElements[i], oldChildren[i])
|
264 | }
|
265 | i++
|
266 | continue
|
267 | }
|
268 |
|
269 | if (newKey == null) {
|
270 | if (oldKey == null) {
|
271 | patchElement(
|
272 | element,
|
273 | (oldElements[i]),
|
274 | oldChildren[i],
|
275 | children[j],
|
276 | isSVG
|
277 | )
|
278 | j++
|
279 | }
|
280 | i++
|
281 | } else {
|
282 | const keyedNode = oldKeyed[newKey] || []
|
283 |
|
284 | if (oldKey === newKey) {
|
285 | patchElement(element, keyedNode[0], keyedNode[1], children[j], isSVG)
|
286 | i++
|
287 | } else if (keyedNode[0]) {
|
288 | patchElement(
|
289 | element,
|
290 | element.insertBefore(keyedNode[0], oldElements[i]),
|
291 | keyedNode[1],
|
292 | children[j],
|
293 | isSVG
|
294 | )
|
295 | } else {
|
296 | patchElement(
|
297 | element,
|
298 | (oldElements[i]),
|
299 | null,
|
300 | children[j],
|
301 | isSVG
|
302 | )
|
303 | }
|
304 |
|
305 | newKeyed[newKey] = children[j]
|
306 | j++
|
307 | }
|
308 | }
|
309 |
|
310 | while (i < oldChildren.length) {
|
311 | if (getKey(oldChildren[i]) == null) {
|
312 | removeElement(element, oldElements[i], oldChildren[i])
|
313 | }
|
314 | i++
|
315 | }
|
316 | for (let k in oldKeyed) {
|
317 | if (!newKeyed[k]) {
|
318 | removeElement(element, oldKeyed[k][0], oldKeyed[k][1])
|
319 | }
|
320 | }
|
321 | }
|
322 | return element
|
323 | }
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 | export function patch(newVNode, element) {
|
332 | if (element) {
|
333 | patchElement(
|
334 | element.parentNode,
|
335 | (element),
|
336 | element && element['vnode'],
|
337 | newVNode
|
338 | )
|
339 | } else {
|
340 | element = patchElement(null, null, null, newVNode)
|
341 | }
|
342 |
|
343 | element['vnode'] = newVNode
|
344 |
|
345 | return element
|
346 | }
|