diff --git a/packages/react/src/components/Dialog/Dialog-story.js b/packages/react/src/components/Dialog/Dialog-story.js index c4066e5af415..72c526b19b02 100644 --- a/packages/react/src/components/Dialog/Dialog-story.js +++ b/packages/react/src/components/Dialog/Dialog-story.js @@ -6,8 +6,10 @@ */ import * as React from 'react'; +import ReactDOM from 'react-dom'; import { FocusScope } from './FocusScope'; import { Dialog } from '../Dialog'; +import { useId } from '../../internal/useId'; export default { title: 'Experimental/unstable_Dialog', @@ -67,11 +69,14 @@ export const Default = () => { export const DialogExample = () => { function Example() { const [open, setOpen] = React.useState(false); + const id = useId(); return ( - <> +
+ + {/* trigger */} - { - setOpen(false); - }}> -

Hello

- -
+ {/* full screen background */} + { + setOpen(false); + }} + /> + + {/* dialog */} + { + setOpen(false); + }} + style={{ + position: 'relative', + zIndex: 9999, + padding: '1rem', + background: 'white', + }}> +
+ Hello +
+
+ +
+ +
+ + ) : null} +
- +
); } return ; }; + +function FullPage(props) { + return ( +
+ ); +} + +function Portal({ children }) { + const [mountNode, setMountNode] = React.useState(null); + + React.useEffect(() => { + // TODO: should this be configurable???? + setMountNode(document.body); + }, []); + + if (mountNode) { + return ReactDOM.createPortal(children, mountNode); + } + + return null; +} diff --git a/packages/react/src/components/Dialog/FocusScope.js b/packages/react/src/components/Dialog/FocusScope.js index 152e6a39fef5..ed2bf9aeb91a 100644 --- a/packages/react/src/components/Dialog/FocusScope.js +++ b/packages/react/src/components/Dialog/FocusScope.js @@ -87,7 +87,9 @@ function useRestoreFocus(container) { element.removeEventListener('focusout', onFocusOut); if (containsFocus.current === true) { - focus(initialActiveElement); + setTimeout(() => { + focus(initialActiveElement); + }, 0); } }; }, []); @@ -141,6 +143,7 @@ const FocusScope = React.forwardRef(function FocusScope(props, forwardRef) { return ( <> { - if (open) { - // + const changes = []; + const queue = Array.from(document.body.childNodes); + + while (queue.length !== 0) { + const node = queue.shift(); + + // If a node is the modal (dialogRef), do nothing + if (node === dialogRef.current) { + continue; + } + + // If a tree contains our `dialogRef`, traverse its children + if (node.contains(dialogRef.current)) { + queue.push(...Array.from(node.childNodes)); + continue; + } + + // If a node is a bumper, do nothing + if ( + node.hasAttribute('data-dialog-bumper') && + (dialogRef.current.previousSibling === node || + dialogRef.current.nextSibling === node) + ) { + continue; + } + + if (node.hasAttribute('aria-hidden') || node.hasAttribute('inert')) { + continue; + } + + // what is aria-hidden === 'false' + + // Otherwise, set it to inert and set aria-hidden to true + node.setAttribute('aria-hidden', 'true'); + node.setAttribute('inert', ''); + + changes.push(node); } - }, [open]); - - if (open) { - return ( - - - - {children} - - - ); - } - return null; + return () => { + changes.forEach((node) => { + node.removeAttribute('inert'); + // This mutation needs to be asynchronous to allow the polyfill time to + // observe the change and allow mutations to occur + // https://github.com/WICG/inert#performance-and-gotchas + setTimeout(() => { + node.removeAttribute('aria-hidden'); + }, 0); + }); + }; + }, []); + + return ( + + {children} + + ); }); Dialog.propTypes = {}; @@ -88,25 +109,4 @@ if (__DEV__) { Dialog.displayName = 'Dialog'; } -function Portal({ children, ...rest }) { - return
{children}
; -} - -function FullPage(props) { - return ( -
- ); -} - export { Dialog };