3
3
// Shared implementation and constants between the inline script and external
4
4
// runtime instruction sets.
5
5
6
+ const ELEMENT_NODE = 1 ;
6
7
const COMMENT_NODE = 8 ;
7
8
const ACTIVITY_START_DATA = '&' ;
8
9
const ACTIVITY_END_DATA = '/&' ;
@@ -84,14 +85,168 @@ export function revealCompletedBoundariesWithViewTransitions(
84
85
revealBoundaries ,
85
86
batch ,
86
87
) {
88
+ let shouldStartViewTransition = false ;
89
+ let autoNameIdx = 0 ;
90
+ const restoreQueue = [ ] ;
91
+ function applyViewTransitionName ( element , classAttributeName ) {
92
+ const className = element . getAttribute ( classAttributeName ) ;
93
+ if ( ! className ) {
94
+ return ;
95
+ }
96
+ // Add any elements we apply a name to a queue to be reverted when we start.
97
+ const elementStyle = element . style ;
98
+ restoreQueue . push (
99
+ element ,
100
+ elementStyle [ 'viewTransitionName' ] ,
101
+ elementStyle [ 'viewTransitionClass' ] ,
102
+ ) ;
103
+ if ( className !== 'auto' ) {
104
+ elementStyle [ 'viewTransitionClass' ] = className ;
105
+ }
106
+ let name = element . getAttribute ( 'vt-name' ) ;
107
+ if ( ! name ) {
108
+ // Auto-generate a name for this one.
109
+ // TODO: We don't have a prefix to pick from here but maybe we don't need it
110
+ // since it's only applicable temporarily during this specific animation.
111
+ const idPrefix = '' ;
112
+ name = '\u00AB' + idPrefix + 'T' + autoNameIdx ++ + '\u00BB' ;
113
+ }
114
+ elementStyle [ 'viewTransitionName' ] = name ;
115
+ shouldStartViewTransition = true ;
116
+ }
87
117
try {
88
118
const existingTransition = document [ '__reactViewTransition' ] ;
89
119
if ( existingTransition ) {
90
120
// Retry after the previous ViewTransition finishes.
91
121
existingTransition . finished . finally ( window [ '$RV' ] . bind ( null , batch ) ) ;
92
122
return ;
93
123
}
94
- const shouldStartViewTransition = window [ '_useVT' ] ; // TODO: Detect.
124
+ // First collect all entering names that might form pairs exiting names.
125
+ const appearingViewTransitions = new Map ( ) ;
126
+ for ( let i = 1 ; i < batch . length ; i += 2 ) {
127
+ const contentNode = batch [ i ] ;
128
+ const appearingElements = contentNode . querySelectorAll ( '[vt-share]' ) ;
129
+ for ( let j = 0 ; j < appearingElements . length ; j ++ ) {
130
+ const appearingElement = appearingElements [ j ] ;
131
+ appearingViewTransitions . set (
132
+ appearingElement . getAttribute ( 'vt-name' ) ,
133
+ appearingElement ,
134
+ ) ;
135
+ }
136
+ }
137
+ // Next we'll find the nodes that we're going to animate and apply names to them..
138
+ for ( let i = 0 ; i < batch . length ; i += 2 ) {
139
+ const suspenseIdNode = batch [ i ] ;
140
+ const parentInstance = suspenseIdNode . parentNode ;
141
+ if ( ! parentInstance ) {
142
+ // We may have client-rendered this boundary already. Skip it.
143
+ continue ;
144
+ }
145
+ const parentRect = parentInstance . getBoundingClientRect ( ) ;
146
+ if (
147
+ ! parentRect . left &&
148
+ ! parentRect . top &&
149
+ ! parentRect . width &&
150
+ ! parentRect . height
151
+ ) {
152
+ // If the parent instance is display: none then we don't animate this boundary.
153
+ // This can happen when this boundary is actually a child of a different boundary that
154
+ // isn't yet revealed or is about to be revealed, but in that case that boundary
155
+ // should do the exit/enter and not this one. Conveniently this also lets us skip
156
+ // this if it's just in a hidden tree in general.
157
+ // TODO: Should we skip it if it's out of viewport? It's possible that it gets
158
+ // brought into the viewport by changing size.
159
+ // TODO: There's a another case where an inner boundary is inside a fallback that
160
+ // is about to be deleted. In that case we should not run exit animations on the inner.
161
+ continue ;
162
+ }
163
+
164
+ // Apply exit animations to the immediate elements inside the fallback.
165
+ let node = suspenseIdNode ;
166
+ let depth = 0 ;
167
+ while ( node ) {
168
+ if ( node . nodeType === COMMENT_NODE ) {
169
+ const data = node . data ;
170
+ if ( data === SUSPENSE_END_DATA ) {
171
+ if ( depth === 0 ) {
172
+ break ;
173
+ } else {
174
+ depth -- ;
175
+ }
176
+ } else if (
177
+ data === SUSPENSE_START_DATA ||
178
+ data === SUSPENSE_PENDING_START_DATA ||
179
+ data === SUSPENSE_QUEUED_START_DATA ||
180
+ data === SUSPENSE_FALLBACK_START_DATA
181
+ ) {
182
+ depth ++ ;
183
+ }
184
+ } else if ( node . nodeType === ELEMENT_NODE ) {
185
+ const exitElement = node ;
186
+ const exitName = exitElement . getAttribute ( 'vt-name' ) ;
187
+ const pairedElement = appearingViewTransitions . get ( exitName ) ;
188
+ applyViewTransitionName (
189
+ exitElement ,
190
+ pairedElement ? 'vt-share' : 'vt-exit' ,
191
+ ) ;
192
+ if ( pairedElement ) {
193
+ // Activate the other side as well.
194
+ applyViewTransitionName ( pairedElement , 'vt-share' ) ;
195
+ appearingViewTransitions . set ( exitName , null ) ; // mark claimed
196
+ }
197
+ // Next we'll look inside this element for pairs to trigger "share".
198
+ const disappearingElements =
199
+ exitElement . querySelectorAll ( '[vt-share]' ) ;
200
+ for ( let j = 0 ; j < disappearingElements . length ; j ++ ) {
201
+ const disappearingElement = disappearingElements [ j ] ;
202
+ const name = disappearingElement . getAttribute ( 'vt-name' ) ;
203
+ const appearingElement = appearingViewTransitions . get ( name ) ;
204
+ if ( appearingElement ) {
205
+ applyViewTransitionName ( disappearingElement , 'vt-share' ) ;
206
+ applyViewTransitionName ( appearingElement , 'vt-share' ) ;
207
+ appearingViewTransitions . set ( name , null ) ; // mark claimed
208
+ }
209
+ }
210
+ }
211
+ node = node . nextSibling ;
212
+ }
213
+
214
+ // Apply enter animations to the new nodes about to be inserted.
215
+ const contentNode = batch [ i + 1 ] ;
216
+ let enterElement = contentNode . firstElementChild ;
217
+ while ( enterElement ) {
218
+ const paired =
219
+ appearingViewTransitions . get ( enterElement . getAttribute ( 'vt-name' ) ) ===
220
+ null ;
221
+ if ( ! paired ) {
222
+ applyViewTransitionName ( enterElement , 'vt-enter' ) ;
223
+ }
224
+ enterElement = enterElement . nextElementSibling ;
225
+ }
226
+
227
+ // Apply update animations to any parents and siblings that might be affected.
228
+ let ancestorElement = parentInstance ;
229
+ do {
230
+ let childElement = ancestorElement . firstElementChild ;
231
+ while ( childElement ) {
232
+ // TODO: Bail out if we can
233
+ const updateClassName = childElement . getAttribute ( 'vt-update' ) ;
234
+ if (
235
+ updateClassName &&
236
+ updateClassName !== 'none' &&
237
+ ! restoreQueue . includes ( childElement )
238
+ ) {
239
+ // If we have already handled this element as part of another exit/enter/share, don't override.
240
+ applyViewTransitionName ( childElement , 'vt-update' ) ;
241
+ }
242
+ childElement = childElement . nextElementSibling ;
243
+ }
244
+ } while (
245
+ ( ancestorElement = ancestorElement . parentNode ) &&
246
+ ancestorElement . nodeType === ELEMENT_NODE &&
247
+ ancestorElement . getAttribute ( 'vt-update' ) !== 'none'
248
+ ) ;
249
+ }
95
250
if ( shouldStartViewTransition ) {
96
251
const transition = ( document [ '__reactViewTransition' ] = document [
97
252
'startViewTransition'
@@ -100,7 +255,19 @@ export function revealCompletedBoundariesWithViewTransitions(
100
255
types : [ ] , // TODO: Add a hard coded type for Suspense reveals.
101
256
} ) ) ;
102
257
transition . ready . finally ( ( ) => {
103
- // TODO
258
+ // Restore all the names/classes that we applied to what they were before.
259
+ // We do it in reverse order in case there were duplicates so the first one wins.
260
+ for ( let i = restoreQueue . length - 3 ; i >= 0 ; i -= 3 ) {
261
+ const element = restoreQueue [ i ] ;
262
+ const elementStyle = element . style ;
263
+ const previousName = restoreQueue [ i + 1 ] ;
264
+ elementStyle [ 'viewTransitionName' ] = previousName ;
265
+ const previousClassName = restoreQueue [ i + 1 ] ;
266
+ elementStyle [ 'viewTransitionClass' ] = previousClassName ;
267
+ if ( element . getAttribute ( 'style' ) === '' ) {
268
+ element . removeAttribute ( 'style' ) ;
269
+ }
270
+ }
104
271
} ) ;
105
272
transition . finished . finally ( ( ) => {
106
273
if ( document [ '__reactViewTransition' ] === transition ) {
0 commit comments