From 1b93be64fcb985456e1b134f40ef89918173cc8d Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Mon, 15 Jul 2019 18:35:50 +0200 Subject: [PATCH 1/4] feat(FocusZone): Provide bidirectional navigation following DOM order --- .../Grid/BestPractices/GridBestPractices.tsx | 4 ++- ...GridExampleKeyboardNavigable.shorthand.tsx | 30 ++++++++++++++--- .../GridExampleKeyboardNavigable.tsx | 32 ++++++++++++++++--- .../components/Grid/Variations/index.tsx | 2 +- packages/react/src/components/Grid/Grid.tsx | 2 +- .../Behaviors/Grid/gridBehavior.ts | 3 ++ .../Behaviors/Grid/gridHorizontalBehavior.ts | 23 +++++++++++++ .../Behaviors/Tab/tabListBehavior.ts | 4 +-- .../Toolbar/menuAsToolbarBehavior.ts | 4 +-- .../FocusZone/FocusZone.types.ts | 5 ++- packages/react/src/lib/accessibility/index.ts | 1 + .../test/specs/behaviors/behavior-test.tsx | 2 ++ .../test/specs/behaviors/testDefinitions.ts | 8 +++++ 13 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 packages/react/src/lib/accessibility/Behaviors/Grid/gridHorizontalBehavior.ts diff --git a/docs/src/examples/components/Grid/BestPractices/GridBestPractices.tsx b/docs/src/examples/components/Grid/BestPractices/GridBestPractices.tsx index 8184b4694..7a8be08f0 100644 --- a/docs/src/examples/components/Grid/BestPractices/GridBestPractices.tsx +++ b/docs/src/examples/components/Grid/BestPractices/GridBestPractices.tsx @@ -3,7 +3,9 @@ import * as React from 'react' import ComponentBestPractices from 'docs/src/components/ComponentBestPractices' const doList = [ - 'Use Grid behavior for bidirectional keyboard navigation. Use appropriate ARIA role for the grid and actionable components inside of it.', + 'Use `gridBehavior` for bidirectional keyboard navigation with 4 arrow keys.', + 'Use `gridHorizontalBehavior` for horizontal keyboard navigation with 4 arrow keys.', + 'Use appropriate ARIA role for the grid and actionable components inside of it when keyboard navigation provided.', ] const dontList = ["Don't use grid component as a replacement for table."] diff --git a/docs/src/examples/components/Grid/Variations/GridExampleKeyboardNavigable.shorthand.tsx b/docs/src/examples/components/Grid/Variations/GridExampleKeyboardNavigable.shorthand.tsx index 6e025c558..2f30ddfc3 100644 --- a/docs/src/examples/components/Grid/Variations/GridExampleKeyboardNavigable.shorthand.tsx +++ b/docs/src/examples/components/Grid/Variations/GridExampleKeyboardNavigable.shorthand.tsx @@ -1,5 +1,13 @@ import * as React from 'react' -import { Grid, Image, Button, gridBehavior } from '@stardust-ui/react' +import { + Grid, + Image, + Button, + Text, + Label, + gridBehavior, + gridHorizontalBehavior, +} from '@stardust-ui/react' import * as _ from 'lodash' const imageNames = [ @@ -51,13 +59,25 @@ const renderImageButtons = () => { const GridExample = () => (
- Grid with images, which are not natively focusable elements. Set 'data-is-focusable=true' to - each item to make grid items focusable and navigable. + + Grid with images, which are not natively focusable elements. Set{' '} + to each item to make grid items focusable and navigable. + Use to provide arrow key navigation in 4 directions. +
- Grid with images, wrapped with buttons, which are natively focusable elements. No need to add - 'data-is-focusable'='true'. + + Grid with buttons images, which are natively focusable elements. No need to add{' '} + + +
+ + Grid with buttons images, which are natively focusable elements. Use{' '} + to provide horizontal navigation within Grid with 4 + arrow keys. + +
) diff --git a/docs/src/examples/components/Grid/Variations/GridExampleKeyboardNavigable.tsx b/docs/src/examples/components/Grid/Variations/GridExampleKeyboardNavigable.tsx index a86e6ca22..f463a83e0 100644 --- a/docs/src/examples/components/Grid/Variations/GridExampleKeyboardNavigable.tsx +++ b/docs/src/examples/components/Grid/Variations/GridExampleKeyboardNavigable.tsx @@ -1,5 +1,13 @@ import * as React from 'react' -import { Grid, Image, Button, gridBehavior } from '@stardust-ui/react' +import { + Grid, + Image, + Button, + Text, + Label, + gridBehavior, + gridHorizontalBehavior, +} from '@stardust-ui/react' import * as _ from 'lodash' const imageNames = [ @@ -56,17 +64,31 @@ const gridStyles = { const GridExample = () => (
- Grid with images, which are not natively focusable elements. Set 'data-is-focusable=true' to - each item to make grid items focusable and navigable. + + Grid with images, which are not natively focusable elements. Set{' '} + to each item to make grid items focusable and navigable. + Use to provide arrow key navigation in 4 directions. + {renderImages()}
- Grid with images, wrapped with button components, which are natively focusable elements. No need - to add 'data-is-focusable'='true' + + Grid with buttons images, which are natively focusable elements. No need to add{' '} + + {renderImageButtons()} +
+ + Grid with buttons images, which are natively focusable elements. Use{' '} + to provide horizontal navigation within Grid with 4 + arrow keys. + + + {renderImageButtons()} +
) diff --git a/docs/src/examples/components/Grid/Variations/index.tsx b/docs/src/examples/components/Grid/Variations/index.tsx index ece598b95..552c988d7 100644 --- a/docs/src/examples/components/Grid/Variations/index.tsx +++ b/docs/src/examples/components/Grid/Variations/index.tsx @@ -21,7 +21,7 @@ const Variations = () => ( /> diff --git a/packages/react/src/components/Grid/Grid.tsx b/packages/react/src/components/Grid/Grid.tsx index 5a13fbfac..28d3c6367 100644 --- a/packages/react/src/components/Grid/Grid.tsx +++ b/packages/react/src/components/Grid/Grid.tsx @@ -20,7 +20,7 @@ export interface GridProps ContentComponentProps { /** * Accessibility behavior if overridden by the user. - * @available gridBehavior + * @available gridBehavior, gridHorizontalBehavior * */ accessibility?: Accessibility diff --git a/packages/react/src/lib/accessibility/Behaviors/Grid/gridBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Grid/gridBehavior.ts index 709681eb8..c0c3df42b 100644 --- a/packages/react/src/lib/accessibility/Behaviors/Grid/gridBehavior.ts +++ b/packages/react/src/lib/accessibility/Behaviors/Grid/gridBehavior.ts @@ -2,6 +2,9 @@ import { Accessibility, FocusZoneMode } from '../../types' import { FocusZoneDirection } from '../../FocusZone' /** + * @description + * Provides navigation between focusable children of Grid component with arrow keys in 4 directions. + * * @specification * Embeds component into FocusZone. * Provides arrow key navigation in bidirectional direction. diff --git a/packages/react/src/lib/accessibility/Behaviors/Grid/gridHorizontalBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Grid/gridHorizontalBehavior.ts new file mode 100644 index 000000000..fac083cd0 --- /dev/null +++ b/packages/react/src/lib/accessibility/Behaviors/Grid/gridHorizontalBehavior.ts @@ -0,0 +1,23 @@ +import { Accessibility, FocusZoneMode } from '../../types' +import { FocusZoneDirection } from '../../FocusZone' + +/** + * @description + * Provides navigation between focusable children of Grid component with 4 arrow keys in horizontal direction (based on DOM order). + * Right/Down arrow keys move to next item, Up/Left arrow keys to previous item. Right and Left arrow keys are switched in RTL mode. + * + * @specification + * Embeds component into FocusZone. + * Provides arrow key navigation in bidirectionalDomOrder direction. + */ +const gridHorizontalBehavior: Accessibility = () => ({ + attributes: {}, + focusZone: { + mode: FocusZoneMode.Embed, + props: { + direction: FocusZoneDirection.bidirectionalDomOrder, + }, + }, +}) + +export default gridHorizontalBehavior diff --git a/packages/react/src/lib/accessibility/Behaviors/Tab/tabListBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Tab/tabListBehavior.ts index 5419bc707..9645d2c3a 100644 --- a/packages/react/src/lib/accessibility/Behaviors/Tab/tabListBehavior.ts +++ b/packages/react/src/lib/accessibility/Behaviors/Tab/tabListBehavior.ts @@ -9,7 +9,7 @@ import tabBehavior from './tabBehavior' * @specification * Adds role 'tablist' to 'root' slot. * Embeds component into FocusZone. - * Provides arrow key navigation in bidirectional direction. + * Provides arrow key navigation in bidirectionalDomOrder direction. * When component's container element receives focus, focus will be set to the default focusable child element of the component. */ const tabListBehavior: Accessibility = () => ({ @@ -22,7 +22,7 @@ const tabListBehavior: Accessibility = () => ({ mode: FocusZoneMode.Embed, props: { shouldFocusInnerElementWhenReceivedFocus: true, - direction: FocusZoneDirection.bidirectional, + direction: FocusZoneDirection.bidirectionalDomOrder, }, }, childBehaviors: { diff --git a/packages/react/src/lib/accessibility/Behaviors/Toolbar/menuAsToolbarBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Toolbar/menuAsToolbarBehavior.ts index e67e63787..ffddc229c 100644 --- a/packages/react/src/lib/accessibility/Behaviors/Toolbar/menuAsToolbarBehavior.ts +++ b/packages/react/src/lib/accessibility/Behaviors/Toolbar/menuAsToolbarBehavior.ts @@ -9,7 +9,7 @@ import menuItemAsToolbarButtonBehavior from './menuItemAsToolbarButtonBehavior' * @specification * Adds role 'toolbar' to 'root' slot. * Embeds component into FocusZone. - * Provides arrow key navigation in bidirectional direction. + * Provides arrow key navigation in bidirectionalDomOrder direction. * When component's container element receives focus, focus will be set to the default focusable child element of the component. */ const menuAsToolbarBehavior: Accessibility = () => ({ @@ -22,7 +22,7 @@ const menuAsToolbarBehavior: Accessibility = () => ({ mode: FocusZoneMode.Embed, props: { shouldFocusInnerElementWhenReceivedFocus: true, - direction: FocusZoneDirection.bidirectional, + direction: FocusZoneDirection.bidirectionalDomOrder, }, }, childBehaviors: { diff --git a/packages/react/src/lib/accessibility/FocusZone/FocusZone.types.ts b/packages/react/src/lib/accessibility/FocusZone/FocusZone.types.ts index 15896b4b8..d29578a35 100644 --- a/packages/react/src/lib/accessibility/FocusZone/FocusZone.types.ts +++ b/packages/react/src/lib/accessibility/FocusZone/FocusZone.types.ts @@ -44,7 +44,7 @@ export interface FocusZoneProps extends React.HTMLAttributes { + const actualFocusZoneHorizontal = parameters.behavior({}).focusZone + expect(actualFocusZoneHorizontal.props.direction).toBe(FocusZoneDirection.bidirectionalDomOrder) + }, +}) + definitions.push({ regexp: /Keyboard navigation is circular/g, testMethod: (parameters: TestMethod) => { From f8ea6615ec8237d3b7eb650d45c1f87f0f6f27cf Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Tue, 16 Jul 2019 14:15:48 +0200 Subject: [PATCH 2/4] Add tests --- .../react/test/specs/lib/FocusZone-test.tsx | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/packages/react/test/specs/lib/FocusZone-test.tsx b/packages/react/test/specs/lib/FocusZone-test.tsx index 9c463939f..083a36cea 100644 --- a/packages/react/test/specs/lib/FocusZone-test.tsx +++ b/packages/react/test/specs/lib/FocusZone-test.tsx @@ -403,6 +403,107 @@ describe('FocusZone', () => { expect(lastFocusedElement).toBe(buttonA) }) + it('can use arrows bidirectionally by following DOM order', () => { + const component = ReactTestUtils.renderIntoDocument<{}, React.Component>( +
+ + + + + +
, + ) + + const focusZone = ReactDOM.findDOMNode(component)!!.firstChild as Element + + const buttonA = focusZone.querySelector('#a') as HTMLElement + const buttonB = focusZone.querySelector('#b') as HTMLElement + const buttonC = focusZone.querySelector('#c') as HTMLElement + + // Assign bounding locations to buttons. + setupElement(buttonA, { + clientRect: { + top: 0, + bottom: 30, + left: 0, + right: 100, + }, + }) + + setupElement(buttonB, { + clientRect: { + top: 30, + bottom: 60, + left: 0, + right: 100, + }, + }) + + setupElement(buttonC, { + clientRect: { + top: 60, + bottom: 90, + left: 0, + right: 100, + }, + }) + + // Pressing down/right arrow keys moves focus to the next focusable item. + // Pressing up/left arrow keys moves focus to the previous focusable item. + + // Focus the first button. + ReactTestUtils.Simulate.focus(buttonA) + expect(lastFocusedElement).toBe(buttonA) + + // Pressing down should go to b. + ReactTestUtils.Simulate.keyDown(focusZone, { which: keyboardKey.ArrowDown }) + expect(lastFocusedElement).toBe(buttonB) + + // Pressing right should go to c. + ReactTestUtils.Simulate.keyDown(focusZone, { which: keyboardKey.ArrowRight }) + expect(lastFocusedElement).toBe(buttonC) + + // Pressing down should stay on c. + ReactTestUtils.Simulate.keyDown(focusZone, { which: keyboardKey.ArrowDown }) + expect(lastFocusedElement).toBe(buttonC) + + // Pressing up should go to b. + ReactTestUtils.Simulate.keyDown(focusZone, { which: keyboardKey.ArrowUp }) + expect(lastFocusedElement).toBe(buttonB) + + // Pressing left should go to a. + ReactTestUtils.Simulate.keyDown(focusZone, { which: keyboardKey.ArrowLeft }) + expect(lastFocusedElement).toBe(buttonA) + + // Pressing left should stay on a. + ReactTestUtils.Simulate.keyDown(focusZone, { which: keyboardKey.ArrowLeft }) + expect(lastFocusedElement).toBe(buttonA) + + // Click on c to focus it. + ReactTestUtils.Simulate.focus(buttonC) + expect(lastFocusedElement).toBe(buttonC) + + // Pressing up should move to b. + ReactTestUtils.Simulate.keyDown(focusZone, { which: keyboardKey.ArrowUp }) + expect(lastFocusedElement).toBe(buttonB) + + // Pressing left should move to a. + ReactTestUtils.Simulate.keyDown(focusZone, { which: keyboardKey.ArrowLeft }) + expect(lastFocusedElement).toBe(buttonA) + + // Pressing right should move to b. + ReactTestUtils.Simulate.keyDown(focusZone, { which: keyboardKey.ArrowRight }) + expect(lastFocusedElement).toBe(buttonB) + + // Press home should go to the first target. + ReactTestUtils.Simulate.keyDown(focusZone, { which: keyboardKey.Home }) + expect(lastFocusedElement).toBe(buttonA) + + // Press end should go to the last target. + ReactTestUtils.Simulate.keyDown(focusZone, { which: keyboardKey.End }) + expect(lastFocusedElement).toBe(buttonC) + }) + it('can reset alignment on mouse down', () => { const component = ReactTestUtils.renderIntoDocument<{}, React.Component>(
From 2bffd6c46e930a9dadbeb773938c3f109b08f31f Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Tue, 16 Jul 2019 14:35:24 +0200 Subject: [PATCH 3/4] update changelog entries --- CHANGELOG.md | 1 + .../react/src/lib/accessibility/FocusZone/CHANGELOG.md | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f9733f02..ff9642c81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add `headerAction` slot to the `Dialog` component @mnajdova ([#1617](https://github.com/stardust-ui/react/pull/1617)) - Add `Slider` component @Bugaa92 ([#1559](https://github.com/stardust-ui/react/pull/1559)) - Add `tooltipAsLabelBehavior` accessibility behavior for `Tooltip` @sophieH29 ([#1635](https://github.com/stardust-ui/react/pull/1635)) +- Add bidirectional navigation following DOM in `FocusZone` @sophieH29 ([#1637](https://github.com/stardust-ui/react/pull/1647)) ### Fixes - Fix `ChatMessage`'s focus border overlays `actionMenu` in Teams theme @mnajdova ([#1637](https://github.com/stardust-ui/react/pull/1637)) diff --git a/packages/react/src/lib/accessibility/FocusZone/CHANGELOG.md b/packages/react/src/lib/accessibility/FocusZone/CHANGELOG.md index 8f5d03eb1..59a2560a5 100644 --- a/packages/react/src/lib/accessibility/FocusZone/CHANGELOG.md +++ b/packages/react/src/lib/accessibility/FocusZone/CHANGELOG.md @@ -2,7 +2,7 @@ This is a list of changes made to this Stardust copy of FocusZone in comparison with the original [Fabric FocusZone @ 0f567e05952c6b50c691df2fb72d100b5e525d9e](https://github.com/OfficeDev/office-ui-fabric-react/blob/0f567e05952c6b50c691df2fb72d100b5e525d9e/packages/office-ui-fabric-react/src/components/FocusZone/FocusZone.tsx). -### fixes +### Fixes - With `defaultTabbableElement` prop set tab indexes are not updated accordingly ([#342](https://github.com/stardust-ui/react/pull/342)) - Remove unused prop `componentRef` ([#397](https://github.com/stardust-ui/react/pull/397)) - Fix `defaultTabbableElement` prop to be as a function ([#450](https://github.com/stardust-ui/react/pull/450)) @@ -22,9 +22,11 @@ This is a list of changes made to this Stardust copy of FocusZone in comparison - Enable RTL @sophieH29 ([#646](https://github.com/stardust-ui/react/pull/646)) - Add `shouldFocusFirstElementWhenReceivedFocus` prop, which forces focus to first element when container receives focus @sophieH29 ([#469](https://github.com/stardust-ui/react/pull/469)) -- Handle keyDownCapture based on `shouldHandleKeyDownCapture` prop @sophieH29 ([#563](https://github.com/stardust-ui/react/pull/563)) +- Handle keyDownCapture based on `shouldHandleKeyDownCapture` prop @sophieH29 ([#563](https://github.com/stardust-ui/react/pull/563)) +- Add `bidirectionalDomOrder` direction allowing 4 arrow keys navigation following DOM order @sophieH29 ([#1637](https://github.com/stardust-ui/react/pull/1647)) + -### feat(FocusZone): Implement FocusZone into renderComponent [#116](https://github.com/stardust-ui/react/pull/116) +#### feat(FocusZone): Implement FocusZone into renderComponent [#116](https://github.com/stardust-ui/react/pull/116) - Prettier and linting fixes, e.g., removing semicolons, removing underscores from private methods. - Moved `IS_FOCUSABLE_ATTRIBUTE` and others to `focusUtilities.ts`. - Added prop types, default props, and handled props. From 1c7dff7333a03f2d0ccdd41f6b595455a92630b4 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Fri, 19 Jul 2019 09:43:24 +0200 Subject: [PATCH 4/4] Remove '4 arrows' --- .../components/Grid/BestPractices/GridBestPractices.tsx | 4 ++-- .../accessibility/Behaviors/Grid/gridHorizontalBehavior.ts | 2 +- packages/react/src/lib/accessibility/FocusZone/CHANGELOG.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/examples/components/Grid/BestPractices/GridBestPractices.tsx b/docs/src/examples/components/Grid/BestPractices/GridBestPractices.tsx index 7a8be08f0..ba5035dcf 100644 --- a/docs/src/examples/components/Grid/BestPractices/GridBestPractices.tsx +++ b/docs/src/examples/components/Grid/BestPractices/GridBestPractices.tsx @@ -3,8 +3,8 @@ import * as React from 'react' import ComponentBestPractices from 'docs/src/components/ComponentBestPractices' const doList = [ - 'Use `gridBehavior` for bidirectional keyboard navigation with 4 arrow keys.', - 'Use `gridHorizontalBehavior` for horizontal keyboard navigation with 4 arrow keys.', + 'Use `gridBehavior` for bidirectional keyboard navigation with arrow keys.', + 'Use `gridHorizontalBehavior` for horizontal keyboard navigation with arrow keys.', 'Use appropriate ARIA role for the grid and actionable components inside of it when keyboard navigation provided.', ] diff --git a/packages/react/src/lib/accessibility/Behaviors/Grid/gridHorizontalBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Grid/gridHorizontalBehavior.ts index fac083cd0..17793595c 100644 --- a/packages/react/src/lib/accessibility/Behaviors/Grid/gridHorizontalBehavior.ts +++ b/packages/react/src/lib/accessibility/Behaviors/Grid/gridHorizontalBehavior.ts @@ -3,7 +3,7 @@ import { FocusZoneDirection } from '../../FocusZone' /** * @description - * Provides navigation between focusable children of Grid component with 4 arrow keys in horizontal direction (based on DOM order). + * Provides navigation between focusable children of Grid component with arrow keys in horizontal direction (based on DOM order). * Right/Down arrow keys move to next item, Up/Left arrow keys to previous item. Right and Left arrow keys are switched in RTL mode. * * @specification diff --git a/packages/react/src/lib/accessibility/FocusZone/CHANGELOG.md b/packages/react/src/lib/accessibility/FocusZone/CHANGELOG.md index 59a2560a5..8e1eddc78 100644 --- a/packages/react/src/lib/accessibility/FocusZone/CHANGELOG.md +++ b/packages/react/src/lib/accessibility/FocusZone/CHANGELOG.md @@ -23,7 +23,7 @@ This is a list of changes made to this Stardust copy of FocusZone in comparison - Add `shouldFocusFirstElementWhenReceivedFocus` prop, which forces focus to first element when container receives focus @sophieH29 ([#469](https://github.com/stardust-ui/react/pull/469)) - Handle keyDownCapture based on `shouldHandleKeyDownCapture` prop @sophieH29 ([#563](https://github.com/stardust-ui/react/pull/563)) -- Add `bidirectionalDomOrder` direction allowing 4 arrow keys navigation following DOM order @sophieH29 ([#1637](https://github.com/stardust-ui/react/pull/1647)) +- Add `bidirectionalDomOrder` direction allowing arrow keys navigation following DOM order @sophieH29 ([#1637](https://github.com/stardust-ui/react/pull/1647)) #### feat(FocusZone): Implement FocusZone into renderComponent [#116](https://github.com/stardust-ui/react/pull/116)