@tanstack/redact 0.0.21 → 0.1.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 (254) 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 +72 -15
  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 +17 -8
  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 +7 -2
  152. package/dist/react/context.js +3 -11
  153. package/dist/react/context.js.map +3 -3
  154. package/dist/react/element.d.ts +6 -3
  155. package/dist/react/element.js +7 -11
  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 +150 -110
  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 +70 -13
  216. package/src/dom/features/context/stub.ts +10 -5
  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/context.ts +4 -11
  242. package/src/react/element.ts +11 -12
  243. package/src/react/hooks.ts +12 -3
  244. package/src/react/index.ts +18 -0
  245. package/src/react/shared-internals.ts +3 -3
  246. package/src/react/view-transition.ts +41 -0
  247. package/src/server/bootstrap-script.ts +36 -5
  248. package/src/server/dispatcher.ts +35 -13
  249. package/src/server/escape.ts +15 -12
  250. package/src/server/renderToString.ts +7 -5
  251. package/src/server/resource-hints.ts +165 -0
  252. package/src/server/stream.ts +154 -62
  253. package/src/server/walk.ts +79 -26
  254. package/src/vite/index.ts +30 -13
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
+ }
@@ -0,0 +1,166 @@
1
+ import { FiberTag, type Fiber } from '../../../core'
2
+ import { REACT_ACTIVITY_TYPE } from '../../../react/activity'
3
+ import { onCommitRollback, queueMutation } from '../../commit'
4
+ import { retainedEffects as effects, inSuspensePrimary, setLayoutDisconnected, walkRetained as walk } from '../../retained-effects'
5
+ import {
6
+ attachRef,
7
+ childrenToArray,
8
+ detachRef,
9
+ enqueueEffect,
10
+ findRoot,
11
+ flushFiberCommits,
12
+ getCurrentRoot,
13
+ handleCommitError,
14
+ installCapability,
15
+ reconcileChildren,
16
+ registerRenderer,
17
+ registerTypeMatcher,
18
+ scheduleLifecycle,
19
+ scheduleUpdate,
20
+ } from '../../reconcile'
21
+
22
+ // Retain effect descriptions, not subscriptions, while a subtree is hidden.
23
+ // Weak keys keep this bookkeeping out of fibers that do not use Activity.
24
+ const disconnected = new WeakSet<Fiber>()
25
+ const hiddenDoms = new WeakSet<Fiber>()
26
+
27
+ function setMembership(set: WeakSet<Fiber>, fiber: Fiber, present: boolean): boolean {
28
+ const previous = set.has(fiber)
29
+ if (previous === present) return previous
30
+ onCommitRollback(() => { if (previous) set.add(fiber); else set.delete(fiber) })
31
+ if (present) set.add(fiber)
32
+ else set.delete(fiber)
33
+ return previous
34
+ }
35
+
36
+ function isHidden(fiber: Fiber): boolean {
37
+ if (!(fiber.root ?? findRoot(fiber))?.a) return false
38
+ for (let parent: Fiber | null = fiber; parent; parent = parent.parent) {
39
+ if (parent.tag === FiberTag.Activity && parent.pp?.mode === 'hidden') return true
40
+ }
41
+ return false
42
+ }
43
+
44
+ function report(fiber: Fiber, fn: () => void): void {
45
+ try { fn() } catch (error) { handleCommitError(fiber, error) }
46
+ }
47
+
48
+ function disconnect(fiber: Fiber): void {
49
+ if (disconnected.has(fiber)) return
50
+ setMembership(disconnected, fiber, true)
51
+ queueMutation(() => {
52
+ // Insertion effects remain connected, just as they do in React.
53
+ const insertion = new Set<() => void>()
54
+ for (const [hook, effect] of effects.get(fiber) ?? []) {
55
+ if (effect.t === 2 && hook.c) insertion.add(hook.c)
56
+ }
57
+ if (fiber.cu) {
58
+ const keep = fiber.cu.filter(cleanup => insertion.has(cleanup))
59
+ for (const cleanup of fiber.cu) {
60
+ if (!insertion.has(cleanup)) report(fiber, cleanup)
61
+ }
62
+ fiber.cu = keep.length ? keep : null
63
+ }
64
+ for (const [hook, effect] of effects.get(fiber) ?? []) {
65
+ if (effect.t !== 2) hook.c = null
66
+ }
67
+ detachRef(fiber.cr ?? fiber.ref)
68
+ fiber.cr = null
69
+ fiber.rc = null
70
+ const instance = fiber.sn?._fiber === fiber ? fiber.sn : null
71
+ if (!fiber.ld && instance?.componentWillUnmount) {
72
+ const props = instance.props, state = instance.state, committed = fiber.ms?.c
73
+ if (committed) { instance.props = committed.props; instance.state = committed.state }
74
+ try { report(fiber, () => instance.componentWillUnmount()) }
75
+ finally { instance.props = props; instance.state = state }
76
+ }
77
+ })
78
+ }
79
+
80
+ function prepare(fiber: Fiber): void {
81
+ if (!isHidden(fiber) || disconnected.has(fiber)) return
82
+ if (fiber.mp == null) setMembership(disconnected, fiber, true)
83
+ else disconnect(fiber)
84
+ }
85
+
86
+ function needsHiddenDom(fiber: Fiber): boolean {
87
+ for (let parent = fiber.parent; parent; parent = parent.parent) {
88
+ if (parent.tag === FiberTag.Activity && parent.pp?.mode === 'hidden') return true
89
+ if (parent.tag === FiberTag.Host) return false
90
+ // A portal starts a new DOM tree, but keeps the parent's Activity state.
91
+ if (parent.tag === FiberTag.Portal) return isHidden(parent)
92
+ }
93
+ return false
94
+ }
95
+
96
+ function syncDom(fiber: Fiber): void {
97
+ if (!(fiber.root ?? findRoot(fiber))?.a) return
98
+ if (!fiber.dom || (fiber.tag !== FiberTag.Host && fiber.tag !== FiberTag.Text)) return
99
+ if (needsHiddenDom(fiber)) {
100
+ setMembership(hiddenDoms, fiber, true)
101
+ queueMutation(() => {
102
+ if (fiber.tag === FiberTag.Text) fiber.dom!.nodeValue = ''
103
+ else (fiber.dom as HTMLElement).style.setProperty('display', 'none', 'important')
104
+ })
105
+ } else if (setMembership(hiddenDoms, fiber, false)) {
106
+ // A Suspense fallback can remain active after its Activity is revealed.
107
+ // Its preserved primary DOM must stay hidden until that boundary resumes.
108
+ for (let parent = fiber.parent; parent; parent = parent.parent) {
109
+ if (parent.tag === FiberTag.Suspense && parent.ms?.d?.some(([node]: [Node, string]) => node === fiber.dom)) return
110
+ }
111
+ const props = fiber.pp
112
+ queueMutation(() => {
113
+ if (fiber.tag === FiberTag.Text) fiber.dom!.nodeValue = props
114
+ else {
115
+ const display = props?.style?.display
116
+ ;(fiber.dom as HTMLElement).style.display = display == null || typeof display === 'boolean' ? '' : ('' + display).trim()
117
+ }
118
+ })
119
+ }
120
+ }
121
+
122
+ function reconnect(fiber: Fiber): void {
123
+ syncDom(fiber)
124
+ if (isHidden(fiber) || inSuspensePrimary(fiber) || !setMembership(disconnected, fiber, false)) return
125
+ setLayoutDisconnected(fiber, false)
126
+ const instance = fiber.sn?._fiber === fiber ? fiber.sn : null
127
+ if (fiber.tag === FiberTag.Host || fiber.tag === FiberTag.Resource) attachRef(fiber, fiber.dom)
128
+ else if (instance || fiber.tag === FiberTag.Fragment) attachRef(fiber, fiber.sn)
129
+ for (const effect of effects.get(fiber)?.values() ?? []) {
130
+ if (effect.t !== 2) enqueueEffect(fiber, effect)
131
+ }
132
+ if (instance?.componentDidMount) {
133
+ scheduleLifecycle(fiber, () => instance.componentDidMount())
134
+ }
135
+ flushFiberCommits(fiber)
136
+ }
137
+
138
+ function finish(fiber: Fiber): void {
139
+ if (fiber.tag === FiberTag.Suspense && (fiber.root ?? findRoot(fiber))?.a) walk(fiber, reconnect, true)
140
+ else syncDom(fiber)
141
+ }
142
+
143
+ function renderActivity(fiber: Fiber, domParent: Node, anchor: Node | null): void {
144
+ const root = fiber.root ?? findRoot(fiber)
145
+ if (root) root.a = true
146
+ const props = fiber.pp ?? {}
147
+ // Hidden content is absent from server HTML. Mount it after hydration so it
148
+ // cannot consume a following visible sibling's hydration cursor.
149
+ if (getCurrentRoot()?.h && props.mode === 'hidden' && !fiber.mp) {
150
+ fiber.mp = props
151
+ queueMicrotask(() => scheduleUpdate(fiber))
152
+ return
153
+ }
154
+ if (isHidden(fiber)) walk(fiber, prepare)
155
+ else walk(fiber, syncDom)
156
+ reconcileChildren(fiber, childrenToArray(props.children), domParent, anchor)
157
+ walk(fiber, reconnect, true)
158
+ fiber.mp = props
159
+ }
160
+
161
+ installCapability('isActivityHidden', isHidden)
162
+ installCapability('activityIsDisconnected', fiber => disconnected.has(fiber))
163
+ installCapability('prepareActivityFiber', prepare)
164
+ installCapability('syncActivity', finish)
165
+ registerTypeMatcher(type => type === REACT_ACTIVITY_TYPE ? FiberTag.Activity : null)
166
+ registerRenderer(FiberTag.Activity, renderActivity)
@@ -0,0 +1 @@
1
+ export * from './full'
@@ -0,0 +1,14 @@
1
+ import { FiberTag, type Fiber } from '../../../core'
2
+ import { REACT_ACTIVITY_TYPE } from '../../../react/activity'
3
+ import { childrenToArray, reconcileChildren, registerRenderer, registerTypeMatcher } from '../../reconcile'
4
+
5
+ // Explicit small-preset downgrade: hidden children unmount instead of being
6
+ // retained. Showing them again creates fresh state, DOM, effects, and refs.
7
+ function renderActivity(fiber: Fiber, parent: Node, anchor: Node | null): void {
8
+ const props = fiber.pp ?? {}
9
+ reconcileChildren(fiber, props.mode === 'hidden' ? [] : childrenToArray(props.children), parent, anchor)
10
+ fiber.mp = props
11
+ }
12
+
13
+ registerTypeMatcher(type => type === REACT_ACTIVITY_TYPE ? FiberTag.Activity : null)
14
+ registerRenderer(FiberTag.Activity, renderActivity)
@@ -1,4 +1,5 @@
1
1
  import { FiberTag, type Fiber, type ReactNode } from '../../../core'
