var domdiff = (function () {
|
|
'use strict';
|
|
|
|
/*! (c) Andrea Giammarchi - ISC */
|
|
var self = null || /* istanbul ignore next */{};
|
|
try {
|
|
self.Map = Map;
|
|
} catch (Map) {
|
|
self.Map = function Map() {
|
|
var i = 0;
|
|
var k = [];
|
|
var v = [];
|
|
return {
|
|
delete: function _delete(key) {
|
|
var had = contains(key);
|
|
if (had) {
|
|
k.splice(i, 1);
|
|
v.splice(i, 1);
|
|
}
|
|
return had;
|
|
},
|
|
forEach: function forEach(callback, context) {
|
|
k.forEach(function (key, i) {
|
|
callback.call(context, v[i], key, this);
|
|
}, this);
|
|
},
|
|
get: function get(key) {
|
|
return contains(key) ? v[i] : void 0;
|
|
},
|
|
has: function has(key) {
|
|
return contains(key);
|
|
},
|
|
set: function set(key, value) {
|
|
v[contains(key) ? i : k.push(key) - 1] = value;
|
|
return this;
|
|
}
|
|
};
|
|
function contains(v) {
|
|
i = k.indexOf(v);
|
|
return -1 < i;
|
|
}
|
|
};
|
|
}
|
|
var Map$1 = self.Map;
|
|
|
|
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, parent, children, start, end) {
|
|
if (end - start < 2) parent.removeChild(get(children[start], -1));else {
|
|
var range = parent.ownerDocument.createRange();
|
|
range.setStartBefore(get(children[start], -1));
|
|
range.setEndAfter(get(children[end - 1], -1));
|
|
range.deleteContents();
|
|
}
|
|
};
|
|
|
|
// - - - - - - - - - - - - - - - - - - -
|
|
// 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 keymap = new Map$1();
|
|
for (var _i = currentStart; _i < currentEnd; _i++) {
|
|
keymap.set(currentNodes[_i], _i);
|
|
}for (var _i2 = futureStart; _i2 < futureEnd; _i2++) {
|
|
var idxInOld = keymap.get(futureNodes[_i2]);
|
|
if (idxInOld != null) {
|
|
k = findK(tresh, minLen, idxInOld);
|
|
/* istanbul ignore else */
|
|
if (-1 < k) {
|
|
tresh[k] = idxInOld;
|
|
link[k] = {
|
|
newi: _i2,
|
|
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 = void 0,
|
|
k = void 0,
|
|
r = void 0,
|
|
c = void 0,
|
|
pv = void 0,
|
|
cv = void 0,
|
|
pd = void 0;
|
|
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 = new Map$1();
|
|
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.set(futureNodes[futureStart], 1);
|
|
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 (live.has(currentNodes[currentStart])) currentStart++;else remove(get, parentNode, 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);
|
|
};
|
|
|
|
/*! (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, parentNode, 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, parentNode, currentNodes, currentStart, i);
|
|
remove(get, parentNode, 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, parentNode, 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;
|
|
|
|
}());
|