UNPKG

lucid-ui

Version:

A UI component library from AppNexus.

316 lines (314 loc) 16.2 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) { 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 __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; 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, __spreadArrays([getState()], args))); } else { var localNextState = reducerFunction.apply(void 0, __spreadArrays([lodash_1.default.get(getState(), localPath)], args)); 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 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) : 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 (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 " + 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 = exports.reduceSelectors(selectors); return 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 = 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 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