@rilaykit/forms 4.0.0 → 5.0.0
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/dist/index.d.mts +108 -93
- package/dist/index.d.ts +108 -93
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ril,
|
|
2
|
+
import { ril, FieldValidationConfig, FormFieldConfig, FormFieldRow, FormValidationConfig, FormValidator, FormConfiguration, ComponentRendererBaseProps, FormBodyRendererProps, ValidationError, ValidationResult, FormRowRendererProps, FormSubmitButtonRendererProps } from '@rilaykit/core';
|
|
3
3
|
export * from '@rilaykit/core';
|
|
4
|
-
|
|
5
|
-
import * as React$1 from 'react';
|
|
6
|
-
import React__default from 'react';
|
|
4
|
+
import React$1 from 'react';
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
7
|
* Configuration for a form field with type safety
|
|
@@ -16,7 +14,11 @@ import React__default from 'react';
|
|
|
16
14
|
* const fieldConfig: FieldConfig<MyComponents, 'text'> = {
|
|
17
15
|
* type: 'text',
|
|
18
16
|
* props: { placeholder: 'Enter your name' },
|
|
19
|
-
* validation: {
|
|
17
|
+
* validation: {
|
|
18
|
+
* validators: [required(), minLength(2)],
|
|
19
|
+
* validateOnChange: true,
|
|
20
|
+
* validateOnBlur: true
|
|
21
|
+
* }
|
|
20
22
|
* };
|
|
21
23
|
* ```
|
|
22
24
|
*/
|
|
@@ -27,28 +29,9 @@ type FieldConfig<C extends Record<string, any>, T extends keyof C> = {
|
|
|
27
29
|
type: T;
|
|
28
30
|
/** Component-specific properties */
|
|
29
31
|
props?: Partial<C[T]>;
|
|
30
|
-
/** Validation
|
|
31
|
-
validation?:
|
|
32
|
-
/** Conditional display logic */
|
|
33
|
-
conditional?: ConditionalConfig;
|
|
32
|
+
/** Validation configuration for this field */
|
|
33
|
+
validation?: FieldValidationConfig;
|
|
34
34
|
};
|
|
35
|
-
/**
|
|
36
|
-
* Options for configuring row layout and appearance
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```typescript
|
|
40
|
-
* const rowOptions: RowOptions = {
|
|
41
|
-
* spacing: 'loose',
|
|
42
|
-
* alignment: 'center'
|
|
43
|
-
* };
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
interface RowOptions {
|
|
47
|
-
/** Spacing between fields in the row */
|
|
48
|
-
spacing?: 'tight' | 'normal' | 'loose';
|
|
49
|
-
/** Vertical alignment of fields in the row */
|
|
50
|
-
alignment?: 'start' | 'center' | 'end' | 'stretch';
|
|
51
|
-
}
|
|
52
35
|
/**
|
|
53
36
|
* Form builder class for creating type-safe form configurations
|
|
54
37
|
*
|
|
@@ -73,7 +56,6 @@ interface RowOptions {
|
|
|
73
56
|
* @remarks
|
|
74
57
|
* - Supports up to 3 fields per row for optimal layout
|
|
75
58
|
* - Automatically generates unique IDs for fields and rows
|
|
76
|
-
* - Provides comprehensive validation before building
|
|
77
59
|
* - Maintains type safety throughout the building process
|
|
78
60
|
*/
|
|
79
61
|
declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
@@ -85,6 +67,8 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
85
67
|
private formId;
|
|
86
68
|
/** Generator for creating unique IDs */
|
|
87
69
|
private idGenerator;
|
|
70
|
+
/** Form-level validation configuration */
|
|
71
|
+
private formValidation?;
|
|
88
72
|
/**
|
|
89
73
|
* Creates a new form builder instance
|
|
90
74
|
*
|
|
@@ -112,11 +96,15 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
112
96
|
*/
|
|
113
97
|
static create<Cm extends Record<string, any> = Record<string, never>>(config: ril<Cm>, formId?: string): form<Cm>;
|
|
114
98
|
/**
|
|
115
|
-
* Converts a FieldConfig to a FormFieldConfig
|
|
99
|
+
* Converts a FieldConfig to a FormFieldConfig
|
|
116
100
|
*
|
|
117
101
|
* This internal method handles the transformation from the builder's field
|
|
118
102
|
* configuration format to the final form field configuration, including
|
|
119
|
-
* component lookup, prop merging, and
|
|
103
|
+
* component lookup, prop merging, ID generation, and validation setup.
|
|
104
|
+
*
|
|
105
|
+
* The validation system combines component-level validation (defined in the component config)
|
|
106
|
+
* with field-level validation (defined in the field config). Component validators are
|
|
107
|
+
* applied first, followed by field validators.
|
|
120
108
|
*
|
|
121
109
|
* @template T - The component type
|
|
122
110
|
* @param fieldConfig - The field configuration to convert
|
|
@@ -129,7 +117,7 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
129
117
|
/**
|
|
130
118
|
* Creates a form row with the specified fields and options
|
|
131
119
|
*
|
|
132
|
-
* This internal method handles row creation
|
|
120
|
+
* This internal method handles row creation,
|
|
133
121
|
* proper spacing, and alignment configuration.
|
|
134
122
|
*
|
|
135
123
|
* @template T - The component type
|
|
@@ -176,7 +164,7 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
176
164
|
* ```
|
|
177
165
|
*/
|
|
178
166
|
add<T extends keyof C & string>(...fields: FieldConfig<C, T>[]): this;
|
|
179
|
-
add<T extends keyof C & string>(fields: FieldConfig<C, T>[]
|
|
167
|
+
add<T extends keyof C & string>(fields: FieldConfig<C, T>[]): this;
|
|
180
168
|
/**
|
|
181
169
|
* Adds multiple fields on separate rows
|
|
182
170
|
*
|
|
@@ -196,10 +184,10 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
196
184
|
* { type: 'text', props: { label: 'Field 1' } },
|
|
197
185
|
* { type: 'text', props: { label: 'Field 2' } },
|
|
198
186
|
* { type: 'text', props: { label: 'Field 3' } }
|
|
199
|
-
* ]
|
|
187
|
+
* ]);
|
|
200
188
|
* ```
|
|
201
189
|
*/
|
|
202
|
-
addSeparateRows<T extends keyof C & string>(fieldConfigs: FieldConfig<C, T>[]
|
|
190
|
+
addSeparateRows<T extends keyof C & string>(fieldConfigs: FieldConfig<C, T>[]): this;
|
|
203
191
|
/**
|
|
204
192
|
* Sets the form identifier
|
|
205
193
|
*
|
|
@@ -215,8 +203,7 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
215
203
|
/**
|
|
216
204
|
* Updates an existing field's configuration
|
|
217
205
|
*
|
|
218
|
-
* This method allows you to modify field properties
|
|
219
|
-
* or conditional logic after the field has been added to the form.
|
|
206
|
+
* This method allows you to modify field properties after the field has been added to the form.
|
|
220
207
|
*
|
|
221
208
|
* @param fieldId - The unique identifier of the field to update
|
|
222
209
|
* @param updates - Partial field configuration with updates to apply
|
|
@@ -227,7 +214,6 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
227
214
|
* ```typescript
|
|
228
215
|
* builder.updateField('email-field', {
|
|
229
216
|
* props: { placeholder: 'Enter your email address' },
|
|
230
|
-
* validation: { required: true, email: true }
|
|
231
217
|
* });
|
|
232
218
|
* ```
|
|
233
219
|
*/
|
|
@@ -319,6 +305,70 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
319
305
|
* ```
|
|
320
306
|
*/
|
|
321
307
|
clear(): this;
|
|
308
|
+
/**
|
|
309
|
+
* Configures validation for the entire form
|
|
310
|
+
*
|
|
311
|
+
* This method sets up form-level validation that will be applied when the
|
|
312
|
+
* form is submitted or when validation is explicitly triggered. Form validators
|
|
313
|
+
* receive all form data and can perform cross-field validation.
|
|
314
|
+
*
|
|
315
|
+
* @param validationConfig - Form validation configuration
|
|
316
|
+
* @returns The form builder instance for method chaining
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* ```typescript
|
|
320
|
+
* builder.setValidation({
|
|
321
|
+
* validators: [
|
|
322
|
+
* (formData, context) => {
|
|
323
|
+
* if (!formData.email && !formData.phone) {
|
|
324
|
+
* return createErrorResult('Either email or phone is required');
|
|
325
|
+
* }
|
|
326
|
+
* return createSuccessResult();
|
|
327
|
+
* }
|
|
328
|
+
* ],
|
|
329
|
+
* validateOnSubmit: true
|
|
330
|
+
* });
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
setValidation(validationConfig: FormValidationConfig): this;
|
|
334
|
+
/**
|
|
335
|
+
* Adds validators to the form-level validation
|
|
336
|
+
*
|
|
337
|
+
* This method allows adding validators to an existing validation configuration
|
|
338
|
+
* without replacing the entire configuration.
|
|
339
|
+
*
|
|
340
|
+
* @param validators - Array of form validators to add
|
|
341
|
+
* @returns The form builder instance for method chaining
|
|
342
|
+
*
|
|
343
|
+
* @example
|
|
344
|
+
* ```typescript
|
|
345
|
+
* builder.addValidators([
|
|
346
|
+
* customFormValidator,
|
|
347
|
+
* anotherFormValidator
|
|
348
|
+
* ]);
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
addValidators(validators: FormValidator[]): this;
|
|
352
|
+
/**
|
|
353
|
+
* Adds validation to a specific field by ID
|
|
354
|
+
*
|
|
355
|
+
* This method allows adding validation to a field after it has been created,
|
|
356
|
+
* useful for dynamic validation requirements.
|
|
357
|
+
*
|
|
358
|
+
* @param fieldId - The ID of the field to add validation to
|
|
359
|
+
* @param validationConfig - Field validation configuration
|
|
360
|
+
* @returns The form builder instance for method chaining
|
|
361
|
+
* @throws Error if the field with the specified ID is not found
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```typescript
|
|
365
|
+
* builder.addFieldValidation('email', {
|
|
366
|
+
* validators: [required(), email()],
|
|
367
|
+
* validateOnBlur: true
|
|
368
|
+
* });
|
|
369
|
+
* ```
|
|
370
|
+
*/
|
|
371
|
+
addFieldValidation(fieldId: string, validationConfig: FieldValidationConfig): this;
|
|
322
372
|
/**
|
|
323
373
|
* Creates a deep copy of the current form builder
|
|
324
374
|
*
|
|
@@ -338,47 +388,24 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
338
388
|
*/
|
|
339
389
|
clone(newFormId?: string): form<C>;
|
|
340
390
|
/**
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
* This method performs comprehensive validation of the form structure,
|
|
344
|
-
* checking for common issues like duplicate IDs, missing components,
|
|
345
|
-
* and invalid row configurations.
|
|
391
|
+
* Checks the current form configuration for basic structural issues.
|
|
346
392
|
*
|
|
347
|
-
* @returns Array of
|
|
348
|
-
*
|
|
349
|
-
* @example
|
|
350
|
-
* ```typescript
|
|
351
|
-
* const errors = builder.validate();
|
|
352
|
-
* if (errors.length > 0) {
|
|
353
|
-
* console.error('Form validation failed:', errors);
|
|
354
|
-
* }
|
|
355
|
-
* ```
|
|
356
|
-
*
|
|
357
|
-
* @remarks
|
|
358
|
-
* Validation checks include:
|
|
359
|
-
* - Duplicate field IDs across the form
|
|
360
|
-
* - Missing component definitions for referenced types
|
|
361
|
-
* - Row constraints (max 3 fields, no empty rows)
|
|
393
|
+
* @returns Array of error messages (empty if valid)
|
|
362
394
|
*/
|
|
363
395
|
validate(): string[];
|
|
364
396
|
/**
|
|
365
397
|
* Builds the final form configuration
|
|
366
398
|
*
|
|
367
|
-
* This method
|
|
399
|
+
* This method creates the complete form
|
|
368
400
|
* configuration object ready for rendering. It includes all field
|
|
369
|
-
* configurations, render settings, and metadata.
|
|
401
|
+
* configurations, render settings, validation configuration, and metadata.
|
|
370
402
|
*
|
|
371
403
|
* @returns Complete form configuration ready for use
|
|
372
|
-
* @throws Error if validation fails
|
|
373
404
|
*
|
|
374
405
|
* @example
|
|
375
406
|
* ```typescript
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
* // Use formConfig with your form renderer
|
|
379
|
-
* } catch (error) {
|
|
380
|
-
* console.error('Failed to build form:', error.message);
|
|
381
|
-
* }
|
|
407
|
+
* const formConfig = builder.build();
|
|
408
|
+
* // Use formConfig with your form renderer
|
|
382
409
|
* ```
|
|
383
410
|
*
|
|
384
411
|
* @remarks
|
|
@@ -388,6 +415,7 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
388
415
|
* - Flattened array of all fields for easy access
|
|
389
416
|
* - Component configuration reference
|
|
390
417
|
* - Render configuration for customization
|
|
418
|
+
* - Form-level validation configuration
|
|
391
419
|
*/
|
|
392
420
|
build(): FormConfiguration;
|
|
393
421
|
/**
|
|
@@ -516,12 +544,7 @@ interface FormProps {
|
|
|
516
544
|
}
|
|
517
545
|
declare function Form({ formConfig, defaultValues, onSubmit, onFieldChange, children }: FormProps): react_jsx_runtime.JSX.Element;
|
|
518
546
|
|
|
519
|
-
|
|
520
|
-
className?: string;
|
|
521
|
-
children?: React.ReactNode | RendererChildrenFunction<FormBodyRendererProps>;
|
|
522
|
-
renderAs?: 'default' | 'children' | boolean;
|
|
523
|
-
}
|
|
524
|
-
declare function FormBody({ className, children, renderAs }: FormBodyProps): string | number | boolean | React$1.ReactElement<any, string | React$1.JSXElementConstructor<any>> | Iterable<React$1.ReactNode> | null | undefined;
|
|
547
|
+
declare function FormBody({ className, ...props }: ComponentRendererBaseProps<FormBodyRendererProps>): react_jsx_runtime.JSX.Element;
|
|
525
548
|
|
|
526
549
|
interface FormFieldProps {
|
|
527
550
|
fieldId: string;
|
|
@@ -534,27 +557,27 @@ declare function FormField({ fieldId, disabled, customProps, className, }: FormF
|
|
|
534
557
|
interface FormState {
|
|
535
558
|
values: Record<string, any>;
|
|
536
559
|
errors: Record<string, ValidationError[]>;
|
|
537
|
-
|
|
538
|
-
|
|
560
|
+
validationState: Record<string, 'idle' | 'validating' | 'valid' | 'invalid'>;
|
|
561
|
+
touched: Record<string, boolean>;
|
|
539
562
|
isDirty: boolean;
|
|
540
|
-
isValid: boolean;
|
|
541
563
|
isSubmitting: boolean;
|
|
564
|
+
isValid: boolean;
|
|
542
565
|
}
|
|
543
566
|
interface FormContextValue {
|
|
544
567
|
formState: FormState;
|
|
545
568
|
formConfig: FormConfiguration;
|
|
546
569
|
setValue: (fieldId: string, value: any) => void;
|
|
547
|
-
|
|
548
|
-
clearError: (fieldId: string) => void;
|
|
549
|
-
markFieldTouched: (fieldId: string) => void;
|
|
550
|
-
setFieldValidating: (fieldId: string, isValidating: boolean) => void;
|
|
570
|
+
setFieldTouched: (fieldId: string, touched?: boolean) => void;
|
|
551
571
|
validateField: (fieldId: string, value?: any) => Promise<ValidationResult>;
|
|
552
|
-
|
|
572
|
+
validateForm: () => Promise<ValidationResult>;
|
|
573
|
+
isFormValid: () => boolean;
|
|
553
574
|
reset: (values?: Record<string, any>) => void;
|
|
554
|
-
submit: (event?:
|
|
575
|
+
submit: (event?: React$1.FormEvent) => Promise<boolean>;
|
|
576
|
+
setError: (fieldId: string, errors: ValidationError[]) => void;
|
|
577
|
+
clearError: (fieldId: string) => void;
|
|
555
578
|
}
|
|
556
579
|
interface FormProviderProps {
|
|
557
|
-
children:
|
|
580
|
+
children: React$1.ReactNode;
|
|
558
581
|
formConfig: FormConfiguration;
|
|
559
582
|
defaultValues?: Record<string, any>;
|
|
560
583
|
onSubmit?: (data: Record<string, any>) => void | Promise<void>;
|
|
@@ -564,19 +587,11 @@ interface FormProviderProps {
|
|
|
564
587
|
declare function FormProvider({ children, formConfig, defaultValues, onSubmit, onFieldChange, className, }: FormProviderProps): react_jsx_runtime.JSX.Element;
|
|
565
588
|
declare function useFormContext(): FormContextValue;
|
|
566
589
|
|
|
567
|
-
interface FormRowProps {
|
|
590
|
+
interface FormRowProps extends ComponentRendererBaseProps<FormRowRendererProps> {
|
|
568
591
|
row: FormFieldRow;
|
|
569
|
-
className?: string;
|
|
570
|
-
children?: React.ReactNode | RendererChildrenFunction<FormRowRendererProps>;
|
|
571
|
-
renderAs?: 'default' | 'children' | boolean;
|
|
572
592
|
}
|
|
573
|
-
declare function FormRow({ row, className,
|
|
593
|
+
declare function FormRow({ row, className, ...props }: FormRowProps): react_jsx_runtime.JSX.Element;
|
|
574
594
|
|
|
575
|
-
|
|
576
|
-
className?: string;
|
|
577
|
-
children?: React__default.ReactNode | RendererChildrenFunction<FormSubmitButtonRendererProps>;
|
|
578
|
-
renderAs?: 'default' | 'children' | boolean;
|
|
579
|
-
}
|
|
580
|
-
declare function FormSubmitButton({ className, children, renderAs }: FormSubmitButtonProps): string | number | boolean | React__default.ReactElement<any, string | React__default.JSXElementConstructor<any>> | Iterable<React__default.ReactNode> | null | undefined;
|
|
595
|
+
declare function FormSubmitButton({ className, ...props }: ComponentRendererBaseProps<FormSubmitButtonRendererProps>): react_jsx_runtime.JSX.Element;
|
|
581
596
|
|
|
582
|
-
export { type FieldConfig, Form, FormBody,
|
|
597
|
+
export { type FieldConfig, Form, FormBody, form as FormBuilder, FormField, FormProvider, FormRow, FormSubmitButton, createForm, form, useFormContext };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ril,
|
|
2
|
+
import { ril, FieldValidationConfig, FormFieldConfig, FormFieldRow, FormValidationConfig, FormValidator, FormConfiguration, ComponentRendererBaseProps, FormBodyRendererProps, ValidationError, ValidationResult, FormRowRendererProps, FormSubmitButtonRendererProps } from '@rilaykit/core';
|
|
3
3
|
export * from '@rilaykit/core';
|
|
4
|
-
|
|
5
|
-
import * as React$1 from 'react';
|
|
6
|
-
import React__default from 'react';
|
|
4
|
+
import React$1 from 'react';
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
7
|
* Configuration for a form field with type safety
|
|
@@ -16,7 +14,11 @@ import React__default from 'react';
|
|
|
16
14
|
* const fieldConfig: FieldConfig<MyComponents, 'text'> = {
|
|
17
15
|
* type: 'text',
|
|
18
16
|
* props: { placeholder: 'Enter your name' },
|
|
19
|
-
* validation: {
|
|
17
|
+
* validation: {
|
|
18
|
+
* validators: [required(), minLength(2)],
|
|
19
|
+
* validateOnChange: true,
|
|
20
|
+
* validateOnBlur: true
|
|
21
|
+
* }
|
|
20
22
|
* };
|
|
21
23
|
* ```
|
|
22
24
|
*/
|
|
@@ -27,28 +29,9 @@ type FieldConfig<C extends Record<string, any>, T extends keyof C> = {
|
|
|
27
29
|
type: T;
|
|
28
30
|
/** Component-specific properties */
|
|
29
31
|
props?: Partial<C[T]>;
|
|
30
|
-
/** Validation
|
|
31
|
-
validation?:
|
|
32
|
-
/** Conditional display logic */
|
|
33
|
-
conditional?: ConditionalConfig;
|
|
32
|
+
/** Validation configuration for this field */
|
|
33
|
+
validation?: FieldValidationConfig;
|
|
34
34
|
};
|
|
35
|
-
/**
|
|
36
|
-
* Options for configuring row layout and appearance
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```typescript
|
|
40
|
-
* const rowOptions: RowOptions = {
|
|
41
|
-
* spacing: 'loose',
|
|
42
|
-
* alignment: 'center'
|
|
43
|
-
* };
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
interface RowOptions {
|
|
47
|
-
/** Spacing between fields in the row */
|
|
48
|
-
spacing?: 'tight' | 'normal' | 'loose';
|
|
49
|
-
/** Vertical alignment of fields in the row */
|
|
50
|
-
alignment?: 'start' | 'center' | 'end' | 'stretch';
|
|
51
|
-
}
|
|
52
35
|
/**
|
|
53
36
|
* Form builder class for creating type-safe form configurations
|
|
54
37
|
*
|
|
@@ -73,7 +56,6 @@ interface RowOptions {
|
|
|
73
56
|
* @remarks
|
|
74
57
|
* - Supports up to 3 fields per row for optimal layout
|
|
75
58
|
* - Automatically generates unique IDs for fields and rows
|
|
76
|
-
* - Provides comprehensive validation before building
|
|
77
59
|
* - Maintains type safety throughout the building process
|
|
78
60
|
*/
|
|
79
61
|
declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
@@ -85,6 +67,8 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
85
67
|
private formId;
|
|
86
68
|
/** Generator for creating unique IDs */
|
|
87
69
|
private idGenerator;
|
|
70
|
+
/** Form-level validation configuration */
|
|
71
|
+
private formValidation?;
|
|
88
72
|
/**
|
|
89
73
|
* Creates a new form builder instance
|
|
90
74
|
*
|
|
@@ -112,11 +96,15 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
112
96
|
*/
|
|
113
97
|
static create<Cm extends Record<string, any> = Record<string, never>>(config: ril<Cm>, formId?: string): form<Cm>;
|
|
114
98
|
/**
|
|
115
|
-
* Converts a FieldConfig to a FormFieldConfig
|
|
99
|
+
* Converts a FieldConfig to a FormFieldConfig
|
|
116
100
|
*
|
|
117
101
|
* This internal method handles the transformation from the builder's field
|
|
118
102
|
* configuration format to the final form field configuration, including
|
|
119
|
-
* component lookup, prop merging, and
|
|
103
|
+
* component lookup, prop merging, ID generation, and validation setup.
|
|
104
|
+
*
|
|
105
|
+
* The validation system combines component-level validation (defined in the component config)
|
|
106
|
+
* with field-level validation (defined in the field config). Component validators are
|
|
107
|
+
* applied first, followed by field validators.
|
|
120
108
|
*
|
|
121
109
|
* @template T - The component type
|
|
122
110
|
* @param fieldConfig - The field configuration to convert
|
|
@@ -129,7 +117,7 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
129
117
|
/**
|
|
130
118
|
* Creates a form row with the specified fields and options
|
|
131
119
|
*
|
|
132
|
-
* This internal method handles row creation
|
|
120
|
+
* This internal method handles row creation,
|
|
133
121
|
* proper spacing, and alignment configuration.
|
|
134
122
|
*
|
|
135
123
|
* @template T - The component type
|
|
@@ -176,7 +164,7 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
176
164
|
* ```
|
|
177
165
|
*/
|
|
178
166
|
add<T extends keyof C & string>(...fields: FieldConfig<C, T>[]): this;
|
|
179
|
-
add<T extends keyof C & string>(fields: FieldConfig<C, T>[]
|
|
167
|
+
add<T extends keyof C & string>(fields: FieldConfig<C, T>[]): this;
|
|
180
168
|
/**
|
|
181
169
|
* Adds multiple fields on separate rows
|
|
182
170
|
*
|
|
@@ -196,10 +184,10 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
196
184
|
* { type: 'text', props: { label: 'Field 1' } },
|
|
197
185
|
* { type: 'text', props: { label: 'Field 2' } },
|
|
198
186
|
* { type: 'text', props: { label: 'Field 3' } }
|
|
199
|
-
* ]
|
|
187
|
+
* ]);
|
|
200
188
|
* ```
|
|
201
189
|
*/
|
|
202
|
-
addSeparateRows<T extends keyof C & string>(fieldConfigs: FieldConfig<C, T>[]
|
|
190
|
+
addSeparateRows<T extends keyof C & string>(fieldConfigs: FieldConfig<C, T>[]): this;
|
|
203
191
|
/**
|
|
204
192
|
* Sets the form identifier
|
|
205
193
|
*
|
|
@@ -215,8 +203,7 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
215
203
|
/**
|
|
216
204
|
* Updates an existing field's configuration
|
|
217
205
|
*
|
|
218
|
-
* This method allows you to modify field properties
|
|
219
|
-
* or conditional logic after the field has been added to the form.
|
|
206
|
+
* This method allows you to modify field properties after the field has been added to the form.
|
|
220
207
|
*
|
|
221
208
|
* @param fieldId - The unique identifier of the field to update
|
|
222
209
|
* @param updates - Partial field configuration with updates to apply
|
|
@@ -227,7 +214,6 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
227
214
|
* ```typescript
|
|
228
215
|
* builder.updateField('email-field', {
|
|
229
216
|
* props: { placeholder: 'Enter your email address' },
|
|
230
|
-
* validation: { required: true, email: true }
|
|
231
217
|
* });
|
|
232
218
|
* ```
|
|
233
219
|
*/
|
|
@@ -319,6 +305,70 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
319
305
|
* ```
|
|
320
306
|
*/
|
|
321
307
|
clear(): this;
|
|
308
|
+
/**
|
|
309
|
+
* Configures validation for the entire form
|
|
310
|
+
*
|
|
311
|
+
* This method sets up form-level validation that will be applied when the
|
|
312
|
+
* form is submitted or when validation is explicitly triggered. Form validators
|
|
313
|
+
* receive all form data and can perform cross-field validation.
|
|
314
|
+
*
|
|
315
|
+
* @param validationConfig - Form validation configuration
|
|
316
|
+
* @returns The form builder instance for method chaining
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* ```typescript
|
|
320
|
+
* builder.setValidation({
|
|
321
|
+
* validators: [
|
|
322
|
+
* (formData, context) => {
|
|
323
|
+
* if (!formData.email && !formData.phone) {
|
|
324
|
+
* return createErrorResult('Either email or phone is required');
|
|
325
|
+
* }
|
|
326
|
+
* return createSuccessResult();
|
|
327
|
+
* }
|
|
328
|
+
* ],
|
|
329
|
+
* validateOnSubmit: true
|
|
330
|
+
* });
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
setValidation(validationConfig: FormValidationConfig): this;
|
|
334
|
+
/**
|
|
335
|
+
* Adds validators to the form-level validation
|
|
336
|
+
*
|
|
337
|
+
* This method allows adding validators to an existing validation configuration
|
|
338
|
+
* without replacing the entire configuration.
|
|
339
|
+
*
|
|
340
|
+
* @param validators - Array of form validators to add
|
|
341
|
+
* @returns The form builder instance for method chaining
|
|
342
|
+
*
|
|
343
|
+
* @example
|
|
344
|
+
* ```typescript
|
|
345
|
+
* builder.addValidators([
|
|
346
|
+
* customFormValidator,
|
|
347
|
+
* anotherFormValidator
|
|
348
|
+
* ]);
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
addValidators(validators: FormValidator[]): this;
|
|
352
|
+
/**
|
|
353
|
+
* Adds validation to a specific field by ID
|
|
354
|
+
*
|
|
355
|
+
* This method allows adding validation to a field after it has been created,
|
|
356
|
+
* useful for dynamic validation requirements.
|
|
357
|
+
*
|
|
358
|
+
* @param fieldId - The ID of the field to add validation to
|
|
359
|
+
* @param validationConfig - Field validation configuration
|
|
360
|
+
* @returns The form builder instance for method chaining
|
|
361
|
+
* @throws Error if the field with the specified ID is not found
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```typescript
|
|
365
|
+
* builder.addFieldValidation('email', {
|
|
366
|
+
* validators: [required(), email()],
|
|
367
|
+
* validateOnBlur: true
|
|
368
|
+
* });
|
|
369
|
+
* ```
|
|
370
|
+
*/
|
|
371
|
+
addFieldValidation(fieldId: string, validationConfig: FieldValidationConfig): this;
|
|
322
372
|
/**
|
|
323
373
|
* Creates a deep copy of the current form builder
|
|
324
374
|
*
|
|
@@ -338,47 +388,24 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
338
388
|
*/
|
|
339
389
|
clone(newFormId?: string): form<C>;
|
|
340
390
|
/**
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
* This method performs comprehensive validation of the form structure,
|
|
344
|
-
* checking for common issues like duplicate IDs, missing components,
|
|
345
|
-
* and invalid row configurations.
|
|
391
|
+
* Checks the current form configuration for basic structural issues.
|
|
346
392
|
*
|
|
347
|
-
* @returns Array of
|
|
348
|
-
*
|
|
349
|
-
* @example
|
|
350
|
-
* ```typescript
|
|
351
|
-
* const errors = builder.validate();
|
|
352
|
-
* if (errors.length > 0) {
|
|
353
|
-
* console.error('Form validation failed:', errors);
|
|
354
|
-
* }
|
|
355
|
-
* ```
|
|
356
|
-
*
|
|
357
|
-
* @remarks
|
|
358
|
-
* Validation checks include:
|
|
359
|
-
* - Duplicate field IDs across the form
|
|
360
|
-
* - Missing component definitions for referenced types
|
|
361
|
-
* - Row constraints (max 3 fields, no empty rows)
|
|
393
|
+
* @returns Array of error messages (empty if valid)
|
|
362
394
|
*/
|
|
363
395
|
validate(): string[];
|
|
364
396
|
/**
|
|
365
397
|
* Builds the final form configuration
|
|
366
398
|
*
|
|
367
|
-
* This method
|
|
399
|
+
* This method creates the complete form
|
|
368
400
|
* configuration object ready for rendering. It includes all field
|
|
369
|
-
* configurations, render settings, and metadata.
|
|
401
|
+
* configurations, render settings, validation configuration, and metadata.
|
|
370
402
|
*
|
|
371
403
|
* @returns Complete form configuration ready for use
|
|
372
|
-
* @throws Error if validation fails
|
|
373
404
|
*
|
|
374
405
|
* @example
|
|
375
406
|
* ```typescript
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
* // Use formConfig with your form renderer
|
|
379
|
-
* } catch (error) {
|
|
380
|
-
* console.error('Failed to build form:', error.message);
|
|
381
|
-
* }
|
|
407
|
+
* const formConfig = builder.build();
|
|
408
|
+
* // Use formConfig with your form renderer
|
|
382
409
|
* ```
|
|
383
410
|
*
|
|
384
411
|
* @remarks
|
|
@@ -388,6 +415,7 @@ declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
|
388
415
|
* - Flattened array of all fields for easy access
|
|
389
416
|
* - Component configuration reference
|
|
390
417
|
* - Render configuration for customization
|
|
418
|
+
* - Form-level validation configuration
|
|
391
419
|
*/
|
|
392
420
|
build(): FormConfiguration;
|
|
393
421
|
/**
|
|
@@ -516,12 +544,7 @@ interface FormProps {
|
|
|
516
544
|
}
|
|
517
545
|
declare function Form({ formConfig, defaultValues, onSubmit, onFieldChange, children }: FormProps): react_jsx_runtime.JSX.Element;
|
|
518
546
|
|
|
519
|
-
|
|
520
|
-
className?: string;
|
|
521
|
-
children?: React.ReactNode | RendererChildrenFunction<FormBodyRendererProps>;
|
|
522
|
-
renderAs?: 'default' | 'children' | boolean;
|
|
523
|
-
}
|
|
524
|
-
declare function FormBody({ className, children, renderAs }: FormBodyProps): string | number | boolean | React$1.ReactElement<any, string | React$1.JSXElementConstructor<any>> | Iterable<React$1.ReactNode> | null | undefined;
|
|
547
|
+
declare function FormBody({ className, ...props }: ComponentRendererBaseProps<FormBodyRendererProps>): react_jsx_runtime.JSX.Element;
|
|
525
548
|
|
|
526
549
|
interface FormFieldProps {
|
|
527
550
|
fieldId: string;
|
|
@@ -534,27 +557,27 @@ declare function FormField({ fieldId, disabled, customProps, className, }: FormF
|
|
|
534
557
|
interface FormState {
|
|
535
558
|
values: Record<string, any>;
|
|
536
559
|
errors: Record<string, ValidationError[]>;
|
|
537
|
-
|
|
538
|
-
|
|
560
|
+
validationState: Record<string, 'idle' | 'validating' | 'valid' | 'invalid'>;
|
|
561
|
+
touched: Record<string, boolean>;
|
|
539
562
|
isDirty: boolean;
|
|
540
|
-
isValid: boolean;
|
|
541
563
|
isSubmitting: boolean;
|
|
564
|
+
isValid: boolean;
|
|
542
565
|
}
|
|
543
566
|
interface FormContextValue {
|
|
544
567
|
formState: FormState;
|
|
545
568
|
formConfig: FormConfiguration;
|
|
546
569
|
setValue: (fieldId: string, value: any) => void;
|
|
547
|
-
|
|
548
|
-
clearError: (fieldId: string) => void;
|
|
549
|
-
markFieldTouched: (fieldId: string) => void;
|
|
550
|
-
setFieldValidating: (fieldId: string, isValidating: boolean) => void;
|
|
570
|
+
setFieldTouched: (fieldId: string, touched?: boolean) => void;
|
|
551
571
|
validateField: (fieldId: string, value?: any) => Promise<ValidationResult>;
|
|
552
|
-
|
|
572
|
+
validateForm: () => Promise<ValidationResult>;
|
|
573
|
+
isFormValid: () => boolean;
|
|
553
574
|
reset: (values?: Record<string, any>) => void;
|
|
554
|
-
submit: (event?:
|
|
575
|
+
submit: (event?: React$1.FormEvent) => Promise<boolean>;
|
|
576
|
+
setError: (fieldId: string, errors: ValidationError[]) => void;
|
|
577
|
+
clearError: (fieldId: string) => void;
|
|
555
578
|
}
|
|
556
579
|
interface FormProviderProps {
|
|
557
|
-
children:
|
|
580
|
+
children: React$1.ReactNode;
|
|
558
581
|
formConfig: FormConfiguration;
|
|
559
582
|
defaultValues?: Record<string, any>;
|
|
560
583
|
onSubmit?: (data: Record<string, any>) => void | Promise<void>;
|
|
@@ -564,19 +587,11 @@ interface FormProviderProps {
|
|
|
564
587
|
declare function FormProvider({ children, formConfig, defaultValues, onSubmit, onFieldChange, className, }: FormProviderProps): react_jsx_runtime.JSX.Element;
|
|
565
588
|
declare function useFormContext(): FormContextValue;
|
|
566
589
|
|
|
567
|
-
interface FormRowProps {
|
|
590
|
+
interface FormRowProps extends ComponentRendererBaseProps<FormRowRendererProps> {
|
|
568
591
|
row: FormFieldRow;
|
|
569
|
-
className?: string;
|
|
570
|
-
children?: React.ReactNode | RendererChildrenFunction<FormRowRendererProps>;
|
|
571
|
-
renderAs?: 'default' | 'children' | boolean;
|
|
572
592
|
}
|
|
573
|
-
declare function FormRow({ row, className,
|
|
593
|
+
declare function FormRow({ row, className, ...props }: FormRowProps): react_jsx_runtime.JSX.Element;
|
|
574
594
|
|
|
575
|
-
|
|
576
|
-
className?: string;
|
|
577
|
-
children?: React__default.ReactNode | RendererChildrenFunction<FormSubmitButtonRendererProps>;
|
|
578
|
-
renderAs?: 'default' | 'children' | boolean;
|
|
579
|
-
}
|
|
580
|
-
declare function FormSubmitButton({ className, children, renderAs }: FormSubmitButtonProps): string | number | boolean | React__default.ReactElement<any, string | React__default.JSXElementConstructor<any>> | Iterable<React__default.ReactNode> | null | undefined;
|
|
595
|
+
declare function FormSubmitButton({ className, ...props }: ComponentRendererBaseProps<FormSubmitButtonRendererProps>): react_jsx_runtime.JSX.Element;
|
|
581
596
|
|
|
582
|
-
export { type FieldConfig, Form, FormBody,
|
|
597
|
+
export { type FieldConfig, Form, FormBody, form as FormBuilder, FormField, FormProvider, FormRow, FormSubmitButton, createForm, form, useFormContext };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var core=require('@rilaykit/core'),se=require('react'),jsxRuntime=require('react/jsx-runtime');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var se__default=/*#__PURE__*/_interopDefault(se);var w=class t{constructor(e,r){this.rows=[];this.idGenerator=new core.IdGenerator;this.config=e,this.formId=r||`form-${Date.now()}`;}static create(e,r){return new t(e,r)}createFormField(e){let r=this.config.getComponent(e.type);if(!r)throw new Error(`No component found with type "${e.type}"`);return {id:e.id||this.idGenerator.next("field"),componentId:r.id,props:{...r.defaultProps,...e.props},validation:e.validation,conditional:e.conditional}}createRow(e,r){if(e.length===0)throw new Error("At least one field is required");if(e.length>3)throw new Error("Maximum 3 fields per row");let n=e.map(o=>this.createFormField(o));return {id:this.idGenerator.next("row"),fields:n,maxColumns:e.length,spacing:r?.spacing||"normal",alignment:r?.alignment||"stretch"}}add(...e){let r,n,o=false;if(e.length===1&&Array.isArray(e[0])?(r=e[0],o=true):e.length===2&&Array.isArray(e[0])?(r=e[0],n=e[1],o=true):r=e,r.length===0)throw new Error("At least one field is required");if(o&&r.length>3)throw new Error("Maximum 3 fields per row");if(r.length===1){let c=this.createRow(r,n);return this.rows.push(c),this}if(r.length<=3){let c=this.createRow(r,n);return this.rows.push(c),this}for(let c of r){let f=this.createRow([c],n);this.rows.push(f);}return this}addSeparateRows(e,r){for(let n of e)this.add([n],r);return this}setId(e){return this.formId=e,this}updateField(e,r){let n=this.findField(e);if(!n)throw new Error(`Field with ID "${e}" not found`);return Object.assign(n,{...r,props:{...n.props,...r.props}}),this}findField(e){for(let r of this.rows){let n=r.fields.find(o=>o.id===e);if(n)return n}return null}removeField(e){return this.rows=this.rows.map(r=>({...r,fields:r.fields.filter(n=>n.id!==e)})).filter(r=>r.fields.length>0),this}getField(e){return this.findField(e)||void 0}getFields(){return this.rows.flatMap(e=>e.fields)}getRows(){return [...this.rows]}clear(){return this.rows=[],this.idGenerator.reset(),this}clone(e){let r=new t(this.config,e||`${this.formId}-clone`);return r.rows=core.deepClone(this.rows),r}validate(){let e=new core.ValidationErrorBuilder,r=this.getFields(),n=r.map(o=>o.id);try{core.ensureUnique(n,"field");}catch(o){e.add("DUPLICATE_FIELD_IDS",o instanceof Error?o.message:String(o));}for(let o of r)e.addIf(!this.config.hasComponent(o.componentId),"MISSING_COMPONENT",`Component "${o.componentId}" not found for field "${o.id}"`);for(let o of this.rows)e.addIf(o.fields.length>3,"TOO_MANY_FIELDS_IN_ROW",`Row "${o.id}" has ${o.fields.length} fields, maximum is 3`),e.addIf(o.fields.length===0,"EMPTY_ROW",`Row "${o.id}" is empty`);return e.build().map(o=>o.message)}build(){let e=this.validate();if(e.length>0)throw new Error(`Form validation failed: ${e.join(", ")}`);return {id:this.formId,rows:[...this.rows],allFields:this.getFields(),config:this.config,renderConfig:this.config.getFormRenderConfig()}}toJSON(){return {id:this.formId,rows:this.rows}}fromJSON(e){return e.id&&(this.formId=e.id),e.rows&&(this.rows=e.rows),this}getStats(){let e=this.getFields(),r=this.rows.map(n=>n.fields.length);return {totalFields:e.length,totalRows:this.rows.length,averageFieldsPerRow:this.rows.length>0?e.length/this.rows.length:0,maxFieldsInRow:r.length>0?Math.max(...r):0,minFieldsInRow:r.length>0?Math.min(...r):0}}};function j(t,e){return w.create(t,e)}core.ril.prototype.form=function(t){return w.create(this,t)};function te(t,e){switch(e.type){case "SET_VALUE":{let r={...t.values,[e.fieldId]:e.value};return {...t,values:r,isDirty:true}}case "SET_ERROR":return {...t,errors:{...t.errors,[e.fieldId]:e.errors},isValid:false};case "CLEAR_ERROR":{let r={...t.errors};return delete r[e.fieldId],{...t,errors:r}}case "MARK_TOUCHED":return {...t,touched:new Set([...t.touched,e.fieldId])};case "SET_VALIDATING":{let r=new Set(t.isValidating);return e.isValidating?r.add(e.fieldId):r.delete(e.fieldId),{...t,isValidating:r}}case "SET_SUBMITTING":return {...t,isSubmitting:e.isSubmitting};case "RESET":return {values:e.values||{},errors:{},touched:new Set,isValidating:new Set,isDirty:false,isValid:true,isSubmitting:false};case "UPDATE_VALIDATION_STATE":{let r=Object.keys(t.errors).some(n=>t.errors[n].length>0);return {...t,isValid:!r}}default:return t}}var L=se.createContext(null);function D({children:t,formConfig:e,defaultValues:r={},onSubmit:n,onFieldChange:o,className:c}){let f={values:r,errors:{},touched:new Set,isValidating:new Set,isDirty:false,isValid:true,isSubmitting:false},[d,a]=se.useReducer(te,f),i=se.useRef(new Map),m=se.useRef(n),l=se.useRef(o);m.current=n,l.current=o;let C=se.useCallback((s,u)=>{a({type:"SET_ERROR",fieldId:s,errors:u}),a({type:"UPDATE_VALIDATION_STATE"});},[]),v=se.useCallback(s=>{a({type:"CLEAR_ERROR",fieldId:s}),a({type:"UPDATE_VALIDATION_STATE"});},[]),S=se.useCallback(s=>{a({type:"MARK_TOUCHED",fieldId:s});},[]),p=se.useCallback((s,u)=>{a({type:"SET_VALIDATING",fieldId:s,isValidating:u});},[]),V=se.useCallback((s,u)=>{if(a({type:"SET_VALUE",fieldId:s,value:u}),d.errors[s]&&d.errors[s].length>0&&(a({type:"CLEAR_ERROR",fieldId:s}),a({type:"UPDATE_VALIDATION_STATE"})),l.current){let g={...d.values,[s]:u};l.current(s,u,g);}},[d.errors,d.values]),h=se.useMemo(()=>e,[e]),T=se.useCallback(async(s,u)=>{let g=h.allFields.find(I=>I.id===s);if(!g?.validation?.validator)return {isValid:true,errors:[]};let J=u!==void 0?u:d.values[s],O=i.current.get(s);O&&clearTimeout(O);let K={fieldId:s,formData:d.values,fieldProps:g.props,touched:d.touched.has(s),dirty:d.isDirty},N=g.validation.debounceMs||0;return new Promise(I=>{let B=async()=>{p(s,true);try{let y=await g.validation.validator(J,K,g.props);y.errors.length>0?C(s,y.errors):v(s),I(y);}catch(y){let k={isValid:false,errors:[{code:"validation_error",message:y instanceof Error?y.message:"Validation error"}]};C(s,k.errors),I(k);}finally{p(s,false);}};if(N>0){let y=setTimeout(B,N);i.current.set(s,y);}else B();})},[h,d.values,d.touched,d.isDirty,C,v,p]),E=se.useCallback(async()=>{let s=h.allFields.map(g=>T(g.id));return (await Promise.all(s)).every(g=>g.isValid)},[h,T]),b=se.useCallback(s=>{a({type:"RESET",values:s});},[]),x=se.useCallback(async s=>{if(s?.preventDefault(),!m.current)return true;a({type:"SET_SUBMITTING",isSubmitting:true});try{return await E()?(await m.current(d.values),!0):!1}catch(u){return console.error("Error during form submission:",u),false}finally{a({type:"SET_SUBMITTING",isSubmitting:false});}},[d.values,E]),A=se.useMemo(()=>({formState:d,formConfig:h,setValue:V,setError:C,clearError:v,markFieldTouched:S,setFieldValidating:p,validateField:T,validateAllFields:E,reset:b,submit:x}),[d,h,V,C,v,S,p,T,E,b,x]);return jsxRuntime.jsx(L.Provider,{value:A,children:jsxRuntime.jsx("form",{onSubmit:x,className:c,noValidate:true,children:t})})}function R(){let t=se.useContext(L);if(!t)throw new Error("useFormContext must be used within a FormProvider");return t}function ie({formConfig:t,defaultValues:e,onSubmit:r,onFieldChange:n,children:o}){let c=t instanceof w?t.build():t;return jsxRuntime.jsx(D,{formConfig:c,defaultValues:e,onSubmit:r,onFieldChange:n,children:o})}function G({fieldId:t,disabled:e=false,customProps:r={},className:n}){let{formState:o,formConfig:c,setValue:f,markFieldTouched:d,validateField:a}=R(),i=se.useMemo(()=>c.allFields.find(s=>s.id===t),[c.allFields,t]);if(!i)throw new Error(`Field with ID "${t}" not found`);let m=se.useMemo(()=>c.config.getComponent(i.componentId),[c.config,i.componentId]);if(!m)throw new Error(`Component with ID "${i.componentId}" not found`);let l=se.useMemo(()=>({value:o.values[i.id],errors:o.errors[i.id]||[],touched:o.touched.has(i.id),validating:o.isValidating.has(i.id)}),[o.values,o.errors,o.touched,o.isValidating,i.id]),C=se.useCallback(s=>{let u=l.errors.length>0;f(i.id,s),(i.validation?.validateOnChange||u&&i.validation?.validator||l.touched&&i.validation?.validator)&&a(i.id,s);},[i.id,i.validation,f,a,l.errors.length,l.touched]),v=se.useCallback(()=>{d(i.id),(i.validation?.validateOnBlur||i.validation?.validator)&&a(i.id);},[i.id,i.validation,d,a]),S=se.useMemo(()=>{if(!i.conditional)return true;try{return i.conditional.condition(o.values)}catch(s){return console.warn(`Conditional evaluation failed for field "${i.id}":`,s),true}},[i.conditional,o.values,i.id]),p=se.useMemo(()=>{if(!i.conditional||!S)return {};switch(i.conditional.action){case "disable":return {disabled:true};case "require":return {required:true};default:return {}}},[i.conditional,S]),V=!S&&i.conditional?.action==="hide",h=se.useMemo(()=>({...m.defaultProps??{},...i.props,...r,...p}),[m.defaultProps,i.props,r,p]),T=se.useMemo(()=>({id:i.id,props:h,value:l.value,onChange:C,onBlur:v,error:l.errors,touched:l.touched,disabled:e||p.disabled,isValidating:l.validating}),[i.id,h,l.value,C,v,l.errors,l.touched,e,p.disabled,l.validating]),E=c.renderConfig?.fieldRenderer,b=m.renderer(T),x=m.useFieldRenderer!==false,A=E&&x?E({children:b,id:i.id,error:l.errors,touched:l.touched,disabled:e||p.disabled,isValidating:l.validating,...h}):b;return jsxRuntime.jsx("div",{className:n,"data-field-id":i.id,"data-field-type":m.type,style:V?{display:"none !important"}:void 0,children:A})}var q=se__default.default.memo(G);function W({row:t,className:e,children:r,renderAs:n}){let{formConfig:o}=R(),c=t.fields.map(l=>jsxRuntime.jsx(q,{fieldId:l.id},l.id));if(n==="children"||n===true){if(typeof r!="function")throw new Error('When renderAs="children" is used, children must be a function that returns React elements');let l={row:t,children:c,className:e,spacing:t.spacing,alignment:t.alignment};return r(l)}let d=o.renderConfig?.rowRenderer;if(!d)throw new Error(`No rowRenderer configured for form "${o.id}". Please configure a rowRenderer using config.setRowRenderer() or config.setFormRenderConfig().`);let a={row:t,children:c,className:e,spacing:t.spacing,alignment:t.alignment},i=core.resolveRendererChildren(r,a),m={...a,children:i||c};return d(m)}var H=W;function ue({className:t,children:e,renderAs:r}){let{formConfig:n}=R(),o=se.useMemo(()=>n.rows.map(m=>jsxRuntime.jsx(H,{row:m},m.id)),[n.rows]);if(r==="children"||r===true){if(typeof e!="function")throw new Error('When renderAs="children" is used, children must be a function that returns React elements');return e({formConfig:n,children:o,className:t})}let f=n.renderConfig?.bodyRenderer;if(!f)throw new Error(`No bodyRenderer configured for form "${n.id}". Please configure a bodyRenderer using config.setBodyRenderer() or config.setFormRenderConfig().`);let d={formConfig:n,children:o,className:t},a=core.resolveRendererChildren(e,d),i={...d,children:a||o};return f(i)}function ge({className:t,children:e,renderAs:r}){let{formState:n,submit:o,formConfig:c}=R(),f={isSubmitting:n.isSubmitting,isValid:n.isValid,isDirty:n.isDirty,onSubmit:o,className:t};if(r==="children"||r===true){if(typeof e!="function")throw new Error('When renderAs="children" is used, children must be a function that returns React elements');return e(f)}let a=c.renderConfig?.submitButtonRenderer;if(!a)throw new Error(`No submitButtonRenderer configured for form "${c.id}". Please configure a submitButtonRenderer using config.setSubmitButtonRenderer() or config.setFormRenderConfig().`);let i=core.resolveRendererChildren(e,f),m={...f,children:i};return a(m)}Object.defineProperty(exports,"createZodValidator",{enumerable:true,get:function(){return core.createZodValidator}});Object.defineProperty(exports,"ril",{enumerable:true,get:function(){return core.ril}});exports.Form=ie;exports.FormBody=ue;exports.FormBuilder=w;exports.FormField=G;exports.FormProvider=D;exports.FormRow=W;exports.FormSubmitButton=ge;exports.createForm=j;exports.form=w;exports.useFormContext=R;
|
|
1
|
+
'use strict';var se=require('react'),core=require('@rilaykit/core'),jsxRuntime=require('react/jsx-runtime');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var se__default=/*#__PURE__*/_interopDefault(se);var v=class t{constructor(e,r){this.rows=[];this.idGenerator=new core.IdGenerator;this.config=e,this.formId=r||`form-${Date.now()}`;}static create(e,r){return new t(e,r)}createFormField(e){let r=this.config.getComponent(e.type);if(!r)throw new Error(`No component found with type "${e.type}"`);let i;return (r.validation||e.validation)&&(i={validateOnChange:e.validation?.validateOnChange??r.validation?.validateOnChange,validateOnBlur:e.validation?.validateOnBlur??r.validation?.validateOnBlur,debounceMs:e.validation?.debounceMs??r.validation?.debounceMs,validators:[...r.validation?.validators||[],...e.validation?.validators||[]]}),{id:e.id||this.idGenerator.next("field"),componentId:r.id,props:{...r.defaultProps,...e.props},validation:i}}createRow(e){if(e.length===0)throw new Error("At least one field is required");if(e.length>3)throw new Error("Maximum 3 fields per row");let r=e.map(i=>this.createFormField(i));return {id:this.idGenerator.next("row"),fields:r,maxColumns:e.length}}add(...e){let r,i=false;if(e.length===1&&Array.isArray(e[0])?(r=e[0],i=true):r=e,r.length===0)throw new Error("At least one field is required");if(i&&r.length>3)throw new Error("Maximum 3 fields per row");if(r.length===1){let o=this.createRow(r);return this.rows.push(o),this}if(r.length<=3){let o=this.createRow(r);return this.rows.push(o),this}for(let o of r){let m=this.createRow([o]);this.rows.push(m);}return this}addSeparateRows(e){for(let r of e)this.add(r);return this}setId(e){return this.formId=e,this}updateField(e,r){let i=this.findField(e);if(!i)throw new Error(`Field with ID "${e}" not found`);return Object.assign(i,{...r,props:{...i.props,...r.props}}),this}findField(e){for(let r of this.rows){let i=r.fields.find(o=>o.id===e);if(i)return i}return null}removeField(e){return this.rows=this.rows.map(r=>({...r,fields:r.fields.filter(i=>i.id!==e)})).filter(r=>r.fields.length>0),this}getField(e){return this.findField(e)||void 0}getFields(){return this.rows.flatMap(e=>e.fields)}getRows(){return [...this.rows]}clear(){return this.rows=[],this.idGenerator.reset(),this}setValidation(e){return this.formValidation=e,this}addValidators(e){return this.formValidation||(this.formValidation={validators:[]}),this.formValidation={...this.formValidation,validators:[...this.formValidation.validators||[],...e]},this}addFieldValidation(e,r){let i=this.findField(e);if(!i)throw new Error(`Field with ID "${e}" not found`);let o={...i.validation,...r,validators:[...i.validation?.validators||[],...r.validators||[]]};return this.updateField(e,{validation:o})}clone(e){let r=new t(this.config,e||`${this.formId}-clone`);return r.rows=core.deepClone(this.rows),r}validate(){let e=[],r=this.getFields(),i=r.map(o=>o.id);try{core.ensureUnique(i,"field");}catch(o){e.push(o instanceof Error?o.message:String(o));}for(let o of r)this.config.hasComponent(o.componentId)||e.push(`Component "${o.componentId}" not found for field "${o.id}"`);for(let o of this.rows)o.fields.length>3&&e.push(`Row "${o.id}" has ${o.fields.length} fields, maximum is 3`),o.fields.length===0&&e.push(`Row "${o.id}" is empty`);return e}build(){let e=this.validate();if(e.length>0)throw new Error(`Form validation failed: ${e.join(", ")}`);return {id:this.formId,rows:[...this.rows],allFields:this.getFields(),config:this.config,renderConfig:this.config.getFormRenderConfig(),validation:this.formValidation}}toJSON(){return {id:this.formId,rows:this.rows}}fromJSON(e){return e.id&&(this.formId=e.id),e.rows&&(this.rows=e.rows),this}getStats(){let e=this.getFields(),r=this.rows.map(i=>i.fields.length);return {totalFields:e.length,totalRows:this.rows.length,averageFieldsPerRow:this.rows.length>0?e.length/this.rows.length:0,maxFieldsInRow:r.length>0?Math.max(...r):0,minFieldsInRow:r.length>0?Math.min(...r):0}}};function Z(t,e){return v.create(t,e)}core.ril.prototype.form=function(t){return v.create(this,t)};function k(){return {isValid:true,errors:[]}}function te(t,e){switch(e.type){case "SET_VALUE":{let r={...t.values,[e.fieldId]:e.value};return {...t,values:r,isDirty:true}}case "SET_FIELD_ERRORS":return {...t,errors:{...t.errors,[e.fieldId]:e.errors},touched:{...t.touched,[e.fieldId]:true}};case "SET_FIELD_VALIDATION_STATE":return {...t,validationState:{...t.validationState,[e.fieldId]:e.state}};case "SET_FIELD_TOUCHED":return {...t,touched:{...t.touched,[e.fieldId]:e.touched}};case "SET_SUBMITTING":return {...t,isSubmitting:e.isSubmitting};case "SET_FORM_VALIDITY":return {...t,isValid:e.isValid};case "RESET":return {values:e.values||{},errors:{},validationState:{},touched:{},isDirty:false,isSubmitting:false,isValid:true};default:return t}}var U=se.createContext(null);function B({children:t,formConfig:e,defaultValues:r={},onSubmit:i,onFieldChange:o,className:m}){let R={values:r,errors:{},validationState:{},touched:{},isDirty:false,isSubmitting:false,isValid:true},[s,d]=se.useReducer(te,R),a=se.useRef(i),c=se.useRef(o),f=se.useRef(r);a.current=i,c.current=o,se.useEffect(()=>{JSON.stringify(f.current)!==JSON.stringify(r)&&(f.current=r,d({type:"RESET",values:r}));},[r]);let E=se.useCallback((n,l)=>{d({type:"SET_VALUE",fieldId:n,value:l}),c.current?.(n,l,{...s.values,[n]:l});},[s.values]),S=se.useCallback((n,l=true)=>{d({type:"SET_FIELD_TOUCHED",fieldId:n,touched:l});},[]),g=se.useCallback(async(n,l)=>{let p=e.allFields.find(u=>u.id===n);if(!p||!p.validation?.validators)return k();let y=l!==void 0?l:s.values[n],_=core.createValidationContext({fieldId:n,formId:e.id,allFormData:{...s.values,[n]:y}});d({type:"SET_FIELD_VALIDATION_STATE",fieldId:n,state:"validating"});try{let u=await core.runValidatorsAsync(p.validation.validators,y,_);return d({type:"SET_FIELD_ERRORS",fieldId:n,errors:u.errors}),d({type:"SET_FIELD_VALIDATION_STATE",fieldId:n,state:u.isValid?"valid":"invalid"}),u}catch(u){let x={isValid:false,errors:[{message:u instanceof Error?u.message:"Validation failed",code:"VALIDATION_ERROR"}]};return d({type:"SET_FIELD_ERRORS",fieldId:n,errors:x.errors}),d({type:"SET_FIELD_VALIDATION_STATE",fieldId:n,state:"invalid"}),x}},[e,s.values]),w=se.useCallback(async()=>{let n=e.allFields.filter(u=>u.validation?.validators).map(u=>g(u.id)),l=await Promise.all(n),p=l.some(u=>!u.isValid),y=k();if(e.validation?.validators){let u=core.createValidationContext({formId:e.id,allFormData:s.values});try{y=await core.runValidatorsAsync(e.validation.validators,s.values,u);}catch(x){y={isValid:false,errors:[{message:x instanceof Error?x.message:"Form validation failed",code:"FORM_VALIDATION_ERROR"}]};}}let _={isValid:!p&&y.isValid,errors:[...l.flatMap(u=>u.errors),...y.errors]};return d({type:"SET_FORM_VALIDITY",isValid:_.isValid}),_},[e,s.values,g]),T=se.useCallback(()=>{let n=Object.values(s.errors).some(p=>p.length>0),l=Object.values(s.validationState).some(p=>p==="invalid");return s.isValid&&!n&&!l},[s.isValid,s.errors,s.validationState]),V=se.useMemo(()=>e,[e]),b=se.useCallback(n=>{d({type:"RESET",values:n});},[]),P=se.useCallback((n,l)=>{d({type:"SET_FIELD_ERRORS",fieldId:n,errors:l}),l.length>0&&d({type:"SET_FORM_VALIDITY",isValid:false});},[]),C=se.useCallback(n=>{d({type:"SET_FIELD_ERRORS",fieldId:n,errors:[]});},[]),D=se.useCallback(async n=>{n?.preventDefault();try{return d({type:"SET_SUBMITTING",isSubmitting:!0}),(await w()).isValid?(a.current&&await a.current(s.values),!0):!1}catch(l){return console.error("Error during form submission:",l),false}finally{d({type:"SET_SUBMITTING",isSubmitting:false});}},[s.values,w]),H=se.useMemo(()=>({formState:s,formConfig:V,setValue:E,setFieldTouched:S,validateField:g,validateForm:w,isFormValid:T,reset:b,submit:D,setError:P,clearError:C}),[s,V,E,S,g,w,T,b,D,P,C]);return jsxRuntime.jsx(U.Provider,{value:H,children:jsxRuntime.jsx("form",{onSubmit:D,className:m,noValidate:true,children:t})})}function h(){let t=se.useContext(U);if(!t)throw new Error("useFormContext must be used within a FormProvider");return t}function ne({formConfig:t,defaultValues:e,onSubmit:r,onFieldChange:i,children:o}){let m=se.useMemo(()=>t instanceof v?t.build():t,[t]);return jsxRuntime.jsx(B,{formConfig:m,defaultValues:e,onSubmit:r,onFieldChange:i,children:o})}function O({fieldId:t,disabled:e=false,customProps:r={},className:i}){let{formState:o,formConfig:m,setValue:R,setFieldTouched:s,validateField:d}=h(),a=se.useMemo(()=>m.allFields.find(C=>C.id===t),[m.allFields,t]);if(!a)throw new Error(`Field with ID "${t}" not found`);let c=se.useMemo(()=>m.config.getComponent(a.componentId),[m.config,a.componentId]);if(!c)throw new Error(`Component with ID "${a.componentId}" not found`);let f=se.useMemo(()=>({value:o.values[t],errors:o.errors[t]||[],validationState:o.validationState[t]||"idle",isValidating:o.validationState[t]==="validating",isTouched:o.touched[t]||false}),[o.values,o.errors,o.validationState,o.touched,t]),E=se.useCallback(async C=>{R(a.id,C),(a.validation?.validateOnChange||f.isTouched)&&await d(a.id,C);},[a.id,a.validation?.validateOnChange,f.isTouched,R,d]),S=se.useCallback(async()=>{f.isTouched||s(a.id,true),(a.validation?.validateOnBlur||!a.validation?.validateOnChange)&&await d(a.id);},[a.id,a.validation?.validateOnBlur,a.validation?.validateOnChange,f.isTouched,s,d]),g=se.useMemo(()=>({...c.defaultProps??{},...a.props,...r}),[c.defaultProps,a.props,r]),w=se.useMemo(()=>({id:a.id,props:g,value:f.value,onChange:E,onBlur:S,disabled:e,error:f.errors,isValidating:f.isValidating,touched:f.isTouched}),[a.id,g,f.value,f.errors,f.isValidating,f.isTouched,E,S,e]),T=m.renderConfig?.fieldRenderer,V=c.renderer(w),b=c.useFieldRenderer!==false,P=T&&b?T({children:V,id:a.id,disabled:e,...g}):V;return jsxRuntime.jsx("div",{className:i,"data-field-id":a.id,"data-field-type":c.type,children:P})}se__default.default.memo(O);function J({row:t,className:e,...r}){let{formConfig:i}=h(),o=t.fields.map(R=>jsxRuntime.jsx(O,{fieldId:R.id},R.id)),m={row:t,children:o,className:e};return jsxRuntime.jsx(core.ComponentRendererWrapper,{name:"FormRow",renderer:i.renderConfig?.rowRenderer,props:m,...r,children:o})}var Y=J;function fe({className:t,...e}){let{formConfig:r}=h(),i=se.useMemo(()=>r.rows.map(m=>jsxRuntime.jsx(Y,{row:m},m.id)),[r.rows]),o={formConfig:r,children:i,className:t};return jsxRuntime.jsx(core.ComponentRendererWrapper,{name:"FormBody",renderer:r.renderConfig?.bodyRenderer,props:o,...e,children:i})}function pe({className:t,...e}){let{formState:r,submit:i,formConfig:o}=h(),m={isSubmitting:r.isSubmitting,onSubmit:i,className:t};return jsxRuntime.jsx(core.ComponentRendererWrapper,{name:"FormSubmitButton",renderer:o.renderConfig?.submitButtonRenderer,props:m,...e})}exports.Form=ne;exports.FormBody=fe;exports.FormBuilder=v;exports.FormField=O;exports.FormProvider=B;exports.FormRow=J;exports.FormSubmitButton=pe;exports.createForm=Z;exports.form=v;exports.useFormContext=h;Object.keys(core).forEach(function(k){if(k!=='default'&&!Object.prototype.hasOwnProperty.call(exports,k))Object.defineProperty(exports,k,{enumerable:true,get:function(){return core[k]}})});
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {ril,IdGenerator,deepClone,ValidationErrorBuilder,ensureUnique,resolveRendererChildren}from'@rilaykit/core';export{createZodValidator,ril}from'@rilaykit/core';import se,{createContext,useMemo,useCallback,useContext,useReducer,useRef}from'react';import {jsx}from'react/jsx-runtime';var w=class t{constructor(e,r){this.rows=[];this.idGenerator=new IdGenerator;this.config=e,this.formId=r||`form-${Date.now()}`;}static create(e,r){return new t(e,r)}createFormField(e){let r=this.config.getComponent(e.type);if(!r)throw new Error(`No component found with type "${e.type}"`);return {id:e.id||this.idGenerator.next("field"),componentId:r.id,props:{...r.defaultProps,...e.props},validation:e.validation,conditional:e.conditional}}createRow(e,r){if(e.length===0)throw new Error("At least one field is required");if(e.length>3)throw new Error("Maximum 3 fields per row");let n=e.map(o=>this.createFormField(o));return {id:this.idGenerator.next("row"),fields:n,maxColumns:e.length,spacing:r?.spacing||"normal",alignment:r?.alignment||"stretch"}}add(...e){let r,n,o=false;if(e.length===1&&Array.isArray(e[0])?(r=e[0],o=true):e.length===2&&Array.isArray(e[0])?(r=e[0],n=e[1],o=true):r=e,r.length===0)throw new Error("At least one field is required");if(o&&r.length>3)throw new Error("Maximum 3 fields per row");if(r.length===1){let c=this.createRow(r,n);return this.rows.push(c),this}if(r.length<=3){let c=this.createRow(r,n);return this.rows.push(c),this}for(let c of r){let f=this.createRow([c],n);this.rows.push(f);}return this}addSeparateRows(e,r){for(let n of e)this.add([n],r);return this}setId(e){return this.formId=e,this}updateField(e,r){let n=this.findField(e);if(!n)throw new Error(`Field with ID "${e}" not found`);return Object.assign(n,{...r,props:{...n.props,...r.props}}),this}findField(e){for(let r of this.rows){let n=r.fields.find(o=>o.id===e);if(n)return n}return null}removeField(e){return this.rows=this.rows.map(r=>({...r,fields:r.fields.filter(n=>n.id!==e)})).filter(r=>r.fields.length>0),this}getField(e){return this.findField(e)||void 0}getFields(){return this.rows.flatMap(e=>e.fields)}getRows(){return [...this.rows]}clear(){return this.rows=[],this.idGenerator.reset(),this}clone(e){let r=new t(this.config,e||`${this.formId}-clone`);return r.rows=deepClone(this.rows),r}validate(){let e=new ValidationErrorBuilder,r=this.getFields(),n=r.map(o=>o.id);try{ensureUnique(n,"field");}catch(o){e.add("DUPLICATE_FIELD_IDS",o instanceof Error?o.message:String(o));}for(let o of r)e.addIf(!this.config.hasComponent(o.componentId),"MISSING_COMPONENT",`Component "${o.componentId}" not found for field "${o.id}"`);for(let o of this.rows)e.addIf(o.fields.length>3,"TOO_MANY_FIELDS_IN_ROW",`Row "${o.id}" has ${o.fields.length} fields, maximum is 3`),e.addIf(o.fields.length===0,"EMPTY_ROW",`Row "${o.id}" is empty`);return e.build().map(o=>o.message)}build(){let e=this.validate();if(e.length>0)throw new Error(`Form validation failed: ${e.join(", ")}`);return {id:this.formId,rows:[...this.rows],allFields:this.getFields(),config:this.config,renderConfig:this.config.getFormRenderConfig()}}toJSON(){return {id:this.formId,rows:this.rows}}fromJSON(e){return e.id&&(this.formId=e.id),e.rows&&(this.rows=e.rows),this}getStats(){let e=this.getFields(),r=this.rows.map(n=>n.fields.length);return {totalFields:e.length,totalRows:this.rows.length,averageFieldsPerRow:this.rows.length>0?e.length/this.rows.length:0,maxFieldsInRow:r.length>0?Math.max(...r):0,minFieldsInRow:r.length>0?Math.min(...r):0}}};function j(t,e){return w.create(t,e)}ril.prototype.form=function(t){return w.create(this,t)};function te(t,e){switch(e.type){case "SET_VALUE":{let r={...t.values,[e.fieldId]:e.value};return {...t,values:r,isDirty:true}}case "SET_ERROR":return {...t,errors:{...t.errors,[e.fieldId]:e.errors},isValid:false};case "CLEAR_ERROR":{let r={...t.errors};return delete r[e.fieldId],{...t,errors:r}}case "MARK_TOUCHED":return {...t,touched:new Set([...t.touched,e.fieldId])};case "SET_VALIDATING":{let r=new Set(t.isValidating);return e.isValidating?r.add(e.fieldId):r.delete(e.fieldId),{...t,isValidating:r}}case "SET_SUBMITTING":return {...t,isSubmitting:e.isSubmitting};case "RESET":return {values:e.values||{},errors:{},touched:new Set,isValidating:new Set,isDirty:false,isValid:true,isSubmitting:false};case "UPDATE_VALIDATION_STATE":{let r=Object.keys(t.errors).some(n=>t.errors[n].length>0);return {...t,isValid:!r}}default:return t}}var L=createContext(null);function D({children:t,formConfig:e,defaultValues:r={},onSubmit:n,onFieldChange:o,className:c}){let f={values:r,errors:{},touched:new Set,isValidating:new Set,isDirty:false,isValid:true,isSubmitting:false},[d,a]=useReducer(te,f),i=useRef(new Map),m=useRef(n),l=useRef(o);m.current=n,l.current=o;let C=useCallback((s,u)=>{a({type:"SET_ERROR",fieldId:s,errors:u}),a({type:"UPDATE_VALIDATION_STATE"});},[]),v=useCallback(s=>{a({type:"CLEAR_ERROR",fieldId:s}),a({type:"UPDATE_VALIDATION_STATE"});},[]),S=useCallback(s=>{a({type:"MARK_TOUCHED",fieldId:s});},[]),p=useCallback((s,u)=>{a({type:"SET_VALIDATING",fieldId:s,isValidating:u});},[]),V=useCallback((s,u)=>{if(a({type:"SET_VALUE",fieldId:s,value:u}),d.errors[s]&&d.errors[s].length>0&&(a({type:"CLEAR_ERROR",fieldId:s}),a({type:"UPDATE_VALIDATION_STATE"})),l.current){let g={...d.values,[s]:u};l.current(s,u,g);}},[d.errors,d.values]),h=useMemo(()=>e,[e]),T=useCallback(async(s,u)=>{let g=h.allFields.find(I=>I.id===s);if(!g?.validation?.validator)return {isValid:true,errors:[]};let J=u!==void 0?u:d.values[s],O=i.current.get(s);O&&clearTimeout(O);let K={fieldId:s,formData:d.values,fieldProps:g.props,touched:d.touched.has(s),dirty:d.isDirty},N=g.validation.debounceMs||0;return new Promise(I=>{let B=async()=>{p(s,true);try{let y=await g.validation.validator(J,K,g.props);y.errors.length>0?C(s,y.errors):v(s),I(y);}catch(y){let k={isValid:false,errors:[{code:"validation_error",message:y instanceof Error?y.message:"Validation error"}]};C(s,k.errors),I(k);}finally{p(s,false);}};if(N>0){let y=setTimeout(B,N);i.current.set(s,y);}else B();})},[h,d.values,d.touched,d.isDirty,C,v,p]),E=useCallback(async()=>{let s=h.allFields.map(g=>T(g.id));return (await Promise.all(s)).every(g=>g.isValid)},[h,T]),b=useCallback(s=>{a({type:"RESET",values:s});},[]),x=useCallback(async s=>{if(s?.preventDefault(),!m.current)return true;a({type:"SET_SUBMITTING",isSubmitting:true});try{return await E()?(await m.current(d.values),!0):!1}catch(u){return console.error("Error during form submission:",u),false}finally{a({type:"SET_SUBMITTING",isSubmitting:false});}},[d.values,E]),A=useMemo(()=>({formState:d,formConfig:h,setValue:V,setError:C,clearError:v,markFieldTouched:S,setFieldValidating:p,validateField:T,validateAllFields:E,reset:b,submit:x}),[d,h,V,C,v,S,p,T,E,b,x]);return jsx(L.Provider,{value:A,children:jsx("form",{onSubmit:x,className:c,noValidate:true,children:t})})}function R(){let t=useContext(L);if(!t)throw new Error("useFormContext must be used within a FormProvider");return t}function ie({formConfig:t,defaultValues:e,onSubmit:r,onFieldChange:n,children:o}){let c=t instanceof w?t.build():t;return jsx(D,{formConfig:c,defaultValues:e,onSubmit:r,onFieldChange:n,children:o})}function G({fieldId:t,disabled:e=false,customProps:r={},className:n}){let{formState:o,formConfig:c,setValue:f,markFieldTouched:d,validateField:a}=R(),i=useMemo(()=>c.allFields.find(s=>s.id===t),[c.allFields,t]);if(!i)throw new Error(`Field with ID "${t}" not found`);let m=useMemo(()=>c.config.getComponent(i.componentId),[c.config,i.componentId]);if(!m)throw new Error(`Component with ID "${i.componentId}" not found`);let l=useMemo(()=>({value:o.values[i.id],errors:o.errors[i.id]||[],touched:o.touched.has(i.id),validating:o.isValidating.has(i.id)}),[o.values,o.errors,o.touched,o.isValidating,i.id]),C=useCallback(s=>{let u=l.errors.length>0;f(i.id,s),(i.validation?.validateOnChange||u&&i.validation?.validator||l.touched&&i.validation?.validator)&&a(i.id,s);},[i.id,i.validation,f,a,l.errors.length,l.touched]),v=useCallback(()=>{d(i.id),(i.validation?.validateOnBlur||i.validation?.validator)&&a(i.id);},[i.id,i.validation,d,a]),S=useMemo(()=>{if(!i.conditional)return true;try{return i.conditional.condition(o.values)}catch(s){return console.warn(`Conditional evaluation failed for field "${i.id}":`,s),true}},[i.conditional,o.values,i.id]),p=useMemo(()=>{if(!i.conditional||!S)return {};switch(i.conditional.action){case "disable":return {disabled:true};case "require":return {required:true};default:return {}}},[i.conditional,S]),V=!S&&i.conditional?.action==="hide",h=useMemo(()=>({...m.defaultProps??{},...i.props,...r,...p}),[m.defaultProps,i.props,r,p]),T=useMemo(()=>({id:i.id,props:h,value:l.value,onChange:C,onBlur:v,error:l.errors,touched:l.touched,disabled:e||p.disabled,isValidating:l.validating}),[i.id,h,l.value,C,v,l.errors,l.touched,e,p.disabled,l.validating]),E=c.renderConfig?.fieldRenderer,b=m.renderer(T),x=m.useFieldRenderer!==false,A=E&&x?E({children:b,id:i.id,error:l.errors,touched:l.touched,disabled:e||p.disabled,isValidating:l.validating,...h}):b;return jsx("div",{className:n,"data-field-id":i.id,"data-field-type":m.type,style:V?{display:"none !important"}:void 0,children:A})}var q=se.memo(G);function W({row:t,className:e,children:r,renderAs:n}){let{formConfig:o}=R(),c=t.fields.map(l=>jsx(q,{fieldId:l.id},l.id));if(n==="children"||n===true){if(typeof r!="function")throw new Error('When renderAs="children" is used, children must be a function that returns React elements');let l={row:t,children:c,className:e,spacing:t.spacing,alignment:t.alignment};return r(l)}let d=o.renderConfig?.rowRenderer;if(!d)throw new Error(`No rowRenderer configured for form "${o.id}". Please configure a rowRenderer using config.setRowRenderer() or config.setFormRenderConfig().`);let a={row:t,children:c,className:e,spacing:t.spacing,alignment:t.alignment},i=resolveRendererChildren(r,a),m={...a,children:i||c};return d(m)}var H=W;function ue({className:t,children:e,renderAs:r}){let{formConfig:n}=R(),o=useMemo(()=>n.rows.map(m=>jsx(H,{row:m},m.id)),[n.rows]);if(r==="children"||r===true){if(typeof e!="function")throw new Error('When renderAs="children" is used, children must be a function that returns React elements');return e({formConfig:n,children:o,className:t})}let f=n.renderConfig?.bodyRenderer;if(!f)throw new Error(`No bodyRenderer configured for form "${n.id}". Please configure a bodyRenderer using config.setBodyRenderer() or config.setFormRenderConfig().`);let d={formConfig:n,children:o,className:t},a=resolveRendererChildren(e,d),i={...d,children:a||o};return f(i)}function ge({className:t,children:e,renderAs:r}){let{formState:n,submit:o,formConfig:c}=R(),f={isSubmitting:n.isSubmitting,isValid:n.isValid,isDirty:n.isDirty,onSubmit:o,className:t};if(r==="children"||r===true){if(typeof e!="function")throw new Error('When renderAs="children" is used, children must be a function that returns React elements');return e(f)}let a=c.renderConfig?.submitButtonRenderer;if(!a)throw new Error(`No submitButtonRenderer configured for form "${c.id}". Please configure a submitButtonRenderer using config.setSubmitButtonRenderer() or config.setFormRenderConfig().`);let i=resolveRendererChildren(e,f),m={...f,children:i};return a(m)}export{ie as Form,ue as FormBody,w as FormBuilder,G as FormField,D as FormProvider,W as FormRow,ge as FormSubmitButton,j as createForm,w as form,R as useFormContext};
|
|
1
|
+
import se,{createContext,useMemo,useCallback,useContext,useReducer,useRef,useEffect}from'react';import {ril,IdGenerator,deepClone,ensureUnique,createValidationContext,runValidatorsAsync,ComponentRendererWrapper}from'@rilaykit/core';export*from'@rilaykit/core';import {jsx}from'react/jsx-runtime';var v=class t{constructor(e,r){this.rows=[];this.idGenerator=new IdGenerator;this.config=e,this.formId=r||`form-${Date.now()}`;}static create(e,r){return new t(e,r)}createFormField(e){let r=this.config.getComponent(e.type);if(!r)throw new Error(`No component found with type "${e.type}"`);let i;return (r.validation||e.validation)&&(i={validateOnChange:e.validation?.validateOnChange??r.validation?.validateOnChange,validateOnBlur:e.validation?.validateOnBlur??r.validation?.validateOnBlur,debounceMs:e.validation?.debounceMs??r.validation?.debounceMs,validators:[...r.validation?.validators||[],...e.validation?.validators||[]]}),{id:e.id||this.idGenerator.next("field"),componentId:r.id,props:{...r.defaultProps,...e.props},validation:i}}createRow(e){if(e.length===0)throw new Error("At least one field is required");if(e.length>3)throw new Error("Maximum 3 fields per row");let r=e.map(i=>this.createFormField(i));return {id:this.idGenerator.next("row"),fields:r,maxColumns:e.length}}add(...e){let r,i=false;if(e.length===1&&Array.isArray(e[0])?(r=e[0],i=true):r=e,r.length===0)throw new Error("At least one field is required");if(i&&r.length>3)throw new Error("Maximum 3 fields per row");if(r.length===1){let o=this.createRow(r);return this.rows.push(o),this}if(r.length<=3){let o=this.createRow(r);return this.rows.push(o),this}for(let o of r){let m=this.createRow([o]);this.rows.push(m);}return this}addSeparateRows(e){for(let r of e)this.add(r);return this}setId(e){return this.formId=e,this}updateField(e,r){let i=this.findField(e);if(!i)throw new Error(`Field with ID "${e}" not found`);return Object.assign(i,{...r,props:{...i.props,...r.props}}),this}findField(e){for(let r of this.rows){let i=r.fields.find(o=>o.id===e);if(i)return i}return null}removeField(e){return this.rows=this.rows.map(r=>({...r,fields:r.fields.filter(i=>i.id!==e)})).filter(r=>r.fields.length>0),this}getField(e){return this.findField(e)||void 0}getFields(){return this.rows.flatMap(e=>e.fields)}getRows(){return [...this.rows]}clear(){return this.rows=[],this.idGenerator.reset(),this}setValidation(e){return this.formValidation=e,this}addValidators(e){return this.formValidation||(this.formValidation={validators:[]}),this.formValidation={...this.formValidation,validators:[...this.formValidation.validators||[],...e]},this}addFieldValidation(e,r){let i=this.findField(e);if(!i)throw new Error(`Field with ID "${e}" not found`);let o={...i.validation,...r,validators:[...i.validation?.validators||[],...r.validators||[]]};return this.updateField(e,{validation:o})}clone(e){let r=new t(this.config,e||`${this.formId}-clone`);return r.rows=deepClone(this.rows),r}validate(){let e=[],r=this.getFields(),i=r.map(o=>o.id);try{ensureUnique(i,"field");}catch(o){e.push(o instanceof Error?o.message:String(o));}for(let o of r)this.config.hasComponent(o.componentId)||e.push(`Component "${o.componentId}" not found for field "${o.id}"`);for(let o of this.rows)o.fields.length>3&&e.push(`Row "${o.id}" has ${o.fields.length} fields, maximum is 3`),o.fields.length===0&&e.push(`Row "${o.id}" is empty`);return e}build(){let e=this.validate();if(e.length>0)throw new Error(`Form validation failed: ${e.join(", ")}`);return {id:this.formId,rows:[...this.rows],allFields:this.getFields(),config:this.config,renderConfig:this.config.getFormRenderConfig(),validation:this.formValidation}}toJSON(){return {id:this.formId,rows:this.rows}}fromJSON(e){return e.id&&(this.formId=e.id),e.rows&&(this.rows=e.rows),this}getStats(){let e=this.getFields(),r=this.rows.map(i=>i.fields.length);return {totalFields:e.length,totalRows:this.rows.length,averageFieldsPerRow:this.rows.length>0?e.length/this.rows.length:0,maxFieldsInRow:r.length>0?Math.max(...r):0,minFieldsInRow:r.length>0?Math.min(...r):0}}};function Z(t,e){return v.create(t,e)}ril.prototype.form=function(t){return v.create(this,t)};function k(){return {isValid:true,errors:[]}}function te(t,e){switch(e.type){case "SET_VALUE":{let r={...t.values,[e.fieldId]:e.value};return {...t,values:r,isDirty:true}}case "SET_FIELD_ERRORS":return {...t,errors:{...t.errors,[e.fieldId]:e.errors},touched:{...t.touched,[e.fieldId]:true}};case "SET_FIELD_VALIDATION_STATE":return {...t,validationState:{...t.validationState,[e.fieldId]:e.state}};case "SET_FIELD_TOUCHED":return {...t,touched:{...t.touched,[e.fieldId]:e.touched}};case "SET_SUBMITTING":return {...t,isSubmitting:e.isSubmitting};case "SET_FORM_VALIDITY":return {...t,isValid:e.isValid};case "RESET":return {values:e.values||{},errors:{},validationState:{},touched:{},isDirty:false,isSubmitting:false,isValid:true};default:return t}}var U=createContext(null);function B({children:t,formConfig:e,defaultValues:r={},onSubmit:i,onFieldChange:o,className:m}){let R={values:r,errors:{},validationState:{},touched:{},isDirty:false,isSubmitting:false,isValid:true},[s,d]=useReducer(te,R),a=useRef(i),c=useRef(o),f=useRef(r);a.current=i,c.current=o,useEffect(()=>{JSON.stringify(f.current)!==JSON.stringify(r)&&(f.current=r,d({type:"RESET",values:r}));},[r]);let E=useCallback((n,l)=>{d({type:"SET_VALUE",fieldId:n,value:l}),c.current?.(n,l,{...s.values,[n]:l});},[s.values]),S=useCallback((n,l=true)=>{d({type:"SET_FIELD_TOUCHED",fieldId:n,touched:l});},[]),g=useCallback(async(n,l)=>{let p=e.allFields.find(u=>u.id===n);if(!p||!p.validation?.validators)return k();let y=l!==void 0?l:s.values[n],_=createValidationContext({fieldId:n,formId:e.id,allFormData:{...s.values,[n]:y}});d({type:"SET_FIELD_VALIDATION_STATE",fieldId:n,state:"validating"});try{let u=await runValidatorsAsync(p.validation.validators,y,_);return d({type:"SET_FIELD_ERRORS",fieldId:n,errors:u.errors}),d({type:"SET_FIELD_VALIDATION_STATE",fieldId:n,state:u.isValid?"valid":"invalid"}),u}catch(u){let x={isValid:false,errors:[{message:u instanceof Error?u.message:"Validation failed",code:"VALIDATION_ERROR"}]};return d({type:"SET_FIELD_ERRORS",fieldId:n,errors:x.errors}),d({type:"SET_FIELD_VALIDATION_STATE",fieldId:n,state:"invalid"}),x}},[e,s.values]),w=useCallback(async()=>{let n=e.allFields.filter(u=>u.validation?.validators).map(u=>g(u.id)),l=await Promise.all(n),p=l.some(u=>!u.isValid),y=k();if(e.validation?.validators){let u=createValidationContext({formId:e.id,allFormData:s.values});try{y=await runValidatorsAsync(e.validation.validators,s.values,u);}catch(x){y={isValid:false,errors:[{message:x instanceof Error?x.message:"Form validation failed",code:"FORM_VALIDATION_ERROR"}]};}}let _={isValid:!p&&y.isValid,errors:[...l.flatMap(u=>u.errors),...y.errors]};return d({type:"SET_FORM_VALIDITY",isValid:_.isValid}),_},[e,s.values,g]),T=useCallback(()=>{let n=Object.values(s.errors).some(p=>p.length>0),l=Object.values(s.validationState).some(p=>p==="invalid");return s.isValid&&!n&&!l},[s.isValid,s.errors,s.validationState]),V=useMemo(()=>e,[e]),b=useCallback(n=>{d({type:"RESET",values:n});},[]),P=useCallback((n,l)=>{d({type:"SET_FIELD_ERRORS",fieldId:n,errors:l}),l.length>0&&d({type:"SET_FORM_VALIDITY",isValid:false});},[]),C=useCallback(n=>{d({type:"SET_FIELD_ERRORS",fieldId:n,errors:[]});},[]),D=useCallback(async n=>{n?.preventDefault();try{return d({type:"SET_SUBMITTING",isSubmitting:!0}),(await w()).isValid?(a.current&&await a.current(s.values),!0):!1}catch(l){return console.error("Error during form submission:",l),false}finally{d({type:"SET_SUBMITTING",isSubmitting:false});}},[s.values,w]),H=useMemo(()=>({formState:s,formConfig:V,setValue:E,setFieldTouched:S,validateField:g,validateForm:w,isFormValid:T,reset:b,submit:D,setError:P,clearError:C}),[s,V,E,S,g,w,T,b,D,P,C]);return jsx(U.Provider,{value:H,children:jsx("form",{onSubmit:D,className:m,noValidate:true,children:t})})}function h(){let t=useContext(U);if(!t)throw new Error("useFormContext must be used within a FormProvider");return t}function ne({formConfig:t,defaultValues:e,onSubmit:r,onFieldChange:i,children:o}){let m=useMemo(()=>t instanceof v?t.build():t,[t]);return jsx(B,{formConfig:m,defaultValues:e,onSubmit:r,onFieldChange:i,children:o})}function O({fieldId:t,disabled:e=false,customProps:r={},className:i}){let{formState:o,formConfig:m,setValue:R,setFieldTouched:s,validateField:d}=h(),a=useMemo(()=>m.allFields.find(C=>C.id===t),[m.allFields,t]);if(!a)throw new Error(`Field with ID "${t}" not found`);let c=useMemo(()=>m.config.getComponent(a.componentId),[m.config,a.componentId]);if(!c)throw new Error(`Component with ID "${a.componentId}" not found`);let f=useMemo(()=>({value:o.values[t],errors:o.errors[t]||[],validationState:o.validationState[t]||"idle",isValidating:o.validationState[t]==="validating",isTouched:o.touched[t]||false}),[o.values,o.errors,o.validationState,o.touched,t]),E=useCallback(async C=>{R(a.id,C),(a.validation?.validateOnChange||f.isTouched)&&await d(a.id,C);},[a.id,a.validation?.validateOnChange,f.isTouched,R,d]),S=useCallback(async()=>{f.isTouched||s(a.id,true),(a.validation?.validateOnBlur||!a.validation?.validateOnChange)&&await d(a.id);},[a.id,a.validation?.validateOnBlur,a.validation?.validateOnChange,f.isTouched,s,d]),g=useMemo(()=>({...c.defaultProps??{},...a.props,...r}),[c.defaultProps,a.props,r]),w=useMemo(()=>({id:a.id,props:g,value:f.value,onChange:E,onBlur:S,disabled:e,error:f.errors,isValidating:f.isValidating,touched:f.isTouched}),[a.id,g,f.value,f.errors,f.isValidating,f.isTouched,E,S,e]),T=m.renderConfig?.fieldRenderer,V=c.renderer(w),b=c.useFieldRenderer!==false,P=T&&b?T({children:V,id:a.id,disabled:e,...g}):V;return jsx("div",{className:i,"data-field-id":a.id,"data-field-type":c.type,children:P})}se.memo(O);function J({row:t,className:e,...r}){let{formConfig:i}=h(),o=t.fields.map(R=>jsx(O,{fieldId:R.id},R.id)),m={row:t,children:o,className:e};return jsx(ComponentRendererWrapper,{name:"FormRow",renderer:i.renderConfig?.rowRenderer,props:m,...r,children:o})}var Y=J;function fe({className:t,...e}){let{formConfig:r}=h(),i=useMemo(()=>r.rows.map(m=>jsx(Y,{row:m},m.id)),[r.rows]),o={formConfig:r,children:i,className:t};return jsx(ComponentRendererWrapper,{name:"FormBody",renderer:r.renderConfig?.bodyRenderer,props:o,...e,children:i})}function pe({className:t,...e}){let{formState:r,submit:i,formConfig:o}=h(),m={isSubmitting:r.isSubmitting,onSubmit:i,className:t};return jsx(ComponentRendererWrapper,{name:"FormSubmitButton",renderer:o.renderConfig?.submitButtonRenderer,props:m,...e})}export{ne as Form,fe as FormBody,v as FormBuilder,O as FormField,B as FormProvider,J as FormRow,pe as FormSubmitButton,Z as createForm,v as form,h as useFormContext};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rilaykit/forms",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Form building utilities and components for RilayKit",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"url": "https://github.com/andyoucreate/rilay/issues"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@rilaykit/core": "
|
|
27
|
+
"@rilaykit/core": "5.0.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"react": ">=18.0.0",
|