diff --git a/src/lib/checkbox/checkbox.html b/src/lib/checkbox/checkbox.html
index 209ceb8b8605..8c979eff04ef 100644
--- a/src/lib/checkbox/checkbox.html
+++ b/src/lib/checkbox/checkbox.html
@@ -12,7 +12,6 @@
[indeterminate]="indeterminate"
[attr.aria-label]="ariaLabel"
[attr.aria-labelledby]="ariaLabelledby"
- (focus)="_onInputFocus()"
(blur)="_onInputBlur()"
(change)="_onInteractionEvent($event)"
(click)="_onInputClick($event)">
diff --git a/src/lib/checkbox/checkbox.spec.ts b/src/lib/checkbox/checkbox.spec.ts
index 857c44348208..a6342eaa76e1 100644
--- a/src/lib/checkbox/checkbox.spec.ts
+++ b/src/lib/checkbox/checkbox.spec.ts
@@ -1,4 +1,11 @@
-import {async, fakeAsync, flushMicrotasks, ComponentFixture, TestBed} from '@angular/core/testing';
+import {
+ async,
+ fakeAsync,
+ flushMicrotasks,
+ ComponentFixture,
+ TestBed,
+ tick,
+} from '@angular/core/testing';
import {NgControl, FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
import {Component, DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
@@ -6,10 +13,22 @@ import {MdCheckbox, MdCheckboxChange, MdCheckboxModule} from './checkbox';
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
import {FakeViewportRuler} from '../core/overlay/position/fake-viewport-ruler';
import {dispatchFakeEvent} from '../core/testing/dispatch-events';
+import {FocusOriginMonitor, FocusOrigin} from '../core';
+import {RIPPLE_FADE_IN_DURATION, RIPPLE_FADE_OUT_DURATION} from '../core/ripple/ripple-renderer';
+import {Subject} from 'rxjs/Subject';
describe('MdCheckbox', () => {
let fixture: ComponentFixture;
+ let fakeFocusOriginMonitorSubject: Subject = new Subject();
+ let fakeFocusOriginMonitor = {
+ monitor: () => fakeFocusOriginMonitorSubject.asObservable(),
+ unmonitor: () => {},
+ focusVia: (element: HTMLElement, renderer: any, focusOrigin: FocusOrigin) => {
+ element.focus();
+ fakeFocusOriginMonitorSubject.next(focusOrigin);
+ }
+ };
beforeEach(async(() => {
TestBed.configureTestingModule({
@@ -27,6 +46,7 @@ describe('MdCheckbox', () => {
],
providers: [
{provide: ViewportRuler, useClass: FakeViewportRuler},
+ {provide: FocusOriginMonitor, useValue: fakeFocusOriginMonitor}
]
});
@@ -321,6 +341,23 @@ describe('MdCheckbox', () => {
expect(inputElement.value).toBe('basic_checkbox');
});
+ it('should show a ripple when focused by a keyboard action', fakeAsync(() => {
+ expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
+ .toBe(0, 'Expected no ripples on load.');
+
+ fakeFocusOriginMonitorSubject.next('keyboard');
+ tick(RIPPLE_FADE_IN_DURATION);
+
+ expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
+ .toBe(1, 'Expected ripple after element is focused.');
+
+ dispatchFakeEvent(checkboxInstance._inputElement.nativeElement, 'blur');
+ tick(RIPPLE_FADE_OUT_DURATION);
+
+ expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
+ .toBe(0, 'Expected no ripple after element is blurred.');
+ }));
+
describe('ripple elements', () => {
it('should show ripples on label mousedown', () => {
diff --git a/src/lib/checkbox/checkbox.ts b/src/lib/checkbox/checkbox.ts
index 429666335b65..a935e2d2ef14 100644
--- a/src/lib/checkbox/checkbox.ts
+++ b/src/lib/checkbox/checkbox.ts
@@ -12,11 +12,20 @@ import {
NgModule,
ModuleWithProviders,
ViewChild,
+ AfterViewInit,
+ OnDestroy,
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
import {coerceBooleanProperty} from '../core/coercion/boolean-property';
-import {MdRippleModule, CompatibilityModule} from '../core';
+import {Subscription} from 'rxjs/Subscription';
+import {
+ CompatibilityModule,
+ MdRippleModule,
+ MdRipple,
+ RippleRef,
+ FocusOriginMonitor,
+} from '../core';
/** Monotonically increasing integer used to auto-generate unique ids for checkbox components. */
@@ -73,13 +82,12 @@ export class MdCheckboxChange {
'[class.mat-checkbox-checked]': 'checked',
'[class.mat-checkbox-disabled]': 'disabled',
'[class.mat-checkbox-label-before]': 'labelPosition == "before"',
- '[class.mat-checkbox-focused]': '_hasFocus',
},
providers: [MD_CHECKBOX_CONTROL_VALUE_ACCESSOR],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
-export class MdCheckbox implements ControlValueAccessor {
+export class MdCheckbox implements ControlValueAccessor, AfterViewInit, OnDestroy {
/**
* Attached to the aria-label attribute of the host element. In most cases, arial-labelledby will
* take precedence so this may be omitted.
@@ -157,6 +165,8 @@ export class MdCheckbox implements ControlValueAccessor {
/** The native ` element */
@ViewChild('input') _inputElement: ElementRef;
+ @ViewChild(MdRipple) _ripple: MdRipple;
+
/**
* Called when the checkbox is blurred. Needed to properly implement ControlValueAccessor.
* @docs-private
@@ -175,14 +185,38 @@ export class MdCheckbox implements ControlValueAccessor {
private _controlValueAccessorChangeFn: (value: any) => void = (value) => {};
- _hasFocus: boolean = false;
+ /** Reference to the focused state ripple. */
+ private _focusedRipple: RippleRef;
+
+ /** Reference to the focus origin monitor subscription. */
+ private _focusedSubscription: Subscription;
constructor(private _renderer: Renderer,
private _elementRef: ElementRef,
- private _changeDetectorRef: ChangeDetectorRef) {
+ private _changeDetectorRef: ChangeDetectorRef,
+ private _focusOriginMonitor: FocusOriginMonitor) {
this.color = 'accent';
}
+ ngAfterViewInit() {
+ this._focusedSubscription = this._focusOriginMonitor
+ .monitor(this._inputElement.nativeElement, this._renderer, false)
+ .subscribe(focusOrigin => {
+ if (!this._focusedRipple && focusOrigin === 'keyboard') {
+ this._focusedRipple = this._ripple.launch(0, 0, { persistent: true, centered: true });
+ }
+ });
+ }
+
+ ngOnDestroy() {
+ this._focusOriginMonitor.unmonitor(this._inputElement.nativeElement);
+
+ if (this._focusedSubscription) {
+ this._focusedSubscription.unsubscribe();
+ this._focusedSubscription = null;
+ }
+ }
+
/**
* Whether the checkbox is checked. Note that setting `checked` will immediately set
* `indeterminate` to false.
@@ -315,14 +349,9 @@ export class MdCheckbox implements ControlValueAccessor {
this.change.emit(event);
}
- /** Informs the component when the input has focus so that we can style accordingly */
- _onInputFocus() {
- this._hasFocus = true;
- }
-
/** Informs the component when we lose focus in order to style accordingly */
_onInputBlur() {
- this._hasFocus = false;
+ this._removeFocusedRipple();
this.onTouched();
}
@@ -348,6 +377,8 @@ export class MdCheckbox implements ControlValueAccessor {
// Preventing bubbling for the second event will solve that issue.
event.stopPropagation();
+ this._removeFocusedRipple();
+
if (!this.disabled) {
this.toggle();
this._transitionCheckState(
@@ -362,8 +393,7 @@ export class MdCheckbox implements ControlValueAccessor {
/** Focuses the checkbox. */
focus(): void {
- this._renderer.invokeElementMethod(this._inputElement.nativeElement, 'focus');
- this._onInputFocus();
+ this._focusOriginMonitor.focusVia(this._inputElement.nativeElement, this._renderer, 'keyboard');
}
_onInteractionEvent(event: Event) {
@@ -405,6 +435,13 @@ export class MdCheckbox implements ControlValueAccessor {
return `mat-checkbox-anim-${animSuffix}`;
}
+ /** Fades out the focused state ripple. */
+ private _removeFocusedRipple(): void {
+ if (this._focusedRipple) {
+ this._focusedRipple.fadeOut();
+ this._focusedRipple = null;
+ }
+ }
}
@@ -412,6 +449,7 @@ export class MdCheckbox implements ControlValueAccessor {
imports: [CommonModule, MdRippleModule, CompatibilityModule],
exports: [MdCheckbox, CompatibilityModule],
declarations: [MdCheckbox],
+ providers: [FocusOriginMonitor]
})
export class MdCheckboxModule {
/** @deprecated */