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 authored Oct 15, 2019
1 parent e7704e2 commit 4cb399a
Show file tree
Hide file tree
Showing 20 changed files with 263 additions and 213 deletions.
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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.

Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit 4cb399a

Please sign in to comment.