Commit Graph

19887 Commits

Author SHA1 Message Date
Ruslan Lesiutin
5b51a2b9e2
fix[DevTools]: support useResourceEffect (#32088)
Since we've started experimenting with it, I've started seeing a spike
in errors:
```
Unsupported hook in the react-debug-tools package: Missing method in Dispatcher: useResourceEffect
```

Adding missing hook to the `Dispatcher` that is proxied by React
DevTools.

I can't really add an example that will use it to our RDT testing shell,
because it uses experimental builds of `react`, which don't have this
hook. I've tested it manually by rebuilding artifacts with
`enableUseResourceEffectHook` flag enabled.

![Screenshot 2025-01-16 at 15 20
00](https://github.com/user-attachments/assets/a0d63fd6-1f17-4710-a2b2-82d484b8987f)
2025-01-16 15:42:53 +00:00
Jack Pope
6093f1862a
Fix local react usage in DOM fixture (#32080)
The DOM fixture hasn't worked on local builds since the UMD support was
removed in https://github.com/facebook/react/pull/28735

Here we update the fixture to set the local experimental builds to
window. Some of the pages are still broken, such as hydration. But these
bugs exist on other versions as well and can be cleaned up separately.
2025-01-16 10:33:24 -05:00
Ruslan Lesiutin
89dbd487fc
fix[DevTools]: fix HostComponent naming in filters for Native (#32086)
Right now we mention DOM elements as Host elements for React Native,
which doesn't make sense.
2025-01-16 14:17:06 +00:00
Ricky
87276ef1ef
[actions] split out compiler notify (#32079) 2025-01-15 17:41:31 -05:00
Szymon Chmal
f13abbbb44
Fix copy functionality in Firefox (#32077)
## Summary

This pull request addresses an issue where the copy functionality was
not working in Firefox. The root cause was the absence of the
'clipboardWrite' permission in the manifest. To ensure consistency
across all supported browsers, the 'clipboardWrite' permission has been
added to the manifests for Chrome, Edge, and Firefox extensions.

Closes #31422 

## How did you test this change?

I ran the modified extension in all browsers (MacOS) and verified that
the copy functionality works in each.

https://github.com/user-attachments/assets/a41ff14b-3d65-409c-ac7f-1ccd72fa944a
2025-01-15 18:41:22 +00:00
Ricky
43d18bc2d3
[internal] fix console patch, add RN (#32075)
The forking for `shared/ReactFeatureFlags` doesn't work in the console
patches. Since they're already forked, we can import the internal
ReactFeatureFlags files directly.

Would have caught this in testing a PR sync, but the PR syncs are broken
right now.
2025-01-15 11:20:44 -05:00
Ruslan Lesiutin
b158439a5b
chore[DevTools]: don't use batchedUpdate (#32074)
It is no-op for concurrent mode now and React DevTools is using
experimental version of React:

886c5ad936/packages/react-dom/src/shared/ReactDOM.js (L51-L54)


540efebcc3/packages/react-reconciler/src/ReactFiberWorkLoop.js (L1646-L1651)
2025-01-15 14:32:33 +00:00
Ricky
059760548f
Flag to remove www console forks (#32058)
Let's remove these
2025-01-15 09:19:21 -05:00
Sebastian "Sebbie" Silbermann
886c5ad936
Reduce risk of leaving shipped Hooks as nullable on Dispatcher (#32068) 2025-01-15 00:40:54 +01:00
Sebastian "Sebbie" Silbermann
f0edf41e3e
Fix Flight fixture CI (#32070) 2025-01-15 00:29:37 +01:00
Sebastian "Sebbie" Silbermann
f9f17f6c8d
[Fizz] Restore useMemoCache in renderers with support for Client APIs (#32067) 2025-01-14 22:59:52 +01:00
Szymon Chmal
453f505256
[DevTools] Prevent crash when starting consecutive profiling sessions (#32066)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

This pull request resolves an issue where consecutive profiling sessions
would cause Dev Tools to freeze due to an infinite loop of state
updates. The problem occurs when the startProfiling function triggers a
call to [`selectCommitIndex(0)` in
SnapshotSelector](b3a95caf61/packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotSelector.js (L77-L85))
as previous profiling data is available, which causes a re-render. Then,
[ProfilerContextProvider calls
`selectCommitIndex(null)`](b3a95caf61/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js (L231-L241))
to clear the view while profiling is in progress, leading to another
re-render and creating an infinite loop. This behavior was prevented by
clearing the existing profiling data before starting a new session.

Closes #31977
Closes #31679

## How did you test this change?

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->

I ran the Dev Tools locally following [the contributing
guideline](b3a95caf61/packages/react-devtools/CONTRIBUTING.md).
I observed the freeze at the start of the second profiling session.
Then, I modified the code to clear the store when starting a new session
and ran the Dev Tools again. This time, no freeze was observed.

Before:

https://github.com/user-attachments/assets/9d790f84-f6d0-4951-8202-e599cf8d225b

After:

https://github.com/user-attachments/assets/af097019-0b8f-49dd-8afc-0f6cd72af787
2025-01-14 20:23:42 +00:00
michael faith
b3a95caf61
fix(eslint-plugin-react-compiler): support v9 context api (#32045)
## Summary

This change fixes a gap in the plugin's support of eslint v9. In one
place that it's using the `SourceCode` api, it's correctly considering
v9's api. But in the other place where `SourceCode` is used, it's only
using the legacy api, which was removed in v9.
2025-01-13 13:49:31 -05:00
Sebastian "Sebbie" Silbermann
6099351c76
Use internal ReactCompilerRuntime in react/compiler-runtime entrypoint (#32054) 2025-01-13 19:42:20 +01:00
mofeiZ
af8532f251
[compiler][ez] Patch compilationMode:infer object method edge case (#32055)
Fix for  https://github.com/facebook/react/issues/31180
2025-01-13 12:18:59 -05:00
Sebastian Markbåge
cabd8a0e70
View Transition Class Names based on event kind (#32050)
This adds five props to `<ViewTransition>` that adds a specific
`view-transition-class` when React wants to animate it based on the
heuristic that triggers.

```js
<ViewTransition
  enter="slide-from-left"
  exit="slide-to-right"
  layout="slide"
  update="none"
  share="cross-fade"
>
```

- `enter`: The <ViewTransition> or its parent Component is mounted and
there's no other <ViewTransition> with the same name being deleted.
- `exit`: The <ViewTransition> or its parent Component is unmounted and
there's no other <ViewTransition> with the same name being deleted.
- `layout`: There are no updates to the content inside this
<ViewTransition> boundary itself but the boundary has resized or moved
due to other changes to siblings.
- `share`: This <ViewTransition> is being mounted and another
<ViewTransition> instance with the same name is being unmounted
elsewhere.
- `update`: The content of <ViewTransition> has changed either due to
DOM mutations or because an inner child <ViewTransition> has resized.

The existing `className` is the baseline and the others are added to it
to combine.

This is convenient to distinguish things like `enter` / `exit` but that
can already be expressed as CSS. The other cases can't be expressed as
purely CSS.

`"none"` is a special value that deactivates the view transition name
under that condition.

The most important feature of this is that you can now limit View
Transitions to only tigger when a particular DOM node is affected, not
when just any child updates, by opt-ing out a subtree. This is safer
when added to shared parent.

```js
<ViewTransition>
  <div>
     <ViewTransition className="none">
       {children}
     </ViewTransition>
  </div>
</ViewTransition>
```

This can't be fully expressed using neither just CSS nor the imperative
refs API since we need some way to have already removed the
`view-transition-name` when this happens. When you think about the
implementation details it might seem a bit strange that you specify the
`class` to `none` to remove the `name` but it's really about picking
which animation should happen for that case default (`undefined`), a
specific one (class) or none (`"none"`).
2025-01-13 09:45:53 -05:00
Sebastian Markbåge
540efebcc3
View Transition Events (#32041)
This adds five events to `<ViewTransition>` that triggers when React
wants to animate it.

- `onEnter`: The `<ViewTransition>` or its parent Component is mounted
and there's no other `<ViewTransition>` with the same name being
deleted.
- `onExit`: The `<ViewTransition>` or its parent Component is unmounted
and there's no other `<ViewTransition>` with the same name being
deleted.
- `onLayout`: There are no updates to the content inside this
`<ViewTransition>` boundary itself but the boundary has resized or moved
due to other changes to siblings.
- `onShare`: This `<ViewTransition>` is being mounted and another
`<ViewTransition>` instance with the same name is being unmounted
elsewhere.
- `onUpdate`: The content of `<ViewTransition>` has changed either due
to DOM mutations or because an inner child `<ViewTransition>` has
resized.

Only one of these events is fired per Transition. If you want to cover
all updates you have to listen to `onLayout`, `onShare` and `onUpdate`.
We could potentially do something like fire `onUpdate` if `onLayout` or
`onShare` isn't specified but it's a little sketchy to have behavior
based on if someone is listening since it limits adding wrappers that
may or may not need it.

Each takes a `ViewTransitionInstance` as an argument so you don't need a
ref to animate it.

```js
<ViewTransition onEnter={inst => inst.new.animate(keyframes, options)}>
```

The timing of this event is after the View Transition's `ready` state
which means that's too late to do any changes to the View Transition's
snapshots but now both the new and old pseudo-elements are ready to
animate.

The order of `onExit` is parent first, where as the others are child
first. This mimics effect mount/unmount.

I implement this by adding to a queue in the commit phase and then call
it while we're finishing up the commit. This is after layout effects but
before passive effects since passive effects fire after the animation is
`finished`.
2025-01-12 13:16:54 -05:00
Sebastian Markbåge
0bf1f39ec6
View Transition Refs (#32038)
This adds refs to View Transition that can resolve to an instance of:

```js
type ViewTransitionRef = {
  name: string,
  group: Animatable,
  imagePair: Animatable,
  old: Animatable,
  new: Animatable,
}
```

Animatable is a type that has `animate(keyframes, options)` and
`getAnimations()` on it. It's the interface that exists on Element that
lets you start animations on it. These ones are like that but for the
four pseudo-elements created by the view transition.

If a name changes, then a new ref is created. That way if you hold onto
a ref during an exit animation spawned by the name change, you can keep
calling functions on it. It will keep referring to the old name rather
than the new name.

This allows imperative control over the animations instead of using CSS
for this.

```js
const viewTransition = ref.current;
const groupAnimation = viewTransition.group.animate(keyframes, options);
const imagePairAnimation = viewTransition.imagePair.animate(keyframes, options);
const oldAnimation = viewTransition.old.animate(keyframes, options);
const newAnimation = viewTransition.new.animate(keyframes, options);
```

The downside of using this API is that it doesn't work with SSR so for
SSR rendered animations they'll fallback to the CSS. You could use this
for progressive enhancement though.

Note: In this PR the ref only controls one DOM node child but there can
be more than one DOM node in the ViewTransition fragment and they are
just left to their defaults. We could try something like making the
`animate()` function apply to multiple children but that could lead to
some weird consequences and the return value would be difficult to
merge. We could try to maintain an array of Animatable that updates with
how ever many things are currently animating but that makes the API more
complicated to use for the simple case. Conceptually this should be like
a fragment so we would ideally combine the multiple children into a
single isolate if we could. Maybe one day the same name could be applied
to multiple children to create a single isolate. For now I think I'll
just leave it like this and you're really expect to just use it with one
DOM node. If you have more than one they just get the default animations
from CSS.

Using this is a little tricky due timing. In this fixture I just use a
layout effect plus rAF to get into the right timing after the
startViewTransition is ready. In the future I'll add an event that fires
when View Transitions heuristics fire with the right timing.
2025-01-10 11:51:37 -05:00
Sebastian Markbåge
056073de4c
[Fiber] Support moveBefore at the top level of a container (#32036)
Parity with appendChild and insertBefore. This allows reordering at the
root while preserving state.
2025-01-09 16:37:00 -05:00
Ruslan Lesiutin
d2a1b8854d
fix[DevTools/Tree]: only scroll to item when panel is visible (#32018)
Stacked on https://github.com/facebook/react/pull/31968. See commit on
top.

Fixes an issue with bank tree view, when we are scrolling to an item
while syncing user DOM selection. This should only have an effect on
browser extension. Added events with `extension` prefix will only be
emitted in browser extension implementation, for other implementations
`useExtensionComponentsPanelVisibility` will return constant `true`
value.

Before:


https://github.com/user-attachments/assets/82667c16-d495-4346-af0a-7ed22ff89cfc


After:


https://github.com/user-attachments/assets/a5d223fd-0328-44f0-af68-5c3863f1efee
2025-01-09 18:38:49 +00:00
Ruslan Lesiutin
f2813ee33d
[DevTools] feat[Tree]: set initial scroll offset when inspected element index is set (#31968)
Stacked on https://github.com/facebook/react/pull/31956. See [commit on
top](ecb8df4175).

Use `initialScrollOffset` prop for `FixedSizeList` from `react-window`.
This happens when user selects an element in built-in Elements panel in
DevTools, and then opens Components panel from React DevTools - elements
will be synced and corresponding React Element will be pre-selected, we
just have to scroll to its position now.
2025-01-09 18:21:55 +00:00
Ruslan Lesiutin
d634548243
DevTools: merge element fields in TreeStateContext (#31956)
Stacked on https://github.com/facebook/react/pull/31892, see commit on
top.

For some reason, there were 2 fields different fields for essentially
same thing: `selectedElementID` and `inspectedElementID`. Basically, the
change is:
```
selectedElementID -> inspectedElementID
selectedElementIndex -> inspectedElementIndex
```

I have a theory that it was due to previously used async approach around
element inspection, and the whole `InspectedElementView` was wrapped in
`Suspense`.
2025-01-09 18:13:24 +00:00
Ruslan Lesiutin
54cfa95d3a
DevTools: fix initial host instance selection (#31892)
Related: https://github.com/facebook/react/pull/31342

This fixes RDT behaviour when some DOM element was pre-selected in
built-in browser's Elements panel, and then Components panel of React
DevTools was opened for the first time. With this change, React DevTools
will correctly display the initial state of the Components Tree with the
corresponding React Element (if possible) pre-selected.

Previously, we would only subscribe listener when `TreeContext` is
mounted, but this only happens when user opens one of React DevTools
panels for the first time. With this change, we keep state inside
`Store`, which is created when Browser DevTools are opened. Later,
`TreeContext` will use it for initial state value.

Planned next changes:
1. Merge `inspectedElementID` and `selectedElementID`, I have no idea
why we need both.
2. Fix issue with `AutoSizer` rendering a blank container.
2025-01-09 18:01:07 +00:00
Ruslan Lesiutin
d5f3c50f58
chore[DevTools/Tree]: don't pre-select root element and remove unused code (#32015)
In this PR:
1. Removed unused code in `Tree.js`
2. Removed logic for pre-selecting first element in the tree by default.
This is a bit clowny, because it steals focus and resets scroll, when
user attempts to expand / collapse some subtree.
3. Updated comments around
1c381c588a.

To expand on 3-rd point, for someone who might be reading this in the
future:
We can't guarantee focus of RDT browser extension panels, because they
are hosted in an `iframe`. Attempting to fire any events won't have any
result, user action with the corresponding `iframe` is required in order
for this `iframe` to obtain focus.

The only reason why built-in Elements panel in Chrome works correctly is
because it is supported natively somewhere in Chrome / Chrome DevTools.
Also, when you select an element on the application page, Chrome will
make sure that Elements panel opened, which technically guarantees focus
inside DevTools window and Elements panel subview.

As of today, we can't navigate user to third-party extensions panels,
there is no API for this, hence no ability to guarantee focused RDT
panels.
2025-01-09 18:00:30 +00:00
Ruslan Lesiutin
79dcc47191
chore[DevTools/TraceUpdates]: display names by default (#32019)
Feature was added in https://github.com/facebook/react/pull/31577, lets
enable it by default. Note: for gradual rollout with React Native, we
will continue to emit different event, requires some changes on React
Native side to support this.

I have plans to make this feature to be accessible via browser context
menu, which has really limited API. In order to minimize potential
divergence, lets make this the default state for the feature.
2025-01-09 18:00:09 +00:00
mofeiZ
d16fe4be5b
[compiler] Playground qol: shared compilation option directives with tests (#32012)
- Adds @compilationMode(all|infer|syntax|annotation) and
@panicMode(none) directives. This is now shared with our test infra
- Playground still defaults to `infer` mode while tests default to `all`
mode
- See added fixture tests
2025-01-09 12:38:16 -05:00
lauren
8932ca32f4
[playground] Partially revert #32009 (#32035)
I had forgotten that our default error reporting threshold was `none`
due to the fact that build pipelines should not throw errors. This
resets it back to throwing on all errors which mostly is the same as the
eslint plugin.

Closes #32014.
2025-01-09 12:21:05 -05:00
Sebastian Markbåge
c4595ca4c8
Add ViewTransitionComponent to Stacks and DevTools (#32034)
Just adding the name so it shows up.

(Note that no experimental ones are added to the list of filters atm.
Including SuspenseList etc.)
2025-01-09 11:33:34 -05:00
Pieter De Baets
74ea0c73a2
Remove enableGetInspectorDataForInstanceInProduction flag (#32033)
## Summary

Callers for this method has been removed in
65bda54232,
so these methods no longer need to be conditionally exported and the
feature flag can be removed.

## How did you test this change?

Flow fabric/native
2025-01-09 15:51:58 +00:00
Sebastian Markbåge
1506685f0e
Suspensey Fonts for View Transition (#32031)
Fonts flickering in while loading can be disturbing to any transition
but especially View Transitions. Even if they don't cause layout thrash
- the paint thrash is bad enough. We might add Suspensey fonts to all
Transitions in the future but it's especially a no-brainer for View
Transitions.

We need to apply mutations to the DOM first to know whether that will
trigger new fonts to load. For general Suspensey fonts, we'd have to
revert the commit by applying mutations in reverse to return to the
previous state. For View Transitions, since a snapshot is already
frozen, we can freeze the screen while we're waiting for the font at no
extra cost. It does mean that the page isn't responsive during this time
but we should only block this for a short period anyway.

The timeout needs to be short enough that it doesn't cause too much of
an issue when it's a new load and slow, yet long enough that you have a
chance to load it. Otherwise we wait for no reason. The assumption here
is that you likely have either cached the font or preloaded it earlier -
or you're on an extremely fast connection. This case is for optimizing
the high end experience.

Before:


https://github.com/user-attachments/assets/e0acfffe-fa49-40d6-82c3-5b08760175fb

After:


https://github.com/user-attachments/assets/615a03d3-9d6b-4eb1-8bd5-182c4c37a628

Note that since the Navigation is blocked on the font now the browser
spinner shows up while the font is loading.
2025-01-09 10:33:44 -05:00
Sebastian Markbåge
fd9cfa416f
Execute layout phase before after mutation phase inside view transition (#32029)
This allows mutations and scrolling in the layout phase to be counted
towards the mutation. This would maybe not be the case for gestures but
it is useful for fire-and-forget.

This also avoids the issue that if you resolve navigation in
useLayoutEffect that it ends up dead locked.

It also means that useLayoutEffect does not observe the scroll
restoration and in fact, the scroll restoration would win over any
manual scrolling in layout effects. For better or worse, this is more in
line with how things worked before and how it works in popstate. So it's
less of a breaking change. This does mean that we can't unify the after
mutation phase with the layout phase though.

To do this we need split out flushSpawnedWork from the flushLayoutEffect
call.

Spawned work from setState inside the layout phase is done outside and
not counted towards the transition. They're sync updates and so are not
eligible for their own View Transitions. It's also tricky to support
this since it's unclear what things like exits in that update would
mean. This work will still be able to mutate the live DOM but it's just
not eligible to trigger new transitions or adjust the target of those.

One difference between popstate is that this spawned work is after
scroll restoration. So any scrolling spawned from a second pass would
now win over scroll restoration.

Another consequence of this change is that you can't safely animate
pseudo elements in useLayoutEffect. We'll introduce a better event for
that anyway.
2025-01-08 19:13:25 -05:00
Sebastian Markbåge
800c9db22e
ViewTransitions in Navigation (#32028)
This adds navigation support to the View Transition fixture using both
`history.pushState/popstate` and the Navigation API models.

Because `popstate` does scroll restoration synchronously at the end of
the event, but `startViewTransition` cannot start synchronously, it
would observe the "old" state as after applying scroll restoration. This
leads to weird artifacts. So we intentionally do not support View
Transitions in `popstate`. If it suspends anyway for some other reason,
then scroll restoration is broken anyway and then it is supported. We
don't have to do anything here because this is already how things worked
because the sync `popstate` special case already included the sync lane
which opts it out of View Transitions.

For the Navigation API, scroll restoration can be blocked. The best way
to do this is to resolve the Navigation API promise after React has
applied its mutation. We can detect if there's currently any pending
navigation and wait to resolve the `startViewTransition` until it
finishes and any scroll restoration has been applied.


https://github.com/user-attachments/assets/f53b3282-6315-4513-b3d6-b8981d66964e

There is a subtle thing here. If we read the viewport metrics before
scroll restoration has been applied, then we might assume something is
or isn't going to be within the viewport incorrectly. This is evident on
the "Slide In from Left" example. When we're going forward to that page
we shift the scroll position such that it's going to appear in the
viewport. If we did this before applying scroll restoration, it would
not animate because it wasn't in the viewport then. Therefore, we need
to run the after mutation phase after scroll restoration.

A consequence of this is that you have to resolve Navigation in
`useInsertionEffect` as otherwise it leads to a deadlock (which
eventually gets broken by `startViewTransition`'s timeout of 10
seconds). Another consequence is that now `useLayoutEffect` observes the
restored state. However, I think what we'll likely do is move the layout
phase to before the after mutation phase which also ensures that
auto-scrolling inside `useLayoutEffect` are considered in the viewport
measurements as well.
2025-01-08 18:57:54 -05:00
Sebastian Markbåge
98418e8902
[Fiber] Suspend the commit while we wait for the previous View Transition to finish (#32002)
Stacked on #31975.

View Transitions cannot handle interruptions in that if you start a new
one before the previous one has finished, it just stops and then
restarts. It doesn't seamlessly transition into the new transition.

This is generally considered a bad thing but I actually think it's quite
good for fire-and-forget animations (gestures is another story). There
are too many examples of bad animations in fast interactions because the
scenario wasn't predicted. Like overlapping toasts or stacked layers
that look bad. The only case interrupts tend to work well is when you do
a strict reversal of an animation like returning to the page you just
left or exiting a modal just being opened. However, we're limited by the
platform even in that regard.

I think one reason interruptions have traditionally been seen as good is
because it's hard if you have a synchronous framework to not interrupt
since your application state has already moved on. We don't have that
limitation since we can suspend commits. We can do all the work to
prepare for the next commit by rendering while the animation is going
but then delay the commit until the previous one finishes.

Another technical limitation earlier animation libraries suffered from
is only have the option to either interrupt or sequence animations since
it's modeling just one change set. Like showing one toast at a time.
That's bad. We don't have that limitation because we can interrupt a
previously suspended commit and start working on a new one instead.
That's what we do for suspended transitions in general. The net effect
is that we batch the commits.

Therefore if you get multiple toasts flying in fast, they can animate as
a batch in together all at once instead of overlapping slightly or being
staggered. Interruptions (often) bad. Staggered animations bad. Batched
animations good.

This PR stashes the currently active View Transition with an expando on
the container that's animating (currently always document). This is
similar to what we do with event handlers etc. We reason we do this with
an expando is that if you have multiple Reacts on the same page they
need to wait for each other. However, one of those might also be the SSR
runtime. So this lets us wait for the SSR runtime's animations to finish
before starting client ones. This could really be a more generic name
since this should ideally be shared across frameworks. It's kind of
strange that this property doesn't already exist in the DOM given that
there can only be one. It would be useful to be able to coordinate this
across libraries.
2025-01-08 13:36:57 -05:00
Sebastian Markbåge
38127b2815
[Fiber] Support only View Transitions v2 (#31996)
Stacked on #31975.

We're going to recommend that the primary way you style a View
Transition is using a View Transition Class (and/or Type). These are
only available in the View Transitions v2 spec. When they're not
available it's better to fallback to just not animating instead of
animating with the wrong styling rules applied.

This is already widely supported in Chrome and Safari 18.2. Safari 18.2
usage is still somewhat low but it's rolling out quickly as we speak.

A way to detect this is by just passing the object form to
`startViewTransition` which throws if it's an earlier version. The
object form is required for `types` but luckily classes rolled out at
the same time. Therefore we're only indirectly detecting class support.

This means that in practice Safari 18.0 and 18.1 won't animate. We could
try to only apply the feature detection if you're actually using classes
or types, but that would create an unfortunate ecosystem burden to try
to support names. It also leads to flaky effects when only some
animations work. Better to just disable them all.

Firefox has yet to ship anything. We'll have to look out for how the
feature detection happens there and if they roll things out in different
order but if you ship late, you deal with web compat as the ball lies.
2025-01-08 13:22:15 -05:00
Sebastian Markbåge
3a5496b3f5
[Fiber] Use className on <ViewTransition> to assign view-transition-class (#31999)
Stacked on #31975.

This is the primary way we recommend styling your View Transitions since
it allows for reusable styling such as a CSS library specializing in
View Transitions in a way that's composable and without naming
conflicts. E.g.

```js
<ViewTransition className="enter-slide-in exit-fade-out update-cross-fade">
```

This doesn't change the HTML `class` attribute. It's not a CSS class.
Instead it assign the `view-transition-class` style prop of the
underlying DOM node while it's transitioning.

You can also just use `<div style={{viewTransitionClass: ...}}>` on the
DOM node but it's convenient to control the Transition completely from
the outside and conceptually we're transitioning the whole fragment. You
can even make Transition components that just wraps existing components.
`<RevealTransition><Component /></RevealTransition>` this way.

Since you can also have multiple wrappers for different circumstances it
allows React's heuristics to use different classes for different
scenarios. We'll likely add more options like configuring different
classes for different `types` or scenarios that can't be described by
CSS alone.

## CSS Modules

```js
import transitions from './transitions.module.css';

<ViewTransition className={transitions.bounceIn}>...</ViewTransition>
```

CSS Modules works well with this strategy because you can have globally
unique namespaces and define your transitions in the CSS modules as a
library that you can import. [As seen in the fixture
here.](8b91b37bb8 (diff-b4d9854171ffdac4d2c01be92a5eff4f8e9e761e6af953094f99ca243b054a85R11))

I did notice an unfortunate bug in how CSS Modules (at least in Webpack)
generates class names. Sometimes the `+` character is used in the hash
of the class name which is not valid for `view-transition-class` and so
it breaks. I had to rename my class names until the hash yielded
something different to work around it. Ideally that bug gets fixed soon.

## className, rly?

`className` isn't exactly the most loved property name, however, I'm
using `className` here too for consistency. Even though in this case
there's no direct equivalent DOM property name. The CSS property is
named `viewTransitionClass`, but the "viewTransition" prefix is implied
by the Component it is on in this case. For most people the fact that
this is actually a different namespace than other CSS classes doesn't
matter. You'll most just use a CSS library anyway and conceptually
you're just assigning classes the same way as `className` on a DOM node.

But if we ever rename the `class` prop then we can do that for this one
as well.
2025-01-08 13:22:06 -05:00
Sebastian Markbåge
a4d122f2d1
Add <ViewTransition> Component (#31975)
This will provide the opt-in for using [View
Transitions](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API)
in React.

View Transitions only trigger for async updates like `startTransition`,
`useDeferredValue`, Actions or `<Suspense>` revealing from fallback to
content. Synchronous updates provide an opt-out but also guarantee that
they commit immediately which View Transitions can't.

There's no need to opt-in to View Transitions at the "cause" side like
event handlers or actions. They don't know what UI will change and
whether that has an animated transition described.

Conceptually the `<ViewTransition>` component is like a DOM fragment
that transitions its children in its own isolate/snapshot. The API works
by wrapping a DOM node or inner component:

```js
import {ViewTransition} from 'react';

<ViewTransition><Component /></ViewTransition>
```

The default is `name="auto"` which will automatically assign a
`view-transition-name` to the inner DOM node. That way you can add a
View Transition to a Component without controlling its DOM nodes styling
otherwise.

A difference between this and the browser's built-in
`view-transition-name: auto` is that switching the DOM nodes within the
`<ViewTransition>` component preserves the same name so this example
cross-fades between the DOM nodes instead of causing an exit and enter:

```js
<ViewTransition>{condition ? <ComponentA /> : <ComponentB />}</ViewTransition>
```

This becomes especially useful with `<Suspense>` as this example
cross-fades between Skeleton and Content:

```js
<ViewTransition>
  <Suspense fallback={<Skeleton />}>
    <Content />
  </Suspense>
</ViewTransition>
```

Where as this example triggers an exit of the Skeleton and an enter of
the Content:

```js
<Suspense fallback={<ViewTransition><Skeleton /></ViewTransition>}>
  <ViewTransition><Content /></ViewTransition>
</Suspense>
```

Managing instances and keys becomes extra important.

You can also specify an explicit `name` property for example for
animating the same conceptual item from one page onto another. However,
best practices is to property namespace these since they can easily
collide. It's also useful to add an `id` to it if available.

```js
<ViewTransition name="my-shared-view">
```

The model in general is the same as plain `view-transition-name` except
React manages a set of heuristics for when to apply it. A problem with
the naive View Transitions model is that it overly opts in every
boundary that *might* transition into transitioning. This is leads to
unfortunate effects like things floating around when unrelated updates
happen. This leads the whole document to animate which means that
nothing is clickable in the meantime. It makes it not useful for smaller
and more local transitions. Best practice is to add
`view-transition-name` only right before you're about to need to animate
the thing. This is tricky to manage globally on complex apps and is not
compositional. Instead we let React manage when a `<ViewTransition>`
"activates" and add/remove the `view-transition-name`. This is also when
React calls `startViewTransition` behind the scenes while it mutates the
DOM.

I've come up with a number of heuristics that I think will make a lot
easier to coordinate this. The principle is that only if something that
updates that particular boundary do we activate it. I hope that one day
maybe browsers will have something like these built-in and we can remove
our implementation.

A `<ViewTransition>` only activates if:

- If a mounted Component renders a `<ViewTransition>` within it outside
the first DOM node, and it is within the viewport, then that
ViewTransition activates as an "enter" animation. This avoids inner
"enter" animations trigger when the parent mounts.
- If an unmounted Component had a `<ViewTransition>` within it outside
the first DOM node, and it was within the viewport, then that
ViewTransition activates as an "exit" animation. This avoids inner
"exit" animations triggering when the parent unmounts.
- If an explicitly named `<ViewTransition name="...">` is deep within an
unmounted tree and one with the same name appears in a mounted tree at
the same time, then both are activated as a pair, but only if they're
both in the viewport. This avoids these triggering "enter" or "exit"
animations when going between parents that don't have a pair.
- If an already mounted `<ViewTransition>` is visible and a DOM
mutation, that might affect how it's painted, happens within its
children but outside any nested `<ViewTransition>`. This allows it to
"cross-fade" between its updates.
- If an already mounted `<ViewTransition>` resizes or moves as the
result of direct DOM nodes siblings changing or moving around. This
allows insertion, deletion and reorders into a list to animate all
children. It is only within one DOM node though, to avoid unrelated
changes in the parent to trigger this. If an item is outside the
viewport before and after, then it's skipped to avoid things flying
across the screen.
- If a `<ViewTransition>` boundary changes size, due to a DOM mutation
within it, then the parent activates (or the root document if there are
no more parents). This ensures that the container can cross-fade to
avoid abrupt relayout. This can be avoided by using absolutely
positioned children. When this can avoid bubbling to the root document,
whatever is not animating is still responsive to clicks during the
transition.

Conceptually each DOM node has its own default that activates the parent
`<ViewTransition>` or no transition if the parent is the root. That
means that if you add a DOM node like `<div><ViewTransition><Component
/></ViewTransition></div>` this won't trigger an "enter" animation since
it was the div that was added, not the ViewTransition. Instead, it might
cause a cross-fade of the parent ViewTransition or no transition if it
had no parent. This ensures that only explicit boundaries perform coarse
animations instead of every single node which is really the benefit of
the View Transitions model. This ends up working out well for simple
cases like switching between two pages immediately while transitioning
one floating item that appears on both pages. Because only the floating
item transitions by default.

Note that it's possible to add manual `view-transition-name` with CSS or
`style={{ viewTransitionName: 'auto' }}` that always transitions as long
as something else has a `<ViewTransition>` that activates. For example a
`<ViewTransition>` can wrap a whole page for a cross-fade but inside of
it an explicit name can be added to something to ensure it animates as a
move when something relates else changes its layout. Instead of just
cross-fading it along with the Page which would be the default.

There's more PRs coming with some optimizations, fixes and expanded
APIs. This first PR explores the above core heuristic.

---------

Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>
2025-01-08 12:11:18 -05:00
Sebastian Markbåge
e30c6693e4
[Fiber] Delete isMounted internals (#31966)
The public API has been deleted a long time ago so this should be unused
unless it's used by hacks. It should be replaced with an
effect/lifecycle that manually tracks this if you need it.

The problem with this API is how the timing implemented because it
requires Placement/Hydration flags to be cleared too early. In fact,
that's why we also have a separate PlacementDEV flag that works
differently.


https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberCommitWork.js#L2157-L2165

We should be able to remove this code now.
2025-01-08 12:08:30 -05:00
Ricky
379089d288
[flags] remove enableDeferRootSchedulingToMicrotask (#32008)
Wait for me to merge, but this has landed everywhere and is ready to
remove.
2025-01-08 12:03:01 -05:00
Ruslan Lesiutin
426872679d
chore[react-devtools-shell]: disable warnings in dev server overlay (#32005)
Disables warnings Webpack DevServer overlay, which is used by React
DevTools shell.

We are testing against `react-native-web` in this shell, and it installs
older versions of the `react-dom` package, and there are some expected
discrepancies between it and `react-dom` from source.

Before:
![Screenshot 2025-01-07 at 12 50
21](https://github.com/user-attachments/assets/ba7d435e-3265-4446-9994-6a77c6d3d4ef)

After:
![Screenshot 2025-01-07 at 12 49
47](https://github.com/user-attachments/assets/cb45d07c-f561-496a-b76f-bdce3154ab88)
2025-01-08 12:14:52 +00:00
Ricky
a160102f3a
[tests] Remove to*Dev matchers (#31989)
Based off: https://github.com/facebook/react/pull/31988

<img width="741" alt="Screenshot 2025-01-06 at 12 52 08 AM"
src="https://github.com/user-attachments/assets/29b159ca-66d4-441f-8817-dd2db66d1edb"
/>

it is done
2025-01-07 14:17:14 -05:00
lauren
f892dabd8c
[ci] Make gh workflow names consistent (#32010)
Super minor change to keep our naming scheme consistent for gh workflows
2025-01-07 12:09:10 -05:00
lauren
6efbc0897f
[playground] Use default compiler config (#32009)
The playground's compilation mode is currently set to 'all' along with
reporting all errors.

This tends to be misleading since people usually expect a 1:1 match
between how the playground works with what the compiler does in their
codebase, eg https://github.com/reactwg/react-compiler/discussions/51.
2025-01-07 11:53:27 -05:00
Ricky
7b402084af
Fix notify target, add lines (#32006) 2025-01-07 09:34:18 -05:00
Ricky
3314162535
bot for pr notifications (#31985)
Going to take some testing to get this right
2025-01-06 17:57:19 -05:00
Ricky
e0c893f51d
[assert helpers] ServerIntegration tests (#31988)
Based off: https://github.com/facebook/react/pull/31986
2025-01-06 14:13:03 -05:00
Ricky
6b865330f4
[assert helpers] react-reconciler (#31986)
Based off: https://github.com/facebook/react/pull/31984
2025-01-06 14:12:53 -05:00
Ricky
83be48b9de
[tests] fix hidden use() warnings (#31984)
`spyOnDev` is such a footgun.
2025-01-06 14:12:35 -05:00
Sebastian Markbåge
defffdbba4
[Fiber] Don't work on scheduled tasks while we're in an async commit but flush it eagerly if we're sync (#31987)
This is a follow up to #31930 and a prerequisite for #31975.

With View Transitions, the commit phase becomes async which means that
other work can sneak in between. We need to be resilient to that.

This PR first refactors the flushMutationEffects and flushLayoutEffects
to use module scope variables to track its arguments so we can defer
them. It shares these with how we were already doing it for
flushPendingEffects.

We also track how far along the commit phase we are so we know what we
have left to flush.

Then callers of flushPassiveEffects become flushPendingEffects. That
helper synchronously flushes any remaining phases we've yet to commit.
That ensure that things are at least consistent if that happens.

Finally, when we are using a scheduled task, we don't do any work. This
ensures that we're not flushing any work too early if we could've
deferred it. This still ensures that we always do flush it before
starting any new work on any root so new roots observe the committed
state.

There are some unfortunate effects that could happen from allowing
things to flush eagerly. Such as if a flushSync sneaks in before
startViewTransition, it'll skip the animation. If it's during a
suspensey font it'll start the transition before the font has loaded
which might be better than breaking flushSync. It'll also potentially
flush passive effects inside the startViewTransition which should
typically be ok.
2025-01-06 11:30:53 -05:00
lauren
3ce77d55a2
[playground:ci] Don't install compiler deps twice (#31995)
The compiler playground already installs the compiler's dependencies in
a preinstall step. No need to repeat it in CI.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31995).
* __->__ #31995
* #31994
2025-01-06 11:02:23 -05:00
lauren
11df5224e6
[rcr] Generate ts defs (#31994)
This was accidentally removed in the esbuild transition.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31994).
* #31995
* __->__ #31994
2025-01-06 11:01:38 -05:00