Merge pull request #12 from brianpooe/claude/angular-signals-upgrade-01LkzyeFbjsqKer2XzATwcxH

refactor: Implement clean Angular architecture with explicit folder s…
This commit is contained in:
Brian Pooe 2025-11-16 11:05:16 +02:00 committed by GitHub
commit d5a96e8d77
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 2065 additions and 2031 deletions

View file

@ -1,13 +1,10 @@
import { Component, ElementRef, HostBinding, OnInit, OnDestroy, computed, input, output, signal, effect, inject } from '@angular/core';
import { Properties } from './interfaces';
import { defaultProperties } from './properties';
import { IvyPinch } from './ivypinch';
import { CommonModule } from '@angular/common';
import { BrightnessService } from './services/brightness.service';
import { ZoomStateService } from './services/zoom-state.service';
import { ZoomControlsComponent } from './components/zoom-controls/zoom-controls.component';
import { BrightnessControlsComponent } from './components/brightness-controls/brightness-controls.component';
import { Properties, defaultProperties } from '../../../models';
import { IvyPinch, BrightnessService, ZoomStateService } from '../../../services';
import { ZoomControlsComponent } from '../../presentational/zoom-controls/zoom-controls.component';
import { BrightnessControlsComponent } from '../../presentational/brightness-controls/brightness-controls.component';
interface ComponentProperties extends Properties {
disabled?: boolean;
@ -25,8 +22,8 @@ export const _defaultComponentProperties: ComponentProperties = {
@Component({
selector: 'pinch-zoom, [pinch-zoom]',
exportAs: 'pinchZoom',
templateUrl: './pinch-zoom.component.html',
styleUrls: ['./pinch-zoom.component.sass'],
templateUrl: './pinch-zoom.container.html',
styleUrls: ['./pinch-zoom.container.sass'],
standalone: true,
imports: [CommonModule, ZoomControlsComponent, BrightnessControlsComponent],
providers: [BrightnessService, ZoomStateService],

View file

@ -1,2 +1,6 @@
export * from './zoom-controls/zoom-controls.component';
export * from './brightness-controls/brightness-controls.component';
// Container (Smart) Components
export * from './containers/pinch-zoom/pinch-zoom.container';
// Presentational (Dumb) Components
export * from './presentational/zoom-controls/zoom-controls.component';
export * from './presentational/brightness-controls/brightness-controls.component';

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,8 @@
// Core models
export * from './properties.model';
export * from './interfaces.model';
// Feature models
export * from './zoom-config.model';
export * from './transform-state.model';
export * from './brightness-state.model';

View file

@ -1,4 +1,4 @@
import { Properties } from './interfaces';
import { Properties } from './interfaces.model';
export const defaultProperties: Properties = {
transitionDuration: 200,

View file

@ -1,2 +1,4 @@
export * from './brightness.service';
export * from './zoom-state.service';
export * from './ivy-pinch.service';
export * from './touches.service';

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,465 @@
/**
* Properties interface for Touches gesture detection.
*/
export interface TouchProperties {
element: HTMLElement;
listeners?: 'auto' | 'mouse and touch';
touchListeners?: TouchListeners;
mouseListeners?: MouseListeners;
otherListeners?: OtherListeners;
resize?: boolean;
}
export type EventType =
| undefined
| 'touchstart'
| 'touchend'
| 'touchmove'
| 'mousedown'
| 'mouseup'
| 'mousemove'
| 'pan'
| 'pinch'
| 'horizontal-swipe'
| 'vertical-swipe'
| 'tap'
| 'longtap'
| 'wheel'
| 'double-tap'
| 'resize';
export type TouchHandler = 'handleTouchstart' | 'handleTouchmove' | 'handleTouchend';
export type MouseHandler = 'handleMousedown' | 'handleMousemove' | 'handleMouseup' | 'handleWheel';
export type OtherHandler = 'handleResize';
export type TouchListeners = Partial<Record<'touchstart' | 'touchmove' | 'touchend', TouchHandler>>;
export type MouseListeners = Partial<Record<'mousedown' | 'mousemove' | 'mouseup' | 'wheel', MouseHandler>>;
export type OtherListeners = Partial<Record<'resize', OtherHandler>>;
/**
* Touch and mouse gesture detection utility class.
* Instantiated directly with 'new Touches(properties)'.
* Not an Angular service - does not use dependency injection.
*/
export class Touches {
private properties: TouchProperties;
private element: HTMLElement;
private elementPosition: DOMRect;
private eventType: EventType = undefined;
private handlers: TouchListeners | MouseListeners | OtherListeners = {};
private startX = 0;
private startY = 0;
private lastTap = 0;
private doubleTapTimeout: number = 0;
private doubleTapMinTimeout = 300;
private tapMinTimeout = 200;
private touchstartTime = 0;
private i: number = 0;
private isMousedown = false;
// Store bound event handlers to prevent memory leaks
private boundHandlers = new Map<string, EventListener>();
private _touchListeners: Record<'touchstart' | 'touchmove' | 'touchend', TouchHandler> = {
touchstart: 'handleTouchstart',
touchmove: 'handleTouchmove',
touchend: 'handleTouchend',
};
private _mouseListeners: Record<'mousedown' | 'mousemove' | 'mouseup' | 'wheel', MouseHandler> = {
mousedown: 'handleMousedown',
mousemove: 'handleMousemove',
mouseup: 'handleMouseup',
wheel: 'handleWheel',
};
private _otherListeners: Record<'resize', OtherHandler> = {
resize: 'handleResize',
};
private get touchListeners(): TouchListeners {
return this.properties.touchListeners ? this.properties.touchListeners : this._touchListeners;
}
private get mouseListeners(): MouseListeners {
return this.properties.mouseListeners ? this.properties.mouseListeners : this._mouseListeners;
}
private get otherListeners(): OtherListeners {
return this.properties.otherListeners ? this.properties.otherListeners : this._otherListeners;
}
constructor(properties: TouchProperties) {
this.properties = properties;
this.element = this.properties.element;
this.elementPosition = this.getElementPosition();
this.toggleEventListeners('addEventListener');
}
public destroy(): void {
this.toggleEventListeners('removeEventListener');
}
private toggleEventListeners(action: 'addEventListener' | 'removeEventListener'): void {
let listeners: TouchListeners | MouseListeners | OtherListeners;
if (this.properties.listeners === 'mouse and touch') {
listeners = Object.assign(this.touchListeners, this.mouseListeners);
} else {
listeners = this.detectTouchScreen() ? this.touchListeners : this.mouseListeners;
}
if (this.properties.resize) {
listeners = Object.assign(listeners, this.otherListeners);
}
for (const listener in listeners) {
const handler = listeners[listener as keyof typeof listeners];
if (!handler) continue;
const method = this[handler as keyof this];
if (typeof method !== 'function') continue;
let boundMethod: EventListener;
if (action === 'addEventListener') {
// Create and store the bound method
boundMethod = method.bind(this) as EventListener;
this.boundHandlers.set(listener, boundMethod);
} else {
// Retrieve the stored bound method
const storedHandler = this.boundHandlers.get(listener);
if (!storedHandler) continue; // Skip if no handler was stored
boundMethod = storedHandler;
}
// Determine target element (Window, Document, or this.element)
const target = listener === 'resize' ? window : listener === 'mouseup' || listener === 'mousemove' ? document : this.element;
// Add or remove the listener
if (action === 'addEventListener') {
target.addEventListener(listener, boundMethod, false);
} else {
target.removeEventListener(listener, boundMethod, false);
this.boundHandlers.delete(listener); // Clean up
}
}
}
public addEventListeners(listener: string): void {
const handler = this._mouseListeners[listener as keyof typeof this._mouseListeners];
if (!handler) return;
const method = this[handler as keyof this];
if (typeof method === 'function') {
// Create a unique key for dynamically added listeners
const key = `dynamic-${listener}`;
const boundMethod = method.bind(this) as EventListener;
this.boundHandlers.set(key, boundMethod);
window.addEventListener(listener, boundMethod, false);
}
}
public removeEventListeners(listener: string): void {
// Try to retrieve the dynamically added listener
const key = `dynamic-${listener}`;
const boundMethod = this.boundHandlers.get(key);
if (boundMethod) {
window.removeEventListener(listener, boundMethod, false);
this.boundHandlers.delete(key);
}
}
/*
* Listeners
*/
/* Touchstart */
private handleTouchstart = (event: TouchEvent): void => {
this.elementPosition = this.getElementPosition();
this.touchstartTime = new Date().getTime();
if (this.eventType === undefined) {
this.getTouchstartPosition(event);
}
this.runHandler('touchstart', event);
};
/* Touchmove */
private handleTouchmove = (event: TouchEvent): void => {
const touches = event.touches;
// Pan
if (this.detectPan(touches)) {
this.runHandler('pan', event);
}
// Pinch
if (this.detectPinch(event)) {
this.runHandler('pinch', event);
}
};
private handleLinearSwipe(event: any): void {
//event.preventDefault();
this.i++;
if (this.i > 3) {
this.eventType = this.getLinearSwipeType(event);
}
if (this.eventType === 'horizontal-swipe') {
this.runHandler('horizontal-swipe', event);
}
if (this.eventType === 'vertical-swipe') {
this.runHandler('vertical-swipe', event);
}
}
/* Touchend */
private handleTouchend = (event: TouchEvent): void => {
const touches = event.touches;
// Double Tap
if (this.detectDoubleTap()) {
this.runHandler('double-tap', event);
}
// Tap
this.detectTap();
this.runHandler('touchend', event);
this.eventType = 'touchend';
if (touches && touches.length === 0) {
this.eventType = undefined;
this.i = 0;
}
};
/* Mousedown */
private handleMousedown = (event: MouseEvent): void => {
this.isMousedown = true;
this.elementPosition = this.getElementPosition();
this.touchstartTime = new Date().getTime();
if (this.eventType === undefined) {
this.getMousedownPosition(event);
}
this.runHandler('mousedown', event);
};
/* Mousemove */
private handleMousemove = (event: MouseEvent): void => {
//event.preventDefault();
if (!this.isMousedown) {
return;
}
// Pan
this.runHandler('pan', event);
// Linear swipe
switch (this.detectLinearSwipe(event)) {
case 'horizontal-swipe':
// FIXME: looks like an error
// @ts-ignore
event.swipeType = 'horizontal-swipe';
this.runHandler('horizontal-swipe', event);
break;
case 'vertical-swipe':
// FIXME: looks like an error
// @ts-ignore
event.swipeType = 'vertical-swipe';
this.runHandler('vertical-swipe', event);
break;
}
// Linear swipe
if (this.detectLinearSwipe(event) || this.eventType === 'horizontal-swipe' || this.eventType === 'vertical-swipe') {
this.handleLinearSwipe(event);
}
};
/* Mouseup */
private handleMouseup = (event: MouseEvent): void => {
// Tap
this.detectTap();
this.isMousedown = false;
this.runHandler('mouseup', event);
this.eventType = undefined;
this.i = 0;
};
/* Wheel */
private handleWheel = (event: WheelEvent): void => {
this.runHandler('wheel', event);
};
/* Resize */
private handleResize = (event: Event): void => {
this.runHandler('resize', event);
};
private runHandler(eventName: EventType, event: unknown): void {
if (eventName && (this.handlers as any)[eventName]) {
const handler = (this.handlers as any)[eventName];
if (handler) {
handler(event as Event);
}
}
}
/*
* Detection
*/
private detectPan(touches: TouchList): boolean {
return (touches.length === 1 && !this.eventType) || this.eventType === 'pan';
}
private detectDoubleTap(): boolean {
if (this.eventType != undefined) {
return false;
}
const currentTime = new Date().getTime();
const tapLength = currentTime - this.lastTap;
window.clearTimeout(this.doubleTapTimeout);
if (tapLength < this.doubleTapMinTimeout && tapLength > 0) {
return true;
} else {
this.doubleTapTimeout = window.setTimeout(() => {
window.clearTimeout(this.doubleTapTimeout);
}, this.doubleTapMinTimeout);
}
this.lastTap = currentTime;
return false;
}
private detectTap(): void {
if (this.eventType != undefined) {
return;
}
const currentTime = new Date().getTime();
const tapLength = currentTime - this.touchstartTime;
if (tapLength > 0) {
if (tapLength < this.tapMinTimeout) {
this.runHandler('tap', {});
} else {
this.runHandler('longtap', {});
}
}
}
private detectPinch(event: TouchEvent): boolean {
const touches = event.touches;
return (touches.length === 2 && this.eventType === undefined) || this.eventType === 'pinch';
}
private detectLinearSwipe(event: MouseEvent | TouchEvent): 'vertical-swipe' | 'horizontal-swipe' | undefined {
const touches = (event as TouchEvent).touches;
if (touches) {
if ((touches.length === 1 && !this.eventType) || this.eventType === 'horizontal-swipe' || this.eventType === 'vertical-swipe') {
return this.getLinearSwipeType(event);
}
} else {
if (!this.eventType || this.eventType === 'horizontal-swipe' || this.eventType === 'vertical-swipe') {
return this.getLinearSwipeType(event);
}
}
return undefined;
}
private getLinearSwipeType(event: TouchEvent | MouseEvent): 'vertical-swipe' | 'horizontal-swipe' {
if (this.eventType !== 'horizontal-swipe' && this.eventType !== 'vertical-swipe') {
const movementX = Math.abs(this.moveLeft(0, event) - this.startX);
const movementY = Math.abs(this.moveTop(0, event) - this.startY);
if (movementY * 3 > movementX) {
return 'vertical-swipe';
} else {
return 'horizontal-swipe';
}
} else {
return this.eventType;
}
}
private getElementPosition(): DOMRect {
return this.element.getBoundingClientRect();
}
private getTouchstartPosition(event: TouchEvent): void {
this.startX = event.touches[0].clientX - this.elementPosition.left;
this.startY = event.touches[0].clientY - this.elementPosition.top;
}
private getMousedownPosition(event: MouseEvent): void {
this.startX = event.clientX - this.elementPosition.left;
this.startY = event.clientY - this.elementPosition.top;
}
private moveLeft(index: number, event: TouchEvent | MouseEvent): number {
const touches = (event as TouchEvent).touches;
if (touches) {
return touches[index].clientX - this.elementPosition.left;
} else {
return (event as MouseEvent).clientX - this.elementPosition.left;
}
}
private moveTop(index: number, event: TouchEvent | MouseEvent): number {
const touches = (event as TouchEvent).touches;
if (touches) {
return touches[index].clientY - this.elementPosition.top;
} else {
return (event as MouseEvent).clientY - this.elementPosition.top;
}
}
private detectTouchScreen(): boolean {
const prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');
const mq = (query: string): boolean => {
return window.matchMedia(query).matches;
};
if ('ontouchstart' in window) {
return true;
}
// include the 'heartz' as a way to have a non matching MQ to help terminate the join
// https://git.io/vznFH
const query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join('');
return mq(query);
}
/* Public properties and methods */
public on(event: EventType, handler: (event: Event) => void): void {
if (event) {
(this.handlers as any)[event] = handler;
}
}
}

View file

@ -1,457 +1,9 @@
export interface Properties {
element: HTMLElement;
listeners?: 'auto' | 'mouse and touch';
touchListeners?: TouchListeners;
mouseListeners?: MouseListeners;
otherListeners?: OtherListeners;
resize?: boolean;
}
export type EventType =
| undefined
| 'touchstart'
| 'touchend'
| 'touchmove'
| 'mousedown'
| 'mouseup'
| 'mousemove'
| 'pan'
| 'pinch'
| 'horizontal-swipe'
| 'vertical-swipe'
| 'tap'
| 'longtap'
| 'wheel'
| 'double-tap'
| 'resize';
export type TouchHandler = 'handleTouchstart' | 'handleTouchmove' | 'handleTouchend';
export type MouseHandler = 'handleMousedown' | 'handleMousemove' | 'handleMouseup' | 'handleWheel';
export type OtherHandler = 'handleResize';
export type TouchListeners = Partial<Record<'touchstart' | 'touchmove' | 'touchend', TouchHandler>>;
export type MouseListeners = Partial<Record<'mousedown' | 'mousemove' | 'mouseup' | 'wheel', MouseHandler>>;
export type OtherListeners = Partial<Record<'resize', OtherHandler>>;
export class Touches {
private properties: Properties;
private element: HTMLElement;
private elementPosition: DOMRect;
private eventType: EventType = undefined;
private handlers: TouchListeners | MouseListeners | OtherListeners = {};
private startX = 0;
private startY = 0;
private lastTap = 0;
private doubleTapTimeout: number = 0;
private doubleTapMinTimeout = 300;
private tapMinTimeout = 200;
private touchstartTime = 0;
private i: number = 0;
private isMousedown = false;
// Store bound event handlers to prevent memory leaks
private boundHandlers = new Map<string, EventListener>();
private _touchListeners: Record<'touchstart' | 'touchmove' | 'touchend', TouchHandler> = {
touchstart: 'handleTouchstart',
touchmove: 'handleTouchmove',
touchend: 'handleTouchend',
};
private _mouseListeners: Record<'mousedown' | 'mousemove' | 'mouseup' | 'wheel', MouseHandler> = {
mousedown: 'handleMousedown',
mousemove: 'handleMousemove',
mouseup: 'handleMouseup',
wheel: 'handleWheel',
};
private _otherListeners: Record<'resize', OtherHandler> = {
resize: 'handleResize',
};
private get touchListeners(): TouchListeners {
return this.properties.touchListeners ? this.properties.touchListeners : this._touchListeners;
}
private get mouseListeners(): MouseListeners {
return this.properties.mouseListeners ? this.properties.mouseListeners : this._mouseListeners;
}
private get otherListeners(): OtherListeners {
return this.properties.otherListeners ? this.properties.otherListeners : this._otherListeners;
}
constructor(properties: Properties) {
this.properties = properties;
this.element = this.properties.element;
this.elementPosition = this.getElementPosition();
this.toggleEventListeners('addEventListener');
}
public destroy(): void {
this.toggleEventListeners('removeEventListener');
}
private toggleEventListeners(action: 'addEventListener' | 'removeEventListener'): void {
let listeners: TouchListeners | MouseListeners | OtherListeners;
if (this.properties.listeners === 'mouse and touch') {
listeners = Object.assign(this.touchListeners, this.mouseListeners);
} else {
listeners = this.detectTouchScreen() ? this.touchListeners : this.mouseListeners;
}
if (this.properties.resize) {
listeners = Object.assign(listeners, this.otherListeners);
}
for (const listener in listeners) {
const handler = listeners[listener as keyof typeof listeners];
if (!handler) continue;
const method = this[handler as keyof this];
if (typeof method !== 'function') continue;
let boundMethod: EventListener;
if (action === 'addEventListener') {
// Create and store the bound method
boundMethod = method.bind(this) as EventListener;
this.boundHandlers.set(listener, boundMethod);
} else {
// Retrieve the stored bound method
const storedHandler = this.boundHandlers.get(listener);
if (!storedHandler) continue; // Skip if no handler was stored
boundMethod = storedHandler;
}
// Determine target element (Window, Document, or this.element)
const target = listener === 'resize' ? window : listener === 'mouseup' || listener === 'mousemove' ? document : this.element;
// Add or remove the listener
if (action === 'addEventListener') {
target.addEventListener(listener, boundMethod, false);
} else {
target.removeEventListener(listener, boundMethod, false);
this.boundHandlers.delete(listener); // Clean up
}
}
}
public addEventListeners(listener: string): void {
const handler = this._mouseListeners[listener as keyof typeof this._mouseListeners];
if (!handler) return;
const method = this[handler as keyof this];
if (typeof method === 'function') {
// Create a unique key for dynamically added listeners
const key = `dynamic-${listener}`;
const boundMethod = method.bind(this) as EventListener;
this.boundHandlers.set(key, boundMethod);
window.addEventListener(listener, boundMethod, false);
}
}
public removeEventListeners(listener: string): void {
// Try to retrieve the dynamically added listener
const key = `dynamic-${listener}`;
const boundMethod = this.boundHandlers.get(key);
if (boundMethod) {
window.removeEventListener(listener, boundMethod, false);
this.boundHandlers.delete(key);
}
}
/*
* Listeners
*/
/* Touchstart */
private handleTouchstart = (event: TouchEvent): void => {
this.elementPosition = this.getElementPosition();
this.touchstartTime = new Date().getTime();
if (this.eventType === undefined) {
this.getTouchstartPosition(event);
}
this.runHandler('touchstart', event);
};
/* Touchmove */
private handleTouchmove = (event: TouchEvent): void => {
const touches = event.touches;
// Pan
if (this.detectPan(touches)) {
this.runHandler('pan', event);
}
// Pinch
if (this.detectPinch(event)) {
this.runHandler('pinch', event);
}
};
private handleLinearSwipe(event: any): void {
//event.preventDefault();
this.i++;
if (this.i > 3) {
this.eventType = this.getLinearSwipeType(event);
}
if (this.eventType === 'horizontal-swipe') {
this.runHandler('horizontal-swipe', event);
}
if (this.eventType === 'vertical-swipe') {
this.runHandler('vertical-swipe', event);
}
}
/* Touchend */
private handleTouchend = (event: TouchEvent): void => {
const touches = event.touches;
// Double Tap
if (this.detectDoubleTap()) {
this.runHandler('double-tap', event);
}
// Tap
this.detectTap();
this.runHandler('touchend', event);
this.eventType = 'touchend';
if (touches && touches.length === 0) {
this.eventType = undefined;
this.i = 0;
}
};
/* Mousedown */
private handleMousedown = (event: MouseEvent): void => {
this.isMousedown = true;
this.elementPosition = this.getElementPosition();
this.touchstartTime = new Date().getTime();
if (this.eventType === undefined) {
this.getMousedownPosition(event);
}
this.runHandler('mousedown', event);
};
/* Mousemove */
private handleMousemove = (event: MouseEvent): void => {
//event.preventDefault();
if (!this.isMousedown) {
return;
}
// Pan
this.runHandler('pan', event);
// Linear swipe
switch (this.detectLinearSwipe(event)) {
case 'horizontal-swipe':
// FIXME: looks like an error
// @ts-ignore
event.swipeType = 'horizontal-swipe';
this.runHandler('horizontal-swipe', event);
break;
case 'vertical-swipe':
// FIXME: looks like an error
// @ts-ignore
event.swipeType = 'vertical-swipe';
this.runHandler('vertical-swipe', event);
break;
}
// Linear swipe
if (this.detectLinearSwipe(event) || this.eventType === 'horizontal-swipe' || this.eventType === 'vertical-swipe') {
this.handleLinearSwipe(event);
}
};
/* Mouseup */
private handleMouseup = (event: MouseEvent): void => {
// Tap
this.detectTap();
this.isMousedown = false;
this.runHandler('mouseup', event);
this.eventType = undefined;
this.i = 0;
};
/* Wheel */
private handleWheel = (event: WheelEvent): void => {
this.runHandler('wheel', event);
};
/* Resize */
private handleResize = (event: Event): void => {
this.runHandler('resize', event);
};
private runHandler(eventName: EventType, event: unknown): void {
if (eventName && (this.handlers as any)[eventName]) {
const handler = (this.handlers as any)[eventName];
if (handler) {
handler(event as Event);
}
}
}
/*
* Detection
*/
private detectPan(touches: TouchList): boolean {
return (touches.length === 1 && !this.eventType) || this.eventType === 'pan';
}
private detectDoubleTap(): boolean {
if (this.eventType != undefined) {
return false;
}
const currentTime = new Date().getTime();
const tapLength = currentTime - this.lastTap;
window.clearTimeout(this.doubleTapTimeout);
if (tapLength < this.doubleTapMinTimeout && tapLength > 0) {
return true;
} else {
this.doubleTapTimeout = window.setTimeout(() => {
window.clearTimeout(this.doubleTapTimeout);
}, this.doubleTapMinTimeout);
}
this.lastTap = currentTime;
return false;
}
private detectTap(): void {
if (this.eventType != undefined) {
return;
}
const currentTime = new Date().getTime();
const tapLength = currentTime - this.touchstartTime;
if (tapLength > 0) {
if (tapLength < this.tapMinTimeout) {
this.runHandler('tap', {});
} else {
this.runHandler('longtap', {});
}
}
}
private detectPinch(event: TouchEvent): boolean {
const touches = event.touches;
return (touches.length === 2 && this.eventType === undefined) || this.eventType === 'pinch';
}
private detectLinearSwipe(event: MouseEvent | TouchEvent): 'vertical-swipe' | 'horizontal-swipe' | undefined {
const touches = (event as TouchEvent).touches;
if (touches) {
if ((touches.length === 1 && !this.eventType) || this.eventType === 'horizontal-swipe' || this.eventType === 'vertical-swipe') {
return this.getLinearSwipeType(event);
}
} else {
if (!this.eventType || this.eventType === 'horizontal-swipe' || this.eventType === 'vertical-swipe') {
return this.getLinearSwipeType(event);
}
}
return undefined;
}
private getLinearSwipeType(event: TouchEvent | MouseEvent): 'vertical-swipe' | 'horizontal-swipe' {
if (this.eventType !== 'horizontal-swipe' && this.eventType !== 'vertical-swipe') {
const movementX = Math.abs(this.moveLeft(0, event) - this.startX);
const movementY = Math.abs(this.moveTop(0, event) - this.startY);
if (movementY * 3 > movementX) {
return 'vertical-swipe';
} else {
return 'horizontal-swipe';
}
} else {
return this.eventType;
}
}
private getElementPosition(): DOMRect {
return this.element.getBoundingClientRect();
}
private getTouchstartPosition(event: TouchEvent): void {
this.startX = event.touches[0].clientX - this.elementPosition.left;
this.startY = event.touches[0].clientY - this.elementPosition.top;
}
private getMousedownPosition(event: MouseEvent): void {
this.startX = event.clientX - this.elementPosition.left;
this.startY = event.clientY - this.elementPosition.top;
}
private moveLeft(index: number, event: TouchEvent | MouseEvent): number {
const touches = (event as TouchEvent).touches;
if (touches) {
return touches[index].clientX - this.elementPosition.left;
} else {
return (event as MouseEvent).clientX - this.elementPosition.left;
}
}
private moveTop(index: number, event: TouchEvent | MouseEvent): number {
const touches = (event as TouchEvent).touches;
if (touches) {
return touches[index].clientY - this.elementPosition.top;
} else {
return (event as MouseEvent).clientY - this.elementPosition.top;
}
}
private detectTouchScreen(): boolean {
const prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');
const mq = (query: string): boolean => {
return window.matchMedia(query).matches;
};
if ('ontouchstart' in window) {
return true;
}
// include the 'heartz' as a way to have a non matching MQ to help terminate the join
// https://git.io/vznFH
const query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join('');
return mq(query);
}
/* Public properties and methods */
public on(event: EventType, handler: (event: Event) => void): void {
if (event) {
(this.handlers as any)[event] = handler;
}
}
}
/**
* @deprecated This file has been moved to services/touches.service.ts
* This re-export maintains backward compatibility.
* Please update your imports to use:
* import { Touches, EventType } from '@meddv/ngx-pinch-zoom';
* or
* import { Touches, EventType } from 'ngx-pinch-zoom/lib/services';
*/
export * from './services/touches.service';

View file

@ -2,15 +2,11 @@
* Public API Surface of ngx-pinch-zoom
*/
// Legacy interfaces (for backward compatibility)
export * from './lib/interfaces';
// Models
// Models (includes legacy interfaces for backward compatibility)
export * from './lib/models';
// Services
export * from './lib/services';
// Components
export * from './lib/pinch-zoom.component';
export * from './lib/components';