sveltejs / svelte

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

Dynamically generated slots and context #3480

Closed sserdyuk closed 3 years ago

sserdyuk commented 4 years ago

Is your feature request related to a problem? Please describe. I am looking at Svelte and wondering if it would work for a project where we would have to build admin interface for a lot of data to display and manage while maintaining a large degree of reusability of visual elements. I don't see it working in Svelte3, so I am entering it here in hopes that it might drive some future decisions.

Describe the solution you'd like I'd like to be able to create a library of UI elements that would have standard baseline behavior but provide extensibility to fit all practical cases. For example, a table could provide ability to reorder columns or switch them on and off or resize them. Such behavior could be encapsulated in a custom component. At the same time the page component containing the table should be able to generate content for slots as necessary to meed business goals. I made a "make believe" version of the code at https://svelte.dev/repl/a6e3c2e49b2e43dd87a50430071463f6?version=3.9.1.

Describe alternatives you've considered I don't see any alternatives

How important is this feature to you? I think it is essential for admin interfaces and dashboards.

Additional context This is a paradigm we used on the server side in a classic MVC app we built before.

ghost commented 4 years ago

EDIT: Oh... see I thought the comments were a general "there's no component libraries out there."

My prior remark about Svelte Material UI still stands, though. See: https://sveltematerialui.com/#/demo/data-table

There's also talk on another issue about making something like $$slots available to grab slot contents in the same way that $$props lets you grab property data. Having that would open up more possibilities of creatively using slotted content.

pngwn commented 4 years ago

I don't really know what this issue is actually asking for, could you clarify what you would like to achieve so we can have a better idea of how it might work?

sserdyuk commented 4 years ago

My apologies for mumbling. It is probably a combination of $$slots and ability to pass context to the slot renderer. The first part is to be able to test if the parent defined a slot named by a certain pattern (like "name-heading" and "name-view") while iterating over a dynamic field list where "name" is one of the fields. The second part is to allow parent define slots that render content based on child's own context. For example, when a table component iterates over the rows, the slot will need to know the current row each time it is called. This may be not in line with how Svelte does things, please feel free to point me to a better way to implement these scenarios.

sserdyuk commented 4 years ago

Actually, I just found how slots can pass values to parents through props. That makes sense. So, the only part I am missing is the dynamic slot naming like in the example below.

<!-- App.svelte -->
<script> let fields = ["name","breed"]; </script>
<FancyTable {fields} {items}>
    <div slot="name-view" let:item={item}>{item.text}</div>
    <div slot="breed-view" let:item={item}>{item.otherText}</div>
    <p slot="footer">Copyright (c) 2019 Svelte Industries</p>
</FancyTable>

