diff --git a/docs/pages/api-docs/unstable-trap-focus.json b/docs/pages/api-docs/unstable-trap-focus.json index 2ef84eebe536a6..cbcf29beb7a1e3 100644 --- a/docs/pages/api-docs/unstable-trap-focus.json +++ b/docs/pages/api-docs/unstable-trap-focus.json @@ -5,6 +5,7 @@ "disableEnforceFocus": { "type": { "name": "bool" } }, "disableRestoreFocus": { "type": { "name": "bool" } }, "getDoc": { "type": { "name": "func" }, "required": true }, + "getTabbable": { "type": { "name": "func" } }, "isEnabled": { "type": { "name": "func" }, "required": true }, "open": { "type": { "name": "bool" }, "required": true } }, diff --git a/docs/translations/api-docs/unstable-trap-focus/unstable-trap-focus.json b/docs/translations/api-docs/unstable-trap-focus/unstable-trap-focus.json index d0a38d8520235f..61e6362c9b7edd 100644 --- a/docs/translations/api-docs/unstable-trap-focus/unstable-trap-focus.json +++ b/docs/translations/api-docs/unstable-trap-focus/unstable-trap-focus.json @@ -6,6 +6,7 @@ "disableEnforceFocus": "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": "If true, the trap focus will not restore focus to previously focused element once trap focus is hidden.", "getDoc": "Return the document to consider. We use it to implement the restore focus between different browser documents.", + "getTabbable": "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": "Do we still want to enforce the focus? This prop helps nesting TrapFocus elements.", "open": "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,
,