UNPKG

lucid-ui

Version:

A UI component library from Xandr.

320 lines (318 loc) 16.4 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(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; }; return __assign.apply(this, arguments); }; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildModernHybridComponent = exports.buildHybridComponent = exports.safeMerge = exports.reduceSelectors = exports.getStatefulPropsContext = exports.bindReducersToState = exports.bindReducerToState = exports.omitFunctionPropsDeep = exports.isPlainObjectOrEsModule = exports.getDeepPaths = void 0; var react_1 = __importStar(require("react")); var lodash_1 = __importDefault(require("lodash")); var logger_1 = require("./logger"); var reselect_1 = require("reselect"); var create_react_class_1 = __importDefault(require("create-react-class")); var hoist_non_react_statics_1 = __importDefault(require("hoist-non-react-statics")); /* Returns an array of paths for each reducer function */ function getDeepPaths(obj, path) { if (obj === void 0) { obj = null; } if (path === void 0) { path = []; } return lodash_1.default.reduce(obj, function (terminalKeys, value, key) { return isPlainObjectOrEsModule(value) ? //getDeepPaths if value is a module or object (another Reducers) terminalKeys.concat(getDeepPaths(value, path.concat(key))) : //add key to terminalKeys (probably a Reducer (function)) terminalKeys.concat([path.concat(key)]); }, []); } exports.getDeepPaths = getDeepPaths; function isPlainObjectOrEsModule(obj) { return lodash_1.default.isPlainObject(obj) || lodash_1.default.get(obj, '__esModule', false); } exports.isPlainObjectOrEsModule = isPlainObjectOrEsModule; /** Recursively removes function type properties from obj */ function omitFunctionPropsDeep(obj) { if (obj === void 0) { obj = null; } return lodash_1.default.reduce(obj, function (memo, value, key) { if (isPlainObjectOrEsModule(value)) { memo[key] = omitFunctionPropsDeep(value); } else if (!lodash_1.default.isFunction(value)) { memo[key] = value; } return memo; }, {}); } exports.omitFunctionPropsDeep = omitFunctionPropsDeep; function bindReducerToState(reducerFunction, _a, path) { var getState = _a.getState, setState = _a.setState; if (path === void 0) { path = []; } var localPath = lodash_1.default.take(path, lodash_1.default.size(path) - 1); return lodash_1.default.assign(function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } if (lodash_1.default.isEmpty(localPath)) { // Source of bug, `reducerFunction` returns undefined setState(reducerFunction.apply(void 0, __spreadArray([getState()], args, false))); } else { var localNextState = reducerFunction.apply(void 0, __spreadArray([lodash_1.default.get(getState(), localPath)], args, false)); setState(lodash_1.default.set(lodash_1.default.clone(getState()), localPath, localNextState)); } }, { path: path }); } exports.bindReducerToState = bindReducerToState; function bindReducersToState(reducers, _a) { var getState = _a.getState, setState = _a.setState; return lodash_1.default.reduce(getDeepPaths(reducers), function (memo, path) { return lodash_1.default.set(memo, path, bindReducerToState(lodash_1.default.get(reducers, path), { getState: getState, setState: setState }, path)); }, {}); } exports.bindReducersToState = bindReducersToState; /* */ function getStatefulPropsContext(reducers, _a) { var getState = _a.getState, setState = _a.setState; var boundReducers = bindReducersToState(reducers, { getState: getState, setState: setState }); var combineFunctionsCustomizer = function (objValue, srcValue) { if (lodash_1.default.isFunction(srcValue) && lodash_1.default.isFunction(objValue)) { return function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } objValue.apply(void 0, args); return srcValue.apply(void 0, args); }; } return safeMerge(objValue, srcValue); }; var bindFunctionOverwritesCustomizer = function (objValue, srcValue) { if (lodash_1.default.isFunction(srcValue) && lodash_1.default.isFunction(objValue)) { return bindReducerToState(srcValue, { getState: getState, setState: setState }, objValue.path); } return safeMerge(objValue, srcValue); }; return { getPropReplaceReducers: function (props) { return lodash_1.default.mergeWith({}, boundReducers, getState(), props, bindFunctionOverwritesCustomizer); }, getProps: function (props) { return lodash_1.default.mergeWith({}, boundReducers, getState(), props, combineFunctionsCustomizer); }, }; } exports.getStatefulPropsContext = getStatefulPropsContext; /** * reduceSelectors * * Generates a root selector from a tree of selectors * @param {Object} selectors - a tree of selectors * @returns {function} root selector that when called with state, calls each of * the selectors in the tree with the state local to that selector. * * This function is memoized because it's recursive, and we want it to reuse * the functions created in the recursive reduce because those functions are * also memoized (reselect selectors are memoized with a cache of 1) and we want * to maintain their caches. * * TODO: the types suck on this function but we spent a couple hours trying to * get them to work and we couldn't figure out how to get generics to pass * through _.memoize correctly. ¯\_(ツ)_/¯ */ exports.reduceSelectors = lodash_1.default.memoize(function (selectors) { if (!isPlainObjectOrEsModule(selectors)) { throw new Error('Selectors must be a plain object with function or plain object values'); } /** * For each iteration of `reduceSelectors`, we return a memoized selector so * that individual branches maintain reference equality if they haven't been * modified, even if a sibling (and therefore the parent) has been modified. */ return (0, reselect_1.createSelector)(lodash_1.default.identity, function (state) { return lodash_1.default.reduce(selectors, function (acc, selector, key) { var _a; return (__assign(__assign({}, acc), (_a = {}, _a[key] = lodash_1.default.isFunction(selector) ? selector(state) : (0, exports.reduceSelectors)(selector)(state[key]), _a))); }, state); }); }); function safeMerge(objValue, srcValue) { // don't merge arrays if (lodash_1.default.isArray(srcValue) && lodash_1.default.isArray(objValue)) { return srcValue; } // guards against traversing react elements which can cause cyclical recursion // If we don't have this clause, lodash (as of 4.7.0) will attempt to // deeply clone the react children, which is really freaking slow. if ((0, react_1.isValidElement)(srcValue) || (lodash_1.default.isArray(srcValue) && lodash_1.default.some(srcValue, react_1.isValidElement)) || (lodash_1.default.isArray(srcValue) && lodash_1.default.isUndefined(objValue))) { return srcValue; } } exports.safeMerge = safeMerge; function buildHybridComponent(baseComponent, _a) { var _b = _a === void 0 ? {} : _a, _c = _b.replaceEvents, replaceEvents = _c === void 0 ? false : _c, // if true, function props replace the existing reducers, else they are invoked *after* state reducer returns _d = _b.reducers, // if true, function props replace the existing reducers, else they are invoked *after* state reducer returns reducers = _d === void 0 ? lodash_1.default.get(baseComponent, 'definition.statics.reducers', {}) : _d, _e = _b.selectors, selectors = _e === void 0 ? lodash_1.default.get(baseComponent, 'definition.statics.selectors', {}) : _e; var _isLucidHybridComponent = baseComponent._isLucidHybridComponent, displayName = baseComponent.displayName, propTypes = baseComponent.propTypes, _f = baseComponent.definition, _g = _f === void 0 ? {} : _f, _h = _g.statics, statics = _h === void 0 ? {} : _h, defaultProps = baseComponent.defaultProps; if (_isLucidHybridComponent) { logger_1.logger.warnOnce(displayName, "Lucid: you are trying to apply buildHybridComponent to ".concat(displayName, ", which is already a hybrid component. Lucid exports hybrid components by default. To access the dumb components, use the -Dumb suffix, e.g. \"ComponentDumb\"")); return baseComponent; } var selector = (0, exports.reduceSelectors)(selectors); return (0, create_react_class_1.default)({ propTypes: propTypes, statics: __assign({ _isLucidHybridComponent: true, peekDefaultProps: defaultProps }, statics), displayName: displayName, getInitialState: function () { var initialState = this.props.initialState; //initial state overrides return lodash_1.default.mergeWith({}, omitFunctionPropsDeep(baseComponent.defaultProps), initialState, safeMerge); }, UNSAFE_componentWillMount: function () { var _this = this; var synchronousState = this.state; //store reference to state, use in place of `this.state` in `getState` this.boundContext = getStatefulPropsContext(reducers, { getState: function () { return lodash_1.default.mergeWith({}, omitFunctionPropsDeep(synchronousState), omitFunctionPropsDeep(_this.props), safeMerge); }, setState: function (state) { synchronousState = state; //synchronously update the state reference _this.setState(state); }, }); }, render: function () { if (replaceEvents) { return react_1.default.createElement(baseComponent, selector(this.boundContext.getPropReplaceReducers(this.props)), this.props.children); } return react_1.default.createElement(baseComponent, selector(this.boundContext.getProps(this.props)), this.props.children); }, }); } exports.buildHybridComponent = buildHybridComponent; function buildModernHybridComponent(BaseComponent, _a) { // TODO: make sure hybrid components don't get double wrapped. Maybe use a type guard? var _b = _a.replaceEvents, replaceEvents = _b === void 0 ? false : _b, _c = _a.reducers, reducers = _c === void 0 ? {} : _c, _d = _a.selectors, selectors = _d === void 0 ? {} : _d; var selector = (0, exports.reduceSelectors)(selectors); var HybridComponent = /** @class */ (function (_super) { __extends(HybridComponent, _super); // Note: we purposefully *do not* set defaultProps here as that would // effectively eliminate our ability to distinguish what props the user // explicity included. function HybridComponent(props) { var _this = _super.call(this, props) || this; var initialState = props.initialState; // initial state overrides _this.state = lodash_1.default.mergeWith({}, omitFunctionPropsDeep(BaseComponent.defaultProps), initialState, safeMerge); return _this; } HybridComponent.prototype.UNSAFE_componentWillMount = function () { var _this = this; // store reference to state, use in place of `this.state` in `getState` var synchronousState = this.state; this.boundContext = getStatefulPropsContext(reducers, { getState: function () { return lodash_1.default.mergeWith({}, omitFunctionPropsDeep(synchronousState), omitFunctionPropsDeep(_this.props), safeMerge); }, setState: function (state) { synchronousState = state; //synchronously update the state reference _this.setState(state); }, }); }; HybridComponent.prototype.render = function () { if (this.boundContext === undefined) { return null; } if (replaceEvents) { return react_1.default.createElement(BaseComponent, selector(this.boundContext.getPropReplaceReducers(this.props)), this.props.children); } return react_1.default.createElement(BaseComponent, selector(this.boundContext.getProps(this.props)), this.props.children); }; // It would be nice to prepend "Hybrid" to this but some of our component // sadly rely on the displayName remaining unchanged. E.g. `VerticalListMenu`. HybridComponent.displayName = BaseComponent.displayName; HybridComponent.propTypes = BaseComponent.propTypes; HybridComponent.reducers = reducers; HybridComponent.selectors = selectors; HybridComponent.peekDefaultProps = BaseComponent.defaultProps; return HybridComponent; }(react_1.default.Component)); // I used a type cast and intersection with `BaseType` here because I // couldn't figure out any other way to generate a valid type signuture to // reflected all the statics on the unerlying base component. @jondlm 2019-11-27 // @ts-ignore return (0, hoist_non_react_statics_1.default)(HybridComponent, BaseComponent); } exports.buildModernHybridComponent = buildModernHybridComponent; /* export function buildStatefulComponent(...args: any[]) { logger.warnOnce( 'buildHybridComponent-once', 'Lucid: buildStatefulComponent has been renamed to buildHybridComponent.' ); // We don't really care about type checking our legacy buildHybridComponent // @ts-ignore return buildHybridComponent(...args); } */ //# sourceMappingURL=state-management.js.map