UNPKG

tiny-dream

Version:

A tiny Incremental DOM implementation for modern browsers.

486 lines (388 loc) 11.4 kB
/*! * Tiny Dream v0.5.3 * (c) 2018 Yoshihide Shiono * Released under the MIT License. */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); 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;