Skip to content

Commit 8f9f3c8

Browse files
devversionjelbourn
authored andcommitted
fix(slide-toggle): support native tabindex attribute (#6613)
Currently the slide-toggle only allows changing the tabIndex using the `tabIndex` binding. Using the native `tabindex` attribute doesn't have any affect. With this change the native tabindex property will be respected. References #6465
1 parent fe37cb2 commit 8f9f3c8

File tree

5 files changed

+115
-24
lines changed

5 files changed

+115
-24
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {mixinTabIndex} from './tabindex';
2+
3+
describe('mixinTabIndex', () => {
4+
5+
it('should augment an existing class with a tabIndex property', () => {
6+
const classWithMixin = mixinTabIndex(TestClass);
7+
const instance = new classWithMixin();
8+
9+
expect(instance.tabIndex)
10+
.toBe(0, 'Expected the mixed-into class to have a tabIndex property');
11+
12+
instance.tabIndex = 4;
13+
14+
expect(instance.tabIndex)
15+
.toBe(4, 'Expected the mixed-into class to have an updated tabIndex property');
16+
});
17+
18+
it('should set tabIndex to `-1` if the disabled property is set to true', () => {
19+
const classWithMixin = mixinTabIndex(TestClass);
20+
const instance = new classWithMixin();
21+
22+
expect(instance.tabIndex)
23+
.toBe(0, 'Expected tabIndex to be set to 0 initially');
24+
25+
instance.disabled = true;
26+
27+
expect(instance.tabIndex)
28+
.toBe(-1, 'Expected tabIndex to be set to -1 if the disabled property is set to true');
29+
});
30+
31+
it('should allow having a custom default tabIndex value', () => {
32+
const classWithMixin = mixinTabIndex(TestClass, 20);
33+
const instance = new classWithMixin();
34+
35+
expect(instance.tabIndex)
36+
.toBe(20, 'Expected tabIndex to be set to 20 initially');
37+
38+
instance.tabIndex = 0;
39+
40+
expect(instance.tabIndex)
41+
.toBe(0, 'Expected tabIndex to still support 0 as value');
42+
});
43+
44+
});
45+
46+
class TestClass {
47+
disabled = false;
48+
}
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Constructor} from './constructor';
10+
import {CanDisable} from './disabled';
11+
12+
/** @docs-private */
13+
export interface HasTabIndex {
14+
tabIndex: number;
15+
}
16+
17+
/** Mixin to augment a directive with a `tabIndex` property. */
18+
export function mixinTabIndex<T extends Constructor<CanDisable>>(base: T, defaultTabIndex = 0)
19+
: Constructor<HasTabIndex> & T {
20+
return class extends base {
21+
private _tabIndex: number = defaultTabIndex;
22+
23+
get tabIndex(): number { return this.disabled ? -1 : this._tabIndex; }
24+
set tabIndex(value: number) {
25+
// If the specified tabIndex value is null or undefined, fall back to the default value.
26+
this._tabIndex = value != null ? value : defaultTabIndex;
27+
}
28+
29+
constructor(...args: any[]) {
30+
super(...args);
31+
}
32+
};
33+
}
34+

src/lib/select/select.ts

+6-16
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {
6666
PlaceholderOptions
6767
} from '../core/placeholder/placeholder-options';
6868
import {Platform} from '@angular/cdk/platform';
69+
import {HasTabIndex, mixinTabIndex} from '../core/common-behaviors/tabindex';
6970

7071
/**
7172
* The following style constants are necessary to save here in order
@@ -155,7 +156,8 @@ export class MdSelectChange {
155156
export class MdSelectBase {
156157
constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {}
157158
}
158-
export const _MdSelectMixinBase = mixinColor(mixinDisabled(MdSelectBase), 'primary');
159+
export const _MdSelectMixinBase =
160+
mixinTabIndex(mixinColor(mixinDisabled(MdSelectBase), 'primary'));
159161

160162

161163
/**
@@ -172,7 +174,7 @@ export class MdSelectTrigger {}
172174
selector: 'md-select, mat-select',
173175
templateUrl: 'select.html',
174176
styleUrls: ['select.css'],
175-
inputs: ['color', 'disabled'],
177+
inputs: ['color', 'disabled', 'tabIndex'],
176178
encapsulation: ViewEncapsulation.None,
177179
changeDetection: ChangeDetectionStrategy.OnPush,
178180
host: {
@@ -200,7 +202,7 @@ export class MdSelectTrigger {}
200202
exportAs: 'mdSelect',
201203
})
202204
export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, OnDestroy, OnInit,
203-
ControlValueAccessor, CanColor, CanDisable {
205+
ControlValueAccessor, CanColor, CanDisable, HasTabIndex {
204206
/** Whether or not the overlay panel is open. */
205207
private _panelOpen = false;
206208

@@ -234,9 +236,6 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
234236
/** The animation state of the placeholder. */
235237
private _placeholderState = '';
236238

237-
/** Tab index for the element. */
238-
private _tabIndex: number;
239-
240239
/** Deals with configuring placeholder options */
241240
private _placeholderOptions: PlaceholderOptions;
242241

@@ -371,15 +370,6 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
371370
}
372371
private _floatPlaceholder: FloatPlaceholderType;
373372

374-
/** Tab index for the select element. */
375-
@Input()
376-
get tabIndex(): number { return this.disabled ? -1 : this._tabIndex; }
377-
set tabIndex(value: number) {
378-
if (typeof value !== 'undefined') {
379-
this._tabIndex = value;
380-
}
381-
}
382-
383373
/** Value of the select control. */
384374
@Input()
385375
get value() { return this._value; }
@@ -445,7 +435,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
445435
this._control.valueAccessor = this;
446436
}
447437

