styled-components 2.1.1 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -1
- package/CODE_OF_CONDUCT.md +1 -1
- package/README.md +12 -67
- package/dist/styled-components.es.js +273 -110
- package/dist/styled-components.js +325 -119
- package/dist/styled-components.min.js +2 -2
- package/lib/hoc/withTheme.js +13 -7
- package/lib/models/BrowserStyleSheet.js +11 -0
- package/lib/models/ComponentStyle.js +45 -2
- package/lib/models/InlineStyle.js +1 -1
- package/lib/models/ServerStyleSheet.js +33 -17
- package/lib/models/StyleSheet.js +9 -0
- package/lib/models/StyledComponent.js +82 -38
- package/lib/models/StyledNativeComponent.js +31 -15
- package/lib/models/ThemeProvider.js +44 -12
- package/lib/native/index.js +1 -1
- package/lib/test/utils.js +5 -2
- package/lib/utils/create-broadcast.js +34 -24
- package/lib/utils/domElements.js +1 -1
- package/lib/utils/flatten.js +4 -1
- package/lib/utils/generateAlphabeticName.js +1 -1
- package/lib/utils/nonce.js +10 -0
- package/lib/utils/once.js +17 -0
- package/package.json +10 -10
- package/src/hoc/withTheme.js +14 -7
- package/src/models/BrowserStyleSheet.js +8 -0
- package/src/models/ComponentStyle.js +42 -2
- package/src/models/InlineStyle.js +1 -1
- package/src/models/ServerStyleSheet.js +27 -12
- package/src/models/StyleSheet.js +9 -0
- package/src/models/StyledComponent.js +81 -26
- package/src/models/StyledNativeComponent.js +30 -10
- package/src/models/ThemeProvider.js +38 -9
- package/src/models/test/ThemeProvider.test.js +7 -8
- package/src/native/index.js +1 -1
- package/src/native/test/native.test.js +14 -0
- package/src/test/__snapshots__/ssr.test.js.snap +147 -0
- package/src/test/expanded-api.test.js +24 -0
- package/src/test/props.test.js +14 -3
- package/src/test/ssr.test.js +90 -123
- package/src/test/styles.test.js +52 -0
- package/src/test/utils.js +5 -2
- package/src/utils/create-broadcast.js +31 -17
- package/src/utils/domElements.js +1 -0
- package/src/utils/flatten.js +16 -6
- package/src/utils/generateAlphabeticName.js +1 -1
- package/src/utils/nonce.js +6 -0
- package/src/utils/once.js +12 -0
- package/typings/styled-components.d.ts +15 -21
- package/typings/tests/issue1068.tsx +226 -0
- package/typings/tests/main-test.tsx +1 -1
- package/typings/tests/string-tags-test.tsx +62 -0
- package/typings/tests/themed-tests/issue1068.tsx +226 -0
- package/typings/tests/themed-tests/mytheme-styled-components.tsx +1 -1
- package/typings/tests/themed-tests/with-theme-test.tsx +2 -1
- package/typings/tests/with-theme-test.tsx +17 -0
- package/lib/constructors/test/injectGlobal.test.js +0 -63
- package/lib/constructors/test/keyframes.test.js +0 -48
- package/lib/constructors/test/styled.test.js +0 -19
- package/lib/models/AbstractStyledComponent.js +0 -43
- package/lib/models/test/ThemeProvider.test.js +0 -200
- package/lib/native/test/native.test.js +0 -290
- package/lib/no-parser/test/basic.test.js +0 -46
- package/lib/no-parser/test/flatten.test.js +0 -125
- package/lib/no-parser/test/keyframes.test.js +0 -45
- package/lib/primitives/test/primitives.test.js +0 -289
- package/lib/test/attrs.test.js +0 -158
- package/lib/test/basic.test.js +0 -267
- package/lib/test/css.test.js +0 -43
- package/lib/test/expanded-api.test.js +0 -90
- package/lib/test/extending.test.js +0 -198
- package/lib/test/overriding.test.js +0 -35
- package/lib/test/props.test.js +0 -38
- package/lib/test/rehydration.test.js +0 -306
- package/lib/test/ssr.test.js +0 -187
- package/lib/test/styles.test.js +0 -146
- package/lib/test/theme.test.js +0 -497
- package/lib/test/warnTooManyClasses.test.js +0 -71
- package/lib/utils/test/extractCompsFromCSS.test.js +0 -46
- package/lib/utils/test/flatten.test.js +0 -109
- package/lib/utils/test/generateAlphabeticName.test.js +0 -14
- package/lib/utils/test/interleave.test.js +0 -22
- package/lib/utils/test/validAttr.test.js +0 -560
- package/src/models/AbstractStyledComponent.js +0 -21
- package/typings/tags.d.ts +0 -137
|
@@ -9,7 +9,7 @@ var generateAlphabeticName = function generateAlphabeticName(code) {
|
|
|
9
9
|
var name = '';
|
|
10
10
|
var x = void 0;
|
|
11
11
|
|
|
12
|
-
for (x = code; x > charsLength; x = Math.floor(x /
|
|
12
|
+
for (x = code; x > charsLength; x = Math.floor(x / charsLength)) {
|
|
13
13
|
name = chars[x % charsLength] + name;
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
|
|
5
|
+
// Helper to call a given function, only once
|
|
6
|
+
exports.default = function (cb) {
|
|
7
|
+
var called = false;
|
|
8
|
+
|
|
9
|
+
return function () {
|
|
10
|
+
if (!called) {
|
|
11
|
+
called = true;
|
|
12
|
+
cb();
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
module.exports = exports["default"];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "styled-components",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "Visual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress 💅",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "typings/styled-components.d.ts",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "npm run build:lib && npm run build:dist",
|
|
11
11
|
"prebuild:lib": "rimraf lib/*",
|
|
12
|
-
"build:lib": "babel --out-dir lib src",
|
|
12
|
+
"build:lib": "babel --out-dir lib --ignore \"*.test.js\" src",
|
|
13
13
|
"prebuild:dist": "rimraf dist/*",
|
|
14
14
|
"build:dist": "rollup -c && rollup -c --environment ESBUNDLE && rollup -c --environment PRODUCTION",
|
|
15
15
|
"build:watch": "npm run build:lib -- --watch",
|
|
@@ -71,9 +71,9 @@
|
|
|
71
71
|
"supports-color": "^3.2.3"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@types/react": "^15.0.
|
|
75
|
-
"@types/react-dom": "^15.5.
|
|
76
|
-
"@types/react-native": "^0.
|
|
74
|
+
"@types/react": "^15.0.37",
|
|
75
|
+
"@types/react-dom": "^15.5.1",
|
|
76
|
+
"@types/react-native": "^0.46.0",
|
|
77
77
|
"babel-cli": "^6.22.2",
|
|
78
78
|
"babel-core": "^6.17.0",
|
|
79
79
|
"babel-eslint": "^7.1.1",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"babel-plugin-transform-react-remove-prop-types": "^0.4.1",
|
|
88
88
|
"babel-preset-env": "^1.4.0",
|
|
89
89
|
"babel-preset-react": "^6.22.0",
|
|
90
|
-
"bundlesize": "^0.
|
|
90
|
+
"bundlesize": "^0.13.2",
|
|
91
91
|
"chokidar": "^1.6.0",
|
|
92
92
|
"danger": "^0.16.0",
|
|
93
93
|
"enzyme": "^2.8.2",
|
|
@@ -100,18 +100,17 @@
|
|
|
100
100
|
"eslint-plugin-react": "^6.8.0",
|
|
101
101
|
"express": "^4.14.1",
|
|
102
102
|
"flow-bin": "^0.47.0",
|
|
103
|
-
"flow-copy-source": "^1.1.0",
|
|
104
103
|
"flow-watch": "^1.1.1",
|
|
105
|
-
"jest": "^
|
|
104
|
+
"jest": "^20.0.4",
|
|
106
105
|
"jsdom": "^9.10.0",
|
|
107
106
|
"lint-staged": "^3.3.0",
|
|
108
107
|
"node-watch": "^0.4.1",
|
|
109
108
|
"pre-commit": "^1.2.2",
|
|
110
109
|
"react": "^15.5.4",
|
|
111
|
-
"react-addons-test-utils": "^15.4.1",
|
|
112
110
|
"react-dom": "^15.5.4",
|
|
113
111
|
"react-native": "^0.39.2",
|
|
114
112
|
"react-primitives": "^0.4.2",
|
|
113
|
+
"react-test-renderer": "^15.6.1",
|
|
115
114
|
"rimraf": "^2.6.1",
|
|
116
115
|
"rollup": "0.43.0",
|
|
117
116
|
"rollup-plugin-babel": "^2.7.1",
|
|
@@ -124,12 +123,13 @@
|
|
|
124
123
|
"rollup-plugin-uglify": "^1.0.1",
|
|
125
124
|
"rollup-plugin-visualizer": "^0.1.5",
|
|
126
125
|
"tslint": "^4.3.1",
|
|
127
|
-
"typescript": "^2.
|
|
126
|
+
"typescript": "^2.4.1"
|
|
128
127
|
},
|
|
129
128
|
"peerDependencies": {
|
|
130
129
|
"react": ">= 0.14.0 < 17.0.0-0"
|
|
131
130
|
},
|
|
132
131
|
"jest": {
|
|
132
|
+
"clearMocks": true,
|
|
133
133
|
"roots": [
|
|
134
134
|
"<rootDir>/src/"
|
|
135
135
|
],
|
package/src/hoc/withTheme.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import React from 'react'
|
|
5
5
|
import PropTypes from 'prop-types'
|
|
6
6
|
import hoistStatics from 'hoist-non-react-statics'
|
|
7
|
-
import { CHANNEL } from '../models/ThemeProvider'
|
|
7
|
+
import { CHANNEL, CHANNEL_NEXT, CONTEXT_CHANNEL_SHAPE } from '../models/ThemeProvider'
|
|
8
8
|
import _isStyledComponent from '../utils/isStyledComponent'
|
|
9
9
|
|
|
10
10
|
const wrapWithTheme = (Component: ReactClass<any>) => {
|
|
@@ -24,24 +24,31 @@ const wrapWithTheme = (Component: ReactClass<any>) => {
|
|
|
24
24
|
|
|
25
25
|
static contextTypes = {
|
|
26
26
|
[CHANNEL]: PropTypes.func,
|
|
27
|
+
[CHANNEL_NEXT]: CONTEXT_CHANNEL_SHAPE,
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
state: { theme?: ?Object } = {};
|
|
30
|
-
|
|
31
|
+
unsubscribeId: number = -1
|
|
31
32
|
|
|
32
33
|
componentWillMount() {
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
const styledContext = this.context[CHANNEL_NEXT]
|
|
35
|
+
if (styledContext === undefined) {
|
|
36
|
+
// eslint-disable-next-line no-console
|
|
37
|
+
console.error('[withTheme] Please use ThemeProvider to be able to use withTheme')
|
|
38
|
+
return
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
|
|
42
|
+
const { subscribe } = styledContext
|
|
43
|
+
this.unsubscribeId = subscribe(theme => {
|
|
39
44
|
this.setState({ theme })
|
|
40
45
|
})
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
componentWillUnmount() {
|
|
44
|
-
if (
|
|
49
|
+
if (this.unsubscribeId !== -1) {
|
|
50
|
+
this.context[CHANNEL_NEXT].unsubscribe(this.unsubscribeId)
|
|
51
|
+
}
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
render() {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @flow
|
|
2
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
3
|
/*
|
|
3
4
|
* Browser Style Sheet with Rehydration
|
|
4
5
|
*
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
* Note: replace · with * in the above snippet.
|
|
17
18
|
* */
|
|
18
19
|
import extractCompsFromCSS from '../utils/extractCompsFromCSS'
|
|
20
|
+
import getNonce from '../utils/nonce'
|
|
19
21
|
import type { Tag } from './StyleSheet'
|
|
20
22
|
import StyleSheet, { SC_ATTR, LOCAL_ATTR } from './StyleSheet'
|
|
21
23
|
|
|
@@ -69,6 +71,12 @@ class BrowserTag implements Tag {
|
|
|
69
71
|
const existingNames = this.el.getAttribute(SC_ATTR)
|
|
70
72
|
this.el.setAttribute(SC_ATTR, existingNames ? `${existingNames} ${name}` : name)
|
|
71
73
|
}
|
|
74
|
+
|
|
75
|
+
const nonce = getNonce()
|
|
76
|
+
|
|
77
|
+
if (nonce) {
|
|
78
|
+
this.el.setAttribute('nonce', nonce)
|
|
79
|
+
}
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
toHTML() {
|
|
@@ -3,6 +3,24 @@ import hashStr from '../vendor/glamor/hash'
|
|
|
3
3
|
|
|
4
4
|
import type { RuleSet, NameGenerator, Flattener, Stringifier } from '../types'
|
|
5
5
|
import StyleSheet from './StyleSheet'
|
|
6
|
+
import isStyledComponent from '../utils/isStyledComponent'
|
|
7
|
+
|
|
8
|
+
const isStaticRules = (rules: RuleSet): boolean => {
|
|
9
|
+
for (let i = 0; i < rules.length; i += 1) {
|
|
10
|
+
const rule = rules[i]
|
|
11
|
+
|
|
12
|
+
// recursive case
|
|
13
|
+
if (Array.isArray(rule) && !isStaticRules(rule)) {
|
|
14
|
+
return false
|
|
15
|
+
} else if (typeof rule === 'function' && !isStyledComponent(rule)) {
|
|
16
|
+
// functions are allowed to be static if they're just being
|
|
17
|
+
// used to get the classname of a nested styled copmonent
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
6
24
|
|
|
7
25
|
/*
|
|
8
26
|
ComponentStyle is all the CSS-specific stuff, not
|
|
@@ -12,9 +30,13 @@ export default (nameGenerator: NameGenerator, flatten: Flattener, stringifyRules
|
|
|
12
30
|
class ComponentStyle {
|
|
13
31
|
rules: RuleSet
|
|
14
32
|
componentId: string
|
|
33
|
+
isStatic: boolean
|
|
34
|
+
lastClassName: ?string
|
|
35
|
+
|
|
15
36
|
|
|
16
37
|
constructor(rules: RuleSet, componentId: string) {
|
|
17
38
|
this.rules = rules
|
|
39
|
+
this.isStatic = isStaticRules(rules)
|
|
18
40
|
this.componentId = componentId
|
|
19
41
|
if (!StyleSheet.instance.hasInjectedComponent(this.componentId)) {
|
|
20
42
|
const placeholder = process.env.NODE_ENV !== 'production' ? `.${componentId} {}` : ''
|
|
@@ -28,16 +50,34 @@ export default (nameGenerator: NameGenerator, flatten: Flattener, stringifyRules
|
|
|
28
50
|
* Returns the hash to be injected on render()
|
|
29
51
|
* */
|
|
30
52
|
generateAndInjectStyles(executionContext: Object, styleSheet: StyleSheet) {
|
|
53
|
+
const { isStatic, lastClassName } = this
|
|
54
|
+
if (isStatic && lastClassName !== undefined) {
|
|
55
|
+
return lastClassName
|
|
56
|
+
}
|
|
57
|
+
|
|
31
58
|
const flatCSS = flatten(this.rules, executionContext)
|
|
32
59
|
const hash = hashStr(this.componentId + flatCSS.join(''))
|
|
33
60
|
|
|
34
61
|
const existingName = styleSheet.getName(hash)
|
|
35
|
-
if (existingName)
|
|
62
|
+
if (existingName !== undefined) {
|
|
63
|
+
if (styleSheet.stylesCacheable) {
|
|
64
|
+
this.lastClassName = existingName
|
|
65
|
+
}
|
|
66
|
+
return existingName
|
|
67
|
+
}
|
|
36
68
|
|
|
37
69
|
const name = nameGenerator(hash)
|
|
38
|
-
if (styleSheet.
|
|
70
|
+
if (styleSheet.stylesCacheable) {
|
|
71
|
+
this.lastClassName = existingName
|
|
72
|
+
}
|
|
73
|
+
if (styleSheet.alreadyInjected(hash, name)) {
|
|
74
|
+
return name
|
|
75
|
+
}
|
|
39
76
|
|
|
40
77
|
const css = `\n${stringifyRules(flatCSS, `.${name}`)}`
|
|
78
|
+
// NOTE: this can only be set when we inject the class-name.
|
|
79
|
+
// For some reason, presumably due to how css is stringifyRules behaves in
|
|
80
|
+
// differently between client and server, styles break.
|
|
41
81
|
styleSheet.inject(this.componentId, true, css, hash, name)
|
|
42
82
|
return name
|
|
43
83
|
}
|
|
@@ -33,7 +33,7 @@ export default (styleSheet: StyleSheet) => {
|
|
|
33
33
|
root.each(node => {
|
|
34
34
|
if (node.type === 'decl') {
|
|
35
35
|
declPairs.push([node.prop, node.value])
|
|
36
|
-
} else {
|
|
36
|
+
} else if (node.type !== 'comment') {
|
|
37
37
|
/* eslint-disable no-console */
|
|
38
38
|
console.warn(`Node of type ${node.type} not supported as an inline style`)
|
|
39
39
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// @flow
|
|
2
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
3
|
import React from 'react'
|
|
3
4
|
import type { Tag } from './StyleSheet'
|
|
4
5
|
import StyleSheet, { SC_ATTR, LOCAL_ATTR, clones } from './StyleSheet'
|
|
5
6
|
import StyleSheetManager from './StyleSheetManager'
|
|
7
|
+
import getNonce from '../utils/nonce'
|
|
6
8
|
|
|
7
9
|
class ServerTag implements Tag {
|
|
8
10
|
isLocal: boolean
|
|
@@ -27,6 +29,10 @@ class ServerTag implements Tag {
|
|
|
27
29
|
this.size += 1
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
concatenateCSS() {
|
|
33
|
+
return Object.keys(this.components).reduce((styles, k) => (styles + this.components[k].css), '')
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
inject(componentId: string, css: string, name: ?string) {
|
|
31
37
|
const comp = this.components[componentId]
|
|
32
38
|
|
|
@@ -39,28 +45,37 @@ class ServerTag implements Tag {
|
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
toHTML() {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
.
|
|
46
|
-
|
|
48
|
+
const attrs = [
|
|
49
|
+
'type="text/css"',
|
|
50
|
+
`${SC_ATTR}="${this.names.join(' ')}"`,
|
|
51
|
+
`${LOCAL_ATTR}="${this.isLocal ? 'true' : 'false'}"`,
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
const nonce = getNonce()
|
|
55
|
+
|
|
56
|
+
if (nonce) {
|
|
57
|
+
attrs.push(`nonce="${nonce}"`)
|
|
58
|
+
}
|
|
47
59
|
|
|
48
|
-
return `<style
|
|
60
|
+
return `<style ${attrs.join(' ')}>${this.concatenateCSS()}</style>`
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
toReactElement(key: string) {
|
|
52
|
-
const
|
|
64
|
+
const attrs: Object = {
|
|
53
65
|
[SC_ATTR]: this.names.join(' '),
|
|
54
66
|
[LOCAL_ATTR]: this.isLocal.toString(),
|
|
55
67
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
|
|
69
|
+
const nonce = getNonce()
|
|
70
|
+
|
|
71
|
+
if (nonce) {
|
|
72
|
+
attrs.nonce = nonce
|
|
73
|
+
}
|
|
59
74
|
|
|
60
75
|
return (
|
|
61
76
|
<style
|
|
62
|
-
key={key} type="text/css" {...
|
|
63
|
-
dangerouslySetInnerHTML={{ __html:
|
|
77
|
+
key={key} type="text/css" {...attrs}
|
|
78
|
+
dangerouslySetInnerHTML={{ __html: this.concatenateCSS() }}
|
|
64
79
|
/>
|
|
65
80
|
)
|
|
66
81
|
}
|
package/src/models/StyleSheet.js
CHANGED
|
@@ -30,6 +30,14 @@ export default class StyleSheet {
|
|
|
30
30
|
hashes: { [string]: string } = {}
|
|
31
31
|
deferredInjections: { [string]: string } = {}
|
|
32
32
|
componentTags: { [string]: Tag }
|
|
33
|
+
// helper for `ComponentStyle` to know when it cache static styles.
|
|
34
|
+
// staticly styled-component can not safely cache styles on the server
|
|
35
|
+
// without all `ComponentStyle` instances saving a reference to the
|
|
36
|
+
// the styleSheet instance they last rendered with,
|
|
37
|
+
// or listening to creation / reset events. otherwise you might create
|
|
38
|
+
// a component with one stylesheet and render it another api response
|
|
39
|
+
// with another, losing styles on from your server-side render.
|
|
40
|
+
stylesCacheable = typeof document !== 'undefined'
|
|
33
41
|
|
|
34
42
|
constructor(tagConstructor: (boolean) => Tag,
|
|
35
43
|
tags: Array<Tag> = [],
|
|
@@ -146,6 +154,7 @@ export default class StyleSheet {
|
|
|
146
154
|
return (isServer ? ServerStyleSheet : BrowserStyleSheet).create()
|
|
147
155
|
}
|
|
148
156
|
|
|
157
|
+
|
|
149
158
|
static clone(oldSheet: StyleSheet) {
|
|
150
159
|
const newSheet = new StyleSheet(
|
|
151
160
|
oldSheet.tagConstructor,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
|
|
3
|
-
import { createElement } from 'react'
|
|
3
|
+
import { Component, createElement } from 'react'
|
|
4
4
|
import PropTypes from 'prop-types'
|
|
5
5
|
|
|
6
6
|
import type { Theme } from './ThemeProvider'
|
|
@@ -12,17 +12,20 @@ import isStyledComponent from '../utils/isStyledComponent'
|
|
|
12
12
|
import getComponentName from '../utils/getComponentName'
|
|
13
13
|
import type { RuleSet, Target } from '../types'
|
|
14
14
|
|
|
15
|
-
import
|
|
16
|
-
import { CHANNEL } from './ThemeProvider'
|
|
15
|
+
import { CHANNEL, CHANNEL_NEXT, CONTEXT_CHANNEL_SHAPE } from './ThemeProvider'
|
|
17
16
|
import StyleSheet, { CONTEXT_KEY } from './StyleSheet'
|
|
18
17
|
|
|
19
18
|
const escapeRegex = /[[\].#*$><+~=|^:(),"'`]/g
|
|
20
19
|
const multiDashRegex = /--+/g
|
|
21
20
|
|
|
21
|
+
// HACK for generating all static styles without needing to allocate
|
|
22
|
+
// an empty execution context every single time...
|
|
23
|
+
const STATIC_EXECUTION_CONTEXT = {}
|
|
24
|
+
|
|
22
25
|
export default (ComponentStyle: Function, constructWithOptions: Function) => {
|
|
23
26
|
/* We depend on components having unique IDs */
|
|
24
27
|
const identifiers = {}
|
|
25
|
-
const generateId = (_displayName: string) => {
|
|
28
|
+
const generateId = (_displayName: string, parentComponentId: string) => {
|
|
26
29
|
const displayName = typeof _displayName !== 'string' ?
|
|
27
30
|
'sc' : _displayName
|
|
28
31
|
.replace(escapeRegex, '-') // Replace all possible CSS selectors
|
|
@@ -32,10 +35,13 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => {
|
|
|
32
35
|
identifiers[displayName] = nr
|
|
33
36
|
|
|
34
37
|
const hash = ComponentStyle.generateName(displayName + nr)
|
|
35
|
-
|
|
38
|
+
const componentId = `${displayName}-${hash}`
|
|
39
|
+
return parentComponentId !== undefined
|
|
40
|
+
? `${parentComponentId}-${componentId}`
|
|
41
|
+
: componentId
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
class BaseStyledComponent extends
|
|
44
|
+
class BaseStyledComponent extends Component {
|
|
39
45
|
static target: Target
|
|
40
46
|
static styledComponentId: string
|
|
41
47
|
static attrs: Object
|
|
@@ -47,6 +53,13 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => {
|
|
|
47
53
|
theme: null,
|
|
48
54
|
generatedClassName: '',
|
|
49
55
|
}
|
|
56
|
+
unsubscribeId: number = -1
|
|
57
|
+
|
|
58
|
+
unsubscribeFromContext() {
|
|
59
|
+
if (this.unsubscribeId !== -1) {
|
|
60
|
+
this.context[CHANNEL_NEXT].unsubscribe(this.unsubscribeId)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
50
63
|
|
|
51
64
|
buildExecutionContext(theme: any, props: any) {
|
|
52
65
|
const { attrs } = this.constructor
|
|
@@ -66,34 +79,55 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => {
|
|
|
66
79
|
}
|
|
67
80
|
|
|
68
81
|
generateAndInjectStyles(theme: any, props: any) {
|
|
69
|
-
const { componentStyle, warnTooManyClasses } = this.constructor
|
|
70
|
-
const executionContext = this.buildExecutionContext(theme, props)
|
|
82
|
+
const { attrs, componentStyle, warnTooManyClasses } = this.constructor
|
|
71
83
|
const styleSheet = this.context[CONTEXT_KEY] || StyleSheet.instance
|
|
72
|
-
const className = componentStyle.generateAndInjectStyles(executionContext, styleSheet)
|
|
73
84
|
|
|
74
|
-
|
|
85
|
+
// staticaly styled-components don't need to build an execution context object,
|
|
86
|
+
// and shouldn't be increasing the number of class names
|
|
87
|
+
if (componentStyle.isStatic && attrs === undefined) {
|
|
88
|
+
return componentStyle.generateAndInjectStyles(STATIC_EXECUTION_CONTEXT, styleSheet)
|
|
89
|
+
} else {
|
|
90
|
+
const executionContext = this.buildExecutionContext(theme, props)
|
|
91
|
+
const className = componentStyle.generateAndInjectStyles(executionContext, styleSheet)
|
|
92
|
+
|
|
93
|
+
if (warnTooManyClasses !== undefined) warnTooManyClasses(className)
|
|
75
94
|
|
|
76
|
-
|
|
95
|
+
return className
|
|
96
|
+
}
|
|
77
97
|
}
|
|
78
98
|
|
|
79
99
|
componentWillMount() {
|
|
100
|
+
const { componentStyle } = this.constructor
|
|
101
|
+
const styledContext = this.context[CHANNEL_NEXT]
|
|
102
|
+
|
|
103
|
+
// If this is a staticaly-styled component, we don't need to the theme
|
|
104
|
+
// to generate or build styles.
|
|
105
|
+
if (componentStyle.isStatic) {
|
|
106
|
+
const generatedClassName = this.generateAndInjectStyles(
|
|
107
|
+
STATIC_EXECUTION_CONTEXT,
|
|
108
|
+
this.props,
|
|
109
|
+
)
|
|
110
|
+
this.setState({ generatedClassName })
|
|
80
111
|
// If there is a theme in the context, subscribe to the event emitter. This
|
|
81
112
|
// is necessary due to pure components blocking context updates, this circumvents
|
|
82
113
|
// that by updating when an event is emitted
|
|
83
|
-
if (
|
|
84
|
-
const subscribe =
|
|
85
|
-
this.
|
|
114
|
+
} else if (styledContext !== undefined) {
|
|
115
|
+
const { subscribe } = styledContext
|
|
116
|
+
this.unsubscribeId = subscribe(nextTheme => {
|
|
86
117
|
// This will be called once immediately
|
|
87
118
|
|
|
88
119
|
// Props should take precedence over ThemeProvider, which should take precedence over
|
|
89
120
|
// defaultProps, but React automatically puts defaultProps on props.
|
|
90
121
|
const { defaultProps } = this.constructor
|
|
122
|
+
/* eslint-disable react/prop-types */
|
|
91
123
|
const isDefaultTheme = defaultProps && this.props.theme === defaultProps.theme
|
|
92
124
|
const theme = this.props.theme && !isDefaultTheme ? this.props.theme : nextTheme
|
|
125
|
+
/* eslint-enable */
|
|
93
126
|
const generatedClassName = this.generateAndInjectStyles(theme, this.props)
|
|
94
127
|
this.setState({ theme, generatedClassName })
|
|
95
128
|
})
|
|
96
129
|
} else {
|
|
130
|
+
// eslint-disable-next-line react/prop-types
|
|
97
131
|
const theme = this.props.theme || {}
|
|
98
132
|
const generatedClassName = this.generateAndInjectStyles(
|
|
99
133
|
theme,
|
|
@@ -104,12 +138,21 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => {
|
|
|
104
138
|
}
|
|
105
139
|
|
|
106
140
|
componentWillReceiveProps(nextProps: { theme?: Theme, [key: string]: any }) {
|
|
141
|
+
// If this is a staticaly-styled component, we don't need to listen to
|
|
142
|
+
// props changes to update styles
|
|
143
|
+
const { componentStyle } = this.constructor
|
|
144
|
+
if (componentStyle.isStatic) {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
107
148
|
this.setState((oldState) => {
|
|
108
149
|
// Props should take precedence over ThemeProvider, which should take precedence over
|
|
109
150
|
// defaultProps, but React automatically puts defaultProps on props.
|
|
110
151
|
const { defaultProps } = this.constructor
|
|
152
|
+
/* eslint-disable react/prop-types */
|
|
111
153
|
const isDefaultTheme = defaultProps && nextProps.theme === defaultProps.theme
|
|
112
154
|
const theme = nextProps.theme && !isDefaultTheme ? nextProps.theme : oldState.theme
|
|
155
|
+
/* eslint-enable */
|
|
113
156
|
const generatedClassName = this.generateAndInjectStyles(theme, nextProps)
|
|
114
157
|
|
|
115
158
|
return { theme, generatedClassName }
|
|
@@ -117,12 +160,11 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => {
|
|
|
117
160
|
}
|
|
118
161
|
|
|
119
162
|
componentWillUnmount() {
|
|
120
|
-
|
|
121
|
-
this.unsubscribe()
|
|
122
|
-
}
|
|
163
|
+
this.unsubscribeFromContext()
|
|
123
164
|
}
|
|
124
165
|
|
|
125
166
|
render() {
|
|
167
|
+
// eslint-disable-next-line react/prop-types
|
|
126
168
|
const { innerRef } = this.props
|
|
127
169
|
const { generatedClassName } = this.state
|
|
128
170
|
const { styledComponentId, target } = this.constructor
|
|
@@ -130,6 +172,7 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => {
|
|
|
130
172
|
const isTargetTag = isTag(target)
|
|
131
173
|
|
|
132
174
|
const className = [
|
|
175
|
+
// eslint-disable-next-line react/prop-types
|
|
133
176
|
this.props.className,
|
|
134
177
|
styledComponentId,
|
|
135
178
|
this.attrs.className,
|
|
@@ -175,7 +218,7 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => {
|
|
|
175
218
|
) => {
|
|
176
219
|
const {
|
|
177
220
|
displayName = isTag(target) ? `styled.${target}` : `Styled(${getComponentName(target)})`,
|
|
178
|
-
componentId = generateId(options.displayName),
|
|
221
|
+
componentId = generateId(options.displayName, options.parentComponentId),
|
|
179
222
|
ParentComponent = BaseStyledComponent,
|
|
180
223
|
rules: extendingRules,
|
|
181
224
|
attrs,
|
|
@@ -185,7 +228,7 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => {
|
|
|
185
228
|
`${options.displayName}-${options.componentId}` : componentId
|
|
186
229
|
|
|
187
230
|
let warnTooManyClasses
|
|
188
|
-
if (
|
|
231
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
189
232
|
warnTooManyClasses = createWarnTooManyClasses(displayName)
|
|
190
233
|
}
|
|
191
234
|
|
|
@@ -197,6 +240,7 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => {
|
|
|
197
240
|
class StyledComponent extends ParentComponent {
|
|
198
241
|
static contextTypes = {
|
|
199
242
|
[CHANNEL]: PropTypes.func,
|
|
243
|
+
[CHANNEL_NEXT]: CONTEXT_CHANNEL_SHAPE,
|
|
200
244
|
[CONTEXT_KEY]: PropTypes.instanceOf(StyleSheet),
|
|
201
245
|
}
|
|
202
246
|
|
|
@@ -208,26 +252,37 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => {
|
|
|
208
252
|
static target = target
|
|
209
253
|
|
|
210
254
|
static withComponent(tag) {
|
|
211
|
-
const {
|
|
212
|
-
|
|
255
|
+
const { componentId: previousComponentId, ...optionsToCopy } = options
|
|
256
|
+
|
|
257
|
+
const newComponentId =
|
|
258
|
+
previousComponentId &&
|
|
259
|
+
`${previousComponentId}-${isTag(tag) ? tag : getComponentName(tag)}`
|
|
260
|
+
|
|
261
|
+
const newOptions = {
|
|
262
|
+
...optionsToCopy,
|
|
263
|
+
componentId: newComponentId,
|
|
264
|
+
ParentComponent: StyledComponent,
|
|
265
|
+
}
|
|
266
|
+
|
|
213
267
|
return createStyledComponent(tag, newOptions, rules)
|
|
214
268
|
}
|
|
215
269
|
|
|
216
270
|
static get extend() {
|
|
217
271
|
const {
|
|
218
|
-
displayName: _,
|
|
219
|
-
componentId: __,
|
|
220
272
|
rules: rulesFromOptions,
|
|
273
|
+
componentId: parentComponentId,
|
|
221
274
|
...optionsToCopy
|
|
222
275
|
} = options
|
|
223
276
|
|
|
224
|
-
const newRules =
|
|
225
|
-
|
|
226
|
-
|
|
277
|
+
const newRules =
|
|
278
|
+
rulesFromOptions === undefined
|
|
279
|
+
? rules
|
|
280
|
+
: rulesFromOptions.concat(rules)
|
|
227
281
|
|
|
228
282
|
const newOptions = {
|
|
229
283
|
...optionsToCopy,
|
|
230
284
|
rules: newRules,
|
|
285
|
+
parentComponentId,
|
|
231
286
|
ParentComponent: StyledComponent,
|
|
232
287
|
}
|
|
233
288
|
|