@tanstack/redact 0.0.21 → 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 -27
  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 -77
  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 +197 -52
  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 +689 -221
  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 -28
  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 -85
  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 +192 -55
  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 +695 -242
  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,5 +1,5 @@
1
1
  import { FiberTag, createFiber, type Fiber } from '../../../core'
2
- import { REACT_SUSPENSE_TYPE } from '../../../react'
2
+ import { REACT_SUSPENSE_TYPE, ReactSharedInternals, startTransition } from '../../../react'
3
3
  import {
4
4
  registerRenderer,
5
5
  registerTypeMatcher,
@@ -15,7 +15,19 @@ import {
15
15
  getCurrentRoot,
16
16
  withCurrentRoot,
17
17
  discardPendingWork,
18
+ findSuspenseBoundary,
19
+ RenderSuspenseCapture,
20
+ activityIsDisconnected,
21
+ isActivityHidden,
22
+ attachRef,
23
+ detachRef,
24
+ enqueueEffect,
25
+ flushFiberCommits,
26
+ scheduleLifecycle,
27
+ handleCommitError,
18
28
  } from '../../reconcile'
29
+ import { cleanupEffect } from '../../dispatcher'
30
+ import { retainedEffects, inSuspensePrimary, setLayoutDisconnected, walkRetained } from '../../retained-effects'
19
31
  import {
20
32
  HydrationCursor,
21
33
  setHydrationCursor,
@@ -24,15 +36,25 @@ import {
24
36
  tryConsumeBoundary,
25
37
  isHydrationBailout,
26
38
  } from '../hydration'
39
+ import { hasContextChanged, renderContextConsumers } from '../context'
40
+ import { checkpointCommit, rewindCommit, queueMutation, onCommitFailure } from '../../commit'
27
41
 
28
42
  let suspendHandler: ((t: Promise<any>) => void) | null = null
29
43
 
44
+ function scheduleRetry(fiber: Fiber): void {
45
+ const previous = ReactSharedInternals.T
46
+ ReactSharedInternals.T = null
47
+ try { startTransition(() => scheduleUpdate(fiber)) } finally { ReactSharedInternals.T = previous }
48
+ }
49
+
30
50
  function realHandleSuspended(fiber: Fiber, thenable: Promise<any>): void {
31
51
  if (suspendHandler) return suspendHandler(thenable)
52
+ const boundary = findSuspenseBoundary(fiber)
53
+ if (boundary) throw new RenderSuspenseCapture(boundary)
32
54
  // Fallback: schedule re-render when promise settles
33
55
  thenable.then(
34
- () => scheduleUpdate(fiber),
35
- () => scheduleUpdate(fiber),
56
+ () => scheduleRetry(fiber),
57
+ () => scheduleRetry(fiber),
36
58
  )
37
59
  }
38
60
 
@@ -46,14 +68,15 @@ function realHandleSuspended(fiber: Fiber, thenable: Promise<any>): void {
46
68
  // their original `display` so we can restore it) and the fallback as a
47
69
  // detached Fragment fiber in `state.f` (deliberately kept OUT of
48
70
  // `fiber.child` so reconciles against `props.children` don't trip on it).
49
- // First-mount suspensions have no committed DOM worth preserving, so they
50
- // keep the original unmount-and-render-fallback behavior.
71
+ // First-mount suspensions discard the failed primary but retain a separate
72
+ // fallback fiber so unsuccessful retries preserve its state and DOM.
51
73
  interface SuspenseState {
52
74
  p?: Promise<any> | null
53
75
  b?: Comment
54
76
  e?: Comment
55
77
  r?: any
56
78
  a?: boolean
79
+ u?: boolean
57
80
  // Re-suspend preservation:
58
81
  d?: Array<[HTMLElement, string]> | null
59
82
  f?: Fiber | null
@@ -61,11 +84,12 @@ interface SuspenseState {
61
84
 
62
85
  function renderSuspense(fiber: Fiber, domParent: Node, anchor: Node | null): void {
63
86
  const props = fiber.pp ?? {}
64
- const state = (fiber.ms ??= {}) as SuspenseState
87
+ let state = (fiber.ms ??= {}) as SuspenseState
65
88
 
66
89
  // Streaming hydration: if the next DOM node is a server-emitted boundary
67
90
  // marker, route through the boundary-aware hydration path.
68
91
  const root = getCurrentRoot()
92
+ if (root) root.sp = true
69
93
  if (root?.h && !state.b) {
70
94
  const boundary = tryConsumeBoundary(fiber.parent!)
71
95
  if (boundary) {
@@ -85,14 +109,11 @@ function renderSuspense(fiber: Fiber, domParent: Node, anchor: Node | null): voi
85
109
  return
86
110
  }
87
111
 
88
- // We were already in the suspended-with-preserved-primary state. Don't
89
- // re-attempt primary children (would re-throw and churn the tree). Just
90
- // refresh the fallback in case its JSX changed, and wait for the pending
91
- // promise to fire scheduleUpdate.
92
- if (state.p) {
112
+ // New primary children or context can unblock a boundary before its old
113
+ // promise settles. A fallback-only update can keep the pending primary.
114
+ if (state.p && !state.u && props.children === fiber.mp?.children && !hasContextChanged(fiber, false)) {
93
115
  if (state.f) {
94
- state.f.pp = { children: props.fallback }
95
- renderFiber(state.f, domParent, anchor)
116
+ renderFallback(state.f, props.fallback, domParent, anchor)
96
117
  } else {
97
118
  // Initial-mount suspended path: no committed primary to preserve.
98
119
  reconcileChildren(fiber, childrenToArray(props.fallback), domParent, anchor)
@@ -105,8 +126,11 @@ function renderSuspense(fiber: Fiber, domParent: Node, anchor: Node | null): voi
105
126
  // attempting the new render. If the new attempt suspends and we did have a
106
127
  // committed primary, we keep it (hidden) rather than destroying it.
107
128
  const hadCommittedPrimary = fiber.mp && fiber.child
129
+ state.u = false
130
+ const checkpoint = checkpointCommit(fiber)
108
131
 
109
132
  const prevHandler = suspendHandler
133
+ fiber.cx?.clear()
110
134
  let pendingThenable: any
111
135
  suspendHandler = (thenable) => {
112
136
  pendingThenable = thenable
@@ -118,63 +142,142 @@ function renderSuspense(fiber: Fiber, domParent: Node, anchor: Node | null): voi
118
142
  }
119
143
 
120
144
  if (pendingThenable) {
145
+ rewindCommit(checkpoint)
146
+ state = (fiber.ms ??= {}) as SuspenseState
147
+ const previousThenable = state.p
121
148
  state.p = pendingThenable
122
149
  const onSettle = () => {
123
- state.p = null
124
- scheduleUpdate(fiber)
150
+ const current = fiber.ms as SuspenseState | null
151
+ if (!current || current.p !== pendingThenable) return
152
+ current.p = null
153
+ scheduleRetry(fiber)
125
154
  }
126
- pendingThenable.then(onSettle, onSettle)
155
+ if (previousThenable !== pendingThenable) pendingThenable.then(onSettle, onSettle)
127
156
 
128
- if (hadCommittedPrimary) {
157
+ // Rewind restored the committed tree, including its existing hidden state.
158
+ // Only the first hide needs update-target, lifecycle and DOM work.
159
+ if (hadCommittedPrimary && !state.d) {
129
160
  // Hide the primary subtree's root host doms so the fallback is the only
130
161
  // thing visible, but the underlying nodes (and their scroll/state/focus)
131
162
  // survive. Save original `display` for the resume path.
132
163
  const hidden: Array<[HTMLElement, string]> = []
133
164
  let c: Fiber | null = hadCommittedPrimary
165
+ while (c) {
166
+ setUpdateBoundary(c, fiber)
167
+ walkRetained(c, disconnectLayout)
168
+ c = c.sibling
169
+ }
170
+ c = hadCommittedPrimary
134
171
  while (c) {
135
172
  hideRootHostDoms(c, hidden)
136
173
  c = c.sibling
137
174
  }
138
175
  state.d = hidden
139
176
 
140
- // Mount fallback in a detached Fragment fiber. Kept off `fiber.child`
141
- // so reconciles of primary don't see it as a stale match candidate.
142
- if (!state.f) {
143
- state.f = createFiber(FiberTag.Fragment, null, null)
144
- state.f.parent = fiber
145
- }
146
- state.f.pp = { children: props.fallback }
147
- renderFiber(state.f, domParent, anchor)
148
- } else {
177
+ } else if (!hadCommittedPrimary) {
149
178
  // First-mount suspension — nothing to preserve.
150
- unmountAllChildren(fiber)
151
- reconcileChildren(fiber, childrenToArray(props.fallback), domParent, anchor)
179
+ unmountAllChildren(fiber, domParent)
180
+ }
181
+ // Keep fallback identity across unsuccessful retries of an initial mount.
182
+ if (!state.f) {
183
+ state.f = createFiber(FiberTag.Fragment, null, null)
184
+ state.f.parent = fiber
152
185
  }
186
+ renderFallback(state.f, props.fallback, domParent, anchor)
153
187
  } else {
188
+ state.p = null
189
+ fiber.cx?.clear()
190
+ const wasHidden = !!state.d
154
191
  // Render succeeded. Clean up any preserved-suspend state from a prior
155
192
  // suspension cycle: unhide primary, unmount the orphan fallback fiber.
156
193
  if (state.d) {
157
- for (const [el, origDisplay] of state.d) {
158
- el.style.display = origDisplay
159
- }
194
+ for (let child = fiber.child; child; child = child.sibling) setUpdateBoundary(child, fiber.su)
195
+ const hidden = state.d
196
+ queueMutation(() => {
197
+ for (const [el, origDisplay] of hidden) el.style.display = origDisplay
198
+ })
160
199
  state.d = null
161
200
  }
162
201
  if (state.f) {
163
- unmountFiber(state.f)
202
+ unmountFiber(state.f, domParent)
164
203
  state.f = null
165
204
  }
205
+ if (wasHidden) for (let child = fiber.child; child; child = child.sibling) walkRetained(child, reconnectLayout, true)
166
206
  }
167
207
  fiber.mp = props
168
208
  }
169
209
 
210
+ function renderFallback(fiber: Fiber, children: any, domParent: Node, anchor: Node | null): void {
211
+ // Local updates remain independently queued, unless an outer hidden primary
212
+ // owns them. Context must still cross an otherwise unchanged fallback.
213
+ if (fiber.mp && fiber.mp.children === children && !fiber.su) renderContextConsumers(fiber)
214
+ else {
215
+ fiber.pp = { children }
216
+ renderFiber(fiber, domParent, anchor)
217
+ }
218
+ }
219
+
220
+ function disconnectLayout(fiber: Fiber): void {
221
+ if (setLayoutDisconnected(fiber, true) || activityIsDisconnected(fiber)) return
222
+ queueMutation(() => {
223
+ for (const [hook, effect] of retainedEffects.get(fiber) ?? []) {
224
+ if (effect.t === 1 && !effect.s) cleanupEffect(hook, fiber)
225
+ }
226
+ const cleanup = fiber.rc
227
+ fiber.rc = null
228
+ if (cleanup) {
229
+ const index = fiber.cu?.indexOf(cleanup) ?? -1
230
+ if (index >= 0) fiber.cu!.splice(index, 1)
231
+ try { cleanup() } catch (error) { handleCommitError(fiber, error) }
232
+ }
233
+ detachRef(fiber.cr ?? fiber.ref, fiber)
234
+ fiber.cr = null
235
+ const instance = fiber.sn?._fiber === fiber ? fiber.sn : null
236
+ if (instance?.componentWillUnmount) {
237
+ const props = instance.props, state = instance.state, committed = fiber.ms?.c
238
+ if (committed) { instance.props = committed.props; instance.state = committed.state }
239
+ try { instance.componentWillUnmount() } catch (error) { handleCommitError(fiber, error) }
240
+ finally { instance.props = props; instance.state = state }
241
+ }
242
+ })
243
+ }
244
+
245
+ function reconnectLayout(fiber: Fiber): void {
246
+ if (!fiber.ld || isActivityHidden(fiber) || activityIsDisconnected(fiber)) return
247
+ if (inSuspensePrimary(fiber)) return
248
+ setLayoutDisconnected(fiber, false)
249
+ const instance = fiber.sn?._fiber === fiber ? fiber.sn : null
250
+ if (fiber.tag === FiberTag.Host || fiber.tag === FiberTag.Resource) attachRef(fiber, fiber.dom)
251
+ else if (instance || fiber.tag === FiberTag.Fragment) attachRef(fiber, fiber.sn)
252
+ for (const effect of retainedEffects.get(fiber)?.values() ?? []) {
253
+ if (effect.t === 1 && !effect.s) enqueueEffect(fiber, effect)
254
+ }
255
+ if (instance?.componentDidMount) scheduleLifecycle(fiber, () => instance.componentDidMount())
256
+ flushFiberCommits(fiber)
257
+ }
258
+
259
+ function setUpdateBoundary(fiber: Fiber, boundary: Fiber | null | undefined): void {
260
+ fiber.su = boundary ?? null
261
+ const primary = fiber.tag === FiberTag.Suspense && fiber.ms?.p ? fiber : boundary
262
+ for (let child = fiber.child; child; child = child.sibling) setUpdateBoundary(child, primary)
263
+ if (fiber.tag === FiberTag.Suspense && fiber.ms?.f) setUpdateBoundary(fiber.ms.f, boundary)
264
+ }
265
+
170
266
  // Walk a fiber subtree collecting host/text DOM nodes that sit at the root
171
267
  // of the subtree (do not descend through their children — display:none on
172
268
  // the root hides the whole element). Used by the hide-on-suspend path.
173
269
  function hideRootHostDoms(fiber: Fiber, out: Array<[HTMLElement, string]>): void {
174
270
  if (fiber.tag === FiberTag.Host) {
175
271
  const el = fiber.dom as HTMLElement
176
- out.push([el, el.style.display])
177
- el.style.display = 'none'
272
+ const existing = out.find(([node]) => node === el)
273
+ const entry: [HTMLElement, string] = existing ?? [el, el.style.display]
274
+ if (!existing) out.push(entry)
275
+ const display = fiber.pp?.style?.display
276
+ queueMutation(() => {
277
+ // Earlier Activity or host updates in this commit can change display.
278
+ if (!existing || el.style.display !== 'none' || display === 'none') entry[1] = el.style.display
279
+ el.style.display = 'none'
280
+ })
178
281
  return
179
282
  }
180
283
  if (fiber.tag === FiberTag.Portal) return
@@ -188,7 +291,7 @@ function hideRootHostDoms(fiber: Fiber, out: Array<[HTMLElement, string]>): void
188
291
  function hydrateSuspenseBoundary(
189
292
  fiber: Fiber,
190
293
  props: any,
191
- boundary: [0 | 1, number, Comment, Comment],
294
+ boundary: [0 | 1 | 2 | 3, number, Comment, Comment],
192
295
  domParent: Node,
193
296
  anchor: Node | null,
194
297
  ): void {
@@ -200,6 +303,13 @@ function hydrateSuspenseBoundary(
200
303
  r: props.children,
201
304
  }
202
305
 
306
+ if (pendingBoundary >= 2) {
307
+ if (pendingBoundary === 3) reportServerFailure(fiber)
308
+ recoverBoundaryHydration(fiber, props.children, startMark.parentNode!, startMark, endMark, true)
309
+ fiber.mp = props
310
+ return
311
+ }
312
+
203
313
  if (!pendingBoundary) {
204
314
  // Real DOM is inline between startMark and endMark. Hydrate into it.
205
315
  const parent = startMark.parentNode!
@@ -209,8 +319,10 @@ function hydrateSuspenseBoundary(
209
319
  advanceCursorPast(fiber.parent!, endMark)
210
320
  } catch (e) {
211
321
  if (!isHydrationBailout(e)) {
212
- clearBoundaryRange(startMark, endMark)
213
- unmountAllChildren(fiber)
322
+ onCommitFailure(() => {
323
+ clearBoundaryRange(startMark, endMark)
324
+ unmountAllChildren(fiber, parent)
325
+ })
214
326
  throw e
215
327
  }
216
328
  recoverBoundaryHydration(fiber, props.children, parent, startMark, endMark)
@@ -253,19 +365,25 @@ function recoverBoundaryHydration(
253
365
  parent: Node,
254
366
  startMark: Comment,
255
367
  endMark: Comment,
368
+ retryBoundary = false,
256
369
  ): void {
257
370
  const root = findRoot(fiber)!
258
371
  const prevHydrating = root.h
259
- discardPendingWork(root)
372
+ if (!retryBoundary) discardPendingWork(root)
260
373
  clearBoundaryRange(startMark, endMark)
261
- unmountAllChildren(fiber)
374
+ unmountAllChildren(fiber, parent)
262
375
  root.h = false
263
376
  try {
264
- reconcileChildren(fiber, childrenToArray(children), parent, endMark)
377
+ if (retryBoundary) {
378
+ fiber.ms = {}
379
+ renderSuspense(fiber, parent, endMark)
380
+ } else reconcileChildren(fiber, childrenToArray(children), parent, endMark)
265
381
  advanceCursorPast(fiber.parent!, endMark)
266
382
  } catch (clientError) {
267
- clearBoundaryRange(startMark, endMark)
268
- unmountAllChildren(fiber)
383
+ onCommitFailure(() => {
384
+ clearBoundaryRange(startMark, endMark)
385
+ unmountAllChildren(fiber, parent)
386
+ })
269
387
  throw clientError
270
388
  } finally {
271
389
  root.h = prevHydrating
@@ -276,14 +394,16 @@ function recoverFallbackHydration(fiber: Fiber, fallback: any, parent: HTMLEleme
276
394
  const root = findRoot(fiber)!
277
395
  const prevHydrating = root.h
278
396
  discardPendingWork(root)
279
- parent.textContent = ''
280
- unmountAllChildren(fiber)
397
+ queueMutation(() => { parent.textContent = '' })
398
+ unmountAllChildren(fiber, parent)
281
399
  root.h = false
282
400
  try {
283
401
  reconcileChildren(fiber, childrenToArray(fallback), parent, null)
284
402
  } catch (clientError) {
285
- parent.textContent = ''
286
- unmountAllChildren(fiber)
403
+ onCommitFailure(() => {
404
+ parent.textContent = ''
405
+ unmountAllChildren(fiber, parent)
406
+ })
287
407
  throw clientError
288
408
  } finally {
289
409
  root.h = prevHydrating
@@ -302,8 +422,14 @@ function rehydrateBoundary(fiber: Fiber): void {
302
422
  // (or at least its container); unmounting here cleans up fibers + fx.
303
423
  withCurrentRoot(root, () => {
304
424
  const prevHydrating = root.h
425
+ if (state.b.data.startsWith('$!') || state.b.data.startsWith('$E')) {
426
+ if (state.b.data.startsWith('$E')) reportServerFailure(fiber)
427
+ recoverBoundaryHydration(fiber, state.r, parent, state.b, state.e, true)
428
+ runEffects(root)
429
+ return
430
+ }
305
431
  try {
306
- unmountAllChildren(fiber)
432
+ unmountAllChildren(fiber, parent)
307
433
 
308
434
  // Re-hydrate with real children against the now-real DOM range.
309
435
  root.h = true
@@ -311,8 +437,10 @@ function rehydrateBoundary(fiber: Fiber): void {
311
437
  reconcileChildren(fiber, childrenToArray(state.r), parent, null)
312
438
  } catch (e) {
313
439
  if (!isHydrationBailout(e)) {
314
- clearBoundaryRange(state.b, state.e)
315
- unmountAllChildren(fiber)
440
+ onCommitFailure(() => {
441
+ clearBoundaryRange(state.b, state.e)
442
+ unmountAllChildren(fiber, parent)
443
+ })
316
444
  throw e
317
445
  }
318
446
 
@@ -325,13 +453,22 @@ function rehydrateBoundary(fiber: Fiber): void {
325
453
  })
326
454
  }
327
455
 
456
+ function reportServerFailure(fiber: Fiber): void {
457
+ const error = new Error('Switched to client rendering because the server render did not complete.')
458
+ const handler = findRoot(fiber)?.re
459
+ if (handler) handler(error, { componentStack: null })
460
+ else console.error(error)
461
+ }
462
+
328
463
  function clearBoundaryRange(startMark: Comment, endMark: Comment): void {
329
- let node = startMark.nextSibling
330
- while (node && node !== endMark) {
331
- const next = node.nextSibling
332
- node.parentNode?.removeChild(node)
333
- node = next
334
- }
464
+ queueMutation(() => {
465
+ let node = startMark.nextSibling
466
+ while (node && node !== endMark) {
467
+ const next = node.nextSibling
468
+ node.parentNode?.removeChild(node)
469
+ node = next
470
+ }
471
+ })
335
472
  }
336
473
 
337
474
  registerTypeMatcher((type) => (type === REACT_SUSPENSE_TYPE ? FiberTag.Suspense : null))