diff --git a/src/lib/checkbox/checkbox.html b/src/lib/checkbox/checkbox.html
index 8c979eff04ef..e2ebc48b0249 100644
--- a/src/lib/checkbox/checkbox.html
+++ b/src/lib/checkbox/checkbox.html
@@ -12,11 +12,11 @@
[indeterminate]="indeterminate"
[attr.aria-label]="ariaLabel"
[attr.aria-labelledby]="ariaLabelledby"
- (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 10d86e54f58d..180a4003fa7a 100644
--- a/src/lib/checkbox/checkbox.spec.ts
+++ b/src/lib/checkbox/checkbox.spec.ts
@@ -359,23 +359,6 @@ describe('MdCheckbox', () => {
.toBe(0, 'Expected no ripple after element is blurred.');
}));
- it('should show a ripple when focused programmatically', fakeAsync(() => {
- expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
- .toBe(0, 'Expected no ripples to be present.');
-
- dispatchFakeEvent(inputElement, 'focus');
- tick(RIPPLE_FADE_IN_DURATION);
-
- expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
- .toBe(1, 'Expected focus ripple to be present.');
-
- dispatchFakeEvent(checkboxInstance._inputElement.nativeElement, 'blur');
- tick(RIPPLE_FADE_OUT_DURATION);
-
- expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
- .toBe(0, 'Expected focus ripple to be removed.');
- }));
-
describe('ripple elements', () => {
it('should show ripples on label mousedown', () => {
@@ -387,30 +370,41 @@ describe('MdCheckbox', () => {
expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(1);
});
- it('should not have a ripple when disabled', () => {
- let rippleElement = checkboxNativeElement.querySelector('[md-ripple]');
- expect(rippleElement).toBeTruthy('Expected an enabled checkbox to have a ripple');
-
+ it('should not show ripples when disabled', () => {
testComponent.isDisabled = true;
fixture.detectChanges();
- rippleElement = checkboxNativeElement.querySelector('[md-ripple]');
- expect(rippleElement).toBeFalsy('Expected a disabled checkbox not to have a ripple');
+ dispatchFakeEvent(labelElement, 'mousedown');
+ dispatchFakeEvent(labelElement, 'mouseup');
+
+ expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(0);
+
+ testComponent.isDisabled = false;
+ fixture.detectChanges();
+
+ dispatchFakeEvent(labelElement, 'mousedown');
+ dispatchFakeEvent(labelElement, 'mouseup');
+
+ expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(1);
});
- it('should remove ripple if mdRippleDisabled input is set', async(() => {
+ it('should remove ripple if mdRippleDisabled input is set', () => {
testComponent.disableRipple = true;
fixture.detectChanges();
- expect(checkboxNativeElement.querySelectorAll('[md-ripple]').length)
- .toBe(0, 'Expect no [md-ripple] in checkbox');
+ dispatchFakeEvent(labelElement, 'mousedown');
+ dispatchFakeEvent(labelElement, 'mouseup');
+
+ expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(0);
testComponent.disableRipple = false;
fixture.detectChanges();
- expect(checkboxNativeElement.querySelectorAll('[md-ripple]').length)
- .toBe(1, 'Expect [md-ripple] in checkbox');
- }));
+ dispatchFakeEvent(labelElement, 'mousedown');
+ dispatchFakeEvent(labelElement, 'mouseup');
+
+ expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(1);
+ });
});
describe('color behaviour', () => {
diff --git a/src/lib/checkbox/checkbox.ts b/src/lib/checkbox/checkbox.ts
index ae9e0b00f09b..e7a69a412169 100644
--- a/src/lib/checkbox/checkbox.ts
+++ b/src/lib/checkbox/checkbox.ts
@@ -15,11 +15,11 @@ import {
} from '@angular/core';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';
import {coerceBooleanProperty} from '../core/coercion/boolean-property';
-import {Subscription} from 'rxjs/Subscription';
import {
MdRipple,
RippleRef,
FocusOriginMonitor,
+ FocusOrigin,
} from '../core';
@@ -183,10 +183,7 @@ export class MdCheckbox implements ControlValueAccessor, AfterViewInit, OnDestro
private _controlValueAccessorChangeFn: (value: any) => void = (value) => {};
/** Reference to the focused state ripple. */
- private _focusedRipple: RippleRef;
-
- /** Reference to the focus origin monitor subscription. */
- private _focusedSubscription: Subscription;
+ private _focusRipple: RippleRef;
constructor(private _renderer: Renderer,
private _elementRef: ElementRef,
@@ -196,13 +193,9 @@ export class MdCheckbox implements ControlValueAccessor, AfterViewInit, OnDestro
}
ngAfterViewInit() {
- this._focusedSubscription = this._focusOriginMonitor
+ this._focusOriginMonitor
.monitor(this._inputElement.nativeElement, this._renderer, false)
- .subscribe(focusOrigin => {
- if (!this._focusedRipple && (focusOrigin === 'keyboard' || focusOrigin === 'program')) {
- this._focusedRipple = this._ripple.launch(0, 0, { persistent: true, centered: true });
- }
- });
+ .subscribe(focusOrigin => this._onInputFocusChange(focusOrigin));
}
ngOnDestroy() {
@@ -343,10 +336,14 @@ export class MdCheckbox implements ControlValueAccessor, AfterViewInit, OnDestro
this.change.emit(event);
}
- /** Informs the component when we lose focus in order to style accordingly */
- _onInputBlur() {
- this._removeFocusedRipple();
- this.onTouched();
+ /** Function is called whenever the focus changes for the input element. */
+ private _onInputFocusChange(focusOrigin: FocusOrigin) {
+ if (!this._focusRipple && focusOrigin === 'keyboard') {
+ this._focusRipple = this._ripple.launch(0, 0, {persistent: true, centered: true});
+ } else if (!focusOrigin) {
+ this._removeFocusRipple();
+ this.onTouched();
+ }
}
/** Toggles the `checked` state of the checkbox. */
@@ -371,7 +368,7 @@ export class MdCheckbox implements ControlValueAccessor, AfterViewInit, OnDestro
// Preventing bubbling for the second event will solve that issue.
event.stopPropagation();
- this._removeFocusedRipple();
+ this._removeFocusRipple();
if (!this.disabled) {
this.toggle();
@@ -387,7 +384,7 @@ export class MdCheckbox implements ControlValueAccessor, AfterViewInit, OnDestro
/** Focuses the checkbox. */
focus(): void {
- this._focusOriginMonitor.focusVia(this._inputElement.nativeElement, this._renderer, 'program');
+ this._focusOriginMonitor.focusVia(this._inputElement.nativeElement, this._renderer, 'keyboard');
}
_onInteractionEvent(event: Event) {
@@ -429,11 +426,11 @@ export class MdCheckbox implements ControlValueAccessor, AfterViewInit, OnDestro
return `mat-checkbox-anim-${animSuffix}`;
}
- /** Fades out the focused state ripple. */
- private _removeFocusedRipple(): void {
- if (this._focusedRipple) {
- this._focusedRipple.fadeOut();
- this._focusedRipple = null;
+ /** Fades out the focus state ripple. */
+ private _removeFocusRipple(): void {
+ if (this._focusRipple) {
+ this._focusRipple.fadeOut();
+ this._focusRipple = null;
}
}
}
diff --git a/src/lib/radio/radio.html b/src/lib/radio/radio.html
index 02423b631eec..ca84be26b665 100644
--- a/src/lib/radio/radio.html
+++ b/src/lib/radio/radio.html
@@ -5,8 +5,9 @@
@@ -18,7 +19,6 @@
[attr.aria-label]="ariaLabel"
[attr.aria-labelledby]="ariaLabelledby"
(change)="_onInputChange($event)"
- (blur)="_onInputBlur()"
(click)="_onInputClick($event)">
diff --git a/src/lib/radio/radio.spec.ts b/src/lib/radio/radio.spec.ts
index e1cc41333fc5..c977f51e6e77 100644
--- a/src/lib/radio/radio.spec.ts
+++ b/src/lib/radio/radio.spec.ts
@@ -228,33 +228,47 @@ describe('MdRadio', () => {
expect(radioInstances.every(radio => !radio.checked)).toBe(true);
});
- it('should not have a ripple on disabled radio buttons', () => {
- let rippleElement = radioNativeElements[0].querySelector('[md-ripple]');
- expect(rippleElement).toBeTruthy('Expected an enabled radio button to have a ripple');
-
+ it('should not show ripples on disabled radio buttons', () => {
radioInstances[0].disabled = true;
fixture.detectChanges();
- rippleElement = radioNativeElements[0].querySelector('[md-ripple]');
- expect(rippleElement).toBeFalsy('Expected a disabled radio button not to have a ripple');
+ dispatchFakeEvent(radioLabelElements[0], 'mousedown');
+ dispatchFakeEvent(radioLabelElements[0], 'mouseup');
+
+ expect(radioNativeElements[0].querySelectorAll('.mat-ripple-element').length)
+ .toBe(0, 'Expected a disabled radio button to not show ripples');
+
+ radioInstances[0].disabled = false;
+ fixture.detectChanges();
+
+ dispatchFakeEvent(radioLabelElements[0], 'mousedown');
+ dispatchFakeEvent(radioLabelElements[0], 'mouseup');
+
+ expect(radioNativeElements[0].querySelectorAll('.mat-ripple-element').length)
+ .toBe(1, 'Expected an enabled radio button to show ripples');
});
- it('should remove ripple if mdRippleDisabled input is set', async(() => {
+ it('should not show ripples if mdRippleDisabled input is set', () => {
+ testComponent.disableRipple = true;
fixture.detectChanges();
- for (let radioNativeElement of radioNativeElements)
- {
- expect(radioNativeElement.querySelectorAll('[md-ripple]').length)
- .toBe(1, 'Expect [md-ripple] in radio buttons');
+
+ for (let radioLabel of radioLabelElements) {
+ dispatchFakeEvent(radioLabel, 'mousedown');
+ dispatchFakeEvent(radioLabel, 'mouseup');
+
+ expect(radioLabel.querySelectorAll('.mat-ripple-element').length).toBe(0);
}
- testComponent.disableRipple = true;
+ testComponent.disableRipple = false;
fixture.detectChanges();
- for (let radioNativeElement of radioNativeElements)
- {
- expect(radioNativeElement.querySelectorAll('[md-ripple]').length)
- .toBe(0, 'Expect no [md-ripple] in radio buttons');
+
+ for (let radioLabel of radioLabelElements) {
+ dispatchFakeEvent(radioLabel, 'mousedown');
+ dispatchFakeEvent(radioLabel, 'mouseup');
+
+ expect(radioLabel.querySelectorAll('.mat-ripple-element').length).toBe(1);
}
- }));
+ });
it(`should update the group's selected radio to null when unchecking that radio
programmatically`, () => {
diff --git a/src/lib/radio/radio.ts b/src/lib/radio/radio.ts
index 7e72e6c2d032..a8dab7a488a0 100644
--- a/src/lib/radio/radio.ts
+++ b/src/lib/radio/radio.ts
@@ -23,9 +23,9 @@ import {
UniqueSelectionDispatcher,
MdRipple,
FocusOriginMonitor,
+ FocusOrigin,
} from '../core';
import {coerceBooleanProperty} from '../core/coercion/boolean-property';
-import {Subscription} from 'rxjs/Subscription';
/**
@@ -405,11 +405,8 @@ export class MdRadioButton implements OnInit, AfterViewInit, OnDestroy {
/** The child ripple instance. */
@ViewChild(MdRipple) _ripple: MdRipple;
- /** Stream of focus event from the focus origin monitor. */
- private _focusOriginMonitorSubscription: Subscription;
-
/** Reference to the current focus ripple. */
- private _focusedRippleRef: RippleRef;
+ private _focusRipple: RippleRef;
/** The native `
` element */
@ViewChild('input') _inputElement: ElementRef;
@@ -446,13 +443,9 @@ export class MdRadioButton implements OnInit, AfterViewInit, OnDestroy {
}
ngAfterViewInit() {
- this._focusOriginMonitorSubscription = this._focusOriginMonitor
+ this._focusOriginMonitor
.monitor(this._inputElement.nativeElement, this._renderer, false)
- .subscribe(focusOrigin => {
- if (focusOrigin === 'keyboard' && !this._focusedRippleRef) {
- this._focusedRippleRef = this._ripple.launch(0, 0, { persistent: true, centered: true });
- }
- });
+ .subscribe(focusOrigin => this._onInputFocusChange(focusOrigin));
}
ngOnDestroy() {
@@ -471,17 +464,6 @@ export class MdRadioButton implements OnInit, AfterViewInit, OnDestroy {
return this.disableRipple || this.disabled;
}
- _onInputBlur() {
- if (this._focusedRippleRef) {
- this._focusedRippleRef.fadeOut();
- this._focusedRippleRef = null;
- }
-
- if (this.radioGroup) {
- this.radioGroup._touch();
- }
- }
-
_onInputClick(event: Event) {
// We have to stop propagation for click events on the visual hidden input element.
// By default, when a user clicks on a label element, a generated click event will be
@@ -516,4 +498,20 @@ export class MdRadioButton implements OnInit, AfterViewInit, OnDestroy {
}
}
+ /** Function is called whenever the focus changes for the input element. */
+ private _onInputFocusChange(focusOrigin: FocusOrigin) {
+ if (!this._focusRipple && focusOrigin === 'keyboard') {
+ this._focusRipple = this._ripple.launch(0, 0, {persistent: true, centered: true});
+ } else if (!focusOrigin) {
+ if (this.radioGroup) {
+ this.radioGroup._touch();
+ }
+
+ if (this._focusRipple) {
+ this._focusRipple.fadeOut();
+ this._focusRipple = null;
+ }
+ }
+ }
+
}
diff --git a/src/lib/slide-toggle/slide-toggle.ts b/src/lib/slide-toggle/slide-toggle.ts
index 864eba4288db..21927436e996 100644
--- a/src/lib/slide-toggle/slide-toggle.ts
+++ b/src/lib/slide-toggle/slide-toggle.ts
@@ -204,7 +204,7 @@ export class MdSlideToggle implements OnDestroy, AfterContentInit, ControlValueA
/** Focuses the slide-toggle. */
focus() {
- this._focusOriginMonitor.focusVia(this._inputElement.nativeElement, this._renderer, 'program');
+ this._focusOriginMonitor.focusVia(this._inputElement.nativeElement, this._renderer, 'keyboard');
}
/** Whether the slide-toggle is checked. */