From 70f3229476853c523a7ef214f2b0cf26a29b1569 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Tue, 24 Sep 2019 17:39:37 +0200 Subject: [PATCH 01/19] feat(bindings): add useAccessibility() --- .github/add-a-feature.md | 1 + .../src/FocusZone/wrapInFocusZone.ts | 36 ++++++ .../src/accessibility/getAccessibility.ts | 50 ++++++++ .../src/accessibility}/getKeyDownHandlers.ts | 32 ++--- .../src/accessibility}/shouldHandleOnKeys.ts | 7 +- .../src/accessibility/types.ts} | 12 +- .../src/hooks/useAccessibility.ts | 67 ++++++++++ packages/react-bindings/src/index.ts | 9 +- .../accesibility}/getKeyDownHandlers-test.ts | 2 +- .../accesibility}/shouldHandleOnKeys-test.ts | 2 +- .../test/hooks/useAccessibility-test.tsx | 118 ++++++++++++++++++ packages/react/src/components/Menu/Menu.tsx | 4 +- packages/react/src/components/Popup/Popup.tsx | 11 +- .../react/src/components/Portal/Portal.tsx | 7 +- .../react/src/components/Tooltip/Tooltip.tsx | 8 +- packages/react/src/components/Tree/Tree.tsx | 5 +- .../react/src/components/Tree/TreeItem.tsx | 4 +- packages/react/src/lib/UIComponent.tsx | 2 +- .../src/lib/applyAccessibilityKeyHandlers.ts | 7 +- packages/react/src/lib/createComponent.ts | 2 +- .../react/src/lib/createStardustComponent.tsx | 4 +- packages/react/src/lib/renderComponent.tsx | 116 ++--------------- 22 files changed, 345 insertions(+), 161 deletions(-) create mode 100644 packages/react-bindings/src/FocusZone/wrapInFocusZone.ts create mode 100644 packages/react-bindings/src/accessibility/getAccessibility.ts rename packages/{react/src/lib => react-bindings/src/accessibility}/getKeyDownHandlers.ts (61%) rename packages/{react/src/lib => react-bindings/src/accessibility}/shouldHandleOnKeys.ts (88%) rename packages/{react/src/lib/accessibility/reactTypes.ts => react-bindings/src/accessibility/types.ts} (58%) create mode 100644 packages/react-bindings/src/hooks/useAccessibility.ts rename packages/{react/test/specs/lib => react-bindings/test/accesibility}/getKeyDownHandlers-test.ts (98%) rename packages/{react/test/specs/lib => react-bindings/test/accesibility}/shouldHandleOnKeys-test.ts (96%) create mode 100644 packages/react-bindings/test/hooks/useAccessibility-test.tsx diff --git a/.github/add-a-feature.md b/.github/add-a-feature.md index dc5ea3fd7f..02f568e43e 100644 --- a/.github/add-a-feature.md +++ b/.github/add-a-feature.md @@ -3,6 +3,7 @@ + - [Propose feature](#propose-feature) - [Prototype](#prototype) - [Spec out the API](#spec-out-the-api) diff --git a/packages/react-bindings/src/FocusZone/wrapInFocusZone.ts b/packages/react-bindings/src/FocusZone/wrapInFocusZone.ts new file mode 100644 index 0000000000..ae60eb3884 --- /dev/null +++ b/packages/react-bindings/src/FocusZone/wrapInFocusZone.ts @@ -0,0 +1,36 @@ +import { AccessibilityDefinition, FocusZoneMode } from '@stardust-ui/accessibility' +import * as React from 'react' + +import FocusZone from './FocusZone' +import { FocusZoneProps } from './FocusZone.types' +import { FOCUSZONE_WRAP_ATTRIBUTE } from './focusUtilities' + +const wrapInFocusZone = ( + element: React.ReactElement, + accessibility: AccessibilityDefinition, + rtl: boolean, +) => { + if (accessibility.focusZone && accessibility.focusZone.mode === FocusZoneMode.Wrap) { + return React.createElement( + FocusZone, + { + [FOCUSZONE_WRAP_ATTRIBUTE]: true, + ...accessibility.focusZone.props, + isRtl: rtl, + } as FocusZoneProps & { [FOCUSZONE_WRAP_ATTRIBUTE]: boolean }, + element, + ) + } + + if (accessibility.focusZone && accessibility.focusZone.mode === FocusZoneMode.Embed) { + return React.createElement(FocusZone, { + ...element.props, + ...accessibility.focusZone.props, + isRtl: rtl, + }) + } + + return element +} + +export default wrapInFocusZone diff --git a/packages/react-bindings/src/accessibility/getAccessibility.ts b/packages/react-bindings/src/accessibility/getAccessibility.ts new file mode 100644 index 0000000000..2a4369e302 --- /dev/null +++ b/packages/react-bindings/src/accessibility/getAccessibility.ts @@ -0,0 +1,50 @@ +import { Accessibility, AccessibilityDefinition } from '@stardust-ui/accessibility' + +import getKeyDownHandlers from './getKeyDownHandlers' +import { AccessibilityActionHandlers, AccessibilityBehavior } from './types' + +const emptyBehavior: AccessibilityBehavior = { + attributes: {}, + keyHandlers: {}, +} + +const getAccessibility = >( + displayName: string, + behavior: Accessibility, + behaviorProps: Props, + isRtlEnabled: boolean, + actionHandlers?: AccessibilityActionHandlers, +): AccessibilityBehavior => { + if (behavior === null || behavior === undefined) { + return emptyBehavior + } + + const definition: AccessibilityDefinition = behavior(behaviorProps) + const keyHandlers = definition.keyActions + ? // @ts-ignore FIX ME + getKeyDownHandlers(actionHandlers, definition.keyActions, isRtlEnabled) + : {} + + if (process.env.NODE_ENV !== 'production') { + // For the non-production builds we enable the runtime accessibility attributes validator. + // We're adding the data-aa-class attribute which is being consumed by the validator, the + // schema is located in @stardust-ui/ability-attributes package. + if (definition.attributes) { + const slotNames = Object.keys(definition.attributes) + slotNames.forEach(slotName => { + // @ts-ignore FIX ME + definition.attributes[slotName]['data-aa-class'] = `${displayName}${ + slotName === 'root' ? '' : `__${slotName}` + }` + }) + } + } + + return { + ...emptyBehavior, + ...definition, + keyHandlers, + } +} + +export default getAccessibility diff --git a/packages/react/src/lib/getKeyDownHandlers.ts b/packages/react-bindings/src/accessibility/getKeyDownHandlers.ts similarity index 61% rename from packages/react/src/lib/getKeyDownHandlers.ts rename to packages/react-bindings/src/accessibility/getKeyDownHandlers.ts index d7c94c3b0f..7cde357976 100644 --- a/packages/react/src/lib/getKeyDownHandlers.ts +++ b/packages/react-bindings/src/accessibility/getKeyDownHandlers.ts @@ -1,10 +1,10 @@ import { KeyActions } from '@stardust-ui/accessibility' -import * as _ from 'lodash' +// @ts-ignore import * as keyboardKey from 'keyboard-key' import * as React from 'react' import shouldHandleOnKeys from './shouldHandleOnKeys' -import { AccessibilityActionHandlers, AccessibilityKeyHandlers } from './accessibility/reactTypes' +import { AccessibilityActionHandlers, AccessibilityKeyHandlers } from './types' const rtlKeyMap = { [keyboardKey.ArrowRight]: keyboardKey.ArrowLeft, @@ -14,31 +14,33 @@ const rtlKeyMap = { /** * Assigns onKeyDown handler to the slot element, based on Component's actions * and keys mappings defined in Accessibility behavior - * @param {AccessibilityActionHandlers} componentActionHandlers Actions handlers defined in a component. - * @param {KeyActions} behaviorKeyActions Mappings of actions and keys defined in Accessibility behavior. + * @param {AccessibilityActionHandlers} actionHandlers Actions handlers defined in a component. + * @param {KeyActions} behaviorActions Mappings of actions and keys defined in Accessibility behavior. * @param {boolean} isRtlEnabled Indicates if Left and Right arrow keys should be swapped in RTL mode. */ const getKeyDownHandlers = ( - componentActionHandlers: AccessibilityActionHandlers, - behaviorKeyActions: KeyActions, + actionHandlers: AccessibilityActionHandlers, + behaviorActions: KeyActions, isRtlEnabled?: boolean, ): AccessibilityKeyHandlers => { + const componentHandlerNames = Object.keys(actionHandlers) const keyHandlers = {} - if (!componentActionHandlers || !behaviorKeyActions) return keyHandlers + if (!actionHandlers || !behaviorActions) return keyHandlers - for (const componentPart in behaviorKeyActions) { - const componentPartKeyAction = behaviorKeyActions[componentPart] - const handledActions = _.intersection( - _.keys(componentPartKeyAction), - _.keys(componentActionHandlers), + for (const slotName in behaviorActions) { + const behaviorSlotAction = behaviorActions[slotName] + const handledActions = Object.keys(behaviorSlotAction).filter( + actionName => componentHandlerNames.indexOf(actionName) !== -1, ) + if (!handledActions.length) continue - keyHandlers[componentPart] = { + // @ts-ignore FIX ME + keyHandlers[slotName] = { onKeyDown: (event: React.KeyboardEvent) => { handledActions.forEach(actionName => { - let keyCombinations = componentPartKeyAction[actionName].keyCombinations + let keyCombinations = behaviorSlotAction[actionName].keyCombinations if (isRtlEnabled) { keyCombinations = keyCombinations.map(keyCombination => { @@ -51,7 +53,7 @@ const getKeyDownHandlers = ( } if (shouldHandleOnKeys(event, keyCombinations)) { - componentActionHandlers[actionName](event) + actionHandlers[actionName](event) } }) }, diff --git a/packages/react/src/lib/shouldHandleOnKeys.ts b/packages/react-bindings/src/accessibility/shouldHandleOnKeys.ts similarity index 88% rename from packages/react/src/lib/shouldHandleOnKeys.ts rename to packages/react-bindings/src/accessibility/shouldHandleOnKeys.ts index 31866d423f..5b9eb6e3e5 100644 --- a/packages/react/src/lib/shouldHandleOnKeys.ts +++ b/packages/react-bindings/src/accessibility/shouldHandleOnKeys.ts @@ -1,6 +1,6 @@ import { KeyCombinations } from '@stardust-ui/accessibility' +// @ts-ignore import * as keyboardKey from 'keyboard-key' -import * as _ from 'lodash' import * as React from 'react' const isKeyModifiersMatch = (modifierValue: boolean, combinationValue?: boolean) => { @@ -15,9 +15,8 @@ const shouldHandleOnKeys = ( event: React.KeyboardEvent, keysCombinations: KeyCombinations[], ): boolean => - _.some( - keysCombinations, - (keysCombination: KeyCombinations) => + keysCombinations.some( + keysCombination => keysCombination.keyCode === keyboardKey.getCode(event) && isKeyModifiersMatch(event.altKey, keysCombination.altKey) && isKeyModifiersMatch(event.shiftKey, keysCombination.shiftKey) && diff --git a/packages/react/src/lib/accessibility/reactTypes.ts b/packages/react-bindings/src/accessibility/types.ts similarity index 58% rename from packages/react/src/lib/accessibility/reactTypes.ts rename to packages/react-bindings/src/accessibility/types.ts index ccf0144cfc..c5dcaa9f3a 100644 --- a/packages/react/src/lib/accessibility/reactTypes.ts +++ b/packages/react-bindings/src/accessibility/types.ts @@ -1,11 +1,7 @@ import { AccessibilityAttributesBySlot, AccessibilityDefinition } from '@stardust-ui/accessibility' import * as React from 'react' -/* - * Accessibility types for React implementation. - */ - -export interface ReactAccessibilityBehavior extends AccessibilityDefinition { +export interface AccessibilityBehavior extends AccessibilityDefinition { attributes: AccessibilityAttributesBySlot keyHandlers: AccessibilityKeyHandlers } @@ -15,11 +11,11 @@ export type AccessibilityKeyHandlers = { } export type AccessibilityHandlerProps = { - onKeyDown?: KeyboardEventHandler + onKeyDown?: AccessibilityKeyboardHandler } export type AccessibilityActionHandlers = { - [actionName: string]: KeyboardEventHandler + [actionName: string]: AccessibilityKeyboardHandler } -export type KeyboardEventHandler = (event: React.KeyboardEvent) => void +export type AccessibilityKeyboardHandler = (event: React.KeyboardEvent) => void diff --git a/packages/react-bindings/src/hooks/useAccessibility.ts b/packages/react-bindings/src/hooks/useAccessibility.ts new file mode 100644 index 0000000000..ff04fcba2c --- /dev/null +++ b/packages/react-bindings/src/hooks/useAccessibility.ts @@ -0,0 +1,67 @@ +import { Accessibility, AccessibilityAttributesBySlot } from '@stardust-ui/accessibility' +import * as React from 'react' + +import getAccessibility from '../accessibility/getAccessibility' +import { AccessibilityBehavior, AccessibilityActionHandlers } from '../accessibility/types' + +type UseAccessibilityOptions = { + actionHandlers?: AccessibilityActionHandlers + debugName?: string + mapPropsToBehavior?: () => Props + rtl?: boolean +} + +const mergeProps = >( + slotName: string, + slotProps: SlotProps, + definition: AccessibilityBehavior, +): SlotProps & Partial => { + const finalProps = { + ...definition.attributes[slotName], + ...slotProps, + } + const slotHandlers = definition.keyHandlers[slotName] + + if (slotHandlers) { + const onKeyDown = (e: React.KeyboardEvent, ...args: any[]) => { + definition.keyHandlers[slotName].onKeyDown(e) + if (slotProps.onKeyDown) { + slotProps.onKeyDown(e, ...args) + } + } + + finalProps.onKeyDown = onKeyDown + } + + return finalProps +} + +const useAccessibility = ( + behavior: Accessibility, + options: UseAccessibilityOptions = {}, +) => { + const { + actionHandlers, + debugName = 'Undefined', + mapPropsToBehavior = () => ({}), + rtl = false, + } = options + const definition = getAccessibility( + debugName, + behavior, + mapPropsToBehavior(), + rtl, + actionHandlers, + ) + + const latestDefinition = React.useRef(definition) + latestDefinition.current = definition + + return React.useCallback( + >(slotName: string, slotProps: SlotProps) => + mergeProps(slotName, slotProps, latestDefinition.current), + [], + ) +} + +export default useAccessibility diff --git a/packages/react-bindings/src/index.ts b/packages/react-bindings/src/index.ts index 986ab73f30..4e9dff1be8 100644 --- a/packages/react-bindings/src/index.ts +++ b/packages/react-bindings/src/index.ts @@ -1,5 +1,5 @@ -export { default as unstable_useDispatchEffect } from './hooks/useDispatchEffect' -export { default as useStateManager } from './hooks/useStateManager' +export { default as unstable_getAccessibility } from './accessibility/getAccessibility' +export * from './accessibility/types' export { default as AutoFocusZone } from './FocusZone/AutoFocusZone' export * from './FocusZone/AutoFocusZone.types' @@ -8,6 +8,11 @@ export * from './FocusZone/FocusTrapZone.types' export { default as FocusZone } from './FocusZone/FocusZone' export * from './FocusZone/FocusZone.types' export * from './FocusZone/focusUtilities' +export { default as unstable_wrapInFocusZone } from './FocusZone/wrapInFocusZone' + +export { default as useAccessibility } from './hooks/useAccessibility' +export { default as unstable_useDispatchEffect } from './hooks/useDispatchEffect' +export { default as useStateManager } from './hooks/useStateManager' export { default as callable } from './utils/callable' export { default as getElementType } from './utils/getElementType' diff --git a/packages/react/test/specs/lib/getKeyDownHandlers-test.ts b/packages/react-bindings/test/accesibility/getKeyDownHandlers-test.ts similarity index 98% rename from packages/react/test/specs/lib/getKeyDownHandlers-test.ts rename to packages/react-bindings/test/accesibility/getKeyDownHandlers-test.ts index e26ff4ff4b..a69dba5c97 100644 --- a/packages/react/test/specs/lib/getKeyDownHandlers-test.ts +++ b/packages/react-bindings/test/accesibility/getKeyDownHandlers-test.ts @@ -1,4 +1,4 @@ -import getKeyDownHandlers from 'src/lib/getKeyDownHandlers' +import getKeyDownHandlers from '../../src/accessibility/getKeyDownHandlers' import * as keyboardKey from 'keyboard-key' const testKeyCode = keyboardKey.ArrowRight diff --git a/packages/react/test/specs/lib/shouldHandleOnKeys-test.ts b/packages/react-bindings/test/accesibility/shouldHandleOnKeys-test.ts similarity index 96% rename from packages/react/test/specs/lib/shouldHandleOnKeys-test.ts rename to packages/react-bindings/test/accesibility/shouldHandleOnKeys-test.ts index 6c7b8a4043..b7e5271025 100644 --- a/packages/react/test/specs/lib/shouldHandleOnKeys-test.ts +++ b/packages/react-bindings/test/accesibility/shouldHandleOnKeys-test.ts @@ -1,4 +1,4 @@ -import shouldHandleOnKeys from 'src/lib/shouldHandleOnKeys' +import shouldHandleOnKeys from '../../src/accessibility/shouldHandleOnKeys' const getEventArg = ( keyCode: number, diff --git a/packages/react-bindings/test/hooks/useAccessibility-test.tsx b/packages/react-bindings/test/hooks/useAccessibility-test.tsx new file mode 100644 index 0000000000..bd67b72876 --- /dev/null +++ b/packages/react-bindings/test/hooks/useAccessibility-test.tsx @@ -0,0 +1,118 @@ +import { Accessibility } from '@stardust-ui/accessibility' +import { useAccessibility } from '@stardust-ui/react-bindings' +import { shallow } from 'enzyme' +// @ts-ignore +import * as keyboardKey from 'keyboard-key' +import * as React from 'react' + +type TestBehaviorProps = { + disabled: boolean +} + +const testBehavior: Accessibility = props => ({ + attributes: { + root: { + 'aria-disabled': props.disabled, + tabIndex: 1, + }, + img: { + 'aria-label': 'Pixel', + role: 'presentation', + }, + }, + keyActions: { + root: { + click: { + keyCombinations: [{ keyCode: keyboardKey.ArrowDown }], + }, + }, + }, +}) + +type TestComponentProps = { + disabled?: boolean + onClick?: (e: React.KeyboardEvent, slotName: string) => void + onKeyDown?: React.KeyboardEventHandler +} & React.HTMLAttributes + +const TestComponent: React.FunctionComponent = props => { + const { disabled, onClick, onKeyDown, ...rest } = props + const getProps = useAccessibility(testBehavior, { + mapPropsToBehavior: () => ({ + disabled, + }), + actionHandlers: { + click: (e: React.KeyboardEvent) => { + if (onClick) onClick(e, 'root') + }, + }, + }) + + return ( +
+ +
+ ) +} + +describe('useAccessibility', () => { + it('sets attributes', () => { + const wrapper = shallow() + + expect(wrapper.find('div').prop('tabIndex')).toBe(1) + expect(wrapper.find('img').prop('role')).toBe('presentation') + }) + + it('attributes can be conditional', () => { + expect( + shallow() + .find('div') + .prop('aria-disabled'), + ).toBe(true) + expect( + shallow() + .find('div') + .prop('aria-disabled'), + ).toBe(false) + }) + + it('attributes can be overridden', () => { + expect( + shallow() + .find('div') + .prop('tabIndex'), + ).toBe(-1) + }) + + it('adds event handlers', () => { + const onKeyDown = jest.fn() + const onClick = jest.fn() + const wrapper = shallow() + + wrapper + .find('div') + .simulate('click') + .simulate('keydown', { + keyCode: keyboardKey.ArrowDown, + }) + + expect(onKeyDown).toBeCalledTimes(1) + expect(onKeyDown).toBeCalledWith( + expect.objectContaining({ + keyCode: keyboardKey.ArrowDown, + }), + ) + + expect(onClick).toBeCalledTimes(1) + expect(onClick).toBeCalledWith( + expect.objectContaining({ + keyCode: keyboardKey.ArrowDown, + }), + 'root', + ) + }) +}) diff --git a/packages/react/src/components/Menu/Menu.tsx b/packages/react/src/components/Menu/Menu.tsx index e90908e0bb..a5e5f3f067 100644 --- a/packages/react/src/components/Menu/Menu.tsx +++ b/packages/react/src/components/Menu/Menu.tsx @@ -1,4 +1,5 @@ import { Accessibility, menuBehavior } from '@stardust-ui/accessibility' +import { AccessibilityBehavior } from '@stardust-ui/react-bindings' import * as customPropTypes from '@stardust-ui/react-proptypes' import * as _ from 'lodash' import * as PropTypes from 'prop-types' @@ -18,7 +19,6 @@ import { import { mergeComponentVariables } from '../../lib/mergeThemes' import MenuItem, { MenuItemProps } from './MenuItem' -import { ReactAccessibilityBehavior } from '../../lib/accessibility/reactTypes' import { ComponentVariablesObject, ComponentSlotStylesPrepared } from '../../themes/types' import { WithAsProp, @@ -169,7 +169,7 @@ class Menu extends AutoControlledComponent, MenuState> { renderItems = ( styles: ComponentSlotStylesPrepared, variables: ComponentVariablesObject, - accessibility: ReactAccessibilityBehavior, + accessibility: AccessibilityBehavior, ) => { const { iconOnly, diff --git a/packages/react/src/components/Popup/Popup.tsx b/packages/react/src/components/Popup/Popup.tsx index 9fefa35fa8..0f8d1c6b36 100644 --- a/packages/react/src/components/Popup/Popup.tsx +++ b/packages/react/src/components/Popup/Popup.tsx @@ -1,5 +1,9 @@ import { Accessibility, popupBehavior } from '@stardust-ui/accessibility' -import { AutoFocusZoneProps, FocusTrapZoneProps } from '@stardust-ui/react-bindings' +import { + AccessibilityBehavior, + AutoFocusZoneProps, + FocusTrapZoneProps, +} from '@stardust-ui/react-bindings' import { EventListener } from '@stardust-ui/react-component-event-listener' import { NodeRef, Unstable_NestingAuto } from '@stardust-ui/react-component-nesting-registry' import { handleRef, toRefObject, Ref } from '@stardust-ui/react-component-ref' @@ -32,7 +36,6 @@ import { } from '../../lib/positioner' import PopupContent, { PopupContentProps } from './PopupContent' -import { ReactAccessibilityBehavior } from '../../lib/accessibility/reactTypes' import { createShorthandFactory, ShorthandFactory } from '../../lib/factories' import createReferenceFromContextClick from './createReferenceFromContextClick' import isRightClick from '../../lib/isRightClick' @@ -454,7 +457,7 @@ export default class Popup extends AutoControlledComponent { const { diff --git a/packages/react/src/components/Portal/Portal.tsx b/packages/react/src/components/Portal/Portal.tsx index 67d615b37b..b605204e29 100644 --- a/packages/react/src/components/Portal/Portal.tsx +++ b/packages/react/src/components/Portal/Portal.tsx @@ -1,5 +1,9 @@ import { AccessibilityAttributes } from '@stardust-ui/accessibility' -import { FocusTrapZone, FocusTrapZoneProps } from '@stardust-ui/react-bindings' +import { + AccessibilityHandlerProps, + FocusTrapZone, + FocusTrapZoneProps, +} from '@stardust-ui/react-bindings' import { EventListener } from '@stardust-ui/react-component-event-listener' import { handleRef, Ref, toRefObject } from '@stardust-ui/react-component-ref' import * as customPropTypes from '@stardust-ui/react-proptypes' @@ -17,7 +21,6 @@ import { rtlTextContainer, } from '../../lib' import PortalInner from './PortalInner' -import { AccessibilityHandlerProps } from '../../lib/accessibility/reactTypes' export type TriggerAccessibility = { attributes?: AccessibilityAttributes diff --git a/packages/react/src/components/Tooltip/Tooltip.tsx b/packages/react/src/components/Tooltip/Tooltip.tsx index 8356eb8b9d..8a7cce22de 100644 --- a/packages/react/src/components/Tooltip/Tooltip.tsx +++ b/packages/react/src/components/Tooltip/Tooltip.tsx @@ -1,3 +1,5 @@ +import { Accessibility, tooltipBehavior } from '@stardust-ui/accessibility' +import { AccessibilityBehavior } from '@stardust-ui/react-bindings' import { toRefObject, Ref } from '@stardust-ui/react-component-ref' import * as customPropTypes from '@stardust-ui/react-proptypes' import * as React from 'react' @@ -26,8 +28,6 @@ import { PopperChildrenProps, } from '../../lib/positioner' import TooltipContent, { TooltipContentProps } from './TooltipContent' -import { Accessibility, tooltipBehavior } from '@stardust-ui/accessibility' -import { ReactAccessibilityBehavior } from '../../lib/accessibility/reactTypes' import PortalInner from '../Portal/PortalInner' export interface TooltipSlotClassNames { @@ -232,7 +232,7 @@ export default class Tooltip extends AutoControlledComponent { const { content, pointing } = this.props diff --git a/packages/react/src/components/Tree/Tree.tsx b/packages/react/src/components/Tree/Tree.tsx index 334e4d5b44..ce03789f14 100644 --- a/packages/react/src/components/Tree/Tree.tsx +++ b/packages/react/src/components/Tree/Tree.tsx @@ -1,5 +1,5 @@ import { Accessibility, treeBehavior } from '@stardust-ui/accessibility' -import { getNextElement } from '@stardust-ui/react-bindings' +import { AccessibilityBehavior, getNextElement } from '@stardust-ui/react-bindings' import * as customPropTypes from '@stardust-ui/react-proptypes' import * as _ from 'lodash' import * as PropTypes from 'prop-types' @@ -27,7 +27,6 @@ import { } from '../../types' import { hasSubtree, removeItemAtIndex } from './lib' import { TreeTitleProps } from './TreeTitle' -import { ReactAccessibilityBehavior } from '../../lib/accessibility/reactTypes' export interface TreeSlotClassNames { item: string @@ -261,7 +260,7 @@ class Tree extends AutoControlledComponent, TreeState> { }, }) - renderContent(accessibility: ReactAccessibilityBehavior): React.ReactElement[] { + renderContent(accessibility: AccessibilityBehavior): React.ReactElement[] { const { itemsForRender } = this.state const { items, renderItemTitle } = this.props diff --git a/packages/react/src/components/Tree/TreeItem.tsx b/packages/react/src/components/Tree/TreeItem.tsx index 8f0d34348d..a511f50268 100644 --- a/packages/react/src/components/Tree/TreeItem.tsx +++ b/packages/react/src/components/Tree/TreeItem.tsx @@ -1,4 +1,5 @@ import { Accessibility, treeItemBehavior } from '@stardust-ui/accessibility' +import { AccessibilityBehavior } from '@stardust-ui/react-bindings' import * as customPropTypes from '@stardust-ui/react-proptypes' import * as _ from 'lodash' import * as PropTypes from 'prop-types' @@ -26,7 +27,6 @@ import { ShorthandCollection, } from '../../types' import { hasSubtree } from './lib' -import { ReactAccessibilityBehavior } from '../../lib/accessibility/reactTypes' export interface TreeItemSlotClassNames { title: string @@ -191,7 +191,7 @@ class TreeItem extends UIComponent, TreeItemState> { }, }) - renderContent(accessibility: ReactAccessibilityBehavior) { + renderContent(accessibility: AccessibilityBehavior) { const { title, renderItemTitle, open, level, index } = this.props const { hasSubtree, treeSize } = this.state diff --git a/packages/react/src/lib/UIComponent.tsx b/packages/react/src/lib/UIComponent.tsx index fac690eaac..51a0c685ff 100644 --- a/packages/react/src/lib/UIComponent.tsx +++ b/packages/react/src/lib/UIComponent.tsx @@ -1,10 +1,10 @@ +import { AccessibilityActionHandlers } from '@stardust-ui/react-bindings' import * as React from 'react' import * as _ from 'lodash' // @ts-ignore We have this export in package, but it is not present in typings import { ThemeContext } from 'react-fela' import renderComponent, { RenderResultConfig } from './renderComponent' -import { AccessibilityActionHandlers } from './accessibility/reactTypes' // TODO @Bugaa92: deprecated by createComponent.tsx class UIComponent extends React.Component { diff --git a/packages/react/src/lib/applyAccessibilityKeyHandlers.ts b/packages/react/src/lib/applyAccessibilityKeyHandlers.ts index 4b138434ce..b45e2f9018 100644 --- a/packages/react/src/lib/applyAccessibilityKeyHandlers.ts +++ b/packages/react/src/lib/applyAccessibilityKeyHandlers.ts @@ -1,8 +1,11 @@ +import { + AccessibilityHandlerProps, + AccessibilityKeyboardHandler, +} from '@stardust-ui/react-bindings' import * as _ from 'lodash' import * as React from 'react' import { Props, ShorthandValue } from '../types' -import { AccessibilityHandlerProps, KeyboardEventHandler } from './accessibility/reactTypes' // Makes sure that 'onKeyDown' is correctly overriden on the slots. // It should be applied after 'unhandledProps' because they can contain 'onKeyDown' from user and is handled by UTs in isConformant() @@ -20,7 +23,7 @@ const applyAccessibilityKeyHandlers = ( return _.mapValues( keyHandlers, - (accessibilityHandler: KeyboardEventHandler, handleName: string) => ( + (accessibilityHandler: AccessibilityKeyboardHandler, handleName: string) => ( e: React.KeyboardEvent, ...args: any[] ) => { diff --git a/packages/react/src/lib/createComponent.ts b/packages/react/src/lib/createComponent.ts index b64d2eecad..3ff2bbc37c 100644 --- a/packages/react/src/lib/createComponent.ts +++ b/packages/react/src/lib/createComponent.ts @@ -1,10 +1,10 @@ +import { AccessibilityActionHandlers } from '@stardust-ui/react-bindings' import * as React from 'react' import * as _ from 'lodash' // @ts-ignore import { ThemeContext } from 'react-fela' import renderComponent, { RenderResultConfig } from './renderComponent' -import { AccessibilityActionHandlers } from './accessibility/reactTypes' import { createShorthandFactory, ShorthandFactory } from './factories' import { ObjectOf, ProviderContextPrepared } from '../types' diff --git a/packages/react/src/lib/createStardustComponent.tsx b/packages/react/src/lib/createStardustComponent.tsx index 051148cd69..7697c37ddf 100644 --- a/packages/react/src/lib/createStardustComponent.tsx +++ b/packages/react/src/lib/createStardustComponent.tsx @@ -1,13 +1,13 @@ +import { AccessibilityBehavior, AccessibilityActionHandlers } from '@stardust-ui/react-bindings' import createComponentInternal, { CreateComponentReturnType } from './createComponent' import * as React from 'react' import * as _ from 'lodash' import { ComponentSlotClasses, ComponentSlotStylesPrepared } from '../themes/types' -import { ReactAccessibilityBehavior, AccessibilityActionHandlers } from './accessibility/reactTypes' import { ObjectOf } from '../types' export interface RenderStardustResultConfig { - accessibility: ReactAccessibilityBehavior + accessibility: AccessibilityBehavior classes: ComponentSlotClasses rtl: boolean styles: ComponentSlotStylesPrepared diff --git a/packages/react/src/lib/renderComponent.tsx b/packages/react/src/lib/renderComponent.tsx index 2f9cc118c5..6be8e3dede 100644 --- a/packages/react/src/lib/renderComponent.tsx +++ b/packages/react/src/lib/renderComponent.tsx @@ -1,16 +1,11 @@ import { - AccessibilityDefinition, - FocusZoneMode, - FocusZoneDefinition, - Accessibility, -} from '@stardust-ui/accessibility' -import { + AccessibilityBehavior, + AccessibilityActionHandlers, callable, - FocusZone, - FocusZoneProps, - FOCUSZONE_WRAP_ATTRIBUTE, getElementType, getUnhandledProps, + unstable_getAccessibility as getAccessibility, + unstable_wrapInFocusZone as wrapInFocusZone, } from '@stardust-ui/react-bindings' import cx from 'classnames' import * as React from 'react' @@ -28,8 +23,6 @@ import { ComponentSlotStylesInput, } from '../themes/types' import { Props, ProviderContextPrepared } from '../types' -import { ReactAccessibilityBehavior, AccessibilityActionHandlers } from './accessibility/reactTypes' -import getKeyDownHandlers from './getKeyDownHandlers' import { emptyTheme, mergeComponentStyles, mergeComponentVariables } from './mergeThemes' import createAnimationStyles from './createAnimationStyles' import Debug, { isEnabled as isDebugEnabled } from './debug' @@ -40,7 +33,7 @@ export interface RenderResultConfig

{ unhandledProps: Props variables: ComponentVariablesObject styles: ComponentSlotStylesPrepared - accessibility: ReactAccessibilityBehavior + accessibility: AccessibilityBehavior rtl: boolean theme: ThemePrepared } @@ -58,94 +51,6 @@ export interface RenderConfig

{ saveDebug: (debug: Debug | null) => void } -const emptyBehavior: ReactAccessibilityBehavior = { - attributes: {}, - keyHandlers: {}, -} - -const getAccessibility = ( - displayName: string, - props: State & PropsWithVarsAndStyles & { accessibility?: Accessibility }, - actionHandlers: AccessibilityActionHandlers, - isRtlEnabled: boolean, -): ReactAccessibilityBehavior => { - const { accessibility } = props - - if (_.isNil(accessibility)) { - return emptyBehavior - } - - const definition: AccessibilityDefinition = accessibility(props) - const keyHandlers = getKeyDownHandlers(actionHandlers, definition.keyActions, isRtlEnabled) - - if (process.env.NODE_ENV !== 'production') { - // For the non-production builds we enable the runtime accessibility attributes validator. - // We're adding the data-aa-class attribute which is being consumed by the validator, the - // schema is located in @stardust-ui/ability-attributes package. - if (definition.attributes) { - const slotNames = Object.keys(definition.attributes) - slotNames.forEach(slotName => { - definition.attributes[slotName]['data-aa-class'] = `${displayName}${ - slotName === 'root' ? '' : `__${slotName}` - }` - }) - } - } - - return { - ...emptyBehavior, - ...definition, - keyHandlers, - } -} - -/** - * This function provides compile-time type checking for the following: - * - if FocusZone implements FocusZone interface, - * - if FocusZone properties extend FocusZoneProps, and - * - if the passed properties extend FocusZoneProps. - * - * Should the FocusZone implementation change at any time, this function should provide a compile-time guarantee - * that the new implementation is backwards compatible with the old implementation. - */ -function wrapInGenericFocusZone< - COMPONENT_PROPS extends FocusZoneProps, - PROPS extends COMPONENT_PROPS, - COMPONENT extends FocusZone & React.Component ->( - FocusZone: { new (...args: any[]): COMPONENT }, - props: PROPS | undefined, - children: React.ReactNode, -) { - props[FOCUSZONE_WRAP_ATTRIBUTE] = true - return {children} -} - -const renderWithFocusZone =

( - render: RenderComponentCallback

, - focusZoneDefinition: FocusZoneDefinition, - config: RenderResultConfig

, -): any => { - if (focusZoneDefinition.mode === FocusZoneMode.Wrap) { - return wrapInGenericFocusZone( - FocusZone, - { - ...focusZoneDefinition.props, - isRtl: config.rtl, - }, - render(config), - ) - } - if (focusZoneDefinition.mode === FocusZoneMode.Embed) { - const originalElementType = config.ElementType - config.ElementType = FocusZone as any - config.unhandledProps = { ...config.unhandledProps, ...focusZoneDefinition.props } - config.unhandledProps.as = originalElementType - config.unhandledProps.isRtl = config.rtl - } - return render(config) -} - const resolveStyles = ( styles: ComponentSlotStylesInput, styleParam: ComponentStyleFunctionParam, @@ -199,11 +104,12 @@ const renderComponent =

( { root: animationCSSProp }, ) - const accessibility: ReactAccessibilityBehavior = getAccessibility( + const accessibility: AccessibilityBehavior = getAccessibility( displayName, + props.accessibility, stateAndProps, - actionHandlers, rtl, + actionHandlers, ) const unhandledProps = getUnhandledProps(handledProps, props) @@ -249,10 +155,6 @@ const renderComponent =

( theme, } - if (accessibility.focusZone) { - return renderWithFocusZone(render, accessibility.focusZone, resolvedConfig) - } - // conditionally add sources for evaluating debug information to component if (isDebugEnabled) { saveDebug( @@ -267,7 +169,7 @@ const renderComponent =

( ) } - return render(resolvedConfig) + return wrapInFocusZone(render(resolvedConfig), accessibility, rtl) } export default renderComponent From 2562d9cb02f18dee2f3fd23fc54c793587113636 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Fri, 4 Oct 2019 21:15:41 +0200 Subject: [PATCH 02/19] add behaviors to project tests --- build/gulp/tasks/test-projects/cra/App.tsx | 3 ++- build/gulp/tasks/test-projects/typings/index.tsx | 2 +- packages/accessibility/src/behaviors/Image/imageBehavior.ts | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/gulp/tasks/test-projects/cra/App.tsx b/build/gulp/tasks/test-projects/cra/App.tsx index ad08bca953..7cd7c5e4be 100644 --- a/build/gulp/tasks/test-projects/cra/App.tsx +++ b/build/gulp/tasks/test-projects/cra/App.tsx @@ -8,6 +8,7 @@ import { Header, Icon, Image, + imageBehavior, Input, Popup, Provider, @@ -29,7 +30,7 @@ class App extends React.Component {