UNPKG

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