1 |
|
2 |
|
3 | import {Component} from 'react';
|
4 | import PropTypes from 'prop-types';
|
5 |
|
6 | import StyleKeeper from './style-keeper';
|
7 | import resolveStyles from './resolve-styles';
|
8 | import getRadiumStyleState from './get-radium-style-state';
|
9 |
|
10 | const KEYS_TO_IGNORE_WHEN_COPYING_PROPERTIES = [
|
11 | 'arguments',
|
12 | 'callee',
|
13 | 'caller',
|
14 | 'length',
|
15 | 'name',
|
16 | 'prototype',
|
17 | 'type'
|
18 | ];
|
19 |
|
20 | let RADIUM_PROTO: Object;
|
21 | let RADIUM_METHODS;
|
22 |
|
23 | function copyProperties(source, target) {
|
24 | Object.getOwnPropertyNames(source).forEach(key => {
|
25 | if (
|
26 | KEYS_TO_IGNORE_WHEN_COPYING_PROPERTIES.indexOf(key) < 0 &&
|
27 | !target.hasOwnProperty(key)
|
28 | ) {
|
29 | const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
30 | Object.defineProperty(target, key, descriptor);
|
31 | }
|
32 | });
|
33 | }
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | function isStateless(component: Function): boolean {
|
40 | const proto = component.prototype || {};
|
41 |
|
42 | return !component.isReactComponent &&
|
43 | !proto.isReactComponent &&
|
44 | !component.render &&
|
45 | !proto.render;
|
46 | }
|
47 |
|
48 |
|
49 |
|
50 | function isNativeClass(component: Function): boolean {
|
51 | return typeof component === 'function' &&
|
52 | /^\s*class\s+/.test(component.toString());
|
53 | }
|
54 |
|
55 |
|
56 | function inherits(subClass, superClass) {
|
57 | if (typeof superClass !== 'function' && superClass !== null) {
|
58 | throw new TypeError(
|
59 | `Super expression must either be null or a function, not ${typeof superClass}`
|
60 | );
|
61 | }
|
62 |
|
63 | subClass.prototype = Object.create(superClass && superClass.prototype, {
|
64 | constructor: {
|
65 | value: subClass,
|
66 | enumerable: false,
|
67 | writable: true,
|
68 | configurable: true
|
69 | }
|
70 | });
|
71 |
|
72 | if (superClass) {
|
73 | if (Object.setPrototypeOf) {
|
74 | Object.setPrototypeOf(subClass, superClass);
|
75 | } else {
|
76 | subClass.__proto__ = superClass;
|
77 | }
|
78 | }
|
79 | }
|
80 |
|
81 | export default function enhanceWithRadium(
|
82 | configOrComposedComponent: Class<any> | constructor | Function | Object,
|
83 | config?: Object = {}
|
84 | ): constructor {
|
85 | if (typeof configOrComposedComponent !== 'function') {
|
86 | const newConfig = {...config, ...configOrComposedComponent};
|
87 | return function(configOrComponent) {
|
88 | return enhanceWithRadium(configOrComponent, newConfig);
|
89 | };
|
90 | }
|
91 |
|
92 | const component: Function = configOrComposedComponent;
|
93 | let ComposedComponent: constructor = component;
|
94 |
|
95 | // Handle Native ES classes.
|
96 | if (isNativeClass(ComposedComponent)) {
|
97 |
|
98 | ComposedComponent = (function(OrigComponent): constructor {
|
99 | function NewComponent() {
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 | const source = new OrigComponent(...arguments);
|
108 |
|
109 |
|
110 | copyProperties(source, this);
|
111 |
|
112 | return this;
|
113 | }
|
114 |
|
115 | inherits(NewComponent, OrigComponent);
|
116 |
|
117 | return NewComponent;
|
118 | })(ComposedComponent);
|
119 | }
|
120 |
|
121 |
|
122 | if (isStateless(ComposedComponent)) {
|
123 | ComposedComponent = class extends Component<any, Object> {
|
124 | render() {
|
125 | return component(this.props, this.context);
|
126 | }
|
127 | };
|
128 |
|
129 | ComposedComponent.displayName = component.displayName || component.name;
|
130 | }
|
131 |
|
132 |
|
133 | if (ComposedComponent === component) {
|
134 | ComposedComponent = class extends ComposedComponent {};
|
135 | }
|
136 |
|
137 | class RadiumEnhancer extends ComposedComponent {
|
138 | static _isRadiumEnhanced = true;
|
139 |
|
140 | state: Object;
|
141 |
|
142 | _radiumMediaQueryListenersByQuery: {
|
143 | [query: string]: {remove: () => void}
|
144 | };
|
145 | _radiumMouseUpListener: {remove: () => void};
|
146 | _radiumIsMounted: boolean;
|
147 | _lastRadiumState: Object;
|
148 | _extraRadiumStateKeys: any;
|
149 |
|
150 | constructor() {
|
151 | super(...arguments);
|
152 |
|
153 | this.state = this.state || {};
|
154 | this.state._radiumStyleState = {};
|
155 | this._radiumIsMounted = true;
|
156 |
|
157 | const self: Object = this;
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | RADIUM_METHODS.forEach(name => {
|
164 | const thisDesc = Object.getOwnPropertyDescriptor(self, name);
|
165 | const thisMethod = (thisDesc || {}).value;
|
166 |
|
167 |
|
168 | if (!thisMethod) {
|
169 | return;
|
170 | }
|
171 |
|
172 | const radiumDesc = Object.getOwnPropertyDescriptor(RADIUM_PROTO, name);
|
173 | const radiumProtoMethod = radiumDesc.value;
|
174 | const superProtoMethod = ComposedComponent.prototype[name];
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 | if (!superProtoMethod && thisMethod !== radiumProtoMethod) {
|
181 |
|
182 | Object.defineProperty(ComposedComponent.prototype, name, thisDesc);
|
183 |
|
184 |
|
185 |
|
186 | delete self[name];
|
187 | }
|
188 | });
|
189 | }
|
190 |
|
191 | componentWillUnmount() {
|
192 | if (super.componentWillUnmount) {
|
193 | super.componentWillUnmount();
|
194 | }
|
195 |
|
196 | this._radiumIsMounted = false;
|
197 |
|
198 | if (this._radiumMouseUpListener) {
|
199 | this._radiumMouseUpListener.remove();
|
200 | }
|
201 |
|
202 | if (this._radiumMediaQueryListenersByQuery) {
|
203 | Object.keys(this._radiumMediaQueryListenersByQuery).forEach(
|
204 | function(query) {
|
205 | this._radiumMediaQueryListenersByQuery[query].remove();
|
206 | },
|
207 | this
|
208 | );
|
209 | }
|
210 | }
|
211 |
|
212 | getChildContext() {
|
213 | const superChildContext = super.getChildContext
|
214 | ? super.getChildContext()
|
215 | : {};
|
216 |
|
217 | if (!this.props.radiumConfig) {
|
218 | return superChildContext;
|
219 | }
|
220 |
|
221 | const newContext = {...superChildContext};
|
222 |
|
223 | if (this.props.radiumConfig) {
|
224 | newContext._radiumConfig = this.props.radiumConfig;
|
225 | }
|
226 |
|
227 | return newContext;
|
228 | }
|
229 |
|
230 | render() {
|
231 | const renderedElement = super.render();
|
232 | let currentConfig = this.props.radiumConfig ||
|
233 | this.context._radiumConfig ||
|
234 | config;
|
235 |
|
236 | if (config && currentConfig !== config) {
|
237 | currentConfig = {
|
238 | ...config,
|
239 | ...currentConfig
|
240 | };
|
241 | }
|
242 |
|
243 | const {extraStateKeyMap, element} = resolveStyles(
|
244 | this,
|
245 | renderedElement,
|
246 | currentConfig
|
247 | );
|
248 | this._extraRadiumStateKeys = Object.keys(extraStateKeyMap);
|
249 |
|
250 | return element;
|
251 | }
|
252 |
|
253 |
|
254 | componentDidUpdate(prevProps, prevState) {
|
255 | if (super.componentDidUpdate) {
|
256 | super.componentDidUpdate.call(this, prevProps, prevState);
|
257 | }
|
258 |
|
259 | if (this._extraRadiumStateKeys.length > 0) {
|
260 | const trimmedRadiumState = this._extraRadiumStateKeys.reduce(
|
261 | (state, key) => {
|
262 | const {[key]: extraStateKey, ...remainingState} = state;
|
263 | return remainingState;
|
264 | },
|
265 | getRadiumStyleState(this)
|
266 | );
|
267 |
|
268 | this._lastRadiumState = trimmedRadiumState;
|
269 | this.setState({_radiumStyleState: trimmedRadiumState});
|
270 | }
|
271 | }
|
272 |
|
273 | }
|
274 |
|
275 |
|
276 | RADIUM_PROTO = RadiumEnhancer.prototype;
|
277 | RADIUM_METHODS = Object.getOwnPropertyNames(RADIUM_PROTO).filter(
|
278 | n => n !== 'constructor' && typeof RADIUM_PROTO[n] === 'function'
|
279 | );
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 | copyProperties(component, RadiumEnhancer);
|
286 |
|
287 | if (process.env.NODE_ENV !== 'production') {
|
288 |
|
289 |
|
290 |
|
291 | copyProperties(ComposedComponent.prototype, RadiumEnhancer.prototype);
|
292 | }
|
293 |
|
294 | if (RadiumEnhancer.propTypes && RadiumEnhancer.propTypes.style) {
|
295 | RadiumEnhancer.propTypes = {
|
296 | ...RadiumEnhancer.propTypes,
|
297 | style: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
|
298 | };
|
299 | }
|
300 |
|
301 | RadiumEnhancer.displayName = component.displayName ||
|
302 | component.name ||
|
303 | 'Component';
|
304 |
|
305 | RadiumEnhancer.contextTypes = {
|
306 | ...RadiumEnhancer.contextTypes,
|
307 | _radiumConfig: PropTypes.object,
|
308 | _radiumStyleKeeper: PropTypes.instanceOf(StyleKeeper)
|
309 | };
|
310 |
|
311 | RadiumEnhancer.childContextTypes = {
|
312 | ...RadiumEnhancer.childContextTypes,
|
313 | _radiumConfig: PropTypes.object,
|
314 | _radiumStyleKeeper: PropTypes.instanceOf(StyleKeeper)
|
315 | };
|
316 |
|
317 | return RadiumEnhancer;
|
318 | }
|