-
-
Notifications
You must be signed in to change notification settings - Fork 305
/
useField.tsx
151 lines (131 loc) 路 4.13 KB
/
useField.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import {
type DeepKeys,
type DeepValue,
FieldApi,
type FieldOptions,
type Narrow,
} from '@tanstack/form-core'
import { useStore } from '@tanstack/vue-store'
import {
type SetupContext,
defineComponent,
type Ref,
onMounted,
onUnmounted,
watch,
} from 'vue-demi'
import { provideFormContext, useFormContext } from './formContext'
declare module '@tanstack/form-core' {
// eslint-disable-next-line no-shadow
interface FieldApi<TData, TFormData> {
Field: FieldComponent<TData, TFormData>
}
}
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>,
TFormData
>,
) => FieldApi<DeepValue<TFormData, TField>, TFormData>
export function useField<TData, TFormData>(
opts: UseFieldOptions<TData, TFormData>,
): {
api: FieldApi<TData, TFormData>
state: Readonly<Ref<FieldApi<TData, 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 })
api.Field = Field as never
return api
})()
const fieldState = useStore(fieldApi.store, (state) => state)
let cleanup!: () => void
onMounted(() => {
cleanup = fieldApi.mount()
})
onUnmounted(() => {
cleanup()
})
watch(
() => opts,
() => {
// Keep options up to date as they are rendered
fieldApi.update({ ...opts, form: formApi } as never)
},
)
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'>
// // ^?
export type FieldComponent<TParentData, TFormData> = <TField>(
fieldOptions: {
children?: (
fieldApi: FieldApi<FieldValue<TParentData, TField>, TFormData>,
) => any
} & 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
}),
context: SetupContext,
) => any
export const Field = defineComponent(
<TData, TFormData>(
fieldOptions: UseFieldOptions<TData, TFormData>,
context: SetupContext,
) => {
const fieldApi = useField({ ...fieldOptions, ...context.attrs })
provideFormContext({
formApi: fieldApi.api.form,
parentFieldName: fieldApi.api.name,
} as never)
return () => context.slots.default!(fieldApi.api, fieldApi.state.value)
},
{ name: 'Field', inheritAttrs: false },
)