+1
-2
@@ -1,2 +0,1 @@ | ||
| ((a,b,c,d,e,f,g,h)=>{String.prototype[d]=1,f=(i,j={},...k)=>({[d]:1,E:i,P:j[d]?{C:[].concat(j,...k)}:(j.C=[].concat(...k))&&j}),a.R=g=(i,j,k='',l=j.childNodes,m=0)=>{for((i.map?i:[i]).map((n,o,p,q=k+'.'+o,r=e[q]||[{},n.E],s=e[q]=r[1]==n.E?r:[{},n.E],t=l[m++],u)=>{n.E&&n.E.call&&(n=n.E(n.P,s[0],v=>c.assign(s[0],v)&&g(i,j,k))),u=n.trim?b.createTextNode(n):b.createElement(n.E),(u=t?t.E!=n.E&&t.data!=n?j.replaceChild(u,t)&&u:t:j.appendChild(u)).E=n.E,n.trim?u.data=n:c.keys(n.P).map((v)=>'style'==v?c.assign(u[v],n.P[v]):u[v]!==n.P[v]&&(u[v]=n.P[v]))&&g(n.P.C,u,q)});l[m];)j.removeChild(l[m])},h=i=>new Proxy(i,{get:(j,k,l)=>h((...m)=>((l=j(...m)).P.className=[l.P.className]+' '+k,l))}),a.H=new Proxy(f,{get:(i,j)=>i[j]||h(f.bind(a,j))})})(window,document,Object,Symbol(),{}); | ||
| (()=>{let e=(e,a={},...c)=>({$:e,a:a.$||a.concat||a.removeChild?{c:[].concat(a,...c)}:(a.c=[].concat(...c),a)}),a=(e=[],a,c)=>e.map(e=>e(a,c)),c=e=>new Proxy(e,{get:(e,a,d)=>c((...c)=>((d=e(...c)).a.className=(d.a.className||"")+" "+a,d))}),d=window.R=((e,c,t=c.childNodes,o=0)=>{for([].concat(e).map((n,l,m,i=t[o++],s=i&&i.a==n.$&&i.s||{},r={s,a:n.$,m:[],u:[],d:[]})=>{for(;(n.$||e).bind;)n=n.$(n.a,s,a=>Object.assign(s,a)&&d(e,c),r);l=n.removeChild?n:n.replace?document.createTextNode(n):document.createElement(n.$),l=i?(n.removeChild?i!=n:i.$!=n.$&&i.data!=n)?(c.replaceChild(l,i),l):i:c.appendChild(l),m=i?i.a==r.a?r.d:(a(i.u),d([],i),r.m):r.m,Object.assign(l,n,r),n.removeChild||n.replace?l.data=n:Object.keys(n.a).map(e=>"style"==e?Object.assign(l[e],n.a[e]):l[e]!==n.a[e]&&(l[e]=n.a[e]))&&d(n.a.c,l),a(m,l,i)});t[o];)a(t[o].u),d([],c.removeChild(t[o]))});window.H=new Proxy(e,{get:(a,d)=>a[d]||c(e.bind(a,d))})})() |
+1
-1
@@ -189,3 +189,3 @@ Apache License | ||
| Copyright {yyyy} {name of copyright owner} | ||
| Copyright 2018 Ioannis Charalampidis | ||
@@ -192,0 +192,0 @@ Licensed under the Apache License, Version 2.0 (the "License"); |
+21
-7
| { | ||
| "name": "dot-dom", | ||
| "version": "0.2.2", | ||
| "version": "0.3.0", | ||
| "description": "A tiny (less than 512 byte) template engine that uses virtual DOM and some of react principles", | ||
| "main": "src/dotdom.js", | ||
| "files": [ | ||
| "src/dotdom.js", | ||
| "dotdom.min.js" | ||
| ], | ||
| "scripts": { | ||
| "test": "./node_modules/.bin/jest", | ||
| "build": "cat src/dotdom.js | perl -0pe 's/BEGIN NPM-GLUE.*END NPM-GLUE//s' | ./node_modules/.bin/babili | tee dotdom.min.js | gzip -9 > dotdom.min.js.gz" | ||
| "build": "./node_modules/.bin/gulp build", | ||
| "watch": "./node_modules/.bin/gulp watch" | ||
| }, | ||
@@ -26,10 +31,19 @@ "repository": { | ||
| "devDependencies": { | ||
| "babel-core": "^6.22.1", | ||
| "babel-preset-es2015": "^6.22.0", | ||
| "babel-register": "^6.22.0", | ||
| "babili": "0.0.10", | ||
| "jest": "^18.1.0", | ||
| "babel-preset-babili": "0.0.11", | ||
| "gulp": "3.9.1", | ||
| "gulp-babel": "^6.1.2", | ||
| "gulp-cli": "1.2.2", | ||
| "gulp-clone": "^1.0.0", | ||
| "gulp-gzip": "1.4.0", | ||
| "gulp-merge": "^0.1.1", | ||
| "gulp-rename": "^1.2.2", | ||
| "gulp-strip-code": "^0.1.4", | ||
| "gulp-uglify-es": "^1.0.4", | ||
| "gulp-util": "^3.0.8", | ||
| "jest": "^23.6.0", | ||
| "mock-browser": "^0.92.12", | ||
| "through2": "^2.0.3", | ||
| "uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony", | ||
| "watch": "^1.0.1" | ||
| } | ||
| } |
+119
-23
@@ -1,4 +0,4 @@ | ||
| # .dom [](https://travis-ci.org/wavesoft/dot-dom) [](https://codepen.io/anon/pen/YNdNwv?editors=0010) | ||
| # .dom [](https://travis-ci.org/wavesoft/dot-dom) [](https://codepen.io/anon/pen/OrzaXB?editors=0010) [](https://gitter.im/wavesoft/dot-dom?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) | ||
| > A tiny (511 byte) virtual DOM template engine for embedded projects | ||
| > A tiny (512 byte) virtual DOM template engine for embedded projects | ||
@@ -17,3 +17,3 @@ | <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/edge.png" alt="IE / Edge" width="16px" height="16px" /> IE / Edge | <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/firefox.png" alt="Firefox" width="16px" height="16px" /> Firefox | <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/chrome.png" alt="Chrome" width="16px" height="16px" /> Chrome | <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/safari.png" alt="Safari" width="16px" height="16px" /> Safari | <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/opera.png" alt="Opera" width="16px" height="16px" /> Opera | <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/safari-ios.png" alt="iOS Safari" width="16px" height="16px" /> iOS Safari | <img src="https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/chrome-android.png" alt="Chrome for Android" width="16px" height="16px" /> Chrome for Android | | ||
| * _Built for the future_ : The library is heavily exploiting the ES6 specifications, meaning that it's **not** supported by older borwsers. Currently it's supported by the 70% of the browsers in the market, but expect this to be 90% within the next year. | ||
| * _Built for the future_ : The library is heavily exploiting the ES6 specifications, meaning that it's **not** supported by older browsers. Currently it's supported by the 70% of the browsers in the market, but expect this to be 90% within the next year. | ||
@@ -29,3 +29,3 @@ * _Declarative_ : Describe your HTML DOM in a structured, natural manner, helping you create powerful yet readable user interfaces. | ||
| For minimum footprint, include `dotdom.min.js.gz` (511b) to your project. | ||
| For minimum footprint, include `dotdom.min.js.gz` (512b) to your project. | ||
@@ -36,8 +36,4 @@ ```html | ||
| Alternatively you can just include the minified version of the library directly before your script. Just copy-paste the following (779b): | ||
| Alternatively you can just include the minified version of the library directly before your script. Just copy-paste the [minified code](https://raw.githubusercontent.com/wavesoft/dot-dom/master/dotdom.min.js). | ||
| ```js | ||
| ((a,b,c,d,e,f,g,h)=>{String.prototype[d]=1,f=(i,j={},...k)=>({[d]:1,E:i,P:j[d]?{C:[].concat(j,...k)}:(j.C=[].concat(...k))&&j}),a.R=g=(i,j,k='',l=j.childNodes,m=0)=>{for((i.map?i:[i]).map((n,o,p,q=k+'.'+o,r=e[q]||[{},n.E],s=e[q]=r[1]==n.E?r:[{},n.E],t=l[m++],u)=>{n.E&&n.E.call&&(n=n.E(n.P,s[0],v=>c.assign(s[0],v)&&g(i,j,k))),u=n.trim?b.createTextNode(n):b.createElement(n.E),(u=t?t.E!=n.E&&t.data!=n?j.replaceChild(u,t)&&u:t:j.appendChild(u)).E=n.E,n.trim?u.data=n:c.keys(n.P).map((v)=>'style'==v?c.assign(u[v],n.P[v]):u[v]!==n.P[v]&&(u[v]=n.P[v]))&&g(n.P.C,u,q)});l[m];)j.removeChild(l[m])},h=i=>new Proxy(i,{get:(j,k,l)=>h((...m)=>((l=j(...m)).P.className=[l.P.className]+' '+k,l))}),a.H=new Proxy(f,{get:(i,j)=>i[j]||h(f.bind(a,j))})})(window,document,Object,Symbol(),{}); | ||
| ``` | ||
| ## Examples | ||
@@ -77,3 +73,3 @@ | ||
| #### 2. Simple component | ||
| #### 2. Stateless Component | ||
@@ -90,10 +86,8 @@ Creating a component on which you can pass properties. | ||
| <pre lang="javascript"> | ||
| class Hello extends React.Component { | ||
| render() { | ||
| function Hello(props) { | ||
| return React.createElement( | ||
| 'div', null, `Hello ${this.props.toWhat}` | ||
| 'div', null, `Hello ${props.toWhat}` | ||
| ); | ||
| } | ||
| } | ||
| <br /> | ||
| ReactDOM.render( | ||
@@ -112,3 +106,3 @@ React.createElement( | ||
| } | ||
| <br /> | ||
| R( | ||
@@ -123,3 +117,3 @@ H(Hello, {toWhat: 'World'}), | ||
| #### 3. Stateful component | ||
| #### 3. Stateful Component | ||
@@ -143,6 +137,6 @@ Creating components that can maintain their own state. | ||
| } | ||
| <br /> | ||
| render() { | ||
| const {clicks} = this.state; | ||
| <br /> | ||
| return React.createElement( | ||
@@ -157,3 +151,3 @@ 'button', { | ||
| } | ||
| <br /> | ||
| ReactDOM.render( | ||
@@ -172,3 +166,3 @@ React.createElement('div', null, | ||
| const {clicks=0} = state; | ||
| <br /> | ||
| return H('button', | ||
@@ -183,3 +177,3 @@ { | ||
| } | ||
| <br /> | ||
| R( | ||
@@ -197,2 +191,65 @@ H('div', | ||
| #### 4. Life-Cycle Component Events | ||
| The component can also subscribe to life-cycle events: | ||
| <table width="100%"> | ||
| <tr> | ||
| <th>React</th> | ||
| <th>.dom</th> | ||
| </tr> | ||
| <tr> | ||
| <td valign="top"> | ||
| <pre lang="javascript"> | ||
| class WithLifeCycle extends React.Component { | ||
| constructor() { | ||
| super(...arguments); | ||
| this.state = { | ||
| mounted: "no" | ||
| }; | ||
| } | ||
| <br /> | ||
| componentDidMount() { | ||
| this.setState({ mounted: "yes" }) | ||
| } | ||
| <br /> | ||
| render() { | ||
| const {mounted} = this.state; | ||
| <br /> | ||
| return React.createElement( | ||
| 'div', null, `mounted = ${mounted}` | ||
| ); | ||
| } | ||
| } | ||
| <br /> | ||
| ReactDOM.render( | ||
| React.createElement('div', null, | ||
| React.createElement(WithLifeCycle, null, null), | ||
| ), | ||
| document.body | ||
| ); | ||
| </pre> | ||
| </td> | ||
| <td valign="top"> | ||
| <pre lang="javascript"> | ||
| function WithLifeCycle(props, state, setState, hooks) { | ||
| const {mounted = "no"} = state; | ||
| hooks.m.push(() => { | ||
| setState({ mounted: "yes" }) | ||
| }); | ||
| <br /> | ||
| return H('div', | ||
| `mounted = ${mounted}` | ||
| ); | ||
| } | ||
| <br /> | ||
| R( | ||
| H('div', H(WithLifeCycle)), | ||
| document.body | ||
| ) | ||
| </pre> | ||
| </td> | ||
| </tr> | ||
| </table> | ||
| ## API Reference | ||
@@ -231,3 +288,3 @@ | ||
| ```js | ||
| const Component = (props, state, setState) { | ||
| const Component = (props, state, setState, hooks) { | ||
@@ -249,2 +306,29 @@ // Return your Virtual DOM | ||
| The `hooks` object can be used when you want to register handlers to the component life-cycle methods. | ||
| #### Component Life-Cycle | ||
| Similar to React, the **.dom** components have a life-cycle: | ||
| * They are **mounted** when their root DOM element is placed on the document. | ||
| * They are **unmounted** when their root DOM element is removed from the document. | ||
| * The yare **updated** when the state, the properties, or the rendered DOM has changed. | ||
| To access the life-cycle methods you need to use the fourth argument on your component function. More specifically you have to push your handling function in either of the following fields: | ||
| ```js | ||
| const Component = (props, state, setState, hooks) { | ||
| hooks.m.push((domElement) => { | ||
| // '.m' is called when the component is mounted | ||
| }); | ||
| hooks.u.push(() => { | ||
| // `.u` is called when the component is unmounted | ||
| }); | ||
| hooks.d.push((domElement, previousDomElement) => { | ||
| // `.d` is called when the component is updated | ||
| }); | ||
| ... | ||
| } | ||
| ``` | ||
| ### Tag Shorthand `tag( [properties], [children ...] )` | ||
@@ -287,2 +371,4 @@ | ||
| Since the project's focus is the small size, it is lacking sanity checks. This makes it susceptible to errors. Be **very careful** with the following caveats: | ||
| * You cannot trigger an update with a property removal. You **must** set the new property to an empty value instead. For example: | ||
@@ -300,2 +386,9 @@ | ||
| * You **must** never use a property named `$` in your components. Doing so, will make the property object to be considered as a Virtual DOM Node and will lead to unexpected results. | ||
| ```js | ||
| // *NEVER* do this! | ||
| R(H(MyComponent, {$: 'Foo'}), document.body) | ||
| ``` | ||
| ## Contribution | ||
@@ -336,1 +429,4 @@ | ||
| ``` | ||
| # License | ||
| Licensed under the [Apache License, Version 2.0](https://raw.githubusercontent.com/wavesoft/dot-dom/master/LICENSE) |
+204
-127
@@ -25,3 +25,3 @@ /** | ||
| const window = {}; | ||
| var window = typeof window !== "undefined" && window || {}; | ||
| module.exports = window; | ||
@@ -31,14 +31,9 @@ | ||
| ((global, document, Object, vnodeFlag, globalState, createElement, render, wrapClassProxy) => { | ||
| (() => { | ||
| let | ||
| /** | ||
| * Put the `vnodeFlag` to all strings in order to be considered as virtual | ||
| * dom nodes. | ||
| */ | ||
| String.prototype[vnodeFlag] = 1; | ||
| /** | ||
| * Create a VNode element | ||
| * | ||
| * @param {String|Function} eleent - The tag name or the component to render | ||
| * @param {String|Function} element - The tag name or the component to render | ||
| * @param {Object} [props] - The object properties | ||
@@ -48,19 +43,60 @@ * @param {Array} [children] - The child VNode elements | ||
| */ | ||
| createElement = (element, props={}, ...children) => ({ | ||
| [vnodeFlag]: 1, // The vnodeFlag symbol is used by the code | ||
| // in the 'P' property to check if the `props` | ||
| // argument is not an object, but a renderable | ||
| // VNode child | ||
| createElement = (element, props={}, ...children) => ( | ||
| { | ||
| E: element, // 'E' holds the name or function passed as | ||
| $: element, // '$' holds the name or function passed as | ||
| // first argument | ||
| P: props[vnodeFlag] // If the props argument is a renderable VNode, | ||
| ? {C: [].concat(props, ...children)} // ... prepend it to the children | ||
| : (props.C = [].concat(...children)) && props // ... otherwise append 'C' to the property | ||
| a: (props.$ || props.concat || props.removeChild) // If the props argument is a renderable VNode, | ||
| // a string, an array (.concat exists on both | ||
| // strings and arrays), or a DOM element, then ... | ||
| ? {c: [].concat(props, ...children)} // ... prepend it to the children | ||
| : ((props.c = [].concat(...children)), props) // ... otherwise append 'C' to the property | ||
| // the .concat ensures that arrays of children | ||
| // will be flattened into a single array. | ||
| }) | ||
| } | ||
| ) | ||
| /** | ||
| * Helper method that calls all methods in an array of functions | ||
| * | ||
| * @param {Array} methods - The array of methods to call | ||
| * @param {Object} arg1 - An arbitrary first argument | ||
| * @param {Object} arg2 - An arbitrary second argument | ||
| */ | ||
| , callLifecycleMethods = (methods = [], arg1, arg2) => | ||
| methods.map(e => e(arg1, arg2)) // Detaching from stack is important, otherwise it | ||
| /** | ||
| * Helper function that wraps an element shorthand function with a proxy | ||
| * that can be used to append class names to the instance. | ||
| * | ||
| * The result is wrapped with the same function, creating a chainable mechanism | ||
| * for appending classes. | ||
| * | ||
| * @param {function} factoryFn - The factory function to call for creating vnode | ||
| */ | ||
| , wrapClassProxy = factoryFn => | ||
| new Proxy( // We are creating a proxy object for every tag in | ||
| // order to be able to customize the class name | ||
| // via a shorthand call. | ||
| factoryFn, | ||
| { | ||
| get: (targetFn, className, _instance) => | ||
| wrapClassProxy( | ||
| (...args) => ( | ||
| (_instance=targetFn(...args)) // We first create the Virtual DOM instance by | ||
| // calling the wrapped factory function | ||
| .a.className = (_instance.a.className || '') // And then we assign the class name, | ||
| + ' ' + className, // concatenating to the previous value | ||
| _instance // And finally we return the instance | ||
| ) | ||
| ) | ||
| } | ||
| ) | ||
| /** | ||
| * Render a VNode in the DOM | ||
@@ -71,13 +107,12 @@ * | ||
| */ | ||
| global.R = render = ( | ||
| , render = window.R = ( | ||
| vnodes, // 1. The vnode tree to render | ||
| dom, // 2. The DOMElement where to render into | ||
| _npath='', // a. The current state path | ||
| _children=dom.childNodes, // b. Shorthand for accessing the children | ||
| _c=0 // c. Counter for processed children | ||
| _children=dom.childNodes, // a. Shorthand for accessing the children | ||
| _c=0 // b. Counter for processed children | ||
| ) => { | ||
| (vnodes.map ? vnodes : [vnodes]).map( // Cast `vnodes` to array if nor already | ||
| [].concat(vnodes).map( // Cast `vnodes` to array if nor already | ||
| // In this `map` loop we ensure that the DOM | ||
@@ -88,18 +123,27 @@ // elements correspond to the correct virtual | ||
| vnode, // 1. We handle the vnode from the array | ||
| index, // 2. And the index | ||
| _new_dom, // 2. We ignore the index, and instead we are using | ||
| // it as the new DOM element placeholder | ||
| _callMethod, // 3. We ignore the array, and instead we are using | ||
| // it as a variable where we are keeping the | ||
| // lifecycle method to call at the end. | ||
| _child=_children[_c++], // a. Get the next DOM child + increment counter | ||
| _state=( // b. Get the current state from the DOM child | ||
| _child && // - If there is no child, bail | ||
| (_child.a == vnode.$) // - If the element has changed, bail | ||
| && _child.s // - If the element has a state, use that | ||
| ) || {}, // - Default state value | ||
| _hooks={ // c. Prepare the hooks object that will be passed | ||
| // down to the functional component | ||
| s: _state, // - The 's' property is keeping a reference to | ||
| // the current element state. (Used above) | ||
| a: vnode.$, // - The 'a' property is keeping a reference | ||
| // to the element (property '$') and is used | ||
| // for space-optimal assignment of the tag to | ||
| // the DOM element through Object.assign in the | ||
| // Update Element phase later. | ||
| m: [], // - The 'm' property contains the `mount` cb | ||
| u: [], // - The 'u' property contains the `unmount` cb | ||
| d: [] // - The 'd' property contains the `update` cb | ||
| } | ||
| _unused1, // We don't handle the array, but we need the | ||
| // placeholder for the local variables after | ||
| _path=_npath+'.'+index, // a. The state path of this vnode | ||
| _path_state=globalState[_path] || [{}, vnode.E], // b. Get the state record for this path | ||
| _state=( // c. Update and get the state record | ||
| globalState[_path] = // The record is an the following format: | ||
| _path_state[1] != vnode.E // [ {state object}, | ||
| ? [{}, vnode.E] // 'vnode element' ] | ||
| : _path_state // The second component is needed in order to | ||
| ), // reset the state if the component has changed | ||
| _child=_children[_c++], // d. Get the next DOM child + increment counter | ||
| _new_dom // e. The new DOM element placeholder | ||
| ) => { | ||
@@ -109,75 +153,124 @@ | ||
| vnode.E && vnode.E.call && // If the vnode is a functional component, expand | ||
| (vnode = vnode.E( // it and replace the current vnode variable. | ||
| for (;(vnode.$ || vnodes).bind;) // Expand recursive functional components until | ||
| // we encounter a non-callable element. (The `vnodes` is | ||
| // used as a reference to any kind of an object in order | ||
| // to be able to resolve `.bind`, even if it's undefined). | ||
| vnode = vnode.$( | ||
| vnode.P, // 1. The component properties | ||
| _state[0], // 2. The stateful component state | ||
| vnode.a, // 1. The component properties | ||
| _state, // 2. The stateful component state | ||
| (newState) => // 3. The setState function | ||
| Object.assign( // First we update the state part of the record | ||
| _state[0], // Note: When we defined the variable we kept the | ||
| newState // reference to the record array | ||
| Object.assign( // First we update the state record, that also | ||
| _state, // updates the contents of the DOM element, since | ||
| newState // the reference is perserved. | ||
| ) && | ||
| render( // We then trigger the same render cycle that will | ||
| vnodes, // update the DOM | ||
| dom, | ||
| _npath | ||
| ) | ||
| dom | ||
| ), | ||
| )); | ||
| _hooks // 4. The lifecycle method hooks | ||
| ); | ||
| /* Create new DOM element */ | ||
| _new_dom = | ||
| vnode.removeChild // If this is a DOM element, pass it through ... | ||
| ? vnode // Otherwise we prepare the new DOM element in advance | ||
| : vnode.replace // in order to save a few comparison bytes later. | ||
| ? document.createTextNode(vnode) | ||
| : document.createElement(vnode.$); | ||
| _new_dom = // We prepare the new DOM element in advance in | ||
| vnode.trim // order to spare a few comparison bytes | ||
| ? document.createTextNode(vnode) | ||
| : document.createElement(vnode.E); | ||
| /* Keep or replace the previous DOM element */ | ||
| _new_dom = | ||
| _child // If we have a previous child we do some reconciliation | ||
| ? (vnode.removeChild // If it's a DOM element reference, check | ||
| ? _child != vnode | ||
| : (_child.$ != vnode.$ && _child.data != vnode)) | ||
| ? ( | ||
| dom.replaceChild( // - If not, we replace the old element with the | ||
| _new_dom, // new one. | ||
| _child | ||
| ), | ||
| _new_dom // ... and we make sure we return the new DOM | ||
| ) | ||
| : _child // - If it's the same, we keep the old child | ||
| /* Keep or replace the previous DOM element */ | ||
| : dom.appendChild( // If we did not have a previous child, just | ||
| _new_dom // append the new node | ||
| ) | ||
| (_new_dom = | ||
| _child // If we have a previous child we first check if | ||
| ? (_child.E != vnode.E && _child.data != vnode) // the VNode element or the text are the same | ||
| /* Prepare lifecycle methods */ | ||
| ? dom.replaceChild( // - If not, we replace the old element with the | ||
| _new_dom, // new one. | ||
| _child | ||
| ) && _new_dom // ... and we make sure we return the new DOM | ||
| _callMethod = // We are not calling the method until we are done with | ||
| // the rendering cycle. Otherwise this could cause an | ||
| // infinite loop if `setState` is used. | ||
| : _child // - If it's the same, we keep the old child | ||
| _child // If there is a DOM reflection | ||
| ? _child.a == _hooks.a // .. and the element has not changed | ||
| ? _hooks.d // - [D] Just update | ||
| : ( | ||
| callLifecycleMethods(_child.u), // - [U] Otherwise unmount the previous one | ||
| render( // And call the render function with empty | ||
| [], // children in order to recursively unmount | ||
| _child // the children tree. | ||
| ), | ||
| _hooks.m // - [M] Mount the new | ||
| ) | ||
| : dom.appendChild( // If we don't have a previous child, just append | ||
| _new_dom | ||
| ) | ||
| ).E = vnode.E; // We keep the vnode element to the .E property in | ||
| // order for the above comparison to work. | ||
| // If there is no DOM reflection | ||
| : _hooks.m; // - [M] Mount the new | ||
| /* Update Element */ | ||
| /* Update Element State */ | ||
| vnode.trim | ||
| ? _new_dom.data = vnode // - String nodes update only the text | ||
| : Object.keys(vnode.P).map( // - Element nodes have properties | ||
| ( | ||
| key // 1. The property name | ||
| ) => | ||
| Object.assign(_new_dom, vnode, _hooks); // Keep the following information in the DOM: | ||
| // - $ : The tag name from the vnode. We use this | ||
| // instead of the .tagName because some | ||
| // browsers convert it to capital-case | ||
| // - u : The `didUnmount` hook that is called when | ||
| // the DOM element is removed | ||
| // | ||
| // By assigning the entire _hooks and vnode | ||
| // objects we expose some unneeded properties, but | ||
| // it occupies less space than assigning $ and u | ||
| // individually. | ||
| key == 'style' ? // The 'style' property is an object and must be | ||
| /* Apply properties to the DOM element */ | ||
| vnode.removeChild || // If this is a DOM element, don't do anything | ||
| vnode.replace | ||
| ? _new_dom.data = vnode // - String nodes update only the text | ||
| : Object.keys(vnode.a).map( // - Element nodes have properties | ||
| ( | ||
| key // 1. The property name | ||
| ) => | ||
| key == 'style' ? // The 'style' property is an object and must be | ||
| // applied recursively. | ||
| Object.assign( | ||
| _new_dom[key], // '[key]' is shorter than '.style' | ||
| vnode.P[key] | ||
| ) | ||
| Object.assign( | ||
| _new_dom[key], // '[key]' is shorter than '.style' | ||
| vnode.a[key] | ||
| ) | ||
| : (_new_dom[key] !== vnode.P[key] && // All properties are applied directly to DOM, as | ||
| (_new_dom[key] = vnode.P[key])) // long as they are different than ther value in the | ||
| : (_new_dom[key] !== vnode.a[key] && // All properties are applied directly to DOM, as | ||
| (_new_dom[key] = vnode.a[key])) // long as they are different than ther value in the | ||
| // instance. This includes `onXXX` event handlers. | ||
| ) && | ||
| render( // Only if we have an element (and not text node) | ||
| vnode.P.C, // we recursively continue rendering into it's | ||
| _new_dom, // child nodes. | ||
| _path | ||
| ) | ||
| ) && | ||
| render( // Only if we have an element (and not text node) | ||
| vnode.a.c, // we recursively continue rendering into it's | ||
| _new_dom // child nodes. | ||
| ) | ||
| /* Call life-cycle methods */ | ||
| callLifecycleMethods( | ||
| _callMethod, | ||
| // Pass the following arguments: | ||
| _new_dom, // 1. The new DOM instance | ||
| _child // 2. The old DOM instance | ||
| ); | ||
| } | ||
@@ -188,35 +281,16 @@ ); | ||
| while (_children[_c]) // The _c property keeps track of the number of | ||
| dom.removeChild(_children[_c]) // elements in the VDom. If there are more child | ||
| } // nodes in the DOM, we remove them. | ||
| for (;_children[_c];) { // The _c property keeps track of the number of | ||
| // elements in the VDom. If there are more child | ||
| // nodes in the DOM, we remove them. | ||
| /** | ||
| * Helper function that wraps an element shorthand function with a proxy | ||
| * that can be used to append class names to the instance. | ||
| * | ||
| * The result is wrapped with the same function, creating a chainable mechanism | ||
| * for appending classes. | ||
| * | ||
| * @param {function} factoryFn - The factory function to call for creating vnode | ||
| */ | ||
| wrapClassProxy = (factoryFn) => | ||
| new Proxy( // We are creating a proxy object for every tag in | ||
| // order to be able to customize the class name | ||
| // via a shorthand call. | ||
| factoryFn, | ||
| { | ||
| get: (targetFn, className, _instance) => | ||
| wrapClassProxy( | ||
| (...args) => ( | ||
| (_instance=targetFn(...args)) // We first create the Virtual DOM instance by | ||
| // calling the wrapped factory function | ||
| callLifecycleMethods(_children[_c].u) // We then call the unmount lifecycle method for the | ||
| // elements that will be removed | ||
| .P.className = // And then we assign the class name, | ||
| [_instance.P.className] + ' ' + className, // concatenating to the previous value | ||
| render( // Remove child an trigger a recursive child removal | ||
| [], // in order to call the correct lifecycle methods in our | ||
| dom.removeChild(_children[_c]) // deep children too. | ||
| ) | ||
| _instance // And finally we return the instance | ||
| ) | ||
| ) | ||
| } | ||
| ) | ||
| } | ||
| } | ||
@@ -228,12 +302,15 @@ /** | ||
| */ | ||
| global.H = new Proxy( | ||
| window.H = new Proxy( | ||
| createElement, | ||
| { | ||
| get: (targetFn, tagName) => | ||
| targetFn[tagName] || wrapClassProxy( | ||
| createElement.bind(global, tagName) | ||
| ) | ||
| } | ||
| targetFn[tagName] || // Make sure we don't override any native | ||
| // property or method from the base function | ||
| wrapClassProxy( // Otherwise, for every tag we extract a | ||
| createElement.bind(targetFn, tagName) // class-wrapped crateElement method, bound to the | ||
| ) // tag named as the property requested. We are not | ||
| // using 'this', therefore we are using any reference | ||
| } // that could lead on reduced code footprint. | ||
| ) | ||
| })(window, document, Object, Symbol(), {}); | ||
| })() |
-3
| { | ||
| "presets": ["es2015"] | ||
| } |
Sorry, the diff of this file is not supported yet
| language: node_js | ||
| node_js: | ||
| - "6.9.5" |
Sorry, the diff of this file is not supported yet
| const dd = require('../dotdom'); | ||
| describe('.dom', function () { | ||
| describe('#H', function () { | ||
| describe('Factory', function () { | ||
| it('should create vnode without arguments', function () { | ||
| const vdom = dd.H('div'); | ||
| expect(vdom.E).toEqual('div'); | ||
| expect(vdom.P).toEqual({C: []}); | ||
| }); | ||
| it('should create vnode with props', function () { | ||
| const vdom = dd.H('div', {foo: 'bar'}); | ||
| expect(vdom.E).toEqual('div'); | ||
| expect(vdom.P).toEqual({foo: 'bar', C: []}); | ||
| }); | ||
| it('should create vnode with props and children', function () { | ||
| const cdom = dd.H('div'); | ||
| const vdom = dd.H('div', {foo: 'bar'}, cdom); | ||
| expect(vdom.E).toEqual('div'); | ||
| expect(vdom.P).toEqual({ | ||
| foo: 'bar', | ||
| C: [ cdom ] | ||
| }); | ||
| }); | ||
| it('should create vnode with props and children as array', function () { | ||
| const cdom1 = dd.H('div'); | ||
| const cdom2 = dd.H('div'); | ||
| const cdom3 = dd.H('div'); | ||
| const vdom = dd.H('div', {foo: 'bar'}, cdom1, [cdom2, cdom3]); | ||
| expect(vdom.E).toEqual('div'); | ||
| expect(vdom.P).toEqual({ | ||
| foo: 'bar', | ||
| C: [ cdom1, cdom2, cdom3 ] | ||
| }); | ||
| }); | ||
| it('should create vnode with props and mixed children', function () { | ||
| const cdom = dd.H('div'); | ||
| const vdom = dd.H('div', {foo: 'bar'}, 'foo', cdom); | ||
| expect(vdom.E).toEqual('div'); | ||
| expect(vdom.P).toEqual({ | ||
| foo: 'bar', | ||
| C: [ 'foo', cdom ] | ||
| }); | ||
| }); | ||
| it('should create vnode with props and string children', function () { | ||
| const vdom = dd.H('div', {foo: 'bar'}, 'foo'); | ||
| expect(vdom.E).toEqual('div'); | ||
| expect(vdom.P).toEqual({ | ||
| foo: 'bar', | ||
| C: [ 'foo' ] | ||
| }); | ||
| }); | ||
| it('should create vnode with only child', function () { | ||
| const vdom = dd.H('div', 'foo'); | ||
| expect(vdom.E).toEqual('div'); | ||
| expect(vdom.P).toEqual({ | ||
| C: [ 'foo' ] | ||
| }); | ||
| }); | ||
| it('should create vnode with children', function () { | ||
| const vdom = dd.H('div', 'foo', 'bar', 'baz'); | ||
| expect(vdom.E).toEqual('div'); | ||
| expect(vdom.P).toEqual({ | ||
| C: [ 'foo', 'bar', 'baz' ] | ||
| }); | ||
| }); | ||
| it('should create vnode with children in arrays', function () { | ||
| const vdom = dd.H('div', 'foo', ['bar', 'baz']); | ||
| expect(vdom.E).toEqual('div'); | ||
| expect(vdom.P).toEqual({ | ||
| C: [ 'foo', 'bar', 'baz' ] | ||
| }); | ||
| }); | ||
| it('should create vnode with only mixed children', function () { | ||
| const cdom = dd.H('div'); | ||
| const vdom = dd.H('div', cdom, 'foo'); | ||
| expect(vdom.E).toEqual('div'); | ||
| expect(vdom.P).toEqual({ | ||
| C: [ cdom, 'foo' ] | ||
| }); | ||
| }); | ||
| }); | ||
| describe('Proxy', function () { | ||
| it('H.apply should be proxied', function () { | ||
| const cdom = dd.H('div'); | ||
| const vdom = dd.H.apply({}, ['div', cdom, 'foo']); | ||
| expect(vdom.E).toEqual('div'); | ||
| expect(vdom.P).toEqual({ | ||
| C: [ cdom, 'foo' ] | ||
| }); | ||
| }); | ||
| it('H.call should be proxied', function () { | ||
| const cdom = dd.H('div'); | ||
| const vdom = dd.H.call({}, 'div', cdom, 'foo'); | ||
| expect(vdom.E).toEqual('div'); | ||
| expect(vdom.P).toEqual({ | ||
| C: [ cdom, 'foo' ] | ||
| }); | ||
| }); | ||
| it('H.tag should be a shorthand', function () { | ||
| const cdom = dd.H('div'); | ||
| const vdom = dd.H.div(cdom, 'foo'); | ||
| expect(vdom.E).toEqual('div'); | ||
| expect(vdom.P).toEqual({ | ||
| C: [ cdom, 'foo' ] | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| describe('#R', function () { | ||
| describe('DOM Manipulation', function () { | ||
| it('should render simple DOM', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom = dd.H('div'); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div></div>' | ||
| ); | ||
| }); | ||
| it('should render 1-level nested DOM', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom = dd.H('div', dd.H('a'), dd.H('b')); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div><a></a><b></b></div>' | ||
| ); | ||
| }); | ||
| it('should render 2-level nested DOM', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom = dd.H('div', | ||
| dd.H('a', dd.H('ul')), | ||
| dd.H('b', dd.H('ol')) | ||
| ); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div><a><ul></ul></a><b><ol></ol></b></div>' | ||
| ); | ||
| }); | ||
| it('should render text children', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom = dd.H('div', 'foo'); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div>foo</div>' | ||
| ); | ||
| }); | ||
| it('should render combined dom and text nodes', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom = dd.H('div', dd.H('a'), 'foo', dd.H('b')); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div><a></a>foo<b></b></div>' | ||
| ); | ||
| }); | ||
| it('should apply style properties', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom = dd.H('div', {style: {color: 'red'}}); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div style="color: red;"></div>' | ||
| ); | ||
| }); | ||
| it('should apply element attributes', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom = dd.H('a', {href: '/'}); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<a href="/"></a>' | ||
| ); | ||
| }); | ||
| it('should apply event handlers', function () { | ||
| const dom = document.createElement('div'); | ||
| const callback = jest.fn(); | ||
| const vdom = dd.H('a', {href: '/', onclick:callback}); | ||
| dd.R(vdom, dom); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<a href="/"></a>' | ||
| ); | ||
| const event = new window.MouseEvent('click'); | ||
| dom.firstChild.dispatchEvent(event); | ||
| expect(callback).toBeCalled(); | ||
| }); | ||
| it('should accept props and children', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom = dd.H('a', {href: '/'}, 'test'); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<a href="/">test</a>' | ||
| ); | ||
| }); | ||
| }); | ||
| describe('Reconciliation', function () { | ||
| it('should not replace DOM if tag & props are the same', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom1 = dd.H('div'); | ||
| const vdom2 = dd.H('div'); | ||
| dd.R(vdom1, dom) | ||
| const c1 = dom.firstChild; | ||
| dd.R(vdom2, dom) | ||
| const c2 = dom.firstChild; | ||
| expect(c1).toBe(c2); | ||
| }); | ||
| it('should replace DOM if tag has changed', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom1 = dd.H('div'); | ||
| const vdom2 = dd.H('span'); | ||
| dd.R(vdom1, dom) | ||
| const c1 = dom.firstChild; | ||
| dd.R(vdom2, dom) | ||
| const c2 = dom.firstChild; | ||
| expect(c1).not.toBe(c2); | ||
| }); | ||
| it('should not replace DOM if props have changed', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom1 = dd.H('div', {foo: 1}); | ||
| const vdom2 = dd.H('div', {foo: 2}); | ||
| dd.R(vdom1, dom) | ||
| const c1 = dom.firstChild; | ||
| dd.R(vdom2, dom) | ||
| const c2 = dom.firstChild; | ||
| expect(c1).toBe(c2); | ||
| }); | ||
| it('should not replace DOM if only children have changed', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom1 = dd.H('div', dd.H('span', 'foo')); | ||
| const vdom2 = dd.H('div', dd.H('span', 'bar')); | ||
| dd.R(vdom1, dom) | ||
| const c1 = dom.firstChild; | ||
| dd.R(vdom2, dom) | ||
| const c2 = dom.firstChild; | ||
| expect(c1).toBe(c2); | ||
| }); | ||
| it('should add new DOM elements keeping the old ones intact', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom1 = [dd.H('div')]; | ||
| const vdom2 = [dd.H('div'), dd.H('span')]; | ||
| dd.R(vdom1, dom) | ||
| const c1 = dom.firstChild; | ||
| expect(dom.children.length).toEqual(1); | ||
| dd.R(vdom2, dom) | ||
| const c2 = dom.firstChild; | ||
| expect(dom.children.length).toEqual(2); | ||
| expect(c1).toBe(c2); | ||
| }); | ||
| it('should remove excess DOM elements', function () { | ||
| const dom = document.createElement('div'); | ||
| const vdom1 = [dd.H('div'), dd.H('span')]; | ||
| const vdom2 = [dd.H('div')]; | ||
| dd.R(vdom1, dom) | ||
| expect(dom.children.length).toEqual(2); | ||
| dd.R(vdom2, dom) | ||
| expect(dom.children.length).toEqual(1); | ||
| }); | ||
| }); | ||
| describe('Components', function () { | ||
| it('should render simple component', function () { | ||
| const dom = document.createElement('div'); | ||
| const Component = function() { | ||
| return dd.H('div') | ||
| } | ||
| const vdom = dd.H(Component); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div></div>' | ||
| ); | ||
| }); | ||
| it('should render nested components', function () { | ||
| const dom = document.createElement('div'); | ||
| const Component = function() { | ||
| return dd.H('div') | ||
| } | ||
| const HostComponent = function() { | ||
| return dd.H('div', | ||
| dd.H(Component), | ||
| dd.H(Component) | ||
| ) | ||
| } | ||
| const vdom = dd.H(HostComponent); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div><div></div><div></div></div>' | ||
| ); | ||
| }); | ||
| it('should render component with props', function () { | ||
| const dom = document.createElement('div'); | ||
| const Component = function(props) { | ||
| return dd.H('a', {href: props.href}) | ||
| } | ||
| const vdom = dd.H(Component, {href: '/'}); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<a href="/"></a>' | ||
| ); | ||
| }); | ||
| it('should render stateful components', function () { | ||
| const dom = document.createElement('div'); | ||
| const Component = function(props, {href='/'}) { | ||
| return dd.H('a', { | ||
| href: href | ||
| }) | ||
| } | ||
| const vdom = dd.H(Component, {href: '/'}); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<a href="/"></a>' | ||
| ); | ||
| }); | ||
| it('should update stateful components', function () { | ||
| const dom = document.createElement('div'); | ||
| const Component = function(props, {clicks=0}, setState) { | ||
| return dd.H('button', { | ||
| onclick() { | ||
| setState({ | ||
| clicks: clicks + 1 | ||
| }) | ||
| } | ||
| }, `${clicks} clicks`) | ||
| } | ||
| const vdom = dd.H(Component); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button>0 clicks</button>' | ||
| ); | ||
| const event = new window.MouseEvent('click'); | ||
| dom.firstChild.dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button>1 clicks</button>' | ||
| ); | ||
| dom.firstChild.dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button>2 clicks</button>' | ||
| ); | ||
| }); | ||
| it('should update independently parallel stateful components', function () { | ||
| const dom = document.createElement('div'); | ||
| const Component = function(props, {clicks=0}, setState) { | ||
| return dd.H('button', { | ||
| onclick() { | ||
| setState({ | ||
| clicks: clicks + 1 | ||
| }) | ||
| } | ||
| }, `${clicks} clicks`) | ||
| } | ||
| const vdom = dd.H('div', | ||
| dd.H(Component), | ||
| dd.H(Component) | ||
| ); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div><button>0 clicks</button><button>0 clicks</button></div>' | ||
| ); | ||
| const event = new window.MouseEvent('click'); | ||
| dom.firstChild.childNodes[0].dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div><button>1 clicks</button><button>0 clicks</button></div>' | ||
| ); | ||
| dom.firstChild.childNodes[0].dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div><button>2 clicks</button><button>0 clicks</button></div>' | ||
| ); | ||
| dom.firstChild.childNodes[1].dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div><button>2 clicks</button><button>1 clicks</button></div>' | ||
| ); | ||
| dom.firstChild.childNodes[1].dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div><button>2 clicks</button><button>2 clicks</button></div>' | ||
| ); | ||
| dom.firstChild.childNodes[0].dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div><button>3 clicks</button><button>2 clicks</button></div>' | ||
| ); | ||
| }); | ||
| it('should maintain state of child components', function () { | ||
| const dom = document.createElement('div'); | ||
| const Component = function(props, {clicks=0}, setState) { | ||
| return dd.H('button', { | ||
| onclick() { | ||
| setState({ | ||
| clicks: clicks + 1 | ||
| }) | ||
| } | ||
| }, `${clicks} clicks`) | ||
| } | ||
| const HostComponent = function(props, {clicks=0}, setState) { | ||
| return dd.H('button', | ||
| { | ||
| onclick() { | ||
| setState({ | ||
| clicks: clicks + 1 | ||
| }) | ||
| } | ||
| }, | ||
| dd.H('div', `${clicks} clicks`), | ||
| dd.H(Component), | ||
| dd.H(Component) | ||
| ) | ||
| } | ||
| const vdom = dd.H(HostComponent); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>0 clicks</div><button>0 clicks</button><button>0 clicks</button></button>' | ||
| ); | ||
| const event = new window.MouseEvent('click'); | ||
| dom.firstChild.dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>1 clicks</div><button>0 clicks</button><button>0 clicks</button></button>' | ||
| ); | ||
| dom.firstChild.childNodes[1].dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>1 clicks</div><button>1 clicks</button><button>0 clicks</button></button>' | ||
| ); | ||
| dom.firstChild.childNodes[1].dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>1 clicks</div><button>2 clicks</button><button>0 clicks</button></button>' | ||
| ); | ||
| dom.firstChild.childNodes[2].dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>1 clicks</div><button>2 clicks</button><button>1 clicks</button></button>' | ||
| ); | ||
| dom.firstChild.childNodes[2].dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>1 clicks</div><button>2 clicks</button><button>2 clicks</button></button>' | ||
| ); | ||
| dom.firstChild.dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>2 clicks</div><button>2 clicks</button><button>2 clicks</button></button>' | ||
| ); | ||
| }); | ||
| it('should discard child state when it\'s type changes', function () { | ||
| const dom = document.createElement('div'); | ||
| const ComponentA = function(props, {clicks=0}, setState) { | ||
| return dd.H('button', { | ||
| title: 'a', | ||
| onclick() { | ||
| setState({ | ||
| clicks: clicks + 1 | ||
| }) | ||
| } | ||
| }, `${clicks} clicks`) | ||
| } | ||
| const ComponentB = function(props, {clicks=0}, setState) { | ||
| return dd.H('button', { | ||
| title: 'b', | ||
| onclick() { | ||
| setState({ | ||
| clicks: clicks + 1 | ||
| }) | ||
| } | ||
| }, `${clicks} clicks`) | ||
| } | ||
| const HostComponent = function(props, {clicks=0}, setState) { | ||
| const children = []; | ||
| if (clicks % 2) { | ||
| children.push(dd.H(ComponentA)); | ||
| children.push(dd.H(ComponentB)); | ||
| } else { | ||
| children.push(dd.H(ComponentB)); | ||
| children.push(dd.H(ComponentA)); | ||
| } | ||
| return dd.H('button', | ||
| { | ||
| onclick() { | ||
| setState({ | ||
| clicks: clicks + 1 | ||
| }) | ||
| } | ||
| }, | ||
| dd.H('div', `${clicks} clicks`), | ||
| children[0], | ||
| children[1] | ||
| ) | ||
| } | ||
| const vdom = dd.H(HostComponent); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>0 clicks</div><button title="b">0 clicks</button><button title="a">0 clicks</button></button>' | ||
| ); | ||
| const event = new window.MouseEvent('click'); | ||
| dom.firstChild.dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>1 clicks</div><button title="a">0 clicks</button><button title="b">0 clicks</button></button>' | ||
| ); | ||
| dom.firstChild.childNodes[1].dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>1 clicks</div><button title="a">1 clicks</button><button title="b">0 clicks</button></button>' | ||
| ); | ||
| dom.firstChild.childNodes[2].dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>1 clicks</div><button title="a">1 clicks</button><button title="b">1 clicks</button></button>' | ||
| ); | ||
| dom.firstChild.dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>2 clicks</div><button title="b">0 clicks</button><button title="a">0 clicks</button></button>' | ||
| ); | ||
| dom.firstChild.childNodes[1].dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>2 clicks</div><button title="b">1 clicks</button><button title="a">0 clicks</button></button>' | ||
| ); | ||
| dom.firstChild.childNodes[2].dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>2 clicks</div><button title="b">1 clicks</button><button title="a">1 clicks</button></button>' | ||
| ); | ||
| dom.firstChild.dispatchEvent(event); | ||
| expect(dom.innerHTML).toEqual( | ||
| '<button><div>3 clicks</div><button title="a">0 clicks</button><button title="b">0 clicks</button></button>' | ||
| ); | ||
| }); | ||
| }); | ||
| describe('Tag Shorthands', function () { | ||
| it(`should dynamically create tag shorthands`, function () { | ||
| const dom = document.createElement('div'); | ||
| const {div} = dd.H; | ||
| const vdom = div(); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| `<div></div>` | ||
| ); | ||
| }); | ||
| it('should expand className shorthands', function () { | ||
| const dom = document.createElement('div'); | ||
| const {div} = dd.H; | ||
| const vdom = div.class1(); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div class=" class1"></div>' | ||
| ); | ||
| }) | ||
| it('should expand multiple className shorthands', function () { | ||
| const dom = document.createElement('div'); | ||
| const {div} = dd.H; | ||
| const vdom = div.class1.class2.class3(); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div class=" class1 class2 class3"></div>' | ||
| ); | ||
| }) | ||
| it('should append className shorthands on className props', function () { | ||
| const dom = document.createElement('div'); | ||
| const {div} = dd.H; | ||
| const vdom = div.class1.class2.class3({className: 'foo'}); | ||
| dd.R(vdom, dom) | ||
| expect(dom.innerHTML).toEqual( | ||
| '<div class="foo class1 class2 class3"></div>' | ||
| ); | ||
| }) | ||
| }); | ||
| }); | ||
| }); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
416
30%44471
-21.69%16
128.57%5
-50%259
-65.09%3
200%1
Infinity%