@tanstack/redact 0.0.1

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 (233) hide show
  1. package/dist/core/index.d.ts +2 -0
  2. package/dist/core/index.js +4 -0
  3. package/dist/core/index.js.map +7 -0
  4. package/dist/core/internal.d.ts +76 -0
  5. package/dist/core/internal.js +59 -0
  6. package/dist/core/internal.js.map +7 -0
  7. package/dist/core/types.d.ts +21 -0
  8. package/dist/core/types.js +10 -0
  9. package/dist/core/types.js.map +7 -0
  10. package/dist/dom/_all.d.ts +17 -0
  11. package/dist/dom/_all.js +51 -0
  12. package/dist/dom/_all.js.map +7 -0
  13. package/dist/dom/client.d.ts +3 -0
  14. package/dist/dom/client.js +8 -0
  15. package/dist/dom/client.js.map +7 -0
  16. package/dist/dom/dispatcher.d.ts +20 -0
  17. package/dist/dom/dispatcher.js +267 -0
  18. package/dist/dom/dispatcher.js.map +7 -0
  19. package/dist/dom/dom.d.ts +10 -0
  20. package/dist/dom/dom.js +262 -0
  21. package/dist/dom/dom.js.map +7 -0
  22. package/dist/dom/event-replay.d.ts +13 -0
  23. package/dist/dom/event-replay.js +46 -0
  24. package/dist/dom/event-replay.js.map +7 -0
  25. package/dist/dom/features/class/full.d.ts +1 -0
  26. package/dist/dom/features/class/full.js +91 -0
  27. package/dist/dom/features/class/full.js.map +7 -0
  28. package/dist/dom/features/class/index.d.ts +1 -0
  29. package/dist/dom/features/class/index.js +3 -0
  30. package/dist/dom/features/class/index.js.map +7 -0
  31. package/dist/dom/features/class/stub.d.ts +1 -0
  32. package/dist/dom/features/class/stub.js +50 -0
  33. package/dist/dom/features/class/stub.js.map +7 -0
  34. package/dist/dom/features/context/full.d.ts +1 -0
  35. package/dist/dom/features/context/full.js +49 -0
  36. package/dist/dom/features/context/full.js.map +7 -0
  37. package/dist/dom/features/context/index.d.ts +1 -0
  38. package/dist/dom/features/context/index.js +3 -0
  39. package/dist/dom/features/context/index.js.map +7 -0
  40. package/dist/dom/features/context/stub.d.ts +1 -0
  41. package/dist/dom/features/context/stub.js +22 -0
  42. package/dist/dom/features/context/stub.js.map +7 -0
  43. package/dist/dom/features/forward-ref/full.d.ts +1 -0
  44. package/dist/dom/features/forward-ref/full.js +51 -0
  45. package/dist/dom/features/forward-ref/full.js.map +7 -0
  46. package/dist/dom/features/forward-ref/index.d.ts +1 -0
  47. package/dist/dom/features/forward-ref/index.js +3 -0
  48. package/dist/dom/features/forward-ref/index.js.map +7 -0
  49. package/dist/dom/features/forward-ref/stub.d.ts +1 -0
  50. package/dist/dom/features/forward-ref/stub.js +22 -0
  51. package/dist/dom/features/forward-ref/stub.js.map +7 -0
  52. package/dist/dom/features/hydration/full.d.ts +61 -0
  53. package/dist/dom/features/hydration/full.js +264 -0
  54. package/dist/dom/features/hydration/full.js.map +7 -0
  55. package/dist/dom/features/hydration/index.d.ts +1 -0
  56. package/dist/dom/features/hydration/index.js +3 -0
  57. package/dist/dom/features/hydration/index.js.map +7 -0
  58. package/dist/dom/features/hydration/stub.d.ts +28 -0
  59. package/dist/dom/features/hydration/stub.js +66 -0
  60. package/dist/dom/features/hydration/stub.js.map +7 -0
  61. package/dist/dom/features/index.d.ts +7 -0
  62. package/dist/dom/features/index.js +9 -0
  63. package/dist/dom/features/index.js.map +7 -0
  64. package/dist/dom/features/lazy/full.d.ts +1 -0
  65. package/dist/dom/features/lazy/full.js +78 -0
  66. package/dist/dom/features/lazy/full.js.map +7 -0
  67. package/dist/dom/features/lazy/index.d.ts +1 -0
  68. package/dist/dom/features/lazy/index.js +3 -0
  69. package/dist/dom/features/lazy/index.js.map +7 -0
  70. package/dist/dom/features/lazy/stub.d.ts +1 -0
  71. package/dist/dom/features/lazy/stub.js +36 -0
  72. package/dist/dom/features/lazy/stub.js.map +7 -0
  73. package/dist/dom/features/memo/full.d.ts +1 -0
  74. package/dist/dom/features/memo/full.js +59 -0
  75. package/dist/dom/features/memo/full.js.map +7 -0
  76. package/dist/dom/features/memo/index.d.ts +1 -0
  77. package/dist/dom/features/memo/index.js +3 -0
  78. package/dist/dom/features/memo/index.js.map +7 -0
  79. package/dist/dom/features/memo/stub.d.ts +1 -0
  80. package/dist/dom/features/memo/stub.js +36 -0
  81. package/dist/dom/features/memo/stub.js.map +7 -0
  82. package/dist/dom/features/portal/full.d.ts +1 -0
  83. package/dist/dom/features/portal/full.js +19 -0
  84. package/dist/dom/features/portal/full.js.map +7 -0
  85. package/dist/dom/features/portal/index.d.ts +1 -0
  86. package/dist/dom/features/portal/index.js +3 -0
  87. package/dist/dom/features/portal/index.js.map +7 -0
  88. package/dist/dom/features/portal/stub.d.ts +1 -0
  89. package/dist/dom/features/portal/stub.js +7 -0
  90. package/dist/dom/features/portal/stub.js.map +7 -0
  91. package/dist/dom/features/suspense/full.d.ts +1 -0
  92. package/dist/dom/features/suspense/full.js +146 -0
  93. package/dist/dom/features/suspense/full.js.map +7 -0
  94. package/dist/dom/features/suspense/index.d.ts +1 -0
  95. package/dist/dom/features/suspense/index.js +3 -0
  96. package/dist/dom/features/suspense/index.js.map +7 -0
  97. package/dist/dom/features/suspense/stub.d.ts +1 -0
  98. package/dist/dom/features/suspense/stub.js +6 -0
  99. package/dist/dom/features/suspense/stub.js.map +7 -0
  100. package/dist/dom/index.d.ts +25 -0
  101. package/dist/dom/index.js +45 -0
  102. package/dist/dom/index.js.map +7 -0
  103. package/dist/dom/portal.d.ts +2 -0
  104. package/dist/dom/portal.js +15 -0
  105. package/dist/dom/portal.js.map +7 -0
  106. package/dist/dom/reconcile.d.ts +40 -0
  107. package/dist/dom/reconcile.js +795 -0
  108. package/dist/dom/reconcile.js.map +7 -0
  109. package/dist/dom/root.d.ts +15 -0
  110. package/dist/dom/root.js +85 -0
  111. package/dist/dom/root.js.map +7 -0
  112. package/dist/dom/test-utils.d.ts +1 -0
  113. package/dist/dom/test-utils.js +10 -0
  114. package/dist/dom/test-utils.js.map +7 -0
  115. package/dist/react/children.d.ts +8 -0
  116. package/dist/react/children.js +75 -0
  117. package/dist/react/children.js.map +7 -0
  118. package/dist/react/class.d.ts +31 -0
  119. package/dist/react/class.js +40 -0
  120. package/dist/react/class.js.map +7 -0
  121. package/dist/react/context.d.ts +27 -0
  122. package/dist/react/context.js +31 -0
  123. package/dist/react/context.js.map +7 -0
  124. package/dist/react/element.d.ts +10 -0
  125. package/dist/react/element.js +64 -0
  126. package/dist/react/element.js.map +7 -0
  127. package/dist/react/hooks.d.ts +37 -0
  128. package/dist/react/hooks.js +146 -0
  129. package/dist/react/hooks.js.map +7 -0
  130. package/dist/react/index.d.ts +76 -0
  131. package/dist/react/index.js +187 -0
  132. package/dist/react/index.js.map +7 -0
  133. package/dist/react/jsx-runtime.d.ts +5 -0
  134. package/dist/react/jsx-runtime.js +21 -0
  135. package/dist/react/jsx-runtime.js.map +7 -0
  136. package/dist/react/memo.d.ts +29 -0
  137. package/dist/react/memo.js +47 -0
  138. package/dist/react/memo.js.map +7 -0
  139. package/dist/react/portal.d.ts +1 -0
  140. package/dist/react/portal.js +6 -0
  141. package/dist/react/portal.js.map +7 -0
  142. package/dist/react/shared-internals.d.ts +33 -0
  143. package/dist/react/shared-internals.js +29 -0
  144. package/dist/react/shared-internals.js.map +7 -0
  145. package/dist/react/suspense.d.ts +15 -0
  146. package/dist/react/suspense.js +16 -0
  147. package/dist/react/suspense.js.map +7 -0
  148. package/dist/scheduler/index.d.ts +44 -0
  149. package/dist/scheduler/index.js +109 -0
  150. package/dist/scheduler/index.js.map +7 -0
  151. package/dist/server/bootstrap-script.d.ts +22 -0
  152. package/dist/server/bootstrap-script.js +16 -0
  153. package/dist/server/bootstrap-script.js.map +7 -0
  154. package/dist/server/dispatcher.d.ts +37 -0
  155. package/dist/server/dispatcher.js +140 -0
  156. package/dist/server/dispatcher.js.map +7 -0
  157. package/dist/server/escape.d.ts +7 -0
  158. package/dist/server/escape.js +159 -0
  159. package/dist/server/escape.js.map +7 -0
  160. package/dist/server/index.d.ts +16 -0
  161. package/dist/server/index.js +26 -0
  162. package/dist/server/index.js.map +7 -0
  163. package/dist/server/renderToString.d.ts +7 -0
  164. package/dist/server/renderToString.js +29 -0
  165. package/dist/server/renderToString.js.map +7 -0
  166. package/dist/server/stream.d.ts +40 -0
  167. package/dist/server/stream.js +200 -0
  168. package/dist/server/stream.js.map +7 -0
  169. package/dist/server/walk.d.ts +33 -0
  170. package/dist/server/walk.js +298 -0
  171. package/dist/server/walk.js.map +7 -0
  172. package/dist/vite/index.d.ts +96 -0
  173. package/dist/vite/index.js +217 -0
  174. package/dist/vite/index.js.map +7 -0
  175. package/package.json +83 -0
  176. package/src/core/index.ts +2 -0
  177. package/src/core/internal.ts +107 -0
  178. package/src/core/types.ts +36 -0
  179. package/src/dom/_all.ts +54 -0
  180. package/src/dom/client.ts +4 -0
  181. package/src/dom/dispatcher.ts +318 -0
  182. package/src/dom/dom.ts +316 -0
  183. package/src/dom/event-replay.ts +55 -0
  184. package/src/dom/features/class/full.ts +104 -0
  185. package/src/dom/features/class/index.ts +1 -0
  186. package/src/dom/features/class/stub.ts +65 -0
  187. package/src/dom/features/context/full.ts +57 -0
  188. package/src/dom/features/context/index.ts +1 -0
  189. package/src/dom/features/context/stub.ts +32 -0
  190. package/src/dom/features/forward-ref/full.ts +54 -0
  191. package/src/dom/features/forward-ref/index.ts +1 -0
  192. package/src/dom/features/forward-ref/stub.ts +28 -0
  193. package/src/dom/features/hydration/full.ts +345 -0
  194. package/src/dom/features/hydration/index.ts +1 -0
  195. package/src/dom/features/hydration/stub.ts +72 -0
  196. package/src/dom/features/index.ts +11 -0
  197. package/src/dom/features/lazy/full.ts +98 -0
  198. package/src/dom/features/lazy/index.ts +1 -0
  199. package/src/dom/features/lazy/stub.ts +45 -0
  200. package/src/dom/features/memo/full.ts +74 -0
  201. package/src/dom/features/memo/index.ts +1 -0
  202. package/src/dom/features/memo/stub.ts +42 -0
  203. package/src/dom/features/portal/full.ts +22 -0
  204. package/src/dom/features/portal/index.ts +1 -0
  205. package/src/dom/features/portal/stub.ts +11 -0
  206. package/src/dom/features/suspense/full.ts +191 -0
  207. package/src/dom/features/suspense/index.ts +1 -0
  208. package/src/dom/features/suspense/stub.ts +12 -0
  209. package/src/dom/index.ts +33 -0
  210. package/src/dom/portal.ts +16 -0
  211. package/src/dom/reconcile.ts +1169 -0
  212. package/src/dom/root.ts +107 -0
  213. package/src/dom/test-utils.ts +6 -0
  214. package/src/react/children.ts +85 -0
  215. package/src/react/class.ts +56 -0
  216. package/src/react/context.ts +50 -0
  217. package/src/react/element.ts +80 -0
  218. package/src/react/hooks.ts +190 -0
  219. package/src/react/index.ts +132 -0
  220. package/src/react/jsx-runtime.ts +16 -0
  221. package/src/react/memo.ts +45 -0
  222. package/src/react/portal.ts +1 -0
  223. package/src/react/shared-internals.ts +79 -0
  224. package/src/react/suspense.ts +18 -0
  225. package/src/scheduler/index.ts +124 -0
  226. package/src/server/bootstrap-script.ts +52 -0
  227. package/src/server/dispatcher.ts +158 -0
  228. package/src/server/escape.ts +187 -0
  229. package/src/server/index.ts +26 -0
  230. package/src/server/renderToString.ts +33 -0
  231. package/src/server/stream.ts +302 -0
  232. package/src/server/walk.ts +448 -0
  233. package/src/vite/index.ts +401 -0
