init.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. import { vnode } from "./vnode.js";
  2. import * as is from "./is.js";
  3. import { htmlDomApi } from "./htmldomapi.js";
  4. function isUndef(s) {
  5. return s === undefined;
  6. }
  7. function isDef(s) {
  8. return s !== undefined;
  9. }
  10. const emptyNode = vnode("", {}, [], undefined, undefined);
  11. function sameVnode(vnode1, vnode2) {
  12. var _a, _b;
  13. const isSameKey = vnode1.key === vnode2.key;
  14. const isSameIs = ((_a = vnode1.data) === null || _a === void 0 ? void 0 : _a.is) === ((_b = vnode2.data) === null || _b === void 0 ? void 0 : _b.is);
  15. const isSameSel = vnode1.sel === vnode2.sel;
  16. const isSameTextOrFragment = !vnode1.sel && vnode1.sel === vnode2.sel
  17. ? typeof vnode1.text === typeof vnode2.text
  18. : true;
  19. return isSameSel && isSameKey && isSameIs && isSameTextOrFragment;
  20. }
  21. /**
  22. * @todo Remove this function when the document fragment is considered stable.
  23. */
  24. function documentFragmentIsNotSupported() {
  25. throw new Error("The document fragment is not supported on this platform.");
  26. }
  27. function isElement(api, vnode) {
  28. return api.isElement(vnode);
  29. }
  30. function isDocumentFragment(api, vnode) {
  31. return api.isDocumentFragment(vnode);
  32. }
  33. function createKeyToOldIdx(children, beginIdx, endIdx) {
  34. var _a;
  35. const map = {};
  36. for (let i = beginIdx; i <= endIdx; ++i) {
  37. const key = (_a = children[i]) === null || _a === void 0 ? void 0 : _a.key;
  38. if (key !== undefined) {
  39. map[key] = i;
  40. }
  41. }
  42. return map;
  43. }
  44. const hooks = [
  45. "create",
  46. "update",
  47. "remove",
  48. "destroy",
  49. "pre",
  50. "post"
  51. ];
  52. export function init(modules, domApi, options) {
  53. const cbs = {
  54. create: [],
  55. update: [],
  56. remove: [],
  57. destroy: [],
  58. pre: [],
  59. post: []
  60. };
  61. const api = domApi !== undefined ? domApi : htmlDomApi;
  62. for (const hook of hooks) {
  63. for (const module of modules) {
  64. const currentHook = module[hook];
  65. if (currentHook !== undefined) {
  66. cbs[hook].push(currentHook);
  67. }
  68. }
  69. }
  70. function emptyNodeAt(elm) {
  71. const id = elm.id ? "#" + elm.id : "";
  72. // elm.className doesn't return a string when elm is an SVG element inside a shadowRoot.
  73. // https://stackoverflow.com/questions/29454340/detecting-classname-of-svganimatedstring
  74. const classes = elm.getAttribute("class");
  75. const c = classes ? "." + classes.split(" ").join(".") : "";
  76. return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm);
  77. }
  78. function emptyDocumentFragmentAt(frag) {
  79. return vnode(undefined, {}, [], undefined, frag);
  80. }
  81. function createRmCb(childElm, listeners) {
  82. return function rmCb() {
  83. if (--listeners === 0) {
  84. const parent = api.parentNode(childElm);
  85. if (parent !== null) {
  86. api.removeChild(parent, childElm);
  87. }
  88. }
  89. };
  90. }
  91. function createElm(vnode, insertedVnodeQueue) {
  92. var _a, _b, _c, _d;
  93. let i;
  94. let data = vnode.data;
  95. if (data !== undefined) {
  96. const init = (_a = data.hook) === null || _a === void 0 ? void 0 : _a.init;
  97. if (isDef(init)) {
  98. init(vnode);
  99. data = vnode.data;
  100. }
  101. }
  102. const children = vnode.children;
  103. const sel = vnode.sel;
  104. if (sel === "!") {
  105. if (isUndef(vnode.text)) {
  106. vnode.text = "";
  107. }
  108. vnode.elm = api.createComment(vnode.text);
  109. }
  110. else if (sel === "") {
  111. // textNode has no selector
  112. vnode.elm = api.createTextNode(vnode.text);
  113. }
  114. else if (sel !== undefined) {
  115. // Parse selector
  116. const hashIdx = sel.indexOf("#");
  117. const dotIdx = sel.indexOf(".", hashIdx);
  118. const hash = hashIdx > 0 ? hashIdx : sel.length;
  119. const dot = dotIdx > 0 ? dotIdx : sel.length;
  120. const tag = hashIdx !== -1 || dotIdx !== -1
  121. ? sel.slice(0, Math.min(hash, dot))
  122. : sel;
  123. const elm = (vnode.elm =
  124. isDef(data) && isDef((i = data.ns))
  125. ? api.createElementNS(i, tag, data)
  126. : api.createElement(tag, data));
  127. if (hash < dot)
  128. elm.setAttribute("id", sel.slice(hash + 1, dot));
  129. if (dotIdx > 0)
  130. elm.setAttribute("class", sel.slice(dot + 1).replace(/\./g, " "));
  131. for (i = 0; i < cbs.create.length; ++i)
  132. cbs.create[i](emptyNode, vnode);
  133. if (is.primitive(vnode.text) &&
  134. (!is.array(children) || children.length === 0)) {
  135. // allow h1 and similar nodes to be created w/ text and empty child list
  136. api.appendChild(elm, api.createTextNode(vnode.text));
  137. }
  138. if (is.array(children)) {
  139. for (i = 0; i < children.length; ++i) {
  140. const ch = children[i];
  141. if (ch != null) {
  142. api.appendChild(elm, createElm(ch, insertedVnodeQueue));
  143. }
  144. }
  145. }
  146. const hook = vnode.data.hook;
  147. if (isDef(hook)) {
  148. (_b = hook.create) === null || _b === void 0 ? void 0 : _b.call(hook, emptyNode, vnode);
  149. if (hook.insert) {
  150. insertedVnodeQueue.push(vnode);
  151. }
  152. }
  153. }
  154. else if (((_c = options === null || options === void 0 ? void 0 : options.experimental) === null || _c === void 0 ? void 0 : _c.fragments) && vnode.children) {
  155. vnode.elm = ((_d = api.createDocumentFragment) !== null && _d !== void 0 ? _d : documentFragmentIsNotSupported)();
  156. for (i = 0; i < cbs.create.length; ++i)
  157. cbs.create[i](emptyNode, vnode);
  158. for (i = 0; i < vnode.children.length; ++i) {
  159. const ch = vnode.children[i];
  160. if (ch != null) {
  161. api.appendChild(vnode.elm, createElm(ch, insertedVnodeQueue));
  162. }
  163. }
  164. }
  165. else {
  166. vnode.elm = api.createTextNode(vnode.text);
  167. }
  168. return vnode.elm;
  169. }
  170. function addVnodes(parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) {
  171. for (; startIdx <= endIdx; ++startIdx) {
  172. const ch = vnodes[startIdx];
  173. if (ch != null) {
  174. api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before);
  175. }
  176. }
  177. }
  178. function invokeDestroyHook(vnode) {
  179. var _a, _b;
  180. const data = vnode.data;
  181. if (data !== undefined) {
  182. (_b = (_a = data === null || data === void 0 ? void 0 : data.hook) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a, vnode);
  183. for (let i = 0; i < cbs.destroy.length; ++i)
  184. cbs.destroy[i](vnode);
  185. if (vnode.children !== undefined) {
  186. for (let j = 0; j < vnode.children.length; ++j) {
  187. const child = vnode.children[j];
  188. if (child != null && typeof child !== "string") {
  189. invokeDestroyHook(child);
  190. }
  191. }
  192. }
  193. }
  194. }
  195. function removeVnodes(parentElm, vnodes, startIdx, endIdx) {
  196. var _a, _b;
  197. for (; startIdx <= endIdx; ++startIdx) {
  198. let listeners;
  199. let rm;
  200. const ch = vnodes[startIdx];
  201. if (ch != null) {
  202. if (isDef(ch.sel)) {
  203. invokeDestroyHook(ch);
  204. listeners = cbs.remove.length + 1;
  205. rm = createRmCb(ch.elm, listeners);
  206. for (let i = 0; i < cbs.remove.length; ++i)
  207. cbs.remove[i](ch, rm);
  208. const removeHook = (_b = (_a = ch === null || ch === void 0 ? void 0 : ch.data) === null || _a === void 0 ? void 0 : _a.hook) === null || _b === void 0 ? void 0 : _b.remove;
  209. if (isDef(removeHook)) {
  210. removeHook(ch, rm);
  211. }
  212. else {
  213. rm();
  214. }
  215. }
  216. else if (ch.children) {
  217. // Fragment node
  218. invokeDestroyHook(ch);
  219. removeVnodes(parentElm, ch.children, 0, ch.children.length - 1);
  220. }
  221. else {
  222. // Text node
  223. api.removeChild(parentElm, ch.elm);
  224. }
  225. }
  226. }
  227. }
  228. function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {
  229. let oldStartIdx = 0;
  230. let newStartIdx = 0;
  231. let oldEndIdx = oldCh.length - 1;
  232. let oldStartVnode = oldCh[0];
  233. let oldEndVnode = oldCh[oldEndIdx];
  234. let newEndIdx = newCh.length - 1;
  235. let newStartVnode = newCh[0];
  236. let newEndVnode = newCh[newEndIdx];
  237. let oldKeyToIdx;
  238. let idxInOld;
  239. let elmToMove;
  240. let before;
  241. while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
  242. if (oldStartVnode == null) {
  243. oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
  244. }
  245. else if (oldEndVnode == null) {
  246. oldEndVnode = oldCh[--oldEndIdx];
  247. }
  248. else if (newStartVnode == null) {
  249. newStartVnode = newCh[++newStartIdx];
  250. }
  251. else if (newEndVnode == null) {
  252. newEndVnode = newCh[--newEndIdx];
  253. }
  254. else if (sameVnode(oldStartVnode, newStartVnode)) {
  255. patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
  256. oldStartVnode = oldCh[++oldStartIdx];
  257. newStartVnode = newCh[++newStartIdx];
  258. }
  259. else if (sameVnode(oldEndVnode, newEndVnode)) {
  260. patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
  261. oldEndVnode = oldCh[--oldEndIdx];
  262. newEndVnode = newCh[--newEndIdx];
  263. }
  264. else if (sameVnode(oldStartVnode, newEndVnode)) {
  265. // Vnode moved right
  266. patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
  267. api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm));
  268. oldStartVnode = oldCh[++oldStartIdx];
  269. newEndVnode = newCh[--newEndIdx];
  270. }
  271. else if (sameVnode(oldEndVnode, newStartVnode)) {
  272. // Vnode moved left
  273. patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
  274. api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
  275. oldEndVnode = oldCh[--oldEndIdx];
  276. newStartVnode = newCh[++newStartIdx];
  277. }
  278. else {
  279. if (oldKeyToIdx === undefined) {
  280. oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
  281. }
  282. idxInOld = oldKeyToIdx[newStartVnode.key];
  283. if (isUndef(idxInOld)) {
  284. // `newStartVnode` is new, create and insert it in beginning
  285. api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
  286. newStartVnode = newCh[++newStartIdx];
  287. }
  288. else if (isUndef(oldKeyToIdx[newEndVnode.key])) {
  289. // `newEndVnode` is new, create and insert it in the end
  290. api.insertBefore(parentElm, createElm(newEndVnode, insertedVnodeQueue), api.nextSibling(oldEndVnode.elm));
  291. newEndVnode = newCh[--newEndIdx];
  292. }
  293. else {
  294. // Neither of the new endpoints are new vnodes, so we make progress by
  295. // moving `newStartVnode` into position
  296. elmToMove = oldCh[idxInOld];
  297. if (elmToMove.sel !== newStartVnode.sel) {
  298. api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
  299. }
  300. else {
  301. patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
  302. oldCh[idxInOld] = undefined;
  303. api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm);
  304. }
  305. newStartVnode = newCh[++newStartIdx];
  306. }
  307. }
  308. }
  309. if (newStartIdx <= newEndIdx) {
  310. before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
  311. addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
  312. }
  313. if (oldStartIdx <= oldEndIdx) {
  314. removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
  315. }
  316. }
  317. function patchVnode(oldVnode, vnode, insertedVnodeQueue) {
  318. var _a, _b, _c, _d, _e, _f, _g, _h;
  319. const hook = (_a = vnode.data) === null || _a === void 0 ? void 0 : _a.hook;
  320. (_b = hook === null || hook === void 0 ? void 0 : hook.prepatch) === null || _b === void 0 ? void 0 : _b.call(hook, oldVnode, vnode);
  321. const elm = (vnode.elm = oldVnode.elm);
  322. if (oldVnode === vnode)
  323. return;
  324. if (vnode.data !== undefined ||
  325. (isDef(vnode.text) && vnode.text !== oldVnode.text)) {
  326. (_c = vnode.data) !== null && _c !== void 0 ? _c : (vnode.data = {});
  327. (_d = oldVnode.data) !== null && _d !== void 0 ? _d : (oldVnode.data = {});
  328. for (let i = 0; i < cbs.update.length; ++i)
  329. cbs.update[i](oldVnode, vnode);
  330. (_g = (_f = (_e = vnode.data) === null || _e === void 0 ? void 0 : _e.hook) === null || _f === void 0 ? void 0 : _f.update) === null || _g === void 0 ? void 0 : _g.call(_f, oldVnode, vnode);
  331. }
  332. const oldCh = oldVnode.children;
  333. const ch = vnode.children;
  334. if (isUndef(vnode.text)) {
  335. if (isDef(oldCh) && isDef(ch)) {
  336. if (oldCh !== ch)
  337. updateChildren(elm, oldCh, ch, insertedVnodeQueue);
  338. }
  339. else if (isDef(ch)) {
  340. if (isDef(oldVnode.text))
  341. api.setTextContent(elm, "");
  342. addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
  343. }
  344. else if (isDef(oldCh)) {
  345. removeVnodes(elm, oldCh, 0, oldCh.length - 1);
  346. }
  347. else if (isDef(oldVnode.text)) {
  348. api.setTextContent(elm, "");
  349. }
  350. }
  351. else if (oldVnode.text !== vnode.text) {
  352. if (isDef(oldCh)) {
  353. removeVnodes(elm, oldCh, 0, oldCh.length - 1);
  354. }
  355. api.setTextContent(elm, vnode.text);
  356. }
  357. (_h = hook === null || hook === void 0 ? void 0 : hook.postpatch) === null || _h === void 0 ? void 0 : _h.call(hook, oldVnode, vnode);
  358. }
  359. return function patch(oldVnode, vnode) {
  360. let i, elm, parent;
  361. const insertedVnodeQueue = [];
  362. for (i = 0; i < cbs.pre.length; ++i)
  363. cbs.pre[i]();
  364. if (isElement(api, oldVnode)) {
  365. oldVnode = emptyNodeAt(oldVnode);
  366. }
  367. else if (isDocumentFragment(api, oldVnode)) {
  368. oldVnode = emptyDocumentFragmentAt(oldVnode);
  369. }
  370. if (sameVnode(oldVnode, vnode)) {
  371. patchVnode(oldVnode, vnode, insertedVnodeQueue);
  372. }
  373. else {
  374. elm = oldVnode.elm;
  375. parent = api.parentNode(elm);
  376. createElm(vnode, insertedVnodeQueue);
  377. if (parent !== null) {
  378. api.insertBefore(parent, vnode.elm, api.nextSibling(elm));
  379. removeVnodes(parent, [oldVnode], 0, 0);
  380. }
  381. }
  382. for (i = 0; i < insertedVnodeQueue.length; ++i) {
  383. insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]);
  384. }
  385. for (i = 0; i < cbs.post.length; ++i)
  386. cbs.post[i]();
  387. return vnode;
  388. };
  389. }
  390. //# sourceMappingURL=init.js.map