Skip to content

Commit 0a7c5d3

Browse files
authored
fix(slider): add pointer move handling; clear old HammerJS pan event. (#14052)
1 parent 50476ee commit 0a7c5d3

File tree

3 files changed

+108
-115
lines changed

3 files changed

+108
-115
lines changed

projects/igniteui-angular/src/lib/core/styles/components/slider/_slider-theme.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
flex-grow: 1;
176176
align-items: center;
177177
transition: all .2s $out-quad;
178+
touch-action: pan-y pinch-zoom;
178179

179180
&:hover {
180181
%igx-slider-track-fill {

projects/igniteui-angular/src/lib/slider/slider.component.spec.ts

Lines changed: 58 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ import { UIInteractions, wait } from '../test-utils/ui-interactions.spec';
99
import { IgxSliderType, IgxThumbFromTemplateDirective, IgxThumbToTemplateDirective, IRangeSliderValue, TickLabelsOrientation, TicksOrientation } from './slider.common';
1010
import { IgxSliderComponent } from './slider.component';
1111

12-
declare let Simulator: any;
1312
const SLIDER_CLASS = '.igx-slider';
13+
const THUMB_TAG = 'igx-thumb';
1414
const THUMB_TO_CLASS = '.igx-slider-thumb-to';
15+
const THUMB_TO_PRESSED_CLASS = '.igx-slider-thumb-to--pressed';
1516
const THUMB_FROM_CLASS = '.igx-slider-thumb-from';
17+
const THUMB_LABEL = 'igx-thumb-label';
1618
const SLIDER_TICKS_ELEMENT = '.igx-slider__ticks';
1719
const SLIDER_TICKS_TOP_ELEMENT = '.igx-slider__ticks--top';
1820
const SLIDER_PRIMARY_GROUP_TICKS_CLASS_NAME = 'igx-slider__ticks-group--tall';
@@ -415,46 +417,57 @@ describe('IgxSlider', () => {
415417
expect(sliderInstance.maxValue).toEqual(expectedMax);
416418
});
417419

418-
it('continuous(smooth) sliding should be allowed', (done) => {
419-
pending(`panRight trigers pointerdown where we are capturing all pointer events by passing a valid pointerId.
420-
Pan guesture does not propagate that option. Respectively (no active pointer...) error is thrown.`);
420+
it('continuous(smooth) sliding should be allowed', async() => {
421421
sliderInstance.continuous = true;
422+
sliderInstance.thumbLabelVisibilityDuration = 10;
422423
fixture.detectChanges();
423424

424425
expect(sliderInstance.continuous).toBe(true);
425426
expect(sliderInstance.value).toBe(150);
426-
const sliderEl = fixture.debugElement.query(By.css(SLIDER_CLASS)).nativeElement;
427-
sliderEl.dispatchEvent(new PointerEvent('pointerdown', { pointerId: 1, clientX: 200 }));
427+
const thumbEl = fixture.debugElement.query(By.css(THUMB_TAG)).nativeElement;
428+
const { x: sliderX, width: sliderWidth } = thumbEl.getBoundingClientRect();
429+
const startX = sliderX + sliderWidth / 2;
430+
431+
thumbEl.dispatchEvent(new Event('focus'));
428432
fixture.detectChanges();
429-
expect(sliderEl).toBeDefined();
430-
return panRight(sliderEl, sliderEl.offsetHeight, sliderEl.offsetWidth, 200)
431-
.then(() => {
432-
fixture.detectChanges();
433-
const activeThumb = fixture.debugElement.query(By.css('.igx-slider-thumb-to--active'));
434-
expect(sliderInstance.value).toBeGreaterThan(150);
435-
expect(activeThumb).toBeNull();
436-
done();
437-
});
433+
434+
(sliderInstance as any).onPointerDown(new PointerEvent('pointerdown', { pointerId: 1, clientX: startX }));
435+
fixture.detectChanges();
436+
437+
(sliderInstance as any).onPointerMove(new PointerEvent('pointermove', { pointerId: 1, clientX: startX + 150 }));
438+
fixture.detectChanges();
439+
await wait();
440+
441+
let activeThumb = fixture.debugElement.query(By.css(THUMB_TO_PRESSED_CLASS));
442+
expect(activeThumb).not.toBeNull();
443+
expect(sliderInstance.value).toBeGreaterThan(sliderInstance.minValue);
444+
445+
(sliderInstance as any).onPointerMove(new PointerEvent('pointermove', { pointerId: 1, clientX: startX }));
446+
fixture.detectChanges();
447+
await wait();
448+
449+
expect(sliderInstance.value).toEqual(sliderInstance.minValue);
438450
});
439451

440-
it('should not move thumb slider and value should remain the same when slider is disabled', (done) => {
441-
pending(`panRight trigers pointerdown where we are capturing all pointer events by passing a valid pointerId.
442-
Pan guesture does not propagate that option. Respectively (no active pointer...) error is thrown.`);
452+
it('should not move thumb slider and value should remain the same when slider is disabled', async() => {
443453
sliderInstance.disabled = true;
444454
fixture.detectChanges();
445455

446-
const sliderEl = fixture.debugElement.query(By.css(SLIDER_CLASS)).nativeElement;
447-
sliderEl.dispatchEvent( new MouseEvent('mosedown'));
456+
const thumbEl = fixture.debugElement.query(By.css(THUMB_TAG)).nativeElement;
457+
const { x: sliderX, width: sliderWidth } = thumbEl.getBoundingClientRect();
458+
const startX = sliderX + sliderWidth / 2;
459+
460+
thumbEl.dispatchEvent(new Event('focus'));
448461
fixture.detectChanges();
449-
expect(sliderEl).toBeDefined();
450-
return panRight(sliderEl, sliderEl.offsetHeight, sliderEl.offsetWidth, 200)
451-
.then(() => {
452-
fixture.detectChanges();
453-
const activeThumb = fixture.debugElement.query(By.css('.igx-slider-thumb-to--active'));
454-
expect(activeThumb).toBeDefined();
455-
expect(sliderInstance.value).toBe(sliderInstance.minValue);
456-
done();
457-
});
462+
463+
(sliderInstance as any).onPointerDown(new PointerEvent('pointerdown', { pointerId: 1, clientX: startX }));
464+
fixture.detectChanges();
465+
466+
(sliderInstance as any).onPointerMove(new PointerEvent('pointermove', { pointerId: 1, clientX: startX + 150 }));
467+
fixture.detectChanges();
468+
await wait();
469+
470+
expect(sliderInstance.value).toBe(sliderInstance.minValue);
458471
});
459472
});
460473

@@ -474,32 +487,27 @@ describe('IgxSlider', () => {
474487
expect((slider.value as IRangeSliderValue).upper).toBe(slider.upperBound);
475488
});
476489

477-
it('continuous(smooth) sliding should be allowed', (done) => {
478-
pending(`panRight trigers pointerdown where we are capturing all pointer events by passing a valid pointerId.
479-
Pan guesture does not propagate that option. Respectively (no active pointer...) error is thrown.`);
480-
const fromThumb = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
490+
it('continuous(smooth) sliding should be allowed', async() => {
481491
slider.continuous = true;
482492
fixture.detectChanges();
483493

484-
expect(slider.continuous).toBe(true);
494+
const fromThumb = fixture.debugElement.query(By.css(THUMB_FROM_CLASS)).nativeElement;
495+
const { x: sliderX, width: sliderWidth } = fromThumb.getBoundingClientRect();
496+
const startX = sliderX + sliderWidth / 2;
485497

486-
const sliderEl = fixture.debugElement.query(By.css(SLIDER_CLASS)).nativeElement;
487-
sliderEl.dispatchEvent( new PointerEvent('pointerdown', { pointerId: 1 }));
488-
fixture.detectChanges();
489498
fromThumb.dispatchEvent(new Event('focus'));
490499
fixture.detectChanges();
491500

492-
expect(sliderEl).toBeDefined();
493-
return panRight(sliderEl, sliderEl.offsetHeight, sliderEl.offsetWidth, 200)
494-
.then(() => {
495-
fixture.detectChanges();
496-
const activetoThumb = fixture.debugElement.query(By.css('.igx-slider-thumb-to--active'));
497-
const activefromThumb = fixture.debugElement.query(By.css('.igx-slider-thumb-from--active'));
498-
expect(slider.value).toEqual({ lower: 60, upper: 100 });
499-
expect(activetoThumb).toBeNull();
500-
expect(activefromThumb).toBeNull();
501-
done();
502-
});
501+
(slider as any).onPointerDown(new PointerEvent('pointerdown', { pointerId: 1, clientX: startX }));
502+
fixture.detectChanges();
503+
await wait();
504+
505+
(slider as any).onPointerMove(new PointerEvent('pointermove', { pointerId: 1, clientX: startX + 150 }));
506+
fixture.detectChanges();
507+
await wait();
508+
509+
expect((slider.value as any).lower).toBeGreaterThan(slider.minValue);
510+
expect((slider.value as any).upper).toEqual(slider.maxValue);
503511
});
504512

505513
// K.D. Removing this functionality because of 0 benefit and lots of issues.
@@ -1750,8 +1758,8 @@ describe('IgxSlider', () => {
17501758
fix.detectChanges();
17511759

17521760
const inst = fix.componentInstance;
1753-
const thumbs = fix.debugElement.queryAll(By.css('igx-thumb'));
1754-
const labels = fix.debugElement.queryAll(By.css('igx-thumb-label'));
1761+
const thumbs = fix.debugElement.queryAll(By.css(THUMB_TAG));
1762+
const labels = fix.debugElement.queryAll(By.css(THUMB_LABEL));
17551763

17561764
expect(inst.dir.rtl).toEqual(true);
17571765

@@ -1803,21 +1811,6 @@ describe('IgxSlider', () => {
18031811
});
18041812
});
18051813

1806-
const panRight = (element, elementHeight, elementWidth, duration) => {
1807-
const panOptions = {
1808-
deltaX: elementWidth * 0.6,
1809-
clientX: elementWidth * 0.6,
1810-
deltaY: 0,
1811-
duration,
1812-
pos: [element.offsetLeft, elementHeight * 0.5]
1813-
};
1814-
1815-
return new Promise<void>(resolve => {
1816-
Simulator.gestures.pan(element, panOptions, () => {
1817-
resolve(null);
1818-
});
1819-
});
1820-
};
18211814

18221815
const verifySecondaryTicsLabelsAreHidden = (ticks, hidden) => {
18231816
const allTicks = Array.from(ticks.nativeElement.querySelectorAll(`${SLIDER_GROUP_TICKS_CLASS}`));

projects/igniteui-angular/src/lib/slider/slider.component.ts

Lines changed: 49 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import {
44
HostBinding, HostListener, Input, NgZone, OnChanges, OnDestroy, OnInit, Output, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChild, ViewChildren, booleanAttribute
55
} from '@angular/core';
66
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
7-
import { merge, noop, Observable, Subject, timer } from 'rxjs';
8-
import { takeUntil, throttleTime } from 'rxjs/operators';
7+
import { animationFrameScheduler, fromEvent, interval, merge, noop, Observable, Subject, timer } from 'rxjs';
8+
import { takeUntil, throttle, throttleTime } from 'rxjs/operators';
99
import { EditorProvider } from '../core/edit-provider';
1010
import { resizeObservable } from '../core/utils';
1111
import { IgxDirectionality } from '../services/direction/directionality';
@@ -774,6 +774,7 @@ export class IgxSliderComponent implements
774774
// ticks
775775
private _primaryTicks = 0;
776776
private _secondaryTicks = 0;
777+
private _sliding = false;
777778

778779
private _labels = new Array<number | string | boolean | null | undefined>();
779780
private _type: IgxSliderType = IgxSliderType.SLIDER;
@@ -793,41 +794,6 @@ export class IgxSliderComponent implements
793794
this.stepDistance = this._step;
794795
}
795796

796-
/**
797-
* @hidden
798-
*/
799-
@HostListener('pointerdown', ['$event'])
800-
public onPointerDown($event: PointerEvent) {
801-
this.findClosestThumb($event);
802-
803-
if (!this.thumbTo.isActive && this.thumbFrom === undefined) {
804-
return;
805-
}
806-
807-
const activeThumb = this.thumbTo.isActive ? this.thumbTo : this.thumbFrom;
808-
activeThumb.nativeElement.setPointerCapture($event.pointerId);
809-
this.showSliderIndicators();
810-
811-
$event.preventDefault();
812-
}
813-
814-
815-
/**
816-
* @hidden
817-
*/
818-
@HostListener('pointerup', ['$event'])
819-
public onPointerUp($event: PointerEvent) {
820-
if (!this.thumbTo.isActive && this.thumbFrom === undefined) {
821-
return;
822-
}
823-
824-
const activeThumb = this.thumbTo.isActive ? this.thumbTo : this.thumbFrom;
825-
activeThumb.nativeElement.releasePointerCapture($event.pointerId);
826-
827-
this.hideSliderIndicators();
828-
this.dragFinished.emit(this.value);
829-
}
830-
831797
/**
832798
* @hidden
833799
*/
@@ -836,14 +802,6 @@ export class IgxSliderComponent implements
836802
this.toggleSliderIndicators();
837803
}
838804

839-
/**
840-
* @hidden
841-
*/
842-
@HostListener('pan', ['$event'])
843-
public onPanListener($event) {
844-
this.update($event.srcEvent.clientX);
845-
}
846-
847805
/**
848806
* Returns whether the `IgxSliderComponent` type is RANGE.
849807
* ```typescript
@@ -1054,6 +1012,11 @@ export class IgxSliderComponent implements
10541012
takeUntil(this._destroyer$)).subscribe(() => this._ngZone.run(() => {
10551013
this.stepDistance = this.calculateStepDistance();
10561014
}));
1015+
fromEvent(this._el.nativeElement, 'pointermove').pipe(
1016+
throttle(() => interval(0, animationFrameScheduler)),
1017+
takeUntil(this._destroyer$)).subscribe(($event: PointerEvent) => this._ngZone.run(() => {
1018+
this.onPointerMove($event);
1019+
}));
10571020
});
10581021
}
10591022

@@ -1195,6 +1158,42 @@ export class IgxSliderComponent implements
11951158
}
11961159
}
11971160

1161+
@HostListener('pointerdown', ['$event'])
1162+
private onPointerDown($event: PointerEvent) {
1163+
this.findClosestThumb($event);
1164+
1165+
if (!this.thumbTo.isActive && this.thumbFrom === undefined) {
1166+
return;
1167+
}
1168+
1169+
this._sliding = true;
1170+
const activeThumb = this.thumbTo.isActive ? this.thumbTo : this.thumbFrom;
1171+
activeThumb.nativeElement.setPointerCapture($event.pointerId);
1172+
this.showSliderIndicators();
1173+
1174+
$event.preventDefault();
1175+
}
1176+
1177+
private onPointerMove($event: PointerEvent) {
1178+
if (this._sliding) {
1179+
this.update($event.clientX);
1180+
}
1181+
}
1182+
1183+
@HostListener('pointerup', ['$event'])
1184+
private onPointerUp($event: PointerEvent) {
1185+
if (!this.thumbTo.isActive && this.thumbFrom === undefined) {
1186+
return;
1187+
}
1188+
1189+
const activeThumb = this.thumbTo.isActive ? this.thumbTo : this.thumbFrom;
1190+
activeThumb.nativeElement.releasePointerCapture($event.pointerId);
1191+
1192+
this._sliding = false;
1193+
this.hideSliderIndicators();
1194+
this.dragFinished.emit(this.value);
1195+
}
1196+
11981197
private validateInitialValue(value: IRangeSliderValue) {
11991198
if (value.upper < value.lower) {
12001199
const temp = value.upper;
@@ -1291,21 +1290,21 @@ export class IgxSliderComponent implements
12911290
}
12921291

12931292
private setTickInterval() {
1294-
let interval;
1293+
let tickInterval;
12951294
const trackProgress = 100;
12961295

12971296
if (this.labelsViewEnabled) {
12981297
// Calc ticks depending on the labels length;
1299-
interval = ((trackProgress / (this.labels.length - 1) * 10)) / 10;
1298+
tickInterval = ((trackProgress / (this.labels.length - 1) * 10)) / 10;
13001299
} else {
13011300
const trackRange = this.maxValue - this.minValue;
1302-
interval = this.step > 1 ?
1301+
tickInterval = this.step > 1 ?
13031302
(trackProgress / ((trackRange / this.step)) * 10) / 10
13041303
: null;
13051304
}
13061305

1307-
this.renderer.setStyle(this.ticks.nativeElement, 'stroke-dasharray', `0, ${interval * Math.sqrt(2)}%`);
1308-
this.renderer.setStyle(this.ticks.nativeElement, 'visibility', this.continuous || interval === null ? 'hidden' : 'visible');
1306+
this.renderer.setStyle(this.ticks.nativeElement, 'stroke-dasharray', `0, ${tickInterval * Math.sqrt(2)}%`);
1307+
this.renderer.setStyle(this.ticks.nativeElement, 'visibility', this.continuous || tickInterval === null ? 'hidden' : 'visible');
13091308
}
13101309

13111310
private showSliderIndicators() {

0 commit comments

Comments
 (0)