sveltejs / rfcs

RFCs for changes to Svelte
274 stars 81 forks source link

Targeted Slots - passing and mixing attributes and styles to component, access the slotted element from inside the component #68

Closed lukaszpolowczyk closed 10 months ago

lukaszpolowczyk commented 2 years ago

Rendered.

People who might like it (and my inspirations): @Tropix126 @Zizico2 @ghughes

Zizico2 commented 2 years ago

To me this feels like the culmination of the discussion about this topic. I think the use of svelte:element is incredibly intuitive. We see it as a dynamic element anyway, we're just adding even more dynamicness (?).

I think this supercedes my RFC in every way. I won't close it for a while since there might be some more discussion over there that's doesn't fit in this RFC. I digress...

Giving my 2 cents on some of the unanswered questions:

The attribute value written in Parent, should overwrite the attribute value written in Child. Maybe it should be possible to decide the order of overwriting?

I don't think it should. It would make slots more cumbersome to work with. You don't know what your getting. It stops being consistent.

Passing more than one targeted:name to one target and to slot:subname.

I can't come up with a use case either. I feel like unless we can find a use case, it's hard to think about advantages and drawbacks, so we shouldn't add it, just because it is added complexity.

Someone sees and thinks it's a simple slot. And it is a Targeted Slot. Therefore, perhaps the name of the slot attribute should be changed into another one. But I don't know into which one?

Is this a problem? Targeted slots are a superset of slots anyway. They can use it as a simple slot. It's literally a "1 child slot".

mimbrown commented 2 years ago

Very interesting proposal here. A couple pushbacks:

  1. The double use of <svelte:element> in both parent and child makes it, IMO, difficult to understand what is going on.
  2. The way this stands, it's pretty easy to write a component with a targeted slot, and comparatively difficult to use them. Given that it's generally going to be library authors creating components with slots and userlanders using them, that seems a bit backwards to me (normally the complexity, if it's unavoidable, should be offloaded onto the library author).
lukaszpolowczyk commented 2 years ago

@mimbrown

  1. There is an option to make the inside of the target called <svelte:target/> or <target/> (straight from Declarative Actions), but it would still be such a slot, it would have a targeted:name attribute: <svelte:target targeted:name>. But the basic distinction is to use the slot="name" attribute in Parent, and targeted:name in Child. I thought that was sufficient?

  2. Show an example that it would be difficult to use this. That is, what would you want to use it for, and what would be the difficulty in doing so? Too much code in Parent? Or what do you have in mind? Show exactly.

mimbrown commented 2 years ago

There is an option to make the inside of the target called or (straight from Declarative Actions), but it would still be such a slot, it would have a targeted:name attribute: . But the basic distinction is to use the slot="name" attribute in Parent, and targeted:name in Child. I thought that was sufficient?

That does help, thanks for pointing that out.

Show an example that it would be difficult to use this. That is, what would you want to use it for, and what would be the difficulty in doing so? Too much code in Parent? Or what do you have in mind? Show exactly.

In my original use case, I was thinking about how much more naturally something like a material-design button is composable functionality, not a reusable component. So, with Declarative Actions-like syntax (in a contrived example), the consumer markup would look something like this:

<script>
import { matRaisedButton, matOutlinedButton } from 'some-lib';
</script>

<div class="actions">
  <button use:matOutlinedButton class="custom-class">Cancel</button>
  <a href="#" use:matRaisedButton class="custom-class">Save</a>
</div>

Whereas targeted actions (as I understand it) would be something like:

<script>
import { MatRaisedButton, MatOutlinedButton } from 'some-lib';
</script>

<div class="actions">
  <MatOutlinedButton><svelte:element slot="button" class="custom-class" />Cancel</MatOutlinedButton>
  <MatRaisedButton><svelte:element slot="button" this="a" href="#" class="custom-class" />Save</MatRaisedButton>
</div>

So, yes, more keystrokes, but that's not actually what I don't like. In the first example, it's transparently obvious what the markup will be. The second is a lot more cognitive load.

lukaszpolowczyk commented 2 years ago

@mimbrown The problem this="a", is a problem in general <svelte:element/>. That' s why it exists, so that there is a dynamic tagName.

To make it simpler in Targeted Slots, you could still allow the use of any element, not just <svelte:element/> - but I really don't know if that is technically possible, which is why I added it in "Rejected at this time". If it is, I'd be happy. :)

Then the example with a would look like this (I also changed the name of the slot, because the name of the slot in Child, you can give any):

  <MatRaisedButton><a slot="link" href="#" class="custom-class">Save</a></MatRaisedButton>

