-
Notifications
You must be signed in to change notification settings - Fork 51
feat(Accessibility): Add accessibility behavior to Popup #218
Changes from all commits
50bda74
d0df7ed
f89f9ec
01710c7
7b9e89e
a1da665
0b800d0
f25dd64
cab3a3a
03fd813
8233bf0
2ec0fae
ce91227
c57ec99
a129632
9e3a04f
37a117a
79f8205
426b09b
8b5a1a7
fa878f2
17e0cb2
283387a
fdd04ba
a68bd9b
474779a
a53bba7
df1f55f
195882a
2300e78
767fe72
dcef9cd
0b3217b
f577ef1
0f3f846
b07c474
1f0edc7
df21650
94040bc
8ed764c
921e20b
ffceb03
e210908
051509a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| export { default } from './Portal' | ||
| export { default, TriggerAccessibility } from './Portal' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import { Accessibility } from '../../interfaces' | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add description :) and if you can use format of the description/sentences which is alrady in ohters beharviors files... Thank you :) |
||
| import * as keyboardKey from 'keyboard-key' | ||
| import _ from 'lodash' | ||
|
|
||
| /** | ||
| * @description | ||
| * Adds role='button' to 'trigger' component's part, if it is not focusable element and no role attribute provided. | ||
| * Adds tabIndex='0' to 'trigger' component's part, if it is not tabbable element and no tabIndex attribute provided. | ||
| * Adds attribute 'aria-disabled=true' to 'trigger' component's part based on the property 'disabled'. | ||
| * Adds attribute 'aria-haspopup=true' to 'trigger' component's part. | ||
| */ | ||
| const PopupBehavior: Accessibility = (props: any) => ({ | ||
| attributes: { | ||
| trigger: { | ||
| role: getAriaAttributeFromProps('role', props, 'button'), | ||
| tabIndex: getAriaAttributeFromProps('tabIndex', props, '0'), | ||
| 'aria-haspopup': 'true', | ||
| 'aria-disabled': !!props['disabled'], | ||
| }, | ||
| }, | ||
| keyActions: { | ||
| trigger: { | ||
| performClick: { | ||
| keyCombinations: [{ keyCode: keyboardKey.Enter }, { keyCode: keyboardKey.Spacebar }], | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's intended behavior here? I understand I can open popup using Enter or Spacebar - where should the focus go after that? Now it remains on the trigger, there is no way how I could navigate inside the popup. What if there is a menu inside the popup? And when the focus remains on the trigger, I would expect it would be closable by Esc (copying
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Focus management would be introduced in future PR (+ FocusTrapZone which also handles focus after popup was rendered #239). This PR just adds accessibility behavior + action handlers. Regarding closing the popup by Esc, initially it was implemented as you mentioned. But after discussion with Milan, we came up that it is not correct from an accessibility point of view. Esc should work for a popup to close it when the focus is on the popup. For trigger element, it will behave the same way as clicking with a mouse - which will toggle popup currently. |
||
| }, | ||
| }, | ||
| popup: { | ||
| closeAndFocusTrigger: { | ||
sophieH29 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| keyCombinations: [{ keyCode: keyboardKey.Escape }], | ||
| }, | ||
| }, | ||
| }, | ||
| }) | ||
|
|
||
| const isFocusable = propsData => { | ||
| try { | ||
| const { as, href, type } = propsData | ||
| return ( | ||
| type === 'button' || | ||
| type === 'input' || | ||
| (type === 'a' && href !== undefined) || | ||
| as === 'button' | ||
| ) | ||
| } catch { | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| const getAriaAttributeFromProps = (attributeName: string, props: any, defaultValue: string) => { | ||
| if (!props.trigger) return undefined | ||
| if (props.trigger.props[attributeName]) { | ||
| return props.trigger.props[attributeName] | ||
| } | ||
| const { as, href } = props.trigger.props | ||
| const { type } = props.trigger | ||
| if (isFocusable({ as, href, type })) { | ||
| return undefined | ||
| } | ||
| return defaultValue | ||
| } | ||
|
|
||
| export default PopupBehavior | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is
Popupnot an auto controlled component (with the same prop name asPortal)?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, a good question :) Initial implementation of Popup doesn't expose this prop to users, and when I was making this change, I didn't want to change API of a component but leave as it is.
So only behavior and key handlers would be added. Anyway, this can be a point for discussion as well as a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, lets address it by means of the follow up PR, will take care of that