-
Notifications
You must be signed in to change notification settings - Fork 48.8k
[Fizz] Add vt- prefix attributes to annotate <ViewTransition> in HTML #33206
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
||
expect(getVisibleChildren(container)).toEqual( | ||
<div> | ||
<div vt-name="«R0»" vt-update="auto" vt-share="auto"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an interesting special case. Removing the fallback and inserting the content is an update
to the parent <ViewTransition>
but since it has no representation without a virtual node there's nothing to handle that update.
Instead, we can rewrite this scenario as a shared element transition between the fallback and the content. Effectively it gets rewritten to as if there was a <ViewTransition>
directly inside each one with a shared name.
This is currently the only case where we have to include an auto-generated name in the HTML to pair these, instead of letting the Fizz runtime generate the name.
9a30448
to
2a3b7a3
Compare
return createFormatContext( | ||
parentContext.insertionMode, | ||
parentContext.selectedValue, | ||
parentContext.tagScope | FALLBACK_SCOPE | ENTER_SCOPE, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy pasta. This should not have fallback scope on it.
pushStringAttribute(target, 'vt-update', viewTransition.update); | ||
if (viewTransition.enter !== null) { | ||
pushStringAttribute(target, 'vt-enter', viewTransition.enter); | ||
} | ||
if (viewTransition.exit !== null) { | ||
pushStringAttribute(target, 'vt-exit', viewTransition.exit); | ||
} | ||
if (viewTransition.share !== null) { | ||
pushStringAttribute(target, 'vt-share', viewTransition.share); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've tried hard to keep the output spec compliant where possible. squatting on a data namespace isn't as culturally problematic as squatting on a potential spec namespace. I think we should eat the bytes and use data-vt-*
. It's not likely meaningfully incompatible with user code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
People enumerate over the data-
set a lot. We lived with it for <link>
and <style>
for precedence since who would add data-
to those but you enumerate dataSet
a lot on your own owned elements. So I think data-
is a no go. Can't add stuff to that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe r-vt-
as the namespace? r-
also seems unlikely to be used in the future
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
x-
will never be added to HTML so x-vt-*
might be the closest thing to compliant. It’s still technically not but at least we know it’ll never block future attributes
// TODO: ViewTransition attributes gets observed by the Custom Element which is a bit sketchy. | ||
pushViewTransitionAttributes(target, formatContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah but isn't this still a plus for styling?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean? The issue is that custom elements are likely to observe any attributes you set on them as their private name space so the extension is more problematic on those.
Allows for more of these to be used if needed like how we pass RenderState.
null means that we don't emit it as an instruction for that case. We want to minimize the number of instructions we emit.
This needs to use an auto assigned name if no explicit one is provided to associate deleted fallback nodes with inserted content nodes.
…n context This is more or less every element except things that are always invisible.
…#33206) Stacked on #33194 and #33200. When Suspense boundaries reveal during streaming, the Fizz runtime will be responsible for animating the reveal if necessary (not in this PR). However, for the future runtime to know what to do it needs to know about the `<ViewTransition>` configuration to apply. Ofc, these are virtual nodes that disappear from the HTML. We could model them as comments like we do with other virtual nodes like Suspense and Activity. However, that doesn't let us target them with querySelector and CSS (for no-JS transitions). We also don't have to model every ViewTransition since not every combination can happen using only the server runtime. So instead this collapses `<ViewTransition>` and applies the configuration to the inner DOM nodes. ```js <ViewTransition name="hi"> <div /> <div /> </ViewTransition> ``` Becomes: ```html <div vt-name="hi" vt-update="auto"></div> <div vt-name="hi_1" vt-update="auto"></div> ``` I use `vt-` prefix as opposed to `data-` to keep these virtual attributes away from user specific ones but we're effectively claiming this namespace. There are four triggers `vt-update`, `vt-enter`, `vt-exit` and `vt-share`. The server resolves which ones might apply to this DOM node. The value represents the class name (after resolving view-transition-type mappings) or `"auto"` if no specific class name is needed but this is still a trigger. The value can also be `"none"`. This is different from missing because for example an `vt-update="none"` will block mutations inside it from triggering the boundary where as a missing `vt-update` would bubble up to be handled by a parent. `vt-name` is technically only necessary when `vt-share` is specified to find a pair. However, since an explicit name can also be used to target specific CSS selectors, we include it even for other cases. We want to exclude as many of these annotations as possible. `vt-enter` can only affect the first DOM node inside a Suspense boundary's content since the reveal would cause it to enter but nothing deeper inside. Similarly `vt-exit` can only affect the first DOM node inside a fallback. So for every other case we can exclude them. (For future MPA ViewTransitions of the whole document it might also be something we annotate to children inside the `<body>` as well.) Ideally we'd only include `vt-enter` for Suspense boundaries that actually flushed a fallback but since we prepare all that content earlier it's hard to know. `vt-share` can be anywhere inside an fallback or content. Technically we don't have to include it outside the root most Suspense boundary or for boundaries that are inlined into the root shell. However, this is tricky to detect. It would also not be correct for future MPA ViewTransitions because in that case the shared scenario can affect anything in the two documents so it needs to be in every node everywhere which is effectively what we do. If a `share` class is specified but it has no explicit name, we can exclude it since it can't match anything. `vt-update` is only necessary if something below or a sibling might update like a Suspense boundary. However, since we don't know when rendering a segment if it'll later asynchronously add a Suspense boundary later we have to assume that anywhere might have a child. So these are always included. We collapse to use the inner most one when directly nested though since that's the one that ends up winning. There are some weird edge cases that can't be fully modeled by the lack of virtual nodes. DiffTrain build for [65b5aae](65b5aae)
…#33206) Stacked on #33194 and #33200. When Suspense boundaries reveal during streaming, the Fizz runtime will be responsible for animating the reveal if necessary (not in this PR). However, for the future runtime to know what to do it needs to know about the `<ViewTransition>` configuration to apply. Ofc, these are virtual nodes that disappear from the HTML. We could model them as comments like we do with other virtual nodes like Suspense and Activity. However, that doesn't let us target them with querySelector and CSS (for no-JS transitions). We also don't have to model every ViewTransition since not every combination can happen using only the server runtime. So instead this collapses `<ViewTransition>` and applies the configuration to the inner DOM nodes. ```js <ViewTransition name="hi"> <div /> <div /> </ViewTransition> ``` Becomes: ```html <div vt-name="hi" vt-update="auto"></div> <div vt-name="hi_1" vt-update="auto"></div> ``` I use `vt-` prefix as opposed to `data-` to keep these virtual attributes away from user specific ones but we're effectively claiming this namespace. There are four triggers `vt-update`, `vt-enter`, `vt-exit` and `vt-share`. The server resolves which ones might apply to this DOM node. The value represents the class name (after resolving view-transition-type mappings) or `"auto"` if no specific class name is needed but this is still a trigger. The value can also be `"none"`. This is different from missing because for example an `vt-update="none"` will block mutations inside it from triggering the boundary where as a missing `vt-update` would bubble up to be handled by a parent. `vt-name` is technically only necessary when `vt-share` is specified to find a pair. However, since an explicit name can also be used to target specific CSS selectors, we include it even for other cases. We want to exclude as many of these annotations as possible. `vt-enter` can only affect the first DOM node inside a Suspense boundary's content since the reveal would cause it to enter but nothing deeper inside. Similarly `vt-exit` can only affect the first DOM node inside a fallback. So for every other case we can exclude them. (For future MPA ViewTransitions of the whole document it might also be something we annotate to children inside the `<body>` as well.) Ideally we'd only include `vt-enter` for Suspense boundaries that actually flushed a fallback but since we prepare all that content earlier it's hard to know. `vt-share` can be anywhere inside an fallback or content. Technically we don't have to include it outside the root most Suspense boundary or for boundaries that are inlined into the root shell. However, this is tricky to detect. It would also not be correct for future MPA ViewTransitions because in that case the shared scenario can affect anything in the two documents so it needs to be in every node everywhere which is effectively what we do. If a `share` class is specified but it has no explicit name, we can exclude it since it can't match anything. `vt-update` is only necessary if something below or a sibling might update like a Suspense boundary. However, since we don't know when rendering a segment if it'll later asynchronously add a Suspense boundary later we have to assume that anywhere might have a child. So these are always included. We collapse to use the inner most one when directly nested though since that's the one that ends up winning. There are some weird edge cases that can't be fully modeled by the lack of virtual nodes. DiffTrain build for [65b5aae](65b5aae)
…facebook#33206) Stacked on facebook#33194 and facebook#33200. When Suspense boundaries reveal during streaming, the Fizz runtime will be responsible for animating the reveal if necessary (not in this PR). However, for the future runtime to know what to do it needs to know about the `<ViewTransition>` configuration to apply. Ofc, these are virtual nodes that disappear from the HTML. We could model them as comments like we do with other virtual nodes like Suspense and Activity. However, that doesn't let us target them with querySelector and CSS (for no-JS transitions). We also don't have to model every ViewTransition since not every combination can happen using only the server runtime. So instead this collapses `<ViewTransition>` and applies the configuration to the inner DOM nodes. ```js <ViewTransition name="hi"> <div /> <div /> </ViewTransition> ``` Becomes: ```html <div vt-name="hi" vt-update="auto"></div> <div vt-name="hi_1" vt-update="auto"></div> ``` I use `vt-` prefix as opposed to `data-` to keep these virtual attributes away from user specific ones but we're effectively claiming this namespace. There are four triggers `vt-update`, `vt-enter`, `vt-exit` and `vt-share`. The server resolves which ones might apply to this DOM node. The value represents the class name (after resolving view-transition-type mappings) or `"auto"` if no specific class name is needed but this is still a trigger. The value can also be `"none"`. This is different from missing because for example an `vt-update="none"` will block mutations inside it from triggering the boundary where as a missing `vt-update` would bubble up to be handled by a parent. `vt-name` is technically only necessary when `vt-share` is specified to find a pair. However, since an explicit name can also be used to target specific CSS selectors, we include it even for other cases. We want to exclude as many of these annotations as possible. `vt-enter` can only affect the first DOM node inside a Suspense boundary's content since the reveal would cause it to enter but nothing deeper inside. Similarly `vt-exit` can only affect the first DOM node inside a fallback. So for every other case we can exclude them. (For future MPA ViewTransitions of the whole document it might also be something we annotate to children inside the `<body>` as well.) Ideally we'd only include `vt-enter` for Suspense boundaries that actually flushed a fallback but since we prepare all that content earlier it's hard to know. `vt-share` can be anywhere inside an fallback or content. Technically we don't have to include it outside the root most Suspense boundary or for boundaries that are inlined into the root shell. However, this is tricky to detect. It would also not be correct for future MPA ViewTransitions because in that case the shared scenario can affect anything in the two documents so it needs to be in every node everywhere which is effectively what we do. If a `share` class is specified but it has no explicit name, we can exclude it since it can't match anything. `vt-update` is only necessary if something below or a sibling might update like a Suspense boundary. However, since we don't know when rendering a segment if it'll later asynchronously add a Suspense boundary later we have to assume that anywhere might have a child. So these are always included. We collapse to use the inner most one when directly nested though since that's the one that ends up winning. There are some weird edge cases that can't be fully modeled by the lack of virtual nodes. DiffTrain build for [65b5aae](facebook@65b5aae)
…facebook#33206) Stacked on facebook#33194 and facebook#33200. When Suspense boundaries reveal during streaming, the Fizz runtime will be responsible for animating the reveal if necessary (not in this PR). However, for the future runtime to know what to do it needs to know about the `<ViewTransition>` configuration to apply. Ofc, these are virtual nodes that disappear from the HTML. We could model them as comments like we do with other virtual nodes like Suspense and Activity. However, that doesn't let us target them with querySelector and CSS (for no-JS transitions). We also don't have to model every ViewTransition since not every combination can happen using only the server runtime. So instead this collapses `<ViewTransition>` and applies the configuration to the inner DOM nodes. ```js <ViewTransition name="hi"> <div /> <div /> </ViewTransition> ``` Becomes: ```html <div vt-name="hi" vt-update="auto"></div> <div vt-name="hi_1" vt-update="auto"></div> ``` I use `vt-` prefix as opposed to `data-` to keep these virtual attributes away from user specific ones but we're effectively claiming this namespace. There are four triggers `vt-update`, `vt-enter`, `vt-exit` and `vt-share`. The server resolves which ones might apply to this DOM node. The value represents the class name (after resolving view-transition-type mappings) or `"auto"` if no specific class name is needed but this is still a trigger. The value can also be `"none"`. This is different from missing because for example an `vt-update="none"` will block mutations inside it from triggering the boundary where as a missing `vt-update` would bubble up to be handled by a parent. `vt-name` is technically only necessary when `vt-share` is specified to find a pair. However, since an explicit name can also be used to target specific CSS selectors, we include it even for other cases. We want to exclude as many of these annotations as possible. `vt-enter` can only affect the first DOM node inside a Suspense boundary's content since the reveal would cause it to enter but nothing deeper inside. Similarly `vt-exit` can only affect the first DOM node inside a fallback. So for every other case we can exclude them. (For future MPA ViewTransitions of the whole document it might also be something we annotate to children inside the `<body>` as well.) Ideally we'd only include `vt-enter` for Suspense boundaries that actually flushed a fallback but since we prepare all that content earlier it's hard to know. `vt-share` can be anywhere inside an fallback or content. Technically we don't have to include it outside the root most Suspense boundary or for boundaries that are inlined into the root shell. However, this is tricky to detect. It would also not be correct for future MPA ViewTransitions because in that case the shared scenario can affect anything in the two documents so it needs to be in every node everywhere which is effectively what we do. If a `share` class is specified but it has no explicit name, we can exclude it since it can't match anything. `vt-update` is only necessary if something below or a sibling might update like a Suspense boundary. However, since we don't know when rendering a segment if it'll later asynchronously add a Suspense boundary later we have to assume that anywhere might have a child. So these are always included. We collapse to use the inner most one when directly nested though since that's the one that ends up winning. There are some weird edge cases that can't be fully modeled by the lack of virtual nodes. DiffTrain build for [65b5aae](facebook@65b5aae)
Stacked on #33194 and #33200.
When Suspense boundaries reveal during streaming, the Fizz runtime will be responsible for animating the reveal if necessary (not in this PR). However, for the future runtime to know what to do it needs to know about the
<ViewTransition>
configuration to apply.Ofc, these are virtual nodes that disappear from the HTML. We could model them as comments like we do with other virtual nodes like Suspense and Activity. However, that doesn't let us target them with querySelector and CSS (for no-JS transitions). We also don't have to model every ViewTransition since not every combination can happen using only the server runtime. So instead this collapses
<ViewTransition>
and applies the configuration to the inner DOM nodes.Becomes:
I use
vt-
prefix as opposed todata-
to keep these virtual attributes away from user specific ones but we're effectively claiming this namespace.There are four triggers
vt-update
,vt-enter
,vt-exit
andvt-share
. The server resolves which ones might apply to this DOM node. The value represents the class name (after resolving view-transition-type mappings) or"auto"
if no specific class name is needed but this is still a trigger.The value can also be
"none"
. This is different from missing because for example anvt-update="none"
will block mutations inside it from triggering the boundary where as a missingvt-update
would bubble up to be handled by a parent.vt-name
is technically only necessary whenvt-share
is specified to find a pair. However, since an explicit name can also be used to target specific CSS selectors, we include it even for other cases.We want to exclude as many of these annotations as possible.
vt-enter
can only affect the first DOM node inside a Suspense boundary's content since the reveal would cause it to enter but nothing deeper inside. Similarlyvt-exit
can only affect the first DOM node inside a fallback. So for every other case we can exclude them. (For future MPA ViewTransitions of the whole document it might also be something we annotate to children inside the<body>
as well.) Ideally we'd only includevt-enter
for Suspense boundaries that actually flushed a fallback but since we prepare all that content earlier it's hard to know.vt-share
can be anywhere inside an fallback or content. Technically we don't have to include it outside the root most Suspense boundary or for boundaries that are inlined into the root shell. However, this is tricky to detect. It would also not be correct for future MPA ViewTransitions because in that case the shared scenario can affect anything in the two documents so it needs to be in every node everywhere which is effectively what we do. If ashare
class is specified but it has no explicit name, we can exclude it since it can't match anything.vt-update
is only necessary if something below or a sibling might update like a Suspense boundary. However, since we don't know when rendering a segment if it'll later asynchronously add a Suspense boundary later we have to assume that anywhere might have a child. So these are always included. We collapse to use the inner most one when directly nested though since that's the one that ends up winning.There are some weird edge cases that can't be fully modeled by the lack of virtual nodes.