[DOM] Copy `source` onto the synthetic toggle event (#37389)
`ToggleEvent` carries a `source` property pointing at the control that opened or closed a popover, but `ToggleEventInterface` only lists `newState` and `oldState`, so the synthetic event never copies it. An `onToggle` handler reads `event.source` as `undefined` even when the native event has it. Adding `source` to the interface copies it off the native event the same way `newState` and `oldState` are copied.
Lazizbek Ergashev committed
Aug 27, 2026 at 00:45 UTC
29d9d3184484b03cb0369e0494617207df777b7a
2 files changed
+37
packages/react-dom-bindings/src/events/SyntheticEvent.js
+1
@@ -609,6 +609,7 @@ const ToggleEventInterface: EventInterfaceType = {
609
...EventInterface,
610
newState: 0,
611
oldState: 0,
612
+ source: 0,
613
};
614
export const SyntheticToggleEvent: $FlowFixMe =
615
createSyntheticEvent(ToggleEventInterface);
packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js
+36
@@ -15,6 +15,7 @@ class ToggleEvent extends Event {
15
super(type, eventInit);
16
this.newState = eventInit.newState;
17
this.oldState = eventInit.oldState;
18
+ this.source = eventInit.source;
19
}
20
}
21
@@ -623,4 +624,39 @@ describe('SimpleEventPlugin', function () {
624
const event = onSubmit.mock.calls[0][0];
625
expect(event.submitter).toBe(submitter);
626
});
627
+
628
+ it('includes the source in toggle events', async function () {
629
+ container = document.createElement('div');
630
+
631
+ const onToggle = jest.fn();
632
+ const root = ReactDOMClient.createRoot(container);
633
+ await act(() => {
634
+ root.render(
635
+ <>
636
+ <button popoverTarget="popover">Toggle popover</button>
637
+ <div id="popover" popover="" onToggle={onToggle}>
638
+ popover content
639
+ </div>
640
+ </>,
641
+ );
642
+ });
643
+
644
+ const source = container.querySelector('button');
645
+ const target = container.querySelector('#popover');
646
+ await act(() => {
647
+ target.dispatchEvent(
648
+ new ToggleEvent('toggle', {
649
+ bubbles: false,
650
+ cancelable: true,
651
+ oldState: 'closed',
652
+ newState: 'open',
653
+ source: source,
654
+ }),
655
+ );
656
+ });
657
+
658
+ expect(onToggle).toHaveBeenCalledTimes(1);
659
+ const event = onToggle.mock.calls[0][0];
660
+ expect(event.source).toBe(source);
661
+ });
662
});