Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't forward concrete HTMLAttributes when using Polymorphic component #10912

Closed
1 task
iivvaannxx opened this issue Apr 30, 2024 · 2 comments
Closed
1 task
Labels
needs triage Issue needs to be triaged

Comments

@iivvaannxx
Copy link

iivvaannxx commented Apr 30, 2024

Astro Info

Astro                    v4.7.0
Node                     v20.12.2
System                   Linux (x64)
Package Manager          bun
Output                   static
Adapter                  none
Integrations             @astrojs/react
                         @astrojs/tailwind
                         @qwikdev/astro
                         astro-icon

If this issue only occurs in one browser, which browser is a problem?

No response

Describe the Bug

When trying to create a Polymorphic component, I usually need to make another component on top of it that has a more concrete implementation but uses some logic or styling defined in the base polymorphic component.

For example, my current use case is quite simple, I have a polymorphic component that can be either a div or an article and this is expected to act as a simple "Card" component. It has default styles but they can be overridden (using tailwind merge). Built on top of it I have another component, let's call it ConcreteCardX which is also a card with "X" specific content. This is intended to be an article, so when I declare its props, I extend from HTMLAttributes<'article'>. Like this:

---
import Card from "./base-card.astro";

export type Props = HTMLAttributes<'article'> & { /* my more concrete props for this card. */ };
const { /* extract all the concrete props so they don't conflict with the "rest" */, ...props } = Astro.props;
---

<Card as="article" {...props}>
  <!-- Insert the concrete content -->
</Card>

I would expect this to work without any errors, but TypeScript can't assign the {...props} even when the only ones that are inside are the HTMLAttributes<'article'>. If you hover over the error you get the typical undecipherable hieroglyphic that TypeScript usually vomits. A huge (180+) union type that can't be assigned to IntrinsicAttributes. I tried understanding which parts are actually "not assignable" and the problem seems to be with AstroBuiltinAttributes although take this with a grain of salt.

I copied the error into a txt file (look below). You can go straight to the end of the error because all the attributes match except for the ones at the end.

