kitchensink
Version:
Dispatch's awesome components and style guide
357 lines (279 loc) • 12.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
var _appendImportantToEachValue = require('./append-important-to-each-value');
var _appendImportantToEachValue2 = _interopRequireDefault(_appendImportantToEachValue);
var _cssRuleSetToString = require('./css-rule-set-to-string');
var _cssRuleSetToString2 = _interopRequireDefault(_cssRuleSetToString);
var _getState = require('./get-state');
var _getState2 = _interopRequireDefault(_getState);
var _getStateKey = require('./get-state-key');
var _getStateKey2 = _interopRequireDefault(_getStateKey);
var _hash = require('./hash');
var _hash2 = _interopRequireDefault(_hash);
var _mergeStyles = require('./merge-styles');
var _plugins = require('./plugins/');
var _plugins2 = _interopRequireDefault(_plugins);
var _exenv = require('exenv');
var _exenv2 = _interopRequireDefault(_exenv);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var DEFAULT_CONFIG = {
plugins: [_plugins2.default.mergeStyleArray, _plugins2.default.checkProps, _plugins2.default.resolveMediaQueries, _plugins2.default.resolveInteractionStyles, _plugins2.default.keyframes, _plugins2.default.visited, _plugins2.default.removeNestedStyles, _plugins2.default.prefix, _plugins2.default.checkProps]
};
// Gross
var globalState = {};
// Declare early for recursive helpers.
var resolveStyles = null;
var _shouldResolveStyles = function _shouldResolveStyles(component) {
return component.type && !component.type._isRadiumEnhanced;
};
var _resolveChildren = function _resolveChildren(_ref) {
var children = _ref.children;
var component = _ref.component;
var config = _ref.config;
var existingKeyMap = _ref.existingKeyMap;
if (!children) {
return children;
}
var childrenType = typeof children === 'undefined' ? 'undefined' : _typeof(children);
if (childrenType === 'string' || childrenType === 'number') {
// Don't do anything with a single primitive child
return children;
}
if (childrenType === 'function') {
// Wrap the function, resolving styles on the result
return function () {
var result = children.apply(this, arguments);
if (_react2.default.isValidElement(result)) {
return resolveStyles(component, result, config, existingKeyMap, true);
}
return result;
};
}
if (_react2.default.Children.count(children) === 1 && children.type) {
// If a React Element is an only child, don't wrap it in an array for
// React.Children.map() for React.Children.only() compatibility.
var onlyChild = _react2.default.Children.only(children);
return resolveStyles(component, onlyChild, config, existingKeyMap, true);
}
return _react2.default.Children.map(children, function (child) {
if (_react2.default.isValidElement(child)) {
return resolveStyles(component, child, config, existingKeyMap, true);
}
return child;
});
};
// Recurse over props, just like children
var _resolveProps = function _resolveProps(_ref2) {
var component = _ref2.component;
var config = _ref2.config;
var existingKeyMap = _ref2.existingKeyMap;
var props = _ref2.props;
var newProps = props;
Object.keys(props).forEach(function (prop) {
// We already recurse over children above
if (prop === 'children') {
return;
}
var propValue = props[prop];
if (_react2.default.isValidElement(propValue)) {
newProps = _extends({}, newProps);
newProps[prop] = resolveStyles(component, propValue, config, existingKeyMap, true);
}
});
return newProps;
};
var _buildGetKey = function _buildGetKey(_ref3) {
var componentName = _ref3.componentName;
var existingKeyMap = _ref3.existingKeyMap;
var renderedElement = _ref3.renderedElement;
// We need a unique key to correlate state changes due to user interaction
// with the rendered element, so we know to apply the proper interactive
// styles.
var originalKey = typeof renderedElement.ref === 'string' ? renderedElement.ref : renderedElement.key;
var key = (0, _getStateKey2.default)(originalKey);
var alreadyGotKey = false;
var getKey = function getKey() {
if (alreadyGotKey) {
return key;
}
alreadyGotKey = true;
if (existingKeyMap[key]) {
var elementName = void 0;
if (typeof renderedElement.type === 'string') {
elementName = renderedElement.type;
} else if (renderedElement.type.constructor) {
elementName = renderedElement.type.constructor.displayName || renderedElement.type.constructor.name;
}
throw new Error('Radium requires each element with interactive styles to have a unique ' + 'key, set using either the ref or key prop. ' + (originalKey ? 'Key "' + originalKey + '" is a duplicate.' : 'Multiple elements have no key specified.') + ' ' + 'Component: "' + componentName + '". ' + (elementName ? 'Element: "' + elementName + '".' : ''));
}
existingKeyMap[key] = true;
return key;
};
return getKey;
};
var _setStyleState = function _setStyleState(component, key, stateKey, value) {
if (!component._radiumIsMounted) {
return;
}
var existing = component._lastRadiumState || component.state && component.state._radiumStyleState || {};
var state = { _radiumStyleState: _extends({}, existing) };
state._radiumStyleState[key] = _extends({}, state._radiumStyleState[key]);
state._radiumStyleState[key][stateKey] = value;
component._lastRadiumState = state._radiumStyleState;
component.setState(state);
};
var _runPlugins = function _runPlugins(_ref4) {
var component = _ref4.component;
var config = _ref4.config;
var existingKeyMap = _ref4.existingKeyMap;
var props = _ref4.props;
var renderedElement = _ref4.renderedElement;
// Don't run plugins if renderedElement is not a simple ReactDOMElement or has
// no style.
if (!_react2.default.isValidElement(renderedElement) || typeof renderedElement.type !== 'string' || !props.style) {
return props;
}
var newProps = props;
var plugins = config.plugins || DEFAULT_CONFIG.plugins;
var componentName = component.constructor.displayName || component.constructor.name;
var getKey = _buildGetKey({ renderedElement: renderedElement, existingKeyMap: existingKeyMap, componentName: componentName });
var getComponentField = function getComponentField(key) {
return component[key];
};
var getGlobalState = function getGlobalState(key) {
return globalState[key];
};
var componentGetState = function componentGetState(stateKey, elementKey) {
return (0, _getState2.default)(component.state, elementKey || getKey(), stateKey);
};
var setState = function setState(stateKey, value, elementKey) {
return _setStyleState(component, elementKey || getKey(), stateKey, value);
};
var addCSS = function addCSS(css) {
var styleKeeper = component._radiumStyleKeeper || component.context._radiumStyleKeeper;
if (!styleKeeper) {
if (__isTestModeEnabled) {
return {
remove: function remove() {}
};
}
throw new Error('To use plugins requiring `addCSS` (e.g. keyframes, media queries), ' + 'please wrap your application in the StyleRoot component. Component ' + 'name: `' + componentName + '`.');
}
return styleKeeper.addCSS(css);
};
var newStyle = props.style;
plugins.forEach(function (plugin) {
var result = plugin({
ExecutionEnvironment: _exenv2.default,
addCSS: addCSS,
appendImportantToEachValue: _appendImportantToEachValue2.default,
componentName: componentName,
config: config,
cssRuleSetToString: _cssRuleSetToString2.default,
getComponentField: getComponentField,
getGlobalState: getGlobalState,
getState: componentGetState,
hash: _hash2.default,
mergeStyles: _mergeStyles.mergeStyles,
props: newProps,
setState: setState,
isNestedStyle: _mergeStyles.isNestedStyle,
style: newStyle
}) || {};
newStyle = result.style || newStyle;
newProps = result.props && Object.keys(result.props).length ? _extends({}, newProps, result.props) : newProps;
var newComponentFields = result.componentFields || {};
Object.keys(newComponentFields).forEach(function (fieldName) {
component[fieldName] = newComponentFields[fieldName];
});
var newGlobalState = result.globalState || {};
Object.keys(newGlobalState).forEach(function (key) {
globalState[key] = newGlobalState[key];
});
});
if (newStyle !== props.style) {
newProps = _extends({}, newProps, { style: newStyle });
}
return newProps;
};
// Wrapper around React.cloneElement. To avoid processing the same element
// twice, whenever we clone an element add a special prop to make sure we don't
// process this element again.
var _cloneElement = function _cloneElement(renderedElement, newProps, newChildren) {
// Only add flag if this is a normal DOM element
if (typeof renderedElement.type === 'string') {
newProps = _extends({}, newProps, { 'data-radium': true });
}
return _react2.default.cloneElement(renderedElement, newProps, newChildren);
};
//
// The nucleus of Radium. resolveStyles is called on the rendered elements
// before they are returned in render. It iterates over the elements and
// children, rewriting props to add event handlers required to capture user
// interactions (e.g. mouse over). It also replaces the style prop because it
// adds in the various interaction styles (e.g. :hover).
//
resolveStyles = function resolveStyles(component, // ReactComponent, flow+eslint complaining
renderedElement) {
var // ReactElement
config = arguments.length <= 2 || arguments[2] === undefined ? DEFAULT_CONFIG : arguments[2];
var existingKeyMap = arguments[3];
var shouldCheckBeforeResolve = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4];
// ReactElement
existingKeyMap = existingKeyMap || {};
if (!renderedElement ||
// Bail if we've already processed this element. This ensures that only the
// owner of an element processes that element, since the owner's render
// function will be called first (which will always be the case, since you
// can't know what else to render until you render the parent component).
renderedElement.props && renderedElement.props['data-radium'] ||
// Bail if this element is a radium enhanced element, because if it is,
// then it will take care of resolving its own styles.
shouldCheckBeforeResolve && !_shouldResolveStyles(renderedElement)) {
return renderedElement;
}
var newChildren = _resolveChildren({
children: renderedElement.props.children,
component: component,
config: config,
existingKeyMap: existingKeyMap
});
var newProps = _resolveProps({
component: component,
config: config,
existingKeyMap: existingKeyMap,
props: renderedElement.props
});
newProps = _runPlugins({
component: component,
config: config,
existingKeyMap: existingKeyMap,
props: newProps,
renderedElement: renderedElement
});
// If nothing changed, don't bother cloning the element. Might be a bit
// wasteful, as we add the sentinal to stop double-processing when we clone.
// Assume benign double-processing is better than unneeded cloning.
if (newChildren === renderedElement.props.children && newProps === renderedElement.props) {
return renderedElement;
}
return _cloneElement(renderedElement, newProps !== renderedElement.props ? newProps : {}, newChildren);
};
// Only for use by tests
var __isTestModeEnabled = false;
if (process.env.NODE_ENV !== 'production') {
resolveStyles.__clearStateForTests = function () {
globalState = {};
};
resolveStyles.__setTestMode = function (isEnabled) {
__isTestModeEnabled = isEnabled;
};
}
exports.default = resolveStyles;
module.exports = exports['default'];