From 81991ff6c38de8bbcd7e29e9ccebce381c5c4216 Mon Sep 17 00:00:00 2001 From: ng-nest-moon Date: Thu, 26 Oct 2023 07:16:56 +0800 Subject: [PATCH] feat(module:slider-select): add range close #83 --- .../slider-select.component.html | 47 +++- .../slider-select/slider-select.component.ts | 252 ++++++++++++++---- .../slider-select/slider-select.property.ts | 7 +- lib/ng-nest/ui/slider-select/style/mixin.scss | 6 + lib/ng-nest/ui/slider-select/style/param.scss | 4 +- lib/ng-nest/ui/tree-select/style/mixin.scss | 3 + .../slider-select/range/range.component.html | 17 ++ .../slider-select/range/range.component.scss | 8 + .../slider-select/range/range.component.ts | 12 + .../slider-select.component.html | 3 +- .../slider-select/slider-select.module.ts | 4 +- 11 files changed, 296 insertions(+), 67 deletions(-) create mode 100644 src/main/test/modules/slider-select/range/range.component.html create mode 100644 src/main/test/modules/slider-select/range/range.component.scss create mode 100644 src/main/test/modules/slider-select/range/range.component.ts diff --git a/lib/ng-nest/ui/slider-select/slider-select.component.html b/lib/ng-nest/ui/slider-select/slider-select.component.html index 0f4771abb..aff0e367c 100644 --- a/lib/ng-nest/ui/slider-select/slider-select.component.html +++ b/lib/ng-nest/ui/slider-select/slider-select.component.html @@ -8,7 +8,7 @@ [class.x-slider-select-reverse]="reverse" > -
+
@@ -17,25 +17,52 @@
+
+
+
diff --git a/lib/ng-nest/ui/slider-select/slider-select.component.ts b/lib/ng-nest/ui/slider-select/slider-select.component.ts index ef914bc53..24640cdeb 100644 --- a/lib/ng-nest/ui/slider-select/slider-select.component.ts +++ b/lib/ng-nest/ui/slider-select/slider-select.component.ts @@ -10,10 +10,22 @@ import { ViewChild, OnDestroy, AfterViewInit, - HostBinding + HostBinding, + ViewChildren, + QueryList } from '@angular/core'; import { XSliderSelectProperty, XSliderSelectPrefix } from './slider-select.property'; -import { XIsEmpty, XIsUndefined, XResize, XClearClass, XConfigService, XResizeObserver } from '@ng-nest/ui/core'; +import { + XIsEmpty, + XIsUndefined, + XResize, + XClearClass, + XConfigService, + XResizeObserver, + XIsNumber, + XIsArray, + XIsNull +} from '@ng-nest/ui/core'; import { CdkDragMove, CdkDragStart, CdkDragEnd } from '@angular/cdk/drag-drop'; import { Subject } from 'rxjs'; import { debounceTime, takeUntil } from 'rxjs/operators'; @@ -29,29 +41,53 @@ import { XValueAccessor } from '@ng-nest/ui/base-form'; }) export class XSliderSelectComponent extends XSliderSelectProperty implements OnInit, OnDestroy, AfterViewInit { @ViewChild('sliderSelect', { static: true }) sliderSelect!: ElementRef; - @ViewChild('dragRef', { static: true }) dragRef!: ElementRef; + @ViewChild('dragStartRef', { static: true }) dragStartRef!: ElementRef; + @ViewChild('dragEndRef', { static: true }) dragEndRef!: ElementRef; @ViewChild('railRef', { static: true }) railRef!: ElementRef; @ViewChild('processRef', { static: true }) processRef!: ElementRef; @HostBinding('class.x-slider-select-vertical') get getVertical() { return this.vertical; } - @ViewChild(XTooltipDirective, { static: true }) tooltip!: XTooltipDirective; - offset: number = 0; - visible: boolean = false; - manual: boolean = false; + @ViewChildren(XTooltipDirective) tooltips!: QueryList; + tooltipStart!: XTooltipDirective; + tooltipEnd!: XTooltipDirective; + + override value: number | number[] = 0; + + startOffset: number = 0; + startVisible: boolean = false; + startManual: boolean = false; start!: number; - override value = 0; - displayValue = '0'; + startDisplayValue = '0'; + showStartTooltip = true; + + endOffset: number = 0; + endVisible: boolean = false; + endManual: boolean = false; + end!: number; + endDisplayValue = '0'; + showEndTooltip = true; + private _unSubject = new Subject(); private _resizeObserver!: XResizeObserver; + isNumber = true; + isArray = false; override get requiredIsEmpty() { return this.validator && this.required && (XIsEmpty(this.value) || this.value === 0); } - override writeValue(value: number) { - if (value === null) value = 0; + override writeValue(value: number | number[]) { + if (XIsNull(value) || XIsUndefined(value)) { + if (this.range) { + value = [0, 0]; + } else { + value = 0; + } + } this.value = value; + this.isNumber = XIsNumber(this.value) && !XIsArray(this.value); + this.isArray = XIsArray(this.value); this.setLeft(); this.setDisplayValue(); } @@ -80,6 +116,13 @@ export class XSliderSelectComponent extends XSliderSelectProperty implements OnI this.setDisplayValue(); this.cdr.detectChanges(); }); + + if (this.tooltips.length > 0) { + this.tooltipStart = this.tooltips.first; + } + if (this.tooltips.length > 1) { + this.tooltipEnd = this.tooltips.last; + } } ngOnDestroy(): void { @@ -89,10 +132,23 @@ export class XSliderSelectComponent extends XSliderSelectProperty implements OnI } change() { - const val = Number(((Number(this.max) - Number(this.min)) * Number(this.offset)) / 100 + Number(this.min)).toFixed( - Number(this.precision) - ); - this.value = parseFloat(val); + const getVal = (offset: number) => { + return parseFloat( + Number(((Number(this.max) - Number(this.min)) * Number(offset)) / 100 + Number(this.min)).toFixed(Number(this.precision)) + ); + }; + const startVal = getVal(this.startOffset); + if (this.isNumber) { + this.value = startVal; + } else { + const endVal = getVal(this.endOffset); + if (endVal < startVal) { + this.value = [endVal, startVal]; + } else { + this.value = [startVal, endVal]; + } + } + this.setDisplayValue(); if (this.onChange) this.onChange(this.value); } @@ -103,17 +159,47 @@ export class XSliderSelectComponent extends XSliderSelectProperty implements OnI } setLeft() { - this.offset = Math.round( - ((this.value + (this.reverse ? Number(this.min) : -Number(this.min))) * 100) / (Number(this.max) - Number(this.min)) - ); - const start = this.offset; + let startVal = 0, + endVal = 0; + const getOffset = (val: number) => { + return Math.round(((val + (this.reverse ? Number(this.min) : -Number(this.min))) * 100) / (Number(this.max) - Number(this.min))); + }; + + if (this.isNumber) { + startVal = this.value as number; + } else if (XIsArray(this.value) && this.value.length > 1) { + startVal = this.value[0]; + endVal = this.value[1]; + + this.endOffset = getOffset(endVal); + const end = this.endOffset; + this.end = end; + } + this.startOffset = getOffset(startVal); + const start = this.startOffset; this.start = start; + this.setDrag(); this.cdr.detectChanges(); } setDisplayValue() { - this.displayValue = Number(this.value).toFixed(Number(this.precision)); + const displayVal = (val: number) => { + return Number(val).toFixed(Number(this.precision)); + }; + if (this.isNumber) { + this.startDisplayValue = displayVal(this.value as number); + } else { + if (XIsArray(this.value) && this.value.length > 1) { + if (this.startOffset > this.endOffset) { + this.startDisplayValue = displayVal(this.value[1]); + this.endDisplayValue = displayVal(this.value[0]); + } else { + this.startDisplayValue = displayVal(this.value[0]); + this.endDisplayValue = displayVal(this.value[1]); + } + } + } this.cdr.detectChanges(); } @@ -129,41 +215,58 @@ export class XSliderSelectComponent extends XSliderSelectProperty implements OnI } } - onInnerClick(_$event: MouseEvent) {} - - started(drag: CdkDragStart) { - const start = this.offset; - this.start = start; - if (this.showTooltip) { - this.manual = true; - this.visible = true; + started(drag: CdkDragStart, type: 'start' | 'end' = 'start') { + if (type === 'start') { + const start = this.startOffset; + this.start = start; + if (this.showStartTooltip) { + this.startManual = true; + this.startVisible = true; + } + } else if (type === 'end') { + const end = this.endOffset; + this.end = end; + if (this.showEndTooltip) { + this.endManual = true; + this.endVisible = true; + } } this.cdr.detectChanges(); this.formControlValidator(); this.dragStartEmit.emit(drag); } - moved(drag: CdkDragMove) { + moved(drag: CdkDragMove, type: 'start' | 'end' = 'start') { let transform = drag.source.getFreeDragPosition(); - this.setDrag(this.vertical ? transform.y : transform.x); + this.setDrag(this.vertical ? transform.y : transform.x, type); drag.source.reset(); - if (this.showTooltip) { - this.tooltip.updatePortal(); + if (type === 'start' && this.showStartTooltip) { + this.tooltipStart.updatePortal(); + } + if (type === 'end' && this.showEndTooltip) { + this.tooltipEnd.updatePortal(); } this.change(); this.dragMoveEmit.emit(drag); } - ended(drag: CdkDragEnd) { - if (this.showTooltip) { - this.manual = false; - this.visible = false; + ended(drag: CdkDragEnd, type: 'start' | 'end' = 'start') { + if (type === 'start') { + if (this.showStartTooltip) { + this.startManual = false; + this.startVisible = false; + } + } else if (type === 'end') { + if (this.showEndTooltip) { + this.endManual = false; + this.endVisible = false; + } } this.cdr.detectChanges(); this.dragEndEmit.emit(drag); } - setDrag(distance: number = 0) { + setDrag(distance: number = 0, type: 'start' | 'end' | 'both' = 'both') { if (typeof this.railRef.nativeElement.getBoundingClientRect !== 'function') return; let railBox = this.railRef.nativeElement.getBoundingClientRect(); let railBoxLength = this.vertical ? railBox.height : railBox.width; @@ -178,31 +281,76 @@ export class XSliderSelectComponent extends XSliderSelectProperty implements OnI ? distance + stepLength - offset : distance - stepLength + offset; - let x = (this.start / 100) * railBoxLength; - if (this.vertical) { - x += this.reverse ? dis : -dis; - } else { - x += this.reverse ? -dis : dis; + const setOffset = (d: number) => { + let x1 = (d / 100) * railBoxLength; + if (this.vertical) { + x1 += this.reverse ? dis : -dis; + } else { + x1 += this.reverse ? -dis : dis; + } + return Math.round((x1 / railBoxLength) * 100); + }; + + if (type === 'both') { + this.startOffset = setOffset(this.start); + this.endOffset = setOffset(this.end); + } else if (type === 'start') { + this.startOffset = setOffset(this.start); + } else if (type === 'end') { + this.endOffset = setOffset(this.end); } - this.offset = Math.round((x / railBoxLength) * 100); + this.setDragStyles(); + } + + setDragStyles() { if (this.vertical) { - if (this.reverse) { - this.renderer.setStyle(this.dragRef.nativeElement, 'top', `${this.offset}%`); + if (this.isArray) { + const wd = Math.abs(this.endOffset - this.startOffset); + const lt = this.endOffset > this.startOffset ? this.startOffset : this.endOffset; + if (this.reverse) { + this.renderer.setStyle(this.dragStartRef.nativeElement, 'top', `${this.startOffset}%`); + this.renderer.setStyle(this.dragEndRef.nativeElement, 'top', `${this.endOffset}%`); + this.renderer.setStyle(this.processRef.nativeElement, 'top', `${lt}%`); + } else { + this.renderer.setStyle(this.dragStartRef.nativeElement, 'bottom', `${this.startOffset}%`); + this.renderer.setStyle(this.dragEndRef.nativeElement, 'bottom', `${this.endOffset}%`); + this.renderer.setStyle(this.processRef.nativeElement, 'bottom', `${lt}%`); + } + this.renderer.setStyle(this.processRef.nativeElement, 'height', `${wd}%`); } else { - this.renderer.setStyle(this.dragRef.nativeElement, 'bottom', `${this.offset}%`); + if (this.reverse) { + this.renderer.setStyle(this.dragStartRef.nativeElement, 'top', `${this.startOffset}%`); + } else { + this.renderer.setStyle(this.dragStartRef.nativeElement, 'bottom', `${this.startOffset}%`); + } + this.renderer.setStyle(this.processRef.nativeElement, 'height', `${this.startOffset}%`); } - this.renderer.setStyle(this.processRef.nativeElement, 'height', `${this.offset}%`); } else { - if (this.reverse) { - this.renderer.setStyle(this.dragRef.nativeElement, 'right', `${this.offset}%`); + if (this.isArray) { + const wd = Math.abs(this.endOffset - this.startOffset); + const lt = this.endOffset > this.startOffset ? this.startOffset : this.endOffset; + if (this.reverse) { + this.renderer.setStyle(this.dragStartRef.nativeElement, 'right', `${this.startOffset}%`); + this.renderer.setStyle(this.dragEndRef.nativeElement, 'right', `${this.endOffset}%`); + this.renderer.setStyle(this.processRef.nativeElement, 'right', `${lt}%`); + } else { + this.renderer.setStyle(this.dragStartRef.nativeElement, 'left', `${this.startOffset}%`); + this.renderer.setStyle(this.dragEndRef.nativeElement, 'left', `${this.endOffset}%`); + this.renderer.setStyle(this.processRef.nativeElement, 'left', `${lt}%`); + } + this.renderer.setStyle(this.processRef.nativeElement, 'width', `${wd}%`); } else { - this.renderer.setStyle(this.dragRef.nativeElement, 'left', `${this.offset}%`); + if (this.reverse) { + this.renderer.setStyle(this.dragStartRef.nativeElement, 'right', `${this.startOffset}%`); + } else { + this.renderer.setStyle(this.dragStartRef.nativeElement, 'left', `${this.startOffset}%`); + } + this.renderer.setStyle(this.processRef.nativeElement, 'width', `${this.startOffset}%`); } - this.renderer.setStyle(this.processRef.nativeElement, 'width', `${this.offset}%`); } - this.renderer.removeStyle(this.dragRef.nativeElement, 'transform'); + this.renderer.removeStyle(this.dragStartRef.nativeElement, 'transform'); } formControlChanges() { diff --git a/lib/ng-nest/ui/slider-select/slider-select.property.ts b/lib/ng-nest/ui/slider-select/slider-select.property.ts index c90f05e42..8be2f4285 100644 --- a/lib/ng-nest/ui/slider-select/slider-select.property.ts +++ b/lib/ng-nest/ui/slider-select/slider-select.property.ts @@ -14,7 +14,7 @@ export const XSliderSelectPrefix = 'x-slider-select'; * SliderSelect Property */ @Component({ selector: `${XSliderSelectPrefix}-property`, template: '' }) -export class XSliderSelectProperty extends XControlValueAccessor implements XSliderSelectOption { +export class XSliderSelectProperty extends XControlValueAccessor implements XSliderSelectOption { /** * @zh_CN 最小值 * @en_US Minimum @@ -50,6 +50,11 @@ export class XSliderSelectProperty extends XControlValueAccessor impleme * @en_US Vertical */ @Input() @XInputBoolean() vertical?: XBoolean; + /** + * @zh_CN 范围 + * @en_US Range + */ + @Input() @XInputBoolean() range?: XBoolean; /** * @zh_CN 开始拖动的事件 * @en_US Start drag event diff --git a/lib/ng-nest/ui/slider-select/style/mixin.scss b/lib/ng-nest/ui/slider-select/style/mixin.scss index 1ab9e4fb0..5418eb9f2 100644 --- a/lib/ng-nest/ui/slider-select/style/mixin.scss +++ b/lib/ng-nest/ui/slider-select/style/mixin.scss @@ -56,6 +56,7 @@ background-color: $--x-primary; border-radius: $--x-slider-select-border-radius; width: 0%; + position: relative; } &-drags { position: relative; @@ -74,6 +75,11 @@ border-radius: $--x-slider-select-button-size; border: calc(#{$--x-border-width} * 2) solid $--x-primary; background-color: $--x-background-a100; + transition: $--x-animation-duration-base; + &:hover, + &-actived { + transform: scale(1.2); + } } &.x-invalid, &.x-required { diff --git a/lib/ng-nest/ui/slider-select/style/param.scss b/lib/ng-nest/ui/slider-select/style/param.scss index d98b9dbf5..0b511b8ef 100644 --- a/lib/ng-nest/ui/slider-select/style/param.scss +++ b/lib/ng-nest/ui/slider-select/style/param.scss @@ -9,10 +9,10 @@ $--x-slider-select-font-size: $--x-font-size; $--x-slider-select-height: calc(#{$--x-font-size} + 0.875rem); /* 轨道高度 */ -$--x-slider-select-track-height: calc(#{$--x-slider-select-height} / 4); +$--x-slider-select-track-height: calc(#{$--x-slider-select-height} / 5); /* 滑块尺寸 */ -$--x-slider-select-button-size: 1rem; +$--x-slider-select-button-size: 0.875rem; /* 内边距 */ $--x-slider-select-padding: 0 $--x-control-padding-horizontal; diff --git a/lib/ng-nest/ui/tree-select/style/mixin.scss b/lib/ng-nest/ui/tree-select/style/mixin.scss index 2da0c9205..c29f79ff0 100644 --- a/lib/ng-nest/ui/tree-select/style/mixin.scss +++ b/lib/ng-nest/ui/tree-select/style/mixin.scss @@ -40,6 +40,9 @@ @include input-size('small', $--x-height-mini, 0 $--x-padding-mini); @include input-size('mini', 1.125rem, 0 0.125rem); } + .x-input-row > .x-input-input > input { + background-color: transparent; + } } } } diff --git a/src/main/test/modules/slider-select/range/range.component.html b/src/main/test/modules/slider-select/range/range.component.html new file mode 100644 index 000000000..bcc940627 --- /dev/null +++ b/src/main/test/modules/slider-select/range/range.component.html @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/main/test/modules/slider-select/range/range.component.scss b/src/main/test/modules/slider-select/range/range.component.scss new file mode 100644 index 000000000..56640f9a7 --- /dev/null +++ b/src/main/test/modules/slider-select/range/range.component.scss @@ -0,0 +1,8 @@ +:host { + x-row { + margin-top: 10rem; + } + x-row:not(:first-child) { + margin-top: 1rem; + } +} diff --git a/src/main/test/modules/slider-select/range/range.component.ts b/src/main/test/modules/slider-select/range/range.component.ts new file mode 100644 index 000000000..9de647a9d --- /dev/null +++ b/src/main/test/modules/slider-select/range/range.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ex-range', + templateUrl: './range.component.html', + styleUrls: ['./range.component.scss'] +}) +export class ExRangeComponent { + model1 = [20, 60]; + + model2!: number; +} diff --git a/src/main/test/modules/slider-select/slider-select.component.html b/src/main/test/modules/slider-select/slider-select.component.html index 221ae8e40..0d599caab 100644 --- a/src/main/test/modules/slider-select/slider-select.component.html +++ b/src/main/test/modules/slider-select/slider-select.component.html @@ -4,4 +4,5 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/src/main/test/modules/slider-select/slider-select.module.ts b/src/main/test/modules/slider-select/slider-select.module.ts index 87e29a1c7..197623ae6 100644 --- a/src/main/test/modules/slider-select/slider-select.module.ts +++ b/src/main/test/modules/slider-select/slider-select.module.ts @@ -12,6 +12,7 @@ import { ExLimitComponent } from './limit/limit.component'; import { ExPrecisionComponent } from './precision/precision.component'; import { ExReverseComponent } from './reverse/reverse.component'; import { ExVerticalComponent } from './vertical/vertical.component'; +import { ExRangeComponent } from './range/range.component'; const routers = [{ path: '', component: TeSliderSelectComponent }]; @@ -25,7 +26,8 @@ const routers = [{ path: '', component: TeSliderSelectComponent }]; ExLimitComponent, ExPrecisionComponent, ExReverseComponent, - ExVerticalComponent + ExVerticalComponent, + ExRangeComponent ] }) export class TeSliderSelectModule {}