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,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
+ }