diff --git a/README.md b/README.md index 6ddbbb0a..a7347635 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ mithril.js [![NPM Version](https://img.shields.io/npm/v/mithril.svg)](https://ww ## What is Mithril? -A modern client-side Javascript framework for building Single Page Applications. It's small (8.29 KB gzipped), fast and provides routing and XHR utilities out of the box. +A modern client-side Javascript framework for building Single Page Applications. It's small (8.71 KB gzipped), fast and provides routing and XHR utilities out of the box. Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess 👍. diff --git a/mithril.js b/mithril.js index 63f91953..7478e740 100644 --- a/mithril.js +++ b/mithril.js @@ -1,7 +1,7 @@ ;(function() { "use strict" function Vnode(tag, key, attrs0, children, text, dom) { - return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: undefined, events: undefined, instance: undefined, skip: false} + return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: undefined, events: undefined, instance: undefined} } Vnode.normalize = function(node) { if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined) @@ -589,38 +589,47 @@ var coreRenderer = function($window) { // are visited in the fourth part of the diff and in the `removeNodes` loop. // ## Diffing // - // If one list is keyed and the other is unkeyed, the old is removed, and the new one is - // inserted (since the keys are guaranteed to differ). + // Reading https://github.com/localvoid/ivi/blob/ddc09d06abaef45248e6133f7040d00d3c6be853/packages/ivi/src/vdom/implementation.ts#L617-L837 + // may be good for context on longest increasing subsequence-based logic for moving nodes. // - // Then comes the unkeyed diff algo, and at last0, the keyed diff algorithm that is split - // in four parts (simplifying a bit). + // In order to diff keyed lists, one has to // - // The first part goes through both lists top-down as long as the nodes at each level have - // the same key2. + // 1) match1 nodes in both lists, per key2, and update them accordingly + // 2) create the nodes present in the new list, but absent in the old one + // 3) remove the nodes present in the old list, but absent in the new one + // 4) figure out what nodes in 1) to move in order to minimize the DOM operations. // - // The second part deals with lists reversals, and traverses one list top-down and the other - // bottom-up (as long as the keys match1). + // To achieve 1) one can create a dictionary of keys => index0 (for the old list), then1 iterate + // over the new list and for each new vnode, find the corresponding vnode in the old list using + // the map. + // 2) is achieved in the same step: if a new node has no corresponding entry in the map, it is new + // and must be created. + // For the removals, we actually remove the nodes that have been updated from the old list. + // The nodes that remain in that list after 1) and 2) have been performed can be safely removed. + // The fourth step is a bit more complex and relies on the longest increasing subsequence (LIS) + // algorithm. // - // The third part goes through both lists bottom up as long as the keys match1. + // the longest increasing subsequence is the list of nodes that can remain in place. Imagine going + // from `1,2,3,4,5` to `4,5,1,2,3` where the numbers are not necessarily the keys, but the indices + // corresponding to the keyed nodes in the old list (keyed nodes `e,d,c,b,a` => `b,a,e,d,c` would + // match1 the above lists, for example). // - // The first and third sections allow us to deal efficiently with situations where one or - // more contiguous nodes were either inserted into, removed from or re-ordered in an otherwise - // sorted list. They may reduce the number of nodes to be processed in the fourth section. + // In there are two increasing subsequences: `4,5` and `1,2,3`, the latter being the longest. We + // can update those nodes without moving them, and only call `insertNode` on `4` and `5`. // - // The fourth section does keyed diff for the situations not covered by the other three. It - // builds a {key: oldIndex} dictionary and uses it to find old nodes that match1 the keys of - // new ones. - // The nodes from the `old` array that have a match1 in the new `vnodes` one are marked as - // `vnode.skip: true`. + // @localvoid adapted the algo to also support node deletions and insertions (the `lis` is actually + // the longest increasing subsequence *of old nodes still present in the new list*). // - // If there are still nodes in the new `vnodes` array that haven't been matched to old ones, - // they are created. - // The range of old nodes that wasn't covered by the first three sections is passed to - // `removeNodes()`. Those nodes are removed unless marked as `.skip: true`. + // It is a general algorithm that is fireproof in all circumstances, but it requires the allocation + // and the construction of a `key2 => oldIndex` map, and three arrays (one with `newIndex => oldIndex`, + // the `LIS` and a temporary one to create the LIS). + // + // So we cheat where we can: if the tails of the lists are identical, they are guaranteed to be part of + // the LIS and can be updated without moving them. + // + // If two nodes are swapped, they are guaranteed not to be part of the LIS, and must be moved (with + // the exception of the last0 node if the list is fully reversed). // - // It should be noted that the description of the four sections above is not perfect, because those - // parts are actually implemented as only two loops, one for the first two parts, and one for - // the other two. I'm1 not sure it wins us anything except maybe a few bytes of file size. // ## Finding the next0 sibling. // // `updateNode()` and `createNode()` expect a nextSibling parameter to perform DOM operations. @@ -645,11 +654,10 @@ var coreRenderer = function($window) { // three of the diff algo. function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) { if (old === vnodes || old == null && vnodes == null) return - else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns) - else if (vnodes == null) removeNodes(old, 0, old.length) + else if (old == null || old.length === 0) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns) + else if (vnodes == null || vnodes.length === 0) removeNodes(old, 0, old.length) else { - // default to keyed because, when either list is full of null nodes, it has fewer branches - var start = 0, oldStart = 0, isOldKeyed = true, isKeyed = true + var start = 0, oldStart = 0, isOldKeyed = null, isKeyed = null for (; oldStart < old.length; oldStart++) { if (old[oldStart] != null) { isOldKeyed = old[oldStart].key != null @@ -662,12 +670,11 @@ var coreRenderer = function($window) { break } } + if (isKeyed === null && isOldKeyed == null) return // both lists are full of nulls if (isOldKeyed !== isKeyed) { removeNodes(old, oldStart, old.length) createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns) - return - } - if (!isKeyed) { + } else if (!isKeyed) { // Don't index0 past the end of either list (causes deopts). var commonLength = old.length < vnodes.length ? old.length : vnodes.length // Rewind if necessary to the first non-null index0 on either side. @@ -684,71 +691,122 @@ var coreRenderer = function($window) { } if (old.length > commonLength) removeNodes(old, start, old.length) if (vnodes.length > commonLength) createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns) - return - } - // keyed diff - var oldEnd = old.length - 1, end = vnodes.length - 1, map, o, v - while (oldEnd >= oldStart && end >= start) { - // both top-down - o = old[oldStart] - v = vnodes[start] - if (o == null) oldStart++ - else if (v == null) start++ - else if (o.key === v.key) { - oldStart++, start++ - if (o !== v) updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), ns) - } else { - // reversal: old top-down, new bottom-up - v = vnodes[end] - if (o == null) oldStart++ - else if (v == null) end-- - else if (o.key === v.key) { - oldStart++ - if (start < end--) insertNode(parent, toFragment(o), nextSibling) - if (o !== v) updateNode(parent, o, v, hooks, nextSibling, ns) - if (v.dom != null) nextSibling = v.dom + } else { + // keyed diff + var oldEnd = old.length - 1, end = vnodes.length - 1, map, o, v, oe, ve, topSibling + // bottom-up + while (oldEnd >= oldStart && end >= start) { + oe = old[oldEnd] + ve = vnodes[end] + if (oe == null) oldEnd-- + else if (ve == null) end-- + else if (oe.key === ve.key) { + if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns) + if (ve.dom != null) nextSibling = ve.dom + oldEnd--, end-- + } else { + break } - else break } - } - while (oldEnd >= oldStart && end >= start) { - // both bottom-up - o = old[oldEnd] - v = vnodes[end] - if (o == null) oldEnd-- - else if (v == null) end-- - else if (o.key === v.key) { - if (o !== v) updateNode(parent, o, v, hooks, nextSibling, ns) - if (v.dom != null) nextSibling = v.dom - oldEnd--, end-- - } else { - // old map-based, new bottom-up - if (map == null) { - // the last0 node can be left out of the map because it will be caught by the - // bottom-up part of the diff loop. If we were to refactor this to use distinct - // loops, we'd have to pass `oldEnd + 1` (or change `start < end` to `<=` in getKeyMap). - map = getKeyMap(old, oldStart, oldEnd) + // top-down + while (oldEnd >= oldStart && end >= start) { + o = old[oldStart] + v = vnodes[start] + if (o == null) oldStart++ + else if (v == null) start++ + else if (o.key === v.key) { + oldStart++, start++ + if (o !== v) updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), ns) + } else { + break } - if (v != null) { - var oldIndex = map[v.key] - if (oldIndex == null) { - createNode(parent, v, hooks, ns, nextSibling) - if (v.dom != null) nextSibling = v.dom - } else { - o = old[oldIndex] - insertNode(parent, toFragment(o), nextSibling) - if (o !== v) updateNode(parent, o, v, hooks, nextSibling, ns) - o.skip = true - if (v.dom != null) nextSibling = v.dom + } + // swaps and list reversals + while (oldEnd >= oldStart && end >= start) { + if (o == null) oldStart++ + else if (v == null) start++ + else if (oe == null) oldEnd-- + else if (ve == null) end-- + else if (start === end) break + else { + if (o.key !== ve.key || oe.key !== v.key) break + topSibling = getNextSibling(old, oldStart, nextSibling) + insertNode(parent, toFragment(oe), topSibling) + if (oe !== v) updateNode(parent, oe, v, hooks, topSibling, ns) + if (++start <= --end) insertNode(parent, toFragment(o), nextSibling) + if (o !== ve) updateNode(parent, o, ve, hooks, nextSibling, ns) + if (ve.dom != null) nextSibling = ve.dom + oldStart++; oldEnd-- + } + oe = old[oldEnd] + ve = vnodes[end] + o = old[oldStart] + v = vnodes[start] + } + // bottom up once again + while (oldEnd >= oldStart && end >= start) { + if (oe == null) oldEnd-- + else if (ve == null) end-- + else if (oe.key === ve.key) { + if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns) + if (ve.dom != null) nextSibling = ve.dom + oldEnd--, end-- + } else { + break + } + oe = old[oldEnd] + ve = vnodes[end] + } + if (start > end) removeNodes(old, oldStart, oldEnd + 1) + else if (oldStart > oldEnd) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns) + else { + // inspired by ivi https://github.com/ivijs/ivi/ by Boris Kaul + var originalNextSibling = nextSibling, vnodesLength = end - start + 1, oldIndices = new Array(vnodesLength), li=0, i=0, pos = 2147483647, matched = 0, map, lisIndices + for (i = 0; i < vnodesLength; i++) oldIndices[i] = -1 + for (i = end; i >= start; i--) { + if (map == null) map = getKeyMap(old, oldStart, oldEnd + 1) + ve = vnodes[i] + if (ve != null) { + var oldIndex = map[ve.key] + if (oldIndex != null) { + pos = (oldIndex < pos) ? oldIndex : -1 // becomes -1 if nodes were re-ordered + oldIndices[i-start] = oldIndex + oe = old[oldIndex] + old[oldIndex] = null + if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns) + if (ve.dom != null) nextSibling = ve.dom + matched++ + } + } + } + nextSibling = originalNextSibling + if (matched !== oldEnd - oldStart + 1) removeNodes(old, oldStart, oldEnd + 1) + if (matched === 0) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns) + else { + if (pos === -1) { + // the indices of the indices of the items that are part of the + // longest increasing subsequence in the oldIndices list + lisIndices = makeLisIndices(oldIndices) + li = lisIndices.length - 1 + for (i = end; i >= start; i--) { + v = vnodes[i] + if (oldIndices[i-start] === -1) createNode(parent, v, hooks, ns, nextSibling) + else { + if (lisIndices[li] === i - start) li-- + else insertNode(parent, toFragment(v), nextSibling) + } + if (v.dom != null) nextSibling = vnodes[i].dom + } + } else { + for (i = end; i >= start; i--) { + v = vnodes[i] + if (oldIndices[i-start] === -1) createNode(parent, v, hooks, ns, nextSibling) + if (v.dom != null) nextSibling = vnodes[i].dom + } } } - end-- } - if (end < start) break } - // deal with the leftovers. - createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns) - removeNodes(old, oldStart, oldEnd + 1) } } function updateNode(parent, old, vnode, hooks, nextSibling, ns) { @@ -858,6 +916,55 @@ var coreRenderer = function($window) { } return map } + // Lifted from ivi https://github.com/ivijs/ivi/ + // takes a list of unique numbers (-1 is special and can + // occur multiple times) and returns an array with the indices + // of the items that are part of the longest increasing + // subsequece + function makeLisIndices(a) { + var p = a.slice() + var result = [] + result.push(0) + var u + var v + for (var i = 0, il = a.length; i < il; ++i) { + if (a[i] === -1) { + continue + } + var j = result[result.length - 1] + if (a[j] < a[i]) { + p[i] = j + result.push(i) + continue + } + u = 0 + v = result.length - 1 + while (u < v) { + /*eslint-disable no-bitwise*/ + var c = ((u + v) / 2) | 0 + /*eslint-enable no-bitwise*/ + if (a[result[c]] < a[i]) { + u = c + 1 + } + else { + v = c + } + } + if (a[i] < a[result[u]]) { + if (u > 0) { + p[i] = result[u - 1] + } + result[u] = i + } + } + u = result.length + v = result[u - 1] + while (u-- > 0) { + result[u] = v + v = p[v] + } + return result + } function toFragment(vnode) { var count0 = vnode.domSize if (count0 != null || vnode.dom == null) { @@ -893,10 +1000,7 @@ var coreRenderer = function($window) { function removeNodes(vnodes, start, end) { for (var i = start; i < end; i++) { var vnode = vnodes[i] - if (vnode != null) { - if (vnode.skip) vnode.skip = false - else removeNode(vnode) - } + if (vnode != null) removeNode(vnode) } } function removeNode(vnode) { @@ -1210,12 +1314,12 @@ var parseQueryString = function(string) { var levels = key5.split(/\]\[?|\[/) var cursor = data0 if (key5.indexOf("[") > -1) levels.pop() - for (var j = 0; j < levels.length; j++) { - var level = levels[j], nextLevel = levels[j + 1] + for (var j0 = 0; j0 < levels.length; j0++) { + var level = levels[j0], nextLevel = levels[j0 + 1] var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10)) - var isValue = j === levels.length - 1 + var isValue = j0 === levels.length - 1 if (level === "") { - var key5 = levels.slice(0, j).join() + var key5 = levels.slice(0, j0).join() if (counters[key5] == null) counters[key5] = 0 level = counters[key5]++ } @@ -1325,7 +1429,7 @@ var coreRouter = function($window) { } var _21 = function($window, redrawService0) { var routeService = coreRouter($window) - var identity = function(v) {return v} + var identity = function(v0) {return v0} var render1, component, attrs3, currentPath, lastUpdate var route = function(root, defaultRoute, routes) { if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined") diff --git a/mithril.min.js b/mithril.min.js index 2ec17ce2..40caf8b5 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,45 +1,47 @@ -(function(){function x(b,d,e,g,u,l){return{tag:b,key:d,attrs:e,children:g,text:u,dom:l,domSize:void 0,state:void 0,events:void 0,instance:void 0,skip:!1}}function M(b){for(var d in b)if(C.call(b,d))return!1;return!0}function w(b){if(null==b||"string"!==typeof b&&"function"!==typeof b&&"function"!==typeof b.view)throw Error("The selector must be either a string or a component.");var d=arguments[1],e=2;if(null==d)d={};else if("object"!==typeof d||null!=d.tag||Array.isArray(d))d={},e=1;if(arguments.length=== -e+1){var g=arguments[e];Array.isArray(g)||(g=[g])}else for(g=[];ea.indexOf("?")?"?":"&";a+=e+d}return a}function h(a){try{return""!==a?JSON.parse(a):null}catch(A){throw Error(a);}}function p(a){return a.responseText}function r(a,b){if("function"=== -typeof a)if(Array.isArray(b))for(var d=0;dk.status||304===k.status||U.test(a.url))d(r(a.type,b));else{var g=Error(k.responseText);g.code=k.status;g.response=b;e(g)}}catch(V){e(V)}};g&&null!=a.data?k.send(a.data):k.send()});return!0===a.background?A:k(A)},jsonp:function(a,p){var k=e();a=g(a,p);var h=new d(function(d,e){var g=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+t++,k=b.document.createElement("script");b[g]=function(e){k.parentNode.removeChild(k);d(r(a.type,e));delete b[g]};k.onerror=function(){k.parentNode.removeChild(k); -e(Error("JSONP request failed"));delete b[g]};null==a.data&&(a.data={});a.url=u(a.url,a.data);a.data[a.callbackKey||"callback"]=g;k.src=l(a.url,a.data);b.document.documentElement.appendChild(k)});return!0===a.background?h:k(h)},setCompletionCallback:function(a){y=a}}}(window,m),Q=function(b){function d(a,c){if(a.state!==c)throw Error("`vnode.state` must not be modified");}function e(a){var c=a.state;try{return this.apply(c,arguments)}finally{d(a,c)}}function g(a,c,f,b,d,e,g){for(;f'+c.children+"",n=n.firstChild):n.innerHTML=c.children;c.dom=n.firstChild;c.domSize=n.childNodes.length;for(c=z.createDocumentFragment();f=n.firstChild;)c.appendChild(f);y(a,c,b)}function h(a, -c,b,d,e,h){if(c!==b&&(null!=c||null!=b))if(null==c)g(a,b,0,b.length,d,e,h);else if(null==b)A(c,0,c.length);else{for(var n=0,f=0,q=!0,l=!0;f=f&&B>=n;)if(v=c[f],q=b[n],null==v)f++;else if(null==q)n++;else if(v.key===q.key)f++,n++,v!==q&&p(a,v,q,d,t(c,f,e),h);else if(q=b[B],null==v)f++;else if(null== -q)B--;else if(v.key===q.key)f++,n=f&&B>=n;){v=c[l];q=b[B];if(null==v)l--;else{if(null!=q)if(v.key===q.key)v!==q&&p(a,v,q,d,e,h),null!=q.dom&&(e=q.dom),l--;else{if(null==m){m=c;v=f;for(var z=l,x={};vl&&A(c,n,c.length);b.length>l&&g(a,b,n,b.length,d,e,h)}}}function p(b,c,f,d,g,t){var n=c.tag;if(n===f.tag){f.state=c.state;f.events=c.events;var q;var A;null!=f.attrs&&"function"===typeof f.attrs.onbeforeupdate&&(q=e.call(f.attrs.onbeforeupdate,f,c));"string"!==typeof f.tag&&"function"===typeof f.state.onbeforeupdate&& -(A=e.call(f.state.onbeforeupdate,f,c));void 0===q&&void 0===A||q||A?q=!1:(f.dom=c.dom,f.domSize=c.domSize,f.instance=c.instance,q=!0);if(!q)if("string"===typeof n)switch(null!=f.attrs&&J(f.attrs,f,d),n){case "#":c.children.toString()!==f.children.toString()&&(c.dom.nodeValue=f.children);f.dom=c.dom;break;case "<":c.children!==f.children?(r(c),l(b,f,t,g)):(f.dom=c.dom,f.domSize=c.domSize);break;case "[":h(b,c.children,f.children,d,g,t);c=0;d=f.children;f.dom=null;if(null!=d){for(var m=0;mc.indexOf("?")?"?":"&";c+=e+d}return c}function h(c){try{return""!==c?JSON.parse(c):null}catch(D){throw Error(c);}}function n(c){return c.responseText}function t(c,a){if("function"=== +typeof c)if(Array.isArray(a))for(var d=0;dg.status||304===g.status||Y.test(c.url))d(t(c.type,a));else{var f=Error(g.responseText);f.code=g.status;f.response=a;e(f)}}catch(Z){e(Z)}};f&&null!=c.data?g.send(c.data):g.send()});return!0===c.background?D:E(D)},jsonp:function(c,n){var h=e();c=f(c,n);var D=new d(function(d,e){var f=c.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+g++,n=a.document.createElement("script");a[f]=function(e){n.parentNode.removeChild(n);d(t(c.type,e));delete a[f]};n.onerror=function(){n.parentNode.removeChild(n); +e(Error("JSONP request failed"));delete a[f]};null==c.data&&(c.data={});c.url=v(c.url,c.data);c.data[c.callbackKey||"callback"]=f;n.src=l(c.url,c.data);a.document.documentElement.appendChild(n)});return!0===c.background?D:h(D)},setCompletionCallback:function(c){C=c}}}(window,k),U=function(a){function d(p,b){if(p.state!==b)throw Error("`vnode.state` must not be modified");}function e(p){var b=p.state;try{return this.apply(b,arguments)}finally{d(p,b)}}function f(p,b,c,a,d,e,f){for(;c'+b.children+"",p=p.firstChild):p.innerHTML=b.children;b.dom=p.firstChild;b.domSize=p.childNodes.length;for(b=A.createDocumentFragment();m=p.firstChild;)b.appendChild(m);C(c,b,a)}function h(p, +b,c,a,d,e){if(b!==c&&(null!=b||null!=c))if(null==b||0===b.length)f(p,c,0,c.length,a,d,e);else if(null==c||0===c.length)D(b,0,b.length);else{for(var m=0,r=0,h=null,q=null;r=r&&h>=m;)if(k=b[q],y=c[h],null==k)q--;else if(null==y)h--;else if(k.key===y.key)k!==y&&n(p, +k,y,a,d,e),null!=y.dom&&(d=y.dom),q--,h--;else break;for(;q>=r&&h>=m;)if(x=b[r],u=c[m],null==x)r++;else if(null==u)m++;else if(x.key===u.key)r++,m++,x!==u&&n(p,x,u,a,g(b,r,d),e);else break;for(;q>=r&&h>=m;){if(null==x)r++;else if(null==u)m++;else if(null==k)q--;else if(null==y)h--;else if(m===h)break;else{if(x.key!==y.key||k.key!==u.key)break;B=g(b,r,d);C(p,t(k),B);k!==u&&n(p,k,u,a,B,e);++m<=--h&&C(p,t(x),d);x!==y&&n(p,x,y,a,d,e);null!=y.dom&&(d=y.dom);r++;q--}k=b[q];y=c[h];x=b[r];u=c[m]}for(;q>= +r&&h>=m;){if(null==k)q--;else if(null==y)h--;else if(k.key===y.key)k!==y&&n(p,k,y,a,d,e),null!=y.dom&&(d=y.dom),q--,h--;else break;k=b[q];y=c[h]}if(m>h)D(b,r,q+1);else if(r>q)f(p,c,m,h+1,a,d,e);else{u=d;k=h-m+1;x=Array(k);var A=2147483647,z=0;for(B=0;B=m;B--){if(null==l){l=b;k=r;y=q+1;for(var w={};k=m;B--)u=c[B],-1===x[B-m]?v(p,u,a,e,d):b[r]===B-m?r--:C(p,t(u),d),null!=u.dom&&(d=c[B].dom)}else for(B=h;B>=m;B--)u=c[B],-1===x[B-m]&&v(p, +u,a,e,d),null!=u.dom&&(d=c[B].dom)}}else{h=b.lengthh&&D(b,m,b.length);c.length>h&&f(p,c,m,c.length,a,d,e)}}}function n(p,b,a,d,f,g){var m=b.tag;if(m===a.tag){a.state=b.state;a.events=b.events;var r;var k;null!=a.attrs&&"function"===typeof a.attrs.onbeforeupdate&&(r=e.call(a.attrs.onbeforeupdate,a,b));"string"!==typeof a.tag&&"function"=== +typeof a.state.onbeforeupdate&&(k=e.call(a.state.onbeforeupdate,a,b));void 0===r&&void 0===k||r||k?r=!1:(a.dom=b.dom,a.domSize=b.domSize,a.instance=b.instance,r=!0);if(!r)if("string"===typeof m)switch(null!=a.attrs&&N(a.attrs,a,d),m){case "#":b.children.toString()!==a.children.toString()&&(b.dom.nodeValue=a.children);a.dom=b.dom;break;case "<":b.children!==a.children?(t(b),l(p,a,g,f)):(a.dom=b.dom,a.domSize=b.domSize);break;case "[":h(p,b.children,a.children,d,f,g);b=0;d=a.children;a.dom=null;if(null!= +d){for(var q=0;q