UNPKG

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