Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Fixes
- Add aria posinset and setsize, hide menu indicator from narration @jurokapsiar ([#1066](https://github.com/stardust-ui/react/pull/1066))
- Fix applying accessibility key handlers @layershifter ([#1072](https://github.com/stardust-ui/react/pull/1072))

### Features
- Add `Alert` component @Bugaa92 ([#1063](https://github.com/stardust-ui/react/pull/1063))
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/Attachment/Attachment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
createShorthandFactory,
commonPropTypes,
isFromKeyboard,
applyAccessibilityKeyHandlers,
} from '../../lib'
import Icon from '../Icon/Icon'
import Button from '../Button/Button'
Expand Down Expand Up @@ -100,8 +101,8 @@ class Attachment extends UIComponent<ReactProps<AttachmentProps>, AttachmentStat
onClick={this.handleClick}
onFocus={this.handleFocus}
{...accessibility.attributes.root}
{...accessibility.keyHandlers.root}
{...unhandledProps}
{...applyAccessibilityKeyHandlers(accessibility.keyHandlers.root, unhandledProps)}
>
{icon && (
<div className={classes.icon}>
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
UIComponent,
commonPropTypes,
rtlTextContainer,
applyAccessibilityKeyHandlers,
} from '../../lib'
import ChatItem from './ChatItem'
import ChatMessage from './ChatMessage'
Expand Down Expand Up @@ -61,9 +62,9 @@ class Chat extends UIComponent<ReactProps<ChatProps>, any> {
<ElementType
className={classes.root}
{...accessibility.attributes.root}
{...accessibility.keyHandlers.root}
{...rtlTextContainer.getAttributes({ forElements: [children] })}
{...unhandledProps}
{...applyAccessibilityKeyHandlers(accessibility.keyHandlers.root, unhandledProps)}
>
{childrenExist(children) ? children : _.map(items, item => ChatItem.create(item))}
</ElementType>
Expand Down
9 changes: 5 additions & 4 deletions packages/react/src/components/Chat/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
commonPropTypes,
isFromKeyboard,
rtlTextContainer,
applyAccessibilityKeyHandlers,
} from '../../lib'
import { ReactProps, ShorthandValue, ComponentEventHandler } from '../../types'
import { chatMessageBehavior, toolbarBehavior } from '../../lib/accessibility'
Expand Down Expand Up @@ -190,13 +191,13 @@ class ChatMessage extends UIComponent<ReactProps<ChatMessageProps>, ChatMessageS

return (
<ElementType
{...accessibility.attributes.root}
{...accessibility.keyHandlers.root}
{...rtlTextContainer.getAttributes({ forElements: [children] })}
{...unhandledProps}
onBlur={this.handleBlur}
onFocus={this.handleFocus}
className={className}
{...accessibility.attributes.root}
{...unhandledProps}
{...applyAccessibilityKeyHandlers(accessibility.keyHandlers.root, unhandledProps)}
{...rtlTextContainer.getAttributes({ forElements: [children] })}
>
{childrenPropExists ? (
children
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ContentComponentProps,
AutoControlledComponent,
doesNodeContainClick,
applyAccessibilityKeyHandlers,
} from '../../lib'
import { dialogBehavior } from '../../lib/accessibility'
import { FocusTrapZoneProps } from '../../lib/accessibility/FocusZone'
Expand Down Expand Up @@ -191,8 +192,8 @@ class Dialog extends AutoControlledComponent<ReactProps<DialogProps>, DialogStat
<ElementType
className={classes.root}
{...accessibility.attributes.popup}
{...accessibility.keyHandlers.popup}
{...unhandledProps}
{...applyAccessibilityKeyHandlers(accessibility.keyHandlers.popup, unhandledProps)}
>
{Header.create(header, {
defaultProps: {
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/List/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ChildrenComponentProps,
commonPropTypes,
rtlTextContainer,
applyAccessibilityKeyHandlers,
} from '../../lib'
import ListItem, { ListItemProps } from './ListItem'
import { listBehavior } from '../../lib/accessibility'
Expand Down Expand Up @@ -166,9 +167,9 @@ class List extends AutoControlledComponent<ReactProps<ListProps>, ListState> {
return (
<ElementType
{...accessibility.attributes.root}
{...accessibility.keyHandlers.root}
{...rtlTextContainer.getAttributes({ forElements: [children] })}
{...unhandledProps}
{...applyAccessibilityKeyHandlers(accessibility.keyHandlers.root, unhandledProps)}
className={classes.root}
>
{childrenExist(children) ? children : this.renderItems()}
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/List/ListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
commonPropTypes,
ContentComponentProps,
isFromKeyboard,
applyAccessibilityKeyHandlers,
} from '../../lib'
import Flex from '../Flex/Flex'
import { listItemBehavior } from '../../lib/accessibility'
Expand Down Expand Up @@ -184,8 +185,8 @@ class ListItem extends UIComponent<ReactProps<ListItemProps>, ListItemState> {
onClick={this.handleClick}
onFocus={this.handleFocus}
{...accessibility.attributes.root}
{...accessibility.keyHandlers.root}
{...unhandledProps}
{...applyAccessibilityKeyHandlers(accessibility.keyHandlers.root, unhandledProps)}
>
{mediaElement}
<Flex.Item grow>
Expand Down
5 changes: 3 additions & 2 deletions packages/react/src/components/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
isFromKeyboard,
EventStack,
rtlTextContainer,
applyAccessibilityKeyHandlers,
} from '../../lib'
import Icon from '../Icon/Icon'
import Menu from './Menu'
Expand Down Expand Up @@ -218,9 +219,9 @@ class MenuItem extends AutoControlledComponent<ReactProps<MenuItemProps>, MenuIt
onBlur={this.handleBlur}
onFocus={this.handleFocus}
{...accessibility.attributes.root}
{...accessibility.keyHandlers.root}
{...unhandledProps}
{...!wrapper && { onClick: this.handleClick }}
{...applyAccessibilityKeyHandlers(accessibility.keyHandlers.root, unhandledProps)}
>
{icon &&
Icon.create(this.props.icon, {
Expand Down Expand Up @@ -259,7 +260,7 @@ class MenuItem extends AutoControlledComponent<ReactProps<MenuItemProps>, MenuIt
defaultProps: {
className: cx(MenuItem.slotClassNames.wrapper, classes.wrapper),
...accessibility.attributes.wrapper,
...accessibility.keyHandlers.wrapper,
...applyAccessibilityKeyHandlers(accessibility.keyHandlers.wrapper, wrapper),
},
overrideProps: () => ({
children: (
Expand Down
5 changes: 3 additions & 2 deletions packages/react/src/components/Popup/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as _ from 'lodash'
import { Popper, PopperChildrenProps } from 'react-popper'

import {
applyAccessibilityKeyHandlers,
childrenExist,
AutoControlledComponent,
EventStack,
Expand Down Expand Up @@ -418,9 +419,9 @@ export default class Popup extends AutoControlledComponent<ReactProps<PopupProps
}}
>
{React.cloneElement(triggerElement, {
...triggerProps,
...accessibility.attributes.trigger,
...accessibility.keyHandlers.trigger,
...triggerProps,
...applyAccessibilityKeyHandlers(accessibility.keyHandlers.trigger, triggerProps),
})}
</Ref>
)
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/RadioGroup/RadioGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ChildrenComponentProps,
commonPropTypes,
rtlTextContainer,
applyAccessibilityKeyHandlers,
} from '../../lib'
import RadioGroupItem, { RadioGroupItemProps } from './RadioGroupItem'
import { radioGroupBehavior } from '../../lib/accessibility'
Expand Down Expand Up @@ -82,9 +83,9 @@ class RadioGroup extends AutoControlledComponent<ReactProps<RadioGroupProps>, an
return (
<ElementType
{...accessibility.attributes.root}
{...accessibility.keyHandlers.root}
{...rtlTextContainer.getAttributes({ forElements: [children] })}
{...unhandledProps}
{...applyAccessibilityKeyHandlers(accessibility.keyHandlers.root, unhandledProps)}
className={classes.root}
>
{childrenExist(children) ? children : this.renderItems(vertical)}
Expand Down
7 changes: 4 additions & 3 deletions packages/react/src/components/RadioGroup/RadioGroupItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
UIComponentProps,
ChildrenComponentProps,
commonPropTypes,
applyAccessibilityKeyHandlers,
} from '../../lib'
import Box from '../Box/Box'
import { ComponentEventHandler, ReactProps, ShorthandValue } from '../../types'
Expand Down Expand Up @@ -157,13 +158,13 @@ class RadioGroupItem extends AutoControlledComponent<
return (
<Ref innerRef={this.elementRef}>
<ElementType
{...accessibility.attributes.root}
{...accessibility.keyHandlers.root}
{...unhandledProps}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onClick={this.handleClick}
className={classes.root}
{...accessibility.attributes.root}
{...unhandledProps}
{...applyAccessibilityKeyHandlers(accessibility.keyHandlers.root, unhandledProps)}
>
{Icon.create(icon || '', {
defaultProps: {
Expand Down
31 changes: 31 additions & 0 deletions packages/react/src/lib/applyAccessibilityKeyHandlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as _ from 'lodash'
import * as React from 'react'

import { Props, ShorthandValue } from '../types'
import { KeyboardHandler, OnKeyDownHandler } from './accessibility/types'

const applyAccessibilityKeyHandlers = (
keyHandlers: OnKeyDownHandler,
value: Props | ShorthandValue,
) => {
const valIsPropsObject = _.isPlainObject(value)
const valIsReactElement = React.isValidElement(value)

const slotProps =
(valIsReactElement && (value as React.ReactElement<Props>).props) ||
(valIsPropsObject && (value as Props)) ||
{}

return _.mapValues(
keyHandlers,
(accessibilityHandler: KeyboardHandler, handleName: string) => (
e: KeyboardEvent,
...args: any[]
) => {
accessibilityHandler(e)
_.invoke(slotProps, handleName, e, ...args)
},
)
}

export default applyAccessibilityKeyHandlers
5 changes: 0 additions & 5 deletions packages/react/src/lib/getKeyDownHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as _ from 'lodash'
import * as keyboardKey from 'keyboard-key'
import keyboardHandlerFilter from './keyboardHandlerFilter'
import { AccessibilityActionHandlers, ActionsKeyHandler, KeyActions } from './accessibility/types'
import { State, PropsWithVarsAndStyles } from '../themes/types'

const rtlKeyMap = {
[keyboardKey.ArrowRight]: keyboardKey.ArrowLeft,
Expand All @@ -14,13 +13,11 @@ const rtlKeyMap = {
* 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 {State & PropsWithVarsAndStyles} props The props which are used to invoke onKeyDown handler passed from top.
* @param {boolean} isRtlEnabled Indicates if Left and Right arrow keys should be swapped in RTL mode.
*/
const getKeyDownHandlers = (
componentActionHandlers: AccessibilityActionHandlers,
behaviorKeyActions: KeyActions,
props: State & PropsWithVarsAndStyles,
isRtlEnabled?: boolean,
): ActionsKeyHandler => {
const keyHandlers = {}
Expand Down Expand Up @@ -57,8 +54,6 @@ const getKeyDownHandlers = (

eventHandler && eventHandler(event)
})

_.invoke(props, 'onKeyDown', event, props)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong because it will be applied to each slot, see mini repro:
https://codesandbox.io/s/3kplmnz95q?module=/example.js
When you will click Enter on dots, you will key onKeyDown twice

},
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'mdn-polyfills/String.prototype.includes'
import * as customPropTypes from './customPropTypes'
import * as commonPropTypes from './commonPropTypes'

export { default as applyAccessibilityKeyHandlers } from './applyAccessibilityKeyHandlers'
export { default as AutoControlledComponent } from './AutoControlledComponent'
export { default as childrenExist } from './childrenExist'
export * from './colorUtils'
Expand Down
7 changes: 1 addition & 6 deletions packages/react/src/lib/renderComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,7 @@ const getAccessibility = (
defaultAccessibility ||
defaultBehavior)(props)

const keyHandlers = getKeyDownHandlers(
actionHandlers,
accessibility.keyActions,
props,
isRtlEnabled,
)
const keyHandlers = getKeyDownHandlers(actionHandlers, accessibility.keyActions, isRtlEnabled)
return {
...accessibility,
keyHandlers,
Expand Down
57 changes: 40 additions & 17 deletions packages/react/test/specs/commonTests/isConformant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,27 @@ export default (Component, options: Conformant = {}) => {
return wrapperComponent ? toNextNonTrivialChild(topLevelChildElement) : topLevelChildElement
}

const getEventTargetComponent = (wrapper: ReactWrapper, listenerName: string) => {
const eventTarget = eventTargets[listenerName]
? wrapper
.find(eventTargets[listenerName])
.hostNodes()
.first()
: wrapper
.find('[data-simulate-event-here]')
.hostNodes()
.first()

// if (eventTarget.length === 0) {
// throw new Error(
// 'The event prop was not delegated to the children. You probably ' +
// 'forgot to use `getUnhandledProps` util to spread the `unhandledProps` props.',
// )
// }

return eventTarget
}

// make sure components are properly exported
if (componentType !== 'function') {
throwError(`Components should export a class or function, got: ${componentType}.`)
Expand Down Expand Up @@ -344,6 +365,24 @@ export default (Component, options: Conformant = {}) => {

expect(element.prop(IS_FOCUSABLE_ATTRIBUTE)).toBe(false)
})

_.forEach(['onKeyDown', 'onKeyPress', 'onKeyUp'], listenerName => {
test(`handles ${listenerName} transparently`, () => {
// onKeyDown => keyDown
const eventName = _.camelCase(listenerName.replace('on', ''))
const handler = jest.fn()

const wrapperProps = {
...requiredProps,
'data-simulate-event-here': true,
[listenerName]: handler,
}
const wrapper = mount(<Component {...wrapperProps} />)

getEventTargetComponent(wrapper, listenerName).simulate(eventName)
expect(handler).toBeCalledTimes(1)
})
})
}

// ----------------------------------------
Expand Down Expand Up @@ -372,23 +411,7 @@ export default (Component, options: Conformant = {}) => {
}

const component = mount(<Component {...props} />).childAt(0)

const eventTarget = eventTargets[listenerName]
? component
.find(eventTargets[listenerName])
.hostNodes()
.first()
: component
.find('[data-simulate-event-here]')
.hostNodes()
.first()

if (eventTarget.length === 0) {
throw new Error(
'The event prop was not delegated to the children. You probably ' +
'forgot to use `getUnhandledProps` util to spread the `unhandledProps` props.',
)
}
const eventTarget = getEventTargetComponent(component, listenerName)
const customHandler: Function = eventTarget.prop(listenerName)

if (customHandler) {
Expand Down
7 changes: 6 additions & 1 deletion packages/react/test/specs/components/Input/Input-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ const setUserInputValue = (inputComp: ReactWrapper, value: string) => {
describe('Input', () => {
describe('conformance', () => {
isConformant(Input, {
eventTargets: { onChange: 'input' },
eventTargets: {
onChange: 'input',
onKeyDown: 'input',
onKeyPress: 'input',
onKeyUp: 'input',
},
})
})

Expand Down
Loading