francis.react
Version:
React integration for Francis
347 lines (294 loc) • 8.23 kB
JavaScript
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 };