diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js index 7dcf9e52be472..bfdded745aea0 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js @@ -796,6 +796,11 @@ function setProp( } break; } + case 'popover': + listenToNonDelegatedEvent('beforetoggle', domElement); + listenToNonDelegatedEvent('toggle', domElement); + setValueForAttribute(domElement, 'popover', value); + break; case 'xlinkActuate': setValueForNamespacedAttribute( domElement, @@ -2835,6 +2840,13 @@ export function diffHydratedProperties( } } + if (props.popover != null) { + // We listen to this event in case to ensure emulated bubble + // listeners still fire for the toggle event. + listenToNonDelegatedEvent('beforetoggle', domElement); + listenToNonDelegatedEvent('toggle', domElement); + } + if (props.onScroll != null) { listenToNonDelegatedEvent('scroll', domElement); } diff --git a/packages/react-dom-bindings/src/events/DOMEventNames.js b/packages/react-dom-bindings/src/events/DOMEventNames.js index 9145e316f9d1f..b9d298cd78ab1 100644 --- a/packages/react-dom-bindings/src/events/DOMEventNames.js +++ b/packages/react-dom-bindings/src/events/DOMEventNames.js @@ -18,6 +18,7 @@ export type DOMEventName = // 'animationstart' | | 'beforeblur' // Not a real event. This is used by event experiments. | 'beforeinput' + | 'beforetoggle' | 'blur' | 'canplay' | 'canplaythrough' diff --git a/packages/react-dom-bindings/src/events/DOMEventProperties.js b/packages/react-dom-bindings/src/events/DOMEventProperties.js index 55b3c4f987186..591489ad25b26 100644 --- a/packages/react-dom-bindings/src/events/DOMEventProperties.js +++ b/packages/react-dom-bindings/src/events/DOMEventProperties.js @@ -34,6 +34,7 @@ export const topLevelEventsToReactNames: Map = const simpleEventPluginEvents = [ 'abort', 'auxClick', + 'beforeToggle', 'cancel', 'canPlay', 'canPlayThrough', diff --git a/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js b/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js index ca89e1d1bd8ab..87907448125f7 100644 --- a/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js +++ b/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js @@ -220,6 +220,7 @@ export const mediaEventTypes: Array = [ // set them on the actual target element itself. This is primarily // because these events do not consistently bubble in the DOM. export const nonDelegatedEvents: Set = new Set([ + 'beforetoggle', 'cancel', 'close', 'invalid', diff --git a/packages/react-dom-bindings/src/events/ReactDOMEventListener.js b/packages/react-dom-bindings/src/events/ReactDOMEventListener.js index 237cde231176b..4ec8d3788f00e 100644 --- a/packages/react-dom-bindings/src/events/ReactDOMEventListener.js +++ b/packages/react-dom-bindings/src/events/ReactDOMEventListener.js @@ -345,6 +345,7 @@ export function getEventPriority(domEventName: DOMEventName): EventPriority { case 'select': case 'selectstart': return DiscreteEventPriority; + case 'beforetoggle': case 'drag': case 'dragenter': case 'dragexit': diff --git a/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js b/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js index b90ca9efdb32e..0dafb1898a58c 100644 --- a/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js @@ -1217,6 +1217,40 @@ describe('ReactDOMEventListener', () => { }); }); + it('onBeforeToggle Popover API', async () => { + await testEmulatedBubblingEvent({ + type: 'div', + targetProps: {popover: 'any'}, + reactEvent: 'onBeforeToggle', + reactEventType: 'beforetoggle', + nativeEvent: 'beforetoggle', + dispatch(node) { + const e = new Event('beforetoggle', { + bubbles: false, + cancelable: true, + }); + node.dispatchEvent(e); + }, + }); + }); + + it('onToggle Popover API', async () => { + await testEmulatedBubblingEvent({ + type: 'div', + targetProps: {popover: 'any'}, + reactEvent: 'onToggle', + reactEventType: 'toggle', + nativeEvent: 'toggle', + dispatch(node) { + const e = new Event('toggle', { + bubbles: false, + cancelable: true, + }); + node.dispatchEvent(e); + }, + }); + }); + it('onVolumeChange', async () => { await testEmulatedBubblingEvent({ type: 'video', @@ -1918,6 +1952,7 @@ describe('ReactDOMEventListener', () => { type={eventConfig.type} targetRef={targetRef} targetProps={{ + ...eventConfig.targetProps, [eventConfig.reactEvent]: e => { log.push('---- inner'); }, @@ -2084,11 +2119,10 @@ describe('ReactDOMEventListener', () => { { log.push('--- inner parent'); @@ -2317,6 +2351,7 @@ describe('ReactDOMEventListener', () => { type={eventConfig.type} targetRef={targetRef} targetProps={{ + ...eventConfig.targetProps, [eventConfig.reactEvent]: e => { e.stopPropagation(); // <--------- log.push('---- inner'); @@ -2654,6 +2689,7 @@ describe('ReactDOMEventListener', () => { } }} targetProps={{ + ...eventConfig.targetProps, [eventConfig.reactEvent]: e => { log.push('---- inner'); }, diff --git a/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap b/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap index 8dda81dcae42b..8179beb22e143 100644 --- a/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap +++ b/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap @@ -8,6 +8,7 @@ exports[`ReactTestUtils Simulate should have locally attached media events 1`] = "animationStart", "auxClick", "beforeInput", + "beforeToggle", "blur", "canPlay", "canPlayThrough", diff --git a/packages/react-dom/src/test-utils/ReactTestUtils.js b/packages/react-dom/src/test-utils/ReactTestUtils.js index daea520a61eca..f4bbfa8343eed 100644 --- a/packages/react-dom/src/test-utils/ReactTestUtils.js +++ b/packages/react-dom/src/test-utils/ReactTestUtils.js @@ -627,6 +627,7 @@ function makeSimulator(eventType) { // A one-time snapshot with no plans to update. We'll probably want to deprecate Simulate API. const simulatedEventTypes = [ + 'beforeToggle', 'blur', 'cancel', 'click',