├── .babelrc ├── .gitignore ├── README.md ├── build ├── reactive-react.es.js └── reactive-react.umd.js ├── demos ├── components │ ├── blink.js │ ├── button.css │ ├── counter.js │ ├── crappybird.js │ ├── index.js │ ├── sourceswitching.js │ └── timer.js ├── custom.css ├── eleventy.css ├── index.html ├── index.js └── time-slicing │ ├── Charts.js │ ├── Clock.js │ ├── README.md │ ├── index.css │ ├── index.html │ └── index.js ├── dependencygraph.svg ├── netlify.toml ├── package.json ├── prototypeAPI.js ├── reactive-react ├── component.js ├── element.js ├── index.js ├── reconciler.js ├── scheduler.js ├── swyxjs.js └── updateProperties.js └── rollup.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-object-rest-spread", 4 | "transform-class-properties", 5 | [ 6 | "transform-react-jsx", 7 | {} 8 | ] 9 | ], 10 | "presets": [ 11 | [ 12 | "env", 13 | { 14 | "targets": { 15 | "node": "current" 16 | } 17 | } 18 | ] 19 | ] 20 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | yarn.lock 4 | .cache -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a prototype mockup of a "Reactive" version of React. Do not use unless you are swyx. 2 | 3 | ## Watch the React Rally talk 4 | 5 | Talk video: https://www.youtube.com/watch?v=nyFHR0dDZo0 6 | 7 | TL;DR with writeup: https://www.swyx.io/ReactRally 8 | 9 | ## Description 10 | 11 | **Note: we are aware of the double subscribe bug when a source is switched. didnt have time to figure out the fix before react rally. Blink tag example has a hacky patch for this.** 12 | 13 | In this alternate universe, Observables became a part of Javascript. 14 | 15 | We take a minimal implementation of Observables, zen-observable. 16 | 17 | 18 | # Try it out 19 | 20 | `yarn start` to run the demo locally 21 | 22 | # The `reactive-react` API 23 | 24 | The React API we are targeting looks something like this (see `/demos` for actual examples): 25 | 26 | ```js 27 | class Counter extends Component { 28 | // demonstrate basic counter 29 | initialState = 0 30 | increment = createHandler(e => 1) 31 | decrement = createHandler(e => -1) 32 | source($) { 33 | const source = merge(this.increment.$, this.decrement.$) 34 | const reducer = (acc, n) => acc + n 35 | return {source, reducer} 36 | } 37 | render(state, stateMap) { 38 | const {name = "counter"} = this.props 39 | return
40 | {name}: {state} 41 | 42 | 43 |
44 | } 45 | } 46 | 47 | // taking info from event handler 48 | class Echo extends Component { 49 | handler = createHandler(e => e.target.value) 50 | initialState = 'hello world' 51 | source($) { 52 | const source = this.handler.$ 53 | const reducer = (acc, n) => n 54 | return {source, reducer} 55 | } 56 | render(state, prevState) { 57 | return
58 | 59 | {state} 60 |
61 | } 62 | } 63 | 64 | class Timer extends Component { 65 | // demonstrate interval time 66 | initialState = 0 67 | source($) { 68 | const reducer = x => x + 1 // count up 69 | const source = Interval(this.props.ms) // tick every second 70 | // source returns an observable 71 | return scan(source, reducer, 0) // from zero 72 | } 73 | render(state, stateMap) { 74 | return
number of seconds elapsed: {state}
75 | } 76 | } 77 | 78 | class Blink extends Component { 79 | // more fun time demo 80 | initialState = true 81 | source($) { 82 | const reducer = x => !x 83 | // tick every ms milliseconds 84 | const source = Interval(this.props.ms) 85 | // source can also return an observable 86 | return scan(source, reducer, true) 87 | } 88 | render(state) { 89 | const style = {display: state ? 'block' : 'none'} 90 | return
Bring back the blink tag!
91 | } 92 | } 93 | 94 | class CrappyBird extends Component { 95 | // merging time and counter 96 | initialState = { 97 | input: 50, 98 | target: 50 99 | } 100 | increment = createHandler(e => 3) 101 | source($) { 102 | return this.combineReducer({ 103 | input: () => { 104 | const source = merge(this.increment.$, Interval(200,-1)) 105 | const reducer = (acc, x) => Math.max(0,acc + x) 106 | return {source, reducer} 107 | }, 108 | target: () => { 109 | const source = Interval(200) 110 | const reducer = (acc) => { 111 | const int = acc + Math.random() * 8 - 4 112 | return int - (int-50)/30 // bias toward 50 113 | } 114 | return {source, reducer} 115 | } 116 | }) 117 | } 118 | render(state, stateMap) { 119 | const {input, target} = state 120 | return
121 | 122 |

Crappy bird

123 |

Bird:

124 |

Target:

125 |
126 | } 127 | } 128 | 129 | function Counters() { 130 | // demonstrate independent states 131 | return
132 | 133 | 134 |
135 | } 136 | function Source() { 137 | // demonstrate ability to switch sources 138 | return } 140 | right={} 141 | /> 142 | } 143 | 144 | class SourceSwitching extends Component { 145 | initialState = true 146 | toggle = createHandler() 147 | source($) { 148 | const source = this.toggle.$ 149 | const reducer = x => !x 150 | return {source, reducer} 151 | } 152 | render(state, stateMap) { 153 | return
154 | 155 | { 156 | state ? this.props.left : this.props.right 157 | } 158 |
159 | } 160 | } 161 | 162 | ``` 163 | 164 | # Local development 165 | 166 | `yarn run build` and then `npm publish` (but its under my namespace @swyx/reactive-react cos someone else has the generic one) 167 | 168 | Here is the project structure: 169 | 170 | ![dependencygraph.svg](dependencygraph.svg) 171 | -------------------------------------------------------------------------------- /build/reactive-react.es.js: -------------------------------------------------------------------------------- 1 | import Observable from 'zen-observable'; 2 | import { createChangeEmitter } from 'change-emitter'; 3 | import { merge } from 'zen-observable/extras'; 4 | import h from 'virtual-dom/h'; 5 | import VText from 'virtual-dom/vnode/vtext'; 6 | import diff from 'virtual-dom/diff'; 7 | import patch from 'virtual-dom/patch'; 8 | import createElement from 'virtual-dom/create-element'; 9 | 10 | var TEXT_ELEMENT = "TEXT ELEMENT"; 11 | 12 | function createElement$1(type, config) { 13 | var _ref; 14 | 15 | var props = Object.assign({}, config); 16 | 17 | for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { 18 | args[_key - 2] = arguments[_key]; 19 | } 20 | 21 | var hasChildren = args.length > 0; 22 | var rawChildren = hasChildren ? (_ref = []).concat.apply(_ref, args) : []; 23 | props.children = rawChildren.filter(function (c) { 24 | return c != null && c !== false; 25 | }).map(function (c) { 26 | return c instanceof Object ? c : createTextElement(c); 27 | }); 28 | return { type: type, props: props }; 29 | } 30 | 31 | function createTextElement(value) { 32 | return createElement$1(TEXT_ELEMENT, { nodeValue: value }); 33 | } 34 | 35 | function createHandler(_fn) { 36 | var emitter = createChangeEmitter(); 37 | var handler = function handler(x) { 38 | emitter.emit(x); 39 | }; 40 | handler.$ = new Observable(function (observer) { 41 | return emitter.listen(function (value) { 42 | observer.next(_fn ? _fn(value) : value); 43 | }); 44 | }); 45 | return handler; 46 | } 47 | 48 | var NOINIT = Symbol('NO_INITIAL_VALUE'); 49 | function scan(obs, cb) { 50 | var seed = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : NOINIT; 51 | 52 | var sub = void 0, 53 | acc = seed, 54 | hasValue = false; 55 | var hasSeed = acc !== NOINIT; 56 | return new Observable(function (observer) { 57 | sub = obs.subscribe(function (value) { 58 | if (observer.closed) return; 59 | var first = !hasValue; 60 | hasValue = true; 61 | if (!first || hasSeed) { 62 | try { 63 | acc = cb(acc, value); 64 | } catch (e) { 65 | return observer.error(e); 66 | } 67 | observer.next(acc); 68 | } else { 69 | acc = value; 70 | } 71 | }); 72 | return sub; 73 | }); 74 | } 75 | 76 | // Flatten a collection of observables and only output the newest from each 77 | 78 | 79 | 80 | 81 | function startWith(obs, val) { 82 | return new Observable(function (observer) { 83 | observer.next(val); // immediately output this value 84 | var handler = obs.subscribe(function (x) { 85 | return observer.next(x); 86 | }); 87 | return function () { 88 | return handler(); 89 | }; 90 | }); 91 | } 92 | 93 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 94 | 95 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 96 | 97 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 98 | 99 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 100 | 101 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 102 | 103 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 104 | 105 | // import { reconcile } from "./reconciler"; 106 | var Component = function () { 107 | function Component(props) { 108 | _classCallCheck(this, Component); 109 | 110 | this.props = props; 111 | this.state = this.state || {}; 112 | } 113 | 114 | // setState(partialState) { 115 | // this.state = Object.assign({}, this.state, partialState); 116 | // updateInstance(this.__internalInstance); 117 | // } 118 | 119 | // class method because it feeds in this.initialState 120 | 121 | 122 | _createClass(Component, [{ 123 | key: 'combineReducer', 124 | value: function combineReducer(obj) { 125 | var _this = this; 126 | 127 | var sources = Object.entries(obj).map(function (_ref) { 128 | var _ref2 = _slicedToArray(_ref, 2), 129 | k = _ref2[0], 130 | fn = _ref2[1]; 131 | 132 | var subReducer = fn(obj); 133 | // there are two forms of return the subreducer can have 134 | // straight stream form 135 | // or object form where we need to scan it into string 136 | if (subReducer.source && subReducer.reducer) { 137 | // object form 138 | subReducer = scan(subReducer.source, subReducer.reducer || function (_, n) { 139 | return n; 140 | }, _this.initialState[k]); 141 | } 142 | return subReducer.map(function (x) { 143 | return _defineProperty({}, k, x); 144 | }); // map to its particular namespace 145 | }); 146 | var source = merge.apply(undefined, _toConsumableArray(sources)); 147 | var reducer = function reducer(acc, n) { 148 | return _extends({}, acc, n); 149 | }; 150 | return { source: source, reducer: reducer }; 151 | } 152 | }]); 153 | 154 | return Component; 155 | }(); 156 | 157 | // function updateInstance(internalInstance) { 158 | // const parentDom = internalInstance.dom.parentNode; 159 | // const element = internalInstance.element; 160 | // reconcile(parentDom, internalInstance, element); 161 | // } 162 | 163 | function createPublicInstance(element /*, internalInstance*/) { 164 | var type = element.type, 165 | props = element.props; 166 | 167 | var publicInstance = new type(props); 168 | // publicInstance.__internalInstance = internalInstance; 169 | return publicInstance; 170 | } 171 | 172 | var _slicedToArray$1 = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 173 | 174 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 175 | 176 | // import { updateDomProperties } from "./updateProperties"; 177 | // import VNode from "virtual-dom/vnode/vnode" 178 | // const circuitBreakerflag = false // set true to enable debugger in infinite loops 179 | // let circuitBreaker = -50 180 | // traverse all children and collect a stream of all sources 181 | // AND render. a bit of duplication, but we get persistent instances which is good 182 | function renderStream(element, instance, state, stateMap) { 183 | // this is a separate function because scope gets messy when being recursive 184 | var isNewStream = false; // assume no stream switching by default 185 | // this is the first ping of data throughout the app 186 | var source = Observable.of(state); 187 | var addToStream = function addToStream(_source) { 188 | // visit each source and merge with source 189 | if (_source) return source = merge(source, _source); 190 | }; 191 | var markNewStream = function markNewStream() { 192 | return isNewStream = true; 193 | }; 194 | var newInstance = render(source, addToStream, markNewStream)(element, instance, state, stateMap); 195 | return { source: source, instance: newInstance, isNewStream: isNewStream }; 196 | } 197 | 198 | /** core render logic */ 199 | function render(source, addToStream, markNewStream) { 200 | // this is the nonrecursive part 201 | return function renderWithStream(element, instance, state, stateMap) { 202 | // recursive part 203 | var newInstance = void 0; 204 | var type = element.type, 205 | props = element.props; 206 | 207 | 208 | var isDomElement = typeof type === "string"; 209 | // if (circuitBreakerflag && circuitBreaker++ > 0) debugger 210 | 211 | var _props$children = props.children, 212 | children = _props$children === undefined ? [] : _props$children, 213 | rest = _objectWithoutProperties(props, ['children']); 214 | 215 | if (isDomElement) { 216 | var childInstances = children.map(function (el, i) { 217 | // ugly but necessary to allow functional children 218 | // mapping element's children to instance's childInstances 219 | var _childInstances = instance && (instance.childInstance || instance.childInstances[i]); 220 | return renderWithStream( // recursion 221 | el, _childInstances, state, stateMap); 222 | }); 223 | var childDoms = childInstances.map(function (childInstance) { 224 | return childInstance.dom; 225 | }); 226 | var lcaseProps = {}; 227 | Object.entries(rest).forEach(function (_ref) { 228 | var _ref2 = _slicedToArray$1(_ref, 2), 229 | k = _ref2[0], 230 | v = _ref2[1]; 231 | 232 | return lcaseProps[formatProps(k)] = v; 233 | }); 234 | var dom = type === TEXT_ELEMENT ? new VText(props.nodeValue) : h(type, lcaseProps, childDoms); // equivalent of appendchild 235 | newInstance = { dom: dom, element: element, childInstances: childInstances }; 236 | } else { 237 | // component element 238 | var publicInstance = void 0; 239 | // debugger 240 | if (instance && instance.publicInstance && instance.element === element) { 241 | // might have to do more diffing of props 242 | // just reuse old instance if it already exists 243 | publicInstance = instance && instance.publicInstance; 244 | } else { 245 | markNewStream(); // mark as dirty in parent scope; will rerender 246 | publicInstance = createPublicInstance(element); 247 | } 248 | var localState = stateMap.get(publicInstance); 249 | if (localState === undefined) localState = publicInstance.initialState; 250 | publicInstance.state = localState; // for access with this.state 251 | if (Object.keys(rest).length) publicInstance.props = rest; // update with new props // TODO: potentially buggy 252 | // console.log({rest}) 253 | if (publicInstance.source) { 254 | var src = publicInstance.source(source); 255 | // there are two forms of Component.source 256 | var src$ = src.reducer && publicInstance.initialState !== undefined ? 257 | // 1. the reducer form 258 | scan(src.source, src.reducer, publicInstance.initialState) : 259 | // 2. and raw stream form 260 | src; 261 | addToStream(src$.map(function (event) { 262 | stateMap.set(publicInstance, event); 263 | return { instance: publicInstance, event: event // tag it to the instance 264 | }; 265 | })); 266 | } 267 | var childElement = publicInstance.render ? publicInstance.render(localState, stateMap) : publicInstance; 268 | 269 | var childInstance = renderWithStream(childElement, instance && instance.childInstance, state, stateMap); 270 | var _dom = childInstance.dom; 271 | newInstance = { dom: _dom, element: element, childInstance: childInstance, publicInstance: publicInstance }; 272 | } 273 | return newInstance; 274 | }; 275 | } 276 | 277 | function formatProps(k) { 278 | if (k.startsWith('on')) return k.toLowerCase(); 279 | return k; 280 | } 281 | 282 | var stateMapPointer = new Map(); 283 | 284 | var emitter = createChangeEmitter(); 285 | // single UI thread; this is the observable that sticks around and swaps out source 286 | var UIthread = new Observable(function (observer) { 287 | emitter.listen(function (x) { 288 | // debugger // success! thread switching! 289 | observer.next(x); 290 | }); 291 | }); 292 | // mount the vdom on to the dom and 293 | // set up the runtime from sources and 294 | // patch the vdom 295 | // --- 296 | // returns an unsubscribe method you can use to unmount 297 | function mount(rootElement, container) { 298 | // initial, throwaway-ish frame 299 | var _renderStream = renderStream(rootElement, {}, undefined, stateMapPointer), 300 | source = _renderStream.source, 301 | instance = _renderStream.instance; 302 | 303 | var instancePointer = instance; 304 | var rootNode = createElement(instance.dom); 305 | var containerChild = container.firstElementChild; 306 | if (containerChild) { 307 | container.replaceChild(rootNode, containerChild); // hot reloaded mount 308 | } else { 309 | container.appendChild(rootNode); // initial mount 310 | } 311 | var currentSrc$ = null; 312 | var SoS = startWith(UIthread, source); // stream of streams 313 | return SoS.subscribe(function (src$) { 314 | // this is the current sourceStream we are working with 315 | if (currentSrc$) console.log('unsub!') || currentSrc$.unsubscribe(); // unsub from old stream 316 | /**** main */ 317 | var source2$ = scan(src$, function (_ref, nextState) { 318 | var instance = _ref.instance, 319 | stateMap = _ref.stateMap; 320 | 321 | var streamOutput = renderStream(rootElement, instance, nextState, stateMap); 322 | if (streamOutput.isNewStream) { 323 | // quick check 324 | var nextSource$ = streamOutput.source; 325 | // debugger 326 | instancePointer = streamOutput.instance; 327 | patch(rootNode, diff(instance.dom, instancePointer.dom)); // render to screen 328 | emitter.emit(nextSource$); // update the UI thread; source will switch 329 | } else { 330 | var nextinstance = streamOutput.instance; 331 | patch(rootNode, diff(instance.dom, nextinstance.dom)); // render to screen 332 | return { instance: nextinstance, stateMap: stateMap }; 333 | } 334 | }, { instance: instancePointer, stateMap: stateMapPointer // accumulator 335 | }); 336 | /**** end main */ 337 | currentSrc$ = source2$.subscribe(); 338 | }); 339 | } 340 | 341 | var index = { 342 | renderStream: renderStream, 343 | createElement: createElement$1, 344 | createHandler: createHandler, 345 | Component: Component, 346 | mount: mount 347 | }; 348 | 349 | export { createElement$1 as createElement, createHandler, Component, renderStream, mount };export default index; 350 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVhY3RpdmUtcmVhY3QuZXMuanMiLCJzb3VyY2VzIjpbIi4uL3JlYWN0aXZlLXJlYWN0L2VsZW1lbnQuanMiLCIuLi9yZWFjdGl2ZS1yZWFjdC9zd3l4anMuanMiLCIuLi9yZWFjdGl2ZS1yZWFjdC9jb21wb25lbnQuanMiLCIuLi9yZWFjdGl2ZS1yZWFjdC9yZWNvbmNpbGVyLmpzIiwiLi4vcmVhY3RpdmUtcmVhY3Qvc2NoZWR1bGVyLmpzIiwiLi4vcmVhY3RpdmUtcmVhY3QvaW5kZXguanMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IE9ic2VydmFibGUgZnJvbSAnemVuLW9ic2VydmFibGUnXG5pbXBvcnQgeyBjcmVhdGVDaGFuZ2VFbWl0dGVyIH0gZnJvbSAnY2hhbmdlLWVtaXR0ZXInXG5cbmV4cG9ydCBjb25zdCBURVhUX0VMRU1FTlQgPSBcIlRFWFQgRUxFTUVOVFwiO1xuXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlRWxlbWVudCh0eXBlLCBjb25maWcsIC4uLmFyZ3MpIHtcbiAgY29uc3QgcHJvcHMgPSBPYmplY3QuYXNzaWduKHt9LCBjb25maWcpO1xuICBjb25zdCBoYXNDaGlsZHJlbiA9IGFyZ3MubGVuZ3RoID4gMDtcbiAgY29uc3QgcmF3Q2hpbGRyZW4gPSBoYXNDaGlsZHJlbiA/IFtdLmNvbmNhdCguLi5hcmdzKSA6IFtdO1xuICBwcm9wcy5jaGlsZHJlbiA9IHJhd0NoaWxkcmVuXG4gICAgLmZpbHRlcihjID0+IGMgIT0gbnVsbCAmJiBjICE9PSBmYWxzZSlcbiAgICAubWFwKGMgPT4gYyBpbnN0YW5jZW9mIE9iamVjdCA/IGMgOiBjcmVhdGVUZXh0RWxlbWVudChjKSk7XG4gIHJldHVybiB7IHR5cGUsIHByb3BzIH07XG59XG5cbmZ1bmN0aW9uIGNyZWF0ZVRleHRFbGVtZW50KHZhbHVlKSB7XG4gIHJldHVybiBjcmVhdGVFbGVtZW50KFRFWFRfRUxFTUVOVCwgeyBub2RlVmFsdWU6IHZhbHVlIH0pO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlSGFuZGxlcihfZm4pIHtcbiAgY29uc3QgZW1pdHRlciA9IGNyZWF0ZUNoYW5nZUVtaXR0ZXIoKVxuICBsZXQgaGFuZGxlciA9IHggPT4ge1xuICAgIGVtaXR0ZXIuZW1pdCh4KVxuICB9XG4gIGhhbmRsZXIuJCA9IG5ldyBPYnNlcnZhYmxlKG9ic2VydmVyID0+IHtcbiAgICByZXR1cm4gZW1pdHRlci5saXN0ZW4odmFsdWUgPT4ge1xuICAgICAgb2JzZXJ2ZXIubmV4dChfZm4gPyBfZm4odmFsdWUpIDogdmFsdWUpXG4gICAgfVxuICAgIClcbiAgfSlcbiAgcmV0dXJuIGhhbmRsZXJcbn0iLCJpbXBvcnQgT2JzZXJ2YWJsZSBmcm9tICd6ZW4tb2JzZXJ2YWJsZSdcbmV4cG9ydCB7IG1lcmdlLCBjb21iaW5lTGF0ZXN0LCB6aXAgfSBmcm9tICd6ZW4tb2JzZXJ2YWJsZS9leHRyYXMnXG5cbmV4cG9ydCBmdW5jdGlvbiBJbnRlcnZhbCh0aWNrID0gMTAwMCwgdGlja0RhdGEgPSBTeW1ib2woJ3RpY2snKSkge1xuICByZXR1cm4gbmV3IE9ic2VydmFibGUob2JzZXJ2ZXIgPT4ge1xuICAgIGxldCB0aW1lciA9ICgpID0+IHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgaWYgKHR5cGVvZiB0aWNrRGF0YSA9PT0gJ2Z1bmN0aW9uJykgdGlja0RhdGEgPSB0aWNrRGF0YSgpXG4gICAgICBvYnNlcnZlci5uZXh0KHRpY2tEYXRhKTtcbiAgICAgIHRpbWVyKClcbiAgICAgIC8vIG9ic2VydmVyLmNvbXBsZXRlKCk7XG4gICAgfSwgdGljayk7XG4gICAgdGltZXIoKVxuICBcbiAgICAvLyBPbiB1bnN1YnNjcmlwdGlvbiwgY2FuY2VsIHRoZSB0aW1lclxuICAgIHJldHVybiAoKSA9PiBjbGVhclRpbWVvdXQodGltZXIpO1xuXG4gIH0pXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBmcm9tRXZlbnQoZWwsIGV2ZW50VHlwZSkge1xuICByZXR1cm4gbmV3IE9ic2VydmFibGUob2JzZXJ2ZXIgPT4ge1xuICAgIGNvbnN0IGhhbmRsZXIgPSBlID0+IG9ic2VydmVyLm5leHQoZSlcbiAgICBlbC5hZGRFdmVudExpc3RlbmVyKGV2ZW50VHlwZSwgaGFuZGxlcilcbiAgICAvLyBvbiB1bnN1YiwgcmVtb3ZlIGV2ZW50IGxpc3RlbmVyXG4gICAgcmV0dXJuICgpID0+IGVsLnJlbW92ZUV2ZW50TGlzdGVuZXIoZXZlbnRUeXBlLCBoYW5kbGVyKVxuICB9KVxufVxuXG5jb25zdCBOT0lOSVQgPSBTeW1ib2woJ05PX0lOSVRJQUxfVkFMVUUnKVxuZXhwb3J0IGZ1bmN0aW9uIHNjYW4ob2JzLCBjYiwgc2VlZCA9IE5PSU5JVCkge1xuICBsZXQgc3ViLCBhY2MgPSBzZWVkLCBoYXNWYWx1ZSA9IGZhbHNlXG4gIGNvbnN0IGhhc1NlZWQgPSBhY2MgIT09IE5PSU5JVFxuICByZXR1cm4gbmV3IE9ic2VydmFibGUob2JzZXJ2ZXIgPT4ge1xuICAgIHN1YiA9IG9icy5zdWJzY3JpYmUodmFsdWUgPT4ge1xuICAgICAgaWYgKG9ic2VydmVyLmNsb3NlZCkgcmV0dXJuXG4gICAgICBsZXQgZmlyc3QgPSAhaGFzVmFsdWU7XG4gICAgICBoYXNWYWx1ZSA9IHRydWVcbiAgICAgIGlmICghZmlyc3QgfHwgaGFzU2VlZCApIHtcbiAgICAgICAgdHJ5IHsgYWNjID0gY2IoYWNjLCB2YWx1ZSkgfVxuICAgICAgICBjYXRjaCAoZSkgeyByZXR1cm4gb2JzZXJ2ZXIuZXJyb3IoZSkgfVxuICAgICAgICBvYnNlcnZlci5uZXh0KGFjYyk7XG4gICAgICB9XG4gICAgICBlbHNlIHtcbiAgICAgICAgYWNjID0gdmFsdWVcbiAgICAgIH1cbiAgICB9KVxuICAgIHJldHVybiBzdWJcbiAgfSlcbn1cblxuLy8gRmxhdHRlbiBhIGNvbGxlY3Rpb24gb2Ygb2JzZXJ2YWJsZXMgYW5kIG9ubHkgb3V0cHV0IHRoZSBuZXdlc3QgZnJvbSBlYWNoXG5leHBvcnQgZnVuY3Rpb24gc3dpdGNoTGF0ZXN0KGhpZ2hlck9ic2VydmFibGUpIHtcbiAgcmV0dXJuIG5ldyBPYnNlcnZhYmxlKG9ic2VydmVyID0+IHtcbiAgICBsZXQgY3VycmVudE9icyA9IG51bGxcbiAgICByZXR1cm4gaGlnaGVyT2JzZXJ2YWJsZS5zdWJzY3JpYmUoe1xuICAgICAgbmV4dChvYnMpIHtcbiAgICAgICAgaWYgKGN1cnJlbnRPYnMpIGN1cnJlbnRPYnMudW5zdWJzY3JpYmUoKSAvLyB1bnN1YiBhbmQgc3dpdGNoXG4gICAgICAgIGN1cnJlbnRPYnMgPSBvYnMuc3Vic2NyaWJlKG9ic2VydmVyLnN1YnNjcmliZSlcbiAgICAgIH0sXG4gICAgICBlcnJvcihlKSB7XG4gICAgICAgIG9ic2VydmVyLmVycm9yKGUpIC8vIHVudGVzdGVkXG4gICAgICB9LFxuICAgICAgY29tcGxldGUoKSB7XG4gICAgICAgIC8vIGkgZG9udCB0aGluayBpdCBzaG91bGQgY29tcGxldGU/XG4gICAgICAgIC8vIG9ic2VydmVyLmNvbXBsZXRlKClcbiAgICAgIH1cbiAgICB9KVxuICB9KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIG1hcFRvQ29uc3RhbnQob2JzLCB2YWwpIHtcbiAgcmV0dXJuIG5ldyBPYnNlcnZhYmxlKG9ic2VydmVyID0+IHtcbiAgICBjb25zdCBoYW5kbGVyID0gb2JzLnN1YnNjcmliZSgoKSA9PiBvYnNlcnZlci5uZXh0KHZhbCkpXG4gICAgcmV0dXJuIGhhbmRsZXJcbiAgfSlcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHN0YXJ0V2l0aChvYnMsIHZhbCkge1xuICByZXR1cm4gbmV3IE9ic2VydmFibGUob2JzZXJ2ZXIgPT4ge1xuICAgIG9ic2VydmVyLm5leHQodmFsKSAvLyBpbW1lZGlhdGVseSBvdXRwdXQgdGhpcyB2YWx1ZVxuICAgIGNvbnN0IGhhbmRsZXIgPSBvYnMuc3Vic2NyaWJlKHggPT4gb2JzZXJ2ZXIubmV4dCh4KSlcbiAgICByZXR1cm4gKCkgPT4gaGFuZGxlcigpXG4gIH0pXG59IiwiLy8gaW1wb3J0IHsgcmVjb25jaWxlIH0gZnJvbSBcIi4vcmVjb25jaWxlclwiO1xuaW1wb3J0IHtJbnRlcnZhbCwgc2Nhbiwgc3RhcnRXaXRoLCBtZXJnZSwgbWFwVG9Db25zdGFudH0gZnJvbSAnLi9zd3l4anMnXG5cbmV4cG9ydCBjbGFzcyBDb21wb25lbnQge1xuICBjb25zdHJ1Y3Rvcihwcm9wcykge1xuICAgIHRoaXMucHJvcHMgPSBwcm9wcztcbiAgICB0aGlzLnN0YXRlID0gdGhpcy5zdGF0ZSB8fCB7fTtcbiAgfVxuXG4gIC8vIHNldFN0YXRlKHBhcnRpYWxTdGF0ZSkge1xuICAvLyAgIHRoaXMuc3RhdGUgPSBPYmplY3QuYXNzaWduKHt9LCB0aGlzLnN0YXRlLCBwYXJ0aWFsU3RhdGUpO1xuICAvLyAgIHVwZGF0ZUluc3RhbmNlKHRoaXMuX19pbnRlcm5hbEluc3RhbmNlKTtcbiAgLy8gfVxuXG4gIC8vIGNsYXNzIG1ldGhvZCBiZWNhdXNlIGl0IGZlZWRzIGluIHRoaXMuaW5pdGlhbFN0YXRlXG4gIGNvbWJpbmVSZWR1Y2VyKG9iaikge1xuICAgIGNvbnN0IHNvdXJjZXMgPSBPYmplY3QuZW50cmllcyhvYmopLm1hcCgoW2ssZm5dKSA9PiB7XG4gICAgICBsZXQgc3ViUmVkdWNlciA9IGZuKG9iailcbiAgICAgIC8vIHRoZXJlIGFyZSB0d28gZm9ybXMgb2YgcmV0dXJuIHRoZSBzdWJyZWR1Y2VyIGNhbiBoYXZlXG4gICAgICAvLyBzdHJhaWdodCBzdHJlYW0gZm9ybVxuICAgICAgLy8gb3Igb2JqZWN0IGZvcm0gd2hlcmUgd2UgbmVlZCB0byBzY2FuIGl0IGludG8gc3RyaW5nXG4gICAgICBpZiAoc3ViUmVkdWNlci5zb3VyY2UgJiYgc3ViUmVkdWNlci5yZWR1Y2VyKSB7IC8vIG9iamVjdCBmb3JtXG4gICAgICAgIHN1YlJlZHVjZXIgPSBzY2FuKHN1YlJlZHVjZXIuc291cmNlLCBcbiAgICAgICAgICBzdWJSZWR1Y2VyLnJlZHVjZXIgfHwgKChfLCBuKSA9PiBuKSwgXG4gICAgICAgICAgdGhpcy5pbml0aWFsU3RhdGVba11cbiAgICAgICAgKVxuICAgICAgfVxuICAgICAgcmV0dXJuIHN1YlJlZHVjZXJcbiAgICAgICAgLm1hcCh4ID0+ICh7W2tdOiB4fSkpIC8vIG1hcCB0byBpdHMgcGFydGljdWxhciBuYW1lc3BhY2VcbiAgICB9KVxuICAgIGNvbnN0IHNvdXJjZSA9IG1lcmdlKC4uLnNvdXJjZXMpXG4gICAgY29uc3QgcmVkdWNlciA9IChhY2MsIG4pID0+ICh7Li4uYWNjLCAuLi5ufSlcbiAgICByZXR1cm4ge3NvdXJjZSwgcmVkdWNlcn1cbiAgfVxufVxuXG4vLyBmdW5jdGlvbiB1cGRhdGVJbnN0YW5jZShpbnRlcm5hbEluc3RhbmNlKSB7XG4vLyAgIGNvbnN0IHBhcmVudERvbSA9IGludGVybmFsSW5zdGFuY2UuZG9tLnBhcmVudE5vZGU7XG4vLyAgIGNvbnN0IGVsZW1lbnQgPSBpbnRlcm5hbEluc3RhbmNlLmVsZW1lbnQ7XG4vLyAgIHJlY29uY2lsZShwYXJlbnREb20sIGludGVybmFsSW5zdGFuY2UsIGVsZW1lbnQpO1xuLy8gfVxuXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlUHVibGljSW5zdGFuY2UoZWxlbWVudC8qLCBpbnRlcm5hbEluc3RhbmNlKi8pIHtcbiAgY29uc3QgeyB0eXBlLCBwcm9wcyB9ID0gZWxlbWVudDtcbiAgY29uc3QgcHVibGljSW5zdGFuY2UgPSBuZXcgdHlwZShwcm9wcyk7XG4gIC8vIHB1YmxpY0luc3RhbmNlLl9faW50ZXJuYWxJbnN0YW5jZSA9IGludGVybmFsSW5zdGFuY2U7XG4gIHJldHVybiBwdWJsaWNJbnN0YW5jZTtcbn1cbiIsImltcG9ydCBPYnNlcnZhYmxlIGZyb20gJ3plbi1vYnNlcnZhYmxlJ1xuaW1wb3J0IHtmcm9tRXZlbnQsIHNjYW4sIG1lcmdlLCBzdGFydFdpdGgsIHN3aXRjaExhdGVzdH0gZnJvbSAnLi9zd3l4anMnXG4vLyBpbXBvcnQgeyB1cGRhdGVEb21Qcm9wZXJ0aWVzIH0gZnJvbSBcIi4vdXBkYXRlUHJvcGVydGllc1wiO1xuaW1wb3J0IHsgVEVYVF9FTEVNRU5UIH0gZnJvbSBcIi4vZWxlbWVudFwiO1xuaW1wb3J0IHsgY3JlYXRlUHVibGljSW5zdGFuY2UgfSBmcm9tIFwiLi9jb21wb25lbnRcIjtcbmltcG9ydCBoIGZyb20gJ3ZpcnR1YWwtZG9tL2gnXG4vLyBpbXBvcnQgVk5vZGUgZnJvbSBcInZpcnR1YWwtZG9tL3Zub2RlL3Zub2RlXCJcbmltcG9ydCBWVGV4dCBmcm9tIFwidmlydHVhbC1kb20vdm5vZGUvdnRleHRcIlxuXG4vLyBjb25zdCBjaXJjdWl0QnJlYWtlcmZsYWcgPSBmYWxzZSAvLyBzZXQgdHJ1ZSB0byBlbmFibGUgZGVidWdnZXIgaW4gaW5maW5pdGUgbG9vcHNcbi8vIGxldCBjaXJjdWl0QnJlYWtlciA9IC01MFxuLy8gdHJhdmVyc2UgYWxsIGNoaWxkcmVuIGFuZCBjb2xsZWN0IGEgc3RyZWFtIG9mIGFsbCBzb3VyY2VzXG4vLyBBTkQgcmVuZGVyLiBhIGJpdCBvZiBkdXBsaWNhdGlvbiwgYnV0IHdlIGdldCBwZXJzaXN0ZW50IGluc3RhbmNlcyB3aGljaCBpcyBnb29kXG5leHBvcnQgZnVuY3Rpb24gcmVuZGVyU3RyZWFtKGVsZW1lbnQsIGluc3RhbmNlLCBzdGF0ZSwgc3RhdGVNYXApIHtcbiAgLy8gdGhpcyBpcyBhIHNlcGFyYXRlIGZ1bmN0aW9uIGJlY2F1c2Ugc2NvcGUgZ2V0cyBtZXNzeSB3aGVuIGJlaW5nIHJlY3Vyc2l2ZVxuICBsZXQgaXNOZXdTdHJlYW0gPSBmYWxzZSAvLyBhc3N1bWUgbm8gc3RyZWFtIHN3aXRjaGluZyBieSBkZWZhdWx0XG4gIC8vIHRoaXMgaXMgdGhlIGZpcnN0IHBpbmcgb2YgZGF0YSB0aHJvdWdob3V0IHRoZSBhcHBcbiAgbGV0IHNvdXJjZSA9IE9ic2VydmFibGUub2Yoc3RhdGUpIFxuICBjb25zdCBhZGRUb1N0cmVhbSA9IF9zb3VyY2UgPT4ge1xuICAgIC8vIHZpc2l0IGVhY2ggc291cmNlIGFuZCBtZXJnZSB3aXRoIHNvdXJjZVxuICAgIGlmIChfc291cmNlKSByZXR1cm4gc291cmNlID0gbWVyZ2Uoc291cmNlLCBfc291cmNlKVxuICB9XG4gIGNvbnN0IG1hcmtOZXdTdHJlYW0gPSAoKSA9PiBpc05ld1N0cmVhbSA9IHRydWVcbiAgY29uc3QgbmV3SW5zdGFuY2UgPSByZW5kZXIoc291cmNlLCBhZGRUb1N0cmVhbSwgbWFya05ld1N0cmVhbSkoZWxlbWVudCwgaW5zdGFuY2UsIHN0YXRlLCBzdGF0ZU1hcClcbiAgcmV0dXJuIHtzb3VyY2UsIGluc3RhbmNlOiBuZXdJbnN0YW5jZSwgaXNOZXdTdHJlYW19XG59XG5cbi8qKiBjb3JlIHJlbmRlciBsb2dpYyAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlbmRlcihzb3VyY2UsIGFkZFRvU3RyZWFtLCBtYXJrTmV3U3RyZWFtKSB7IC8vIHRoaXMgaXMgdGhlIG5vbnJlY3Vyc2l2ZSBwYXJ0XG4gIHJldHVybiBmdW5jdGlvbiByZW5kZXJXaXRoU3RyZWFtKGVsZW1lbnQsIGluc3RhbmNlLCBzdGF0ZSwgc3RhdGVNYXApIHsgLy8gcmVjdXJzaXZlIHBhcnRcbiAgICBsZXQgbmV3SW5zdGFuY2VcbiAgICBjb25zdCB7IHR5cGUsIHByb3BzIH0gPSBlbGVtZW50XG4gIFxuICAgIGNvbnN0IGlzRG9tRWxlbWVudCA9IHR5cGVvZiB0eXBlID09PSBcInN0cmluZ1wiO1xuICAgIC8vIGlmIChjaXJjdWl0QnJlYWtlcmZsYWcgJiYgY2lyY3VpdEJyZWFrZXIrKyA+IDApIGRlYnVnZ2VyXG4gICAgY29uc3Qge2NoaWxkcmVuID0gW10sIC4uLnJlc3R9ID0gcHJvcHNcbiAgICBpZiAoaXNEb21FbGVtZW50KSB7XG4gICAgICBjb25zdCBjaGlsZEluc3RhbmNlcyA9IGNoaWxkcmVuLm1hcChcbiAgICAgICAgKGVsLCBpKSA9PiB7XG4gICAgICAgICAgLy8gdWdseSBidXQgbmVjZXNzYXJ5IHRvIGFsbG93IGZ1bmN0aW9uYWwgY2hpbGRyZW5cbiAgICAgICAgICAvLyBtYXBwaW5nIGVsZW1lbnQncyBjaGlsZHJlbiB0byBpbnN0YW5jZSdzIGNoaWxkSW5zdGFuY2VzXG4gICAgICAgICAgY29uc3QgX2NoaWxkSW5zdGFuY2VzID0gaW5zdGFuY2UgJiYgKGluc3RhbmNlLmNoaWxkSW5zdGFuY2UgfHwgaW5zdGFuY2UuY2hpbGRJbnN0YW5jZXNbaV0pXG4gICAgICAgICAgcmV0dXJuIHJlbmRlcldpdGhTdHJlYW0oICAvLyByZWN1cnNpb25cbiAgICAgICAgICAgIGVsLCBcbiAgICAgICAgICAgIF9jaGlsZEluc3RhbmNlcywgXG4gICAgICAgICAgICBzdGF0ZSwgXG4gICAgICAgICAgICBzdGF0ZU1hcCkgXG4gICAgICAgIH1cbiAgICAgICk7XG4gICAgICBjb25zdCBjaGlsZERvbXMgPSBjaGlsZEluc3RhbmNlcy5tYXAoY2hpbGRJbnN0YW5jZSA9PiBjaGlsZEluc3RhbmNlLmRvbSk7XG4gICAgICBsZXQgbGNhc2VQcm9wcyA9IHt9XG4gICAgICBPYmplY3QuZW50cmllcyhyZXN0KS5mb3JFYWNoKChbaywgdl0pID0+IGxjYXNlUHJvcHNbZm9ybWF0UHJvcHMoayldID0gdilcbiAgICAgIGNvbnN0IGRvbSA9IHR5cGUgPT09IFRFWFRfRUxFTUVOVFxuICAgICAgICA/IG5ldyBWVGV4dChwcm9wcy5ub2RlVmFsdWUpXG4gICAgICAgIDogaCh0eXBlLCBsY2FzZVByb3BzLCBjaGlsZERvbXMpOyAvLyBlcXVpdmFsZW50IG9mIGFwcGVuZGNoaWxkXG4gICAgICBuZXdJbnN0YW5jZSA9IHsgZG9tLCBlbGVtZW50LCBjaGlsZEluc3RhbmNlcyB9O1xuICAgIH0gZWxzZSB7IC8vIGNvbXBvbmVudCBlbGVtZW50XG4gICAgICBsZXQgcHVibGljSW5zdGFuY2UgXG4gICAgICAvLyBkZWJ1Z2dlclxuICAgICAgaWYgKGluc3RhbmNlICYmIGluc3RhbmNlLnB1YmxpY0luc3RhbmNlICYmIGluc3RhbmNlLmVsZW1lbnQgPT09IGVsZW1lbnQpIHsgLy8gbWlnaHQgaGF2ZSB0byBkbyBtb3JlIGRpZmZpbmcgb2YgcHJvcHNcbiAgICAgICAgLy8ganVzdCByZXVzZSBvbGQgaW5zdGFuY2UgaWYgaXQgYWxyZWFkeSBleGlzdHNcbiAgICAgICAgcHVibGljSW5zdGFuY2UgPSBpbnN0YW5jZSAmJiBpbnN0YW5jZS5wdWJsaWNJbnN0YW5jZVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgbWFya05ld1N0cmVhbSgpIC8vIG1hcmsgYXMgZGlydHkgaW4gcGFyZW50IHNjb3BlOyB3aWxsIHJlcmVuZGVyXG4gICAgICAgIHB1YmxpY0luc3RhbmNlID0gY3JlYXRlUHVibGljSW5zdGFuY2UoZWxlbWVudCk7XG4gICAgICB9XG4gICAgICBsZXQgbG9jYWxTdGF0ZSA9IHN0YXRlTWFwLmdldChwdWJsaWNJbnN0YW5jZSlcbiAgICAgIGlmIChsb2NhbFN0YXRlID09PSB1bmRlZmluZWQpIGxvY2FsU3RhdGUgPSBwdWJsaWNJbnN0YW5jZS5pbml0aWFsU3RhdGVcbiAgICAgIHB1YmxpY0luc3RhbmNlLnN0YXRlID0gbG9jYWxTdGF0ZSAvLyBmb3IgYWNjZXNzIHdpdGggdGhpcy5zdGF0ZVxuICAgICAgaWYgKE9iamVjdC5rZXlzKHJlc3QpLmxlbmd0aCkgcHVibGljSW5zdGFuY2UucHJvcHMgPSByZXN0IC8vIHVwZGF0ZSB3aXRoIG5ldyBwcm9wcyAvLyBUT0RPOiBwb3RlbnRpYWxseSBidWdneVxuICAgICAgLy8gY29uc29sZS5sb2coe3Jlc3R9KVxuICAgICAgaWYgKHB1YmxpY0luc3RhbmNlLnNvdXJjZSkge1xuICAgICAgICBjb25zdCBzcmMgPSBwdWJsaWNJbnN0YW5jZS5zb3VyY2Uoc291cmNlKVxuICAgICAgICAvLyB0aGVyZSBhcmUgdHdvIGZvcm1zIG9mIENvbXBvbmVudC5zb3VyY2VcbiAgICAgICAgY29uc3Qgc3JjJCA9IHNyYy5yZWR1Y2VyICYmIHB1YmxpY0luc3RhbmNlLmluaXRpYWxTdGF0ZSAhPT0gdW5kZWZpbmVkID8gXG4gICAgICAgICAgICAvLyAxLiB0aGUgcmVkdWNlciBmb3JtXG4gICAgICAgICAgICBzY2FuKHNyYy5zb3VyY2UsIHNyYy5yZWR1Y2VyLCBwdWJsaWNJbnN0YW5jZS5pbml0aWFsU3RhdGUpIDogXG4gICAgICAgICAgICAvLyAyLiBhbmQgcmF3IHN0cmVhbSBmb3JtXG4gICAgICAgICAgICBzcmNcbiAgICAgICAgYWRkVG9TdHJlYW0oc3JjJFxuICAgICAgICAgIC5tYXAoZXZlbnQgPT4ge1xuICAgICAgICAgICAgc3RhdGVNYXAuc2V0KHB1YmxpY0luc3RhbmNlLCBldmVudClcbiAgICAgICAgICAgIHJldHVybiB7aW5zdGFuY2U6IHB1YmxpY0luc3RhbmNlLCBldmVudH0gLy8gdGFnIGl0IHRvIHRoZSBpbnN0YW5jZVxuICAgICAgICAgIH0pIFxuICAgICAgICApO1xuICAgICAgfVxuICAgICAgY29uc3QgY2hpbGRFbGVtZW50ID0gcHVibGljSW5zdGFuY2UucmVuZGVyID8gXG4gICAgICAgICAgcHVibGljSW5zdGFuY2UucmVuZGVyKGxvY2FsU3RhdGUsIHN0YXRlTWFwKSA6IFxuICAgICAgICAgIHB1YmxpY0luc3RhbmNlO1xuXG4gICAgICBjb25zdCBjaGlsZEluc3RhbmNlID0gcmVuZGVyV2l0aFN0cmVhbShjaGlsZEVsZW1lbnQsIGluc3RhbmNlICYmIGluc3RhbmNlLmNoaWxkSW5zdGFuY2UsIHN0YXRlLCBzdGF0ZU1hcClcbiAgICAgIGNvbnN0IGRvbSA9IGNoaWxkSW5zdGFuY2UuZG9tXG4gICAgICBuZXdJbnN0YW5jZSA9IHsgZG9tLCBlbGVtZW50LCBjaGlsZEluc3RhbmNlLCBwdWJsaWNJbnN0YW5jZSB9XG4gICAgfVxuICAgIHJldHVybiBuZXdJbnN0YW5jZVxuICB9XG59XG5cbmZ1bmN0aW9uIGZvcm1hdFByb3BzKGspIHtcbiAgaWYgKGsuc3RhcnRzV2l0aCgnb24nKSkgcmV0dXJuIGsudG9Mb3dlckNhc2UoKVxuICByZXR1cm4ga1xufSIsImltcG9ydCBPYnNlcnZhYmxlIGZyb20gJ3plbi1vYnNlcnZhYmxlJ1xuaW1wb3J0IHtmcm9tRXZlbnQsIHNjYW4sIG1lcmdlLCBzdGFydFdpdGgsIHN3aXRjaExhdGVzdH0gZnJvbSAnLi9zd3l4anMnXG5pbXBvcnQgZGlmZiBmcm9tICd2aXJ0dWFsLWRvbS9kaWZmJztcbmltcG9ydCBwYXRjaCBmcm9tICd2aXJ0dWFsLWRvbS9wYXRjaCc7XG5pbXBvcnQgY3JlYXRlRWxlbWVudCBmcm9tICd2aXJ0dWFsLWRvbS9jcmVhdGUtZWxlbWVudCc7XG5pbXBvcnQgeyBjcmVhdGVDaGFuZ2VFbWl0dGVyIH0gZnJvbSAnY2hhbmdlLWVtaXR0ZXInXG5pbXBvcnQgeyByZW5kZXJTdHJlYW0gfSBmcm9tICcuL3JlY29uY2lsZXInXG5cbmV4cG9ydCBjb25zdCBzdGF0ZU1hcFBvaW50ZXIgPSBuZXcgTWFwKClcblxubGV0IGNpcmN1aXRCcmVha2VyID0gLTIwXG5cbmNvbnN0IGVtaXR0ZXIgPSBjcmVhdGVDaGFuZ2VFbWl0dGVyKClcbi8vIHNpbmdsZSBVSSB0aHJlYWQ7IHRoaXMgaXMgdGhlIG9ic2VydmFibGUgdGhhdCBzdGlja3MgYXJvdW5kIGFuZCBzd2FwcyBvdXQgc291cmNlXG5jb25zdCBVSXRocmVhZCA9IG5ldyBPYnNlcnZhYmxlKG9ic2VydmVyID0+IHtcbiAgZW1pdHRlci5saXN0ZW4oeCA9PiB7XG4gICAgLy8gZGVidWdnZXIgLy8gc3VjY2VzcyEgdGhyZWFkIHN3aXRjaGluZyFcbiAgICBvYnNlcnZlci5uZXh0KHgpXG4gIH0pXG59KVxuLy8gbW91bnQgdGhlIHZkb20gb24gdG8gdGhlIGRvbSBhbmQgXG4vLyBzZXQgdXAgdGhlIHJ1bnRpbWUgZnJvbSBzb3VyY2VzIGFuZFxuLy8gcGF0Y2ggdGhlIHZkb21cbi8vIC0tLVxuLy8gcmV0dXJucyBhbiB1bnN1YnNjcmliZSBtZXRob2QgeW91IGNhbiB1c2UgdG8gdW5tb3VudFxuZXhwb3J0IGZ1bmN0aW9uIG1vdW50KHJvb3RFbGVtZW50LCBjb250YWluZXIpIHtcbiAgLy8gaW5pdGlhbCwgdGhyb3dhd2F5LWlzaCBmcmFtZVxuICBsZXQge3NvdXJjZSwgaW5zdGFuY2V9ID0gcmVuZGVyU3RyZWFtKHJvb3RFbGVtZW50LCB7fSwgdW5kZWZpbmVkLCBzdGF0ZU1hcFBvaW50ZXIpXG4gIGxldCBpbnN0YW5jZVBvaW50ZXIgPSBpbnN0YW5jZVxuICBjb25zdCByb290Tm9kZSA9IGNyZWF0ZUVsZW1lbnQoaW5zdGFuY2UuZG9tKVxuICBjb25zdCBjb250YWluZXJDaGlsZCA9IGNvbnRhaW5lci5maXJzdEVsZW1lbnRDaGlsZFxuICBpZiAoY29udGFpbmVyQ2hpbGQpIHtcbiAgICBjb250YWluZXIucmVwbGFjZUNoaWxkKHJvb3ROb2RlLGNvbnRhaW5lckNoaWxkKSAvLyBob3QgcmVsb2FkZWQgbW91bnRcbiAgfSBlbHNlIHtcbiAgICBjb250YWluZXIuYXBwZW5kQ2hpbGQocm9vdE5vZGUpIC8vIGluaXRpYWwgbW91bnRcbiAgfVxuICBsZXQgY3VycmVudFNyYyQgPSBudWxsXG4gIGxldCBTb1MgPSBzdGFydFdpdGgoVUl0aHJlYWQsIHNvdXJjZSkgLy8gc3RyZWFtIG9mIHN0cmVhbXNcbiAgcmV0dXJuIFNvUy5zdWJzY3JpYmUoXG4gICAgc3JjJCA9PiB7IC8vIHRoaXMgaXMgdGhlIGN1cnJlbnQgc291cmNlU3RyZWFtIHdlIGFyZSB3b3JraW5nIHdpdGhcbiAgICAgIGlmIChjdXJyZW50U3JjJCkgY29uc29sZS5sb2coJ3Vuc3ViIScpIHx8IGN1cnJlbnRTcmMkLnVuc3Vic2NyaWJlKCkgLy8gdW5zdWIgZnJvbSBvbGQgc3RyZWFtXG4gICAgICAvKioqKiBtYWluICovXG4gICAgICBjb25zdCBzb3VyY2UyJCA9IHNjYW4oXG4gICAgICAgIHNyYyQsIFxuICAgICAgICAoe2luc3RhbmNlLCBzdGF0ZU1hcH0sIG5leHRTdGF0ZSkgPT4ge1xuICAgICAgICAgIGNvbnN0IHN0cmVhbU91dHB1dCA9IHJlbmRlclN0cmVhbShyb290RWxlbWVudCwgaW5zdGFuY2UsIG5leHRTdGF0ZSwgc3RhdGVNYXApXG4gICAgICAgICAgaWYgKHN0cmVhbU91dHB1dC5pc05ld1N0cmVhbSkgeyAvLyBxdWljayBjaGVja1xuICAgICAgICAgICAgY29uc3QgbmV4dFNvdXJjZSQgPSBzdHJlYW1PdXRwdXQuc291cmNlXG4gICAgICAgICAgICAvLyBkZWJ1Z2dlclxuICAgICAgICAgICAgaW5zdGFuY2VQb2ludGVyID0gc3RyZWFtT3V0cHV0Lmluc3RhbmNlXG4gICAgICAgICAgICBwYXRjaChyb290Tm9kZSwgZGlmZihpbnN0YW5jZS5kb20sIGluc3RhbmNlUG9pbnRlci5kb20pKSAvLyByZW5kZXIgdG8gc2NyZWVuXG4gICAgICAgICAgICBlbWl0dGVyLmVtaXQobmV4dFNvdXJjZSQpIC8vIHVwZGF0ZSB0aGUgVUkgdGhyZWFkOyBzb3VyY2Ugd2lsbCBzd2l0Y2hcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgY29uc3QgbmV4dGluc3RhbmNlID0gc3RyZWFtT3V0cHV0Lmluc3RhbmNlXG4gICAgICAgICAgICBwYXRjaChyb290Tm9kZSwgZGlmZihpbnN0YW5jZS5kb20sIG5leHRpbnN0YW5jZS5kb20pKSAvLyByZW5kZXIgdG8gc2NyZWVuXG4gICAgICAgICAgICByZXR1cm4ge2luc3RhbmNlOiBuZXh0aW5zdGFuY2UsIHN0YXRlTWFwOiBzdGF0ZU1hcH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtpbnN0YW5jZTogaW5zdGFuY2VQb2ludGVyLCBzdGF0ZU1hcDogc3RhdGVNYXBQb2ludGVyfSAvLyBhY2N1bXVsYXRvclxuICAgICAgKVxuICAgICAgLyoqKiogZW5kIG1haW4gKi9cbiAgICAgIGN1cnJlbnRTcmMkID0gXG4gICAgICAgIHNvdXJjZTIkXG4gICAgICAgICAgLnN1YnNjcmliZSgpXG4gICAgfVxuICApXG59XG4iLCJpbXBvcnQgeyBjcmVhdGVFbGVtZW50LCBjcmVhdGVIYW5kbGVyIH0gZnJvbSBcIi4vZWxlbWVudFwiO1xuaW1wb3J0IHsgQ29tcG9uZW50IH0gZnJvbSBcIi4vY29tcG9uZW50XCI7XG5pbXBvcnQgeyByZW5kZXJTdHJlYW0gfSBmcm9tIFwiLi9yZWNvbmNpbGVyXCJcbmltcG9ydCB7IG1vdW50IH0gZnJvbSBcIi4vc2NoZWR1bGVyXCI7XG5cbmV4cG9ydCBkZWZhdWx0IHtcbiAgcmVuZGVyU3RyZWFtLFxuICBjcmVhdGVFbGVtZW50LFxuICBjcmVhdGVIYW5kbGVyLFxuICBDb21wb25lbnQsXG4gIG1vdW50XG59O1xuXG5leHBvcnQgeyBjcmVhdGVFbGVtZW50LCBjcmVhdGVIYW5kbGVyLCBDb21wb25lbnQsIFxuICByZW5kZXJTdHJlYW0sIFxuICBtb3VudCB9O1xuIl0sIm5hbWVzIjpbIlRFWFRfRUxFTUVOVCIsImNyZWF0ZUVsZW1lbnQiLCJ0eXBlIiwiY29uZmlnIiwicHJvcHMiLCJPYmplY3QiLCJhc3NpZ24iLCJhcmdzIiwiaGFzQ2hpbGRyZW4iLCJsZW5ndGgiLCJyYXdDaGlsZHJlbiIsImNvbmNhdCIsImNoaWxkcmVuIiwiZmlsdGVyIiwiYyIsIm1hcCIsImNyZWF0ZVRleHRFbGVtZW50IiwidmFsdWUiLCJub2RlVmFsdWUiLCJjcmVhdGVIYW5kbGVyIiwiX2ZuIiwiZW1pdHRlciIsImNyZWF0ZUNoYW5nZUVtaXR0ZXIiLCJoYW5kbGVyIiwiZW1pdCIsIngiLCIkIiwiT2JzZXJ2YWJsZSIsImxpc3RlbiIsIm5leHQiLCJOT0lOSVQiLCJTeW1ib2wiLCJzY2FuIiwib2JzIiwiY2IiLCJzZWVkIiwic3ViIiwiYWNjIiwiaGFzVmFsdWUiLCJoYXNTZWVkIiwic3Vic2NyaWJlIiwib2JzZXJ2ZXIiLCJjbG9zZWQiLCJmaXJzdCIsImUiLCJlcnJvciIsInN0YXJ0V2l0aCIsInZhbCIsIkNvbXBvbmVudCIsInN0YXRlIiwib2JqIiwic291cmNlcyIsImVudHJpZXMiLCJrIiwiZm4iLCJzdWJSZWR1Y2VyIiwic291cmNlIiwicmVkdWNlciIsIl8iLCJuIiwiaW5pdGlhbFN0YXRlIiwibWVyZ2UiLCJjcmVhdGVQdWJsaWNJbnN0YW5jZSIsImVsZW1lbnQiLCJwdWJsaWNJbnN0YW5jZSIsInJlbmRlclN0cmVhbSIsImluc3RhbmNlIiwic3RhdGVNYXAiLCJpc05ld1N0cmVhbSIsIm9mIiwiYWRkVG9TdHJlYW0iLCJfc291cmNlIiwibWFya05ld1N0cmVhbSIsIm5ld0luc3RhbmNlIiwicmVuZGVyIiwicmVuZGVyV2l0aFN0cmVhbSIsImlzRG9tRWxlbWVudCIsInJlc3QiLCJjaGlsZEluc3RhbmNlcyIsImVsIiwiaSIsIl9jaGlsZEluc3RhbmNlcyIsImNoaWxkSW5zdGFuY2UiLCJjaGlsZERvbXMiLCJkb20iLCJsY2FzZVByb3BzIiwiZm9yRWFjaCIsInYiLCJmb3JtYXRQcm9wcyIsIlZUZXh0IiwiaCIsImxvY2FsU3RhdGUiLCJnZXQiLCJ1bmRlZmluZWQiLCJrZXlzIiwic3JjIiwic3JjJCIsInNldCIsImV2ZW50IiwiY2hpbGRFbGVtZW50Iiwic3RhcnRzV2l0aCIsInRvTG93ZXJDYXNlIiwic3RhdGVNYXBQb2ludGVyIiwiTWFwIiwiVUl0aHJlYWQiLCJtb3VudCIsInJvb3RFbGVtZW50IiwiY29udGFpbmVyIiwiaW5zdGFuY2VQb2ludGVyIiwicm9vdE5vZGUiLCJjb250YWluZXJDaGlsZCIsImZpcnN0RWxlbWVudENoaWxkIiwicmVwbGFjZUNoaWxkIiwiYXBwZW5kQ2hpbGQiLCJjdXJyZW50U3JjJCIsIlNvUyIsImNvbnNvbGUiLCJsb2ciLCJ1bnN1YnNjcmliZSIsInNvdXJjZTIkIiwibmV4dFN0YXRlIiwic3RyZWFtT3V0cHV0IiwibmV4dFNvdXJjZSQiLCJkaWZmIiwibmV4dGluc3RhbmNlIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFHTyxJQUFNQSxlQUFlLGNBQXJCOztBQUVQLEFBQU8sU0FBU0MsZUFBVCxDQUF1QkMsSUFBdkIsRUFBNkJDLE1BQTdCLEVBQThDOzs7TUFDN0NDLFFBQVFDLE9BQU9DLE1BQVAsQ0FBYyxFQUFkLEVBQWtCSCxNQUFsQixDQUFkOztvQ0FENkNJLElBQU07UUFBQTs7O01BRTdDQyxjQUFjRCxLQUFLRSxNQUFMLEdBQWMsQ0FBbEM7TUFDTUMsY0FBY0YsY0FBYyxZQUFHRyxNQUFILGFBQWFKLElBQWIsQ0FBZCxHQUFtQyxFQUF2RDtRQUNNSyxRQUFOLEdBQWlCRixZQUNkRyxNQURjLENBQ1A7V0FBS0Ms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 351 | -------------------------------------------------------------------------------- /build/reactive-react.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('zen-observable'), require('change-emitter'), require('zen-observable/extras'), require('virtual-dom/h'), require('virtual-dom/vnode/vtext'), require('virtual-dom/diff'), require('virtual-dom/patch'), require('virtual-dom/create-element')) : 3 | typeof define === 'function' && define.amd ? define(['exports', 'zen-observable', 'change-emitter', 'zen-observable/extras', 'virtual-dom/h', 'virtual-dom/vnode/vtext', 'virtual-dom/diff', 'virtual-dom/patch', 'virtual-dom/create-element'], factory) : 4 | (factory((global['reactive-react'] = global['reactive-react'] || {}),global.Observable,global.changeEmitter,global.zenObservable_extras,global.h,global.VText,global.diff,global.patch,global.createElement)); 5 | }(this, (function (exports,Observable,changeEmitter,zenObservable_extras,h,VText,diff,patch,createElement) { 'use strict'; 6 | 7 | Observable = 'default' in Observable ? Observable['default'] : Observable; 8 | h = 'default' in h ? h['default'] : h; 9 | VText = 'default' in VText ? VText['default'] : VText; 10 | diff = 'default' in diff ? diff['default'] : diff; 11 | patch = 'default' in patch ? patch['default'] : patch; 12 | createElement = 'default' in createElement ? createElement['default'] : createElement; 13 | 14 | var TEXT_ELEMENT = "TEXT ELEMENT"; 15 | 16 | function createElement$1(type, config) { 17 | var _ref; 18 | 19 | var props = Object.assign({}, config); 20 | 21 | for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { 22 | args[_key - 2] = arguments[_key]; 23 | } 24 | 25 | var hasChildren = args.length > 0; 26 | var rawChildren = hasChildren ? (_ref = []).concat.apply(_ref, args) : []; 27 | props.children = rawChildren.filter(function (c) { 28 | return c != null && c !== false; 29 | }).map(function (c) { 30 | return c instanceof Object ? c : createTextElement(c); 31 | }); 32 | return { type: type, props: props }; 33 | } 34 | 35 | function createTextElement(value) { 36 | return createElement$1(TEXT_ELEMENT, { nodeValue: value }); 37 | } 38 | 39 | function createHandler(_fn) { 40 | var emitter = changeEmitter.createChangeEmitter(); 41 | var handler = function handler(x) { 42 | emitter.emit(x); 43 | }; 44 | handler.$ = new Observable(function (observer) { 45 | return emitter.listen(function (value) { 46 | observer.next(_fn ? _fn(value) : value); 47 | }); 48 | }); 49 | return handler; 50 | } 51 | 52 | var NOINIT = Symbol('NO_INITIAL_VALUE'); 53 | function scan(obs, cb) { 54 | var seed = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : NOINIT; 55 | 56 | var sub = void 0, 57 | acc = seed, 58 | hasValue = false; 59 | var hasSeed = acc !== NOINIT; 60 | return new Observable(function (observer) { 61 | sub = obs.subscribe(function (value) { 62 | if (observer.closed) return; 63 | var first = !hasValue; 64 | hasValue = true; 65 | if (!first || hasSeed) { 66 | try { 67 | acc = cb(acc, value); 68 | } catch (e) { 69 | return observer.error(e); 70 | } 71 | observer.next(acc); 72 | } else { 73 | acc = value; 74 | } 75 | }); 76 | return sub; 77 | }); 78 | } 79 | 80 | // Flatten a collection of observables and only output the newest from each 81 | 82 | 83 | 84 | 85 | function startWith(obs, val) { 86 | return new Observable(function (observer) { 87 | observer.next(val); // immediately output this value 88 | var handler = obs.subscribe(function (x) { 89 | return observer.next(x); 90 | }); 91 | return function () { 92 | return handler(); 93 | }; 94 | }); 95 | } 96 | 97 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 98 | 99 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 100 | 101 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 102 | 103 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 104 | 105 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 106 | 107 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 108 | 109 | // import { reconcile } from "./reconciler"; 110 | var Component = function () { 111 | function Component(props) { 112 | _classCallCheck(this, Component); 113 | 114 | this.props = props; 115 | this.state = this.state || {}; 116 | } 117 | 118 | // setState(partialState) { 119 | // this.state = Object.assign({}, this.state, partialState); 120 | // updateInstance(this.__internalInstance); 121 | // } 122 | 123 | // class method because it feeds in this.initialState 124 | 125 | 126 | _createClass(Component, [{ 127 | key: 'combineReducer', 128 | value: function combineReducer(obj) { 129 | var _this = this; 130 | 131 | var sources = Object.entries(obj).map(function (_ref) { 132 | var _ref2 = _slicedToArray(_ref, 2), 133 | k = _ref2[0], 134 | fn = _ref2[1]; 135 | 136 | var subReducer = fn(obj); 137 | // there are two forms of return the subreducer can have 138 | // straight stream form 139 | // or object form where we need to scan it into string 140 | if (subReducer.source && subReducer.reducer) { 141 | // object form 142 | subReducer = scan(subReducer.source, subReducer.reducer || function (_, n) { 143 | return n; 144 | }, _this.initialState[k]); 145 | } 146 | return subReducer.map(function (x) { 147 | return _defineProperty({}, k, x); 148 | }); // map to its particular namespace 149 | }); 150 | var source = zenObservable_extras.merge.apply(undefined, _toConsumableArray(sources)); 151 | var reducer = function reducer(acc, n) { 152 | return _extends({}, acc, n); 153 | }; 154 | return { source: source, reducer: reducer }; 155 | } 156 | }]); 157 | 158 | return Component; 159 | }(); 160 | 161 | // function updateInstance(internalInstance) { 162 | // const parentDom = internalInstance.dom.parentNode; 163 | // const element = internalInstance.element; 164 | // reconcile(parentDom, internalInstance, element); 165 | // } 166 | 167 | function createPublicInstance(element /*, internalInstance*/) { 168 | var type = element.type, 169 | props = element.props; 170 | 171 | var publicInstance = new type(props); 172 | // publicInstance.__internalInstance = internalInstance; 173 | return publicInstance; 174 | } 175 | 176 | var _slicedToArray$1 = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 177 | 178 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 179 | 180 | // import { updateDomProperties } from "./updateProperties"; 181 | // import VNode from "virtual-dom/vnode/vnode" 182 | // const circuitBreakerflag = false // set true to enable debugger in infinite loops 183 | // let circuitBreaker = -50 184 | // traverse all children and collect a stream of all sources 185 | // AND render. a bit of duplication, but we get persistent instances which is good 186 | function renderStream(element, instance, state, stateMap) { 187 | // this is a separate function because scope gets messy when being recursive 188 | var isNewStream = false; // assume no stream switching by default 189 | // this is the first ping of data throughout the app 190 | var source = Observable.of(state); 191 | var addToStream = function addToStream(_source) { 192 | // visit each source and merge with source 193 | if (_source) return source = zenObservable_extras.merge(source, _source); 194 | }; 195 | var markNewStream = function markNewStream() { 196 | return isNewStream = true; 197 | }; 198 | var newInstance = render(source, addToStream, markNewStream)(element, instance, state, stateMap); 199 | return { source: source, instance: newInstance, isNewStream: isNewStream }; 200 | } 201 | 202 | /** core render logic */ 203 | function render(source, addToStream, markNewStream) { 204 | // this is the nonrecursive part 205 | return function renderWithStream(element, instance, state, stateMap) { 206 | // recursive part 207 | var newInstance = void 0; 208 | var type = element.type, 209 | props = element.props; 210 | 211 | 212 | var isDomElement = typeof type === "string"; 213 | // if (circuitBreakerflag && circuitBreaker++ > 0) debugger 214 | 215 | var _props$children = props.children, 216 | children = _props$children === undefined ? [] : _props$children, 217 | rest = _objectWithoutProperties(props, ['children']); 218 | 219 | if (isDomElement) { 220 | var childInstances = children.map(function (el, i) { 221 | // ugly but necessary to allow functional children 222 | // mapping element's children to instance's childInstances 223 | var _childInstances = instance && (instance.childInstance || instance.childInstances[i]); 224 | return renderWithStream( // recursion 225 | el, _childInstances, state, stateMap); 226 | }); 227 | var childDoms = childInstances.map(function (childInstance) { 228 | return childInstance.dom; 229 | }); 230 | var lcaseProps = {}; 231 | Object.entries(rest).forEach(function (_ref) { 232 | var _ref2 = _slicedToArray$1(_ref, 2), 233 | k = _ref2[0], 234 | v = _ref2[1]; 235 | 236 | return lcaseProps[formatProps(k)] = v; 237 | }); 238 | var dom = type === TEXT_ELEMENT ? new VText(props.nodeValue) : h(type, lcaseProps, childDoms); // equivalent of appendchild 239 | newInstance = { dom: dom, element: element, childInstances: childInstances }; 240 | } else { 241 | // component element 242 | var publicInstance = void 0; 243 | // debugger 244 | if (instance && instance.publicInstance && instance.element === element) { 245 | // might have to do more diffing of props 246 | // just reuse old instance if it already exists 247 | publicInstance = instance && instance.publicInstance; 248 | } else { 249 | markNewStream(); // mark as dirty in parent scope; will rerender 250 | publicInstance = createPublicInstance(element); 251 | } 252 | var localState = stateMap.get(publicInstance); 253 | if (localState === undefined) localState = publicInstance.initialState; 254 | publicInstance.state = localState; // for access with this.state 255 | if (Object.keys(rest).length) publicInstance.props = rest; // update with new props // TODO: potentially buggy 256 | // console.log({rest}) 257 | if (publicInstance.source) { 258 | var src = publicInstance.source(source); 259 | // there are two forms of Component.source 260 | var src$ = src.reducer && publicInstance.initialState !== undefined ? 261 | // 1. the reducer form 262 | scan(src.source, src.reducer, publicInstance.initialState) : 263 | // 2. and raw stream form 264 | src; 265 | addToStream(src$.map(function (event) { 266 | stateMap.set(publicInstance, event); 267 | return { instance: publicInstance, event: event // tag it to the instance 268 | }; 269 | })); 270 | } 271 | var childElement = publicInstance.render ? publicInstance.render(localState, stateMap) : publicInstance; 272 | 273 | var childInstance = renderWithStream(childElement, instance && instance.childInstance, state, stateMap); 274 | var _dom = childInstance.dom; 275 | newInstance = { dom: _dom, element: element, childInstance: childInstance, publicInstance: publicInstance }; 276 | } 277 | return newInstance; 278 | }; 279 | } 280 | 281 | function formatProps(k) { 282 | if (k.startsWith('on')) return k.toLowerCase(); 283 | return k; 284 | } 285 | 286 | var stateMapPointer = new Map(); 287 | 288 | var emitter = changeEmitter.createChangeEmitter(); 289 | // single UI thread; this is the observable that sticks around and swaps out source 290 | var UIthread = new Observable(function (observer) { 291 | emitter.listen(function (x) { 292 | // debugger // success! thread switching! 293 | observer.next(x); 294 | }); 295 | }); 296 | // mount the vdom on to the dom and 297 | // set up the runtime from sources and 298 | // patch the vdom 299 | // --- 300 | // returns an unsubscribe method you can use to unmount 301 | function mount(rootElement, container) { 302 | // initial, throwaway-ish frame 303 | var _renderStream = renderStream(rootElement, {}, undefined, stateMapPointer), 304 | source = _renderStream.source, 305 | instance = _renderStream.instance; 306 | 307 | var instancePointer = instance; 308 | var rootNode = createElement(instance.dom); 309 | var containerChild = container.firstElementChild; 310 | if (containerChild) { 311 | container.replaceChild(rootNode, containerChild); // hot reloaded mount 312 | } else { 313 | container.appendChild(rootNode); // initial mount 314 | } 315 | var currentSrc$ = null; 316 | var SoS = startWith(UIthread, source); // stream of streams 317 | return SoS.subscribe(function (src$) { 318 | // this is the current sourceStream we are working with 319 | if (currentSrc$) console.log('unsub!') || currentSrc$.unsubscribe(); // unsub from old stream 320 | /**** main */ 321 | var source2$ = scan(src$, function (_ref, nextState) { 322 | var instance = _ref.instance, 323 | stateMap = _ref.stateMap; 324 | 325 | var streamOutput = renderStream(rootElement, instance, nextState, stateMap); 326 | if (streamOutput.isNewStream) { 327 | // quick check 328 | var nextSource$ = streamOutput.source; 329 | // debugger 330 | instancePointer = streamOutput.instance; 331 | patch(rootNode, diff(instance.dom, instancePointer.dom)); // render to screen 332 | emitter.emit(nextSource$); // update the UI thread; source will switch 333 | } else { 334 | var nextinstance = streamOutput.instance; 335 | patch(rootNode, diff(instance.dom, nextinstance.dom)); // render to screen 336 | return { instance: nextinstance, stateMap: stateMap }; 337 | } 338 | }, { instance: instancePointer, stateMap: stateMapPointer // accumulator 339 | }); 340 | /**** end main */ 341 | currentSrc$ = source2$.subscribe(); 342 | }); 343 | } 344 | 345 | var index = { 346 | renderStream: renderStream, 347 | createElement: createElement$1, 348 | createHandler: createHandler, 349 | Component: Component, 350 | mount: mount 351 | }; 352 | 353 | exports['default'] = index; 354 | exports.createElement = createElement$1; 355 | exports.createHandler = createHandler; 356 | exports.Component = Component; 357 | exports.renderStream = renderStream; 358 | exports.mount = mount; 359 | 360 | Object.defineProperty(exports, '__esModule', { value: true }); 361 | 362 | }))); 363 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVhY3RpdmUtcmVhY3QudW1kLmpzIiwic291cmNlcyI6WyIuLi9yZWFjdGl2ZS1yZWFjdC9lbGVtZW50LmpzIiwiLi4vcmVhY3RpdmUtcmVhY3Qvc3d5eGpzLmpzIiwiLi4vcmVhY3RpdmUtcmVhY3QvY29tcG9uZW50LmpzIiwiLi4vcmVhY3RpdmUtcmVhY3QvcmVjb25jaWxlci5qcyIsIi4uL3JlYWN0aXZlLXJlYWN0L3NjaGVkdWxlci5qcyIsIi4uL3JlYWN0aXZlLXJlYWN0L2luZGV4LmpzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBPYnNlcnZhYmxlIGZyb20gJ3plbi1vYnNlcnZhYmxlJ1xuaW1wb3J0IHsgY3JlYXRlQ2hhbmdlRW1pdHRlciB9IGZyb20gJ2NoYW5nZS1lbWl0dGVyJ1xuXG5leHBvcnQgY29uc3QgVEVYVF9FTEVNRU5UID0gXCJURVhUIEVMRU1FTlRcIjtcblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUVsZW1lbnQodHlwZSwgY29uZmlnLCAuLi5hcmdzKSB7XG4gIGNvbnN0IHByb3BzID0gT2JqZWN0LmFzc2lnbih7fSwgY29uZmlnKTtcbiAgY29uc3QgaGFzQ2hpbGRyZW4gPSBhcmdzLmxlbmd0aCA+IDA7XG4gIGNvbnN0IHJhd0NoaWxkcmVuID0gaGFzQ2hpbGRyZW4gPyBbXS5jb25jYXQoLi4uYXJncykgOiBbXTtcbiAgcHJvcHMuY2hpbGRyZW4gPSByYXdDaGlsZHJlblxuICAgIC5maWx0ZXIoYyA9PiBjICE9IG51bGwgJiYgYyAhPT0gZmFsc2UpXG4gICAgLm1hcChjID0+IGMgaW5zdGFuY2VvZiBPYmplY3QgPyBjIDogY3JlYXRlVGV4dEVsZW1lbnQoYykpO1xuICByZXR1cm4geyB0eXBlLCBwcm9wcyB9O1xufVxuXG5mdW5jdGlvbiBjcmVhdGVUZXh0RWxlbWVudCh2YWx1ZSkge1xuICByZXR1cm4gY3JlYXRlRWxlbWVudChURVhUX0VMRU1FTlQsIHsgbm9kZVZhbHVlOiB2YWx1ZSB9KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUhhbmRsZXIoX2ZuKSB7XG4gIGNvbnN0IGVtaXR0ZXIgPSBjcmVhdGVDaGFuZ2VFbWl0dGVyKClcbiAgbGV0IGhhbmRsZXIgPSB4ID0+IHtcbiAgICBlbWl0dGVyLmVtaXQoeClcbiAgfVxuICBoYW5kbGVyLiQgPSBuZXcgT2JzZXJ2YWJsZShvYnNlcnZlciA9PiB7XG4gICAgcmV0dXJuIGVtaXR0ZXIubGlzdGVuKHZhbHVlID0+IHtcbiAgICAgIG9ic2VydmVyLm5leHQoX2ZuID8gX2ZuKHZhbHVlKSA6IHZhbHVlKVxuICAgIH1cbiAgICApXG4gIH0pXG4gIHJldHVybiBoYW5kbGVyXG59IiwiaW1wb3J0IE9ic2VydmFibGUgZnJvbSAnemVuLW9ic2VydmFibGUnXG5leHBvcnQgeyBtZXJnZSwgY29tYmluZUxhdGVzdCwgemlwIH0gZnJvbSAnemVuLW9ic2VydmFibGUvZXh0cmFzJ1xuXG5leHBvcnQgZnVuY3Rpb24gSW50ZXJ2YWwodGljayA9IDEwMDAsIHRpY2tEYXRhID0gU3ltYm9sKCd0aWNrJykpIHtcbiAgcmV0dXJuIG5ldyBPYnNlcnZhYmxlKG9ic2VydmVyID0+IHtcbiAgICBsZXQgdGltZXIgPSAoKSA9PiBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgIGlmICh0eXBlb2YgdGlja0RhdGEgPT09ICdmdW5jdGlvbicpIHRpY2tEYXRhID0gdGlja0RhdGEoKVxuICAgICAgb2JzZXJ2ZXIubmV4dCh0aWNrRGF0YSk7XG4gICAgICB0aW1lcigpXG4gICAgICAvLyBvYnNlcnZlci5jb21wbGV0ZSgpO1xuICAgIH0sIHRpY2spO1xuICAgIHRpbWVyKClcbiAgXG4gICAgLy8gT24gdW5zdWJzY3JpcHRpb24sIGNhbmNlbCB0aGUgdGltZXJcbiAgICByZXR1cm4gKCkgPT4gY2xlYXJUaW1lb3V0KHRpbWVyKTtcblxuICB9KVxufVxuXG5leHBvcnQgZnVuY3Rpb24gZnJvbUV2ZW50KGVsLCBldmVudFR5cGUpIHtcbiAgcmV0dXJuIG5ldyBPYnNlcnZhYmxlKG9ic2VydmVyID0+IHtcbiAgICBjb25zdCBoYW5kbGVyID0gZSA9PiBvYnNlcnZlci5uZXh0KGUpXG4gICAgZWwuYWRkRXZlbnRMaXN0ZW5lcihldmVudFR5cGUsIGhhbmRsZXIpXG4gICAgLy8gb24gdW5zdWIsIHJlbW92ZSBldmVudCBsaXN0ZW5lclxuICAgIHJldHVybiAoKSA9PiBlbC5yZW1vdmVFdmVudExpc3RlbmVyKGV2ZW50VHlwZSwgaGFuZGxlcilcbiAgfSlcbn1cblxuY29uc3QgTk9JTklUID0gU3ltYm9sKCdOT19JTklUSUFMX1ZBTFVFJylcbmV4cG9ydCBmdW5jdGlvbiBzY2FuKG9icywgY2IsIHNlZWQgPSBOT0lOSVQpIHtcbiAgbGV0IHN1YiwgYWNjID0gc2VlZCwgaGFzVmFsdWUgPSBmYWxzZVxuICBjb25zdCBoYXNTZWVkID0gYWNjICE9PSBOT0lOSVRcbiAgcmV0dXJuIG5ldyBPYnNlcnZhYmxlKG9ic2VydmVyID0+IHtcbiAgICBzdWIgPSBvYnMuc3Vic2NyaWJlKHZhbHVlID0+IHtcbiAgICAgIGlmIChvYnNlcnZlci5jbG9zZWQpIHJldHVyblxuICAgICAgbGV0IGZpcnN0ID0gIWhhc1ZhbHVlO1xuICAgICAgaGFzVmFsdWUgPSB0cnVlXG4gICAgICBpZiAoIWZpcnN0IHx8IGhhc1NlZWQgKSB7XG4gICAgICAgIHRyeSB7IGFjYyA9IGNiKGFjYywgdmFsdWUpIH1cbiAgICAgICAgY2F0Y2ggKGUpIHsgcmV0dXJuIG9ic2VydmVyLmVycm9yKGUpIH1cbiAgICAgICAgb2JzZXJ2ZXIubmV4dChhY2MpO1xuICAgICAgfVxuICAgICAgZWxzZSB7XG4gICAgICAgIGFjYyA9IHZhbHVlXG4gICAgICB9XG4gICAgfSlcbiAgICByZXR1cm4gc3ViXG4gIH0pXG59XG5cbi8vIEZsYXR0ZW4gYSBjb2xsZWN0aW9uIG9mIG9ic2VydmFibGVzIGFuZCBvbmx5IG91dHB1dCB0aGUgbmV3ZXN0IGZyb20gZWFjaFxuZXhwb3J0IGZ1bmN0aW9uIHN3aXRjaExhdGVzdChoaWdoZXJPYnNlcnZhYmxlKSB7XG4gIHJldHVybiBuZXcgT2JzZXJ2YWJsZShvYnNlcnZlciA9PiB7XG4gICAgbGV0IGN1cnJlbnRPYnMgPSBudWxsXG4gICAgcmV0dXJuIGhpZ2hlck9ic2VydmFibGUuc3Vic2NyaWJlKHtcbiAgICAgIG5leHQob2JzKSB7XG4gICAgICAgIGlmIChjdXJyZW50T2JzKSBjdXJyZW50T2JzLnVuc3Vic2NyaWJlKCkgLy8gdW5zdWIgYW5kIHN3aXRjaFxuICAgICAgICBjdXJyZW50T2JzID0gb2JzLnN1YnNjcmliZShvYnNlcnZlci5zdWJzY3JpYmUpXG4gICAgICB9LFxuICAgICAgZXJyb3IoZSkge1xuICAgICAgICBvYnNlcnZlci5lcnJvcihlKSAvLyB1bnRlc3RlZFxuICAgICAgfSxcbiAgICAgIGNvbXBsZXRlKCkge1xuICAgICAgICAvLyBpIGRvbnQgdGhpbmsgaXQgc2hvdWxkIGNvbXBsZXRlP1xuICAgICAgICAvLyBvYnNlcnZlci5jb21wbGV0ZSgpXG4gICAgICB9XG4gICAgfSlcbiAgfSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBtYXBUb0NvbnN0YW50KG9icywgdmFsKSB7XG4gIHJldHVybiBuZXcgT2JzZXJ2YWJsZShvYnNlcnZlciA9PiB7XG4gICAgY29uc3QgaGFuZGxlciA9IG9icy5zdWJzY3JpYmUoKCkgPT4gb2JzZXJ2ZXIubmV4dCh2YWwpKVxuICAgIHJldHVybiBoYW5kbGVyXG4gIH0pXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBzdGFydFdpdGgob2JzLCB2YWwpIHtcbiAgcmV0dXJuIG5ldyBPYnNlcnZhYmxlKG9ic2VydmVyID0+IHtcbiAgICBvYnNlcnZlci5uZXh0KHZhbCkgLy8gaW1tZWRpYXRlbHkgb3V0cHV0IHRoaXMgdmFsdWVcbiAgICBjb25zdCBoYW5kbGVyID0gb2JzLnN1YnNjcmliZSh4ID0+IG9ic2VydmVyLm5leHQoeCkpXG4gICAgcmV0dXJuICgpID0+IGhhbmRsZXIoKVxuICB9KVxufSIsIi8vIGltcG9ydCB7IHJlY29uY2lsZSB9IGZyb20gXCIuL3JlY29uY2lsZXJcIjtcbmltcG9ydCB7SW50ZXJ2YWwsIHNjYW4sIHN0YXJ0V2l0aCwgbWVyZ2UsIG1hcFRvQ29uc3RhbnR9IGZyb20gJy4vc3d5eGpzJ1xuXG5leHBvcnQgY2xhc3MgQ29tcG9uZW50IHtcbiAgY29uc3RydWN0b3IocHJvcHMpIHtcbiAgICB0aGlzLnByb3BzID0gcHJvcHM7XG4gICAgdGhpcy5zdGF0ZSA9IHRoaXMuc3RhdGUgfHwge307XG4gIH1cblxuICAvLyBzZXRTdGF0ZShwYXJ0aWFsU3RhdGUpIHtcbiAgLy8gICB0aGlzLnN0YXRlID0gT2JqZWN0LmFzc2lnbih7fSwgdGhpcy5zdGF0ZSwgcGFydGlhbFN0YXRlKTtcbiAgLy8gICB1cGRhdGVJbnN0YW5jZSh0aGlzLl9faW50ZXJuYWxJbnN0YW5jZSk7XG4gIC8vIH1cblxuICAvLyBjbGFzcyBtZXRob2QgYmVjYXVzZSBpdCBmZWVkcyBpbiB0aGlzLmluaXRpYWxTdGF0ZVxuICBjb21iaW5lUmVkdWNlcihvYmopIHtcbiAgICBjb25zdCBzb3VyY2VzID0gT2JqZWN0LmVudHJpZXMob2JqKS5tYXAoKFtrLGZuXSkgPT4ge1xuICAgICAgbGV0IHN1YlJlZHVjZXIgPSBmbihvYmopXG4gICAgICAvLyB0aGVyZSBhcmUgdHdvIGZvcm1zIG9mIHJldHVybiB0aGUgc3VicmVkdWNlciBjYW4gaGF2ZVxuICAgICAgLy8gc3RyYWlnaHQgc3RyZWFtIGZvcm1cbiAgICAgIC8vIG9yIG9iamVjdCBmb3JtIHdoZXJlIHdlIG5lZWQgdG8gc2NhbiBpdCBpbnRvIHN0cmluZ1xuICAgICAgaWYgKHN1YlJlZHVjZXIuc291cmNlICYmIHN1YlJlZHVjZXIucmVkdWNlcikgeyAvLyBvYmplY3QgZm9ybVxuICAgICAgICBzdWJSZWR1Y2VyID0gc2NhbihzdWJSZWR1Y2VyLnNvdXJjZSwgXG4gICAgICAgICAgc3ViUmVkdWNlci5yZWR1Y2VyIHx8ICgoXywgbikgPT4gbiksIFxuICAgICAgICAgIHRoaXMuaW5pdGlhbFN0YXRlW2tdXG4gICAgICAgIClcbiAgICAgIH1cbiAgICAgIHJldHVybiBzdWJSZWR1Y2VyXG4gICAgICAgIC5tYXAoeCA9PiAoe1trXTogeH0pKSAvLyBtYXAgdG8gaXRzIHBhcnRpY3VsYXIgbmFtZXNwYWNlXG4gICAgfSlcbiAgICBjb25zdCBzb3VyY2UgPSBtZXJnZSguLi5zb3VyY2VzKVxuICAgIGNvbnN0IHJlZHVjZXIgPSAoYWNjLCBuKSA9PiAoey4uLmFjYywgLi4ubn0pXG4gICAgcmV0dXJuIHtzb3VyY2UsIHJlZHVjZXJ9XG4gIH1cbn1cblxuLy8gZnVuY3Rpb24gdXBkYXRlSW5zdGFuY2UoaW50ZXJuYWxJbnN0YW5jZSkge1xuLy8gICBjb25zdCBwYXJlbnREb20gPSBpbnRlcm5hbEluc3RhbmNlLmRvbS5wYXJlbnROb2RlO1xuLy8gICBjb25zdCBlbGVtZW50ID0gaW50ZXJuYWxJbnN0YW5jZS5lbGVtZW50O1xuLy8gICByZWNvbmNpbGUocGFyZW50RG9tLCBpbnRlcm5hbEluc3RhbmNlLCBlbGVtZW50KTtcbi8vIH1cblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZVB1YmxpY0luc3RhbmNlKGVsZW1lbnQvKiwgaW50ZXJuYWxJbnN0YW5jZSovKSB7XG4gIGNvbnN0IHsgdHlwZSwgcHJvcHMgfSA9IGVsZW1lbnQ7XG4gIGNvbnN0IHB1YmxpY0luc3RhbmNlID0gbmV3IHR5cGUocHJvcHMpO1xuICAvLyBwdWJsaWNJbnN0YW5jZS5fX2ludGVybmFsSW5zdGFuY2UgPSBpbnRlcm5hbEluc3RhbmNlO1xuICByZXR1cm4gcHVibGljSW5zdGFuY2U7XG59XG4iLCJpbXBvcnQgT2JzZXJ2YWJsZSBmcm9tICd6ZW4tb2JzZXJ2YWJsZSdcbmltcG9ydCB7ZnJvbUV2ZW50LCBzY2FuLCBtZXJnZSwgc3RhcnRXaXRoLCBzd2l0Y2hMYXRlc3R9IGZyb20gJy4vc3d5eGpzJ1xuLy8gaW1wb3J0IHsgdXBkYXRlRG9tUHJvcGVydGllcyB9IGZyb20gXCIuL3VwZGF0ZVByb3BlcnRpZXNcIjtcbmltcG9ydCB7IFRFWFRfRUxFTUVOVCB9IGZyb20gXCIuL2VsZW1lbnRcIjtcbmltcG9ydCB7IGNyZWF0ZVB1YmxpY0luc3RhbmNlIH0gZnJvbSBcIi4vY29tcG9uZW50XCI7XG5pbXBvcnQgaCBmcm9tICd2aXJ0dWFsLWRvbS9oJ1xuLy8gaW1wb3J0IFZOb2RlIGZyb20gXCJ2aXJ0dWFsLWRvbS92bm9kZS92bm9kZVwiXG5pbXBvcnQgVlRleHQgZnJvbSBcInZpcnR1YWwtZG9tL3Zub2RlL3Z0ZXh0XCJcblxuLy8gY29uc3QgY2lyY3VpdEJyZWFrZXJmbGFnID0gZmFsc2UgLy8gc2V0IHRydWUgdG8gZW5hYmxlIGRlYnVnZ2VyIGluIGluZmluaXRlIGxvb3BzXG4vLyBsZXQgY2lyY3VpdEJyZWFrZXIgPSAtNTBcbi8vIHRyYXZlcnNlIGFsbCBjaGlsZHJlbiBhbmQgY29sbGVjdCBhIHN0cmVhbSBvZiBhbGwgc291cmNlc1xuLy8gQU5EIHJlbmRlci4gYSBiaXQgb2YgZHVwbGljYXRpb24sIGJ1dCB3ZSBnZXQgcGVyc2lzdGVudCBpbnN0YW5jZXMgd2hpY2ggaXMgZ29vZFxuZXhwb3J0IGZ1bmN0aW9uIHJlbmRlclN0cmVhbShlbGVtZW50LCBpbnN0YW5jZSwgc3RhdGUsIHN0YXRlTWFwKSB7XG4gIC8vIHRoaXMgaXMgYSBzZXBhcmF0ZSBmdW5jdGlvbiBiZWNhdXNlIHNjb3BlIGdldHMgbWVzc3kgd2hlbiBiZWluZyByZWN1cnNpdmVcbiAgbGV0IGlzTmV3U3RyZWFtID0gZmFsc2UgLy8gYXNzdW1lIG5vIHN0cmVhbSBzd2l0Y2hpbmcgYnkgZGVmYXVsdFxuICAvLyB0aGlzIGlzIHRoZSBmaXJzdCBwaW5nIG9mIGRhdGEgdGhyb3VnaG91dCB0aGUgYXBwXG4gIGxldCBzb3VyY2UgPSBPYnNlcnZhYmxlLm9mKHN0YXRlKSBcbiAgY29uc3QgYWRkVG9TdHJlYW0gPSBfc291cmNlID0+IHtcbiAgICAvLyB2aXNpdCBlYWNoIHNvdXJjZSBhbmQgbWVyZ2Ugd2l0aCBzb3VyY2VcbiAgICBpZiAoX3NvdXJjZSkgcmV0dXJuIHNvdXJjZSA9IG1lcmdlKHNvdXJjZSwgX3NvdXJjZSlcbiAgfVxuICBjb25zdCBtYXJrTmV3U3RyZWFtID0gKCkgPT4gaXNOZXdTdHJlYW0gPSB0cnVlXG4gIGNvbnN0IG5ld0luc3RhbmNlID0gcmVuZGVyKHNvdXJjZSwgYWRkVG9TdHJlYW0sIG1hcmtOZXdTdHJlYW0pKGVsZW1lbnQsIGluc3RhbmNlLCBzdGF0ZSwgc3RhdGVNYXApXG4gIHJldHVybiB7c291cmNlLCBpbnN0YW5jZTogbmV3SW5zdGFuY2UsIGlzTmV3U3RyZWFtfVxufVxuXG4vKiogY29yZSByZW5kZXIgbG9naWMgKi9cbmV4cG9ydCBmdW5jdGlvbiByZW5kZXIoc291cmNlLCBhZGRUb1N0cmVhbSwgbWFya05ld1N0cmVhbSkgeyAvLyB0aGlzIGlzIHRoZSBub25yZWN1cnNpdmUgcGFydFxuICByZXR1cm4gZnVuY3Rpb24gcmVuZGVyV2l0aFN0cmVhbShlbGVtZW50LCBpbnN0YW5jZSwgc3RhdGUsIHN0YXRlTWFwKSB7IC8vIHJlY3Vyc2l2ZSBwYXJ0XG4gICAgbGV0IG5ld0luc3RhbmNlXG4gICAgY29uc3QgeyB0eXBlLCBwcm9wcyB9ID0gZWxlbWVudFxuICBcbiAgICBjb25zdCBpc0RvbUVsZW1lbnQgPSB0eXBlb2YgdHlwZSA9PT0gXCJzdHJpbmdcIjtcbiAgICAvLyBpZiAoY2lyY3VpdEJyZWFrZXJmbGFnICYmIGNpcmN1aXRCcmVha2VyKysgPiAwKSBkZWJ1Z2dlclxuICAgIGNvbnN0IHtjaGlsZHJlbiA9IFtdLCAuLi5yZXN0fSA9IHByb3BzXG4gICAgaWYgKGlzRG9tRWxlbWVudCkge1xuICAgICAgY29uc3QgY2hpbGRJbnN0YW5jZXMgPSBjaGlsZHJlbi5tYXAoXG4gICAgICAgIChlbCwgaSkgPT4ge1xuICAgICAgICAgIC8vIHVnbHkgYnV0IG5lY2Vzc2FyeSB0byBhbGxvdyBmdW5jdGlvbmFsIGNoaWxkcmVuXG4gICAgICAgICAgLy8gbWFwcGluZyBlbGVtZW50J3MgY2hpbGRyZW4gdG8gaW5zdGFuY2UncyBjaGlsZEluc3RhbmNlc1xuICAgICAgICAgIGNvbnN0IF9jaGlsZEluc3RhbmNlcyA9IGluc3RhbmNlICYmIChpbnN0YW5jZS5jaGlsZEluc3RhbmNlIHx8IGluc3RhbmNlLmNoaWxkSW5zdGFuY2VzW2ldKVxuICAgICAgICAgIHJldHVybiByZW5kZXJXaXRoU3RyZWFtKCAgLy8gcmVjdXJzaW9uXG4gICAgICAgICAgICBlbCwgXG4gICAgICAgICAgICBfY2hpbGRJbnN0YW5jZXMsIFxuICAgICAgICAgICAgc3RhdGUsIFxuICAgICAgICAgICAgc3RhdGVNYXApIFxuICAgICAgICB9XG4gICAgICApO1xuICAgICAgY29uc3QgY2hpbGREb21zID0gY2hpbGRJbnN0YW5jZXMubWFwKGNoaWxkSW5zdGFuY2UgPT4gY2hpbGRJbnN0YW5jZS5kb20pO1xuICAgICAgbGV0IGxjYXNlUHJvcHMgPSB7fVxuICAgICAgT2JqZWN0LmVudHJpZXMocmVzdCkuZm9yRWFjaCgoW2ssIHZdKSA9PiBsY2FzZVByb3BzW2Zvcm1hdFByb3BzKGspXSA9IHYpXG4gICAgICBjb25zdCBkb20gPSB0eXBlID09PSBURVhUX0VMRU1FTlRcbiAgICAgICAgPyBuZXcgVlRleHQocHJvcHMubm9kZVZhbHVlKVxuICAgICAgICA6IGgodHlwZSwgbGNhc2VQcm9wcywgY2hpbGREb21zKTsgLy8gZXF1aXZhbGVudCBvZiBhcHBlbmRjaGlsZFxuICAgICAgbmV3SW5zdGFuY2UgPSB7IGRvbSwgZWxlbWVudCwgY2hpbGRJbnN0YW5jZXMgfTtcbiAgICB9IGVsc2UgeyAvLyBjb21wb25lbnQgZWxlbWVudFxuICAgICAgbGV0IHB1YmxpY0luc3RhbmNlIFxuICAgICAgLy8gZGVidWdnZXJcbiAgICAgIGlmIChpbnN0YW5jZSAmJiBpbnN0YW5jZS5wdWJsaWNJbnN0YW5jZSAmJiBpbnN0YW5jZS5lbGVtZW50ID09PSBlbGVtZW50KSB7IC8vIG1pZ2h0IGhhdmUgdG8gZG8gbW9yZSBkaWZmaW5nIG9mIHByb3BzXG4gICAgICAgIC8vIGp1c3QgcmV1c2Ugb2xkIGluc3RhbmNlIGlmIGl0IGFscmVhZHkgZXhpc3RzXG4gICAgICAgIHB1YmxpY0luc3RhbmNlID0gaW5zdGFuY2UgJiYgaW5zdGFuY2UucHVibGljSW5zdGFuY2VcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIG1hcmtOZXdTdHJlYW0oKSAvLyBtYXJrIGFzIGRpcnR5IGluIHBhcmVudCBzY29wZTsgd2lsbCByZXJlbmRlclxuICAgICAgICBwdWJsaWNJbnN0YW5jZSA9IGNyZWF0ZVB1YmxpY0luc3RhbmNlKGVsZW1lbnQpO1xuICAgICAgfVxuICAgICAgbGV0IGxvY2FsU3RhdGUgPSBzdGF0ZU1hcC5nZXQocHVibGljSW5zdGFuY2UpXG4gICAgICBpZiAobG9jYWxTdGF0ZSA9PT0gdW5kZWZpbmVkKSBsb2NhbFN0YXRlID0gcHVibGljSW5zdGFuY2UuaW5pdGlhbFN0YXRlXG4gICAgICBwdWJsaWNJbnN0YW5jZS5zdGF0ZSA9IGxvY2FsU3RhdGUgLy8gZm9yIGFjY2VzcyB3aXRoIHRoaXMuc3RhdGVcbiAgICAgIGlmIChPYmplY3Qua2V5cyhyZXN0KS5sZW5ndGgpIHB1YmxpY0luc3RhbmNlLnByb3BzID0gcmVzdCAvLyB1cGRhdGUgd2l0aCBuZXcgcHJvcHMgLy8gVE9ETzogcG90ZW50aWFsbHkgYnVnZ3lcbiAgICAgIC8vIGNvbnNvbGUubG9nKHtyZXN0fSlcbiAgICAgIGlmIChwdWJsaWNJbnN0YW5jZS5zb3VyY2UpIHtcbiAgICAgICAgY29uc3Qgc3JjID0gcHVibGljSW5zdGFuY2Uuc291cmNlKHNvdXJjZSlcbiAgICAgICAgLy8gdGhlcmUgYXJlIHR3byBmb3JtcyBvZiBDb21wb25lbnQuc291cmNlXG4gICAgICAgIGNvbnN0IHNyYyQgPSBzcmMucmVkdWNlciAmJiBwdWJsaWNJbnN0YW5jZS5pbml0aWFsU3RhdGUgIT09IHVuZGVmaW5lZCA/IFxuICAgICAgICAgICAgLy8gMS4gdGhlIHJlZHVjZXIgZm9ybVxuICAgICAgICAgICAgc2NhbihzcmMuc291cmNlLCBzcmMucmVkdWNlciwgcHVibGljSW5zdGFuY2UuaW5pdGlhbFN0YXRlKSA6IFxuICAgICAgICAgICAgLy8gMi4gYW5kIHJhdyBzdHJlYW0gZm9ybVxuICAgICAgICAgICAgc3JjXG4gICAgICAgIGFkZFRvU3RyZWFtKHNyYyRcbiAgICAgICAgICAubWFwKGV2ZW50ID0+IHtcbiAgICAgICAgICAgIHN0YXRlTWFwLnNldChwdWJsaWNJbnN0YW5jZSwgZXZlbnQpXG4gICAgICAgICAgICByZXR1cm4ge2luc3RhbmNlOiBwdWJsaWNJbnN0YW5jZSwgZXZlbnR9IC8vIHRhZyBpdCB0byB0aGUgaW5zdGFuY2VcbiAgICAgICAgICB9KSBcbiAgICAgICAgKTtcbiAgICAgIH1cbiAgICAgIGNvbnN0IGNoaWxkRWxlbWVudCA9IHB1YmxpY0luc3RhbmNlLnJlbmRlciA/IFxuICAgICAgICAgIHB1YmxpY0luc3RhbmNlLnJlbmRlcihsb2NhbFN0YXRlLCBzdGF0ZU1hcCkgOiBcbiAgICAgICAgICBwdWJsaWNJbnN0YW5jZTtcblxuICAgICAgY29uc3QgY2hpbGRJbnN0YW5jZSA9IHJlbmRlcldpdGhTdHJlYW0oY2hpbGRFbGVtZW50LCBpbnN0YW5jZSAmJiBpbnN0YW5jZS5jaGlsZEluc3RhbmNlLCBzdGF0ZSwgc3RhdGVNYXApXG4gICAgICBjb25zdCBkb20gPSBjaGlsZEluc3RhbmNlLmRvbVxuICAgICAgbmV3SW5zdGFuY2UgPSB7IGRvbSwgZWxlbWVudCwgY2hpbGRJbnN0YW5jZSwgcHVibGljSW5zdGFuY2UgfVxuICAgIH1cbiAgICByZXR1cm4gbmV3SW5zdGFuY2VcbiAgfVxufVxuXG5mdW5jdGlvbiBmb3JtYXRQcm9wcyhrKSB7XG4gIGlmIChrLnN0YXJ0c1dpdGgoJ29uJykpIHJldHVybiBrLnRvTG93ZXJDYXNlKClcbiAgcmV0dXJuIGtcbn0iLCJpbXBvcnQgT2JzZXJ2YWJsZSBmcm9tICd6ZW4tb2JzZXJ2YWJsZSdcbmltcG9ydCB7ZnJvbUV2ZW50LCBzY2FuLCBtZXJnZSwgc3RhcnRXaXRoLCBzd2l0Y2hMYXRlc3R9IGZyb20gJy4vc3d5eGpzJ1xuaW1wb3J0IGRpZmYgZnJvbSAndmlydHVhbC1kb20vZGlmZic7XG5pbXBvcnQgcGF0Y2ggZnJvbSAndmlydHVhbC1kb20vcGF0Y2gnO1xuaW1wb3J0IGNyZWF0ZUVsZW1lbnQgZnJvbSAndmlydHVhbC1kb20vY3JlYXRlLWVsZW1lbnQnO1xuaW1wb3J0IHsgY3JlYXRlQ2hhbmdlRW1pdHRlciB9IGZyb20gJ2NoYW5nZS1lbWl0dGVyJ1xuaW1wb3J0IHsgcmVuZGVyU3RyZWFtIH0gZnJvbSAnLi9yZWNvbmNpbGVyJ1xuXG5leHBvcnQgY29uc3Qgc3RhdGVNYXBQb2ludGVyID0gbmV3IE1hcCgpXG5cbmxldCBjaXJjdWl0QnJlYWtlciA9IC0yMFxuXG5jb25zdCBlbWl0dGVyID0gY3JlYXRlQ2hhbmdlRW1pdHRlcigpXG4vLyBzaW5nbGUgVUkgdGhyZWFkOyB0aGlzIGlzIHRoZSBvYnNlcnZhYmxlIHRoYXQgc3RpY2tzIGFyb3VuZCBhbmQgc3dhcHMgb3V0IHNvdXJjZVxuY29uc3QgVUl0aHJlYWQgPSBuZXcgT2JzZXJ2YWJsZShvYnNlcnZlciA9PiB7XG4gIGVtaXR0ZXIubGlzdGVuKHggPT4ge1xuICAgIC8vIGRlYnVnZ2VyIC8vIHN1Y2Nlc3MhIHRocmVhZCBzd2l0Y2hpbmchXG4gICAgb2JzZXJ2ZXIubmV4dCh4KVxuICB9KVxufSlcbi8vIG1vdW50IHRoZSB2ZG9tIG9uIHRvIHRoZSBkb20gYW5kIFxuLy8gc2V0IHVwIHRoZSBydW50aW1lIGZyb20gc291cmNlcyBhbmRcbi8vIHBhdGNoIHRoZSB2ZG9tXG4vLyAtLS1cbi8vIHJldHVybnMgYW4gdW5zdWJzY3JpYmUgbWV0aG9kIHlvdSBjYW4gdXNlIHRvIHVubW91bnRcbmV4cG9ydCBmdW5jdGlvbiBtb3VudChyb290RWxlbWVudCwgY29udGFpbmVyKSB7XG4gIC8vIGluaXRpYWwsIHRocm93YXdheS1pc2ggZnJhbWVcbiAgbGV0IHtzb3VyY2UsIGluc3RhbmNlfSA9IHJlbmRlclN0cmVhbShyb290RWxlbWVudCwge30sIHVuZGVmaW5lZCwgc3RhdGVNYXBQb2ludGVyKVxuICBsZXQgaW5zdGFuY2VQb2ludGVyID0gaW5zdGFuY2VcbiAgY29uc3Qgcm9vdE5vZGUgPSBjcmVhdGVFbGVtZW50KGluc3RhbmNlLmRvbSlcbiAgY29uc3QgY29udGFpbmVyQ2hpbGQgPSBjb250YWluZXIuZmlyc3RFbGVtZW50Q2hpbGRcbiAgaWYgKGNvbnRhaW5lckNoaWxkKSB7XG4gICAgY29udGFpbmVyLnJlcGxhY2VDaGlsZChyb290Tm9kZSxjb250YWluZXJDaGlsZCkgLy8gaG90IHJlbG9hZGVkIG1vdW50XG4gIH0gZWxzZSB7XG4gICAgY29udGFpbmVyLmFwcGVuZENoaWxkKHJvb3ROb2RlKSAvLyBpbml0aWFsIG1vdW50XG4gIH1cbiAgbGV0IGN1cnJlbnRTcmMkID0gbnVsbFxuICBsZXQgU29TID0gc3RhcnRXaXRoKFVJdGhyZWFkLCBzb3VyY2UpIC8vIHN0cmVhbSBvZiBzdHJlYW1zXG4gIHJldHVybiBTb1Muc3Vic2NyaWJlKFxuICAgIHNyYyQgPT4geyAvLyB0aGlzIGlzIHRoZSBjdXJyZW50IHNvdXJjZVN0cmVhbSB3ZSBhcmUgd29ya2luZyB3aXRoXG4gICAgICBpZiAoY3VycmVudFNyYyQpIGNvbnNvbGUubG9nKCd1bnN1YiEnKSB8fCBjdXJyZW50U3JjJC51bnN1YnNjcmliZSgpIC8vIHVuc3ViIGZyb20gb2xkIHN0cmVhbVxuICAgICAgLyoqKiogbWFpbiAqL1xuICAgICAgY29uc3Qgc291cmNlMiQgPSBzY2FuKFxuICAgICAgICBzcmMkLCBcbiAgICAgICAgKHtpbnN0YW5jZSwgc3RhdGVNYXB9LCBuZXh0U3RhdGUpID0+IHtcbiAgICAgICAgICBjb25zdCBzdHJlYW1PdXRwdXQgPSByZW5kZXJTdHJlYW0ocm9vdEVsZW1lbnQsIGluc3RhbmNlLCBuZXh0U3RhdGUsIHN0YXRlTWFwKVxuICAgICAgICAgIGlmIChzdHJlYW1PdXRwdXQuaXNOZXdTdHJlYW0pIHsgLy8gcXVpY2sgY2hlY2tcbiAgICAgICAgICAgIGNvbnN0IG5leHRTb3VyY2UkID0gc3RyZWFtT3V0cHV0LnNvdXJjZVxuICAgICAgICAgICAgLy8gZGVidWdnZXJcbiAgICAgICAgICAgIGluc3RhbmNlUG9pbnRlciA9IHN0cmVhbU91dHB1dC5pbnN0YW5jZVxuICAgICAgICAgICAgcGF0Y2gocm9vdE5vZGUsIGRpZmYoaW5zdGFuY2UuZG9tLCBpbnN0YW5jZVBvaW50ZXIuZG9tKSkgLy8gcmVuZGVyIHRvIHNjcmVlblxuICAgICAgICAgICAgZW1pdHRlci5lbWl0KG5leHRTb3VyY2UkKSAvLyB1cGRhdGUgdGhlIFVJIHRocmVhZDsgc291cmNlIHdpbGwgc3dpdGNoXG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGNvbnN0IG5leHRpbnN0YW5jZSA9IHN0cmVhbU91dHB1dC5pbnN0YW5jZVxuICAgICAgICAgICAgcGF0Y2gocm9vdE5vZGUsIGRpZmYoaW5zdGFuY2UuZG9tLCBuZXh0aW5zdGFuY2UuZG9tKSkgLy8gcmVuZGVyIHRvIHNjcmVlblxuICAgICAgICAgICAgcmV0dXJuIHtpbnN0YW5jZTogbmV4dGluc3RhbmNlLCBzdGF0ZU1hcDogc3RhdGVNYXB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7aW5zdGFuY2U6IGluc3RhbmNlUG9pbnRlciwgc3RhdGVNYXA6IHN0YXRlTWFwUG9pbnRlcn0gLy8gYWNjdW11bGF0b3JcbiAgICAgIClcbiAgICAgIC8qKioqIGVuZCBtYWluICovXG4gICAgICBjdXJyZW50U3JjJCA9IFxuICAgICAgICBzb3VyY2UyJFxuICAgICAgICAgIC5zdWJzY3JpYmUoKVxuICAgIH1cbiAgKVxufVxuIiwiaW1wb3J0IHsgY3JlYXRlRWxlbWVudCwgY3JlYXRlSGFuZGxlciB9IGZyb20gXCIuL2VsZW1lbnRcIjtcbmltcG9ydCB7IENvbXBvbmVudCB9IGZyb20gXCIuL2NvbXBvbmVudFwiO1xuaW1wb3J0IHsgcmVuZGVyU3RyZWFtIH0gZnJvbSBcIi4vcmVjb25jaWxlclwiXG5pbXBvcnQgeyBtb3VudCB9IGZyb20gXCIuL3NjaGVkdWxlclwiO1xuXG5leHBvcnQgZGVmYXVsdCB7XG4gIHJlbmRlclN0cmVhbSxcbiAgY3JlYXRlRWxlbWVudCxcbiAgY3JlYXRlSGFuZGxlcixcbiAgQ29tcG9uZW50LFxuICBtb3VudFxufTtcblxuZXhwb3J0IHsgY3JlYXRlRWxlbWVudCwgY3JlYXRlSGFuZGxlciwgQ29tcG9uZW50LCBcbiAgcmVuZGVyU3RyZWFtLCBcbiAgbW91bnQgfTtcbiJdLCJuYW1lcyI6WyJURVhUX0VMRU1FTlQiLCJjcmVhdGVFbGVtZW50IiwidHlwZSIsImNvbmZpZyIsInByb3BzIiwiT2JqZWN0IiwiYXNzaWduIiwiYXJncyIsImhhc0NoaWxkcmVuIiwibGVuZ3RoIiwicmF3Q2hpbGRyZW4iLCJjb25jYXQiLCJjaGlsZHJlbiIsImZpbHRlciIsImMiLCJtYXAiLCJjcmVhdGVUZXh0RWxlbWVudCIsInZhbHVlIiwibm9kZVZhbHVlIiwiY3JlYXRlSGFuZGxlciIsIl9mbiIsImVtaXR0ZXIiLCJjcmVhdGVDaGFuZ2VFbWl0dGVyIiwiaGFuZGxlciIsImVtaXQiLCJ4IiwiJCIsIk9ic2VydmFibGUiLCJsaXN0ZW4iLCJuZXh0IiwiTk9JTklUIiwiU3ltYm9sIiwic2NhbiIsIm9icyIsImNiIiwic2VlZCIsInN1YiIsImFjYyIsImhhc1ZhbHVlIiwiaGFzU2VlZCIsInN1YnNjcmliZSIsIm9ic2VydmVyIiwiY2xvc2VkIiwiZmlyc3QiLCJlIiwiZXJyb3IiLCJzdGFydFdpdGgiLCJ2YWwiLCJDb21wb25lbnQiLCJzdGF0ZSIsIm9iaiIsInNvdXJjZXMiLCJlbnRyaWVzIiwiayIsImZuIiwic3ViUmVkdWNlciIsInNvdXJjZSIsInJlZHVjZXIiLCJfIiwibiIsImluaXRpYWxTdGF0ZSIsIm1lcmdlIiwiY3JlYXRlUHVibGljSW5zdGFuY2UiLCJlbGVtZW50IiwicHVibGljSW5zdGFuY2UiLCJyZW5kZXJTdHJlYW0iLCJpbnN0YW5jZSIsInN0YXRlTWFwIiwiaXNOZXdTdHJlYW0iLCJvZiIsImFkZFRvU3RyZWFtIiwiX3NvdXJjZSIsIm1hcmtOZXdTdHJlYW0iLCJuZXdJbnN0YW5jZSIsInJlbmRlciIsInJlbmRlcldpdGhTdHJlYW0iLCJpc0RvbUVsZW1lbnQiLCJyZXN0IiwiY2hpbGRJbnN0YW5jZXMiLCJlbCIsImkiLCJfY2hpbGRJbnN0YW5jZXMiLCJjaGlsZEluc3RhbmNlIiwiY2hpbGREb21zIiwiZG9tIiwibGNhc2VQcm9wcyIsImZvckVhY2giLCJ2IiwiZm9ybWF0UHJvcHMiLCJWVGV4dCIsImgiLCJsb2NhbFN0YXRlIiwiZ2V0IiwidW5kZWZpbmVkIiwia2V5cyIsInNyYyIsInNyYyQiLCJzZXQiLCJldmVudCIsImNoaWxkRWxlbWVudCIsInN0YXJ0c1dpdGgiLCJ0b0xvd2VyQ2FzZSIsInN0YXRlTWFwUG9pbnRlciIsIk1hcCIsIlVJdGhyZWFkIiwibW91bnQiLCJyb290RWxlbWVudCIsImNvbnRhaW5lciIsImluc3RhbmNlUG9pbnRlciIsInJvb3ROb2RlIiwiY29udGFpbmVyQ2hpbGQiLCJmaXJzdEVsZW1lbnRDaGlsZCIsInJlcGxhY2VDaGlsZCIsImFwcGVuZENoaWxkIiwiY3VycmVudFNyYyQiLCJTb1MiLCJjb25zb2xlIiwibG9nIiwidW5zdWJzY3JpYmUiLCJzb3VyY2UyJCIsIm5leHRTdGF0ZSIsInN0cmVhbU91dHB1dCIsIm5leHRTb3VyY2UkIiwiZGlmZiIsIm5leHRpbnN0YW5jZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7OztBQUdPLElBQU1BLGVBQWUsY0FBckI7O0FBRVAsQUFBTyxTQUFTQyxlQUFULENBQXVCQyxJQUF2QixFQUE2QkMsTUFBN0IsRUFBOEM7OztNQUM3Q0MsUUFBUUMsT0FBT0MsTUFBUCxDQUFjLEVBQWQsRUFBa0JILE1BQWxCLENBQWQ7O29DQUQ2Q0ksSUFBTTtRQUFBOzs7TUFFN0NDLGNBQWNELEtBQUtFLE1BQUwsR0FBYyxDQUFsQztNQUNNQyxjQUFjRixjQUFjLFlBQUdHLE1BQUgsYUFBYUosSUFBYixDQUFkLEdBQW1DLEVBQXZEO1FBQ01LLFFBQU4sR0FBaUJGLFlBQ2RHLE1BRGMsQ0FDUDtX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 364 | -------------------------------------------------------------------------------- /demos/components/blink.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | import {mount, createElement, Component, createHandler} from '../../reactive-react' 3 | import {Interval, scan, startWith, merge, mapToConstant} from '../../reactive-react/swyxjs' 4 | import Observable from 'zen-observable' 5 | 6 | export default class Blink extends Component { 7 | // more fun time demo 8 | // initialState = true // proper 9 | initialState = 0 // hacky 10 | source($) { 11 | // there is a bug right now where switching sources subscribes to the new source twice 12 | // havent been able to chase it down so i had to do this hacky thing 13 | // i'm sorry :( breaking demos last minute before talk sucks 14 | // const reducer = x => !x // the proper reducer 15 | const reducer = (acc, x) => acc + x // hacky reducer 16 | const source = Interval(500, 1) // tick every second 17 | return {source, reducer} 18 | } 19 | render(state) { 20 | const style = { 21 | visibility: ((state/2 + 1) % 2) ? // hacky 22 | 'visible' : 23 | 'hidden'} 24 | return
Bring back the blink tag!
25 | } 26 | } -------------------------------------------------------------------------------- /demos/components/button.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* BonBon Buttons 1.1 by simurai.com 4 | 5 | 1.1 Added unprefixed attributes, :focus style, */} 40 | + 41 | 42 |
43 |
44 |
45 | 💩 46 |
47 |
48 |
49 |
50 |
51 |
52 | 🎯 53 |
54 |
55 |
56 | 57 | 58 | } 59 | } 60 | 61 | 62 | // {/*

Bird:

63 | //

Target:

*/} -------------------------------------------------------------------------------- /demos/components/index.js: -------------------------------------------------------------------------------- 1 | import Timer from './timer' 2 | import Blink from './blink' 3 | import Counter, {Counters} from './counter' 4 | import CrappyBird from './crappybird' 5 | import Source, {SourceSwitching} from './sourceswitching' 6 | import CrazyCharts from '../time-slicing' 7 | 8 | // barrel rollup of all neighbors 9 | // export Timer from './timer' 10 | // export Blink from './blink' 11 | // export Counter, {Counters} from './counter' 12 | // export CrappyBird from './crappybird' 13 | // export Source, {SourceSwitching} from './sourceswitching' 14 | export {Timer, Blink, Counter, Counters, CrappyBird, Source, SourceSwitching, CrazyCharts} -------------------------------------------------------------------------------- /demos/components/sourceswitching.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | import {mount, createElement, Component, createHandler} from '../../reactive-react' 3 | import {Interval, scan, startWith, merge, mapToConstant} from '../../reactive-react/swyxjs' 4 | import Observable from 'zen-observable' 5 | 6 | import Timer from './timer' 7 | import Counter from './counter' 8 | 9 | export class SourceSwitching extends Component { 10 | initialState = true 11 | toggle = createHandler() 12 | source($) { 13 | const source = this.toggle.$ 14 | const reducer = x => !x 15 | return {source, reducer} 16 | } 17 | render(state, stateMap) { 18 | return
19 | 20 | { 21 | state ? this.props.left : this.props.right 22 | } 23 |
24 | } 25 | } 26 | 27 | export default function Source() { 28 | // demonstrate ability to switch sources 29 | return } 31 | left={} 32 | right={} 33 | /> 34 | } -------------------------------------------------------------------------------- /demos/components/timer.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | import {mount, createElement, Component, createHandler} from '../../reactive-react' 3 | import {Interval, scan, startWith, merge, mapToConstant} from '../../reactive-react/swyxjs' 4 | import Observable from 'zen-observable' 5 | 6 | export default class Timer extends Component { 7 | // demonstrate interval time 8 | initialState = 0 9 | source($) { 10 | const source = Interval(this.props.ms) // tick every second 11 | const reducer = x => x + 0.5 // count up 12 | // source returns an observable 13 | return scan(source, reducer, 0) // from zero 14 | } 15 | render(state, stateMap) { 16 | return
number of seconds elapsed: {state}
17 | } 18 | } -------------------------------------------------------------------------------- /demos/custom.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: rgb(2,0,36); 3 | background: linear-gradient(45deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 23%, rgba(161,0,0,1) 100%); 4 | } 5 | html { 6 | overflow: scroll; 7 | } 8 | .container { 9 | width: 700px 10 | } 11 | 12 | .titleLink { 13 | display: flex 14 | } 15 | 16 | .titleLink a { 17 | margin-left: 10px; 18 | } -------------------------------------------------------------------------------- /demos/eleventy.css: -------------------------------------------------------------------------------- 1 | *{margin:0;padding:0}::-moz-selection{background-color:#222;color:#067ada}::selection{background-color:#222;color:#067ada}html{background-color:#222}body{text-align:center;background-color:#067ada;color:#cde4f8;font-family:sans-serif;font-weight:30;font-size:16px;line-height:1.8}@media (min-width: 740px){body{font-size:18px}}.container{margin-left:auto;margin-right:auto;width:90%;text-align:left}h1,h2,h3{color:#fff;font-weight:600;margin-top:2.5em;margin-bottom:1em;line-height:1em}h1{margin-top:0.4em;font-size:4em;line-height:0.9em}@media (min-width: 740px){h1{font-size:5em}}@media (min-width: 1200px){h1{font-size:6em}}p{margin-bottom:1em}.subtitle{margin-top:-3em;margin-bottom:6em}ul,ol{padding-left:1em}a:link,a:visited{color:#222;text-decoration:none;border-bottom:solid 1px #1f87de}a:hover,a:focus{color:#fff;border-bottom:solid 1px #222}.nav{padding-top:3em}.nav li{display:inline}.nav a:link,.nav a:visited{display:inline-block;border-top:solid 1px #9bcaf0;border-bottom-style:none;padding-top:0.8em;padding-bottom:2em;margin-left:0;margin-right:0.7em;width:8em;text-align:left;color:#cde4f8;text-decoration:none}.nav a:hover,.nav a:focus{color:#fff;border-top:solid 1px #222;border-bottom-style:none}.nav small{display:block;font-size:0.7em;color:#9bcaf0}code{font-size:0.8em;background-color:#0971c8;color:#fff;padding-top:2px;padding-bottom:2px;padding-left:4px;padding-right:4px}pre{margin-top:2em;margin-bottom:2em;padding-top:1em;padding-bottom:1em;padding-left:2em;padding-right:2em;line-height:1.2;background-color:#0971c8;border:solid 1px #0e60a3;overflow:auto}pre code{border-style:none;padding-top:0;padding-bottom:0;padding-left:0;padding-right:0}footer{margin-top:6em;padding-top:4em;padding-bottom:6em;border-top:solid 1px #1f87de;font-size:0.7em;color:#6aafe9}footer a:link,footer a:visited{color:#b4d7f4;border-bottom:solid 1px #1a3c59}footer a:hover,footer a:focus{color:#222;border-bottom:solid 1px #fff} 2 | -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demos/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | import {mount, createElement, Component, createHandler} from '../reactive-react' 3 | import {Interval, scan, startWith, merge, mapToConstant} from '../reactive-react/swyxjs' 4 | import Observable from 'zen-observable' 5 | 6 | import {Timer, Blink, Counter, Counters, CrappyBird, Source, SourceSwitching, CrazyCharts} from './components' 7 | 8 | import './eleventy.css' 9 | import './custom.css' 10 | 11 | class App extends Component { 12 | initialState = "counter" 13 | router = createHandler(e => e.target.name) 14 | source($) { 15 | return this.router.$ 16 | } 17 | render(state) { 18 | const Display = { 19 | 'counter': () => , 20 | // 'timer': () => , 21 | 'blink': () => , 22 | 'crappy': () => , 23 | 'charts': () => , 24 | }[state] || (() => ) 25 | 26 | function selectedLink(name) { 27 | return { 28 | name, 29 | style: state === name && {color: 'yellow'} 30 | } 31 | } 32 | return
33 |
34 |
reactive-react Demo: {state}
35 |
36 | @swyx 37 | ☢️ 38 | Slides 39 | Github 40 |
41 |
42 |
43 | 60 |
61 | {Display()} 62 |
63 | By swyx 64 |
65 |
66 | } 67 | } 68 | 69 | mount(, document.getElementById('app')) 70 | -------------------------------------------------------------------------------- /demos/time-slicing/Charts.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | import {mount, createElement, Component, createHandler} from '../../reactive-react' 3 | import * as d3 from "d3"; 4 | 5 | const colors = ['#fff489', '#fa57c1', '#b166cc', '#7572ff', '#69a6f9']; 6 | const containerWidth = 565 / 2 7 | const containerHeight = 400 / 2 8 | 9 | 10 | export default class Charts extends Component { 11 | render() { 12 | const data = this.props.data; 13 | const container = document.getElementById("demo") 14 | if (!container) return
Type something
// initial load only 15 | bottomChart(data) 16 | leftChart(data) 17 | rightChart(data) 18 | return ( 19 |
20 |

number of datapoints: {data[0].length * 5}

21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 | ); 35 | } 36 | } 37 | 38 | 39 | function responsivefy(svg) { 40 | // get container + svg aspect ratio 41 | if (!svg.node()) return 42 | var container = d3.select(svg.node().parentNode), 43 | width = parseInt(svg.style("width")), 44 | height = parseInt(svg.style("height")), 45 | aspect = width / height; 46 | 47 | // add viewBox and preserveAspectRatio properties, 48 | // and call resize so that svg resizes on inital page load 49 | svg.attr("viewBox", "0 0 " + width + " " + height) 50 | .attr("preserveAspectRatio", "xMinYMid") 51 | .call(resize); 52 | 53 | // to register multiple listeners for same event type, 54 | // you need to add namespace, i.e., 'click.foo' 55 | // necessary if you call invoke this function for multiple svgs 56 | // api docs: https://github.com/mbostock/d3/wiki/Selections#on 57 | d3.select(window).on("resize." + container.attr("id"), resize); 58 | 59 | // get width of container and resize svg to fit it 60 | function resize() { 61 | var targetWidth = parseInt(container.style("width")); 62 | svg.attr("width", targetWidth); 63 | svg.attr("height", Math.round(targetWidth / aspect)); 64 | } 65 | } 66 | 67 | function bottomChart(data) { 68 | 69 | var margin = { top: 10, right: 20, bottom: 30, left: 30 }; 70 | var width = containerWidth * 2 - margin.left - margin.right; 71 | var height = containerHeight - margin.top - margin.bottom; 72 | 73 | // really janky clearance 74 | const temp = d3.select(".bottomchart") 75 | .selectAll("svg") 76 | if (temp._groups[0] && temp._groups[0].length) temp._groups[0] 77 | .forEach((d, i) => d.parentNode.removeChild(d)) 78 | 79 | var svg = d3.select('.bottomchart') 80 | .append('svg') 81 | .attr('width', width + margin.left + margin.right) 82 | .attr('height', height + margin.top + margin.bottom) 83 | // .call(responsivefy) 84 | .append('g') 85 | .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')'); 86 | 87 | var xScale = d3.scaleLinear() 88 | .domain([ 89 | d3.min(data, co => d3.min(co, d => d.x)), 90 | d3.max(data, co => d3.max(co, d => d.x)) 91 | ]) 92 | .range([0, width]); 93 | svg 94 | .append('g') 95 | .attr('transform', `translate(0, ${height})`) 96 | .style('stroke', 'yellow') 97 | .call(d3.axisBottom(xScale).ticks(5)); 98 | 99 | var yScale = d3.scaleLinear() 100 | .domain([ 101 | d3.min(data, co => d3.min(co, d => d.y)), 102 | d3.max(data, co => d3.max(co, d => d.y)) 103 | ]) 104 | .range([height, 0]); 105 | svg 106 | .append('g') 107 | .style('stroke', 'yellow') 108 | .call(d3.axisLeft(yScale)); 109 | 110 | var area = d3.area() 111 | .x(d => xScale(d.x)) 112 | .y0(yScale(yScale.domain()[0])) 113 | .y1(d => yScale(d.y)) 114 | .curve(d3.curveCatmullRom.alpha(0.5)); 115 | 116 | svg 117 | .selectAll('.area') 118 | .data(data) 119 | .enter() 120 | .append('path') 121 | .attr('class', 'area') 122 | .attr('d', d => area(d)) 123 | .style('stroke', (d, i) => colors[i]) 124 | .style('stroke-width', 2) 125 | .style('fill', (d, i) => colors[i]) 126 | .style('fill-opacity', 0.5) 127 | } 128 | 129 | 130 | 131 | function rightChart(data) { 132 | 133 | var margin = { top: 10, right: 20, bottom: 30, left: 30 }; 134 | var width = containerWidth - margin.left - margin.right; 135 | var height = containerHeight - margin.top - margin.bottom; 136 | 137 | // really janky clearance 138 | const temp = d3.select(".rightchart") 139 | .selectAll("svg") 140 | if (temp._groups[0] && temp._groups[0].length) temp._groups[0] 141 | .forEach((d, i) => d.parentNode.removeChild(d)) 142 | 143 | var svg = d3.select('.rightchart') 144 | .append('svg') 145 | .attr('width', width + margin.left + margin.right) 146 | .attr('height', height + margin.top + margin.bottom) 147 | // .call(responsivefy) 148 | .append('g') 149 | .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')'); 150 | 151 | var xScale = d3.scaleLinear() 152 | .domain([ 153 | d3.min(data, co => d3.min(co, d => d.x)), 154 | d3.max(data, co => d3.max(co, d => d.x)) 155 | ]) 156 | .range([0, width]); 157 | svg 158 | .append('g') 159 | .attr('transform', `translate(0, ${height})`) 160 | .style('stroke', 'yellow') 161 | .call(d3.axisBottom(xScale).ticks(5)); 162 | 163 | var yScale = d3.scaleLinear() 164 | .domain([ 165 | d3.min(data, co => d3.min(co, d => d.y)), 166 | d3.max(data, co => d3.max(co, d => d.y)) 167 | ]) 168 | .range([height, 0]); 169 | svg 170 | .append('g') 171 | .style('stroke', 'yellow') 172 | .call(d3.axisLeft(yScale)); 173 | 174 | 175 | var line = d3.line() 176 | .x(d => xScale(d.x)) 177 | .y(d => yScale(d.y)) 178 | .curve(d3.curveCatmullRom.alpha(0.5)); 179 | 180 | svg 181 | .selectAll('.line') 182 | .data(data) 183 | .enter() 184 | .append('path') 185 | .attr('class', 'line') 186 | .attr('d', d => line(d)) 187 | // .style('stroke', (d, i) => ['#FF9900', '#3369E8'][i]) 188 | .style('stroke', (d, i) => colors[i]) 189 | .style('stroke-width', 2) 190 | .style('fill', 'none'); 191 | } 192 | 193 | 194 | function leftChart(data) { 195 | 196 | var margin = { top: 10, right: 20, bottom: 30, left: 30 }; 197 | var width = containerWidth - margin.left - margin.right; 198 | var height = containerHeight - margin.top - margin.bottom; 199 | 200 | // really janky clearance 201 | const temp = d3.select(".leftchart") 202 | .selectAll("svg") 203 | if (temp._groups[0] && temp._groups[0].length) temp._groups[0] 204 | .forEach((d, i) => d.parentNode.removeChild(d)) 205 | 206 | var svg = d3.select('.leftchart') 207 | .append('svg') 208 | .attr('width', width + margin.left + margin.right) 209 | .attr('height', height + margin.top + margin.bottom) 210 | // .call(responsivefy) 211 | .append('g') 212 | .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')'); 213 | 214 | var xScale = d3.scaleLinear() 215 | .domain([ 216 | d3.min(data, co => d3.min(co, d => d.x)), 217 | d3.max(data, co => d3.max(co, d => d.x)) 218 | ]) 219 | .range([0, width]); 220 | svg 221 | .append('g') 222 | .attr('transform', `translate(0, ${height})`) 223 | .style('stroke', 'yellow') 224 | .call(d3.axisBottom(xScale).ticks(5)); 225 | 226 | var yScale = d3.scaleLinear() 227 | .domain([ 228 | d3.min(data, co => d3.min(co, d => d.y)), 229 | d3.max(data, co => d3.max(co, d => d.y)) 230 | ]) 231 | .range([height, 0]); 232 | svg 233 | .append('g') 234 | .style('stroke', 'yellow') 235 | .call(d3.axisLeft(yScale)); 236 | 237 | var circlesArr = svg 238 | .selectAll('.ball') 239 | .data(data) 240 | .enter() 241 | .append('g') 242 | .each(function(d, i) { 243 | var node = d3.select(this).selectAll('g') 244 | .data(d) 245 | .enter() 246 | .append('g') 247 | .attr('class', 'ball') 248 | .attr('transform', d => { 249 | return `translate(${xScale(d.x)}, ${yScale(d.y)})`; 250 | }); 251 | node 252 | .append('circle') 253 | .attr('cx', 0) 254 | .attr('cy', 0) 255 | .attr('r', d => 5) 256 | .style('fill-opacity', 0.5) 257 | .style('fill', colors[i]); 258 | }) 259 | } -------------------------------------------------------------------------------- /demos/time-slicing/Clock.js: -------------------------------------------------------------------------------- 1 | // /** @jsx createElement */ 2 | // import {mount, createElement, Component, createHandler} from '../../reactive-react' 3 | 4 | // const SPEED = 0.003 / Math.PI; 5 | // const FRAMES = 10; 6 | 7 | // export default class Clock extends PureComponent { 8 | // faceRef = createRef(); 9 | // arcGroupRef = createRef(); 10 | // clockHandRef = createRef(); 11 | // frame = null; 12 | // hitCounter = 0; 13 | // rotation = 0; 14 | // t0 = Date.now(); 15 | // arcs = []; 16 | 17 | // animate = () => { 18 | // const now = Date.now(); 19 | // const td = now - this.t0; 20 | // this.rotation = (this.rotation + SPEED * td) % (2 * Math.PI); 21 | // this.t0 = now; 22 | 23 | // this.arcs.push({rotation: this.rotation, td}); 24 | 25 | // let lx, ly, tx, ty; 26 | // if (this.arcs.length > FRAMES) { 27 | // this.arcs.forEach(({rotation, td}, i) => { 28 | // lx = tx; 29 | // ly = ty; 30 | // const r = 145; 31 | // tx = 155 + r * Math.cos(rotation); 32 | // ty = 155 + r * Math.sin(rotation); 33 | // const bigArc = SPEED * td < Math.PI ? '0' : '1'; 34 | // const path = `M${tx} ${ty}A${r} ${r} 0 ${bigArc} 0 ${lx} ${ly}L155 155`; 35 | // const hue = 120 - Math.min(120, td / 4); 36 | // const colour = `hsl(${hue}, 100%, ${60 - i * (30 / FRAMES)}%)`; 37 | // if (i !== 0) { 38 | // const arcEl = this.arcGroupRef.current.children[i - 1]; 39 | // arcEl.setAttribute('d', path); 40 | // arcEl.setAttribute('fill', colour); 41 | // } 42 | // }); 43 | // this.clockHandRef.current.setAttribute('d', `M155 155L${tx} ${ty}`); 44 | // this.arcs.shift(); 45 | // } 46 | 47 | // if (this.hitCounter > 0) { 48 | // this.faceRef.current.setAttribute( 49 | // 'fill', 50 | // `hsla(0, 0%, ${this.hitCounter}%, 0.95)` 51 | // ); 52 | // this.hitCounter -= 1; 53 | // } else { 54 | // this.hitCounter = 0; 55 | // this.faceRef.current.setAttribute('fill', 'hsla(0, 0%, 5%, 0.95)'); 56 | // } 57 | 58 | // this.frame = requestAnimationFrame(this.animate); 59 | // }; 60 | 61 | // componentDidMount() { 62 | // this.frame = requestAnimationFrame(this.animate); 63 | // if (this.faceRef.current) { 64 | // this.faceRef.current.addEventListener('click', this.handleClick); 65 | // } 66 | // } 67 | 68 | // componentDidUpdate() { 69 | // console.log('componentDidUpdate()', this.faceRef.current); 70 | // } 71 | 72 | // componentWillUnmount() { 73 | // this.faceRef.current.removeEventListener('click', this.handleClick); 74 | // if (this.frame) { 75 | // cancelAnimationFrame(this.frame); 76 | // } 77 | // } 78 | 79 | // handleClick = e => { 80 | // e.stopPropagation(); 81 | // this.hitCounter = 50; 82 | // }; 83 | 84 | // render() { 85 | // const paths = new Array(FRAMES); 86 | // for (let i = 0; i < FRAMES; i++) { 87 | // paths.push(); 88 | // } 89 | // return ( 90 | //
91 | // 92 | // 100 | // {paths} 101 | // 102 | // 103 | //
104 | // ); 105 | // } 106 | // } 107 | -------------------------------------------------------------------------------- /demos/time-slicing/README.md: -------------------------------------------------------------------------------- 1 | note that this isnt actually a demo of time slicing - its just meant to show the performance issues. 2 | 3 | this was originally written as a separate demo from all the rest and that may change. -------------------------------------------------------------------------------- /demos/time-slicing/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0px; 4 | margin: 0px; 5 | user-select: none; 6 | font-family: Karla, Helvetica Neue, Helvetica, sans-serif; 7 | background: rgb(34, 34, 34); 8 | color: white; 9 | overflow: hidden; 10 | } 11 | 12 | .VictoryContainer { 13 | opacity: 0.8; 14 | } 15 | 16 | * { 17 | box-sizing: border-box; 18 | } 19 | 20 | #root { 21 | height: 100vh; 22 | } 23 | 24 | .container { 25 | width: 100%; 26 | max-width: 960px; 27 | margin: auto; 28 | padding: 10px; 29 | } 30 | 31 | .rendering { 32 | margin-top: 20px; 33 | margin-bottom: 20px; 34 | zoom: 1.8; 35 | } 36 | 37 | label { 38 | zoom: 1; 39 | margin-right: 50px; 40 | font-size: 30px; 41 | } 42 | 43 | label.selected { 44 | font-weight: bold; 45 | } 46 | 47 | label:nth-child(1).selected { 48 | color: rgb(253, 25, 153); 49 | } 50 | 51 | label:nth-child(2).selected { 52 | color: rgb(255, 240, 1); 53 | } 54 | 55 | label:nth-child(3).selected { 56 | color: #61dafb; 57 | } 58 | 59 | .chart { 60 | width: 100%; 61 | height: 100%; 62 | } 63 | 64 | .input { 65 | padding: 16px; 66 | font-size: 30px; 67 | width: 100%; 68 | display: block; 69 | } 70 | .input.sync { 71 | outline-color: rgba(253, 25, 153, 0.1); 72 | } 73 | .input.debounced { 74 | outline-color: rgba(255, 240, 1, 0.1); 75 | } 76 | .input.async { 77 | outline-color: rgba(97, 218, 251, 0.1); 78 | } 79 | 80 | 81 | label { 82 | font-size: 20px; 83 | } 84 | 85 | label label { 86 | display: 'inline-block'; 87 | margin-left: 20px; 88 | } 89 | 90 | .row { 91 | flex: 1; 92 | display: flex; 93 | margin-top: 20px; 94 | min-height: 100%; 95 | } 96 | 97 | .column { 98 | flex: 1; 99 | } 100 | 101 | .demo { 102 | position: relative; 103 | min-height: 100vh; 104 | } 105 | 106 | .stutterer { 107 | transform: scale(1.5); 108 | height: 310px; 109 | width: 310px; 110 | position: absolute; 111 | left: 0; 112 | right: 0; 113 | top: -256px; 114 | bottom: 0; 115 | margin: auto; 116 | box-shadow: 0 0 10px 10px rgba(0, 0, 0, 0.2); 117 | border-radius: 200px; 118 | } 119 | 120 | .clockHand { 121 | stroke: white; 122 | stroke-width: 10px; 123 | stroke-linecap: round; 124 | } 125 | 126 | .clockFace { 127 | stroke: white; 128 | stroke-width: 10px; 129 | } 130 | 131 | .arcHand { 132 | } 133 | 134 | .innerLine { 135 | border-radius: 6px; 136 | position: absolute; 137 | height: 149px; 138 | left: 47.5%; 139 | top: 0%; 140 | width: 5%; 141 | background-color: red; 142 | transform-origin: bottom center; 143 | } 144 | -------------------------------------------------------------------------------- /demos/time-slicing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demos/time-slicing/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx createElement */ 2 | import {mount, createElement, Component, createHandler} from '../../reactive-react' 3 | import {fromEvent} from '../../reactive-react/swyxjs' 4 | import Observable from 'zen-observable' 5 | 6 | import _ from 'lodash'; 7 | import Charts from './Charts'; 8 | // import Clock from './Clock'; 9 | import './index.css'; 10 | 11 | let cachedData = new Map(); 12 | 13 | export default class App extends Component { 14 | initialState = '' 15 | 16 | // Random data for the chart 17 | getStreamData(input) { 18 | if (cachedData.has(input)) { 19 | return cachedData.get(input); 20 | } 21 | const multiplier = input.length !== 0 ? input.length : 1; 22 | const complexity = 23 | (parseInt(window.location.search.substring(1), 10) / 100) * 25 || 25; 24 | const data = _.range(5).map(t => 25 | _.range(complexity * multiplier).map((j, i) => { 26 | return { 27 | x: j, 28 | y: (t + 1) * _.random(0, 255), 29 | }; 30 | }) 31 | ); 32 | cachedData.set(input, data); 33 | return data; 34 | } 35 | 36 | handleChange = createHandler(e => e.target.value) 37 | source() { 38 | return { 39 | source: this.handleChange.$, 40 | reducer: (_, x) => x 41 | } 42 | } 43 | 44 | render(state, stateMap) { 45 | const Wrapper = 'div' 46 | const data = this.getStreamData(state); 47 | return ( 48 |
49 | 56 | 57 |
58 | 59 |
60 |
61 |
62 | ); 63 | } 64 | } 65 | 66 | 67 | const container = document.getElementById('app'); 68 | mount(, container); 69 | -------------------------------------------------------------------------------- /dependencygraph.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | dependency-cruiser output 11 | 12 | 13 | cluster_/reactive-react 14 | 15 | reactive-react 16 | 17 | 18 | 19 | reactive-react/component.js 20 | 21 | 22 | component.js 23 | 24 | 25 | 26 | 27 | 28 | reactive-react/swyxjs.js 29 | 30 | 31 | swyxjs.js 32 | 33 | 34 | 35 | 36 | 37 | reactive-react/component.js->reactive-react/swyxjs.js 38 | 39 | 40 | 41 | 42 | 43 | reactive-react/element.js 44 | 45 | 46 | element.js 47 | 48 | 49 | 50 | 51 | 52 | reactive-react/index.js 53 | 54 | 55 | index.js 56 | 57 | 58 | 59 | 60 | 61 | reactive-react/index.js->reactive-react/component.js 62 | 63 | 64 | 65 | 66 | 67 | reactive-react/index.js->reactive-react/element.js 68 | 69 | 70 | 71 | 72 | 73 | reactive-react/reconciler.js 74 | 75 | 76 | reconciler.js 77 | 78 | 79 | 80 | 81 | 82 | reactive-react/index.js->reactive-react/reconciler.js 83 | 84 | 85 | 86 | 87 | 88 | reactive-react/scheduler.js 89 | 90 | 91 | scheduler.js 92 | 93 | 94 | 95 | 96 | 97 | reactive-react/index.js->reactive-react/scheduler.js 98 | 99 | 100 | 101 | 102 | 103 | reactive-react/reconciler.js->reactive-react/component.js 104 | 105 | 106 | 107 | 108 | 109 | reactive-react/reconciler.js->reactive-react/element.js 110 | 111 | 112 | 113 | 114 | 115 | reactive-react/reconciler.js->reactive-react/swyxjs.js 116 | 117 | 118 | 119 | 120 | 121 | reactive-react/scheduler.js->reactive-react/reconciler.js 122 | 123 | 124 | 125 | 126 | 127 | reactive-react/scheduler.js->reactive-react/swyxjs.js 128 | 129 | 130 | 131 | 132 | 133 | reactive-react/updateProperties.js 134 | 135 | 136 | updateProperties.js 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [Settings] 2 | ID = "reactive-react" 3 | 4 | # for more: https://www.netlify.com/docs/netlify-toml-reference/ 5 | 6 | [build] 7 | # This is the directory to change to before starting a build. 8 | # base = "project/" 9 | # NOTE: This is where we will look for package.json/.nvmrc/etc, not root. 10 | # This is the directory that you are publishing from (relative to root of your repo) 11 | publish = "demos/dist/" 12 | # This will be your default build command 13 | command = "npm run demo:build" 14 | # This is where we will look for your lambda functions 15 | # functions = "project/functions/" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@swyx/reactive-react", 3 | "version": "1.0.2", 4 | "license": "MIT", 5 | "dependencies": { 6 | "change-emitter": "^0.1.6", 7 | "d3": "^5.5.0", 8 | "virtual-dom": "^2.1.1", 9 | "zen-observable": "^0.8.9" 10 | }, 11 | "main": "build/reactive-react.umd.js", 12 | "module": "build/reactive-react.es.js", 13 | "files": [ 14 | "build" 15 | ], 16 | "devDependencies": { 17 | "ava": "^0.19.0", 18 | "babel-cli": "^6.24.1", 19 | "babel-plugin-transform-class-properties": "^6.24.1", 20 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 21 | "babel-plugin-transform-react-jsx": "^6.24.1", 22 | "babel-preset-env": "^1.3.3", 23 | "babel-register": "^6.24.1", 24 | "browser-env": "^2.0.29", 25 | "npm-run-all": "^4.0.2", 26 | "parcel-bundler": "^1.9.7", 27 | "rollup": "^0.41.6", 28 | "rollup-plugin-babel": "^3.0.7" 29 | }, 30 | "scripts": { 31 | "start": "npm run demo:start", 32 | "demo:start": "parcel demos/index.html --no-hmr", 33 | "demo:build": "parcel build demos/index.html -d demos/dist", 34 | "build:deptree": "depcruise --exclude '^node_modules' --output-type dot reactive-react | dot -T svg > dependencygraph.svg", 35 | "build:module": "rollup -c -f es -n reactive-react -o build/reactive-react.es.js", 36 | "build:main": "rollup -c -f umd -n reactive-react -o build/reactive-react.umd.js", 37 | "build": "run-p build:module build:main build:deptree", 38 | "prepublishOnly": "npm run build" 39 | }, 40 | "babel": { 41 | "plugins": [ 42 | "transform-object-rest-spread", 43 | "transform-class-properties", 44 | [ 45 | "transform-react-jsx", 46 | {} 47 | ] 48 | ], 49 | "presets": [ 50 | [ 51 | "env", 52 | { 53 | "targets": { 54 | "node": "current" 55 | } 56 | } 57 | ] 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /prototypeAPI.js: -------------------------------------------------------------------------------- 1 | 2 | class Abc extends Component { 3 | source($) { 4 | return Observable.of(1) 5 | } 6 | render($) { 7 | return 8 | } 9 | } 10 | 11 | class LabeledSlider extends Component { 12 | constructor() { 13 | this.myRef = Creat.createRef(); 14 | } 15 | source($) { 16 | return this.myRef() 17 | } 18 | render(value, prop$) { 19 | return 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /reactive-react/component.js: -------------------------------------------------------------------------------- 1 | // import { reconcile } from "./reconciler"; 2 | import {Interval, scan, startWith, merge, mapToConstant} from './swyxjs' 3 | 4 | export class Component { 5 | constructor(props) { 6 | this.props = props; 7 | this.state = this.state || {}; 8 | } 9 | 10 | // setState(partialState) { 11 | // this.state = Object.assign({}, this.state, partialState); 12 | // updateInstance(this.__internalInstance); 13 | // } 14 | 15 | // class method because it feeds in this.initialState 16 | combineReducer(obj) { 17 | const sources = Object.entries(obj).map(([k,fn]) => { 18 | let subReducer = fn(obj) 19 | // there are two forms of return the subreducer can have 20 | // straight stream form 21 | // or object form where we need to scan it into string 22 | if (subReducer.source) { // object form 23 | subReducer = scan(subReducer.source, 24 | subReducer.reducer || ((_, n) => n), 25 | this.initialState[k] 26 | ) 27 | } 28 | return subReducer 29 | .map(x => ({[k]: x})) // map to its particular namespace 30 | }) 31 | const source = merge(...sources) 32 | const reducer = (acc, n) => ({...acc, ...n}) 33 | return {source, reducer} 34 | } 35 | } 36 | 37 | // function updateInstance(internalInstance) { 38 | // const parentDom = internalInstance.dom.parentNode; 39 | // const element = internalInstance.element; 40 | // reconcile(parentDom, internalInstance, element); 41 | // } 42 | 43 | export function createPublicInstance(element/*, internalInstance*/) { 44 | const { type, props } = element; 45 | const publicInstance = new type(props); 46 | // publicInstance.__internalInstance = internalInstance; 47 | return publicInstance; 48 | } 49 | -------------------------------------------------------------------------------- /reactive-react/element.js: -------------------------------------------------------------------------------- 1 | import Observable from 'zen-observable' 2 | import { createChangeEmitter } from 'change-emitter' 3 | 4 | export const TEXT_ELEMENT = "TEXT ELEMENT"; 5 | 6 | export function createElement(type, config, ...args) { 7 | const props = Object.assign({}, config); 8 | const hasChildren = args.length > 0; 9 | const rawChildren = hasChildren ? [].concat(...args) : []; 10 | props.children = rawChildren 11 | .filter(c => c != null && c !== false) 12 | .map(c => c instanceof Object ? c : createTextElement(c)); 13 | return { type, props }; 14 | } 15 | 16 | function createTextElement(value) { 17 | return createElement(TEXT_ELEMENT, { nodeValue: value }); 18 | } 19 | 20 | export function createHandler(_fn) { 21 | const emitter = createChangeEmitter() 22 | let handler = x => { 23 | emitter.emit(x) 24 | } 25 | handler.$ = new Observable(observer => { 26 | return emitter.listen(value => { 27 | observer.next(_fn ? _fn(value) : value) 28 | } 29 | ) 30 | }) 31 | return handler 32 | } -------------------------------------------------------------------------------- /reactive-react/index.js: -------------------------------------------------------------------------------- 1 | import { createElement, createHandler } from "./element"; 2 | import { Component } from "./component"; 3 | import { renderStream } from "./reconciler" 4 | import { mount } from "./scheduler"; 5 | 6 | export default { 7 | renderStream, 8 | createElement, 9 | createHandler, 10 | Component, 11 | mount 12 | }; 13 | 14 | export { createElement, createHandler, Component, 15 | renderStream, 16 | mount }; 17 | -------------------------------------------------------------------------------- /reactive-react/reconciler.js: -------------------------------------------------------------------------------- 1 | import Observable from 'zen-observable' 2 | import {fromEvent, scan, merge, startWith, switchLatest} from './swyxjs' 3 | // import { updateDomProperties } from "./updateProperties"; 4 | import { TEXT_ELEMENT } from "./element"; 5 | import { createPublicInstance } from "./component"; 6 | import h from 'virtual-dom/h' 7 | // import VNode from "virtual-dom/vnode/vnode" 8 | import VText from "virtual-dom/vnode/vtext" 9 | 10 | const circuitBreakerflag = !true // set true to enable debugger in infinite loops 11 | let circuitBreaker = -500 12 | // traverse all children and collect a stream of all sources 13 | // AND render. a bit of duplication, but we get persistent instances which is good 14 | export function renderStream(element, instance, state, stateMap) { 15 | // this is a separate function because scope gets messy when being recursive 16 | let isNewStream = false // assume no stream switching by default 17 | // this is the first ping of data throughout the app 18 | let source = Observable.of(state) 19 | const addToStream = _source => { 20 | // visit each source and merge with source 21 | if (_source) return source = merge(source, _source) 22 | } 23 | const markNewStream = () => isNewStream = true 24 | const newInstance = render(source, addToStream, markNewStream)(element, instance, state, stateMap) 25 | return {source, instance: newInstance, isNewStream} 26 | } 27 | 28 | /** core render logic */ 29 | export function render(source, addToStream, markNewStream) { // this is the nonrecursive part 30 | return function renderWithStream(element, instance, state, stateMap) { // recursive part 31 | let newInstance 32 | const { type, props } = element 33 | 34 | const isDomElement = typeof type === "string"; 35 | if (circuitBreakerflag && circuitBreaker++ > 0) debugger 36 | const {children = [], ...rest} = props 37 | if (isDomElement) { 38 | const childInstances = children.map( 39 | (el, i) => { 40 | // ugly but necessary to allow functional children 41 | // mapping element's children to instance's childInstances 42 | const _childInstances = instance && (instance.childInstance || instance.childInstances[i]) 43 | return renderWithStream( // recursion 44 | el, 45 | _childInstances, 46 | state, 47 | stateMap) 48 | } 49 | ); 50 | const childDoms = childInstances.map(childInstance => childInstance.dom); 51 | let lcaseProps = {} 52 | Object.entries(rest).forEach(([k, v]) => lcaseProps[formatProps(k)] = v) 53 | const dom = type === TEXT_ELEMENT 54 | ? new VText(props.nodeValue) 55 | : h(type, lcaseProps, childDoms); // equivalent of appendchild 56 | newInstance = { dom, element, childInstances }; 57 | } else { // component element 58 | let publicInstance 59 | // might have to do more diffing of props in future; further research needed 60 | // used to compare instance.element === element; this proved too easy to be false so went for types 61 | if (instance && instance.publicInstance && instance.element.type === element.type) { 62 | // just reuse old instance if it already exists 63 | publicInstance = instance && instance.publicInstance 64 | } else { 65 | markNewStream() // mark as dirty in parent scope; will rerender 66 | if (circuitBreakerflag && circuitBreaker++ > 0) debugger 67 | if (stateMap.has(publicInstance)) stateMap.delete(publicInstance) 68 | publicInstance = createPublicInstance(element); 69 | } 70 | let localState = stateMap.get(publicInstance) 71 | if (localState === undefined) localState = publicInstance.initialState 72 | publicInstance.state = localState // for access with this.state 73 | if (Object.keys(rest).length) publicInstance.props = rest // update with new props // TODO: potentially buggy 74 | // console.log({rest}) 75 | if (publicInstance.source) { 76 | const src = publicInstance.source(source) 77 | // there are two forms of Component.source 78 | const src$ = src.reducer && publicInstance.initialState !== undefined ? 79 | // 1. the reducer form 80 | scan(src.source, src.reducer, publicInstance.initialState) : 81 | // 2. and raw stream form 82 | src 83 | addToStream(src$ 84 | .map(event => { 85 | stateMap.set(publicInstance, event) 86 | return {instance: publicInstance, event} // tag it to the instance 87 | }) 88 | ); 89 | } 90 | const childElement = publicInstance.render ? 91 | publicInstance.render(localState, stateMap) : 92 | publicInstance; 93 | 94 | const childInstance = renderWithStream(childElement, instance && instance.childInstance, state, stateMap) 95 | const dom = childInstance.dom 96 | newInstance = { dom, element, childInstance, publicInstance } 97 | } 98 | return newInstance 99 | } 100 | } 101 | 102 | function formatProps(k) { 103 | if (k.startsWith('on')) return k.toLowerCase() 104 | return k 105 | } -------------------------------------------------------------------------------- /reactive-react/scheduler.js: -------------------------------------------------------------------------------- 1 | import Observable from 'zen-observable' 2 | import {fromEvent, scan, merge, startWith, switchLatest} from './swyxjs' 3 | import diff from 'virtual-dom/diff'; 4 | import patch from 'virtual-dom/patch'; 5 | import createElement from 'virtual-dom/create-element'; 6 | import { createChangeEmitter } from 'change-emitter' 7 | import { renderStream } from './reconciler' 8 | 9 | export const stateMapPointer = new Map() 10 | 11 | const circuitBreakerflag = !true // set true to enable debugger in infinite loops 12 | let circuitBreaker = -200 13 | 14 | const emitter = createChangeEmitter() 15 | // single UI thread; this is the observable that sticks around and swaps out source 16 | const UIthread = new Observable(observer => { 17 | emitter.listen(x => { 18 | observer.next(x) 19 | }) 20 | }) 21 | // mount the vdom on to the dom and 22 | // set up the runtime from sources and 23 | // patch the vdom 24 | // --- 25 | // returns an unsubscribe method you can use to unmount 26 | export function mount(rootElement, container) { 27 | // initial, throwaway-ish frame 28 | let {source, instance} = renderStream(rootElement, {}, undefined, stateMapPointer) 29 | let instancePointer = instance 30 | const rootNode = createElement(instance.dom) 31 | const containerChild = container.firstElementChild 32 | if (containerChild) { 33 | container.replaceChild(rootNode,containerChild) // hot reloaded mount 34 | } else { 35 | container.appendChild(rootNode) // initial mount 36 | } 37 | let currentSrc$ = null 38 | let SoS = startWith(UIthread, source) // stream of streams 39 | return SoS.subscribe( 40 | src$ => { // this is the current sourceStream we are working with 41 | if (currentSrc$) console.log('unsub!') || currentSrc$.unsubscribe() // unsub from old stream 42 | if (circuitBreakerflag && circuitBreaker++ > 0) debugger 43 | /**** main */ 44 | const source2$ = scan( 45 | src$, 46 | ({instance, stateMap}, nextState) => { 47 | const streamOutput = renderStream(rootElement, instance, nextState, stateMap) 48 | if (streamOutput.isNewStream) { // quick check 49 | const nextSource$ = streamOutput.source 50 | instancePointer = streamOutput.instance 51 | patch(rootNode, diff(instance.dom, instancePointer.dom)) // render to screen 52 | emitter.emit(nextSource$) // update the UI thread; source will switch 53 | } else { 54 | const nextinstance = streamOutput.instance 55 | patch(rootNode, diff(instance.dom, nextinstance.dom)) // render to screen 56 | return {instance: nextinstance, stateMap: stateMap} 57 | } 58 | }, 59 | {instance: instancePointer, stateMap: stateMapPointer} // accumulator 60 | ) 61 | /**** end main */ 62 | currentSrc$ = 63 | source2$ 64 | .subscribe() 65 | } 66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /reactive-react/swyxjs.js: -------------------------------------------------------------------------------- 1 | import Observable from 'zen-observable' 2 | export { merge, combineLatest, zip } from 'zen-observable/extras' 3 | 4 | export function Interval(tick = 1000, tickData = Symbol('tick')) { 5 | return new Observable(observer => { 6 | let timer = () => setTimeout(() => { 7 | if (typeof tickData === 'function') tickData = tickData() 8 | observer.next(tickData); 9 | timer() 10 | // observer.complete(); 11 | }, tick); 12 | timer() 13 | 14 | // On unsubscription, cancel the timer 15 | return () => clearTimeout(timer); 16 | 17 | }) 18 | } 19 | 20 | export function fromEvent(el, eventType) { 21 | return new Observable(observer => { 22 | const handler = e => observer.next(e) 23 | el.addEventListener(eventType, handler) 24 | // on unsub, remove event listener 25 | return () => el.removeEventListener(eventType, handler) 26 | }) 27 | } 28 | 29 | const NOINIT = Symbol('NO_INITIAL_VALUE') 30 | export function scan(obs, cb, seed = NOINIT) { 31 | let sub, acc = seed, hasValue = false 32 | const hasSeed = acc !== NOINIT 33 | return new Observable(observer => { 34 | sub = obs.subscribe(value => { 35 | if (observer.closed) return 36 | let first = !hasValue; 37 | hasValue = true 38 | if (!first || hasSeed ) { 39 | try { acc = cb(acc, value) } 40 | catch (e) { return observer.error(e) } 41 | observer.next(acc); 42 | } 43 | else { 44 | acc = value 45 | } 46 | }) 47 | return sub 48 | }) 49 | } 50 | 51 | // Flatten a collection of observables and only output the newest from each 52 | export function switchLatest(higherObservable) { 53 | return new Observable(observer => { 54 | let currentObs = null 55 | return higherObservable.subscribe({ 56 | next(obs) { 57 | if (currentObs) currentObs.unsubscribe() // unsub and switch 58 | currentObs = obs.subscribe(observer.subscribe) 59 | }, 60 | error(e) { 61 | observer.error(e) // untested 62 | }, 63 | complete() { 64 | // i dont think it should complete? 65 | // observer.complete() 66 | } 67 | }) 68 | }); 69 | } 70 | 71 | export function mapToConstant(obs, val) { 72 | return new Observable(observer => { 73 | const handler = obs.subscribe(() => observer.next(val)) 74 | return handler 75 | }) 76 | } 77 | 78 | export function startWith(obs, val) { 79 | return new Observable(observer => { 80 | observer.next(val) // immediately output this value 81 | const handler = obs.subscribe(x => observer.next(x)) 82 | return () => handler() 83 | }) 84 | } -------------------------------------------------------------------------------- /reactive-react/updateProperties.js: -------------------------------------------------------------------------------- 1 | export function updateDomProperties(dom, prevProps, nextProps) { 2 | const isEvent = name => name.startsWith("on"); 3 | const isAttribute = name => !isEvent(name) && name != "children"; 4 | 5 | // Remove event listeners 6 | Object.keys(prevProps).filter(isEvent).forEach(name => { 7 | const eventType = name.toLowerCase().substring(2); 8 | dom.removeEventListener(eventType, prevProps[name]); 9 | }); 10 | 11 | // Remove attributes 12 | Object.keys(prevProps).filter(isAttribute).forEach(name => { 13 | dom[name] = null; 14 | }); 15 | 16 | // Set attributes 17 | Object.keys(nextProps).filter(isAttribute).forEach(name => { 18 | dom[name] = nextProps[name]; 19 | }); 20 | 21 | // Add event listeners 22 | Object.keys(nextProps).filter(isEvent).forEach(name => { 23 | const eventType = name.toLowerCase().substring(2); 24 | dom.addEventListener(eventType, nextProps[name]); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | // Rollup plugins 2 | import babel from 'rollup-plugin-babel'; 3 | 4 | export default { 5 | entry: 'reactive-react/index.js', 6 | dest: 'build/reactive-react.js', 7 | format: 'iife', 8 | sourceMap: 'inline', 9 | moduleName: 'reactive-react', 10 | plugins: [ 11 | babel({ 12 | babelrc: false, 13 | presets: [['env', { modules: false }]], 14 | plugins: [ 15 | "transform-object-rest-spread", 16 | "transform-class-properties", 17 | [ 18 | "transform-react-jsx", 19 | {} 20 | ] 21 | ], 22 | exclude: 'node_modules/**', 23 | }), 24 | ], 25 | }; --------------------------------------------------------------------------------