Type '{ children: any[]; as: "article"; 'class:list'?: string | Record<string, boolean> | Record<any, any> | Iterable<string> | Iterable<any> | undefined; accesskey?: string | null | undefined; autocapitalize?: string | null | undefined; autofocus?: string | boolean | null | undefined; class?: string | null | undefined; contenteditable?: string | boolean | null | undefined; dir?: string | null | undefined; draggable?: boolean | "true" | "false" | null | undefined; enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | null | undefined; exportparts?: string | null | undefined; hidden?: string | boolean | null | undefined; id?: string | null | undefined; inert?: string | boolean | null | undefined; inputmode?: "url" | "text" | "search" | "none" | "tel" | "email" | "numeric" | "decimal" | null | undefined; is?: string | null | undefined; itemid?: string | null | undefined; itemprop?: string | null | undefined; itemref?: string | null | undefined; itemscope?: string | boolean | null | undefined; itemtype?: string | null | undefined; lang?: string | null | undefined; part?: string | null | undefined; popover?: string | boolean | null | undefined; slot?: string | null | undefined; spellcheck?: boolean | "true" | "false" | null | undefined; style?: string | CSSProperties | null | undefined; tabindex?: string | number | null | undefined; translate?: "" | "yes" | "no" | null | undefined; radiogroup?: string | null | undefined; role?: AriaRole | null | undefined; about?: string | null | undefined; datatype?: string | null | undefined; inlist?: any; prefix?: string | null | undefined; property?: string | null | undefined; resource?: string | null | undefined; typeof?: string | null | undefined; vocab?: string | null | undefined; contextmenu?: string | null | undefined; autosave?: string | null | undefined; color?: string | null | undefined; results?: string | number | null | undefined; security?: string | null | undefined; unselectable?: "on" | "off" | null | undefined; 'aria-activedescendant'?: string | null | undefined; 'aria-atomic'?: boolean | "true" | "false" | null | undefined; 'aria-autocomplete'?: "none" | "list" | "inline" | "both" | null | undefined; 'aria-busy'?: boolean | "true" | "false" | null | undefined; 'aria-checked'?: boolean | "true" | "false" | "mixed" | null | undefined; 'aria-colcount'?: string | number | null | undefined; 'aria-colindex'?: string | number | null | undefined; 'aria-colspan'?: string | number | null | undefined; 'aria-controls'?: string | null | undefined; 'aria-current'?: boolean | "time" | "true" | "false" | "page" | "step" | "location" | "date" | null | undefined; 'aria-describedby'?: string | null | undefined; 'aria-details'?: string | null | undefined; 'aria-disabled'?: boolean | "true" | "false" | null | undefined; 'aria-dropeffect'?: "link" | "none" | "copy" | "execute" | "move" | "popup" | null | undefined; 'aria-errormessage'?: string | null | undefined; 'aria-expanded'?: boolean | "true" | "false" | null | undefined; 'aria-flowto'?: string | null | undefined; 'aria-grabbed'?: boolean | "true" | "false" | null | undefined; 'aria-haspopup'?: boolean | "dialog" | "menu" | "true" | "false" | "grid" | "listbox" | "tree" | null | undefined; 'aria-hidden'?: boolean | "true" | "false" | null | undefined; 'aria-invalid'?: boolean | "true" | "false" | "grammar" | "spelling" | null | undefined; 'aria-keyshortcuts'?: string | null | undefined; 'aria-label'?: string | null | undefined; 'aria-labelledby'?: string | null | undefined; 'aria-level'?: string | number | null | undefined; 'aria-live'?: "off" | "assertive" | "polite" | null | undefined; 'aria-modal'?: boolean | "true" | "false" | null | undefined; 'aria-multiline'?: boolean | "true" | "false" | null | undefined; 'aria-multiselectable'?: boolean | "true" | "false" | null | undefined; 'aria-orientation'?: "horizontal" | "vertical" | null | undefined; 'aria-owns'?: string | null | undefined; 'aria-placeholder'?: string | null | undefined; 'aria-posinset'?: string | number | null | undefined; 'aria-pressed'?: boolean | "true" | "false" | "mixed" | null | undefined; 'aria-readonly'?: boolean | "true" | "false" | null | undefined; 'aria-relevant'?: "text" | "additions" | "additions removals" | "additions text" | "all" | "removals" | "removals additions" | "removals text" | "text additions" | "text removals" | null | undefined; 'aria-required'?: boolean | "true" | "false" | null | undefined; 'aria-roledescription'?: string | null | undefined; 'aria-rowcount'?: string | number | null | undefined; 'aria-rowindex'?: string | number | null | undefined; 'aria-rowspan'?: string | number | null | undefined; 'aria-selected'?: boolean | "true" | "false" | null | undefined; 'aria-setsize'?: string | number | null | undefined; 'aria-sort'?: "none" | "ascending" | "descending" | "other" | null | undefined; 'aria-valuemax'?: string | number | null | undefined; 'aria-valuemin'?: string | number | null | undefined; 'aria-valuenow'?: string | number | null | undefined; 'aria-valuetext'?: string | null | undefined; oncopy?: string | null | undefined; oncut?: string | null | undefined; onpaste?: string | null | undefined; oncompositionend?: string | null | undefined; oncompositionstart?: string | null | undefined; oncompositionupdate?: string | null | undefined; onfocus?: string | null | undefined; onfocusin?: string | null | undefined; onfocusout?: string | null | undefined; onblur?: string | null | undefined; onchange?: string | null | undefined; oninput?: string | null | undefined; onreset?: string | null | undefined; onsubmit?: string | null | undefined; oninvalid?: string | null | undefined; onbeforeinput?: string | null | undefined; onload?: string | null | undefined; onerror?: string | null | undefined; ontoggle?: string | null | undefined; onkeydown?: string | null | undefined; onkeypress?: string | null | undefined; onkeyup?: string | null | undefined; onabort?: string | null | undefined; oncanplay?: string | null | undefined; oncanplaythrough?: string | null | undefined; oncuechange?: string | null | undefined; ondurationchange?: string | null | undefined; onemptied?: string | null | undefined; onencrypted?: string | null | undefined; onended?: string | null | undefined; onloadeddata?: string | null | undefined; onloadedmetadata?: string | null | undefined; onloadstart?: string | null | undefined; onpause?: string | null | undefined; onplay?: string | null | undefined; onplaying?: string | null | undefined; onprogress?: string | null | undefined; onratechange?: string | null | undefined; onseeked?: string | null | undefined; onseeking?: string | null | undefined; onstalled?: string | null | undefined; onsuspend?: string | null | undefined; ontimeupdate?: string | null | undefined; onvolumechange?: string | null | undefined; onwaiting?: string | null | undefined; onauxclick?: string | null | undefined; onclick?: string | null | undefined; oncontextmenu?: string | null | undefined; ondblclick?: string | null | undefined; ondrag?: string | null | undefined; ondragend?: string | null | undefined; ondragenter?: string | null | undefined; ondragexit?: string | null | undefined; ondragleave?: string | null | undefined; ondragover?: string | null | undefined; ondragstart?: string | null | undefined; ondrop?: string | null | undefined; onmousedown?: string | null | undefined; onmouseenter?: string | null | undefined; onmouseleave?: string | null | undefined; onmousemove?: string | null | undefined; onmouseout?: string | null | undefined; onmouseover?: string | null | undefined; onmouseup?: string | null | undefined; onselect?: string | null | undefined; onselectionchange?: string | null | undefined; onselectstart?: string | null | undefined; ontouchcancel?: string | null | undefined; ontouchend?: string | null | undefined; ontouchmove?: string | null | undefined; ontouchstart?: string | null | undefined; ongotpointercapture?: string | null | undefined; onpointercancel?: string | null | undefined; onpointerdown?: string | null | undefined; onpointerenter?: string | null | undefined; onpointerleave?: string | null | undefined; onpointermove?: string | null | undefined; onpointerout?: string | null | undefined; onpointerover?: string | null | undefined; onpointerup?: string | null | undefined; onlostpointercapture?: string | null | undefined; onscroll?: string | null | undefined; onresize?: string | null | undefined; onwheel?: string | null | undefined; onanimationstart?: string | null | undefined; onanimationend?: string | null | undefined; onanimationiteration?: string | null | undefined; ontransitionstart?: string | null | undefined; ontransitionrun?: string | null | undefined; ontransitionend?: string | null | undefined; ontransitioncancel?: string | null | undefined; onmessage?: string | null | undefined; onmessageerror?: string | null | undefined; oncancel?: string | null | undefined; onclose?: string | null | undefined; onfullscreenchange?: string | null | undefined; onfullscreenerror?: string | null | undefined; }' is not assignable to type 'IntrinsicAttributes & Omit<Omit<{ as: "article"; }, "as"> & { as: "article"; }, "as"> & { as?: "article" | undefined; } & Omit<OmitIndexSignature<HTMLAttributes>, "set:html" | "set:text" | "is:raw" | "transition:animate" | "transition:name" | "transition:persist">'.
  Type '{ children: any[]; as: "article"; 'class:list'?: string | Record<string, boolean> | Record<any, any> | Iterable<string> | Iterable<any> | undefined; accesskey?: string | null | undefined; autocapitalize?: string | null | undefined; autofocus?: string | boolean | null | undefined; class?: string | null | undefined; contenteditable?: string | boolean | null | undefined; dir?: string | null | undefined; draggable?: boolean | "true" | "false" | null | undefined; enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | null | undefined; exportparts?: string | null | undefined; hidden?: string | boolean | null | undefined; id?: string | null | undefined; inert?: string | boolean | null | undefined; inputmode?: "url" | "text" | "search" | "none" | "tel" | "email" | "numeric" | "decimal" | null | undefined; is?: string | null | undefined; itemid?: string | null | undefined; itemprop?: string | null | undefined; itemref?: string | null | undefined; itemscope?: string | boolean | null | undefined; itemtype?: string | null | undefined; lang?: string | null | undefined; part?: string | null | undefined; popover?: string | boolean | null | undefined; slot?: string | null | undefined; spellcheck?: boolean | "true" | "false" | null | undefined; style?: string | CSSProperties | null | undefined; tabindex?: string | number | null | undefined; translate?: "" | "yes" | "no" | null | undefined; radiogroup?: string | null | undefined; role?: AriaRole | null | undefined; about?: string | null | undefined; datatype?: string | null | undefined; inlist?: any; prefix?: string | null | undefined; property?: string | null | undefined; resource?: string | null | undefined; typeof?: string | null | undefined; vocab?: string | null | undefined; contextmenu?: string | null | undefined; autosave?: string | null | undefined; color?: string | null | undefined; results?: string | number | null | undefined; security?: string | null | undefined; unselectable?: "on" | "off" | null | undefined; 'aria-activedescendant'?: string | null | undefined; 'aria-atomic'?: boolean | "true" | "false" | null | undefined; 'aria-autocomplete'?: "none" | "list" | "inline" | "both" | null | undefined; 'aria-busy'?: boolean | "true" | "false" | null | undefined; 'aria-checked'?: boolean | "true" | "false" | "mixed" | null | undefined; 'aria-colcount'?: string | number | null | undefined; 'aria-colindex'?: string | number | null | undefined; 'aria-colspan'?: string | number | null | undefined; 'aria-controls'?: string | null | undefined; 'aria-current'?: boolean | "time" | "true" | "false" | "page" | "step" | "location" | "date" | null | undefined; 'aria-describedby'?: string | null | undefined; 'aria-details'?: string | null | undefined; 'aria-disabled'?: boolean | "true" | "false" | null | undefined; 'aria-dropeffect'?: "link" | "none" | "copy" | "execute" | "move" | "popup" | null | undefined; 'aria-errormessage'?: string | null | undefined; 'aria-expanded'?: boolean | "true" | "false" | null | undefined; 'aria-flowto'?: string | null | undefined; 'aria-grabbed'?: boolean | "true" | "false" | null | undefined; 'aria-haspopup'?: boolean | "dialog" | "menu" | "true" | "false" | "grid" | "listbox" | "tree" | null | undefined; 'aria-hidden'?: boolean | "true" | "false" | null | undefined; 'aria-invalid'?: boolean | "true" | "false" | "grammar" | "spelling" | null | undefined; 'aria-keyshortcuts'?: string | null | undefined; 'aria-label'?: string | null | undefined; 'aria-labelledby'?: string | null | undefined; 'aria-level'?: string | number | null | undefined; 'aria-live'?: "off" | "assertive" | "polite" | null | undefined; 'aria-modal'?: boolean | "true" | "false" | null | undefined; 'aria-multiline'?: boolean | "true" | "false" | null | undefined; 'aria-multiselectable'?: boolean | "true" | "false" | null | undefined; 'aria-orientation'?: "horizontal" | "vertical" | null | undefined; 'aria-owns'?: string | null | undefined; 'aria-placeholder'?: string | null | undefined; 'aria-posinset'?: string | number | null | undefined; 'aria-pressed'?: boolean | "true" | "false" | "mixed" | null | undefined; 'aria-readonly'?: boolean | "true" | "false" | null | undefined; 'aria-relevant'?: "text" | "additions" | "additions removals" | "additions text" | "all" | "removals" | "removals additions" | "removals text" | "text additions" | "text removals" | null | undefined; 'aria-required'?: boolean | "true" | "false" | null | undefined; 'aria-roledescription'?: string | null | undefined; 'aria-rowcount'?: string | number | null | undefined; 'aria-rowindex'?: string | number | null | undefined; 'aria-rowspan'?: string | number | null | undefined; 'aria-selected'?: boolean | "true" | "false" | null | undefined; 'aria-setsize'?: string | number | null | undefined; 'aria-sort'?: "none" | "ascending" | "descending" | "other" | null | undefined; 'aria-valuemax'?: string | number | null | undefined; 'aria-valuemin'?: string | number | null | undefined; 'aria-valuenow'?: string | number | null | undefined; 'aria-valuetext'?: string | null | undefined; oncopy?: string | null | undefined; oncut?: string | null | undefined; onpaste?: string | null | undefined; oncompositionend?: string | null | undefined; oncompositionstart?: string | null | undefined; oncompositionupdate?: string | null | undefined; onfocus?: string | null | undefined; onfocusin?: string | null | undefined; onfocusout?: string | null | undefined; onblur?: string | null | undefined; onchange?: string | null | undefined; oninput?: string | null | undefined; onreset?: string | null | undefined; onsubmit?: string | null | undefined; oninvalid?: string | null | undefined; onbeforeinput?: string | null | undefined; onload?: string | null | undefined; onerror?: string | null | undefined; ontoggle?: string | null | undefined; onkeydown?: string | null | undefined; onkeypress?: string | null | undefined; onkeyup?: string | null | undefined; onabort?: string | null | undefined; oncanplay?: string | null | undefined; oncanplaythrough?: string | null | undefined; oncuechange?: string | null | undefined; ondurationchange?: string | null | undefined; onemptied?: string | null | undefined; onencrypted?: string | null | undefined; onended?: string | null | undefined; onloadeddata?: string | null | undefined; onloadedmetadata?: string | null | undefined; onloadstart?: string | null | undefined; onpause?: string | null | undefined; onplay?: string | null | undefined; onplaying?: string | null | undefined; onprogress?: string | null | undefined; onratechange?: string | null | undefined; onseeked?: string | null | undefined; onseeking?: string | null | undefined; onstalled?: string | null | undefined; onsuspend?: string | null | undefined; ontimeupdate?: string | null | undefined; onvolumechange?: string | null | undefined; onwaiting?: string | null | undefined; onauxclick?: string | null | undefined; onclick?: string | null | undefined; oncontextmenu?: string | null | undefined; ondblclick?: string | null | undefined; ondrag?: string | null | undefined; ondragend?: string | null | undefined; ondragenter?: string | null | undefined; ondragexit?: string | null | undefined; ondragleave?: string | null | undefined; ondragover?: string | null | undefined; ondragstart?: string | null | undefined; ondrop?: string | null | undefined; onmousedown?: string | null | undefined; onmouseenter?: string | null | undefined; onmouseleave?: string | null | undefined; onmousemove?: string | null | undefined; onmouseout?: string | null | undefined; onmouseover?: string | null | undefined; onmouseup?: string | null | undefined; onselect?: string | null | undefined; onselectionchange?: string | null | undefined; onselectstart?: string | null | undefined; ontouchcancel?: string | null | undefined; ontouchend?: string | null | undefined; ontouchmove?: string | null | undefined; ontouchstart?: string | null | undefined; ongotpointercapture?: string | null | undefined; onpointercancel?: string | null | undefined; onpointerdown?: string | null | undefined; onpointerenter?: string | null | undefined; onpointerleave?: string | null | undefined; onpointermove?: string | null | undefined; onpointerout?: string | null | undefined; onpointerover?: string | null | undefined; onpointerup?: string | null | undefined; onlostpointercapture?: string | null | undefined; onscroll?: string | null | undefined; onresize?: string | null | undefined; onwheel?: string | null | undefined; onanimationstart?: string | null | undefined; onanimationend?: string | null | undefined; onanimationiteration?: string | null | undefined; ontransitionstart?: string | null | undefined; ontransitionrun?: string | null | undefined; ontransitionend?: string | null | undefined; ontransitioncancel?: string | null | undefined; onmessage?: string | null | undefined; onmessageerror?: string | null | undefined; oncancel?: string | null | undefined; onclose?: string | null | undefined; onfullscreenchange?: string | null | undefined; onfullscreenerror?: string | null | undefined; }' is not assignable to type 'IntrinsicAttributes'.ts(2322)

