lucid-ui
Version:
A UI component library from AppNexus.
365 lines (302 loc) • 17.1 kB
JavaScript
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);
}
*/