docs: Update documentation and modernize example app

Documentation updates:
- Corrected library README to reflect IvyPinchService and TouchesService are Angular services, not utility classes
- Updated CHANGELOG to accurately describe service architecture
- All services now correctly documented as using Angular's DI system

Example app modernization:
- Converted AppComponent to standalone component (removed NgModule)
- Updated to use Angular 20 signals (signal() for state)
- Updated main.ts to use bootstrapApplication instead of bootstrapModule
- Fixed template to call signal values as functions
- Added JSDoc documentation

All features demonstrated:
- Click-to-zoom functionality
- Brightness control
- Combined workflows (click-to-zoom + brightness)
- All configuration options
- Programmatic control methods
This commit is contained in:
Claude 2025-11-16 10:03:44 +00:00
parent f4d8717cc9
commit 27bd5d7326
No known key found for this signature in database
5 changed files with 61 additions and 49 deletions

View file

@ -148,11 +148,11 @@ For users upgrading from previous versions:
```
lib/
├── models/ # All data models and interfaces
├── services/ # Business logic and utility classes
│ ├── brightness.service.ts # Angular service
│ ├── zoom-state.service.ts # Angular service
│ ├── ivy-pinch.service.ts # Core zoom logic (utility class)
│ └── touches.service.ts # Gesture detection (utility class)
├── services/ # Business logic (all Angular services)
│ ├── brightness.service.ts # Angular service for brightness state management
│ ├── zoom-state.service.ts # Angular service for zoom state management
│ ├── ivy-pinch.service.ts # Angular service for core zoom/pan engine
│ └── touches.service.ts # Angular service for gesture detection
└── components/
├── containers/ # Smart components (with DI)
└── presentational/ # Dumb components (pure UI)

View file

@ -85,8 +85,8 @@ lib/
├── services/ # Business logic
│ ├── brightness.service.ts # Angular service for brightness state
│ ├── zoom-state.service.ts # Angular service for zoom state
│ ├── ivy-pinch.service.ts # Core zoom/pan engine (utility class)
│ └── touches.service.ts # Gesture detection (utility class)
│ ├── ivy-pinch.service.ts # Angular service for core zoom/pan engine
│ └── touches.service.ts # Angular service for gesture detection
└── components/
├── containers/ # Smart components (with DI)
@ -109,9 +109,10 @@ lib/
**Service-Based Architecture**
- Business logic extracted into reusable services
- `BrightnessService` and `ZoomStateService` use Angular's DI system
- Core zoom logic (`IvyPinch`, `Touches`) are utility classes instantiated with `new`
- All business logic extracted into reusable Angular services
- `BrightnessService` and `ZoomStateService` manage reactive state
- `IvyPinchService` and `TouchesService` are Angular services handling core zoom/gesture logic
- All services use Angular's DI system and are provided at component level
**Signal-Based Reactivity**

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: {{ zoomstate() }}</p>
<pinch-zoom [transitionDuration]="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: {{ zoomstate() }}</p>
<pinch-zoom [limitZoom]="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: {{ 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: {{ zoomstate() }}</p>
<pinch-zoom [autoZoomOut]="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: {{ zoomstate() }}</p>
<pinch-zoom [doubleTap]="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: {{ zoomstate() }}</p>
<pinch-zoom [doubleTapScale]="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: {{ 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: {{ 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: {{ 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: {{ 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: {{ 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: {{ 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: {{ 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: {{ 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: {{ 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: {{ 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: {{ 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: {{ 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: {{ 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: {{ 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"
@ -263,7 +263,7 @@
Click any point on the image to zoom to that exact location. Perfect for anomaly detection and defect inspection. Click again to
zoom out.
</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<p>Zoom State: {{ zoomstate() }}</p>
<pinch-zoom [enableClickToZoom]="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"
@ -275,7 +275,7 @@
<div class="demo">
<h2>clickToZoomScale: 3.5</h2>
<p>Target zoom level when clicking to zoom. Default is 2.5x.</p>
<p>Zoom State: {{ this.zoomstate }}</p>
<p>Zoom State: {{ zoomstate() }}</p>
<pinch-zoom [enableClickToZoom]="true" [clickToZoomScale]="3.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"
@ -290,7 +290,7 @@
Enable brightness controls alongside zoom controls. Perfect for brightening dark images or examining details in shadows.
Controls appear at the bottom center of the image.
</p>
<p>Brightness State: {{ this.brightnessstate.toFixed(2) }}</p>
<p>Brightness State: {{ brightnessstate().toFixed(2) }}</p>
<pinch-zoom [enableBrightnessControl]="true" (brightnessChanged)="onBrightnessChanged($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"
@ -302,7 +302,7 @@
<div class="demo">
<h2>brightnessStep: 0.2</h2>
<p>Brightness adjustment increment. Larger steps for faster adjustments. Default is 0.1.</p>
<p>Brightness State: {{ this.brightnessstate.toFixed(2) }}</p>
<p>Brightness State: {{ brightnessstate().toFixed(2) }}</p>
<pinch-zoom [enableBrightnessControl]="true" [brightnessStep]="0.2" (brightnessChanged)="onBrightnessChanged($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"
@ -314,7 +314,7 @@
<div class="demo">
<h2>minBrightness: 0.5, maxBrightness: 1.5</h2>
<p>Limit the brightness range. Default range is 0.1 to 2.0.</p>
<p>Brightness State: {{ this.brightnessstate.toFixed(2) }}</p>
<p>Brightness State: {{ brightnessstate().toFixed(2) }}</p>
<pinch-zoom
[enableBrightnessControl]="true"
[minBrightness]="0.5"
@ -334,7 +334,7 @@
Perfect for defect inspection and anomaly detection workflows. Click to zoom in on suspicious areas, adjust brightness to see
details.
</p>
<p>Zoom State: {{ this.zoomstate }} | Brightness State: {{ this.brightnessstate.toFixed(2) }}</p>
<p>Zoom State: {{ zoomstate() }} | Brightness State: {{ brightnessstate().toFixed(2) }}</p>
<pinch-zoom
[enableClickToZoom]="true"
[clickToZoomScale]="2.5"

View file

@ -1,21 +1,29 @@
import { Component } from '@angular/core';
import { Component, signal } from '@angular/core';
import { PinchZoomComponent } from 'ngx-pinch-zoom';
/**
* Example app demonstrating all ngx-pinch-zoom features.
* Uses Angular 20 standalone component with signals.
*/
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.sass'],
standalone: false,
standalone: true,
imports: [PinchZoomComponent],
})
export class AppComponent {
title = 'ivypinchApp';
public zoomstate = 1;
public brightnessstate = 1.0;
onZoomChanged(zoom: number) {
this.zoomstate = zoom;
// Use signals for reactive state management
zoomstate = signal(1);
brightnessstate = signal(1.0);
onZoomChanged(zoom: number): void {
this.zoomstate.set(zoom);
}
onBrightnessChanged(brightness: number) {
this.brightnessstate = brightness;
onBrightnessChanged(brightness: number): void {
this.brightnessstate.set(brightness);
}
}

View file

@ -1,13 +1,16 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppModule } from './app/app.module';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
bootstrapApplication(AppComponent, {
providers: [
provideRouter([]), // Empty routes for now
],
}).catch((err) => console.error(err));