@tanstack/redact 0.0.20 → 0.1.0

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 (253) hide show
  1. package/dist/core/attributes.js +2 -1
  2. package/dist/core/attributes.js.map +2 -2
  3. package/dist/core/browser.d.ts +9 -0
  4. package/dist/core/browser.js +35 -0
  5. package/dist/core/browser.js.map +7 -0
  6. package/dist/core/index.d.ts +2 -2
  7. package/dist/core/index.js +3 -3
  8. package/dist/core/internal.d.ts +31 -8
  9. package/dist/core/internal.js +3 -1
  10. package/dist/core/internal.js.map +2 -2
  11. package/dist/core/resource-hints.d.ts +7 -0
  12. package/dist/core/resource-hints.js +19 -0
  13. package/dist/core/resource-hints.js.map +7 -0
  14. package/dist/core/types.js +1 -1
  15. package/dist/dom/_all.d.ts +11 -16
  16. package/dist/dom/_all.js +9 -20
  17. package/dist/dom/_all.js.map +2 -2
  18. package/dist/dom/client.d.ts +4 -4
  19. package/dist/dom/client.js +4 -4
  20. package/dist/dom/commit.d.ts +23 -0
  21. package/dist/dom/commit.js +191 -0
  22. package/dist/dom/commit.js.map +7 -0
  23. package/dist/dom/dispatcher.d.ts +4 -22
  24. package/dist/dom/dispatcher.js +184 -126
  25. package/dist/dom/dispatcher.js.map +3 -3
  26. package/dist/dom/dom.d.ts +2 -0
  27. package/dist/dom/dom.js +72 -52
  28. package/dist/dom/dom.js.map +2 -2
  29. package/dist/dom/error-info.d.ts +2 -0
  30. package/dist/dom/error-info.js +16 -0
  31. package/dist/dom/error-info.js.map +7 -0
  32. package/dist/dom/event-replay.js +1 -1
  33. package/dist/dom/features/activity/full.d.ts +1 -0
  34. package/dist/dom/features/activity/full.js +162 -0
  35. package/dist/dom/features/activity/full.js.map +7 -0
  36. package/dist/dom/features/activity/index.d.ts +1 -0
  37. package/dist/dom/features/activity/index.js +3 -0
  38. package/dist/dom/features/activity/index.js.map +7 -0
  39. package/dist/dom/features/activity/stub.d.ts +1 -0
  40. package/dist/dom/features/activity/stub.js +12 -0
  41. package/dist/dom/features/activity/stub.js.map +7 -0
  42. package/dist/dom/features/class/full.js +87 -21
  43. package/dist/dom/features/class/full.js.map +2 -2
  44. package/dist/dom/features/class/index.d.ts +1 -1
  45. package/dist/dom/features/class/index.js +2 -2
  46. package/dist/dom/features/class/stub.js +3 -3
  47. package/dist/dom/features/context/full.d.ts +4 -1
  48. package/dist/dom/features/context/full.js +69 -9
  49. package/dist/dom/features/context/full.js.map +2 -2
  50. package/dist/dom/features/context/index.d.ts +1 -1
  51. package/dist/dom/features/context/index.js +2 -2
  52. package/dist/dom/features/context/stub.d.ts +4 -1
  53. package/dist/dom/features/context/stub.js +15 -4
  54. package/dist/dom/features/context/stub.js.map +2 -2
  55. package/dist/dom/features/forward-ref/full.js +12 -21
  56. package/dist/dom/features/forward-ref/full.js.map +2 -2
  57. package/dist/dom/features/forward-ref/index.d.ts +1 -1
  58. package/dist/dom/features/forward-ref/index.js +2 -2
  59. package/dist/dom/features/forward-ref/stub.js +4 -4
  60. package/dist/dom/features/fragment-refs/index.d.ts +1 -0
  61. package/dist/dom/features/fragment-refs/index.js +8 -0
  62. package/dist/dom/features/fragment-refs/index.js.map +7 -0
  63. package/dist/dom/features/fragment-refs/stub.d.ts +3 -0
  64. package/dist/dom/features/fragment-refs/stub.js +14 -0
  65. package/dist/dom/features/fragment-refs/stub.js.map +7 -0
  66. package/dist/dom/features/hydration/full.d.ts +11 -6
  67. package/dist/dom/features/hydration/full.js +76 -76
  68. package/dist/dom/features/hydration/full.js.map +2 -2
  69. package/dist/dom/features/hydration/index.d.ts +1 -1
  70. package/dist/dom/features/hydration/index.js +2 -2
  71. package/dist/dom/features/hydration/stub.d.ts +7 -2
  72. package/dist/dom/features/hydration/stub.js +1 -1
  73. package/dist/dom/features/hydration/stub.js.map +1 -1
  74. package/dist/dom/features/index.d.ts +8 -7
  75. package/dist/dom/features/index.js +9 -8
  76. package/dist/dom/features/index.js.map +2 -2
  77. package/dist/dom/features/lazy/full.js +4 -4
  78. package/dist/dom/features/lazy/index.d.ts +1 -1
  79. package/dist/dom/features/lazy/index.js +2 -2
  80. package/dist/dom/features/lazy/stub.js +4 -4
  81. package/dist/dom/features/memo/full.js +8 -6
  82. package/dist/dom/features/memo/full.js.map +2 -2
  83. package/dist/dom/features/memo/index.d.ts +1 -1
  84. package/dist/dom/features/memo/index.js +2 -2
  85. package/dist/dom/features/memo/stub.js +4 -4
  86. package/dist/dom/features/portal/full.js +4 -4
  87. package/dist/dom/features/portal/index.d.ts +1 -1
  88. package/dist/dom/features/portal/index.js +2 -2
  89. package/dist/dom/features/portal/stub.js +4 -4
  90. package/dist/dom/features/suspense/full.js +192 -47
  91. package/dist/dom/features/suspense/full.js.map +2 -2
  92. package/dist/dom/features/suspense/index.d.ts +1 -1
  93. package/dist/dom/features/suspense/index.js +2 -2
  94. package/dist/dom/features/suspense/stub.js +4 -4
  95. package/dist/dom/features/view-transition/full.d.ts +6 -0
  96. package/dist/dom/features/view-transition/full.js +280 -0
  97. package/dist/dom/features/view-transition/full.js.map +7 -0
  98. package/dist/dom/features/view-transition/index.d.ts +1 -0
  99. package/dist/dom/features/view-transition/index.js +3 -0
  100. package/dist/dom/features/view-transition/index.js.map +7 -0
  101. package/dist/dom/features/view-transition/resources.d.ts +11 -0
  102. package/dist/dom/features/view-transition/resources.js +108 -0
  103. package/dist/dom/features/view-transition/resources.js.map +7 -0
  104. package/dist/dom/features/view-transition/stub.d.ts +6 -0
  105. package/dist/dom/features/view-transition/stub.js +22 -0
  106. package/dist/dom/features/view-transition/stub.js.map +7 -0
  107. package/dist/dom/features/view-transition/visual.d.ts +14 -0
  108. package/dist/dom/features/view-transition/visual.js +356 -0
  109. package/dist/dom/features/view-transition/visual.js.map +7 -0
  110. package/dist/dom/fragment-instance.d.ts +40 -0
  111. package/dist/dom/fragment-instance.js +324 -0
  112. package/dist/dom/fragment-instance.js.map +7 -0
  113. package/dist/dom/index.d.ts +11 -11
  114. package/dist/dom/index.js +11 -18
  115. package/dist/dom/index.js.map +3 -3
  116. package/dist/dom/portal.d.ts +2 -2
  117. package/dist/dom/portal.js +2 -2
  118. package/dist/dom/portal.js.map +1 -1
  119. package/dist/dom/reconcile.d.ts +32 -3
  120. package/dist/dom/reconcile.js +686 -222
  121. package/dist/dom/reconcile.js.map +2 -2
  122. package/dist/dom/resource-hints.d.ts +6 -0
  123. package/dist/dom/resource-hints.js +115 -0
  124. package/dist/dom/resource-hints.js.map +7 -0
  125. package/dist/dom/resource-store.d.ts +13 -0
  126. package/dist/dom/resource-store.js +43 -0
  127. package/dist/dom/resource-store.js.map +7 -0
  128. package/dist/dom/resources.d.ts +1 -0
  129. package/dist/dom/resources.js +44 -0
  130. package/dist/dom/resources.js.map +7 -0
  131. package/dist/dom/retained-effects.d.ts +6 -0
  132. package/dist/dom/retained-effects.js +49 -0
  133. package/dist/dom/retained-effects.js.map +7 -0
  134. package/dist/dom/root-internal.d.ts +4 -4
  135. package/dist/dom/root-internal.js +2 -2
  136. package/dist/dom/root-internal.js.map +2 -2
  137. package/dist/dom/root.d.ts +5 -5
  138. package/dist/dom/root.js +11 -11
  139. package/dist/dom/root.js.map +2 -2
  140. package/dist/dom/test-utils.js +1 -1
  141. package/dist/react/activity.d.ts +7 -0
  142. package/dist/react/activity.js +8 -0
  143. package/dist/react/activity.js.map +7 -0
  144. package/dist/react/children.d.ts +1 -1
  145. package/dist/react/children.js +3 -3
  146. package/dist/react/children.js.map +1 -1
  147. package/dist/react/class.d.ts +1 -1
  148. package/dist/react/class.js +3 -3
  149. package/dist/react/class.js.map +1 -1
  150. package/dist/react/compiler-runtime.js +2 -2
  151. package/dist/react/context.d.ts +1 -1
  152. package/dist/react/context.js +3 -3
  153. package/dist/react/context.js.map +1 -1
  154. package/dist/react/element.d.ts +6 -3
  155. package/dist/react/element.js +2 -2
  156. package/dist/react/element.js.map +2 -2
  157. package/dist/react/hooks.d.ts +26 -7
  158. package/dist/react/hooks.js +13 -3
  159. package/dist/react/hooks.js.map +2 -2
  160. package/dist/react/index.d.ts +39 -24
  161. package/dist/react/index.js +39 -19
  162. package/dist/react/index.js.map +3 -3
  163. package/dist/react/jsx-runtime.d.ts +2 -2
  164. package/dist/react/jsx-runtime.js +3 -3
  165. package/dist/react/memo.d.ts +1 -1
  166. package/dist/react/memo.js +1 -1
  167. package/dist/react/portal.js +1 -1
  168. package/dist/react/shared-internals.d.ts +15 -5
  169. package/dist/react/shared-internals.js +2 -3
  170. package/dist/react/shared-internals.js.map +2 -2
  171. package/dist/react/suspense.js +1 -1
  172. package/dist/react/view-transition.d.ts +32 -0
  173. package/dist/react/view-transition.js +13 -0
  174. package/dist/react/view-transition.js.map +7 -0
  175. package/dist/scheduler/index.js +1 -1
  176. package/dist/server/bootstrap-script.d.ts +1 -23
  177. package/dist/server/bootstrap-script.js +7 -6
  178. package/dist/server/bootstrap-script.js.map +2 -2
  179. package/dist/server/dispatcher.d.ts +28 -6
  180. package/dist/server/dispatcher.js +33 -15
  181. package/dist/server/dispatcher.js.map +2 -2
  182. package/dist/server/escape.js +16 -15
  183. package/dist/server/escape.js.map +2 -2
  184. package/dist/server/index.d.ts +6 -6
  185. package/dist/server/index.js +7 -7
  186. package/dist/server/renderToString.d.ts +1 -1
  187. package/dist/server/renderToString.js +12 -8
  188. package/dist/server/renderToString.js.map +2 -2
  189. package/dist/server/resource-hints.d.ts +22 -0
  190. package/dist/server/resource-hints.js +148 -0
  191. package/dist/server/resource-hints.js.map +7 -0
  192. package/dist/server/stream.d.ts +15 -2
  193. package/dist/server/stream.js +155 -63
  194. package/dist/server/stream.js.map +2 -2
  195. package/dist/server/walk.d.ts +15 -2
  196. package/dist/server/walk.js +143 -96
  197. package/dist/server/walk.js.map +2 -2
  198. package/dist/vite/index.d.ts +6 -0
  199. package/dist/vite/index.js +22 -6
  200. package/dist/vite/index.js.map +2 -2
  201. package/package.json +1 -1
  202. package/src/core/attributes.ts +1 -0
  203. package/src/core/browser.ts +33 -0
  204. package/src/core/internal.ts +24 -6
  205. package/src/core/resource-hints.ts +36 -0
  206. package/src/dom/_all.ts +1 -7
  207. package/src/dom/commit.ts +186 -0
  208. package/src/dom/dispatcher.ts +190 -134
  209. package/src/dom/dom.ts +70 -54
  210. package/src/dom/error-info.ts +15 -0
  211. package/src/dom/features/activity/full.ts +166 -0
  212. package/src/dom/features/activity/index.ts +1 -0
  213. package/src/dom/features/activity/stub.ts +14 -0
  214. package/src/dom/features/class/full.ts +72 -21
  215. package/src/dom/features/context/full.ts +66 -4
  216. package/src/dom/features/context/stub.ts +6 -0
  217. package/src/dom/features/forward-ref/full.ts +9 -19
  218. package/src/dom/features/fragment-refs/index.ts +1 -0
  219. package/src/dom/features/fragment-refs/stub.ts +3 -0
  220. package/src/dom/features/hydration/full.ts +84 -82
  221. package/src/dom/features/hydration/stub.ts +1 -1
  222. package/src/dom/features/index.ts +1 -0
  223. package/src/dom/features/memo/full.ts +4 -3
  224. package/src/dom/features/suspense/full.ts +187 -50
  225. package/src/dom/features/view-transition/full.ts +288 -0
  226. package/src/dom/features/view-transition/index.ts +1 -0
  227. package/src/dom/features/view-transition/resources.ts +107 -0
  228. package/src/dom/features/view-transition/stub.ts +7 -0
  229. package/src/dom/features/view-transition/visual.ts +406 -0
  230. package/src/dom/fragment-instance.ts +337 -0
  231. package/src/dom/index.ts +6 -7
  232. package/src/dom/portal.ts +1 -1
  233. package/src/dom/reconcile.ts +691 -244
  234. package/src/dom/resource-hints.ts +115 -0
  235. package/src/dom/resource-store.ts +52 -0
  236. package/src/dom/resources.ts +39 -0
  237. package/src/dom/retained-effects.ts +38 -0
  238. package/src/dom/root-internal.ts +4 -4
  239. package/src/dom/root.ts +12 -14
  240. package/src/react/activity.ts +10 -0
  241. package/src/react/element.ts +6 -2
  242. package/src/react/hooks.ts +12 -3
  243. package/src/react/index.ts +18 -0
  244. package/src/react/shared-internals.ts +3 -3
  245. package/src/react/view-transition.ts +41 -0
  246. package/src/server/bootstrap-script.ts +36 -5
  247. package/src/server/dispatcher.ts +35 -13
  248. package/src/server/escape.ts +15 -12
  249. package/src/server/renderToString.ts +7 -5
  250. package/src/server/resource-hints.ts +165 -0
  251. package/src/server/stream.ts +154 -62
  252. package/src/server/walk.ts +72 -11
  253. package/src/vite/index.ts +30 -13
