Repository: facebook/react
Pull request: [react-dom] Include `submitter` in `submit` events



## Summary

[`SubmitEvent.submitter` is widely supported](https://developer.mozilla.org/en-US/docs/Web/API/SubmitEvent/submitter#browser_compatibility) so we should expose it. We assume `submitter` is supported since https://github.com/facebook/react/pull/29028.

This doesn't include a polyfill for older browsers.

Closes https://github.com/facebook/react/issues/33033

## How did you test this change?

- added tests which fail without adding `SubmitEventInterface`


Changed files:
filename: packages/react-dom-bindings/src/events/SyntheticEvent.js
patch: @@ -522,6 +522,17 @@ export const SyntheticPointerEvent: $FlowFixMe = createSyntheticEvent(
   PointerEventInterface,
 );
 
+/**
+ * @interface SubmitEvent
+ * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-submitevent-interface
+ */
+const SubmitEventInterface: EventInterfaceType = {
+  ...EventInterface,
+  submitter: 0,
+};
+export const SyntheticSubmitEvent: $FlowFixMe =
+  createSyntheticEvent(SubmitEventInterface);
+
 /**
  * @interface TouchEvent
  * @see http://www.w3.org/TR/touch-events/

filename: packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js
patch: @@ -27,6 +27,7 @@ import {
   SyntheticWheelEvent,
   SyntheticClipboardEvent,
   SyntheticPointerEvent,
+  SyntheticSubmitEvent,
   SyntheticToggleEvent,
 } from '../../events/SyntheticEvent';
 
@@ -162,6 +163,9 @@ function extractEvents(
     case 'pointerup':
       SyntheticEventCtor = SyntheticPointerEvent;
       break;
+    case 'submit':
+      SyntheticEventCtor = SyntheticSubmitEvent;
+      break;
     case 'toggle':
     case 'beforetoggle':
       // MDN claims <details> should not receive ToggleEvent contradicting the spec: https://html.spec.whatwg.org/multipage/indices.html#event-toggle

filename: packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js
patch: @@ -672,9 +672,10 @@ describe('ReactDOMEventListener', () => {
         reactEventType: 'submit',
         nativeEvent: 'submit',
         dispatch(node) {
-          const e = new Event('submit', {
+          const e = new SubmitEvent('submit', {
             bubbles: true,
             cancelable: true,
+            submitter: null,
           });
           node.dispatchEvent(e);
         },

filename: packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js
patch: @@ -591,4 +591,36 @@ describe('SimpleEventPlugin', function () {
       );
     });
   });
+
+  it('includes the submitter in submit events', async function () {
+    container = document.createElement('div');
+
+    const onSubmit = jest.fn(event => {
+      event.preventDefault();
+    });
+    const root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(
+        <form onSubmit={onSubmit}>
+          <button type="submit" id="submitter">
+            Submit
+          </button>
+        </form>,
+      );
+    });
+
+    const submitter = container.querySelector('#submitter');
+    const submitEvent = new SubmitEvent('submit', {
+      bubbles: true,
+      cancelable: true,
+      submitter: submitter,
+    });
+    await act(() => {
+      submitter.dispatchEvent(submitEvent);
+    });
+
+    expect(onSubmit).toHaveBeenCalledTimes(1);
+    const event = onSubmit.mock.calls[0][0];
+    expect(event.submitter).toBe(submitter);
+  });
 });