From 3c7a112b603ef6e0099e360c9daea0914cfd9b68 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 25 Sep 2019 15:57:23 +0100 Subject: [PATCH 1/2] [react-interactions] Add more documentation for a11y components --- .../accessibility/docs/FocusControl.md | 60 +++++++++++++++++++ .../accessibility/docs/FocusManager.md | 39 ++++++++++++ .../accessibility/docs/TabbableScope.md | 58 ++++++++++++++++++ .../accessibility/src/FocusControl.js | 4 +- .../__tests__/FocusManager-test.internal.js | 8 +-- 5 files changed, 161 insertions(+), 8 deletions(-) create mode 100644 packages/react-interactions/accessibility/docs/FocusControl.md create mode 100644 packages/react-interactions/accessibility/docs/FocusManager.md create mode 100644 packages/react-interactions/accessibility/docs/TabbableScope.md diff --git a/packages/react-interactions/accessibility/docs/FocusControl.md b/packages/react-interactions/accessibility/docs/FocusControl.md new file mode 100644 index 0000000000000..95710297c40fd --- /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 KeyboarFocusMover(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..1dfaa369d6059 --- /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..028bf4303ee85 --- /dev/null +++ b/packages/react-interactions/accessibility/docs/TabbableScope.md @@ -0,0 +1,58 @@ +# 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: + +```js +if (props.tabIndex === -1 || props.disabled) { + return false; +} +if (props.tabIndex === 0 || props.contentEditable === true) { + return true; +} +if (type === 'a' || type === 'area') { + return !!props.href && props.rel !== 'ignore'; +} +if (type === 'input') { + return props.type !== 'hidden' && props.type !== 'file'; +} +return ( + type === 'button' || + type === 'textarea' || + type === 'object' || + type === 'select' || + type === 'iframe' || + type === 'embed' +); +``` \ No newline at end of file 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); From 4afdee7b7ce8fded7692c4fd37ae8a2e764ca8af Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 25 Sep 2019 17:50:57 +0100 Subject: [PATCH 2/2] address feedback --- .../accessibility/docs/FocusControl.md | 2 +- .../accessibility/docs/FocusManager.md | 4 +-- .../accessibility/docs/TabbableScope.md | 25 +------------------ 3 files changed, 4 insertions(+), 27 deletions(-) diff --git a/packages/react-interactions/accessibility/docs/FocusControl.md b/packages/react-interactions/accessibility/docs/FocusControl.md index 95710297c40fd..1088e64ecb48f 100644 --- a/packages/react-interactions/accessibility/docs/FocusControl.md +++ b/packages/react-interactions/accessibility/docs/FocusControl.md @@ -15,7 +15,7 @@ const { getPreviousScope, } = FocusControl; -function KeyboarFocusMover(props) { +function KeyboardFocusMover(props) { const scopeRef = useRef(null); useEffect(() => { diff --git a/packages/react-interactions/accessibility/docs/FocusManager.md b/packages/react-interactions/accessibility/docs/FocusManager.md index 1dfaa369d6059..9a1b48099567f 100644 --- a/packages/react-interactions/accessibility/docs/FocusManager.md +++ b/packages/react-interactions/accessibility/docs/FocusManager.md @@ -8,14 +8,14 @@ control. These are the various props that `FocusManager` accepts: ```jsx function MyDialog(props) { return ( - +

{props.title}

{props.text}

-
+ ) } ``` diff --git a/packages/react-interactions/accessibility/docs/TabbableScope.md b/packages/react-interactions/accessibility/docs/TabbableScope.md index 028bf4303ee85..a975fdb2e7ac9 100644 --- a/packages/react-interactions/accessibility/docs/TabbableScope.md +++ b/packages/react-interactions/accessibility/docs/TabbableScope.md @@ -32,27 +32,4 @@ function FocusableNodeCollector(props) { `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: - -```js -if (props.tabIndex === -1 || props.disabled) { - return false; -} -if (props.tabIndex === 0 || props.contentEditable === true) { - return true; -} -if (type === 'a' || type === 'area') { - return !!props.href && props.rel !== 'ignore'; -} -if (type === 'input') { - return props.type !== 'hidden' && props.type !== 'file'; -} -return ( - type === 'button' || - type === 'textarea' || - type === 'object' || - type === 'select' || - type === 'iframe' || - type === 'embed' -); -``` \ No newline at end of file +to the browser. See the [implementation](../src/TabbableScope.js#L12-L33) here.