style: Apply prettier formatting

Apply consistent code formatting with prettier to all project files
This commit is contained in:
Claude 2025-11-15 16:14:34 +00:00
parent 5194dde20b
commit c0546dbf46
No known key found for this signature in database
15 changed files with 421 additions and 440 deletions

View file

@ -35,15 +35,15 @@ This release modernizes the library with Angular 20 and signals API.
- **Karma/Jasmine**: Removed old testing infrastructure
- **ESLint**: Removed eslint and unnecessary linting dependencies
- **Polyfills**: Removed polyfills.ts (not needed in modern browsers)
- **Test Files**: Removed *.spec.ts files (to be reimplemented with modern testing tools)
- **Test Files**: Removed \*.spec.ts files (to be reimplemented with modern testing tools)
- **Backward Compatibility Inputs**: Removed deprecated kebab-case input bindings
### 🔧 Technical Changes
- Updated `tsconfig.json` with modern settings:
- `moduleResolution: "bundler"`
- `strict: true`
- `strictTemplates: true`
- `moduleResolution: "bundler"`
- `strict: true`
- `strictTemplates: true`
- Simplified `angular.json` configuration
- Removed deprecated tslint configuration
- Updated package scripts for simpler workflow
@ -52,6 +52,7 @@ This release modernizes the library with Angular 20 and signals API.
### 📦 Dependencies
#### Updated
- `@angular/*`: 19.0.0 → 20.0.0
- `typescript`: 5.5.4 → 5.8.0
- `rxjs`: 7.5.2 → 7.8.0
@ -61,6 +62,7 @@ This release modernizes the library with Angular 20 and signals API.
- `prettier`: 2.7.1 → 3.0.0
#### Removed
- `cypress`
- `@cypress/schematic`
- `karma` and related packages
@ -91,13 +93,15 @@ This release modernizes the library with Angular 20 and signals API.
2. **Minimum TypeScript Version**: Now requires TypeScript 5.8.0+
3. **Minimum Node Version**: Now requires Node.js 18.19.1+
4. **Component Properties**: Computed properties must be called as functions:
```typescript
// Before
component.scale
// After
component.scale()
```
```typescript
// Before
component.scale;
// After
component.scale();
```
5. **Removed Exports**: Some internal utilities may no longer be exported
### 🔄 Migration Guide
@ -105,34 +109,38 @@ This release modernizes the library with Angular 20 and signals API.
For users upgrading from previous versions:
1. Update Angular to 20+:
```bash
ng update @angular/core@20 @angular/cli@20
```
```bash
ng update @angular/core@20 @angular/cli@20
```
2. Update your package.json:
```bash
npm install @meddv/ngx-pinch-zoom@20.0.0
```
```bash
npm install @meddv/ngx-pinch-zoom@20.0.0
```
3. Template changes: None required! Input/output binding syntax remains the same.
4. Component reference changes (if using ViewChild):
```typescript
// Before
@ViewChild('pinchZoom') pinchZoom: PinchZoomComponent;
// After (recommended)
pinchZoom = viewChild<PinchZoomComponent>('pinchZoom');
```
```typescript
// Before
@ViewChild('pinchZoom') pinchZoom: PinchZoomComponent;
// After (recommended)
pinchZoom = viewChild<PinchZoomComponent>('pinchZoom');
```
5. Accessing computed properties:
```typescript
// Before
const scale = this.pinchZoom.scale;
// After
const scale = this.pinchZoom()?.scale();
```
```typescript
// Before
const scale = this.pinchZoom.scale;
// After
const scale = this.pinchZoom()?.scale();
```
### 🙏 Credits

View file

