acrylblends 0.6.2 → 0.6.3

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 (181) hide show
  1. package/README.md +4 -1
  2. package/docs/framework/README.md +3 -2
  3. package/docs/framework/UPSTREAM.md +1 -1
  4. package/docs/framework/cordis/acryl-in-a-cordis-app.md +6 -0
  5. package/docs/framework/getting-started/basic.md +1 -0
  6. package/docs/framework/getting-started/desktop.md +41 -0
  7. package/docs/framework/guides/jsx-dom.md +0 -3
  8. package/docs/framework/guides/jsx.md +0 -3
  9. package/docs/framework/helpers/css.md +0 -3
  10. package/docs/framework/upstream.json +3 -3
  11. package/lib/accepts.d.ts +1 -1
  12. package/lib/acryl-base.d.ts +1 -1
  13. package/lib/adapter.d.ts +1 -1
  14. package/lib/{app-BdrVHBlJ.d.ts → app-wFwpg-bt.d.ts} +1 -1
  15. package/lib/base-BpQy2Rk7.d.ts +783 -0
  16. package/lib/base-ddiVmiE7.js +721 -0
  17. package/lib/basic-auth.d.ts +1 -1
  18. package/lib/bearer-auth.d.ts +1 -1
  19. package/lib/body-limit.d.ts +1 -1
  20. package/lib/cache.d.ts +1 -1
  21. package/lib/client.d.ts +2 -2
  22. package/lib/combine.d.ts +1 -1
  23. package/lib/common-D8Z4JRCw.d.ts +46 -0
  24. package/lib/components-Ce5FRyHU.js +268 -0
  25. package/lib/components-DYZdxJIJ.js +24 -0
  26. package/lib/compress.d.ts +1 -1
  27. package/lib/conninfo.d.ts +2 -2
  28. package/lib/constants-BR9jM9BC.js +9 -0
  29. package/lib/{context-B5Y1lLha.d.ts → context-DVQBjuns.d.ts} +1 -1
  30. package/lib/context-DxFdY_un.d.ts +250 -0
  31. package/lib/context-storage.d.ts +1 -1
  32. package/lib/{cookie-D9_nQbsM.d.ts → cookie-CCelEqky.d.ts} +1 -1
  33. package/lib/cookie.d.ts +3 -3
  34. package/lib/cors.d.ts +1 -1
  35. package/lib/csrf.d.ts +1 -1
  36. package/lib/css-D2jb7X8b.js +320 -0
  37. package/lib/css.d.ts +82 -0
  38. package/lib/css.js +134 -0
  39. package/lib/dev.d.ts +2 -2
  40. package/lib/etag.d.ts +1 -1
  41. package/lib/factory.d.ts +2 -2
  42. package/lib/hooks-Bq5q9IMl.js +762 -0
  43. package/lib/html-SllAe0YT.d.ts +46 -0
  44. package/lib/html.d.ts +1 -1
  45. package/lib/index-C850MeBA.d.ts +94 -0
  46. package/lib/{index-m12zcmph.d.ts → index-CUTfU69R.d.ts} +3 -3
  47. package/lib/{index-DtEoak1i.d.ts → index-Cqcdluyn.d.ts} +2 -2
  48. package/lib/{index-Kqou8Fjh.d.ts → index-CsvB-F9j.d.ts} +2 -2
  49. package/lib/index.d.ts +4 -4
  50. package/lib/ip-restriction.d.ts +2 -2
  51. package/lib/jsx/dom/client.d.ts +30 -0
  52. package/lib/jsx/dom/client.js +49 -0
  53. package/lib/jsx/dom/css.d.ts +84 -0
  54. package/lib/jsx/dom/css.js +2 -0
  55. package/lib/jsx/dom/jsx-dev-runtime.d.ts +3 -0
  56. package/lib/jsx/dom/jsx-dev-runtime.js +2 -0
  57. package/lib/jsx/dom/jsx-runtime.d.ts +3 -0
  58. package/lib/jsx/dom/jsx-runtime.js +2 -0
  59. package/lib/jsx/dom/server.d.ts +105 -0
  60. package/lib/jsx/dom/server.js +37 -0
  61. package/lib/jsx/dom.d.ts +5 -0
  62. package/lib/jsx/dom.js +74 -0
  63. package/lib/jsx/jsx-dev-runtime.d.ts +6 -0
  64. package/lib/jsx/jsx-dev-runtime.js +14 -0
  65. package/lib/jsx/jsx-runtime.d.ts +10 -0
  66. package/lib/jsx/jsx-runtime.js +30 -0
  67. package/lib/jsx/streaming.d.ts +2 -0
  68. package/lib/jsx/streaming.js +2 -0
  69. package/lib/jsx-dev-runtime-B22O-Npk.d.ts +7 -0
  70. package/lib/jsx-dev-runtime-kAhR_4C7.js +354 -0
  71. package/lib/jsx-runtime-BVVgDSdq.d.ts +1 -0
  72. package/lib/jsx.d.ts +3 -0
  73. package/lib/jsx.js +47 -0
  74. package/lib/jwk.d.ts +4 -4
  75. package/lib/{jwt-BA65QhnJ.d.ts → jwt-BARhotDz.d.ts} +1 -1
  76. package/lib/jwt.d.ts +4 -4
  77. package/lib/language.d.ts +1 -1
  78. package/lib/logger.d.ts +1 -1
  79. package/lib/loopback-only.d.ts +1 -1
  80. package/lib/method-not-allowed.d.ts +2 -2
  81. package/lib/method-override.d.ts +2 -2
  82. package/lib/mount.d.ts +1 -1
  83. package/lib/node-server/conninfo.d.ts +1 -1
  84. package/lib/node-server/serve-static.d.ts +1 -1
  85. package/lib/node-server/serve-static.js +1 -1
  86. package/lib/node-server.d.ts +1 -1
  87. package/lib/node-server.js +1 -1
  88. package/lib/powered-by.d.ts +1 -1
  89. package/lib/pretty-json.d.ts +1 -1
  90. package/lib/proxy.d.ts +1 -1
  91. package/lib/quick.d.ts +1 -1
  92. package/lib/request-id.d.ts +1 -1
  93. package/lib/request.d.ts +1 -1
  94. package/lib/route.d.ts +1 -1
  95. package/lib/schema-validator.d.ts +1 -1
  96. package/lib/secure-headers.d.ts +1 -1
  97. package/lib/{serve-static-Dw_m6KxR.js → serve-static-Cw23shXv.js} +1 -0
  98. package/lib/serve-static.d.ts +1 -1
  99. package/lib/streaming.d.ts +1 -1
  100. package/lib/testing.d.ts +4 -4
  101. package/lib/timeout.d.ts +1 -1
  102. package/lib/timing.d.ts +1 -1
  103. package/lib/tiny.d.ts +1 -1
  104. package/lib/trailing-slash.d.ts +1 -1
  105. package/lib/{types-Bd29gh2o.d.ts → types-CQiGmsWW.d.ts} +3 -3
  106. package/lib/{types-D9DEXMjE.d.ts → types-Lqx5BxQF.d.ts} +8 -1
  107. package/lib/{types-ZM9gIiKh.d.ts → types-nEL8VPDC.d.ts} +1 -1
  108. package/lib/types.d.ts +1 -1
  109. package/lib/utils/body.d.ts +1 -1
  110. package/lib/utils/cookie.d.ts +1 -1
  111. package/lib/utils/crypto.d.ts +1 -1
  112. package/lib/utils/html.d.ts +1 -45
  113. package/lib/utils/ipaddr.d.ts +1 -1
  114. package/lib/utils/jwt.d.ts +1 -1
  115. package/lib/utils/types.d.ts +2 -2
  116. package/lib/validator.d.ts +2 -2
  117. package/lib/ws.d.ts +1 -1
  118. package/package.json +45 -1
  119. package/plugins/acryl-inspect/upstream.json +1 -1
  120. package/plugins/acryl-node-server/upstream.json +1 -1
  121. package/plugins/acryl-router/src/core/helper/css/common.case.test.tsx +506 -0
  122. package/plugins/acryl-router/src/core/helper/css/common.ts +337 -0
  123. package/plugins/acryl-router/src/core/helper/css/index.test.tsx +678 -0
  124. package/plugins/acryl-router/src/core/helper/css/index.ts +262 -0
  125. package/plugins/acryl-router/src/core/helper/streaming/sse.test.tsx +557 -0
  126. package/plugins/acryl-router/src/core/jsx/base.test.tsx +97 -0
  127. package/plugins/acryl-router/src/core/jsx/base.ts +490 -0
  128. package/plugins/acryl-router/src/core/jsx/children.test.ts +52 -0
  129. package/plugins/acryl-router/src/core/jsx/children.ts +20 -0
  130. package/plugins/acryl-router/src/core/jsx/components.test.tsx +936 -0
  131. package/plugins/acryl-router/src/core/jsx/components.ts +257 -0
  132. package/plugins/acryl-router/src/core/jsx/constants.ts +6 -0
  133. package/plugins/acryl-router/src/core/jsx/context-isolation.test.ts +655 -0
  134. package/plugins/acryl-router/src/core/jsx/context.ts +266 -0
  135. package/plugins/acryl-router/src/core/jsx/dom/client.test.tsx +128 -0
  136. package/plugins/acryl-router/src/core/jsx/dom/client.ts +89 -0
  137. package/plugins/acryl-router/src/core/jsx/dom/components.test.tsx +330 -0
  138. package/plugins/acryl-router/src/core/jsx/dom/components.ts +39 -0
  139. package/plugins/acryl-router/src/core/jsx/dom/context.test.tsx +184 -0
  140. package/plugins/acryl-router/src/core/jsx/dom/context.ts +52 -0
  141. package/plugins/acryl-router/src/core/jsx/dom/css.test.tsx +149 -0
  142. package/plugins/acryl-router/src/core/jsx/dom/css.ts +263 -0
  143. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.test.tsx +172 -0
  144. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.ts +91 -0
  145. package/plugins/acryl-router/src/core/jsx/dom/index.test.tsx +3681 -0
  146. package/plugins/acryl-router/src/core/jsx/dom/index.ts +170 -0
  147. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.test.tsx +1344 -0
  148. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.ts +438 -0
  149. package/plugins/acryl-router/src/core/jsx/dom/jsx-dev-runtime.ts +23 -0
  150. package/plugins/acryl-router/src/core/jsx/dom/jsx-runtime.ts +8 -0
  151. package/plugins/acryl-router/src/core/jsx/dom/render.ts +879 -0
  152. package/plugins/acryl-router/src/core/jsx/dom/server.test.tsx +199 -0
  153. package/plugins/acryl-router/src/core/jsx/dom/server.ts +76 -0
  154. package/plugins/acryl-router/src/core/jsx/dom/utils.ts +7 -0
  155. package/plugins/acryl-router/src/core/jsx/hooks/dom.test.tsx +859 -0
  156. package/plugins/acryl-router/src/core/jsx/hooks/index.ts +437 -0
  157. package/plugins/acryl-router/src/core/jsx/hooks/string.test.tsx +40 -0
  158. package/plugins/acryl-router/src/core/jsx/index.test.tsx +1744 -0
  159. package/plugins/acryl-router/src/core/jsx/index.ts +115 -0
  160. package/plugins/acryl-router/src/core/jsx/intrinsic-element/common.ts +23 -0
  161. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.test.tsx +666 -0
  162. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.ts +210 -0
  163. package/plugins/acryl-router/src/core/jsx/intrinsic-elements.ts +938 -0
  164. package/plugins/acryl-router/src/core/jsx/jsx-dev-runtime.ts +26 -0
  165. package/plugins/acryl-router/src/core/jsx/jsx-runtime.test.tsx +85 -0
  166. package/plugins/acryl-router/src/core/jsx/jsx-runtime.ts +51 -0
  167. package/plugins/acryl-router/src/core/jsx/streaming.test.tsx +1281 -0
  168. package/plugins/acryl-router/src/core/jsx/streaming.ts +231 -0
  169. package/plugins/acryl-router/src/core/jsx/types.ts +41 -0
  170. package/plugins/acryl-router/src/core/jsx/utils.test.ts +366 -0
  171. package/plugins/acryl-router/src/core/jsx/utils.ts +202 -0
  172. package/plugins/acryl-router/src/core/utils/types.ts +6 -0
  173. package/plugins/acryl-router/src/helpers/index.ts +1 -0
  174. package/plugins/acryl-router/tests/jsx.spec.tsx +63 -0
  175. package/plugins/acryl-router/upstream.json +3 -3
  176. package/plugins/acryl-router-http/upstream.json +1 -1
  177. package/plugins/acryl-state/upstream.json +1 -1
  178. package/plugins/desktop/upstream.json +1 -1
  179. package/plugins/desktop-web/upstream.json +1 -1
  180. package/plugins/save/plugin.json +1 -1
  181. package/templates/todo/upstream.json +1 -1
