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.

388 lines
7.9 KiB

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