Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.

Commit 70f3229

Browse files
committed
feat(bindings): add useAccessibility()
1 parent db0932f commit 70f3229

22 files changed

+345
-161
lines changed

.github/add-a-feature.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
44
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
55

6+
67
- [Propose feature](#propose-feature)
78
- [Prototype](#prototype)
89
- [Spec out the API](#spec-out-the-api)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { AccessibilityDefinition, FocusZoneMode } from '@stardust-ui/accessibility'
2+
import * as React from 'react'
3+
4+
import FocusZone from './FocusZone'
5+
import { FocusZoneProps } from './FocusZone.types'
6+
import { FOCUSZONE_WRAP_ATTRIBUTE } from './focusUtilities'
7+
8+
const wrapInFocusZone = (
9+
element: React.ReactElement,
10+
accessibility: AccessibilityDefinition,
11+
rtl: boolean,
12+
) => {
13+
if (accessibility.focusZone && accessibility.focusZone.mode === FocusZoneMode.Wrap) {
14+
return React.createElement(
15+
FocusZone,
16+
{
17+
[FOCUSZONE_WRAP_ATTRIBUTE]: true,
18+
...accessibility.focusZone.props,
19+
isRtl: rtl,
20+
} as FocusZoneProps & { [FOCUSZONE_WRAP_ATTRIBUTE]: boolean },
21+
element,
22+
)
23+
}
24+
25+
if (accessibility.focusZone && accessibility.focusZone.mode === FocusZoneMode.Embed) {
26+
return React.createElement(FocusZone, {
27+
...element.props,
28+
...accessibility.focusZone.props,
29+
isRtl: rtl,
30+
})
31+
}
32+
33+
return element
34+
}
35+
36+
export default wrapInFocusZone
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Accessibility, AccessibilityDefinition } from '@stardust-ui/accessibility'
2+
3+
import getKeyDownHandlers from './getKeyDownHandlers'
4+
import { AccessibilityActionHandlers, AccessibilityBehavior } from './types'
5+
6+
const emptyBehavior: AccessibilityBehavior = {
7+
attributes: {},
8+
keyHandlers: {},
9+
}
10+
11+
const getAccessibility = <Props extends Record<string, any>>(
12+
displayName: string,
13+
behavior: Accessibility<Props>,
14+
behaviorProps: Props,
15+
isRtlEnabled: boolean,
16+
actionHandlers?: AccessibilityActionHandlers,
17+
): AccessibilityBehavior => {
18+
if (behavior === null || behavior === undefined) {
19+
return emptyBehavior
20+
}
21+
22+
const definition: AccessibilityDefinition = behavior(behaviorProps)
23+
const keyHandlers = definition.keyActions
24+
? // @ts-ignore FIX ME
25+
getKeyDownHandlers(actionHandlers, definition.keyActions, isRtlEnabled)
26+
: {}
27+
28+
if (process.env.NODE_ENV !== 'production') {
29+
// For the non-production builds we enable the runtime accessibility attributes validator.
30+
// We're adding the data-aa-class attribute which is being consumed by the validator, the
31+
// schema is located in @stardust-ui/ability-attributes package.
32+
if (definition.attributes) {
33+
const slotNames = Object.keys(definition.attributes)
34+
slotNames.forEach(slotName => {
35+
// @ts-ignore FIX ME
36+
definition.attributes[slotName]['data-aa-class'] = `${displayName}${
37+
slotName === 'root' ? '' : `__${slotName}`
38+
}`
39+
})
40+
}
41+
}
42+
43+
return {
44+
...emptyBehavior,
45+
...definition,
46+
keyHandlers,
47+
}
48+
}
49+
50+
export default getAccessibility

packages/react/src/lib/getKeyDownHandlers.ts renamed to packages/react-bindings/src/accessibility/getKeyDownHandlers.ts

+17-15
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { KeyActions } from '@stardust-ui/accessibility'
2-
import * as _ from 'lodash'
2+
// @ts-ignore
33
import * as keyboardKey from 'keyboard-key'
44
import * as React from 'react'
55

66
import shouldHandleOnKeys from './shouldHandleOnKeys'
7-
import { AccessibilityActionHandlers, AccessibilityKeyHandlers } from './accessibility/reactTypes'
7+
import { AccessibilityActionHandlers, AccessibilityKeyHandlers } from './types'
88

99
const rtlKeyMap = {
1010
[keyboardKey.ArrowRight]: keyboardKey.ArrowLeft,
@@ -14,31 +14,33 @@ const rtlKeyMap = {
1414
/**
1515
* Assigns onKeyDown handler to the slot element, based on Component's actions
1616
* and keys mappings defined in Accessibility behavior
17-
* @param {AccessibilityActionHandlers} componentActionHandlers Actions handlers defined in a component.
18-
* @param {KeyActions} behaviorKeyActions Mappings of actions and keys defined in Accessibility behavior.
17+
* @param {AccessibilityActionHandlers} actionHandlers Actions handlers defined in a component.
18+
* @param {KeyActions} behaviorActions Mappings of actions and keys defined in Accessibility behavior.
1919
* @param {boolean} isRtlEnabled Indicates if Left and Right arrow keys should be swapped in RTL mode.
2020
*/
2121
const getKeyDownHandlers = (
22-
componentActionHandlers: AccessibilityActionHandlers,
23-
behaviorKeyActions: KeyActions,
22+
actionHandlers: AccessibilityActionHandlers,
23+
behaviorActions: KeyActions,
2424
isRtlEnabled?: boolean,
2525
): AccessibilityKeyHandlers => {
26+
const componentHandlerNames = Object.keys(actionHandlers)
2627
const keyHandlers = {}
2728

28-
if (!componentActionHandlers || !behaviorKeyActions) return keyHandlers
29+
if (!actionHandlers || !behaviorActions) return keyHandlers
2930

30-
for (const componentPart in behaviorKeyActions) {
31-
const componentPartKeyAction = behaviorKeyActions[componentPart]
32-
const handledActions = _.intersection(
33-
_.keys(componentPartKeyAction),
34-
_.keys(componentActionHandlers),
31+
for (const slotName in behaviorActions) {
32+
const behaviorSlotAction = behaviorActions[slotName]
33+
const handledActions = Object.keys(behaviorSlotAction).filter(
34+
actionName => componentHandlerNames.indexOf(actionName) !== -1,
3535
)
36+
3637
if (!handledActions.length) continue
3738

38-
keyHandlers[componentPart] = {
39+
// @ts-ignore FIX ME
40+
keyHandlers[slotName] = {
3941
onKeyDown: (event: React.KeyboardEvent) => {
4042
handledActions.forEach(actionName => {
41-
let keyCombinations = componentPartKeyAction[actionName].keyCombinations
43+
let keyCombinations = behaviorSlotAction[actionName].keyCombinations
4244

4345
if (isRtlEnabled) {
4446
keyCombinations = keyCombinations.map(keyCombination => {
@@ -51,7 +53,7 @@ const getKeyDownHandlers = (
5153
}
5254

5355
if (shouldHandleOnKeys(event, keyCombinations)) {
54-
componentActionHandlers[actionName](event)
56+
actionHandlers[actionName](event)
5557
}
5658
})
5759
},

packages/react/src/lib/shouldHandleOnKeys.ts renamed to packages/react-bindings/src/accessibility/shouldHandleOnKeys.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { KeyCombinations } from '@stardust-ui/accessibility'
2+
// @ts-ignore
23
import * as keyboardKey from 'keyboard-key'
3-
import * as _ from 'lodash'
44
import * as React from 'react'
55

66
const isKeyModifiersMatch = (modifierValue: boolean, combinationValue?: boolean) => {
@@ -15,9 +15,8 @@ const shouldHandleOnKeys = (
1515
event: React.KeyboardEvent,
1616
keysCombinations: KeyCombinations[],
1717
): boolean =>
18-
_.some(
19-
keysCombinations,
20-
(keysCombination: KeyCombinations) =>
18+
keysCombinations.some(
19+
keysCombination =>
2120
keysCombination.keyCode === keyboardKey.getCode(event) &&
2221
isKeyModifiersMatch(event.altKey, keysCombination.altKey) &&
2322
isKeyModifiersMatch(event.shiftKey, keysCombination.shiftKey) &&
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import { AccessibilityAttributesBySlot, AccessibilityDefinition } from '@stardust-ui/accessibility'
22
import * as React from 'react'
33

4-
/*
5-
* Accessibility types for React implementation.
6-
*/
7-
8-
export interface ReactAccessibilityBehavior extends AccessibilityDefinition {
4+
export interface AccessibilityBehavior extends AccessibilityDefinition {
95
attributes: AccessibilityAttributesBySlot
106
keyHandlers: AccessibilityKeyHandlers
117
}
@@ -15,11 +11,11 @@ export type AccessibilityKeyHandlers = {
1511
}
1612

1713
export type AccessibilityHandlerProps = {
18-
onKeyDown?: KeyboardEventHandler
14+
onKeyDown?: AccessibilityKeyboardHandler
1915
}
2016

2117
export type AccessibilityActionHandlers = {
22-
[actionName: string]: KeyboardEventHandler
18+
[actionName: string]: AccessibilityKeyboardHandler
2319
}
2420

25-
export type KeyboardEventHandler = (event: React.KeyboardEvent) => void
21+
export type AccessibilityKeyboardHandler = (event: React.KeyboardEvent) => void
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { Accessibility, AccessibilityAttributesBySlot } from '@stardust-ui/accessibility'
2+
import * as React from 'react'
3+
4+
import getAccessibility from '../accessibility/getAccessibility'
5+
import { AccessibilityBehavior, AccessibilityActionHandlers } from '../accessibility/types'
6+
7+
type UseAccessibilityOptions<Props> = {
8+
actionHandlers?: AccessibilityActionHandlers
9+
debugName?: string
10+
mapPropsToBehavior?: () => Props
11+
rtl?: boolean
12+
}
13+
14+
const mergeProps = <SlotProps extends Record<string, any>>(
15+
slotName: string,
16+
slotProps: SlotProps,
17+
definition: AccessibilityBehavior,
18+
): SlotProps & Partial<AccessibilityAttributesBySlot> => {
19+
const finalProps = {
20+
...definition.attributes[slotName],
21+
...slotProps,
22+
}
23+
const slotHandlers = definition.keyHandlers[slotName]
24+
25+
if (slotHandlers) {
26+
const onKeyDown = (e: React.KeyboardEvent, ...args: any[]) => {
27+
definition.keyHandlers[slotName].onKeyDown(e)
28+
if (slotProps.onKeyDown) {
29+
slotProps.onKeyDown(e, ...args)
30+
}
31+
}
32+
33+
finalProps.onKeyDown = onKeyDown
34+
}
35+
36+
return finalProps
37+
}
38+
39+
const useAccessibility = <Props>(
40+
behavior: Accessibility<Props>,
41+
options: UseAccessibilityOptions<Props> = {},
42+
) => {
43+
const {
44+
actionHandlers,
45+
debugName = 'Undefined',
46+
mapPropsToBehavior = () => ({}),
47+
rtl = false,
48+
} = options
49+
const definition = getAccessibility(
50+
debugName,
51+
behavior,
52+
mapPropsToBehavior(),
53+
rtl,
54+
actionHandlers,
55+
)
56+
57+
const latestDefinition = React.useRef<AccessibilityBehavior>(definition)
58+
latestDefinition.current = definition
59+
60+
return React.useCallback(
61+
<SlotProps extends Record<string, any>>(slotName: string, slotProps: SlotProps) =>
62+
mergeProps(slotName, slotProps, latestDefinition.current),
63+
[],
64+
)
65+
}
66+
67+
export default useAccessibility

packages/react-bindings/src/index.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
export { default as unstable_useDispatchEffect } from './hooks/useDispatchEffect'
2-
export { default as useStateManager } from './hooks/useStateManager'
1+
export { default as unstable_getAccessibility } from './accessibility/getAccessibility'
2+
export * from './accessibility/types'
33

44
export { default as AutoFocusZone } from './FocusZone/AutoFocusZone'
55
export * from './FocusZone/AutoFocusZone.types'
@@ -8,6 +8,11 @@ export * from './FocusZone/FocusTrapZone.types'
88
export { default as FocusZone } from './FocusZone/FocusZone'
99
export * from './FocusZone/FocusZone.types'
1010
export * from './FocusZone/focusUtilities'
11+
export { default as unstable_wrapInFocusZone } from './FocusZone/wrapInFocusZone'
12+
13+
export { default as useAccessibility } from './hooks/useAccessibility'
14+
export { default as unstable_useDispatchEffect } from './hooks/useDispatchEffect'
15+
export { default as useStateManager } from './hooks/useStateManager'
1116

1217
export { default as callable } from './utils/callable'
1318
export { default as getElementType } from './utils/getElementType'

packages/react/test/specs/lib/getKeyDownHandlers-test.ts renamed to packages/react-bindings/test/accesibility/getKeyDownHandlers-test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import getKeyDownHandlers from 'src/lib/getKeyDownHandlers'
1+
import getKeyDownHandlers from '../../src/accessibility/getKeyDownHandlers'
22
import * as keyboardKey from 'keyboard-key'
33

44
const testKeyCode = keyboardKey.ArrowRight

packages/react/test/specs/lib/shouldHandleOnKeys-test.ts renamed to packages/react-bindings/test/accesibility/shouldHandleOnKeys-test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import shouldHandleOnKeys from 'src/lib/shouldHandleOnKeys'
1+
import shouldHandleOnKeys from '../../src/accessibility/shouldHandleOnKeys'
22

33
const getEventArg = (
44
keyCode: number,

0 commit comments

Comments
 (0)