diff --git a/docs/pages/api-docs/unstable-trap-focus.md b/docs/pages/api-docs/unstable-trap-focus.md index 164d0b4202a1d0..18882c260fb8b0 100644 --- a/docs/pages/api-docs/unstable-trap-focus.md +++ b/docs/pages/api-docs/unstable-trap-focus.md @@ -31,6 +31,7 @@ Utility component that locks focus inside the component. | disableEnforceFocus | bool | false | If `true`, the trap focus will not prevent focus from leaving the trap focus while open.
Generally this should never be set to `true` as it makes the trap focus less accessible to assistive technologies, like screen readers. | | disableRestoreFocus | bool | false | If `true`, the trap focus will not restore focus to previously focused element once trap focus is hidden. | | getDoc* | func | | Return the document to consider. We use it to implement the restore focus between different browser documents. | +| getTabbable | func | | Returns an array of ordered tabbable nodes (i.e. in tab order) within the root. For instance, you can provide the "tabbable" npm dependency.

**Signature:**
`function(root: HTMLElement) => void`
| | isEnabled* | func | | Do we still want to enforce the focus? This prop helps nesting TrapFocus elements. | | open* | bool | | If `true`, focus is locked. | diff --git a/packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.d.ts b/packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.d.ts index a0d6caa2c878d7..375b9b260c6b76 100644 --- a/packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.d.ts +++ b/packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.d.ts @@ -11,6 +11,12 @@ export interface TrapFocusProps { * We use it to implement the restore focus between different browser documents. */ getDoc: () => Document; + /** + * Returns an array of ordered tabbable nodes (i.e. in tab order) within the root. + * For instance, you can provide the "tabbable" npm dependency. + * @param {HTMLElement} root + */ + getTabbable?: (root: HTMLElement) => string[]; /** * Do we still want to enforce the focus? * This prop helps nesting TrapFocus elements. diff --git a/packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.js b/packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.js index 6779b6fb3cff40..16946a8bb6af43 100644 --- a/packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.js +++ b/packages/material-ui/src/Unstable_TrapFocus/Unstable_TrapFocus.js @@ -5,6 +5,106 @@ import { exactProp, elementAcceptingRef } from '@material-ui/utils'; import ownerDocument from '../utils/ownerDocument'; import useForkRef from '../utils/useForkRef'; +// Inspired by https://github.com/focus-trap/tabbable +const candidatesSelector = [ + 'input', + 'select', + 'textarea', + 'a[href]', + 'button', + '[tabindex]', + 'audio[controls]', + 'video[controls]', + '[contenteditable]:not([contenteditable="false"])', +].join(','); + +function getTabIndex(node) { + const tabindexAttr = parseInt(node.getAttribute('tabindex'), 10); + + if (!Number.isNaN(tabindexAttr)) { + return tabindexAttr; + } + + // Browsers do not return `tabIndex` correctly for contentEditable nodes; + // https://bugs.chromium.org/p/chromium/issues/detail?id=661108&q=contenteditable%20tabindex&can=2 + // so if they don't have a tabindex attribute specifically set, assume it's 0. + // in Chrome,
,