├── .npmrc ├── cjs ├── package.json ├── index.js ├── state.js ├── dom.js └── utils.js ├── test ├── package.json ├── uland.html ├── index.js └── index.html ├── .gitignore ├── .npmignore ├── esm ├── index.js ├── state.js ├── dom.js └── utils.js ├── .travis.yml ├── rollup ├── es.config.js ├── babel.dom.js ├── babel.config.js └── babel.state.js ├── state.js ├── LICENSE ├── es.js ├── dom.js ├── min.js ├── package.json ├── index.js └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /cjs/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nyc_output 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nyc_output 3 | .travis.yml 4 | node_modules/ 5 | rollup/ 6 | test/ 7 | -------------------------------------------------------------------------------- /esm/index.js: -------------------------------------------------------------------------------- 1 | import dom from './dom.js'; 2 | import state from './state.js'; 3 | 4 | export default (options = {}) => (options.dom ? dom : state)(options); 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | git: 5 | depth: 1 6 | branches: 7 | only: 8 | - master 9 | - /^greenkeeper/.*$/ 10 | after_success: 11 | - "npm run coveralls" 12 | -------------------------------------------------------------------------------- /esm/state.js: -------------------------------------------------------------------------------- 1 | import {defineProperties, loop, noop} from './utils.js'; 2 | 3 | const value = (props, key) => props[key]; 4 | 5 | export default ({ 6 | all = false, 7 | shallow = true, 8 | useState = noop 9 | } = {}) => (props, update) => defineProperties( 10 | {}, loop(props, value, all, shallow, useState, update) 11 | ); 12 | -------------------------------------------------------------------------------- /cjs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const dom = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('./dom.js')); 3 | const state = (m => m.__esModule ? /* istanbul ignore next */ m.default : /* istanbul ignore next */ m)(require('./state.js')); 4 | 5 | module.exports = (options = {}) => (options.dom ? dom : state)(options); 6 | -------------------------------------------------------------------------------- /cjs/state.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {defineProperties, loop, noop} = require('./utils.js'); 3 | 4 | const value = (props, key) => props[key]; 5 | 6 | module.exports = ({ 7 | all = false, 8 | shallow = true, 9 | useState = noop 10 | } = {}) => (props, update) => defineProperties( 11 | {}, loop(props, value, all, shallow, useState, update) 12 | ); 13 | -------------------------------------------------------------------------------- /rollup/es.config.js: -------------------------------------------------------------------------------- 1 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 2 | import {terser} from 'rollup-plugin-terser'; 3 | 4 | export default { 5 | input: './esm/index.js', 6 | plugins: [ 7 | nodeResolve(), 8 | terser() 9 | ], 10 | output: { 11 | exports: 'named', 12 | file: './es.js', 13 | format: 'iife', 14 | name: 'reactiveProps' 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /rollup/babel.dom.js: -------------------------------------------------------------------------------- 1 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 2 | import babel from '@rollup/plugin-babel'; 3 | 4 | export default { 5 | input: './esm/dom.js', 6 | plugins: [ 7 | nodeResolve(), 8 | babel({ 9 | presets: ['@babel/preset-env'], 10 | babelHelpers: 'bundled' 11 | }) 12 | ], 13 | output: { 14 | exports: 'named', 15 | file: './dom.max.js', 16 | format: 'iife', 17 | name: 'domHandler' 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /rollup/babel.config.js: -------------------------------------------------------------------------------- 1 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 2 | import babel from '@rollup/plugin-babel'; 3 | 4 | export default { 5 | input: './esm/index.js', 6 | plugins: [ 7 | nodeResolve(), 8 | babel({ 9 | presets: ['@babel/preset-env'], 10 | babelHelpers: 'bundled' 11 | }) 12 | ], 13 | output: { 14 | exports: 'named', 15 | file: './index.js', 16 | format: 'iife', 17 | name: 'reactiveProps' 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /rollup/babel.state.js: -------------------------------------------------------------------------------- 1 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 2 | import babel from '@rollup/plugin-babel'; 3 | 4 | export default { 5 | input: './esm/state.js', 6 | plugins: [ 7 | nodeResolve(), 8 | babel({ 9 | presets: ['@babel/preset-env'], 10 | babelHelpers: 'bundled' 11 | }) 12 | ], 13 | output: { 14 | exports: 'named', 15 | file: './state.max.js', 16 | format: 'iife', 17 | name: 'stateHandler' 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /state.js: -------------------------------------------------------------------------------- 1 | var stateHandler=function(t){"use strict";var n=Object.defineProperties,e=Object.keys,r=function(t,n,e,r,o){return{configurable:!0,get:function(){return r},set:function(u){(t||u!==r||n&&"object"==typeof u&&u)&&(r=u,e?o.call(this,r):o.call(this))}}},o=function(t,n,o,i,c,a){for(var l={},f=c!==u,s=[o,i,f],v=e(t),d=0;d0&&void 0!==arguments[0]?arguments[0]:{},e=t.all,r=void 0!==e&&e,c=t.shallow,a=void 0===c||c,l=t.useState,f=void 0===l?u:l;return function(t,e){return n({},o(t,i,r,a,f,e))}}}(); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2020, Andrea Giammarchi, @WebReflection 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /es.js: -------------------------------------------------------------------------------- 1 | self.reactiveProps=function(t){"use strict";const{defineProperties:e,keys:r}=Object,l=(t,e,r,l,s)=>({configurable:!0,get:()=>l,set(a){(t||a!==l||e&&"object"==typeof a&&a)&&(l=a,r?s.call(this,l):s.call(this))}}),s=(t,e,s,o,n,c)=>{const u={},i=n!==a,b=[s,o,i];for(let s=r(t),a=0;a{};var o=({all:t=!1,shallow:r=!0,useState:l=a,getAttribute:o=((t,e)=>t.getAttribute(e))}={})=>(a,n,c)=>{const u=s(n,((t,e)=>{let r=t[e],l=typeof r;return a.hasOwnProperty(e)?(r=a[e],delete a[e]):a.hasAttribute(e)&&(r=o(a,e),"number"==l?r=+r:"boolean"==l&&(r=!/^(?:false|0|)$/.test(r))),r}),t,r,l,c);return e(a,u)};const n=(t,e)=>t[e];var c=({all:t=!1,shallow:r=!0,useState:l=a}={})=>(a,o)=>e({},s(a,n,t,r,l,o));return t.default=(t={})=>(t.dom?o:c)(t),t}({}).default; 2 | -------------------------------------------------------------------------------- /dom.js: -------------------------------------------------------------------------------- 1 | var domHandler=function(t){"use strict";var e=Object.defineProperties,n=Object.keys,r=function(t,e,n,r,o){return{configurable:!0,get:function(){return r},set:function(u){(t||u!==r||e&&"object"==typeof u&&u)&&(r=u,n?o.call(this,r):o.call(this))}}},o=function(t,e,o,i,a,c){for(var l={},f=a!==u,s=[o,i,f],v=n(t),b=0;b0&&void 0!==arguments[0]?arguments[0]:{},n=t.all,r=void 0!==n&&n,i=t.shallow,a=void 0===i||i,c=t.useState,l=void 0===c?u:c,f=t.getAttribute,s=void 0===f?function(t,e){return t.getAttribute(e)}:f;return function(t,n,u){var i=o(n,(function(e,n){var r=e[n],o=typeof r;return t.hasOwnProperty(n)?(r=t[n],delete t[n]):t.hasAttribute(n)&&(r=s(t,n),"number"==o?r=+r:"boolean"==o&&(r=!/^(?:false|0|)$/.test(r))),r}),r,a,l,u);return e(t,i)}}}(); -------------------------------------------------------------------------------- /esm/dom.js: -------------------------------------------------------------------------------- 1 | import {defineProperties, loop, noop} from './utils.js'; 2 | 3 | export default ({ 4 | all = false, 5 | shallow = true, 6 | useState = noop, 7 | getAttribute = (element, key) => element.getAttribute(key) 8 | } = {}) => (element, props, update) => { 9 | const value = (props, key) => { 10 | let result = props[key], type = typeof result; 11 | if (element.hasOwnProperty(key)) { 12 | result = element[key]; 13 | delete element[key]; 14 | } 15 | else if (element.hasAttribute(key)) { 16 | result = getAttribute(element, key); 17 | if (type == 'number') 18 | result = +result; 19 | else if (type == 'boolean') 20 | result = !/^(?:false|0|)$/.test(result); 21 | } 22 | return result; 23 | }; 24 | const desc = loop(props, value, all, shallow, useState, update); 25 | return defineProperties(element, desc); 26 | }; 27 | -------------------------------------------------------------------------------- /cjs/dom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {defineProperties, loop, noop} = require('./utils.js'); 3 | 4 | module.exports = ({ 5 | all = false, 6 | shallow = true, 7 | useState = noop, 8 | getAttribute = (element, key) => element.getAttribute(key) 9 | } = {}) => (element, props, update) => { 10 | const value = (props, key) => { 11 | let result = props[key], type = typeof result; 12 | if (element.hasOwnProperty(key)) { 13 | result = element[key]; 14 | delete element[key]; 15 | } 16 | else if (element.hasAttribute(key)) { 17 | result = getAttribute(element, key); 18 | if (type == 'number') 19 | result = +result; 20 | else if (type == 'boolean') 21 | result = !/^(?:false|0|)$/.test(result); 22 | } 23 | return result; 24 | }; 25 | const desc = loop(props, value, all, shallow, useState, update); 26 | return defineProperties(element, desc); 27 | }; 28 | -------------------------------------------------------------------------------- /esm/utils.js: -------------------------------------------------------------------------------- 1 | const {defineProperties, keys} = Object; 2 | 3 | const accessor = (all, shallow, hook, value, update) => ({ 4 | configurable: true, 5 | get: () => value, 6 | set(_) { 7 | if (all || _ !== value || (shallow && typeof _ === 'object' && _)) { 8 | value = _; 9 | if (hook) 10 | update.call(this, value); 11 | else 12 | update.call(this); 13 | } 14 | } 15 | }); 16 | 17 | const loop = (props, get, all, shallow, useState, update) => { 18 | const desc = {}; 19 | const hook = useState !== noop; 20 | const args = [all, shallow, hook]; 21 | for (let ke = keys(props), y = 0; y < ke.length; y++) { 22 | const value = get(props, ke[y]); 23 | const extras = hook ? useState(value) : [value, useState]; 24 | if (update) 25 | extras[1] = update; 26 | desc[ke[y]] = accessor.apply(null, args.concat(extras)); 27 | } 28 | return desc; 29 | }; 30 | 31 | const noop = () => {}; 32 | 33 | export {defineProperties, loop, noop}; 34 | -------------------------------------------------------------------------------- /cjs/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {defineProperties, keys} = Object; 3 | 4 | const accessor = (all, shallow, hook, value, update) => ({ 5 | configurable: true, 6 | get: () => value, 7 | set(_) { 8 | if (all || _ !== value || (shallow && typeof _ === 'object' && _)) { 9 | value = _; 10 | if (hook) 11 | update.call(this, value); 12 | else 13 | update.call(this); 14 | } 15 | } 16 | }); 17 | 18 | const loop = (props, get, all, shallow, useState, update) => { 19 | const desc = {}; 20 | const hook = useState !== noop; 21 | const args = [all, shallow, hook]; 22 | for (let ke = keys(props), y = 0; y < ke.length; y++) { 23 | const value = get(props, ke[y]); 24 | const extras = hook ? useState(value) : [value, useState]; 25 | if (update) 26 | extras[1] = update; 27 | desc[ke[y]] = accessor.apply(null, args.concat(extras)); 28 | } 29 | return desc; 30 | }; 31 | 32 | const noop = () => {}; 33 | 34 | exports.defineProperties = defineProperties; 35 | exports.loop = loop; 36 | exports.noop = noop; 37 | -------------------------------------------------------------------------------- /min.js: -------------------------------------------------------------------------------- 1 | self.reactiveProps=function(t){"use strict";var n=Object.defineProperties,e=Object.keys,r=function(t,n,e,r,o){return{configurable:!0,get:function(){return r},set:function(u){(t||u!==r||n&&"object"==typeof u&&u)&&(r=u,e?o.call(this,r):o.call(this))}}},o=function(t,n,o,i,a,c){for(var l={},f=a!==u,v=[o,i,f],s=e(t),d=0;d0&&void 0!==arguments[0]?arguments[0]:{},e=t.all,r=void 0!==e&&e,i=t.shallow,a=void 0===i||i,c=t.useState,l=void 0===c?u:c,f=t.getAttribute,v=void 0===f?function(t,n){return t.getAttribute(n)}:f;return function(t,e,u){var i=o(e,(function(n,e){var r=n[e],o=typeof r;return t.hasOwnProperty(e)?(r=t[e],delete t[e]):t.hasAttribute(e)&&(r=v(t,e),"number"==o?r=+r:"boolean"==o&&(r=!/^(?:false|0|)$/.test(r))),r}),r,a,l,u);return n(t,i)}},a=function(t,n){return t[n]},c=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.all,r=void 0!==e&&e,i=t.shallow,c=void 0===i||i,l=t.useState,f=void 0===l?u:l;return function(t,e){return n({},o(t,a,r,c,f,e))}};return function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return(t.dom?i:c)(t)}}(); -------------------------------------------------------------------------------- /test/uland.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactive-props", 3 | "version": "0.2.2", 4 | "description": "An all-in-one implementation of the Reactive State for Data & DOM patterns.", 5 | "main": "./cjs/index.js", 6 | "scripts": { 7 | "build": "npm run cjs && npm run rollup:es && npm run rollup:babel && npm run babel:extras && npm run babel:typeof && npm run fix:default && npm run min && npm run test", 8 | "cjs": "ascjs --no-default esm cjs", 9 | "rollup:es": "rollup --config rollup/es.config.js && sed -i.bck 's/^var /self./' es.js && rm -rf es.js.bck", 10 | "rollup:babel": "rollup --config rollup/babel.config.js && sed -i.bck 's/^var /self./' index.js && rm -rf index.js.bck", 11 | "babel:extras": "npm run babel:dom && npm run babel:state", 12 | "babel:dom": "rollup --config rollup/babel.dom.js && sed -i.bck 's/^var /self./' dom.js && rm -rf dom.js.bck", 13 | "babel:state": "rollup --config rollup/babel.state.js && sed -i.bck 's/^var /self./' state.js && rm -rf state.js.bck", 14 | "babel:typeof": "drop-babel-typeof index.js && drop-babel-typeof dom.max.js && drop-babel-typeof state.max.js", 15 | "min": "terser index.js --comments='/^!/' -c -m -o min.js && npm run min:extras && rm {dom.max.js,state.max.js}", 16 | "min:extras": "terser dom.max.js --comments='/^!/' -c -m -o dom.js && terser state.max.js --comments='/^!/' -c -m -o state.js", 17 | "fix:default": "npm run fix:index && npm run fix:extras", 18 | "fix:index": "sed -i 's/exports.default =/return/' index.js && sed -i 's/({})/({}).default/' es.js", 19 | "fix:extras": "sed -i 's/exports.default =/return/' dom.max.js && sed -i 's/exports.default =/return/' state.max.js", 20 | "coveralls": "nyc report --reporter=text-lcov | coveralls", 21 | "test": "nyc node test/index.js" 22 | }, 23 | "keywords": [ 24 | "reactive", 25 | "state", 26 | "hooks", 27 | "helper" 28 | ], 29 | "author": "Andrea Giammarchi", 30 | "license": "ISC", 31 | "devDependencies": { 32 | "@babel/core": "^7.11.6", 33 | "@babel/preset-env": "^7.11.5", 34 | "@rollup/plugin-babel": "^5.2.1", 35 | "@rollup/plugin-node-resolve": "^9.0.0", 36 | "ascjs": "^4.0.1", 37 | "coveralls": "^3.1.0", 38 | "drop-babel-typeof": "^1.0.3", 39 | "linkedom": "^0.1.39", 40 | "nyc": "^15.1.0", 41 | "rollup": "^2.28.2", 42 | "rollup-plugin-terser": "^7.0.2", 43 | "terser": "^5.3.4" 44 | }, 45 | "module": "./esm/index.js", 46 | "type": "module", 47 | "exports": { 48 | ".": { 49 | "import": "./esm/index.js", 50 | "default": "./cjs/index.js" 51 | }, 52 | "./package.json": "./package.json", 53 | "./dom": { 54 | "import": "./esm/dom.js", 55 | "default": "./cjs/dom.js" 56 | }, 57 | "./esm/dom.js": { 58 | "import": "./esm/dom.js", 59 | "default": "./cjs/dom.js" 60 | }, 61 | "./state": { 62 | "import": "./esm/state.js", 63 | "default": "./cjs/state.js" 64 | } 65 | }, 66 | "unpkg": "min.js", 67 | "repository": { 68 | "type": "git", 69 | "url": "git+https://github.com/WebReflection/reactive-props.git" 70 | }, 71 | "bugs": { 72 | "url": "https://github.com/WebReflection/reactive-props/issues" 73 | }, 74 | "homepage": "https://github.com/WebReflection/reactive-props#readme" 75 | } 76 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | self.reactiveProps = (function (exports) { 2 | 'use strict'; 3 | 4 | 5 | 6 | var defineProperties = Object.defineProperties, 7 | keys = Object.keys; 8 | 9 | var accessor = function accessor(all, shallow, hook, value, update) { 10 | return { 11 | configurable: true, 12 | get: function get() { 13 | return value; 14 | }, 15 | set: function set(_) { 16 | if (all || _ !== value || shallow && typeof(_) === 'object' && _) { 17 | value = _; 18 | if (hook) update.call(this, value);else update.call(this); 19 | } 20 | } 21 | }; 22 | }; 23 | 24 | var loop = function loop(props, get, all, shallow, useState, update) { 25 | var desc = {}; 26 | var hook = useState !== noop; 27 | var args = [all, shallow, hook]; 28 | 29 | for (var ke = keys(props), y = 0; y < ke.length; y++) { 30 | var value = get(props, ke[y]); 31 | var extras = hook ? useState(value) : [value, useState]; 32 | if (update) extras[1] = update; 33 | desc[ke[y]] = accessor.apply(null, args.concat(extras)); 34 | } 35 | 36 | return desc; 37 | }; 38 | 39 | var noop = function noop() {}; 40 | 41 | var dom = (function () { 42 | var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 43 | _ref$all = _ref.all, 44 | all = _ref$all === void 0 ? false : _ref$all, 45 | _ref$shallow = _ref.shallow, 46 | shallow = _ref$shallow === void 0 ? true : _ref$shallow, 47 | _ref$useState = _ref.useState, 48 | useState = _ref$useState === void 0 ? noop : _ref$useState, 49 | _ref$getAttribute = _ref.getAttribute, 50 | getAttribute = _ref$getAttribute === void 0 ? function (element, key) { 51 | return element.getAttribute(key); 52 | } : _ref$getAttribute; 53 | 54 | return function (element, props, update) { 55 | var value = function value(props, key) { 56 | var result = props[key], 57 | type = typeof(result); 58 | 59 | if (element.hasOwnProperty(key)) { 60 | result = element[key]; 61 | delete element[key]; 62 | } else if (element.hasAttribute(key)) { 63 | result = getAttribute(element, key); 64 | if (type == 'number') result = +result;else if (type == 'boolean') result = !/^(?:false|0|)$/.test(result); 65 | } 66 | 67 | return result; 68 | }; 69 | 70 | var desc = loop(props, value, all, shallow, useState, update); 71 | return defineProperties(element, desc); 72 | }; 73 | }); 74 | 75 | var value = function value(props, key) { 76 | return props[key]; 77 | }; 78 | 79 | var state = (function () { 80 | var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 81 | _ref$all = _ref.all, 82 | all = _ref$all === void 0 ? false : _ref$all, 83 | _ref$shallow = _ref.shallow, 84 | shallow = _ref$shallow === void 0 ? true : _ref$shallow, 85 | _ref$useState = _ref.useState, 86 | useState = _ref$useState === void 0 ? noop : _ref$useState; 87 | 88 | return function (props, update) { 89 | return defineProperties({}, loop(props, value, all, shallow, useState, update)); 90 | }; 91 | }); 92 | 93 | var index = (function () { 94 | var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 95 | return (options.dom ? dom : state)(options); 96 | }); 97 | 98 | return index; 99 | 100 | return exports; 101 | 102 | }({})); 103 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const {document} = require('linkedom').parseHTML(''); 2 | global.document = document; 3 | 4 | const genericHandler = require('../cjs'); 5 | const stateHandler = require('../cjs/state'); 6 | const domHandler = require('../cjs/dom'); 7 | const {noop} = require('../cjs/utils.js'); 8 | 9 | let element, invoked, reactive, state; 10 | 11 | console.assert(typeof genericHandler() === 'function'); 12 | console.assert(noop() === void 0); 13 | 14 | console.log(' default state handler'); 15 | reactive = stateHandler(); 16 | invoked = false; 17 | data = []; 18 | state = reactive({prop: '', data}, () => invoked = true); 19 | console.assert(state.prop === ''); 20 | state.prop = 'OK'; 21 | console.assert(invoked); 22 | invoked = false; 23 | console.assert(state.prop === 'OK'); 24 | state.prop = 'OK'; 25 | console.assert(!invoked); 26 | console.assert(state.prop === 'OK'); 27 | invoked = false; 28 | data.push(1); 29 | state.data = data; 30 | console.assert(invoked); 31 | console.assert(state.data === data); 32 | 33 | console.log(' not shallow state handler'); 34 | reactive = stateHandler({shallow: false}); 35 | invoked = false; 36 | data = []; 37 | state = reactive({prop: '', data}, () => invoked = true); 38 | console.assert(state.prop === ''); 39 | state.prop = 'OK'; 40 | console.assert(invoked); 41 | invoked = false; 42 | console.assert(state.prop === 'OK'); 43 | state.prop = 'OK'; 44 | console.assert(!invoked); 45 | console.assert(state.prop === 'OK'); 46 | invoked = false; 47 | data.push(1); 48 | state.data = data; 49 | console.assert(!invoked); 50 | console.assert(state.data === data); 51 | 52 | console.log(' all state handler'); 53 | reactive = genericHandler({all: true}); 54 | invoked = false; 55 | state = reactive({prop: ''}, () => invoked = true); 56 | console.assert(state.prop === ''); 57 | state.prop = 'OK'; 58 | console.assert(invoked); 59 | invoked = false; 60 | console.assert(state.prop === 'OK'); 61 | state.prop = 'OK'; 62 | console.assert(invoked); 63 | console.assert(state.prop === 'OK'); 64 | 65 | console.log(' hooked state handler'); 66 | reactive = genericHandler({useState: value => [value, () => invoked = true]}); 67 | invoked = false; 68 | state = reactive({prop: ''}); 69 | console.assert(state.prop === ''); 70 | state.prop = 'OK'; 71 | console.assert(invoked); 72 | invoked = false; 73 | console.assert(state.prop === 'OK'); 74 | state.prop = 'OK'; 75 | console.assert(!invoked); 76 | console.assert(state.prop === 'OK'); 77 | 78 | console.log(' hooked overloaded handler'); 79 | reactive = genericHandler({useState: value => [value, () => invoked = true]}); 80 | invoked = false; 81 | state = reactive({prop: ''}, () => {}); 82 | console.assert(state.prop === ''); 83 | state.prop = 'OK'; 84 | console.assert(!invoked); 85 | console.assert(state.prop === 'OK'); 86 | 87 | console.log(' hooked all handler'); 88 | reactive = genericHandler({all: true, useState: value => [value, () => invoked = true]}); 89 | invoked = false; 90 | state = reactive({prop: ''}); 91 | console.assert(state.prop === ''); 92 | state.prop = 'OK'; 93 | console.assert(invoked); 94 | invoked = false; 95 | console.assert(state.prop === 'OK'); 96 | state.prop = 'OK'; 97 | console.assert(invoked); 98 | console.assert(state.prop === 'OK'); 99 | 100 | console.log(' default dom handler'); 101 | reactive = domHandler(); 102 | invoked = false; 103 | element = document.createElement('p'); 104 | state = reactive(element, {prop: ''}, () => invoked = true); 105 | console.assert(element === state); 106 | console.assert(state.prop === ''); 107 | state.prop = 'OK'; 108 | console.assert(invoked); 109 | invoked = false; 110 | console.assert(state.prop === 'OK'); 111 | state.prop = 'OK'; 112 | console.assert(!invoked); 113 | console.assert(state.prop === 'OK'); 114 | 115 | console.log(' default dom handler with property'); 116 | reactive = genericHandler({dom: true}); 117 | invoked = false; 118 | element = document.createElement('p'); 119 | element.prop = 'initial'; 120 | state = reactive(element, {prop: ''}, () => invoked = true); 121 | console.assert(element === state); 122 | console.assert(state.prop === 'initial'); 123 | state.prop = 'OK'; 124 | console.assert(invoked); 125 | invoked = false; 126 | console.assert(state.prop === 'OK'); 127 | state.prop = 'OK'; 128 | console.assert(!invoked); 129 | console.assert(state.prop === 'OK'); 130 | 131 | console.log(' default dom handler with attribute'); 132 | reactive = genericHandler({dom: true}); 133 | invoked = false; 134 | element = document.createElement('p'); 135 | element.setAttribute('prop', 'initial'); 136 | state = reactive(element, {prop: ''}, () => invoked = true); 137 | console.assert(element === state); 138 | console.assert(state.prop === 'initial'); 139 | state.prop = 'OK'; 140 | console.assert(invoked); 141 | invoked = false; 142 | console.assert(state.prop === 'OK'); 143 | state.prop = 'OK'; 144 | console.assert(!invoked); 145 | console.assert(state.prop === 'OK'); 146 | 147 | reactive = genericHandler({dom: true}); 148 | invoked = false; 149 | element = document.createElement('p'); 150 | element.setAttribute('falsy', 'false'); 151 | element.setAttribute('truthy', 1); 152 | element.setAttribute('num', '4'); 153 | state = reactive(element, {falsy: false, truthy: true, num: 1}, () => invoked = true); 154 | console.assert(element.falsy === false); 155 | console.assert(element.truthy === true); 156 | console.assert(element.num === 4); 157 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | reactive-state 7 | 8 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reactive-props 2 | 3 | [![Build Status](https://travis-ci.com/WebReflection/reactive-props.svg?branch=master)](https://travis-ci.com/WebReflection/reactive-props) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/reactive-props/badge.svg?branch=master)](https://coveralls.io/github/WebReflection/reactive-props?branch=master) 4 | 5 | An all-in-one implementation of the [Reactive State for Data & DOM](https://medium.com/@WebReflection/reactive-state-for-data-dom-78332ddafd0e) patterns, compatible down to IE9. 6 | 7 | 8 | ### Live Examples 9 | 10 | * **[test page](https://webreflection.github.io/reactive-props/test/)** to be sure your target browser is compatible (IE9+) 11 | * **[wickedElements](https://github.com/WebReflection/wicked-elements#readme)** usage [live demo](https://codepen.io/WebReflection/pen/RwaYzjE) 12 | * **[µce](https://github.com/WebReflection/uce#readme)** usage [live demo](https://codepen.io/WebReflection/pen/LYNJwoV) 13 | * **[µland](https://github.com/WebReflection/uland#readme)** usage [live demo](https://codepen.io/WebReflection/pen/YzqOoRB) 14 | * **[hookedElements](https://github.com/WebReflection/hooked-elements#readme)** usage [live demo](https://codepen.io/WebReflection/pen/qBZMJeX) 15 | 16 | 17 | ## API 18 | 19 | This module exports a default helper function that can create utilities to define reactive properties / states for data or DOM elements. 20 | For documentation sake, this function will be named `createHandler`, and it accepts an optional configuration object, with the following properties: 21 | 22 | * `all:boolean`, signals that all set properties should invoke the related update. If `true`, even if a property has the same value it had before, the related update function will be invoked. 23 | * `shallow:boolean`, signals that that even if the property value is the same, the update should happen in case it's the same object, or the same array, set before. If `false`, and `all = false` too (default), no update happens in case the object is the exact same as before. 24 | * `dom:boolean`, signals that properties will be attached to a DOM element, which needs to be passed along. By default, the returned utility to create reactive properties has a `(props[, update])` signature, but when `dom = true`, the returned helper will have a `(element, props[, update])` signature. By default, `dom` is `false. 25 | * `getAttribute(element, key):any` is an optional helper to retrieve the right value when `dom = true` and the element already had an attribute with the reactive property name. `` will pass to this helper the `element` reference and the `checked` attribute name. By default, this helper returns `element.getAttribute("checked")`, but it is possible to return `JSON.parse(element.getAttribute("checked"))` instead, so that the initial `element.checked` will return a proper boolean value. 26 | * `useState(value):void` is an optional helper that accepts any generic `useState` handler from any *hooks* based library. If provided, it will be invoked passing along the new value when all conditions are match (see previous `all` and `shallow` description) 27 | 28 | The resulting helper returns either the `state` object with reactive properties, or the passed `element`. 29 | 30 | ```js 31 | // for reactive states 32 | const reactiveProps = createHandler(); 33 | const state = reactiveProps({...}, update); 34 | 35 | // for reactive elements 36 | const reactiveElement = createHandler({dom: true}); 37 | const el = reactiveElement(document.querySelector('el'), {...}, update); 38 | ``` 39 | 40 | If an `update` function is provided, it will be used to invoke state changes per each update, bypassing the possible `useState`. 41 | 42 | 43 | ```js 44 | import {useState} from 'augmentor'; 45 | 46 | const reactiveProps = createHandler({useState}); 47 | const state = reactiveProps({test: ''}); 48 | state.test = 'OK'; 49 | // will invoke useState('OK') 50 | 51 | const overload = reactiveProps({test: ''}, console.log); 52 | overload.test = 'OK'; 53 | // will simply log "OK" without invoking useState("OK") 54 | ``` 55 | 56 | 57 | #### Default Use Cases 58 | 59 | The default value goal of all options is to cover these common use cases: 60 | 61 | * *primitive properties* that would trigger updates only if different 62 | * *non immutable data* that would trigger updates if properties are objects/arrays. Use `shallow = false` option if data is granted to be immutable deep down each inner value 63 | * *integrated hooks* to work within a variety of libraries that offer a `useState` hook 64 | 65 | For any other combined use case, please refer to [the related post](https://medium.com/@WebReflection/reactive-state-for-data-dom-78332ddafd0e) and find out your fine tuned reactive state handler. 66 | 67 | 68 | #### Partial Imports 69 | 70 | If all you need is either the *state handler* or the *dom handler*, it is possible to import just those two separately, resulting in a slightly smaller bundle. 71 | 72 | ```js 73 | // const genericHandler = require('reactive-props'); 74 | import genericHandler from 'reactive-props'; 75 | 76 | // const domHandler = require('reactive-props/dom'); 77 | import domHandler from 'reactive-props/dom'; 78 | 79 | // produces same results 80 | domHandler(); 81 | genericHandler({dom: true}); 82 | 83 | // const stateHandler = require('reactive-props/state'); 84 | import stateHandler from 'reactive-props/state'; 85 | 86 | // produces same results 87 | stateHandler(); 88 | genericHandler({dom: false}); 89 | ``` 90 | 91 | 92 | ### Basic Example 93 | 94 | ```js 95 | // const createHandler = require('reactive-props'); 96 | import createHandler from 'reactive-props'; 97 | 98 | const reactiveProps = createHandler(); 99 | const reactiveElement = createHandler({dom: true}); 100 | 101 | // create reactive props 102 | const state = reactiveProps( 103 | // props to react for 104 | {test: ''}, 105 | // called on each prop update 106 | () => console.log(state) 107 | ); 108 | 109 | state.test; // "" 110 | state.test = 'value'; // {"test":"value"} 111 | 112 | // create reactive elements 113 | const body = reactiveElement( 114 | document.body, 115 | {test: ''}, 116 | () => console.log('body.test', body.test) 117 | ); 118 | 119 | body.test; // "" 120 | body.test = 'value'; // body.test "value" 121 | ``` 122 | --------------------------------------------------------------------------------