@rilaykit/forms 2.0.1 → 4.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 +429 -43
- package/dist/index.d.ts +429 -43
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -5,118 +5,504 @@ export { createZodValidator, ril } from '@rilaykit/core';
|
|
|
5
5
|
import * as React$1 from 'react';
|
|
6
6
|
import React__default from 'react';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for a form field with type safety
|
|
10
|
+
*
|
|
11
|
+
* @template C - The component configuration map
|
|
12
|
+
* @template T - The specific component type key
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const fieldConfig: FieldConfig<MyComponents, 'text'> = {
|
|
17
|
+
* type: 'text',
|
|
18
|
+
* props: { placeholder: 'Enter your name' },
|
|
19
|
+
* validation: { required: true }
|
|
20
|
+
* };
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
type FieldConfig<C extends Record<string, any>, T extends keyof C> = {
|
|
24
|
+
/** Unique identifier for the field. Auto-generated if not provided */
|
|
25
|
+
id?: string;
|
|
26
|
+
/** Component type from the registered components */
|
|
27
|
+
type: T;
|
|
28
|
+
/** Component-specific properties */
|
|
29
|
+
props?: Partial<C[T]>;
|
|
30
|
+
/** Validation rules for the field */
|
|
12
31
|
validation?: ValidationConfig;
|
|
32
|
+
/** Conditional display logic */
|
|
13
33
|
conditional?: ConditionalConfig;
|
|
14
|
-
}
|
|
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
|
+
*/
|
|
15
46
|
interface RowOptions {
|
|
47
|
+
/** Spacing between fields in the row */
|
|
16
48
|
spacing?: 'tight' | 'normal' | 'loose';
|
|
49
|
+
/** Vertical alignment of fields in the row */
|
|
17
50
|
alignment?: 'start' | 'center' | 'end' | 'stretch';
|
|
18
51
|
}
|
|
19
52
|
/**
|
|
20
|
-
* Form builder class for creating form configurations
|
|
21
|
-
*
|
|
53
|
+
* Form builder class for creating type-safe form configurations
|
|
54
|
+
*
|
|
55
|
+
* This class provides a fluent API for building forms with automatic validation,
|
|
56
|
+
* type safety, and flexible layout options. It manages field registration,
|
|
57
|
+
* row organization, and form configuration generation.
|
|
58
|
+
*
|
|
59
|
+
* @template C - Component configuration map defining available component types
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* // Create a form builder with typed components
|
|
64
|
+
* const formBuilder = form.create(rilConfig, 'user-registration')
|
|
65
|
+
* .add({ type: 'text', props: { label: 'Name' } })
|
|
66
|
+
* .add(
|
|
67
|
+
* { type: 'email', props: { label: 'Email' } },
|
|
68
|
+
* { type: 'password', props: { label: 'Password' } }
|
|
69
|
+
* )
|
|
70
|
+
* .build();
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @remarks
|
|
74
|
+
* - Supports up to 3 fields per row for optimal layout
|
|
75
|
+
* - Automatically generates unique IDs for fields and rows
|
|
76
|
+
* - Provides comprehensive validation before building
|
|
77
|
+
* - Maintains type safety throughout the building process
|
|
22
78
|
*/
|
|
23
|
-
declare class form {
|
|
79
|
+
declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
80
|
+
/** The ril configuration instance containing component definitions */
|
|
24
81
|
private config;
|
|
82
|
+
/** Array of form rows containing field configurations */
|
|
25
83
|
private rows;
|
|
84
|
+
/** Unique identifier for this form */
|
|
26
85
|
private formId;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
static create(config: ril, formId?: string): form;
|
|
86
|
+
/** Generator for creating unique IDs */
|
|
87
|
+
private idGenerator;
|
|
30
88
|
/**
|
|
31
|
-
*
|
|
89
|
+
* Creates a new form builder instance
|
|
90
|
+
*
|
|
91
|
+
* @param config - The ril configuration containing component definitions
|
|
92
|
+
* @param formId - Optional unique identifier for the form. Auto-generated if not provided
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* const builder = new form(rilConfig, 'my-form');
|
|
97
|
+
* ```
|
|
32
98
|
*/
|
|
33
|
-
|
|
99
|
+
constructor(config: ril<C>, formId?: string);
|
|
34
100
|
/**
|
|
35
|
-
*
|
|
101
|
+
* Static factory method to create a new form builder
|
|
102
|
+
*
|
|
103
|
+
* @template Cm - Component configuration map
|
|
104
|
+
* @param config - The ril configuration instance
|
|
105
|
+
* @param formId - Optional form identifier
|
|
106
|
+
* @returns A new form builder instance
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* const builder = form.create(rilConfig, 'registration-form');
|
|
111
|
+
* ```
|
|
36
112
|
*/
|
|
37
|
-
|
|
113
|
+
static create<Cm extends Record<string, any> = Record<string, never>>(config: ril<Cm>, formId?: string): form<Cm>;
|
|
38
114
|
/**
|
|
39
|
-
*
|
|
115
|
+
* Converts a FieldConfig to a FormFieldConfig with proper validation
|
|
116
|
+
*
|
|
117
|
+
* This internal method handles the transformation from the builder's field
|
|
118
|
+
* configuration format to the final form field configuration, including
|
|
119
|
+
* component lookup, prop merging, and ID generation.
|
|
120
|
+
*
|
|
121
|
+
* @template T - The component type
|
|
122
|
+
* @param fieldConfig - The field configuration to convert
|
|
123
|
+
* @returns A complete FormFieldConfig ready for use
|
|
124
|
+
* @throws Error if the specified component type is not registered
|
|
125
|
+
*
|
|
126
|
+
* @internal
|
|
40
127
|
*/
|
|
41
|
-
|
|
128
|
+
private createFormField;
|
|
129
|
+
/**
|
|
130
|
+
* Creates a form row with the specified fields and options
|
|
131
|
+
*
|
|
132
|
+
* This internal method handles row creation with validation of field limits,
|
|
133
|
+
* proper spacing, and alignment configuration.
|
|
134
|
+
*
|
|
135
|
+
* @template T - The component type
|
|
136
|
+
* @param fieldConfigs - Array of field configurations for the row
|
|
137
|
+
* @param rowOptions - Optional row layout configuration
|
|
138
|
+
* @returns A complete FormFieldRow configuration
|
|
139
|
+
* @throws Error if no fields provided or more than 3 fields specified
|
|
140
|
+
*
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
private createRow;
|
|
42
144
|
/**
|
|
43
|
-
*
|
|
145
|
+
* Universal method for adding fields to the form
|
|
146
|
+
*
|
|
147
|
+
* This is the primary method for adding fields to your form. It supports multiple
|
|
148
|
+
* usage patterns for maximum flexibility:
|
|
149
|
+
*
|
|
150
|
+
* - Single field: Creates a new row with one field
|
|
151
|
+
* - Multiple fields (≤3): Creates one row with all fields
|
|
152
|
+
* - Multiple fields (>3): Creates separate rows for each field
|
|
153
|
+
* - Array with options: Explicit control over row configuration
|
|
154
|
+
*
|
|
155
|
+
* @template T - The component type
|
|
156
|
+
* @param fields - Field configurations (variadic or array)
|
|
157
|
+
* @returns The form builder instance for method chaining
|
|
158
|
+
* @throws Error if no fields provided or invalid configuration
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* // Single field on its own row
|
|
163
|
+
* builder.add({ type: 'text', props: { label: 'Name' } });
|
|
164
|
+
*
|
|
165
|
+
* // Multiple fields on same row (max 3)
|
|
166
|
+
* builder.add(
|
|
167
|
+
* { type: 'text', props: { label: 'First Name' } },
|
|
168
|
+
* { type: 'text', props: { label: 'Last Name' } }
|
|
169
|
+
* );
|
|
170
|
+
*
|
|
171
|
+
* // Array syntax with row options
|
|
172
|
+
* builder.add([
|
|
173
|
+
* { type: 'email', props: { label: 'Email' } },
|
|
174
|
+
* { type: 'phone', props: { label: 'Phone' } }
|
|
175
|
+
* ], { spacing: 'loose', alignment: 'center' });
|
|
176
|
+
* ```
|
|
44
177
|
*/
|
|
45
|
-
|
|
178
|
+
add<T extends keyof C & string>(...fields: FieldConfig<C, T>[]): this;
|
|
179
|
+
add<T extends keyof C & string>(fields: FieldConfig<C, T>[], rowOptions?: RowOptions): this;
|
|
46
180
|
/**
|
|
47
|
-
*
|
|
181
|
+
* Adds multiple fields on separate rows
|
|
182
|
+
*
|
|
183
|
+
* This method is useful when you want to ensure each field gets its own row,
|
|
184
|
+
* regardless of the number of fields. It's an alternative to the add() method
|
|
185
|
+
* when you need explicit control over row separation.
|
|
186
|
+
*
|
|
187
|
+
* @template T - The component type
|
|
188
|
+
* @param fieldConfigs - Array of field configurations
|
|
189
|
+
* @param rowOptions - Optional row layout configuration applied to all rows
|
|
190
|
+
* @returns The form builder instance for method chaining
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```typescript
|
|
194
|
+
* // Each field will be on its own row
|
|
195
|
+
* builder.addSeparateRows([
|
|
196
|
+
* { type: 'text', props: { label: 'Field 1' } },
|
|
197
|
+
* { type: 'text', props: { label: 'Field 2' } },
|
|
198
|
+
* { type: 'text', props: { label: 'Field 3' } }
|
|
199
|
+
* ], { spacing: 'loose' });
|
|
200
|
+
* ```
|
|
48
201
|
*/
|
|
49
|
-
|
|
202
|
+
addSeparateRows<T extends keyof C & string>(fieldConfigs: FieldConfig<C, T>[], rowOptions?: RowOptions): this;
|
|
50
203
|
/**
|
|
51
|
-
*
|
|
204
|
+
* Sets the form identifier
|
|
205
|
+
*
|
|
206
|
+
* @param id - The new form identifier
|
|
207
|
+
* @returns The form builder instance for method chaining
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```typescript
|
|
211
|
+
* builder.setId('user-profile-form');
|
|
212
|
+
* ```
|
|
52
213
|
*/
|
|
53
214
|
setId(id: string): this;
|
|
54
215
|
/**
|
|
55
|
-
*
|
|
216
|
+
* Updates an existing field's configuration
|
|
217
|
+
*
|
|
218
|
+
* This method allows you to modify field properties, validation rules,
|
|
219
|
+
* or conditional logic after the field has been added to the form.
|
|
220
|
+
*
|
|
221
|
+
* @param fieldId - The unique identifier of the field to update
|
|
222
|
+
* @param updates - Partial field configuration with updates to apply
|
|
223
|
+
* @returns The form builder instance for method chaining
|
|
224
|
+
* @throws Error if the field with the specified ID is not found
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```typescript
|
|
228
|
+
* builder.updateField('email-field', {
|
|
229
|
+
* props: { placeholder: 'Enter your email address' },
|
|
230
|
+
* validation: { required: true, email: true }
|
|
231
|
+
* });
|
|
232
|
+
* ```
|
|
56
233
|
*/
|
|
57
234
|
updateField(fieldId: string, updates: Partial<Omit<FormFieldConfig, 'id'>>): this;
|
|
58
235
|
/**
|
|
59
|
-
*
|
|
236
|
+
* Finds a field by its unique identifier
|
|
237
|
+
*
|
|
238
|
+
* This internal method searches through all rows to locate a field
|
|
239
|
+
* with the specified ID.
|
|
240
|
+
*
|
|
241
|
+
* @param fieldId - The field identifier to search for
|
|
242
|
+
* @returns The field configuration if found, null otherwise
|
|
243
|
+
*
|
|
244
|
+
* @internal
|
|
60
245
|
*/
|
|
61
246
|
private findField;
|
|
62
247
|
/**
|
|
63
|
-
*
|
|
248
|
+
* Removes a field from the form
|
|
249
|
+
*
|
|
250
|
+
* This method removes the specified field and cleans up any empty rows
|
|
251
|
+
* that result from the removal. The form structure is automatically
|
|
252
|
+
* reorganized to maintain consistency.
|
|
253
|
+
*
|
|
254
|
+
* @param fieldId - The unique identifier of the field to remove
|
|
255
|
+
* @returns The form builder instance for method chaining
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```typescript
|
|
259
|
+
* builder.removeField('unwanted-field-id');
|
|
260
|
+
* ```
|
|
64
261
|
*/
|
|
65
262
|
removeField(fieldId: string): this;
|
|
66
263
|
/**
|
|
67
|
-
*
|
|
264
|
+
* Retrieves a field configuration by its ID
|
|
265
|
+
*
|
|
266
|
+
* @param fieldId - The unique identifier of the field
|
|
267
|
+
* @returns The field configuration if found, undefined otherwise
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```typescript
|
|
271
|
+
* const emailField = builder.getField('email-field');
|
|
272
|
+
* if (emailField) {
|
|
273
|
+
* console.log('Email field props:', emailField.props);
|
|
274
|
+
* }
|
|
275
|
+
* ```
|
|
68
276
|
*/
|
|
69
277
|
getField(fieldId: string): FormFieldConfig | undefined;
|
|
70
278
|
/**
|
|
71
|
-
*
|
|
279
|
+
* Gets all fields as a flat array
|
|
280
|
+
*
|
|
281
|
+
* This method flattens the row structure to provide a simple array
|
|
282
|
+
* of all field configurations in the form, maintaining their order.
|
|
283
|
+
*
|
|
284
|
+
* @returns Array of all field configurations in the form
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```typescript
|
|
288
|
+
* const allFields = builder.getFields();
|
|
289
|
+
* console.log(`Form has ${allFields.length} fields`);
|
|
290
|
+
* ```
|
|
72
291
|
*/
|
|
73
292
|
getFields(): FormFieldConfig[];
|
|
74
293
|
/**
|
|
75
|
-
*
|
|
294
|
+
* Gets all rows in the form
|
|
295
|
+
*
|
|
296
|
+
* Returns a copy of the internal rows array to prevent external
|
|
297
|
+
* modification while allowing inspection of the form structure.
|
|
298
|
+
*
|
|
299
|
+
* @returns Array of all form rows
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* const rows = builder.getRows();
|
|
304
|
+
* console.log(`Form has ${rows.length} rows`);
|
|
305
|
+
* ```
|
|
76
306
|
*/
|
|
77
307
|
getRows(): FormFieldRow[];
|
|
78
308
|
/**
|
|
79
|
-
*
|
|
309
|
+
* Clears all fields and rows from the form
|
|
310
|
+
*
|
|
311
|
+
* This method resets the form to an empty state and resets the ID generator
|
|
312
|
+
* to ensure clean ID generation for subsequent fields.
|
|
313
|
+
*
|
|
314
|
+
* @returns The form builder instance for method chaining
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```typescript
|
|
318
|
+
* builder.clear().add({ type: 'text', props: { label: 'New start' } });
|
|
319
|
+
* ```
|
|
80
320
|
*/
|
|
81
321
|
clear(): this;
|
|
82
322
|
/**
|
|
83
|
-
*
|
|
323
|
+
* Creates a deep copy of the current form builder
|
|
324
|
+
*
|
|
325
|
+
* This method creates a completely independent copy of the form builder,
|
|
326
|
+
* including all field configurations and internal state. The cloned
|
|
327
|
+
* builder can be modified without affecting the original.
|
|
328
|
+
*
|
|
329
|
+
* @param newFormId - Optional new form ID for the clone
|
|
330
|
+
* @returns A new form builder instance with copied configuration
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```typescript
|
|
334
|
+
* const originalForm = builder.clone();
|
|
335
|
+
* const modifiedForm = builder.clone('modified-form')
|
|
336
|
+
* .add({ type: 'text', props: { label: 'Additional field' } });
|
|
337
|
+
* ```
|
|
84
338
|
*/
|
|
85
|
-
clone(newFormId?: string): form
|
|
339
|
+
clone(newFormId?: string): form<C>;
|
|
86
340
|
/**
|
|
87
|
-
*
|
|
341
|
+
* Validates the current form configuration
|
|
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.
|
|
346
|
+
*
|
|
347
|
+
* @returns Array of validation error messages (empty if valid)
|
|
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)
|
|
88
362
|
*/
|
|
89
363
|
validate(): string[];
|
|
90
364
|
/**
|
|
91
|
-
*
|
|
365
|
+
* Builds the final form configuration
|
|
366
|
+
*
|
|
367
|
+
* This method performs final validation and creates the complete form
|
|
368
|
+
* configuration object ready for rendering. It includes all field
|
|
369
|
+
* configurations, render settings, and metadata.
|
|
370
|
+
*
|
|
371
|
+
* @returns Complete form configuration ready for use
|
|
372
|
+
* @throws Error if validation fails
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* ```typescript
|
|
376
|
+
* try {
|
|
377
|
+
* const formConfig = builder.build();
|
|
378
|
+
* // Use formConfig with your form renderer
|
|
379
|
+
* } catch (error) {
|
|
380
|
+
* console.error('Failed to build form:', error.message);
|
|
381
|
+
* }
|
|
382
|
+
* ```
|
|
383
|
+
*
|
|
384
|
+
* @remarks
|
|
385
|
+
* The returned configuration includes:
|
|
386
|
+
* - Form ID and metadata
|
|
387
|
+
* - All rows with their field configurations
|
|
388
|
+
* - Flattened array of all fields for easy access
|
|
389
|
+
* - Component configuration reference
|
|
390
|
+
* - Render configuration for customization
|
|
92
391
|
*/
|
|
93
392
|
build(): FormConfiguration;
|
|
94
393
|
/**
|
|
95
|
-
*
|
|
394
|
+
* Exports the form configuration as JSON
|
|
395
|
+
*
|
|
396
|
+
* This method serializes the form configuration to a plain JavaScript
|
|
397
|
+
* object suitable for storage, transmission, or debugging.
|
|
398
|
+
*
|
|
399
|
+
* @returns Plain object representation of the form
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```typescript
|
|
403
|
+
* const formJson = builder.toJSON();
|
|
404
|
+
* localStorage.setItem('savedForm', JSON.stringify(formJson));
|
|
405
|
+
* ```
|
|
96
406
|
*/
|
|
97
407
|
toJSON(): any;
|
|
98
408
|
/**
|
|
99
|
-
*
|
|
409
|
+
* Imports form configuration from JSON
|
|
410
|
+
*
|
|
411
|
+
* This method restores form state from a previously exported JSON
|
|
412
|
+
* configuration. It's useful for loading saved forms or restoring
|
|
413
|
+
* form state from external sources.
|
|
414
|
+
*
|
|
415
|
+
* @param json - The JSON object containing form configuration
|
|
416
|
+
* @returns The form builder instance for method chaining
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```typescript
|
|
420
|
+
* const savedForm = JSON.parse(localStorage.getItem('savedForm'));
|
|
421
|
+
* builder.fromJSON(savedForm);
|
|
422
|
+
* ```
|
|
423
|
+
*
|
|
424
|
+
* @remarks
|
|
425
|
+
* - Only imports basic form structure (ID and rows)
|
|
426
|
+
* - Does not validate imported configuration
|
|
427
|
+
* - Existing form content is replaced
|
|
100
428
|
*/
|
|
101
429
|
fromJSON(json: any): this;
|
|
102
430
|
/**
|
|
103
|
-
*
|
|
431
|
+
* Gets comprehensive statistics about the form
|
|
432
|
+
*
|
|
433
|
+
* This method provides useful metrics about the form structure,
|
|
434
|
+
* helpful for analytics, debugging, or UI display purposes.
|
|
435
|
+
*
|
|
436
|
+
* @returns Object containing form statistics
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* ```typescript
|
|
440
|
+
* const stats = builder.getStats();
|
|
441
|
+
* console.log(`Form has ${stats.totalFields} fields in ${stats.totalRows} rows`);
|
|
442
|
+
* console.log(`Average fields per row: ${stats.averageFieldsPerRow.toFixed(1)}`);
|
|
443
|
+
* ```
|
|
444
|
+
*
|
|
445
|
+
* @remarks
|
|
446
|
+
* Statistics include:
|
|
447
|
+
* - Total number of fields and rows
|
|
448
|
+
* - Average fields per row
|
|
449
|
+
* - Maximum and minimum fields in any row
|
|
450
|
+
* - Useful for form complexity analysis
|
|
104
451
|
*/
|
|
105
452
|
getStats(): {
|
|
453
|
+
/** Total number of fields across all rows */
|
|
106
454
|
totalFields: number;
|
|
455
|
+
/** Total number of rows in the form */
|
|
107
456
|
totalRows: number;
|
|
457
|
+
/** Average number of fields per row */
|
|
108
458
|
averageFieldsPerRow: number;
|
|
459
|
+
/** Maximum number of fields in any single row */
|
|
109
460
|
maxFieldsInRow: number;
|
|
461
|
+
/** Minimum number of fields in any single row */
|
|
110
462
|
minFieldsInRow: number;
|
|
111
463
|
};
|
|
112
464
|
}
|
|
113
465
|
/**
|
|
114
466
|
* Factory function to create a form builder directly
|
|
467
|
+
*
|
|
468
|
+
* This is a convenience function that provides an alternative to using
|
|
469
|
+
* the class constructor or static create method. It's particularly useful
|
|
470
|
+
* for functional programming styles or when you prefer function calls
|
|
471
|
+
* over class instantiation.
|
|
472
|
+
*
|
|
473
|
+
* @template C - Component configuration map
|
|
474
|
+
* @param config - The ril configuration instance
|
|
475
|
+
* @param formId - Optional form identifier
|
|
476
|
+
* @returns A new form builder instance
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* ```typescript
|
|
480
|
+
* const builder = createForm(rilConfig, 'contact-form')
|
|
481
|
+
* .add({ type: 'text', props: { label: 'Name' } })
|
|
482
|
+
* .add({ type: 'email', props: { label: 'Email' } });
|
|
483
|
+
* ```
|
|
484
|
+
*/
|
|
485
|
+
declare function createForm<C extends Record<string, any>>(config: ril<C>, formId?: string): form<C>;
|
|
486
|
+
/**
|
|
487
|
+
* Module augmentation to add createForm method to ril instances
|
|
488
|
+
*
|
|
489
|
+
* This declaration extends the ril interface to include the createForm
|
|
490
|
+
* method, allowing for a more integrated API experience.
|
|
115
491
|
*/
|
|
116
|
-
declare function createForm(config: ril, formId?: string): form;
|
|
117
492
|
declare module '@rilaykit/core' {
|
|
118
|
-
interface ril {
|
|
119
|
-
|
|
493
|
+
interface ril<C extends Record<string, any> = Record<string, never>> {
|
|
494
|
+
/**
|
|
495
|
+
* Creates a new form builder using this ril configuration
|
|
496
|
+
*
|
|
497
|
+
* @param formId - Optional form identifier
|
|
498
|
+
* @returns A new form builder instance
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* ```typescript
|
|
502
|
+
* const builder = rilConfig.createForm('my-form');
|
|
503
|
+
* ```
|
|
504
|
+
*/
|
|
505
|
+
form(formId?: string): form<C>;
|
|
120
506
|
}
|
|
121
507
|
}
|
|
122
508
|
|
|
@@ -135,7 +521,7 @@ interface FormBodyProps {
|
|
|
135
521
|
children?: React.ReactNode | RendererChildrenFunction<FormBodyRendererProps>;
|
|
136
522
|
renderAs?: 'default' | 'children' | boolean;
|
|
137
523
|
}
|
|
138
|
-
declare function FormBody({ className, children, renderAs }: FormBodyProps): React$1.ReactNode;
|
|
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;
|
|
139
525
|
|
|
140
526
|
interface FormFieldProps {
|
|
141
527
|
fieldId: string;
|
|
@@ -143,7 +529,7 @@ interface FormFieldProps {
|
|
|
143
529
|
customProps?: Record<string, any>;
|
|
144
530
|
className?: string;
|
|
145
531
|
}
|
|
146
|
-
declare function FormField({ fieldId, disabled, customProps, className, }: FormFieldProps): react_jsx_runtime.JSX.Element
|
|
532
|
+
declare function FormField({ fieldId, disabled, customProps, className, }: FormFieldProps): react_jsx_runtime.JSX.Element;
|
|
147
533
|
|
|
148
534
|
interface FormState {
|
|
149
535
|
values: Record<string, any>;
|
|
@@ -184,13 +570,13 @@ interface FormRowProps {
|
|
|
184
570
|
children?: React.ReactNode | RendererChildrenFunction<FormRowRendererProps>;
|
|
185
571
|
renderAs?: 'default' | 'children' | boolean;
|
|
186
572
|
}
|
|
187
|
-
declare function FormRow({ row, className, children, renderAs }: FormRowProps): React$1.ReactNode;
|
|
573
|
+
declare function FormRow({ row, className, children, renderAs }: FormRowProps): string | number | boolean | React$1.ReactElement<any, string | React$1.JSXElementConstructor<any>> | Iterable<React$1.ReactNode> | null | undefined;
|
|
188
574
|
|
|
189
575
|
interface FormSubmitButtonProps {
|
|
190
576
|
className?: string;
|
|
191
577
|
children?: React__default.ReactNode | RendererChildrenFunction<FormSubmitButtonRendererProps>;
|
|
192
578
|
renderAs?: 'default' | 'children' | boolean;
|
|
193
579
|
}
|
|
194
|
-
declare function FormSubmitButton({ className, children, renderAs }: FormSubmitButtonProps): React__default.ReactNode;
|
|
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;
|
|
195
581
|
|
|
196
582
|
export { type FieldConfig, Form, FormBody, type FormBodyProps, form as FormBuilder, type FormContextValue, FormField, type FormFieldProps, type FormProps, FormProvider, type FormProviderProps, FormRow, type FormRowProps, type FormState, FormSubmitButton, type FormSubmitButtonProps, createForm, form, useFormContext };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,118 +5,504 @@ export { createZodValidator, ril } from '@rilaykit/core';
|
|
|
5
5
|
import * as React$1 from 'react';
|
|
6
6
|
import React__default from 'react';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for a form field with type safety
|
|
10
|
+
*
|
|
11
|
+
* @template C - The component configuration map
|
|
12
|
+
* @template T - The specific component type key
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const fieldConfig: FieldConfig<MyComponents, 'text'> = {
|
|
17
|
+
* type: 'text',
|
|
18
|
+
* props: { placeholder: 'Enter your name' },
|
|
19
|
+
* validation: { required: true }
|
|
20
|
+
* };
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
type FieldConfig<C extends Record<string, any>, T extends keyof C> = {
|
|
24
|
+
/** Unique identifier for the field. Auto-generated if not provided */
|
|
25
|
+
id?: string;
|
|
26
|
+
/** Component type from the registered components */
|
|
27
|
+
type: T;
|
|
28
|
+
/** Component-specific properties */
|
|
29
|
+
props?: Partial<C[T]>;
|
|
30
|
+
/** Validation rules for the field */
|
|
12
31
|
validation?: ValidationConfig;
|
|
32
|
+
/** Conditional display logic */
|
|
13
33
|
conditional?: ConditionalConfig;
|
|
14
|
-
}
|
|
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
|
+
*/
|
|
15
46
|
interface RowOptions {
|
|
47
|
+
/** Spacing between fields in the row */
|
|
16
48
|
spacing?: 'tight' | 'normal' | 'loose';
|
|
49
|
+
/** Vertical alignment of fields in the row */
|
|
17
50
|
alignment?: 'start' | 'center' | 'end' | 'stretch';
|
|
18
51
|
}
|
|
19
52
|
/**
|
|
20
|
-
* Form builder class for creating form configurations
|
|
21
|
-
*
|
|
53
|
+
* Form builder class for creating type-safe form configurations
|
|
54
|
+
*
|
|
55
|
+
* This class provides a fluent API for building forms with automatic validation,
|
|
56
|
+
* type safety, and flexible layout options. It manages field registration,
|
|
57
|
+
* row organization, and form configuration generation.
|
|
58
|
+
*
|
|
59
|
+
* @template C - Component configuration map defining available component types
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* // Create a form builder with typed components
|
|
64
|
+
* const formBuilder = form.create(rilConfig, 'user-registration')
|
|
65
|
+
* .add({ type: 'text', props: { label: 'Name' } })
|
|
66
|
+
* .add(
|
|
67
|
+
* { type: 'email', props: { label: 'Email' } },
|
|
68
|
+
* { type: 'password', props: { label: 'Password' } }
|
|
69
|
+
* )
|
|
70
|
+
* .build();
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @remarks
|
|
74
|
+
* - Supports up to 3 fields per row for optimal layout
|
|
75
|
+
* - Automatically generates unique IDs for fields and rows
|
|
76
|
+
* - Provides comprehensive validation before building
|
|
77
|
+
* - Maintains type safety throughout the building process
|
|
22
78
|
*/
|
|
23
|
-
declare class form {
|
|
79
|
+
declare class form<C extends Record<string, any> = Record<string, never>> {
|
|
80
|
+
/** The ril configuration instance containing component definitions */
|
|
24
81
|
private config;
|
|
82
|
+
/** Array of form rows containing field configurations */
|
|
25
83
|
private rows;
|
|
84
|
+
/** Unique identifier for this form */
|
|
26
85
|
private formId;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
static create(config: ril, formId?: string): form;
|
|
86
|
+
/** Generator for creating unique IDs */
|
|
87
|
+
private idGenerator;
|
|
30
88
|
/**
|
|
31
|
-
*
|
|
89
|
+
* Creates a new form builder instance
|
|
90
|
+
*
|
|
91
|
+
* @param config - The ril configuration containing component definitions
|
|
92
|
+
* @param formId - Optional unique identifier for the form. Auto-generated if not provided
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* const builder = new form(rilConfig, 'my-form');
|
|
97
|
+
* ```
|
|
32
98
|
*/
|
|
33
|
-
|
|
99
|
+
constructor(config: ril<C>, formId?: string);
|
|
34
100
|
/**
|
|
35
|
-
*
|
|
101
|
+
* Static factory method to create a new form builder
|
|
102
|
+
*
|
|
103
|
+
* @template Cm - Component configuration map
|
|
104
|
+
* @param config - The ril configuration instance
|
|
105
|
+
* @param formId - Optional form identifier
|
|
106
|
+
* @returns A new form builder instance
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* const builder = form.create(rilConfig, 'registration-form');
|
|
111
|
+
* ```
|
|
36
112
|
*/
|
|
37
|
-
|
|
113
|
+
static create<Cm extends Record<string, any> = Record<string, never>>(config: ril<Cm>, formId?: string): form<Cm>;
|
|
38
114
|
/**
|
|
39
|
-
*
|
|
115
|
+
* Converts a FieldConfig to a FormFieldConfig with proper validation
|
|
116
|
+
*
|
|
117
|
+
* This internal method handles the transformation from the builder's field
|
|
118
|
+
* configuration format to the final form field configuration, including
|
|
119
|
+
* component lookup, prop merging, and ID generation.
|
|
120
|
+
*
|
|
121
|
+
* @template T - The component type
|
|
122
|
+
* @param fieldConfig - The field configuration to convert
|
|
123
|
+
* @returns A complete FormFieldConfig ready for use
|
|
124
|
+
* @throws Error if the specified component type is not registered
|
|
125
|
+
*
|
|
126
|
+
* @internal
|
|
40
127
|
*/
|
|
41
|
-
|
|
128
|
+
private createFormField;
|
|
129
|
+
/**
|
|
130
|
+
* Creates a form row with the specified fields and options
|
|
131
|
+
*
|
|
132
|
+
* This internal method handles row creation with validation of field limits,
|
|
133
|
+
* proper spacing, and alignment configuration.
|
|
134
|
+
*
|
|
135
|
+
* @template T - The component type
|
|
136
|
+
* @param fieldConfigs - Array of field configurations for the row
|
|
137
|
+
* @param rowOptions - Optional row layout configuration
|
|
138
|
+
* @returns A complete FormFieldRow configuration
|
|
139
|
+
* @throws Error if no fields provided or more than 3 fields specified
|
|
140
|
+
*
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
private createRow;
|
|
42
144
|
/**
|
|
43
|
-
*
|
|
145
|
+
* Universal method for adding fields to the form
|
|
146
|
+
*
|
|
147
|
+
* This is the primary method for adding fields to your form. It supports multiple
|
|
148
|
+
* usage patterns for maximum flexibility:
|
|
149
|
+
*
|
|
150
|
+
* - Single field: Creates a new row with one field
|
|
151
|
+
* - Multiple fields (≤3): Creates one row with all fields
|
|
152
|
+
* - Multiple fields (>3): Creates separate rows for each field
|
|
153
|
+
* - Array with options: Explicit control over row configuration
|
|
154
|
+
*
|
|
155
|
+
* @template T - The component type
|
|
156
|
+
* @param fields - Field configurations (variadic or array)
|
|
157
|
+
* @returns The form builder instance for method chaining
|
|
158
|
+
* @throws Error if no fields provided or invalid configuration
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* // Single field on its own row
|
|
163
|
+
* builder.add({ type: 'text', props: { label: 'Name' } });
|
|
164
|
+
*
|
|
165
|
+
* // Multiple fields on same row (max 3)
|
|
166
|
+
* builder.add(
|
|
167
|
+
* { type: 'text', props: { label: 'First Name' } },
|
|
168
|
+
* { type: 'text', props: { label: 'Last Name' } }
|
|
169
|
+
* );
|
|
170
|
+
*
|
|
171
|
+
* // Array syntax with row options
|
|
172
|
+
* builder.add([
|
|
173
|
+
* { type: 'email', props: { label: 'Email' } },
|
|
174
|
+
* { type: 'phone', props: { label: 'Phone' } }
|
|
175
|
+
* ], { spacing: 'loose', alignment: 'center' });
|
|
176
|
+
* ```
|
|
44
177
|
*/
|
|
45
|
-
|
|
178
|
+
add<T extends keyof C & string>(...fields: FieldConfig<C, T>[]): this;
|
|
179
|
+
add<T extends keyof C & string>(fields: FieldConfig<C, T>[], rowOptions?: RowOptions): this;
|
|
46
180
|
/**
|
|
47
|
-
*
|
|
181
|
+
* Adds multiple fields on separate rows
|
|
182
|
+
*
|
|
183
|
+
* This method is useful when you want to ensure each field gets its own row,
|
|
184
|
+
* regardless of the number of fields. It's an alternative to the add() method
|
|
185
|
+
* when you need explicit control over row separation.
|
|
186
|
+
*
|
|
187
|
+
* @template T - The component type
|
|
188
|
+
* @param fieldConfigs - Array of field configurations
|
|
189
|
+
* @param rowOptions - Optional row layout configuration applied to all rows
|
|
190
|
+
* @returns The form builder instance for method chaining
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```typescript
|
|
194
|
+
* // Each field will be on its own row
|
|
195
|
+
* builder.addSeparateRows([
|
|
196
|
+
* { type: 'text', props: { label: 'Field 1' } },
|
|
197
|
+
* { type: 'text', props: { label: 'Field 2' } },
|
|
198
|
+
* { type: 'text', props: { label: 'Field 3' } }
|
|
199
|
+
* ], { spacing: 'loose' });
|
|
200
|
+
* ```
|
|
48
201
|
*/
|
|
49
|
-
|
|
202
|
+
addSeparateRows<T extends keyof C & string>(fieldConfigs: FieldConfig<C, T>[], rowOptions?: RowOptions): this;
|
|
50
203
|
/**
|
|
51
|
-
*
|
|
204
|
+
* Sets the form identifier
|
|
205
|
+
*
|
|
206
|
+
* @param id - The new form identifier
|
|
207
|
+
* @returns The form builder instance for method chaining
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```typescript
|
|
211
|
+
* builder.setId('user-profile-form');
|
|
212
|
+
* ```
|
|
52
213
|
*/
|
|
53
214
|
setId(id: string): this;
|
|
54
215
|
/**
|
|
55
|
-
*
|
|
216
|
+
* Updates an existing field's configuration
|
|
217
|
+
*
|
|
218
|
+
* This method allows you to modify field properties, validation rules,
|
|
219
|
+
* or conditional logic after the field has been added to the form.
|
|
220
|
+
*
|
|
221
|
+
* @param fieldId - The unique identifier of the field to update
|
|
222
|
+
* @param updates - Partial field configuration with updates to apply
|
|
223
|
+
* @returns The form builder instance for method chaining
|
|
224
|
+
* @throws Error if the field with the specified ID is not found
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```typescript
|
|
228
|
+
* builder.updateField('email-field', {
|
|
229
|
+
* props: { placeholder: 'Enter your email address' },
|
|
230
|
+
* validation: { required: true, email: true }
|
|
231
|
+
* });
|
|
232
|
+
* ```
|
|
56
233
|
*/
|
|
57
234
|
updateField(fieldId: string, updates: Partial<Omit<FormFieldConfig, 'id'>>): this;
|
|
58
235
|
/**
|
|
59
|
-
*
|
|
236
|
+
* Finds a field by its unique identifier
|
|
237
|
+
*
|
|
238
|
+
* This internal method searches through all rows to locate a field
|
|
239
|
+
* with the specified ID.
|
|
240
|
+
*
|
|
241
|
+
* @param fieldId - The field identifier to search for
|
|
242
|
+
* @returns The field configuration if found, null otherwise
|
|
243
|
+
*
|
|
244
|
+
* @internal
|
|
60
245
|
*/
|
|
61
246
|
private findField;
|
|
62
247
|
/**
|
|
63
|
-
*
|
|
248
|
+
* Removes a field from the form
|
|
249
|
+
*
|
|
250
|
+
* This method removes the specified field and cleans up any empty rows
|
|
251
|
+
* that result from the removal. The form structure is automatically
|
|
252
|
+
* reorganized to maintain consistency.
|
|
253
|
+
*
|
|
254
|
+
* @param fieldId - The unique identifier of the field to remove
|
|
255
|
+
* @returns The form builder instance for method chaining
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```typescript
|
|
259
|
+
* builder.removeField('unwanted-field-id');
|
|
260
|
+
* ```
|
|
64
261
|
*/
|
|
65
262
|
removeField(fieldId: string): this;
|
|
66
263
|
/**
|
|
67
|
-
*
|
|
264
|
+
* Retrieves a field configuration by its ID
|
|
265
|
+
*
|
|
266
|
+
* @param fieldId - The unique identifier of the field
|
|
267
|
+
* @returns The field configuration if found, undefined otherwise
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```typescript
|
|
271
|
+
* const emailField = builder.getField('email-field');
|
|
272
|
+
* if (emailField) {
|
|
273
|
+
* console.log('Email field props:', emailField.props);
|
|
274
|
+
* }
|
|
275
|
+
* ```
|
|
68
276
|
*/
|
|
69
277
|
getField(fieldId: string): FormFieldConfig | undefined;
|
|
70
278
|
/**
|
|
71
|
-
*
|
|
279
|
+
* Gets all fields as a flat array
|
|
280
|
+
*
|
|
281
|
+
* This method flattens the row structure to provide a simple array
|
|
282
|
+
* of all field configurations in the form, maintaining their order.
|
|
283
|
+
*
|
|
284
|
+
* @returns Array of all field configurations in the form
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```typescript
|
|
288
|
+
* const allFields = builder.getFields();
|
|
289
|
+
* console.log(`Form has ${allFields.length} fields`);
|
|
290
|
+
* ```
|
|
72
291
|
*/
|
|
73
292
|
getFields(): FormFieldConfig[];
|
|
74
293
|
/**
|
|
75
|
-
*
|
|
294
|
+
* Gets all rows in the form
|
|
295
|
+
*
|
|
296
|
+
* Returns a copy of the internal rows array to prevent external
|
|
297
|
+
* modification while allowing inspection of the form structure.
|
|
298
|
+
*
|
|
299
|
+
* @returns Array of all form rows
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* const rows = builder.getRows();
|
|
304
|
+
* console.log(`Form has ${rows.length} rows`);
|
|
305
|
+
* ```
|
|
76
306
|
*/
|
|
77
307
|
getRows(): FormFieldRow[];
|
|
78
308
|
/**
|
|
79
|
-
*
|
|
309
|
+
* Clears all fields and rows from the form
|
|
310
|
+
*
|
|
311
|
+
* This method resets the form to an empty state and resets the ID generator
|
|
312
|
+
* to ensure clean ID generation for subsequent fields.
|
|
313
|
+
*
|
|
314
|
+
* @returns The form builder instance for method chaining
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```typescript
|
|
318
|
+
* builder.clear().add({ type: 'text', props: { label: 'New start' } });
|
|
319
|
+
* ```
|
|
80
320
|
*/
|
|
81
321
|
clear(): this;
|
|
82
322
|
/**
|
|
83
|
-
*
|
|
323
|
+
* Creates a deep copy of the current form builder
|
|
324
|
+
*
|
|
325
|
+
* This method creates a completely independent copy of the form builder,
|
|
326
|
+
* including all field configurations and internal state. The cloned
|
|
327
|
+
* builder can be modified without affecting the original.
|
|
328
|
+
*
|
|
329
|
+
* @param newFormId - Optional new form ID for the clone
|
|
330
|
+
* @returns A new form builder instance with copied configuration
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```typescript
|
|
334
|
+
* const originalForm = builder.clone();
|
|
335
|
+
* const modifiedForm = builder.clone('modified-form')
|
|
336
|
+
* .add({ type: 'text', props: { label: 'Additional field' } });
|
|
337
|
+
* ```
|
|
84
338
|
*/
|
|
85
|
-
clone(newFormId?: string): form
|
|
339
|
+
clone(newFormId?: string): form<C>;
|
|
86
340
|
/**
|
|
87
|
-
*
|
|
341
|
+
* Validates the current form configuration
|
|
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.
|
|
346
|
+
*
|
|
347
|
+
* @returns Array of validation error messages (empty if valid)
|
|
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)
|
|
88
362
|
*/
|
|
89
363
|
validate(): string[];
|
|
90
364
|
/**
|
|
91
|
-
*
|
|
365
|
+
* Builds the final form configuration
|
|
366
|
+
*
|
|
367
|
+
* This method performs final validation and creates the complete form
|
|
368
|
+
* configuration object ready for rendering. It includes all field
|
|
369
|
+
* configurations, render settings, and metadata.
|
|
370
|
+
*
|
|
371
|
+
* @returns Complete form configuration ready for use
|
|
372
|
+
* @throws Error if validation fails
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* ```typescript
|
|
376
|
+
* try {
|
|
377
|
+
* const formConfig = builder.build();
|
|
378
|
+
* // Use formConfig with your form renderer
|
|
379
|
+
* } catch (error) {
|
|
380
|
+
* console.error('Failed to build form:', error.message);
|
|
381
|
+
* }
|
|
382
|
+
* ```
|
|
383
|
+
*
|
|
384
|
+
* @remarks
|
|
385
|
+
* The returned configuration includes:
|
|
386
|
+
* - Form ID and metadata
|
|
387
|
+
* - All rows with their field configurations
|
|
388
|
+
* - Flattened array of all fields for easy access
|
|
389
|
+
* - Component configuration reference
|
|
390
|
+
* - Render configuration for customization
|
|
92
391
|
*/
|
|
93
392
|
build(): FormConfiguration;
|
|
94
393
|
/**
|
|
95
|
-
*
|
|
394
|
+
* Exports the form configuration as JSON
|
|
395
|
+
*
|
|
396
|
+
* This method serializes the form configuration to a plain JavaScript
|
|
397
|
+
* object suitable for storage, transmission, or debugging.
|
|
398
|
+
*
|
|
399
|
+
* @returns Plain object representation of the form
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```typescript
|
|
403
|
+
* const formJson = builder.toJSON();
|
|
404
|
+
* localStorage.setItem('savedForm', JSON.stringify(formJson));
|
|
405
|
+
* ```
|
|
96
406
|
*/
|
|
97
407
|
toJSON(): any;
|
|
98
408
|
/**
|
|
99
|
-
*
|
|
409
|
+
* Imports form configuration from JSON
|
|
410
|
+
*
|
|
411
|
+
* This method restores form state from a previously exported JSON
|
|
412
|
+
* configuration. It's useful for loading saved forms or restoring
|
|
413
|
+
* form state from external sources.
|
|
414
|
+
*
|
|
415
|
+
* @param json - The JSON object containing form configuration
|
|
416
|
+
* @returns The form builder instance for method chaining
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```typescript
|
|
420
|
+
* const savedForm = JSON.parse(localStorage.getItem('savedForm'));
|
|
421
|
+
* builder.fromJSON(savedForm);
|
|
422
|
+
* ```
|
|
423
|
+
*
|
|
424
|
+
* @remarks
|
|
425
|
+
* - Only imports basic form structure (ID and rows)
|
|
426
|
+
* - Does not validate imported configuration
|
|
427
|
+
* - Existing form content is replaced
|
|
100
428
|
*/
|
|
101
429
|
fromJSON(json: any): this;
|
|
102
430
|
/**
|
|
103
|
-
*
|
|
431
|
+
* Gets comprehensive statistics about the form
|
|
432
|
+
*
|
|
433
|
+
* This method provides useful metrics about the form structure,
|
|
434
|
+
* helpful for analytics, debugging, or UI display purposes.
|
|
435
|
+
*
|
|
436
|
+
* @returns Object containing form statistics
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* ```typescript
|
|
440
|
+
* const stats = builder.getStats();
|
|
441
|
+
* console.log(`Form has ${stats.totalFields} fields in ${stats.totalRows} rows`);
|
|
442
|
+
* console.log(`Average fields per row: ${stats.averageFieldsPerRow.toFixed(1)}`);
|
|
443
|
+
* ```
|
|
444
|
+
*
|
|
445
|
+
* @remarks
|
|
446
|
+
* Statistics include:
|
|
447
|
+
* - Total number of fields and rows
|
|
448
|
+
* - Average fields per row
|
|
449
|
+
* - Maximum and minimum fields in any row
|
|
450
|
+
* - Useful for form complexity analysis
|
|
104
451
|
*/
|
|
105
452
|
getStats(): {
|
|
453
|
+
/** Total number of fields across all rows */
|
|
106
454
|
totalFields: number;
|
|
455
|
+
/** Total number of rows in the form */
|
|
107
456
|
totalRows: number;
|
|
457
|
+
/** Average number of fields per row */
|
|
108
458
|
averageFieldsPerRow: number;
|
|
459
|
+
/** Maximum number of fields in any single row */
|
|
109
460
|
maxFieldsInRow: number;
|
|
461
|
+
/** Minimum number of fields in any single row */
|
|
110
462
|
minFieldsInRow: number;
|
|
111
463
|
};
|
|
112
464
|
}
|
|
113
465
|
/**
|
|
114
466
|
* Factory function to create a form builder directly
|
|
467
|
+
*
|
|
468
|
+
* This is a convenience function that provides an alternative to using
|
|
469
|
+
* the class constructor or static create method. It's particularly useful
|
|
470
|
+
* for functional programming styles or when you prefer function calls
|
|
471
|
+
* over class instantiation.
|
|
472
|
+
*
|
|
473
|
+
* @template C - Component configuration map
|
|
474
|
+
* @param config - The ril configuration instance
|
|
475
|
+
* @param formId - Optional form identifier
|
|
476
|
+
* @returns A new form builder instance
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* ```typescript
|
|
480
|
+
* const builder = createForm(rilConfig, 'contact-form')
|
|
481
|
+
* .add({ type: 'text', props: { label: 'Name' } })
|
|
482
|
+
* .add({ type: 'email', props: { label: 'Email' } });
|
|
483
|
+
* ```
|
|
484
|
+
*/
|
|
485
|
+
declare function createForm<C extends Record<string, any>>(config: ril<C>, formId?: string): form<C>;
|
|
486
|
+
/**
|
|
487
|
+
* Module augmentation to add createForm method to ril instances
|
|
488
|
+
*
|
|
489
|
+
* This declaration extends the ril interface to include the createForm
|
|
490
|
+
* method, allowing for a more integrated API experience.
|
|
115
491
|
*/
|
|
116
|
-
declare function createForm(config: ril, formId?: string): form;
|
|
117
492
|
declare module '@rilaykit/core' {
|
|
118
|
-
interface ril {
|
|
119
|
-
|
|
493
|
+
interface ril<C extends Record<string, any> = Record<string, never>> {
|
|
494
|
+
/**
|
|
495
|
+
* Creates a new form builder using this ril configuration
|
|
496
|
+
*
|
|
497
|
+
* @param formId - Optional form identifier
|
|
498
|
+
* @returns A new form builder instance
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* ```typescript
|
|
502
|
+
* const builder = rilConfig.createForm('my-form');
|
|
503
|
+
* ```
|
|
504
|
+
*/
|
|
505
|
+
form(formId?: string): form<C>;
|
|
120
506
|
}
|
|
121
507
|
}
|
|
122
508
|
|
|
@@ -135,7 +521,7 @@ interface FormBodyProps {
|
|
|
135
521
|
children?: React.ReactNode | RendererChildrenFunction<FormBodyRendererProps>;
|
|
136
522
|
renderAs?: 'default' | 'children' | boolean;
|
|
137
523
|
}
|
|
138
|
-
declare function FormBody({ className, children, renderAs }: FormBodyProps): React$1.ReactNode;
|
|
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;
|
|
139
525
|
|
|
140
526
|
interface FormFieldProps {
|
|
141
527
|
fieldId: string;
|
|
@@ -143,7 +529,7 @@ interface FormFieldProps {
|
|
|
143
529
|
customProps?: Record<string, any>;
|
|
144
530
|
className?: string;
|
|
145
531
|
}
|
|
146
|
-
declare function FormField({ fieldId, disabled, customProps, className, }: FormFieldProps): react_jsx_runtime.JSX.Element
|
|
532
|
+
declare function FormField({ fieldId, disabled, customProps, className, }: FormFieldProps): react_jsx_runtime.JSX.Element;
|
|
147
533
|
|
|
148
534
|
interface FormState {
|
|
149
535
|
values: Record<string, any>;
|
|
@@ -184,13 +570,13 @@ interface FormRowProps {
|
|
|
184
570
|
children?: React.ReactNode | RendererChildrenFunction<FormRowRendererProps>;
|
|
185
571
|
renderAs?: 'default' | 'children' | boolean;
|
|
186
572
|
}
|
|
187
|
-
declare function FormRow({ row, className, children, renderAs }: FormRowProps): React$1.ReactNode;
|
|
573
|
+
declare function FormRow({ row, className, children, renderAs }: FormRowProps): string | number | boolean | React$1.ReactElement<any, string | React$1.JSXElementConstructor<any>> | Iterable<React$1.ReactNode> | null | undefined;
|
|
188
574
|
|
|
189
575
|
interface FormSubmitButtonProps {
|
|
190
576
|
className?: string;
|
|
191
577
|
children?: React__default.ReactNode | RendererChildrenFunction<FormSubmitButtonRendererProps>;
|
|
192
578
|
renderAs?: 'default' | 'children' | boolean;
|
|
193
579
|
}
|
|
194
|
-
declare function FormSubmitButton({ className, children, renderAs }: FormSubmitButtonProps): React__default.ReactNode;
|
|
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;
|
|
195
581
|
|
|
196
582
|
export { type FieldConfig, Form, FormBody, type FormBodyProps, form as FormBuilder, type FormContextValue, FormField, type FormFieldProps, type FormProps, FormProvider, type FormProviderProps, FormRow, type FormRowProps, type FormState, FormSubmitButton, type FormSubmitButtonProps, createForm, form, useFormContext };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var core=require('@rilaykit/core'),or=require('react'),jsxRuntime=require('react/jsx-runtime');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var or__default=/*#__PURE__*/_interopDefault(or);var v=class o{constructor(r,e){this.rows=[];this.rowCounter=0;this.config=r,this.formId=e||`form-${Date.now()}`;}static create(r,e){return new o(r,e)}createFormField(r){let e=this.config.getComponent(r.type);if(!e)throw new Error(`No component found with type "${r.type}"`);return {id:r.id,componentId:e.id,props:{...e.defaultProps,...r.props},validation:r.validation,conditional:r.conditional}}createRow(r,e){if(r.length===0)throw new Error("At least one field is required");if(r.length>3)throw new Error("Maximum 3 fields per row");let i=r.map(s=>this.createFormField(s));return {id:`row-${++this.rowCounter}`,fields:i,maxColumns:r.length,spacing:e?.spacing||"normal",alignment:e?.alignment||"stretch"}}addField(r){return this.addRowFields([r])}addRowFields(r,e){let i=this.createRow(r,e);return this.rows.push(i),this}addFields(r){for(let e of r)this.addField(e);return this}setId(r){return this.formId=r,this}updateField(r,e){let i=this.findField(r);if(!i)throw new Error(`Field with ID "${r}" not found`);return Object.assign(i,{...e,props:{...i.props,...e.props}}),this}findField(r){for(let e of this.rows){let i=e.fields.find(s=>s.id===r);if(i)return i}return null}removeField(r){return this.rows=this.rows.map(e=>({...e,fields:e.fields.filter(i=>i.id!==r)})).filter(e=>e.fields.length>0),this}getField(r){return this.findField(r)||void 0}getFields(){return this.rows.flatMap(r=>r.fields)}getRows(){return [...this.rows]}clear(){return this.rows=[],this.rowCounter=0,this}clone(r){let e=new o(this.config,r||`${this.formId}-clone`);return e.rows=this.rows.map(i=>({...i,fields:i.fields.map(s=>({...s}))})),e.rowCounter=this.rowCounter,e}validate(){let r=[],e=this.getFields(),i=e.map(d=>d.id),s=i.filter((d,u)=>i.indexOf(d)!==u);s.length>0&&r.push(`Duplicate field IDs: ${s.join(", ")}`);for(let d of e)this.config.hasComponent(d.componentId)||r.push(`Component "${d.componentId}" not found for field "${d.id}"`);for(let d of this.rows)d.fields.length>3&&r.push(`Row "${d.id}" has ${d.fields.length} fields, maximum is 3`),d.fields.length===0&&r.push(`Row "${d.id}" is empty`);return r}build(){let r=this.validate();if(r.length>0)throw new Error(`Form validation failed: ${r.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(r){return r.id&&(this.formId=r.id),r.rows&&(this.rows=r.rows,this.rowCounter=this.rows.length),this}getStats(){let r=this.getFields(),e=this.rows.map(i=>i.fields.length);return {totalFields:r.length,totalRows:this.rows.length,averageFieldsPerRow:this.rows.length>0?r.length/this.rows.length:0,maxFieldsInRow:e.length>0?Math.max(...e):0,minFieldsInRow:e.length>0?Math.min(...e):0}}};function Z(o,r){return v.create(o,r)}core.ril.prototype.createForm=function(o){return v.create(this,o)};function j(o,r){switch(r.type){case "SET_VALUE":{let e={...o.values,[r.fieldId]:r.value};return {...o,values:e,isDirty:true}}case "SET_ERROR":return {...o,errors:{...o.errors,[r.fieldId]:r.errors},isValid:false};case "CLEAR_ERROR":{let e={...o.errors};return delete e[r.fieldId],{...o,errors:e}}case "MARK_TOUCHED":return {...o,touched:new Set([...o.touched,r.fieldId])};case "SET_VALIDATING":{let e=new Set(o.isValidating);return r.isValidating?e.add(r.fieldId):e.delete(r.fieldId),{...o,isValidating:e}}case "SET_SUBMITTING":return {...o,isSubmitting:r.isSubmitting};case "RESET":return {values:r.values||{},errors:{},touched:new Set,isValidating:new Set,isDirty:false,isValid:true,isSubmitting:false};case "UPDATE_VALIDATION_STATE":{let e=Object.keys(o.errors).some(i=>o.errors[i].length>0);return {...o,isValid:!e}}default:return o}}var k=or.createContext(null);function B({children:o,formConfig:r,defaultValues:e={},onSubmit:i,onFieldChange:s,className:d}){let u={values:e,errors:{},touched:new Set,isValidating:new Set,isDirty:false,isValid:true,isSubmitting:false},[a,l]=or.useReducer(j,u),t=or.useRef(new Map),c=or.useRef(i),m=or.useRef(s);c.current=i,m.current=s;let R=or.useCallback((n,f)=>{l({type:"SET_ERROR",fieldId:n,errors:f}),l({type:"UPDATE_VALIDATION_STATE"});},[]),b=or.useCallback(n=>{l({type:"CLEAR_ERROR",fieldId:n}),l({type:"UPDATE_VALIDATION_STATE"});},[]),V=or.useCallback(n=>{l({type:"MARK_TOUCHED",fieldId:n});},[]),p=or.useCallback((n,f)=>{l({type:"SET_VALIDATING",fieldId:n,isValidating:f});},[]),x=or.useCallback((n,f)=>{if(l({type:"SET_VALUE",fieldId:n,value:f}),a.errors[n]&&a.errors[n].length>0&&(l({type:"CLEAR_ERROR",fieldId:n}),l({type:"UPDATE_VALIDATION_STATE"})),m.current){let g={...a.values,[n]:f};m.current(n,f,g);}},[a.errors,a.values]),w=or.useMemo(()=>r,[r]),E=or.useCallback(async(n,f)=>{let g=w.allFields.find(I=>I.id===n);if(!g?.validation?.validator)return {isValid:true,errors:[]};let K=f!==void 0?f:a.values[n],N=t.current.get(n);N&&clearTimeout(N);let W={fieldId:n,formData:a.values,fieldProps:g.props,touched:a.touched.has(n),dirty:a.isDirty},_=g.validation.debounceMs||0;return new Promise(I=>{let O=async()=>{p(n,true);try{let y=await g.validation.validator(K,W,g.props);y.errors.length>0?R(n,y.errors):b(n),I(y);}catch(y){let U={isValid:false,errors:[{code:"validation_error",message:y instanceof Error?y.message:"Validation error"}]};R(n,U.errors),I(U);}finally{p(n,false);}};if(_>0){let y=setTimeout(O,_);t.current.set(n,y);}else O();})},[w,a.values,a.touched,a.isDirty,R,b,p]),P=or.useCallback(async()=>{let n=w.allFields.map(g=>E(g.id));return (await Promise.all(n)).every(g=>g.isValid)},[w,E]),A=or.useCallback(n=>{l({type:"RESET",values:n});},[]),T=or.useCallback(async n=>{if(n?.preventDefault(),!c.current)return true;l({type:"SET_SUBMITTING",isSubmitting:true});try{return await P()?(await c.current(a.values),!0):!1}catch(f){return console.error("Error during form submission:",f),false}finally{l({type:"SET_SUBMITTING",isSubmitting:false});}},[a.values,P]),h=or.useMemo(()=>({formState:a,formConfig:w,setValue:x,setError:R,clearError:b,markFieldTouched:V,setFieldValidating:p,validateField:E,validateAllFields:P,reset:A,submit:T}),[a,w,x,R,b,V,p,E,P,A,T]);return jsxRuntime.jsx(k.Provider,{value:h,children:jsxRuntime.jsx("form",{onSubmit:T,className:d,noValidate:true,children:o})})}function C(){let o=or.useContext(k);if(!o)throw new Error("useFormContext must be used within a FormProvider");return o}function rr({formConfig:o,defaultValues:r,onSubmit:e,onFieldChange:i,children:s}){let d=o instanceof v?o.build():o;return jsxRuntime.jsx(B,{formConfig:d,defaultValues:r,onSubmit:e,onFieldChange:i,children:s})}function G({fieldId:o,disabled:r=false,customProps:e={},className:i}){let{formState:s,formConfig:d,setValue:u,markFieldTouched:a,validateField:l}=C(),t=or.useMemo(()=>d.allFields.find(h=>h.id===o),[d.allFields,o]);if(!t)throw new Error(`Field with ID "${o}" not found`);let c=or.useMemo(()=>d.config.getComponent(t.componentId),[d.config,t.componentId]);if(!c)throw new Error(`Component with ID "${t.componentId}" not found`);let m=or.useMemo(()=>({value:s.values[t.id],errors:s.errors[t.id]||[],touched:s.touched.has(t.id),validating:s.isValidating.has(t.id)}),[s.values,s.errors,s.touched,s.isValidating,t.id]),R=or.useCallback(h=>{let n=m.errors.length>0;u(t.id,h),(t.validation?.validateOnChange||n&&t.validation?.validator||m.touched&&t.validation?.validator)&&l(t.id,h);},[t.id,t.validation,u,l,m.errors.length,m.touched]),b=or.useCallback(()=>{a(t.id),(t.validation?.validateOnBlur||t.validation?.validator)&&l(t.id);},[t.id,t.validation,a,l]),V=or.useMemo(()=>{if(!t.conditional)return true;try{return t.conditional.condition(s.values)}catch(h){return console.warn(`Conditional evaluation failed for field "${t.id}":`,h),true}},[t.conditional,s.values,t.id]),p=or.useMemo(()=>{if(!t.conditional||!V)return {};switch(t.conditional.action){case "disable":return {disabled:true};case "require":return {required:true};default:return {}}},[t.conditional,V]);if(!V&&t.conditional?.action==="hide")return null;let x=or.useMemo(()=>({...c.defaultProps,...t.props,...e,...p}),[c.defaultProps,t.props,e,p]),w=or.useMemo(()=>({id:t.id,props:x,value:m.value,onChange:R,onBlur:b,error:m.errors,touched:m.touched,disabled:r||p.disabled,isValidating:m.validating}),[t.id,x,m.value,R,b,m.errors,m.touched,r,p.disabled,m.validating]),E=d.renderConfig?.fieldRenderer,P=c.renderer(w),A=c.useFieldRenderer!==false,T=E&&A?E({children:P,id:t.id,error:m.errors,touched:m.touched,disabled:r||p.disabled,isValidating:m.validating,...x}):P;return jsxRuntime.jsx("div",{className:i,"data-field-id":t.id,"data-field-type":c.type,children:T})}var q=or__default.default.memo(G);function H({row:o,className:r,children:e,renderAs:i}){let{formConfig:s}=C(),d=o.fields.map(m=>jsxRuntime.jsx(q,{fieldId:m.id},m.id));if(i==="children"||i===true){if(typeof e!="function")throw new Error('When renderAs="children" is used, children must be a function that returns React elements');let m={row:o,children:d,className:r,spacing:o.spacing,alignment:o.alignment};return e(m)}let a=s.renderConfig?.rowRenderer;if(!a)throw new Error(`No rowRenderer configured for form "${s.id}". Please configure a rowRenderer using config.setRowRenderer() or config.setFormRenderConfig().`);let l={row:o,children:d,className:r,spacing:o.spacing,alignment:o.alignment},t=core.resolveRendererChildren(e,l),c={...l,children:t||d};return a(c)}var J=H;function ar({className:o,children:r,renderAs:e}){let{formConfig:i}=C(),s=or.useMemo(()=>i.rows.map(c=>jsxRuntime.jsx(J,{row:c},c.id)),[i.rows]);if(e==="children"||e===true){if(typeof r!="function")throw new Error('When renderAs="children" is used, children must be a function that returns React elements');return r({formConfig:i,children:s,className:o})}let u=i.renderConfig?.bodyRenderer;if(!u)throw new Error(`No bodyRenderer configured for form "${i.id}". Please configure a bodyRenderer using config.setBodyRenderer() or config.setFormRenderConfig().`);let a={formConfig:i,children:s,className:o},l=core.resolveRendererChildren(r,a),t={...a,children:l||s};return u(t)}function cr({className:o,children:r,renderAs:e}){let{formState:i,submit:s,formConfig:d}=C(),u={isSubmitting:i.isSubmitting,isValid:i.isValid,isDirty:i.isDirty,onSubmit:s,className:o};if(e==="children"||e===true){if(typeof r!="function")throw new Error('When renderAs="children" is used, children must be a function that returns React elements');return r(u)}let l=d.renderConfig?.submitButtonRenderer;if(!l)throw new Error(`No submitButtonRenderer configured for form "${d.id}". Please configure a submitButtonRenderer using config.setSubmitButtonRenderer() or config.setFormRenderConfig().`);let t=core.resolveRendererChildren(r,u),c={...u,children:t};return l(c)}Object.defineProperty(exports,"createZodValidator",{enumerable:true,get:function(){return core.createZodValidator}});Object.defineProperty(exports,"ril",{enumerable:true,get:function(){return core.ril}});exports.Form=rr;exports.FormBody=ar;exports.FormBuilder=v;exports.FormField=G;exports.FormProvider=B;exports.FormRow=H;exports.FormSubmitButton=cr;exports.createForm=Z;exports.form=v;exports.useFormContext=C;
|
|
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;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {ril,resolveRendererChildren}from'@rilaykit/core';export{createZodValidator,ril}from'@rilaykit/core';import or,{createContext,useMemo,useCallback,useContext,useReducer,useRef}from'react';import {jsx}from'react/jsx-runtime';var v=class o{constructor(r,e){this.rows=[];this.rowCounter=0;this.config=r,this.formId=e||`form-${Date.now()}`;}static create(r,e){return new o(r,e)}createFormField(r){let e=this.config.getComponent(r.type);if(!e)throw new Error(`No component found with type "${r.type}"`);return {id:r.id,componentId:e.id,props:{...e.defaultProps,...r.props},validation:r.validation,conditional:r.conditional}}createRow(r,e){if(r.length===0)throw new Error("At least one field is required");if(r.length>3)throw new Error("Maximum 3 fields per row");let i=r.map(s=>this.createFormField(s));return {id:`row-${++this.rowCounter}`,fields:i,maxColumns:r.length,spacing:e?.spacing||"normal",alignment:e?.alignment||"stretch"}}addField(r){return this.addRowFields([r])}addRowFields(r,e){let i=this.createRow(r,e);return this.rows.push(i),this}addFields(r){for(let e of r)this.addField(e);return this}setId(r){return this.formId=r,this}updateField(r,e){let i=this.findField(r);if(!i)throw new Error(`Field with ID "${r}" not found`);return Object.assign(i,{...e,props:{...i.props,...e.props}}),this}findField(r){for(let e of this.rows){let i=e.fields.find(s=>s.id===r);if(i)return i}return null}removeField(r){return this.rows=this.rows.map(e=>({...e,fields:e.fields.filter(i=>i.id!==r)})).filter(e=>e.fields.length>0),this}getField(r){return this.findField(r)||void 0}getFields(){return this.rows.flatMap(r=>r.fields)}getRows(){return [...this.rows]}clear(){return this.rows=[],this.rowCounter=0,this}clone(r){let e=new o(this.config,r||`${this.formId}-clone`);return e.rows=this.rows.map(i=>({...i,fields:i.fields.map(s=>({...s}))})),e.rowCounter=this.rowCounter,e}validate(){let r=[],e=this.getFields(),i=e.map(d=>d.id),s=i.filter((d,u)=>i.indexOf(d)!==u);s.length>0&&r.push(`Duplicate field IDs: ${s.join(", ")}`);for(let d of e)this.config.hasComponent(d.componentId)||r.push(`Component "${d.componentId}" not found for field "${d.id}"`);for(let d of this.rows)d.fields.length>3&&r.push(`Row "${d.id}" has ${d.fields.length} fields, maximum is 3`),d.fields.length===0&&r.push(`Row "${d.id}" is empty`);return r}build(){let r=this.validate();if(r.length>0)throw new Error(`Form validation failed: ${r.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(r){return r.id&&(this.formId=r.id),r.rows&&(this.rows=r.rows,this.rowCounter=this.rows.length),this}getStats(){let r=this.getFields(),e=this.rows.map(i=>i.fields.length);return {totalFields:r.length,totalRows:this.rows.length,averageFieldsPerRow:this.rows.length>0?r.length/this.rows.length:0,maxFieldsInRow:e.length>0?Math.max(...e):0,minFieldsInRow:e.length>0?Math.min(...e):0}}};function Z(o,r){return v.create(o,r)}ril.prototype.createForm=function(o){return v.create(this,o)};function j(o,r){switch(r.type){case "SET_VALUE":{let e={...o.values,[r.fieldId]:r.value};return {...o,values:e,isDirty:true}}case "SET_ERROR":return {...o,errors:{...o.errors,[r.fieldId]:r.errors},isValid:false};case "CLEAR_ERROR":{let e={...o.errors};return delete e[r.fieldId],{...o,errors:e}}case "MARK_TOUCHED":return {...o,touched:new Set([...o.touched,r.fieldId])};case "SET_VALIDATING":{let e=new Set(o.isValidating);return r.isValidating?e.add(r.fieldId):e.delete(r.fieldId),{...o,isValidating:e}}case "SET_SUBMITTING":return {...o,isSubmitting:r.isSubmitting};case "RESET":return {values:r.values||{},errors:{},touched:new Set,isValidating:new Set,isDirty:false,isValid:true,isSubmitting:false};case "UPDATE_VALIDATION_STATE":{let e=Object.keys(o.errors).some(i=>o.errors[i].length>0);return {...o,isValid:!e}}default:return o}}var k=createContext(null);function B({children:o,formConfig:r,defaultValues:e={},onSubmit:i,onFieldChange:s,className:d}){let u={values:e,errors:{},touched:new Set,isValidating:new Set,isDirty:false,isValid:true,isSubmitting:false},[a,l]=useReducer(j,u),t=useRef(new Map),c=useRef(i),m=useRef(s);c.current=i,m.current=s;let R=useCallback((n,f)=>{l({type:"SET_ERROR",fieldId:n,errors:f}),l({type:"UPDATE_VALIDATION_STATE"});},[]),b=useCallback(n=>{l({type:"CLEAR_ERROR",fieldId:n}),l({type:"UPDATE_VALIDATION_STATE"});},[]),V=useCallback(n=>{l({type:"MARK_TOUCHED",fieldId:n});},[]),p=useCallback((n,f)=>{l({type:"SET_VALIDATING",fieldId:n,isValidating:f});},[]),x=useCallback((n,f)=>{if(l({type:"SET_VALUE",fieldId:n,value:f}),a.errors[n]&&a.errors[n].length>0&&(l({type:"CLEAR_ERROR",fieldId:n}),l({type:"UPDATE_VALIDATION_STATE"})),m.current){let g={...a.values,[n]:f};m.current(n,f,g);}},[a.errors,a.values]),w=useMemo(()=>r,[r]),E=useCallback(async(n,f)=>{let g=w.allFields.find(I=>I.id===n);if(!g?.validation?.validator)return {isValid:true,errors:[]};let K=f!==void 0?f:a.values[n],N=t.current.get(n);N&&clearTimeout(N);let W={fieldId:n,formData:a.values,fieldProps:g.props,touched:a.touched.has(n),dirty:a.isDirty},_=g.validation.debounceMs||0;return new Promise(I=>{let O=async()=>{p(n,true);try{let y=await g.validation.validator(K,W,g.props);y.errors.length>0?R(n,y.errors):b(n),I(y);}catch(y){let U={isValid:false,errors:[{code:"validation_error",message:y instanceof Error?y.message:"Validation error"}]};R(n,U.errors),I(U);}finally{p(n,false);}};if(_>0){let y=setTimeout(O,_);t.current.set(n,y);}else O();})},[w,a.values,a.touched,a.isDirty,R,b,p]),P=useCallback(async()=>{let n=w.allFields.map(g=>E(g.id));return (await Promise.all(n)).every(g=>g.isValid)},[w,E]),A=useCallback(n=>{l({type:"RESET",values:n});},[]),T=useCallback(async n=>{if(n?.preventDefault(),!c.current)return true;l({type:"SET_SUBMITTING",isSubmitting:true});try{return await P()?(await c.current(a.values),!0):!1}catch(f){return console.error("Error during form submission:",f),false}finally{l({type:"SET_SUBMITTING",isSubmitting:false});}},[a.values,P]),h=useMemo(()=>({formState:a,formConfig:w,setValue:x,setError:R,clearError:b,markFieldTouched:V,setFieldValidating:p,validateField:E,validateAllFields:P,reset:A,submit:T}),[a,w,x,R,b,V,p,E,P,A,T]);return jsx(k.Provider,{value:h,children:jsx("form",{onSubmit:T,className:d,noValidate:true,children:o})})}function C(){let o=useContext(k);if(!o)throw new Error("useFormContext must be used within a FormProvider");return o}function rr({formConfig:o,defaultValues:r,onSubmit:e,onFieldChange:i,children:s}){let d=o instanceof v?o.build():o;return jsx(B,{formConfig:d,defaultValues:r,onSubmit:e,onFieldChange:i,children:s})}function G({fieldId:o,disabled:r=false,customProps:e={},className:i}){let{formState:s,formConfig:d,setValue:u,markFieldTouched:a,validateField:l}=C(),t=useMemo(()=>d.allFields.find(h=>h.id===o),[d.allFields,o]);if(!t)throw new Error(`Field with ID "${o}" not found`);let c=useMemo(()=>d.config.getComponent(t.componentId),[d.config,t.componentId]);if(!c)throw new Error(`Component with ID "${t.componentId}" not found`);let m=useMemo(()=>({value:s.values[t.id],errors:s.errors[t.id]||[],touched:s.touched.has(t.id),validating:s.isValidating.has(t.id)}),[s.values,s.errors,s.touched,s.isValidating,t.id]),R=useCallback(h=>{let n=m.errors.length>0;u(t.id,h),(t.validation?.validateOnChange||n&&t.validation?.validator||m.touched&&t.validation?.validator)&&l(t.id,h);},[t.id,t.validation,u,l,m.errors.length,m.touched]),b=useCallback(()=>{a(t.id),(t.validation?.validateOnBlur||t.validation?.validator)&&l(t.id);},[t.id,t.validation,a,l]),V=useMemo(()=>{if(!t.conditional)return true;try{return t.conditional.condition(s.values)}catch(h){return console.warn(`Conditional evaluation failed for field "${t.id}":`,h),true}},[t.conditional,s.values,t.id]),p=useMemo(()=>{if(!t.conditional||!V)return {};switch(t.conditional.action){case "disable":return {disabled:true};case "require":return {required:true};default:return {}}},[t.conditional,V]);if(!V&&t.conditional?.action==="hide")return null;let x=useMemo(()=>({...c.defaultProps,...t.props,...e,...p}),[c.defaultProps,t.props,e,p]),w=useMemo(()=>({id:t.id,props:x,value:m.value,onChange:R,onBlur:b,error:m.errors,touched:m.touched,disabled:r||p.disabled,isValidating:m.validating}),[t.id,x,m.value,R,b,m.errors,m.touched,r,p.disabled,m.validating]),E=d.renderConfig?.fieldRenderer,P=c.renderer(w),A=c.useFieldRenderer!==false,T=E&&A?E({children:P,id:t.id,error:m.errors,touched:m.touched,disabled:r||p.disabled,isValidating:m.validating,...x}):P;return jsx("div",{className:i,"data-field-id":t.id,"data-field-type":c.type,children:T})}var q=or.memo(G);function H({row:o,className:r,children:e,renderAs:i}){let{formConfig:s}=C(),d=o.fields.map(m=>jsx(q,{fieldId:m.id},m.id));if(i==="children"||i===true){if(typeof e!="function")throw new Error('When renderAs="children" is used, children must be a function that returns React elements');let m={row:o,children:d,className:r,spacing:o.spacing,alignment:o.alignment};return e(m)}let a=s.renderConfig?.rowRenderer;if(!a)throw new Error(`No rowRenderer configured for form "${s.id}". Please configure a rowRenderer using config.setRowRenderer() or config.setFormRenderConfig().`);let l={row:o,children:d,className:r,spacing:o.spacing,alignment:o.alignment},t=resolveRendererChildren(e,l),c={...l,children:t||d};return a(c)}var J=H;function ar({className:o,children:r,renderAs:e}){let{formConfig:i}=C(),s=useMemo(()=>i.rows.map(c=>jsx(J,{row:c},c.id)),[i.rows]);if(e==="children"||e===true){if(typeof r!="function")throw new Error('When renderAs="children" is used, children must be a function that returns React elements');return r({formConfig:i,children:s,className:o})}let u=i.renderConfig?.bodyRenderer;if(!u)throw new Error(`No bodyRenderer configured for form "${i.id}". Please configure a bodyRenderer using config.setBodyRenderer() or config.setFormRenderConfig().`);let a={formConfig:i,children:s,className:o},l=resolveRendererChildren(r,a),t={...a,children:l||s};return u(t)}function cr({className:o,children:r,renderAs:e}){let{formState:i,submit:s,formConfig:d}=C(),u={isSubmitting:i.isSubmitting,isValid:i.isValid,isDirty:i.isDirty,onSubmit:s,className:o};if(e==="children"||e===true){if(typeof r!="function")throw new Error('When renderAs="children" is used, children must be a function that returns React elements');return r(u)}let l=d.renderConfig?.submitButtonRenderer;if(!l)throw new Error(`No submitButtonRenderer configured for form "${d.id}". Please configure a submitButtonRenderer using config.setSubmitButtonRenderer() or config.setFormRenderConfig().`);let t=resolveRendererChildren(r,u),c={...u,children:t};return l(c)}export{rr as Form,ar as FormBody,v as FormBuilder,G as FormField,B as FormProvider,H as FormRow,cr as FormSubmitButton,Z as createForm,v as form,C as useFormContext};
|
|
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};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rilaykit/forms",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.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": "4.0.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"react": ">=18.0.0",
|