superfinesse
Version:
modified version of superfine
306 lines (279 loc) • 11.9 kB
JavaScript
const RECYCLED_NODE = 1,
TEXT_NODE = 3,
EMPTY_OBJ = {},
EMPTY_ARR = [],
map = EMPTY_ARR.map,
IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|^--/i,
isArray = Array.isArray,
d = document,
eventProxy = function (e) {
return this.__ev[e.type](e);
},
setStyle = (style, key, value) => {
if (key[0] === '-') style.setProperty(key, value);
else style[key] = typeof value === 'number'
&& IS_NON_DIMENSIONAL.test(key) === false ? value + 'px' : value == null ? '' : value;
},
// preact inspired setProperty
setProperty = (dom, name, value, oldValue, isSvg, _style, _newHTML) => {
if (isSvg) {
if (name === 'className') name = 'class';
} else if (name === 'class') name = 'className';
if (name === 'key' || name === 'children') {
} else if (name === 'style') {
_style = dom.style;
if (typeof value === 'string') _style.cssText = value;
else {
if (typeof oldValue === 'string') (_style.cssText = '', oldValue = null);
if (oldValue) for (let i in oldValue) if (!(value && i in value)) setStyle(_style, i, '');
if (value) for (let i in value) if (!oldValue || value[i] !== oldValue[i]) setStyle(_style, i, value[i]);
}
} else if (name[0] === 'o' && name[1] === 'n') {
let useCapture = name !== (name = name.replace(/Capture$/, '')),
nameLower = name.toLowerCase();
name = (nameLower in dom ? nameLower : name).slice(2);
if (value) {
if (!oldValue) dom.addEventListener(name, eventProxy, useCapture);
(dom.__ev || (dom.__ev = {}))[name] = value;
} else dom.removeEventListener(name, eventProxy, useCapture);
} else if (name === 'dangerouslySetInnerHTML') {
if (value || oldValue) {
if (!value || !oldValue || value.__html != oldValue.__html) {
dom.innerHTML = (value && value.__html) || '';
}
if (value) return true
}
} else if (!(['list', 'tagName', 'form', 'type', 'size'].includes(name)) && !isSvg && name in dom) {
dom[name] = value == null ? '' : value;
} else if (typeof value !== 'function') {
if (name !== (name = name.replace(/^xlink:?/, ''))) {
if (value == null || value === false) {
dom.removeAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase());
} else dom.setAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase(), value);
} else if (value == null || (value === false && !/^ar/.test(name))) dom.removeAttribute(name);
else dom.setAttribute(name, value);
}
},
diffProps = (dom, newProps, oldProps, isSvg, _skipDiffChildren) => {
let i;
for (i in oldProps) if (!(i in newProps)) {
if (setProperty(dom, i, null, oldProps[i], isSvg)) _skipDiffChildren = true;
}
for (i in newProps) if (i !== 'value' && i !== 'checked' && oldProps[i] !== newProps[i]) {
if (setProperty(dom, i, newProps[i], oldProps[i], isSvg)) _skipDiffChildren = true;
}
return _skipDiffChildren
},
getKey = (vnode) => vnode == null ? null : vnode.key,
patchNode = (parent, node, oldVNode, newVNode, isSvg) => {
if (oldVNode === newVNode) {
} else if (
oldVNode != null &&
oldVNode.type === TEXT_NODE &&
newVNode.type === TEXT_NODE
) {
if (oldVNode.name !== newVNode.name) node.nodeValue = newVNode.name
} else if (oldVNode == null || oldVNode.name !== newVNode.name) {
node = parent.insertBefore(createNode(newVNode, isSvg), node)
if (oldVNode != null) {
parent.removeChild(oldVNode.node)
}
} else {
let tmpVKid, oldVKid,
oldKey, newKey,
oldVProps = oldVNode.props,
newVProps = newVNode.props,
oldVKids = oldVNode.children,
newVKids = newVNode.children,
oldHead = 0,
newHead = 0,
oldTail = oldVKids.length - 1,
newTail = newVKids.length - 1;
isSvg = isSvg || newVNode.name === "svg"
if (diffProps(node, newVProps, oldVProps, isSvg)) {
return (newVNode.node = node);
}
while (newHead <= newTail && oldHead <= oldTail) {
if (
(oldKey = getKey(oldVKids[oldHead])) == null ||
oldKey !== getKey(newVKids[newHead])
) {
break
}
patchNode(
node,
oldVKids[oldHead].node,
oldVKids[oldHead++],
newVKids[newHead++],
isSvg
)
}
while (newHead <= newTail && oldHead <= oldTail) {
if (
(oldKey = getKey(oldVKids[oldTail])) == null ||
oldKey !== getKey(newVKids[newTail])
) {
break
}
patchNode(
node,
oldVKids[oldTail].node,
oldVKids[oldTail--],
newVKids[newTail--],
isSvg
)
}
if (oldHead > oldTail) {
while (newHead <= newTail) {
node.insertBefore(
createNode(newVKids[newHead++], isSvg),
(oldVKid = oldVKids[oldHead]) && oldVKid.node
)
}
} else if (newHead > newTail) {
while (oldHead <= oldTail) {
node.removeChild(oldVKids[oldHead++].node)
}
} else {
for (var i = oldHead, keyed = {}, newKeyed = {}; i <= oldTail; i++) {
if ((oldKey = oldVKids[i].key) != null) {
keyed[oldKey] = oldVKids[i]
}
}
while (newHead <= newTail) {
oldKey = getKey((oldVKid = oldVKids[oldHead]))
newKey = getKey(newVKids[newHead])
if (
newKeyed[oldKey] ||
(newKey != null && newKey === getKey(oldVKids[oldHead + 1]))
) {
if (oldKey == null) {
node.removeChild(oldVKid.node)
}
oldHead++
continue
}
if (newKey == null || oldVNode.type === RECYCLED_NODE) {
if (oldKey == null) {
patchNode(
node,
oldVKid && oldVKid.node,
oldVKid,
newVKids[newHead],
isSvg
)
newHead++
}
oldHead++
} else {
if (oldKey === newKey) {
patchNode(node, oldVKid.node, oldVKid, newVKids[newHead], isSvg)
newKeyed[newKey] = true
oldHead++
} else {
if ((tmpVKid = keyed[newKey]) != null) {
patchNode(
node,
node.insertBefore(tmpVKid.node, oldVKid && oldVKid.node),
tmpVKid,
newVKids[newHead],
isSvg
)
newKeyed[newKey] = true
} else {
patchNode(
node,
oldVKid && oldVKid.node,
null,
newVKids[newHead],
isSvg
)
}
}
newHead++
}
}
while (oldHead <= oldTail) {
if (getKey((oldVKid = oldVKids[oldHead++])) == null) {
node.removeChild(oldVKid.node)
}
}
for (var i in keyed) {
if (newKeyed[i] == null) {
node.removeChild(keyed[i].node)
}
}
}
}
return (newVNode.node = node)
},
createNode = (vnode, isSvg) => {
var node = vnode.type === TEXT_NODE
? d.createTextNode(vnode.name)
: (isSvg = isSvg || vnode.name === "svg")
? d.createElementNS("http://www.w3.org/2000/svg", vnode.name)
: d.createElement(vnode.name);
diffProps(node, vnode.props, {}, isSvg);
for (var i = 0, len = vnode.children.length; i < len; i++) {
node.appendChild(createNode(vnode.children[i], isSvg))
}
return (vnode.node = node)
},
createVNode = (name, props, children, node, key, type) => ({
name: name,
props: props,
children: children,
node: node,
type: type,
key: key,
}),
createTextVNode = (value, node) => createVNode(value, EMPTY_OBJ, EMPTY_ARR, node, null, TEXT_NODE),
recycleNode = (node, parent) => {
return node.nodeType === TEXT_NODE
? createTextVNode(node.nodeValue, node)
: createVNode(
node.nodeName.toLowerCase(),
EMPTY_OBJ,
map.call(node.childNodes, recycleNode),
node,
null,
RECYCLED_NODE
)
},
Fragment = (props) => props.children,
render = (vdom, node, options) => {
let oldVDom = node.vdom || recycleNode(node);
vdom = createVNode(// this enables the use of a root fragment
oldVDom.name,
EMPTY_OBJ,
[].concat(vdom),
oldVDom.node
);
return ((node = patchNode(
node.parentNode,
node,
oldVDom,
vdom
)).vdom = vdom), node
},
mount = (vdom, node) => {
if (!isArray(vdom)) vdom = [vdom];
for (var i = vdom.length; i--;) node.insertBefore(createNode(vdom[i]), node.firstChild)
},
h = function (name, props) {
for (var vnode, rest = [], children = [], i = arguments.length; i-- > 2;) rest.push(arguments[i]);
if ((props = props == null ? {} : props).children != null) {
if (rest.length <= 0) rest.push(props.children);
delete props.children;
}
while (rest.length > 0) {
if (isArray((vnode = rest.pop()))) {
for (var i = vnode.length; i-- > 0;) rest.push(vnode[i])
} else if (vnode === false || vnode === true || vnode == null) {
} else children.push(typeof vnode === 'object' ? vnode : createTextVNode(vnode));
}
return typeof name === 'function'
? (props.children = props.children || children) && name(props)
: createVNode(name, props, children, null, props.key)
};
export {h, render, Fragment, createNode, mount};