mirror of
https://github.com/LukeHagar/form.git
synced 2025-12-09 04:19:49 +00:00
fix: Field components should now infer state.value properly
* chore: refactor TS typings for React * fix: field should now infer state.value properly in React adapter * chore: fix Vue package typings * chore: fix linting * chore: fix React adapter * chore: improve performance of TData type in FieldApi * chore: add back index and parent type * chore: add Vue TSC dep on Vue example * chore: fix lint and type test * chore: update Vite stuff * chore: add implicit dep for Vue and React examples * chore: add type test pre-req * chore: install deps from examples in PR CI * chore: remove filter from more installation
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
export type NoInfer<T> = [T][T extends any ? 0 : never]
|
||||
import type { FieldOptions, DeepKeys } from '@tanstack/form-core'
|
||||
|
||||
export type ReleaseVersion = 2
|
||||
export type UseFieldOptions<
|
||||
TData,
|
||||
TFormData,
|
||||
TName = unknown extends TFormData ? string : DeepKeys<TFormData>,
|
||||
> = FieldOptions<TData, TFormData, TName> & {
|
||||
mode?: 'value' | 'array'
|
||||
}
|
||||
|
||||
@@ -1,28 +1,22 @@
|
||||
import { FieldApi } from '@tanstack/form-core'
|
||||
import type {
|
||||
FieldState,
|
||||
DeepKeys,
|
||||
DeepValue,
|
||||
FieldOptions,
|
||||
Narrow,
|
||||
import {
|
||||
FieldApi,
|
||||
type FieldApiOptions,
|
||||
type FormApi,
|
||||
} from '@tanstack/form-core'
|
||||
import type { DeepKeys, DeepValue, Narrow } from '@tanstack/form-core'
|
||||
import { useStore } from '@tanstack/vue-store'
|
||||
import { defineComponent, onMounted, onUnmounted, watch } from 'vue-demi'
|
||||
import type { SlotsType, SetupContext, Ref } from 'vue-demi'
|
||||
import { provideFormContext, useFormContext } from './formContext'
|
||||
import type { UseFieldOptions } from './types'
|
||||
|
||||
declare module '@tanstack/form-core' {
|
||||
// eslint-disable-next-line no-shadow
|
||||
interface FieldApi<TData, TFormData> {
|
||||
Field: FieldComponent<TData, TFormData>
|
||||
interface FieldApi<_TData, TFormData, Opts, TData> {
|
||||
Field: FieldComponent<TFormData, TData>
|
||||
}
|
||||
}
|
||||
|
||||
export interface UseFieldOptions<TData, TFormData>
|
||||
extends FieldOptions<TData, TFormData> {
|
||||
mode?: 'value' | 'array'
|
||||
}
|
||||
|
||||
export type UseField<TFormData> = <TField extends DeepKeys<TFormData>>(
|
||||
opts?: { name: Narrow<TField> } & UseFieldOptions<
|
||||
DeepValue<TFormData, TField>,
|
||||
@@ -30,25 +24,45 @@ export type UseField<TFormData> = <TField extends DeepKeys<TFormData>>(
|
||||
>,
|
||||
) => FieldApi<DeepValue<TFormData, TField>, TFormData>
|
||||
|
||||
export function useField<TData, TFormData>(
|
||||
opts: UseFieldOptions<TData, TFormData>,
|
||||
export function useField<
|
||||
TData,
|
||||
TFormData,
|
||||
TName extends unknown extends TFormData
|
||||
? string
|
||||
: DeepKeys<TFormData> = unknown extends TFormData
|
||||
? string
|
||||
: DeepKeys<TFormData>,
|
||||
>(
|
||||
opts: UseFieldOptions<TData, TFormData, TName>,
|
||||
): {
|
||||
api: FieldApi<TData, TFormData>
|
||||
state: Readonly<Ref<FieldApi<TData, TFormData>['state']>>
|
||||
api: FieldApi<
|
||||
TData,
|
||||
TFormData,
|
||||
Omit<typeof opts, 'onMount'> & {
|
||||
form: FormApi<TFormData>
|
||||
}
|
||||
>
|
||||
state: Readonly<
|
||||
Ref<
|
||||
FieldApi<
|
||||
TData,
|
||||
TFormData,
|
||||
Omit<typeof opts, 'onMount'> & {
|
||||
form: FormApi<TFormData>
|
||||
}
|
||||
>['state']
|
||||
>
|
||||
>
|
||||
} {
|
||||
// Get the form API either manually or from context
|
||||
const { formApi, parentFieldName } = useFormContext()
|
||||
|
||||
const fieldApi = (() => {
|
||||
const name = (
|
||||
typeof opts.index === 'number'
|
||||
? [parentFieldName, opts.index, opts.name]
|
||||
: [parentFieldName, opts.name]
|
||||
)
|
||||
.filter((d) => d !== undefined)
|
||||
.join('.')
|
||||
|
||||
const api = new FieldApi({ ...opts, form: formApi, name: name as never })
|
||||
const api = new FieldApi({
|
||||
...opts,
|
||||
form: formApi,
|
||||
name: opts.name,
|
||||
} as never)
|
||||
|
||||
api.Field = Field as never
|
||||
|
||||
@@ -77,56 +91,49 @@ export function useField<TData, TFormData>(
|
||||
return { api: fieldApi, state: fieldState } as never
|
||||
}
|
||||
|
||||
// export type FieldValue<TFormData, TField> = TFormData extends any[]
|
||||
// ? TField extends `[${infer TIndex extends number | 'i'}].${infer TRest}`
|
||||
// ? DeepValue<TFormData[TIndex extends 'i' ? number : TIndex], TRest>
|
||||
// : TField extends `[${infer TIndex extends number | 'i'}]`
|
||||
// ? TFormData[TIndex extends 'i' ? number : TIndex]
|
||||
// : never
|
||||
// : TField extends `${infer TPrefix}[${infer TIndex extends
|
||||
// | number
|
||||
// | 'i'}].${infer TRest}`
|
||||
// ? DeepValue<
|
||||
// DeepValue<TFormData, TPrefix>[TIndex extends 'i' ? number : TIndex],
|
||||
// TRest
|
||||
// >
|
||||
// : TField extends `${infer TPrefix}[${infer TIndex extends number | 'i'}]`
|
||||
// ? DeepValue<TFormData, TPrefix>[TIndex extends 'i' ? number : TIndex]
|
||||
// : DeepValue<TFormData, TField>
|
||||
|
||||
export type FieldValue<TFormData, TField> = TFormData extends any[]
|
||||
? unknown extends TField
|
||||
? TFormData[number]
|
||||
: DeepValue<TFormData[number], TField>
|
||||
: DeepValue<TFormData, TField>
|
||||
|
||||
// type Test1 = FieldValue<{ foo: { bar: string }[] }, 'foo'>
|
||||
// // ^?
|
||||
// type Test2 = FieldValue<{ foo: { bar: string }[] }, 'foo[i]'>
|
||||
// // ^?
|
||||
// type Test3 = FieldValue<{ foo: { bar: string }[] }, 'foo[2].bar'>
|
||||
// // ^?
|
||||
type FieldComponentProps<
|
||||
TParentData,
|
||||
TFormData,
|
||||
TField,
|
||||
TName extends unknown extends TFormData ? string : DeepKeys<TFormData>,
|
||||
> = (TParentData extends any[]
|
||||
? {
|
||||
name?: TName
|
||||
index: number
|
||||
}
|
||||
: {
|
||||
name: TName
|
||||
index?: never
|
||||
}) &
|
||||
Omit<UseFieldOptions<TField, TFormData, TName>, 'name' | 'index'>
|
||||
|
||||
export type FieldComponent<TParentData, TFormData> = <TField>(
|
||||
fieldOptions: Omit<
|
||||
UseFieldOptions<FieldValue<TParentData, TField>, TFormData>,
|
||||
'name' | 'index'
|
||||
> &
|
||||
(TParentData extends any[]
|
||||
? {
|
||||
name?: TField extends undefined ? TField : DeepKeys<TParentData>
|
||||
index: number
|
||||
}
|
||||
: {
|
||||
name: TField extends undefined ? TField : DeepKeys<TParentData>
|
||||
index?: never
|
||||
}),
|
||||
export type FieldComponent<TParentData, TFormData> = <
|
||||
// Type of the field
|
||||
TField,
|
||||
// Name of the field
|
||||
TName extends unknown extends TFormData ? string : DeepKeys<TFormData>,
|
||||
>(
|
||||
fieldOptions: FieldComponentProps<TParentData, TFormData, TField, TName>,
|
||||
context: SetupContext<
|
||||
{},
|
||||
SlotsType<{
|
||||
default: {
|
||||
field: FieldApi<FieldValue<TParentData, TField>, TFormData>
|
||||
state: FieldState<any>
|
||||
field: FieldApi<
|
||||
TField,
|
||||
TFormData,
|
||||
FieldApiOptions<TField, TFormData, TName>
|
||||
>
|
||||
state: FieldApi<
|
||||
TField,
|
||||
TFormData,
|
||||
FieldApiOptions<TField, TFormData, TName>
|
||||
>['state']
|
||||
}
|
||||
}>
|
||||
>,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FormApi, type FormState, type FormOptions } from '@tanstack/form-core'
|
||||
import { useStore } from '@tanstack/vue-store'
|
||||
import { type NoInfer, useStore } from '@tanstack/vue-store'
|
||||
import { type UseField, type FieldComponent, Field, useField } from './useField'
|
||||
import { provideFormContext } from './formContext'
|
||||
import {
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
type SetupContext,
|
||||
defineComponent,
|
||||
} from 'vue-demi'
|
||||
import type { NoInfer } from './types'
|
||||
|
||||
declare module '@tanstack/form-core' {
|
||||
// eslint-disable-next-line no-shadow
|
||||
|
||||
Reference in New Issue
Block a user