@samitouri / QOS-React / commits / 29d9d31844

[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 });