var domdiff = (function () {
|
|
'use strict';
|
|
|
|
var iOF = [].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 : iOF.call(parent.querySelectorAll('option'), child);
|
|
}
|
|
|
|
start++;
|
|
}
|
|
};
|
|
var eqeq = function eqeq(a, b) {
|
|
return a == b;
|
|
};
|
|
var identity = function identity(O) {
|
|
return O;
|
|
};
|
|
var indexOf = 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 {
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/*! (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(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;
|
|
}
|
|
}
|
|
/* 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;
|
|
}
|
|
} // 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;
|
|
|
|
}());
|