UNPKG

@taiga-ui/kit

Version:

Taiga UI Angular main components kit

232 lines (227 loc) 20 kB
import { __decorate } from 'tslib'; import * as i0 from '@angular/core'; import { inject, EventEmitter, Directive, Output, signal, computed, ElementRef, Component, ChangeDetectionStrategy, Input, ViewChildren } from '@angular/core'; import * as i2 from '@angular/forms'; import { FormsModule } from '@angular/forms'; import { TuiControl } from '@taiga-ui/cdk/classes'; import { EMPTY_QUERY } from '@taiga-ui/cdk/constants'; import { tuiFallbackValueProvider } from '@taiga-ui/cdk/tokens'; import { tuiInjectElement } from '@taiga-ui/cdk/utils/dom'; import { tuiClamp, tuiRound, tuiQuantize } from '@taiga-ui/cdk/utils/math'; import { tuiPure } from '@taiga-ui/cdk/utils/miscellaneous'; import * as i3 from '@taiga-ui/kit/components/slider'; import { TUI_FLOATING_PRECISION, TUI_SLIDER_OPTIONS, tuiPercentageToKeyStepValue, tuiKeyStepValueToPercentage, TuiSliderComponent, TuiSlider } from '@taiga-ui/kit/components/slider'; import { DOCUMENT } from '@angular/common'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { tuiTypedFromEvent } from '@taiga-ui/cdk/observables'; import { merge, filter, map, tap, switchMap, startWith, takeUntil, repeat } from 'rxjs'; class TuiRangeChange { constructor() { this.doc = inject(DOCUMENT); this.el = tuiInjectElement(); this.range = inject(TuiRange); /** * TODO replace with pointer events (when all supported browsers can handle them). * Don't forget to use setPointerCapture instead of listening all doc events */ this.pointerDown$ = tuiTypedFromEvent(this.el, 'pointerdown', { passive: true, capture: true, }); this.pointerMove$ = merge(tuiTypedFromEvent(this.doc, 'touchmove').pipe(filter(({ touches }) => touches.length === 1), map(({ touches }) => touches[0]), filter((event) => !!event)), tuiTypedFromEvent(this.doc, 'mousemove')); this.pointerUp$ = merge(tuiTypedFromEvent(this.doc, 'touchend', { passive: true }), tuiTypedFromEvent(this.doc, 'mouseup', { passive: true })); this.activeThumbChange = new EventEmitter(); let activeThumb; this.pointerDown$ .pipe(tap(({ clientX, target }) => { activeThumb = this.detectActiveThumb(clientX, target); this.activeThumbChange.emit(activeThumb); if (this.range.focusable) { this.el.focus(); } }), switchMap((event) => this.pointerMove$.pipe(startWith(event))), map(({ clientX }) => this.getFractionFromEvents(clientX ?? 0)), takeUntil(this.pointerUp$), repeat(), takeUntilDestroyed()) .subscribe((fraction) => { const value = this.range.toValue(fraction); this.range.processValue(value, activeThumb === 'right'); }); } getFractionFromEvents(clickClientX) { const hostRect = this.el.getBoundingClientRect(); const value = clickClientX - hostRect.left; const total = hostRect.width; return tuiClamp(tuiRound(value / total, TUI_FLOATING_PRECISION), 0, 1); } detectActiveThumb(clientX, target) { const [leftSliderRef, rightSliderRef] = this.range.slidersRefs; switch (target) { case leftSliderRef?.nativeElement: return 'left'; case rightSliderRef?.nativeElement: return 'right'; default: return this.findNearestActiveThumb(clientX); } } findNearestActiveThumb(clientX) { const fraction = this.getFractionFromEvents(clientX); const deltaLeft = fraction * 100 - this.range.left(); const deltaRight = fraction * 100 - 100 + this.range.right(); return Math.abs(deltaLeft) > Math.abs(deltaRight) || deltaRight > 0 || (this.range.left() === 0 && this.range.right() === 100) ? 'right' : 'left'; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiRangeChange, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: TuiRangeChange, isStandalone: true, outputs: { activeThumbChange: "activeThumbChange" }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiRangeChange, decorators: [{ type: Directive, args: [{ standalone: true, }] }], ctorParameters: function () { return []; }, propDecorators: { activeThumbChange: [{ type: Output }] } }); class TuiRange extends TuiControl { constructor() { super(...arguments); // TODO: refactor to signal inputs after Angular update this.changes = signal(1); this.el = tuiInjectElement(); this.options = inject(TUI_SLIDER_OPTIONS); this.lastActiveThumb = 'right'; this.min = 0; this.max = 100; this.step = 1; this.size = this.options.size; this.segments = 1; this.keySteps = null; this.focusable = true; this.margin = 0; this.limit = Infinity; this.slidersRefs = EMPTY_QUERY; this.left = computed(() => this.toPercent(this.value()[0])); this.right = computed(() => 100 - this.toPercent(this.value()[1])); } ngOnChanges() { this.changes.set(this.changes() + 1); } processValue(value, right) { if (right) { this.updateEnd(value); } else { this.updateStart(value); } this.lastActiveThumb = right ? 'right' : 'left'; } toValue(fraction) { return tuiPercentageToKeyStepValue(tuiClamp(tuiQuantize(fraction, this.fractionStep), 0, 1) * 100, this.computedKeySteps); } get fractionStep() { return this.step / (this.max - this.min); } get computedKeySteps() { return this.computePureKeySteps(this.keySteps, this.min, this.max); } get segmentWidthRatio() { return 1 / this.segments; } changeByStep(coefficient, target) { const [sliderLeftRef, sliderRightRef] = this.slidersRefs; const leftThumbElement = sliderLeftRef?.nativeElement; const rightThumbElement = sliderRightRef?.nativeElement; const isRightThumb = target === this.el ? this.lastActiveThumb === 'right' : target === rightThumbElement; const activeThumbElement = isRightThumb ? rightThumbElement : leftThumbElement; const previousValue = isRightThumb ? this.value()[1] : this.value()[0]; /** @bad TODO think about a solution without twice conversion */ const previousFraction = this.toPercent(previousValue) / 100; const newFractionValue = previousFraction + coefficient * this.fractionStep; this.processValue(this.toValue(newFractionValue), isRightThumb); activeThumbElement?.focus(); } toPercent(value) { return (this.changes() && tuiKeyStepValueToPercentage(value, this.computedKeySteps)); } computePureKeySteps(keySteps, min, max) { return (keySteps || [ [0, min], [100, max], ]); } updateStart(value) { const newValue = Math.min(value, this.value()[1]); const distance = this.value()[1] - newValue; if (!this.checkDistance(distance)) { return; } this.onChange([newValue, this.value()[1]]); } updateEnd(value) { const newValue = Math.max(value, this.value()[0]); const distance = newValue - this.value()[0]; if (!this.checkDistance(distance)) { return; } this.onChange([this.value()[0], newValue]); } checkDistance(distance) { return tuiClamp(distance, this.margin, this.limit) === distance; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiRange, deps: null, target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: TuiRange, isStandalone: true, selector: "tui-range", inputs: { min: "min", max: "max", step: "step", size: "size", segments: "segments", keySteps: "keySteps", focusable: "focusable", margin: "margin", limit: "limit" }, host: { listeners: { "focusout": "onTouched()", "keydown.arrowUp.prevent": "changeByStep(1, $event.target)", "keydown.arrowRight.prevent": "changeByStep(1, $event.target)", "keydown.arrowLeft.prevent": "changeByStep(-1, $event.target)", "keydown.arrowDown.prevent": "changeByStep(-1, $event.target)" }, properties: { "attr.data-size": "size", "attr.tabindex": "-1", "attr.aria-disabled": "disabled()", "style.--left.%": "left()", "style.--right.%": "right()", "style.background": "options.trackColor", "class._disabled": "disabled()" } }, providers: [tuiFallbackValueProvider([0, 0])], viewQueries: [{ propertyName: "slidersRefs", predicate: TuiSliderComponent, descendants: true, read: ElementRef }], usesInheritance: true, usesOnChanges: true, hostDirectives: [{ directive: TuiRangeChange, outputs: ["activeThumbChange", "activeThumbChange"] }], ngImport: i0, template: "<div\n class=\"t-track\"\n [style.--bg-size-ratio]=\"1 - segmentWidthRatio\"\n [style.--segment-width.%]=\"segmentWidthRatio * 100\"\n>\n <input\n automation-id=\"tui-range__left\"\n readonly\n step=\"any\"\n tuiSlider\n type=\"range\"\n class=\"t-thumb\"\n [disabled]=\"disabled()\"\n [keySteps]=\"computedKeySteps\"\n [max]=\"max\"\n [min]=\"min\"\n [ngModel]=\"value()[0]\"\n [ngModelOptions]=\"{standalone: true}\"\n [size]=\"size\"\n [tabIndex]=\"focusable ? 0 : -1\"\n />\n <input\n automation-id=\"tui-range__right\"\n readonly\n step=\"any\"\n tuiSlider\n type=\"range\"\n class=\"t-thumb\"\n [disabled]=\"disabled()\"\n [keySteps]=\"computedKeySteps\"\n [max]=\"max\"\n [min]=\"min\"\n [ngModel]=\"value()[1]\"\n [ngModelOptions]=\"{standalone: true}\"\n [size]=\"size\"\n [tabIndex]=\"focusable ? 0 : -1\"\n />\n</div>\n", styles: [":host{position:relative;display:block;block-size:.125rem;border-radius:var(--tui-radius-m);background:var(--tui-border-normal);cursor:pointer;outline:none;margin:.4375rem 0;touch-action:pan-x}:host:active{cursor:ew-resize}:host:after{content:\"\";position:absolute;top:-.4375rem;bottom:-.4375rem;inline-size:100%}:host._disabled{opacity:var(--tui-disabled-opacity);pointer-events:none}:host[data-size=s] .t-track{position:relative;margin:0 .25rem;block-size:100%}:host[data-size=s] .t-track:before{content:\"\";position:absolute;top:0;left:max(calc(var(--left) - 1px),1px);right:max(var(--right),1px);block-size:100%;background:var(--tui-background-accent-1);margin:0 -.25rem}:host[data-size=s] .t-track:after{position:absolute;top:0;left:0;bottom:0;right:0;content:\"\";left:.125rem;right:.375rem;background-image:repeating-linear-gradient(to right,var(--tui-text-tertiary) 0 .25rem,transparent 0 calc(var(--segment-width) / var(--bg-size-ratio)));background-position-x:right;background-repeat:no-repeat;background-size:calc(100% * var(--bg-size-ratio))}:host[data-size=m] .t-track{position:relative;margin:0 .375rem;block-size:100%}:host[data-size=m] .t-track:before{content:\"\";position:absolute;top:0;left:max(calc(var(--left) - 1px),1px);right:max(var(--right),1px);block-size:100%;background:var(--tui-background-accent-1);margin:0 -.375rem}:host[data-size=m] .t-track:after{position:absolute;top:0;left:0;bottom:0;right:0;content:\"\";left:.25rem;right:.5rem;background-image:repeating-linear-gradient(to right,var(--tui-text-tertiary) 0 .25rem,transparent 0 calc(var(--segment-width) / var(--bg-size-ratio)));background-position-x:right;background-repeat:no-repeat;background-size:calc(100% * var(--bg-size-ratio))}.t-thumb{pointer-events:none;position:absolute;top:.0625rem;left:0;right:0;z-index:1;transform:translateY(-50%)}.t-thumb::-webkit-slider-thumb{pointer-events:all}.t-thumb::-moz-range-thumb{pointer-events:all}input[type=range].t-thumb::-webkit-slider-runnable-track{background:transparent}input[type=range].t-thumb::-moz-range-track{background:transparent}input[type=range].t-thumb::-moz-range-progress{background:transparent}input[type=range].t-thumb::-ms-track{background:transparent}input[type=range].t-thumb::-ms-fill-lower{background:transparent}.t-thumb:last-of-type{--tui-slider-thumb-transform: translateX(50%) translateX(1px)}.t-thumb:first-of-type{--tui-slider-thumb-transform: translateX(-50%) translateX(-1px)}:host._disabled .t-thumb{opacity:1}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3.TuiSliderComponent, selector: "input[type=range][tuiSlider]", inputs: ["size", "segments"] }, { kind: "directive", type: i3.TuiSliderKeySteps, selector: "input[tuiSlider][keySteps]", inputs: ["keySteps"] }, { kind: "directive", type: i3.TuiSliderReadonly, selector: "input[tuiSlider][readonly]", inputs: ["readonly"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } __decorate([ tuiPure ], TuiRange.prototype, "computePureKeySteps", null); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TuiRange, decorators: [{ type: Component, args: [{ standalone: true, selector: 'tui-range', imports: [FormsModule, TuiSlider], changeDetection: ChangeDetectionStrategy.OnPush, providers: [tuiFallbackValueProvider([0, 0])], hostDirectives: [ { directive: TuiRangeChange, outputs: ['activeThumbChange'], }, ], host: { '[attr.data-size]': 'size', '[attr.tabindex]': '-1', '[attr.aria-disabled]': 'disabled()', '[style.--left.%]': 'left()', '[style.--right.%]': 'right()', '[style.background]': 'options.trackColor', '[class._disabled]': 'disabled()', '(focusout)': 'onTouched()', '(keydown.arrowUp.prevent)': 'changeByStep(1, $event.target)', '(keydown.arrowRight.prevent)': 'changeByStep(1, $event.target)', '(keydown.arrowLeft.prevent)': 'changeByStep(-1, $event.target)', '(keydown.arrowDown.prevent)': 'changeByStep(-1, $event.target)', }, template: "<div\n class=\"t-track\"\n [style.--bg-size-ratio]=\"1 - segmentWidthRatio\"\n [style.--segment-width.%]=\"segmentWidthRatio * 100\"\n>\n <input\n automation-id=\"tui-range__left\"\n readonly\n step=\"any\"\n tuiSlider\n type=\"range\"\n class=\"t-thumb\"\n [disabled]=\"disabled()\"\n [keySteps]=\"computedKeySteps\"\n [max]=\"max\"\n [min]=\"min\"\n [ngModel]=\"value()[0]\"\n [ngModelOptions]=\"{standalone: true}\"\n [size]=\"size\"\n [tabIndex]=\"focusable ? 0 : -1\"\n />\n <input\n automation-id=\"tui-range__right\"\n readonly\n step=\"any\"\n tuiSlider\n type=\"range\"\n class=\"t-thumb\"\n [disabled]=\"disabled()\"\n [keySteps]=\"computedKeySteps\"\n [max]=\"max\"\n [min]=\"min\"\n [ngModel]=\"value()[1]\"\n [ngModelOptions]=\"{standalone: true}\"\n [size]=\"size\"\n [tabIndex]=\"focusable ? 0 : -1\"\n />\n</div>\n", styles: [":host{position:relative;display:block;block-size:.125rem;border-radius:var(--tui-radius-m);background:var(--tui-border-normal);cursor:pointer;outline:none;margin:.4375rem 0;touch-action:pan-x}:host:active{cursor:ew-resize}:host:after{content:\"\";position:absolute;top:-.4375rem;bottom:-.4375rem;inline-size:100%}:host._disabled{opacity:var(--tui-disabled-opacity);pointer-events:none}:host[data-size=s] .t-track{position:relative;margin:0 .25rem;block-size:100%}:host[data-size=s] .t-track:before{content:\"\";position:absolute;top:0;left:max(calc(var(--left) - 1px),1px);right:max(var(--right),1px);block-size:100%;background:var(--tui-background-accent-1);margin:0 -.25rem}:host[data-size=s] .t-track:after{position:absolute;top:0;left:0;bottom:0;right:0;content:\"\";left:.125rem;right:.375rem;background-image:repeating-linear-gradient(to right,var(--tui-text-tertiary) 0 .25rem,transparent 0 calc(var(--segment-width) / var(--bg-size-ratio)));background-position-x:right;background-repeat:no-repeat;background-size:calc(100% * var(--bg-size-ratio))}:host[data-size=m] .t-track{position:relative;margin:0 .375rem;block-size:100%}:host[data-size=m] .t-track:before{content:\"\";position:absolute;top:0;left:max(calc(var(--left) - 1px),1px);right:max(var(--right),1px);block-size:100%;background:var(--tui-background-accent-1);margin:0 -.375rem}:host[data-size=m] .t-track:after{position:absolute;top:0;left:0;bottom:0;right:0;content:\"\";left:.25rem;right:.5rem;background-image:repeating-linear-gradient(to right,var(--tui-text-tertiary) 0 .25rem,transparent 0 calc(var(--segment-width) / var(--bg-size-ratio)));background-position-x:right;background-repeat:no-repeat;background-size:calc(100% * var(--bg-size-ratio))}.t-thumb{pointer-events:none;position:absolute;top:.0625rem;left:0;right:0;z-index:1;transform:translateY(-50%)}.t-thumb::-webkit-slider-thumb{pointer-events:all}.t-thumb::-moz-range-thumb{pointer-events:all}input[type=range].t-thumb::-webkit-slider-runnable-track{background:transparent}input[type=range].t-thumb::-moz-range-track{background:transparent}input[type=range].t-thumb::-moz-range-progress{background:transparent}input[type=range].t-thumb::-ms-track{background:transparent}input[type=range].t-thumb::-ms-fill-lower{background:transparent}.t-thumb:last-of-type{--tui-slider-thumb-transform: translateX(50%) translateX(1px)}.t-thumb:first-of-type{--tui-slider-thumb-transform: translateX(-50%) translateX(-1px)}:host._disabled .t-thumb{opacity:1}\n"] }] }], propDecorators: { min: [{ type: Input }], max: [{ type: Input }], step: [{ type: Input }], size: [{ type: Input }], segments: [{ type: Input }], keySteps: [{ type: Input }], focusable: [{ type: Input }], margin: [{ type: Input }], limit: [{ type: Input }], slidersRefs: [{ type: ViewChildren, args: [TuiSliderComponent, { read: ElementRef }] }], computePureKeySteps: [] } }); /** * Generated bundle index. Do not edit. */ export { TuiRange, TuiRangeChange }; //# sourceMappingURL=taiga-ui-kit-components-range.mjs.map