UNPKG

11.7 kBJavaScriptView Raw
1var RECYCLED_NODE = 1
2var LAZY_NODE = 2
3var TEXT_NODE = 3
4var EMPTY_OBJ = {}
5var EMPTY_ARR = []
6var map = EMPTY_ARR.map
7var isArray = Array.isArray
8var defer =
9 typeof requestAnimationFrame !== "undefined"
10 ? requestAnimationFrame
11 : setTimeout
12
13var createClass = function(obj) {
14 var out = ""
15
16 if (typeof obj === "string") return obj
17
18 if (isArray(obj) && obj.length > 0) {
19 for (var k = 0, tmp; k < obj.length; k++) {
20 if ((tmp = createClass(obj[k])) !== "") {
21 out += (out && " ") + tmp
22 }
23 }
24 } else {
25 for (var k in obj) {
26 if (obj[k]) {
27 out += (out && " ") + k
28 }
29 }
30 }
31
32 return out
33}
34
35var merge = function(a, b) {
36 var out = {}
37
38 for (var k in a) out[k] = a[k]
39 for (var k in b) out[k] = b[k]
40
41 return out
42}
43
44var batch = function(list) {
45 return list.reduce(function(out, item) {
46 return out.concat(
47 !item || item === true
48 ? 0
49 : typeof item[0] === "function"
50 ? [item]
51 : batch(item)
52 )
53 }, EMPTY_ARR)
54}
55
56var isSameAction = function(a, b) {
57 return isArray(a) && isArray(b) && a[0] === b[0] && typeof a[0] === "function"
58}
59
60var shouldRestart = function(a, b) {
61 if (a !== b) {
62 for (var k in merge(a, b)) {
63 if (a[k] !== b[k] && !isSameAction(a[k], b[k])) return true
64 b[k] = a[k]
65 }
66 }
67}
68
69var patchSubs = function(oldSubs, newSubs, dispatch) {
70 for (
71 var i = 0, oldSub, newSub, subs = [];
72 i < oldSubs.length || i < newSubs.length;
73 i++
74 ) {
75 oldSub = oldSubs[i]
76 newSub = newSubs[i]
77 subs.push(
78 newSub
79 ? !oldSub ||
80 newSub[0] !== oldSub[0] ||
81 shouldRestart(newSub[1], oldSub[1])
82 ? [
83 newSub[0],
84 newSub[1],
85 newSub[0](dispatch, newSub[1]),
86 oldSub && oldSub[2]()
87 ]
88 : oldSub
89 : oldSub && oldSub[2]()
90 )
91 }
92 return subs
93}
94
95var patchProperty = function(node, key, oldValue, newValue, listener, isSvg) {
96 if (key === "key") {
97 } else if (key === "style") {
98 for (var k in merge(oldValue, newValue)) {
99 oldValue = newValue == null || newValue[k] == null ? "" : newValue[k]
100 if (k[0] === "-") {
101 node[key].setProperty(k, oldValue)
102 } else {
103 node[key][k] = oldValue
104 }
105 }
106 } else if (key[0] === "o" && key[1] === "n") {
107 if (
108 !((node.actions || (node.actions = {}))[
109 (key = key.slice(2).toLowerCase())
110 ] = newValue)
111 ) {
112 node.removeEventListener(key, listener)
113 } else if (!oldValue) {
114 node.addEventListener(key, listener)
115 }
116 } else if (!isSvg && key !== "list" && key in node) {
117 node[key] = newValue == null ? "" : newValue
118 } else if (
119 newValue == null ||
120 newValue === false ||
121 (key === "class" && !(newValue = createClass(newValue)))
122 ) {
123 node.removeAttribute(key)
124 } else {
125 node.setAttribute(key, newValue)
126 }
127}
128
129var createNode = function(vdom, listener, isSvg) {
130 var ns = "http://www.w3.org/2000/svg"
131 var props = vdom.props
132 var node =
133 vdom.type === TEXT_NODE
134 ? document.createTextNode(vdom.name)
135 : (isSvg = isSvg || vdom.name === "svg")
136 ? document.createElementNS(ns, vdom.name, { is: props.is })
137 : document.createElement(vdom.name, { is: props.is })
138
139 for (var k in props) {
140 patchProperty(node, k, null, props[k], listener, isSvg)
141 }
142
143 for (var i = 0, len = vdom.children.length; i < len; i++) {
144 node.appendChild(
145 createNode(
146 (vdom.children[i] = getVNode(vdom.children[i])),
147 listener,
148 isSvg
149 )
150 )
151 }
152
153 return (vdom.node = node)
154}
155
156var getKey = function(vdom) {
157 return vdom == null ? null : vdom.key
158}
159
160var patch = function(parent, node, oldVNode, newVNode, listener, isSvg) {
161 if (oldVNode === newVNode) {
162 } else if (
163 oldVNode != null &&
164 oldVNode.type === TEXT_NODE &&
165 newVNode.type === TEXT_NODE
166 ) {
167 if (oldVNode.name !== newVNode.name) node.nodeValue = newVNode.name
168 } else if (oldVNode == null || oldVNode.name !== newVNode.name) {
169 node = parent.insertBefore(
170 createNode((newVNode = getVNode(newVNode)), listener, isSvg),
171 node
172 )
173 if (oldVNode != null) {
174 parent.removeChild(oldVNode.node)
175 }
176 } else {
177 var tmpVKid
178 var oldVKid
179
180 var oldKey
181 var newKey
182
183 var oldVProps = oldVNode.props
184 var newVProps = newVNode.props
185
186 var oldVKids = oldVNode.children
187 var newVKids = newVNode.children
188
189 var oldHead = 0
190 var newHead = 0
191 var oldTail = oldVKids.length - 1
192 var newTail = newVKids.length - 1
193
194 isSvg = isSvg || newVNode.name === "svg"
195
196 for (var i in merge(oldVProps, newVProps)) {
197 if (
198 (i === "value" || i === "selected" || i === "checked"
199 ? node[i]
200 : oldVProps[i]) !== newVProps[i]
201 ) {
202 patchProperty(node, i, oldVProps[i], newVProps[i], listener, isSvg)
203 }
204 }
205
206 while (newHead <= newTail && oldHead <= oldTail) {
207 if (
208 (oldKey = getKey(oldVKids[oldHead])) == null ||
209 oldKey !== getKey(newVKids[newHead])
210 ) {
211 break
212 }
213
214 patch(
215 node,
216 oldVKids[oldHead].node,
217 oldVKids[oldHead],
218 (newVKids[newHead] = getVNode(
219 newVKids[newHead++],
220 oldVKids[oldHead++]
221 )),
222 listener,
223 isSvg
224 )
225 }
226
227 while (newHead <= newTail && oldHead <= oldTail) {
228 if (
229 (oldKey = getKey(oldVKids[oldTail])) == null ||
230 oldKey !== getKey(newVKids[newTail])
231 ) {
232 break
233 }
234
235 patch(
236 node,
237 oldVKids[oldTail].node,
238 oldVKids[oldTail],
239 (newVKids[newTail] = getVNode(
240 newVKids[newTail--],
241 oldVKids[oldTail--]
242 )),
243 listener,
244 isSvg
245 )
246 }
247
248 if (oldHead > oldTail) {
249 while (newHead <= newTail) {
250 node.insertBefore(
251 createNode(
252 (newVKids[newHead] = getVNode(newVKids[newHead++])),
253 listener,
254 isSvg
255 ),
256 (oldVKid = oldVKids[oldHead]) && oldVKid.node
257 )
258 }
259 } else if (newHead > newTail) {
260 while (oldHead <= oldTail) {
261 node.removeChild(oldVKids[oldHead++].node)
262 }
263 } else {
264 for (var i = oldHead, keyed = {}, newKeyed = {}; i <= oldTail; i++) {
265 if ((oldKey = oldVKids[i].key) != null) {
266 keyed[oldKey] = oldVKids[i]
267 }
268 }
269
270 while (newHead <= newTail) {
271 oldKey = getKey((oldVKid = oldVKids[oldHead]))
272 newKey = getKey(
273 (newVKids[newHead] = getVNode(newVKids[newHead], oldVKid))
274 )
275
276 if (
277 newKeyed[oldKey] ||
278 (newKey != null && newKey === getKey(oldVKids[oldHead + 1]))
279 ) {
280 if (oldKey == null) {
281 node.removeChild(oldVKid.node)
282 }
283 oldHead++
284 continue
285 }
286
287 if (newKey == null || oldVNode.type === RECYCLED_NODE) {
288 if (oldKey == null) {
289 patch(
290 node,
291 oldVKid && oldVKid.node,
292 oldVKid,
293 newVKids[newHead],
294 listener,
295 isSvg
296 )
297 newHead++
298 }
299 oldHead++
300 } else {
301 if (oldKey === newKey) {
302 patch(
303 node,
304 oldVKid.node,
305 oldVKid,
306 newVKids[newHead],
307 listener,
308 isSvg
309 )
310 newKeyed[newKey] = true
311 oldHead++
312 } else {
313 if ((tmpVKid = keyed[newKey]) != null) {
314 patch(
315 node,
316 node.insertBefore(tmpVKid.node, oldVKid && oldVKid.node),
317 tmpVKid,
318 newVKids[newHead],
319 listener,
320 isSvg
321 )
322 newKeyed[newKey] = true
323 } else {
324 patch(
325 node,
326 oldVKid && oldVKid.node,
327 null,
328 newVKids[newHead],
329 listener,
330 isSvg
331 )
332 }
333 }
334 newHead++
335 }
336 }
337
338 while (oldHead <= oldTail) {
339 if (getKey((oldVKid = oldVKids[oldHead++])) == null) {
340 node.removeChild(oldVKid.node)
341 }
342 }
343
344 for (var i in keyed) {
345 if (newKeyed[i] == null) {
346 node.removeChild(keyed[i].node)
347 }
348 }
349 }
350 }
351
352 return (newVNode.node = node)
353}
354
355var propsChanged = function(a, b) {
356 for (var k in a) if (a[k] !== b[k]) return true
357 for (var k in b) if (a[k] !== b[k]) return true
358}
359
360var getTextVNode = function(node) {
361 return typeof node === "object" ? node : createTextVNode(node)
362}
363
364var getVNode = function(newVNode, oldVNode) {
365 return newVNode.type === LAZY_NODE
366 ? ((!oldVNode ||
367 (oldVNode.type !== LAZY_NODE ||
368 propsChanged(oldVNode.lazy, newVNode.lazy))) &&
369 ((oldVNode = getTextVNode(newVNode.lazy.view(newVNode.lazy))).lazy =
370 newVNode.lazy),
371 oldVNode)
372 : newVNode
373}
374
375var createVNode = function(name, props, children, node, key, type) {
376 return {
377 name: name,
378 props: props,
379 children: children,
380 node: node,
381 type: type,
382 key: key
383 }
384}
385
386var createTextVNode = function(value, node) {
387 return createVNode(value, EMPTY_OBJ, EMPTY_ARR, node, undefined, TEXT_NODE)
388}
389
390var recycleNode = function(node) {
391 return node.nodeType === TEXT_NODE
392 ? createTextVNode(node.nodeValue, node)
393 : createVNode(
394 node.nodeName.toLowerCase(),
395 EMPTY_OBJ,
396 map.call(node.childNodes, recycleNode),
397 node,
398 undefined,
399 RECYCLED_NODE
400 )
401}
402
403export var Lazy = function(props) {
404 return {
405 lazy: props,
406 type: LAZY_NODE
407 }
408}
409
410export var h = function(name, props) {
411 for (var vdom, rest = [], children = [], i = arguments.length; i-- > 2; ) {
412 rest.push(arguments[i])
413 }
414
415 while (rest.length > 0) {
416 if (isArray((vdom = rest.pop()))) {
417 for (var i = vdom.length; i-- > 0; ) {
418 rest.push(vdom[i])
419 }
420 } else if (vdom === false || vdom === true || vdom == null) {
421 } else {
422 children.push(getTextVNode(vdom))
423 }
424 }
425
426 props = props || EMPTY_OBJ
427
428 return typeof name === "function"
429 ? name(props, children)
430 : createVNode(name, props, children, undefined, props.key)
431}
432
433export var app = function(props) {
434 var state = {}
435 var lock = false
436 var view = props.view
437 var node = props.node
438 var vdom = node && recycleNode(node)
439 var subscriptions = props.subscriptions
440 var subs = []
441
442 var listener = function(event) {
443 dispatch(this.actions[event.type], event)
444 }
445
446 var setState = function(newState) {
447 if (state !== newState) {
448 state = newState
449 if (subscriptions) {
450 subs = patchSubs(subs, batch([subscriptions(state)]), dispatch)
451 }
452 if (view && !lock) defer(render, (lock = true))
453 }
454 return state
455 }
456
457 var dispatch = (props.middleware ||
458 function(obj) {
459 return obj
460 })(function(action, props) {
461 return typeof action === "function"
462 ? dispatch(action(state, props))
463 : isArray(action)
464 ? typeof action[0] === "function" || isArray(action[0])
465 ? dispatch(
466 action[0],
467 typeof action[1] === "function" ? action[1](props) : action[1]
468 )
469 : (batch(action.slice(1)).map(function(fx) {
470 fx && fx[0](dispatch, fx[1])
471 }, setState(action[0])),
472 state)
473 : setState(action)
474 })
475
476 var render = function() {
477 lock = false
478 node = patch(
479 node.parentNode,
480 node,
481 vdom,
482 (vdom = getTextVNode(view(state))),
483 listener
484 )
485 }
486
487 dispatch(props.init)
488}