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.

379 lines
7.7 KiB

5 years ago
  1. import Map from '@ungap/essential-map';
  2. const {indexOf: iOF} = [];
  3. export const append = (get, parent, children, start, end, before) => {
  4. const isSelect = 'selectedIndex' in parent;
  5. let noSelection = isSelect;
  6. while (start < end) {
  7. const child = get(children[start], 1);
  8. parent.insertBefore(child, before);
  9. if (isSelect && noSelection && child.selected) {
  10. noSelection = !noSelection;
  11. let {selectedIndex} = parent;
  12. parent.selectedIndex = selectedIndex < 0 ?
  13. start :
  14. iOF.call(parent.querySelectorAll('option'), child);
  15. }
  16. start++;
  17. }
  18. };
  19. export const eqeq = (a, b) => a == b;
  20. export const identity = O => O;
  21. export const indexOf = (
  22. moreNodes,
  23. moreStart,
  24. moreEnd,
  25. lessNodes,
  26. lessStart,
  27. lessEnd,
  28. compare
  29. ) => {
  30. const length = lessEnd - lessStart;
  31. /* istanbul ignore if */
  32. if (length < 1)
  33. return -1;
  34. while ((moreEnd - moreStart) >= length) {
  35. let m = moreStart;
  36. let l = lessStart;
  37. while (
  38. m < moreEnd &&
  39. l < lessEnd &&
  40. compare(moreNodes[m], lessNodes[l])
  41. ) {
  42. m++;
  43. l++;
  44. }
  45. if (l === lessEnd)
  46. return moreStart;
  47. moreStart = m + 1;
  48. }
  49. return -1;
  50. };
  51. export const isReversed = (
  52. futureNodes,
  53. futureEnd,
  54. currentNodes,
  55. currentStart,
  56. currentEnd,
  57. compare
  58. ) => {
  59. while (
  60. currentStart < currentEnd &&
  61. compare(
  62. currentNodes[currentStart],
  63. futureNodes[futureEnd - 1]
  64. )) {
  65. currentStart++;
  66. futureEnd--;
  67. };
  68. return futureEnd === 0;
  69. };
  70. export const next = (get, list, i, length, before) => i < length ?
  71. get(list[i], 0) :
  72. (0 < i ?
  73. get(list[i - 1], -0).nextSibling :
  74. before);
  75. export const remove = (get, parent, children, start, end) => {
  76. if ((end - start) < 2)
  77. parent.removeChild(get(children[start], -1));
  78. else {
  79. const range = parent.ownerDocument.createRange();
  80. range.setStartBefore(get(children[start], -1));
  81. range.setEndAfter(get(children[end - 1], -1));
  82. range.deleteContents();
  83. }
  84. };
  85. // - - - - - - - - - - - - - - - - - - -
  86. // diff related constants and utilities
  87. // - - - - - - - - - - - - - - - - - - -
  88. const DELETION = -1;
  89. const INSERTION = 1;
  90. const SKIP = 0;
  91. const SKIP_OND = 50;
  92. const HS = (
  93. futureNodes,
  94. futureStart,
  95. futureEnd,
  96. futureChanges,
  97. currentNodes,
  98. currentStart,
  99. currentEnd,
  100. currentChanges
  101. ) => {
  102. let k = 0;
  103. /* istanbul ignore next */
  104. let minLen = futureChanges < currentChanges ? futureChanges : currentChanges;
  105. const link = Array(minLen++);
  106. const tresh = Array(minLen);
  107. tresh[0] = -1;
  108. for (let i = 1; i < minLen; i++)
  109. tresh[i] = currentEnd;
  110. const keymap = new Map;
  111. for (let i = currentStart; i < currentEnd; i++)
  112. keymap.set(currentNodes[i], i);
  113. for (let i = futureStart; i < futureEnd; i++) {
  114. const idxInOld = keymap.get(futureNodes[i]);
  115. if (idxInOld != null) {
  116. k = findK(tresh, minLen, idxInOld);
  117. /* istanbul ignore else */
  118. if (-1 < k) {
  119. tresh[k] = idxInOld;
  120. link[k] = {
  121. newi: i,
  122. oldi: idxInOld,
  123. prev: link[k - 1]
  124. };
  125. }
  126. }
  127. }
  128. k = --minLen;
  129. --currentEnd;
  130. while (tresh[k] > currentEnd) --k;
  131. minLen = currentChanges + futureChanges - k;
  132. const diff = Array(minLen);
  133. let ptr = link[k];
  134. --futureEnd;
  135. while (ptr) {
  136. const {newi, oldi} = ptr;
  137. while (futureEnd > newi) {
  138. diff[--minLen] = INSERTION;
  139. --futureEnd;
  140. }
  141. while (currentEnd > oldi) {
  142. diff[--minLen] = DELETION;
  143. --currentEnd;
  144. }
  145. diff[--minLen] = SKIP;
  146. --futureEnd;
  147. --currentEnd;
  148. ptr = ptr.prev;
  149. }
  150. while (futureEnd >= futureStart) {
  151. diff[--minLen] = INSERTION;
  152. --futureEnd;
  153. }
  154. while (currentEnd >= currentStart) {
  155. diff[--minLen] = DELETION;
  156. --currentEnd;
  157. }
  158. return diff;
  159. };
  160. // this is pretty much the same petit-dom code without the delete map part
  161. // https://github.com/yelouafi/petit-dom/blob/bd6f5c919b5ae5297be01612c524c40be45f14a7/src/vdom.js#L556-L561
  162. const OND = (
  163. futureNodes,
  164. futureStart,
  165. rows,
  166. currentNodes,
  167. currentStart,
  168. cols,
  169. compare
  170. ) => {
  171. const length = rows + cols;
  172. const v = [];
  173. let d, k, r, c, pv, cv, pd;
  174. outer: for (d = 0; d <= length; d++) {
  175. /* istanbul ignore if */
  176. if (d > SKIP_OND)
  177. return null;
  178. pd = d - 1;
  179. /* istanbul ignore next */
  180. pv = d ? v[d - 1] : [0, 0];
  181. cv = v[d] = [];
  182. for (k = -d; k <= d; k += 2) {
  183. if (k === -d || (k !== d && pv[pd + k - 1] < pv[pd + k + 1])) {
  184. c = pv[pd + k + 1];
  185. } else {
  186. c = pv[pd + k - 1] + 1;
  187. }
  188. r = c - k;
  189. while (
  190. c < cols &&
  191. r < rows &&
  192. compare(
  193. currentNodes[currentStart + c],
  194. futureNodes[futureStart + r]
  195. )
  196. ) {
  197. c++;
  198. r++;
  199. }
  200. if (c === cols && r === rows) {
  201. break outer;
  202. }
  203. cv[d + k] = c;
  204. }
  205. }
  206. const diff = Array(d / 2 + length / 2);
  207. let diffIdx = diff.length - 1;
  208. for (d = v.length - 1; d >= 0; d--) {
  209. while (
  210. c > 0 &&
  211. r > 0 &&
  212. compare(
  213. currentNodes[currentStart + c - 1],
  214. futureNodes[futureStart + r - 1]
  215. )
  216. ) {
  217. // diagonal edge = equality
  218. diff[diffIdx--] = SKIP;
  219. c--;
  220. r--;
  221. }
  222. if (!d)
  223. break;
  224. pd = d - 1;
  225. /* istanbul ignore next */
  226. pv = d ? v[d - 1] : [0, 0];
  227. k = c - r;
  228. if (k === -d || (k !== d && pv[pd + k - 1] < pv[pd + k + 1])) {
  229. // vertical edge = insertion
  230. r--;
  231. diff[diffIdx--] = INSERTION;
  232. } else {
  233. // horizontal edge = deletion
  234. c--;
  235. diff[diffIdx--] = DELETION;
  236. }
  237. }
  238. return diff;
  239. };
  240. const applyDiff = (
  241. diff,
  242. get,
  243. parentNode,
  244. futureNodes,
  245. futureStart,
  246. currentNodes,
  247. currentStart,
  248. currentLength,
  249. before
  250. ) => {
  251. const live = new Map;
  252. const length = diff.length;
  253. let currentIndex = currentStart;
  254. let i = 0;
  255. while (i < length) {
  256. switch (diff[i++]) {
  257. case SKIP:
  258. futureStart++;
  259. currentIndex++;
  260. break;
  261. case INSERTION:
  262. // TODO: bulk appends for sequential nodes
  263. live.set(futureNodes[futureStart], 1);
  264. append(
  265. get,
  266. parentNode,
  267. futureNodes,
  268. futureStart++,
  269. futureStart,
  270. currentIndex < currentLength ?
  271. get(currentNodes[currentIndex], 0) :
  272. before
  273. );
  274. break;
  275. case DELETION:
  276. currentIndex++;
  277. break;
  278. }
  279. }
  280. i = 0;
  281. while (i < length) {
  282. switch (diff[i++]) {
  283. case SKIP:
  284. currentStart++;
  285. break;
  286. case DELETION:
  287. // TODO: bulk removes for sequential nodes
  288. if (live.has(currentNodes[currentStart]))
  289. currentStart++;
  290. else
  291. remove(
  292. get,
  293. parentNode,
  294. currentNodes,
  295. currentStart++,
  296. currentStart
  297. );
  298. break;
  299. }
  300. }
  301. };
  302. const findK = (ktr, length, j) => {
  303. let lo = 1;
  304. let hi = length;
  305. while (lo < hi) {
  306. const mid = ((lo + hi) / 2) >>> 0;
  307. if (j < ktr[mid])
  308. hi = mid;
  309. else
  310. lo = mid + 1;
  311. }
  312. return lo;
  313. }
  314. export const smartDiff = (
  315. get,
  316. parentNode,
  317. futureNodes,
  318. futureStart,
  319. futureEnd,
  320. futureChanges,
  321. currentNodes,
  322. currentStart,
  323. currentEnd,
  324. currentChanges,
  325. currentLength,
  326. compare,
  327. before
  328. ) => {
  329. applyDiff(
  330. OND(
  331. futureNodes,
  332. futureStart,
  333. futureChanges,
  334. currentNodes,
  335. currentStart,
  336. currentChanges,
  337. compare
  338. ) ||
  339. HS(
  340. futureNodes,
  341. futureStart,
  342. futureEnd,
  343. futureChanges,
  344. currentNodes,
  345. currentStart,
  346. currentEnd,
  347. currentChanges
  348. ),
  349. get,
  350. parentNode,
  351. futureNodes,
  352. futureStart,
  353. currentNodes,
  354. currentStart,
  355. currentLength,
  356. before
  357. );
  358. };