Skip to content

Commit

Permalink
revert "fix: improve types for .attrs() (#4288)"
Browse files Browse the repository at this point in the history
This reverts commit 028628b.
  • Loading branch information
quantizor committed May 7, 2024
1 parent f688bef commit 1a4d47e
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 137 deletions.
119 changes: 46 additions & 73 deletions packages/styled-components/src/constructors/constructWithOptions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Attrs,
BaseObject,
ExecutionProps,
Interpolation,
IStyledComponent,
IStyledComponentFactory,
Expand All @@ -25,25 +26,30 @@ type AttrsResult<T extends Attrs<any>> = T extends (...args: any) => infer P
: never;

/**
* Extract non-optional fields from given object type.
* Based on Attrs being a simple object or function that returns
* a prop object, inspect the attrs result and attempt to extract
* any "as" prop usage to modify the runtime target.
*/
type RequiredFields<T, Ex> = Pick<
T,
{
[K in keyof T]-?: undefined extends T[K] ? never : K;
}[Exclude<keyof T, Ex>]
>;
type AttrsTarget<
R extends Runtime,
T extends Attrs<any>,
FallbackTarget extends StyledTarget<R>,
Result extends ExecutionProps = AttrsResult<T>,
> = Result extends { as: infer RuntimeTarget }
? RuntimeTarget extends KnownTarget
? RuntimeTarget
: FallbackTarget
: FallbackTarget;

export interface Styled<
R extends Runtime,
Target extends StyledTarget<R>,
OuterProps extends object,
OuterStatics extends object = BaseObject,
InnerProps extends object = OuterProps,
> {
<Props extends object = BaseObject, Statics extends object = BaseObject>(
initialStyles: Styles<Substitute<InnerProps, NoInfer<Props>>>,
...interpolations: Interpolation<Substitute<InnerProps, NoInfer<Props>>>[]
initialStyles: Styles<Substitute<OuterProps, NoInfer<Props>>>,
...interpolations: Interpolation<Substitute<OuterProps, NoInfer<Props>>>[]
): IStyledComponent<R, Substitute<OuterProps, Props>> &
OuterStatics &
Statics &
Expand All @@ -57,71 +63,36 @@ export interface Styled<
Props extends object = BaseObject,
PrivateMergedProps extends object = Substitute<OuterProps, Props>,
PrivateAttrsArg extends Attrs<PrivateMergedProps> = Attrs<PrivateMergedProps>,
PrivateResolvedTarget extends StyledTarget<R> = AttrsTarget<R, PrivateAttrsArg, Target>,
>(
attrs: PrivateAttrsArg
) => StyledAttrsResult<
) => Styled<
R,
Target,
OuterProps,
OuterStatics,
InnerProps,
Props,
PrivateMergedProps,
PrivateAttrsArg
PrivateResolvedTarget,
PrivateResolvedTarget extends KnownTarget
? Substitute<
Substitute<OuterProps, React.ComponentPropsWithRef<PrivateResolvedTarget>>,
Props
>
: PrivateMergedProps,
OuterStatics
>;

withConfig: (config: StyledOptions<R, OuterProps>) => Styled<R, Target, OuterProps, OuterStatics>;
}

type StyledAttrsResult<
R extends Runtime,
Target extends StyledTarget<R>,
OuterProps extends object,
OuterStatics extends object = BaseObject,
InnerProps extends object = OuterProps,
Props extends object = BaseObject,
PrivateMergedProps extends object = Substitute<OuterProps, Props>,
PrivateAttrsArg extends Attrs<PrivateMergedProps> = Attrs<PrivateMergedProps>,
> = (
AttrsResult<PrivateAttrsArg> extends { as: infer RuntimeTarget extends KnownTarget }
? {
Target: RuntimeTarget;
TargetProps: Substitute<OuterProps, React.ComponentPropsWithRef<RuntimeTarget>>;
}
: { Target: Target; TargetProps: OuterProps }
) extends {
Target: infer PrivateResolvedTarget extends StyledTarget<R>;
TargetProps: infer TargetProps extends object;
}
? Styled<
R,
PrivateResolvedTarget,
PrivateResolvedTarget extends KnownTarget
? Substitute<TargetProps, Props & Partial<RequiredFields<PrivateAttrsArg, 'as'>>>
: PrivateMergedProps,
OuterStatics,
PrivateResolvedTarget extends KnownTarget
? Substitute<
Substitute<InnerProps, React.ComponentPropsWithRef<PrivateResolvedTarget>>,
Props
>
: PrivateMergedProps
>
: unknown;

