diff --git a/packages/react-interactions/accessibility/docs/FocusControl.md b/packages/react-interactions/accessibility/docs/FocusControl.md new file mode 100644 index 0000000000000..1088e64ecb48f --- /dev/null +++ b/packages/react-interactions/accessibility/docs/FocusControl.md @@ -0,0 +1,60 @@ +# FocusControl + +`FocusControl` 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`. +A ref from `FocusManager` can also be used instead. + +## Example + +```jsx +const { + focusFirst, + focusNext, + focusPrevious, + getNextScope, + getPreviousScope, +} = FocusControl; + +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 ( + + {props.children} + + ); +} +``` + +## FocusControl API + +### `focusFirst` + +Focus the first node that matches the given scope. + +### `focusNext` + +Focus the next sequential node that matchs the given scope. + +### `focusPrevious` + +Focus the previous sequential node that matchs the given scope. + +### `getNextScope` + +Focus the first node that matches the next sibling scope from the given scope. + +### `getPreviousScope` + +Focus the first node that matches the previous sibling scope from the given scope. \ No newline at end of file diff --git a/packages/react-interactions/accessibility/docs/FocusManager.md b/packages/react-interactions/accessibility/docs/FocusManager.md new file mode 100644 index 0000000000000..9a1b48099567f --- /dev/null +++ b/packages/react-interactions/accessibility/docs/FocusManager.md @@ -0,0 +1,39 @@ +# FocusManager + +`FocusManager` is a component that is designed to provide basic focus management +control. These are the various props that `FocusManager` accepts: + +## Usage + +```jsx +function MyDialog(props) { + return ( + +
+

{props.title}

+

{props.text}

+ + +

+
+ ) +} +``` + +### `scope` +`FocusManager` accepts a custom `ReactScope`. If a custom one is not supplied, `FocusManager` +will default to using `TabbableScope`. + +### `autoFocus` +When enabled, the first host node that matches the `FocusManager` scope will be focused +upon the `FocusManager` mounting. + +### `restoreFocus` +When enabled, the previous host node that was focused as `FocusManager` is mounted, +has its focus restored upon `FocusManager` unmounting. + +### `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. \ No newline at end of file diff --git a/packages/react-interactions/accessibility/docs/TabbableScope.md b/packages/react-interactions/accessibility/docs/TabbableScope.md new file mode 100644 index 0000000000000..a975fdb2e7ac9 --- /dev/null +++ b/packages/react-interactions/accessibility/docs/TabbableScope.md @@ -0,0 +1,35 @@ +# TabbableScope + +`TabbableScope` is a custom scope implementation that can be used with +`FocusManager`, `FocusList`, `FocusTable` and `FocusControl` modules. + +## Usage + +```jsx +function FocusableNodeCollector(props) { + const scopeRef = useRef(null); + + useEffect(() => { + const scope = scopeRef.current; + + if (scope) { + const tabFocusableNodes = scope.getScopedNodes(); + if (tabFocusableNodes && props.onFocusableNodes) { + props.onFocusableNodes(tabFocusableNodes); + } + } + }); + + return ( + + {props.children} + + ); +} +``` + +## Implementation + +`TabbableScope` uses the experimental `React.unstable_createScope` API. The query +function used for the scope is designed to collect DOM nodes that are tab focusable +to the browser. See the [implementation](../src/TabbableScope.js#L12-L33) here. diff --git a/packages/react-interactions/accessibility/src/FocusControl.js b/packages/react-interactions/accessibility/src/FocusControl.js index f3fcabf7d027c..be0be363a1127 100644 --- a/packages/react-interactions/accessibility/src/FocusControl.js +++ b/packages/react-interactions/accessibility/src/FocusControl.js @@ -110,7 +110,7 @@ export function focusPrevious( } } -export function getNextController( +export function getNextScope( scope: ReactScopeMethods, ): null | ReactScopeMethods { const allScopes = scope.getChildrenFromRoot(); @@ -124,7 +124,7 @@ export function getNextController( return allScopes[currentScopeIndex + 1]; } -export function getPreviousController( +export function getPreviousScope( scope: ReactScopeMethods, ): null | ReactScopeMethods { const allScopes = scope.getChildrenFromRoot(); diff --git a/packages/react-interactions/accessibility/src/__tests__/FocusManager-test.internal.js b/packages/react-interactions/accessibility/src/__tests__/FocusManager-test.internal.js index 1cf07104c0f37..0d33ec035fd46 100644 --- a/packages/react-interactions/accessibility/src/__tests__/FocusManager-test.internal.js +++ b/packages/react-interactions/accessibility/src/__tests__/FocusManager-test.internal.js @@ -301,16 +301,12 @@ describe('FocusManager', () => { FocusControl.focusPrevious(firstFocusController); expect(document.activeElement).toBe(buttonRef.current); - const nextController = FocusControl.getNextController( - firstFocusController, - ); + const nextController = FocusControl.getNextScope(firstFocusController); expect(nextController).toBe(secondFocusController); FocusControl.focusFirst(nextController); expect(document.activeElement).toBe(divRef.current); - const previousController = FocusControl.getPreviousController( - nextController, - ); + const previousController = FocusControl.getPreviousScope(nextController); expect(previousController).toBe(firstFocusController); FocusControl.focusFirst(previousController); expect(document.activeElement).toBe(buttonRef.current);