@connect-soft/form-generator 1.1.0-alpha8 → 1.1.1
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 +490 -2
- package/dist/index.js +800 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +783 -56
- package/dist/index.mjs.map +1 -1
- package/dist/types/components/form/create-template-fields.d.ts +5 -1
- package/dist/types/components/form/create-template-fields.d.ts.map +1 -1
- package/dist/types/components/form/fields-context.d.ts +26 -0
- package/dist/types/components/form/fields-context.d.ts.map +1 -0
- package/dist/types/components/form/form-generator-typed.d.ts +47 -0
- package/dist/types/components/form/form-generator-typed.d.ts.map +1 -0
- package/dist/types/components/form/form-generator.d.ts +24 -21
- package/dist/types/components/form/form-generator.d.ts.map +1 -1
- package/dist/types/components/form/form-utils.d.ts +42 -2
- package/dist/types/components/form/form-utils.d.ts.map +1 -1
- package/dist/types/components/form/index.d.ts +3 -1
- package/dist/types/components/form/index.d.ts.map +1 -1
- package/dist/types/components/form/template-field-context.d.ts +14 -0
- package/dist/types/components/form/template-field-context.d.ts.map +1 -0
- package/dist/types/index.d.ts +9 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/field-types.d.ts +8 -3
- package/dist/types/lib/field-types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- **Type-Safe**: Full TypeScript inference for form values and field types
|
|
14
14
|
- **Field Type Checking**: Compile-time validation of `field.type` with autocomplete
|
|
15
15
|
- **Extensible Types**: Add custom field types via module augmentation
|
|
16
|
-
- **Imperative API**: Control form via ref (`setValues`, `reset`, `submit`, etc.)
|
|
16
|
+
- **Imperative API**: Control form via ref (`setValues`, `reset`, `setDefaultValues`, `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
|
|
@@ -524,6 +524,69 @@ Highlight specific fields while rendering the rest normally:
|
|
|
524
524
|
</FormGenerator>
|
|
525
525
|
```
|
|
526
526
|
|
|
527
|
+
### Hook-Based Field Access with useTemplateField
|
|
528
|
+
|
|
529
|
+
When you need to access fields from **child components** (not inline in the render function), use the `useTemplateField` hook. It returns the pre-rendered field element and marks it as accessed, so it won't appear in remaining fields.
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
import { FormGenerator, useTemplateField, RemainingFields } from '@connect-soft/form-generator';
|
|
533
|
+
|
|
534
|
+
// Child component that claims a field
|
|
535
|
+
function EmailSection() {
|
|
536
|
+
const email = useTemplateField('email');
|
|
537
|
+
return <div className="highlighted">{email}</div>;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function PasswordSection() {
|
|
541
|
+
const password = useTemplateField('password');
|
|
542
|
+
return <div className="special">{password}</div>;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
<FormGenerator fields={fieldDefinitions} onSubmit={handleSubmit}>
|
|
546
|
+
{({ buttons }) => (
|
|
547
|
+
<div>
|
|
548
|
+
<EmailSection />
|
|
549
|
+
<PasswordSection />
|
|
550
|
+
<div className="other-fields">
|
|
551
|
+
<RemainingFields />
|
|
552
|
+
</div>
|
|
553
|
+
{buttons.submit}
|
|
554
|
+
</div>
|
|
555
|
+
)}
|
|
556
|
+
</FormGenerator>
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
**Why not just use `fields.remaining` with hooks?** The render function runs before child components render, so `fields.remaining` is evaluated before any hooks in child components mark fields as accessed. `<RemainingFields />` is a component that evaluates at its own render time — after sibling components above it have already claimed their fields.
|
|
560
|
+
|
|
561
|
+
You can freely mix proxy access in the render function with hook access in child components:
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
<FormGenerator fields={fieldDefinitions} onSubmit={handleSubmit}>
|
|
565
|
+
{({ fields, buttons }) => (
|
|
566
|
+
<div>
|
|
567
|
+
{/* Proxy access in the render function */}
|
|
568
|
+
<div className="hero">{fields.email}</div>
|
|
569
|
+
{/* Hook access in a child component */}
|
|
570
|
+
<PasswordSection />
|
|
571
|
+
{/* RemainingFields excludes both email and password */}
|
|
572
|
+
<RemainingFields />
|
|
573
|
+
{buttons.submit}
|
|
574
|
+
</div>
|
|
575
|
+
)}
|
|
576
|
+
</FormGenerator>
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
#### useTemplateField
|
|
580
|
+
|
|
581
|
+
| Parameter | Type | Description |
|
|
582
|
+
|-----------|------|-------------|
|
|
583
|
+
| `name` | `string` | Field name to retrieve |
|
|
584
|
+
| **Returns** | `ReactElement \| undefined` | The pre-rendered field element, or `undefined` if not found |
|
|
585
|
+
|
|
586
|
+
#### RemainingFields
|
|
587
|
+
|
|
588
|
+
A component that renders all fields not yet accessed via `useTemplateField` or the fields proxy. Must be used within a `FormGenerator` custom layout.
|
|
589
|
+
|
|
527
590
|
### Form State Access
|
|
528
591
|
|
|
529
592
|
Use form state for conditional rendering:
|
|
@@ -544,6 +607,355 @@ Use form state for conditional rendering:
|
|
|
544
607
|
|
|
545
608
|
---
|
|
546
609
|
|
|
610
|
+
## Raw Field Props with useFieldProps
|
|
611
|
+
|
|
612
|
+
For complete control over field rendering, use the `useFieldProps` hook to get raw form binding props. This is useful when you want to create custom field components without registering them globally.
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
import { FormGenerator, useFieldProps } from '@connect-soft/form-generator';
|
|
616
|
+
|
|
617
|
+
const fields = [
|
|
618
|
+
{ type: 'text', name: 'email', label: 'Email', required: true },
|
|
619
|
+
{ type: 'password', name: 'password', label: 'Password' },
|
|
620
|
+
] as const;
|
|
621
|
+
|
|
622
|
+
// Custom component using the hook
|
|
623
|
+
function CustomEmailField() {
|
|
624
|
+
const { value, onChange, onBlur, ref, field, fieldState } = useFieldProps<string>('email');
|
|
625
|
+
|
|
626
|
+
return (
|
|
627
|
+
<div className="my-custom-field">
|
|
628
|
+
<label>{field.label}{field.required && ' *'}</label>
|
|
629
|
+
<input
|
|
630
|
+
ref={ref}
|
|
631
|
+
type="email"
|
|
632
|
+
value={value ?? ''}
|
|
633
|
+
onChange={(e) => onChange(e.target.value)}
|
|
634
|
+
onBlur={onBlur}
|
|
635
|
+
placeholder={field.placeholder}
|
|
636
|
+
/>
|
|
637
|
+
{fieldState.error && (
|
|
638
|
+
<span className="error">{fieldState.error.message}</span>
|
|
639
|
+
)}
|
|
640
|
+
</div>
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Use in FormGenerator with custom layout
|
|
645
|
+
<FormGenerator fields={fields} onSubmit={handleSubmit}>
|
|
646
|
+
{({ fields, buttons }) => (
|
|
647
|
+
<div>
|
|
648
|
+
<CustomEmailField />
|
|
649
|
+
{fields.password} {/* Mix with pre-rendered fields */}
|
|
650
|
+
{buttons.submit}
|
|
651
|
+
</div>
|
|
652
|
+
)}
|
|
653
|
+
</FormGenerator>
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### useFieldProps Return Value
|
|
657
|
+
|
|
658
|
+
| Property | Type | Description |
|
|
659
|
+
|----------|------|-------------|
|
|
660
|
+
| `name` | `string` | Field name (with any prefix applied) |
|
|
661
|
+
| `value` | `TValue` | Current field value |
|
|
662
|
+
| `onChange` | `(value: TValue) => void` | Change handler |
|
|
663
|
+
| `onBlur` | `() => void` | Blur handler |
|
|
664
|
+
| `ref` | `Ref<any>` | Ref to attach to input element |
|
|
665
|
+
| `field` | `BaseField` | Full field definition (type, label, required, etc.) |
|
|
666
|
+
| `fieldState` | `FieldState` | Validation state (see below) |
|
|
667
|
+
|
|
668
|
+
### FieldState Properties
|
|
669
|
+
|
|
670
|
+
| Property | Type | Description |
|
|
671
|
+
|----------|------|-------------|
|
|
672
|
+
| `invalid` | `boolean` | Whether the field has validation errors |
|
|
673
|
+
| `error` | `{ type: string; message?: string }` | Error details if invalid |
|
|
674
|
+
| `isDirty` | `boolean` | Whether the value has changed from default |
|
|
675
|
+
| `isTouched` | `boolean` | Whether the field has been focused and blurred |
|
|
676
|
+
|
|
677
|
+
### When to Use useFieldProps vs Registered Components
|
|
678
|
+
|
|
679
|
+
| Use Case | Approach |
|
|
680
|
+
|----------|----------|
|
|
681
|
+
| Reusable field component across forms | Register with `registerField` |
|
|
682
|
+
| One-off custom field in a specific form | Use `useFieldProps` |
|
|
683
|
+
| Need full control over a single field | Use `useFieldProps` |
|
|
684
|
+
| Consistent field styling across app | Register with `registerField` |
|
|
685
|
+
|
|
686
|
+
### Example: Complete Custom Form
|
|
687
|
+
|
|
688
|
+
```typescript
|
|
689
|
+
import { FormGenerator, useFieldProps } from '@connect-soft/form-generator';
|
|
690
|
+
|
|
691
|
+
const fields = [
|
|
692
|
+
{ type: 'text', name: 'firstName', label: 'First Name', required: true },
|
|
693
|
+
{ type: 'text', name: 'lastName', label: 'Last Name', required: true },
|
|
694
|
+
{ type: 'email', name: 'email', label: 'Email', required: true },
|
|
695
|
+
] as const;
|
|
696
|
+
|
|
697
|
+
function NameFields() {
|
|
698
|
+
const firstName = useFieldProps<string>('firstName');
|
|
699
|
+
const lastName = useFieldProps<string>('lastName');
|
|
700
|
+
|
|
701
|
+
return (
|
|
702
|
+
<div className="name-row">
|
|
703
|
+
<div className="field">
|
|
704
|
+
<label>{firstName.field.label}</label>
|
|
705
|
+
<input
|
|
706
|
+
ref={firstName.ref}
|
|
707
|
+
value={firstName.value ?? ''}
|
|
708
|
+
onChange={(e) => firstName.onChange(e.target.value)}
|
|
709
|
+
onBlur={firstName.onBlur}
|
|
710
|
+
/>
|
|
711
|
+
{firstName.fieldState.error && (
|
|
712
|
+
<span className="error">{firstName.fieldState.error.message}</span>
|
|
713
|
+
)}
|
|
714
|
+
</div>
|
|
715
|
+
<div className="field">
|
|
716
|
+
<label>{lastName.field.label}</label>
|
|
717
|
+
<input
|
|
718
|
+
ref={lastName.ref}
|
|
719
|
+
value={lastName.value ?? ''}
|
|
720
|
+
onChange={(e) => lastName.onChange(e.target.value)}
|
|
721
|
+
onBlur={lastName.onBlur}
|
|
722
|
+
/>
|
|
723
|
+
{lastName.fieldState.error && (
|
|
724
|
+
<span className="error">{lastName.fieldState.error.message}</span>
|
|
725
|
+
)}
|
|
726
|
+
</div>
|
|
727
|
+
</div>
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function MyForm() {
|
|
732
|
+
return (
|
|
733
|
+
<FormGenerator fields={fields} onSubmit={handleSubmit}>
|
|
734
|
+
{({ fields, buttons, isSubmitting }) => (
|
|
735
|
+
<div className="custom-form">
|
|
736
|
+
<NameFields />
|
|
737
|
+
{fields.email}
|
|
738
|
+
<button type="submit" disabled={isSubmitting}>
|
|
739
|
+
{isSubmitting ? 'Submitting...' : 'Submit'}
|
|
740
|
+
</button>
|
|
741
|
+
</div>
|
|
742
|
+
)}
|
|
743
|
+
</FormGenerator>
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
## Type-Safe Forms with StrictFormGenerator
|
|
751
|
+
|
|
752
|
+
For maximum type safety, use `StrictFormGenerator` which requires a Zod schema. This ensures field names match your schema and provides fully typed form values.
|
|
753
|
+
|
|
754
|
+
```typescript
|
|
755
|
+
import { StrictFormGenerator } from '@connect-soft/form-generator';
|
|
756
|
+
import { z } from 'zod';
|
|
757
|
+
|
|
758
|
+
const userSchema = z.object({
|
|
759
|
+
email: z.string().email('Invalid email'),
|
|
760
|
+
password: z.string().min(8, 'Password must be at least 8 characters'),
|
|
761
|
+
age: z.number().min(18, 'Must be 18 or older'),
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
<StrictFormGenerator
|
|
765
|
+
schema={userSchema}
|
|
766
|
+
fields={[
|
|
767
|
+
{ type: 'email', name: 'email', label: 'Email' }, // name must be keyof schema
|
|
768
|
+
{ type: 'password', name: 'password', label: 'Password' },
|
|
769
|
+
{ type: 'number', name: 'age', label: 'Age' },
|
|
770
|
+
// { type: 'text', name: 'invalid' } // TypeScript error: 'invalid' not in schema
|
|
771
|
+
]}
|
|
772
|
+
onSubmit={(values) => {
|
|
773
|
+
// values: { email: string; password: string; age: number }
|
|
774
|
+
console.log(values.email); // Fully typed!
|
|
775
|
+
}}
|
|
776
|
+
/>
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
### Typed Field Helpers
|
|
780
|
+
|
|
781
|
+
Use helper functions for even stricter type checking:
|
|
782
|
+
|
|
783
|
+
```typescript
|
|
784
|
+
import { StrictFormGenerator, createFieldFactory } from '@connect-soft/form-generator';
|
|
785
|
+
import { z } from 'zod';
|
|
786
|
+
|
|
787
|
+
const loginSchema = z.object({
|
|
788
|
+
email: z.string().email(),
|
|
789
|
+
password: z.string(),
|
|
790
|
+
rememberMe: z.boolean(),
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
// Create a typed field factory from schema
|
|
794
|
+
const defineField = createFieldFactory(loginSchema);
|
|
795
|
+
|
|
796
|
+
// Each field is validated against the schema
|
|
797
|
+
const fields = [
|
|
798
|
+
defineField({ type: 'email', name: 'email', label: 'Email' }),
|
|
799
|
+
defineField({ type: 'password', name: 'password', label: 'Password' }),
|
|
800
|
+
defineField({ type: 'checkbox', name: 'rememberMe', label: 'Remember Me' }),
|
|
801
|
+
];
|
|
802
|
+
|
|
803
|
+
<StrictFormGenerator
|
|
804
|
+
schema={loginSchema}
|
|
805
|
+
fields={fields}
|
|
806
|
+
onSubmit={handleSubmit}
|
|
807
|
+
/>
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### StrictFormGenerator vs FormGenerator
|
|
811
|
+
|
|
812
|
+
| Feature | FormGenerator | StrictFormGenerator |
|
|
813
|
+
|---------|---------------|---------------------|
|
|
814
|
+
| Schema | No (infers from fields) | Required (Zod) |
|
|
815
|
+
| Field name checking | Inferred from fields | Enforced at compile-time |
|
|
816
|
+
| Type inference | From field definitions | From Zod schema |
|
|
817
|
+
| Constraint detection | No | Yes (automatic) |
|
|
818
|
+
| Use case | Quick prototyping | Production apps |
|
|
819
|
+
|
|
820
|
+
### Automatic Schema Constraint Detection
|
|
821
|
+
|
|
822
|
+
`StrictFormGenerator` automatically extracts constraints from your Zod schema and propagates them to field components. This means you don't need to duplicate constraints in both your schema and field definitions.
|
|
823
|
+
|
|
824
|
+
#### Supported Constraints
|
|
825
|
+
|
|
826
|
+
**Number fields** (`z.number()`):
|
|
827
|
+
| Zod Method | Field Property | Example |
|
|
828
|
+
|------------|----------------|---------|
|
|
829
|
+
| `.min(n)` | `min` | `z.number().min(0)` → `{ min: 0 }` |
|
|
830
|
+
| `.max(n)` | `max` | `z.number().max(100)` → `{ max: 100 }` |
|
|
831
|
+
| `.int()` | `step: 1` | `z.number().int()` → `{ step: 1 }` |
|
|
832
|
+
| `.multipleOf(n)` | `step` | `z.number().multipleOf(0.01)` → `{ step: 0.01 }` |
|
|
833
|
+
| `.positive()` | `min` | `z.number().positive()` → `{ min: 0 }` (exclusive) |
|
|
834
|
+
| `.nonnegative()` | `min: 0` | `z.number().nonnegative()` → `{ min: 0 }` |
|
|
835
|
+
|
|
836
|
+
**String fields** (`z.string()`):
|
|
837
|
+
| Zod Method | Field Property | Example |
|
|
838
|
+
|------------|----------------|---------|
|
|
839
|
+
| `.min(n)` | `minLength` | `z.string().min(3)` → `{ minLength: 3 }` |
|
|
840
|
+
| `.max(n)` | `maxLength` | `z.string().max(100)` → `{ maxLength: 100 }` |
|
|
841
|
+
| `.length(n)` | `minLength` + `maxLength` | `z.string().length(6)` → `{ minLength: 6, maxLength: 6 }` |
|
|
842
|
+
| `.regex(pattern)` | `pattern` | `z.string().regex(/^[A-Z]+$/)` → `{ pattern: '^[A-Z]+$' }` |
|
|
843
|
+
|
|
844
|
+
**Date fields** (`z.date()`):
|
|
845
|
+
| Zod Method | Field Property | Example |
|
|
846
|
+
|------------|----------------|---------|
|
|
847
|
+
| `.min(date)` | `min` (ISO string) | `z.date().min(new Date('2020-01-01'))` → `{ min: '2020-01-01' }` |
|
|
848
|
+
| `.max(date)` | `max` (ISO string) | `z.date().max(new Date('2030-12-31'))` → `{ max: '2030-12-31' }` |
|
|
849
|
+
|
|
850
|
+
#### Example
|
|
851
|
+
|
|
852
|
+
```typescript
|
|
853
|
+
import { StrictFormGenerator } from '@connect-soft/form-generator';
|
|
854
|
+
import { z } from 'zod';
|
|
855
|
+
|
|
856
|
+
const userSchema = z.object({
|
|
857
|
+
username: z.string().min(3).max(20).regex(/^[a-z0-9_]+$/),
|
|
858
|
+
age: z.number().int().min(18).max(120),
|
|
859
|
+
price: z.number().multipleOf(0.01).min(0),
|
|
860
|
+
birthDate: z.date().min(new Date('1900-01-01')).max(new Date()),
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
// No need to specify min/max/minLength/maxLength in fields!
|
|
864
|
+
// They are automatically extracted from the schema
|
|
865
|
+
<StrictFormGenerator
|
|
866
|
+
schema={userSchema}
|
|
867
|
+
fields={[
|
|
868
|
+
{ type: 'text', name: 'username', label: 'Username' },
|
|
869
|
+
{ type: 'number', name: 'age', label: 'Age' },
|
|
870
|
+
{ type: 'number', name: 'price', label: 'Price' },
|
|
871
|
+
{ type: 'date', name: 'birthDate', label: 'Birth Date' },
|
|
872
|
+
]}
|
|
873
|
+
onSubmit={handleSubmit}
|
|
874
|
+
/>
|
|
875
|
+
|
|
876
|
+
// Field components receive:
|
|
877
|
+
// username: { minLength: 3, maxLength: 20, pattern: '^[a-z0-9_]+$', required: true }
|
|
878
|
+
// age: { min: 18, max: 120, step: 1, required: true }
|
|
879
|
+
// price: { min: 0, step: 0.01, required: true }
|
|
880
|
+
// birthDate: { min: '1900-01-01', max: '2026-02-02', required: true }
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
#### Using Constraints in Field Components
|
|
884
|
+
|
|
885
|
+
Your registered field components can use these constraints directly:
|
|
886
|
+
|
|
887
|
+
```typescript
|
|
888
|
+
registerField('number', ({ field, formField, fieldState }) => (
|
|
889
|
+
<div>
|
|
890
|
+
<label>{field.label}</label>
|
|
891
|
+
<input
|
|
892
|
+
type="number"
|
|
893
|
+
{...formField}
|
|
894
|
+
min={field.min}
|
|
895
|
+
max={field.max}
|
|
896
|
+
step={field.step}
|
|
897
|
+
/>
|
|
898
|
+
{fieldState.error && <span>{fieldState.error.message}</span>}
|
|
899
|
+
</div>
|
|
900
|
+
));
|
|
901
|
+
|
|
902
|
+
registerField('text', ({ field, formField, fieldState }) => (
|
|
903
|
+
<div>
|
|
904
|
+
<label>{field.label}</label>
|
|
905
|
+
<input
|
|
906
|
+
type="text"
|
|
907
|
+
{...formField}
|
|
908
|
+
minLength={field.minLength}
|
|
909
|
+
maxLength={field.maxLength}
|
|
910
|
+
pattern={field.pattern}
|
|
911
|
+
/>
|
|
912
|
+
{fieldState.error && <span>{fieldState.error.message}</span>}
|
|
913
|
+
</div>
|
|
914
|
+
));
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
#### Manual Constraint Merging
|
|
918
|
+
|
|
919
|
+
You can also manually merge constraints using the utility functions:
|
|
920
|
+
|
|
921
|
+
```typescript
|
|
922
|
+
import { mergeSchemaConstraints, analyzeSchema } from '@connect-soft/form-generator';
|
|
923
|
+
|
|
924
|
+
const schema = z.object({
|
|
925
|
+
age: z.number().min(0).max(120),
|
|
926
|
+
name: z.string().min(1).max(100),
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// Analyze schema to get field info
|
|
930
|
+
const fieldInfo = analyzeSchema(schema);
|
|
931
|
+
// => [
|
|
932
|
+
// { name: 'age', type: 'number', required: true, min: 0, max: 120 },
|
|
933
|
+
// { name: 'name', type: 'string', required: true, minLength: 1, maxLength: 100 }
|
|
934
|
+
// ]
|
|
935
|
+
|
|
936
|
+
// Or merge constraints into existing fields
|
|
937
|
+
const fields = [
|
|
938
|
+
{ type: 'number', name: 'age', label: 'Age' },
|
|
939
|
+
{ type: 'text', name: 'name', label: 'Name' },
|
|
940
|
+
];
|
|
941
|
+
|
|
942
|
+
const fieldsWithConstraints = mergeSchemaConstraints(schema, fields);
|
|
943
|
+
// => [
|
|
944
|
+
// { type: 'number', name: 'age', label: 'Age', required: true, min: 0, max: 120 },
|
|
945
|
+
// { type: 'text', name: 'name', label: 'Name', required: true, minLength: 1, maxLength: 100 }
|
|
946
|
+
// ]
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
### Available Helpers
|
|
950
|
+
|
|
951
|
+
| Helper | Description |
|
|
952
|
+
|--------|-------------|
|
|
953
|
+
| `createFieldFactory(schema)` | Create a field factory for a schema |
|
|
954
|
+
| `typedField<typeof schema>()` | Create a single typed field |
|
|
955
|
+
| `typedFields<typeof schema>([...])` | Create an array of typed fields |
|
|
956
|
+
|
|
957
|
+
---
|
|
958
|
+
|
|
547
959
|
## TypeScript Type Inference
|
|
548
960
|
|
|
549
961
|
Get full type inference from field definitions:
|
|
@@ -611,6 +1023,23 @@ function MyForm() {
|
|
|
611
1023
|
});
|
|
612
1024
|
};
|
|
613
1025
|
|
|
1026
|
+
// Set values without marking the form as dirty
|
|
1027
|
+
const handleLoadData = () => {
|
|
1028
|
+
formRef.current?.setValues(
|
|
1029
|
+
{ email: 'loaded@api.com', age: 30 },
|
|
1030
|
+
{ shouldDirty: false }
|
|
1031
|
+
);
|
|
1032
|
+
};
|
|
1033
|
+
|
|
1034
|
+
// Set values and make them the new baseline for isDirty
|
|
1035
|
+
const handleSetDefaults = () => {
|
|
1036
|
+
formRef.current?.setDefaultValues({
|
|
1037
|
+
email: 'new-default@example.com',
|
|
1038
|
+
age: 25,
|
|
1039
|
+
});
|
|
1040
|
+
// isDirty is now false — these are the new default values
|
|
1041
|
+
};
|
|
1042
|
+
|
|
614
1043
|
return (
|
|
615
1044
|
<>
|
|
616
1045
|
<FormGenerator
|
|
@@ -621,6 +1050,8 @@ function MyForm() {
|
|
|
621
1050
|
<button type="button" onClick={handleExternalSubmit}>Submit Externally</button>
|
|
622
1051
|
<button type="button" onClick={handleReset}>Reset Form</button>
|
|
623
1052
|
<button type="button" onClick={handleSetValues}>Set Values</button>
|
|
1053
|
+
<button type="button" onClick={handleLoadData}>Load Data</button>
|
|
1054
|
+
<button type="button" onClick={handleSetDefaults}>Set Defaults</button>
|
|
624
1055
|
</>
|
|
625
1056
|
);
|
|
626
1057
|
}
|
|
@@ -630,9 +1061,10 @@ function MyForm() {
|
|
|
630
1061
|
|
|
631
1062
|
| Method | Description |
|
|
632
1063
|
|--------|-------------|
|
|
633
|
-
| `setValues(values)` | Set form values (partial update) |
|
|
1064
|
+
| `setValues(values, options?)` | Set form values (partial update). Options: `{ shouldDirty?, shouldValidate? }` |
|
|
634
1065
|
| `getValues()` | Get current form values |
|
|
635
1066
|
| `reset(values?)` | Reset to default or provided values |
|
|
1067
|
+
| `setDefaultValues(values)` | Set values and make them the new baseline for `isDirty` |
|
|
636
1068
|
| `submit()` | Programmatically submit the form |
|
|
637
1069
|
| `clearErrors()` | Clear all validation errors |
|
|
638
1070
|
| `setError(name, error)` | Set error for a specific field |
|
|
@@ -640,6 +1072,24 @@ function MyForm() {
|
|
|
640
1072
|
| `isDirty()` | Check if form has unsaved changes |
|
|
641
1073
|
| `form` | Access underlying react-hook-form instance |
|
|
642
1074
|
|
|
1075
|
+
### SetValuesOptions
|
|
1076
|
+
|
|
1077
|
+
| Option | Type | Default | Description |
|
|
1078
|
+
|--------|------|---------|-------------|
|
|
1079
|
+
| `shouldDirty` | `boolean` | `true` | Whether setting the value marks the field as dirty |
|
|
1080
|
+
| `shouldValidate` | `boolean` | `true` | Whether to trigger validation after setting values |
|
|
1081
|
+
|
|
1082
|
+
### setDefaultValues vs reset vs setValues
|
|
1083
|
+
|
|
1084
|
+
| Method | Sets values | Resets dirty state | New dirty baseline |
|
|
1085
|
+
|--------|-------------|--------------------|--------------------|
|
|
1086
|
+
| `setValues(v)` | Yes | No (marks dirty) | No |
|
|
1087
|
+
| `setValues(v, { shouldDirty: false })` | Yes | No (keeps current) | No |
|
|
1088
|
+
| `reset(v)` | Yes | Yes | Yes (merges with original defaults) |
|
|
1089
|
+
| `setDefaultValues(v)` | Yes | Yes | Yes (merges with original defaults) |
|
|
1090
|
+
|
|
1091
|
+
Use `setDefaultValues` when loading data from an API and you want the loaded values to become the "clean" baseline (e.g., editing an existing record). Use `setValues` with `{ shouldDirty: false }` when you want to set a value without affecting dirty tracking at all.
|
|
1092
|
+
|
|
643
1093
|
### Watching Form Values
|
|
644
1094
|
|
|
645
1095
|
Detect when field values change from the parent component:
|
|
@@ -735,6 +1185,44 @@ function ValueWatcher() {
|
|
|
735
1185
|
| `validation` | `ZodType` | Zod validation schema |
|
|
736
1186
|
| `className` | `string` | CSS class for field wrapper |
|
|
737
1187
|
|
|
1188
|
+
### Field Type-Specific Properties
|
|
1189
|
+
|
|
1190
|
+
**Text fields** (`text`, `email`, `password`, `tel`, `url`, `search`):
|
|
1191
|
+
| Property | Type | Description |
|
|
1192
|
+
|----------|------|-------------|
|
|
1193
|
+
| `placeholder` | `string` | Placeholder text |
|
|
1194
|
+
| `minLength` | `number` | Minimum character length |
|
|
1195
|
+
| `maxLength` | `number` | Maximum character length |
|
|
1196
|
+
| `pattern` | `string` | HTML5 validation pattern |
|
|
1197
|
+
|
|
1198
|
+
**Number fields** (`number`, `range`):
|
|
1199
|
+
| Property | Type | Description |
|
|
1200
|
+
|----------|------|-------------|
|
|
1201
|
+
| `placeholder` | `string` | Placeholder text |
|
|
1202
|
+
| `min` | `number` | Minimum value |
|
|
1203
|
+
| `max` | `number` | Maximum value |
|
|
1204
|
+
| `step` | `number` | Step increment |
|
|
1205
|
+
|
|
1206
|
+
**Date fields** (`date`, `datetime`, `datetime-local`):
|
|
1207
|
+
| Property | Type | Description |
|
|
1208
|
+
|----------|------|-------------|
|
|
1209
|
+
| `min` | `string` | Minimum date (ISO format: YYYY-MM-DD) |
|
|
1210
|
+
| `max` | `string` | Maximum date (ISO format: YYYY-MM-DD) |
|
|
1211
|
+
|
|
1212
|
+
### Schema Analysis Utilities
|
|
1213
|
+
|
|
1214
|
+
| Function | Description |
|
|
1215
|
+
|----------|-------------|
|
|
1216
|
+
| `analyzeSchema(schema)` | Extract detailed field info from Zod schema |
|
|
1217
|
+
| `mergeSchemaConstraints(schema, fields)` | Merge schema constraints into field definitions |
|
|
1218
|
+
| `mergeSchemaRequirements(schema, fields)` | Merge only required status (legacy) |
|
|
1219
|
+
| `getNumberConstraints(schema)` | Extract min/max/step from number schema |
|
|
1220
|
+
| `getStringConstraints(schema)` | Extract minLength/maxLength/pattern from string schema |
|
|
1221
|
+
| `getDateConstraints(schema)` | Extract min/max dates from date schema |
|
|
1222
|
+
| `isSchemaRequired(schema)` | Check if schema field is required |
|
|
1223
|
+
| `unwrapSchema(schema)` | Unwrap optional/nullable wrappers |
|
|
1224
|
+
| `getSchemaTypeName(schema)` | Get base type name (string, number, etc.) |
|
|
1225
|
+
|
|
738
1226
|
---
|
|
739
1227
|
|
|
740
1228
|
## Links
|