/*! (c) 2018 Andrea Giammarchi (ISC) */
|
|
|
|
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
|
|
options // optional object with one of the following properties
|
|
// before: domNode
|
|
// compare(generic, generic) => true if same generic
|
|
// node(generic) => Node
|
|
) => {
|
|
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)
|
|
);
|
|
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;
|
|
};
|
|
|
|
export default domdiff;
|