@connect-soft/form-generator 1.1.0-alpha8 → 1.1.0-alpha9

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 CHANGED
@@ -544,6 +544,225 @@ Use form state for conditional rendering:
544
544
 
545
545
  ---
546
546
 
547
+ ## Raw Field Props with useFieldProps
548
+
549
+ 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.
550
+
551
+ ```typescript
552
+ import { FormGenerator, useFieldProps } from '@connect-soft/form-generator';
553
+
554
+ const fields = [
555
+ { type: 'text', name: 'email', label: 'Email', required: true },
556
+ { type: 'password', name: 'password', label: 'Password' },
557
+ ] as const;
558
+
559
+ // Custom component using the hook
560
+ function CustomEmailField() {
561
+ const { value, onChange, onBlur, ref, field, fieldState } = useFieldProps<string>('email');
562
+
563
+ return (
564
+ <div className="my-custom-field">
565
+ <label>{field.label}{field.required && ' *'}</label>
566
+ <input
567
+ ref={ref}
568
+ type="email"
569
+ value={value ?? ''}
570
+ onChange={(e) => onChange(e.target.value)}
571
+ onBlur={onBlur}
572
+ placeholder={field.placeholder}
573
+ />
574
+ {fieldState.error && (
575
+ <span className="error">{fieldState.error.message}</span>
576
+ )}
577
+ </div>
578
+ );
579
+ }
580
+
581
+ // Use in FormGenerator with custom layout
582
+ <FormGenerator fields={fields} onSubmit={handleSubmit}>
583
+ {({ fields, buttons }) => (
584
+ <div>
585
+ <CustomEmailField />
586
+ {fields.password} {/* Mix with pre-rendered fields */}
587
+ {buttons.submit}
588
+ </div>
589
+ )}
590
+ </FormGenerator>
591
+ ```
592
+
593
+ ### useFieldProps Return Value
594
+
595
+ | Property | Type | Description |
596
+ |----------|------|-------------|
597
+ | `name` | `string` | Field name (with any prefix applied) |
598
+ | `value` | `TValue` | Current field value |
599
+ | `onChange` | `(value: TValue) => void` | Change handler |
600
+ | `onBlur` | `() => void` | Blur handler |
601
+ | `ref` | `Ref<any>` | Ref to attach to input element |
602
+ | `field` | `BaseField` | Full field definition (type, label, required, etc.) |
603
+ | `fieldState` | `FieldState` | Validation state (see below) |
604
+
605
+ ### FieldState Properties
606
+
607
+ | Property | Type | Description |
608
+ |----------|------|-------------|
609
+ | `invalid` | `boolean` | Whether the field has validation errors |
610
+ | `error` | `{ type: string; message?: string }` | Error details if invalid |
611
+ | `isDirty` | `boolean` | Whether the value has changed from default |
612
+ | `isTouched` | `boolean` | Whether the field has been focused and blurred |
613
+
614
+ ### When to Use useFieldProps vs Registered Components
615
+
616
+ | Use Case | Approach |
617
+ |----------|----------|
618
+ | Reusable field component across forms | Register with `registerField` |
619
+ | One-off custom field in a specific form | Use `useFieldProps` |
620
+ | Need full control over a single field | Use `useFieldProps` |
621
+ | Consistent field styling across app | Register with `registerField` |
622
+
623
+ ### Example: Complete Custom Form
624
+
625
+ ```typescript
626
+ import { FormGenerator, useFieldProps } from '@connect-soft/form-generator';
627
+
628
+ const fields = [
629
+ { type: 'text', name: 'firstName', label: 'First Name', required: true },
630
+ { type: 'text', name: 'lastName', label: 'Last Name', required: true },
631
+ { type: 'email', name: 'email', label: 'Email', required: true },
632
+ ] as const;
633
+
634
+ function NameFields() {
635
+ const firstName = useFieldProps<string>('firstName');
636
+ const lastName = useFieldProps<string>('lastName');
637
+
638
+ return (
639
+ <div className="name-row">
640
+ <div className="field">
641
+ <label>{firstName.field.label}</label>
642
+ <input
643
+ ref={firstName.ref}
644
+ value={firstName.value ?? ''}
645
+ onChange={(e) => firstName.onChange(e.target.value)}
646
+ onBlur={firstName.onBlur}
647
+ />
648
+ {firstName.fieldState.error && (
649
+ <span className="error">{firstName.fieldState.error.message}</span>
650
+ )}
651
+ </div>
652
+ <div className="field">
653
+ <label>{lastName.field.label}</label>
654
+ <input
655
+ ref={lastName.ref}
656
+ value={lastName.value ?? ''}
657
+ onChange={(e) => lastName.onChange(e.target.value)}
658
+ onBlur={lastName.onBlur}
659
+ />
660
+ {lastName.fieldState.error && (
661
+ <span className="error">{lastName.fieldState.error.message}</span>
662
+ )}
663
+ </div>
664
+ </div>
665
+ );
666
+ }
667
+
668
+ function MyForm() {
669
+ return (
670
+ <FormGenerator fields={fields} onSubmit={handleSubmit}>
671
+ {({ fields, buttons, isSubmitting }) => (
672
+ <div className="custom-form">
673
+ <NameFields />
674
+ {fields.email}
675
+ <button type="submit" disabled={isSubmitting}>
676
+ {isSubmitting ? 'Submitting...' : 'Submit'}
677
+ </button>
678
+ </div>
679
+ )}
680
+ </FormGenerator>
681
+ );
682
+ }
683
+ ```
684
+
685
+ ---
686
+
687
+ ## Type-Safe Forms with StrictFormGenerator
688
+
689
+ For maximum type safety, use `StrictFormGenerator` which requires a Zod schema. This ensures field names match your schema and provides fully typed form values.
690
+
691
+ ```typescript
692
+ import { StrictFormGenerator } from '@connect-soft/form-generator';
693
+ import { z } from 'zod';
694
+
695
+ const userSchema = z.object({
696
+ email: z.string().email('Invalid email'),
697
+ password: z.string().min(8, 'Password must be at least 8 characters'),
698
+ age: z.number().min(18, 'Must be 18 or older'),
699
+ });
700
+
701
+ <StrictFormGenerator
702
+ schema={userSchema}
703
+ fields={[
704
+ { type: 'email', name: 'email', label: 'Email' }, // name must be keyof schema
705
+ { type: 'password', name: 'password', label: 'Password' },
706
+ { type: 'number', name: 'age', label: 'Age' },
707
+ // { type: 'text', name: 'invalid' } // TypeScript error: 'invalid' not in schema
708
+ ]}
709
+ onSubmit={(values) => {
710
+ // values: { email: string; password: string; age: number }
711
+ console.log(values.email); // Fully typed!
712
+ }}
713
+ />
714
+ ```
715
+
716
+ ### Typed Field Helpers
717
+
718
+ Use helper functions for even stricter type checking:
719
+
720
+ ```typescript
721
+ import { StrictFormGenerator, createFieldFactory } from '@connect-soft/form-generator';
722
+ import { z } from 'zod';
723
+
724
+ const loginSchema = z.object({
725
+ email: z.string().email(),
726
+ password: z.string(),
727
+ rememberMe: z.boolean(),
728
+ });
729
+
730
+ // Create a typed field factory from schema
731
+ const defineField = createFieldFactory(loginSchema);
732
+
733
+ // Each field is validated against the schema
734
+ const fields = [
735
+ defineField({ type: 'email', name: 'email', label: 'Email' }),
736
+ defineField({ type: 'password', name: 'password', label: 'Password' }),
737
+ defineField({ type: 'checkbox', name: 'rememberMe', label: 'Remember Me' }),
738
+ ];
739
+
740
+ <StrictFormGenerator
741
+ schema={loginSchema}
742
+ fields={fields}
743
+ onSubmit={handleSubmit}
744
+ />
745
+ ```
746
+
747
+ ### StrictFormGenerator vs FormGenerator
748
+
749
+ | Feature | FormGenerator | StrictFormGenerator |
750
+ |---------|---------------|---------------------|
751
+ | Schema | No (infers from fields) | Required (Zod) |
752
+ | Field name checking | Inferred from fields | Enforced at compile-time |
753
+ | Type inference | From field definitions | From Zod schema |
754
+ | Use case | Quick prototyping | Production apps |
755
+
756
+ ### Available Helpers
757
+
758
+ | Helper | Description |
759
+ |--------|-------------|
760
+ | `createFieldFactory(schema)` | Create a field factory for a schema |
761
+ | `typedField<typeof schema>()` | Create a single typed field |
762
+ | `typedFields<typeof schema>([...])` | Create an array of typed fields |
763
+
764
+ ---
765
+
547
766
  ## TypeScript Type Inference
548
767
 
549
768
  Get full type inference from field definitions: