Skip to content

Commit 4e0dd92

Browse files
committed
feat: add FieldDefinition interface and PathsOf utility type for field props and path autocomplete
1 parent 2f7881e commit 4e0dd92

4 files changed

Lines changed: 157 additions & 67 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
- feat: enable strictNullChecks across all source files
55
- fix: resolve all noImplicitAny errors across source files
66
- chore: enable strictFunctionTypes, alwaysStrict, strictBindCallApply, noImplicitThis, useUnknownInCatchVariables, strictPropertyInitialization
7+
- feat: add `FieldDefinition` interface for autocomplete of field props in constructor
8+
- feat: add `PathsOf<T>` recursive utility type for nested field path inference
9+
- feat: override `select()` in `Form` with proper return type
10+
- feat: export `PathsOf` and `FieldDefinition` from package entry point
11+
- docs: improved JSDoc on `$()` and `PathsOf` with usage examples
712

813
# 6.12.7 (master)
914

@@ -517,7 +522,7 @@ fix: #371 #399 #408
517522
- Introduced Form & Field `validating` mobx computed (async check).
518523
- Events Hooks: returned Form instance (was a Field instance).
519524
- Fixed Async error state (removed `loadingMessage`)
520-
- Fixed Async "validating..." Message (null as default).
525+
- Fixed Async \"validating...\" Message (null as default).
521526
- `loadingMessage` option removed.
522527

523528
# 1.19
@@ -527,7 +532,7 @@ fix: #371 #399 #408
527532
- Better Array Handling.
528533
- Optional `submit()` onSuccess/onError callbacks.
529534
- Fixed Empty Array Handling in Field Container.
530-
- Removed Initial Array element "0" (no more del(0)).
535+
- Removed Initial Array element \"0\" (no more del(0)).
531536

532537
# 1.18
533538

@@ -547,7 +552,7 @@ fix: #371 #399 #408
547552
- Introduced form computed validating().
548553
- Introduced alwaysShowDefaultError option.
549554
- Introduced setup() method.
550-
- Introduced "disabled" computed prop
555+
- Introduced \"disabled\" computed prop
551556
- Introduced Fields Props Bindings
552557
- Introduced initial props: types, bindings.
553558
- Introduced field props: type, bindings.
@@ -594,67 +599,4 @@ fix: #371 #399 #408
594599

595600
# 1.10
596601

597-
- Fixed some documentation errors
598-
- Enabled VJF enhancement
599-
- Support for React Select
600-
601-
# 1.9
602-
603-
- Validation Plugins
604-
- Multiple Validation Styles
605-
- Support for Async Validation
606-
- Support for Material UI, React Widgets
607-
- Package name changed from `mobx-ajv-form` to `mobx-react-form`
608-
609-
# 1.8
610-
611-
- Decoupled validator from fields
612-
613-
# 1.7
614-
615-
- Validation using mobx observe on field $value
616-
- Display errors of related fields
617-
618-
# 1.5
619-
620-
- Custom Validation Functions
621-
622-
# 1.4
623-
624-
- Support for v5 json schema rules
625-
626-
# 1.3
627-
628-
- Updated constructor with single object
629-
- Custom Validation Keywords
630-
- Created API Documentation
631-
- Added methods: isDirty();
632-
- Renamed computed values: valid to isValid
633-
- Added some tests
634-
635-
# 1.2
636-
637-
- Added silent mode
638-
- Updated methods: validate(); clear(); reset();
639-
- Clear genericErrorMessage on clear/reset
640-
641-
# 1.2
642-
643-
- Added methods: reset();
644-
- Updated methods: clear();
645-
646-
# 1.1
647-
648-
- Fixed UMD build external libs
649-
650-
# 1.1
651-
652-
- Added UMD build/support
653-
654-
# 1.1
655-
656-
- Updated README.md
657-
658-
# 1.0
659-
660-
- First Release
602+
- Fix

src/Form.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,34 @@ export default class Form<F extends Record<string, any> = Record<string, any>> e
182182
return new FieldClass(data);
183183
}
184184

185+
/**
186+
* Select a field by key with type inference.
187+
* Provides autocomplete for field keys when the form is typed with a generic `F`.
188+
*
189+
* @example
190+
* // Top-level keys get autocomplete from keyof F:
191+
* form.$('username'); // returns Field<string>
192+
*
193+
* @example
194+
* // For nested paths, use the `PathsOf` utility type:
195+
* import { PathsOf } from 'mobx-react-form';
196+
*
197+
* function getField(form: Form<NestedClubFields>, path: PathsOf<NestedClubFields>) {
198+
* return form.$(path); // path autocompletes to "club" | "club.name" | "members[].firstname" | ...
199+
* }
200+
*/
185201
override $(key: keyof F): Field<F[keyof F]> {
186202
return super.$(key as string) as Field<F[keyof F]>;
187203
}
188204

