Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions types/react-dom/test/react-dom-tests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -700,3 +700,66 @@ function cacheSignalTest() {
}
}
}

function formrelatedEventTests() {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved from the React tests. It makes more sense here since React tests don't have access to the full lib/dom.d.ts.

React.createElement("textarea", {
value: "5",
onChange: event => {
// createElement is not inferring props for textarea. JSX works though.
// $ExpectType EventTarget & HTMLElement
event.target;
// @ts-expect-error
event.target.value;
},
});

React.createElement("input", {
onChange: event => {
// $ExpectType EventTarget & HTMLInputElement
event.target;
// $ExpectType string
event.target.value;
},
});

<div
onChange={event => {
// Should be EventTarget since we don't know from where the change event bubbled.
// $ExpectType EventTarget & HTMLDivElement
event.target;
}}
/>;
<input
onChange={event => {
// $ExpectType EventTarget & HTMLInputElement
event.target;
// $ExpectType string
event.target.value;
}}
/>;
<select
onChange={event => {
// $ExpectType EventTarget & HTMLSelectElement
event.target;
// $ExpectType string
event.target.value;
}}
/>;
<textarea
onChange={event => {
// $ExpectType EventTarget & HTMLTextAreaElement
event.target;
// $ExpectType string
event.target.value;
}}
/>;

<form
onSubmit={event => {
// @ts-expect-error -- submitter is not yet exposed by React
event.submitter;
// $ExpectType EventTarget & HTMLFormElement
event.target;
}}
/>;
}
1 change: 1 addition & 0 deletions types/react/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface KeyboardEvent extends Event {}
interface MouseEvent extends Event {}
interface TouchEvent extends Event {}
interface PointerEvent extends Event {}
interface SubmitEvent extends Event {}
interface ToggleEvent extends Event {}
interface TransitionEvent extends Event {}
interface UIEvent extends Event {}
Expand Down
73 changes: 54 additions & 19 deletions types/react/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type NativeKeyboardEvent = KeyboardEvent;
type NativeMouseEvent = MouseEvent;
type NativeTouchEvent = TouchEvent;
type NativePointerEvent = PointerEvent;
type NativeSubmitEvent = SubmitEvent;
type NativeToggleEvent = ToggleEvent;
type NativeTransitionEvent = TransitionEvent;
type NativeUIEvent = UIEvent;
Expand Down Expand Up @@ -2065,15 +2066,28 @@ declare namespace React {
target: EventTarget & Target;
}

/**
* @deprecated FormEvent doesn't actually exist.
* You probably meant to use {@link ChangeEvent}, {@link InputEvent}, {@link SubmitEvent}, or just {@link SyntheticEvent} instead
* depending on the event type.
*/
interface FormEvent<T = Element> extends SyntheticEvent<T> {
}

interface InvalidEvent<T = Element> extends SyntheticEvent<T> {
target: EventTarget & T;
}

