diff --git a/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js b/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js
index 9a9c3786b7715..7310a9c49b955 100644
--- a/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js
@@ -13,6 +13,7 @@ let React;
let ReactDOM;
let Scheduler;
+let act;
describe('ReactDOMNativeEventHeuristic-test', () => {
let container;
@@ -23,6 +24,7 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
React = require('react');
ReactDOM = require('react-dom');
Scheduler = require('scheduler');
+ act = require('react-dom/test-utils').unstable_concurrentAct;
document.body.appendChild(container);
});
@@ -225,4 +227,68 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
// Therefore the form should have been submitted.
expect(formSubmitted).toBe(true);
});
+
+ // @gate experimental
+ // @gate enableDiscreteEventMicroTasks && enableNativeEventPriorityInference
+ it('mouse over should be user-blocking but not discrete', async () => {
+ const root = ReactDOM.unstable_createRoot(container);
+
+ const target = React.createRef(null);
+ function Foo() {
+ const [isHover, setHover] = React.useState(false);
+ React.useLayoutEffect(() => {
+ target.current.onmouseover = () => setHover(true);
+ });
+ return
{isHover ? 'hovered' : 'not hovered'}
;
+ }
+
+ await act(async () => {
+ root.render();
+ });
+ expect(container.textContent).toEqual('not hovered');
+
+ await act(async () => {
+ const mouseOverEvent = document.createEvent('MouseEvents');
+ mouseOverEvent.initEvent('mouseover', true, true);
+ dispatchAndSetCurrentEvent(target.current, mouseOverEvent);
+
+ // 3s should be enough to expire the updates
+ Scheduler.unstable_advanceTime(3000);
+ expect(Scheduler).toFlushExpired([]);
+ expect(container.textContent).toEqual('hovered');
+ });
+ });
+
+ // @gate experimental
+ // @gate enableDiscreteEventMicroTasks && enableNativeEventPriorityInference
+ it('mouse enter should be user-blocking but not discrete', async () => {
+ const root = ReactDOM.unstable_createRoot(container);
+
+ const target = React.createRef(null);
+ function Foo() {
+ const [isHover, setHover] = React.useState(false);
+ React.useLayoutEffect(() => {
+ target.current.onmouseenter = () => setHover(true);
+ });
+ return {isHover ? 'hovered' : 'not hovered'}
;
+ }
+
+ await act(async () => {
+ root.render();
+ });
+ expect(container.textContent).toEqual('not hovered');
+
+ await act(async () => {
+ // Note: React does not use native mouseenter/mouseleave events
+ // but we should still correctly determine their priority.
+ const mouseEnterEvent = document.createEvent('MouseEvents');
+ mouseEnterEvent.initEvent('mouseenter', true, true);
+ dispatchAndSetCurrentEvent(target.current, mouseEnterEvent);
+
+ // 3s should be enough to expire the updates
+ Scheduler.unstable_advanceTime(3000);
+ expect(Scheduler).toFlushExpired([]);
+ expect(container.textContent).toEqual('hovered');
+ });
+ });
});
diff --git a/packages/react-dom/src/events/DOMEventNames.js b/packages/react-dom/src/events/DOMEventNames.js
index 42d72bc61ac22..5c8b5916f3867 100644
--- a/packages/react-dom/src/events/DOMEventNames.js
+++ b/packages/react-dom/src/events/DOMEventNames.js
@@ -17,6 +17,8 @@ export type DOMEventName =
// 'animationend |
// 'animationstart' |
| 'beforeblur' // Not a real event. This is used by event experiments.
+ | 'beforeinput'
+ | 'blur'
| 'canplay'
| 'canplaythrough'
| 'cancel'
@@ -44,9 +46,12 @@ export type DOMEventName =
| 'encrypted'
| 'ended'
| 'error'
+ | 'focus'
| 'focusin'
| 'focusout'
+ | 'fullscreenchange'
| 'gotpointercapture'
+ | 'hashchange'
| 'input'
| 'invalid'
| 'keydown'
@@ -58,6 +63,8 @@ export type DOMEventName =
| 'loadedmetadata'
| 'lostpointercapture'
| 'mousedown'
+ | 'mouseenter'
+ | 'mouseleave'
| 'mousemove'
| 'mouseout'
| 'mouseover'
@@ -74,12 +81,15 @@ export type DOMEventName =
| 'pointerout'
| 'pointerover'
| 'pointerup'
+ | 'popstate'
| 'progress'
| 'ratechange'
| 'reset'
| 'scroll'
| 'seeked'
| 'seeking'
+ | 'select'
+ | 'selectstart'
| 'selectionchange'
| 'stalled'
| 'submit'
diff --git a/packages/react-dom/src/events/ReactDOMEventListener.js b/packages/react-dom/src/events/ReactDOMEventListener.js
index 358c303b7918b..0a89d2cb70a7f 100644
--- a/packages/react-dom/src/events/ReactDOMEventListener.js
+++ b/packages/react-dom/src/events/ReactDOMEventListener.js
@@ -397,6 +397,16 @@ export function getEventPriority(domEventName: DOMEventName): * {
// eslint-disable-next-line no-fallthrough
case 'beforeblur':
case 'afterblur':
+ // Not used by React but could be by user code:
+ // eslint-disable-next-line no-fallthrough
+ case 'beforeinput':
+ case 'blur':
+ case 'fullscreenchange':
+ case 'focus':
+ case 'hashchange':
+ case 'popstate':
+ case 'select':
+ case 'selectstart':
return InputDiscreteLanePriority;
case 'drag':
case 'dragenter':
@@ -413,6 +423,10 @@ export function getEventPriority(domEventName: DOMEventName): * {
case 'toggle':
case 'touchmove':
case 'wheel':
+ // Not used by React but could be by user code:
+ // eslint-disable-next-line no-fallthrough
+ case 'mouseenter':
+ case 'mouseleave':
return InputContinuousLanePriority;
default:
return DefaultLanePriority;