@@ -137,6 +137,11 @@ export type Props = {
137
137
'view-transition-name' ?: string ,
138
138
viewTransitionClass ?: string ,
139
139
'view-transition-class' ?: string ,
140
+ margin ?: string ,
141
+ marginTop ?: string ,
142
+ 'margin-top' ?: string ,
143
+ marginBottom ?: string ,
144
+ 'margin-bottom' ?: string ,
140
145
...
141
146
} ,
142
147
bottom ?: null | number ,
@@ -1161,6 +1166,59 @@ export function unhideTextInstance(
1161
1166
textInstance . nodeValue = text ;
1162
1167
}
1163
1168
1169
+ function warnForBlockInsideInline ( instance : HTMLElement ) {
1170
+ if ( __DEV__ ) {
1171
+ let nextNode = instance . firstChild ;
1172
+ outer: while ( nextNode != null ) {
1173
+ let node : Node = nextNode ;
1174
+ if (
1175
+ node . nodeType === ELEMENT_NODE &&
1176
+ getComputedStyle ( ( node : any ) ) . display === 'block'
1177
+ ) {
1178
+ console . error (
1179
+ "You're about to start a <ViewTransition> around a display: inline " +
1180
+ 'element <%s>, which itself has a display: block element <%s> inside it. ' +
1181
+ 'This might trigger a bug in Safari which causes the View Transition to ' +
1182
+ 'be skipped with a duplicate name error.\n' +
1183
+ 'https://bugs.webkit.org/show_bug.cgi?id=290923' ,
1184
+ instance . tagName . toLocaleLowerCase ( ) ,
1185
+ ( node : any ) . tagName . toLocaleLowerCase ( ) ,
1186
+ ) ;
1187
+ break ;
1188
+ }
1189
+ if ( node . firstChild != null ) {
1190
+ nextNode = node . firstChild ;
1191
+ continue ;
1192
+ }
1193
+ if ( node === instance ) {
1194
+ break ;
1195
+ }
1196
+ while ( node . nextSibling == null ) {
1197
+ if ( node . parentNode == null || node . parentNode === instance ) {
1198
+ break ;
1199
+ }
1200
+ node = node . parentNode ;
1201
+ }
1202
+ nextNode = node . nextSibling ;
1203
+ }
1204
+ }
1205
+ }
1206
+
1207
+ function countClientRects ( rects : Array < ClientRect > ) : number {
1208
+ if ( rects . length === 1 ) {
1209
+ return 1 ;
1210
+ }
1211
+ // Count non-zero rects.
1212
+ let count = 0 ;
1213
+ for ( let i = 0 ; i < rects . length ; i ++ ) {
1214
+ const rect = rects [ i ] ;
1215
+ if ( rect . width > 0 && rect . height > 0 ) {
1216
+ count ++ ;
1217
+ }
1218
+ }
1219
+ return count ;
1220
+ }
1221
+
1164
1222
export function applyViewTransitionName (
1165
1223
instance : Instance ,
1166
1224
name : string ,
@@ -1173,13 +1231,42 @@ export function applyViewTransitionName(
1173
1231
// $FlowFixMe[prop-missing]
1174
1232
instance . style . viewTransitionClass = className ;
1175
1233
}
1234
+ const computedStyle = getComputedStyle ( instance ) ;
1235
+ if ( computedStyle . display === 'inline' ) {
1236
+ // WebKit has a bug where assigning a name to display: inline elements errors
1237
+ // if they have display: block children. We try to work around this bug in the
1238
+ // simple case by converting it automatically to display: inline-block.
1239
+ // https://bugs.webkit.org/show_bug.cgi?id=290923
1240
+ const rects = instance . getClientRects ( ) ;
1241
+ if ( countClientRects ( rects ) === 1 ) {
1242
+ // If the instance has a single client rect, that means that it can be
1243
+ // expressed as a display: inline-block or block.
1244
+ // This will cause layout thrash but we live with it since inline view transitions
1245
+ // are unusual.
1246
+ const style = instance . style ;
1247
+ // If there's literally only one rect, then it's likely on a single line like an
1248
+ // inline-block. If it's multiple rects but all but one of them are empty it's
1249
+ // likely because it's a single block that caused a line break.
1250
+ style . display = rects . length === 1 ? 'inline-block' : 'block' ;
1251
+ // Margin doesn't apply to inline so should be zero. However, padding top/bottom
1252
+ // applies to inline-block positioning which we can offset by setting the margin
1253
+ // to the negative padding to get it back into original position.
1254
+ style . marginTop = '-' + computedStyle . paddingTop ;
1255
+ style . marginBottom = '-' + computedStyle . paddingBottom ;
1256
+ } else {
1257
+ // This case cannot be easily fixed if it has blocks but it's also fine if
1258
+ // it doesn't have blocks. So we only warn in DEV about this being an issue.
1259
+ warnForBlockInsideInline ( instance ) ;
1260
+ }
1261
+ }
1176
1262
}
1177
1263
1178
1264
export function restoreViewTransitionName (
1179
1265
instance : Instance ,
1180
1266
props : Props ,
1181
1267
) : void {
1182
1268
instance = ( ( instance : any ) : HTMLElement ) ;
1269
+ const style = instance . style ;
1183
1270
const styleProp = props [ STYLE ] ;
1184
1271
const viewTransitionName =
1185
1272
styleProp != null
@@ -1190,7 +1277,7 @@ export function restoreViewTransitionName(
1190
1277
: null
1191
1278
: null ;
1192
1279
// $FlowFixMe[prop-missing]
1193
- instance . style . viewTransitionName =
1280
+ style . viewTransitionName =
1194
1281
viewTransitionName == null || typeof viewTransitionName === 'boolean'
1195
1282
? ''
1196
1283
: // The value would've errored already if it wasn't safe.
@@ -1205,12 +1292,39 @@ export function restoreViewTransitionName(
1205
1292
: null
1206
1293
: null ;
1207
1294
// $FlowFixMe[prop-missing]
1208
- instance . style . viewTransitionClass =
1295
+ style . viewTransitionClass =
1209
1296
viewTransitionClass == null || typeof viewTransitionClass === 'boolean'
1210
1297
? ''
1211
1298
: // The value would've errored already if it wasn't safe.
1212
1299
// eslint-disable-next-line react-internal/safe-string-coercion
1213
1300
( '' + viewTransitionClass ) . trim ( ) ;
1301
+ if ( style . display === 'inline-block' ) {
1302
+ // We might have overridden the style. Reset it to what it should be.
1303
+ if ( styleProp == null ) {
1304
+ style . display = style . margin = '' ;
1305
+ } else {
1306
+ const display = styleProp . display ;
1307
+ style . display =
1308
+ display == null || typeof display === 'boolean' ? '' : display ;
1309
+ const margin = styleProp . margin ;
1310
+ if ( margin != null ) {
1311
+ style . margin = margin ;
1312
+ } else {
1313
+ const marginTop = styleProp . hasOwnProperty ( 'marginTop' )
1314
+ ? styleProp . marginTop
1315
+ : styleProp [ 'margin-top' ] ;
1316
+ style . marginTop =
1317
+ marginTop == null || typeof marginTop === 'boolean' ? '' : marginTop ;
1318
+ const marginBottom = styleProp . hasOwnProperty ( 'marginBottom' )
1319
+ ? styleProp . marginBottom
1320
+ : styleProp [ 'margin-bottom' ] ;
1321
+ style . marginBottom =
1322
+ marginBottom == null || typeof marginBottom === 'boolean'
1323
+ ? ''
1324
+ : marginBottom ;
1325
+ }
1326
+ }
1327
+ }
1214
1328
}
1215
1329
1216
1330
export function cancelViewTransitionName (
0 commit comments