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
package/src/swr.ts ADDED
@@ -0,0 +1,360 @@
1
+ import { __DEV__ } from "./env.js"
2
+ import { useHook } from "./hooks/utils.js"
3
+ import { Signal } from "./signals/base.js"
4
+ import {
5
+ noop,
6
+ deepCompare,
7
+ safeStringify,
8
+ sideEffectsEnabled,
9
+ shallowCompare,
10
+ } from "./utils.js"
11
+
12
+ export type UseSWRState<T> = (
13
+ | {
14
+ data: null
15
+ error: null
16
+ loading: true
17
+ }
18
+ | {
19
+ data: T
20
+ error: null
21
+ loading: false
22
+ }
23
+ | {
24
+ data: null
25
+ error: UseSWRError
26
+ loading: false
27
+ }
28
+ ) & {
29
+ mutate: (callback: () => Promise<T>) => void
30
+ isMutating: Signal<boolean>
31
+ isValidating: Signal<boolean>
32
+ }
33
+
34
+ export class UseSWRError extends Error {
35
+ constructor(message: unknown) {
36
+ super(message instanceof Error ? message.message : String(message))
37
+ this.name = "UseSWRError"
38
+ this.cause = message
39
+ }
40
+ }
41
+
42
+ export type SWRResourceState<T> = {
43
+ data: T | null
44
+ loading: boolean
45
+ error: Error | null
46
+ }
47
+
48
+ export type SWRRetryState = {
49
+ count: number
50
+ timeout: number
51
+ delay: number
52
+ } | null
53
+
54
+ export type SWRCacheEntry<T> = {
55
+ key: any
56
+ resource: Signal<SWRResourceState<T>>
57
+ fetcher: (args: any) => Promise<T>
58
+ subscribers: Set<SWRHook>
59
+ isMutating: Signal<boolean>
60
+ isValidating: Signal<boolean>
61
+ retryState: SWRRetryState
62
+ options: SWROptions
63
+ refreshInterval?: number
64
+ }
65
+
66
+ export type SWRCache = Map<string, SWRCacheEntry<any>>
67
+
68
+ export type SWROptions = {
69
+ /**
70
+ * Specify `false` to disable revalidation on focus
71
+ */
72
+ revalidateOnFocus?: boolean
73
+
74
+ /**
75
+ * Number of times to retry a failed fetch
76
+ * @default Infinity
77
+ */
78
+ maxRetryCount?: number
79
+
80
+ /**
81
+ * Enable automatic refetching of stale data every `refreshInterval` milliseconds
82
+ */
83
+ refreshInterval?: number
84
+
85
+ /**
86
+ * Enable refetching of stale data when offline - useful for offline-first apps
87
+ */
88
+ refetchWhenOffline?: boolean
89
+ }
90
+
91
+ type SWRHook = Kaioken.Hook<{
92
+ strKey: string
93
+ options: SWROptions
94
+ update: () => void
95
+ }>
96
+
97
+ type SWRTupleKey = readonly [any, ...unknown[]]
98
+ export type SWRKey =
99
+ | string
100
+ | SWRTupleKey
101
+ | Record<any, any>
102
+ | null
103
+ | undefined
104
+ | false
105
+
106
+ let SWR_GLOBAL_CACHE: SWRCache
107
+ let IS_ONLINE = false
108
+
109
+ if ("window" in globalThis) {
110
+ SWR_GLOBAL_CACHE = window.__kaioken!.globalState[Symbol.for("SWR_GLOBAL")] ??=
111
+ new Map()
112
+
113
+ IS_ONLINE = navigator.onLine
114
+ window.addEventListener("online", () => {
115
+ IS_ONLINE = true
116
+ })
117
+ window.addEventListener("offline", () => {
118
+ IS_ONLINE = false
119
+ })
120
+
121
+ let blurStart: number | null = null
122
+ window.addEventListener("blur", () => {
123
+ blurStart = Date.now()
124
+ })
125
+ window.addEventListener("focus", () => {
126
+ const blurDuration = blurStart ? Date.now() - blurStart : 0
127
+ blurStart = null
128
+ if (blurDuration < 3_000) return // only trigger revalidation after 3 seconds
129
+
130
+ SWR_GLOBAL_CACHE.forEach((entry) => {
131
+ if (
132
+ entry.subscribers.size === 0 ||
133
+ entry.options.revalidateOnFocus === false ||
134
+ (entry.options.refetchWhenOffline === false && IS_ONLINE === false)
135
+ ) {
136
+ return
137
+ }
138
+
139
+ entry.isValidating.value = true
140
+ performFetch(entry, () => {
141
+ entry.isValidating.value = false
142
+ })
143
+ })
144
+ })
145
+ }
146
+
147
+ function createSWRCacheEntry<T, K extends SWRKey>(
148
+ key: K,
149
+ fetcher: (args: K) => Promise<T>
150
+ ) {
151
+ return {
152
+ key,
153
+ resource: new Signal<SWRResourceState<T>>({
154
+ data: null,
155
+ loading: true,
156
+ error: null,
157
+ }),
158
+ fetcher,
159
+ subscribers: new Set(),
160
+ isMutating: new Signal(false),
161
+ isValidating: new Signal(false),
162
+ retryState: null,
163
+ options: {},
164
+ } satisfies SWRCacheEntry<T>
165
+ }
166
+
167
+ export function preloadSWR<T>(
168
+ key: SWRKey,
169
+ fetcher: (args: SWRKey) => Promise<T>
170
+ ) {
171
+ if (!("window" in globalThis)) return
172
+
173
+ const strKey = safeStringify(key, { functions: false })
174
+ if (!SWR_GLOBAL_CACHE.has(strKey)) {
175
+ const entry = createSWRCacheEntry(key, fetcher)
176
+ SWR_GLOBAL_CACHE.set(strKey, entry)
177
+ performFetch(entry)
178
+ }
179
+ }
180
+
181
+ export function getSWRState<T>(key: SWRKey): SWRCacheEntry<T> | null {
182
+ const strKey = safeStringify(key, { functions: false })
183
+ return SWR_GLOBAL_CACHE.get(strKey) ?? null
184
+ }
185
+
186
+ export function useSWR<T, K extends SWRKey>(
187
+ key: K,
188
+ fetcher: (args: K) => Promise<T>,
189
+ options: SWROptions = {}
190
+ ) {
191
+ if (!sideEffectsEnabled())
192
+ return { data: null, error: null, loading: true } as UseSWRState<T>
193
+
194
+ return useHook(
195
+ "useSWR",
196
+ { strKey: "", options, update: noop } satisfies SWRHook,
197
+ ({ hook, isInit, isHMR, update }) => {
198
+ if (__DEV__) {
199
+ if (isInit) {
200
+ hook.dev = {
201
+ devtools: {
202
+ get: () => ({
203
+ key: hook.strKey,
204
+ value: SWR_GLOBAL_CACHE.get(strKey)!,
205
+ }),
206
+ },
207
+ initialArgs: [key, options],
208
+ }
209
+ }
210
+ if (isHMR) {
211
+ const entry = SWR_GLOBAL_CACHE.get(hook.strKey)
212
+ const [k, o] = hook.dev!.initialArgs
213
+ hook.dev!.initialArgs = [key, options]
214
+
215
+ if (entry) {
216
+ if (!shallowCompare(k, key)) {
217
+ // if key changed, new entry will be created
218
+ } else if (
219
+ entry.fetcher !== fetcher ||
220
+ !shallowCompare(o, options)
221
+ ) {
222
+ entry.fetcher = fetcher
223
+ entry.options = options
224
+ clearTimeout(entry.refreshInterval)
225
+ clearTimeout(entry.retryState?.timeout)
226
+ performFetch(entry)
227
+ }
228
+ }
229
+ }
230
+ }
231
+ hook.options = options
232
+ const strKey = safeStringify(key, { functions: false })
233
+ let entry: SWRCacheEntry<T>
234
+ if (isInit || strKey !== hook.strKey) {
235
+ if (strKey !== hook.strKey) {
236
+ hook.cleanup?.()
237
+ }
238
+ hook.strKey = strKey
239
+ hook.update = update
240
+
241
+ let isNewEntry = false
242
+ if (!SWR_GLOBAL_CACHE.has(strKey)) {
243
+ isNewEntry = true
244
+ entry = createSWRCacheEntry(key, fetcher)
245
+ SWR_GLOBAL_CACHE.set(strKey, entry)
246
+
247
+ entry.resource.subscribe(() => {
248
+ entry.subscribers.forEach((sub) => sub.update())
249
+ })
250
+
251
+ performFetch(entry)
252
+ }
253
+
254
+ entry ??= SWR_GLOBAL_CACHE.get(strKey)!
255
+ const subs = entry.subscribers
256
+ if (subs.size === 0) {
257
+ if (!isNewEntry) {
258
+ entry.isValidating.value = true
259
+ performFetch(entry, () => {
260
+ entry.isValidating.value = false
261
+ })
262
+ }
263
+
264
+ entry.options = options
265
+ if (options.refreshInterval) {
266
+ entry.refreshInterval = window.setInterval(() => {
267
+ if (entry.subscribers.size === 0) return
268
+ performFetch(entry)
269
+ }, options.refreshInterval)
270
+ }
271
+ }
272
+ subs.add(hook)
273
+ hook.cleanup = () => {
274
+ subs.delete(hook)
275
+ if (entry.subscribers.size === 0) {
276
+ window.clearInterval(entry.refreshInterval)
277
+ window.clearTimeout(entry.retryState?.timeout)
278
+ }
279
+ }
280
+ }
281
+
282
+ entry ??= SWR_GLOBAL_CACHE.get(strKey)!
283
+ const { resource, isMutating, isValidating } = entry
284
+ const { data, loading, error } = resource.peek()
285
+
286
+ const mutate: UseSWRState<T>["mutate"] = (callback) => {
287
+ isMutating.value = true
288
+ callback()
289
+ .then((data) => {
290
+ if (entry.retryState) {
291
+ window.clearTimeout(entry.retryState.timeout)
292
+ entry.retryState = null
293
+ entry.isValidating.value = false
294
+ }
295
+ const prev = resource.peek().data
296
+ if (deepCompare(prev, data)) return
297
+ resource.value = {
298
+ data,
299
+ loading: false,
300
+ error: null,
301
+ }
302
+ })
303
+ .finally(() => {
304
+ isMutating.value = false
305
+ })
306
+ }
307
+
308
+ return {
309
+ data,
310
+ loading,
311
+ error,
312
+ isMutating,
313
+ isValidating,
314
+ mutate,
315
+ } as UseSWRState<T>
316
+ }
317
+ )
318
+ }
319
+
320
+ function performFetch<T>(entry: SWRCacheEntry<T>, onSuccess?: () => void) {
321
+ entry.fetcher(entry.key).then(
322
+ (data) => {
323
+ if (entry.retryState) {
324
+ window.clearTimeout(entry.retryState.timeout)
325
+ entry.retryState = null
326
+ }
327
+ const prev = entry.resource.peek().data
328
+ if (!deepCompare(prev, data)) {
329
+ entry.resource.value = {
330
+ data,
331
+ loading: false,
332
+ error: null,
333
+ }
334
+ }
335
+
336
+ onSuccess?.()
337
+ },
338
+ (error) => {
339
+ entry.resource.value = {
340
+ data: null,
341
+ loading: false,
342
+ error: new UseSWRError(error),
343
+ }
344
+ const retryState: SWRRetryState = (entry.retryState ??= {
345
+ count: 0,
346
+ timeout: 0,
347
+ delay: 250,
348
+ })
349
+ if (retryState.count >= (entry.options.maxRetryCount ?? Infinity)) {
350
+ return
351
+ }
352
+
353
+ retryState.timeout = window.setTimeout(() => {
354
+ retryState.count++
355
+ retryState.delay *= 2
356
+ performFetch(entry, onSuccess)
357
+ }, entry.retryState.delay)
358
+ }
359
+ )
360
+ }
@@ -0,0 +1,80 @@
1
+ import { useCallback } from "./hooks/useCallback.js"
2
+ import { useEffect } from "./hooks/useEffect.js"
3
+ import { useLayoutEffect } from "./hooks/useLayoutEffect.js"
4
+ import { useRef } from "./hooks/useRef.js"
5
+ import { useState } from "./hooks/useState.js"
6
+
7
+ export type TransitionState = "entering" | "entered" | "exiting" | "exited"
8
+ type TransitionProps = {
9
+ in: boolean
10
+ /**
11
+ * Initial state of the transition
12
+ * @default "exited"
13
+ */
14
+ initialState?: "entered" | "exited"
15
+ duration?:
16
+ | number
17
+ | {
18
+ in: number
19
+ out: number
20
+ }
21
+ element: (state: "entering" | "entered" | "exiting" | "exited") => JSX.Element
22
+ onTransitionEnd?: (state: "entered" | "exited") => void
23
+ }
24
+
25
+ export function Transition(props: TransitionProps) {
26
+ const [tState, setTState] = useState<TransitionState>(
27
+ props.initialState || "exited"
28
+ )
29
+ const timeoutRef = useRef<number | null>(null)
30
+
31
+ useLayoutEffect(() => {
32
+ if (props.in && tState !== "entered" && tState !== "entering") {
33
+ setTransitionState("entering")
34
+ queueStateChange("entered")
35
+ } else if (!props.in && tState !== "exited" && tState !== "exiting") {
36
+ setTransitionState("exiting")
37
+ queueStateChange("exited")
38
+ }
39
+ }, [props.in, tState])
40
+
41
+ useEffect(() => () => clearTimeout(timeoutRef.current), [])
42
+
43
+ const setTransitionState = useCallback((transitionState: TransitionState) => {
44
+ clearTimeout(timeoutRef.current)
45
+ setTState(transitionState)
46
+ if (transitionState === "entered" || transitionState === "exited") {
47
+ if (props.onTransitionEnd) props.onTransitionEnd(transitionState)
48
+ }
49
+ }, [])
50
+
51
+ const queueStateChange = useCallback(
52
+ (transitionState: "entered" | "exited") => {
53
+ timeoutRef.current = window.setTimeout(
54
+ () => setTransitionState(transitionState),
55
+ getTiming(transitionState, props.duration)
56
+ )
57
+ },
58
+ [props.duration]
59
+ )
60
+
61
+ return props.element(tState)
62
+ }
63
+
64
+ const defaultDuration = 150
65
+ function getTiming(
66
+ transitionState: "entered" | "exited",
67
+ duration: TransitionProps["duration"]
68
+ ): number {
69
+ if (typeof duration === "number") return duration
70
+ switch (transitionState) {
71
+ case "entered":
72
+ return duration?.in ?? defaultDuration
73
+ case "exited":
74
+ return duration?.out ?? defaultDuration
75
+ }
76
+ }
77
+
78
+ function clearTimeout(id: number | null) {
79
+ if (id != null) window.clearTimeout(id)
80
+ }