@tanstack/form-core 0.0.9 → 0.0.12
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/build/lib/FieldApi.cjs +307 -0
- package/build/lib/FieldApi.cjs.map +1 -0
- package/build/lib/FieldApi.d.ts +112 -0
- package/build/lib/FieldApi.d.ts.map +1 -0
- package/build/lib/FieldApi.js +305 -0
- package/build/lib/FieldApi.js.map +1 -0
- package/build/lib/FieldApi.legacy.cjs +307 -0
- package/build/lib/FieldApi.legacy.cjs.map +1 -0
- package/build/lib/FieldApi.legacy.js +305 -0
- package/build/lib/FieldApi.legacy.js.map +1 -0
- package/build/lib/FormApi.cjs +248 -0
- package/build/lib/FormApi.cjs.map +1 -0
- package/build/{types → lib}/FormApi.d.ts +16 -11
- package/build/lib/FormApi.d.ts.map +1 -0
- package/build/lib/FormApi.js +246 -0
- package/build/lib/FormApi.js.map +1 -0
- package/build/lib/FormApi.legacy.cjs +248 -0
- package/build/lib/FormApi.legacy.cjs.map +1 -0
- package/build/lib/FormApi.legacy.js +246 -0
- package/build/lib/FormApi.legacy.js.map +1 -0
- package/build/lib/_virtual/_rollupPluginBabelHelpers.cjs +65 -0
- package/build/lib/_virtual/_rollupPluginBabelHelpers.cjs.map +1 -0
- package/build/lib/_virtual/_rollupPluginBabelHelpers.js +56 -0
- package/build/{cjs → lib}/_virtual/_rollupPluginBabelHelpers.js.map +1 -1
- package/build/lib/_virtual/_rollupPluginBabelHelpers.legacy.cjs +65 -0
- package/build/lib/_virtual/_rollupPluginBabelHelpers.legacy.cjs.map +1 -0
- package/build/lib/_virtual/_rollupPluginBabelHelpers.legacy.js +56 -0
- package/build/lib/_virtual/_rollupPluginBabelHelpers.legacy.js.map +1 -0
- package/build/lib/index.cjs +14 -0
- package/build/lib/index.cjs.map +1 -0
- package/build/{types → lib}/index.d.ts +1 -0
- package/build/lib/index.d.ts.map +1 -0
- package/build/lib/index.js +4 -0
- package/build/{cjs → lib}/index.js.map +1 -1
- package/build/lib/index.legacy.cjs +14 -0
- package/build/lib/index.legacy.cjs.map +1 -0
- package/build/lib/index.legacy.js +4 -0
- package/build/lib/index.legacy.js.map +1 -0
- package/build/lib/tests/FieldApi.spec.d.ts +2 -0
- package/build/lib/tests/FieldApi.spec.d.ts.map +1 -0
- package/build/lib/tests/FieldApi.test-d.d.ts +2 -0
- package/build/lib/tests/FieldApi.test-d.d.ts.map +1 -0
- package/build/lib/tests/FormApi.spec.d.ts +2 -0
- package/build/lib/tests/FormApi.spec.d.ts.map +1 -0
- package/build/{cjs/utils.js → lib/utils.cjs} +18 -27
- package/build/lib/utils.cjs.map +1 -0
- package/build/{types → lib}/utils.d.ts +10 -0
- package/build/lib/utils.d.ts.map +1 -0
- package/build/lib/utils.js +77 -0
- package/build/lib/utils.js.map +1 -0
- package/build/lib/utils.legacy.cjs +81 -0
- package/build/lib/utils.legacy.cjs.map +1 -0
- package/build/lib/utils.legacy.js +77 -0
- package/build/lib/utils.legacy.js.map +1 -0
- package/package.json +23 -10
- package/src/FieldApi.ts +190 -138
- package/src/FormApi.ts +84 -118
- package/src/tests/FieldApi.spec.ts +143 -0
- package/src/tests/FieldApi.test-d.ts +41 -0
- package/src/tests/FormApi.spec.ts +216 -0
- package/src/utils.ts +10 -1
- package/build/cjs/FieldApi.js +0 -345
- package/build/cjs/FieldApi.js.map +0 -1
- package/build/cjs/FormApi.js +0 -317
- package/build/cjs/FormApi.js.map +0 -1
- package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +0 -31
- package/build/cjs/index.js +0 -26
- package/build/cjs/utils.js.map +0 -1
- package/build/esm/index.js +0 -724
- package/build/esm/index.js.map +0 -1
- package/build/stats-html.html +0 -2689
- package/build/stats-react.json +0 -190
- package/build/types/FieldApi.d.ts +0 -79
- package/build/types/tests/test.test.d.ts +0 -0
- package/build/umd/index.development.js +0 -785
- package/build/umd/index.development.js.map +0 -1
- package/build/umd/index.production.js +0 -22
- package/build/umd/index.production.js.map +0 -1
- package/src/tests/test.test.tsx +0 -5
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
function functionalUpdate(updater, input) {
|
|
2
|
+
return typeof updater === 'function' ? updater(input) : updater;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get a value from an object using a path, including dot notation.
|
|
7
|
+
*/
|
|
8
|
+
function getBy(obj, path) {
|
|
9
|
+
const pathArray = makePathArray(path);
|
|
10
|
+
const pathObj = pathArray;
|
|
11
|
+
return pathObj.reduce((current, pathPart) => {
|
|
12
|
+
if (typeof current !== 'undefined') {
|
|
13
|
+
return current[pathPart];
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}, obj);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Set a value on an object using a path, including dot notation.
|
|
21
|
+
*/
|
|
22
|
+
function setBy(obj, _path, updater) {
|
|
23
|
+
const path = makePathArray(_path);
|
|
24
|
+
function doSet(parent) {
|
|
25
|
+
if (!path.length) {
|
|
26
|
+
return functionalUpdate(updater, parent);
|
|
27
|
+
}
|
|
28
|
+
const key = path.shift();
|
|
29
|
+
if (typeof key === 'string') {
|
|
30
|
+
if (typeof parent === 'object') {
|
|
31
|
+
return {
|
|
32
|
+
...parent,
|
|
33
|
+
[key]: doSet(parent[key])
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
[key]: doSet()
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (typeof key === 'number') {
|
|
41
|
+
if (Array.isArray(parent)) {
|
|
42
|
+
const prefix = parent.slice(0, key);
|
|
43
|
+
return [...(prefix.length ? prefix : new Array(key)), doSet(parent[key]), ...parent.slice(key + 1)];
|
|
44
|
+
}
|
|
45
|
+
return [...new Array(key), doSet()];
|
|
46
|
+
}
|
|
47
|
+
throw new Error('Uh oh!');
|
|
48
|
+
}
|
|
49
|
+
return doSet(obj);
|
|
50
|
+
}
|
|
51
|
+
const reFindNumbers0 = /^(\d*)$/gm;
|
|
52
|
+
const reFindNumbers1 = /\.(\d*)\./gm;
|
|
53
|
+
const reFindNumbers2 = /^(\d*)\./gm;
|
|
54
|
+
const reFindNumbers3 = /\.(\d*$)/gm;
|
|
55
|
+
const reFindMultiplePeriods = /\.{2,}/gm;
|
|
56
|
+
const intPrefix = '__int__';
|
|
57
|
+
const intReplace = intPrefix + "$1";
|
|
58
|
+
function makePathArray(str) {
|
|
59
|
+
if (typeof str !== 'string') {
|
|
60
|
+
throw new Error('Path must be a string.');
|
|
61
|
+
}
|
|
62
|
+
return str.replace('[', '.').replace(']', '').replace(reFindNumbers0, intReplace).replace(reFindNumbers1, "." + intReplace + ".").replace(reFindNumbers2, intReplace + ".").replace(reFindNumbers3, "." + intReplace).replace(reFindMultiplePeriods, '.').split('.').map(d => {
|
|
63
|
+
if (d.indexOf(intPrefix) === 0) {
|
|
64
|
+
return parseInt(d.substring(intPrefix.length), 10);
|
|
65
|
+
}
|
|
66
|
+
return d;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Is this type a tuple?
|
|
71
|
+
|
|
72
|
+
// If this type is a tuple, what indices are allowed?
|
|
73
|
+
|
|
74
|
+
// Hack to get TypeScript to show simplified types in error messages
|
|
75
|
+
|
|
76
|
+
export { functionalUpdate, getBy, setBy };
|
|
77
|
+
//# sourceMappingURL=utils.legacy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.legacy.js","sources":["../../src/utils.ts"],"sourcesContent":["export type UpdaterFn<TInput, TOutput = TInput> = (input: TInput) => TOutput\n\nexport type Updater<TInput, TOutput = TInput> =\n | TOutput\n | UpdaterFn<TInput, TOutput>\n\nexport function functionalUpdate<TInput, TOutput = TInput>(\n updater: Updater<TInput, TOutput>,\n input: TInput,\n): TOutput {\n return typeof updater === 'function'\n ? (updater as UpdaterFn<TInput, TOutput>)(input)\n : updater\n}\n\n/**\n * Get a value from an object using a path, including dot notation.\n */\nexport function getBy(obj: any, path: any) {\n const pathArray = makePathArray(path)\n const pathObj = pathArray\n return pathObj.reduce((current: any, pathPart: any) => {\n if (typeof current !== 'undefined') {\n return current[pathPart]\n }\n return undefined\n }, obj)\n}\n\n/**\n * Set a value on an object using a path, including dot notation.\n */\nexport function setBy(obj: any, _path: any, updater: Updater<any>) {\n const path = makePathArray(_path)\n\n function doSet(parent?: any): any {\n if (!path.length) {\n return functionalUpdate(updater, parent)\n }\n\n const key = path.shift()\n\n if (typeof key === 'string') {\n if (typeof parent === 'object') {\n return {\n ...parent,\n [key]: doSet(parent[key]),\n }\n }\n return {\n [key]: doSet(),\n }\n }\n\n if (typeof key === 'number') {\n if (Array.isArray(parent)) {\n const prefix = parent.slice(0, key)\n return [\n ...(prefix.length ? prefix : new Array(key)),\n doSet(parent[key]),\n ...parent.slice(key + 1),\n ]\n }\n return [...new Array(key), doSet()]\n }\n\n throw new Error('Uh oh!')\n }\n\n return doSet(obj)\n}\n\nconst reFindNumbers0 = /^(\\d*)$/gm\nconst reFindNumbers1 = /\\.(\\d*)\\./gm\nconst reFindNumbers2 = /^(\\d*)\\./gm\nconst reFindNumbers3 = /\\.(\\d*$)/gm\nconst reFindMultiplePeriods = /\\.{2,}/gm\n\nconst intPrefix = '__int__'\nconst intReplace = `${intPrefix}$1`\n\nfunction makePathArray(str: string) {\n if (typeof str !== 'string') {\n throw new Error('Path must be a string.')\n }\n\n return str\n .replace('[', '.')\n .replace(']', '')\n .replace(reFindNumbers0, intReplace)\n .replace(reFindNumbers1, `.${intReplace}.`)\n .replace(reFindNumbers2, `${intReplace}.`)\n .replace(reFindNumbers3, `.${intReplace}`)\n .replace(reFindMultiplePeriods, '.')\n .split('.')\n .map((d) => {\n if (d.indexOf(intPrefix) === 0) {\n return parseInt(d.substring(intPrefix.length), 10)\n }\n return d\n })\n}\n\nexport type RequiredByKey<T, K extends keyof T> = Omit<T, K> &\n Required<Pick<T, K>>\n\ntype ComputeRange<\n N extends number,\n Result extends Array<unknown> = [],\n> = Result['length'] extends N\n ? Result\n : ComputeRange<N, [...Result, Result['length']]>\ntype Index40 = ComputeRange<40>[number]\n\n// Is this type a tuple?\ntype IsTuple<T> = T extends readonly any[] & { length: infer Length }\n ? Length extends Index40\n ? T\n : never\n : never\n\n// If this type is a tuple, what indices are allowed?\ntype AllowedIndexes<\n Tuple extends ReadonlyArray<any>,\n Keys extends number = never,\n> = Tuple extends readonly []\n ? Keys\n : Tuple extends readonly [infer _, ...infer Tail]\n ? AllowedIndexes<Tail, Keys | Tail['length']>\n : Keys\n\nexport type DeepKeys<T> = unknown extends T\n ? keyof T\n : object extends T\n ? string\n : T extends readonly any[] & IsTuple<T>\n ? AllowedIndexes<T> | DeepKeysPrefix<T, AllowedIndexes<T>>\n : T extends any[]\n ? DeepKeys<T[number]>\n : T extends Date\n ? never\n : T extends object\n ? (keyof T & string) | DeepKeysPrefix<T, keyof T>\n : never\n\ntype DeepKeysPrefix<T, TPrefix> = TPrefix extends keyof T & (number | string)\n ? `${TPrefix}.${DeepKeys<T[TPrefix]> & string}`\n : never\n\nexport type DeepValue<T, TProp> = T extends Record<string | number, any>\n ? TProp extends `${infer TBranch}.${infer TDeepProp}`\n ? DeepValue<T[TBranch], TDeepProp>\n : T[TProp & string]\n : never\n\ntype Narrowable = string | number | bigint | boolean\n\ntype NarrowRaw<A> =\n | (A extends [] ? [] : never)\n | (A extends Narrowable ? A : never)\n | {\n [K in keyof A]: A[K] extends Function ? A[K] : NarrowRaw<A[K]>\n }\n\nexport type Narrow<A> = Try<A, [], NarrowRaw<A>>\n\ntype Try<A1, A2, Catch = never> = A1 extends A2 ? A1 : Catch\n\n// Hack to get TypeScript to show simplified types in error messages\nexport type Pretty<T> = { [K in keyof T]: T[K] } & {}\n"],"names":["functionalUpdate","updater","input","getBy","obj","path","pathArray","makePathArray","pathObj","reduce","current","pathPart","undefined","setBy","_path","doSet","parent","length","key","shift","Array","isArray","prefix","slice","Error","reFindNumbers0","reFindNumbers1","reFindNumbers2","reFindNumbers3","reFindMultiplePeriods","intPrefix","intReplace","str","replace","split","map","d","indexOf","parseInt","substring"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/form-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"description": "Powerful, type-safe, framework agnostic forms.",
|
|
5
5
|
"author": "tannerlinsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,23 +10,36 @@
|
|
|
10
10
|
"type": "github",
|
|
11
11
|
"url": "https://github.com/sponsors/tannerlinsley"
|
|
12
12
|
},
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
13
|
+
"type": "module",
|
|
14
|
+
"types": "build/lib/index.d.ts",
|
|
15
|
+
"main": "build/lib/index.legacy.cjs",
|
|
16
|
+
"module": "build/lib/index.legacy.js",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./build/lib/index.d.ts",
|
|
20
|
+
"import": "./build/lib/index.js",
|
|
21
|
+
"require": "./build/lib/index.cjs",
|
|
22
|
+
"default": "./build/lib/index.cjs"
|
|
23
|
+
},
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
16
26
|
"sideEffects": false,
|
|
17
27
|
"files": [
|
|
18
|
-
"build
|
|
28
|
+
"build/lib/*",
|
|
19
29
|
"src"
|
|
20
30
|
],
|
|
21
31
|
"dependencies": {
|
|
22
|
-
"@tanstack/store": "0.0.1-beta.
|
|
32
|
+
"@tanstack/store": "0.0.1-beta.89"
|
|
23
33
|
},
|
|
24
34
|
"scripts": {
|
|
25
|
-
"clean": "rimraf ./build",
|
|
35
|
+
"clean": "rimraf ./build && rimraf ./coverage",
|
|
26
36
|
"test:eslint": "eslint --ext .ts,.tsx ./src",
|
|
27
|
-
"test:types": "tsc",
|
|
28
|
-
"test:lib": "
|
|
37
|
+
"test:types": "tsc --noEmit && vitest typecheck",
|
|
38
|
+
"test:lib": "vitest run --coverage",
|
|
29
39
|
"test:lib:dev": "pnpm run test:lib --watch",
|
|
30
|
-
"build
|
|
40
|
+
"test:build": "publint --strict",
|
|
41
|
+
"build": "pnpm build:rollup && pnpm build:types",
|
|
42
|
+
"build:rollup": "rollup --config rollup.config.js",
|
|
43
|
+
"build:types": "tsc --emitDeclarationOnly"
|
|
31
44
|
}
|
|
32
45
|
}
|
package/src/FieldApi.ts
CHANGED
|
@@ -1,26 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
import type { DeepKeys, DeepValue, RequiredByKey, Updater } from './utils'
|
|
1
|
+
import type { DeepKeys, DeepValue, Updater } from './utils'
|
|
3
2
|
import type { FormApi, ValidationError } from './FormApi'
|
|
4
3
|
import { Store } from '@tanstack/store'
|
|
5
4
|
|
|
6
5
|
export type ValidationCause = 'change' | 'blur' | 'submit'
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
type ValidateFn<TData, TFormData> = (
|
|
8
|
+
value: TData,
|
|
9
|
+
fieldApi: FieldApi<TData, TFormData>,
|
|
10
|
+
) => ValidationError
|
|
11
|
+
|
|
12
|
+
type ValidateAsyncFn<TData, TFormData> = (
|
|
13
|
+
value: TData,
|
|
14
|
+
fieldApi: FieldApi<TData, TFormData>,
|
|
15
|
+
) => ValidationError | Promise<ValidationError>
|
|
16
|
+
|
|
17
|
+
export interface FieldOptions<
|
|
18
|
+
_TData,
|
|
19
|
+
TFormData,
|
|
20
|
+
/**
|
|
21
|
+
* This allows us to restrict the name to only be a valid field name while
|
|
22
|
+
* also assigning it to a generic
|
|
23
|
+
*/
|
|
24
|
+
TName = unknown extends TFormData ? string : DeepKeys<TFormData>,
|
|
25
|
+
/**
|
|
26
|
+
* If TData is unknown, we can use the TName generic to determine the type
|
|
27
|
+
*/
|
|
28
|
+
TData = unknown extends _TData ? DeepValue<TFormData, TName> : _TData,
|
|
29
|
+
> {
|
|
30
|
+
name: TName
|
|
10
31
|
index?: TData extends any[] ? number : never
|
|
11
32
|
defaultValue?: TData
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
validateAsyncOn?: ValidationCause // Default: 'blur'
|
|
23
|
-
validateAsyncDebounceMs?: number
|
|
33
|
+
asyncDebounceMs?: number
|
|
34
|
+
asyncAlways?: boolean
|
|
35
|
+
onMount?: (formApi: FieldApi<TData, TFormData>) => void
|
|
36
|
+
onChange?: ValidateFn<TData, TFormData>
|
|
37
|
+
onChangeAsync?: ValidateAsyncFn<TData, TFormData>
|
|
38
|
+
onChangeAsyncDebounceMs?: number
|
|
39
|
+
onBlur?: ValidateFn<TData, TFormData>
|
|
40
|
+
onBlurAsync?: ValidateAsyncFn<TData, TFormData>
|
|
41
|
+
onBlurAsyncDebounceMs?: number
|
|
42
|
+
onSubmitAsync?: ValidateAsyncFn<TData, TFormData>
|
|
24
43
|
defaultMeta?: Partial<FieldMeta>
|
|
25
44
|
}
|
|
26
45
|
|
|
@@ -50,12 +69,12 @@ export type UserInputProps = {
|
|
|
50
69
|
|
|
51
70
|
export type ChangeProps<TData> = {
|
|
52
71
|
value: TData
|
|
53
|
-
onChange: (
|
|
72
|
+
onChange: (value: TData) => void
|
|
54
73
|
onBlur: (event: any) => void
|
|
55
74
|
}
|
|
56
75
|
|
|
57
|
-
export type InputProps = {
|
|
58
|
-
value:
|
|
76
|
+
export type InputProps<T> = {
|
|
77
|
+
value: T
|
|
59
78
|
onChange: (event: any) => void
|
|
60
79
|
onBlur: (event: any) => void
|
|
61
80
|
}
|
|
@@ -67,19 +86,33 @@ export type FieldState<TData> = {
|
|
|
67
86
|
meta: FieldMeta
|
|
68
87
|
}
|
|
69
88
|
|
|
89
|
+
/**
|
|
90
|
+
* TData may not be known at the time of FieldApi construction, so we need to
|
|
91
|
+
* use a conditional type to determine if TData is known or not.
|
|
92
|
+
*
|
|
93
|
+
* If TData is not known, we use the TFormData type to determine the type of
|
|
94
|
+
* the field value based on the field name.
|
|
95
|
+
*/
|
|
96
|
+
type GetTData<Name, TData, TFormData> = unknown extends TData
|
|
97
|
+
? DeepValue<TFormData, Name>
|
|
98
|
+
: TData
|
|
99
|
+
|
|
70
100
|
export class FieldApi<TData, TFormData> {
|
|
71
101
|
uid: number
|
|
72
102
|
form: FormApi<TFormData>
|
|
73
103
|
name!: DeepKeys<TFormData>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
104
|
+
/**
|
|
105
|
+
* This is a hack that allows us to use `GetTData` without calling it everywhere
|
|
106
|
+
*
|
|
107
|
+
* Unfortunately this hack appears to be needed alongside the `TName` hack
|
|
108
|
+
* further up in this file. This properly types all of the internal methods,
|
|
109
|
+
* while the `TName` hack types the options properly
|
|
110
|
+
*/
|
|
111
|
+
_tdata!: GetTData<typeof this.name, TData, TFormData>
|
|
112
|
+
store!: Store<FieldState<typeof this._tdata>>
|
|
113
|
+
state!: FieldState<typeof this._tdata>
|
|
114
|
+
prevState!: FieldState<typeof this._tdata>
|
|
115
|
+
options: FieldOptions<typeof this._tdata, TFormData> = {} as any
|
|
83
116
|
|
|
84
117
|
constructor(opts: FieldApiOptions<TData, TFormData>) {
|
|
85
118
|
this.form = opts.form
|
|
@@ -92,7 +125,7 @@ export class FieldApi<TData, TFormData> {
|
|
|
92
125
|
|
|
93
126
|
this.name = opts.name as any
|
|
94
127
|
|
|
95
|
-
this.store = new Store<FieldState<
|
|
128
|
+
this.store = new Store<FieldState<typeof this._tdata>>(
|
|
96
129
|
{
|
|
97
130
|
value: this.getValue(),
|
|
98
131
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
@@ -103,23 +136,22 @@ export class FieldApi<TData, TFormData> {
|
|
|
103
136
|
},
|
|
104
137
|
},
|
|
105
138
|
{
|
|
106
|
-
onUpdate: (
|
|
107
|
-
|
|
108
|
-
|
|
139
|
+
onUpdate: () => {
|
|
140
|
+
const state = this.store.state
|
|
141
|
+
|
|
142
|
+
state.meta.touchedError = state.meta.isTouched
|
|
143
|
+
? state.meta.error
|
|
109
144
|
: undefined
|
|
110
145
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.state = next
|
|
114
|
-
if (next.value !== prevState.value) {
|
|
115
|
-
this.validate('change', next.value)
|
|
116
|
-
}
|
|
146
|
+
this.prevState = state
|
|
147
|
+
this.state = state
|
|
117
148
|
},
|
|
118
149
|
},
|
|
119
150
|
)
|
|
120
151
|
|
|
121
152
|
this.state = this.store.state
|
|
122
|
-
this.
|
|
153
|
+
this.prevState = this.state
|
|
154
|
+
this.update(opts as never)
|
|
123
155
|
}
|
|
124
156
|
|
|
125
157
|
mount = () => {
|
|
@@ -127,9 +159,22 @@ export class FieldApi<TData, TFormData> {
|
|
|
127
159
|
info.instances[this.uid] = this
|
|
128
160
|
|
|
129
161
|
const unsubscribe = this.form.store.subscribe(() => {
|
|
130
|
-
this
|
|
162
|
+
this.store.batch(() => {
|
|
163
|
+
const nextValue = this.getValue()
|
|
164
|
+
const nextMeta = this.getMeta()
|
|
165
|
+
|
|
166
|
+
if (nextValue !== this.state.value) {
|
|
167
|
+
this.store.setState((prev) => ({ ...prev, value: nextValue }))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (nextMeta !== this.state.meta) {
|
|
171
|
+
this.store.setState((prev) => ({ ...prev, meta: nextMeta }))
|
|
172
|
+
}
|
|
173
|
+
})
|
|
131
174
|
})
|
|
132
175
|
|
|
176
|
+
this.options.onMount?.(this as never)
|
|
177
|
+
|
|
133
178
|
return () => {
|
|
134
179
|
unsubscribe()
|
|
135
180
|
delete info.instances[this.uid]
|
|
@@ -139,37 +184,30 @@ export class FieldApi<TData, TFormData> {
|
|
|
139
184
|
}
|
|
140
185
|
}
|
|
141
186
|
|
|
142
|
-
|
|
143
|
-
this.store.batch(() => {
|
|
144
|
-
const nextValue = this.getValue()
|
|
145
|
-
const nextMeta = this.getMeta()
|
|
146
|
-
|
|
147
|
-
if (nextValue !== this.state.value) {
|
|
148
|
-
this.store.setState((prev) => ({ ...prev, value: nextValue }))
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (nextMeta !== this.state.meta) {
|
|
152
|
-
this.store.setState((prev) => ({ ...prev, meta: nextMeta }))
|
|
153
|
-
}
|
|
154
|
-
})
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
update = (opts: FieldApiOptions<TData, TFormData>) => {
|
|
187
|
+
update = (opts: FieldApiOptions<typeof this._tdata, TFormData>) => {
|
|
158
188
|
this.options = {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
validateAsyncDebounceMs:
|
|
163
|
-
this.form.options.defaultValidateAsyncDebounceMs ?? 0,
|
|
189
|
+
asyncDebounceMs: this.form.options.asyncDebounceMs ?? 0,
|
|
190
|
+
onChangeAsyncDebounceMs: this.form.options.onChangeAsyncDebounceMs ?? 0,
|
|
191
|
+
onBlurAsyncDebounceMs: this.form.options.onBlurAsyncDebounceMs ?? 0,
|
|
164
192
|
...opts,
|
|
165
|
-
}
|
|
193
|
+
} as never
|
|
166
194
|
|
|
167
195
|
// Default Value
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
this.options.defaultValue !== undefined
|
|
171
|
-
|
|
172
|
-
|
|
196
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
197
|
+
if (this.state.value === undefined) {
|
|
198
|
+
if (this.options.defaultValue !== undefined) {
|
|
199
|
+
this.setValue(this.options.defaultValue as never)
|
|
200
|
+
} else if (
|
|
201
|
+
opts.form.options.defaultValues?.[
|
|
202
|
+
this.options.name as keyof TFormData
|
|
203
|
+
] !== undefined
|
|
204
|
+
) {
|
|
205
|
+
this.setValue(
|
|
206
|
+
opts.form.options.defaultValues[
|
|
207
|
+
this.options.name as keyof TFormData
|
|
208
|
+
] as never,
|
|
209
|
+
)
|
|
210
|
+
}
|
|
173
211
|
}
|
|
174
212
|
|
|
175
213
|
// Default Meta
|
|
@@ -179,46 +217,61 @@ export class FieldApi<TData, TFormData> {
|
|
|
179
217
|
}
|
|
180
218
|
}
|
|
181
219
|
|
|
182
|
-
getValue = ():
|
|
220
|
+
getValue = (): typeof this._tdata => {
|
|
183
221
|
return this.form.getFieldValue(this.name)
|
|
184
222
|
}
|
|
223
|
+
|
|
185
224
|
setValue = (
|
|
186
|
-
updater: Updater<
|
|
225
|
+
updater: Updater<typeof this._tdata>,
|
|
187
226
|
options?: { touch?: boolean; notify?: boolean },
|
|
188
|
-
) =>
|
|
227
|
+
) => {
|
|
228
|
+
this.form.setFieldValue(this.name, updater as never, options)
|
|
229
|
+
this.validate('change', this.state.value)
|
|
230
|
+
}
|
|
189
231
|
|
|
190
232
|
getMeta = (): FieldMeta => this.form.getFieldMeta(this.name)
|
|
233
|
+
|
|
191
234
|
setMeta = (updater: Updater<FieldMeta>) =>
|
|
192
235
|
this.form.setFieldMeta(this.name, updater)
|
|
193
236
|
|
|
194
237
|
getInfo = () => this.form.getFieldInfo(this.name)
|
|
195
238
|
|
|
196
|
-
pushValue = (
|
|
197
|
-
this.
|
|
198
|
-
|
|
199
|
-
|
|
239
|
+
pushValue = (
|
|
240
|
+
value: typeof this._tdata extends any[]
|
|
241
|
+
? (typeof this._tdata)[number]
|
|
242
|
+
: never,
|
|
243
|
+
) => this.form.pushFieldValue(this.name, value as any)
|
|
244
|
+
|
|
245
|
+
insertValue = (
|
|
246
|
+
index: number,
|
|
247
|
+
value: typeof this._tdata extends any[]
|
|
248
|
+
? (typeof this._tdata)[number]
|
|
249
|
+
: never,
|
|
250
|
+
) => this.form.insertFieldValue(this.name, index, value as any)
|
|
251
|
+
|
|
200
252
|
removeValue = (index: number) => this.form.removeFieldValue(this.name, index)
|
|
253
|
+
|
|
201
254
|
swapValues = (aIndex: number, bIndex: number) =>
|
|
202
255
|
this.form.swapFieldValues(this.name, aIndex, bIndex)
|
|
203
256
|
|
|
204
|
-
getSubField = <TName extends DeepKeys<
|
|
205
|
-
new FieldApi<DeepValue<
|
|
206
|
-
name: `${this.name}.${name}` as
|
|
257
|
+
getSubField = <TName extends DeepKeys<typeof this._tdata>>(name: TName) =>
|
|
258
|
+
new FieldApi<DeepValue<typeof this._tdata, TName>, TFormData>({
|
|
259
|
+
name: `${this.name}.${name}` as never,
|
|
207
260
|
form: this.form,
|
|
208
261
|
})
|
|
209
262
|
|
|
210
|
-
validateSync =
|
|
211
|
-
const {
|
|
263
|
+
validateSync = (value = this.state.value, cause: ValidationCause) => {
|
|
264
|
+
const { onChange, onBlur } = this.options
|
|
265
|
+
const validate =
|
|
266
|
+
cause === 'submit' ? undefined : cause === 'change' ? onChange : onBlur
|
|
212
267
|
|
|
213
|
-
if (!validate)
|
|
214
|
-
return
|
|
215
|
-
}
|
|
268
|
+
if (!validate) return
|
|
216
269
|
|
|
217
270
|
// Use the validationCount for all field instances to
|
|
218
271
|
// track freshness of the validation
|
|
219
272
|
const validationCount = (this.getInfo().validationCount || 0) + 1
|
|
220
273
|
this.getInfo().validationCount = validationCount
|
|
221
|
-
const error = normalizeError(validate(value, this))
|
|
274
|
+
const error = normalizeError(validate(value as never, this as never))
|
|
222
275
|
|
|
223
276
|
if (this.state.meta.error !== error) {
|
|
224
277
|
this.setMeta((prev) => ({
|
|
@@ -249,12 +302,33 @@ export class FieldApi<TData, TFormData> {
|
|
|
249
302
|
}))
|
|
250
303
|
}
|
|
251
304
|
|
|
252
|
-
validateAsync = async (value = this.state.value) => {
|
|
253
|
-
const {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
305
|
+
validateAsync = async (value = this.state.value, cause: ValidationCause) => {
|
|
306
|
+
const {
|
|
307
|
+
onChangeAsync,
|
|
308
|
+
onBlurAsync,
|
|
309
|
+
onSubmitAsync,
|
|
310
|
+
asyncDebounceMs,
|
|
311
|
+
onBlurAsyncDebounceMs,
|
|
312
|
+
onChangeAsyncDebounceMs,
|
|
313
|
+
} = this.options
|
|
314
|
+
|
|
315
|
+
const validate =
|
|
316
|
+
cause === 'change'
|
|
317
|
+
? onChangeAsync
|
|
318
|
+
: cause === 'submit'
|
|
319
|
+
? onSubmitAsync
|
|
320
|
+
: onBlurAsync
|
|
321
|
+
|
|
322
|
+
if (!validate) return
|
|
323
|
+
|
|
324
|
+
const debounceMs =
|
|
325
|
+
cause === 'submit'
|
|
326
|
+
? 0
|
|
327
|
+
: (cause === 'change'
|
|
328
|
+
? onChangeAsyncDebounceMs
|
|
329
|
+
: onBlurAsyncDebounceMs) ??
|
|
330
|
+
asyncDebounceMs ??
|
|
331
|
+
500
|
|
258
332
|
|
|
259
333
|
if (this.state.meta.isValidating !== true)
|
|
260
334
|
this.setMeta((prev) => ({ ...prev, isValidating: true }))
|
|
@@ -273,14 +347,14 @@ export class FieldApi<TData, TFormData> {
|
|
|
273
347
|
})
|
|
274
348
|
}
|
|
275
349
|
|
|
276
|
-
if (
|
|
277
|
-
await new Promise((r) => setTimeout(r,
|
|
350
|
+
if (debounceMs > 0) {
|
|
351
|
+
await new Promise((r) => setTimeout(r, debounceMs))
|
|
278
352
|
}
|
|
279
353
|
|
|
280
354
|
// Only kick off validation if this validation is the latest attempt
|
|
281
355
|
if (checkLatest()) {
|
|
282
356
|
try {
|
|
283
|
-
const rawError = await
|
|
357
|
+
const rawError = await validate(value as never, this as never)
|
|
284
358
|
|
|
285
359
|
if (checkLatest()) {
|
|
286
360
|
const error = normalizeError(rawError)
|
|
@@ -308,89 +382,67 @@ export class FieldApi<TData, TFormData> {
|
|
|
308
382
|
return this.getInfo().validationPromise
|
|
309
383
|
}
|
|
310
384
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
// Must meet *at least* the validation level to validate,
|
|
316
|
-
// e.g. if validateOn is 'change' and validateCause is 'blur',
|
|
317
|
-
// the field will still validate
|
|
318
|
-
return Object.keys(validateCauseLevels).some((d) =>
|
|
319
|
-
isAsync
|
|
320
|
-
? validateAsyncOn
|
|
321
|
-
: validateOn === d && level >= validateCauseLevels[d],
|
|
322
|
-
)
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
validate = async (
|
|
326
|
-
cause?: ValidationCause,
|
|
327
|
-
value?: TData,
|
|
328
|
-
): Promise<ValidationError> => {
|
|
385
|
+
validate = (
|
|
386
|
+
cause: ValidationCause,
|
|
387
|
+
value?: typeof this._tdata,
|
|
388
|
+
): ValidationError | Promise<ValidationError> => {
|
|
329
389
|
// If the field is pristine and validatePristine is false, do not validate
|
|
330
|
-
if (!this.
|
|
390
|
+
if (!this.state.meta.isTouched) return
|
|
331
391
|
|
|
332
392
|
// Attempt to sync validate first
|
|
333
|
-
|
|
334
|
-
this.validateSync(value)
|
|
335
|
-
}
|
|
393
|
+
this.validateSync(value, cause)
|
|
336
394
|
|
|
337
395
|
// If there is an error, return it, do not attempt async validation
|
|
338
396
|
if (this.state.meta.error) {
|
|
339
|
-
|
|
397
|
+
if (!this.options.asyncAlways) {
|
|
398
|
+
return this.state.meta.error
|
|
399
|
+
}
|
|
340
400
|
}
|
|
341
401
|
|
|
342
402
|
// No error? Attempt async validation
|
|
343
|
-
|
|
344
|
-
return this.validateAsync(value)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// If there is no sync error or async validation attempt, there is no error
|
|
348
|
-
return undefined
|
|
403
|
+
return this.validateAsync(value, cause)
|
|
349
404
|
}
|
|
350
405
|
|
|
351
406
|
getChangeProps = <T extends UserChangeProps<any>>(
|
|
352
407
|
props: T = {} as T,
|
|
353
|
-
): ChangeProps<
|
|
408
|
+
): ChangeProps<typeof this._tdata> &
|
|
409
|
+
Omit<T, keyof ChangeProps<typeof this._tdata>> => {
|
|
354
410
|
return {
|
|
355
411
|
...props,
|
|
356
412
|
value: this.state.value,
|
|
357
413
|
onChange: (value) => {
|
|
358
|
-
this.setValue(value)
|
|
414
|
+
this.setValue(value as never)
|
|
359
415
|
props.onChange?.(value)
|
|
360
416
|
},
|
|
361
417
|
onBlur: (e) => {
|
|
418
|
+
const prevTouched = this.state.meta.isTouched
|
|
362
419
|
this.setMeta((prev) => ({ ...prev, isTouched: true }))
|
|
420
|
+
if (!prevTouched) {
|
|
421
|
+
this.validate('change')
|
|
422
|
+
}
|
|
363
423
|
this.validate('blur')
|
|
364
|
-
props.onBlur?.(e)
|
|
365
424
|
},
|
|
366
|
-
} as ChangeProps<
|
|
425
|
+
} as ChangeProps<typeof this._tdata> &
|
|
426
|
+
Omit<T, keyof ChangeProps<typeof this._tdata>>
|
|
367
427
|
}
|
|
368
428
|
|
|
369
429
|
getInputProps = <T extends UserInputProps>(
|
|
370
430
|
props: T = {} as T,
|
|
371
|
-
): InputProps
|
|
431
|
+
): InputProps<typeof this._tdata> &
|
|
432
|
+
Omit<T, keyof InputProps<typeof this._tdata>> => {
|
|
372
433
|
return {
|
|
373
434
|
...props,
|
|
374
|
-
value:
|
|
435
|
+
value: this.state.value,
|
|
375
436
|
onChange: (e) => {
|
|
376
437
|
this.setValue(e.target.value)
|
|
377
438
|
props.onChange?.(e.target.value)
|
|
378
439
|
},
|
|
379
440
|
onBlur: this.getChangeProps(props).onBlur,
|
|
380
|
-
}
|
|
441
|
+
} as InputProps<typeof this._tdata> &
|
|
442
|
+
Omit<T, keyof InputProps<typeof this._tdata>>
|
|
381
443
|
}
|
|
382
444
|
}
|
|
383
445
|
|
|
384
|
-
const validateCauseLevels = {
|
|
385
|
-
change: 0,
|
|
386
|
-
blur: 1,
|
|
387
|
-
submit: 2,
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function getValidationCauseLevel(cause?: ValidationCause) {
|
|
391
|
-
return !cause ? 3 : validateCauseLevels[cause]
|
|
392
|
-
}
|
|
393
|
-
|
|
394
446
|
function normalizeError(rawError?: ValidationError) {
|
|
395
447
|
if (rawError) {
|
|
396
448
|
if (typeof rawError !== 'string') {
|