Skip to content

Commit

Permalink
[react-interactions] Modify Scope query mechanism (#17095)
Browse files Browse the repository at this point in the history
  • Loading branch information
trueadm committed Oct 15, 2019
1 parent e7704e2 commit 4cb399a
Show file tree
Hide file tree
Showing 20 changed files with 263 additions and 213 deletions.
Expand Up @@ -10,11 +10,11 @@ using the `tabFocus` prop.

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

function MyDialog(props) {
return (
<FocusContain tabScope={TabbableScope} disabled={false}>
<FocusContain scopeQuery={tabbableScopeQuery} disabled={false}>
<div>
<h2>{props.title}<h2>
<p>{props.text}</p>
Expand Down
Expand Up @@ -14,6 +14,10 @@ import {
getPreviousScope,
} from 'react-interactions/accessibility/focus-manager';

function scopeQuery(type) {
return type === 'div';
}

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

Expand All @@ -22,9 +26,9 @@ function KeyboardFocusMover(props) {

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

Expand Down
37 changes: 0 additions & 37 deletions packages/react-interactions/accessibility/docs/TabbableScope.md

This file was deleted.

@@ -0,0 +1,31 @@
# TabbableScopeQuery

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

## Usage

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

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

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

if (scope) {
const tabFocusableNodes = scope.queryAllNodes(tabbableScopeQuery);
if (tabFocusableNodes && props.onFocusableNodes) {
props.onFocusableNodes(tabFocusableNodes);
}
}
});

return (
<TabbableScope ref={scopeRef}>
{props.children}
</TabbableScope>
);
}
```
17 changes: 9 additions & 8 deletions packages/react-interactions/accessibility/src/FocusContain.js
Expand Up @@ -7,7 +7,6 @@
* @flow
*/

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

import React from 'react';
Expand All @@ -21,15 +20,17 @@ import {
type FocusContainProps = {|
children: React.Node,
disabled?: boolean,
tabScope: ReactScope,
scopeQuery: (type: string | Object, props: Object) => boolean,
|};

const {useLayoutEffect, useRef} = React;

const FocusContainScope = React.unstable_createScope();

export default function FocusContain({
children,
disabled,
tabScope: TabScope,
scopeQuery,
}: FocusContainProps): React.Node {
const scopeRef = useRef(null);
// This ensures tabbing works through the React tree (including Portals and Suspense nodes)
Expand All @@ -42,9 +43,9 @@ export default function FocusContain({
const scope = scopeRef.current;
if (scope !== null) {
if (event.shiftKey) {
focusPrevious(scope, event, true);
focusPrevious(scopeQuery, scope, event, true);
} else {
focusNext(scope, event, true);
focusNext(scopeQuery, scope, event, true);
}
}
},
Expand All @@ -71,7 +72,7 @@ export default function FocusContain({
disabled !== true &&
!scope.containsNode(document.activeElement)
) {
const fistElem = scope.getFirstNode();
const fistElem = scope.queryFirstNode(scopeQuery);
if (fistElem !== null) {
fistElem.focus();
}
Expand All @@ -81,8 +82,8 @@ export default function FocusContain({
);

return (
<TabScope ref={scopeRef} listeners={[keyboard, focusWithin]}>
<FocusContainScope ref={scopeRef} listeners={[keyboard, focusWithin]}>
{children}
</TabScope>
</FocusContainScope>
);
}
60 changes: 31 additions & 29 deletions packages/react-interactions/accessibility/src/FocusGroup.js
Expand Up @@ -7,7 +7,7 @@
* @flow
*/

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

import React from 'react';
Expand All @@ -23,14 +23,18 @@ type FocusGroupProps = {|
children: React.Node,
portrait: boolean,
wrap?: boolean,
tabScope?: ReactScope,
tabScopeQuery?: (type: string | Object, props: Object) => boolean,
allowModifiers?: boolean,
|};

const {useRef} = React;

function focusGroupItem(cell: ReactScopeMethods, event: KeyboardEvent): void {
const firstScopedNode = cell.getFirstNode();
function focusGroupItem(
scopeQuery: (type: string | Object, props: Object) => boolean,
cell: ReactScopeMethods,
event: KeyboardEvent,
): void {
const firstScopedNode = cell.queryFirstNode(scopeQuery);
if (firstScopedNode !== null) {
firstScopedNode.focus();
event.preventDefault();
Expand Down Expand Up @@ -91,30 +95,25 @@ function hasModifierKey(event: KeyboardEvent): boolean {
}

export function createFocusGroup(
scope: ReactScope,
scopeQuery: (type: string | Object, props: Object) => boolean,
): [(FocusGroupProps) => React.Node, (FocusItemProps) => React.Node] {
const TableScope = React.unstable_createScope(scope.fn);
const TableScope = React.unstable_createScope();

function Group({
children,
portrait,
wrap,
tabScope: TabScope,
tabScopeQuery,
allowModifiers,
}: FocusGroupProps): React.Node {
const tabScopeRef = useRef(null);
return (
<TableScope
type="group"
portrait={portrait}
wrap={wrap}
tabScopeRef={tabScopeRef}
tabScopeQuery={tabScopeQuery}
allowModifiers={allowModifiers}>
{TabScope ? (
<TabScope ref={tabScopeRef}>{children}</TabScope>
) : (
children
)}
{children}
</TableScope>
);
}
Expand All @@ -132,19 +131,22 @@ export function createFocusGroup(
const key = event.key;

if (key === 'Tab') {
const tabScope = getGroupProps(currentItem).tabScopeRef.current;
if (tabScope) {
const activeNode = document.activeElement;
const nodes = tabScope.getAllNodes();
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node !== activeNode) {
setElementCanTab(node, false);
} else {
setElementCanTab(node, true);
const tabScopeQuery = getGroupProps(currentItem).tabScopeQuery;
if (tabScopeQuery) {
const groupScope = currentItem.getParent();
if (groupScope) {
const activeNode = document.activeElement;
const nodes = groupScope.queryAllNodes(tabScopeQuery);
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node !== activeNode) {
setElementCanTab(node, false);
} else {
setElementCanTab(node, true);
}
}
return;
}
return;
}
event.continuePropagation();
return;
Expand All @@ -166,7 +168,7 @@ export function createFocusGroup(
currentItem,
);
if (previousGroupItem) {
focusGroupItem(previousGroupItem, event);
focusGroupItem(scopeQuery, previousGroupItem, event);
return;
}
}
Expand All @@ -176,7 +178,7 @@ export function createFocusGroup(
if (portrait) {
const nextGroupItem = getNextGroupItem(group, currentItem);
if (nextGroupItem) {
focusGroupItem(nextGroupItem, event);
focusGroupItem(scopeQuery, nextGroupItem, event);
return;
}
}
Expand All @@ -189,7 +191,7 @@ export function createFocusGroup(
currentItem,
);
if (previousGroupItem) {
focusGroupItem(previousGroupItem, event);
focusGroupItem(scopeQuery, previousGroupItem, event);
return;
}
}
Expand All @@ -199,7 +201,7 @@ export function createFocusGroup(
if (!portrait) {
const nextGroupItem = getNextGroupItem(group, currentItem);
if (nextGroupItem) {
focusGroupItem(nextGroupItem, event);
focusGroupItem(scopeQuery, nextGroupItem, event);
return;
}
}
Expand Down
17 changes: 12 additions & 5 deletions packages/react-interactions/accessibility/src/FocusManager.js
Expand Up @@ -12,9 +12,14 @@ import type {KeyboardEvent} from 'react-interactions/events/keyboard';

import getTabbableNodes from './shared/getTabbableNodes';

export function focusFirst(scope: ReactScopeMethods): void {
const [, firstTabbableElem] = getTabbableNodes(scope);
focusElem(firstTabbableElem);
export function focusFirst(
scopeQuery: (type: string | Object, props: Object) => boolean,
scope: ReactScopeMethods,
): void {
const firstNode = scope.queryFirstNode(scopeQuery);
if (firstNode) {
focusElem(firstNode);
}
}

function focusElem(elem: null | HTMLElement): void {
Expand All @@ -24,6 +29,7 @@ function focusElem(elem: null | HTMLElement): void {
}

export function focusNext(
scopeQuery: (type: string | Object, props: Object) => boolean,
scope: ReactScopeMethods,
event?: KeyboardEvent,
contain?: boolean,
Expand All @@ -34,7 +40,7 @@ export function focusNext(
lastTabbableElem,
currentIndex,
focusedElement,
] = getTabbableNodes(scope);
] = getTabbableNodes(scopeQuery, scope);

if (focusedElement === null) {
if (event) {
Expand All @@ -58,6 +64,7 @@ export function focusNext(
}

export function focusPrevious(
scopeQuery: (type: string | Object, props: Object) => boolean,
scope: ReactScopeMethods,
event?: KeyboardEvent,
contain?: boolean,
Expand All @@ -68,7 +75,7 @@ export function focusPrevious(
lastTabbableElem,
currentIndex,
focusedElement,
] = getTabbableNodes(scope);
] = getTabbableNodes(scopeQuery, scope);

if (focusedElement === null) {
if (event) {
Expand Down

0 comments on commit 4cb399a

Please sign in to comment.