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

Implement forward directive #60

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open

Conversation

Tropix126
Copy link

@Tropix126
Copy link
Author

Tropix126 commented Oct 7, 2021

Apology for the rather brief document; i'm not especially familliar with svelte's internals.

@Tropix126

This comment has been minimized.

@Tropix126
Copy link
Author

Tropix126 commented Oct 7, 2021

Have updated with thoughts from my comment above. Let me know if this should be split into a separate RFC at some point.

@kevmodrome
Copy link

kevmodrome commented Oct 8, 2021

I like this a lot!

I'd definitely opt for a more separated thing, not allowing forward:use:internalAction. Better to have it separate to make it clear what is going on IMO.

@kevmodrome
Copy link

Just re-read the part on Additional Proposal - foward effects and singular event forwarding. This is trivial to get around at the moment for actions: <button forward:use use:action />. The same can be done with events: <button forward:on on:click={internalFunction} />.

Where it might make sense is in the following situation where you want to run a function on all events: <button forward:on={internalFunction} /> vs <button forward forward:on on:*={internalFunction}.

@Florian-Schoenherr
Copy link

I like that it also solves forwarding use and even multiple of them.

@TheOnlyTails
Copy link

I really like forwarding specific events and actions (maybe transitions and animations too?) with forward:on:event & forward:use:action. Should definitely make it in.

@kevmodrome
Copy link

I really like forwarding specific events and actions (maybe transitions and animations too?) with forward:on:event & forward:use:action. Should definitely make it in.

forward:on:event is not needed, you can already do that today by just writing on:event :)

@TheOnlyTails
Copy link

Using forward is a little more explicit though. When I first saw on:event being used, I had no idea what it means. With forward:, it's clear that you're forwarding the event.

@PatrickG
Copy link

PatrickG commented Oct 8, 2021

I really like [...] & forward:use:action. Should definitely make it in.

What should forward:use:action do? action is a function that the component author specifies. It gets executed, and then?
I guess the way to go would be forward:use use:action.

As for the forward:on:click={handler}, the only thing that could be achived with it instead of on:click on:click={handler} would be, if it does not get forwarded if the handler would call event.stopImmediatePropagation() (I hope this sentence makes sense).

@dummdidumm
Copy link
Member

I don't think we need any extra syntax for exposing transitions/actions. You can expose that through props alreay, which is top-down, and introducing a forward-syntax for that, which would be bottom-up, is confusing to me. So the RFC would be reduced to forward of events, at which point the question is if bubble is the better wording.

@Tropix126

This comment has been minimized.

@kevmodrome
Copy link

kevmodrome commented Oct 8, 2021

I don't think we need any extra syntax for exposing transitions/actions. You can expose that through props alreay, which is top-down, and introducing a forward-syntax for that, which would be bottom-up, is confusing to me. So the RFC would be reduced to forward of events, at which point the question is if bubble is the better wording.

I don't think this is as clear cut as you make it out to be. For transitions I see your point and I think I agree but for actions it's a bit trickier. This makes it a lot easier to apply multiple actions, rather than having to pass in an array of them into some prop. Applying multiple actions dynamically is not very ergonomic at the moment - you have to write a utility action that takes an array of actions. Compare these two as a consumer of the component:

<MyInput use:autoResize={400} use:copyOnClick={{ once: true }} />

vs

<MyInput actionsToApply=[{action: autoResize, props: 500}, {action: copyOnClick, props: { once: true }}]

Another thing it has going for it is that immediately obvious that there is an action or transition applied to the component, I think that's worth something.

@Tropix126
Copy link
Author

Tropix126 commented Oct 8, 2021

I don't think we need any extra syntax for exposing transitions/actions. You can expose that through props alreay, which is top-down, and introducing a forward-syntax for that, which would be bottom-up, is confusing to me. So the RFC would be reduced to forward of events, at which point the question is if bubble is the better wording.

