From 3d2c8b2b38e7eace0e8f4623f7bca02b4bb125a0 Mon Sep 17 00:00:00 2001 From: Barsukov Nikita Date: Tue, 19 Apr 2022 14:12:30 +0300 Subject: [PATCH] fix(kit): `Slider` readonly state (mobile chrome bug) --- .../kit/components/range/range.style.less | 1 + .../slider/slider-readonly.directive.ts | 55 +++++++++++++++++-- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/projects/kit/components/range/range.style.less b/projects/kit/components/range/range.style.less index fece30f7e751..1364938109ed 100644 --- a/projects/kit/components/range/range.style.less +++ b/projects/kit/components/range/range.style.less @@ -12,6 +12,7 @@ cursor: pointer; outline: none; margin: @extra-click-area-space 0; + touch-action: pan-x; &:active { cursor: ew-resize; diff --git a/projects/kit/components/slider/slider-readonly.directive.ts b/projects/kit/components/slider/slider-readonly.directive.ts index 9ceadd634711..e8a3627937ca 100644 --- a/projects/kit/components/slider/slider-readonly.directive.ts +++ b/projects/kit/components/slider/slider-readonly.directive.ts @@ -1,5 +1,13 @@ -import {Directive, HostListener, Input} from '@angular/core'; -import {tuiDefaultProp} from '@taiga-ui/cdk'; +import {DOCUMENT} from '@angular/common'; +import {Directive, ElementRef, HostListener, Inject, Input} from '@angular/core'; +import { + tuiCoerceBooleanProperty, + tuiDefaultProp, + TuiDestroyService, + typedFromEvent, +} from '@taiga-ui/cdk'; +import {combineLatest, merge, Observable} from 'rxjs'; +import {filter, mapTo, takeUntil, tap} from 'rxjs/operators'; const SLIDER_INTERACTION_KEYS = new Set([ 'ArrowLeft', @@ -16,18 +24,55 @@ const SLIDER_INTERACTION_KEYS = new Set([ * Native doesn't work. * This directive imitates this native behaviour. */ +// @dynamic @Directive({ selector: 'input[tuiSlider][readonly]', + providers: [TuiDestroyService], }) export class TuiSliderReadonlyDirective { @Input() @tuiDefaultProp() - readonly: '' | boolean = true; + readonly: string | boolean = true; + + constructor( + @Inject(ElementRef) elementRef: ElementRef, + @Inject(DOCUMENT) documentRef: Document, + @Inject(TuiDestroyService) + destroy$: Observable, + ) { + const touchStart$ = typedFromEvent(elementRef.nativeElement, 'touchstart', { + passive: false, + }); + const touchMove$ = typedFromEvent(documentRef, 'touchmove', { + passive: false, + }); + const touchEnd$ = typedFromEvent(documentRef, 'touchend', { + passive: true, + }); + + const shouldPreventMove$ = merge( + touchStart$.pipe( + tap(e => this.preventEvent(e)), + mapTo(true), + ), + touchEnd$.pipe(mapTo(false)), + ); + + /** + * @bad TODO think about another solution. + * Keep in mind that preventing touch event (on slider) inside `@HostListener('touchstart')` doesn't work for mobile chrome. + */ + combineLatest([touchMove$, shouldPreventMove$]) + .pipe( + filter(([_, shouldPreventMove]) => shouldPreventMove), + takeUntil(destroy$), + ) + .subscribe(([moveEvent]) => this.preventEvent(moveEvent)); + } @HostListener('mousedown', ['$event']) - @HostListener('touchstart', ['$event']) preventEvent(event: Event) { - if (this.readonly === '' || this.readonly) { + if (event.cancelable && tuiCoerceBooleanProperty(this.readonly)) { event.preventDefault(); } }