kiru 0.54.3 → 1.0.1

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