UNPKG

12.5 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _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; };
8
9var _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; };
10
11var _appendImportantToEachValue = require('./append-important-to-each-value');
12
13var _appendImportantToEachValue2 = _interopRequireDefault(_appendImportantToEachValue);
14
15var _cssRuleSetToString = require('./css-rule-set-to-string');
16
17var _cssRuleSetToString2 = _interopRequireDefault(_cssRuleSetToString);
18
19var _getState = require('./get-state');
20
21var _getState2 = _interopRequireDefault(_getState);
22
23var _getStateKey = require('./get-state-key');
24
25var _getStateKey2 = _interopRequireDefault(_getStateKey);
26
27var _hash = require('./hash');
28
29var _hash2 = _interopRequireDefault(_hash);
30
31var _mergeStyles = require('./merge-styles');
32
33var _plugins = require('./plugins/');
34
35var _plugins2 = _interopRequireDefault(_plugins);
36
37var _exenv = require('exenv');
38
39var _exenv2 = _interopRequireDefault(_exenv);
40
41var _react = require('react');
42
43var _react2 = _interopRequireDefault(_react);
44
45function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
46
47var DEFAULT_CONFIG = {
48 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]
49};
50
51// Gross
52var globalState = {};
53
54// Declare early for recursive helpers.
55var resolveStyles = null;
56
57var _shouldResolveStyles = function _shouldResolveStyles(component) {
58 return component.type && !component.type._isRadiumEnhanced;
59};
60
61var _resolveChildren = function _resolveChildren(_ref) {
62 var children = _ref.children;
63 var component = _ref.component;
64 var config = _ref.config;
65 var existingKeyMap = _ref.existingKeyMap;
66
67 if (!children) {
68 return children;
69 }
70
71 var childrenType = typeof children === 'undefined' ? 'undefined' : _typeof(children);
72
73 if (childrenType === 'string' || childrenType === 'number') {
74 // Don't do anything with a single primitive child
75 return children;
76 }
77
78 if (childrenType === 'function') {
79 // Wrap the function, resolving styles on the result
80 return function () {
81 var result = children.apply(this, arguments);
82 if (_react2.default.isValidElement(result)) {
83 return resolveStyles(component, result, config, existingKeyMap, true);
84 }
85 return result;
86 };
87 }
88
89 if (_react2.default.Children.count(children) === 1 && children.type) {
90 // If a React Element is an only child, don't wrap it in an array for
91 // React.Children.map() for React.Children.only() compatibility.
92 var onlyChild = _react2.default.Children.only(children);
93 return resolveStyles(component, onlyChild, config, existingKeyMap, true);
94 }
95
96 return _react2.default.Children.map(children, function (child) {
97 if (_react2.default.isValidElement(child)) {
98 return resolveStyles(component, child, config, existingKeyMap, true);
99 }
100
101 return child;
102 });
103};
104
105// Recurse over props, just like children
106var _resolveProps = function _resolveProps(_ref2) {
107 var component = _ref2.component;
108 var config = _ref2.config;
109 var existingKeyMap = _ref2.existingKeyMap;
110 var props = _ref2.props;
111
112 var newProps = props;
113
114 Object.keys(props).forEach(function (prop) {
115 // We already recurse over children above
116 if (prop === 'children') {
117 return;
118 }
119
120 var propValue = props[prop];
121 if (_react2.default.isValidElement(propValue)) {
122 newProps = _extends({}, newProps);
123 newProps[prop] = resolveStyles(component, propValue, config, existingKeyMap, true);
124 }
125 });
126
127 return newProps;
128};
129
130var _buildGetKey = function _buildGetKey(_ref3) {
131 var componentName = _ref3.componentName;
132 var existingKeyMap = _ref3.existingKeyMap;
133 var renderedElement = _ref3.renderedElement;
134
135 // We need a unique key to correlate state changes due to user interaction
136 // with the rendered element, so we know to apply the proper interactive
137 // styles.
138 var originalKey = typeof renderedElement.ref === 'string' ? renderedElement.ref : renderedElement.key;
139 var key = (0, _getStateKey2.default)(originalKey);
140
141 var alreadyGotKey = false;
142 var getKey = function getKey() {
143 if (alreadyGotKey) {
144 return key;
145 }
146
147 alreadyGotKey = true;
148
149 if (existingKeyMap[key]) {
150 var elementName = void 0;
151 if (typeof renderedElement.type === 'string') {
152 elementName = renderedElement.type;
153 } else if (renderedElement.type.constructor) {
154 elementName = renderedElement.type.constructor.displayName || renderedElement.type.constructor.name;
155 }
156
157 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 + '".' : ''));
158 }
159
160 existingKeyMap[key] = true;
161
162 return key;
163 };
164
165 return getKey;
166};
167
168var _setStyleState = function _setStyleState(component, key, stateKey, value) {
169 if (!component._radiumIsMounted) {
170 return;
171 }
172
173 var existing = component._lastRadiumState || component.state && component.state._radiumStyleState || {};
174
175 var state = { _radiumStyleState: _extends({}, existing) };
176 state._radiumStyleState[key] = _extends({}, state._radiumStyleState[key]);
177 state._radiumStyleState[key][stateKey] = value;
178
179 component._lastRadiumState = state._radiumStyleState;
180 component.setState(state);
181};
182
183var _runPlugins = function _runPlugins(_ref4) {
184 var component = _ref4.component;
185 var config = _ref4.config;
186 var existingKeyMap = _ref4.existingKeyMap;
187 var props = _ref4.props;
188 var renderedElement = _ref4.renderedElement;
189
190 // Don't run plugins if renderedElement is not a simple ReactDOMElement or has
191 // no style.
192 if (!_react2.default.isValidElement(renderedElement) || typeof renderedElement.type !== 'string' || !props.style) {
193 return props;
194 }
195
196 var newProps = props;
197
198 var plugins = config.plugins || DEFAULT_CONFIG.plugins;
199
200 var componentName = component.constructor.displayName || component.constructor.name;
201 var getKey = _buildGetKey({ renderedElement: renderedElement, existingKeyMap: existingKeyMap, componentName: componentName });
202 var getComponentField = function getComponentField(key) {
203 return component[key];
204 };
205 var getGlobalState = function getGlobalState(key) {
206 return globalState[key];
207 };
208 var componentGetState = function componentGetState(stateKey, elementKey) {
209 return (0, _getState2.default)(component.state, elementKey || getKey(), stateKey);
210 };
211 var setState = function setState(stateKey, value, elementKey) {
212 return _setStyleState(component, elementKey || getKey(), stateKey, value);
213 };
214
215 var addCSS = function addCSS(css) {
216 var styleKeeper = component._radiumStyleKeeper || component.context._radiumStyleKeeper;
217 if (!styleKeeper) {
218 if (__isTestModeEnabled) {
219 return {
220 remove: function remove() {}
221 };
222 }
223
224 throw new Error('To use plugins requiring `addCSS` (e.g. keyframes, media queries), ' + 'please wrap your application in the StyleRoot component. Component ' + 'name: `' + componentName + '`.');
225 }
226
227 return styleKeeper.addCSS(css);
228 };
229
230 var newStyle = props.style;
231
232 plugins.forEach(function (plugin) {
233 var result = plugin({
234 ExecutionEnvironment: _exenv2.default,
235 addCSS: addCSS,
236 appendImportantToEachValue: _appendImportantToEachValue2.default,
237 componentName: componentName,
238 config: config,
239 cssRuleSetToString: _cssRuleSetToString2.default,
240 getComponentField: getComponentField,
241 getGlobalState: getGlobalState,
242 getState: componentGetState,
243 hash: _hash2.default,
244 mergeStyles: _mergeStyles.mergeStyles,
245 props: newProps,
246 setState: setState,
247 isNestedStyle: _mergeStyles.isNestedStyle,
248 style: newStyle
249 }) || {};
250
251 newStyle = result.style || newStyle;
252
253 newProps = result.props && Object.keys(result.props).length ? _extends({}, newProps, result.props) : newProps;
254
255 var newComponentFields = result.componentFields || {};
256 Object.keys(newComponentFields).forEach(function (fieldName) {
257 component[fieldName] = newComponentFields[fieldName];
258 });
259
260 var newGlobalState = result.globalState || {};
261 Object.keys(newGlobalState).forEach(function (key) {
262 globalState[key] = newGlobalState[key];
263 });
264 });
265
266 if (newStyle !== props.style) {
267 newProps = _extends({}, newProps, { style: newStyle });
268 }
269
270 return newProps;
271};
272
273// Wrapper around React.cloneElement. To avoid processing the same element
274// twice, whenever we clone an element add a special prop to make sure we don't
275// process this element again.
276var _cloneElement = function _cloneElement(renderedElement, newProps, newChildren) {
277 // Only add flag if this is a normal DOM element
278 if (typeof renderedElement.type === 'string') {
279 newProps = _extends({}, newProps, { 'data-radium': true });
280 }
281
282 return _react2.default.cloneElement(renderedElement, newProps, newChildren);
283};
284
285//
286// The nucleus of Radium. resolveStyles is called on the rendered elements
287// before they are returned in render. It iterates over the elements and
288// children, rewriting props to add event handlers required to capture user
289// interactions (e.g. mouse over). It also replaces the style prop because it
290// adds in the various interaction styles (e.g. :hover).
291//
292resolveStyles = function resolveStyles(component, // ReactComponent, flow+eslint complaining
293renderedElement) {
294 var config = arguments.length <= 2 || arguments[2] === undefined ? DEFAULT_CONFIG : arguments[2];
295 var existingKeyMap = arguments[3];
296 var shouldCheckBeforeResolve = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4];
297 // ReactElement
298 existingKeyMap = existingKeyMap || {};
299 if (!renderedElement ||
300 // Bail if we've already processed this element. This ensures that only the
301 // owner of an element processes that element, since the owner's render
302 // function will be called first (which will always be the case, since you
303 // can't know what else to render until you render the parent component).
304 renderedElement.props && renderedElement.props['data-radium'] ||
305
306 // Bail if this element is a radium enhanced element, because if it is,
307 // then it will take care of resolving its own styles.
308 shouldCheckBeforeResolve && !_shouldResolveStyles(renderedElement)) {
309 return renderedElement;
310 }
311
312 var newChildren = _resolveChildren({
313 children: renderedElement.props.children,
314 component: component,
315 config: config,
316 existingKeyMap: existingKeyMap
317 });
318
319 var newProps = _resolveProps({
320 component: component,
321 config: config,
322 existingKeyMap: existingKeyMap,
323 props: renderedElement.props
324 });
325
326 newProps = _runPlugins({
327 component: component,
328 config: config,
329 existingKeyMap: existingKeyMap,
330 props: newProps,
331 renderedElement: renderedElement
332 });
333
334 // If nothing changed, don't bother cloning the element. Might be a bit
335 // wasteful, as we add the sentinal to stop double-processing when we clone.
336 // Assume benign double-processing is better than unneeded cloning.
337 if (newChildren === renderedElement.props.children && newProps === renderedElement.props) {
338 return renderedElement;
339 }
340
341 return _cloneElement(renderedElement, newProps !== renderedElement.props ? newProps : {}, newChildren);
342};
343
344// Only for use by tests
345var __isTestModeEnabled = false;
346if (process.env.NODE_ENV !== 'production') {
347 resolveStyles.__clearStateForTests = function () {
348 globalState = {};
349 };
350 resolveStyles.__setTestMode = function (isEnabled) {
351 __isTestModeEnabled = isEnabled;
352 };
353}
354
355exports.default = resolveStyles;
356module.exports = exports['default'];
\No newline at end of file