<!-- FancyTable.svelte -->
<ul>
    {#each items as item}
              {#each fields as field}
            <li class="fancy">
                <slot name="{field}-view" item={item}></slot>
            </li>
              {/each}
    {/each}
</ul>

<slot name="footer"></slot>
Demysdcl commented 4 years ago

I have that component in Vue and I can't create it in Svelte, because doesn't have dynamic slots

<template>
  <section>
    <ul class="tabs">
      <li
        class="tab-header"
        v-for="(tab, idx) in tabs"
        :key="`tab-${idx}`"
        @click="tabSelected = idx"
      >
        <slot :name="`header-${idx}`">{tab}</slot>
      </li>
    </ul>
    <div class="tab-content">
      <div
        v-for="(tab, idx) in tabs"
        :key="`content-${idx}`"
        class="tab-internal"
      >
        <slot v-if="idx === tabSelected" :name="`tab-${idx}`">
          Implement tab-{{ idx }}
        </slot>
      </div>
    </div>
  </section>
</template>

<script>
  export default {
    props: {
      tabs: Array
    },
    data: () => ({
      tabSelected: 0
    })
  };
</script>
rodhoward commented 4 years ago

I think a great example of this is a simple Tabs component. For each tab I want to dynamically open a named slot.

<script>
  export let tabs; // array of strings.
  export let currentTab = tabs[0];
</script>

<div class="tabs">
  <ul>
    {#each tabs as tab}
      <li class:is-active={tab === currentTab}>
        <a on:click={() => (currentTab = tab)}>{tab}</a>
      </li>
    {/each}
  </ul>
</div>
{#each tabs as tab}
  <slot name={tab} /> <!-- DOESN'T WORK-->
{/each}
Ciantic commented 3 years ago

It's interesting that dynamically named slots still works if you target web components.

E.g.

    {#each [1,2,3,4,5] as i}
      <div class="slide">
        <slot name={i}></slot>
      </div>
    {/each}

Svelte compiles that normally, and produces a working web component with 5 slots.

For me it would be just sufficient to suppress that error message.

Edit If you only target web-components, then there is a workaround:

    {#each [1,2,3,4,5] as i}
      <div class="slide">
        {@html `<slot name="${i}"></slot>`}
      </div>
    {/each}
antony commented 3 years ago

I think this is a question of usage rather than a limitation in Svelte.

For the scenario above, there is no reason to use slots. You can have an array of components, and then a loop, which has <svelte:component this={yourComponent} /> inside it.

Closing due to age, but also because I think this is a case of using the right tool for the job.

adzza24 commented 3 years ago

Can't believe this feature request got closed, I have only been using Svelte a short while and this already seems like a huge limitation. Why would you not want to allow slot names to be dynamic?

antony commented 3 years ago

As per my comment, it's possible to achieve what the issue was trying to achieve by just using a more appropriate paradigm.

We try to not just grow the API when it isn't necessary, as it makes Svelte harder to learn, and is a non-reversible operation.

If there is a scenario that my solution doesn't cover then perhaps open an RFC in the RFCs project to detail what is missing and your proposed solution.

deepakputhraya commented 3 years ago

I am not sure if the solution works. I have posted a similar problem statement on Stackoverflow. https://stackoverflow.com/questions/67649278/dynamic-data-preserve-changes-in-the-child-component I am seeing the same problem that @rodhoward was mentioning. https://codesandbox.io/s/revogrid-svelte-playground-i66kh?file=/App.svelte

stephenlrandall commented 2 years ago

For anyone new to Svelte learning how to solve this kind of problem, here's a REPL of how to use the previous suggestion of svelte:component. The Container.svelte component controls the layout, and takes both a component and the corresponding content as props. More generally, you can even define a component type per child and render things that way, as done in GeneralContainer.svelte.

dondre commented 2 years ago

@stephenlrandall Appreciate your example but this is a prime example of why @antony is so wrong. Dynamic slot names are intuitive and svelte: components are just plain confusing and messy. I'm depressed, my dynamic layout component lays in ruin.

antony commented 2 years ago

@dondre this isn't a helpful comment. If there is a feature missing, please create an RFC.

Trystan-SA commented 2 years ago

This feature would make things more easy to write. For example Carbon-Components-Svelte use this syntax to create a tab component :

<script>
  import { Tabs, Tab, TabContent } from "carbon-components-svelte";
</script>

<Tabs>
  <Tab label="Tab label 1" />
  <Tab label="Tab label 2" />
  <Tab label="Tab label 3" />
  <div slot="content">
    <TabContent>Content 1</TabContent>
    <TabContent>Content 2</TabContent>
    <TabContent>Content 3</TabContent>
  </div>
</Tabs>

With dynamic named slots, you can simplify it by only importing one Tab.svelte module instead of using 3. And writing the component itself would also be a lot easier.

<script>
    import Tabs from '../components/Tabs.svelte'
</script>

<Tabs Tabs={["Category 1", "Category 2", "Category 3"]}>
    <slot name="Category 1">   <h1>Content 1</h1>   </slot>
    <slot name="Category 2">   <h1>Content 2</h1>   </slot>
    <slot name="Category 3">   <h1>Content 3</h1>   </slot> 
</Tabs>

Anyway, I believe you can make this work by manipulating CSS style based on class names.

givehug commented 2 years ago

You can have an array of components, and then a loop, which has inside it.

What If I have a WidgetShowcase component to which I would like to dynamically inject different widgets on different pages, some widgets may be missing on some pages, widgets have there place on the page, they are not simply rendered as a list, there are 30 widgets, so I'd like to have it dynamic instead of hardcoding on each page. Looping through children and then matching by index doesn't seem like a nice paradigm.

I think every attribute should be able to accept dynamic value, otherwise you'll have many issues like this :)

vonsa commented 2 years ago

Without changing svelte, creating a Tabs component could be done like this:

Tabs:

<script>
  export let tabs

  let activeTab = 0
</script>

<div class="tabs-container">
  {#each tabs as label, index}
    <button on:click={() => { activeTab = index}}>{label}</label>
  {/each}

<slot {activeTab} />

Parent component:

<Tabs tabs={['Tab 1', 'Tab 2', 'Tab 3']} let:activeTab>
    {#if activeTab === 0}
        <Component1 />
    {/if}
    {#if activeTab === 1}
        <Component2 />
    {/if}
    {#if activeTab === 2}
        <Component3 />
    {/if}
</Tabs>

The Tabs component seems pretty reusable here but implementing it in the parent component could probably be done more elegantly, as creating if statements for each component is a bit redundant.

I think that using slots instead of can provide more flexibility regarding composition, event/variable bubbling and also stimulates readability.

teamore commented 2 years ago

I also came across this limitation and think it would be very useful to assign names to slots dynamically to avoid getting this message if you try doing so: Error: ValidationError: <slot> name cannot be dynamic

ivands commented 2 years ago
maxicarlos08 commented 2 years ago

+1 For this feature

Or at least document somewhere that it is missing.

The solutions here would not work well if the number of tabs is dynamic and the component used in each tab is different (or when it even isn't a component).

vonsa commented 2 years ago

Just to expand on my previous Tabs example, here is a more dynamic implementation method:

Tabs:

<script>
    import { setContext } from 'svelte';
    import { writable } from 'svelte/store';

    const activeTab = writable(0);
    const tabs = []

    const tabsContext = setContext('tabs', { activeTab, addTab })

    function addTab(label: string): number {
        const index = tabs.length + 1 | 0;
        tabs = [...tabs, { label, index }];
    }
</script>

<div class="tabs-container">
  {#each tabs as label, index}
    <button
        on:click={() => activeTab$.set(index)}
        class:selected={$activeTab === index}
    >
        {label}
    </button>  
  {/each}
</div>

<style>
    .selected {
        /* active tab styling */
    }
</style>

Tab:

<script>
    import { getContext } from 'svelte';

    export let label: string

    const { activeTab, addTab } = getContext('tabs')
    const index = addTab(label)
</script>

{#if $activeTab === index}
    <slot />
{/if}

Implementing the Tabs component:


<Tabs>
    <Tab label="First tab">
        <!-- HTML output -->
    </Tab>
    <Tab label="Second tab">
        <!-- HTML output -->
    </Tab>
    <Tab label="Third tab">
        <!-- HTML output -->
    </Tab>
</Tabs>```
0xDjole commented 2 years ago

Can this be reopened? I believe a framework should be able to support this.

climech commented 2 years ago

@antony

We try to not just grow the API when it isn't necessary, as it makes Svelte harder to learn, and is a non-reversible operation.

While I agree that this is a sensible approach generally, I don't think it applies in this case. Being new to Svelte, this behavior was counter-intuitive to me—my assumption was that naming slots dynamically would just work, only to hit a wall at the end, and having to search for a workaround. So I would make the opposite argument, that supporting this would make Svelte easier to learn. The other solutions just aren't as intuitive to me.

timonmasberg commented 1 year ago

At the beginning, Svelte is so much fun because you can just do things the way you think things should be done. But when you get deeper and deeper into developing your application, you just constantly hit so many dead ends (like this closed issue) that it sometimes becomes really frustrating. For me, this is always the turning point where I choose different frameworks.

cyan-2048 commented 1 year ago

sad how this issue got closed... the "solution" provided is bs as well.... svelte:component would only work for components, i want to create tabs dynamically i don't want to create another component for another tab...

akvadrako commented 1 year ago

@cyan-2048

There are a number of cases where svelte works best with many small components instead of one big one. If you use the https://github.com/srmullen/svelte-subcomponent-preprocessor you can even have them in one file.

You also don't need to use svelte:component if you use #if / :else if blocks or the Tabs component above. Alas, #switch statements would be cleaner (#530).

DeedleFake commented 1 year ago

I am amazed that there seems to be no way to do this. Why in the world would this not work, and how would making something that works everywhere else, namely dynamic properties, work for slots too make the framework more confusing? Currently it works differently for seemingly no reason. That's way more confusing to me.

My use case involves a component that displays other components based on data loaded from a server. For example,

<!-- Chooser.svelte -->
<script>
  export let name
  $: val = loadValue(name)
</script>
<slot name={val} /> <!-- Doesn't work. -->

<!-- App.svelte -->
<Chooser name="some-data">
  <div slot="a">...</div>
  <div slot="b">...</div>
</Chooser>

The fact that there are zero workarounds for this, such as rendering a slot dynamically using $$slots or something, is bizarre.

kalkronline commented 1 year ago

Just ran into this today, it's kind of a disappointing limitation...

DeedleFake commented 1 year ago

For my usecase mentioned above, the let workaround actually works decently well. For example:

<!-- Chooser.svelte -->
<script>
  export let name
  $: val = loadValue(name)
</script>
<slot value={val} />

<!-- App.svelte -->
<Chooser name="some-data" let:value>
  {#if value == "a"}
    <div>...</div>
  {:else if value == "b"}
    <div>...</div>
  {/if}
</Chooser>

It introduces an extra level of indentation and some manual checks, but it has fewer potential confusions from mismatched types and it allows for more complex logic. I still think that the inability to use dynamic slots is strange and confusing, though.

Conduitry commented 1 year ago

If you're going to @ a maintainer unsolicited, try not to say something so fucking stupid.

PfisterFactor commented 1 year ago

I would also like to see this implemented. I don't believe it clutters the API, quite the opposite - it removes a limitation of the current API that requires hacky workarounds.

federicofazzeri commented 1 year ago

Seems to me the let directive is perfect for solving this kind of problems where you need to set data on the parent Component..

<Parent let:props let:slots>
    <Child {...props}>
        {slots.default} 
        <span slot="one">{slots.one}</span>
    </Child>
</Parent>
jarecsni commented 1 year ago

Same here, this would be very useful. I'm working on a framework which generates content dynamically. This kind of limitation makes such work impossible (I am hoping to find a workaround but it's really annoying) Why would it be a good idea to have literal values for this?

jarecsni commented 1 year ago

So this means named slots are pretty much useless for a lot of scenarios, and one will need to fall back to using an array of child components and dump them in the required order. Very much not ideal. Seems like a rather arbitrary limitation.

jarecsni commented 1 year ago

Update... I tried to overcome this by using indexed names like <slot name="0"> etc and when generating the contents for the slots, doing like <span slot={i}> so its not even possible to use an array of components. I mean come on this is ridiculous. Why would part of a framework introduce such hampering limitation? @Rich-Harris please :)

mariusnita commented 1 year ago

Btw web component frameworks like Shoelace have their own notion of "slot" and Svelte effectively doesn't allow you to create custom components that wrap Shoelace web components. The hacky workaround is to use {@html ...}.

DeedleFake commented 1 year ago

@mariusnita

I'd never heard of Shoelace before, but I took a look and I'm confused about why you say it doesn't work with Svelte. For example, if you wanted a Svelte component to wrap an sl-button, you should be able to define a Button.svelte component with the following:

<sl-button>
  {#if $$slots.prefix}
    <div slot="prefix" style:display="contents">
      <slot name="prefix" />
    </div>
  {/if}

  <slot />
</sl-button>

Then, to do the equiquivalent of the named slot example from the Shoelace documentation, you could just do:

<Button>
  <Icon slot="prefix" name="gear" />
  Settings
</Button>

The wrapper div isn't wonderful, but it should work just fine.

Edit: Uhhh... Looks like Svelte doesn't let you give a slot attribute to an element that isn't a direct child of a Svelte component. That's annoying, but it's not the same as this issue.

jarecsni commented 1 year ago

This is what I'm trying to do:

<svelte:component this={beanRendererComponent} {bean} {...props}>
    {#if bean.children}
        {#each bean.children as childBean, i}
            <span name={i}>
                <GenericBean bean={childBean}/>
            </span>
        {/each}
    {/if}
</svelte:component>

So what I'm saying essentially is this: Svelte's unreasonable restriction on this property blocks any non-direct (programmatic use) which is really beyond belief. I will see if it is easy enough to raise a PR for this, as this is really blocking me (I can try this same idea with props, but slots are way more elegant for this). Will keep posting here.

mariusnita commented 1 year ago

@DeedleFake thanks for the workaround suggestion.

B-Esmaili commented 1 year ago

Now what is the outcome of this conversation? After 4 years i can't still conditionally provide slot content? This is totally unacceptable. This one by itself is as irritating as the rest of framework is awesome. This is literally unacceptable. You should stop working on any other feature until this one gets enough dedication. At the end thanks for really awesome framework. @Rich-Harris

ptrxyz commented 1 year ago

Today I also ran into this problem. Sadly the proposed workarounds don't work in many cases and simply closing it without a proper examination is a bit ignorant in my opinion. Dynamic slots make sense to solve the example above, sure you could do it in a different way but way more verbose. And didn't Svelte start with trying to make things much simpler? In addition, the proposed workaround with get's super messy when you have a lot of shared state. Like form fields that need to be validated or something. "If A is selected in component X, component B should only show the options U, V and W. Then, if W, then the validation should be for numbers instead of string... ". Something like this is pretty common and passing references to components or values around is often quite messy and verbose...

Anyway, I would love this to be reopened and reconsidered. Svelte 4 is on the horizon, maybe this might be something for the roadmap?

I think this is a question of usage rather than a limitation in Svelte.

For the scenario above, there is no reason to use slots. You can have an array of components, and then a loop, which has <svelte:component this={yourComponent} /> inside it.

Closing due to age, but also because I think this is a case of using the right tool for the job.

kesey-bm commented 1 year ago

I am also having this issue. I am trying to build a dynamic grid component, which accepts a layout object as the prop. Then, I want to be able to use slots to set the content based on the layout prop. There are workarounds, like setting the component for each layout element directly, which is what I will probably end up doing, however, I would rather be able to use slots especially for main content.

For example:

 <script>
    let layout = {
        header: {
            start: [1, 1],
            end: [1, 6]
        },
        sidebar: {
            start: [2, 1],
            end: [4, 2]
        },
        main: {
            start: [2, 3],
            end: [4, 5]
        },
        preview: {
            start: [2, 6],
            end: [4, 6]
        }
    };
</script>

<Grid {layout}>
    <header slot="header">Header content</header>
    <main slot="main">Main content</main>
    <aside slot="sidebar">Sidebar content</aside>
    <aside slot="preview">Preview content</aside>
</Grid>
jajabarr commented 1 year ago

+1, I would really love to see how the svelte maintainers here would implement a tabbed view component.

Svelte becomes a problem when:

  1. You can't treat markdown as markdown
  2. You're convinced you need to leak svelte implementation details to the consumer (e.g. let: directives) for something to work
  3. You need to escape the framework often to get work done (e.g. document.appendChild...)

Svelte runs code using script tags and html. Everything about it is screaming to the developer "I look familiar, you know what to do!". However, in cases like this, it says "Hey, I know you're used to putting whatever you want in markdown under that div, but for this cool component you wrote, you can't just do that, and there's no such thing as 'children'!".

It's frustrating that such a common API wouldn't exist for the current "most loved" framework. Vanilla can do it, React can do it, but not Svelte?

I don't want to be that guy that asks you to do something and then doesn't take my own crack at it, so here's my version of the TabbedView component mentioned above:

https://svelte.dev/repl/936c1e58d23f457daaee6f1b68b46de0?version=3.59.1

It's... annoying, but the only way I could get to a point where a consumer isn't seeing the word 'let' or 'slot' anywhere and I can customize all the views and labels in html and have as many or few as I need.

This issue shouldn't be closed, it should be fixed - myself and everyone else wants to love Svelte. So either lead by example and improve the docs to spell out exactly how you'd want us to handle these scenarios, or just give us the API's we're craving.

Anprotaku commented 11 months ago

This feature is really needed vanilla Js can do it so why cant svelte? I really love svelte however this limitation has some problems like:

When moving elements around in plain JS the element gets removed from DOM and then reinserted. Making the element in question lose its state. For iframes this would make the iframe lose its state completely.

Using slots, i could move elements around without having to worrt about their internal state being reset and just contiue having their state.

Example:

/********* main.svelte *********/
<script lang="ts">
  import Box from "./box.svelte";
  export let slotname: string;
</script>

<Box {slotname}>
  <iframe slot="first" src="https://example.com"></iframe>
  <iframe slot="second" src="https://wikipedia.com"></iframe>
</Box>
/********* box.svelte *********/

<script lang="ts">
    export let slotname: string;
</script>
<div>
    <slot name={slotname}></slot>
</div>

This way the iframes local states wont change between changes of slots, and i can come back to where i was if i change back too the first slot.

guradia commented 6 months ago

For example:

<Grid {layout}>

Header content
Main content

I'm doing something very similar. But i let the Grid build the Layout (grid-template) by what slots are populated. Which does work fine with $$slot inside Grid's code.

But:

calvin-kimani commented 4 weeks ago

I think this is a question of usage rather than a limitation in Svelte.

For the scenario above, there is no reason to use slots. You can have an array of components, and then a loop, which has <svelte:component this={yourComponent} /> inside it.

Closing due to age, but also because I think this is a case of using the right tool for the job.

Hey there! So, I was working on creating a Tabs component in Svelte, you know, those neat little tabs you see on websites for organizing content? But I ran into a snag when I tried to dynamically generate the tabs and their content using slots. Svelte doesn't allow dynamic slot names, which kind of threw a wrench in my plans.

Feeling frustrated, I came across this discussion where this comment pointed out that it's more about how you use Svelte rather than a limitation of the framework itself. It got me thinking: maybe trying to force dynamic content generation into slots isn't the best approach. After all, slots are meant for displaying content, not for dynamically generating it.

So, I took a step back and reconsidered my approach. Instead of using dynamic slots, I decided to leverage Svelte's context API to manage the state and interactions between the tabs and their content. This way, I could still dynamically generate tabs and their corresponding content without bumping into the limitation of dynamic slot names.

Here's what I came up with, which I believe can be used in other contexts.

I created a Tabs component that holds the state for the tabs and panels using Svelte's writable stores. Then, I used context to share this state with the Tab, TabList, and TabContent components. Each Tab component registers itself with the Tabs component when it mounts, and the Tabs component keeps track of the active tab.

This approach allowed me to decouple the tabs and their content while still enabling them to communicate and update each other as needed. It's a bit of a workaround, but it gets the job done without relying on dynamic slot names.

Overall, I'm pretty happy with how it turned out. It's a good reminder that sometimes the best solution isn't the most obvious one, and it's all about finding the right tool for the job.

Tabs.ts

export { default as Tabs } from './Tabs.svelte';
export { default as Tab } from './Tab.svelte';
export { default as TabList } from './TabList.svelte';
export { default as TabContent } from './TabContent.svelte';

Tabs.svelte

<script>
    import { writable } from 'svelte/store';
    import { setContext } from 'svelte';

    let tabs = writable(0);
    let panels = writable(0);
    const activeTab = writable(0);

    function registerTab(){
        tabs.set(++$tabs)
        return $tabs - 1 
    }

    function registerPanel(){
        panels.set(++$panels)
        return $panels - 1 
    }

    function setActiveTab(tab){
        activeTab.set(tab)
    }

    setContext('TABS', { activeTab, setActiveTab, registerTab, registerPanel })

</script>

<div>
    Tabs: {$tabs}
    Panels: {$panels}
    ActiveTab: {$activeTab}
</div>

<div {...$$restProps}>
    <slot />    
</div>

Tablist.svelte

<ul {...$$restProps}>
    <slot />
</ul>

Tab.svelte

<script>
    import { getContext, onMount } from 'svelte'
    const { activeTab, setActiveTab, registerTab } = getContext('TABS');
    let id;

    onMount(() => {
        id = registerTab()
    })

</script>

<div class="w-full my-10">
    <h1>TAB ID: {id}</h1>
    <div>
        ActiveTab: {$activeTab}
    </div>

    <button on:click={() => setActiveTab(id)}>
        Update Active Tab
    </button>   
</div>

TabContent.svelte

<script>
    import { getContext, onMount } from 'svelte'
    const { activeTab, registerPanel } = getContext('TABS');
    let id;

    onMount(() => {
        id = registerPanel()
    })

</script>

{#if $activeTab == id }
<div class="w-full my-10">
    <h1>Panel ID: {id}</h1>
    <div>
        ActiveTab: {$activeTab}
    </div>
</div>
{/if}
adzza24 commented 4 weeks ago

It's a nice solution, but still not ideal. It means every time you implement tabs you need to setup a bespoke component rather than just being able to create a single tabs component and throw anything at it to render out in a tab format. For an admin app like I'm working on, which has tabs on 100 different pages, this would mean a lot of code bloat and a really complicated folder structure.

I'd see this as a very nice work around, but not a solution to the OP. I just ended up using a different framework. Which is a shame because I like svelte. This limitation probably means I wouldn't use it again either, unless for a really simple, uninteractive app.

brunnerh commented 4 weeks ago

There are a lot of workarounds that do not require bespoke components and this will be possible in Svelte 5 anyway.

Transformed example from original post ```svelte

Table of cats

{#snippet name_view(row)}{row.name.toUpperCase()}{/snippet} {#snippet name_heading()}Name{/snippet} {#snippet id_view(row)}{row.name}{/snippet}
``` ```svelte {#each fields as field, i} {/each} {#each rows as row, i} {#each fields as field, i} {/each} {/each}
{#snippet fallback()}{field}{/snippet} {@render (rest[`${field}_heading`] ?? fallback)()}
{#snippet fallback(row)}{row[field]}{/snippet} {@render (rest[`${field}_view`] ?? fallback)(row)}
```