448-
this._tabIndex = parseInt(tabIndex) || 0;
438+
this.tabIndex = parseInt(tabIndex) || 0;
449439
this._placeholderOptions = placeholderOptions ? placeholderOptions : {};
450440
this.floatPlaceholder = this._placeholderOptions.float || 'auto';
451441
}

src/lib/slide-toggle/slide-toggle.spec.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('MdSlideToggle without forms', () => {
1616
beforeEach(async(() => {
1717
TestBed.configureTestingModule({
1818
imports: [MdSlideToggleModule],
19-
declarations: [SlideToggleBasic],
19+
declarations: [SlideToggleBasic, SlideToggleWithTabindexAttr],
2020
providers: [
2121
{provide: HAMMER_GESTURE_CONFIG, useFactory: () => gestureConfig = new TestGestureConfig()}
2222
]
@@ -333,6 +333,18 @@ describe('MdSlideToggle without forms', () => {
333333

334334
expect(fixture.componentInstance.lastEvent).toBeFalsy();
335335
}));
336+
337+
it('should be able to set the tabindex via the native attribute', async(() => {
338+
const fixture = TestBed.createComponent(SlideToggleWithTabindexAttr);
339+
340+
fixture.detectChanges();
341+
342+
const slideToggle = fixture.debugElement
343+
.query(By.directive(MdSlideToggle)).componentInstance as MdSlideToggle;
344+
345+
expect(slideToggle.tabIndex)
346+
.toBe(5, 'Expected tabIndex property to have been set based on the native attribute');
347+
}));
336348
});
337349

338350
describe('with dragging', () => {
@@ -789,3 +801,8 @@ class SlideToggleWithModel {
789801
class SlideToggleWithFormControl {
790802
formControl = new FormControl();
791803
}
804+
805+
@Component({
806+
template: `<md-slide-toggle tabindex="5"></md-slide-toggle>`
807+
})
808+
class SlideToggleWithTabindexAttr {}

src/lib/slide-toggle/slide-toggle.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {
1010
AfterContentInit,
11+
Attribute,
1112
ChangeDetectionStrategy,
1213
ChangeDetectorRef,
1314
Component,
@@ -35,6 +36,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
3536
import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled';
3637
import {CanColor, mixinColor} from '../core/common-behaviors/color';
3738
import {CanDisableRipple, mixinDisableRipple} from '../core/common-behaviors/disable-ripple';
39+
import {HasTabIndex, mixinTabIndex} from '../core/common-behaviors/tabindex';
3840

3941
// Increasing integer for generating unique ids for slide-toggle components.
4042
let nextUniqueId = 0;
@@ -57,7 +59,7 @@ export class MdSlideToggleBase {
5759
constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {}
5860
}
5961
export const _MdSlideToggleMixinBase =
60-
mixinColor(mixinDisableRipple(mixinDisabled(MdSlideToggleBase)), 'accent');
62+
mixinTabIndex(mixinColor(mixinDisableRipple(mixinDisabled(MdSlideToggleBase)), 'accent'));
6163

6264
/** Represents a slidable "switch" toggle that can be moved between on and off. */
6365
@Component({
@@ -73,12 +75,12 @@ export const _MdSlideToggleMixinBase =
7375
templateUrl: 'slide-toggle.html',
7476
styleUrls: ['slide-toggle.css'],
7577
providers: [MD_SLIDE_TOGGLE_VALUE_ACCESSOR],
76-
inputs: ['disabled', 'disableRipple', 'color'],
78+
inputs: ['disabled', 'disableRipple', 'color', 'tabIndex'],
7779
encapsulation: ViewEncapsulation.None,
7880
changeDetection: ChangeDetectionStrategy.OnPush
7981
})
8082
export class MdSlideToggle extends _MdSlideToggleMixinBase implements OnDestroy, AfterContentInit,
81-
ControlValueAccessor, CanDisable, CanColor, CanDisableRipple {
83+
ControlValueAccessor, CanDisable, CanColor, HasTabIndex, CanDisableRipple {
8284

8385
private onChange = (_: any) => {};
8486
private onTouched = () => {};
@@ -97,9 +99,6 @@ export class MdSlideToggle extends _MdSlideToggleMixinBase implements OnDestroy,
9799
/** A unique id for the slide-toggle input. If none is supplied, it will be auto-generated. */
98100
@Input() id: string = this._uniqueId;
99101

100-
/** Used to specify the tabIndex value for the underlying input element. */
101-
@Input() tabIndex: number = 0;
102-
103102
/** Whether the label should appear after or before the slide-toggle. Defaults to 'after' */
104103
@Input() labelPosition: 'before' | 'after' = 'after';
105104

@@ -139,8 +138,11 @@ export class MdSlideToggle extends _MdSlideToggleMixinBase implements OnDestroy,
139138
renderer: Renderer2,
140139
private _platform: Platform,
141140
private _focusOriginMonitor: FocusOriginMonitor,
142-
private _changeDetectorRef: ChangeDetectorRef) {
141+
private _changeDetectorRef: ChangeDetectorRef,
142+
@Attribute('tabindex') tabIndex: string) {
143143
super(renderer, elementRef);
144+
145+
this.tabIndex = parseInt(tabIndex) || 0;
144146
}
145147

146148
ngAfterContentInit() {

0 commit comments

Comments
 (0)