UNPKG

lucid-ui

Version:

A UI component library from AppNexus.

302 lines (246 loc) 13.7 kB
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; }