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

It would be nice to be able to write a slot without an extra DOM element #2080

Closed
chris-morgan opened this issue Feb 13, 2019 · 26 comments
Closed

Comments

@chris-morgan
Copy link
Contributor

Currently, you need a single DOM element to fill a slot. This limits slots’ usefulness in various scenarios.

#1037 and #1713 are about allowing a Svelte component to fill a slot, and I believe there is consensus in v3 that this is desirable and intended. Given that a component does not necessarily render to a single DOM element, this is interesting.

It would also be nice to be able to do this without needing to create a whole new component, or a DOM element. Two strawman syntaxes:

{#slot "foo"}
    …
{/slot}
<div slot="foo" svelte:slot-behaviour="exclude-wrapping-element">
    …
</div>

There may be considerations for Custom Elements, but if it’s sorted out for all Svelte components first then there should be nothing fundamentally new here.

@theSherwood
Copy link

This idea really seems to open up the possibility of using a component purely for doing operations on application state while remaining agnostic about how that gets rendered. Seems useful.

@steve-taylor
Copy link

Another strawman syntax:

<svelte:array slot="foo"></svelte:array>

@chris-morgan
Copy link
Contributor Author

Yet another possible syntax I thought about a few days ago:

<template slot="foo"></template>

Giving this special meaning to <template> is likely to be controversial.

@steve-taylor
Copy link

Out of all these, I like {#slot expression} the most.

@tomblachut
Copy link

I vote for element syntax, I find it more consistent than using special block, because slot is standard attribute applied to elements.

There should be a way to declare elements that don't yield DOM Element by themselves but can contain children and affect them. Essentially what component tags do from consumer perspective.

  • Angular has <ng-container> that you can apply directives to
  • React has <Fragment> very roughly for that
  • @chris-morgan I agree that overloading <template> is controversial. Namespacing with svelte: is much better IMO
  • @steve-taylor I think <svelte:array> is misleading name for people coming from different backgrounds. Newcommers could think that <svelte:array> must contain elements of the same type for example.

I like <svelte:fragment slot="foo"> as there's already precedent with namespacing special elements.

@CreaturesInUnitards
Copy link
Contributor

CreaturesInUnitards commented Apr 29, 2019

Out of all these, I like {#slot expression} the most.

💯 Svelte already has a special block syntax for other purposes. Adding another variant would be perfectly ergonomic and unceremonious.

I vote for element syntax, I find it more consistent than using special block, because slot is standard attribute applied to elements.

This is perfectly sane, of course, but I don't find it to be persuasive.

  • element syntax creates the visual impression of further nesting, which block syntax does not.
  • in fact, the purpose of this proposal is to wrap a fragment without nesting; block syntax visually reinforces author intent.
  • I'm not sure the fact that slot is a standard attribute applied to elements is actually a feature, so much as it is a harmless byproduct. But either way, it would likely be trivial for the block implementation to apply the slot attribute to the top level elements.

@CreaturesInUnitards
Copy link
Contributor

One final point regardless of the syntactic choice. This is the current state of things:

<!-- MyComponent -->
<div>
  <slot name="foo"></slot>
  <slot name="bar"></slot>
</div>

...

<!-- MyApp -->
<p slot="bar">1</p>
<p slot="foo">2</p>
<p slot="bar">3</p>
<p slot="foo">4</p>

...

<!-- results -->
2
4
1
3

...and that's just silly.

@Conduitry
Copy link
Member

I suppose I don't have strong opinions about whether multiple things should be able to be inserted into one slot, but given that it's already possible, how is this silly? The component first renders the two foo slots (2 and 4) and then renders the two bar slots (1 and 3).

@pngwn
Copy link
Member

pngwn commented Apr 29, 2019

As conduitry said, that behaviour is what I would expect.

The only issue with adding block syntax for this is that we then have two ways of passing elements into slots: via an attribute and via a block. Generally the Svelte philosophy has been to have only one way of doing things. This change would not only introduce another way but quite a different other way.

@CreaturesInUnitards
Copy link
Contributor

@Conduitry I suppose "silly" is a bit reductive. I mean to say this enables a fairly gnarly type of indirection in markup that could be lead to some very frustrating pebkac scenarios. Block syntax for multiple unwrapped elements would be intuitive.

@CreaturesInUnitards
Copy link
Contributor

@pngwn I agree wholeheartedly with the one-good-way philosophy where it makes sense. But, like Svelte's class syntactic variants, this calls for 2 ways because we're talking about 2 different problems.

Take class. If I want to specify 2 classes on a single element based on 2 different bools, it's better to use the shorthand class:foo={bool1} class:bar={bool2} than to jam checks for both bools in a single class={ ... }.

In the case of slot, block syntax for a single element would be excessive, but for wrapping multiples it would be the clearest and cleanest. I don't see that as two ways of doing one thing.

@tomblachut
Copy link

tomblachut commented Apr 29, 2019

I've been thinking that block syntax for slots would - if I didn't miss anything - be one of it's kind. if, each & await simply stamp out zero or more elements based on their content, in place. slot block would apply attribute to it's children and moreover would be illegal outside components.


I'm not sure the fact that slot is a standard attribute applied to elements is actually a feature, so much as it is a harmless byproduct.

@CreaturesInUnitards just to be sure you understood me correctly, I meant that slot attribute is in HTML spec already. BTW there's bigger chance HTML later introduces grouping element that affects its children than it introduces blocks.

@CreaturesInUnitards
Copy link
Contributor

@tomblachut yeah I get you. For my taste the tradeoffs favor block syntax, but I'm not gonna jump up & down about it :)

I'm confident the maintainers will make a good decision on this; I just wanted to make sure my points were considered. I feel like they're on the table now, so I'm good with whatever happens.

@codev0
Copy link

codev0 commented Jun 18, 2019

Hi there,
Another solution for this issue https://github.com/qutran/svelte-fragment, example from readme

<script>
  import Wrapper from './Wrapper.svelte';
  import Component from './Component.svelte';
  import fragment from 'svelte-fragment';
</script>

<Wrapper>
  <!-- Component will be rendered without wrapper `template` -->
  <template use:fragment slot="content" let:data>
    <Component {data} />
  </template>
</Wrapper>

@brucou
Copy link

brucou commented Jun 28, 2019

I bumped into that issue while trying to make a higher order component with Svelte. I am not sure why the syntax <Component slot="..." /> would not be allowed. It says reserved for future use, I welcome any solution. Component composition is about component and slot is about composition so they should work together.

@Conduitry
Copy link
Member

It says 'reserved for future use' because it is an intended feature that has not been implemented yet. Without this error, adding it would be a breaking change, as it would conflict with a regular prop called slot.

@brucou
Copy link

brucou commented Jun 28, 2019 via email

@rotwurstesser
Copy link

rotwurstesser commented Jul 6, 2019

this would make my code a bit easier to read when using refs =) removing the extra div on <div> slot="template"> {svelte loop in here}</div> would be great!

@Conduitry
Copy link
Member

There's actually a rather older issue about this, #1037, which I'm closing this in favor of.

@chris-morgan
Copy link
Contributor Author

chris-morgan commented Oct 15, 2019

Well, #1037 was for a different matter, as I mentioned in the issue description, but they can reasonably be merged to be tackled at once.

@NikolayMakhonin
Copy link

NikolayMakhonin commented Jan 24, 2020

This is my kludge of named slots without extra DOM elements.
If you have any idea how to simplify it - welcome.

Repl

Table.svelte
<script>
    export let slotRow = false
    export let slotRows = false
    export let slotCells = false
    export let slotHead = false
    export let slotHeadCells = false
    
    export let items
</script>

<table>
    {#if slotHead} <slot _slot="head"></slot> {:else}
        <thead>
            <tr>
                {#if slotHeadCells} <slot _slot="head-cells"></slot> {:else}
                    <th>default head A</th>
                    <th>default head B</th>
                {/if}
            </tr>
        </thead>
    {/if}
    <tbody>
        {#if slotRows} <slot _slot="rows"></slot> {:else}
            {#each items as item}
                {#if slotRow} <slot _slot="row"></slot> {:else}
                    <tr>
                        {#if slotCells} <slot _slot="cells" {item}></slot> {:else}
                            <td>default cell A{item}</td>
                            <td>default cell B{item}</td>
                        {/if}
                    </tr>
                {/if}
            {/each}
        {/if}
    </tbody>
</table>

<style>
    table, th, td {
        border: 2px solid lightgray;
    }
    table {
        margin: 1em;
    }
</style>
App.svelte
<script>
    import Table from './Table.svelte'
</script>

<Table items={[1, 2, 3]} slotCells={true} slotHeadCells={true} let:_slot="{_slot}" let:item={item}>
    {#if _slot === 'head-cells'}
        <th>head A</th>
        <th>head B</th>
    {:else if _slot === 'cells'}
        <td>cell A{item}</td>
        <td>cell B{item}</td>
    {/if}
</Table>

<Table items={[1, 2, 3]} slotRow={true} slotHead={true} let:_slot="{_slot}" let:item={item}>
    {#if _slot === 'row'}
        <tr>
            <td>cell A{item}</td>
            <td>cell B{item}</td>
        </tr>
    {/if}
</Table>

<Table items={[1, 2, 3]}>
    
</Table>

<style>
    th, td {
        border: 2px solid blue;
    }
</style>

@gushogg-blake
Copy link

I'd like to see named slots without extra elements as well. Would be especially nice in the case of passing named slot contents up through a component hierarchy, for example I have a TextInput component for creating <input>s, and some variants on it like Quantity for inputting numeric quantities. The TextInput has named slots either side of the input, labelBefore and labelAfter, which have to be defined in Quantity as well, creating two extra elements (and more as the hierarchy gets deeper).

Repl - https://svelte.dev/repl/500406762679481e971f2a718698b289?version=3.18.1

Also the {#slot name="name"} proposal just seems much more natural to me; <div slot="name"> stuck out as a bit unlike the rest of Svelte when I read it (we don't do <div if="condition">).

@PierBover
Copy link

I may be biased, since I've used Vue for a couple of years, but to me <template> makes the most sense to express "hey, don't render this tag in the dom".

I'm not sure #1037 is the same use case as this... Personally I'd rather not have to create a new component to solve this problem.

@johnnysprinkles
Copy link

Why is this issue closed, it's a different issue than #1037. Can we re-open?

My particular use case is a big nested Flexbox layout, where immediate childhood is necessary or else you need a bunch of <div slot="foo" style="width: 100%; height: 100%"> junk.

@shiftgeist
Copy link

fyi for anyone who runs into this issue: I found my answer at https://svelte.dev/tutorial/svelte-fragment

The svelte:fragment element allows you to place content in a named slot without wrapping it in a container DOM element. This keeps the flow layout of your document intact.

@johnnysprinkles
Copy link

Yeah, I'm pretty sure svelte:fragment was added after this bug was opened, and resolves it. I think it's just like a JSX <> fragment. In some ways there's less need for fragments in Svelte since you can have multiple top-level nodes, but slots is the one case I can think of for needed it.

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