@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
@@ -0,0 +1,406 @@
1
+ import { FiberTag, type Fiber, type FiberRoot } from '../../../core'
2
+ import {
3
+ REACT_VIEW_TRANSITION_TYPE,
4
+ type ViewTransitionClass,
5
+ type ViewTransitionInstance,
6
+ type ViewTransitionProps,
7
+ type ViewTransitionPseudoElement,
8
+ } from '../../../react/view-transition'
9
+ import { transitionResources, type TransitionImages } from './resources'
10
+
11
+ type Kind = 'enter' | 'exit' | 'update' | 'share'
12
+ type Host = HTMLElement | SVGElement
13
+ type Rect = [number, number, number, number]
14
+ interface Boundary {
15
+ fiber: Fiber
16
+ props: ViewTransitionProps
17
+ hosts: Host[]
18
+ ancestors: Fiber[]
19
+ parent: Fiber | null
20
+ name: string
21
+ explicit: boolean
22
+ rects: Rect[]
23
+ content: string
24
+ inViewport: boolean
25
+ }
26
+ interface Snapshot {
27
+ boundaries: Map<Fiber, Boundary>
28
+ visible: Set<Fiber>
29
+ images: TransitionImages
30
+ }
31
+ interface Capture {
32
+ old?: Boundary | undefined
33
+ next?: Boundary | undefined
34
+ kind: Kind
35
+ name: string
36
+ oldHosts: Host[]
37
+ newEnabled: boolean
38
+ }
39
+ interface Event {
40
+ boundary: Boundary
41
+ kind: Kind
42
+ name: string
43
+ }
44
+
45
+ const names = new WeakMap<Fiber, string>()
46
+ const styleProperties = ['view-transition-name', 'view-transition-class']
47
+ let nextName = 0
48
+
49
+ function byType(value: ViewTransitionClass | undefined, types: string[]): string | undefined {
50
+ if (typeof value === 'string') return value
51
+ if (!value) return undefined
52
+ let result: string | undefined
53
+ for (const type of types) {
54
+ const match = value[type]
55
+ if (match === 'none') return match
56
+ if (match != null) result = result == null ? match : result + ' ' + match
57
+ }
58
+ return result ?? value.default
59
+ }
60
+
61
+ function className(props: ViewTransitionProps, kind: Kind, types: string[]): string | undefined {
62
+ const value = byType(props[kind], types) ?? byType(props.default, types)
63
+ return value === 'auto' ? undefined : value
64
+ }
65
+
66
+ function visibleChildren(fiber: Fiber, visit: (child: Fiber) => void): void {
67
+ if (fiber.tag === FiberTag.Activity && fiber.pp?.mode === 'hidden') return
68
+ if (fiber.tag === FiberTag.Suspense && fiber.ms?.f) {
69
+ visit(fiber.ms.f)
70
+ return
71
+ }
72
+ for (let child = fiber.child; child; child = child.sibling) visit(child)
73
+ }
74
+
75
+ // Ignore nested boundary content: its mutations belong to that boundary.
76
+ function contents(node: Node, nested: Set<Node>, first = false): string {
77
+ if (!first && nested.has(node)) return '|'
78
+ if (node.nodeType === 3) return JSON.stringify(node.nodeValue)
79
+ if (node.nodeType !== 1) return ''
80
+ const element = node as Host
81
+ let result = '<' + element.nodeName
82
+ for (const attribute of element.attributes) {
83
+ if (attribute.name !== 'style') result += ' ' + attribute.name + '=' + JSON.stringify(attribute.value)
84
+ }
85
+ for (let i = 0; i < element.style.length; i++) {
86
+ const property = element.style[i]!
87
+ if (property !== 'view-transition-name' && property !== 'view-transition-class') {
88
+ result += ';' + property + ':' + element.style.getPropertyValue(property) + '!' + element.style.getPropertyPriority(property)
89
+ }
90
+ }
91
+ if ('value' in element) result += '=' + (element as HTMLInputElement).value
92
+ if ('checked' in element) result += ':' + (element as HTMLInputElement).checked
93
+ result += '>'
94
+ for (let child = node.firstChild; child; child = child.nextSibling) result += contents(child, nested)
95
+ return result + '</' + element.nodeName + '>'
96
+ }
97
+
98
+ function snapshot(roots: FiberRoot[], doc: Document, types: string[], connected = true): Snapshot {
99
+ const boundaries = new Map<Fiber, Boundary>()
100
+ const visible = new Set<Fiber>()
101
+ const images: TransitionImages = new Map()
102
+ const allHosts = new Set<Node>()
103
+ const empty: Boundary[] = []
104
+ const ancestors: Fiber[] = []
105
+ function walk(fiber: Fiber, parent: Fiber | null, pending: Boundary[]): void {
106
+ if (fiber.um) return
107
+ if (fiber.type === REACT_VIEW_TRANSITION_TYPE) {
108
+ const props: ViewTransitionProps = fiber.pp ?? {}
109
+ const explicitName = byType(props.name, types)
110
+ let name = explicitName
111
+ if (name == null || name === 'auto') {
112
+ name = names.get(fiber)
113
+ if (!name) names.set(fiber, name = '_redact_t_' + (++nextName).toString(32))
114
+ }
115
+ const boundary: Boundary = {
116
+ fiber, props, hosts: [], ancestors: ancestors.slice(), parent, name,
117
+ explicit: explicitName != null && explicitName !== 'auto', rects: [], content: '', inViewport: false,
118
+ }
119
+ boundaries.set(fiber, boundary)
120
+ pending = pending.concat(boundary)
121
+ parent = fiber
122
+ }
123
+ if (fiber.tag === FiberTag.Host) {
124
+ visible.add(fiber)
125
+ const dom = fiber.dom as Host | null
126
+ if (parent && fiber.type === 'img' && dom?.ownerDocument === doc) {
127
+ images.set(dom as HTMLImageElement, fiber.pp)
128
+ }
129
+ if (pending.length && dom?.ownerDocument === doc && (!connected || dom.isConnected) && dom.style) {
130
+ for (const boundary of pending) boundary.hosts.push(dom)
131
+ allHosts.add(dom)
132
+ }
133
+ pending = empty
134
+ ancestors.push(fiber)
135
+ }
136
+ visibleChildren(fiber, child => walk(child, parent, pending))
137
+ if (fiber.tag === FiberTag.Host) ancestors.pop()
138
+ }
139
+ for (const root of roots) walk(root.r, null, empty)
140
+ const view = doc.defaultView
141
+ if (connected) for (const boundary of boundaries.values()) {
142
+ boundary.rects = boundary.hosts.map(host => {
143
+ const rect = host.getBoundingClientRect()
144
+ return [rect.x, rect.y, rect.width, rect.height]
145
+ })
146
+ boundary.inViewport = boundary.rects.some(([x, y, width, height]) => width > 0 && height > 0 && x + width > 0 && y + height > 0 && x < (view?.innerWidth ?? Infinity) && y < (view?.innerHeight ?? Infinity))
147
+ boundary.content = boundary.hosts.map(host => contents(host, allHosts, true)).join('')
148
+ }
149
+ return { boundaries, visible, images }
150
+ }
151
+
152
+ function pseudo(doc: Document, kind: string, name: string): ViewTransitionPseudoElement {
153
+ const scope = doc.documentElement
154
+ const selector = '::view-transition-' + kind + '(' + nativeName(name) + ')'
155
+ return {
156
+ animate(keyframes, options) {
157
+ return scope.animate(keyframes, { ...(typeof options === 'number' ? { duration: options } : options), pseudoElement: selector })
158
+ },
159
+ getAnimations() {
160
+ return scope.getAnimations({ subtree: true }).filter(animation => {
161
+ const effect = animation.effect as KeyframeEffect | null
162
+ return effect?.target === scope && effect.pseudoElement === selector
163
+ })
164
+ },
165
+ getComputedStyle() { return doc.defaultView!.getComputedStyle(scope, selector) },
166
+ }
167
+ }
168
+
169
+ function nativeName(name: string): string {
170
+ return CSS.escape(name) === name ? name : CSS.escape('r-' + btoa(name).replace(/=/g, ''))
171
+ }
172
+
173
+ function instance(doc: Document, name: string): ViewTransitionInstance {
174
+ return {
175
+ name,
176
+ group: pseudo(doc, 'group', name),
177
+ imagePair: pseudo(doc, 'image-pair', name),
178
+ old: pseudo(doc, 'old', name),
179
+ new: pseudo(doc, 'new', name),
180
+ }
181
+ }
182
+
183
+ function topLevel(boundary: Boundary, other: Snapshot, side: Snapshot): boolean {
184
+ // An inserted/deleted host wrapper owns its subtree's enter/exit animation.
185
+ if (boundary.ancestors.some(fiber => !other.visible.has(fiber))) return false
186
+ for (let parent = boundary.parent; parent; parent = side.boundaries.get(parent)?.parent ?? null) {
187
+ if (!other.boundaries.has(parent)) return false
188
+ }
189
+ return true
190
+ }
191
+
192
+ function changed(before: Boundary, after: Boundary): boolean {
193
+ return before.content !== after.content || before.hosts.length !== after.hosts.length ||
194
+ before.hosts.some((host, i) => host !== after.hosts[i] || before.rects[i]!.some((value, axis) => value !== after.rects[i]![axis]))
195
+ }
196
+
197
+ export interface PreparedViewTransition {
198
+ document: Document
199
+ exclude(root: FiberRoot): void
200
+ prepare(): Promise<void> | undefined
201
+ waitForResources(fontsWereLoaded: boolean): Promise<void> | undefined
202
+ beforeCommit(): boolean
203
+ afterCommit(): void
204
+ ready(): void
205
+ restore(): void
206
+ finish(): void
207
+ cancel(): void
208
+ }
209
+
210
+ export function prepareViewTransition(rootIterable: Iterable<FiberRoot>, types: string[]): PreparedViewTransition | null {
211
+ const roots = Array.from(rootIterable)
212
+ const container = roots[0]?.c
213
+ const doc = container?.nodeType === 9 ? container as unknown as Document : container?.ownerDocument
214
+ if (!doc?.documentElement) return null
215
+ const before = snapshot(roots, doc, types)
216
+ const saved = new Map<Host, string[]>()
217
+ const suppressed = new Set<string>()
218
+ const events: Event[] = []
219
+ const cleanups: (() => void)[] = []
220
+ const captures: Capture[] = []
221
+ let done = false
222
+ let after: Snapshot
223
+ let resources: ReturnType<typeof transitionResources> | undefined
224
+
225
+ function apply(host: Host, name: string, classes?: string): void {
226
+ let previous = saved.get(host)
227
+ if (!previous) saved.set(host, previous = [])
228
+ for (let index = 0; index < 2; index++) {
229
+ const property = styleProperties[index]!
230
+ const offset = index * 3
231
+ const current = host.style.getPropertyValue(property)
232
+ // A committed style prop may replace either temporary value.
233
+ if (current !== previous[offset + 2]) {
234
+ previous[offset] = current
235
+ previous[offset + 1] = host.style.getPropertyPriority(property)
236
+ }
237
+ const value = index ? classes === 'none' ? '' : classes ?? previous[offset]! : nativeName(name)
238
+ host.style.setProperty(property, previous[offset + 2] = value, 'important')
239
+ }
240
+ }
241
+
242
+ function applyHosts(hosts: Host[], name: string, props: ViewTransitionProps, kind: Kind): void {
243
+ const classes = className(props, kind, types)
244
+ hosts.forEach((host, index) => apply(host, index ? name + '_' + index : name, classes))
245
+ }
246
+
247
+ function suppress(hosts: Host[], name: string): void {
248
+ hosts.forEach((_, index) => suppressed.add(index ? name + '_' + index : name))
249
+ }
250
+
251
+ function ownership(side: 'old' | 'next'): Map<Host, Capture> {
252
+ const owners = new Map<Host, Capture>()
253
+ for (const capture of captures) {
254
+ if (side === 'next' && !capture.newEnabled) continue
255
+ for (const host of capture[side]?.hosts ?? []) owners.set(host, capture)
256
+ }
257
+ return owners
258
+ }
259
+
260
+ function capture(kind: Kind, old?: Boundary, next?: Boundary, enabled = true): void {
261
+ captures.push({ old, next, kind, name: (old ?? next)!.name, oldHosts: [], newEnabled: !!next && enabled })
262
+ }
263
+
264
+ function restore(): void {
265
+ for (const [host, previous] of saved) {
266
+ for (let index = 0; index < 2; index++) {
267
+ const property = styleProperties[index]!
268
+ const offset = index * 3
269
+ if (host.style.getPropertyValue(property) === previous[offset + 2]) {
270
+ host.style.setProperty(property, previous[offset]!, previous[offset + 1])
271
+ }
272
+ }
273
+ }
274
+ saved.clear()
275
+ }
276
+
277
+ function finish(): void {
278
+ if (done) return
279
+ done = true
280
+ resources?.cancel()
281
+ restore()
282
+ let error: unknown
283
+ let failed = false
284
+ for (const cleanup of cleanups.splice(0)) {
285
+ try { cleanup() } catch (caught) { if (!failed) { error = caught; failed = true } }
286
+ }
287
+ if (failed) throw error
288
+ }
289
+
290
+ return {
291
+ document: doc,
292
+ exclude(root) {
293
+ const index = roots.indexOf(root)
294
+ if (index >= 0) roots.splice(index, 1)
295
+ for (const [fiber] of before.boundaries) if (fiber.root === root) before.boundaries.delete(fiber)
296
+ },
297
+ prepare() {
298
+ after = snapshot(roots, doc, types, false)
299
+ resources = transitionResources(doc, before.images, after.images)
300
+ return resources.before()
301
+ },
302
+ waitForResources(fontsWereLoaded) { if (!done) return resources?.after(fontsWereLoaded) },
303
+ beforeCommit() {
304
+ if (done) return false
305
+ const entering = new Map<string, Boundary>()
306
+ const paired = new Set<Fiber>()
307
+ for (const boundary of after.boundaries.values()) {
308
+ if (!before.boundaries.has(boundary.fiber) && boundary.explicit) entering.set(boundary.name, boundary)
309
+ }
310
+ for (const old of before.boundaries.values()) {
311
+ const current = after.boundaries.get(old.fiber)
312
+ if (current) {
313
+ if (className(current.props, 'update', types) !== 'none') capture('update', old, current)
314
+ } else {
315
+ const shared = old.explicit && old.inViewport ? entering.get(old.name) : undefined
316
+ if (shared) {
317
+ entering.delete(old.name)
318
+ if (className(old.props, 'share', types) !== 'none') {
319
+ paired.add(shared.fiber)
320
+ capture('share', old, shared, className(shared.props, 'share', types) !== 'none')
321
+ }
322
+ } else if (topLevel(old, after, before) && old.inViewport && className(old.props, 'exit', types) !== 'none') {
323
+ capture('exit', old)
324
+ }
325
+ }
326
+ }
327
+ for (const boundary of after.boundaries.values()) {
328
+ if (before.boundaries.has(boundary.fiber) || paired.has(boundary.fiber)) continue
329
+ if (topLevel(boundary, before, after) && className(boundary.props, 'enter', types) !== 'none') {
330
+ capture('enter', undefined, boundary)
331
+ }
332
+ }
333
+ // A directly nested boundary owns updates; an outer presence boundary
334
+ // owns entering/exiting hosts. Shared descendants take precedence.
335
+ captures.sort((a, b) => (a.kind === 'share' ? 2 : a.kind === 'update' ? 0 : 1) - (b.kind === 'share' ? 2 : b.kind === 'update' ? 0 : 1))
336
+ if (!captures.some(capture => capture.old?.hosts.length || (capture.newEnabled && capture.next?.hosts.length))) return false
337
+ const owners = ownership('old')
338
+ apply(doc.documentElement, 'none')
339
+ for (const boundary of before.boundaries.values()) for (const host of boundary.hosts) apply(host, 'none')
340
+ for (const capture of captures) {
341
+ capture.oldHosts = capture.old?.hosts.filter(host => owners.get(host) === capture) ?? []
342
+ const props = capture.kind === 'update' ? capture.next!.props : capture.old?.props
343
+ if (props) applyHosts(capture.oldHosts, capture.name, props, capture.kind)
344
+ }
345
+ return true
346
+ },
347
+ afterCommit() {
348
+ if (done) return
349
+ const after = snapshot(roots, doc, types)
350
+ for (const capture of captures) {
351
+ if (capture.next) capture.next = after.boundaries.get(capture.next.fiber)
352
+ if (capture.kind !== 'update' && !capture.next?.inViewport) capture.newEnabled = false
353
+ }
354
+ const owners = ownership('next')
355
+ for (const boundary of after.boundaries.values()) for (const host of boundary.hosts) apply(host, 'none')
356
+ for (const capture of captures) {
357
+ const newHosts = capture.next?.hosts.filter(host => owners.get(host) === capture) ?? []
358
+ if (capture.next) applyHosts(newHosts, capture.name, capture.next.props, capture.kind)
359
+ if (!capture.oldHosts.length && !newHosts.length) continue
360
+ if (capture.kind === 'update') {
361
+ if (capture.next && capture.old && changed(capture.old, capture.next)) events.push({ boundary: capture.next, name: capture.name, kind: 'update' })
362
+ else suppress(capture.oldHosts, capture.name)
363
+ } else {
364
+ const boundary = capture.kind === 'enter' ? capture.next : capture.old
365
+ if (boundary) events.push({ boundary, name: capture.name, kind: capture.kind })
366
+ }
367
+ }
368
+ },
369
+ ready() {
370
+ if (done) return
371
+ // These captures were needed before the synchronous commit could tell
372
+ // which boundaries changed. Cancel their native animations, not the DOM.
373
+ const nativeSuppressed = new Set([...suppressed].map(nativeName))
374
+ for (const animation of doc.documentElement.getAnimations({ subtree: true })) {
375
+ const effect = animation.effect as KeyframeEffect | null
376
+ const selector = effect?.pseudoElement
377
+ if (effect?.target !== doc.documentElement || !selector) continue
378
+ const start = selector.indexOf('(')
379
+ if (start >= 0 && nativeSuppressed.has(selector.slice(start + 1, -1))) animation.cancel()
380
+ }
381
+ for (const name of suppressed) {
382
+ const animation = pseudo(doc, 'group', name).animate({ opacity: 0 }, { duration: 0, fill: 'forwards' })
383
+ cleanups.push(() => animation.cancel())
384
+ }
385
+ let error: unknown
386
+ let failed = false
387
+ for (const event of events) {
388
+ if (done) break
389
+ const callback = event.boundary.props[('on' + event.kind[0]!.toUpperCase() + event.kind.slice(1)) as 'onEnter']
390
+ if (callback) {
391
+ try {
392
+ const cleanup = callback(instance(doc, event.name), types)
393
+ if (typeof cleanup === 'function') {
394
+ if (done) cleanup()
395
+ else cleanups.push(cleanup)
396
+ }
397
+ } catch (caught) { if (!failed) { error = caught; failed = true } }
398
+ }
399
+ }
400
+ if (failed) throw error
401
+ },
402
+ restore,
403
+ finish,
404
+ cancel: finish,
405
+ }
406
+ }