@vydra-js/forms
v0.0.1
Published
Reactive forms system for Web Components with validation, form groups, and automatic binding to DOM elements.
Downloads
15
Readme
@vydra-js/forms
Reactive forms system for Web Components with validation, form groups, and automatic binding to DOM elements.
Installation
npm install @vydra-js/formsQuick Start
import { FormControl, FormGroup, required, email } from '@vydra-js/forms';
// Create form controls
const nameControl = new FormControl('', [required]);
const emailControl = new FormControl('', [email]);
// Create form group
const form = new FormGroup({
name: nameControl,
email: emailControl,
});
if (form.valid) {
console.log(form.value); // { name: "...", email: "..." }
}API
FormControl
Represents a single form field.
new FormControl(value: T, validators?: ValidatorFn[])Methods
getValue(): T
Returns the current value.
setValue(value: T): void
Sets a new value.
patchValue(value: Partial<T>): void
Patches partial value.
reset(value?: T): void
Resets to initial value.
validate(): ValidationResult
Runs all validators.
hasError(error: string): boolean
Checks for specific error.
getError(error: string): unknown
Gets error details.
enabled: boolean
Whether control is enabled.
disabled: boolean
Whether control is disabled.
valid: boolean
Whether control passes validation.
invalid: boolean
Whether control has errors.
pristine: boolean
Whether value hasn't changed.
dirty: boolean
Whether value has changed.
touched: boolean
Whether control has been focused/blurred.
FormGroup
Groups multiple controls together.
new FormGroup(controls: {[key: string]: FormControl}, validators?: ValidatorFn[])Methods
getControl(name: string): FormControl
Gets a control by name.
getAllControls(): FormControl[]
Returns all controls.
value: object
Returns all values as object.
reset(): void
Resets all controls.
valid: boolean
Whether entire group is valid.
Validator Functions
Built-in validators:
// Required
required: ValidatorFn
// Email
email: ValidatorFn
// Min length
minLength(n: number): ValidatorFn
// Max length
maxLength(n: number): ValidatorFn
// Pattern (regex)
pattern(regex: RegExp, errorKey?: string): ValidatorFnCustom Validator
const customValidator: ValidatorFn = (control: FormControl) => {
if (control.getValue() === 'forbidden') {
return { forbidden: true };
}
return null;
};BindForm Directive
Automatically connects FormGroup to DOM elements.
import { bindForm } from '@vydra-js/forms';
// In template
html`<input type="text" ${bindForm(form, 'name')} />`;This directive:
- Binds
valueproperty to control - Listens to
inputevents - Updates control on change
- Handles validation display
Concepts
Reactive Forms
Vydra forms follow reactive patterns:
- Immutable operations:
setValuevspatchValue - Push-based: Value changes via events
- Validation: Synchronous and async
Validation Flow
- User interacts with input
inputevent fires- Control updates value
- Validator functions run
valid/invalidstates update- UI reflects state
Usage Examples
Login Form
import { FormControl, FormGroup, required, email, pattern } from '@vydra-js/forms';
const loginForm = new FormGroup({
email: new FormControl('', [required, email]),
password: new FormControl('', [required, minLength(8)]),
});
const emailControl = loginForm.getControl('email');
const passwordControl = loginForm.getControl('password');
// Check form validity
if (loginForm.valid) {
const data = loginForm.value;
// Submit to server
}FormControl with Custom Validator
import { FormControl, ValidatorFn } from '@vydra-js/forms';
const strongPassword: ValidatorFn = (control) => {
const value = control.getValue();
if (value.length < 8 || !/[A-Z]/.test(value) || !/[0-9]/.test(value)) {
return { strongPassword: true };
}
return null;
};
const password = new FormControl('', [required, strongPassword]);Using in Web Components
import { ScopedElementsMixin } from '@vydra-js/core';
import { LitElement, html, css } from 'lit';
import { FormGroup, required, bindForm } from '@vydra-js/forms';
class LoginForm extends ScopedElementsMixin(LitElement) {
static styles = css`
input {
display: block;
margin-bottom: 8px;
}
.error {
color: red;
}
`;
private form = new FormGroup({
username: new FormControl('', [required]),
password: new FormControl('', [required]),
});
render() {
const username = this.form.getControl('username');
const password = this.form.getControl('password');
return html`
<form @submit=${this.handleSubmit}>
<input type="text" placeholder="Username" ${bindForm(this.form, 'username')} />
${username.invalid ? html`<span class="error">Required</span>` : ''}
<input type="password" placeholder="Password" ${bindForm(this.form, 'password')} />
${password.invalid ? html`<span class="error">Required</span>` : ''}
<button type="submit" ?disabled=${this.form.invalid}>Login</button>
</form>
`;
}
private handleSubmit(e: Event) {
e.preventDefault();
console.log(this.form.value);
}
}Validating Nested Groups
const form = new FormGroup({
name: new FormGroup({
first: new FormControl('', [required]),
last: new FormControl('', [required]),
}),
email: new FormControl('', [required, email]),
});
// Access nested values
form.getControl('name').getControl('first');Integration
With HTTP
import { HttpBase } from '@vydra-js/http';
class ApiService extends HttpBase {
async login(credentials: ReturnType<typeof form.value>) {
return this.post('/api/login', credentials);
}
}With Event Bus
import { VydraBus } from '@vydra-js/bus';
const bus = new VydraBus('global');
bus.subscribe('auth:logout', () => form.reset());Best Practices
Group related controls
// Good const form = new FormGroup({ billing: new FormGroup({ street, city, zip }), shipping: new FormGroup({ street, city, zip }), });Use appropriate validators
// Good - specific validation new FormControl('', [required, email, maxLength(100)]);Handle form reset
form.reset(); // Resets to initial valuesDisable controls when needed
control.disable(); // Disables validation and excluded from value
Type Definitions
type ValidatorFn = (control: FormControl) => ValidationResult | null;
type ValidationResult = Record<string, unknown> | null;
interface BindOptions {
valueKey?: string;
updateOn?: 'change' | 'blur' | 'submit';
}See Also
- HTTP - API communication
- Event Bus - Event system
- Example App - Forms example
