-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Support two-way-data-binding with custom-input-elements #4838
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
Comments
The issue here is that Svelte can't tell at compile time which elements might be custom elements, and which might be regular elements. Allowing |
Thinking back on this, it might be fairly safe to distinguish custom elements depending on whether their name contains a hyphen. So theoretically that takes care of the question of how to handle the validation, but I still don't know what code ought to be generated for this. Do we assume the component class has the private Svelte APIs for handling component binding? |
I think a hyphen will be good! Another idea to distinguish custom elements: Like Vue ist does:
But svelte should not ignore this elements completely. This should more a registration of custom elements? Is it possible to validate the binding with HTMLElement.hasAttribute? |
I'm facing this same issue using Shoelace components, for example https://shoelace.style/components/input fig. 1 'value' is not a valid binding on elements svelte(invalid-binding) Shoelace does provide a fig. 2 |
I agree that it should be safe to distinguish custom elements based on the presence of a hyphen -- per the spec, custom element names must contain a hyphen, and there are only a few (mostly SVG) native elements that have hyphens in them. As for what code should be generated, here's what I have to do when using a custom input element that Svelte could potentially compile away (REPL): <script>
let inputVal;
function handleInput(e) {
inputVal = e.target.value;
}
</script>
<p>
Current value: {inputVal}
</p>
<custom-input on:input={handleInput}></custom-input> It's not a ton of boilerplate, but it can get annoying with multiple custom elements on the page. A binding would simplify this to <script>
let inputVal;
</script>
<p>
Current value: {inputVal}
</p>
<custom-input bind:value={inputVal}></custom-input> In this example, custom-input is a vanilla custom element, not one created with Svelte. I don't think we should expect the custom element to have the component APIs that come from being generated with Svelte. IMO, the majority of custom elements used in a Svelte app would come from external libraries. I'm not familiar with the inner workings of the Svelte compiler, but here's a simple approach I think could work:
There are some caveats here:
|
Shoelace components (and really any custom element containing an input inside a shadow root) will emit an This is important because Example: a custom element date picker that has an internal Similarly, not all custom elements make use of |
This is a good point -- I didn't think about this. I'm not familiar with enough custom element libraries to know how many emit a custom event for a text input and how many only rely on the native input event -- any idea which approach is more common?
Is the goal to make I'm hoping to find a solution that provides a good default case for handling custom elements, but there's always going to be more complex cases that require custom setup. |
It's a good practice to use Custom Events to differentiate an event emitted by a custom element, but it's by no means a requirement. Custom Events can even use the same name as a native event, e.g. I mostly see Custom Events in the wild, but the naming conventions tend to vary. In Shoelace, you have
I agree. I was mostly pointing out that Custom Elements can be (and are) designed in different ways compared to standard form controls. Because of that, it's really not possible to support binding for all custom elements without a more verbose syntax. The question of whether or not it's worth it depends on what that syntax looks like, I guess. My biggest concern here is settling on "custom elements that utilize a |
What's interesting is that we've mostly said goodbye with two-way prop bindings when Angular (in contrast to AngularJS) and Vue were released and did just fine for more than 5 years not having them and having to default to using events from components. When I saw two-way bindings are back with Svelte I had mixed emotions about it and Svelte tutorial itself warns from overusing it BUT nevertheless I quickly embraced them. So maybe we're just barking up the wrong tree here? |
Just tossing this out here as a possible syntax for fun, so don't take it too seriously. 😆 <sl-input name="name" value="foo" bind:value[sl-input]={name} />
<ion-input name="name" value="foo" bind:value[ionInput]={name} />
<fast-text-field name="name" value="name" bind:value[change]={name} /> With something like this, you could infer that it's a custom element by the presence of The only thing the custom element needs to commit to is that |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
I like the proposal from @claviska. (Also commenting to prevent staleness) |
Any progress on this? |
I too would love to see this (for Ionic). |
I am interested in this issue as well. I would love two-way binding with custom elements. |
@sidharthramesh @Pugio @rdytogokev please don't spam this thread. Svelte is a community-developed piece of software, that just recently got its first full-time maintainer. Aka instead of spamming, go ahead and make a pull request. |
@samuelstroschein I never meant for my comment to be perceived as spam. In fact, I am shocked that you called what I did "spamming". I would love to make a pull request and help out with this effort. But, I don't know how to help. Or even know where to start. I have spent hours trying to figure out a workaround for this issue on my own. I feel like I am spinning wheels and getting nowhere. While I understand basic JavaScript, I don't view myself as an expert. I consume JavaScript to work on projects, not invent it for platforms. I do appreciate the work you do, in fact, I depend on it. I want to respect you, not bring you down. So where could a user of the project go to have their voice be weighed in for consideration, if not here? |
@rdytogokev Sorry, didn't mean to be offensive. It's just that your comment provides no value which can't be expressed with a simple thumbs-up. I thought it's common etiquette by now. A simple thumbs-up won't notify everyone who is involved and "silently" express interest in (solving) this issue. While commenting "i would love to see this", "i am interested in this too" notifies everyone involved without any value.
Fair point and same problem I have. But how would I know that from one of X "I am interested" comments? Just comment that directly and for sure someone will help out :) |
This library provide a simple solution for this issue might help |
The solution for Ionic (and possibly all wc based UI frameworks) imho calls for svelte wrappers on each element. And these wrappers doing the capture of events to emit new values. Examples are already given here and easy to implement. While this may seem overkill there are other reasons wrapping web components in Svelte native elements - especially Ionic - type safety, type ahead support and tree shaking. So why not bridging this issue in the same go. Ionic-svelte package (I am maintainer) in near future will provide these - already present in experimental stage. Easily generated from the Stencil source. |
Just to update, Vue handles this with the following option in its Vite plugin. Would be really great if we could do this in Svelte too: plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: tag => tag.startsWith('sl-')
}
}
})
], |
This feature would definitely be helpful as configurable since there's also the case of |
Do you have link to an example? |
i'd love to contribute to this issue, but i need kind of a documentation that explains the internal processes of svelte in order to find the right place to experiment with. I dont think its really efficient to just read all the source code. But if its the only way to go i ll do it. Help is warmly appreciated. |
I’m looking into Shoelace currently (especially since FontAwesome´s team took ownership and raised some money for Similarly to @Raphael2b3 I would be interested to (try to) contribute. Should a contribution target Svelte 4 or Svelte 5 ? Is the codebase very different between the two ? |
In term of feature design, I see (at least) three possibilities:
customElementBindings: {
sl-input: ['input'], //this allows to do <sl-input bind:value={email}></sl-input>
sl-checkbox: ['checked'] //<sl-checkbox bind:checked={acceptTermsAndConditions}></sl-checkbox>
} One benefit would be that webcomponents libraries could easily provide code configuration snippets to paste in the svelte.config to have strongly typed custom bindings |
I think any solution that just assumes custom elements work like native dom elements for certain bindings is too brittle and paints us framework authors and custom element authors into a corner. A better approach might be to establish a convention: A binding on a custom element is a combination of a property and an event. i.e. when you do |
Thank you for the feedback. Could we imagine a more specific solution to give control on both the setter and event? (So no need for the custom element authors to strictly follow a convention) Something like that: <script>
let myDescription;
</script>
<my-custom-element custom-bind:text:onDescriptionChange={myDescription}></my-custom-element> We could have a shorthand syntax for components that expose an actual property (like it's the case for Shoelace's input.value) <script>
let myValue;
</script>
<sl-input custom-bind:value={myDescription}></sl-input> |
You can create an action that will catch events with a different name and create new ones with the correct. <script lang="ts">
import type { Action } from "svelte/action";
export let text: string;
type MyChangeEvent = CustomEvent<{ data: string }>;
const action: Action = (node) => {
function listener(event: MyChangeEvent) {
const new_event = new CustomEvent("textchange", { detail: event.detail.data });
node.dispatchEvent(new_event);
}
node.addEventListener("mychangeevent", listener as any);
return {
destroy: () => {
node.removeEventListener("mychangeevent", listener as any);
},
};
};
</script>
<my-custom-element {text} on:mychangeevent={(e) => (text = e.detail.data)}></my-custom-element>
<my-custom-element bind:text use:action></my-custom-element> You can make it built-in and with the ability to pass the name of the event (if the name differs) and the function (if the path to the value differs). <my-custom-element bind:text use:custom-change-event={"mychangeevent"}></my-custom-element>
<my-custom-element bind:text use:custom-change-event={(e) => e.detail.data}></my-custom-element>
<my-custom-element bind:text use:custom-change-event={{ name: "mychangeevent", value: (e) => e.detail.data }}
></my-custom-element> |
Custom (Input) Elements like ui5-input uses the propname value for the inputvalue attribute.
But svelte only permit value as propname when the element's name is input, textarea or select
svelte/src/compiler/compile/nodes/Element.ts
Line 532 in bdabd89
otherwise svelte throws an error that value is not a valid binding-element
pls have a look here:
https://codesandbox.io/s/divine-architecture-3qvlc?file=/App.svelte
The text was updated successfully, but these errors were encountered: