New bind for components (bind:this for the component's underlying element)
Opened this issue · 7 comments
Describe the problem
Disclaimer: I know bind:this works differently in components, so this would be a new type of bind request.
There are several open issues like #9299 that request some way to obtain a reference to the underlying element a component renders.
I know, I should not open issues for existing issues, but I figured nobody will look at old issues at this point.
Describe the proposed solution
Since we now have attachments up and running beautifully, we can now do this:
<script>
import Component from './Component.svelte';
let ref = $state();
</script>
<Component {@attach (node) => { ref = node; return () => ref = undefined }} />Not exactly what some of us have asked, but I bet good enough.
So my proposal: Since we have a compiler, let <Component bind:element={ref} /> be translated to <Component {@attach (node) => { ref = node; return () => ref = undefined }} /> by the compiler, effectively creating a binding directive for the underlying element.
I know, this depends on the component author to spread properties. Basic attachment rules.
UNLESS!, you guys really want to implement something that doesn't depend on attachments or the benevolence of component authors. Up to you.
Thanks!
Importance
nice to have
Maybe bind:node is shorter and therefore nicer.
Personally I think sharing references of elements of the component to the outside tree is an anti-pattern and thus shouldn't be easy. It violates incapsulation which is one of the key points of abstracting something as a component. Exporting a well defined API is architecturally a better approach which is how it works currently.
The main usage is enabling the use of libraries like floating-ui. I understand that encapsulation is a good thing, but history has demonstrated that this is in fact a need voiced by many of us. For as long as we want to consume and use imperative-styled libraries, we will need this.
Unless we did this in a completely different way that doesn't use bind:, this would be a breaking change, since node/element/whatever would be a reserved binding. Additionally, components aren't necessarily one element— what element would bind:node reference for this component?
<script>
let { name = $bindable('world'), count = $bindable(0) } = $props();
</script>
<h1>Hello {name}!</h1>
<input bind:value={name} />
<button onclick={() => count += 1}>
clicks: {count}
</button>what element would
bind:nodereference for this component?
As per the rules of attachments, the one where the properties as spread. Like I said, this is dependent on the benevolence of the component author (if the component spreads rest props at all), and it will vary from component to component. If you would like to read about automatic node selection (a completely different approach to this proposal), read the referenced issue.
I do agree that a new bind: name would be a breaking change. Perhaps then nodebind:variable, where variable is the variable we'll use to store the reference?
The main usage is enabling the use of libraries like
floating-ui. I understand that encapsulation is a good thing, but history has demonstrated that this is in fact a need voiced by many of us. For as long as we want to consume and use imperative-styled libraries, we will need this.
In these cases I think it's worth adding workarounds to user-space code than to the framework. You have document.querySelector so in practice it's possible to obtain any node in the tree if needed. Yeah, that would be dirty, but it looks like we agree that violating component boundaries is dirty anyway
I think this is a rare enough requirement that using an attachment to get the element is probably good enough™.
You also should not need to reset the local state, it will be garbage collected along with the component.
<Component {@attach n => ref = n} />
<!-- vs. something like -->
<Component bind-node:ref />