lucid-ui
Version:
A UI component library from Xandr.
320 lines (318 loc) • 16.4 kB
JavaScript
;
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) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
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 __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
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, __spreadArray([getState()], args, false)));
}
else {
var localNextState = reducerFunction.apply(void 0, __spreadArray([lodash_1.default.get(getState(), localPath)], args, false));
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 (0, 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)
: (0, 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 ((0, 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 ".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 = (0, exports.reduceSelectors)(selectors);
return (0, 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 = (0, 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 (0, 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