UNPKG

snabbdom

Version:

A virtual DOM library with focus on simplicity, modularity, powerful features and performance.

390 lines 16.3 kB
import { vnode } from "./vnode.js"; import * as is from "./is.js"; import { htmlDomApi } from "./htmldomapi.js"; function isUndef(s) { return s === undefined; } function isDef(s) { return s !== undefined; } const emptyNode = vnode("", {}, [], undefined, undefined); function sameVnode(vnode1, vnode2) { var _a, _b; const isSameKey = vnode1.key === vnode2.key; const isSameIs = ((_a = vnode1.data) === null || _a === void 0 ? void 0 : _a.is) === ((_b = vnode2.data) === null || _b === void 0 ? void 0 : _b.is); const isSameSel = vnode1.sel === vnode2.sel; const isSameTextOrFragment = !vnode1.sel && vnode1.sel === vnode2.sel ? typeof vnode1.text === typeof vnode2.text : true; return isSameSel && isSameKey && isSameIs && isSameTextOrFragment; } /** * @todo Remove this function when the document fragment is considered stable. */ function documentFragmentIsNotSupported() { throw new Error("The document fragment is not supported on this platform."); } function isElement(api, vnode) { return api.isElement(vnode); } function isDocumentFragment(api, vnode) { return api.isDocumentFragment(vnode); } function createKeyToOldIdx(children, beginIdx, endIdx) { var _a; const map = {}; for (let i = beginIdx; i <= endIdx; ++i) { const key = (_a = children[i]) === null || _a === void 0 ? void 0 : _a.key; if (key !== undefined) { map[key] = i; } } return map; } const hooks = [ "create", "update", "remove", "destroy", "pre", "post" ]; export function init(modules, domApi, options) { const cbs = { create: [], update: [], remove: [], destroy: [], pre: [], post: [] }; const api = domApi !== undefined ? domApi : htmlDomApi; for (const hook of hooks) { for (const module of modules) { const currentHook = module[hook]; if (currentHook !== undefined) { cbs[hook].push(currentHook); } } } function emptyNodeAt(elm) { const id = elm.id ? "#" + elm.id : ""; // elm.className doesn't return a string when elm is an SVG element inside a shadowRoot. // https://stackoverflow.com/questions/29454340/detecting-classname-of-svganimatedstring const classes = elm.getAttribute("class"); const c = classes ? "." + classes.split(" ").join(".") : ""; return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm); } function emptyDocumentFragmentAt(frag) { return vnode(undefined, {}, [], undefined, frag); } function createRmCb(childElm, listeners) { return function rmCb() { if (--listeners === 0) { const parent = api.parentNode(childElm); if (parent !== null) { api.removeChild(parent, childElm); } } }; } function createElm(vnode, insertedVnodeQueue) { var _a, _b, _c, _d; let i; let data = vnode.data; if (data !== undefined) { const init = (_a = data.hook) === null || _a === void 0 ? void 0 : _a.init; if (isDef(init)) { init(vnode); data = vnode.data; } } const children = vnode.children; const sel = vnode.sel; if (sel === "!") { if (isUndef(vnode.text)) { vnode.text = ""; } vnode.elm = api.createComment(vnode.text); } else if (sel === "") { // textNode has no selector vnode.elm = api.createTextNode(vnode.text); } else if (sel !== undefined) { // Parse selector const hashIdx = sel.indexOf("#"); const dotIdx = sel.indexOf(".", hashIdx); const hash = hashIdx > 0 ? hashIdx : sel.length; const dot = dotIdx > 0 ? dotIdx : sel.length; const tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel; const elm = (vnode.elm = isDef(data) && isDef((i = data.ns)) ? api.createElementNS(i, tag, data) : api.createElement(tag, data)); if (hash < dot) elm.setAttribute("id", sel.slice(hash + 1, dot)); if (dotIdx > 0) elm.setAttribute("class", sel.slice(dot + 1).replace(/\./g, " ")); for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode); if (is.primitive(vnode.text) && (!is.array(children) || children.length === 0)) { // allow h1 and similar nodes to be created w/ text and empty child list api.appendChild(elm, api.createTextNode(vnode.text)); } if (is.array(children)) { for (i = 0; i < children.length; ++i) { const ch = children[i]; if (ch != null) { api.appendChild(elm, createElm(ch, insertedVnodeQueue)); } } } const hook = vnode.data.hook; if (isDef(hook)) { (_b = hook.create) === null || _b === void 0 ? void 0 : _b.call(hook, emptyNode, vnode); if (hook.insert) { insertedVnodeQueue.push(vnode); } } } else if (((_c = options === null || options === void 0 ? void 0 : options.experimental) === null || _c === void 0 ? void 0 : _c.fragments) && vnode.children) { vnode.elm = ((_d = api.createDocumentFragment) !== null && _d !== void 0 ? _d : documentFragmentIsNotSupported)(); for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode); for (i = 0; i < vnode.children.length; ++i) { const ch = vnode.children[i]; if (ch != null) { api.appendChild(vnode.elm, createElm(ch, insertedVnodeQueue)); } } } else { vnode.elm = api.createTextNode(vnode.text); } return vnode.elm; } function addVnodes(parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) { for (; startIdx <= endIdx; ++startIdx) { const ch = vnodes[startIdx]; if (ch != null) { api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before); } } } function invokeDestroyHook(vnode) { var _a, _b; const data = vnode.data; if (data !== undefined) { (_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); for (let i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode); if (vnode.children !== undefined) { for (let j = 0; j < vnode.children.length; ++j) { const child = vnode.children[j]; if (child != null && typeof child !== "string") { invokeDestroyHook(child); } } } } } function removeVnodes(parentElm, vnodes, startIdx, endIdx) { var _a, _b; for (; startIdx <= endIdx; ++startIdx) { let listeners; let rm; const ch = vnodes[startIdx]; if (ch != null) { if (isDef(ch.sel)) { invokeDestroyHook(ch); listeners = cbs.remove.length + 1; rm = createRmCb(ch.elm, listeners); for (let i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm); 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; if (isDef(removeHook)) { removeHook(ch, rm); } else { rm(); } } else if (ch.children) { // Fragment node invokeDestroyHook(ch); removeVnodes(parentElm, ch.children, 0, ch.children.length - 1); } else { // Text node api.removeChild(parentElm, ch.elm); } } } } function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) { let oldStartIdx = 0; let newStartIdx = 0; let oldEndIdx = oldCh.length - 1; let oldStartVnode = oldCh[0]; let oldEndVnode = oldCh[oldEndIdx]; let newEndIdx = newCh.length - 1; let newStartVnode = newCh[0]; let newEndVnode = newCh[newEndIdx]; let oldKeyToIdx; let idxInOld; let elmToMove; let before; while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (oldStartVnode == null) { oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left } else if (oldEndVnode == null) { oldEndVnode = oldCh[--oldEndIdx]; } else if (newStartVnode == null) { newStartVnode = newCh[++newStartIdx]; } else if (newEndVnode == null) { newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldStartVnode, newStartVnode)) { patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue); oldStartVnode = oldCh[++oldStartIdx]; newStartVnode = newCh[++newStartIdx]; } else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue); oldEndVnode = oldCh[--oldEndIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue); api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm)); oldStartVnode = oldCh[++oldStartIdx]; newEndVnode = newCh[--newEndIdx]; } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue); api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm); oldEndVnode = oldCh[--oldEndIdx]; newStartVnode = newCh[++newStartIdx]; } else { if (oldKeyToIdx === undefined) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); } idxInOld = oldKeyToIdx[newStartVnode.key]; if (isUndef(idxInOld)) { // `newStartVnode` is new, create and insert it in beginning api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm); newStartVnode = newCh[++newStartIdx]; } else if (isUndef(oldKeyToIdx[newEndVnode.key])) { // `newEndVnode` is new, create and insert it in the end api.insertBefore(parentElm, createElm(newEndVnode, insertedVnodeQueue), api.nextSibling(oldEndVnode.elm)); newEndVnode = newCh[--newEndIdx]; } else { // Neither of the new endpoints are new vnodes, so we make progress by // moving `newStartVnode` into position elmToMove = oldCh[idxInOld]; if (elmToMove.sel !== newStartVnode.sel) { api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm); } else { patchVnode(elmToMove, newStartVnode, insertedVnodeQueue); oldCh[idxInOld] = undefined; api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm); } newStartVnode = newCh[++newStartIdx]; } } } if (newStartIdx <= newEndIdx) { before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm; addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue); } if (oldStartIdx <= oldEndIdx) { removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); } } function patchVnode(oldVnode, vnode, insertedVnodeQueue) { var _a, _b, _c, _d, _e, _f, _g, _h; const hook = (_a = vnode.data) === null || _a === void 0 ? void 0 : _a.hook; (_b = hook === null || hook === void 0 ? void 0 : hook.prepatch) === null || _b === void 0 ? void 0 : _b.call(hook, oldVnode, vnode); const elm = (vnode.elm = oldVnode.elm); if (oldVnode === vnode) return; if (vnode.data !== undefined || (isDef(vnode.text) && vnode.text !== oldVnode.text)) { (_c = vnode.data) !== null && _c !== void 0 ? _c : (vnode.data = {}); (_d = oldVnode.data) !== null && _d !== void 0 ? _d : (oldVnode.data = {}); for (let i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode); (_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); } const oldCh = oldVnode.children; const ch = vnode.children; if (isUndef(vnode.text)) { if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue); } else if (isDef(ch)) { if (isDef(oldVnode.text)) api.setTextContent(elm, ""); addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); } else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1); } else if (isDef(oldVnode.text)) { api.setTextContent(elm, ""); } } else if (oldVnode.text !== vnode.text) { if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1); } api.setTextContent(elm, vnode.text); } (_h = hook === null || hook === void 0 ? void 0 : hook.postpatch) === null || _h === void 0 ? void 0 : _h.call(hook, oldVnode, vnode); } return function patch(oldVnode, vnode) { let i, elm, parent; const insertedVnodeQueue = []; for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i](); if (isElement(api, oldVnode)) { oldVnode = emptyNodeAt(oldVnode); } else if (isDocumentFragment(api, oldVnode)) { oldVnode = emptyDocumentFragmentAt(oldVnode); } if (sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode, insertedVnodeQueue); } else { elm = oldVnode.elm; parent = api.parentNode(elm); createElm(vnode, insertedVnodeQueue); if (parent !== null) { api.insertBefore(parent, vnode.elm, api.nextSibling(elm)); removeVnodes(parent, [oldVnode], 0, 0); } } for (i = 0; i < insertedVnodeQueue.length; ++i) { insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]); } for (i = 0; i < cbs.post.length; ++i) cbs.post[i](); return vnode; }; } //# sourceMappingURL=init.js.map