Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(selection-list): improve accessibility of selection list #10137

Merged
merged 1 commit into from
Feb 28, 2018

Conversation

devversion
Copy link
Member

@devversion devversion commented Feb 24, 2018

  • Since the selection list is still a list that contains some content, there should be a way for screenreader users, to navigate through the options (even if disabled). With this change, if the list is disabled, it's still possible to walk through the options.
  • Adds a new functionality to the ListKeyManager that allows the developer to control the items that can't be focused. skipPredicate. (e.g. for the selection list we want to make sure that users can navigate using the arrow keys to disabled items as well)
  • Testing: Fixes that by default all fake events bubble up the DOM. This works in most of the cases, but it's wrong to always bubble events. e.g. focus doesn't bubble.

Fixes #9995

@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Feb 24, 2018
@devversion devversion added the Accessibility This issue is related to accessibility (a11y) label Feb 24, 2018
expect(keyManager.activeItemIndex).toBe(2);
});

it('should be able to not skip any item', () => {
Copy link
Member

Choose a reason for hiding this comment

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

This is already covered by the custom predicate test.

});

it('should be able to skip items with a custom predicate', () => {
keyManager.skipPredicate((item: any) => item.skipItem);
Copy link
Member

Choose a reason for hiding this comment

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

I think the item here is a FakeFocusable. Alternatively you could let TS infer the type itself.

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

* Predicate function that determines items that should be skipped by the list key manager.
* By default, disabled items are skipped by the key manager.
*/
skipPredicate(predicateFn: (item: T) => boolean): this {
Copy link
Member

@crisbeto crisbeto Feb 25, 2018

Choose a reason for hiding this comment

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

Needs an @param for the predicate. Also it might be better to call it predicate, because the consumer can see that it's a function by looking at the type.

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

* Predicate function that can be used to check whether an item should be skipped
* by the key manager. By default, disabled items are skipped.
*/
private _skipPredicateFn = (item: T) => item.disabled;
Copy link
Member

Choose a reason for hiding this comment

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

Call this one skipPredicate?

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 would conflict with the function name that sets the predicate. We could name it _skipPredicate, but having the Fn suffix feels appropriate IMO.

@@ -74,7 +74,7 @@ export function createKeyboardEvent(type: string, keyCode: number, target?: Elem
}

/** Creates a fake event object with any desired event type. */
export function createFakeEvent(type: string, canBubble = true, cancelable = true) {
export function createFakeEvent(type: string, canBubble = false, cancelable = true) {
Copy link
Member

Choose a reason for hiding this comment

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

I think that we should leave this one as true, because most of the events bubble. We can opt-out of it for the ones that don't.

Copy link
Member Author

@devversion devversion Feb 25, 2018

Choose a reason for hiding this comment

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

We can also check for the type and just default to false if the event is either focus | blur.

Copy link
Member

Choose a reason for hiding this comment

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

We could, but there might be more that don't bubble. We open ourselves up to having to maintain a list of events that don't bubble.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's why I think that we should be explicit the other way. If some tests insist on event bubbling, which is very unlikely, then that test should explicitly turn on bubbling.

Bubbling will most likely causing more side-effects, so disabling it by default in tests might be better. That's at least what I noticed while working with the focus event right now.

'class': 'mat-selection-list',
'(focus)': 'focus()',
'(blur)': '_onTouched()',
'(keydown)': '_keydown($event)',
'[attr.tabindex]': 'disabled ? 0 : tabIndex',
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be just tabIndex? Either way the list will be focusable, unless the consumer passed in -1, in which case this will make it focusable only when disabled.

Copy link
Member Author

Choose a reason for hiding this comment

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

With -1 the selection list won't be tabbable anymore, but it should be tabbable if disabled for a11y.

Copy link
Member

Choose a reason for hiding this comment

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

What I meant was that with this setup, if the consumer set it to -1 explicitly, it would only be tabbable when it's disabled which seems wrong.

@@ -392,10 +406,6 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu

/** Passes relevant key presses to our key manager. */
_keydown(event: KeyboardEvent) {
if (this.disabled) {
Copy link
Member

Choose a reason for hiding this comment

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

This check still has to apply to the ENTER and SPACE keypresses.

Copy link
Member Author

@devversion devversion Feb 25, 2018

Choose a reason for hiding this comment

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

Good point. Done

@devversion devversion force-pushed the fix/a11y-selection-list branch 2 times, most recently from a625f70 to 9cb81dc Compare February 25, 2018 16:48
@devversion
Copy link
Member Author

devversion commented Feb 25, 2018

@crisbeto Done. Addressed most of your comments.

Copy link
Member

@crisbeto crisbeto left a comment

Choose a reason for hiding this comment

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

LGTM, left a couple more nits.

// Always prevent space from scrolling the page since the list has focus
event.preventDefault();

if (!this.disabled) {
this._toggleSelectOnFocusedOption();
Copy link
Member

Choose a reason for hiding this comment

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

I'd move the preventDefault into the check as well. That's what it used to do before.

@@ -72,6 +78,16 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
/** Stream that emits whenever the active item of the list manager changes. */
change = new Subject<number>();

/**
* Predicate function that determines items that should be skipped by the list key manager.
* By default, disabled items are skipped by the key manager.
Copy link
Member

Choose a reason for hiding this comment

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

This sounds like it's describing the predicate argument, instead of what the method does. Consider changing it to something like "Sets the predicate function that the determines..."?

* Since the selection list is still a `list` that contains some content, there should be a way for screenreader users, to navigate through the options (even if disabled). With this change, if the list is disabled, it's still possible to walk through the options.
* Adds a new functionality to the `ListKeyManager` that allows the developer to control the items that can't be focused. `skipPredicate`. (e.g. for the selection list we want to make sure that users can navigate using the arrow keys to disabled items as well)
* Testing: Fixes that by default all fake events bubble up the DOM. This works in most of the cases, but it's wrong to always bubble events. e.g. `focus` doesn't bubble.

Fixes angular#9995
@devversion devversion added action: merge The PR is ready for merge by the caretaker target: patch This PR is targeted for the next patch release labels Feb 25, 2018
@tinayuangao tinayuangao merged commit 51fce51 into angular:master Feb 28, 2018
tinayuangao pushed a commit that referenced this pull request Mar 5, 2018
* Since the selection list is still a `list` that contains some content, there should be a way for screenreader users, to navigate through the options (even if disabled). With this change, if the list is disabled, it's still possible to walk through the options.
* Adds a new functionality to the `ListKeyManager` that allows the developer to control the items that can't be focused. `skipPredicate`. (e.g. for the selection list we want to make sure that users can navigate using the arrow keys to disabled items as well)
* Testing: Fixes that by default all fake events bubble up the DOM. This works in most of the cases, but it's wrong to always bubble events. e.g. `focus` doesn't bubble.

Fixes #9995
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 8, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Accessibility This issue is related to accessibility (a11y) action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement target: patch This PR is targeted for the next patch release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

selection-list: improve a11y if list is disabled
5 participants