@kaliber/forms 2.1.2 → 3.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaliber/forms",
3
- "version": "2.1.2",
3
+ "version": "3.0.0-beta.0",
4
4
  "main": "index.js",
5
5
  "sideEffects": false,
6
6
  "publishConfig": {
@@ -25,7 +25,19 @@
25
25
  "private": false,
26
26
  "license": "MIT",
27
27
  "repository": "https://github.com/kaliberjs/forms.git",
28
+ "scripts": {
29
+ "postinstall": "if test -f ./scripts/install-peer-dependencies-for-typescript.js; then node ./scripts/install-peer-dependencies-for-typescript.js; fi",
30
+ "test.types": "tsc --project tsconfig.type.test.json"
31
+ },
28
32
  "dependencies": {
29
33
  "react-fast-compare": "^3.0.1"
34
+ },
35
+ "peerDependencies": {
36
+ "@types/react": "^18.0.0",
37
+ "react": "^18.3.1"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "22",
41
+ "typescript": "^5.8.2"
30
42
  }
31
43
  }
package/src/components.js CHANGED
@@ -2,22 +2,42 @@ import {
2
2
  useFormFieldValue, useFormFieldsValues,
3
3
  useFormFieldSnapshot
4
4
  } from './hooks'
5
+ /** @import { Field } from './types.ts' */
5
6
 
7
+ /**
8
+ * @template {Field} T
9
+ * @template {(value: ReturnType<typeof useFormFieldValue<T>>) => any} R
10
+ * @arg {{ field: T, render: R }} props
11
+ * @returns {null | ReturnType<R>}
12
+ */
6
13
  export function FormFieldValue({ field, render }) {
7
14
  const value = useFormFieldValue(field)
8
15
  return valueOrNull(render(value))
9
16
  }
10
17
 
18
+ /**
19
+ * @template {[...Field[]]} const T
20
+ * @template {(values: ReturnType<typeof useFormFieldsValues<T>>) => any} R
21
+ * @arg {{ fields: T, render: R }} props
22
+ * @returns {null | ReturnType<R>}
23
+ */
11
24
  export function FormFieldsValues({ fields, render }) {
12
25
  const values = useFormFieldsValues(fields)
13
26
  return valueOrNull(render(values))
14
27
  }
15
28
 
29
+ /**
30
+ * @template {Field} T
31
+ * @template {(valid: boolean) => any} R
32
+ * @arg {{ field: T, render: R }} props
33
+ * @returns {null | ReturnType<R>}
34
+ */
16
35
  export function FormFieldValid({ field, render }) {
17
36
  const { invalid } = useFormFieldSnapshot(field)
18
37
  return valueOrNull(render(!invalid))
19
38
  }
20
39
 
40
+ /** @template T @arg {T} value @returns {T | null} */
21
41
  function valueOrNull(value) {
22
42
  return typeof value === 'undefined' ? null : value
23
- }
43
+ }
@@ -0,0 +1,74 @@
1
+ import { FormFieldsValues, FormFieldValid, FormFieldValue } from './components'
2
+ import { useForm } from './hooks'
3
+ import { array, object } from './schema'
4
+ import { expectAssignable, expectNotAny, expectNotNever, Prepared } from './type.test.helpers.ts'
5
+ import { optionalT } from './validation'
6
+
7
+ const { form } = useForm({
8
+ fields: {
9
+ a: optionalT('string'),
10
+ b: optionalT('number'),
11
+ c: optionalT('boolean'),
12
+ d: object({
13
+ a: optionalT('string'),
14
+ b: optionalT('number'),
15
+ c: optionalT('boolean'),
16
+ }),
17
+ e: array({
18
+ a: optionalT('string'),
19
+ b: optionalT('number'),
20
+ c: optionalT('boolean'),
21
+ })
22
+ },
23
+ onSubmit() {}
24
+ })
25
+
26
+ FormFieldValue({
27
+ field: form,
28
+ render(value) {
29
+ expectNotAny(value)
30
+ expectNotNever(value)
31
+ expectAssignable<
32
+ {
33
+ a: string
34
+ b: number
35
+ c: boolean
36
+ d: {
37
+ a: string
38
+ b: number
39
+ c: boolean
40
+ }
41
+ e: {
42
+ a: string
43
+ b: number
44
+ c: boolean
45
+ }[]
46
+ },
47
+ Prepared<typeof value>
48
+ >
49
+ }
50
+ })
51
+
52
+ FormFieldsValues({
53
+ fields: [form.fields.a, form.fields.b],
54
+ render(value) {
55
+ expectNotAny(value)
56
+ expectNotNever(value)
57
+ expectAssignable<
58
+ [string, number],
59
+ Prepared<typeof value>
60
+ >
61
+ }
62
+ })
63
+
64
+ FormFieldValid({
65
+ field: form.fields.a,
66
+ render(value) {
67
+ expectNotAny(value)
68
+ expectNotNever(value)
69
+ expectAssignable<
70
+ boolean,
71
+ Prepared<typeof value>
72
+ >
73
+ }
74
+ })
package/src/fields.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { normalize } from './normalize'
2
2
  import { createState, subscribeToAll, subscribeToChildren } from './state'
