diff --git a/src/lib/slide-toggle/BUILD.bazel b/src/lib/slide-toggle/BUILD.bazel index df73202b988..af154030316 100644 --- a/src/lib/slide-toggle/BUILD.bazel +++ b/src/lib/slide-toggle/BUILD.bazel @@ -11,6 +11,7 @@ ng_module( deps = [ "//src/lib/core", "//src/cdk/a11y", + "//src/cdk/bidi", "//src/cdk/coercion", "//src/cdk/observers", "//src/cdk/platform", diff --git a/src/lib/slide-toggle/slide-toggle.scss b/src/lib/slide-toggle/slide-toggle.scss index fc68eac53ea..6980ba5e232 100644 --- a/src/lib/slide-toggle/slide-toggle.scss +++ b/src/lib/slide-toggle/slide-toggle.scss @@ -33,6 +33,10 @@ $mat-slide-toggle-bar-track-width: $mat-slide-toggle-bar-width - $mat-slide-togg &.mat-checked { .mat-slide-toggle-thumb-container { transform: translate3d($mat-slide-toggle-bar-track-width, 0, 0); + + [dir='rtl'] & { + transform: translate3d(-$mat-slide-toggle-bar-track-width, 0, 0); + } } } @@ -115,6 +119,11 @@ $mat-slide-toggle-bar-track-width: $mat-slide-toggle-bar-width - $mat-slide-togg ._mat-animation-noopable & { transition: none; } + + [dir='rtl'] & { + left: auto; + right: 0; + } } // The visual thumb element that moves inside of the thumb bar. @@ -147,8 +156,15 @@ $mat-slide-toggle-bar-track-width: $mat-slide-toggle-bar-width - $mat-slide-togg .mat-slide-toggle-input { // Move the input to the bottom and in the middle of the thumb. // Visual improvement to properly show browser popups when being required. + $horizontal-offset: $mat-slide-toggle-thumb-size / 2; + bottom: 0; - left: $mat-slide-toggle-thumb-size / 2; + left: $horizontal-offset; + + [dir='rtl'] & { + left: auto; + right: $horizontal-offset; + } } .mat-slide-toggle-bar, diff --git a/src/lib/slide-toggle/slide-toggle.spec.ts b/src/lib/slide-toggle/slide-toggle.spec.ts index 9c0c34fbb5f..f9e51211846 100644 --- a/src/lib/slide-toggle/slide-toggle.spec.ts +++ b/src/lib/slide-toggle/slide-toggle.spec.ts @@ -5,6 +5,7 @@ import {ComponentFixture, fakeAsync, flushMicrotasks, TestBed, tick} from '@angu 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'; import {MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS} from './slide-toggle-config'; import {MatSlideToggle, MatSlideToggleChange, MatSlideToggleModule} from './index'; @@ -18,7 +19,7 @@ describe('MatSlideToggle without forms', () => { mutationObserverCallbacks = []; TestBed.configureTestingModule({ - imports: [MatSlideToggleModule], + imports: [MatSlideToggleModule, BidiModule], declarations: [ SlideToggleBasic, SlideToggleWithTabindexAttr, @@ -493,6 +494,29 @@ describe('MatSlideToggle without forms', () => { expect(slideThumbContainer.classList).not.toContain('mat-dragging'); })); + it('should drag from start to end in RTL', fakeAsync(() => { + testComponent.direction = 'rtl'; + fixture.detectChanges(); + + expect(slideToggle.checked).toBe(false); + + gestureConfig.emitEventForElement('slidestart', slideThumbContainer); + + expect(slideThumbContainer.classList).toContain('mat-dragging'); + + gestureConfig.emitEventForElement('slide', slideThumbContainer, { + deltaX: -200 // Arbitrary, large delta that will be clamped to the end of the slide-toggle. + }); + + gestureConfig.emitEventForElement('slideend', slideThumbContainer); + + // Flush the timeout for the slide ending. + tick(); + + expect(slideToggle.checked).toBe(true); + expect(slideThumbContainer.classList).not.toContain('mat-dragging'); + })); + it('should drag from end to start', fakeAsync(() => { slideToggle.checked = true; @@ -513,6 +537,29 @@ describe('MatSlideToggle without forms', () => { expect(slideThumbContainer.classList).not.toContain('mat-dragging'); })); + it('should drag from end to start in RTL', fakeAsync(() => { + testComponent.direction = 'rtl'; + fixture.detectChanges(); + + slideToggle.checked = true; + + gestureConfig.emitEventForElement('slidestart', slideThumbContainer); + + expect(slideThumbContainer.classList).toContain('mat-dragging'); + + gestureConfig.emitEventForElement('slide', slideThumbContainer, { + deltaX: 200 // Arbitrary, large delta that will be clamped to the end of the slide-toggle. + }); + + gestureConfig.emitEventForElement('slideend', slideThumbContainer); + + // Flush the timeout for the slide ending. + tick(); + + expect(slideToggle.checked).toBe(false); + expect(slideThumbContainer.classList).not.toContain('mat-dragging'); + })); + it('should not drag when disabled', fakeAsync(() => { slideToggle.disabled = true; @@ -943,7 +990,7 @@ describe('MatSlideToggle with forms', () => { @Component({ template: ` - void = () => {}; onSlideChange = (event: MatSlideToggleChange) => this.lastEvent = event; diff --git a/src/lib/slide-toggle/slide-toggle.ts b/src/lib/slide-toggle/slide-toggle.ts index ead0370a221..bb4e02cded5 100644 --- a/src/lib/slide-toggle/slide-toggle.ts +++ b/src/lib/slide-toggle/slide-toggle.ts @@ -7,6 +7,7 @@ */ import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y'; +import {Directionality} from '@angular/cdk/bidi'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; import {Platform} from '@angular/cdk/platform'; import { @@ -193,7 +194,8 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro private _ngZone: NgZone, @Inject(MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS) public defaults: MatSlideToggleDefaultOptions, - @Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string) { + @Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string, + @Optional() private _dir?: Directionality) { super(elementRef); this.tabIndex = parseInt(tabIndex) || 0; } @@ -330,9 +332,10 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro _onDrag(event: HammerInput) { if (this._dragging) { - this._dragPercentage = this._getDragPercentage(event.deltaX); + const direction = this._dir && this._dir.value === 'rtl' ? -1 : 1; + this._dragPercentage = this._getDragPercentage(event.deltaX * direction); // Calculate the moved distance based on the thumb bar width. - const dragX = (this._dragPercentage / 100) * this._thumbBarWidth; + const dragX = (this._dragPercentage / 100) * this._thumbBarWidth * direction; this._thumbEl.nativeElement.style.transform = `translate3d(${dragX}px, 0, 0)`; } }