@@ -0,0 +1,1169 @@
1
+ import {
2
+ FiberTag,
3
+ FiberFlag,
4
+ createFiber,
5
+ REACT_ELEMENT_TYPE,
6
+ REACT_LEGACY_ELEMENT_TYPE,
7
+ REACT_FRAGMENT_TYPE,
8
+ type Fiber,
9
+ type FiberRoot,
10
+ type ReactElement,
11
+ type ReactNode,
12
+ type Hook,
13
+ type Effect,
14
+ } from '../core'
15
+ import {
16
+ ReactSharedInternals,
17
+ REACT_LAZY_TYPE,
18
+ REACT_STRICT_MODE_TYPE,
19
+ REACT_PROFILER_TYPE,
20
+ } from '../react'
21
+ import { createHostNode, setProp } from './dom'
22
+ import { makeDispatcher } from './dispatcher'
23
+ import {
24
+ adoptHostDom,
25
+ adoptTextDom,
26
+ tryConsumeBoundary,
27
+ advanceCursorPast,
28
+ setHydrationCursor,
29
+ getHydrationCursor,
30
+ clearHydrationCursor,
31
+ HydrationCursor,
32
+ findHostParent as findHydrationHost,
33
+ } from './features/hydration'
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Render scheduling
37
+ // ---------------------------------------------------------------------------
38
+
39
+ let currentRoot: FiberRoot | null = null
40
+ let flushing = false
41
+ let isBatching = false
42
+ const pendingRoots = new Set<FiberRoot>()
43
+
44
+ // Set by rerenderFiber to identify the exact memo-tagged fiber whose INTERNAL
45
+ // state (hook update, useSyncExternalStore notification) triggered this render
46
+ // pass. renderMemo checks this to bypass its prop-equality gate for that fiber.
47
+ // Without the bypass, a memo bail would swallow state changes: React's memo is
48
+ // only a parent-triggered gate — state-driven rerenders must always run the
49
+ // inner function. Router-adjacent components (Outlet, Match, MatchInner) are
50
+ // all memo-wrapped and subscribe to stores; missing this bypass breaks nav
51
+ // content updates even though the URL changes.
52
+ let forceRerenderingFiber: Fiber | null = null
53
+
54
+ export function scheduleUpdate(fiber: Fiber): void {
55
+ // Drop updates scheduled on already-unmounted fibers. Subscribers (router,
56
+ // query, any external store) can fire after unmount if their cleanup was
57
+ // missed, and letting those reach rerenderFiber mounts zombie DOM into the
58
+ // old .parent's DOM (which stays reachable via the stale pointer).
59
+ if (fiber.unmounted) return
60
+ const root = findRoot(fiber)
61
+ if (!root) return
62
+ root.pending.add(fiber)
63
+ fiber.dirty = true
64
+ pendingRoots.add(root)
65
+ if (isBatching) return
66
+ if (!root.scheduled) {
67
+ root.scheduled = true
68
+ queueMicrotask(flushPending)
69
+ }
70
+ }
71
+
72
+ export function flushSyncWork(fn: () => void): void {
73
+ const wasBatching = isBatching
74
+ isBatching = true
75
+ try {
76
+ fn()
77
+ } finally {
78
+ isBatching = wasBatching
79
+ }
80
+ flushPending()
81
+ }
82
+
83
+ export function batchedUpdates<T>(fn: () => T): T {
84
+ const wasBatching = isBatching
85
+ isBatching = true
86
+ try {
87
+ return fn()
88
+ } finally {
89
+ isBatching = wasBatching
90
+ if (!wasBatching) flushPending()
91
+ }
92
+ }
93
+
94
+ function flushPending(): void {
95
+ if (flushing) return
96
+ flushing = true
97
+ try {
98
+ let guard = 0
99
+ while (pendingRoots.size > 0) {
100
+ if (++guard > 50) {
101
+ throw new Error('flushPending exceeded 50 iterations — suspected infinite update loop.')
102
+ }
103
+ const roots = [...pendingRoots]
104
+ pendingRoots.clear()
105
+ for (const root of roots) {
106
+ root.scheduled = false
107
+ // Render each pending fiber from shallowest first so an ancestor's
108
+ // cascade reaches descendants before we try to render them directly.
109
+ // Descendants rendered via cascade still have `dirty=true` (only
110
+ // rerenderFiber clears it); when we later reach them in this loop,
111
+ // rerenderFiber's own `if (!dirty) return` is our short-circuit. We
112
+ // previously filtered descendants of dirty ancestors here, but that
113
+ // loses updates whenever an ancestor's render doesn't actually reach
114
+ // the descendant — e.g. React.memo bailing on equal props. Keep all
115
+ // dirty fibers and let rerenderFiber de-dupe via its dirty check.
116
+ const pending = [...root.pending]
117
+ root.pending.clear()
118
+ pending.sort((a, b) => fiberDepth(a) - fiberDepth(b))
119
+ for (const fiber of pending) {
120
+ rerenderFiber(fiber, root)
121
+ }
122
+ runEffects(root)
123
+ }
124
+ }
125
+ } finally {
126
+ flushing = false
127
+ }
128
+ }
129
+
130
+ function fiberDepth(fiber: Fiber): number {
131
+ let d = 0
132
+ let p: Fiber | null = fiber.parent
133
+ while (p) {
134
+ d++
135
+ p = p.parent
136
+ }
137
+ return d
138
+ }
139
+
140
+ export function findRoot(fiber: Fiber): FiberRoot | null {
141
+ let f: Fiber | null = fiber
142
+ while (f) {
143
+ if (f.root) return f.root
144
+ f = f.parent
145
+ }
146
+ return null
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Entry points (called by createRoot)
151
+ // ---------------------------------------------------------------------------
152
+
153
+ export function renderRoot(root: FiberRoot, children: ReactNode): void {
154
+ const rootFiber = root.current
155
+ rootFiber.pendingProps = { children }
156
+ currentRoot = root
157
+ try {
158
+ reconcileChildren(rootFiber, childrenToArray(children), root.container as Node, null)
159
+ rootFiber.memoizedProps = rootFiber.pendingProps
160
+ rootFiber.dirty = false
161
+ } finally {
162
+ currentRoot = null
163
+ }
164
+ runEffects(root)
165
+ }
166
+
167
+ function rerenderFiber(fiber: Fiber, root: FiberRoot): void {
168
+ if (!fiber.dirty) return
169
+ // Skip fibers that were unmounted between scheduling and flush. Without this,
170
+ // the flush loop re-enters a zombie fiber whose .parent is still set; its
171
+ // render mounts fresh DOM into the old parent's still-attached DOM (since
172
+ // unmountFiber only clears fiber.child, not fiber.parent). Visible as route
173
+ // content from a previous location staying on screen after nav, because a
174
+ // pending rerender on the old route's LibraryLandingPage (unmounted during
175
+ // Outlet's shallow-first render) still fires from root.pending.
176
+ if (fiber.unmounted) return
177
+ // Clear BEFORE rendering so a scheduleUpdate() triggered mid-render (e.g.
178
+ // error boundary catching a descendant throw) marks us dirty for the next
179
+ // flush iteration instead of being wiped out when render() completes.
180
+ fiber.dirty = false
181
+ currentRoot = root
182
+ // If this rerender is resuming a hydration that was deferred by a suspension,
183
+ // re-activate hydration mode for its duration so descendants adopt DOM
184
+ // instead of re-creating it.
185
+ const resumeHydration =
186
+ fiber.memoizedState && (fiber.memoizedState as any)._pendingHydration === true
187
+ const prevHydrating = root.hydrating
188
+ if (resumeHydration) {
189
+ delete (fiber.memoizedState as any)._pendingHydration
190
+ root.hydrating = true
191
+ }
192
+ const prevForcing = forceRerenderingFiber
193
+ forceRerenderingFiber = fiber
194
+ try {
195
+ renderFiber(fiber, getHostParent(fiber), getAnchor(fiber))
196
+ } finally {
197
+ forceRerenderingFiber = prevForcing
198
+ if (resumeHydration) {
199
+ root.hydrating = prevHydrating
200
+ // Deferred hydration completed — detach the preserved cursor so future
201
+ // updates (post-hydration state changes) don't try to adopt stale DOM.
202
+ clearHydrationCursor(fiber)
203
+ }
204
+ currentRoot = null
205
+ }
206
+ }
207
+
208
+ // ---------------------------------------------------------------------------
209
+ // Element → children normalization
210
+ // ---------------------------------------------------------------------------
211
+
212
+ type TextChild = { _text: string }
213
+ type NormalizedChild = ReactElement | TextChild | null
214
+
215
+ // NEVER use `'_text' in child` to distinguish text wrappers from elements.
216
+ // TanStack's RSC renderable proxies (createRscProxy with renderable: true) are
217
+ // Proxy wrappers around real React elements whose `has` trap returns `true`
218
+ // for ANY string key — so `'_text' in rscProxy` is TRUE even though the proxy
219
+ // is an element. That misidentification set a Text fiber's `pendingProps` to
220
+ // `child._text` (another chained RSC proxy), which then rendered as
221
+ // `[object Object]` when createTextNode stringified the element. React
222
+ // elements always carry `$$typeof`; our text wrapper never does — so the
223
+ // presence of `$$typeof` is the invariant we rely on.
224
+ function isTextChild(child: Exclude<NormalizedChild, null>): child is TextChild {
225
+ return (child as any).$$typeof === undefined
226
+ }
227
+
228
+ export function childrenToArray(children: ReactNode): NormalizedChild[] {
229
+ const out: NormalizedChild[] = []
230
+ pushChildren(children, out)
231
+ return out
232
+ }
233
+
234
+ function pushChildren(node: ReactNode, out: NormalizedChild[]): void {
235
+ if (node == null || typeof node === 'boolean') return
236
+ if (typeof node === 'string' || typeof node === 'number') {
237
+ // Empty strings render no text node (matches React + the `<!-- -->`
238
+ // separator elision on the SSR side so server/client agree).
239
+ if (node === '') return
240
+ out.push({ _text: '' + node })
241
+ return
242
+ }
243
+ if (Array.isArray(node)) {
244
+ for (let i = 0; i < node.length; i++) pushChildren(node[i], out)
245
+ return
246
+ }
247
+ if (isIterable(node)) {
248
+ for (const item of node as Iterable<ReactNode>) pushChildren(item, out)
249
+ return
250
+ }
251
+ if (typeof node === 'object') {
252
+ const t = (node as any).$$typeof
253
+ if (ACCEPTED_ELEMENT_MARKERS.has(t)) {
254
+ out.push(node as ReactElement)
255
+ return
256
+ }
257
+ // Raw React.lazy as a child. RSC Flight encodes 'use client' components
258
+ // (CodeBlock, CodeExplorer, etc.) as bare Lazy objects in the tree, not
259
+ // wrapped in REACT_ELEMENT_TYPE. Dropping them made code snippets
260
+ // disappear from docs pages. The RSC decoder pre-awaits payloads via
261
+ // `awaitLazyElements`, so by render time the status is 'fulfilled' and
262
+ // `_init()` returns the resolved element synchronously.
263
+ if (t === REACT_LAZY_TYPE) {
264
+ const lazy = node as any
265
+ const resolved = lazy._init(lazy._payload)
266
+ pushChildren(resolved, out)
267
+ return
268
+ }
269
+ }
270
+ }
271
+
272
+ function isIterable(obj: any): boolean {
273
+ return obj != null && typeof obj !== 'string' && typeof obj[Symbol.iterator] === 'function'
274
+ }
275
+
276
+ function getKeyOf(child: NormalizedChild, index: number): string {
277
+ if (!child) return 'n' + index
278
+ if (isTextChild(child)) return '$t' + index
279
+ if (child.key != null) return 'k' + child.key
280
+ return 'i' + index
281
+ }
282
+
283
+ function sameType(fiber: Fiber, child: NormalizedChild): boolean {
284
+ if (!child) return false
285
+ if (isTextChild(child)) return fiber.tag === FiberTag.Text
286
+ return fiber.type === child.type && sameKey(fiber.key, child.key)
287
+ }
288
+
289
+ function sameKey(a: string | null, b: string | null | undefined): boolean {
290
+ return (a ?? null) === (b ?? null)
291
+ }
292
+
293
+ // ---------------------------------------------------------------------------
294
+ // Fiber creation
295
+ // ---------------------------------------------------------------------------
296
+
297
+ function fiberFromChild(child: NormalizedChild, parent: Fiber): Fiber {
298
+ if (!child) return createFiber(FiberTag.Fragment, null, null)
299
+ if (isTextChild(child)) {
300
+ const f = createFiber(FiberTag.Text, null, null)
301
+ f.pendingProps = child._text
302
+ f.parent = parent
303
+ return f
304
+ }
305
+ const type = child.type
306
+ let tag: FiberTag = FiberTag.Host
307
+ const marker = type && (type as any).$$typeof
308
+ if (typeof type === 'string') tag = FiberTag.Host
309
+ else if (type === REACT_FRAGMENT_TYPE) tag = FiberTag.Fragment
310
+ else if (type === REACT_STRICT_MODE_TYPE || type === REACT_PROFILER_TYPE) tag = FiberTag.Fragment
311
+ else {
312
+ // Feature-registered type matchers (Portal, future extractions). Features
313
+ // that carry the symbol as element.type directly (rather than wrapping in
314
+ // REACT_ELEMENT_TYPE) match here by type identity.
315
+ let matched: FiberTag | null = null
316
+ for (const m of TYPE_MATCHERS) {
317
+ matched = m(type, marker)
318
+ if (matched !== null) break
319
+ }
320
+ if (matched !== null) tag = matched
321
+ else if (typeof type === 'function') {
322
+ tag = type.prototype && type.prototype.isReactComponent ? FiberTag.Class : FiberTag.Function
323
+ }
324
+ }
325
+ const f = createFiber(tag, type, child.key ?? null)
326
+ f.ref = (child as any).ref ?? null
327
+ f.pendingProps = child.props
328
+ f.parent = parent
329
+ return f
330
+ }
331
+
332
+ // ---------------------------------------------------------------------------
333
+ // Reconciliation
334
+ // ---------------------------------------------------------------------------
335
+
336
+ /**
337
+ * Reconcile a parent fiber's child list against new normalized children.
338
+ * Mutates parent.child and the sibling chain.
339
+ * Mounts new host DOM into `domParent` before `anchor` (or appends if anchor === null).
340
+ */
341
+ export function reconcileChildren(
342
+ parent: Fiber,
343
+ newChildren: NormalizedChild[],
344
+ domParent: Node,
345
+ anchor: Node | null,
346
+ ): void {
347
+ const existing = collectChildren(parent)
348
+ const keyed = new Map<string, Fiber>()
349
+ for (const f of existing) {
350
+ if (f.key != null) keyed.set('k' + f.key, f)
351
+ }
352
+
353
+ let prevNewFiber: Fiber | null = null
354
+ const claimed = new Set<Fiber>()
355
+ let structurallyChanged = false
356
+ // Budget-guided positional matching. We walk `existing` (unkeyed only) with a
357
+ // single cursor `existingIdx` and, on a type mismatch, choose insert vs delete
358
+ // based on the remaining length delta (`budget`):
359
+ // budget > 0: more new than old remain → treat slot as an INSERTION: keep
360
+ // the old cursor and create a fresh fiber for new[i].
361
+ // budget < 0: more old than new remain → treat slot as a DELETION: advance
362
+ // the old cursor past the mismatched fiber (it'll be unmounted
363
+ // in the unclaimed pass) and retry.
364
+ // budget == 0: equal remaining → treat as REPLACE by preferring delete
365
+ // until budget flips positive or we hit a match.
366
+ // This avoids greedy forward scans that steal a later same-type fiber for a
367
+ // newly inserted leading sibling (e.g. smallMenu flipping null → <div>
368
+ // stealing the content <div>'s fiber and tearing down the drawer fragment).
369
+ let existingIdx = 0
370
+ let unkeyedOld = 0
371
+ for (const f of existing) if (f.key == null) unkeyedOld++
372
+ let unkeyedNew = 0
373
+ for (const c of newChildren) if (c != null) unkeyedNew++
374
+ let budget = unkeyedNew - unkeyedOld
375
+
376
+ for (let i = 0; i < newChildren.length; i++) {
377
+ const child = newChildren[i]
378
+ if (child == null) continue
379
+
380
+ let match: Fiber | null = null
381
+
382
+ // key-based match
383
+ if (child && typeof child === 'object' && !isTextChild(child) && (child as ReactElement).key != null) {
384
+ const k = 'k' + (child as ReactElement).key
385
+ const m = keyed.get(k)
386
+ if (m && m.type === (child as ReactElement).type) {
387
+ match = m
388
+ keyed.delete(k)
389
+ }
390
+ }
391
+
392
+ if (!match) {
393
+ while (existingIdx < existing.length) {
394
+ const cand = existing[existingIdx]!
395
+ if (claimed.has(cand) || cand.key != null) {
396
+ existingIdx++
397
+ continue
398
+ }
399
+ if (sameType(cand, child)) {
400
+ match = cand
401
+ existingIdx++
402
+ break
403
+ }
404
+ // Type mismatch at the cursor. Resolve via budget.
405
+ if (budget > 0) {
406
+ // Insertion: leave cand in place, create new for child.
407
+ break
408
+ }
409
+ // Deletion (or replace-as-delete-first): advance past cand. It remains
410
+ // unclaimed and will be unmounted at the end.
411
+ existingIdx++
412
+ budget++
413
+ }
414
+ }
415
+
416
+ // Detect reorder: matched fiber is not at its original position
417
+ if (match && existing[i] !== match) structurallyChanged = true
418
+
419
+ let fiber: Fiber
420
+ if (match) {
421
+ claimed.add(match)
422
+ fiber = match
423
+ if (isTextChild(child!)) {
424
+ fiber.pendingProps = child._text
425
+ } else {
426
+ fiber.type = (child as ReactElement).type
427
+ fiber.pendingProps = (child as ReactElement).props
428
+ fiber.ref = (child as any).ref ?? null
429
+ }
430
+ } else {
431
+ fiber = fiberFromChild(child, parent)
432
+ structurallyChanged = true
433
+ if (budget > 0) budget--
434
+ }
435
+
436
+ fiber.parent = parent
437
+ fiber.sibling = null
438
+ if (prevNewFiber) prevNewFiber.sibling = fiber
439
+ else parent.child = fiber
440
+ prevNewFiber = fiber
441
+
442
+ // Render this fiber (mount or update)
443
+ renderFiber(fiber, domParent, anchor)
444
+ }
445
+
446
+ if (!prevNewFiber) parent.child = null
447
+ else prevNewFiber.sibling = null
448
+
449
+ // Head content is additive — server may inject metadata/stylesheets (Vite
450
+ // dev styles, Sentry, analytics) that aren't in the React tree. Unmounting
451
+ // them on every reconcile thrashes styles and causes flash of unstyled
452
+ // content. Keep existing head children that weren't matched this pass.
453
+ const parentIsHeadHost =
454
+ parent.tag === FiberTag.Host &&
455
+ typeof parent.type === 'string' &&
456
+ (parent.type as string).toLowerCase() === 'head'
457
+
458
+ if (!parentIsHeadHost) {
459
+ // Unmount unclaimed
460
+ for (const f of existing) {
461
+ if (!claimed.has(f)) {
462
+ unmountFiber(f, domParent)
463
+ structurallyChanged = true
464
+ }
465
+ }
466
+ // Leftover keyed
467
+ for (const f of keyed.values()) {
468
+ if (!claimed.has(f)) {
469
+ unmountFiber(f, domParent)
470
+ structurallyChanged = true
471
+ }
472
+ }
473
+ }
474
+
475
+ // During hydration, DOM is already in document order from the cursor-driven
476
+ // adoption walk. Running placeChildrenInOrder here would reappend nodes to
477
+ // the end of domParent when the true anchor (often an end marker comment)
478
+ // isn't reflected in `anchor`. Skip it in hydration mode.
479
+ //
480
+ // For <head>, skip always — HeadContent re-renders routinely (route match
481
+ // changes, providers updating), and reordering every <link>/<style>/<meta>
482
+ // on each re-render causes stylesheet flash and re-download. Head element
483
+ // ordering is semantically fluid; the browser doesn't care about exact
484
+ // order within <head>.
485
+ const parentIsHead =
486
+ (domParent as Element).nodeType === 1 &&
487
+ (domParent as Element).tagName.toLowerCase() === 'head'
488
+ if (structurallyChanged && !currentRoot?.hydrating && !parentIsHead) {
489
+ placeChildrenInOrder(parent, domParent, anchor)
490
+ }
491
+ }
492
+
493
+ function placeChildrenInOrder(parent: Fiber, domParent: Node, anchor: Node | null): void {
494
+ const doms: Node[] = []
495
+ let c = parent.child
496
+ while (c) {
497
+ collectHostDoms(c, doms)
498
+ c = c.sibling
499
+ }
500
+
501
+ // Pre-check: if our fiber-owned DOM is already in document order within
502
+ // domParent AND the end anchor matches, no reorder is needed. This is the
503
+ // common case on stable re-renders, and avoids detaching/re-attaching
504
+ // subtrees (which cancels CSS animations and triggers layout).
505
+ if (doms.length > 0) {
506
+ let current: Node | null = doms[0]!
507
+ let inOrder = current.parentNode === domParent
508
+ for (let i = 1; inOrder && i < doms.length; i++) {
509
+ current = current!.nextSibling
510
+ // Skip foreign nodes (SSR-injected scripts, dev-styles) between owned
511
+ // fiber DOMs — they should stay where they are.
512
+ while (current && !doms.includes(current as Node)) {
513
+ current = current.nextSibling
514
+ }
515
+ if (current !== doms[i]) inOrder = false
516
+ }
517
+ if (inOrder) return
518
+ }
519
+
520
+ // Reverse-iterate, anchoring each node before the one that should follow it.
521
+ // This works because by the time we're placing doms[i], doms[i+1] is already
522
+ // in its final slot. Forward iteration is buggy: insertBefore(doms[i],
523
+ // doms[i+1]) pulls doms[i] forward past any nodes that SHOULD move behind
524
+ // it, leaving those nodes mis-anchored (app-starter Analyze/Lucky swap, npm
525
+ // stats library dropdown reorder — both reported by users).
526
+ //
527
+ // Concrete example: start=[A, R, L], target=[A, L, R]. Forward pass gives
528
+ // [L, A, R] (wrong). Reverse pass moves R to end, then L and A are already
529
+ // correct — 1 move, matches target.
530
+ //
531
+ // Skip nodes already in their target position so CSS transitions on stable
532
+ // siblings aren't cancelled (e.g. drawer slide animation).
533
+ for (let i = doms.length - 1; i >= 0; i--) {
534
+ const d = doms[i]!
535
+ const targetNext: Node | null = i + 1 < doms.length ? doms[i + 1]! : anchor
536
+ if (d.parentNode !== domParent || d.nextSibling !== targetNext) {
537
+ domParent.insertBefore(d, targetNext)
538
+ }
539
+ }
540
+ }
541
+
542
+ function collectHostDoms(fiber: Fiber, out: Node[]): void {
543
+ if (fiber.tag === FiberTag.Host || fiber.tag === FiberTag.Text) {
544
+ if (fiber.dom) out.push(fiber.dom)
545
+ return
546
+ }
547
+ if (fiber.tag === FiberTag.Portal) return
548
+ let c = fiber.child
549
+ while (c) {
550
+ collectHostDoms(c, out)
551
+ c = c.sibling
552
+ }
553
+ }
554
+
555
+ function collectChildren(parent: Fiber): Fiber[] {
556
+ const out: Fiber[] = []
557
+ let c = parent.child
558
+ while (c) {
559
+ out.push(c)
560
+ c = c.sibling
561
+ }
562
+ return out
563
+ }
564
+
565
+ // ---------------------------------------------------------------------------
566
+ // Rendering per fiber tag
567
+ // ---------------------------------------------------------------------------
568
+
569
+ export type RenderFn = (fiber: Fiber, domParent: Node, anchor: Node | null) => void
570
+ export type TypeMatcher = (type: any, marker: any) => FiberTag | null
571
+
572
+ // Mutable renderer registry indexed by FiberTag. Feature modules install their
573
+ // renderer via registerRenderer(); unregistered features render as no-ops. The
574
+ // initial registrations below rely on function-declaration hoisting — every
575
+ // render* function is declared with `function` later in this file.
576
+ const RENDERERS: Array<RenderFn | undefined> = new Array(13)
577
+
578
+ // Element-marker allowlist for child normalization (pushChildren). Core-always
579
+ // markers are seeded here; features add their own via registerElementMarker.
580
+ const ACCEPTED_ELEMENT_MARKERS = new Set<symbol>([
581
+ REACT_ELEMENT_TYPE as symbol,
582
+ REACT_LEGACY_ELEMENT_TYPE as symbol,
583
+ ])
584
+
585
+ // Type-to-tag matchers tried in registration order from fiberFromChild's
586
+ // fallback branch. Features register here for element types that aren't
587
+ // marker-based (e.g. Portal, where element.type IS the symbol).
588
+ const TYPE_MATCHERS: TypeMatcher[] = []
589
+
590
+ export function registerRenderer(tag: FiberTag, fn: RenderFn): void {
591
+ RENDERERS[tag] = fn
592
+ }
593
+
594
+ export function registerTypeMatcher(m: TypeMatcher): void {
595
+ TYPE_MATCHERS.push(m)
596
+ }
597
+
598
+ export function registerElementMarker(sym: symbol): void {
599
+ ACCEPTED_ELEMENT_MARKERS.add(sym)
600
+ }
601
+
602
+ // Accessor + scoped setter for the module-level `currentRoot`. Feature modules
603
+ // need these to participate in the render loop (e.g. Suspense re-hydration
604
+ // must temporarily set the root while rebuilding a boundary subtree).
605
+ export function getCurrentRoot(): FiberRoot | null {
606
+ return currentRoot
607
+ }
608
+
609
+ export function withCurrentRoot<T>(root: FiberRoot | null, fn: () => T): T {
610
+ const prev = currentRoot
611
+ currentRoot = root
612
+ try {
613
+ return fn()
614
+ } finally {
615
+ currentRoot = prev
616
+ }
617
+ }
618
+
619
+ // The memo feature uses this to bypass its prop-equality gate on state-driven
620
+ // rerenders of the memoized fiber itself (hook update / subscribed store),
621
+ // where props haven't changed by definition.
622
+ export function getForceRerenderingFiber(): Fiber | null {
623
+ return forceRerenderingFiber
624
+ }
625
+
626
+ registerRenderer(FiberTag.Text, renderText)
627
+ registerRenderer(FiberTag.Host, renderHost)
628
+ registerRenderer(FiberTag.Function, renderFunction)
629
+ registerRenderer(FiberTag.Fragment, renderFragment)
630
+
631
+ export function renderFiber(fiber: Fiber, domParent: Node, anchor: Node | null): void {
632
+ const fn = RENDERERS[fiber.tag]
633
+ if (fn) fn(fiber, domParent, anchor)
634
+ }
635
+
636
+ function renderText(fiber: Fiber, domParent: Node, anchor: Node | null): void {
637
+ const text = fiber.pendingProps as string
638
+ if (!fiber.dom) {
639
+ const hydrated = currentRoot?.hydrating ? adoptTextDom(fiber, fiber.parent!, text) : false
640
+ if (!hydrated) {
641
+ fiber.dom = document.createTextNode(text)
642
+ insertInto(domParent, fiber.dom, anchor)
643
+ }
644
+ } else if ((fiber.dom as Text).data !== text) {
645
+ ;(fiber.dom as Text).data = text
646
+ }
647
+ fiber.memoizedProps = text
648
+ // dirty cleared at rerender start; leaving true lets mid-render schedule persist
649
+ }
650
+
651
+ function renderHost(fiber: Fiber, domParent: Node, anchor: Node | null): void {
652
+ const props = fiber.pendingProps ?? {}
653
+ const prev = fiber.memoizedProps ?? {}
654
+ const type = fiber.type as string
655
+ const isSvg = type === 'svg' || (domParent as Element).namespaceURI === 'http://www.w3.org/2000/svg'
656
+
657
+ // <select value> must be applied AFTER children mount — setting `.value`
658
+ // on a `<select>` with no matching `<option>` yet resets it to empty. Same
659
+ // for `defaultValue` on first mount. Stash and replay.
660
+ const isSelect = type === 'select'
661
+ const deferredSelectValue =
662
+ isSelect && (props.value !== undefined || props.defaultValue !== undefined)
663
+ ? props.value !== undefined ? props.value : props.defaultValue
664
+ : undefined
665
+
666
+ if (!fiber.dom) {
667
+ const hydrated = currentRoot?.hydrating ? adoptHostDom(fiber, fiber.parent!) : false
668
+ if (!hydrated) {
669
+ fiber.dom = createHostNode(type, isSvg)
670
+ // Two passes so form-control attributes (notably <input type>) are in
671
+ // place before event handlers attach. setEventHandler reads the
672
+ // element's runtime state to decide the DOM event name (e.g. onChange
673
+ // → `input` vs `change`); binding before `type` is applied would
674
+ // attach to the wrong event for checkbox/radio/file inputs.
675
+ for (const k in props) {
676
+ if (isSelect && (k === 'value' || k === 'defaultValue')) continue
677
+ if (isEventProp(k)) continue
678
+ setProp(fiber.dom as Element, k, props[k], undefined, isSvg)
679
+ }
680
+ for (const k in props) {
681
+ if (!isEventProp(k)) continue
682
+ setProp(fiber.dom as Element, k, props[k], undefined, isSvg)
683
+ }
684
+ insertInto(domParent, fiber.dom, anchor)
685
+ }
686
+ attachRef(fiber, fiber.dom)
687
+ } else {
688
+ const el = fiber.dom as Element
689
+ for (const k in prev) {
690
+ if (!(k in props)) setProp(el, k, undefined, prev[k], isSvg)
691
+ }
692
+ // Non-event props first for the same reason as above: a `type` change
693
+ // must land before we ask setEventHandler to resolve the DOM event for
694
+ // `onChange`.
695
+ for (const k in props) {
696
+ if (isSelect && (k === 'value' || k === 'defaultValue')) continue
697
+ if (isEventProp(k)) continue
698
+ if (prev[k] !== props[k]) setProp(el, k, props[k], prev[k], isSvg)
699
+ }
700
+ for (const k in props) {
701
+ if (!isEventProp(k)) continue
702
+ if (prev[k] !== props[k]) setProp(el, k, props[k], prev[k], isSvg)
703
+ }
704
+ if (prev !== props) syncRefIfChanged(fiber, fiber.dom)
705
+ }
706
+
707
+ // Children go into this DOM node
708
+ reconcileChildren(fiber, childrenToArray(props.children), fiber.dom!, null)
709
+
710
+ // During hydration, if after reconciling all client-expected children we
711
+ // still have server DOM left in the cursor for this host, that's a
712
+ // structural mismatch (server produced more than client wants). Report.
713
+ // <head>/<html> are position-insensitive — leftover here is normal
714
+ // (Vite dev-style injections, SSR-only scripts, etc.).
715
+ if (currentRoot?.hydrating) {
716
+ const parentTag = (fiber.type as string).toLowerCase()
717
+ if (parentTag !== 'head' && parentTag !== 'html') {
718
+ const cursor = getHydrationCursor(fiber)
719
+ if (cursor) {
720
+ const leftover = cursor.remaining().filter(
721
+ (n) => n.nodeType === 1 || n.nodeType === 3,
722
+ )
723
+ if (leftover.length > 0 && currentRoot.onRecoverableError) {
724
+ currentRoot.onRecoverableError(
725
+ new Error(
726
+ `Hydration mismatch: server rendered ${leftover.length} extra ` +
727
+ `${leftover.length === 1 ? 'node' : 'nodes'} inside <${parentTag}> ` +
728
+ `that the client tree did not.`,
729
+ ),
730
+ )
731
+ for (const n of leftover) n.parentNode?.removeChild(n)
732
+ }
733
+ }
734
+ }
735
+ }
736
+
737
+ // Apply <select> value after options are mounted.
738
+ if (isSelect && deferredSelectValue !== undefined) {
739
+ const select = fiber.dom as HTMLSelectElement
740
+ if (Array.isArray(deferredSelectValue)) {
741
+ const asStrings = deferredSelectValue.map((v) => '' + v)
742
+ for (const opt of Array.from(select.options)) {
743
+ opt.selected = asStrings.includes(opt.value)
744
+ }
745
+ } else {
746
+ select.value = '' + deferredSelectValue
747
+ }
748
+ }
749
+
750
+ fiber.memoizedProps = props
751
+ // dirty cleared at rerender start; leaving true lets mid-render schedule persist
752
+ }
753
+
754
+ function renderFunction(fiber: Fiber, domParent: Node, anchor: Node | null): void {
755
+ const prevDispatcher = ReactSharedInternals.H
756
+ const prevFiber = ReactSharedInternals.currentFiber
757
+ const prevHook = ReactSharedInternals.currentHook
758
+ const prevIndex = ReactSharedInternals.hookIndex
759
+
760
+ ReactSharedInternals.H = makeDispatcher()
761
+ ReactSharedInternals.currentFiber = fiber
762
+ ReactSharedInternals.currentHook = null
763
+ ReactSharedInternals.hookIndex = 0
764
+
765
+ let rendered: ReactNode
766
+ let deferredForHydration = false
767
+ try {
768
+ rendered = (fiber.type as Function)(fiber.pendingProps ?? {})
769
+ } catch (e: any) {
770
+ if (isThenable(e)) {
771
+ if (currentRoot?.hydrating) {
772
+ // Suspension during initial hydration. Leave the existing DOM alone
773
+ // and preserve the in-scope hydration cursor on THIS fiber so it
774
+ // survives the synchronous endHydration() that fires when the initial
775
+ // hydrateRoot() call returns. When the promise settles, the fiber
776
+ // re-renders (see rerenderFiber) with hydration re-activated and its
777
+ // descendants adopt DOM instead of creating new nodes.
778
+ const hostParent = findHydrationHost(fiber)
779
+ const inheritedCursor = getHydrationCursor(hostParent)
780
+ if (inheritedCursor) {
781
+ setHydrationCursor(fiber, inheritedCursor)
782
+ }
783
+ fiber.memoizedState = {
784
+ ...(fiber.memoizedState ?? {}),
785
+ _pendingHydration: true,
786
+ }
787
+ // Mirror renderLazy's guard: mark the nearest Suspense ancestor as
788
+ // awaiting hydration-resume, so any re-render of that Suspense (e.g.
789
+ // rehydrateBoundary fired by $RC, or an unrelated state update from a
790
+ // sibling) doesn't re-enter `tryChildren`, re-throw, and flip Suspense
791
+ // into its suspended+pending path — which would unmount our deferred
792
+ // subtree and remount a fallback on top of the SSR content. By
793
+ // pinning the Suspense to a "hydration-suspended" no-op until our
794
+ // resume fires, the deferred re-render owns the adoption pass.
795
+ let sus: Fiber | null = fiber.parent
796
+ while (sus && sus.tag !== FiberTag.Suspense) sus = sus.parent
797
+ if (sus && sus.memoizedState) {
798
+ ;(sus.memoizedState as any)._awaitingLazyHydration = true
799
+ }
800
+ const clearAwait = () => {
801
+ if (sus && sus.memoizedState) {
802
+ ;(sus.memoizedState as any)._awaitingLazyHydration = false
803
+ }
804
+ scheduleUpdate(fiber)
805
+ }
806
+ e.then(clearAwait, clearAwait)
807
+ deferredForHydration = true
808
+ } else {
809
+ CAPABILITIES.handleSuspended(fiber, e)
810
+ rendered = null
811
+ }
812
+ } else {
813
+ handleErrorInRender(fiber, e)
814
+ return
815
+ }
816
+ } finally {
817
+ ReactSharedInternals.H = prevDispatcher
818
+ ReactSharedInternals.currentFiber = prevFiber
819
+ ReactSharedInternals.currentHook = prevHook
820
+ ReactSharedInternals.hookIndex = prevIndex
821
+ }
822
+
823
+ if (deferredForHydration) return
824
+
825
+ reconcileChildren(fiber, childrenToArray(rendered), domParent, anchor)
826
+ fiber.memoizedProps = fiber.pendingProps
827
+ // dirty cleared at rerender start; leaving true lets mid-render schedule persist
828
+ }
829
+
830
+ function hasAncestorHydrationCursor(_fiber: Fiber): boolean {
831
+ // Reserved for future per-Suspense-boundary hydration deferral. For now the
832
+ // top-level hydration path is all we need to special-case.
833
+ return false
834
+ }
835
+
836
+ function renderFragment(fiber: Fiber, domParent: Node, anchor: Node | null): void {
837
+ const props = fiber.pendingProps ?? {}
838
+ reconcileChildren(fiber, childrenToArray(props.children), domParent, anchor)
839
+ fiber.memoizedProps = props
840
+ // dirty cleared at rerender start; leaving true lets mid-render schedule persist
841
+ }
842
+
843
+ // ---------------------------------------------------------------------------
844
+ // Error handling + default Suspense capability
845
+ // ---------------------------------------------------------------------------
846
+
847
+ // Default handler when the Suspense feature isn't installed: just schedule
848
+ // a re-render when the thrown thenable settles. No boundary walk, no
849
+ // fallback swap — children render empty during the pending window.
850
+ function defaultHandleSuspended(fiber: Fiber, thenable: Promise<any>): void {
851
+ thenable.then(
852
+ () => scheduleUpdate(fiber),
853
+ () => scheduleUpdate(fiber),
854
+ )
855
+ }
856
+
857
+ // ---------------------------------------------------------------------------
858
+ // Capability hooks — cross-cutting behaviors that features override.
859
+ // Defaults here preserve today's behavior so the indirection is transparent
860
+ // when all features are loaded. A feature's full-module can install its own
861
+ // implementation via installCapability(); stubs leave the default in place,
862
+ // where the default may intentionally degrade (e.g. a no-Context build's
863
+ // readContext never walks the tree because no Provider fibers exist).
864
+ // ---------------------------------------------------------------------------
865
+
866
+ export interface Capabilities {
867
+ handleSuspended: (fiber: Fiber, thenable: Promise<any>) => void
868
+ readContext: (fiber: Fiber, ctx: any) => any
869
+ }
870
+
871
+ const CAPABILITIES: Capabilities = {
872
+ handleSuspended: defaultHandleSuspended,
873
+ readContext: defaultReadContext,
874
+ }
875
+
876
+ export function installCapability<K extends keyof Capabilities>(
877
+ name: K,
878
+ fn: Capabilities[K],
879
+ ): void {
880
+ CAPABILITIES[name] = fn
881
+ }
882
+
883
+ // Wrapper for features that catch thrown thenables inside their render
884
+ // functions. Delegates to the installed Suspense capability.
885
+ export function handleSuspended(fiber: Fiber, thenable: Promise<any>): void {
886
+ CAPABILITIES.handleSuspended(fiber, thenable)
887
+ }
888
+
889
+ export function handleErrorInRender(fiber: Fiber, err: any): void {
890
+ // Bubble to nearest class boundary with getDerivedStateFromError / componentDidCatch
891
+ let f: Fiber | null = fiber.parent
892
+ while (f) {
893
+ if (f.tag === FiberTag.Class) {
894
+ const Ctor = f.type as any
895
+ const instance = f.stateNode
896
+ if (Ctor.getDerivedStateFromError) {
897
+ const update = Ctor.getDerivedStateFromError(err)
898
+ instance.state = { ...instance.state, ...update }
899
+ }
900
+ if (instance.componentDidCatch) {
901
+ try {
902
+ instance.componentDidCatch(err, { componentStack: '' })
903
+ } catch {}
904
+ }
905
+ scheduleUpdate(f)
906
+ return
907
+ }
908
+ f = f.parent
909
+ }
910
+ // No boundary — report to root
911
+ if (currentRoot?.onUncaughtError) currentRoot.onUncaughtError(err)
912
+ else throw err
913
+ }
914
+
915
+ export function isThenable(x: any): x is Promise<any> {
916
+ return x != null && typeof x.then === 'function'
917
+ }
918
+
919
+ // ---------------------------------------------------------------------------
920
+ // Unmount
921
+ // ---------------------------------------------------------------------------
922
+
923
+ function unmountFiber(fiber: Fiber, domParent: Node): void {
924
+ fiber.unmounted = true
925
+ // Recurse first
926
+ let c = fiber.child
927
+ while (c) {
928
+ const next = c.sibling
929
+ unmountFiber(c, fiber.tag === FiberTag.Host ? fiber.dom! : domParent)
930
+ c = next
931
+ }
932
+ fiber.child = null
933
+
934
+ // Run cleanups (effects + layout effects)
935
+ if (fiber.cleanups) {
936
+ for (const cleanup of fiber.cleanups) {
937
+ try {
938
+ cleanup()
939
+ } catch (e) {
940
+ if (currentRoot?.onRecoverableError) currentRoot.onRecoverableError(e)
941
+ }
942
+ }
943
+ fiber.cleanups = null
944
+ }
945
+
946
+ if (fiber.tag === FiberTag.Class && fiber.stateNode?.componentWillUnmount) {
947
+ try {
948
+ fiber.stateNode.componentWillUnmount()
949
+ } catch (e) {
950
+ if (currentRoot?.onRecoverableError) currentRoot.onRecoverableError(e)
951
+ }
952
+ fiber.stateNode._fiber = null
953
+ fiber.stateNode._enqueueUpdate = null
954
+ fiber.stateNode._forceUpdate = null
955
+ }
956
+
957
+ // Detach ref
958
+ if (fiber.ref) detachRef(fiber.ref)
959
+
960
+ // Remove DOM if host
961
+ if (fiber.tag === FiberTag.Host && fiber.dom && fiber.dom.parentNode) {
962
+ fiber.dom.parentNode.removeChild(fiber.dom)
963
+ } else if (fiber.tag === FiberTag.Text && fiber.dom && fiber.dom.parentNode) {
964
+ fiber.dom.parentNode.removeChild(fiber.dom)
965
+ }
966
+ }
967
+
968
+ export function unmountAllChildren(parent: Fiber, domParent: Node): void {
969
+ let c = parent.child
970
+ while (c) {
971
+ const next = c.sibling
972
+ unmountFiber(c, domParent)
973
+ c = next
974
+ }
975
+ parent.child = null
976
+ }
977
+
978
+ // ---------------------------------------------------------------------------
979
+ // DOM navigation helpers
980
+ // ---------------------------------------------------------------------------
981
+
982
+ function insertInto(parent: Node, node: Node, anchor: Node | null): void {
983
+ // Anchor may have been removed or moved since it was computed (mutations
984
+ // from unmount, boundary reveal, user code, HMR). If it's no longer a child
985
+ // of `parent`, fall back to append — trying to insertBefore a non-child
986
+ // throws NotFoundError and dev-loops the reconciler.
987
+ if (anchor && anchor.parentNode === parent) {
988
+ parent.insertBefore(node, anchor)
989
+ } else {
990
+ parent.appendChild(node)
991
+ }
992
+ }
993
+
994
+ function getHostParent(fiber: Fiber): Node {
995
+ let p = fiber.parent
996
+ while (p) {
997
+ if (p.tag === FiberTag.Host) return p.dom!
998
+ if (p.tag === FiberTag.Root)
999
+ return (p.stateNode as Node) || (p.dom as Node) || (p.root?.container as Node)
1000
+ if (p.tag === FiberTag.Portal) {
1001
+ // Portal renders its children into the `container` prop, not into any
1002
+ // DOM element the portal fiber "owns". Read the container from the
1003
+ // portal's own props so a rerenderFiber triggered on a descendant
1004
+ // (e.g. a Floating-UI-positioned popper in a Radix Portal) finds its
1005
+ // host parent — otherwise getHostParent returns undefined and the
1006
+ // next renderHost crashes reading `.namespaceURI` on undefined.
1007
+ const props = (p.pendingProps ?? p.memoizedProps) as { container?: Element } | null
1008
+ return (props?.container as Node) || (p.stateNode as Node) || (p.dom as Node) || (p.root?.container as Node)
1009
+ }
1010
+ p = p.parent
1011
+ }
1012
+ throw new Error('No host parent found.')
1013
+ }
1014
+
1015
+ function getAnchor(fiber: Fiber): Node | null {
1016
+ // Return the first DOM node that comes after this fiber within the host parent
1017
+ let f: Fiber | null = fiber.sibling
1018
+ while (f) {
1019
+ const d = firstDomNode(f)
1020
+ if (d) return d
1021
+ f = f.sibling
1022
+ }
1023
+ // Ascend
1024
+ let p = fiber.parent
1025
+ while (p && p.tag !== FiberTag.Host && p.tag !== FiberTag.Root && p.tag !== FiberTag.Portal) {
1026
+ if (p.sibling) {
1027
+ const d = firstDomNode(p.sibling)
1028
+ if (d) return d
1029
+ }
1030
+ p = p.parent
1031
+ }
1032
+ return null
1033
+ }
1034
+
1035
+ function firstDomNode(fiber: Fiber): Node | null {
1036
+ if (fiber.tag === FiberTag.Host || fiber.tag === FiberTag.Text) return fiber.dom
1037
+ let c = fiber.child
1038
+ while (c) {
1039
+ const d = firstDomNode(c)
1040
+ if (d) return d
1041
+ c = c.sibling
1042
+ }
1043
+ return null
1044
+ }
1045
+
1046
+ // ---------------------------------------------------------------------------
1047
+ // Context read — exported for dispatcher.ts (useContext, use()). Delegates to
1048
+ // the installed capability so the Context feature can override with a walking
1049
+ // implementation that finds the nearest Provider fiber. When the feature is
1050
+ // stubbed, the default here returns ctx._currentValue — correct because no
1051
+ // Provider fibers exist in the tree (Provider element → Fragment via the
1052
+ // stub's type matcher).
1053
+ // ---------------------------------------------------------------------------
1054
+
1055
+ export function readContext(fiber: Fiber, ctx: any): any {
1056
+ return CAPABILITIES.readContext(fiber, ctx)
1057
+ }
1058
+
1059
+ function defaultReadContext(_fiber: Fiber, ctx: any): any {
1060
+ return ctx._currentValue
1061
+ }
1062
+
1063
+ // ---------------------------------------------------------------------------
1064
+ // Refs
1065
+ // ---------------------------------------------------------------------------
1066
+
1067
+ function attachRef(fiber: Fiber, value: any): void {
1068
+ const ref = fiber.ref ?? (fiber.pendingProps?.ref ?? null)
1069
+ if (!ref) return
1070
+ if (typeof ref === 'function') {
1071
+ const cleanup = ref(value)
1072
+ if (typeof cleanup === 'function') {
1073
+ fiber.cleanups ||= []
1074
+ fiber.cleanups.push(cleanup)
1075
+ } else {
1076
+ fiber.cleanups ||= []
1077
+ fiber.cleanups.push(() => ref(null))
1078
+ }
1079
+ } else {
1080
+ ref.current = value
1081
+ }
1082
+ }
1083
+
1084
+ function syncRefIfChanged(fiber: Fiber, value: any): void {
1085
+ const ref = fiber.ref ?? (fiber.pendingProps?.ref ?? null)
1086
+ if (!ref) return
1087
+ if (typeof ref === 'object' && ref.current !== value) ref.current = value
1088
+ }
1089
+
1090
+ function detachRef(ref: any): void {
1091
+ if (typeof ref === 'function') {
1092
+ try {
1093
+ ref(null)
1094
+ } catch {}
1095
+ } else if (ref && typeof ref === 'object') {
1096
+ ref.current = null
1097
+ }
1098
+ }
1099
+
1100
+ // ---------------------------------------------------------------------------
1101
+ // Effects
1102
+ // ---------------------------------------------------------------------------
1103
+
1104
+ const pendingEffects: Array<{ fiber: Fiber; effect: Effect }> = []
1105
+ const pendingLayoutEffects: Array<{ fiber: Fiber; effect: Effect }> = []
1106
+ const pendingLifecycles: Array<{ fiber: Fiber; fn: () => void }> = []
1107
+
1108
+ export function enqueueEffect(fiber: Fiber, effect: Effect): void {
1109
+ if (effect.tag === 'layout' || effect.tag === 'insertion') {
1110
+ pendingLayoutEffects.push({ fiber, effect })
1111
+ } else {
1112
+ pendingEffects.push({ fiber, effect })
1113
+ }
1114
+ }
1115
+
1116
+ export function scheduleLifecycle(fiber: Fiber, fn: () => void): void {
1117
+ pendingLifecycles.push({ fiber, fn })
1118
+ }
1119
+
1120
+ export function runEffects(root: FiberRoot): void {
1121
+ // Layout effects synchronously
1122
+ while (pendingLayoutEffects.length) {
1123
+ const { fiber, effect } = pendingLayoutEffects.shift()!
1124
+ runEffect(fiber, effect, root)
1125
+ }
1126
+ // Then lifecycles
1127
+ while (pendingLifecycles.length) {
1128
+ const { fn } = pendingLifecycles.shift()!
1129
+ try {
1130
+ fn()
1131
+ } catch (e) {
1132
+ if (root.onCaughtError) root.onCaughtError(e)
1133
+ }
1134
+ }
1135
+ // Passive effects on microtask
1136
+ if (pendingEffects.length) {
1137
+ const batch = pendingEffects.splice(0)
1138
+ queueMicrotask(() => {
1139
+ for (const { fiber, effect } of batch) runEffect(fiber, effect, root)
1140
+ })
1141
+ }
1142
+ }
1143
+
1144
+ function runEffect(fiber: Fiber, effect: Effect, root: FiberRoot): void {
1145
+ try {
1146
+ const cleanup = effect.create()
1147
+ effect.destroy = typeof cleanup === 'function' ? cleanup : undefined
1148
+ if (effect.destroy) {
1149
+ fiber.cleanups ||= []
1150
+ fiber.cleanups.push(effect.destroy)
1151
+ }
1152
+ } catch (e) {
1153
+ if (root.onCaughtError) root.onCaughtError(e)
1154
+ }
1155
+ }
1156
+
1157
+ // ---------------------------------------------------------------------------
1158
+ // Utilities
1159
+ // ---------------------------------------------------------------------------
1160
+
1161
+ function isEventProp(name: string): boolean {
1162
+ return (
1163
+ name.length > 2 &&
1164
+ name.charCodeAt(0) === 111 /* o */ &&
1165
+ name.charCodeAt(1) === 110 /* n */ &&
1166
+ name.charCodeAt(2) >= 65 /* 'A'-ish: any uppercase start (onClick, onChange, …) */
1167
+ )
1168
+ }
1169
+