@neutro/form 0.0.5 → 0.1.0

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/dist/core.d.cts CHANGED
@@ -10,7 +10,7 @@ type _DeepPartialObject<T> = {
10
10
  [P in keyof T]?: DeepPartial<T[P]>;
11
11
  };
12
12
  type Prev = [never, 0, 1, 2, 3, 4, 5, ...any[]];
13
- type PathImpl<T, K extends keyof T, Depth extends number = 5> = [Depth] extends [never] ? never : K extends string ? T[K] extends Primitive ? K : T[K] extends Array<infer U> ? K | `${K}.${number}` | (U extends object ? `${K}.${number}.${PathImpl<U, keyof U, Prev[Depth]>}` : never) : T[K] extends object ? K | `${K}.${PathImpl<T[K], keyof T[K], Prev[Depth]>}` : K : never;
13
+ type PathImpl<T, K extends keyof T, Depth extends number = 5> = [Depth] extends [never] ? never : K extends string ? T[K] extends Primitive ? K : T[K] extends Array<infer U> ? K | `${K}.${number}` | (U extends object ? `${K}.${number}.${PathImpl<U, keyof U, Prev[Depth]>}` : never) : NonNullable<T[K]> extends object ? K | `${K}.${PathImpl<NonNullable<T[K]>, keyof NonNullable<T[K]>, Prev[Depth]>}` : K : never;
14
14
  type Path<T> = PathImpl<T, keyof T> & string;
15
15
  type _GetPathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? _GetPathValue<NonNullable<T[K]>, Rest> : T extends readonly any[] ? _GetPathValue<NonNullable<T[number]>, Rest> : unknown : P extends keyof T ? T[P] : T extends readonly any[] ? T[number] : unknown;
16
16
  type GetPathValue<T, P extends string> = _GetPathValue<T, P>;
@@ -23,7 +23,7 @@ interface FormState<T> {
23
23
  isValidating: boolean;
24
24
  }
25
25
  type FormSubscriber<T> = (state: FormState<T>) => void;
