lucid-ui
Version:
A UI component library from AppNexus.
302 lines (246 loc) • 13.7 kB
JavaScript
import _last from "lodash/last";
import _mergeWith from "lodash/mergeWith";
import _memoize from "lodash/memoize";
import _isUndefined from "lodash/isUndefined";
import _isFunction from "lodash/isFunction";
import _reduce from "lodash/reduce";
import _dropRight from "lodash/dropRight";
import _slice from "lodash/slice";
import _has from "lodash/has";
import _get from "lodash/get";
import _isArray from "lodash/isArray";
import _isEmpty from "lodash/isEmpty";
import _identity from "lodash/identity";
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
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; }
function _toArray(arr) { return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
import { createSelector } from 'reselect';
import { reduceSelectors, safeMerge } from './state-management';
import { logger, isDevMode } from './logger';
/**
* Marks a function on the reducer tree as a thunk action creator so it doesn't
* get incorporated into the redux reducer
*
* @return {function} with `isThunk` set to `true`
*/
export function thunk(fn) {
fn.isThunk = true;
return fn;
}
/**
* Creates a redux reducer and connectors (inputs to redux-react's `connect`)
*
* @param {Object} param
* @param {Object} param.initialState - the initial state object that the reducer will return
* @param {Object} param.reducers - a tree of lucid reducers
* @param {string[]} param.rootPath - array of strings representing the path to local state in global state
* @param {function} param.rootSelector - a top-level selector which takes as input state that has run through every selector in param.selectors
* @param {Object} param.selectors - a tree of lucid selectors
* @return {Object} redux reducer and connectors
*/
export function getReduxPrimitives(_ref) {
var initialState = _ref.initialState,
reducers = _ref.reducers,
_ref$rootPath = _ref.rootPath,
rootPath = _ref$rootPath === void 0 ? [] : _ref$rootPath,
_ref$rootSelector = _ref.rootSelector,
rootSelector = _ref$rootSelector === void 0 ? _identity : _ref$rootSelector,
selectors = _ref.selectors;
/* istanbul ignore if */
if (isDevMode && _isEmpty(rootPath)) {
logger.warn("`getReduxPrimitives` warning:\n`rootPath` is empty");
}
/* istanbul ignore if */
if (isDevMode && !initialState) {
logger.warn("`getReduxPrimitives` warning:\nMissing `initialState` for component at `rootPath` ".concat(_isArray(rootPath) ? rootPath.join(',') : rootPath, "\nComponents should have an `initialState` property or a `getDefaultProps` defined.\n"));
} // we need this in scope so actionCreators can refer to it
var dispatchTree;
var reducer = createReduxReducer(reducers, initialState, rootPath);
var selector = selectors ? reduceSelectors(selectors) : _identity;
var rootPathSelector = function rootPathSelector(state) {
return _isEmpty(rootPath) ? state : _get(state, rootPath);
};
var mapStateToProps = createSelector([rootPathSelector], function (rootState) {
return rootSelector(selector(rootState));
}); // dispatch could be store.dispatch's return value or an async lib's return value?
var mapDispatchToProps = function mapDispatchToProps(dispatch) {
return getDispatchTree(reducers, rootPath, dispatch);
};
var devModeMapStateToProps = function devModeMapStateToProps(rootState) {
/* istanbul ignore if */
if (isDevMode && !_has(rootState, rootPath)) {
logger.warn("`getReduxPrimitives` warning:\n`rootPath` ".concat(rootPath, " does not exist in the redux store.\nMake sure your `rootPath` is correct.\n"));
}
return mapStateToProps(rootState);
};
return {
reducer: reducer,
connectors: [isDevMode ? devModeMapStateToProps : mapStateToProps, mapDispatchToProps, mergeProps]
};
/**
* @param {function} node - a node in the the reducer tree, either a reducer or a thunk
* @param {string[]} path - the path to the reducer in the reducer tree
* @param {string[]} rootPath - array of strings representing the path to local state in global state
* @return {function} action creator that returns either an action or a thunk
*/
function createActionCreator(node, rootPath, path) {
if (node.isThunk) {
return function thunk() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return function thunkInner(dispatch, getState) {
var pathToLocalDispatchTree = _slice(path, rootPath.length, -1);
var pathToLocalState = _dropRight(path);
var localDispatchTree = _isEmpty(pathToLocalDispatchTree) ? dispatchTree : _get(dispatchTree, pathToLocalDispatchTree);
var getLocalState = _isEmpty(pathToLocalState) ? getState : function () {
return _get(getState(), pathToLocalState);
};
for (var _len2 = arguments.length, rest = new Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
rest[_key2 - 2] = arguments[_key2];
}
return node.apply(void 0, args).apply(void 0, [localDispatchTree, getLocalState, dispatch, getState].concat(rest));
};
};
}
return function actionCreator() {
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
var _ref2 = isDevMode ? cleanArgs(args) : args,
_ref3 = _toArray(_ref2),
payload = _ref3[0],
meta = _ref3.slice(1);
return {
type: path.join('.'),
payload: payload,
meta: meta
};
};
}
/**
* Walks the reducer tree and generates a tree of action creators that correspond to each reducer
* @param {Object} reducers - a tree of lucid reducers
* @param {string[]} rootPath - array of strings representing the path to local state in global state
* @returns {Object} action creator tree
*/
function createActionCreatorTree(reducers, rootPath) {
var path = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : rootPath;
return _reduce(reducers, function (memo, node, key) {
var currentPath = path.concat(key);
return _objectSpread(_objectSpread({}, memo), {}, _defineProperty({}, key, _isFunction(node) ? createActionCreator(node, rootPath, currentPath) : createActionCreatorTree(node, rootPath, currentPath)));
}, {});
}
/**
* Walks the reducer tree and generates an action creator tree, then binds dispatch to each node
* @param {Object} reducers - a tree of lucid reducers
* @param {string[]} rootPath - array of strings representing the path to local state in global state
* @param {function} dispatch - the redux store's `dispatch` function
*/
function getDispatchTree(reducers, rootPath, dispatch) {
var actionCreatorTree = createActionCreatorTree(reducers, rootPath);
dispatchTree = bindActionCreatorTree(actionCreatorTree, dispatch);
/* istanbul ignore if */
if (isDevMode) {
//@ts-ignore
window.lucidReduxUtil = window.lucidReduxUtil || {}; //@ts-ignore
window.lucidReduxUtil[rootPath] = {
actionCreatorTree: actionCreatorTree,
dispatchTree: dispatchTree
};
}
return dispatchTree;
}
}
/**
* Walks the reducer tree and generates a tree of redux reducers, converting the
* signature from `(state, payload) => state` to `(state, action) => state`
* @param {Object} reducers - a tree of lucid reducers
* @param {string[]} path - array of strings representing the path to the reducer
* @return {Object} redux reducer tree
*/
function createReduxReducerTree(reducers) {
var path = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
return _reduce(reducers, function (memo, node, key) {
// filter out thunks from the reducer tree
if (node.isThunk) {
return memo;
}
var currentPath = path.concat(key);
return _objectSpread(_objectSpread({}, memo), {}, _defineProperty({}, key, _isFunction(node) ? function reduxReducer(state, action) {
var type = action.type,
payload = action.payload,
_action$meta = action.meta,
meta = _action$meta === void 0 ? [] : _action$meta;
if (_isUndefined(state) || type !== currentPath.join('.')) {
return state;
}
return node.apply(void 0, [state, payload].concat(_toConsumableArray(meta)));
} : createReduxReducerTree(node, currentPath)));
}, {});
}
/**
* Returns a function that calls every reducer in the reducer tree with the reducer's local state and action
* @param {Object} reduxReducerTree - tree of redux reducers with signature `(state, action) => state`
* @param {Object} initialState - the initial state object that the reducer will return
* @return {function} the redux reducer
*/
function createReducerFromReducerTree(reduxReducerTree, initialState) {
return function reduxReducer(state, action) {
if (_isUndefined(state)) {
return initialState;
}
return _reduce(reduxReducerTree, function (state, node, key) {
return _objectSpread(_objectSpread({}, state), _isFunction(node) ? node(state, action) : _defineProperty({}, key, createReducerFromReducerTree(node, {})(state[key], action)));
}, state);
};
}
/**
* Generates a redux reducer from a tree of lucid reducers
* @param {Object} reducers - a tree of lucid reducers
* @param {Object} initialState - the initial state object that the reducer will return
* @param {string[]} rootPath - array of strings representing the path to part of global state this reducer applies to
* @return {function} the redux reducer
*/
function createReduxReducer(reducers, initialState, rootPath) {
var reducerTree = createReduxReducerTree(reducers, rootPath);
return createReducerFromReducerTree(reducerTree, initialState);
}
/**
* Binds redux store.dispatch to actionCreators in a tree
* @param {Object} actionCreatorTree - a tree of redux action creator functions
* @param {function} dispatch - the redux store's `dispatch` function
* @param {string[]} path - array of strings representing the path to the action creator
*/
function bindActionCreatorTree(actionCreatorTree, dispatch) {
var path = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
return _reduce(actionCreatorTree, function (memo, node, key) {
return _objectSpread(_objectSpread({}, memo), {}, _defineProperty({}, key, _isFunction(node) ? function boundActionCreator() {
var action = actionCreatorTree[key].apply(actionCreatorTree, arguments);
return dispatch(action);
} : bindActionCreatorTree(node, dispatch, path.concat(key))));
}, // @ts-ignore
{});
}
/**
* Merges state, dispatchTree, and ownProps into a single props object
* @param {Object} state
* @param {Object} dispatchTree
* @param {Object} ownProps
* @return {Object}
*/
var mergeProps = _memoize(function (state, dispatchTree, ownProps) {
return _mergeWith({}, state, dispatchTree, ownProps, safeMerge);
});
export function cleanArgs(args) {
return _has(_last(args), 'event') ? _dropRight(args) : args;
}