Skip to content

Commit d33cf1f

Browse files
committed
fix(slide-toggle): invert the thumb and slide gesture in rtl
Inverts the direction of the slide toggle's thumb, as well as the dragging gesture in RTL. Previously it had the same behavior as in LTR.
1 parent adda21f commit d33cf1f

File tree

4 files changed

+74
-6
lines changed

4 files changed

+74
-6
lines changed

src/lib/slide-toggle/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ ng_module(
1111
deps = [
1212
"//src/lib/core",
1313
"//src/cdk/a11y",
14+
"//src/cdk/bidi",
1415
"//src/cdk/coercion",
1516
"//src/cdk/observers",
1617
"//src/cdk/platform",

src/lib/slide-toggle/slide-toggle.scss

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ $mat-slide-toggle-bar-track-width: $mat-slide-toggle-bar-width - $mat-slide-togg
3333
&.mat-checked {
3434
.mat-slide-toggle-thumb-container {
3535
transform: translate3d($mat-slide-toggle-bar-track-width, 0, 0);
36+
37+
[dir='rtl'] & {
38+
transform: translate3d(-$mat-slide-toggle-bar-track-width, 0, 0);
39+
}
3640
}
3741
}
3842

@@ -115,6 +119,11 @@ $mat-slide-toggle-bar-track-width: $mat-slide-toggle-bar-width - $mat-slide-togg
115119
._mat-animation-noopable & {
116120
transition: none;
117121
}
122+
123+
[dir='rtl'] & {
124+
left: auto;
125+
right: 0;
126+
}
118127
}
119128

120129
// 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
147156
.mat-slide-toggle-input {
148157
// Move the input to the bottom and in the middle of the thumb.
149158
// Visual improvement to properly show browser popups when being required.
159+
$horizontal-offset: $mat-slide-toggle-thumb-size / 2;
160+
150161
bottom: 0;
151-
left: $mat-slide-toggle-thumb-size / 2;
162+
left: $horizontal-offset;
163+
164+
[dir='rtl'] & {
165+
left: auto;
166+
right: $horizontal-offset;
167+
}
152168
}
153169

154170
.mat-slide-toggle-bar,

src/lib/slide-toggle/slide-toggle.spec.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {ComponentFixture, fakeAsync, flushMicrotasks, TestBed, tick} from '@angu
55
import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms';
66
import {defaultRippleAnimationConfig} from '@angular/material/core';
77
import {By, HAMMER_GESTURE_CONFIG} from '@angular/platform-browser';
8+
import {BidiModule, Direction} from '@angular/cdk/bidi';
89
import {TestGestureConfig} from '../slider/test-gesture-config';
910
import {MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS} from './slide-toggle-config';
1011
import {MatSlideToggle, MatSlideToggleChange, MatSlideToggleModule} from './index';
@@ -18,7 +19,7 @@ describe('MatSlideToggle without forms', () => {
1819
mutationObserverCallbacks = [];
1920

2021
TestBed.configureTestingModule({
21-
imports: [MatSlideToggleModule],
22+
imports: [MatSlideToggleModule, BidiModule],
2223
declarations: [
2324
SlideToggleBasic,
2425
SlideToggleWithTabindexAttr,
@@ -493,6 +494,29 @@ describe('MatSlideToggle without forms', () => {
493494
expect(slideThumbContainer.classList).not.toContain('mat-dragging');
494495
}));
495496

497+
it('should drag from start to end in RTL', fakeAsync(() => {
498+
testComponent.direction = 'rtl';
499+
fixture.detectChanges();
500+
501+
expect(slideToggle.checked).toBe(false);
502+
503+
gestureConfig.emitEventForElement('slidestart', slideThumbContainer);
504+
505+
expect(slideThumbContainer.classList).toContain('mat-dragging');
506+
507+
gestureConfig.emitEventForElement('slide', slideThumbContainer, {
508+
deltaX: -200 // Arbitrary, large delta that will be clamped to the end of the slide-toggle.
509+
});
510+
511+
gestureConfig.emitEventForElement('slideend', slideThumbContainer);
512+
513+
// Flush the timeout for the slide ending.
514+
tick();
515+
516+
expect(slideToggle.checked).toBe(true);
517+
expect(slideThumbContainer.classList).not.toContain('mat-dragging');
518+
}));
519+
496520
it('should drag from end to start', fakeAsync(() => {
497521
slideToggle.checked = true;
498522

@@ -513,6 +537,29 @@ describe('MatSlideToggle without forms', () => {
513537
expect(slideThumbContainer.classList).not.toContain('mat-dragging');
514538
}));
515539

540+
it('should drag from end to start in RTL', fakeAsync(() => {
541+
testComponent.direction = 'rtl';
542+
fixture.detectChanges();
543+
544+
slideToggle.checked = true;
545+
546+
gestureConfig.emitEventForElement('slidestart', slideThumbContainer);
547+
548+
expect(slideThumbContainer.classList).toContain('mat-dragging');
549+
550+
gestureConfig.emitEventForElement('slide', slideThumbContainer, {
551+
deltaX: 200 // Arbitrary, large delta that will be clamped to the end of the slide-toggle.
552+
});
553+
554+
gestureConfig.emitEventForElement('slideend', slideThumbContainer);
555+
556+
// Flush the timeout for the slide ending.
557+
tick();
558+
559+
expect(slideToggle.checked).toBe(false);
560+
expect(slideThumbContainer.classList).not.toContain('mat-dragging');
561+
}));
562+
516563
it('should not drag when disabled', fakeAsync(() => {
517564
slideToggle.disabled = true;
518565

@@ -943,7 +990,7 @@ describe('MatSlideToggle with forms', () => {
943990

944991
@Component({
945992
template: `
946-
<mat-slide-toggle [required]="isRequired"
993+
<mat-slide-toggle [dir]="direction" [required]="isRequired"
947994
[disabled]="isDisabled"
948995
[color]="slideColor"
949996
[id]="slideId"
@@ -976,6 +1023,7 @@ class SlideToggleBasic {
9761023
labelPosition: string;
9771024
toggleTriggered: number = 0;
9781025
dragTriggered: number = 0;
1026+
direction: Direction = 'ltr';
9791027

9801028
onSlideClick: (event?: Event) => void = () => {};
9811029
onSlideChange = (event: MatSlideToggleChange) => this.lastEvent = event;

src/lib/slide-toggle/slide-toggle.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y';
10+
import {Directionality} from '@angular/cdk/bidi';
1011
import {coerceBooleanProperty} from '@angular/cdk/coercion';
1112
import {Platform} from '@angular/cdk/platform';
1213
import {
@@ -193,7 +194,8 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
193194
private _ngZone: NgZone,
194195
@Inject(MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS)
195196
public defaults: MatSlideToggleDefaultOptions,
196-
@Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string) {
197+
@Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string,
198+
@Optional() private _dir?: Directionality) {
197199
super(elementRef);
198200
this.tabIndex = parseInt(tabIndex) || 0;
199201
}
@@ -330,9 +332,10 @@ export class MatSlideToggle extends _MatSlideToggleMixinBase implements OnDestro
330332

331333
_onDrag(event: HammerInput) {
332334
if (this._dragging) {
333-
this._dragPercentage = this._getDragPercentage(event.deltaX);
335+
const direction = this._dir && this._dir.value === 'rtl' ? -1 : 1;
336+
this._dragPercentage = this._getDragPercentage(event.deltaX * direction);
334337
// Calculate the moved distance based on the thumb bar width.
335-
const dragX = (this._dragPercentage / 100) * this._thumbBarWidth;
338+
const dragX = (this._dragPercentage / 100) * this._thumbBarWidth * direction;
336339
this._thumbEl.nativeElement.style.transform = `translate3d(${dragX}px, 0, 0)`;
337340
}
338341
}

0 commit comments

Comments
 (0)