diff --git a/CHANGELOG.md b/CHANGELOG.md index e4fedc1d5b0..882b01e99ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,19 +56,30 @@ All notable changes for each version of this project will be documented in this ``` - - `IgxCarousel`: - - `keyboardSupport` input is added, which can be used to enable and disable keyboard navigation - - `gesturesSupport` input is added, which can be used to enable and disable gestures - - `maximumIndicatorsCount` input is added, which can be used to set the number of visible indicators - - `indicatorsOrientation` input is added, which can be used to set the position of indicators it can be top or bottom - - `animationType` input is added, which can be used to set animation when changing slides - - `indicatorTemplate` directive is added, which can be used to provide a custom indicator for carousel. If this property is not provided, a default indicator template will be used instead. - - `nextButtonTemplate` directive is added, which is used to provide a custom next button template. If not provided, a default next button is used. - - `prevButtonTemplate` directive is added, which is used to provide a custom previous button template. If not provided, a default previous button is used. +- `IgxSlider`: + - `primaryTicks` input was added. Which sets the number of primary ticks + - `secondaryTicks` input was added. Which sets the number of secondary ticks. + - `showTicks` input was added. Which show/hide all slider ticks and tick labels. + - `primaryTickLabels` input was added. Which shows/hides all primary tick labels. + - `secondaryTickLabels` input was added. Shows/hides all secondary tick labels. + - `ticksOrientation` input was added. Allows to change ticks orientation to top|bottom|mirror. + - `tickLabelsOrientation` input was added. Allows you to change the rotation of all tick labels from horizontal to vertical(toptobottom, bottomtotop). + - `igxSliderTickLabel` directive has been introduced. Allows you to set a custom template for all tick labels. + +- `IgxCarousel`: + - `keyboardSupport` input is added, which can be used to enable and disable keyboard navigation + - `gesturesSupport` input is added, which can be used to enable and disable gestures + - `maximumIndicatorsCount` input is added, which can be used to set the number of visible indicators + - `indicatorsOrientation` input is added, which can be used to set the position of indicators it can be top or bottom + - `animationType` input is added, which can be used to set animation when changing slides + - `indicatorTemplate` directive is added, which can be used to provide a custom indicator for carousel. If this property is not provided, a default indicator template will be used instead. + - `nextButtonTemplate` directive is added, which is used to provide a custom next button template. If not provided, a default next button is used. + - `prevButtonTemplate` directive is added, which is used to provide a custom previous button template. If not provided, a default previous button is used. - `IgxSelect`: - adding `IgxSelectHeaderDirective` and `IgxSelectFooterDirective`. These can be used to provide a custom header, respectively footer templates for the `igxSelect` drop-down list. If there are no templates marked with these directives - no default templates will be used so the drop-down list will not have header nor footer. + ## 8.2.6 ### New Features diff --git a/projects/igniteui-angular/src/lib/core/styles/components/slider/_slider-component.scss b/projects/igniteui-angular/src/lib/core/styles/components/slider/_slider-component.scss index 12dad4a6091..df490c7c20c 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/slider/_slider-component.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/slider/_slider-component.scss @@ -22,8 +22,44 @@ @extend %igx-slider-track-fill !optional; } - @include e(track-ticks) { - @extend %igx-slider-track-ticks !optional; + @include e(ticks) { + @extend %igx-slider__ticks !optional; + } + + @include e(ticks, $m: tall) { + @extend %igx-slider__ticks--tall !optional; + } + + @include e(ticks, $m: top) { + @extend %igx-slider__ticks--top !optional; + } + + @include e(tick-label, $m: hidden) { + @extend %igx-slider__tick-label--hidden !optional; + } + + @include e(tick-labels, $m: top-bottom) { + @extend %igx-slider__tick-labels--top-bottom !optional; + } + + @include e(tick-labels, $m: bottom-top) { + @extend %igx-slider__tick-labels--bottom-top !optional; + } + + @include e(ticks-group) { + @extend %igx-slider__ticks-group !optional; + } + + @include e(ticks-group, $m: tall) { + @extend %igx-slider__ticks-group--tall !optional; + } + + @include e(ticks-tick) { + @extend %igx-slider__ticks-tick !optional; + } + + @include e(ticks-label) { + @extend %igx-slider__ticks-label !optional; } @include e(thumbs) { @@ -76,6 +112,10 @@ } } + @include e(track-steps) { + @extend %igx-slider-track-steps !optional; + } + @include m(disabled) { @extend %igx-slider-display !optional; @@ -84,14 +124,21 @@ @extend %igx-slider-track--disabled !optional; } + @include e(track-steps) { + @extend %igx-slider-track-steps--disabled !optional; + } + @include e(track-fill) { @extend %igx-slider-track-fill !optional; @extend %igx-slider-track-fill--disabled !optional; } - @include e(track-ticks) { - @extend %igx-slider-track-ticks !optional; - @extend %igx-slider-track-ticks--disabled !optional; + @include e(ticks-tick) { + @extend %igx-slider__tick--disabled !optional; + } + + @include e(ticks-label) { + @extend %igx-slider__ticks-labels--disabled !optional; } @each $t in $thumbs { diff --git a/projects/igniteui-angular/src/lib/core/styles/components/slider/_slider-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/slider/_slider-theme.scss index 0804499c5df..d9ff564d298 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/slider/_slider-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/slider/_slider-theme.scss @@ -17,6 +17,11 @@ /// @param {Color} $base-track-color [null] - The base background color of the track. /// @param {Color} $disabled-base-track-color [null] - The base background color of the track when is disabled. /// +/// @param {Color} $tick-label-color [null] - The color of the tick label. +/// @param {Color} $tick-label-color--tall [null] - The color of the tall tick label . +/// @param {Color} $tick-color [null] - The background-color of the tick. +/// @param {Color} $tick-color--tall [null] - The background-color of the tall tick. +/// /// @requires $default-palette /// @requires $light-schema /// @requires apply-palette @@ -44,7 +49,11 @@ $thumb-disabled-border-color: null, $track-hover-color: null, $thumb-hover-color: null, - $base-track-hover-color: null + $base-track-hover-color: null, + $tick-label-color: null, + $tick-label-color-tall: null, + $tick-color: null, + $tick-color-tall: null, ) { $name: 'igx-slider'; $slider-schema: (); @@ -75,7 +84,11 @@ disabled-base-track-color: $disabled-base-track-color, thumb-border-color: $thumb-border-color, thumb-disabled-border-color: $thumb-disabled-border-color, - base-track-hover-color: $base-track-hover-color + base-track-hover-color: $base-track-hover-color, + tick-label-color: $tick-label-color, + tick-label-color-tall: $tick-label-color-tall, + tick-color: $tick-color, + tick-color-tall: $tick-color-tall, )); } @@ -96,6 +109,13 @@ fluent: 4px ), map-get($theme, variant)); + // Slide ticks + $tick-push: rem(4px); + $base-tick-height: rem(8px); + $tick-height: $base-tick-height; + $tick-height--tall: $base-tick-height * 2; + $tick-width: rem(2px); + $thumb-border-width: map-get(( material: 0, fluent: 2px @@ -165,10 +185,11 @@ height: 0; cursor: default; z-index: 1; + left: 0; } %igx-slider-track { - position: absolute; + position: relative; width: 100%; height: rem($slider-track-height); background: --var($theme, 'base-track-color'); @@ -179,7 +200,110 @@ background: --var($theme, 'disabled-base-track-color'); } - %igx-slider-track-ticks { + %igx-slider-track-fill { + position: absolute; + width: 100%; + height: inherit; + background: --var($theme, 'track-color'); + transform-origin: #{$left} center; + transform: scaleX(0); + } + + %igx-slider-track-fill--disabled { + visibility: hidden; + } + + %igx-slider__ticks { + width: 100%; + display: flex; + position: absolute; + top: $tick-push; + justify-content: space-between; + z-index: 1; + + &%igx-slider__ticks--top { + bottom: $tick-push; + top: auto; + align-items: flex-end; + } + } + + %igx-slider__ticks-group { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + + &:first-of-type { + margin-left: rem(-1px); + } + + &:last-of-type { + margin-left: rem(-1px); + } + } + + %igx-slider__ticks-label { + color: --var($theme, 'tick-label-color'); + position: absolute; + top: $tick-height--tall; + transform: translate(-50%); + line-height: .7; + opacity: 1; + transition: opacity .2s $ease-in-out-quad; + } + + %igx-slider__ticks-tick { + background: --var($theme, 'tick-color'); + height: $tick-height; + width: $tick-width; + } + + %igx-slider__ticks--tall { + %igx-slider__ticks-label { + top: calc(#{$tick-height--tall} + #{$tick-height}); + } + } + + %igx-slider__tick--disabled { + background: --var($theme, 'disabled-base-track-color')!important; + } + + %igx-slider__ticks-labels--disabled { + color: --var($theme, 'disabled-base-track-color')!important; + } + + %igx-slider__ticks-group--tall { + %igx-slider__ticks-tick { + height: $tick-height--tall; + background: --var($theme, 'tick-color-tall'); + } + + %igx-slider__ticks-label { + top: calc(#{$tick-height--tall} + #{$tick-height}); + color: --var($theme, 'tick-label-color-tall'); + } + } + + %igx-slider__ticks--top { + %igx-slider__ticks-label { + bottom: calc(#{$tick-height} + #{$tick-height}); + top: auto; + } + + &%igx-slider__ticks--tall { + %igx-slider__ticks-label { + bottom: calc(#{$tick-height--tall} + #{$tick-height}); + top: auto; + } + } + } + + %igx-slider__tick-label--hidden { + opacity: 0; + } + + %igx-slider-track-steps { position: absolute; width: 100%; height: rem($slider-track-height); @@ -189,21 +313,63 @@ z-index: 1; } - %igx-slider-track-ticks--disabled { + %igx-slider-track-steps--disabled { visibility: hidden; } - %igx-slider-track-fill { - position: absolute; - width: 100%; - height: inherit; - background: --var($theme, 'track-color'); - transform-origin: #{$left} center; - transform: scaleX(0); + %igx-slider__tick-labels--top-bottom { + %igx-slider__ticks-group { + display: block; + } + + %igx-slider__ticks-label { + writing-mode: vertical-rl; + transform: translate(-50%) rotate(0deg); + } + + %igx-slider__ticks--tall { + %igx-slider__ticks-label { + top: calc(#{$tick-height--tall} + #{rem(2px)}); + } + } + + &%igx-slider__ticks--top { + %igx-slider__ticks-label { + writing-mode: vertical-rl; + transform: translate(-50%) rotate(0deg); + } + + %igx-slider__ticks--tall { + %igx-slider__ticks-label { + bottom: calc(#{$tick-height--tall} + #{rem(2px)}); + } + } + } } - %igx-slider-track-fill--disabled { - visibility: hidden; + %igx-slider__tick-labels--bottom-top { + %igx-slider__ticks-group { + display: block; + } + + + %igx-slider__ticks-label { + writing-mode: vertical-rl; + transform: translate(-50%) rotate(180deg); + } + + &%igx-slider__ticks--top { + %igx-slider__ticks-label { + writing-mode: vertical-rl; + transform: translate(-50%) rotate(180deg); + } + + %igx-slider__ticks--tall { + %igx-slider__ticks-label { + bottom: calc(#{$tick-height--tall} + #{rem(2px)}); + } + } + } } %igx-thumb-display { diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_slider.scss b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_slider.scss index 98d6ff3a683..280b64d7fb6 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_slider.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/schemas/light/_slider.scss @@ -21,12 +21,33 @@ /// @property {map} base-track-hover-color [igx-color: ('secondary', 500), rgba: .24] - The base background color of the track on hover. /// @property {map} disabled-base-track-color [igx-color: ('grays', 400)] - The base background color of the track when is disabled. /// +/// @property {map} tick-label-color [igx-color: ('grays', 400)] - The color of the tick label. +/// @property {map} tick-label-color--tall [igx-color: ('grays', 800)] - The color of the tall tick label . +/// @property {map} tick-color [igx-color: ('grays', 400)] - The background-color of the tick. +/// @property {map} tick-color--tall [igx-color: ('grays', 400)] - The background-color of the tall tick. + /// @see $default-palette $_light-slider: extend( $_default-shape-slider, ( variant: 'material', + tick-color: ( + igx-color: ('grays', 500) + ), + + tick-color-tall: ( + igx-color: ('grays', 500) + ), + + tick-label-color: ( + igx-color: ('grays', 500) + ), + + tick-label-color-tall: ( + igx-color: ('grays', 900) + ), + track-color: ( igx-color: ('secondary', 500) ), diff --git a/projects/igniteui-angular/src/lib/slider/label/thumb-label.component.ts b/projects/igniteui-angular/src/lib/slider/label/thumb-label.component.ts index c90ee59335d..c0336b690a0 100644 --- a/projects/igniteui-angular/src/lib/slider/label/thumb-label.component.ts +++ b/projects/igniteui-angular/src/lib/slider/label/thumb-label.component.ts @@ -23,6 +23,9 @@ export class IgxThumbLabelComponent { @Input() public continuous: boolean; + @Input() + public deactiveState: boolean; + @HostBinding('class.igx-slider__label-from') public get thumbFromClass() { return this.type === SliderHandle.FROM; @@ -54,10 +57,10 @@ export class IgxThumbLabelComponent { } public set active(val: boolean) { - if (this.continuous) { - return; + if (this.continuous || this.deactiveState) { + this._active = false; + } else { + this._active = val; } - - this._active = val; } } diff --git a/projects/igniteui-angular/src/lib/slider/slider.common.ts b/projects/igniteui-angular/src/lib/slider/slider.common.ts index 5028ce22dcc..841a62ba9a7 100644 --- a/projects/igniteui-angular/src/lib/slider/slider.common.ts +++ b/projects/igniteui-angular/src/lib/slider/slider.common.ts @@ -33,6 +33,26 @@ export class IgxThumbFromTemplateDirective {} }) export class IgxThumbToTemplateDirective {} +/** + * Template directive that allows you to set a custom template, represeting primary/secondary tick labels of the {@link IgxSliderComponent} + * + * @context {@link IgxTicksComponent.context} + */ +@Directive({ + selector: '[igxSliderTickLabel]' +}) +export class IgxTickLabelTemplateDirective {} + +export interface IRangeSliderValue { + lower: number; + upper: number; +} + +export interface ISliderValueChangeEventArgs extends IBaseEventArgs { + oldValue: number | IRangeSliderValue; + value: number | IRangeSliderValue; +} + export enum SliderType { /** * Slider with single thumb. @@ -49,12 +69,20 @@ export enum SliderHandle { TO } -export interface IRangeSliderValue { - lower: number; - upper: number; +/** + * Slider Tick labels Orientation + */ +export enum TickLabelsOrientation { + horizontal, + toptobottom, + bottomtotop } -export interface ISliderValueChangeEventArgs extends IBaseEventArgs { - oldValue: number | IRangeSliderValue; - value: number | IRangeSliderValue; +/** + * Slider Ticks orientation + */ +export enum TicksOrientation { + top, + bottom, + mirror } diff --git a/projects/igniteui-angular/src/lib/slider/slider.component.html b/projects/igniteui-angular/src/lib/slider/slider.component.html index 386897c590b..df9fa02f8f9 100644 --- a/projects/igniteui-angular/src/lib/slider/slider.component.html +++ b/projects/igniteui-angular/src/lib/slider/slider.component.html @@ -1,6 +1,34 @@
+ +
-
+
+ +
+ [context]="context" + [deactiveState]="deactivateThumbLabel"> - + [context]="context" + [deactiveState]="deactivateThumbLabel">
diff --git a/projects/igniteui-angular/src/lib/slider/slider.component.spec.ts b/projects/igniteui-angular/src/lib/slider/slider.component.spec.ts index a31a701ef1b..aa077ab64e9 100644 --- a/projects/igniteui-angular/src/lib/slider/slider.component.spec.ts +++ b/projects/igniteui-angular/src/lib/slider/slider.component.spec.ts @@ -1,17 +1,26 @@ -import { Component, ViewChild} from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { async, TestBed, ComponentFixture, fakeAsync, tick, flush } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; +import { Component, ViewChild, ElementRef} from '@angular/core'; +import { async, TestBed, ComponentFixture } from '@angular/core/testing'; +import { By, HammerModule } from '@angular/platform-browser'; import { IgxSliderComponent, IgxSliderModule } from './slider.component'; import { UIInteractions, wait } from '../test-utils/ui-interactions.spec'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { configureTestSuite } from '../test-utils/configure-suite'; -import { SliderType, IRangeSliderValue } from './slider.common'; +import { SliderType, IRangeSliderValue, TicksOrientation, TickLabelsOrientation } from './slider.common'; +import { FormsModule } from '@angular/forms'; declare var Simulator: any; const SLIDER_CLASS = '.igx-slider'; const THUMB_TO_CLASS = '.igx-slider__thumb-to'; const THUMB_FROM_CLASS = '.igx-slider__thumb-from'; +const SLIDER_TICKS_ELEMENT = '.igx-slider__ticks'; +const SLIDER_TICKS_TOP_ELEMENT = '.igx-slider__ticks--top'; +const SLIDER_PRIMARY_GROUP_TICKS_CLASS = '.igx-slider__ticks-group--tall'; +const SLIDER_PRIMARY_GROUP_TICKS_CLASS_NAME = 'igx-slider__ticks-group--tall'; +const SLIDER_GROUP_TICKS_CLASS = '.igx-slider__ticks-group'; +const SLIDER_TICK_LABELS_CLASS = '.igx-slider__ticks-label'; +const SLIDER_TICK_LABELS_HIDDEN_CLASS = 'igx-slider__tick-label--hidden'; +const TOP_TO_BOTTOM_TICK_LABLES = '.igx-slider__tick-labels--top-bottom'; +const BOTTOM_TO_TOP_TICK_LABLES = '.igx-slider__tick-labels--bottom-top'; describe('IgxSlider', () => { configureTestSuite(); @@ -23,10 +32,11 @@ describe('IgxSlider', () => { SliderTestComponent, SliderWithLabelsComponent, RangeSliderWithLabelsComponent, - RangeSliderWithCustomTemplateComponent + RangeSliderWithCustomTemplateComponent, + SliderTicksComponent ], imports: [ - IgxSliderModule, NoopAnimationsModule, FormsModule + IgxSliderModule, NoopAnimationsModule, FormsModule, HammerModule ] }).compileComponents(); })); @@ -77,22 +87,22 @@ describe('IgxSlider', () => { expect(slider.maxValue).toBe(expectedMaxValue); }); - it('should reduce minValue when greater than maxValue', () => { + it('should prevent setting minValue when greater than maxValue', () => { slider.maxValue = 6; slider.minValue = 10; - const expectedMinValue = slider.maxValue - 1; + const expectedMinValue = 0; fixture.detectChanges(); expect(slider.minValue).toBe(expectedMinValue); expect(slider.minValue).toBeLessThan(slider.maxValue); }); - it('should increase minValue when greater than maxValue', () => { + it('should prevent setting maxValue when lower than minValue', () => { slider.minValue = 3; slider.maxValue = -5; - const expectedMaxValue = slider.minValue + 1; + const expectedMaxValue = 100; fixture.detectChanges(); expect(slider.maxValue).toBe(expectedMaxValue); @@ -636,7 +646,7 @@ describe('IgxSlider', () => { }); it('tick marks(steps) should be shown equally spread based on labels length', () => { - const ticks = fixture.nativeElement.querySelector('.igx-slider__track-ticks'); + const ticks = fixture.nativeElement.querySelector('.igx-slider__track-steps'); const sliderWidth = parseInt(fixture.nativeElement.querySelector('igx-slider').clientWidth, 10); fixture.detectChanges(); @@ -921,7 +931,7 @@ describe('IgxSlider', () => { }); it('tick marks(steps) should be shown equally spread based on labels length', () => { - const ticks = fixture.nativeElement.querySelector('.igx-slider__track-ticks'); + const ticks = fixture.nativeElement.querySelector('.igx-slider__track-steps'); const sliderWidth = parseInt(fixture.nativeElement.querySelector('igx-slider').clientWidth, 10); fixture.detectChanges(); @@ -1126,7 +1136,7 @@ describe('IgxSlider', () => { it('should draw tick marks', () => { const fixture = TestBed.createComponent(SliderInitializeTestComponent); - const ticks = fixture.nativeElement.querySelector('.igx-slider__track-ticks'); + const ticks = fixture.nativeElement.querySelector('.igx-slider__track-steps'); // Slider steps <= 1. No marks should be drawn; expect(ticks.style.background).toBeFalsy(); @@ -1142,7 +1152,7 @@ describe('IgxSlider', () => { const fixture = TestBed.createComponent(SliderInitializeTestComponent); fixture.detectChanges(); - const ticks = fixture.nativeElement.querySelector('.igx-slider__track-ticks'); + const ticks = fixture.nativeElement.querySelector('.igx-slider__track-steps'); const slider = fixture.componentInstance.slider; expect(ticks.style.background).toBeFalsy(); @@ -1318,6 +1328,195 @@ describe('IgxSlider', () => { }); }); + describe('igxSlider ticks', () => { + let fixture: ComponentFixture; + let slider: IgxSliderComponent; + + beforeEach(() => { + fixture = TestBed.createComponent(SliderTicksComponent); + slider = fixture.componentInstance.slider; + fixture.detectChanges(); + }); + + it('should render a specific amount of primary ticks', () => { + const ticks = fixture.debugElement.query(By.css(SLIDER_TICKS_ELEMENT)); + expect(ticks).not.toBeNull(); + + const expectedPrimary = 5; + fixture.componentInstance.primaryTicks = expectedPrimary; + fixture.detectChanges(); + + const primaryTicks = ticks.nativeElement + .querySelectorAll(SLIDER_PRIMARY_GROUP_TICKS_CLASS); + expect(primaryTicks.length).toEqual(expectedPrimary); + }); + + it('should render a specific amount of secondary ticks', () => { + const ticks = fixture.debugElement.query(By.css(SLIDER_TICKS_ELEMENT)); + expect(ticks).not.toBeNull(); + + const expectedSecondary = 5; + fixture.componentInstance.secondaryTicks = expectedSecondary; + fixture.detectChanges(); + + const secondaryTicks = ticks.nativeElement + .querySelectorAll(`${SLIDER_GROUP_TICKS_CLASS}:not(${SLIDER_PRIMARY_GROUP_TICKS_CLASS})`); + expect(secondaryTicks.length).toEqual(expectedSecondary); + }); + + it('should render secondary and primary ticks', () => { + const ticks = fixture.debugElement.query(By.css(SLIDER_TICKS_ELEMENT)); + expect(ticks).not.toBeNull(); + + const expectedPrimary = 5; + const expectedSecondary = 3; + fixture.componentInstance.primaryTicks = expectedPrimary; + fixture.componentInstance.secondaryTicks = expectedSecondary; + fixture.detectChanges(); + + const primaryTicks = ticks.nativeElement + .querySelectorAll(SLIDER_PRIMARY_GROUP_TICKS_CLASS); + expect(primaryTicks.length).toEqual(expectedPrimary); + + const secondaryTicks = ticks.nativeElement + .querySelectorAll(`${SLIDER_GROUP_TICKS_CLASS}:not(${SLIDER_PRIMARY_GROUP_TICKS_CLASS})`); + expect(secondaryTicks.length).toEqual((expectedPrimary - 1) * expectedSecondary); + }); + + it('hide/show top and bottom ticks', () => { + let ticks = fixture.debugElement.nativeElement.querySelector(SLIDER_TICKS_ELEMENT); + let ticksTop = fixture.debugElement.nativeElement.querySelector(SLIDER_TICKS_TOP_ELEMENT); + + expect(ticks).not.toBeNull(); + expect(ticksTop).toBeNull(); + + fixture.componentInstance.showTicks = false; + fixture.detectChanges(); + + ticks = fixture.debugElement.nativeElement.querySelector(SLIDER_TICKS_ELEMENT); + ticksTop = fixture.debugElement.nativeElement.querySelector(SLIDER_TICKS_TOP_ELEMENT); + + expect(ticks).toBeNull(); + expect(ticksTop).toBeNull(); + + fixture.componentInstance.showTicks = true; + fixture.componentInstance.ticksOrientation = TicksOrientation.mirror; + fixture.detectChanges(); + + ticks = fixture.debugElement.nativeElement.querySelector(SLIDER_TICKS_ELEMENT); + ticksTop = fixture.debugElement.nativeElement.querySelector(SLIDER_TICKS_TOP_ELEMENT); + expect(ticks).not.toBeNull(); + expect(ticksTop).not.toBeNull(); + + fixture.componentInstance.showTicks = false; + fixture.detectChanges(); + + ticks = fixture.debugElement.nativeElement.querySelector(SLIDER_TICKS_ELEMENT); + ticksTop = fixture.debugElement.nativeElement.querySelector(SLIDER_TICKS_TOP_ELEMENT); + expect(ticks).toBeNull(); + expect(ticksTop).toBeNull(); + }); + + it('show/hide primary tick labels', () => { + const ticks = fixture.debugElement.query(By.css(SLIDER_TICKS_ELEMENT)); + const primaryTicks = 5; + const secondaryTicks = 3; + fixture.componentInstance.primaryTicks = primaryTicks; + fixture.componentInstance.secondaryTicks = secondaryTicks; + fixture.detectChanges(); + + verifyPrimaryTicsLabelsAreHidden(ticks, false); + verifySecondaryTicsLabelsAreHidden(ticks, false); + + fixture.componentInstance.primaryTickLabels = false; + fixture.detectChanges(); + + verifyPrimaryTicsLabelsAreHidden(ticks, true); + verifySecondaryTicsLabelsAreHidden(ticks, false); + + fixture.componentInstance.primaryTickLabels = true; + fixture.detectChanges(); + + verifyPrimaryTicsLabelsAreHidden(ticks, false); + verifySecondaryTicsLabelsAreHidden(ticks, false); + }); + + it('show/hide secondary tick labels', () => { + const ticks = fixture.debugElement.query(By.css(SLIDER_TICKS_ELEMENT)); + const primaryTicks = 5; + const secondaryTicks = 3; + fixture.componentInstance.primaryTicks = primaryTicks; + fixture.componentInstance.secondaryTicks = secondaryTicks; + fixture.detectChanges(); + + verifyPrimaryTicsLabelsAreHidden(ticks, false); + verifySecondaryTicsLabelsAreHidden(ticks, false); + + fixture.componentInstance.secondaryTickLabels = false; + fixture.detectChanges(); + + verifyPrimaryTicsLabelsAreHidden(ticks, false); + verifySecondaryTicsLabelsAreHidden(ticks, true); + + fixture.componentInstance.secondaryTickLabels = true; + fixture.detectChanges(); + verifyPrimaryTicsLabelsAreHidden(ticks, false); + verifySecondaryTicsLabelsAreHidden(ticks, false); + }); + + it('change ticks orientation (top, bottom, mirror)', () => { + let bottomTicks = fixture.debugElement.nativeElement + .querySelector(`${SLIDER_TICKS_ELEMENT}:not(${SLIDER_TICKS_TOP_ELEMENT})`); + + expect(bottomTicks).not.toBeNull(); + + fixture.componentInstance.ticksOrientation = TicksOrientation.top; + fixture.detectChanges(); + + let topTIcks = fixture.debugElement.nativeElement.querySelector(SLIDER_TICKS_TOP_ELEMENT); + bottomTicks = fixture.debugElement.nativeElement + .querySelector(`${SLIDER_TICKS_ELEMENT}:not(${SLIDER_TICKS_TOP_ELEMENT})`); + expect(topTIcks).not.toBeNull(); + expect(bottomTicks).toBeNull(); + + fixture.componentInstance.ticksOrientation = TicksOrientation.mirror; + fixture.detectChanges(); + + topTIcks = fixture.debugElement.nativeElement.querySelector(SLIDER_TICKS_TOP_ELEMENT); + bottomTicks = fixture.debugElement.nativeElement + .querySelector(`${SLIDER_TICKS_ELEMENT}:not(${SLIDER_TICKS_TOP_ELEMENT})`); + expect(topTIcks).not.toBeNull(); + expect(bottomTicks).not.toBeNull(); + }); + + it('change ticks label orientation (horizontal, toptobottom, bottomtotop)', () => { + fixture.componentInstance.primaryTicks = 5; + const nativeElem = fixture.debugElement.nativeElement; + fixture.detectChanges(); + + let labelsTopBottom = nativeElem.querySelector(TOP_TO_BOTTOM_TICK_LABLES); + let labelsBottomTop = nativeElem.querySelector(BOTTOM_TO_TOP_TICK_LABLES); + expect(labelsBottomTop).toBeNull(); + expect(labelsTopBottom).toBeNull(); + + fixture.componentInstance.tickLabelsOrientation = TickLabelsOrientation.bottomtotop; + fixture.detectChanges(); + + labelsBottomTop = nativeElem.querySelector(BOTTOM_TO_TOP_TICK_LABLES); + labelsTopBottom = nativeElem.querySelector(TOP_TO_BOTTOM_TICK_LABLES); + expect(labelsBottomTop).not.toBeNull(); + expect(labelsTopBottom).toBeNull(); + + fixture.componentInstance.tickLabelsOrientation = TickLabelsOrientation.toptobottom; + fixture.detectChanges(); + + labelsBottomTop = nativeElem.querySelector(BOTTOM_TO_TOP_TICK_LABLES); + labelsTopBottom = nativeElem.querySelector(TOP_TO_BOTTOM_TICK_LABLES); + expect(labelsTopBottom).not.toBeNull(); + expect(labelsBottomTop).toBeNull(); + }); + }); + describe('EditorProvider', () => { it('Should return correct edit element (single)', () => { const fixture = TestBed.createComponent(SliderInitializeTestComponent); @@ -1356,7 +1555,52 @@ describe('IgxSlider', () => { }); }); } + + function verifySecondaryTicsLabelsAreHidden(ticks, hidden) { + const allTicks = Array.from(ticks.nativeElement.querySelectorAll(`${SLIDER_GROUP_TICKS_CLASS}`)); + const secondaryTicks = allTicks.filter((tick: any) => + !tick.classList.contains(SLIDER_PRIMARY_GROUP_TICKS_CLASS_NAME) + ); + secondaryTicks.forEach(tick => { + const label = (tick as HTMLElement).querySelector(SLIDER_TICK_LABELS_CLASS); + expect(label.classList.contains(SLIDER_TICK_LABELS_HIDDEN_CLASS)).toEqual(hidden); + }); + } + + function verifyPrimaryTicsLabelsAreHidden(ticks, hidden) { + const primaryTicks = ticks.nativeElement.querySelectorAll(`${SLIDER_PRIMARY_GROUP_TICKS_CLASS}`); + primaryTicks.forEach(tick => { + const label = (tick as HTMLElement).querySelector(SLIDER_TICK_LABELS_CLASS); + expect(label.classList.contains(SLIDER_TICK_LABELS_HIDDEN_CLASS)).toEqual(hidden); + }); + } }); + +@Component({ + selector: 'igx-slider-ticks', + template: ` + + ` +}) +export class SliderTicksComponent { + @ViewChild(IgxSliderComponent) + public slider: IgxSliderComponent; + + public primaryTicks = 0; + public secondaryTicks = 0; + public showTicks = true; + public ticksOrientation = TicksOrientation.bottom; + public primaryTickLabels = true; + public secondaryTickLabels = true; + public tickLabelsOrientation = TickLabelsOrientation.horizontal; +} @Component({ selector: 'igx-slider-test-component', template: ` @@ -1368,7 +1612,7 @@ class SliderInitializeTestComponent { @Component({ template: ` - + ` }) export class SliderMinMaxComponent { diff --git a/projects/igniteui-angular/src/lib/slider/slider.component.ts b/projects/igniteui-angular/src/lib/slider/slider.component.ts index f156062f60b..4f752bc0709 100644 --- a/projects/igniteui-angular/src/lib/slider/slider.component.ts +++ b/projects/igniteui-angular/src/lib/slider/slider.component.ts @@ -10,23 +10,30 @@ import { ViewChildren, QueryList, ChangeDetectorRef, - AfterContentChecked + AfterContentChecked, + NgZone, + OnChanges } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; import { EditorProvider } from '../core/edit-provider'; import { DeprecateProperty } from '../core/deprecateDecorators'; import { IgxSliderThumbComponent } from './thumb/thumb-slider.component'; import { Subject, merge, Observable, timer } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { takeUntil, retry } from 'rxjs/operators'; import { SliderHandle, IgxThumbFromTemplateDirective, IgxThumbToTemplateDirective, IRangeSliderValue, SliderType, - ISliderValueChangeEventArgs + ISliderValueChangeEventArgs, + TicksOrientation, + TickLabelsOrientation, + IgxTickLabelTemplateDirective } from './slider.common'; import { IgxThumbLabelComponent } from './label/thumb-label.component'; - +import { IgxTicksComponent } from './ticks/ticks.component'; +import { IgxTickLabelsPipe } from './ticks/tick.pipe'; +import { HammerModule } from '@angular/platform-browser'; const noop = () => { }; @@ -59,6 +66,7 @@ export class IgxSliderComponent implements OnInit, AfterViewInit, AfterContentChecked, + OnChanges, OnDestroy { // Limit handle travel zone @@ -77,23 +85,21 @@ export class IgxSliderComponent implements private _disabled = false; private _step = 1; + // ticks + private _primaryTicks = 0; + private _secondaryTicks = 0; + private _labels = new Array(); private _type = SliderType.SLIDER; private _destroyer$ = new Subject(); private _indicatorsDestroyer$ = new Subject(); private _indicatorsTimer: Observable; - + private _onTypeChanged: Subject = new Subject(); private _onChangeCallback: (_: any) => void = noop; private _onTouchedCallback: () => void = noop; - /** - * @hidden - */ - @ViewChild('track', { static: true }) - private track: ElementRef; - /** * @hidden */ @@ -128,6 +134,12 @@ export class IgxSliderComponent implements return this.labelRefs.find(label => label.type === SliderHandle.TO); } + /** + * @hidden + */ + @ViewChild('track', { static: true }) + public trackRef: ElementRef; + /** * @hidden */ @@ -150,6 +162,12 @@ export class IgxSliderComponent implements @ContentChild(IgxThumbToTemplateDirective, { read: TemplateRef }) public thumbToTemplateRef: TemplateRef; + /** + * @hidden + */ + @ContentChild(IgxTickLabelTemplateDirective, { read: TemplateRef, static: false }) + public tickLabelTemplateRef: TemplateRef; + /** * @hidden */ @@ -242,6 +260,9 @@ export class IgxSliderComponent implements if (this._hasViewInit) { this.updateTrack(); } + + this._cdr.detectChanges(); + this._onTypeChanged.next(type); } /** @@ -268,12 +289,14 @@ export class IgxSliderComponent implements public set labels(labels: Array) { this._labels = labels; - this._pMax = 1; + this._pMax = this.valueToFraction(this.upperBound, 0, 1); + this._pMin = this.valueToFraction(this.lowerBound, 0, 1); + + this.stepDistance = this.calculateStepDistance(); + this.positionHandlesAndUpdateTrack(); if (this._hasViewInit) { - this.stepDistance = this.calculateStepDistance(); - this.positionHandlesAndUpdateTrack(); - this.setTickInterval(labels); + this.setTickInterval(); } } @@ -281,9 +304,10 @@ export class IgxSliderComponent implements * Returns the template context corresponding * to {@link IgxThumbFromTemplateDirective} and {@link IgxThumbToTemplateDirective} templates. * + * ```typescript * return { - * $implicit: {@link value}, - * labels: {@link labels} + * $implicit // returns the value of the label, + * labels // returns the labels collection the user has passed. * } * ``` */ @@ -308,7 +332,7 @@ export class IgxSliderComponent implements if (this._hasViewInit) { this.stepDistance = this.calculateStepDistance(); this.normalizeByStep(this.value); - this.setTickInterval(this.labels); + this.setTickInterval(); } } @@ -381,7 +405,7 @@ export class IgxSliderComponent implements public set continuous(continuous: boolean) { this._continuous = continuous; if (this._hasViewInit) { - this.setTickInterval(null); + this.setTickInterval(); } } @@ -409,52 +433,6 @@ export class IgxSliderComponent implements this.continuous = continuous; } - /** - * Returns the maximum value for the {@link IgxSliderComponent}. - * ```typescript - *@ViewChild("slider") - *public slider: IgxSliderComponent; - *ngAfterViewInit(){ - * let sliderMax = this.slider.maxValue; - *} - * ``` - */ - public get maxValue(): number { - return this.labelsViewEnabled ? - this.labels.length - 1 : - this._maxValue; - } - - /** - * Sets the maximal value for the `IgxSliderComponent`. - * The default maximum value is 100. - * ```html - * - * ``` - */ - @Input() - public set maxValue(value: number) { - if (value <= this._minValue) { - this._maxValue = this._minValue + 1; - } else { - this._maxValue = value; - } - - if (value < this.lowerBound) { - this.updateLowerBoundAndMinTravelZone(); - this.upperBound = value; - } - - // refresh max travel zone limits. - this._pMax = 1; - // recalculate step distance. - this.stepDistance = this.calculateStepDistance(); - this.positionHandlesAndUpdateTrack(); - if (this._hasViewInit) { - this.setTickInterval(null); - } - } - /** *Returns the minimal value of the `IgxSliderComponent`. *```typescript @@ -483,7 +461,7 @@ export class IgxSliderComponent implements @Input() public set minValue(value: number) { if (value >= this.maxValue) { - this._minValue = this.maxValue - 1; + return; } else { this._minValue = value; } @@ -499,7 +477,53 @@ export class IgxSliderComponent implements this.stepDistance = this.calculateStepDistance(); this.positionHandlesAndUpdateTrack(); if (this._hasViewInit) { - this.setTickInterval(null); + this.setTickInterval(); + } + } + + /** + * Returns the maximum value for the {@link IgxSliderComponent}. + * ```typescript + *@ViewChild("slider") + *public slider: IgxSliderComponent; + *ngAfterViewInit(){ + * let sliderMax = this.slider.maxValue; + *} + * ``` + */ + public get maxValue(): number { + return this.labelsViewEnabled ? + this.labels.length - 1 : + this._maxValue; + } + + /** + * Sets the maximal value for the `IgxSliderComponent`. + * The default maximum value is 100. + * ```html + * + * ``` + */ + @Input() + public set maxValue(value: number) { + if (value <= this._minValue) { + return; + } else { + this._maxValue = value; + } + + if (value < this.lowerBound) { + this.updateLowerBoundAndMinTravelZone(); + this.upperBound = value; + } + + // refresh max travel zone limits. + this._pMax = 1; + // recalculate step distance. + this.stepDistance = this.calculateStepDistance(); + this.positionHandlesAndUpdateTrack(); + if (this._hasViewInit) { + this.setTickInterval(); } } @@ -536,8 +560,8 @@ export class IgxSliderComponent implements this._lowerBound = this.valueInRange(value, this.minValue, this.maxValue); - // Refresh time travel zone. - this._pMin = this.valueToFraction(this._lowerBound) || 0; + // Refresh min travel zone. + this._pMin = this.valueToFraction(this._lowerBound, 0, 1); this.positionHandlesAndUpdateTrack(); } @@ -574,7 +598,7 @@ export class IgxSliderComponent implements this._upperBound = this.valueInRange(value, this.minValue, this.maxValue); // Refresh time travel zone. - this._pMax = this.valueToFraction(this._upperBound) || 1; + this._pMax = this.valueToFraction(this._upperBound, 0, 1); this.positionHandlesAndUpdateTrack(); } @@ -619,7 +643,7 @@ export class IgxSliderComponent implements @Input() public set value(value: number | IRangeSliderValue) { if (!this.isRange) { - this.upperValue = value as number; + this.upperValue = value as number - (value as number % this.step); } else { value = this.validateInitialValue(value as IRangeSliderValue); this.upperValue = (value as IRangeSliderValue).upper; @@ -633,6 +657,120 @@ export class IgxSliderComponent implements } } + /** + * Returns the number of the presented primary ticks. + * ```typescript + * const primaryTicks = this.slider.primaryTicks; + * ``` + */ + @Input() + public get primaryTicks() { + if (this.labelsViewEnabled) { + return this._primaryTicks = this.labels.length; + } + return this._primaryTicks; + } + + /** + * Sets the number of primary ticks. If {@link @labels} is enabled, this property won't function. + * Insted enable ticks by {@link showTicks} property. + * ```typescript + * this.slider.primaryTicks = 5; + * ``` + */ + public set primaryTicks(val: number) { + if (val <= 1) { + return; + } + + this._primaryTicks = val; + } + + /** + * Returns the number of the presented secondary ticks. + * ```typescript + * const secondaryTicks = this.slider.secondaryTicks; + * ``` + */ + @Input() + public get secondaryTicks() { + return this._secondaryTicks; + } + + /** + * Sets the number of secondary ticks. The property functions even when {@link labels} is enabled, + * but all secondary ticks won't present any tick labels. + * ```typescript + * this.slider.secondaryTicks = 5; + * ``` + */ + public set secondaryTicks(val: number) { + if (val <= 1 ) { + return; + } + + this._secondaryTicks = val; + } + + /** + * Show/hide slider ticks + * ```html + * + * ``` + */ + @Input() + public showTicks = false; + + /** + * show/hide primary tick labels + * ```html + * + * ``` + */ + @Input() + public primaryTickLabels = true; + + /** + * show/hide secondary tick labels + * ```html + * + * ``` + */ + @Input() + public secondaryTickLabels = true; + + /** + * Changes ticks orientation: + * bottom - The default orienation, below the slider track. + * top - Above the slider track + * mirror - combines top and bottom orientation. + * ```html + * + * ``` + */ + @Input() + public ticksOrientation: TicksOrientation = TicksOrientation.bottom; + + /** + * Changes tick labels rotation: + * horizontal - The default rotation + * toptobottom - Rotates tick labels vertically to 90deg + * bottomtotop - Rotate tick labels vertically to -90deg + * ```html + * + * ``` + */ + @Input() + public tickLabelsOrientation = TickLabelsOrientation.horizontal; + + /** + * @hidden + */ + public get deactivateThumbLabel() { + return ((this.primaryTicks && this.primaryTickLabels) || (this.secondaryTicks && this.secondaryTickLabels)) && + (this.ticksOrientation === TicksOrientation.top || this.ticksOrientation === TicksOrientation.mirror); + } + /** * This event is emitted when user has stopped interacting the thumb and value is changed. * ```typescript @@ -648,7 +786,10 @@ export class IgxSliderComponent implements public onValueChange = new EventEmitter(); - constructor(private renderer: Renderer2, private _el: ElementRef, private _cdr: ChangeDetectorRef) { } + constructor( + private renderer: Renderer2, + private _el: ElementRef, + private _cdr: ChangeDetectorRef) { } /** * @hidden @@ -700,11 +841,17 @@ export class IgxSliderComponent implements this.update($event.srcEvent.clientX); } + /** + * @hidden + */ @HostListener('panstart') public onPanStart() { this.showSliderIndicators(); } + /** + * @hidden + */ @HostListener('panend') public onPanEnd() { this.hideSliderIndicators(); @@ -832,6 +979,22 @@ export class IgxSliderComponent implements return !!(this.labels && this.labels.length > 1); } + /** + * @hidden + */ + public get showTopTicks() { + return this.ticksOrientation === TicksOrientation.top || + this.ticksOrientation === TicksOrientation.mirror; + } + + /** + * @hidden + */ + public get showBottomTicks() { + return this.ticksOrientation === TicksOrientation.bottom || + this.ticksOrientation === TicksOrientation.mirror; + } + /** * @hidden */ @@ -843,20 +1006,37 @@ export class IgxSliderComponent implements this._pMax = this.valueToFraction(this.upperBound) || 1; } + public ngOnChanges(changes) { + if (changes.minValue && changes.maxValue && + changes.minValue.currentValue < changes.maxValue.currentValue) { + this._maxValue = changes.maxValue.currentValue; + this._minValue = changes.minValue.currentValue; + } + } + /** * @hidden */ public ngAfterViewInit() { this._hasViewInit = true; this.positionHandlesAndUpdateTrack(); - this.setTickInterval(this.labels); + this.setTickInterval(); this.changeThumbFocusableState(this.disabled); this.subscribeTo(this.thumbFrom, this.thumbChanged.bind(this)); this.subscribeTo(this.thumbTo, this.thumbChanged.bind(this)); - this.thumbs.changes.pipe(takeUntil(this._destroyer$)).subscribe(change => { - const thumbFrom = change.find((thumb: IgxSliderThumbComponent) => thumb.type === SliderHandle.FROM); + // Implementing a workaround in regards of the following bug: https://github.com/angular/angular/issues/30088 + // this.thumbs.changes.pipe(takeUntil(this._destroyer$)).subscribe(change => { + // const thumbFrom = change.find((thumb: IgxSliderThumbComponent) => thumb.type === SliderHandle.FROM); + // const labelFrom = this.labelRefs.find((label: IgxThumbLabelComponent) => label.type === SliderHandle.FROM); + // this.positionHandle(thumbFrom, labelFrom, this.lowerValue); + // // this.subscribeTo(thumbFrom, this.thumbChanged.bind(this)); + // this.changeThumbFocusableState(this.disabled); + // }); + + this._onTypeChanged.pipe(takeUntil(this._destroyer$)).subscribe((type: SliderType) => { + const thumbFrom = this.thumbs.find((thumb: IgxSliderThumbComponent) => thumb.type === SliderHandle.FROM); const labelFrom = this.labelRefs.find((label: IgxThumbLabelComponent) => label.type === SliderHandle.FROM); this.positionHandle(thumbFrom, labelFrom, this.lowerValue); this.subscribeTo(thumbFrom, this.thumbChanged.bind(this)); @@ -909,7 +1089,7 @@ export class IgxSliderComponent implements } /** @hidden */ - public getEditElement() { + public getEditElement() { return this.isRange ? this.thumbFrom.nativeElement : this.thumbTo.nativeElement; } @@ -974,10 +1154,15 @@ export class IgxSliderComponent implements this.toggleSliderIndicators(); } + /** + * @hidden + */ public onHoverChange(state: boolean) { return state ? this.showSliderIndicators() : this.hideSliderIndicators(); } + + private swapThumb(value: IRangeSliderValue) { if (this.thumbFrom.isActive) { value.upper = this.upperValue; @@ -988,7 +1173,6 @@ export class IgxSliderComponent implements } this.toggleThumb(); - return value; } @@ -1016,8 +1200,8 @@ export class IgxSliderComponent implements /** * if {@link SliderType.SLIDER} than the initial value shold be the lowest one. */ - if (!this.isRange && this.value === this.upperBound) { - this.value = this.lowerBound; + if (!this.isRange && this._upperValue === undefined) { + this._upperValue = this.lowerBound; } } @@ -1093,7 +1277,7 @@ export class IgxSliderComponent implements } } - private setTickInterval(labels) { + private setTickInterval() { let interval; const trackProgress = 100; if (this.labelsViewEnabled) { @@ -1205,9 +1389,9 @@ export class IgxSliderComponent implements trackLeftIndention = Math.round((1 / positionGap * fromPosition) * 100); } - this.renderer.setStyle(this.track.nativeElement, 'transform', `scaleX(${positionGap}) translateX(${trackLeftIndention}%)`); + this.renderer.setStyle(this.trackRef.nativeElement, 'transform', `scaleX(${positionGap}) translateX(${trackLeftIndention}%)`); } else { - this.renderer.setStyle(this.track.nativeElement, 'transform', `scaleX(${toPosition})`); + this.renderer.setStyle(this.trackRef.nativeElement, 'transform', `scaleX(${toPosition})`); } } @@ -1266,15 +1450,20 @@ export class IgxSliderComponent implements IgxSliderComponent, IgxThumbFromTemplateDirective, IgxThumbToTemplateDirective, + IgxTickLabelTemplateDirective, IgxSliderThumbComponent, - IgxThumbLabelComponent], + IgxThumbLabelComponent, + IgxTicksComponent, + IgxTickLabelsPipe], exports: [ IgxSliderComponent, IgxThumbFromTemplateDirective, IgxThumbToTemplateDirective, + IgxTickLabelTemplateDirective, IgxSliderThumbComponent, - IgxThumbLabelComponent], - imports: [CommonModule] + IgxThumbLabelComponent, + IgxTicksComponent], + imports: [CommonModule, FormsModule] }) export class IgxSliderModule { } diff --git a/projects/igniteui-angular/src/lib/slider/thumb/thumb-slider.component.ts b/projects/igniteui-angular/src/lib/slider/thumb/thumb-slider.component.ts index 4ded53b004a..9374f7e571c 100644 --- a/projects/igniteui-angular/src/lib/slider/thumb/thumb-slider.component.ts +++ b/projects/igniteui-angular/src/lib/slider/thumb/thumb-slider.component.ts @@ -65,6 +65,9 @@ export class IgxSliderThumbComponent implements OnInit, OnDestroy { @Input() public type: SliderHandle; + @Input() + public deactiveState: boolean; + @Output() public onThumbValueChange = new EventEmitter(); @@ -217,8 +220,11 @@ export class IgxSliderThumbComponent implements OnInit, OnDestroy { private toggleThumbIndicators(visible: boolean) { this._isPressed = visible; - if (!this.continuous) { + if (this.continuous || this.deactiveState) { + this._isActive = false; + } else { this._isActive = visible; } + } } diff --git a/projects/igniteui-angular/src/lib/slider/ticks/tick.pipe.ts b/projects/igniteui-angular/src/lib/slider/ticks/tick.pipe.ts new file mode 100644 index 00000000000..e749ab55e3b --- /dev/null +++ b/projects/igniteui-angular/src/lib/slider/ticks/tick.pipe.ts @@ -0,0 +1,24 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'spreadTickLabels' +}) +export class IgxTickLabelsPipe implements PipeTransform { + + + public transform(labels: Array, secondaryTicks: number) { + if (!labels) { + return; + } + + const result = []; + labels.forEach(item => { + result.push(item); + for (let i = 0; i < secondaryTicks; i++) { + result.push(''); + } + }); + + return result; + } +} diff --git a/projects/igniteui-angular/src/lib/slider/ticks/ticks.component.html b/projects/igniteui-angular/src/lib/slider/ticks/ticks.component.html new file mode 100644 index 00000000000..f43243e821b --- /dev/null +++ b/projects/igniteui-angular/src/lib/slider/ticks/ticks.component.html @@ -0,0 +1,11 @@ +
+
+ + + +
+
+ + + {{ value }} + diff --git a/projects/igniteui-angular/src/lib/slider/ticks/ticks.component.ts b/projects/igniteui-angular/src/lib/slider/ticks/ticks.component.ts new file mode 100644 index 00000000000..32cb997dd57 --- /dev/null +++ b/projects/igniteui-angular/src/lib/slider/ticks/ticks.component.ts @@ -0,0 +1,141 @@ +import { Component, Input, ElementRef, AfterViewInit, TemplateRef, HostBinding } from '@angular/core'; +import { TicksOrientation, TickLabelsOrientation } from '../slider.common'; + +/** + * @hidden + */ +@Component({ + selector: 'igx-ticks', + templateUrl: 'ticks.component.html', +}) +export class IgxTicksComponent { + @Input() + public primaryTicks: number; + + @Input() + public secondaryTicks: number; + + @Input() + public primaryTickLabels: boolean; + + @Input() + public secondaryTickLabels: boolean; + + @Input() + public ticksOrientation: TicksOrientation; + + @Input() + public tickLabelsOrientation: TickLabelsOrientation; + + @Input() + public maxValue: number; + + @Input() + public minValue: number; + + @Input() + public labelsViewEnabled: boolean; + + @Input() + public labels: Array; + + @Input() + public tickLabelTemplateRef: TemplateRef; + + /** + * @hidden + */ + @HostBinding('class.igx-slider__ticks') + public ticksClass = true; + + /** + * @hidden + */ + @HostBinding('class.igx-slider__ticks--top') + public get ticksTopClass() { + return this.ticksOrientation === TicksOrientation.top; + } + + /** + * @hidden + */ + @HostBinding('class.igx-slider__ticks--tall') + public get hasPrimaryClass() { + return this.primaryTicks > 0; + } + + /** + * @hidden + */ + @HostBinding('class.igx-slider__tick-labels--top-bottom') + public get labelsTopToBottomClass() { + return this.tickLabelsOrientation === TickLabelsOrientation.toptobottom; + } + + /** + * @hidden + */ + @HostBinding('class.igx-slider__tick-labels--bottom-top') + public get labelsBottomToTopClass() { + return this.tickLabelsOrientation === TickLabelsOrientation.bottomtotop; + } + + /** + * Returns the template context corresponding to + * {@link IgxTickLabelTemplateDirective} + * + * ```typescript + * return { + * $implicit //returns the value per each tick label. + * isPrimery //returns if the tick is primary. + * labels // returns the {@link labels} collection. + * index // returns the index per each tick of the whole sequence. + * } + * ``` + * + * @param idx the index per each tick label. + */ + public context(idx: number): any { + return { + $implicit: this.tickLabel(idx), + isPrimary: this.isPrimary(idx), + labels: this.labels, + index: idx + }; + } + + /** + * @hidden + */ + public get ticksLength() { + return this.primaryTicks > 0 ? + ((this.primaryTicks - 1) * this.secondaryTicks) + this.primaryTicks : + this.secondaryTicks > 0 ? this.secondaryTicks : 0; + } + + public hiddenTickLabels(idx: number) { + return this.isPrimary(idx) ? this.primaryTickLabels : this.secondaryTickLabels; + } + + /** + * @hidden + */ + public isPrimary(idx: number) { + return this.primaryTicks <= 0 ? false : + idx % (this.secondaryTicks + 1) === 0; + } + + /** + * @hidden + */ + public tickLabel(idx: number) { + if (this.labelsViewEnabled) { + return this.labels[idx]; + } + + const labelStep = (Math.max(this.minValue, this.maxValue) - Math.min(this.minValue, this.maxValue)) / (this.ticksLength - 1); + const labelVal = labelStep * idx; + + return (this.minValue + labelVal).toFixed(2); + } +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ba2632a7333..4b2b039262e 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -236,7 +236,8 @@ const components = [ IgxDragDropModule, IgxDividerModule, SharedModule, - routing + routing, + HammerModule ], providers: [ LocalService, diff --git a/src/app/slider/slider.sample.html b/src/app/slider/slider.sample.html index 36261d82305..f4126fc30ed 100644 --- a/src/app/slider/slider.sample.html +++ b/src/app/slider/slider.sample.html @@ -1,17 +1,37 @@ -
+ +

Slider

-

Slider

- + +
+
+ + + + + + +
-
+ +

Range label slider

{{getLowerVal}} {{getUpperVal}}
-
+ --> -
+ + diff --git a/src/app/slider/slider.sample.scss b/src/app/slider/slider.sample.scss index 48de94d0a60..f5390cd2e0c 100644 --- a/src/app/slider/slider.sample.scss +++ b/src/app/slider/slider.sample.scss @@ -14,5 +14,16 @@ .outer-label { width: 20%; text-align: center; - padding-top: 10px + padding-top: 10px; +} + +.sample-component { + position: relative; + width: 100%; + padding-top: 70px; +} + +igx-slider { + width: 80%; + margin: 50px auto; } diff --git a/src/app/slider/slider.sample.ts b/src/app/slider/slider.sample.ts index 926a209cede..71e59b1f319 100644 --- a/src/app/slider/slider.sample.ts +++ b/src/app/slider/slider.sample.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { SliderType, ISliderValueChangeEventArgs, IRangeSliderValue } from 'igniteui-angular'; +import { SliderType, ISliderValueChangeEventArgs, IRangeSliderValue, TickLabelsOrientation, TicksOrientation } from 'igniteui-angular'; class Task { title: string; @@ -20,12 +20,18 @@ export class SliderSampleComponent { private _lowerValue: Date; private _upperValue: Date; + public labelOrientaion = TickLabelsOrientation.horizontal; + public ticksOrientation = TicksOrientation.bottom; + public primaryTickLabels = true; + public secondaryTickLabels = true; public sliderType: SliderType = SliderType.RANGE; - public labels = new Array(); + public labelsDates = new Array(); + public task: Task = new Task('Implement new app', 30); + public labels = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; public rangeValue = { - lower: 30, - upper: 60 + lower: 34, + upper: 67 }; public rangeLabel = { @@ -35,11 +41,11 @@ export class SliderSampleComponent { constructor() { for (let i = 0; i <= 500; i++) { - this.labels.push(new Date(2019, 10, i)); + this.labelsDates.push(new Date(2019, 10, i)); } - this._lowerValue = this.labels[0]; - this._upperValue = this.labels[this.labels.length - 1]; + this._lowerValue = this.labelsDates[0]; + this._upperValue = this.labelsDates[this.labels.length - 1]; } public get getLowerVal() { @@ -51,9 +57,40 @@ export class SliderSampleComponent { } public valueChange(evt: ISliderValueChangeEventArgs) { - this._lowerValue = this.labels[(evt.value as IRangeSliderValue).lower]; - this._upperValue = this.labels[(evt.value as IRangeSliderValue).upper]; + this._lowerValue = this.labelsDates[(evt.value as IRangeSliderValue).lower]; + this._upperValue = this.labelsDates[(evt.value as IRangeSliderValue).upper]; + } + + public changeLabels() { + this.labels = new Array('asd', 'bsd'); + } + + public changeLabelOrientation() { + if (this.labelOrientaion === TickLabelsOrientation.horizontal) { + this.labelOrientaion = TickLabelsOrientation.toptobottom; + } else if(this.labelOrientaion === TickLabelsOrientation.toptobottom) { + this.labelOrientaion = TickLabelsOrientation.bottomtotop; + } else { + this.labelOrientaion = TickLabelsOrientation.horizontal; + } + } + + public changeTicksOrientation() { + if (this.ticksOrientation === TicksOrientation.mirror) { + this.ticksOrientation = TicksOrientation.top; + } else if (this.ticksOrientation === TicksOrientation.top) { + this.ticksOrientation = TicksOrientation.bottom; + } else { + this.ticksOrientation = TicksOrientation.mirror; + } + } + + public tickLabel(value, primary, index, labels) { + if (primary) { + return Math.round(value); + } + + return value; } - task: Task = new Task('Implement new app', 30); }