You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
6.7 KiB
JavaScript
218 lines
6.7 KiB
JavaScript
// jshint newcap: false
|
|
(function (root, factory) {
|
|
if (typeof define === 'function' && define.amd) {
|
|
define([], factory); // AMD. Register as an anonymous module.
|
|
} else if (typeof exports === 'object') {
|
|
module.exports = factory(); // NodeJS
|
|
} else { // Browser globals (root is window)
|
|
root.snabbdom = factory();
|
|
}
|
|
}(this, function () {
|
|
|
|
'use strict';
|
|
|
|
var isArr = Array.isArray;
|
|
|
|
function isString(s) { return typeof s === 'string'; }
|
|
function isPrimitive(s) { return typeof s === 'string' || typeof s === 'number'; }
|
|
function isUndef(s) { return s === undefined; }
|
|
|
|
function VNode(tag, props, children, text, elm) {
|
|
var key = isUndef(props) ? undefined : props.key;
|
|
return {tag: tag, props: props, children: children,
|
|
text: text, elm: elm, key: key};
|
|
}
|
|
|
|
function emptyNodeAt(elm) {
|
|
return VNode(elm.tagName, {style: {}, class: {}}, [], undefined, elm);
|
|
}
|
|
var emptyNode = VNode(undefined, {style: {}, class: {}}, [], undefined);
|
|
|
|
var frag = document.createDocumentFragment();
|
|
|
|
function h(selector, b, c) {
|
|
var props = {}, children, tag, text, i;
|
|
if (arguments.length === 3) {
|
|
props = b;
|
|
if (isArr(c)) { children = c; }
|
|
else if (isPrimitive(c)) { text = c; }
|
|
} else if (arguments.length === 2) {
|
|
if (isArr(b)) { children = b; }
|
|
else if (isPrimitive(b)) { text = b; }
|
|
else { props = b; }
|
|
}
|
|
// Parse selector
|
|
var hashIdx = selector.indexOf('#');
|
|
var dotIdx = selector.indexOf('.', hashIdx);
|
|
var hash = hashIdx > 0 ? hashIdx : selector.length;
|
|
var dot = dotIdx > 0 ? dotIdx : selector.length;
|
|
tag = selector.slice(0, Math.min(hash, dot));
|
|
if (hash < dot) props.id = selector.slice(hash + 1, dot);
|
|
if (dotIdx > 0) props.className = selector.slice(dot+1).replace(/\./g, ' ');
|
|
|
|
if (isArr(children)) {
|
|
for (i = 0; i < children.length; ++i) {
|
|
if (isPrimitive(children[i])) children[i] = VNode(undefined, undefined, undefined, children[i]);
|
|
}
|
|
}
|
|
return VNode(tag, props, children, text, undefined);
|
|
}
|
|
|
|
function updateProps(elm, oldProps, props) {
|
|
var key, val, name, on;
|
|
for (key in props) {
|
|
val = props[key];
|
|
if (key === 'style') {
|
|
for (name in val) {
|
|
on = val[name];
|
|
if (on !== oldProps.style[name]) {
|
|
elm.style[name] = on;
|
|
}
|
|
}
|
|
} else if (key === 'class') {
|
|
for (name in val) {
|
|
on = val[name];
|
|
if (on !== oldProps.class[name]) {
|
|
elm.classList[on ? 'add' : 'remove'](name);
|
|
}
|
|
}
|
|
} else if (key !== 'key') {
|
|
elm[key] = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
function createElm(vnode) {
|
|
var elm, children;
|
|
if (!isUndef(vnode.tag)) {
|
|
elm = document.createElement(vnode.tag);
|
|
if (!isUndef(vnode.props)) {
|
|
updateProps(elm, emptyNode.props, vnode.props);
|
|
}
|
|
children = vnode.children;
|
|
if (isArr(children)) {
|
|
for (var i = 0; i < children.length; ++i) {
|
|
elm.appendChild(createElm(children[i]));
|
|
}
|
|
} else if (isPrimitive(vnode.text)) {
|
|
elm.textContent = vnode.text;
|
|
}
|
|
} else {
|
|
elm = document.createTextNode(vnode.text);
|
|
}
|
|
vnode.elm = elm;
|
|
return elm;
|
|
}
|
|
|
|
function sameVnode(vnode1, vnode2) {
|
|
return vnode1.key === vnode2.key && vnode1.tag === vnode2.tag;
|
|
}
|
|
|
|
function createKeyToOldIdx(children, beginIdx, endIdx) {
|
|
var i, map = {}, key;
|
|
for (i = beginIdx; i <= endIdx; ++i) {
|
|
key = children[i].key;
|
|
if (!isUndef(key)) {
|
|
map[key] = i;
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
function updateChildren(parentElm, oldCh, newCh) {
|
|
var oldStartIdx = 0, oldEndIdx, oldStartVnode, oldEndVnode;
|
|
if (isUndef(oldCh)) {
|
|
oldEndIdx = -1;
|
|
} else {
|
|
oldEndIdx = oldCh.length - 1;
|
|
oldStartVnode = oldCh[0];
|
|
oldEndVnode = oldCh[oldEndIdx];
|
|
}
|
|
|
|
var newStartIdx = 0, newEndIdx, newStartVnode, newEndVnode;
|
|
if (isUndef(newCh)) {
|
|
newEndIdx = -1;
|
|
} else {
|
|
newEndIdx = newCh.length - 1;
|
|
newStartVnode = newCh[0];
|
|
newEndVnode = newCh[newEndIdx];
|
|
}
|
|
|
|
var oldKeyToIdx, idxInOld, elmToMove;
|
|
|
|
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
|
|
if (isUndef(oldStartVnode)) {
|
|
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
|
|
} else if (isUndef(oldEndVnode)) {
|
|
oldEndVnode = oldCh[--oldEndIdx];
|
|
} else if (sameVnode(oldStartVnode, newStartVnode)) {
|
|
patchVnode(oldStartVnode, newStartVnode);
|
|
oldStartVnode = oldCh[++oldStartIdx];
|
|
newStartVnode = newCh[++newStartIdx];
|
|
} else if (sameVnode(oldEndVnode, newEndVnode)) {
|
|
patchVnode(oldEndVnode, newEndVnode);
|
|
oldEndVnode = oldCh[--oldEndIdx];
|
|
newEndVnode = newCh[--newEndIdx];
|
|
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
|
|
patchVnode(oldStartVnode, newEndVnode);
|
|
parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling);
|
|
oldStartVnode = oldCh[++oldStartIdx];
|
|
newEndVnode = newCh[--newEndIdx];
|
|
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
|
|
patchVnode(oldEndVnode, newStartVnode);
|
|
parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm);
|
|
oldEndVnode = oldCh[--oldEndIdx];
|
|
newStartVnode = newCh[++newStartIdx];
|
|
} else {
|
|
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
|
|
idxInOld = oldKeyToIdx[newStartVnode.key];
|
|
if (isUndef(idxInOld)) { // New element
|
|
createElm(newStartVnode);
|
|
parentElm.insertBefore(newStartVnode.elm, oldStartVnode.elm);
|
|
newStartVnode = newCh[++newStartIdx];
|
|
} else {
|
|
elmToMove = oldCh[idxInOld];
|
|
patchVnode(elmToMove, newStartVnode);
|
|
oldCh[idxInOld] = undefined;
|
|
parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm);
|
|
newStartVnode = newCh[++newStartIdx];
|
|
}
|
|
}
|
|
}
|
|
if (oldStartIdx > oldEndIdx) { // Done with old elements
|
|
for (; newStartIdx <= newEndIdx; ++newStartIdx) {
|
|
frag.appendChild(createElm(newCh[newStartIdx]));
|
|
}
|
|
if (isUndef(oldStartVnode)) {
|
|
parentElm.appendChild(frag);
|
|
} else {
|
|
parentElm.insertBefore(frag, oldStartVnode.elm);
|
|
}
|
|
} else if (newStartIdx > newEndIdx) { // Done with new elements
|
|
for (; oldStartIdx <= oldEndIdx; ++oldStartIdx) {
|
|
var ch = oldCh[oldStartIdx];
|
|
if (!isUndef(ch)) {
|
|
parentElm.removeChild(ch.elm);
|
|
ch.elm = undefined;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function patchVnode(oldVnode, newVnode) {
|
|
var elm = newVnode.elm = oldVnode.elm;
|
|
if (!isUndef(newVnode.props)) {
|
|
updateProps(elm, oldVnode.props, newVnode.props);
|
|
}
|
|
if (isUndef(newVnode.text)) {
|
|
updateChildren(elm, oldVnode.children, newVnode.children);
|
|
} else if (oldVnode.text !== newVnode.text) {
|
|
elm.textContent = newVnode.text;
|
|
}
|
|
return newVnode;
|
|
}
|
|
|
|
return {h: h, createElm: createElm, patchElm: patchVnode, patch: patchVnode, emptyNodeAt: emptyNodeAt, emptyNode: emptyNode};
|
|
|
|
}));
|