unpkg/public/react-motion@0.5.2/lib/mergeDiff.js.flow

106 lines
4.0 KiB
Plaintext

/* @flow */
import type {TransitionStyle} from './Types';
// core keys merging algorithm. If previous render's keys are [a, b], and the
// next render's [c, b, d], what's the final merged keys and ordering?
// - c and a must both be before b
// - b before d
// - ordering between a and c ambiguous
// this reduces to merging two partially ordered lists (e.g. lists where not
// every item has a definite ordering, like comparing a and c above). For the
// ambiguous ordering we deterministically choose to place the next render's
// item after the previous'; so c after a
// this is called a topological sorting. Except the existing algorithms don't
// work well with js bc of the amount of allocation, and isn't optimized for our
// current use-case bc the runtime is linear in terms of edges (see wiki for
// meaning), which is huge when two lists have many common elements
export default function mergeDiff(
prev: Array<TransitionStyle>,
next: Array<TransitionStyle>,
onRemove: (prevIndex: number, prevStyleCell: TransitionStyle) => ?TransitionStyle
): Array<TransitionStyle> {
// bookkeeping for easier access of a key's index below. This is 2 allocations +
// potentially triggering chrome hash map mode for objs (so it might be faster
// to loop through and find a key's index each time), but I no longer care
let prevKeyIndex: {[key: string]: number} = {};
for (let i = 0; i < prev.length; i++) {
prevKeyIndex[prev[i].key] = i;
}
let nextKeyIndex: {[key: string]: number} = {};
for (let i = 0; i < next.length; i++) {
nextKeyIndex[next[i].key] = i;
}
// first, an overly elaborate way of merging prev and next, eliminating
// duplicates (in terms of keys). If there's dupe, keep the item in next).
// This way of writing it saves allocations
let ret = [];
for (let i = 0; i < next.length; i++) {
ret[i] = next[i];
}
for (let i = 0; i < prev.length; i++) {
if (!Object.prototype.hasOwnProperty.call(nextKeyIndex, prev[i].key)) {
// this is called my TM's `mergeAndSync`, which calls willLeave. We don't
// merge in keys that the user desires to kill
const fill = onRemove(i, prev[i]);
if (fill != null) {
ret.push(fill);
}
}
}
// now all the items all present. Core sorting logic to have the right order
return ret.sort((a, b) => {
const nextOrderA = nextKeyIndex[a.key];
const nextOrderB = nextKeyIndex[b.key];
const prevOrderA = prevKeyIndex[a.key];
const prevOrderB = prevKeyIndex[b.key];
if (nextOrderA != null && nextOrderB != null) {
// both keys in next
return nextKeyIndex[a.key] - nextKeyIndex[b.key];
} else if (prevOrderA != null && prevOrderB != null) {
// both keys in prev
return prevKeyIndex[a.key] - prevKeyIndex[b.key];
} else if (nextOrderA != null) {
// key a in next, key b in prev
// how to determine the order between a and b? We find a "pivot" (term
// abuse), a key present in both prev and next, that is sandwiched between
// a and b. In the context of our above example, if we're comparing a and
// d, b's (the only) pivot
for (let i = 0; i < next.length; i++) {
const pivot = next[i].key;
if (!Object.prototype.hasOwnProperty.call(prevKeyIndex, pivot)) {
continue;
}
if (nextOrderA < nextKeyIndex[pivot] && prevOrderB > prevKeyIndex[pivot]) {
return -1;
} else if (nextOrderA > nextKeyIndex[pivot] && prevOrderB < prevKeyIndex[pivot]) {
return 1;
}
}
// pluggable. default to: next bigger than prev
return 1;
}
// prevOrderA, nextOrderB
for (let i = 0; i < next.length; i++) {
const pivot = next[i].key;
if (!Object.prototype.hasOwnProperty.call(prevKeyIndex, pivot)) {
continue;
}
if (nextOrderB < nextKeyIndex[pivot] && prevOrderA > prevKeyIndex[pivot]) {
return 1;
} else if (nextOrderB > nextKeyIndex[pivot] && prevOrderA < prevKeyIndex[pivot]) {
return -1;
}
}
// pluggable. default to: next bigger than prev
return -1;
});
}