3
3
  import isEqual from 'react-fast-compare'
4
+ /** @import { Falsy, Field, NormalizedField, PartialWithStringKey, State, Validate, ValidationContext, ValidationError, ValidationFunction } from './types.ts' */
4
5
 
5
6
  const constructors = {
6
7
  basic: createBasicFormField,
@@ -8,6 +9,15 @@ const constructors = {
8
9
  object: createObjectFormField,
9
10
  }
10
11
 
12
+ /**
13
+ * @template {NormalizedField.Object} const T
14
+ *
15
+ * @arg {{
16
+ * name?: string,
17
+ * initialValue?: PartialWithStringKey<NormalizedField.ToValue<T>>,
18
+ * field: T,
19
+ * }} props
20
+ */
11
21
  export function createObjectFormField({ name = '', initialValue = {}, field }) {
12
22
 
13
23
  const fields = createFormFields(initialValue, field.fields, name && `${name}.`)
@@ -17,18 +27,19 @@ export function createObjectFormField({ name = '', initialValue = {}, field }) {
17
27
  const internalState = createState(initialState)
18
28
  const validate = bindValidate(field.validate, internalState)
19
29
 
20
- const value = {
21
- get() { return mapValues(fields, child => child.value.get()) },
30
+ const value = /** @satisfies {State.Readonly} */ ({
31
+ get() { return mapValues(fields, /** @arg {Field} child */ child => child.value.get()) },
22
32
  subscribe(f) {
23
33
  return subscribeToChildren({
24
34
  children,
35
+ /** @arg {unknown} _ */
25
36
  notify: _ => f(value.get()),
26
37
  subscribeToChild: (x, f) => x.value.subscribe(f),
27
38
  })
28
39
  },
29
- }
40
+ })
30
41
 
31
- return {
42
+ return /** @type {Field.FromNormalizedField<T>} */ ({
32
43
  type: 'object',
33
44
  name,
34
45
  validate(context) {
@@ -49,15 +60,21 @@ export function createObjectFormField({ name = '', initialValue = {}, field }) {
49
60
  value,
50
61
  state: { get: internalState.get, subscribe: internalState.subscribe },
51
62
  fields,
52
- }
63
+ })
53
64
 
65
+ /**
66
+ * @arg {any} initialValues
67
+ * @arg {T['fields']} fields
68
+ * @arg {string} namePrefix
69
+ */
54
70
  function createFormFields(initialValues, fields, namePrefix = '') {
55
71
  return mapValues(fields, (field, name) => {
56
72
  const fullName = `${namePrefix}${name}`
57
73
  const normalizedField = normalize(field, fullName)
58
- const constructor = constructors[normalizedField.type]
59
- return constructor({
74
+ const createFormField = constructors[normalizedField.type]
75
+ return createFormField({
60
76
  name: fullName,
77
+ // @ts-expect-error - If you know how to fix this, please let me know
61
78
  field: normalizedField,
62
79
  initialValue: initialValues[name]
63
80
  })
@@ -65,7 +82,15 @@ export function createObjectFormField({ name = '', initialValue = {}, field }) {
65
82
  }
66
83
  }
67
84
 
68
- function createArrayFormField({ name, initialValue = [], field }) {
85
+ /**
86
+ * @arg {{
87
+ * name?: string,
88
+ * initialValue?: { [name: string]: any }[],
89
+ * field: NormalizedField.Array,
90
+ * }} props
91
+ * @returns {Field.Array}
92
+ */
93
+ function createArrayFormField({ name = '', initialValue = [], field }) {
69
94
 
70
95
  let index = 0
71
96
 
@@ -76,7 +101,7 @@ function createArrayFormField({ name, initialValue = [], field }) {
76
101
  const internalState = createState(initialState)
77
102
  const validate = bindValidate(field.validate, internalState)
78
103
 
79
- const value = {
104
+ const value = /** @satisfies {State.Readonly} */ ({
80
105
  get() {
81
106
  const { children } = internalState.get()
82
107
  return children.map(child => child.value.get())
@@ -85,12 +110,12 @@ function createArrayFormField({ name, initialValue = [], field }) {
85
110
  return subscribeToAll({
86
111
  state: internalState,
87
112
  childrenFromState: x => x.children,
88
- notify:_ => f(value.get()),
113
+ notify: _ => f(value.get()),
89
114
  subscribeToChild: (x, f) => x.value.subscribe(f),
90
115
  onlyNotifyOnChildChange: true,
91
116
  })
92
117
  },
93
- }
118
+ })
94
119
 
95
120
  return {
96
121
  type: 'array',
@@ -128,6 +153,7 @@ function createArrayFormField({ name, initialValue = [], field }) {
128
153
  }
129
154
  }
130
155
 
156
+ /** @arg {{ [name: string]: any }} initialValue */
131
157
  function createFormField(initialValue) {
132
158
  const fullName = `${name}[${index++}]`
133
159
  const fields = typeof field.fields == 'function' ? field.fields(initialValue) : field.fields
@@ -139,13 +165,21 @@ function createArrayFormField({ name, initialValue = [], field }) {
139
165
  }
140
166
  }
141
167
 
168
+ /**
169
+ * @arg {{
170
+ * name: string,
171
+ * initialValue?: any,
172
+ * field: NormalizedField.Basic,
173
+ * }} props
174
+ * @returns {Field.Basic}
175
+ */
142
176
  function createBasicFormField({ name, initialValue, field }) {
143
177
 
144
178
  const initialFormFieldState = deriveFormFieldState({ value: initialValue })
145
179
  const internalState = createState(initialFormFieldState)
146
180
  const validate = bindValidate(field.validate, internalState)
147
181
 
148
- const value = {
182
+ const value = /** @satisfies {State.ReadonlyWithHistory} */ ({
149
183
  get() { return internalState.get().value },
150
184
  subscribe(f) {
151
185
  return internalState.subscribe(({ value: newValue }, { value: oldValue }) => {
@@ -153,7 +187,7 @@ function createBasicFormField({ name, initialValue, field }) {
153
187
  f(newValue, oldValue)
154
188
  })
155
189
  },
156
- }
190
+ })
157
191
 
158
192
  return {
159
193
  type: 'basic',
@@ -189,17 +223,41 @@ function createBasicFormField({ name, initialValue, field }) {
189
223
  }
190
224
  }
191
225
 
226
+ /**
227
+ * @template {Record<string, any>} T1
228
+ * @template {Record<string, any>} T2
229
+ * @arg {T1} formFieldState
230
+ * @arg {T2} update
231
+ */
192
232
  function updateState(formFieldState, update) {
193
233
  return deriveFormFieldState({ ...formFieldState, ...update })
194
234
  }
195
235
 
236
+ /**
237
+ * @template T
238
+ * @template {keyof T} K
239
+ * @arg {T} o
240
+ * @arg {K[]} properties
241
+ * @returns {Pick<T, K>}
242
+ */
196
243
  function pick(o, properties) {
244
+ // @ts-expect-error
197
245
  return properties.reduce(
198
246
  (result, property) => ({ ...result, [property]: o[property] }),
199
247
  {}
200
248
  )
201
249
  }
202
250
 
251
+ /**
252
+ * @template {Record<string, any>} const T
253
+ * @arg {{
254
+ * error?: Falsy | ValidationError,
255
+ * isSubmitted?: boolean,
256
+ * isVisited?: boolean,
257
+ * hasFocus?: boolean,
258
+ * } & T} state
259
+ * @return {T & State.Common}
260
+ */
203
261
  function deriveFormFieldState({
204
262
  error = false,
205
263
  isSubmitted = false,
@@ -207,7 +265,7 @@ function deriveFormFieldState({
207
265
  hasFocus = false,
208
266
  ...rest
209
267
  }) {
210
- return {
268
+ return /** @type {T & State.Common} */ ({
211
269
  ...rest,
212
270
  error,
213
271
  isSubmitted,
@@ -215,18 +273,38 @@ function deriveFormFieldState({
215
273
  hasFocus,
216
274
  invalid: !!error,
217
275
  showError: !!error && !hasFocus && (isVisited || isSubmitted)
218
- }
276
+ })
219
277
  }
220
278
 
279
+ /**
280
+ * @template {{ [key: string]: any }} O
281
+ * @template {(v: O[keyof O], k: keyof O & string, o: O) => any} F
282
+ *
283
+ * @param {O} o
284
+ * @param {F} f
285
+ * @returns {{ [key in keyof O]: ReturnType<F> }}
286
+ */
221
287
  function mapValues(o, f) {
288
+ // @ts-expect-error
222
289
  return Object.entries(o).reduce(
223
- (result, [k, v]) => (result[k] = f(v, k), result),
290
+ // @ts-expect-error
291
+ (result, [k, v]) => (result[k] = f(v, k, o), result),
224
292
  {}
225
293
  )
226
294
  }
227
295
 
296
+ /**
297
+ * @template T
298
+ * @template {State.ReadWrite} S
299
+ * @arg {null | ValidationFunction<T>} f
300
+ * @arg {S} state
301
+ */
228
302
  function bindValidate(f, state) {
229
303
  return f && (
304
+ /**
305
+ * @arg {Parameters<ValidationFunction<T>>} args
306
+ * @returns {S extends State.ReadWrite<infer X> ? X : never}
307
+ */
230
308
  (...args) => {
231
309
  const error = (f && f(...args)) || false
232
310
  return state.update(x => isEqual(error, x.error) ? x : updateState(x, { error }))
@@ -234,6 +312,10 @@ function bindValidate(f, state) {
234
312
  )
235
313
  }
236
314
 
315
+ /**
316
+ * @arg {ValidationContext} context
317
+ * @arg {any} parent
318
+ */
237
319
  function addParent(context, parent) {
238
320
  return { ...context, parents: [...context.parents, parent] }
239
321
  }
@@ -0,0 +1,135 @@
1
+ import { createObjectFormField } from './fields'
2
+ import { normalize } from './normalize'
3
+ import { array, object } from './schema'
4
+ import { asConst } from './type-helpers'
5
+ import { expectAssignable, expectNotAny, expectNotNever, Prepared } from './type.test.helpers.ts'
6
+ import { Field, PartialWithStringKey, State, ValidationContext } from './types.ts'
7
+ import { email, number, optional, required } from './validation'
8
+
9
+ const simpleObjectInput = asConst({
10
+ noValidation: optional,
11
+ singleValidation: email,
12
+ multipleValidation: [required, number],
13
+ })
14
+ type SimpleObjectValueType = {
15
+ noValidation: unknown,
16
+ singleValidation: string,
17
+ multipleValidation: number,
18
+ }
19
+ type SimpleObjectFieldsType = {
20
+ noValidation: BasicFieldType<unknown>,
21
+ singleValidation: BasicFieldType<string>,
22
+ multipleValidation: BasicFieldType<number>,
23
+ }
24
+ const simpleObjectNormalizedField = normalize(object(simpleObjectInput))
25
+ const simpleObjectWithValidationNormalizedField = normalize(object(validate, simpleObjectInput))
26
+
27
+ type BasicFieldType<T> = {
28
+ type: 'basic',
29
+ name: string,
30
+ validate(context: ValidationContext): void,
31
+ setSubmitted(isSubmitted: boolean): void,
32
+ reset(): void,
33
+ value: State.Readonly<T>,
34
+ state: State.Readonly<State.Basic<T>>,
35
+ eventHandlers: {
36
+ onBlur(): void,
37
+ onFocus(): void,
38
+ onChange(eOrValue: Field.Event<T> | T): void,
39
+ }
40
+ }
41
+
42
+ type ObjectFieldType<Fields, Value> = {
43
+ type: 'object',
44
+ name: string,
45
+ validate(context: ValidationContext): void,
46
+ setSubmitted(isSubmitted: boolean): void,
47
+ reset(): void,
48
+ value: State.Readonly<Value>,
49
+ state: State.Readonly<State.Object>,
50
+ fields: Fields
51
+ }
52
+
53
+ type ArrayFieldType<Field, Value extends Array<any>> = {
54
+ type: 'array',
55
+ name: string,
56
+ validate(context: ValidationContext): void,
57
+ setSubmitted(isSubmitted: boolean): void,
58
+ reset(): void,
59
+ value: State.Readonly<Value>,
60
+ state: State.Readonly<State.Array<Field>>,
61
+ helpers: {
62
+ add(initialValue: PartialWithStringKey<Value extends Array<infer X> ? X : never>): void,
63
+ remove(entry: Field): void,
64
+ }
65
+ }
66
+
67
+ {
68
+ const simpleObjectField = createObjectFormField({ field: simpleObjectNormalizedField })
69
+ const simpleObjectWithValidationField = createObjectFormField({ field: simpleObjectWithValidationNormalizedField })
70
+ type SimpleObjectFields = (typeof simpleObjectField)['fields']
71
+
72
+ type NoValidationFieldType = SimpleObjectFields['noValidation']
73
+ expectAssignable<
74
+ BasicFieldType<unknown>,
75
+ Prepared<NoValidationFieldType>
76
+ >
77
+
78
+ type SingleValidationFieldType = SimpleObjectFields['singleValidation']
79
+ expectAssignable<
80
+ BasicFieldType<string>,
81
+ Prepared<SingleValidationFieldType>
82
+ >
83
+
84
+ type MultipleValidationFieldType = SimpleObjectFields['multipleValidation']
85
+ expectAssignable<
86
+ BasicFieldType<number>,
87
+ Prepared<MultipleValidationFieldType>
88
+ >
89
+
90
+ expectNotAny(simpleObjectField)
91
+ expectNotNever(simpleObjectField)
92
+ expectAssignable<
93
+ ObjectFieldType<SimpleObjectFieldsType, SimpleObjectValueType>,
94
+ Prepared<typeof simpleObjectField>
95
+ >
96
+ expectAssignable<
97
+ ObjectFieldType<SimpleObjectFieldsType, SimpleObjectValueType>,
98
+ Prepared<typeof simpleObjectWithValidationField>
99
+ >
100
+ }
101
+
102
+ {
103
+ const objectWithSubFields = createObjectFormField({
104
+ field: normalize(object({
105
+ object: simpleObjectNormalizedField,
106
+ array: array(simpleObjectInput)
107
+ }))
108
+ })
109
+
110
+ expectNotAny(objectWithSubFields)
111
+ expectNotNever(objectWithSubFields)
112
+ expectAssignable<
113
+ ObjectFieldType<
114
+ {
115
+ object: ObjectFieldType<
116
+ SimpleObjectFieldsType,
117
+ SimpleObjectValueType
118
+ >,
119
+ array: ArrayFieldType<
120
+ ObjectFieldType<
121
+ SimpleObjectFieldsType,
122
+ SimpleObjectValueType
123
+ >,
124
+ SimpleObjectValueType[]
125
+ >,
126
+ },
127
+ { object: SimpleObjectValueType, array: SimpleObjectValueType[] }
128
+ >,
129
+ Prepared<typeof objectWithSubFields>
130
+ >
131
+ }
132
+
133
+ function validate<T>(value: T) {
134
+ return value === 'failure' && { id: 'error' }
135
+ }
package/src/hooks.js CHANGED
@@ -2,14 +2,28 @@ import isEqual from 'react-fast-compare'
2
2
  import { createObjectFormField } from './fields'
3
3
  import { normalize } from './normalize'
4
4
  import * as snapshot from './snapshot'
5
+ import React from 'react'
6
+ import { asAny } from './type-helpers'
7
+ /** @import { Validate, NormalizedField, Field, InitialValue, FieldInput, State, Snapshot, Expand, MapTuple } from './types.ts' */
5
8
 
6
9
  let formCounter = 0 // This will stop working when we need a number greater than 9007199254740991
7
10
  function useFormId() { return React.useMemo(() => `form${++formCounter}`, []) }
8
11
 
9
-
12
+ /**
13
+ * @template {FieldInput.Object} const A
14
+ * @template {Validate<FieldInput.ObjectToValue<A>>} const C
15
+ *
16
+ * @arg {{
17
+ * fields: A,
18
+ * initialValues?: Expand<InitialValue<A>>,
19
+ * validate?: C,
20
+ * onSubmit: (snapshot: any) => void,
21
+ * formId?: string,
22
+ * }} props
23
+ */
10
24
  export function useForm({ initialValues = undefined, fields, validate = undefined, onSubmit, formId = useFormId() }) {
11
- const initialValuesRef = React.useRef(null)
12
- const formRef = React.useRef(null)
25
+ const initialValuesRef = React.useRef(/** @type {InitialValue<A> | undefined} */ (asAny(null)))
26
+ const formRef = React.useRef(/** @type {Field.ObjectFromObjectInput<A>} */ (asAny(null)))
13
27
 
14
28
  if (!isEqual(initialValuesRef.current, initialValues)) {
15
29
  initialValuesRef.current = initialValues
@@ -28,6 +42,7 @@ export function useForm({ initialValues = undefined, fields, validate = undefine
28
42
 
29
43
  return { form: formRef.current, submit, reset }
30
44
 
45
+ /** @arg {React.FormEvent<HTMLFormElement>} e */
31
46
  function handleSubmit(e) {
32
47
  if (e) e.preventDefault()
33
48
  formRef.current.setSubmitted(true)
@@ -39,6 +54,11 @@ export function useForm({ initialValues = undefined, fields, validate = undefine
39
54
  }
40
55
  }
41
56
 
57
+ /**
58
+ * @template {State.Readonly} T
59
+ * @arg {T} state
60
+ * @returns {T extends State.Readonly<infer X> ? X : never}
61
+ */
42
62
  function useFormFieldState(state) {
43
63
  const [formFieldState, setFormFieldState] = React.useState(state.get)
44
64
 
@@ -53,6 +73,10 @@ function useFormFieldState(state) {
53
73
  return formFieldState
54
74
  }
55
75
 
76
+ /**
77
+ * @template {[...State.Readonly[]]} const T
78
+ * @arg {T} states
79
+ */
56
80
  function useFieldStates(states) {
57
81
  const [fieldStates, setFieldStates] = React.useState(getStates)
58
82
 
@@ -70,17 +94,24 @@ function useFieldStates(states) {
70
94
  () => {}
71
95
  )
72
96
  },
73
- states // explanation below
97
+ states // explanation below on why we supply the array directly
74
98
  )
75
99
 
76
100
  return fieldStates
77
101
 
78
- function getStates() { return states.map(x => x.get()) }
102
+ function getStates() {
103
+ return /** @type {State.StateTupleToValueTuple<T>}*/ (states.map(x => x.get()))
104
+ }
79
105
  }
80
106
 
107
+ /**
108
+ * @template {Field} T
109
+ * @arg {T} field
110
+ * @returns {Expand<Snapshot.FromField<T>>}
111
+ */
81
112
  export function useFormFieldSnapshot(field) {
82
113
  const state = React.useMemo(
83
- () => ({
114
+ () => /** @satisfies {State.Readonly} */ ({
84
115
  get() { return snapshot.get(field) },
85
116
  subscribe(f) { return snapshot.subscribe(field, f) }
86
117
  }),
@@ -89,14 +120,27 @@ export function useFormFieldSnapshot(field) {
89
120
  return useFormFieldState(state)
90
121
  }
91
122
 
123
+ /**
124
+ * @template {Field} T
125
+ * @arg {T} field
126
+ * @returns {ReturnType<typeof useFormFieldState<T['value']>>}
127
+ */
92
128
  export function useFormFieldValue(field) {
93
129
  return useFormFieldState(field.value)
94
130
  }
95
131
 
132
+ /**
133
+ * @template {[...Field[]]} const T
134
+ * @arg {T} fields
135
+ */
96
136
  export function useFormFieldsValues(fields) {
97
- return useFieldStates(fields.map(x => x.value))
137
+ return useFieldStates(/** @type {MapTuple<T, 'value'>} */ (fields.map(x => x.value)))
98
138
  }
99
139
 
140
+ /**
141
+ * @template T
142
+ * @arg {Field.Basic<T>} field
143
+ */
100
144
  export function useFormField(field) {
101
145
  if (!field) throw new Error('No field was passed in')
102
146
  const { name, eventHandlers } = field
@@ -105,12 +149,16 @@ export function useFormField(field) {
105
149
  return { name, state, eventHandlers }
106
150
  }
107
151
 
152
+ /**
153
+ * @arg {Field.Basic<number | string>} field
154
+ */
108
155
  export function useNumberFormField(field) {
109
156
  const { name, state, eventHandlers: { onChange, ...originalEventHandlers } } = useFormField(field)
110
157
  const eventHandlers = { ...originalEventHandlers, onChange: handleChange }
111
158
 
112
159
  return { name, state, eventHandlers }
113
160
 
161
+ /** @arg {React.ChangeEvent<HTMLInputElement>} e */
114
162
  function handleChange(e) {
115
163
  const userValue = e.target.value
116
164
  const value = Number(userValue)
@@ -118,17 +166,23 @@ export function useNumberFormField(field) {
118
166
  }
119
167
  }
120
168
 
169
+ /** @arg {Field.Basic<boolean>} field */
121
170
  export function useBooleanFormField(field) {
122
171
  const { name, state, eventHandlers: { onChange, ...originalEventHandlers } } = useFormField(field)
123
172
  const eventHandlers = { ...originalEventHandlers, onChange: handleChange }
124
173
 
125
174
  return { name, state, eventHandlers }
126
175
 
176
+ /** @arg {React.ChangeEvent<HTMLInputElement>} e */
127
177
  function handleChange(e) {
128
178
  onChange(e.target.checked)
129
179
  }
130
180
  }
131
181
 
182
+ /**
183
+ * @template {Field.ObjectFields} T
184
+ * @arg {Field.Array<T>} field
185
+ */
132
186
  export function useArrayFormField(field) {
133
187
  const { name, helpers } = field
134
188
  const state = useFormFieldState(field.state)
@@ -136,6 +190,10 @@ export function useArrayFormField(field) {
136
190
  return { name, state, helpers }
137
191
  }
138
192
 
193
+ /**
194
+ * @template {Field.ObjectFields} T
195
+ * @arg {Field.Object<T>} field
196
+ */
139
197
  export function useObjectFormField(field) {
140
198
  const { name, fields } = field
141
199
  const state = useFormFieldState(field.state)