diff --git a/docs/pages/api-docs/click-away-listener.json b/docs/pages/api-docs/click-away-listener.json
index 635def8deaccae..7867d5969660d1 100644
--- a/docs/pages/api-docs/click-away-listener.json
+++ b/docs/pages/api-docs/click-away-listener.json
@@ -21,7 +21,7 @@
"name": "ClickAwayListener",
"styles": { "classes": [], "globalClasses": {}, "name": null },
"spread": false,
- "filename": "/packages/material-ui/src/ClickAwayListener/ClickAwayListener.js",
+ "filename": "/packages/material-ui/src/ClickAwayListener/ClickAwayListener.tsx",
"inheritance": null,
"demos": "
",
"styledComponent": true,
diff --git a/docs/scripts/buildApi.ts b/docs/scripts/buildApi.ts
index fee6dc982812cb..bca149010acd47 100644
--- a/docs/scripts/buildApi.ts
+++ b/docs/scripts/buildApi.ts
@@ -505,7 +505,21 @@ async function annotateComponentDefinition(context: {
if (babel.types.isIdentifier(babelPath.node.declaration)) {
const bindingId = babelPath.node.declaration.name;
const binding = babelPath.scope.bindings[bindingId];
- node = binding.path.parentPath.node;
+
+ // The JSDOC MUST be located at the declaration
+ if (babel.types.isFunctionDeclaration(binding.path.node)) {
+ // For function declarations the binding is equal to the declaration
+ // /**
+ // */
+ // function Component() {}
+ node = binding.path.node;
+ } else {
+ // For variable declarations the binding points to the declarator.
+ // /**
+ // */
+ // const Component = () => {}
+ node = binding.path.parentPath.node;
+ }
}
}
@@ -906,7 +920,7 @@ async function parseComponentSource(
// Ignore what we might have generated in `annotateComponentDefinition`
const annotatedDescriptionMatch = fullDescription.match(/(Demos|API):\r?\n\r?\n/);
if (annotatedDescriptionMatch !== null) {
- reactAPI.description = fullDescription.slice(0, annotatedDescriptionMatch.index);
+ reactAPI.description = fullDescription.slice(0, annotatedDescriptionMatch.index).trim();
}
return reactAPI;
diff --git a/docs/src/pages/components/button-group/SplitButton.tsx b/docs/src/pages/components/button-group/SplitButton.tsx
index fdeadef0293ae0..4a08b07caf0ed2 100644
--- a/docs/src/pages/components/button-group/SplitButton.tsx
+++ b/docs/src/pages/components/button-group/SplitButton.tsx
@@ -33,7 +33,7 @@ export default function SplitButton() {
setOpen((prevOpen) => !prevOpen);
};
- const handleClose = (event: React.MouseEvent) => {
+ const handleClose = (event: Event) => {
if (
anchorRef.current &&
anchorRef.current.contains(event.target as HTMLElement)
diff --git a/docs/src/pages/components/menus/MenuListComposition.tsx b/docs/src/pages/components/menus/MenuListComposition.tsx
index e820d14a32263c..9070c91154b883 100644
--- a/docs/src/pages/components/menus/MenuListComposition.tsx
+++ b/docs/src/pages/components/menus/MenuListComposition.tsx
@@ -28,7 +28,7 @@ export default function MenuListComposition() {
setOpen((prevOpen) => !prevOpen);
};
- const handleClose = (event: React.MouseEvent) => {
+ const handleClose = (event: Event | React.SyntheticEvent) => {
if (
anchorRef.current &&
anchorRef.current.contains(event.target as HTMLElement)
diff --git a/packages/material-ui-utils/src/ownerDocument.ts b/packages/material-ui-utils/src/ownerDocument.ts
index eed9b51474ccbf..39b1b60ff95a7c 100644
--- a/packages/material-ui-utils/src/ownerDocument.ts
+++ b/packages/material-ui-utils/src/ownerDocument.ts
@@ -1,3 +1,3 @@
-export default function ownerDocument(node: Node | undefined): Document {
+export default function ownerDocument(node: Node | null | undefined): Document {
return (node && node.ownerDocument) || document;
}
diff --git a/packages/material-ui/src/ClickAwayListener/ClickAwayListener.d.ts b/packages/material-ui/src/ClickAwayListener/ClickAwayListener.d.ts
deleted file mode 100644
index 4259dff9c598a9..00000000000000
--- a/packages/material-ui/src/ClickAwayListener/ClickAwayListener.d.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import * as React from 'react';
-
-export interface ClickAwayListenerProps {
- /**
- * The wrapped element.
- */
- children?: React.ReactNode;
- /**
- * If `true`, the React tree is ignored and only the DOM tree is considered.
- * This prop changes how portaled elements are handled.
- * @default false
- */
- disableReactTree?: boolean;
- /**
- * The mouse event to listen to. You can disable the listener by providing `false`.
- * @default 'onClick'
- */
- mouseEvent?: 'onClick' | 'onMouseDown' | 'onMouseUp' | false;
- /**
- * Callback fired when a "click away" event is detected.
- */
- onClickAway: (event: React.MouseEvent) => void;
- /**
- * The touch event to listen to. You can disable the listener by providing `false`.
- * @default 'onTouchEnd'
- */
- touchEvent?: 'onTouchStart' | 'onTouchEnd' | false;
-}
-
-/**
- * Listen for click events that occur somewhere in the document, outside of the element itself.
- * For instance, if you need to hide a menu when people click anywhere else on your page.
- *
- * Demos:
- *
- * - [Click Away Listener](https://material-ui.com/components/click-away-listener/)
- * - [Menus](https://material-ui.com/components/menus/)
- *
- * API:
- *
- * - [ClickAwayListener API](https://material-ui.com/api/click-away-listener/)
- */
-export default function ClickAwayListener(props: ClickAwayListenerProps): JSX.Element;
diff --git a/packages/material-ui/src/ClickAwayListener/ClickAwayListener.js b/packages/material-ui/src/ClickAwayListener/ClickAwayListener.tsx
similarity index 64%
rename from packages/material-ui/src/ClickAwayListener/ClickAwayListener.js
rename to packages/material-ui/src/ClickAwayListener/ClickAwayListener.tsx
index 735c990f09d823..3839c2de2ea41c 100644
--- a/packages/material-ui/src/ClickAwayListener/ClickAwayListener.js
+++ b/packages/material-ui/src/ClickAwayListener/ClickAwayListener.tsx
@@ -5,22 +5,64 @@ import ownerDocument from '../utils/ownerDocument';
import useForkRef from '../utils/useForkRef';
import useEventCallback from '../utils/useEventCallback';
-function mapEventPropToEvent(eventProp) {
- return eventProp.substring(2).toLowerCase();
+// TODO: return `EventHandlerName extends `on${infer EventName}` ? Lowercase : never` once generatePropTypes runs with TS 4.1
+function mapEventPropToEvent(
+ eventProp: ClickAwayMouseEventHandler | ClickAwayTouchEventHandler,
+): 'click' | 'mousedown' | 'mouseup' | 'touchstart' | 'touchend' {
+ return eventProp.substring(2).toLowerCase() as any;
}
-function clickedRootScrollbar(event, doc) {
+function clickedRootScrollbar(event: MouseEvent, doc: Document) {
return (
doc.documentElement.clientWidth < event.clientX ||
doc.documentElement.clientHeight < event.clientY
);
}
+type ClickAwayMouseEventHandler = 'onClick' | 'onMouseDown' | 'onMouseUp';
+type ClickAwayTouchEventHandler = 'onTouchStart' | 'onTouchEnd';
+
+export interface ClickAwayListenerProps {
+ /**
+ * The wrapped element.
+ */
+ children: React.ReactElement;
+ /**
+ * If `true`, the React tree is ignored and only the DOM tree is considered.
+ * This prop changes how portaled elements are handled.
+ * @default false
+ */
+ disableReactTree?: boolean;
+ /**
+ * The mouse event to listen to. You can disable the listener by providing `false`.
+ * @default 'onClick'
+ */
+ mouseEvent?: ClickAwayMouseEventHandler | false;
+ /**
+ * Callback fired when a "click away" event is detected.
+ */
+ onClickAway: (event: MouseEvent | TouchEvent) => void;
+ /**
+ * The touch event to listen to. You can disable the listener by providing `false`.
+ * @default 'onTouchEnd'
+ */
+ touchEvent?: ClickAwayTouchEventHandler | false;
+}
+
/**
* Listen for click events that occur somewhere in the document, outside of the element itself.
* For instance, if you need to hide a menu when people click anywhere else on your page.
+ *
+ * Demos:
+ *
+ * - [Click Away Listener](https://material-ui.com/components/click-away-listener/)
+ * - [Menus](https://material-ui.com/components/menus/)
+ *
+ * API:
+ *
+ * - [ClickAwayListener API](https://material-ui.com/api/click-away-listener/)
*/
-function ClickAwayListener(props) {
+function ClickAwayListener(props: ClickAwayListenerProps): JSX.Element {
const {
children,
disableReactTree = false,
@@ -29,7 +71,7 @@ function ClickAwayListener(props) {
touchEvent = 'onTouchEnd',
} = props;
const movedRef = React.useRef(false);
- const nodeRef = React.useRef(null);
+ const nodeRef = React.useRef(null);
const activatedRef = React.useRef(false);
const syntheticEventRef = React.useRef(false);
@@ -44,7 +86,11 @@ function ClickAwayListener(props) {
};
}, []);
- const handleRef = useForkRef(children.ref, nodeRef);
+ const handleRef = useForkRef(
+ // @ts-expect-error TODO upstream fix
+ children.ref,
+ nodeRef,
+ );
// The handler doesn't take event.defaultPrevented into account:
//
@@ -52,7 +98,7 @@ function ClickAwayListener(props) {
// clicking a checkbox to check it, hitting a button to submit a form,
// and hitting left arrow to move the cursor in a text input etc.
// Only special HTML elements have these default behaviors.
- const handleClickAway = useEventCallback((event) => {
+ const handleClickAway = useEventCallback((event: MouseEvent | TouchEvent) => {
// Given developers can stop the propagation of the synthetic event,
// we can only be confident with a positive value.
const insideReactTree = syntheticEventRef.current;
@@ -63,7 +109,11 @@ function ClickAwayListener(props) {
// 1. IE11 support, which trigger the handleClickAway even after the unbind
// 2. The child might render null.
// 3. Behave like a blur listener.
- if (!activatedRef.current || !nodeRef.current || clickedRootScrollbar(event, doc)) {
+ if (
+ !activatedRef.current ||
+ !nodeRef.current ||
+ ('clientX' in event && clickedRootScrollbar(event, doc))
+ ) {
return;
}
@@ -80,7 +130,14 @@ function ClickAwayListener(props) {
insideDOM = event.composedPath().indexOf(nodeRef.current) > -1;
} else {
insideDOM =
- !doc.documentElement.contains(event.target) || nodeRef.current.contains(event.target);
+ !doc.documentElement.contains(
+ // @ts-expect-error returns `false` as intended when not dispatched from a Node
+ event.target,
+ ) ||
+ nodeRef.current.contains(
+ // @ts-expect-error returns `false` as intended when not dispatched from a Node
+ event.target,
+ );
}
if (!insideDOM && (disableReactTree || !insideReactTree)) {
@@ -89,7 +146,7 @@ function ClickAwayListener(props) {
});
// Keep track of mouse/touch events that bubbled up through the portal.
- const createHandleSynthetic = (handlerName) => (event) => {
+ const createHandleSynthetic = (handlerName: string) => (event: React.SyntheticEvent) => {
syntheticEventRef.current = true;
const childrenPropsHandler = children.props[handlerName];
@@ -98,7 +155,10 @@ function ClickAwayListener(props) {
}
};
- const childrenProps = { ref: handleRef };
+ const childrenProps: { ref: React.Ref } & Pick<
+ React.DOMAttributes,
+ ClickAwayMouseEventHandler | ClickAwayTouchEventHandler
+ > = { ref: handleRef };
if (touchEvent !== false) {
childrenProps[touchEvent] = createHandleSynthetic(touchEvent);
@@ -150,7 +210,7 @@ function ClickAwayListener(props) {
ClickAwayListener.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
- // | To update them edit the d.ts file and run "yarn proptypes" |
+ // | To update them edit TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
* The wrapped element.
@@ -180,7 +240,7 @@ ClickAwayListener.propTypes = {
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line
- ClickAwayListener['propTypes' + ''] = exactProp(ClickAwayListener.propTypes);
+ (ClickAwayListener as any)['propTypes' + ''] = exactProp(ClickAwayListener.propTypes);
}
export default ClickAwayListener;
diff --git a/packages/material-ui/src/ClickAwayListener/index.js b/packages/material-ui/src/ClickAwayListener/index.js
deleted file mode 100644
index e207d41b085555..00000000000000
--- a/packages/material-ui/src/ClickAwayListener/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './ClickAwayListener';
diff --git a/packages/material-ui/src/ClickAwayListener/index.d.ts b/packages/material-ui/src/ClickAwayListener/index.ts
similarity index 100%
rename from packages/material-ui/src/ClickAwayListener/index.d.ts
rename to packages/material-ui/src/ClickAwayListener/index.ts
diff --git a/packages/material-ui/tsconfig.build.json b/packages/material-ui/tsconfig.build.json
index 32766f15f19783..3a28b86f0de3f7 100644
--- a/packages/material-ui/tsconfig.build.json
+++ b/packages/material-ui/tsconfig.build.json
@@ -10,7 +10,7 @@
"outDir": "build",
"rootDir": "./src"
},
- "include": ["src/**/*.ts"],
- "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"],
+ "include": ["./src/**/*.ts*"],
+ "exclude": ["src/**/*.spec.ts*", "src/**/*.test.ts*"],
"references": [{ "path": "../material-ui-unstyled/tsconfig.build.json" }]
}
diff --git a/scripts/generateProptypes.ts b/scripts/generateProptypes.ts
index e0d4bdb6d79a4e..418bb7c6f15bf6 100644
--- a/scripts/generateProptypes.ts
+++ b/scripts/generateProptypes.ts
@@ -350,6 +350,8 @@ async function run(argv: HandlerArgv) {
.filter((filePath) => {
return filePattern.test(filePath);
});
+ // May not be able to understand all files due to mismatch in TS versions.
+ // Check `programm.getSyntacticDiagnostics()` if referenced files could not be compiled.
const program = ttp.createTSProgram(files, tsconfig);
const promises = files.map>(async (tsFile) => {