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.

387 lines
7.8 KiB

4 years ago
  1. 'use strict';
  2. const {indexOf: iOF} = [];
  3. 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. exports.append = append;
  20. const eqeq = (a, b) => a == b;
  21. exports.eqeq = eqeq;
  22. const identity = O => O;
  23. exports.identity = identity;
  24. const indexOf = (
  25. moreNodes,
  26. moreStart,
  27. moreEnd,
  28. lessNodes,
  29. lessStart,
  30. lessEnd,
  31. compare
  32. ) => {
  33. const length = lessEnd - lessStart;
  34. /* istanbul ignore if */
  35. if (length < 1)
  36. return -1;
  37. while ((moreEnd - moreStart) >= length) {
  38. let m = moreStart;
  39. let l = lessStart;
  40. while (
  41. m < moreEnd &&
  42. l < lessEnd &&
  43. compare(moreNodes[m], lessNodes[l])
  44. ) {
  45. m++;
  46. l++;
  47. }
  48. if (l === lessEnd)
  49. return moreStart;
  50. moreStart = m + 1;
  51. }
  52. return -1;
  53. };
  54. exports.indexOf = indexOf;
  55. const isReversed = (
  56. futureNodes,
  57. futureEnd,
  58. currentNodes,
  59. currentStart,
  60. currentEnd,
  61. compare
  62. ) => {
  63. while (
  64. currentStart < currentEnd &&
  65. compare(
  66. currentNodes[currentStart],
  67. futureNodes[futureEnd - 1]
  68. )) {
  69. currentStart++;
  70. futureEnd--;
  71. };
  72. return futureEnd === 0;
  73. };
  74. exports.isReversed = isReversed;
  75. const next = (get, list, i, length, before) => i < length ?
  76. get(list[i], 0) :
  77. (0 < i ?
  78. get(list[i - 1], -0).nextSibling :
  79. before);
  80. exports.next = next;
  81. const remove = (get, children, start, end) => {
  82. while (start < end)
  83. drop(get(children[start++], -1));
  84. };
  85. exports.remove = remove;
  86. // - - - - - - - - - - - - - - - - - - -
  87. // diff related constants and utilities
  88. // - - - - - - - - - - - - - - - - - - -
  89. const DELETION = -1;
  90. const INSERTION = 1;
  91. const SKIP = 0;
  92. const SKIP_OND = 50;
  93. const HS = (
  94. futureNodes,
  95. futureStart,
  96. futureEnd,
  97. futureChanges,
  98. currentNodes,
  99. currentStart,
  100. currentEnd,
  101. currentChanges
  102. ) => {
  103. let k = 0;
  104. /* istanbul ignore next */
  105. let minLen = futureChanges < currentChanges ? futureChanges : currentChanges;
  106. const link = Array(minLen++);
  107. const tresh = Array(minLen);
  108. tresh[0] = -1;
  109. for (let i = 1; i < minLen; i++)
  110. tresh[i] = currentEnd;
  111. const nodes = currentNodes.slice(currentStart, currentEnd);
  112. for (let i = futureStart; i < futureEnd; i++) {
  113. const index = nodes.indexOf(futureNodes[i]);
  114. if (-1 < index) {
  115. const idxInOld = index + currentStart;
  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 = [];
  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.push(futureNodes[futureStart]);
  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 (-1 < live.indexOf(currentNodes[currentStart]))
  289. currentStart++;
  290. else
  291. remove(
  292. get,
  293. currentNodes,
  294. currentStart++,
  295. currentStart
  296. );
  297. break;
  298. }
  299. }
  300. };
  301. const findK = (ktr, length, j) => {
  302. let lo = 1;
  303. let hi = length;
  304. while (lo < hi) {
  305. const mid = ((lo + hi) / 2) >>> 0;
  306. if (j < ktr[mid])
  307. hi = mid;
  308. else
  309. lo = mid + 1;
  310. }
  311. return lo;
  312. }
  313. const smartDiff = (
  314. get,
  315. parentNode,
  316. futureNodes,
  317. futureStart,
  318. futureEnd,
  319. futureChanges,
  320. currentNodes,
  321. currentStart,
  322. currentEnd,
  323. currentChanges,
  324. currentLength,
  325. compare,
  326. before
  327. ) => {
  328. applyDiff(
  329. OND(
  330. futureNodes,
  331. futureStart,
  332. futureChanges,
  333. currentNodes,
  334. currentStart,
  335. currentChanges,
  336. compare
  337. ) ||
  338. HS(
  339. futureNodes,
  340. futureStart,
  341. futureEnd,
  342. futureChanges,
  343. currentNodes,
  344. currentStart,
  345. currentEnd,
  346. currentChanges
  347. ),
  348. get,
  349. parentNode,
  350. futureNodes,
  351. futureStart,
  352. currentNodes,
  353. currentStart,
  354. currentLength,
  355. before
  356. );
  357. };
  358. exports.smartDiff = smartDiff;
  359. const drop = node => (node.remove || dropChild).call(node);
  360. function dropChild() {
  361. const {parentNode} = this;
  362. /* istanbul ignore else */
  363. if (parentNode)
  364. parentNode.removeChild(this);
  365. }