Skip to content

Commit 30a6efc

Browse files
committed
fix(selection-list): tabIndex should respect disabled state
* Currently if the selection-list is disabled, the tabIndex may be still set to a valid value that allows tabbing to the element. The `mixinTabIndex` respects the disabled state of the selection list.
1 parent dcfe515 commit 30a6efc

File tree

2 files changed

+67
-13
lines changed

2 files changed

+67
-13
lines changed

src/lib/list/selection-list.spec.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,49 @@ describe('MdSelectionList', () => {
214214
});
215215
});
216216

217+
describe('with tabindex', () => {
218+
219+
beforeEach(async(() => {
220+
TestBed.configureTestingModule({
221+
imports: [MdListModule],
222+
declarations: [
223+
SelectionListWithTabindexAttr,
224+
SelectionListWithTabindexBinding,
225+
]
226+
});
227+
228+
TestBed.compileComponents();
229+
}));
230+
231+
it('should properly handle native tabindex attribute', () => {
232+
const fixture = TestBed.createComponent(SelectionListWithTabindexAttr);
233+
const selectionList = fixture.debugElement.query(By.directive(MdSelectionList));
234+
235+
expect(selectionList.componentInstance.tabIndex)
236+
.toBe(5, 'Expected the selection-list tabindex to be set to the attribute value.');
237+
});
238+
239+
it('should support changing the tabIndex through binding', () => {
240+
const fixture = TestBed.createComponent(SelectionListWithTabindexBinding);
241+
const selectionList = fixture.debugElement.query(By.directive(MdSelectionList));
242+
243+
expect(selectionList.componentInstance.tabIndex)
244+
.toBe(0, 'Expected the tabIndex to be set to "0" by default.');
245+
246+
fixture.componentInstance.tabIndex = 3;
247+
fixture.detectChanges();
248+
249+
expect(selectionList.componentInstance.tabIndex)
250+
.toBe(3, 'Expected the tabIndex to updated through binding.');
251+
252+
fixture.componentInstance.disabled = true;
253+
fixture.detectChanges();
254+
255+
expect(selectionList.componentInstance.tabIndex)
256+
.toBe(-1, 'Expected the tabIndex to be set to "-1" if selection list is disabled.');
257+
});
258+
});
259+
217260
describe('with single option', () => {
218261
let fixture: ComponentFixture<SelectionListWithOnlyOneOption>;
219262
let listOption: DebugElement;
@@ -458,3 +501,16 @@ class SelectionListWithDisabledOption {
458501
</mat-selection-list>`})
459502
class SelectionListWithOnlyOneOption {
460503
}
504+
505+
@Component({
506+
template: `<mat-selection-list tabindex="5"></mat-selection-list>`
507+
})
508+
class SelectionListWithTabindexAttr {}
509+
510+
@Component({
511+
template: `<mat-selection-list [tabIndex]="tabIndex" [disabled]="disabled"></mat-selection-list>`
512+
})
513+
class SelectionListWithTabindexBinding {
514+
tabIndex: number;
515+
disabled: boolean;
516+
}

src/lib/list/selection-list.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
OnDestroy,
2424
forwardRef,
2525
Inject,
26+
Attribute,
2627
} from '@angular/core';
2728
import {coerceBooleanProperty, SelectionModel, MdLine, MdLineSetter} from '../core';
2829
import {FocusKeyManager} from '../core/a11y/focus-key-manager';
@@ -34,11 +35,13 @@ import {RxChain, switchMap, startWith} from '../core/rxjs/index';
3435
import {merge} from 'rxjs/observable/merge';
3536
import {CanDisableRipple, mixinDisableRipple} from '../core/common-behaviors/disable-ripple';
3637
import {MATERIAL_COMPATIBILITY_MODE} from '../core/compatibility/compatibility';
38+
import {mixinTabIndex, HasTabIndex} from '../core/common-behaviors/tabindex';
3739

3840

3941
/** @docs-private */
4042
export class MdSelectionListBase {}
41-
export const _MdSelectionListMixinBase = mixinDisableRipple(mixinDisabled(MdSelectionListBase));
43+
export const _MdSelectionListMixinBase =
44+
mixinTabIndex(mixinDisableRipple(mixinDisabled(MdSelectionListBase)));
4245

4346
/** @docs-private */
4447
export class MdListOptionBase {}
@@ -182,10 +185,10 @@ export class MdListOption extends _MdListOptionMixinBase
182185
@Component({
183186
moduleId: module.id,
184187
selector: 'md-selection-list, mat-selection-list',
185-
inputs: ['disabled', 'disableRipple'],
188+
inputs: ['disabled', 'disableRipple', 'tabIndex'],
186189
host: {
187190
'role': 'listbox',
188-
'[attr.tabindex]': '_tabIndex',
191+
'[tabIndex]': 'tabIndex',
189192
'class': 'mat-selection-list',
190193
'(focus)': 'focus()',
191194
'(keydown)': '_keydown($event)',
@@ -195,11 +198,8 @@ export class MdListOption extends _MdListOptionMixinBase
195198
encapsulation: ViewEncapsulation.None,
196199
changeDetection: ChangeDetectionStrategy.OnPush
197200
})
198-
export class MdSelectionList extends _MdSelectionListMixinBase
199-
implements FocusableOption, CanDisable, CanDisableRipple, AfterContentInit, OnDestroy {
200-
201-
/** Tab index for the selection-list. */
202-
_tabIndex = 0;
201+
export class MdSelectionList extends _MdSelectionListMixinBase implements FocusableOption,
202+
CanDisable, CanDisableRipple, HasTabIndex, AfterContentInit, OnDestroy {
203203

204204
/** Subscription to all list options' onFocus events */
205205
private _optionFocusSubscription = Subscription.EMPTY;
@@ -216,17 +216,15 @@ export class MdSelectionList extends _MdSelectionListMixinBase
216216
/** The currently selected options. */
217217
selectedOptions: SelectionModel<MdListOption> = new SelectionModel<MdListOption>(true);
218218

219-
constructor(private _element: ElementRef) {
219+
constructor(private _element: ElementRef, @Attribute('tabindex') tabIndex: string) {
220220
super();
221+
222+
this.tabIndex = parseInt(tabIndex) || 0;
221223
}
222224

223225
ngAfterContentInit(): void {
224226
this._keyManager = new FocusKeyManager<MdListOption>(this.options).withWrap();
225227

226-
if (this.disabled) {
227-
this._tabIndex = -1;
228-
}
229-
230228
this._optionFocusSubscription = this._onFocusSubscription();
231229
this._optionDestroyStream = this._onDestroySubscription();
232230
}

0 commit comments

Comments
 (0)