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.
- package/LICENSE +7 -0
- package/README.md +5 -0
- package/package.json +81 -0
- package/src/appContext.ts +186 -0
- package/src/cloneVNode.ts +14 -0
- package/src/constants.ts +146 -0
- package/src/context.ts +56 -0
- package/src/dom.ts +712 -0
- package/src/element.ts +54 -0
- package/src/env.ts +6 -0
- package/src/error.ts +85 -0
- package/src/flags.ts +15 -0
- package/src/form/index.ts +662 -0
- package/src/form/types.ts +261 -0
- package/src/form/utils.ts +19 -0
- package/src/generateId.ts +19 -0
- package/src/globalContext.ts +161 -0
- package/src/globals.ts +21 -0
- package/src/hmr.ts +178 -0
- package/src/hooks/index.ts +14 -0
- package/src/hooks/useAsync.ts +136 -0
- package/src/hooks/useCallback.ts +31 -0
- package/src/hooks/useContext.ts +79 -0
- package/src/hooks/useEffect.ts +44 -0
- package/src/hooks/useEffectEvent.ts +24 -0
- package/src/hooks/useId.ts +42 -0
- package/src/hooks/useLayoutEffect.ts +47 -0
- package/src/hooks/useMemo.ts +33 -0
- package/src/hooks/useReducer.ts +50 -0
- package/src/hooks/useRef.ts +40 -0
- package/src/hooks/useState.ts +62 -0
- package/src/hooks/useSyncExternalStore.ts +59 -0
- package/src/hooks/useViewTransition.ts +26 -0
- package/src/hooks/utils.ts +259 -0
- package/src/hydration.ts +67 -0
- package/src/index.ts +61 -0
- package/src/jsx.ts +11 -0
- package/src/lazy.ts +238 -0
- package/src/memo.ts +48 -0
- package/src/portal.ts +43 -0
- package/src/profiling.ts +105 -0
- package/src/props.ts +36 -0
- package/src/reconciler.ts +531 -0
- package/src/renderToString.ts +91 -0
- package/src/router/index.ts +2 -0
- package/src/router/route.ts +51 -0
- package/src/router/router.ts +275 -0
- package/src/router/routerUtils.ts +49 -0
- package/src/scheduler.ts +522 -0
- package/src/signals/base.ts +237 -0
- package/src/signals/computed.ts +139 -0
- package/src/signals/effect.ts +60 -0
- package/src/signals/globals.ts +11 -0
- package/src/signals/index.ts +12 -0
- package/src/signals/jsx.ts +45 -0
- package/src/signals/types.ts +10 -0
- package/src/signals/utils.ts +12 -0
- package/src/signals/watch.ts +151 -0
- package/src/ssr/client.ts +29 -0
- package/src/ssr/hydrationBoundary.ts +63 -0
- package/src/ssr/index.ts +1 -0
- package/src/ssr/server.ts +124 -0
- package/src/store.ts +241 -0
- package/src/swr.ts +360 -0
- package/src/transition.ts +80 -0
- package/src/types.dom.ts +1250 -0
- package/src/types.ts +209 -0
- package/src/types.utils.ts +39 -0
- package/src/utils.ts +581 -0
- package/src/warning.ts +9 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { __DEV__ } from "../env.js"
|
|
2
|
+
import { noop } from "../utils.js"
|
|
3
|
+
import { depsRequireChange, sideEffectsEnabled, useHook } from "./utils.js"
|
|
4
|
+
|
|
5
|
+
export type UseAsyncState<T> = (
|
|
6
|
+
| /** loading*/ {
|
|
7
|
+
data: null
|
|
8
|
+
loading: true
|
|
9
|
+
error: null
|
|
10
|
+
}
|
|
11
|
+
| /** loaded */ {
|
|
12
|
+
data: T
|
|
13
|
+
loading: false
|
|
14
|
+
error: null
|
|
15
|
+
}
|
|
16
|
+
| /** error */ {
|
|
17
|
+
data: null
|
|
18
|
+
loading: false
|
|
19
|
+
error: UseAsyncError
|
|
20
|
+
}
|
|
21
|
+
) & {
|
|
22
|
+
invalidate: () => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type UseAsyncCallbackContext = {
|
|
26
|
+
abortSignal: AbortSignal
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class UseAsyncError extends Error {
|
|
30
|
+
constructor(message: unknown) {
|
|
31
|
+
super(message instanceof Error ? message.message : String(message))
|
|
32
|
+
this.name = "UseAsyncError"
|
|
33
|
+
this.cause = message
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type AsyncTaskState<T> = {
|
|
38
|
+
data: T | null
|
|
39
|
+
loading: boolean
|
|
40
|
+
error: Error | null
|
|
41
|
+
abortController: AbortController
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Runs an asynchronous function on initial render, or when a value provided in the [dependency
|
|
45
|
+
* array](https://kaioken.dev/docs/hooks/dependency-arrays) has changed.
|
|
46
|
+
*
|
|
47
|
+
* @see https://kaioken.dev/docs/hooks/useAsync
|
|
48
|
+
*/
|
|
49
|
+
export function useAsync<T>(
|
|
50
|
+
func: (ctx: UseAsyncCallbackContext) => Promise<T>,
|
|
51
|
+
deps: unknown[]
|
|
52
|
+
): UseAsyncState<T> {
|
|
53
|
+
if (!sideEffectsEnabled())
|
|
54
|
+
return {
|
|
55
|
+
data: null,
|
|
56
|
+
loading: true,
|
|
57
|
+
error: null,
|
|
58
|
+
invalidate: noop,
|
|
59
|
+
}
|
|
60
|
+
return useHook(
|
|
61
|
+
"useAsync",
|
|
62
|
+
{
|
|
63
|
+
deps,
|
|
64
|
+
id: 0,
|
|
65
|
+
task: null as any as AsyncTaskState<T>,
|
|
66
|
+
load: noop as (
|
|
67
|
+
func: (ctx: UseAsyncCallbackContext) => Promise<T>
|
|
68
|
+
) => void,
|
|
69
|
+
},
|
|
70
|
+
({ hook, isInit, isHMR, update }) => {
|
|
71
|
+
if (__DEV__) {
|
|
72
|
+
hook.dev = { devtools: { get: () => ({ value: hook.task }) } }
|
|
73
|
+
if (isHMR) {
|
|
74
|
+
isInit = true
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (isInit) {
|
|
78
|
+
hook.cleanup = () => abortTask(hook.task)
|
|
79
|
+
hook.load = (func) => {
|
|
80
|
+
let invalidated = false
|
|
81
|
+
const abortController = new AbortController()
|
|
82
|
+
abortController.signal.addEventListener("abort", () => {
|
|
83
|
+
invalidated = true
|
|
84
|
+
})
|
|
85
|
+
const id = ++hook.id
|
|
86
|
+
const task: AsyncTaskState<T> = (hook.task = {
|
|
87
|
+
abortController,
|
|
88
|
+
data: null,
|
|
89
|
+
loading: true,
|
|
90
|
+
error: null,
|
|
91
|
+
})
|
|
92
|
+
func({ abortSignal: abortController.signal })
|
|
93
|
+
.then((result: T) => {
|
|
94
|
+
if (id !== hook.id) abortTask(task)
|
|
95
|
+
if (invalidated) return
|
|
96
|
+
|
|
97
|
+
task.data = result
|
|
98
|
+
task.loading = false
|
|
99
|
+
task.error = null
|
|
100
|
+
update()
|
|
101
|
+
})
|
|
102
|
+
.catch((error) => {
|
|
103
|
+
if (id !== hook.id) abortTask(task)
|
|
104
|
+
if (invalidated) return
|
|
105
|
+
|
|
106
|
+
task.data = null
|
|
107
|
+
task.loading = false
|
|
108
|
+
task.error = new UseAsyncError(error)
|
|
109
|
+
update()
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (isInit || depsRequireChange(deps, hook.deps)) {
|
|
115
|
+
abortTask(hook.task)
|
|
116
|
+
hook.deps = deps
|
|
117
|
+
hook.load(func)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const { abortController, ...rest } = hook.task
|
|
121
|
+
return {
|
|
122
|
+
...rest,
|
|
123
|
+
invalidate: () => {
|
|
124
|
+
abortTask(hook.task)
|
|
125
|
+
hook.load(func)
|
|
126
|
+
update()
|
|
127
|
+
},
|
|
128
|
+
} as UseAsyncState<T>
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function abortTask<T>(task: AsyncTaskState<T> | null): void {
|
|
134
|
+
if (task === null || task.abortController.signal.aborted) return
|
|
135
|
+
task.abortController.abort()
|
|
136
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { __DEV__ } from "../env.js"
|
|
2
|
+
import { depsRequireChange, useHook, sideEffectsEnabled } from "./utils.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a memoized callback function.
|
|
6
|
+
*
|
|
7
|
+
* @see https://kaioken.dev/docs/hooks/useCallback
|
|
8
|
+
*/
|
|
9
|
+
export function useCallback<T extends Function>(
|
|
10
|
+
callback: T,
|
|
11
|
+
deps: unknown[]
|
|
12
|
+
): T {
|
|
13
|
+
if (!sideEffectsEnabled()) return callback
|
|
14
|
+
return useHook("useCallback", { callback, deps }, ({ hook, isHMR }) => {
|
|
15
|
+
if (__DEV__) {
|
|
16
|
+
hook.dev = {
|
|
17
|
+
devtools: {
|
|
18
|
+
get: () => ({ callback: hook.callback, dependencies: hook.deps }),
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
if (isHMR) {
|
|
22
|
+
hook.deps = []
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (depsRequireChange(deps, hook.deps)) {
|
|
26
|
+
hook.deps = deps
|
|
27
|
+
hook.callback = callback
|
|
28
|
+
}
|
|
29
|
+
return hook.callback
|
|
30
|
+
})
|
|
31
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { ContextProviderNode } from "../types.utils.js"
|
|
2
|
+
import { type HookCallbackState, useHook } from "./utils.js"
|
|
3
|
+
import { __DEV__ } from "../env.js"
|
|
4
|
+
import { $CONTEXT_PROVIDER } from "../constants.js"
|
|
5
|
+
|
|
6
|
+
type UseContextHookState<T> = {
|
|
7
|
+
provider?: ContextProviderNode<T>
|
|
8
|
+
context: Kaioken.Context<T>
|
|
9
|
+
warnIfNotFound: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Gets the current value of a context provider created by the context.
|
|
14
|
+
*
|
|
15
|
+
* @see https://kaioken.dev/docs/hooks/useContext
|
|
16
|
+
*/
|
|
17
|
+
export function useContext<T>(
|
|
18
|
+
context: Kaioken.Context<T>,
|
|
19
|
+
warnIfNotFound = true
|
|
20
|
+
): T {
|
|
21
|
+
return useHook(
|
|
22
|
+
"useContext",
|
|
23
|
+
{
|
|
24
|
+
context,
|
|
25
|
+
warnIfNotFound,
|
|
26
|
+
} satisfies UseContextHookState<T>,
|
|
27
|
+
useContextCallback as typeof useContextCallback<T>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const useContextCallback = <T>({
|
|
32
|
+
hook,
|
|
33
|
+
isInit,
|
|
34
|
+
vNode,
|
|
35
|
+
}: HookCallbackState<UseContextHookState<T>>) => {
|
|
36
|
+
if (__DEV__) {
|
|
37
|
+
hook.dev = {
|
|
38
|
+
devtools: {
|
|
39
|
+
get: () => ({
|
|
40
|
+
contextName: hook.context.Provider.displayName || "",
|
|
41
|
+
value: hook.provider
|
|
42
|
+
? hook.provider.props.value
|
|
43
|
+
: hook.context.default(),
|
|
44
|
+
}),
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (isInit) {
|
|
49
|
+
let n = vNode.parent
|
|
50
|
+
while (n) {
|
|
51
|
+
if (n.type === $CONTEXT_PROVIDER) {
|
|
52
|
+
const provider = n as ContextProviderNode<T>
|
|
53
|
+
const { ctx, value, dependents } = provider.props
|
|
54
|
+
if (ctx === hook.context) {
|
|
55
|
+
dependents.add(vNode)
|
|
56
|
+
hook.cleanup = () => dependents.delete(vNode)
|
|
57
|
+
hook.provider = provider
|
|
58
|
+
return value
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
n = n.parent
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!hook.provider) {
|
|
65
|
+
if (__DEV__) {
|
|
66
|
+
hook.warnIfNotFound && warnProviderNotFound(hook.context)
|
|
67
|
+
}
|
|
68
|
+
return hook.context.default()
|
|
69
|
+
}
|
|
70
|
+
return hook.provider.props.value
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const contextsNotFound = new Set<Kaioken.Context<any>>()
|
|
74
|
+
function warnProviderNotFound(ctx: Kaioken.Context<any>) {
|
|
75
|
+
if (!contextsNotFound.has(ctx)) {
|
|
76
|
+
contextsNotFound.add(ctx)
|
|
77
|
+
console.warn("[kaioken]: Unable to find context provider")
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { __DEV__ } from "../env.js"
|
|
2
|
+
import {
|
|
3
|
+
cleanupHook,
|
|
4
|
+
depsRequireChange,
|
|
5
|
+
sideEffectsEnabled,
|
|
6
|
+
useHook,
|
|
7
|
+
} from "./utils.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Runs a function after the component is rendered, or when a value provided in the optional [dependency
|
|
11
|
+
* array](https://kaioken.dev/docs/hooks/dependency-arrays) has changed.
|
|
12
|
+
*
|
|
13
|
+
* @see https://kaioken.dev/docs/hooks/useEffect
|
|
14
|
+
* */
|
|
15
|
+
export function useEffect(
|
|
16
|
+
callback: () => void | (() => void),
|
|
17
|
+
deps?: unknown[]
|
|
18
|
+
): void {
|
|
19
|
+
if (!sideEffectsEnabled()) return
|
|
20
|
+
return useHook(
|
|
21
|
+
"useEffect",
|
|
22
|
+
{ deps },
|
|
23
|
+
({ hook, isInit, isHMR, queueEffect }) => {
|
|
24
|
+
if (__DEV__) {
|
|
25
|
+
hook.dev = {
|
|
26
|
+
devtools: { get: () => ({ callback, dependencies: hook.deps }) },
|
|
27
|
+
}
|
|
28
|
+
if (isHMR) {
|
|
29
|
+
isInit = true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (isInit || depsRequireChange(deps, hook.deps)) {
|
|
33
|
+
hook.deps = deps
|
|
34
|
+
cleanupHook(hook)
|
|
35
|
+
queueEffect(() => {
|
|
36
|
+
const cleanup = callback()
|
|
37
|
+
if (typeof cleanup === "function") {
|
|
38
|
+
hook.cleanup = cleanup
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { node } from "../globals.js"
|
|
2
|
+
import { __DEV__ } from "../env.js"
|
|
3
|
+
import { sideEffectsEnabled, useHook } from "./utils.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Wraps a function to be called within effects and other callbacks.
|
|
7
|
+
* The function will be called with the same arguments as the original function.
|
|
8
|
+
*
|
|
9
|
+
* @see https://kaioken.dev/docs/hooks/useEffectEvent
|
|
10
|
+
*/
|
|
11
|
+
export function useEffectEvent<T extends Function>(callback: T): T {
|
|
12
|
+
if (!sideEffectsEnabled()) return callback
|
|
13
|
+
return useHook("useEffectEvent", { callback }, ({ hook }) => {
|
|
14
|
+
hook.callback = callback
|
|
15
|
+
return function () {
|
|
16
|
+
if (node.current) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
"A function wrapped in useEffectEvent can't be called during rendering."
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
return hook.callback.apply(void 0, arguments)
|
|
22
|
+
} as any as T
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { __DEV__ } from "../env.js"
|
|
2
|
+
import { HookCallback, useHook } from "./utils.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a unique id for the current node. This is derived based on the node's position in your application tree.
|
|
6
|
+
* Useful for assigning predictable ids to elements.
|
|
7
|
+
*
|
|
8
|
+
* @see https://kaioken.dev/docs/hooks/useId
|
|
9
|
+
*/
|
|
10
|
+
export function useId(): string {
|
|
11
|
+
return useHook("useId", createUseIdState, useIdCallback)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type UseIdState = {
|
|
15
|
+
id: string
|
|
16
|
+
idx: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const createUseIdState = (): UseIdState => ({
|
|
20
|
+
id: "",
|
|
21
|
+
idx: 0,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const useIdCallback: HookCallback<UseIdState> = ({ hook, isInit, vNode }) => {
|
|
25
|
+
if (__DEV__) {
|
|
26
|
+
hook.dev = {
|
|
27
|
+
devtools: { get: () => ({ id: hook.id }) },
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (isInit || vNode.index !== hook.idx) {
|
|
31
|
+
hook.idx = vNode.index
|
|
32
|
+
const accumulator: number[] = []
|
|
33
|
+
let n: Kaioken.VNode | null = vNode
|
|
34
|
+
while (n) {
|
|
35
|
+
accumulator.push(n.index)
|
|
36
|
+
accumulator.push(n.depth)
|
|
37
|
+
n = n.parent
|
|
38
|
+
}
|
|
39
|
+
hook.id = `k:${BigInt(accumulator.join("")).toString(36)}`
|
|
40
|
+
}
|
|
41
|
+
return hook.id
|
|
42
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { __DEV__ } from "../env.js"
|
|
2
|
+
import {
|
|
3
|
+
cleanupHook,
|
|
4
|
+
depsRequireChange,
|
|
5
|
+
sideEffectsEnabled,
|
|
6
|
+
useHook,
|
|
7
|
+
} from "./utils.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Runs a function before the component is rendered, or when a value provided in the optional [dependency
|
|
11
|
+
* array](https://kaioken.dev/docs/hooks/dependency-arrays) has changed.
|
|
12
|
+
*
|
|
13
|
+
* @see https://kaioken.dev/docs/hooks/useLayoutEffect
|
|
14
|
+
* */
|
|
15
|
+
export function useLayoutEffect(
|
|
16
|
+
callback: () => void | (() => void),
|
|
17
|
+
deps?: unknown[]
|
|
18
|
+
): void {
|
|
19
|
+
if (!sideEffectsEnabled()) return
|
|
20
|
+
return useHook(
|
|
21
|
+
"useLayoutEffect",
|
|
22
|
+
{ deps },
|
|
23
|
+
({ hook, isInit, isHMR, queueEffect }) => {
|
|
24
|
+
if (__DEV__) {
|
|
25
|
+
hook.dev = {
|
|
26
|
+
devtools: { get: () => ({ callback, dependencies: hook.deps }) },
|
|
27
|
+
}
|
|
28
|
+
if (isHMR) {
|
|
29
|
+
isInit = true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (isInit || depsRequireChange(deps, hook.deps)) {
|
|
33
|
+
hook.deps = deps
|
|
34
|
+
cleanupHook(hook)
|
|
35
|
+
queueEffect(
|
|
36
|
+
() => {
|
|
37
|
+
const cleanup = callback()
|
|
38
|
+
if (typeof cleanup === "function") {
|
|
39
|
+
hook.cleanup = cleanup
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{ immediate: true }
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { __DEV__ } from "../env.js"
|
|
2
|
+
import { depsRequireChange, sideEffectsEnabled, useHook } from "./utils.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a memoized value that only changes when the [dependency
|
|
6
|
+
* array](https://kaioken.dev/docs/hooks/dependency-arrays) has changed.
|
|
7
|
+
*
|
|
8
|
+
* @see https://kaioken.dev/docs/hooks/useMemo
|
|
9
|
+
*/
|
|
10
|
+
export function useMemo<T>(factory: () => T, deps: unknown[]): T {
|
|
11
|
+
if (!sideEffectsEnabled()) return factory()
|
|
12
|
+
return useHook(
|
|
13
|
+
"useMemo",
|
|
14
|
+
{ deps, value: undefined as T },
|
|
15
|
+
({ hook, isInit, isHMR }) => {
|
|
16
|
+
if (__DEV__) {
|
|
17
|
+
hook.dev = {
|
|
18
|
+
devtools: {
|
|
19
|
+
get: () => ({ value: hook.value, dependencies: hook.deps }),
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
if (isHMR) {
|
|
23
|
+
isInit = true
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (isInit || depsRequireChange(deps, hook.deps)) {
|
|
27
|
+
hook.deps = deps
|
|
28
|
+
hook.value = factory()
|
|
29
|
+
}
|
|
30
|
+
return hook.value
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { __DEV__ } from "../env.js"
|
|
2
|
+
import { noop } from "../utils.js"
|
|
3
|
+
import { sideEffectsEnabled, useHook } from "./utils.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates 'dispatcher-driven' state.
|
|
7
|
+
*
|
|
8
|
+
* @see https://kaioken.dev/docs/hooks/useReducer
|
|
9
|
+
*/
|
|
10
|
+
export function useReducer<T, A>(
|
|
11
|
+
reducer: (state: T, action: A) => T,
|
|
12
|
+
state: T
|
|
13
|
+
): readonly [T, (action: A) => void] {
|
|
14
|
+
if (!sideEffectsEnabled()) return [state, noop]
|
|
15
|
+
|
|
16
|
+
return useHook(
|
|
17
|
+
"useReducer",
|
|
18
|
+
{ state, dispatch: noop as (action: A) => void },
|
|
19
|
+
({ hook, isInit, isHMR, update }) => {
|
|
20
|
+
if (__DEV__) {
|
|
21
|
+
if (isInit) {
|
|
22
|
+
hook.dev = {
|
|
23
|
+
devtools: {
|
|
24
|
+
get: () => ({ value: hook.state }),
|
|
25
|
+
set: ({ value }) => (hook.state = value),
|
|
26
|
+
} satisfies Kaioken.HookDevtoolsProvisions<{ value: T }>,
|
|
27
|
+
initialArgs: [reducer, state],
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (isHMR) {
|
|
31
|
+
const [r, s] = hook.dev!.initialArgs
|
|
32
|
+
if (r !== reducer || s !== state) {
|
|
33
|
+
hook.state = state
|
|
34
|
+
isInit = true
|
|
35
|
+
hook.dev!.initialArgs = [reducer, state]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (isInit) {
|
|
40
|
+
hook.dispatch = (action: A) => {
|
|
41
|
+
const newState = reducer(hook.state, action)
|
|
42
|
+
if (Object.is(hook.state, newState)) return
|
|
43
|
+
hook.state = newState
|
|
44
|
+
update()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return [hook.state, hook.dispatch] as const
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { __DEV__ } from "../env.js"
|
|
2
|
+
import { sideEffectsEnabled, useHook } from "./utils.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a ref object. Useful for persisting values between renders or getting
|
|
6
|
+
* a reference to an element.
|
|
7
|
+
*
|
|
8
|
+
* @see https://kaioken.dev/docs/hooks/useRef
|
|
9
|
+
*/
|
|
10
|
+
export function useRef<T>(initialValue: T): Kaioken.MutableRefObject<T>
|
|
11
|
+
export function useRef<T>(initialValue: T | null): Kaioken.RefObject<T>
|
|
12
|
+
export function useRef<T = undefined>(): Kaioken.MutableRefObject<T | undefined>
|
|
13
|
+
export function useRef<T>(initialValue?: T | null) {
|
|
14
|
+
if (!sideEffectsEnabled()) return { current: initialValue }
|
|
15
|
+
return useHook(
|
|
16
|
+
"useRef",
|
|
17
|
+
{ ref: { current: initialValue } },
|
|
18
|
+
({ hook, isInit, isHMR }) => {
|
|
19
|
+
if (__DEV__) {
|
|
20
|
+
if (isInit) {
|
|
21
|
+
hook.dev = {
|
|
22
|
+
devtools: {
|
|
23
|
+
get: () => ({ value: hook.ref.current! }),
|
|
24
|
+
set: ({ value }) => (hook.ref.current = value),
|
|
25
|
+
} satisfies Kaioken.HookDevtoolsProvisions<{ value: T }>,
|
|
26
|
+
initialArgs: [hook.ref.current],
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (isHMR) {
|
|
30
|
+
const [v] = hook.dev!.initialArgs
|
|
31
|
+
if (v !== initialValue) {
|
|
32
|
+
hook.ref = { current: initialValue }
|
|
33
|
+
hook.dev!.initialArgs = [initialValue]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return hook.ref
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { __DEV__ } from "../env.js"
|
|
2
|
+
import { noop } from "../utils.js"
|
|
3
|
+
import { sideEffectsEnabled, useHook } from "./utils.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a stateful value, and returns the current value and a function to update it.
|
|
7
|
+
*
|
|
8
|
+
* @see https://kaioken.dev/docs/hooks/useState
|
|
9
|
+
*/
|
|
10
|
+
export function useState<T>(
|
|
11
|
+
initial: T | (() => T)
|
|
12
|
+
): readonly [T, (value: Kaioken.StateSetter<T>) => void] {
|
|
13
|
+
if (!sideEffectsEnabled()) {
|
|
14
|
+
return [
|
|
15
|
+
typeof initial === "function" ? (initial as Function)() : initial,
|
|
16
|
+
noop,
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
return useHook(
|
|
20
|
+
"useState",
|
|
21
|
+
{
|
|
22
|
+
state: undefined as T,
|
|
23
|
+
dispatch: noop as (value: Kaioken.StateSetter<T>) => void,
|
|
24
|
+
},
|
|
25
|
+
({ hook, isInit, update, isHMR }) => {
|
|
26
|
+
if (__DEV__) {
|
|
27
|
+
if (isInit) {
|
|
28
|
+
hook.dev = {
|
|
29
|
+
devtools: {
|
|
30
|
+
get: () => ({ value: hook.state }),
|
|
31
|
+
set: ({ value }) => (hook.state = value),
|
|
32
|
+
} satisfies Kaioken.HookDevtoolsProvisions<{ value: T }>,
|
|
33
|
+
initialArgs: [initial],
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (isHMR) {
|
|
37
|
+
const [v] = hook.dev!.initialArgs
|
|
38
|
+
if (v !== initial) {
|
|
39
|
+
isInit = true
|
|
40
|
+
hook.dev!.initialArgs = [initial]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (isInit) {
|
|
46
|
+
hook.state =
|
|
47
|
+
typeof initial === "function" ? (initial as Function)() : initial
|
|
48
|
+
hook.dispatch = (setter: Kaioken.StateSetter<T>) => {
|
|
49
|
+
const newState =
|
|
50
|
+
typeof setter === "function"
|
|
51
|
+
? (setter as Function)(hook.state)
|
|
52
|
+
: setter
|
|
53
|
+
if (Object.is(hook.state, newState)) return
|
|
54
|
+
hook.state = newState
|
|
55
|
+
update()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return [hook.state, hook.dispatch] as const
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { node } from "../globals.js"
|
|
2
|
+
import { KaiokenError } from "../error.js"
|
|
3
|
+
import { noop } from "../utils.js"
|
|
4
|
+
import { sideEffectsEnabled, useHook } from "./utils.js"
|
|
5
|
+
import { __DEV__ } from "../env.js"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Allows you to use a generic external store as long as it provides
|
|
9
|
+
* a subscribe function and a way to get its current state.
|
|
10
|
+
*
|
|
11
|
+
* @see https://kaioken.dev/docs/hooks/useSyncExternalStore
|
|
12
|
+
*/
|
|
13
|
+
export function useSyncExternalStore<T>(
|
|
14
|
+
subscribe: (callback: () => void) => () => void,
|
|
15
|
+
getState: () => T,
|
|
16
|
+
getServerState?: () => T
|
|
17
|
+
): T {
|
|
18
|
+
if (!sideEffectsEnabled()) {
|
|
19
|
+
if (getServerState === undefined) {
|
|
20
|
+
throw new KaiokenError({
|
|
21
|
+
message:
|
|
22
|
+
"useSyncExternalStore must receive a getServerSnapshot function if the component is rendered on the server.",
|
|
23
|
+
vNode: node.current!,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
return getServerState()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return useHook(
|
|
30
|
+
"useSyncExternalStore",
|
|
31
|
+
{
|
|
32
|
+
state: null as T,
|
|
33
|
+
unsubscribe: noop as () => void,
|
|
34
|
+
subscribe,
|
|
35
|
+
},
|
|
36
|
+
({ hook, isInit, update }) => {
|
|
37
|
+
if (__DEV__) {
|
|
38
|
+
hook.dev = {
|
|
39
|
+
devtools: { get: () => ({ value: hook.state }) },
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (isInit || hook.subscribe !== subscribe) {
|
|
43
|
+
hook.state = getState()
|
|
44
|
+
hook.subscribe = subscribe
|
|
45
|
+
hook.unsubscribe = subscribe(() => {
|
|
46
|
+
const newState = getState()
|
|
47
|
+
if (Object.is(hook.state, newState)) return
|
|
48
|
+
hook.state = newState
|
|
49
|
+
update()
|
|
50
|
+
})
|
|
51
|
+
hook.cleanup = () => {
|
|
52
|
+
hook.unsubscribe()
|
|
53
|
+
hook.unsubscribe = noop
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return hook.state
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ctx, node } from "../globals.js"
|
|
2
|
+
import { noop } from "../utils.js"
|
|
3
|
+
import { sideEffectsEnabled } from "./utils.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Allows you to easily use the [View Transition API](https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition)
|
|
7
|
+
* by wrapping the `callback` in a `document.startViewTransition` call.
|
|
8
|
+
*
|
|
9
|
+
* Falls back to the regular `callback` if not supported.
|
|
10
|
+
*
|
|
11
|
+
* @see https://kaioken.dev/docs/hooks/useViewTransition
|
|
12
|
+
*/
|
|
13
|
+
export function useViewTransition() {
|
|
14
|
+
if (!sideEffectsEnabled()) return noop
|
|
15
|
+
const appCtx = ctx.current
|
|
16
|
+
return (callback: () => void) => {
|
|
17
|
+
if (node.current) {
|
|
18
|
+
throw new Error("useViewTransition can't be called during rendering.")
|
|
19
|
+
}
|
|
20
|
+
if (!document.startViewTransition) return callback()
|
|
21
|
+
document.startViewTransition(() => {
|
|
22
|
+
callback()
|
|
23
|
+
appCtx.flushSync()
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
}
|