@power-js/core
Advanced tools
Comparing version 0.0.2 to 0.0.3
@@ -12,3 +12,3 @@ (function (global, factory) { | ||
*/ | ||
var DATA_COMPONENT_ATTRIBUTE = 'power-component'; | ||
var POWER_COMPONENT_ATTRIBUTE = 'power-component'; | ||
/** | ||
@@ -20,3 +20,3 @@ * data node id | ||
var DATA_NODE_ATTRIBUTE = 'power-id'; | ||
var POWER_NODE_ATTRIBUTE = 'power-id'; | ||
@@ -28,3 +28,3 @@ /** | ||
var _counter = 0; | ||
var counter = 0; | ||
/** | ||
@@ -42,5 +42,7 @@ * Creates a Virtual Node | ||
this.children = children || []; | ||
this.props = props || {}; | ||
_counter += 1; | ||
this.props[DATA_NODE_ATTRIBUTE] = _counter; | ||
this.props = props || {}; // increment counter | ||
counter += 1; // assign counter to props | ||
this.props[POWER_NODE_ATTRIBUTE] = counter; | ||
return this; | ||
@@ -156,2 +158,13 @@ } | ||
/** | ||
* Determines if the passed children are keyed or not | ||
* @private | ||
* @param {Array} oldChildren | ||
* @param {Array} newChildren | ||
* @return {Boolean} | ||
*/ | ||
var isKeyedList = function isKeyedList(oldChildren, newChildren) { | ||
return newChildren.length && newChildren[0].props && newChildren[0].props.key || oldChildren.length && oldChildren[0].props && oldChildren[0].props.key; | ||
}; | ||
/** | ||
* Determines wheter the passed object is a vnode | ||
@@ -213,2 +226,85 @@ * @private | ||
/** | ||
* Renders a component or vnodes in the given root | ||
* @public | ||
* @param {Object|Function} model | ||
* @param {DOM Element} root | ||
*/ | ||
var render = function render(model, root) { | ||
// assign document.body if no root is given | ||
var _root = root || document.body; // JSX will transform Component into functions | ||
if (isFunction(model.tagName)) { | ||
// TODO: better checking | ||
return render(new model.tagName(model.props), _root); | ||
} // handle a class being passed in | ||
if (!isVNode(model) && !model._power) { | ||
return render(new model(), _root); | ||
} // check if model is a component | ||
if (model._power && model.componentWillMount) { | ||
model.componentWillMount(model); | ||
} // convert the vnodes / component into real dom elements | ||
var domTree = model.create(); | ||
if (isHtml(domTree)) { | ||
_root.appendChild(domTree); | ||
} | ||
if (model._power && model.componentDidMount) { | ||
model.componentDidMount(model); | ||
} | ||
return model; | ||
}; | ||
function _classCallCheck(instance, Constructor) { | ||
if (!(instance instanceof Constructor)) { | ||
throw new TypeError("Cannot call a class as a 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); | ||
} | ||
} | ||
function _createClass(Constructor, protoProps, staticProps) { | ||
if (protoProps) _defineProperties(Constructor.prototype, protoProps); | ||
if (staticProps) _defineProperties(Constructor, staticProps); | ||
return Constructor; | ||
} | ||
/** | ||
* Shallow copies all properties from the config object to the target object | ||
* @param {Object} target The receiving object you want to apply the source objects to | ||
* @param {Object} arguments The source object(s) containing the new or updated properties | ||
* @return {Object} The target object | ||
*/ | ||
var extend = function extend(target) { | ||
var sources = [].slice.call(arguments, 1); | ||
for (var i = 0, k = sources.length; i < k; i++) { | ||
var props = sources[i]; | ||
for (var prop in props) { | ||
target[prop] = props[prop]; | ||
} | ||
} | ||
return target; | ||
}; | ||
/** | ||
* Assigns a callback function to the event type on the specificed element that | ||
@@ -280,3 +376,6 @@ * will be called whenever the event is triggered | ||
if (prop === 'style') { | ||
updateElementStyles(element, props[prop]); | ||
if (!isEqual(element.style, props.style)) { | ||
updateElementStyles(element, props[prop]); | ||
} | ||
continue; | ||
@@ -290,5 +389,6 @@ } | ||
if (isElementAttribute(element, prop)) { | ||
element.setAttribute(jsxProps[prop] || prop, props[prop]); | ||
continue; | ||
if (isElementAttribute(element, prop) || prop === 'key') { | ||
if (!element[prop] || props[prop] !== element[prop]) { | ||
element.setAttribute(jsxProps[prop] || prop, props[prop]); | ||
} | ||
} | ||
@@ -356,9 +456,12 @@ } | ||
var element = document.createElement(vnode.tagName.name || vnode.tagName); | ||
var fragment = document.createDocumentFragment(); | ||
if (isObject(vnode.props)) { | ||
decorateElement(element, vnode.props); | ||
if (vnode.children && vnode.children.length) { | ||
appendChildren(fragment, vnode.children); | ||
} | ||
if (vnode.children && vnode.children.length) { | ||
appendChildren(element, vnode.children); | ||
element.appendChild(fragment); | ||
if (vnode.props && Object.keys(vnode.props).length) { | ||
decorateElement(element, vnode.props); | ||
} | ||
@@ -370,82 +473,24 @@ | ||
/** | ||
* Renders a component or vnodes in the given root | ||
* @public | ||
* @param {Object|Function} model | ||
* @param {DOM Element} root | ||
* checks out the difference between 2 objects | ||
* and merges it into the component element | ||
* @private | ||
* @param {Object} oldObj | ||
* @param {Object} newObj | ||
* @param {HTMLElement} element | ||
*/ | ||
var render = function render(model, root) { | ||
// JSX will transform Component into functions | ||
if (isFunction(model.tagName)) { | ||
// TODO: better checking | ||
return render(new model.tagName(model.props), root); | ||
} // check if a root is given | ||
if (!isHtml(root)) { | ||
throw 'You MUST provide a valid DOM element as your root.'; | ||
} // check if model is neither a vdom or component | ||
if (!isVNode(model) && !model._power) { | ||
return render(new model(), root); | ||
} // check if model is a component | ||
if (model._power && model.componentWillMount) { | ||
model.componentWillMount(model); | ||
} // convert the vnodes / component into real dom elements | ||
var domTree = model._power ? model.create() : createElement(model); | ||
if (isHtml(domTree)) { | ||
root.appendChild(domTree); | ||
var propsDiff = function propsDiff(oldObj, newObj, element) { | ||
// prevent unneeded iterations | ||
if (isEqual(oldObj, newObj)) { | ||
return; | ||
} | ||
if (model._power && model.componentDidMount) { | ||
model.componentDidMount(model); | ||
} | ||
}; | ||
function _classCallCheck(instance, Constructor) { | ||
if (!(instance instanceof Constructor)) { | ||
throw new TypeError("Cannot call a class as a 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); | ||
} | ||
} | ||
function _createClass(Constructor, protoProps, staticProps) { | ||
if (protoProps) _defineProperties(Constructor.prototype, protoProps); | ||
if (staticProps) _defineProperties(Constructor, staticProps); | ||
return Constructor; | ||
} | ||
/** | ||
* Shallow copies all properties from the config object to the target object | ||
* @param {Object} target The receiving object you want to apply the source objects to | ||
* @param {Object} arguments The source object(s) containing the new or updated properties | ||
* @return {Object} The target object | ||
*/ | ||
var extend = function extend(target) { | ||
var sources = [].slice.call(arguments, 1); | ||
for (var i = 0, k = sources.length; i < k; i++) { | ||
var props = sources[i]; | ||
for (var prop in props) { | ||
target[prop] = props[prop]; | ||
for (var key in oldObj) { | ||
if (!newObj[key]) { | ||
// removing attribute from element | ||
element.removeAttribute(jsxProps[key] || key); | ||
} | ||
} | ||
return target; | ||
decorateElement(element, newObj); | ||
}; | ||
@@ -465,69 +510,9 @@ | ||
/** | ||
* working on difference between 2 vnodes | ||
* Replaces one child node of the specified node with another | ||
* @private | ||
* @param {Object} oldVNode | ||
* @param {Object} newVNode | ||
* @param {Class} Component | ||
* @param {HTMLElement} oldChild | ||
* @param {HTMLElement} newChild | ||
*/ | ||
var diff = function diff(oldVNode, newVNode, Component) { | ||
// get the element id | ||
var powerId = oldVNode.props[DATA_NODE_ATTRIBUTE]; // check if newVNode props is null | ||
if (newVNode.props === null) { | ||
newVNode.props = {}; | ||
} // merge the node id | ||
newVNode.props[DATA_NODE_ATTRIBUTE] = oldVNode.props[DATA_NODE_ATTRIBUTE]; // get the dom element to the vnode | ||
var element = Component.node.querySelector("[".concat(DATA_NODE_ATTRIBUTE, "=\"").concat(powerId, "\"]")); // compare the tag | ||
if (oldVNode.tagName !== newVNode.tagName) { | ||
console.log('tagName changed'); | ||
} // compare props | ||
propsDiff(oldVNode.props, newVNode.props, element); // compare children | ||
childrenDiff(oldVNode.children, newVNode.children, element, Component); | ||
}; | ||
/** | ||
* checks out the difference between 2 objects | ||
* and merges it into the component element | ||
* @private | ||
* @param {Object} oldObj | ||
* @param {Object} newObj | ||
* @param {HTMLElement} element | ||
*/ | ||
var propsDiff = function propsDiff(oldObj, newObj, element) { | ||
// prevent unneeded iterations | ||
if (isEqual(oldObj, newObj)) { | ||
return; | ||
} | ||
if (!isEqual(oldObj.style, newObj.style)) { | ||
// update styling | ||
updateElementStyles(element, newObj.style, oldObj.style); | ||
} | ||
for (var key in oldObj) { | ||
if (!newObj[key]) { | ||
// removing attribute from element | ||
element.removeAttribute(jsxProps[key] || key); | ||
} | ||
} | ||
for (var _key in newObj) { | ||
if (_key !== 'style' && !isEvent(_key)) { | ||
// check if there a new key | ||
if (!oldObj[_key] || newObj[_key] !== oldObj[_key]) { | ||
// add attribute to element | ||
element.setAttribute(jsxProps[_key] || _key, newObj[_key]); | ||
} | ||
} | ||
} | ||
}; | ||
/** | ||
* checks out the difference between 2 Arrays | ||
@@ -576,3 +561,3 @@ * and merges it into the component element | ||
while (childLengthDiff > 0) { | ||
removeNode(Component.node.querySelector("[".concat(DATA_NODE_ATTRIBUTE, "=\"").concat(oldChildren[childLength].props[DATA_NODE_ATTRIBUTE], "\"]"))); | ||
removeNode(Component.node.querySelector("[".concat(POWER_NODE_ATTRIBUTE, "=\"").concat(oldChildren[childLength].props[POWER_NODE_ATTRIBUTE], "\"]"))); | ||
childLength -= 1; | ||
@@ -583,2 +568,77 @@ childLengthDiff -= 1; | ||
/** | ||
* diffing keyed lists | ||
* @param {Array} oldChilds | ||
* @param {Array} newChilds | ||
* @param {DOM Element} parent | ||
*/ | ||
var keyChildrenDiff = function keyChildrenDiff(oldChilds, newChilds, parent) { | ||
// get every old key | ||
var oldKeys = oldChilds.map(function (child) { | ||
return child.props.key; | ||
}); // get every new key | ||
var newKeys = newChilds.map(function (child) { | ||
return child.props.key; | ||
}); | ||
if (oldKeys.length > newKeys.length) { | ||
var differenceKeys = oldKeys.filter(function (key) { | ||
return newKeys.indexOf(key) < 0; | ||
}); | ||
differenceKeys.forEach(function (diff) { | ||
var element = parent.querySelector("[key=\"".concat(diff, "\"]")); | ||
if (parent) { | ||
parent.removeChild(element); | ||
} | ||
}); | ||
} else if (oldKeys.length < newKeys.length) { | ||
var _differenceKeys = newKeys.filter(function (key) { | ||
return oldKeys.indexOf(key) < 0; | ||
}); | ||
_differenceKeys.forEach(function (key) { | ||
newChilds.forEach(function (child) { | ||
if (child.props.key === key) { | ||
parent.appendChild(createElement(child)); | ||
} | ||
}); | ||
}); | ||
} | ||
}; | ||
/** | ||
* working on difference between 2 vnodes | ||
* @private | ||
* @param {Object} oldVNode | ||
* @param {Object} newVNode | ||
* @param {Class} Component | ||
*/ | ||
var diff = function diff(oldVNode, newVNode, Component) { | ||
// get the element id | ||
var powerId = oldVNode.props[POWER_NODE_ATTRIBUTE]; // check if newVNode props is null | ||
if (newVNode.props === null) { | ||
newVNode.props = {}; | ||
} // merge the node id | ||
newVNode.props[POWER_NODE_ATTRIBUTE] = powerId; // get the dom element to the vnode | ||
var element = Component.node.querySelector("[".concat(POWER_NODE_ATTRIBUTE, "=\"").concat(powerId, "\"]")); | ||
var newChildren = newVNode.children; | ||
var oldChildren = oldVNode.children; // compare props | ||
propsDiff(oldVNode.props, newVNode.props, element); // compare children | ||
if (isKeyedList(oldChildren, newChildren)) { | ||
keyChildrenDiff(oldChildren, newChildren, element); | ||
} else { | ||
childrenDiff(oldChildren, newChildren, element, Component); | ||
} | ||
}; | ||
var ARRAY_MODIFIERS = ['push', 'pop', 'shift', 'unshift', 'splice']; | ||
@@ -702,3 +762,3 @@ /** | ||
this.node = document.createElement(this.name); | ||
this.node.setAttribute(DATA_COMPONENT_ATTRIBUTE, true); // get the vnode construct | ||
this.node.setAttribute(POWER_COMPONENT_ATTRIBUTE, true); // get the vnode construct | ||
@@ -705,0 +765,0 @@ this.componentVDom = this.render(); // convert props into proxy object |
@@ -1,1 +0,1 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.power={})}(this,function(t){"use strict";var f="power-id",o=0;function a(t,e,n){return this.tagName=t||"div",this.children=n||[],this.props=e||{},o+=1,this.props[f]=o,this}var s=[];var i=function(t,e,n){return t.substr(!n||n<0?0:+n,e.length)===e},r=function(t,e){return JSON.stringify(t)===JSON.stringify(e)},p=function(t){return(i(t,"on")?t.toLowerCase():"on".concat(t))in window},l=function(t){return t instanceof Element},d=function(t){return t&&t.constructor===a},c={},e={};["Array","Boolean","Date","Error","Function","Null","Number","Object","RegExp","String","Undefined"].forEach(function(t){var n=t.toLowerCase();c["[object ".concat(t,"]")]=n,e["is".concat(t)]=function(t){return(null===(e=t)?String(e):c[{}.toString.call(e)])===n;var e}});var u=e.isArray,h=e.isFunction,m=e.isObject,v=e.isString,y=function(e,t,n){var o=t.startsWith("on")?t.substring(2,t.length).toLowerCase():t;e.addEventListener(o,function(t){return n.call(e,t,e)})},g=function(t,e,n){if(!r(e,n)&&(v(e)&&(t.style.cssText=e),m(e))){if(m(n))for(var o in n)o in e||(t.style[o]="");for(var i in e)t.style[i]=e[i]}},w={htmlFor:"for",className:"class"},b=function(t){var e=document.createElement(t.tagName.name||t.tagName);return m(t.props)&&function(t,e){for(var n in e)"style"!==n?p(n)?y(t,n,e[n]):((o=n)in t||"class"===o||i(o,"data-")||i(o,"power-"))&&t.setAttribute(w[n]||n,e[n]):g(t,e[n]);var o}(e,t.props),t.children&&t.children.length&&function t(e,n){for(var o=0,i=n.length;o<i;o++){var r=n[o];d(r)?(s=r,e.appendChild(b(s))):u(r)?t(e,r):(a=r,e.appendChild(document.createTextNode(a)))}var a,s}(e,t.children),e},n=function t(e,n){if(h(e.tagName))return t(new e.tagName(e.props),n);if(!l(n))throw"You MUST provide a valid DOM element as your root.";if(!d(e)&&!e._power)return t(new e,n);e._power&&e.componentWillMount&&e.componentWillMount(e);var o=e._power?e.create():b(e);l(o)&&n.appendChild(o),e._power&&e.componentDidMount&&e.componentDidMount(e)};function C(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}var N=function(t){for(var e=[].slice.call(arguments,1),n=0,o=e.length;n<o;n++){var i=e[n];for(var r in i)t[r]=i[r]}return t},D=function(t,e,n){var o=t.props[f];null===e.props&&(e.props={}),e.props[f]=t.props[f];var i=n.node.querySelector("[".concat(f,'="').concat(o,'"]'));t.tagName,e.tagName,S(t.props,e.props,i),U(t.children,e.children,i,n)},S=function(t,e,n){if(!r(t,e)){for(var o in r(t.style,e.style)||g(n,e.style,t.style),t)e[o]||n.removeAttribute(w[o]||o);for(var i in e)"style"===i||p(i)||t[i]&&e[i]===t[i]||n.setAttribute(w[i]||i,e[i])}},U=function t(e,n,o,i){for(var r=0,a=n.length;r<a;r++){var s=n[r];if(void 0===e[r]&&d(s)){s.props||(s.props={});var p=b(s,i);o.appendChild(p)}else if(v(s)&&s!==e[r]){var l=document.createTextNode(s);o.replaceChild(l,o.childNodes[r])}else d(s)?D(e[r],s,i):s.pop&&e[r]&&e[r].pop&&t(e[r],s,o,i)}for(var c,u=e.length-n.length,h=e.length-1;0<u;)(c=i.node.querySelector("[".concat(f,'="').concat(e[h].props[f],'"]')))&&c.parentNode&&c.parentNode.removeChild(c),h-=1,u-=1},x=["push","pop","shift","unshift","splice"],E=function(){function n(t){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,n);var e=this.constructor;this.componentWillInitialize&&this.componentWillInitialize(this),this._power=!0,this.name=e.name,this.state=h(this.getInitialState)?this.getInitialState():m(e.initialState)?e.initialState:{},this.props=h(this.getDefaultProps)?this.getDefaultProps():m(e.defaultProps)?e.defaultProps:{},t&&(this.props=N({},this.props,t)),this.componentDidInitialize&&this.componentDidInitialize(this)}var t,e,o;return t=n,(e=[{key:"create",value:function(){var i,t;return this.node=document.createElement(this.name),this.node.setAttribute("power-component",!0),this.componentVDom=this.render(),this.props=(t=(i=this).props,new Proxy(t,{get:function(t,e){var n=t[e],o={get:function(e,n){var t=e[n];return h(t)&&u(e)?function(){var t=Array.prototype[n].apply(e,arguments);return x.includes(n)&&i.shouldComponentUpdate(i.props,i.state)&&i.update(),t}:t}};return u(n)?new Proxy(n,o):n},set:function(t,e,n){var o=t[e];return i.shouldComponentUpdate(t,i.state)&&o!==n&&(t[e]=n,i.update()),!0}})),this.template=b(this.componentVDom,this),this.node.appendChild(this.template),this.node}},{key:"shouldComponentUpdate",value:function(){return!0}},{key:"setState",value:function(t,e){if(!r(t,this.state)){var n=t;if(h(n)&&(n=n.call(this,this.state,this.props)),n=N({},this.state,n),!this.shouldComponentUpdate(this.props,n))return!1;this.state=n,this.update(),h(e)&&e.call(this)}}},{key:"forceUpdate",value:function(t){this.update(),h(t)&&t.call(this)}},{key:"update",value:function(){this.componentWillUpdate&&this.componentWillUpdate(this);var t=this.render();this.patch(this.componentVDom,t),this.componentVDom=t,this.componentDidUpdate&&this.componentDidUpdate(this)}},{key:"patch",value:function(t,e){D(t,e,this)}},{key:"destroy",value:function(){this.componentWillUnmount&&this.componentWillUnmount(this),this.node.parentElement.removeChild(this.node),this.componentDidUnmount&&this.componentDidUnmount(this)}}])&&C(t.prototype,e),o&&C(t,o),n}(),W="1.0.0-beta",k={h:function(t,e){for(var n=[],o=arguments.length;2<o--;)s[s.length]=arguments[o];for(;s.length;){var i=s.pop();if(i.pop)for(var r=i.length;r--;)s[s.length]=i[r];else"boolean"==typeof i&&(i=null),"number"==typeof i&&(i=String(i)),"function"!=typeof i&&null===i&&(i=""),n[n.length]=i}return new a(t,e,n)},render:n,Component:E,version:W};t.default=k,t.render=n,t.Component=E,t.version=W,Object.defineProperty(t,"__esModule",{value:!0})}); | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.power={})}(this,function(t){"use strict";var f="power-id",o=0;function a(t,e,n){return this.tagName=t||"div",this.children=n||[],this.props=e||{},o+=1,this.props[f]=o,this}var s=[];var i=function(t,e,n){return t.substr(!n||n<0?0:+n,e.length)===e},d=function(t,e){return JSON.stringify(t)===JSON.stringify(e)},m=function(t){return t&&t.constructor===a},r={},e={};["Array","Boolean","Date","Error","Function","Null","Number","Object","RegExp","String","Undefined"].forEach(function(t){var n=t.toLowerCase();r["[object ".concat(t,"]")]=n,e["is".concat(t)]=function(t){return(null===(e=t)?String(e):r[{}.toString.call(e)])===n;var e}});var p=e.isArray,c=e.isFunction,l=e.isObject,v=e.isString,n=function t(e,n){var o=n||document.body;if(c(e.tagName))return t(new e.tagName(e.props),o);if(!m(e)&&!e._power)return t(new e,o);e._power&&e.componentWillMount&&e.componentWillMount(e);var r=e.create();return r instanceof Element&&o.appendChild(r),e._power&&e.componentDidMount&&e.componentDidMount(e),e};function u(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}var h=function(t){for(var e=[].slice.call(arguments,1),n=0,o=e.length;n<o;n++){var r=e[n];for(var i in r)t[i]=r[i]}return t},y=function(e,t,n){var o=t.startsWith("on")?t.substring(2,t.length).toLowerCase():t;e.addEventListener(o,function(t){return n.call(e,t,e)})},g=function(t,e,n){if(!d(e,n)&&(v(e)&&(t.style.cssText=e),l(e))){if(l(n))for(var o in n)o in e||(t.style[o]="");for(var r in e)t.style[r]=e[r]}},b={htmlFor:"for",className:"class"},w=function(t,e){for(var n in e)"style"!==n?(i(r=n,"on")?r.toLowerCase():"on".concat(r))in window?y(t,n,e[n]):((o=n)in t||"class"===o||i(o,"data-")||i(o,"power-")||"key"===n)&&(t[n]&&e[n]===t[n]||t.setAttribute(b[n]||n,e[n])):d(t.style,e.style)||g(t,e[n]);var o,r},C=function(t){var e=document.createElement(t.tagName.name||t.tagName),n=document.createDocumentFragment();return t.children&&t.children.length&&function t(e,n){for(var o=0,r=n.length;o<r;o++){var i=n[o];m(i)?(s=i,e.appendChild(C(s))):p(i)?t(e,i):(a=i,e.appendChild(document.createTextNode(a)))}var a,s}(n,t.children),e.appendChild(n),t.props&&Object.keys(t.props).length&&w(e,t.props),e},k=function(t,e,n){var o=t.props[f];null===e.props&&(e.props={}),e.props[f]=o;var r,i,a,s,p,c,l=n.node.querySelector("[".concat(f,'="').concat(o,'"]')),u=e.children,h=t.children;!function(t,e,n){if(!d(t,e)){for(var o in t)e[o]||n.removeAttribute(b[o]||o);w(n,e)}}(t.props,e.props,l),p=h,(c=u).length&&c[0].props&&c[0].props.key||p.length&&p[0].props&&p[0].props.key?(r=u,i=l,a=h.map(function(t){return t.props.key}),s=r.map(function(t){return t.props.key}),a.length>s.length?a.filter(function(t){return s.indexOf(t)<0}).forEach(function(t){var e=i.querySelector('[key="'.concat(t,'"]'));i&&i.removeChild(e)}):a.length<s.length&&s.filter(function(t){return a.indexOf(t)<0}).forEach(function(e){r.forEach(function(t){t.props.key===e&&i.appendChild(C(t))})})):function t(e,n,o,r){for(var i=0,a=n.length;i<a;i++){var s=n[i];if(void 0===e[i]&&m(s)){s.props||(s.props={});var p=C(s,r);o.appendChild(p)}else if(v(s)&&s!==e[i]){var c=document.createTextNode(s);o.replaceChild(c,o.childNodes[i])}else m(s)?k(e[i],s,r):s.pop&&e[i]&&e[i].pop&&t(e[i],s,o,r)}for(var l,u=e.length-n.length,h=e.length-1;0<u;)(l=r.node.querySelector("[".concat(f,'="').concat(e[h].props[f],'"]')))&&l.parentNode&&l.parentNode.removeChild(l),h-=1,u-=1}(h,u,l,n)},D=["push","pop","shift","unshift","splice"],N=function(){function n(t){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,n);var e=this.constructor;this.componentWillInitialize&&this.componentWillInitialize(this),this._power=!0,this.name=e.name,this.state=c(this.getInitialState)?this.getInitialState():l(e.initialState)?e.initialState:{},this.props=c(this.getDefaultProps)?this.getDefaultProps():l(e.defaultProps)?e.defaultProps:{},t&&(this.props=h({},this.props,t)),this.componentDidInitialize&&this.componentDidInitialize(this)}var t,e,o;return t=n,(e=[{key:"create",value:function(){var r,t;return this.node=document.createElement(this.name),this.node.setAttribute("power-component",!0),this.componentVDom=this.render(),this.props=(t=(r=this).props,new Proxy(t,{get:function(t,e){var n=t[e],o={get:function(e,n){var t=e[n];return c(t)&&p(e)?function(){var t=Array.prototype[n].apply(e,arguments);return D.includes(n)&&r.shouldComponentUpdate(r.props,r.state)&&r.update(),t}:t}};return p(n)?new Proxy(n,o):n},set:function(t,e,n){var o=t[e];return r.shouldComponentUpdate(t,r.state)&&o!==n&&(t[e]=n,r.update()),!0}})),this.template=C(this.componentVDom,this),this.node.appendChild(this.template),this.node}},{key:"shouldComponentUpdate",value:function(){return!0}},{key:"setState",value:function(t,e){if(!d(t,this.state)){var n=t;if(c(n)&&(n=n.call(this,this.state,this.props)),n=h({},this.state,n),!this.shouldComponentUpdate(this.props,n))return!1;this.state=n,this.update(),c(e)&&e.call(this)}}},{key:"forceUpdate",value:function(t){this.update(),c(t)&&t.call(this)}},{key:"update",value:function(){this.componentWillUpdate&&this.componentWillUpdate(this);var t=this.render();this.patch(this.componentVDom,t),this.componentVDom=t,this.componentDidUpdate&&this.componentDidUpdate(this)}},{key:"patch",value:function(t,e){k(t,e,this)}},{key:"destroy",value:function(){this.componentWillUnmount&&this.componentWillUnmount(this),this.node.parentElement.removeChild(this.node),this.componentDidUnmount&&this.componentDidUnmount(this)}}])&&u(t.prototype,e),o&&u(t,o),n}(),S="1.0.0-beta",U={h:function(t,e){for(var n=[],o=arguments.length;2<o--;)s[s.length]=arguments[o];for(;s.length;){var r=s.pop();if(r.pop)for(var i=r.length;i--;)s[s.length]=r[i];else"boolean"==typeof r&&(r=null),"number"==typeof r&&(r=String(r)),"function"!=typeof r&&null===r&&(r=""),n[n.length]=r}return new a(t,e,n)},render:n,Component:N,version:S};t.default=U,t.render=n,t.Component=N,t.version=S,Object.defineProperty(t,"__esModule",{value:!0})}); |
{ | ||
"name": "@power-js/core", | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"description": "A powerful JavaScript library for building web components.", | ||
@@ -13,6 +13,6 @@ "main": "dist/power.js", | ||
"lint": "eslint -c ./.eslintrc --fix ./src --ext .js", | ||
"test": "jest --coverage", | ||
"test": "jest --coverage && codecov --token=863c5601-7d90-406b-b0d0-804f19fd322a", | ||
"test:unit": "jest --coverage", | ||
"test:coverage": "jest --coverage && open ./coverage/lcov-report/index.html", | ||
"test:watch": "jest --watch", | ||
"prepare": "npm run bundle && npm run test", | ||
"docs": "set NODE_ENV=docs && documentation build src/** -f md -o docs/readme.md" | ||
@@ -22,3 +22,3 @@ }, | ||
"type": "git", | ||
"url": "git+https://github.com/janmarkuslanger/powerjs.git" | ||
"url": "git+https://github.com/power-js/power-js.git" | ||
}, | ||
@@ -28,5 +28,5 @@ "author": "Jan-Markus Langer", | ||
"bugs": { | ||
"url": "https://github.com/janmarkuslanger/powerjs/issues" | ||
"url": "https://github.com/power-js/power-js/issues" | ||
}, | ||
"homepage": "https://github.com/janmarkuslanger/powerjs#readme", | ||
"homepage": "https://github.com/power-js/power-js#readme", | ||
"devDependencies": { | ||
@@ -40,2 +40,3 @@ "@babel/core": "^7.0.1", | ||
"babel-preset-env": "^1.7.0", | ||
"codecov": "^3.1.0", | ||
"eslint": "^5.6.0", | ||
@@ -42,0 +43,0 @@ "eslint-config-prettier": "^2.6.0", |
@@ -1,9 +0,21 @@ | ||
<p align="center" ><a href="https://github.com/janmarkuslanger/powerjs"><img alt="PowerJS is pra Logo" src="https://camo.githubusercontent.com/9bb8be7b58dc3dc9722117a2ef2e59a46272770e/68747470733a2f2f63646e2e7261776769742e636f6d2f6a616e6d61726b75736c616e6765722f706f7765726a732f66643538323436352f6173736574732f6c6f676f2e737667" width="300" height="auto"/></a></p> | ||
<p align="center" ><a href="https://github.com/power-js/power-js"><img alt="PowerJS" src="https://user-images.githubusercontent.com/1918732/47975313-295a4a00-e062-11e8-8ae7-2e6124405f9c.png" width="500" height="auto"/></a></p> | ||
<p align="center"><strong>A powerful JavaScript library for building web components</strong></p> | ||
<p align="center"> | ||
<img src="https://img.shields.io/github/license/power-js/power-js.svg"> | ||
<img src="https://travis-ci.com/power-js/power-js.svg?branch=master"> | ||
<img src="https://codecov.io/gh/power-js/power-js/branch/master/graph/badge.svg"> | ||
<img src="https://img.shields.io/github/size/power-js/power-js/dist/power.js.svg"> | ||
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg"> | ||
</p> | ||
--- | ||
## Why PowerJS? | ||
This library was designed as a lightweight React-like solution with a smaller footprint and faster performance. This is **not** a React replacement nor was it ever intended to be. Our goal was to provide a similar interface to prevent having to learn yet another front-end library. | ||
We aren't sure what this library will evolve into but we wanted to start with "good bones". Please help us define what this can be by opening some PRs or submitting ideas! | ||
**Fast Rendering** | ||
@@ -15,4 +27,4 @@ <br>Virtual DOM for detecting deltas and isolating renders. | ||
**Size** | ||
<br>Library is extremely small, just 2.3k (gzipped) | ||
**Bundle Size** | ||
<br>Library is extremely small, just 2.2k (gzipped) | ||
@@ -31,6 +43,6 @@ **No Dependencies** | ||
- <a href="https://github.com/power-js/power-js/blob/master/CHANGELOG.md">Change log</a> | ||
- <a href="https://github.com/power-js/todo-app">Example TODO app</a> | ||
- <a href="https://github.com/power-js/todo-app">A simple Todo app</a> | ||
## Before you start | ||
You can use PowerJS with just place the script into your document, but if you prefer html Syntax you should take a look at JSX. There is a <a href="https://babeljs.io/docs/en/babel-plugin-transform-react-jsx/">Babel plugin</a> which transforms your JSX Syntax into a VDom. | ||
If you would like to use JSX with PowerJS you'll need to install this [Babel Plugin](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx) which will convert the JSX into a Virtual DOM. There's an example of this plugin config in the Todo app [here](https://github.com/power-js/todo-app/blob/master/.babelrc). | ||
@@ -41,3 +53,3 @@ ## Installation | ||
### Script | ||
Included via `script` | ||
@@ -53,4 +65,10 @@ ```js | ||
Included via `import` | ||
### NPM | ||
You'll need to install PowerJS via NPM | ||
```js | ||
npm install @power-js/core | ||
``` | ||
Include via `import` | ||
```js | ||
import Power from '@power-js/core'; | ||
@@ -60,3 +78,3 @@ | ||
Included via `require` | ||
Include via `require` | ||
```js | ||
@@ -67,11 +85,8 @@ const Power = require('@power-js/core'); | ||
## Get started | ||
## Example | ||
Here's an example of a simple counter component. | ||
There is a <a href="https://github.com/janmarkuslanger/powerjs-starter">repository</a> on github which helps you to get started. | ||
Here is a typical example for a counter component. | ||
JSX: | ||
```js | ||
```jsx | ||
import Power, { render, Component } from '@power-js/core'; | ||
@@ -118,7 +133,21 @@ | ||
## Contributors | ||
Feel free to fork this project and PR! | ||
## Contributing | ||
| [<img src="https://avatars.githubusercontent.com/u/1918732?v=3" width="100px;"/><br /><sub><b>Dysfunc</b></sub>](https://github.com/dysfunc)<br />๐ป ๐ | [<img src="https://avatars.githubusercontent.com/u/26633123?v=3" width="100px;"/><br /><sub><b>Jan-Markus Langer</b></sub>](https://github.com/janmarkuslanger)<br />๐ป ๐ | | ||
| :---: | :---: | | ||
The purpose of this repo is to continue to evolve PowerJS as an open source project driven by its contributors and communal support. We appreciate all the support we've received, and look forward to working with many new faces. To get started contributing just review our contribution guide, code of conduct and open a PR. | ||
### [Code of Conduct](./CODE_OF_CONDUCT.md) | ||
We have a Code of Conduct that we expect collaborators to adhere to. Please read it [in its entirety](./CODE_OF_CONDUCT.md) so you understand our expectations of you and what is acceptable behavior. | ||
### [Contributing Guide](CONTRIBUTING.md) | ||
Read our [contributing guide](CONTRIBUTING.md) to learn about our development process. | ||
### Core Contributors | ||
[Jan-Markus Langer](https://github.com/janmarkuslanger) ยท [Dysfunc](https://github.com/dysfunc) | ||
### License | ||
[MIT](./LICENSE) |
import { extend } from '../utils/objects'; | ||
import { isEqual, isFunction, isObject } from '../utils/is'; | ||
import { DATA_COMPONENT_ATTRIBUTE } from '../constants'; | ||
import { POWER_COMPONENT_ATTRIBUTE } from '../constants'; | ||
import { createElement } from '../dom/createElement'; | ||
import { diff } from '../vdom/diff'; | ||
import { diff } from '../diff/diff'; | ||
import { proxy } from './proxy'; | ||
@@ -59,3 +59,3 @@ | ||
this.node.setAttribute(DATA_COMPONENT_ATTRIBUTE, true); | ||
this.node.setAttribute(POWER_COMPONENT_ATTRIBUTE, true); | ||
@@ -62,0 +62,0 @@ // get the vnode construct |
@@ -6,3 +6,3 @@ /** | ||
*/ | ||
export const DATA_COMPONENT_ATTRIBUTE = 'power-component'; | ||
export const POWER_COMPONENT_ATTRIBUTE = 'power-component'; | ||
@@ -14,3 +14,3 @@ /** | ||
*/ | ||
export const DATA_NODE_ATTRIBUTE = 'power-id'; | ||
export const POWER_NODE_ATTRIBUTE = 'power-id'; | ||
@@ -17,0 +17,0 @@ /** |
@@ -1,2 +0,1 @@ | ||
import { isObject } from '../utils/is'; | ||
import { decorateElement } from './decorateElement'; | ||
@@ -15,9 +14,12 @@ import { appendChildren } from './appendChildren'; | ||
const element = document.createElement(vnode.tagName.name || vnode.tagName); | ||
const fragment = document.createDocumentFragment(); | ||
if (isObject(vnode.props)) { | ||
decorateElement(element, vnode.props); | ||
if (vnode.children && vnode.children.length) { | ||
appendChildren(fragment, vnode.children); | ||
} | ||
if (vnode.children && vnode.children.length) { | ||
appendChildren(element, vnode.children); | ||
element.appendChild(fragment); | ||
if (vnode.props && Object.keys(vnode.props).length) { | ||
decorateElement(element, vnode.props); | ||
} | ||
@@ -24,0 +26,0 @@ |
import { addEventListener } from './addEventListener'; | ||
import { updateElementStyles } from './updateElementStyles'; | ||
import { isElementAttribute, isEvent } from '../utils/is'; | ||
import { isElementAttribute, isEqual, isEvent } from '../utils/is'; | ||
import { jsxProps } from '../vdom/jsxProps'; | ||
@@ -16,3 +16,6 @@ | ||
if (prop === 'style') { | ||
updateElementStyles(element, props[prop]); | ||
if (!isEqual(element.style, props.style)) { | ||
updateElementStyles(element, props[prop]); | ||
} | ||
continue; | ||
@@ -26,7 +29,8 @@ } | ||
if (isElementAttribute(element, prop)) { | ||
element.setAttribute(jsxProps[prop] || prop, props[prop]); | ||
continue; | ||
if (isElementAttribute(element, prop) || prop === 'key') { | ||
if (!element[prop] || props[prop] !== element[prop]) { | ||
element.setAttribute(jsxProps[prop] || prop, props[prop]); | ||
} | ||
} | ||
} | ||
}; |
import { isHtml, isVNode, isFunction } from '../utils/is'; | ||
import { createElement } from '../dom/createElement'; | ||
@@ -11,16 +10,13 @@ /** | ||
export const render = (model, root) => { | ||
// assign document.body if no root is given | ||
const _root = root || document.body; | ||
// JSX will transform Component into functions | ||
if (isFunction(model.tagName)) { | ||
// TODO: better checking | ||
return render(new model.tagName(model.props), root); | ||
return render(new model.tagName(model.props), _root); | ||
} | ||
// check if a root is given | ||
if (!isHtml(root)) { | ||
throw 'You MUST provide a valid DOM element as your root.'; | ||
} | ||
// check if model is neither a vdom or component | ||
// handle a class being passed in | ||
if (!isVNode(model) && !model._power) { | ||
return render(new model(), root); | ||
return render(new model(), _root); | ||
} | ||
@@ -34,6 +30,6 @@ | ||
// convert the vnodes / component into real dom elements | ||
const domTree = model._power ? model.create() : createElement(model); | ||
const domTree = model.create(); | ||
if (isHtml(domTree)) { | ||
root.appendChild(domTree); | ||
_root.appendChild(domTree); | ||
} | ||
@@ -44,2 +40,4 @@ | ||
} | ||
return model; | ||
}; |
@@ -5,6 +5,7 @@ import { isElementAttribute } from './isElementAttribute'; | ||
import { isHtml } from './isHtml'; | ||
import { isVNode } from './isVnode'; | ||
import { isKeyedList } from './isKeyedList'; | ||
import { isVNode } from './isVNode'; | ||
import { isArray, isBoolean, isError, isFunction, isNull, isNumber, isObject, isRegExp, isString, isUndefined } from './common'; | ||
export { isArray, isBoolean, isError, isEqual, isFunction, isNull, isNumber, isObject, isRegExp, isString, isUndefined, isElementAttribute, isEvent, isHtml, isVNode }; | ||
export { isArray, isBoolean, isError, isEqual, isFunction, isNull, isNumber, isObject, isRegExp, isString, isUndefined, isElementAttribute, isEvent, isHtml, isKeyedList, isVNode }; |
@@ -1,2 +0,2 @@ | ||
import { DATA_NODE_ATTRIBUTE } from '../constants'; | ||
import { POWER_NODE_ATTRIBUTE } from '../constants'; | ||
@@ -7,3 +7,3 @@ /** | ||
*/ | ||
let _counter = 0; | ||
let counter = 0; | ||
@@ -23,7 +23,9 @@ /** | ||
_counter += 1; | ||
// increment counter | ||
counter += 1; | ||
this.props[DATA_NODE_ATTRIBUTE] = _counter; | ||
// assign counter to props | ||
this.props[POWER_NODE_ATTRIBUTE] = counter; | ||
return this; | ||
} |
@@ -17,4 +17,9 @@ import { createElement } from '../../src/dom/createElement'; | ||
expect(element.tagName).toEqual('DIV'); | ||
const element2 = createElement({ tagName: 'div', props: null, children: [] }); | ||
expect(element2.children.length).toEqual(0); | ||
expect(element2.nodeType).toEqual(1); | ||
expect(element2.tagName).toEqual('DIV'); | ||
}); | ||
}); | ||
}); |
@@ -7,3 +7,5 @@ import { updateElementStyles } from '../../src/dom/updateElementStyles'; | ||
const element = document.createElement('div'); | ||
const oldStyle = {}; | ||
const oldStyle = { | ||
fontSize: '21px' | ||
}; | ||
const newStyle = { | ||
@@ -10,0 +12,0 @@ fontSize: '14px', |
@@ -16,2 +16,3 @@ import { render } from '../../src/render/render'; | ||
render(<MyComponent />, document.body); | ||
const element = document.querySelector('#hello'); | ||
@@ -23,6 +24,32 @@ | ||
it('should throw if the root element is invalid', () => { | ||
expect(() => render(<MyComponent />, null)).toThrowError('You MUST provide a valid DOM element as your root.'); | ||
it('should trigger componentWillMount and componentDidMount', () => { | ||
const componentDidMount = jest.fn(); | ||
const componentWillMount = jest.fn(); | ||
MyComponent.prototype.componentDidMount = componentDidMount; | ||
MyComponent.prototype.componentWillMount = componentWillMount; | ||
const spyDid = jest.spyOn(MyComponent.prototype, 'componentDidMount'); | ||
const spyWill = jest.spyOn(MyComponent.prototype, 'componentWillMount'); | ||
render(<MyComponent />); | ||
expect(componentDidMount).toHaveBeenCalledTimes(1); | ||
expect(spyDid).toHaveBeenCalled(); | ||
expect(componentWillMount).toHaveBeenCalledTimes(1); | ||
expect(spyWill).toHaveBeenCalled(); | ||
spyDid.mockRestore(); | ||
spyWill.mockRestore(); | ||
}); | ||
it('should handle component classes (not vnodes)', () => { | ||
expect(MyComponent._power).toBeUndefined(); | ||
const result = render(MyComponent, document.body); | ||
expect(result._power).toBe(true); | ||
expect(result.name).toBe('MyComponent'); | ||
}); | ||
}); | ||
}); |
@@ -12,3 +12,3 @@ import { h } from '../../src/vdom/h'; | ||
describe('#h', () => { | ||
it('should return a VNode with the expected attributes', () => { | ||
it('should return a new VNode', () => { | ||
const component = <MyComponent />; | ||
@@ -19,5 +19,6 @@ | ||
expect(component).toHaveProperty('props'); | ||
expect(component.constructor.name).toEqual('VNode'); | ||
}); | ||
it('should contain children of mixed types', () => { | ||
it('should handle processing children of mixed types', () => { | ||
const component = ( | ||
@@ -30,2 +31,3 @@ <MyComponent id="test"> | ||
{true} | ||
{() => 'hello'} | ||
</MyComponent> | ||
@@ -41,3 +43,3 @@ ); | ||
it('should handle rendering of invalid children (boolean)', () => { | ||
it('should drop children that are of type boolean', () => { | ||
const component = ( | ||
@@ -55,3 +57,3 @@ <MyComponent> | ||
it('should handle rendering of invalid children (number)', () => { | ||
it('should cast children that are numbers to strings', () => { | ||
const component = ( | ||
@@ -58,0 +60,0 @@ <MyComponent> |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
95813
73
2182
147
28