From 020b369bc5eabd068701cff470741e7db5597717 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 3 Nov 2016 15:07:15 -0700 Subject: [PATCH 01/28] Addressed comments. --- src/lib/slider/slider.ts | 126 +++++++-------------------------------- 1 file changed, 20 insertions(+), 106 deletions(-) diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index e44638151165..b99300a2830b 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -1,31 +1,19 @@ import { - NgModule, - ModuleWithProviders, - Component, - ElementRef, - Input, - Output, - ViewEncapsulation, - forwardRef, - EventEmitter, - Optional + NgModule, + ModuleWithProviders, + Component, + ElementRef, + Input, + Output, + ViewEncapsulation, + forwardRef, + EventEmitter, } from '@angular/core'; import {NG_VALUE_ACCESSOR, ControlValueAccessor, FormsModule} from '@angular/forms'; import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; import {MdGestureConfig, coerceBooleanProperty, coerceNumberProperty} from '../core'; import {Input as HammerInput} from 'hammerjs'; -import {Dir} from '../core/rtl/dir'; import {CommonModule} from '@angular/common'; -import { - PAGE_UP, - PAGE_DOWN, - END, - HOME, - LEFT_ARROW, - UP_ARROW, - RIGHT_ARROW, - DOWN_ARROW -} from '../core/keyboard/keycodes'; /** * Visually, a 30px separation between tick marks looks best. This is very subjective but it is @@ -56,12 +44,10 @@ export class MdSliderChange { host: { '(blur)': '_onBlur()', '(click)': '_onClick($event)', - '(keydown)': '_onKeydown($event)', '(mouseenter)': '_onMouseenter()', '(slide)': '_onSlide($event)', '(slideend)': '_onSlideEnd()', '(slidestart)': '_onSlideStart($event)', - 'role': 'slider', 'tabindex': '0', '[attr.aria-disabled]': 'disabled', '[attr.aria-valuemax]': 'max', @@ -70,7 +56,6 @@ export class MdSliderChange { '[class.md-slider-active]': '_isActive', '[class.md-slider-disabled]': 'disabled', '[class.md-slider-has-ticks]': 'tickInterval', - '[class.md-slider-inverted]': 'invert', '[class.md-slider-sliding]': '_isSliding', '[class.md-slider-thumb-label-showing]': 'thumbLabel', }, @@ -142,6 +127,7 @@ export class MdSlider implements ControlValueAccessor { private _tickIntervalPercent: number = 0; get tickIntervalPercent() { return this._tickIntervalPercent; } + get halfTickIntervalPercent() { return this._tickIntervalPercent / 2; } /** The percentage of the slider that coincides with the value. */ private _percent: number = 0; @@ -193,47 +179,25 @@ export class MdSlider implements ControlValueAccessor { this._percent = this._calculatePercentage(this.value); } - /** Whether the slider is inverted. */ - @Input() - get invert() { return this._invert; } - set invert(value: boolean) { this._invert = coerceBooleanProperty(value); } - private _invert = false; - - /** CSS styles for the track fill element. */ - get trackFillStyles(): { [key: string]: string } { - return { - 'flexBasis': `${this.percent * 100}%` - }; + get trackFillFlexBasis() { + return this.percent * 100 + '%'; } - /** CSS styles for the ticks container element. */ - get ticksContainerStyles(): { [key: string]: string } { - return { - 'marginLeft': `${this.direction == 'rtl' ? '' : '-'}${this.tickIntervalPercent / 2 * 100}%` - }; + get ticksMarginLeft() { + return this.tickIntervalPercent / 2 * 100 + '%'; } - /** CSS styles for the ticks element. */ - get ticksStyles() { - let styles: { [key: string]: string } = { - 'backgroundSize': `${this.tickIntervalPercent * 100}% 2px` - }; - if (this.direction == 'rtl') { - styles['marginRight'] = `-${this.tickIntervalPercent / 2 * 100}%`; - } else { - styles['marginLeft'] = `${this.tickIntervalPercent / 2 * 100}%`; - } - return styles; + get ticksContainerMarginLeft() { + return '-' + this.ticksMarginLeft; } - /** The language direction for this slider element. */ - get direction() { - return (this._dir && this._dir.value == 'rtl') ? 'rtl' : 'ltr'; + get ticksBackgroundSize() { + return this.tickIntervalPercent * 100 + '% 2px'; } @Output() change = new EventEmitter(); - constructor(@Optional() private _dir: Dir, elementRef: ElementRef) { + constructor(elementRef: ElementRef) { this._renderer = new SliderRenderer(elementRef); } @@ -292,53 +256,6 @@ export class MdSlider implements ControlValueAccessor { this.onTouched(); } - _onKeydown(event: KeyboardEvent) { - if (this.disabled) { return; } - - switch (event.keyCode) { - case PAGE_UP: - this._increment(10); - break; - case PAGE_DOWN: - this._increment(-10); - break; - case END: - this.value = this.max; - break; - case HOME: - this.value = this.min; - break; - case LEFT_ARROW: - this._increment(this._isLeftMin() ? -1 : 1); - break; - case UP_ARROW: - this._increment(1); - break; - case RIGHT_ARROW: - this._increment(this._isLeftMin() ? 1 : -1); - break; - case DOWN_ARROW: - this._increment(-1); - break; - default: - // Return if the key is not one that we explicitly handle to avoid calling preventDefault on - // it. - return; - } - - event.preventDefault(); - } - - /** Whether the left side of the slider is the minimum value. */ - private _isLeftMin() { - return (this.direction == 'rtl') == this.invert; - } - - /** Increments the slider by the given number of steps (negative number decrements). */ - private _increment(numSteps: number) { - this.value = this._clamp(this.value + this.step * numSteps, this.min, this.max); - } - /** * Calculate the new value from the new physical location. The value will always be snapped. */ @@ -352,9 +269,6 @@ export class MdSlider implements ControlValueAccessor { // The exact value is calculated from the event and used to find the closest snap value. let percent = this._clamp((pos - offset) / size); - if (!this._isLeftMin()) { - percent = 1 - percent; - } let exactValue = this._calculateValue(percent); // This calculation finds the closest step by finding the closest whole number divisible by the @@ -475,7 +389,7 @@ export class SliderRenderer { @NgModule({ - imports: [CommonModule, FormsModule], + imports: [FormsModule, CommonModule], exports: [MdSlider], declarations: [MdSlider], providers: [ From aeb0e42bc9d76bca5ffae7ccebcfa62c4b6e3cb1 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 3 Nov 2016 16:21:29 -0700 Subject: [PATCH 02/28] PercentPipe was adding extra space before '%', so replaced it. --- src/lib/slider/slider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index b99300a2830b..8436f9c1cb24 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -127,7 +127,6 @@ export class MdSlider implements ControlValueAccessor { private _tickIntervalPercent: number = 0; get tickIntervalPercent() { return this._tickIntervalPercent; } - get halfTickIntervalPercent() { return this._tickIntervalPercent / 2; } /** The percentage of the slider that coincides with the value. */ private _percent: number = 0; From 90c63f25cf297a94ee8198286ab5e0e759661215 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 4 Nov 2016 15:29:58 -0700 Subject: [PATCH 03/28] remove CommonModule from imports. --- src/lib/slider/slider.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 8436f9c1cb24..4e02a3c90f50 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -13,7 +13,6 @@ import {NG_VALUE_ACCESSOR, ControlValueAccessor, FormsModule} from '@angular/for import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; import {MdGestureConfig, coerceBooleanProperty, coerceNumberProperty} from '../core'; import {Input as HammerInput} from 'hammerjs'; -import {CommonModule} from '@angular/common'; /** * Visually, a 30px separation between tick marks looks best. This is very subjective but it is @@ -388,7 +387,7 @@ export class SliderRenderer { @NgModule({ - imports: [FormsModule, CommonModule], + imports: [FormsModule], exports: [MdSlider], declarations: [MdSlider], providers: [ From 23808e559613641caa94520de13eaa3f5ae78e8b Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 4 Nov 2016 16:25:29 -0700 Subject: [PATCH 04/28] fix(slider): keyboard support. --- src/lib/slider/slider.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 4e02a3c90f50..719a32c1b8b2 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -43,10 +43,19 @@ export class MdSliderChange { host: { '(blur)': '_onBlur()', '(click)': '_onClick($event)', + '(keydown.arrowdown)': '_increment($event, -1)', + '(keydown.arrowleft)': '_increment($event, -1)', + '(keydown.arrowright)': '_increment($event, 1)', + '(keydown.arrowup)': '_increment($event, 1)', + '(keydown.end)': '_onEndKeyPressed($event)', + '(keydown.home)': '_onHomeKeyPressed($event)', + '(keydown.pagedown)': '_increment($event, -10)', + '(keydown.pageup)': '_increment($event, 10)', '(mouseenter)': '_onMouseenter()', '(slide)': '_onSlide($event)', '(slideend)': '_onSlideEnd()', '(slidestart)': '_onSlideStart($event)', + 'role': 'slider', 'tabindex': '0', '[attr.aria-disabled]': 'disabled', '[attr.aria-valuemax]': 'max', @@ -254,6 +263,24 @@ export class MdSlider implements ControlValueAccessor { this.onTouched(); } + /** Increments the slider by the given number of steps (negative number decrements. */ + _increment(event: KeyboardEvent, numSteps: number) { + this.value = this._clamp(this.value + this.step * numSteps, this.min, this.max); + event.preventDefault(); + } + + /** Handles end key pressed. */ + _onEndKeyPressed(event: KeyboardEvent) { + this.value = this.max; + event.preventDefault(); + } + + /** Handles home key pressed. */ + _onHomeKeyPressed(event: KeyboardEvent) { + this.value = this.min; + event.preventDefault(); + } + /** * Calculate the new value from the new physical location. The value will always be snapped. */ From 9c9688d711b4004a0f75a17467169e39243e79ce Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Mon, 7 Nov 2016 11:54:57 -0800 Subject: [PATCH 05/28] prevent keyboard interaction with disabled slider. --- src/lib/slider/slider.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 719a32c1b8b2..6717c97f4c29 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -265,18 +265,24 @@ export class MdSlider implements ControlValueAccessor { /** Increments the slider by the given number of steps (negative number decrements. */ _increment(event: KeyboardEvent, numSteps: number) { + if (this.disabled) { return; } + this.value = this._clamp(this.value + this.step * numSteps, this.min, this.max); event.preventDefault(); } /** Handles end key pressed. */ _onEndKeyPressed(event: KeyboardEvent) { + if (this.disabled) { return; } + this.value = this.max; event.preventDefault(); } /** Handles home key pressed. */ _onHomeKeyPressed(event: KeyboardEvent) { + if (this.disabled) { return; } + this.value = this.min; event.preventDefault(); } From 1add946872d5e502daac7eaec7cd6cc4e20bdd9c Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 10 Nov 2016 13:34:34 -0800 Subject: [PATCH 06/28] fix comment --- src/lib/slider/slider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 6717c97f4c29..28330fa1aac2 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -263,7 +263,7 @@ export class MdSlider implements ControlValueAccessor { this.onTouched(); } - /** Increments the slider by the given number of steps (negative number decrements. */ + /** Increments the slider by the given number of steps (negative number decrements). */ _increment(event: KeyboardEvent, numSteps: number) { if (this.disabled) { return; } From 522b8c68913eb95c5680d783d35aadd9526904bc Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 10 Nov 2016 15:01:56 -0800 Subject: [PATCH 07/28] switch to event.keyCode --- src/lib/slider/slider.ts | 57 +++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 28330fa1aac2..93d7425bd041 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -43,14 +43,7 @@ export class MdSliderChange { host: { '(blur)': '_onBlur()', '(click)': '_onClick($event)', - '(keydown.arrowdown)': '_increment($event, -1)', - '(keydown.arrowleft)': '_increment($event, -1)', - '(keydown.arrowright)': '_increment($event, 1)', - '(keydown.arrowup)': '_increment($event, 1)', - '(keydown.end)': '_onEndKeyPressed($event)', - '(keydown.home)': '_onHomeKeyPressed($event)', - '(keydown.pagedown)': '_increment($event, -10)', - '(keydown.pageup)': '_increment($event, 10)', + '(keydown)': '_onKeydown($event)', '(mouseenter)': '_onMouseenter()', '(slide)': '_onSlide($event)', '(slideend)': '_onSlideEnd()', @@ -263,28 +256,44 @@ export class MdSlider implements ControlValueAccessor { this.onTouched(); } - /** Increments the slider by the given number of steps (negative number decrements). */ - _increment(event: KeyboardEvent, numSteps: number) { + _onKeydown(event: KeyboardEvent) { if (this.disabled) { return; } - this.value = this._clamp(this.value + this.step * numSteps, this.min, this.max); - event.preventDefault(); - } - - /** Handles end key pressed. */ - _onEndKeyPressed(event: KeyboardEvent) { - if (this.disabled) { return; } + switch (event.keyCode) { + case 33: /* page up */ + this._increment(10); + break; + case 34: /* page down */ + this._increment(-10); + break; + case 35: /* end */ + this.value = this.max; + break; + case 36: /* home */ + this.value = this.min; + break; + case 37: /* left arrow */ + this._increment(-1); + break; + case 38: /* up arrow */ + this._increment(1); + break; + case 39: /* right arrow */ + this._increment(1); + break; + case 40: /* down arrow */ + this._increment(-1); + break; + default: + return; + } - this.value = this.max; event.preventDefault(); } - /** Handles home key pressed. */ - _onHomeKeyPressed(event: KeyboardEvent) { - if (this.disabled) { return; } - - this.value = this.min; - event.preventDefault(); + /** Increments the slider by the given number of steps (negative number decrements). */ + private _increment(numSteps: number) { + this.value = this._clamp(this.value + this.step * numSteps, this.min, this.max); } /** From 02a49d52c25ca71de9fbfa8e23bdd935d049e7ed Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 10 Nov 2016 15:55:22 -0800 Subject: [PATCH 08/28] added tests --- src/lib/slider/slider.ts | 44 ++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 93d7425bd041..6405e988209d 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -1,18 +1,28 @@ import { - NgModule, - ModuleWithProviders, - Component, - ElementRef, - Input, - Output, - ViewEncapsulation, - forwardRef, - EventEmitter, + NgModule, + ModuleWithProviders, + Component, + ElementRef, + Input, + Output, + ViewEncapsulation, + forwardRef, + EventEmitter } from '@angular/core'; import {NG_VALUE_ACCESSOR, ControlValueAccessor, FormsModule} from '@angular/forms'; import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; import {MdGestureConfig, coerceBooleanProperty, coerceNumberProperty} from '../core'; import {Input as HammerInput} from 'hammerjs'; +import { + PAGE_UP, + PAGE_DOWN, + END, + HOME, + LEFT_ARROW, + UP_ARROW, + RIGHT_ARROW, + DOWN_ARROW +} from '../core/keyboard/keycodes'; /** * Visually, a 30px separation between tick marks looks best. This is very subjective but it is @@ -260,28 +270,28 @@ export class MdSlider implements ControlValueAccessor { if (this.disabled) { return; } switch (event.keyCode) { - case 33: /* page up */ + case PAGE_UP: this._increment(10); break; - case 34: /* page down */ + case PAGE_DOWN: this._increment(-10); break; - case 35: /* end */ + case END: this.value = this.max; break; - case 36: /* home */ + case HOME: this.value = this.min; break; - case 37: /* left arrow */ + case LEFT_ARROW: this._increment(-1); break; - case 38: /* up arrow */ + case UP_ARROW: this._increment(1); break; - case 39: /* right arrow */ + case RIGHT_ARROW: this._increment(1); break; - case 40: /* down arrow */ + case DOWN_ARROW: this._increment(-1); break; default: From 88e435bff5875b7338c38512c6aa4c0639e7a632 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 11 Nov 2016 09:29:44 -0800 Subject: [PATCH 09/28] comment why default: return; --- src/lib/slider/slider.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 6405e988209d..21394dd3b481 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -295,6 +295,8 @@ export class MdSlider implements ControlValueAccessor { this._increment(-1); break; default: + // Return if the key is not one that we explicitly handle to avoid calling preventDefault on + // it. return; } From df7bfc8c94f0ddff69751fd52e5cf8ea314cea2d Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 8 Nov 2016 16:56:03 -0800 Subject: [PATCH 10/28] fix(slider): support for rtl and inverted sliders. --- src/demo-app/slider/slider-demo.html | 6 +++ src/lib/slider/slider.scss | 6 +-- src/lib/slider/slider.ts | 57 ++++++++++++++++++---------- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/demo-app/slider/slider-demo.html b/src/demo-app/slider/slider-demo.html index 4b6c65478563..18494fc29a7f 100644 --- a/src/demo-app/slider/slider-demo.html +++ b/src/demo-app/slider/slider-demo.html @@ -37,6 +37,12 @@

Slider with two-way binding

Inverted slider

+

Vertical slider

+ + +

Inverted vertical slider

+ + diff --git a/src/lib/slider/slider.scss b/src/lib/slider/slider.scss index 2b2c3f36f860..39ae8bfcb253 100644 --- a/src/lib/slider/slider.scss +++ b/src/lib/slider/slider.scss @@ -47,8 +47,8 @@ md-slider { box-shadow: inset (-2 * $md-slider-tick-size) 0 0 (-$md-slider-tick-size) $md-slider-tick-color; } -[dir='rtl'] .md-slider-has-ticks.md-slider-active .md-slider-track, -[dir='rtl'] .md-slider-has-ticks:hover .md-slider-track { +[dir="rtl"] .md-slider-has-ticks.md-slider-active .md-slider-track, +[dir="rtl"] .md-slider-has-ticks:hover .md-slider-track { box-shadow: inset (2 * $md-slider-tick-size) 0 0 (-$md-slider-tick-size) $md-slider-tick-color; } @@ -75,7 +75,7 @@ md-slider { overflow: hidden; } -[dir='rtl'] .md-slider-ticks-container { +[dir="rtl"] .md-slider-ticks-container { // translateZ(0) prevents chrome bug where overflow: hidden; doesn't work. transform: translateZ(0) rotate(180deg); } diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 21394dd3b481..a2acd2124000 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -1,18 +1,21 @@ import { - NgModule, - ModuleWithProviders, - Component, - ElementRef, - Input, - Output, - ViewEncapsulation, - forwardRef, - EventEmitter + NgModule, + ModuleWithProviders, + Component, + ElementRef, + Input, + Output, + ViewEncapsulation, + forwardRef, + EventEmitter, + Optional, } from '@angular/core'; import {NG_VALUE_ACCESSOR, ControlValueAccessor, FormsModule} from '@angular/forms'; import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; import {MdGestureConfig, coerceBooleanProperty, coerceNumberProperty} from '../core'; import {Input as HammerInput} from 'hammerjs'; +import {Dir} from "../core/rtl/dir"; +import {CommonModule} from '@angular/common'; import { PAGE_UP, PAGE_DOWN, @@ -67,6 +70,7 @@ export class MdSliderChange { '[class.md-slider-active]': '_isActive', '[class.md-slider-disabled]': 'disabled', '[class.md-slider-has-ticks]': 'tickInterval', + '[class.md-slider-inverted]': 'invert', '[class.md-slider-sliding]': '_isSliding', '[class.md-slider-thumb-label-showing]': 'thumbLabel', }, @@ -189,25 +193,35 @@ export class MdSlider implements ControlValueAccessor { this._percent = this._calculatePercentage(this.value); } - get trackFillFlexBasis() { - return this.percent * 100 + '%'; - } + /** Whether the slider is inverted. */ + @Input() + get invert() { return this._invert }; + set invert(value: boolean) { this._invert = coerceBooleanProperty(value); } + private _invert = false; - get ticksMarginLeft() { - return this.tickIntervalPercent / 2 * 100 + '%'; + get trackFillStyles(): { [key: string]: string } { + return { + 'flexBasis': `${this.percent * 100}%` + }; } - get ticksContainerMarginLeft() { - return '-' + this.ticksMarginLeft; + get ticksContainerStyles(): { [key: string]: string } { + return { + 'marginLeft': `${this._dir.value == 'rtl' ? '' : '-'}${this.tickIntervalPercent / 2 * 100}%`, + }; } - get ticksBackgroundSize() { - return this.tickIntervalPercent * 100 + '% 2px'; + get ticksStyles(): { [key: string]: string } { + let direction = this._dir.value == 'rtl' ? 'Right' : 'Left'; + return { + 'backgroundSize': `${this.tickIntervalPercent * 100}% 2px`, + [`margin${direction}`]: `-${this.tickIntervalPercent / 2 * 100}%`, + }; } @Output() change = new EventEmitter(); - constructor(elementRef: ElementRef) { + constructor(@Optional() private _dir: Dir, elementRef: ElementRef) { this._renderer = new SliderRenderer(elementRef); } @@ -321,6 +335,9 @@ export class MdSlider implements ControlValueAccessor { // The exact value is calculated from the event and used to find the closest snap value. let percent = this._clamp((pos - offset) / size); + if ((this._dir.value == 'rtl') != this.invert) { + percent = 1 - percent; + } let exactValue = this._calculateValue(percent); // This calculation finds the closest step by finding the closest whole number divisible by the @@ -441,7 +458,7 @@ export class SliderRenderer { @NgModule({ - imports: [FormsModule], + imports: [CommonModule, FormsModule], exports: [MdSlider], declarations: [MdSlider], providers: [ From a79ab54a7f3dd45cfc1d48ad32b19df9d71b3b03 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 8 Nov 2016 17:44:20 -0800 Subject: [PATCH 11/28] clean up demo html file --- src/demo-app/slider/slider-demo.html | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/demo-app/slider/slider-demo.html b/src/demo-app/slider/slider-demo.html index 18494fc29a7f..4b6c65478563 100644 --- a/src/demo-app/slider/slider-demo.html +++ b/src/demo-app/slider/slider-demo.html @@ -37,12 +37,6 @@

Slider with two-way binding

Inverted slider

-

Vertical slider

- - -

Inverted vertical slider

- - From 6514ec65e98dc862e8a51a376a597c11ccd0e735 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 9 Nov 2016 15:29:57 -0800 Subject: [PATCH 12/28] fixed tests and lint issues --- src/lib/slider/slider.scss | 6 +++--- src/lib/slider/slider.ts | 30 +++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/lib/slider/slider.scss b/src/lib/slider/slider.scss index 39ae8bfcb253..2b2c3f36f860 100644 --- a/src/lib/slider/slider.scss +++ b/src/lib/slider/slider.scss @@ -47,8 +47,8 @@ md-slider { box-shadow: inset (-2 * $md-slider-tick-size) 0 0 (-$md-slider-tick-size) $md-slider-tick-color; } -[dir="rtl"] .md-slider-has-ticks.md-slider-active .md-slider-track, -[dir="rtl"] .md-slider-has-ticks:hover .md-slider-track { +[dir='rtl'] .md-slider-has-ticks.md-slider-active .md-slider-track, +[dir='rtl'] .md-slider-has-ticks:hover .md-slider-track { box-shadow: inset (2 * $md-slider-tick-size) 0 0 (-$md-slider-tick-size) $md-slider-tick-color; } @@ -75,7 +75,7 @@ md-slider { overflow: hidden; } -[dir="rtl"] .md-slider-ticks-container { +[dir='rtl'] .md-slider-ticks-container { // translateZ(0) prevents chrome bug where overflow: hidden; doesn't work. transform: translateZ(0) rotate(180deg); } diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index a2acd2124000..a7023a12b64c 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -14,7 +14,7 @@ import {NG_VALUE_ACCESSOR, ControlValueAccessor, FormsModule} from '@angular/for import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; import {MdGestureConfig, coerceBooleanProperty, coerceNumberProperty} from '../core'; import {Input as HammerInput} from 'hammerjs'; -import {Dir} from "../core/rtl/dir"; +import {Dir} from '../core/rtl/dir'; import {CommonModule} from '@angular/common'; import { PAGE_UP, @@ -195,28 +195,40 @@ export class MdSlider implements ControlValueAccessor { /** Whether the slider is inverted. */ @Input() - get invert() { return this._invert }; + get invert() { return this._invert; } set invert(value: boolean) { this._invert = coerceBooleanProperty(value); } private _invert = false; + /** CSS styles for the track fill element. */ get trackFillStyles(): { [key: string]: string } { return { 'flexBasis': `${this.percent * 100}%` }; } + /** CSS styles for the ticks container element. */ get ticksContainerStyles(): { [key: string]: string } { return { - 'marginLeft': `${this._dir.value == 'rtl' ? '' : '-'}${this.tickIntervalPercent / 2 * 100}%`, + 'marginLeft': `${this.direction == 'rtl' ? '' : '-'}${this.tickIntervalPercent / 2 * 100}%` }; } - get ticksStyles(): { [key: string]: string } { - let direction = this._dir.value == 'rtl' ? 'Right' : 'Left'; - return { - 'backgroundSize': `${this.tickIntervalPercent * 100}% 2px`, - [`margin${direction}`]: `-${this.tickIntervalPercent / 2 * 100}%`, + /** CSS styles for the ticks element. */ + get ticksStyles() { + let styles: { [key: string]: string } = { + 'backgroundSize': `${this.tickIntervalPercent * 100}% 2px` }; + if (this.direction == 'rtl') { + styles['marginRight'] = `-${this.tickIntervalPercent / 2 * 100}%`; + } else { + styles['marginLeft'] = `${this.tickIntervalPercent / 2 * 100}%`; + } + return styles; + } + + /** The language direction for this slider element. */ + get direction() { + return (this._dir && this._dir.value == 'rtl') ? 'rtl' : 'ltr'; } @Output() change = new EventEmitter(); @@ -335,7 +347,7 @@ export class MdSlider implements ControlValueAccessor { // The exact value is calculated from the event and used to find the closest snap value. let percent = this._clamp((pos - offset) / size); - if ((this._dir.value == 'rtl') != this.invert) { + if ((this.direction == 'rtl') != this.invert) { percent = 1 - percent; } let exactValue = this._calculateValue(percent); From de13a20ddad401c86ea5c39e4a3cfe230cfbb778 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 10 Nov 2016 10:13:11 -0800 Subject: [PATCH 13/28] added tests --- src/lib/slider/slider.spec.ts | 68 +---------------------------------- 1 file changed, 1 insertion(+), 67 deletions(-) diff --git a/src/lib/slider/slider.spec.ts b/src/lib/slider/slider.spec.ts index b22ebc66c76d..494e614565d7 100644 --- a/src/lib/slider/slider.spec.ts +++ b/src/lib/slider/slider.spec.ts @@ -12,8 +12,7 @@ import { PAGE_DOWN, PAGE_UP, END, - HOME, - LEFT_ARROW + HOME, LEFT_ARROW } from '../core/keyboard/keycodes'; @@ -891,71 +890,6 @@ describe('MdSlider', () => { expect(sliderInstance.value).toBe(30); }); - - it('should decrement inverted slider by 1 on right arrow pressed', () => { - testComponent.invert = true; - sliderInstance.value = 100; - fixture.detectChanges(); - - dispatchKeydownEvent(sliderNativeElement, RIGHT_ARROW); - fixture.detectChanges(); - - expect(sliderInstance.value).toBe(99); - }); - - it('should increment inverted slider by 1 on left arrow pressed', () => { - testComponent.invert = true; - fixture.detectChanges(); - - dispatchKeydownEvent(sliderNativeElement, LEFT_ARROW); - fixture.detectChanges(); - - expect(sliderInstance.value).toBe(1); - }); - - it('should decrement RTL slider by 1 on right arrow pressed', () => { - testComponent.dir = 'rtl'; - sliderInstance.value = 100; - fixture.detectChanges(); - - dispatchKeydownEvent(sliderNativeElement, RIGHT_ARROW); - fixture.detectChanges(); - - expect(sliderInstance.value).toBe(99); - }); - - it('should increment RTL slider by 1 on left arrow pressed', () => { - testComponent.dir = 'rtl'; - fixture.detectChanges(); - - dispatchKeydownEvent(sliderNativeElement, LEFT_ARROW); - fixture.detectChanges(); - - expect(sliderInstance.value).toBe(1); - }); - - it('should increment inverted RTL slider by 1 on right arrow pressed', () => { - testComponent.dir = 'rtl'; - testComponent.invert = true; - fixture.detectChanges(); - - dispatchKeydownEvent(sliderNativeElement, RIGHT_ARROW); - fixture.detectChanges(); - - expect(sliderInstance.value).toBe(1); - }); - - it('should decrement inverted RTL slider by 1 on left arrow pressed', () => { - testComponent.dir = 'rtl'; - testComponent.invert = true; - sliderInstance.value = 100; - fixture.detectChanges(); - - dispatchKeydownEvent(sliderNativeElement, LEFT_ARROW); - fixture.detectChanges(); - - expect(sliderInstance.value).toBe(99); - }); }); }); From fccfd37c1cf81e2818e1622504cb55bc1e6aa0a0 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 11 Nov 2016 09:26:05 -0800 Subject: [PATCH 14/28] swap left/right arrow behavior in rtl --- src/lib/slider/slider.spec.ts | 68 ++++++++++++++++++++++++++++++++++- src/lib/slider/slider.ts | 31 +++++++++------- 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/src/lib/slider/slider.spec.ts b/src/lib/slider/slider.spec.ts index 494e614565d7..b22ebc66c76d 100644 --- a/src/lib/slider/slider.spec.ts +++ b/src/lib/slider/slider.spec.ts @@ -12,7 +12,8 @@ import { PAGE_DOWN, PAGE_UP, END, - HOME, LEFT_ARROW + HOME, + LEFT_ARROW } from '../core/keyboard/keycodes'; @@ -890,6 +891,71 @@ describe('MdSlider', () => { expect(sliderInstance.value).toBe(30); }); + + it('should decrement inverted slider by 1 on right arrow pressed', () => { + testComponent.invert = true; + sliderInstance.value = 100; + fixture.detectChanges(); + + dispatchKeydownEvent(sliderNativeElement, RIGHT_ARROW); + fixture.detectChanges(); + + expect(sliderInstance.value).toBe(99); + }); + + it('should increment inverted slider by 1 on left arrow pressed', () => { + testComponent.invert = true; + fixture.detectChanges(); + + dispatchKeydownEvent(sliderNativeElement, LEFT_ARROW); + fixture.detectChanges(); + + expect(sliderInstance.value).toBe(1); + }); + + it('should decrement RTL slider by 1 on right arrow pressed', () => { + testComponent.dir = 'rtl'; + sliderInstance.value = 100; + fixture.detectChanges(); + + dispatchKeydownEvent(sliderNativeElement, RIGHT_ARROW); + fixture.detectChanges(); + + expect(sliderInstance.value).toBe(99); + }); + + it('should increment RTL slider by 1 on left arrow pressed', () => { + testComponent.dir = 'rtl'; + fixture.detectChanges(); + + dispatchKeydownEvent(sliderNativeElement, LEFT_ARROW); + fixture.detectChanges(); + + expect(sliderInstance.value).toBe(1); + }); + + it('should increment inverted RTL slider by 1 on right arrow pressed', () => { + testComponent.dir = 'rtl'; + testComponent.invert = true; + fixture.detectChanges(); + + dispatchKeydownEvent(sliderNativeElement, RIGHT_ARROW); + fixture.detectChanges(); + + expect(sliderInstance.value).toBe(1); + }); + + it('should decrement inverted RTL slider by 1 on left arrow pressed', () => { + testComponent.dir = 'rtl'; + testComponent.invert = true; + sliderInstance.value = 100; + fixture.detectChanges(); + + dispatchKeydownEvent(sliderNativeElement, LEFT_ARROW); + fixture.detectChanges(); + + expect(sliderInstance.value).toBe(99); + }); }); }); diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index a7023a12b64c..5770fb7711b9 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -1,14 +1,14 @@ import { - NgModule, - ModuleWithProviders, - Component, - ElementRef, - Input, - Output, - ViewEncapsulation, - forwardRef, - EventEmitter, - Optional, + NgModule, + ModuleWithProviders, + Component, + ElementRef, + Input, + Output, + ViewEncapsulation, + forwardRef, + EventEmitter, + Optional } from '@angular/core'; import {NG_VALUE_ACCESSOR, ControlValueAccessor, FormsModule} from '@angular/forms'; import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; @@ -309,13 +309,13 @@ export class MdSlider implements ControlValueAccessor { this.value = this.min; break; case LEFT_ARROW: - this._increment(-1); + this._increment(this._isLeftMin() ? -1 : 1); break; case UP_ARROW: this._increment(1); break; case RIGHT_ARROW: - this._increment(1); + this._increment(this._isLeftMin() ? 1 : -1); break; case DOWN_ARROW: this._increment(-1); @@ -329,6 +329,11 @@ export class MdSlider implements ControlValueAccessor { event.preventDefault(); } + /** Whether the left side of the slider is the minimum value. */ + private _isLeftMin() { + return (this.direction == 'rtl') == this.invert + } + /** Increments the slider by the given number of steps (negative number decrements). */ private _increment(numSteps: number) { this.value = this._clamp(this.value + this.step * numSteps, this.min, this.max); @@ -347,7 +352,7 @@ export class MdSlider implements ControlValueAccessor { // The exact value is calculated from the event and used to find the closest snap value. let percent = this._clamp((pos - offset) / size); - if ((this.direction == 'rtl') != this.invert) { + if (!this._isLeftMin()) { percent = 1 - percent; } let exactValue = this._calculateValue(percent); From 269743f5624bde631810d602e8bafb89bb81c320 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 11 Nov 2016 10:20:33 -0800 Subject: [PATCH 15/28] fixed lint issues --- src/lib/slider/slider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 5770fb7711b9..e44638151165 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -331,7 +331,7 @@ export class MdSlider implements ControlValueAccessor { /** Whether the left side of the slider is the minimum value. */ private _isLeftMin() { - return (this.direction == 'rtl') == this.invert + return (this.direction == 'rtl') == this.invert; } /** Increments the slider by the given number of steps (negative number decrements). */ From 9e7c30baaee3a388eaf3492f3d62d851f880d3e1 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 10 Nov 2016 13:28:11 -0800 Subject: [PATCH 16/28] started work --- src/demo-app/slider/slider-demo.html | 6 ++ src/lib/slider/slider.scss | 135 +++++++++++++++++---------- src/lib/slider/slider.ts | 10 +- 3 files changed, 101 insertions(+), 50 deletions(-) diff --git a/src/demo-app/slider/slider-demo.html b/src/demo-app/slider/slider-demo.html index 4b6c65478563..923a998d5589 100644 --- a/src/demo-app/slider/slider-demo.html +++ b/src/demo-app/slider/slider-demo.html @@ -37,6 +37,12 @@

Slider with two-way binding

Inverted slider

+

Vertical slider

+ + +

Inverted vertical slider

+ + diff --git a/src/lib/slider/slider.scss b/src/lib/slider/slider.scss index 2b2c3f36f860..1a0be2931fed 100644 --- a/src/lib/slider/slider.scss +++ b/src/lib/slider/slider.scss @@ -42,30 +42,12 @@ md-slider { transition: box-shadow $swift-ease-out-duration $swift-ease-out-timing-function; } -.md-slider-has-ticks.md-slider-active .md-slider-track, -.md-slider-has-ticks:hover .md-slider-track { - box-shadow: inset (-2 * $md-slider-tick-size) 0 0 (-$md-slider-tick-size) $md-slider-tick-color; -} - -[dir='rtl'] .md-slider-has-ticks.md-slider-active .md-slider-track, -[dir='rtl'] .md-slider-has-ticks:hover .md-slider-track { - box-shadow: inset (2 * $md-slider-tick-size) 0 0 (-$md-slider-tick-size) $md-slider-tick-color; -} - -.md-slider-inverted .md-slider-track { - flex-direction: row-reverse; -} - .md-slider-track-fill { flex: 0 0 50%; height: $md-slider-track-thickness; transition: flex-basis $swift-ease-out-duration $swift-ease-out-timing-function; } -.md-slider-sliding .md-slider-track-fill { - transition: none; -} - .md-slider-ticks-container { position: absolute; left: 0; @@ -75,11 +57,6 @@ md-slider { overflow: hidden; } -[dir='rtl'] .md-slider-ticks-container { - // translateZ(0) prevents chrome bug where overflow: hidden; doesn't work. - transform: translateZ(0) rotate(180deg); -} - .md-slider-ticks { background: repeating-linear-gradient(to right, $md-slider-tick-color, $md-slider-tick-color $md-slider-tick-size, transparent 0, transparent) repeat; @@ -93,11 +70,6 @@ md-slider { transition: opacity $swift-ease-out-duration $swift-ease-out-timing-function; } -.md-slider-has-ticks.md-slider-active .md-slider-ticks, -.md-slider-has-ticks:hover .md-slider-ticks { - opacity: 1; -} - .md-slider-thumb-container { flex: 0 0 auto; position: relative; @@ -117,16 +89,8 @@ md-slider { transition: transform $swift-ease-out-duration $swift-ease-out-timing-function; } -.md-slider-active .md-slider-thumb { - transform: scale($md-slider-thumb-focus-scale); -} - -.md-slider-active.md-slider-thumb-label-showing .md-slider-thumb { - transform: scale(0); -} - .md-slider-thumb-label { - display: flex; + display: none; align-items: center; justify-content: center; position: absolute; @@ -136,20 +100,11 @@ md-slider { height: $md-slider-thumb-label-size; border-radius: 50%; transform: translateY($md-slider-thumb-label-size / 2 + $md-slider-thumb-arrow-gap) - scale(0.4) rotate(45deg); + scale(0.4) rotate(45deg); transition: 300ms $swift-ease-in-out-timing-function; transition-property: transform, border-radius; } -.md-slider-active .md-slider-thumb-label { - border-radius: 50% 50% 0; - transform: rotate(45deg); -} - -md-slider:not(.md-slider-thumb-label-showing) .md-slider-thumb-label { - display: none; -} - .md-slider-thumb-label-text { z-index: 1; font-size: 12px; @@ -159,6 +114,88 @@ md-slider:not(.md-slider-thumb-label-showing) .md-slider-thumb-label { transition: opacity 300ms $swift-ease-in-out-timing-function; } -.md-slider-active .md-slider-thumb-label-text { - opacity: 1; + +// Slider sliding state. +.md-slider-sliding { + .md-slider-track-fill { + transition: none; + } +} + + +// Slider with ticks. +.md-slider-has-ticks { + &.md-slider-active, + &:hover { + .md-slider-track { + box-shadow: + inset (-2 * $md-slider-tick-size) 0 0 (-$md-slider-tick-size) $md-slider-tick-color; + } + + .md-slider-ticks { + opacity: 1; + } + } +} + + +// Slider with thumb label. +.md-slider-thumb-label-showing { + .md-slider-thumb-label { + display: flex; + } +} + + +// Inverted slider. +.md-slider-inverted { + .md-slider-track { + flex-direction: row-reverse; + } +} + + +// Active slider. +.md-slider-active { + .md-slider-thumb { + transform: scale($md-slider-thumb-focus-scale); + } + + &.md-slider-thumb-label-showing .md-slider-thumb { + transform: scale(0); + } + + .md-slider-thumb-label { + border-radius: 50% 50% 0; + transform: rotate(45deg); + } + + .md-slider-thumb-label-text { + opacity: 1; + } +} + + +// Vertical slider. +.md-slider-vertical { + +} + + +// Slider in RTL languages. +[dir='rtl'] { + .md-slider-ticks-container { + // translateZ(0) prevents chrome bug where overflow: hidden; doesn't work. + transform: translateZ(0) rotate(180deg); + } + + .md-slider-has-ticks { + &.md-slider-active, + &:hover { + .md-slider-track { + box-shadow: + inset (2 * $md-slider-tick-size) 0 0 (-$md-slider-tick-size) $md-slider-tick-color; + } + } + } } diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index e44638151165..44235affa89c 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -70,9 +70,11 @@ export class MdSliderChange { '[class.md-slider-active]': '_isActive', '[class.md-slider-disabled]': 'disabled', '[class.md-slider-has-ticks]': 'tickInterval', + '[class.md-slider-horizontal]': '!vertical', '[class.md-slider-inverted]': 'invert', '[class.md-slider-sliding]': '_isSliding', '[class.md-slider-thumb-label-showing]': 'thumbLabel', + '[class.md-slider-vertical]': 'vertical', }, templateUrl: 'slider.html', styleUrls: ['slider.css'], @@ -196,9 +198,15 @@ export class MdSlider implements ControlValueAccessor { /** Whether the slider is inverted. */ @Input() get invert() { return this._invert; } - set invert(value: boolean) { this._invert = coerceBooleanProperty(value); } + set invert(value: any) { this._invert = coerceBooleanProperty(value); } private _invert = false; + /** Whether the slider is vertical. */ + @Input() + get vertical() { return this._vertical; } + set vertical(value: any) { this._vertical = coerceBooleanProperty(value); } + private _vertical = false; + /** CSS styles for the track fill element. */ get trackFillStyles(): { [key: string]: string } { return { From f93260a2dffcd279ac42508ff265fbe6a6738fc7 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 11 Nov 2016 14:28:49 -0800 Subject: [PATCH 17/28] sorta working --- src/lib/slider/slider.html | 2 +- src/lib/slider/slider.scss | 83 +++++++++++++++++++++++++++++--------- src/lib/slider/slider.ts | 26 ++++++++---- 3 files changed, 82 insertions(+), 29 deletions(-) diff --git a/src/lib/slider/slider.html b/src/lib/slider/slider.html index e39f29ed400a..2d0f628a4d8a 100644 --- a/src/lib/slider/slider.html +++ b/src/lib/slider/slider.html @@ -3,7 +3,7 @@
-
+
{{value}} diff --git a/src/lib/slider/slider.scss b/src/lib/slider/slider.scss index 1a0be2931fed..9f4d325d7eb2 100644 --- a/src/lib/slider/slider.scss +++ b/src/lib/slider/slider.scss @@ -22,30 +22,24 @@ $md-slider-tick-size: 2px !default; md-slider { - display: inline-block; + display: inline-flex; + align-items: center; box-sizing: border-box; - position: relative; - height: $md-slider-thickness; - min-width: $md-slider-min-size; padding: $md-slider-padding; outline: none; vertical-align: middle; } .md-slider-track { - display: flex; - flex-grow: 1; - align-items: center; + flex: 1; position: relative; - top: ($md-slider-thickness - $md-slider-track-thickness) / 2 - $md-slider-padding; - height: $md-slider-track-thickness; transition: box-shadow $swift-ease-out-duration $swift-ease-out-timing-function; } .md-slider-track-fill { - flex: 0 0 50%; - height: $md-slider-track-thickness; - transition: flex-basis $swift-ease-out-duration $swift-ease-out-timing-function; + position: absolute; + transform-origin: 0 0; + transition: transform $swift-ease-out-duration $swift-ease-out-timing-function; } .md-slider-ticks-container { @@ -71,10 +65,8 @@ md-slider { } .md-slider-thumb-container { - flex: 0 0 auto; - position: relative; - width: 0; - height: 0; + position: absolute; + transition: transform $swift-ease-out-duration $swift-ease-out-timing-function; } .md-slider-thumb { @@ -84,7 +76,6 @@ md-slider { width: $md-slider-thumb-size; height: $md-slider-thumb-size; border-radius: 50%; - transform-origin: 50% 50%; transform: scale($md-slider-thumb-default-scale); transition: transform $swift-ease-out-duration $swift-ease-out-timing-function; } @@ -117,7 +108,8 @@ md-slider { // Slider sliding state. .md-slider-sliding { - .md-slider-track-fill { + .md-slider-track-fill, + .md-slider-thumb-container { transition: none; } } @@ -149,8 +141,8 @@ md-slider { // Inverted slider. .md-slider-inverted { - .md-slider-track { - flex-direction: row-reverse; + .md-slider-track-fill { + transform-origin: 100% 100%; } } @@ -176,9 +168,50 @@ md-slider { } +// Horizontal slider. +.md-slider-horizontal { + height: $md-slider-thickness; + min-width: $md-slider-min-size; + + .md-slider-track { + height: $md-slider-track-thickness; + } + + .md-slider-track-fill { + height: $md-slider-track-thickness; + width: 100%; + transform: scaleX(0); + } + + .md-slider-thumb-container { + width: 100%; + height: 0; + top: 50%; + } +} + + // Vertical slider. .md-slider-vertical { + flex-direction: column; + width: $md-slider-thickness; + min-height: $md-slider-min-size; + + .md-slider-track { + width: $md-slider-track-thickness; + } + .md-slider-track-fill { + height: 100%; + width: $md-slider-track-thickness; + transform: scaleY(0); + } + + .md-slider-thumb-container { + height: 100%; + width: 0; + left: 50%; + } } @@ -198,4 +231,14 @@ md-slider { } } } + + .md-slider-track-fill { + transform-origin: 100% 100%; + } + + .md-slider-inverted { + .md-slider-track-fill { + transform-origin: 0 0; + } + } } diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 44235affa89c..e918bb710f0f 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -209,8 +209,9 @@ export class MdSlider implements ControlValueAccessor { /** CSS styles for the track fill element. */ get trackFillStyles(): { [key: string]: string } { + let axis = this.vertical ? 'Y' : 'X'; return { - 'flexBasis': `${this.percent * 100}%` + 'transform': `scale${axis}(${this.percent})` }; } @@ -234,6 +235,14 @@ export class MdSlider implements ControlValueAccessor { return styles; } + get thumbContainerStyles(): { [key: string]: string } { + let axis = this.vertical ? 'Y' : 'X'; + let offset = (this._isLeftMin() ? this.percent : 1 - this.percent) * 100; + return { + 'transform': `translate${axis}(${offset}%)` + }; + } + /** The language direction for this slider element. */ get direction() { return (this._dir && this._dir.value == 'rtl') ? 'rtl' : 'ltr'; @@ -264,7 +273,7 @@ export class MdSlider implements ControlValueAccessor { this._isActive = true; this._isSliding = false; this._renderer.addFocus(); - this._updateValueFromPosition(event.clientX); + this._updateValueFromPosition({x: event.clientX, y: event.clientY}); this._emitValueIfChanged(); } @@ -275,7 +284,7 @@ export class MdSlider implements ControlValueAccessor { // Prevent the slide from selecting anything else. event.preventDefault(); - this._updateValueFromPosition(event.center.x); + this._updateValueFromPosition({x: event.center.x, y: event.center.y}); } _onSlideStart(event: HammerInput) { @@ -287,7 +296,7 @@ export class MdSlider implements ControlValueAccessor { this._isSliding = true; this._isActive = true; this._renderer.addFocus(); - this._updateValueFromPosition(event.center.x); + this._updateValueFromPosition({x: event.center.x, y: event.center.y}); } _onSlideEnd() { @@ -350,16 +359,17 @@ export class MdSlider implements ControlValueAccessor { /** * Calculate the new value from the new physical location. The value will always be snapped. */ - private _updateValueFromPosition(pos: number) { + private _updateValueFromPosition(pos: {x: number, y: number}) { if (!this._sliderDimensions) { return; } - let offset = this._sliderDimensions.left; - let size = this._sliderDimensions.width; + let offset = this.vertical ? this._sliderDimensions.top : this._sliderDimensions.left; + let size = this.vertical ? this._sliderDimensions.height : this._sliderDimensions.width; + let posComponent = this.vertical ? pos.y : pos.x; // The exact value is calculated from the event and used to find the closest snap value. - let percent = this._clamp((pos - offset) / size); + let percent = this._clamp((posComponent - offset) / size); if (!this._isLeftMin()) { percent = 1 - percent; } From 1e6d240ce88fea12917210b9aeeca2a110a2e4f7 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 11 Nov 2016 16:35:16 -0800 Subject: [PATCH 18/28] works (except end tick is sometimes hidden). all the tests are probably broken T-T --- src/lib/slider/slider.scss | 106 ++++++++++++++++++++++++++++--------- src/lib/slider/slider.ts | 35 ++++++++---- 2 files changed, 106 insertions(+), 35 deletions(-) diff --git a/src/lib/slider/slider.scss b/src/lib/slider/slider.scss index 9f4d325d7eb2..6a76350b873e 100644 --- a/src/lib/slider/slider.scss +++ b/src/lib/slider/slider.scss @@ -46,20 +46,10 @@ md-slider { position: absolute; left: 0; top: 0; - height: $md-slider-track-thickness; - width: 100%; overflow: hidden; } .md-slider-ticks { - background: repeating-linear-gradient(to right, $md-slider-tick-color, - $md-slider-tick-color $md-slider-tick-size, transparent 0, transparent) repeat; - // Firefox doesn't draw the gradient correctly with 'to right' - // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1314319). - background: -moz-repeating-linear-gradient(0.0001deg, $md-slider-tick-color, - $md-slider-tick-color $md-slider-tick-size, transparent 0, transparent) repeat; - height: $md-slider-track-thickness; - width: 100%; opacity: 0; transition: opacity $swift-ease-out-duration $swift-ease-out-timing-function; } @@ -71,8 +61,8 @@ md-slider { .md-slider-thumb { position: absolute; - left: -$md-slider-thumb-size / 2; - top: -$md-slider-thumb-size / 2; + right: -$md-slider-thumb-size / 2; + bottom: -$md-slider-thumb-size / 2; width: $md-slider-thumb-size; height: $md-slider-thumb-size; border-radius: 50%; @@ -85,13 +75,9 @@ md-slider { align-items: center; justify-content: center; position: absolute; - left: -$md-slider-thumb-label-size / 2; - top: -($md-slider-thumb-label-size + $md-slider-thumb-arrow-gap); width: $md-slider-thumb-label-size; height: $md-slider-thumb-label-size; border-radius: 50%; - transform: translateY($md-slider-thumb-label-size / 2 + $md-slider-thumb-arrow-gap) - scale(0.4) rotate(45deg); transition: 300ms $swift-ease-in-out-timing-function; transition-property: transform, border-radius; } @@ -101,7 +87,6 @@ md-slider { font-size: 12px; font-weight: bold; opacity: 0; - transform: rotate(-45deg); transition: opacity 300ms $swift-ease-in-out-timing-function; } @@ -119,11 +104,6 @@ md-slider { .md-slider-has-ticks { &.md-slider-active, &:hover { - .md-slider-track { - box-shadow: - inset (-2 * $md-slider-tick-size) 0 0 (-$md-slider-tick-size) $md-slider-tick-color; - } - .md-slider-ticks { opacity: 1; } @@ -159,7 +139,6 @@ md-slider { .md-slider-thumb-label { border-radius: 50% 50% 0; - transform: rotate(45deg); } .md-slider-thumb-label-text { @@ -183,11 +162,52 @@ md-slider { transform: scaleX(0); } + .md-slider-ticks-container { + height: $md-slider-track-thickness; + width: 100%; + } + + .md-slider-ticks { + background: repeating-linear-gradient(to right, $md-slider-tick-color, + $md-slider-tick-color $md-slider-tick-size, transparent 0, transparent) repeat; + // Firefox doesn't draw the gradient correctly with 'to right' + // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1314319). + background: -moz-repeating-linear-gradient(0.0001deg, $md-slider-tick-color, + $md-slider-tick-color $md-slider-tick-size, transparent 0, transparent) repeat; + height: $md-slider-track-thickness; + width: 100%; + } + .md-slider-thumb-container { width: 100%; height: 0; top: 50%; } + + .md-slider-thumb-label { + right: -$md-slider-thumb-label-size / 2; + top: -($md-slider-thumb-label-size + $md-slider-thumb-arrow-gap); + transform: translateY($md-slider-thumb-label-size / 2 + $md-slider-thumb-arrow-gap) scale(0.4) rotate(45deg); + } + + .md-slider-thumb-label-text { + transform: rotate(-45deg); + } + + &.md-slider-active { + .md-slider-thumb-label { + transform: rotate(45deg); + } + } + + &.md-slider-has-ticks { + &.md-slider-active, + &:hover { + .md-slider-track { + box-shadow: inset (-2 * $md-slider-tick-size) 0 0 (-$md-slider-tick-size) $md-slider-tick-color; + } + } + } } @@ -207,11 +227,49 @@ md-slider { transform: scaleY(0); } + .md-slider-ticks-container { + width: $md-slider-track-thickness; + height: 100%; + } + + .md-slider-ticks { + background: repeating-linear-gradient(to bottom, $md-slider-tick-color, + $md-slider-tick-color $md-slider-tick-size, transparent 0, transparent) repeat; + width: $md-slider-track-thickness; + height: 100%; + } + .md-slider-thumb-container { height: 100%; width: 0; left: 50%; } + + .md-slider-thumb-label { + bottom: -$md-slider-thumb-label-size / 2; + left: -($md-slider-thumb-label-size + $md-slider-thumb-arrow-gap); + transform: translateX($md-slider-thumb-label-size / 2 + $md-slider-thumb-arrow-gap) + scale(0.4) rotate(-45deg); + } + + .md-slider-thumb-label-text { + transform: rotate(45deg); + } + + &.md-slider-active { + .md-slider-thumb-label { + transform: rotate(-45deg); + } + } + + &.md-slider-has-ticks { + &.md-slider-active, + &:hover { + .md-slider-track { + box-shadow: inset 0 (-2 * $md-slider-tick-size) 0 (-$md-slider-tick-size) $md-slider-tick-color; + } + } + } } @@ -222,7 +280,7 @@ md-slider { transform: translateZ(0) rotate(180deg); } - .md-slider-has-ticks { + .md-slider-horizontal.md-slider-has-ticks { &.md-slider-active, &:hover { .md-slider-track { diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index e918bb710f0f..c010932a8fcf 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -217,29 +217,41 @@ export class MdSlider implements ControlValueAccessor { /** CSS styles for the ticks container element. */ get ticksContainerStyles(): { [key: string]: string } { - return { - 'marginLeft': `${this.direction == 'rtl' ? '' : '-'}${this.tickIntervalPercent / 2 * 100}%` - }; + let sign = this.direction == 'rtl' ? '' : '-'; + let offset = this.tickIntervalPercent / 2 * 100; + if (this.vertical) { + return { + 'transform': `translate3d(0, -${offset}%, 0)` + }; + } else { + return { + 'marginLeft': `${sign}${offset}%` + }; + } } /** CSS styles for the ticks element. */ get ticksStyles() { + let tickSize = this.tickIntervalPercent * 100; + let backgroundSize = this.vertical ? `2px ${tickSize}%` : `${tickSize}% 2px`; + let marginSide = this.direction == 'rtl' ? 'Right' : 'Left'; + let marginSign = this.direction == 'rtl' ? '-' : ''; let styles: { [key: string]: string } = { - 'backgroundSize': `${this.tickIntervalPercent * 100}% 2px` + 'backgroundSize': backgroundSize, }; - if (this.direction == 'rtl') { - styles['marginRight'] = `-${this.tickIntervalPercent / 2 * 100}%`; + if (this.vertical) { + styles['transform'] = `translate3d(0, ${tickSize / 2}%, 0)`; } else { - styles['marginLeft'] = `${this.tickIntervalPercent / 2 * 100}%`; + styles[`margin${marginSide}`] = `${marginSign}${tickSize / 2}%`; } return styles; } get thumbContainerStyles(): { [key: string]: string } { let axis = this.vertical ? 'Y' : 'X'; - let offset = (this._isLeftMin() ? this.percent : 1 - this.percent) * 100; + let offset = (this._isLeftMin() ? 1 - this.percent : this.percent) * 100; return { - 'transform': `translate${axis}(${offset}%)` + 'transform': `translate${axis}(-${offset}%)` }; } @@ -403,10 +415,11 @@ export class MdSlider implements ControlValueAccessor { } if (this.tickInterval == 'auto') { - let pixelsPerStep = this._sliderDimensions.width * this.step / (this.max - this.min); + let trackSize = this.vertical ? this._sliderDimensions.height : this._sliderDimensions.width; + let pixelsPerStep = trackSize * this.step / (this.max - this.min); let stepsPerTick = Math.ceil(MIN_AUTO_TICK_SEPARATION / pixelsPerStep); let pixelsPerTick = stepsPerTick * this.step; - this._tickIntervalPercent = pixelsPerTick / (this._sliderDimensions.width); + this._tickIntervalPercent = pixelsPerTick / trackSize; } else { this._tickIntervalPercent = this.tickInterval * this.step / (this.max - this.min); } From 44d1446244233c9da0b30399efbc170904d53cc9 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 15 Nov 2016 10:14:07 -0800 Subject: [PATCH 19/28] fix tick glitches --- src/demo-app/slider/slider-demo.html | 2 +- src/lib/slider/slider.scss | 7 ++++++- src/lib/slider/slider.ts | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/demo-app/slider/slider-demo.html b/src/demo-app/slider/slider-demo.html index 923a998d5589..aba8619d3c68 100644 --- a/src/demo-app/slider/slider-demo.html +++ b/src/demo-app/slider/slider-demo.html @@ -35,7 +35,7 @@

Slider with two-way binding

Inverted slider

- +

Vertical slider

diff --git a/src/lib/slider/slider.scss b/src/lib/slider/slider.scss index 6a76350b873e..007aeae47c25 100644 --- a/src/lib/slider/slider.scss +++ b/src/lib/slider/slider.scss @@ -33,6 +33,10 @@ md-slider { .md-slider-track { flex: 1; position: relative; + // Without translateZ Chrome & Firefox display some strange rendering behavior. Firefox doesn't + // immediately redraw the track when the slider thumb moves, and Chrome shows ticks that fall + // outside of the ticks-container with overflow: hidden. + transform: translateZ(0); transition: box-shadow $swift-ease-out-duration $swift-ease-out-timing-function; } @@ -276,7 +280,8 @@ md-slider { // Slider in RTL languages. [dir='rtl'] { .md-slider-ticks-container { - // translateZ(0) prevents chrome bug where overflow: hidden; doesn't work. + // Without translateZ Chrome has a rendering bug where the ticks jiggle slightly when the slider + // thumb is moving. transform: translateZ(0) rotate(180deg); } diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index c010932a8fcf..0f3717ee45b3 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -221,7 +221,7 @@ export class MdSlider implements ControlValueAccessor { let offset = this.tickIntervalPercent / 2 * 100; if (this.vertical) { return { - 'transform': `translate3d(0, -${offset}%, 0)` + 'transform': `translateY(-${offset}%)` }; } else { return { @@ -240,7 +240,7 @@ export class MdSlider implements ControlValueAccessor { 'backgroundSize': backgroundSize, }; if (this.vertical) { - styles['transform'] = `translate3d(0, ${tickSize / 2}%, 0)`; + styles['transform'] = `translateY(${tickSize / 2}%)`; } else { styles[`margin${marginSide}`] = `${marginSign}${tickSize / 2}%`; } From feba7dd07776a51c527a5fe636282c3011109096 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 15 Nov 2016 10:41:14 -0800 Subject: [PATCH 20/28] all translate, no margin for tick placement --- src/lib/slider/slider.scss | 10 ---------- src/lib/slider/slider.ts | 32 ++++++++++++-------------------- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/src/lib/slider/slider.scss b/src/lib/slider/slider.scss index 007aeae47c25..fe9804a92653 100644 --- a/src/lib/slider/slider.scss +++ b/src/lib/slider/slider.scss @@ -33,10 +33,6 @@ md-slider { .md-slider-track { flex: 1; position: relative; - // Without translateZ Chrome & Firefox display some strange rendering behavior. Firefox doesn't - // immediately redraw the track when the slider thumb moves, and Chrome shows ticks that fall - // outside of the ticks-container with overflow: hidden. - transform: translateZ(0); transition: box-shadow $swift-ease-out-duration $swift-ease-out-timing-function; } @@ -279,12 +275,6 @@ md-slider { // Slider in RTL languages. [dir='rtl'] { - .md-slider-ticks-container { - // Without translateZ Chrome has a rendering bug where the ticks jiggle slightly when the slider - // thumb is moving. - transform: translateZ(0) rotate(180deg); - } - .md-slider-horizontal.md-slider-has-ticks { &.md-slider-active, &:hover { diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 0f3717ee45b3..4334ac26945d 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -217,34 +217,26 @@ export class MdSlider implements ControlValueAccessor { /** CSS styles for the ticks container element. */ get ticksContainerStyles(): { [key: string]: string } { - let sign = this.direction == 'rtl' ? '' : '-'; + let axis = this.vertical ? 'Y' : 'X'; + let sign = !this.vertical && this.direction == 'rtl' ? '' : '-'; let offset = this.tickIntervalPercent / 2 * 100; - if (this.vertical) { - return { - 'transform': `translateY(-${offset}%)` - }; - } else { - return { - 'marginLeft': `${sign}${offset}%` - }; - } + return { + 'transform': `translate${axis}(${sign}${offset}%)` + }; } /** CSS styles for the ticks element. */ - get ticksStyles() { + get ticksStyles(): { [key: string]: string } { let tickSize = this.tickIntervalPercent * 100; let backgroundSize = this.vertical ? `2px ${tickSize}%` : `${tickSize}% 2px`; - let marginSide = this.direction == 'rtl' ? 'Right' : 'Left'; - let marginSign = this.direction == 'rtl' ? '-' : ''; - let styles: { [key: string]: string } = { + let axis = this.vertical ? 'Y' : 'X'; + let sign = !this.vertical && this.direction == 'rtl' ? '-' : ''; + let rotate = !this.vertical && this.direction == 'rtl' ? ' rotate(180deg)' : ''; + return { 'backgroundSize': backgroundSize, + // Without translateZ ticks sometimes jitter as the slider moves on Chrome & Firefox. + 'transform': `translateZ(0) translate${axis}(${sign}${tickSize / 2}%)${rotate}` }; - if (this.vertical) { - styles['transform'] = `translateY(${tickSize / 2}%)`; - } else { - styles[`margin${marginSide}`] = `${marginSign}${tickSize / 2}%`; - } - return styles; } get thumbContainerStyles(): { [key: string]: string } { From e876893add9241be20b3d1b4ed03b113cab9b191 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 15 Nov 2016 13:53:49 -0800 Subject: [PATCH 21/28] make sure the last tick appears on top of fill --- src/lib/slider/slider.scss | 58 ++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/lib/slider/slider.scss b/src/lib/slider/slider.scss index fe9804a92653..9cc1eb2176b3 100644 --- a/src/lib/slider/slider.scss +++ b/src/lib/slider/slider.scss @@ -33,7 +33,6 @@ md-slider { .md-slider-track { flex: 1; position: relative; - transition: box-shadow $swift-ease-out-duration $swift-ease-out-timing-function; } .md-slider-track-fill { @@ -56,6 +55,7 @@ md-slider { .md-slider-thumb-container { position: absolute; + z-index: 1; transition: transform $swift-ease-out-duration $swift-ease-out-timing-function; } @@ -102,8 +102,20 @@ md-slider { // Slider with ticks. .md-slider-has-ticks { + .md-slider-track::after { + content: ''; + position: absolute; + border: 0 solid $md-slider-tick-color; + opacity: 0; + transition: opacity 300ms $swift-ease-in-out-timing-function; + } + &.md-slider-active, &:hover { + .md-slider-track::after { + opacity: 1; + } + .md-slider-ticks { opacity: 1; } @@ -156,6 +168,12 @@ md-slider { height: $md-slider-track-thickness; } + .md-slider-track::after { + height: $md-slider-track-thickness; + border-left-width: $md-slider-tick-size; + right: 0; + } + .md-slider-track-fill { height: $md-slider-track-thickness; width: 100%; @@ -187,7 +205,8 @@ md-slider { .md-slider-thumb-label { right: -$md-slider-thumb-label-size / 2; top: -($md-slider-thumb-label-size + $md-slider-thumb-arrow-gap); - transform: translateY($md-slider-thumb-label-size / 2 + $md-slider-thumb-arrow-gap) scale(0.4) rotate(45deg); + transform: translateY($md-slider-thumb-label-size / 2 + $md-slider-thumb-arrow-gap) scale(0.4) + rotate(45deg); } .md-slider-thumb-label-text { @@ -199,15 +218,6 @@ md-slider { transform: rotate(45deg); } } - - &.md-slider-has-ticks { - &.md-slider-active, - &:hover { - .md-slider-track { - box-shadow: inset (-2 * $md-slider-tick-size) 0 0 (-$md-slider-tick-size) $md-slider-tick-color; - } - } - } } @@ -221,6 +231,12 @@ md-slider { width: $md-slider-track-thickness; } + .md-slider-track::after { + width: $md-slider-track-thickness; + border-top-width: $md-slider-tick-size; + bottom: 0; + } + .md-slider-track-fill { height: 100%; width: $md-slider-track-thickness; @@ -261,28 +277,14 @@ md-slider { transform: rotate(-45deg); } } - - &.md-slider-has-ticks { - &.md-slider-active, - &:hover { - .md-slider-track { - box-shadow: inset 0 (-2 * $md-slider-tick-size) 0 (-$md-slider-tick-size) $md-slider-tick-color; - } - } - } } // Slider in RTL languages. [dir='rtl'] { - .md-slider-horizontal.md-slider-has-ticks { - &.md-slider-active, - &:hover { - .md-slider-track { - box-shadow: - inset (2 * $md-slider-tick-size) 0 0 (-$md-slider-tick-size) $md-slider-tick-color; - } - } + .md-slider-track::after { + left: 0; + right: auto; } .md-slider-track-fill { From 2cbeb8517441f6b19c62f36606c08beabed53a21 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 15 Nov 2016 14:42:04 -0800 Subject: [PATCH 22/28] fix tests --- src/lib/slider/slider.spec.ts | 44 +++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/lib/slider/slider.spec.ts b/src/lib/slider/slider.spec.ts index b22ebc66c76d..a72f9560270e 100644 --- a/src/lib/slider/slider.spec.ts +++ b/src/lib/slider/slider.spec.ts @@ -109,21 +109,21 @@ describe('MdSlider', () => { }); it('should update the track fill on click', () => { - expect(trackFillElement.style.flexBasis).toBe('0%'); + expect(trackFillElement.style.transform).toBe('scaleX(0)'); dispatchClickEventSequence(sliderNativeElement, 0.39); fixture.detectChanges(); - expect(trackFillElement.style.flexBasis).toBe('39%'); + expect(trackFillElement.style.transform).toBe('scaleX(0.39)'); }); it('should update the track fill on slide', () => { - expect(trackFillElement.style.flexBasis).toBe('0%'); + expect(trackFillElement.style.transform).toBe('scaleX(0)'); dispatchSlideEventSequence(sliderNativeElement, 0, 0.86, gestureConfig); fixture.detectChanges(); - expect(trackFillElement.style.flexBasis).toBe('86%'); + expect(trackFillElement.style.transform).toBe('scaleX(0.86)'); }); it('should add the md-slider-active class on click', () => { @@ -276,7 +276,7 @@ describe('MdSlider', () => { fixture.detectChanges(); // The closest snap is halfway on the slider. - expect(trackFillElement.style.flexBasis).toBe('50%'); + expect(trackFillElement.style.transform).toBe('scaleX(0.5)'); }); it('should snap the fill to the nearest value on slide', () => { @@ -284,7 +284,7 @@ describe('MdSlider', () => { fixture.detectChanges(); // The closest snap is at the halfway point on the slider. - expect(trackFillElement.style.flexBasis).toBe('50%'); + expect(trackFillElement.style.transform).toBe('scaleX(0.5)'); }); it('should adjust fill and ticks on mouse enter when min changes', () => { @@ -294,11 +294,11 @@ describe('MdSlider', () => { dispatchMouseenterEvent(sliderNativeElement); fixture.detectChanges(); - expect(trackFillElement.style.flexBasis).toBe('75%'); + expect(trackFillElement.style.transform).toBe('scaleX(0.75)'); expect(ticksElement.style.backgroundSize).toBe('75% 2px'); // Make sure it cuts off the last half tick interval. - expect(ticksElement.style.marginLeft).toBe('37.5%'); - expect(ticksContainerElement.style.marginLeft).toBe('-37.5%'); + expect(ticksElement.style.transform).toBe('translateZ(0px) translateX(37.5%)'); + expect(ticksContainerElement.style.transform).toBe('translateX(-37.5%)'); }); it('should adjust fill and ticks on mouse enter when max changes', () => { @@ -311,11 +311,11 @@ describe('MdSlider', () => { dispatchMouseenterEvent(sliderNativeElement); fixture.detectChanges(); - expect(trackFillElement.style.flexBasis).toBe('50%'); + expect(trackFillElement.style.transform).toBe('scaleX(0.5)'); expect(ticksElement.style.backgroundSize).toBe('50% 2px'); // Make sure it cuts off the last half tick interval. - expect(ticksElement.style.marginLeft).toBe('25%'); - expect(ticksContainerElement.style.marginLeft).toBe('-25%'); + expect(ticksElement.style.transform).toBe('translateZ(0px) translateX(25%)'); + expect(ticksContainerElement.style.transform).toBe('translateX(-25%)'); }); }); @@ -390,7 +390,7 @@ describe('MdSlider', () => { fixture.detectChanges(); // The closest step is at 75% of the slider. - expect(trackFillElement.style.flexBasis).toBe('75%'); + expect(trackFillElement.style.transform).toBe('scaleX(0.75)'); }); it('should set the correct step value on slide', () => { @@ -405,7 +405,7 @@ describe('MdSlider', () => { fixture.detectChanges(); // The closest snap is at the end of the slider. - expect(trackFillElement.style.flexBasis).toBe('100%'); + expect(trackFillElement.style.transform).toBe('scaleX(1)'); }); }); @@ -434,8 +434,8 @@ describe('MdSlider', () => { // Ticks should be 30px apart (therefore 30% for a 100px long slider). expect(ticksElement.style.backgroundSize).toBe('30% 2px'); // Make sure it cuts off the last half tick interval. - expect(ticksElement.style.marginLeft).toBe('15%'); - expect(ticksContainerElement.style.marginLeft).toBe('-15%'); + expect(ticksElement.style.transform).toBe('translateZ(0px) translateX(15%)'); + expect(ticksContainerElement.style.transform).toBe('translateX(-15%)'); }); }); @@ -465,8 +465,8 @@ describe('MdSlider', () => { // long with 100 values, this is 18%. expect(ticksElement.style.backgroundSize).toBe('18% 2px'); // Make sure it cuts off the last half tick interval. - expect(ticksElement.style.marginLeft).toBe('9%'); - expect(ticksContainerElement.style.marginLeft).toBe('-9%'); + expect(ticksElement.style.transform).toBe('translateZ(0px) translateX(9%)'); + expect(ticksContainerElement.style.transform).toBe('translateX(-9%)'); }); }); @@ -638,7 +638,7 @@ describe('MdSlider', () => { it('should initialize based on bound value', () => { expect(sliderInstance.value).toBe(50); - expect(trackFillElement.style.flexBasis).toBe('50%'); + expect(trackFillElement.style.transform).toBe('scaleX(0.5)'); }); it('should update when bound value changes', () => { @@ -646,7 +646,7 @@ describe('MdSlider', () => { fixture.detectChanges(); expect(sliderInstance.value).toBe(75); - expect(trackFillElement.style.flexBasis).toBe('75%'); + expect(trackFillElement.style.transform).toBe('scaleX(0.75)'); }); }); @@ -676,7 +676,7 @@ describe('MdSlider', () => { }); it('should set the fill to the min value', () => { - expect(trackFillElement.style.flexBasis).toBe('0%'); + expect(trackFillElement.style.transform).toBe('scaleX(0)'); }); }); @@ -706,7 +706,7 @@ describe('MdSlider', () => { }); it('should set the fill to the max value', () => { - expect(trackFillElement.style.flexBasis).toBe('100%'); + expect(trackFillElement.style.transform).toBe('scaleX(1)'); }); }); From 0e444863d6ef82feac82c39a5a861f477f04386d Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 15 Nov 2016 15:04:03 -0800 Subject: [PATCH 23/28] fixed test issues on mobile --- src/lib/slider/slider.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/slider/slider.spec.ts b/src/lib/slider/slider.spec.ts index a72f9560270e..f9c0c5396cd8 100644 --- a/src/lib/slider/slider.spec.ts +++ b/src/lib/slider/slider.spec.ts @@ -297,7 +297,7 @@ describe('MdSlider', () => { expect(trackFillElement.style.transform).toBe('scaleX(0.75)'); expect(ticksElement.style.backgroundSize).toBe('75% 2px'); // Make sure it cuts off the last half tick interval. - expect(ticksElement.style.transform).toBe('translateZ(0px) translateX(37.5%)'); + expect(ticksElement.style.transform).toContain('translateX(37.5%)'); expect(ticksContainerElement.style.transform).toBe('translateX(-37.5%)'); }); @@ -314,7 +314,7 @@ describe('MdSlider', () => { expect(trackFillElement.style.transform).toBe('scaleX(0.5)'); expect(ticksElement.style.backgroundSize).toBe('50% 2px'); // Make sure it cuts off the last half tick interval. - expect(ticksElement.style.transform).toBe('translateZ(0px) translateX(25%)'); + expect(ticksElement.style.transform).toContain('translateX(25%)'); expect(ticksContainerElement.style.transform).toBe('translateX(-25%)'); }); }); @@ -434,7 +434,7 @@ describe('MdSlider', () => { // Ticks should be 30px apart (therefore 30% for a 100px long slider). expect(ticksElement.style.backgroundSize).toBe('30% 2px'); // Make sure it cuts off the last half tick interval. - expect(ticksElement.style.transform).toBe('translateZ(0px) translateX(15%)'); + expect(ticksElement.style.transform).toContain('translateX(15%)'); expect(ticksContainerElement.style.transform).toBe('translateX(-15%)'); }); }); @@ -465,7 +465,7 @@ describe('MdSlider', () => { // long with 100 values, this is 18%. expect(ticksElement.style.backgroundSize).toBe('18% 2px'); // Make sure it cuts off the last half tick interval. - expect(ticksElement.style.transform).toBe('translateZ(0px) translateX(9%)'); + expect(ticksElement.style.transform).toContain('translateX(9%)'); expect(ticksContainerElement.style.transform).toBe('translateX(-9%)'); }); }); From a741d6924017f8203c888d9f3f000525ff8f98fe Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 15 Nov 2016 15:47:17 -0800 Subject: [PATCH 24/28] fix mobile bug where slide doesn't work until after click --- src/lib/slider/slider.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 4334ac26945d..0b064cee494d 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -296,6 +296,9 @@ export class MdSlider implements ControlValueAccessor { return; } + // Simulate mouseenter in case this is a mobile device. + this._onMouseenter(); + event.preventDefault(); this._isSliding = true; this._isActive = true; From 5a5456eff5ebb306a59d02c8d99373c0f7e23935 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 16 Nov 2016 14:17:02 -0800 Subject: [PATCH 25/28] swap the meaning of normal and inverted for vertical sliders --- src/lib/slider/slider.scss | 16 +++++++----- src/lib/slider/slider.spec.ts | 20 +++++++------- src/lib/slider/slider.ts | 49 ++++++++++++++++++++++++++++------- 3 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/lib/slider/slider.scss b/src/lib/slider/slider.scss index 9cc1eb2176b3..79a618f6b899 100644 --- a/src/lib/slider/slider.scss +++ b/src/lib/slider/slider.scss @@ -132,7 +132,7 @@ md-slider { // Inverted slider. -.md-slider-inverted { +.md-slider-axis-inverted { .md-slider-track-fill { transform-origin: 100% 100%; } @@ -287,13 +287,15 @@ md-slider { right: auto; } - .md-slider-track-fill { - transform-origin: 100% 100%; - } - - .md-slider-inverted { + .md-slider-horizontal { .md-slider-track-fill { - transform-origin: 0 0; + transform-origin: 100% 100%; + } + + &.md-slider-axis-inverted { + .md-slider-track-fill { + transform-origin: 0 0; + } } } } diff --git a/src/lib/slider/slider.spec.ts b/src/lib/slider/slider.spec.ts index f9c0c5396cd8..39a498994e52 100644 --- a/src/lib/slider/slider.spec.ts +++ b/src/lib/slider/slider.spec.ts @@ -892,25 +892,25 @@ describe('MdSlider', () => { expect(sliderInstance.value).toBe(30); }); - it('should decrement inverted slider by 1 on right arrow pressed', () => { + it('should increment inverted slider by 1 on right arrow pressed', () => { testComponent.invert = true; - sliderInstance.value = 100; fixture.detectChanges(); dispatchKeydownEvent(sliderNativeElement, RIGHT_ARROW); fixture.detectChanges(); - expect(sliderInstance.value).toBe(99); + expect(sliderInstance.value).toBe(1); }); - it('should increment inverted slider by 1 on left arrow pressed', () => { + it('should decrement inverted slider by 1 on left arrow pressed', () => { testComponent.invert = true; + sliderInstance.value = 100; fixture.detectChanges(); dispatchKeydownEvent(sliderNativeElement, LEFT_ARROW); fixture.detectChanges(); - expect(sliderInstance.value).toBe(1); + expect(sliderInstance.value).toBe(99); }); it('should decrement RTL slider by 1 on right arrow pressed', () => { @@ -934,27 +934,27 @@ describe('MdSlider', () => { expect(sliderInstance.value).toBe(1); }); - it('should increment inverted RTL slider by 1 on right arrow pressed', () => { + it('should decrement inverted RTL slider by 1 on right arrow pressed', () => { testComponent.dir = 'rtl'; testComponent.invert = true; + sliderInstance.value = 100; fixture.detectChanges(); dispatchKeydownEvent(sliderNativeElement, RIGHT_ARROW); fixture.detectChanges(); - expect(sliderInstance.value).toBe(1); + expect(sliderInstance.value).toBe(99); }); - it('should decrement inverted RTL slider by 1 on left arrow pressed', () => { + it('should increment inverted RTL slider by 1 on left arrow pressed', () => { testComponent.dir = 'rtl'; testComponent.invert = true; - sliderInstance.value = 100; fixture.detectChanges(); dispatchKeydownEvent(sliderNativeElement, LEFT_ARROW); fixture.detectChanges(); - expect(sliderInstance.value).toBe(99); + expect(sliderInstance.value).toBe(1); }); }); }); diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 0b064cee494d..625b99117268 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -71,7 +71,7 @@ export class MdSliderChange { '[class.md-slider-disabled]': 'disabled', '[class.md-slider-has-ticks]': 'tickInterval', '[class.md-slider-horizontal]': '!vertical', - '[class.md-slider-inverted]': 'invert', + '[class.md-slider-axis-inverted]': 'invertAxis', '[class.md-slider-sliding]': '_isSliding', '[class.md-slider-thumb-label-showing]': 'thumbLabel', '[class.md-slider-vertical]': 'vertical', @@ -207,6 +207,24 @@ export class MdSlider implements ControlValueAccessor { set vertical(value: any) { this._vertical = coerceBooleanProperty(value); } private _vertical = false; + /** + * Whether the axis of the slider is inverted. + * (i.e. whether moving the thumb in the positive x or y direction decreases the slider's value). + */ + get invertAxis() { + // Standard non-inverted mode for a vertical slider should be dragging the thumb from bottom to + // top. However from a y-axis standpoint this is inverted. + return this.vertical ? !this.invert : this.invert; + } + + /** + * Whether mouse events should be converted to a slider position by calculating their distance + * from the right or bottom edge of the slider as opposed to the top or left. + */ + get invertMouseCoords() { + return (this.direction == 'rtl' && !this.vertical) ? !this.invertAxis : this.invertAxis; + } + /** CSS styles for the track fill element. */ get trackFillStyles(): { [key: string]: string } { let axis = this.vertical ? 'Y' : 'X'; @@ -218,6 +236,8 @@ export class MdSlider implements ControlValueAccessor { /** CSS styles for the ticks container element. */ get ticksContainerStyles(): { [key: string]: string } { let axis = this.vertical ? 'Y' : 'X'; + // For a horizontal slider in RTL languages we push the ticks container off the left edge + // instead of the right edge to avoid causing a horizontal scrollbar to appear. let sign = !this.vertical && this.direction == 'rtl' ? '' : '-'; let offset = this.tickIntervalPercent / 2 * 100; return { @@ -230,6 +250,9 @@ export class MdSlider implements ControlValueAccessor { let tickSize = this.tickIntervalPercent * 100; let backgroundSize = this.vertical ? `2px ${tickSize}%` : `${tickSize}% 2px`; let axis = this.vertical ? 'Y' : 'X'; + // Depending on the direction we pushed the ticks container, push the ticks the opposite + // direction to re-center them but clip off the end edge. In RTL languages we need to flip the + // ticks 180 degrees so we're really cutting off the end edge abd not the start. let sign = !this.vertical && this.direction == 'rtl' ? '-' : ''; let rotate = !this.vertical && this.direction == 'rtl' ? ' rotate(180deg)' : ''; return { @@ -241,7 +264,11 @@ export class MdSlider implements ControlValueAccessor { get thumbContainerStyles(): { [key: string]: string } { let axis = this.vertical ? 'Y' : 'X'; - let offset = (this._isLeftMin() ? 1 - this.percent : this.percent) * 100; + // For a horizontal slider in RTL languages we push the thumb container off the left edge + // instead of the right edge to avoid causing a horizontal scrollbar to appear. + let invertOffset = + (this.direction == 'rtl' && !this.vertical) ? !this.invertAxis : this.invertAxis; + let offset = (invertOffset ? this.percent : 1 - this.percent) * 100; return { 'transform': `translate${axis}(-${offset}%)` }; @@ -333,13 +360,20 @@ export class MdSlider implements ControlValueAccessor { this.value = this.min; break; case LEFT_ARROW: - this._increment(this._isLeftMin() ? -1 : 1); + // It's kind of debatable what's the correct thing to do for inverted sliders. For a sighted + // user it would make more sense that when they press an arrow key the thumb moves in that + // direction. However for a blind user, nothing about the slider indicates that it is + // inverted. They will expect left to be decrement, regardless of how it appears on the + // screen. For speakers of RTL languages, they probably expect left to mean increment. + // Therefore we flip the meaning of the side arrow keys for RTL but not for inverted. + this._increment(this.direction == 'rtl' ? 1 : -1); break; case UP_ARROW: this._increment(1); break; case RIGHT_ARROW: - this._increment(this._isLeftMin() ? 1 : -1); + // See comment on LEFT_ARROW about the conditions under which we flip the meaning. + this._increment(this.direction == 'rtl' ? -1 : 1); break; case DOWN_ARROW: this._increment(-1); @@ -353,11 +387,6 @@ export class MdSlider implements ControlValueAccessor { event.preventDefault(); } - /** Whether the left side of the slider is the minimum value. */ - private _isLeftMin() { - return (this.direction == 'rtl') == this.invert; - } - /** Increments the slider by the given number of steps (negative number decrements). */ private _increment(numSteps: number) { this.value = this._clamp(this.value + this.step * numSteps, this.min, this.max); @@ -377,7 +406,7 @@ export class MdSlider implements ControlValueAccessor { // The exact value is calculated from the event and used to find the closest snap value. let percent = this._clamp((posComponent - offset) / size); - if (!this._isLeftMin()) { + if (this.invertMouseCoords) { percent = 1 - percent; } let exactValue = this._calculateValue(percent); From 4b7bd3ae5892650e747e9ef798ab8a2326ecefd9 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 16 Nov 2016 14:33:35 -0800 Subject: [PATCH 26/28] vertical mode tests --- src/lib/slider/slider.spec.ts | 83 ++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 5 deletions(-) diff --git a/src/lib/slider/slider.spec.ts b/src/lib/slider/slider.spec.ts index 39a498994e52..951562e71017 100644 --- a/src/lib/slider/slider.spec.ts +++ b/src/lib/slider/slider.spec.ts @@ -38,6 +38,7 @@ describe('MdSlider', () => { SliderWithValueGreaterThanMax, SliderWithChangeHandler, SliderWithDirAndInvert, + VerticalSlider, ], providers: [ {provide: HAMMER_GESTURE_CONFIG, useFactory: () => { @@ -957,12 +958,73 @@ describe('MdSlider', () => { expect(sliderInstance.value).toBe(1); }); }); + + describe('vertical slider', () => { + let fixture: ComponentFixture; + let sliderDebugElement: DebugElement; + let sliderNativeElement: HTMLElement; + let sliderTrackElement: HTMLElement; + let trackFillElement: HTMLElement; + let sliderInstance: MdSlider; + let testComponent: VerticalSlider; + + beforeEach(() => { + fixture = TestBed.createComponent(VerticalSlider); + fixture.detectChanges(); + + testComponent = fixture.debugElement.componentInstance; + sliderDebugElement = fixture.debugElement.query(By.directive(MdSlider)); + sliderInstance = sliderDebugElement.injector.get(MdSlider); + sliderNativeElement = sliderDebugElement.nativeElement; + sliderTrackElement = sliderNativeElement.querySelector('.md-slider-track'); + trackFillElement = sliderNativeElement.querySelector('.md-slider-track-fill'); + }); + + it('updates value on click', () => { + dispatchClickEventSequence(sliderNativeElement, 0.3); + fixture.detectChanges(); + + expect(sliderInstance.value).toBe(70); + }); + + it('updates value on click in inverted mode', () => { + testComponent.invert = true; + fixture.detectChanges(); + + dispatchClickEventSequence(sliderNativeElement, 0.3); + fixture.detectChanges(); + + expect(sliderInstance.value).toBe(30); + }); + + it('should update the track fill on click', () => { + expect(trackFillElement.style.transform).toBe('scaleY(0)'); + + dispatchClickEventSequence(sliderNativeElement, 0.39); + fixture.detectChanges(); + + expect(trackFillElement.style.transform).toBe('scaleY(0.61)'); + }); + + it('should update the track fill on click in inverted mode', () => { + testComponent.invert = true; + fixture.detectChanges(); + + expect(trackFillElement.style.transform).toBe('scaleY(0)'); + + dispatchClickEventSequence(sliderNativeElement, 0.39); + fixture.detectChanges(); + + expect(trackFillElement.style.transform).toBe('scaleY(0.39)'); + }); + }); }); // Disable animations and make the slider an even 100px (+ 8px padding on either side) // so we get nice round values in tests. const styles = ` - md-slider { min-width: 116px !important; } + .md-slider-horizontal { min-width: 116px !important; } + .md-slider-vertical { min-height: 116px !important; } .md-slider-track-fill { transition: none !important; } `; @@ -1062,6 +1124,14 @@ class SliderWithDirAndInvert { invert = false; } +@Component({ + template: ``, + styles: [styles], +}) +class VerticalSlider { + invert = false; +} + /** * Dispatches a click event sequence (consisting of moueseenter, click) from an element. * Note: The mouse event truncates the position for the click. @@ -1072,8 +1142,8 @@ class SliderWithDirAndInvert { function dispatchClickEventSequence(sliderElement: HTMLElement, percentage: number): void { let trackElement = sliderElement.querySelector('.md-slider-track'); let dimensions = trackElement.getBoundingClientRect(); - let y = dimensions.top; let x = dimensions.left + (dimensions.width * percentage); + let y = dimensions.top + (dimensions.height * percentage); dispatchMouseenterEvent(sliderElement); @@ -1110,9 +1180,10 @@ function dispatchSlideEvent(sliderElement: HTMLElement, percent: number, let trackElement = sliderElement.querySelector('.md-slider-track'); let dimensions = trackElement.getBoundingClientRect(); let x = dimensions.left + (dimensions.width * percent); + let y = dimensions.top + (dimensions.height * percent); gestureConfig.emitEventForElement('slide', sliderElement, { - center: { x: x }, + center: { x: x, y: y }, srcEvent: { preventDefault: jasmine.createSpy('preventDefault') } }); } @@ -1128,11 +1199,12 @@ function dispatchSlideStartEvent(sliderElement: HTMLElement, percent: number, let trackElement = sliderElement.querySelector('.md-slider-track'); let dimensions = trackElement.getBoundingClientRect(); let x = dimensions.left + (dimensions.width * percent); + let y = dimensions.top + (dimensions.height * percent); dispatchMouseenterEvent(sliderElement); gestureConfig.emitEventForElement('slidestart', sliderElement, { - center: { x: x }, + center: { x: x, y: y }, srcEvent: { preventDefault: jasmine.createSpy('preventDefault') } }); } @@ -1148,9 +1220,10 @@ function dispatchSlideEndEvent(sliderElement: HTMLElement, percent: number, let trackElement = sliderElement.querySelector('.md-slider-track'); let dimensions = trackElement.getBoundingClientRect(); let x = dimensions.left + (dimensions.width * percent); + let y = dimensions.top + (dimensions.height * percent); gestureConfig.emitEventForElement('slideend', sliderElement, { - center: { x: x }, + center: { x: x, y: y }, srcEvent: { preventDefault: jasmine.createSpy('preventDefault') } }); } From 80eb98b69df781df6a72d1fc470898614b420ea0 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 16 Nov 2016 15:32:10 -0800 Subject: [PATCH 27/28] fix vertical slider in ie11 --- src/lib/slider/slider.scss | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib/slider/slider.scss b/src/lib/slider/slider.scss index 79a618f6b899..d26066cf6c24 100644 --- a/src/lib/slider/slider.scss +++ b/src/lib/slider/slider.scss @@ -22,8 +22,8 @@ $md-slider-tick-size: 2px !default; md-slider { - display: inline-flex; - align-items: center; + display: inline-block; + position: relative; box-sizing: border-box; padding: $md-slider-padding; outline: none; @@ -31,8 +31,7 @@ md-slider { } .md-slider-track { - flex: 1; - position: relative; + position: absolute; } .md-slider-track-fill { @@ -166,6 +165,9 @@ md-slider { .md-slider-track { height: $md-slider-track-thickness; + top: ($md-slider-thickness - $md-slider-track-thickness) / 2; + left: $md-slider-padding; + right: $md-slider-padding; } .md-slider-track::after { @@ -223,12 +225,14 @@ md-slider { // Vertical slider. .md-slider-vertical { - flex-direction: column; width: $md-slider-thickness; min-height: $md-slider-min-size; .md-slider-track { width: $md-slider-track-thickness; + top: $md-slider-padding; + bottom: $md-slider-padding; + left: ($md-slider-thickness - $md-slider-track-thickness) / 2; } .md-slider-track::after { From f6cb1dd4a79358a60064d32795c85a964fefb54c Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 29 Nov 2016 09:47:22 -0800 Subject: [PATCH 28/28] fixed lint issue --- src/lib/slider/slider.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 625b99117268..1e8ea3e9d31e 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -360,12 +360,13 @@ export class MdSlider implements ControlValueAccessor { this.value = this.min; break; case LEFT_ARROW: - // It's kind of debatable what's the correct thing to do for inverted sliders. For a sighted - // user it would make more sense that when they press an arrow key the thumb moves in that - // direction. However for a blind user, nothing about the slider indicates that it is - // inverted. They will expect left to be decrement, regardless of how it appears on the - // screen. For speakers of RTL languages, they probably expect left to mean increment. - // Therefore we flip the meaning of the side arrow keys for RTL but not for inverted. + // NOTE: For a sighted user it would make more sense that when they press an arrow key on an + // inverted slider the thumb moves in that direction. However for a blind user, nothing + // about the slider indicates that it is inverted. They will expect left to be decrement, + // regardless of how it appears on the screen. For speakers ofRTL languages, they probably + // expect left to mean increment. Therefore we flip the meaning of the side arrow keys for + // RTL. For inverted sliders we prefer a good a11y experience to having it "look right" for + // sighted users, therefore we do not swap the meaning. this._increment(this.direction == 'rtl' ? 1 : -1); break; case UP_ARROW: