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; }());