import Map from '@ungap/essential-map';
|
|
|
|
const {indexOf: iOF} = [];
|
|
export const append = (get, parent, children, start, end, before) => {
|
|
const isSelect = 'selectedIndex' in parent;
|
|
let noSelection = isSelect;
|
|
while (start < end) {
|
|
const child = get(children[start], 1);
|
|
parent.insertBefore(child, before);
|
|
if (isSelect && noSelection && child.selected) {
|
|
noSelection = !noSelection;
|
|
let {selectedIndex} = parent;
|
|
parent.selectedIndex = selectedIndex < 0 ?
|
|
start :
|
|
iOF.call(parent.querySelectorAll('option'), child);
|
|
}
|
|
start++;
|
|
}
|
|
};
|
|
|
|
export const eqeq = (a, b) => a == b;
|
|
|
|
export const identity = O => O;
|
|
|
|
export const indexOf = (
|
|
moreNodes,
|
|
moreStart,
|
|
moreEnd,
|
|
lessNodes,
|
|
lessStart,
|
|
lessEnd,
|
|
compare
|
|
) => {
|
|
const length = lessEnd - lessStart;
|
|
/* istanbul ignore if */
|
|
if (length < 1)
|
|
return -1;
|
|
while ((moreEnd - moreStart) >= length) {
|
|
let m = moreStart;
|
|
let l = lessStart;
|
|
while (
|
|
m < moreEnd &&
|
|
l < lessEnd &&
|
|
compare(moreNodes[m], lessNodes[l])
|
|
) {
|
|
m++;
|
|
l++;
|
|
}
|
|
if (l === lessEnd)
|
|
return moreStart;
|
|
moreStart = m + 1;
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
export const isReversed = (
|
|
futureNodes,
|
|
futureEnd,
|
|
currentNodes,
|
|
currentStart,
|
|
currentEnd,
|
|
compare
|
|
) => {
|
|
while (
|
|
currentStart < currentEnd &&
|
|
compare(
|
|
currentNodes[currentStart],
|
|
futureNodes[futureEnd - 1]
|
|
)) {
|
|
currentStart++;
|
|
futureEnd--;
|
|
};
|
|
return futureEnd === 0;
|
|
};
|
|
|
|
export const next = (get, list, i, length, before) => i < length ?
|
|
get(list[i], 0) :
|
|
(0 < i ?
|
|
get(list[i - 1], -0).nextSibling :
|
|
before);
|
|
|
|
export const remove = (get, parent, children, start, end) => {
|
|
if ((end - start) < 2)
|
|
parent.removeChild(get(children[start], -1));
|
|
else {
|
|
const range = parent.ownerDocument.createRange();
|
|
range.setStartBefore(get(children[start], -1));
|
|
range.setEndAfter(get(children[end - 1], -1));
|
|
range.deleteContents();
|
|
}
|
|
};
|
|
|
|
// - - - - - - - - - - - - - - - - - - -
|
|
// diff related constants and utilities
|
|
// - - - - - - - - - - - - - - - - - - -
|
|
|
|
const DELETION = -1;
|
|
const INSERTION = 1;
|
|
const SKIP = 0;
|
|
const SKIP_OND = 50;
|
|
|
|
const HS = (
|
|
futureNodes,
|
|
futureStart,
|
|
futureEnd,
|
|
futureChanges,
|
|
currentNodes,
|
|
currentStart,
|
|
currentEnd,
|
|
currentChanges
|
|
) => {
|
|
|
|
let k = 0;
|
|
/* istanbul ignore next */
|
|
let minLen = futureChanges < currentChanges ? futureChanges : currentChanges;
|
|
const link = Array(minLen++);
|
|
const tresh = Array(minLen);
|
|
tresh[0] = -1;
|
|
|
|
for (let i = 1; i < minLen; i++)
|
|
tresh[i] = currentEnd;
|
|
|
|
const keymap = new Map;
|
|
for (let i = currentStart; i < currentEnd; i++)
|
|
keymap.set(currentNodes[i], i);
|
|
|
|
for (let i = futureStart; i < futureEnd; i++) {
|
|
const idxInOld = keymap.get(futureNodes[i]);
|
|
if (idxInOld != null) {
|
|
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;
|
|
const diff = Array(minLen);
|
|
let ptr = link[k];
|
|
--futureEnd;
|
|
while (ptr) {
|
|
const {newi, oldi} = ptr;
|
|
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
|
|
const OND = (
|
|
futureNodes,
|
|
futureStart,
|
|
rows,
|
|
currentNodes,
|
|
currentStart,
|
|
cols,
|
|
compare
|
|
) => {
|
|
const length = rows + cols;
|
|
const v = [];
|
|
let 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;
|
|
}
|
|
}
|
|
|
|
const diff = Array(d / 2 + length / 2);
|
|
let 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;
|
|
};
|
|
|
|
const applyDiff = (
|
|
diff,
|
|
get,
|
|
parentNode,
|
|
futureNodes,
|
|
futureStart,
|
|
currentNodes,
|
|
currentStart,
|
|
currentLength,
|
|
before
|
|
) => {
|
|
const live = new Map;
|
|
const length = diff.length;
|
|
let currentIndex = currentStart;
|
|
let 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;
|
|
}
|
|
}
|
|
};
|
|
|
|
const findK = (ktr, length, j) => {
|
|
let lo = 1;
|
|
let hi = length;
|
|
while (lo < hi) {
|
|
const mid = ((lo + hi) / 2) >>> 0;
|
|
if (j < ktr[mid])
|
|
hi = mid;
|
|
else
|
|
lo = mid + 1;
|
|
}
|
|
return lo;
|
|
}
|
|
|
|
export const 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
|
|
);
|
|
};
|