kiru 0.44.4

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.
Files changed (70) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +5 -0
  3. package/package.json +81 -0
  4. package/src/appContext.ts +186 -0
  5. package/src/cloneVNode.ts +14 -0
  6. package/src/constants.ts +146 -0
  7. package/src/context.ts +56 -0
  8. package/src/dom.ts +712 -0
  9. package/src/element.ts +54 -0
  10. package/src/env.ts +6 -0
  11. package/src/error.ts +85 -0
  12. package/src/flags.ts +15 -0
  13. package/src/form/index.ts +662 -0
  14. package/src/form/types.ts +261 -0
  15. package/src/form/utils.ts +19 -0
  16. package/src/generateId.ts +19 -0
  17. package/src/globalContext.ts +161 -0
  18. package/src/globals.ts +21 -0
  19. package/src/hmr.ts +178 -0
  20. package/src/hooks/index.ts +14 -0
  21. package/src/hooks/useAsync.ts +136 -0
  22. package/src/hooks/useCallback.ts +31 -0
  23. package/src/hooks/useContext.ts +79 -0
  24. package/src/hooks/useEffect.ts +44 -0
  25. package/src/hooks/useEffectEvent.ts +24 -0
  26. package/src/hooks/useId.ts +42 -0
  27. package/src/hooks/useLayoutEffect.ts +47 -0
  28. package/src/hooks/useMemo.ts +33 -0
  29. package/src/hooks/useReducer.ts +50 -0
  30. package/src/hooks/useRef.ts +40 -0
  31. package/src/hooks/useState.ts +62 -0
  32. package/src/hooks/useSyncExternalStore.ts +59 -0
  33. package/src/hooks/useViewTransition.ts +26 -0
  34. package/src/hooks/utils.ts +259 -0
  35. package/src/hydration.ts +67 -0
  36. package/src/index.ts +61 -0
  37. package/src/jsx.ts +11 -0
  38. package/src/lazy.ts +238 -0
  39. package/src/memo.ts +48 -0
  40. package/src/portal.ts +43 -0
  41. package/src/profiling.ts +105 -0
  42. package/src/props.ts +36 -0
  43. package/src/reconciler.ts +531 -0
  44. package/src/renderToString.ts +91 -0
  45. package/src/router/index.ts +2 -0
  46. package/src/router/route.ts +51 -0
  47. package/src/router/router.ts +275 -0
  48. package/src/router/routerUtils.ts +49 -0
  49. package/src/scheduler.ts +522 -0
  50. package/src/signals/base.ts +237 -0
  51. package/src/signals/computed.ts +139 -0
  52. package/src/signals/effect.ts +60 -0
  53. package/src/signals/globals.ts +11 -0
  54. package/src/signals/index.ts +12 -0
  55. package/src/signals/jsx.ts +45 -0
  56. package/src/signals/types.ts +10 -0
  57. package/src/signals/utils.ts +12 -0
  58. package/src/signals/watch.ts +151 -0
  59. package/src/ssr/client.ts +29 -0
  60. package/src/ssr/hydrationBoundary.ts +63 -0
  61. package/src/ssr/index.ts +1 -0
  62. package/src/ssr/server.ts +124 -0
  63. package/src/store.ts +241 -0
  64. package/src/swr.ts +360 -0
  65. package/src/transition.ts +80 -0
  66. package/src/types.dom.ts +1250 -0
  67. package/src/types.ts +209 -0
  68. package/src/types.utils.ts +39 -0
  69. package/src/utils.ts +581 -0
  70. package/src/warning.ts +9 -0
