import isBrowser from '#is-browser'
import isDevelopment from '#is-development'
import { Theme, ThemeContext, withEmotionCache } from '@emotion/react'
import { Interpolation, serializeStyles } from '@emotion/serialize'
import { useInsertionEffectAlwaysWithSyncFallback } from '@emotion/use-insertion-effect-with-fallbacks'
import {
EmotionCache,
getRegisteredStyles,
insertStyles,
registerStyles,
SerializedStyles
} from '@emotion/utils'
import * as React from 'react'
import { CreateStyled, ElementType, StyledOptions } from './types'
import { composeShouldForwardProps, getDefaultShouldForwardProp } from './utils'
export type {
ArrayInterpolation,
ComponentSelector,
CSSObject,
FunctionInterpolation,
Interpolation
} from '@emotion/serialize'
const ILLEGAL_ESCAPE_SEQUENCE_ERROR = `You have illegal escape sequence in your template literal, most likely inside content's property value.
Because you write your CSS inside a JavaScript string you actually have to do double escaping, so for example "content: '\\00d7';" should become "content: '\\\\00d7';".
You can read more about this here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#ES2018_revision_of_illegal_escape_sequences`
const Insertion = ({
cache,
serialized,
isStringTag
}: {
cache: EmotionCache
serialized: SerializedStyles
isStringTag: boolean
}) => {
registerStyles(cache, serialized, isStringTag)
const rules = useInsertionEffectAlwaysWithSyncFallback(() =>
insertStyles(cache, serialized, isStringTag)
)
if (!isBrowser && rules !== undefined) {
let serializedNames = serialized.name
let next = serialized.next
while (next !== undefined) {
serializedNames += ' ' + next.name
next = next.next
}
return (
)
}
return null
}
const createStyled = (tag: ElementType, options?: StyledOptions) => {
if (isDevelopment) {
if (tag === undefined) {
throw new Error(
'You are trying to create a styled element with an undefined component.\nYou may have forgotten to import it.'
)
}
}
const isReal = tag.__emotion_real === tag
const baseTag = (isReal && tag.__emotion_base) || tag
let identifierName: string | undefined
let targetClassName: string | undefined
if (options !== undefined) {
identifierName = options.label
targetClassName = options.target
}
const shouldForwardProp = composeShouldForwardProps(tag, options, isReal)
const defaultShouldForwardProp =
shouldForwardProp || getDefaultShouldForwardProp(baseTag)
const shouldUseAs = !defaultShouldForwardProp('as')
return function () {
// eslint-disable-next-line prefer-rest-params
let args = arguments as any as Array<
TemplateStringsArray | Interpolation
>
let styles =
isReal && tag.__emotion_styles !== undefined
? tag.__emotion_styles.slice(0)
: []
if (identifierName !== undefined) {
styles.push(`label:${identifierName};`)
}
if (
args[0] == null ||
(args[0] as TemplateStringsArray).raw === undefined
) {
// eslint-disable-next-line prefer-spread
styles.push.apply(styles, args)
} else {
const templateStringsArr = args[0] as TemplateStringsArray
if (isDevelopment && templateStringsArr[0] === undefined) {
console.error(ILLEGAL_ESCAPE_SEQUENCE_ERROR)
}
styles.push(templateStringsArr[0])
let len = args.length
let i = 1
for (; i < len; i++) {
if (isDevelopment && templateStringsArr[i] === undefined) {
console.error(ILLEGAL_ESCAPE_SEQUENCE_ERROR)
}
styles.push(args[i], templateStringsArr[i])
}
}
const Styled: ElementType = withEmotionCache(
(props: Record, cache, ref) => {
const FinalTag =
(shouldUseAs && (props.as as React.ElementType)) || baseTag
let className = ''
let classInterpolations: Interpolation[] = []
let mergedProps = props
if (props.theme == null) {
mergedProps = {}
for (let key in props) {
mergedProps[key] = props[key]
}
mergedProps.theme = React.useContext(ThemeContext)
}
if (typeof props.className === 'string') {
className = getRegisteredStyles(
cache.registered,
classInterpolations,
props.className
)
} else if (props.className != null) {
className = `${props.className} `
}
const serialized = serializeStyles(
styles.concat(classInterpolations),
cache.registered,
mergedProps
)
className += `${cache.key}-${serialized.name}`
if (targetClassName !== undefined) {
className += ` ${targetClassName}`
}
const finalShouldForwardProp =
shouldUseAs && shouldForwardProp === undefined
? getDefaultShouldForwardProp(FinalTag)
: defaultShouldForwardProp
let newProps: Record = {}
for (let key in props) {
if (shouldUseAs && key === 'as') continue
if (finalShouldForwardProp(key)) {
newProps[key] = props[key]
}
}
newProps.className = className
if (ref) {
newProps.ref = ref
}
return (
<>
>
)
}
)
Styled.displayName =
identifierName !== undefined
? identifierName
: `Styled(${
typeof baseTag === 'string'
? baseTag
: baseTag.displayName || baseTag.name || 'Component'
})`
Styled.defaultProps = tag.defaultProps
Styled.__emotion_real = Styled
Styled.__emotion_base = baseTag
Styled.__emotion_styles = styles
Styled.__emotion_forwardProp = shouldForwardProp
Object.defineProperty(Styled, 'toString', {
value() {
if (targetClassName === undefined && isDevelopment) {
return 'NO_COMPONENT_SELECTOR'
}
return `.${targetClassName}`
}
})
;(Styled as any).withComponent = (
nextTag: ElementType,
nextOptions: StyledOptions
) => {
const newStyled = createStyled(nextTag, {
...options,
...nextOptions,
shouldForwardProp: composeShouldForwardProps(Styled, nextOptions, true)
})
return (newStyled as any)(...styles)
}
return Styled
}
}
export default createStyled as CreateStyled