You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
4.7 KiB

/*! (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;