@@ -1,6 +1,9 @@
1
- import type { Hook, Fiber, FiberRoot, Effect } from '../core'
2
- import { ReactSharedInternals, REACT_CONTEXT_TYPE } from '../react'
3
- import { scheduleUpdate, enqueueEffect, readContext } from './reconcile'
1
+ import type { Hook, Fiber, FiberRoot, Effect, ReactNode } from '../core'
2
+ import { ReactSharedInternals, REACT_CONTEXT_TYPE, startTransition } from '../react'
3
+ import { scheduleUpdate, enqueueEffect, readContext, rememberActivityEffect, handleCommitError } from './reconcile'
4
+ import { REACT_RECOVERABLE_TYPE } from '../core/browser'
5
+ import { queueCommitEffects } from './commit'
6
+ import { deferTransitionPassive } from './features/view-transition'
4
7
 
5
8
  function getCurrentFiber(): Fiber {
6
9
  const f = ReactSharedInternals.F
@@ -14,32 +17,9 @@ function getCurrentFiber(): Fiber {
14
17
  }
15
18
 
16
19
  function nextHook(): Hook {
17
- const fiber = getCurrentFiber()
18
- const idx = ReactSharedInternals.I++
19
-
20
- let prev = ReactSharedInternals.K
21
-
22
- if (idx === 0) {
23
- if (fiber.hooks) {
24
- ReactSharedInternals.K = fiber.hooks
25
- return fiber.hooks
26
- }
27
- const h: Hook = { s: undefined, q: undefined, d: undefined, c: undefined, n: null }
28
- fiber.hooks = h
29
- ReactSharedInternals.K = h
30
- return h
31
- }
32
-
33
- if (prev && prev.n) {
34
- ReactSharedInternals.K = prev.n
35
- return prev.n
36
- }
37
-
38
- const h: Hook = { s: undefined, q: undefined, d: undefined, c: undefined, n: null }
39
- if (prev) prev.n = h
40
- else fiber.hooks = h
41
- ReactSharedInternals.K = h
42
- return h
20
+ const hooks = getCurrentFiber().hooks ||= []
21
+ const index = ReactSharedInternals.I++
22
+ return hooks[index] ||= { s: undefined, q: undefined, d: undefined, c: undefined }
43
23
  }
44
24
 
45
25
  function depsEqual(
@@ -57,110 +37,185 @@ function depsEqual(
57
37
 
58
38
  type BasicStateAction<S> = S | ((p: S) => S)
59
39
 
40
+ function initializeReducer(hook: Hook, fiber: Fiber, initialArg: any, init?: (arg: any) => any): void {
41
+ hook.s = init ? init(initialArg) : initialArg
42
+ hook.c = null
43
+ hook.q = (action: any) => {
44
+ if (fiber.um || fiber.pd) return
45
+ ;(hook.c ||= []).push(action)
46
+ scheduleUpdate(fiber)
47
+ }
48
+ }
49
+
50
+ // Reducer slots reuse the effect-cleanup field for pending actions. Zero marks
51
+ // eager state work, null means no update. Replacing arrays lets retries restore
52
+ // pending actions without copying or shifting them.
53
+ function reducePending(hook: Hook, reducer: (state: any, action: any) => any, previous = hook.s): void {
54
+ const actions = hook.c
55
+ if (actions == null) return
56
+ hook.c = null
57
+ let state = hook.s
58
+ if (actions) for (const action of actions) state = reducer(state, action)
59
+ hookFlags |= Object.is(state, previous) ? 2 : 3
60
+ hook.s = state
61
+ }
62
+
63
+ let hookEffects: any[] | undefined
64
+ // Bit 1 records a changed value; bit 2 records a processed reducer queue.
65
+ let hookFlags = 0
66
+ export const HOOK_BAILOUT = Symbol()
67
+
68
+ function resetHookEffects(effects: any[]): void {
69
+ for (let index = 0; index < effects.length; index += 4) effects[index].d = effects[index + 1]
70
+ }
71
+
72
+ export function renderWithHooks(
73
+ fiber: Fiber,
74
+ render: (props: any, ref?: any) => ReactNode,
75
+ props: any,
76
+ ref?: any,
77
+ canBail = false,
78
+ ): ReactNode | typeof HOOK_BAILOUT {
79
+ const previousDispatcher = ReactSharedInternals.H, previousFiber = ReactSharedInternals.F, previousIndex = ReactSharedInternals.I
80
+ const previousEffects = hookEffects, previousFlags = hookFlags
81
+ hookEffects = undefined
82
+ hookFlags = 0
83
+ ReactSharedInternals.H = DISPATCHER
84
+ ReactSharedInternals.F = fiber
85
+ try {
86
+ let rendered: ReactNode, attempts = 0
87
+ do {
88
+ if (++attempts > 25) throw new Error(process.env.NODE_ENV !== 'production' ? 'Too many re-renders.' : '')
89
+ if (hookEffects) {
90
+ resetHookEffects(hookEffects)
91
+ hookEffects = undefined
92
+ }
93
+ ReactSharedInternals.I = 0
94
+ fiber.dy = false
95
+ rendered = render(props, ref)
96
+ } while (fiber.dy)
97
+ if (canBail && hookFlags === 2) {
98
+ // React accepts the rendered dependencies on a reducer bailout, but
99
+ // keeps the previous committed effect descriptions until real work.
100
+ hookEffects = undefined
101
+ return HOOK_BAILOUT
102
+ }
103
+ if (hookEffects) {
104
+ const effects: any[] = hookEffects
105
+ for (let index = 0; index < effects.length; index += 4) {
106
+ const hook = effects[index], effect = effects[index + 2]
107
+ rememberActivityEffect(fiber, hook, effect)
108
+ if (effects[index + 3]) enqueueEffect(fiber, effect)
109
+ }
110
+ hookEffects = undefined
111
+ }
112
+ return rendered
113
+ } finally {
114
+ if (hookEffects) resetHookEffects(hookEffects)
115
+ hookEffects = previousEffects
116
+ hookFlags = previousFlags
117
+ ReactSharedInternals.H = previousDispatcher
118
+ ReactSharedInternals.F = previousFiber
119
+ ReactSharedInternals.I = previousIndex
120
+ }
121
+ }
122
+
123
+ export function cleanupEffect(hook: Hook, fiber: Fiber): void {
124
+ if (!hook.c) return
125
+ const cleanup = hook.c
126
+ hook.c = null
127
+ if (fiber.cu) {
128
+ const index = fiber.cu.indexOf(cleanup)
129
+ if (index >= 0) fiber.cu.splice(index, 1)
130
+ }
131
+ try { cleanup() } catch (error) { handleCommitError(fiber, error) }
132
+ }
133
+
134
+ function useEffectImpl(type: Effect['t'], create: () => any, deps?: ReadonlyArray<unknown>): void {
135
+ const hook = nextHook()
136
+ const fiber = getCurrentFiber()
137
+ const changed = hook.d === undefined || !depsEqual(hook.d, deps)
138
+ if (!changed && !fiber.root?.a && !(type === 1 && fiber.root?.sp)) return
139
+ const previous = hook.d
140
+ hook.d = deps
141
+ const effect: Effect = {
142
+ t: type,
143
+ c: () => {
144
+ cleanupEffect(hook, fiber)
145
+ const cleanup = create()
146
+ // Each effect owns its cleanup even when hooks return the same function.
147
+ hook.c = typeof cleanup === 'function' ? () => {
148
+ if (type === 0) {
149
+ const dispose = () => {
150
+ try { cleanup() } catch (error) { handleCommitError(fiber, error) }
151
+ }
152
+ if (!deferTransitionPassive(dispose)) queueCommitEffects(dispose)
153
+ } else cleanup()
154
+ } : null
155
+ return hook.c
156
+ },
157
+ }
158
+ if (type === 1) effect.d = () => cleanupEffect(hook, fiber)
159
+ ;(hookEffects ||= []).push(hook, previous, effect, changed)
160
+ }
161
+
60
162
  // Singleton — every method reads render context via ReactSharedInternals,
61
163
  // and per-hook closures live on the hook itself, so nothing is render-local
62
164
  // to capture. Allocating a fresh wrapper + 17 method closures per function-
63
165
  // component render was pure GC pressure.
64
166
  const DISPATCHER = makeDispatcherImpl()
65
167
 
66
- export function makeDispatcher() {
67
- return DISPATCHER
68
- }
69
-
70
168
  function makeDispatcherImpl() {
71
169
  return {
72
170
  useState<S>(initial: S | (() => S)): [S, (a: BasicStateAction<S>) => void] {
73
- return this.useReducer(
74
- basicReducer as (state: S, action: BasicStateAction<S>) => S,
75
- typeof initial == 'function' ? (initial as () => S)() : initial,
76
- )
77
- },
78
-
79
- useReducer<S, A>(reducer: (s: S, a: A) => S, initialArg: any, init?: (a: any) => S) {
80
- const hook = nextHook()
81
- const fiber = getCurrentFiber()
171
+ const hook = nextHook(), fiber = getCurrentFiber()
82
172
  if (hook.q === undefined) {
83
- hook.s = init ? init(initialArg) : initialArg
84
- const queue: any = { r: reducer }
85
- const dispatch = (action: A) => {
86
- const currentState = hook.s as S
87
- const next = queue.r(currentState, action)
88
- if (!Object.is(next, currentState)) {
173
+ hook.s = hook.d = typeof initial === 'function' ? (initial as () => S)() : initial
174
+ hook.c = null
175
+ hook.q = (action: BasicStateAction<S>) => {
176
+ if (fiber.um || fiber.pd) return
177
+ if (ReactSharedInternals.F !== fiber && !hook.c) {
178
+ let next: S
179
+ try {
180
+ next = basicReducer(hook.s, action)
181
+ } catch {
182
+ hook.c = [action]
183
+ scheduleUpdate(fiber)
184
+ return
185
+ }
186
+ if (Object.is(next, hook.s)) return
89
187
  hook.s = next
188
+ hook.c = 0
90
189
  scheduleUpdate(fiber)
190
+ return
91
191
  }
192
+ ;(hook.c ||= []).push(action)
193
+ scheduleUpdate(fiber)
92
194
  }
93
- queue.d = dispatch
94
- hook.q = queue
95
- } else {
96
- hook.q.r = reducer
97
195
  }
98
- return [hook.s, hook.q.d] as [S, (a: A) => void]
196
+ reducePending(hook, basicReducer, hook.d)
197
+ hook.d = hook.s
198
+ return [hook.s, hook.q]
99
199
  },
100
200
 
101
- useEffect(create: () => any, deps?: ReadonlyArray<unknown>) {
201
+ useReducer<S, A>(reducer: (s: S, a: A) => S, initialArg: any, init?: (a: any) => S) {
102
202
  const hook = nextHook()
103
- const fiber = getCurrentFiber()
104
- const prevDeps = hook.d
105
- if (prevDeps !== undefined && depsEqual(prevDeps, deps)) return
106
- hook.d = deps
107
- const effect: Effect = {
108
- t: 0,
109
- c: () => {
110
- // Run the prior cleanup INSIDE the effect run, not during the
111
- // dispatch/render phase. If render A → B → C all happen back-to-
112
- // back before the passive microtask drains, dispatch-time cleanup
113
- // only fires once (between A→B) and fx B + C both run fresh,
114
- // leaving two side-fx (e.g. two plot SVGs) in the DOM. Doing
115
- // it here, at effect-run time, means every new create first tears
116
- // down whatever cleanup is currently live on the hook.
117
- if (hook.c) {
118
- try { hook.c() } catch {}
119
- // The prior cleanup was also pushed onto fiber.cu; remove
120
- // it so unmount doesn't double-call it.
121
- if (fiber.cu) {
122
- const i = fiber.cu.indexOf(hook.c)
123
- if (i >= 0) fiber.cu.splice(i, 1)
124
- }
125
- hook.c = null
126
- }
127
- const c = create()
128
- hook.c = typeof c == 'function' ? c : null
129
- return hook.c
130
- },
131
- }
132
- enqueueEffect(fiber, effect)
203
+ if (hook.q === undefined) initializeReducer(hook, getCurrentFiber(), initialArg, init)
204
+ reducePending(hook, reducer)
205
+ hook.d = reducer
206
+ return [hook.s, hook.q] as [S, (a: A) => void]
207
+ },
208
+
209
+ useEffect(create: () => any, deps?: ReadonlyArray<unknown>) {
210
+ useEffectImpl(0, create, deps)
133
211
  },
134
212
 
135
213
  useLayoutEffect(create: () => any, deps?: ReadonlyArray<unknown>) {
136
- const hook = nextHook()
137
- const fiber = getCurrentFiber()
138
- const prevDeps = hook.d
139
- if (prevDeps !== undefined && depsEqual(prevDeps, deps)) return
140
- hook.d = deps
141
- const effect: Effect = {
142
- t: 1,
143
- c: () => {
144
- // Mirror useEffect: tear down the prior cleanup at run time so
145
- // coalesced renders don't leak side-fx.
146
- if (hook.c) {
147
- try { hook.c() } catch {}
148
- if (fiber.cu) {
149
- const i = fiber.cu.indexOf(hook.c)
150
- if (i >= 0) fiber.cu.splice(i, 1)
151
- }
152
- hook.c = null
153
- }
154
- const c = create()
155
- hook.c = typeof c == 'function' ? c : null
156
- return hook.c
157
- },
158
- }
159
- enqueueEffect(fiber, effect)
214
+ useEffectImpl(1, create, deps)
160
215
  },
161
216
 
162
217
  useInsertionEffect(create: () => any, deps?: ReadonlyArray<unknown>) {
163
- return this.useLayoutEffect(create, deps)
218
+ useEffectImpl(2, create, deps)
164
219
  },
165
220
 
166
221
  useRef<T>(initial: T) {
@@ -190,14 +245,16 @@ function makeDispatcherImpl() {
190
245
  },
191
246
 
192
247
  useImperativeHandle<T>(ref: any, factory: () => T, deps?: ReadonlyArray<unknown>) {
193
- const hook = nextHook()
194
- if (hook.d !== undefined && depsEqual(hook.d, deps)) return
195
- hook.d = deps
196
- const value = factory()
197
- if (ref) {
198
- if (typeof ref == 'function') ref(value)
199
- else ref.current = value
200
- }
248
+ useEffectImpl(1, () => {
249
+ if (!ref) return
250
+ const value = factory()
251
+ if (typeof ref === 'function') {
252
+ const cleanup = ref(value)
253
+ return typeof cleanup === 'function' ? cleanup : () => ref(null)
254
+ }
255
+ ref.current = value
256
+ return () => { ref.current = null }
257
+ }, deps == null ? undefined : [...deps, ref])
201
258
  },
202
259
 
203
260
  useDebugValue<T>(_value: T, _formatter?: (v: T) => any): void {
@@ -217,13 +274,19 @@ function makeDispatcherImpl() {
217
274
  },
218
275
 
219
276
  useTransition(): [boolean, (fn: () => void) => void] {
220
- return [false, (fn: () => void) => fn()]
277
+ return [false, startTransition]
221
278
  },
222
279
 
223
280
  useDeferredValue<T>(v: T): T {
224
281
  return v
225
282
  },
226
283
 
284
+ useCacheRefresh(): () => void {
285
+ // There is no client cache to invalidate. Preserve hook identity and
286
+ // leave state and scheduling alone, as with the uncached client cache().
287
+ return this.useCallback(() => {}, [])
288
+ },
289
+
227
290
  useSyncExternalStore<T>(
228
291
  subscribe: (cb: () => void) => () => void,
229
292
  getSnapshot: () => T,
@@ -251,26 +314,19 @@ function makeDispatcherImpl() {
251
314
  const isHydrating = Boolean(root?.h)
252
315
  const value =
253
316
  isHydrating && getServerSnapshot ? getServerSnapshot() : getSnapshot()
317
+ if (!Object.is(hook.s, value)) hookFlags |= 1
254
318
  hook.s = value
255
319
 
256
320
  const deps = [subscribe]
257
- if (hook.d === undefined || !depsEqual(hook.d, deps)) {
321
+ const changed = hook.d === undefined || !depsEqual(hook.d, deps)
322
+ if (changed || fiber.root?.a) {
323
+ const previous = hook.d
258
324
  hook.d = deps
259
325
  const effect: Effect = {
260
326
  t: 1,
327
+ s: true,
261
328
  c: () => {
262
- if (hook.c) {
263
- try {
264
- hook.c()
265
- } catch {
266
- // ignore cleanup failures
267
- }
268
- if (fiber.cu) {
269
- const i = fiber.cu.indexOf(hook.c)
270
- if (i >= 0) fiber.cu.splice(i, 1)
271
- }
272
- hook.c = null
273
- }
329
+ cleanupEffect(hook, fiber)
274
330
 
275
331
  let unsubscribed = false
276
332
  const cleanup = () => {
@@ -293,7 +349,6 @@ function makeDispatcherImpl() {
293
349
  }
294
350
 
295
351
  if (!Object.is(hook.s, next)) {
296
- hook.s = next
297
352
  scheduleUpdate(fiber)
298
353
  }
299
354
  }
@@ -311,12 +366,13 @@ function makeDispatcherImpl() {
311
366
  return cleanup
312
367
  },
313
368
  }
314
- enqueueEffect(fiber, effect)
369
+ ;(hookEffects ||= []).push(hook, previous, effect, changed)
315
370
  }
316
371
  return value
317
372
  },
318
373
 
319
374
  use<T>(resource: any): T {
375
+ if (resource?.$$typeof === REACT_RECOVERABLE_TYPE) return undefined as T
320
376
  if (resource == null) {
321
377
  if (process.env.NODE_ENV !== 'production') {
322
378
  throw new Error('use() received null or undefined')
@@ -372,7 +428,7 @@ let idCounter = 0
372
428
  function findRootFromFiber(fiber: Fiber): FiberRoot | null {
373
429
  let f: Fiber | null = fiber
374
430
  while (f) {
375
- if (f.root) return f.root
431
+ if (f.root) return fiber.root = f.root
376
432
  f = f.parent
377
433
  }
378
434
  return null
package/src/dom/dom.ts CHANGED
@@ -9,6 +9,7 @@ const BOOLEAN_ATTRS = new Set([
9
9
  'autoplay',
10
10
  'checked',
11
11
  'controls',
12
+ 'credentialless',
12
13
  'default',
13
14
  'defer',
14
15
  'disabled',
@@ -51,6 +52,54 @@ export function isSvgElement(el: Element): boolean {
51
52
  return (el as any).ownerSVGElement !== undefined || el.tagName === 'svg'
52
53
  }
53
54
 
55
+ export function syncInputValue(el: HTMLInputElement, props: any, prev?: any): void {
56
+ if (!prev) {
57
+ el.checked = el.defaultChecked = !!(props.checked ?? props.defaultChecked)
58
+ } else if (props.checked != null) {
59
+ el.checked = !!props.checked
60
+ } else if (props.defaultChecked != null) {
61
+ el.defaultChecked = !!props.defaultChecked
62
+ }
63
+ const value = props.value
64
+ const fallback = props.defaultValue
65
+ if (!prev) {
66
+ if (value == null && (el.type === 'submit' || el.type === 'reset')) return
67
+ if (value != null || fallback != null) {
68
+ const initial = '' + (value ?? fallback)
69
+ if (el.value !== initial) el.value = initial
70
+ el.defaultValue = initial
71
+ }
72
+ return
73
+ }
74
+ if (value != null) {
75
+ // Number inputs keep equivalent user spelling, including trailing zeroes.
76
+ if (el.type === 'number' ? (value === 0 && el.value === '') || el.value != value : el.value !== '' + value) {
77
+ el.value = '' + value
78
+ }
79
+ const nextDefault = el.type === 'number' && el.value == value ? el.value : '' + value
80
+ if (el.defaultValue !== nextDefault) el.defaultValue = nextDefault
81
+ } else if (fallback != null) {
82
+ if (el.defaultValue !== '' + fallback) el.defaultValue = '' + fallback
83
+ } else if (prev.defaultValue != null || el.type === 'submit' || el.type === 'reset') {
84
+ el.removeAttribute('value')
85
+ }
86
+ }
87
+
88
+ export function syncTextareaValue(el: HTMLTextAreaElement, props: any, mounting = false): void {
89
+ if (mounting) {
90
+ const children = props.children
91
+ const initial = props.value ?? props.defaultValue ?? (Array.isArray(children) ? children[0] : children) ?? ''
92
+ el.defaultValue = initial
93
+ // The strict check also preserves React's clean numeric-default state.
94
+ if (initial !== '' && el.textContent === initial) el.value = initial
95
+ return
96
+ }
97
+ const value = props.value
98
+ if (value != null && el.value !== '' + value) el.value = '' + value
99
+ const fallback = props.defaultValue ?? value ?? ''
100
+ if (el.defaultValue !== '' + fallback) el.defaultValue = '' + fallback
101
+ }
102
+
54
103
  export function setProp(
55
104
  el: Element,
56
105
  name: string,
@@ -59,6 +108,14 @@ export function setProp(
59
108
  isSvg: boolean,
60
109
  ): void {
61
110
  if (name === 'children' || name === 'key' || name === 'ref') return
111
+ // These names need no alias or boolean-attribute normalization.
112
+ if (name.length > 5 && (name.charCodeAt(0) === 97 || name.charCodeAt(0) === 100)) {
113
+ if (name.startsWith('aria-') || name.startsWith('data-')) {
114
+ if (next == null) el.removeAttribute(name)
115
+ else el.setAttribute(name, '' + next)
116
+ return
117
+ }
118
+ }
62
119
  const attr = attributeName(name, isSvg)
63
120
 
64
121
  // defaultValue / defaultChecked are IDL-property-only — they seed the
@@ -76,19 +133,8 @@ export function setProp(
76
133
  return
77
134
  }
78
135
 
79
- if (name === 'className') {
80
- if (isSvg) {
81
- if (next == null) el.removeAttribute('class')
82
- else el.setAttribute('class', '' + next)
83
- } else {
84
- ;(el as HTMLElement).className = next == null ? '' : '' + next
85
- }
86
- return
87
- }
88
-
89
- if (name === 'class') {
90
- if (next == null) el.removeAttribute('class')
91
- else el.setAttribute('class', '' + next)
136
+ if (name === 'className' && !isSvg) {
137
+ ;(el as HTMLElement).className = next == null ? '' : '' + next
92
138
  return
93
139
  }
94
140
 
@@ -105,17 +151,11 @@ export function setProp(
105
151
  }
106
152
 
107
153
  if (name[0] === 'o' && name[1] === 'n') {
108
- setEventHandler(el, name, next, prev)
154
+ setEventHandler(el, name, next)
109
155
  return
110
156
  }
111
157
 
112
- if (name === 'htmlFor') {
113
- if (next == null) el.removeAttribute('for')
114
- else el.setAttribute('for', '' + next)
115
- return
116
- }
117
-
118
- if (STRING_BOOLEAN_ATTRS.has(attr)) {
158
+ if (name === 'className' || name === 'class' || name === 'htmlFor' || STRING_BOOLEAN_ATTRS.has(attr)) {
119
159
  if (next == null) el.removeAttribute(attr)
120
160
  else el.setAttribute(attr, '' + next)
121
161
  return
@@ -128,32 +168,12 @@ export function setProp(
128
168
  } catch {}
129
169
  }
130
170
 
131
- if (BOOLEAN_ATTRS.has(name.toLowerCase())) {
132
- if (next) el.setAttribute(attr, '')
133
- else el.removeAttribute(attr)
134
- return
135
- }
136
-
137
- // aria-* and data-* attributes stringify booleans to `"true"`/`"false"`
138
- // rather than using the HTML boolean-attribute presence/absence semantics.
139
- // This matches React and the ARIA spec (aria-hidden="false" is meaningful).
140
- if (name.length > 5 && (name.charCodeAt(0) === 97 /* a */ || name.charCodeAt(0) === 100 /* d */)) {
141
- if (name.startsWith('aria-') || name.startsWith('data-')) {
142
- if (next == null) {
143
- el.removeAttribute(attr)
144
- } else {
145
- el.setAttribute(attr, '' + next)
146
- }
147
- return
148
- }
149
- }
171
+ if (BOOLEAN_ATTRS.has(attr)) next = !!next
150
172
 
151
173
  if (next == null || next === false) {
152
174
  el.removeAttribute(attr)
153
- } else if (next === true) {
154
- el.setAttribute(attr, '')
155
175
  } else {
156
- el.setAttribute(attr, '' + next)
176
+ el.setAttribute(attr, next === true ? '' : next)
157
177
  }
158
178
  }
159
179
 
@@ -241,7 +261,7 @@ export interface SyntheticEvent extends Event {
241
261
  currentTarget: EventTarget & Element
242
262
  }
243
263
 
244
- function setEventHandler(el: Element, name: string, next: any, prev: any): void {
264
+ function setEventHandler(el: Element, name: string, next: any): void {
245
265
  // Only function handlers are accepted. Reject strings/objects/etc — the
246
266
  // legacy `onclick="..."` HTML attribute (string handler) is a known XSS
247
267
  // vector if a parent spreads untrusted props onto a host element. React
@@ -256,13 +276,11 @@ function setEventHandler(el: Element, name: string, next: any, prev: any): void
256
276
  // `onInput` on the same text input — which both dispatch from the native
257
277
  // `input` event — can coexist without clobbering each other's handlers.
258
278
  const handlers = ((el as any).__handlers ||= Object.create(null))
259
- const key = name
260
-
261
- const existing = handlers[key]
279
+ const existing = handlers[name]
262
280
 
263
281
  if (existing && !next) {
264
- el.removeEventListener(existing.event, existing.listener, existing.capture)
265
- handlers[key] = null
282
+ el.removeEventListener(existing.event, existing.listener, capture)
283
+ handlers[name] = null
266
284
  return
267
285
  }
268
286
 
@@ -271,7 +289,6 @@ function setEventHandler(el: Element, name: string, next: any, prev: any): void
271
289
  current: next as Function,
272
290
  listener: null as any,
273
291
  event: eventName,
274
- capture,
275
292
  }
276
293
  entry.listener = (e: Event) => {
277
294
  // React hands handlers a SyntheticEvent that carries `.nativeEvent`.
@@ -282,7 +299,7 @@ function setEventHandler(el: Element, name: string, next: any, prev: any): void
282
299
  if ((e as any).nativeEvent === undefined) (e as any).nativeEvent = e
283
300
  entry.current(e)
284
301
  }
285
- handlers[key] = entry
302
+ handlers[name] = entry
286
303
  el.addEventListener(eventName, entry.listener, capture)
287
304
  return
288
305
  }
@@ -291,10 +308,9 @@ function setEventHandler(el: Element, name: string, next: any, prev: any): void
291
308
  // Re-bind if the effective DOM event changed (e.g. <input> whose `type`
292
309
  // flipped from "text" to "checkbox" — onChange should now follow `change`
293
310
  // instead of `input`). This is rare but keeps semantics correct.
294
- if (existing.event !== eventName || existing.capture !== capture) {
295
- el.removeEventListener(existing.event, existing.listener, existing.capture)
311
+ if (existing.event !== eventName) {
312
+ el.removeEventListener(existing.event, existing.listener, capture)
296
313
  existing.event = eventName
297
- existing.capture = capture
298
314
  el.addEventListener(eventName, existing.listener, capture)
299
315
  }
300
316
  existing.current = next
@@ -0,0 +1,15 @@
1
+ import { FiberTag, type Fiber } from '../core'
2
+
3
+ export function componentStack(fiber: Fiber | null): string {
4
+ let stack = ''
5
+ for (; fiber; fiber = fiber.parent) {
6
+ let type = fiber.type
7
+ while (type && typeof type === 'object') type = type.type || type.render
8
+ const name = typeof type === 'string' ? type
9
+ : typeof type === 'function' ? type.displayName || type.name || 'Anonymous'
10
+ : fiber.tag === FiberTag.Suspense ? 'Suspense'
11
+ : fiber.tag === FiberTag.Activity ? 'Activity' : ''
12
+ if (name) stack += '\n at ' + name
13
+ }
14
+ return stack
15
+ }