UNPKG

6.48 kBJavaScriptView Raw
1// @flow
2import * as React from 'react'
3import {
4 getDefaultShouldForwardProp,
5 composeShouldForwardProps,
6 type StyledOptions,
7 type CreateStyled,
8 type PrivateStyledComponent,
9 type StyledElementType
10} from './utils'
11import { withEmotionCache, ThemeContext } from '@emotion/react'
12import {
13 getRegisteredStyles,
14 insertStyles,
15 registerStyles
16} from '@emotion/utils'
17import { serializeStyles } from '@emotion/serialize'
18import useInsertionEffectMaybe from './useInsertionEffectMaybe'
19
20const ILLEGAL_ESCAPE_SEQUENCE_ERROR = `You have illegal escape sequence in your template literal, most likely inside content's property value.
21Because you write your CSS inside a JavaScript string you actually have to do double escaping, so for example "content: '\\00d7';" should become "content: '\\\\00d7';".
22You can read more about this here:
23https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#ES2018_revision_of_illegal_escape_sequences`
24
25let isBrowser = typeof document !== 'undefined'
26
27const Insertion = ({ cache, serialized, isStringTag }) => {
28 registerStyles(cache, serialized, isStringTag)
29
30 const rules = useInsertionEffectMaybe(() =>
31 insertStyles(cache, serialized, isStringTag)
32 )
33
34 if (!isBrowser && rules !== undefined) {
35 let serializedNames = serialized.name
36 let next = serialized.next
37 while (next !== undefined) {
38 serializedNames += ' ' + next.name
39 next = next.next
40 }
41 return (
42 <style
43 {...{
44 [`data-emotion`]: `${cache.key} ${serializedNames}`,
45 dangerouslySetInnerHTML: { __html: rules },
46 nonce: cache.sheet.nonce
47 }}
48 />
49 )
50 }
51 return null
52}
53
54let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
55 if (process.env.NODE_ENV !== 'production') {
56 if (tag === undefined) {
57 throw new Error(
58 'You are trying to create a styled element with an undefined component.\nYou may have forgotten to import it.'
59 )
60 }
61 }
62 const isReal = tag.__emotion_real === tag
63 const baseTag = (isReal && tag.__emotion_base) || tag
64
65 let identifierName
66 let targetClassName
67 if (options !== undefined) {
68 identifierName = options.label
69 targetClassName = options.target
70 }
71
72 const shouldForwardProp = composeShouldForwardProps(tag, options, isReal)
73 const defaultShouldForwardProp =
74 shouldForwardProp || getDefaultShouldForwardProp(baseTag)
75 const shouldUseAs = !defaultShouldForwardProp('as')
76
77 return function <Props>(): PrivateStyledComponent<Props> {
78 let args = arguments
79 let styles =
80 isReal && tag.__emotion_styles !== undefined
81 ? tag.__emotion_styles.slice(0)
82 : []
83
84 if (identifierName !== undefined) {
85 styles.push(`label:${identifierName};`)
86 }
87 if (args[0] == null || args[0].raw === undefined) {
88 styles.push.apply(styles, args)
89 } else {
90 if (process.env.NODE_ENV !== 'production' && args[0][0] === undefined) {
91 console.error(ILLEGAL_ESCAPE_SEQUENCE_ERROR)
92 }
93 styles.push(args[0][0])
94 let len = args.length
95 let i = 1
96 for (; i < len; i++) {
97 if (process.env.NODE_ENV !== 'production' && args[0][i] === undefined) {
98 console.error(ILLEGAL_ESCAPE_SEQUENCE_ERROR)
99 }
100 styles.push(args[i], args[0][i])
101 }
102 }
103
104 // $FlowFixMe: we need to cast StatelessFunctionalComponent to our PrivateStyledComponent class
105 const Styled: PrivateStyledComponent<Props> = withEmotionCache(
106 (props, cache, ref) => {
107 const FinalTag = (shouldUseAs && props.as) || baseTag
108
109 let className = ''
110 let classInterpolations = []
111 let mergedProps = props
112 if (props.theme == null) {
113 mergedProps = {}
114 for (let key in props) {
115 mergedProps[key] = props[key]
116 }
117 mergedProps.theme = React.useContext(ThemeContext)
118 }
119
120 if (typeof props.className === 'string') {
121 className = getRegisteredStyles(
122 cache.registered,
123 classInterpolations,
124 props.className
125 )
126 } else if (props.className != null) {
127 className = `${props.className} `
128 }
129
130 const serialized = serializeStyles(
131 styles.concat(classInterpolations),
132 cache.registered,
133 mergedProps
134 )
135 className += `${cache.key}-${serialized.name}`
136 if (targetClassName !== undefined) {
137 className += ` ${targetClassName}`
138 }
139
140 const finalShouldForwardProp =
141 shouldUseAs && shouldForwardProp === undefined
142 ? getDefaultShouldForwardProp(FinalTag)
143 : defaultShouldForwardProp
144
145 let newProps = {}
146
147 for (let key in props) {
148 if (shouldUseAs && key === 'as') continue
149
150 if (
151 // $FlowFixMe
152 finalShouldForwardProp(key)
153 ) {
154 newProps[key] = props[key]
155 }
156 }
157
158 newProps.className = className
159 newProps.ref = ref
160
161 return (
162 <>
163 <Insertion
164 cache={cache}
165 serialized={serialized}
166 isStringTag={typeof FinalTag === 'string'}
167 />
168 <FinalTag {...newProps} />
169 </>
170 )
171 }
172 )
173
174 Styled.displayName =
175 identifierName !== undefined
176 ? identifierName
177 : `Styled(${
178 typeof baseTag === 'string'
179 ? baseTag
180 : baseTag.displayName || baseTag.name || 'Component'
181 })`
182
183 Styled.defaultProps = tag.defaultProps
184 Styled.__emotion_real = Styled
185 Styled.__emotion_base = baseTag
186 Styled.__emotion_styles = styles
187 Styled.__emotion_forwardProp = shouldForwardProp
188
189 Object.defineProperty(Styled, 'toString', {
190 value() {
191 if (
192 targetClassName === undefined &&
193 process.env.NODE_ENV !== 'production'
194 ) {
195 return 'NO_COMPONENT_SELECTOR'
196 }
197 // $FlowFixMe: coerce undefined to string
198 return `.${targetClassName}`
199 }
200 })
201
202 Styled.withComponent = (
203 nextTag: StyledElementType<Props>,
204 nextOptions?: StyledOptions
205 ) => {
206 return createStyled(nextTag, {
207 ...options,
208 // $FlowFixMe
209 ...nextOptions,
210 shouldForwardProp: composeShouldForwardProps(Styled, nextOptions, true)
211 })(...styles)
212 }
213
214 return Styled
215 }
216}
217
218export default createStyled