Skip to content

[react-interactions] Repurpose React a11y modules #16997

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/react-interactions/accessibility/docs/FocusContain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# FocusContain

`FocusContain` is a component that contains user-focusability to only that
of the children of the component. This means focus control will not escape
unless the componoent is disabled (using the `disabled` prop) or unmounted.
Additionally, `FocusContain` can contain tab focus when passed a `ReactScope`
using the `tabFocus` prop.

## Usage

```jsx
import FocusContain from 'react-interactions/accessibility/focus-contain';
import TabbableScope from 'react-interactions/accessibility/tabbable-scope';

function MyDialog(props) {
return (
<FocusContain tabScope={TabbableScope} disabled={false}>
<div>
<h2>{props.title}<h2>
<p>{props.text}</p>
<Button onPress={...}>Accept</Button>
<Button onPress={...}>Close</Button>
</div>
</FocusContain>
)
}
```
60 changes: 0 additions & 60 deletions packages/react-interactions/accessibility/docs/FocusControl.md

This file was deleted.

74 changes: 47 additions & 27 deletions packages/react-interactions/accessibility/docs/FocusManager.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,59 @@
# FocusManager

`FocusManager` is a component that is designed to provide basic focus management
control. These are the various props that `FocusManager` accepts:
`FocusManager` is a module that exports a selection of helpful utility functions to be used
in conjunction with the `ref` from a React Scope, such as `TabbableScope`.

## Usage
## Example

```jsx
function MyDialog(props) {
import {
focusFirst,
focusNext,
focusPrevious,
getNextScope,
getPreviousScope,
} from 'react-interactions/accessibility/focus-manager';

function KeyboardFocusMover(props) {
const scopeRef = useRef(null);

useEffect(() => {
const scope = scopeRef.current;

if (scope) {
// Focus the first tabbable DOM node in my children
focusFirst(scope);
// Then focus the next chilkd
focusNext(scope);
}
});

return (
<FocusManager containFocus={true} autoFocus={true}>
<div>
<h2>{props.title}<h2>
<p>{props.text}</p>
<Button onPress={...}>Accept</Button>
<Button onPress={...}>Close</Button>
</div>
</FocusManager>
)
<TabbableScope ref={scopeRef}>
{props.children}
</TabbableScope>
);
}
```

### `scope`
`FocusManager` accepts a custom `ReactScope`. If a custom one is not supplied, `FocusManager`
will default to using `TabbableScope`.
## FocusManager API

### `focusFirst`

Focus the first node that matches the given scope.

### `focusNext`

Focus the next sequential node that matches the given scope.

### `focusPrevious`

Focus the previous sequential node that matches the given scope.

### `getNextScope`

### `autoFocus`
When enabled, the first host node that matches the `FocusManager` scope will be focused
upon the `FocusManager` mounting.
Focus the first node that matches the next sibling scope from the given scope.

### `restoreFocus`
When enabled, the previous host node that was focused as `FocusManager` is mounted,
has its focus restored upon `FocusManager` unmounting.
### `getPreviousScope`

### `containFocus`
This contains the user focus to only that of `FocusManager`s sub-tree. Tabbing or
interacting with nodes outside the sub-tree will restore focus back into the `FocusManager`.
This is useful for modals, dialogs, dropdowns and other UI elements that require
a form of user-focus control that is similar to the `inert` property on the web.
Focus the first node that matches the previous sibling scope from the given scope.
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# TabbableScope

`TabbableScope` is a custom scope implementation that can be used with
`FocusManager`, `FocusList`, `FocusTable` and `FocusControl` modules.
`FocusContain`, `FocusGroup`, `FocusTable` and `FocusManager` modules.

## Usage

```jsx
import TabbableScope from 'react-interactions/accessibility/tabbable-scope';

function FocusableNodeCollector(props) {
const scopeRef = useRef(null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@

'use strict';

module.exports = require('./src/FocusControl');
module.exports = require('./src/FocusContain');
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@

'use strict';

module.exports = require('./src/FocusList');
module.exports = require('./src/FocusGroup');
84 changes: 84 additions & 0 deletions packages/react-interactions/accessibility/src/FocusContain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {ReactScope} from 'shared/ReactTypes';
import type {KeyboardEvent} from 'react-interactions/events/keyboard';

import React from 'react';
import {useFocusWithin} from 'react-interactions/events/focus';
import {useKeyboard} from 'react-interactions/events/keyboard';
import {
focusPrevious,
focusNext,
} from 'react-interactions/accessibility/focus-manager';

type FocusContainProps = {|
children: React.Node,
disabled?: boolean,
tabScope: ReactScope,
|};

const {useLayoutEffect, useRef} = React;

export default function FocusContain({
children,
disabled,
tabScope: TabScope,
}: FocusContainProps) {
const scopeRef = useRef(null);
// This ensures tabbing works through the React tree (including Portals and Suspense nodes)
const keyboard = useKeyboard({
onKeyDown(event: KeyboardEvent): void {
if (disabled || event.key !== 'Tab') {
event.continuePropagation();
return;
}
const scope = scopeRef.current;
if (scope !== null) {
if (event.shiftKey) {
focusPrevious(scope, event, true);
} else {
focusNext(scope, event, true);
}
}
},
});
const focusWithin = useFocusWithin({
onBlurWithin: function(event) {
if (disabled) {
event.continuePropagation();
return;
}
const lastNode = event.target;
if (lastNode) {
requestAnimationFrame(() => {
(lastNode: any).focus();
});
}
},
});
useLayoutEffect(
() => {
const scope = scopeRef.current;
if (scope && !disabled) {
const elems = scope.getScopedNodes();
if (elems && elems.indexOf(document.activeElement) === -1) {
elems[0].focus();
}
}
},
[disabled],
);

return (
<TabScope ref={scopeRef} listeners={[keyboard, focusWithin]}>
{children}
</TabScope>
);
}
Loading