tiny-dream
Version:
A tiny Incremental DOM implementation for modern browsers.
492 lines (393 loc) • 11.7 kB
JavaScript
/*!
* Tiny Dream v0.5.3
* (c) 2018 Yoshihide Shiono
* Released under the MIT License.
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.TinyDream = {})));
}(this, (function (exports) { 'use strict';
var _this = undefined;
function EmptyObject() {}
EmptyObject.prototype = Object.create(null);
var createEmptyObject = function createEmptyObject() {
return new EmptyObject();
};
var cachedHasOwnProperty = Object.prototype.hasOwnProperty;
var hasOwnProperty = function hasOwnProperty(map, property) {
return cachedHasOwnProperty.call(map, property);
};
var isDocumentRoot = function isDocumentRoot(node) {
// TODO check this is accurate
return node === document;
};
var detectAncestors = function detectAncestors(node, root) {
var ancestors = [];
var current = node;
while (current !== root) {
ancestors.push(current);
current = current.parentNode;
}
return ancestors;
};
var detectRootNode = function detectRootNode() {
var current = _this;
var prev = current;
while (current) {
prev = current;
current = current.parentNode;
}
return prev;
};
var detectActiveNode = function detectActiveNode(node) {
var rootNode = detectRootNode.call(node);
return isDocumentRoot(rootNode) ? rootNode.activeElement : null;
};
var detectFocusedAncestors = function detectFocusedAncestors(node, rootNode) {
var activeNode = detectActiveNode(node);
if (!activeNode || !node.contains(activeNode)) {
return [];
}
return detectAncestors(activeNode, rootNode);
};
var moveBefore = function moveBefore(parentNode, node, referenceNode) {
var insertReferenceNode = node.nextSibling;
var current = referenceNode;
while (current !== node) {
var next = current.nextSibling;
parentNode.insertBefore(current, insertReferenceNode);
current = next;
}
};
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var NodeData = function NodeData(nameOrCtor, key, typeId) {
classCallCheck(this, NodeData);
this.attributes = [];
this.attributesApplied = false;
this.key = key;
this.keyMap = createEmptyObject();
this.focused = false;
this.nameOrCtor = nameOrCtor;
this.text = null;
this.typeId = typeId;
};
var initNodeData = function initNodeData(node, nameOrCtor, key, typeId) {
var data = new NodeData(nameOrCtor, key, typeId);
node['__TinyDreamDOMData'] = data;
return data;
};
var isElementNode = function isElementNode(node) {
try {
// for browsers supporting W3 DOM2 like Firefox, Opera and Chrome
return node instanceof HTMLElement;
} catch (e) {
// for IE
return (typeof node === 'undefined' ? 'undefined' : _typeof(node)) === 'object' && node.nodeType === 1 && _typeof(node.style) === 'object' && _typeof(node.ownerDocument) === 'object';
}
};
var importNode = function importNode(node) {
if (node['__TinyDreamDOMData']) {
return;
}
var isElement = isElementNode(node);
var nodeName = isElement ? node.localName : node.nodeName;
var key = isElement ? node.getAttribute('key') : null;
var typeId = node['typeId'];
var data = initNodeData(node, nodeName, key, typeId);
if (key) {
getNodeData(node.parentNode).keyMap[key] = node;
}
if (isElement) {
var attributes = node.attributes;
var attrsArr = data.attributes;
for (var i = 0; i < attributes.length; i += 1) {
var attr = attributes[i];
var name = attr.name;
var value = attr.value;
attrsArr.push(name);
attrsArr.push(value);
}
}
for (var child = node.firstChild; child; child = child.nextSibling) {
importNode(child);
}
};
var getNodeData = function getNodeData(node) {
importNode(node);
return node['__TinyDreamDOMData'];
};
var getNamespaceForTag = function getNamespaceForTag(tag, parent) {
if (tag === 'svg') {
return 'http://www.w3.org/2000/svg';
}
if (getNodeData(parent).nameOrCtor === 'foreignObject') {
return null;
}
return parent.namespaceURI;
};
var createElement = function createElement(doc, parent, nameOrCtor, key, typeId) {
var el = void 0;
if (typeof nameOrCtor === 'function') {
el = new nameOrCtor();
} else {
var namespace = getNamespaceForTag(nameOrCtor, parent);
if (namespace) {
el = doc.createElementNS(namespace, nameOrCtor);
} else {
el = doc.createElement(nameOrCtor);
}
}
initNodeData(el, nameOrCtor, key, typeId);
return el;
};
var createTextNode = function createTextNode(doc) {
var node = doc.createTextNode('');
initNodeData(node, '#text', null);
return node;
};
var doc = null;
var currentNode = null;
var currentParent = null;
var markFocused = function markFocused(focusedAncestors, focused) {
for (var i = 0; i < focusedAncestors.length; i += 1) {
getNodeData(focusedAncestors[i]).focused = focused;
}
};
var matches = function matches(matchNode, nameOrCtor, key, typeId) {
var data = getNodeData(matchNode);
return nameOrCtor === data.nameOrCtor && typeId === data.typeId && key == data.key;
};
var alignWithDOM = function alignWithDOM(nameOrCtor, key, typeId) {
if (currentNode && matches(currentNode, nameOrCtor, key, typeId)) {
return;
}
var parentData = getNodeData(currentParent);
var keyMap = parentData && parentData.keyMap;
var node = void 0;
if (key) {
var keyNode = keyMap[key];
if (keyNode) {
if (matches(keyNode, nameOrCtor, key, typeId)) {
node = keyNode;
} else {
getNodeData(keyNode).key = null;
}
}
}
if (!node) {
if (nameOrCtor === '#text') {
node = createTextNode(doc);
} else {
node = createElement(doc, currentParent, nameOrCtor, key, typeId);
}
if (key) {
keyMap[key] = node;
}
}
if (getNodeData(node).focused) {
moveBefore(currentParent, node, currentNode);
} else {
currentParent.insertBefore(node, currentNode);
}
currentNode = node;
};
var clearUnvisitedDOM = function clearUnvisitedDOM(parentNode, startNode, endNode) {
var data = getNodeData(parentNode);
var keyMap = data.keyMap;
var child = startNode;
while (child !== endNode) {
var next = child.nextSibling;
var key = getNodeData(child).key;
parentNode.removeChild(child);
if (key) {
delete keyMap[key];
}
child = next;
}
};
var enterNode = function enterNode() {
currentParent = currentNode;
currentNode = null;
};
var getNextNode = function getNextNode() {
if (currentNode) {
return currentNode.nextSibling;
}
return currentParent.firstChild;
};
var nextNode = function nextNode() {
currentNode = getNextNode();
};
var exitNode = function exitNode() {
clearUnvisitedDOM(currentParent, getNextNode(), null);
currentNode = currentParent;
currentParent = currentParent.parentNode;
};
var open = function open(nameOrCtor, key, typeId) {
nextNode();
alignWithDOM(nameOrCtor, key, typeId);
enterNode();
return currentParent;
};
var close = function close() {
exitNode();
return currentNode;
};
var text = function text() {
nextNode();
alignWithDOM('#text', null);
return currentNode;
};
var component = function component(c, data) {
if (hasOwnProperty(c, 'render')) {
c.render.apply(null, [data]);
}
};
var patch = function patch(node, fn, data) {
var prevDoc = doc;
var prevCurrentNode = currentNode;
var prevCurrentParent = currentParent;
doc = node && node.ownerDocument || document;
currentParent = node && node.parentNode;
var focusedAncestors = detectFocusedAncestors(node, currentParent);
markFocused(focusedAncestors, true);
currentNode = node;
enterNode();
fn(data);
exitNode();
markFocused(focusedAncestors, false);
doc = prevDoc;
currentNode = prevCurrentNode;
currentParent = prevCurrentParent;
return node;
};
var getNamespace = function getNamespace(name) {
if (name.lastIndexOf('xml:', 0) === 0) {
return 'http://www.w3.org/XML/1998/namespace';
}
if (name.lastIndexOf('xlink:', 0) === 0) {
return 'http://www.w3.org/1999/xlink';
}
};
var applyAttr = function applyAttr(el, name, value) {
if (value === null) {
el.removeAttribute(name);
return;
}
var attrNS = getNamespace(name);
if (attrNS) {
el.setAttributeNS(attrNS, name, value);
return;
}
el.setAttribute(name, value);
};
var applyProp = function applyProp(el, name, value) {
el[name] = value;
};
var setStyleValue = function setStyleValue(style, prop, value) {
if (-1 < prop.indexOf('-')) {
style.setProperty(prop, value);
return;
}
style[prop] = value;
};
var applyStyle = function applyStyle(el, ignore, style) {
if (typeof style === 'string') {
el.style.cssText = style;
return;
}
for (var prop in style) {
if (hasOwnProperty(style, prop)) {
setStyleValue(el.style, prop, style[prop]);
}
}
};
var applyAttributeTyped = function applyAttributeTyped(el, name, value) {
var type = typeof value === 'undefined' ? 'undefined' : _typeof(value);
if (type === 'object' || type === 'function') {
applyProp(el, name, value);
} else {
applyAttr(el, name, value);
}
};
var attributes = createEmptyObject();
attributes['__TinyDream__'] = applyAttributeTyped;
attributes['style'] = applyStyle;
var updateAttribute = function updateAttribute(el, name, value) {
var mutator = attributes[name] || attributes['__TinyDream__'];
mutator(el, name, value);
};
var tagOpen = function tagOpen(nameOrCtor, attributes$$1, key) {
var node = open(nameOrCtor, key);
if (!attributes$$1) {
return;
}
var data = getNodeData(node);
if (!data.attributesApplied) {
for (var i = 0; i < attributes$$1.length; i += 2) {
var name = attributes$$1[i];
var value = attributes$$1[i + 1];
updateAttribute(node, name, value);
}
data.attributesApplied = true;
}
var attrs = data.attributes;
var isNew = !attrs.length;
for (var _i = 0; _i < attributes$$1.length; _i += 2) {
var _name = attributes$$1[_i];
if (isNew) {
attrs[_i] = _name;
}
var _value = attributes$$1[_i + 1];
if (isNew || attrs[_i + 1] !== _value) {
attrs[_i + 1] = _value;
updateAttribute(node, _name, _value);
}
}
return node;
};
var tagClose = function tagClose(nameOrCtor) {
var node = close();
return node;
};
var tagVoid = function tagVoid(nameOrCtor, attributes$$1, key) {
tagOpen(nameOrCtor, attributes$$1, key);
return tagClose(nameOrCtor);
};
var text$1 = function text$$1(value, attributes$$1) {
var node = text();
var data = getNodeData(node);
if (data.text !== value) {
data.text = value;
node.data = value;
}
return node;
};
var baseElement = void 0;
var baseComponent = void 0;
var TinyDream = function TinyDream(id, app) {
baseElement = document.getElementById(id);
baseComponent = app;
};
var patch$1 = function patch$$1(data) {
patch(baseElement, baseComponent.render, data);
};
exports.TinyDream = TinyDream;
exports.tagOpen = tagOpen;
exports.tagClose = tagClose;
exports.tagVoid = tagVoid;
exports.text = text$1;
exports.component = component;
exports.patch = patch$1;
Object.defineProperty(exports, '__esModule', { value: true });
})));