@connect-soft/form-generator 1.1.0-alpha5 → 1.1.0-alpha7
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/README.md +164 -59
- package/dist/index.js +363 -454
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +363 -451
- package/dist/index.mjs.map +1 -1
- package/dist/types/components/form/array-field-renderer.d.ts +39 -0
- package/dist/types/components/form/array-field-renderer.d.ts.map +1 -0
- package/dist/types/components/form/create-template-fields.d.ts +1 -2
- package/dist/types/components/form/create-template-fields.d.ts.map +1 -1
- package/dist/types/components/form/field-renderer.d.ts +1 -6
- package/dist/types/components/form/field-renderer.d.ts.map +1 -1
- package/dist/types/components/form/form-generator.d.ts.map +1 -1
- package/dist/types/components/form/form-utils.d.ts +3 -9
- package/dist/types/components/form/form-utils.d.ts.map +1 -1
- package/dist/types/components/form/index.d.ts +4 -2
- package/dist/types/components/form/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/field-registry.d.ts +10 -50
- package/dist/types/lib/field-registry.d.ts.map +1 -1
- package/dist/types/lib/field-types.d.ts +18 -30
- package/dist/types/lib/field-types.d.ts.map +1 -1
- package/dist/types/lib/index.d.ts +3 -5
- package/dist/types/lib/index.d.ts.map +1 -1
- package/dist/types/lib/template-types.d.ts +18 -17
- package/dist/types/lib/template-types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
- **Imperative API**: Control form via ref (`setValues`, `reset`, `submit`, etc.)
|
|
17
17
|
- **Flexible**: Register custom field components with a simple API
|
|
18
18
|
- **Validation**: Built-in Zod validation support
|
|
19
|
-
- **
|
|
19
|
+
- **Array Fields**: Repeatable field groups with `useFieldArray` integration
|
|
20
|
+
- **Custom Layouts**: Full control over form layout via render props
|
|
20
21
|
- **Lightweight**: No UI dependencies, minimal footprint
|
|
21
22
|
- **HTML Fallbacks**: Works out of the box with native HTML inputs
|
|
22
23
|
|
|
@@ -69,58 +70,105 @@ function MyForm() {
|
|
|
69
70
|
|
|
70
71
|
## Registering Custom Components
|
|
71
72
|
|
|
72
|
-
Register your own UI components to replace the HTML fallbacks:
|
|
73
|
+
Register your own UI components to replace the HTML fallbacks. Field components are fully responsible for rendering their own label, description, and error messages:
|
|
73
74
|
|
|
74
75
|
```typescript
|
|
75
|
-
import { registerFields,
|
|
76
|
+
import { registerFields, registerFormComponent } from '@connect-soft/form-generator';
|
|
76
77
|
import { Input } from './ui/input';
|
|
77
78
|
import { Label } from './ui/label';
|
|
78
79
|
import { Checkbox } from './ui/checkbox';
|
|
79
80
|
import { Button } from './ui/button';
|
|
80
81
|
|
|
81
|
-
// Register field components
|
|
82
|
+
// Register field components - they handle their own rendering
|
|
82
83
|
registerFields({
|
|
83
|
-
text: ({ field, formField }) => (
|
|
84
|
-
<
|
|
85
|
-
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
text: ({ field, formField, fieldState }) => (
|
|
85
|
+
<div className="form-field">
|
|
86
|
+
{field.label && <Label>{field.label}{field.required && ' *'}</Label>}
|
|
87
|
+
<Input
|
|
88
|
+
{...formField}
|
|
89
|
+
type={field.fieldType || 'text'}
|
|
90
|
+
placeholder={field.placeholder}
|
|
91
|
+
disabled={field.disabled}
|
|
92
|
+
/>
|
|
93
|
+
{field.description && <p className="text-sm text-muted">{field.description}</p>}
|
|
94
|
+
{fieldState.error && <span className="text-red-500 text-sm">{fieldState.error.message}</span>}
|
|
95
|
+
</div>
|
|
90
96
|
),
|
|
91
|
-
number: ({ field, formField }) => (
|
|
92
|
-
<
|
|
93
|
-
{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
number: ({ field, formField, fieldState }) => (
|
|
98
|
+
<div className="form-field">
|
|
99
|
+
{field.label && <Label>{field.label}</Label>}
|
|
100
|
+
<Input
|
|
101
|
+
{...formField}
|
|
102
|
+
type="number"
|
|
103
|
+
min={field.min}
|
|
104
|
+
max={field.max}
|
|
105
|
+
/>
|
|
106
|
+
{fieldState.error && <span className="text-red-500 text-sm">{fieldState.error.message}</span>}
|
|
107
|
+
</div>
|
|
98
108
|
),
|
|
99
|
-
checkbox: {
|
|
100
|
-
|
|
109
|
+
checkbox: ({ field, formField }) => (
|
|
110
|
+
<div className="flex items-center gap-2">
|
|
101
111
|
<Checkbox
|
|
102
112
|
checked={formField.value}
|
|
103
113
|
onCheckedChange={formField.onChange}
|
|
104
114
|
disabled={field.disabled}
|
|
105
115
|
/>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
},
|
|
116
|
+
{field.label && <Label>{field.label}</Label>}
|
|
117
|
+
</div>
|
|
118
|
+
),
|
|
111
119
|
});
|
|
112
120
|
|
|
113
|
-
// Register
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
121
|
+
// Register the submit button component
|
|
122
|
+
registerFormComponent('SubmitButton', Button);
|
|
123
|
+
|
|
124
|
+
// Optional: Register field wrappers for custom styling
|
|
125
|
+
registerFormComponent('FieldWrapper', ({ children, name, type, className }) => (
|
|
126
|
+
<div className={`field-wrapper field-${type}`} data-field={name}>
|
|
127
|
+
{children}
|
|
128
|
+
</div>
|
|
129
|
+
));
|
|
130
|
+
|
|
131
|
+
registerFormComponent('FieldsWrapper', ({ children }) => (
|
|
132
|
+
<div className="form-fields">
|
|
133
|
+
{children}
|
|
134
|
+
</div>
|
|
135
|
+
));
|
|
120
136
|
```
|
|
121
137
|
|
|
122
138
|
---
|
|
123
139
|
|
|
140
|
+
## Field Wrappers
|
|
141
|
+
|
|
142
|
+
Customize how fields are wrapped without modifying individual field components:
|
|
143
|
+
|
|
144
|
+
### FieldWrapper
|
|
145
|
+
|
|
146
|
+
Wraps each individual field. Receives the field's `name`, `type`, and `className`:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
registerFormComponent('FieldWrapper', ({ children, name, type, className }) => (
|
|
150
|
+
<div className={`form-group ${className || ''}`} data-field-type={type}>
|
|
151
|
+
{children}
|
|
152
|
+
</div>
|
|
153
|
+
));
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### FieldsWrapper
|
|
157
|
+
|
|
158
|
+
Wraps all fields together (excludes the submit button). Useful for grid layouts:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
registerFormComponent('FieldsWrapper', ({ children, className }) => (
|
|
162
|
+
<div className={`grid grid-cols-2 gap-4 ${className || ''}`}>
|
|
163
|
+
{children}
|
|
164
|
+
</div>
|
|
165
|
+
));
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Both default to `React.Fragment`, adding zero DOM overhead when not customized.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
124
172
|
## Field Types
|
|
125
173
|
|
|
126
174
|
Built-in HTML fallback types:
|
|
@@ -230,40 +278,96 @@ const schema = z.object({
|
|
|
230
278
|
|
|
231
279
|
---
|
|
232
280
|
|
|
233
|
-
##
|
|
281
|
+
## Array Fields
|
|
234
282
|
|
|
235
|
-
|
|
283
|
+
Create repeatable field groups with `useFieldArray` integration:
|
|
236
284
|
|
|
237
285
|
```typescript
|
|
238
286
|
const fields = [
|
|
287
|
+
{ type: 'text', name: 'name', label: 'Name' },
|
|
239
288
|
{
|
|
240
|
-
type: '
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
{ type: 'text', name: '
|
|
245
|
-
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
type: 'columns',
|
|
249
|
-
columns: [
|
|
250
|
-
{
|
|
251
|
-
width: '1',
|
|
252
|
-
children: [
|
|
253
|
-
{ type: 'text', name: 'city', label: 'City' },
|
|
254
|
-
],
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
width: '1',
|
|
258
|
-
children: [
|
|
259
|
-
{ type: 'text', name: 'zip', label: 'ZIP Code' },
|
|
260
|
-
],
|
|
261
|
-
},
|
|
289
|
+
type: 'array',
|
|
290
|
+
name: 'contacts',
|
|
291
|
+
label: 'Contacts',
|
|
292
|
+
fields: [
|
|
293
|
+
{ type: 'text', name: 'email', label: 'Email' },
|
|
294
|
+
{ type: 'text', name: 'phone', label: 'Phone' },
|
|
262
295
|
],
|
|
296
|
+
minItems: 1,
|
|
297
|
+
maxItems: 5,
|
|
263
298
|
},
|
|
264
|
-
];
|
|
299
|
+
] as const;
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Default Array Rendering
|
|
303
|
+
|
|
304
|
+
Array fields render automatically with add/remove functionality:
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
<FormGenerator
|
|
308
|
+
fields={fields}
|
|
309
|
+
onSubmit={(values) => {
|
|
310
|
+
console.log(values.contacts); // Array<{ email: string, phone: string }>
|
|
311
|
+
}}
|
|
312
|
+
/>
|
|
265
313
|
```
|
|
266
314
|
|
|
315
|
+
### Custom Array Rendering with useArrayField
|
|
316
|
+
|
|
317
|
+
For full control, use the `useArrayField` hook in a custom layout:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import { FormGenerator, useArrayField } from '@connect-soft/form-generator';
|
|
321
|
+
|
|
322
|
+
<FormGenerator fields={fields} onSubmit={handleSubmit}>
|
|
323
|
+
{({ fields, arrays, buttons }) => {
|
|
324
|
+
const contacts = useArrayField(arrays.contacts.field);
|
|
325
|
+
|
|
326
|
+
return (
|
|
327
|
+
<div>
|
|
328
|
+
{fields.name}
|
|
329
|
+
|
|
330
|
+
<h3>Contacts</h3>
|
|
331
|
+
{contacts.items.map(({ id, index, remove, fields: itemFields }) => (
|
|
332
|
+
<div key={id} className="contact-row">
|
|
333
|
+
{itemFields.email}
|
|
334
|
+
{itemFields.phone}
|
|
335
|
+
{contacts.canRemove && (
|
|
336
|
+
<button type="button" onClick={remove}>Remove</button>
|
|
337
|
+
)}
|
|
338
|
+
</div>
|
|
339
|
+
))}
|
|
340
|
+
|
|
341
|
+
{contacts.canAppend && (
|
|
342
|
+
<button type="button" onClick={contacts.append}>
|
|
343
|
+
Add Contact
|
|
344
|
+
</button>
|
|
345
|
+
)}
|
|
346
|
+
|
|
347
|
+
{buttons.submit}
|
|
348
|
+
</div>
|
|
349
|
+
);
|
|
350
|
+
}}
|
|
351
|
+
</FormGenerator>
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### useArrayField Return Values
|
|
355
|
+
|
|
356
|
+
| Property | Type | Description |
|
|
357
|
+
|----------|------|-------------|
|
|
358
|
+
| `items` | `Array<{ id, index }>` | Array items with unique ids |
|
|
359
|
+
| `append` | `() => void` | Add new empty item |
|
|
360
|
+
| `appendWith` | `(values) => void` | Add item with values |
|
|
361
|
+
| `prepend` | `() => void` | Add item at beginning |
|
|
362
|
+
| `remove` | `(index) => void` | Remove item at index |
|
|
363
|
+
| `move` | `(from, to) => void` | Move item |
|
|
364
|
+
| `swap` | `(a, b) => void` | Swap two items |
|
|
365
|
+
| `insert` | `(index, values?) => void` | Insert at index |
|
|
366
|
+
| `canAppend` | `boolean` | Can add more items (respects maxItems) |
|
|
367
|
+
| `canRemove` | `boolean` | Can remove items (respects minItems) |
|
|
368
|
+
| `renderField` | `(index, name) => ReactElement` | Render single field |
|
|
369
|
+
| `renderItem` | `(index) => Record<string, ReactElement>` | Render all fields for item |
|
|
370
|
+
|
|
267
371
|
---
|
|
268
372
|
|
|
269
373
|
## Custom Layouts
|
|
@@ -297,7 +401,7 @@ The render function receives:
|
|
|
297
401
|
| Property | Type | Description |
|
|
298
402
|
|----------|------|-------------|
|
|
299
403
|
| `fields` | `TemplateFields` | Pre-rendered fields (see below) |
|
|
300
|
-
| `
|
|
404
|
+
| `arrays` | `Record<string, TemplateArrayField>` | Array field definitions (use with `useArrayField`) |
|
|
301
405
|
| `buttons` | `{ submit, reset? }` | Pre-rendered buttons |
|
|
302
406
|
| `title` | `string` | Form title prop |
|
|
303
407
|
| `description` | `string` | Form description prop |
|
|
@@ -306,7 +410,8 @@ The render function receives:
|
|
|
306
410
|
| `isValid` | `boolean` | Form validity state |
|
|
307
411
|
| `isDirty` | `boolean` | Form dirty state |
|
|
308
412
|
| `renderField` | `function` | Manual field renderer |
|
|
309
|
-
| `
|
|
413
|
+
| `FieldWrapper` | `ComponentType` | Registered FieldWrapper component |
|
|
414
|
+
| `FieldsWrapper` | `ComponentType` | Registered FieldsWrapper component |
|
|
310
415
|
|
|
311
416
|
### Fields Object
|
|
312
417
|
|