I am probably missing something or doing something wrong, if that's the case then forgive me. But this makes sense in my head and I don't understand why the props can't just be forwarded. It confuses me even more that according to IntrinsicElements both div and article have the same props so they shouldn't conflict in any way.

A different and probably more understandable example is linked below in the minimal reproduction. The example uses button and a tags to showcase the problem.

What's the expected result?

Should be able to forward props to polymorphic components when the {...props} given are compatible with the polymorphic tag used. This means, if {...props} is nothing else than HTMLAttributes<'a'> I should be able to pass them to a polymorphic <Link as="a" {...props} /> without TypeScript complaining.

Link to Minimal Reproducible Example

https://stackblitz.com/edit/github-yxezbu?file=src%2Fwrapper.astro

Participation

  • I am willing to submit a pull request for this issue.
@github-actions github-actions bot added the needs triage Issue needs to be triaged label Apr 30, 2024
@ArmandPhilippot
Copy link
Contributor

I think the issue is the same as #10780

You can fix the issue (for now) by declaring your Props with Omit:

export type Props = Omit<HTMLAttributes<"a">, 'slot'> & {
  content: string;
};

I tested with your reproduction and I don't see the error anymore.

@iivvaannxx
Copy link
Author

Thanks a lot, @ArmandPhilippot! That worked! I didn't see that issue before posting this one. I will close it in favor of that one. It definitely seems to be the same problem but in a different situation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs triage Issue needs to be triaged
Projects
None yet
Development

No branches or pull requests

2 participants