withastro / astro

The web framework for content-driven websites. ⭐️ Star to support our work!
https://astro.build
Other
47.06k stars 2.5k forks source link

Typescript error with dynamic tag #10277

Closed silveltman closed 8 months ago

silveltman commented 9 months ago

Astro Info

Astro                    v4.4.4
Node                     v18.17.1
System                   macOS (arm64)
Package Manager          yarn
Output                   static
Adapter                  none
Integrations             unocss
                         @astrojs/sitemap
                         astro-robots-txt
                         fullui-integration

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

na

Describe the Bug

I have this component:

---
import type { Polymorphic } from 'astro/types'

interface Base
  extends Polymorphic<{ as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' }> {}

type Props = Base & {}

const { as: Tag = 'h2', ...rest } = Astro.props
---

<Tag {...rest} />

Typescript error:

Type '{ class?: string | null | undefined; 'class:list'?: string | Record<string, boolean> | Record<any, any> | Iterable<string> | Iterable<any> | undefined; ... 188 more ...; onfullscreenerror?: string | ... 1 more ... | undefined; }' is not assignable to type 'IntrinsicAttributes & HTMLAttributes'.
  Type '{ class?: string | null | undefined; 'class:list'?: string | Record<string, boolean> | Record<any, any> | Iterable<string> | Iterable<any> | undefined; ... 188 more ...; onfullscreenerror?: string | ... 1 more ... | undefined; }' is not assignable to type 'IntrinsicAttributes'.ts(2322)
const Tag: NonNullable<"h1" | "h2" | "h3" | "h4" | "h5" | "h6">

Changing the line to type Props = Base will remove the error.

Also, chaing the interface to Omit slot will remvoe the error:

interface Base
  extends Omit<
    Polymorphic<{ as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' }>,
    'slot'
  > {}

Or this:

interface Base
  extends Polymorphic<{ as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' }> {
  title?: string
  slot: any
}

Or even slot: string

What's the expected result?

I expect it not to give a ts error

Link to Minimal Reproducible Example

https://stackblitz.com/edit/github-8wxgcg?file=src%2Fcomponents%2FComponent.astro

Participation

lilnasy commented 9 months ago

The Polymorphic type establishes a relationship between one property ("as") and the rest. When creating intersection, that relationship is lost in the resulting type.

Is there a case where type Props = Base & {} is necessary?

silveltman commented 9 months ago

The example I gave was a simplified version, my actual component looks more like this:

---
import type { Polymorphic } from 'astro/types'
import {  buildProps, type BuildProps } from '../../utils'

interface Base
  extends Polymorphic<{ as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' }> {
  title?: string
}

type Props = BuildProps<Base, 'title'>
const props = buildProps(Astro, 'title')

const { title, as: Tag = 'h2', class: className, ...rest } = props
---

  <Tag
    class:list={['title', className]}
    {...rest}
  >
    <Fragment set:html={title} />
  </Tag>

I do some modifications to my base Props which I do in every component. For your info, the BuildProps turn the above Base interface into the following:

type Props = Omit<Base, "slot" | "title" | "_title"> & {
    title?: string | Base | undefined;
    _title?: string | Base | undefined;
}

Then the buildProps function turns it back, allowing me to pass in all props as an object to a key with a specified name. Reason for this is that it allows me have a lot of control over my component from within my content collections. I could for example do the following:


---
title:
_title:
  size: large
---
lilnasy commented 8 months ago

I think this issue comes down to typescript works, not astro's use of it.

You should be able to maintain the relationship between the tag name in "as" and the rest of the props by extending the interface instead.

-type Props = Base & {}
+interface Props extends Base {}
-type Props = BuildProps<Base, 'title'>
+interface Props extends BuildProps<Base, 'title'> {}
lilnasy commented 8 months ago

Closing as this comes down to typescript limitations. Feel free to reopen if you think that is not the case.