Skip to content

Commit 9a191b3

Browse files
crisbetojosephperrott
authored andcommitted
fix(slide-toggle): invert the thumb and slide gesture in rtl (#12284)
1 parent f9d3757 commit 9a191b3

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)