Skip to content

Commit 4fb5ae6

Browse files
committed
feat(renderer): add improved typings to schema
1 parent 6b7c81d commit 4fb5ae6

File tree

6 files changed

+81
-10
lines changed

6 files changed

+81
-10
lines changed

packages/react-form-renderer/src/common-types/component-mapper.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,15 @@ interface ComponentMapper {
99
[key: string]: React.ComponentType | React.FunctionComponent | React.ElementType | ExtendedMapperComponent;
1010
}
1111

12+
// Pure generic type that extracts component props from any ComponentMapper
13+
export type ComponentPropsMap<T extends ComponentMapper> = {
14+
[K in keyof T]: T[K] extends React.ComponentType<infer P>
15+
? P
16+
: T[K] extends ExtendedMapperComponent
17+
? T[K]['component'] extends React.ComponentType<infer P>
18+
? P
19+
: never
20+
: never;
21+
};
22+
1223
export default ComponentMapper;

packages/react-form-renderer/src/common-types/field.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { AnyObject } from './any-object';
55
import { FieldInputProps } from 'react-final-form';
66
import { FormOptions } from '../renderer-context';
77
import { Meta } from '../use-field-api';
8+
import ComponentMapper, { ComponentPropsMap } from './component-mapper';
89

910
export type FieldAction = [string, ...any[]];
1011

@@ -23,7 +24,46 @@ export type ResolvePropsFunction<FormValues = Record<string, any>, FieldValue =
2324
formOptions: FormOptions<FormValues>
2425
) => AnyObject;
2526

26-
interface Field<FormValues = Record<string, any>, FieldValue = FormValues[keyof FormValues]> extends AnyObject {
27+
// Re-export BaseFieldProps from use-field-api for consistency
28+
export { BaseFieldProps } from '../use-field-api';
29+
30+
// Strict base field properties for generic type inference (without AnyObject)
31+
export interface StrictBaseFieldProps<FormValues = Record<string, any>, FieldValue = FormValues[keyof FormValues]> {
32+
name: string;
33+
label?: string;
34+
helperText?: string;
35+
description?: string;
36+
isRequired?: boolean;
37+
isDisabled?: boolean;
38+
isReadOnly?: boolean;
39+
isVisible?: boolean;
40+
hideField?: boolean;
41+
validate?: Validator[];
42+
condition?: ConditionDefinition | ConditionDefinition[];
43+
initializeOnMount?: boolean;
44+
dataType?: DataType;
45+
initialValue?: FieldValue;
46+
clearedValue?: FieldValue;
47+
clearOnUnmount?: boolean;
48+
actions?: FieldActions;
49+
resolveProps?: ResolvePropsFunction<FormValues, FieldValue>;
50+
}
51+
52+
// Utility type for users to create typed field component props without AnyObject
53+
export type TypedFieldProps<P = {}> = StrictBaseFieldProps & P;
54+
55+
// Generic field type that infers component-specific props from any ComponentMapper
56+
export type Field<
57+
T extends ComponentMapper = ComponentMapper,
58+
C extends keyof T = keyof T,
59+
FormValues = Record<string, any>,
60+
FieldValue = FormValues[keyof FormValues]
61+
> = StrictBaseFieldProps<FormValues, FieldValue> & {
62+
component: C;
63+
} & ComponentPropsMap<T>[C];
64+
65+
// For backward compatibility - preserve the original interface
66+
interface LegacyField<FormValues = Record<string, any>, FieldValue = FormValues[keyof FormValues]> extends AnyObject {
2767
name: string;
2868
component: string;
2969
validate?: Validator[];
@@ -37,4 +77,5 @@ interface Field<FormValues = Record<string, any>, FieldValue = FormValues[keyof
3777
resolveProps?: ResolvePropsFunction<FormValues, FieldValue>;
3878
}
3979

40-
export default Field;
80+
// Export the backward-compatible version as default for existing code
81+
export default LegacyField;

packages/react-form-renderer/src/common-types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export * from './form-template-render-props';
88
export { default as SchemaValidatorMapper } from './schema-validator-mapper';
99
export * from './schema-validator-mapper';
1010
export { default as Schema } from './schema';
11+
export * from './schema';
1112
export { FieldInputProps as Input } from 'react-final-form';
Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
11
import { ReactNode } from 'react';
2-
import Field from './field';
2+
import LegacyField, { Field } from './field';
3+
import ComponentMapper from './component-mapper';
34

4-
interface Schema {
5+
// Helper type to create a union of all possible Field types for a ComponentMapper
6+
type FieldUnion<T extends ComponentMapper> = {
7+
[K in keyof T]: Field<T, K>;
8+
}[keyof T];
9+
10+
// Generic schema type that infers all component props from ComponentMapper
11+
export interface Schema<T extends ComponentMapper = ComponentMapper> {
12+
title?: ReactNode;
13+
description?: ReactNode;
14+
fields: FieldUnion<T>[];
15+
}
16+
17+
// Backward-compatible legacy schema type
18+
export interface LegacySchema {
519
title?: ReactNode;
620
description?: ReactNode;
7-
fields: Field[];
21+
fields: LegacyField[];
822
}
923

24+
// Export the generic Schema as default for new TypeScript usage
1025
export default Schema;
26+
27+
// Export LegacySchema for explicit legacy usage
28+
export { LegacySchema as LegacySchemaType };

packages/react-form-renderer/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export * from './validators/validator-functions';
2121
export { default as WizardContext } from './wizard-context';
2222

2323
// Export commonly used types
24-
export { Field, Schema, ComponentMapper, AnyObject } from './common-types';
24+
export { Field, Schema, ComponentMapper, AnyObject, TypedFieldProps } from './common-types';
2525

2626
// Export all types
2727
export * from './component-types';

packages/react-form-renderer/src/validation/validation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import getVisibleFields from '../get-visible-fields';
66
import defaultValidatorMapper from '../validator-mapper';
77
import { getValidate, ValidatorMapper as HelperValidatorMapper } from '../use-field-api/validator-helpers';
88
import { ValidatorFunction } from '../validators';
9-
import Schema from '../common-types/schema';
10-
import Field from '../common-types/field';
9+
import { LegacySchema as Schema } from '../common-types/schema';
10+
import LegacyField from '../common-types/field';
1111
import ComponentMapper from '../common-types/component-mapper';
1212
import { ValidatorMapper } from '../validator-mapper';
1313
import { ActionMapper } from '../form-renderer/action-mapper';
@@ -52,7 +52,7 @@ const changeToDefaultComponent = (schema: Schema): Schema => {
5252
...field,
5353
...(field.component && { component: DEFAULT_COMPONENT }),
5454
...(field.fields && {
55-
fields: field.fields.map((subField: Field) => ({
55+
fields: field.fields.map((subField: LegacyField) => ({
5656
...subField,
5757
...(subField.component && { component: DEFAULT_COMPONENT }),
5858
})),
@@ -99,7 +99,7 @@ const validation = async (schema: Schema, options: ValidationOptions): Promise<V
9999
finalSchema = changeToDefaultComponent(schema);
100100
}
101101

102-
defaultSchemaValidator(finalSchema, finalComponentMapper, validatorTypes, actionTypes, schemaValidatorMapper);
102+
defaultSchemaValidator(finalSchema as any, finalComponentMapper, validatorTypes, actionTypes, schemaValidatorMapper);
103103

104104
const visibleFieldsResult = getVisibleFields(finalSchema, values, undefined, conditionMapper);
105105
finalSchema = visibleFieldsResult as Schema;

0 commit comments

Comments
 (0)