@tanstack/form-core
Advanced tools
Comparing version 0.0.11 to 0.0.12
{ | ||
"name": "@tanstack/form-core", | ||
"version": "0.0.11", | ||
"version": "0.0.12", | ||
"description": "Powerful, type-safe, framework agnostic forms.", | ||
@@ -13,8 +13,18 @@ "author": "tannerlinsley", | ||
}, | ||
"types": "build/types/index.d.ts", | ||
"main": "build/cjs/index.js", | ||
"module": "build/esm/index.js", | ||
"type": "module", | ||
"types": "build/lib/index.d.ts", | ||
"main": "build/lib/index.legacy.cjs", | ||
"module": "build/lib/index.legacy.js", | ||
"exports": { | ||
".": { | ||
"types": "./build/lib/index.d.ts", | ||
"import": "./build/lib/index.js", | ||
"require": "./build/lib/index.cjs", | ||
"default": "./build/lib/index.cjs" | ||
}, | ||
"./package.json": "./package.json" | ||
}, | ||
"sideEffects": false, | ||
"files": [ | ||
"build/**", | ||
"build/lib/*", | ||
"src" | ||
@@ -26,9 +36,12 @@ ], | ||
"scripts": { | ||
"clean": "rimraf ./build", | ||
"clean": "rimraf ./build && rimraf ./coverage", | ||
"test:eslint": "eslint --ext .ts,.tsx ./src", | ||
"test:types": "tsc", | ||
"test:lib": "jest --config ./jest.config.ts", | ||
"test:types": "tsc --noEmit && vitest typecheck", | ||
"test:lib": "vitest run --coverage", | ||
"test:lib:dev": "pnpm run test:lib --watch", | ||
"build:types": "tsc --build" | ||
"test:build": "publint --strict", | ||
"build": "pnpm build:rollup && pnpm build:types", | ||
"build:rollup": "rollup --config rollup.config.js", | ||
"build:types": "tsc --emitDeclarationOnly" | ||
} | ||
} |
@@ -1,3 +0,2 @@ | ||
// | ||
import type { DeepKeys, DeepValue, RequiredByKey, Updater } from './utils' | ||
import type { DeepKeys, DeepValue, Updater } from './utils' | ||
import type { FormApi, ValidationError } from './FormApi' | ||
@@ -18,4 +17,16 @@ import { Store } from '@tanstack/store' | ||
export interface FieldOptions<TData, TFormData> { | ||
name: unknown extends TFormData ? string : DeepKeys<TFormData> | ||
export interface FieldOptions< | ||
_TData, | ||
TFormData, | ||
/** | ||
* This allows us to restrict the name to only be a valid field name while | ||
* also assigning it to a generic | ||
*/ | ||
TName = unknown extends TFormData ? string : DeepKeys<TFormData>, | ||
/** | ||
* If TData is unknown, we can use the TName generic to determine the type | ||
*/ | ||
TData = unknown extends _TData ? DeepValue<TFormData, TName> : _TData, | ||
> { | ||
name: TName | ||
index?: TData extends any[] ? number : never | ||
@@ -66,4 +77,4 @@ defaultValue?: TData | ||
export type InputProps = { | ||
value: string | ||
export type InputProps<T> = { | ||
value: T | ||
onChange: (event: any) => void | ||
@@ -80,2 +91,13 @@ onBlur: (event: any) => void | ||
/** | ||
* TData may not be known at the time of FieldApi construction, so we need to | ||
* use a conditional type to determine if TData is known or not. | ||
* | ||
* If TData is not known, we use the TFormData type to determine the type of | ||
* the field value based on the field name. | ||
*/ | ||
type GetTData<Name, TData, TFormData> = unknown extends TData | ||
? DeepValue<TFormData, Name> | ||
: TData | ||
export class FieldApi<TData, TFormData> { | ||
@@ -85,6 +107,14 @@ uid: number | ||
name!: DeepKeys<TFormData> | ||
store!: Store<FieldState<TData>> | ||
state!: FieldState<TData> | ||
#prevState!: FieldState<TData> | ||
options: FieldOptions<TData, TFormData> = {} as any | ||
/** | ||
* This is a hack that allows us to use `GetTData` without calling it everywhere | ||
* | ||
* Unfortunately this hack appears to be needed alongside the `TName` hack | ||
* further up in this file. This properly types all of the internal methods, | ||
* while the `TName` hack types the options properly | ||
*/ | ||
_tdata!: GetTData<typeof this.name, TData, TFormData> | ||
store!: Store<FieldState<typeof this._tdata>> | ||
state!: FieldState<typeof this._tdata> | ||
prevState!: FieldState<typeof this._tdata> | ||
options: FieldOptions<typeof this._tdata, TFormData> = {} as any | ||
@@ -102,3 +132,3 @@ constructor(opts: FieldApiOptions<TData, TFormData>) { | ||
this.store = new Store<FieldState<TData>>( | ||
this.store = new Store<FieldState<typeof this._tdata>>( | ||
{ | ||
@@ -121,7 +151,3 @@ value: this.getValue(), | ||
if (state.value !== this.#prevState.value) { | ||
this.validate('change', state.value) | ||
} | ||
this.#prevState = state | ||
this.prevState = state | ||
this.state = state | ||
@@ -133,4 +159,4 @@ }, | ||
this.state = this.store.state | ||
this.#prevState = this.state | ||
this.update(opts) | ||
this.prevState = this.state | ||
this.update(opts as never) | ||
} | ||
@@ -157,3 +183,3 @@ | ||
this.options.onMount?.(this) | ||
this.options.onMount?.(this as never) | ||
@@ -169,3 +195,3 @@ return () => { | ||
update = (opts: FieldApiOptions<TData, TFormData>) => { | ||
update = (opts: FieldApiOptions<typeof this._tdata, TFormData>) => { | ||
this.options = { | ||
@@ -176,10 +202,20 @@ asyncDebounceMs: this.form.options.asyncDebounceMs ?? 0, | ||
...opts, | ||
} | ||
} as never | ||
// Default Value | ||
if ( | ||
this.state.value === undefined && | ||
this.options.defaultValue !== undefined | ||
) { | ||
this.setValue(this.options.defaultValue) | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
if (this.state.value === undefined) { | ||
if (this.options.defaultValue !== undefined) { | ||
this.setValue(this.options.defaultValue as never) | ||
} else if ( | ||
opts.form.options.defaultValues?.[ | ||
this.options.name as keyof TFormData | ||
] !== undefined | ||
) { | ||
this.setValue( | ||
opts.form.options.defaultValues[ | ||
this.options.name as keyof TFormData | ||
] as never, | ||
) | ||
} | ||
} | ||
@@ -194,11 +230,16 @@ | ||
getValue = (): TData => { | ||
getValue = (): typeof this._tdata => { | ||
return this.form.getFieldValue(this.name) | ||
} | ||
setValue = ( | ||
updater: Updater<TData>, | ||
updater: Updater<typeof this._tdata>, | ||
options?: { touch?: boolean; notify?: boolean }, | ||
) => this.form.setFieldValue(this.name, updater as any, options) | ||
) => { | ||
this.form.setFieldValue(this.name, updater as never, options) | ||
this.validate('change', this.state.value) | ||
} | ||
getMeta = (): FieldMeta => this.form.getFieldMeta(this.name) | ||
setMeta = (updater: Updater<FieldMeta>) => | ||
@@ -209,17 +250,27 @@ this.form.setFieldMeta(this.name, updater) | ||
pushValue = (value: TData extends any[] ? TData[number] : never) => | ||
this.form.pushFieldValue(this.name, value as any) | ||
insertValue = (index: number, value: TData) => | ||
this.form.insertFieldValue(this.name, index, value as any) | ||
pushValue = ( | ||
value: typeof this._tdata extends any[] | ||
? (typeof this._tdata)[number] | ||
: never, | ||
) => this.form.pushFieldValue(this.name, value as any) | ||
insertValue = ( | ||
index: number, | ||
value: typeof this._tdata extends any[] | ||
? (typeof this._tdata)[number] | ||
: never, | ||
) => this.form.insertFieldValue(this.name, index, value as any) | ||
removeValue = (index: number) => this.form.removeFieldValue(this.name, index) | ||
swapValues = (aIndex: number, bIndex: number) => | ||
this.form.swapFieldValues(this.name, aIndex, bIndex) | ||
getSubField = <TName extends DeepKeys<TData>>(name: TName) => | ||
new FieldApi<DeepValue<TData, TName>, TFormData>({ | ||
name: `${this.name}.${name}` as any, | ||
getSubField = <TName extends DeepKeys<typeof this._tdata>>(name: TName) => | ||
new FieldApi<DeepValue<typeof this._tdata, TName>, TFormData>({ | ||
name: `${this.name}.${name}` as never, | ||
form: this.form, | ||
}) | ||
validateSync = async (value = this.state.value, cause: ValidationCause) => { | ||
validateSync = (value = this.state.value, cause: ValidationCause) => { | ||
const { onChange, onBlur } = this.options | ||
@@ -235,3 +286,3 @@ const validate = | ||
this.getInfo().validationCount = validationCount | ||
const error = normalizeError(validate(value, this)) | ||
const error = normalizeError(validate(value as never, this as never)) | ||
@@ -319,3 +370,3 @@ if (this.state.meta.error !== error) { | ||
try { | ||
const rawError = await validate(value, this) | ||
const rawError = await validate(value as never, this as never) | ||
@@ -350,3 +401,3 @@ if (checkLatest()) { | ||
cause: ValidationCause, | ||
value?: TData, | ||
value?: typeof this._tdata, | ||
): ValidationError | Promise<ValidationError> => { | ||
@@ -372,3 +423,4 @@ // If the field is pristine and validatePristine is false, do not validate | ||
props: T = {} as T, | ||
): ChangeProps<TData> & Omit<T, keyof ChangeProps<TData>> => { | ||
): ChangeProps<typeof this._tdata> & | ||
Omit<T, keyof ChangeProps<typeof this._tdata>> => { | ||
return { | ||
@@ -378,3 +430,3 @@ ...props, | ||
onChange: (value) => { | ||
this.setValue(value) | ||
this.setValue(value as never) | ||
props.onChange?.(value) | ||
@@ -390,3 +442,4 @@ }, | ||
}, | ||
} as ChangeProps<TData> & Omit<T, keyof ChangeProps<TData>> | ||
} as ChangeProps<typeof this._tdata> & | ||
Omit<T, keyof ChangeProps<typeof this._tdata>> | ||
} | ||
@@ -396,6 +449,7 @@ | ||
props: T = {} as T, | ||
): InputProps & Omit<T, keyof InputProps> => { | ||
): InputProps<typeof this._tdata> & | ||
Omit<T, keyof InputProps<typeof this._tdata>> => { | ||
return { | ||
...props, | ||
value: String(this.state.value), | ||
value: this.state.value, | ||
onChange: (e) => { | ||
@@ -406,3 +460,4 @@ this.setValue(e.target.value) | ||
onBlur: this.getChangeProps(props).onBlur, | ||
} | ||
} as InputProps<typeof this._tdata> & | ||
Omit<T, keyof InputProps<typeof this._tdata>> | ||
} | ||
@@ -409,0 +464,0 @@ } |
@@ -167,15 +167,29 @@ import { Store } from '@tanstack/store' | ||
this.store.batch(() => { | ||
if ( | ||
options.defaultState && | ||
const shouldUpdateValues = | ||
options.defaultValues && | ||
options.defaultValues !== this.options.defaultValues | ||
const shouldUpdateState = | ||
options.defaultState !== this.options.defaultState | ||
) { | ||
this.store.setState((prev) => ({ | ||
...prev, | ||
...options.defaultState, | ||
})) | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
if (!shouldUpdateValues || !shouldUpdateValues) { | ||
return | ||
} | ||
if (options.defaultValues !== this.options.defaultValues) { | ||
this.store.setState(() => getDefaultFormState(options.defaultValues!)) | ||
} | ||
this.store.setState(() => | ||
getDefaultFormState( | ||
Object.assign( | ||
{}, | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
shouldUpdateState ? options.defaultState : {}, | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
shouldUpdateValues | ||
? { | ||
values: options.defaultValues, | ||
} | ||
: {}, | ||
), | ||
), | ||
) | ||
}) | ||
@@ -187,3 +201,8 @@ | ||
reset = () => | ||
this.store.setState(() => getDefaultFormState(this.options.defaultValues!)) | ||
this.store.setState(() => | ||
getDefaultFormState({ | ||
...this.options.defaultState, | ||
values: this.options.defaultValues ?? this.options.defaultState?.values, | ||
}), | ||
) | ||
@@ -286,2 +305,3 @@ validateAllFields = async (cause: ValidationCause) => { | ||
getFieldInfo = <TField extends DeepKeys<TFormData>>(field: TField) => { | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
return (this.fieldInfo[field] ||= { | ||
@@ -333,3 +353,3 @@ instances: {}, | ||
field: TField, | ||
value: DeepValue<TFormData, TField>, | ||
value: DeepValue<TFormData, TField>[number], | ||
opts?: { touch?: boolean }, | ||
@@ -347,3 +367,3 @@ ) => { | ||
index: number, | ||
value: DeepValue<TFormData, TField>, | ||
value: DeepValue<TFormData, TField>[number], | ||
opts?: { touch?: boolean }, | ||
@@ -386,5 +406,5 @@ ) => { | ||
const prev2 = prev[index2]! | ||
return setBy(setBy(prev, [index1], prev2), [index2], prev1) | ||
return setBy(setBy(prev, `${index1}`, prev2), `${index2}`, prev1) | ||
}) | ||
} | ||
} |
@@ -16,2 +16,5 @@ export type UpdaterFn<TInput, TOutput = TInput> = (input: TInput) => TOutput | ||
/** | ||
* Get a value from an object using a path, including dot notation. | ||
*/ | ||
export function getBy(obj: any, path: any) { | ||
@@ -28,2 +31,5 @@ const pathArray = makePathArray(path) | ||
/** | ||
* Set a value on an object using a path, including dot notation. | ||
*/ | ||
export function setBy(obj: any, _path: any, updater: Updater<any>) { | ||
@@ -80,3 +86,3 @@ const path = makePathArray(_path) | ||
if (typeof str !== 'string') { | ||
throw new Error() | ||
throw new Error('Path must be a string.') | ||
} | ||
@@ -165,1 +171,4 @@ | ||
type Try<A1, A2, Catch = never> = A1 extends A2 ? A1 : Catch | ||
// Hack to get TypeScript to show simplified types in error messages | ||
export type Pretty<T> = { [K in keyof T]: T[K] } & {} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
63
4117
Yes
390649
1