I did discuss this slightly in the RFC itself, but i'm fairly against the idea of leaving it to the developers of component libraries to abstract away the svelte language syntax. It's painful for the library's users since there can and will be inconisistencies, and it's painful for the developers, since the method isn't especially obvious and is a whole lot of boilerplate to do something extremely simple. People know how to use the native syntax, so why not use it?

@dummdidumm
Copy link
Member

Regarding actions/passing in stuff: This is true for the case where you want to expose one specific element, but fails when you want to be able to apply actions/transitions/whatever to multiple DOM elements, at which point it could get more confusing. Conceptionally, an DOM element consists of - well - one element, whereas for components it could be 0-x.
Regarding "leave it to authors to abstract away svelte language syntax": I don't think this is particulary painful. Writing export let transition; ... transition:transition is only a couple more keystrokes than forward:transition.
I feel your desire to have a uniform API (this actions=[{action: .., x: ..,y: ..} doesn't feel very idiomatic), but I don't think it's that tradeoff-free as it is presented here.

@Tropix126
Copy link
Author

Tropix126 commented Oct 8, 2021

Regarding actions/passing in stuff: This is true for the case where you want to expose one specific element, but fails when you want to be able to apply actions/transitions/whatever to multiple DOM elements, at which point it could get more confusing. Conceptionally, an DOM element consists of - well - one element, whereas for components it could be 0-x. Regarding "leave it to authors to abstract away svelte language syntax": I don't think this is particulary painful. Writing export let transition; ... transition:transition is only a couple more keystrokes than forward:transition. I feel your desire to have a uniform API (this actions=[{action: .., x: ..,y: ..} doesn't feel very idiomatic), but I don't think it's that tradeoff-free as it is presented here.

This doesn't necessarily invalidate the use of use props, it just provides an easier pathway for the most common scenario. $$restProps is a similar example in my eyes. You could export a prop for every possible attribute someone might need or you could use the convenient solution and route all of the attributes to a single DOM element in your template.

Although custom action syntax might not seem like a lot, it ends up stacking up when you have to deal with every edge case over a large amount of components.

@Florian-Schoenherr
Copy link

Florian-Schoenherr commented Oct 8, 2021

What should forward:use:action do? action is a function that the component author specifies.

that's not necessarily true, as sometimes you would want to pass an/multiple actions into the component

but fails when you want to be able to apply actions/transitions/whatever to multiple DOM elements

is that any different from forwarding multiple on:click events in one component?
edit: yes, but basically it would run for every element
and I think people will always build code which doesn't do what I would expect, but they can be teached

I really don't see the problem with having a uniform, really dynamic way of passing things down.
It is obvious that if you want to modularize things furthest, you NEED all actions, all events, all transitions, all props, all bindings (and so on).

The question is more how to do that without too much development/maintenance overhead and a simple, idiomatic API.

@Tropix126
Copy link
Author

Tropix126 commented Oct 8, 2021

So, i've updated the RFC:

At the time of opening this PR, I was actually unaware that an element could take in more than one on directive of the same type, meaning you can both forward something like a click event and use an internal function without dispatching. This drastically simplifies the implementation and removes the need for some of the syntax people are opposed to.

So i've removed the proposal for this sort of syntax:

<button forward:use:internalAction={}></button>

and replaced it with the equivalent

<button foward:use use:internalFunction={}></button>

@gtm-nayan
Copy link

gtm-nayan commented Oct 9, 2021

This could be expanded to cover the use case for forwarding CSS variables,

<!--Button.svelte-->
<button forward:--btn-color></button>
<!--Consumer.svelte-->
<Button --btn-color="#007FFF" />

Unless I'm missing something, this works around the issue of css vars on components potentially messing up the css structure with the extra div, since we now know exactly which element to put style="--btn-color: #007FFF;" on. Makes themable components a lot more easier.

@Florian-Schoenherr
Copy link

@698969 I think that's also interesting, but might be better to think about later?
What I found really confusing was that you can do css-vars on a component but not on an element like
<button --btn-color="#007FFF"> but that's off-topic

@lukaszpolowczyk
Copy link

@Tropix126 Whereas I would like it to work ONLY with named groups.
That would make the action of forward: absolutely unambiguous, even if someone doesn't know the syntax. The group name itself makes you know that what you added in the group::on:click component call is the same as what you gave in, for example, forward:group::on:click.

Of course, it doesn't have to be a double colon, it's just a suggestion, but just naming the group makes the syntax much more unambiguous at a glance.

And the fact that it provides more opportunities is just another plus.

This is how I see it.

@ptrxyz
Copy link

ptrxyz commented Aug 2, 2022

I also wouldn't like the group syntax as it looks pretty alien to me.

How about sticking to a similar syntax that we already have:

If <button on:click> (on:click without a handler) emits a click event to the parent component, why couldn't we have <button forward:on> to forward all events without an explicitly added handler the parent?

So with forward:on all events without a handler are emitted. If you want to handle it inside the child component add a handler. If you then also want to forward the event, you can manually add dispatch to your handler to make it bubble to the parent.
If you do not want to emit an event, how about adding a modifier such as |stopPropagation or something similar.

(Analog to |stopPropagation, one could also argue to add a explicit modifier |doPropagate for the events that I have a handler and that should also be forwarded to the parent. I don't think that's how Svelte does it for now, so it would probably be a bit of an extra thing. )

<button on:click>
<button forward:on> 
<button forward:on on:click on:focus={something}>
<button forward:on on:focus|stopPropagation>

Example 1: forward click events only
Example 2: forward all events
Example 3: forward all events (incl. click), but not focus, unless the function tells you to do so.
Example 4: forward all events except focus

For example 3 one could argue, that focus should be forwarded, just as the other events, unless |stopPropagation is added. However i think as soon as you attach a handler explicitly, you can also dispatch the event manually if you want it to bubble up.

And of course it doesn't need to be stopPropagation, if people think this is too loaded then. How about noForward then?

@Tropix126
Copy link
Author

Tropix126 commented Aug 2, 2022

@ptrxyz Your fourth example would still forward focus.
This is interesting, but how would it consistently tie into forwarding other directives?

@wickning1
Copy link

I just came across this today and realized it seeks to solve essentially the same problem we’re discussing in sveltejs/svelte#5218

@ptrxyz
Copy link

ptrxyz commented Aug 3, 2022

@ptrxyz Your fourth example would still forward focus. This is interesting, but how would it consistently tie into forwarding other directives?

Example 4 was meant to show how it would be possible to exclude events from being forwarded by adding a directive like |stopPropagation. I chose this, was it fits quite nicely in my mind. It stops the event from bubbling.
I see this from the "child to parent" direction: the child component defines what should be forwarded to the parent, in example 4 this would be every event, however focus is stopped from propagating upwards, so it would not reach the parent.

For me, forward should not try to be ManBearPig and try to solve everything. It's a way to cope with a simple problem: what's the "catch all" event handler and how to expose those events to the outside world, so we don't have to do on:X, on:Y, on:Z etc..

For anything else, I would probably introduce a new keyword as it's a totally different cup of tea. Let me explain my thought there:
For me actions and transitions are a different from the 'catch-all-event'-thing since the direction is the other way around: the child should 'attach'/use the actions passed down from the parent and not hand them upwards.
Further the parent is a single node, the child just hands over a bunch of events and the parent knows how to deal with it. On the other hand, the child consists of 0..many nodes and we seem to want to pass down 0..many actions and transitions. This has implications on the syntax, as the child needs a way to address the passed down things and associate them with the correct nodes it consists of.

@ptrxyz
Copy link

ptrxyz commented Aug 3, 2022

Maybe an idea on how to solve the 2nd part. Look at this code:

<!-- Parent -->
<MyComponent
    on:focus={willNeverHappen}
    on:keyDown={handledInParent}
    on:click={handledInParent}
    use:actionForChild={action1_params, identifier="action1"}
    use:actionForChild={action2_params, identifier="action2"}
    transition:fade={animation1_params, identifier="anim1"}
    transition:slide={animation2_params, identifier="anim2"}
>

<!-- this is consistent with the syntax we have now for DOM nodes: -->
<button
    on:click={handledInParent}
    transition:fade={animation1_params, identifier="ThisWillNeverBeUsed"
>

Here, the identifier is an optional property to keep things consistent with the "normal" signature for transitions/actions but makes it possible for the child to identify them.

The child component can now access the action/transition handlers by their ID:

<script lang="js">
import { defaultAction } from "$actions/my_default_action'

function FunctionReturningAnAction(node) {
    return $$attachables?.use?.action1 || someDefaultAction || undefined
}
</script>

<!-- Child Component -->
<button
    forward:on
    attach:use={FunctionReturningAnAction}
    attach:transition={(this) => $$attachables.use.transition1 || undefined}
    on:click
    on:focus|stopPropagation
    on:keyup={handleInClient}
>

<button
    forward:on
    attach:transition={"transition2"}  // <-- should this be allowed?
    on:click
    on:focus|stopPropagation
    on:keyup={handleInClient}
>

I'd introduce a directive called attach that attaches a thing that's identified by the part after the colon ::

attach:use --> attach an action
attach:transition --> attach a transition
attach:whatevermightcome --> for future use

The directive expects a function that returns a matching object, so for attach:use, the function should return an action object for attach:transition, the function should return a transition object etc. If we want it fancy, one could make a case that it would be enough to simply put a string there which is then "auto-resolved" to the matching object from the $$attachables object, however for the sake of general usability, I wouldn't see that as a good idea.
To access the "attachables", one could introduce something similar to $$props, let's call it $$attachables, which is an object that holds all the attachables from the parent. In my example the child would receive an object like this:

$$attachables = {
    "use": {
        "action1": <callable here that represents the action `actionForChild({action1_params})`>,
        "action2": <callable here that represents the action `actionForChild({action2_params})`>
    },
    "transition": {
        "transition1" : <callable here that represents the transition `fade({animation1_params})`>,
        "transition2" : <callable here that represents the transition `slide({animation2_params})`>
    }
}

So tl;dr:

  • Syntax to pass down stuff to children: same as for DOM nodes, but with optional 'identifier'; instead of only allowing a single 'use:' and 'transition:' directive, we make sure the tuples <directive, id> are unique.
  • Passing down attachables: "attachables" are made available to the child in a $$attachable object
  • Syntax to access attachables in the child component: consistent to use:<action>={{params}} or transition:<transition>={{params}}, in this form attach:use=<function that returns "action({params})"> respectively attach:transition=<function that returns "transition({params})"> (==> general notation attach:<something>=<function that returns "something({params})">).

@lukaszpolowczyk
Copy link

lukaszpolowczyk commented Aug 4, 2022

I also wouldn't like the group syntax as it looks pretty alien to me.

@ptrxyz If this seems extraneous, I'll give another idea.
Also, someone may not like it, but it's hard, everyone won't get along:

Targeted Slots

Description and example - #41 (comment)

This is a suggestion for a proposal, on Declarative Actions.
The author of the proposal Declarative Actions liked it - #41 (comment)

I think Targeted Slots solves the same problems. Although the original targets were not identical.

This would solve the problems of Forward Directive and Declarative Actions all at once.

My example of "named forward groups" turned into Targeted Slots:

<Button>

  <svelte:element slot=name1
    on:click={_=>console.log("group-name1 click")}
    on:mouseup={_=>console.log("group-name1 mouseup")}
  >I'm a button with an action!</svelte:element>
  
  <svelte:element slot=name2
    on:click={_=>console.log("group-name2 click")}
    on:mouseup={_=>console.log("group-name2 mouseup")}
  >I'm a button with an action!</svelte:element>
  
</Button>
<!-- Button.svelte -->
<slot targeted:name1 this=button />

<slot targeted:name2 this=button />

This would have the result:

<button 
  on:click={_=>console.log("group-name1 click")}
  on:mouseup={_=>console.log("group-name1 mouseup")}
>I'm a button with an action!</button>

<button 
  on:click={_=>console.log("group-name2 click")}
  on:mouseup={_=>console.log("group-name2 mouseup")}
>I'm a button with an action!</button>

But it's also worth seeing an example from here - #41 (comment)

The point is that you can add tagName, and special and normal attributes inside the component if you want, and you can add when calling the component if you want.

I also call out the author of Forward Directive @Tropix126 - please evaluate.

DISCLAIMER: If one believes that "slots should be left alone", one could call it different. But it would literally work as upgraded slots.

@rgossiaux
Copy link

@ptrxyz Your fourth example would still forward focus. This is interesting, but how would it consistently tie into forwarding other directives?

Example 4 was meant to show how it would be possible to exclude events from being forwarded by adding a directive like |stopPropagation. I chose this, was it fits quite nicely in my mind. It stops the event from bubbling. I see this from the "child to parent" direction: the child component defines what should be forwarded to the parent, in example 4 this would be every event, however focus is stopped from propagating upwards, so it would not reach the parent.

For me, forward should not try to be ManBearPig and try to solve everything. It's a way to cope with a simple problem: what's the "catch all" event handler and how to expose those events to the outside world, so we don't have to do on:X, on:Y, on:Z etc..

For anything else, I would probably introduce a new keyword as it's a totally different cup of tea. Let me explain my thought there: For me actions and transitions are a different from the 'catch-all-event'-thing since the direction is the other way around: the child should 'attach'/use the actions passed down from the parent and not hand them upwards. Further the parent is a single node, the child just hands over a bunch of events and the parent knows how to deal with it. On the other hand, the child consists of 0..many nodes and we seem to want to pass down 0..many actions and transitions. This has implications on the syntax, as the child needs a way to address the passed down things and associate them with the correct nodes it consists of.

I think this is a conceptual misunderstanding. The direction of all directives is parent-to-child, not the other way around. The on: directive passes an event handler down to a child and is not any different conceptually from actions or transitions.

The problem that the forward directive is intended to (partially) solve is that Svelte has a bunch of syntactical magic in the form of directives that cannot be easily interacted with programatically. In a framework like React, everything is a prop & so the programmer has total control--and wrapping components is trivial. In Svelte, however, the directives are hidden from component authors, and a side effect is that non-trivial composition of components & elements becomes unreasonably difficult.

The idea here should be to create something which is flexible & future-proof enough to solve forwarding for both existing and future directives, but no more complicated than necessary. I also agree with @Tropix126 that getting something is better than nothing and if that means sacrificing exclusions then so be it, though I still believe it's a problem worth taking seriously.

@lukaszpolowczyk
Copy link

@rgossiaux With my proposal, there is no problem with exclusions.
See - #60 (comment)

Almost no new syntax. It's "just" a slight improvement of the slots.

@ptrxyz
Copy link

ptrxyz commented Aug 4, 2022

@ptrxyz If this seems extraneous, I'll give another idea.
Also, someone may not like it, but it's hard, everyone won't get along:

I guess it's less about liking something, more about how consistent it is within the rest of the framework. I am not aware of any other place where a group::-like notation is used so far, so I'd move away from it since it seems to be a construct that would only be invented to make a single feature work. I'd rather like to see a solution that feels 'svelte' and is more in line with the rest of the framework.

About targeted slots: you suggest to remove the requirement for this on svelte:element and instead take this from the slot the element is slotted into? Would <slot> then also allow for other attributes and directives such as class:, use:, bind:this, on: and transition: etc? So basically the resulting node would be what you get when you create an instance of this taken from slot, attach all the attributes and directives of <slot> and then add (& overwrite) the attributes and directives of <svelte:element ...>? I assume this would only work with HTML elements and this can not be a Svelte component, right?

What I mean is, if this:

<svelte:element slot="slot1"
    data-something="yes"
    on:click
    use:action
    class:bold={bold_condition}
>inner stuff</svelte:element>

plus:

<slot name="slot1" this="button"
    data-else="no"
    on:focus={function_defined_in_child}
    class:bold={bold_condition}
    use:action2>
    <span>This is rendered when slot1 is not provided</span>
</slot>

would result in this:

<button
    data-else="no"
    on:focus={function_defined_in_child}
    class:bold={bold_condition}
    data-something="yes"
    on:click
    use:action
    class:bold={bold_condition}
>inner stuff</button>

If I got this right, I assume it would work. I like the idea! It's also more widely usable, not just in the context discussed here.

Since we only need to transport two pieces of information then (the "what to merge" and "with what to merge this on"), one could make it a bit more crips maybe and not need the slot stuff while staying true to svelty syntax:

Something like this:

<!-- parent -->
<MyComponent>
    <svelte:element on:focus={console.log} update="native_button">inner stuff</svelte:element>
    <svelte:element on:click={console.log} update="custom_component">more stuff</svelte:element>
</MyComponent>
<!-- child component -->
<button svelte:name="native_button">
<CustomComponent svelte:name="custom_component">

would get you this:

<!-- renders to this -->
<button
    svelte:name='native'
    on:focus={console.log}
>inner stuff</button>

<CustomComponent
    svelte:name='custom'
    on:click={console.log}
>more stuff</CustomComponent>

update (or merge_with or target or a more suitable attribute name) can be used to specify what to merge "this element" with, svelte:name is used to internally name the component.

note: I was thinking if it would work to replace svelte:name with a simple bind:this={some_var} and make the component export some_var, so that the parent then could simply do <svelte:element update={mycomponent.some_var}>, but I assume that's probably not possible to do at compile time... ?

@ptrxyz
Copy link

ptrxyz commented Aug 4, 2022

<svelte:element slot=name1>...</svelte:element>
<slot targeted:name1 this=button />

Little note: Shouldn't the name be strings? targeted="name1" and <svelte:element slot="name1">

And another note: does it need to be called "targeted:name1". Can't it simply follow the syntax we already have and add a this: <slot name="somename" this="button">
Because basically in both cases you transport the same information of "this slot is named 'name1'". However in the other case, you add the extra information of "this slot is a target for something" which I am not sure if it's necessary. The slot doesn't need to "know" and the compiler can easily find that out by checking if there is a this.

So to make some rules for this approach:

  • If slot has a this, we consider it a targeted slot, this means <slot this='...'> should create a new node of type this and attach all attributes & directives of slot, as well as every <svelte:element> that would be slotted into that slot. This is effectively what <svelte:element> does but extended by the merging stuff.
  • If slot doesn't have a this, it's a normal slot and it should not accept any directives & attributes other than name. In addition it would be replaced by whatever is specified using slot="something" on the parent, if nothing is given default to what's enclosed by <slot>....</slot>

Seems a bit messy tbh. I like the shorter notation I suggested in my previous comment a bit more then. The rule set would be:

  • <svelte:element> can be used as a vehicle to transport directives & attributes which are applied to whatever is referenced by the update attribute.

@lukaszpolowczyk
Copy link

lukaszpolowczyk commented Aug 5, 2022

I guess it's less about liking something(...).

Ok.

About targeted slots: you suggest to(...).

Yes.

I assume this would only work with HTML elements and this can not be a Svelte component, right?

To meet the needs of Declarative Actions and Forward Directive, it is enough to work only with HTML elements.
But the development path is open.

If I got this right, I assume it would work. I like the idea!

I'm glad. :)

<!-- child component -->
<button svelte:name="native_button">.
<CustomComponent svelte:name="custom_component">.

Here is the problem.
The button element.
Sometimes someone wants to give a tagName in svelte:element (in parent) via this attribute, not in child.

So either slot or another special tag must be left.

In addition, the slot allows you to pass a property from child to parent, via let:val - that would be missing. I described in an UPDATE here - #41 (comment)
Worth reading.
I want Targeted Slot to have all (almost all?) slot functions + more(what is in Targeted Slot).

It doesn't have to be called slot etc, names can be found better.

note: I was thinking if it would work to replace svelte:name with a simple bind:this={some_var}.

This is rather out of the question. See Declarative Actions - there the most important thing is that bind:this can be used in the normal way.

Also consider that the normal <slot> can be used multiple times. I'd like to keep this too, as it also gives more possibilities for Targeted Slot.


Little note: Shouldn't the name be strings? targeted="name1" and <svelte:element slot="name1">.

This is a string. According to the syntax, the absence of quotation marks is still a string. Quotation marks are only necessary if the string has whitespace characters. But this is a matter of pure HTML.

And another note: does it need to be called "targeted:name1". Can't it simply follow the syntax we already have and add a this: .

As I wrote before, sometimes you want to add a tagName in <svelte:element this="div">. Then it wouldn't be possible.
Another thing is that slots give you the possibility to use multiple slots, with the same name (slots allow this - I've already written too), in the same child component. Then it would also not be possible.

So there must be targeted:name or something similar.

The third thing is that the <slot> passes parameters, and you have to pass them somehow in Targeted Slots. I also wrote about this in the UPDATE.

Summary:

  • Attribute/tag names can be changed if you find a better one. And not to confuse regular slots, with Targeted Slots.
  • I do not exclude the use of components as Targeted Slot.
  • I want Targeted Slots to take everything they can from regular slots.
  • I am very happy that already the second person liked it. I wonder what the people who develop the SvelteJS engine think about this and know how to implement it technically.

If @Tropix126 also says it's a good solution, and someone from the SvelteJS maintainers says it makes sense, then rfcs should be done.
Because for now I described it in the Forward Directive thread and in the Declarative Actions thread. It would be better to have it in one place.

@ptrxyz
Copy link

ptrxyz commented Aug 5, 2022

So, can you summarize how this would blend in with the topic of this RFC? I get how "beefing up" slots is a thing, but I wonder if it's also the best solution for the problems presented here.

As an example, how would it look when you have a third party component library and you want to capture focus, click and keyup events from a parent component. As of now, one would have to verbosely write down all the event listeners in the child component.

Could you for demonstration purposes rewrite this if your idea was implemented?

<script>
    // simple child component
    export let caption = "Button";
</script>

<button on:click on:focus on:keyup>{caption}</button>
<script>
    // parent component
   import Button from './button.svelte'
</script>

<Button on:click={console.log} caption="Hello!" />

REPL: https://svelte.dev/repl/eb2fb28276a442ff802fce2c9bc53fa1?version=3.49.0

@lukaszpolowczyk
Copy link

lukaszpolowczyk commented Aug 5, 2022

@ptrxyz This way:

<script>
    // simple child component
</script>

<slot targeted:button this="button" on:click on:focus on:keyup/>
<script>
    // parent component
   import Button from './button.svelte'
</script>

<Button>
  <svelte:element slot="button" on:click={console.log}>Hello!</svelte:element>
</Button>

And frankly, you can consider a slot without a name when there is only one slot. Because slots allow only one slot. But I'm not sure, because then it loses the advantages of passing attributes through svelte:element:

<script>
    // simple child component
</script>

<slot targeted:default this="button" on:click on:focus on:keyup/>
<script>
    // parent component
   import Button from './button.svelte'
</script>

<Button on:click={console.log}>Hello!</Button>

This is basically almost the same as forward:directive. But after using named slots, it gives you more possibilities. When you have named slots, you have something like exceptions, etc. (by using the names)

Unfortunately, it requires the use of the targeted:default attribute. Alternatively, it would not be <slot>, but e.g. <target>, and then it could be treated the same as a slot without a name, but with extra Targeted Slots functions.


I wonder what to do with the "default content" slots <slot>this default content</slot>.
Sometimes you want to set the content inside the child component.

With Targeted Slot you can do it like this:

<script>
    // simple child component
    const content = "Button";
</script>

<slot targeted:button={ {content} } this="button" on:click on:focus on:keyup/>
<script>
    // parent component
   import Button from './button.svelte'
</script>

<Button>
  <svelte:element slot="button" on:click={console.log} let:content>{content}</svelte:element>
</Button>

...BUT sometimes you want to put there not only text, but more HTML sub-elements. That can't be done.
This is a thing to think about.
If you would add the possibility of more complex content, then it certainly can no longer be called <slot>, but in a different way. Because that would already be an alternative action than <slot>.

In theory, something like:

<script>
    // simple child component
</script>

<target targeted:button this="button" on:click on:focus on:keyup>Button <span>someting</span></target>
<script>
    // parent component
   import Button from './button.svelte'
</script>

<Button>
  <svelte:element slot="button" on:click={console.log}></svelte:element>
</Button>

...Where Button <span>something</span> is not instead of svelte:element, but inside svelte:element. E.g. provided that svelte:element has no content.
That is, adding content to svelte:element, would overwrite the content from inside <target></target>.

This could also be done in other ways, but I'll let it go for now.

@lukaszpolowczyk
Copy link

@Tropix126 Check out my RFCS:
#68 - Targeted Slots - passing and mixing attributes and styles to component, access the slotted element from inside the component

It solves all problems and more.
There are examples.

I'm sure @ptrxyz will be interested.

@FeldrinH
Copy link

The proposal says:

  • There are some directives that could not be forwarded, most notably bind.

I'm not very familiar with Svelte internals so maybe I'm missing something. Why can't bind directives be forwarded?

@TheOnlyTails
Copy link

bind: is, by definition, acting on the whole component, not just the markup. That's why forwarding it means nothing. This proposal mostly applies to directives that cannot be used on components, like use:, style:, etc.

@Tropix126
Copy link
Author

Tropix126 commented Aug 17, 2022

I'm not very familiar with Svelte internals so maybe I'm missing something. Why can't bind directives be forwarded?

First and foremost, bind already has predefined behavior for components. bind:this on a component get's you the component's constructor, and bind:property will initiate a two-way bind between a component property and an existing variable. Both of these conflict with the concept of what forward brings to the table, and most functionality offered by bind can already be "forwarded" through property binding (or othewise probably shouldn't be forwarded at all).

For example, you can easily simulate a "bind:this" to a component's inner element by exporting a prop like so:

<script>
	export let someEl = null;
</script>

<div bind:this={someEl} />

then access it like

<Component bind:someEl={someVariable} />

A similar story applies to bind:group and bind:value, and many others as well. Just export a corresponding group or value prop and use property binding.

@lukaszpolowczyk
Copy link

@Tropix126 Why are you ignoring me?
I hacked some work into my RFC and would like you to review it.

@Tropix126
Copy link
Author

@lukaszpolowczyk I've been rather busy as of recent and haven't had time to look through it, although i've seen your comments. I'll have a look when I get the chance.

@adiguba
Copy link

adiguba commented Oct 27, 2022

Hello,

I haven't see this before sending an similar issue.

Just one remark : I think that filtering the forward type is ambigous. If a component forward to a node, all directive should be usable...

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

Successfully merging this pull request may close these issues.

None yet