2
+ import { checkpointCommit, rewindCommit, queueBeforeMutation, queueMutation } from '../../commit'
2
3
  import {
3
4
  registerRenderer,
4
5
  reconcileChildren,
@@ -8,17 +9,30 @@ import {
8
9
  isThenable,
9
10
  handleSuspended,
10
11
  handleErrorInRender,
12
+ readContext,
13
+ attachRef,
14
+ syncRefIfChanged,
15
+ isActivityHidden,
16
+ layoutIsDisconnected,
17
+ RenderErrorCapture,
18
+ handleCommitError,
11
19
  } from '../../reconcile'
12
20
 
13
21
  function renderClass(fiber: Fiber, domParent: Node, anchor: Node | null): void {
22
+ fiber.cx?.clear()
14
23
  const Ctor = fiber.type as any
15
24
  let instance = fiber.sn
16
25
  const props = fiber.pp ?? {}
17
26
  const isNew = !instance
27
+ const previous = fiber.ms?.c
28
+ let didRender = true
29
+ let captured = fiber.ms?.error instanceof RenderErrorCapture ? fiber.ms.error as RenderErrorCapture : undefined
30
+ if (captured) delete fiber.ms.error
18
31
 
19
32
  // Class contextType: read the current value of the subscribed context so
20
33
  // `this.context` reflects the nearest Provider. Evaluated every render.
21
- const ctxValue = Ctor.contextType ? Ctor.contextType._currentValue : undefined
34
+ const ctxValue = Ctor.contextType ? readContext(fiber, Ctor.contextType) : undefined
35
+ const contextChanged = instance && !Object.is(instance.context, ctxValue)
22
36
 
23
37
  if (isNew) {
24
38
  instance = new Ctor(props, ctxValue)
@@ -47,39 +61,31 @@ function renderClass(fiber: Fiber, domParent: Node, anchor: Node | null): void {
47
61
  if (d) instance.state = { ...instance.state, ...d }
48
62
  }
49
63
  } else {
50
- const prevProps = instance.props
51
- const prevState = instance.state
52
64
  // Refresh context on every render — Providers higher up may have changed.
53
65
  instance.context = ctxValue
54
66
  if (Ctor.getDerivedStateFromProps) {
55
67
  const d = Ctor.getDerivedStateFromProps(props, instance.state)
56
68
  if (d) instance.state = { ...instance.state, ...d }
57
69
  }
58
- if (instance.shouldComponentUpdate) {
70
+ if (!captured && instance.shouldComponentUpdate && !contextChanged) {
59
71
  if (!instance.shouldComponentUpdate(props, instance.state, instance.context)) {
60
- instance.props = props
61
- fiber.mp = props
62
- // Still need to render children with previous output
63
- if (fiber.ms?.r) {
64
- reconcileChildren(fiber, childrenToArray(fiber.ms.r), domParent, anchor)
65
- }
66
- return
72
+ didRender = false
67
73
  }
68
74
  }
69
75
  instance.props = props
70
- // New snapshot must win over any stale one from a previous render —
71
- // otherwise componentDidUpdate keeps seeing the original props and can
72
- // ping-pong setState forever.
73
- fiber.ms = { ...(fiber.ms ?? {}), p: prevProps, s: prevState }
76
+ }
77
+ if (captured && Ctor.getDerivedStateFromError) {
78
+ const update = Ctor.getDerivedStateFromError(captured.error)
79
+ if (update != null) instance.state = { ...instance.state, ...update }
74
80
  }
75
81
 
76
82
  let rendered: ReactNode
77
83
  try {
78
- rendered = instance.render()
84
+ rendered = captured && !Ctor.getDerivedStateFromError ? null : didRender ? instance.render() : fiber.ms?.r
79
85
  } catch (e: any) {
80
86
  if (isThenable(e)) {
81
87
  handleSuspended(fiber, e)
82
- rendered = null
88
+ return
83
89
  } else {
84
90
  handleErrorInRender(fiber, e)
85
91
  return
@@ -87,24 +93,62 @@ function renderClass(fiber: Fiber, domParent: Node, anchor: Node | null): void {
87
93
  }
88
94
  fiber.ms = { ...(fiber.ms ?? {}), r: rendered }
89
95
 
90
- reconcileChildren(fiber, childrenToArray(rendered), domParent, anchor)
96
+ const canCatch = Ctor.getDerivedStateFromError || instance.componentDidCatch
97
+ if (canCatch) fiber.ms.b = Ctor
98
+ if (canCatch && fiber.root) fiber.root.eb = true
99
+ const checkpoint = canCatch ? checkpointCommit(fiber) : -1
100
+ try {
101
+ reconcileChildren(fiber, childrenToArray(rendered), domParent, anchor)
102
+ } catch (error) {
103
+ if (!(error instanceof RenderErrorCapture) || error.boundary !== fiber) throw error
104
+ rewindCommit(checkpoint)
105
+ captured = error
106
+ didRender = true
107
+ if (Ctor.getDerivedStateFromError) {
108
+ const update = Ctor.getDerivedStateFromError(error.error)
109
+ if (update != null) instance.state = { ...instance.state, ...update }
110
+ try { rendered = instance.render() } catch (fallbackError) {
111
+ handleErrorInRender(fiber, fallbackError)
112
+ return
113
+ }
114
+ } else rendered = null
115
+ fiber.ms = { ...(fiber.ms ?? {}), r: rendered }
116
+ reconcileChildren(fiber, childrenToArray(rendered), domParent, anchor)
117
+ }
91
118
  fiber.mp = props
119
+ const state = instance.state
120
+ queueMutation(() => { fiber.ms.c = { props, state } })
121
+
122
+ if (isNew) attachRef(fiber, instance)
123
+ else syncRefIfChanged(fiber, instance)
92
124
 
93
125
  // Schedule lifecycle
94
126
  if (isNew) {
95
- if (instance.componentDidMount || instance.componentWillUnmount) {
96
- scheduleLifecycle(fiber, () => {
97
- // An abandoned render never mounted, so it has no unmount lifecycle.
98
- if (instance.componentWillUnmount) {
99
- fiber.cu ||= []
100
- fiber.cu.push(() => instance.componentWillUnmount())
127
+ if (instance.componentDidMount) {
128
+ scheduleLifecycle(fiber, () => instance.componentDidMount())
129
+ }
130
+ } else if (previous && didRender) {
131
+ let snapshot: any
132
+ if (instance.getSnapshotBeforeUpdate && !layoutIsDisconnected(fiber)) {
133
+ queueBeforeMutation(() => {
134
+ if (!fiber.um && !fiber.pd && !isActivityHidden(fiber) && !layoutIsDisconnected(fiber)) {
135
+ try { snapshot = instance.getSnapshotBeforeUpdate(previous.props, previous.state) }
136
+ catch (error) { handleCommitError(fiber, error) }
101
137
  }
102
- instance.componentDidMount?.()
103
138
  })
104
139
  }
105
- } else if (instance.componentDidUpdate) {
106
- const { p, s } = fiber.ms ?? {}
107
- scheduleLifecycle(fiber, () => instance.componentDidUpdate(p, s))
140
+ if (instance.componentDidUpdate) {
141
+ scheduleLifecycle(fiber, () => instance.componentDidUpdate(previous.props, previous.state, snapshot))
142
+ }
143
+ }
144
+ if (captured) {
145
+ const error = captured.error
146
+ const info = { componentStack: captured.stack }
147
+ scheduleLifecycle(fiber, () => {
148
+ try { fiber.root?.ce?.(error, { ...info, errorBoundary: instance }) }
149
+ catch (callbackError) { setTimeout(() => { throw callbackError }) }
150
+ instance.componentDidCatch?.(error, info)
151
+ })
108
152
  }
109
153
  }
110
154