UNPKG

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
'use strict'; 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;