From 3a3df03360bade9b9b0ad5304343a48b9527d317 Mon Sep 17 00:00:00 2001 From: Christian24 Date: Mon, 29 Aug 2022 21:08:35 +0000 Subject: [PATCH 1/6] initial commit --- rfcs/0000-rfc-forms.md | 111 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 rfcs/0000-rfc-forms.md diff --git a/rfcs/0000-rfc-forms.md b/rfcs/0000-rfc-forms.md new file mode 100644 index 0000000..c3b36de --- /dev/null +++ b/rfcs/0000-rfc-forms.md @@ -0,0 +1,111 @@ +--- +Status: Active +Champions: christian24 +PR: {{ update_with_pr_number }} +--- + +# @lit-labs/forms + +Add advanced form validation for web components. This is a followup to the discussion in the lit repository: https://github.com/lit/lit/discussions/2489 + +## Objective + +Provide an easy to use dev experience for validating forms with web components in them. + +### Goals +- Provide a common API, so developers can quickly start using the @lit-labs/forms package without changing their existing controls. +- Allow cross field validation +- Allow async validation +- get and set dirty, touched, valid states for individual controls +- allow for configuration (e. g. change on `blur` or `change`) +- It should be typed +- It should be possible to set initial values for individual controls. +- subscribe to value changes and update dependant controls accordinly. +- validate all controls of a form at the same time. +- Allow for dynamic forms (e. g. arrays of controls etc.) + +### Non-Goals +- The goal is not to provide a set of Validators + +## Motivation + +In enterprise applications forms with complex validations often play a huge part. In React and Angular a lot of easy to use solutions exist (Formik, React Hook Form and Angular Reactive Forms). With web components several alternatives exist as well (@vaadin/form or https://github.com/realiarthur/lite-form), but none have really gained traction. It would be awesome to have lightweight standard in the Lit community or hopefully even beyond. + +## Detailed Design + +This is heavily influenced by Angular Reactive Forms. A prototype has been implemented by @UserGalileo here: https://github.com/UserGalileo/lit-reactive-forms + +### General Overview +In general there are three types of form elements: + +#### Form group: +A form group is the most high level concept. It is a group of form controls. It can also hold form arrays. It combines values and state into single objects. Form groups can be nested. It allows to run validation on all children. State of the children can also be set. + +#### Form control: +This is the most low level concept. This model holds the value and state of an individual control. Changes to the model should be reflected in the rendered control. Validation can be run on this. + +#### Form array: +This allows dynamic lists to be part of a form. They act as aggregators similar to a form group, but new children can be added or deleted at runtime. Form array children can either be form controls or form groups. (Form array would also be possible as a child, but not sure this would make sense, it might be a quick win API wise though.) + +All of these should have listeners for changes to value and status, so that users can patch the values or status of dependant controls when necessary. + +### Validation + +Validation should allow for async validators. All validators can be async by default Validators are pure functions. There are two types of validators: + +#### Form control level validator + +This would be just a function declared as `(value) => string`. `value` being the current value of the control and the returned string being the potential error message describing the validation error. If an empty string is returned the control is valid. This is inspired by https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement/setCustomValidity + +Validators should only be run for controls that are connected. + +#### Form group level validator + +This validator runs on a form group and gets passed all values of the children and returns an object of error messages for the children. + +### How to define a form + +#### Defining the model +The model for a form can be created similar to Angular Reactive Forms through a `FormBuilder` class. Every control must be initialized with an initial value. + + + +## Implementation Considerations + +### Implementation Plan + +Is there anything important to note about implementation plan? Can it be done in a single PR or will it need to be staged out across several? + +### Backward Compatibility + +As this is a new library, it should not. + +### Testing Plan + +How will this proposal be tested? Are unit tests sufficient, or do we need integration tests? Is any unique testing infrastructure required? + +### Performance and Code Size Impact + +What impact will this proposal have on performance and code size? What benchmarks should we create to evaluate the proposal? + +### Interoperability + +Is this proposal for a feature that could be interoperable across web components not written in Lit? Does it create a tight coupling between components written in Lit? Could it be a [Web Components Community Group](https://github.com/w3c/webcomponents-cg) [Community Protocol](https://github.com/webcomponents-cg/community-protocols)? + +### Security Impact + +What impact will this proposal have on security? Does the proposal require a security review? (We have a security team available for reviews) + +We especially care about the handling of untrusted user input by library code so that we contnue to prevent XSS vectors. + +### Documentation Plan + +Do we need to create or update any documentation to complete this proposal? Does related documentation have a clear home in our docs outline? What playground examples or tutorials should be created? + +## Downsides + +Many proposals involve trade-offs. What are they for this proposal and what are the downsides of this approach? + +## Alternatives + +What alternatives were considered and rejected? Why? From fc1c16d50acc23685dfe904d4e5ab63896b48f09 Mon Sep 17 00:00:00 2001 From: Christian24 Date: Tue, 30 Aug 2022 18:49:04 +0000 Subject: [PATCH 2/6] Initial draft --- rfcs/0000-rfc-forms.md | 57 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/rfcs/0000-rfc-forms.md b/rfcs/0000-rfc-forms.md index c3b36de..1f20c3f 100644 --- a/rfcs/0000-rfc-forms.md +++ b/rfcs/0000-rfc-forms.md @@ -66,11 +66,50 @@ This validator runs on a form group and gets passed all values of the children a ### How to define a form #### Defining the model -The model for a form can be created similar to Angular Reactive Forms through a `FormBuilder` class. Every control must be initialized with an initial value. - - +The model for a form can be created similar to Angular Reactive Forms through a `FormBuilder` class. Every control must be initialized with an initial value. The following example is taken from @UserGalilelo's prototype: + +````js +fb = new FormBuilder(this); + +form = this.fb.group({ + user: this.fb.group({ + name: this.fb.control(''), + surname: this.fb.control(''), + }), + consent: this.fb.control(false) +}, { + validators: [], + asyncValidators: [], +}); + + +// Binding (dotted syntax for nested FormGroups) +render() { + const { bind } = this.form; + + return html` + + + + ` +} +```` + +The model and the elements are connected by use of a directive. There is an additional directive which simplifies rendering a form array, similar to `repeat` which takes an instance of form array as a parameter and as a second parameter takes a render function, which the directive uses to render an individual item of the form array. + +The `bind` directive subscribes to changes to the element via event listeners and updates the model accordingly. There is an abstraction called an accessor, which can be defined for web components specifying details like +- change event +- how to trigger an element's builtin validation (if exists) +- how to get and set the element's current value +- how to set an error message on the element (if available) +- how to change the element's state + +Similarly, if the model is updated, the directive will update the element. ## Implementation Considerations +So far the only feature that really is Lit-dependant is the use of directives. Maybe directives can be provided, but potentially an API along the lines of `formControl.registerElement(htmlElement)` can be provided as a substitute to support none Lit environments? + +The prototype uses `rxjs` heavily. Is an external dependency a problem / show stopper? ### Implementation Plan @@ -90,22 +129,20 @@ What impact will this proposal have on performance and code size? What benchmark ### Interoperability -Is this proposal for a feature that could be interoperable across web components not written in Lit? Does it create a tight coupling between components written in Lit? Could it be a [Web Components Community Group](https://github.com/w3c/webcomponents-cg) [Community Protocol](https://github.com/webcomponents-cg/community-protocols)? +This could potentially be implemented to be usable in any web component environment. ### Security Impact -What impact will this proposal have on security? Does the proposal require a security review? (We have a security team available for reviews) - -We especially care about the handling of untrusted user input by library code so that we contnue to prevent XSS vectors. +Since we only read and write value and states to existing element's this shouldn't have much of a security implication. ### Documentation Plan -Do we need to create or update any documentation to complete this proposal? Does related documentation have a clear home in our docs outline? What playground examples or tutorials should be created? +A pretty good README should be part of the npm package. ## Downsides -Many proposals involve trade-offs. What are they for this proposal and what are the downsides of this approach? +This approach is quite oppinionated. It doesn't neccessarily live close to the web standards. It more tries to improve developer experience in enterprise contexts. ## Alternatives -What alternatives were considered and rejected? Why? +An approach similar to Formik, where the model is defined in the template and the validation is a simple function (that's not part of the model) was considered, but was after an initial proof of concept abandoned due to complexities involving nesting of controls, grouping controls together or having a dynamic list of form controls. From ae1492eecbe64fcde4a466911dd61544af56cd9d Mon Sep 17 00:00:00 2001 From: Christian24 Date: Wed, 31 Aug 2022 16:26:13 +0000 Subject: [PATCH 3/6] Added some sections based on review feedback --- rfcs/0000-rfc-forms.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/rfcs/0000-rfc-forms.md b/rfcs/0000-rfc-forms.md index 1f20c3f..ec182d5 100644 --- a/rfcs/0000-rfc-forms.md +++ b/rfcs/0000-rfc-forms.md @@ -16,16 +16,19 @@ Provide an easy to use dev experience for validating forms with web components i - Provide a common API, so developers can quickly start using the @lit-labs/forms package without changing their existing controls. - Allow cross field validation - Allow async validation -- get and set dirty, touched, valid states for individual controls -- allow for configuration (e. g. change on `blur` or `change`) -- It should be typed +- Get and change state for individual controls +- Allow for configuration (e. g. when should changes be reflected in the model (when the element looses focus vs when the value of the element changes.)) +- The API should be type safe. - It should be possible to set initial values for individual controls. -- subscribe to value changes and update dependant controls accordinly. -- validate all controls of a form at the same time. +- Subscribe to value changes of a control +- Subscribe to state changes of a control. +- Update the value of a control. +- Organize controls into groups +- Validate or update groups - Allow for dynamic forms (e. g. arrays of controls etc.) ### Non-Goals -- The goal is not to provide a set of Validators +- The goal is not to provide a set of Validators. Validators should just be pure functions. They can be shared across projects, but it is not a goal to provide a library of common validators with this project. ## Motivation @@ -68,7 +71,7 @@ This validator runs on a form group and gets passed all values of the children a #### Defining the model The model for a form can be created similar to Angular Reactive Forms through a `FormBuilder` class. Every control must be initialized with an initial value. The following example is taken from @UserGalilelo's prototype: -````js +```js fb = new FormBuilder(this); form = this.fb.group({ @@ -93,7 +96,7 @@ render() { ` } -```` +``` The model and the elements are connected by use of a directive. There is an additional directive which simplifies rendering a form array, similar to `repeat` which takes an instance of form array as a parameter and as a second parameter takes a render function, which the directive uses to render an individual item of the form array. From dc07241874b856123081f0f48aaa0d68810eaf12 Mon Sep 17 00:00:00 2001 From: Christian24 Date: Thu, 1 Sep 2022 05:07:25 +0000 Subject: [PATCH 4/6] Added support for serialization and multi step wizards. --- rfcs/0000-rfc-forms.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rfcs/0000-rfc-forms.md b/rfcs/0000-rfc-forms.md index ec182d5..30bfcbb 100644 --- a/rfcs/0000-rfc-forms.md +++ b/rfcs/0000-rfc-forms.md @@ -25,7 +25,8 @@ Provide an easy to use dev experience for validating forms with web components i - Update the value of a control. - Organize controls into groups - Validate or update groups -- Allow for dynamic forms (e. g. arrays of controls etc.) +- Form states should be serializable and can be used to update a form. +- Allow for dynamic forms (e. g. arrays of controls or multi step wizards etc.) ### Non-Goals - The goal is not to provide a set of Validators. Validators should just be pure functions. They can be shared across projects, but it is not a goal to provide a library of common validators with this project. From 8d88f029517e21b25882fba6a5a3d1c46f2f8d0c Mon Sep 17 00:00:00 2001 From: Christian Siebmanns Date: Sat, 4 Nov 2023 21:35:13 +0100 Subject: [PATCH 5/6] Update RFC to speak about wrappers --- rfcs/0000-rfc-forms.md | 105 +++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 35 deletions(-) diff --git a/rfcs/0000-rfc-forms.md b/rfcs/0000-rfc-forms.md index 30bfcbb..c426cc7 100644 --- a/rfcs/0000-rfc-forms.md +++ b/rfcs/0000-rfc-forms.md @@ -10,14 +10,14 @@ Add advanced form validation for web components. This is a followup to the discu ## Objective -Provide an easy to use dev experience for validating forms with web components in them. +Provide an easy-to-use dev experience for validating forms with web components in them. ### Goals - Provide a common API, so developers can quickly start using the @lit-labs/forms package without changing their existing controls. - Allow cross field validation - Allow async validation - Get and change state for individual controls -- Allow for configuration (e. g. when should changes be reflected in the model (when the element looses focus vs when the value of the element changes.)) +- Allow for configuration (e.g. when should changes be reflected in the model (when the element looses focus vs when the value of the element changes.)) - The API should be type safe. - It should be possible to set initial values for individual controls. - Subscribe to value changes of a control @@ -30,6 +30,7 @@ Provide an easy to use dev experience for validating forms with web components i ### Non-Goals - The goal is not to provide a set of Validators. Validators should just be pure functions. They can be shared across projects, but it is not a goal to provide a library of common validators with this project. +- Build an entire form solution from scratch. The Lit team has voiced no interest in doing this. ## Motivation @@ -37,16 +38,16 @@ In enterprise applications forms with complex validations often play a huge part ## Detailed Design -This is heavily influenced by Angular Reactive Forms. A prototype has been implemented by @UserGalileo here: https://github.com/UserGalileo/lit-reactive-forms +The idea is to wrap an existing form library. -### General Overview +### General Overview for forms In general there are three types of form elements: #### Form group: -A form group is the most high level concept. It is a group of form controls. It can also hold form arrays. It combines values and state into single objects. Form groups can be nested. It allows to run validation on all children. State of the children can also be set. +A form group is the highest level concept. It is a group of form controls. It can also hold form arrays. It combines values and state into single objects. Form groups can be nested. It allows to run validation on all children. State of the children can also be set. #### Form control: -This is the most low level concept. This model holds the value and state of an individual control. Changes to the model should be reflected in the rendered control. Validation can be run on this. +This is the lowest level concept. This model holds the value and state of an individual control. Changes to the model should be reflected in the rendered control. Validation can be run on this. #### Form array: This allows dynamic lists to be part of a form. They act as aggregators similar to a form group, but new children can be added or deleted at runtime. Form array children can either be form controls or form groups. (Form array would also be possible as a child, but not sure this would make sense, it might be a quick win API wise though.) @@ -69,37 +70,59 @@ This validator runs on a form group and gets passed all values of the children a ### How to define a form -#### Defining the model -The model for a form can be created similar to Angular Reactive Forms through a `FormBuilder` class. Every control must be initialized with an initial value. The following example is taken from @UserGalilelo's prototype: +There are multiple competing API suggestions: +#### Separation of template and form configuration +```ts +const form = new FormApi(...); -```js -fb = new FormBuilder(this); - -form = this.fb.group({ - user: this.fb.group({ - name: this.fb.control(''), - surname: this.fb.control(''), - }), - consent: this.fb.control(false) -}, { - validators: [], - asyncValidators: [], +// Validation logic +const fieldA = new FieldApi({ + form, + ...whateverElse +}); + +const fieldB = new FieldApi({ + form, + ...whateverElse }); +return html` +
form.handleSubmit()}> + + + + + + +
+`; +``` -// Binding (dotted syntax for nested FormGroups) -render() { - const { bind } = this.form; - - return html` - - - - ` -} +#### Field configuration inside the template +This is closer to how other Tanstack Form adapters currently work: +```ts +const form = new FormApi(...); + +return html` +
form.handleSubmit())}> + + ${form.Field({onChange: z.string().min(1), render: (fieldA) => html` + + +`})} +
+`; ``` -The model and the elements are connected by use of a directive. There is an additional directive which simplifies rendering a form array, similar to `repeat` which takes an instance of form array as a parameter and as a second parameter takes a render function, which the directive uses to render an individual item of the form array. +### bind directive +To make syncing form elements and the form model easier, the idea is to create a `bind`directive that automatically connects the two: + +```js +// field is the instance of the form field + +``` + +The model and the elements are connected by use of the directive. The `bind` directive subscribes to changes to the element via event listeners and updates the model accordingly. There is an abstraction called an accessor, which can be defined for web components specifying details like - change event @@ -110,10 +133,21 @@ The `bind` directive subscribes to changes to the element via event listeners an Similarly, if the model is updated, the directive will update the element. +An additional idea is that the directive could be cross-form-wrapper: An interface how to represent a model could be defined that works across multiple form-integrations. + +### Potential field array directive +Potentially an additional directive could be created which simplifies rendering a form array, similar to `repeat` directive. It would take the field instance that is an array as a parameter. As a second parameter it would take a render function, which the directive uses to render an individual item of the form array. ## Implementation Considerations -So far the only feature that really is Lit-dependant is the use of directives. Maybe directives can be provided, but potentially an API along the lines of `formControl.registerElement(htmlElement)` can be provided as a substitute to support none Lit environments? +A form library to base the wrapper on needs to be decided. So far POCs for `final-form` and `@tanstack/form-core` have been created. + +With `final-form`a lack of type safety of the defined form was apparent. + +`@tanstack/form-core` is still in heavy development with a prospective version 1.0 release in December or early next year. + +If the API `Field configuration inside the template` is chosen the wrapper could be an official Tanstack Form adapter that would live in the Tanstack Form repo. If the other API is chosen they adapter would have to live in the `Lit`-repo. + +Other form solutions can be suggested in this RFC. -The prototype uses `rxjs` heavily. Is an external dependency a problem / show stopper? ### Implementation Plan @@ -145,8 +179,9 @@ A pretty good README should be part of the npm package. ## Downsides -This approach is quite oppinionated. It doesn't neccessarily live close to the web standards. It more tries to improve developer experience in enterprise contexts. +A wrapper probably does mean that some compromises about implementation or developer experience need to be made. ## Alternatives -An approach similar to Formik, where the model is defined in the template and the validation is a simple function (that's not part of the model) was considered, but was after an initial proof of concept abandoned due to complexities involving nesting of controls, grouping controls together or having a dynamic list of form controls. +A previous version of this RFC explored building an entirely lit-focused library from scratch. Due to missing interest and expertise this was ultimately abandoned. + From 9482e1b4fdcc7ba90e6d483bc76958271f81eca8 Mon Sep 17 00:00:00 2001 From: Christian Siebmanns Date: Sat, 4 Nov 2023 21:37:52 +0100 Subject: [PATCH 6/6] Update RFC to speak about wrappers --- rfcs/0000-rfc-forms.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/0000-rfc-forms.md b/rfcs/0000-rfc-forms.md index c426cc7..0414b7b 100644 --- a/rfcs/0000-rfc-forms.md +++ b/rfcs/0000-rfc-forms.md @@ -1,7 +1,7 @@ --- Status: Active Champions: christian24 -PR: {{ update_with_pr_number }} +PR: #8 --- # @lit-labs/forms