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

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
@@ -120,8 +120,53 @@ registerFields({
120
120
 
121
121
  // Register the submit button component
122
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
+ ));
136
+ ```
137
+
138
+ ---
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
+ ));
123
166
  ```
124
167
 
168
+ Both default to `React.Fragment`, adding zero DOM overhead when not customized.
169
+
125
170
  ---
126
171
 
127
172
  ## Field Types
@@ -175,12 +220,13 @@ const fields = [
175
220
  ] as const;
176
221
  ```
177
222
 
178
- Don't forget to register the component for your custom field:
223
+ Then register the component for your custom field:
179
224
 
180
225
  ```typescript
181
226
  import { registerField } from '@connect-soft/form-generator';
182
227
  import { ColorPicker } from './components/ColorPicker';
183
228
 
229
+ // Type-safe: 'color-picker' must exist in FieldTypeRegistry
184
230
  registerField('color-picker', ({ field, formField }) => (
185
231
  <ColorPicker
186
232
  value={formField.value}
@@ -189,6 +235,71 @@ registerField('color-picker', ({ field, formField }) => (
189
235
  showAlpha={field.showAlpha}
190
236
  />
191
237
  ));
238
+
239
+ // ❌ TypeScript error: 'unknown-type' is not in FieldTypeRegistry
240
+ // registerField('unknown-type', MyComponent);
241
+ ```
242
+
243
+ > **Note:** Both `registerField` and `registerFields` enforce that field types must be defined in `FieldTypeRegistry`. This ensures type safety between your type definitions and runtime registrations.
244
+
245
+ ### Field Type Validation Helpers
246
+
247
+ Use helper functions for strict type checking without `as const`:
248
+
249
+ ```typescript
250
+ import { createField, createArrayField, strictFields } from '@connect-soft/form-generator';
251
+
252
+ // Create a single field with full type checking
253
+ const emailField = createField({
254
+ type: 'email',
255
+ name: 'email',
256
+ label: 'Email',
257
+ placeholder: 'Enter your email' // TypeScript knows this is valid for email
258
+ });
259
+
260
+ // Create an array field
261
+ const contacts = createArrayField({
262
+ name: 'contacts',
263
+ fields: [
264
+ { type: 'text', name: 'name', label: 'Name' },
265
+ { type: 'email', name: 'email', label: 'Email' }
266
+ ],
267
+ minItems: 1,
268
+ maxItems: 5
269
+ });
270
+
271
+ // Create an array of fields with type checking
272
+ const fields = strictFields([
273
+ { type: 'text', name: 'username', label: 'Username' },
274
+ { type: 'email', name: 'email', label: 'Email' },
275
+ // { type: 'unknown', name: 'bad' } // TypeScript error!
276
+ ]);
277
+ ```
278
+
279
+ ### Runtime Field Type Validation
280
+
281
+ Enable runtime validation to catch unregistered field types during development:
282
+
283
+ ```typescript
284
+ // Warn in console for unregistered types (recommended for development)
285
+ <FormGenerator
286
+ fields={fields}
287
+ onSubmit={handleSubmit}
288
+ validateTypes
289
+ />
290
+
291
+ // Throw an error for unregistered types
292
+ <FormGenerator
293
+ fields={fields}
294
+ onSubmit={handleSubmit}
295
+ validateTypes={{ throwOnError: true }}
296
+ />
297
+
298
+ // Manual validation
299
+ import { validateFieldTypes, getRegisteredFieldTypes } from '@connect-soft/form-generator';
300
+
301
+ const registeredTypes = getRegisteredFieldTypes();
302
+ validateFieldTypes(fields, registeredTypes, { throwOnError: true });
192
303
  ```
193
304
 
194
305
  ---
@@ -365,6 +476,8 @@ The render function receives:
365
476
  | `isValid` | `boolean` | Form validity state |
366
477
  | `isDirty` | `boolean` | Form dirty state |
367
478
  | `renderField` | `function` | Manual field renderer |
479
+ | `FieldWrapper` | `ComponentType` | Registered FieldWrapper component |
480
+ | `FieldsWrapper` | `ComponentType` | Registered FieldsWrapper component |
368
481
 
369
482
  ### Fields Object
370
483
 
@@ -527,6 +640,63 @@ function MyForm() {
527
640
  | `isDirty()` | Check if form has unsaved changes |
528
641
  | `form` | Access underlying react-hook-form instance |
529
642
 
643
+ ### Watching Form Values
644
+
645
+ Detect when field values change from the parent component:
646
+
647
+ ```typescript
648
+ import { useRef, useEffect } from 'react';
649
+ import { FormGenerator, FormGeneratorRef } from '@connect-soft/form-generator';
650
+
651
+ function MyForm() {
652
+ const formRef = useRef<FormGeneratorRef>(null);
653
+
654
+ useEffect(() => {
655
+ // Watch all fields for changes
656
+ const subscription = formRef.current?.form.watch((values, { name, type }) => {
657
+ console.log('Changed field:', name);
658
+ console.log('New values:', values);
659
+ });
660
+
661
+ return () => subscription?.unsubscribe();
662
+ }, []);
663
+
664
+ return (
665
+ <FormGenerator
666
+ ref={formRef}
667
+ fields={fields}
668
+ onSubmit={handleSubmit}
669
+ />
670
+ );
671
+ }
672
+ ```
673
+
674
+ Or use `useWatch` inside a custom layout:
675
+
676
+ ```typescript
677
+ import { FormGenerator, useWatch } from '@connect-soft/form-generator';
678
+
679
+ function ValueWatcher() {
680
+ const email = useWatch({ name: 'email' }); // Watch specific field
681
+
682
+ useEffect(() => {
683
+ console.log('Email changed:', email);
684
+ }, [email]);
685
+
686
+ return null;
687
+ }
688
+
689
+ <FormGenerator fields={fields} onSubmit={handleSubmit}>
690
+ {({ fields, buttons }) => (
691
+ <>
692
+ {fields.all}
693
+ <ValueWatcher />
694
+ {buttons.submit}
695
+ </>
696
+ )}
697
+ </FormGenerator>
698
+ ```
699
+
530
700
  ---
531
701
 
532
702
  ## API Reference
@@ -548,6 +718,7 @@ function MyForm() {
548
718
  | `description` | `string` | - | Form description (available in render props) |
549
719
  | `showReset` | `boolean` | `false` | Include reset button in `buttons.reset` |
550
720
  | `resetText` | `string` | `'Reset'` | Reset button text |
721
+ | `validateTypes` | `boolean \| ValidateTypesOptions` | `false` | Runtime validation of field types |
551
722
 
552
723
  ### Field Base Properties
553
724
 
package/dist/index.js CHANGED
@@ -2795,6 +2795,49 @@ function t(r,e){try{var o=r();}catch(r){return e(r)}return o&&o.then?o.then(void
2795
2795
  function isArrayField(field) {
2796
2796
  return field.type === 'array' && 'fields' in field;
2797
2797
  }
2798
+ function createField(field) {
2799
+ return field;
2800
+ }
2801
+ function createArrayField(field) {
2802
+ return {
2803
+ ...field,
2804
+ type: 'array'
2805
+ };
2806
+ }
2807
+ function strictFields(fields) {
2808
+ return fields;
2809
+ }
2810
+ function validateFieldType(type, registeredTypes) {
2811
+ if (!registeredTypes.includes(type) && type !== 'array') {
2812
+ throw new Error(`Unregistered field type: "${type}". ` + `Registered types: ${registeredTypes.join(', ')}. ` + `Use registerField("${type}", YourComponent) to register it.`);
2813
+ }
2814
+ return type;
2815
+ }
2816
+ function validateFieldTypes(fields, registeredTypes, options = {}) {
2817
+ const {
2818
+ throwOnError = false,
2819
+ warn = true
2820
+ } = options;
2821
+ const invalidTypes = [];
2822
+ const checkField = field => {
2823
+ if (field.type === 'array' && 'fields' in field) {
2824
+ field.fields.forEach(checkField);
2825
+ } else if (!registeredTypes.includes(field.type) && field.type !== 'array') {
2826
+ invalidTypes.push(field.type);
2827
+ }
2828
+ };
2829
+ fields.forEach(checkField);
2830
+ if (invalidTypes.length > 0) {
2831
+ const uniqueInvalid = [...new Set(invalidTypes)];
2832
+ const message = `Unregistered field type(s): ${uniqueInvalid.map(t => `"${t}"`).join(', ')}. ` + `Registered types: ${registeredTypes.join(', ')}. ` + 'Register custom types with registerField() or use module augmentation for TypeScript support.';
2833
+ if (throwOnError) {
2834
+ throw new Error(message);
2835
+ } else if (warn && typeof console !== 'undefined') {
2836
+ console.warn(`[FormGenerator] ${message}`);
2837
+ }
2838
+ }
2839
+ return fields;
2840
+ }
2798
2841
 
2799
2842
  const fieldRegistry = new Map();
2800
2843
  const componentRegistry = {};
@@ -2808,7 +2851,13 @@ const defaultFormComponents = {
2808
2851
  type: 'submit',
2809
2852
  className: className !== null && className !== void 0 ? className : 'form-submit',
2810
2853
  disabled: disabled || isSubmitting
2811
- }, isSubmitting ? 'Submitting...' : children)
2854
+ }, isSubmitting ? 'Submitting...' : children),
2855
+ FieldWrapper: ({
2856
+ children
2857
+ }) => /*#__PURE__*/React.createElement(React.Fragment, null, children),
2858
+ FieldsWrapper: ({
2859
+ children
2860
+ }) => /*#__PURE__*/React.createElement(React.Fragment, null, children)
2812
2861
  };
2813
2862
  const htmlInputTypes = {
2814
2863
  text: 'text',
@@ -2951,27 +3000,15 @@ function createDefaultFieldComponent(type) {
2951
3000
  };
2952
3001
  return FallbackComponent;
2953
3002
  }
2954
- function registerField(type, component, options = {}, config = {}) {
2955
- const {
2956
- override = true
2957
- } = config;
2958
- const exists = fieldRegistry.has(type);
2959
- if (override === 'only' && !exists) return;
2960
- if (override === false && exists) return;
3003
+ function registerField(type, component, options = {}) {
2961
3004
  fieldRegistry.set(type, {
2962
3005
  component,
2963
3006
  options
2964
3007
  });
2965
3008
  }
2966
- function registerFields(fields, config = {}) {
2967
- const {
2968
- override = true
2969
- } = config;
3009
+ function registerFields(fields) {
2970
3010
  Object.entries(fields).forEach(([type, definition]) => {
2971
3011
  var _a;
2972
- const exists = fieldRegistry.has(type);
2973
- if (override === 'only' && !exists) return;
2974
- if (override === false && exists) return;
2975
3012
  if (typeof definition === 'function') {
2976
3013
  fieldRegistry.set(type, {
2977
3014
  component: definition,
@@ -3006,24 +3043,12 @@ function unregisterField(type) {
3006
3043
  function getRegisteredFieldTypes() {
3007
3044
  return Array.from(fieldRegistry.keys());
3008
3045
  }
3009
- function registerFormComponents(components, config = {}) {
3010
- const {
3011
- override = true
3012
- } = config;
3046
+ function registerFormComponents(components) {
3013
3047
  Object.keys(components).forEach(key => {
3014
- const exists = key in componentRegistry;
3015
- if (override === 'only' && !exists) return;
3016
- if (override === false && exists) return;
3017
3048
  componentRegistry[key] = components[key];
3018
3049
  });
3019
3050
  }
3020
- function registerFormComponent(name, component, config = {}) {
3021
- const {
3022
- override = true
3023
- } = config;
3024
- const exists = name in componentRegistry;
3025
- if (override === 'only' && !exists) return;
3026
- if (override === false && exists) return;
3051
+ function registerFormComponent(name, component) {
3027
3052
  componentRegistry[name] = component;
3028
3053
  }
3029
3054
  function getFormComponent(name) {
@@ -3508,8 +3533,19 @@ function FormGeneratorImpl(props) {
3508
3533
  showReset = false,
3509
3534
  resetText = 'Reset',
3510
3535
  children,
3511
- ref
3536
+ ref,
3537
+ validateTypes
3512
3538
  } = props;
3539
+ React.useMemo(() => {
3540
+ if (validateTypes) {
3541
+ const registeredTypes = getRegisteredFieldTypes();
3542
+ const options = typeof validateTypes === 'object' ? validateTypes : {
3543
+ throwOnError: false,
3544
+ warn: true
3545
+ };
3546
+ validateFieldTypes(fields, registeredTypes, options);
3547
+ }
3548
+ }, [fields, validateTypes]);
3513
3549
  const userSchema = hasSchema(props) ? props.schema : undefined;
3514
3550
  const onSubmit = props.onSubmit;
3515
3551
  const formRef = React.useRef(null);
@@ -3561,6 +3597,8 @@ function FormGeneratorImpl(props) {
3561
3597
  form: form
3562
3598
  }), [form, handleSubmit, mergedDefaultValues]);
3563
3599
  const SubmitButton = getFormComponent('SubmitButton');
3600
+ const FieldWrapper = getFormComponent('FieldWrapper');
3601
+ const FieldsWrapper = getFormComponent('FieldsWrapper');
3564
3602
  const {
3565
3603
  regularFields,
3566
3604
  arrayFields
@@ -3583,8 +3621,13 @@ function FormGeneratorImpl(props) {
3583
3621
  const entries = new Map();
3584
3622
  regularFields.forEach((field_0, index) => {
3585
3623
  if (!field_0.hidden) {
3586
- const element = jsxRuntime.jsx(FieldRenderer, {
3587
- field: field_0
3624
+ const element = jsxRuntime.jsx(FieldWrapper, {
3625
+ name: field_0.name,
3626
+ type: field_0.type,
3627
+ className: field_0.className,
3628
+ children: jsxRuntime.jsx(FieldRenderer, {
3629
+ field: field_0
3630
+ })
3588
3631
  }, field_0.name || `field-${index}`);
3589
3632
  entries.set(field_0.name, {
3590
3633
  field: field_0,
@@ -3594,7 +3637,7 @@ function FormGeneratorImpl(props) {
3594
3637
  }
3595
3638
  });
3596
3639
  return entries;
3597
- }, [regularFields]);
3640
+ }, [regularFields, FieldWrapper]);
3598
3641
  const templateFields = React.useMemo(() => createTemplateFields(fieldEntries), [fieldEntries]);
3599
3642
  const buttons = React.useMemo(() => {
3600
3643
  const result = {
@@ -3614,10 +3657,10 @@ function FormGeneratorImpl(props) {
3614
3657
  }
3615
3658
  return result;
3616
3659
  }, [SubmitButton, disabled, form.formState.isSubmitting, submitText, showReset, resetText, handleReset]);
3617
- const renderField = React.useCallback((field_1, options) => {
3660
+ const renderField = React.useCallback((field_1, options_0) => {
3618
3661
  return jsxRuntime.jsx(FieldRenderer, {
3619
3662
  field: field_1,
3620
- namePrefix: options === null || options === void 0 ? void 0 : options.namePrefix
3663
+ namePrefix: options_0 === null || options_0 === void 0 ? void 0 : options_0.namePrefix
3621
3664
  });
3622
3665
  }, []);
3623
3666
  if (!children) {
@@ -3627,11 +3670,23 @@ function FormGeneratorImpl(props) {
3627
3670
  ref: formRef,
3628
3671
  onSubmit: handleSubmit,
3629
3672
  className: className,
3630
- children: [regularFields.map((field_2, index_0) => jsxRuntime.jsx(FieldRenderer, {
3631
- field: field_2
3632
- }, field_2.name || `field-${index_0}`)), arrayFields.map(arrayField => jsxRuntime.jsx(ArrayFieldRenderer, {
3633
- field: arrayField
3634
- }, arrayField.name)), jsxRuntime.jsx(SubmitButton, {
3673
+ children: [jsxRuntime.jsxs(FieldsWrapper, {
3674
+ children: [regularFields.map((field_2, index_0) => jsxRuntime.jsx(FieldWrapper, {
3675
+ name: field_2.name,
3676
+ type: field_2.type,
3677
+ className: field_2.className,
3678
+ children: jsxRuntime.jsx(FieldRenderer, {
3679
+ field: field_2
3680
+ })
3681
+ }, field_2.name || `field-${index_0}`)), arrayFields.map(arrayField => jsxRuntime.jsx(FieldWrapper, {
3682
+ name: arrayField.name,
3683
+ type: "array",
3684
+ className: arrayField.className,
3685
+ children: jsxRuntime.jsx(ArrayFieldRenderer, {
3686
+ field: arrayField
3687
+ })
3688
+ }, arrayField.name))]
3689
+ }), jsxRuntime.jsx(SubmitButton, {
3635
3690
  disabled: disabled || form.formState.isSubmitting,
3636
3691
  isSubmitting: form.formState.isSubmitting,
3637
3692
  children: submitText
@@ -3651,7 +3706,9 @@ function FormGeneratorImpl(props) {
3651
3706
  isSubmitting: form.formState.isSubmitting,
3652
3707
  isValid: form.formState.isValid,
3653
3708
  isDirty: form.formState.isDirty,
3654
- renderField
3709
+ renderField,
3710
+ FieldWrapper,
3711
+ FieldsWrapper
3655
3712
  };
3656
3713
  const renderFn = children;
3657
3714
  return jsxRuntime.jsx(FormProvider, {
@@ -3680,6 +3737,8 @@ exports.FormProvider = FormProvider;
3680
3737
  exports.clearAllRegistries = clearAllRegistries;
3681
3738
  exports.clearFieldRegistry = clearFieldRegistry;
3682
3739
  exports.clearFormComponentRegistry = clearFormComponentRegistry;
3740
+ exports.createArrayField = createArrayField;
3741
+ exports.createField = createField;
3683
3742
  exports.getFieldComponent = getFieldComponent;
3684
3743
  exports.getFormComponent = getFormComponent;
3685
3744
  exports.getFormComponents = getFormComponents;
@@ -3692,10 +3751,13 @@ exports.registerFields = registerFields;
3692
3751
  exports.registerFormComponent = registerFormComponent;
3693
3752
  exports.registerFormComponents = registerFormComponents;
3694
3753
  exports.resetFormComponentRegistry = resetFormComponentRegistry;
3754
+ exports.strictFields = strictFields;
3695
3755
  exports.unregisterField = unregisterField;
3696
3756
  exports.useArrayField = useArrayField;
3697
3757
  exports.useFieldArray = useFieldArray;
3698
3758
  exports.useForm = useForm;
3699
3759
  exports.useFormContext = useFormContext;
3700
3760
  exports.useWatch = useWatch;
3761
+ exports.validateFieldType = validateFieldType;
3762
+ exports.validateFieldTypes = validateFieldTypes;
3701
3763
  //# sourceMappingURL=index.js.map