UNPKG

lucid-ui

Version:

A UI component library from AppNexus.

365 lines (302 loc) 17.1 kB
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } import _isUndefined from "lodash/isUndefined"; import _some from "lodash/some"; import _isArray from "lodash/isArray"; import _identity from "lodash/identity"; import _memoize from "lodash/memoize"; import _mergeWith from "lodash/mergeWith"; import _clone from "lodash/clone"; import _set from "lodash/set"; import _isEmpty from "lodash/isEmpty"; import _assign from "lodash/assign"; import _size from "lodash/size"; import _take from "lodash/take"; import _isFunction from "lodash/isFunction"; import _get from "lodash/get"; import _isPlainObject from "lodash/isPlainObject"; import _reduce from "lodash/reduce"; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } import React, { isValidElement } from 'react'; import { logger } from './logger'; import { createSelector } from 'reselect'; import createClass from 'create-react-class'; import hoistNonReactStatics from 'hoist-non-react-statics'; // TODO: could we somehow type the `...args` with a generic? /* Returns an array of paths for each reducer function */ export function getDeepPaths() { var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; var path = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; return _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)]); }, []); } export function isPlainObjectOrEsModule(obj) { return _isPlainObject(obj) || _get(obj, '__esModule', false); } /** Recursively removes function type properties from obj */ export function omitFunctionPropsDeep() { var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; return _reduce(obj, function (memo, value, key) { if (isPlainObjectOrEsModule(value)) { memo[key] = omitFunctionPropsDeep(value); } else if (!_isFunction(value)) { memo[key] = value; } return memo; }, {}); } export function bindReducerToState(reducerFunction, _ref) { var getState = _ref.getState, setState = _ref.setState; var path = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; var localPath = _take(path, _size(path) - 1); return _assign(function () { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } if (_isEmpty(localPath)) { // Source of bug, `reducerFunction` returns undefined setState(reducerFunction.apply(void 0, [getState()].concat(args))); } else { var localNextState = reducerFunction.apply(void 0, [_get(getState(), localPath)].concat(args)); setState(_set(_clone(getState()), localPath, localNextState)); } }, { path: path }); } export function bindReducersToState(reducers, _ref2) { var getState = _ref2.getState, setState = _ref2.setState; return _reduce(getDeepPaths(reducers), function (memo, path) { return _set(memo, path, bindReducerToState(_get(reducers, path), { getState: getState, setState: setState }, path)); }, {}); } /* */ export function getStatefulPropsContext(reducers, _ref3) { var getState = _ref3.getState, setState = _ref3.setState; var boundReducers = bindReducersToState(reducers, { getState: getState, setState: setState }); var combineFunctionsCustomizer = function combineFunctionsCustomizer(objValue, srcValue) { if (_isFunction(srcValue) && _isFunction(objValue)) { return function () { objValue.apply(void 0, arguments); return srcValue.apply(void 0, arguments); }; } return safeMerge(objValue, srcValue); }; var bindFunctionOverwritesCustomizer = function bindFunctionOverwritesCustomizer(objValue, srcValue) { if (_isFunction(srcValue) && _isFunction(objValue)) { return bindReducerToState(srcValue, { getState: getState, setState: setState }, objValue.path); } return safeMerge(objValue, srcValue); }; return { getPropReplaceReducers: function getPropReplaceReducers(props) { return _mergeWith({}, boundReducers, getState(), props, bindFunctionOverwritesCustomizer); }, getProps: function getProps(props) { return _mergeWith({}, boundReducers, getState(), props, combineFunctionsCustomizer); } }; } /** * 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. ¯\_(ツ)_/¯ */ export var reduceSelectors = _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 createSelector(_identity, function (state) { return _reduce(selectors, function (acc, selector, key) { return _objectSpread(_objectSpread({}, acc), {}, _defineProperty({}, key, _isFunction(selector) ? selector(state) : reduceSelectors(selector)(state[key]))); }, state); }); }); export function safeMerge(objValue, srcValue) { // don't merge arrays if (_isArray(srcValue) && _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 ( /*#__PURE__*/isValidElement(srcValue) || _isArray(srcValue) && _some(srcValue, isValidElement) || _isArray(srcValue) && _isUndefined(objValue)) { return srcValue; } } export function buildHybridComponent(baseComponent) { var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref4$replaceEvents = _ref4.replaceEvents, replaceEvents = _ref4$replaceEvents === void 0 ? false : _ref4$replaceEvents, _ref4$reducers = _ref4.reducers, reducers = _ref4$reducers === void 0 ? _get(baseComponent, 'definition.statics.reducers', {}) : _ref4$reducers, _ref4$selectors = _ref4.selectors, selectors = _ref4$selectors === void 0 ? _get(baseComponent, 'definition.statics.selectors', {}) : _ref4$selectors; var _isLucidHybridComponent = baseComponent._isLucidHybridComponent, displayName = baseComponent.displayName, propTypes = baseComponent.propTypes, _baseComponent$defini = baseComponent.definition; _baseComponent$defini = _baseComponent$defini === void 0 ? {} : _baseComponent$defini; var _baseComponent$defini2 = _baseComponent$defini.statics, statics = _baseComponent$defini2 === void 0 ? {} : _baseComponent$defini2, defaultProps = baseComponent.defaultProps; if (_isLucidHybridComponent) { 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 = reduceSelectors(selectors); return createClass({ propTypes: propTypes, statics: _objectSpread({ _isLucidHybridComponent: true, peekDefaultProps: defaultProps }, statics), displayName: displayName, getInitialState: function getInitialState() { var initialState = this.props.initialState; //initial state overrides return _mergeWith({}, omitFunctionPropsDeep(baseComponent.defaultProps), initialState, safeMerge); }, UNSAFE_componentWillMount: function UNSAFE_componentWillMount() { 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 getState() { return _mergeWith({}, omitFunctionPropsDeep(synchronousState), omitFunctionPropsDeep(_this.props), safeMerge); }, setState: function setState(state) { synchronousState = state; //synchronously update the state reference _this.setState(state); } }); }, render: function render() { if (replaceEvents) { return /*#__PURE__*/React.createElement(baseComponent, selector(this.boundContext.getPropReplaceReducers(this.props)), this.props.children); } return /*#__PURE__*/React.createElement(baseComponent, selector(this.boundContext.getProps(this.props)), this.props.children); } }); } export function buildModernHybridComponent(BaseComponent, _ref5) { var _ref5$replaceEvents = _ref5.replaceEvents, replaceEvents = _ref5$replaceEvents === void 0 ? false : _ref5$replaceEvents, _ref5$reducers = _ref5.reducers, reducers = _ref5$reducers === void 0 ? {} : _ref5$reducers, _ref5$selectors = _ref5.selectors, selectors = _ref5$selectors === void 0 ? {} : _ref5$selectors; var selector = reduceSelectors(selectors); var HybridComponent = /*#__PURE__*/function (_React$Component) { _inherits(HybridComponent, _React$Component); var _super = _createSuper(HybridComponent); // It would be nice to prepend "Hybrid" to this but some of our component // sadly rely on the displayName remaining unchanged. E.g. `VerticalListMenu`. // 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 _this2; _classCallCheck(this, HybridComponent); _this2 = _super.call(this, props); _defineProperty(_assertThisInitialized(_this2), "boundContext", void 0); var initialState = props.initialState; // initial state overrides _this2.state = _mergeWith({}, omitFunctionPropsDeep(BaseComponent.defaultProps), initialState, safeMerge); return _this2; } _createClass(HybridComponent, [{ key: "UNSAFE_componentWillMount", value: function UNSAFE_componentWillMount() { var _this3 = this; // store reference to state, use in place of `this.state` in `getState` var synchronousState = this.state; this.boundContext = getStatefulPropsContext(reducers, { getState: function getState() { return _mergeWith({}, omitFunctionPropsDeep(synchronousState), omitFunctionPropsDeep(_this3.props), safeMerge); }, setState: function setState(state) { synchronousState = state; //synchronously update the state reference _this3.setState(state); } }); } }, { key: "render", value: function render() { if (this.boundContext === undefined) { return null; } if (replaceEvents) { return /*#__PURE__*/React.createElement(BaseComponent, selector(this.boundContext.getPropReplaceReducers(this.props)), this.props.children); } return /*#__PURE__*/React.createElement(BaseComponent, selector(this.boundContext.getProps(this.props)), this.props.children); } }]); return HybridComponent; }(React.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 _defineProperty(HybridComponent, "displayName", BaseComponent.displayName); _defineProperty(HybridComponent, "propTypes", BaseComponent.propTypes); _defineProperty(HybridComponent, "reducers", reducers); _defineProperty(HybridComponent, "selectors", selectors); _defineProperty(HybridComponent, "peekDefaultProps", BaseComponent.defaultProps); return hoistNonReactStatics(HybridComponent, BaseComponent); } /* 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); } */