diff --git a/.gitignore b/.gitignore index 58b805f..475f26d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .DS_Store -node_modules/ \ No newline at end of file +node_modules/ +coverage/ +package-lock.json diff --git a/.npmignore b/.npmignore index 391f31b..ab01da5 100644 --- a/.npmignore +++ b/.npmignore @@ -8,4 +8,4 @@ _config.yml .travis.yml babel-plugins.json package-lock.json -rollup.coinfig.js \ No newline at end of file +rollup.config.js \ No newline at end of file diff --git a/README.md b/README.md index c4229aa..9e8e8cd 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,34 @@ [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat)](https://github.com/WebReflection/donate) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/domdiff/badge.svg?branch=master)](https://coveralls.io/github/WebReflection/domdiff?branch=master) [![Build Status](https://travis-ci.org/WebReflection/domdiff.svg?branch=master)](https://travis-ci.org/WebReflection/domdiff) [![License: ISC](https://img.shields.io/badge/License-ISC-yellow.svg)](https://opensource.org/licenses/ISC) -A vDOM-less implementation of the [snabbdom](https://github.com/snabbdom/snabbdom) diffing logic. +A vDOM-less implementation of the [petit-dom](https://github.com/yelouafi/petit-dom) diffing logic, at the core of [hyperHTML](https://github.com/WebReflection/hyperHTML). + + +### V2 breaking change + + * the good old snabdom diff logic has been 100% replaced + * lists with `null` or `undefined` nodes are not allowed anymore + +... but I guess having null nodes in the equation was quite possibly a bad idea in the first place ... + +#### V2 Diffing Strategies: + + * common prefixes + * common suffixes + * skip same lists + * add boundaries + * remove boundaries + * simple sub-sequences insertions and removals + * one to many and many to one replacements + * fast inverted list swap + * O(ND) algo with a limit of 50 attempts + * last fallback with a simplified Hunt Szymanski algorithm + +The current goal is to have in about 1K the best DOM diffing library out there. + +#### V1 breaking change + +The signature has moved from `parent, current[], future[], getNode(), beforeNode` to `parent, current[], future[], {before, compare(), node()}`. ### Signature @@ -13,8 +40,10 @@ futureNodes = domdiff( parentNode, // where changes happen currentNodes, // Array of current items/nodes futureNodes, // Array of future items/nodes (returned) - getNode, // optional way to retrieve a node from an item - beforeNode // optional item/node to use as insertBefore delimiter + options // optional object with one of the following properties + // before: domNode + // compare(generic, generic) => true if same generic + // node(generic) => Node ); ``` @@ -56,3 +85,95 @@ parentNode.textContent; ### Compatibility: Every. JavaScript. Engine. + + +### A `{node: (generic, info) => node}` callback for complex data + +The optional `{node: (generic, info) => node}` is invoked per each operation on the DOM. + +This can be useful to represent node through wrappers, whenever that is needed. + +The passed `info` value can be: + + * `1` when the item/node is being appended + * `0` when the item/node is being used as insert _before_ reference + * `-0` when the item/node is being used as insert _after_ reference + * `-1` when the item/node is being removed + +[Example](https://codepen.io/WebReflection/pen/bYJVPd?editors=0010) + +```js +function node(item, i) { + // case removal or case after + if ((1 / i) < 0) { + // case removal + if (i) { + // if the item has more than a node + // remove all other nodes at once + if (item.length > 1) { + const range = document.createRange(); + range.setStartBefore(item[1]); + range.setEndAfter(item[item.length - 1]); + range.deleteContents(); + } + // return the first node to be removed + return item[0]; + } + // case after + else { + return item[item.length - 1]; + } + } + // case insert + else if (i) { + const fragment = document.createDocumentFragment(); + fragment.append(...item); + return fragment; + } + // case before + else { + return item[0]; + } +} + +const and = [document.createTextNode(' & ')]; + +const Bob = [ + document.createTextNode('B'), + document.createTextNode('o'), + document.createTextNode('b') +]; + +const Lucy = [ + document.createTextNode('L'), + document.createTextNode('u'), + document.createTextNode('c'), + document.createTextNode('y') +]; + +// clean the body for demo purpose +document.body.textContent = ''; +let content = domdiff( + document.body, + [], + [Bob, and, Lucy], + {node} +); + +// ... later on ... +content = domdiff( + document.body, + content, + [Lucy, and, Bob], + {node} +); + +// clean up +domdiff( + document.body, + content, + [], + {node} +); + +``` diff --git a/babel-plugins.json b/babel-plugins.json deleted file mode 100644 index d982204..0000000 --- a/babel-plugins.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - "check-es2015-constants", - "transform-es2015-arrow-functions", - "transform-es2015-block-scoped-functions", - "transform-es2015-block-scoping", - "transform-es2015-computed-properties", - "transform-es2015-destructuring", - "transform-es2015-duplicate-keys", - "transform-es2015-function-name", - "transform-es2015-literals", - "transform-es2015-shorthand-properties", - "transform-es2015-spread" -] \ No newline at end of file diff --git a/cjs/index.js b/cjs/index.js index ffadf64..2345036 100644 --- a/cjs/index.js +++ b/cjs/index.js @@ -1,117 +1,217 @@ 'use strict'; -/*! (c) 2017 Andrea Giammarchi (ISC) */ +/*! (c) 2018 Andrea Giammarchi (ISC) */ -/** - * This code is a revisited port of the snabbdom vDOM diffing logic, - * the same that fuels as fork Vue.js, Preact, and other libraries. - * @credits https://github.com/snabbdom/snabbdom - */ - -const identity = O => O; +const { + eqeq, identity, indexOf, isReversed, next, append, remove, smartDiff +} = require('./utils.js'); const domdiff = ( parentNode, // where changes happen currentNodes, // Array of current items/nodes futureNodes, // Array of future items/nodes - getNode, // optional way to retrieve a node from an item - beforeNode // optional item/node to use as insertBefore delimiter + options // optional object with one of the following properties + // before: domNode + // compare(generic, generic) => true if same generic + // node(generic) => Node ) => { - const get = getNode || identity; - const before = beforeNode == null ? null : get(beforeNode); - let currentStart = 0, futureStart = 0; - let currentEnd = currentNodes.length - 1; - let currentStartNode = currentNodes[0]; - let currentEndNode = currentNodes[currentEnd]; - let futureEnd = futureNodes.length - 1; - let futureStartNode = futureNodes[0]; - let futureEndNode = futureNodes[futureEnd]; - while (currentStart <= currentEnd && futureStart <= futureEnd) { - if (currentStartNode == null) { - currentStartNode = currentNodes[++currentStart]; - } - else if (currentEndNode == null) { - currentEndNode = currentNodes[--currentEnd]; - } - else if (futureStartNode == null) { - futureStartNode = futureNodes[++futureStart]; - } - else if (futureEndNode == null) { - futureEndNode = futureNodes[--futureEnd]; - } - else if (currentStartNode == futureStartNode) { - currentStartNode = currentNodes[++currentStart]; - futureStartNode = futureNodes[++futureStart]; - } - else if (currentEndNode == futureEndNode) { - currentEndNode = currentNodes[--currentEnd]; - futureEndNode = futureNodes[--futureEnd]; - } - else if (currentStartNode == futureEndNode) { - parentNode.insertBefore( - get(currentStartNode), - get(currentEndNode).nextSibling || before + if (!options) + options = {}; + + const compare = options.compare || eqeq; + const get = options.node || identity; + const before = options.before == null ? null : get(options.before, 0); + + const currentLength = currentNodes.length; + let currentEnd = currentLength; + let currentStart = 0; + + let futureEnd = futureNodes.length; + let futureStart = 0; + + // common prefix + while ( + currentStart < currentEnd && + futureStart < futureEnd && + compare(currentNodes[currentStart], futureNodes[futureStart]) + ) { + currentStart++; + futureStart++; + } + + // common suffix + while ( + currentStart < currentEnd && + futureStart < futureEnd && + compare(currentNodes[currentEnd - 1], futureNodes[futureEnd - 1]) + ) { + currentEnd--; + futureEnd--; + } + + const currentSame = currentStart === currentEnd; + const futureSame = futureStart === futureEnd; + + // same list + if (currentSame && futureSame) + return futureNodes; + + // only stuff to add + if (currentSame && futureStart < futureEnd) { + append( + get, + parentNode, + futureNodes, + futureStart, + futureEnd, + next(get, currentNodes, currentStart, currentLength, before) + ); + return futureNodes; + } + + // only stuff to remove + if (futureSame && currentStart < currentEnd) { + remove( + get, + currentNodes, + currentStart, + currentEnd + ); + return futureNodes; + } + + const currentChanges = currentEnd - currentStart; + const futureChanges = futureEnd - futureStart; + let i = -1; + + // 2 simple indels: the shortest sequence is a subsequence of the longest + if (currentChanges < futureChanges) { + i = indexOf( + futureNodes, + futureStart, + futureEnd, + currentNodes, + currentStart, + currentEnd, + compare + ); + // inner diff + if (-1 < i) { + append( + get, + parentNode, + futureNodes, + futureStart, + i, + get(currentNodes[currentStart], 0) ); - currentStartNode = currentNodes[++currentStart]; - futureEndNode = futureNodes[--futureEnd]; - } - else if (currentEndNode == futureStartNode) { - parentNode.insertBefore( - get(currentEndNode), - get(currentStartNode) + append( + get, + parentNode, + futureNodes, + i + currentChanges, + futureEnd, + next(get, currentNodes, currentEnd, currentLength, before) ); - currentEndNode = currentNodes[--currentEnd]; - futureStartNode = futureNodes[++futureStart]; - } - else { - let index = currentNodes.indexOf(futureStartNode); - if (index < 0) { - parentNode.insertBefore( - get(futureStartNode), - get(currentStartNode) - ); - futureStartNode = futureNodes[++futureStart]; - } - else { - let el = currentNodes[index]; - // until I am sure the else could never happen - // it might be a vDOM thing 'cause it never happens here - /* istanbul ignore if */ - if (el != futureStartNode) { - parentNode.insertBefore( - get(futureStartNode), - get(currentStartNode) - ); - } else { - currentNodes[index] = null; - parentNode.insertBefore( - get(el), - get(currentStartNode) - ); - } - futureStartNode = futureNodes[++futureStart]; - } + return futureNodes; } } - if (currentStart > currentEnd) { - const pin = futureNodes[futureEnd + 1]; - const place = pin != null ? get(pin) : before; - while (futureStart <= futureEnd) { - const ch = futureNodes[futureStart++]; - // until I am sure the else could never happen - // it might be a vDOM thing 'cause it never happens here - /* istanbul ignore else */ - if (ch != null) parentNode.insertBefore(get(ch), place); + /* istanbul ignore else */ + else if (futureChanges < currentChanges) { + i = indexOf( + currentNodes, + currentStart, + currentEnd, + futureNodes, + futureStart, + futureEnd, + compare + ); + // outer diff + if (-1 < i) { + remove( + get, + currentNodes, + currentStart, + i + ); + remove( + get, + currentNodes, + i + futureChanges, + currentEnd + ); + return futureNodes; } } - // until I am sure the else could never happen - // it might be a vDOM thing 'cause it never happens here + + // common case with one replacement for many nodes + // or many nodes replaced for a single one /* istanbul ignore else */ - else if (futureStart > futureEnd) { - while (currentStart <= currentEnd) { - const ch = currentNodes[currentStart++]; - if (ch != null) parentNode.removeChild(get(ch)); - } + if ((currentChanges < 2 || futureChanges < 2)) { + append( + get, + parentNode, + futureNodes, + futureStart, + futureEnd, + get(currentNodes[currentStart], 0) + ); + remove( + get, + currentNodes, + currentStart, + currentEnd + ); + return futureNodes; + } + + // the half match diff part has been skipped in petit-dom + // https://github.com/yelouafi/petit-dom/blob/bd6f5c919b5ae5297be01612c524c40be45f14a7/src/vdom.js#L391-L397 + // accordingly, I think it's safe to skip in here too + // if one day it'll come out like the speediest thing ever to do + // then I might add it in here too + + // Extra: before going too fancy, what about reversed lists ? + // This should bail out pretty quickly if that's not the case. + if ( + currentChanges === futureChanges && + isReversed( + futureNodes, + futureEnd, + currentNodes, + currentStart, + currentEnd, + compare + ) + ) { + append( + get, + parentNode, + futureNodes, + futureStart, + futureEnd, + next(get, currentNodes, currentEnd, currentLength, before) + ); + return futureNodes; } + + // last resort through a smart diff + smartDiff( + get, + parentNode, + futureNodes, + futureStart, + futureEnd, + futureChanges, + currentNodes, + currentStart, + currentEnd, + currentChanges, + currentLength, + compare, + before + ); + return futureNodes; }; diff --git a/cjs/package.json b/cjs/package.json new file mode 100644 index 0000000..0292b99 --- /dev/null +++ b/cjs/package.json @@ -0,0 +1 @@ +{"type":"commonjs"} \ No newline at end of file diff --git a/cjs/utils.js b/cjs/utils.js new file mode 100644 index 0000000..efc98cc --- /dev/null +++ b/cjs/utils.js @@ -0,0 +1,388 @@ +'use strict'; +const {indexOf: iOF} = require('uarray'); + +const append = (get, parent, children, start, end, before) => { + const isSelect = 'selectedIndex' in parent; + let noSelection = isSelect; + while (start < end) { + const child = get(children[start], 1); + parent.insertBefore(child, before); + if (isSelect && noSelection && child.selected) { + noSelection = !noSelection; + let {selectedIndex} = parent; + parent.selectedIndex = selectedIndex < 0 ? + start : + iOF.call(parent.querySelectorAll('option'), child); + } + start++; + } +}; +exports.append = append; + +const eqeq = (a, b) => a == b; +exports.eqeq = eqeq; + +const identity = O => O; +exports.identity = identity; + +const indexOf = ( + moreNodes, + moreStart, + moreEnd, + lessNodes, + lessStart, + lessEnd, + compare +) => { + const length = lessEnd - lessStart; + /* istanbul ignore if */ + if (length < 1) + return -1; + while ((moreEnd - moreStart) >= length) { + let m = moreStart; + let l = lessStart; + while ( + m < moreEnd && + l < lessEnd && + compare(moreNodes[m], lessNodes[l]) + ) { + m++; + l++; + } + if (l === lessEnd) + return moreStart; + moreStart = m + 1; + } + return -1; +}; +exports.indexOf = indexOf; + +const isReversed = ( + futureNodes, + futureEnd, + currentNodes, + currentStart, + currentEnd, + compare +) => { + while ( + currentStart < currentEnd && + compare( + currentNodes[currentStart], + futureNodes[futureEnd - 1] + )) { + currentStart++; + futureEnd--; + }; + return futureEnd === 0; +}; +exports.isReversed = isReversed; + +const next = (get, list, i, length, before) => i < length ? + get(list[i], 0) : + (0 < i ? + get(list[i - 1], -0).nextSibling : + before); +exports.next = next; + +const remove = (get, children, start, end) => { + while (start < end) + drop(get(children[start++], -1)); +}; +exports.remove = remove; + +// - - - - - - - - - - - - - - - - - - - +// diff related constants and utilities +// - - - - - - - - - - - - - - - - - - - + +const DELETION = -1; +const INSERTION = 1; +const SKIP = 0; +const SKIP_OND = 50; + +const HS = ( + futureNodes, + futureStart, + futureEnd, + futureChanges, + currentNodes, + currentStart, + currentEnd, + currentChanges +) => { + + let k = 0; + /* istanbul ignore next */ + let minLen = futureChanges < currentChanges ? futureChanges : currentChanges; + const link = Array(minLen++); + const tresh = Array(minLen); + tresh[0] = -1; + + for (let i = 1; i < minLen; i++) + tresh[i] = currentEnd; + + const nodes = currentNodes.slice(currentStart, currentEnd); + + for (let i = futureStart; i < futureEnd; i++) { + const index = nodes.indexOf(futureNodes[i]); + if (-1 < index) { + const idxInOld = index + currentStart; + k = findK(tresh, minLen, idxInOld); + /* istanbul ignore else */ + if (-1 < k) { + tresh[k] = idxInOld; + link[k] = { + newi: i, + oldi: idxInOld, + prev: link[k - 1] + }; + } + } + } + + k = --minLen; + --currentEnd; + while (tresh[k] > currentEnd) --k; + + minLen = currentChanges + futureChanges - k; + const diff = Array(minLen); + let ptr = link[k]; + --futureEnd; + while (ptr) { + const {newi, oldi} = ptr; + while (futureEnd > newi) { + diff[--minLen] = INSERTION; + --futureEnd; + } + while (currentEnd > oldi) { + diff[--minLen] = DELETION; + --currentEnd; + } + diff[--minLen] = SKIP; + --futureEnd; + --currentEnd; + ptr = ptr.prev; + } + while (futureEnd >= futureStart) { + diff[--minLen] = INSERTION; + --futureEnd; + } + while (currentEnd >= currentStart) { + diff[--minLen] = DELETION; + --currentEnd; + } + return diff; +}; + +// this is pretty much the same petit-dom code without the delete map part +// https://github.com/yelouafi/petit-dom/blob/bd6f5c919b5ae5297be01612c524c40be45f14a7/src/vdom.js#L556-L561 +const OND = ( + futureNodes, + futureStart, + rows, + currentNodes, + currentStart, + cols, + compare +) => { + const length = rows + cols; + const v = []; + let d, k, r, c, pv, cv, pd; + outer: for (d = 0; d <= length; d++) { + /* istanbul ignore if */ + if (d > SKIP_OND) + return null; + pd = d - 1; + /* istanbul ignore next */ + pv = d ? v[d - 1] : [0, 0]; + cv = v[d] = []; + for (k = -d; k <= d; k += 2) { + if (k === -d || (k !== d && pv[pd + k - 1] < pv[pd + k + 1])) { + c = pv[pd + k + 1]; + } else { + c = pv[pd + k - 1] + 1; + } + r = c - k; + while ( + c < cols && + r < rows && + compare( + currentNodes[currentStart + c], + futureNodes[futureStart + r] + ) + ) { + c++; + r++; + } + if (c === cols && r === rows) { + break outer; + } + cv[d + k] = c; + } + } + + const diff = Array(d / 2 + length / 2); + let diffIdx = diff.length - 1; + for (d = v.length - 1; d >= 0; d--) { + while ( + c > 0 && + r > 0 && + compare( + currentNodes[currentStart + c - 1], + futureNodes[futureStart + r - 1] + ) + ) { + // diagonal edge = equality + diff[diffIdx--] = SKIP; + c--; + r--; + } + if (!d) + break; + pd = d - 1; + /* istanbul ignore next */ + pv = d ? v[d - 1] : [0, 0]; + k = c - r; + if (k === -d || (k !== d && pv[pd + k - 1] < pv[pd + k + 1])) { + // vertical edge = insertion + r--; + diff[diffIdx--] = INSERTION; + } else { + // horizontal edge = deletion + c--; + diff[diffIdx--] = DELETION; + } + } + return diff; +}; + +const applyDiff = ( + diff, + get, + parentNode, + futureNodes, + futureStart, + currentNodes, + currentStart, + currentLength, + before +) => { + const live = []; + const length = diff.length; + let currentIndex = currentStart; + let i = 0; + while (i < length) { + switch (diff[i++]) { + case SKIP: + futureStart++; + currentIndex++; + break; + case INSERTION: + // TODO: bulk appends for sequential nodes + live.push(futureNodes[futureStart]); + append( + get, + parentNode, + futureNodes, + futureStart++, + futureStart, + currentIndex < currentLength ? + get(currentNodes[currentIndex], 0) : + before + ); + break; + case DELETION: + currentIndex++; + break; + } + } + i = 0; + while (i < length) { + switch (diff[i++]) { + case SKIP: + currentStart++; + break; + case DELETION: + // TODO: bulk removes for sequential nodes + if (-1 < live.indexOf(currentNodes[currentStart])) + currentStart++; + else + remove( + get, + currentNodes, + currentStart++, + currentStart + ); + break; + } + } +}; + +const findK = (ktr, length, j) => { + let lo = 1; + let hi = length; + while (lo < hi) { + const mid = ((lo + hi) / 2) >>> 0; + if (j < ktr[mid]) + hi = mid; + else + lo = mid + 1; + } + return lo; +} + +const smartDiff = ( + get, + parentNode, + futureNodes, + futureStart, + futureEnd, + futureChanges, + currentNodes, + currentStart, + currentEnd, + currentChanges, + currentLength, + compare, + before +) => { + applyDiff( + OND( + futureNodes, + futureStart, + futureChanges, + currentNodes, + currentStart, + currentChanges, + compare + ) || + HS( + futureNodes, + futureStart, + futureEnd, + futureChanges, + currentNodes, + currentStart, + currentEnd, + currentChanges + ), + get, + parentNode, + futureNodes, + futureStart, + currentNodes, + currentStart, + currentLength, + before + ); +}; +exports.smartDiff = smartDiff; + +const drop = node => (node.remove || dropChild).call(node); + +function dropChild() { + const {parentNode} = this; + /* istanbul ignore else */ + if (parentNode) + parentNode.removeChild(this); +} diff --git a/coverage/coverage.json b/coverage/coverage.json deleted file mode 100644 index 3e7e2d9..0000000 --- a/coverage/coverage.json +++ /dev/null @@ -1 +0,0 @@ -{"/home/webreflection/code/domdiff/cjs/index.js":{"path":"/home/webreflection/code/domdiff/cjs/index.js","s":{"1":1,"2":569,"3":1,"4":98,"5":98,"6":98,"7":98,"8":98,"9":98,"10":98,"11":98,"12":98,"13":98,"14":447,"15":44,"16":403,"17":11,"18":392,"19":22,"20":370,"21":5,"22":365,"23":140,"24":140,"25":225,"26":50,"27":50,"28":175,"29":52,"30":52,"31":52,"32":123,"33":27,"34":27,"35":27,"36":96,"37":96,"38":52,"39":52,"40":44,"41":44,"42":0,"43":44,"44":44,"45":44,"46":98,"47":52,"48":52,"49":52,"50":63,"51":63,"52":63,"53":46,"54":46,"55":116,"56":116,"57":100,"58":98,"59":1},"b":{"1":[98,96],"2":[50,48],"3":[545,493],"4":[44,403],"5":[11,392],"6":[22,370],"7":[5,365],"8":[140,225],"9":[50,175],"10":[52,123],"11":[52,9],"12":[27,96],"13":[52,44],"14":[0,44],"15":[52,46],"16":[25,27],"17":[63,0],"18":[46,0],"19":[100,16]},"f":{},"fnMap":{},"statementMap":{"1":{"start":{"line":10,"column":0},"end":{"line":10,"column":24}},"2":{"start":{"line":10,"column":22},"end":{"line":10,"column":23}},"3":{"start":{"line":12,"column":0},"end":{"line":116,"column":2}},"4":{"start":{"line":19,"column":2},"end":{"line":19,"column":34}},"5":{"start":{"line":20,"column":2},"end":{"line":20,"column":61}},"6":{"start":{"line":21,"column":2},"end":{"line":21,"column":40}},"7":{"start":{"line":22,"column":2},"end":{"line":22,"column":43}},"8":{"start":{"line":23,"column":2},"end":{"line":23,"column":41}},"9":{"start":{"line":24,"column":2},"end":{"line":24,"column":48}},"10":{"start":{"line":25,"column":2},"end":{"line":25,"column":41}},"11":{"start":{"line":26,"column":2},"end":{"line":26,"column":39}},"12":{"start":{"line":27,"column":2},"end":{"line":27,"column":45}},"13":{"start":{"line":28,"column":2},"end":{"line":94,"column":3}},"14":{"start":{"line":29,"column":4},"end":{"line":93,"column":5}},"15":{"start":{"line":30,"column":6},"end":{"line":30,"column":54}},"16":{"start":{"line":32,"column":9},"end":{"line":93,"column":5}},"17":{"start":{"line":33,"column":6},"end":{"line":33,"column":50}},"18":{"start":{"line":35,"column":9},"end":{"line":93,"column":5}},"19":{"start":{"line":36,"column":6},"end":{"line":36,"column":51}},"20":{"start":{"line":38,"column":9},"end":{"line":93,"column":5}},"21":{"start":{"line":39,"column":6},"end":{"line":39,"column":47}},"22":{"start":{"line":41,"column":9},"end":{"line":93,"column":5}},"23":{"start":{"line":42,"column":6},"end":{"line":42,"column":54}},"24":{"start":{"line":43,"column":6},"end":{"line":43,"column":51}},"25":{"start":{"line":45,"column":9},"end":{"line":93,"column":5}},"26":{"start":{"line":46,"column":6},"end":{"line":46,"column":50}},"27":{"start":{"line":47,"column":6},"end":{"line":47,"column":47}},"28":{"start":{"line":49,"column":9},"end":{"line":93,"column":5}},"29":{"start":{"line":50,"column":6},"end":{"line":53,"column":8}},"30":{"start":{"line":54,"column":6},"end":{"line":54,"column":54}},"31":{"start":{"line":55,"column":6},"end":{"line":55,"column":47}},"32":{"start":{"line":57,"column":9},"end":{"line":93,"column":5}},"33":{"start":{"line":58,"column":6},"end":{"line":61,"column":8}},"34":{"start":{"line":62,"column":6},"end":{"line":62,"column":50}},"35":{"start":{"line":63,"column":6},"end":{"line":63,"column":51}},"36":{"start":{"line":66,"column":6},"end":{"line":66,"column":56}},"37":{"start":{"line":67,"column":6},"end":{"line":92,"column":7}},"38":{"start":{"line":68,"column":8},"end":{"line":71,"column":10}},"39":{"start":{"line":72,"column":8},"end":{"line":72,"column":53}},"40":{"start":{"line":75,"column":8},"end":{"line":75,"column":37}},"41":{"start":{"line":79,"column":8},"end":{"line":90,"column":9}},"42":{"start":{"line":80,"column":10},"end":{"line":83,"column":12},"skip":true},"43":{"start":{"line":85,"column":10},"end":{"line":85,"column":37}},"44":{"start":{"line":86,"column":10},"end":{"line":89,"column":12}},"45":{"start":{"line":91,"column":8},"end":{"line":91,"column":53}},"46":{"start":{"line":95,"column":2},"end":{"line":114,"column":3}},"47":{"start":{"line":96,"column":4},"end":{"line":96,"column":43}},"48":{"start":{"line":97,"column":4},"end":{"line":97,"column":50}},"49":{"start":{"line":98,"column":4},"end":{"line":104,"column":5}},"50":{"start":{"line":99,"column":6},"end":{"line":99,"column":44}},"51":{"start":{"line":103,"column":6},"end":{"line":103,"column":62}},"52":{"start":{"line":103,"column":22},"end":{"line":103,"column":62}},"53":{"start":{"line":109,"column":7},"end":{"line":114,"column":3}},"54":{"start":{"line":110,"column":4},"end":{"line":113,"column":5}},"55":{"start":{"line":111,"column":6},"end":{"line":111,"column":46}},"56":{"start":{"line":112,"column":6},"end":{"line":112,"column":54}},"57":{"start":{"line":112,"column":22},"end":{"line":112,"column":54}},"58":{"start":{"line":115,"column":2},"end":{"line":115,"column":21}},"59":{"start":{"line":118,"column":0},"end":{"line":118,"column":78}}},"branchMap":{"1":{"line":19,"type":"binary-expr","locations":[{"start":{"line":19,"column":14},"end":{"line":19,"column":21}},{"start":{"line":19,"column":25},"end":{"line":19,"column":33}}]},"2":{"line":20,"type":"cond-expr","locations":[{"start":{"line":20,"column":38},"end":{"line":20,"column":42}},{"start":{"line":20,"column":45},"end":{"line":20,"column":60}}]},"3":{"line":28,"type":"binary-expr","locations":[{"start":{"line":28,"column":9},"end":{"line":28,"column":35}},{"start":{"line":28,"column":39},"end":{"line":28,"column":63}}]},"4":{"line":29,"type":"if","locations":[{"start":{"line":29,"column":4},"end":{"line":29,"column":4}},{"start":{"line":29,"column":4},"end":{"line":29,"column":4}}]},"5":{"line":32,"type":"if","locations":[{"start":{"line":32,"column":9},"end":{"line":32,"column":9}},{"start":{"line":32,"column":9},"end":{"line":32,"column":9}}]},"6":{"line":35,"type":"if","locations":[{"start":{"line":35,"column":9},"end":{"line":35,"column":9}},{"start":{"line":35,"column":9},"end":{"line":35,"column":9}}]},"7":{"line":38,"type":"if","locations":[{"start":{"line":38,"column":9},"end":{"line":38,"column":9}},{"start":{"line":38,"column":9},"end":{"line":38,"column":9}}]},"8":{"line":41,"type":"if","locations":[{"start":{"line":41,"column":9},"end":{"line":41,"column":9}},{"start":{"line":41,"column":9},"end":{"line":41,"column":9}}]},"9":{"line":45,"type":"if","locations":[{"start":{"line":45,"column":9},"end":{"line":45,"column":9}},{"start":{"line":45,"column":9},"end":{"line":45,"column":9}}]},"10":{"line":49,"type":"if","locations":[{"start":{"line":49,"column":9},"end":{"line":49,"column":9}},{"start":{"line":49,"column":9},"end":{"line":49,"column":9}}]},"11":{"line":52,"type":"binary-expr","locations":[{"start":{"line":52,"column":8},"end":{"line":52,"column":39}},{"start":{"line":52,"column":43},"end":{"line":52,"column":49}}]},"12":{"line":57,"type":"if","locations":[{"start":{"line":57,"column":9},"end":{"line":57,"column":9}},{"start":{"line":57,"column":9},"end":{"line":57,"column":9}}]},"13":{"line":67,"type":"if","locations":[{"start":{"line":67,"column":6},"end":{"line":67,"column":6}},{"start":{"line":67,"column":6},"end":{"line":67,"column":6}}]},"14":{"line":79,"type":"if","locations":[{"start":{"line":79,"column":8},"end":{"line":79,"column":8},"skip":true},{"start":{"line":79,"column":8},"end":{"line":79,"column":8}}]},"15":{"line":95,"type":"if","locations":[{"start":{"line":95,"column":2},"end":{"line":95,"column":2}},{"start":{"line":95,"column":2},"end":{"line":95,"column":2}}]},"16":{"line":97,"type":"cond-expr","locations":[{"start":{"line":97,"column":32},"end":{"line":97,"column":40}},{"start":{"line":97,"column":43},"end":{"line":97,"column":49}}]},"17":{"line":103,"type":"if","locations":[{"start":{"line":103,"column":6},"end":{"line":103,"column":6}},{"start":{"line":103,"column":6},"end":{"line":103,"column":6},"skip":true}]},"18":{"line":109,"type":"if","locations":[{"start":{"line":109,"column":7},"end":{"line":109,"column":7}},{"start":{"line":109,"column":7},"end":{"line":109,"column":7},"skip":true}]},"19":{"line":112,"type":"if","locations":[{"start":{"line":112,"column":6},"end":{"line":112,"column":6}},{"start":{"line":112,"column":6},"end":{"line":112,"column":6}}]}}}} \ No newline at end of file diff --git a/coverage/lcov-report/base.css b/coverage/lcov-report/base.css deleted file mode 100644 index 29737bc..0000000 --- a/coverage/lcov-report/base.css +++ /dev/null @@ -1,213 +0,0 @@ -body, html { - margin:0; padding: 0; - height: 100%; -} -body { - font-family: Helvetica Neue, Helvetica, Arial; - font-size: 14px; - color:#333; -} -.small { font-size: 12px; } -*, *:after, *:before { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; - } -h1 { font-size: 20px; margin: 0;} -h2 { font-size: 14px; } -pre { - font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; - margin: 0; - padding: 0; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} -a { color:#0074D9; text-decoration:none; } -a:hover { text-decoration:underline; } -.strong { font-weight: bold; } -.space-top1 { padding: 10px 0 0 0; } -.pad2y { padding: 20px 0; } -.pad1y { padding: 10px 0; } -.pad2x { padding: 0 20px; } -.pad2 { padding: 20px; } -.pad1 { padding: 10px; } -.space-left2 { padding-left:55px; } -.space-right2 { padding-right:20px; } -.center { text-align:center; } -.clearfix { display:block; } -.clearfix:after { - content:''; - display:block; - height:0; - clear:both; - visibility:hidden; - } -.fl { float: left; } -@media only screen and (max-width:640px) { - .col3 { width:100%; max-width:100%; } - .hide-mobile { display:none!important; } -} - -.quiet { - color: #7f7f7f; - color: rgba(0,0,0,0.5); -} -.quiet a { opacity: 0.7; } - -.fraction { - font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; - font-size: 10px; - color: #555; - background: #E8E8E8; - padding: 4px 5px; - border-radius: 3px; - vertical-align: middle; -} - -div.path a:link, div.path a:visited { color: #333; } -table.coverage { - border-collapse: collapse; - margin: 10px 0 0 0; - padding: 0; -} - -table.coverage td { - margin: 0; - padding: 0; - vertical-align: top; -} -table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; -} -table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; -} - -table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; -} -.missing-if-branch { - display: inline-block; - margin-right: 5px; - border-radius: 3px; - position: relative; - padding: 0 4px; - background: #333; - color: yellow; -} - -.skip-if-branch { - display: none; - margin-right: 10px; - position: relative; - padding: 0 4px; - background: #ccc; - color: white; -} -.missing-if-branch .typ, .skip-if-branch .typ { - color: inherit !important; -} -.coverage-summary { - border-collapse: collapse; - width: 100%; -} -.coverage-summary tr { border-bottom: 1px solid #bbb; } -.keyline-all { border: 1px solid #ddd; } -.coverage-summary td, .coverage-summary th { padding: 10px; } -.coverage-summary tbody { border: 1px solid #bbb; } -.coverage-summary td { border-right: 1px solid #bbb; } -.coverage-summary td:last-child { border-right: none; } -.coverage-summary th { - text-align: left; - font-weight: normal; - white-space: nowrap; -} -.coverage-summary th.file { border-right: none !important; } -.coverage-summary th.pct { } -.coverage-summary th.pic, -.coverage-summary th.abs, -.coverage-summary td.pct, -.coverage-summary td.abs { text-align: right; } -.coverage-summary td.file { white-space: nowrap; } -.coverage-summary td.pic { min-width: 120px !important; } -.coverage-summary tfoot td { } - -.coverage-summary .sorter { - height: 10px; - width: 7px; - display: inline-block; - margin-left: 0.5em; - background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; -} -.coverage-summary .sorted .sorter { - background-position: 0 -20px; -} -.coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; -} -.status-line { height: 10px; } -/* dark red */ -.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } -.low .chart { border:1px solid #C21F39 } -/* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } -/* light red */ -.low, .cline-no { background:#FCE1E5 } -/* light green */ -.high, .cline-yes { background:rgb(230,245,208) } -/* medium green */ -.cstat-yes { background:rgb(161,215,106) } -/* dark green */ -.status-line.high, .high .cover-fill { background:rgb(77,146,33) } -.high .chart { border:1px solid rgb(77,146,33) } -/* dark yellow (gold) */ -.medium .chart { border:1px solid #f9cd0b; } -.status-line.medium, .medium .cover-fill { background: #f9cd0b; } -/* light yellow */ -.medium { background: #fff4c2; } -/* light gray */ -span.cline-neutral { background: #eaeaea; } - -.cbranch-no { background: yellow !important; color: #111; } - -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } - - -.cover-fill, .cover-empty { - display:inline-block; - height: 12px; -} -.chart { - line-height: 0; -} -.cover-empty { - background: white; -} -.cover-full { - border-right: none !important; -} -pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; -} -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -48px; -} -.footer, .push { - height: 48px; -} diff --git a/coverage/lcov-report/cjs/index.html b/coverage/lcov-report/cjs/index.html deleted file mode 100644 index 389c96b..0000000 --- a/coverage/lcov-report/cjs/index.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - Code coverage report for cjs/ - - - - - - - -
-
-

- all files cjs/ -

-
-
- 100% - Statements - 59/59 -
-
- 100% - Branches - 38/38 -
-
- 100% - Functions - 0/0 -
-
- 100% - Lines - 56/56 -
-
- 1 statement, 3 branches - Ignored      -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
index.js
100%59/59100%38/38100%0/0100%56/56
-
-
- - - - - - - diff --git a/coverage/lcov-report/cjs/index.js.html b/coverage/lcov-report/cjs/index.js.html deleted file mode 100644 index bdc474c..0000000 --- a/coverage/lcov-report/cjs/index.js.html +++ /dev/null @@ -1,423 +0,0 @@ - - - - Code coverage report for cjs/index.js - - - - - - - -
-
-

- all files / cjs/ index.js -

-
-
- 100% - Statements - 59/59 -
-
- 100% - Branches - 38/38 -
-
- 100% - Functions - 0/0 -
-
- 100% - Lines - 56/56 -
-
- 1 statement, 3 branches - Ignored      -
-
-
-
-

-
-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119  -  -  -  -  -  -  -  -  -569× -  - -  -  -  -  -  -  -98× -98× -98× -98× -98× -98× -98× -98× -98× -98× -447× -44× -  -403× -11× -  -392× -22× -  -370× - -  -365× -140× -140× -  -225× -50× -50× -  -175× -52× -  -  -  -52× -52× -  -123× -27× -  -  -  -27× -27× -  -  -96× -96× -52× -  -  -  -52× -  -  -44× -  -  -  -44× - -  -  -  -  -44× -44× -  -  -  -  -44× -  -  -  -98× -52× -52× -52× -63× -  -  -  -63× -  -  -  -  -  -46× -46× -116× -116× -  -  -98× -  -  - - 
'use strict';
-/*! (c) 2017 Andrea Giammarchi (ISC) */
- 
-/**
- * This code is a revisited port of the snabbdom vDOM diffing logic,
- * the same that fuels as fork Vue.js, Preact, and other libraries.
- * @credits https://github.com/snabbdom/snabbdom
- */
- 
-const identity = O => O;
- 
-const domdiff = (
-  parentNode,     // where changes happen
-  currentNodes,   // Array of current items/nodes
-  futureNodes,    // Array of future items/nodes
-  getNode,        // optional way to retrieve a node from an item
-  beforeNode      // optional item/node to use as insertBefore delimiter
-) => {
-  const get = getNode || identity;
-  const before = beforeNode == null ? null : get(beforeNode);
-  let currentStart = 0, futureStart = 0;
-  let currentEnd = currentNodes.length - 1;
-  let currentStartNode = currentNodes[0];
-  let currentEndNode = currentNodes[currentEnd];
-  let futureEnd = futureNodes.length - 1;
-  let futureStartNode = futureNodes[0];
-  let futureEndNode = futureNodes[futureEnd];
-  while (currentStart <= currentEnd && futureStart <= futureEnd) {
-    if (currentStartNode == null) {
-      currentStartNode = currentNodes[++currentStart];
-    }
-    else if (currentEndNode == null) {
-      currentEndNode = currentNodes[--currentEnd];
-    }
-    else if (futureStartNode == null) {
-      futureStartNode = futureNodes[++futureStart];
-    }
-    else if (futureEndNode == null) {
-      futureEndNode = futureNodes[--futureEnd];
-    }
-    else if (currentStartNode == futureStartNode) {
-      currentStartNode = currentNodes[++currentStart];
-      futureStartNode = futureNodes[++futureStart];
-    }
-    else if (currentEndNode == futureEndNode) {
-      currentEndNode = currentNodes[--currentEnd];
-      futureEndNode = futureNodes[--futureEnd];
-    }
-    else if (currentStartNode == futureEndNode) {
-      parentNode.insertBefore(
-        get(currentStartNode),
-        get(currentEndNode).nextSibling || before
-      );
-      currentStartNode = currentNodes[++currentStart];
-      futureEndNode = futureNodes[--futureEnd];
-    }
-    else if (currentEndNode == futureStartNode) {
-      parentNode.insertBefore(
-        get(currentEndNode),
-        get(currentStartNode)
-      );
-      currentEndNode = currentNodes[--currentEnd];
-      futureStartNode = futureNodes[++futureStart];
-    }
-    else {
-      let index = currentNodes.indexOf(futureStartNode);
-      if (index < 0) {
-        parentNode.insertBefore(
-          get(futureStartNode),
-          get(currentStartNode)
-        );
-        futureStartNode = futureNodes[++futureStart];
-      }
-      else {
-        let el = currentNodes[index];
-        // until I am sure the else could never happen
-        // it might be a vDOM thing 'cause it never happens here
-        /* istanbul ignore if */
-        Iif (el != futureStartNode) {
-          parentNode.insertBefore(
-            get(futureStartNode),
-            get(currentStartNode)
-          );
-        } else {
-          currentNodes[index] = null;
-          parentNode.insertBefore(
-            get(el),
-            get(currentStartNode)
-          );
-        }
-        futureStartNode = futureNodes[++futureStart];
-      }
-    }
-  }
-  if (currentStart > currentEnd) {
-    const pin = futureNodes[futureEnd + 1];
-    const place = pin != null ? get(pin) : before;
-    while (futureStart <= futureEnd) {
-      const ch = futureNodes[futureStart++];
-      // until I am sure the else could never happen
-      // it might be a vDOM thing 'cause it never happens here
-      /* istanbul ignore else */
-      Eif (ch != null) parentNode.insertBefore(get(ch), place);
-    }
-  }
-  // until I am sure the else could never happen
-  // it might be a vDOM thing 'cause it never happens here
-  /* istanbul ignore else */
-  else Eif (futureStart > futureEnd) {
-    while (currentStart <= currentEnd) {
-      const ch = currentNodes[currentStart++];
-      if (ch != null) parentNode.removeChild(get(ch));
-    }
-  }
-  return futureNodes;
-};
- 
-Object.defineProperty(exports, '__esModule', {value: true}).default = domdiff;
- 
-
-
- - - - - - - diff --git a/coverage/lcov-report/index.html b/coverage/lcov-report/index.html deleted file mode 100644 index 6d62ef1..0000000 --- a/coverage/lcov-report/index.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - Code coverage report for All files - - - - - - - -
-
-

- / -

-
-
- 100% - Statements - 59/59 -
-
- 100% - Branches - 38/38 -
-
- 100% - Functions - 0/0 -
-
- 100% - Lines - 56/56 -
-
- 1 statement, 3 branches - Ignored      -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
cjs/
100%59/59100%38/38100%0/0100%56/56
-
-
- - - - - - - diff --git a/coverage/lcov-report/prettify.css b/coverage/lcov-report/prettify.css deleted file mode 100644 index b317a7c..0000000 --- a/coverage/lcov-report/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/coverage/lcov-report/prettify.js b/coverage/lcov-report/prettify.js deleted file mode 100644 index ef51e03..0000000 --- a/coverage/lcov-report/prettify.js +++ /dev/null @@ -1 +0,0 @@ -window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/coverage/lcov-report/sort-arrow-sprite.png b/coverage/lcov-report/sort-arrow-sprite.png deleted file mode 100644 index 03f704a..0000000 Binary files a/coverage/lcov-report/sort-arrow-sprite.png and /dev/null differ diff --git a/coverage/lcov-report/sorter.js b/coverage/lcov-report/sorter.js deleted file mode 100644 index 6c5034e..0000000 --- a/coverage/lcov-report/sorter.js +++ /dev/null @@ -1,158 +0,0 @@ -var addSorting = (function () { - "use strict"; - var cols, - currentSort = { - index: 0, - desc: false - }; - - // returns the summary table element - function getTable() { return document.querySelector('.coverage-summary'); } - // returns the thead element of the summary table - function getTableHeader() { return getTable().querySelector('thead tr'); } - // returns the tbody element of the summary table - function getTableBody() { return getTable().querySelector('tbody'); } - // returns the th element for nth column - function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } - - // loads all columns - function loadColumns() { - var colNodes = getTableHeader().querySelectorAll('th'), - colNode, - cols = [], - col, - i; - - for (i = 0; i < colNodes.length; i += 1) { - colNode = colNodes[i]; - col = { - key: colNode.getAttribute('data-col'), - sortable: !colNode.getAttribute('data-nosort'), - type: colNode.getAttribute('data-type') || 'string' - }; - cols.push(col); - if (col.sortable) { - col.defaultDescSort = col.type === 'number'; - colNode.innerHTML = colNode.innerHTML + ''; - } - } - return cols; - } - // attaches a data attribute to every tr element with an object - // of data values keyed by column name - function loadRowData(tableRow) { - var tableCols = tableRow.querySelectorAll('td'), - colNode, - col, - data = {}, - i, - val; - for (i = 0; i < tableCols.length; i += 1) { - colNode = tableCols[i]; - col = cols[i]; - val = colNode.getAttribute('data-value'); - if (col.type === 'number') { - val = Number(val); - } - data[col.key] = val; - } - return data; - } - // loads all row data - function loadData() { - var rows = getTableBody().querySelectorAll('tr'), - i; - - for (i = 0; i < rows.length; i += 1) { - rows[i].data = loadRowData(rows[i]); - } - } - // sorts the table using the data for the ith column - function sortByIndex(index, desc) { - var key = cols[index].key, - sorter = function (a, b) { - a = a.data[key]; - b = b.data[key]; - return a < b ? -1 : a > b ? 1 : 0; - }, - finalSorter = sorter, - tableBody = document.querySelector('.coverage-summary tbody'), - rowNodes = tableBody.querySelectorAll('tr'), - rows = [], - i; - - if (desc) { - finalSorter = function (a, b) { - return -1 * sorter(a, b); - }; - } - - for (i = 0; i < rowNodes.length; i += 1) { - rows.push(rowNodes[i]); - tableBody.removeChild(rowNodes[i]); - } - - rows.sort(finalSorter); - - for (i = 0; i < rows.length; i += 1) { - tableBody.appendChild(rows[i]); - } - } - // removes sort indicators for current column being sorted - function removeSortIndicators() { - var col = getNthColumn(currentSort.index), - cls = col.className; - - cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); - col.className = cls; - } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; - } - // adds event listeners for all sorter widgets - function enableUI() { - var i, - el, - ithSorter = function ithSorter(i) { - var col = cols[i]; - - return function () { - var desc = col.defaultDescSort; - - if (currentSort.index === i) { - desc = !currentSort.desc; - } - sortByIndex(i, desc); - removeSortIndicators(); - currentSort.index = i; - currentSort.desc = desc; - addSortIndicators(); - }; - }; - for (i =0 ; i < cols.length; i += 1) { - if (cols[i].sortable) { - // add the click event handler on the th so users - // dont have to click on those tiny arrows - el = getNthColumn(i).querySelector('.sorter').parentElement; - if (el.addEventListener) { - el.addEventListener('click', ithSorter(i)); - } else { - el.attachEvent('onclick', ithSorter(i)); - } - } - } - } - // adds sorting functionality to the UI - return function () { - if (!getTable()) { - return; - } - cols = loadColumns(); - loadData(cols); - addSortIndicators(); - enableUI(); - }; -})(); - -window.addEventListener('load', addSorting); diff --git a/coverage/lcov.info b/coverage/lcov.info deleted file mode 100644 index 88084a2..0000000 --- a/coverage/lcov.info +++ /dev/null @@ -1,103 +0,0 @@ -TN: -SF:/home/webreflection/code/domdiff/cjs/index.js -FNF:0 -FNH:0 -DA:10,569 -DA:12,1 -DA:19,98 -DA:20,98 -DA:21,98 -DA:22,98 -DA:23,98 -DA:24,98 -DA:25,98 -DA:26,98 -DA:27,98 -DA:28,98 -DA:29,447 -DA:30,44 -DA:32,403 -DA:33,11 -DA:35,392 -DA:36,22 -DA:38,370 -DA:39,5 -DA:41,365 -DA:42,140 -DA:43,140 -DA:45,225 -DA:46,50 -DA:47,50 -DA:49,175 -DA:50,52 -DA:54,52 -DA:55,52 -DA:57,123 -DA:58,27 -DA:62,27 -DA:63,27 -DA:66,96 -DA:67,96 -DA:68,52 -DA:72,52 -DA:75,44 -DA:79,44 -DA:80,1 -DA:85,44 -DA:86,44 -DA:91,44 -DA:95,98 -DA:96,52 -DA:97,52 -DA:98,52 -DA:99,63 -DA:103,63 -DA:109,46 -DA:110,46 -DA:111,116 -DA:112,116 -DA:115,98 -DA:118,1 -LF:56 -LH:56 -BRDA:19,1,0,98 -BRDA:19,1,1,96 -BRDA:20,2,0,50 -BRDA:20,2,1,48 -BRDA:28,3,0,545 -BRDA:28,3,1,493 -BRDA:29,4,0,44 -BRDA:29,4,1,403 -BRDA:32,5,0,11 -BRDA:32,5,1,392 -BRDA:35,6,0,22 -BRDA:35,6,1,370 -BRDA:38,7,0,5 -BRDA:38,7,1,365 -BRDA:41,8,0,140 -BRDA:41,8,1,225 -BRDA:45,9,0,50 -BRDA:45,9,1,175 -BRDA:49,10,0,52 -BRDA:49,10,1,123 -BRDA:52,11,0,52 -BRDA:52,11,1,9 -BRDA:57,12,0,27 -BRDA:57,12,1,96 -BRDA:67,13,0,52 -BRDA:67,13,1,44 -BRDA:79,14,0,0 -BRDA:79,14,1,44 -BRDA:95,15,0,52 -BRDA:95,15,1,46 -BRDA:97,16,0,25 -BRDA:97,16,1,27 -BRDA:103,17,0,63 -BRDA:103,17,1,0 -BRDA:109,18,0,46 -BRDA:109,18,1,0 -BRDA:112,19,0,100 -BRDA:112,19,1,16 -BRF:38 -BRH:38 -end_of_record diff --git a/esm/.eslintrc b/esm/.eslintrc index 6284ba7..06410ca 100644 --- a/esm/.eslintrc +++ b/esm/.eslintrc @@ -1,5 +1,5 @@ { - "extends": "eslint:recommended", + // "extends": "eslint:recommended", "globals": { "Promise": true }, diff --git a/esm/index.js b/esm/index.js index 690e61a..703a20a 100644 --- a/esm/index.js +++ b/esm/index.js @@ -1,116 +1,218 @@ -/*! (c) 2017 Andrea Giammarchi (ISC) */ +/*! (c) 2018 Andrea Giammarchi (ISC) */ -/** - * This code is a revisited port of the snabbdom vDOM diffing logic, - * the same that fuels as fork Vue.js, Preact, and other libraries. - * @credits https://github.com/snabbdom/snabbdom - */ - -const identity = O => O; +import { + eqeq, identity, indexOf, isReversed, next, + append, remove, + smartDiff +} from './utils.js'; const domdiff = ( parentNode, // where changes happen currentNodes, // Array of current items/nodes futureNodes, // Array of future items/nodes - getNode, // optional way to retrieve a node from an item - beforeNode // optional item/node to use as insertBefore delimiter + options // optional object with one of the following properties + // before: domNode + // compare(generic, generic) => true if same generic + // node(generic) => Node ) => { - const get = getNode || identity; - const before = beforeNode == null ? null : get(beforeNode); - let currentStart = 0, futureStart = 0; - let currentEnd = currentNodes.length - 1; - let currentStartNode = currentNodes[0]; - let currentEndNode = currentNodes[currentEnd]; - let futureEnd = futureNodes.length - 1; - let futureStartNode = futureNodes[0]; - let futureEndNode = futureNodes[futureEnd]; - while (currentStart <= currentEnd && futureStart <= futureEnd) { - if (currentStartNode == null) { - currentStartNode = currentNodes[++currentStart]; - } - else if (currentEndNode == null) { - currentEndNode = currentNodes[--currentEnd]; - } - else if (futureStartNode == null) { - futureStartNode = futureNodes[++futureStart]; - } - else if (futureEndNode == null) { - futureEndNode = futureNodes[--futureEnd]; - } - else if (currentStartNode == futureStartNode) { - currentStartNode = currentNodes[++currentStart]; - futureStartNode = futureNodes[++futureStart]; - } - else if (currentEndNode == futureEndNode) { - currentEndNode = currentNodes[--currentEnd]; - futureEndNode = futureNodes[--futureEnd]; - } - else if (currentStartNode == futureEndNode) { - parentNode.insertBefore( - get(currentStartNode), - get(currentEndNode).nextSibling || before + if (!options) + options = {}; + + const compare = options.compare || eqeq; + const get = options.node || identity; + const before = options.before == null ? null : get(options.before, 0); + + const currentLength = currentNodes.length; + let currentEnd = currentLength; + let currentStart = 0; + + let futureEnd = futureNodes.length; + let futureStart = 0; + + // common prefix + while ( + currentStart < currentEnd && + futureStart < futureEnd && + compare(currentNodes[currentStart], futureNodes[futureStart]) + ) { + currentStart++; + futureStart++; + } + + // common suffix + while ( + currentStart < currentEnd && + futureStart < futureEnd && + compare(currentNodes[currentEnd - 1], futureNodes[futureEnd - 1]) + ) { + currentEnd--; + futureEnd--; + } + + const currentSame = currentStart === currentEnd; + const futureSame = futureStart === futureEnd; + + // same list + if (currentSame && futureSame) + return futureNodes; + + // only stuff to add + if (currentSame && futureStart < futureEnd) { + append( + get, + parentNode, + futureNodes, + futureStart, + futureEnd, + next(get, currentNodes, currentStart, currentLength, before) + ); + return futureNodes; + } + + // only stuff to remove + if (futureSame && currentStart < currentEnd) { + remove( + get, + currentNodes, + currentStart, + currentEnd + ); + return futureNodes; + } + + const currentChanges = currentEnd - currentStart; + const futureChanges = futureEnd - futureStart; + let i = -1; + + // 2 simple indels: the shortest sequence is a subsequence of the longest + if (currentChanges < futureChanges) { + i = indexOf( + futureNodes, + futureStart, + futureEnd, + currentNodes, + currentStart, + currentEnd, + compare + ); + // inner diff + if (-1 < i) { + append( + get, + parentNode, + futureNodes, + futureStart, + i, + get(currentNodes[currentStart], 0) ); - currentStartNode = currentNodes[++currentStart]; - futureEndNode = futureNodes[--futureEnd]; - } - else if (currentEndNode == futureStartNode) { - parentNode.insertBefore( - get(currentEndNode), - get(currentStartNode) + append( + get, + parentNode, + futureNodes, + i + currentChanges, + futureEnd, + next(get, currentNodes, currentEnd, currentLength, before) ); - currentEndNode = currentNodes[--currentEnd]; - futureStartNode = futureNodes[++futureStart]; - } - else { - let index = currentNodes.indexOf(futureStartNode); - if (index < 0) { - parentNode.insertBefore( - get(futureStartNode), - get(currentStartNode) - ); - futureStartNode = futureNodes[++futureStart]; - } - else { - let el = currentNodes[index]; - // until I am sure the else could never happen - // it might be a vDOM thing 'cause it never happens here - /* istanbul ignore if */ - if (el != futureStartNode) { - parentNode.insertBefore( - get(futureStartNode), - get(currentStartNode) - ); - } else { - currentNodes[index] = null; - parentNode.insertBefore( - get(el), - get(currentStartNode) - ); - } - futureStartNode = futureNodes[++futureStart]; - } + return futureNodes; } } - if (currentStart > currentEnd) { - const pin = futureNodes[futureEnd + 1]; - const place = pin != null ? get(pin) : before; - while (futureStart <= futureEnd) { - const ch = futureNodes[futureStart++]; - // until I am sure the else could never happen - // it might be a vDOM thing 'cause it never happens here - /* istanbul ignore else */ - if (ch != null) parentNode.insertBefore(get(ch), place); + /* istanbul ignore else */ + else if (futureChanges < currentChanges) { + i = indexOf( + currentNodes, + currentStart, + currentEnd, + futureNodes, + futureStart, + futureEnd, + compare + ); + // outer diff + if (-1 < i) { + remove( + get, + currentNodes, + currentStart, + i + ); + remove( + get, + currentNodes, + i + futureChanges, + currentEnd + ); + return futureNodes; } } - // until I am sure the else could never happen - // it might be a vDOM thing 'cause it never happens here + + // common case with one replacement for many nodes + // or many nodes replaced for a single one /* istanbul ignore else */ - else if (futureStart > futureEnd) { - while (currentStart <= currentEnd) { - const ch = currentNodes[currentStart++]; - if (ch != null) parentNode.removeChild(get(ch)); - } + if ((currentChanges < 2 || futureChanges < 2)) { + append( + get, + parentNode, + futureNodes, + futureStart, + futureEnd, + get(currentNodes[currentStart], 0) + ); + remove( + get, + currentNodes, + currentStart, + currentEnd + ); + return futureNodes; + } + + // the half match diff part has been skipped in petit-dom + // https://github.com/yelouafi/petit-dom/blob/bd6f5c919b5ae5297be01612c524c40be45f14a7/src/vdom.js#L391-L397 + // accordingly, I think it's safe to skip in here too + // if one day it'll come out like the speediest thing ever to do + // then I might add it in here too + + // Extra: before going too fancy, what about reversed lists ? + // This should bail out pretty quickly if that's not the case. + if ( + currentChanges === futureChanges && + isReversed( + futureNodes, + futureEnd, + currentNodes, + currentStart, + currentEnd, + compare + ) + ) { + append( + get, + parentNode, + futureNodes, + futureStart, + futureEnd, + next(get, currentNodes, currentEnd, currentLength, before) + ); + return futureNodes; } + + // last resort through a smart diff + smartDiff( + get, + parentNode, + futureNodes, + futureStart, + futureEnd, + futureChanges, + currentNodes, + currentStart, + currentEnd, + currentChanges, + currentLength, + compare, + before + ); + return futureNodes; }; diff --git a/esm/utils.js b/esm/utils.js new file mode 100644 index 0000000..b797065 --- /dev/null +++ b/esm/utils.js @@ -0,0 +1,379 @@ +import {indexOf as iOF} from 'uarray'; + +export const append = (get, parent, children, start, end, before) => { + const isSelect = 'selectedIndex' in parent; + let noSelection = isSelect; + while (start < end) { + const child = get(children[start], 1); + parent.insertBefore(child, before); + if (isSelect && noSelection && child.selected) { + noSelection = !noSelection; + let {selectedIndex} = parent; + parent.selectedIndex = selectedIndex < 0 ? + start : + iOF.call(parent.querySelectorAll('option'), child); + } + start++; + } +}; + +export const eqeq = (a, b) => a == b; + +export const identity = O => O; + +export const indexOf = ( + moreNodes, + moreStart, + moreEnd, + lessNodes, + lessStart, + lessEnd, + compare +) => { + const length = lessEnd - lessStart; + /* istanbul ignore if */ + if (length < 1) + return -1; + while ((moreEnd - moreStart) >= length) { + let m = moreStart; + let l = lessStart; + while ( + m < moreEnd && + l < lessEnd && + compare(moreNodes[m], lessNodes[l]) + ) { + m++; + l++; + } + if (l === lessEnd) + return moreStart; + moreStart = m + 1; + } + return -1; +}; + +export const isReversed = ( + futureNodes, + futureEnd, + currentNodes, + currentStart, + currentEnd, + compare +) => { + while ( + currentStart < currentEnd && + compare( + currentNodes[currentStart], + futureNodes[futureEnd - 1] + )) { + currentStart++; + futureEnd--; + }; + return futureEnd === 0; +}; + +export const next = (get, list, i, length, before) => i < length ? + get(list[i], 0) : + (0 < i ? + get(list[i - 1], -0).nextSibling : + before); + +export const remove = (get, children, start, end) => { + while (start < end) + drop(get(children[start++], -1)); +}; + +// - - - - - - - - - - - - - - - - - - - +// diff related constants and utilities +// - - - - - - - - - - - - - - - - - - - + +const DELETION = -1; +const INSERTION = 1; +const SKIP = 0; +const SKIP_OND = 50; + +const HS = ( + futureNodes, + futureStart, + futureEnd, + futureChanges, + currentNodes, + currentStart, + currentEnd, + currentChanges +) => { + + let k = 0; + /* istanbul ignore next */ + let minLen = futureChanges < currentChanges ? futureChanges : currentChanges; + const link = Array(minLen++); + const tresh = Array(minLen); + tresh[0] = -1; + + for (let i = 1; i < minLen; i++) + tresh[i] = currentEnd; + + const nodes = currentNodes.slice(currentStart, currentEnd); + + for (let i = futureStart; i < futureEnd; i++) { + const index = nodes.indexOf(futureNodes[i]); + if (-1 < index) { + const idxInOld = index + currentStart; + k = findK(tresh, minLen, idxInOld); + /* istanbul ignore else */ + if (-1 < k) { + tresh[k] = idxInOld; + link[k] = { + newi: i, + oldi: idxInOld, + prev: link[k - 1] + }; + } + } + } + + k = --minLen; + --currentEnd; + while (tresh[k] > currentEnd) --k; + + minLen = currentChanges + futureChanges - k; + const diff = Array(minLen); + let ptr = link[k]; + --futureEnd; + while (ptr) { + const {newi, oldi} = ptr; + while (futureEnd > newi) { + diff[--minLen] = INSERTION; + --futureEnd; + } + while (currentEnd > oldi) { + diff[--minLen] = DELETION; + --currentEnd; + } + diff[--minLen] = SKIP; + --futureEnd; + --currentEnd; + ptr = ptr.prev; + } + while (futureEnd >= futureStart) { + diff[--minLen] = INSERTION; + --futureEnd; + } + while (currentEnd >= currentStart) { + diff[--minLen] = DELETION; + --currentEnd; + } + return diff; +}; + +// this is pretty much the same petit-dom code without the delete map part +// https://github.com/yelouafi/petit-dom/blob/bd6f5c919b5ae5297be01612c524c40be45f14a7/src/vdom.js#L556-L561 +const OND = ( + futureNodes, + futureStart, + rows, + currentNodes, + currentStart, + cols, + compare +) => { + const length = rows + cols; + const v = []; + let d, k, r, c, pv, cv, pd; + outer: for (d = 0; d <= length; d++) { + /* istanbul ignore if */ + if (d > SKIP_OND) + return null; + pd = d - 1; + /* istanbul ignore next */ + pv = d ? v[d - 1] : [0, 0]; + cv = v[d] = []; + for (k = -d; k <= d; k += 2) { + if (k === -d || (k !== d && pv[pd + k - 1] < pv[pd + k + 1])) { + c = pv[pd + k + 1]; + } else { + c = pv[pd + k - 1] + 1; + } + r = c - k; + while ( + c < cols && + r < rows && + compare( + currentNodes[currentStart + c], + futureNodes[futureStart + r] + ) + ) { + c++; + r++; + } + if (c === cols && r === rows) { + break outer; + } + cv[d + k] = c; + } + } + + const diff = Array(d / 2 + length / 2); + let diffIdx = diff.length - 1; + for (d = v.length - 1; d >= 0; d--) { + while ( + c > 0 && + r > 0 && + compare( + currentNodes[currentStart + c - 1], + futureNodes[futureStart + r - 1] + ) + ) { + // diagonal edge = equality + diff[diffIdx--] = SKIP; + c--; + r--; + } + if (!d) + break; + pd = d - 1; + /* istanbul ignore next */ + pv = d ? v[d - 1] : [0, 0]; + k = c - r; + if (k === -d || (k !== d && pv[pd + k - 1] < pv[pd + k + 1])) { + // vertical edge = insertion + r--; + diff[diffIdx--] = INSERTION; + } else { + // horizontal edge = deletion + c--; + diff[diffIdx--] = DELETION; + } + } + return diff; +}; + +const applyDiff = ( + diff, + get, + parentNode, + futureNodes, + futureStart, + currentNodes, + currentStart, + currentLength, + before +) => { + const live = []; + const length = diff.length; + let currentIndex = currentStart; + let i = 0; + while (i < length) { + switch (diff[i++]) { + case SKIP: + futureStart++; + currentIndex++; + break; + case INSERTION: + // TODO: bulk appends for sequential nodes + live.push(futureNodes[futureStart]); + append( + get, + parentNode, + futureNodes, + futureStart++, + futureStart, + currentIndex < currentLength ? + get(currentNodes[currentIndex], 0) : + before + ); + break; + case DELETION: + currentIndex++; + break; + } + } + i = 0; + while (i < length) { + switch (diff[i++]) { + case SKIP: + currentStart++; + break; + case DELETION: + // TODO: bulk removes for sequential nodes + if (-1 < live.indexOf(currentNodes[currentStart])) + currentStart++; + else + remove( + get, + currentNodes, + currentStart++, + currentStart + ); + break; + } + } +}; + +const findK = (ktr, length, j) => { + let lo = 1; + let hi = length; + while (lo < hi) { + const mid = ((lo + hi) / 2) >>> 0; + if (j < ktr[mid]) + hi = mid; + else + lo = mid + 1; + } + return lo; +} + +export const smartDiff = ( + get, + parentNode, + futureNodes, + futureStart, + futureEnd, + futureChanges, + currentNodes, + currentStart, + currentEnd, + currentChanges, + currentLength, + compare, + before +) => { + applyDiff( + OND( + futureNodes, + futureStart, + futureChanges, + currentNodes, + currentStart, + currentChanges, + compare + ) || + HS( + futureNodes, + futureStart, + futureEnd, + futureChanges, + currentNodes, + currentStart, + currentEnd, + currentChanges + ), + get, + parentNode, + futureNodes, + futureStart, + currentNodes, + currentStart, + currentLength, + before + ); +}; + +const drop = node => (node.remove || dropChild).call(node); + +function dropChild() { + const {parentNode} = this; + /* istanbul ignore else */ + if (parentNode) + parentNode.removeChild(this); +} diff --git a/index.js b/index.js index 4654fd6..b0b5331 100644 --- a/index.js +++ b/index.js @@ -1,102 +1,394 @@ -var domdiff = (function (exports) { -'use strict'; - -/*! (c) 2017 Andrea Giammarchi (ISC) */ - -/** - * This code is a revisited port of the snabbdom vDOM diffing logic, - * the same that fuels as fork Vue.js, Preact, and other libraries. - * @credits https://github.com/snabbdom/snabbdom - */ - -var identity = function identity(O) { - return O; -}; - -var domdiff = function domdiff(parentNode, // where changes happen -currentNodes, // Array of current items/nodes -futureNodes, // Array of future items/nodes -getNode, // optional way to retrieve a node from an item -beforeNode // optional item/node to use as insertBefore delimiter -) { - var get = getNode || identity; - var before = beforeNode == null ? null : get(beforeNode); - var currentStart = 0, - futureStart = 0; - var currentEnd = currentNodes.length - 1; - var currentStartNode = currentNodes[0]; - var currentEndNode = currentNodes[currentEnd]; - var futureEnd = futureNodes.length - 1; - var futureStartNode = futureNodes[0]; - var futureEndNode = futureNodes[futureEnd]; - while (currentStart <= currentEnd && futureStart <= futureEnd) { - if (currentStartNode == null) { - currentStartNode = currentNodes[++currentStart]; - } else if (currentEndNode == null) { - currentEndNode = currentNodes[--currentEnd]; - } else if (futureStartNode == null) { - futureStartNode = futureNodes[++futureStart]; - } else if (futureEndNode == null) { - futureEndNode = futureNodes[--futureEnd]; - } else if (currentStartNode == futureStartNode) { - currentStartNode = currentNodes[++currentStart]; - futureStartNode = futureNodes[++futureStart]; - } else if (currentEndNode == futureEndNode) { - currentEndNode = currentNodes[--currentEnd]; - futureEndNode = futureNodes[--futureEnd]; - } else if (currentStartNode == futureEndNode) { - parentNode.insertBefore(get(currentStartNode), get(currentEndNode).nextSibling || before); - currentStartNode = currentNodes[++currentStart]; - futureEndNode = futureNodes[--futureEnd]; - } else if (currentEndNode == futureStartNode) { - parentNode.insertBefore(get(currentEndNode), get(currentStartNode)); - currentEndNode = currentNodes[--currentEnd]; - futureStartNode = futureNodes[++futureStart]; - } else { - var index = currentNodes.indexOf(futureStartNode); - if (index < 0) { - parentNode.insertBefore(get(futureStartNode), get(currentStartNode)); - futureStartNode = futureNodes[++futureStart]; - } else { - var el = currentNodes[index]; - // until I am sure the else could never happen - // it might be a vDOM thing 'cause it never happens here - /* istanbul ignore if */ - if (el != futureStartNode) { - parentNode.insertBefore(get(futureStartNode), get(currentStartNode)); +var domdiff = (function () { + 'use strict'; + + var _ref = [], + indexOf = _ref.indexOf; + + var append = function append(get, parent, children, start, end, before) { + var isSelect = 'selectedIndex' in parent; + var noSelection = isSelect; + + while (start < end) { + var child = get(children[start], 1); + parent.insertBefore(child, before); + + if (isSelect && noSelection && child.selected) { + noSelection = !noSelection; + var selectedIndex = parent.selectedIndex; + parent.selectedIndex = selectedIndex < 0 ? start : indexOf.call(parent.querySelectorAll('option'), child); + } + + start++; + } + }; + var eqeq = function eqeq(a, b) { + return a == b; + }; + var identity = function identity(O) { + return O; + }; + var indexOf$1 = function indexOf(moreNodes, moreStart, moreEnd, lessNodes, lessStart, lessEnd, compare) { + var length = lessEnd - lessStart; + /* istanbul ignore if */ + + if (length < 1) return -1; + + while (moreEnd - moreStart >= length) { + var m = moreStart; + var l = lessStart; + + while (m < moreEnd && l < lessEnd && compare(moreNodes[m], lessNodes[l])) { + m++; + l++; + } + + if (l === lessEnd) return moreStart; + moreStart = m + 1; + } + + return -1; + }; + var isReversed = function isReversed(futureNodes, futureEnd, currentNodes, currentStart, currentEnd, compare) { + while (currentStart < currentEnd && compare(currentNodes[currentStart], futureNodes[futureEnd - 1])) { + currentStart++; + futureEnd--; + } + return futureEnd === 0; + }; + var next = function next(get, list, i, length, before) { + return i < length ? get(list[i], 0) : 0 < i ? get(list[i - 1], -0).nextSibling : before; + }; + var remove = function remove(get, children, start, end) { + while (start < end) { + drop(get(children[start++], -1)); + } + }; // - - - - - - - - - - - - - - - - - - - + // diff related constants and utilities + // - - - - - - - - - - - - - - - - - - - + + var DELETION = -1; + var INSERTION = 1; + var SKIP = 0; + var SKIP_OND = 50; + + var HS = function HS(futureNodes, futureStart, futureEnd, futureChanges, currentNodes, currentStart, currentEnd, currentChanges) { + var k = 0; + /* istanbul ignore next */ + + var minLen = futureChanges < currentChanges ? futureChanges : currentChanges; + var link = Array(minLen++); + var tresh = Array(minLen); + tresh[0] = -1; + + for (var i = 1; i < minLen; i++) { + tresh[i] = currentEnd; + } + + var nodes = currentNodes.slice(currentStart, currentEnd); + + for (var _i = futureStart; _i < futureEnd; _i++) { + var index = nodes.indexOf(futureNodes[_i]); + + if (-1 < index) { + var idxInOld = index + currentStart; + k = findK(tresh, minLen, idxInOld); + /* istanbul ignore else */ + + if (-1 < k) { + tresh[k] = idxInOld; + link[k] = { + newi: _i, + oldi: idxInOld, + prev: link[k - 1] + }; + } + } + } + + k = --minLen; + --currentEnd; + + while (tresh[k] > currentEnd) { + --k; + } + + minLen = currentChanges + futureChanges - k; + var diff = Array(minLen); + var ptr = link[k]; + --futureEnd; + + while (ptr) { + var _ptr = ptr, + newi = _ptr.newi, + oldi = _ptr.oldi; + + while (futureEnd > newi) { + diff[--minLen] = INSERTION; + --futureEnd; + } + + while (currentEnd > oldi) { + diff[--minLen] = DELETION; + --currentEnd; + } + + diff[--minLen] = SKIP; + --futureEnd; + --currentEnd; + ptr = ptr.prev; + } + + while (futureEnd >= futureStart) { + diff[--minLen] = INSERTION; + --futureEnd; + } + + while (currentEnd >= currentStart) { + diff[--minLen] = DELETION; + --currentEnd; + } + + return diff; + }; // this is pretty much the same petit-dom code without the delete map part + // https://github.com/yelouafi/petit-dom/blob/bd6f5c919b5ae5297be01612c524c40be45f14a7/src/vdom.js#L556-L561 + + + var OND = function OND(futureNodes, futureStart, rows, currentNodes, currentStart, cols, compare) { + var length = rows + cols; + var v = []; + var d, k, r, c, pv, cv, pd; + + outer: for (d = 0; d <= length; d++) { + /* istanbul ignore if */ + if (d > SKIP_OND) return null; + pd = d - 1; + /* istanbul ignore next */ + + pv = d ? v[d - 1] : [0, 0]; + cv = v[d] = []; + + for (k = -d; k <= d; k += 2) { + if (k === -d || k !== d && pv[pd + k - 1] < pv[pd + k + 1]) { + c = pv[pd + k + 1]; } else { - currentNodes[index] = null; - parentNode.insertBefore(get(el), get(currentStartNode)); + c = pv[pd + k - 1] + 1; + } + + r = c - k; + + while (c < cols && r < rows && compare(currentNodes[currentStart + c], futureNodes[futureStart + r])) { + c++; + r++; + } + + if (c === cols && r === rows) { + break outer; } - futureStartNode = futureNodes[++futureStart]; + + cv[d + k] = c; } } - } - if (currentStart > currentEnd) { - var pin = futureNodes[futureEnd + 1]; - var place = pin != null ? get(pin) : before; - while (futureStart <= futureEnd) { - var ch = futureNodes[futureStart++]; - // until I am sure the else could never happen - // it might be a vDOM thing 'cause it never happens here - /* istanbul ignore else */ - if (ch != null) parentNode.insertBefore(get(ch), place); + + var diff = Array(d / 2 + length / 2); + var diffIdx = diff.length - 1; + + for (d = v.length - 1; d >= 0; d--) { + while (c > 0 && r > 0 && compare(currentNodes[currentStart + c - 1], futureNodes[futureStart + r - 1])) { + // diagonal edge = equality + diff[diffIdx--] = SKIP; + c--; + r--; + } + + if (!d) break; + pd = d - 1; + /* istanbul ignore next */ + + pv = d ? v[d - 1] : [0, 0]; + k = c - r; + + if (k === -d || k !== d && pv[pd + k - 1] < pv[pd + k + 1]) { + // vertical edge = insertion + r--; + diff[diffIdx--] = INSERTION; + } else { + // horizontal edge = deletion + c--; + diff[diffIdx--] = DELETION; + } } + + return diff; + }; + + var applyDiff = function applyDiff(diff, get, parentNode, futureNodes, futureStart, currentNodes, currentStart, currentLength, before) { + var live = []; + var length = diff.length; + var currentIndex = currentStart; + var i = 0; + + while (i < length) { + switch (diff[i++]) { + case SKIP: + futureStart++; + currentIndex++; + break; + + case INSERTION: + // TODO: bulk appends for sequential nodes + live.push(futureNodes[futureStart]); + append(get, parentNode, futureNodes, futureStart++, futureStart, currentIndex < currentLength ? get(currentNodes[currentIndex], 0) : before); + break; + + case DELETION: + currentIndex++; + break; + } + } + + i = 0; + + while (i < length) { + switch (diff[i++]) { + case SKIP: + currentStart++; + break; + + case DELETION: + // TODO: bulk removes for sequential nodes + if (-1 < live.indexOf(currentNodes[currentStart])) currentStart++;else remove(get, currentNodes, currentStart++, currentStart); + break; + } + } + }; + + var findK = function findK(ktr, length, j) { + var lo = 1; + var hi = length; + + while (lo < hi) { + var mid = (lo + hi) / 2 >>> 0; + if (j < ktr[mid]) hi = mid;else lo = mid + 1; + } + + return lo; + }; + + var smartDiff = function smartDiff(get, parentNode, futureNodes, futureStart, futureEnd, futureChanges, currentNodes, currentStart, currentEnd, currentChanges, currentLength, compare, before) { + applyDiff(OND(futureNodes, futureStart, futureChanges, currentNodes, currentStart, currentChanges, compare) || HS(futureNodes, futureStart, futureEnd, futureChanges, currentNodes, currentStart, currentEnd, currentChanges), get, parentNode, futureNodes, futureStart, currentNodes, currentStart, currentLength, before); + }; + + var drop = function drop(node) { + return (node.remove || dropChild).call(node); + }; + + function dropChild() { + var parentNode = this.parentNode; + /* istanbul ignore else */ + + if (parentNode) parentNode.removeChild(this); } - // until I am sure the else could never happen - // it might be a vDOM thing 'cause it never happens here - /* istanbul ignore else */ - else if (futureStart > futureEnd) { - while (currentStart <= currentEnd) { - var _ch = currentNodes[currentStart++]; - if (_ch != null) parentNode.removeChild(get(_ch)); + + /*! (c) 2018 Andrea Giammarchi (ISC) */ + + var domdiff = function domdiff(parentNode, // where changes happen + currentNodes, // Array of current items/nodes + futureNodes, // Array of future items/nodes + options // optional object with one of the following properties + // before: domNode + // compare(generic, generic) => true if same generic + // node(generic) => Node + ) { + if (!options) options = {}; + var compare = options.compare || eqeq; + var get = options.node || identity; + var before = options.before == null ? null : get(options.before, 0); + var currentLength = currentNodes.length; + var currentEnd = currentLength; + var currentStart = 0; + var futureEnd = futureNodes.length; + var futureStart = 0; // common prefix + + while (currentStart < currentEnd && futureStart < futureEnd && compare(currentNodes[currentStart], futureNodes[futureStart])) { + currentStart++; + futureStart++; + } // common suffix + + + while (currentStart < currentEnd && futureStart < futureEnd && compare(currentNodes[currentEnd - 1], futureNodes[futureEnd - 1])) { + currentEnd--; + futureEnd--; + } + + var currentSame = currentStart === currentEnd; + var futureSame = futureStart === futureEnd; // same list + + if (currentSame && futureSame) return futureNodes; // only stuff to add + + if (currentSame && futureStart < futureEnd) { + append(get, parentNode, futureNodes, futureStart, futureEnd, next(get, currentNodes, currentStart, currentLength, before)); + return futureNodes; + } // only stuff to remove + + + if (futureSame && currentStart < currentEnd) { + remove(get, currentNodes, currentStart, currentEnd); + return futureNodes; + } + + var currentChanges = currentEnd - currentStart; + var futureChanges = futureEnd - futureStart; + var i = -1; // 2 simple indels: the shortest sequence is a subsequence of the longest + + if (currentChanges < futureChanges) { + i = indexOf$1(futureNodes, futureStart, futureEnd, currentNodes, currentStart, currentEnd, compare); // inner diff + + if (-1 < i) { + append(get, parentNode, futureNodes, futureStart, i, get(currentNodes[currentStart], 0)); + append(get, parentNode, futureNodes, i + currentChanges, futureEnd, next(get, currentNodes, currentEnd, currentLength, before)); + return futureNodes; } } - return futureNodes; -}; + /* istanbul ignore else */ + else if (futureChanges < currentChanges) { + i = indexOf$1(currentNodes, currentStart, currentEnd, futureNodes, futureStart, futureEnd, compare); // outer diff + + if (-1 < i) { + remove(get, currentNodes, currentStart, i); + remove(get, currentNodes, i + futureChanges, currentEnd); + return futureNodes; + } + } // common case with one replacement for many nodes + // or many nodes replaced for a single one + + /* istanbul ignore else */ + + + if (currentChanges < 2 || futureChanges < 2) { + append(get, parentNode, futureNodes, futureStart, futureEnd, get(currentNodes[currentStart], 0)); + remove(get, currentNodes, currentStart, currentEnd); + return futureNodes; + } // the half match diff part has been skipped in petit-dom + // https://github.com/yelouafi/petit-dom/blob/bd6f5c919b5ae5297be01612c524c40be45f14a7/src/vdom.js#L391-L397 + // accordingly, I think it's safe to skip in here too + // if one day it'll come out like the speediest thing ever to do + // then I might add it in here too + // Extra: before going too fancy, what about reversed lists ? + // This should bail out pretty quickly if that's not the case. + + + if (currentChanges === futureChanges && isReversed(futureNodes, futureEnd, currentNodes, currentStart, currentEnd, compare)) { + append(get, parentNode, futureNodes, futureStart, futureEnd, next(get, currentNodes, currentEnd, currentLength, before)); + return futureNodes; + } // last resort through a smart diff + + smartDiff(get, parentNode, futureNodes, futureStart, futureEnd, futureChanges, currentNodes, currentStart, currentEnd, currentChanges, currentLength, compare, before); + return futureNodes; + }; + -return domdiff; + return domdiff; -}({})); +}()); diff --git a/min.js b/min.js index 239a5e8..cd28ee8 100644 --- a/min.js +++ b/min.js @@ -1,2 +1,2 @@ -var domdiff=function(e){"use strict";/*! (c) 2017 Andrea Giammarchi (ISC) */ -var l=function(e){return e};return function(e,r,n,f,i){for(var s=f||l,t=null==i?null:s(i),u=0,o=0,v=r.length-1,a=r[0],B=r[v],c=n.length-1,d=n[0],g=n[c];u<=v&&o<=c;)if(null==a)a=r[++u];else if(null==B)B=r[--v];else if(null==d)d=n[++o];else if(null==g)g=n[--c];else if(a==d)a=r[++u],d=n[++o];else if(B==g)B=r[--v],g=n[--c];else if(a==g)e.insertBefore(s(a),s(B).nextSibling||t),a=r[++u],g=n[--c];else if(B==d)e.insertBefore(s(B),s(a)),B=r[--v],d=n[++o];else{var h=r.indexOf(d);if(h<0)e.insertBefore(s(d),s(a)),d=n[++o];else{var m=r[h];m!=d?e.insertBefore(s(d),s(a)):(r[h]=null,e.insertBefore(s(m),s(a))),d=n[++o]}}if(u>v)for(var x=n[c+1],b=null!=x?s(x):t;o<=c;){var C=n[o++];null!=C&&e.insertBefore(s(C),b)}else if(o>c)for(;u<=v;){var O=r[u++];null!=O&&e.removeChild(s(O))}return n}}(); \ No newline at end of file +var domdiff=function(){"use strict";function g(r,e,n,f,t,o){for(var i=("selectedIndex"in e),a=i;fi;)--u;c=a+f-u;var g=Array(c),p=l[u];for(--n;p;){for(var k=p.newi,y=p.oldi;k>>0;n index.js", - "cjs": "ascjs ./esm ./cjs", + "cleanup": "echo \"$(cat index.js | sed 's/return exports;/return domdiff;/' | sed -e 's/exports.*;//g' | sed 's/exports//' | sed 's/}({}));/}());/')\" > index.js", + "cjs": "ascjs ./esm ./cjs && sed -i.bck 's/(m => m.__esModule ? m.default : m)//' cjs/utils.js && rm cjs/utils.js.bck", "coveralls": "cat ./coverage/lcov.info | coveralls", "min": "uglifyjs index.js --support-ie8 --comments=/^!/ -cmo min.js", "size": "echo \"gzip: $(cat min.js | gzip -c9 | wc -c)\" && echo \"brotli: $(cat min.js | brotli | wc -c)\" && echo ''", @@ -22,32 +23,20 @@ "author": "Andrea Giammarchi", "license": "ISC", "devDependencies": { - "ascjs": "^2.4.0", - "babel-cli": "^6.26.0", - "babel-plugin-check-es2015-constants": "^6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoping": "^6.26.0", - "babel-plugin-transform-es2015-classes": "^6.24.1", - "babel-plugin-transform-es2015-computed-properties": "^6.24.1", - "babel-plugin-transform-es2015-destructuring": "^6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", - "babel-plugin-transform-es2015-function-name": "^6.24.1", - "babel-plugin-transform-es2015-literals": "^6.22.0", - "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", - "babel-plugin-transform-es2015-spread": "^6.22.0", - "babel-plugin-transform-es2015-template-literals": "^6.22.0", - "coveralls": "^3.0.0", + "@babel/core": "^7.8.7", + "@babel/preset-env": "^7.8.7", + "ascjs": "^3.1.2", + "coveralls": "^3.0.9", "istanbul": "^0.4.5", - "rollup": "^0.52.0", - "rollup-plugin-babel": "^3.0.2", + "rollup": "^2.0.6", + "rollup-plugin-babel": "^4.4.0", + "rollup-plugin-node-resolve": "^5.2.0", "tressa": "^0.3.1", - "uglify-js": "^2.8.29" + "uglify-js": "^3.8.0" }, "directories": { "test": "test" }, - "dependencies": {}, "repository": { "type": "git", "url": "git+https://github.com/WebReflection/domdiff.git" @@ -55,5 +44,8 @@ "bugs": { "url": "https://github.com/WebReflection/domdiff/issues" }, - "homepage": "https://github.com/WebReflection/domdiff#readme" + "homepage": "https://github.com/WebReflection/domdiff#readme", + "dependencies": { + "uarray": "^1.0.0" + } } diff --git a/rollup.config.js b/rollup.config.js index 5de2f84..5366875 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,12 +1,15 @@ import babel from 'rollup-plugin-babel'; - +import resolve from 'rollup-plugin-node-resolve'; export default { input: 'esm/index.js', plugins: [ + resolve(), babel({ - plugins: require('./babel-plugins.json') + presets: ['@babel/preset-env'] }) ], + context: 'null', + moduleContext: 'null', output: { exports: 'named', file: 'index.js', diff --git a/test/benchmark.html b/test/benchmark.html new file mode 100644 index 0000000..7b8351c --- /dev/null +++ b/test/benchmark.html @@ -0,0 +1,144 @@ + + + + DOMDiff Benchmark + + + + + + \ No newline at end of file diff --git a/test/diff.html b/test/diff.html new file mode 100644 index 0000000..fe8ac07 --- /dev/null +++ b/test/diff.html @@ -0,0 +1,104 @@ + + + + DOM Diff - Bob & Lucy + + +
+ + + \ No newline at end of file diff --git a/test/fuzzysort.html b/test/fuzzysort.html new file mode 100644 index 0000000..5df0c2a --- /dev/null +++ b/test/fuzzysort.html @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/index.html b/test/index.html index 7a067a2..8d8a59b 100644 --- a/test/index.html +++ b/test/index.html @@ -5,7 +5,7 @@ - + \ No newline at end of file diff --git a/test/node.js b/test/node.js index 25103a8..a91b450 100644 --- a/test/node.js +++ b/test/node.js @@ -1,4 +1,5 @@ global.tressa = require('tressa'); +global.Set = undefined; global.domdiff = require('../cjs').default; global.document = { createTextNode: function (value) { @@ -12,16 +13,29 @@ global.document = { ); }, body: { + ownerDocument: { + createDocumentFragment: function () { + var cn = []; + return { + appendChild: function (node) { + node.parentNode = this; + cn.push(node); + } + }; + } + }, childNodes: [], insertBefore: function (before, after) { var cn = this.childNodes; var i = cn.indexOf(before); if (-1 < i) cn.splice(i, 1); + before.parentNode = this; if (after == null) cn.push(before); else cn.splice(cn.indexOf(after), 0, before); }, removeChild: function (child) { var cn = this.childNodes; + delete child.parentNode; cn.splice(cn.indexOf(child), 1); } } diff --git a/test/test.js b/test/test.js index e179b39..4498354 100644 --- a/test/test.js +++ b/test/test.js @@ -6,11 +6,19 @@ var clean = function () { }; var compare = function (state, value) { assert( + state.length === value.length && value.split('').every(function (v, i) { return state[i] === nodes[v]; }), value || '[empy]' ); + if (document.body.textContent) { + assert( + state.every(function (node, i) { + return document.body.childNodes[i] === node; + }) + ); + } }; var notNull = function (any) { return any != null; }; @@ -34,6 +42,248 @@ var nodes = { }; clean(); + +var options = {before: document.createTextNode('-')}; +var futureState = domdiff( + document.body, + [], + [nodes.b, nodes.c, nodes.d] +); +compare(futureState, 'bcd'); + +// same list, no changes +futureState = domdiff( + document.body, + futureState, + [nodes.b, nodes.c, nodes.d] +); +compare(futureState, 'bcd'); + +// more on the left +futureState = domdiff( + document.body, + futureState, + [nodes.a, nodes.b, nodes.c, nodes.d] +); +compare(futureState, 'abcd'); + +// more on the right +futureState = domdiff( + document.body, + futureState, + [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e, nodes.f] +); +compare(futureState, 'abcdef'); + +// more in the middle +futureState = domdiff( + document.body, + futureState, + [nodes.a, nodes.b, nodes.c, nodes.g, nodes.h, nodes.i, nodes.d, nodes.e, nodes.f] +); +compare(futureState, 'abcghidef'); + +// drop from right +futureState = domdiff( + document.body, + futureState, + [nodes.a, nodes.b, nodes.c, nodes.g, nodes.h, nodes.i, nodes.d, nodes.e] +); +compare(futureState, 'abcghide'); + +// drop from left +futureState = domdiff( + document.body, + futureState, + [nodes.c, nodes.g, nodes.h, nodes.i, nodes.d, nodes.e] +); +compare(futureState, 'cghide'); + +// drop in between +futureState = domdiff( + document.body, + futureState, + [nodes.c, nodes.g, nodes.d, nodes.e] +); +compare(futureState, 'cgde'); + +// drop all nodes +futureState = domdiff( + document.body, + futureState, + [] +); +compare(futureState, ''); + +domdiff( + document.body, + futureState, + [options.before] +); + +futureState = domdiff( + document.body, + futureState, + [nodes.b, nodes.c, nodes.d], + options +); +compare(futureState, 'bcd'); + +// same list, no changes with before +futureState = domdiff( + document.body, + futureState, + [nodes.b, nodes.c, nodes.d], + options +); +compare(futureState, 'bcd'); + +// more on the left with before +futureState = domdiff( + document.body, + futureState, + [nodes.a, nodes.b, nodes.c, nodes.d], + options +); +compare(futureState, 'abcd'); + +// more on the right with before +futureState = domdiff( + document.body, + futureState, + [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e, nodes.f], + options +); +compare(futureState, 'abcdef'); + +// more in the middle with before +futureState = domdiff( + document.body, + futureState, + [nodes.a, nodes.b, nodes.c, nodes.g, nodes.h, nodes.i, nodes.d, nodes.e, nodes.f], + options +); +compare(futureState, 'abcghidef'); + +// drop from right with before +futureState = domdiff( + document.body, + futureState, + [nodes.a, nodes.b, nodes.c, nodes.g, nodes.h, nodes.i, nodes.d, nodes.e], + options +); +compare(futureState, 'abcghide'); + +// drop from left with before +futureState = domdiff( + document.body, + futureState, + [nodes.c, nodes.g, nodes.h, nodes.i, nodes.d, nodes.e], + options +); +compare(futureState, 'cghide'); + +// drop in between with before +futureState = domdiff( + document.body, + futureState, + [nodes.c, nodes.g, nodes.d, nodes.e], + options +); +compare(futureState, 'cgde'); + + +// drop one in between with before +futureState = domdiff( + document.body, + futureState, + [nodes.c, nodes.g, nodes.e], + options +); +compare(futureState, 'cge'); + +// drop all nodes with before +futureState = domdiff( + document.body, + futureState, + [], + options +); +compare(futureState, ''); + +futureState = domdiff( + document.body, + futureState, + [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e, nodes.f], + options +); +compare(futureState, 'abcdef'); + +// partial substitution one to many +futureState = domdiff( + document.body, + futureState, + [nodes.a, nodes.b, nodes.g, nodes.i, nodes.d, nodes.e, nodes.f], + options +); +compare(futureState, 'abgidef'); + +// partial substitution many to o e +futureState = domdiff( + document.body, + futureState, + [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e, nodes.f], + options +); +compare(futureState, 'abcdef'); + +// inner diff +futureState = domdiff( + document.body, + futureState, + [nodes.j, nodes.g, nodes.a, nodes.b, nodes.c, nodes.d, nodes.e, nodes.f, nodes.h, nodes.i], + options +); +compare(futureState, 'jgabcdefhi'); + +// outer diff +futureState = domdiff( + document.body, + futureState, + [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e, nodes.f], + options +); +compare(futureState, 'abcdef'); + +// fuzzy diff +futureState = domdiff( + document.body, + futureState, + [nodes.a, nodes.g, nodes.c, nodes.d, nodes.h, nodes.i], + options +); +compare(futureState, 'agcdhi'); + +// fuzzy diff +futureState = domdiff( + document.body, + futureState, + [nodes.i, nodes.g, nodes.a, nodes.d, nodes.h, nodes.c], + options +); +compare(futureState, 'igadhc'); + +// reversed diff +futureState = domdiff( + document.body, + futureState, + [nodes.c, nodes.h, nodes.d, nodes.a, nodes.g, nodes.i], + options +); +compare(futureState, 'chdagi'); + +clean(); + var newState = domdiff( document.body, [], @@ -106,7 +356,7 @@ newState = domdiff( nodes.g, nodes.h, nodes.i, nodes.j, nodes.k ], - Object + {node: Object, compare: (a, b) => a === b} ); compare(newState, 'abcdefghijk'); @@ -116,12 +366,15 @@ newState = domdiff( [ nodes.g, nodes.h, nodes.i ], - Object + {node: Object} ); compare(newState, 'ghi'); document.body.insertBefore(nodes.f, nodes.g); -compare([].slice.call(document.body.childNodes), 'fghi'); +if ('onclick' in document.body) { + console.log('browser only'); + compare([].slice.call(document.body.childNodes), 'fghi'); +} clean(); document.body.insertBefore(nodes.k, null); @@ -131,19 +384,18 @@ newState = domdiff( [ nodes.c, nodes.d, nodes.e ], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'cde'); +/* newState = domdiff( document.body, newState, [ null, nodes.c, nodes.d, nodes.e, null ], - null, - nodes.k + {before: nodes.k} ); compare(newState.filter(notNull), 'cde'); @@ -153,11 +405,10 @@ newState = domdiff( [ nodes.a, nodes.c, null, nodes.e, nodes.f ], - null, - nodes.k + {before: nodes.k} ); compare(newState.filter(notNull), 'acef'); - +*/ newState = domdiff( document.body, @@ -166,8 +417,7 @@ newState = domdiff( nodes.c, nodes.d, nodes.e, nodes.g, nodes.h, nodes.i ], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'cdeghi'); @@ -179,8 +429,7 @@ newState = domdiff( nodes.d, nodes.e, nodes.f, nodes.g, nodes.h, nodes.i ], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcdefghi'); @@ -192,8 +441,7 @@ newState = domdiff( nodes.d, nodes.e, nodes.f, nodes.g, nodes.i, nodes.h ], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'bacdefgih'); @@ -201,8 +449,7 @@ newState = domdiff( document.body, newState, [], - null, - nodes.k + {before: nodes.k} ); compare(newState, ''); @@ -212,8 +459,7 @@ newState = domdiff( document.body, newState, [nodes.a], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'a'); @@ -221,8 +467,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abc'); @@ -230,8 +475,7 @@ newState = domdiff( document.body, newState, [nodes.d, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'de'); @@ -239,8 +483,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcde'); @@ -248,8 +491,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.d, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abde'); @@ -257,8 +499,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcde'); @@ -266,8 +507,7 @@ newState = domdiff( document.body, newState, [nodes.b, nodes.c, nodes.d], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'bcd'); @@ -275,8 +515,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcde'); @@ -284,8 +523,7 @@ newState = domdiff( document.body, newState, [], - null, - nodes.k + {before: nodes.k} ); compare(newState, ''); @@ -293,8 +531,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abc'); @@ -302,8 +539,7 @@ newState = domdiff( document.body, newState, [nodes.a, document.createTextNode('b'), nodes.c], - null, - nodes.k + {before: nodes.k} ); assert( newState[1] !== nodes.b && @@ -319,8 +555,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcde'); @@ -328,8 +563,7 @@ newState = domdiff( document.body, newState, [nodes.c, nodes.d, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'cde'); @@ -337,8 +571,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcde'); @@ -346,8 +579,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abc'); @@ -355,8 +587,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcde'); @@ -364,8 +595,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.d, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abde'); @@ -375,8 +605,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcd'); @@ -384,8 +613,7 @@ newState = domdiff( document.body, newState, [nodes.b, nodes.c, nodes.a, nodes.d], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'bcad'); @@ -393,8 +621,31 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c], - null, - nodes.k + {before: nodes.k} +); +compare(newState, 'abc'); + +newState = domdiff( + document.body, + newState, + [nodes.b, nodes.c, nodes.a, nodes.d], + {before: nodes.k} +); +compare(newState, 'bcad'); + +newState = domdiff( + document.body, + newState, + [nodes.c, nodes.b, nodes.d], + {before: nodes.k} +); +compare(newState, 'cbd'); + +newState = domdiff( + document.body, + newState, + [nodes.a, nodes.b, nodes.c], + {before: nodes.k} ); compare(newState, 'abc'); @@ -402,8 +653,7 @@ newState = domdiff( document.body, newState, [nodes.b, nodes.c, nodes.a], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'bca'); @@ -411,8 +661,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcd'); @@ -420,8 +669,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.d, nodes.b, nodes.c], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'adbc'); @@ -429,8 +677,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.d, nodes.b, nodes.c], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'adbc'); @@ -440,8 +687,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcde'); @@ -449,8 +695,7 @@ newState = domdiff( document.body, newState, [nodes.d, nodes.a, nodes.b, nodes.c, nodes.f], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'dabcf'); @@ -458,8 +703,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.d, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'ade'); @@ -467,8 +711,7 @@ newState = domdiff( document.body, newState, [nodes.d, nodes.f], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'df'); @@ -476,8 +719,7 @@ newState = domdiff( document.body, newState, [nodes.b, nodes.d, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'bde'); @@ -485,8 +727,7 @@ newState = domdiff( document.body, newState, [nodes.d, nodes.e, nodes.c], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'dec'); @@ -494,8 +735,7 @@ newState = domdiff( document.body, newState, [nodes.j, nodes.a, nodes.b, nodes.c], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'jabc'); @@ -503,8 +743,7 @@ newState = domdiff( document.body, newState, [nodes.d, nodes.a, nodes.b, nodes.c, nodes.j, nodes.e], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'dabcje'); @@ -512,8 +751,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e, nodes.f, nodes.g, nodes.h], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcdefgh'); @@ -521,8 +759,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e, nodes.f, nodes.g, nodes.h].reverse(), - null, - nodes.k + {before: nodes.k} ); compare(newState, 'hgfedcba'); @@ -530,8 +767,7 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e, nodes.f], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcdef'); @@ -539,8 +775,7 @@ newState = domdiff( document.body, newState, [nodes.e, nodes.d, nodes.c, nodes.b, nodes.f, nodes.a], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'edcbfa'); @@ -550,50 +785,46 @@ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e, nodes.f], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcdef'); +/* newState = domdiff( document.body, newState, [null, nodes.c, undefined, null, nodes.b, nodes.a, null, nodes.f, nodes.e, null, nodes.d, undefined], - null, - nodes.k + {before: nodes.k} ); compare(newState.filter(notNull), 'cbafed'); +*/ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e, nodes.f], - null, - nodes.k + {before: nodes.k} ); compare(newState, 'abcdef'); +/* newState = domdiff( document.body, newState, [null, null, undefined, null, null, undefined], - null, - nodes.k + {before: nodes.k} ); compare(newState.filter(notNull), ''); +*/ newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e, nodes.f].reverse(), - null, - nodes.k + {before: nodes.k} ); compare(newState, 'fedcba'); - - - clean(); tressa.log('## snabbdom - updating children (unpinned)'); newState = domdiff( @@ -869,12 +1100,14 @@ newState = domdiff( ); compare(newState, 'abcdef'); +/* newState = domdiff( document.body, newState, [null, nodes.c, undefined, null, nodes.b, nodes.a, null, nodes.f, nodes.e, null, nodes.d, undefined] ); compare(newState.filter(notNull), 'cbafed'); +*/ newState = domdiff( document.body, @@ -883,16 +1116,175 @@ newState = domdiff( ); compare(newState, 'abcdef'); +/* newState = domdiff( document.body, newState, [null, null, undefined, null, null, undefined] ); compare(newState.filter(notNull), ''); +*/ +console.time('average'); newState = domdiff( document.body, newState, [nodes.a, nodes.b, nodes.c, nodes.d, nodes.e, nodes.f].reverse() ); compare(newState, 'fedcba'); +console.timeEnd('average'); + +newState = domdiff( + document.body, + newState, + [nodes.a] +); +compare(newState, 'a'); + +newState = domdiff( + document.body, + newState, + [nodes.b, nodes.c, nodes.d] +); +compare(newState, 'bcd'); + +newState = domdiff( + document.body, + newState, + [nodes.a] +); +compare(newState, 'a'); + +newState = domdiff( + document.body, + newState, + [] +); + +var data = [ + {"id": 4054, "title": "Serie 2"}, + {"id": 3982, "title": "Serie 3"}, + {"id": 4016, "title": "Gracias"}, + {"id":3984, "title": "Comparte"}, + {"id":3952, "title": "Lección 1"}, + {"id":3955, "title": "Lección 2"} +]; + +var wm = {}; + +var getItem = function (item) { return wm[item.id]; }; + +data.forEach(function (item) { + wm[item.id] = document.createTextNode(item.id + ': ' + item.title); +}); + +newState = domdiff( + document.body, + newState, + data.slice().map(getItem) +); + +newState = domdiff( + document.body, + newState, + data.slice().sort(function (a, b) { + return a.title.localeCompare(b.title); + }).map(getItem) +); + +newState = domdiff( + document.body, + newState, + Array(1000).join('.').split('.').map(function (v, i) { + return document.createTextNode('' + i); + }) +); + +console.time('random'); +newState = domdiff( + document.body, + newState, + newState.slice().sort(function () { + return Math.random() < .5 ? 1 : -1; + }) +); +console.timeEnd('random'); + +console.time('reversed'); +newState = domdiff( + document.body, + newState, + newState.slice().reverse() +); +console.timeEnd('reversed'); + +document.body.selectedIndex = -1; +newState = domdiff( + document.body, + newState, + [] +); + +var option = document.createTextNode('b'); +option.selected = true; +newState = domdiff( + document.body, + newState, + [ + document.createTextNode('a'), + option, + document.createTextNode('c') + ] +); +console.assert(document.body.selectedIndex === 1, 'selectedIndex is OK'); + +newState = domdiff( + document.body, + newState, + [] +); +document.body.selectedIndex = 0; +document.body.querySelectorAll = _ => newState.concat(newOptions); +option = document.createTextNode('option'); +option.selected = true; +newState = domdiff( + document.body, + newState, + [ + document.createTextNode('option') + ] +); +var newOptions = [ + document.createTextNode('option'), + option, + document.createTextNode('option') +]; +newState = domdiff( + document.body, + newState, + newOptions +); +console.assert(document.body.selectedIndex === 2, 'partial selectedIndex is OK'); + +var parent1 = document.createDocumentFragment(); +var parent2 = document.createDocumentFragment(); +var nodeA = document.createTextNode('option'); +var nodeB = document.createTextNode('option'); +var nodeC = document.createTextNode('option'); +var nodeD = document.createTextNode('option'); + +domdiff( + parent1, + [nodeA, nodeB], + [nodeA, nodeB, nodeC] +); + +domdiff( + parent2, + [nodeC, nodeD], + [nodeD] +); + +// */ + +tressa.end(); diff --git a/types.d.ts b/types.d.ts new file mode 100644 index 0000000..2fb81df --- /dev/null +++ b/types.d.ts @@ -0,0 +1,44 @@ +export interface IDomDiffNodeMarkerOptions { + /** + * A specific live node to use as boundary for all nodes operations. + * With live nodes [a,d] and {before: d}, the operation [] => [b, c] + * would place nodes right before d, resulting a live collection of [a, b, c, d]. + * + * `before` doesn't necessarily have to be a node + */ + before: T; +} + +export interface IDomDiffOptionsGenericComparisonFn { + /** + * A callback to compare between two generic objects, each could be a node or anything. returns `true` to indicate they are the same + */ + compare: (currentNode: T1, futureNode: T2) => boolean; +} + +export interface IDomDiffOPtionsEachNodeCallbackFn { + /** + * The optional function is invoked per each operation on the list of current"Nodes". + * This can be useful to represent node through wrappers, whenever that is needed. + * @param info + * `1` when the item/node is being appended + * `0` when the item/node is being used as insert _before_ reference + * `-0` when the item/node is being used as insert _after_ reference + * `-1` when the item/node is being removed + */ + node: (generic: T, info: -1 | -0 | 0 | 1) => Node; +} + +/** + * A vDOM-less implementation of the [petit-dom](https://github.com/yelouafi/petit-dom) diffing logic + * that will mutate child nodes the first argument - parentNode + * @param parentNode Where changes happen + * @param currentNodes Array of current items / nodes. + * @param futureNodes Array of future items / nodes + */ +export default function domdiff( + parentNode: Node, + currentNodes: TCurrentItems, + futureNodes: TFutureItems, + options?: IDomDiffNodeMarkerOptions | IDomDiffOptionsGenericComparisonFn | IDomDiffOPtionsEachNodeCallbackFn +): void;