UNPKG

styled-components

Version:
397 lines (396 loc) 20.4 kB
import type * as CSS from 'csstype'; import React from 'react'; import ComponentStyle from './models/ComponentStyle'; import { DefaultTheme } from './models/ThemeProvider'; import createWarnTooManyClasses from './utils/createWarnTooManyClasses'; import type { SupportedHTMLElements } from './utils/domElements'; export { CSS, DefaultTheme, SupportedHTMLElements }; export interface ExoticComponentWithDisplayName<P extends BaseObject = {}> extends React.ExoticComponent<P> { defaultProps?: Partial<P> | undefined; displayName?: string | undefined; } /** * Use this type to disambiguate between a styled-component instance * and a StyleFunction or any other type of function. */ export type StyledComponentBrand = { readonly _sc: symbol; }; export type BaseObject = {}; export type OmitNever<T> = { [K in keyof T as T[K] extends never ? never : K]: T[K]; }; export type FastOmit<T extends BaseObject, U extends string | number | symbol> = { [K in keyof T as K extends U ? never : K]: T[K]; }; export type Runtime = 'web' | 'native'; export type AnyComponent<P extends BaseObject = any> = ExoticComponentWithDisplayName<P> | React.ComponentType<P>; export type KnownTarget = SupportedHTMLElements | AnyComponent; export type WebTarget = (string & {}) | KnownTarget; export type NativeTarget = AnyComponent; export type StyledTarget<R extends Runtime> = R extends 'web' ? WebTarget : NativeTarget; export interface StyledOptions<R extends Runtime, Props extends BaseObject> { attrs?: Attrs<Props>[] | undefined; componentId?: (R extends 'web' ? string : never) | undefined; displayName?: string | undefined; parentComponentId?: (R extends 'web' ? string : never) | undefined; shouldForwardProp?: ShouldForwardProp<R> | undefined; } export type Dict<T = any> = { [key: string]: T; }; /** * This type is intended for when data attributes are composed via * the `.attrs` API: * * ```tsx * styled.div.attrs<DataAttributes>({ 'data-testid': 'foo' })`` * ``` * * Would love to figure out how to support this natively without having to * manually compose the type, but haven't figured out a way to do so yet that * doesn't cause specificity loss (see `test/types.tsx` if you attempt to embed * `DataAttributes` directly in the `Attrs<>` type.) */ export type DataAttributes = { [key: `data-${string}`]: any; }; export type ExecutionProps = { /** * Dynamically adjust the rendered component or HTML tag, e.g. * ``` * const StyledButton = styled.button`` * * <StyledButton as="a" href="/foo"> * I'm an anchor now * </StyledButton> * ``` */ as?: KnownTarget | undefined; forwardedAs?: KnownTarget | undefined; theme?: DefaultTheme | undefined; }; /** * ExecutionProps but with `theme` narrowed from optional to required. * * Note: in RSC environments where ThemeProvider is a no-op, * `theme` will be `undefined` at runtime. */ export interface ExecutionContext extends ExecutionProps { theme: DefaultTheme; } export interface StyleFunction<Props extends BaseObject> { (executionContext: ExecutionContext & Props): Interpolation<Props>; } export type Interpolation<Props extends BaseObject> = StyleFunction<Props> | StyledObject<Props> | TemplateStringsArray | string | number | false | undefined | null | Keyframes | StyledComponentBrand | RuleSet<Props>; export type Attrs<Props extends BaseObject = BaseObject> = (ExecutionProps & Partial<Props>) | ((props: ExecutionContext & Props) => ExecutionProps & Partial<Props>); export type RuleSet<Props extends BaseObject = BaseObject> = Interpolation<Props>[]; export type Styles<Props extends BaseObject> = TemplateStringsArray | StyledObject<Props> | StyleFunction<Props>; export type NameGenerator = (hash: number) => string; export interface StyleSheet { create: Function; } export interface Keyframes { id: string; name: string; rules: string; } export interface Flattener<Props extends BaseObject> { (chunks: Interpolation<Props>[], executionContext: object | null | undefined, styleSheet: StyleSheet | null | undefined): Interpolation<Props>[]; } export interface Stringifier { (css: string, selector?: string | undefined, prefix?: string | undefined, componentId?: string | undefined): string[]; hash: string; } export interface ShouldForwardProp<R extends Runtime> { (prop: string, elementToBeCreated: StyledTarget<R>): boolean; } export interface CommonStatics<out R extends Runtime, in out Props extends BaseObject> { attrs: Attrs<Props>[]; target: StyledTarget<R>; shouldForwardProp?: ShouldForwardProp<R> | undefined; } export interface IStyledStatics<out R extends Runtime, in out OuterProps extends BaseObject> extends CommonStatics<R, OuterProps> { componentStyle: R extends 'web' ? ComponentStyle : never; foldedComponentIds: R extends 'web' ? string : never; inlineStyle: R extends 'native' ? InstanceType<IInlineStyleConstructor<OuterProps>> : never; target: StyledTarget<R>; styledComponentId: R extends 'web' ? string : never; warnTooManyClasses?: (R extends 'web' ? ReturnType<typeof createWarnTooManyClasses> : never) | undefined; } /** ExecutionProps sans as/forwardedAs, pre-resolved so call sites relate against a concrete interface. */ interface ThemedExecutionProps { theme?: DefaultTheme | undefined; } /** * Props of a render target, for `as` / `forwardedAs`. * * One distributive conditional, never two nested, and tags resolve by indexed * access rather than `React.ComponentPropsWithRef`. Both are load-bearing: this * shape is the #5767 fix, and nesting a `T extends KnownTarget` check around it * costs ~4x the check time. The `AnyComponent` arm doubles as that test, and * every non-target falls through to `{}`. * * The `style` widening happens here, once per target, rather than at every JSX * call site -- directly via {@link WithCSSVars} on the intrinsic arm, which * needs no guard, and via {@link OverrideStyle} on the component arm, which * does. See AGENTS.md before changing any of it. * * `R` carries the runtime so the widening stays web-only; it is deliberately * undefaulted, since a default is what would let a native call site pick up web * CSS by omission. */ export type TargetProps<R extends Runtime, T> = T extends keyof React.JSX.IntrinsicElements ? IntrinsicProps<T> : T extends AnyComponent ? ComponentTargetProps<R, T> : {}; /** * Props of an HTML or SVG tag. * * Both branches of {@link TargetProps} are named rather than inlined, so a * component's type reads as `Substituted<IntrinsicProps<"button">, { … }>` * instead of the full expansion of every tag attribute. See {@link WithCSSVars} * for why a conditional's inline branch cannot keep a name. * * Applies the widening directly rather than through {@link OverrideStyle}: every * intrinsic element declares `style`, so the guard has nothing to decide here. */ type IntrinsicProps<T extends keyof React.JSX.IntrinsicElements> = WithCSSVars<React.JSX.IntrinsicElements[T]>; /** * Props of a component render target. Named for the same reason as {@link IntrinsicProps}. * * The `style` widening is web-only: a React Native `style` takes a `ViewStyle`, * which carries neither web CSS nor custom properties. This is the only seam that * knows the runtime, which is why the gate sits here rather than inside * {@link OverrideStyle}. The conditional is over `Runtime` -- two members, concrete * at every entry point -- never over the target union. */ type ComponentTargetProps<R extends Runtime, T extends AnyComponent> = R extends 'web' ? OverrideStyle<React.ComponentPropsWithRef<T>> : React.ComponentPropsWithRef<T>; /** * Used by PolymorphicComponent to define prop override cascading order. */ export type PolymorphicComponentProps<R extends Runtime, BaseProps extends BaseObject, AsTarget extends StyledTarget<R> | (BaseProps extends { as?: infer A; } ? A : never) | void, ForwardedAsTarget extends StyledTarget<R> | void, AsTargetProps extends BaseObject = TargetProps<R, AsTarget>, ForwardedAsTargetProps extends BaseObject = TargetProps<R, ForwardedAsTarget>> = NoInfer<FastOmit<MergeProps<BaseProps, Substitute<ForwardedAsTargetProps, AsTargetProps>>, keyof ExecutionProps>> & ThemedExecutionProps & { as?: AsTarget; forwardedAs?: ForwardedAsTarget; }; /** * Resolves the call-site props for one usage of a polymorphic component from its * `as` / `forwardedAs` targets. An `as` render target has its props merged over * the base props and requires `as`; plain usage (or `as` being the wrapped * component's own non-target type, e.g. Next.js Link's `as?: Url`) reaches * {@link PolymorphicComponentProps} not at all, so the base props stay untouched * and ref callbacks infer with spread props (#5687), the wrapped `as` stays * assignable (#5734), and BaseProps keys keep completing (#5741). `forwardedAs` * merges the same way, and loses to `as` where both name a target. * * The target test is `string | AnyComponent`, not `KnownTarget`: narrowing it * drops custom element strings (`as="my-element"`) out of the target branch. * * Load-bearing shape, do not simplify: two conditionals with `unknown` sibling * branches (not one three-way conditional), a leading flat `{ as?; forwardedAs? }` * member, and positive `extends [string | AnyComponent]` discriminants. Collapsing * the conditionals, dropping the flat member, or using a `[void]` discriminant * each regress plain-call-site cost, `as`-target completion, or ref-callback * inference (#5687) respectively. */ type PolymorphicCallProps<R extends Runtime, BaseProps extends BaseObject, AsTarget extends StyledTarget<R> | (BaseProps extends { as?: infer A; } ? A : never) | void, ForwardedAsTarget extends StyledTarget<R> | void> = { as?: AsTarget | undefined; forwardedAs?: ForwardedAsTarget | undefined; } & ([ AsTarget ] extends [string | AnyComponent] ? PolymorphicComponentProps<R, BaseProps, AsTarget, ForwardedAsTarget> & { as: AsTarget; } : unknown) & ([AsTarget] extends [string | AnyComponent] ? unknown : [ForwardedAsTarget] extends [string | AnyComponent] ? PolymorphicComponentProps<R, BaseProps, void, ForwardedAsTarget> & { forwardedAs: ForwardedAsTarget; } : NoInfer<FastOmit<BaseProps, keyof ExecutionProps>> & ThemedExecutionProps); /** * This type forms the signature for a forwardRef-enabled component * that accepts the "as" prop to dynamically change the underlying * rendered JSX. The interface will automatically attempt to extract * props from the given rendering target to get proper typing for * any specialized props in the target component. */ export interface PolymorphicComponent<out R extends Runtime, in out BaseProps extends BaseObject> extends React.ForwardRefExoticComponent<FastOmit<BaseProps, 'as' | 'forwardedAs'> & { as?: StyledTarget<R> | undefined; forwardedAs?: StyledTarget<R> | undefined; }> { <AsTarget extends StyledTarget<R> | (BaseProps extends { as?: infer A; } ? A : never) | void = void, ForwardedAsTarget extends StyledTarget<R> | void = void>(props: PolymorphicCallProps<R, BaseProps, AsTarget, ForwardedAsTarget>): React.JSX.Element; } /** * Some wrapped targets can't be statically introspected and their props * collapse to `{}` -- most notably polymorphic-factory components (e.g. Mantine * v7's `Button`, `Card`, `Menu.Item`), whose generic callable signature defeats * `React.ComponentPropsWithRef`. A closed `{}` would reject every prop at the JSX * call site, including `children`. Falling back to a permissive prop bag keeps * these components usable; targets with introspectable props are unchanged. * * Applied only to the JSX call surface (`PolymorphicComponent`), never to the * statics (`IStyledStatics`, `defaultProps`), so internal code keeps the real * `Props` and the widening can't leak past the call site. * * The test distributes over `Props` first: `keyof` on a union intersects each * member's keys, so a union of disjoint shapes has `keyof` of `never` while * being perfectly introspectable. Checking each member alone avoids widening it. */ export type WidenUntypedProps<Props extends BaseObject> = WidenForUntypedTarget<Props, Props>; /** * Widens because the *target* is un-introspectable, even when the component * declares props of its own. * * `Target` must be the target's props, never a bag the component's own props * were merged into. Pass the latter and the test degrades: adding one transient * prop makes `keyof` non-`never`, the widening switches off, and the target's * own props including `children` start being rejected. That is #5756, and every * call site here passes `TargetProps<R, Target>` for that reason. * * Applying it to an already-widened `Target` is a no-op, since the index * signature makes `keyof` be `string`. */ export type WidenForUntypedTarget<Target extends BaseObject, Props extends BaseObject> = (Target extends unknown ? (keyof Target extends never ? true : false) : never) extends true ? Props & { [key: string]: unknown; } : Props; export interface IStyledComponentBase<out R extends Runtime, in out Props extends BaseObject = BaseObject> extends PolymorphicComponent<R, WidenUntypedProps<Props>>, IStyledStatics<R, Props>, StyledComponentBrand { defaultProps?: (ExecutionProps & Partial<Props>) | undefined; toString: () => string; } /** * Intersected with `string` so styled components can be used as computed * property keys in object styles: `{ [MyComponent]: { ... } }`. * The conditional `R extends 'web' ? string : {}` was removed to avoid * a type alias with a conditional - type aliases require full structural * comparison on every use, while this unconditional intersection is cheaper. */ export type IStyledComponent<R extends Runtime, Props extends BaseObject = BaseObject> = IStyledComponentBase<R, Props> & string; export interface IStyledComponentFactory<out R extends Runtime, in Target extends StyledTarget<R>, in out OuterProps extends BaseObject, out OuterStatics extends BaseObject = BaseObject> { <Props extends BaseObject = BaseObject, Statics extends BaseObject = BaseObject>(target: Target, options: StyledOptions<R, OuterProps & Props>, rules: RuleSet<OuterProps & Props>): IStyledComponent<R, Substitute<OuterProps, Props>> & OuterStatics & Statics; } export interface IInlineStyleConstructor<Props extends BaseObject> { new (rules: RuleSet<Props>): IInlineStyle<Props>; } export interface IInlineStyle<Props extends BaseObject> { rules: RuleSet<Props>; generateStyleObject(executionContext: ExecutionContext & Props): object; } export type CSSProperties = CSS.Properties<number | (string & {})>; export type CSSPropertiesWithVars = CSSProperties & { [key: `--${string}`]: string | number | undefined; }; /** * A `style` type that accepts exactly the fields given and nothing else. * * A declared `style` normally narrows the fields it names and leaves the rest of * CSS available, which is what you want when constraining one or two properties: * * ```tsx * // `width` must be a number; `color` and custom properties still work * const Box = styled.div<{ style?: { width: number } }>``; * ``` * * Wrap the declaration in `CustomStyle` when the point is to forbid everything * else, rather than writing `color?: never` for every property by hand: * * ```tsx * // `width` is the only accepted style field * const Box = styled.div<{ style?: CustomStyle<{ width: number }> }>``; * ``` */ export type CustomStyle<T extends object> = T & { [K in Exclude<keyof CSSPropertiesWithVars, keyof T>]?: never; }; /** * Widens a target's `style` prop so CSS custom properties are accepted, and the * taken branch of {@link OverrideStyle}. Keep it named: a conditional alias * loses its name once it resolves, so an inline branch prints its whole * expansion in every hover and error. * * `(P['style'] & {})` is load-bearing under `exactOptionalPropertyTypes` -- it * filters `undefined` out so the `?:` stays the sole optional source -- and the * explicit `| undefined` then restores `style={undefined}`. */ type WithCSSVars<P extends BaseObject> = Omit<P, 'style'> & { style?: CSSPropertiesWithVars | (P[keyof P & 'style'] & {}) | undefined; }; /** * Applies the `style` widening to a target that may or may not declare `style`. * * Applied once per target in {@link TargetProps}, never to a merged prop bag at * a JSX call site. It runs before a component's own props, which then merge over * it via {@link MergeProps} rather than replacing it. * * The test is `'style' extends keyof P`, not `P extends { style?: infer S }`: * the latter is vacuously satisfied by `{}`, which would hand a `style` key to * targets that expose no props at all and defeat `WidenUntypedProps` (#5756). * Only {@link ComponentTargetProps} needs the guard; every intrinsic element * declares `style`, so {@link IntrinsicProps} applies `WithCSSVars` directly. */ type OverrideStyle<P extends BaseObject> = 'style' extends keyof P ? WithCSSVars<P> : P; export type CSSPseudos = { [K in CSS.Pseudos]?: CSSObject; }; export type CSSKeyframes = object & { [key: string]: CSSObject; }; export type CSSObject<Props extends BaseObject = BaseObject> = StyledObject<Props>; export interface StyledObject<Props extends BaseObject = BaseObject> extends CSSProperties, CSSPseudos { [key: string]: StyledObject<Props> | string | number | StyleFunction<Props> | RuleSet<any> | undefined; } /** * The `css` prop is not declared by default in the types as it would cause `css` to be present * on the types of anything that uses styled-components indirectly, even if they do not use the * babel plugin. * * To enable support for the `css` prop in TypeScript, create a `styled-components.d.ts` file in * your project source with the following contents: * * ```ts * import type { CSSProp } from "styled-components"; * * declare module "react" { * interface Attributes { * css?: CSSProp; * } * } * ``` * * In order to get accurate typings for `props.theme` in `css` interpolations, see * {@link DefaultTheme}. */ export type CSSProp = Interpolation<any>; export type { NoInfer } from './utils/noInfer'; /** The taken branch of {@link Substitute}. Named so it survives into hovers and * error messages; see {@link WithCSSVars} for why an inline branch does not. */ export type Substituted<A extends BaseObject, B> = FastOmit<A, keyof B> & B; export type Substitute<A extends BaseObject, B> = keyof B extends never ? A : Substituted<A, B>; /** * A component's own props over its target's props, with `style` merged rather * than replaced, so `styled.div<{ style?: { width: number } }>` constrains * `width` and leaves the rest of CSS accepted. A field declared `never` is * removed; {@link CustomStyle} removes everything a declaration omits. * * Under `exactOptionalPropertyTypes` the intersection leaves no `undefined` arm, * so such a component rejects an explicit `style={undefined}`; declare * `style?: X | undefined` to allow it. Omitting the prop is unaffected. * * Both conditional spellings of this were measured and rejected, one of them * fatal. Keep it an intersection; see AGENTS.md before changing the shape. */ export type MergeProps<A extends BaseObject, B> = keyof B extends never ? A : Merged<A, B>; /** The taken branch of {@link MergeProps}, named so hovers print a name rather * than the expansion. Keep it named; see {@link Substituted}. */ export type Merged<A extends BaseObject, B> = FastOmit<A, Exclude<keyof B, 'style'>> & B; /** * Makes keys in K optional while keeping all others required. * Used to make attrs-provided props optional on the final component. * * The guard is `[K] extends [never]`, not `keyof K extends never`. `K` is the set * of attrs-provided keys and is `never` for any component without `.attrs()`, * which is most of them, but `keyof never` is `string | number | symbol`, so the * old spelling never short-circuited. Every such component paid an omit plus a * `Partial<Pick<...>>` that removed and re-added nothing, and carried both in its * displayed type. */ export type MakeAttrsOptional<P extends BaseObject, K extends keyof any> = [K] extends [never] ? P : FastOmit<P, K & keyof P> & Partial<Pick<P, K & keyof P>>; export type InsertionTarget = HTMLElement | ShadowRoot;