Skip to content

Commit

Permalink
chore: check-in work
Browse files Browse the repository at this point in the history
  • Loading branch information
joshblack committed Aug 17, 2021
1 parent ad2f010 commit 761bc45
Show file tree
Hide file tree
Showing 9 changed files with 479 additions and 0 deletions.
Binary file not shown.
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"lodash.throttle": "^4.1.1",
"react-is": "^16.8.6",
"use-resize-observer": "^6.0.0",
"wicg-inert": "^3.1.1",
"window-or-global": "^1.0.1"
},
"devDependencies": {
Expand Down
104 changes: 104 additions & 0 deletions packages/react/src/components/Dialog/Dialog-story.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import * as React from 'react';
import { FocusScope } from './FocusScope';
import { Dialog } from '../Dialog';

export default {
title: 'Experimental/unstable_Dialog',
};

export const Default = () => {
function DemoComponent() {
const [open, setOpen] = React.useState(false);
const ref = React.useRef(null);

return (
<div
style={{
border: '1px solid black',
background: 'rgba(0, 0, 0, 0.1)',
padding: '1rem',
}}>
<button
type="button"
onClick={() => {
setOpen(true);
}}>
Open
</button>
{open ? (
<FocusScope>
<div>
<p>
Elit hic at labore culpa itaque fugiat. Consequuntur iure autem
autem officiis dolores facilis nulla earum! Neque quia nemo
sequi assumenda ratione officia Voluptate beatae eligendi
placeat nemo laborum, ratione.
</p>
<DemoComponent />
<button
ref={ref}
onClick={() => {
setOpen(false);
}}>
Close
</button>
</div>
</FocusScope>
) : null}
</div>
);
}

return (
<>
<DemoComponent />
<button>Hello</button>
</>
);
};

export const DialogExample = () => {
function Example() {
const [open, setOpen] = React.useState(false);
return (
<>
<div>
<button>First</button>
</div>
<button
type="button"
onClick={() => {
setOpen(true);
}}>
Open
</button>
<Dialog
open={open}
onDismiss={() => {
setOpen(false);
}}>
<h1>Hello</h1>
<button
type="button"
onClick={() => {
setOpen(false);
}}>
Close
</button>
</Dialog>
<div>
<button>Last</button>
</div>
</>
);
}

return <Example />;
};
185 changes: 185 additions & 0 deletions packages/react/src/components/Dialog/FocusScope.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import * as React from 'react';
import { useEvent } from '../../internal/useEvent';
import { match, keys } from '../../internal/keyboard';

function createFocusWalker(container) {
return document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
acceptNode(node) {
if (node.tabIndex >= 0 && !node.disabled) {
return NodeFilter.FILTER_ACCEPT;
}
return NodeFilter.FILTER_SKIP;
},
});
}

function createFocusScope(root) {
const focusScope = {
getFirstDescendant() {
const walker = createFocusWalker(root.current);
return walker.firstChild();
},
focusFirstDescendant() {
const walker = createFocusWalker(root.current);
const firstChild = walker.firstChild();
if (firstChild) {
focus(firstChild);
}
},
focusLastDescendant() {
const walker = createFocusWalker(root.current);
const lastChild = walker.lastChild();
if (lastChild) {
focus(lastChild);
}
},
};

return focusScope;
}

function useFocusScope(containerRef) {
const focusScope = React.useRef(null);

if (focusScope.current === null) {
focusScope.current = createFocusScope(containerRef);
}

return focusScope;
}

function useRestoreFocus(container) {
const containsFocus = React.useRef(false);

React.useEffect(() => {
const initialActiveElement = document.activeElement;

if (container.current && container.current.contains) {
containsFocus.current = container.current.contains(
document.activeElement
);
}

function onFocusIn() {
containsFocus.current = true;
}

function onFocusOut(event) {
if (container.current && container.current.contains) {
containsFocus.current = container.current.contains(event.relatedTarget);
}
}

const { current: element } = container;

element.addEventListener('focusin', onFocusIn);
element.addEventListener('focusout', onFocusOut);

return () => {
element.removeEventListener('focusin', onFocusIn);
element.removeEventListener('focusout', onFocusOut);

if (containsFocus.current === true) {
focus(initialActiveElement);
}
};
}, []);
}

function useRefs(refs) {
return React.useCallback((node) => {
refs.forEach((ref) => {
if (typeof ref === 'function') {
ref(node);
} else if (ref !== null && ref !== undefined) {
ref.current = node;
}
});
}, refs);
}

function useAutoFocus(getElementOrRef) {
const callbackRef = React.useRef(getElementOrRef);

React.useEffect(() => {
if (callbackRef.current) {
const elementOrRef = callbackRef.current();
const element = elementOrRef.current || elementOrRef;
if (element) {
focus(element);
}
}
}, []);
}

const FocusScope = React.forwardRef(function FocusScope(props, forwardRef) {
const {
as: BaseComponent = 'div',
children,
initialFocusRef,
...rest
} = props;
const containerRef = React.useRef(null);
const focusScope = useFocusScope(containerRef);
const ref = useRefs([forwardRef, containerRef]);

useRestoreFocus(containerRef);
useAutoFocus(() => {
if (initialFocusRef) {
return initialFocusRef;
}
return focusScope.current.getFirstDescendant();
});

return (
<>
<span
tabIndex="0"
style={{
outline: 'none',
opacity: '0',
position: 'fixed',
pointerEvents: 'none',
}}
onFocus={() => {
focusScope.current.focusLastDescendant();
}}
/>
<BaseComponent {...rest} ref={ref}>
{children}
</BaseComponent>
<span
tabIndex="0"
style={{
outline: 'none',
opacity: '0',
position: 'fixed',
pointerEvents: 'none',
}}
onFocus={() => {
focusScope.current.focusFirstDescendant();
}}
/>
</>
);
});

if (__DEV__) {
FocusScope.displayName = 'FocusScope';
}

function focus(elementOrRef) {
const element = elementOrRef.current || elementOrRef;
if (element && element.focus && document.activeElement !== element) {
element.focus();
}
}

export { FocusScope };
10 changes: 10 additions & 0 deletions packages/react/src/components/Dialog/__tests__/FocusScope-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

describe('FocusScope', () => {
//
});
Loading

0 comments on commit 761bc45

Please sign in to comment.