export default function constructWithOptions<
R extends Runtime,
Target extends StyledTarget<R>,
OuterProps extends object = Target extends KnownTarget
? React.ComponentPropsWithRef<Target>
: BaseObject,
OuterStatics extends object = BaseObject,
InnerProps extends object = OuterProps,
>(
componentConstructor: IStyledComponentFactory<R, StyledTarget<R>, object, any>,
tag: StyledTarget<R>,
options: StyledOptions<R, OuterProps> = EMPTY_OBJECT
): Styled<R, Target, OuterProps, OuterStatics, InnerProps> {
): Styled<R, Target, OuterProps, OuterStatics> {
/**
* We trust that the tag is a valid component as long as it isn't
* falsish. Typically the tag here is a string or function (i.e.
Expand All @@ -135,13 +106,13 @@ export default function constructWithOptions<

/* This is callable directly as a template function */
const templateFunction = <Props extends object = BaseObject, Statics extends object = BaseObject>(
initialStyles: Styles<Substitute<InnerProps, Props>>,
...interpolations: Interpolation<Substitute<InnerProps, Props>>[]
initialStyles: Styles<Substitute<OuterProps, Props>>,
...interpolations: Interpolation<Substitute<OuterProps, Props>>[]
) =>
componentConstructor<Substitute<InnerProps, Props>, Statics>(
componentConstructor<Substitute<OuterProps, Props>, Statics>(
tag,
options as StyledOptions<R, Substitute<InnerProps, Props>>,
css<Substitute<InnerProps, Props>>(initialStyles, ...interpolations)
options as StyledOptions<R, Substitute<OuterProps, Props>>,
css<Substitute<OuterProps, Props>>(initialStyles, ...interpolations)
);

/**
Expand All @@ -154,22 +125,24 @@ export default function constructWithOptions<
Props extends object = BaseObject,
PrivateMergedProps extends object = Substitute<OuterProps, Props>,
PrivateAttrsArg extends Attrs<PrivateMergedProps> = Attrs<PrivateMergedProps>,
PrivateResolvedTarget extends StyledTarget<R> = AttrsTarget<R, PrivateAttrsArg, Target>,
>(
attrs: PrivateAttrsArg
): StyledAttrsResult<
R,
Target,
OuterProps,
OuterStatics,
InnerProps,
Props,
PrivateMergedProps,
PrivateAttrsArg
> =>
constructWithOptions<R, Target, any, any, any>(componentConstructor, tag, {
) =>
constructWithOptions<
R,
PrivateResolvedTarget,
PrivateResolvedTarget extends KnownTarget
? Substitute<
Substitute<OuterProps, React.ComponentPropsWithRef<PrivateResolvedTarget>>,
Props
>
: PrivateMergedProps,
OuterStatics
>(componentConstructor, tag, {
...options,
attrs: Array.prototype.concat(options.attrs, attrs).filter(Boolean),
}) as any;
});

/**
* If config methods are called, wrap up a new template function
Expand Down
64 changes: 0 additions & 64 deletions packages/styled-components/src/test/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,70 +270,6 @@ const AttrRequiredTest4 = styled(DivWithUnfulfilledRequiredProps).attrs({
waz: 42,
})``;

{
const DivWithRequiredFooBar = styled.div<{ foo: number; bar: string }>``;
// @ts-expect-error must provide both foo and bar
<DivWithRequiredFooBar />;
// @ts-expect-error must provide both foo and bar
<DivWithRequiredFooBar foo={3} />;
// @ts-expect-error must provide both foo and bar
<DivWithRequiredFooBar bar="3" />;
// OK
<DivWithRequiredFooBar foo={3} bar="3" />;

// foo is provided, so it becomes optional
const DivWithRequiredBar = styled(DivWithRequiredFooBar).attrs({ foo: 42 })`
margin; ${props => props.foo * 10}px;
`;
// @ts-expect-error must provide bar
<DivWithRequiredBar />;
// OK
<DivWithRequiredBar bar="3" />;
// OK. Can still provide foo if we want
<DivWithRequiredBar foo={3} bar="3" />;
// @ts-expect-error foo must be a number
<DivWithRequiredBar foo="3" bar="3" />;

const Div = styled(DivWithRequiredBar).attrs({ bar: '42' })`
margin: ${props => {
// @ts-expect-error foo is optional
const foo: number = props.foo;
const bar: string = props.bar;
return foo * Number(bar);
}}px;
`;
// OK
<Div />;
<Div foo={3} />;
<Div bar="3" />;
<Div foo={3} bar="3" />;
}

{
// double attrs
const DivWithRequiredFooBar = styled.div<{ foo: number; bar: number }>``;
const Div = styled(DivWithRequiredFooBar).attrs({ foo: 42 }).attrs({ bar: 42 })`
margin: ${props => props.foo * props.bar}px;
`;

<Div />;
<Div foo={3} />;
<Div bar={3} />;
<Div foo={3} bar={3} />;
}

{
// double overriding
const Div = styled.div``;
const H1 = styled(Div).attrs({ as: 'h1' })``;
const Label = styled(H1).attrs({ as: 'label' })``;

<Label
ref={(el: HTMLLabelElement | null) => {}}
onCopy={(e: React.ClipboardEvent<HTMLLabelElement>) => {}}
/>;
}

/** Intrinsic props and ref are being incorrectly types when using `as`
* https://github.com/styled-components/styled-components/issues/3800#issuecomment-1548941843
*/
Expand Down

0 comments on commit 1a4d47e

Please sign in to comment.