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:
Corbin Crutchley
2023-09-09 03:40:29 -07:00
committed by GitHub
parent b5a768f182
commit 160f71275f
14 changed files with 421 additions and 512 deletions

View File

@@ -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'
}

View File

@@ -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']
}
}>
>,

View File

@@ -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