mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(slider): add thumb-label (#976)
This commit is contained in:
committed by
Jeremy Elbourn
parent
844a2130bd
commit
22d70aeb8f
@@ -1,7 +1,8 @@
|
||||
<div class="md-slider-wrapper">
|
||||
<div class="md-slider-container"
|
||||
[class.md-slider-sliding]="isSliding"
|
||||
[class.md-slider-active]="isActive">
|
||||
[class.md-slider-active]="isActive"
|
||||
[class.md-slider-thumb-label-showing]="thumbLabel">
|
||||
<div class="md-slider-track-container">
|
||||
<div class="md-slider-track"></div>
|
||||
<div class="md-slider-track md-slider-track-fill"></div>
|
||||
@@ -11,6 +12,9 @@
|
||||
<div class="md-slider-thumb-container">
|
||||
<div class="md-slider-thumb-position">
|
||||
<div class="md-slider-thumb"></div>
|
||||
<div class="md-slider-thumb-label">
|
||||
<span class="md-slider-thumb-label-text">{{value}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
// This refers to the thickness of the slider. On a horizontal slider this is the height, on a
|
||||
// vertical slider this is the width.
|
||||
$md-slider-thickness: 20px !default;
|
||||
$md-slider-thickness: 48px !default;
|
||||
$md-slider-min-size: 128px !default;
|
||||
$md-slider-padding: 8px !default;
|
||||
|
||||
@@ -18,6 +18,16 @@ $md-slider-off-color: rgba(black, 0.26);
|
||||
$md-slider-focused-color: rgba(black, 0.38);
|
||||
$md-slider-disabled-color: rgba(black, 0.26);
|
||||
|
||||
$md-slider-thumb-arrow-height: 16px !default;
|
||||
$md-slider-thumb-arrow-width: 28px !default;
|
||||
|
||||
$md-slider-thumb-label-size: 28px !default;
|
||||
// The thumb has to be moved down so that it appears right over the slider track when visible and
|
||||
// on the slider track when not.
|
||||
$md-slider-thumb-label-top: ($md-slider-thickness / 2) -
|
||||
($md-slider-thumb-default-scale * $md-slider-thumb-size / 2) - $md-slider-thumb-label-size -
|
||||
$md-slider-thumb-arrow-height + 10px !default;
|
||||
|
||||
/**
|
||||
* Uses a container height and an item height to center an item vertically within the container.
|
||||
*/
|
||||
@@ -137,6 +147,43 @@ md-slider *::after {
|
||||
border-color: md-color($md-accent);
|
||||
}
|
||||
|
||||
.md-slider-thumb-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: absolute;
|
||||
left: -($md-slider-thumb-label-size / 2);
|
||||
top: $md-slider-thumb-label-top;
|
||||
width: $md-slider-thumb-label-size;
|
||||
height: $md-slider-thumb-label-size;
|
||||
border-radius: 50%;
|
||||
|
||||
transform: scale(0.4) translate3d(0, (-$md-slider-thumb-label-top + 10) / 0.4, 0) rotate(45deg);
|
||||
transition: 300ms $swift-ease-in-out-timing-function;
|
||||
transition-property: transform, border-radius;
|
||||
|
||||
background-color: md-color($md-accent);
|
||||
}
|
||||
|
||||
.md-slider-thumb-label-text {
|
||||
z-index: 1;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
opacity: 0;
|
||||
transform: rotate(-45deg);
|
||||
transition: opacity 300ms $swift-ease-in-out-timing-function;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.md-slider-container:not(.md-slider-thumb-label-showing) .md-slider-thumb-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.md-slider-active.md-slider-thumb-label-showing .md-slider-thumb {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.md-slider-sliding .md-slider-thumb-position,
|
||||
.md-slider-sliding .md-slider-track-fill {
|
||||
transition: none;
|
||||
@@ -147,7 +194,11 @@ md-slider *::after {
|
||||
transform: scale($md-slider-thumb-focus-scale);
|
||||
}
|
||||
|
||||
.md-slider-disabled .md-slider-thumb::after {
|
||||
background-color: $md-slider-disabled-color;
|
||||
border-color: $md-slider-disabled-color;
|
||||
.md-slider-active .md-slider-thumb-label {
|
||||
border-radius: 50% 50% 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.md-slider-active .md-slider-thumb-label-text {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ describe('MdSlider', () => {
|
||||
SliderWithValue,
|
||||
SliderWithStep,
|
||||
SliderWithAutoTickInterval,
|
||||
SliderWithSetTickInterval
|
||||
SliderWithSetTickInterval,
|
||||
SliderWithThumbLabel,
|
||||
],
|
||||
});
|
||||
|
||||
@@ -118,7 +119,7 @@ describe('MdSlider', () => {
|
||||
// offset relative to the track, subtract the offset on the track fill.
|
||||
let thumbPosition = thumbDimensions.left - trackFillDimensions.left;
|
||||
// The track fill width should be equal to the thumb's position.
|
||||
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
|
||||
expect(trackFillDimensions.width).toBe(thumbPosition);
|
||||
});
|
||||
|
||||
it('should update the thumb position on click', () => {
|
||||
@@ -144,7 +145,7 @@ describe('MdSlider', () => {
|
||||
// offset relative to the track, subtract the offset on the track fill.
|
||||
let thumbPosition = thumbDimensions.left - trackFillDimensions.left;
|
||||
// The track fill width should be equal to the thumb's position.
|
||||
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
|
||||
expect(trackFillDimensions.width).toBe(thumbPosition);
|
||||
});
|
||||
|
||||
it('should update the thumb position on slide', () => {
|
||||
@@ -309,7 +310,7 @@ describe('MdSlider', () => {
|
||||
|
||||
// The closest snap is halfway on the slider.
|
||||
expect(thumbDimensions.left).toBe(sliderDimensions.width * 0.5 + sliderDimensions.left);
|
||||
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
|
||||
expect(trackFillDimensions.width).toBe(thumbPosition);
|
||||
});
|
||||
|
||||
it('should snap the thumb and fill to the nearest value on slide', () => {
|
||||
@@ -325,7 +326,7 @@ describe('MdSlider', () => {
|
||||
|
||||
// The closest snap is at the halfway point on the slider.
|
||||
expect(thumbDimensions.left).toBe(sliderDimensions.left + sliderDimensions.width * 0.5);
|
||||
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
|
||||
expect(trackFillDimensions.width).toBe(thumbPosition);
|
||||
|
||||
});
|
||||
});
|
||||
@@ -410,7 +411,7 @@ describe('MdSlider', () => {
|
||||
|
||||
// The closest step is at 75% of the slider.
|
||||
expect(thumbDimensions.left).toBe(sliderDimensions.width * 0.75 + sliderDimensions.left);
|
||||
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
|
||||
expect(trackFillDimensions.width).toBe(thumbPosition);
|
||||
});
|
||||
|
||||
it('should set the correct step value on slide', () => {
|
||||
@@ -433,7 +434,7 @@ describe('MdSlider', () => {
|
||||
|
||||
// The closest snap is at the end of the slider.
|
||||
expect(thumbDimensions.left).toBe(sliderDimensions.width + sliderDimensions.left);
|
||||
expect(Math.round(trackFillDimensions.width)).toBe(Math.round(thumbPosition));
|
||||
expect(trackFillDimensions.width).toBe(thumbPosition);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -516,6 +517,77 @@ describe('MdSlider', () => {
|
||||
+ 'black 2px, transparent 2px, transparent)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('slider with thumb label', () => {
|
||||
let fixture: ComponentFixture<SliderWithThumbLabel>;
|
||||
let sliderDebugElement: DebugElement;
|
||||
let sliderNativeElement: HTMLElement;
|
||||
let sliderInstance: MdSlider;
|
||||
let sliderTrackElement: HTMLElement;
|
||||
let sliderContainerElement: Element;
|
||||
let thumbLabelTextElement: Element;
|
||||
|
||||
beforeEach(async(() => {
|
||||
builder.createAsync(SliderWithThumbLabel).then(f => {
|
||||
fixture = f;
|
||||
fixture.detectChanges();
|
||||
|
||||
sliderDebugElement = fixture.debugElement.query(By.directive(MdSlider));
|
||||
sliderNativeElement = sliderDebugElement.nativeElement;
|
||||
sliderInstance = sliderDebugElement.componentInstance;
|
||||
sliderTrackElement = <HTMLElement>sliderNativeElement.querySelector('.md-slider-track');
|
||||
sliderContainerElement = sliderNativeElement.querySelector('.md-slider-container');
|
||||
thumbLabelTextElement = sliderNativeElement.querySelector('.md-slider-thumb-label-text');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should add the thumb label class to the slider container', () => {
|
||||
expect(sliderContainerElement.classList).toContain('md-slider-thumb-label-showing');
|
||||
});
|
||||
|
||||
it('should update the thumb label text on click', () => {
|
||||
expect(thumbLabelTextElement.textContent).toBe('0');
|
||||
|
||||
dispatchClickEvent(sliderTrackElement, 0.13);
|
||||
fixture.detectChanges();
|
||||
|
||||
// The thumb label text is set to the slider's value. These should always be the same.
|
||||
expect(thumbLabelTextElement.textContent).toBe('13');
|
||||
});
|
||||
|
||||
it('should update the thumb label text on slide', () => {
|
||||
expect(thumbLabelTextElement.textContent).toBe('0');
|
||||
|
||||
dispatchSlideEvent(sliderTrackElement, sliderNativeElement, 0, 0.56, gestureConfig);
|
||||
fixture.detectChanges();
|
||||
|
||||
// The thumb label text is set to the slider's value. These should always be the same.
|
||||
expect(thumbLabelTextElement.textContent).toBe(`${sliderInstance.value}`);
|
||||
});
|
||||
|
||||
it('should show the thumb label on click', () => {
|
||||
expect(sliderContainerElement.classList).not.toContain('md-slider-active');
|
||||
expect(sliderContainerElement.classList).toContain('md-slider-thumb-label-showing');
|
||||
|
||||
dispatchClickEvent(sliderNativeElement, 0.49);
|
||||
fixture.detectChanges();
|
||||
|
||||
// The thumb label appears when the slider is active and the 'md-slider-thumb-label-showing'
|
||||
// class is applied.
|
||||
expect(sliderContainerElement.classList).toContain('md-slider-thumb-label-showing');
|
||||
expect(sliderContainerElement.classList).toContain('md-slider-active');
|
||||
});
|
||||
|
||||
it('should show the thumb label on slide', () => {
|
||||
expect(sliderContainerElement.classList).not.toContain('md-slider-active');
|
||||
|
||||
dispatchSlideEvent(sliderTrackElement, sliderNativeElement, 0, 0.91, gestureConfig);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(sliderContainerElement.classList).toContain('md-slider-thumb-label-showing');
|
||||
expect(sliderContainerElement.classList).toContain('md-slider-active');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// The transition has to be removed in order to test the updated positions without setTimeout.
|
||||
@@ -572,6 +644,17 @@ class SliderWithAutoTickInterval { }
|
||||
})
|
||||
class SliderWithSetTickInterval { }
|
||||
|
||||
@Component({
|
||||
template: `<md-slider thumb-label></md-slider>`,
|
||||
styles: [`
|
||||
.md-slider-thumb-label, .md-slider-thumb-label-text {
|
||||
transition: none !important;
|
||||
}
|
||||
`],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
class SliderWithThumbLabel { }
|
||||
|
||||
/**
|
||||
* Dispatches a click event from an element.
|
||||
* Note: The mouse event truncates the position for the click.
|
||||
|
||||
@@ -47,6 +47,11 @@ export class MdSlider implements AfterContentInit {
|
||||
@HostBinding('attr.aria-disabled')
|
||||
disabled: boolean = false;
|
||||
|
||||
/** Whether or not to show the thumb label. */
|
||||
@Input('thumb-label')
|
||||
@BooleanFieldValue()
|
||||
thumbLabel: boolean = false;
|
||||
|
||||
/** The miniumum value that the slider can have. */
|
||||
private _min: number = 0;
|
||||
|
||||
@@ -341,7 +346,7 @@ export class SliderRenderer {
|
||||
<HTMLElement>this._sliderElement.querySelector('.md-slider-thumb-position');
|
||||
let fillTrackElement = <HTMLElement>this._sliderElement.querySelector('.md-slider-track-fill');
|
||||
|
||||
let position = percent * width;
|
||||
let position = Math.round(percent * width);
|
||||
|
||||
fillTrackElement.style.width = `${position}px`;
|
||||
applyCssTransform(thumbPositionElement, `translateX(${position}px)`);
|
||||
|
||||
@@ -30,3 +30,6 @@
|
||||
<h1>Slider with set tick interval</h1>
|
||||
<md-slider tick-interval="auto"></md-slider>
|
||||
<md-slider tick-interval="9"></md-slider>
|
||||
|
||||
<h1>Slider with Thumb Label</h1>
|
||||
<md-slider thumb-label></md-slider>
|
||||
|
||||
Reference in New Issue
Block a user