Skip to content

Commit

Permalink
fix(slide-toggle): support native tabindex attribute (#6613)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
devversion authored and jelbourn committed Sep 1, 2017
1 parent fe37cb2 commit 8f9f3c8
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 24 deletions.
48 changes: 48 additions & 0 deletions src/lib/core/common-behaviors/tabindex.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {mixinTabIndex} from './tabindex';

describe('mixinTabIndex', () => {

it('should augment an existing class with a tabIndex property', () => {
const classWithMixin = mixinTabIndex(TestClass);
const instance = new classWithMixin();

expect(instance.tabIndex)
.toBe(0, 'Expected the mixed-into class to have a tabIndex property');

instance.tabIndex = 4;

expect(instance.tabIndex)
.toBe(4, 'Expected the mixed-into class to have an updated tabIndex property');
});

it('should set tabIndex to `-1` if the disabled property is set to true', () => {
const classWithMixin = mixinTabIndex(TestClass);
const instance = new classWithMixin();

expect(instance.tabIndex)
.toBe(0, 'Expected tabIndex to be set to 0 initially');

instance.disabled = true;

expect(instance.tabIndex)
.toBe(-1, 'Expected tabIndex to be set to -1 if the disabled property is set to true');
});

it('should allow having a custom default tabIndex value', () => {
const classWithMixin = mixinTabIndex(TestClass, 20);
const instance = new classWithMixin();

expect(instance.tabIndex)
.toBe(20, 'Expected tabIndex to be set to 20 initially');

instance.tabIndex = 0;

expect(instance.tabIndex)
.toBe(0, 'Expected tabIndex to still support 0 as value');
});

});

class TestClass {
disabled = false;
}
34 changes: 34 additions & 0 deletions src/lib/core/common-behaviors/tabindex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Constructor} from './constructor';
import {CanDisable} from './disabled';

/** @docs-private */
export interface HasTabIndex {
tabIndex: number;
}

/** Mixin to augment a directive with a `tabIndex` property. */
export function mixinTabIndex<T extends Constructor<CanDisable>>(base: T, defaultTabIndex = 0)
: Constructor<HasTabIndex> & T {
return class extends base {
private _tabIndex: number = defaultTabIndex;

get tabIndex(): number { return this.disabled ? -1 : this._tabIndex; }
set tabIndex(value: number) {
// If the specified tabIndex value is null or undefined, fall back to the default value.
this._tabIndex = value != null ? value : defaultTabIndex;
}

constructor(...args: any[]) {
super(...args);
}
};
}

22 changes: 6 additions & 16 deletions src/lib/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import {
PlaceholderOptions
} from '../core/placeholder/placeholder-options';
import {Platform} from '@angular/cdk/platform';
import {HasTabIndex, mixinTabIndex} from '../core/common-behaviors/tabindex';

/**
* The following style constants are necessary to save here in order
Expand Down Expand Up @@ -155,7 +156,8 @@ export class MdSelectChange {
export class MdSelectBase {
constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {}
}
export const _MdSelectMixinBase = mixinColor(mixinDisabled(MdSelectBase), 'primary');
export const _MdSelectMixinBase =
mixinTabIndex(mixinColor(mixinDisabled(MdSelectBase), 'primary'));


/**
Expand All @@ -172,7 +174,7 @@ export class MdSelectTrigger {}
selector: 'md-select, mat-select',
templateUrl: 'select.html',
styleUrls: ['select.css'],
inputs: ['color', 'disabled'],
inputs: ['color', 'disabled', 'tabIndex'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
Expand Down Expand Up @@ -200,7 +202,7 @@ export class MdSelectTrigger {}
exportAs: 'mdSelect',
})
export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, OnDestroy, OnInit,
ControlValueAccessor, CanColor, CanDisable {
ControlValueAccessor, CanColor, CanDisable, HasTabIndex {
/** Whether or not the overlay panel is open. */
private _panelOpen = false;

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

/** Tab index for the element. */
private _tabIndex: number;

/** Deals with configuring placeholder options */
private _placeholderOptions: PlaceholderOptions;

Expand Down Expand Up @@ -371,15 +370,6 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
}
private _floatPlaceholder: FloatPlaceholderType;

/** Tab index for the select element. */
@Input()
get tabIndex(): number { return this.disabled ? -1 : this._tabIndex; }
set tabIndex(value: number) {
if (typeof value !== 'undefined') {
this._tabIndex = value;
}
}

/** Value of the select control. */
@Input()
get value() { return this._value; }
Expand Down Expand Up @@ -445,7 +435,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On
this._control.valueAccessor = this;
}

this._tabIndex = parseInt(tabIndex) || 0;
this.tabIndex = parseInt(tabIndex) || 0;
this._placeholderOptions = placeholderOptions ? placeholderOptions : {};
this.floatPlaceholder = this._placeholderOptions.float || 'auto';
}
Expand Down
19 changes: 18 additions & 1 deletion src/lib/slide-toggle/slide-toggle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('MdSlideToggle without forms', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdSlideToggleModule],
declarations: [SlideToggleBasic],
declarations: [SlideToggleBasic, SlideToggleWithTabindexAttr],
providers: [
{provide: HAMMER_GESTURE_CONFIG, useFactory: () => gestureConfig = new TestGestureConfig()}
]
Expand Down Expand Up @@ -333,6 +333,18 @@ describe('MdSlideToggle without forms', () => {

expect(fixture.componentInstance.lastEvent).toBeFalsy();
}));

it('should be able to set the tabindex via the native attribute', async(() => {
const fixture = TestBed.createComponent(SlideToggleWithTabindexAttr);

fixture.detectChanges();

const slideToggle = fixture.debugElement
.query(By.directive(MdSlideToggle)).componentInstance as MdSlideToggle;

expect(slideToggle.tabIndex)
.toBe(5, 'Expected tabIndex property to have been set based on the native attribute');
}));
});

describe('with dragging', () => {
Expand Down Expand Up @@ -789,3 +801,8 @@ class SlideToggleWithModel {
class SlideToggleWithFormControl {
formControl = new FormControl();
}

@Component({
template: `<md-slide-toggle tabindex="5"></md-slide-toggle>`
})
class SlideToggleWithTabindexAttr {}
16 changes: 9 additions & 7 deletions src/lib/slide-toggle/slide-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {
AfterContentInit,
Attribute,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Expand Down Expand Up @@ -35,6 +36,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled';
import {CanColor, mixinColor} from '../core/common-behaviors/color';
import {CanDisableRipple, mixinDisableRipple} from '../core/common-behaviors/disable-ripple';
import {HasTabIndex, mixinTabIndex} from '../core/common-behaviors/tabindex';

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

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

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

/** Used to specify the tabIndex value for the underlying input element. */
@Input() tabIndex: number = 0;

/** Whether the label should appear after or before the slide-toggle. Defaults to 'after' */
@Input() labelPosition: 'before' | 'after' = 'after';

Expand Down Expand Up @@ -139,8 +138,11 @@ export class MdSlideToggle extends _MdSlideToggleMixinBase implements OnDestroy,
renderer: Renderer2,
private _platform: Platform,
private _focusOriginMonitor: FocusOriginMonitor,
private _changeDetectorRef: ChangeDetectorRef) {
private _changeDetectorRef: ChangeDetectorRef,
@Attribute('tabindex') tabIndex: string) {
super(renderer, elementRef);

this.tabIndex = parseInt(tabIndex) || 0;
}

ngAfterContentInit() {
Expand Down

0 comments on commit 8f9f3c8

Please sign in to comment.