@lit/form - A new package for forms? #2489
Replies: 12 comments 18 replies
-
In order to demonstrate how this could work, I've created a really simple POC with some of those features which you can see here: https://stackblitz.com/edit/vitejs-vite-lnwwol?file=src/my-element.ts It's not tested or anything, just a "train on I've also tried implementing some sort of validation with both synchronous and asynchronous validations and it seems to be working quite fine but I haven't included it in this demo since I feel that would be far more a personal preference and I wanted to keep the main example clean. |
Beta Was this translation helpful? Give feedback.
-
FYI we have form support in Vaadin as part of |
Beta Was this translation helpful? Give feedback.
-
Another flavour: a-la Angular (Reactive Forms)With a different API (more verbose) we could have any type of nested forms without too much trouble. This is another version which would be heavily inspired by Angular Forms (and the new RFC for Strictly Typed Forms), and it'd be indeed more "complete". There would be 3 types: FormControl (single control), FormGroup (a record of controls) and FormArray (an array of controls or groups). Example: form = new FormGroup(this, {
user: new FormGroup(this, {
name: new FormControl(this, { defaultValue: 'Michele' }),
surname: new FormControl(this, { defaultValue: 'Stieven' }),
gender: new FormControl<'M' | 'F'>(this, { defaultValue: 'M' }),
}),
agreement: new FormControl(this, { defaultValue: false }),
phones: new FormArray<FormControl<string>>(this),
addresses: new FormArray<FormGroup<{
street: FormControl<string>
nr: FormControl<string>
}>>(this)
}); Which could be made easier to write with a helper, a FormBuilder (again, a-la Angular): fb = new FormBuilder(this);
form = this.fb.group({
user: this.fb.group({
name: this.fb.control('Michele'),
surname: this.fb.control('Stieven'),
gender: this.fb.control<'M' | 'F'>('M'),
}),
agreement: this.fb.control(false),
phones: this.fb.array<FormControl<string>>(),
addresses: this.fb.array<FormGroup<{
street: FormControl<string>
nr: FormControl<string>
}>>()
}); Typings would be inferred by the structure provided by the user, in this case we'd have: // Group type
FormGroup<{
user: FormGroup<{
name: FormControl<string>;
surname: FormControl<string>;
gender: FormControl<"M" | "F">;
}>;
agreement: FormControl<boolean>;
phones: FormArray<FormControl<string>>;
addresses: FormArray<FormGroup<{
street: FormControl<string>
nr: FormControl<string>
}>>;
}>
// Value type, auto-generated
const value: {
user: {
name: string;
surname: string;
gender: "M" | "F";
}>;
agreement: boolean;
phones: Array<string>;
addresses: Array<{
street: string;
nr: string;
}>;
} = this.form.value; Bindings would also be done with an Element Directive, with full type-safety: html`
<!-- Bind to a standalone control -->
<input type="text" ${this.control.bind()}>
<!-- Bind to a control of a group -->
<input type="text" ${this.form.bind('agreement')}>
<!-- Nested groups -->
<input type="text" ${this.form.bind('user.name')}>
<!-- Array bindings -->
${this.form.get('phones').controls.map(c => html`
<input type="text" ${c.bind()}>
`)}
<!-- Arrays of groups -->
${this.form.get('addresses').controls.map(c => html`
<input type="text" ${c.bind('street')}>
<input type="text" ${c.bind('nr')}>
`)}
` Each one of these 3 types would implement an interface AbstractControl {
value: any;
reset(key?: any): void;
set(value: any): void;
enable(key?: any): void;
disable(key?: any): void;
valueChanges(): Observable<any>;
disabledChanges(): Observable<boolean>;
isDirty(): boolean;
isTouched(): boolean;
isBlurred(): boolean;
isDisabled(): boolean;
} With additions: FormGroup:
FormArray:
Doable?Yes: I've got another POC ready if you're curious (not touching Validation yet, not tested or anything, you may find it broken cause I'm playing with it :P), so it's definitely doable. In my POC I got this working with approximately 700 lines of code. |
Beta Was this translation helpful? Give feedback.
-
@UserGalileo I just started working on something similar last night. I would be very interested in making a library that handles this well happen. My version works more like Formik: https://formik.org/docs/overview#the-gist The idea being all validation can be handled by yup: https://github.com/jquense/yup My POC is very early, so nothing to show yet other than some dirty code :) |
Beta Was this translation helpful? Give feedback.
-
we have a similar implementation that's biased towards our ORM and provides some shortcuts. How do you propose using custom elements as fields? We provide an interface which guarantees that each field throws certain value changed events. In this case only native browser elements would be supported? |
Beta Was this translation helpful? Give feedback.
-
After some time playing around with code, I think I've come to the conclusion that the 2 solutions are fundamentally divergent and that the first one would be much more in the spirit of Lit, while the second one (a-la Angular) would be suitable for a 3rd party library. The first solution assumes a flat structure: this is not only more simple but to me it seems more pointed towards the standards for forms. In pure HTML, if I'm not mistaken, there's no way to send a nested value when you submit a form. So sending something like this would not be possible: {
user: {
name: 'mario',
surname: 'rossi',
}
} Instead, it should be sent like this: <input type="text" name="user.name" >
<input type="text" name="user.surname" > {
"user.name": "mario",
"user.surname": "rossi"
} In practice, it's the template names that count, not the model. If I'm correct, this means that the first solution would be the only one to support Progressive Enhancement if we ever have some custom Having a nested structure would imply using JavaScript to send whatever we want, at that point we're free but this introduces opinions. Examples about disabled fields:
This is why I think there should be an official package, surely the Lit team could make decisions which are as close as possible to how the browser behaves (or will behave in the future), there are things a lot of us don't even know about. The second solution however, being a more opinionated one and assuming an ajax submission, would be free to diverge from standards. Any thoughts? :) |
Beta Was this translation helpful? Give feedback.
-
Good to see people are exploring this! I fully agree - as a newcomer this is one of the first friction points I hit when making an app with lit. I'm not experienced with dev myself, and I'm biased towards the needs of boring line-of-business apps that are very form-heavy because that's mostly what I work on. I'm comfortable with a library that's on the more "fully featured" end of the scale for solving this problem, purely because it's where so much time is spent on the sort of projects I work on. The PoCs you've shared above look like a really nice way to achieve this - think I'd lean toward the more Angular-style approach but that may be familiarity bias. Re. nesting - appreciate the arguments above and I don't know anything about emerging standards - however the ability to nest if needed might make it easier to fit the shape of the submission to match existing APIs for some use cases? |
Beta Was this translation helpful? Give feedback.
-
I forgot to reply here ;) Well I prefer the Angular style as well, because it allows for typings. I have a prototype similar to @UserGalileo, but it is very specific to our existing internal component library (for example every component already has some validation logic builtin like My main goal was to handle initial values, reset and validation. A couple things I have learnt:
So I settled with always providing initial values when initializing a control.
I hope I didn't forget anything, but that's what I have learnt so far. I would love to drive the discussion forward, but at the same time I fear the idea of an official |
Beta Was this translation helpful? Give feedback.
-
Since we have a new RFC Process does someone here want to draft an RFC for a If we need some higher-bandwidth discussion to get consensus on an API style before the RFC, we could host a call just on forms. |
Beta Was this translation helpful? Give feedback.
-
I didn't found @lit-labs/forms package in npm ? does you have published it for user? |
Beta Was this translation helpful? Give feedback.
-
Hi all. How can we know the status of @lit-labs/forms? How to participate? How can we help? |
Beta Was this translation helpful? Give feedback.
-
Here's an rough attempt at an adapter for https://final-form.org/ with a reactive controller and directive: I'm curious if people would find something like this useful. It's not meant to say we don't need a separate forms package but the scope of the package as proposed in the RFC is pretty huge and using an existing library could be a low cost way to provide something useful while discussions happen around the RFC. |
Beta Was this translation helpful? Give feedback.
-
Hi everyone!
I love what's being done with Lit Labs: Router, SSR, Motion... All of these packages aim to enrich the Developer Experience by providing tools to build not only reusable components and design systems, but also fully featured applications with Lit. And I think it's great that the team is investigating on what would be needed for a framework: #2462.
There's really one thing that I miss from other frameworks/libraries: utilities for forms. I'm used to work with Angular which has a great Forms API, but I think solutions like React Hook Form would be more relevant since it has a smaller API, I think that would be ideal for Lit.
3rd party solutions already exist or are being developed, one example for all is
@vaadin/form
from their Fusion framework (renamed to Hilla, link). I'm not 100% sold on their implementation but it's definitely a useful and interesting package, and perhaps the most mature I've found.Personally, I think a 1st party library in the spirit of Lit (lightweight, with best practices, progressive enhancement...) would be awesome and would make adopting Lit for complex applications significantly easier and more appealing for many.
List of features
This is a partial summary of features that I'd love/expect:
input
vsblur
)<form>
substitute (or directive?) to intercept submissions and handle async tasks withfetch
(could allow Progressive Enhancement similar to Remix)setValidators(...)
)yup
)a11y
best practices (eg. automatic HTML attributes forrequired
,minlength
...)And I'm sure there'd be more, we could take inspiration by Angular Forms, React Hook Form, Formik, etc...
Examples
In my mind I imagine something like this (which is something I'm working on in my spare time as a POC):
A Controller for a group of fields with some configuration. Types would be completely optional, but to be preferred.
This controller would contain a factory for a Directive to be used on the template, which would work with every native HTML control and eventually custom controls:
I'm not sure I would allow nested properties (eg.
bind('address.street')
) because the idea is to support custom controls and their value could be of any non-primitive type, so it'd be unclear whetheraddress.street
is supposed to be used as a field key or not. But I'm brainstorming.The value inside the Controller would be the Single Source of Truth for the form, and the Directive would 2-way bind to that automatically. The Controller would keep informations and methods about the form, examples:
And eventually, a derived Controller (or the same one, de gustibus) for Validations. I would keep validators on the Controller side (not the Directive) because the model would be the source of truth, not the controls in the template (which may disappear temporarily or there may be multiple controls for the same field).
I think overall it'd be a fairly simple API but a really helpful one. Would it be interesting to add something like this to Lit as a separate package? Obviously mine is just an example so don't take my API seriously. I'm sure there'd be a lot that the Lit team could take care of with regards to best practices and a11y that I don't even think of.
Beta Was this translation helpful? Give feedback.
All reactions