From c3443ddcff6e5c6cddb4dd38e5e93bafd3496ecf Mon Sep 17 00:00:00 2001 From: Elias Trizotti Date: Thu, 3 Nov 2022 08:07:18 -0300 Subject: [PATCH 01/16] Convert to typescript --- .../FocusTrap/{FocusTrap.js => FocusTrap.tsx} | 17 +++++++++-------- .../{FocusTrap.d.ts => FocusTrap.types.ts} | 0 packages/mui-base/src/FocusTrap/index.js | 1 - .../src/FocusTrap/{index.d.ts => index.ts} | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) rename packages/mui-base/src/FocusTrap/{FocusTrap.js => FocusTrap.tsx} (96%) rename packages/mui-base/src/FocusTrap/{FocusTrap.d.ts => FocusTrap.types.ts} (100%) delete mode 100644 packages/mui-base/src/FocusTrap/index.js rename packages/mui-base/src/FocusTrap/{index.d.ts => index.ts} (53%) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.js b/packages/mui-base/src/FocusTrap/FocusTrap.tsx similarity index 96% rename from packages/mui-base/src/FocusTrap/FocusTrap.js rename to packages/mui-base/src/FocusTrap/FocusTrap.tsx index c03dd7cc57fda9..53bc9937670fcd 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.js +++ b/packages/mui-base/src/FocusTrap/FocusTrap.tsx @@ -7,6 +7,7 @@ import { unstable_useForkRef as useForkRef, unstable_ownerDocument as ownerDocument, } from '@mui/utils'; +import { FocusTrapProps } from './FocusTrap.types'; // Inspired by https://github.com/focus-trap/tabbable const candidatesSelector = [ @@ -21,7 +22,7 @@ const candidatesSelector = [ '[contenteditable]:not([contenteditable="false"])', ].join(','); -function getTabIndex(node) { +function getTabIndex(node: HTMLElement): number { const tabindexAttr = parseInt(node.getAttribute('tabindex'), 10); if (!Number.isNaN(tabindexAttr)) { @@ -47,7 +48,7 @@ function getTabIndex(node) { return node.tabIndex; } -function isNonTabbableRadio(node) { +function isNonTabbableRadio(node: HTMLElement): boolean { if (node.tagName !== 'INPUT' || node.type !== 'radio') { return false; } @@ -67,7 +68,7 @@ function isNonTabbableRadio(node) { return roving !== node; } -function isNodeMatchingSelectorFocusable(node) { +function isNodeMatchingSelectorFocusable(node: HTMLElement): boolean { if ( node.disabled || (node.tagName === 'INPUT' && node.type === 'hidden') || @@ -78,7 +79,7 @@ function isNodeMatchingSelectorFocusable(node) { return true; } -function defaultGetTabbable(root) { +function defaultGetTabbable(root: HTMLElement): Array { const regularTabNodes = []; const orderedTabNodes = []; @@ -108,14 +109,14 @@ function defaultGetTabbable(root) { .concat(regularTabNodes); } -function defaultIsEnabled() { +function defaultIsEnabled(): boolean { return true; } /** * Utility component that locks focus inside the component. */ -function FocusTrap(props) { +function FocusTrap(props: FocusTrapProps) { const { children, disableAutoFocus = false, @@ -302,7 +303,7 @@ function FocusTrap(props) { }; }, [disableAutoFocus, disableEnforceFocus, disableRestoreFocus, isEnabled, open, getTabbable]); - const onFocus = (event) => { + const onFocus = (event: FocusEvent) => { if (nodeToRestore.current === null) { nodeToRestore.current = event.relatedTarget; } @@ -315,7 +316,7 @@ function FocusTrap(props) { } }; - const handleFocusSentinel = (event) => { + const handleFocusSentinel = (event: FocusEvent) => { if (nodeToRestore.current === null) { nodeToRestore.current = event.relatedTarget; } diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.d.ts b/packages/mui-base/src/FocusTrap/FocusTrap.types.ts similarity index 100% rename from packages/mui-base/src/FocusTrap/FocusTrap.d.ts rename to packages/mui-base/src/FocusTrap/FocusTrap.types.ts diff --git a/packages/mui-base/src/FocusTrap/index.js b/packages/mui-base/src/FocusTrap/index.js deleted file mode 100644 index 5131ffa9169f3a..00000000000000 --- a/packages/mui-base/src/FocusTrap/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './FocusTrap'; diff --git a/packages/mui-base/src/FocusTrap/index.d.ts b/packages/mui-base/src/FocusTrap/index.ts similarity index 53% rename from packages/mui-base/src/FocusTrap/index.d.ts rename to packages/mui-base/src/FocusTrap/index.ts index 6206922d9df2c0..d1078fd4502d3a 100644 --- a/packages/mui-base/src/FocusTrap/index.d.ts +++ b/packages/mui-base/src/FocusTrap/index.ts @@ -1,2 +1,2 @@ export { default } from './FocusTrap'; -export * from './FocusTrap'; +export * from './FocusTrap.types'; \ No newline at end of file From 21e2737cef08169bcbe58561117cfc7b9b8db495 Mon Sep 17 00:00:00 2001 From: trizotti Date: Fri, 4 Nov 2022 09:43:19 -0300 Subject: [PATCH 02/16] Refactor --- packages/mui-base/src/FocusTrap/FocusTrap.tsx | 85 +++++++++++-------- .../mui-base/src/FocusTrap/FocusTrap.types.ts | 2 +- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.tsx index 53bc9937670fcd..07a3d859772364 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.tsx @@ -23,7 +23,8 @@ const candidatesSelector = [ ].join(','); function getTabIndex(node: HTMLElement): number { - const tabindexAttr = parseInt(node.getAttribute('tabindex'), 10); + + const tabindexAttr = parseInt(node.getAttribute('tabindex') || '', 10); if (!Number.isNaN(tabindexAttr)) { return tabindexAttr; @@ -48,7 +49,7 @@ function getTabIndex(node: HTMLElement): number { return node.tabIndex; } -function isNonTabbableRadio(node: HTMLElement): boolean { +function isNonTabbableRadio(node: HTMLInputElement): boolean { if (node.tagName !== 'INPUT' || node.type !== 'radio') { return false; } @@ -57,7 +58,7 @@ function isNonTabbableRadio(node: HTMLElement): boolean { return false; } - const getRadio = (selector) => node.ownerDocument.querySelector(`input[type="radio"]${selector}`); + const getRadio = (selector: string) => node.ownerDocument.querySelector(`input[type="radio"]${selector}`); let roving = getRadio(`[name="${node.name}"]:checked`); @@ -68,7 +69,7 @@ function isNonTabbableRadio(node: HTMLElement): boolean { return roving !== node; } -function isNodeMatchingSelectorFocusable(node: HTMLElement): boolean { +function isNodeMatchingSelectorFocusable(node: HTMLInputElement): boolean { if ( node.disabled || (node.tagName === 'INPUT' && node.type === 'hidden') || @@ -79,24 +80,30 @@ function isNodeMatchingSelectorFocusable(node: HTMLElement): boolean { return true; } -function defaultGetTabbable(root: HTMLElement): Array { - const regularTabNodes = []; - const orderedTabNodes = []; +function defaultGetTabbable(root: HTMLElement): HTMLElement[] { + interface OrderedTabNode { + documentOrder: number + tabIndex: number, + node: HTMLElement + } + + const regularTabNodes: Array = []; + const orderedTabNodes: Array = []; Array.from(root.querySelectorAll(candidatesSelector)).forEach((node, i) => { - const nodeTabIndex = getTabIndex(node); + const nodeTabIndex = getTabIndex(node as HTMLElement); - if (nodeTabIndex === -1 || !isNodeMatchingSelectorFocusable(node)) { + if (nodeTabIndex === -1 || !isNodeMatchingSelectorFocusable(node as HTMLInputElement)) { return; } if (nodeTabIndex === 0) { - regularTabNodes.push(node); + regularTabNodes.push(node as HTMLElement); } else { orderedTabNodes.push({ documentOrder: i, tabIndex: nodeTabIndex, - node, + node: node as HTMLElement }); } }); @@ -126,18 +133,19 @@ function FocusTrap(props: FocusTrapProps) { isEnabled = defaultIsEnabled, open, } = props; - const ignoreNextEnforceFocus = React.useRef(); - const sentinelStart = React.useRef(null); - const sentinelEnd = React.useRef(null); - const nodeToRestore = React.useRef(null); - const reactFocusEventTarget = React.useRef(null); + const ignoreNextEnforceFocus = React.useRef(false); + const sentinelStart = React.useRef(null); + const sentinelEnd = React.useRef(null); + const nodeToRestore = React.useRef(null); + const reactFocusEventTarget = React.useRef(null); // This variable is useful when disableAutoFocus is true. // It waits for the active element to move into the component to activate. const activated = React.useRef(false); - const rootRef = React.useRef(null); + const rootRef = React.useRef(null); + // @ts-expect-error TODO upstream fix const handleRef = useForkRef(children.ref, rootRef); - const lastKeydown = React.useRef(null); + const lastKeydown = React.useRef(null); React.useEffect(() => { // We might render an empty child. @@ -167,7 +175,7 @@ function FocusTrap(props: FocusTrapProps) { ].join('\n'), ); } - rootRef.current.setAttribute('tabIndex', -1); + rootRef.current.setAttribute('tabIndex', '-1'); } if (activated.current) { @@ -182,9 +190,9 @@ function FocusTrap(props: FocusTrapProps) { // in nodeToRestore.current being null. // Not all elements in IE11 have a focus method. // Once IE11 support is dropped the focus() call can be unconditional. - if (nodeToRestore.current && nodeToRestore.current.focus) { + if (nodeToRestore.current && (nodeToRestore.current as HTMLElement).focus) { ignoreNextEnforceFocus.current = true; - nodeToRestore.current.focus(); + (nodeToRestore.current as HTMLElement).focus(); } nodeToRestore.current = null; @@ -203,8 +211,9 @@ function FocusTrap(props: FocusTrapProps) { const doc = ownerDocument(rootRef.current); - const contain = (nativeEvent) => { + const contain = (nativeEvent: FocusEvent | null) => { const { current: rootElement } = rootRef; + // Cleanup functions are executed lazily in React 17. // Contain can be called between the component being unmounted and its cleanup function being run. if (rootElement === null) { @@ -236,26 +245,28 @@ function FocusTrap(props: FocusTrapProps) { return; } - let tabbable = []; + let tabbable: string[] | HTMLElement[] = []; if ( doc.activeElement === sentinelStart.current || doc.activeElement === sentinelEnd.current ) { - tabbable = getTabbable(rootRef.current); + tabbable = getTabbable(rootRef.current as HTMLElement); } - if (tabbable.length > 0) { + if (tabbable.length) { const isShiftTab = Boolean( lastKeydown.current?.shiftKey && lastKeydown.current?.key === 'Tab', ); const focusNext = tabbable[0]; const focusPrevious = tabbable[tabbable.length - 1]; - - if (isShiftTab) { - focusPrevious.focus(); - } else { - focusNext.focus(); + + if (typeof focusNext !== 'string' && typeof focusPrevious !== 'string') { + if (isShiftTab) { + focusPrevious.focus(); + } else { + focusNext.focus(); + } } } else { rootElement.focus(); @@ -263,7 +274,7 @@ function FocusTrap(props: FocusTrapProps) { } }; - const loopFocus = (nativeEvent) => { + const loopFocus = (nativeEvent: KeyboardEvent) => { lastKeydown.current = nativeEvent; if (disableEnforceFocus || !isEnabled() || nativeEvent.key !== 'Tab') { @@ -276,7 +287,9 @@ function FocusTrap(props: FocusTrapProps) { // We need to ignore the next contain as // it will try to move the focus back to the rootRef element. ignoreNextEnforceFocus.current = true; - sentinelEnd.current.focus(); + if(sentinelEnd.current) { + sentinelEnd.current.focus(); + } } }; @@ -290,8 +303,8 @@ function FocusTrap(props: FocusTrapProps) { // The whatwg spec defines how the browser should behave but does not explicitly mention any events: // https://html.spec.whatwg.org/multipage/interaction.html#focus-fixup-rule. const interval = setInterval(() => { - if (doc.activeElement.tagName === 'BODY') { - contain(); + if (doc.activeElement && doc.activeElement.tagName === 'BODY') { + contain(null); } }, 50); @@ -316,7 +329,7 @@ function FocusTrap(props: FocusTrapProps) { } }; - const handleFocusSentinel = (event: FocusEvent) => { + const handleFocusSentinel = (event: React.FocusEvent) => { if (nodeToRestore.current === null) { nodeToRestore.current = event.relatedTarget; } @@ -399,7 +412,7 @@ FocusTrap.propTypes /* remove-proptypes */ = { if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line - FocusTrap['propTypes' + ''] = exactProp(FocusTrap.propTypes); + (FocusTrap as any)['propTypes' + ''] = exactProp(FocusTrap.propTypes); } export default FocusTrap; diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.types.ts b/packages/mui-base/src/FocusTrap/FocusTrap.types.ts index 55cc5d7d88bfb6..e1239e00aec24a 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.types.ts +++ b/packages/mui-base/src/FocusTrap/FocusTrap.types.ts @@ -24,7 +24,7 @@ export interface FocusTrapProps { /** * A single child content element. */ - children: React.ReactElement; + children: React.ReactElement; /** * If `true`, the focus trap will not automatically shift focus to itself when it opens, and * replace it to the last focused element when it closes. From 1f77617bd28954ce2230ff08e4732792b9944095 Mon Sep 17 00:00:00 2001 From: trizotti Date: Fri, 4 Nov 2022 09:44:41 -0300 Subject: [PATCH 03/16] Prettier the code --- packages/mui-base/src/FocusTrap/FocusTrap.tsx | 20 +++++++++---------- packages/mui-base/src/FocusTrap/index.ts | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.tsx index 07a3d859772364..937568264358fa 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.tsx @@ -23,7 +23,6 @@ const candidatesSelector = [ ].join(','); function getTabIndex(node: HTMLElement): number { - const tabindexAttr = parseInt(node.getAttribute('tabindex') || '', 10); if (!Number.isNaN(tabindexAttr)) { @@ -58,7 +57,8 @@ function isNonTabbableRadio(node: HTMLInputElement): boolean { return false; } - const getRadio = (selector: string) => node.ownerDocument.querySelector(`input[type="radio"]${selector}`); + const getRadio = (selector: string) => + node.ownerDocument.querySelector(`input[type="radio"]${selector}`); let roving = getRadio(`[name="${node.name}"]:checked`); @@ -82,9 +82,9 @@ function isNodeMatchingSelectorFocusable(node: HTMLInputElement): boolean { function defaultGetTabbable(root: HTMLElement): HTMLElement[] { interface OrderedTabNode { - documentOrder: number - tabIndex: number, - node: HTMLElement + documentOrder: number; + tabIndex: number; + node: HTMLElement; } const regularTabNodes: Array = []; @@ -103,7 +103,7 @@ function defaultGetTabbable(root: HTMLElement): HTMLElement[] { orderedTabNodes.push({ documentOrder: i, tabIndex: nodeTabIndex, - node: node as HTMLElement + node: node as HTMLElement, }); } }); @@ -213,7 +213,7 @@ function FocusTrap(props: FocusTrapProps) { const contain = (nativeEvent: FocusEvent | null) => { const { current: rootElement } = rootRef; - + // Cleanup functions are executed lazily in React 17. // Contain can be called between the component being unmounted and its cleanup function being run. if (rootElement === null) { @@ -245,7 +245,7 @@ function FocusTrap(props: FocusTrapProps) { return; } - let tabbable: string[] | HTMLElement[] = []; + let tabbable: string[] | HTMLElement[] = []; if ( doc.activeElement === sentinelStart.current || doc.activeElement === sentinelEnd.current @@ -260,7 +260,7 @@ function FocusTrap(props: FocusTrapProps) { const focusNext = tabbable[0]; const focusPrevious = tabbable[tabbable.length - 1]; - + if (typeof focusNext !== 'string' && typeof focusPrevious !== 'string') { if (isShiftTab) { focusPrevious.focus(); @@ -287,7 +287,7 @@ function FocusTrap(props: FocusTrapProps) { // We need to ignore the next contain as // it will try to move the focus back to the rootRef element. ignoreNextEnforceFocus.current = true; - if(sentinelEnd.current) { + if (sentinelEnd.current) { sentinelEnd.current.focus(); } } diff --git a/packages/mui-base/src/FocusTrap/index.ts b/packages/mui-base/src/FocusTrap/index.ts index d1078fd4502d3a..93f0fe84d13a4a 100644 --- a/packages/mui-base/src/FocusTrap/index.ts +++ b/packages/mui-base/src/FocusTrap/index.ts @@ -1,2 +1,2 @@ export { default } from './FocusTrap'; -export * from './FocusTrap.types'; \ No newline at end of file +export * from './FocusTrap.types'; From 0e0d900fa28dc71f62549c70854327362e583541 Mon Sep 17 00:00:00 2001 From: trizotti Date: Fri, 4 Nov 2022 11:14:04 -0300 Subject: [PATCH 04/16] Fix type file, change array convention --- packages/mui-base/src/FocusTrap/FocusTrap.tsx | 16 ++++++++-------- .../mui-base/src/FocusTrap/FocusTrap.types.ts | 13 ------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.tsx index 937568264358fa..78e6e82eeb9a0f 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.tsx @@ -22,6 +22,12 @@ const candidatesSelector = [ '[contenteditable]:not([contenteditable="false"])', ].join(','); +interface OrderedTabNode { + documentOrder: number; + tabIndex: number; + node: HTMLElement; +} + function getTabIndex(node: HTMLElement): number { const tabindexAttr = parseInt(node.getAttribute('tabindex') || '', 10); @@ -81,14 +87,8 @@ function isNodeMatchingSelectorFocusable(node: HTMLInputElement): boolean { } function defaultGetTabbable(root: HTMLElement): HTMLElement[] { - interface OrderedTabNode { - documentOrder: number; - tabIndex: number; - node: HTMLElement; - } - - const regularTabNodes: Array = []; - const orderedTabNodes: Array = []; + const regularTabNodes: HTMLElement[] = []; + const orderedTabNodes: OrderedTabNode[] = []; Array.from(root.querySelectorAll(candidatesSelector)).forEach((node, i) => { const nodeTabIndex = getTabIndex(node as HTMLElement); diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.types.ts b/packages/mui-base/src/FocusTrap/FocusTrap.types.ts index e1239e00aec24a..e53f99b57ba871 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.types.ts +++ b/packages/mui-base/src/FocusTrap/FocusTrap.types.ts @@ -50,16 +50,3 @@ export interface FocusTrapProps { */ disableRestoreFocus?: boolean; } - -/** - * Utility component that locks focus inside the component. - * - * Demos: - * - * - [Focus Trap](https://mui.com/base/react-focus-trap/) - * - * API: - * - * - [FocusTrap API](https://mui.com/base/api/focus-trap/) - */ -export default function FocusTrap(props: FocusTrapProps): JSX.Element; From ce877d083de4b76fef9d6dd9f353c5929ef2d729 Mon Sep 17 00:00:00 2001 From: trizotti Date: Fri, 4 Nov 2022 13:29:13 -0300 Subject: [PATCH 05/16] Run yarn proptypes --- packages/mui-base/src/FocusTrap/FocusTrap.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.tsx index 78e6e82eeb9a0f..bcc531434e2cc8 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.tsx @@ -358,7 +358,7 @@ function FocusTrap(props: FocusTrapProps) { FocusTrap.propTypes /* remove-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" | // ---------------------------------------------------------------------- /** * A single child content element. @@ -408,7 +408,7 @@ FocusTrap.propTypes /* remove-proptypes */ = { * If `true`, focus is locked. */ open: PropTypes.bool.isRequired, -}; +} as any; if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line From 9cf4b841bc5cc34e8cb11a2c1ae6f85ccc177ad2 Mon Sep 17 00:00:00 2001 From: trizotti Date: Fri, 4 Nov 2022 13:45:08 -0300 Subject: [PATCH 06/16] Fix incorrect deletion --- packages/mui-base/src/FocusTrap/FocusTrap.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.tsx index bcc531434e2cc8..1f37fcd3e4dc09 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.tsx @@ -253,7 +253,7 @@ function FocusTrap(props: FocusTrapProps) { tabbable = getTabbable(rootRef.current as HTMLElement); } - if (tabbable.length) { + if (tabbable.length > 0) { const isShiftTab = Boolean( lastKeydown.current?.shiftKey && lastKeydown.current?.key === 'Tab', ); From f12e5395ec38c219e04571f0dfc5f83de4e3a794 Mon Sep 17 00:00:00 2001 From: trizotti Date: Mon, 28 Nov 2022 10:32:07 -0300 Subject: [PATCH 07/16] Convert test file to tsx --- .../{FocusTrap.test.js => FocusTrap.test.tsx} | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) rename packages/mui-base/src/FocusTrap/{FocusTrap.test.js => FocusTrap.test.tsx} (91%) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.test.js b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx similarity index 91% rename from packages/mui-base/src/FocusTrap/FocusTrap.test.js rename to packages/mui-base/src/FocusTrap/FocusTrap.test.tsx index ec70b8b528ca3f..2e08459f75813e 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.test.js +++ b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx @@ -5,22 +5,28 @@ import { act, createRenderer, screen } from 'test/utils'; import FocusTrap from '@mui/base/FocusTrap'; import Portal from '@mui/base/Portal'; +interface GenericProps { + [index: string]: any; +} + describe('', () => { const { clock, render } = createRenderer(); - let initialFocus = null; + let initialFocus: HTMLElement | null = null; beforeEach(() => { initialFocus = document.createElement('button'); initialFocus.tabIndex = 0; document.body.appendChild(initialFocus); act(() => { - initialFocus.focus(); + if (initialFocus) { + initialFocus.focus(); + } }); }); afterEach(() => { - document.body.removeChild(initialFocus); + document.body.removeChild(initialFocus as HTMLElement); }); it('should return focus to the root', () => { @@ -37,7 +43,9 @@ describe('', () => { expect(getByTestId('auto-focus')).toHaveFocus(); act(() => { - initialFocus.focus(); + if (initialFocus) { + initialFocus.focus(); + } }); expect(getByTestId('root')).toHaveFocus(); }); @@ -56,7 +64,9 @@ describe('', () => { expect(getByTestId('auto-focus')).toHaveFocus(); act(() => { - initialFocus.focus(); + if (initialFocus) { + initialFocus.focus(); + } }); expect(initialFocus).toHaveFocus(); @@ -77,7 +87,7 @@ describe('', () => { }); it('should warn if the root content is not focusable', () => { - const UnfocusableDialog = React.forwardRef((_, ref) =>
); + const UnfocusableDialog = React.forwardRef((_, ref) =>
); expect(() => { render( @@ -110,7 +120,7 @@ describe('', () => { }); it('does not steal focus from a portaled element if any prop but open changes', () => { - function Test(props) { + function Test(props: GenericProps) { return (
@@ -147,8 +157,11 @@ describe('', () => { }); it('undesired: lazy root does not get autofocus', () => { - let mountDeferredComponent; - const DeferredComponent = React.forwardRef(function DeferredComponent(props, ref) { + let mountDeferredComponent: React.DispatchWithoutAction; + const DeferredComponent = React.forwardRef(function DeferredComponent( + props, + ref, + ) { const [mounted, setMounted] = React.useReducer(() => true, false); mountDeferredComponent = setMounted; @@ -177,8 +190,8 @@ describe('', () => { }); it('does not bounce focus around due to sync focus-restore + focus-contain', () => { - const eventLog = []; - function Test(props) { + const eventLog: Array = []; + function Test(props: GenericProps) { return (
eventLog.push('blur')}> @@ -203,7 +216,7 @@ describe('', () => { }); it('does not focus if isEnabled returns false', () => { - function Test(props) { + function Test(props: GenericProps) { return (
@@ -230,7 +243,7 @@ describe('', () => { }); it('restores focus when closed', () => { - function Test(props) { + function Test(props: GenericProps) { return (
@@ -247,7 +260,7 @@ describe('', () => { }); it('undesired: enabling restore-focus logic when closing has no effect', () => { - function Test(props) { + function Test(props: GenericProps) { return (
@@ -265,7 +278,7 @@ describe('', () => { }); it('undesired: setting `disableRestoreFocus` to false before closing has no effect', () => { - function Test(props) { + function Test(props: GenericProps) { return (
@@ -341,7 +354,7 @@ describe('', () => {
-
, @@ -368,7 +381,7 @@ describe('', () => { }); it('should restore the focus', () => { - const Test = (props) => ( + const Test = (props: GenericProps) => (
From 900180711cdc2a809c863782668d68cab61c00b2 Mon Sep 17 00:00:00 2001 From: trizotti Date: Mon, 28 Nov 2022 10:39:37 -0300 Subject: [PATCH 08/16] Fix JsDocs of isEnabled --- packages/mui-base/src/FocusTrap/FocusTrap.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.types.ts b/packages/mui-base/src/FocusTrap/FocusTrap.types.ts index e53f99b57ba871..90a746233ea97e 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.types.ts +++ b/packages/mui-base/src/FocusTrap/FocusTrap.types.ts @@ -16,7 +16,7 @@ export interface FocusTrapProps { * It allows to toggle the open state without having to wait for a rerender when changing the `open` prop. * This prop should be memoized. * It can be used to support multiple focus trap mounted at the same time. - * @default function defaultIsEnabled() { + * @default function defaultIsEnabled(): boolean { * return true; * } */ From c8f778f42e6940b8ca8a696e955dfcf5b13cccf6 Mon Sep 17 00:00:00 2001 From: trizotti Date: Wed, 30 Nov 2022 09:39:37 -0300 Subject: [PATCH 09/16] Generate proptypes --- packages/mui-base/src/FocusTrap/FocusTrap.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.tsx index 1f37fcd3e4dc09..5606b320bea9ef 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.tsx @@ -399,7 +399,7 @@ FocusTrap.propTypes /* remove-proptypes */ = { * It allows to toggle the open state without having to wait for a rerender when changing the `open` prop. * This prop should be memoized. * It can be used to support multiple focus trap mounted at the same time. - * @default function defaultIsEnabled() { + * @default function defaultIsEnabled(): boolean { * return true; * } */ From 9bc7c636fdf4cbb24d9b4c812ecb02f9c639fb00 Mon Sep 17 00:00:00 2001 From: trizotti Date: Wed, 30 Nov 2022 09:47:28 -0300 Subject: [PATCH 10/16] generate docs --- docs/pages/base/api/focus-trap.json | 4 ++-- packages/mui-base/src/FocusTrap/FocusTrap.tsx | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/pages/base/api/focus-trap.json b/docs/pages/base/api/focus-trap.json index 15b34f7040272a..7152d1a72eb1f1 100644 --- a/docs/pages/base/api/focus-trap.json +++ b/docs/pages/base/api/focus-trap.json @@ -8,13 +8,13 @@ "getTabbable": { "type": { "name": "func" } }, "isEnabled": { "type": { "name": "func" }, - "default": "function defaultIsEnabled() {\n return true;\n}" + "default": "function defaultIsEnabled(): boolean {\n return true;\n}" } }, "name": "FocusTrap", "styles": { "classes": [], "globalClasses": {}, "name": null }, "spread": false, - "filename": "/packages/mui-base/src/FocusTrap/FocusTrap.js", + "filename": "/packages/mui-base/src/FocusTrap/FocusTrap.tsx", "inheritance": null, "demos": "", "cssComponent": false diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.tsx index 5606b320bea9ef..962387189a2ea3 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.tsx @@ -122,6 +122,14 @@ function defaultIsEnabled(): boolean { /** * Utility component that locks focus inside the component. + * + * Demos: + * + * - [Focus Trap](https://mui.com/base/react-focus-trap/) + * + * API: + * + * - [FocusTrap API](https://mui.com/base/api/focus-trap/) */ function FocusTrap(props: FocusTrapProps) { const { From 47c1f8fd7e2272a144460328281e674652d6c093 Mon Sep 17 00:00:00 2001 From: Trizotti Date: Wed, 14 Dec 2022 17:15:17 -0300 Subject: [PATCH 11/16] Update packages/mui-base/src/FocusTrap/FocusTrap.test.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove casting Co-authored-by: Michał Dudak Signed-off-by: Trizotti --- packages/mui-base/src/FocusTrap/FocusTrap.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx index ff70f47edc2ee1..8927971b2eeb47 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx @@ -26,7 +26,7 @@ describe('', () => { }); afterEach(() => { - document.body.removeChild(initialFocus as HTMLElement); + document.body.removeChild(initialFocus!); }); it('should return focus to the root', () => { From bc9e04216a83a44dcd505349986f52cbf9d36655 Mon Sep 17 00:00:00 2001 From: Trizotti Date: Wed, 14 Dec 2022 17:15:43 -0300 Subject: [PATCH 12/16] Update packages/mui-base/src/FocusTrap/FocusTrap.test.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Dudak Signed-off-by: Trizotti --- packages/mui-base/src/FocusTrap/FocusTrap.test.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx index 8927971b2eeb47..c6d43e885ae57c 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx @@ -19,9 +19,7 @@ describe('', () => { initialFocus.tabIndex = 0; document.body.appendChild(initialFocus); act(() => { - if (initialFocus) { - initialFocus.focus(); - } + initialFocus!.focus(); }); }); From e94345f787c65f367fa93f828ca428d2eba9f4bf Mon Sep 17 00:00:00 2001 From: Trizotti Date: Wed, 14 Dec 2022 17:16:29 -0300 Subject: [PATCH 13/16] Update packages/mui-base/src/FocusTrap/FocusTrap.test.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Dudak Signed-off-by: Trizotti --- packages/mui-base/src/FocusTrap/FocusTrap.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx index c6d43e885ae57c..706f896d338a82 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx @@ -188,7 +188,7 @@ describe('', () => { }); it('does not bounce focus around due to sync focus-restore + focus-contain', () => { - const eventLog: Array = []; + const eventLog: string[] = []; function Test(props: GenericProps) { return (
eventLog.push('blur')}> From bed8bd3128deb584b6aa93f218fe97ac7a8c295e Mon Sep 17 00:00:00 2001 From: Trizotti Date: Thu, 15 Dec 2022 09:53:15 -0300 Subject: [PATCH 14/16] Update packages/mui-base/src/FocusTrap/FocusTrap.test.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Dudak Signed-off-by: Trizotti --- packages/mui-base/src/FocusTrap/FocusTrap.test.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx index 706f896d338a82..5ddb15d8e27e58 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx @@ -62,9 +62,7 @@ describe('', () => { expect(getByTestId('auto-focus')).toHaveFocus(); act(() => { - if (initialFocus) { - initialFocus.focus(); - } + initialFocus!.focus(); }); expect(initialFocus).toHaveFocus(); From 726a541c38289442919d12dbeef8532f81b1deba Mon Sep 17 00:00:00 2001 From: Trizotti Date: Thu, 15 Dec 2022 09:53:21 -0300 Subject: [PATCH 15/16] Update packages/mui-base/src/FocusTrap/FocusTrap.test.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Dudak Signed-off-by: Trizotti --- packages/mui-base/src/FocusTrap/FocusTrap.test.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx index 5ddb15d8e27e58..bcab2c4cb14248 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx @@ -41,9 +41,7 @@ describe('', () => { expect(getByTestId('auto-focus')).toHaveFocus(); act(() => { - if (initialFocus) { - initialFocus.focus(); - } + initialFocus!.focus(); }); expect(getByTestId('root')).toHaveFocus(); }); From ceed9bdc16e3adc46896b73dae4b2fd6da7d0b86 Mon Sep 17 00:00:00 2001 From: trizotti Date: Thu, 15 Dec 2022 10:09:13 -0300 Subject: [PATCH 16/16] Fix wrong type --- packages/mui-base/src/FocusTrap/FocusTrap.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx index bcab2c4cb14248..a9704c772d70c4 100644 --- a/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx +++ b/packages/mui-base/src/FocusTrap/FocusTrap.test.tsx @@ -81,7 +81,7 @@ describe('', () => { }); it('should warn if the root content is not focusable', () => { - const UnfocusableDialog = React.forwardRef((_, ref) =>
); + const UnfocusableDialog = React.forwardRef((_, ref) =>
); expect(() => { render(