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.

380 lines
7.6 KiB

4 years ago
import {indexOf as iOF} from 'uarray';
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, children, start, end) => {
while (start < end)
drop(get(children[start++], -1));
};
// - - - - - - - - - - - - - - - - - - -
// 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 nodes = currentNodes.slice(currentStart, currentEnd);
for (let i = futureStart; i < futureEnd; i++) {
const index = nodes.indexOf(futureNodes[i]);
if (-1 < index) {
const 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;
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 = [];
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.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;
}
}
};
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
);
};
const drop = node => (node.remove || dropChild).call(node);
function dropChild() {
const {parentNode} = this;
/* istanbul ignore else */
if (parentNode)
parentNode.removeChild(this);
}