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

Svelte 5: List of children #11566

Open
ThorFjelldalen opened this issue May 12, 2024 · 7 comments
Open

Svelte 5: List of children #11566

ThorFjelldalen opened this issue May 12, 2024 · 7 comments

Comments

@ThorFjelldalen
Copy link

ThorFjelldalen commented May 12, 2024

Describe the problem

In Svelte 5 we can pass a children snippet to a component, as the default "slot". The way this makes writing a component feels intuitive, as what you write inside the component is what gets rendered when calling render children() in the template.

But what is a little less intuitive here, is that the children is actually one block of template, ie. one Snippet, which might be several elements, or one element, not really a list of children.

I want to create a List component with a list of children, where each child gets wrapped with content defined in the List component. This is equivalent to the FancyList examples, but I have always found those examples to be incredibly unintuitive, coming from Flutter which has really modular component capabilities.

Today we can do the following in Svelte 5:

List component:

<script lang="ts">
	import type { Snippet } from 'svelte'

	let {
		children = [],
		activeIndex = $bindable(0)
	}: {
		children: Snippet[]
		activeIndex?: number
	} = $props()
</script>

<!-- The list wrapping -->
<div>
	{#each children as child, index}
		<!-- Each child wrapping -->
		<button onclick={() => (activeIndex = index)}>
			{@render child()}
		</button>
	{/each}
</div>

Using it:

{#snippet optA()}
	<span>A</span>
{/snippet}

{#snippet optB()}
	<span>B</span>
{/snippet}

{#snippet optC()}
	<span>C</span>
{/snippet}
<MyList children={[optA, optB, optC]} bind:activeIndex />

I love that this is possible with the new Snippet approach, but I'm not sure I would immediately remember how to do it the second time. It doesn't "roll off the tongue", like other Svelte syntax may do.

Describe the proposed solution

At the very least, I would like to be able to do this:

<MyList children={[optA, optB, optC]} bind:activeIndex>
	{#snippet optA()}
		<span>A</span>
	{/snippet}

	{#snippet optB()}
		<span>B</span>
	{/snippet}

	{#snippet optC()}
		<span>C</span>
	{/snippet}
</MyList>

But then we get errors as the children are not assigned before they are used.

But in a perfect world I would like to just do this:

<MyList bind:activeIndex>
	<span>A</span>
	<span>B</span>
	<span>C</span>
</MyList>

With some way of telling Svelte that the default children Snippet, is in fact a list of children.

Importance

would make my life easier

@adiguba
Copy link
Contributor

adiguba commented May 12, 2024

Hello,

For the first solution, you can use the restProps to detect snippets :

<MyList bind:activeIndex>
	{#snippet optA()}
		<span>A</span>
	{/snippet}

	{#snippet optB()}
		<span>B</span>
	{/snippet}

	{#snippet optC()}
		<span>C</span>
	{/snippet}
</MyList>

Example here : REPL

For the second solution it may be difficult with Svelte.
It's "doable" by manipulating the DOM, but it will break the reactivity of the component...

Something similar can be possible using sub-components :

<MyList bind:activeIndex>
	<MyItem>A</MyItem>
	<MyItem>B</MyItem>
	<MyItem>C</MyItem>
</MyList>

Example : REPL

But there is no way to check if it's used correctly, and order may not be respected some {#if} or {#each} are used...

@ThorFjelldalen
Copy link
Author

ThorFjelldalen commented May 12, 2024

Thank you! I like the first approach, and will probably use it in the meantime.

But I still think it feels too much of a workaround, and would rather wish for lists of dynamic content to be first class citizens of svelte. They almost are, it's just that the children snippet isn't interpreted as a list of children, and doing so requires special handling.

@Rich-Harris
Copy link
Member

Leaving aside the feature request itself (not really sure how such a thing could work, need to think on it), can you provide a repro for this bit?

we get errors as the children are not assigned before they are used

It seems to work okay?

@adiguba
Copy link
Contributor

adiguba commented May 13, 2024

Leaving aside the feature request itself (not really sure how such a thing could work, need to think on it),

Just a few simple ideas to create a link between the components.

=> On the child component, we should be able to access the parent component, using a rune or a specific function.

<script>
    // return the direct parent component (as if we had used bind:this),
    // (maybe null if this component is inside a HTML node ???)
    const parent = $parent();
</script>

=> On the parent component, we should be able to access all its child components (direct only?).
Perhaps via an improvement of @render, which would allow us to bind the child components into an array.

<script>
    let {
        children
    } = $props();

    // a simple array, which will be populated by the child components
    const childs = $state([]);
</script>

<div>
    <!-- render children, binding component on childs -->
    {@render children() bind childs}
</div>

We could perhaps use a rune or a function for more advanced things :

    const childs = $childs(); // bind all childs

    const childs = $childs(true); // bind all childs 

    const childs = $childs(false); // bind only direct childs (root of children, without any HTML node)

    const childs = $childs(MyItem); // bind only all childs of type MyItem

    const childs = $childs(MyItem, MyComp); // bind only all childs of type MyItem or MyComp

@brunnerh
Copy link
Member

Leaving aside the feature request itself (not really sure how such a thing could work, need to think on it), can you provide a repro for this bit?

we get errors as the children are not assigned before they are used

It seems to work okay?

Possibly just a language tools thing? It shows errors like:

Cannot find name 'optA'. ts(2304)

@ThorFjelldalen
Copy link
Author

Sorry! @Rich-Harris That does seem to work without errors during runtime!

The errors occur in development, like @brunnerh mentioned, one where passing the snippets to the children argument:
image

And one where declaring the snippets:

image

@dummdidumm
Copy link
Member

dummdidumm commented May 13, 2024

I'd argue the error is correct. Looking at the compiled output, optA and optB are passed as properties to MyList, because that's how the snippet behavior is specified. In this case this isn't what's the desired behavior though, and so I argue while it works at runtime in this case it shouldn't be relied upon and the language tools error is sound.

@ThorFjelldalen ThorFjelldalen changed the title Svelte 5 List of children Svelte 5: List of children May 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants