diff --git a/src/lib/slide-toggle/_slide-toggle-theme.scss b/src/lib/slide-toggle/_slide-toggle-theme.scss index dec7e55ae945..178144c7f8de 100644 --- a/src/lib/slide-toggle/_slide-toggle-theme.scss +++ b/src/lib/slide-toggle/_slide-toggle-theme.scss @@ -45,11 +45,10 @@ // Ripple colors are based on the current palette and the state of the slide-toggle. // See https://material.google.com/components/selection-controls.html#selection-controls-switch - $ripple-checked-opacity: 0.12; - $ripple-unchecked-color: mat-color($foreground, base, if($is-dark, 0.12, 0.06)); - $ripple-primary-color: mat-color($primary, $thumb-checked-hue, $ripple-checked-opacity); - $ripple-accent-color: mat-color($accent, $thumb-checked-hue, $ripple-checked-opacity); - $ripple-warn-color: mat-color($warn, $thumb-checked-hue, $ripple-checked-opacity); + $ripple-unchecked-color: mat-color($foreground, base); + $ripple-primary-color: mat-color($primary, $thumb-checked-hue); + $ripple-accent-color: mat-color($accent, $thumb-checked-hue); + $ripple-warn-color: mat-color($warn, $thumb-checked-hue); .mat-slide-toggle { @include _mat-slide-toggle-checked($accent, $thumb-checked-hue); diff --git a/src/lib/slide-toggle/slide-toggle.html b/src/lib/slide-toggle/slide-toggle.html index 337a8ee3bb31..fd1dae718a15 100644 --- a/src/lib/slide-toggle/slide-toggle.html +++ b/src/lib/slide-toggle/slide-toggle.html @@ -27,8 +27,10 @@ [matRippleTrigger]="label" [matRippleDisabled]="disableRipple || disabled" [matRippleCentered]="true" - [matRippleRadius]="23" + [matRippleRadius]="20" [matRippleAnimation]="{enterDuration: 150}"> + +
diff --git a/src/lib/slide-toggle/slide-toggle.scss b/src/lib/slide-toggle/slide-toggle.scss index d6b285469c03..dbeec46dcabd 100644 --- a/src/lib/slide-toggle/slide-toggle.scss +++ b/src/lib/slide-toggle/slide-toggle.scss @@ -9,7 +9,7 @@ $mat-slide-toggle-thumb-size: 20px !default; $mat-slide-toggle-bar-border-radius: 8px !default; $mat-slide-toggle-height: 24px !default; $mat-slide-toggle-spacing: 8px !default; -$mat-slide-toggle-ripple-radius: 23px !default; +$mat-slide-toggle-ripple-radius: 20px !default; $mat-slide-toggle-bar-width: 36px !default; $mat-slide-toggle-bar-height: 14px !default; $mat-slide-toggle-bar-track-width: $mat-slide-toggle-bar-width - $mat-slide-toggle-thumb-size; @@ -189,6 +189,31 @@ $mat-slide-toggle-bar-track-width: $mat-slide-toggle-bar-width - $mat-slide-togg width: $mat-slide-toggle-ripple-radius * 2; z-index: 1; pointer-events: none; + + .mat-ripple-element:not(.mat-slide-toggle-persistent-ripple) { + opacity: 0.16; + } +} + +.mat-slide-toggle-persistent-ripple { + width: 100%; + height: 100%; + transform: none; + + .mat-slide-toggle-bar:hover & { + opacity: 0.04; + } + + .mat-slide-toggle.cdk-focused & { + opacity: 0.12; + } + + // We do this here, rather than having a `:not(.mat-slide-toggle-disabled)` + // above in the `:hover`, because the `:not` will bump the specificity + // a lot and will cause it to overide the focus styles. + &, .mat-slide-toggle.mat-disabled .mat-slide-toggle-bar:hover & { + opacity: 0; + } } /** Custom styling to make the slide-toggle usable in high contrast mode. */ diff --git a/src/lib/slide-toggle/slide-toggle.spec.ts b/src/lib/slide-toggle/slide-toggle.spec.ts index 84eaee5e0d72..24e06416a8cc 100644 --- a/src/lib/slide-toggle/slide-toggle.spec.ts +++ b/src/lib/slide-toggle/slide-toggle.spec.ts @@ -10,7 +10,6 @@ import { tick, } from '@angular/core/testing'; import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms'; -import {defaultRippleAnimationConfig} from '@angular/material/core'; import {By, HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; import {BidiModule, Direction} from '@angular/cdk/bidi'; import {TestGestureConfig} from '../slider/test-gesture-config'; @@ -271,26 +270,6 @@ describe('MatSlideToggle without forms', () => { subscription.unsubscribe(); })); - it('should show a ripple when focused by a keyboard action', fakeAsync(() => { - expect(slideToggleElement.querySelectorAll('.mat-ripple-element').length) - .toBe(0, 'Expected no ripples to be present.'); - - dispatchFakeEvent(inputElement, 'keydown'); - dispatchFakeEvent(inputElement, 'focus'); - - tick(defaultRippleAnimationConfig.enterDuration); - - expect(slideToggleElement.querySelectorAll('.mat-ripple-element').length) - .toBe(1, 'Expected the focus ripple to be showing up.'); - - dispatchFakeEvent(inputElement, 'blur'); - - tick(defaultRippleAnimationConfig.exitDuration); - - expect(slideToggleElement.querySelectorAll('.mat-ripple-element').length) - .toBe(0, 'Expected focus ripple to be removed.'); - })); - it('should forward the required attribute', () => { testComponent.isRequired = true; fixture.detectChanges(); @@ -322,24 +301,27 @@ describe('MatSlideToggle without forms', () => { }); it('should show ripples on label mousedown', () => { - expect(slideToggleElement.querySelectorAll('.mat-ripple-element').length).toBe(0); + const rippleSelector = '.mat-ripple-element:not(.mat-slide-toggle-persistent-ripple)'; + + expect(slideToggleElement.querySelectorAll(rippleSelector).length).toBe(0); dispatchFakeEvent(labelElement, 'mousedown'); dispatchFakeEvent(labelElement, 'mouseup'); - expect(slideToggleElement.querySelectorAll('.mat-ripple-element').length).toBe(1); + expect(slideToggleElement.querySelectorAll(rippleSelector).length).toBe(1); }); it('should not show ripples when disableRipple is set', () => { + const rippleSelector = '.mat-ripple-element:not(.mat-slide-toggle-persistent-ripple)'; testComponent.disableRipple = true; fixture.detectChanges(); - expect(slideToggleElement.querySelectorAll('.mat-ripple-element').length).toBe(0); + expect(slideToggleElement.querySelectorAll(rippleSelector).length).toBe(0); dispatchFakeEvent(labelElement, 'mousedown'); dispatchFakeEvent(labelElement, 'mouseup'); - expect(slideToggleElement.querySelectorAll('.mat-ripple-element').length).toBe(0); + expect(slideToggleElement.querySelectorAll(rippleSelector).length).toBe(0); }); }); diff --git a/src/lib/slide-toggle/slide-toggle.ts b/src/lib/slide-toggle/slide-toggle.ts index 1306a1bd5519..598faab208a7 100644 --- a/src/lib/slide-toggle/slide-toggle.ts +++ b/src/lib/slide-toggle/slide-toggle.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y'; +import {FocusMonitor} from '@angular/cdk/a11y'; import {Directionality} from '@angular/cdk/bidi'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; import {Platform} from '@angular/cdk/platform'; @@ -35,12 +35,10 @@ import { CanDisableRipple, HammerInput, HasTabIndex, - MatRipple, mixinColor, mixinDisabled, mixinDisableRipple, mixinTabIndex, - RippleRef, } from '@angular/material/core'; import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; import { @@ -104,9 +102,6 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro private _required: boolean = false; private _checked: boolean = false; - /** Reference to the focus state ripple. */ - private _focusRipple: RippleRef | null; - /** Whether the thumb is currently being dragged. */ private _dragging = false; @@ -179,9 +174,6 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro /** Reference to the underlying input element. */ @ViewChild('input') _inputElement: ElementRef; - /** Reference to the ripple directive on the thumb container. */ - @ViewChild(MatRipple) _ripple: MatRipple; - constructor(elementRef: ElementRef, /** * @deprecated The `_platform` parameter to be removed. @@ -202,12 +194,21 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro ngAfterContentInit() { this._focusMonitor - .monitor(this._inputElement.nativeElement) - .subscribe(focusOrigin => this._onInputFocusChange(focusOrigin)); + .monitor(this._elementRef.nativeElement, true) + .subscribe(focusOrigin => { + if (!focusOrigin) { + // When a focused element becomes disabled, the browser *immediately* fires a blur event. + // Angular does not expect events to be raised during change detection, so any state + // change (such as a form control's 'ng-touched') will cause a changed-after-checked + // error. See https://github.com/angular/angular/issues/17793. To work around this, + // we defer telling the form control it has been touched until the next tick. + Promise.resolve().then(() => this.onTouched()); + } + }); } ngOnDestroy() { - this._focusMonitor.stopMonitoring(this._inputElement.nativeElement); + this._focusMonitor.stopMonitoring(this._elementRef.nativeElement); } /** Method being called whenever the underlying input emits a change event. */ @@ -282,28 +283,6 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro this.onChange(this.checked); } - /** Function is called whenever the focus changes for the input element. */ - private _onInputFocusChange(focusOrigin: FocusOrigin) { - // TODO(paul): support `program`. See https://github.com/angular/material2/issues/9889 - if (!this._focusRipple && focusOrigin === 'keyboard') { - // For keyboard focus show a persistent ripple as focus indicator. - this._focusRipple = this._ripple.launch(0, 0, {persistent: true}); - } else if (!focusOrigin) { - // When a focused element becomes disabled, the browser *immediately* fires a blur event. - // Angular does not expect events to be raised during change detection, so any state change - // (such as a form control's 'ng-touched') will cause a changed-after-checked error. - // See https://github.com/angular/angular/issues/17793. To work around this, we defer telling - // the form control it has been touched until the next tick. - Promise.resolve().then(() => this.onTouched()); - - // Fade out and clear the focus ripple if one is currently present. - if (this._focusRipple) { - this._focusRipple.fadeOut(); - this._focusRipple = null; - } - } - } - /** * Emits a change event on the `change` output. Also notifies the FormControl about the change. */