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 && 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 _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 component = _ref.component,
64 config = _ref.config,
65 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 config = _ref2.config,
109 existingKeyMap = _ref2.existingKeyMap,
110 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 existingKeyMap = _ref3.existingKeyMap,
133 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 config = _ref4.config,
186 existingKeyMap = _ref4.existingKeyMap,
187 props = _ref4.props,
188 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({
202 renderedElement: renderedElement,
203 existingKeyMap: existingKeyMap,
204 componentName: componentName
205 });
206 var getComponentField = function getComponentField(key) {
207 return component[key];
208 };
209 var getGlobalState = function getGlobalState(key) {
210 return globalState[key];
211 };
212 var componentGetState = function componentGetState(stateKey, elementKey) {
213 return (0, _getState2.default)(component.state, elementKey || getKey(), stateKey);
214 };
215 var setState = function setState(stateKey, value, elementKey) {
216 return _setStyleState(component, elementKey || getKey(), stateKey, value);
217 };
218
219 var addCSS = function addCSS(css) {
220 var styleKeeper = component._radiumStyleKeeper || component.context._radiumStyleKeeper;
221 if (!styleKeeper) {
222 if (__isTestModeEnabled) {
223 return {
224 remove: function remove() {}
225 };
226 }
227
228 throw new Error('To use plugins requiring `addCSS` (e.g. keyframes, media queries), ' + 'please wrap your application in the StyleRoot component. Component ' + 'name: `' + componentName + '`.');
229 }
230
231 return styleKeeper.addCSS(css);
232 };
233
234 var newStyle = props.style;
235
236 plugins.forEach(function (plugin) {
237 var result = plugin({
238 ExecutionEnvironment: _exenv2.default,
239 addCSS: addCSS,
240 appendImportantToEachValue: _appendImportantToEachValue2.default,
241 componentName: componentName,
242 config: config,
243 cssRuleSetToString: _cssRuleSetToString2.default,
244 getComponentField: getComponentField,
245 getGlobalState: getGlobalState,
246 getState: componentGetState,
247 hash: _hash2.default,
248 mergeStyles: _mergeStyles.mergeStyles,
249 props: newProps,
250 setState: setState,
251 isNestedStyle: _mergeStyles.isNestedStyle,
252 style: newStyle
253 }) || {};
254
255 newStyle = result.style || newStyle;
256
257 newProps = result.props && Object.keys(result.props).length ? _extends({}, newProps, result.props) : newProps;
258
259 var newComponentFields = result.componentFields || {};
260 Object.keys(newComponentFields).forEach(function (fieldName) {
261 component[fieldName] = newComponentFields[fieldName];
262 });
263
264 var newGlobalState = result.globalState || {};
265 Object.keys(newGlobalState).forEach(function (key) {
266 globalState[key] = newGlobalState[key];
267 });
268 });
269
270 if (newStyle !== props.style) {
271 newProps = _extends({}, newProps, { style: newStyle });
272 }
273
274 return newProps;
275};
276
277// Wrapper around React.cloneElement. To avoid processing the same element
278// twice, whenever we clone an element add a special prop to make sure we don't
279// process this element again.
280var _cloneElement = function _cloneElement(renderedElement, newProps, newChildren) {
281 // Only add flag if this is a normal DOM element
282 if (typeof renderedElement.type === 'string') {
283 newProps = _extends({}, newProps, { 'data-radium': true });
284 }
285
286 return _react2.default.cloneElement(renderedElement, newProps, newChildren);
287};
288
289//
290// The nucleus of Radium. resolveStyles is called on the rendered elements
291// before they are returned in render. It iterates over the elements and
292// children, rewriting props to add event handlers required to capture user
293// interactions (e.g. mouse over). It also replaces the style prop because it
294// adds in the various interaction styles (e.g. :hover).
295//
296resolveStyles = function resolveStyles(component, // ReactComponent, flow+eslint complaining
297renderedElement) {
298 var config = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_CONFIG;
299 var existingKeyMap = arguments[3];
300 var shouldCheckBeforeResolve = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
301
302 // ReactElement
303 existingKeyMap = existingKeyMap || {};
304 if (!renderedElement ||
305 // Bail if we've already processed this element. This ensures that only the
306 // owner of an element processes that element, since the owner's render
307 // function will be called first (which will always be the case, since you
308 // can't know what else to render until you render the parent component).
309 renderedElement.props && renderedElement.props['data-radium'] ||
310 // Bail if this element is a radium enhanced element, because if it is,
311 // then it will take care of resolving its own styles.
312 shouldCheckBeforeResolve && !_shouldResolveStyles(renderedElement)) {
313 return renderedElement;
314 }
315
316 var newChildren = _resolveChildren({
317 children: renderedElement.props.children,
318 component: component,
319 config: config,
320 existingKeyMap: existingKeyMap
321 });
322
323 var newProps = _resolveProps({
324 component: component,
325 config: config,
326 existingKeyMap: existingKeyMap,
327 props: renderedElement.props
328 });
329
330 newProps = _runPlugins({
331 component: component,
332 config: config,
333 existingKeyMap: existingKeyMap,
334 props: newProps,
335 renderedElement: renderedElement
336 });
337
338 // If nothing changed, don't bother cloning the element. Might be a bit
339 // wasteful, as we add the sentinal to stop double-processing when we clone.
340 // Assume benign double-processing is better than unneeded cloning.
341 if (newChildren === renderedElement.props.children && newProps === renderedElement.props) {
342 return renderedElement;
343 }
344
345 return _cloneElement(renderedElement, newProps !== renderedElement.props ? newProps : {}, newChildren);
346};
347
348// Only for use by tests
349var __isTestModeEnabled = false;
350if (process.env.NODE_ENV !== 'production') {
351 resolveStyles.__clearStateForTests = function () {
352 globalState = {};
353 };
354 resolveStyles.__setTestMode = function (isEnabled) {
355 __isTestModeEnabled = isEnabled;
356 };
357}
358
359exports.default = resolveStyles;
360module.exports = exports['default'];
\No newline at end of file