@@ -0,0 +1,662 @@
1
+ import { __DEV__ } from "../env.js"
2
+ import { Fragment } from "../element.js"
3
+ import { generateRandomID } from "../generateId.js"
4
+ import { safeStringify, shallowCompare } from "../utils.js"
5
+ import { useEffect } from "../hooks/useEffect.js"
6
+ import { useMemo } from "../hooks/useMemo.js"
7
+ import { useRef } from "../hooks/useRef.js"
8
+ import { useHook, useRequestUpdate } from "../hooks/utils.js"
9
+ import { objGet, objSet } from "./utils.js"
10
+ import type {
11
+ AsyncValidatorContext,
12
+ FormContext,
13
+ FormController,
14
+ FormFieldContext,
15
+ FormFieldProps,
16
+ FormFieldState,
17
+ FormFieldValidators,
18
+ FormStateSubscriber,
19
+ FormSubscribeProps,
20
+ InferRecordKeyValue,
21
+ RecordKey,
22
+ SelectorState,
23
+ UseFormConfig,
24
+ UseFormInternalState,
25
+ UseFormState,
26
+ } from "./types"
27
+
28
+ export type * from "./types"
29
+
30
+ function createFormController<T extends Record<string, unknown>>(
31
+ config: UseFormConfig<T>
32
+ ): FormController<T> {
33
+ let isSubmitting = false
34
+ const subscribers = new Set<FormStateSubscriber<T>>()
35
+ const state: T = structuredClone(config.initialValues ?? {}) as T
36
+ const formFieldValidators = {} as {
37
+ [fieldName in RecordKey<T>]: Partial<
38
+ FormFieldValidators<RecordKey<T>, InferRecordKeyValue<T, fieldName>>
39
+ >
40
+ }
41
+ const formFieldDependencies = {} as {
42
+ [key in RecordKey<T>]: Set<RecordKey<T>>
43
+ }
44
+ const formFieldsTouched = {} as {
45
+ [key in RecordKey<T>]?: boolean
46
+ }
47
+ const formFieldUpdaters = new Map<RecordKey<T>, Set<() => void>>()
48
+ const asyncFormFieldValidators = {} as {
49
+ [key in RecordKey<T>]: {
50
+ onChangeAsync?: {
51
+ timeout: number
52
+ epoch: number
53
+ abortController: AbortController | null
54
+ }
55
+ }
56
+ }
57
+ const formFieldErrors = {} as {
58
+ [key in RecordKey<T>]: {
59
+ onMount?: any
60
+ onChange?: any
61
+ onBlur?: any
62
+ onSubmit?: any
63
+ onChangeAsync?: any
64
+ }
65
+ }
66
+
67
+ const canSubmit = () => {
68
+ return isAnyFieldValidating() === false && getErrors().length === 0
69
+ }
70
+
71
+ const getSelectorState = (): SelectorState<T> => {
72
+ return {
73
+ values: state,
74
+ canSubmit: canSubmit(),
75
+ isSubmitting,
76
+ }
77
+ }
78
+
79
+ const updateSubscribers = () => {
80
+ const selectorState = getSelectorState()
81
+ for (const sub of subscribers) {
82
+ const { selector, selection, update } = sub
83
+ const newSelection = selector(selectorState)
84
+ if (shallowCompare(selection, newSelection)) continue
85
+ sub.selection = newSelection
86
+ update()
87
+ }
88
+ }
89
+
90
+ const updateFieldComponents = (name: RecordKey<T>) => {
91
+ if (!formFieldUpdaters.has(name)) return
92
+ for (const update of formFieldUpdaters.get(name)!) update()
93
+ }
94
+
95
+ const validateField = async <K extends RecordKey<T>>(
96
+ name: K,
97
+ evt: keyof Omit<
98
+ FormFieldValidators<RecordKey<T>, InferRecordKeyValue<T, K>>,
99
+ "onChangeAsyncDebounceMs"
100
+ >
101
+ ) => {
102
+ const fieldErrors = (formFieldErrors[name] ??= {})
103
+ const asyncFieldValidatorStates = (asyncFormFieldValidators[name] ??= {})
104
+ const fieldValidators = formFieldValidators[name] ?? {}
105
+
106
+ const validatorCtx = { value: getFieldValue(name) }
107
+
108
+ switch (evt) {
109
+ case "onMount": {
110
+ if (!fieldValidators.onMount) return
111
+
112
+ fieldErrors.onMount = fieldValidators.onMount(validatorCtx)
113
+ updateSubscribers()
114
+ updateFieldComponents(name)
115
+ break
116
+ }
117
+ case "onChange": {
118
+ if (fieldErrors.onMount) delete fieldErrors.onMount
119
+ formFieldsTouched[name] = true
120
+ if (!fieldValidators.onChange) return
121
+
122
+ fieldErrors.onChange = fieldValidators.onChange(validatorCtx)
123
+
124
+ if (
125
+ asyncFieldValidatorStates.onChangeAsync &&
126
+ asyncFieldValidatorStates.onChangeAsync.timeout !== -1
127
+ ) {
128
+ window.clearTimeout(asyncFieldValidatorStates.onChangeAsync.timeout)
129
+ asyncFieldValidatorStates.onChangeAsync.abortController?.abort()
130
+
131
+ asyncFieldValidatorStates.onChangeAsync = {
132
+ epoch: asyncFieldValidatorStates.onChangeAsync.epoch,
133
+ timeout: -1,
134
+ abortController: null,
135
+ }
136
+ delete fieldErrors.onChangeAsync
137
+ } else if (fieldErrors.onChangeAsync) {
138
+ delete fieldErrors.onChangeAsync
139
+ }
140
+
141
+ updateSubscribers()
142
+ updateFieldComponents(name)
143
+ break
144
+ }
145
+ case "onBlur": {
146
+ formFieldsTouched[name] = true
147
+ if (!fieldValidators.onBlur) return
148
+ fieldErrors.onBlur = fieldValidators.onBlur(validatorCtx)
149
+ updateSubscribers()
150
+ updateFieldComponents(name)
151
+ break
152
+ }
153
+ case "onChangeAsync": {
154
+ if (fieldErrors.onChange || !fieldValidators.onChangeAsync) return
155
+
156
+ window.clearTimeout(asyncFieldValidatorStates.onChangeAsync?.timeout)
157
+
158
+ const epoch = (asyncFieldValidatorStates.onChangeAsync?.epoch ?? 0) + 1
159
+ const debounceMs = fieldValidators.onChangeAsyncDebounceMs ?? 0
160
+
161
+ const abortController = new AbortController()
162
+ const asyncValidatorCtx: AsyncValidatorContext<any> = {
163
+ ...validatorCtx,
164
+ abortSignal: abortController.signal,
165
+ }
166
+
167
+ if (debounceMs <= 0) {
168
+ fieldErrors.onChangeAsync = await fieldValidators.onChangeAsync(
169
+ asyncValidatorCtx
170
+ )
171
+ updateSubscribers()
172
+ updateFieldComponents(name)
173
+ return
174
+ }
175
+ asyncFieldValidatorStates.onChangeAsync = {
176
+ abortController,
177
+ timeout: window.setTimeout(() => {
178
+ fieldValidators
179
+ .onChangeAsync?.(asyncValidatorCtx)
180
+ .then((result) => {
181
+ if (fieldErrors.onChange) return
182
+ if (epoch !== asyncFieldValidatorStates.onChangeAsync?.epoch) {
183
+ return
184
+ }
185
+ fieldErrors.onChangeAsync = result
186
+ asyncFieldValidatorStates.onChangeAsync = {
187
+ timeout: -1,
188
+ epoch,
189
+ abortController,
190
+ }
191
+ updateSubscribers()
192
+ updateFieldComponents(name)
193
+ })
194
+ }, debounceMs),
195
+ epoch,
196
+ }
197
+ updateSubscribers()
198
+ break
199
+ }
200
+ case "onSubmit": {
201
+ fieldErrors.onSubmit = fieldValidators.onSubmit?.(validatorCtx)
202
+ }
203
+ }
204
+ }
205
+
206
+ const getFieldValue = <K extends RecordKey<T>>(
207
+ name: K
208
+ ): InferRecordKeyValue<T, K> => {
209
+ return objGet(state, (name as string).split(".")) as InferRecordKeyValue<
210
+ T,
211
+ K
212
+ >
213
+ }
214
+
215
+ const getFieldState = <K extends RecordKey<T>>(
216
+ name: K
217
+ ): FormFieldState<T, K> => {
218
+ let errors: any[] = []
219
+ let isValidating = false
220
+ const fieldErrors = formFieldErrors[name] ?? {}
221
+ const asyncMeta = asyncFormFieldValidators[name] ?? {}
222
+ if (asyncMeta.onChangeAsync && asyncMeta.onChangeAsync.timeout !== -1) {
223
+ isValidating = true
224
+ }
225
+
226
+ if (!isValidating) {
227
+ errors.push(
228
+ ...[
229
+ fieldErrors.onChangeAsync,
230
+ fieldErrors.onMount,
231
+ fieldErrors.onChange,
232
+ fieldErrors.onBlur,
233
+ ].filter(Boolean)
234
+ )
235
+ }
236
+
237
+ return {
238
+ value: getFieldValue(name),
239
+ errors,
240
+ isTouched: !!formFieldsTouched[name],
241
+ isValidating,
242
+ }
243
+ }
244
+
245
+ const onFieldChanged = (name: RecordKey<T>) => {
246
+ validateField(name, "onChange")
247
+ if (formFieldValidators[name]?.onChangeAsync) {
248
+ validateField(name, "onChangeAsync")
249
+ }
250
+
251
+ if (formFieldDependencies[name]) {
252
+ for (const dependentOn of formFieldDependencies[name]) {
253
+ validateField(dependentOn, "onChange")
254
+ if (formFieldValidators[dependentOn]?.onChangeAsync) {
255
+ validateField(dependentOn, "onChangeAsync")
256
+ }
257
+ }
258
+ }
259
+
260
+ formFieldUpdaters.get(name)?.forEach((update) => update())
261
+ }
262
+
263
+ const setFieldValue = <K extends RecordKey<T>>(
264
+ name: K,
265
+ value: InferRecordKeyValue<T, K>
266
+ ) => {
267
+ objSet(state, (name as string).split("."), value)
268
+
269
+ onFieldChanged(name)
270
+ }
271
+
272
+ const removeFieldMeta = (name: RecordKey<T>) => {
273
+ for (const metaMap of [
274
+ formFieldErrors,
275
+ asyncFormFieldValidators,
276
+ formFieldsTouched,
277
+ formFieldValidators,
278
+ ]) {
279
+ delete metaMap[name]
280
+ for (const key in metaMap) {
281
+ if (key.startsWith(`${name}.`)) {
282
+ delete metaMap[key as RecordKey<T>]
283
+ }
284
+ }
285
+ }
286
+
287
+ formFieldUpdaters.delete(name)
288
+ for (const key in formFieldUpdaters) {
289
+ if (key.startsWith(`${name}.`)) {
290
+ formFieldUpdaters.delete(key as RecordKey<T>)
291
+ }
292
+ }
293
+ }
294
+
295
+ const arrayFieldReplace = (name: RecordKey<T>, index: number, value: any) => {
296
+ const path = [...(name as string).split("."), index.toString()]
297
+ objSet(state, path, value)
298
+
299
+ removeFieldMeta(`${name}.${index}` as RecordKey<T>)
300
+
301
+ onFieldChanged(name)
302
+ updateFieldComponents(name)
303
+ }
304
+
305
+ const arrayFieldPush = (name: RecordKey<T>, value: any) => {
306
+ const path = [...(name as string).split(".")]
307
+ const arr = objGet<any[]>(state, path)
308
+ arr.push(value)
309
+
310
+ onFieldChanged(name)
311
+ updateFieldComponents(name)
312
+ }
313
+
314
+ const arrayFieldRemove = (name: RecordKey<T>, index: number) => {
315
+ const path = [...(name as string).split(".")]
316
+ const arr = objGet<any[]>(state, path)
317
+ arr.splice(index, 1)
318
+
319
+ removeFieldMeta(`${name}.${index}` as RecordKey<T>)
320
+
321
+ onFieldChanged(name)
322
+ updateFieldComponents(name)
323
+ }
324
+
325
+ const getErrors = () => {
326
+ const errors: string[] = []
327
+ for (const fieldName in formFieldErrors) {
328
+ const meta = formFieldErrors[fieldName as RecordKey<T>]
329
+ errors.push(
330
+ ...[
331
+ meta.onChangeAsync,
332
+ meta.onMount,
333
+ meta.onChange,
334
+ meta.onBlur,
335
+ ].filter(Boolean)
336
+ )
337
+ }
338
+ return errors
339
+ }
340
+
341
+ const isAnyFieldValidating = () => {
342
+ for (const fieldName in asyncFormFieldValidators) {
343
+ const fieldValidators =
344
+ asyncFormFieldValidators[fieldName as RecordKey<T>]
345
+ if (
346
+ fieldValidators.onChangeAsync &&
347
+ fieldValidators.onChangeAsync.timeout !== -1
348
+ ) {
349
+ return true
350
+ }
351
+ }
352
+ return false
353
+ }
354
+
355
+ const connectField = (name: RecordKey<T>, update: () => void) => {
356
+ if (!formFieldUpdaters.has(name)) {
357
+ formFieldUpdaters.set(name, new Set())
358
+ }
359
+ formFieldUpdaters.get(name)!.add(update)
360
+ }
361
+ const disconnectField = (name: RecordKey<T>, update: () => void) => {
362
+ if (!formFieldUpdaters.has(name)) return
363
+ formFieldUpdaters.get(name)!.delete(update)
364
+
365
+ const asyncValidators = asyncFormFieldValidators[name] ?? {}
366
+ const { abortController, timeout } = asyncValidators.onChangeAsync ?? {}
367
+ window.clearTimeout(timeout)
368
+ abortController?.abort()
369
+ }
370
+
371
+ const reset = (values?: T) => {
372
+ if (values) {
373
+ for (const key in values) {
374
+ state[key] = values[key]
375
+ }
376
+ } else {
377
+ const initialValues = (config.initialValues ?? {}) as T
378
+ const keys = new Set([
379
+ ...Object.keys(state),
380
+ ...Object.keys(initialValues),
381
+ ])
382
+ for (const key of keys) {
383
+ if (key in initialValues) {
384
+ state[key as RecordKey<T>] = structuredClone(
385
+ initialValues[key] as T[RecordKey<T>]
386
+ )
387
+ } else {
388
+ delete state[key]
389
+ }
390
+ }
391
+ }
392
+ for (const fieldName in formFieldsTouched) {
393
+ delete formFieldsTouched[fieldName as RecordKey<T>]
394
+ }
395
+ for (const fieldName in asyncFormFieldValidators) {
396
+ const asyncFieldValidators =
397
+ asyncFormFieldValidators[fieldName as RecordKey<T>]
398
+ const { timeout, abortController } =
399
+ asyncFieldValidators?.onChangeAsync ?? {}
400
+ if (timeout !== -1) {
401
+ window.clearTimeout(timeout)
402
+ }
403
+ abortController?.abort()
404
+ delete asyncFormFieldValidators[fieldName as RecordKey<T>]
405
+ }
406
+ for (const fieldName in formFieldErrors) {
407
+ delete formFieldErrors[fieldName as RecordKey<T>]
408
+ }
409
+ updateSubscribers()
410
+ formFieldUpdaters.forEach((updaters) => {
411
+ updaters.forEach((update) => update())
412
+ })
413
+ }
414
+
415
+ const validateForm = async () => {
416
+ for (const fieldName in formFieldValidators) {
417
+ const fieldValidators = formFieldValidators[fieldName as RecordKey<T>]
418
+ if (fieldValidators?.onChange) {
419
+ await validateField(fieldName as RecordKey<T>, "onChange")
420
+ }
421
+ if (fieldValidators?.onSubmit) {
422
+ await validateField(fieldName as RecordKey<T>, "onSubmit")
423
+ }
424
+ if (
425
+ !formFieldErrors[fieldName as RecordKey<T>]?.onChange &&
426
+ fieldValidators?.onChangeAsync
427
+ ) {
428
+ const value = state[fieldName] as T[RecordKey<T>]
429
+ const abortController = new AbortController()
430
+ const asyncValidators = (asyncFormFieldValidators[
431
+ fieldName as RecordKey<T>
432
+ ] ??= {})
433
+ const epoch = asyncValidators.onChangeAsync?.epoch ?? 0
434
+ asyncValidators.onChangeAsync = {
435
+ timeout: 0,
436
+ epoch,
437
+ abortController,
438
+ }
439
+ const ctx: AsyncValidatorContext<any> = {
440
+ value,
441
+ abortSignal: abortController.signal,
442
+ }
443
+ formFieldErrors[fieldName as RecordKey<T>].onChangeAsync =
444
+ await fieldValidators.onChangeAsync(ctx)
445
+ asyncValidators.onChangeAsync = {
446
+ timeout: -1,
447
+ epoch,
448
+ abortController: null,
449
+ }
450
+ updateFieldComponents(fieldName as RecordKey<T>)
451
+ updateSubscribers()
452
+ }
453
+ }
454
+ return getErrors()
455
+ }
456
+
457
+ const deleteField = (name: RecordKey<T>) => {
458
+ delete state[name]
459
+ delete formFieldErrors[name]
460
+ delete asyncFormFieldValidators[name]
461
+ delete formFieldsTouched[name]
462
+ delete formFieldValidators[name]
463
+ formFieldUpdaters.delete(name)
464
+
465
+ updateSubscribers()
466
+ }
467
+ const resetField = (name: RecordKey<T>) => {
468
+ if (config.initialValues?.[name]) {
469
+ state[name] = config.initialValues[name]
470
+ } else {
471
+ delete state[name]
472
+ }
473
+ delete formFieldErrors[name]
474
+ delete asyncFormFieldValidators[name]
475
+ delete formFieldsTouched[name]
476
+
477
+ updateSubscribers()
478
+ }
479
+
480
+ const getFormContext = (): FormContext<T> => {
481
+ return {
482
+ deleteField,
483
+ resetField,
484
+ setFieldValue,
485
+ validateForm,
486
+ state,
487
+ }
488
+ }
489
+
490
+ const setFieldValidators = <K extends RecordKey<T>>(
491
+ name: K,
492
+ validators: Partial<
493
+ FormFieldValidators<RecordKey<T>, InferRecordKeyValue<T, K>>
494
+ >
495
+ ) => {
496
+ formFieldValidators[name] = validators
497
+ if (validators.dependentOn && validators.dependentOn.length > 0) {
498
+ for (const dependentOn of validators.dependentOn) {
499
+ if (!formFieldDependencies[dependentOn]) {
500
+ formFieldDependencies[dependentOn] = new Set()
501
+ }
502
+ formFieldDependencies[dependentOn].add(name)
503
+ }
504
+ }
505
+ }
506
+
507
+ return {
508
+ subscribers,
509
+ state,
510
+ validateField,
511
+ getFieldState,
512
+ setFieldValue,
513
+ arrayFieldReplace,
514
+ arrayFieldPush,
515
+ arrayFieldRemove,
516
+ connectField,
517
+ disconnectField,
518
+ setFieldValidators,
519
+ getSelectorState,
520
+ validateForm,
521
+ reset,
522
+ getFormContext,
523
+ }
524
+ }
525
+
526
+ export function useForm<T extends Record<string, unknown> = {}>(
527
+ config: UseFormConfig<T>
528
+ ): UseFormState<T> {
529
+ return useHook(
530
+ "useForm",
531
+ {} as UseFormInternalState<T>,
532
+ ({ hook, isInit, isHMR }) => {
533
+ if (__DEV__) {
534
+ if (isInit) {
535
+ hook.dev = {
536
+ initialArgs: [config],
537
+ }
538
+ }
539
+ if (isHMR) {
540
+ const [c] = hook.dev!.initialArgs
541
+ if (safeStringify(c) !== safeStringify(config)) {
542
+ hook.cleanup?.()
543
+ isInit = true
544
+ hook.dev!.initialArgs = [config]
545
+ }
546
+ }
547
+ }
548
+ if (isInit) {
549
+ const $controller = (hook.formController = createFormController(config))
550
+
551
+ hook.Field = function Field<
552
+ Name extends RecordKey<T>,
553
+ Validators extends FormFieldValidators<
554
+ RecordKey<T>,
555
+ InferRecordKeyValue<T, Name>
556
+ >,
557
+ IsArray extends boolean
558
+ >(props: FormFieldProps<T, Name, Validators, IsArray>) {
559
+ const didMount = useRef(false)
560
+ const update = useRequestUpdate()
561
+ if (props.validators) {
562
+ $controller.setFieldValidators(props.name, props.validators)
563
+ }
564
+ useEffect(() => {
565
+ $controller.connectField(props.name, update)
566
+ if (props.validators?.onMount && !didMount.current) {
567
+ didMount.current = true
568
+ $controller.validateField(props.name, "onMount")
569
+ }
570
+ return () => {
571
+ $controller.disconnectField(props.name, update)
572
+ }
573
+ }, [])
574
+
575
+ const fieldState = $controller.getFieldState(props.name)
576
+
577
+ const childProps: FormFieldContext<T, Name, Validators, false> = {
578
+ name: props.name,
579
+ state: fieldState as FormFieldContext<
580
+ T,
581
+ Name,
582
+ Validators,
583
+ false
584
+ >["state"],
585
+ handleChange: (value: InferRecordKeyValue<T, Name>) => {
586
+ $controller.setFieldValue(props.name, value)
587
+ },
588
+ handleBlur: () => {
589
+ $controller.validateField(props.name, "onBlur")
590
+ },
591
+ }
592
+
593
+ if (props.array) {
594
+ const asArrayProps = childProps as FormFieldContext<
595
+ T,
596
+ Name,
597
+ Validators,
598
+ true
599
+ >
600
+ asArrayProps.items = {
601
+ replace: (index: number, value: any) => {
602
+ $controller.arrayFieldReplace(props.name, index, value)
603
+ },
604
+ push: (value: any) => {
605
+ $controller.arrayFieldPush(props.name, value)
606
+ },
607
+ remove: (index: number) => {
608
+ $controller.arrayFieldRemove(props.name, index)
609
+ },
610
+ }
611
+ }
612
+ return Fragment({
613
+ key: useMemo(generateRandomID, []),
614
+ children: props.children(
615
+ childProps as FormFieldContext<T, Name, Validators, IsArray>
616
+ ),
617
+ })
618
+ }
619
+ hook.Subscribe = function Subscribe<
620
+ Selector extends (state: SelectorState<T>) => unknown
621
+ >(props: FormSubscribeProps<T, Selector, ReturnType<Selector>>) {
622
+ const selection = useHook(
623
+ "useFormSubscription",
624
+ { sub: null! as FormStateSubscriber<T> },
625
+ ({ hook, isInit, isHMR, update }) => {
626
+ if (__DEV__) {
627
+ if (isHMR) {
628
+ isInit = true
629
+ hook.cleanup?.()
630
+ }
631
+ }
632
+ if (isInit) {
633
+ hook.sub = {
634
+ selector: props.selector,
635
+ selection: props.selector($controller.getSelectorState()),
636
+ update,
637
+ }
638
+ $controller.subscribers.add(hook.sub)
639
+ hook.cleanup = () => $controller.subscribers.delete(hook.sub)
640
+ }
641
+ return hook.sub.selection
642
+ }
643
+ ) as ReturnType<Selector>
644
+ return props.children(selection)
645
+ }
646
+ }
647
+ return {
648
+ Field: hook.Field,
649
+ Subscribe: hook.Subscribe,
650
+ handleSubmit: async () => {
651
+ const errors = await hook.formController.validateForm()
652
+ const formCtx = hook.formController.getFormContext()
653
+ if (errors.length) return config.onSubmitInvalid?.(formCtx)
654
+ await config.onSubmit?.(formCtx)
655
+ },
656
+ reset: (values?: T) => hook.formController.reset(values),
657
+ getFieldState: <K extends RecordKey<T>>(name: K) =>
658
+ hook.formController.getFieldState(name),
659
+ } satisfies UseFormState<T>
660
+ }
661
+ )
662
+ }