26
- type PathSubscriber = (value: any, fieldState: {
26
+ type PathSubscriber<V = any> = (value: V, fieldState: {
27
27
  error?: string;
28
28
  touched?: boolean;
29
29
  dirty?: boolean;
@@ -92,21 +92,98 @@ type BuiltInRule = 'required' | 'accepted' | 'email' | 'url' | 'numeric' | 'inte
92
92
  requiredUnless: string;
93
93
  message?: string;
94
94
  };
95
- interface FormConfig<T> {
95
+ type ValidationMode = 'onChange' | 'onBlur' | 'onTouched' | 'onSubmitOnly';
96
+ interface ValidationModeConfig<T extends object> {
97
+ default?: ValidationMode;
98
+ fields?: Partial<Record<Path<T> | (string & {}), ValidationMode>>;
99
+ }
100
+ type FormAction = {
101
+ type: 'SET';
102
+ path: string;
103
+ value: unknown;
104
+ options?: {
105
+ touch?: boolean;
106
+ validate?: boolean;
107
+ };
108
+ } | {
109
+ type: 'VALIDATE';
110
+ paths?: string[];
111
+ } | {
112
+ type: 'SUBMIT';
113
+ } | {
114
+ type: 'RESET';
115
+ newValues?: unknown;
116
+ } | {
117
+ type: 'SET_ERRORS';
118
+ errors: Record<string, string>;
119
+ } | {
120
+ type: 'CONNECT';
121
+ path: string;
122
+ } | {
123
+ type: 'DISCONNECT';
124
+ path: string;
125
+ } | {
126
+ type: 'BLUR';
127
+ path: string;
128
+ } | {
129
+ type: 'BATCH_START';
130
+ } | {
131
+ type: 'BATCH_END';
132
+ } | {
133
+ type: 'ARRAY_APPEND';
134
+ path: string;
135
+ item: unknown;
136
+ } | {
137
+ type: 'ARRAY_INSERT';
138
+ path: string;
139
+ index: number;
140
+ item: unknown;
141
+ } | {
142
+ type: 'ARRAY_REMOVE';
143
+ path: string;
144
+ index: number;
145
+ } | {
146
+ type: 'ARRAY_MOVE';
147
+ path: string;
148
+ from: number;
149
+ to: number;
150
+ } | {
151
+ type: 'ARRAY_SWAP';
152
+ path: string;
153
+ i: number;
154
+ j: number;
155
+ } | {
156
+ type: 'CLEAR_ERRORS';
157
+ };
158
+ interface AriaPropsOptions {
159
+ required?: boolean;
160
+ errorId?: string;
161
+ }
162
+ interface AriaProps {
163
+ 'aria-invalid': 'true' | 'false';
164
+ 'aria-describedby': string | undefined;
165
+ 'aria-required': true | undefined;
166
+ }
167
+ interface FormConfig<T extends object> {
96
168
  initialValues: T;
97
- rules?: Partial<Record<string, BuiltInRule | BuiltInRule[]>>;
169
+ rules?: Partial<Record<Path<T> | (string & {}), BuiltInRule | BuiltInRule[]>>;
98
170
  validator?: (values: T, scopePaths?: string[], signal?: AbortSignal) => Record<string, string> | Promise<Record<string, string>>;
99
171
  dependencies?: Record<string, string[]>;
100
172
  asyncDebounceMs?: number;
173
+ /** Per-field validation trigger mode. Defaults to 'onTouched'. */
174
+ validationMode?: ValidationMode | ValidationModeConfig<T>;
101
175
  }
102
176
  interface ConnectOptions {
103
177
  persist?: boolean;
104
178
  format?: (val: string) => string;
179
+ validateOn?: ValidationMode;
105
180
  }
106
181
  interface FormInstance<T extends object> {
107
182
  subscribe: (fn: FormSubscriber<T>) => () => void;
108
- subscribeToPath: (path: Path<T> | string, fn: PathSubscriber) => () => void;
109
- get: (path: Path<T> | string | string[]) => any;
183
+ subscribeToPath<P extends Path<T>>(path: P, fn: PathSubscriber<GetPathValue<T, P>>): () => void;
184
+ subscribeToPath(path: string, fn: PathSubscriber): () => void;
185
+ get<P extends Path<T>>(path: P): GetPathValue<T, P>;
186
+ get(path: string | string[]): any;
110
187
  set: (path: Path<T> | string | string[], val: any, options?: {
111
188
  touch?: boolean;
112
189
  validate?: boolean;
@@ -117,6 +194,7 @@ interface FormInstance<T extends object> {
117
194
  handleSubmit: (onValid: (payload: Partial<T>) => void | Promise<void>, onInvalid?: (errors: Record<string, string>) => void) => (e?: Event) => void;
118
195
  getState: () => FormState<T>;
119
196
  getPayload: () => Partial<T>;
197
+ getAriaProps: (path: Path<T> | string, options?: AriaPropsOptions) => AriaProps;
120
198
  batch: (fn: () => void) => void;
121
199
  arrayAppend: (path: Path<T> | string | string[], item: any) => void;
122
200
  arrayInsert: (path: Path<T> | string | string[], index: number, item: any) => void;
@@ -126,6 +204,15 @@ interface FormInstance<T extends object> {
126
204
  reset: (newValues?: T) => void;
127
205
  getConnectedCount: () => number;
128
206
  destroy: () => void;
207
+ setErrors: (errors: Record<Path<T> | (string & {}), string>) => void;
208
+ clearErrors: () => void;
209
+ /**
210
+ * Returns the effective ValidationMode for a field. Useful for debugging
211
+ * validation timing; framework adapters should rely on this only in custom
212
+ * event handlers, not in render logic.
213
+ */
214
+ getFieldMode: (path: string) => ValidationMode;
215
+ _subscribeToActions: (fn: (action: FormAction, state: FormState<T>) => void) => () => void;
129
216
  }
130
217
  declare function zodAdapter<T>(schema: {
131
218
  safeParse: (values: T) => any;
@@ -158,4 +245,4 @@ declare function extractAllPaths(obj: any, prefix?: string): string[];
158
245
  declare function compileDependencyScopes(dependencies: Record<string, string[]>, initialValues: any): Record<string, string[]>;
159
246
  declare function createForm<T extends object>(config: FormConfig<T>): FormInstance<T>;
160
247
 
161
- export { type BuiltInRule, type ConnectOptions, type DeepPartial, type FormConfig, type FormInstance, type FormState, type FormSubscriber, type GetPathValue, type Path, type PathImpl, type PathSubscriber, type Primitive, classValidatorAdapter, compileDependencyScopes, createForm, deepClone, extractAllPaths, getNestedValue, isDeepEqual, setNestedValue, valibotAdapter, yupAdapter, zodAdapter };
248
+ export { type AriaProps, type AriaPropsOptions, type BuiltInRule, type ConnectOptions, type DeepPartial, type FormAction, type FormConfig, type FormInstance, type FormState, type FormSubscriber, type GetPathValue, type Path, type PathImpl, type PathSubscriber, type Primitive, type ValidationMode, type ValidationModeConfig, classValidatorAdapter, compileDependencyScopes, createForm, deepClone, extractAllPaths, getNestedValue, isDeepEqual, setNestedValue, valibotAdapter, yupAdapter, zodAdapter };
package/dist/core.d.ts CHANGED
@@ -10,7 +10,7 @@ type _DeepPartialObject<T> = {
10
10
  [P in keyof T]?: DeepPartial<T[P]>;
11
11
  };
12
12
  type Prev = [never, 0, 1, 2, 3, 4, 5, ...any[]];
13
- type PathImpl<T, K extends keyof T, Depth extends number = 5> = [Depth] extends [never] ? never : K extends string ? T[K] extends Primitive ? K : T[K] extends Array<infer U> ? K | `${K}.${number}` | (U extends object ? `${K}.${number}.${PathImpl<U, keyof U, Prev[Depth]>}` : never) : T[K] extends object ? K | `${K}.${PathImpl<T[K], keyof T[K], Prev[Depth]>}` : K : never;
13
+ type PathImpl<T, K extends keyof T, Depth extends number = 5> = [Depth] extends [never] ? never : K extends string ? T[K] extends Primitive ? K : T[K] extends Array<infer U> ? K | `${K}.${number}` | (U extends object ? `${K}.${number}.${PathImpl<U, keyof U, Prev[Depth]>}` : never) : NonNullable<T[K]> extends object ? K | `${K}.${PathImpl<NonNullable<T[K]>, keyof NonNullable<T[K]>, Prev[Depth]>}` : K : never;
14
14
  type Path<T> = PathImpl<T, keyof T> & string;
15
15
  type _GetPathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? _GetPathValue<NonNullable<T[K]>, Rest> : T extends readonly any[] ? _GetPathValue<NonNullable<T[number]>, Rest> : unknown : P extends keyof T ? T[P] : T extends readonly any[] ? T[number] : unknown;
16
16
  type GetPathValue<T, P extends string> = _GetPathValue<T, P>;
@@ -23,7 +23,7 @@ interface FormState<T> {
23
23
  isValidating: boolean;
24
24
  }
25
25
  type FormSubscriber<T> = (state: FormState<T>) => void;
26
- type PathSubscriber = (value: any, fieldState: {
26
+ type PathSubscriber<V = any> = (value: V, fieldState: {
27
27
  error?: string;
28
28
  touched?: boolean;
29
29
  dirty?: boolean;
@@ -92,21 +92,98 @@ type BuiltInRule = 'required' | 'accepted' | 'email' | 'url' | 'numeric' | 'inte
92
92
  requiredUnless: string;
93
93
  message?: string;
94
94
  };
95
- interface FormConfig<T> {
95
+ type ValidationMode = 'onChange' | 'onBlur' | 'onTouched' | 'onSubmitOnly';
96
+ interface ValidationModeConfig<T extends object> {
97
+ default?: ValidationMode;
98
+ fields?: Partial<Record<Path<T> | (string & {}), ValidationMode>>;
99
+ }
100
+ type FormAction = {
101
+ type: 'SET';
102
+ path: string;
103
+ value: unknown;
104
+ options?: {
105
+ touch?: boolean;
106
+ validate?: boolean;
107
+ };
108
+ } | {
109
+ type: 'VALIDATE';
110
+ paths?: string[];
111
+ } | {
112
+ type: 'SUBMIT';
113
+ } | {
114
+ type: 'RESET';
115
+ newValues?: unknown;
116
+ } | {
117
+ type: 'SET_ERRORS';
118
+ errors: Record<string, string>;
119
+ } | {
120
+ type: 'CONNECT';
121
+ path: string;
122
+ } | {
123
+ type: 'DISCONNECT';
124
+ path: string;
125
+ } | {
126
+ type: 'BLUR';
127
+ path: string;
128
+ } | {
129
+ type: 'BATCH_START';
130
+ } | {
131
+ type: 'BATCH_END';
132
+ } | {
133
+ type: 'ARRAY_APPEND';
134
+ path: string;
135
+ item: unknown;
136
+ } | {
137
+ type: 'ARRAY_INSERT';
138
+ path: string;
139
+ index: number;
140
+ item: unknown;
141
+ } | {
142
+ type: 'ARRAY_REMOVE';
143
+ path: string;
144
+ index: number;
145
+ } | {
146
+ type: 'ARRAY_MOVE';
147
+ path: string;
148
+ from: number;
149
+ to: number;
150
+ } | {
151
+ type: 'ARRAY_SWAP';
152
+ path: string;
153
+ i: number;
154
+ j: number;
155
+ } | {
156
+ type: 'CLEAR_ERRORS';
157
+ };
158
+ interface AriaPropsOptions {
159
+ required?: boolean;
160
+ errorId?: string;
161
+ }
162
+ interface AriaProps {
163
+ 'aria-invalid': 'true' | 'false';
164
+ 'aria-describedby': string | undefined;
165
+ 'aria-required': true | undefined;
166
+ }
167
+ interface FormConfig<T extends object> {
96
168
  initialValues: T;
97
- rules?: Partial<Record<string, BuiltInRule | BuiltInRule[]>>;
169
+ rules?: Partial<Record<Path<T> | (string & {}), BuiltInRule | BuiltInRule[]>>;
98
170
  validator?: (values: T, scopePaths?: string[], signal?: AbortSignal) => Record<string, string> | Promise<Record<string, string>>;
99
171
  dependencies?: Record<string, string[]>;
100
172
  asyncDebounceMs?: number;
173
+ /** Per-field validation trigger mode. Defaults to 'onTouched'. */
174
+ validationMode?: ValidationMode | ValidationModeConfig<T>;
101
175
  }
102
176
  interface ConnectOptions {
103
177
  persist?: boolean;
104
178
  format?: (val: string) => string;
179
+ validateOn?: ValidationMode;
105
180
  }
106
181
  interface FormInstance<T extends object> {
107
182
  subscribe: (fn: FormSubscriber<T>) => () => void;
108
- subscribeToPath: (path: Path<T> | string, fn: PathSubscriber) => () => void;
109
- get: (path: Path<T> | string | string[]) => any;
183
+ subscribeToPath<P extends Path<T>>(path: P, fn: PathSubscriber<GetPathValue<T, P>>): () => void;
184
+ subscribeToPath(path: string, fn: PathSubscriber): () => void;
185
+ get<P extends Path<T>>(path: P): GetPathValue<T, P>;
186
+ get(path: string | string[]): any;
110
187
  set: (path: Path<T> | string | string[], val: any, options?: {
111
188
  touch?: boolean;
112
189
  validate?: boolean;
@@ -117,6 +194,7 @@ interface FormInstance<T extends object> {
117
194
  handleSubmit: (onValid: (payload: Partial<T>) => void | Promise<void>, onInvalid?: (errors: Record<string, string>) => void) => (e?: Event) => void;
118
195
  getState: () => FormState<T>;
119
196
  getPayload: () => Partial<T>;
197
+ getAriaProps: (path: Path<T> | string, options?: AriaPropsOptions) => AriaProps;
120
198
  batch: (fn: () => void) => void;
121
199
  arrayAppend: (path: Path<T> | string | string[], item: any) => void;
122
200
  arrayInsert: (path: Path<T> | string | string[], index: number, item: any) => void;
@@ -126,6 +204,15 @@ interface FormInstance<T extends object> {
126
204
  reset: (newValues?: T) => void;
127
205
  getConnectedCount: () => number;
128
206
  destroy: () => void;
207
+ setErrors: (errors: Record<Path<T> | (string & {}), string>) => void;
208
+ clearErrors: () => void;
209
+ /**
210
+ * Returns the effective ValidationMode for a field. Useful for debugging
211
+ * validation timing; framework adapters should rely on this only in custom
212
+ * event handlers, not in render logic.
213
+ */
214
+ getFieldMode: (path: string) => ValidationMode;
215
+ _subscribeToActions: (fn: (action: FormAction, state: FormState<T>) => void) => () => void;
129
216
  }
130
217
  declare function zodAdapter<T>(schema: {
131
218
  safeParse: (values: T) => any;
@@ -158,4 +245,4 @@ declare function extractAllPaths(obj: any, prefix?: string): string[];
158
245
  declare function compileDependencyScopes(dependencies: Record<string, string[]>, initialValues: any): Record<string, string[]>;
159
246
  declare function createForm<T extends object>(config: FormConfig<T>): FormInstance<T>;
160
247
 
161
- export { type BuiltInRule, type ConnectOptions, type DeepPartial, type FormConfig, type FormInstance, type FormState, type FormSubscriber, type GetPathValue, type Path, type PathImpl, type PathSubscriber, type Primitive, classValidatorAdapter, compileDependencyScopes, createForm, deepClone, extractAllPaths, getNestedValue, isDeepEqual, setNestedValue, valibotAdapter, yupAdapter, zodAdapter };
248
+ export { type AriaProps, type AriaPropsOptions, type BuiltInRule, type ConnectOptions, type DeepPartial, type FormAction, type FormConfig, type FormInstance, type FormState, type FormSubscriber, type GetPathValue, type Path, type PathImpl, type PathSubscriber, type Primitive, type ValidationMode, type ValidationModeConfig, classValidatorAdapter, compileDependencyScopes, createForm, deepClone, extractAllPaths, getNestedValue, isDeepEqual, setNestedValue, valibotAdapter, yupAdapter, zodAdapter };