@@ -0,0 +1,437 @@
1
+ import type { JSX } from '../base.ts'
2
+ import { DOM_STASH } from '../constants.ts'
3
+ import { buildDataStack, update } from '../dom/render.ts'
4
+ import type { Context, Node, NodeObject, PendingType, UpdateHook } from '../dom/render.ts'
5
+
6
+ type UpdateStateFunction<T> = (newState: T | ((currentState: T) => T)) => void
7
+
8
+ const STASH_SATE = 0
9
+ export const STASH_EFFECT = 1
10
+ const STASH_CALLBACK = 2
11
+ const STASH_MEMO = 3
12
+ const STASH_REF = 4
13
+
14
+ export type EffectData = [
15
+ readonly unknown[] | undefined, // deps
16
+ (() => void | (() => void)) | undefined, // layout effect
17
+ (() => void) | undefined, // cleanup
18
+ (() => void) | undefined, // effect
19
+ (() => void) | undefined, // insertion effect
20
+ ]
21
+
22
+ const resolvedPromiseValueMap: WeakMap<Promise<unknown>, unknown> = new WeakMap<
23
+ Promise<unknown>,
24
+ unknown
25
+ >()
26
+
27
+ const isDepsChanged = (
28
+ prevDeps: readonly unknown[] | undefined,
29
+ deps: readonly unknown[] | undefined
30
+ ): boolean =>
31
+ !prevDeps ||
32
+ !deps ||
33
+ prevDeps.length !== deps.length ||
34
+ deps.some((dep, i) => dep !== prevDeps[i])
35
+
36
+ let viewTransitionState:
37
+ | [
38
+ boolean, // isUpdating
39
+ boolean, // useViewTransition() is called
40
+ ]
41
+ | undefined = undefined
42
+
43
+ const documentStartViewTransition: (cb: () => void) => { finished: Promise<void> } = (cb) => {
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ if ((document as any)?.startViewTransition) {
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ return (document as any).startViewTransition(cb)
48
+ } else {
49
+ cb()
50
+ return { finished: Promise.resolve() }
51
+ }
52
+ }
53
+
54
+ let updateHook: UpdateHook | undefined = undefined
55
+ const viewTransitionHook = (
56
+ context: Context,
57
+ node: Node,
58
+ cb: (context: Context) => void
59
+ ): Promise<void> => {
60
+ const state: [boolean, boolean] = [true, false]
61
+ let lastVC = node.vC
62
+ return documentStartViewTransition(() => {
63
+ if (lastVC === node.vC) {
64
+ viewTransitionState = state
65
+ cb(context)
66
+ viewTransitionState = undefined
67
+ lastVC = node.vC
68
+ }
69
+ }).finished.then(() => {
70
+ if (state[1] && lastVC === node.vC) {
71
+ state[0] = false
72
+ viewTransitionState = state
73
+ cb(context)
74
+ viewTransitionState = undefined
75
+ }
76
+ })
77
+ }
78
+
79
+ export const startViewTransition = (callback: () => void): void => {
80
+ updateHook = viewTransitionHook
81
+
82
+ try {
83
+ callback()
84
+ } finally {
85
+ updateHook = undefined
86
+ }
87
+ }
88
+
89
+ export const useViewTransition = (): [boolean, (callback: () => void) => void] => {
90
+ const buildData = buildDataStack.at(-1) as [Context, NodeObject]
91
+ if (!buildData) {
92
+ return [false, () => {}]
93
+ }
94
+
95
+ if (viewTransitionState) {
96
+ viewTransitionState[1] = true
97
+ }
98
+ return [!!viewTransitionState?.[0], startViewTransition]
99
+ }
100
+
101
+ // PendingType is defined in "../dom/render", 3 is used for useDeferredValue
102
+ const pendingStack: [PendingType | 3, Promise<void>][] = []
103
+ const runCallback = (type: PendingType, callback: Function): void => {
104
+ let resolve: (() => void) | undefined
105
+ const promise = new Promise<void>((r) => (resolve = r))
106
+ pendingStack.push([type, promise])
107
+ try {
108
+ const res = callback()
109
+ if (res instanceof Promise) {
110
+ res.then(resolve, resolve)
111
+ } else {
112
+ resolve!()
113
+ }
114
+ } finally {
115
+ pendingStack.pop()
116
+ }
117
+ }
118
+
119
+ export const startTransition = (callback: () => void): void => {
120
+ runCallback(1, callback)
121
+ }
122
+ const startTransitionHook = (callback: () => void | Promise<void>): void => {
123
+ runCallback(2, callback)
124
+ }
125
+
126
+ export const useTransition = (): [boolean, (callback: () => void | Promise<void>) => void] => {
127
+ const buildData = buildDataStack.at(-1) as [Context, NodeObject]
128
+ if (!buildData) {
129
+ return [false, () => {}]
130
+ }
131
+
132
+ const [error, setError] = useState<[Error]>()
133
+ const [state, updateState] = useState<boolean>()
134
+ if (error) {
135
+ throw error[0]
136
+ }
137
+ const startTransitionLocalHook = useCallback<typeof startTransitionHook>(
138
+ (callback) => {
139
+ startTransitionHook(() => {
140
+ updateState((state) => !state)
141
+ let res = callback()
142
+ if (res instanceof Promise) {
143
+ res = res.catch((e) => {
144
+ setError([e])
145
+ })
146
+ }
147
+ return res
148
+ })
149
+ },
150
+ [state]
151
+ )
152
+
153
+ const [context] = buildData
154
+ return [context[0] === 2, startTransitionLocalHook]
155
+ }
156
+
157
+ type UseDeferredValue = <T>(value: T, initialValue?: T) => T
158
+ export const useDeferredValue: UseDeferredValue = <T>(value: T, ...rest: [T | undefined]): T => {
159
+ const [values, setValues] = useState<[T, T]>(
160
+ (rest.length ? [rest[0], rest[0]] : [value, value]) as [T, T]
161
+ )
162
+ if (Object.is(values[1], value)) {
163
+ return values[1]
164
+ }
165
+
166
+ pendingStack.push([3, Promise.resolve()])
167
+ updateHook = async (context: Context, _, cb: (context: Context) => void) => {
168
+ cb(context)
169
+ values[0] = value
170
+ }
171
+ setValues([values[0], value])
172
+ updateHook = undefined
173
+ pendingStack.pop()
174
+
175
+ return values[0]
176
+ }
177
+
178
+ type UseStateType = {
179
+ <T>(initialState: T | (() => T)): [T, UpdateStateFunction<T>]
180
+ <T = undefined>(): [T | undefined, UpdateStateFunction<T | undefined>]
181
+ }
182
+ export const useState: UseStateType = <T>(
183
+ initialState?: T | (() => T)
184
+ ): [T, UpdateStateFunction<T>] => {
185
+ const resolveInitialState = () =>
186
+ typeof initialState === 'function' ? (initialState as () => T)() : (initialState as T)
187
+
188
+ const buildData = buildDataStack.at(-1) as [unknown, NodeObject]
189
+ if (!buildData) {
190
+ return [resolveInitialState(), () => {}]
191
+ }
192
+ const [, node] = buildData
193
+
194
+ const stateArray = (node[DOM_STASH][1][STASH_SATE] ||= [])
195
+ const hookIndex = node[DOM_STASH][0]++
196
+
197
+ return (stateArray[hookIndex] ||= [
198
+ resolveInitialState(),
199
+ (newState: T | ((currentState: T) => T)) => {
200
+ const localUpdateHook = updateHook
201
+ const stateData = stateArray[hookIndex]
202
+ if (typeof newState === 'function') {
203
+ newState = (newState as (currentState: T) => T)(stateData[0])
204
+ }
205
+
206
+ if (!Object.is(newState, stateData[0])) {
207
+ stateData[0] = newState
208
+ if (pendingStack.length) {
209
+ const [pendingType, pendingPromise] = pendingStack.at(-1) as [
210
+ PendingType | 3,
211
+ Promise<void>,
212
+ ]
213
+ Promise.all([
214
+ pendingType === 3
215
+ ? node
216
+ : update([pendingType, false, localUpdateHook as UpdateHook], node),
217
+ pendingPromise,
218
+ ]).then(([node]) => {
219
+ if (!node || !(pendingType === 2 || pendingType === 3)) {
220
+ return
221
+ }
222
+
223
+ const lastVC = node.vC
224
+
225
+ const addUpdateTask = () => {
226
+ setTimeout(() => {
227
+ // return if `node` is rerendered after current transition
228
+ if (lastVC !== node.vC) {
229
+ return
230
+ }
231
+ update([pendingType === 3 ? 1 : 0, false, localUpdateHook as UpdateHook], node)
232
+ })
233
+ }
234
+
235
+ requestAnimationFrame(addUpdateTask)
236
+ })
237
+ } else {
238
+ update([0, false, localUpdateHook as UpdateHook], node)
239
+ }
240
+ }
241
+ },
242
+ ])
243
+ }
244
+
245
+ export const useReducer = <T, A>(
246
+ reducer: (state: T, action: A) => T,
247
+ initialArg: T,
248
+ init?: (initialState: T) => T
249
+ ): [T, (action: A) => void] => {
250
+ const handler = useCallback(
251
+ (action: A) => {
252
+ setState((state) => reducer(state, action))
253
+ },
254
+ [reducer]
255
+ )
256
+ const [state, setState] = useState(() => (init ? init(initialArg) : initialArg))
257
+ return [state, handler]
258
+ }
259
+
260
+ const useEffectCommon = (
261
+ index: number,
262
+ effect: () => void | (() => void),
263
+ deps?: readonly unknown[]
264
+ ): void => {
265
+ const buildData = buildDataStack.at(-1) as [unknown, NodeObject]
266
+ if (!buildData) {
267
+ return
268
+ }
269
+ const [, node] = buildData
270
+
271
+ const effectDepsArray = (node[DOM_STASH][1][STASH_EFFECT] ||= [])
272
+ const hookIndex = node[DOM_STASH][0]++
273
+
274
+ const [prevDeps, , prevCleanup] = (effectDepsArray[hookIndex] ||= [])
275
+ if (isDepsChanged(prevDeps, deps)) {
276
+ if (prevCleanup) {
277
+ prevCleanup()
278
+ }
279
+ const runner = () => {
280
+ data[index] = undefined // clear this effect in order to avoid calling effect twice
281
+ data[2] = effect() as (() => void) | undefined
282
+ }
283
+ const data: EffectData = [deps, undefined, undefined, undefined, undefined]
284
+ data[index] = runner
285
+ effectDepsArray[hookIndex] = data
286
+ }
287
+ }
288
+ export const useEffect = (effect: () => void | (() => void), deps?: readonly unknown[]): void =>
289
+ useEffectCommon(3, effect, deps)
290
+ export const useLayoutEffect = (
291
+ effect: () => void | (() => void),
292
+ deps?: readonly unknown[]
293
+ ): void => useEffectCommon(1, effect, deps)
294
+ export const useInsertionEffect = (
295
+ effect: () => void | (() => void),
296
+ deps?: readonly unknown[]
297
+ ): void => useEffectCommon(4, effect, deps)
298
+
299
+ export const useCallback = <T extends Function>(callback: T, deps: readonly unknown[]): T => {
300
+ const buildData = buildDataStack.at(-1) as [unknown, NodeObject]
301
+ if (!buildData) {
302
+ return callback
303
+ }
304
+ const [, node] = buildData
305
+
306
+ const callbackArray = (node[DOM_STASH][1][STASH_CALLBACK] ||= [])
307
+ const hookIndex = node[DOM_STASH][0]++
308
+
309
+ const prevDeps = callbackArray[hookIndex]
310
+ if (isDepsChanged(prevDeps?.[1], deps)) {
311
+ callbackArray[hookIndex] = [callback, deps]
312
+ } else {
313
+ callback = callbackArray[hookIndex][0] as T
314
+ }
315
+ return callback
316
+ }
317
+
318
+ export type RefObject<T> = { current: T }
319
+
320
+ export function useRef<T>(initialValue: T): RefObject<T>
321
+ export function useRef<T>(initialValue: T | null): RefObject<T | null>
322
+ export function useRef<T>(initialValue: T | undefined): RefObject<T | undefined>
323
+ export function useRef<T>(initialValue?: T | null): RefObject<T | null | undefined> {
324
+ const buildData = buildDataStack.at(-1) as [unknown, NodeObject]
325
+ if (!buildData) {
326
+ return { current: initialValue } as RefObject<T | null | undefined>
327
+ }
328
+ const [, node] = buildData
329
+
330
+ const refArray = (node[DOM_STASH][1][STASH_REF] ||= [])
331
+ const hookIndex = node[DOM_STASH][0]++
332
+
333
+ return (refArray[hookIndex] ||= { current: initialValue })
334
+ }
335
+
336
+ export const use = <T>(promise: Promise<T>): T => {
337
+ const cachedRes = resolvedPromiseValueMap.get(promise) as [T] | [undefined, unknown] | undefined
338
+ if (cachedRes) {
339
+ if (cachedRes.length === 2) {
340
+ throw cachedRes[1]
341
+ }
342
+ return cachedRes[0] as T
343
+ }
344
+ promise.then(
345
+ (res) => resolvedPromiseValueMap.set(promise, [res]),
346
+ (e) => resolvedPromiseValueMap.set(promise, [undefined, e])
347
+ )
348
+
349
+ throw promise
350
+ }
351
+
352
+ export const useMemo = <T>(factory: () => T, deps: readonly unknown[]): T => {
353
+ const buildData = buildDataStack.at(-1) as [unknown, NodeObject]
354
+ if (!buildData) {
355
+ return factory()
356
+ }
357
+ const [, node] = buildData
358
+
359
+ const memoArray = (node[DOM_STASH][1][STASH_MEMO] ||= [])
360
+ const hookIndex = node[DOM_STASH][0]++
361
+
362
+ const prevDeps = memoArray[hookIndex]
363
+ if (isDepsChanged(prevDeps?.[1], deps)) {
364
+ memoArray[hookIndex] = [factory(), deps]
365
+ }
366
+ return memoArray[hookIndex][0] as T
367
+ }
368
+
369
+ let idCounter = 0
370
+ export const useId = (): string => useMemo(() => `:r${(idCounter++).toString(32)}:`, [])
371
+
372
+ // Define to avoid errors. This hook currently does nothing.
373
+ export const useDebugValue = (_value: unknown, _formatter?: (value: unknown) => string): void => {}
374
+
375
+ export const createRef = <T>(): RefObject<T | null> => {
376
+ return { current: null }
377
+ }
378
+
379
+ export const forwardRef = <T, P = {}>(
380
+ Component: (props: P, ref?: RefObject<T | null>) => JSX.Element
381
+ ): ((props: P & { ref?: RefObject<T | null> }) => JSX.Element) => {
382
+ return (props) => {
383
+ const { ref, ...rest } = props
384
+ return Component(rest as P, ref)
385
+ }
386
+ }
387
+
388
+ export const useImperativeHandle = <T>(
389
+ ref: RefObject<T | null>,
390
+ createHandle: () => T,
391
+ deps: readonly unknown[]
392
+ ): void => {
393
+ useEffect(() => {
394
+ ref.current = createHandle()
395
+ return () => {
396
+ ref.current = null
397
+ }
398
+ }, deps)
399
+ }
400
+
401
+ export const useSyncExternalStore = <T>(
402
+ subscribe: (callback: () => void) => () => void,
403
+ getSnapshot: () => T,
404
+ getServerSnapshot?: () => T
405
+ ): T => {
406
+ const buildData = buildDataStack.at(-1) as [Context, unknown]
407
+ if (!buildData) {
408
+ // now a stringify process, maybe in server side
409
+ if (!getServerSnapshot) {
410
+ throw new Error('getServerSnapshot is required for server side rendering')
411
+ }
412
+ return getServerSnapshot()
413
+ }
414
+
415
+ const snapshot =
416
+ buildData[0][4] && getServerSnapshot ? (getServerSnapshot as () => T)() : getSnapshot()
417
+ const [, setVersion] = useState(0)
418
+ const latestSnapshot = useRef<[T, () => T]>([snapshot, getSnapshot])
419
+ latestSnapshot.current = [snapshot, getSnapshot]
420
+ const unsubscribeRef = useRef<() => void>(null)
421
+
422
+ // Swap subscriptions at effect flush: a returned cleanup would run synchronously
423
+ // during render when `subscribe` changes, leaving a window with no subscription.
424
+ useEffect(() => {
425
+ const update = () => setVersion((version) => version + 1)
426
+ unsubscribeRef.current?.()
427
+ unsubscribeRef.current = subscribe(update)
428
+ const [snapshot, getSnapshot] = latestSnapshot.current!
429
+ if (!Object.is(snapshot, getSnapshot())) {
430
+ update()
431
+ }
432
+ }, [subscribe])
433
+
434
+ useEffect(() => () => unsubscribeRef.current?.(), [])
435
+
436
+ return snapshot
437
+ }
@@ -0,0 +1,40 @@
1
+ /** @jsxImportSource ../ */
2
+ import { useState, useSyncExternalStore } from '../index.ts'
3
+
4
+ describe('useState', () => {
5
+ it('should be rendered with initial state', () => {
6
+ const Component = () => {
7
+ const [state] = useState('hello')
8
+ return <span>{state}</span>
9
+ }
10
+ const template = <Component />
11
+ expect(template.toString()).toBe('<span>hello</span>')
12
+ })
13
+ })
14
+
15
+ describe('useSyncExternalStore', () => {
16
+ it('should be rendered with result of getServerSnapshot()', () => {
17
+ const unsubscribe = vi.fn()
18
+ const subscribe = vi.fn(() => unsubscribe)
19
+ const getSnapshot = vi.fn()
20
+ const getServerSnapshot = vi.fn(() => 100)
21
+ const App = () => {
22
+ const count = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
23
+ return <div>{count}</div>
24
+ }
25
+ const template = <App />
26
+ expect(template.toString()).toBe('<div>100</div>')
27
+ expect(unsubscribe).not.toBeCalled()
28
+ expect(subscribe).not.toBeCalled()
29
+ expect(getSnapshot).not.toBeCalled()
30
+ })
31
+
32
+ it('should raise an error if getServerShot() is not provided', () => {
33
+ const App = () => {
34
+ const count = useSyncExternalStore(vi.fn(), vi.fn())
35
+ return <div>{count}</div>
36
+ }
37
+ const template = <App />
38
+ expect(() => template.toString()).toThrowError()
39
+ })
40
+ })