205+
/**
206+
* Select a field by path.
207+
* For typed autocomplete, use `$()` instead, which is typed with `keyof F`.
208+
*/
209+
override select(path: string | number, fields: any = null, isStrict: boolean = true): Field<any> {
210+
return super.select(path, fields, isStrict) as Field<any>;
211+
}
212+
189213
override values(): { [K in keyof F]?: F[K] } {
190214
return super.values() as { [K in keyof F]?: F[K] };
191215
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ import Field from "./Field";
33

44
export default Form;
55
export { Form, Field };
6+
export type { PathsOf, FieldDefinition } from "./models/FormInterface";

src/models/FormInterface.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,58 @@ import { BaseInterface } from "./BaseInterface";
33
import { FieldInterface, FieldConstructor } from "./FieldInterface";
44
import { OptionsModel } from "./OptionsModel";
55
import { ValidatorInterface, ValidationPlugins } from "./ValidatorInterface";
6+
/**
7+
* Recursively derives all possible field paths from a form values type `T`.
8+
* Supports nested objects, arrays (with `[]` suffix), and dot-separated paths.
9+
* Excludes built-in non-plain objects like Date, File, Blob, etc.
10+
*
11+
* @example
12+
* // --- Basic usage ---
13+
* type Profile = {
14+
* username: string;
15+
* email: string;
16+
* };
17+
* // PathsOf<Profile> = "username" | "email"
18+
*
19+
* @example
20+
* // --- Nested objects ---
21+
* type NestedClubFields = {
22+
* club: { name: string; city: string };
23+
* members: { firstname: string; lastname: string; hobbies: string[] }[];
24+
* };
25+
* // PathsOf<NestedClubFields> = "club" | "members" | "club.name" | "club.city"
26+
* // | "members[].firstname" | "members[].lastname" | "members[].hobbies"
27+
*
28+
* @example
29+
* // --- Usage with form.$() or form.select() ---
30+
* const form = new Form<{ profile: { name: string; age: number } }>({ });
31+
*
32+
* // Top-level keys get autocomplete directly from keyof F:
33+
* form.$('profile'); // ✓ autocomplete, returns Field<{ name: string; age: number }>
34+
*
35+
* // For nested paths, use PathsOf in a helper function:
36+
* import { PathsOf } from 'mobx-react-form';
37+
*
38+
* function getField(form: Form<{ profile: { name: string; age: number } }>, path: PathsOf<{ profile: { name: string; age: number } }>) {
39+
* return form.$(path).value;
40+
* // ^— path autocompletes to: "profile" | "profile.name" | "profile.age"
41+
* }
42+
* getField(form, 'profile.name'); // ✓ returns string
43+
*/
44+
export type PathsOf<T> = T extends (infer U)[]
45+
? PathsOf<U>
46+
: T extends Date | File | Blob | RegExp | Map<any, any> | Set<any> | Promise<any>
47+
? never
48+
: T extends object
49+
? { [K in keyof T & string]:
50+
T[K] extends (infer U)[]
51+
? K | `${K}[].${PathsOf<U>}`
52+
: T[K] extends object
53+
? K | `${K}.${PathsOf<T[K]>}`
54+
: K
55+
}[keyof T & string]
56+
: never;
57+
658
export interface FormInterface<F extends Record<string, any> = Record<string, any>> extends BaseInterface<F> {
759
name: string;
860
extra: Record<string, any>;
@@ -33,8 +85,79 @@ export interface FormInterface<F extends Record<string, any> = Record<string, an
3385
makeField(data: FieldConstructor, FieldClass?: typeof Field): FieldInterface;
3486
}
3587

88+
export interface FieldDefinition {
89+
/** Field value */
90+
value?: any;
91+
/** Field label */
92+
label?: string;
93+
/** Field placeholder */
94+
placeholder?: string;
95+
/** Validation rules string (e.g. "required|string|min:3") */
96+
rules?: string;
97+
/** Field type (e.g. "text", "checkbox", "password", "file", etc.) */
98+
type?: string;
99+
/** Whether the field is disabled */
100+
disabled?: boolean;
101+
/** Related field paths */
102+
related?: string[];
103+
/** Default value */
104+
default?: any;
105+
/** Initial value */
106+
initial?: any;
107+
/** Nested sub-fields definition */
108+
fields?: any;
109+
/** Binding name for the UI component */
110+
bindings?: string;
111+
/** Extra metadata */
112+
extra?: Record<string, any>;
113+
/** Field-level options */
114+
options?: OptionsModel;
115+
/** Field hooks */
116+
hooks?: Record<string, any>;
117+
/** Field handlers */
118+
handlers?: Record<string, any>;
119+
/** Custom validation functions */
120+
validators?: any[];
121+
/** Which field prop to use for validation output */
122+
validatedWith?: string;
123+
/** MobX observers */
124+
observers?: any[];
125+
/** MobX interceptors */
126+
interceptors?: any[];
127+
/** Ref callback/object */
128+
ref?: any;
129+
/** Whether the field is nullable */
130+
nullable?: boolean;
131+
/** Auto-focus on mount */
132+
autoFocus?: boolean;
133+
/** Input mode for mobile keyboards */
134+
inputMode?: string;
135+
/** Autocomplete attribute */
136+
autoComplete?: string;
137+
/** Input converter function */
138+
input?: Function;
139+
/** Output converter function */
140+
output?: Function;
141+
/** Value converter function */
142+
converter?: Function;
143+
/** Multiple converters */
144+
converters?: Function[];
145+
/** Computed value */
146+
computed?: any;
147+
/** Field name (overrides the key) */
148+
name?: string;
149+
/** Custom Field class */
150+
class?: any;
151+
}
152+
36153
export interface FieldsDefinitions {
37154
struct?: string[];
155+
/**
156+
* Field definitions.
157+
* - For autocomplete on field props, use `Record<string, FieldDefinition>`
158+
* (e.g. `const fields: Record<string, FieldDefinition> = { ... }`)
159+
* - Or pass an array of field path strings for struct/separated mode.
160+
*/
38161
fields?: any;
39162
computed?: any;
40163
values?: any;

0 commit comments

Comments
 (0)