const CHANGE_TYPE_TEXT = Symbol('text')
const CHANGE_TYPE_PROP = Symbol('props')
const CHANGE_TYPE_REPLACE = Symbol('replace')
function checkChangeType(newNode, oldNode) {
if (typeof newNode !== typeof oldNode || newNode.tagName !== oldNode.tagName) {
return CHANGE_TYPE_REPLACE;
}
if (typeof newNode === 'string') {
if (newNode !== oldNode) {
return CHANGE_TYPE_TEXT;
}
return;
}
const propsChanged = Reflect
.ownKeys(newNode.props)
.reduce(
(prev, name) => prev || (oldNode.props[name] !== newNode.props[name]),
false
);
if (propsChanged) {
return CHANGE_TYPE_PROP;
}
return;
}
/**
*
* @param {HTMLElement} $node
* @param {Object} removedAttrs
* @param {Object} newAttrWithVals
*/
function replaceAttribute($node, removedAttrs, newAttrs) {
if (!$node) {
return
}
Reflect.ownKeys(removedAttrs)
.forEach(attr => $node.removeAttribute(attr))
Reflect.ownKeys(newAttrs)
.forEach(attr => $node.setAttribute(attr, newAttrs[attr]))
}
function updateEl($parent, newNode, oldNode, index = 0) {
let changeType = null;
if (!oldNode) {
$parent.appendChild(newNode.render());
} else if (!newNode) {
$parent.removeChild($parent.childNodes[index]);
} else if (changeType = checkChangeType(newNode, oldNode)) {
if (changeType === CHANGE_TYPE_TEXT) {
$parent.replaceChild(
document.createTextNode(newNode),
$parent.childNodes[index]
);
} else if (changeType === CHANGE_TYPE_REPLACE) {
$parent.replaceChild(newNode.render(), $parent.childNodes[index]);
} else if (changeType === CHANGE_TYPE_PROP) {
replaceAttribute($parent.childNodes[index], oldNode.props, newNode.props)
}
} else if (newNode.tagName) {
const newLength = newNode.children.length;
const oldLength = oldNode.children.length;
for (let i = 0; i < newLength || i < oldLength; ++i) {
updateEl(
$parent.childNodes[index],
newNode.children[i],
oldNode.children[i],
i
);
}
}
}