@coherent.js/forms 1.0.0-beta.5 → 1.0.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/types/index.d.ts +361 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coherent.js/forms",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.6",
|
|
4
4
|
"description": "SSR + Hydration form system for Coherent.js applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"author": "Coherent.js Team",
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@coherent.js/core": "1.0.0-beta.
|
|
26
|
-
"@coherent.js/state": "1.0.0-beta.
|
|
25
|
+
"@coherent.js/core": "1.0.0-beta.6",
|
|
26
|
+
"@coherent.js/state": "1.0.0-beta.6"
|
|
27
27
|
},
|
|
28
28
|
"repository": {
|
|
29
29
|
"type": "git",
|
package/types/index.d.ts
CHANGED
|
@@ -3,132 +3,455 @@
|
|
|
3
3
|
* @module @coherent.js/forms
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import type { CoherentNode, CoherentElement, StrictCoherentElement } from '@coherent.js/core';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Form Field Types
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Available form field input types
|
|
14
|
+
*/
|
|
15
|
+
export type FormFieldType =
|
|
16
|
+
| 'text'
|
|
17
|
+
| 'email'
|
|
18
|
+
| 'password'
|
|
19
|
+
| 'number'
|
|
20
|
+
| 'tel'
|
|
21
|
+
| 'url'
|
|
22
|
+
| 'date'
|
|
23
|
+
| 'time'
|
|
24
|
+
| 'datetime-local'
|
|
25
|
+
| 'checkbox'
|
|
26
|
+
| 'radio'
|
|
27
|
+
| 'select'
|
|
28
|
+
| 'textarea'
|
|
29
|
+
| 'file'
|
|
30
|
+
| 'hidden'
|
|
31
|
+
| 'color'
|
|
32
|
+
| 'range'
|
|
33
|
+
| 'search'
|
|
34
|
+
| 'month'
|
|
35
|
+
| 'week';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Option for select and radio fields
|
|
39
|
+
*/
|
|
40
|
+
export interface SelectOption {
|
|
41
|
+
value: string | number;
|
|
42
|
+
label: string;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Typed form field definition with generic value type
|
|
48
|
+
* @template T - The type of the field value
|
|
49
|
+
*/
|
|
50
|
+
export interface FormField<T = unknown> {
|
|
51
|
+
/** Field input type */
|
|
52
|
+
type: FormFieldType;
|
|
53
|
+
/** Field name (used as form data key) */
|
|
10
54
|
name: string;
|
|
55
|
+
/** Human-readable label */
|
|
11
56
|
label?: string;
|
|
57
|
+
/** Placeholder text */
|
|
12
58
|
placeholder?: string;
|
|
59
|
+
/** Whether field is required */
|
|
13
60
|
required?: boolean;
|
|
14
|
-
|
|
15
|
-
|
|
61
|
+
/** Whether field is disabled */
|
|
62
|
+
disabled?: boolean;
|
|
63
|
+
/** Whether field is readonly */
|
|
64
|
+
readonly?: boolean;
|
|
65
|
+
/** Default/initial value */
|
|
66
|
+
defaultValue?: T;
|
|
67
|
+
/** Current value */
|
|
68
|
+
value?: T;
|
|
69
|
+
/** Options for select/radio fields */
|
|
70
|
+
options?: SelectOption[];
|
|
71
|
+
/** Validation rules */
|
|
16
72
|
validators?: Validator[];
|
|
17
|
-
|
|
73
|
+
/** Field-specific validation configuration */
|
|
74
|
+
validation?: FieldValidation<T>;
|
|
75
|
+
/** Additional HTML attributes */
|
|
76
|
+
attributes?: Record<string, unknown>;
|
|
77
|
+
/** Transform function to convert raw input to typed value */
|
|
78
|
+
transform?: (value: unknown) => T;
|
|
18
79
|
}
|
|
19
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Field validation configuration with typed value
|
|
83
|
+
* @template T - The type of the field value
|
|
84
|
+
*/
|
|
85
|
+
export interface FieldValidation<T = unknown> {
|
|
86
|
+
/** Required validation with optional custom message */
|
|
87
|
+
required?: boolean | string;
|
|
88
|
+
/** Minimum length for string values */
|
|
89
|
+
minLength?: number | { value: number; message: string };
|
|
90
|
+
/** Maximum length for string values */
|
|
91
|
+
maxLength?: number | { value: number; message: string };
|
|
92
|
+
/** Minimum value for number values */
|
|
93
|
+
min?: number | { value: number; message: string };
|
|
94
|
+
/** Maximum value for number values */
|
|
95
|
+
max?: number | { value: number; message: string };
|
|
96
|
+
/** Regular expression pattern validation */
|
|
97
|
+
pattern?: RegExp | { value: RegExp; message: string };
|
|
98
|
+
/** Custom validation function */
|
|
99
|
+
custom?: (value: T, formData: Record<string, unknown>) => boolean | string | Promise<boolean | string>;
|
|
100
|
+
/** Validate on change (real-time) */
|
|
101
|
+
validateOnChange?: boolean;
|
|
102
|
+
/** Validate on blur */
|
|
103
|
+
validateOnBlur?: boolean;
|
|
104
|
+
/** Debounce time in milliseconds for validation */
|
|
105
|
+
debounce?: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ============================================================================
|
|
109
|
+
// Form Builder Types
|
|
110
|
+
// ============================================================================
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Form configuration options
|
|
114
|
+
*/
|
|
20
115
|
export interface FormConfig {
|
|
21
|
-
fields
|
|
116
|
+
/** Form fields */
|
|
117
|
+
fields?: FormField[];
|
|
118
|
+
/** Form action URL */
|
|
22
119
|
action?: string;
|
|
120
|
+
/** Form submission method */
|
|
23
121
|
method?: 'get' | 'post';
|
|
122
|
+
/** Form CSS class name */
|
|
24
123
|
className?: string;
|
|
124
|
+
/** Submit button text */
|
|
25
125
|
submitText?: string;
|
|
126
|
+
/** Form submit handler */
|
|
26
127
|
onSubmit?: (data: FormData) => void | Promise<void>;
|
|
128
|
+
/** Form encoding type */
|
|
129
|
+
enctype?: 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain';
|
|
130
|
+
/** Whether to disable browser validation */
|
|
131
|
+
novalidate?: boolean;
|
|
132
|
+
/** Form ID */
|
|
133
|
+
id?: string;
|
|
27
134
|
}
|
|
28
135
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Typed form builder with generic form data shape
|
|
138
|
+
* @template T - The shape of the form data
|
|
139
|
+
*/
|
|
140
|
+
export interface FormBuilder<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
141
|
+
/**
|
|
142
|
+
* Add a field to the form
|
|
143
|
+
* @template K - The key in the form data
|
|
144
|
+
*/
|
|
145
|
+
addField<K extends keyof T>(name: K, field: Omit<FormField<T[K]>, 'name'>): FormBuilder<T>;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Remove a field from the form
|
|
149
|
+
*/
|
|
150
|
+
removeField(name: keyof T): FormBuilder<T>;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Set the form action URL
|
|
154
|
+
*/
|
|
155
|
+
setAction(action: string): FormBuilder<T>;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Set the form submission method
|
|
159
|
+
*/
|
|
160
|
+
setMethod(method: 'get' | 'post'): FormBuilder<T>;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Set the form submit handler
|
|
164
|
+
*/
|
|
165
|
+
onSubmit(handler: (data: T) => void | Promise<void>): FormBuilder<T>;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Build the form as a CoherentNode
|
|
169
|
+
*/
|
|
170
|
+
build(): CoherentNode;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Render the form (alias for build)
|
|
174
|
+
*/
|
|
175
|
+
render(): CoherentNode;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Validate form data
|
|
179
|
+
*/
|
|
180
|
+
validate(data: unknown): { valid: boolean; errors: Record<keyof T, string[]> };
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get current field definitions
|
|
184
|
+
*/
|
|
185
|
+
getFields(): FormField[];
|
|
35
186
|
}
|
|
36
187
|
|
|
37
|
-
|
|
38
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Create a typed form builder
|
|
190
|
+
* @template T - The shape of the form data
|
|
191
|
+
*/
|
|
192
|
+
export function createFormBuilder<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
193
|
+
config?: FormConfig
|
|
194
|
+
): FormBuilder<T>;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Build a form from configuration
|
|
198
|
+
*/
|
|
199
|
+
export function buildForm(config: FormConfig): CoherentNode;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Render a single form field
|
|
203
|
+
*/
|
|
204
|
+
export function renderField<T = unknown>(field: FormField<T>): CoherentNode;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Validate a single field value
|
|
208
|
+
* @returns Error message or null if valid
|
|
209
|
+
*/
|
|
210
|
+
export function validateField<T>(field: FormField<T>, value: unknown): string | null;
|
|
211
|
+
|
|
212
|
+
// ============================================================================
|
|
213
|
+
// Form Builder Class
|
|
214
|
+
// ============================================================================
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Form builder class implementation
|
|
218
|
+
*/
|
|
219
|
+
export class FormBuilder<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
220
|
+
constructor(config?: FormConfig);
|
|
221
|
+
addField<K extends keyof T>(name: K, field: Omit<FormField<T[K]>, 'name'>): this;
|
|
222
|
+
removeField(name: keyof T): this;
|
|
223
|
+
setAction(action: string): this;
|
|
224
|
+
setMethod(method: 'get' | 'post'): this;
|
|
225
|
+
onSubmit(handler: (data: T) => void | Promise<void>): this;
|
|
226
|
+
build(): CoherentNode;
|
|
227
|
+
render(): CoherentNode;
|
|
228
|
+
validate(data: unknown): { valid: boolean; errors: Record<keyof T, string[]> };
|
|
229
|
+
getFields(): FormField[];
|
|
230
|
+
}
|
|
39
231
|
|
|
40
|
-
//
|
|
232
|
+
// ============================================================================
|
|
233
|
+
// Form Hydration Types
|
|
234
|
+
// ============================================================================
|
|
41
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Options for hydrating a form on the client
|
|
238
|
+
*/
|
|
42
239
|
export interface HydrationOptions {
|
|
240
|
+
/** Enable validation */
|
|
43
241
|
validation?: boolean;
|
|
242
|
+
/** Enable real-time validation as user types */
|
|
44
243
|
realTimeValidation?: boolean;
|
|
244
|
+
/** Form submit handler */
|
|
45
245
|
onSubmit?: (event: Event, data: FormData) => void | Promise<void>;
|
|
246
|
+
/** Validation error handler */
|
|
46
247
|
onValidate?: (errors: ValidationErrors) => void;
|
|
248
|
+
/** Prevent default form submission */
|
|
249
|
+
preventSubmit?: boolean;
|
|
47
250
|
}
|
|
48
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Hydrated form interface for client-side interaction
|
|
254
|
+
*/
|
|
49
255
|
export interface HydratedForm {
|
|
256
|
+
/** The form DOM element */
|
|
50
257
|
element: HTMLFormElement;
|
|
258
|
+
/** Validate all form fields */
|
|
51
259
|
validate(): ValidationResult;
|
|
260
|
+
/** Reset form to initial values */
|
|
52
261
|
reset(): void;
|
|
262
|
+
/** Get current form data */
|
|
53
263
|
getData(): FormData;
|
|
54
|
-
|
|
264
|
+
/** Get form data as object */
|
|
265
|
+
getValues<T = Record<string, unknown>>(): T;
|
|
266
|
+
/** Set form field values */
|
|
267
|
+
setData(data: Record<string, unknown>): void;
|
|
268
|
+
/** Set a single field value */
|
|
269
|
+
setValue(name: string, value: unknown): void;
|
|
270
|
+
/** Destroy hydration and clean up event listeners */
|
|
55
271
|
destroy(): void;
|
|
272
|
+
/** Check if form is valid */
|
|
273
|
+
isValid(): boolean;
|
|
274
|
+
/** Get validation errors */
|
|
275
|
+
getErrors(): ValidationErrors;
|
|
56
276
|
}
|
|
57
277
|
|
|
58
|
-
|
|
278
|
+
/**
|
|
279
|
+
* Hydrate a form element for client-side interactivity
|
|
280
|
+
*/
|
|
281
|
+
export function hydrateForm(
|
|
282
|
+
formElement: HTMLFormElement | string,
|
|
283
|
+
options?: HydrationOptions
|
|
284
|
+
): HydratedForm;
|
|
59
285
|
|
|
60
|
-
//
|
|
286
|
+
// ============================================================================
|
|
287
|
+
// Validation Types
|
|
288
|
+
// ============================================================================
|
|
61
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Validation result
|
|
292
|
+
*/
|
|
62
293
|
export interface ValidationResult {
|
|
294
|
+
/** Whether all fields are valid */
|
|
63
295
|
valid: boolean;
|
|
296
|
+
/** Validation errors by field name */
|
|
64
297
|
errors: ValidationErrors;
|
|
65
298
|
}
|
|
66
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Validation errors mapped by field name
|
|
302
|
+
*/
|
|
67
303
|
export interface ValidationErrors {
|
|
68
304
|
[fieldName: string]: string[];
|
|
69
305
|
}
|
|
70
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Validator function type
|
|
309
|
+
*/
|
|
71
310
|
export interface Validator {
|
|
72
|
-
(value:
|
|
311
|
+
(value: unknown): boolean | string | Promise<boolean | string>;
|
|
73
312
|
}
|
|
74
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Form validator class
|
|
316
|
+
*/
|
|
75
317
|
export class FormValidator {
|
|
76
318
|
constructor(rules: Record<string, Validator[]>);
|
|
77
|
-
|
|
78
|
-
|
|
319
|
+
/** Synchronous validation */
|
|
320
|
+
validate(data: Record<string, unknown>): ValidationResult;
|
|
321
|
+
/** Asynchronous validation */
|
|
322
|
+
validateAsync(data: Record<string, unknown>): Promise<ValidationResult>;
|
|
323
|
+
/** Add a validation rule */
|
|
79
324
|
addRule(field: string, validator: Validator): void;
|
|
325
|
+
/** Remove a validation rule */
|
|
80
326
|
removeRule(field: string, validator: Validator): void;
|
|
327
|
+
/** Clear all rules for a field */
|
|
328
|
+
clearRules(field: string): void;
|
|
81
329
|
}
|
|
82
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Create a form validator
|
|
333
|
+
*/
|
|
83
334
|
export function createValidator(rules: Record<string, Validator[]>): FormValidator;
|
|
84
|
-
export function validate(data: Record<string, any>, rules: Record<string, Validator[]>): ValidationResult;
|
|
85
335
|
|
|
86
|
-
|
|
336
|
+
/**
|
|
337
|
+
* Validate data against rules
|
|
338
|
+
*/
|
|
339
|
+
export function validate(
|
|
340
|
+
data: Record<string, unknown>,
|
|
341
|
+
rules: Record<string, Validator[]>
|
|
342
|
+
): ValidationResult;
|
|
343
|
+
|
|
344
|
+
// ============================================================================
|
|
345
|
+
// Built-in Validators
|
|
346
|
+
// ============================================================================
|
|
87
347
|
|
|
348
|
+
/**
|
|
349
|
+
* Built-in validator functions
|
|
350
|
+
*/
|
|
88
351
|
export const validators: {
|
|
352
|
+
/** Require a value to be present */
|
|
89
353
|
required(message?: string): Validator;
|
|
354
|
+
/** Validate email format */
|
|
90
355
|
email(message?: string): Validator;
|
|
356
|
+
/** Minimum string length */
|
|
91
357
|
minLength(length: number, message?: string): Validator;
|
|
358
|
+
/** Maximum string length */
|
|
92
359
|
maxLength(length: number, message?: string): Validator;
|
|
360
|
+
/** Minimum numeric value */
|
|
93
361
|
min(value: number, message?: string): Validator;
|
|
362
|
+
/** Maximum numeric value */
|
|
94
363
|
max(value: number, message?: string): Validator;
|
|
364
|
+
/** Pattern matching */
|
|
95
365
|
pattern(regex: RegExp, message?: string): Validator;
|
|
366
|
+
/** Match another field's value */
|
|
96
367
|
matches(field: string, message?: string): Validator;
|
|
368
|
+
/** Validate URL format */
|
|
97
369
|
url(message?: string): Validator;
|
|
370
|
+
/** Validate as number */
|
|
98
371
|
number(message?: string): Validator;
|
|
372
|
+
/** Validate as integer */
|
|
99
373
|
integer(message?: string): Validator;
|
|
374
|
+
/** Validate as positive number */
|
|
100
375
|
positive(message?: string): Validator;
|
|
376
|
+
/** Validate as negative number */
|
|
101
377
|
negative(message?: string): Validator;
|
|
378
|
+
/** Validate date format */
|
|
102
379
|
date(message?: string): Validator;
|
|
103
|
-
|
|
104
|
-
|
|
380
|
+
/** Custom validation function */
|
|
381
|
+
custom(fn: (value: unknown) => boolean | string, message?: string): Validator;
|
|
382
|
+
/** Async validation function */
|
|
383
|
+
async(fn: (value: unknown) => Promise<boolean | string>): Validator;
|
|
105
384
|
};
|
|
106
385
|
|
|
386
|
+
/** Alias for validators */
|
|
107
387
|
export const formValidators: typeof validators;
|
|
108
388
|
|
|
109
|
-
//
|
|
389
|
+
// ============================================================================
|
|
390
|
+
// Advanced Validation
|
|
391
|
+
// ============================================================================
|
|
110
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Validation rule configuration
|
|
395
|
+
*/
|
|
111
396
|
export interface ValidationRule {
|
|
397
|
+
/** The validator function */
|
|
112
398
|
validator: Validator;
|
|
399
|
+
/** Custom error message */
|
|
113
400
|
message?: string;
|
|
401
|
+
/** Whether this is an async validator */
|
|
114
402
|
async?: boolean;
|
|
115
403
|
}
|
|
116
404
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
405
|
+
/**
|
|
406
|
+
* Create an async validator
|
|
407
|
+
*/
|
|
408
|
+
export function createAsyncValidator(
|
|
409
|
+
fn: (value: unknown) => Promise<boolean | string>
|
|
410
|
+
): Validator;
|
|
123
411
|
|
|
124
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Combine multiple validators into one
|
|
414
|
+
*/
|
|
125
415
|
export function combineValidators(...validators: Validator[]): Validator;
|
|
126
|
-
export function conditionalValidator(condition: (data: Record<string, any>) => boolean, validator: Validator): Validator;
|
|
127
416
|
|
|
128
|
-
|
|
417
|
+
/**
|
|
418
|
+
* Create a conditional validator
|
|
419
|
+
*/
|
|
420
|
+
export function conditionalValidator(
|
|
421
|
+
condition: (data: Record<string, unknown>) => boolean,
|
|
422
|
+
validator: Validator
|
|
423
|
+
): Validator;
|
|
424
|
+
|
|
425
|
+
// ============================================================================
|
|
426
|
+
// Form Utilities
|
|
427
|
+
// ============================================================================
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Parse FormData to typed object
|
|
431
|
+
*/
|
|
432
|
+
export function parseFormData<T extends Record<string, unknown>>(formData: FormData): T;
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Serialize object to FormData
|
|
436
|
+
*/
|
|
437
|
+
export function toFormData(data: Record<string, unknown>): FormData;
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Create a form group (fieldset)
|
|
441
|
+
*/
|
|
442
|
+
export function createFieldGroup(
|
|
443
|
+
legend: string,
|
|
444
|
+
fields: FormField[]
|
|
445
|
+
): CoherentNode;
|
|
446
|
+
|
|
447
|
+
// ============================================================================
|
|
448
|
+
// Deprecated Functions (Backward Compatibility)
|
|
449
|
+
// ============================================================================
|
|
129
450
|
|
|
130
451
|
/** @deprecated Use createFormBuilder() on server + hydrateForm() on client */
|
|
131
|
-
export function createForm(config: FormConfig):
|
|
452
|
+
export function createForm(config: FormConfig): CoherentNode;
|
|
132
453
|
|
|
133
454
|
/** @deprecated Use validators with hydrateForm() instead */
|
|
134
|
-
export function enhancedForm(
|
|
455
|
+
export function enhancedForm(
|
|
456
|
+
config: FormConfig & { validation?: Record<string, Validator[]> }
|
|
457
|
+
): CoherentNode;
|