@ -40,20 +40,20 @@ ngx-pinch-zoom/
### Core Components
1. **PinchZoomComponent** (`pinch-zoom.component.ts`)
- Main Angular component
- Uses signals for all inputs and outputs
- Implements modern Angular patterns (inject, computed, effect)
- Standalone component
- Main Angular component
- Uses signals for all inputs and outputs
- Implements modern Angular patterns (inject, computed, effect)
- Standalone component
2. **IvyPinch** (`ivypinch.ts`)
- Core zoom/pan logic
- Handles transformations and calculations
- No Angular dependencies (plain TypeScript class)
- Core zoom/pan logic
- Handles transformations and calculations
- No Angular dependencies (plain TypeScript class)
3. **Touches** (`touches.ts`)
- Event handling for touch and mouse interactions
- Gesture detection (pinch, pan, tap, double-tap)
- Cross-browser compatibility
- Event handling for touch and mouse interactions
- Gesture detection (pinch, pan, tap, double-tap)
- Cross-browser compatibility
## Development Setup
@ -94,6 +94,7 @@ npm run build-lib
```
Output is generated in `dist/ngx-pinch-zoom/` with:
- ESM bundles
- TypeScript declaration files
- package.json for npm publishing
@ -121,7 +122,7 @@ disabled = input<boolean>(false);
zoomChanged = output<number>();
scale = computed<number>(() => {
return this.currentScale();
return this.currentScale();
});
```
@ -147,30 +148,30 @@ The project uses strict TypeScript settings:
```json
{
"strict": true,
"strictNullChecks": true,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
"strict": true,
"strictNullChecks": true,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
```
### Adding New Features
1. **Update the component** (`pinch-zoom.component.ts`):
- Add input signals for configuration
- Add computed signals for derived state
- Update the component template if needed
- Add input signals for configuration
- Add computed signals for derived state
- Update the component template if needed
2. **Update core logic** (`ivypinch.ts`):
- Add properties to the `Properties` interface
- Implement the functionality
- Test calculations thoroughly
- Add properties to the `Properties` interface
- Implement the functionality
- Test calculations thoroughly
3. **Document the feature**:
- Update README.md with the new property
- Add usage examples
- Document default values
- Update README.md with the new property
- Add usage examples
- Document default values
### Testing Changes
@ -205,16 +206,16 @@ npm update
### Debugging
1. **Component Issues**:
- Check signal reactivity with Angular DevTools
- Use `effect(() => console.log(signalValue()))`
- Check signal reactivity with Angular DevTools
- Use `effect(() => console.log(signalValue()))`
2. **Touch/Mouse Issues**:
- Add console.logs in `touches.ts` event handlers
- Check browser dev tools for event listeners
- Add console.logs in `touches.ts` event handlers
- Check browser dev tools for event listeners
3. **Transform Issues**:
- Inspect `ivypinch.ts` transformElement method
- Check CSS transform values in browser inspector
- Inspect `ivypinch.ts` transformElement method
- Check CSS transform values in browser inspector
## Browser Compatibility
@ -237,12 +238,14 @@ The library supports all modern browsers:
### Build Failures
**TypeScript errors**: Ensure strict mode compliance
```bash
# Check TypeScript version
npx tsc --version # Should be ~5.8.0
```
**Dependency conflicts**: Clear cache and reinstall
```bash
rm -rf node_modules package-lock.json
npm install

View file

@ -1,101 +1,101 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"cli": {
"analytics": false
},
"projects": {
"ivypinchApp": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "sass"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/ivypinchApp",
"index": "src/index.html",
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.sass"],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kB",
"maximumError": "10kB"
}
]
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "ivypinchApp:build:production"
},
"development": {
"buildTarget": "ivypinchApp:build:development"
}
},
"defaultConfiguration": "development"
}
}
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"cli": {
"analytics": false
},
"ngx-pinch-zoom": {
"projectType": "library",
"root": "projects/ngx-pinch-zoom",
"sourceRoot": "projects/ngx-pinch-zoom/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "projects/ngx-pinch-zoom/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "projects/ngx-pinch-zoom/tsconfig.lib.prod.json"
"projects": {
"ivypinchApp": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "sass"
}
},
"development": {
"tsConfig": "projects/ngx-pinch-zoom/tsconfig.lib.json"
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/ivypinchApp",
"index": "src/index.html",
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.sass"],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kB",
"maximumError": "10kB"
}
]
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "ivypinchApp:build:production"
},
"development": {
"buildTarget": "ivypinchApp:build:development"
}
},
"defaultConfiguration": "development"
}
}
},
"ngx-pinch-zoom": {
"projectType": "library",
"root": "projects/ngx-pinch-zoom",
"sourceRoot": "projects/ngx-pinch-zoom/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "projects/ngx-pinch-zoom/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "projects/ngx-pinch-zoom/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "projects/ngx-pinch-zoom/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
}
}
},
"defaultConfiguration": "production"
}
}
}
}
}

View file

@ -77,42 +77,44 @@ graph TB
### Design Principles
1. **Separation of Concerns**
- **PinchZoomComponent**: Angular integration, signals, lifecycle
- **IvyPinch**: Pure business logic, no Angular dependencies
- **Touches**: Pure event handling, no Angular dependencies
- **PinchZoomComponent**: Angular integration, signals, lifecycle
- **IvyPinch**: Pure business logic, no Angular dependencies
- **Touches**: Pure event handling, no Angular dependencies
2. **Signal-Based Reactivity**
- All inputs use Angular 20+ input signals
- Computed signals for derived state
- Output signals for events
- Effects for side effects
- All inputs use Angular 20+ input signals
- Computed signals for derived state
- Output signals for events
- Effects for side effects
3. **Framework Independence**
- IvyPinch and Touches can work without Angular
- Could be ported to React, Vue, or vanilla JS
- DOM manipulation is isolated
- IvyPinch and Touches can work without Angular
- Could be ported to React, Vue, or vanilla JS
- DOM manipulation is isolated
4. **Performance First**
- CSS transforms for hardware acceleration
- `translate3d` forces GPU rendering
- No layout thrashing (read/write separation)
- Minimal change detection triggers
- CSS transforms for hardware acceleration
- `translate3d` forces GPU rendering
- No layout thrashing (read/write separation)
- Minimal change detection triggers
## Component Architecture
### PinchZoomComponent (Angular Layer)
**Responsibilities:**
- Provide Angular component interface
- Manage input/output signals
- Initialize and cleanup IvyPinch
- Bridge between Angular and core logic
**Key Features:**
```typescript
@Component({
selector: 'pinch-zoom, [pinch-zoom]',
standalone: true, // No NgModule needed
standalone: true, // No NgModule needed
// ...
})
export class PinchZoomComponent {
@ -143,6 +145,7 @@ export class PinchZoomComponent {
### IvyPinch (Core Logic Layer)
**Responsibilities:**
- Calculate zoom scale from pinch gestures
- Calculate pan position from swipe gestures
- Apply constraints (min/max zoom, pan limits)
@ -150,21 +153,22 @@ export class PinchZoomComponent {
- Notify component of changes
**Key State:**
```typescript
class IvyPinch {
// Zoom state
public scale: number = 1; // Current scale (1 = 100%)
private initialScale: number = 1; // Scale at gesture start
private maxScale: number = 3; // Maximum allowed scale
public scale: number = 1; // Current scale (1 = 100%)
private initialScale: number = 1; // Scale at gesture start
private maxScale: number = 3; // Maximum allowed scale
// Position state
public moveX: number = 0; // Horizontal offset (px)
public moveY: number = 0; // Vertical offset (px)
private initialMoveX: number = 0; // Position at gesture start
public moveX: number = 0; // Horizontal offset (px)
public moveY: number = 0; // Vertical offset (px)
private initialMoveX: number = 0; // Position at gesture start
private initialMoveY: number = 0;
// Gesture state
private distance: number = 0; // Distance between fingers
private distance: number = 0; // Distance between fingers
private initialDistance: number = 0; // Distance at pinch start
}
```
@ -172,12 +176,14 @@ class IvyPinch {
### Touches (Event Handling Layer)
**Responsibilities:**
- Listen to touch/mouse events
- Detect gesture types (pinch, pan, double-tap)
- Calculate touch positions and distances
- Emit typed events to IvyPinch
**Event Flow:**
```typescript
class Touches {
// Event type state machine
@ -190,14 +196,14 @@ class Touches {
private handlers = {
pinch: [],
pan: [],
tap: []
tap: [],
};
// Core detection loop
detectGesture() {
this.detectDoubleTap() || // Check tap first
this.detectPinch() || // Then pinch
this.detectLinearSwipe(); // Finally pan
this.detectDoubleTap() || // Check tap first
this.detectPinch() || // Then pinch
this.detectLinearSwipe(); // Finally pan
}
}
```
@ -281,6 +287,7 @@ flowchart TD
### Why Signals?
Angular 20's signals provide:
1. **Fine-grained reactivity** - Only affected components update
2. **Automatic dependency tracking** - Computed signals track dependencies
3. **Better performance** - Less change detection overhead
@ -393,6 +400,7 @@ mergedProperties = computed<ComponentProperties>(() => {
```
This ensures:
- Individual inputs take priority
- properties object can set multiple at once
- Defaults are always present
@ -609,18 +617,18 @@ emit(eventName: string, event: any) {
```typescript
type TouchEvent =
| 'touchstart' // Finger touches screen
| 'touchmove' // Finger moves
| 'touchend' // Finger lifts
| 'mousedown' // Mouse button pressed
| 'mousemove' // Mouse moves
| 'mouseup' // Mouse button released
| 'wheel'; // Mouse wheel scrolled
| 'touchstart' // Finger touches screen
| 'touchmove' // Finger moves
| 'touchend' // Finger lifts
| 'mousedown' // Mouse button pressed
| 'mousemove' // Mouse moves
| 'mouseup' // Mouse button released
| 'wheel'; // Mouse wheel scrolled
type GestureEvent =
| 'pinch' // Two-finger zoom
| 'pan' // One-finger drag
| 'tap'; // Double-tap
| 'pinch' // Two-finger zoom
| 'pan' // One-finger drag
| 'tap'; // Double-tap
```
## Transform Mathematics
@ -668,6 +676,7 @@ const newMoveY = this.moveY - (centerY - this.moveY) * (newScale / this.scale -
#### Mathematical Derivation
Let:
- `P` = point to zoom at (in screen coordinates)
- `S₁` = old scale
- `S₂` = new scale
@ -677,7 +686,7 @@ Let:
We want: Point P maps to same position before and after zoom.
Before zoom: `P_image = (P - T₁) / S₁`
After zoom: `P_image = (P - T₂) / S₂`
After zoom: `P_image = (P - T₂) / S₂`
Setting equal:
`(P - T₁) / S₁ = (P - T₂) / S₂`
@ -699,10 +708,12 @@ transform: translate(100px, 50px) scale(2);
```
Is equivalent to:
1. Scale by 2
2. Then translate by (100px, 50px)
**Our transform:**
```css
transform: translate3d(moveX, moveY, 0) scale(scale);
```
@ -721,6 +732,7 @@ transform: translate3d(100px, 50px, 0) scale(2);
```
Becomes:
```
| 2 0 0 100 |
| 0 2 0 50 |

View file

@ -20,15 +20,17 @@ Let's add the ability to rotate images with a two-finger twist gesture.
#### Step 1: Add Properties
**File: `interfaces.ts`**
```typescript
export interface Properties {
// ... existing properties
enableRotation?: boolean;
rotationLockThreshold?: number; // Degrees before rotation activates
rotationLockThreshold?: number; // Degrees before rotation activates
}
```
**File: `properties.ts`**
```typescript
export const defaultProperties: Properties = {
// ... existing defaults
@ -40,6 +42,7 @@ export const defaultProperties: Properties = {
#### Step 2: Add Input Signal
**File: `pinch-zoom.component.ts`**
```typescript
export class PinchZoomComponent implements OnInit, OnDestroy {
// ... existing inputs
@ -60,14 +63,15 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
#### Step 3: Add State to IvyPinch
**File: `ivypinch.ts`**
```typescript
export class IvyPinch {
// ... existing properties
// Rotation state
public rotation: number = 0; // Current rotation in degrees
private initialRotation: number = 0; // Rotation at gesture start
private rotationAngle: number = 0; // Cumulative rotation
public rotation: number = 0; // Current rotation in degrees
private initialRotation: number = 0; // Rotation at gesture start
private rotationAngle: number = 0; // Cumulative rotation
// ... existing methods
@ -86,10 +90,7 @@ export class IvyPinch {
const touch2 = touches[1];
// Calculate angle between fingers
const angle = Math.atan2(
touch2.clientY - touch1.clientY,
touch2.clientX - touch1.clientX
) * (180 / Math.PI);
const angle = Math.atan2(touch2.clientY - touch1.clientY, touch2.clientX - touch1.clientX) * (180 / Math.PI);
// Initialize on first move
if (this.rotationAngle === 0) {
@ -114,20 +115,10 @@ export class IvyPinch {
this.rotation = this.initialRotation + delta;
// Apply transform with rotation
this.transformElementWithRotation(
this.scale,
this.moveX,
this.moveY,
this.rotation
);
this.transformElementWithRotation(this.scale, this.moveX, this.moveY, this.rotation);
};
private transformElementWithRotation(
scale: number,
moveX: number,
moveY: number,
rotation: number
): void {
private transformElementWithRotation(scale: number, moveX: number, moveY: number, rotation: number): void {
const transform = `
translate3d(${moveX}px, ${moveY}px, 0)
scale(${scale})
@ -146,6 +137,7 @@ export class IvyPinch {
#### Step 4: Register Event Handler
**File: `ivypinch.ts` (in constructor)**
```typescript
constructor(properties: Properties, private zoomChanged: (scale: number) => void) {
// ... existing initialization
@ -166,6 +158,7 @@ constructor(properties: Properties, private zoomChanged: (scale: number) => void
#### Step 5: Add Output Event (Optional)
**File: `pinch-zoom.component.ts`**
```typescript
export class PinchZoomComponent implements OnInit, OnDestroy {
// ... existing outputs
@ -186,6 +179,7 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
#### Step 6: Update IvyPinch Callback
**File: `ivypinch.ts`**
```typescript
private zoomChanged: (scale: number, rotation?: number) => void;
@ -200,10 +194,7 @@ private handleRotation = (event: any): void => {
#### Step 7: Test
```html
<pinch-zoom
[enableRotation]="true"
[rotationLockThreshold]="15"
(rotationChanged)="onRotationChange($event)">
<pinch-zoom [enableRotation]="true" [rotationLockThreshold]="15" (rotationChanged)="onRotationChange($event)">
<img src="image.jpg" />
</pinch-zoom>
```
@ -215,20 +206,22 @@ Add custom easing functions for smoother zoom animations.
#### Step 1: Define Easing Functions
**File: `ivypinch.ts`**
```typescript
type EasingFunction = 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';
const easingFunctions = {
'linear': (t: number) => t,
linear: (t: number) => t,
'ease-in': (t: number) => t * t,
'ease-out': (t: number) => t * (2 - t),
'ease-in-out': (t: number) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
'ease-in-out': (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
};
```
#### Step 2: Add Property
**File: `interfaces.ts`**
```typescript
export interface Properties {
// ... existing
@ -239,6 +232,7 @@ export interface Properties {
#### Step 3: Implement Animated Zoom
**File: `ivypinch.ts`**
```typescript
private animateZoom(
targetScale: number,
@ -300,6 +294,7 @@ handleTouchStart(event: TouchEvent) {
#### Step 2: Fix Initialization
**File: `ivypinch.ts`**
```typescript
private handleLinearSwipe = (event: any): void => {
// Store initial position on first move
@ -345,6 +340,7 @@ private resetSwipeState(): void {
#### Fix 1: Adjust Timing Window
**File: `touches.ts`**
```typescript
detectDoubleTap(): boolean {
const now = Date.now();
@ -425,6 +421,7 @@ detectDoubleTap(): boolean {
#### Step 1: Verify Listeners Are Removed
**File: `touches.ts`**
```typescript
destroy(): void {
console.log('[Touches] Removing listeners');
@ -452,6 +449,7 @@ destroy(): void {
#### Step 2: Ensure destroy() is Called
**File: `pinch-zoom.component.ts`**
```typescript
ngOnDestroy(): void {
console.log('[PinchZoom] Component destroying');
@ -482,6 +480,7 @@ ngOnDestroy(): void {
Zoom to a specific coordinate programmatically.
**File: `ivypinch.ts`**
```typescript
public zoomToPoint(
targetScale: number,
@ -555,6 +554,7 @@ private animateZoomToPoint(
```
**Usage:**
```typescript
// In component
@ViewChild('pinchZoom') pinchZoomRef!: PinchZoomComponent;
@ -570,6 +570,7 @@ zoomToTopLeft() {
Add a visual indicator of current zoom level.
**File: `pinch-zoom.component.ts`**
```typescript
export class PinchZoomComponent implements OnInit, OnDestroy {
// ... existing code
@ -590,23 +591,25 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
```
**File: `pinch-zoom.component.html`**
```html
<div class="pinch-zoom-container">
<ng-content></ng-content>
<!-- Zoom indicator -->
@if (isZoomedIn()) {
<div class="zoom-indicator">
<div class="zoom-label">{{ zoomPercentage() }}%</div>
<div class="zoom-bar">
<div class="zoom-bar-fill" [style.width]="zoomBarWidth()"></div>
</div>
<div class="zoom-indicator">
<div class="zoom-label">{{ zoomPercentage() }}%</div>
<div class="zoom-bar">
<div class="zoom-bar-fill" [style.width]="zoomBarWidth()"></div>
</div>
</div>
}
</div>
```
**File: `pinch-zoom.component.sass`**
```sass
.zoom-indicator
position: absolute
@ -643,6 +646,7 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
Reduce the number of transform updates during panning.
**File: `ivypinch.ts`**
```typescript
private lastPanTime: number = 0;
private readonly PAN_THROTTLE_MS = 16; // ~60fps
@ -664,6 +668,7 @@ private handleLinearSwipe = (event: any): void => {
Batch transform updates to animation frames.
**File: `ivypinch.ts`**
```typescript
private rafId: number | null = null;
private pendingTransform: {
@ -712,6 +717,7 @@ public destroy(): void {
Avoid expensive calculations on every resize event.
**File: `ivypinch.ts`**
```typescript
private resizeTimeout: number | null = null;
@ -747,6 +753,7 @@ public destroy(): void {
Add visual overlay showing current transform state.
**Create: `transform-debugger.component.ts`**
```typescript
import { Component, input, computed } from '@angular/core';
@ -763,37 +770,39 @@ import { Component, input, computed } from '@angular/core';
<div class="debug-grid"></div>
</div>
`,
styles: [`
.debug-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 9999;
}
styles: [
`
.debug-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 9999;
}
.debug-info {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.8);
color: #0f0;
padding: 10px;
font-family: monospace;
font-size: 12px;
}
.debug-info {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.8);
color: #0f0;
padding: 10px;
font-family: monospace;
font-size: 12px;
}
.debug-grid {
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(0, 255, 0, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 255, 0, 0.1) 1px, transparent 1px);
background-size: 50px 50px;
}
`]
.debug-grid {
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(0, 255, 0, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 255, 0, 0.1) 1px, transparent 1px);
background-size: 50px 50px;
}
`,
],
})
export class TransformDebugger {
scale = input<number>(1);
@ -805,16 +814,13 @@ export class TransformDebugger {
```
**Usage:**
```html
<pinch-zoom #pz>
<img src="image.jpg" />
</pinch-zoom>
<transform-debugger
[scale]="pz.scale()"
[moveX]="pz.moveX()"
[moveY]="pz.moveY()">
</transform-debugger>
<transform-debugger [scale]="pz.scale()" [moveX]="pz.moveX()" [moveY]="pz.moveY()"> </transform-debugger>
```
### Technique 2: Event Flow Logging
@ -822,6 +828,7 @@ export class TransformDebugger {
Add comprehensive logging to trace event flow.
**File: `touches.ts`**
```typescript
private logEvent(stage: string, data: any): void {
if (!this.debug) return;
@ -853,6 +860,7 @@ handleTouchMove(event: TouchEvent): void {
### Unit Testing IvyPinch
**File: `ivypinch.spec.ts`**
```typescript
import { IvyPinch } from './ivypinch';
import { Properties } from './interfaces';
@ -892,8 +900,8 @@ describe('IvyPinch', () => {
const mockEvent = {
touches: [
{ clientX: 0, clientY: 0 },
{ clientX: 500, clientY: 0 }
]
{ clientX: 500, clientY: 0 },
],
};
// This would zoom to 5x, but limit is 3x
@ -914,6 +922,7 @@ describe('IvyPinch', () => {
### Integration Testing
**File: `pinch-zoom.component.spec.ts`**
```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PinchZoomComponent } from './pinch-zoom.component';
@ -924,7 +933,7 @@ describe('PinchZoomComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PinchZoomComponent]
imports: [PinchZoomComponent],
}).compileComponents();
fixture = TestBed.createComponent(PinchZoomComponent);
@ -964,6 +973,7 @@ describe('PinchZoomComponent', () => {
## Summary
This guide covers:
- ✅ Adding new features (rotation, easing)
- ✅ Fixing common bugs (jumps, double-tap, memory leaks)
- ✅ Common customizations (zoom to point, indicators)

View file

@ -7,25 +7,25 @@ Welcome! This documentation will help you understand and maintain the ngx-pinch-
### For New Maintainers - Start Here
1. **[QUICK_REFERENCE.md](QUICK_REFERENCE.md)** ⚡
- Fast lookup for common tasks
- Code locations
- Common patterns
- Debugging checklist
- **Read this first for quick answers!**
- Fast lookup for common tasks
- Code locations
- Common patterns
- Debugging checklist
- **Read this first for quick answers!**
2. **[ARCHITECTURE.md](ARCHITECTURE.md)** 🏗️
- Deep dive into how everything works
- Component interaction diagrams
- Transform mathematics explained
- Event flow documentation
- **Read this to understand the system**
- Deep dive into how everything works
- Component interaction diagrams
- Transform mathematics explained
- Event flow documentation
- **Read this to understand the system**
3. **[IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md)** 🛠️
- Step-by-step implementation examples
- How to add features
- How to fix bugs
- Code customizations
- **Read this when making changes**
- Step-by-step implementation examples
- How to add features
- How to fix bugs
- Code customizations
- **Read this when making changes**
### Documentation Organization
@ -62,50 +62,50 @@ docs/
### "Where do I find...?"
| Question | Answer |
|----------|--------|
| How pinch zoom works? | ARCHITECTURE.md → "Transform Mathematics" |
| How to add an input property? | IMPLEMENTATION_GUIDE.md → "Adding New Features" |
| Why isn't pan working? | QUICK_REFERENCE.md → "Debugging Checklist" |
| What is `transformElement()`? | ARCHITECTURE.md → "Transform Mathematics" |
| How to add rotation? | IMPLEMENTATION_GUIDE.md → "Feature: Add Rotation Support" |
| Performance best practices? | QUICK_REFERENCE.md → "Performance Tips" |
| Event flow diagram? | ARCHITECTURE.md → "Event System" |
| Question | Answer |
| ----------------------------- | --------------------------------------------------------- |
| How pinch zoom works? | ARCHITECTURE.md → "Transform Mathematics" |
| How to add an input property? | IMPLEMENTATION_GUIDE.md → "Adding New Features" |
| Why isn't pan working? | QUICK_REFERENCE.md → "Debugging Checklist" |
| What is `transformElement()`? | ARCHITECTURE.md → "Transform Mathematics" |
| How to add rotation? | IMPLEMENTATION_GUIDE.md → "Feature: Add Rotation Support" |
| Performance best practices? | QUICK_REFERENCE.md → "Performance Tips" |
| Event flow diagram? | ARCHITECTURE.md → "Event System" |
### "How do I...?"
| Task | Document |
|------|----------|
| Fix image jumping | IMPLEMENTATION_GUIDE.md → "Bug: Image Jumps on First Touch" |
| Task | Document |
| ------------------------ | ----------------------------------------------------------------------- |
| Fix image jumping | IMPLEMENTATION_GUIDE.md → "Bug: Image Jumps on First Touch" |
| Add custom zoom behavior | IMPLEMENTATION_GUIDE.md → "Custom: Programmatic Zoom to Specific Point" |
| Debug transforms | IMPLEMENTATION_GUIDE.md → "Technique 1: Visual Transform Debugging" |
| Improve performance | IMPLEMENTATION_GUIDE.md → "Performance Optimization" |
| Add zoom indicator | IMPLEMENTATION_GUIDE.md → "Custom: Zoom Level Indicator" |
| Debug transforms | IMPLEMENTATION_GUIDE.md → "Technique 1: Visual Transform Debugging" |
| Improve performance | IMPLEMENTATION_GUIDE.md → "Performance Optimization" |
| Add zoom indicator | IMPLEMENTATION_GUIDE.md → "Custom: Zoom Level Indicator" |
## 📖 Reading Code
### Source Files (In Order of Complexity)
1. **properties.ts** (Easiest)
- Just default configuration values
- Good starting point
- Just default configuration values
- Good starting point
2. **interfaces.ts** (Easy)
- TypeScript type definitions
- Understand data structures
- TypeScript type definitions
- Understand data structures
3. **pinch-zoom.component.ts** (Medium)
- Angular component with signals
- See how properties flow
- Angular component with signals
- See how properties flow
4. **touches.ts** (Medium-Hard)
- Event detection logic
- Gesture recognition
- Event detection logic
- Gesture recognition
5. **ivypinch.ts** (Hardest)
- Core zoom/pan mathematics
- Transform calculations
- **Most complex file in the library**
- Core zoom/pan mathematics
- Transform calculations
- **Most complex file in the library**
### Recommended Reading Order
@ -157,20 +157,20 @@ flowchart TD
### Key Concepts to Understand
1. **CSS Transform Matrix**
- How we apply zoom and pan using a single CSS property
- See: ARCHITECTURE.md → "Transform Mathematics"
- How we apply zoom and pan using a single CSS property
- See: ARCHITECTURE.md → "Transform Mathematics"
2. **Signal-Based Reactivity**
- How Angular signals provide fine-grained updates
- See: ARCHITECTURE.md → "Signal Architecture"
- How Angular signals provide fine-grained updates
- See: ARCHITECTURE.md → "Signal Architecture"
3. **Gesture State Machine**
- How events transition between states
- See: QUICK_REFERENCE.md → "Gesture State Machine"
- How events transition between states
- See: QUICK_REFERENCE.md → "Gesture State Machine"
4. **Coordinate Systems**
- Element-relative vs page-relative positions
- See: ARCHITECTURE.md → "Transform Mathematics"
- Element-relative vs page-relative positions
- See: ARCHITECTURE.md → "Transform Mathematics"
## 🛠️ Making Changes
@ -205,7 +205,7 @@ console.log('[IvyPinch] handlePinch', {
scale: this.scale,
distance: this.distance,
moveX: this.moveX,
moveY: this.moveY
moveY: this.moveY,
});
```

View file

@ -1003,9 +1003,7 @@ export class IvyPinch {
* @returns Distance in pixels
*/
private getDistance(touches: TouchList): number {
return Math.sqrt(
Math.pow(touches[0].pageX - touches[1].pageX, 2) + Math.pow(touches[0].pageY - touches[1].pageY, 2),
);
return Math.sqrt(Math.pow(touches[0].pageX - touches[1].pageX, 2) + Math.pow(touches[0].pageY - touches[1].pageY, 2));
}
// ========================================================================
@ -1132,11 +1130,9 @@ export class IvyPinch {
// Calculate position to zoom at tap location
this.moveX =
this.initialMoveX -
(changedTouches[0].clientX - this.elementPosition.left) * (this.properties.doubleTapScale - 1);
this.initialMoveX - (changedTouches[0].clientX - this.elementPosition.left) * (this.properties.doubleTapScale - 1);
this.moveY =
this.initialMoveY -
(changedTouches[0].clientY - this.elementPosition.top) * (this.properties.doubleTapScale - 1);
this.initialMoveY - (changedTouches[0].clientY - this.elementPosition.top) * (this.properties.doubleTapScale - 1);
}
// Case 2: Programmatic zoom (zoom at center)
else {
@ -1279,10 +1275,7 @@ export class IvyPinch {
// Check if scaled image exceeds container
if (this.scale > 1) {
return (
imgHeight * this.scale > this.parentElement.offsetHeight ||
imgWidth * this.scale > this.parentElement.offsetWidth
);
return imgHeight * this.scale > this.parentElement.offsetHeight || imgWidth * this.scale > this.parentElement.offsetWidth;
}
// Check if normal image exceeds container

View file

@ -302,7 +302,6 @@ export class IvyPinch {
this.scale = newScale;
this.zoomChanged(this.scale);
/* Get cursor position over image */
const xCenter = event.clientX - this.elementPosition.left - this.initialMoveX;
const yCenter = event.clientY - this.elementPosition.top - this.initialMoveY;
@ -538,9 +537,7 @@ export class IvyPinch {
}
private getDistance(touches: TouchList): number {
return Math.sqrt(
Math.pow(touches[0].pageX - touches[1].pageX, 2) + Math.pow(touches[0].pageY - touches[1].pageY, 2),
);
return Math.sqrt(Math.pow(touches[0].pageX - touches[1].pageX, 2) + Math.pow(touches[0].pageY - touches[1].pageY, 2));
}
private getImageHeight(): number {
@ -556,15 +553,7 @@ export class IvyPinch {
private transformElement(duration: number): void {
this.element.style.transition = 'all ' + duration + 'ms';
this.element.style.transform =
'matrix(' +
Number(this.scale) +
', 0, 0, ' +
Number(this.scale) +
', ' +
Number(this.moveX) +
', ' +
Number(this.moveY) +
')';
'matrix(' + Number(this.scale) + ', 0, 0, ' + Number(this.scale) + ', ' + Number(this.moveX) + ', ' + Number(this.moveY) + ')';
}
private isTouchScreen(): boolean {
@ -593,10 +582,7 @@ export class IvyPinch {
const imgWidth = this.getImageWidth();
if (this.scale > 1) {
return (
imgHeight * this.scale > this.parentElement.offsetHeight ||
imgWidth * this.scale > this.parentElement.offsetWidth
);
return imgHeight * this.scale > this.parentElement.offsetHeight || imgWidth * this.scale > this.parentElement.offsetWidth;
}
if (this.scale === 1) {
return imgHeight > this.parentElement.offsetHeight || imgWidth > this.parentElement.offsetWidth;
@ -660,11 +646,9 @@ export class IvyPinch {
this.scale = this.initialScale * this.properties.doubleTapScale!;
this.zoomChanged(this.scale);
this.moveX =
this.initialMoveX -
(changedTouches[0].clientX - this.elementPosition.left) * (this.properties.doubleTapScale - 1);
this.initialMoveX - (changedTouches[0].clientX - this.elementPosition.left) * (this.properties.doubleTapScale - 1);
this.moveY =
this.initialMoveY -
(changedTouches[0].clientY - this.elementPosition.top) * (this.properties.doubleTapScale - 1);
this.initialMoveY - (changedTouches[0].clientY - this.elementPosition.top) * (this.properties.doubleTapScale - 1);
} else {
const zoomControlScale = this.properties.zoomControlScale || 0;
this.scale = this.initialScale * (zoomControlScale + 1);

View file

@ -1,48 +1,48 @@
<div class="pinch-zoom-content" [class.pz-dragging]="isDragging()">
<ng-content></ng-content>
<ng-content></ng-content>
</div>
<!-- Controls container at bottom center -->
@if (isControl() || isBrightnessControl()) {
<div class="pz-controls-container">
<!-- Zoom control -->
@if (isControl()) {
<div
class="pz-zoom-button"
[class.pz-zoom-button-out]="isZoomedIn()"
(click)="toggleZoom()"
(keydown.enter)="toggleZoom()"
(keydown.space)="toggleZoom(); $event.preventDefault()"
role="button"
[attr.aria-label]="isZoomedIn() ? 'Zoom out' : 'Zoom in'"
tabindex="0"
></div>
}
<div class="pz-controls-container">
<!-- Zoom control -->
@if (isControl()) {
<div
class="pz-zoom-button"
[class.pz-zoom-button-out]="isZoomedIn()"
(click)="toggleZoom()"
(keydown.enter)="toggleZoom()"
(keydown.space)="toggleZoom(); $event.preventDefault()"
role="button"
[attr.aria-label]="isZoomedIn() ? 'Zoom out' : 'Zoom in'"
tabindex="0"
></div>
}
<!-- Brightness controls -->
@if (isBrightnessControl()) {
<div class="pz-brightness-controls">
<div
class="pz-brightness-button pz-brightness-decrease"
[class.pz-brightness-button-disabled]="isBrightnessAtMin()"
(click)="brightnessOut()"
(keydown.enter)="brightnessOut()"
(keydown.space)="brightnessOut(); $event.preventDefault()"
role="button"
aria-label="Decrease brightness"
tabindex="0"
></div>
<div
class="pz-brightness-button pz-brightness-increase"
[class.pz-brightness-button-disabled]="isBrightnessAtMax()"
(click)="brightnessIn()"
(keydown.enter)="brightnessIn()"
(keydown.space)="brightnessIn(); $event.preventDefault()"
role="button"
aria-label="Increase brightness"
tabindex="0"
></div>
</div>
}
</div>
<!-- Brightness controls -->
@if (isBrightnessControl()) {
<div class="pz-brightness-controls">
<div
class="pz-brightness-button pz-brightness-decrease"
[class.pz-brightness-button-disabled]="isBrightnessAtMin()"
(click)="brightnessOut()"
(keydown.enter)="brightnessOut()"
(keydown.space)="brightnessOut(); $event.preventDefault()"
role="button"
aria-label="Decrease brightness"
tabindex="0"
></div>
<div
class="pz-brightness-button pz-brightness-increase"
[class.pz-brightness-button-disabled]="isBrightnessAtMax()"
(click)="brightnessIn()"
(keydown.enter)="brightnessIn()"
(keydown.space)="brightnessIn(); $event.preventDefault()"
role="button"
aria-label="Increase brightness"
tabindex="0"
></div>
</div>
}
</div>
}

View file

@ -1,16 +1,4 @@
import {
Component,
ElementRef,
HostBinding,
OnInit,
OnDestroy,
computed,
input,
output,
signal,
effect,
inject,
} from '@angular/core';
import { Component, ElementRef, HostBinding, OnInit, OnDestroy, computed, input, output, signal, effect, inject } from '@angular/core';
import { Properties } from './interfaces';
import { defaultProperties } from './properties';
@ -269,20 +257,14 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
}
brightnessIn(): number {
const newBrightness = Math.min(
this.brightness() + this.brightnessStep(),
this.maxBrightness()
);
const newBrightness = Math.min(this.brightness() + this.brightnessStep(), this.maxBrightness());
this.currentBrightness.set(newBrightness);
this.brightnessChanged.emit(newBrightness);
return newBrightness;
}
brightnessOut(): number {
const newBrightness = Math.max(
this.brightness() - this.brightnessStep(),
this.minBrightness()
);
const newBrightness = Math.max(this.brightness() - this.brightnessStep(), this.minBrightness());
this.currentBrightness.set(newBrightness);
this.brightnessChanged.emit(newBrightness);
return newBrightness;

View file

@ -24,7 +24,7 @@ export type EventType =
| 'wheel'
| 'double-tap'
| 'resize';
export type TouchHandler ='handleTouchstart' | 'handleTouchmove' | 'handleTouchend';
export type TouchHandler = 'handleTouchstart' | 'handleTouchmove' | 'handleTouchend';
export type MouseHandler = 'handleMousedown' | 'handleMousemove' | 'handleMouseup' | 'handleWheel';
export type OtherHandler = 'handleResize';
@ -124,10 +124,7 @@ export class Touches {
}
// Determine target element (Window, Document, or this.element)
const target =
listener === 'resize' ? window :
(listener === 'mouseup' || listener === 'mousemove') ? document :
this.element;
const target = listener === 'resize' ? window : listener === 'mouseup' || listener === 'mousemove' ? document : this.element;
// Add or remove the listener
if (action === 'addEventListener') {
@ -280,11 +277,7 @@ export class Touches {
}
// Linear swipe
if (
this.detectLinearSwipe(event) ||
this.eventType === 'horizontal-swipe' ||
this.eventType === 'vertical-swipe'
) {
if (this.detectLinearSwipe(event) || this.eventType === 'horizontal-swipe' || this.eventType === 'vertical-swipe') {
this.handleLinearSwipe(event);
}
};
@ -378,11 +371,7 @@ export class Touches {
const touches = (event as TouchEvent).touches;
if (touches) {
if (
(touches.length === 1 && !this.eventType) ||
this.eventType === 'horizontal-swipe' ||
this.eventType === 'vertical-swipe'
) {
if ((touches.length === 1 && !this.eventType) || this.eventType === 'horizontal-swipe' || this.eventType === 'vertical-swipe') {
return this.getLinearSwipeType(event);
}
} else {

View file

@ -5,7 +5,7 @@
<div class="demo">
<h2>transition-duration: 1000</h2>
<p>Defines the speed of the animation of positioning and transforming.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [transition-duration]="1000" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -17,7 +17,7 @@
<div class="demo">
<h2>limit-zoom: 2</h2>
<p>Limit the maximum available scale. By default, the maximum scale is calculated based on the original image size.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [limit-zoom]="2" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -29,7 +29,7 @@
<div class="demo">
<h2>minScale: 1</h2>
<p>Limit the minimum acceptable scale. With a value of 1, it is recommended to use this parameter with limitPan.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [minScale]="1" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -41,7 +41,7 @@
<div class="demo">
<h2>auto-zoom-out: true</h2>
<p>Automatic restoration of the original size of an image after its zooming in by two fingers.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [auto-zoom-out]="true" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -53,7 +53,7 @@
<div class="demo">
<h2>double-tap: false</h2>
<p>Zooming in and zooming out of an image, depending on its current condition, with double tap.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [double-tap]="false" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -65,7 +65,7 @@
<div class="demo">
<h2>double-tap-scale: 4</h2>
<p>Double tap scaling factor.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [double-tap-scale]="4" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -76,7 +76,7 @@
<!-- disabled -->
<div class="demo">
<h2>disabled: true</h2>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [disabled]="true" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -88,7 +88,7 @@
<div class="demo">
<h2>disablePan: true</h2>
<p>Turn off panning with one finger.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [disablePan]="true" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -100,7 +100,7 @@
<div class="demo">
<h2>minPanScale: 2</h2>
<p>Minimum zoom at which panning is enabled.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [minPanScale]="2" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -115,7 +115,7 @@
hidden - the overflow is clipped, and the rest of the content will be invisible. visible - the overflow is not clipped. The
content renders outside the element's box.
</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [overflow]="'visible'" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -130,7 +130,7 @@
Disable zoom controls. auto - Disable zoom controls on touch screen devices. never - show zoom controls on all devices. disable
- disable zoom controls on all devices.
</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [disableZoomControl]="'disable'" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -142,7 +142,7 @@
<div class="demo">
<h2>zoomControlScale: 2</h2>
<p>Zoom factor when using zoom controls.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [zoomControlScale]="2" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -154,7 +154,7 @@
<div class="demo">
<h2>backgroundColor: 'rgba(0,0,0,0.65)'</h2>
<p>The background color of the container.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [backgroundColor]="'rgba(0,0,0,0.65)'" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -166,7 +166,7 @@
<div class="demo">
<h2>limitPan: true</h2>
<p>Stop panning when the edge of the image reaches the edge of the screen.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [limitPan]="true" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -181,7 +181,7 @@
By default, subscriptions are made for mouse and touch screen events. The value auto means that the subscription will be only
for touch events or only for mouse events, depending on the type of screen.
</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [listeners]="'auto'" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -193,7 +193,7 @@
<div class="demo">
<h2>wheel: false</h2>
<p>Scale with the mouse wheel.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [wheel]="false" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -205,7 +205,7 @@
<div class="demo">
<h2>wheelZoomFactor: 0.5</h2>
<p>Zoom factor when zoomed in with the mouse wheel.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [wheelZoomFactor]="0.5" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -223,7 +223,7 @@
image, then specify the attributes width and height for the <img /> tag. When setting the property value to `true`, a
subscription to the window resize listener will be created.
</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [autoHeight]="true" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -237,7 +237,7 @@
<div class="demo">
<h2>draggableImage: true</h2>
<p>Sets the attribute draggable to the img tag.</p>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [draggableImage]="true" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
@ -248,7 +248,7 @@
<!-- draggableImage -->
<div class="demo">
<h2>draggableOnPinch: true</h2>
<p>Zoom State: {{this.zoomstate}}</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<pinch-zoom [draggableOnPinch]="true" (zoomChanged)="onZoomChanged($event)">
<img
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"

View file

@ -4,7 +4,7 @@ import { Component } from '@angular/core';
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.sass'],
standalone: false
standalone: false,
})
export class AppComponent {
title = 'ivypinchApp';

View file

@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import {PinchZoomComponent} from "ngx-pinch-zoom";
import { PinchZoomComponent } from 'ngx-pinch-zoom';
@NgModule({
declarations: [AppComponent],

View file

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />