UNPKG

francis.react

Version:
347 lines (294 loc) 8.23 kB
import React__default, { useState, useEffect } from 'react'; export * from 'react'; import { isObservable, subscribe, isNext, isError, atom } from 'francis'; /*#__PURE__*/ function isObject(x) { if (!!x && typeof x === "object") { var proto = Object.getPrototypeOf(x); return proto === Object.prototype || proto === null; } else { return false; } } /*#__PURE__*/ function curry2(f) { return function (a1, a2) { if (arguments.length < 2) { return function (a2) { return f(a1, a2); }; } else { return f(a1, a2); } }; } function collectObservables(input, output) { var has = false; var keys = Object.keys(input); var n = keys.length; if (n === 0) { return void 0; } for (var i = 0; i < n; i++) { var k = keys[i]; var v = input[k]; if (isObservable(v)) { if (has === false) { has = true; } output[k] = v; } } return has; } var $ = React__default.createElement; var LIFT = "francis-lift"; var stateInstanceCache = {}; var instanceId = 0; var Wrapper = function (props) { var s = useState({ id: instanceId++ }); var state = s[0]; var instance = activate(state.id, props, s[1]); useEffect(function () { return function () { return deactivate(state.id); }; }, []); return $(Value, { instance: instance }); }; Wrapper.displayName = "Francis.Wrapper"; function createElement(tag, props) { var ch = [], len = arguments.length - 2; while (len-- > 0) ch[len] = arguments[len + 2]; if (typeof tag === "string" || props && props[LIFT]) { var obs = { props: {}, ch: {}, style: {} }; var style = props && props.style; var hasPropObs = props && collectObservables(props, obs.props); var hasStyleObs = style && collectObservables(props.style, obs.style); var hasChildObs = ch.length > 0 && collectObservables(ch, obs.ch); if (hasPropObs || hasStyleObs || hasChildObs) { return $(Wrapper, { key: props ? props.key : undefined, tag: tag, props: props, ch: ch, obs: obs }); } } return $.apply(void 0, [tag, props].concat(ch)); } function Value(ref) { var instance = ref.instance; return instance.elem(); } function activate(id, next, notify) { var instance = stateInstanceCache[id]; if (instance === undefined) { instance = new StateInstance(id, notify); stateInstanceCache[id] = instance; } instance.activate(next); return instance; } function deactivate(id) { var instance = stateInstanceCache[id]; if (instance !== undefined) { delete stateInstanceCache[id]; instance.deactivate(); } } var StateInstance = function StateInstance(id, notify) { this.id = id; this.notify = notify; this.numPending = 0; this.subscriptions = { props: {}, ch: {}, style: {} }; this.state = {}; this.activating = false; this.current = void 0; this.promise = null; this.resolve = void 0; }; StateInstance.prototype.notifyChange = function notifyChange() { if (this.promise !== null) { var ref = this; var resolve = ref.resolve; this.promise = null; this.resolve = void 0; resolve(); } if (this.activating === false) { var ref$1 = this; var notify = ref$1.notify; notify({ id: this.id }); } }; StateInstance.prototype.elem = function elem() { var this$1 = this; if (this.numPending === 0) { return $.apply(void 0, [this.current.tag, this.state.props].concat(this.state.ch)); } else { if (this.promise === null) { this.promise = new Promise(function (resolve) { return this$1.resolve = resolve; }); } throw this.promise; } }; StateInstance.prototype.activate = function activate(next) { if (next !== this.current) { var props = next.props; var ch = next.ch; var obs = next.obs; this.current = next; this.activating = true; var ref = this; var state = ref.state; var subscriptions = ref.subscriptions; state.props = Object.assign({}, props); state.ch = ch.slice(); this.subs(state, subscriptions.props, obs.props, updateProp); this.subs(state, subscriptions.ch, obs.ch, updateChild); this.subs(state, subscriptions.style, obs.style, updateStyle); this.activating = false; } }; StateInstance.prototype.deactivate = function deactivate() { var ref = this; var subscriptions = ref.subscriptions; this.prev = this.notify = void 0; this.state = this.subscriptions = {}; this.unsubs(subscriptions.props); this.unsubs(subscriptions.ch); this.unsubs(subscriptions.style); }; StateInstance.prototype.unsubs = function unsubs(subscriptions) { var keys = Object.keys(subscriptions); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; var s = subscriptions[key]; var dispose = s.dispose; if (dispose !== null) { dispose(); } } }; StateInstance.prototype.subs = function subs(state, subscriptions, observables, f) { var keys = Object.keys(observables); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; var obs = observables[key]; var subscription = subscriptions[key]; if (subscription !== undefined) { if (subscription.obs !== obs) { // we have existing subscription for the given key but for different observable // => we must dispose the existing subscription and subscribe to new observable var dispose = subscription.dispose; if (dispose !== null) { dispose(); } ++this.numPending; subscription.obs = obs; subscription.value = void 0; subscription.ready = false; subscription.dispose = this.subscribe(obs, subscription, key, f); } else if (subscription.ready === true) { // we have existing subscription for same observable and it's ready // => copy the existing value to the state f(state, key, subscription.value); } } else { // no existing subscription, create entirely new one ++this.numPending; subscription = subscriptions[key] = { obs: obs, ready: false, dispose: null, value: void 0 }; subscription.dispose = this.subscribe(obs, subscription, key, f); } } }; StateInstance.prototype.subscribe = function subscribe$1(obs, subscription, key, f) { return subscribe(this.onEvent.bind(this, key, subscription, f), obs); }; StateInstance.prototype.onEvent = function onEvent(key, subscription, f, event) { if (isNext(event)) { var ref = this; var state = ref.state; var value = subscription.value = event.value; var changed = f(state, key, value); if (subscription.ready === false) { subscription.ready = true; --this.numPending; } if (changed === true && this.numPending === 0) { this.notifyChange(); } } else if (isError(event)) { if (typeof console !== "undefined") { console.error("Unhandled observable error in VDOM subscriber"); console.error(event.error); } } }; function updateProp(state, key, value) { if (state.props[key] !== value) { state.props[prop] = value; return true; } return false; } function updateStyle(state, key, value) { var obj, obj$1; var style = state.props.style; if (isObject(style)) { if (style[key] !== value) { state.props.style = Object.assign({}, style, (obj = {}, obj[key] = value, obj)); return true; } } else { state.props.value = (obj$1 = {}, obj$1[key] = value, obj$1); return true; } return false; } function updateChild(state, key, value) { var idx = parseInt(key); if (state.ch[idx] !== value) { state.ch[idx] = value; return true; } return false; } var useStateAtom = function (initialState) { var s = useState(atom(initialState)); return s[0]; }; var useSubscription = /*#__PURE__*/ curry2(function (f, observable) { useEffect(function () { return subscribe(f, observable); }, []); }); React__default.createElement = createElement; export default React__default; export { createElement, useStateAtom, useSubscription };