diff --git a/components/core/dom/reverse.ts b/components/core/dom/reverse.ts deleted file mode 100644 index 5a739f9d4a4..00000000000 --- a/components/core/dom/reverse.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function reverseChildNodes(parent: HTMLElement): void { - const children = parent.childNodes; - let length = children.length; - if (length) { - const nodes: Node[] = []; - children.forEach((node, i) => nodes[ i ] = node); - while (length--) { - parent.appendChild(nodes[ length ]); - } - } -} diff --git a/components/core/style/map.ts b/components/core/style/map.ts deleted file mode 100644 index a344f42a3d5..00000000000 --- a/components/core/style/map.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function classMapToString(map: { [ key: string ]: boolean }): string { - return Object.keys(map).filter(item => !!map[ item ]).join(' '); -} diff --git a/components/core/util/array.ts b/components/core/util/array.ts index a05e9ffff17..a6c0302ba00 100644 --- a/components/core/util/array.ts +++ b/components/core/util/array.ts @@ -23,3 +23,7 @@ export function arrayEquals(array1: T[], array2: T[]): boolean { } return true; } + +export function shallowCopyArray(source: T[]): T[] { + return source.slice(); +} diff --git a/components/core/util/check.ts b/components/core/util/check.ts index 79ffa21dfdb..02c71003169 100644 --- a/components/core/util/check.ts +++ b/components/core/util/check.ts @@ -5,7 +5,9 @@ export function isNotNil(value: any): boolean { return (typeof(value) !== 'undefined') && value !== null; } -/** 校验对象是否相等 */ +/** + * Examine if two objects are shallowly equaled. + */ export function shallowEqual(objA: {}, objB: {}): boolean { if (objA === objB) { return true; diff --git a/components/core/util/dom.ts b/components/core/util/dom.ts index e2e5c6f8f42..61f486f5840 100644 --- a/components/core/util/dom.ts +++ b/components/core/util/dom.ts @@ -1,5 +1,28 @@ +import { Observable } from 'rxjs'; + import { filterNotEmptyNode } from './check'; +/** + * Silent an event by stopping and preventing it. + */ +export function silentEvent(e: Event): void { + e.stopPropagation(); + e.preventDefault(); +} + +export function getElementOffset(elem: HTMLElement): { top: number, left: number } { + if (!elem.getClientRects().length) { + return { top: 0, left: 0 }; + } + + const rect = elem.getBoundingClientRect(); + const win = elem.ownerDocument.defaultView; + return { + top : rect.top + win.pageYOffset, + left: rect.left + win.pageXOffset + }; +} + export function findFirstNotEmptyNode(element: HTMLElement): Node { const children = element.childNodes; for (let i = 0; i < children.length; i++) { @@ -20,4 +43,29 @@ export function findLastNotEmptyNode(element: HTMLElement): Node { } } return null; -} \ No newline at end of file +} + +export function reverseChildNodes(parent: HTMLElement): void { + const children = parent.childNodes; + let length = children.length; + if (length) { + const nodes: Node[] = []; + children.forEach((node, i) => nodes[ i ] = node); + while (length--) { + parent.appendChild(nodes[ length ]); + } + } +} + +export interface MouseTouchObserverConfig { + end: string; + move: string; + pluckKey: string[]; + start: string; + + end$?: Observable; + moveResolved$?: Observable; + startPlucked$?: Observable; + + filter?(e: Event): boolean; +} diff --git a/components/core/util/getMentions.ts b/components/core/util/getMentions.ts index d609bca212c..05f322cc389 100644 --- a/components/core/util/getMentions.ts +++ b/components/core/util/getMentions.ts @@ -1,4 +1,3 @@ - export function getRegExp(prefix: string | string[]): RegExp { const prefixArray = Array.isArray(prefix) ? prefix : [prefix]; let prefixToken = prefixArray.join('').replace(/(\$|\^)/g, '\\$1'); diff --git a/components/core/util/number.ts b/components/core/util/number.ts new file mode 100644 index 00000000000..946f97d832f --- /dev/null +++ b/components/core/util/number.ts @@ -0,0 +1,19 @@ +export function getPercent(min: number, max: number, value: number): number { + return (value - min) / (max - min) * 100; +} + +export function getPrecision(num: number): number { + const numStr = num.toString(); + const dotIndex = numStr.indexOf('.'); + return dotIndex >= 0 ? numStr.length - dotIndex - 1 : 0; +} + +export function ensureNumberInRange(num: number, min: number, max: number): number { + if (isNaN(num) || num < min) { + return min; + } else if (num > max) { + return max; + } else { + return num; + } +} diff --git a/components/slider/demo/tooltip.md b/components/slider/demo/tooltip.md new file mode 100644 index 00000000000..5bd7ac21b35 --- /dev/null +++ b/components/slider/demo/tooltip.md @@ -0,0 +1,16 @@ +--- +order: 7 +title: + zh-CN: 控制 Tooltip 的显示 + en-US: Control visibility of Tooltip +--- + +## zh-CN + +当 `nzTooltipVisible` 为 `always` 时,将始终显示 ToolTip,为 `never` 时反之则始终不显示,即使在拖动、移入时也是如此。 + +## en-US + +When `nzTooltipVisible` is `always`, Tooltip will show always. And set to `never`, tooltip would never show, even when user is dragging or hovering. + + diff --git a/components/slider/demo/tooltip.ts b/components/slider/demo/tooltip.ts new file mode 100644 index 00000000000..926c1f937d6 --- /dev/null +++ b/components/slider/demo/tooltip.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'nz-demo-slider-tooltip', + template: ` + + + ` +}) +export class NzDemoSliderTooltipComponent { +} diff --git a/components/slider/nz-slider-definitions.ts b/components/slider/nz-slider-definitions.ts new file mode 100644 index 00000000000..aa08a2a30fc --- /dev/null +++ b/components/slider/nz-slider-definitions.ts @@ -0,0 +1,60 @@ +export type Mark = string | MarkObj; + +export type MarkObj = { + style?: object; + label: string; +} + +export class Marks { + [ key: number ]: Mark; +} + +/** + * Marks that have been processed. + */ +export interface ExtendedMark { + value: number; + offset: number; + config: Mark; +} + +export interface DisplayedMark extends ExtendedMark { + active: boolean; + label: string; + style?: object; +} + +export class MarksArray extends Array<{ value: number, offset: number, config: Mark }> { + [ index: number ]: { + value: number; + offset: number; + config: Mark; + } +} + +export interface DisplayedStep extends ExtendedMark { + active: boolean; + style?: object; +} + +export type NzSliderShowTooltip = 'always' | 'never' | 'default'; + +export type SliderValue = number[] | number; + +export class SliderHandler { + offset: number; + value: number; + active: boolean; +} + +export function isValueARange(value: SliderValue): value is number[] { + if (value instanceof Array) { + return value.length === 2; + } else { + return false; + } +} + +export function isConfigAObject(config: Mark): config is MarkObj { + return config instanceof Object; +} diff --git a/components/slider/nz-slider-error.ts b/components/slider/nz-slider-error.ts new file mode 100644 index 00000000000..542146ec961 --- /dev/null +++ b/components/slider/nz-slider-error.ts @@ -0,0 +1,7 @@ +export function getValueTypeNotMatchError(): Error { + return new Error(`The "nzRange" can't match the "nzValue"'s type, please check these properties: "nzRange", "nzValue", "nzDefaultValue".`); +} + +export function getValueTypeNotLegalError(): Error { + return new Error(``); +} \ No newline at end of file diff --git a/components/slider/nz-slider-handle.component.html b/components/slider/nz-slider-handle.component.html index aa0f0c34e65..67ac65c7297 100644 --- a/components/slider/nz-slider-handle.component.html +++ b/components/slider/nz-slider-handle.component.html @@ -1,4 +1,8 @@ - -
+ +
-
\ No newline at end of file +
diff --git a/components/slider/nz-slider-handle.component.ts b/components/slider/nz-slider-handle.component.ts index 76073fd30a7..1dfb7f4200d 100644 --- a/components/slider/nz-slider-handle.component.ts +++ b/components/slider/nz-slider-handle.component.ts @@ -1,78 +1,126 @@ -import { Component, HostListener, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; +import { + AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, + Component, + ElementRef, + Input, + NgZone, + OnChanges, + OnDestroy, + SimpleChanges, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { fromEvent, Subscription } from 'rxjs'; -import { toBoolean } from '../core/util/convert'; +import { InputBoolean } from '../core/util/convert'; import { NzToolTipComponent } from '../tooltip/nz-tooltip.component'; +import { NzSliderShowTooltip } from './nz-slider-definitions'; import { NzSliderComponent } from './nz-slider.component'; @Component({ + changeDetection : ChangeDetectionStrategy.OnPush, + encapsulation : ViewEncapsulation.None, selector : 'nz-slider-handle', preserveWhitespaces: false, templateUrl : './nz-slider-handle.component.html' }) -export class NzSliderHandleComponent implements OnChanges { +export class NzSliderHandleComponent implements OnChanges, AfterViewInit, OnDestroy { + @ViewChild('tooltip') tooltip: NzToolTipComponent; - // Static properties - @Input() nzClassName: string; @Input() nzVertical: string; @Input() nzOffset: number; - @Input() nzValue: number; // [For tooltip] - @Input() nzTipFormatter: (value: number) => string; // [For tooltip] - @Input() set nzActive(value: boolean) { // [For tooltip] - const show = toBoolean(value); - if (this.tooltip) { - if (show) { - this.tooltip.show(); - } else { - this.tooltip.hide(); - } - } - } + @Input() nzValue: number; + @Input() nzTooltipVisible: NzSliderShowTooltip = 'default'; + @Input() nzTipFormatter: (value: number) => string; + @Input() @InputBoolean() nzActive = false; - // Locals - @ViewChild('tooltip') tooltip: NzToolTipComponent; // [For tooltip] - tooltipTitle: string; // [For tooltip] + tooltipTitle: string; style: object = {}; - constructor(private _slider: NzSliderComponent) { + private hovers_ = new Subscription(); + + constructor( + private sliderComponent: NzSliderComponent, + private ngZone: NgZone, + private el: ElementRef, + private cdr: ChangeDetectorRef + ) { } ngOnChanges(changes: SimpleChanges): void { - if (changes.nzOffset) { - this._updateStyle(); + const { nzOffset, nzValue, nzActive, nzTooltipVisible } = changes; + + if (nzOffset) { + this.updateStyle(); + } + if (nzValue) { + this.updateTooltipTitle(); + this.updateTooltipPosition(); } - if (changes.nzValue) { - this._updateTooltipTitle(); // [For tooltip] - this._updateTooltipPosition(); // [For tooltip] + if (nzActive) { + if (nzActive.currentValue) { + this.toggleTooltip(true); + } else { + this.toggleTooltip(false); + } + } + if (nzTooltipVisible && !nzTooltipVisible.isFirstChange()) { + this.tooltip.show(); } } - // Hover to toggle tooltip when not dragging - @HostListener('mouseenter', [ '$event' ]) - onMouseEnter($event: MouseEvent): void { - if (!this._slider.isDragging) { - this.nzActive = true; + ngAfterViewInit(): void { + if (this.nzTooltipVisible === 'always' && this.tooltip) { + Promise.resolve().then(() => this.tooltip.show()); } + + // NOTE: run outside of Angular for performance consideration. + this.ngZone.runOutsideAngular(() => { + this.hovers_.add(fromEvent(this.el.nativeElement, 'mouseenter').subscribe(() => { + if (!this.sliderComponent.isDragging) { + this.toggleTooltip(true); + this.updateTooltipPosition(); + this.cdr.detectChanges(); + } + })); + this.hovers_.add(fromEvent(this.el.nativeElement, 'mouseleave').subscribe(() => { + if (!this.sliderComponent.isDragging) { + this.toggleTooltip(false); + this.cdr.detectChanges(); + } + })); + }); } - @HostListener('mouseleave', [ '$event' ]) - onMouseLeave($event: MouseEvent): void { - if (!this._slider.isDragging) { - this.nzActive = false; + ngOnDestroy(): void { + this.hovers_.unsubscribe(); + } + + private toggleTooltip(show: boolean): void { + if (this.nzTooltipVisible !== 'default' || !this.tooltip) { + return; + } + + if (show) { + this.tooltip.show(); + } else { + this.tooltip.hide(); } } - private _updateTooltipTitle(): void { // [For tooltip] + private updateTooltipTitle(): void { this.tooltipTitle = this.nzTipFormatter ? this.nzTipFormatter(this.nzValue) : `${this.nzValue}`; } - private _updateTooltipPosition(): void { // [For tooltip] + private updateTooltipPosition(): void { if (this.tooltip) { - window.setTimeout(() => this.tooltip.updatePosition(), 0); // MAY use ngAfterViewChecked? but this will be called so many times. + Promise.resolve().then(() => this.tooltip.updatePosition()); } } - private _updateStyle(): void { + private updateStyle(): void { this.style[ this.nzVertical ? 'bottom' : 'left' ] = `${this.nzOffset}%`; } } diff --git a/components/slider/nz-slider-marks.component.html b/components/slider/nz-slider-marks.component.html index 1473911167b..946b2a3f76a 100644 --- a/components/slider/nz-slider-marks.component.html +++ b/components/slider/nz-slider-marks.component.html @@ -1,3 +1,10 @@ -
- +
+ +
\ No newline at end of file diff --git a/components/slider/nz-slider-marks.component.ts b/components/slider/nz-slider-marks.component.ts index 7e455aea9bb..6be1f6ed5ed 100644 --- a/components/slider/nz-slider-marks.component.ts +++ b/components/slider/nz-slider-marks.component.ts @@ -1,131 +1,95 @@ -import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core'; -import { toBoolean } from '../core/util/convert'; +import { InputBoolean } from '../core/util/convert'; + +import { isConfigAObject, DisplayedMark, ExtendedMark, Mark } from './nz-slider-definitions'; @Component({ - selector : 'nz-slider-marks', + changeDetection : ChangeDetectionStrategy.OnPush, + encapsulation : ViewEncapsulation.None, preserveWhitespaces: false, + selector : 'nz-slider-marks', templateUrl : './nz-slider-marks.component.html' }) export class NzSliderMarksComponent implements OnChanges { - private _vertical = false; - private _included = false; - - // Dynamic properties @Input() nzLowerBound: number = null; @Input() nzUpperBound: number = null; - @Input() nzMarksArray: MarksArray; - - // Static properties - @Input() nzClassName: string; - @Input() nzMin: number; // Required - @Input() nzMax: number; // Required - - @Input() - set nzVertical(value: boolean) { // Required - this._vertical = toBoolean(value); - } - - get nzVertical(): boolean { - return this._vertical; - } - - @Input() - set nzIncluded(value: boolean) { - this._included = toBoolean(value); - } - - get nzIncluded(): boolean { - return this._included; - } + @Input() nzMarksArray: ExtendedMark[]; + @Input() nzMin: number; + @Input() nzMax: number; + @Input() @InputBoolean() nzVertical = false; + @Input() @InputBoolean() nzIncluded = false; - // TODO: using named interface - attrs: Array<{ id: number, value: number, offset: number, classes: { [ key: string ]: boolean }, style: object, label: Mark }>; // points for inner use + marks: DisplayedMark[]; ngOnChanges(changes: SimpleChanges): void { if (changes.nzMarksArray) { - this.buildAttrs(); + this.buildMarks(); } if (changes.nzMarksArray || changes.nzLowerBound || changes.nzUpperBound) { this.togglePointActive(); } } - trackById(index: number, attr: { id: number, value: number, offset: number, classes: { [ key: string ]: boolean }, style: object, label: Mark }): number { - return attr.id; + trackById(index: number, mark: DisplayedMark): number { + return mark.value; } - buildAttrs(): void { + private buildMarks(): void { const range = this.nzMax - this.nzMin; - this.attrs = this.nzMarksArray.map(mark => { + + this.marks = this.nzMarksArray.map(mark => { const { value, offset, config } = mark; - // calc styles - let label = config; - let style: object; - if (this.nzVertical) { - style = { - marginBottom: '-50%', - bottom : `${(value - this.nzMin) / range * 100}%` - }; - } else { - const marksCount = this.nzMarksArray.length; - const unit = 100 / (marksCount - 1); - const markWidth = unit * 0.9; - style = { - width : `${markWidth}%`, - marginLeft: `${-markWidth / 2}%`, - left : `${(value - this.nzMin) / range * 100}%` - }; - } - // custom configuration - if (typeof config === 'object') { - label = config.label; - if (config.style) { - style = { ...style, ...config.style }; - } - } + const style = this.buildStyles(value, range, config); + const label = isConfigAObject(config) ? config.label : config; + return { - id : value, - value, + label, offset, - classes: { - [ `${this.nzClassName}-text` ]: true - }, style, - label + value, + config, + active: false }; - }); // END - map + }); } - togglePointActive(): void { - if (this.attrs && this.nzLowerBound !== null && this.nzUpperBound !== null) { - this.attrs.forEach(attr => { - const value = attr.value; - const isActive = (!this.nzIncluded && value === this.nzUpperBound) || - (this.nzIncluded && value <= this.nzUpperBound && value >= this.nzLowerBound); - attr.classes[ `${this.nzClassName}-text-active` ] = isActive; - }); - } - } + private buildStyles(value: number, range: number, config: Mark): { [ key: string ]: string } { + let style; -} + if (this.nzVertical) { + style = { + marginBottom: '-50%', + bottom : `${(value - this.nzMin) / range * 100}%` + }; + } else { + const marksCount = this.nzMarksArray.length; + const unit = 100 / (marksCount - 1); + const markWidth = unit * 0.9; + style = { + width : `${markWidth}%`, + marginLeft: `${-markWidth / 2}%`, + left : `${(value - this.nzMin) / range * 100}%` + }; + } -// DEFINITIONS + if (isConfigAObject(config) && config.style) { + style = { ...style, ...config.style }; + } -export type Mark = string | { - style: object; - label: string; -}; + return style; + } -export class Marks { - number: Mark; -} + private togglePointActive(): void { + if (this.marks && this.nzLowerBound !== null && this.nzUpperBound !== null) { + this.marks.forEach(mark => { + const value = mark.value; + const isActive = + (!this.nzIncluded && value === this.nzUpperBound) || + (this.nzIncluded && value <= this.nzUpperBound && value >= this.nzLowerBound); -// TODO: extends Array could cause unexpected behavior when targeting es5 or below -export class MarksArray extends Array<{ value: number, offset: number, config: Mark }> { - [ index: number ]: { - value: number; - offset: number; - config: Mark; + mark.active = isActive; + }); + } } } diff --git a/components/slider/nz-slider-step.component.html b/components/slider/nz-slider-step.component.html index 4855c1784e1..42d25084169 100644 --- a/components/slider/nz-slider-step.component.html +++ b/components/slider/nz-slider-step.component.html @@ -1,3 +1,8 @@ -
- +
+ +
\ No newline at end of file diff --git a/components/slider/nz-slider-step.component.ts b/components/slider/nz-slider-step.component.ts index 0f8a9f3a943..94a8eefe481 100644 --- a/components/slider/nz-slider-step.component.ts +++ b/components/slider/nz-slider-step.component.ts @@ -1,46 +1,24 @@ -import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core'; -import { toBoolean } from '../core/util/convert'; +import { InputBoolean } from '../core/util/convert'; -import { MarksArray } from './nz-slider-marks.component'; +import { DisplayedStep, MarksArray } from './nz-slider-definitions'; @Component({ + changeDetection : ChangeDetectionStrategy.OnPush, + encapsulation : ViewEncapsulation.None, selector : 'nz-slider-step', preserveWhitespaces: false, templateUrl : './nz-slider-step.component.html' }) export class NzSliderStepComponent implements OnChanges { - private _vertical = false; - private _included = false; - - // Dynamic properties @Input() nzLowerBound: number = null; @Input() nzUpperBound: number = null; @Input() nzMarksArray: MarksArray; + @Input() @InputBoolean() nzVertical = false; + @Input() @InputBoolean() nzIncluded = false; - // Static properties - @Input() nzPrefixCls: string; - - @Input() - set nzVertical(value: boolean) { // Required - this._vertical = toBoolean(value); - } - - get nzVertical(): boolean { - return this._vertical; - } - - @Input() - set nzIncluded(value: boolean) { - this._included = toBoolean(value); - } - - get nzIncluded(): boolean { - return this._included; - } - - // TODO: using named interface - attrs: Array<{ id: number, value: number, offset: number, classes: { [ key: string ]: boolean }, style: object }>; + marks: DisplayedStep[]; ngOnChanges(changes: SimpleChanges): void { if (changes.nzMarksArray) { @@ -51,37 +29,37 @@ export class NzSliderStepComponent implements OnChanges { } } - trackById(index: number, attr: { id: number, value: number, offset: number, classes: { [ key: string ]: boolean }, style: object }): number { - return attr.id; + trackById(index: number, step: DisplayedStep): number { + return step.value; } buildAttrs(): void { const orient = this.nzVertical ? 'bottom' : 'left'; - const prefixCls = this.nzPrefixCls; - this.attrs = this.nzMarksArray.map(mark => { - const { value, offset } = mark; + + this.marks = this.nzMarksArray.map(mark => { + const { value, offset, config } = mark; + return { - id : value, value, offset, - style : { + config, + active: false, + style : { [ orient ]: `${offset}%` - }, - classes: { - [ `${prefixCls}-dot` ] : true, - [ `${prefixCls}-dot-active` ]: false } }; }); } togglePointActive(): void { - if (this.attrs && this.nzLowerBound !== null && this.nzUpperBound !== null) { - this.attrs.forEach(attr => { + if (this.marks && this.nzLowerBound !== null && this.nzUpperBound !== null) { + this.marks.forEach(attr => { const value = attr.value; - const isActive = (!this.nzIncluded && value === this.nzUpperBound) || + const isActive = + (!this.nzIncluded && value === this.nzUpperBound) || (this.nzIncluded && value <= this.nzUpperBound && value >= this.nzLowerBound); - attr.classes[ `${this.nzPrefixCls}-dot-active` ] = isActive; + + attr.active = isActive; }); } } diff --git a/components/slider/nz-slider-track.component.html b/components/slider/nz-slider-track.component.html index ecd5161a7f8..9c01d454036 100644 --- a/components/slider/nz-slider-track.component.html +++ b/components/slider/nz-slider-track.component.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/components/slider/nz-slider-track.component.ts b/components/slider/nz-slider-track.component.ts index d1911ed116d..f089c26c51f 100644 --- a/components/slider/nz-slider-track.component.ts +++ b/components/slider/nz-slider-track.component.ts @@ -1,42 +1,29 @@ -import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core'; -import { toBoolean } from '../core/util/convert'; +import { InputBoolean } from '../core/util/convert'; + +export interface NzSliderTrackStyle { + bottom?: string; + height?: string; + left?: string; + width?: string; + visibility?: string; +} @Component({ + changeDetection : ChangeDetectionStrategy.OnPush, + encapsulation : ViewEncapsulation.None, selector : 'nz-slider-track', preserveWhitespaces: false, templateUrl : './nz-slider-track.component.html' }) export class NzSliderTrackComponent implements OnChanges { - private _vertical = false; - private _included = false; - - // Dynamic properties @Input() nzOffset; @Input() nzLength; + @Input() @InputBoolean() nzVertical = false; + @Input() @InputBoolean() nzIncluded = false; - // Static properties - @Input() nzClassName; - - @Input() - set nzVertical(value: boolean) { // Required - this._vertical = toBoolean(value); - } - - get nzVertical(): boolean { - return this._vertical; - } - - @Input() - set nzIncluded(value: boolean) { - this._included = toBoolean(value); - } - - get nzIncluded(): boolean { - return this._included; - } - - style: { bottom?: string, height?: string, left?: string, width?: string, visibility?: string } = {}; + style: NzSliderTrackStyle = {}; ngOnChanges(changes: SimpleChanges): void { if (changes.nzIncluded) { @@ -46,11 +33,14 @@ export class NzSliderTrackComponent implements OnChanges { if (this.nzVertical) { this.style.bottom = `${this.nzOffset}%`; this.style.height = `${this.nzLength}%`; + this.style.left = null; + this.style.width = null; } else { this.style.left = `${this.nzOffset}%`; this.style.width = `${this.nzLength}%`; + this.style.bottom = null; + this.style.height = null; } } } - } diff --git a/components/slider/nz-slider.component.html b/components/slider/nz-slider.component.html index ff1f1592fd6..2de8b46609d 100644 --- a/components/slider/nz-slider.component.html +++ b/components/slider/nz-slider.component.html @@ -1,37 +1,36 @@ -
+
- + + [nzIncluded]="nzIncluded"> - + + [nzIncluded]="nzIncluded">
\ No newline at end of file diff --git a/components/slider/nz-slider.component.ts b/components/slider/nz-slider.component.ts index f55eb2fdc0b..4f2826009d0 100644 --- a/components/slider/nz-slider.component.ts +++ b/components/slider/nz-slider.component.ts @@ -1,6 +1,6 @@ -/* tslint:disable:variable-name */ import { forwardRef, + ChangeDetectionStrategy, Component, ElementRef, EventEmitter, @@ -10,39 +10,26 @@ import { OnInit, Output, SimpleChanges, - ViewChild + ViewChild, + ViewEncapsulation } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { fromEvent, merge, Observable, Subscription } from 'rxjs'; import { distinctUntilChanged, filter, map, pluck, takeUntil, tap } from 'rxjs/operators'; -import { toBoolean } from '../core/util/convert'; +import { ClassMap } from '../core/interface/interface'; +import { InputBoolean } from '../core/util/convert'; +import { getElementOffset, silentEvent, MouseTouchObserverConfig } from '../core/util/dom'; -import { Marks, MarksArray } from './nz-slider-marks.component'; -import { NzSliderService } from './nz-slider.service'; +import { arrayEquals, shallowCopyArray } from '../core/util/array'; +import { ensureNumberInRange, getPercent, getPrecision } from '../core/util/number'; -export type SliderValue = number[] | number; - -export class SliderHandle { - offset: number; - value: number; - active: boolean; -} - -interface MouseTouchObserverConfig { - start: string; - move: string; - end: string; - pluckKey: string[]; - - filter?(e: Event): boolean; - - startPlucked$?: Observable; - end$?: Observable; - moveResolved$?: Observable; -} +import { isValueARange, Marks, NzSliderShowTooltip, SliderHandler, SliderValue } from './nz-slider-definitions'; +import { getValueTypeNotMatchError } from './nz-slider-error'; @Component({ + // changeDetection : ChangeDetectionStrategy.OnPush, + encapsulation : ViewEncapsulation.None, selector : 'nz-slider', preserveWhitespaces: false, providers : [ { @@ -53,147 +40,77 @@ interface MouseTouchObserverConfig { templateUrl : './nz-slider.component.html' }) export class NzSliderComponent implements ControlValueAccessor, OnInit, OnChanges, OnDestroy { + @ViewChild('slider') slider: ElementRef; - // Debugging - @Input() nzDebugId: number | string = null; // set this id will print debug informations to console - - // Dynamic property settings - @Input() - set nzDisabled(value: boolean) { - this._disabled = toBoolean(value); - } - - get nzDisabled(): boolean { - return this._disabled; - } - - // Static configurations (properties that can only specify once) - @Input() nzStep = 1; + @Input() @InputBoolean() nzDisabled = false; + @Input() @InputBoolean() nzDots: boolean = false; + @Input() @InputBoolean() nzIncluded: boolean = true; + @Input() @InputBoolean() nzRange: boolean = false; + @Input() @InputBoolean() nzVertical: boolean = false; + @Input() nzDefaultValue: SliderValue = null; @Input() nzMarks: Marks = null; - @Input() nzMin = 0; @Input() nzMax = 100; - @Input() nzDefaultValue: SliderValue = null; + @Input() nzMin = 0; + @Input() nzStep = 1; + @Input() nzTooltipVisible: NzSliderShowTooltip = 'default'; @Input() nzTipFormatter: (value: number) => string; - @Output() readonly nzOnAfterChange = new EventEmitter(); - - @Input() - set nzVertical(value: boolean) { - this._vertical = toBoolean(value); - } - - get nzVertical(): boolean { - return this._vertical; - } - - @Input() - set nzRange(value: boolean) { - this._range = toBoolean(value); - } - - get nzRange(): boolean { - return this._range; - } - @Input() - set nzDots(value: boolean) { - this._dots = toBoolean(value); - } - - get nzDots(): boolean { - return this._dots; - } - - @Input() - set nzIncluded(value: boolean) { - this._included = toBoolean(value); - } - - get nzIncluded(): boolean { - return this._included; - } - - // Inside properties - private _disabled = false; - private _dots = false; - private _included = true; - private _range = false; - private _vertical = false; + @Output() readonly nzOnAfterChange = new EventEmitter(); value: SliderValue = null; // CORE value state - @ViewChild('slider') slider: ElementRef; sliderDOM: HTMLDivElement; cacheSliderStart: number = null; cacheSliderLength: number = null; - prefixCls = 'ant-slider'; - classMap: object; activeValueIndex: number = null; // Current activated handle's index ONLY for range=true track = { offset: null, length: null }; // Track's offset and length - handles: SliderHandle[]; // Handles' offset + handlers: SliderHandler[]; // Handles' offset marksArray: Marks[]; // "marks" in array type with more data & FILTER out the invalid mark bounds = { lower: null, upper: null }; // now for nz-slider-step - onValueChange: (value: SliderValue) => void; // Used by ngModel. BUG: onValueChange() will not success to effect the "value" variable ( [(ngModel)]="value" ) when the first initializing, except using "nextTick" functionality (MAY angular2's problem ?) - onTouched: () => void = () => { - } // onTouch function registered via registerOnTouch (ControlValueAccessor). isDragging = false; // Current dragging state - // Events observables & subscriptions - dragstart$: Observable; - dragmove$: Observable; - dragend$: Observable; - dragstart_: Subscription; - dragmove_: Subscription; - dragend_: Subscription; + private dragStart$: Observable; + private dragMove$: Observable; + private dragEnd$: Observable; + private dragStart_: Subscription; + private dragMove_: Subscription; + private dragEnd_: Subscription; - // |-------------------------------------------------------------------------------------------- - // | value accessors & ngModel accessors - // |-------------------------------------------------------------------------------------------- + onValueChange(value: SliderValue): void { + } - setValue(val: SliderValue, isWriteValue: boolean = false): void { - if (isWriteValue) { // [ngModel-writeValue]: Formatting before setting value, always update current value, but trigger onValueChange ONLY when the "formatted value" not equals "input value" - this.value = this.formatValue(val); - this.log(`[ngModel:setValue/writeValue]Update track & handles`); - this.updateTrackAndHandles(); - // if (!this.isValueEqual(this.value, val)) { - // this.log(`[ngModel:setValue/writeValue]onValueChange`, val); - // if (this.onValueChange) { // NOTE: onValueChange will be unavailable when writeValue() called at the first time - // this.onValueChange(this.value); - // } - // } - } else { // [Normal]: setting value, ONLY check changed, then update and trigger onValueChange - if (!this.isValueEqual(this.value, val)) { - this.value = val; - this.log(`[Normal:setValue]Update track & handles`); - this.updateTrackAndHandles(); - this.log(`[Normal:setValue]onValueChange`, val); - if (this.onValueChange) { // NOTE: onValueChange will be unavailable when writeValue() called at the first time - this.onValueChange(this.value); - } - } - } + onTouched(): void { } - getValue(cloneAndSort: boolean = false): SliderValue { - // TODO: using type guard, remove type cast - if (cloneAndSort && this.nzRange) { // clone & sort range values - return this.utils.cloneArray(this.value as number[]).sort((a, b) => a - b); + ngOnInit(): void { + this.assertValueTypeMatch(this.nzDefaultValue); + this.handlers = this.generateHandlers(this.nzRange ? 2 : 1); + this.sliderDOM = this.slider.nativeElement; + this.marksArray = this.nzMarks ? this.generateMarkItems(this.nzMarks) : null; + this.createDraggingObservables(); + this.toggleDragDisabled(this.nzDisabled); + + if (this.getValue() === null) { + this.setValue(this.formatValue(null)); } - return this.value; } - // clone & sort current value and convert them to offsets, then return the new one - getValueToOffset(value?: SliderValue): SliderValue { - let normalizedValue = value; - if (typeof normalizedValue === 'undefined') { - normalizedValue = this.getValue(true); + ngOnChanges(changes: SimpleChanges): void { + const { nzDisabled, nzMarks, nzRange } = changes; + + if (nzDisabled && !nzDisabled.firstChange) { + this.toggleDragDisabled(nzDisabled.currentValue); + } else if (nzMarks && !nzMarks.firstChange) { + this.marksArray = this.nzMarks ? this.generateMarkItems(this.nzMarks) : null; + } else if (nzRange && !nzRange.firstChange) { + this.setValue(this.formatValue(null)); } - // TODO: using type guard, remove type cast - return this.nzRange ? - (normalizedValue as number[]).map(val => this.valueToOffset(val)) : - this.valueToOffset(normalizedValue as number); + } + + ngOnDestroy(): void { + this.unsubscribeDrag(); } writeValue(val: SliderValue): void { - this.log(`[ngModel/writeValue]current writing value = `, val); this.setValue(val, true); } @@ -205,76 +122,56 @@ export class NzSliderComponent implements ControlValueAccessor, OnInit, OnChange this.onTouched = fn; } - setDisabledState(isDisabled: boolean): void { - this.nzDisabled = isDisabled; - this.toggleDragDisabled(isDisabled); - this.setClassMap(); + setValue(value: SliderValue, isWriteValue: boolean = false): void { + if (isWriteValue) { + this.value = this.formatValue(value); + this.updateTrackAndHandles(); + } else { + if (!this.areValuesEqual(this.value, value)) { + this.value = value; + this.updateTrackAndHandles(); + if (this.onValueChange) { + this.onValueChange(this.value); + } + } + } } - // |-------------------------------------------------------------------------------------------- - // | Lifecycle hooks - // |-------------------------------------------------------------------------------------------- - - constructor(private utils: NzSliderService) { + getValue(cloneAndSort: boolean = false): SliderValue { + if (cloneAndSort && isValueARange(this.value)) { + return shallowCopyArray(this.value).sort((a, b) => a - b); + } + return this.value; } - // initialize event binding, class init, etc. (called only once) - ngOnInit(): void { - // initial checking - this.checkValidValue(this.nzDefaultValue); // check nzDefaultValue - // default handles - this.handles = this._generateHandles(this.nzRange ? 2 : 1); - // initialize - this.sliderDOM = this.slider.nativeElement; - if (this.getValue() === null) { - this.setValue(this.formatValue(null)); - } // init with default value - this.marksArray = this.nzMarks === null ? null : this.toMarksArray(this.nzMarks); - // event bindings - this.createDrag(); - // initialize drag's disabled status - this.toggleDragDisabled(this.nzDisabled); - // the first time to init classes - this.setClassMap(); - } + /** + * Clone & sort current value and convert them to offsets, then return the new one. + */ + getValueToOffset(value?: SliderValue): SliderValue { + let normalizedValue = value; - ngOnChanges(changes: SimpleChanges): void { - const { nzDisabled, nzMarks, nzRange } = changes; - if (nzDisabled && !nzDisabled.firstChange) { - this.toggleDragDisabled(nzDisabled.currentValue); - this.setClassMap(); - } else if (nzMarks && !nzMarks.firstChange) { - this.marksArray = this.nzMarks ? this.toMarksArray(this.nzMarks) : null; - } else if (nzRange && !nzRange.firstChange) { - this.setValue(this.formatValue(null)); // Change to default value when nzRange changed + if (typeof normalizedValue === 'undefined') { + normalizedValue = this.getValue(true); } - } - ngOnDestroy(): void { - this.unsubscribeDrag(); + return isValueARange(normalizedValue) + ? normalizedValue.map(val => this.valueToOffset(val)) + : this.valueToOffset(normalizedValue); } - // |-------------------------------------------------------------------------------------------- - // | Basic flow functions - // |-------------------------------------------------------------------------------------------- - - setClassMap(): void { - this.classMap = { - [ this.prefixCls ] : true, - [ `${this.prefixCls}-disabled` ] : this.nzDisabled, - [ `${this.prefixCls}-vertical` ] : this.nzVertical, - [ `${this.prefixCls}-with-marks` ]: this.marksArray ? this.marksArray.length : 0 - }; + setDisabledState(isDisabled: boolean): void { + this.nzDisabled = isDisabled; + this.toggleDragDisabled(isDisabled); } - // find the cloest value to be activated (only for range = true) + // find the closest value to be activated (only for range = true) setActiveValueIndex(pointerValue: number): void { - if (this.nzRange) { + const value = this.getValue(); + if (isValueARange(value)) { let minimal = null; let gap; let activeIndex; - // TODO: using type guard, remove type cast - (this.getValue() as number[]).forEach((val, index) => { + value.forEach((val, index) => { gap = Math.abs(pointerValue - val); if (minimal === null || gap < minimal) { minimal = gap; @@ -286,9 +183,8 @@ export class NzSliderComponent implements ControlValueAccessor, OnInit, OnChange } setActiveValue(pointerValue: number): void { - if (this.nzRange) { - // TODO: using type guard, remove type cast - const newValue = this.utils.cloneArray(this.value as number[]); + if (isValueARange(this.value)) { + const newValue = shallowCopyArray(this.value); newValue[ this.activeValueIndex ] = pointerValue; this.setValue(newValue); } else { @@ -296,6 +192,9 @@ export class NzSliderComponent implements ControlValueAccessor, OnInit, OnChange } } + /** + * Update track and handlers' position and length. + */ updateTrackAndHandles(): void { const value = this.getValue(); const offset = this.getValueToOffset(value); @@ -304,7 +203,7 @@ export class NzSliderComponent implements ControlValueAccessor, OnInit, OnChange const boundParts = this.nzRange ? valueSorted as number[] : [ 0, valueSorted ]; const trackParts = this.nzRange ? [ offsetSorted[ 0 ], offsetSorted[ 1 ] - offsetSorted[ 0 ] ] : [ 0, offsetSorted ]; - this.handles.forEach((handle, index) => { + this.handlers.forEach((handle, index) => { handle.offset = this.nzRange ? offset[ index ] : offset; handle.value = this.nzRange ? value[ index ] : value; }); @@ -312,25 +211,11 @@ export class NzSliderComponent implements ControlValueAccessor, OnInit, OnChange [ this.track.offset, this.track.length ] = trackParts; } - toMarksArray(marks: Marks): Marks[] { - const marksArray = []; - for (const key in marks) { - const mark = marks[ key ]; - const val = typeof key === 'number' ? key : parseFloat(key); - if (val < this.nzMin || val > this.nzMax) { - continue; - } - marksArray.push({ value: val, offset: this.valueToOffset(val), config: mark }); - } - return marksArray; - } - // |-------------------------------------------------------------------------------------------- // | Event listeners & bindings // |-------------------------------------------------------------------------------------------- onDragStart(value: number): void { - this.log('[onDragStart]dragging value = ', value); this.toggleDragMoving(true); // cache DOM layout/reflow operations this.cacheSliderProperty(); @@ -338,53 +223,56 @@ export class NzSliderComponent implements ControlValueAccessor, OnInit, OnChange this.setActiveValueIndex(value); this.setActiveValue(value); // Tooltip visibility of handles - this._showHandleTooltip(this.nzRange ? this.activeValueIndex : 0); + this.showHandleTooltip(this.nzRange ? this.activeValueIndex : 0); } onDragMove(value: number): void { - this.log('[onDragMove]dragging value = ', value); // trigger drag moving this.setActiveValue(value); } onDragEnd(): void { - this.log('[onDragEnd]'); this.toggleDragMoving(false); this.nzOnAfterChange.emit(this.getValue(true)); // remove cache DOM layout/reflow operations this.cacheSliderProperty(true); // Hide all tooltip - this._hideAllHandleTooltip(); + this.hideAllHandleTooltip(); } - createDrag(): void { + /** + * Create user interactions handlers. + */ + private createDraggingObservables(): void { const sliderDOM = this.sliderDOM; const orientField = this.nzVertical ? 'pageY' : 'pageX'; const mouse: MouseTouchObserverConfig = { - start : 'mousedown', move: 'mousemove', end: 'mouseup', + start : 'mousedown', + move : 'mousemove', + end : 'mouseup', pluckKey: [ orientField ] }; const touch: MouseTouchObserverConfig = { - start : 'touchstart', move: 'touchmove', end: 'touchend', + start : 'touchstart', + move : 'touchmove', + end : 'touchend', pluckKey: [ 'touches', '0', orientField ], - filter : (e: MouseEvent | TouchEvent) => !this.utils.isNotTouchEvent(e as TouchEvent) + filter : (e: MouseEvent | TouchEvent) => e instanceof TouchEvent }; - // make observables + [ mouse, touch ].forEach(source => { const { start, move, end, pluckKey, filter: filterFunc = (() => true) } = source; - // start + source.startPlucked$ = fromEvent(sliderDOM, start).pipe( filter(filterFunc), - tap(this.utils.pauseEvent), + tap(silentEvent), pluck(...pluckKey), map((position: number) => this.findClosestValue(position)) ); - // end source.end$ = fromEvent(document, end); - // resolve move source.moveResolved$ = fromEvent(document, move).pipe( filter(filterFunc), - tap(this.utils.pauseEvent), + tap(silentEvent), pluck(...pluckKey), distinctUntilChanged(), map((position: number) => this.findClosestValue(position)), @@ -394,43 +282,40 @@ export class NzSliderComponent implements ControlValueAccessor, OnInit, OnChange // merge to become moving // source.move$ = source.startPlucked$.mergeMapTo(source.moveResolved$); }); - // merge mouse and touch observables - this.dragstart$ = merge(mouse.startPlucked$, touch.startPlucked$); - // this.dragmove$ = Observable.merge(mouse.move$, touch.move$); - this.dragmove$ = merge(mouse.moveResolved$, touch.moveResolved$); - this.dragend$ = merge(mouse.end$, touch.end$); + + this.dragStart$ = merge(mouse.startPlucked$, touch.startPlucked$); + this.dragMove$ = merge(mouse.moveResolved$, touch.moveResolved$); + this.dragEnd$ = merge(mouse.end$, touch.end$); } subscribeDrag(periods: string[] = [ 'start', 'move', 'end' ]): void { - this.log('[subscribeDrag]this.dragstart$ = ', this.dragstart$); - if (periods.indexOf('start') !== -1 && this.dragstart$ && !this.dragstart_) { - this.dragstart_ = this.dragstart$.subscribe(this.onDragStart.bind(this)); + if (periods.indexOf('start') !== -1 && this.dragStart$ && !this.dragStart_) { + this.dragStart_ = this.dragStart$.subscribe(this.onDragStart.bind(this)); } - if (periods.indexOf('move') !== -1 && this.dragmove$ && !this.dragmove_) { - this.dragmove_ = this.dragmove$.subscribe(this.onDragMove.bind(this)); + if (periods.indexOf('move') !== -1 && this.dragMove$ && !this.dragMove_) { + this.dragMove_ = this.dragMove$.subscribe(this.onDragMove.bind(this)); } - if (periods.indexOf('end') !== -1 && this.dragend$ && !this.dragend_) { - this.dragend_ = this.dragend$.subscribe(this.onDragEnd.bind(this)); + if (periods.indexOf('end') !== -1 && this.dragEnd$ && !this.dragEnd_) { + this.dragEnd_ = this.dragEnd$.subscribe(this.onDragEnd.bind(this)); } } unsubscribeDrag(periods: string[] = [ 'start', 'move', 'end' ]): void { - this.log('[unsubscribeDrag]this.dragstart_ = ', this.dragstart_); - if (periods.indexOf('start') !== -1 && this.dragstart_) { - this.dragstart_.unsubscribe(); - this.dragstart_ = null; + if (periods.indexOf('start') !== -1 && this.dragStart_) { + this.dragStart_.unsubscribe(); + this.dragStart_ = null; } - if (periods.indexOf('move') !== -1 && this.dragmove_) { - this.dragmove_.unsubscribe(); - this.dragmove_ = null; + if (periods.indexOf('move') !== -1 && this.dragMove_) { + this.dragMove_.unsubscribe(); + this.dragMove_ = null; } - if (periods.indexOf('end') !== -1 && this.dragend_) { - this.dragend_.unsubscribe(); - this.dragend_ = null; + if (periods.indexOf('end') !== -1 && this.dragEnd_) { + this.dragEnd_.unsubscribe(); + this.dragEnd_ = null; } } @@ -445,7 +330,7 @@ export class NzSliderComponent implements ControlValueAccessor, OnInit, OnChange } } - toggleDragDisabled(disabled: boolean): void { + private toggleDragDisabled(disabled: boolean): void { if (disabled) { this.unsubscribeDrag(); } else { @@ -461,7 +346,7 @@ export class NzSliderComponent implements ControlValueAccessor, OnInit, OnChange findClosestValue(position: number): number { const sliderStart = this.getSliderStartPosition(); const sliderLength = this.getSliderLength(); - const ratio = this.utils.correctNumLimit((position - sliderStart) / sliderLength, 0, 1); + const ratio = ensureNumberInRange((position - sliderStart) / sliderLength, 0, 1); const val = (this.nzMax - this.nzMin) * (this.nzVertical ? 1 - ratio : ratio) + this.nzMin; const points = (this.nzMarks === null ? [] : Object.keys(this.nzMarks).map(parseFloat)); // push closest step @@ -474,18 +359,18 @@ export class NzSliderComponent implements ControlValueAccessor, OnInit, OnChange const closest = points[ gaps.indexOf(Math.min(...gaps)) ]; // return the fixed return this.nzStep === null ? closest : - parseFloat(closest.toFixed(this.utils.getPrecision(this.nzStep))); + parseFloat(closest.toFixed(getPrecision(this.nzStep))); } valueToOffset(value: number): number { - return this.utils.valueToOffset(this.nzMin, this.nzMax, value); + return getPercent(this.nzMin, this.nzMax, value); } getSliderStartPosition(): number { if (this.cacheSliderStart !== null) { return this.cacheSliderStart; } - const offset = this.utils.getElementOffset(this.sliderDOM); + const offset = getElementOffset(this.sliderDOM); return this.nzVertical ? offset.top : offset.left; } @@ -506,25 +391,24 @@ export class NzSliderComponent implements ControlValueAccessor, OnInit, OnChange formatValue(value: SliderValue): SliderValue { // NOTE: will return new value let res = value; - if (!this.checkValidValue(value)) { // if empty, use default value + if (!this.assertValueIsValid(value)) { // if empty, use default value res = this.nzDefaultValue === null ? (this.nzRange ? [ this.nzMin, this.nzMax ] : this.nzMin) : this.nzDefaultValue; } else { // format - // TODO: using type guard, remove type cast - res = this.nzRange ? - (value as number[]).map(val => this.utils.correctNumLimit(val, this.nzMin, this.nzMax)) : - this.utils.correctNumLimit(value as number, this.nzMin, this.nzMax); + res = isValueARange(value) + ? value.map(val => ensureNumberInRange(val, this.nzMin, this.nzMax)) + : ensureNumberInRange(value, this.nzMin, this.nzMax); } return res; } - // check if value is valid and throw error if value-type/range not match - checkValidValue(value: SliderValue): boolean { - const range = this.nzRange; + /** + * Check if value is valid and throw error if value-type/range not match. + */ + private assertValueIsValid(value: SliderValue): boolean { if (value === null || value === undefined) { return false; - } // it's an invalid value, just return - const isArray = Array.isArray(value); + } if (!Array.isArray(value)) { let parsedValue: number = value; if (typeof value !== 'number') { @@ -532,58 +416,56 @@ export class NzSliderComponent implements ControlValueAccessor, OnInit, OnChange } if (isNaN(parsedValue)) { return false; - } // it's an invalid value, just return + } } - if (isArray !== !!range) { // value type not match - throw new Error(`The "nzRange" can't match the "nzValue"'s type, please check these properties: "nzRange", "nzValue", "nzDefaultValue".`); + + return this.assertValueTypeMatch(value); + } + + /** + * Assert that if `this.nzRange` is `true`, value is also a range, vice versa. + */ + private assertValueTypeMatch(value: SliderValue): boolean { + if (isValueARange(value) !== this.nzRange) { + throw getValueTypeNotMatchError(); } return true; } - isValueEqual(value: SliderValue, val: SliderValue): boolean { - if (typeof value !== typeof val) { + private areValuesEqual(valA: SliderValue, valB: SliderValue): boolean { + if (typeof valA !== typeof valB) { return false; } - if (Array.isArray(value)) { - const len = value.length; - for (let i = 0; i < len; i++) { - if (value[ i ] !== val[ i ]) { - return false; - } - } - return true; - } else { - return value === val; - } - } - // print debug info - // TODO: should not kept in component - /* tslint:disable-next-line:no-any */ - log(...messages: any[]): void { - if (this.nzDebugId !== null) { - const args = [ `[nz-slider][#${this.nzDebugId}] ` ].concat(Array.prototype.slice.call(arguments)); - console.log.apply(null, args); - } + return isValueARange(valA) && isValueARange(valB) + ? arrayEquals(valA, valB) + : valA === valB; } // Show one handle's tooltip and hide others' - private _showHandleTooltip(handleIndex: number = 0): void { - this.handles.forEach((handle, index) => { - this.handles[ index ].active = index === handleIndex; + private showHandleTooltip(handleIndex: number = 0): void { + this.handlers.forEach((handle, index) => { + this.handlers[ index ].active = index === handleIndex; }); } - private _hideAllHandleTooltip(): void { - this.handles.forEach(handle => handle.active = false); + private hideAllHandleTooltip(): void { + this.handlers.forEach(handle => handle.active = false); } - private _generateHandles(amount: number): SliderHandle[] { - const handles: SliderHandle[] = []; - for (let i = 0; i < amount; i++) { - handles.push({ offset: null, value: null, active: false }); - } - return handles; + private generateHandlers(amount: number): SliderHandler[] { + return Array(amount).fill(0).map(() => ({ offset: null, value: null, active: false })); } + private generateMarkItems(marks: Marks): Marks[] | null { + const marksArray = []; + for (const key in marks) { + const mark = marks[ key ]; + const val = typeof key === 'number' ? key : parseFloat(key); + if (val >= this.nzMin && val <= this.nzMax) { + marksArray.push({ value: val, offset: this.valueToOffset(val), config: mark }); + } + } + return marksArray.length ? marksArray : null; + } } diff --git a/components/slider/nz-slider.module.ts b/components/slider/nz-slider.module.ts index 711c7b71d05..d95f06ab833 100644 --- a/components/slider/nz-slider.module.ts +++ b/components/slider/nz-slider.module.ts @@ -8,12 +8,22 @@ import { NzSliderMarksComponent } from './nz-slider-marks.component'; import { NzSliderStepComponent } from './nz-slider-step.component'; import { NzSliderTrackComponent } from './nz-slider-track.component'; import { NzSliderComponent } from './nz-slider.component'; -import { NzSliderService } from './nz-slider.service'; @NgModule({ - exports: [ NzSliderComponent, NzSliderTrackComponent, NzSliderHandleComponent, NzSliderStepComponent, NzSliderMarksComponent ], - declarations: [ NzSliderComponent, NzSliderTrackComponent, NzSliderHandleComponent, NzSliderStepComponent, NzSliderMarksComponent ], - imports: [ CommonModule, NzToolTipModule ], - providers: [ NzSliderService ] + exports: [ + NzSliderComponent, + NzSliderTrackComponent, + NzSliderHandleComponent, + NzSliderStepComponent, + NzSliderMarksComponent + ], + declarations: [ + NzSliderComponent, + NzSliderTrackComponent, + NzSliderHandleComponent, + NzSliderStepComponent, + NzSliderMarksComponent + ], + imports: [ CommonModule, NzToolTipModule ] }) export class NzSliderModule { } diff --git a/components/slider/nz-slider.service.ts b/components/slider/nz-slider.service.ts deleted file mode 100644 index e62466a4f24..00000000000 --- a/components/slider/nz-slider.service.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable() -export class NzSliderService { - - pauseEvent(e: Event): void { - e.stopPropagation(); - e.preventDefault(); - } - - getPrecision(num: number): number { - const numStr = num.toString(); - const dotIndex = numStr.indexOf('.'); - return dotIndex >= 0 ? numStr.length - dotIndex - 1 : 0; - } - - cloneArray(arr: T[]): T[] { - return arr.slice(); - } - - isNotTouchEvent(e: TouchEvent): boolean { - return !e.touches || e.touches.length > 1 || - (e.type.toLowerCase() === 'touchend' && e.touches.length > 0); - } - - // convert value to offset in percent - valueToOffset(min: number, max: number, value: number): number { - return (value - min) / (max - min) * 100; - } - - correctNumLimit(num: number, min: number, max: number): number { - let res = +num; - if (isNaN(res)) { return min; } - if (num < min) { res = min; } else if (num > max) { res = max; } - return res; - } - - /** - * get the offset of an element relative to the document (Reference from jquery's offset()) - * @param elem HTMLElement ref - */ - getElementOffset(elem: HTMLElement): { top: number, left: number } { - // Return zeros for disconnected and hidden (display: none) elements (gh-2310) - // Support: IE <=11 only - // Running getBoundingClientRect on a - // disconnected node in IE throws an error - if (!elem.getClientRects().length) { - return { top: 0, left: 0 }; - } - // Get document-relative position by adding viewport scroll to viewport-relative gBCR - const rect = elem.getBoundingClientRect(); - const win = elem.ownerDocument.defaultView; - return { - top: rect.top + win.pageYOffset, - left: rect.left + win.pageXOffset - }; - } - -} diff --git a/components/slider/nz-slider.spec.ts b/components/slider/nz-slider.spec.ts index 7cc399e2ce3..ffdfcd1dac6 100644 --- a/components/slider/nz-slider.spec.ts +++ b/components/slider/nz-slider.spec.ts @@ -126,6 +126,14 @@ describe('NzSlider', () => { it('should pass un-covered code testing', () => { expect(sliderInstance.getValueToOffset()).toBe(sliderInstance.value); }); + + it('should always display tooltip work', () => { + + }); + + it('should never display tooltip word', () => { + + }); }); describe('disabled slider', () => { diff --git a/components/slider/public-api.ts b/components/slider/public-api.ts index d24e18947a1..e74297ddceb 100644 --- a/components/slider/public-api.ts +++ b/components/slider/public-api.ts @@ -1,6 +1,5 @@ export * from './nz-slider.component'; export * from './nz-slider.module'; -export * from './nz-slider.service'; export * from './nz-slider-handle.component'; export * from './nz-slider-marks.component'; export * from './nz-slider-step.component'; diff --git a/components/timeline/nz-timeline.component.ts b/components/timeline/nz-timeline.component.ts index e32029b7fa4..73a1f4d13c0 100644 --- a/components/timeline/nz-timeline.component.ts +++ b/components/timeline/nz-timeline.component.ts @@ -18,7 +18,7 @@ import { import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { reverseChildNodes } from '../core/dom/reverse'; +import { reverseChildNodes } from '../core/util/dom'; import { NzTimelineItemComponent } from './nz-timeline-item.component'; export type NzTimelineMode = 'left' | 'alternate' | 'right';