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 ); };