And as for the amount of code in Parent - It's because of the universality of Targeted Slots - that you can use more than one slot.
But you can think of exceptions. Here we have an exception that one slot is enough.

You can think about the syntax (exception) - a shortcut:

<svelte:element slot={MatOutlinedButton} class="custom-class">Cancel</svelte:element>

This would be a literal shortcut, you could handle it with a preprocessor that simply converts this code, to the target code:

  <MatOutlinedButton><svelte:element slot="default" class="custom-class">Cancel</svelte:element></MatOutlinedButton>

That this preprocessor would have to name slot="default", and you would have to handle this in your Child component via targeted:default.

Note1: This syntax would be very similar to use:matRaisedButton, but still more flexible. Note2: This has absolutely nothing to do with <svelte:component this={matRaisedButton}/>, it should not be confused.


You could also build such an exception into the normal behavior of Targeted Slots, but that complicates the proposal. I simply did not want to complicate the proposal.

The syntax slot={MatRaisedButton} complicates the proposal in several ways:

This is something I didn't describe in "Rejected at this time," because it has too many ambiguities and complicates the proposal. I only wrote about "cross-mix attributes (between Component and element)", which I reject at this point.


Using any element tagName and slot={MatRaisedButton} e.g.

<a slot={MatRaisedButton} href="#" class="custom-class">Save</a>

...is nice, but I don't know if technically possible and some problems need to be solved.

It may be possible, but I'm waiting for some SvelteJS member to say something.

mimbrown commented 2 years ago

My thoughts are clarifying on this. At first I thought the difference here was just a difference in mental model, but that's not true. This proposal is about a svelte component that provides targeted access to its elements. This is a very worthy goal. I see some really good applications, for example solving the long-standing problem that reusable form elements generally have the underlying input element nested underneath other elements (like a label), but consumers of those components normally want to add custom classes, listeners, and attributes to the input element, not its parent. Targeted slots would solve that issue.

But the Declarative Actions proposal is about defining a reusable action (or composable) that the consumer can use however they wish, including by "composing" multiple actions with an element that they create. That means the consumer can do this:

<script>
import dropTarget from './dropTarget.svelte';
import matRaisedButton from './matRaisedButton.svelte';
</script>

<button use:matRaisedButton use:dropTarget class="custom-class">Click or drop</button>

Unless I'm very much mistaken, this is functionality that targeted slots can't do. I'm trying to say I don't think targeted slots is a superset solution to declarative actions, they're different proposals.

lukaszpolowczyk commented 2 years ago

@mimbrown Inside Child you can refer to the button element, using bind:this={target}.

<!-- Child.svelte -->
<script>
 let target
</script>
<svelte:element targeted:name bind:this={target}/>

In this code, target is the node equivalent of use:action.

But the equivalent of many use:action1 use:action2 is not in Targeted Slots. This is true. I would have to think about how to include such a thing in my proposal.

Nothing prevents you from using use:action use:action next to the Targeted Slot, but I understand that this is not enough.

I don't know if it will work for me, but I will try to think of something.

mimbrown commented 2 years ago

@lukaszpolowczyk I don't think it should be included, because I think it's a separate issue. Declarative Actions is a proposal for allowing declarative, composable pieces of functionality to be defined. Targeted slots is a proposal for giving consumers insight and "targetability" into a custom svelte component. Two separate possible solutions to two different problems.

lukaszpolowczyk commented 2 years ago

@mimbrown But my inspiration was Declarative Actions, among other things. The creator of Declarative Actions said that my proposal replaces Declarative Actions. Declarative Actions has the very purpose you described. :D

I see no reason to separate it.

At this point I have no idea, so there is nothing to talk about.

If something comes to mind, I will just describe it.

mimbrown commented 2 years ago

@Zizico2 Do you have any desire to weigh in on this?

lukaszpolowczyk commented 1 year ago

@mimbrown @Zizico2 I have a simple idea for a replacement for a lot of use:action.

<!-- Parent.svelte -->
<svelte:component bind:these={[ Child1, Child2 ]}>
  <svelte:element slot="name"/>
</svelte:component>

The bind:these attribute allows you to pass several declarative actions, and combine in one slot, as long as there is the same slot name in Child1 and Child2.

The issue that this requires the same slot name is rather a problem, you would need to be able to set the slot name under each Child1 Child2, because Child2 may have a different slot name than Child1.

What do you guys think?

I am not adding this to the description of this rfc for now.

EDIT: Eventually, this should only work if there are unnamed slots inside Child1 and Child2. Then there is no problem.

dummdidumm commented 10 months ago

Snippets will replace slots (they are still around but deprecated) in Svelte 5 and will have a lot more flexibility with regarding to passing things around. Therefore closing this RFC - thank you.