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,29 @@
1
+ import type { AppContext, AppContextOptions } from "../appContext"
2
+ import { hydrationStack } from "../hydration.js"
3
+ import { renderMode } from "../globals.js"
4
+ import { mount } from "../index.js"
5
+
6
+ export function hydrate<T extends Record<string, unknown>>(
7
+ appFunc: (props: T) => JSX.Element,
8
+ container: AppContextOptions,
9
+ appProps?: T
10
+ ): Promise<AppContext<T>>
11
+
12
+ export function hydrate<T extends Record<string, unknown>>(
13
+ appFunc: (props: T) => JSX.Element,
14
+ container: HTMLElement,
15
+ appProps?: T
16
+ ): Promise<AppContext<T>>
17
+
18
+ export function hydrate<T extends Record<string, unknown>>(
19
+ appFunc: (props: T) => JSX.Element,
20
+ optionsOrRoot: HTMLElement | AppContextOptions,
21
+ appProps = {} as T
22
+ ) {
23
+ hydrationStack.clear()
24
+ const prevRenderMode = renderMode.current
25
+ renderMode.current = "hydrate"
26
+ return mount(appFunc, optionsOrRoot as any, appProps).finally(() => {
27
+ renderMode.current = prevRenderMode
28
+ })
29
+ }
@@ -0,0 +1,63 @@
1
+ import { createContext } from "../context.js"
2
+ import { $HYDRATION_BOUNDARY } from "../constants.js"
3
+ import { createElement, Fragment } from "../element.js"
4
+ import { renderMode } from "../globals.js"
5
+
6
+ type EventsArray = (keyof GlobalEventHandlersEventMap)[]
7
+
8
+ export const HYDRATION_BOUNDARY_MARKER = "kaioken:h-boundary"
9
+ export const DEFAULT_INTERACTION_EVENTS = [
10
+ "pointerdown",
11
+ "keydown",
12
+ "focus",
13
+ "input",
14
+ ] as const satisfies EventsArray
15
+
16
+ export type HydrationBoundaryMode = "eager" | "interaction"
17
+ export type HydrationBoundaryProps<T extends HydrationBoundaryMode> = {
18
+ /**
19
+ * Determines the strategy to use when hydrating the boundary.
20
+ * - `eager`: hydrate immediately.
21
+ * - `interaction`: hydrate upon the first user interaction.
22
+ * @default "eager"
23
+ */
24
+ mode?: T
25
+ children: JSX.Children
26
+ } & (T extends "interaction"
27
+ ? {
28
+ /**
29
+ * List of events that will trigger the hydration.
30
+ * @default ["pointerdown", "keydown", "focus", "input"]
31
+ */
32
+ events?: EventsArray
33
+ }
34
+ : {})
35
+
36
+ export const HydrationBoundaryContext = createContext<{
37
+ mode: HydrationBoundaryMode
38
+ events: string[]
39
+ }>(null!)
40
+
41
+ export function Experimental_HydrationBoundary<T extends HydrationBoundaryMode>(
42
+ props: HydrationBoundaryProps<T>
43
+ ) {
44
+ const provider = createElement(
45
+ HydrationBoundaryContext.Provider,
46
+ {
47
+ value: {
48
+ mode: props.mode || "eager",
49
+ // @ts-expect-error this is fine
50
+ events: props.events ?? DEFAULT_INTERACTION_EVENTS,
51
+ },
52
+ },
53
+ createElement($HYDRATION_BOUNDARY, props)
54
+ )
55
+ if (renderMode.current === "string" || renderMode.current === "stream") {
56
+ /**
57
+ * in order to ensure consistent tree structure, we're simulating
58
+ * the generated loader + wrapper components here.
59
+ */
60
+ return Fragment({ children: Fragment({ children: provider }) })
61
+ }
62
+ return provider
63
+ }
@@ -0,0 +1 @@
1
+ export * from "./hydrationBoundary.js"
@@ -0,0 +1,124 @@
1
+ import { Readable } from "node:stream"
2
+ import { Fragment } from "../element.js"
3
+ import { AppContext, createAppContext } from "../appContext.js"
4
+ import { renderMode, ctx, node, nodeToCtxMap } from "../globals.js"
5
+ import {
6
+ isVNode,
7
+ encodeHtmlEntities,
8
+ propsToElementAttributes,
9
+ isExoticType,
10
+ } from "../utils.js"
11
+ import { Signal } from "../signals/base.js"
12
+ import { $HYDRATION_BOUNDARY, voidElements } from "../constants.js"
13
+ import { assertValidElementProps } from "../props.js"
14
+ import { HYDRATION_BOUNDARY_MARKER } from "./hydrationBoundary.js"
15
+ import { __DEV__ } from "../env.js"
16
+
17
+ type RequestState = {
18
+ stream: Readable
19
+ ctx: AppContext
20
+ }
21
+
22
+ export function renderToReadableStream<T extends Record<string, unknown>>(
23
+ appFunc: (props: T) => JSX.Element,
24
+ appProps = {} as T
25
+ ): Readable {
26
+ const prev = renderMode.current
27
+ renderMode.current = "stream"
28
+ const state: RequestState = {
29
+ stream: new Readable(),
30
+ ctx: createAppContext(appFunc, appProps, { rootType: Fragment }),
31
+ }
32
+ const prevCtx = ctx.current
33
+ ctx.current = state.ctx
34
+ renderToStream_internal(state, state.ctx.rootNode, null, 0)
35
+ state.stream.push(null)
36
+ renderMode.current = prev
37
+ ctx.current = prevCtx
38
+
39
+ return state.stream
40
+ }
41
+
42
+ function renderToStream_internal(
43
+ state: RequestState,
44
+ el: unknown,
45
+ parent: Kaioken.VNode | null,
46
+ idx: number
47
+ ): void {
48
+ if (el === null) return
49
+ if (el === undefined) return
50
+ if (typeof el === "boolean") return
51
+ if (typeof el === "string") {
52
+ state.stream.push(encodeHtmlEntities(el))
53
+ return
54
+ }
55
+ if (typeof el === "number" || typeof el === "bigint") {
56
+ state.stream.push(el.toString())
57
+ return
58
+ }
59
+ if (el instanceof Array) {
60
+ el.forEach((c, i) => renderToStream_internal(state, c, parent, i))
61
+ return
62
+ }
63
+ if (Signal.isSignal(el)) {
64
+ state.stream.push(String(el.peek()))
65
+ return
66
+ }
67
+ if (!isVNode(el)) {
68
+ state.stream.push(String(el))
69
+ return
70
+ }
71
+ el.parent = parent
72
+ el.depth = (parent?.depth ?? -1) + 1
73
+ el.index = idx
74
+ const props = el.props ?? {}
75
+ const children = props.children
76
+ const type = el.type
77
+ if (type === "#text") {
78
+ state.stream.push(encodeHtmlEntities(props.nodeValue ?? ""))
79
+ return
80
+ }
81
+ if (isExoticType(type)) {
82
+ if (type === $HYDRATION_BOUNDARY) {
83
+ state.stream.push(`<!--${HYDRATION_BOUNDARY_MARKER}-->`)
84
+ renderToStream_internal(state, children, el, idx)
85
+ state.stream.push(`<!--/${HYDRATION_BOUNDARY_MARKER}-->`)
86
+ return
87
+ }
88
+ return renderToStream_internal(state, children, el, idx)
89
+ }
90
+
91
+ if (typeof type !== "string") {
92
+ nodeToCtxMap.set(el, state.ctx)
93
+ node.current = el
94
+ const res = type(props)
95
+ node.current = null
96
+ return renderToStream_internal(state, res, parent, idx)
97
+ }
98
+
99
+ if (__DEV__) {
100
+ assertValidElementProps(el)
101
+ }
102
+ const attrs = propsToElementAttributes(props)
103
+ state.stream.push(`<${type}${attrs.length ? ` ${attrs}` : ""}>`)
104
+
105
+ if (!voidElements.has(type)) {
106
+ if ("innerHTML" in props) {
107
+ state.stream.push(
108
+ String(
109
+ Signal.isSignal(props.innerHTML)
110
+ ? props.innerHTML.peek()
111
+ : props.innerHTML
112
+ )
113
+ )
114
+ } else {
115
+ if (Array.isArray(children)) {
116
+ children.forEach((c, i) => renderToStream_internal(state, c, el, i))
117
+ } else {
118
+ renderToStream_internal(state, children, el, 0)
119
+ }
120
+ }
121
+
122
+ state.stream.push(`</${type}>`)
123
+ }
124
+ }
package/src/store.ts ADDED
@@ -0,0 +1,241 @@
1
+ import type { Prettify } from "./types.utils.js"
2
+ import { __DEV__ } from "./env.js"
3
+ import { sideEffectsEnabled, useHook } from "./hooks/utils.js"
4
+ import { safeStringify, shallowCompare } from "./utils.js"
5
+ import { $HMR_ACCEPT } from "./constants.js"
6
+ import { HMRAccept } from "./hmr.js"
7
+
8
+ export { createStore }
9
+ export type { Store, MethodFactory }
10
+
11
+ type MethodFactory<T> = (
12
+ setState: (setter: Kaioken.StateSetter<T>) => void,
13
+ getState: () => T
14
+ ) => Record<string, Function>
15
+
16
+ type StoreHook<T, U extends Record<string, Function>> = {
17
+ <R>(sliceFn: (state: T) => R): Prettify<{ value: R } & U>
18
+ <
19
+ F extends null | ((state: T) => unknown),
20
+ R extends F extends Function ? ReturnType<F> : T
21
+ >(
22
+ sliceFn: F,
23
+ equality: (prev: R, next: R, compare: typeof shallowCompare) => boolean
24
+ ): Prettify<{ value: R } & U>
25
+ (): Prettify<{ value: T } & U>
26
+ }
27
+
28
+ type Subscribe<T> = {
29
+ (fn: (value: T) => void): () => void
30
+ <R>(sliceFn: (value: T) => R, fn: (value: R) => void): () => void
31
+ }
32
+
33
+ type Store<T, U extends Record<string, Function>> = StoreHook<T, U> & {
34
+ getState: () => T
35
+ setState: (setter: Kaioken.StateSetter<T>) => void
36
+ methods: U
37
+ subscribe: Subscribe<T>
38
+ }
39
+
40
+ type NodeState = {
41
+ update: () => void
42
+ slices: {
43
+ sliceFn: Function | null
44
+ eq:
45
+ | ((prev: any, next: any, compare: typeof shallowCompare) => boolean)
46
+ | undefined
47
+ value: any
48
+ }[]
49
+ }
50
+
51
+ type InternalStoreState<T, U extends MethodFactory<T>> = {
52
+ value: T
53
+ epoch: number
54
+ subscribers: Set<Kaioken.VNode | Function>
55
+ nodeStateMap: WeakMap<Kaioken.VNode, NodeState>
56
+ methods: ReturnType<U>
57
+ }
58
+
59
+ function createStore<T, U extends MethodFactory<T>>(
60
+ initial: T,
61
+ methodFactory: U
62
+ ): Store<T, ReturnType<U>> {
63
+ const state = {
64
+ $initial: null as string | null,
65
+ current: {
66
+ value: initial,
67
+ epoch: 0,
68
+ subscribers: new Set<Kaioken.VNode | Function>(),
69
+ nodeStateMap: new WeakMap<Kaioken.VNode, NodeState>(),
70
+ methods: null as any as ReturnType<U>,
71
+ } satisfies InternalStoreState<T, U>,
72
+ }
73
+ if (__DEV__) {
74
+ state.$initial = safeStringify(initial)
75
+ }
76
+
77
+ const getState = () => state.current.value
78
+
79
+ const setState = (setter: Kaioken.StateSetter<T>) => {
80
+ state.current.value =
81
+ typeof setter === "function"
82
+ ? (setter as Function)(state.current.value)
83
+ : setter
84
+
85
+ const { value, subscribers, nodeStateMap } = state.current
86
+
87
+ subscribers.forEach((n) => {
88
+ if (typeof n === "function") return n(value)
89
+ const nodeState = nodeStateMap.get(n)
90
+ if (!nodeState) return
91
+ const { slices, update } = nodeState
92
+ let computeChanged = slices.length === 0
93
+
94
+ for (let i = 0; i < slices.length; i++) {
95
+ const slice = slices[i]
96
+ if (!slice) continue
97
+ const { sliceFn, eq, value: sliceVal } = slice
98
+ const newSliceVal = sliceFn === null ? value : sliceFn(value)
99
+ slices[i] = { sliceFn, eq, value: newSliceVal }
100
+
101
+ if (computeChanged) continue
102
+ if (eq && eq(sliceVal, newSliceVal, shallowCompare)) {
103
+ continue
104
+ } else if (!eq && shallowCompare(sliceVal, newSliceVal)) {
105
+ continue
106
+ }
107
+
108
+ computeChanged = true
109
+ }
110
+ if (computeChanged) {
111
+ update()
112
+ }
113
+ })
114
+ state.current.epoch++
115
+ }
116
+ state.current.methods = methodFactory(setState, getState) as ReturnType<U>
117
+
118
+ function useStore<R>(
119
+ sliceFn: null | ((state: T) => R) = null,
120
+ equality?: (prev: R, next: R, compare: typeof shallowCompare) => boolean
121
+ ) {
122
+ if (!sideEffectsEnabled()) {
123
+ return {
124
+ value: sliceFn ? sliceFn(state.current.value) : state.current.value,
125
+ ...state.current.methods,
126
+ }
127
+ }
128
+ return useHook(
129
+ "useStore",
130
+ { value: null as any as T | R, lastChangeSync: -1 },
131
+ ({ hook, isInit, isHMR, vNode, index, update }) => {
132
+ if (__DEV__) {
133
+ if (isInit) {
134
+ hook.dev = {
135
+ devtools: {
136
+ get: () => ({ value: hook.value }),
137
+ },
138
+ initialArgs: [sliceFn, equality],
139
+ }
140
+ }
141
+ if (isHMR) {
142
+ const [fn, eq] = hook.dev!.initialArgs
143
+ if (fn !== sliceFn || eq !== equality) {
144
+ isInit = true
145
+ hook.dev!.initialArgs = [sliceFn, equality]
146
+ }
147
+ }
148
+ }
149
+ const { subscribers, nodeStateMap, epoch, value, methods } =
150
+ state.current
151
+
152
+ const nodeState = nodeStateMap.get(vNode) ?? {
153
+ slices: [],
154
+ update,
155
+ }
156
+ let slice: (typeof nodeState.slices)[number]
157
+ if (isInit || !nodeState.slices[index]) {
158
+ subscribers.add(vNode)
159
+ hook.lastChangeSync = epoch
160
+ hook.value = sliceFn === null ? value : sliceFn(value)
161
+ slice = nodeState.slices[index] = {
162
+ sliceFn,
163
+ eq: equality,
164
+ value: hook.value,
165
+ }
166
+ nodeStateMap.set(vNode, nodeState)
167
+
168
+ hook.cleanup = () => {
169
+ nodeStateMap.delete(vNode)
170
+ subscribers.delete(vNode)
171
+ }
172
+ } else {
173
+ slice = nodeState.slices[index]
174
+ if (slice.sliceFn !== sliceFn) {
175
+ slice.value = hook.value = sliceFn ? sliceFn(value) : value
176
+ slice.sliceFn = sliceFn
177
+ }
178
+ slice.eq = equality
179
+ }
180
+
181
+ if (hook.lastChangeSync !== epoch) {
182
+ hook.value = slice ? slice.value : state.current.value
183
+ hook.lastChangeSync = epoch
184
+ }
185
+
186
+ return { value: hook.value, ...methods }
187
+ }
188
+ )
189
+ }
190
+
191
+ const store = Object.assign(useStore, {
192
+ get getState() {
193
+ return getState
194
+ },
195
+ get setState() {
196
+ return setState
197
+ },
198
+ get methods() {
199
+ return { ...state.current.methods }
200
+ },
201
+ subscribe: ((sliceOrCb, cb) => {
202
+ if (cb === undefined) {
203
+ state.current.subscribers.add(sliceOrCb)
204
+ return () => (state.current.subscribers.delete(sliceOrCb), void 0)
205
+ }
206
+
207
+ const selection = {
208
+ current: null as ReturnType<typeof sliceOrCb> | null,
209
+ }
210
+ const sliceWrapper = (newState: T) => {
211
+ const next = sliceOrCb(newState)
212
+ if (shallowCompare(next, selection.current)) return
213
+ selection.current = next
214
+ cb(next)
215
+ }
216
+ state.current.subscribers.add(sliceWrapper)
217
+ return () => (state.current.subscribers.delete(sliceWrapper), void 0)
218
+ }) as Subscribe<T>,
219
+ })
220
+
221
+ if (__DEV__) {
222
+ return Object.assign(store, {
223
+ [$HMR_ACCEPT]: {
224
+ provide: () => state,
225
+ inject: (prev) => {
226
+ state.current = prev.current
227
+ state.current.methods = methodFactory(
228
+ setState,
229
+ getState
230
+ ) as ReturnType<U>
231
+ if (state.$initial !== prev.$initial) {
232
+ setState(initial)
233
+ }
234
+ },
235
+ destroy: () => {},
236
+ } satisfies HMRAccept<typeof state>,
237
+ })
238
+ }
239
+
240
+ return store
241
+ }