interface ChangeEvent<T = Element> extends SyntheticEvent<T> {
target: EventTarget & T;
/**
* change events bubble in React so their target is generally unknown.
* Only for form elements we know their target type because form events can't
* be nested.
* This type exists purely to narrow `target` for form elements. It doesn't
* reflect a DOM event. Change events are just fired as standard {@link SyntheticEvent}.
*/
interface ChangeEvent<CurrentTarget = Element, Target = Element> extends SyntheticEvent<CurrentTarget> {
// TODO: This is wrong for change event handlers on arbitrary. Should
// be EventTarget & Target, but kept for backward compatibility until React 20.
target: EventTarget & CurrentTarget;
}

interface InputEvent<T = Element> extends SyntheticEvent<T, NativeInputEvent> {
Expand Down Expand Up @@ -2143,6 +2157,13 @@ declare namespace React {
shiftKey: boolean;
}

interface SubmitEvent<T = Element> extends SyntheticEvent<T, NativeSubmitEvent> {
// Currently not exposed by Reat
// submitter: HTMLElement | null;
// SubmitEvents are always targetted at HTMLFormElements.
target: EventTarget & HTMLFormElement;
}

interface TouchEvent<T = Element> extends UIEvent<T, NativeTouchEvent> {
altKey: boolean;
changedTouches: TouchList;
Expand Down Expand Up @@ -2198,11 +2219,19 @@ declare namespace React {
type CompositionEventHandler<T = Element> = EventHandler<CompositionEvent<T>>;
type DragEventHandler<T = Element> = EventHandler<DragEvent<T>>;
type FocusEventHandler<T = Element> = EventHandler<FocusEvent<T>>;
/**
* @deprecated FormEventHandler doesn't actually exist.
* You probably meant to use {@link ChangeEventHandler}, {@link InputEventHandler}, {@link SubmitEventHandler}, or just {@link EventHandler} instead
* depending on the event type.
*/
type FormEventHandler<T = Element> = EventHandler<FormEvent<T>>;
type ChangeEventHandler<T = Element> = EventHandler<ChangeEvent<T>>;
type ChangeEventHandler<CurrentTarget = Element, Target = Element> = EventHandler<
ChangeEvent<CurrentTarget, Target>
>;
type InputEventHandler<T = Element> = EventHandler<InputEvent<T>>;
type KeyboardEventHandler<T = Element> = EventHandler<KeyboardEvent<T>>;
type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;
type SubmitEventHandler<T = Element> = EventHandler<SubmitEvent<T>>;
type TouchEventHandler<T = Element> = EventHandler<TouchEvent<T>>;
type PointerEventHandler<T = Element> = EventHandler<PointerEvent<T>>;
type UIEventHandler<T = Element> = EventHandler<UIEvent<T>>;
Expand Down Expand Up @@ -2256,19 +2285,19 @@ declare namespace React {
onBlur?: FocusEventHandler<T> | undefined;
onBlurCapture?: FocusEventHandler<T> | undefined;

// Form Events
onChange?: FormEventHandler<T> | undefined;
onChangeCapture?: FormEventHandler<T> | undefined;
// form related Events
onChange?: ChangeEventHandler<T> | undefined;
onChangeCapture?: ChangeEventHandler<T> | undefined;
onBeforeInput?: InputEventHandler<T> | undefined;
onBeforeInputCapture?: FormEventHandler<T> | undefined;
onInput?: FormEventHandler<T> | undefined;
onInputCapture?: FormEventHandler<T> | undefined;
onReset?: FormEventHandler<T> | undefined;
onResetCapture?: FormEventHandler<T> | undefined;
onSubmit?: FormEventHandler<T> | undefined;
onSubmitCapture?: FormEventHandler<T> | undefined;
onInvalid?: FormEventHandler<T> | undefined;
onInvalidCapture?: FormEventHandler<T> | undefined;
onBeforeInputCapture?: InputEventHandler<T> | undefined;
onInput?: InputEventHandler<T> | undefined;
onInputCapture?: InputEventHandler<T> | undefined;
onReset?: ReactEventHandler<T> | undefined;
onResetCapture?: ReactEventHandler<T> | undefined;
onSubmit?: SubmitEventHandler<T> | undefined;
onSubmitCapture?: SubmitEventHandler<T> | undefined;
onInvalid?: ReactEventHandler<T> | undefined;
onInvalidCapture?: ReactEventHandler<T> | undefined;

// Image Events
onLoad?: ReactEventHandler<T> | undefined;
Expand Down Expand Up @@ -3275,7 +3304,9 @@ declare namespace React {
value?: string | readonly string[] | number | undefined;
width?: number | string | undefined;

onChange?: ChangeEventHandler<T> | undefined;
// No other element dispatching change events can be nested in a <input>
// so we know the target will be a HTMLInputElement.
onChange?: ChangeEventHandler<T, HTMLInputElement> | undefined;
}

interface KeygenHTMLAttributes<T> extends HTMLAttributes<T> {
Expand Down Expand Up @@ -3440,7 +3471,9 @@ declare namespace React {
required?: boolean | undefined;
size?: number | undefined;
value?: string | readonly string[] | number | undefined;
onChange?: ChangeEventHandler<T> | undefined;
// No other element dispatching change events can be nested in a <select>
// so we know the target will be a HTMLSelectElement.
onChange?: ChangeEventHandler<T, HTMLSelectElement> | undefined;
}

interface SourceHTMLAttributes<T> extends HTMLAttributes<T> {
Expand Down Expand Up @@ -3492,7 +3525,9 @@ declare namespace React {
value?: string | readonly string[] | number | undefined;
wrap?: string | undefined;

onChange?: ChangeEventHandler<T> | undefined;
// No other element dispatching change events can be nested in a <textare>
// so we know the target will be a HTMLTextAreaElement.
onChange?: ChangeEventHandler<T, HTMLTextAreaElement> | undefined;
}

interface TdHTMLAttributes<T> extends HTMLAttributes<T> {
Expand Down
19 changes: 0 additions & 19 deletions types/react/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,25 +633,6 @@ function handler(e: React.MouseEvent) {

const keyboardExtendsUI: React.UIEventHandler = (e: React.KeyboardEvent) => {};

//
// The SyntheticEvent.target.value should be accessible for onChange
// --------------------------------------------------------------------------
Comment on lines -636 to -638
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test wasn't actually testing it. Just that you can cast event.target up to a more specific EventTarget e.g. HTMLTextAreaElement. react-dom tests now assert in more detail when the target can be narrowed and not.

class SyntheticEventTargetValue extends React.Component<{}, { value: string }> {
state: { value: string };
constructor(props: {}) {
super(props);
this.state = { value: "a" };
}
render() {
return React.createElement("textarea", {
value: this.state.value,
onChange: e => {
const target: HTMLTextAreaElement = e.target;
},
});
}
}

React.createElement("input", {
onChange: event => {
// `event.target` is guaranteed to be HTMLInputElement
Expand Down
1 change: 1 addition & 0 deletions types/react/ts5.0/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface KeyboardEvent extends Event {}
interface MouseEvent extends Event {}
interface TouchEvent extends Event {}
interface PointerEvent extends Event {}
interface SubmitEvent extends Event {}
interface ToggleEvent extends Event {}
interface TransitionEvent extends Event {}
interface UIEvent extends Event {}
Expand Down
73 changes: 54 additions & 19 deletions types/react/ts5.0/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type NativeKeyboardEvent = KeyboardEvent;
type NativeMouseEvent = MouseEvent;
type NativeTouchEvent = TouchEvent;
type NativePointerEvent = PointerEvent;
type NativeSubmitEvent = SubmitEvent;
type NativeToggleEvent = ToggleEvent;
type NativeTransitionEvent = TransitionEvent;
type NativeUIEvent = UIEvent;
Expand Down Expand Up @@ -2064,15 +2065,28 @@ declare namespace React {
target: EventTarget & Target;
}

/**
* @deprecated FormEvent doesn't actually exist.
* You probably meant to use {@link ChangeEvent}, {@link InputEvent}, {@link SubmitEvent}, or just {@link SyntheticEvent} instead
* depending on the event type.
*/
interface FormEvent<T = Element> extends SyntheticEvent<T> {
}

interface InvalidEvent<T = Element> extends SyntheticEvent<T> {
target: EventTarget & T;
}

interface ChangeEvent<T = Element> extends SyntheticEvent<T> {
target: EventTarget & T;
/**
* change events bubble in React so their target is generally unknown.
* Only for form elements we know their target type because form events can't
* be nested.
* This type exists purely to narrow `target` for form elements. It doesn't
* reflect a DOM event. React fires change events as {@link SyntheticEvent}.
*/
interface ChangeEvent<CurrentTarget = Element, Target = Element> extends SyntheticEvent<CurrentTarget> {
// TODO: This is wrong for change event handlers on arbitrary. Should
// be EventTarget & Target, but kept for backward compatibility until React 20.
target: EventTarget & CurrentTarget;
}

interface InputEvent<T = Element> extends SyntheticEvent<T, NativeInputEvent> {
Expand Down Expand Up @@ -2142,6 +2156,13 @@ declare namespace React {
shiftKey: boolean;
}

interface SubmitEvent<T = Element> extends SyntheticEvent<T, NativeSubmitEvent> {
// Currently not exposed by Reat
// submitter: HTMLElement | null;
// SubmitEvents are always targetted at HTMLFormElements.
target: EventTarget & HTMLFormElement;
}

interface TouchEvent<T = Element> extends UIEvent<T, NativeTouchEvent> {
altKey: boolean;
changedTouches: TouchList;
Expand Down Expand Up @@ -2197,11 +2218,19 @@ declare namespace React {
type CompositionEventHandler<T = Element> = EventHandler<CompositionEvent<T>>;
type DragEventHandler<T = Element> = EventHandler<DragEvent<T>>;
type FocusEventHandler<T = Element> = EventHandler<FocusEvent<T>>;
/**
* @deprecated FormEventHandler doesn't actually exist.
* You probably meant to use {@link ChangeEventHandler}, {@link InputEventHandler}, {@link SubmitEventHandler}, or just {@link EventHandler} instead
* depending on the event type.
*/
type FormEventHandler<T = Element> = EventHandler<FormEvent<T>>;
type ChangeEventHandler<T = Element> = EventHandler<ChangeEvent<T>>;
type ChangeEventHandler<CurrentTarget = Element, Target = Element> = EventHandler<
ChangeEvent<CurrentTarget, Target>
>;
type InputEventHandler<T = Element> = EventHandler<InputEvent<T>>;
type KeyboardEventHandler<T = Element> = EventHandler<KeyboardEvent<T>>;
type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;
type SubmitEventHandler<T = Element> = EventHandler<SubmitEvent<T>>;
type TouchEventHandler<T = Element> = EventHandler<TouchEvent<T>>;
type PointerEventHandler<T = Element> = EventHandler<PointerEvent<T>>;
type UIEventHandler<T = Element> = EventHandler<UIEvent<T>>;
Expand Down Expand Up @@ -2255,19 +2284,19 @@ declare namespace React {
onBlur?: FocusEventHandler<T> | undefined;
onBlurCapture?: FocusEventHandler<T> | undefined;

// Form Events
onChange?: FormEventHandler<T> | undefined;
onChangeCapture?: FormEventHandler<T> | undefined;
// form related Events
onChange?: ChangeEventHandler<T> | undefined;
onChangeCapture?: ChangeEventHandler<T> | undefined;
onBeforeInput?: InputEventHandler<T> | undefined;
onBeforeInputCapture?: FormEventHandler<T> | undefined;
onInput?: FormEventHandler<T> | undefined;
onInputCapture?: FormEventHandler<T> | undefined;
onReset?: FormEventHandler<T> | undefined;
onResetCapture?: FormEventHandler<T> | undefined;
onSubmit?: FormEventHandler<T> | undefined;
onSubmitCapture?: FormEventHandler<T> | undefined;
onInvalid?: FormEventHandler<T> | undefined;
onInvalidCapture?: FormEventHandler<T> | undefined;
onBeforeInputCapture?: InputEventHandler<T> | undefined;
onInput?: InputEventHandler<T> | undefined;
onInputCapture?: InputEventHandler<T> | undefined;
onReset?: ReactEventHandler<T> | undefined;
onResetCapture?: ReactEventHandler<T> | undefined;
onSubmit?: SubmitEventHandler<T> | undefined;
onSubmitCapture?: SubmitEventHandler<T> | undefined;
onInvalid?: ReactEventHandler<T> | undefined;
onInvalidCapture?: ReactEventHandler<T> | undefined;

// Image Events
onLoad?: ReactEventHandler<T> | undefined;
Expand Down Expand Up @@ -3274,7 +3303,9 @@ declare namespace React {
value?: string | readonly string[] | number | undefined;
width?: number | string | undefined;

onChange?: ChangeEventHandler<T> | undefined;
// No other element dispatching change events can be nested in a <input>
// so we know the target will be a HTMLInputElement.
onChange?: ChangeEventHandler<T, HTMLInputElement> | undefined;
}

interface KeygenHTMLAttributes<T> extends HTMLAttributes<T> {
Expand Down Expand Up @@ -3439,7 +3470,9 @@ declare namespace React {
required?: boolean | undefined;
size?: number | undefined;
value?: string | readonly string[] | number | undefined;
onChange?: ChangeEventHandler<T> | undefined;
// No other element dispatching change events can be nested in a <select>
// so we know the target will be a HTMLSelectElement.
onChange?: ChangeEventHandler<T, HTMLSelectElement> | undefined;
}

interface SourceHTMLAttributes<T> extends HTMLAttributes<T> {
Expand Down Expand Up @@ -3491,7 +3524,9 @@ declare namespace React {
value?: string | readonly string[] | number | undefined;
wrap?: string | undefined;

onChange?: ChangeEventHandler<T> | undefined;
// No other element dispatching change events can be nested in a <textarea>
// so we know the target will be a HTMLTextAreaElement.
onChange?: ChangeEventHandler<T, HTMLTextAreaElement> | undefined;
}

interface TdHTMLAttributes<T> extends HTMLAttributes<T> {
Expand Down
Loading