diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d500c1..5d4ab52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ + * added cancelUserChange input EventEmitter (issue #139) + # 18.0.0 * add support for Angular 18 (issue #379) diff --git a/src/demo-app/app/app-router.config.ts b/src/demo-app/app/app-router.config.ts index c25b882..11d9df6 100644 --- a/src/demo-app/app/app-router.config.ts +++ b/src/demo-app/app/app-router.config.ts @@ -10,6 +10,7 @@ import { LimitedRangeSliderComponent, LimitedSliderComponent, NoSwitchingRangeSliderComponent, + PreventChangeOnScrollSliderComponent, PushRangeSliderComponent, RangeSliderComponent, ReactiveFormRangeSliderComponent, @@ -34,6 +35,7 @@ export const routerConfig: Routes = [ { path: 'limited-range-slider', component: LimitedRangeSliderComponent }, { path: 'limited-slider', component: LimitedSliderComponent }, { path: 'no-switching-range-slider', component: NoSwitchingRangeSliderComponent }, + { path: 'prevent-change-on-scroll-slider', component: PreventChangeOnScrollSliderComponent }, { path: 'push-range-slider', component: PushRangeSliderComponent }, { path: 'range-slider', component: RangeSliderComponent }, { path: 'reactive-form-range-slider', component: ReactiveFormRangeSliderComponent }, diff --git a/src/demo-app/app/app.module.ts b/src/demo-app/app/app.module.ts index caaa36e..196d076 100644 --- a/src/demo-app/app/app.module.ts +++ b/src/demo-app/app/app.module.ts @@ -38,6 +38,7 @@ import { LogScaleSliderComponent, ManualRefreshSliderComponent, NoSwitchingRangeSliderComponent, + PreventChangeOnScrollSliderComponent, PushRangeSliderComponent, RangeSliderComponent, ReactiveFormRangeSliderComponent, @@ -96,6 +97,7 @@ import { routerConfig, routerOptions } from './app-router.config'; LogScaleSliderComponent, ManualRefreshSliderComponent, NoSwitchingRangeSliderComponent, + PreventChangeOnScrollSliderComponent, PushRangeSliderComponent, RangeSliderComponent, ReactiveFormRangeSliderComponent, diff --git a/src/demo-app/app/demos.component.html b/src/demo-app/app/demos.component.html index 4f74785..94e3829 100644 --- a/src/demo-app/app/demos.component.html +++ b/src/demo-app/app/demos.component.html @@ -9,6 +9,7 @@ + diff --git a/src/demo-app/app/snippets/index.ts b/src/demo-app/app/snippets/index.ts index 84011d4..9bca2d9 100644 --- a/src/demo-app/app/snippets/index.ts +++ b/src/demo-app/app/snippets/index.ts @@ -25,6 +25,7 @@ export * from './limited-slider/limited-slider.component'; export * from './log-scale-slider/log-scale-slider.component'; export * from './manual-refresh-slider/manual-refresh-slider.component'; export * from './no-switching-range-slider/no-switching-range-slider.component'; +export * from './prevent-change-on-scroll-slider/prevent-change-on-scroll-slider.component'; export * from './push-range-slider/push-range-slider.component'; export * from './range-slider/range-slider.component'; export * from './reactive-form-range-slider/reactive-form-range-slider.component'; diff --git a/src/demo-app/app/snippets/prevent-change-on-scroll-slider/prevent-change-on-scroll-slider.component.id-template.html b/src/demo-app/app/snippets/prevent-change-on-scroll-slider/prevent-change-on-scroll-slider.component.id-template.html new file mode 100644 index 0000000..c9a54bb --- /dev/null +++ b/src/demo-app/app/snippets/prevent-change-on-scroll-slider/prevent-change-on-scroll-slider.component.id-template.html @@ -0,0 +1 @@ +prevent-change-on-scroll-slider diff --git a/src/demo-app/app/snippets/prevent-change-on-scroll-slider/prevent-change-on-scroll-slider.component.template.html b/src/demo-app/app/snippets/prevent-change-on-scroll-slider/prevent-change-on-scroll-slider.component.template.html new file mode 100644 index 0000000..a6169c5 --- /dev/null +++ b/src/demo-app/app/snippets/prevent-change-on-scroll-slider/prevent-change-on-scroll-slider.component.template.html @@ -0,0 +1,2 @@ +

Value:

+ diff --git a/src/demo-app/app/snippets/prevent-change-on-scroll-slider/prevent-change-on-scroll-slider.component.title-template.html b/src/demo-app/app/snippets/prevent-change-on-scroll-slider/prevent-change-on-scroll-slider.component.title-template.html new file mode 100644 index 0000000..93fb412 --- /dev/null +++ b/src/demo-app/app/snippets/prevent-change-on-scroll-slider/prevent-change-on-scroll-slider.component.title-template.html @@ -0,0 +1 @@ +Prevent change on page scroll using touch gesture diff --git a/src/demo-app/app/snippets/prevent-change-on-scroll-slider/prevent-change-on-scroll-slider.component.ts b/src/demo-app/app/snippets/prevent-change-on-scroll-slider/prevent-change-on-scroll-slider.component.ts new file mode 100644 index 0000000..8d4b4cf --- /dev/null +++ b/src/demo-app/app/snippets/prevent-change-on-scroll-slider/prevent-change-on-scroll-slider.component.ts @@ -0,0 +1,20 @@ +import { Component, HostListener, EventEmitter } from '@angular/core'; +import { Options } from '@local/ngx-slider'; + +@Component({ + selector: 'app-prevent-change-on-scroll-slider', + templateUrl: './prevent-change-on-scroll-slider.component.html' +}) +export class PreventChangeOnScrollSliderComponent { + value: number = 100; + options: Options = { + floor: 0, + ceil: 250 + }; + emitOnScroll: EventEmitter = new EventEmitter; + + @HostListener('window:scroll', ['$event']) + public onScroll(event: any): void { + this.emitOnScroll.emit(); + } +} diff --git a/src/ngx-slider/lib/slider.component.ts b/src/ngx-slider/lib/slider.component.ts index 4bb2ab8..5f0d47e 100644 --- a/src/ngx-slider/lib/slider.component.ts +++ b/src/ngx-slider/lib/slider.component.ts @@ -195,6 +195,18 @@ export class SliderComponent ); } + private cancelUserChangeSubscription: any; + @Input() set cancelUserChange(cancelUserChange: EventEmitter) { + this.unsubscribeCancelUserChange(); + + this.cancelUserChangeSubscription = cancelUserChange.subscribe(() => { + if (this.moving) { + this.positionTrackingHandle(this.preStartHandleValue); + this.forceEnd(true); + } + }); + } + // Slider type, true means range slider public get range(): boolean { return ( @@ -240,6 +252,8 @@ export class SliderComponent private touchId: number = null; // Values recorded when first dragging the bar private dragging: Dragging = new Dragging(); + // Value of hanlde at the beginning of onStart() + private preStartHandleValue: number = null; /* Slider DOM elements */ @@ -575,6 +589,13 @@ export class SliderComponent } } + private unsubscribeCancelUserChange(): void { + if (!ValueHelper.isNullOrUndefined(this.cancelUserChangeSubscription)) { + this.cancelUserChangeSubscription.unsubscribe(); + this.cancelUserChangeSubscription = null; + } + } + private getPointerElement(pointerType: PointerType): SliderHandleDirective { if (pointerType === PointerType.Min) { return this.minHandleElement; @@ -2165,6 +2186,10 @@ export class SliderComponent this.getPointerElement(pointerType); pointerElement.active = true; + // Store currentTrackingValue as soon as it is available to allow + // the slide to be canceled. (E.g. on scroll detected.) + this.preStartHandleValue = this.getCurrentTrackingValue(); + if (this.viewOptions.keyboardSupport) { pointerElement.focus(); } @@ -2294,18 +2319,16 @@ export class SliderComponent this.positionTrackingHandle(newValue); } - private onEnd(event: MouseEvent | TouchEvent): void { - if (CompatibilityHelper.isTouchEvent(event)) { - const changedTouches: TouchList = (event as TouchEvent).changedTouches; - if (changedTouches[0].identifier !== this.touchId) { - return; - } - } - + private forceEnd(disableAnimation: boolean = false): void { this.moving = false; if (this.viewOptions.animate) { this.sliderElementAnimateClass = true; } + if (disableAnimation) { + this.sliderElementAnimateClass = false; + // make sure the slider animate class is set according to the viewOptions after forceEnd() with disabled animations finishes + setTimeout(() => {this.sliderElementAnimateClass = this.viewOptions.animate}); + } this.touchId = null; @@ -2322,6 +2345,17 @@ export class SliderComponent this.userChangeEnd.emit(this.getChangeContext()); } + private onEnd(event: MouseEvent | TouchEvent): void { + if (CompatibilityHelper.isTouchEvent(event)) { + const changedTouches: TouchList = (event as TouchEvent).changedTouches; + if (changedTouches[0].identifier !== this.touchId) { + return; + } + } + + this.forceEnd(); + } + private onPointerFocus(pointerType: PointerType): void { const pointerElement: SliderHandleDirective = this.getPointerElement(pointerType); diff --git a/typedoc/README.md b/typedoc/README.md index 46d33ef..aaa8623 100644 --- a/typedoc/README.md +++ b/typedoc/README.md @@ -19,6 +19,7 @@ The slider component takes the following inputs and outputs: [options]="" [manualRefresh]="" [triggerFocus]="" + [cancelUserChange]="" (userChangeStart)="" (userChange)="" (userChangeEnd)="" @@ -57,6 +58,10 @@ For a complete example, see the [dynamic options slider demo](routerLink:///demo `triggerFocus` input is provided to set the focus programmatically on a slider handle. The emitter takes a `PointerType` as argument, or if left `undefined`, will default to `PointerType.Min`. Refer to the [example demo](routerLink:///demos#trigger-focus-slider) to see how it works. +### Cancel user change + +`cancelUserChange` input ends current user intraction and restores value of a slider handle that was present before `userChangeStart` triggered. Refer to the [example demo](routerLink:///demos#prevent-change-on-scroll-slider) to see how it works. + ### User change events `userChangeStart`, `userChange` and `userChangeEnd` provide output events that are triggered by user interaction (through keyboard, mouse or touchpad). The event handler also passes a `ChangeContext` object which contains details about the changes. Refer to the [example demo](routerLink:///demos#user-events-slider) to see how it works.