sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
78.79k stars 4.14k forks source link

Feat: [Types] Improves "svelte/element" with directive-free types, and a configurable children #13653

Open adiguba opened 4 hours ago

adiguba commented 4 hours ago

Describe the problem

The module "svelte/elements" provides the definitions of HTML attributes that can be used to declare props to spread in a component that "wrap" a HTML element.

For example for a component Button, using directly the type HTMLButtonAttributes

<script lang="ts">
    import type { HTMLButtonAttributes } from 'svelte/elements';

    let { children, ...rest } : HTMLButtonAttributes = $props();
</script>

<button {...rest}>
    {@render children?.()}
</button>

Or via the equivalent using SvelteHTMLElements

<script lang="ts">
    import type { SvelteHTMLElements } from 'svelte/elements';

    let { children, ...rest } : SvelteHTMLElements['button'] = $props();
</script>

<button {...rest}>
    {@render children?.()}
</button>

But there are 2 flaws :

In order to remove the bind:/on: directives, II need to write something like that :

    let { children, ...rest } : Omit<HTMLButtonAttributes, `bind:${string}` | `on:${string}`> = $props();

And if I need to specify a parameter for the children snippet, I have to write :

    let { children, ...rest } : Omit<HTMLButtonAttributes, `bind:${string}` | `on:${string}` | 'children'>
        & { children?: Snippet<[number]> } = $props();

Describe the proposed solution

It would be nice if Svelte 5 had an official type to handle this in "svelte/elements".

Something like this might work :

export type SvelteHTMLProps<TagName extends string, Parameters extends unknown[] = []> = {
    children?: import('svelte').Snippet<Parameters>;
} & Omit<SvelteHTMLElements[TagName], `bind:${string}` | `on:${string}` | `children`>;

So we can use SvelteHTMLProps<'button'>in order to define our Button component :

<script lang="ts">
    import type { SvelteHTMLProps } from 'svelte/elements';

    let { children, ...rest } : SvelteHTMLProps<'button'> = $props();
</script>

<button {...rest}>
    {@render children?.()}
</button>

Or SvelteHTMLProps<'button', [number]>to define the children parameter type :

<script lang="ts">
    import type { SvelteHTMLProps } from 'svelte/elements';

    let { children, onclick, ...rest } : SvelteHTMLProps<'button', [number]> = $props();

    let count = $state(0);

    function countClick(evt) {
        count++;
        onclick?.(evt);
    }
</script>

<button onclick={countClick} {...rest}>
    {@render children?.(count)}
</button> 

Importance

nice to have

paoloricciuti commented 4 hours ago

I wonder: there's a disadvantage in directly define those types this way? Obviously with a type argument to specify the children snippets arguments.

dummdidumm commented 3 hours ago

There's a related issue somewhere asking for snippet type being relaxed. That would solve most of these problems already. on: and bind: being present... Not sure what best to do here. As shown it's somewhat straightforward to implement a helper type in user land, so let's wait for more use cases / upvotes first

webJose commented 3 hours ago

Hello! My 2 cents is that children should not exist in the base types. Sometimes we write components that allow no children. I think the component should be explicit about the existence children by specifying it in its Props type.