@tanstack/form-core
Advanced tools
Comparing version 0.11.0 to 0.12.0
{ | ||
"name": "@tanstack/form-core", | ||
"version": "0.11.0", | ||
"version": "0.12.0", | ||
"description": "Powerful, type-safe, framework agnostic forms.", | ||
@@ -14,14 +14,14 @@ "author": "tannerlinsley", | ||
"type": "module", | ||
"types": "build/legacy/index.d.ts", | ||
"main": "build/legacy/index.cjs", | ||
"module": "build/legacy/index.js", | ||
"types": "dist/mjs/index.d.mts", | ||
"main": "dist/cjs/index.cjs", | ||
"module": "dist/mjs/index.mjs", | ||
"exports": { | ||
".": { | ||
"import": { | ||
"types": "./build/modern/index.d.ts", | ||
"default": "./build/modern/index.js" | ||
"types": "./dist/mjs/index.d.mts", | ||
"default": "./dist/mjs/index.mjs" | ||
}, | ||
"require": { | ||
"types": "./build/modern/index.d.cts", | ||
"default": "./build/modern/index.cjs" | ||
"types": "./dist/cjs/index.d.cts", | ||
"default": "./dist/cjs/index.cjs" | ||
} | ||
@@ -33,14 +33,5 @@ }, | ||
"files": [ | ||
"build", | ||
"dist", | ||
"src" | ||
], | ||
"nx": { | ||
"targets": { | ||
"test:build": { | ||
"dependsOn": [ | ||
"build" | ||
] | ||
} | ||
} | ||
}, | ||
"dependencies": { | ||
@@ -50,10 +41,14 @@ "@tanstack/store": "0.1.3" | ||
"scripts": { | ||
"clean": "rimraf ./build && rimraf ./coverage", | ||
"clean": "rimraf ./dist && rimraf ./coverage", | ||
"test:eslint": "eslint --ext .ts,.tsx ./src", | ||
"test:types": "tsc --noEmit && vitest typecheck", | ||
"test:types:versions49": "../../node_modules/typescript49/bin/tsc --noEmit", | ||
"test:types:versions50": "../../node_modules/typescript50/bin/tsc --noEmit", | ||
"test:types:versions51": "../../node_modules/typescript51/bin/tsc --noEmit", | ||
"test:types:versions52": "tsc --noEmit", | ||
"test:types": "pnpm run \"/^test:types:versions.*/\" && vitest typecheck", | ||
"test:lib": "vitest run --coverage", | ||
"test:lib:dev": "pnpm run test:lib --watch", | ||
"test:build": "publint --strict", | ||
"build": "tsup" | ||
"build": "vite build" | ||
} | ||
} |
@@ -48,12 +48,18 @@ import { Store } from '@tanstack/store' | ||
: TFormValidator extends Validator<TParentData, infer FFN> | ||
? | ||
| FFN | ||
| FieldValidateFn< | ||
TParentData, | ||
TName, | ||
TFieldValidator, | ||
TFormValidator, | ||
TData | ||
> | ||
: FieldValidateFn<TParentData, TName, TFieldValidator, TFormValidator, TData> | ||
? | ||
| FFN | ||
| FieldValidateFn< | ||
TParentData, | ||
TName, | ||
TFieldValidator, | ||
TFormValidator, | ||
TData | ||
> | ||
: FieldValidateFn< | ||
TParentData, | ||
TName, | ||
TFieldValidator, | ||
TFormValidator, | ||
TData | ||
> | ||
@@ -97,18 +103,18 @@ export type FieldValidateAsyncFn< | ||
: TFormValidator extends Validator<TParentData, infer FFN> | ||
? | ||
| FFN | ||
| FieldValidateAsyncFn< | ||
TParentData, | ||
TName, | ||
TFieldValidator, | ||
TFormValidator, | ||
TData | ||
> | ||
: FieldValidateAsyncFn< | ||
TParentData, | ||
TName, | ||
TFieldValidator, | ||
TFormValidator, | ||
TData | ||
> | ||
? | ||
| FFN | ||
| FieldValidateAsyncFn< | ||
TParentData, | ||
TName, | ||
TFieldValidator, | ||
TFormValidator, | ||
TData | ||
> | ||
: FieldValidateAsyncFn< | ||
TParentData, | ||
TName, | ||
TFieldValidator, | ||
TFormValidator, | ||
TData | ||
> | ||
@@ -575,4 +581,2 @@ export interface FieldValidators< | ||
fieldValidatorMeta?.lastAbortController.abort() | ||
// Sorry Safari 12 | ||
// eslint-disable-next-line compat/compat | ||
const controller = new AbortController() | ||
@@ -589,11 +593,19 @@ | ||
rawError = await new Promise((rawResolve, rawReject) => { | ||
setTimeout(() => { | ||
setTimeout(async () => { | ||
if (controller.signal.aborted) return rawResolve(undefined) | ||
this.runValidator({ | ||
validate: validateObj.validate, | ||
value: { value, fieldApi: this, signal: controller.signal }, | ||
type: 'validateAsync', | ||
}) | ||
.then(rawResolve) | ||
.catch(rawReject) | ||
try { | ||
rawResolve( | ||
await this.runValidator({ | ||
validate: validateObj.validate, | ||
value: { | ||
value, | ||
fieldApi: this, | ||
signal: controller.signal, | ||
}, | ||
type: 'validateAsync', | ||
}), | ||
) | ||
} catch (e) { | ||
rawReject(e) | ||
} | ||
}, validateObj.debounceMs) | ||
@@ -682,4 +694,2 @@ }) | ||
return 'onSubmit' | ||
case 'change': | ||
return 'onChange' | ||
case 'blur': | ||
@@ -689,3 +699,8 @@ return 'onBlur' | ||
return 'onMount' | ||
case 'server': | ||
return 'onServer' | ||
case 'change': | ||
default: | ||
return 'onChange' | ||
} | ||
} |
@@ -68,6 +68,16 @@ import { Store } from '@tanstack/store' | ||
export type FormOptions< | ||
export interface FormTransform< | ||
TFormData, | ||
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined, | ||
> = { | ||
> { | ||
fn: ( | ||
formBase: FormApi<TFormData, TFormValidator>, | ||
) => FormApi<TFormData, TFormValidator> | ||
deps: unknown[] | ||
} | ||
export interface FormOptions< | ||
TFormData, | ||
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined, | ||
> { | ||
defaultValues?: TFormData | ||
@@ -87,2 +97,3 @@ defaultState?: Partial<FormState<TFormData>> | ||
}) => void | ||
transform?: FormTransform<TFormData, TFormValidator> | ||
} | ||
@@ -156,2 +167,3 @@ | ||
onMount: undefined, | ||
onServer: undefined, | ||
}, | ||
@@ -174,2 +186,4 @@ } | ||
prevTransformArray: unknown[] = [] | ||
constructor(opts?: FormOptions<TFormData, TFormValidator>) { | ||
@@ -223,4 +237,17 @@ this.store = new Store<FormState<TFormData>>( | ||
this.store.state = state | ||
this.state = state | ||
this.store.state = this.state | ||
// Only run transform if state has shallowly changed - IE how React.useEffect works | ||
const transformArray = this.options.transform?.deps ?? [] | ||
const shouldTransform = | ||
transformArray.length !== this.prevTransformArray.length || | ||
transformArray.some((val, i) => val !== this.prevTransformArray[i]) | ||
if (shouldTransform) { | ||
// This mutates the state | ||
this.options.transform?.fn(this) | ||
this.store.state = this.state | ||
this.prevTransformArray = transformArray | ||
} | ||
}, | ||
@@ -275,10 +302,15 @@ }, | ||
const oldOptions = this.options | ||
// Options need to be updated first so that when the store is updated, the state is correct for the derived state | ||
this.options = options | ||
this.store.batch(() => { | ||
const shouldUpdateValues = | ||
options.defaultValues && | ||
options.defaultValues !== this.options.defaultValues && | ||
options.defaultValues !== oldOptions.defaultValues && | ||
!this.state.isTouched | ||
const shouldUpdateState = | ||
options.defaultState !== this.options.defaultState && | ||
options.defaultState !== oldOptions.defaultState && | ||
!this.state.isTouched | ||
@@ -303,4 +335,2 @@ | ||
}) | ||
this.options = options | ||
} | ||
@@ -418,4 +448,2 @@ | ||
fieldValidatorMeta?.lastAbortController.abort() | ||
// Sorry Safari 12 | ||
// eslint-disable-next-line compat/compat | ||
const controller = new AbortController() | ||
@@ -432,15 +460,19 @@ | ||
rawError = await new Promise((rawResolve, rawReject) => { | ||
setTimeout(() => { | ||
setTimeout(async () => { | ||
if (controller.signal.aborted) return rawResolve(undefined) | ||
this.runValidator({ | ||
validate: validateObj.validate!, | ||
value: { | ||
value: this.state.values, | ||
formApi: this, | ||
signal: controller.signal, | ||
}, | ||
type: 'validateAsync', | ||
}) | ||
.then(rawResolve) | ||
.catch(rawReject) | ||
try { | ||
rawResolve( | ||
await this.runValidator({ | ||
validate: validateObj.validate!, | ||
value: { | ||
value: this.state.values, | ||
formApi: this, | ||
signal: controller.signal, | ||
}, | ||
type: 'validateAsync', | ||
}), | ||
) | ||
} catch (e) { | ||
rawReject(e) | ||
} | ||
}, validateObj.debounceMs) | ||
@@ -575,2 +607,3 @@ }) | ||
onMount: undefined, | ||
onServer: undefined, | ||
}, | ||
@@ -707,4 +740,2 @@ }) | ||
return 'onSubmit' | ||
case 'change': | ||
return 'onChange' | ||
case 'blur': | ||
@@ -714,3 +745,8 @@ return 'onBlur' | ||
return 'onMount' | ||
case 'server': | ||
return 'onServer' | ||
case 'change': | ||
default: | ||
return 'onChange' | ||
} | ||
} |
@@ -5,1 +5,2 @@ export * from './FormApi' | ||
export * from './types' | ||
export * from './mergeForm' |
@@ -141,2 +141,3 @@ import { expect } from 'vitest' | ||
onMount: undefined, | ||
onServer: undefined, | ||
}, | ||
@@ -143,0 +144,0 @@ }) |
@@ -9,3 +9,4 @@ export type ValidationError = undefined | false | null | string | ||
export type ValidationCause = 'change' | 'blur' | 'submit' | 'mount' | ||
// "server" is only intended for SSR/SSG validation and should not execute anything | ||
export type ValidationCause = 'change' | 'blur' | 'submit' | 'mount' | 'server' | ||
@@ -12,0 +13,0 @@ export type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}` |
@@ -167,6 +167,8 @@ import type { ValidationCause, Validator } from './types' | ||
: T extends FormValidators<any, any> | ||
? Array< | ||
AsyncValidator<T['onChangeAsync'] | T['onBlurAsync'] | T['onSubmitAsync']> | ||
> | ||
: never { | ||
? Array< | ||
AsyncValidator< | ||
T['onChangeAsync'] | T['onBlurAsync'] | T['onSubmitAsync'] | ||
> | ||
> | ||
: never { | ||
const { asyncDebounceMs } = options | ||
@@ -207,2 +209,4 @@ const { | ||
return [changeValidator, blurValidator, submitValidator] as never | ||
case 'server': | ||
return [] as never | ||
case 'blur': | ||
@@ -231,4 +235,4 @@ return [blurValidator] as never | ||
: T extends FormValidators<any, any> | ||
? Array<SyncValidator<T['onChange'] | T['onBlur'] | T['onSubmit']>> | ||
: never { | ||
? Array<SyncValidator<T['onChange'] | T['onBlur'] | T['onSubmit']>> | ||
: never { | ||
const { onChange, onBlur, onSubmit } = (options.validators || {}) as | ||
@@ -242,10 +246,23 @@ | FieldValidators<any, any> | ||
// Allows us to clear onServer errors | ||
const serverValidator = { | ||
cause: 'server', | ||
validate: () => undefined, | ||
} as const | ||
switch (cause) { | ||
case 'submit': | ||
return [changeValidator, blurValidator, submitValidator] as never | ||
return [ | ||
changeValidator, | ||
blurValidator, | ||
submitValidator, | ||
serverValidator, | ||
] as never | ||
case 'server': | ||
return [serverValidator] as never | ||
case 'blur': | ||
return [blurValidator] as never | ||
return [blurValidator, serverValidator] as never | ||
case 'change': | ||
default: | ||
return [changeValidator] as never | ||
return [changeValidator, serverValidator] as never | ||
} | ||
@@ -279,4 +296,4 @@ } | ||
: Tuple extends readonly [infer _, ...infer Tail] | ||
? AllowedIndexes<Tail, Keys | Tail['length']> | ||
: Keys | ||
? AllowedIndexes<Tail, Keys | Tail['length']> | ||
: Keys | ||
@@ -286,14 +303,14 @@ export type DeepKeys<T, TDepth extends any[] = []> = TDepth['length'] extends 5 | ||
: unknown extends T | ||
? string | ||
: object extends T | ||
? string | ||
: T extends readonly any[] & IsTuple<T> | ||
? AllowedIndexes<T> | DeepKeysPrefix<T, AllowedIndexes<T>, TDepth> | ||
: T extends any[] | ||
? DeepKeys<T[number], [...TDepth, any]> | ||
: T extends Date | ||
? never | ||
: T extends object | ||
? (keyof T & string) | DeepKeysPrefix<T, keyof T, TDepth> | ||
: never | ||
? string | ||
: object extends T | ||
? string | ||
: T extends readonly any[] & IsTuple<T> | ||
? AllowedIndexes<T> | DeepKeysPrefix<T, AllowedIndexes<T>, TDepth> | ||
: T extends any[] | ||
? DeepKeys<T[number], [...TDepth, any]> | ||
: T extends Date | ||
? never | ||
: T extends object | ||
? (keyof T & string) | DeepKeysPrefix<T, keyof T, TDepth> | ||
: never | ||
@@ -300,0 +317,0 @@ type DeepKeysPrefix< |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
7559
397277
46
1