refract-xstream
Version:
Refract bindings for React with xstream: harness the power of reactive programming to supercharge your components!
497 lines (478 loc) • 19 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var $$observable = _interopDefault(require('symbol-observable'));
var xs = _interopDefault(require('xstream'));
var dropRepeats = _interopDefault(require('xstream/extra/dropRepeats'));
var React = require('react');
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
var PROPS_EFFECT = '@@refract/effect/props';
var COMPONENT_EFFECT = '@@refract/effect/component';
var toProps = function (props) { return ({
type: PROPS_EFFECT,
payload: {
replace: false,
props: props
}
}); };
var asProps = function (props) { return ({
type: PROPS_EFFECT,
payload: {
replace: true,
props: props
}
}); };
var toRender = function (data) { return ({
type: COMPONENT_EFFECT,
payload: data
}); };
var MOUNT_EVENT = '@@refract/event/mount';
var UNMOUNT_EVENT = '@@refract/event/unmount';
var DataType;
(function (DataType) {
DataType["EVENT"] = "event";
DataType["PROPS"] = "props";
DataType["CALLBACK"] = "callback";
})(DataType || (DataType = {}));
var isEvent = function (eventName) { return function (data, index) {
return data.type === DataType.EVENT &&
data.payload.name === eventName;
}; };
var isProps = function (data) {
return data.type === DataType.PROPS;
};
var isCallback = function (propName) { return function (data) {
return data.type === DataType.CALLBACK &&
data.payload.name === propName;
}; };
var createEventData = function (name, value) { return ({
type: DataType.EVENT,
payload: {
name: name,
value: value
}
}); };
var createPropsData = function (props) { return ({
type: DataType.PROPS,
payload: props
}); };
var createCallbackData = function (name, args) { return ({
type: DataType.CALLBACK,
payload: {
name: name,
args: args
}
}); };
var shallowEquals = function (left, right) {
return left === right ||
(Object.keys(left).length === Object.keys(right).length &&
Object.keys(left).every(function (leftKey) { return left[leftKey] === right[leftKey]; }));
};
var subscribeToSink = function (sink, next, error) {
return sink.subscribe({
next: next,
error: error,
complete: function () { return void 0; }
});
};
var getComponentBase = function (data, pushEvent) {
var fromEvent = function (eventName, valueTransformer) {
return data.filter(isEvent(eventName)).map(function (data) {
var value = data.payload.value;
return valueTransformer ? valueTransformer(value) : value;
});
};
function useEvent(eventName, seedValue) {
var hasSeedValue = arguments.length > 1;
var events$ = fromEvent(eventName);
var pushEventValue = pushEvent(eventName);
return [
hasSeedValue ? events$ : events$.startWith(seedValue),
pushEventValue
];
}
return {
mount: data.filter(isEvent(MOUNT_EVENT)).mapTo(undefined),
unmount: data.filter(isEvent(UNMOUNT_EVENT)).mapTo(undefined),
fromEvent: fromEvent,
pushEvent: pushEvent,
useEvent: useEvent
};
};
var getObserve = function (getProp, data, decoratedProps) {
return function observe(propName, valueTransformer) {
if (decoratedProps &&
propName &&
typeof getProp(propName) === 'function') {
return data()
.filter(isCallback(propName))
.map(function (data) {
var args = data.payload.args;
return valueTransformer ? valueTransformer(args) : args[0];
});
}
if (propName) {
return data()
.filter(isProps)
.map(function (data) {
var prop = data.payload[propName];
return valueTransformer ? valueTransformer(prop) : prop;
})
.compose(dropRepeats());
}
return data()
.filter(isProps)
.map(function (data) { return data.payload; })
.compose(dropRepeats(shallowEquals));
};
};
var createComponent = function (getProp, dataObservable, pushEvent, decoratedProps) {
var data = function () { return xs.from(dataObservable); };
return __assign({ observe: getObserve(getProp, data, decoratedProps) }, getComponentBase(data(), pushEvent));
};
var createObservable = function (subscribe) {
return (_a = {
subscribe: subscribe
},
_a[$$observable] = function () {
return this;
},
_a);
var _a;
};
var configureComponent = function (aperture, instance, isValidElement, isComponentClass, handler, errorHandler, mergeProps, decorateProps, componentName) {
if (isValidElement === void 0) { isValidElement = function () { return false; }; }
if (isComponentClass === void 0) { isComponentClass = function () { return false; }; }
if (componentName === void 0) { componentName = 'unknown component'; }
instance.state = {
renderEffect: false,
children: null,
props: {}
};
var setState = function (state) {
if (instance.unmounted) {
return;
}
if (instance.mounted) {
instance.setState(state);
}
else if (typeof state === 'function') {
instance.state = state(instance.state);
}
else {
instance.state = __assign({}, instance.state, state);
}
};
var finalHandler = function (initialProps, initialContext) {
var effectHandler = handler
? handler(initialProps, initialContext)
: function () { return void 0; };
return function (effect) {
if (isValidElement(effect)) {
setState({
renderEffect: true,
children: effect
});
}
else if (effect && effect.type === PROPS_EFFECT) {
var payload_1 = effect.payload;
if (mergeProps) {
setState(function (prev) { return ({
replace: payload_1.replace,
props: __assign({}, prev.props, payload_1.props)
}); });
}
else {
setState({
replace: payload_1.replace,
props: payload_1.props
});
}
}
else {
effectHandler(effect);
}
};
};
var decoratedProps = {};
var listeners = [];
var addListener = function (listener) {
listeners = listeners.concat(listener);
};
var removeListener = function (listener) {
listeners = listeners.filter(function (l) { return l !== listener; });
};
var pushEvent = function (eventName) { return function (val) {
listeners.forEach(function (listener) {
listener.next(createEventData(eventName, val));
});
}; };
var decorateProp = function (container, prop, propName) {
if (propName === 'children' || isComponentClass(prop)) {
return;
}
container[propName] = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
listeners.forEach(function (listener) {
listener.next(createCallbackData(propName, args));
});
return prop.apply(void 0, args);
};
};
if (decorateProps) {
Object.keys(instance.props).forEach(function (propName) {
if (typeof instance.props[propName] === 'function') {
decorateProp(decoratedProps, instance.props[propName], propName);
}
});
}
var dataObservable = createObservable(function (listener) {
addListener(listener);
listener.next(createPropsData(instance.props));
return { unsubscribe: function () { return removeListener(listener); } };
});
var component = createComponent(function (propName) { return instance.props[propName]; }, dataObservable, pushEvent, decorateProps);
var sinkObservable = aperture(component, instance.props, instance.context);
if (!sinkObservable) {
throw new Error("Your Refract aperture didn't return an observable entity in " + componentName + " (component).");
}
var sinkSubscription = subscribeToSink(sinkObservable, finalHandler(instance.props, instance.context), errorHandler
? errorHandler(instance.props, instance.context)
: undefined);
instance.reDecorateProps = function (nextProps) {
if (decorateProps) {
Object.keys(nextProps).forEach(function (propName) {
if (typeof instance.props[propName] === 'function' &&
nextProps[propName] !== instance.props[propName]) {
decorateProp(decoratedProps, nextProps[propName], propName);
}
});
}
};
instance.pushProps = function (props) {
listeners.forEach(function (listener) {
listener.next(createPropsData(props));
});
};
instance.triggerMount = function () {
pushEvent(MOUNT_EVENT)();
};
instance.triggerUnmount = function () {
pushEvent(UNMOUNT_EVENT)();
sinkSubscription.unsubscribe();
};
instance.getChildProps = function () {
var state = instance.state;
var stateProps = state.props;
if (state.replace === true) {
return __assign({}, stateProps, { pushEvent: pushEvent });
}
var additionalProps = __assign({}, decoratedProps, { pushEvent: pushEvent });
if (state.replace === false) {
return __assign({}, instance.props, additionalProps, stateProps);
}
return __assign({}, instance.props, additionalProps);
};
instance.havePropsChanged = function (newProps, newState) {
var state = instance.state;
if (state.renderEffect || newState.renderEffect) {
return state.children !== newState.children;
}
var haveStatePropsChanged = !shallowEquals(state.props, newState.props);
if (newState.replace === true) {
return haveStatePropsChanged;
}
var havePropsChanged = !shallowEquals(instance.props, newProps);
if (newState.replace === false) {
return havePropsChanged || haveStatePropsChanged;
}
return havePropsChanged;
};
};
var isComponentClass = function (ComponentClass) {
return Boolean(ComponentClass &&
ComponentClass.prototype &&
ComponentClass.prototype.isReactComponent);
};
var Empty = function () { return null; };
var withEffects = function (aperture, config) {
if (config === void 0) { config = {}; }
return function (BaseComponent) {
if (BaseComponent === void 0) { BaseComponent = Empty; }
return _a = /** @class */ (function (_super) {
__extends(WithEffects, _super);
function WithEffects(props, context) {
var _this = _super.call(this, props, context) || this;
_this.mounted = false;
_this.unmounted = false;
configureComponent(aperture, _this, React.isValidElement, isComponentClass, config.handler, config.errorHandler, config.mergeProps, config.decorateProps !== false, BaseComponent.displayName || BaseComponent.name);
return _this;
}
WithEffects.prototype.componentDidMount = function () {
this.mounted = true;
this.triggerMount();
};
WithEffects.prototype.UNSAFE_componentWillReceiveProps = function (nextProps) {
this.reDecorateProps(nextProps);
this.pushProps(nextProps);
};
WithEffects.prototype.shouldComponentUpdate = function (nextProps, nextState) {
return this.havePropsChanged(nextProps, nextState);
};
WithEffects.prototype.componentWillUnmount = function () {
this.unmounted = true;
this.triggerUnmount();
};
WithEffects.prototype.render = function () {
if (this.state.children) {
return this.state.children;
}
return React.createElement(BaseComponent, this.getChildProps());
};
return WithEffects;
}(React.Component)),
_a.contextType = config.Context || null,
_a;
var _a;
};
};
var identity = function (arg) { return arg; };
var compose = function () {
var fns = [];
for (var _i = 0; _i < arguments.length; _i++) {
fns[_i] = arguments[_i];
}
if (fns.length === 0) {
return identity;
}
if (fns.length === 1) {
return fns[0];
}
return function (arg) {
return fns.reduceRight(function (previousResult, fn) { return fn(previousResult); }, arg);
};
};
var configureHook = function (aperture, data, handler, errorHandler, hookName) {
if (handler === void 0) { handler = function () { return function () { return void 0; }; }; }
if (hookName === void 0) { hookName = 'unknown hook'; }
var returnedData;
var lastData = data;
var setComponentData;
var finalHandler = function (initialData) {
var effectHandler = handler(initialData);
return function (effect) {
if (effect && effect.type === COMPONENT_EFFECT) {
if (setComponentData) {
setComponentData(effect.payload);
}
else {
returnedData = effect.payload;
}
}
else {
effectHandler(effect);
}
};
};
var listeners = [];
var addListener = function (listener) {
listeners = listeners.concat(listener);
};
var removeListener = function (listener) {
listeners = listeners.filter(function (l) { return l !== listener; });
};
var pushEvent = function (eventName) { return function (val) {
listeners.forEach(function (listener) {
listener.next(createEventData(eventName, val));
});
}; };
var dataObservable = createObservable(function (listener) {
addListener(listener);
listener.next(createPropsData(lastData));
return { unsubscribe: function () { return removeListener(listener); } };
});
var component = createComponent(function (propName) { return data[propName]; }, dataObservable, pushEvent, false);
var sinkObservable = aperture(component, data);
if (!sinkObservable) {
throw new Error("Your Refract aperture didn't return an observable entity in " + hookName + " (hook).");
}
var sinkSubscription = subscribeToSink(sinkObservable, finalHandler(data), errorHandler ? errorHandler(data) : undefined);
var pushMountEvent = function () {
pushEvent(MOUNT_EVENT)();
};
var pushUnmountEvent = function () {
pushEvent(UNMOUNT_EVENT)();
};
return {
data: returnedData,
unsubscribe: function () {
pushUnmountEvent();
sinkSubscription.unsubscribe();
},
pushMountEvent: pushMountEvent,
pushData: function (data) {
lastData = data;
listeners.forEach(function (listener) {
listener.next(createPropsData(data));
});
},
registerSetData: function (setData) {
setComponentData = function (data) { return setData(function (hook) { return (__assign({}, hook, { data: data })); }); };
}
};
};
// @ts-ignore
var useRefract = function (aperture, data, config) {
if (config === void 0) { config = {}; }
var initialHook = React.useMemo(function () {
return configureHook(aperture, data, config.handler, config.errorHandler, config.hookName);
}, []);
var _a = React.useState(initialHook), hook = _a[0], setData = _a[1];
React.useLayoutEffect(function () {
hook.registerSetData(setData);
hook.pushMountEvent();
return function () { return hook.unsubscribe(); };
}, []);
React.useEffect(function () {
hook.pushData(data);
}, [data]);
return hook.data;
};
exports.withEffects = withEffects;
exports.compose = compose;
exports.asProps = asProps;
exports.toProps = toProps;
exports.PROPS_EFFECT = PROPS_EFFECT;
exports.useRefract = useRefract;
exports.toRender = toRender;
exports.COMPONENT_EFFECT = COMPONENT_EFFECT;