language-tools
language-tools copied to clipboard
Slot props not given TypeScript types when nested in <svelte:self>
Describe the bug
Normally, when I pass a typed prop to a slot, components passing in an element to that slot can access a typed copy of that prop from the parent.
Example:
<!-- App.svelte -->
<script lang="ts">
import Doubler from './Doubler.svelte';
</script>
<Doubler value={25}>
<!-- vvvvvvv this has the type of `number` -->
<strong slot="myslot" let:doubled>{doubled}</strong>
</Doubler>
<!-- Doubler.svelte -->
<script lang="ts">
export let value: number;
$: doubled = value * 2;
</script>
<strong>
<slot name="myslot" {doubled} />
</strong>
But I noticed one case where the slot props do not get types as expected. I was creating a component to represent tree structures that allows for custom rendering of each node using slots (see my reproduction repo for the slimmed-down code). It uses <svelte:self> to render deeper levels of the tree recursively (very similar to the tutorial on svelte:self, but allowing for custom rendering of the node). In that case, any slots placed inside the <svelte:self> do not retain the types of the props, and end up as any.
Reproduction
Here's a repo from a Skeleton project sveltekit build with the issue demonstrated:
https://github.com/fritz-c/svelte-nested-slot-type-issue
Here is the commit with the changes I made isolated:
https://github.com/fritz-c/svelte-nested-slot-type-issue/commit/906dde5f57cd066aeb3db90bcc075eef4d1bc6cf
Logs
No response
System Info
System:
OS: macOS 13.1
CPU: (8) x64 Intel(R) Core(TM) i7-1068NG7 CPU @ 2.30GHz
Memory: 2.81 GB / 32.00 GB
Shell: 5.8.1 - /bin/zsh
Binaries:
Node: 16.16.0 - ~/.volta/tools/image/node/16.16.0/bin/node
Yarn: 1.22.11 - ~/.volta/tools/image/yarn/1.22.11/bin/yarn
npm: 8.11.0 - ~/.volta/tools/image/node/16.16.0/bin/npm
Browsers:
Chrome: 109.0.5414.87
Firefox: 106.0.5
Safari: 16.2
npmPackages:
svelte: ^3.54.0 => 3.55.1
Severity
annoyance
I stumbled upon this myself. I'll explain my case.
Recursive Component Example
The following code works, even with the commented lines uncommented.
<script lang="ts" context="module">
export type NodeItem<T extends NodeItem<T>> = {
id: number;
text: string;
nodes?: T[];
}
</script>
<script lang="ts" generics="TItem extends NodeItem<TItem>">
export let nodes: TItem[];
$: console.log('Nodes: %o', nodes);
$: console.log('Slots: %o', $$slots);
</script>
<ul>
{#each nodes as node (node.id)}
<li>
<slot {node}>
<span>{node.text}</span>
</slot>
{#if (node?.nodes?.length ?? 0) > 0}
<svelte:self nodes={node.nodes} let:node={subNode}>
<!-- <slot node={subNode}> -->
<span>{subNode.text}</span>
<!-- </slot> -->
</svelte:self>
{/if}
</li>
{/each}
</ul>
This (with the commented lines still commented), produces the following type for the component, as per VS Code's tooltip:
function render<TItem extends NodeItem<TItem>>(): {
props: {
nodes: TItem[];
};
slots: {
default: {
node: TItem;
};
};
events: {};
}
Note how the node variable is correctly inferred to be of type TItem.
Now, if we uncomment the commented lines so the slotted content is passed down the recursion, the type changes to any:
function render<TItem extends NodeItem<TItem>>(): {
props: {
nodes: TItem[];
};
slots: {
default: {
node: any;
};
};
events: {};
}
This version still works properly, and I even get the desired result: The slot content specified by the consumer of the component is correctly used down in the recursion.
If I modify the component to provide separate slots (one for the root element and one for the recursion part), then the node variable regains its correct type of TItem, and the new slot's node variable is of type any. So I guess this hints to the fact that the problem is only inside <svelte:self>, as the OP of this issue originally stated.
Desired Result
My desired result would be to not lose the correct data type when <svelte:self> is used.
I'll continue my research, as my ultimate goal is to create specialized variations of this base component, and I suspect it might reveal more problems.
Thank you, team, for your efforts making this amazing framework possible.