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.

378 lines
7.6 KiB

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