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.
374 lines
15 KiB
374 lines
15 KiB
|
2 years ago
|
import { vnode } from "./vnode";
|
||
|
|
import * as is from "./is";
|
||
|
|
import { htmlDomApi } from "./htmldomapi";
|
||
|
|
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);
|
||
|
|
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 !== 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.array(children)) {
|
||
|
|
for (i = 0; i < children.length; ++i) {
|
||
|
|
const ch = children[i];
|
||
|
|
if (ch != null) {
|
||
|
|
api.appendChild(elm, createElm(ch, insertedVnodeQueue));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (is.primitive(vnode.text)) {
|
||
|
|
api.appendChild(elm, api.createTextNode(vnode.text));
|
||
|
|
}
|
||
|
|
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)) {
|
||
|
|
// New element
|
||
|
|
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
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
|