diff --git a/CHANGELOG.md b/CHANGELOG.md index 844cb74855d..4580d5f9ac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,17 +70,13 @@ All notable changes for each version of this project will be documented in this - `IgxInputGroupComponent`, `IgxSelectComponent`, `IgxDatePickerComponent`, `IgxDateRangePickerComponent`, `IgxTimePickerComponent` - The default `type` has changed from `line` to `box`. The `ng update` migration automatically adds `type="line"` to existing instances that do not already have an explicit `type` binding to preserve their appearance. -### General +- **Removed Hammer.js dependency** + - The `hammerjs` and `@types/hammerjs` peer dependencies have been removed. All touch gesture support (Carousel swipe, Navigation Drawer pan/swipe, List Item pan, Time Picker vertical scroll, Grid Cell double-tap on iOS) is now implemented with native Pointer Events / Touch Events APIs. + - `HammerGesturesManager` and related types (`HammerInput`, `HammerStatic`, `HammerManager`, `HammerOptions`) are no longer exported from `igniteui-angular/core`. + - The `ng add` schematic no longer prompts for or installs `hammerjs`. + - If your application imported `hammerjs` solely for Ignite UI components, you can safely remove it from your `package.json` dependencies, `angular.json` scripts/polyfills, and any `import 'hammerjs'` statements. -- **Touch Gestures (HammerJS)** _(optional)_ - - `HammerModule`, previously exported from `@angular/platform-browser`, is no longer available in Angular 22. Touch gesture support (Slider, Drag & Drop, Carousel swipe, Navigation Drawer) is optional. To enable it, install the `hammerjs` package and add it to the `scripts` array in your project's `angular.json`: - ```bash - npm install hammerjs - ``` - ```json - // angular.json — inside your project's architect.build.options - "scripts": ["./node_modules/hammerjs/hammer.min.js"] - ``` +### General - `IgxSelectComponent` - The default positioning strategy has changed from the internal overlap strategy to `AutoPositionStrategy`. The dropdown now opens below (or above, if there is not enough space) the input element, consistent with other connected components. diff --git a/angular.json b/angular.json index ecca8e54bf2..e56c33637d7 100644 --- a/angular.json +++ b/angular.json @@ -193,8 +193,7 @@ "options": { "polyfills": [ "zone.js", - "zone.js/testing", - "hammerjs" + "zone.js/testing" ], "styles": [ "src/styles/styles.scss", @@ -287,8 +286,7 @@ "options": { "polyfills": [ "zone.js", - "zone.js/testing", - "hammerjs" + "zone.js/testing" ], "styles": [ "src/styles/styles.scss" diff --git a/package-lock.json b/package-lock.json index e1724542f6e..e1d024cfcfd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,7 +53,6 @@ "@microsoft/signalr": "^7.0.12", "@types/estree": "^1.0.0", "@types/express": "^5.0.0", - "@types/hammerjs": "^2.0.46", "@types/jasmine": "^5.1.7", "@types/jasminewd2": "^2.0.10", "@types/node": "^20.17.6", @@ -72,8 +71,6 @@ "gulp-shell": "^0.6.5", "gulp-typescript": "^5.0.1", "gulp-uglify": "^3.0.1", - "hammer-simulator": "0.0.1", - "hammerjs": "^2.0.8", "ig-typedoc-theme": "^7.0.1", "igniteui-angular-charts": "^22.0.0", "igniteui-angular-core": "^22.0.0", @@ -1182,9 +1179,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1199,9 +1193,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1216,9 +1207,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1233,9 +1221,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1250,9 +1235,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1267,9 +1249,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1284,9 +1263,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1301,9 +1277,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1318,9 +1291,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1335,9 +1305,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1352,9 +1319,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1369,9 +1333,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1386,9 +1347,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5048,9 +5006,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5068,9 +5023,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5088,9 +5040,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5108,9 +5057,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5128,9 +5074,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5148,9 +5091,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5168,9 +5108,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5694,6 +5631,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5714,6 +5652,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5734,6 +5673,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5754,6 +5694,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5774,6 +5715,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5794,6 +5736,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5814,6 +5757,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5834,6 +5778,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5854,6 +5799,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5874,6 +5820,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5894,6 +5841,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5914,6 +5862,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5934,6 +5883,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6833,13 +6783,6 @@ "@types/send": "*" } }, - "node_modules/@types/hammerjs": { - "version": "2.0.46", - "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", - "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -13729,23 +13672,6 @@ "uncrypto": "^0.1.3" } }, - "node_modules/hammer-simulator": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/hammer-simulator/-/hammer-simulator-0.0.1.tgz", - "integrity": "sha512-WbyZImCJlHOs2HtkPJSCksq1i/V/MIbpk44/ALOCTF03FvOKhWcwAl3x4W9dQm8cW0VCM57HpxaCjslDEYPIJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/hammerjs": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", - "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/handlebars": { "version": "4.7.9", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", @@ -23179,6 +23105,33 @@ "sassdoc-extras": "^2.5.0" } }, + "node_modules/sassdoc-theme-default/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/sassdoc-theme-default/node_modules/commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", @@ -23207,6 +23160,21 @@ "jsonfile": "^2.1.0" } }, + "node_modules/sassdoc-theme-default/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/sassdoc-theme-default/node_modules/jsonfile": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", @@ -23243,6 +23211,36 @@ } } }, + "node_modules/sassdoc-theme-default/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/sassdoc-theme-default/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/sassdoc/node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", diff --git a/package.json b/package.json index 71367f87051..4756f8b8780 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,6 @@ "@microsoft/signalr": "^7.0.12", "@types/estree": "^1.0.0", "@types/express": "^5.0.0", - "@types/hammerjs": "^2.0.46", "@types/jasmine": "^5.1.7", "@types/jasminewd2": "^2.0.10", "@types/node": "^20.17.6", @@ -127,8 +126,6 @@ "gulp-shell": "^0.6.5", "gulp-typescript": "^5.0.1", "gulp-uglify": "^3.0.1", - "hammer-simulator": "0.0.1", - "hammerjs": "^2.0.8", "ig-typedoc-theme": "^7.0.1", "igniteui-angular-charts": "^22.0.0", "igniteui-angular-core": "^22.0.0", diff --git a/projects/igniteui-angular-extras/ng-package.json b/projects/igniteui-angular-extras/ng-package.json index 440b8ef6706..b654df46e20 100644 --- a/projects/igniteui-angular-extras/ng-package.json +++ b/projects/igniteui-angular-extras/ng-package.json @@ -10,8 +10,6 @@ ] }, "allowedNonPeerDependencies": [ - "@types/hammerjs", - "hammerjs", "igniteui-trial-watermark", "lodash-es", "@igniteui/material-icons-extended", diff --git a/projects/igniteui-angular/carousel/src/carousel/carousel.component.spec.ts b/projects/igniteui-angular/carousel/src/carousel/carousel.component.spec.ts index ba4e8bfda8b..ca13f195564 100644 --- a/projects/igniteui-angular/carousel/src/carousel/carousel.component.spec.ts +++ b/projects/igniteui-angular/carousel/src/carousel/carousel.component.spec.ts @@ -10,7 +10,6 @@ import { IgxSlideComponent } from './slide.component'; import { IgxCarouselIndicatorDirective, IgxCarouselNextButtonDirective, IgxCarouselPrevButtonDirective } from './carousel.directives'; import { CarouselIndicatorsOrientation, CarouselAnimationType } from './enums'; import { UIInteractions, wait } from 'igniteui-angular/test-utils/ui-interactions.spec'; -import { HammerGesturesManager } from 'igniteui-angular/core'; describe('Carousel', () => { let fixture; @@ -1112,40 +1111,37 @@ class HelperTestFunctions { expect(carousel.slides.find((slide) => slide.active && slide.index !== index)).toBeUndefined(); } - public static simulateTap(fixture, carousel) { + public static simulateTap(_fixture, carousel) { const activeSlide = carousel.get(carousel.current).nativeElement; - const carouselElement = fixture.debugElement.query(By.css('igx-carousel')); - const touchManager = carouselElement.injector.get(HammerGesturesManager); - const hammerManager = touchManager.getManagerForElement(carouselElement.nativeElement); - (hammerManager as any).emit('tap', { target: activeSlide, srcEvent: { preventDefault: () => {} } }); + carousel.onTap({ target: activeSlide }); } public static simulatePan(fixture, carousel, deltaOffset, velocity, dir: 'horizontal' | 'vertical') { const activeSlide = carousel.get(carousel.current).nativeElement; - const carouselElement = fixture.debugElement.query(By.css('igx-carousel')); - const touchManager = carouselElement.injector.get(HammerGesturesManager); - const hammerManager = touchManager.getManagerForElement(carouselElement.nativeElement); const deltaX = dir === 'horizontal' ? activeSlide.offsetWidth * deltaOffset : 0; const deltaY = dir === 'horizontal' ? 0 : activeSlide.offsetHeight * deltaOffset; - - let event; - if (dir === 'horizontal') { - event = deltaOffset < 0 ? 'panleft' : 'panright'; - } else { - event = deltaOffset < 0 ? 'panup' : 'pandown'; - } const panOptions = { deltaX, deltaY, - duration: 100, velocity, - preventDefault: ( () => { }), - srcEvent: { preventDefault: () => {} } + preventDefault: () => { } }; - (hammerManager as any).emit(event, panOptions); + if (dir === 'horizontal') { + if (deltaOffset < 0) { + carousel.onPanLeft(panOptions); + } else { + carousel.onPanRight(panOptions); + } + } else { + if (deltaOffset < 0) { + carousel.onPanUp(panOptions); + } else { + carousel.onPanDown(panOptions); + } + } fixture.detectChanges(); - (hammerManager as any).emit('panend', panOptions); + carousel.onPanEnd(panOptions); fixture.detectChanges(); } } diff --git a/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts b/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts index d13cde0aae6..2179eaf8806 100644 --- a/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts +++ b/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts @@ -3,14 +3,13 @@ import { AfterContentInit, Component, ContentChild, ContentChildren, ElementRef, import { merge, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { CarouselResourceStringsEN, ICarouselResourceStrings, isLeftToRight} from 'igniteui-angular/core'; -import { first, IBaseEventArgs, last, PlatformUtil } from 'igniteui-angular/core'; +import { first, IBaseEventArgs, IgxTouchManager, last, PlatformUtil } from 'igniteui-angular/core'; import { CarouselAnimationDirection, IgxCarouselComponentBase } from './carousel-base'; import { IgxCarouselIndicatorDirective, IgxCarouselNextButtonDirective, IgxCarouselPrevButtonDirective } from './carousel.directives'; import { IgxSlideComponent } from './slide.component'; import { IgxIconComponent } from 'igniteui-angular/icon'; import { IgxButtonDirective } from 'igniteui-angular/directives'; import { getCurrentResourceStrings, onResourceChangeHandle } from 'igniteui-angular/core'; -import { HammerGesturesManager } from 'igniteui-angular/core'; import { CarouselAnimationType, CarouselIndicatorsOrientation } from './enums'; let NEXT_ID = 0; @@ -37,7 +36,6 @@ let NEXT_ID = 0; * ``` */ @Component({ - providers: [HammerGesturesManager], selector: 'igx-carousel', templateUrl: 'carousel.component.html', styles: [` @@ -52,7 +50,6 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On private element = inject(ElementRef); private iterableDiffers = inject(IterableDiffers); private platformUtil = inject(PlatformUtil); - private touchManager = inject(HammerGesturesManager); @@ -665,7 +662,7 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On if (this.lastInterval) { clearInterval(this.lastInterval); } - this.touchManager.destroy(); + this._gestures?.destroy(); } /** @hidden */ @@ -886,17 +883,40 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On return this.currentItem.nativeElement; } + private _gestures: IgxTouchManager | null = null; + private registerGestureEvents() { if (!this.gesturesSupport || !this.platformUtil.isBrowser) { return; } - const el = this.element.nativeElement; - this.touchManager.addEventListener(el, 'tap', (e) => this.onTap(e)); - this.touchManager.addEventListener(el, 'panleft', (e) => this.onPanLeft(e)); - this.touchManager.addEventListener(el, 'panright', (e) => this.onPanRight(e)); - this.touchManager.addEventListener(el, 'panup', (e) => this.onPanUp(e)); - this.touchManager.addEventListener(el, 'pandown', (e) => this.onPanDown(e)); - this.touchManager.addEventListener(el, 'panend', (e) => this.onPanEnd(e)); + + this._gestures = new IgxTouchManager(this.element.nativeElement, { + tap: (event) => this.onTap(event), + panMove: (event) => this.onPan(event), + panEnd: (event) => this.onPanEnd(event) + }, { tapThreshold: 5 }); + } + + /** + * Routes a pan gesture to the orientation-specific handler so that only + * gestures matching the carousel's axis affect the active slide. + * + * @hidden + */ + private onPan(event) { + if (Math.abs(event.deltaX) >= Math.abs(event.deltaY)) { + if (event.deltaX < 0) { + this.onPanLeft(event); + } else { + this.onPanRight(event); + } + } else { + if (event.deltaY < 0) { + this.onPanUp(event); + } else { + this.onPanDown(event); + } + } } private resetInterval() { @@ -968,7 +988,7 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On const index = delta < 0 ? this.getNextIndex() : this.getPrevIndex(); const offset = delta < 0 ? slideSize + delta : -slideSize + delta; - if (!this.gesturesSupport || event.isFinal || Math.abs(delta) + panOffset >= slideSize) { + if (!this.gesturesSupport || Math.abs(delta) + panOffset >= slideSize) { return; } diff --git a/projects/igniteui-angular/core/src/core/touch-annotations.ts b/projects/igniteui-angular/core/src/core/touch-annotations.ts deleted file mode 100644 index 142683007a3..00000000000 --- a/projects/igniteui-angular/core/src/core/touch-annotations.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Stripped-down HammerJS annotations. - */ - -/** -* @hidden -* @internal -*/ -export interface HammerInput { - preventDefault: () => void; - deltaX: number; - deltaY: number; - center: { x: number; y: number; }; - pointerType: string; - distance: number; -} - -/** -* @hidden -* @internal -*/ -export interface HammerStatic { - new(element: HTMLElement | SVGElement, options?: any): HammerManager; - - Pan: Recognizer; - Swipe: Recognizer; - Tap: Recognizer; - TouchInput: HammerInput; - DIRECTION_HORIZONTAL: number; - DIRECTION_VERTICAL: number; -} - -/** -* @hidden -* @internal -*/ -export interface Recognizer { } - -/** -* @hidden -* @internal -*/ -export interface HammerManager { - set(options: any): HammerManager; - off(events: string, handler?: (event: HammerInput) => void): void; - on(events: string, handler: (event: HammerInput) => void): void; - destroy(): void; - get(event: string): HammerManager; -} - -/** -* @hidden -* @internal -*/ -export interface HammerOptions { - cssProps?: { [key: string]: string }; - recognizers?: any[]; - inputClass?: HammerInput; -} diff --git a/projects/igniteui-angular/core/src/core/touch.ts b/projects/igniteui-angular/core/src/core/touch.ts index 36341f15bc7..93898ffbd1c 100644 --- a/projects/igniteui-angular/core/src/core/touch.ts +++ b/projects/igniteui-angular/core/src/core/touch.ts @@ -1,170 +1,217 @@ -import { Injectable, NgZone, DOCUMENT, inject } from '@angular/core'; -import { ɵgetDOM as getDOM } from '@angular/platform-browser'; -import { PlatformUtil } from './utils'; -import { HammerManager, HammerOptions, HammerStatic } from './touch-annotations'; +/** + * Normalized gesture event emitted by {@link IgxTouchManager}. + * + * It intentionally mirrors the small subset of the previous Hammer.js input shape + * (`deltaX`, `deltaY`, `center`, `distance`, `pointerType`) so existing gesture + * handlers can consume it without changes. + * + * @hidden + * @internal + */ +export interface IgxGestureEvent { + /** The type of the pointer that produced the gesture (e.g. `touch`, `pen`, `mouse`). */ + pointerType: string; + /** Horizontal distance (in px) from the gesture origin to the current pointer position. */ + deltaX: number; + /** Vertical distance (in px) from the gesture origin to the current pointer position. */ + deltaY: number; + /** Euclidean distance (in px) from the gesture origin to the current pointer position. */ + distance: number; + /** Gesture velocity in px/ms, measured from the gesture origin to the current pointer position. */ + velocity: number; + /** Current pointer position. */ + center: { x: number; y: number }; + /** The original event target. */ + target: EventTarget; + /** The underlying native pointer event. */ + originalEvent: PointerEvent; + /** Prevents the default action of the underlying native pointer event. */ + preventDefault: () => void; + /** + * Resets the gesture origin to the current pointer position so that subsequent + * `deltaX`/`deltaY`/`distance` values are measured relative to it. + */ + resetOrigin: () => void; +} -const EVENT_SUFFIX = 'precise'; +/** + * Callbacks invoked by {@link IgxTouchManager} for the recognized gestures. + * + * @hidden + * @internal + */ +export interface IgxTouchManagerCallbacks { + /** Fired on pointer down once the pointer type passes the configured filter. */ + panStart?: (event: IgxGestureEvent) => void; + /** Fired on each pointer move while a gesture is tracked. */ + panMove?: (event: IgxGestureEvent) => void; + /** Fired on pointer up at the end of a tracked gesture. */ + panEnd?: (event: IgxGestureEvent) => void; + /** Fired on pointer cancel for a tracked gesture. */ + panCancel?: (event: IgxGestureEvent) => void; + /** Fired on pointer up when the movement stays below `tapThreshold`. Suppresses `panEnd`. */ + tap?: (event: IgxGestureEvent) => void; + /** Fired on pointer up for a fast, primarily horizontal gesture, before `panEnd`. */ + swipe?: (event: IgxGestureEvent) => void; +} /** - * Touch gestures manager based on Hammer.js - * Use with caution, this will track references for single manager per element. Very TBD. Much TODO. + * Options controlling how {@link IgxTouchManager} recognizes gestures. * * @hidden + * @internal */ -@Injectable() -export class HammerGesturesManager { - private _zone = inject(NgZone); - private doc = inject(DOCUMENT); - private platformUtil = inject(PlatformUtil); +export interface IgxTouchManagerOptions { + /** Pointer types to handle. Defaults to `['touch', 'pen']` (mouse excluded). */ + pointerTypes?: string[]; + /** Whether to capture the pointer on the target during a gesture. Defaults to `true`. */ + setPointerCapture?: boolean; + /** Maximum movement (in px) for a pointer up to be recognized as a tap. Defaults to `0` (disabled). */ + tapThreshold?: number; + /** Minimum velocity (in px/ms) for a primarily horizontal gesture to be recognized as a swipe. Defaults to `0.3`. */ + swipeVelocityThreshold?: number; +} - public static Hammer: HammerStatic = typeof window !== 'undefined' ? (window as any).Hammer : null; - /** - * Event option defaults for each recognizer, see http://hammerjs.github.io/api/ for API listing. - */ - protected hammerOptions: HammerOptions = {}; - - private platformBrowser: boolean; - private _hammerManagers: Array<{ element: EventTarget; manager: HammerManager }> = []; - - constructor() { - this.platformBrowser = this.platformUtil.isBrowser; - if (this.platformBrowser && HammerGesturesManager.Hammer) { - this.hammerOptions = { - // D.P. #447 Force TouchInput due to PointerEventInput bug (https://github.com/hammerjs/hammer.js/issues/1065) - // see https://github.com/IgniteUI/igniteui-angular/issues/447#issuecomment-324601803 - inputClass: HammerGesturesManager.Hammer.TouchInput, - recognizers: [ - [HammerGesturesManager.Hammer.Pan, { threshold: 0 }], - [HammerGesturesManager.Hammer.Swipe, { direction: HammerGesturesManager.Hammer.DIRECTION_HORIZONTAL }], - [HammerGesturesManager.Hammer.Tap], - [HammerGesturesManager.Hammer.Tap, { event: 'doubletap', taps: 2 }, ['tap']] - ] - }; - } +/** + * Lightweight, zoneless pointer-based gesture manager. + * + * Consolidates the pan/swipe/tap recognition logic shared across components + * (carousel, navigation drawer, list item, time picker) on top of native + * Pointer Events. It does not depend on `NgZone`; consumers update their own + * state inside the provided callbacks. + * + * @hidden + * @internal + * + * @example + * ```ts + * this._gestures = new IgxTouchManager(this.element.nativeElement, { + * panMove: (e) => this.pan(e), + * panEnd: (e) => this.onPanEnd(e), + * tap: (e) => this.onTap(e) + * }, { tapThreshold: 5 }); + * // ... + * this._gestures.destroy(); + * ``` + */ +export class IgxTouchManager { + private _startX = 0; + private _startY = 0; + private _startTime = 0; + private _tracking = false; + private readonly _pointerTypes: string[]; + private readonly _setPointerCapture: boolean; + private readonly _tapThreshold: number; + private readonly _swipeVelocityThreshold: number; + + constructor( + private target: EventTarget, + private callbacks: IgxTouchManagerCallbacks, + options: IgxTouchManagerOptions = {} + ) { + this._pointerTypes = options.pointerTypes ?? ['touch', 'pen']; + this._setPointerCapture = options.setPointerCapture ?? true; + this._tapThreshold = options.tapThreshold ?? 0; + this._swipeVelocityThreshold = options.swipeVelocityThreshold ?? 0.3; + + this.target.addEventListener('pointerdown', this._onPointerDown); + this.target.addEventListener('pointermove', this._onPointerMove); + this.target.addEventListener('pointerup', this._onPointerUp); + this.target.addEventListener('pointercancel', this._onPointerCancel); } - public supports(eventName: string): boolean { - return eventName.toLowerCase().endsWith('.' + EVENT_SUFFIX); + /** Detaches all listeners and stops tracking. */ + public destroy(): void { + this.target.removeEventListener('pointerdown', this._onPointerDown); + this.target.removeEventListener('pointermove', this._onPointerMove); + this.target.removeEventListener('pointerup', this._onPointerUp); + this.target.removeEventListener('pointercancel', this._onPointerCancel); + this._tracking = false; } - /** - * Add listener extended with options for Hammer.js. Will use defaults if none are provided. - * Modeling after other event plugins for easy future modifications. - */ - public addEventListener( - element: HTMLElement, - eventName: string, - eventHandler: (eventObj) => void, - options: HammerOptions = null): () => void { - if (!this.platformBrowser) { - return; - } + private _accepts(pointerType: string): boolean { + return this._pointerTypes.includes(pointerType); + } - // Creating the manager bind events, must be done outside of angular - return this._zone.runOutsideAngular(() => { - if (!HammerGesturesManager.Hammer) { - //no hammer - return; + private _createEvent(event: PointerEvent): IgxGestureEvent { + const deltaX = event.clientX - this._startX; + const deltaY = event.clientY - this._startY; + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + const elapsed = Date.now() - this._startTime; + const velocity = elapsed > 0 ? distance / elapsed : 0; + + return { + pointerType: event.pointerType, + deltaX, + deltaY, + distance, + velocity, + center: { x: event.clientX, y: event.clientY }, + target: event.target, + originalEvent: event, + preventDefault: () => event.preventDefault(), + resetOrigin: () => { + this._startX = event.clientX; + this._startY = event.clientY; + this._startTime = Date.now(); } - let mc: HammerManager = this.getManagerForElement(element); - if (mc === null) { - // new Hammer is a shortcut for Manager with defaults - mc = new HammerGesturesManager.Hammer(element, Object.assign(this.hammerOptions, options)); - this.addManagerForElement(element, mc); - } - const handler = (eventObj) => this._zone.run(() => eventHandler(eventObj)); - mc.on(eventName, handler); - return () => mc.off(eventName, handler); - }); + }; } - /** - * Add listener extended with options for Hammer.js. Will use defaults if none are provided. - * Modeling after other event plugins for easy future modifications. - * - * @param target Can be one of either window, body or document(fallback default). - */ - public addGlobalEventListener(target: string, eventName: string, eventHandler: (eventObj) => void): () => void { - if (!this.platformBrowser || !HammerGesturesManager.Hammer) { + private _onPointerDown = (event: PointerEvent) => { + if (!this._accepts(event.pointerType)) { return; } + this._startX = event.clientX; + this._startY = event.clientY; + this._startTime = Date.now(); + this._tracking = true; + + if (this._setPointerCapture && typeof (this.target as Element).setPointerCapture === 'function') { + try { + (this.target as Element).setPointerCapture(event.pointerId); + } catch { + // `setPointerCapture` can throw a `NotFoundError` when the pointer is no longer active + // (e.g. for synthetic events). Capturing is a best-effort enhancement, so ignore it. + } + } - const element = this.getGlobalEventTarget(target); - - // Creating the manager bind events, must be done outside of angular - return this.addEventListener(element as HTMLElement, eventName, eventHandler); - } - - /** - * Exposes [Dom]Adapter.getGlobalEventTarget to get global event targets. - * Supported: window, document, body. Defaults to document for invalid args. - * - * @param target Target name - */ - public getGlobalEventTarget(target: string): EventTarget { - return getDOM().getGlobalEventTarget(this.doc, target); - } - - /** - * Set HammerManager options. - * - * @param element The DOM element used to create the manager on. - * - * ### Example - * - * ```ts - * manager.setManagerOption(myElem, "pan", { pointers: 1 }); - * ``` - */ - public setManagerOption(element: EventTarget, event: string, options: any) { - const manager = this.getManagerForElement(element); - manager.get(event).set(options); - } + this.callbacks.panStart?.(this._createEvent(event)); + }; - /** - * Add an element and manager map to the internal collection. - * - * @param element The DOM element used to create the manager on. - */ - public addManagerForElement(element: EventTarget, manager: HammerManager) { - this._hammerManagers.push({element, manager}); - } + private _onPointerMove = (event: PointerEvent) => { + if (!this._tracking || !this._accepts(event.pointerType)) { + return; + } + this.callbacks.panMove?.(this._createEvent(event)); + }; - /** - * Get HammerManager for the element or null - * - * @param element The DOM element used to create the manager on. - */ - public getManagerForElement(element: EventTarget): HammerManager { - const result = this._hammerManagers.filter(value => value.element === element); - return result.length ? result[0].manager : null; - } + private _onPointerUp = (event: PointerEvent) => { + if (!this._tracking || !this._accepts(event.pointerType)) { + return; + } + this._tracking = false; + const gesture = this._createEvent(event); - /** - * Destroys the HammerManager for the element, removing event listeners in the process. - * - * @param element The DOM element used to create the manager on. - */ - public removeManagerForElement(element: HTMLElement) { - let index: number = null; - for (let i = 0; i < this._hammerManagers.length; i++) { - if (element === this._hammerManagers[i].element) { - index = i; - break; - } + if (this.callbacks.tap && gesture.distance < this._tapThreshold) { + this.callbacks.tap(gesture); + return; } - if (index !== null) { - const item = this._hammerManagers.splice(index, 1)[0]; - // destroy also - item.manager.destroy(); + + if (this.callbacks.swipe && + gesture.velocity > this._swipeVelocityThreshold && + Math.abs(gesture.deltaX) > Math.abs(gesture.deltaY)) { + this.callbacks.swipe(gesture); } - } - /** Destroys all internally tracked HammerManagers, removing event listeners in the process. */ - public destroy() { - for (const item of this._hammerManagers) { - item.manager.destroy(); + this.callbacks.panEnd?.(gesture); + }; + + private _onPointerCancel = (event: PointerEvent) => { + if (!this._tracking) { + return; } - this._hammerManagers = []; - } + this._tracking = false; + this.callbacks.panCancel?.(this._createEvent(event)); + }; } diff --git a/projects/igniteui-angular/core/src/public_api.ts b/projects/igniteui-angular/core/src/public_api.ts index 7e15de44aa4..3c920d31b2f 100644 --- a/projects/igniteui-angular/core/src/public_api.ts +++ b/projects/igniteui-angular/core/src/public_api.ts @@ -7,7 +7,6 @@ export * from './core/types'; export * from './core/selection'; export * from './core/edit-provider'; export * from './core/touch'; -export * from './core/touch-annotations'; // Grid actions tokens export * from './grid-column-actions/token'; diff --git a/projects/igniteui-angular/grids/core/src/cell.component.ts b/projects/igniteui-angular/grids/core/src/cell.component.ts index 35e8223ada2..c0d1cb28789 100644 --- a/projects/igniteui-angular/grids/core/src/cell.component.ts +++ b/projects/igniteui-angular/grids/core/src/cell.component.ts @@ -39,7 +39,6 @@ import { IgxPercentFormatterPipe } from 'igniteui-angular/core'; import { IgxGridSelectionService } from './selection/selection.service'; -import { HammerGesturesManager } from 'igniteui-angular/core'; import { GridSelectionMode } from './common/enums'; import { CellType, IgxCellTemplateContext, IGX_GRID_BASE, RowType } from './common/grid.interface'; import { IgxRowDirective } from './row.directive'; @@ -80,7 +79,6 @@ import { IgxTimePickerComponent } from 'igniteui-angular/time-picker'; changeDetection: ChangeDetectionStrategy.OnPush, selector: 'igx-grid-cell', templateUrl: './cell.component.html', - providers: [HammerGesturesManager], imports: [ NgClass, NgTemplateOutlet, @@ -116,7 +114,6 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy, CellT public cdr = inject(ChangeDetectorRef); private element = inject(ElementRef); protected zone = inject(NgZone); - private touchManager = inject(HammerGesturesManager); protected platformUtil = inject(PlatformUtil); private _destroy$ = new Subject(); @@ -844,8 +841,29 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy, CellT private _highlight: IgxTextHighlightDirective; private _cellSelection: GridSelectionMode = GridSelectionMode.multiple; private _vIndex = -1; + private _lastTapTime = 0; + private readonly _doubleTapThreshold = 300; - + /** + * @hidden + * @internal + */ + private _onTouchEnd = (event: TouchEvent) => { + const now = Date.now(); + if (now - this._lastTapTime < this._doubleTapThreshold) { + this._lastTapTime = 0; + const syntheticEvent = new MouseEvent('dblclick', { + bubbles: true, + cancelable: true, + clientX: event.changedTouches?.[0]?.clientX, + clientY: event.changedTouches?.[0]?.clientY + }); + Object.defineProperty(syntheticEvent, 'type', { value: 'doubletap' }); + this.zone.run(() => this.onDoubleClick(syntheticEvent)); + } else { + this._lastTapTime = now; + } + }; /** * @hidden @@ -887,12 +905,10 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy, CellT this.zone.runOutsideAngular(() => { this.nativeElement.addEventListener('pointerdown', this.pointerdown); this.addPointerListeners(this.cellSelectionMode); + if (this.platformUtil.isIOS) { + this.nativeElement.addEventListener('touchend', this._onTouchEnd); + } }); - if (this.platformUtil.isIOS) { - this.touchManager.addEventListener(this.nativeElement, 'doubletap', this.onDoubleClick, { - cssProps: {} /* don't disable user-select, etc */ - }); - } } @@ -940,7 +956,9 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy, CellT this.nativeElement.removeEventListener('pointerdown', this.pointerdown); this.removePointerListeners(this.cellSelectionMode); }); - this.touchManager.destroy(); + if (this.platformUtil.isIOS) { + this.nativeElement.removeEventListener('touchend', this._onTouchEnd); + } this._destroy$.next(); this._destroy$.complete(); } diff --git a/projects/igniteui-angular/grids/grid/src/cell.spec.ts b/projects/igniteui-angular/grids/grid/src/cell.spec.ts index fd12de9fdff..d828019fd18 100644 --- a/projects/igniteui-angular/grids/grid/src/cell.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/cell.spec.ts @@ -8,7 +8,7 @@ import { VirtualGridComponent, NoScrollsComponent, NoColumnWidthGridComponent, IgxGridDateTimeColumnComponent } from '../../../test-utils/grid-samples.spec'; import { GridFunctions } from '../../../test-utils/grid-functions.spec'; import { CellType, IGridCellEventArgs, IgxColumnComponent } from 'igniteui-angular/grids/core'; -import { HammerGesturesManager, PlatformUtil } from 'igniteui-angular/core'; +import { PlatformUtil } from 'igniteui-angular/core'; describe('IgxGrid - Cell component #grid', () => { @@ -279,21 +279,7 @@ describe('IgxGrid - Cell component #grid', () => { }).compileComponents(); })); - it('Should not attach doubletap handler for non-iOS', () => { - const addListenerSpy = spyOn(HammerGesturesManager.prototype, 'addEventListener'); - const platformUtil: PlatformUtil = TestBed.inject(PlatformUtil); - const oldIsIOS = platformUtil.isIOS; - platformUtil.isIOS = false; - const fix = TestBed.createComponent(NoScrollsComponent); - fix.detectChanges(); - // spyOnProperty(PlatformUtil.prototype, 'isIOS').and.returnValue(false); - expect(addListenerSpy).not.toHaveBeenCalled(); - - platformUtil.isIOS = oldIsIOS; - }); - it('Should handle doubletap on iOS, trigger doubleClick event', () => { - const addListenerSpy = spyOn(HammerGesturesManager.prototype, 'addEventListener'); const platformUtil: PlatformUtil = TestBed.inject(PlatformUtil); const oldIsIOS = platformUtil.isIOS; platformUtil.isIOS = true; @@ -303,11 +289,6 @@ describe('IgxGrid - Cell component #grid', () => { const grid = fix.componentInstance.grid; const firstCellElem = grid.gridAPI.get_cell_by_index(0, 'ID'); - // should attach 'doubletap' - expect(addListenerSpy.calls.count()).toBeGreaterThan(1); - expect(addListenerSpy).toHaveBeenCalledWith(firstCellElem.nativeElement, 'doubletap', firstCellElem.onDoubleClick, - { cssProps: {} as any }); - spyOn(grid.doubleClick, 'emit').and.callThrough(); const event = { diff --git a/projects/igniteui-angular/grids/grid/src/expandable-cell.component.ts b/projects/igniteui-angular/grids/grid/src/expandable-cell.component.ts index 6e301d9c76b..626c25d192f 100644 --- a/projects/igniteui-angular/grids/grid/src/expandable-cell.component.ts +++ b/projects/igniteui-angular/grids/grid/src/expandable-cell.component.ts @@ -16,7 +16,7 @@ import { IgxGridCellImageAltPipe, IgxStringReplacePipe } from 'igniteui-angular/grids/core'; -import { HammerGesturesManager, IgxNumberFormatterPipe, IgxDateFormatterPipe, IgxCurrencyFormatterPipe, IgxPercentFormatterPipe } from 'igniteui-angular/core'; +import { IgxNumberFormatterPipe, IgxDateFormatterPipe, IgxCurrencyFormatterPipe, IgxPercentFormatterPipe } from 'igniteui-angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { IgxChipComponent } from 'igniteui-angular/chips'; import { IgxDateTimeEditorDirective, IgxFocusDirective, IgxTextHighlightDirective, IgxTooltipDirective, IgxTooltipTargetDirective } from 'igniteui-angular/directives'; @@ -30,7 +30,6 @@ import { IgxTimePickerComponent } from 'igniteui-angular/time-picker'; changeDetection: ChangeDetectionStrategy.OnPush, selector: 'igx-expandable-grid-cell', templateUrl: 'expandable-cell.component.html', - providers: [HammerGesturesManager], imports: [IgxChipComponent, IgxTextHighlightDirective, IgxIconComponent, NgClass, FormsModule, ReactiveFormsModule, IgxInputGroupComponent, IgxInputDirective, IgxFocusDirective, IgxCheckboxComponent, IgxDatePickerComponent, IgxTimePickerComponent, IgxDateTimeEditorDirective, IgxPrefixDirective, IgxSuffixDirective, NgTemplateOutlet, diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-cell.component.ts b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-cell.component.ts index 2fd9724e185..f4c3ccae941 100644 --- a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-cell.component.ts +++ b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-cell.component.ts @@ -1,5 +1,4 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { HammerGesturesManager } from 'igniteui-angular/core'; import { IgxColumnFormatterPipe, IgxGridCellComponent, @@ -21,7 +20,6 @@ import { IgxCurrencyFormatterPipe, IgxDateFormatterPipe, IgxNumberFormatterPipe, changeDetection: ChangeDetectionStrategy.OnPush, selector: 'igx-hierarchical-grid-cell', templateUrl: '../../core/src/cell.component.html', - providers: [HammerGesturesManager], imports: [IgxChipComponent, IgxTextHighlightDirective, IgxIconComponent, NgClass, FormsModule, ReactiveFormsModule, IgxInputGroupComponent, IgxInputDirective, IgxFocusDirective, IgxTextSelectionDirective, IgxCheckboxComponent, IgxDatePickerComponent, IgxTimePickerComponent, IgxDateTimeEditorDirective, IgxPrefixDirective, diff --git a/projects/igniteui-angular/grids/tree-grid/src/tree-cell.component.ts b/projects/igniteui-angular/grids/tree-grid/src/tree-cell.component.ts index a704712b7a7..ce59187e6e9 100644 --- a/projects/igniteui-angular/grids/tree-grid/src/tree-cell.component.ts +++ b/projects/igniteui-angular/grids/tree-grid/src/tree-cell.component.ts @@ -9,7 +9,7 @@ import { IgxTreeGridRow } from 'igniteui-angular/grids/core'; import { RowType } from 'igniteui-angular/grids/core'; import { IgxGridCellImageAltPipe, IgxStringReplacePipe, IgxColumnFormatterPipe } from 'igniteui-angular/grids/core'; import { ReactiveFormsModule } from '@angular/forms'; -import { HammerGesturesManager, IgxNumberFormatterPipe, IgxPercentFormatterPipe, IgxCurrencyFormatterPipe, IgxDateFormatterPipe } from 'igniteui-angular/core'; +import { IgxNumberFormatterPipe, IgxPercentFormatterPipe, IgxCurrencyFormatterPipe, IgxDateFormatterPipe } from 'igniteui-angular/core'; import { IgxChipComponent } from 'igniteui-angular/chips'; import { IgxDateTimeEditorDirective, IgxFocusDirective, IgxTextHighlightDirective, IgxTextSelectionDirective, IgxTooltipDirective, IgxTooltipTargetDirective } from 'igniteui-angular/directives'; import { IgxIconComponent } from 'igniteui-angular/icon'; @@ -24,7 +24,6 @@ import { IgxGridExpandableCellComponent } from 'igniteui-angular/grids/grid'; changeDetection: ChangeDetectionStrategy.OnPush, selector: 'igx-tree-grid-cell', templateUrl: 'tree-cell.component.html', - providers: [HammerGesturesManager], imports: [ NgClass, NgStyle, diff --git a/projects/igniteui-angular/karma.conf.js b/projects/igniteui-angular/karma.conf.js index 927e9fdbf8d..8c595fd45e5 100644 --- a/projects/igniteui-angular/karma.conf.js +++ b/projects/igniteui-angular/karma.conf.js @@ -8,8 +8,6 @@ module.exports = function (config) { basePath: '', frameworks: ['parallel', 'jasmine', '@angular-devkit/build-angular'], files: [ - { pattern: '../../node_modules/hammerjs/hammer.min.js', watched: false }, - { pattern: '../../node_modules/hammer-simulator/index.js', watched: false } ], plugins: [ 'karma-parallel', diff --git a/projects/igniteui-angular/karma.grid.conf.js b/projects/igniteui-angular/karma.grid.conf.js index d339e64f69f..3365f4348d8 100644 --- a/projects/igniteui-angular/karma.grid.conf.js +++ b/projects/igniteui-angular/karma.grid.conf.js @@ -6,8 +6,6 @@ module.exports = function (config) { basePath: '', frameworks: ['parallel', 'jasmine', 'jasmine-spec-tags', '@angular-devkit/build-angular'], files: [ - { pattern: '../../node_modules/hammerjs/hammer.min.js', watched: false }, - { pattern: '../../node_modules/hammer-simulator/index.js', watched: false }, { pattern: './test.css', watched: false }, { pattern: '../../dist/igniteui-angular/styles/igniteui-angular.css', watched: false } ], diff --git a/projects/igniteui-angular/karma.hierarchical-grid.conf.js b/projects/igniteui-angular/karma.hierarchical-grid.conf.js index 8c564a10a16..3eac05c546f 100644 --- a/projects/igniteui-angular/karma.hierarchical-grid.conf.js +++ b/projects/igniteui-angular/karma.hierarchical-grid.conf.js @@ -6,8 +6,6 @@ module.exports = function (config) { basePath: '', frameworks: ['parallel', 'jasmine', 'jasmine-spec-tags', '@angular-devkit/build-angular'], files: [ - { pattern: '../../node_modules/hammerjs/hammer.min.js', watched: false }, - { pattern: '../../node_modules/hammer-simulator/index.js', watched: false }, { pattern: './test.css', watched: false }, { pattern: '../../dist/igniteui-angular/styles/igniteui-angular.css', watched: false } ], diff --git a/projects/igniteui-angular/karma.non-grid.conf.js b/projects/igniteui-angular/karma.non-grid.conf.js index 09bd55ea925..abd54c39503 100644 --- a/projects/igniteui-angular/karma.non-grid.conf.js +++ b/projects/igniteui-angular/karma.non-grid.conf.js @@ -6,8 +6,6 @@ module.exports = function (config) { basePath: '', frameworks: ['parallel', 'jasmine', 'jasmine-spec-tags', '@angular-devkit/build-angular'], files: [ - { pattern: '../../node_modules/hammerjs/hammer.min.js', watched: false }, - { pattern: '../../node_modules/hammer-simulator/index.js', watched: false }, { pattern: './test.css', watched: false }, { pattern: '../../dist/igniteui-angular/styles/igniteui-angular.css', watched: false } ], diff --git a/projects/igniteui-angular/karma.pivot-grid.conf.js b/projects/igniteui-angular/karma.pivot-grid.conf.js index a037310ecf7..4f26fdfe1c8 100644 --- a/projects/igniteui-angular/karma.pivot-grid.conf.js +++ b/projects/igniteui-angular/karma.pivot-grid.conf.js @@ -6,8 +6,6 @@ module.exports = function (config) { basePath: '', frameworks: ['parallel', 'jasmine', 'jasmine-spec-tags', '@angular-devkit/build-angular'], files: [ - { pattern: '../../node_modules/hammerjs/hammer.min.js', watched: false }, - { pattern: '../../node_modules/hammer-simulator/index.js', watched: false }, { pattern: './test.css', watched: false }, { pattern: '../../dist/igniteui-angular/styles/igniteui-angular.css', watched: false } ], diff --git a/projects/igniteui-angular/karma.test-perf.conf.js b/projects/igniteui-angular/karma.test-perf.conf.js index f79b27cc778..9f1a870da70 100644 --- a/projects/igniteui-angular/karma.test-perf.conf.js +++ b/projects/igniteui-angular/karma.test-perf.conf.js @@ -6,8 +6,6 @@ module.exports = function (config) { basePath: '', frameworks: ['jasmine', 'jasmine-spec-tags', '@angular-devkit/build-angular'], files: [ - { pattern: '../../node_modules/hammerjs/hammer.min.js', watched: false }, - { pattern: '../../node_modules/hammer-simulator/index.js', watched: false }, { pattern: './test.css', watched: false }, { pattern: '../../dist/igniteui-angular/styles/igniteui-angular.css', watched: false } ], diff --git a/projects/igniteui-angular/karma.tree-grid.conf.js b/projects/igniteui-angular/karma.tree-grid.conf.js index 4b2882a6d2a..d55e7d87cbf 100644 --- a/projects/igniteui-angular/karma.tree-grid.conf.js +++ b/projects/igniteui-angular/karma.tree-grid.conf.js @@ -6,8 +6,6 @@ module.exports = function (config) { basePath: '', frameworks: ['parallel', 'jasmine', 'jasmine-spec-tags', '@angular-devkit/build-angular'], files: [ - { pattern: '../../node_modules/hammerjs/hammer.min.js', watched: false }, - { pattern: '../../node_modules/hammer-simulator/index.js', watched: false }, { pattern: './test.css', watched: false }, { pattern: '../../dist/igniteui-angular/styles/igniteui-angular.css', watched: false } ], diff --git a/projects/igniteui-angular/karma.watch.conf.js b/projects/igniteui-angular/karma.watch.conf.js index 6b371d168e1..6214a1a4c9a 100644 --- a/projects/igniteui-angular/karma.watch.conf.js +++ b/projects/igniteui-angular/karma.watch.conf.js @@ -6,8 +6,6 @@ module.exports = function (config) { basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], files: [ - { pattern: '../../node_modules/hammerjs/hammer.min.js', watched: false }, - { pattern: '../../node_modules/hammer-simulator/index.js', watched: false }, { pattern: './test.css', watched: false } ], plugins: [ diff --git a/projects/igniteui-angular/list/src/list/list-item.component.ts b/projects/igniteui-angular/list/src/list/list-item.component.ts index 23c7cd876c9..87c0b065021 100644 --- a/projects/igniteui-angular/list/src/list/list-item.component.ts +++ b/projects/igniteui-angular/list/src/list/list-item.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, ElementRef, HostBinding, HostListener, Input, Renderer2, ViewChild, booleanAttribute, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, HostBinding, HostListener, Input, OnDestroy, OnInit, Renderer2, ViewChild, booleanAttribute, inject } from '@angular/core'; import { IgxListPanState, @@ -6,8 +6,7 @@ import { IgxListBaseDirective } from './list.common'; -import { HammerGesturesManager } from 'igniteui-angular/core'; -import { rem } from 'igniteui-angular/core'; +import { rem, IgxTouchManager } from 'igniteui-angular/core'; import { NgTemplateOutlet } from '@angular/common'; /** @@ -25,17 +24,21 @@ import { NgTemplateOutlet } from '@angular/common'; * ``` */ @Component({ - providers: [HammerGesturesManager], selector: 'igx-list-item', templateUrl: 'list-item.component.html', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet] }) -export class IgxListItemComponent implements IListChild { +export class IgxListItemComponent implements IListChild, OnInit, OnDestroy { public list = inject(IgxListBaseDirective); private elementRef = inject(ElementRef); private _renderer = inject(Renderer2); + /** + * @hidden + */ + private _gestures: IgxTouchManager | null = null; + /** * Provides a reference to the template's base element shown when left panning a list item. * ```typescript @@ -333,6 +336,25 @@ export class IgxListItemComponent implements IListChild { return this.hidden ? 'none' : ''; } + /** + * @hidden + */ + public ngOnInit() { + this._gestures = new IgxTouchManager(this.elementRef.nativeElement, { + panStart: () => this.panStart(), + panMove: (event) => this.panMove(event), + panEnd: () => this.panEnd(), + panCancel: () => this.panCancel() + }); + } + + /** + * @hidden + */ + public ngOnDestroy() { + this._gestures?.destroy(); + } + /** * @hidden */ @@ -345,7 +367,6 @@ export class IgxListItemComponent implements IListChild { /** * @hidden */ - @HostListener('panstart') public panStart() { if (this.isTrue(this.isHeader)) { return; @@ -360,7 +381,6 @@ export class IgxListItemComponent implements IListChild { /** * @hidden */ - @HostListener('pancancel') public panCancel() { this.resetPanPosition(); this.list.endPan.emit({ item: this, direction: this.lastPanDir, keepItem: false }); @@ -369,7 +389,6 @@ export class IgxListItemComponent implements IListChild { /** * @hidden */ - @HostListener('panmove', ['$event']) public panMove(ev) { if (this.isTrue(this.isHeader)) { return; @@ -390,7 +409,6 @@ export class IgxListItemComponent implements IListChild { /** * @hidden */ - @HostListener('panend') public panEnd() { if (this.isTrue(this.isHeader)) { return; diff --git a/projects/igniteui-angular/list/src/list/list.component.spec.ts b/projects/igniteui-angular/list/src/list/list.component.spec.ts index b9414781111..2987385f056 100644 --- a/projects/igniteui-angular/list/src/list/list.component.spec.ts +++ b/projects/igniteui-angular/list/src/list/list.component.spec.ts @@ -764,16 +764,25 @@ describe('List', () => { /* factorX - the coefficient used to calculate deltaX. Pan left by providing negative factorX; Pan right - positive factorX. */ + const dispatchPointer = (nativeElement, type, clientX, clientY = 0) => { + const event = new PointerEvent(type, { + pointerType: 'touch', + pointerId: 1, + clientX, + clientY, + bubbles: true, + cancelable: true + }); + nativeElement.dispatchEvent(event); + }; + const panItem = (elementRefObject, factorX) => { - const itemWidth = elementRefObject.nativeElement.offsetWidth; + const nativeElement = elementRefObject.nativeElement; + const itemWidth = nativeElement.offsetWidth; - elementRefObject.triggerEventHandler('panstart', { - deltaX: factorX < 0 ? -10 : 10 - }); - elementRefObject.triggerEventHandler('panmove', { - deltaX: factorX * itemWidth, duration: 200 - }); - elementRefObject.triggerEventHandler('panend', null); + dispatchPointer(nativeElement, 'pointerdown', 0); + dispatchPointer(nativeElement, 'pointermove', factorX * itemWidth); + dispatchPointer(nativeElement, 'pointerup', factorX * itemWidth); return new Promise(resolve => { resolve(); }); @@ -785,27 +794,19 @@ describe('List', () => { }; const clickAndDrag = (itemNativeElement, factorX) => { - const itemWidth = itemNativeElement.nativeElement.offsetWidth; + const nativeElement = itemNativeElement.nativeElement; + const itemWidth = nativeElement.offsetWidth; - itemNativeElement.triggerEventHandler('panstart', { - deltaX: factorX < 0 ? -10 : 10 - }); - itemNativeElement.triggerEventHandler('panmove', { - deltaX: factorX * itemWidth, duration: 200 - }); + dispatchPointer(nativeElement, 'pointerdown', 0); + dispatchPointer(nativeElement, 'pointermove', factorX * itemWidth); }; const cancelItemPanning = (itemNativeElement, factorX, factorY) => { - itemNativeElement.triggerEventHandler('panstart', { - deltaX: factorX - }); - itemNativeElement.triggerEventHandler('panmove', { - deltaX: factorX, - deltaY: factorY, - additionalEvent: 'panup' - }); + const nativeElement = itemNativeElement.nativeElement; - itemNativeElement.triggerEventHandler('pancancel', null); + dispatchPointer(nativeElement, 'pointerdown', 0, 0); + dispatchPointer(nativeElement, 'pointermove', factorX, factorY); + dispatchPointer(nativeElement, 'pointercancel', factorX, factorY); }; const verifyItemsCount = (list, expectedCount) => { diff --git a/projects/igniteui-angular/navigation-drawer/src/navigation-drawer/navigation-drawer.component.spec.ts b/projects/igniteui-angular/navigation-drawer/src/navigation-drawer/navigation-drawer.component.spec.ts index 52de9bff4c8..4da632f915b 100644 --- a/projects/igniteui-angular/navigation-drawer/src/navigation-drawer/navigation-drawer.component.spec.ts +++ b/projects/igniteui-angular/navigation-drawer/src/navigation-drawer/navigation-drawer.component.spec.ts @@ -3,15 +3,11 @@ import { Component, ElementRef, Renderer2, ViewChild, ChangeDetectionStrategy } import { By } from '@angular/platform-browser'; import { wait } from '../../../test-utils/ui-interactions.spec'; import { IgxNavigationDrawerComponent } from './navigation-drawer.component'; -import { HammerGesturesManager, IgxNavigationService } from 'igniteui-angular/core'; -import { HammerInput } from 'igniteui-angular/core'; +import { IgxNavigationService } from 'igniteui-angular/core'; import { IgxNavDrawerItemDirective, IgxNavDrawerMiniTemplateDirective, IgxNavDrawerTemplateDirective } from './navigation-drawer.directives'; import { IgxNavbarComponent } from 'igniteui-angular/navbar'; import { IgxFlexDirective, IgxLayoutDirective } from 'igniteui-angular/directives'; -// HammerJS simulator from https://github.com/hammerjs/simulator, manual typings TODO -declare let Simulator: any; - describe('Navigation Drawer', () => { let widthSpyOverride: jasmine.Spy; beforeEach(waitForAsync(() => { @@ -25,8 +21,7 @@ describe('Navigation Drawer', () => { providers: [ IgxNavigationDrawerComponent, { provide: ElementRef, useValue: null }, - Renderer2, - HammerGesturesManager + Renderer2 ] }).compileComponents(); @@ -111,14 +106,11 @@ describe('Navigation Drawer', () => { const fixture = TestBed.createComponent(TestComponentDIComponent); fixture.detectChanges(); const state: IgxNavigationService = fixture.componentInstance.navDrawer.state; - const touchManager = fixture.componentInstance.navDrawer.touchManager; expect(state.get('testNav')).toBeDefined(); - expect(touchManager.getManagerForElement(document) instanceof Hammer.Manager).toBeTruthy(); fixture.destroy(); expect(state.get('testNav')).toBeUndefined(); - expect(touchManager.getManagerForElement(document)).toBe(null); }).catch((reason) => Promise.reject(reason)); })); @@ -398,6 +390,11 @@ describe('Navigation Drawer', () => { let navDrawer; let fixture: ComponentFixture; + // immediate requestAnimationFrame so setXSize applies the transform synchronously for mid-gesture assertions + spyOn(window, 'requestAnimationFrame').and.callFake(callback => { + callback(0); return 0; + }); + // Using bare minimum of timeouts, jasmine.DEFAULT_TIMEOUT_INTERVAL can be modified only in beforeEach TestBed.compileComponents().then(() => { fixture = TestBed.createComponent(TestComponentDIComponent); @@ -409,7 +406,7 @@ describe('Navigation Drawer', () => { expect(fixture.componentInstance.navDrawer.isOpen).toEqual(false); - const listener = navDrawer.renderer.listen(document.body, 'panmove', () => { + const listener = navDrawer.renderer.listen(document, 'pointermove', () => { // mid gesture expect(navDrawer.drawer.classList).toContain('panning'); @@ -682,20 +679,19 @@ describe('Navigation Drawer', () => { expect(drawer.drawer.matches(':popover-open')).toBeFalsy(); }); - describe('direct gesture handler unit tests (mocked HammerInput)', () => { + describe('direct gesture handler unit tests (mocked gesture input)', () => { let fixture: ComponentFixture; let navDrawer: IgxNavigationDrawerComponent; - /** Builds a minimal HammerInput-like object. */ - const makeHammerInput = (overrides: Partial<{ + const makeGestureInput = (overrides: Partial<{ deltaX: number; deltaY: number; pointerType: string; center: { x: number; y: number }; distance: number; - }> = {}): HammerInput => ({ + }> = {}): any => ({ deltaX: 0, deltaY: 0, pointerType: 'touch', center: { x: 0, y: 0 }, distance: 0, preventDefault: () => {}, ...overrides - } as HammerInput); + }); beforeEach(waitForAsync(() => { TestBed.compileComponents().then(() => { @@ -711,20 +707,20 @@ describe('Navigation Drawer', () => { it('swipe: should toggle drawer on a valid swipe', () => { expect(navDrawer.isOpen).toBeFalse(); // simulate a swipe from the left edge - (navDrawer as any).swipe(makeHammerInput({ deltaX: 250, center: { x: 200, y: 10 }, distance: 250 })); + (navDrawer as any).swipe(makeGestureInput({ deltaX: 250, center: { x: 200, y: 10 }, distance: 250 })); expect(navDrawer.isOpen).toBeTrue(); }); it('swipe: should return early when enableGestures is false', () => { navDrawer.enableGestures = false; const spy = spyOn(navDrawer, 'toggle'); - (navDrawer as any).swipe(makeHammerInput({ deltaX: 250, center: { x: 10, y: 10 }, distance: 250 })); + (navDrawer as any).swipe(makeGestureInput({ deltaX: 250, center: { x: 10, y: 10 }, distance: 250 })); expect(spy).not.toHaveBeenCalled(); }); it('swipe: should return early when pointerType is not touch', () => { const spy = spyOn(navDrawer, 'toggle'); - (navDrawer as any).swipe(makeHammerInput({ pointerType: 'mouse', deltaX: 250, center: { x: 10, y: 10 }, distance: 250 })); + (navDrawer as any).swipe(makeGestureInput({ pointerType: 'mouse', deltaX: 250, center: { x: 10, y: 10 }, distance: 250 })); expect(spy).not.toHaveBeenCalled(); }); @@ -733,27 +729,27 @@ describe('Navigation Drawer', () => { fixture.detectChanges(); expect(navDrawer.isOpen).toBeTrue(); // negative deltaX triggers isOpen && deltaX < 0 → toggle (close) - (navDrawer as any).swipe(makeHammerInput({ deltaX: -200, center: { x: 200, y: 10 }, distance: 200 })); + (navDrawer as any).swipe(makeGestureInput({ deltaX: -200, center: { x: 200, y: 10 }, distance: 200 })); expect(navDrawer.isOpen).toBeFalse(); }); it('panstart: should set _panning flag when conditions are met', () => { expect((navDrawer as any)._panning).toBeFalse(); // simulate start from left edge (startPosition < maxEdgeZone) - (navDrawer as any).panstart(makeHammerInput({ deltaX: 0, center: { x: 30, y: 10 }, distance: 0 })); + (navDrawer as any).panstart(makeGestureInput({ deltaX: 0, center: { x: 30, y: 10 }, distance: 0 })); expect((navDrawer as any)._panning).toBeTrue(); }); it('panstart: should not set _panning when gestures disabled', () => { navDrawer.enableGestures = false; - (navDrawer as any).panstart(makeHammerInput({ deltaX: 0, center: { x: 10, y: 10 }, distance: 0 })); + (navDrawer as any).panstart(makeGestureInput({ deltaX: 0, center: { x: 10, y: 10 }, distance: 0 })); expect((navDrawer as any)._panning).toBeFalse(); }); it('panstart: should not set _panning when pin is true', () => { navDrawer.pin = true; fixture.detectChanges(); - (navDrawer as any).panstart(makeHammerInput({ deltaX: 0, center: { x: 10, y: 10 }, distance: 0 })); + (navDrawer as any).panstart(makeGestureInput({ deltaX: 0, center: { x: 10, y: 10 }, distance: 0 })); expect((navDrawer as any)._panning).toBeFalse(); navDrawer.pin = false; }); @@ -765,14 +761,14 @@ describe('Navigation Drawer', () => { (navDrawer as any)._panLimit = 280; const setXSpy = spyOn(navDrawer, 'setXSize').and.callThrough(); // opening pan: not open, positive deltaX, visibleWidth < panLimit - (navDrawer as any).pan(makeHammerInput({ deltaX: 100, center: { x: 100, y: 10 }, distance: 100 })); + (navDrawer as any).pan(makeGestureInput({ deltaX: 100, center: { x: 100, y: 10 }, distance: 100 })); expect(setXSpy).toHaveBeenCalled(); }); it('pan: should return early when _panning is false', () => { (navDrawer as any)._panning = false; const setXSpy = spyOn(navDrawer, 'setXSize'); - (navDrawer as any).pan(makeHammerInput({ deltaX: 200 })); + (navDrawer as any).pan(makeGestureInput({ deltaX: 200 })); expect(setXSpy).not.toHaveBeenCalled(); }); @@ -781,7 +777,7 @@ describe('Navigation Drawer', () => { (navDrawer as any)._panStartWidth = 0; (navDrawer as any)._panLimit = 280; // visibleWidth = 0 + 200 = 200 ≥ 280/2 → open - (navDrawer as any).panEnd(makeHammerInput({ deltaX: 200 })); + (navDrawer as any).panEnd(makeGestureInput({ deltaX: 200 })); expect(navDrawer.isOpen).toBeTrue(); }); @@ -792,7 +788,7 @@ describe('Navigation Drawer', () => { (navDrawer as any)._panStartWidth = 280; (navDrawer as any)._panLimit = 0; // visibleWidth = 280 + (-200) = 80 ≤ 280/2 → close - (navDrawer as any).panEnd(makeHammerInput({ deltaX: -200 })); + (navDrawer as any).panEnd(makeGestureInput({ deltaX: -200 })); expect(navDrawer.isOpen).toBeFalse(); }); @@ -800,7 +796,7 @@ describe('Navigation Drawer', () => { (navDrawer as any)._panning = false; const openSpy = spyOn(navDrawer, 'open'); const closeSpy = spyOn(navDrawer, 'close'); - (navDrawer as any).panEnd(makeHammerInput({ deltaX: 200 })); + (navDrawer as any).panEnd(makeGestureInput({ deltaX: 200 })); expect(openSpy).not.toHaveBeenCalled(); expect(closeSpy).not.toHaveBeenCalled(); }); @@ -818,41 +814,45 @@ describe('Navigation Drawer', () => { }); }); - const swipe = (element, posX, posY, duration, deltaX, deltaY) => { - const swipeOptions = { - deltaX, - deltaY, - duration, - pos: [posX, posY] - }; - - return new Promise(resolve => { - - // force touch (https://github.com/hammerjs/hammer.js/issues/1065) - Simulator.setType('touch'); - Simulator.gestures.swipe(element, swipeOptions, () => { - resolve(); - }); - }); + const dispatchTouchPointerEvent = (element, type, clientX, clientY) => { + element.dispatchEvent(new PointerEvent(type, { + bubbles: true, + cancelable: true, + pointerId: 1, + pointerType: 'touch', + clientX, + clientY + })); }; - const pan = (element, posX, posY, duration, deltaX, deltaY) => { - const swipeOptions = { - deltaX, - deltaY, - duration, - pos: [posX, posY] - }; + const withMockedNow = (values: number[], callback: () => void) => { + const originalNow = Date.now; + let callIndex = 0; + (Date as any).now = () => values[Math.min(callIndex++, values.length - 1)]; + try { + callback(); + } finally { + (Date as any).now = originalNow; + } + }; - return new Promise(resolve => { + const swipe = (element, posX, posY, duration, deltaX, deltaY) => new Promise(resolve => { + withMockedNow([0, duration], () => { + dispatchTouchPointerEvent(element, 'pointerdown', posX, posY); + dispatchTouchPointerEvent(element, 'pointermove', posX + deltaX, posY + deltaY); + dispatchTouchPointerEvent(element, 'pointerup', posX + deltaX, posY + deltaY); + }); + resolve(); + }); - // force touch (https://github.com/hammerjs/hammer.js/issues/1065) - Simulator.setType('touch'); - Simulator.gestures.pan(element, swipeOptions, () => { - resolve(); - }); + const pan = (element, posX, posY, duration, deltaX, deltaY) => new Promise(resolve => { + withMockedNow([0, Math.max(duration, 1000)], () => { + dispatchTouchPointerEvent(element, 'pointerdown', posX, posY); + dispatchTouchPointerEvent(element, 'pointermove', posX + deltaX, posY + deltaY); + dispatchTouchPointerEvent(element, 'pointerup', posX + deltaX, posY + deltaY); }); - }; + resolve(); + }); }); @Component({ diff --git a/projects/igniteui-angular/navigation-drawer/src/navigation-drawer/navigation-drawer.component.ts b/projects/igniteui-angular/navigation-drawer/src/navigation-drawer/navigation-drawer.component.ts index 1b09c3510ae..11262e1feb4 100644 --- a/projects/igniteui-angular/navigation-drawer/src/navigation-drawer/navigation-drawer.component.ts +++ b/projects/igniteui-angular/navigation-drawer/src/navigation-drawer/navigation-drawer.component.ts @@ -1,12 +1,10 @@ import { AfterContentInit, afterRenderEffect, Component, ContentChild, ElementRef, EventEmitter, HostBinding, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChange, ViewChild, Renderer2, booleanAttribute, inject, signal, computed, ChangeDetectionStrategy } from '@angular/core'; +import { DOCUMENT, NgTemplateOutlet } from '@angular/common'; import { fromEvent, interval, Subscription } from 'rxjs'; import { debounce } from 'rxjs/operators'; import { IgxNavigationService, IToggleView } from 'igniteui-angular/core'; -import { HammerGesturesManager } from 'igniteui-angular/core'; import { IgxNavDrawerMiniTemplateDirective, IgxNavDrawerTemplateDirective, IgxNavDrawerItemDirective } from './navigation-drawer.directives'; -import { PlatformUtil } from 'igniteui-angular/core'; -import { NgTemplateOutlet } from '@angular/common'; -import { HammerInput } from 'igniteui-angular/core'; +import { IgxTouchManager, PlatformUtil } from 'igniteui-angular/core'; let NEXT_ID = 0; /** @@ -30,7 +28,6 @@ let NEXT_ID = 0; * ``` */ @Component({ - providers: [HammerGesturesManager], selector: 'igx-nav-drawer', templateUrl: 'navigation-drawer.component.html', styles: [` @@ -51,7 +48,7 @@ export class IgxNavigationDrawerComponent implements private elementRef = inject(ElementRef); private _state = inject(IgxNavigationService, { optional: true }); private renderer = inject(Renderer2); - private _touchManager = inject(HammerGesturesManager); + private _document = inject(DOCUMENT); private platformUtil = inject(PlatformUtil); private _isOpen = signal(false); @@ -360,6 +357,7 @@ export class IgxNavigationDrawerComponent implements } private _gesturesAttached = false; + private _gestures: IgxTouchManager | null = null; private _widthCache: { width: number; miniWidth: number; windowWidth: number } = { width: null, miniWidth: null, windowWidth: null }; private _resizeObserver: Subscription; private css: { [name: string]: string } = { @@ -438,9 +436,10 @@ export class IgxNavigationDrawerComponent implements /** * @hidden + * @deprecated in version 19.2.0. No longer uses HammerGesturesManager; native pointer events are used instead. */ public get touchManager() { - return this._touchManager; + return null; } /** @@ -487,7 +486,9 @@ export class IgxNavigationDrawerComponent implements * @hidden */ public ngOnDestroy() { - this._touchManager.destroy(); + this._gestures?.destroy(); + this._gestures = null; + this._gesturesAttached = false; if (this._state) { this._state.remove(this.id); } @@ -508,7 +509,8 @@ export class IgxNavigationDrawerComponent implements if (changes.pin && changes.pin.currentValue !== undefined) { this.pin = !!(this.pin && this.pin.toString() === 'true'); if (this.pin) { - this._touchManager.destroy(); + this._gestures?.destroy(); + this._gestures = null; this._gesturesAttached = false; } else { this.ensureEvents(); @@ -662,18 +664,14 @@ export class IgxNavigationDrawerComponent implements private ensureEvents() { // set listeners for swipe/pan only if needed, but just once if (this.enableGestures && !this.pin && !this._gesturesAttached) { - // Built-in manager handler(L20887) causes endless loop and max stack exception. - // https://github.com/angular/angular/issues/6993 - // Use ours for now (until beta.10): - // this.renderer.listen(document, "swipe", this.swipe); - this._touchManager.addGlobalEventListener('document', 'swipe', this.swipe); - this._gesturesAttached = true; + this._gestures = new IgxTouchManager(this._document, { + panStart: (event) => this.panstart(event), + panMove: (event) => this.pan(event), + swipe: (event) => this.swipe(event), + panEnd: (event) => this.panEnd(event) + }, { pointerTypes: ['touch'], setPointerCapture: false }); - // this.renderer.listen(document, "panstart", this.panstart); - // this.renderer.listen(document, "pan", this.pan); - this._touchManager.addGlobalEventListener('document', 'panstart', this.panstart); - this._touchManager.addGlobalEventListener('document', 'panmove', this.pan); - this._touchManager.addGlobalEventListener('document', 'panend', this.panEnd); + this._gesturesAttached = true; } if (!this._resizeObserver && this.platformUtil.isBrowser) { this._resizeObserver = fromEvent(window, 'resize').pipe(debounce(() => interval(150))) @@ -713,7 +711,7 @@ export class IgxNavigationDrawerComponent implements } }; - private swipe = (evt: HammerInput) => { + private swipe = (evt: any) => { // TODO: Could also force input type: http://stackoverflow.com/a/27108052 if (!this.enableGestures || evt.pointerType !== 'touch') { return; @@ -738,7 +736,7 @@ export class IgxNavigationDrawerComponent implements } }; - private panstart = (evt: HammerInput) => { // TODO: test code + private panstart = (evt: any) => { // TODO: test code if (!this.enableGestures || this.pin || evt.pointerType !== 'touch') { return; } @@ -756,7 +754,7 @@ export class IgxNavigationDrawerComponent implements } }; - private pan = (evt: HammerInput) => { + private pan = (evt: any) => { // TODO: input.deltaX = prevDelta.x + (center.x - offset.x); // get actual delta (not total session one) from event? // pan WILL also fire after a full swipe, only resize on flag @@ -802,7 +800,7 @@ export class IgxNavigationDrawerComponent implements } }; - private panEnd = (evt: HammerInput) => { + private panEnd = (evt: any) => { if (this._panning) { const deltaX = this.position === 'right' ? -evt.deltaX : evt.deltaX; const visibleWidth: number = this._panStartWidth + deltaX; diff --git a/projects/igniteui-angular/package.json b/projects/igniteui-angular/package.json index 44f1597ef4a..f827e11fda2 100644 --- a/projects/igniteui-angular/package.json +++ b/projects/igniteui-angular/package.json @@ -84,18 +84,10 @@ "@angular/core": "22", "@angular/animations": "22", "@angular/forms": "22", - "hammerjs": "^2.0.8", - "@types/hammerjs": "^2.0.46", "igniteui-webcomponents": "^7.2.1", "igniteui-grid-lite": "~0.7.1" }, "peerDependenciesMeta": { - "hammerjs": { - "optional": true - }, - "@types/hammerjs": { - "optional": true - }, "igniteui-webcomponents": { "optional": true }, diff --git a/projects/igniteui-angular/schematics/interfaces/options.ts b/projects/igniteui-angular/schematics/interfaces/options.ts index 22f94304917..14ca48801ea 100644 --- a/projects/igniteui-angular/schematics/interfaces/options.ts +++ b/projects/igniteui-angular/schematics/interfaces/options.ts @@ -2,5 +2,4 @@ export interface Options { [key: string]: any; polyfills: boolean; resetCss: boolean; - addHammer: boolean; } diff --git a/projects/igniteui-angular/schematics/ng-add/index.spec.ts b/projects/igniteui-angular/schematics/ng-add/index.spec.ts index ddf7d6f7815..5d1258c7a3c 100644 --- a/projects/igniteui-angular/schematics/ng-add/index.spec.ts +++ b/projects/igniteui-angular/schematics/ng-add/index.spec.ts @@ -71,7 +71,7 @@ describe('ng-add schematics', () => { it('should add packages to package.json dependencies', async () => { const expectedDeps = DEPENDENCIES_MAP.filter(dep => dep.target === PackageTarget.REGULAR).map(dep => dep.name); const expectedDevDeps = DEPENDENCIES_MAP.filter(dep => dep.target === PackageTarget.DEV).map(dep => dep.name); - await runner.runSchematic('ng-add', { normalizeCss: false, addHammer: true }, tree); + await runner.runSchematic('ng-add', { normalizeCss: false }, tree); const pkgJsonData = JSON.parse(tree.readContent('/package.json')); expect(pkgJsonData.dependencies).toBeTruthy(); expect(pkgJsonData.devDependencies).toBeTruthy(); @@ -86,123 +86,6 @@ describe('ng-add schematics', () => { } }); - it('should add the correct igniteui-angular packages to package.json dependencies', async () => { - await runner.runSchematic('ng-add', { normalizeCss: false }, tree); - const pkgJsonData = JSON.parse(tree.readContent('/package.json')); - // hammer is optional now. - expect(pkgJsonData.dependencies['hammerjs']).toBeFalsy(); - }); - - it('should add hammerjs dependency to package.json dependencies if addHammer prompt is set.', async () => { - await runner.runSchematic('ng-add', { normalizeCss: false, addHammer: true }, tree); - const pkgJsonData = JSON.parse(tree.readContent('/package.json')); - expect(pkgJsonData.dependencies['hammerjs']).toBeTruthy(); - }); - - it('should NOT add hammer.js to the main.ts file', async () => { - await runner.runSchematic('ng-add', { normalizeCss: false, addHammer: true }, tree); - const mainTs = tree.read(`${sourceRoot}/main.ts`).toString(); - expect(mainTs).not.toContain('import \'hammerjs\';'); - }); - - it('should NOT add hammer.js to the test.ts file', async () => { - await runner.runSchematic('ng-add', { normalizeCss: false, addHammer: true }, tree); - const testTs = tree.read(`${sourceRoot}/test.ts`).toString(); - expect(testTs).not.toContain('import \'hammerjs\';'); - }); - - // Hammer is optional now. - it('should not add hammer.js in angular.json build options under scripts', async () => { - await runner.runSchematic('ng-add', { normalizeCss: false }, tree); - const ngJsonConfigResult = JSON.parse(tree.read('/angular.json').toString()); - expect(ngJsonConfigResult.projects.testProj.architect.build.options.scripts).not.toContain('./node_modules/hammerjs/hammer.min.js'); - }); - - it('should add hammer.js in angular.json build options under scripts if addHammer prompt is set.', async () => { - await runner.runSchematic('ng-add', { normalizeCss: false, addHammer: true }, tree); - const ngJsonConfigResult = JSON.parse(tree.read('/angular.json').toString()); - expect(ngJsonConfigResult.projects.testProj.architect.build.options.scripts).toContain('./node_modules/hammerjs/hammer.min.js'); - }); - - // Hammer is optional now. - it('should not add hammer.js in angular.json test options under scripts', async () => { - await runner.runSchematic('ng-add', { normalizeCss: false }, tree); - const ngJsonConfigResult = JSON.parse(tree.read('/angular.json').toString()); - expect(ngJsonConfigResult.projects.testProj.architect.test.options.scripts).not.toContain('./node_modules/hammerjs/hammer.min.js'); - }); - - it('should add hammer.js in angular.json test options under scripts if addHammer prompt is set.', async () => { - await runner.runSchematic('ng-add', { normalizeCss: false, addHammer: true }, tree); - const ngJsonConfigResult = JSON.parse(tree.read('/angular.json').toString()); - expect(ngJsonConfigResult.projects.testProj.architect.test.options.scripts).toContain('./node_modules/hammerjs/hammer.min.js'); - }); - - it('should NOT duplicate hammer.js if it exists in angular.json build options', async () => { - const ngJsonConfig1 = JSON.parse(tree.read('/angular.json').toString()); - ngJsonConfig1.projects.testProj.architect.build.options.scripts.push('./node_modules/hammerjs/hammer.min.js'); - tree.overwrite('/angular.json', JSON.stringify(ngJsonConfig1)); - await runner.runSchematic('ng-add', { normalizeCss: false, addHammer: true }, tree); - - const ngJsonConfigResult = JSON.parse(tree.read('/angular.json').toString()); - expect(ngJsonConfigResult.projects.testProj.architect.build.options.scripts.length).toBe(1); - expect(ngJsonConfigResult.projects.testProj.architect.build.options.scripts).toMatch('./node_modules/hammerjs/hammer.min.js'); - }); - - it('should NOT duplicate hammer.js if it exists in angular.json test options', async () => { - const ngJsonConfig1 = JSON.parse(tree.read('/angular.json').toString()); - ngJsonConfig1.projects.testProj.architect.test.options.scripts.push('./node_modules/hammerjs/hammer.min.js'); - tree.overwrite('/angular.json', JSON.stringify(ngJsonConfig1)); - await runner.runSchematic('ng-add', { normalizeCss: false, addHammer: true }, tree); - - const ngJsonConfigResult = JSON.parse(tree.read('/angular.json').toString()); - expect(ngJsonConfigResult.projects.testProj.architect.test.options.scripts.length).toBe(1); - expect(ngJsonConfigResult.projects.testProj.architect.test.options.scripts).toMatch('./node_modules/hammerjs/hammer.min.js'); - }); - - it('should NOT add hammer.js to main.ts if it exists in angular.json build options', async () => { - const ngJsonConfig1 = JSON.parse(tree.read('/angular.json').toString()); - ngJsonConfig1.projects.testProj.architect.build.options.scripts.push('./node_modules/hammerjs/hammer.min.js'); - tree.overwrite('/angular.json', JSON.stringify(ngJsonConfig1)); - await runner.runSchematic('ng-add', { normalizeCss: false, addHammer: true }, tree); - - const newContent = tree.read(`${sourceRoot}/main.ts`).toString(); - expect(newContent).toMatch('// test comment'); - }); - - it('should NOT add hammer.js to test.ts if it exists in angular.json test options', async () => { - const ngJsonConfig1 = JSON.parse(tree.read('/angular.json').toString()); - ngJsonConfig1.projects.testProj.architect.test.options.scripts.push('./node_modules/hammerjs/hammer.min.js'); - tree.overwrite('/angular.json', JSON.stringify(ngJsonConfig1)); - await runner.runSchematic('ng-add', { normalizeCss: false, addHammer: true }, tree); - - const newContent = tree.read(`${sourceRoot}/test.ts`).toString(); - expect(newContent).toMatch('// test comment'); - }); - - // Hammer is optional now. - it('should not add hammer.js to package.json dependencies', async () => { - await runner.runSchematic('ng-add', { normalizeCss: false }, tree); - const pkgJsonData = JSON.parse(tree.readContent('/package.json')); - expect(pkgJsonData.dependencies['hammerjs']).toBeFalsy(); - }); - - it('should add hammer.js to package.json dependencies if addHammer prompt is set.', async () => { - await runner.runSchematic('ng-add', { normalizeCss: false, addHammer: true }, tree); - const pkgJsonData = JSON.parse(tree.readContent('/package.json')); - expect(pkgJsonData.dependencies['hammerjs']).toBeTruthy(); - }); - - it('should NOT add hammer.js to angular.json if it exists in main.ts options', async () => { - const mainTsPath = `${sourceRoot}/main.ts`; - const content = tree.read(mainTsPath).toString(); - tree.overwrite(mainTsPath, 'import \'hammerjs\';\n' + content); - await runner.runSchematic('ng-add', { normalizeCss: false, addHammer: true }, tree); - - const ngJsonConfigResult = JSON.parse(tree.read('/angular.json').toString()); - expect(ngJsonConfigResult.projects.testProj.architect.build.options.scripts.length).toBe(0); - expect(ngJsonConfigResult.projects.testProj.architect.build.options.scripts).not.toContain('./node_modules/hammerjs/hammer.min.js'); - }); - it('should add the CLI only to devDependencies', async () => { await runner.runSchematic('ng-add', { normalizeCss: false }, tree); const pkgJsonData = JSON.parse(tree.readContent('/package.json')); diff --git a/projects/igniteui-angular/schematics/ng-add/schema.json b/projects/igniteui-angular/schematics/ng-add/schema.json index 0a6b2fe42c6..0c7905ed8f2 100644 --- a/projects/igniteui-angular/schematics/ng-add/schema.json +++ b/projects/igniteui-angular/schematics/ng-add/schema.json @@ -9,12 +9,6 @@ "description": "Add reset CSS lib", "default": true, "x-prompt": "Add CSS library to reset HTML element styles across browsers?" - }, - "addHammer": { - "type": "boolean", - "description": "Add HammerJS", - "default": false, - "x-prompt": "Add HammerJS setup for touch-specific interactions support?" } }, "required": [] diff --git a/projects/igniteui-angular/schematics/utils/dependency-handler.ts b/projects/igniteui-angular/schematics/utils/dependency-handler.ts index 80f86324481..36bdb102417 100644 --- a/projects/igniteui-angular/schematics/utils/dependency-handler.ts +++ b/projects/igniteui-angular/schematics/utils/dependency-handler.ts @@ -33,8 +33,6 @@ export const DEPENDENCIES_MAP: PackageEntry[] = [ { name: '@angular/common', target: PackageTarget.NONE }, { name: '@angular/core', target: PackageTarget.NONE }, { name: '@angular/animations', target: PackageTarget.NONE }, - { name: 'hammerjs', target: PackageTarget.REGULAR }, - { name: '@types/hammerjs', target: PackageTarget.DEV }, { name: 'igniteui-webcomponents', target: PackageTarget.NONE }, { name: 'igniteui-grid-lite', target: PackageTarget.NONE }, // igxDevDependencies @@ -141,25 +139,6 @@ export const getPropertyFromWorkspace = (targetProp: string, workspace: any, cur return null; }; -const addHammerToConfig = - async (project: workspaces.ProjectDefinition, tree: Tree, config: string, context: SchematicContext): Promise => { - const projectOptions = getTargetedProjectOptions(project, config, context); - const tsPath = getConfigFile(project, 'main', context, config); - const hammerImport = 'import \'hammerjs\';\n'; - const tsContent = tree.read(tsPath)?.toString(); - // if there are no elements in the architect[config]options.scripts array that contain hammerjs - // and the "main" file does not contain an import with hammerjs - if (!projectOptions?.scripts?.some(el => el.includes('hammerjs')) && !tsContent?.includes(hammerImport)) { - const hammerjsFilePath = './node_modules/hammerjs/hammer.min.js'; - if (projectOptions?.scripts) { - projectOptions.scripts.push(hammerjsFilePath); - return; - } - context.logger.warn(`Could not find a matching scripts array property under ${config} options. ` + - `It could require you to manually update it to 'scripts': [ ${hammerjsFilePath}] `); - } - }; - export const includeStylePreprocessorOptions = async (workspaceHost: workspaces.WorkspaceHost, workspace: workspaces.WorkspaceDefinition, context: SchematicContext, tree: Tree): Promise => { await Promise.all(Array.from(workspace.projects.values()).map(async (project: workspaces.ProjectDefinition) => { if (project.extensions['projectType'] === ProjectType.Library) return; @@ -198,10 +177,6 @@ const addStylePreprocessorOptions = const includeDependencies = async (workspaceHost: workspaces.WorkspaceHost, workspace: workspaces.WorkspaceDefinition, pkgJson: any, context: SchematicContext, tree: Tree, options: Options): Promise => { const allDeps = Object.keys(pkgJson.dependencies).concat(Object.keys(pkgJson.peerDependencies)); for (const pkg of allDeps) { - // In case of hammerjs and user prompted to not add hammer, skip - if (pkg === 'hammerjs' && !options.addHammer) { - continue; - } const version = pkgJson.dependencies[pkg] || pkgJson.peerDependencies[pkg]; const entry = DEPENDENCIES_MAP.find(e => e.name === pkg); if (!entry || entry.target === PackageTarget.NONE) { @@ -209,12 +184,6 @@ const includeDependencies = async (workspaceHost: workspaces.WorkspaceHost, work } logIncludingDependency(context, pkg, version); addPackageToPkgJson(tree, pkg, version, entry.target); - if (pkg === 'hammerjs') { - await Promise.all(Array.from(workspace.projects.values()).map(async (project) => { - await addHammerToConfig(project, tree, 'build', context); - await addHammerToConfig(project, tree, 'test', context); - })); - } } await workspaces.writeWorkspace(workspace, workspaceHost); }; diff --git a/projects/igniteui-angular/time-picker/src/time-picker/time-picker.component.spec.ts b/projects/igniteui-angular/time-picker/src/time-picker/time-picker.component.spec.ts index e539eeda4d5..126774a4519 100644 --- a/projects/igniteui-angular/time-picker/src/time-picker/time-picker.component.spec.ts +++ b/projects/igniteui-angular/time-picker/src/time-picker/time-picker.component.spec.ts @@ -15,8 +15,6 @@ import { IgxDateTimeEditorDirective } from '../../../directives/src/directives/d import { IgxItemListDirective, IgxTimeItemDirective } from './time-picker.directives'; import { IgxPickerClearComponent, IgxPickerToggleComponent } from '../../../core/src/date-common/public_api'; import { Subscription } from 'rxjs'; -import { HammerGesturesManager } from 'igniteui-angular/core'; -import { HammerOptions } from 'igniteui-angular/core'; import { registerLocaleData } from "@angular/common"; import localeJa from "@angular/common/locales/ja"; import localeBg from "@angular/common/locales/bg"; @@ -203,7 +201,6 @@ describe('IgxTimePicker', () => { { provide: IGX_TIME_PICKER_COMPONENT, useExisting: IgxTimePickerComponent }, IgxTimePickerComponent, PlatformUtil, - HammerGesturesManager, IgxItemListDirective ] }); @@ -453,25 +450,6 @@ describe('IgxTimePicker', () => { expect(timePicker.validate(mockFormControl)).toEqual({ maxValue: true }); }); - it('should handle panmove event correctly', () => { - const touchManager = TestBed.inject(HammerGesturesManager); - const itemListDirective = TestBed.inject(IgxItemListDirective); - spyOn(touchManager, 'addEventListener'); - - itemListDirective.ngOnInit(); - expect(touchManager.addEventListener).toHaveBeenCalledTimes(1); - const hammerOptions: HammerOptions = { recognizers: [[HammerGesturesManager.Hammer.Pan, { direction: HammerGesturesManager.Hammer.DIRECTION_VERTICAL, threshold: 10 }]] }; - expect(touchManager.addEventListener).toHaveBeenCalledWith( - elementRef.nativeElement, - 'pan', - (itemListDirective as any).onPanMove, - hammerOptions); - - spyOn(itemListDirective, 'onPanMove').and.callThrough(); - const event = { type: 'pan' }; - (itemListDirective as any).onPanMove(event); - expect(itemListDirective['onPanMove']).toHaveBeenCalled(); - }); }); describe('Interaction tests', () => { diff --git a/projects/igniteui-angular/time-picker/src/time-picker/time-picker.directives.ts b/projects/igniteui-angular/time-picker/src/time-picker/time-picker.directives.ts index 8f6eb6010bf..ebdf6c8b93d 100644 --- a/projects/igniteui-angular/time-picker/src/time-picker/time-picker.directives.ts +++ b/projects/igniteui-angular/time-picker/src/time-picker/time-picker.directives.ts @@ -14,19 +14,20 @@ import { OnDestroy, OnInit } from '@angular/core'; -import { DateTimeUtil, HammerGesturesManager, HammerInput, HammerOptions, I18N_FORMATTER } from 'igniteui-angular/core'; +import { DateTimeUtil, I18N_FORMATTER, IgxTouchManager } from 'igniteui-angular/core'; import { IgxTimePickerBase, IGX_TIME_PICKER_COMPONENT } from './time-picker.common'; /** @hidden */ @Directive({ selector: '[igxItemList]', - providers: [HammerGesturesManager], standalone: true }) export class IgxItemListDirective implements OnInit, OnDestroy { public timePicker = inject(IGX_TIME_PICKER_COMPONENT); private elementRef = inject(ElementRef); - private touchManager = inject(HammerGesturesManager); + + private _lastDelta = 0; + private _gestures: IgxTouchManager | null = null; @HostBinding('attr.tabindex') public tabindex = 0; @@ -185,34 +186,40 @@ export class IgxItemListDirective implements OnInit, OnDestroy { * @hidden @internal */ public ngOnInit() { - const hammerOptions: HammerOptions = { - recognizers: [ - [ - HammerGesturesManager.Hammer?.Pan, - { - direction: HammerGesturesManager.Hammer?.DIRECTION_VERTICAL, - threshold: this.PAN_THRESHOLD - } - ] - ] - }; - this.touchManager.addEventListener(this.elementRef.nativeElement, 'pan', this.onPanMove, hammerOptions); + const threshold = this.PAN_THRESHOLD; + + this._gestures = new IgxTouchManager(this.elementRef.nativeElement, { + panStart: () => { + this._lastDelta = 0; + }, + panMove: (event) => { + if (Math.abs(event.deltaY) < threshold) { + return; + } + + const newDelta = event.deltaY < 0 ? -1 : event.deltaY > 0 ? 1 : 0; + if (newDelta !== 0 && newDelta !== this._lastDelta) { + this._lastDelta = newDelta; + this.nextItem(newDelta); + event.resetOrigin(); + } + }, + panEnd: () => { + this._lastDelta = 0; + }, + panCancel: () => { + this._lastDelta = 0; + } + }); } /** * @hidden @internal */ public ngOnDestroy() { - this.touchManager.destroy(); + this._gestures?.destroy(); } - private onPanMove = (event: HammerInput) => { - const delta = event.deltaY < 0 ? -1 : event.deltaY > 0 ? 1 : 0; - if (delta !== 0) { - this.nextItem(delta); - } - }; - private nextItem(delta: number): void { switch (this.type) { case 'hourList': { diff --git a/projects/igniteui-angular/tsconfig.spec.json b/projects/igniteui-angular/tsconfig.spec.json index e9f259bee39..767fc5a9da2 100644 --- a/projects/igniteui-angular/tsconfig.spec.json +++ b/projects/igniteui-angular/tsconfig.spec.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "../../out-tsc/spec", - "types": ["jasmine", "node", "hammerjs"] + "types": ["jasmine", "node"] }, "exclude": [ "migrations/**/*.spec.ts", diff --git a/src/tsconfig.spec.json b/src/tsconfig.spec.json index d781921c326..ddd20a5688f 100644 --- a/src/tsconfig.spec.json +++ b/src/tsconfig.spec.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/spec", - "types": ["jasmine", "node", "hammerjs"] + "types": ["jasmine", "node"] }, "files": ["test.ts", "polyfills.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"],