@connect-soft/form-generator 1.1.0-alpha7 → 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
@@ -220,12 +220,13 @@ const fields = [
220
220
  ] as const;
221
221
  ```
222
222
 
223
- Don't forget to register the component for your custom field:
223
+ Then register the component for your custom field:
224
224
 
225
225
  ```typescript
226
226
  import { registerField } from '@connect-soft/form-generator';
227
227
  import { ColorPicker } from './components/ColorPicker';
228
228
 
229
+ // Type-safe: 'color-picker' must exist in FieldTypeRegistry
229
230
  registerField('color-picker', ({ field, formField }) => (
230
231
  <ColorPicker
231
232
  value={formField.value}
@@ -234,6 +235,71 @@ registerField('color-picker', ({ field, formField }) => (
234
235
  showAlpha={field.showAlpha}
235
236
  />
236
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 });
237
303
  ```
238
304
 
239
305
  ---
@@ -574,6 +640,63 @@ function MyForm() {
574
640
  | `isDirty()` | Check if form has unsaved changes |
575
641
  | `form` | Access underlying react-hook-form instance |
576
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
+
577
700
  ---
578
701
 
579
702
  ## API Reference
@@ -595,6 +718,7 @@ function MyForm() {
595
718
  | `description` | `string` | - | Form description (available in render props) |
596
719
  | `showReset` | `boolean` | `false` | Include reset button in `buttons.reset` |
597
720
  | `resetText` | `string` | `'Reset'` | Reset button text |
721
+ | `validateTypes` | `boolean \| ValidateTypesOptions` | `false` | Runtime validation of field types |
598
722
 
599
723
  ### Field Base Properties
600
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 = {};
@@ -2957,27 +3000,15 @@ function createDefaultFieldComponent(type) {
2957
3000
  };
2958
3001
  return FallbackComponent;
2959
3002
  }
2960
- function registerField(type, component, options = {}, config = {}) {
2961
- const {
2962
- override = true
2963
- } = config;
2964
- const exists = fieldRegistry.has(type);
2965
- if (override === 'only' && !exists) return;
2966
- if (override === false && exists) return;
3003
+ function registerField(type, component, options = {}) {
2967
3004
  fieldRegistry.set(type, {
2968
3005
  component,
2969
3006
  options
2970
3007
  });
2971
3008
  }
2972
- function registerFields(fields, config = {}) {
2973
- const {
2974
- override = true
2975
- } = config;
3009
+ function registerFields(fields) {
2976
3010
  Object.entries(fields).forEach(([type, definition]) => {
2977
3011
  var _a;
2978
- const exists = fieldRegistry.has(type);
2979
- if (override === 'only' && !exists) return;
2980
- if (override === false && exists) return;
2981
3012
  if (typeof definition === 'function') {
2982
3013
  fieldRegistry.set(type, {
2983
3014
  component: definition,
@@ -3012,24 +3043,12 @@ function unregisterField(type) {
3012
3043
  function getRegisteredFieldTypes() {
3013
3044
  return Array.from(fieldRegistry.keys());
3014
3045
  }
3015
- function registerFormComponents(components, config = {}) {
3016
- const {
3017
- override = true
3018
- } = config;
3046
+ function registerFormComponents(components) {
3019
3047
  Object.keys(components).forEach(key => {
3020
- const exists = key in componentRegistry;
3021
- if (override === 'only' && !exists) return;
3022
- if (override === false && exists) return;
3023
3048
  componentRegistry[key] = components[key];
3024
3049
  });
3025
3050
  }
3026
- function registerFormComponent(name, component, config = {}) {
3027
- const {
3028
- override = true
3029
- } = config;
3030
- const exists = name in componentRegistry;
3031
- if (override === 'only' && !exists) return;
3032
- if (override === false && exists) return;
3051
+ function registerFormComponent(name, component) {
3033
3052
  componentRegistry[name] = component;
3034
3053
  }
3035
3054
  function getFormComponent(name) {
@@ -3514,8 +3533,19 @@ function FormGeneratorImpl(props) {
3514
3533
  showReset = false,
3515
3534
  resetText = 'Reset',
3516
3535
  children,
3517
- ref
3536
+ ref,
3537
+ validateTypes
3518
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]);
3519
3549
  const userSchema = hasSchema(props) ? props.schema : undefined;
3520
3550
  const onSubmit = props.onSubmit;
3521
3551
  const formRef = React.useRef(null);
@@ -3627,10 +3657,10 @@ function FormGeneratorImpl(props) {
3627
3657
  }
3628
3658
  return result;
3629
3659
  }, [SubmitButton, disabled, form.formState.isSubmitting, submitText, showReset, resetText, handleReset]);
3630
- const renderField = React.useCallback((field_1, options) => {
3660
+ const renderField = React.useCallback((field_1, options_0) => {
3631
3661
  return jsxRuntime.jsx(FieldRenderer, {
3632
3662
  field: field_1,
3633
- namePrefix: options === null || options === void 0 ? void 0 : options.namePrefix
3663
+ namePrefix: options_0 === null || options_0 === void 0 ? void 0 : options_0.namePrefix
3634
3664
  });
3635
3665
  }, []);
3636
3666
  if (!children) {
@@ -3707,6 +3737,8 @@ exports.FormProvider = FormProvider;
3707
3737
  exports.clearAllRegistries = clearAllRegistries;
3708
3738
  exports.clearFieldRegistry = clearFieldRegistry;
3709
3739
  exports.clearFormComponentRegistry = clearFormComponentRegistry;
3740
+ exports.createArrayField = createArrayField;
3741
+ exports.createField = createField;
3710
3742
  exports.getFieldComponent = getFieldComponent;
3711
3743
  exports.getFormComponent = getFormComponent;
3712
3744
  exports.getFormComponents = getFormComponents;
@@ -3719,10 +3751,13 @@ exports.registerFields = registerFields;
3719
3751
  exports.registerFormComponent = registerFormComponent;
3720
3752
  exports.registerFormComponents = registerFormComponents;
3721
3753
  exports.resetFormComponentRegistry = resetFormComponentRegistry;
3754
+ exports.strictFields = strictFields;
3722
3755
  exports.unregisterField = unregisterField;
3723
3756
  exports.useArrayField = useArrayField;
3724
3757
  exports.useFieldArray = useFieldArray;
3725
3758
  exports.useForm = useForm;
3726
3759
  exports.useFormContext = useFormContext;
3727
3760
  exports.useWatch = useWatch;
3761
+ exports.validateFieldType = validateFieldType;
3762
+ exports.validateFieldTypes = validateFieldTypes;
3728
3763
  //# sourceMappingURL=index.js.map