{"_id":"@alekstar79/zoom-pan","name":"@alekstar79/zoom-pan","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@alekstar79/zoom-pan","version":"1.0.0","description":"High-performance 2D zoom and pan library with TypeScript and zero dependencies","keywords":["zoom","pan","canvas","transform","interactive","typescript","reactive","performance"],"author":{"name":"Aleksey Tarasenko","email":"alekstar79@yandex.ru"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/alekstar79/zoom-pan.git"},"bugs":{"url":"https://github.com/alekstar79/zoom-pan/issues"},"homepage":"https://github.com/alekstar79/zoom-pan#readme","type":"module","main":"./dist/index.umd.js","module":"./dist/index.esm.js","types":"./dist/index.d.ts","typesVersions":{"*":{"*":["./dist/index.d.ts"]}},"exports":{".":{"import":"./dist/index.esm.js","require":"./dist/index.umd.js","types":"./dist/index.d.ts"}},"scripts":{"build":"vite build && tsc --emitDeclarationOnly","build:min":"vite build && node minify.js","postbuild":"node minify.js","build:demo":"vite build --config vite.config.demo.ts","dev:demo":"vite --config vite.config.demo.ts","demo":"yarn dev:demo","test":"vitest run","test:unit":"vitest run tests/unit","test:integration":"vitest run tests/integration","test:e2e":"vitest run tests/e2e","test:watch":"vitest","test:coverage":"vitest run --coverage","test:ui":"vitest --ui","lint":"eslint src --ext .ts,.tsx","lint:fix":"eslint src --ext .ts,.tsx --fix","format":"prettier --write \"src/**/*.{ts,tsx,json,md}\"","type-check":"tsc --noEmit","prepublishOnly":"npm run build && npm run test"},"devDependencies":{"@types/node":"^20.10.0","@typescript-eslint/eslint-plugin":"^6.13.0","@typescript-eslint/parser":"^6.13.0","@vitest/coverage-v8":"^1.0.0","@vitest/ui":"^1.0.0","eslint":"^8.55.0","jsdom":"^27.2.0","prettier":"^3.1.0","terser":"^5.44.1","typescript":"^5.3.3","vite":"^5.0.7","vite-plugin-dts":"^4.5.4","vite-tsconfig-paths":"^5.1.4","vitest":"^1.0.4"},"engines":{"node":">=20.0.0"},"sideEffects":false,"browserslist":["last 2 Chrome versions","last 2 Firefox versions","last 2 Safari versions","last 2 Edge versions"],"_id":"@alekstar79/zoom-pan@1.0.0","gitHead":"309caddceb7817452ffd326df6ae70be94bceff9","_nodeVersion":"22.21.1","_npmVersion":"10.9.4","dist":{"integrity":"sha512-NPr0dX2A3joIfGLKE2WITjr+IEARUbhb3q2jQFnwtGAthDWvbwaax5k0Q7vZ0utxNWpSlfeGL6sK1QBnMREGXg==","shasum":"ff9eeb55cee5123745cac2d96d2c5a4f01a259e5","tarball":"https://registry.npmjs.org/@alekstar79/zoom-pan/-/zoom-pan-1.0.0.tgz","fileCount":7,"unpackedSize":45943,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCICK9ScRUNxIgte1Bdd6EcqkCjtTqWbXLCRaDoRUMnWfUAiBuMadoW++GwNp+c84mvc2jlbcSf5pMj/db3ak2VyUbIg=="}]},"_npmUser":{"name":"alekstar79","email":"alekstar79@yandex.ru"},"directories":{},"maintainers":[{"name":"alekstar79","email":"alekstar79@yandex.ru"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/zoom-pan_1.0.0_1764357050994_0.16453710280093792"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-28T19:10:50.936Z","1.0.0":"2025-11-28T19:10:51.181Z","modified":"2025-11-28T19:10:51.508Z"},"maintainers":[{"name":"alekstar79","email":"alekstar79@yandex.ru"}],"description":"High-performance 2D zoom and pan library with TypeScript and zero dependencies","homepage":"https://github.com/alekstar79/zoom-pan#readme","keywords":["zoom","pan","canvas","transform","interactive","typescript","reactive","performance"],"repository":{"type":"git","url":"git+https://github.com/alekstar79/zoom-pan.git"},"author":{"name":"Aleksey Tarasenko","email":"alekstar79@yandex.ru"},"bugs":{"url":"https://github.com/alekstar79/zoom-pan/issues"},"license":"MIT","readme":"# Zoom Pan\n\n[![NPM](https://img.shields.io/npm/v/@alekstar79/zoom-pan.svg)](https://www.npmjs.com/package/@alekstar79/zoom-pan)\n[![GitHub repo](https://img.shields.io/badge/github-repo-green.svg?style=flat)](https://github.com/alekstar79/zoom-pan)\n[![Typescript](https://img.shields.io/badge/TypeScript-Ready-blue?logo=typescript)]()\n[![License](https://img.shields.io/badge/License-MIT-green)]()\n[![Version](https://img.shields.io/badge/Version-1.0.0-orange)]()\n[![Coverage](https://img.shields.io/badge/Coverage-90%25-green)]()\n\nA high-performance, TypeScript-first library for 2D zoom and pan transformations in the browser.\n\n![Zoom Pan](banner.svg)\n\n<!-- TOC -->\n* [Zoom Pan](#zoom-pan)\n  * [✨ Features](#-features)\n  * [📥 Installation](#-installation)\n    * [npm](#npm)\n    * [yarn](#yarn)\n    * [pnpm](#pnpm)\n  * [🚀 Quick Start](#-quick-start)\n    * [Basic Usage (Helper Function)](#basic-usage-helper-function)\n    * [Default Key Bindings](#default-key-bindings)\n    * [Hook Usage (Fine-grained Control)](#hook-usage-fine-grained-control)\n    * [Advanced Usage (Class-based)](#advanced-usage-class-based)\n  * [📚 API Documentation](#-api-documentation)\n    * [`zoomPan(config: IZoomPanHelperConfig): IZoomPanInstance`](#zoompanconfig-izoompanhelperconfig-izoompaninstance)\n    * [`useZoomPan(config: IUseZoomPanConfig): IZoomPanAPI`](#usezoompanconfig-iusezoompanconfig-izoompanapi)\n    * [`Renderer` Class](#renderer-class)\n  * [🎯 Common Patterns](#-common-patterns)\n    * [Center Zoom](#center-zoom)\n    * [Fit to Bounds](#fit-to-bounds)\n    * [Animation](#animation)\n  * [🧪 Testing](#-testing)\n  * [📦 Build Targets](#-build-targets)\n  * [🎨 Styling](#-styling)\n  * [🌍 Browser Support](#-browser-support)\n  * [🤝 Contributing](#-contributing)\n  * [📄 License](#-license)\n  * [📞 Support](#-support)\n<!-- TOC -->\n\n## ✨ Features\n\n- **🚀 High Performance**: Batches DOM updates using requestAnimationFrame for smooth 60fps interactions\n- **📦 Fully Typed**: Written in TypeScript with strict type checking and full type exports\n- **🔍 Zero Dependencies**: Lightweight (~5KB minified) with no external dependencies\n- **♻️ Reactive State Management**: Built-in Watcher system for reactive transformation tracking\n- **🎯 Flexible API**: Three levels of abstraction - Renderer (core), useZoomPan (hook), zoomPan (helper)\n- **⚙️ Configurable**: Full control over zoom constraints, sensitivity, and behavior\n- **🧪 Well Tested**: Comprehensive test coverage with Vitest\n- **♿ Accessible**: Supports keyboard controls and modifier keys\n- **🌍 Cross-browser**: Works on all modern browsers and has fallbacks for older APIs\n\n## 📥 Installation\n\n### npm\n\n```bash\nnpm install zoom-pan\n```\n\n### yarn\n\n```bash\nyarn add zoom-pan\n```\n\n### pnpm\n\n```bash\npnpm add zoom-pan\n```\n\n## 🚀 Quick Start\n\n### Basic Usage (Helper Function)\n\nThe simplest way to get started - just one function call with default event bindings:\n\n```typescript\nimport { zoomPan } from 'zoom-pan';\n\nconst element = document.getElementById('canvas');\n\n// Activate zoom/pan with default controls\nconst { api, destroy } = zoomPan({\n  element,\n  minScale: 0.5,\n  maxScale: 5,\n  scaleSensitivity: 50,\n});\n\n// Programmatic control\napi.zoom({ x: 100, y: 100, deltaScale: 1 }); // Zoom in at point (100, 100)\napi.panBy({ movementX: 50, movementY: 50 }); // Pan by offset\napi.panTo({ x: 200, y: 200, scale: 2 });     // Pan to position with scale\n\n// Cleanup\ndestroy();\n```\n\n### Default Key Bindings\n\n- **Zoom**: Ctrl + Mouse Wheel\n- **Pan**: Shift + Left Mouse Drag\n\n### Hook Usage (Fine-grained Control)\n\nFor more control over event handling:\n\n```typescript\nimport { useZoomPan } from 'zoom-pan';\n\nconst element = document.getElementById('canvas');\n\nconst { zoom, panBy, panTo } = useZoomPan({\n  element,\n  minScale: 0.1,\n  maxScale: 30,\n  scaleSensitivity: 50,\n});\n\n// Attach custom event listeners\nelement.addEventListener('wheel', (event) => {\n  if (event.ctrlKey) {\n    event.preventDefault();\n    zoom(event);\n  }\n});\n\nelement.addEventListener('mousemove', (event) => {\n  if (event.shiftKey && event.buttons === 1) {\n    panBy(event);\n  }\n});\n```\n\n### Advanced Usage (Class-based)\n\nFor complex scenarios with state tracking:\n\n```typescript\nimport { Renderer } from 'zoom-pan';\n\nconst element = document.getElementById('canvas');\n\n// Create renderer with optional Watcher integration\nconst renderer = new Renderer({\n  minScale: 0.1,\n  maxScale: 30,\n  scaleSensitivity: 50,\n});\n\n// Access transformation state\nconst state = renderer.getState();\nconsole.log(state.scale, state.translateX, state.translateY);\n\n// Track state changes reactively\nif (renderer instanceof Renderer) {\n  // State is automatically managed\n}\n\n// Apply transformations\nrenderer.zoom({\n  element,\n  x: 250,\n  y: 250,\n  deltaScale: 1,\n});\n\nrenderer.panBy({\n  element,\n  originX: 50,\n  originY: 50,\n});\n\nrenderer.reset();\nrenderer.destroy();\n```\n\n## 📚 API Documentation\n\n### `zoomPan(config: IZoomPanHelperConfig): IZoomPanInstance`\n\nSets up zoom/pan with automatic event binding.\n\n**Parameters:**\n\n```typescript\ninterface IZoomPanHelperConfig {\n  element: HTMLElement;           // DOM element to zoom/pan\n  minScale?: number;              // Minimum zoom level (default: 0.1)\n  maxScale?: number;              // Maximum zoom level (default: 30)\n  scaleSensitivity?: number;      // Zoom sensitivity (default: 50)\n  enableZoom?: boolean;           // Enable zoom (default: true)\n  enablePan?: boolean;            // Enable pan (default: true)\n  enableMouseWheel?: boolean;     // Allow zoom without Ctrl (default: false)\n}\n```\n\n**Returns:**\n\n```typescript\ninterface IZoomPanInstance {\n  api: {\n    zoom(event: WheelEvent | { x, y, deltaScale }): void;\n    panBy(event: MouseEvent | { movementX, movementY }): void;\n    panTo(params: { x, y, scale? }): void;\n  };\n  destroy(): void;  // Cleanup all event listeners\n}\n```\n\n**Example:**\n\n```typescript\nconst { api, destroy } = zoomPan({ element });\n\n// Programmatic zoom at center\napi.zoom({\n  x: element.clientWidth / 2,\n  y: element.clientHeight / 2,\n  deltaScale: 1,\n});\n\n// Cleanup before unmounting\ndestroy();\n```\n\n### `useZoomPan(config: IUseZoomPanConfig): IZoomPanAPI`\n\nReturns zoom/pan methods without automatic event binding (for custom controls).\n\n**Example:**\n\n```typescript\nconst { zoom, panBy, panTo } = useZoomPan({ element });\n\n// Custom keybindings\ndocument.addEventListener('keydown', (e) => {\n  if (e.key === 'ArrowUp') {\n    panBy({ movementX: 0, movementY: -10 });\n  }\n});\n```\n\n### `Renderer` Class\n\nCore transformation engine. Advanced usage for custom implementations.\n\n**Constructor:**\n\n```typescript\nconst renderer = new Renderer(\n  {\n    minScale: 0.1,\n    maxScale: 30,\n    scaleSensitivity: 50,\n  }\n);\n```\n\n**Methods:**\n\n```typescript\n// Zoom at a specific point\nrenderer.zoom({\n  element,\n  x: number,\n  y: number,\n  deltaScale: number, // -1 to 1\n});\n\n// Pan by offset\nrenderer.panBy({\n  element,\n  originX: number,\n  originY: number,\n});\n\n// Pan to absolute position\nrenderer.panTo({\n  element,\n  originX: number,\n  originY: number,\n  scale?: number,\n});\n\n// Get current state\nconst state = renderer.getState();\n// {\n//   scale: number,\n//   translateX: number,\n//   translateY: number,\n//   originX: number,\n//   originY: number,\n// }\n\n// Reset to initial state\nrenderer.reset();\n\n// Check if active\nrenderer.isActive(): boolean;\n\n// Cleanup\nrenderer.destroy();\n```\n\n## 🎯 Common Patterns\n\n### Center Zoom\n\n```typescript\nconst { api } = zoomPan({ element });\n\nfunction zoomToCenter(deltaScale: number) {\n  const center = {\n    x: element.clientWidth / 2,\n    y: element.clientHeight / 2,\n  };\n  api.zoom({\n    x: center.x,\n    y: center.y,\n    deltaScale,\n  });\n}\n\nzoomToCenter(1); // Zoom in at center\n```\n\n### Fit to Bounds\n\n```typescript\nimport { Renderer } from 'zoom-pan';\n\nconst renderer = new Renderer({ minScale: 0.1, maxScale: 30 });\n\nfunction fitBounds(bounds: { x, y, width, height }) {\n  const element = document.getElementById('canvas');\n  const scale = Math.min(\n    element.clientWidth / bounds.width,\n    element.clientHeight / bounds.height\n  );\n\n  renderer.panTo({\n    element,\n    originX: bounds.x,\n    originY: bounds.y,\n    scale,\n  });\n}\n```\n\n### Animation\n\n```typescript\nimport { zoomPan } from 'zoom-pan';\n\nconst { api } = zoomPan({ element });\n\nfunction animateZoom(targetScale: number, duration: number) {\n  const startTime = Date.now();\n  const startState = { scale: 1 }; // Get from renderer if needed\n\n  function animate() {\n    const elapsed = Date.now() - startTime;\n    const progress = Math.min(elapsed / duration, 1);\n    \n    const scale = startState.scale + (targetScale - startState.scale) * progress;\n    \n    if (progress < 1) {\n      requestAnimationFrame(animate);\n    }\n  }\n\n  animate();\n}\n```\n\n## 🧪 Testing\n\nRun tests with coverage:\n\n```bash\nnpm test\nnpm run test:coverage\n```\n\nExample test:\n\n```typescript\nimport { describe, it, expect } from 'vitest';\nimport { Renderer } from 'zoom-pan';\n\ndescribe('Renderer', () => {\n  it('should zoom in', () => {\n    const renderer = new Renderer({\n      minScale: 0.1,\n      maxScale: 10,\n    }, false);\n\n    const element = document.createElement('div');\n    renderer.zoom({ element, x: 250, y: 250, deltaScale: 1 });\n\n    const state = renderer.getState();\n    expect(state.scale).toBeGreaterThan(1);\n  });\n});\n```\n\n## 📦 Build Targets\n\n- **ES Module** (.mjs): For modern bundlers and direct import\n- **UMD** (.umd.js): For browser `<script>` tags and older bundlers\n- **TypeScript Declarations**: Full .d.ts and .d.ts.map files\n\n## 🎨 Styling\n\nThe library doesn't apply any CSS. Apply your own:\n\n```css\n/* Smooth transitions */\n#canvas {\n  transition: transform 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);\n}\n\n/* Visual feedback */\n#canvas:active {\n  cursor: grabbing;\n}\n\n#canvas {\n  cursor: grab;\n}\n```\n\n## 🌍 Browser Support\n\n- Chrome/Edge 90+\n- Firefox 88+\n- Safari 14+\n- Mobile browsers (iOS Safari 14+, Chrome Android)\n\n## 🤝 Contributing\n\nContributions welcome! Please:\n\n1. Fork the repository\n2. Create a feature branch\n3. Add tests for new functionality\n4. Submit a pull request\n\n## 📄 License\n\nMIT - See LICENSE file for details\n\n## 📞 Support\n\nFor issues and questions:\n- [GitHub Issues](https://github.com/alekstar79/zoom-pan/issues)\n- [Discussions](https://github.com/alekstar79/zoom-pan/discussions)\n\n---\n","readmeFilename":"README.md","_rev":"1-3964aeea01fc2ff4acba180d9b148818"}