moy-dom
Version:
A flexiable Virtual DOM library for building modern web interface.
934 lines (829 loc) • 27 kB
JavaScript
/**
* moy-dom v1.1.3
* (jp) 2018 murakami
* @license MIT
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.moyDom = {})));
}(this, (function (exports) { 'use strict';
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var slicedToArray = function () {
function sliceIterator(arr, i) {
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"]) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
return function (arr, i) {
if (Array.isArray(arr)) {
return arr;
} else if (Symbol.iterator in Object(arr)) {
return sliceIterator(arr, i);
} else {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
};
}();
function makeKeyIndexAndFree(list) {
var keyIndex = new Map(),
free = [];
var itemKey = void 0;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = list.entries()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _ref = _step.value;
var _ref2 = slicedToArray(_ref, 2);
var index = _ref2[0];
var item = _ref2[1];
itemKey = item.key;
if (itemKey !== undefined) {
keyIndex.set(itemKey, index);
} else {
free.push(item);
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return {
keyIndex: keyIndex,
free: free
};
}
/**
* [remove remove list item]
* @param {[Number]} simulateIndex [index]
* @param {[Array]} simulateList [list]
* @param {[Object]} changes [changes]
* @return {[undefined]} [undefined]
*/
function remove(simulateIndex, simulateList, changes) {
simulateList.splice(simulateIndex, 1);
changes.push({
type: 0,
index: simulateIndex
});
}
/**
* [move move list two item]
* @param {[Number]} newIndex [toIndex]
* @param {[Number]} simulateFindIndex [fromIndex]
* @param {[Array]} simulateList [List]
* @param {[changes]} changes [changes]
* @return {[unfined]} [undefined]
*/
function move(newIndex, simulateFindIndex, simulateList, changes) {
var _ref = [simulateList[simulateFindIndex], simulateList[newIndex]];
simulateList[newIndex] = _ref[0];
simulateList[simulateFindIndex] = _ref[1];
changes.push({
type: 1,
toIndex: newIndex,
fromIndex: simulateFindIndex
});
}
/**
* [insert insert a given location item to a list]
* @param {[Number]} newIndex [index]
* @param {[Any]} newItem [item]
* @param {[Array]} simulateList [list]
* @param {[Object]} changes [changes]
* @return {[undefined]} [undefined]
*/
function insert(newIndex, newItem, simulateList, changes) {
simulateList.splice(newIndex, 0, newItem);
changes.push({
type: 2,
index: newIndex,
item: newItem
});
}
/**
* [append append a child to a list]
* @param {[Any]} newItem [child]
* @param {[Array]} simulateList [list]
* @param {[Object]} changes [changes]
* @return {[undefined]} [undefined]
*/
function append(newItem, simulateList, changes) {
simulateList.push(newItem); //no valid
changes.push({
type: 3,
item: newItem
});
}
/**
* [listDiff diff two List]
* @param {[Array]} newList [the new List]
* @param {[Array]} oldList [the old List]
* @return {[Object]} [a children property for deep compare and a changes for two list's diffs]
*
* @changes's type
* 0 -> remove
* 1 -> move
* 2 -> insert
* 3 -> append
*/
function listDiff(newList, oldList) {
var _makeKeyIndexAndFree = makeKeyIndexAndFree(newList),
newKeyIndex = _makeKeyIndexAndFree.keyIndex,
newFree = _makeKeyIndexAndFree.free,
children = [],
changes = [],
newFreeLength = newFree.length;
var newFreeIndex = 0,
hittingItemKeyIndex = void 0,
oldItemKey = void 0;
// first we get old list same struct children for deep compare
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = oldList[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var oldItem = _step.value;
oldItemKey = oldItem.key;
if (oldItemKey !== undefined) {
hittingItemKeyIndex = newKeyIndex.get(oldItemKey);
children.push(hittingItemKeyIndex !== undefined ? newList[hittingItemKeyIndex] : null);
} else {
// original place
children.push(newFreeIndex < newFreeLength ? newFree[newFreeIndex++] : null);
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var simulateList = [].concat(children); // the simulate for list changes
var simulateIndex = 0,
simulateLength = simulateList.length;
// remove null items represent not exit items in new list
while (simulateIndex < simulateLength) {
if (simulateList[simulateIndex] === null) {
remove(simulateIndex, simulateList, changes);
simulateLength--;
} else {
simulateIndex++;
}
}
simulateIndex = 0;
var newIndex = 0,
newLength = newList.length,
simulateKeyIndex = void 0,
// may need simulateKeyIndex for deep compare
simulateItem = void 0,
simulateItemKey = void 0,
newItem = void 0,
newItemKey = void 0;
// point to point compare
while (simulateIndex < simulateLength && newIndex < newLength) {
simulateItem = simulateList[simulateIndex];
simulateItemKey = simulateItem.key;
newItem = newList[newIndex];
newItemKey = newItem.key;
// the same node
if (newItemKey === simulateItemKey) {
simulateIndex++;
newIndex++;
} else {
if (!simulateKeyIndex) {
// get simulateKeyIndex
simulateKeyIndex = makeKeyIndexAndFree(simulateList).keyIndex;
}
if (hittingItemKeyIndex = simulateKeyIndex.get(newItemKey)) {
// find the same key in old list, just move it
move(newIndex++, hittingItemKeyIndex, simulateList, changes);
simulateIndex++;
} else {
// otherwise inset a new one
insert(newIndex, newItem, simulateList, changes);
simulateLength++;
simulateIndex++;
newIndex++;
}
}
}
// rest new list item should be append
while (newIndex < newLength) {
append(newList[newIndex++], simulateList, changes);
}
return {
children: children,
changes: changes
};
}
/**
* [diffProps diff two object(props)]
* @param {[Object]} newTreeProps [the new props]
* @param {[Object]} oldTreeProps [the old props]
* @return {[Object]} [null for no changes, Object for changes]
*/
function diffProps(newTreeProps, oldTreeProps) {
var propPatches = {};
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = Object.entries(oldTreeProps)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _ref = _step.value;
var _ref2 = slicedToArray(_ref, 2);
var key = _ref2[0];
var value = _ref2[1];
if (newTreeProps[key] !== value) {
propPatches[key] = newTreeProps[key]; // undefined represents remove the attribute
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = Object.entries(newTreeProps)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var _ref3 = _step2.value;
var _ref4 = slicedToArray(_ref3, 2);
var _key = _ref4[0];
var _value = _ref4[1];
if (!Reflect.has(oldTreeProps, _key)) {
propPatches[_key] = _value;
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
for (var propPatch in propPatches) {
return propPatches;
}
return null;
}
/**
* [diffChildren diff two children list]
* @param {[Array]} newChildren [the Element Array for old children]
* @param {[Array]} oldChildren [the Element Array for new children]
* @param {[Number]} index [current node index]
* @param {[Object]} patches [patches]
* @param {[Object]} currentPatches [current node patches]
* @return {[undefined]} [undefined]
*/
var diffChildren = function diffChildren(newChildren, oldChildren, index, patches, currentPatches) {
var _listDiff = listDiff(newChildren, oldChildren),
children = _listDiff.children,
changes = _listDiff.changes;
if (changes.length) {
currentPatches.push({ // remove, move, insert, append node
type: 1,
changes: changes
});
}
newChildren = children; // children -> newChildren for deep compare
var leftNode = null,
currentNodeIndex = index;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = oldChildren.entries()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _ref = _step.value;
var _ref2 = slicedToArray(_ref, 2);
var _index = _ref2[0];
var oldChild = _ref2[1];
var newChild = newChildren[_index];
currentNodeIndex += 1 + (leftNode && leftNode.count ? leftNode.count : 0);
dfsWalk(newChild, oldChild, currentNodeIndex, patches);
leftNode = oldChild;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
};
/**
* [dfsWalk diff two node tree]
* @param {[Element]} newTree [new tree]
* @param {[Element]} oldTree [old tree]
* @param {[Number]} index [current tree node index]
* @param {[Object]} patches [patches]
* @return {[undefined]} [undefined]
*/
function dfsWalk(newTree, oldTree, index, patches) {
var currentPatches = [];
if (newTree === null) ; else if (Object.prototype.toString.call(newTree) === '[object String]' && Object.prototype.toString.call(oldTree) === '[object String]') {
newTree !== oldTree && currentPatches.push({
type: 3, // text node
content: newTree
});
} else if (newTree.tagName === oldTree.tagName && newTree.key === oldTree.key) {
var propPatches = diffProps(newTree.props, oldTree.props);
if (propPatches) {
currentPatches.push({
type: 2, // props changed
props: propPatches
});
}
if (!newTree.props.ignore) {
diffChildren(newTree.children, oldTree.children, index, patches, currentPatches);
}
} else {
currentPatches.push({
type: 0, // not the same node, replace it
node: newTree
});
}
if (currentPatches.length) {
patches[index] = currentPatches;
}
}
function diff(newTree, oldTree) {
var index = 0,
patches = {};
if (oldTree === null) {
if (newTree !== null) {
patches.rootWithNull = {
type: 4, // append newNode
vnode: newTree
};
}
return patches;
} else {
if (newTree === null) {
patches.rootWithNull = {
type: 5 // remove oldNode
};
return patches;
}
}
if (Object.prototype.toString.call(newTree) !== '[object Element]') {
newTree += '';
}
if (Object.prototype.toString.call(oldTree) !== '[object Element]') {
oldTree += '';
}
dfsWalk(newTree, oldTree, index, patches);
return patches;
}
var domAttrs = {
selected: true,
checked: true,
muted: true,
value: true
},
boolAttrs = {
allowfullscreen: true,
async: true,
autofocus: true,
autoplay: true,
compact: true,
controls: true,
declare: true,
default: true,
defaultchecked: true,
defaultmuted: true,
defaultselected: true,
defer: true,
disabled: true,
enabled: true,
formnovalidate: true,
hidden: true,
indeterminate: true,
inert: true,
ismap: true,
itemscope: true,
loop: true,
multiple: true,
nohref: true,
noresize: true,
noshade: true,
novalidate: true,
nowrap: true,
open: true,
pauseonexit: true,
readonly: true,
required: true,
reversed: true,
scoped: true,
seamless: true,
sortable: true,
translate: true,
truespeed: true,
typemustmatch: true,
visible: true
};
/**
* [setProps set node propertities with given props]
* @param {[DOMElement]} node [the node]
* @param {[Object]} props [given props]
* @return {[undefined]} [undefined]
*/
function setProps(node, props) {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = Object.entries(props)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _ref = _step.value;
var _ref2 = slicedToArray(_ref, 2);
var key = _ref2[0];
var value = _ref2[1];
if (domAttrs[key] || /^on\w+/.test(key)) {
node[key] = value === undefined ? null : value;
} else if (boolAttrs[key]) {
if (value) {
node.setAttribute(key, key);
} else {
node.removeAttribute(key);
}
} else {
if (value === undefined) {
node.removeAttribute(key);
} else {
node.setAttribute(key, value);
}
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
/**
* [renderChildren render node's children for given changes]
* @param {[DOMElement]} node [description]
* @param {[Array]} changes [the given changes]
* @return {[undefined]} [undefined]
*/
function renderChildren(node, changes) {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = changes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _ref = _step.value;
var index = _ref.index,
type = _ref.type,
item = _ref.item,
toIndex = _ref.toIndex,
fromIndex = _ref.fromIndex;
if (type === 0) {
//remove
node.removeChild(node.childNodes[index]);
} else if (type === 1) {
// move
node.insertBefore(node.childNodes[toIndex], node.childNodes[fromIndex]);
node.insertBefore(node.childNodes[fromIndex], node.childNodes[toIndex]);
} else if (type === 2) {
// insert
var insertNode = Object.prototype.toString.call(item) === '[object Element]' ? item.render() : document.createTextNode(item);
node.insertBefore(insertNode, node.childNodes[index]);
} else if (type === 3) {
// append
var _insertNode = Object.prototype.toString.call(item) === '[object Element]' ? item.render() : document.createTextNode(item);
node.appendChild(_insertNode);
} else {
throw new Error('Unknown reorder type ' + type);
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
/**
* [applyPatches apply the patches to the node]
* @param {[DOMElement]} node [the node]
* @param {[patches]} currentPatches [the patches]
* @return {[undefined]} [undefined]
*/
function applyPatches(node, currentPatches) {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = currentPatches[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var currentPatch = _step.value;
switch (currentPatch.type) {
case 0:
{
// replace
var newNode = Object.prototype.toString.call(currentPatch.node) === '[object Element]' ? currentPatch.node.render() : document.createTextNode(currentPatch.node);
node.parentNode.replaceChild(newNode, node);
break;
}
case 1:
{
// remove, move, insert, append node
renderChildren(node, currentPatch.changes);
break;
}
case 2:
{
//props
setProps(node, currentPatch.props);
break;
}
case 3:
{
// text node
node.textContent = currentPatch.content;
break;
}
default:
{
throw new Error('Unknown patch type ' + currentPatch.type);
}
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
/**
* [dfsWalk apply patches for given node(recursion)]
* @param {[DOMElements]} node [the node]
* @param {[Object]} walker [the current node index, Object for deep compare changed the index]
* @param {[patches]} patches [the patches]
* @return {[undefined]} [undefined]
*/
function dfsWalk$1(node, walker, patches) {
var currentPatches = patches[walker.index];
var len = node.childNodes.length;
for (var i = 0; i < len; i++) {
var child = node.childNodes[i];
walker.index++;
dfsWalk$1(child, walker, patches);
}
if (currentPatches) {
applyPatches(node, currentPatches);
}
}
function patch(node, patches) {
var rootWithNullPatch = patches.rootWithNull;
if (rootWithNullPatch) {
var type = rootWithNullPatch.type,
vnode = rootWithNullPatch.vnode;
if (type === 4) {
var newNode = Object.prototype.toString.call(vnode) === '[object Element]' ? vnode.render() : document.createTextNode(vnode);
node.appendChild(newNode);
}
if (type === 5) {
node.removeChild(node.lastChild);
}
return;
}
var walker = { index: 0 };
dfsWalk$1(node.lastChild, walker, patches);
}
var Element = function () {
function Element(tagName, props, children) {
classCallCheck(this, Element);
this.tagName = tagName;
this.props = props;
this.key = props.key;
var count = 0,
childrenFilter = [],
length = children.length,
child = void 0;
for (var i = 0; i < length; i++) {
child = children[i];
if (child !== null) {
if (Object.prototype.toString.call(child) === '[object Element]') {
count += child.count + 1;
childrenFilter.push(child);
} else {
count++;
childrenFilter.push('' + child);
}
}
}
this.children = childrenFilter;
this.count = count;
}
createClass(Element, [{
key: 'render',
value: function render() {
var el = document.createElement(this.tagName);
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = Object.entries(this.props)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _ref = _step.value;
var _ref2 = slicedToArray(_ref, 2);
var propKey = _ref2[0];
var propValue = _ref2[1];
if (domAttrs[propKey] || /^on\w+/.test(propKey)) {
el[propKey] = propValue === undefined ? null : propValue;
} else if (boolAttrs[propKey]) {
if (propValue) {
el.setAttribute(propKey, propKey);
}
} else {
if (propValue !== undefined) {
el.setAttribute(propKey, propValue);
}
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = this.children[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var _child = _step2.value;
el.appendChild(Object.prototype.toString.call(_child) === '[object Element]' ? _child.render() : document.createTextNode(_child));
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
return el;
}
}, {
key: Symbol.toStringTag,
get: function get$$1() {
return 'Element';
}
}], [{
key: 'of',
value: function of(tagName) {
var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
for (var _len = arguments.length, children = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
children[_key - 2] = arguments[_key];
}
if (Object.prototype.toString.call(props) !== '[object Object]') {
children.unshift(props);
props = {};
}
return new Element(tagName, props, children);
}
}]);
return Element;
}();
var appRoot = void 0,
appGetNode = void 0,
appCurrentNode = void 0;
var render = function render(root, getNode) {
appCurrentNode = getNode();
appRoot = root;
appGetNode = getNode;
if (appCurrentNode !== null) {
appRoot.appendChild(Object.prototype.toString.call(appCurrentNode) === '[object Element]' ? appCurrentNode.render() : document.createTextNode(appCurrentNode));
}
};
var reRender = function reRender() {
var node = appGetNode();
patch(appRoot, diff(node, appCurrentNode));
appCurrentNode = node;
};
exports.Element = Element;
exports.render = render;
exports.reRender = reRender;
Object.defineProperty(exports, '__esModule', { value: true });
})));